### What problem does this PR solve? fix: Fixed an issue where the first message would be displayed when sending the second message #2625 ### Type of change - [x] Bug Fix (non-breaking change which fixes an issue) - [ ] New Feature (non-breaking change which adds functionality) - [ ] Documentation Update - [ ] Refactoring - [ ] Performance Improvement - [ ] Other (please describe):tags/v0.12.0
| @@ -37,7 +37,9 @@ from graphrag.mind_map_extractor import MindMapExtractor | |||
| def set_conversation(): | |||
| req = request.json | |||
| conv_id = req.get("conversation_id") | |||
| if conv_id: | |||
| is_new = req.get("is_new") | |||
| del req["is_new"] | |||
| if not is_new: | |||
| del req["conversation_id"] | |||
| try: | |||
| if not ConversationService.update_by_id(conv_id, req): | |||
| @@ -56,7 +58,7 @@ def set_conversation(): | |||
| if not e: | |||
| return get_data_error_result(retmsg="Dialog not found") | |||
| conv = { | |||
| "id": get_uuid(), | |||
| "id": conv_id, | |||
| "dialog_id": req["dialog_id"], | |||
| "name": req.get("name", "New conversation"), | |||
| "message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}] | |||
| @@ -30,7 +30,7 @@ export default defineConfig({ | |||
| copy: ['src/conf.json'], | |||
| proxy: { | |||
| '/v1': { | |||
| target: 'http://127.0.0.1:9456/', | |||
| target: 'http://127.0.0.1:9380/', | |||
| changeOrigin: true, | |||
| ws: true, | |||
| logger: console, | |||
| @@ -117,7 +117,7 @@ const MessageInput = ({ | |||
| file, | |||
| }) => { | |||
| let nextConversationId: string = conversationId; | |||
| if (createConversationBeforeUploadDocument && !conversationId) { | |||
| if (createConversationBeforeUploadDocument) { | |||
| const creatingRet = await createConversationBeforeUploadDocument( | |||
| file.name, | |||
| ); | |||
| @@ -234,8 +234,14 @@ const MessageInput = ({ | |||
| > | |||
| <Button | |||
| type={'text'} | |||
| disabled={disabled} | |||
| icon={ | |||
| <SvgIcon name="paper-clip" width={18} height={22}></SvgIcon> | |||
| <SvgIcon | |||
| name="paper-clip" | |||
| width={18} | |||
| height={22} | |||
| disabled={disabled} | |||
| ></SvgIcon> | |||
| } | |||
| ></Button> | |||
| </Upload> | |||
| @@ -2,11 +2,10 @@ import { useDeleteMessage, useFeedback } from '@/hooks/chat-hooks'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks'; | |||
| import { IFeedbackRequestBody } from '@/interfaces/request/chat'; | |||
| import { ConversationContext } from '@/pages/chat/context'; | |||
| import { getMessagePureId } from '@/utils/chat'; | |||
| import { hexStringToUint8Array } from '@/utils/common-util'; | |||
| import { SpeechPlayer } from 'openai-speech-stream-player'; | |||
| import { useCallback, useContext, useEffect, useRef, useState } from 'react'; | |||
| import { useCallback, useEffect, useRef, useState } from 'react'; | |||
| export const useSendFeedback = (messageId: string) => { | |||
| const { visible, hideModal, showModal } = useSetModalState(); | |||
| @@ -59,24 +58,21 @@ export const useSpeech = (content: string, audioBinary?: string) => { | |||
| const { read } = useSpeechWithSse(); | |||
| const player = useRef<SpeechPlayer>(); | |||
| const [isPlaying, setIsPlaying] = useState<boolean>(false); | |||
| const callback = useContext(ConversationContext); | |||
| const initialize = useCallback(async () => { | |||
| player.current = new SpeechPlayer({ | |||
| audio: ref.current!, | |||
| onPlaying: () => { | |||
| setIsPlaying(true); | |||
| callback?.(true); | |||
| }, | |||
| onPause: () => { | |||
| setIsPlaying(false); | |||
| callback?.(false); | |||
| }, | |||
| onChunkEnd: () => {}, | |||
| mimeType: 'audio/mpeg', | |||
| }); | |||
| await player.current.init(); | |||
| }, [callback]); | |||
| }, []); | |||
| const pause = useCallback(() => { | |||
| player.current?.pause(); | |||
| @@ -103,7 +99,11 @@ export const useSpeech = (content: string, audioBinary?: string) => { | |||
| if (audioBinary) { | |||
| const units = hexStringToUint8Array(audioBinary); | |||
| if (units) { | |||
| player.current?.feed(units); | |||
| try { | |||
| player.current?.feed(units); | |||
| } catch (error) { | |||
| console.warn(error); | |||
| } | |||
| } | |||
| } | |||
| }, [audioBinary]); | |||
| @@ -19,6 +19,7 @@ export enum SharedFrom { | |||
| export enum ChatSearchParams { | |||
| DialogId = 'dialogId', | |||
| ConversationId = 'conversationId', | |||
| isNew = 'isNew', | |||
| } | |||
| export const EmptyConversationId = 'empty'; | |||
| @@ -12,18 +12,22 @@ import { | |||
| import i18n from '@/locales/config'; | |||
| import { IClientConversation } from '@/pages/chat/interface'; | |||
| import chatService from '@/services/chat-service'; | |||
| import { buildMessageListWithUuid, isConversationIdExist } from '@/utils/chat'; | |||
| import { | |||
| buildMessageListWithUuid, | |||
| getConversationId, | |||
| isConversationIdExist, | |||
| } from '@/utils/chat'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { message } from 'antd'; | |||
| import dayjs, { Dayjs } from 'dayjs'; | |||
| import { has, set } from 'lodash'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { useSearchParams } from 'umi'; | |||
| import { history, useSearchParams } from 'umi'; | |||
| //#region logic | |||
| export const useClickDialogCard = () => { | |||
| const [, setSearchParams] = useSearchParams(); | |||
| const [_, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo(() => { | |||
| return new URLSearchParams(); | |||
| @@ -44,6 +48,25 @@ export const useClickDialogCard = () => { | |||
| return { handleClickDialog }; | |||
| }; | |||
| 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 useGetChatSearchParams = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| @@ -51,6 +74,7 @@ export const useGetChatSearchParams = () => { | |||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||
| conversationId: | |||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||
| isNew: currentQueryParameters.get(ChatSearchParams.isNew) || '', | |||
| }; | |||
| }; | |||
| @@ -60,6 +84,7 @@ export const useGetChatSearchParams = () => { | |||
| export const useFetchNextDialogList = () => { | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { | |||
| data, | |||
| @@ -70,11 +95,20 @@ export const useFetchNextDialogList = () => { | |||
| initialData: [], | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| refetchOnMount: false, | |||
| queryFn: async (...params) => { | |||
| console.log('🚀 ~ queryFn: ~ params:', params); | |||
| const { data } = await chatService.listDialog(); | |||
| if (data.retcode === 0 && data.data.length > 0) { | |||
| handleClickDialog(data.data[0].id); | |||
| if (data.retcode === 0) { | |||
| const list: IDialog[] = data.data; | |||
| if (list.length > 0) { | |||
| if (list.every((x) => x.id !== dialogId)) { | |||
| handleClickDialog(data.data[0].id); | |||
| } | |||
| } else { | |||
| history.push('/chat'); | |||
| } | |||
| } | |||
| return data?.data ?? []; | |||
| @@ -86,6 +120,7 @@ export const useFetchNextDialogList = () => { | |||
| export const useSetNextDialog = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| @@ -96,8 +131,10 @@ export const useSetNextDialog = () => { | |||
| const { data } = await chatService.setDialog(params); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ | |||
| exact: false, | |||
| queryKey: ['fetchDialogList'], | |||
| }); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: ['fetchDialog'], | |||
| }); | |||
| @@ -166,6 +203,7 @@ export const useRemoveNextDialog = () => { | |||
| const { data } = await chatService.removeDialog({ dialogIds }); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] }); | |||
| message.success(i18n.t('message.deleted')); | |||
| } | |||
| return data.retcode; | |||
| @@ -181,6 +219,7 @@ export const useRemoveNextDialog = () => { | |||
| export const useFetchNextConversationList = () => { | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| @@ -193,7 +232,9 @@ export const useFetchNextConversationList = () => { | |||
| enabled: !!dialogId, | |||
| queryFn: async () => { | |||
| const { data } = await chatService.listConversation({ dialogId }); | |||
| if (data.retcode === 0 && data.data.length > 0) { | |||
| handleClickConversation(data.data[0].id, ''); | |||
| } | |||
| return data?.data; | |||
| }, | |||
| }); | |||
| @@ -202,7 +243,7 @@ export const useFetchNextConversationList = () => { | |||
| }; | |||
| export const useFetchNextConversation = () => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { isNew, conversationId } = useGetChatSearchParams(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| @@ -214,17 +255,9 @@ export const useFetchNextConversation = () => { | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| if (isConversationIdExist(conversationId)) { | |||
| if (isNew !== 'true' && isConversationIdExist(conversationId)) { | |||
| const { data } = await chatService.getConversation({ conversationId }); | |||
| // if (data.retcode === 0 && needToBeSaved) { | |||
| // yield put({ | |||
| // type: 'kFModel/fetch_document_thumbnails', | |||
| // payload: { | |||
| // doc_ids: getDocumentIdsFromConversionReference(data.data), | |||
| // }, | |||
| // }); | |||
| // yield put({ type: 'setCurrentConversation', payload: data.data }); | |||
| // } | |||
| const conversation = data?.data ?? {}; | |||
| const messageList = buildMessageListWithUuid(conversation?.message); | |||
| @@ -265,7 +298,12 @@ export const useUpdateNextConversation = () => { | |||
| } = useMutation({ | |||
| mutationKey: ['updateConversation'], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data } = await chatService.setConversation(params); | |||
| const { data } = await chatService.setConversation({ | |||
| ...params, | |||
| conversation_id: params.conversation_id | |||
| ? params.conversation_id | |||
| : getConversationId(), | |||
| }); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); | |||
| } | |||
| @@ -224,6 +224,7 @@ export const useSendMessageWithSse = ( | |||
| const send = useCallback( | |||
| async ( | |||
| body: any, | |||
| controller?: AbortController, | |||
| ): Promise<{ response: Response; data: ResponseType } | undefined> => { | |||
| try { | |||
| setDone(false); | |||
| @@ -234,6 +235,7 @@ export const useSendMessageWithSse = ( | |||
| 'Content-Type': 'application/json', | |||
| }, | |||
| body: JSON.stringify(body), | |||
| signal: controller?.signal, | |||
| }); | |||
| const res = response.clone().json(); | |||
| @@ -249,6 +251,7 @@ export const useSendMessageWithSse = ( | |||
| const { done, value } = x; | |||
| if (done) { | |||
| console.info('done'); | |||
| setAnswer({} as IAnswer); | |||
| break; | |||
| } | |||
| try { | |||
| @@ -268,9 +271,12 @@ export const useSendMessageWithSse = ( | |||
| } | |||
| console.info('done?'); | |||
| setDone(true); | |||
| setAnswer({} as IAnswer); | |||
| return { data: await res, response }; | |||
| } catch (e) { | |||
| setDone(true); | |||
| setAnswer({} as IAnswer); | |||
| console.warn(e); | |||
| } | |||
| }, | |||
| @@ -63,6 +63,7 @@ export interface IConversation { | |||
| name: string; | |||
| update_date: string; | |||
| update_time: number; | |||
| is_new: true; | |||
| } | |||
| export interface Message { | |||
| @@ -580,7 +580,7 @@ The above is the content you need to summarize.`, | |||
| addGoogleRegion: 'Google Cloud Region', | |||
| GoogleRegionMessage: 'Please input Google Cloud Region', | |||
| modelProvidersWarn: | |||
| 'Please add both embedding model and LLM in <b>Settings > Model</b> providers firstly.', | |||
| 'Please add both embedding model and LLM in <b>Settings > Model providers</b> firstly.', | |||
| }, | |||
| message: { | |||
| registered: 'Registered!', | |||
| @@ -19,10 +19,13 @@ import { | |||
| } from '@/hooks/chat-hooks'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { memo } from 'react'; | |||
| import { ConversationContext } from '../context'; | |||
| import styles from './index.less'; | |||
| const ChatContainer = () => { | |||
| interface IProps { | |||
| controller: AbortController; | |||
| } | |||
| const ChatContainer = ({ controller }: IProps) => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { data: conversation } = useFetchNextConversation(); | |||
| @@ -36,8 +39,7 @@ const ChatContainer = () => { | |||
| handlePressEnter, | |||
| regenerateMessage, | |||
| removeMessageById, | |||
| redirectToNewConversation, | |||
| } = useSendNextMessage(); | |||
| } = useSendNextMessage(controller); | |||
| const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = | |||
| useClickDrawer(); | |||
| @@ -54,35 +56,33 @@ const ChatContainer = () => { | |||
| <Flex flex={1} vertical className={styles.messageContainer}> | |||
| <div> | |||
| <Spin spinning={loading}> | |||
| <ConversationContext.Provider value={redirectToNewConversation}> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages.length - 1 === i | |||
| } | |||
| key={message.id} | |||
| item={message} | |||
| nickname={userInfo.nickname} | |||
| avatar={userInfo.avatar} | |||
| reference={buildMessageItemReference( | |||
| { | |||
| message: derivedMessages, | |||
| reference: conversation.reference, | |||
| }, | |||
| message, | |||
| )} | |||
| clickDocumentButton={clickDocumentButton} | |||
| index={i} | |||
| removeMessageById={removeMessageById} | |||
| regenerateMessage={regenerateMessage} | |||
| sendLoading={sendLoading} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| </ConversationContext.Provider> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages.length - 1 === i | |||
| } | |||
| key={message.id} | |||
| item={message} | |||
| nickname={userInfo.nickname} | |||
| avatar={userInfo.avatar} | |||
| reference={buildMessageItemReference( | |||
| { | |||
| message: derivedMessages, | |||
| reference: conversation.reference, | |||
| }, | |||
| message, | |||
| )} | |||
| clickDocumentButton={clickDocumentButton} | |||
| index={i} | |||
| removeMessageById={removeMessageById} | |||
| regenerateMessage={regenerateMessage} | |||
| sendLoading={sendLoading} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| </Spin> | |||
| </div> | |||
| <div ref={ref} /> | |||
| @@ -1,6 +1 @@ | |||
| export enum ChatSearchParams { | |||
| DialogId = 'dialogId', | |||
| ConversationId = 'conversationId', | |||
| } | |||
| export const EmptyConversationId = 'empty'; | |||
| @@ -1,4 +1,4 @@ | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { ChatSearchParams, MessageType } from '@/constants/chat'; | |||
| import { fileIconMap } from '@/constants/common'; | |||
| import { | |||
| useFetchManualConversation, | |||
| @@ -24,6 +24,8 @@ import { | |||
| } from '@/hooks/logic-hooks'; | |||
| import { IConversation, IDialog, Message } from '@/interfaces/database/chat'; | |||
| import { getFileExtension } from '@/utils'; | |||
| import api from '@/utils/api'; | |||
| import { getConversationId } from '@/utils/chat'; | |||
| import { useMutationState } from '@tanstack/react-query'; | |||
| import { get } from 'lodash'; | |||
| import trim from 'lodash/trim'; | |||
| @@ -32,18 +34,57 @@ import { | |||
| 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 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 }; | |||
| }; | |||
| 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 useSelectCurrentDialog = () => { | |||
| const data = useMutationState({ | |||
| filters: { mutationKey: ['fetchDialog'] }, | |||
| @@ -169,22 +210,26 @@ export const useSelectDerivedConversationList = () => { | |||
| const { data: conversationList, loading } = useFetchNextConversationList(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const prologue = currentDialog?.prompt_config?.prologue ?? ''; | |||
| const { setNewConversationRouteParams } = useSetNewConversationRouteParams(); | |||
| const addTemporaryConversation = useCallback(() => { | |||
| const conversationId = getConversationId(); | |||
| setList((pre) => { | |||
| if (dialogId) { | |||
| setNewConversationRouteParams(conversationId, 'true'); | |||
| const nextList = [ | |||
| { | |||
| id: '', | |||
| id: conversationId, | |||
| name: t('newConversation'), | |||
| dialog_id: dialogId, | |||
| is_new: true, | |||
| message: [ | |||
| { | |||
| content: prologue, | |||
| role: MessageType.Assistant, | |||
| }, | |||
| ], | |||
| } as IConversation, | |||
| } as any, | |||
| ...conversationList, | |||
| ]; | |||
| return nextList; | |||
| @@ -192,42 +237,32 @@ export const useSelectDerivedConversationList = () => { | |||
| return pre; | |||
| }); | |||
| }, [conversationList, dialogId, prologue, t]); | |||
| }, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]); | |||
| // When you first enter the page, select the top conversation card | |||
| useEffect(() => { | |||
| addTemporaryConversation(); | |||
| }, [addTemporaryConversation]); | |||
| setList([...conversationList]); | |||
| }, [conversationList]); | |||
| 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({ | |||
| async ( | |||
| message: string, | |||
| isNew: boolean = false, | |||
| conversationId?: string, | |||
| ) => { | |||
| const data = await updateConversation({ | |||
| dialog_id: dialogId, | |||
| name: message, | |||
| is_new: isNew, | |||
| conversation_id: conversationId, | |||
| message: [ | |||
| { | |||
| role: MessageType.Assistant, | |||
| @@ -235,6 +270,8 @@ export const useSetConversation = () => { | |||
| }, | |||
| ], | |||
| }); | |||
| return data; | |||
| }, | |||
| [updateConversation, dialogId], | |||
| ); | |||
| @@ -242,22 +279,6 @@ export const useSetConversation = () => { | |||
| return { setConversation }; | |||
| }; | |||
| // 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 useSelectNextMessages = () => { | |||
| const { | |||
| ref, | |||
| @@ -271,10 +292,10 @@ export const useSelectNextMessages = () => { | |||
| } = useSelectDerivedMessages(); | |||
| const { data: conversation, loading } = useFetchNextConversation(); | |||
| const { data: dialog } = useFetchNextDialog(); | |||
| const { conversationId, dialogId } = useGetChatSearchParams(); | |||
| const { conversationId, dialogId, isNew } = useGetChatSearchParams(); | |||
| const addPrologue = useCallback(() => { | |||
| if (dialogId !== '' && conversationId === '') { | |||
| if (dialogId !== '' && isNew === 'true') { | |||
| const prologue = dialog.prompt_config?.prologue; | |||
| const nextMessage = { | |||
| @@ -285,17 +306,25 @@ export const useSelectNextMessages = () => { | |||
| setDerivedMessages([nextMessage]); | |||
| } | |||
| }, [conversationId, dialog, dialogId, setDerivedMessages]); | |||
| }, [isNew, dialog, dialogId, setDerivedMessages]); | |||
| useEffect(() => { | |||
| addPrologue(); | |||
| }, [addPrologue]); | |||
| useEffect(() => { | |||
| if (conversationId) { | |||
| if ( | |||
| conversationId && | |||
| isNew !== 'true' && | |||
| conversation.message?.length > 0 | |||
| ) { | |||
| setDerivedMessages(conversation.message); | |||
| } | |||
| }, [conversation.message, conversationId, setDerivedMessages]); | |||
| if (!conversationId) { | |||
| setDerivedMessages([]); | |||
| } | |||
| }, [conversation.message, conversationId, setDerivedMessages, isNew]); | |||
| return { | |||
| ref, | |||
| @@ -325,12 +354,14 @@ export const useHandleMessageInputChange = () => { | |||
| }; | |||
| }; | |||
| export const useSendNextMessage = () => { | |||
| export const useSendNextMessage = (controller: AbortController) => { | |||
| const { setConversation } = useSetConversation(); | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { conversationId, isNew } = useGetChatSearchParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const { send, answer, done, setDone, resetAnswer } = useSendMessageWithSse(); | |||
| const { send, answer, done } = useSendMessageWithSse( | |||
| api.completeConversation, | |||
| ); | |||
| const { | |||
| ref, | |||
| derivedMessages, | |||
| @@ -341,17 +372,8 @@ export const useSendNextMessage = () => { | |||
| removeMessageById, | |||
| removeMessagesAfterCurrentMessage, | |||
| } = useSelectNextMessages(); | |||
| const { data: dialog } = useFetchNextDialog(); | |||
| const currentConversationIdRef = useRef<string>(''); | |||
| const redirectToNewConversation = useCallback( | |||
| (isPlaying: boolean) => { | |||
| if (!conversationId && dialog?.prompt_config?.tts && !isPlaying) { | |||
| handleClickConversation(currentConversationIdRef.current); | |||
| } | |||
| }, | |||
| [dialog, handleClickConversation, conversationId], | |||
| ); | |||
| const { setConversationIsNew, getConversationIsNew } = | |||
| useSetChatRouteParams(); | |||
| const sendMessage = useCallback( | |||
| async ({ | |||
| @@ -363,49 +385,46 @@ export const useSendNextMessage = () => { | |||
| currentConversationId?: string; | |||
| messages?: Message[]; | |||
| }) => { | |||
| const res = await send({ | |||
| conversation_id: currentConversationId ?? conversationId, | |||
| messages: [...(messages ?? derivedMessages ?? []), message], | |||
| }); | |||
| const res = await send( | |||
| { | |||
| conversation_id: currentConversationId ?? conversationId, | |||
| messages: [...(messages ?? derivedMessages ?? []), message], | |||
| }, | |||
| controller, | |||
| ); | |||
| if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { | |||
| // cancel loading | |||
| setValue(message.content); | |||
| console.info('removeLatestMessage111'); | |||
| removeLatestMessage(); | |||
| } else { | |||
| if (currentConversationId) { | |||
| console.info('111'); | |||
| // new conversation | |||
| if (!dialog?.prompt_config?.tts) { | |||
| handleClickConversation(currentConversationId); | |||
| } | |||
| } else { | |||
| console.info('222'); | |||
| // fetchConversation(conversationId); | |||
| } | |||
| } | |||
| }, | |||
| [ | |||
| dialog, | |||
| derivedMessages, | |||
| conversationId, | |||
| handleClickConversation, | |||
| removeLatestMessage, | |||
| setValue, | |||
| send, | |||
| controller, | |||
| ], | |||
| ); | |||
| const handleSendMessage = useCallback( | |||
| async (message: Message) => { | |||
| if (conversationId !== '') { | |||
| const isNew = getConversationIsNew(); | |||
| if (isNew !== 'true') { | |||
| sendMessage({ message }); | |||
| } else { | |||
| const data = await setConversation(message.content); | |||
| const data = await setConversation( | |||
| message.content, | |||
| true, | |||
| conversationId, | |||
| ); | |||
| if (data.retcode === 0) { | |||
| setConversationIsNew(''); | |||
| const id = data.data.id; | |||
| currentConversationIdRef.current = id; | |||
| // currentConversationIdRef.current = id; | |||
| sendMessage({ | |||
| message, | |||
| currentConversationId: id, | |||
| @@ -414,7 +433,13 @@ export const useSendNextMessage = () => { | |||
| } | |||
| } | |||
| }, | |||
| [conversationId, setConversation, sendMessage], | |||
| [ | |||
| setConversation, | |||
| sendMessage, | |||
| setConversationIsNew, | |||
| getConversationIsNew, | |||
| conversationId, | |||
| ], | |||
| ); | |||
| const { regenerateMessage } = useRegenerateMessage({ | |||
| @@ -425,24 +450,10 @@ export const useSendNextMessage = () => { | |||
| useEffect(() => { | |||
| // #1289 | |||
| console.log('🚀 ~ useEffect ~ answer:', answer, done); | |||
| if ( | |||
| answer.answer && | |||
| (answer?.conversationId === conversationId || | |||
| ((!done || (done && answer.audio_binary)) && conversationId === '')) | |||
| ) { | |||
| if (answer.answer && conversationId && isNew !== 'true') { | |||
| addNewestAnswer(answer); | |||
| } | |||
| }, [answer, addNewestAnswer, conversationId, done]); | |||
| useEffect(() => { | |||
| // #1289 switch to another conversion window when the last conversion answer doesn't finish. | |||
| if (conversationId) { | |||
| setDone(true); | |||
| } else { | |||
| resetAnswer(); | |||
| } | |||
| }, [setDone, conversationId, resetAnswer]); | |||
| }, [answer, addNewestAnswer, conversationId, isNew]); | |||
| const handlePressEnter = useCallback( | |||
| (documentIds: string[]) => { | |||
| @@ -479,7 +490,6 @@ export const useSendNextMessage = () => { | |||
| ref, | |||
| derivedMessages, | |||
| removeMessageById, | |||
| redirectToNewConversation, | |||
| }; | |||
| }; | |||
| @@ -494,15 +504,12 @@ export const useGetFileIcon = () => { | |||
| }; | |||
| 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; | |||
| }; | |||
| @@ -531,6 +538,7 @@ export const useRenameConversation = () => { | |||
| ...conversation, | |||
| conversation_id: conversation.id, | |||
| name, | |||
| is_new: false, | |||
| }); | |||
| if (ret.retcode === 0) { | |||
| @@ -564,7 +572,7 @@ export const useRenameConversation = () => { | |||
| export const useGetSendButtonDisabled = () => { | |||
| const { dialogId, conversationId } = useGetChatSearchParams(); | |||
| return dialogId === '' && conversationId === ''; | |||
| return dialogId === '' || conversationId === ''; | |||
| }; | |||
| export const useSendButtonDisabled = (value: string) => { | |||
| @@ -575,18 +583,13 @@ 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); | |||
| } | |||
| const data = await setConversation(message, true); | |||
| return data; | |||
| }, | |||
| [setConversation, handleClickConversation], | |||
| [setConversation], | |||
| ); | |||
| return { | |||
| @@ -17,15 +17,15 @@ import { | |||
| Space, | |||
| Spin, | |||
| Tag, | |||
| Tooltip, | |||
| Typography, | |||
| } from 'antd'; | |||
| import { MenuItemProps } from 'antd/lib/menu/MenuItem'; | |||
| import classNames from 'classnames'; | |||
| import { useCallback } from 'react'; | |||
| import { useCallback, useState } from 'react'; | |||
| import ChatConfigurationModal from './chat-configuration-modal'; | |||
| import ChatContainer from './chat-container'; | |||
| import { | |||
| useClickConversationCard, | |||
| useDeleteConversation, | |||
| useDeleteDialog, | |||
| useEditDialog, | |||
| @@ -36,6 +36,7 @@ import { | |||
| import ChatOverviewModal from '@/components/api-service/chat-overview-modal'; | |||
| import { | |||
| useClickConversationCard, | |||
| useClickDialogCard, | |||
| useFetchNextDialogList, | |||
| useGetChatSearchParams, | |||
| @@ -89,6 +90,7 @@ const Chat = () => { | |||
| showModal: showOverviewModal, | |||
| } = useSetModalState(); | |||
| const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>(); | |||
| const [controller, setController] = useState(new AbortController()); | |||
| const handleAppCardEnter = (id: string) => () => { | |||
| handleItemEnter(id); | |||
| @@ -139,31 +141,28 @@ const Chat = () => { | |||
| showConversationRenameModal(conversationId); | |||
| }; | |||
| const handleDialogCardClick = (dialogId: string) => () => { | |||
| handleClickDialog(dialogId); | |||
| }; | |||
| const handleDialogCardClick = useCallback( | |||
| (dialogId: string) => () => { | |||
| handleClickDialog(dialogId); | |||
| }, | |||
| [handleClickDialog], | |||
| ); | |||
| const handleConversationCardClick = (dialogId: string) => () => { | |||
| handleClickConversation(dialogId); | |||
| }; | |||
| const handleConversationCardClick = useCallback( | |||
| (conversationId: string, isNew: boolean) => () => { | |||
| handleClickConversation(conversationId, isNew ? 'true' : ''); | |||
| setController((pre) => { | |||
| pre.abort(); | |||
| return new AbortController(); | |||
| }); | |||
| }, | |||
| [handleClickConversation], | |||
| ); | |||
| const handleCreateTemporaryConversation = useCallback(() => { | |||
| addTemporaryConversation(); | |||
| }, [addTemporaryConversation]); | |||
| const items: MenuProps['items'] = [ | |||
| { | |||
| key: '1', | |||
| onClick: handleCreateTemporaryConversation, | |||
| label: ( | |||
| <Space> | |||
| <PlusOutlined /> | |||
| {t('newChat')} | |||
| </Space> | |||
| ), | |||
| }, | |||
| ]; | |||
| const buildAppItems = (dialog: IDialog) => { | |||
| const dialogId = dialog.id; | |||
| @@ -297,10 +296,9 @@ const Chat = () => { | |||
| <b>{t('chat')}</b> | |||
| <Tag>{conversationList.length}</Tag> | |||
| </Space> | |||
| <Dropdown menu={{ items }}> | |||
| {/* <FormOutlined /> */} | |||
| <PlusOutlined /> | |||
| </Dropdown> | |||
| <Tooltip title={t('newChat')}> | |||
| <PlusOutlined onClick={handleCreateTemporaryConversation} /> | |||
| </Tooltip> | |||
| </Flex> | |||
| <Divider></Divider> | |||
| <Flex vertical gap={10} className={styles.chatTitleContent}> | |||
| @@ -312,7 +310,7 @@ const Chat = () => { | |||
| <Card | |||
| key={x.id} | |||
| hoverable | |||
| onClick={handleConversationCardClick(x.id)} | |||
| onClick={handleConversationCardClick(x.id, x.is_new)} | |||
| onMouseEnter={handleConversationCardEnter(x.id)} | |||
| onMouseLeave={handleConversationItemLeave} | |||
| className={classNames(styles.chatTitleCard, { | |||
| @@ -347,7 +345,7 @@ const Chat = () => { | |||
| </Flex> | |||
| </Flex> | |||
| <Divider type={'vertical'} className={styles.divider}></Divider> | |||
| <ChatContainer></ChatContainer> | |||
| <ChatContainer controller={controller}></ChatContainer> | |||
| {dialogEditVisible && ( | |||
| <ChatConfigurationModal | |||
| visible={dialogEditVisible} | |||
| @@ -89,7 +89,7 @@ const FishAudioModal = ({ | |||
| <Form.Item<FieldType> | |||
| label={t('addFishAudioRefID')} | |||
| name="fish_audio_refid" | |||
| rules={[{ required: false, message: t('FishAudioRefIDMessage') }]} | |||
| rules={[{ required: true, message: t('FishAudioRefIDMessage') }]} | |||
| > | |||
| <Input placeholder={t('FishAudioRefIDMessage')} /> | |||
| </Form.Item> | |||
| @@ -32,3 +32,7 @@ export const buildMessageListWithUuid = (messages?: Message[]) => { | |||
| })) ?? [] | |||
| ); | |||
| }; | |||
| export const getConversationId = () => { | |||
| return uuid().replace(/-/g, ''); | |||
| }; | |||