Procházet zdrojové kódy

feat: 聊天消息列表增加显示时间

王晓东 před 3 měsíci
rodič
revize
91cd9cac2d

+ 1 - 1
src/components/custom-share/ShareWxaCard/index.tsx

@@ -120,7 +120,7 @@ export default forwardRef(({ agent }: Props, ref) => {
       avatarWrapper.addChild(avatar)
       view.addChild(avatarWrapper);
     }
-
+    console.log(agent)
     // 用户名
     const name = new RichText({
       text: agent?.name ?? "",

+ 7 - 9
src/components/custom-share/index.tsx

@@ -14,7 +14,7 @@ import {
 import Taro, { useShareAppMessage, useShareTimeline } from "@tarojs/taro";
 import { getSharePromise } from "./shareUtils";
 import Stage from "@/libs/duducanvas/Stage";
-import { TAgent, TAgentDetail } from "@/types/agent";
+import { TAgentDetail } from "@/types/agent";
 import { DEFAULT_AVATAR } from "@/config";
 import { useIsLogin } from "@/xiaolanbenlib/hooks/data/useAuth";
 
@@ -152,13 +152,14 @@ export default (props: Props) => {
       rect.addChild(circleBg)
       
     }
-
+    
     // 名称与公司名容器
     const infoList = new Container();
     infoList.y = 110 * ratio;
+    infoList.x = 0;
     infoList.direction = "column";
     infoList.gap = 4 * ratio;
-    infoList.width = card.width * ratio;
+    infoList.width = card.width
     infoList.height = 42 * ratio;
     // 用户名
     const name = new RichText({
@@ -183,16 +184,13 @@ export default (props: Props) => {
     companyName.lineGap = 8;
     companyName.shadow = '0 0 4 white'
     infoList.addChild(name, companyName);
-
     stage.addChild(card, infoList);
+    
+    console.log(name)
     stage.update();
   };
 
-  // 延迟获取 canvas
-  
-  // Taro.nextTick(() => {
-  //   initCanvas();
-  // });
+
   // 您好,这是xxx的智能体主页!
   const shareTitle = '快和我的智能体聊聊吧' // '想了解我?快和我的AI聊聊吧',
   

+ 126 - 120
src/pages/chat/index.tsx

@@ -1,4 +1,4 @@
-import { View, ScrollView, Video } from "@tarojs/components";
+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";
@@ -6,7 +6,14 @@ 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 { 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 IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24";
@@ -16,43 +23,39 @@ import { useAgentStore } from "@/store/agentStore";
 import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
 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() {
   const router = useRouter();
-  
+
   const { agentId, isVisitor } = router.params;
   if (!agentId) {
     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 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 scrollTop = useTextChat((state) => state.scrollTop);
   // 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 res = await getMessageHistories({
       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 [showWelcome, setShowWelcome] = useState(!list.length);
@@ -94,21 +116,20 @@ const agent = useAgentStore((state) => {
     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(() => {
-    if(agentId){
-      if(isVisitor){
-        fetchAgentProfile(agentId)
-      }else{
+    if (agentId) {
+      if (isVisitor) {
+        fetchAgentProfile(agentId);
+      } else {
         fetchAgent(agentId);
       }
     }
@@ -119,32 +140,6 @@ const agent = useAgentStore((state) => {
     setShowWelcome(!messageList.length && !list.length);
   }, [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(() => {
     if (pageIndex === 1) {
@@ -164,12 +159,12 @@ const agent = useAgentStore((state) => {
     // 只在长度真正变化时才触发
     if (prevLengthRef.current !== messagesLength) {
       prevLengthRef.current = messagesLength;
-      
+
       // 使用 setTimeout 确保 DOM 更新完成后再计算高度
       const timer = setTimeout(() => {
         triggerHeightUpdate();
       }, 100);
-      
+
       return () => clearTimeout(timer);
     }
   }, [messagesLength, triggerHeightUpdate]);
@@ -178,7 +173,6 @@ const agent = useAgentStore((state) => {
     destroy();
   });
 
-
   const renderNavLeft = () => {
     return (
       <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({
-      frontColor: '#ffffff',
-      backgroundColor: 'transparent'
-    })
-    return ()=> {
+      frontColor: "#ffffff",
+      backgroundColor: "transparent",
+    });
+    return () => {
       Taro.setNavigationBarColor({
-        frontColor: '#000000',
-        backgroundColor: 'transparent'
-      })
-    }
-  },[])
-  
-
+        frontColor: "#000000",
+        backgroundColor: "transparent",
+      });
+    };
+  }, []);
 
-  
   return (
-    <PageCustom fullPage style={{ overflow: "hidden" }} styleBg={getBgContent()}>
+    <PageCustom
+      fullPage
+      style={{ overflow: "hidden" }}
+      styleBg={getBgContent()}
+    >
       <NavBarNormal blur leftColumn={renderNavLeft}>
         {/* <>{`${scrollTop}`}--{autoScroll ? 'true': 'false'}</> */}
       </NavBarNormal>
@@ -236,27 +230,40 @@ const agent = useAgentStore((state) => {
           scrollTop={scrollTop}
           scrollWithAnimation
           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} />}
-            {/* 复制 histories 再 reverse 否则会影响 state */}
-            {allMessages.map((message) => {
-              const reasoningContent = (message as any).reasoningContent || '';
+            {result.map((group) => {
               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 className="pb-40 pt-8">
-          {(agent) &&  <RecommendQuestions agent={agent} />}
+            {agent && <RecommendQuestions agent={agent} />}
           </View>
         </ScrollView>
         <View className="w-full h-54">
@@ -267,16 +274,15 @@ const agent = useAgentStore((state) => {
           style={{
             bottom: `${keyboardHeight}px`,
           }}
-
         >
           <View className="bg-[#F5FAFF]">
-          {agent && (
+            {agent && (
               <InputBar
                 agent={agent}
                 histories={list}
                 setShowWelcome={setShowWelcome}
               ></InputBar>
-          )}
+            )}
           </View>
         </View>
       </View>

+ 1 - 1
src/pages/knowledge-item/index.tsx

@@ -241,7 +241,7 @@ export default function Index() {
         </BottomBar>}
         
 
-        {(knowledgeId!== undefined) && <ShareToEntPopup knowledgeId={knowledgeId} setShowPopup={setShowPopup} showPopup={showPopup}/>}
+        {(knowledgeId!== undefined) && !isEnt && <ShareToEntPopup knowledgeId={knowledgeId} setShowPopup={setShowPopup} showPopup={showPopup}/>}
         
       </View>
     </PageCustom>

+ 45 - 33
src/pages/knowledge/components/CompanyList/index.tsx

@@ -1,9 +1,12 @@
 import { View } from "@tarojs/components";
 
-import EmptyData, { EmptyDataTitle, EmptyDataSubInfo } from "@/components/EmptyData";
+import EmptyData, {
+  EmptyDataTitle,
+  EmptyDataSubInfo,
+} from "@/components/EmptyData";
 import { useEffect, useState } from "react";
-
-import PickerSingleColumn from "@/components/Picker/PickerSingleColumn";
+import Popup from "@/components/popup/popup";
+import WemetaRadio from "@/components/WemetaRadio/index";
 import IconArrowDownRounded from "@/components/icon/IconArrowDownRounded";
 import { useUserStore } from "@/store/userStore";
 import Taro, { useDidShow } from "@tarojs/taro";
@@ -19,16 +22,15 @@ import { TEntItem } from "@/types/user";
 // }]
 
 interface IProps {
-  extraEnt?: TEntItem[]
-  currentEnt: TEntItem | null
-  setCurrentEnt: (ent:TEntItem) => void
+  extraEnt?: TEntItem[];
+  currentEnt: TEntItem | null;
+  setCurrentEnt: (ent: TEntItem) => void;
 }
 
-const Index = ({currentEnt, setCurrentEnt, extraEnt = []}: IProps) => {
-  const {entList, fetchMyEntList } = useUserStore();
+const Index = ({ currentEnt, setCurrentEnt, extraEnt = [] }: IProps) => {
+  const { entList, fetchMyEntList } = useUserStore();
 
-  
-  const allEnt = [...entList, ...extraEnt]
+  const allEnt = [...entList, ...extraEnt];
   // 当前选中的值
   const options = allEnt.map((item) => item.entName);
   // 是否显示选择器
@@ -42,13 +44,14 @@ const Index = ({currentEnt, setCurrentEnt, extraEnt = []}: IProps) => {
     const ent = allEnt.find((item) => item.entName === value);
     if (ent) {
       setCurrentEnt(ent);
+      setShowPicker(false);
     }
   };
 
   useEffect(() => {
     // 如果没有设置过当前企业,且有企业列表,则设置一个默认的为当前企业
-    console.log(currentEnt, allEnt, entList)
-    if (!currentEnt &&  allEnt.length) {
+    console.log(currentEnt, allEnt, entList);
+    if (!currentEnt && allEnt.length) {
       setSelected(allEnt[0].entName);
       setCurrentEnt(allEnt[0]);
     }
@@ -62,20 +65,18 @@ const Index = ({currentEnt, setCurrentEnt, extraEnt = []}: IProps) => {
   if (!allEnt.length) {
     return (
       <View className="flex-center pt-22 w-full">
-        <EmptyData type={'box'}>
+        <EmptyData type={"box"}>
           <EmptyDataTitle>开通企业知识库</EmptyDataTitle>
-            <EmptyDataSubInfo>
-              <View>让员工智能体高效运转,全公司知识自动同步</View>
-              <View>客户咨询准确率提升80%+</View>
-            </EmptyDataSubInfo>
-            <View
-              className="button-rounded button-primary-light button-border-primary button-inline-flex mt-20"
-              onClick={() =>
-                Taro.navigateTo({ url: "/pages/contact-us/index" })
-              }
-            >
-              联系我们
-            </View>
+          <EmptyDataSubInfo>
+            <View>让员工智能体高效运转,全公司知识自动同步</View>
+            <View>客户咨询准确率提升80%+</View>
+          </EmptyDataSubInfo>
+          <View
+            className="button-rounded button-primary-light button-border-primary button-inline-flex mt-20"
+            onClick={() => Taro.navigateTo({ url: "/pages/contact-us/index" })}
+          >
+            联系我们
+          </View>
         </EmptyData>
       </View>
     );
@@ -83,13 +84,7 @@ const Index = ({currentEnt, setCurrentEnt, extraEnt = []}: IProps) => {
 
   // 渲染公司选择器
   return (
-    <PickerSingleColumn
-      options={options}
-      selected={selected}
-      onPicked={handleChange}
-      showPicker={showPicker}
-      setShowPicker={setShowPicker}
-    >
+    <>
       <View
         className="flex items-center gap-2 bg-gray-3 rounded-12 p-12 mb-16"
         onClick={() => setShowPicker(true)}
@@ -101,7 +96,24 @@ const Index = ({currentEnt, setCurrentEnt, extraEnt = []}: IProps) => {
           <IconArrowDownRounded />
         </View>
       </View>
-    </PickerSingleColumn>
+      <Popup title="选择企业" show={showPicker} setShow={setShowPicker}>
+        <View className="flex flex-col gap-12 w-full overflow-y-auto max-h-[440px]">
+          {options.map((item) => {
+            return (
+              <View
+                className="flex items-center rounded-8 bg-gray-3 px-16 py-20"
+                onClick={() => handleChange(item)}
+              >
+                <View className="flex-1 text-black font-medium text-14 leading-22 font-pingfangSCMedium">
+                  {item}
+                </View>
+                <WemetaRadio checkbox checked={selected === item} />
+              </View>
+            );
+          })}
+        </View>
+      </Popup>
+    </>
   );
 };
 

+ 1 - 0
src/pages/knowledge/components/CompanyTab/index.tsx

@@ -19,6 +19,7 @@ const Index = () => {
   const [totalCount, setTotalCount] = useState<number>(0);  
   const {entList } = useUserStore();
 
+
   const renderScrollList = () => {
     if (!ent?.entId) {
       return <></>;

+ 1 - 0
src/pages/profile/index.tsx

@@ -145,6 +145,7 @@ export default function Profile() {
         });
       }}>前往访问</View>
     }
+    
     // 下线了
     return <View className="button-rounded-big" onClick={()=> {
       Taro.navigateBack()

+ 19 - 11
src/pages/visiteor-detail/index.tsx

@@ -13,13 +13,13 @@ import { getVisitorSessions } from "@/service/visitor";
 
 import style from "./index.module.less";
 import { useLoadMoreInfinite, createKey } from "@/utils/loadMoreInfinite";
-import EmptyData from "@/components/EmptyData";
+import EmptyData, { EmptyDataSubInfo } from "@/components/EmptyData";
 
 export default () => {
   const router = useRouter();
   const { visitorId } = router.params;
   const [visitor, setVisitor] = useState<TVisitorAgent>();
-  const [totalCount, setTotalCount] = useState(0)
+  const [totalCount, setTotalCount] = useState(0);
 
   if (!visitorId) {
     return <View>...</View>;
@@ -34,7 +34,7 @@ export default () => {
     }
   };
 
-  const fetcher = async ([_url, {nextId, pageSize}]) => {
+  const fetcher = async ([_url, { nextId, pageSize }]) => {
     const res = await getVisitorSessions({
       startId: nextId,
       pageSize,
@@ -44,8 +44,7 @@ export default () => {
     let _totalCount = res.data.totalCount;
     // 记录 totalCount
     if (_totalCount && _totalCount !== null) {
-      
-      setTotalCount(_totalCount)
+      setTotalCount(_totalCount);
     }
     return res.data;
   };
@@ -54,10 +53,9 @@ export default () => {
     createKey(`api/v1/my/visitor/sessions${visitorId}`, 5),
     fetcher
   );
-  
 
   const newList = list.map((item: TSessionItem, itemIndex: number) => {
-    let  turns = totalCount - itemIndex
+    let turns = totalCount - itemIndex;
     turns = turns <= 0 ? 1 : turns;
     return { ...item, visitTimes: turns };
   });
@@ -69,14 +67,15 @@ export default () => {
   useEffect(() => {
     fetchData();
   }, []);
-  
 
   return (
     <PageCustom fullPage style={{ overflow: "hidden" }}>
       <NavBarNormal scrollFadeIn backText="访问详情"></NavBarNormal>
       {visitor ? <VisitorSummary data={visitor} /> : <></>}
       <View className="rounded-container-header">
-        <View className="text-14 font-medium text-black font-pingfangSCMedium px-16 pb-14">访问记录</View>
+        <View className="text-14 font-medium text-black font-pingfangSCMedium px-16 pb-14">
+          访问记录
+        </View>
       </View>
       <View className="flex-1 overflow-hidden w-full">
         <ScrollView
@@ -89,7 +88,14 @@ export default () => {
         >
           <View>
             <View className="flex flex-col gap-16 px-16 w-full pb-100">
-              {newList.length <= 0 && <View className="pt-100"><EmptyData type={'chat'} /></View>}
+              {newList.length <= 0 && (
+                <EmptyData type="chat">
+                  <EmptyDataSubInfo>
+                    <View>当前暂无用户访问记录</View>
+                    <View>你的智能体正在等待首次互动</View>
+                  </EmptyDataSubInfo>
+                </EmptyData>
+              )}
               {newList.map((item) => {
                 return (
                   <View className="w-full">
@@ -98,7 +104,9 @@ export default () => {
                     </View>
                     <View className="flex h-full w-full">
                       <View className="text-14 font-normal text-black leading-20 flex flex-col items-end">
-                        <View className="text-14 leading-24">{item?.msgTime.slice(10, 16)} </View>
+                        <View className="text-14 leading-24">
+                          {item?.msgTime.slice(10, 16)}{" "}
+                        </View>
                       </View>
                       <View className="flex flex-1 w-full">
                         <View className={style.gutterLine}>

+ 13 - 11
src/store/textChat.ts

@@ -7,7 +7,7 @@ import { generateUUID } from '@/utils/index'
 
 import { getMessageHistories, type TGetMessageHistoriesParams,  } from '@/service/bot'
 import { EChatRole, TAnyMessage, TRobotMessage, TMessage } from "@/types/bot";
-
+import {getFormatNow} from '@/utils/timeUtils'
 
 
 type TRobotMessageWithOptionalId = Omit<TRobotMessage, "msgUk"> & {
@@ -82,8 +82,9 @@ export const useTextChat = create<TextChat>((set, get) => ({
   },
   pushRobotMessage: (message) => {
     const msgUk = generateUk()
+    const sessionId = get().sessionId ?? ''
     set((state) => {
-      const newRobotMessage = { ...message, msgUk, role: EChatRole.Assistant } as TRobotMessage
+      const newRobotMessage = { ...message, msgUk, sessionId, msgTime: getFormatNow(), role: EChatRole.Assistant } as TRobotMessage
       return {
         list: [...state.list, newRobotMessage],
         currentRobotMsgUk: msgUk,
@@ -99,16 +100,10 @@ export const useTextChat = create<TextChat>((set, get) => ({
     }, 100)
     return msgUk
   },
-  setScrollTop: ()=> {
-    set((state) => {
-      return {
-        scrollTop: state.scrollTop + 1,
-      };
-    });
-  },
   pushMessage: (content: string) => {
+    const sessionId = get().sessionId ?? ''
     const msgUk = generateUk();
-    const newMessage:TMessage ={ msgUk, content: content, role: EChatRole.User, saveStatus: 0 }
+    const newMessage:TMessage ={ msgUk, sessionId, msgTime: getFormatNow(), content: content, role: EChatRole.User, saveStatus: 0 }
     set((state) => {
       return {
         list: [...state.list,  newMessage],
@@ -123,7 +118,14 @@ export const useTextChat = create<TextChat>((set, get) => ({
       });
     }, 100)
     
-    return {msgUk, sessionId: get().sessionId ?? ''}
+    return {msgUk, sessionId}
+  },
+  setScrollTop: ()=> {
+    set((state) => {
+      return {
+        scrollTop: state.scrollTop + 1,
+      };
+    });
   },
   updateMessage: (content, msgUk) => {
     set((state) => {

+ 19 - 0
src/utils/messageUtils.ts

@@ -0,0 +1,19 @@
+import { EContentType, TAnyMessage } from "@/types/bot";
+
+export function formatMessageItem(item: TAnyMessage){
+  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)
+    }
+  }
+  return item;
+}

+ 39 - 0
src/utils/timeUtils.ts

@@ -0,0 +1,39 @@
+import dayjs from 'dayjs';
+import 'dayjs/locale/zh-cn'; // 中文语言包
+import relativeTime from 'dayjs/plugin/relativeTime';
+import isToday from 'dayjs/plugin/isToday';
+import isYesterday from 'dayjs/plugin/isYesterday';
+import weekday from 'dayjs/plugin/weekday';
+
+// 加载插件
+dayjs.extend(relativeTime);
+dayjs.extend(isToday);
+dayjs.extend(isYesterday);
+dayjs.extend(weekday);
+dayjs.locale('zh-cn'); // 设置中文
+
+
+// 格式化时间类似微信聊天时间
+export function formatMessageTime(timeStr:string) {
+  const now = dayjs();
+  const target = dayjs(timeStr.replace(/\//g, '-')); // 将/替换为-确保解析正确
+  if (target.isToday()) {
+    // 今天:显示 HH:mm
+    return target.format('HH:mm');
+  } else if (target.isYesterday()) {
+    // 昨天:显示 昨天 HH:mm
+    return `昨天 ${target.format('HH:mm')}`;
+  } else if (now.diff(target, 'day') <= 7) {
+    // 一周内:显示 周几 HH:mm
+    const weekdays = ['日', '一', '二', '三', '四', '五', '六'];
+    return `周${weekdays[target.day()]} ${target.format('HH:mm')}`;
+  } else {
+    // 一周前:显示 月/日 HH:mm
+    return target.format('M/D HH:mm');
+  }
+}
+// 生成当前 msgTime
+export function getFormatNow() {
+  const now = dayjs();
+  return now.format('YYYY-MM-DD HH:mm:ss')
+}