### What problem does this PR solve? Feat: Delete or filter conversations #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.20.2
| @@ -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 }; | |||
| }; | |||
| @@ -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 = () => { | |||
| @@ -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> | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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'} | |||
| @@ -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> | |||
| @@ -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> | |||
| ))} | |||
| @@ -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, | |||
| }; | |||
| }; | |||