### What problem does this PR solve? Feat: Rename a dataset #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)tags/v0.18.0
| @@ -78,13 +78,15 @@ const DropdownMenuItem = React.forwardRef< | |||
| React.ElementRef<typeof DropdownMenuPrimitive.Item>, | |||
| React.ComponentPropsWithoutRef<typeof DropdownMenuPrimitive.Item> & { | |||
| inset?: boolean; | |||
| justifyBetween?: boolean; | |||
| } | |||
| >(({ className, inset, ...props }, ref) => ( | |||
| >(({ className, inset, justifyBetween = true, ...props }, ref) => ( | |||
| <DropdownMenuPrimitive.Item | |||
| ref={ref} | |||
| className={cn( | |||
| 'relative flex cursor-default select-none items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-none transition-colors focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 [&_svg]:pointer-events-none [&_svg]:size-4 [&_svg]:shrink-0', | |||
| inset && 'pl-8', | |||
| justifyBetween && 'flex justify-between', | |||
| className, | |||
| )} | |||
| {...props} | |||
| @@ -180,7 +180,7 @@ export const useDeleteKnowledge = () => { | |||
| //#region knowledge configuration | |||
| export const useUpdateKnowledge = () => { | |||
| export const useUpdateKnowledge = (shouldFetchList = false) => { | |||
| const knowledgeBaseId = useKnowledgeBaseId(); | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| @@ -191,12 +191,18 @@ export const useUpdateKnowledge = () => { | |||
| mutationKey: ['saveKnowledge'], | |||
| mutationFn: async (params: Record<string, any>) => { | |||
| const { data = {} } = await kbService.updateKb({ | |||
| kb_id: knowledgeBaseId, | |||
| kb_id: params?.kb_id ? params?.kb_id : knowledgeBaseId, | |||
| ...params, | |||
| }); | |||
| if (data.code === 0) { | |||
| message.success(i18n.t(`message.updated`)); | |||
| queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] }); | |||
| if (shouldFetchList) { | |||
| queryClient.invalidateQueries({ | |||
| queryKey: ['infiniteFetchKnowledgeList'], | |||
| }); | |||
| } else { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] }); | |||
| } | |||
| } | |||
| return data; | |||
| }, | |||
| @@ -23,7 +23,7 @@ import { z } from 'zod'; | |||
| const FormId = 'dataset-creating-form'; | |||
| export function InputForm() { | |||
| export function InputForm({ onOk }: IModalProps<any>) { | |||
| const { t } = useTranslation(); | |||
| const FormSchema = z.object({ | |||
| @@ -43,7 +43,7 @@ export function InputForm() { | |||
| }); | |||
| function onSubmit(data: z.infer<typeof FormSchema>) { | |||
| console.log('🚀 ~ onSubmit ~ data:', data); | |||
| onOk?.(data.name); | |||
| } | |||
| return ( | |||
| @@ -74,7 +74,7 @@ export function InputForm() { | |||
| ); | |||
| } | |||
| export function DatasetCreatingDialog({ hideModal }: IModalProps<any>) { | |||
| export function DatasetCreatingDialog({ hideModal, onOk }: IModalProps<any>) { | |||
| const { t } = useTranslation(); | |||
| return ( | |||
| @@ -83,7 +83,7 @@ export function DatasetCreatingDialog({ hideModal }: IModalProps<any>) { | |||
| <DialogHeader> | |||
| <DialogTitle>{t('knowledgeList.createKnowledgeBase')}</DialogTitle> | |||
| </DialogHeader> | |||
| <InputForm></InputForm> | |||
| <InputForm onOk={onOk}></InputForm> | |||
| <DialogFooter> | |||
| <Button type="submit" variant={'tertiary'} size={'sm'} form={FormId}> | |||
| {t('common.save')} | |||
| @@ -0,0 +1,54 @@ | |||
| import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; | |||
| import { | |||
| DropdownMenu, | |||
| DropdownMenuContent, | |||
| DropdownMenuItem, | |||
| DropdownMenuSeparator, | |||
| DropdownMenuTrigger, | |||
| } from '@/components/ui/dropdown-menu'; | |||
| import { useDeleteKnowledge } from '@/hooks/knowledge-hooks'; | |||
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||
| import { PenLine, Trash2 } from 'lucide-react'; | |||
| import { PropsWithChildren, useCallback } from 'react'; | |||
| import { useTranslation } from 'react-i18next'; | |||
| import { useRenameDataset } from './use-rename-dataset'; | |||
| export function DatasetDropdown({ | |||
| children, | |||
| showDatasetRenameModal, | |||
| dataset, | |||
| }: PropsWithChildren & | |||
| Pick<ReturnType<typeof useRenameDataset>, 'showDatasetRenameModal'> & { | |||
| dataset: IKnowledge; | |||
| }) { | |||
| const { t } = useTranslation(); | |||
| const { deleteKnowledge } = useDeleteKnowledge(); | |||
| const handleShowDatasetRenameModal = useCallback(() => { | |||
| showDatasetRenameModal(dataset); | |||
| }, [dataset, showDatasetRenameModal]); | |||
| const handleDelete = useCallback(() => { | |||
| deleteKnowledge(dataset.id); | |||
| }, [dataset.id, deleteKnowledge]); | |||
| return ( | |||
| <DropdownMenu> | |||
| <DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger> | |||
| <DropdownMenuContent> | |||
| <DropdownMenuItem onClick={handleShowDatasetRenameModal}> | |||
| {t('common.rename')} <PenLine /> | |||
| </DropdownMenuItem> | |||
| <DropdownMenuSeparator /> | |||
| <ConfirmDeleteDialog onOk={handleDelete}> | |||
| <DropdownMenuItem | |||
| className="text-text-delete-red" | |||
| onSelect={(e) => e.preventDefault()} | |||
| > | |||
| {t('common.delete')} <Trash2 /> | |||
| </DropdownMenuItem> | |||
| </ConfirmDeleteDialog> | |||
| </DropdownMenuContent> | |||
| </DropdownMenu> | |||
| ); | |||
| } | |||
| @@ -1,8 +1,7 @@ | |||
| import { KnowledgeRouteKey } from '@/constants/knowledge'; | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useCreateKnowledge } from '@/hooks/knowledge-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useNavigate } from 'umi'; | |||
| export const useSearchKnowledge = () => { | |||
| const [searchString, setSearchString] = useState<string>(''); | |||
| @@ -19,7 +18,7 @@ export const useSearchKnowledge = () => { | |||
| export const useSaveKnowledge = () => { | |||
| const { visible: visible, hideModal, showModal } = useSetModalState(); | |||
| const { loading, createKnowledge } = useCreateKnowledge(); | |||
| const navigate = useNavigate(); | |||
| const { navigateToDataset } = useNavigatePage(); | |||
| const onCreateOk = useCallback( | |||
| async (name: string) => { | |||
| @@ -29,12 +28,10 @@ export const useSaveKnowledge = () => { | |||
| if (ret?.code === 0) { | |||
| hideModal(); | |||
| navigate( | |||
| `/knowledge/${KnowledgeRouteKey.Configuration}?id=${ret.data.kb_id}`, | |||
| ); | |||
| navigateToDataset(ret.data.kb_id)(); | |||
| } | |||
| }, | |||
| [createKnowledge, hideModal, navigate], | |||
| [createKnowledge, hideModal, navigateToDataset], | |||
| ); | |||
| return { | |||
| @@ -1,5 +1,5 @@ | |||
| import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog'; | |||
| import ListFilterBar from '@/components/list-filter-bar'; | |||
| import { RenameDialog } from '@/components/rename-dialog'; | |||
| import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; | |||
| import { Button } from '@/components/ui/button'; | |||
| import { Card, CardContent } from '@/components/ui/card'; | |||
| @@ -7,10 +7,12 @@ import { useInfiniteFetchKnowledgeList } from '@/hooks/knowledge-hooks'; | |||
| import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks'; | |||
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||
| import { formatDate } from '@/utils/date'; | |||
| import { ChevronRight, Plus, Trash2 } from 'lucide-react'; | |||
| import { ChevronRight, Ellipsis, Plus } from 'lucide-react'; | |||
| import { useMemo } from 'react'; | |||
| import { DatasetCreatingDialog } from './dataset-creating-dialog'; | |||
| import { DatasetDropdown } from './dataset-dropdown'; | |||
| import { useSaveKnowledge } from './hooks'; | |||
| import { useRenameDataset } from './use-rename-dataset'; | |||
| export default function Datasets() { | |||
| const { | |||
| @@ -41,6 +43,15 @@ export default function Datasets() { | |||
| return data?.pages.at(-1).total ?? 0; | |||
| }, [data?.pages]); | |||
| const { | |||
| datasetRenameLoading, | |||
| initialDatasetName, | |||
| onDatasetRenameOk, | |||
| datasetRenameVisible, | |||
| hideDatasetRenameModal, | |||
| showDatasetRenameModal, | |||
| } = useRenameDataset(); | |||
| return ( | |||
| <section className="p-8 text-foreground"> | |||
| <ListFilterBar title="Datasets" showDialog={showModal}> | |||
| @@ -59,11 +70,14 @@ export default function Datasets() { | |||
| <AvatarImage src={dataset.avatar} /> | |||
| <AvatarFallback className="rounded-lg">CN</AvatarFallback> | |||
| </Avatar> | |||
| <ConfirmDeleteDialog> | |||
| <DatasetDropdown | |||
| showDatasetRenameModal={showDatasetRenameModal} | |||
| dataset={dataset} | |||
| > | |||
| <Button variant="ghost" size="icon"> | |||
| <Trash2 /> | |||
| <Ellipsis /> | |||
| </Button> | |||
| </ConfirmDeleteDialog> | |||
| </DatasetDropdown> | |||
| </div> | |||
| <div className="flex justify-between items-end"> | |||
| <div> | |||
| @@ -92,6 +106,14 @@ export default function Datasets() { | |||
| loading={creatingLoading} | |||
| ></DatasetCreatingDialog> | |||
| )} | |||
| {datasetRenameVisible && ( | |||
| <RenameDialog | |||
| hideModal={hideDatasetRenameModal} | |||
| onOk={onDatasetRenameOk} | |||
| initialName={initialDatasetName} | |||
| loading={datasetRenameLoading} | |||
| ></RenameDialog> | |||
| )} | |||
| </section> | |||
| ); | |||
| } | |||
| @@ -0,0 +1,47 @@ | |||
| import { useSetModalState } from '@/hooks/common-hooks'; | |||
| import { useUpdateKnowledge } from '@/hooks/knowledge-hooks'; | |||
| import { IKnowledge } from '@/interfaces/database/knowledge'; | |||
| import { omit } from 'lodash'; | |||
| import { useCallback, useState } from 'react'; | |||
| export const useRenameDataset = () => { | |||
| const [dataset, setDataset] = useState<IKnowledge>({} as IKnowledge); | |||
| const { | |||
| visible: datasetRenameVisible, | |||
| hideModal: hideDatasetRenameModal, | |||
| showModal: showDatasetRenameModal, | |||
| } = useSetModalState(); | |||
| const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(true); | |||
| const onDatasetRenameOk = useCallback( | |||
| async (name: string) => { | |||
| const ret = await saveKnowledgeConfiguration({ | |||
| ...omit(dataset, ['id', 'update_time', 'nickname', 'tenant_avatar']), | |||
| kb_id: dataset.id, | |||
| name, | |||
| }); | |||
| if (ret.code === 0) { | |||
| hideDatasetRenameModal(); | |||
| } | |||
| }, | |||
| [saveKnowledgeConfiguration, dataset, hideDatasetRenameModal], | |||
| ); | |||
| const handleShowDatasetRenameModal = useCallback( | |||
| async (record: IKnowledge) => { | |||
| setDataset(record); | |||
| showDatasetRenameModal(); | |||
| }, | |||
| [showDatasetRenameModal], | |||
| ); | |||
| return { | |||
| datasetRenameLoading: loading, | |||
| initialDatasetName: dataset?.name, | |||
| onDatasetRenameOk, | |||
| datasetRenameVisible, | |||
| hideDatasetRenameModal, | |||
| showDatasetRenameModal: handleShowDatasetRenameModal, | |||
| }; | |||
| }; | |||
| @@ -40,6 +40,7 @@ module.exports = { | |||
| 'colors-text-inverse-strong': 'var(--colors-text-inverse-strong)', | |||
| 'colors-text-persist-light': 'var(--colors-text-persist-light)', | |||
| 'colors-text-inverse-weak': 'var(--colors-text-inverse-weak)', | |||
| 'text-delete-red': 'var(--text-delete-red)', | |||
| primary: { | |||
| DEFAULT: 'hsl(var(--primary))', | |||
| @@ -61,6 +61,7 @@ | |||
| --colors-text-inverse-strong: rgba(255, 255, 255, 1); | |||
| --colors-text-persist-light: rgba(255, 255, 255, 1); | |||
| --colors-text-inverse-weak: rgba(184, 181, 203, 1); | |||
| --text-delete-red: rgba(216, 73, 75, 1); | |||
| --sidebar-background: 0 0% 98%; | |||
| --sidebar-foreground: 240 5.3% 26.1%; | |||
| @@ -153,6 +154,7 @@ | |||
| --colors-text-inverse-strong: rgba(17, 16, 23, 1); | |||
| --colors-text-persist-light: rgba(255, 255, 255, 1); | |||
| --colors-text-inverse-weak: rgba(84, 80, 106, 1); | |||
| --text-delete-red: rgba(216, 73, 75, 1); | |||
| --sidebar-background: 240 5.9% 10%; | |||
| --sidebar-foreground: 240 4.8% 95.9%; | |||