Browse Source

Feat: Create a conversation #3221 (#9269)

### 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
balibabu 2 months ago
parent
commit
fb0426419e
No account linked to committer's email address

+ 42
- 30
web/src/hooks/use-chat-request.ts View File

import message from '@/components/ui/message'; import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat'; import { ChatSearchParams } from '@/constants/chat';
import { IDialog } from '@/interfaces/database/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 { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks'; import { useDebounce } from 'ahooks';
import { useCallback, useMemo } from 'react'; import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { history, useSearchParams } from 'umi';
import { useParams, useSearchParams } from 'umi';
import { import {
useGetPaginationWithRouter, useGetPaginationWithRouter,
useHandleSearchChange, useHandleSearchChange,
FetchDialogList = 'fetchDialogList', FetchDialogList = 'fetchDialogList',
RemoveDialog = 'removeDialog', RemoveDialog = 'removeDialog',
SetDialog = 'setDialog', SetDialog = 'setDialog',
FetchDialog = 'fetchDialog',
} }


export const useGetChatSearchParams = () => { export const useGetChatSearchParams = () => {
return { handleClickDialog }; return { handleClickDialog };
}; };


export const useFetchDialogList = (pureFetch = false) => {
const { handleClickDialog } = useClickDialogCard();
const { dialogId } = useGetChatSearchParams();
export const useFetchDialogList = () => {
const { searchString, handleInputChange } = useHandleSearchChange(); const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter(); const { pagination, setPagination } = useGetPaginationWithRouter();
const debouncedSearchString = useDebounce(searchString, { wait: 500 }); const debouncedSearchString = useDebounce(searchString, { wait: 500 });
data, data,
isFetching: loading, isFetching: loading,
refetch, refetch,
} = useQuery<IDialog[]>({
} = useQuery<{ dialogs: IDialog[]; total: number }>({
queryKey: [ queryKey: [
ChatApiAction.FetchDialogList, ChatApiAction.FetchDialogList,
{ {
...pagination, ...pagination,
}, },
], ],
initialData: [],
initialData: { dialogs: [], total: 0 },
gcTime: 0, gcTime: 0,
refetchOnWindowFocus: false, 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 };
}, },
}); });


