textChat.ts 6.7 KB

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