Feat: Delete or filter conversations #3221 (#9491)

### What problem does this PR solve?

Feat: Delete or filter conversations #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-08-15 12:05:27 +08:00
committed by GitHub
parent 2114e966d8
commit 30ae78755b
8 changed files with 118 additions and 17 deletions

View File

@ -0,0 +1,14 @@
import { useCallback, useState } from 'react';
export const useHandleSearchStrChange = () => {
const [searchString, setSearchString] = useState('');
const handleInputChange = useCallback(
(e: React.ChangeEvent<HTMLInputElement | HTMLTextAreaElement>) => {
const value = e.target.value;
setSearchString(value);
},
[],
);
return { handleInputChange, searchString };
};

View File

@ -17,6 +17,7 @@ import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
import { useHandleSearchStrChange } from './logic-hooks/use-change-search';
export const enum ChatApiAction {
FetchDialogList = 'fetchDialogList',
@ -229,6 +230,9 @@ export const useClickConversationCard = () => {
export const useFetchConversationList = () => {
const { id } = useParams();
const { handleClickConversation } = useClickConversationCard();
const { searchString, handleInputChange } = useHandleSearchStrChange();
const {
data,
isFetching: loading,
@ -239,6 +243,11 @@ export const useFetchConversationList = () => {
gcTime: 0,
refetchOnWindowFocus: false,
enabled: !!id,
select(data) {
return searchString
? data.filter((x) => x.name.includes(searchString))
: data;
},
queryFn: async () => {
const { data } = await chatService.listConversation(
{ params: { dialog_id: id } },
@ -255,7 +264,7 @@ export const useFetchConversationList = () => {
},
});
return { data, loading, refetch };
return { data, loading, refetch, searchString, handleInputChange };
};
export const useFetchConversation = () => {

View File

@ -75,7 +75,7 @@ export function ChatSettings({ switchSettingVisible }: ChatSettingsProps) {
</div>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit, onInvalid)}>
<section className="space-y-6 overflow-auto max-h-[87vh] pr-4">
<section className="space-y-6 overflow-auto max-h-[85vh] pr-4">
<ChatBasicSetting></ChatBasicSetting>
<Separator />
<ChatPromptEngine></ChatPromptEngine>

View File

@ -0,0 +1,48 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useRemoveConversation } from '@/hooks/use-chat-request';
import { IConversation } from '@/interfaces/database/chat';
import { Trash2 } from 'lucide-react';
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
export function ConversationDropdown({
children,
conversation,
}: PropsWithChildren & {
conversation: IConversation;
}) {
const { t } = useTranslation();
const { removeConversation } = useRemoveConversation();
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
removeConversation([conversation.id]);
}, [conversation.id, removeConversation]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<ConfirmDeleteDialog onOk={handleDelete}>
<DropdownMenuItem
className="text-state-error"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
{t('common.delete')} <Trash2 />
</DropdownMenuItem>
</ConfirmDeleteDialog>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -18,13 +18,12 @@ import {
} from '@/hooks/use-chat-request';
import { cn } from '@/lib/utils';
import { isEmpty } from 'lodash';
import { ArrowUpRight, LogOut } from 'lucide-react';
import { ArrowUpRight, LogOut, Send } from 'lucide-react';
import { useTranslation } from 'react-i18next';
import { useHandleClickConversationCard } from '../hooks/use-click-card';
import { ChatSettings } from './app-settings/chat-settings';
import { MultipleChatBox } from './chat-box/multiple-chat-box';
import { SingleChatBox } from './chat-box/single-chat-box';
import { LLMSelectForm } from './llm-select-form';
import { Sessions } from './sessions';
import { useAddChatBox } from './use-add-box';
import { useSwitchDebugMode } from './use-switch-debug-mode';
@ -88,6 +87,10 @@ export default function Chat() {
</BreadcrumbItem>
</BreadcrumbList>
</Breadcrumb>
<Button>
<Send />
{t('common.embedIntoSite')}
</Button>
</PageHeader>
<div className="flex flex-1 min-h-0">
<Sessions
@ -103,10 +106,7 @@ export default function Chat() {
className={cn('p-5', { 'border-b': hasSingleChatBox })}
>
<CardTitle className="flex justify-between items-center text-base">
<div className="flex gap-3 items-center">
{conversation.name}
<LLMSelectForm></LLMSelectForm>
</div>
<div>{conversation.name}</div>
<Button
variant={'ghost'}

View File

@ -1,12 +1,16 @@
import { LargeModelFormFieldWithoutFilter } from '@/components/large-model-form-field';
import { LlmSettingSchema } from '@/components/llm-setting-items/next';
import { Form } from '@/components/ui/form';
import { useFetchDialog } from '@/hooks/use-chat-request';
import { zodResolver } from '@hookform/resolvers/zod';
import { isEmpty } from 'lodash';
import { useEffect } from 'react';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
export function LLMSelectForm() {
const FormSchema = z.object(LlmSettingSchema);
const { data } = useFetchDialog();
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
@ -15,6 +19,15 @@ export function LLMSelectForm() {
},
});
// const values = useWatch({ control: form.control, name: ['llm_id'] });
useEffect(() => {
if (!isEmpty(data)) {
form.reset({ llm_id: data.llm_id, ...data.llm_setting });
}
form.reset(data);
}, [data, form]);
return (
<Form {...form}>
<LargeModelFormFieldWithoutFilter></LargeModelFormFieldWithoutFilter>

View File

@ -10,9 +10,10 @@ import {
} from '@/hooks/use-chat-request';
import { cn } from '@/lib/utils';
import { PanelLeftClose, PanelRightClose, Plus } from 'lucide-react';
import { useCallback, useState } from 'react';
import { useCallback } from 'react';
import { useHandleClickConversationCard } from '../hooks/use-click-card';
import { useSelectDerivedConversationList } from '../hooks/use-select-conversation-list';
import { ConversationDropdown } from './conversation-dropdown';
type SessionProps = Pick<
ReturnType<typeof useHandleClickConversationCard>,
@ -23,11 +24,14 @@ export function Sessions({
handleConversationCardClick,
switchSettingVisible,
}: SessionProps) {
const { list: conversationList, addTemporaryConversation } =
useSelectDerivedConversationList();
const {
list: conversationList,
addTemporaryConversation,
handleInputChange,
searchString,
} = useSelectDerivedConversationList();
const { data } = useFetchDialog();
const { visible, switchVisible } = useSetModalState(true);
const [searchStr, setSearchStr] = useState('');
const handleCardClick = useCallback(
(conversationId: string, isNew: boolean) => () => {
@ -71,8 +75,8 @@ export function Sessions({
</div>
<div className="pb-4">
<SearchInput
onChange={(e) => setSearchStr(e.target.value)}
value={searchStr}
onChange={handleInputChange}
value={searchString}
></SearchInput>
</div>
<div className="space-y-4 flex-1 overflow-auto">
@ -86,7 +90,9 @@ export function Sessions({
>
<CardContent className="px-3 py-2 flex justify-between items-center group">
{x.name}
<MoreButton></MoreButton>
<ConversationDropdown conversation={x}>
<MoreButton></MoreButton>
</ConversationDropdown>
</CardContent>
</Card>
))}

View File

@ -43,7 +43,12 @@ export const useSelectDerivedConversationList = () => {
const { t } = useTranslate('chat');
const [list, setList] = useState<Array<IConversation>>([]);
const { data: conversationList, loading } = useFetchConversationList();
const {
data: conversationList,
loading,
handleInputChange,
searchString,
} = useFetchConversationList();
const { id: dialogId } = useParams();
const { setNewConversationRouteParams } = useSetNewConversationRouteParams();
const prologue = useFindPrologueFromDialogList();
@ -81,5 +86,11 @@ export const useSelectDerivedConversationList = () => {
setList([...conversationList]);
}, [conversationList]);
return { list, addTemporaryConversation, loading };
return {
list,
addTemporaryConversation,
loading,
handleInputChange,
searchString,
};
};