### What problem does this PR solve? Feat: Create a conversation #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.1
| @@ -1,12 +1,12 @@ | |||
| import message from '@/components/ui/message'; | |||
| import { ChatSearchParams } from '@/constants/chat'; | |||
| import { IDialog } from '@/interfaces/database/chat'; | |||
| import chatService from '@/services/chat-service'; | |||
| import chatService from '@/services/next-chat-service '; | |||
| import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query'; | |||
| import { useDebounce } from 'ahooks'; | |||
| import { useCallback, useMemo } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { history, useSearchParams } from 'umi'; | |||
| import { useParams, useSearchParams } from 'umi'; | |||
| import { | |||
| useGetPaginationWithRouter, | |||
| useHandleSearchChange, | |||
| @@ -16,6 +16,7 @@ export const enum ChatApiAction { | |||
| FetchDialogList = 'fetchDialogList', | |||
| RemoveDialog = 'removeDialog', | |||
| SetDialog = 'setDialog', | |||
| FetchDialog = 'fetchDialog', | |||
| } | |||
| export const useGetChatSearchParams = () => { | |||
| @@ -52,9 +53,7 @@ export const useClickDialogCard = () => { | |||
| return { handleClickDialog }; | |||
| }; | |||
| export const useFetchDialogList = (pureFetch = false) => { | |||
| const { handleClickDialog } = useClickDialogCard(); | |||
| const { dialogId } = useGetChatSearchParams(); | |||
| export const useFetchDialogList = () => { | |||
| const { searchString, handleInputChange } = useHandleSearchChange(); | |||
| const { pagination, setPagination } = useGetPaginationWithRouter(); | |||
| const debouncedSearchString = useDebounce(searchString, { wait: 500 }); | |||
| @@ -63,7 +62,7 @@ export const useFetchDialogList = (pureFetch = false) => { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery<IDialog[]>({ | |||
| } = useQuery<{ dialogs: IDialog[]; total: number }>({ | |||
| queryKey: [ | |||
| ChatApiAction.FetchDialogList, | |||
| { | |||
| @@ -71,27 +70,17 @@ export const useFetchDialogList = (pureFetch = false) => { | |||
| ...pagination, | |||
| }, | |||
| ], | |||
| initialData: [], | |||
| initialData: { dialogs: [], total: 0 }, | |||
| gcTime: 0, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async (...params) => { | |||
| console.log('🚀 ~ queryFn: ~ params:', params); | |||
| const { data } = await chatService.listDialog(); | |||
| if (data.code === 0) { | |||
| const list: IDialog[] = data.data; | |||
| if (!pureFetch) { | |||
| if (list.length > 0) { | |||
| if (list.every((x) => x.id !== dialogId)) { | |||
| handleClickDialog(data.data[0].id); | |||
| } | |||
| } else { | |||
| history.push('/chat'); | |||
| } | |||
| } | |||
| } | |||
| return data?.data ?? []; | |||
| queryFn: async () => { | |||
| const { data } = await chatService.listDialog({ | |||
| keywords: debouncedSearchString, | |||
| page_size: pagination.pageSize, | |||
| page: pagination.current, | |||
| }); | |||
| return data?.data ?? { dialogs: [], total: 0 }; | |||
| }, | |||
| }); | |||
| @@ -147,17 +136,14 @@ export const useSetDialog = () => { | |||
| mutateAsync, | |||
| } = useMutation({ | |||
| mutationKey: [ChatApiAction.SetDialog], | |||
| mutationFn: async (params: IDialog) => { | |||
| mutationFn: async (params: Partial<IDialog>) => { | |||
| const { data } = await chatService.setDialog(params); | |||
| if (data.code === 0) { | |||
| queryClient.invalidateQueries({ | |||
| exact: false, | |||
| queryKey: ['fetchDialogList'], | |||
| queryKey: [ChatApiAction.FetchDialogList], | |||
| }); | |||
| queryClient.invalidateQueries({ | |||
| queryKey: ['fetchDialog'], | |||
| }); | |||
| message.success( | |||
| t(`message.${params.dialog_id ? 'modified' : 'created'}`), | |||
| ); | |||
| @@ -168,3 +154,29 @@ export const useSetDialog = () => { | |||
| return { data, loading, setDialog: mutateAsync }; | |||
| }; | |||
| export const useFetchDialog = () => { | |||
| const { id } = useParams(); | |||
| const { | |||
| data, | |||
| isFetching: loading, | |||
| refetch, | |||
| } = useQuery<IDialog>({ | |||
| queryKey: [ChatApiAction.FetchDialog, id], | |||
| gcTime: 0, | |||
| initialData: {} as IDialog, | |||
| enabled: !!id, | |||
| refetchOnWindowFocus: false, | |||
| queryFn: async () => { | |||
| const { data } = await chatService.getDialog( | |||
| { params: { dialogId: id } }, | |||
| true, | |||
| ); | |||
| return data?.data ?? ({} as IDialog); | |||
| }, | |||
| }); | |||
| return { data, loading, refetch }; | |||
| }; | |||
| @@ -2,9 +2,9 @@ import { useFetchDialogList } from '@/hooks/use-chat-request'; | |||
| import { ApplicationCard } from './application-card'; | |||
| export function ChatList() { | |||
| const { data } = useFetchDialogList(true); | |||
| const { data } = useFetchDialogList(); | |||
| return data | |||
| return data.dialogs | |||
| .slice(0, 10) | |||
| .map((x) => ( | |||
| <ApplicationCard | |||
| @@ -1,6 +1,7 @@ | |||
| import { PageHeader } from '@/components/page-header'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useFetchDialog } from '@/hooks/use-chat-request'; | |||
| import { EllipsisVertical } from 'lucide-react'; | |||
| import { AppSettings } from './app-settings'; | |||
| import { ChatBox } from './chat-box'; | |||
| @@ -8,10 +9,11 @@ import { Sessions } from './sessions'; | |||
| export default function Chat() { | |||
| const { navigateToChatList } = useNavigatePage(); | |||
| useFetchDialog(); | |||
| return ( | |||
| <section className="h-full flex flex-col"> | |||
| <PageHeader back={navigateToChatList} title="Chat app 01"> | |||
| <PageHeader> | |||
| <div className="flex items-center gap-2"> | |||
| <Button variant={'icon'} size={'icon'}> | |||
| <EllipsisVertical /> | |||
| @@ -1,8 +1,33 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useSetDialog } from '@/hooks/use-chat-request'; | |||
| import { IDialog } from '@/interfaces/database/chat'; | |||
| import { isEmpty } from 'lodash'; | |||
| import { useCallback, useState } from 'react'; | |||
| const InitialData = { | |||
| name: '', | |||
| icon: '', | |||
| language: 'English', | |||
| prompt_config: { | |||
| empty_response: '', | |||
| prologue: '你好! 我是你的助理,有什么可以帮到你的吗?', | |||
| quote: true, | |||
| keyword: false, | |||
| tts: false, | |||
| system: | |||
| '你是一个智能助手,请总结知识库的内容来回答问题,请列举知识库中的数据详细回答。当所有知识库内容都与问题无关时,你的回答必须包括“知识库中未找到您要的答案!”这句话。回答需要考虑聊天历史。\n 以下是知识库:\n {knowledge}\n 以上是知识库。', | |||
| refine_multiturn: false, | |||
| use_kg: false, | |||
| reasoning: false, | |||
| parameters: [{ key: 'knowledge', optional: false }], | |||
| }, | |||
| llm_id: '', | |||
| llm_setting: {}, | |||
| similarity_threshold: 0.2, | |||
| vector_similarity_weight: 0.30000000000000004, | |||
| top_n: 8, | |||
| }; | |||
| export const useRenameChat = () => { | |||
| const [chat, setChat] = useState<IDialog>({} as IDialog); | |||
| const { | |||
| @@ -14,10 +39,11 @@ export const useRenameChat = () => { | |||
| const onChatRenameOk = useCallback( | |||
| async (name: string) => { | |||
| const ret = await setDialog({ | |||
| ...chat, | |||
| const nextChat = { | |||
| ...(isEmpty(chat) ? InitialData : chat), | |||
| name, | |||
| }); | |||
| }; | |||
| const ret = await setDialog(nextChat); | |||
| if (ret === 0) { | |||
| hideChatRenameModal(); | |||
| @@ -27,19 +53,26 @@ export const useRenameChat = () => { | |||
| ); | |||
| const handleShowChatRenameModal = useCallback( | |||
| async (record: IDialog) => { | |||
| setChat(record); | |||
| (record?: IDialog) => { | |||
| if (record) { | |||
| setChat(record); | |||
| } | |||
| showChatRenameModal(); | |||
| }, | |||
| [showChatRenameModal], | |||
| ); | |||
| const handleHideModal = useCallback(() => { | |||
| hideChatRenameModal(); | |||
| setChat({} as IDialog); | |||
| }, [hideChatRenameModal]); | |||
| return { | |||
| chatRenameLoading: loading, | |||
| initialChatName: chat?.name, | |||
| onChatRenameOk, | |||
| chatRenameVisible, | |||
| hideChatRenameModal, | |||
| hideChatRenameModal: handleHideModal, | |||
| showChatRenameModal: handleShowChatRenameModal, | |||
| }; | |||
| }; | |||
| @@ -11,7 +11,7 @@ import { ChatCard } from './chat-card'; | |||
| import { useRenameChat } from './hooks/use-rename-chat'; | |||
| export default function ChatList() { | |||
| const { data: chatList, setPagination, pagination } = useFetchDialogList(); | |||
| const { data, setPagination, pagination } = useFetchDialogList(); | |||
| const { t } = useTranslation(); | |||
| const { | |||
| initialChatName, | |||
| @@ -29,11 +29,15 @@ export default function ChatList() { | |||
| [setPagination], | |||
| ); | |||
| const handleShowCreateModal = useCallback(() => { | |||
| showChatRenameModal(); | |||
| }, [showChatRenameModal]); | |||
| return ( | |||
| <section className="flex flex-col w-full flex-1"> | |||
| <div className="px-8 pt-8"> | |||
| <ListFilterBar title="Chat apps"> | |||
| <Button> | |||
| <Button onClick={handleShowCreateModal}> | |||
| <Plus className="size-2.5" /> | |||
| {t('chat.createChat')} | |||
| </Button> | |||
| @@ -41,7 +45,7 @@ export default function ChatList() { | |||
| </div> | |||
| <div className="flex-1 overflow-auto"> | |||
| <div className="flex flex-wrap gap-4 px-8"> | |||
| {chatList.map((x) => { | |||
| {data.dialogs.map((x) => { | |||
| return ( | |||
| <ChatCard | |||
| key={x.id} | |||
| @@ -65,6 +69,7 @@ export default function ChatList() { | |||
| onOk={onChatRenameOk} | |||
| initialName={initialChatName} | |||
| loading={chatRenameLoading} | |||
| title={initialChatName || t('chat.createChat')} | |||
| ></RenameDialog> | |||
| )} | |||
| </section> | |||
| @@ -0,0 +1,133 @@ | |||
| import api from '@/utils/api'; | |||
| import { registerNextServer } from '@/utils/register-server'; | |||
| const { | |||
| getDialog, | |||
| setDialog, | |||
| listDialog, | |||
| removeDialog, | |||
| getConversation, | |||
| getConversationSSE, | |||
| setConversation, | |||
| completeConversation, | |||
| listConversation, | |||
| removeConversation, | |||
| createToken, | |||
| listToken, | |||
| removeToken, | |||
| getStats, | |||
| createExternalConversation, | |||
| getExternalConversation, | |||
| completeExternalConversation, | |||
| uploadAndParseExternal, | |||
| deleteMessage, | |||
| thumbup, | |||
| tts, | |||
| ask, | |||
| mindmap, | |||
| getRelatedQuestions, | |||
| listNextDialog, | |||
| } = api; | |||
| const methods = { | |||
| getDialog: { | |||
| url: getDialog, | |||
| method: 'get', | |||
| }, | |||
| setDialog: { | |||
| url: setDialog, | |||
| method: 'post', | |||
| }, | |||
| removeDialog: { | |||
| url: removeDialog, | |||
| method: 'post', | |||
| }, | |||
| listDialog: { | |||
| url: listNextDialog, | |||
| method: 'post', | |||
| }, | |||
| listConversation: { | |||
| url: listConversation, | |||
| method: 'get', | |||
| }, | |||
| getConversation: { | |||
| url: getConversation, | |||
| method: 'get', | |||
| }, | |||
| getConversationSSE: { | |||
| url: getConversationSSE, | |||
| method: 'get', | |||
| }, | |||
| setConversation: { | |||
| url: setConversation, | |||
| method: 'post', | |||
| }, | |||
| completeConversation: { | |||
| url: completeConversation, | |||
| method: 'post', | |||
| }, | |||
| removeConversation: { | |||
| url: removeConversation, | |||
| method: 'post', | |||
| }, | |||
| createToken: { | |||
| url: createToken, | |||
| method: 'post', | |||
| }, | |||
| listToken: { | |||
| url: listToken, | |||
| method: 'get', | |||
| }, | |||
| removeToken: { | |||
| url: removeToken, | |||
| method: 'post', | |||
| }, | |||
| getStats: { | |||
| url: getStats, | |||
| method: 'get', | |||
| }, | |||
| createExternalConversation: { | |||
| url: createExternalConversation, | |||
| method: 'get', | |||
| }, | |||
| getExternalConversation: { | |||
| url: getExternalConversation, | |||
| method: 'get', | |||
| }, | |||
| completeExternalConversation: { | |||
| url: completeExternalConversation, | |||
| method: 'post', | |||
| }, | |||
| uploadAndParseExternal: { | |||
| url: uploadAndParseExternal, | |||
| method: 'post', | |||
| }, | |||
| deleteMessage: { | |||
| url: deleteMessage, | |||
| method: 'post', | |||
| }, | |||
| thumbup: { | |||
| url: thumbup, | |||
| method: 'post', | |||
| }, | |||
| tts: { | |||
| url: tts, | |||
| method: 'post', | |||
| }, | |||
| ask: { | |||
| url: ask, | |||
| method: 'post', | |||
| }, | |||
| getMindMap: { | |||
| url: mindmap, | |||
| method: 'post', | |||
| }, | |||
| getRelatedQuestions: { | |||
| url: getRelatedQuestions, | |||
| method: 'post', | |||
| }, | |||
| } as const; | |||
| const chatService = registerNextServer<keyof typeof methods>(methods); | |||
| export default chatService; | |||
| @@ -108,6 +108,9 @@ export default { | |||
| completeExternalConversation: `${api_host}/api/completion`, | |||
| uploadAndParseExternal: `${api_host}/api/document/upload_and_parse`, | |||
| // next chat | |||
| listNextDialog: `${api_host}/dialog/next`, | |||
| // file manager | |||
| listFile: `${api_host}/file/list`, | |||
| uploadFile: `${api_host}/file/upload`, | |||