|
@@ -1,4 +1,4 @@
|
|
|
-import { View, ScrollView, Video } from "@tarojs/components";
|
|
|
|
|
|
|
+import { View, ScrollView } from "@tarojs/components";
|
|
|
import NavBarNormal from "@/components/NavBarNormal/index";
|
|
import NavBarNormal from "@/components/NavBarNormal/index";
|
|
|
import PageCustom from "@/components/page-custom/index";
|
|
import PageCustom from "@/components/page-custom/index";
|
|
|
import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro";
|
|
import Taro, { useDidShow, useRouter, useUnload } from "@tarojs/taro";
|
|
@@ -6,7 +6,14 @@ import ChatMessage from "@/components/chat-message";
|
|
|
import InputBar from "./components/input-bar";
|
|
import InputBar from "./components/input-bar";
|
|
|
import { useEffect, useState, useRef, useMemo } from "react";
|
|
import { useEffect, useState, useRef, useMemo } from "react";
|
|
|
import { useTextChat } from "@/store/textChat";
|
|
import { useTextChat } from "@/store/textChat";
|
|
|
-import { TRobotMessage, TMessage, EContentType, EChatRole } from "@/types/bot";
|
|
|
|
|
|
|
+import { formatMessageTime } from "@/utils/timeUtils";
|
|
|
|
|
+import { formatMessageItem } from "@/utils/messageUtils";
|
|
|
|
|
+import {
|
|
|
|
|
+ TRobotMessage,
|
|
|
|
|
+ TMessage,
|
|
|
|
|
+ EContentType,
|
|
|
|
|
+ TAnyMessage,
|
|
|
|
|
+} from "@/types/bot";
|
|
|
|
|
|
|
|
import ChatGreeting from "./components/ChatGreeting";
|
|
import ChatGreeting from "./components/ChatGreeting";
|
|
|
import IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24";
|
|
import IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24";
|
|
@@ -16,43 +23,39 @@ import { useAgentStore } from "@/store/agentStore";
|
|
|
import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
|
|
import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
|
|
|
import { getMessageHistories } from "@/service/bot";
|
|
import { getMessageHistories } from "@/service/bot";
|
|
|
|
|
|
|
|
-
|
|
|
|
|
-import RecommendQuestions from './components/RecommendQuestions'
|
|
|
|
|
-import {useKeyboard} from './components/keyboard'
|
|
|
|
|
-import { saveMessageToServer } from "./components/input-bar/message";
|
|
|
|
|
-import { generateUUID, getLoginId } from '@/utils/index'
|
|
|
|
|
-
|
|
|
|
|
|
|
+import RecommendQuestions from "./components/RecommendQuestions";
|
|
|
|
|
+import { useKeyboard } from "./components/keyboard";
|
|
|
|
|
|
|
|
export default function Index() {
|
|
export default function Index() {
|
|
|
const router = useRouter();
|
|
const router = useRouter();
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
const { agentId, isVisitor } = router.params;
|
|
const { agentId, isVisitor } = router.params;
|
|
|
if (!agentId) {
|
|
if (!agentId) {
|
|
|
return <View>没有相应的智能体</View>;
|
|
return <View>没有相应的智能体</View>;
|
|
|
}
|
|
}
|
|
|
-
|
|
|
|
|
- const { fetchAgent, fetchAgentProfile, } = useAgentStore()
|
|
|
|
|
-
|
|
|
|
|
-const agent = useAgentStore((state) => {
|
|
|
|
|
- if(isVisitor === 'true'){
|
|
|
|
|
- return state.agentProfile
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const { fetchAgent, fetchAgentProfile } = useAgentStore();
|
|
|
|
|
+
|
|
|
|
|
+ const agent = useAgentStore((state) => {
|
|
|
|
|
+ if (isVisitor === "true") {
|
|
|
|
|
+ return state.agentProfile;
|
|
|
}
|
|
}
|
|
|
- return state.agent
|
|
|
|
|
|
|
+ return state.agent;
|
|
|
});
|
|
});
|
|
|
const scrollViewRef = useRef<any>(null);
|
|
const scrollViewRef = useRef<any>(null);
|
|
|
const messageList = useTextChat((state) => state.list);
|
|
const messageList = useTextChat((state) => state.list);
|
|
|
-
|
|
|
|
|
- const {keyboardHeight, marginTopOffset, triggerHeightUpdate,} = useKeyboard(scrollViewRef, '#messageList', '#scrollView')
|
|
|
|
|
-
|
|
|
|
|
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const { keyboardHeight, marginTopOffset, triggerHeightUpdate } = useKeyboard(
|
|
|
|
|
+ scrollViewRef,
|
|
|
|
|
+ "#messageList",
|
|
|
|
|
+ "#scrollView"
|
|
|
|
|
+ );
|
|
|
|
|
|
|
|
-
|
|
|
|
|
const { destroy, setScrollTop, genSessionId, setAutoScroll } = useTextChat();
|
|
const { destroy, setScrollTop, genSessionId, setAutoScroll } = useTextChat();
|
|
|
const scrollTop = useTextChat((state) => state.scrollTop);
|
|
const scrollTop = useTextChat((state) => state.scrollTop);
|
|
|
// const autoScroll = useTextChat((state) => state.autoScroll);
|
|
// const autoScroll = useTextChat((state) => state.autoScroll);
|
|
|
-
|
|
|
|
|
- const fetcher = async ([_url,{ nextId, pageSize}]) => {
|
|
|
|
|
|
|
+
|
|
|
|
|
+ const fetcher = async ([_url, { nextId, pageSize }]) => {
|
|
|
const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
|
|
const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
|
|
|
const res = await getMessageHistories({
|
|
const res = await getMessageHistories({
|
|
|
agentId,
|
|
agentId,
|
|
@@ -63,27 +66,46 @@ const agent = useAgentStore((state) => {
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
// 获取历史聊天记录
|
|
// 获取历史聊天记录
|
|
|
- const { list, loadMore, pageIndex, mutate } = useLoadMoreInfinite<TMessage[]|TRobotMessage[]>(
|
|
|
|
|
- createKey(`messeagehistories${isVisitor}${agentId}`),
|
|
|
|
|
- fetcher,
|
|
|
|
|
- );
|
|
|
|
|
|
|
+ const { list, loadMore, pageIndex, mutate } = useLoadMoreInfinite<
|
|
|
|
|
+ TMessage[] | TRobotMessage[]
|
|
|
|
|
+ >(createKey(`messeagehistories${isVisitor}${agentId}`), fetcher);
|
|
|
|
|
|
|
|
- const parsedList = list.map((item: TMessage|TRobotMessage) => {
|
|
|
|
|
- if(item.contentType == EContentType.AiseekQA){
|
|
|
|
|
- try{
|
|
|
|
|
- const contentJson = JSON.parse(item.content as string)
|
|
|
|
|
- item.content = contentJson.answer.text
|
|
|
|
|
- // 把消息详情放入统一 body 中
|
|
|
|
|
- item.body = {...item, content: contentJson, contentType: EContentType.AiseekQA}
|
|
|
|
|
- }catch(e){
|
|
|
|
|
- // console.error(e)
|
|
|
|
|
|
|
+ // 解析消息体 content
|
|
|
|
|
+ const parsedList = list.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;
|
|
|
}
|
|
}
|
|
|
- }
|
|
|
|
|
- return item
|
|
|
|
|
- })
|
|
|
|
|
-
|
|
|
|
|
- const allMessages = useMemo(()=> [...[...parsedList].reverse(), ...messageList], [parsedList, messageList])
|
|
|
|
|
- const messagesLength = useMemo(() => allMessages.length, [allMessages.length]);
|
|
|
|
|
|
|
+ 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 prevLengthRef = useRef(messagesLength);
|
|
|
|
|
|
|
|
const [showWelcome, setShowWelcome] = useState(!list.length);
|
|
const [showWelcome, setShowWelcome] = useState(!list.length);
|
|
@@ -94,21 +116,20 @@ const agent = useAgentStore((state) => {
|
|
|
loadMore();
|
|
loadMore();
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
- const handleTouchMove = ()=> {
|
|
|
|
|
- console.log('set auto scroll false')
|
|
|
|
|
- setAutoScroll(false)
|
|
|
|
|
- }
|
|
|
|
|
-
|
|
|
|
|
|
|
+ const handleTouchMove = () => {
|
|
|
|
|
+ console.log("set auto scroll false");
|
|
|
|
|
+ setAutoScroll(false);
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- useDidShow(()=> {
|
|
|
|
|
- mutate(undefined,{revalidate: true})
|
|
|
|
|
- })
|
|
|
|
|
|
|
+ useDidShow(() => {
|
|
|
|
|
+ mutate();
|
|
|
|
|
+ });
|
|
|
|
|
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
- if(agentId){
|
|
|
|
|
- if(isVisitor){
|
|
|
|
|
- fetchAgentProfile(agentId)
|
|
|
|
|
- }else{
|
|
|
|
|
|
|
+ if (agentId) {
|
|
|
|
|
+ if (isVisitor) {
|
|
|
|
|
+ fetchAgentProfile(agentId);
|
|
|
|
|
+ } else {
|
|
|
fetchAgent(agentId);
|
|
fetchAgent(agentId);
|
|
|
}
|
|
}
|
|
|
}
|
|
}
|
|
@@ -119,32 +140,6 @@ const agent = useAgentStore((state) => {
|
|
|
setShowWelcome(!messageList.length && !list.length);
|
|
setShowWelcome(!messageList.length && !list.length);
|
|
|
}, [list, messageList]);
|
|
}, [list, messageList]);
|
|
|
|
|
|
|
|
-
|
|
|
|
|
- // 将 greeting 开场白存入聊天记录里
|
|
|
|
|
- // useEffect(()=> {
|
|
|
|
|
- // console.log(showWelcome, agent)
|
|
|
|
|
- // if(!showWelcome){
|
|
|
|
|
- // return;
|
|
|
|
|
- // }
|
|
|
|
|
- // const loginId = getLoginId();
|
|
|
|
|
- // if (!loginId || !agent?.agentId || !agent?.greeting || !sessionId) {
|
|
|
|
|
- // return;
|
|
|
|
|
- // }
|
|
|
|
|
- // saveMessageToServer({
|
|
|
|
|
- // loginId,
|
|
|
|
|
- // messages: [{
|
|
|
|
|
- // content: agent.greeting,
|
|
|
|
|
- // contentType: EContentType.TextPlain,
|
|
|
|
|
- // role: EChatRole.Assistant,
|
|
|
|
|
- // saveStatus: 2,
|
|
|
|
|
- // isStreaming: false,
|
|
|
|
|
- // msgUk: generateUUID(),
|
|
|
|
|
- // }],
|
|
|
|
|
- // agentId: agent.agentId,
|
|
|
|
|
- // sessionId,
|
|
|
|
|
- // })
|
|
|
|
|
- // }, [showWelcome, sessionId])
|
|
|
|
|
-
|
|
|
|
|
// 首次进入界面滚动到底
|
|
// 首次进入界面滚动到底
|
|
|
useEffect(() => {
|
|
useEffect(() => {
|
|
|
if (pageIndex === 1) {
|
|
if (pageIndex === 1) {
|
|
@@ -164,12 +159,12 @@ const agent = useAgentStore((state) => {
|
|
|
// 只在长度真正变化时才触发
|
|
// 只在长度真正变化时才触发
|
|
|
if (prevLengthRef.current !== messagesLength) {
|
|
if (prevLengthRef.current !== messagesLength) {
|
|
|
prevLengthRef.current = messagesLength;
|
|
prevLengthRef.current = messagesLength;
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
// 使用 setTimeout 确保 DOM 更新完成后再计算高度
|
|
// 使用 setTimeout 确保 DOM 更新完成后再计算高度
|
|
|
const timer = setTimeout(() => {
|
|
const timer = setTimeout(() => {
|
|
|
triggerHeightUpdate();
|
|
triggerHeightUpdate();
|
|
|
}, 100);
|
|
}, 100);
|
|
|
-
|
|
|
|
|
|
|
+
|
|
|
return () => clearTimeout(timer);
|
|
return () => clearTimeout(timer);
|
|
|
}
|
|
}
|
|
|
}, [messagesLength, triggerHeightUpdate]);
|
|
}, [messagesLength, triggerHeightUpdate]);
|
|
@@ -178,7 +173,6 @@ const agent = useAgentStore((state) => {
|
|
|
destroy();
|
|
destroy();
|
|
|
});
|
|
});
|
|
|
|
|
|
|
|
-
|
|
|
|
|
const renderNavLeft = () => {
|
|
const renderNavLeft = () => {
|
|
|
return (
|
|
return (
|
|
|
<View
|
|
<View
|
|
@@ -191,33 +185,33 @@ const agent = useAgentStore((state) => {
|
|
|
);
|
|
);
|
|
|
};
|
|
};
|
|
|
|
|
|
|
|
-
|
|
|
|
|
// 大背景可以是视频,也可以是图片
|
|
// 大背景可以是视频,也可以是图片
|
|
|
- const getBgContent = ()=> {
|
|
|
|
|
- if(!agent?.avatarUrl || !!!agent?.enabledChatBg){
|
|
|
|
|
- return ''
|
|
|
|
|
|
|
+ const getBgContent = () => {
|
|
|
|
|
+ if (!agent?.avatarUrl || !!!agent?.enabledChatBg) {
|
|
|
|
|
+ return "";
|
|
|
}
|
|
}
|
|
|
- return agent?.avatarUrl
|
|
|
|
|
- }
|
|
|
|
|
|
|
+ return agent?.avatarUrl;
|
|
|
|
|
+ };
|
|
|
|
|
|
|
|
- useEffect(()=> {
|
|
|
|
|
|
|
+ useEffect(() => {
|
|
|
Taro.setNavigationBarColor({
|
|
Taro.setNavigationBarColor({
|
|
|
- frontColor: '#ffffff',
|
|
|
|
|
- backgroundColor: 'transparent'
|
|
|
|
|
- })
|
|
|
|
|
- return ()=> {
|
|
|
|
|
|
|
+ frontColor: "#ffffff",
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ });
|
|
|
|
|
+ return () => {
|
|
|
Taro.setNavigationBarColor({
|
|
Taro.setNavigationBarColor({
|
|
|
- frontColor: '#000000',
|
|
|
|
|
- backgroundColor: 'transparent'
|
|
|
|
|
- })
|
|
|
|
|
- }
|
|
|
|
|
- },[])
|
|
|
|
|
-
|
|
|
|
|
-
|
|
|
|
|
|
|
+ frontColor: "#000000",
|
|
|
|
|
+ backgroundColor: "transparent",
|
|
|
|
|
+ });
|
|
|
|
|
+ };
|
|
|
|
|
+ }, []);
|
|
|
|
|
|
|
|
-
|
|
|
|
|
return (
|
|
return (
|
|
|
- <PageCustom fullPage style={{ overflow: "hidden" }} styleBg={getBgContent()}>
|
|
|
|
|
|
|
+ <PageCustom
|
|
|
|
|
+ fullPage
|
|
|
|
|
+ style={{ overflow: "hidden" }}
|
|
|
|
|
+ styleBg={getBgContent()}
|
|
|
|
|
+ >
|
|
|
<NavBarNormal blur leftColumn={renderNavLeft}>
|
|
<NavBarNormal blur leftColumn={renderNavLeft}>
|
|
|
{/* <>{`${scrollTop}`}--{autoScroll ? 'true': 'false'}</> */}
|
|
{/* <>{`${scrollTop}`}--{autoScroll ? 'true': 'false'}</> */}
|
|
|
</NavBarNormal>
|
|
</NavBarNormal>
|
|
@@ -236,27 +230,40 @@ const agent = useAgentStore((state) => {
|
|
|
scrollTop={scrollTop}
|
|
scrollTop={scrollTop}
|
|
|
scrollWithAnimation
|
|
scrollWithAnimation
|
|
|
onScrollToUpper={onScrollToUpper}
|
|
onScrollToUpper={onScrollToUpper}
|
|
|
- onScrollToLower={()=> setAutoScroll(true)}
|
|
|
|
|
|
|
+ onScrollToLower={() => setAutoScroll(true)}
|
|
|
>
|
|
>
|
|
|
- <View id="messageList" className="flex flex-col gap-16 px-18" onTouchMove={handleTouchMove}>
|
|
|
|
|
|
|
+ <View
|
|
|
|
|
+ id="messageList"
|
|
|
|
|
+ className="flex flex-col gap-16 px-18"
|
|
|
|
|
+ onTouchMove={handleTouchMove}
|
|
|
|
|
+ >
|
|
|
{showWelcome && <ChatGreeting agent={agent} />}
|
|
{showWelcome && <ChatGreeting agent={agent} />}
|
|
|
- {/* 复制 histories 再 reverse 否则会影响 state */}
|
|
|
|
|
- {allMessages.map((message) => {
|
|
|
|
|
- const reasoningContent = (message as any).reasoningContent || '';
|
|
|
|
|
|
|
+ {result.map((group) => {
|
|
|
return (
|
|
return (
|
|
|
- <ChatMessage
|
|
|
|
|
- key={message.msgUk}
|
|
|
|
|
- textReasoning={reasoningContent}
|
|
|
|
|
- agent={agent}
|
|
|
|
|
- role={message.role}
|
|
|
|
|
- text={message.content}
|
|
|
|
|
- message={message}
|
|
|
|
|
- ></ChatMessage>
|
|
|
|
|
|
|
+ <>
|
|
|
|
|
+ <View className="text-12 leading-20 text-gray-25 block text-center w-full">
|
|
|
|
|
+ {formatMessageTime(group.dt)}
|
|
|
|
|
+ </View>
|
|
|
|
|
+ {group.list.map((message) => {
|
|
|
|
|
+ const reasoningContent =
|
|
|
|
|
+ (message as any).reasoningContent || "";
|
|
|
|
|
+ return (
|
|
|
|
|
+ <ChatMessage
|
|
|
|
|
+ key={message.msgUk}
|
|
|
|
|
+ textReasoning={reasoningContent}
|
|
|
|
|
+ agent={agent}
|
|
|
|
|
+ role={message.role}
|
|
|
|
|
+ text={message.content}
|
|
|
|
|
+ message={message}
|
|
|
|
|
+ ></ChatMessage>
|
|
|
|
|
+ );
|
|
|
|
|
+ })}
|
|
|
|
|
+ </>
|
|
|
);
|
|
);
|
|
|
})}
|
|
})}
|
|
|
</View>
|
|
</View>
|
|
|
<View className="pb-40 pt-8">
|
|
<View className="pb-40 pt-8">
|
|
|
- {(agent) && <RecommendQuestions agent={agent} />}
|
|
|
|
|
|
|
+ {agent && <RecommendQuestions agent={agent} />}
|
|
|
</View>
|
|
</View>
|
|
|
</ScrollView>
|
|
</ScrollView>
|
|
|
<View className="w-full h-54">
|
|
<View className="w-full h-54">
|
|
@@ -267,16 +274,15 @@ const agent = useAgentStore((state) => {
|
|
|
style={{
|
|
style={{
|
|
|
bottom: `${keyboardHeight}px`,
|
|
bottom: `${keyboardHeight}px`,
|
|
|
}}
|
|
}}
|
|
|
-
|
|
|
|
|
>
|
|
>
|
|
|
<View className="bg-[#F5FAFF]">
|
|
<View className="bg-[#F5FAFF]">
|
|
|
- {agent && (
|
|
|
|
|
|
|
+ {agent && (
|
|
|
<InputBar
|
|
<InputBar
|
|
|
agent={agent}
|
|
agent={agent}
|
|
|
histories={list}
|
|
histories={list}
|
|
|
setShowWelcome={setShowWelcome}
|
|
setShowWelcome={setShowWelcome}
|
|
|
></InputBar>
|
|
></InputBar>
|
|
|
- )}
|
|
|
|
|
|
|
+ )}
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|
|
|
</View>
|
|
</View>
|