Forráskód Böngészése

feat: 聊天框顶部增加下拉菜单

王晓东 1 hónapja
szülő
commit
7b2222eaf9

+ 9 - 2
project.private.config.json

@@ -8,12 +8,19 @@
   "condition": {
     "miniprogram": {
       "list": [
+        {
+          "name": "pages/chat/index",
+          "pathName": "pages/chat/index",
+          "query": "agentId=p_2e73c9d7efaYfDo2-agent_2747&isVisitor=false",
+          "scene": null,
+          "launchMode": "default"
+        },
         {
           "name": "pages/editor-contact/index",
           "pathName": "pages/editor-contact/index",
           "query": "agentId=p_2e73c9d7efaYfDo2-agent_2731",
-          "scene": null,
-          "launchMode": "default"
+          "launchMode": "default",
+          "scene": null
         },
         {
           "name": "pages/agent-gen/index",

+ 25 - 18
src/app.less

@@ -440,34 +440,41 @@
   height: 16px;
   border-radius: 4px;
 }
-z
-
 .bubble-wrapper {
-  position: relative;
+  top: calc(100% + 12px);
+  left: -64px;
+  z-index: 2;
+  width: 142px;
+  position: absolute;
   display: inline-block;
   filter: drop-shadow(0 4px 16px rgba(0, 0, 0, 0.15)); /* 🔑 关键:父级统一阴影 */
+  box-shadow: 0 5px 5px -3px #0000001a, 0 8px 10px 1px #0000000f, 0 3px 14px 2px #0000000d;
 }
 
 .bubble {
   position: relative;
   background: white;
-  border-radius: 12px;
-  padding: 16px 20px;
-  max-width: 300px;
-  font-size: 16px;
-  /* ❌ 移除了 box-shadow */
+  border-radius: 4px;
+  padding: 12px;
+  max-width: 142px;
+  font-size: 14px;
+  line-height: 22px;
+  box-shadow: 0 5px 5px -3px #0000001a, 0 8px 10px 1px #0000000f, 0 3px 14px 2px #0000000d;
 }
-
-.bubble::after {
-  content: '';
+.bubble-arrow-wrap{
   position: absolute;
-  top: 100%;
-  left: 24px;
-  width: 16px;
-  height: 16px;
+  top: 0;
+  margin-left: -5px;
+  left: 50%;
+  width: 10px;
+  height: 10px;
+  transform: translateY(-50%) scaleX(0.8);
+}
+.bubble-arrow {
+  width: 10px;
+  height: 10px;
   background: white;
-  border-radius: 3px;
-  transform: translateY(-50%) rotate(45deg);
-  /* ❌ 移除了 box-shadow 和 z-index */
+  border-radius: 1px;
+  transform: rotate(45deg);
 }
 

+ 0 - 5
src/components/AgentPage/index.tsx

@@ -70,11 +70,6 @@ export default function Index({ agentId }: IProps) {
       <NavBarNormal blur scrollFadeIn scrollFadeInDelta={240} leftColumn={Logo}></NavBarNormal>
       {renderDefaultPlaceholder()}
       <View className="blur-rounded-container">
-        <view className="bubble-wrapper">
-          <view className="bubble">
-            这是一个带阴影的圆润气泡框
-          </view>
-        </view>
         {(!!agent) ? <AgentActionBar isVisitor={false} agent={agent}></AgentActionBar> : <></>}
         <View className={`flex flex-col gap-12 w-full p-16`}>
           <ComponentList components={components}></ComponentList>

+ 7 - 0
src/components/icon/IconArrowRightGray/index.tsx

@@ -0,0 +1,7 @@
+import { Image } from '@tarojs/components'
+import Icon from '@/images/svgs/chat/IconArrowRightGray.svg'
+export default () => {
+  return (
+    <Image src={Icon} mode="widthFix" style={{width: '16px', height: '16px'}}></Image>
+  )
+}

+ 13 - 0
src/components/icon/IconArrowVerticalFilled/index.tsx

@@ -0,0 +1,13 @@
+import { Image } from '@tarojs/components'
+import ArrowVerticalFilledWhite from '@/images/svgs/chat/ArrowVerticalFilledWhite.svg'
+import ArrowVerticalFilledGray from '@/images/svgs/chat/ArrowVerticalFilledGray.svg'
+interface IProps {
+  color?: 'white' | 'gray'
+  rotation?: number
+}
+export default ({color, rotation = 0}: IProps) => {
+  const Icon = color === 'white' ? ArrowVerticalFilledWhite : ArrowVerticalFilledGray
+  return (
+    <Image src={Icon} mode="widthFix" style={{width: '16px', height: '16px', transform: `rotate(${rotation}deg)`}}></Image>
+  )
+}

+ 7 - 0
src/components/icon/IconClean/index.tsx

@@ -0,0 +1,7 @@
+import { Image } from '@tarojs/components'
+import Icon from '@/images/svgs/chat/IconClean.svg'
+export default () => {
+  return (
+    <Image src={Icon} mode="widthFix" style={{width: '16px', height: '16px'}}></Image>
+  )
+}

+ 7 - 0
src/components/icon/IconMicOutline/index.tsx

@@ -0,0 +1,7 @@
+import { Image } from '@tarojs/components'
+import Icon from '@/images/svgs/chat/IconMicOutline.svg'
+export default () => {
+  return (
+    <Image src={Icon} mode="widthFix" style={{width: '16px', height: '16px'}}></Image>
+  )
+}

+ 0 - 1
src/components/wemeta-textarea/index.module.less

@@ -1,7 +1,6 @@
 .inputContainer{
   display: flex;
   align-items: center;
-  padding: 14px 16px;
   border-radius: 12px;
   background-color: white;
   border: 2px solid transparent;

+ 3 - 1
src/components/wemeta-textarea/index.tsx

@@ -19,6 +19,7 @@ interface Props {
   bgColor?: string;
   autoFocus?: boolean;
   extraClass?: string;
+  className?: string;
   adjustPosition?: boolean
   onConfirm?: (value: string) => void;
   placeholderStyle?: string;
@@ -27,6 +28,7 @@ let isInbox = false;
 const index = ({
   value,
   bgColor,
+  className,
   extraClass,
   style,
   disabled,
@@ -75,7 +77,7 @@ const index = ({
     <View
       className={`${
         focus ? styleIndex.inputContainerFocused : styleIndex.inputContainer
-      }  p-12`}
+      }  ${className ? className : "px-16 py-14"}`}
       style={bgColor ? { backgroundColor: bgColor } : {}}
     >
       {prefix && prefix()}

+ 3 - 0
src/images/svgs/chat/ArrowVerticalFilledGray.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="design-iconfont">
+  <path d="M5.68429195,6.66709494 L10.3961317,0.832340092 C10.5103964,0.669804795 10.5238361,0.456912777 10.4309184,0.28129829 C10.3380006,0.105683803 10.1544307,-0.00297037204 9.95577286,0 L0.536496914,0 C0.337839028,-0.00297036946 0.154269138,0.105683807 0.0613514207,0.281298293 C-0.0315662966,0.456912779 -0.0181266665,0.669804796 0.0961380607,0.832340092 L4.80797782,6.66709494 C4.90749456,6.81090712 5.07124771,6.89672841 5.24613489,6.89672841 C5.42102206,6.89672841 5.58477521,6.81090712 5.68429195,6.66709494 L5.68429195,6.66709494 Z" transform="matrix(1 0 0 -1 3 11)" fill="#111" fill-opacity=".85" fill-rule="nonzero"/>
+</svg>

+ 3 - 0
src/images/svgs/chat/ArrowVerticalFilledWhite.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="design-iconfont">
+  <path d="M5.68429195,6.66709494 L10.3961317,0.832340092 C10.5103964,0.669804795 10.5238361,0.456912777 10.4309184,0.28129829 C10.3380006,0.105683803 10.1544307,-0.00297037204 9.95577286,0 L0.536496914,0 C0.337839028,-0.00297036946 0.154269138,0.105683807 0.0613514207,0.281298293 C-0.0315662966,0.456912779 -0.0181266665,0.669804796 0.0961380607,0.832340092 L4.80797782,6.66709494 C4.90749456,6.81090712 5.07124771,6.89672841 5.24613489,6.89672841 C5.42102206,6.89672841 5.58477521,6.81090712 5.68429195,6.66709494 L5.68429195,6.66709494 Z" transform="matrix(1 0 0 -1 3 11)" fill="#FFF" fill-opacity=".85"  fill-rule="nonzero"/>
+</svg>

+ 3 - 0
src/images/svgs/chat/IconArrowRightGray.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="design-iconfont">
+  <path d="M1.1375,13.7984375 L7.6984375,7.2375 C7.8265625,7.109375 7.8265625,6.9015625 7.6984375,6.7734375 L1.11875,0.1921875 C0.8625,-0.0640625 0.446875,-0.0640625 0.1921875,0.1921875 C-0.0640625,0.4484375 -0.0640625,0.8640625 0.1921875,1.11875 L5.9609375,6.8875 C6.025,6.9515625 6.025,7.0546875 5.9609375,7.11875 L0.2109375,12.86875 C-0.0453125,13.125 -0.0453125,13.540625 0.2109375,13.7953125 C0.4671875,14.0546875 0.88125,14.0546875 1.1375,13.7984375 Z" transform="translate(4.08 1.04)" fill="#111" fill-opacity=".45" fill-rule="nonzero"/>
+</svg>

+ 3 - 0
src/images/svgs/chat/IconClean.svg

@@ -0,0 +1,3 @@
+<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" class="design-iconfont">
+  <path d="M4,12 L4,9.33333334 C4,8.96514351 4.29847684,8.66666667 4.66666667,8.66666667 C5.03485651,8.66666667 5.33333334,8.96514351 5.33333334,9.33333334 L5.33333334,12 L11.3333333,12 L11.3333333,7.33333334 L2,7.33333334 L2,12 L4,12 Z M1.33333334,6 L12,6 L12,4 L8,4 L8,1.33333334 L5.33333334,1.33333334 L5.33333334,4 L1.33333334,4 L1.33333334,6 Z M0.666666672,12.6666667 L0.666666672,7.33333334 C0.298476836,7.33333334 0,7.03485651 0,6.66666667 L0,3.33333334 C0,2.96514351 0.298476836,2.66666667 0.666666672,2.66666667 L4,2.66666667 L4,0.666666672 C4,0.298476836 4.29847684,0 4.66666667,0 L8.66666667,0 C9.03485651,0 9.33333334,0.298476836 9.33333334,0.666666672 L9.33333334,2.66666667 L12.6666667,2.66666667 C13.0348565,2.66666667 13.3333333,2.96514351 13.3333333,3.33333334 L13.3333333,6.66666667 C13.3333333,7.03485651 13.0348565,7.33333334 12.6666667,7.33333334 L12.6666667,12.6666667 C12.6666667,13.0348565 12.3681898,13.3333333 12,13.3333333 L1.33333334,13.3333333 C0.965143508,13.3333333 0.666666672,13.0348565 0.666666672,12.6666667 Z" transform="translate(1.36 1.28)" fill="#111" fill-rule="nonzero"/>
+</svg>

A különbségek nem kerülnek megjelenítésre, a fájl túl nagy
+ 1 - 0
src/images/svgs/chat/IconMicOutline.svg


+ 2 - 1
src/pages/chat/components/InputBar/TextInputBar.tsx

@@ -42,8 +42,9 @@ export default ({disabled, onIconClick, onSend}:Props) => {
       <WemetaTextarea
       	adjustPosition={false}
         disabled={disabled}
+        className="px-16 py-6"
         // 必须添加 height 与 maxHeight 否则在初始化时高度会先 maxheight 再跳回到 lineHeight,导致闪烁和 高度计算错误
-        style={{'borderRadius': '10px', maxHeight: '120px', 'lineHeight': '24px', 'height': '24px'}}
+        style={{'borderRadius': '10px', maxHeight: '120px', 'lineHeight': '1', 'height': '24px'}}
         autoHeight
         placeholder="有问题尽管问我..."
         value={value}

+ 1 - 1
src/pages/chat/components/InputBar/VoiceInputBar.tsx

@@ -70,7 +70,7 @@ export default ({agentId,disabled, onIconClick, onSend, beforeSend, onError}:Pro
   return <>
     {/* <TextInputBar onIconClick={handleTextInputBarSwitch}></TextInputBar> */}
 
-      <View className={`flex bg-white gap-6 items-center p-16 rounded-10 ${style.speechButton} ${ idle ? '': style.speechButtonActive}`}>
+      <View className={`flex bg-white gap-6 items-center px-16 py-6 rounded-10 ${style.speechButton} ${ idle ? '': style.speechButtonActive}`}>
         <View
           className="flex-1 flex items-center justify-center leading-34 h-34"
           onLongPress={onLongPress}

+ 4 - 1
src/pages/chat/components/InputBar/useChatInput.ts

@@ -33,6 +33,7 @@ export const useChatInput = ({
 
   // 聊天框 store
   const {
+    questions,
     pushRobotMessage,
     updateRobotMessage,
     getCurrentRobotMessage,
@@ -40,7 +41,7 @@ export const useChatInput = ({
     updateMessage,
     deleteMessage,
     setQuestions,
-    questions,
+    setIsNewChat,
     messageStopHandle,
     setMessageStopHandle,
     setReacting,
@@ -91,7 +92,9 @@ export const useChatInput = ({
     if(messageStopHandle){
       messageStopHandle()
     }
+
     setShowWelcome?.(false);
+    setIsNewChat(false);
     setQuestions([]);
     initPlay();
     setReacting(true)

+ 70 - 33
src/pages/chat/components/PersonalCard/index.tsx

@@ -1,55 +1,92 @@
-
+import { useState } from 'react'
+import Taro from '@tarojs/taro'
 import { Image, View } from '@tarojs/components'
-
-import TagCertificated from "@/components/tag-certificated";
 import IconCertificateColor from "@/components/icon/icon-certificate-color";
 import { TAgentDetail } from '@/types/agent';
 import { AvatarMedia } from '@/components/AvatarMedia';
+import IconMicOutline from '@/components/icon/IconMicOutline';
+import IconArrowRightGray from '@/components/icon/IconArrowRightGray';
+import IconClean from '@/components/icon/IconClean';
+import IconArrowVerticalFilled from '@/components/icon/IconArrowVerticalFilled';
+import { useTextChat } from '@/store/textChatStore';
 interface IProps {
   agent: TAgentDetail|null
   haveBg: boolean
+  onClear: ()=>void
 }
-export default ({haveBg, agent}:IProps) => { 
+export default ({haveBg, agent, onClear}:IProps) => {
   const nameStyle = haveBg ? 'text-white' : 'text-black'
   const entStyle = haveBg ? 'text-white' : 'text-black-45'
+  const [show, setShow] = useState(false)
+  const { setIsNewChat } = useTextChat()
+
+  const handleClick = (e:any) => {
+    e.stopPropagation()
+    setShow(!show)
+  }
+  const handleClean = (e:any) => {
+    // e.stopPropagation()
+    console.log('clean')
+    setIsNewChat(true)
+    onClear()
+  }
+  const handleVoiceChoose = (e:any) => {
+    Taro.navigateTo({
+      url: '/pages/voice/index'
+    })
+    console.log('handleVoiceChoose')
+  }
   const renderContent = () => {
     return <>
-        <View className="rounded-full overflow-hidden w-36 h-36 shrink-0">
-          <AvatarMedia source={agent?.avatarLogo || ''} className='w-36 h-36'></AvatarMedia>
-        </View>
-        <View className="flex flex-col flex-1 gap-6">
-          <View className="flex items-center gap-4">
-            <View className={`text-14 font-medium leading-14 ${nameStyle}`}>{agent?.name}</View>
-            {agent?.isEnt && <View className="text-12 leading-12"><IconCertificateColor/></View>}
+      <View className='flex items-center' onClick={handleClick}>
+        <View className='flex-1 flex items-center mr-16'>
+          <View className="rounded-full overflow-hidden w-36 h-36 shrink-0 mr-8">
+            <AvatarMedia source={agent?.avatarLogo || ''} className='w-36 h-36'></AvatarMedia>
+          </View>
+          <View className="flex flex-col flex-1 gap-6">
+            <View className="flex items-center gap-4">
+              <View className={`text-14 font-medium leading-14 ${nameStyle}`}>{agent?.name}</View>
+              {agent?.isEnt && <View className="text-12 leading-12"><IconCertificateColor/></View>}
+            </View>
+            {agent?.entName && <View className="flex items-center gap-2">
+              <View className={`text-12 leading-12 truncate max-w-[180px] ${entStyle}`}>
+                {agent?.entName}
+              </View>
+            </View>}
           </View>
-          {agent?.entName && <View className="flex items-center gap-2">
-            <View className={`text-12 leading-12 truncate max-w-[180px] ${entStyle}`}>
-              {agent?.entName}
+        </View>
+        <View className='w-16 h-16'>
+          <View className='relative w-16 h-16'>
+            <View>
+              <IconArrowVerticalFilled color={ haveBg ? 'white' : 'gray' } rotation={show ? 0 : 180}  />
             </View>
-          </View>}
+            {!!show && <View className="bubble-wrapper">
+              <View className="bubble flex flex-col gap-8">
+                <View className='bubble-arrow-wrap'>
+                  <View className='bubble-arrow'></View>
+                </View>
+                <View className='flex items-center gap-4' onClick={handleClean}>
+                  <View className='w-16 h-16'>
+                    <IconClean />
+                  </View>
+                  <View className='flex-1'>清除对话</View>
+                </View>
+                <View className='h-1' style={{backgroundColor: 'rgba(17,17,17,.15)', width: '100%'}}></View>
+                <View className='flex items-center gap-4' onClick={handleVoiceChoose}>
+                  <View className='w-16 h-16'><IconMicOutline/></View>
+                  <View className='flex-1'>音色选择</View>
+                  <View className='w-16 h-16'><IconArrowRightGray/></View>
+                </View>
+              </View>
+            </View>}
+          </View>
         </View>
+      </View>
     </>
-    // return <>
-    //     <View className="rounded-full overflow-hidden w-60 h-60">
-    //       <AvatarMedia source={agent?.avatarLogo || ''} className='w-60 h-60'></AvatarMedia>
-    //     </View>
-    //     <View className="flex flex-col flex-1">
-    //       <View className="flex items-end gap-8">
-    //         <View className="text-24 font-medium leading-32">{agent?.name}</View>
-    //         <View className="text-12 leading-20">{agent?.position}</View>
-    //       </View>
-    //       <View className="flex items-center gap-2">
-    //         <View className="text-12 leading-20 truncate max-w-[188px]">
-    //           {agent?.entName}
-    //         </View>
-    //         {agent?.isEnt && <TagCertificated />}
-    //       </View>
-    //     </View>
-    // </>
   }
   return (
     <View className="flex items-center gap-8">
       {renderContent()}
     </View>
   )
-}
+}

+ 16 - 7
src/pages/chat/hooks/useChatMessages.ts

@@ -4,6 +4,8 @@ import { getMessageHistories } from '@/service/chat';
 import { useTextChat } from '@/store/textChatStore';
 import { formatMessageItem } from '@/utils/messageUtils';
 import { TMessage, TRobotMessage, TAnyMessage } from '@/types/bot';
+import { useState } from "react";
+
 
 /**
  * 聊天消息管理 Hook
@@ -19,9 +21,10 @@ export const useChatMessages = (agentId: string, isVisitor?: string): {
   pageIndex: number;
   mutate: (data: any, params: any) => void;
   resetAndLoadFirstPage: () => void;
+  resetAndClearData: () => void;
 } => {
   const messageList = useTextChat((state) => state.list);
-
+  const [resetSignal, setResetSignal] = useState(0);
   // 获取历史聊天记录的 fetcher 函数
   const fetcher = async ([_url, { nextId, pageSize }]) => {
     const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
@@ -34,9 +37,9 @@ export const useChatMessages = (agentId: string, isVisitor?: string): {
   };
 
   // 使用无限滚动加载历史消息
-  const { list, loadMore, pageIndex, mutate, resetAndLoadFirstPage } = useLoadMoreInfinite<
+  const { list, loadMore, setSize, pageIndex, mutate, resetAndLoadFirstPage } = useLoadMoreInfinite<
     TMessage[] | TRobotMessage[]
-  >(createKey(`messeagehistories${isVisitor}${agentId}`), fetcher, {
+  >(createKey(`messeagehistories${isVisitor}${agentId}${resetSignal}`), fetcher, {
     revalidateOnMount: false
   });
   // 解析消息体 content,并展平数组
@@ -49,13 +52,13 @@ export const useChatMessages = (agentId: string, isVisitor?: string): {
   // 最后返回数组,以按组显示聊天记录时间 隔开每一组聊天记录
   const groupedMessages = useMemo(() => {
     const allMessages = [...[...parsedList].reverse(), ...messageList];
-    
+
     const resultMap = allMessages.reduce((acc, item) => {
       const { sessionId, msgTime } = item;
       if (!sessionId || !msgTime) {
         return acc;
       }
-      
+
       let _msgTime = msgTime.replace(/\-/g, "/");
       if (!acc[sessionId]) {
         acc[sessionId] = {
@@ -80,20 +83,26 @@ export const useChatMessages = (agentId: string, isVisitor?: string): {
   // 消息总长度(用于触发滚动等副作用)
   const messagesLength = useMemo(() => groupedMessages.length, [groupedMessages.length]);
 
+  const resetAndClearData = ()=> {
+    mutate([], false);
+    setSize(0);
+  };
+
   return {
     // 原始数据(保持原始结构供其他用途)
     historyList: parsedList, // 已经展平的消息列表
     messageList,
     parsedList,
-    
+
     // 处理后的数据
     groupedMessages,
     messagesLength,
-    
+
     // 操作方法
     loadMore,
     pageIndex,
     mutate,
     resetAndLoadFirstPage,
+    resetAndClearData,
   };
 };

+ 9 - 5
src/pages/chat/hooks/useChatScrollManager.ts

@@ -23,6 +23,7 @@ export const useChatScrollManager = (messagesLength: number, pageIndex: number,
 
   const { setScrollTop, setAutoScroll } = useTextChat();
   const scrollTop = useTextChat((state) => state.scrollTop);
+  const isNewChat = useTextChat((state) => state.isNewChat);
 
   // 键盘高度管理
   const { keyboardHeight, marginTopOffset, triggerHeightUpdate } = useKeyboard(
@@ -63,8 +64,11 @@ export const useChatScrollManager = (messagesLength: number, pageIndex: number,
 
   // 滚动事件处理
   const handleScrollToUpper = () => {
-    console.log("onscroll");
-    loadMore?.();
+    console.log("onscroll", scrollTop, 3333);
+    // 如果是非新对话,则尝试去加载更多历史消息
+    if(!isNewChat){
+      loadMore?.();
+    }
   };
 
   const handleTouchMove = () => {
@@ -79,17 +83,17 @@ export const useChatScrollManager = (messagesLength: number, pageIndex: number,
   return {
     // refs
     scrollViewRef,
-    
+
     // 滚动相关状态
     scrollTop,
     keyboardHeight,
     marginTopOffset,
-    
+
     // 事件处理函数
     handleScrollToUpper,
     handleTouchMove,
     handleScrollToLower,
-    
+
     // 工具方法
     setScrollTop,
     setAutoScroll,

+ 9 - 9
src/pages/chat/hooks/useChatUI.tsx

@@ -11,7 +11,8 @@ import { useTextChat } from '@/store/textChatStore';
 export const useChatUI = (
   agent: any,
   historyListLength: number,
-  messageListLength: number
+  messageListLength: number,
+  isNewChat: boolean
 ): {
   showWelcome: boolean;
   haveBg: boolean;
@@ -19,9 +20,9 @@ export const useChatUI = (
   inputContainerBottomOffset: number;
   setShowWelcome: (show: boolean) => void;
   getBgContent: () => string;
-  createNavLeftRenderer: (PersonalCard: any, IconArrowLeftWhite24: any, IconArrowLeft: any) => () => JSX.Element;
+  createNavLeftRenderer: (PersonalCard: any, IconArrowLeftWhite24: any, IconArrowLeft: any, onClear: ()=>void) => () => JSX.Element;
 } => {
-  const [showWelcome, setShowWelcome] = useState(!historyListLength);
+  const [showWelcome, setShowWelcome] = useState(!historyListLength && !isNewChat);
   const [inputContainerHeight, setInputContainerHeight] = useState(0);
   // const scrollTop = useTextChat((state)=> state.scrollTop)
 
@@ -43,15 +44,14 @@ export const useChatUI = (
   };
 
   // 渲染导航栏左侧内容的工厂函数
-  const createNavLeftRenderer = (PersonalCard: any, IconArrowLeftWhite24: any, IconArrowLeft: any) => {
+  const createNavLeftRenderer = (PersonalCard: any, IconArrowLeftWhite24: any, IconArrowLeft: any, onClear: ()=>void) => {
     return () => {
       return (
         <View
           className="flex items-center gap-8"
-          onClick={() => Taro.navigateBack()}
         >
-          {haveBg ? <IconArrowLeftWhite24 /> : <IconArrowLeft />}
-          <PersonalCard agent={agent} haveBg={haveBg} />
+          <View onClick={() => Taro.navigateBack()}>{haveBg ? <IconArrowLeftWhite24 /> : <IconArrowLeft />}</View>
+          <PersonalCard agent={agent} haveBg={haveBg} onClear={onClear} />
           {/* {scrollTop} */}
         </View>
       );
@@ -60,8 +60,8 @@ export const useChatUI = (
 
   // 是否显示欢迎UI
   useEffect(() => {
-    setShowWelcome(!messageListLength && !historyListLength);
-  }, [historyListLength, messageListLength]);
+    setShowWelcome(!messageListLength && !historyListLength && !isNewChat);
+  }, [historyListLength, messageListLength, isNewChat]);
 
   // 设置导航栏颜色
   useEffect(() => {

+ 21 - 8
src/pages/chat/index.tsx

@@ -38,6 +38,8 @@ export default function Index() {
   // 使用抽离的 hooks
   const { agent } = useChatAgent(agentId, isVisitor);
 
+
+
   const {
     historyList,
     groupedMessages,
@@ -46,8 +48,11 @@ export default function Index() {
     pageIndex,
     mutate,
     resetAndLoadFirstPage,
+    resetAndClearData
   } = useChatMessages(agentId, isVisitor);
 
+  // const loadMoreCallback = isNewChat ? null : loadMore
+
   // 获取原始历史消息列表长度(用于UI状态判断)
   const rawHistoryListLength = historyList.length;
 
@@ -61,8 +66,16 @@ export default function Index() {
     handleScrollToLower,
   } = useChatScrollManager(messagesLength, pageIndex, loadMore);
 
+  // InputBar 相关状态
+  const [isVoice, setIsVoice] = useState(false);
+  const [disabled, setDisabled] = useState(false);
+
   // 获取当前消息列表长度
   const currentMessageListLength = useTextChat((state) => state.list.length);
+  const isNewChat = useTextChat((state) => state.isNewChat);
+  const messageStopHandle = useTextChat((state) => state.messageStopHandle);
+
+  const { destroy, genSessionId, clearList } = useTextChat();
 
   const {
     showWelcome,
@@ -72,19 +85,13 @@ export default function Index() {
     setShowWelcome,
     getBgContent,
     createNavLeftRenderer,
-  } = useChatUI(agent, rawHistoryListLength, currentMessageListLength);
+  } = useChatUI(agent, rawHistoryListLength, currentMessageListLength, isNewChat);
 
   const [streamVoiceEnable, setStreamVoiceEnable] = usePersistentState(
     "streamVoiceEnable",
     false
   );
 
-  // InputBar 相关状态
-  const [isVoice, setIsVoice] = useState(false);
-  const [disabled, setDisabled] = useState(false);
-
-  const { destroy, genSessionId } = useTextChat();
-
   // 统一的聊天输入逻辑 - 只在主组件中实例化一次
   const chatInputActions = useChatInput({
     agent,
@@ -112,7 +119,12 @@ export default function Index() {
   const onScrollToUpper = handleScrollToUpper;
 
   // 使用工厂函数创建导航栏左侧渲染器
-  const renderNavLeft = createNavLeftRenderer(PersonalCard, IconArrowLeftWhite24, IconArrowLeft);
+  const renderNavLeft = createNavLeftRenderer(PersonalCard, IconArrowLeftWhite24, IconArrowLeft, ()=> {
+    console.log('clear chat')
+    messageStopHandle?.();
+    clearList()
+    resetAndClearData()
+  });
   // console.log('----scrollTop: ', scrollTop, '----')
   return (
     <PageCustom
@@ -145,6 +157,7 @@ export default function Index() {
             className="flex flex-col gap-16 px-18"
             onTouchMove={handleTouchMove}
           >
+            {isNewChat && <View className="text-white-70 text-12 leading-20 text-center pt-22">聊聊新话题</View>}
             {showWelcome && <ChatGreeting agent={agent} chatInputActions={chatInputActions} />}
             {groupedMessages.map((group, groupIndex) => {
               return (

+ 11 - 2
src/store/textChatStore.ts

@@ -21,6 +21,7 @@ export interface TextChat {
   currentRobotMsgUk: string; // 当前正在说话的 AI 机器人 id, 可用于控制是否继续输出文本至当前 message 框内
   scrollTop: number; // 控制聊天内容变化后滚动至最底部
   autoScroll: boolean
+  isNewChat: boolean // 是否是新对话
   list: TAnyMessage[]
   questions: string[] //推荐问题
   sessionId: string|null
@@ -29,6 +30,7 @@ export interface TextChat {
   messageStopHandle: (()=>void) | null; // 停止接收智能文本体消或语音消息的句柄
   // 显示聊天历史
   setAutoScroll: (b: boolean) => void // 是否自动滚动
+  setIsNewChat: (b: boolean) => void // 是否是新对话
   genSessionId: () => void // 进入聊天界面后,本次的 sessionId
   setQuestions: (q:string[]) => void // 设置推荐问题
   setScrollTop: (num?: number) => void
@@ -44,6 +46,7 @@ export interface TextChat {
   deleteMessage: (msgUk: string) => void;
   // 清空
   destroy: () => void;
+  clearList: () => void;
   setMessageRespeaking: (respeaking: boolean)=> void
   setMessageStopHandle: (func: (()=> void) | null) => void
   setReacting: (reacting: boolean)=> void
@@ -59,6 +62,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
   currentRobotMsgUk: INIT_CURRENT_ROBOT_MSG_UK,
   scrollTop: 0,
   autoScroll: true,
+  isNewChat: false,
   list: [],
   sessionId: null,
   questions: [],
@@ -78,6 +82,9 @@ export const useTextChat = create<TextChat>((set, get) => ({
     // console.log('setAutoScroll:', b)
     set({autoScroll: b})
   },
+  setIsNewChat: (b)=> {
+    set({isNewChat: b})
+  },
   setQuestions: (q:string[])=> {
     set({questions: q})
   },
@@ -90,6 +97,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
   clearList: ()=> {
     set({
       list: [],
+      questions: [],
     });
   },
   // 重置
@@ -100,6 +108,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
       currentRobotMsgUk: INIT_CURRENT_ROBOT_MSG_UK,
       scrollTop: 0,
       sessionId: null,
+      isNewChat: false,
     });
   },
   pushRobotMessage: (message) => {
@@ -139,7 +148,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
         };
       });
     }, 100)
-    
+
     return {msgUk, sessionId}
   },
   setScrollTop: (num?: number)=> {
@@ -200,7 +209,7 @@ export const useTextChat = create<TextChat>((set, get) => ({
         }
         return message; // 返回未修改的 message
       });
-      
+
       return { list: updatedList}; // 返回新的状态
     });
   },

+ 3 - 1
src/utils/loadMoreInfinite.ts

@@ -41,6 +41,7 @@ export const createKey = (query: string, pageSize: number = 10, extra: Record<st
 }
 export const useLoadMoreInfinite = <T>(getKey, fetcher, params = {}) => {
   const {cache} = useSWRConfig()
+
   const { data, setSize, size, mutate, isLoading, error } = useSWRInfinite<{
     data: T
     nextId?: string
@@ -83,6 +84,7 @@ export const useLoadMoreInfinite = <T>(getKey, fetcher, params = {}) => {
     setSize(1)
     await mutate();
   }
+
   return {
     data,
     list,
@@ -96,4 +98,4 @@ export const useLoadMoreInfinite = <T>(getKey, fetcher, params = {}) => {
     loadMore,
     error,
   }
-}
+}

Nem az összes módosított fájl került megjelenítésre, mert túl sok fájl változott