import { View, ScrollView } from "@tarojs/components"; import NavBarNormal from "@/components/NavBarNormal/index"; import PageCustom from "@/components/page-custom/index"; import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro"; import ChatMessage from "@/components/chat-message"; import InputBar from "./components/input-bar"; import { useEffect, useState, useRef, useMemo } from "react"; import { useTextChat } from "@/store/textChat"; import { formatMessageTime } from "@/utils/timeUtils"; import { formatMessageItem } from "@/utils/messageUtils"; import { TRobotMessage, TMessage, TAnyMessage, } from "@/types/bot"; import ChatGreeting from "./components/ChatGreeting"; import IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24"; import IconArrowLeft from "@/components/icon/icon-arrow-left"; import PersonalCard from "./components/personal-card"; import ButtonEnableStreamVoice from "./components/OptionButtons/ButtonEnableStreamVoice"; import { useAgentStore } from "@/store/agentStore"; import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite"; import { getMessageHistories } from "@/service/chat"; import RecommendQuestions from "./components/RecommendQuestions"; import { useKeyboard } from "./components/keyboard"; import { useAppStore } from "@/store/appStore"; import { usePersistentState } from '@/hooks/usePersistentState'; export default function Index() { const router = useRouter(); const { agentId, isVisitor } = router.params; if (!agentId) { return 没有相应的智能体; } const { fetchAgent, fetchAgentProfile } = useAgentStore(); const bottomSafeHeight = useAppStore( state => state.bottomSafeHeight) const agent = useAgentStore((state) => { if (isVisitor === "true") { return state.agentProfile; } return state.agent; }); const scrollViewRef = useRef(null); const messageList = useTextChat((state) => state.list); const [inputContainerHeight,setInputContainerHeight]= useState(0) const [streamVoiceEnable, setStreamVoiceEnable]= usePersistentState('streamVoiceEnable', false) const { keyboardHeight, marginTopOffset, triggerHeightUpdate } = useKeyboard( scrollViewRef, "#messageList", "#scrollView" ); // 输入框容器 // 针对没有 safeArea?.bottom 的手机,需要额外增加 12 高度 let inputContainerBottomOffset = 0 if(bottomSafeHeight <= 0){ inputContainerBottomOffset = 12 } const { destroy, setScrollTop, genSessionId, setAutoScroll } = useTextChat(); const scrollTop = useTextChat((state) => state.scrollTop); // 放在组件里 const initialScrolledRef = useRef(false); const fetcher = async ([_url, { nextId, pageSize }]) => { const _nextId = nextId ? decodeURIComponent(nextId) : nextId; const res = await getMessageHistories({ agentId, startId: _nextId, pageSize, }); return res.data; }; // 获取历史聊天记录 const { list, loadMore, pageIndex, mutate } = useLoadMoreInfinite< TMessage[] | TRobotMessage[] >(createKey(`messeagehistories${isVisitor}${agentId}`), fetcher); // 解析消息体 content const parsedList = list.filter((item)=> !!item.content.length).map(formatMessageItem); // 1. 按 sessionId 分组,并记录每组的最早时间 const resultMap = useMemo(() => { const allMessages = [...[...parsedList].reverse(), ...messageList]; return allMessages.reduce((acc, item) => { const { sessionId, msgTime } = item; if (!sessionId || !msgTime) { return acc; } let _msgTime = msgTime.replace(/\-/g, "/"); if (!acc[sessionId]) { acc[sessionId] = { dt: _msgTime, // 初始化当前组的最早时间 list: [item], // 初始化当前组的记录列表 }; } else { // 更新最早时间(如果当前记录的 msgTime 更早) if (new Date(_msgTime) < new Date(acc[sessionId].dt)) { acc[sessionId].dt = msgTime; console.log('yoyoyo') } // 将记录添加到当前组 acc[sessionId].list.push(item); } return acc; }, {}) as { dt: string; list: TAnyMessage[]; }[]; }, [parsedList, messageList]); // 2. 转换为最终数组格式 const result = Object.values(resultMap); const messagesLength = useMemo(() => result.length, [result.length]); const prevLengthRef = useRef(messagesLength); const [showWelcome, setShowWelcome] = useState(!list.length); const haveBg = (!!agent?.enabledChatBg && !!agent.avatarUrl?.length) // 加载更多 const onScrollToUpper = () => { console.log("onscroll"); loadMore(); }; const handleTouchMove = () => { console.log("set auto scroll false"); setAutoScroll(false); }; useDidShow(() => { mutate(); }); useEffect(() => { if (agentId) { if (isVisitor) { fetchAgentProfile(agentId); } else { fetchAgent(agentId); } } }, [agentId, isVisitor]); // 是否显示欢迎 ui useEffect(() => { setShowWelcome(!messageList.length && !list.length); }, [list, messageList]); // 首次进入界面滚动到底 // 已有:messagesLength 是一个 number(来源于 result.length) useEffect(() => { // 仅首次 pageIndex === 1 且已经有消息时滚动一次 if (pageIndex === 1 && messagesLength > 0 && !initialScrolledRef.current) { initialScrolledRef.current = true; // 下1秒再滚,确保 DOM 已完成渲染 setTimeout(() => setScrollTop(), 1000); } }, [pageIndex, messagesLength]); // 首次进入聊天生成 session id useEffect(() => { genSessionId(); }, []); // 监听消息列表变化,触发键盘高度重新计算 useEffect(() => { // 只在长度真正变化时才触发 if (prevLengthRef.current !== messagesLength) { prevLengthRef.current = messagesLength; // 使用 setTimeout 确保 DOM 更新完成后再计算高度 const timer = setTimeout(() => { triggerHeightUpdate(); }, 100); return () => clearTimeout(timer); } }, [messagesLength, triggerHeightUpdate]); useUnload(() => { destroy(); }); const renderNavLeft = () => { return ( Taro.navigateBack()} > {haveBg ? : } ); }; // 大背景可以是视频,也可以是图片 const getBgContent = () => { if (!agent?.avatarUrl || !!!agent?.enabledChatBg) { return ""; } return agent?.avatarUrl; }; useEffect(() => { if(haveBg){ Taro.setNavigationBarColor({ frontColor: "#ffffff", backgroundColor: "transparent", }); } return () => { Taro.setNavigationBarColor({ frontColor: "#000000", backgroundColor: "transparent", }); }; }, [haveBg]); useEffect(()=> { const query = Taro.createSelectorQuery(); // 输入框高度 query .select('#inputContainer') .boundingClientRect((rect: any) => { if (rect) { // console.log('ScrollView height:', rect.height) setInputContainerHeight(rect.height - bottomSafeHeight); } }) .exec(); }, [agent]) return ( {/* <>{`${scrollTop}`}--{autoScroll ? 'true': 'false'} */} setAutoScroll(true)} > {showWelcome && } {result.map((group) => { return ( <> {formatMessageTime(group.dt)} {group.list.map((message) => { const reasoningContent = (message as any).reasoningContent || ""; return ( ); })} ); })} {agent && } {agent && } ); }