Feat: Bind data to datasets page #3221 (#4938)

### What problem does this PR solve?

Feat: Bind data to datasets page #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-02-14 09:38:48 +08:00 committed by GitHub
parent 17fa2e9e8e
commit c1583a3e1d
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 571 additions and 256 deletions

View File

@ -0,0 +1,128 @@
import { useSelectParserList } from '@/hooks/user-setting-hooks';
import { useCallback, useEffect, useMemo, useState } from 'react';
const ParserListMap = new Map([
[
['pdf'],
[
'naive',
'resume',
'manual',
'paper',
'book',
'laws',
'presentation',
'one',
'qa',
'knowledge_graph',
],
],
[
['doc', 'docx'],
[
'naive',
'resume',
'book',
'laws',
'one',
'qa',
'manual',
'knowledge_graph',
],
],
[
['xlsx', 'xls'],
['naive', 'qa', 'table', 'one', 'knowledge_graph'],
],
[['ppt', 'pptx'], ['presentation']],
[
['jpg', 'jpeg', 'png', 'gif', 'bmp', 'tif', 'tiff', 'webp', 'svg', 'ico'],
['picture'],
],
[
['txt'],
[
'naive',
'resume',
'book',
'laws',
'one',
'qa',
'table',
'knowledge_graph',
],
],
[
['csv'],
[
'naive',
'resume',
'book',
'laws',
'one',
'qa',
'table',
'knowledge_graph',
],
],
[['md'], ['naive', 'qa', 'knowledge_graph']],
[['json'], ['naive', 'knowledge_graph']],
[['eml'], ['email']],
]);
const getParserList = (
values: string[],
parserList: Array<{
value: string;
label: string;
}>,
) => {
return parserList.filter((x) => values?.some((y) => y === x.value));
};
export const useFetchParserListOnMount = (
documentId: string,
parserId: string,
documentExtension: string,
// form: FormInstance,
) => {
const [selectedTag, setSelectedTag] = useState('');
const parserList = useSelectParserList();
// const handleChunkMethodSelectChange = useHandleChunkMethodSelectChange(form); // TODO
const nextParserList = useMemo(() => {
const key = [...ParserListMap.keys()].find((x) =>
x.some((y) => y === documentExtension),
);
if (key) {
const values = ParserListMap.get(key);
return getParserList(values ?? [], parserList);
}
return getParserList(
['naive', 'resume', 'book', 'laws', 'one', 'qa', 'table'],
parserList,
);
}, [parserList, documentExtension]);
useEffect(() => {
setSelectedTag(parserId);
}, [parserId, documentId]);
const handleChange = (tag: string) => {
// handleChunkMethodSelectChange(tag);
setSelectedTag(tag);
};
return { parserList: nextParserList, handleChange, selectedTag };
};
const hideAutoKeywords = ['qa', 'table', 'resume', 'knowledge_graph', 'tag'];
export const useShowAutoKeywords = () => {
const showAutoKeywords = useCallback((selectedTag: string) => {
return hideAutoKeywords.every((x) => selectedTag !== x);
}, []);
return showAutoKeywords;
};

View File

@ -0,0 +1,127 @@
import { Button } from '@/components/ui/button';
import {
Dialog,
DialogContent,
DialogFooter,
DialogHeader,
DialogTitle,
} from '@/components/ui/dialog';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
FormMessage,
} from '@/components/ui/form';
import { useTranslate } from '@/hooks/common-hooks';
import { IModalProps } from '@/interfaces/common';
import { IParserConfig } from '@/interfaces/database/document';
import { IChangeParserConfigRequestBody } from '@/interfaces/request/document';
import { zodResolver } from '@hookform/resolvers/zod';
import { useForm } from 'react-hook-form';
import { z } from 'zod';
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from '../ui/select';
import { useFetchParserListOnMount } from './hooks';
interface IProps
extends IModalProps<{
parserId: string;
parserConfig: IChangeParserConfigRequestBody;
}> {
loading: boolean;
parserId: string;
parserConfig: IParserConfig;
documentExtension: string;
documentId: string;
}
export function ChunkMethodDialog({
hideModal,
onOk,
parserId,
documentId,
documentExtension,
}: IProps) {
const { t } = useTranslate('knowledgeDetails');
const { parserList } = useFetchParserListOnMount(
documentId,
parserId,
documentExtension,
// form,
);
const FormSchema = z.object({
name: z
.string()
.min(1, {
message: 'namePlaceholder',
})
.trim(),
});
const form = useForm<z.infer<typeof FormSchema>>({
resolver: zodResolver(FormSchema),
defaultValues: { name: '' },
});
async function onSubmit(data: z.infer<typeof FormSchema>) {
const ret = await onOk?.();
if (ret) {
hideModal?.();
}
}
return (
<Dialog open onOpenChange={hideModal}>
<DialogContent>
<DialogHeader>
<DialogTitle>{t('chunkMethod')}</DialogTitle>
</DialogHeader>
<Form {...form}>
<form onSubmit={form.handleSubmit(onSubmit)} className="space-y-6">
<FormField
control={form.control}
name="name"
render={({ field }) => (
<FormItem>
<FormLabel>{t('name')}</FormLabel>
<FormControl>
<Select
{...field}
autoComplete="off"
onValueChange={field.onChange}
>
<FormControl>
<SelectTrigger>
<SelectValue placeholder="Select a verified email to display" />
</SelectTrigger>
</FormControl>
<SelectContent>
{parserList.map((x) => (
<SelectItem value={x.value} key={x.value}>
{x.label}
</SelectItem>
))}
</SelectContent>
</Select>
</FormControl>
<FormMessage />
</FormItem>
)}
/>
</form>
</Form>
<DialogFooter>
<Button type="submit">Save changes</Button>
</DialogFooter>
</DialogContent>
</Dialog>
);
}

