mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-12 21:49:00 +08:00
### What problem does this PR solve? Feat: Add FilesTable #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
be5f830878
commit
b4614e9517
@ -15,6 +15,7 @@ import weekYear from 'dayjs/plugin/weekYear';
|
|||||||
import weekday from 'dayjs/plugin/weekday';
|
import weekday from 'dayjs/plugin/weekday';
|
||||||
import React, { ReactNode, useEffect, useState } from 'react';
|
import React, { ReactNode, useEffect, useState } from 'react';
|
||||||
import { ThemeProvider, useTheme } from './components/theme-provider';
|
import { ThemeProvider, useTheme } from './components/theme-provider';
|
||||||
|
import { TooltipProvider } from './components/ui/tooltip';
|
||||||
import storage from './utils/authorization-util';
|
import storage from './utils/authorization-util';
|
||||||
|
|
||||||
dayjs.extend(customParseFormat);
|
dayjs.extend(customParseFormat);
|
||||||
@ -78,11 +79,13 @@ const RootProvider = ({ children }: React.PropsWithChildren) => {
|
|||||||
}, []);
|
}, []);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<QueryClientProvider client={queryClient}>
|
<TooltipProvider>
|
||||||
<ThemeProvider defaultTheme="light" storageKey="ragflow-ui-theme">
|
<QueryClientProvider client={queryClient}>
|
||||||
<Root>{children}</Root>
|
<ThemeProvider defaultTheme="light" storageKey="ragflow-ui-theme">
|
||||||
</ThemeProvider>
|
<Root>{children}</Root>
|
||||||
</QueryClientProvider>
|
</ThemeProvider>
|
||||||
|
</QueryClientProvider>
|
||||||
|
</TooltipProvider>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
export function rootContainer(container: ReactNode) {
|
export function rootContainer(container: ReactNode) {
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Filter, Search } from 'lucide-react';
|
import { Filter } from 'lucide-react';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
import { Button } from './ui/button';
|
import { Button } from './ui/button';
|
||||||
|
import { SearchInput } from './ui/input';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
title: string;
|
title: string;
|
||||||
@ -17,7 +18,7 @@ export default function ListFilterBar({
|
|||||||
<span className="text-3xl font-bold ">{title}</span>
|
<span className="text-3xl font-bold ">{title}</span>
|
||||||
<div className="flex gap-4 items-center">
|
<div className="flex gap-4 items-center">
|
||||||
<Filter className="size-5" />
|
<Filter className="size-5" />
|
||||||
<Search className="size-5" />
|
<SearchInput></SearchInput>
|
||||||
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
|
13
web/src/components/skeleton-card.tsx
Normal file
13
web/src/components/skeleton-card.tsx
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
import { Skeleton } from '@/components/ui/skeleton';
|
||||||
|
|
||||||
|
export function SkeletonCard() {
|
||||||
|
return (
|
||||||
|
<div className="flex flex-col space-y-3 items-center">
|
||||||
|
<Skeleton className="h-[125px] w-[250px] rounded-xl" />
|
||||||
|
<div className="space-y-2 w-[250px]">
|
||||||
|
<Skeleton className="h-4 w-[250px]" />
|
||||||
|
<Skeleton className="h-4 w-[200px]" />
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
27
web/src/components/table-skeleton.tsx
Normal file
27
web/src/components/table-skeleton.tsx
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { PropsWithChildren } from 'react';
|
||||||
|
import { SkeletonCard } from './skeleton-card';
|
||||||
|
import { TableCell, TableRow } from './ui/table';
|
||||||
|
|
||||||
|
type IProps = { columnsLength: number };
|
||||||
|
|
||||||
|
function Row({ children, columnsLength }: PropsWithChildren & IProps) {
|
||||||
|
return (
|
||||||
|
<TableRow>
|
||||||
|
<TableCell colSpan={columnsLength} className="h-24 text-center ">
|
||||||
|
{children}
|
||||||
|
</TableCell>
|
||||||
|
</TableRow>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableSkeleton({ columnsLength }: { columnsLength: number }) {
|
||||||
|
return (
|
||||||
|
<Row columnsLength={columnsLength}>
|
||||||
|
<SkeletonCard></SkeletonCard>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function TableEmpty({ columnsLength }: { columnsLength: number }) {
|
||||||
|
return <Row columnsLength={columnsLength}>No results.</Row>;
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
import * as React from 'react';
|
import * as React from 'react';
|
||||||
|
|
||||||
import { cn } from '@/lib/utils';
|
import { cn } from '@/lib/utils';
|
||||||
|
import { Search } from 'lucide-react';
|
||||||
|
|
||||||
export interface InputProps
|
export interface InputProps
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
||||||
@ -22,4 +23,38 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|||||||
);
|
);
|
||||||
Input.displayName = 'Input';
|
Input.displayName = 'Input';
|
||||||
|
|
||||||
export { Input };
|
export interface ExpandedInputProps extends Omit<InputProps, 'prefix'> {
|
||||||
|
prefix?: React.ReactNode;
|
||||||
|
suffix?: React.ReactNode;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ExpandedInput = ({ suffix, prefix, ...props }: ExpandedInputProps) => {
|
||||||
|
return (
|
||||||
|
<div className="relative">
|
||||||
|
<span
|
||||||
|
className={cn({
|
||||||
|
['absolute left-3 top-[50%] translate-y-[-50%]']: prefix,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{prefix}
|
||||||
|
</span>
|
||||||
|
<Input
|
||||||
|
className={cn({ 'pr-10': suffix, 'pl-10': prefix })}
|
||||||
|
{...props}
|
||||||
|
></Input>
|
||||||
|
<span
|
||||||
|
className={cn({
|
||||||
|
['absolute right-3 top-[50%] translate-y-[-50%]']: suffix,
|
||||||
|
})}
|
||||||
|
>
|
||||||
|
{suffix}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
const SearchInput = (props: InputProps) => {
|
||||||
|
return <ExpandedInput suffix={<Search />} {...props}></ExpandedInput>;
|
||||||
|
};
|
||||||
|
|
||||||
|
export { ExpandedInput, Input, SearchInput };
|
||||||
|
@ -10,6 +10,7 @@ import { Routes } from '@/routes';
|
|||||||
import {
|
import {
|
||||||
ChevronDown,
|
ChevronDown,
|
||||||
Cpu,
|
Cpu,
|
||||||
|
File,
|
||||||
Github,
|
Github,
|
||||||
House,
|
House,
|
||||||
Library,
|
Library,
|
||||||
@ -33,7 +34,7 @@ export function Header() {
|
|||||||
{ path: Routes.Chat, name: t('chat'), icon: MessageSquareText },
|
{ path: Routes.Chat, name: t('chat'), icon: MessageSquareText },
|
||||||
{ path: Routes.Search, name: t('search'), icon: Search },
|
{ path: Routes.Search, name: t('search'), icon: Search },
|
||||||
{ path: Routes.Agent, name: t('flow'), icon: Cpu },
|
{ path: Routes.Agent, name: t('flow'), icon: Cpu },
|
||||||
// { path: '/file', name: t('fileManager'), icon: FileIcon },
|
{ path: Routes.Files, name: t('fileManager'), icon: File },
|
||||||
],
|
],
|
||||||
[t],
|
[t],
|
||||||
);
|
);
|
||||||
|
@ -13,7 +13,7 @@ export default function Dataset() {
|
|||||||
documentUploadLoading,
|
documentUploadLoading,
|
||||||
} = useHandleUploadDocument();
|
} = useHandleUploadDocument();
|
||||||
return (
|
return (
|
||||||
<section className="p-8 text-foreground">
|
<section className="p-8">
|
||||||
<ListFilterBar title="Files" showDialog={showDocumentUploadModal}>
|
<ListFilterBar title="Files" showDialog={showDocumentUploadModal}>
|
||||||
<Upload />
|
<Upload />
|
||||||
Upload file
|
Upload file
|
||||||
|
@ -50,7 +50,7 @@ export function InputForm() {
|
|||||||
<Form {...form}>
|
<Form {...form}>
|
||||||
<form
|
<form
|
||||||
onSubmit={form.handleSubmit(onSubmit)}
|
onSubmit={form.handleSubmit(onSubmit)}
|
||||||
className="w-2/3 space-y-6"
|
className="space-y-6"
|
||||||
id={FormId}
|
id={FormId}
|
||||||
>
|
>
|
||||||
<FormField
|
<FormField
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import NewDocumentLink from '@/components/new-document-link';
|
import NewDocumentLink from '@/components/new-document-link';
|
||||||
import SvgIcon from '@/components/svg-icon';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import { useDownloadFile } from '@/hooks/file-manager-hooks';
|
import { useDownloadFile } from '@/hooks/file-manager-hooks';
|
||||||
import { IFile } from '@/interfaces/database/file-manager';
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
@ -8,13 +7,13 @@ import {
|
|||||||
isSupportedPreviewDocumentType,
|
isSupportedPreviewDocumentType,
|
||||||
} from '@/utils/document-util';
|
} from '@/utils/document-util';
|
||||||
import {
|
import {
|
||||||
DeleteOutlined,
|
|
||||||
DownloadOutlined,
|
DownloadOutlined,
|
||||||
EditOutlined,
|
EditOutlined,
|
||||||
EyeOutlined,
|
EyeOutlined,
|
||||||
LinkOutlined,
|
LinkOutlined,
|
||||||
} from '@ant-design/icons';
|
} from '@ant-design/icons';
|
||||||
import { Button, Space, Tooltip } from 'antd';
|
import { Button, Space, Tooltip } from 'antd';
|
||||||
|
import { FolderInput, Trash2 } from 'lucide-react';
|
||||||
import { useHandleDeleteFile } from '../hooks';
|
import { useHandleDeleteFile } from '../hooks';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
@ -92,15 +91,21 @@ const ActionCell = ({
|
|||||||
type="text"
|
type="text"
|
||||||
disabled={beingUsed}
|
disabled={beingUsed}
|
||||||
onClick={onShowMoveFileModal}
|
onClick={onShowMoveFileModal}
|
||||||
|
className="flex items-end"
|
||||||
>
|
>
|
||||||
<SvgIcon name={`move`} width={16}></SvgIcon>
|
<FolderInput className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
{isKnowledgeBase || (
|
{isKnowledgeBase || (
|
||||||
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
||||||
<Button type="text" disabled={beingUsed} onClick={handleRemoveFile}>
|
<Button
|
||||||
<DeleteOutlined size={20} />
|
type="text"
|
||||||
|
disabled={beingUsed}
|
||||||
|
onClick={handleRemoveFile}
|
||||||
|
className="flex items-end"
|
||||||
|
>
|
||||||
|
<Trash2 className="size-4" />
|
||||||
</Button>
|
</Button>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
|
@ -1,5 +1,3 @@
|
|||||||
import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg';
|
|
||||||
import SvgIcon from '@/components/svg-icon';
|
|
||||||
import { useTranslate } from '@/hooks/common-hooks';
|
import { useTranslate } from '@/hooks/common-hooks';
|
||||||
import {
|
import {
|
||||||
IListResult,
|
IListResult,
|
||||||
@ -29,6 +27,7 @@ import {
|
|||||||
useSelectBreadcrumbItems,
|
useSelectBreadcrumbItems,
|
||||||
} from './hooks';
|
} from './hooks';
|
||||||
|
|
||||||
|
import { FolderInput, Trash2 } from 'lucide-react';
|
||||||
import styles from './index.less';
|
import styles from './index.less';
|
||||||
|
|
||||||
interface IProps
|
interface IProps
|
||||||
@ -127,8 +126,8 @@ const FileToolbar = ({
|
|||||||
onClick: handleRemoveFile,
|
onClick: handleRemoveFile,
|
||||||
label: (
|
label: (
|
||||||
<Flex gap={10}>
|
<Flex gap={10}>
|
||||||
<span className={styles.deleteIconWrapper}>
|
<span className="flex items-center justify-center">
|
||||||
<DeleteIcon width={18} />
|
<Trash2 className="size-4" />
|
||||||
</span>
|
</span>
|
||||||
<b>{t('delete', { keyPrefix: 'common' })}</b>
|
<b>{t('delete', { keyPrefix: 'common' })}</b>
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -139,8 +138,8 @@ const FileToolbar = ({
|
|||||||
onClick: handleShowMoveFileModal,
|
onClick: handleShowMoveFileModal,
|
||||||
label: (
|
label: (
|
||||||
<Flex gap={10}>
|
<Flex gap={10}>
|
||||||
<span className={styles.deleteIconWrapper}>
|
<span className="flex items-center justify-center">
|
||||||
<SvgIcon name={`move`} width={18}></SvgIcon>
|
<FolderInput className="size-4"></FolderInput>
|
||||||
</span>
|
</span>
|
||||||
<b>{t('move', { keyPrefix: 'common' })}</b>
|
<b>{t('move', { keyPrefix: 'common' })}</b>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
343
web/src/pages/files/files-table.tsx
Normal file
343
web/src/pages/files/files-table.tsx
Normal file
@ -0,0 +1,343 @@
|
|||||||
|
'use client';
|
||||||
|
|
||||||
|
import {
|
||||||
|
ColumnDef,
|
||||||
|
ColumnFiltersState,
|
||||||
|
SortingState,
|
||||||
|
VisibilityState,
|
||||||
|
flexRender,
|
||||||
|
getCoreRowModel,
|
||||||
|
getFilteredRowModel,
|
||||||
|
getSortedRowModel,
|
||||||
|
useReactTable,
|
||||||
|
} from '@tanstack/react-table';
|
||||||
|
import { ArrowUpDown, MoreHorizontal, Pencil } from 'lucide-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import SvgIcon from '@/components/svg-icon';
|
||||||
|
import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
|
||||||
|
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,
|
||||||
|
TableCell,
|
||||||
|
TableHead,
|
||||||
|
TableHeader,
|
||||||
|
TableRow,
|
||||||
|
} from '@/components/ui/table';
|
||||||
|
import {
|
||||||
|
Tooltip,
|
||||||
|
TooltipContent,
|
||||||
|
TooltipTrigger,
|
||||||
|
} from '@/components/ui/tooltip';
|
||||||
|
import { useFetchFileList } from '@/hooks/file-manager-hooks';
|
||||||
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
import { formatFileSize } from '@/utils/common-util';
|
||||||
|
import { formatDate } from '@/utils/date';
|
||||||
|
import { getExtension } from '@/utils/document-util';
|
||||||
|
import { useMemo } from 'react';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
import { useNavigateToOtherFolder } from './hooks';
|
||||||
|
|
||||||
|
export function FilesTable() {
|
||||||
|
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||||
|
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
||||||
|
[],
|
||||||
|
);
|
||||||
|
const [columnVisibility, setColumnVisibility] =
|
||||||
|
React.useState<VisibilityState>({});
|
||||||
|
const [rowSelection, setRowSelection] = React.useState({});
|
||||||
|
const { t } = useTranslation('translation', {
|
||||||
|
keyPrefix: 'fileManager',
|
||||||
|
});
|
||||||
|
const navigateToOtherFolder = useNavigateToOtherFolder();
|
||||||
|
|
||||||
|
const { pagination, data, loading, setPagination } = useFetchFileList();
|
||||||
|
|
||||||
|
const columns: ColumnDef<IFile>[] = [
|
||||||
|
{
|
||||||
|
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');
|
||||||
|
const type = row.original.type;
|
||||||
|
const id = row.original.id;
|
||||||
|
const isFolder = type === 'folder';
|
||||||
|
|
||||||
|
const handleNameClick = () => {
|
||||||
|
if (isFolder) {
|
||||||
|
navigateToOtherFolder(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Tooltip>
|
||||||
|
<TooltipTrigger asChild>
|
||||||
|
<div className="flex gap-2">
|
||||||
|
<SvgIcon
|
||||||
|
name={`file-icon/${isFolder ? 'folder' : getExtension(name)}`}
|
||||||
|
width={24}
|
||||||
|
></SvgIcon>
|
||||||
|
<span
|
||||||
|
className={cn('truncate', { ['cursor-pointer']: isFolder })}
|
||||||
|
onClick={handleNameClick}
|
||||||
|
>
|
||||||
|
{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">
|
||||||
|
{formatDate(row.getValue('create_time'))}
|
||||||
|
</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'size',
|
||||||
|
header: ({ column }) => {
|
||||||
|
return (
|
||||||
|
<Button
|
||||||
|
variant="ghost"
|
||||||
|
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
||||||
|
>
|
||||||
|
{t('size')}
|
||||||
|
<ArrowUpDown />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
},
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<div className="capitalize">{formatFileSize(row.getValue('size'))}</div>
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
accessorKey: 'kbs_info',
|
||||||
|
header: t('knowledgeBase'),
|
||||||
|
cell: ({ row }) => (
|
||||||
|
<Button variant="destructive" size={'sm'}>
|
||||||
|
{row.getValue('kbs_info')}
|
||||||
|
</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?.files || [],
|
||||||
|
columns,
|
||||||
|
onSortingChange: setSorting,
|
||||||
|
onColumnFiltersChange: setColumnFilters,
|
||||||
|
getCoreRowModel: getCoreRowModel(),
|
||||||
|
// getPaginationRowModel: getPaginationRowModel(),
|
||||||
|
getSortedRowModel: getSortedRowModel(),
|
||||||
|
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: data?.total ?? 0,
|
||||||
|
debugTable: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="w-full">
|
||||||
|
<div className="rounded-md border">
|
||||||
|
<Table>
|
||||||
|
<TableHeader>
|
||||||
|
{table.getHeaderGroups().map((headerGroup) => (
|
||||||
|
<TableRow key={headerGroup.id}>
|
||||||
|
{headerGroup.headers.map((header) => {
|
||||||
|
return (
|
||||||
|
<TableHead key={header.id}>
|
||||||
|
{header.isPlaceholder
|
||||||
|
? null
|
||||||
|
: flexRender(
|
||||||
|
header.column.columnDef.header,
|
||||||
|
header.getContext(),
|
||||||
|
)}
|
||||||
|
</TableHead>
|
||||||
|
);
|
||||||
|
})}
|
||||||
|
</TableRow>
|
||||||
|
))}
|
||||||
|
</TableHeader>
|
||||||
|
<TableBody>
|
||||||
|
{loading ? (
|
||||||
|
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
|
||||||
|
) : table.getRowModel().rows?.length ? (
|
||||||
|
table.getRowModel().rows.map((row) => (
|
||||||
|
<TableRow
|
||||||
|
key={row.id}
|
||||||
|
data-state={row.getIsSelected() && 'selected'}
|
||||||
|
>
|
||||||
|
{row.getVisibleCells().map((cell) => (
|
||||||
|
<TableCell
|
||||||
|
key={cell.id}
|
||||||
|
className={cell.column.columnDef.meta?.cellClassName}
|
||||||
|
>
|
||||||
|
{flexRender(
|
||||||
|
cell.column.columnDef.cell,
|
||||||
|
cell.getContext(),
|
||||||
|
)}
|
||||||
|
</TableCell>
|
||||||
|
))}
|
||||||
|
</TableRow>
|
||||||
|
))
|
||||||
|
) : (
|
||||||
|
<TableEmpty columnsLength={columns.length}></TableEmpty>
|
||||||
|
)}
|
||||||
|
</TableBody>
|
||||||
|
</Table>
|
||||||
|
</div>
|
||||||
|
<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 {data?.total}{' '}
|
||||||
|
row(s) selected.
|
||||||
|
</div>
|
||||||
|
<div className="space-x-2">
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.previousPage()}
|
||||||
|
disabled={!table.getCanPreviousPage()}
|
||||||
|
>
|
||||||
|
Previous
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
variant="outline"
|
||||||
|
size="sm"
|
||||||
|
onClick={() => table.nextPage()}
|
||||||
|
disabled={!table.getCanNextPage()}
|
||||||
|
>
|
||||||
|
Next
|
||||||
|
</Button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
294
web/src/pages/files/hooks.ts
Normal file
294
web/src/pages/files/hooks.ts
Normal file
@ -0,0 +1,294 @@
|
|||||||
|
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||||
|
import {
|
||||||
|
useConnectToKnowledge,
|
||||||
|
useCreateFolder,
|
||||||
|
useDeleteFile,
|
||||||
|
useFetchParentFolderList,
|
||||||
|
useMoveFile,
|
||||||
|
useRenameFile,
|
||||||
|
useUploadFile,
|
||||||
|
} from '@/hooks/file-manager-hooks';
|
||||||
|
import { IFile } from '@/interfaces/database/file-manager';
|
||||||
|
import { TableRowSelection } from 'antd/es/table/interface';
|
||||||
|
import { UploadFile } from 'antd/lib';
|
||||||
|
import { useCallback, useMemo, useState } from 'react';
|
||||||
|
import { useNavigate, useSearchParams } from 'umi';
|
||||||
|
|
||||||
|
export const useGetFolderId = () => {
|
||||||
|
const [searchParams] = useSearchParams();
|
||||||
|
const id = searchParams.get('folderId') as string;
|
||||||
|
|
||||||
|
return id ?? '';
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useGetRowSelection = () => {
|
||||||
|
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
||||||
|
|
||||||
|
const rowSelection: TableRowSelection<IFile> = {
|
||||||
|
selectedRowKeys,
|
||||||
|
getCheckboxProps: (record) => {
|
||||||
|
return { disabled: record.source_type === 'knowledgebase' };
|
||||||
|
},
|
||||||
|
onChange: (newSelectedRowKeys: React.Key[]) => {
|
||||||
|
setSelectedRowKeys(newSelectedRowKeys);
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return { rowSelection, setSelectedRowKeys };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useNavigateToOtherFolder = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
const navigateToOtherFolder = useCallback(
|
||||||
|
(folderId: string) => {
|
||||||
|
navigate(`/file?folderId=${folderId}`);
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
return navigateToOtherFolder;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useRenameCurrentFile = () => {
|
||||||
|
const [file, setFile] = useState<IFile>({} as IFile);
|
||||||
|
const {
|
||||||
|
visible: fileRenameVisible,
|
||||||
|
hideModal: hideFileRenameModal,
|
||||||
|
showModal: showFileRenameModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { renameFile, loading } = useRenameFile();
|
||||||
|
|
||||||
|
const onFileRenameOk = useCallback(
|
||||||
|
async (name: string) => {
|
||||||
|
const ret = await renameFile({
|
||||||
|
fileId: file.id,
|
||||||
|
name,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret === 0) {
|
||||||
|
hideFileRenameModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[renameFile, file, hideFileRenameModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleShowFileRenameModal = useCallback(
|
||||||
|
async (record: IFile) => {
|
||||||
|
setFile(record);
|
||||||
|
showFileRenameModal();
|
||||||
|
},
|
||||||
|
[showFileRenameModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileRenameLoading: loading,
|
||||||
|
initialFileName: file.name,
|
||||||
|
onFileRenameOk,
|
||||||
|
fileRenameVisible,
|
||||||
|
hideFileRenameModal,
|
||||||
|
showFileRenameModal: handleShowFileRenameModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useSelectBreadcrumbItems = () => {
|
||||||
|
const parentFolderList = useFetchParentFolderList();
|
||||||
|
|
||||||
|
return parentFolderList.length === 1
|
||||||
|
? []
|
||||||
|
: parentFolderList.map((x) => ({
|
||||||
|
title: x.name === '/' ? 'root' : x.name,
|
||||||
|
path: `/file?folderId=${x.id}`,
|
||||||
|
}));
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleCreateFolder = () => {
|
||||||
|
const {
|
||||||
|
visible: folderCreateModalVisible,
|
||||||
|
hideModal: hideFolderCreateModal,
|
||||||
|
showModal: showFolderCreateModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { createFolder, loading } = useCreateFolder();
|
||||||
|
const id = useGetFolderId();
|
||||||
|
|
||||||
|
const onFolderCreateOk = useCallback(
|
||||||
|
async (name: string) => {
|
||||||
|
const ret = await createFolder({ parentId: id, name });
|
||||||
|
|
||||||
|
if (ret === 0) {
|
||||||
|
hideFolderCreateModal();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[createFolder, hideFolderCreateModal, id],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
folderCreateLoading: loading,
|
||||||
|
onFolderCreateOk,
|
||||||
|
folderCreateModalVisible,
|
||||||
|
hideFolderCreateModal,
|
||||||
|
showFolderCreateModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleDeleteFile = (
|
||||||
|
fileIds: string[],
|
||||||
|
setSelectedRowKeys: (keys: string[]) => void,
|
||||||
|
) => {
|
||||||
|
const { deleteFile: removeDocument } = useDeleteFile();
|
||||||
|
const showDeleteConfirm = useShowDeleteConfirm();
|
||||||
|
const parentId = useGetFolderId();
|
||||||
|
|
||||||
|
const handleRemoveFile = () => {
|
||||||
|
showDeleteConfirm({
|
||||||
|
onOk: async () => {
|
||||||
|
const code = await removeDocument({ fileIds, parentId });
|
||||||
|
if (code === 0) {
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return { handleRemoveFile };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleUploadFile = () => {
|
||||||
|
const {
|
||||||
|
visible: fileUploadVisible,
|
||||||
|
hideModal: hideFileUploadModal,
|
||||||
|
showModal: showFileUploadModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { uploadFile, loading } = useUploadFile();
|
||||||
|
const id = useGetFolderId();
|
||||||
|
|
||||||
|
const onFileUploadOk = useCallback(
|
||||||
|
async (fileList: UploadFile[]): Promise<number | undefined> => {
|
||||||
|
if (fileList.length > 0) {
|
||||||
|
const ret: number = await uploadFile({ fileList, parentId: id });
|
||||||
|
if (ret === 0) {
|
||||||
|
hideFileUploadModal();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[uploadFile, hideFileUploadModal, id],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
fileUploadLoading: loading,
|
||||||
|
onFileUploadOk,
|
||||||
|
fileUploadVisible,
|
||||||
|
hideFileUploadModal,
|
||||||
|
showFileUploadModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleConnectToKnowledge = () => {
|
||||||
|
const {
|
||||||
|
visible: connectToKnowledgeVisible,
|
||||||
|
hideModal: hideConnectToKnowledgeModal,
|
||||||
|
showModal: showConnectToKnowledgeModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { connectFileToKnowledge: connectToKnowledge, loading } =
|
||||||
|
useConnectToKnowledge();
|
||||||
|
const [record, setRecord] = useState<IFile>({} as IFile);
|
||||||
|
|
||||||
|
const initialValue = useMemo(() => {
|
||||||
|
return Array.isArray(record?.kbs_info)
|
||||||
|
? record?.kbs_info?.map((x) => x.kb_id)
|
||||||
|
: [];
|
||||||
|
}, [record?.kbs_info]);
|
||||||
|
|
||||||
|
const onConnectToKnowledgeOk = useCallback(
|
||||||
|
async (knowledgeIds: string[]) => {
|
||||||
|
const ret = await connectToKnowledge({
|
||||||
|
fileIds: [record.id],
|
||||||
|
kbIds: knowledgeIds,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret === 0) {
|
||||||
|
hideConnectToKnowledgeModal();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
[connectToKnowledge, hideConnectToKnowledgeModal, record.id],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleShowConnectToKnowledgeModal = useCallback(
|
||||||
|
(record: IFile) => {
|
||||||
|
setRecord(record);
|
||||||
|
showConnectToKnowledgeModal();
|
||||||
|
},
|
||||||
|
[showConnectToKnowledgeModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialValue,
|
||||||
|
connectToKnowledgeLoading: loading,
|
||||||
|
onConnectToKnowledgeOk,
|
||||||
|
connectToKnowledgeVisible,
|
||||||
|
hideConnectToKnowledgeModal,
|
||||||
|
showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleBreadcrumbClick = () => {
|
||||||
|
const navigate = useNavigate();
|
||||||
|
|
||||||
|
const handleBreadcrumbClick = useCallback(
|
||||||
|
(path?: string) => {
|
||||||
|
if (path) {
|
||||||
|
navigate(path);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
[navigate],
|
||||||
|
);
|
||||||
|
|
||||||
|
return { handleBreadcrumbClick };
|
||||||
|
};
|
||||||
|
|
||||||
|
export const useHandleMoveFile = (
|
||||||
|
setSelectedRowKeys: (keys: string[]) => void,
|
||||||
|
) => {
|
||||||
|
const {
|
||||||
|
visible: moveFileVisible,
|
||||||
|
hideModal: hideMoveFileModal,
|
||||||
|
showModal: showMoveFileModal,
|
||||||
|
} = useSetModalState();
|
||||||
|
const { moveFile, loading } = useMoveFile();
|
||||||
|
const [sourceFileIds, setSourceFileIds] = useState<string[]>([]);
|
||||||
|
|
||||||
|
const onMoveFileOk = useCallback(
|
||||||
|
async (targetFolderId: string) => {
|
||||||
|
const ret = await moveFile({
|
||||||
|
src_file_ids: sourceFileIds,
|
||||||
|
dest_file_id: targetFolderId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (ret === 0) {
|
||||||
|
setSelectedRowKeys([]);
|
||||||
|
hideMoveFileModal();
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
},
|
||||||
|
[moveFile, hideMoveFileModal, sourceFileIds, setSelectedRowKeys],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleShowMoveFileModal = useCallback(
|
||||||
|
(ids: string[]) => {
|
||||||
|
setSourceFileIds(ids);
|
||||||
|
showMoveFileModal();
|
||||||
|
},
|
||||||
|
[showMoveFileModal],
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
initialValue: '',
|
||||||
|
moveFileLoading: loading,
|
||||||
|
onMoveFileOk,
|
||||||
|
moveFileVisible,
|
||||||
|
hideMoveFileModal,
|
||||||
|
showMoveFileModal: handleShowMoveFileModal,
|
||||||
|
};
|
||||||
|
};
|
15
web/src/pages/files/index.tsx
Normal file
15
web/src/pages/files/index.tsx
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
import ListFilterBar from '@/components/list-filter-bar';
|
||||||
|
import { Upload } from 'lucide-react';
|
||||||
|
import { FilesTable } from './files-table';
|
||||||
|
|
||||||
|
export default function Files() {
|
||||||
|
return (
|
||||||
|
<section className="p-8">
|
||||||
|
<ListFilterBar title="Files">
|
||||||
|
<Upload />
|
||||||
|
Upload file
|
||||||
|
</ListFilterBar>
|
||||||
|
<FilesTable></FilesTable>
|
||||||
|
</section>
|
||||||
|
);
|
||||||
|
}
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import {
|
import {
|
||||||
@ -175,24 +174,20 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|||||||
<Background />
|
<Background />
|
||||||
<Controls>
|
<Controls>
|
||||||
<ControlButton onClick={handleImportJson}>
|
<ControlButton onClick={handleImportJson}>
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<FolderInput className={controlIconClassname} />
|
||||||
<FolderInput className={controlIconClassname} />
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>Import</TooltipContent>
|
||||||
<TooltipContent>Import</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
<ControlButton onClick={handleExportJson}>
|
<ControlButton onClick={handleExportJson}>
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger asChild>
|
||||||
<TooltipTrigger asChild>
|
<FolderOutput className={controlIconClassname} />
|
||||||
<FolderOutput className={controlIconClassname} />
|
</TooltipTrigger>
|
||||||
</TooltipTrigger>
|
<TooltipContent>Export</TooltipContent>
|
||||||
<TooltipContent>Export</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
</ControlButton>
|
</ControlButton>
|
||||||
</Controls>
|
</Controls>
|
||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
|
@ -1,7 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
Tooltip,
|
Tooltip,
|
||||||
TooltipContent,
|
TooltipContent,
|
||||||
TooltipProvider,
|
|
||||||
TooltipTrigger,
|
TooltipTrigger,
|
||||||
} from '@/components/ui/tooltip';
|
} from '@/components/ui/tooltip';
|
||||||
import { PropsWithChildren } from 'react';
|
import { PropsWithChildren } from 'react';
|
||||||
@ -10,13 +9,11 @@ import { useTranslation } from 'react-i18next';
|
|||||||
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<TooltipProvider>
|
<Tooltip>
|
||||||
<Tooltip>
|
<TooltipTrigger>{children}</TooltipTrigger>
|
||||||
<TooltipTrigger>{children}</TooltipTrigger>
|
<TooltipContent>
|
||||||
<TooltipContent>
|
<p>{t('flow.testRun')}</p>
|
||||||
<p>{t('flow.testRun')}</p>
|
</TooltipContent>
|
||||||
</TooltipContent>
|
</Tooltip>
|
||||||
</Tooltip>
|
|
||||||
</TooltipProvider>
|
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -40,7 +40,7 @@ const KnowledgeCard = ({ item }: IProps) => {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<Badge.Ribbon
|
<Badge.Ribbon
|
||||||
text={item.nickname}
|
text={item?.nickname}
|
||||||
color={userInfo.nickname === item.nickname ? '#1677ff' : 'pink'}
|
color={userInfo.nickname === item.nickname ? '#1677ff' : 'pink'}
|
||||||
className={classNames(styles.ribbon, {
|
className={classNames(styles.ribbon, {
|
||||||
[styles.hideRibbon]: item.permission !== 'team',
|
[styles.hideRibbon]: item.permission !== 'team',
|
||||||
|
@ -97,8 +97,8 @@ const Login = () => {
|
|||||||
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')]">
|
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')] bg-cover bg-center">
|
||||||
<div className="inline-block">
|
<div className="inline-block bg-colors-background-neutral-standard rounded-lg">
|
||||||
{step === Step.SignIn && <SignInCard></SignInCard>}
|
{step === Step.SignIn && <SignInCard></SignInCard>}
|
||||||
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
||||||
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
||||||
|
@ -7,6 +7,7 @@ export enum Routes {
|
|||||||
Agent = '/agent',
|
Agent = '/agent',
|
||||||
Search = '/next-search',
|
Search = '/next-search',
|
||||||
Chat = '/next-chat',
|
Chat = '/next-chat',
|
||||||
|
Files = '/files',
|
||||||
ProfileSetting = '/profile-setting',
|
ProfileSetting = '/profile-setting',
|
||||||
DatasetTesting = '/testing',
|
DatasetTesting = '/testing',
|
||||||
DatasetSetting = '/setting',
|
DatasetSetting = '/setting',
|
||||||
@ -189,6 +190,17 @@ const routes = [
|
|||||||
},
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: Routes.Files,
|
||||||
|
layout: false,
|
||||||
|
component: '@/layouts/next',
|
||||||
|
routes: [
|
||||||
|
{
|
||||||
|
path: Routes.Files,
|
||||||
|
component: `@/pages${Routes.Files}`,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
{
|
{
|
||||||
path: Routes.DatasetBase,
|
path: Routes.DatasetBase,
|
||||||
layout: false,
|
layout: false,
|
||||||
|
@ -113,3 +113,28 @@ export function hexToArrayBuffer(input: string) {
|
|||||||
|
|
||||||
return view.buffer;
|
return view.buffer;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function formatFileSize(bytes: number, si = true, dp = 1) {
|
||||||
|
let nextBytes = bytes;
|
||||||
|
const thresh = si ? 1000 : 1024;
|
||||||
|
|
||||||
|
if (Math.abs(bytes) < thresh) {
|
||||||
|
return nextBytes + ' B';
|
||||||
|
}
|
||||||
|
|
||||||
|
const units = si
|
||||||
|
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
||||||
|
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
||||||
|
let u = -1;
|
||||||
|
const r = 10 ** dp;
|
||||||
|
|
||||||
|
do {
|
||||||
|
nextBytes /= thresh;
|
||||||
|
++u;
|
||||||
|
} while (
|
||||||
|
Math.round(Math.abs(nextBytes) * r) / r >= thresh &&
|
||||||
|
u < units.length - 1
|
||||||
|
);
|
||||||
|
|
||||||
|
return nextBytes.toFixed(dp) + ' ' + units[u];
|
||||||
|
}
|
||||||
|
8
web/typings.d.ts
vendored
8
web/typings.d.ts
vendored
@ -1,5 +1,13 @@
|
|||||||
|
import '@tanstack/react-table';
|
||||||
declare module 'lodash';
|
declare module 'lodash';
|
||||||
|
|
||||||
declare global {
|
declare global {
|
||||||
type Nullable<T> = T | null;
|
type Nullable<T> = T | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
declare module '@tanstack/react-table' {
|
||||||
|
interface ColumnMeta {
|
||||||
|
headerClassName?: string;
|
||||||
|
cellClassName?: string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user