import { FileUploadProps } from '@/components/file-upload'; import { useHandleFilterSubmit } from '@/components/list-filter-bar/use-handle-filter-submit'; import message from '@/components/ui/message'; import { ChatSearchParams } from '@/constants/chat'; import { IClientConversation, IConversation, IDialog, IExternalChatInfo, } from '@/interfaces/database/chat'; import { IAskRequestBody, IFeedbackRequestBody, } from '@/interfaces/request/chat'; import i18n from '@/locales/config'; import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message'; import chatService from '@/services/next-chat-service'; import api from '@/utils/api'; import { buildMessageListWithUuid } from '@/utils/chat'; import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; import { useDebounce } from 'ahooks'; import { has } from 'lodash'; import { useCallback, useRef } from 'react'; import { useTranslation } from 'react-i18next'; import { useParams, useSearchParams } from 'react-router'; import { useGetPaginationWithRouter, useHandleSearchChange, } from './logic-hooks'; import { useHandleSearchStrChange } from './logic-hooks/use-change-search'; export const enum ChatApiAction { FetchChatList = 'fetchChatList', DeleteChat = 'deleteChat', CreateChat = 'createChat', UpdateChat = 'updateChat', PatchChat = 'patchChat', FetchChat = 'fetchChat', FetchSessionList = 'fetchSessionList', FetchSession = 'fetchSession', FetchSessionManually = 'fetchSessionManually', CreateSession = 'createSession', UpdateSession = 'updateSession', RemoveSession = 'removeSession', DeleteMessage = 'deleteMessage', FetchMindMap = 'fetchMindMap', FetchRelatedQuestions = 'fetchRelatedQuestions', UploadAndParse = 'upload_and_parse', FetchExternalChatInfo = 'fetchExternalChatInfo', Feedback = 'feedback', CreateSharedConversation = 'createSharedConversation', } export const useGetChatSearchParams = () => { const [currentQueryParameters] = useSearchParams(); return { dialogId: currentQueryParameters.get(ChatSearchParams.DialogId) || '', conversationId: currentQueryParameters.get(ChatSearchParams.ConversationId) || '', isNew: currentQueryParameters.get(ChatSearchParams.isNew) || '', }; }; export const useFetchChatList = () => { const { searchString, handleInputChange } = useHandleSearchChange(); const { pagination, setPagination } = useGetPaginationWithRouter(); const debouncedSearchString = useDebounce(searchString, { wait: 500 }); const { filterValue, handleFilterSubmit } = useHandleFilterSubmit(); const { data, isFetching: loading, refetch, } = useQuery<{ chats: IDialog[]; total: number }>({ queryKey: [ ChatApiAction.FetchChatList, { debouncedSearchString, filterValue, ...pagination, }, ], initialData: { chats: [], total: 0 }, gcTime: 0, refetchOnWindowFocus: false, queryFn: async () => { const { data } = await chatService.listChats( { params: { keywords: debouncedSearchString, page_size: pagination.pageSize, page: pagination.current, owner_ids: filterValue.owner, }, data: {}, paramsSerializer: { indexes: null }, }, true, ); return data?.data ?? { chats: [], total: 0 }; }, }); const onInputChange: React.ChangeEventHandler = useCallback( (e) => { handleInputChange(e); }, [handleInputChange], ); return { data, loading, refetch, searchString, handleInputChange: onInputChange, pagination: { ...pagination, total: data?.total }, setPagination, filterValue, handleFilterSubmit, }; }; export const useDeleteChat = () => { const queryClient = useQueryClient(); const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.DeleteChat], mutationFn: async (chatId: string) => { const { data } = await chatService.deleteChat(chatId); if (data.code === 0) { queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchChatList], }); message.success(t('message.deleted')); } return data.code; }, }); return { data, loading, deleteChat: mutateAsync }; }; export const useCreateChat = () => { const queryClient = useQueryClient(); const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.CreateChat], mutationFn: async (params: Record) => { const { data } = await chatService.createChat(params); if (data.code === 0) { queryClient.invalidateQueries({ exact: false, queryKey: [ChatApiAction.FetchChatList], }); message.success(t('message.created')); } return data?.code; }, }); return { data, loading, createChat: mutateAsync }; }; export const useUpdateChat = () => { const queryClient = useQueryClient(); const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.UpdateChat], mutationFn: async ({ chatId, params, }: { chatId: string; params: Record; }) => { const { data } = await chatService.updateChat( { url: api.updateChat(chatId), data: params }, true, ); if (data.code === 0) { queryClient.invalidateQueries({ exact: false, queryKey: [ChatApiAction.FetchChatList], }); queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchChat] }); message.success(t('message.modified')); } return data?.code; }, }); return { data, loading, updateChat: mutateAsync }; }; export const usePatchChat = () => { const queryClient = useQueryClient(); const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.PatchChat], mutationFn: async ({ chatId, params, }: { chatId: string; params: Record; }) => { const { data } = await chatService.patchChat( { url: api.patchChat(chatId), data: params }, true, ); if (data.code === 0) { queryClient.invalidateQueries({ exact: false, queryKey: [ChatApiAction.FetchChatList], }); queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchChat] }); message.success(t('message.modified')); } return data?.code; }, }); return { data, loading, patchChat: mutateAsync }; }; export const useFetchChat = () => { const { id } = useParams(); const { data, isFetching: loading, refetch, } = useQuery({ queryKey: [ChatApiAction.FetchChat, id], gcTime: 0, initialData: {} as IDialog, enabled: !!id, refetchOnWindowFocus: false, queryFn: async () => { const { data } = await chatService.getChat(id); return data?.data ?? ({} as IDialog); }, }); return { data, loading, refetch }; }; //#region Session export const useFetchSessionList = () => { const { id } = useParams(); const { searchString, handleInputChange } = useHandleSearchStrChange(); const { data, isFetching: loading, refetch, } = useQuery({ queryKey: [ChatApiAction.FetchSessionList, id], initialData: [], gcTime: 0, refetchOnWindowFocus: false, enabled: !!id, select(data) { return searchString ? data.filter((x) => x.name.includes(searchString)) : data; }, queryFn: async () => { const { data } = await chatService.listSessions( { url: api.listSessions(id!) }, true, ); return data?.data; }, }); return { data, loading, refetch, searchString, handleInputChange }; }; export function useFetchSessionManually() { const { id: chatId } = useParams(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.FetchSessionManually], mutationFn: async (sessionId) => { const { data } = await chatService.getSession( { url: api.getSession(chatId!, sessionId) }, true, ); const conversation = data?.data ?? {}; const messageList = buildMessageListWithUuid(conversation?.messages); return { ...conversation, messages: messageList }; }, }); return { data, loading, fetchSessionManually: mutateAsync }; } export const useCreateSession = () => { const queryClient = useQueryClient(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.CreateSession], mutationFn: async ({ chatId, name }: { chatId: string; name: string }) => { const { data } = await chatService.createSession( { url: api.createSession(chatId), data: { name } }, true, ); if (data.code === 0) { queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchSessionList], }); } return data; }, }); return { data, loading, createSession: mutateAsync }; }; export const useUpdateSession = () => { const { t } = useTranslation(); const queryClient = useQueryClient(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.UpdateSession], mutationFn: async ({ chatId, sessionId, params, }: { chatId: string; sessionId: string; params: Record; }) => { const { data } = await chatService.updateSession( { url: api.updateSession(chatId, sessionId), data: params }, true, ); if (data.code === 0) { queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchSessionList], }); message.success(t(`message.modified`)); } return data; }, }); return { data, loading, updateSession: mutateAsync }; }; export const useRemoveSessions = () => { const queryClient = useQueryClient(); const { id: chatId } = useParams(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.RemoveSession], mutationFn: async (sessionIds: string[]) => { const { data } = await chatService.removeSessions( { url: api.removeSessions(chatId!), data: { ids: sessionIds } }, true, ); if (data.code === 0) { queryClient.invalidateQueries({ queryKey: [ChatApiAction.FetchSessionList], }); } return data.code; }, }); return { data, loading, removeSessions: mutateAsync }; }; export const useDeleteMessage = () => { const { conversationId } = useGetChatSearchParams(); const { id: chatId } = useParams(); const { t } = useTranslation(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.DeleteMessage], mutationFn: async (messageId: string) => { const { data } = await chatService.deleteMessage( { url: api.deleteMessage(chatId!, conversationId, messageId) }, true, ); if (data.code === 0) { message.success(t(`message.deleted`)); } return data.code; }, }); return { data, loading, deleteMessage: mutateAsync }; }; export const useFeedback = () => { const { conversationId } = useGetChatSearchParams(); const { id: chatId } = useParams(); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.Feedback], mutationFn: async (params: IFeedbackRequestBody) => { const { data } = await chatService.thumbup( { url: api.thumbup(chatId!, conversationId, params.messageId!), data: { thumbup: params.thumbup, feedback: params.feedback }, }, true, ); if (data.code === 0) { message.success(i18n.t(`message.operated`)); } return data.code; }, }); return { data, loading, feedback: mutateAsync }; }; type UploadParameters = Parameters>; type X = { file: UploadParameters[0][0]; options: UploadParameters[1]; conversationId?: string; }; export function useUploadAndParseFile() { const { conversationId: id } = useGetChatSearchParams(); const { t } = useTranslation(); const controller = useRef(new AbortController()); const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.UploadAndParse], mutationFn: async ({ file, options: { onProgress, onSuccess, onError }, conversationId, }: X) => { try { const formData = new FormData(); formData.append('file', file); formData.append('conversation_id', conversationId || id); const { data } = await chatService.documentInfoUpload( { url: api.documentInfoUpload, signal: controller.current.signal, data: formData, onUploadProgress: ({ progress }) => { onProgress(file, (progress || 0) * 100 - 1); }, }, true, ); onProgress(file, 100); if (data.code === 0) { onSuccess(file); message.success(t(`message.uploaded`)); } else { onError(file, new Error(data.message)); } return data; } catch (error) { onError(file, error as Error); } }, }); const cancel = useCallback(() => { controller.current.abort(); controller.current = new AbortController(); }, [controller]); return { data, loading, uploadAndParseFile: mutateAsync, cancel }; } export const useFetchExternalChatInfo = () => { const { sharedId: id } = useGetSharedChatSearchParams(); const { data, isFetching: loading, refetch, } = useQuery({ queryKey: [ChatApiAction.FetchExternalChatInfo, id], gcTime: 0, initialData: {} as IExternalChatInfo, enabled: !!id, refetchOnWindowFocus: false, queryFn: async () => { const { data } = await chatService.fetchExternalChatInfo(id!); return data?.data; }, }); return { data, loading, refetch }; }; //#endregion Session //#region search page export const useFetchMindMap = () => { const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.FetchMindMap], gcTime: 0, mutationFn: async (params: IAskRequestBody) => { try { const ret = await chatService.chatsMindmap(params); return ret?.data?.data ?? {}; } catch (error: any) { if (has(error, 'message')) { message.error(error.message); } return []; } }, }); return { data, loading, fetchMindMap: mutateAsync }; }; export const useFetchRelatedQuestions = () => { const { data, isPending: loading, mutateAsync, } = useMutation({ mutationKey: [ChatApiAction.FetchRelatedQuestions], gcTime: 0, mutationFn: async (question: string): Promise => { const { data } = await chatService.chatsRelatedQuestions({ question }); return data?.data ?? []; }, }); return { data, loading, fetchRelatedQuestions: mutateAsync }; }; //#endregion