View File

@ -16,6 +16,7 @@ import { UploadFile, message } from 'antd';
import { get } from 'lodash';
import { useCallback, useMemo, useState } from 'react';
import { IHighlight } from 'react-pdf-highlighter';
import { useParams } from 'umi';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
@ -61,6 +62,7 @@ export const useFetchNextDocumentList = () => {
const { knowledgeId } = useGetKnowledgeSearchParams();
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
const { id } = useParams();
const { data, isFetching: loading } = useQuery<{
docs: IDocumentInfo[];
@ -69,9 +71,10 @@ export const useFetchNextDocumentList = () => {
queryKey: ['fetchDocumentList', searchString, pagination],
initialData: { docs: [], total: 0 },
refetchInterval: 15000,
enabled: !!knowledgeId || !!id,
queryFn: async () => {
const ret = await kbService.get_document_list({
kb_id: knowledgeId,
kb_id: knowledgeId || id,
keywords: searchString,
page_size: pagination.pageSize,
page: pagination.current,

View File

@ -9,9 +9,12 @@ export const useNavigatePage = () => {
navigate(Routes.Datasets);
}, [navigate]);
const navigateToDataset = useCallback(() => {
navigate(Routes.Dataset);
}, [navigate]);
const navigateToDataset = useCallback(
(id: string) => () => {
navigate(`${Routes.Dataset}/${id}`);
},
[navigate],
);
const navigateToHome = useCallback(() => {
navigate(Routes.Home);

View File

@ -1,7 +1,6 @@
'use client';
import {
ColumnDef,
ColumnFiltersState,
SortingState,
VisibilityState,
@ -12,20 +11,10 @@ import {
getSortedRowModel,
useReactTable,
} from '@tanstack/react-table';
import { ArrowUpDown, MoreHorizontal, Pencil } from 'lucide-react';
import * as React from 'react';
import { ChunkMethodDialog } from '@/components/chunk-method-dialog';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Switch } from '@/components/ui/switch';
import {
Table,
TableBody,
@ -34,43 +23,22 @@ import {
TableHeader,
TableRow,
} from '@/components/ui/table';
import { RunningStatus } from '@/constants/knowledge';
import { useFetchNextDocumentList } from '@/hooks/document-hooks';
import { useSetSelectedRecord } from '@/hooks/logic-hooks';
import { IDocumentInfo } from '@/interfaces/database/document';
import { useTranslation } from 'react-i18next';
const data: IDocumentInfo[] = [
{
chunk_num: 1,
create_date: 'Thu, 28 Nov 2024 17:10:22 GMT',
create_time: 1732785022792,
created_by: 'b0975cb4bc3111ee9b830aef05f5e94f',
id: '990cb30ead6811efb9b9fa163e197198',
kb_id: '25a8cfbe9cd411efbc12fa163e197198',
location: 'mian.jpg',
name: 'mian.jpg',
parser_config: {
pages: [[1, 1000000]],
},
parser_id: 'picture',
process_begin_at: 'Thu, 28 Nov 2024 17:10:25 GMT',
process_duation: 8.46185,
progress: 1,
progress_msg:
'\nTask has been received.\nPage(1~100000001): Finish OCR: (用小麦粉\n金\nONGXI ...)\nPage(1~100000001): OCR results is too long to use CV LLM.\nPage(1~100000001): Finished slicing files (1 chunks in 0.34s). Start to embedding the content.\nPage(1~100000001): Finished embedding (in 0.35s)! Start to build index!\nPage(1~100000001): Indexing elapsed in 0.02s.\nPage(1~100000001): Done!',
run: RunningStatus.RUNNING,
size: 19692,
source_type: 'local',
status: '1',
thumbnail:
'/v1/document/image/25a8cfbe9cd411efbc12fa163e197198-thumbnail_990cb30ead6811efb9b9fa163e197198.png',
token_num: 115,
type: 'visual',
update_date: 'Thu, 28 Nov 2024 17:10:33 GMT',
update_time: 1732785033462,
},
];
import { getExtension } from '@/utils/document-util';
import { useMemo } from 'react';
import { useChangeDocumentParser } from './hooks';
import { useDatasetTableColumns } from './use-dataset-table-columns';
export function DatasetTable() {
const {
// searchString,
documents,
pagination,
// handleInputChange,
setPagination,
} = useFetchNextDocumentList();
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
[],
@ -78,122 +46,31 @@ export function DatasetTable() {
const [columnVisibility, setColumnVisibility] =
React.useState<VisibilityState>({});
const [rowSelection, setRowSelection] = React.useState({});
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
const { currentRecord, setRecord } = useSetSelectedRecord<IDocumentInfo>();
const {
changeParserLoading,
onChangeParserOk,
changeParserVisible,
hideChangeParserModal,
showChangeParserModal,
} = useChangeDocumentParser(currentRecord.id);
const columns = useDatasetTableColumns({
showChangeParserModal,
setCurrentRecord: setRecord,
});
const columns: ColumnDef<IDocumentInfo>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('name')}
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => (
<div className="capitalize">{row.getValue('name')}</div>
),
},
{
accessorKey: 'create_time',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('uploadDate')}
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => (
<div className="lowercase">{row.getValue('create_time')}</div>
),
},
{
accessorKey: 'parser_id',
header: t('chunkMethod'),
cell: ({ row }) => (
<div className="capitalize">{row.getValue('parser_id')}</div>
),
},
{
accessorKey: 'run',
header: t('parsingStatus'),
cell: ({ row }) => (
<Button variant="destructive" size={'sm'}>
{row.getValue('run')}
</Button>
),
},
{
id: 'actions',
header: t('action'),
enableHiding: false,
cell: ({ row }) => {
const payment = row.original;
return (
<section className="flex gap-4 items-center">
<Switch id="airplane-mode" />
<Button variant="secondary" size={'icon'}>
<Pencil />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="secondary" size={'icon'}>
<MoreHorizontal />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(payment.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</section>
);
},
},
];
const currentPagination = useMemo(() => {
return {
pageIndex: (pagination.current || 1) - 1,
pageSize: pagination.pageSize || 10,
};
}, [pagination]);
const table = useReactTable({
data,
data: documents,
columns,
onSortingChange: setSorting,
onColumnFiltersChange: setColumnFilters,
@ -203,12 +80,29 @@ export function DatasetTable() {
getFilteredRowModel: getFilteredRowModel(),
onColumnVisibilityChange: setColumnVisibility,
onRowSelectionChange: setRowSelection,
onPaginationChange: (updaterOrValue: any) => {
if (typeof updaterOrValue === 'function') {
const nextPagination = updaterOrValue(currentPagination);
setPagination({
page: nextPagination.pageIndex + 1,
pageSize: nextPagination.pageSize,
});
} else {
setPagination({
page: updaterOrValue.pageIndex,
pageSize: updaterOrValue.pageSize,
});
}
},
manualPagination: true, //we're doing manual "server-side" pagination
state: {
sorting,
columnFilters,
columnVisibility,
rowSelection,
pagination: currentPagination,
},
rowCount: pagination.total ?? 0,
});
return (
@ -241,7 +135,10 @@ export function DatasetTable() {
data-state={row.getIsSelected() && 'selected'}
>
{row.getVisibleCells().map((cell) => (
<TableCell key={cell.id}>
<TableCell
key={cell.id}
className={cell.column.columnDef.meta?.cellClassName}
>
{flexRender(
cell.column.columnDef.cell,
cell.getContext(),
@ -266,7 +163,7 @@ export function DatasetTable() {
<div className="flex items-center justify-end space-x-2 py-4">
<div className="flex-1 text-sm text-muted-foreground">
{table.getFilteredSelectedRowModel().rows.length} of{' '}
{table.getFilteredRowModel().rows.length} row(s) selected.
{pagination?.total} row(s) selected.
</div>
<div className="space-x-2">
<Button
@ -287,6 +184,18 @@ export function DatasetTable() {
</Button>
</div>
</div>
{changeParserVisible && (
<ChunkMethodDialog
documentId={currentRecord.id}
parserId={currentRecord.parser_id}
parserConfig={currentRecord.parser_config}
documentExtension={getExtension(currentRecord.name)}
onOk={onChangeParserOk}
visible={changeParserVisible}
hideModal={hideChangeParserModal}
loading={changeParserLoading}
></ChunkMethodDialog>
)}
</div>
);
}

View File

@ -93,7 +93,13 @@ export const useChangeDocumentParser = (documentId: string) => {
} = useSetModalState();
const onChangeParserOk = useCallback(
async (parserId: string, parserConfig: IChangeParserConfigRequestBody) => {
async ({
parserId,
parserConfig,
}: {
parserId: string;
parserConfig: IChangeParserConfigRequestBody;
}) => {
const ret = await setDocumentParser({
parserId,
documentId,

View File

@ -0,0 +1,194 @@
import SvgIcon from '@/components/svg-icon';
import { Button } from '@/components/ui/button';
import { Checkbox } from '@/components/ui/checkbox';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuLabel,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { Switch } from '@/components/ui/switch';
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { IDocumentInfo } from '@/interfaces/database/document';
import { cn } from '@/lib/utils';
import { getExtension } from '@/utils/document-util';
import { ColumnDef } from '@tanstack/table-core';
import { ArrowUpDown, MoreHorizontal, Pencil, Wrench } from 'lucide-react';
import { useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useChangeDocumentParser } from './hooks';
type UseDatasetTableColumnsType = Pick<
ReturnType<typeof useChangeDocumentParser>,
'showChangeParserModal'
> & { setCurrentRecord: (record: IDocumentInfo) => void };
export function useDatasetTableColumns({
showChangeParserModal,
setCurrentRecord,
}: UseDatasetTableColumnsType) {
const { t } = useTranslation('translation', {
keyPrefix: 'knowledgeDetails',
});
// const onShowRenameModal = (record: IDocumentInfo) => {
// setCurrentRecord(record);
// showRenameModal();
// };
const onShowChangeParserModal = useCallback(
(record: IDocumentInfo) => () => {
setCurrentRecord(record);
showChangeParserModal();
},
[setCurrentRecord, showChangeParserModal],
);
// const onShowSetMetaModal = useCallback(() => {
// setRecord();
// showSetMetaModal();
// }, [setRecord, showSetMetaModal]);
const columns: ColumnDef<IDocumentInfo>[] = [
{
id: 'select',
header: ({ table }) => (
<Checkbox
checked={
table.getIsAllPageRowsSelected() ||
(table.getIsSomePageRowsSelected() && 'indeterminate')
}
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
aria-label="Select all"
/>
),
cell: ({ row }) => (
<Checkbox
checked={row.getIsSelected()}
onCheckedChange={(value) => row.toggleSelected(!!value)}
aria-label="Select row"
/>
),
enableSorting: false,
enableHiding: false,
},
{
accessorKey: 'name',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('name')}
<ArrowUpDown />
</Button>
);
},
meta: { cellClassName: 'max-w-[20vw]' },
cell: ({ row }) => {
const name: string = row.getValue('name');
// return <div className="capitalize">{row.getValue('name')}</div>;
return (
<Tooltip>
<TooltipTrigger asChild>
<div className="flex gap-2">
<SvgIcon
name={`file-icon/${getExtension(name)}`}
width={24}
></SvgIcon>
<span className={cn('truncate')}>{name}</span>
</div>
</TooltipTrigger>
<TooltipContent>
<p>{name}</p>
</TooltipContent>
</Tooltip>
);
},
},
{
accessorKey: 'create_time',
header: ({ column }) => {
return (
<Button
variant="ghost"
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
>
{t('uploadDate')}
<ArrowUpDown />
</Button>
);
},
cell: ({ row }) => (
<div className="lowercase">{row.getValue('create_time')}</div>
),
},
{
accessorKey: 'parser_id',
header: t('chunkMethod'),
cell: ({ row }) => (
<div className="capitalize">{row.getValue('parser_id')}</div>
),
},
{
accessorKey: 'run',
header: t('parsingStatus'),
cell: ({ row }) => (
<Button variant="destructive" size={'sm'}>
{row.getValue('run')}
</Button>
),
},
{
id: 'actions',
header: t('action'),
enableHiding: false,
cell: ({ row }) => {
const record = row.original;
return (
<section className="flex gap-4 items-center">
<Switch id="airplane-mode" />
<Button
variant="icon"
size={'icon'}
onClick={onShowChangeParserModal(record)}
>
<Wrench />
</Button>
<Button variant="icon" size={'icon'}>
<Pencil />
</Button>
<DropdownMenu>
<DropdownMenuTrigger asChild>
<Button variant="icon" size={'icon'}>
<MoreHorizontal />
</Button>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuLabel>Actions</DropdownMenuLabel>
<DropdownMenuItem
onClick={() => navigator.clipboard.writeText(record.id)}
>
Copy payment ID
</DropdownMenuItem>
<DropdownMenuSeparator />
<DropdownMenuItem>View customer</DropdownMenuItem>
<DropdownMenuItem>View payment details</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</section>
);
},
},
];
return columns;
}

View File

@ -1,87 +1,17 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import ListFilterBar from '@/components/list-filter-bar';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
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 { useMemo } from 'react';
import { DatasetCreatingDialog } from './dataset-creating-dialog';
import { useSaveKnowledge } from './hooks';
const datasets = [
{
id: 1,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 2,
title: 'HR knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 3,
title: 'IT knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 4,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 5,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 6,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 7,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 8,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
{
id: 9,
title: 'Legal knowledge base',
files: '1,242 files',
size: '152 MB',
created: '12.02.2024',
image: 'https://github.com/shadcn.png',
},
];
export default function Datasets() {
const {
visible,
@ -92,6 +22,25 @@ export default function Datasets() {
} = useSaveKnowledge();
const { navigateToDataset } = useNavigatePage();
const {
fetchNextPage,
data,
hasNextPage,
searchString,
handleInputChange,
loading,
} = useInfiniteFetchKnowledgeList();
const nextList: IKnowledge[] = useMemo(() => {
const list =
data?.pages?.flatMap((x) => (Array.isArray(x.kbs) ? x.kbs : [])) ?? [];
return list;
}, [data?.pages]);
const total = useMemo(() => {
return data?.pages.at(-1).total ?? 0;
}, [data?.pages]);
return (
<section className="p-8 text-foreground">
<ListFilterBar title="Datasets" showDialog={showModal}>
@ -99,17 +48,17 @@ export default function Datasets() {
Create dataset
</ListFilterBar>
<div className="grid gap-6 sm:grid-cols-1 md:grid-cols-2 lg:grid-cols-4 xl:grid-cols-6 2xl:grid-cols-8">
{datasets.map((dataset) => (
{nextList.map((dataset) => (
<Card
key={dataset.id}
className="bg-colors-background-inverse-weak flex-1"
>
<CardContent className="p-4">
<div className="flex justify-between mb-4">
<div
className="w-[70px] h-[70px] rounded-xl bg-cover"
style={{ backgroundImage: `url(${dataset.image})` }}
/>
<Avatar className="w-[70px] h-[70px] rounded-lg">
<AvatarImage src={dataset.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<ConfirmDeleteDialog>
<Button variant="ghost" size="icon">
<Trash2 />
@ -118,20 +67,16 @@ export default function Datasets() {
</div>
<div className="flex justify-between items-end">
<div>
<h3 className="text-lg font-semibold mb-2">
{dataset.title}
</h3>
<h3 className="text-lg font-semibold mb-2">{dataset.name}</h3>
<p className="text-sm opacity-80">{dataset.doc_num} files</p>
<p className="text-sm opacity-80">
{dataset.files} | {dataset.size}
</p>
<p className="text-sm opacity-80">
Created {dataset.created}
Created {formatDate(dataset.update_time)}
</p>
</div>
<Button
variant="secondary"
variant="icon"
size="icon"
onClick={navigateToDataset}
onClick={navigateToDataset(dataset.id)}
>
<ChevronRight className="h-6 w-6" />
</Button>

View File

@ -58,7 +58,7 @@ export function Datasets() {
<Button
variant="icon"
size="icon"
onClick={navigateToDataset}
onClick={navigateToDataset(dataset.id)}
>
<ChevronRight className="h-6 w-6" />
</Button>

View File

@ -222,7 +222,7 @@ const routes = [
component: `@/pages${Routes.DatasetBase}`,
routes: [
{
path: Routes.Dataset,
path: `${Routes.Dataset}/:id`,
component: `@/pages${Routes.Dataset}`,
},
{