diff --git a/web/src/hooks/logic-hooks.ts b/web/src/hooks/logic-hooks.ts index dad471767..73b389fd7 100644 --- a/web/src/hooks/logic-hooks.ts +++ b/web/src/hooks/logic-hooks.ts @@ -433,7 +433,7 @@ export const useSelectDerivedMessages = () => { ); const addNewestQuestion = useCallback( - (message: Message, answer: string = '') => { + (message: IMessage, answer: string = '') => { setDerivedMessages((pre) => { return [ ...pre, @@ -446,6 +446,7 @@ export const useSelectDerivedMessages = () => { { role: MessageType.Assistant, content: answer, + conversationId: message.conversationId, id: buildMessageUuid({ ...message, role: MessageType.Assistant }), }, ]; diff --git a/web/src/hooks/use-chat-request.ts b/web/src/hooks/use-chat-request.ts index c88ce71f9..7cbd087eb 100644 --- a/web/src/hooks/use-chat-request.ts +++ b/web/src/hooks/use-chat-request.ts @@ -16,11 +16,11 @@ import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send- import { isConversationIdExist } from '@/pages/next-chats/utils'; import chatService from '@/services/next-chat-service'; import api from '@/utils/api'; -import { buildMessageListWithUuid, getConversationId } from '@/utils/chat'; +import { buildMessageListWithUuid, generateConversationId } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { has } from 'lodash'; -import { useCallback, useMemo, useRef } from 'react'; +import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'umi'; import { @@ -36,6 +36,7 @@ export const enum ChatApiAction { FetchDialog = 'fetchDialog', FetchConversationList = 'fetchConversationList', FetchConversation = 'fetchConversation', + FetchConversationManually = 'fetchConversationManually', UpdateConversation = 'updateConversation', RemoveConversation = 'removeConversation', DeleteMessage = 'deleteMessage', @@ -59,29 +60,6 @@ export const useGetChatSearchParams = () => { }; }; -export const useClickDialogCard = () => { - // eslint-disable-next-line @typescript-eslint/no-unused-vars - const [_, setSearchParams] = useSearchParams(); - - const newQueryParameters: URLSearchParams = useMemo(() => { - return new URLSearchParams(); - }, []); - - const handleClickDialog = useCallback( - (dialogId: string) => { - newQueryParameters.set(ChatSearchParams.DialogId, dialogId); - // newQueryParameters.set( - // ChatSearchParams.ConversationId, - // EmptyConversationId, - // ); - setSearchParams(newQueryParameters); - }, - [newQueryParameters, setSearchParams], - ); - - return { handleClickDialog }; -}; - export const useFetchDialogList = () => { const { searchString, handleInputChange } = useHandleSearchChange(); const { pagination, setPagination } = useGetPaginationWithRouter(); @@ -222,28 +200,8 @@ export const useFetchDialog = () => { //#region Conversation -export const useClickConversationCard = () => { - const [currentQueryParameters, setSearchParams] = useSearchParams(); - const newQueryParameters: URLSearchParams = useMemo( - () => new URLSearchParams(currentQueryParameters.toString()), - [currentQueryParameters], - ); - - const handleClickConversation = useCallback( - (conversationId: string, isNew: string) => { - newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); - newQueryParameters.set(ChatSearchParams.isNew, isNew); - setSearchParams(newQueryParameters); - }, - [setSearchParams, newQueryParameters], - ); - - return { handleClickConversation }; -}; - export const useFetchConversationList = () => { const { id } = useParams(); - const { handleClickConversation } = useClickConversationCard(); const { searchString, handleInputChange } = useHandleSearchStrChange(); @@ -267,13 +225,6 @@ export const useFetchConversationList = () => { { params: { dialog_id: id } }, true, ); - if (data.code === 0) { - if (data.data.length > 0) { - handleClickConversation(data.data[0].id, ''); - } else { - handleClickConversation('', ''); - } - } return data?.data; }, }); @@ -281,45 +232,33 @@ export const useFetchConversationList = () => { return { data, loading, refetch, searchString, handleInputChange }; }; -export const useFetchConversation = () => { - const { isNew, conversationId } = useGetChatSearchParams(); - const { sharedId } = useGetSharedChatSearchParams(); +export function useFetchConversationManually() { const { data, - isFetching: loading, - refetch, - } = useQuery({ - queryKey: [ChatApiAction.FetchConversation, conversationId], - initialData: {} as IClientConversation, - // enabled: isConversationIdExist(conversationId), - gcTime: 0, - refetchOnWindowFocus: false, - queryFn: async () => { - if ( - isNew !== 'true' && - isConversationIdExist(sharedId || conversationId) - ) { - const { data } = await chatService.getConversation( - { - params: { - conversationId: conversationId || sharedId, - }, + isPending: loading, + mutateAsync, + } = useMutation({ + mutationKey: [ChatApiAction.FetchConversationManually], + mutationFn: async (conversationId) => { + const { data } = await chatService.getConversation( + { + params: { + conversationId, }, - true, - ); + }, + true, + ); - const conversation = data?.data ?? {}; + const conversation = data?.data ?? {}; - const messageList = buildMessageListWithUuid(conversation?.message); + const messageList = buildMessageListWithUuid(conversation?.message); - return { ...conversation, message: messageList }; - } - return { message: [] }; + return { ...conversation, message: messageList }; }, }); - return { data, loading, refetch }; -}; + return { data, loading, fetchConversationManually: mutateAsync }; +} export const useUpdateConversation = () => { const { t } = useTranslation(); @@ -335,7 +274,7 @@ export const useUpdateConversation = () => { ...params, conversation_id: params.conversation_id ? params.conversation_id - : getConversationId(), + : generateConversationId(), }); if (data.code === 0) { queryClient.invalidateQueries({ diff --git a/web/src/interfaces/database/chat.ts b/web/src/interfaces/database/chat.ts index dbdf78a73..6728937bf 100644 --- a/web/src/interfaces/database/chat.ts +++ b/web/src/interfaces/database/chat.ts @@ -187,6 +187,7 @@ export interface IExternalChatInfo { export interface IMessage extends Message { id: string; reference?: IReference; // the latest news has reference + conversationId?: string; // To distinguish which conversation the message belongs to } export interface IClientConversation extends IConversation { diff --git a/web/src/pages/next-chats/chat/chat-box/multiple-chat-box.tsx b/web/src/pages/next-chats/chat/chat-box/multiple-chat-box.tsx index 53db05331..685c530a0 100644 --- a/web/src/pages/next-chats/chat/chat-box/multiple-chat-box.tsx +++ b/web/src/pages/next-chats/chat/chat-box/multiple-chat-box.tsx @@ -15,12 +15,12 @@ import { import { MessageType } from '@/constants/chat'; import { useScrollToBottom } from '@/hooks/logic-hooks'; import { - useFetchConversation, useFetchDialog, useGetChatSearchParams, useSetDialog, } from '@/hooks/use-chat-request'; import { useFetchUserInfo } from '@/hooks/use-user-setting-request'; +import { IClientConversation, IMessage } from '@/interfaces/database/chat'; import { buildMessageUuidWithRole } from '@/utils/chat'; import { zodResolver } from '@hookform/resolvers/zod'; import { t } from 'i18next'; @@ -38,13 +38,14 @@ import { useCreateConversationBeforeUploadDocument } from '../../hooks/use-creat import { useSendMessage } from '../../hooks/use-send-chat-message'; import { useSendMultipleChatMessage } from '../../hooks/use-send-multiple-message'; import { buildMessageItemReference } from '../../utils'; -import { IMessage } from '../interface'; import { useAddChatBox } from '../use-add-box'; +import { useSetDefaultModel } from './use-set-default-model'; type MultipleChatBoxProps = { controller: AbortController; chatBoxIds: string[]; stopOutputMessage(): void; + conversation: IClientConversation; } & Pick< ReturnType, 'removeChatBox' | 'addChatBox' | 'chatBoxIds' @@ -55,6 +56,7 @@ type ChatCardProps = { idx: number; derivedMessages: IMessage[]; sendLoading: boolean; + conversation: IClientConversation; } & Pick< MultipleChatBoxProps, 'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds' @@ -72,6 +74,7 @@ const ChatCard = forwardRef(function ChatCard( derivedMessages, sendLoading, clickDocumentButton, + conversation, }: ChatCardProps, ref, ) { @@ -97,7 +100,8 @@ const ChatCard = forwardRef(function ChatCard( const { data: userInfo } = useFetchUserInfo(); const { data: currentDialog } = useFetchDialog(); - const { data: conversation } = useFetchConversation(); + + useSetDefaultModel(form); const isLatestChat = idx === chatBoxIds.length - 1; @@ -202,6 +206,7 @@ export function MultipleChatBox({ removeChatBox, addChatBox, stopOutputMessage, + conversation, }: MultipleChatBoxProps) { const { value, @@ -237,6 +242,7 @@ export function MultipleChatBox({ ref={setFormRef(id)} sendLoading={sendLoading} clickDocumentButton={clickDocumentButton} + conversation={conversation} > ))} diff --git a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx index aac8f53d9..fea05c723 100644 --- a/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx +++ b/web/src/pages/next-chats/chat/chat-box/single-chat-box.tsx @@ -4,12 +4,13 @@ import PdfSheet from '@/components/pdf-drawer'; import { useClickDrawer } from '@/components/pdf-drawer/hooks'; import { MessageType } from '@/constants/chat'; import { - useFetchConversation, useFetchDialog, useGetChatSearchParams, } from '@/hooks/use-chat-request'; import { useFetchUserInfo } from '@/hooks/use-user-setting-request'; +import { IClientConversation } from '@/interfaces/database/chat'; import { buildMessageUuidWithRole } from '@/utils/chat'; +import { useEffect } from 'react'; import { useGetSendButtonDisabled, useSendButtonDisabled, @@ -21,9 +22,14 @@ import { buildMessageItemReference } from '../../utils'; interface IProps { controller: AbortController; stopOutputMessage(): void; + conversation: IClientConversation; } -export function SingleChatBox({ controller, stopOutputMessage }: IProps) { +export function SingleChatBox({ + controller, + stopOutputMessage, + conversation, +}: IProps) { const { value, scrollRef, @@ -37,18 +43,32 @@ export function SingleChatBox({ controller, stopOutputMessage }: IProps) { removeMessageById, handleUploadFile, removeFile, + setDerivedMessages, } = useSendMessage(controller); const { data: userInfo } = useFetchUserInfo(); const { data: currentDialog } = useFetchDialog(); const { createConversationBeforeUploadDocument } = useCreateConversationBeforeUploadDocument(); const { conversationId } = useGetChatSearchParams(); - const { data: conversation } = useFetchConversation(); const disabled = useGetSendButtonDisabled(); const sendDisabled = useSendButtonDisabled(value); const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = useClickDrawer(); + useEffect(() => { + const messages = conversation?.message; + if (Array.isArray(messages)) { + setDerivedMessages(messages); + } + }, [conversation?.message, setDerivedMessages]); + + useEffect(() => { + // Clear the message list after deleting the conversation. + if (conversationId === '') { + setDerivedMessages([]); + } + }, [conversationId, setDerivedMessages]); + return (
diff --git a/web/src/pages/next-chats/chat/chat-box/use-set-default-model.ts b/web/src/pages/next-chats/chat/chat-box/use-set-default-model.ts new file mode 100644 index 000000000..661f5a72d --- /dev/null +++ b/web/src/pages/next-chats/chat/chat-box/use-set-default-model.ts @@ -0,0 +1,18 @@ +import { LlmModelType } from '@/constants/knowledge'; +import { useComposeLlmOptionsByModelTypes } from '@/hooks/use-llm-request'; +import { useMount } from 'ahooks'; +import { UseFormReturn } from 'react-hook-form'; + +export function useSetDefaultModel(form: UseFormReturn) { + const modelOptions = useComposeLlmOptionsByModelTypes([ + LlmModelType.Chat, + LlmModelType.Image2text, + ]); + + useMount(() => { + const firstModel = modelOptions.at(0)?.options.at(0)?.value; + if (firstModel) { + form.setValue('llm_id', firstModel); + } + }); +} diff --git a/web/src/pages/next-chats/chat/conversation-dropdown.tsx b/web/src/pages/next-chats/chat/conversation-dropdown.tsx index 27bed0a7e..a38129314 100644 --- a/web/src/pages/next-chats/chat/conversation-dropdown.tsx +++ b/web/src/pages/next-chats/chat/conversation-dropdown.tsx @@ -5,11 +5,15 @@ import { DropdownMenuItem, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; -import { useRemoveConversation } from '@/hooks/use-chat-request'; +import { + useGetChatSearchParams, + useRemoveConversation, +} from '@/hooks/use-chat-request'; import { IConversation } from '@/interfaces/database/chat'; import { Trash2 } from 'lucide-react'; import { MouseEventHandler, PropsWithChildren, useCallback } from 'react'; import { useTranslation } from 'react-i18next'; +import { useChatUrlParams } from '../hooks/use-chat-url'; export function ConversationDropdown({ children, @@ -20,22 +24,27 @@ export function ConversationDropdown({ removeTemporaryConversation?: (conversationId: string) => void; }) { const { t } = useTranslation(); - + const { setConversationBoth } = useChatUrlParams(); const { removeConversation } = useRemoveConversation(); + const { isNew } = useGetChatSearchParams(); - const handleDelete: MouseEventHandler = useCallback(() => { - if (conversation.is_new && removeTemporaryConversation) { - removeTemporaryConversation(conversation.id); - removeConversation([]); - } else { - removeConversation([conversation.id]); - } - }, [ - conversation.id, - conversation.is_new, - removeConversation, - removeTemporaryConversation, - ]); + const handleDelete: MouseEventHandler = + useCallback(async () => { + if (isNew === 'true' && removeTemporaryConversation) { + removeTemporaryConversation(conversation.id); + } else { + const code = await removeConversation([conversation.id]); + if (code === 0) { + setConversationBoth('', ''); + } + } + }, [ + conversation.id, + isNew, + removeConversation, + removeTemporaryConversation, + setConversationBoth, + ]); return ( diff --git a/web/src/pages/next-chats/chat/index.tsx b/web/src/pages/next-chats/chat/index.tsx index 9bc45b296..5d19cbcbd 100644 --- a/web/src/pages/next-chats/chat/index.tsx +++ b/web/src/pages/next-chats/chat/index.tsx @@ -15,13 +15,17 @@ import { SharedFrom } from '@/constants/chat'; import { useSetModalState } from '@/hooks/common-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { - useFetchConversation, + useFetchConversationList, + useFetchConversationManually, useFetchDialog, useGetChatSearchParams, } from '@/hooks/use-chat-request'; +import { IClientConversation } from '@/interfaces/database/chat'; import { cn } from '@/lib/utils'; +import { useMount } from 'ahooks'; import { isEmpty } from 'lodash'; import { ArrowUpRight, LogOut, Send } from 'lucide-react'; +import { useCallback, useMemo, useState } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams } from 'umi'; import { useHandleClickConversationCard } from '../hooks/use-click-card'; @@ -37,26 +41,54 @@ export default function Chat() { const { navigateToChatList } = useNavigatePage(); const { data } = useFetchDialog(); const { t } = useTranslation(); - const { data: conversation } = useFetchConversation(); + const [currentConversation, setCurrentConversation] = + useState({} as IClientConversation); + + const { fetchConversationManually } = useFetchConversationManually(); const { handleConversationCardClick, controller, stopOutputMessage } = useHandleClickConversationCard(); const { visible: settingVisible, switchVisible: switchSettingVisible } = useSetModalState(true); - const { - removeChatBox, - addChatBox, - chatBoxIds, - hasSingleChatBox, - hasThreeChatBox, - } = useAddChatBox(); + + const { isDebugMode, switchDebugMode } = useSwitchDebugMode(); + const { removeChatBox, addChatBox, chatBoxIds, hasSingleChatBox } = + useAddChatBox(isDebugMode); const { showEmbedModal, hideEmbedModal, embedVisible, beta } = useShowEmbedModal(); const { conversationId, isNew } = useGetChatSearchParams(); - const { isDebugMode, switchDebugMode } = useSwitchDebugMode(); + const { data: dialogList } = useFetchConversationList(); + + const currentConversationName = useMemo(() => { + return dialogList.find((x) => x.id === conversationId)?.name; + }, [conversationId, dialogList]); + + const fetchConversation: typeof handleConversationCardClick = useCallback( + async (conversationId, isNew) => { + if (conversationId && !isNew) { + const conversation = await fetchConversationManually(conversationId); + if (!isEmpty(conversation)) { + setCurrentConversation(conversation); + } + } + }, + [fetchConversationManually], + ); + + const handleSessionClick: typeof handleConversationCardClick = useCallback( + (conversationId, isNew) => { + handleConversationCardClick(conversationId, isNew); + fetchConversation(conversationId, isNew); + }, + [fetchConversation, handleConversationCardClick], + ); + + useMount(() => { + fetchConversation(conversationId, isNew === 'true'); + }); if (isDebugMode) { return ( @@ -75,6 +107,7 @@ export default function Chat() { removeChatBox={removeChatBox} addChatBox={addChatBox} stopOutputMessage={stopOutputMessage} + conversation={currentConversation} >
); @@ -104,7 +137,7 @@ export default function Chat() {
@@ -115,16 +148,8 @@ export default function Chat() { className={cn('p-5', { 'border-b': hasSingleChatBox })} > -
{conversation.name}
-
@@ -133,6 +158,7 @@ export default function Chat() { diff --git a/web/src/pages/next-chats/chat/use-add-box.ts b/web/src/pages/next-chats/chat/use-add-box.ts index a093afa7d..83a389a46 100644 --- a/web/src/pages/next-chats/chat/use-add-box.ts +++ b/web/src/pages/next-chats/chat/use-add-box.ts @@ -1,7 +1,7 @@ -import { useCallback, useState } from 'react'; +import { useCallback, useEffect, useState } from 'react'; import { v4 as uuid } from 'uuid'; -export function useAddChatBox() { +export function useAddChatBox(isDebugMode: boolean) { const [ids, setIds] = useState([uuid()]); const hasSingleChatBox = ids.length === 1; @@ -16,6 +16,12 @@ export function useAddChatBox() { setIds((prev) => prev.filter((x) => x !== id)); }, []); + useEffect(() => { + if (!isDebugMode) { + setIds((pre) => pre.slice(0, 1)); + } + }, [isDebugMode]); + return { chatBoxIds: ids, hasSingleChatBox, diff --git a/web/src/pages/next-chats/hooks/use-button-disabled.tsx b/web/src/pages/next-chats/hooks/use-button-disabled.tsx index 50bad8867..d2193b8cb 100644 --- a/web/src/pages/next-chats/hooks/use-button-disabled.tsx +++ b/web/src/pages/next-chats/hooks/use-button-disabled.tsx @@ -1,12 +1,10 @@ -import { useGetChatSearchParams } from '@/hooks/use-chat-request'; import { trim } from 'lodash'; import { useParams } from 'umi'; export const useGetSendButtonDisabled = () => { - const { conversationId } = useGetChatSearchParams(); const { id: dialogId } = useParams(); - return dialogId === '' || conversationId === ''; + return dialogId === ''; }; export const useSendButtonDisabled = (value: string) => { diff --git a/web/src/pages/next-chats/hooks/use-chat-url.ts b/web/src/pages/next-chats/hooks/use-chat-url.ts new file mode 100644 index 000000000..554ceb9c9 --- /dev/null +++ b/web/src/pages/next-chats/hooks/use-chat-url.ts @@ -0,0 +1,97 @@ +import { ChatSearchParams } from '@/constants/chat'; +import { useGetChatSearchParams } from '@/hooks/use-chat-request'; +import { IMessage } from '@/interfaces/database/chat'; +import { generateConversationId } from '@/utils/chat'; +import { useCallback, useMemo } from 'react'; +import { useSearchParams } from 'umi'; +import { useSetConversation } from './use-set-conversation'; + +/** + * Consolidated hook for managing chat URL parameters (conversationId and isNew) + * Replaces: useClickConversationCard from use-chat-request.ts and useSetChatRouteParams from use-set-chat-route.ts + */ +export const useChatUrlParams = () => { + const [currentQueryParameters, setSearchParams] = useSearchParams(); + const newQueryParameters: URLSearchParams = useMemo( + () => new URLSearchParams(currentQueryParameters.toString()), + [currentQueryParameters], + ); + + const setConversationId = useCallback( + (conversationId: string) => { + newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); + setSearchParams(newQueryParameters); + }, + [setSearchParams, newQueryParameters], + ); + + const setIsNew = useCallback( + (isNew: string) => { + newQueryParameters.set(ChatSearchParams.isNew, isNew); + setSearchParams(newQueryParameters); + }, + [setSearchParams, newQueryParameters], + ); + + const getIsNew = useCallback(() => { + return newQueryParameters.get(ChatSearchParams.isNew); + }, [newQueryParameters]); + + const setConversationBoth = useCallback( + (conversationId: string, isNew: string) => { + newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); + newQueryParameters.set(ChatSearchParams.isNew, isNew); + setSearchParams(newQueryParameters); + }, + [setSearchParams, newQueryParameters], + ); + + return { + setConversationId, + setIsNew, + getIsNew, + setConversationBoth, + }; +}; + +export function useCreateConversationBeforeSendMessage() { + const { conversationId, isNew } = useGetChatSearchParams(); + const { setConversation } = useSetConversation(); + const { setIsNew, setConversationBoth } = useChatUrlParams(); + + // Create conversation if it doesn't exist + const createConversationBeforeSendMessage = useCallback( + async (value: string) => { + let currentMessages: Array = []; + const currentConversationId = generateConversationId(); + if (conversationId === '' || isNew === 'true') { + if (conversationId === '') { + setConversationBoth(currentConversationId, 'true'); + } + const data = await setConversation( + value, + true, + conversationId || currentConversationId, + ); + if (data.code !== 0) { + return; + } else { + setIsNew(''); + currentMessages = data.data.message; + } + } + + const targetConversationId = conversationId || currentConversationId; + + return { + targetConversationId, + currentMessages, + }; + }, + [conversationId, isNew, setConversation, setConversationBoth, setIsNew], + ); + + return { + createConversationBeforeSendMessage, + }; +} diff --git a/web/src/pages/next-chats/hooks/use-click-card.ts b/web/src/pages/next-chats/hooks/use-click-card.ts index 4b9141ece..fc5fe2ebf 100644 --- a/web/src/pages/next-chats/hooks/use-click-card.ts +++ b/web/src/pages/next-chats/hooks/use-click-card.ts @@ -1,9 +1,9 @@ -import { useClickConversationCard } from '@/hooks/use-chat-request'; import { useCallback, useState } from 'react'; +import { useChatUrlParams } from './use-chat-url'; export function useHandleClickConversationCard() { const [controller, setController] = useState(new AbortController()); - const { handleClickConversation } = useClickConversationCard(); + const { setConversationBoth } = useChatUrlParams(); const stopOutputMessage = useCallback(() => { setController((pre) => { @@ -14,10 +14,10 @@ export function useHandleClickConversationCard() { const handleConversationCardClick = useCallback( (conversationId: string, isNew: boolean) => { - handleClickConversation(conversationId, isNew ? 'true' : ''); + setConversationBoth(conversationId, isNew ? 'true' : ''); stopOutputMessage(); }, - [handleClickConversation, stopOutputMessage], + [setConversationBoth, stopOutputMessage], ); return { controller, handleConversationCardClick, stopOutputMessage }; diff --git a/web/src/pages/next-chats/hooks/use-create-conversation.ts b/web/src/pages/next-chats/hooks/use-create-conversation.ts index 3cf34cd01..33d1389d1 100644 --- a/web/src/pages/next-chats/hooks/use-create-conversation.ts +++ b/web/src/pages/next-chats/hooks/use-create-conversation.ts @@ -1,23 +1,23 @@ import { useCallback } from 'react'; import { useParams } from 'umi'; -import { useSetChatRouteParams } from './use-set-chat-route'; +import { useChatUrlParams } from './use-chat-url'; import { useSetConversation } from './use-set-conversation'; export const useCreateConversationBeforeUploadDocument = () => { const { setConversation } = useSetConversation(); const { id: dialogId } = useParams(); - const { getConversationIsNew } = useSetChatRouteParams(); + const { getIsNew } = useChatUrlParams(); const createConversationBeforeUploadDocument = useCallback( async (message: string) => { - const isNew = getConversationIsNew(); + const isNew = getIsNew(); if (isNew === 'true') { const data = await setConversation(message, true); return data; } }, - [setConversation, getConversationIsNew], + [setConversation, getIsNew], ); return { diff --git a/web/src/pages/next-chats/hooks/use-select-conversation-list.ts b/web/src/pages/next-chats/hooks/use-select-conversation-list.ts index a37d3078b..633f02c7d 100644 --- a/web/src/pages/next-chats/hooks/use-select-conversation-list.ts +++ b/web/src/pages/next-chats/hooks/use-select-conversation-list.ts @@ -1,13 +1,14 @@ -import { ChatSearchParams, MessageType } from '@/constants/chat'; +import { MessageType } from '@/constants/chat'; import { useTranslate } from '@/hooks/common-hooks'; import { useFetchConversationList, useFetchDialogList, } from '@/hooks/use-chat-request'; import { IConversation } from '@/interfaces/database/chat'; -import { getConversationId } from '@/utils/chat'; +import { generateConversationId } from '@/utils/chat'; import { useCallback, useEffect, useMemo, useState } from 'react'; -import { useParams, useSearchParams } from 'umi'; +import { useParams } from 'umi'; +import { useChatUrlParams } from './use-chat-url'; export const useFindPrologueFromDialogList = () => { const { id: dialogId } = useParams(); @@ -20,25 +21,6 @@ export const useFindPrologueFromDialogList = () => { return prologue; }; -export const useSetNewConversationRouteParams = () => { - const [currentQueryParameters, setSearchParams] = useSearchParams(); - const newQueryParameters: URLSearchParams = useMemo( - () => new URLSearchParams(currentQueryParameters.toString()), - [currentQueryParameters], - ); - - const setNewConversationRouteParams = useCallback( - (conversationId: string, isNew: string) => { - newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); - newQueryParameters.set(ChatSearchParams.isNew, isNew); - setSearchParams(newQueryParameters); - }, - [newQueryParameters, setSearchParams], - ); - - return { setNewConversationRouteParams }; -}; - export const useSelectDerivedConversationList = () => { const { t } = useTranslate('chat'); @@ -49,15 +31,16 @@ export const useSelectDerivedConversationList = () => { handleInputChange, searchString, } = useFetchConversationList(); + const { id: dialogId } = useParams(); - const { setNewConversationRouteParams } = useSetNewConversationRouteParams(); const prologue = useFindPrologueFromDialogList(); + const { setConversationBoth } = useChatUrlParams(); const addTemporaryConversation = useCallback(() => { - const conversationId = getConversationId(); + const conversationId = generateConversationId(); setList((pre) => { if (dialogId) { - setNewConversationRouteParams(conversationId, 'true'); + setConversationBoth(conversationId, 'true'); const nextList = [ { id: conversationId, @@ -78,7 +61,7 @@ export const useSelectDerivedConversationList = () => { return pre; }); - }, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]); + }, [dialogId, setConversationBoth, t, prologue, conversationList]); const removeTemporaryConversation = useCallback((conversationId: string) => { setList((prevList) => { diff --git a/web/src/pages/next-chats/hooks/use-send-chat-message.ts b/web/src/pages/next-chats/hooks/use-send-chat-message.ts index 50c80e05c..19df539ce 100644 --- a/web/src/pages/next-chats/hooks/use-send-chat-message.ts +++ b/web/src/pages/next-chats/hooks/use-send-chat-message.ts @@ -1,4 +1,3 @@ -import { FileUploadProps } from '@/components/file-upload'; import { MessageType } from '@/constants/chat'; import { useHandleMessageInputChange, @@ -6,19 +5,15 @@ import { useSelectDerivedMessages, useSendMessageWithSse, } from '@/hooks/logic-hooks'; -import { - useFetchConversation, - useGetChatSearchParams, -} from '@/hooks/use-chat-request'; -import { IMessage, Message } from '@/interfaces/database/chat'; +import { useGetChatSearchParams } from '@/hooks/use-chat-request'; +import { IMessage } from '@/interfaces/database/chat'; import api from '@/utils/api'; import { trim } from 'lodash'; import { useCallback, useEffect } from 'react'; import { useParams } from 'umi'; import { v4 as uuid } from 'uuid'; +import { useCreateConversationBeforeSendMessage } from './use-chat-url'; import { useFindPrologueFromDialogList } from './use-select-conversation-list'; -import { useSetChatRouteParams } from './use-set-chat-route'; -import { useSetConversation } from './use-set-conversation'; import { useUploadFile } from './use-upload-file'; export const useSelectNextMessages = () => { @@ -33,8 +28,7 @@ export const useSelectNextMessages = () => { removeMessageById, removeMessagesAfterCurrentMessage, } = useSelectDerivedMessages(); - const { data: conversation, loading } = useFetchConversation(); - const { conversationId, isNew } = useGetChatSearchParams(); + const { isNew, conversationId } = useGetChatSearchParams(); const { id: dialogId } = useParams(); const prologue = useFindPrologueFromDialogList(); @@ -44,45 +38,31 @@ export const useSelectNextMessages = () => { role: MessageType.Assistant, content: prologue, id: uuid(), + conversationId: conversationId, } as IMessage; setDerivedMessages([nextMessage]); } - }, [dialogId, isNew, prologue, setDerivedMessages]); + }, [conversationId, dialogId, isNew, prologue, setDerivedMessages]); useEffect(() => { addPrologue(); }, [addPrologue]); - useEffect(() => { - if ( - conversationId && - isNew !== 'true' && - conversation.message?.length > 0 - ) { - setDerivedMessages(conversation.message); - } - - if (!conversationId) { - setDerivedMessages([]); - } - }, [conversation.message, conversationId, setDerivedMessages, isNew]); - return { scrollRef, messageContainerRef, derivedMessages, - loading, addNewestAnswer, addNewestQuestion, removeLatestMessage, removeMessageById, removeMessagesAfterCurrentMessage, + setDerivedMessages, }; }; export const useSendMessage = (controller: AbortController) => { - const { setConversation } = useSetConversation(); const { conversationId, isNew } = useGetChatSearchParams(); const { handleInputChange, value, setValue } = useHandleMessageInputChange(); @@ -96,31 +76,13 @@ export const useSendMessage = (controller: AbortController) => { scrollRef, messageContainerRef, derivedMessages, - loading, addNewestAnswer, addNewestQuestion, removeLatestMessage, removeMessageById, removeMessagesAfterCurrentMessage, + setDerivedMessages, } = useSelectNextMessages(); - const { setConversationIsNew, getConversationIsNew } = - useSetChatRouteParams(); - - const onUploadFile: NonNullable = useCallback( - async (files, options) => { - const isNew = getConversationIsNew(); - - if (isNew === 'true' && Array.isArray(files) && files.length) { - const data = await setConversation(files[0].name, true); - if (data.code === 0) { - handleUploadFile(files, options, data.data?.id); - } - } else { - handleUploadFile(files, options); - } - }, - [getConversationIsNew, handleUploadFile, setConversation], - ); const sendMessage = useCallback( async ({ @@ -128,14 +90,19 @@ export const useSendMessage = (controller: AbortController) => { currentConversationId, messages, }: { - message: Message; + message: IMessage; currentConversationId?: string; - messages?: Message[]; + messages?: IMessage[]; }) => { const res = await send( { conversation_id: currentConversationId ?? conversationId, - messages: [...(messages ?? derivedMessages ?? []), message], + messages: [ + ...(Array.isArray(messages) && messages?.length > 0 + ? messages + : derivedMessages ?? []), + message, + ], }, controller, ); @@ -157,44 +124,62 @@ export const useSendMessage = (controller: AbortController) => { ], ); - const handleSendMessage = useCallback( - async (message: Message) => { - const isNew = getConversationIsNew(); - if (isNew !== 'true') { - sendMessage({ message }); - } else { - const data = await setConversation( - message.content, - true, - conversationId, - ); - if (data.code === 0) { - setConversationIsNew(''); - const id = data.data.id; - // currentConversationIdRef.current = id; - sendMessage({ - message, - currentConversationId: id, - messages: data.data.message, - }); - } - } - }, - [ - setConversation, - sendMessage, - setConversationIsNew, - getConversationIsNew, - conversationId, - ], - ); - const { regenerateMessage } = useRegenerateMessage({ removeMessagesAfterCurrentMessage, sendMessage, messages: derivedMessages, }); + const { createConversationBeforeSendMessage } = + useCreateConversationBeforeSendMessage(); + + const handlePressEnter = useCallback(async () => { + if (trim(value) === '') return; + + const data = await createConversationBeforeSendMessage(value); + + if (data === undefined) { + return; + } + + const { targetConversationId, currentMessages } = data; + + const id = uuid(); + + addNewestQuestion({ + content: value, + files: files, + id, + role: MessageType.User, + conversationId: targetConversationId, + }); + + if (done) { + setValue(''); + sendMessage({ + currentConversationId: targetConversationId, + messages: currentMessages, + message: { + id, + content: value.trim(), + role: MessageType.User, + files: files, + conversationId: targetConversationId, + }, + }); + } + clearFiles(); + }, [ + value, + createConversationBeforeSendMessage, + addNewestQuestion, + files, + done, + clearFiles, + setValue, + sendMessage, + ]); + useEffect(() => { // #1289 if (answer.answer && conversationId && isNew !== 'true') { @@ -202,36 +187,6 @@ export const useSendMessage = (controller: AbortController) => { } }, [answer, addNewestAnswer, conversationId, isNew]); - const handlePressEnter = useCallback(() => { - if (trim(value) === '') return; - const id = uuid(); - - addNewestQuestion({ - content: value, - files: files, - id, - role: MessageType.User, - }); - if (done) { - setValue(''); - handleSendMessage({ - id, - content: value.trim(), - role: MessageType.User, - files: files, - }); - } - clearFiles(); - }, [ - value, - addNewestQuestion, - files, - done, - clearFiles, - setValue, - handleSendMessage, - ]); - return { handlePressEnter, handleInputChange, @@ -239,13 +194,13 @@ export const useSendMessage = (controller: AbortController) => { setValue, regenerateMessage, sendLoading: !done, - loading, scrollRef, messageContainerRef, derivedMessages, removeMessageById, - handleUploadFile: onUploadFile, + handleUploadFile, isUploading, removeFile, + setDerivedMessages, }; }; diff --git a/web/src/pages/next-chats/hooks/use-send-multiple-message.ts b/web/src/pages/next-chats/hooks/use-send-multiple-message.ts index 4b60e05f0..19b02bc57 100644 --- a/web/src/pages/next-chats/hooks/use-send-multiple-message.ts +++ b/web/src/pages/next-chats/hooks/use-send-multiple-message.ts @@ -12,6 +12,7 @@ import { trim } from 'lodash'; import { useCallback, useEffect, useState } from 'react'; import { v4 as uuid } from 'uuid'; import { useBuildFormRefs } from './use-build-form-refs'; +import { useCreateConversationBeforeSendMessage } from './use-chat-url'; import { useUploadFile } from './use-upload-file'; export function useSendMultipleChatMessage( @@ -29,7 +30,11 @@ export function useSendMultipleChatMessage( api.completeConversation, ); - const { handleUploadFile, files, clearFiles } = useUploadFile(); + const { handleUploadFile, isUploading, files, clearFiles, removeFile } = + useUploadFile(); + + const { createConversationBeforeSendMessage } = + useCreateConversationBeforeSendMessage(); const { setFormRef, getLLMConfigById, isLLMConfigEmpty } = useBuildFormRefs(chatBoxIds); @@ -170,10 +175,18 @@ export function useSendMultipleChatMessage( ], ); - const handlePressEnter = useCallback(() => { + const handlePressEnter = useCallback(async () => { if (trim(value) === '') return; const id = uuid(); + const data = await createConversationBeforeSendMessage(value); + + if (data === undefined) { + return; + } + + const { targetConversationId, currentMessages } = data; + chatBoxIds.forEach((chatBoxId) => { if (!isLLMConfigEmpty(chatBoxId)) { addNewestQuestion({ @@ -182,6 +195,7 @@ export function useSendMultipleChatMessage( role: MessageType.User, chatBoxId, files, + conversationId: targetConversationId, }); } }); @@ -196,8 +210,11 @@ export function useSendMultipleChatMessage( content: value.trim(), role: MessageType.User, files, + conversationId: targetConversationId, }, chatBoxId, + currentConversationId: targetConversationId, + messages: currentMessages, }); } }); @@ -205,6 +222,7 @@ export function useSendMultipleChatMessage( clearFiles(); }, [ value, + createConversationBeforeSendMessage, chatBoxIds, allDone, clearFiles, @@ -234,5 +252,7 @@ export function useSendMultipleChatMessage( sendLoading: !allDone, setFormRef, handleUploadFile, + isUploading, + removeFile, }; } diff --git a/web/src/pages/next-chats/hooks/use-set-chat-route.ts b/web/src/pages/next-chats/hooks/use-set-chat-route.ts deleted file mode 100644 index 2389e69a4..000000000 --- a/web/src/pages/next-chats/hooks/use-set-chat-route.ts +++ /dev/null @@ -1,25 +0,0 @@ -import { ChatSearchParams } from '@/constants/chat'; -import { useCallback, useMemo } from 'react'; -import { useSearchParams } from 'umi'; - -export const useSetChatRouteParams = () => { - const [currentQueryParameters, setSearchParams] = useSearchParams(); - const newQueryParameters: URLSearchParams = useMemo( - () => new URLSearchParams(currentQueryParameters.toString()), - [currentQueryParameters], - ); - - const setConversationIsNew = useCallback( - (value: string) => { - newQueryParameters.set(ChatSearchParams.isNew, value); - setSearchParams(newQueryParameters); - }, - [newQueryParameters, setSearchParams], - ); - - const getConversationIsNew = useCallback(() => { - return newQueryParameters.get(ChatSearchParams.isNew); - }, [newQueryParameters]); - - return { setConversationIsNew, getConversationIsNew }; -}; diff --git a/web/src/pages/next-chats/hooks/use-set-conversation.ts b/web/src/pages/next-chats/hooks/use-set-conversation.ts index 9f3ac2f81..f0876fcc0 100644 --- a/web/src/pages/next-chats/hooks/use-set-conversation.ts +++ b/web/src/pages/next-chats/hooks/use-set-conversation.ts @@ -22,6 +22,7 @@ export const useSetConversation = () => { { role: MessageType.Assistant, content: message, + conversationId, }, ], }); diff --git a/web/src/pages/next-chats/hooks/use-upload-file.ts b/web/src/pages/next-chats/hooks/use-upload-file.ts index 1562b2268..c37a461e9 100644 --- a/web/src/pages/next-chats/hooks/use-upload-file.ts +++ b/web/src/pages/next-chats/hooks/use-upload-file.ts @@ -1,6 +1,12 @@ import { FileUploadProps } from '@/components/file-upload'; -import { useUploadAndParseFile } from '@/hooks/use-chat-request'; +import { + useGetChatSearchParams, + useUploadAndParseFile, +} from '@/hooks/use-chat-request'; +import { generateConversationId } from '@/utils/chat'; import { useCallback, useState } from 'react'; +import { useChatUrlParams } from './use-chat-url'; +import { useSetConversation } from './use-set-conversation'; export function useUploadFile() { const { uploadAndParseFile, loading, cancel } = useUploadAndParseFile(); @@ -8,6 +14,9 @@ export function useUploadFile() { const [fileMap, setFileMap] = useState>>( new Map(), ); + const { setConversation } = useSetConversation(); + const { conversationId, isNew } = useGetChatSearchParams(); + const { setIsNew, setConversationBoth } = useChatUrlParams(); type FileUploadParameters = Parameters< NonNullable @@ -35,6 +44,44 @@ export function useUploadFile() { [uploadAndParseFile], ); + const createConversationBeforeUploadFile: NonNullable< + FileUploadProps['onUpload'] + > = useCallback( + async (files, options) => { + if ( + (conversationId === '' || isNew === 'true') && + Array.isArray(files) && + files.length + ) { + const currentConversationId = generateConversationId(); + + if (conversationId === '') { + setConversationBoth(currentConversationId, 'true'); + } + + const data = await setConversation( + files[0].name, + true, + conversationId || currentConversationId, + ); + if (data.code === 0) { + setIsNew(''); + handleUploadFile(files, options, data.data?.id); + } + } else { + handleUploadFile(files, options); + } + }, + [ + conversationId, + handleUploadFile, + isNew, + setConversation, + setConversationBoth, + setIsNew, + ], + ); + const clearFiles = useCallback(() => { setCurrentFiles([]); setFileMap(new Map()); @@ -55,7 +102,7 @@ export function useUploadFile() { ); return { - handleUploadFile, + handleUploadFile: createConversationBeforeUploadFile, files: currentFiles, isUploading: loading, removeFile, diff --git a/web/src/utils/chat.ts b/web/src/utils/chat.ts index 08ef3c820..39c91d669 100644 --- a/web/src/utils/chat.ts +++ b/web/src/utils/chat.ts @@ -26,7 +26,7 @@ export const buildMessageListWithUuid = (messages?: Message[]) => { ); }; -export const getConversationId = () => { +export const generateConversationId = () => { return uuid().replace(/-/g, ''); };