index.tsx 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266
  1. import { View, ScrollView } from "@tarojs/components";
  2. import NavBarNormal from "@/components/NavBarNormal/index";
  3. import PageCustom from "@/components/page-custom/index";
  4. import Taro, { useRouter, useUnload } from "@tarojs/taro";
  5. import ChatMessage from "@/components/chat-message";
  6. import style from "./index.module.less";
  7. import InputBar from "./components/input-bar";
  8. import { useEffect, useState, useRef } from "react";
  9. import { useTextChat } from "@/store/textChat";
  10. import { EAI_MODEL } from "@/consts/enum";
  11. import type { TMessage, TRobotMessage } from "@/store/textChat";
  12. import ChatWelcome from "./components/chat-welcome";
  13. import IconArrowLeft from "@/components/icon/icon-arrow-left";
  14. import PersonalCard from "./components/personal-card";
  15. import { useAgentStore } from "@/store/agentStore";
  16. import { useLoadMore } from "@/utils/loadMore";
  17. import { getMessageHistories } from "@/service/bot";
  18. import { useAppStore } from "@/store/appStore";
  19. // 类型谓词函数
  20. function isRobotMessage(
  21. message: TMessage | TRobotMessage
  22. ): message is TRobotMessage {
  23. return "robot" in message && "reasoningContent" in message;
  24. }
  25. export default function Index() {
  26. const router = useRouter();
  27. const { agentId, isVisitor } = router.params;
  28. if (!agentId) {
  29. return <View>没有相应的智能体</View>;
  30. }
  31. const headerHeight = useAppStore((state) => state.headerHeight);
  32. const { fetchAgent, fetchAgentProfile, } = useAgentStore();
  33. const agent = useAgentStore((state) => {
  34. if(isVisitor === 'true'){
  35. return state.agentProfile
  36. }
  37. return state.agent
  38. });
  39. const [deepThink, setDeepThink] = useState(EAI_MODEL.DeepseekChat);
  40. const [histories, setHistories] = useState<(TMessage | TRobotMessage)[]>([]);
  41. const [keyboardHeight, setKeyboardHeight] = useState(0);
  42. const [contentHeight, setContentHeight] = useState(0);
  43. const [scrollViewHeight, setScrollViewHeight] = useState(0);
  44. const scrollViewRef = useRef<any>(null);
  45. const messageList = useTextChat((state) => state.list);
  46. const { destroy, setScrollTop, genSessionId } = useTextChat();
  47. const scrollTop = useTextChat((state) => state.scrollTop);
  48. const fetcher = async ([_url, nextId, page, pageSize]) => {
  49. if (!agent) {
  50. return null;
  51. }
  52. const _nextId = nextId ? decodeURIComponent(nextId) : nextId;
  53. const res = await getMessageHistories({
  54. agentId,
  55. startId: _nextId,
  56. pageSize,
  57. });
  58. return res.data;
  59. };
  60. const { data, loadMore, page } = useLoadMore<(TMessage | TRobotMessage)[]>({
  61. url: `messeagehistories${isVisitor}${agentId}`,
  62. fetcher,
  63. });
  64. const [showWelcome, setShowWelcome] = useState(!histories.length);
  65. // 加载更多
  66. const onScrollToUpper = () => {
  67. console.log("onscroll");
  68. loadMore();
  69. };
  70. useEffect(() => {
  71. if(agentId){
  72. if(isVisitor){
  73. fetchAgentProfile(agentId)
  74. }else{
  75. fetchAgent(agentId);
  76. }
  77. }
  78. }, [agentId, isVisitor]);
  79. useEffect(() => {
  80. setShowWelcome(!histories.length);
  81. }, [histories]);
  82. useEffect(() => {
  83. if (data?.data) {
  84. const combine = [...histories, ...data.data]
  85. setHistories(combine);
  86. if (page === 1) {
  87. setTimeout(() => {
  88. setScrollTop();
  89. }, 300);
  90. }
  91. }
  92. }, [data, page,]);
  93. // 计算 marginTopOffset 偏移的距离
  94. const marginTopOffset = (() => {
  95. if (keyboardHeight <= 0) return 0;
  96. // 如果内容超过滚动容器,取键盘弹起高度
  97. if (contentHeight > scrollViewHeight) {
  98. return -keyboardHeight;
  99. }
  100. // 如果内容+键盘弹起高度超过滚动容器, 则取其差值
  101. if (contentHeight + keyboardHeight > scrollViewHeight) {
  102. // 内容+键盘弹起高度 - 滚动容器高度
  103. return -(contentHeight + keyboardHeight - scrollViewHeight);
  104. }
  105. })();
  106. useEffect(() => {
  107. // 监听键盘高度变化
  108. Taro.onKeyboardHeightChange((res) => {
  109. if (res.height <= 0) {
  110. return setKeyboardHeight(0);
  111. }
  112. setKeyboardHeight(res.height - 24);
  113. });
  114. return () => {
  115. // 清理监听器
  116. Taro.offKeyboardHeightChange();
  117. };
  118. }, []);
  119. // 监听内容高度和 ScrollView 高度变化
  120. useEffect(() => {
  121. if (scrollViewRef.current) {
  122. const query = Taro.createSelectorQuery();
  123. // 获取聊天内容高度
  124. query
  125. .select("#message-list")
  126. .boundingClientRect((rect: any) => {
  127. if (rect) {
  128. setContentHeight(rect.height);
  129. }
  130. })
  131. .exec();
  132. // 获取滚动容器高度
  133. query
  134. .select("#scroll-view")
  135. .boundingClientRect((rect: any) => {
  136. if (rect) {
  137. setScrollViewHeight(rect.height);
  138. }
  139. })
  140. .exec();
  141. }
  142. }, [messageList]);
  143. useEffect(() => {
  144. genSessionId();
  145. }, []);
  146. useUnload(() => {
  147. destroy();
  148. });
  149. const switchDeepThink = () => {
  150. if (deepThink === EAI_MODEL.DeepseekChat) {
  151. setDeepThink(EAI_MODEL.DeepseekReasoner);
  152. return;
  153. }
  154. setDeepThink(EAI_MODEL.DeepseekChat);
  155. };
  156. const renderNavLeft = () => {
  157. return (
  158. <View
  159. className="flex items-center gap-8"
  160. onClick={() => Taro.navigateBack()}
  161. >
  162. <IconArrowLeft />
  163. <View className={showWelcome ? "hidden" : "block"}>
  164. <PersonalCard agent={agent} size="mini" />
  165. </View>
  166. </View>
  167. );
  168. };
  169. // 自定义背景样式
  170. const bgImageStyle = {
  171. backgroundImage: `url(${agent?.avatarUrl})`,
  172. };
  173. return (
  174. <PageCustom fullPage style={{ overflow: "hidden" }}>
  175. <NavBarNormal leftColumn={renderNavLeft}></NavBarNormal>
  176. {(!!agent?.enabledChatBg) ? <View className={style.topBg} style={bgImageStyle}></View> : <></>}
  177. <View
  178. className="flex flex-col w-full h-screen relative z-10"
  179. style={{ marginTop: `${marginTopOffset}px` }}
  180. >
  181. <ScrollView
  182. ref={scrollViewRef}
  183. scrollY
  184. id="scrollView"
  185. style={{
  186. flex: 1,
  187. height: "1px", // 高度自适应
  188. }}
  189. scrollTop={scrollTop}
  190. scrollWithAnimation
  191. onScrollToUpper={onScrollToUpper}
  192. >
  193. {showWelcome && <ChatWelcome agent={agent} />}
  194. <View id="messageList" className="flex flex-col gap-8 px-18 pb-140">
  195. {/* 复制 histories 再 reverse 否则会影响 state */}
  196. {[...[...histories].reverse(), ...messageList].map((message) => {
  197. const robotMessage = isRobotMessage(message) ? message : null;
  198. return (
  199. <ChatMessage
  200. key={message.msgUk}
  201. textReasoning={robotMessage?.reasoningContent}
  202. agent={agent}
  203. role={message.role}
  204. text={message.content}
  205. message={message}
  206. ></ChatMessage>
  207. );
  208. })}
  209. </View>
  210. </ScrollView>
  211. <View className="h-140 w-10"></View>
  212. <View
  213. className="fixed left-0 right-0 bottom-0 min-h-130 z-50"
  214. style={{
  215. bottom: `${keyboardHeight}px`,
  216. }}
  217. >
  218. {/* <View
  219. onClick={switchDeepThink}
  220. className={
  221. deepThink === EAI_MODEL.DeepseekReasoner
  222. ? style.deepMindMarkActive
  223. : style.deepMindMark
  224. }
  225. >
  226. 深度思考(R1)
  227. </View> */}
  228. {agent && (
  229. <InputBar
  230. aiModel={deepThink}
  231. agent={agent}
  232. histories={histories}
  233. setShowWelcome={setShowWelcome}
  234. ></InputBar>
  235. )}
  236. </View>
  237. </View>
  238. </PageCustom>
  239. );
  240. }