mutateAsync, mutateAsync,
} = useMutation({ } = useMutation({
mutationKey: [ChatApiAction.SetDialog], mutationKey: [ChatApiAction.SetDialog],
mutationFn: async (params: IDialog) => {
mutationFn: async (params: Partial<IDialog>) => {
const { data } = await chatService.setDialog(params); const { data } = await chatService.setDialog(params);
if (data.code === 0) { if (data.code === 0) {
queryClient.invalidateQueries({ queryClient.invalidateQueries({
exact: false, exact: false,
queryKey: ['fetchDialogList'],
queryKey: [ChatApiAction.FetchDialogList],
}); });


queryClient.invalidateQueries({
queryKey: ['fetchDialog'],
});
message.success( message.success(
t(`message.${params.dialog_id ? 'modified' : 'created'}`), t(`message.${params.dialog_id ? 'modified' : 'created'}`),
); );


return { data, loading, setDialog: mutateAsync }; 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
- 2
web/src/pages/home/chat-list.tsx View File

import { ApplicationCard } from './application-card'; import { ApplicationCard } from './application-card';


export function ChatList() { export function ChatList() {
const { data } = useFetchDialogList(true);
const { data } = useFetchDialogList();


return data
return data.dialogs
.slice(0, 10) .slice(0, 10)
.map((x) => ( .map((x) => (
<ApplicationCard <ApplicationCard

+ 3
- 1
web/src/pages/next-chats/chat/index.tsx View File

import { PageHeader } from '@/components/page-header'; import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button'; import { Button } from '@/components/ui/button';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchDialog } from '@/hooks/use-chat-request';
import { EllipsisVertical } from 'lucide-react'; import { EllipsisVertical } from 'lucide-react';
import { AppSettings } from './app-settings'; import { AppSettings } from './app-settings';
import { ChatBox } from './chat-box'; import { ChatBox } from './chat-box';


export default function Chat() { export default function Chat() {
const { navigateToChatList } = useNavigatePage(); const { navigateToChatList } = useNavigatePage();
useFetchDialog();


return ( return (
<section className="h-full flex flex-col"> <section className="h-full flex flex-col">
<PageHeader back={navigateToChatList} title="Chat app 01">
<PageHeader>
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<Button variant={'icon'} size={'icon'}> <Button variant={'icon'} size={'icon'}>
<EllipsisVertical /> <EllipsisVertical />

+ 39
- 6
web/src/pages/next-chats/hooks/use-rename-chat.ts View File

import { useSetModalState } from '@/hooks/common-hooks'; import { useSetModalState } from '@/hooks/common-hooks';
import { useSetDialog } from '@/hooks/use-chat-request'; import { useSetDialog } from '@/hooks/use-chat-request';
import { IDialog } from '@/interfaces/database/chat'; import { IDialog } from '@/interfaces/database/chat';
import { isEmpty } from 'lodash';
import { useCallback, useState } from 'react'; 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 = () => { export const useRenameChat = () => {
const [chat, setChat] = useState<IDialog>({} as IDialog); const [chat, setChat] = useState<IDialog>({} as IDialog);
const { const {


const onChatRenameOk = useCallback( const onChatRenameOk = useCallback(
async (name: string) => { async (name: string) => {
const ret = await setDialog({
...chat,
const nextChat = {
...(isEmpty(chat) ? InitialData : chat),
name, name,
});
};
const ret = await setDialog(nextChat);


if (ret === 0) { if (ret === 0) {
hideChatRenameModal(); hideChatRenameModal();
); );


const handleShowChatRenameModal = useCallback( const handleShowChatRenameModal = useCallback(
async (record: IDialog) => {
setChat(record);
(record?: IDialog) => {
if (record) {
setChat(record);
}
showChatRenameModal(); showChatRenameModal();
}, },
[showChatRenameModal], [showChatRenameModal],
); );


const handleHideModal = useCallback(() => {
hideChatRenameModal();
setChat({} as IDialog);
}, [hideChatRenameModal]);

return { return {
chatRenameLoading: loading, chatRenameLoading: loading,
initialChatName: chat?.name, initialChatName: chat?.name,
onChatRenameOk, onChatRenameOk,
chatRenameVisible, chatRenameVisible,
hideChatRenameModal,
hideChatRenameModal: handleHideModal,
showChatRenameModal: handleShowChatRenameModal, showChatRenameModal: handleShowChatRenameModal,
}; };
}; };

+ 8
- 3
web/src/pages/next-chats/index.tsx View File

import { useRenameChat } from './hooks/use-rename-chat'; import { useRenameChat } from './hooks/use-rename-chat';


export default function ChatList() { export default function ChatList() {
const { data: chatList, setPagination, pagination } = useFetchDialogList();
const { data, setPagination, pagination } = useFetchDialogList();
const { t } = useTranslation(); const { t } = useTranslation();
const { const {
initialChatName, initialChatName,
[setPagination], [setPagination],
); );


const handleShowCreateModal = useCallback(() => {
showChatRenameModal();
}, [showChatRenameModal]);

return ( return (
<section className="flex flex-col w-full flex-1"> <section className="flex flex-col w-full flex-1">
<div className="px-8 pt-8"> <div className="px-8 pt-8">
<ListFilterBar title="Chat apps"> <ListFilterBar title="Chat apps">
<Button>
<Button onClick={handleShowCreateModal}>
<Plus className="size-2.5" /> <Plus className="size-2.5" />
{t('chat.createChat')} {t('chat.createChat')}
</Button> </Button>
</div> </div>
<div className="flex-1 overflow-auto"> <div className="flex-1 overflow-auto">
<div className="flex flex-wrap gap-4 px-8"> <div className="flex flex-wrap gap-4 px-8">
{chatList.map((x) => {
{data.dialogs.map((x) => {
return ( return (
<ChatCard <ChatCard
key={x.id} key={x.id}
onOk={onChatRenameOk} onOk={onChatRenameOk}
initialName={initialChatName} initialName={initialChatName}
loading={chatRenameLoading} loading={chatRenameLoading}
title={initialChatName || t('chat.createChat')}
></RenameDialog> ></RenameDialog>
)} )}
</section> </section>

+ 133
- 0
web/src/services/next-chat-service .ts View File

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;

+ 3
- 0
web/src/utils/api.ts View File

completeExternalConversation: `${api_host}/api/completion`, completeExternalConversation: `${api_host}/api/completion`,
uploadAndParseExternal: `${api_host}/api/document/upload_and_parse`, uploadAndParseExternal: `${api_host}/api/document/upload_and_parse`,


// next chat
listNextDialog: `${api_host}/dialog/next`,

// file manager // file manager
listFile: `${api_host}/file/list`, listFile: `${api_host}/file/list`,
uploadFile: `${api_host}/file/upload`, uploadFile: `${api_host}/file/upload`,

Loading…
Cancel
Save