balibabu
commited on
Commit
·
d1bf860
1
Parent(s):
2435d05
Feat: Add FilesTable #3221 (#4491)
Browse files### What problem does this PR solve?
Feat: Add FilesTable #3221
### Type of change
- [x] New Feature (non-breaking change which adds functionality)
- web/src/app.tsx +8 -5
- web/src/components/list-filter-bar.tsx +3 -2
- web/src/components/skeleton-card.tsx +13 -0
- web/src/components/table-skeleton.tsx +27 -0
- web/src/components/ui/input.tsx +36 -1
- web/src/layouts/next-header.tsx +2 -1
- web/src/pages/dataset/dataset/index.tsx +1 -1
- web/src/pages/datasets/dataset-creating-dialog.tsx +1 -1
- web/src/pages/file-manager/action-cell/index.tsx +10 -5
- web/src/pages/file-manager/file-toolbar.tsx +5 -6
- web/src/pages/files/files-table.tsx +343 -0
- web/src/pages/files/hooks.ts +294 -0
- web/src/pages/files/index.tsx +15 -0
- web/src/pages/flow/canvas/index.tsx +12 -17
- web/src/pages/flow/flow-tooltip.tsx +6 -9
- web/src/pages/knowledge/knowledge-card/index.tsx +1 -1
- web/src/pages/login-next/index.tsx +2 -2
- web/src/routes.ts +12 -0
- web/src/utils/common-util.ts +25 -0
- web/typings.d.ts +8 -0
web/src/app.tsx
CHANGED
@@ -15,6 +15,7 @@ import weekYear from 'dayjs/plugin/weekYear';
|
|
15 |
import weekday from 'dayjs/plugin/weekday';
|
16 |
import React, { ReactNode, useEffect, useState } from 'react';
|
17 |
import { ThemeProvider, useTheme } from './components/theme-provider';
|
|
|
18 |
import storage from './utils/authorization-util';
|
19 |
|
20 |
dayjs.extend(customParseFormat);
|
@@ -78,11 +79,13 @@ const RootProvider = ({ children }: React.PropsWithChildren) => {
|
|
78 |
}, []);
|
79 |
|
80 |
return (
|
81 |
-
<
|
82 |
-
<
|
83 |
-
<
|
84 |
-
|
85 |
-
|
|
|
|
|
86 |
);
|
87 |
};
|
88 |
export function rootContainer(container: ReactNode) {
|
|
|
15 |
import weekday from 'dayjs/plugin/weekday';
|
16 |
import React, { ReactNode, useEffect, useState } from 'react';
|
17 |
import { ThemeProvider, useTheme } from './components/theme-provider';
|
18 |
+
import { TooltipProvider } from './components/ui/tooltip';
|
19 |
import storage from './utils/authorization-util';
|
20 |
|
21 |
dayjs.extend(customParseFormat);
|
|
|
79 |
}, []);
|
80 |
|
81 |
return (
|
82 |
+
<TooltipProvider>
|
83 |
+
<QueryClientProvider client={queryClient}>
|
84 |
+
<ThemeProvider defaultTheme="light" storageKey="ragflow-ui-theme">
|
85 |
+
<Root>{children}</Root>
|
86 |
+
</ThemeProvider>
|
87 |
+
</QueryClientProvider>
|
88 |
+
</TooltipProvider>
|
89 |
);
|
90 |
};
|
91 |
export function rootContainer(container: ReactNode) {
|
web/src/components/list-filter-bar.tsx
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
-
import { Filter
|
2 |
import { PropsWithChildren } from 'react';
|
3 |
import { Button } from './ui/button';
|
|
|
4 |
|
5 |
interface IProps {
|
6 |
title: string;
|
@@ -17,7 +18,7 @@ export default function ListFilterBar({
|
|
17 |
<span className="text-3xl font-bold ">{title}</span>
|
18 |
<div className="flex gap-4 items-center">
|
19 |
<Filter className="size-5" />
|
20 |
-
<
|
21 |
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
22 |
{children}
|
23 |
</Button>
|
|
|
1 |
+
import { Filter } from 'lucide-react';
|
2 |
import { PropsWithChildren } from 'react';
|
3 |
import { Button } from './ui/button';
|
4 |
+
import { SearchInput } from './ui/input';
|
5 |
|
6 |
interface IProps {
|
7 |
title: string;
|
|
|
18 |
<span className="text-3xl font-bold ">{title}</span>
|
19 |
<div className="flex gap-4 items-center">
|
20 |
<Filter className="size-5" />
|
21 |
+
<SearchInput></SearchInput>
|
22 |
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
23 |
{children}
|
24 |
</Button>
|
web/src/components/skeleton-card.tsx
ADDED
@@ -0,0 +1,13 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { Skeleton } from '@/components/ui/skeleton';
|
2 |
+
|
3 |
+
export function SkeletonCard() {
|
4 |
+
return (
|
5 |
+
<div className="flex flex-col space-y-3 items-center">
|
6 |
+
<Skeleton className="h-[125px] w-[250px] rounded-xl" />
|
7 |
+
<div className="space-y-2 w-[250px]">
|
8 |
+
<Skeleton className="h-4 w-[250px]" />
|
9 |
+
<Skeleton className="h-4 w-[200px]" />
|
10 |
+
</div>
|
11 |
+
</div>
|
12 |
+
);
|
13 |
+
}
|
web/src/components/table-skeleton.tsx
ADDED
@@ -0,0 +1,27 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { PropsWithChildren } from 'react';
|
2 |
+
import { SkeletonCard } from './skeleton-card';
|
3 |
+
import { TableCell, TableRow } from './ui/table';
|
4 |
+
|
5 |
+
type IProps = { columnsLength: number };
|
6 |
+
|
7 |
+
function Row({ children, columnsLength }: PropsWithChildren & IProps) {
|
8 |
+
return (
|
9 |
+
<TableRow>
|
10 |
+
<TableCell colSpan={columnsLength} className="h-24 text-center ">
|
11 |
+
{children}
|
12 |
+
</TableCell>
|
13 |
+
</TableRow>
|
14 |
+
);
|
15 |
+
}
|
16 |
+
|
17 |
+
export function TableSkeleton({ columnsLength }: { columnsLength: number }) {
|
18 |
+
return (
|
19 |
+
<Row columnsLength={columnsLength}>
|
20 |
+
<SkeletonCard></SkeletonCard>
|
21 |
+
</Row>
|
22 |
+
);
|
23 |
+
}
|
24 |
+
|
25 |
+
export function TableEmpty({ columnsLength }: { columnsLength: number }) {
|
26 |
+
return <Row columnsLength={columnsLength}>No results.</Row>;
|
27 |
+
}
|
web/src/components/ui/input.tsx
CHANGED
@@ -1,6 +1,7 @@
|
|
1 |
import * as React from 'react';
|
2 |
|
3 |
import { cn } from '@/lib/utils';
|
|
|
4 |
|
5 |
export interface InputProps
|
6 |
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
@@ -22,4 +23,38 @@ const Input = React.forwardRef<HTMLInputElement, InputProps>(
|
|
22 |
);
|
23 |
Input.displayName = 'Input';
|
24 |
|
25 |
-
export
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
import * as React from 'react';
|
2 |
|
3 |
import { cn } from '@/lib/utils';
|
4 |
+
import { Search } from 'lucide-react';
|
5 |
|
6 |
export interface InputProps
|
7 |
extends React.InputHTMLAttributes<HTMLInputElement> {}
|
|
|
23 |
);
|
24 |
Input.displayName = 'Input';
|
25 |
|
26 |
+
export interface ExpandedInputProps extends Omit<InputProps, 'prefix'> {
|
27 |
+
prefix?: React.ReactNode;
|
28 |
+
suffix?: React.ReactNode;
|
29 |
+
}
|
30 |
+
|
31 |
+
const ExpandedInput = ({ suffix, prefix, ...props }: ExpandedInputProps) => {
|
32 |
+
return (
|
33 |
+
<div className="relative">
|
34 |
+
<span
|
35 |
+
className={cn({
|
36 |
+
['absolute left-3 top-[50%] translate-y-[-50%]']: prefix,
|
37 |
+
})}
|
38 |
+
>
|
39 |
+
{prefix}
|
40 |
+
</span>
|
41 |
+
<Input
|
42 |
+
className={cn({ 'pr-10': suffix, 'pl-10': prefix })}
|
43 |
+
{...props}
|
44 |
+
></Input>
|
45 |
+
<span
|
46 |
+
className={cn({
|
47 |
+
['absolute right-3 top-[50%] translate-y-[-50%]']: suffix,
|
48 |
+
})}
|
49 |
+
>
|
50 |
+
{suffix}
|
51 |
+
</span>
|
52 |
+
</div>
|
53 |
+
);
|
54 |
+
};
|
55 |
+
|
56 |
+
const SearchInput = (props: InputProps) => {
|
57 |
+
return <ExpandedInput suffix={<Search />} {...props}></ExpandedInput>;
|
58 |
+
};
|
59 |
+
|
60 |
+
export { ExpandedInput, Input, SearchInput };
|
web/src/layouts/next-header.tsx
CHANGED
@@ -10,6 +10,7 @@ import { Routes } from '@/routes';
|
|
10 |
import {
|
11 |
ChevronDown,
|
12 |
Cpu,
|
|
|
13 |
Github,
|
14 |
House,
|
15 |
Library,
|
@@ -33,7 +34,7 @@ export function Header() {
|
|
33 |
{ path: Routes.Chat, name: t('chat'), icon: MessageSquareText },
|
34 |
{ path: Routes.Search, name: t('search'), icon: Search },
|
35 |
{ path: Routes.Agent, name: t('flow'), icon: Cpu },
|
36 |
-
|
37 |
],
|
38 |
[t],
|
39 |
);
|
|
|
10 |
import {
|
11 |
ChevronDown,
|
12 |
Cpu,
|
13 |
+
File,
|
14 |
Github,
|
15 |
House,
|
16 |
Library,
|
|
|
34 |
{ path: Routes.Chat, name: t('chat'), icon: MessageSquareText },
|
35 |
{ path: Routes.Search, name: t('search'), icon: Search },
|
36 |
{ path: Routes.Agent, name: t('flow'), icon: Cpu },
|
37 |
+
{ path: Routes.Files, name: t('fileManager'), icon: File },
|
38 |
],
|
39 |
[t],
|
40 |
);
|
web/src/pages/dataset/dataset/index.tsx
CHANGED
@@ -13,7 +13,7 @@ export default function Dataset() {
|
|
13 |
documentUploadLoading,
|
14 |
} = useHandleUploadDocument();
|
15 |
return (
|
16 |
-
<section className="p-8
|
17 |
<ListFilterBar title="Files" showDialog={showDocumentUploadModal}>
|
18 |
<Upload />
|
19 |
Upload file
|
|
|
13 |
documentUploadLoading,
|
14 |
} = useHandleUploadDocument();
|
15 |
return (
|
16 |
+
<section className="p-8">
|
17 |
<ListFilterBar title="Files" showDialog={showDocumentUploadModal}>
|
18 |
<Upload />
|
19 |
Upload file
|
web/src/pages/datasets/dataset-creating-dialog.tsx
CHANGED
@@ -50,7 +50,7 @@ export function InputForm() {
|
|
50 |
<Form {...form}>
|
51 |
<form
|
52 |
onSubmit={form.handleSubmit(onSubmit)}
|
53 |
-
className="
|
54 |
id={FormId}
|
55 |
>
|
56 |
<FormField
|
|
|
50 |
<Form {...form}>
|
51 |
<form
|
52 |
onSubmit={form.handleSubmit(onSubmit)}
|
53 |
+
className="space-y-6"
|
54 |
id={FormId}
|
55 |
>
|
56 |
<FormField
|
web/src/pages/file-manager/action-cell/index.tsx
CHANGED
@@ -1,5 +1,4 @@
|
|
1 |
import NewDocumentLink from '@/components/new-document-link';
|
2 |
-
import SvgIcon from '@/components/svg-icon';
|
3 |
import { useTranslate } from '@/hooks/common-hooks';
|
4 |
import { useDownloadFile } from '@/hooks/file-manager-hooks';
|
5 |
import { IFile } from '@/interfaces/database/file-manager';
|
@@ -8,13 +7,13 @@ import {
|
|
8 |
isSupportedPreviewDocumentType,
|
9 |
} from '@/utils/document-util';
|
10 |
import {
|
11 |
-
DeleteOutlined,
|
12 |
DownloadOutlined,
|
13 |
EditOutlined,
|
14 |
EyeOutlined,
|
15 |
LinkOutlined,
|
16 |
} from '@ant-design/icons';
|
17 |
import { Button, Space, Tooltip } from 'antd';
|
|
|
18 |
import { useHandleDeleteFile } from '../hooks';
|
19 |
|
20 |
interface IProps {
|
@@ -92,15 +91,21 @@ const ActionCell = ({
|
|
92 |
type="text"
|
93 |
disabled={beingUsed}
|
94 |
onClick={onShowMoveFileModal}
|
|
|
95 |
>
|
96 |
-
<
|
97 |
</Button>
|
98 |
</Tooltip>
|
99 |
)}
|
100 |
{isKnowledgeBase || (
|
101 |
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
102 |
-
<Button
|
103 |
-
|
|
|
|
|
|
|
|
|
|
|
104 |
</Button>
|
105 |
</Tooltip>
|
106 |
)}
|
|
|
1 |
import NewDocumentLink from '@/components/new-document-link';
|
|
|
2 |
import { useTranslate } from '@/hooks/common-hooks';
|
3 |
import { useDownloadFile } from '@/hooks/file-manager-hooks';
|
4 |
import { IFile } from '@/interfaces/database/file-manager';
|
|
|
7 |
isSupportedPreviewDocumentType,
|
8 |
} from '@/utils/document-util';
|
9 |
import {
|
|
|
10 |
DownloadOutlined,
|
11 |
EditOutlined,
|
12 |
EyeOutlined,
|
13 |
LinkOutlined,
|
14 |
} from '@ant-design/icons';
|
15 |
import { Button, Space, Tooltip } from 'antd';
|
16 |
+
import { FolderInput, Trash2 } from 'lucide-react';
|
17 |
import { useHandleDeleteFile } from '../hooks';
|
18 |
|
19 |
interface IProps {
|
|
|
91 |
type="text"
|
92 |
disabled={beingUsed}
|
93 |
onClick={onShowMoveFileModal}
|
94 |
+
className="flex items-end"
|
95 |
>
|
96 |
+
<FolderInput className="size-4" />
|
97 |
</Button>
|
98 |
</Tooltip>
|
99 |
)}
|
100 |
{isKnowledgeBase || (
|
101 |
<Tooltip title={t('delete', { keyPrefix: 'common' })}>
|
102 |
+
<Button
|
103 |
+
type="text"
|
104 |
+
disabled={beingUsed}
|
105 |
+
onClick={handleRemoveFile}
|
106 |
+
className="flex items-end"
|
107 |
+
>
|
108 |
+
<Trash2 className="size-4" />
|
109 |
</Button>
|
110 |
</Tooltip>
|
111 |
)}
|
web/src/pages/file-manager/file-toolbar.tsx
CHANGED
@@ -1,5 +1,3 @@
|
|
1 |
-
import { ReactComponent as DeleteIcon } from '@/assets/svg/delete.svg';
|
2 |
-
import SvgIcon from '@/components/svg-icon';
|
3 |
import { useTranslate } from '@/hooks/common-hooks';
|
4 |
import {
|
5 |
IListResult,
|
@@ -29,6 +27,7 @@ import {
|
|
29 |
useSelectBreadcrumbItems,
|
30 |
} from './hooks';
|
31 |
|
|
|
32 |
import styles from './index.less';
|
33 |
|
34 |
interface IProps
|
@@ -127,8 +126,8 @@ const FileToolbar = ({
|
|
127 |
onClick: handleRemoveFile,
|
128 |
label: (
|
129 |
<Flex gap={10}>
|
130 |
-
<span className=
|
131 |
-
<
|
132 |
</span>
|
133 |
<b>{t('delete', { keyPrefix: 'common' })}</b>
|
134 |
</Flex>
|
@@ -139,8 +138,8 @@ const FileToolbar = ({
|
|
139 |
onClick: handleShowMoveFileModal,
|
140 |
label: (
|
141 |
<Flex gap={10}>
|
142 |
-
<span className=
|
143 |
-
<
|
144 |
</span>
|
145 |
<b>{t('move', { keyPrefix: 'common' })}</b>
|
146 |
</Flex>
|
|
|
|
|
|
|
1 |
import { useTranslate } from '@/hooks/common-hooks';
|
2 |
import {
|
3 |
IListResult,
|
|
|
27 |
useSelectBreadcrumbItems,
|
28 |
} from './hooks';
|
29 |
|
30 |
+
import { FolderInput, Trash2 } from 'lucide-react';
|
31 |
import styles from './index.less';
|
32 |
|
33 |
interface IProps
|
|
|
126 |
onClick: handleRemoveFile,
|
127 |
label: (
|
128 |
<Flex gap={10}>
|
129 |
+
<span className="flex items-center justify-center">
|
130 |
+
<Trash2 className="size-4" />
|
131 |
</span>
|
132 |
<b>{t('delete', { keyPrefix: 'common' })}</b>
|
133 |
</Flex>
|
|
|
138 |
onClick: handleShowMoveFileModal,
|
139 |
label: (
|
140 |
<Flex gap={10}>
|
141 |
+
<span className="flex items-center justify-center">
|
142 |
+
<FolderInput className="size-4"></FolderInput>
|
143 |
</span>
|
144 |
<b>{t('move', { keyPrefix: 'common' })}</b>
|
145 |
</Flex>
|
web/src/pages/files/files-table.tsx
ADDED
@@ -0,0 +1,343 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
'use client';
|
2 |
+
|
3 |
+
import {
|
4 |
+
ColumnDef,
|
5 |
+
ColumnFiltersState,
|
6 |
+
SortingState,
|
7 |
+
VisibilityState,
|
8 |
+
flexRender,
|
9 |
+
getCoreRowModel,
|
10 |
+
getFilteredRowModel,
|
11 |
+
getSortedRowModel,
|
12 |
+
useReactTable,
|
13 |
+
} from '@tanstack/react-table';
|
14 |
+
import { ArrowUpDown, MoreHorizontal, Pencil } from 'lucide-react';
|
15 |
+
import * as React from 'react';
|
16 |
+
|
17 |
+
import SvgIcon from '@/components/svg-icon';
|
18 |
+
import { TableEmpty, TableSkeleton } from '@/components/table-skeleton';
|
19 |
+
import { Button } from '@/components/ui/button';
|
20 |
+
import { Checkbox } from '@/components/ui/checkbox';
|
21 |
+
import {
|
22 |
+
DropdownMenu,
|
23 |
+
DropdownMenuContent,
|
24 |
+
DropdownMenuItem,
|
25 |
+
DropdownMenuLabel,
|
26 |
+
DropdownMenuSeparator,
|
27 |
+
DropdownMenuTrigger,
|
28 |
+
} from '@/components/ui/dropdown-menu';
|
29 |
+
import { Switch } from '@/components/ui/switch';
|
30 |
+
import {
|
31 |
+
Table,
|
32 |
+
TableBody,
|
33 |
+
TableCell,
|
34 |
+
TableHead,
|
35 |
+
TableHeader,
|
36 |
+
TableRow,
|
37 |
+
} from '@/components/ui/table';
|
38 |
+
import {
|
39 |
+
Tooltip,
|
40 |
+
TooltipContent,
|
41 |
+
TooltipTrigger,
|
42 |
+
} from '@/components/ui/tooltip';
|
43 |
+
import { useFetchFileList } from '@/hooks/file-manager-hooks';
|
44 |
+
import { IFile } from '@/interfaces/database/file-manager';
|
45 |
+
import { cn } from '@/lib/utils';
|
46 |
+
import { formatFileSize } from '@/utils/common-util';
|
47 |
+
import { formatDate } from '@/utils/date';
|
48 |
+
import { getExtension } from '@/utils/document-util';
|
49 |
+
import { useMemo } from 'react';
|
50 |
+
import { useTranslation } from 'react-i18next';
|
51 |
+
import { useNavigateToOtherFolder } from './hooks';
|
52 |
+
|
53 |
+
export function FilesTable() {
|
54 |
+
const [sorting, setSorting] = React.useState<SortingState>([]);
|
55 |
+
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
|
56 |
+
[],
|
57 |
+
);
|
58 |
+
const [columnVisibility, setColumnVisibility] =
|
59 |
+
React.useState<VisibilityState>({});
|
60 |
+
const [rowSelection, setRowSelection] = React.useState({});
|
61 |
+
const { t } = useTranslation('translation', {
|
62 |
+
keyPrefix: 'fileManager',
|
63 |
+
});
|
64 |
+
const navigateToOtherFolder = useNavigateToOtherFolder();
|
65 |
+
|
66 |
+
const { pagination, data, loading, setPagination } = useFetchFileList();
|
67 |
+
|
68 |
+
const columns: ColumnDef<IFile>[] = [
|
69 |
+
{
|
70 |
+
id: 'select',
|
71 |
+
header: ({ table }) => (
|
72 |
+
<Checkbox
|
73 |
+
checked={
|
74 |
+
table.getIsAllPageRowsSelected() ||
|
75 |
+
(table.getIsSomePageRowsSelected() && 'indeterminate')
|
76 |
+
}
|
77 |
+
onCheckedChange={(value) => table.toggleAllPageRowsSelected(!!value)}
|
78 |
+
aria-label="Select all"
|
79 |
+
/>
|
80 |
+
),
|
81 |
+
cell: ({ row }) => (
|
82 |
+
<Checkbox
|
83 |
+
checked={row.getIsSelected()}
|
84 |
+
onCheckedChange={(value) => row.toggleSelected(!!value)}
|
85 |
+
aria-label="Select row"
|
86 |
+
/>
|
87 |
+
),
|
88 |
+
enableSorting: false,
|
89 |
+
enableHiding: false,
|
90 |
+
},
|
91 |
+
{
|
92 |
+
accessorKey: 'name',
|
93 |
+
header: ({ column }) => {
|
94 |
+
return (
|
95 |
+
<Button
|
96 |
+
variant="ghost"
|
97 |
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
98 |
+
>
|
99 |
+
{t('name')}
|
100 |
+
<ArrowUpDown />
|
101 |
+
</Button>
|
102 |
+
);
|
103 |
+
},
|
104 |
+
meta: { cellClassName: 'max-w-[20vw]' },
|
105 |
+
cell: ({ row }) => {
|
106 |
+
const name: string = row.getValue('name');
|
107 |
+
const type = row.original.type;
|
108 |
+
const id = row.original.id;
|
109 |
+
const isFolder = type === 'folder';
|
110 |
+
|
111 |
+
const handleNameClick = () => {
|
112 |
+
if (isFolder) {
|
113 |
+
navigateToOtherFolder(id);
|
114 |
+
}
|
115 |
+
};
|
116 |
+
|
117 |
+
return (
|
118 |
+
<Tooltip>
|
119 |
+
<TooltipTrigger asChild>
|
120 |
+
<div className="flex gap-2">
|
121 |
+
<SvgIcon
|
122 |
+
name={`file-icon/${isFolder ? 'folder' : getExtension(name)}`}
|
123 |
+
width={24}
|
124 |
+
></SvgIcon>
|
125 |
+
<span
|
126 |
+
className={cn('truncate', { ['cursor-pointer']: isFolder })}
|
127 |
+
onClick={handleNameClick}
|
128 |
+
>
|
129 |
+
{name}
|
130 |
+
</span>
|
131 |
+
</div>
|
132 |
+
</TooltipTrigger>
|
133 |
+
<TooltipContent>
|
134 |
+
<p>{name}</p>
|
135 |
+
</TooltipContent>
|
136 |
+
</Tooltip>
|
137 |
+
);
|
138 |
+
},
|
139 |
+
},
|
140 |
+
{
|
141 |
+
accessorKey: 'create_time',
|
142 |
+
header: ({ column }) => {
|
143 |
+
return (
|
144 |
+
<Button
|
145 |
+
variant="ghost"
|
146 |
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
147 |
+
>
|
148 |
+
{t('uploadDate')}
|
149 |
+
<ArrowUpDown />
|
150 |
+
</Button>
|
151 |
+
);
|
152 |
+
},
|
153 |
+
cell: ({ row }) => (
|
154 |
+
<div className="lowercase">
|
155 |
+
{formatDate(row.getValue('create_time'))}
|
156 |
+
</div>
|
157 |
+
),
|
158 |
+
},
|
159 |
+
{
|
160 |
+
accessorKey: 'size',
|
161 |
+
header: ({ column }) => {
|
162 |
+
return (
|
163 |
+
<Button
|
164 |
+
variant="ghost"
|
165 |
+
onClick={() => column.toggleSorting(column.getIsSorted() === 'asc')}
|
166 |
+
>
|
167 |
+
{t('size')}
|
168 |
+
<ArrowUpDown />
|
169 |
+
</Button>
|
170 |
+
);
|
171 |
+
},
|
172 |
+
cell: ({ row }) => (
|
173 |
+
<div className="capitalize">{formatFileSize(row.getValue('size'))}</div>
|
174 |
+
),
|
175 |
+
},
|
176 |
+
{
|
177 |
+
accessorKey: 'kbs_info',
|
178 |
+
header: t('knowledgeBase'),
|
179 |
+
cell: ({ row }) => (
|
180 |
+
<Button variant="destructive" size={'sm'}>
|
181 |
+
{row.getValue('kbs_info')}
|
182 |
+
</Button>
|
183 |
+
),
|
184 |
+
},
|
185 |
+
{
|
186 |
+
id: 'actions',
|
187 |
+
header: t('action'),
|
188 |
+
enableHiding: false,
|
189 |
+
cell: ({ row }) => {
|
190 |
+
const payment = row.original;
|
191 |
+
|
192 |
+
return (
|
193 |
+
<section className="flex gap-4 items-center">
|
194 |
+
<Switch id="airplane-mode" />
|
195 |
+
<Button variant="secondary" size={'icon'}>
|
196 |
+
<Pencil />
|
197 |
+
</Button>
|
198 |
+
<DropdownMenu>
|
199 |
+
<DropdownMenuTrigger asChild>
|
200 |
+
<Button variant="secondary" size={'icon'}>
|
201 |
+
<MoreHorizontal />
|
202 |
+
</Button>
|
203 |
+
</DropdownMenuTrigger>
|
204 |
+
<DropdownMenuContent align="end">
|
205 |
+
<DropdownMenuLabel>Actions</DropdownMenuLabel>
|
206 |
+
<DropdownMenuItem
|
207 |
+
onClick={() => navigator.clipboard.writeText(payment.id)}
|
208 |
+
>
|
209 |
+
Copy payment ID
|
210 |
+
</DropdownMenuItem>
|
211 |
+
<DropdownMenuSeparator />
|
212 |
+
<DropdownMenuItem>View customer</DropdownMenuItem>
|
213 |
+
<DropdownMenuItem>View payment details</DropdownMenuItem>
|
214 |
+
</DropdownMenuContent>
|
215 |
+
</DropdownMenu>
|
216 |
+
</section>
|
217 |
+
);
|
218 |
+
},
|
219 |
+
},
|
220 |
+
];
|
221 |
+
|
222 |
+
const currentPagination = useMemo(() => {
|
223 |
+
return {
|
224 |
+
pageIndex: (pagination.current || 1) - 1,
|
225 |
+
pageSize: pagination.pageSize || 10,
|
226 |
+
};
|
227 |
+
}, [pagination]);
|
228 |
+
|
229 |
+
const table = useReactTable({
|
230 |
+
data: data?.files || [],
|
231 |
+
columns,
|
232 |
+
onSortingChange: setSorting,
|
233 |
+
onColumnFiltersChange: setColumnFilters,
|
234 |
+
getCoreRowModel: getCoreRowModel(),
|
235 |
+
// getPaginationRowModel: getPaginationRowModel(),
|
236 |
+
getSortedRowModel: getSortedRowModel(),
|
237 |
+
getFilteredRowModel: getFilteredRowModel(),
|
238 |
+
onColumnVisibilityChange: setColumnVisibility,
|
239 |
+
onRowSelectionChange: setRowSelection,
|
240 |
+
onPaginationChange: (updaterOrValue: any) => {
|
241 |
+
if (typeof updaterOrValue === 'function') {
|
242 |
+
const nextPagination = updaterOrValue(currentPagination);
|
243 |
+
setPagination({
|
244 |
+
page: nextPagination.pageIndex + 1,
|
245 |
+
pageSize: nextPagination.pageSize,
|
246 |
+
});
|
247 |
+
} else {
|
248 |
+
setPagination({
|
249 |
+
page: updaterOrValue.pageIndex,
|
250 |
+
pageSize: updaterOrValue.pageSize,
|
251 |
+
});
|
252 |
+
}
|
253 |
+
},
|
254 |
+
manualPagination: true, //we're doing manual "server-side" pagination
|
255 |
+
|
256 |
+
state: {
|
257 |
+
sorting,
|
258 |
+
columnFilters,
|
259 |
+
columnVisibility,
|
260 |
+
rowSelection,
|
261 |
+
pagination: currentPagination,
|
262 |
+
},
|
263 |
+
rowCount: data?.total ?? 0,
|
264 |
+
debugTable: true,
|
265 |
+
});
|
266 |
+
|
267 |
+
return (
|
268 |
+
<div className="w-full">
|
269 |
+
<div className="rounded-md border">
|
270 |
+
<Table>
|
271 |
+
<TableHeader>
|
272 |
+
{table.getHeaderGroups().map((headerGroup) => (
|
273 |
+
<TableRow key={headerGroup.id}>
|
274 |
+
{headerGroup.headers.map((header) => {
|
275 |
+
return (
|
276 |
+
<TableHead key={header.id}>
|
277 |
+
{header.isPlaceholder
|
278 |
+
? null
|
279 |
+
: flexRender(
|
280 |
+
header.column.columnDef.header,
|
281 |
+
header.getContext(),
|
282 |
+
)}
|
283 |
+
</TableHead>
|
284 |
+
);
|
285 |
+
})}
|
286 |
+
</TableRow>
|
287 |
+
))}
|
288 |
+
</TableHeader>
|
289 |
+
<TableBody>
|
290 |
+
{loading ? (
|
291 |
+
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
|
292 |
+
) : table.getRowModel().rows?.length ? (
|
293 |
+
table.getRowModel().rows.map((row) => (
|
294 |
+
<TableRow
|
295 |
+
key={row.id}
|
296 |
+
data-state={row.getIsSelected() && 'selected'}
|
297 |
+
>
|
298 |
+
{row.getVisibleCells().map((cell) => (
|
299 |
+
<TableCell
|
300 |
+
key={cell.id}
|
301 |
+
className={cell.column.columnDef.meta?.cellClassName}
|
302 |
+
>
|
303 |
+
{flexRender(
|
304 |
+
cell.column.columnDef.cell,
|
305 |
+
cell.getContext(),
|
306 |
+
)}
|
307 |
+
</TableCell>
|
308 |
+
))}
|
309 |
+
</TableRow>
|
310 |
+
))
|
311 |
+
) : (
|
312 |
+
<TableEmpty columnsLength={columns.length}></TableEmpty>
|
313 |
+
)}
|
314 |
+
</TableBody>
|
315 |
+
</Table>
|
316 |
+
</div>
|
317 |
+
<div className="flex items-center justify-end space-x-2 py-4">
|
318 |
+
<div className="flex-1 text-sm text-muted-foreground">
|
319 |
+
{table.getFilteredSelectedRowModel().rows.length} of {data?.total}{' '}
|
320 |
+
row(s) selected.
|
321 |
+
</div>
|
322 |
+
<div className="space-x-2">
|
323 |
+
<Button
|
324 |
+
variant="outline"
|
325 |
+
size="sm"
|
326 |
+
onClick={() => table.previousPage()}
|
327 |
+
disabled={!table.getCanPreviousPage()}
|
328 |
+
>
|
329 |
+
Previous
|
330 |
+
</Button>
|
331 |
+
<Button
|
332 |
+
variant="outline"
|
333 |
+
size="sm"
|
334 |
+
onClick={() => table.nextPage()}
|
335 |
+
disabled={!table.getCanNextPage()}
|
336 |
+
>
|
337 |
+
Next
|
338 |
+
</Button>
|
339 |
+
</div>
|
340 |
+
</div>
|
341 |
+
</div>
|
342 |
+
);
|
343 |
+
}
|
web/src/pages/files/hooks.ts
ADDED
@@ -0,0 +1,294 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
2 |
+
import {
|
3 |
+
useConnectToKnowledge,
|
4 |
+
useCreateFolder,
|
5 |
+
useDeleteFile,
|
6 |
+
useFetchParentFolderList,
|
7 |
+
useMoveFile,
|
8 |
+
useRenameFile,
|
9 |
+
useUploadFile,
|
10 |
+
} from '@/hooks/file-manager-hooks';
|
11 |
+
import { IFile } from '@/interfaces/database/file-manager';
|
12 |
+
import { TableRowSelection } from 'antd/es/table/interface';
|
13 |
+
import { UploadFile } from 'antd/lib';
|
14 |
+
import { useCallback, useMemo, useState } from 'react';
|
15 |
+
import { useNavigate, useSearchParams } from 'umi';
|
16 |
+
|
17 |
+
export const useGetFolderId = () => {
|
18 |
+
const [searchParams] = useSearchParams();
|
19 |
+
const id = searchParams.get('folderId') as string;
|
20 |
+
|
21 |
+
return id ?? '';
|
22 |
+
};
|
23 |
+
|
24 |
+
export const useGetRowSelection = () => {
|
25 |
+
const [selectedRowKeys, setSelectedRowKeys] = useState<React.Key[]>([]);
|
26 |
+
|
27 |
+
const rowSelection: TableRowSelection<IFile> = {
|
28 |
+
selectedRowKeys,
|
29 |
+
getCheckboxProps: (record) => {
|
30 |
+
return { disabled: record.source_type === 'knowledgebase' };
|
31 |
+
},
|
32 |
+
onChange: (newSelectedRowKeys: React.Key[]) => {
|
33 |
+
setSelectedRowKeys(newSelectedRowKeys);
|
34 |
+
},
|
35 |
+
};
|
36 |
+
|
37 |
+
return { rowSelection, setSelectedRowKeys };
|
38 |
+
};
|
39 |
+
|
40 |
+
export const useNavigateToOtherFolder = () => {
|
41 |
+
const navigate = useNavigate();
|
42 |
+
const navigateToOtherFolder = useCallback(
|
43 |
+
(folderId: string) => {
|
44 |
+
navigate(`/file?folderId=${folderId}`);
|
45 |
+
},
|
46 |
+
[navigate],
|
47 |
+
);
|
48 |
+
|
49 |
+
return navigateToOtherFolder;
|
50 |
+
};
|
51 |
+
|
52 |
+
export const useRenameCurrentFile = () => {
|
53 |
+
const [file, setFile] = useState<IFile>({} as IFile);
|
54 |
+
const {
|
55 |
+
visible: fileRenameVisible,
|
56 |
+
hideModal: hideFileRenameModal,
|
57 |
+
showModal: showFileRenameModal,
|
58 |
+
} = useSetModalState();
|
59 |
+
const { renameFile, loading } = useRenameFile();
|
60 |
+
|
61 |
+
const onFileRenameOk = useCallback(
|
62 |
+
async (name: string) => {
|
63 |
+
const ret = await renameFile({
|
64 |
+
fileId: file.id,
|
65 |
+
name,
|
66 |
+
});
|
67 |
+
|
68 |
+
if (ret === 0) {
|
69 |
+
hideFileRenameModal();
|
70 |
+
}
|
71 |
+
},
|
72 |
+
[renameFile, file, hideFileRenameModal],
|
73 |
+
);
|
74 |
+
|
75 |
+
const handleShowFileRenameModal = useCallback(
|
76 |
+
async (record: IFile) => {
|
77 |
+
setFile(record);
|
78 |
+
showFileRenameModal();
|
79 |
+
},
|
80 |
+
[showFileRenameModal],
|
81 |
+
);
|
82 |
+
|
83 |
+
return {
|
84 |
+
fileRenameLoading: loading,
|
85 |
+
initialFileName: file.name,
|
86 |
+
onFileRenameOk,
|
87 |
+
fileRenameVisible,
|
88 |
+
hideFileRenameModal,
|
89 |
+
showFileRenameModal: handleShowFileRenameModal,
|
90 |
+
};
|
91 |
+
};
|
92 |
+
|
93 |
+
export const useSelectBreadcrumbItems = () => {
|
94 |
+
const parentFolderList = useFetchParentFolderList();
|
95 |
+
|
96 |
+
return parentFolderList.length === 1
|
97 |
+
? []
|
98 |
+
: parentFolderList.map((x) => ({
|
99 |
+
title: x.name === '/' ? 'root' : x.name,
|
100 |
+
path: `/file?folderId=${x.id}`,
|
101 |
+
}));
|
102 |
+
};
|
103 |
+
|
104 |
+
export const useHandleCreateFolder = () => {
|
105 |
+
const {
|
106 |
+
visible: folderCreateModalVisible,
|
107 |
+
hideModal: hideFolderCreateModal,
|
108 |
+
showModal: showFolderCreateModal,
|
109 |
+
} = useSetModalState();
|
110 |
+
const { createFolder, loading } = useCreateFolder();
|
111 |
+
const id = useGetFolderId();
|
112 |
+
|
113 |
+
const onFolderCreateOk = useCallback(
|
114 |
+
async (name: string) => {
|
115 |
+
const ret = await createFolder({ parentId: id, name });
|
116 |
+
|
117 |
+
if (ret === 0) {
|
118 |
+
hideFolderCreateModal();
|
119 |
+
}
|
120 |
+
},
|
121 |
+
[createFolder, hideFolderCreateModal, id],
|
122 |
+
);
|
123 |
+
|
124 |
+
return {
|
125 |
+
folderCreateLoading: loading,
|
126 |
+
onFolderCreateOk,
|
127 |
+
folderCreateModalVisible,
|
128 |
+
hideFolderCreateModal,
|
129 |
+
showFolderCreateModal,
|
130 |
+
};
|
131 |
+
};
|
132 |
+
|
133 |
+
export const useHandleDeleteFile = (
|
134 |
+
fileIds: string[],
|
135 |
+
setSelectedRowKeys: (keys: string[]) => void,
|
136 |
+
) => {
|
137 |
+
const { deleteFile: removeDocument } = useDeleteFile();
|
138 |
+
const showDeleteConfirm = useShowDeleteConfirm();
|
139 |
+
const parentId = useGetFolderId();
|
140 |
+
|
141 |
+
const handleRemoveFile = () => {
|
142 |
+
showDeleteConfirm({
|
143 |
+
onOk: async () => {
|
144 |
+
const code = await removeDocument({ fileIds, parentId });
|
145 |
+
if (code === 0) {
|
146 |
+
setSelectedRowKeys([]);
|
147 |
+
}
|
148 |
+
return;
|
149 |
+
},
|
150 |
+
});
|
151 |
+
};
|
152 |
+
|
153 |
+
return { handleRemoveFile };
|
154 |
+
};
|
155 |
+
|
156 |
+
export const useHandleUploadFile = () => {
|
157 |
+
const {
|
158 |
+
visible: fileUploadVisible,
|
159 |
+
hideModal: hideFileUploadModal,
|
160 |
+
showModal: showFileUploadModal,
|
161 |
+
} = useSetModalState();
|
162 |
+
const { uploadFile, loading } = useUploadFile();
|
163 |
+
const id = useGetFolderId();
|
164 |
+
|
165 |
+
const onFileUploadOk = useCallback(
|
166 |
+
async (fileList: UploadFile[]): Promise<number | undefined> => {
|
167 |
+
if (fileList.length > 0) {
|
168 |
+
const ret: number = await uploadFile({ fileList, parentId: id });
|
169 |
+
if (ret === 0) {
|
170 |
+
hideFileUploadModal();
|
171 |
+
}
|
172 |
+
return ret;
|
173 |
+
}
|
174 |
+
},
|
175 |
+
[uploadFile, hideFileUploadModal, id],
|
176 |
+
);
|
177 |
+
|
178 |
+
return {
|
179 |
+
fileUploadLoading: loading,
|
180 |
+
onFileUploadOk,
|
181 |
+
fileUploadVisible,
|
182 |
+
hideFileUploadModal,
|
183 |
+
showFileUploadModal,
|
184 |
+
};
|
185 |
+
};
|
186 |
+
|
187 |
+
export const useHandleConnectToKnowledge = () => {
|
188 |
+
const {
|
189 |
+
visible: connectToKnowledgeVisible,
|
190 |
+
hideModal: hideConnectToKnowledgeModal,
|
191 |
+
showModal: showConnectToKnowledgeModal,
|
192 |
+
} = useSetModalState();
|
193 |
+
const { connectFileToKnowledge: connectToKnowledge, loading } =
|
194 |
+
useConnectToKnowledge();
|
195 |
+
const [record, setRecord] = useState<IFile>({} as IFile);
|
196 |
+
|
197 |
+
const initialValue = useMemo(() => {
|
198 |
+
return Array.isArray(record?.kbs_info)
|
199 |
+
? record?.kbs_info?.map((x) => x.kb_id)
|
200 |
+
: [];
|
201 |
+
}, [record?.kbs_info]);
|
202 |
+
|
203 |
+
const onConnectToKnowledgeOk = useCallback(
|
204 |
+
async (knowledgeIds: string[]) => {
|
205 |
+
const ret = await connectToKnowledge({
|
206 |
+
fileIds: [record.id],
|
207 |
+
kbIds: knowledgeIds,
|
208 |
+
});
|
209 |
+
|
210 |
+
if (ret === 0) {
|
211 |
+
hideConnectToKnowledgeModal();
|
212 |
+
}
|
213 |
+
return ret;
|
214 |
+
},
|
215 |
+
[connectToKnowledge, hideConnectToKnowledgeModal, record.id],
|
216 |
+
);
|
217 |
+
|
218 |
+
const handleShowConnectToKnowledgeModal = useCallback(
|
219 |
+
(record: IFile) => {
|
220 |
+
setRecord(record);
|
221 |
+
showConnectToKnowledgeModal();
|
222 |
+
},
|
223 |
+
[showConnectToKnowledgeModal],
|
224 |
+
);
|
225 |
+
|
226 |
+
return {
|
227 |
+
initialValue,
|
228 |
+
connectToKnowledgeLoading: loading,
|
229 |
+
onConnectToKnowledgeOk,
|
230 |
+
connectToKnowledgeVisible,
|
231 |
+
hideConnectToKnowledgeModal,
|
232 |
+
showConnectToKnowledgeModal: handleShowConnectToKnowledgeModal,
|
233 |
+
};
|
234 |
+
};
|
235 |
+
|
236 |
+
export const useHandleBreadcrumbClick = () => {
|
237 |
+
const navigate = useNavigate();
|
238 |
+
|
239 |
+
const handleBreadcrumbClick = useCallback(
|
240 |
+
(path?: string) => {
|
241 |
+
if (path) {
|
242 |
+
navigate(path);
|
243 |
+
}
|
244 |
+
},
|
245 |
+
[navigate],
|
246 |
+
);
|
247 |
+
|
248 |
+
return { handleBreadcrumbClick };
|
249 |
+
};
|
250 |
+
|
251 |
+
export const useHandleMoveFile = (
|
252 |
+
setSelectedRowKeys: (keys: string[]) => void,
|
253 |
+
) => {
|
254 |
+
const {
|
255 |
+
visible: moveFileVisible,
|
256 |
+
hideModal: hideMoveFileModal,
|
257 |
+
showModal: showMoveFileModal,
|
258 |
+
} = useSetModalState();
|
259 |
+
const { moveFile, loading } = useMoveFile();
|
260 |
+
const [sourceFileIds, setSourceFileIds] = useState<string[]>([]);
|
261 |
+
|
262 |
+
const onMoveFileOk = useCallback(
|
263 |
+
async (targetFolderId: string) => {
|
264 |
+
const ret = await moveFile({
|
265 |
+
src_file_ids: sourceFileIds,
|
266 |
+
dest_file_id: targetFolderId,
|
267 |
+
});
|
268 |
+
|
269 |
+
if (ret === 0) {
|
270 |
+
setSelectedRowKeys([]);
|
271 |
+
hideMoveFileModal();
|
272 |
+
}
|
273 |
+
return ret;
|
274 |
+
},
|
275 |
+
[moveFile, hideMoveFileModal, sourceFileIds, setSelectedRowKeys],
|
276 |
+
);
|
277 |
+
|
278 |
+
const handleShowMoveFileModal = useCallback(
|
279 |
+
(ids: string[]) => {
|
280 |
+
setSourceFileIds(ids);
|
281 |
+
showMoveFileModal();
|
282 |
+
},
|
283 |
+
[showMoveFileModal],
|
284 |
+
);
|
285 |
+
|
286 |
+
return {
|
287 |
+
initialValue: '',
|
288 |
+
moveFileLoading: loading,
|
289 |
+
onMoveFileOk,
|
290 |
+
moveFileVisible,
|
291 |
+
hideMoveFileModal,
|
292 |
+
showMoveFileModal: handleShowMoveFileModal,
|
293 |
+
};
|
294 |
+
};
|
web/src/pages/files/index.tsx
ADDED
@@ -0,0 +1,15 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import ListFilterBar from '@/components/list-filter-bar';
|
2 |
+
import { Upload } from 'lucide-react';
|
3 |
+
import { FilesTable } from './files-table';
|
4 |
+
|
5 |
+
export default function Files() {
|
6 |
+
return (
|
7 |
+
<section className="p-8">
|
8 |
+
<ListFilterBar title="Files">
|
9 |
+
<Upload />
|
10 |
+
Upload file
|
11 |
+
</ListFilterBar>
|
12 |
+
<FilesTable></FilesTable>
|
13 |
+
</section>
|
14 |
+
);
|
15 |
+
}
|
web/src/pages/flow/canvas/index.tsx
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
import {
|
2 |
Tooltip,
|
3 |
TooltipContent,
|
4 |
-
TooltipProvider,
|
5 |
TooltipTrigger,
|
6 |
} from '@/components/ui/tooltip';
|
7 |
import {
|
@@ -175,24 +174,20 @@ function FlowCanvas({ drawerVisible, hideDrawer }: IProps) {
|
|
175 |
<Background />
|
176 |
<Controls>
|
177 |
<ControlButton onClick={handleImportJson}>
|
178 |
-
<
|
179 |
-
<
|
180 |
-
<
|
181 |
-
|
182 |
-
|
183 |
-
|
184 |
-
</Tooltip>
|
185 |
-
</TooltipProvider>
|
186 |
</ControlButton>
|
187 |
<ControlButton onClick={handleExportJson}>
|
188 |
-
<
|
189 |
-
<
|
190 |
-
<
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
</Tooltip>
|
195 |
-
</TooltipProvider>
|
196 |
</ControlButton>
|
197 |
</Controls>
|
198 |
</ReactFlow>
|
|
|
1 |
import {
|
2 |
Tooltip,
|
3 |
TooltipContent,
|
|
|
4 |
TooltipTrigger,
|
5 |
} from '@/components/ui/tooltip';
|
6 |
import {
|
|
|
174 |
<Background />
|
175 |
<Controls>
|
176 |
<ControlButton onClick={handleImportJson}>
|
177 |
+
<Tooltip>
|
178 |
+
<TooltipTrigger asChild>
|
179 |
+
<FolderInput className={controlIconClassname} />
|
180 |
+
</TooltipTrigger>
|
181 |
+
<TooltipContent>Import</TooltipContent>
|
182 |
+
</Tooltip>
|
|
|
|
|
183 |
</ControlButton>
|
184 |
<ControlButton onClick={handleExportJson}>
|
185 |
+
<Tooltip>
|
186 |
+
<TooltipTrigger asChild>
|
187 |
+
<FolderOutput className={controlIconClassname} />
|
188 |
+
</TooltipTrigger>
|
189 |
+
<TooltipContent>Export</TooltipContent>
|
190 |
+
</Tooltip>
|
|
|
|
|
191 |
</ControlButton>
|
192 |
</Controls>
|
193 |
</ReactFlow>
|
web/src/pages/flow/flow-tooltip.tsx
CHANGED
@@ -1,7 +1,6 @@
|
|
1 |
import {
|
2 |
Tooltip,
|
3 |
TooltipContent,
|
4 |
-
TooltipProvider,
|
5 |
TooltipTrigger,
|
6 |
} from '@/components/ui/tooltip';
|
7 |
import { PropsWithChildren } from 'react';
|
@@ -10,13 +9,11 @@ import { useTranslation } from 'react-i18next';
|
|
10 |
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
11 |
const { t } = useTranslation();
|
12 |
return (
|
13 |
-
<
|
14 |
-
<
|
15 |
-
|
16 |
-
<
|
17 |
-
|
18 |
-
|
19 |
-
</Tooltip>
|
20 |
-
</TooltipProvider>
|
21 |
);
|
22 |
};
|
|
|
1 |
import {
|
2 |
Tooltip,
|
3 |
TooltipContent,
|
|
|
4 |
TooltipTrigger,
|
5 |
} from '@/components/ui/tooltip';
|
6 |
import { PropsWithChildren } from 'react';
|
|
|
9 |
export const RunTooltip = ({ children }: PropsWithChildren) => {
|
10 |
const { t } = useTranslation();
|
11 |
return (
|
12 |
+
<Tooltip>
|
13 |
+
<TooltipTrigger>{children}</TooltipTrigger>
|
14 |
+
<TooltipContent>
|
15 |
+
<p>{t('flow.testRun')}</p>
|
16 |
+
</TooltipContent>
|
17 |
+
</Tooltip>
|
|
|
|
|
18 |
);
|
19 |
};
|
web/src/pages/knowledge/knowledge-card/index.tsx
CHANGED
@@ -40,7 +40,7 @@ const KnowledgeCard = ({ item }: IProps) => {
|
|
40 |
|
41 |
return (
|
42 |
<Badge.Ribbon
|
43 |
-
text={item
|
44 |
color={userInfo.nickname === item.nickname ? '#1677ff' : 'pink'}
|
45 |
className={classNames(styles.ribbon, {
|
46 |
[styles.hideRibbon]: item.permission !== 'team',
|
|
|
40 |
|
41 |
return (
|
42 |
<Badge.Ribbon
|
43 |
+
text={item?.nickname}
|
44 |
color={userInfo.nickname === item.nickname ? '#1677ff' : 'pink'}
|
45 |
className={classNames(styles.ribbon, {
|
46 |
[styles.hideRibbon]: item.permission !== 'team',
|
web/src/pages/login-next/index.tsx
CHANGED
@@ -97,8 +97,8 @@ const Login = () => {
|
|
97 |
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
98 |
|
99 |
return (
|
100 |
-
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')]">
|
101 |
-
<div className="inline-block">
|
102 |
{step === Step.SignIn && <SignInCard></SignInCard>}
|
103 |
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
104 |
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
|
|
97 |
const step = Number((searchParams.get('step') ?? Step.SignIn) as Step);
|
98 |
|
99 |
return (
|
100 |
+
<div className="w-full h-full flex items-center pl-[15%] bg-[url('@/assets/svg/next-login-bg.svg')] bg-cover bg-center">
|
101 |
+
<div className="inline-block bg-colors-background-neutral-standard rounded-lg">
|
102 |
{step === Step.SignIn && <SignInCard></SignInCard>}
|
103 |
{step === Step.SignUp && <SignUpCard></SignUpCard>}
|
104 |
{step === Step.VerifyEmail && <VerifyEmailCard></VerifyEmailCard>}
|
web/src/routes.ts
CHANGED
@@ -7,6 +7,7 @@ export enum Routes {
|
|
7 |
Agent = '/agent',
|
8 |
Search = '/next-search',
|
9 |
Chat = '/next-chat',
|
|
|
10 |
ProfileSetting = '/profile-setting',
|
11 |
DatasetTesting = '/testing',
|
12 |
DatasetSetting = '/setting',
|
@@ -189,6 +190,17 @@ const routes = [
|
|
189 |
},
|
190 |
],
|
191 |
},
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
192 |
{
|
193 |
path: Routes.DatasetBase,
|
194 |
layout: false,
|
|
|
7 |
Agent = '/agent',
|
8 |
Search = '/next-search',
|
9 |
Chat = '/next-chat',
|
10 |
+
Files = '/files',
|
11 |
ProfileSetting = '/profile-setting',
|
12 |
DatasetTesting = '/testing',
|
13 |
DatasetSetting = '/setting',
|
|
|
190 |
},
|
191 |
],
|
192 |
},
|
193 |
+
{
|
194 |
+
path: Routes.Files,
|
195 |
+
layout: false,
|
196 |
+
component: '@/layouts/next',
|
197 |
+
routes: [
|
198 |
+
{
|
199 |
+
path: Routes.Files,
|
200 |
+
component: `@/pages${Routes.Files}`,
|
201 |
+
},
|
202 |
+
],
|
203 |
+
},
|
204 |
{
|
205 |
path: Routes.DatasetBase,
|
206 |
layout: false,
|
web/src/utils/common-util.ts
CHANGED
@@ -113,3 +113,28 @@ export function hexToArrayBuffer(input: string) {
|
|
113 |
|
114 |
return view.buffer;
|
115 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
113 |
|
114 |
return view.buffer;
|
115 |
}
|
116 |
+
|
117 |
+
export function formatFileSize(bytes: number, si = true, dp = 1) {
|
118 |
+
let nextBytes = bytes;
|
119 |
+
const thresh = si ? 1000 : 1024;
|
120 |
+
|
121 |
+
if (Math.abs(bytes) < thresh) {
|
122 |
+
return nextBytes + ' B';
|
123 |
+
}
|
124 |
+
|
125 |
+
const units = si
|
126 |
+
? ['kB', 'MB', 'GB', 'TB', 'PB', 'EB', 'ZB', 'YB']
|
127 |
+
: ['KiB', 'MiB', 'GiB', 'TiB', 'PiB', 'EiB', 'ZiB', 'YiB'];
|
128 |
+
let u = -1;
|
129 |
+
const r = 10 ** dp;
|
130 |
+
|
131 |
+
do {
|
132 |
+
nextBytes /= thresh;
|
133 |
+
++u;
|
134 |
+
} while (
|
135 |
+
Math.round(Math.abs(nextBytes) * r) / r >= thresh &&
|
136 |
+
u < units.length - 1
|
137 |
+
);
|
138 |
+
|
139 |
+
return nextBytes.toFixed(dp) + ' ' + units[u];
|
140 |
+
}
|
web/typings.d.ts
CHANGED
@@ -1,5 +1,13 @@
|
|
|
|
1 |
declare module 'lodash';
|
2 |
|
3 |
declare global {
|
4 |
type Nullable<T> = T | null;
|
5 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1 |
+
import '@tanstack/react-table';
|
2 |
declare module 'lodash';
|
3 |
|
4 |
declare global {
|
5 |
type Nullable<T> = T | null;
|
6 |
}
|
7 |
+
|
8 |
+
declare module '@tanstack/react-table' {
|
9 |
+
interface ColumnMeta {
|
10 |
+
headerClassName?: string;
|
11 |
+
cellClassName?: string;
|
12 |
+
}
|
13 |
+
}
|