You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

use-send-agent-message.ts 8.9KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342
  1. import sonnerMessage from '@/components/ui/message';
  2. import { MessageType } from '@/constants/chat';
  3. import {
  4. useHandleMessageInputChange,
  5. useSelectDerivedMessages,
  6. } from '@/hooks/logic-hooks';
  7. import {
  8. IEventList,
  9. IInputEvent,
  10. IMessageEndData,
  11. IMessageEndEvent,
  12. IMessageEvent,
  13. MessageEventType,
  14. useSendMessageBySSE,
  15. } from '@/hooks/use-send-message';
  16. import { Message } from '@/interfaces/database/chat';
  17. import i18n from '@/locales/config';
  18. import api from '@/utils/api';
  19. import { get } from 'lodash';
  20. import trim from 'lodash/trim';
  21. import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
  22. import { useParams } from 'umi';
  23. import { v4 as uuid } from 'uuid';
  24. import { BeginId } from '../constant';
  25. import { AgentChatLogContext } from '../context';
  26. import { transferInputsArrayToObject } from '../form/begin-form/use-watch-change';
  27. import { useSelectBeginNodeDataInputs } from '../hooks/use-get-begin-query';
  28. import { BeginQuery } from '../interface';
  29. import useGraphStore from '../store';
  30. import { receiveMessageError } from '../utils';
  31. export function findMessageFromList(eventList: IEventList) {
  32. const messageEventList = eventList.filter(
  33. (x) => x.event === MessageEventType.Message,
  34. ) as IMessageEvent[];
  35. let nextContent = '';
  36. let startIndex = -1;
  37. let endIndex = -1;
  38. messageEventList.forEach((x, idx) => {
  39. const { data } = x;
  40. const { content, start_to_think, end_to_think } = data;
  41. if (start_to_think === true) {
  42. nextContent += '<think>' + content;
  43. startIndex = idx;
  44. return;
  45. }
  46. if (end_to_think === true) {
  47. endIndex = idx;
  48. nextContent += content + '</think>';
  49. return;
  50. }
  51. nextContent += content;
  52. });
  53. const currentIdx = messageEventList.length - 1;
  54. // Make sure that after start_to_think === true and before end_to_think === true, add a </think> tag at the end.
  55. if (startIndex >= 0 && startIndex <= currentIdx && endIndex === -1) {
  56. nextContent += '</think>';
  57. }
  58. return {
  59. id: eventList[0]?.message_id,
  60. content: nextContent,
  61. };
  62. }
  63. export function findInputFromList(eventList: IEventList) {
  64. const inputEvent = eventList.find(
  65. (x) => x.event === MessageEventType.UserInputs,
  66. ) as IInputEvent;
  67. if (!inputEvent) {
  68. return {};
  69. }
  70. return {
  71. id: inputEvent?.message_id,
  72. data: inputEvent?.data,
  73. };
  74. }
  75. export function getLatestError(eventList: IEventList) {
  76. return get(eventList.at(-1), 'data.outputs._ERROR');
  77. }
  78. export const useGetBeginNodePrologue = () => {
  79. const getNode = useGraphStore((state) => state.getNode);
  80. return useMemo(() => {
  81. const formData = get(getNode(BeginId), 'data.form', {});
  82. if (formData?.enablePrologue) {
  83. return formData?.prologue;
  84. }
  85. }, [getNode]);
  86. };
  87. export function useFindMessageReference(answerList: IEventList) {
  88. const [messageEndEventList, setMessageEndEventList] = useState<
  89. IMessageEndEvent[]
  90. >([]);
  91. const findReferenceByMessageId = useCallback(
  92. (messageId: string) => {
  93. const event = messageEndEventList.find(
  94. (item) => item.message_id === messageId,
  95. );
  96. if (event) {
  97. return (event?.data as IMessageEndData)?.reference;
  98. }
  99. },
  100. [messageEndEventList],
  101. );
  102. useEffect(() => {
  103. const messageEndEvent = answerList.find(
  104. (x) => x.event === MessageEventType.MessageEnd,
  105. );
  106. if (messageEndEvent) {
  107. setMessageEndEventList((list) => {
  108. const nextList = [...list];
  109. if (
  110. nextList.every((x) => x.message_id !== messageEndEvent.message_id)
  111. ) {
  112. nextList.push(messageEndEvent as IMessageEndEvent);
  113. }
  114. return nextList;
  115. });
  116. }
  117. }, [answerList]);
  118. return { findReferenceByMessageId };
  119. }
  120. interface UploadResponseDataType {
  121. created_at: number;
  122. created_by: string;
  123. extension: string;
  124. id: string;
  125. mime_type: string;
  126. name: string;
  127. preview_url: null;
  128. size: number;
  129. }
  130. export function useSetUploadResponseData() {
  131. const [uploadResponseList, setUploadResponseList] = useState<
  132. UploadResponseDataType[]
  133. >([]);
  134. const [fileList, setFileList] = useState<File[]>([]);
  135. const append = useCallback((data: UploadResponseDataType, files: File[]) => {
  136. setUploadResponseList((prev) => [...prev, data]);
  137. setFileList((pre) => [...pre, ...files]);
  138. }, []);
  139. const clear = useCallback(() => {
  140. setUploadResponseList([]);
  141. setFileList([]);
  142. }, []);
  143. return {
  144. uploadResponseList,
  145. fileList,
  146. setUploadResponseList,
  147. appendUploadResponseList: append,
  148. clearUploadResponseList: clear,
  149. };
  150. }
  151. export const useSendAgentMessage = (
  152. url?: string,
  153. addEventList?: (data: IEventList, messageId: string) => void,
  154. beginParams?: any[],
  155. ) => {
  156. const { id: agentId } = useParams();
  157. const { handleInputChange, value, setValue } = useHandleMessageInputChange();
  158. const inputs = useSelectBeginNodeDataInputs();
  159. const [sessionId, setSessionId] = useState<string | null>(null);
  160. const { send, answerList, done, stopOutputMessage } = useSendMessageBySSE(
  161. url || api.runCanvas,
  162. );
  163. const messageId = useMemo(() => {
  164. return answerList[0]?.message_id;
  165. }, [answerList]);
  166. useEffect(() => {
  167. if (answerList[0]?.session_id) {
  168. setSessionId(answerList[0]?.session_id);
  169. }
  170. }, [answerList]);
  171. const { findReferenceByMessageId } = useFindMessageReference(answerList);
  172. const prologue = useGetBeginNodePrologue();
  173. const {
  174. derivedMessages,
  175. ref,
  176. removeLatestMessage,
  177. removeMessageById,
  178. addNewestOneQuestion,
  179. addNewestOneAnswer,
  180. } = useSelectDerivedMessages();
  181. const { addEventList: addEventListFun } = useContext(AgentChatLogContext);
  182. const {
  183. appendUploadResponseList,
  184. clearUploadResponseList,
  185. uploadResponseList,
  186. fileList,
  187. } = useSetUploadResponseData();
  188. const sendMessage = useCallback(
  189. async ({ message }: { message: Message; messages?: Message[] }) => {
  190. const params: Record<string, unknown> = {
  191. id: agentId,
  192. };
  193. params.running_hint_text = i18n.t('flow.runningHintText', {
  194. defaultValue: 'is running...🕞',
  195. });
  196. if (message.content) {
  197. const query = inputs;
  198. params.query = message.content;
  199. // params.message_id = message.id;
  200. params.inputs = transferInputsArrayToObject(
  201. beginParams ? beginParams : query,
  202. ); // begin operator inputs
  203. params.files = uploadResponseList;
  204. params.session_id = sessionId;
  205. }
  206. try {
  207. const res = await send(params);
  208. clearUploadResponseList();
  209. if (receiveMessageError(res)) {
  210. sonnerMessage.error(res?.data?.message);
  211. // cancel loading
  212. setValue(message.content);
  213. removeLatestMessage();
  214. } else {
  215. // refetch(); // pull the message list after sending the message successfully
  216. }
  217. } catch (error) {
  218. console.log('🚀 ~ useSendAgentMessage ~ error:', error);
  219. }
  220. },
  221. [
  222. agentId,
  223. sessionId,
  224. send,
  225. clearUploadResponseList,
  226. inputs,
  227. beginParams,
  228. uploadResponseList,
  229. setValue,
  230. removeLatestMessage,
  231. ],
  232. );
  233. const sendFormMessage = useCallback(
  234. (body: { id?: string; inputs: Record<string, BeginQuery> }) => {
  235. send(body);
  236. addNewestOneQuestion({
  237. content: Object.entries(body.inputs)
  238. .map(([key, val]) => `${key}: ${val.value}`)
  239. .join('<br/>'),
  240. role: MessageType.User,
  241. });
  242. },
  243. [addNewestOneQuestion, send],
  244. );
  245. const handlePressEnter = useCallback(() => {
  246. if (trim(value) === '') return;
  247. const id = uuid();
  248. const msgBody = {
  249. id,
  250. content: value.trim(),
  251. role: MessageType.User,
  252. };
  253. if (done) {
  254. setValue('');
  255. sendMessage({
  256. message: msgBody,
  257. });
  258. }
  259. addNewestOneQuestion({ ...msgBody, files: fileList });
  260. }, [value, done, addNewestOneQuestion, fileList, setValue, sendMessage]);
  261. useEffect(() => {
  262. const { content, id } = findMessageFromList(answerList);
  263. const inputAnswer = findInputFromList(answerList);
  264. if (answerList.length > 0) {
  265. addNewestOneAnswer({
  266. answer: content || getLatestError(answerList),
  267. id: id,
  268. ...inputAnswer,
  269. });
  270. }
  271. }, [answerList, addNewestOneAnswer]);
  272. useEffect(() => {
  273. if (prologue) {
  274. addNewestOneAnswer({
  275. answer: prologue,
  276. });
  277. }
  278. }, [addNewestOneAnswer, agentId, prologue, send, sendFormMessage]);
  279. useEffect(() => {
  280. if (typeof addEventList === 'function') {
  281. addEventList(answerList, messageId);
  282. } else if (typeof addEventListFun === 'function') {
  283. addEventListFun(answerList, messageId);
  284. }
  285. }, [addEventList, answerList, addEventListFun, messageId]);
  286. return {
  287. value,
  288. sendLoading: !done,
  289. derivedMessages,
  290. ref,
  291. handlePressEnter,
  292. handleInputChange,
  293. removeMessageById,
  294. stopOutputMessage,
  295. send,
  296. sendFormMessage,
  297. findReferenceByMessageId,
  298. appendUploadResponseList,
  299. };
  300. };