textChatStore.ts 6.8 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209
  1. /**
  2. * 文本|语音聊天框
  3. */
  4. import { create } from "zustand";
  5. import { generateUUID } from '@/utils/index'
  6. import { EChatRole, TAnyMessage, TRobotMessage, TMessage } from "@/types/bot";
  7. import {getFormatNow} from '@/utils/timeUtils'
  8. type TRobotMessageWithOptionalId = Omit<TRobotMessage, "msgUk"> & {
  9. msgUk?: string; // 将 messageId 设置为可选
  10. reasoningContent?: string; // 添加 reasoningContent
  11. };
  12. const INIT_CURRENT_ROBOT_MSG_UK = ''
  13. export interface TextChat {
  14. currentRobotMsgUk: string; // 当前正在说话的 AI 机器人 id, 可用于控制是否继续输出文本至当前 message 框内
  15. scrollTop: number; // 控制聊天内容变化后滚动至最底部
  16. autoScroll: boolean
  17. list: TAnyMessage[]
  18. questions: string[] //推荐问题
  19. sessionId: string|null
  20. isReacting: boolean // 是否正在聊天交互
  21. messageRespeaking: boolean; // 当前正在重放回复消息内容 text to speech
  22. messageStopHandle: (()=>void) | null; // 停止接收智能文本体消或语音消息的句柄
  23. // 显示聊天历史
  24. setAutoScroll: (b: boolean) => void // 是否自动滚动
  25. genSessionId: () => void // 进入聊天界面后,本次的 sessionId
  26. setQuestions: (q:string[]) => void // 设置推荐问题
  27. setScrollTop: (num?: number) => void
  28. // 将机器人气泡框推入聊天框
  29. pushRobotMessage: (message: TRobotMessageWithOptionalId) => string;
  30. // 将自己发出的气泡框推入聊天框
  31. pushMessage: (content: string) => {msgUk: string, sessionId: string};
  32. // 更新自己发出的气泡框
  33. updateMessage: (content: string, msgUk: string) => string;
  34. // 更新机器人汽泡框内的内容实现 gpt 的效果
  35. updateRobotMessage: (content: string, body?: Record<string,any>, saveStatus?: number, replaceContent?: boolean) => void;
  36. getCurrentRobotMessage:() => TRobotMessage|undefined
  37. deleteMessage: (msgUk: string) => void;
  38. // 清空
  39. destroy: () => void;
  40. setMessageRespeaking: (respeaking: boolean)=> void
  41. setMessageStopHandle: (func: (()=> void) | null) => void
  42. setReacting: (reacting: boolean)=> void
  43. updateMessageReaction: (msgId: number, data: {isLike: boolean, isDislike: boolean})=> void
  44. }
  45. // 新messageId 为 index 加 1
  46. const generateUk = () => {
  47. return generateUUID();
  48. }
  49. export const useTextChat = create<TextChat>((set, get) => ({
  50. currentRobotMsgUk: INIT_CURRENT_ROBOT_MSG_UK,
  51. scrollTop: 0,
  52. autoScroll: true,
  53. list: [],
  54. sessionId: null,
  55. questions: [],
  56. isReacting: false,
  57. messageRespeaking: false,
  58. messageStopHandle: null,
  59. setMessageRespeaking: (respeaking)=> {
  60. set({messageRespeaking: respeaking})
  61. },
  62. setMessageStopHandle: (func)=> {
  63. set({messageStopHandle: func})
  64. },
  65. setReacting: (isReacting)=> {
  66. set({isReacting})
  67. },
  68. setAutoScroll: (b)=> {
  69. set({autoScroll: b})
  70. },
  71. setQuestions: (q:string[])=> {
  72. set({questions: q})
  73. },
  74. genSessionId: ()=> {
  75. const sessionId = generateUUID();
  76. set({
  77. sessionId
  78. })
  79. },
  80. // 重置
  81. destroy: () => {
  82. set({
  83. list: [],
  84. questions: [],
  85. currentRobotMsgUk: INIT_CURRENT_ROBOT_MSG_UK,
  86. scrollTop: 0,
  87. sessionId: null,
  88. });
  89. },
  90. pushRobotMessage: (message) => {
  91. const msgUk = generateUk()
  92. const sessionId = get().sessionId ?? ''
  93. set((state) => {
  94. const newRobotMessage = { ...message, msgUk, sessionId, msgTime: getFormatNow(), role: EChatRole.Assistant } as TRobotMessage
  95. return {
  96. list: [...state.list, newRobotMessage],
  97. currentRobotMsgUk: msgUk,
  98. scrollTop: state.scrollTop + 1,
  99. };
  100. });
  101. setTimeout(()=> {
  102. set((state) => {
  103. return {
  104. scrollTop: state.scrollTop + 1,
  105. };
  106. });
  107. }, 100)
  108. return msgUk
  109. },
  110. pushMessage: (content: string) => {
  111. const sessionId = get().sessionId ?? ''
  112. const msgUk = generateUk();
  113. const newMessage:TMessage ={ msgUk, sessionId, msgTime: getFormatNow(), content: content, role: EChatRole.User, saveStatus: 0 }
  114. set((state) => {
  115. return {
  116. list: [...state.list, newMessage],
  117. autoScroll: true
  118. };
  119. });
  120. setTimeout(()=> {
  121. set((state) => {
  122. return {
  123. scrollTop: state.scrollTop + 1,
  124. };
  125. });
  126. }, 100)
  127. return {msgUk, sessionId}
  128. },
  129. setScrollTop: (num?: number)=> {
  130. console.log('setScrollTop')
  131. set((state) => {
  132. if(num!== undefined){
  133. return {
  134. scrollTop: num,
  135. }
  136. }
  137. if(state.autoScroll){
  138. return {
  139. scrollTop: state.scrollTop + 1,
  140. };
  141. }
  142. return {scrollTop: state.scrollTop};
  143. });
  144. },
  145. updateMessage: (content, msgUk) => {
  146. set((state) => {
  147. const updatedList = state.list.map((message) => {
  148. if (message.msgUk === msgUk) {
  149. return { ...message, content: message.content + content, saveStatus: 0 }; // 更新 content
  150. }
  151. return message; // 返回未修改的 message
  152. });
  153. return { list: updatedList, scrollTop: state.scrollTop + 1 }; // 返回新的状态
  154. });
  155. return msgUk
  156. },
  157. updateRobotMessage: (content, body={}, saveStatus: number = 0, replaceContent: boolean = false) => {
  158. set((state) => {
  159. const updatedList = state.list.map((message) => {
  160. if (message.msgUk === state.currentRobotMsgUk) {
  161. // 如果是 replaceContent 则认为需要更新全部内容,否则在原 content 上追加
  162. const _content = replaceContent ? content : message.content + content;
  163. // 更新消息后, saveStatus 变为 0,说明又需要上报此消息
  164. return { ...message, content: _content, body, saveStatus: saveStatus } as TRobotMessage // 更新 content
  165. }
  166. return message; // 返回未修改的 message
  167. });
  168. return { list: updatedList, scrollTop: state.autoScroll ? state.scrollTop + 1 : state.scrollTop}; // 返回新的状态
  169. });
  170. },
  171. updateMessageReaction: (msgId, data)=> {
  172. set((state) => {
  173. const updatedList = state.list.map((message) => {
  174. if (message.msgId === msgId) {
  175. return { ...message, isLike: data.isLike, isDislike: data.isDislike} // 更新 content
  176. }
  177. return message; // 返回未修改的 message
  178. });
  179. return { list: updatedList}; // 返回新的状态
  180. });
  181. },
  182. getCurrentRobotMessage: ()=> {
  183. const state = get()
  184. const currentMessage = state.list.find((message)=> message.msgUk === state.currentRobotMsgUk) as TRobotMessage
  185. return currentMessage
  186. },
  187. deleteMessage: (msgUk)=> {
  188. set((state) => {
  189. // 如果对话框是空的,则删除
  190. const filtered = state.list.filter((message) => {
  191. const isEmptyContent = message.content.length <= 0
  192. return (message.msgUk !== msgUk && isEmptyContent)
  193. });
  194. return { list: filtered, scrollTop: state.scrollTop + 1 }; // 返回新的状态
  195. });
  196. }
  197. }));