index.tsx 5.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. import { View, ScrollView } from "@tarojs/components";
  2. import NavBarNormal from "@/components/nav-bar-normal/index";
  3. import PageCustom from "@/components/page-custom/index";
  4. import Taro, { useUnload } from "@tarojs/taro";
  5. import { useCurrentCharacter } from "@/store/characterStore";
  6. import ChatMessage from "@/components/chat-message";
  7. import style from "./index.module.less";
  8. // import { useAppStore } from "@/store/appStore";
  9. import InputBar from "./components/input-bar";
  10. import { useEffect, useState, useRef } from "react";
  11. import { useTextChat } from "@/store/textChat";
  12. import { EAI_MODEL } from "@/consts/enum";
  13. import type { TMessage, TRobotMessage } from "@/store/textChat";
  14. import ChatWelcome from './components/chat-welcome'
  15. import IconArrowLeft from "@/components/icon/icon-arrow-left";
  16. import PersonalCard from './components/personal-card'
  17. // 类型谓词函数
  18. function isRobotMessage(message: TMessage | TRobotMessage): message is TRobotMessage {
  19. return 'robot' in message && 'reasoningContent' in message;
  20. }
  21. export default function Index() {
  22. const [deepThink, setDeepThink] = useState(EAI_MODEL.DeepseekChat);
  23. const [showWelcome, setShowWelcome] = useState(true);
  24. const [keyboardHeight, setKeyboardHeight] = useState(0);
  25. const [contentHeight, setContentHeight] = useState(0);
  26. const [scrollViewHeight, setScrollViewHeight] = useState(0);
  27. const scrollViewRef = useRef<any>(null);
  28. // const headerHeight = useAppStore((state) => state.headerHeight);
  29. const character = useCurrentCharacter();
  30. const messageList = useTextChat((state) => state.list);
  31. const { destroy } = useTextChat();
  32. const scrollTop = useTextChat((state) => state.scrollTop);
  33. const { pushRobotMessage } = useTextChat();
  34. // 计算 marginTopOffset 偏移的距离
  35. const marginTopOffset = (() => {
  36. if (keyboardHeight <= 0) return 0;
  37. // 如果内容超过滚动容器,取键盘弹起高度
  38. if(contentHeight > scrollViewHeight){
  39. return -(keyboardHeight)
  40. }
  41. // 如果内容+键盘弹起高度超过滚动容器, 则取其差值
  42. if( contentHeight + keyboardHeight > scrollViewHeight){
  43. // 内容+键盘弹起高度 - 滚动容器高度
  44. return -(contentHeight + keyboardHeight - scrollViewHeight)
  45. }
  46. })();
  47. useUnload(() => {
  48. destroy();
  49. });
  50. useEffect(() => {
  51. // 监听键盘高度变化
  52. Taro.onKeyboardHeightChange((res) => {
  53. if(res.height <= 0){
  54. return setKeyboardHeight(0);
  55. }
  56. setShowWelcome(false)
  57. setKeyboardHeight(res.height - 24);
  58. });
  59. return () => {
  60. // 清理监听器
  61. Taro.offKeyboardHeightChange();
  62. };
  63. }, []);
  64. // 监听内容高度和 ScrollView 高度变化
  65. useEffect(() => {
  66. if (scrollViewRef.current) {
  67. const query = Taro.createSelectorQuery();
  68. // 获取聊天内容高度
  69. query.select('#messageList').boundingClientRect((rect: any) => {
  70. if (rect) {
  71. setContentHeight(rect.height);
  72. }
  73. }).exec();
  74. // 获取滚动容器高度
  75. query.select('#scrollView').boundingClientRect((rect: any) => {
  76. if (rect) {
  77. setScrollViewHeight(rect.height);
  78. }
  79. }).exec();
  80. }
  81. }, [messageList]);
  82. const switchDeepThink = () => {
  83. if (deepThink === EAI_MODEL.DeepseekChat) {
  84. setDeepThink(EAI_MODEL.DeepseekReasoner);
  85. return;
  86. }
  87. setDeepThink(EAI_MODEL.DeepseekChat);
  88. };
  89. useEffect(() => {
  90. // 主动打招呼
  91. if (character) {
  92. const greetingText = `Hi~我是${character.name},和我聊聊吧~`;
  93. pushRobotMessage({
  94. content: greetingText,
  95. reasoningContent: "",
  96. robot: {
  97. avatar: character.avatar ?? "",
  98. name: character.name ?? "",
  99. profileId: character.profileId ?? "",
  100. },
  101. });
  102. }
  103. }, []);
  104. const renderNavLeft = ()=> {
  105. return (
  106. <View className="flex items-center gap-8">
  107. <IconArrowLeft />
  108. <View className={showWelcome ? 'hidden' : 'block'}>
  109. <PersonalCard size="mini" />
  110. </View>
  111. </View>
  112. )
  113. }
  114. return (
  115. <PageCustom>
  116. <NavBarNormal scrollFadeIn leftColumn={renderNavLeft}></NavBarNormal>
  117. <View className="flex flex-col w-full h-screen">
  118. <ScrollView
  119. ref={scrollViewRef}
  120. scrollY
  121. id="scrollView"
  122. style={{
  123. flex: 1,
  124. height: "1px", // 高度自适应
  125. }}
  126. scrollTop={scrollTop}
  127. scrollWithAnimation
  128. >
  129. {showWelcome && <ChatWelcome/>}
  130. <View id="messageList" className="flex flex-col gap-8 px-18 pb-140">
  131. {messageList.map((message) => {
  132. const robotMessage = isRobotMessage(message) ? message : null;
  133. return (
  134. <ChatMessage
  135. key={message.messageId}
  136. textReasoning={robotMessage?.reasoningContent}
  137. character={robotMessage?.robot}
  138. text={message.content}
  139. ></ChatMessage>
  140. );
  141. })}
  142. </View>
  143. </ScrollView>
  144. <View className="h-140 w-10"></View>
  145. <View
  146. className="fixed left-0 right-0 bottom-0 min-h-130 z-50"
  147. style={{
  148. bottom: `${keyboardHeight}px`
  149. }}
  150. >
  151. <View
  152. onClick={switchDeepThink}
  153. className={
  154. deepThink === EAI_MODEL.DeepseekReasoner
  155. ? style.deepMindMarkActive
  156. : style.deepMindMark
  157. }
  158. >
  159. 深度思考(R1)
  160. </View>
  161. <InputBar aiModel={deepThink} character={null}></InputBar>
  162. </View>
  163. </View>
  164. </PageCustom>
  165. );
  166. }