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 9.4KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366
  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, resetAnswerList } =
  161. useSendMessageBySSE(url || api.runCanvas);
  162. const messageId = useMemo(() => {
  163. return answerList[0]?.message_id;
  164. }, [answerList]);
  165. useEffect(() => {
  166. if (answerList[0]?.session_id) {
  167. setSessionId(answerList[0]?.session_id);
  168. }
  169. }, [answerList]);
  170. const { findReferenceByMessageId } = useFindMessageReference(answerList);
  171. const prologue = useGetBeginNodePrologue();
  172. const {
  173. derivedMessages,
  174. scrollRef,
  175. messageContainerRef,
  176. removeLatestMessage,
  177. removeMessageById,
  178. addNewestOneQuestion,
  179. addNewestOneAnswer,
  180. removeAllMessages,
  181. scrollToBottom,
  182. } = useSelectDerivedMessages();
  183. const { addEventList: addEventListFun } = useContext(AgentChatLogContext);
  184. const {
  185. appendUploadResponseList,
  186. clearUploadResponseList,
  187. uploadResponseList,
  188. fileList,
  189. } = useSetUploadResponseData();
  190. const sendMessage = useCallback(
  191. async ({ message }: { message: Message; messages?: Message[] }) => {
  192. const params: Record<string, unknown> = {
  193. id: agentId,
  194. };
  195. params.running_hint_text = i18n.t('flow.runningHintText', {
  196. defaultValue: 'is running...🕞',
  197. });
  198. if (message.content) {
  199. const query = inputs;
  200. params.query = message.content;
  201. // params.message_id = message.id;
  202. params.inputs = transferInputsArrayToObject(
  203. beginParams ? beginParams : query,
  204. ); // begin operator inputs
  205. params.files = uploadResponseList;
  206. params.session_id = sessionId;
  207. }
  208. try {
  209. const res = await send(params);
  210. clearUploadResponseList();
  211. if (receiveMessageError(res)) {
  212. sonnerMessage.error(res?.data?.message);
  213. // cancel loading
  214. setValue(message.content);
  215. removeLatestMessage();
  216. } else {
  217. // refetch(); // pull the message list after sending the message successfully
  218. }
  219. } catch (error) {
  220. console.log('🚀 ~ useSendAgentMessage ~ error:', error);
  221. }
  222. },
  223. [
  224. agentId,
  225. sessionId,
  226. send,
  227. clearUploadResponseList,
  228. inputs,
  229. beginParams,
  230. uploadResponseList,
  231. setValue,
  232. removeLatestMessage,
  233. ],
  234. );
  235. const sendFormMessage = useCallback(
  236. (body: { id?: string; inputs: Record<string, BeginQuery> }) => {
  237. send({ ...body, session_id: sessionId });
  238. addNewestOneQuestion({
  239. content: Object.entries(body.inputs)
  240. .map(([key, val]) => `${key}: ${val.value}`)
  241. .join('<br/>'),
  242. role: MessageType.User,
  243. });
  244. },
  245. [addNewestOneQuestion, send, sessionId],
  246. );
  247. // reset session
  248. const resetSession = useCallback(() => {
  249. stopOutputMessage();
  250. resetAnswerList();
  251. setSessionId(null);
  252. removeAllMessages();
  253. }, [resetAnswerList, removeAllMessages, stopOutputMessage]);
  254. const handlePressEnter = useCallback(() => {
  255. if (trim(value) === '') return;
  256. const id = uuid();
  257. const msgBody = {
  258. id,
  259. content: value.trim(),
  260. role: MessageType.User,
  261. };
  262. if (done) {
  263. setValue('');
  264. sendMessage({
  265. message: msgBody,
  266. });
  267. }
  268. addNewestOneQuestion({ ...msgBody, files: fileList });
  269. setTimeout(() => {
  270. scrollToBottom();
  271. }, 100);
  272. }, [
  273. value,
  274. done,
  275. addNewestOneQuestion,
  276. fileList,
  277. setValue,
  278. sendMessage,
  279. scrollToBottom,
  280. ]);
  281. useEffect(() => {
  282. const { content, id } = findMessageFromList(answerList);
  283. const inputAnswer = findInputFromList(answerList);
  284. if (answerList.length > 0) {
  285. addNewestOneAnswer({
  286. answer: content || getLatestError(answerList),
  287. id: id,
  288. ...inputAnswer,
  289. });
  290. }
  291. }, [answerList, addNewestOneAnswer]);
  292. useEffect(() => {
  293. if (prologue) {
  294. addNewestOneAnswer({
  295. answer: prologue,
  296. });
  297. }
  298. }, [addNewestOneAnswer, agentId, prologue, send, sendFormMessage]);
  299. useEffect(() => {
  300. if (typeof addEventList === 'function') {
  301. addEventList(answerList, messageId);
  302. } else if (typeof addEventListFun === 'function') {
  303. addEventListFun(answerList, messageId);
  304. }
  305. }, [addEventList, answerList, addEventListFun, messageId]);
  306. return {
  307. value,
  308. sendLoading: !done,
  309. derivedMessages,
  310. scrollRef,
  311. messageContainerRef,
  312. handlePressEnter,
  313. handleInputChange,
  314. removeMessageById,
  315. stopOutputMessage,
  316. send,
  317. sendFormMessage,
  318. resetSession,
  319. findReferenceByMessageId,
  320. appendUploadResponseList,
  321. addNewestOneAnswer,
  322. };
  323. };