textChat.ts 7.5 KB

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