diff --git a/web/src/app.tsx b/web/src/app.tsx index 9371dda17..ed1a39583 100644 --- a/web/src/app.tsx +++ b/web/src/app.tsx @@ -1,13 +1,15 @@ +import { Toaster as Sonner } from '@/components/ui/sonner'; +import { Toaster } from '@/components/ui/toaster'; import i18n from '@/locales/config'; import { QueryClient, QueryClientProvider } from '@tanstack/react-query'; import { ReactQueryDevtools } from '@tanstack/react-query-devtools'; import { App, ConfigProvider, ConfigProviderProps, theme } from 'antd'; import pt_BR from 'antd/lib/locale/pt_BR'; +import deDE from 'antd/locale/de_DE'; import enUS from 'antd/locale/en_US'; import vi_VN from 'antd/locale/vi_VN'; import zhCN from 'antd/locale/zh_CN'; import zh_HK from 'antd/locale/zh_HK'; -import deDE from 'antd/locale/de_DE'; import dayjs from 'dayjs'; import advancedFormat from 'dayjs/plugin/advancedFormat'; import customParseFormat from 'dayjs/plugin/customParseFormat'; @@ -67,6 +69,8 @@ function Root({ children }: React.PropsWithChildren) { locale={locale} > {children} + + diff --git a/web/src/components/list-filter-bar/filter-popover.tsx b/web/src/components/list-filter-bar/filter-popover.tsx index 9064379eb..6baa7dee4 100644 --- a/web/src/components/list-filter-bar/filter-popover.tsx +++ b/web/src/components/list-filter-bar/filter-popover.tsx @@ -4,7 +4,7 @@ import { PopoverTrigger, } from '@/components/ui/popover'; import { zodResolver } from '@hookform/resolvers/zod'; -import { PropsWithChildren, useCallback, useEffect } from 'react'; +import { PropsWithChildren, useCallback, useEffect, useState } from 'react'; import { useForm } from 'react-hook-form'; import { ZodArray, ZodString, z } from 'zod'; @@ -24,12 +24,14 @@ export type CheckboxFormMultipleProps = { filters?: FilterCollection[]; value?: FilterValue; onChange?: FilterChange; + setOpen(open: boolean): void; }; function CheckboxFormMultiple({ filters = [], value, onChange, + setOpen, }: CheckboxFormMultipleProps) { const fieldsDict = filters?.reduce>>((pre, cur) => { pre[cur.field] = []; @@ -53,14 +55,14 @@ function CheckboxFormMultiple({ }); function onSubmit(data: z.infer) { - console.log('🚀 ~ onSubmit ~ data:', data); - // setOwnerIds(data.items); onChange?.(data); + setOpen(false); } const onReset = useCallback(() => { onChange?.(fieldsDict); - }, [fieldsDict, onChange]); + setOpen(false); + }, [fieldsDict, onChange, setOpen]); useEffect(() => { form.reset(value); @@ -148,14 +150,17 @@ export function FilterPopover({ onChange, filters, }: PropsWithChildren & CheckboxFormMultipleProps) { + const [open, setOpen] = useState(false); + return ( - + {children} diff --git a/web/src/hooks/logic-hooks/use-row-selection.ts b/web/src/hooks/logic-hooks/use-row-selection.ts new file mode 100644 index 000000000..18aff547e --- /dev/null +++ b/web/src/hooks/logic-hooks/use-row-selection.ts @@ -0,0 +1,29 @@ +import { RowSelectionState } from '@tanstack/react-table'; +import { isEmpty } from 'lodash'; +import { useMemo, useState } from 'react'; + +export function useRowSelection() { + const [rowSelection, setRowSelection] = useState({}); + + return { + rowSelection, + setRowSelection, + rowSelectionIsEmpty: isEmpty(rowSelection), + }; +} + +export type UseRowSelectionType = ReturnType; + +export function useSelectedIds>( + rowSelection: RowSelectionState, + list: T, +) { + const selectedIds = useMemo(() => { + const indexes = Object.keys(rowSelection); + return list + .filter((x, idx) => indexes.some((y) => Number(y) === idx)) + .map((x) => x.id); + }, [list, rowSelection]); + + return { selectedIds }; +} diff --git a/web/src/layouts/index.tsx b/web/src/layouts/index.tsx index e7f2f85b0..a186cb537 100644 --- a/web/src/layouts/index.tsx +++ b/web/src/layouts/index.tsx @@ -4,9 +4,6 @@ import { Outlet } from 'umi'; import '../locales/config'; import Header from './components/header'; -import { Toaster as Sonner } from '@/components/ui/sonner'; -import { Toaster } from '@/components/ui/toaster'; - import styles from './index.less'; const { Content } = Layout; @@ -32,8 +29,6 @@ const App: React.FC = () => { > - - ); diff --git a/web/src/pages/dataset/dataset/dataset-table.tsx b/web/src/pages/dataset/dataset/dataset-table.tsx index 4be3cdee9..64a2f9081 100644 --- a/web/src/pages/dataset/dataset/dataset-table.tsx +++ b/web/src/pages/dataset/dataset/dataset-table.tsx @@ -24,6 +24,7 @@ import { TableHeader, TableRow, } from '@/components/ui/table'; +import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection'; import { useFetchDocumentList } from '@/hooks/use-document-request'; import { getExtension } from '@/utils/document-util'; import { useMemo } from 'react'; @@ -36,12 +37,15 @@ import { useSaveMeta } from './use-save-meta'; export type DatasetTableProps = Pick< ReturnType, 'documents' | 'setPagination' | 'pagination' ->; +> & + Pick; export function DatasetTable({ documents, pagination, setPagination, + rowSelection, + setRowSelection, }: DatasetTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( @@ -49,7 +53,6 @@ export function DatasetTable({ ); const [columnVisibility, setColumnVisibility] = React.useState({}); - const [rowSelection, setRowSelection] = React.useState({}); const { changeParserLoading, diff --git a/web/src/pages/dataset/dataset/index.tsx b/web/src/pages/dataset/dataset/index.tsx index 33c20d4f2..ef2825457 100644 --- a/web/src/pages/dataset/dataset/index.tsx +++ b/web/src/pages/dataset/dataset/index.tsx @@ -10,6 +10,7 @@ import { DropdownMenuSeparator, DropdownMenuTrigger, } from '@/components/ui/dropdown-menu'; +import { useRowSelection } from '@/hooks/logic-hooks/use-row-selection'; import { useFetchDocumentList } from '@/hooks/use-document-request'; import { Upload } from 'lucide-react'; import { useTranslation } from 'react-i18next'; @@ -28,7 +29,7 @@ export default function Dataset() { onDocumentUploadOk, documentUploadLoading, } = useHandleUploadDocument(); - const { list } = useBulkOperateDataset(); + const { searchString, documents, @@ -48,6 +49,15 @@ export default function Dataset() { showCreateModal, } = useCreateEmptyDocument(); + const { rowSelection, rowSelectionIsEmpty, setRowSelection } = + useRowSelection(); + + const { list } = useBulkOperateDataset({ + documents, + rowSelection, + setRowSelection, + }); + return (
- + {rowSelectionIsEmpty || } {documentUploadVisible && ( & { + documents: IDocumentInfo[]; +}) { const { t } = useTranslation(); + const { selectedIds: selectedRowKeys } = useSelectedIds( + rowSelection, + documents, + ); + + const { runDocumentByIds } = useRunDocument(); + const { setDocumentStatus } = useSetDocumentStatus(); + const { removeDocument } = useRemoveDocument(); + + const runDocument = useCallback( + (run: number) => { + const nonVirtualKeys = selectedRowKeys.filter( + (x) => + !documents.some((y) => x === y.id && y.type === DocumentType.Virtual), + ); + + if (nonVirtualKeys.length === 0) { + toast.error(t('Please select a non-empty file list')); + return; + } + runDocumentByIds({ + documentIds: nonVirtualKeys, + run, + shouldDelete: false, + }); + }, + [documents, runDocumentByIds, selectedRowKeys, t], + ); + + const handleRunClick = useCallback(() => { + runDocument(1); + }, [runDocument]); + + const handleCancelClick = useCallback(() => { + runDocument(2); + }, [runDocument]); + + const onChangeStatus = useCallback( + (enabled: boolean) => { + selectedRowKeys.forEach((id) => { + setDocumentStatus({ status: enabled, documentId: id }); + }); + }, + [selectedRowKeys, setDocumentStatus], + ); + + const handleEnableClick = useCallback(() => { + onChangeStatus(true); + }, [onChangeStatus]); + + const handleDisableClick = useCallback(() => { + onChangeStatus(false); + }, [onChangeStatus]); + + const handleDelete = useCallback(() => { + const deletedKeys = selectedRowKeys.filter( + (x) => + !documents + .filter((y) => y.run === RunningStatus.RUNNING) + .some((y) => y.id === x), + ); + if (deletedKeys.length === 0) { + toast.error(t('theDocumentBeingParsedCannotBeDeleted')); + return; + } + + return removeDocument(deletedKeys); + }, [selectedRowKeys, removeDocument, documents, t]); const list = [ { id: 'enabled', label: t('knowledgeDetails.enabled'), icon: , - onClick: () => {}, + onClick: handleEnableClick, }, { id: 'disabled', label: t('knowledgeDetails.disabled'), icon: , - onClick: () => {}, + onClick: handleDisableClick, }, { id: 'run', label: t('knowledgeDetails.run'), icon: , - onClick: () => {}, + onClick: handleRunClick, }, { id: 'cancel', label: t('knowledgeDetails.cancel'), icon: , - onClick: () => {}, + onClick: handleCancelClick, }, { id: 'delete', label: t('common.delete'), icon: , - onClick: () => {}, + onClick: async () => { + const code = await handleDelete(); + if (code === 0) { + setRowSelection({}); + } + }, }, ]; diff --git a/web/src/pages/dataset/sidebar/index.tsx b/web/src/pages/dataset/sidebar/index.tsx index 7adb287dc..0d8673190 100644 --- a/web/src/pages/dataset/sidebar/index.tsx +++ b/web/src/pages/dataset/sidebar/index.tsx @@ -1,8 +1,10 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; import { Button } from '@/components/ui/button'; import { useSecondPathName } from '@/hooks/route-hook'; import { useFetchKnowledgeBaseConfiguration } from '@/hooks/use-knowledge-request'; import { cn } from '@/lib/utils'; import { Routes } from '@/routes'; +import { formatDate } from '@/utils/date'; import { Banknote, LayoutGrid, User } from 'lucide-react'; import { useHandleMenuClick } from './hooks'; @@ -16,15 +18,6 @@ const items = [ { icon: Banknote, label: 'Settings', key: Routes.DatasetSetting }, ]; -const dataset = { - id: 1, - title: 'Legal knowledge base', - files: '1,242 files', - size: '152 MB', - created: '12.02.2024', - image: 'https://github.com/shadcn.png', -}; - export function SideBar() { const pathName = useSecondPathName(); const { handleMenuClick } = useHandleMenuClick(); @@ -33,16 +26,18 @@ export function SideBar() { return (