### What problem does this PR solve? Feat: Upload files in the chat box #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.2
| import { FormInstance } from 'antd/lib'; | import { FormInstance } from 'antd/lib'; | ||||
| import axios from 'axios'; | import axios from 'axios'; | ||||
| import { EventSourceParserStream } from 'eventsource-parser/stream'; | import { EventSourceParserStream } from 'eventsource-parser/stream'; | ||||
| import { omit } from 'lodash'; | |||||
| import { has, isEmpty, omit } from 'lodash'; | |||||
| import { | import { | ||||
| ChangeEventHandler, | ChangeEventHandler, | ||||
| useCallback, | useCallback, | ||||
| return appConf; | return appConf; | ||||
| }; | }; | ||||
| function useSetDoneRecord() { | |||||
| const [doneRecord, setDoneRecord] = useState<Record<string, boolean>>({}); | |||||
| const clearDoneRecord = useCallback(() => { | |||||
| setDoneRecord({}); | |||||
| }, []); | |||||
| const setDoneRecordById = useCallback((id: string, val: boolean) => { | |||||
| setDoneRecord((prev) => ({ ...prev, [id]: val })); | |||||
| }, []); | |||||
| const allDone = useMemo(() => { | |||||
| return Object.values(doneRecord).every((val) => val); | |||||
| }, [doneRecord]); | |||||
| useEffect(() => { | |||||
| if (!isEmpty(doneRecord) && allDone) { | |||||
| clearDoneRecord(); | |||||
| } | |||||
| }, [allDone, clearDoneRecord, doneRecord]); | |||||
| return { | |||||
| doneRecord, | |||||
| setDoneRecord, | |||||
| setDoneRecordById, | |||||
| clearDoneRecord, | |||||
| allDone, | |||||
| }; | |||||
| } | |||||
| export const useSendMessageWithSse = ( | export const useSendMessageWithSse = ( | ||||
| url: string = api.completeConversation, | url: string = api.completeConversation, | ||||
| ) => { | ) => { | ||||
| const [answer, setAnswer] = useState<IAnswer>({} as IAnswer); | const [answer, setAnswer] = useState<IAnswer>({} as IAnswer); | ||||
| const [done, setDone] = useState(true); | const [done, setDone] = useState(true); | ||||
| const { doneRecord, clearDoneRecord, setDoneRecordById, allDone } = | |||||
| useSetDoneRecord(); | |||||
| const timer = useRef<any>(); | const timer = useRef<any>(); | ||||
| const sseRef = useRef<AbortController>(); | const sseRef = useRef<AbortController>(); | ||||
| }, 1000); | }, 1000); | ||||
| }, []); | }, []); | ||||
| const setDoneValue = useCallback( | |||||
| (body: any, value: boolean) => { | |||||
| if (has(body, 'chatBoxId')) { | |||||
| setDoneRecordById(body.chatBoxId, value); | |||||
| } else { | |||||
| setDone(value); | |||||
| } | |||||
| }, | |||||
| [setDoneRecordById], | |||||
| ); | |||||
| const send = useCallback( | const send = useCallback( | ||||
| async ( | async ( | ||||
| body: any, | body: any, | ||||
| ): Promise<{ response: Response; data: ResponseType } | undefined> => { | ): Promise<{ response: Response; data: ResponseType } | undefined> => { | ||||
| initializeSseRef(); | initializeSseRef(); | ||||
| try { | try { | ||||
| setDone(false); | |||||
| setDoneValue(body, false); | |||||
| const response = await fetch(url, { | const response = await fetch(url, { | ||||
| method: 'POST', | method: 'POST', | ||||
| headers: { | headers: { | ||||
| } | } | ||||
| } | } | ||||
| } | } | ||||
| setDone(true); | |||||
| setDoneValue(body, true); | |||||
| resetAnswer(); | resetAnswer(); | ||||
| return { data: await res, response }; | return { data: await res, response }; | ||||
| } catch (e) { | } catch (e) { | ||||
| setDone(true); | |||||
| setDoneValue(body, true); | |||||
| resetAnswer(); | resetAnswer(); | ||||
| // Swallow fetch errors silently | // Swallow fetch errors silently | ||||
| } | } | ||||
| }, | }, | ||||
| [initializeSseRef, url, resetAnswer], | |||||
| [initializeSseRef, setDoneValue, url, resetAnswer], | |||||
| ); | ); | ||||
| const stopOutputMessage = useCallback(() => { | const stopOutputMessage = useCallback(() => { | ||||
| sseRef.current?.abort(); | sseRef.current?.abort(); | ||||
| }, []); | }, []); | ||||
| return { send, answer, done, setDone, resetAnswer, stopOutputMessage }; | |||||
| return { | |||||
| send, | |||||
| answer, | |||||
| done, | |||||
| doneRecord, | |||||
| allDone, | |||||
| setDone, | |||||
| resetAnswer, | |||||
| stopOutputMessage, | |||||
| clearDoneRecord, | |||||
| }; | |||||
| }; | }; | ||||
| export const useSpeechWithSse = (url: string = api.tts) => { | export const useSpeechWithSse = (url: string = api.tts) => { |
| DeleteMessage = 'deleteMessage', | DeleteMessage = 'deleteMessage', | ||||
| FetchMindMap = 'fetchMindMap', | FetchMindMap = 'fetchMindMap', | ||||
| FetchRelatedQuestions = 'fetchRelatedQuestions', | FetchRelatedQuestions = 'fetchRelatedQuestions', | ||||
| UploadAndParse = 'upload_and_parse', | |||||
| } | } | ||||
| export const useGetChatSearchParams = () => { | export const useGetChatSearchParams = () => { | ||||
| queryKey: [ChatApiAction.FetchDialogList], | queryKey: [ChatApiAction.FetchDialogList], | ||||
| }); | }); | ||||
| queryClient.invalidateQueries({ | |||||
| queryKey: [ChatApiAction.FetchDialog], | |||||
| }); | |||||
| message.success( | message.success( | ||||
| t(`message.${params.dialog_id ? 'modified' : 'created'}`), | t(`message.${params.dialog_id ? 'modified' : 'created'}`), | ||||
| ); | ); | ||||
| return { data, loading, deleteMessage: mutateAsync }; | return { data, loading, deleteMessage: mutateAsync }; | ||||
| }; | }; | ||||
| export function useUploadAndParseFile() { | |||||
| const { conversationId } = useGetChatSearchParams(); | |||||
| const { t } = useTranslation(); | |||||
| const { | |||||
| data, | |||||
| isPending: loading, | |||||
| mutateAsync, | |||||
| } = useMutation({ | |||||
| mutationKey: [ChatApiAction.UploadAndParse], | |||||
| mutationFn: async (file: File) => { | |||||
| const formData = new FormData(); | |||||
| formData.append('file', file); | |||||
| formData.append('conversation_id', conversationId); | |||||
| const { data } = await chatService.uploadAndParse(formData); | |||||
| if (data.code === 0) { | |||||
| message.success(t(`message.uploaded`)); | |||||
| } | |||||
| return data; | |||||
| }, | |||||
| }); | |||||
| return { data, loading, uploadAndParseFile: mutateAsync }; | |||||
| } | |||||
| //#endregion | //#endregion | ||||
| //#region search page | //#region search page |
| exception_default_value: '', | exception_default_value: '', | ||||
| tools: [], | tools: [], | ||||
| mcp: [], | mcp: [], | ||||
| cite: true, | |||||
| outputs: { | outputs: { | ||||
| // structured_output: { | // structured_output: { | ||||
| // topic: { | // topic: { |
| FormLabel, | FormLabel, | ||||
| } from '@/components/ui/form'; | } from '@/components/ui/form'; | ||||
| import { Input, NumberInput } from '@/components/ui/input'; | import { Input, NumberInput } from '@/components/ui/input'; | ||||
| import { Switch } from '@/components/ui/switch'; | |||||
| import { LlmModelType } from '@/constants/knowledge'; | import { LlmModelType } from '@/constants/knowledge'; | ||||
| import { useFindLlmByUuid } from '@/hooks/use-llm-request'; | import { useFindLlmByUuid } from '@/hooks/use-llm-request'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| exception_goto: z.array(z.string()).optional(), | exception_goto: z.array(z.string()).optional(), | ||||
| exception_default_value: z.string().optional(), | exception_default_value: z.string().optional(), | ||||
| ...LargeModelFilterFormSchema, | ...LargeModelFilterFormSchema, | ||||
| cite: z.boolean().optional(), | |||||
| }); | }); | ||||
| const outputList = buildOutputList(initialAgentValues.outputs); | const outputList = buildOutputList(initialAgentValues.outputs); | ||||
| <Collapse title={<div>Advanced Settings</div>}> | <Collapse title={<div>Advanced Settings</div>}> | ||||
| <FormContainer> | <FormContainer> | ||||
| <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> | <MessageHistoryWindowSizeFormField></MessageHistoryWindowSizeFormField> | ||||
| <FormField | |||||
| control={form.control} | |||||
| name={`cite`} | |||||
| render={({ field }) => ( | |||||
| <FormItem className="flex-1"> | |||||
| <FormLabel tooltip={t('flow.citeTip')}> | |||||
| {t('flow.cite')} | |||||
| </FormLabel> | |||||
| <FormControl> | |||||
| <Switch | |||||
| checked={field.value} | |||||
| onCheckedChange={field.onChange} | |||||
| ></Switch> | |||||
| </FormControl> | |||||
| </FormItem> | |||||
| )} | |||||
| /> | |||||
| <FormField | <FormField | ||||
| control={form.control} | control={form.control} | ||||
| name={`max_retries`} | name={`max_retries`} |
| useFetchConversation, | useFetchConversation, | ||||
| useFetchDialog, | useFetchDialog, | ||||
| useGetChatSearchParams, | useGetChatSearchParams, | ||||
| useSetDialog, | |||||
| } from '@/hooks/use-chat-request'; | } from '@/hooks/use-chat-request'; | ||||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | ||||
| import { buildMessageUuidWithRole } from '@/utils/chat'; | import { buildMessageUuidWithRole } from '@/utils/chat'; | ||||
| import { zodResolver } from '@hookform/resolvers/zod'; | import { zodResolver } from '@hookform/resolvers/zod'; | ||||
| import { isEmpty, omit } from 'lodash'; | |||||
| import { ListCheck, Plus, Trash2 } from 'lucide-react'; | import { ListCheck, Plus, Trash2 } from 'lucide-react'; | ||||
| import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; | import { forwardRef, useCallback, useImperativeHandle, useRef } from 'react'; | ||||
| import { useForm } from 'react-hook-form'; | |||||
| import { useForm, useWatch } from 'react-hook-form'; | |||||
| import { useParams } from 'umi'; | |||||
| import { z } from 'zod'; | import { z } from 'zod'; | ||||
| import { | import { | ||||
| useGetSendButtonDisabled, | useGetSendButtonDisabled, | ||||
| id: string; | id: string; | ||||
| idx: number; | idx: number; | ||||
| derivedMessages: IMessage[]; | derivedMessages: IMessage[]; | ||||
| sendLoading: boolean; | |||||
| } & Pick< | } & Pick< | ||||
| MultipleChatBoxProps, | MultipleChatBoxProps, | ||||
| 'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds' | 'controller' | 'removeChatBox' | 'addChatBox' | 'chatBoxIds' | ||||
| addChatBox, | addChatBox, | ||||
| chatBoxIds, | chatBoxIds, | ||||
| derivedMessages, | derivedMessages, | ||||
| sendLoading, | |||||
| }: ChatCardProps, | }: ChatCardProps, | ||||
| ref, | ref, | ||||
| ) { | ) { | ||||
| const { sendLoading, regenerateMessage, removeMessageById } = | |||||
| useSendMessage(controller); | |||||
| const { id: dialogId } = useParams(); | |||||
| const { setDialog } = useSetDialog(); | |||||
| const { regenerateMessage, removeMessageById } = useSendMessage(controller); | |||||
| const messageContainerRef = useRef<HTMLDivElement>(null); | const messageContainerRef = useRef<HTMLDivElement>(null); | ||||
| }, | }, | ||||
| }); | }); | ||||
| const llmId = useWatch({ control: form.control, name: 'llm_id' }); | |||||
| const { data: userInfo } = useFetchUserInfo(); | const { data: userInfo } = useFetchUserInfo(); | ||||
| const { data: currentDialog } = useFetchDialog(); | const { data: currentDialog } = useFetchDialog(); | ||||
| const { data: conversation } = useFetchConversation(); | const { data: conversation } = useFetchConversation(); | ||||
| removeChatBox(id); | removeChatBox(id); | ||||
| }, [id, removeChatBox]); | }, [id, removeChatBox]); | ||||
| const handleApplyConfig = useCallback(() => { | |||||
| const values = form.getValues(); | |||||
| setDialog({ | |||||
| ...currentDialog, | |||||
| llm_id: values.llm_id, | |||||
| llm_setting: omit(values, 'llm_id'), | |||||
| dialog_id: dialogId, | |||||
| }); | |||||
| }, [currentDialog, dialogId, form, setDialog]); | |||||
| useImperativeHandle(ref, () => ({ | useImperativeHandle(ref, () => ({ | ||||
| getFormData: () => form.getValues(), | getFormData: () => form.getValues(), | ||||
| })); | })); | ||||
| <div className="space-x-2"> | <div className="space-x-2"> | ||||
| <Tooltip> | <Tooltip> | ||||
| <TooltipTrigger> | <TooltipTrigger> | ||||
| <Button variant={'ghost'}> | |||||
| <Button | |||||
| variant={'ghost'} | |||||
| disabled={isEmpty(llmId)} | |||||
| onClick={handleApplyConfig} | |||||
| > | |||||
| <ListCheck /> | <ListCheck /> | ||||
| </Button> | </Button> | ||||
| </TooltipTrigger> | </TooltipTrigger> | ||||
| handlePressEnter, | handlePressEnter, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| setFormRef, | setFormRef, | ||||
| handleUploadFile, | |||||
| } = useSendMultipleChatMessage(controller, chatBoxIds); | } = useSendMultipleChatMessage(controller, chatBoxIds); | ||||
| const { createConversationBeforeUploadDocument } = | const { createConversationBeforeUploadDocument } = | ||||
| addChatBox={addChatBox} | addChatBox={addChatBox} | ||||
| derivedMessages={messageRecord[id]} | derivedMessages={messageRecord[id]} | ||||
| ref={setFormRef(id)} | ref={setFormRef(id)} | ||||
| sendLoading={sendLoading} | |||||
| ></ChatCard> | ></ChatCard> | ||||
| ))} | ))} | ||||
| </div> | </div> | ||||
| createConversationBeforeUploadDocument | createConversationBeforeUploadDocument | ||||
| } | } | ||||
| stopOutputMessage={stopOutputMessage} | stopOutputMessage={stopOutputMessage} | ||||
| onUpload={handleUploadFile} | |||||
| /> | /> | ||||
| </div> | </div> | ||||
| </section> | </section> |
| regenerateMessage, | regenerateMessage, | ||||
| removeMessageById, | removeMessageById, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| handleUploadFile, | |||||
| } = useSendMessage(controller); | } = useSendMessage(controller); | ||||
| const { data: userInfo } = useFetchUserInfo(); | const { data: userInfo } = useFetchUserInfo(); | ||||
| const { data: currentDialog } = useFetchDialog(); | const { data: currentDialog } = useFetchDialog(); | ||||
| createConversationBeforeUploadDocument | createConversationBeforeUploadDocument | ||||
| } | } | ||||
| stopOutputMessage={stopOutputMessage} | stopOutputMessage={stopOutputMessage} | ||||
| onUpload={handleUploadFile} | |||||
| /> | /> | ||||
| </section> | </section> | ||||
| ); | ); |
| import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'; | ||||
| import { useSetModalState } from '@/hooks/common-hooks'; | import { useSetModalState } from '@/hooks/common-hooks'; | ||||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | ||||
| import { useFetchConversation, useFetchDialog } from '@/hooks/use-chat-request'; | |||||
| import { | |||||
| useFetchConversation, | |||||
| useFetchDialog, | |||||
| useGetChatSearchParams, | |||||
| } from '@/hooks/use-chat-request'; | |||||
| import { cn } from '@/lib/utils'; | import { cn } from '@/lib/utils'; | ||||
| import { isEmpty } from 'lodash'; | |||||
| import { ArrowUpRight, LogOut } from 'lucide-react'; | import { ArrowUpRight, LogOut } from 'lucide-react'; | ||||
| import { useTranslation } from 'react-i18next'; | import { useTranslation } from 'react-i18next'; | ||||
| import { useHandleClickConversationCard } from '../hooks/use-click-card'; | import { useHandleClickConversationCard } from '../hooks/use-click-card'; | ||||
| hasThreeChatBox, | hasThreeChatBox, | ||||
| } = useAddChatBox(); | } = useAddChatBox(); | ||||
| const { conversationId, isNew } = useGetChatSearchParams(); | |||||
| const { isDebugMode, switchDebugMode } = useSwitchDebugMode(); | const { isDebugMode, switchDebugMode } = useSwitchDebugMode(); | ||||
| if (isDebugMode) { | if (isDebugMode) { | ||||
| <Button | <Button | ||||
| variant={'ghost'} | variant={'ghost'} | ||||
| onClick={switchDebugMode} | onClick={switchDebugMode} | ||||
| disabled={hasThreeChatBox} | |||||
| disabled={ | |||||
| hasThreeChatBox || | |||||
| isEmpty(conversationId) || | |||||
| isNew === 'true' | |||||
| } | |||||
| > | > | ||||
| <ArrowUpRight /> Multiple Models | <ArrowUpRight /> Multiple Models | ||||
| </Button> | </Button> | ||||
| </CardTitle> | </CardTitle> | ||||
| </CardHeader> | </CardHeader> | ||||
| <CardContent className="flex-1 p-0"> | |||||
| <CardContent className="flex-1 p-0 min-h-0"> | |||||
| <SingleChatBox controller={controller}></SingleChatBox> | <SingleChatBox controller={controller}></SingleChatBox> | ||||
| </CardContent> | </CardContent> | ||||
| </Card> | </Card> |
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { IMessage } from '../chat/interface'; | import { IMessage } from '../chat/interface'; | ||||
| import { useFindPrologueFromDialogList } from './use-select-conversation-list'; | import { useFindPrologueFromDialogList } from './use-select-conversation-list'; | ||||
| import { useUploadFile } from './use-upload-file'; | |||||
| export const useSetChatRouteParams = () => { | export const useSetChatRouteParams = () => { | ||||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | const [currentQueryParameters, setSearchParams] = useSearchParams(); | ||||
| const { conversationId, isNew } = useGetChatSearchParams(); | const { conversationId, isNew } = useGetChatSearchParams(); | ||||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | ||||
| const { handleUploadFile, fileIds, clearFileIds } = useUploadFile(); | |||||
| const { send, answer, done } = useSendMessageWithSse( | const { send, answer, done } = useSendMessageWithSse( | ||||
| api.completeConversation, | api.completeConversation, | ||||
| ); | ); | ||||
| } | } | ||||
| }, [answer, addNewestAnswer, conversationId, isNew]); | }, [answer, addNewestAnswer, conversationId, isNew]); | ||||
| const handlePressEnter = useCallback( | |||||
| (documentIds: string[]) => { | |||||
| if (trim(value) === '') return; | |||||
| const id = uuid(); | |||||
| const handlePressEnter = useCallback(() => { | |||||
| if (trim(value) === '') return; | |||||
| const id = uuid(); | |||||
| addNewestQuestion({ | |||||
| content: value, | |||||
| doc_ids: documentIds, | |||||
| addNewestQuestion({ | |||||
| content: value, | |||||
| doc_ids: fileIds, | |||||
| id, | |||||
| role: MessageType.User, | |||||
| }); | |||||
| if (done) { | |||||
| setValue(''); | |||||
| handleSendMessage({ | |||||
| id, | id, | ||||
| content: value.trim(), | |||||
| role: MessageType.User, | role: MessageType.User, | ||||
| doc_ids: fileIds, | |||||
| }); | }); | ||||
| if (done) { | |||||
| setValue(''); | |||||
| handleSendMessage({ | |||||
| id, | |||||
| content: value.trim(), | |||||
| role: MessageType.User, | |||||
| doc_ids: documentIds, | |||||
| }); | |||||
| } | |||||
| }, | |||||
| [addNewestQuestion, handleSendMessage, done, setValue, value], | |||||
| ); | |||||
| } | |||||
| clearFileIds(); | |||||
| }, [ | |||||
| value, | |||||
| addNewestQuestion, | |||||
| fileIds, | |||||
| done, | |||||
| clearFileIds, | |||||
| setValue, | |||||
| handleSendMessage, | |||||
| ]); | |||||
| return { | return { | ||||
| handlePressEnter, | handlePressEnter, | ||||
| derivedMessages, | derivedMessages, | ||||
| removeMessageById, | removeMessageById, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| handleUploadFile, | |||||
| }; | }; | ||||
| }; | }; |
| import { v4 as uuid } from 'uuid'; | import { v4 as uuid } from 'uuid'; | ||||
| import { IMessage } from '../chat/interface'; | import { IMessage } from '../chat/interface'; | ||||
| import { useBuildFormRefs } from './use-build-form-refs'; | import { useBuildFormRefs } from './use-build-form-refs'; | ||||
| import { useUploadFile } from './use-upload-file'; | |||||
| export function useSendMultipleChatMessage( | export function useSendMultipleChatMessage( | ||||
| controller: AbortController, | controller: AbortController, | ||||
| const { conversationId } = useGetChatSearchParams(); | const { conversationId } = useGetChatSearchParams(); | ||||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | ||||
| const { send, answer, done } = useSendMessageWithSse( | |||||
| const { send, answer, allDone } = useSendMessageWithSse( | |||||
| api.completeConversation, | api.completeConversation, | ||||
| ); | ); | ||||
| const { handleUploadFile, fileIds, clearFileIds } = useUploadFile(); | |||||
| const { setFormRef, getLLMConfigById, isLLMConfigEmpty } = | const { setFormRef, getLLMConfigById, isLLMConfigEmpty } = | ||||
| useBuildFormRefs(chatBoxIds); | useBuildFormRefs(chatBoxIds); | ||||
| id, | id, | ||||
| role: MessageType.User, | role: MessageType.User, | ||||
| chatBoxId, | chatBoxId, | ||||
| doc_ids: fileIds, | |||||
| }); | }); | ||||
| } | } | ||||
| }); | }); | ||||
| if (done) { | |||||
| // TODO: | |||||
| if (allDone) { | |||||
| setValue(''); | setValue(''); | ||||
| chatBoxIds.forEach((chatBoxId) => { | chatBoxIds.forEach((chatBoxId) => { | ||||
| if (!isLLMConfigEmpty(chatBoxId)) { | if (!isLLMConfigEmpty(chatBoxId)) { | ||||
| id, | id, | ||||
| content: value.trim(), | content: value.trim(), | ||||
| role: MessageType.User, | role: MessageType.User, | ||||
| doc_ids: fileIds, | |||||
| }, | }, | ||||
| chatBoxId, | chatBoxId, | ||||
| }); | }); | ||||
| } | } | ||||
| }); | }); | ||||
| } | } | ||||
| clearFileIds(); | |||||
| }, [ | }, [ | ||||
| value, | value, | ||||
| chatBoxIds, | chatBoxIds, | ||||
| done, | |||||
| allDone, | |||||
| clearFileIds, | |||||
| isLLMConfigEmpty, | isLLMConfigEmpty, | ||||
| addNewestQuestion, | addNewestQuestion, | ||||
| fileIds, | |||||
| setValue, | setValue, | ||||
| sendMessage, | sendMessage, | ||||
| ]); | ]); | ||||
| handleInputChange, | handleInputChange, | ||||
| handlePressEnter, | handlePressEnter, | ||||
| stopOutputMessage, | stopOutputMessage, | ||||
| sendLoading: false, | |||||
| sendLoading: !allDone, | |||||
| setFormRef, | setFormRef, | ||||
| handleUploadFile, | |||||
| }; | }; | ||||
| } | } |
| import { FileUploadProps } from '@/components/file-upload'; | |||||
| import { useUploadAndParseFile } from '@/hooks/use-chat-request'; | |||||
| import { useCallback, useState } from 'react'; | |||||
| export function useUploadFile() { | |||||
| const { uploadAndParseFile } = useUploadAndParseFile(); | |||||
| const [fileIds, setFileIds] = useState<string[]>([]); | |||||
| const handleUploadFile: NonNullable<FileUploadProps['onUpload']> = | |||||
| useCallback( | |||||
| async (files) => { | |||||
| if (Array.isArray(files) && files.length) { | |||||
| const ret = await uploadAndParseFile(files[0]); | |||||
| if (ret.code === 0 && Array.isArray(ret.data)) { | |||||
| setFileIds((list) => [...list, ...ret.data]); | |||||
| } | |||||
| } | |||||
| }, | |||||
| [uploadAndParseFile], | |||||
| ); | |||||
| const clearFileIds = useCallback(() => { | |||||
| setFileIds([]); | |||||
| }, []); | |||||
| return { handleUploadFile, clearFileIds, fileIds }; | |||||
| } |
| mindmap, | mindmap, | ||||
| getRelatedQuestions, | getRelatedQuestions, | ||||
| listNextDialog, | listNextDialog, | ||||
| upload_and_parse, | |||||
| } = api; | } = api; | ||||
| const methods = { | const methods = { | ||||
| url: getRelatedQuestions, | url: getRelatedQuestions, | ||||
| method: 'post', | method: 'post', | ||||
| }, | }, | ||||
| uploadAndParse: { | |||||
| method: 'post', | |||||
| url: upload_and_parse, | |||||
| }, | |||||
| } as const; | } as const; | ||||
| const chatService = registerNextServer<keyof typeof methods>(methods); | const chatService = registerNextServer<keyof typeof methods>(methods); |