### What problem does this PR solve? feat: Fetch conversation list by @tanstack/react-query feat: Displays error message that task_executor does not exist ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.11.0
| @@ -1,3 +1,4 @@ | |||
| .thumbnailImg { | |||
| display: inline-block; | |||
| max-width: 20px; | |||
| } | |||
| @@ -15,3 +15,10 @@ export enum SharedFrom { | |||
| Agent = 'agent', | |||
| Chat = 'chat', | |||
| } | |||
| export enum ChatSearchParams { | |||
| DialogId = 'dialogId', | |||
| ConversationId = 'conversationId', | |||
| } | |||
| export const EmptyConversationId = 'empty'; | |||
| @@ -1,131 +1,255 @@ | |||
| import { ChatSearchParams } from '@/constants/chat'; | |||
| import { | |||
| IConversation, | |||
| IDialog, | |||
| IStats, | |||
| IToken, | |||
| Message, | |||
| } from '@/interfaces/database/chat'; | |||
| import i18n from '@/locales/config'; | |||
| import { IClientConversation, IMessage } from '@/pages/chat/interface'; | |||
| import chatService from '@/services/chat-service'; | |||
| import { isConversationIdExist } from '@/utils/chat'; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { message } from 'antd'; | |||
| import dayjs, { Dayjs } from 'dayjs'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useDispatch, useSelector } from 'umi'; | |||
| import { useCallback, useMemo, useState } from 'react'; | |||
| import { useSearchParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| //#region logic | |||
| export const useClickDialogCard = () => { | |||
| const [, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo(() => { | |||
| return new URLSearchParams(); | |||
| }, []); | |||
| const handleClickDialog = useCallback( | |||
| (dialogId: string) => { | |||
| newQueryParameters.set(ChatSearchParams.DialogId, dialogId); | |||
| // newQueryParameters.set( | |||
| // ChatSearchParams.ConversationId, | |||
| // EmptyConversationId, | |||
| // ); | |||
| setSearchParams(newQueryParameters); | |||
| }, | |||
| [newQueryParameters, setSearchParams], | |||
| ); | |||
| export const useFetchDialogList = () => { | |||
| const dispatch = useDispatch(); | |||
| return { handleClickDialog }; | |||
| }; | |||
| const fetchDialogList = useCallback(() => { | |||
| return dispatch<any>({ type: 'chatModel/listDialog' }); | |||
| }, [dispatch]); | |||
| export const useGetChatSearchParams = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| return fetchDialogList; | |||
| return { | |||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||
| conversationId: | |||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||
| }; | |||
| }; | |||
| export const useSelectDialogList = () => { | |||
| const dialogList: IDialog[] = useSelector( | |||
| (state: any) => state.chatModel.dialogList, | |||
| ); | |||
| //#endregion | |||
| return dialogList; | |||
| }; | |||
| //#region dialog | |||
| export const useFetchConversationList = () => { | |||
| const dispatch = useDispatch(); | |||
| export const useFetchNextDialogList = () => { | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const fetchConversationList = useCallback( | |||
| async (dialogId: string) => { | |||
| if (dialogId) { | |||
| dispatch({ | |||
| type: 'chatModel/listConversation', | |||
| payload: { dialog_id: dialogId }, | |||
| }); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery<IDialog[]>({ | |||
| queryKey: ['fetchDialogList'], | |||
| initialData: [], | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| const { data } = await chatService.listDialog(); | |||
| if (data.retcode === 0 && data.data.length > 0) { | |||
| handleClickDialog(data.data[0].id); | |||
| } | |||
| return data?.data ?? []; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return fetchConversationList; | |||
| return { data, loading, refetch }; | |||
| }; | |||
| export const useSelectConversationList = () => { | |||
| const conversationList: IConversation[] = useSelector( | |||
| (state: any) => state.chatModel.conversationList, | |||
| ); | |||
| export const useSetNextDialog = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['setDialog'], | |||
| mutationFn: async (params: IDialog) => { | |||
| const { data } = await chatService.setDialog(params); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] }); | |||
| message.success( | |||
| i18n.t(`message.${params.id ? 'modified' : 'created'}`), | |||
| ); | |||
| } | |||
| return data?.retcode; | |||
| }, | |||
| }); | |||
| return conversationList; | |||
| return { data, loading, setDialog: mutateAsync }; | |||
| }; | |||
| export const useFetchConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const fetchConversation = useCallback( | |||
| (conversationId: string, needToBeSaved = true) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/getConversation', | |||
| payload: { | |||
| needToBeSaved, | |||
| conversation_id: conversationId, | |||
| }, | |||
| }); | |||
| export const useFetchNextDialog = () => { | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { data, isFetching: loading } = useQuery<IDialog>({ | |||
| queryKey: ['fetchDialog', dialogId], | |||
| gcTime: 0, | |||
| initialData: {} as IDialog, | |||
| enabled: !!dialogId, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| const { data } = await chatService.getDialog({ dialogId }); | |||
| return data?.data ?? ({} as IDialog); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return fetchConversation; | |||
| return { data, loading }; | |||
| }; | |||
| export const useFetchDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| export const useFetchManualDialog = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['fetchManualDialog'], | |||
| gcTime: 0, | |||
| mutationFn: async (dialogId: string) => { | |||
| const { data } = await chatService.getDialog({ dialogId }); | |||
| const fetchDialog = useCallback( | |||
| (dialogId: string, needToBeSaved = true) => { | |||
| if (dialogId) { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/getDialog', | |||
| payload: { dialog_id: dialogId, needToBeSaved }, | |||
| }); | |||
| return data; | |||
| }, | |||
| }); | |||
| return { data, loading, fetchDialog: mutateAsync }; | |||
| }; | |||
| export const useRemoveNextDialog = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['removeDialog'], | |||
| mutationFn: async (dialogIds: string[]) => { | |||
| const { data } = await chatService.removeDialog({ dialogIds }); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchDialogList'] }); | |||
| message.success(i18n.t('message.deleted')); | |||
| } | |||
| return data.retcode; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return fetchDialog; | |||
| return { data, loading, removeDialog: mutateAsync }; | |||
| }; | |||
| export const useRemoveDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| //#endregion | |||
| //#region conversation | |||
| const removeDocument = useCallback( | |||
| (dialogIds: Array<string>) => { | |||
| return dispatch({ | |||
| type: 'chatModel/removeDialog', | |||
| payload: { | |||
| dialog_ids: dialogIds, | |||
| }, | |||
| }); | |||
| export const useFetchNextConversationList = () => { | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery<IConversation[]>({ | |||
| queryKey: ['fetchConversationList', dialogId], | |||
| initialData: [], | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| enabled: !!dialogId, | |||
| queryFn: async () => { | |||
| const { data } = await chatService.listConversation({ dialogId }); | |||
| return data?.data; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return removeDocument; | |||
| return { data, loading, refetch }; | |||
| }; | |||
| export const useUpdateConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| export const useFetchNextConversation = () => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery<IClientConversation>({ | |||
| queryKey: ['fetchConversation', conversationId], | |||
| initialData: {} as IClientConversation, | |||
| // enabled: isConversationIdExist(conversationId), | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| if (isConversationIdExist(conversationId)) { | |||
| const { data } = await chatService.getConversation({ conversationId }); | |||
| // if (data.retcode === 0 && needToBeSaved) { | |||
| // yield put({ | |||
| // type: 'kFModel/fetch_document_thumbnails', | |||
| // payload: { | |||
| // doc_ids: getDocumentIdsFromConversionReference(data.data), | |||
| // }, | |||
| // }); | |||
| // yield put({ type: 'setCurrentConversation', payload: data.data }); | |||
| // } | |||
| const conversation = data?.data ?? {}; | |||
| const messageList = | |||
| conversation?.message?.map((x: Message | IMessage) => ({ | |||
| ...x, | |||
| id: 'id' in x && x.id ? x.id : uuid(), | |||
| })) ?? []; | |||
| return { ...conversation, message: messageList }; | |||
| } | |||
| return { message: [] }; | |||
| }, | |||
| }); | |||
| const updateConversation = useCallback( | |||
| (payload: any) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/setConversation', | |||
| payload, | |||
| }); | |||
| return { data, loading, refetch }; | |||
| }; | |||
| export const useFetchManualConversation = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['fetchManualConversation'], | |||
| gcTime: 0, | |||
| mutationFn: async (conversationId: string) => { | |||
| const { data } = await chatService.getConversation({ conversationId }); | |||
| return data; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return updateConversation; | |||
| return { data, loading, fetchConversation: mutateAsync }; | |||
| }; | |||
| export const useUpdateNextConversation = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| @@ -134,7 +258,9 @@ export const useUpdateNextConversation = () => { | |||
| mutationKey: ['updateConversation'], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data } = await chatService.setConversation(params); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); | |||
| } | |||
| return data; | |||
| }, | |||
| }); | |||
| @@ -142,72 +268,34 @@ export const useUpdateNextConversation = () => { | |||
| return { data, loading, updateConversation: mutateAsync }; | |||
| }; | |||
| export const useSetDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| const setDialog = useCallback( | |||
| (payload: IDialog) => { | |||
| return dispatch<any>({ type: 'chatModel/setDialog', payload }); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| return setDialog; | |||
| }; | |||
| export const useRemoveConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const removeConversation = useCallback( | |||
| (conversationIds: Array<string>, dialogId: string) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/removeConversation', | |||
| payload: { | |||
| dialog_id: dialogId, | |||
| conversation_ids: conversationIds, | |||
| }, | |||
| }); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| return removeConversation; | |||
| }; | |||
| export const useRemoveNextConversation = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| /* | |||
| @deprecated | |||
| */ | |||
| export const useCompleteConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const completeConversation = useCallback( | |||
| (payload: any) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/completeConversation', | |||
| payload, | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['removeConversation'], | |||
| mutationFn: async (conversationIds: string[]) => { | |||
| const { data } = await chatService.removeConversation({ | |||
| conversationIds, | |||
| dialogId, | |||
| }); | |||
| if (data.retcode === 0) { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchConversationList'] }); | |||
| } | |||
| return data.retcode; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return completeConversation; | |||
| return { data, loading, removeConversation: mutateAsync }; | |||
| }; | |||
| //#endregion | |||
| // #region API provided for external calls | |||
| export const useCreateToken = (params: Record<string, any>) => { | |||
| const dispatch = useDispatch(); | |||
| const createToken = useCallback(() => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/createToken', | |||
| payload: params, | |||
| }); | |||
| }, [dispatch, params]); | |||
| return createToken; | |||
| }; | |||
| export const useCreateNextToken = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| @@ -303,36 +391,41 @@ export const useFetchNextStats = () => { | |||
| //#region shared chat | |||
| export const useCreateSharedConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| export const useCreateNextSharedConversation = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['createSharedConversation'], | |||
| mutationFn: async (userId?: string) => { | |||
| const { data } = await chatService.createExternalConversation({ userId }); | |||
| const createSharedConversation = useCallback( | |||
| (userId?: string) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/createExternalConversation', | |||
| payload: { userId }, | |||
| }); | |||
| return data; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return createSharedConversation; | |||
| return { data, loading, createSharedConversation: mutateAsync }; | |||
| }; | |||
| export const useFetchSharedConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| export const useFetchNextSharedConversation = () => { | |||
| const { | |||
| data, | |||
| isPending: loading, | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: ['fetchSharedConversation'], | |||
| mutationFn: async (conversationId: string) => { | |||
| const { data } = await chatService.getExternalConversation( | |||
| null, | |||
| conversationId, | |||
| ); | |||
| const fetchSharedConversation = useCallback( | |||
| (conversationId: string) => { | |||
| return dispatch<any>({ | |||
| type: 'chatModel/getExternalConversation', | |||
| payload: conversationId, | |||
| }); | |||
| return data; | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return fetchSharedConversation; | |||
| return { data, loading, fetchConversation: mutateAsync }; | |||
| }; | |||
| //#endregion | |||
| @@ -28,8 +28,9 @@ export interface ISystemStatus { | |||
| mysql: Minio; | |||
| redis: Redis; | |||
| task_executor: { | |||
| error?: string; | |||
| status: string; | |||
| elapsed: TaskExecutorElapsed; | |||
| elapsed?: TaskExecutorElapsed; | |||
| }; | |||
| } | |||
| @@ -8,7 +8,6 @@ import { | |||
| useFetchConversationOnMount, | |||
| useGetFileIcon, | |||
| useGetSendButtonDisabled, | |||
| useSelectConversationLoading, | |||
| useSendButtonDisabled, | |||
| useSendMessage, | |||
| } from '../hooks'; | |||
| @@ -16,6 +15,7 @@ import { buildMessageItemReference } from '../utils'; | |||
| import MessageInput from '@/components/message-input'; | |||
| import { useFetchUserInfo } from '@/hooks/user-setting-hooks'; | |||
| import { memo } from 'react'; | |||
| import styles from './index.less'; | |||
| const ChatContainer = () => { | |||
| @@ -26,6 +26,7 @@ const ChatContainer = () => { | |||
| removeLatestMessage, | |||
| addNewestAnswer, | |||
| conversationId, | |||
| loading, | |||
| } = useFetchConversationOnMount(); | |||
| const { | |||
| handleInputChange, | |||
| @@ -43,7 +44,6 @@ const ChatContainer = () => { | |||
| const disabled = useGetSendButtonDisabled(); | |||
| const sendDisabled = useSendButtonDisabled(value); | |||
| useGetFileIcon(); | |||
| const loading = useSelectConversationLoading(); | |||
| const { data: userInfo } = useFetchUserInfo(); | |||
| const { createConversationBeforeUploadDocument } = | |||
| useCreateConversationBeforeUploadDocument(); | |||
| @@ -104,4 +104,4 @@ const ChatContainer = () => { | |||
| ); | |||
| }; | |||
| export default ChatContainer; | |||
| export default memo(ChatContainer); | |||
| @@ -1,16 +1,16 @@ | |||
| import { MessageType } from '@/constants/chat'; | |||
| import { fileIconMap } from '@/constants/common'; | |||
| import { | |||
| useFetchConversation, | |||
| useFetchConversationList, | |||
| useFetchDialog, | |||
| useFetchDialogList, | |||
| useRemoveConversation, | |||
| useRemoveDialog, | |||
| useSelectConversationList, | |||
| useSelectDialogList, | |||
| useSetDialog, | |||
| useUpdateConversation, | |||
| useFetchManualConversation, | |||
| useFetchManualDialog, | |||
| useFetchNextConversation, | |||
| useFetchNextConversationList, | |||
| useFetchNextDialog, | |||
| useGetChatSearchParams, | |||
| useRemoveNextConversation, | |||
| useRemoveNextDialog, | |||
| useSetNextDialog, | |||
| useUpdateNextConversation, | |||
| } from '@/hooks/chat-hooks'; | |||
| import { | |||
| useSetModalState, | |||
| @@ -18,7 +18,6 @@ import { | |||
| useTranslate, | |||
| } from '@/hooks/common-hooks'; | |||
| import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; | |||
| import { | |||
| IAnswer, | |||
| IConversation, | |||
| @@ -27,6 +26,8 @@ import { | |||
| } from '@/interfaces/database/chat'; | |||
| import { IChunk } from '@/interfaces/database/knowledge'; | |||
| import { getFileExtension } from '@/utils'; | |||
| import { useMutationState } from '@tanstack/react-query'; | |||
| import { get } from 'lodash'; | |||
| import omit from 'lodash/omit'; | |||
| import trim from 'lodash/trim'; | |||
| import { | |||
| @@ -37,7 +38,7 @@ import { | |||
| useRef, | |||
| useState, | |||
| } from 'react'; | |||
| import { useDispatch, useSearchParams, useSelector } from 'umi'; | |||
| import { useSearchParams } from 'umi'; | |||
| import { v4 as uuid } from 'uuid'; | |||
| import { ChatSearchParams } from './constants'; | |||
| import { | |||
| @@ -45,70 +46,20 @@ import { | |||
| IMessage, | |||
| VariableTableDataType, | |||
| } from './interface'; | |||
| import { ChatModelState } from './model'; | |||
| import { isConversationIdExist } from './utils'; | |||
| export const useSelectCurrentDialog = () => { | |||
| const currentDialog: IDialog = useSelector( | |||
| (state: any) => state.chatModel.currentDialog, | |||
| ); | |||
| return currentDialog; | |||
| }; | |||
| export const useFetchDialogOnMount = ( | |||
| dialogId: string, | |||
| visible: boolean, | |||
| ): IDialog => { | |||
| const currentDialog: IDialog = useSelectCurrentDialog(); | |||
| const fetchDialog = useFetchDialog(); | |||
| useEffect(() => { | |||
| if (dialogId && visible) { | |||
| fetchDialog(dialogId); | |||
| } | |||
| }, [dialogId, fetchDialog, visible]); | |||
| return currentDialog; | |||
| }; | |||
| export const useSetCurrentDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| const currentDialog: IDialog = useSelector( | |||
| (state: any) => state.chatModel.currentDialog, | |||
| ); | |||
| const setCurrentDialog = useCallback( | |||
| (dialogId: string) => { | |||
| dispatch({ | |||
| type: 'chatModel/setCurrentDialog', | |||
| payload: { id: dialogId }, | |||
| }); | |||
| const data = useMutationState({ | |||
| filters: { mutationKey: ['fetchDialog'] }, | |||
| select: (mutation) => { | |||
| return get(mutation, 'state.data.data', {}); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| }); | |||
| return { currentDialog, setCurrentDialog }; | |||
| }; | |||
| export const useResetCurrentDialog = () => { | |||
| const dispatch = useDispatch(); | |||
| const resetCurrentDialog = useCallback(() => { | |||
| dispatch({ | |||
| type: 'chatModel/setCurrentDialog', | |||
| payload: {}, | |||
| }); | |||
| }, [dispatch]); | |||
| return { resetCurrentDialog }; | |||
| return (data.at(-1) ?? {}) as IDialog; | |||
| }; | |||
| export const useSelectPromptConfigParameters = (): VariableTableDataType[] => { | |||
| const currentDialog: IDialog = useSelector( | |||
| (state: any) => state.chatModel.currentDialog, | |||
| ); | |||
| const { data: currentDialog } = useFetchNextDialog(); | |||
| const finalParameters: VariableTableDataType[] = useMemo(() => { | |||
| const parameters = currentDialog?.prompt_config?.parameters ?? []; | |||
| @@ -129,83 +80,15 @@ export const useSelectPromptConfigParameters = (): VariableTableDataType[] => { | |||
| export const useDeleteDialog = () => { | |||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||
| const removeDocument = useRemoveDialog(); | |||
| const { removeDialog } = useRemoveNextDialog(); | |||
| const onRemoveDialog = (dialogIds: Array<string>) => { | |||
| showDeleteConfirm({ onOk: () => removeDocument(dialogIds) }); | |||
| showDeleteConfirm({ onOk: () => removeDialog(dialogIds) }); | |||
| }; | |||
| return { onRemoveDialog }; | |||
| }; | |||
| export const useGetChatSearchParams = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| return { | |||
| dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', | |||
| conversationId: | |||
| currentQueryParameters.get(ChatSearchParams.ConversationId) || '', | |||
| }; | |||
| }; | |||
| export const useSetCurrentConversation = () => { | |||
| const dispatch = useDispatch(); | |||
| const setCurrentConversation = useCallback( | |||
| (currentConversation: IClientConversation) => { | |||
| dispatch({ | |||
| type: 'chatModel/setCurrentConversation', | |||
| payload: currentConversation, | |||
| }); | |||
| }, | |||
| [dispatch], | |||
| ); | |||
| return setCurrentConversation; | |||
| }; | |||
| export const useClickDialogCard = () => { | |||
| const [currentQueryParameters, setSearchParams] = useSearchParams(); | |||
| const newQueryParameters: URLSearchParams = useMemo(() => { | |||
| return new URLSearchParams(); | |||
| }, []); | |||
| const handleClickDialog = useCallback( | |||
| (dialogId: string) => { | |||
| newQueryParameters.set(ChatSearchParams.DialogId, dialogId); | |||
| // newQueryParameters.set( | |||
| // ChatSearchParams.ConversationId, | |||
| // EmptyConversationId, | |||
| // ); | |||
| setSearchParams(newQueryParameters); | |||
| }, | |||
| [newQueryParameters, setSearchParams], | |||
| ); | |||
| return { handleClickDialog }; | |||
| }; | |||
| export const useSelectFirstDialogOnMount = () => { | |||
| const fetchDialogList = useFetchDialogList(); | |||
| const dialogList = useSelectDialogList(); | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const fetchList = useCallback(async () => { | |||
| const data = await fetchDialogList(); | |||
| if (data.retcode === 0 && data.data.length > 0) { | |||
| handleClickDialog(data.data[0].id); | |||
| } | |||
| }, [fetchDialogList, handleClickDialog]); | |||
| useEffect(() => { | |||
| fetchList(); | |||
| }, [fetchList]); | |||
| return dialogList; | |||
| }; | |||
| export const useHandleItemHover = () => { | |||
| const [activated, setActivated] = useState<string>(''); | |||
| @@ -226,9 +109,8 @@ export const useHandleItemHover = () => { | |||
| export const useEditDialog = () => { | |||
| const [dialog, setDialog] = useState<IDialog>({} as IDialog); | |||
| const fetchDialog = useFetchDialog(); | |||
| const submitDialog = useSetDialog(); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', ['setDialog']); | |||
| const { fetchDialog } = useFetchManualDialog(); | |||
| const { setDialog: submitDialog, loading } = useSetNextDialog(); | |||
| const { | |||
| visible: dialogEditVisible, | |||
| @@ -255,7 +137,7 @@ export const useEditDialog = () => { | |||
| const handleShowDialogEditModal = useCallback( | |||
| async (dialogId?: string) => { | |||
| if (dialogId) { | |||
| const ret = await fetchDialog(dialogId, false); | |||
| const ret = await fetchDialog(dialogId); | |||
| if (ret.retcode === 0) { | |||
| setDialog(ret.data); | |||
| } | |||
| @@ -282,25 +164,15 @@ export const useEditDialog = () => { | |||
| //#region conversation | |||
| export const useFetchConversationListOnMount = () => { | |||
| const conversationList = useSelectConversationList(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const fetchConversationList = useFetchConversationList(); | |||
| useEffect(() => { | |||
| fetchConversationList(dialogId); | |||
| }, [fetchConversationList, dialogId]); | |||
| return conversationList; | |||
| }; | |||
| export const useSelectDerivedConversationList = () => { | |||
| const { t } = useTranslate('chat'); | |||
| const [list, setList] = useState<Array<IConversation>>([]); | |||
| let chatModel: ChatModelState = useSelector((state: any) => state.chatModel); | |||
| const { conversationList, currentDialog } = chatModel; | |||
| const { data: currentDialog } = useFetchNextDialog(); | |||
| const { data: conversationList, loading } = useFetchNextConversationList(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const prologue = currentDialog?.prompt_config?.prologue ?? ''; | |||
| const { t } = useTranslate('chat'); | |||
| const addTemporaryConversation = useCallback(() => { | |||
| setList((pre) => { | |||
| if (dialogId) { | |||
| @@ -329,7 +201,7 @@ export const useSelectDerivedConversationList = () => { | |||
| addTemporaryConversation(); | |||
| }, [addTemporaryConversation]); | |||
| return { list, addTemporaryConversation }; | |||
| return { list, addTemporaryConversation, loading }; | |||
| }; | |||
| export const useClickConversationCard = () => { | |||
| @@ -352,7 +224,7 @@ export const useClickConversationCard = () => { | |||
| export const useSetConversation = () => { | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const updateConversation = useUpdateConversation(); | |||
| const { updateConversation } = useUpdateNextConversation(); | |||
| const setConversation = useCallback( | |||
| (message: string) => { | |||
| @@ -376,11 +248,8 @@ export const useSetConversation = () => { | |||
| export const useSelectCurrentConversation = () => { | |||
| const [currentConversation, setCurrentConversation] = | |||
| useState<IClientConversation>({} as IClientConversation); | |||
| const conversation: IClientConversation = useSelector( | |||
| (state: any) => state.chatModel.currentConversation, | |||
| ); | |||
| const dialog = useSelectCurrentDialog(); | |||
| const { data: conversation, loading } = useFetchNextConversation(); | |||
| const { data: dialog } = useFetchNextDialog(); | |||
| const { conversationId, dialogId } = useGetChatSearchParams(); | |||
| const addNewestConversation = useCallback( | |||
| @@ -474,6 +343,7 @@ export const useSelectCurrentConversation = () => { | |||
| addNewestConversation, | |||
| removeLatestMessage, | |||
| addNewestAnswer, | |||
| loading, | |||
| }; | |||
| }; | |||
| @@ -495,25 +365,15 @@ export const useScrollToBottom = (currentConversation: IClientConversation) => { | |||
| export const useFetchConversationOnMount = () => { | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const fetchConversation = useFetchConversation(); | |||
| const { | |||
| currentConversation, | |||
| addNewestConversation, | |||
| removeLatestMessage, | |||
| addNewestAnswer, | |||
| loading, | |||
| } = useSelectCurrentConversation(); | |||
| const ref = useScrollToBottom(currentConversation); | |||
| const fetchConversationOnMount = useCallback(() => { | |||
| if (isConversationIdExist(conversationId)) { | |||
| fetchConversation(conversationId); | |||
| } | |||
| }, [fetchConversation, conversationId]); | |||
| useEffect(() => { | |||
| fetchConversationOnMount(); | |||
| }, [fetchConversationOnMount]); | |||
| return { | |||
| currentConversation, | |||
| addNewestConversation, | |||
| @@ -521,6 +381,7 @@ export const useFetchConversationOnMount = () => { | |||
| removeLatestMessage, | |||
| addNewestAnswer, | |||
| conversationId, | |||
| loading, | |||
| }; | |||
| }; | |||
| @@ -655,13 +516,12 @@ export const useGetFileIcon = () => { | |||
| }; | |||
| export const useDeleteConversation = () => { | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const showDeleteConfirm = useShowDeleteConfirm(); | |||
| const removeConversation = useRemoveConversation(); | |||
| const { removeConversation } = useRemoveNextConversation(); | |||
| const deleteConversation = (conversationIds: Array<string>) => async () => { | |||
| const ret = await removeConversation(conversationIds, dialogId); | |||
| const ret = await removeConversation(conversationIds); | |||
| if (ret === 0) { | |||
| handleClickConversation(''); | |||
| } | |||
| @@ -679,13 +539,13 @@ export const useRenameConversation = () => { | |||
| const [conversation, setConversation] = useState<IClientConversation>( | |||
| {} as IClientConversation, | |||
| ); | |||
| const fetchConversation = useFetchConversation(); | |||
| const { fetchConversation } = useFetchManualConversation(); | |||
| const { | |||
| visible: conversationRenameVisible, | |||
| hideModal: hideConversationRenameModal, | |||
| showModal: showConversationRenameModal, | |||
| } = useSetModalState(); | |||
| const updateConversation = useUpdateConversation(); | |||
| const { updateConversation, loading } = useUpdateNextConversation(); | |||
| const onConversationRenameOk = useCallback( | |||
| async (name: string) => { | |||
| @@ -702,13 +562,9 @@ export const useRenameConversation = () => { | |||
| [updateConversation, conversation, hideConversationRenameModal], | |||
| ); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||
| 'setConversation', | |||
| ]); | |||
| const handleShowConversationRenameModal = useCallback( | |||
| async (conversationId: string) => { | |||
| const ret = await fetchConversation(conversationId, false); | |||
| const ret = await fetchConversation(conversationId); | |||
| if (ret.retcode === 0) { | |||
| setConversation(ret.data); | |||
| } | |||
| @@ -751,16 +607,6 @@ export const useClickDrawer = () => { | |||
| }; | |||
| }; | |||
| export const useSelectDialogListLoading = () => { | |||
| return useOneNamespaceEffectsLoading('chatModel', ['listDialog']); | |||
| }; | |||
| export const useSelectConversationListLoading = () => { | |||
| return useOneNamespaceEffectsLoading('chatModel', ['listConversation']); | |||
| }; | |||
| export const useSelectConversationLoading = () => { | |||
| return useOneNamespaceEffectsLoading('chatModel', ['getConversation']); | |||
| }; | |||
| export const useGetSendButtonDisabled = () => { | |||
| const { dialogId, conversationId } = useGetChatSearchParams(); | |||
| @@ -26,22 +26,20 @@ import ChatConfigurationModal from './chat-configuration-modal'; | |||
| import ChatContainer from './chat-container'; | |||
| import { | |||
| useClickConversationCard, | |||
| useClickDialogCard, | |||
| useDeleteConversation, | |||
| useDeleteDialog, | |||
| useEditDialog, | |||
| useFetchConversationListOnMount, | |||
| useFetchDialogOnMount, | |||
| useGetChatSearchParams, | |||
| useHandleItemHover, | |||
| useRenameConversation, | |||
| useSelectConversationListLoading, | |||
| useSelectDerivedConversationList, | |||
| useSelectDialogListLoading, | |||
| useSelectFirstDialogOnMount, | |||
| } from './hooks'; | |||
| import ChatOverviewModal from '@/components/api-service/chat-overview-modal'; | |||
| import { | |||
| useClickDialogCard, | |||
| useFetchNextDialogList, | |||
| useGetChatSearchParams, | |||
| } from '@/hooks/chat-hooks'; | |||
| import { useSetModalState, useTranslate } from '@/hooks/common-hooks'; | |||
| import { useSetSelectedRecord } from '@/hooks/logic-hooks'; | |||
| import { IDialog } from '@/interfaces/database/chat'; | |||
| @@ -50,14 +48,17 @@ import styles from './index.less'; | |||
| const { Text } = Typography; | |||
| const Chat = () => { | |||
| const dialogList = useSelectFirstDialogOnMount(); | |||
| const { data: dialogList, loading: dialogLoading } = useFetchNextDialogList(); | |||
| const { onRemoveDialog } = useDeleteDialog(); | |||
| const { onRemoveConversation } = useDeleteConversation(); | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const { handleClickConversation } = useClickConversationCard(); | |||
| const { dialogId, conversationId } = useGetChatSearchParams(); | |||
| const { list: conversationList, addTemporaryConversation } = | |||
| useSelectDerivedConversationList(); | |||
| const { | |||
| list: conversationList, | |||
| addTemporaryConversation, | |||
| loading: conversationLoading, | |||
| } = useSelectDerivedConversationList(); | |||
| const { activated, handleItemEnter, handleItemLeave } = useHandleItemHover(); | |||
| const { | |||
| activated: conversationActivated, | |||
| @@ -81,8 +82,6 @@ const Chat = () => { | |||
| hideDialogEditModal, | |||
| showDialogEditModal, | |||
| } = useEditDialog(); | |||
| const dialogLoading = useSelectDialogListLoading(); | |||
| const conversationLoading = useSelectConversationListLoading(); | |||
| const { t } = useTranslate('chat'); | |||
| const { | |||
| visible: overviewVisible, | |||
| @@ -91,8 +90,6 @@ const Chat = () => { | |||
| } = useSetModalState(); | |||
| const { currentRecord, setRecord } = useSetSelectedRecord<IDialog>(); | |||
| useFetchDialogOnMount(dialogId, true); | |||
| const handleAppCardEnter = (id: string) => () => { | |||
| handleItemEnter(id); | |||
| }; | |||
| @@ -236,8 +233,6 @@ const Chat = () => { | |||
| return appItems; | |||
| }; | |||
| useFetchConversationListOnMount(); | |||
| return ( | |||
| <Flex className={styles.chatWrapper}> | |||
| <Flex className={styles.chatAppWrapper}> | |||
| @@ -1,10 +1,9 @@ | |||
| import { MessageType, SharedFrom } from '@/constants/chat'; | |||
| import { | |||
| useCreateSharedConversation, | |||
| useFetchSharedConversation, | |||
| useCreateNextSharedConversation, | |||
| useFetchNextSharedConversation, | |||
| } from '@/hooks/chat-hooks'; | |||
| import { useSendMessageWithSse } from '@/hooks/logic-hooks'; | |||
| import { useOneNamespaceEffectsLoading } from '@/hooks/store-hooks'; | |||
| import { IAnswer, Message } from '@/interfaces/database/chat'; | |||
| import api from '@/utils/api'; | |||
| import omit from 'lodash/omit'; | |||
| @@ -25,12 +24,12 @@ export const useCreateSharedConversationOnMount = () => { | |||
| const [currentQueryParameters] = useSearchParams(); | |||
| const [conversationId, setConversationId] = useState(''); | |||
| const createConversation = useCreateSharedConversation(); | |||
| const { createSharedConversation: createConversation } = | |||
| useCreateNextSharedConversation(); | |||
| const sharedId = currentQueryParameters.get('shared_id'); | |||
| const userId = currentQueryParameters.get('user_id'); | |||
| const setConversation = useCallback(async () => { | |||
| console.info(sharedId); | |||
| if (sharedId) { | |||
| const data = await createConversation(userId ?? undefined); | |||
| const id = data.data?.id; | |||
| @@ -50,10 +49,7 @@ export const useCreateSharedConversationOnMount = () => { | |||
| export const useSelectCurrentSharedConversation = (conversationId: string) => { | |||
| const [currentConversation, setCurrentConversation] = | |||
| useState<IClientConversation>({} as IClientConversation); | |||
| const fetchConversation = useFetchSharedConversation(); | |||
| const loading = useOneNamespaceEffectsLoading('chatModel', [ | |||
| 'getExternalConversation', | |||
| ]); | |||
| const { fetchConversation, loading } = useFetchNextSharedConversation(); | |||
| const ref = useScrollToBottom(currentConversation); | |||
| @@ -147,7 +143,8 @@ export const useSendSharedMessage = ( | |||
| addNewestAnswer: (answer: IAnswer) => void, | |||
| ) => { | |||
| const conversationId = conversation.id; | |||
| const setConversation = useCreateSharedConversation(); | |||
| const { createSharedConversation: setConversation } = | |||
| useCreateNextSharedConversation(); | |||
| const { handleInputChange, value, setValue } = useHandleMessageInputChange(); | |||
| const { send, answer, done } = useSendMessageWithSse( | |||
| @@ -1,72 +1,14 @@ | |||
| import { Authorization } from '@/constants/authorization'; | |||
| import { getAuthorization } from '@/utils/authorization-util'; | |||
| import { PlusOutlined } from '@ant-design/icons'; | |||
| import type { GetProp, UploadFile, UploadProps } from 'antd'; | |||
| import type { UploadFile, UploadProps } from 'antd'; | |||
| import { Image, Input, Upload } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import { useGetChatSearchParams } from '../chat/hooks'; | |||
| type FileType = Parameters<GetProp<UploadProps, 'beforeUpload'>>[0]; | |||
| const getBase64 = (file: FileType): Promise<string> => | |||
| new Promise((resolve, reject) => { | |||
| const reader = new FileReader(); | |||
| reader.readAsDataURL(file); | |||
| reader.onload = () => resolve(reader.result as string); | |||
| reader.onerror = (error) => reject(error); | |||
| }); | |||
| const InputWithUpload = () => { | |||
| const [previewOpen, setPreviewOpen] = useState(false); | |||
| const [previewImage, setPreviewImage] = useState(''); | |||
| const { conversationId } = useGetChatSearchParams(); | |||
| const [fileList, setFileList] = useState<UploadFile[]>([ | |||
| { | |||
| uid: '-1', | |||
| name: 'image.png', | |||
| status: 'done', | |||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| }, | |||
| { | |||
| uid: '-2', | |||
| name: 'image.png', | |||
| status: 'done', | |||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| }, | |||
| { | |||
| uid: '-3', | |||
| name: 'image.png', | |||
| status: 'done', | |||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| }, | |||
| { | |||
| uid: '-4', | |||
| name: 'image.png', | |||
| status: 'done', | |||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| }, | |||
| { | |||
| uid: '-xxx', | |||
| percent: 50, | |||
| name: 'image.png', | |||
| status: 'uploading', | |||
| url: 'https://zos.alipayobjects.com/rmsportal/jkjgkEfvpUPVyRjUImniVslZfWPnJuuZ.png', | |||
| }, | |||
| { | |||
| uid: '-5', | |||
| name: 'image.png', | |||
| status: 'error', | |||
| }, | |||
| ]); | |||
| const handlePreview = async (file: UploadFile) => { | |||
| if (!file.url && !file.preview) { | |||
| file.preview = await getBase64(file.originFileObj as FileType); | |||
| } | |||
| setPreviewImage(file.url || (file.preview as string)); | |||
| setPreviewOpen(true); | |||
| }; | |||
| const [fileList, setFileList] = useState<UploadFile[]>([]); | |||
| const handleChange: UploadProps['onChange'] = ({ fileList: newFileList }) => | |||
| setFileList(newFileList); | |||
| @@ -84,7 +26,6 @@ const InputWithUpload = () => { | |||
| action="/v1/document/upload_and_parse" | |||
| listType="picture-card" | |||
| fileList={fileList} | |||
| onPreview={handlePreview} | |||
| onChange={handleChange} | |||
| multiple | |||
| headers={{ [Authorization]: getAuthorization() }} | |||
| @@ -70,9 +70,13 @@ const SystemInfo = () => { | |||
| key={key} | |||
| > | |||
| {key === 'task_executor' ? ( | |||
| <TaskBarChat | |||
| data={info.elapsed as TaskExecutorElapsed} | |||
| ></TaskBarChat> | |||
| info?.elapsed ? ( | |||
| <TaskBarChat | |||
| data={info.elapsed as TaskExecutorElapsed} | |||
| ></TaskBarChat> | |||
| ) : ( | |||
| <Text className={styles.error}>{info.error}</Text> | |||
| ) | |||
| ) : ( | |||
| Object.keys(info) | |||
| .filter((x) => x !== 'status') | |||
| @@ -0,0 +1,5 @@ | |||
| import { EmptyConversationId } from '@/constants/chat'; | |||
| export const isConversationIdExist = (conversationId: string) => { | |||
| return conversationId !== EmptyConversationId && conversationId !== ''; | |||
| }; | |||