| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663 |
- import { MessageType } from '@/constants/chat';
- import { fileIconMap } from '@/constants/common';
- import {
- useFetchManualConversation,
- useFetchManualDialog,
- useFetchNextConversation,
- useFetchNextConversationList,
- useFetchNextDialog,
- useGetChatSearchParams,
- useRemoveNextConversation,
- useRemoveNextDialog,
- useSetNextDialog,
- useUpdateNextConversation,
- } from '@/hooks/chat-hooks';
- import {
- useSetModalState,
- useShowDeleteConfirm,
- useTranslate,
- } from '@/hooks/common-hooks';
- import {
- useRemoveMessageById,
- useSendMessageWithSse,
- } from '@/hooks/logic-hooks';
- import {
- IAnswer,
- IConversation,
- IDialog,
- Message,
- } from '@/interfaces/database/chat';
- import { IChunk } from '@/interfaces/database/knowledge';
- import { getFileExtension } from '@/utils';
- import { buildMessageUuid } from '@/utils/chat';
- import { useMutationState } from '@tanstack/react-query';
- import { get } from 'lodash';
- import trim from 'lodash/trim';
- import {
- ChangeEventHandler,
- useCallback,
- useEffect,
- useMemo,
- useRef,
- useState,
- } from 'react';
- import { useSearchParams } from 'umi';
- import { v4 as uuid } from 'uuid';
- import { ChatSearchParams } from './constants';
- import {
- IClientConversation,
- IMessage,
- VariableTableDataType,
- } from './interface';
-
- export const useSelectCurrentDialog = () => {
- const data = useMutationState({
- filters: { mutationKey: ['fetchDialog'] },
- select: (mutation) => {
- return get(mutation, 'state.data.data', {});
- },
- });
-
- return (data.at(-1) ?? {}) as IDialog;
- };
-
- export const useSelectPromptConfigParameters = (): VariableTableDataType[] => {
- const { data: currentDialog } = useFetchNextDialog();
-
- const finalParameters: VariableTableDataType[] = useMemo(() => {
- const parameters = currentDialog?.prompt_config?.parameters ?? [];
- if (!currentDialog.id) {
- // The newly created chat has a default parameter
- return [{ key: uuid(), variable: 'knowledge', optional: false }];
- }
- return parameters.map((x) => ({
- key: uuid(),
- variable: x.key,
- optional: x.optional,
- }));
- }, [currentDialog]);
-
- return finalParameters;
- };
-
- export const useDeleteDialog = () => {
- const showDeleteConfirm = useShowDeleteConfirm();
-
- const { removeDialog } = useRemoveNextDialog();
-
- const onRemoveDialog = (dialogIds: Array<string>) => {
- showDeleteConfirm({ onOk: () => removeDialog(dialogIds) });
- };
-
- return { onRemoveDialog };
- };
-
- export const useHandleItemHover = () => {
- const [activated, setActivated] = useState<string>('');
-
- const handleItemEnter = (id: string) => {
- setActivated(id);
- };
-
- const handleItemLeave = () => {
- setActivated('');
- };
-
- return {
- activated,
- handleItemEnter,
- handleItemLeave,
- };
- };
-
- export const useEditDialog = () => {
- const [dialog, setDialog] = useState<IDialog>({} as IDialog);
- const { fetchDialog } = useFetchManualDialog();
- const { setDialog: submitDialog, loading } = useSetNextDialog();
-
- const {
- visible: dialogEditVisible,
- hideModal: hideDialogEditModal,
- showModal: showDialogEditModal,
- } = useSetModalState();
-
- const hideModal = useCallback(() => {
- setDialog({} as IDialog);
- hideDialogEditModal();
- }, [hideDialogEditModal]);
-
- const onDialogEditOk = useCallback(
- async (dialog: IDialog) => {
- const ret = await submitDialog(dialog);
-
- if (ret === 0) {
- hideModal();
- }
- },
- [submitDialog, hideModal],
- );
-
- const handleShowDialogEditModal = useCallback(
- async (dialogId?: string) => {
- if (dialogId) {
- const ret = await fetchDialog(dialogId);
- if (ret.retcode === 0) {
- setDialog(ret.data);
- }
- }
- showDialogEditModal();
- },
- [showDialogEditModal, fetchDialog],
- );
-
- const clearDialog = useCallback(() => {
- setDialog({} as IDialog);
- }, []);
-
- return {
- dialogSettingLoading: loading,
- initialDialog: dialog,
- onDialogEditOk,
- dialogEditVisible,
- hideDialogEditModal: hideModal,
- showDialogEditModal: handleShowDialogEditModal,
- clearDialog,
- };
- };
-
- //#region conversation
-
- export const useSelectDerivedConversationList = () => {
- const { t } = useTranslate('chat');
-
- const [list, setList] = useState<Array<IConversation>>([]);
- const { data: currentDialog } = useFetchNextDialog();
- const { data: conversationList, loading } = useFetchNextConversationList();
- const { dialogId } = useGetChatSearchParams();
- const prologue = currentDialog?.prompt_config?.prologue ?? '';
-
- const addTemporaryConversation = useCallback(() => {
- setList((pre) => {
- if (dialogId) {
- const nextList = [
- {
- id: '',
- name: t('newConversation'),
- dialog_id: dialogId,
- message: [
- {
- content: prologue,
- role: MessageType.Assistant,
- },
- ],
- } as IConversation,
- ...conversationList,
- ];
- return nextList;
- }
-
- return pre;
- });
- }, [conversationList, dialogId, prologue, t]);
-
- useEffect(() => {
- addTemporaryConversation();
- }, [addTemporaryConversation]);
-
- return { list, addTemporaryConversation, loading };
- };
-
- export const useClickConversationCard = () => {
- const [currentQueryParameters, setSearchParams] = useSearchParams();
- const newQueryParameters: URLSearchParams = useMemo(
- () => new URLSearchParams(currentQueryParameters.toString()),
- [currentQueryParameters],
- );
-
- const handleClickConversation = useCallback(
- (conversationId: string) => {
- newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
- setSearchParams(newQueryParameters);
- },
- [newQueryParameters, setSearchParams],
- );
-
- return { handleClickConversation };
- };
-
- export const useSetConversation = () => {
- const { dialogId } = useGetChatSearchParams();
- const { updateConversation } = useUpdateNextConversation();
-
- const setConversation = useCallback(
- (message: string) => {
- return updateConversation({
- dialog_id: dialogId,
- name: message,
- message: [
- {
- role: MessageType.Assistant,
- content: message,
- },
- ],
- });
- },
- [updateConversation, dialogId],
- );
-
- return { setConversation };
- };
-
- export const useSelectCurrentConversation = () => {
- const [currentConversation, setCurrentConversation] =
- useState<IClientConversation>({} as IClientConversation);
- const { data: conversation, loading } = useFetchNextConversation();
- const { data: dialog } = useFetchNextDialog();
- const { conversationId, dialogId } = useGetChatSearchParams();
- const { removeMessageById } = useRemoveMessageById(setCurrentConversation);
-
- // Show the entered message in the conversation immediately after sending the message
- const addNewestConversation = useCallback(
- (message: Message, answer: string = '') => {
- setCurrentConversation((pre) => {
- return {
- ...pre,
- message: [
- ...pre.message,
- {
- ...message,
- id: buildMessageUuid(message),
- } as IMessage,
- {
- role: MessageType.Assistant,
- content: answer,
- id: buildMessageUuid({ ...message, role: MessageType.Assistant }),
- reference: {},
- } as IMessage,
- ],
- };
- });
- },
- [],
- );
-
- // Add the streaming message to the last item in the message list
- const addNewestAnswer = useCallback((answer: IAnswer) => {
- setCurrentConversation((pre) => {
- const latestMessage = pre.message?.at(-1);
-
- if (latestMessage) {
- return {
- ...pre,
- message: [
- ...pre.message.slice(0, -1),
- {
- ...latestMessage,
- content: answer.answer,
- reference: answer.reference,
- id: buildMessageUuid({
- id: answer.id,
- role: MessageType.Assistant,
- }),
- prompt: answer.prompt,
- } as IMessage,
- ],
- };
- }
- return pre;
- });
- }, []);
-
- const removeLatestMessage = useCallback(() => {
- setCurrentConversation((pre) => {
- const nextMessages = pre.message?.slice(0, -2) ?? [];
- return {
- ...pre,
- message: nextMessages,
- };
- });
- }, []);
-
- const addPrologue = useCallback(() => {
- if (dialogId !== '' && conversationId === '') {
- const prologue = dialog.prompt_config?.prologue;
-
- const nextMessage = {
- role: MessageType.Assistant,
- content: prologue,
- id: uuid(),
- } as IMessage;
-
- setCurrentConversation({
- id: '',
- dialog_id: dialogId,
- reference: [],
- message: [nextMessage],
- } as any);
- }
- }, [conversationId, dialog, dialogId]);
-
- useEffect(() => {
- addPrologue();
- }, [addPrologue]);
-
- useEffect(() => {
- if (conversationId) {
- setCurrentConversation(conversation);
- }
- }, [conversation, conversationId]);
-
- return {
- currentConversation,
- addNewestConversation,
- removeLatestMessage,
- addNewestAnswer,
- removeMessageById,
- loading,
- };
- };
-
- export const useScrollToBottom = (currentConversation: IClientConversation) => {
- const ref = useRef<HTMLDivElement>(null);
-
- const scrollToBottom = useCallback(() => {
- if (currentConversation.id) {
- ref.current?.scrollIntoView({ behavior: 'instant' });
- }
- }, [currentConversation]);
-
- useEffect(() => {
- scrollToBottom();
- }, [scrollToBottom]);
-
- return ref;
- };
-
- export const useFetchConversationOnMount = () => {
- const { conversationId } = useGetChatSearchParams();
- const {
- currentConversation,
- addNewestConversation,
- removeLatestMessage,
- addNewestAnswer,
- loading,
- removeMessageById,
- } = useSelectCurrentConversation();
- const ref = useScrollToBottom(currentConversation);
-
- return {
- currentConversation,
- addNewestConversation,
- ref,
- removeLatestMessage,
- addNewestAnswer,
- conversationId,
- loading,
- removeMessageById,
- };
- };
-
- export const useHandleMessageInputChange = () => {
- const [value, setValue] = useState('');
-
- const handleInputChange: ChangeEventHandler<HTMLInputElement> = (e) => {
- const value = e.target.value;
- const nextValue = value.replaceAll('\\n', '\n').replaceAll('\\t', '\t');
- setValue(nextValue);
- };
-
- return {
- handleInputChange,
- value,
- setValue,
- };
- };
-
- export const useSendMessage = (
- conversation: IClientConversation,
- addNewestConversation: (message: Message, answer?: string) => void,
- removeLatestMessage: () => void,
- addNewestAnswer: (answer: IAnswer) => void,
- ) => {
- const { setConversation } = useSetConversation();
- const { conversationId } = useGetChatSearchParams();
- const { handleInputChange, value, setValue } = useHandleMessageInputChange();
-
- const { handleClickConversation } = useClickConversationCard();
- const { send, answer, done, setDone } = useSendMessageWithSse();
-
- const sendMessage = useCallback(
- async (message: Message, documentIds: string[], id?: string) => {
- const res = await send({
- conversation_id: id ?? conversationId,
- messages: [
- ...(conversation?.message ?? []),
- {
- ...message,
- doc_ids: documentIds,
- },
- ],
- });
-
- if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) {
- // cancel loading
- setValue(message.content);
- console.info('removeLatestMessage111');
- removeLatestMessage();
- } else {
- if (id) {
- console.info('111');
- // new conversation
- handleClickConversation(id);
- } else {
- console.info('222');
- // fetchConversation(conversationId);
- }
- }
- },
- [
- conversation?.message,
- conversationId,
- handleClickConversation,
- removeLatestMessage,
- setValue,
- send,
- ],
- );
-
- const handleSendMessage = useCallback(
- async (message: Message, documentIds: string[]) => {
- if (conversationId !== '') {
- sendMessage(message, documentIds);
- } else {
- const data = await setConversation(message.content);
- if (data.retcode === 0) {
- const id = data.data.id;
- sendMessage(message, documentIds, id);
- }
- }
- },
- [conversationId, setConversation, sendMessage],
- );
-
- useEffect(() => {
- // #1289
- if (answer.answer && answer?.conversationId === conversationId) {
- addNewestAnswer(answer);
- }
- }, [answer, addNewestAnswer, conversationId]);
-
- useEffect(() => {
- // #1289 switch to another conversion window when the last conversion answer doesn't finish.
- if (conversationId) {
- setDone(true);
- }
- }, [setDone, conversationId]);
-
- const handlePressEnter = useCallback(
- (documentIds: string[]) => {
- if (trim(value) === '') return;
- const id = uuid();
-
- addNewestConversation({
- content: value,
- doc_ids: documentIds,
- id,
- role: MessageType.User,
- });
- if (done) {
- setValue('');
- handleSendMessage(
- { id, content: value.trim(), role: MessageType.User },
- documentIds,
- );
- }
- },
- [addNewestConversation, handleSendMessage, done, setValue, value],
- );
-
- return {
- handlePressEnter,
- handleInputChange,
- value,
- setValue,
- loading: !done,
- };
- };
-
- export const useGetFileIcon = () => {
- const getFileIcon = (filename: string) => {
- const ext: string = getFileExtension(filename);
- const iconPath = fileIconMap[ext as keyof typeof fileIconMap];
- return `@/assets/svg/file-icon/${iconPath}`;
- };
-
- return getFileIcon;
- };
-
- export const useDeleteConversation = () => {
- const { handleClickConversation } = useClickConversationCard();
- const showDeleteConfirm = useShowDeleteConfirm();
- const { removeConversation } = useRemoveNextConversation();
-
- const deleteConversation = (conversationIds: Array<string>) => async () => {
- const ret = await removeConversation(conversationIds);
- if (ret === 0) {
- handleClickConversation('');
- }
- return ret;
- };
-
- const onRemoveConversation = (conversationIds: Array<string>) => {
- showDeleteConfirm({ onOk: deleteConversation(conversationIds) });
- };
-
- return { onRemoveConversation };
- };
-
- export const useRenameConversation = () => {
- const [conversation, setConversation] = useState<IClientConversation>(
- {} as IClientConversation,
- );
- const { fetchConversation } = useFetchManualConversation();
- const {
- visible: conversationRenameVisible,
- hideModal: hideConversationRenameModal,
- showModal: showConversationRenameModal,
- } = useSetModalState();
- const { updateConversation, loading } = useUpdateNextConversation();
-
- const onConversationRenameOk = useCallback(
- async (name: string) => {
- const ret = await updateConversation({
- ...conversation,
- conversation_id: conversation.id,
- name,
- });
-
- if (ret.retcode === 0) {
- hideConversationRenameModal();
- }
- },
- [updateConversation, conversation, hideConversationRenameModal],
- );
-
- const handleShowConversationRenameModal = useCallback(
- async (conversationId: string) => {
- const ret = await fetchConversation(conversationId);
- if (ret.retcode === 0) {
- setConversation(ret.data);
- }
- showConversationRenameModal();
- },
- [showConversationRenameModal, fetchConversation],
- );
-
- return {
- conversationRenameLoading: loading,
- initialConversationName: conversation.name,
- onConversationRenameOk,
- conversationRenameVisible,
- hideConversationRenameModal,
- showConversationRenameModal: handleShowConversationRenameModal,
- };
- };
-
- export const useClickDrawer = () => {
- const { visible, showModal, hideModal } = useSetModalState();
- const [selectedChunk, setSelectedChunk] = useState<IChunk>({} as IChunk);
- const [documentId, setDocumentId] = useState<string>('');
-
- const clickDocumentButton = useCallback(
- (documentId: string, chunk: IChunk) => {
- showModal();
- setSelectedChunk(chunk);
- setDocumentId(documentId);
- },
- [showModal],
- );
-
- return {
- clickDocumentButton,
- visible,
- showModal,
- hideModal,
- selectedChunk,
- documentId,
- };
- };
-
- export const useGetSendButtonDisabled = () => {
- const { dialogId, conversationId } = useGetChatSearchParams();
-
- return dialogId === '' && conversationId === '';
- };
-
- export const useSendButtonDisabled = (value: string) => {
- return trim(value) === '';
- };
-
- export const useCreateConversationBeforeUploadDocument = () => {
- const { setConversation } = useSetConversation();
- const { dialogId } = useGetChatSearchParams();
-
- const { handleClickConversation } = useClickConversationCard();
-
- const createConversationBeforeUploadDocument = useCallback(
- async (message: string) => {
- const data = await setConversation(message);
- if (data.retcode === 0) {
- const id = data.data.id;
- handleClickConversation(id);
- }
- return data;
- },
- [setConversation, handleClickConversation],
- );
-
- return {
- createConversationBeforeUploadDocument,
- dialogId,
- };
- };
- //#endregion
|