Browse Source

Feat: Search conversation by name #3221 (#9283)

### What problem does this PR solve?

Feat: Search conversation by name #3221
### Type of change


- [x] New Feature (non-breaking change which adds functionality)
tags/v0.20.1
balibabu 2 months ago
parent
commit
581a54fbbb
No account linked to committer's email address

+ 244
- 6
web/src/hooks/use-chat-request.ts View File

@@ -1,9 +1,15 @@
import message from '@/components/ui/message';
import { ChatSearchParams } from '@/constants/chat';
import { IDialog } from '@/interfaces/database/chat';
import { IConversation, IDialog } from '@/interfaces/database/chat';
import { IAskRequestBody } from '@/interfaces/request/chat';
import { IClientConversation } from '@/pages/next-chats/chat/interface';
import { useGetSharedChatSearchParams } from '@/pages/next-chats/hooks/use-send-shared-message';
import { isConversationIdExist } from '@/pages/next-chats/utils';
import chatService from '@/services/next-chat-service ';
import { buildMessageListWithUuid, getConversationId } from '@/utils/chat';
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { has } from 'lodash';
import { useCallback, useMemo } from 'react';
import { useTranslation } from 'react-i18next';
import { useParams, useSearchParams } from 'umi';
@@ -17,6 +23,13 @@ export const enum ChatApiAction {
RemoveDialog = 'removeDialog',
SetDialog = 'setDialog',
FetchDialog = 'fetchDialog',
FetchConversationList = 'fetchConversationList',
FetchConversation = 'fetchConversation',
UpdateConversation = 'updateConversation',
RemoveConversation = 'removeConversation',
DeleteMessage = 'deleteMessage',
FetchMindMap = 'fetchMindMap',
FetchRelatedQuestions = 'fetchRelatedQuestions',
}

export const useGetChatSearchParams = () => {
@@ -74,11 +87,17 @@ export const useFetchDialogList = () => {
gcTime: 0,
refetchOnWindowFocus: false,
queryFn: async () => {
const { data } = await chatService.listDialog({
keywords: debouncedSearchString,
page_size: pagination.pageSize,
page: pagination.current,
});
const { data } = await chatService.listDialog(
{
params: {
keywords: debouncedSearchString,
page_size: pagination.pageSize,
page: pagination.current,
},
data: {},
},
true,
);

return data?.data ?? { dialogs: [], total: 0 };
},
@@ -180,3 +199,222 @@ export const useFetchDialog = () => {

return { data, loading, refetch };
};

//#region Conversation

export const useClickConversationCard = () => {
const [currentQueryParameters, setSearchParams] = useSearchParams();
const newQueryParameters: URLSearchParams = useMemo(
() => new URLSearchParams(currentQueryParameters.toString()),
[currentQueryParameters],
);

const handleClickConversation = useCallback(
(conversationId: string, isNew: string) => {
newQueryParameters.set(ChatSearchParams.ConversationId, conversationId);
newQueryParameters.set(ChatSearchParams.isNew, isNew);
setSearchParams(newQueryParameters);
},
[setSearchParams, newQueryParameters],
);

return { handleClickConversation };
};

export const useFetchConversationList = () => {
const { id } = useParams();
const { handleClickConversation } = useClickConversationCard();
const {
data,
isFetching: loading,
refetch,
} = useQuery<IConversation[]>({
queryKey: [ChatApiAction.FetchConversationList, id],
initialData: [],
gcTime: 0,
refetchOnWindowFocus: false,
enabled: !!id,
queryFn: async () => {
const { data } = await chatService.listConversation(
{ params: { dialog_id: id } },
true,
);
if (data.code === 0) {
if (data.data.length > 0) {
handleClickConversation(data.data[0].id, '');
} else {
handleClickConversation('', '');
}
}
return data?.data;
},
});

return { data, loading, refetch };
};

export const useFetchConversation = () => {
const { isNew, conversationId } = useGetChatSearchParams();
const { sharedId } = useGetSharedChatSearchParams();
const {
data,
isFetching: loading,
refetch,
} = useQuery<IClientConversation>({
queryKey: [ChatApiAction.FetchConversation, conversationId],
initialData: {} as IClientConversation,
// enabled: isConversationIdExist(conversationId),
gcTime: 0,
refetchOnWindowFocus: false,
queryFn: async () => {
if (
isNew !== 'true' &&
isConversationIdExist(sharedId || conversationId)
) {
const { data } = await chatService.getConversation({
conversationId: conversationId || sharedId,
});

const conversation = data?.data ?? {};

const messageList = buildMessageListWithUuid(conversation?.message);

return { ...conversation, message: messageList };
}
return { message: [] };
},
});

return { data, loading, refetch };
};

export const useUpdateConversation = () => {
const { t } = useTranslation();
const queryClient = useQueryClient();
const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.UpdateConversation],
mutationFn: async (params: Record<string, any>) => {
const { data } = await chatService.setConversation({
...params,
conversation_id: params.conversation_id
? params.conversation_id
: getConversationId(),
});
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [ChatApiAction.FetchConversationList],
});
message.success(t(`message.modified`));
}
return data;
},
});

return { data, loading, updateConversation: mutateAsync };
};

export const useRemoveConversation = () => {
const queryClient = useQueryClient();
const { dialogId } = useGetChatSearchParams();

const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.RemoveConversation],
mutationFn: async (conversationIds: string[]) => {
const { data } = await chatService.removeConversation({
conversationIds,
dialogId,
});
if (data.code === 0) {
queryClient.invalidateQueries({
queryKey: [ChatApiAction.FetchConversationList],
});
}
return data.code;
},
});

