### 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
| isNew !== 'true' && | isNew !== 'true' && | ||||
| isConversationIdExist(sharedId || conversationId) | 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 ?? {}; | const conversation = data?.data ?? {}; | ||||
| </section> | </section> | ||||
| <div className="flex justify-between items-end"> | <div className="flex justify-between items-end"> | ||||
| <div className="w-full"> | <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} | {data.name} | ||||
| </h3> | </h3> | ||||
| <p className="text-xs text-text-sub-title">{data.description}</p> | <p className="text-xs text-text-sub-title">{data.description}</p> |
| 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> | |||||
| ); | |||||
| } |
| import { ChatPromptEngine } from './chat-prompt-engine'; | import { ChatPromptEngine } from './chat-prompt-engine'; | ||||
| import { useChatSettingSchema } from './use-chat-setting-schema'; | import { useChatSettingSchema } from './use-chat-setting-schema'; | ||||
| export function AppSettings() { | |||||
| export function ChatSettings() { | |||||
| const formSchema = useChatSettingSchema(); | const formSchema = useChatSettingSchema(); | ||||
| const form = useForm<z.infer<typeof formSchema>>({ | const form = useForm<z.infer<typeof formSchema>>({ | ||||
| resolver: zodResolver(formSchema), | resolver: zodResolver(formSchema), | ||||
| defaultValues: { | defaultValues: { | ||||
| } | } | ||||
| return ( | 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> | </section> | ||||
| ); | ); | ||||
| } | } |
| 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 ( | 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> | </section> | ||||
| ); | ); | ||||
| } | } |
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | ||||
| import { useFetchDialog } from '@/hooks/use-chat-request'; | import { useFetchDialog } from '@/hooks/use-chat-request'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { AppSettings } from './app-settings'; | |||||
| import { useHandleClickConversationCard } from '../hooks/use-click-card'; | |||||
| import { ChatBox } from './chat-box'; | import { ChatBox } from './chat-box'; | ||||
| import { Sessions } from './sessions'; | import { Sessions } from './sessions'; | ||||
| const { navigateToChatList } = useNavigatePage(); | const { navigateToChatList } = useNavigatePage(); | ||||
| const { data } = useFetchDialog(); | const { data } = useFetchDialog(); | ||||
| const { t } = useTranslation(); | const { t } = useTranslation(); | ||||
| const { handleConversationCardClick, controller } = | |||||
| useHandleClickConversationCard(); | |||||
| return ( | return ( | ||||
| <section className="h-full flex flex-col"> | <section className="h-full flex flex-col"> | ||||
| </BreadcrumbList> | </BreadcrumbList> | ||||
| </Breadcrumb> | </Breadcrumb> | ||||
| </PageHeader> | </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> | </div> | ||||
| </section> | </section> | ||||
| ); | ); |
| import { MoreButton } from '@/components/more-button'; | import { MoreButton } from '@/components/more-button'; | ||||
| import { Button } from '@/components/ui/button'; | import { Button } from '@/components/ui/button'; | ||||
| import { Card, CardContent } from '@/components/ui/card'; | 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 { 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 ( | 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"> | <div className="flex justify-between items-center mb-4"> | ||||
| <span className="text-xl font-bold">Conversations</span> | <span className="text-xl font-bold">Conversations</span> | ||||
| <Button variant={'ghost'}> | |||||
| <Button variant={'ghost'} onClick={addTemporaryConversation}> | |||||
| <Plus></Plus> | <Plus></Plus> | ||||
| </Button> | </Button> | ||||
| </div> | </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> | ||||
| <div className="py-2"> | |||||
| <ChatSettingSheet> | |||||
| <Button className="w-full">Chat Settings</Button> | |||||
| </ChatSettingSheet> | |||||
| </div> | |||||
| </section> | </section> | ||||
| ); | ); | ||||
| } | } |
| 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) === ''; | |||||
| }; |
| 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 }; | |||||
| } |
| 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, | |||||
| }; | |||||
| }; |
| 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 }; | |||||
| }; |
| 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, | |||||
| }; | |||||
| }; |