fix: Fixed an issue where the first message would be displayed when sending the second message #2625 (#2626)

### 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):
This commit is contained in:
balibabu 2024-09-27 18:20:19 +08:00 committed by GitHub
parent 34abcf7704
commit ca2de896c7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 267 additions and 213 deletions

View File

@ -37,7 +37,9 @@ from graphrag.mind_map_extractor import MindMapExtractor
def set_conversation(): def set_conversation():
req = request.json req = request.json
conv_id = req.get("conversation_id") 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"] del req["conversation_id"]
try: try:
if not ConversationService.update_by_id(conv_id, req): if not ConversationService.update_by_id(conv_id, req):
@ -56,7 +58,7 @@ def set_conversation():
if not e: if not e:
return get_data_error_result(retmsg="Dialog not found") return get_data_error_result(retmsg="Dialog not found")
conv = { conv = {
"id": get_uuid(), "id": conv_id,
"dialog_id": req["dialog_id"], "dialog_id": req["dialog_id"],
"name": req.get("name", "New conversation"), "name": req.get("name", "New conversation"),
"message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}] "message": [{"role": "assistant", "content": dia.prompt_config["prologue"]}]

View File

@ -30,7 +30,7 @@ export default defineConfig({
copy: ['src/conf.json'], copy: ['src/conf.json'],
proxy: { proxy: {
'/v1': { '/v1': {
target: 'http://127.0.0.1:9456/', target: 'http://127.0.0.1:9380/',
changeOrigin: true, changeOrigin: true,
ws: true, ws: true,
logger: console, logger: console,

View File

@ -117,7 +117,7 @@ const MessageInput = ({
file, file,
}) => { }) => {
let nextConversationId: string = conversationId; let nextConversationId: string = conversationId;
if (createConversationBeforeUploadDocument && !conversationId) { if (createConversationBeforeUploadDocument) {
const creatingRet = await createConversationBeforeUploadDocument( const creatingRet = await createConversationBeforeUploadDocument(
file.name, file.name,
); );
@ -234,8 +234,14 @@ const MessageInput = ({
> >
<Button <Button
type={'text'} type={'text'}
disabled={disabled}
icon={ icon={
<SvgIcon name="paper-clip" width={18} height={22}></SvgIcon> <SvgIcon
name="paper-clip"
width={18}
height={22}
disabled={disabled}
></SvgIcon>
} }
></Button> ></Button>
</Upload> </Upload>

View File

@ -2,11 +2,10 @@ import { useDeleteMessage, useFeedback } from '@/hooks/chat-hooks';
import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks'; import { IRemoveMessageById, useSpeechWithSse } from '@/hooks/logic-hooks';
import { IFeedbackRequestBody } from '@/interfaces/request/chat'; import { IFeedbackRequestBody } from '@/interfaces/request/chat';
import { ConversationContext } from '@/pages/chat/context';
import { getMessagePureId } from '@/utils/chat'; import { getMessagePureId } from '@/utils/chat';
import { hexStringToUint8Array } from '@/utils/common-util'; import { hexStringToUint8Array } from '@/utils/common-util';
import { SpeechPlayer } from 'openai-speech-stream-player'; 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) => { export const useSendFeedback = (messageId: string) => {
const { visible, hideModal, showModal } = useSetModalState(); const { visible, hideModal, showModal } = useSetModalState();
@ -59,24 +58,21 @@ export const useSpeech = (content: string, audioBinary?: string) => {
const { read } = useSpeechWithSse(); const { read } = useSpeechWithSse();
const player = useRef<SpeechPlayer>(); const player = useRef<SpeechPlayer>();
const [isPlaying, setIsPlaying] = useState<boolean>(false); const [isPlaying, setIsPlaying] = useState<boolean>(false);
const callback = useContext(ConversationContext);
const initialize = useCallback(async () => { const initialize = useCallback(async () => {
player.current = new SpeechPlayer({ player.current = new SpeechPlayer({
audio: ref.current!, audio: ref.current!,
onPlaying: () => { onPlaying: () => {
setIsPlaying(true); setIsPlaying(true);
callback?.(true);
}, },
onPause: () => { onPause: () => {
setIsPlaying(false); setIsPlaying(false);
callback?.(false);
}, },
onChunkEnd: () => {}, onChunkEnd: () => {},
mimeType: 'audio/mpeg', mimeType: 'audio/mpeg',
}); });
await player.current.init(); await player.current.init();
}, [callback]); }, []);
const pause = useCallback(() => { const pause = useCallback(() => {
player.current?.pause(); player.current?.pause();
@ -103,7 +99,11 @@ export const useSpeech = (content: string, audioBinary?: string) => {
if (audioBinary) { if (audioBinary) {
const units = hexStringToUint8Array(audioBinary); const units = hexStringToUint8Array(audioBinary);
if (units) { if (units) {
player.current?.feed(units); try {
player.current?.feed(units);
} catch (error) {
console.warn(error);
}
} }
} }
}, [audioBinary]); }, [audioBinary]);

View File

@ -19,6 +19,7 @@ export enum SharedFrom {
export enum ChatSearchParams { export enum ChatSearchParams {
DialogId = 'dialogId', DialogId = 'dialogId',
ConversationId = 'conversationId', ConversationId = 'conversationId',
isNew = 'isNew',
} }
export const EmptyConversationId = 'empty'; export const EmptyConversationId = 'empty';

View File

@ -12,18 +12,22 @@ import {
import i18n from '@/locales/config'; import i18n from '@/locales/config';
import { IClientConversation } from '@/pages/chat/interface'; import { IClientConversation } from '@/pages/chat/interface';
import chatService from '@/services/chat-service'; 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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { message } from 'antd'; import { message } from 'antd';
import dayjs, { Dayjs } from 'dayjs'; import dayjs, { Dayjs } from 'dayjs';
import { has, set } from 'lodash'; import { has, set } from 'lodash';
import { useCallback, useMemo, useState } from 'react'; import { useCallback, useMemo, useState } from 'react';
import { useSearchParams } from 'umi'; import { history, useSearchParams } from 'umi';
//#region logic //#region logic
export const useClickDialogCard = () => { export const useClickDialogCard = () => {
const [, setSearchParams] = useSearchParams(); const [_, setSearchParams] = useSearchParams();
const newQueryParameters: URLSearchParams = useMemo(() => { const newQueryParameters: URLSearchParams = useMemo(() => {
return new URLSearchParams(); return new URLSearchParams();
@ -44,6 +48,25 @@ export const useClickDialogCard = () => {
return { handleClickDialog }; 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 = () => { export const useGetChatSearchParams = () => {
const [currentQueryParameters] = useSearchParams(); const [currentQueryParameters] = useSearchParams();
@ -51,6 +74,7 @@ export const useGetChatSearchParams = () => {
dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '',
conversationId: conversationId:
currentQueryParameters.get(ChatSearchParams.ConversationId) || '', currentQueryParameters.get(ChatSearchParams.ConversationId) || '',
isNew: currentQueryParameters.get(ChatSearchParams.isNew) || '',
}; };
}; };
@ -60,6 +84,7 @@ export const useGetChatSearchParams = () => {
export const useFetchNextDialogList = () => { export const useFetchNextDialogList = () => {
const { handleClickDialog } = useClickDialogCard(); const { handleClickDialog } = useClickDialogCard();
const { dialogId } = useGetChatSearchParams();
const { const {
data, data,
@ -70,11 +95,20 @@ export const useFetchNextDialogList = () => {
initialData: [], initialData: [],
gcTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
queryFn: async () => { refetchOnMount: false,
queryFn: async (...params) => {
console.log('🚀 ~ queryFn: ~ params:', params);
const { data } = await chatService.listDialog(); const { data } = await chatService.listDialog();
if (data.retcode === 0 && data.data.length > 0) { if (data.retcode === 0) {
handleClickDialog(data.data[0].id); 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 ?? []; return data?.data ?? [];
@ -86,6 +120,7 @@ export const useFetchNextDialogList = () => {
export const useSetNextDialog = () => { export const useSetNextDialog = () => {
const queryClient = useQueryClient(); const queryClient = useQueryClient();
const { const {
data, data,
isPending: loading, isPending: loading,
@ -96,8 +131,10 @@ export const useSetNextDialog = () => {
const { data } = await chatService.setDialog(params); const { data } = await chatService.setDialog(params);
if (data.retcode === 0) { if (data.retcode === 0) {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
exact: false,
queryKey: ['fetchDialogList'], queryKey: ['fetchDialogList'],
}); });
queryClient.invalidateQueries({ queryClient.invalidateQueries({
queryKey: ['fetchDialog'], queryKey: ['fetchDialog'],
}); });
@ -166,6 +203,7 @@ export const useRemoveNextDialog = () => {
const { data } = await chatService.removeDialog({ dialogIds }); const { data } = await chatService.removeDialog({ dialogIds });
if (data.retcode === 0) { if (data.retcode === 0) {
queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] }); queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] });
message.success(i18n.t('message.deleted')); message.success(i18n.t('message.deleted'));
} }
return data.retcode; return data.retcode;
@ -181,6 +219,7 @@ export const useRemoveNextDialog = () => {
export const useFetchNextConversationList = () => { export const useFetchNextConversationList = () => {
const { dialogId } = useGetChatSearchParams(); const { dialogId } = useGetChatSearchParams();
const { handleClickConversation } = useClickConversationCard();
const { const {
data, data,
isFetching: loading, isFetching: loading,
@ -193,7 +232,9 @@ export const useFetchNextConversationList = () => {
enabled: !!dialogId, enabled: !!dialogId,
queryFn: async () => { queryFn: async () => {
const { data } = await chatService.listConversation({ dialogId }); const { data } = await chatService.listConversation({ dialogId });
if (data.retcode === 0 && data.data.length > 0) {
handleClickConversation(data.data[0].id, '');
}
return data?.data; return data?.data;
}, },
}); });
@ -202,7 +243,7 @@ export const useFetchNextConversationList = () => {
}; };
export const useFetchNextConversation = () => { export const useFetchNextConversation = () => {
const { conversationId } = useGetChatSearchParams(); const { isNew, conversationId } = useGetChatSearchParams();
const { const {
data, data,
isFetching: loading, isFetching: loading,
@ -214,17 +255,9 @@ export const useFetchNextConversation = () => {
gcTime: 0, gcTime: 0,
refetchOnWindowFocus: false, refetchOnWindowFocus: false,
queryFn: async () => { queryFn: async () => {
if (isConversationIdExist(conversationId)) { if (isNew !== 'true' && isConversationIdExist(conversationId)) {
const { data } = await chatService.getConversation({ 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 conversation = data?.data ?? {};
const messageList = buildMessageListWithUuid(conversation?.message); const messageList = buildMessageListWithUuid(conversation?.message);
@ -265,7 +298,12 @@ export const useUpdateNextConversation = () => {
} = useMutation({ } = useMutation({
mutationKey: ['updateConversation'], mutationKey: ['updateConversation'],
mutationFn: async (params: Record<string, any>) => { 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) { if (data.retcode === 0) {
queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] });
} }

View File

@ -224,6 +224,7 @@ export const useSendMessageWithSse = (
const send = useCallback( const send = useCallback(
async ( async (
body: any, body: any,
controller?: AbortController,
): Promise<{ response: Response; data: ResponseType } | undefined> => { ): Promise<{ response: Response; data: ResponseType } | undefined> => {
try { try {
setDone(false); setDone(false);
@ -234,6 +235,7 @@ export const useSendMessageWithSse = (
'Content-Type': 'application/json', 'Content-Type': 'application/json',
}, },
body: JSON.stringify(body), body: JSON.stringify(body),
signal: controller?.signal,
}); });
const res = response.clone().json(); const res = response.clone().json();
@ -249,6 +251,7 @@ export const useSendMessageWithSse = (
const { done, value } = x; const { done, value } = x;
if (done) { if (done) {
console.info('done'); console.info('done');
setAnswer({} as IAnswer);
break; break;
} }
try { try {
@ -268,9 +271,12 @@ export const useSendMessageWithSse = (
} }
console.info('done?'); console.info('done?');
setDone(true); setDone(true);
setAnswer({} as IAnswer);
return { data: await res, response }; return { data: await res, response };
} catch (e) { } catch (e) {
setDone(true); setDone(true);
setAnswer({} as IAnswer);
console.warn(e); console.warn(e);
} }
}, },

View File

@ -63,6 +63,7 @@ export interface IConversation {
name: string; name: string;
update_date: string; update_date: string;
update_time: number; update_time: number;
is_new: true;
} }
export interface Message { export interface Message {

View File

@ -580,7 +580,7 @@ The above is the content you need to summarize.`,
addGoogleRegion: 'Google Cloud Region', addGoogleRegion: 'Google Cloud Region',
GoogleRegionMessage: 'Please input Google Cloud Region', GoogleRegionMessage: 'Please input Google Cloud Region',
modelProvidersWarn: 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: { message: {
registered: 'Registered!', registered: 'Registered!',

View File

@ -19,10 +19,13 @@ import {
} from '@/hooks/chat-hooks'; } from '@/hooks/chat-hooks';
import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; import { useFetchUserInfo } from '@/hooks/user-setting-hooks';
import { memo } from 'react'; import { memo } from 'react';
import { ConversationContext } from '../context';
import styles from './index.less'; import styles from './index.less';
const ChatContainer = () => { interface IProps {
controller: AbortController;
}
const ChatContainer = ({ controller }: IProps) => {
const { conversationId } = useGetChatSearchParams(); const { conversationId } = useGetChatSearchParams();
const { data: conversation } = useFetchNextConversation(); const { data: conversation } = useFetchNextConversation();
@ -36,8 +39,7 @@ const ChatContainer = () => {
handlePressEnter, handlePressEnter,
regenerateMessage, regenerateMessage,
removeMessageById, removeMessageById,
redirectToNewConversation, } = useSendNextMessage(controller);
} = useSendNextMessage();
const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } = const { visible, hideModal, documentId, selectedChunk, clickDocumentButton } =
useClickDrawer(); useClickDrawer();
@ -54,35 +56,33 @@ const ChatContainer = () => {
<Flex flex={1} vertical className={styles.messageContainer}> <Flex flex={1} vertical className={styles.messageContainer}>
<div> <div>
<Spin spinning={loading}> <Spin spinning={loading}>
<ConversationContext.Provider value={redirectToNewConversation}> {derivedMessages?.map((message, i) => {
{derivedMessages?.map((message, i) => { return (
return ( <MessageItem
<MessageItem loading={
loading={ message.role === MessageType.Assistant &&
message.role === MessageType.Assistant && sendLoading &&
sendLoading && derivedMessages.length - 1 === i
derivedMessages.length - 1 === i }
} key={message.id}
key={message.id} item={message}
item={message} nickname={userInfo.nickname}
nickname={userInfo.nickname} avatar={userInfo.avatar}
avatar={userInfo.avatar} reference={buildMessageItemReference(
reference={buildMessageItemReference( {
{ message: derivedMessages,
message: derivedMessages, reference: conversation.reference,
reference: conversation.reference, },
}, message,
message, )}
)} clickDocumentButton={clickDocumentButton}
clickDocumentButton={clickDocumentButton} index={i}
index={i} removeMessageById={removeMessageById}
removeMessageById={removeMessageById} regenerateMessage={regenerateMessage}
regenerateMessage={regenerateMessage} sendLoading={sendLoading}
sendLoading={sendLoading} ></MessageItem>
></MessageItem> );
); })}
})}
</ConversationContext.Provider>
</Spin> </Spin>
</div> </div>
<div ref={ref} /> <div ref={ref} />

View File

@ -1,6 +1 @@
export enum ChatSearchParams {
DialogId = 'dialogId',
ConversationId = 'conversationId',
}
export const EmptyConversationId = 'empty'; export const EmptyConversationId = 'empty';

View File

@ -1,4 +1,4 @@
import { MessageType } from '@/constants/chat'; import { ChatSearchParams, MessageType } from '@/constants/chat';
import { fileIconMap } from '@/constants/common'; import { fileIconMap } from '@/constants/common';
import { import {
useFetchManualConversation, useFetchManualConversation,
@ -24,6 +24,8 @@ import {
} from '@/hooks/logic-hooks'; } from '@/hooks/logic-hooks';
import { IConversation, IDialog, Message } from '@/interfaces/database/chat'; import { IConversation, IDialog, Message } from '@/interfaces/database/chat';
import { getFileExtension } from '@/utils'; import { getFileExtension } from '@/utils';
import api from '@/utils/api';
import { getConversationId } from '@/utils/chat';
import { useMutationState } from '@tanstack/react-query'; import { useMutationState } from '@tanstack/react-query';
import { get } from 'lodash'; import { get } from 'lodash';
import trim from 'lodash/trim'; import trim from 'lodash/trim';
@ -32,18 +34,57 @@ import {
useCallback, useCallback,
useEffect, useEffect,
useMemo, useMemo,
useRef,
useState, useState,
} from 'react'; } from 'react';
import { useSearchParams } from 'umi'; import { useSearchParams } from 'umi';
import { v4 as uuid } from 'uuid'; import { v4 as uuid } from 'uuid';
import { ChatSearchParams } from './constants';
import { import {
IClientConversation, IClientConversation,
IMessage, IMessage,
VariableTableDataType, VariableTableDataType,
} from './interface'; } 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 = () => { export const useSelectCurrentDialog = () => {
const data = useMutationState({ const data = useMutationState({
filters: { mutationKey: ['fetchDialog'] }, filters: { mutationKey: ['fetchDialog'] },
@ -169,22 +210,26 @@ export const useSelectDerivedConversationList = () => {
const { data: conversationList, loading } = useFetchNextConversationList(); const { data: conversationList, loading } = useFetchNextConversationList();
const { dialogId } = useGetChatSearchParams(); const { dialogId } = useGetChatSearchParams();
const prologue = currentDialog?.prompt_config?.prologue ?? ''; const prologue = currentDialog?.prompt_config?.prologue ?? '';
const { setNewConversationRouteParams } = useSetNewConversationRouteParams();
const addTemporaryConversation = useCallback(() => { const addTemporaryConversation = useCallback(() => {
const conversationId = getConversationId();
setList((pre) => { setList((pre) => {
if (dialogId) { if (dialogId) {
setNewConversationRouteParams(conversationId, 'true');
const nextList = [ const nextList = [
{ {
id: '', id: conversationId,
name: t('newConversation'), name: t('newConversation'),
dialog_id: dialogId, dialog_id: dialogId,
is_new: true,
message: [ message: [
{ {
content: prologue, content: prologue,
role: MessageType.Assistant, role: MessageType.Assistant,
}, },
], ],
} as IConversation, } as any,
...conversationList, ...conversationList,
]; ];
return nextList; return nextList;
@ -192,42 +237,32 @@ export const useSelectDerivedConversationList = () => {
return pre; return pre;
}); });
}, [conversationList, dialogId, prologue, t]); }, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]);
// When you first enter the page, select the top conversation card
useEffect(() => { useEffect(() => {
addTemporaryConversation(); setList([...conversationList]);
}, [addTemporaryConversation]); }, [conversationList]);
return { list, addTemporaryConversation, loading }; 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 = () => { export const useSetConversation = () => {
const { dialogId } = useGetChatSearchParams(); const { dialogId } = useGetChatSearchParams();
const { updateConversation } = useUpdateNextConversation(); const { updateConversation } = useUpdateNextConversation();
const setConversation = useCallback( const setConversation = useCallback(
(message: string) => { async (
return updateConversation({ message: string,
isNew: boolean = false,
conversationId?: string,
) => {
const data = await updateConversation({
dialog_id: dialogId, dialog_id: dialogId,
name: message, name: message,
is_new: isNew,
conversation_id: conversationId,
message: [ message: [
{ {
role: MessageType.Assistant, role: MessageType.Assistant,
@ -235,6 +270,8 @@ export const useSetConversation = () => {
}, },
], ],
}); });
return data;
}, },
[updateConversation, dialogId], [updateConversation, dialogId],
); );
@ -242,22 +279,6 @@ export const useSetConversation = () => {
return { setConversation }; 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 = () => { export const useSelectNextMessages = () => {
const { const {
ref, ref,
@ -271,10 +292,10 @@ export const useSelectNextMessages = () => {
} = useSelectDerivedMessages(); } = useSelectDerivedMessages();
const { data: conversation, loading } = useFetchNextConversation(); const { data: conversation, loading } = useFetchNextConversation();
const { data: dialog } = useFetchNextDialog(); const { data: dialog } = useFetchNextDialog();
const { conversationId, dialogId } = useGetChatSearchParams(); const { conversationId, dialogId, isNew } = useGetChatSearchParams();
const addPrologue = useCallback(() => { const addPrologue = useCallback(() => {
if (dialogId !== '' && conversationId === '') { if (dialogId !== '' && isNew === 'true') {
const prologue = dialog.prompt_config?.prologue; const prologue = dialog.prompt_config?.prologue;
const nextMessage = { const nextMessage = {
@ -285,17 +306,25 @@ export const useSelectNextMessages = () => {
setDerivedMessages([nextMessage]); setDerivedMessages([nextMessage]);
} }
}, [conversationId, dialog, dialogId, setDerivedMessages]); }, [isNew, dialog, dialogId, setDerivedMessages]);
useEffect(() => { useEffect(() => {
addPrologue(); addPrologue();
}, [addPrologue]); }, [addPrologue]);
useEffect(() => { useEffect(() => {
if (conversationId) { if (
conversationId &&
isNew !== 'true' &&
conversation.message?.length > 0
) {
setDerivedMessages(conversation.message); setDerivedMessages(conversation.message);
} }
}, [conversation.message, conversationId, setDerivedMessages]);
if (!conversationId) {
setDerivedMessages([]);
}
}, [conversation.message, conversationId, setDerivedMessages, isNew]);
return { return {
ref, ref,
@ -325,12 +354,14 @@ export const useHandleMessageInputChange = () => {
}; };
}; };
export const useSendNextMessage = () => { export const useSendNextMessage = (controller: AbortController) => {
const { setConversation } = useSetConversation(); const { setConversation } = useSetConversation();
const { conversationId } = useGetChatSearchParams(); const { conversationId, isNew } = useGetChatSearchParams();
const { handleInputChange, value, setValue } = useHandleMessageInputChange(); const { handleInputChange, value, setValue } = useHandleMessageInputChange();
const { handleClickConversation } = useClickConversationCard();
const { send, answer, done, setDone, resetAnswer } = useSendMessageWithSse(); const { send, answer, done } = useSendMessageWithSse(
api.completeConversation,
);
const { const {
ref, ref,
derivedMessages, derivedMessages,
@ -341,17 +372,8 @@ export const useSendNextMessage = () => {
removeMessageById, removeMessageById,
removeMessagesAfterCurrentMessage, removeMessagesAfterCurrentMessage,
} = useSelectNextMessages(); } = useSelectNextMessages();
const { data: dialog } = useFetchNextDialog(); const { setConversationIsNew, getConversationIsNew } =
const currentConversationIdRef = useRef<string>(''); useSetChatRouteParams();
const redirectToNewConversation = useCallback(
(isPlaying: boolean) => {
if (!conversationId && dialog?.prompt_config?.tts && !isPlaying) {
handleClickConversation(currentConversationIdRef.current);
}
},
[dialog, handleClickConversation, conversationId],
);
const sendMessage = useCallback( const sendMessage = useCallback(
async ({ async ({
@ -363,49 +385,46 @@ export const useSendNextMessage = () => {
currentConversationId?: string; currentConversationId?: string;
messages?: Message[]; messages?: Message[];
}) => { }) => {
const res = await send({ const res = await send(
conversation_id: currentConversationId ?? conversationId, {
messages: [...(messages ?? derivedMessages ?? []), message], conversation_id: currentConversationId ?? conversationId,
}); messages: [...(messages ?? derivedMessages ?? []), message],
},
controller,
);
if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) { if (res && (res?.response.status !== 200 || res?.data?.retcode !== 0)) {
// cancel loading // cancel loading
setValue(message.content); setValue(message.content);
console.info('removeLatestMessage111'); console.info('removeLatestMessage111');
removeLatestMessage(); removeLatestMessage();
} else {
if (currentConversationId) {
console.info('111');
// new conversation
if (!dialog?.prompt_config?.tts) {
handleClickConversation(currentConversationId);
}
} else {
console.info('222');
// fetchConversation(conversationId);
}
} }
}, },
[ [
dialog,
derivedMessages, derivedMessages,
conversationId, conversationId,
handleClickConversation,
removeLatestMessage, removeLatestMessage,
setValue, setValue,
send, send,
controller,
], ],
); );
const handleSendMessage = useCallback( const handleSendMessage = useCallback(
async (message: Message) => { async (message: Message) => {
if (conversationId !== '') { const isNew = getConversationIsNew();
if (isNew !== 'true') {
sendMessage({ message }); sendMessage({ message });
} else { } else {
const data = await setConversation(message.content); const data = await setConversation(
message.content,
true,
conversationId,
);
if (data.retcode === 0) { if (data.retcode === 0) {
setConversationIsNew('');
const id = data.data.id; const id = data.data.id;
currentConversationIdRef.current = id; // currentConversationIdRef.current = id;
sendMessage({ sendMessage({
message, message,
currentConversationId: id, currentConversationId: id,
@ -414,7 +433,13 @@ export const useSendNextMessage = () => {
} }
} }
}, },
[conversationId, setConversation, sendMessage], [
setConversation,
sendMessage,
setConversationIsNew,
getConversationIsNew,
conversationId,
],
); );
const { regenerateMessage } = useRegenerateMessage({ const { regenerateMessage } = useRegenerateMessage({
@ -425,24 +450,10 @@ export const useSendNextMessage = () => {
useEffect(() => { useEffect(() => {
// #1289 // #1289
console.log('🚀 ~ useEffect ~ answer:', answer, done); if (answer.answer && conversationId && isNew !== 'true') {
if (
answer.answer &&
(answer?.conversationId === conversationId ||
((!done || (done && answer.audio_binary)) && conversationId === ''))
) {
addNewestAnswer(answer); addNewestAnswer(answer);
} }
}, [answer, addNewestAnswer, conversationId, done]); }, [answer, addNewestAnswer, conversationId, isNew]);
useEffect(() => {
// #1289 switch to another conversion window when the last conversion answer doesn't finish.
if (conversationId) {
setDone(true);
} else {
resetAnswer();
}
}, [setDone, conversationId, resetAnswer]);
const handlePressEnter = useCallback( const handlePressEnter = useCallback(
(documentIds: string[]) => { (documentIds: string[]) => {
@ -479,7 +490,6 @@ export const useSendNextMessage = () => {
ref, ref,
derivedMessages, derivedMessages,
removeMessageById, removeMessageById,
redirectToNewConversation,
}; };
}; };
@ -494,15 +504,12 @@ export const useGetFileIcon = () => {
}; };
export const useDeleteConversation = () => { export const useDeleteConversation = () => {
const { handleClickConversation } = useClickConversationCard();
const showDeleteConfirm = useShowDeleteConfirm(); const showDeleteConfirm = useShowDeleteConfirm();
const { removeConversation } = useRemoveNextConversation(); const { removeConversation } = useRemoveNextConversation();
const deleteConversation = (conversationIds: Array<string>) => async () => { const deleteConversation = (conversationIds: Array<string>) => async () => {
const ret = await removeConversation(conversationIds); const ret = await removeConversation(conversationIds);
if (ret === 0) {
handleClickConversation('');
}
return ret; return ret;
}; };
@ -531,6 +538,7 @@ export const useRenameConversation = () => {
...conversation, ...conversation,
conversation_id: conversation.id, conversation_id: conversation.id,
name, name,
is_new: false,
}); });
if (ret.retcode === 0) { if (ret.retcode === 0) {
@ -564,7 +572,7 @@ export const useRenameConversation = () => {
export const useGetSendButtonDisabled = () => { export const useGetSendButtonDisabled = () => {
const { dialogId, conversationId } = useGetChatSearchParams(); const { dialogId, conversationId } = useGetChatSearchParams();
return dialogId === '' && conversationId === ''; return dialogId === '' || conversationId === '';
}; };
export const useSendButtonDisabled = (value: string) => { export const useSendButtonDisabled = (value: string) => {
@ -575,18 +583,13 @@ export const useCreateConversationBeforeUploadDocument = () => {
const { setConversation } = useSetConversation(); const { setConversation } = useSetConversation();
const { dialogId } = useGetChatSearchParams(); const { dialogId } = useGetChatSearchParams();
const { handleClickConversation } = useClickConversationCard();
const createConversationBeforeUploadDocument = useCallback( const createConversationBeforeUploadDocument = useCallback(
async (message: string) => { async (message: string) => {
const data = await setConversation(message); const data = await setConversation(message, true);
if (data.retcode === 0) {
const id = data.data.id;
handleClickConversation(id);
}
return data; return data;
}, },
[setConversation, handleClickConversation], [setConversation],
); );
return { return {

View File

@ -17,15 +17,15 @@ import {
Space, Space,
Spin, Spin,
Tag, Tag,
Tooltip,
Typography, Typography,
} from 'antd'; } from 'antd';
import { MenuItemProps } from 'antd/lib/menu/MenuItem'; import { MenuItemProps } from 'antd/lib/menu/MenuItem';
import classNames from 'classnames'; import classNames from 'classnames';
import { useCallback } from 'react'; import { useCallback, useState } from 'react';
import ChatConfigurationModal from './chat-configuration-modal'; import ChatConfigurationModal from './chat-configuration-modal';
import ChatContainer from './chat-container'; import ChatContainer from './chat-container';
import { import {
useClickConversationCard,
useDeleteConversation, useDeleteConversation,
useDeleteDialog, useDeleteDialog,
useEditDialog, useEditDialog,
@ -36,6 +36,7 @@ import {
import ChatOverviewModal from '@/components/api-service/chat-overview-modal'; import ChatOverviewModal from '@/components/api-service/chat-overview-modal';
import { import {
useClickConversationCard,
useClickDialogCard, useClickDialogCard,
useFetchNextDialogList, useFetchNextDialogList,
useGetChatSearchParams, useGetChatSearchParams,
@ -89,6 +90,7 @@ const Chat = () => {
showModal: showOverviewModal, showModal: showOverviewModal,
} = useSetModalState(); } = useSetModalState();
const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>(); const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>();
const [controller, setController] = useState(new AbortController());
const handleAppCardEnter = (id: string) => () => { const handleAppCardEnter = (id: string) => () => {
handleItemEnter(id); handleItemEnter(id);
@ -139,31 +141,28 @@ const Chat = () => {
showConversationRenameModal(conversationId); showConversationRenameModal(conversationId);
}; };
const handleDialogCardClick = (dialogId: string) => () => { const handleDialogCardClick = useCallback(
handleClickDialog(dialogId); (dialogId: string) => () => {
}; handleClickDialog(dialogId);
},
[handleClickDialog],
);
const handleConversationCardClick = (dialogId: string) => () => { const handleConversationCardClick = useCallback(
handleClickConversation(dialogId); (conversationId: string, isNew: boolean) => () => {
}; handleClickConversation(conversationId, isNew ? 'true' : '');
setController((pre) => {
pre.abort();
return new AbortController();
});
},
[handleClickConversation],
);
const handleCreateTemporaryConversation = useCallback(() => { const handleCreateTemporaryConversation = useCallback(() => {
addTemporaryConversation(); addTemporaryConversation();
}, [addTemporaryConversation]); }, [addTemporaryConversation]);
const items: MenuProps['items'] = [
{
key: '1',
onClick: handleCreateTemporaryConversation,
label: (
<Space>
<PlusOutlined />
{t('newChat')}
</Space>
),
},
];
const buildAppItems = (dialog: IDialog) => { const buildAppItems = (dialog: IDialog) => {
const dialogId = dialog.id; const dialogId = dialog.id;
@ -297,10 +296,9 @@ const Chat = () => {
<b>{t('chat')}</b> <b>{t('chat')}</b>
<Tag>{conversationList.length}</Tag> <Tag>{conversationList.length}</Tag>
</Space> </Space>
<Dropdown menu={{ items }}> <Tooltip title={t('newChat')}>
{/* <FormOutlined /> */} <PlusOutlined onClick={handleCreateTemporaryConversation} />
<PlusOutlined /> </Tooltip>
</Dropdown>
</Flex> </Flex>
<Divider></Divider> <Divider></Divider>
<Flex vertical gap={10} className={styles.chatTitleContent}> <Flex vertical gap={10} className={styles.chatTitleContent}>
@ -312,7 +310,7 @@ const Chat = () => {
<Card <Card
key={x.id} key={x.id}
hoverable hoverable
onClick={handleConversationCardClick(x.id)} onClick={handleConversationCardClick(x.id, x.is_new)}
onMouseEnter={handleConversationCardEnter(x.id)} onMouseEnter={handleConversationCardEnter(x.id)}
onMouseLeave={handleConversationItemLeave} onMouseLeave={handleConversationItemLeave}
className={classNames(styles.chatTitleCard, { className={classNames(styles.chatTitleCard, {
@ -347,7 +345,7 @@ const Chat = () => {
</Flex> </Flex>
</Flex> </Flex>
<Divider type={'vertical'} className={styles.divider}></Divider> <Divider type={'vertical'} className={styles.divider}></Divider>
<ChatContainer></ChatContainer> <ChatContainer controller={controller}></ChatContainer>
{dialogEditVisible && ( {dialogEditVisible && (
<ChatConfigurationModal <ChatConfigurationModal
visible={dialogEditVisible} visible={dialogEditVisible}

View File

@ -89,7 +89,7 @@ const FishAudioModal = ({
<Form.Item<FieldType> <Form.Item<FieldType>
label={t('addFishAudioRefID')} label={t('addFishAudioRefID')}
name="fish_audio_refid" name="fish_audio_refid"
rules={[{ required: false, message: t('FishAudioRefIDMessage') }]} rules={[{ required: true, message: t('FishAudioRefIDMessage') }]}
> >
<Input placeholder={t('FishAudioRefIDMessage')} /> <Input placeholder={t('FishAudioRefIDMessage')} />
</Form.Item> </Form.Item>

View File

@ -32,3 +32,7 @@ export const buildMessageListWithUuid = (messages?: Message[]) => {
})) ?? [] })) ?? []
); );
}; };
export const getConversationId = () => {
return uuid().replace(/-/g, '');
};