import sonnerMessage from '@/components/ui/message';
import { MessageType } from '@/constants/chat';
import {
useHandleMessageInputChange,
useSelectDerivedMessages,
} from '@/hooks/logic-hooks';
import {
IEventList,
IInputEvent,
IMessageEndData,
IMessageEndEvent,
IMessageEvent,
MessageEventType,
useSendMessageBySSE,
} from '@/hooks/use-send-message';
import { Message } from '@/interfaces/database/chat';
import i18n from '@/locales/config';
import api from '@/utils/api';
import { get } from 'lodash';
import trim from 'lodash/trim';
import { useCallback, useContext, useEffect, useMemo, useState } from 'react';
import { useParams } from 'umi';
import { v4 as uuid } from 'uuid';
import { BeginId } from '../constant';
import { AgentChatLogContext } from '../context';
import { transferInputsArrayToObject } from '../form/begin-form/use-watch-change';
import { useSelectBeginNodeDataInputs } from '../hooks/use-get-begin-query';
import { BeginQuery } from '../interface';
import useGraphStore from '../store';
import { receiveMessageError } from '../utils';
export function findMessageFromList(eventList: IEventList) {
const messageEventList = eventList.filter(
(x) => x.event === MessageEventType.Message,
) as IMessageEvent[];
let nextContent = '';
let startIndex = -1;
let endIndex = -1;
messageEventList.forEach((x, idx) => {
const { data } = x;
const { content, start_to_think, end_to_think } = data;
if (start_to_think === true) {
nextContent += '' + content;
startIndex = idx;
return;
}
if (end_to_think === true) {
endIndex = idx;
nextContent += content + '';
return;
}
nextContent += content;
});
const currentIdx = messageEventList.length - 1;
// Make sure that after start_to_think === true and before end_to_think === true, add a tag at the end.
if (startIndex >= 0 && startIndex <= currentIdx && endIndex === -1) {
nextContent += '';
}
return {
id: eventList[0]?.message_id,
content: nextContent,
};
}
export function findInputFromList(eventList: IEventList) {
const inputEvent = eventList.find(
(x) => x.event === MessageEventType.UserInputs,
) as IInputEvent;
if (!inputEvent) {
return {};
}
return {
id: inputEvent?.message_id,
data: inputEvent?.data,
};
}
export function getLatestError(eventList: IEventList) {
return get(eventList.at(-1), 'data.outputs._ERROR');
}
export const useGetBeginNodePrologue = () => {
const getNode = useGraphStore((state) => state.getNode);
return useMemo(() => {
const formData = get(getNode(BeginId), 'data.form', {});
if (formData?.enablePrologue) {
return formData?.prologue;
}
}, [getNode]);
};
export function useFindMessageReference(answerList: IEventList) {
const [messageEndEventList, setMessageEndEventList] = useState<
IMessageEndEvent[]
>([]);
const findReferenceByMessageId = useCallback(
(messageId: string) => {
const event = messageEndEventList.find(
(item) => item.message_id === messageId,
);
if (event) {
return (event?.data as IMessageEndData)?.reference;
}
},
[messageEndEventList],
);
useEffect(() => {
const messageEndEvent = answerList.find(
(x) => x.event === MessageEventType.MessageEnd,
);
if (messageEndEvent) {
setMessageEndEventList((list) => {
const nextList = [...list];
if (
nextList.every((x) => x.message_id !== messageEndEvent.message_id)
) {
nextList.push(messageEndEvent as IMessageEndEvent);
}
return nextList;
});
}
}, [answerList]);
return { findReferenceByMessageId };
}
interface UploadResponseDataType {
created_at: number;
created_by: string;
extension: string;
id: string;
mime_type: string;
name: string;
preview_url: null;
size: number;
}
export function useSetUploadResponseData() {
const [uploadResponseList, setUploadResponseList] = useState<
UploadResponseDataType[]
>([]);
const [fileList, setFileList] = useState([]);
const append = useCallback((data: UploadResponseDataType, files: File[]) => {
setUploadResponseList((prev) => [...prev, data]);
setFileList((pre) => [...pre, ...files]);
}, []);
const clear = useCallback(() => {
setUploadResponseList([]);
setFileList([]);
}, []);
return {
uploadResponseList,
fileList,
setUploadResponseList,
appendUploadResponseList: append,
clearUploadResponseList: clear,
};
}
export const useSendAgentMessage = (
url?: string,
addEventList?: (data: IEventList, messageId: string) => void,
beginParams?: any[],
) => {
const { id: agentId } = useParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const inputs = useSelectBeginNodeDataInputs();
const [sessionId, setSessionId] = useState(null);
const { send, answerList, done, stopOutputMessage, resetAnswerList } =
useSendMessageBySSE(url || api.runCanvas);
const messageId = useMemo(() => {
return answerList[0]?.message_id;
}, [answerList]);
useEffect(() => {
if (answerList[0]?.session_id) {
setSessionId(answerList[0]?.session_id);
}
}, [answerList]);
const { findReferenceByMessageId } = useFindMessageReference(answerList);
const prologue = useGetBeginNodePrologue();
const {
derivedMessages,
scrollRef,
messageContainerRef,
removeLatestMessage,
removeMessageById,
addNewestOneQuestion,
addNewestOneAnswer,
removeAllMessages,
scrollToBottom,
} = useSelectDerivedMessages();
const { addEventList: addEventListFun } = useContext(AgentChatLogContext);
const {
appendUploadResponseList,
clearUploadResponseList,
uploadResponseList,
fileList,
} = useSetUploadResponseData();
const sendMessage = useCallback(
async ({ message }: { message: Message; messages?: Message[] }) => {
const params: Record = {
id: agentId,
};
params.running_hint_text = i18n.t('flow.runningHintText', {
defaultValue: 'is running...🕞',
});
if (message.content) {
const query = inputs;
params.query = message.content;
// params.message_id = message.id;
params.inputs = transferInputsArrayToObject(
beginParams ? beginParams : query,
); // begin operator inputs
params.files = uploadResponseList;
params.session_id = sessionId;
}
try {
const res = await send(params);
clearUploadResponseList();
if (receiveMessageError(res)) {
sonnerMessage.error(res?.data?.message);
// cancel loading
setValue(message.content);
removeLatestMessage();
} else {
// refetch(); // pull the message list after sending the message successfully
}
} catch (error) {
console.log('🚀 ~ useSendAgentMessage ~ error:', error);
}
},
[
agentId,
sessionId,
send,
clearUploadResponseList,
inputs,
beginParams,
uploadResponseList,
setValue,
removeLatestMessage,
],
);
const sendFormMessage = useCallback(
(body: { id?: string; inputs: Record }) => {
send({ ...body, session_id: sessionId });
addNewestOneQuestion({
content: Object.entries(body.inputs)
.map(([key, val]) => `${key}: ${val.value}`)
.join('
'),
role: MessageType.User,
});
},
[addNewestOneQuestion, send, sessionId],
);
// reset session
const resetSession = useCallback(() => {
stopOutputMessage();
resetAnswerList();
setSessionId(null);
removeAllMessages();
}, [resetAnswerList, removeAllMessages, stopOutputMessage]);
const handlePressEnter = useCallback(() => {
if (trim(value) === '') return;
const id = uuid();
const msgBody = {
id,
content: value.trim(),
role: MessageType.User,
};
if (done) {
setValue('');
sendMessage({
message: msgBody,
});
}
addNewestOneQuestion({ ...msgBody, files: fileList });
setTimeout(() => {
scrollToBottom();
}, 100);
}, [
value,
done,
addNewestOneQuestion,
fileList,
setValue,
sendMessage,
scrollToBottom,
]);
useEffect(() => {
const { content, id } = findMessageFromList(answerList);
const inputAnswer = findInputFromList(answerList);
if (answerList.length > 0) {
addNewestOneAnswer({
answer: content || getLatestError(answerList),
id: id,
...inputAnswer,
});
}
}, [answerList, addNewestOneAnswer]);
useEffect(() => {
if (prologue) {
addNewestOneAnswer({
answer: prologue,
});
}
}, [addNewestOneAnswer, agentId, prologue, send, sendFormMessage]);
useEffect(() => {
if (typeof addEventList === 'function') {
addEventList(answerList, messageId);
} else if (typeof addEventListFun === 'function') {
addEventListFun(answerList, messageId);
}
}, [addEventList, answerList, addEventListFun, messageId]);
return {
value,
sendLoading: !done,
derivedMessages,
scrollRef,
messageContainerRef,
handlePressEnter,
handleInputChange,
removeMessageById,
stopOutputMessage,
send,
sendFormMessage,
resetSession,
findReferenceByMessageId,
appendUploadResponseList,
addNewestOneAnswer,
};
};