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
15 changed files with 267 additions and 213 deletions

View File

@ -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} />

View File

@ -1,6 +1 @@
export enum ChatSearchParams {
DialogId = 'dialogId',
ConversationId = 'conversationId',
}
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 {
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 {

View File

@ -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}

View File

@ -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>