### What problem does this PR solve? Feat: Render chat page #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.1
| @@ -271,9 +271,14 @@ export const useFetchConversation = () => { | |||
| isNew !== 'true' && | |||
| isConversationIdExist(sharedId || conversationId) | |||
| ) { | |||
| const { data } = await chatService.getConversation({ | |||
| conversationId: conversationId || sharedId, | |||
| }); | |||
| const { data } = await chatService.getConversation( | |||
| { | |||
| params: { | |||
| conversationId: conversationId || sharedId, | |||
| }, | |||
| }, | |||
| true, | |||
| ); | |||
| const conversation = data?.data ?? {}; | |||
| @@ -31,7 +31,7 @@ export function ChatCard({ data, showChatRenameModal }: IProps) { | |||
| </section> | |||
| <div className="flex justify-between items-end"> | |||
| <div className="w-full"> | |||
| <h3 className="text-lg font-semibold mb-2 line-clamp-1"> | |||
| <h3 className="text-lg font-semibold mb-2 line-clamp-1 truncate"> | |||
| {data.name} | |||
| </h3> | |||
| <p className="text-xs text-text-sub-title">{data.description}</p> | |||
| @@ -0,0 +1,23 @@ | |||
| import { | |||
| Sheet, | |||
| SheetContent, | |||
| SheetHeader, | |||
| SheetTitle, | |||
| SheetTrigger, | |||
| } from '@/components/ui/sheet'; | |||
| import { PropsWithChildren } from 'react'; | |||
| import { ChatSettings } from './chat-settings'; | |||
| export function ChatSettingSheet({ children }: PropsWithChildren) { | |||
| return ( | |||
| <Sheet> | |||
| <SheetTrigger asChild>{children}</SheetTrigger> | |||
| <SheetContent> | |||
| <SheetHeader> | |||
| <SheetTitle>Chat Settings</SheetTitle> | |||
| </SheetHeader> | |||
| <ChatSettings></ChatSettings> | |||
| </SheetContent> | |||
| </Sheet> | |||
| ); | |||
| } | |||
| @@ -7,8 +7,9 @@ import { ChatModelSettings } from './chat-model-settings'; | |||
| import { ChatPromptEngine } from './chat-prompt-engine'; | |||
| import { useChatSettingSchema } from './use-chat-setting-schema'; | |||
| export function AppSettings() { | |||
| export function ChatSettings() { | |||
| const formSchema = useChatSettingSchema(); | |||
| const form = useForm<z.infer<typeof formSchema>>({ | |||
| resolver: zodResolver(formSchema), | |||
| defaultValues: { | |||
| @@ -32,27 +33,19 @@ export function AppSettings() { | |||
| } | |||
| return ( | |||
| <section className="py-6 w-[500px] max-w-[25%] "> | |||
| <div className="text-2xl font-bold mb-4 text-colors-text-neutral-strong px-6"> | |||
| App settings | |||
| </div> | |||
| <div className="overflow-auto max-h-[81vh] px-6 "> | |||
| <FormProvider {...form}> | |||
| <form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6"> | |||
| <ChatBasicSetting></ChatBasicSetting> | |||
| <ChatPromptEngine></ChatPromptEngine> | |||
| <ChatModelSettings></ChatModelSettings> | |||
| </form> | |||
| </FormProvider> | |||
| </div> | |||
| <div className="p-6 text-center"> | |||
| <p className="text-colors-text-neutral-weak mb-1"> | |||
| There are unsaved changes | |||
| </p> | |||
| <Button variant={'tertiary'} className="w-full"> | |||
| Update | |||
| </Button> | |||
| </div> | |||
| <section className="py-6"> | |||
| <FormProvider {...form}> | |||
| <form | |||
| onSubmit={form.handleSubmit(onSubmit)} | |||
| className="space-y-6 overflow-auto max-h-[88vh] pr-4" | |||
| > | |||
| <ChatBasicSetting></ChatBasicSetting> | |||
| <ChatPromptEngine></ChatPromptEngine> | |||
| <ChatModelSettings></ChatModelSettings> | |||
| </form> | |||
| </FormProvider> | |||
| <Button className="w-full my-4">Update</Button> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -1,9 +1,95 @@ | |||
| import { ChatInput } from '@/components/chat-input'; | |||
| import { NextMessageInput } from '@/components/message-input/next'; | |||
| import MessageItem from '@/components/message-item'; | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { | |||
| useFetchConversation, | |||
| useFetchDialog, | |||
| useGetChatSearchParams, | |||
| } from '@/hooks/use-chat-request'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | |||
| import { | |||
| useGetSendButtonDisabled, | |||
| useSendButtonDisabled, | |||
| } from '../hooks/use-button-disabled'; | |||
| import { useCreateConversationBeforeUploadDocument } from '../hooks/use-create-conversation'; | |||
| import { useSendMessage } from '../hooks/use-send-chat-message'; | |||
| import { buildMessageItemReference } from '../utils'; | |||
| interface IProps { | |||
| controller: AbortController; | |||
| } | |||
| export function ChatBox({ controller }: IProps) { | |||
| const { | |||
| value, | |||
| scrollRef, | |||
| messageContainerRef, | |||
| sendLoading, | |||
| derivedMessages, | |||
| handleInputChange, | |||
| handlePressEnter, | |||
| regenerateMessage, | |||
| removeMessageById, | |||
| stopOutputMessage, | |||
| } = useSendMessage(controller); | |||
| const { data: userInfo } = useFetchUserInfo(); | |||
| const { data: currentDialog } = useFetchDialog(); | |||
| const { createConversationBeforeUploadDocument } = | |||
| useCreateConversationBeforeUploadDocument(); | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { data: conversation } = useFetchConversation(); | |||
| const disabled = useGetSendButtonDisabled(); | |||
| const sendDisabled = useSendButtonDisabled(value); | |||
| export function ChatBox() { | |||
| return ( | |||
| <section className="border-x flex-1"> | |||
| <ChatInput></ChatInput> | |||
| <section className="border-x flex flex-col p-5 w-full"> | |||
| <div ref={messageContainerRef} className="flex-1 overflow-auto"> | |||
| <div className="w-full"> | |||
| {derivedMessages?.map((message, i) => { | |||
| return ( | |||
| <MessageItem | |||
| loading={ | |||
| message.role === MessageType.Assistant && | |||
| sendLoading && | |||
| derivedMessages.length - 1 === i | |||
| } | |||
| key={buildMessageUuidWithRole(message)} | |||
| item={message} | |||
| nickname={userInfo.nickname} | |||
| avatar={userInfo.avatar} | |||
| avatarDialog={currentDialog.icon} | |||
| reference={buildMessageItemReference( | |||
| { | |||
| message: derivedMessages, | |||
| reference: conversation.reference, | |||
| }, | |||
| message, | |||
| )} | |||
| // clickDocumentButton={clickDocumentButton} | |||
| index={i} | |||
| removeMessageById={removeMessageById} | |||
| regenerateMessage={regenerateMessage} | |||
| sendLoading={sendLoading} | |||
| ></MessageItem> | |||
| ); | |||
| })} | |||
| </div> | |||
| <div ref={scrollRef} /> | |||
| </div> | |||
| <NextMessageInput | |||
| disabled={disabled} | |||
| sendDisabled={sendDisabled} | |||
| sendLoading={sendLoading} | |||
| value={value} | |||
| onInputChange={handleInputChange} | |||
| onPressEnter={handlePressEnter} | |||
| conversationId={conversationId} | |||
| createConversationBeforeUploadDocument={ | |||
| createConversationBeforeUploadDocument | |||
| } | |||
| stopOutputMessage={stopOutputMessage} | |||
| /> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -10,7 +10,7 @@ import { | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchDialog } from '@/hooks/use-chat-request'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { AppSettings } from './app-settings'; | |||
| import { useHandleClickConversationCard } from '../hooks/use-click-card'; | |||
| import { ChatBox } from './chat-box'; | |||
| import { Sessions } from './sessions'; | |||
| @@ -18,6 +18,8 @@ export default function Chat() { | |||
| const { navigateToChatList } = useNavigatePage(); | |||
| const { data } = useFetchDialog(); | |||
| const { t } = useTranslation(); | |||
| const { handleConversationCardClick, controller } = | |||
| useHandleClickConversationCard(); | |||
| return ( | |||
| <section className="h-full flex flex-col"> | |||
| @@ -36,10 +38,11 @@ export default function Chat() { | |||
| </BreadcrumbList> | |||
| </Breadcrumb> | |||
| </PageHeader> | |||
| <div className="flex flex-1"> | |||
| <Sessions></Sessions> | |||
| <ChatBox></ChatBox> | |||
| <AppSettings></AppSettings> | |||
| <div className="flex flex-1 min-h-0"> | |||
| <Sessions | |||
| handleConversationCardClick={handleConversationCardClick} | |||
| ></Sessions> | |||
| <ChatBox controller={controller}></ChatBox> | |||
| </div> | |||
| </section> | |||
| ); | |||
| @@ -1,37 +1,60 @@ | |||
| import { MoreButton } from '@/components/more-button'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| import { useFetchConversationList } from '@/hooks/use-chat-request'; | |||
| import { useGetChatSearchParams } from '@/hooks/use-chat-request'; | |||
| import { cn } from '@/lib/utils'; | |||
| import { Plus } from 'lucide-react'; | |||
| import { useCallback } from 'react'; | |||
| import { useHandleClickConversationCard } from '../hooks/use-click-card'; | |||
| import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list'; | |||
| import { ChatSettingSheet } from './app-settings/chat-settings-sheet'; | |||
| function SessionCard() { | |||
| return ( | |||
| <Card> | |||
| <CardContent className="px-3 py-2 flex justify-between items-center group"> | |||
| xxx | |||
| <MoreButton></MoreButton> | |||
| </CardContent> | |||
| </Card> | |||
| type SessionProps = Pick< | |||
| ReturnType<typeof useHandleClickConversationCard>, | |||
| 'handleConversationCardClick' | |||
| >; | |||
| export function Sessions({ handleConversationCardClick }: SessionProps) { | |||
| const { list: conversationList, addTemporaryConversation } = | |||
| useSelectDerivedConversationList(); | |||
| const handleCardClick = useCallback( | |||
| (conversationId: string, isNew: boolean) => () => { | |||
| handleConversationCardClick(conversationId, isNew); | |||
| }, | |||
| [handleConversationCardClick], | |||
| ); | |||
| } | |||
| export function Sessions() { | |||
| const sessionList = new Array(10).fill(1); | |||
| const {} = useFetchConversationList(); | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| return ( | |||
| <section className="p-6 w-[400px] max-w-[20%]"> | |||
| <section className="p-6 w-[400px] max-w-[20%] flex flex-col"> | |||
| <div className="flex justify-between items-center mb-4"> | |||
| <span className="text-xl font-bold">Conversations</span> | |||
| <Button variant={'ghost'}> | |||
| <Button variant={'ghost'} onClick={addTemporaryConversation}> | |||
| <Plus></Plus> | |||
| </Button> | |||
| </div> | |||
| <div className="space-y-4"> | |||
| {sessionList.map((x) => ( | |||
| <SessionCard key={x}></SessionCard> | |||
| <div className="space-y-4 flex-1 overflow-auto"> | |||
| {conversationList.map((x) => ( | |||
| <Card | |||
| key={x.id} | |||
| onClick={handleCardClick(x.id, x.is_new)} | |||
| className={cn('cursor-pointer bg-transparent', { | |||
| 'bg-background-card': conversationId === x.id, | |||
| })} | |||
| > | |||
| <CardContent className="px-3 py-2 flex justify-between items-center group"> | |||
| {x.name} | |||
| <MoreButton></MoreButton> | |||
| </CardContent> | |||
| </Card> | |||
| ))} | |||
| </div> | |||
| <div className="py-2"> | |||
| <ChatSettingSheet> | |||
| <Button className="w-full">Chat Settings</Button> | |||
| </ChatSettingSheet> | |||
| </div> | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,14 @@ | |||
| import { useGetChatSearchParams } from '@/hooks/use-chat-request'; | |||
| import { trim } from 'lodash'; | |||
| import { useParams } from 'umi'; | |||
| export const useGetSendButtonDisabled = () => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { id: dialogId } = useParams(); | |||
| return dialogId === '' || conversationId === ''; | |||
| }; | |||
| export const useSendButtonDisabled = (value: string) => { | |||
| return trim(value) === ''; | |||
| }; | |||
| @@ -0,0 +1,20 @@ | |||
| import { useClickConversationCard } from '@/hooks/use-chat-request'; | |||
| import { useCallback, useState } from 'react'; | |||
| export function useHandleClickConversationCard() { | |||
| const [controller, setController] = useState(new AbortController()); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const handleConversationCardClick = useCallback( | |||
| (conversationId: string, isNew: boolean) => { | |||
| handleClickConversation(conversationId, isNew ? 'true' : ''); | |||
| setController((pre) => { | |||
| pre.abort(); | |||
| return new AbortController(); | |||
| }); | |||
| }, | |||
| [handleClickConversation], | |||
| ); | |||
| return { controller, handleConversationCardClick }; | |||
| } | |||
| @@ -0,0 +1,29 @@ | |||
| import { useGetChatSearchParams } from '@/hooks/use-chat-request'; | |||
| import { useCallback } from 'react'; | |||
| import { | |||
| useSetChatRouteParams, | |||
| useSetConversation, | |||
| } from './use-send-chat-message'; | |||
| export const useCreateConversationBeforeUploadDocument = () => { | |||
| const { setConversation } = useSetConversation(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { getConversationIsNew } = useSetChatRouteParams(); | |||
| const createConversationBeforeUploadDocument = useCallback( | |||
| async (message: string) => { | |||
| const isNew = getConversationIsNew(); | |||
| if (isNew === 'true') { | |||
| const data = await setConversation(message, true); | |||
| return data; | |||
| } | |||
| }, | |||
| [setConversation, getConversationIsNew], | |||
| ); | |||
| return { | |||
| createConversationBeforeUploadDocument, | |||
| dialogId, | |||
| }; | |||
| }; | |||
| @@ -0,0 +1,85 @@ | |||
| import { ChatSearchParams, MessageType } from '@/constants/chat'; | |||
| import { useTranslate } from '@/hooks/common-hooks'; | |||
| import { | |||
| useFetchConversationList, | |||
| useFetchDialogList, | |||
| } from '@/hooks/use-chat-request'; | |||
| import { IConversation } from '@/interfaces/database/chat'; | |||
| import { getConversationId } from '@/utils/chat'; | |||
| import { useCallback, useEffect, useMemo, useState } from 'react'; | |||
| import { useParams, useSearchParams } from 'umi'; | |||
| export const useFindPrologueFromDialogList = () => { | |||
| const { id: dialogId } = useParams(); | |||
| const { data } = useFetchDialogList(); | |||
| const prologue = useMemo(() => { | |||
| return data.dialogs.find((x) => x.id === dialogId)?.prompt_config.prologue; | |||
| }, [dialogId, data]); | |||
| return prologue; | |||
| }; | |||
| export const useSetNewConversationRouteParams = () => { | |||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo( | |||
| () => new URLSearchParams(currentQueryParameters.toString()), | |||
| [currentQueryParameters], | |||
| ); | |||
| const setNewConversationRouteParams = useCallback( | |||
| (conversationId: string, isNew: string) => { | |||
| newQueryParameters.set(ChatSearchParams.ConversationId, conversationId); | |||
| newQueryParameters.set(ChatSearchParams.isNew, isNew); | |||
| setSearchParams(newQueryParameters); | |||
| }, | |||
| [newQueryParameters, setSearchParams], | |||
| ); | |||
| return { setNewConversationRouteParams }; | |||
| }; | |||
| export const useSelectDerivedConversationList = () => { | |||
| const { t } = useTranslate('chat'); | |||
| const [list, setList] = useState<Array<IConversation>>([]); | |||
| const { data: conversationList, loading } = useFetchConversationList(); | |||
| const { id: dialogId } = useParams(); | |||
| const { setNewConversationRouteParams } = useSetNewConversationRouteParams(); | |||
| const prologue = useFindPrologueFromDialogList(); | |||
| const addTemporaryConversation = useCallback(() => { | |||
| const conversationId = getConversationId(); | |||
| setList((pre) => { | |||
| if (dialogId) { | |||
| setNewConversationRouteParams(conversationId, 'true'); | |||
| const nextList = [ | |||
| { | |||
| id: conversationId, | |||
| name: t('newConversation'), | |||
| dialog_id: dialogId, | |||
| is_new: true, | |||
| message: [ | |||
| { | |||
| content: prologue, | |||
| role: MessageType.Assistant, | |||
| }, | |||
| ], | |||
| } as any, | |||
| ...conversationList, | |||
| ]; | |||
| return nextList; | |||
| } | |||
| return pre; | |||
| }); | |||
| }, [conversationList, dialogId, prologue, t, setNewConversationRouteParams]); | |||
| // When you first enter the page, select the top conversation card | |||
| useEffect(() => { | |||
| setList([...conversationList]); | |||
| }, [conversationList]); | |||
| return { list, addTemporaryConversation, loading }; | |||
| }; | |||
| @@ -0,0 +1,279 @@ | |||
| import { ChatSearchParams, MessageType } from '@/constants/chat'; | |||
| import { | |||
| useHandleMessageInputChange, | |||
| useRegenerateMessage, | |||
| useSelectDerivedMessages, | |||
| useSendMessageWithSse, | |||
| } from '@/hooks/logic-hooks'; | |||
| import { | |||
| useFetchConversation, | |||
| useGetChatSearchParams, | |||
| useUpdateConversation, | |||
| } from '@/hooks/use-chat-request'; | |||
| import { Message } from '@/interfaces/database/chat'; | |||
| import api from '@/utils/api'; | |||
| import { trim } from 'lodash'; | |||
| import { useCallback, useEffect, useMemo } from 'react'; | |||
| import { useParams, useSearchParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { IMessage } from '../chat/interface'; | |||
| import { useFindPrologueFromDialogList } from './use-select-conversation-list'; | |||
| 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 useSelectNextMessages = () => { | |||
| const { | |||
| scrollRef, | |||
| messageContainerRef, | |||
| setDerivedMessages, | |||
| derivedMessages, | |||
| addNewestAnswer, | |||
| addNewestQuestion, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| removeMessagesAfterCurrentMessage, | |||
| } = useSelectDerivedMessages(); | |||
| const { data: conversation, loading } = useFetchConversation(); | |||
| const { conversationId, isNew } = useGetChatSearchParams(); | |||
| const { id: dialogId } = useParams(); | |||
| const prologue = useFindPrologueFromDialogList(); | |||
| const addPrologue = useCallback(() => { | |||
| if (dialogId !== '' && isNew === 'true') { | |||
| const nextMessage = { | |||
| role: MessageType.Assistant, | |||
| content: prologue, | |||
| id: uuid(), | |||
| } as IMessage; | |||
| setDerivedMessages([nextMessage]); | |||
| } | |||
| }, [dialogId, isNew, prologue, setDerivedMessages]); | |||
| useEffect(() => { | |||
| addPrologue(); | |||
| }, [addPrologue]); | |||
| useEffect(() => { | |||
| if ( | |||
| conversationId && | |||
| isNew !== 'true' && | |||
| conversation.message?.length > 0 | |||
| ) { | |||
| setDerivedMessages(conversation.message); | |||
| } | |||
| if (!conversationId) { | |||
| setDerivedMessages([]); | |||
| } | |||
| }, [conversation.message, conversationId, setDerivedMessages, isNew]); | |||
| return { | |||
| scrollRef, | |||
| messageContainerRef, | |||
| derivedMessages, | |||
| loading, | |||
| addNewestAnswer, | |||
| addNewestQuestion, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| removeMessagesAfterCurrentMessage, | |||
| }; | |||
| }; | |||
| export const useSetConversation = () => { | |||
| const { id: dialogId } = useParams(); | |||
| const { updateConversation } = useUpdateConversation(); | |||
| const setConversation = useCallback( | |||
| 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, | |||
| content: message, | |||
| }, | |||
| ], | |||
| }); | |||
| return data; | |||
| }, | |||
| [updateConversation, dialogId], | |||
| ); | |||
| return { setConversation }; | |||
| }; | |||
| export const useSendMessage = (controller: AbortController) => { | |||
| const { setConversation } = useSetConversation(); | |||
| const { conversationId, isNew } = useGetChatSearchParams(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { send, answer, done } = useSendMessageWithSse( | |||
| api.completeConversation, | |||
| ); | |||
| const { | |||
| scrollRef, | |||
| messageContainerRef, | |||
| derivedMessages, | |||
| loading, | |||
| addNewestAnswer, | |||
| addNewestQuestion, | |||
| removeLatestMessage, | |||
| removeMessageById, | |||
| removeMessagesAfterCurrentMessage, | |||
| } = useSelectNextMessages(); | |||
| const { setConversationIsNew, getConversationIsNew } = | |||
| useSetChatRouteParams(); | |||
| const stopOutputMessage = useCallback(() => { | |||
| controller.abort(); | |||
| }, [controller]); | |||
| const sendMessage = useCallback( | |||
| async ({ | |||
| message, | |||
| currentConversationId, | |||
| messages, | |||
| }: { | |||
| message: Message; | |||
| currentConversationId?: string; | |||
| messages?: Message[]; | |||
| }) => { | |||
| const res = await send( | |||
| { | |||
| conversation_id: currentConversationId ?? conversationId, | |||
| messages: [...(messages ?? derivedMessages ?? []), message], | |||
| }, | |||
| controller, | |||
| ); | |||
| if (res && (res?.response.status !== 200 || res?.data?.code !== 0)) { | |||
| // cancel loading | |||
| setValue(message.content); | |||
| console.info('removeLatestMessage111'); | |||
| removeLatestMessage(); | |||
| } | |||
| }, | |||
| [ | |||
| derivedMessages, | |||
| conversationId, | |||
| removeLatestMessage, | |||
| setValue, | |||
| send, | |||
| controller, | |||
| ], | |||
| ); | |||
| const handleSendMessage = useCallback( | |||
| async (message: Message) => { | |||
| const isNew = getConversationIsNew(); | |||
| if (isNew !== 'true') { | |||
| sendMessage({ message }); | |||
| } else { | |||
| const data = await setConversation( | |||
| message.content, | |||
| true, | |||
| conversationId, | |||
| ); | |||
| if (data.code === 0) { | |||
| setConversationIsNew(''); | |||
| const id = data.data.id; | |||
| // currentConversationIdRef.current = id; | |||
| sendMessage({ | |||
| message, | |||
| currentConversationId: id, | |||
| messages: data.data.message, | |||
| }); | |||
| } | |||
| } | |||
| }, | |||
| [ | |||
| setConversation, | |||
| sendMessage, | |||
| setConversationIsNew, | |||
| getConversationIsNew, | |||
| conversationId, | |||
| ], | |||
| ); | |||
| const { regenerateMessage } = useRegenerateMessage({ | |||
| removeMessagesAfterCurrentMessage, | |||
| sendMessage, | |||
| messages: derivedMessages, | |||
| }); | |||
| useEffect(() => { | |||
| // #1289 | |||
| if (answer.answer && conversationId && isNew !== 'true') { | |||
| addNewestAnswer(answer); | |||
| } | |||
| }, [answer, addNewestAnswer, conversationId, isNew]); | |||
| const handlePressEnter = useCallback( | |||
| (documentIds: string[]) => { | |||
| if (trim(value) === '') return; | |||
| const id = uuid(); | |||
| addNewestQuestion({ | |||
| content: value, | |||
| doc_ids: documentIds, | |||
| id, | |||
| role: MessageType.User, | |||
| }); | |||
| if (done) { | |||
| setValue(''); | |||
| handleSendMessage({ | |||
| id, | |||
| content: value.trim(), | |||
| role: MessageType.User, | |||
| doc_ids: documentIds, | |||
| }); | |||
| } | |||
| }, | |||
| [addNewestQuestion, handleSendMessage, done, setValue, value], | |||
| ); | |||
| return { | |||
| handlePressEnter, | |||
| handleInputChange, | |||
| value, | |||
| setValue, | |||
| regenerateMessage, | |||
| sendLoading: !done, | |||
| loading, | |||
| scrollRef, | |||
| messageContainerRef, | |||
| derivedMessages, | |||
| removeMessageById, | |||
| stopOutputMessage, | |||
| }; | |||
| }; | |||