return { data, loading, removeConversation: mutateAsync };
};

export const useDeleteMessage = () => {
const { conversationId } = useGetChatSearchParams();
const { t } = useTranslation();

const {
data,
isPending: loading,
mutateAsync,
} = useMutation({
mutationKey: [ChatApiAction.DeleteMessage],
mutationFn: async (messageId: string) => {
const { data } = await chatService.deleteMessage({
messageId,
conversationId,
});

if (data.code === 0) {
message.success(t(`message.deleted`));
}

return data.code;
},
});

return { data, loading, deleteMessage: mutateAsync };
};

//#endregion

//#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.getMindMap(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<string[]> => {
const { data } = await chatService.getRelatedQuestions({ question });

return data?.data ?? [];
},
});

return { data, loading, fetchRelatedQuestions: mutateAsync };
};
//#endregion

+ 24
- 11
web/src/pages/next-chats/chat/index.tsx View File

@@ -1,27 +1,40 @@
import { PageHeader } from '@/components/page-header';
import { Button } from '@/components/ui/button';
import {
Breadcrumb,
BreadcrumbItem,
BreadcrumbLink,
BreadcrumbList,
BreadcrumbPage,
BreadcrumbSeparator,
} from '@/components/ui/breadcrumb';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchDialog } from '@/hooks/use-chat-request';
import { EllipsisVertical } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { AppSettings } from './app-settings';
import { ChatBox } from './chat-box';
import { Sessions } from './sessions';

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

return (
<section className="h-full flex flex-col">
<PageHeader>
<div className="flex items-center gap-2">
<Button variant={'icon'} size={'icon'}>
<EllipsisVertical />
</Button>
<Button variant={'tertiary'} size={'sm'}>
Publish
</Button>
</div>
<Breadcrumb>
<BreadcrumbList>
<BreadcrumbItem>
<BreadcrumbLink onClick={navigateToChatList}>
{t('chat.chat')}
</BreadcrumbLink>
</BreadcrumbItem>
<BreadcrumbSeparator />
<BreadcrumbItem>
<BreadcrumbPage>{data.name}</BreadcrumbPage>
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
</PageHeader>
<div className="flex flex-1">
<Sessions></Sessions>

+ 33
- 0
web/src/pages/next-chats/chat/interface.ts View File

@@ -0,0 +1,33 @@
import { IConversation, IReference, Message } from '@/interfaces/database/chat';
import { FormInstance } from 'antd';

export interface ISegmentedContentProps {
show: boolean;
form: FormInstance;
setHasError: (hasError: boolean) => void;
}

export interface IVariable {
temperature: number;
top_p: number;
frequency_penalty: number;
presence_penalty: number;
max_tokens: number;
}

export interface VariableTableDataType {
key: string;
variable: string;
optional: boolean;
}

export type IPromptConfigParameters = Omit<VariableTableDataType, 'variable'>;

export interface IMessage extends Message {
id: string;
reference?: IReference; // the latest news has reference
}

export interface IClientConversation extends IConversation {
message: IMessage[];
}

+ 10
- 11
web/src/pages/next-chats/chat/sessions.tsx View File

@@ -1,15 +1,15 @@
import { MoreButton } from '@/components/more-button';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { EllipsisVertical, Plus } from 'lucide-react';
import { useFetchConversationList } from '@/hooks/use-chat-request';
import { Plus } from 'lucide-react';

function SessionCard() {
return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
<CardContent className="px-3 py-2 flex justify-between items-center">
<Card>
<CardContent className="px-3 py-2 flex justify-between items-center group">
xxx
<Button variant={'icon'} size={'icon'}>
<EllipsisVertical />
</Button>
<MoreButton></MoreButton>
</CardContent>
</Card>
);
@@ -17,20 +17,19 @@ function SessionCard() {

export function Sessions() {
const sessionList = new Array(10).fill(1);
const {} = useFetchConversationList();

return (
<section className="p-6 w-[400px] max-w-[20%]">
<div className="flex justify-between items-center mb-4">
<span className="text-colors-text-neutral-strong text-2xl font-bold">
Sessions
</span>
<Button variant={'icon'} size={'icon'}>
<span className="text-xl font-bold">Conversations</span>
<Button variant={'ghost'}>
<Plus></Plus>
</Button>
</div>
<div className="space-y-4">
{sessionList.map((x) => (
<SessionCard key={x.id}></SessionCard>
<SessionCard key={x}></SessionCard>
))}
</div>
</section>

+ 7
- 2
web/src/pages/next-chats/index.tsx View File

@@ -11,7 +11,8 @@ import { ChatCard } from './chat-card';
import { useRenameChat } from './hooks/use-rename-chat';

export default function ChatList() {
const { data, setPagination, pagination } = useFetchDialogList();
const { data, setPagination, pagination, handleInputChange, searchString } =
useFetchDialogList();
const { t } = useTranslation();
const {
initialChatName,
@@ -36,7 +37,11 @@ export default function ChatList() {
return (
<section className="flex flex-col w-full flex-1">
<div className="px-8 pt-8">
<ListFilterBar title="Chat apps">
<ListFilterBar
title="Chat apps"
onSearchChange={handleInputChange}
searchString={searchString}
>
<Button onClick={handleShowCreateModal}>
<Plus className="size-2.5" />
{t('chat.createChat')}

Loading…
Cancel
Save