index.tsx 7.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236
  1. import { View, ScrollView } from "@tarojs/components";
  2. import NavBarNormal from "@/components/NavBarNormal/index";
  3. import PageCustom from "@/components/page-custom/index";
  4. import { useDidShow, useRouter, useUnload } from "@tarojs/taro";
  5. import ChatMessage from "@/components/chat-message";
  6. import InputBar from "./components/InputBar";
  7. import { useEffect, useState } from "react";
  8. import { useTextChat } from "@/store/textChatStore";
  9. import { formatMessageTime } from "@/utils/timeUtils";
  10. import ChatGreeting from "./components/ChatGreeting";
  11. import IconArrowLeftWhite24 from "@/components/icon/IconArrowLeftWhite24";
  12. import IconArrowLeft from "@/components/icon/icon-arrow-left";
  13. import PersonalCard from "./components/PersonalCard";
  14. import ButtonEnableStreamVoice from "./components/OptionButtons/ButtonEnableStreamVoice";
  15. import RecommendQuestions from "./components/RecommendQuestions";
  16. import { usePersistentState } from "@/hooks/usePersistentState";
  17. // 导入我们抽离的 hooks 和常量
  18. import {
  19. useChatMessages,
  20. useChatScrollManager,
  21. useChatUI,
  22. useChatAgent,
  23. } from "./hooks";
  24. import { useChatInput } from "./components/InputBar/useChatInput";
  25. export default function Index() {
  26. const router = useRouter();
  27. const { agentId, isVisitor } = router.params;
  28. if (!agentId) {
  29. return <View>没有相应的智能体</View>;
  30. }
  31. // 使用抽离的 hooks
  32. const { agent } = useChatAgent(agentId, isVisitor);
  33. const {
  34. historyList,
  35. groupedMessages,
  36. messagesLength,
  37. loadMore,
  38. pageIndex,
  39. mutate,
  40. resetAndLoadFirstPage,
  41. } = useChatMessages(agentId, isVisitor);
  42. // 获取原始历史消息列表长度(用于UI状态判断)
  43. const rawHistoryListLength = historyList.length;
  44. const {
  45. scrollViewRef,
  46. scrollTop,
  47. keyboardHeight,
  48. marginTopOffset,
  49. handleScrollToUpper,
  50. handleTouchMove,
  51. handleScrollToLower,
  52. } = useChatScrollManager(messagesLength, pageIndex, loadMore);
  53. // 获取当前消息列表长度
  54. const currentMessageListLength = useTextChat((state) => state.list.length);
  55. const {
  56. showWelcome,
  57. haveBg,
  58. inputContainerHeight,
  59. inputContainerBottomOffset,
  60. setShowWelcome,
  61. getBgContent,
  62. createNavLeftRenderer,
  63. } = useChatUI(agent, rawHistoryListLength, currentMessageListLength);
  64. const [streamVoiceEnable, setStreamVoiceEnable] = usePersistentState(
  65. "streamVoiceEnable",
  66. false
  67. );
  68. // InputBar 相关状态
  69. const [isVoice, setIsVoice] = useState(false);
  70. const [disabled, setDisabled] = useState(false);
  71. const { destroy, genSessionId, clearList } = useTextChat();
  72. // 统一的聊天输入逻辑 - 只在主组件中实例化一次
  73. const chatInputActions = useChatInput({
  74. agent,
  75. enableOutputAudioStream: streamVoiceEnable,
  76. setShowWelcome,
  77. setIsVoice,
  78. setDisabled,
  79. historyList,
  80. });
  81. // 页面显示时刷新数据
  82. useDidShow(() => {
  83. // 需要清掉当前已经进行的聊天
  84. // clearList();
  85. });
  86. // 首次进入聊天生成 session id
  87. useEffect(() => {
  88. genSessionId();
  89. // 重新拉取第一页数据,
  90. resetAndLoadFirstPage()
  91. }, [genSessionId]);
  92. // 页面卸载时清理
  93. useUnload(() => {
  94. destroy();
  95. });
  96. // 加载更多的处理函数(已经在 useChatScrollManager 中处理)
  97. const onScrollToUpper = handleScrollToUpper;
  98. // 使用工厂函数创建导航栏左侧渲染器
  99. const renderNavLeft = createNavLeftRenderer(PersonalCard, IconArrowLeftWhite24, IconArrowLeft);
  100. // console.log('----scrollTop: ', scrollTop, '----')
  101. return (
  102. <PageCustom
  103. fullPage
  104. style={{ overflow: "hidden" }}
  105. styleBg={getBgContent()}
  106. >
  107. <NavBarNormal blur leftColumn={renderNavLeft}>
  108. {/* <>{`${scrollTop}`}--</> */}
  109. </NavBarNormal>
  110. <View
  111. className="flex flex-col w-full h-full relative z-10 flex-1"
  112. style={{ top: `${marginTopOffset}px` }}
  113. >
  114. <ScrollView
  115. ref={scrollViewRef}
  116. scrollY
  117. id="scrollView"
  118. style={{
  119. flex: 1,
  120. height: "1px", // 高度自适应
  121. }}
  122. scrollTop={scrollTop}
  123. scrollWithAnimation
  124. onScrollToUpper={onScrollToUpper}
  125. onScrollToLower={handleScrollToLower}
  126. >
  127. <View
  128. id="messageList"
  129. className="flex flex-col gap-16 px-18"
  130. onTouchMove={handleTouchMove}
  131. >
  132. {showWelcome && <ChatGreeting agent={agent} chatInputActions={chatInputActions} />}
  133. {groupedMessages.map((group, groupIndex) => {
  134. return (
  135. <View key={groupIndex} className="flex flex-col gap-16">
  136. <View
  137. className={`text-12 leading-20 block text-center w-full ${
  138. haveBg
  139. ? "text-white-70"
  140. : "text-black-25"
  141. }`}
  142. >
  143. {formatMessageTime(group.dt)}
  144. </View>
  145. {group.list.map((message) => {
  146. const reasoningContent =
  147. (message as any).reasoningContent || "";
  148. return (
  149. <ChatMessage
  150. key={message.msgUk}
  151. textReasoning={reasoningContent}
  152. agent={agent}
  153. role={message.role}
  154. text={message.content}
  155. message={message}
  156. mutate={mutate as any}
  157. />
  158. );
  159. })}
  160. </View>
  161. );
  162. })}
  163. </View>
  164. <View className="pb-40 pt-8">
  165. {agent && (
  166. <RecommendQuestions
  167. enableOutputAudioStream={streamVoiceEnable}
  168. agent={agent}
  169. chatInputActions={chatInputActions}
  170. />
  171. )}
  172. </View>
  173. </ScrollView>
  174. <View
  175. className="w-full h-60"
  176. style={{
  177. height: inputContainerHeight,
  178. }}
  179. ></View>
  180. {/* ${keyboardHeight <= 0 ? 'transition-[bottom] delay-300' : ''} */}
  181. <View
  182. className={`bottom-bar px-16 pt-12 z-50`}
  183. id="inputContainer"
  184. style={{
  185. bottom: `${keyboardHeight + inputContainerBottomOffset}px`,
  186. }}
  187. >
  188. <View className="bg-[#F5FAFF]">
  189. {agent && (
  190. <View className="flex flex-col w-full gap-8">
  191. <InputBar
  192. enableOutputAudioStream={streamVoiceEnable}
  193. agent={agent}
  194. // histories={historyList}
  195. setShowWelcome={setShowWelcome}
  196. chatInputActions={chatInputActions}
  197. isVoice={isVoice}
  198. setIsVoice={setIsVoice}
  199. disabled={disabled}
  200. />
  201. <View>
  202. <ButtonEnableStreamVoice
  203. setEnable={setStreamVoiceEnable}
  204. enable={streamVoiceEnable}
  205. />
  206. </View>
  207. </View>
  208. )}
  209. </View>
  210. </View>
  211. </View>
  212. </PageCustom>
  213. );
  214. }