mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-05 15:40:43 +08:00
### What problem does this PR solve? Feat: Create a folder #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
parent
94181a990b
commit
51d9bde5a3
@ -3,18 +3,19 @@ import React, {
|
||||
ChangeEventHandler,
|
||||
FunctionComponent,
|
||||
PropsWithChildren,
|
||||
ReactNode,
|
||||
} from 'react';
|
||||
import { Button, ButtonProps } from './ui/button';
|
||||
import { SearchInput } from './ui/input';
|
||||
|
||||
interface IProps {
|
||||
title: string;
|
||||
showDialog?: () => void;
|
||||
title?: string;
|
||||
FilterPopover?: FunctionComponent<any>;
|
||||
searchString?: string;
|
||||
onSearchChange?: ChangeEventHandler<HTMLInputElement>;
|
||||
count?: number;
|
||||
showFilter?: boolean;
|
||||
leftPanel?: ReactNode;
|
||||
}
|
||||
|
||||
const FilterButton = React.forwardRef<
|
||||
@ -31,16 +32,16 @@ const FilterButton = React.forwardRef<
|
||||
export default function ListFilterBar({
|
||||
title,
|
||||
children,
|
||||
showDialog,
|
||||
FilterPopover,
|
||||
searchString,
|
||||
onSearchChange,
|
||||
count,
|
||||
showFilter = true,
|
||||
leftPanel,
|
||||
}: PropsWithChildren<IProps>) {
|
||||
return (
|
||||
<div className="flex justify-between mb-6">
|
||||
<span className="text-3xl font-bold ">{title}</span>
|
||||
<div className="flex justify-between mb-6 items-center">
|
||||
<span className="text-3xl font-bold ">{leftPanel || title}</span>
|
||||
<div className="flex gap-4 items-center">
|
||||
{showFilter &&
|
||||
(FilterPopover ? (
|
||||
@ -55,9 +56,7 @@ export default function ListFilterBar({
|
||||
value={searchString}
|
||||
onChange={onSearchChange}
|
||||
></SearchInput>
|
||||
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
||||
{children}
|
||||
</Button>
|
||||
{children}
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
|
95
web/src/components/ui/single-tree-select.tsx
Normal file
95
web/src/components/ui/single-tree-select.tsx
Normal file
@ -0,0 +1,95 @@
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { ChevronDown, X } from 'lucide-react';
|
||||
import React, { ReactNode, useState } from 'react';
|
||||
|
||||
export type TreeNode = {
|
||||
id: number;
|
||||
label: ReactNode;
|
||||
children?: TreeNode[];
|
||||
};
|
||||
|
||||
type SingleSelectTreeDropdownProps = {
|
||||
allowDelete?: boolean;
|
||||
treeData: TreeNode[];
|
||||
};
|
||||
|
||||
const SingleTreeSelect: React.FC<SingleSelectTreeDropdownProps> = ({
|
||||
allowDelete = false,
|
||||
treeData,
|
||||
}) => {
|
||||
const [selectedOption, setSelectedOption] = useState<TreeNode | null>(null);
|
||||
|
||||
const handleSelect = (option: TreeNode) => {
|
||||
setSelectedOption(option);
|
||||
};
|
||||
|
||||
const handleDelete = (e: React.MouseEvent<HTMLButtonElement>) => {
|
||||
e.stopPropagation();
|
||||
console.log(
|
||||
'Delete button clicked. Current selected option:',
|
||||
selectedOption,
|
||||
);
|
||||
setSelectedOption(null);
|
||||
console.log('After deletion, selected option:', selectedOption);
|
||||
};
|
||||
|
||||
const renderTree = (nodes: TreeNode[]) => {
|
||||
return nodes.map((node) => (
|
||||
<div key={node.id} className="pl-4">
|
||||
<DropdownMenuItem
|
||||
onClick={() => handleSelect(node)}
|
||||
className={`flex items-center ${
|
||||
selectedOption?.id === node.id ? 'bg-gray-500' : ''
|
||||
}`}
|
||||
>
|
||||
<span>{node.label}</span>
|
||||
{node.children && (
|
||||
<ChevronDown className="ml-2 h-4 w-4 text-gray-400" />
|
||||
)}
|
||||
</DropdownMenuItem>
|
||||
{node.children && renderTree(node.children)}
|
||||
</div>
|
||||
));
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="relative">
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<button
|
||||
type="button"
|
||||
className="flex items-center justify-between space-x-1 p-2 border rounded-md focus:outline-none w-full"
|
||||
>
|
||||
{selectedOption ? (
|
||||
<>
|
||||
<span>{selectedOption.label}</span>
|
||||
{allowDelete && (
|
||||
<button
|
||||
type="button"
|
||||
className="ml-2 text-gray-500 hover:text-red-500 focus:outline-none"
|
||||
onClick={handleDelete}
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
'Select an option'
|
||||
)}
|
||||
<ChevronDown className="h-4 w-4" />
|
||||
</button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className=" mt-2 w-56 origin-top-right rounded-md shadow-lg ring-1 ring-black ring-opacity-5 focus:outline-none">
|
||||
{renderTree(treeData)}
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export default SingleTreeSelect;
|
@ -94,6 +94,13 @@ export const useNavigatePage = () => {
|
||||
[getQueryString, id, navigate],
|
||||
);
|
||||
|
||||
const navigateToFiles = useCallback(
|
||||
(folderId?: string) => {
|
||||
navigate(`${Routes.Files}?folderId=${folderId}`);
|
||||
},
|
||||
[navigate],
|
||||
);
|
||||
|
||||
return {
|
||||
navigateToDatasetList,
|
||||
navigateToDataset,
|
||||
@ -109,5 +116,6 @@ export const useNavigatePage = () => {
|
||||
navigateToAgentTemplates,
|
||||
navigateToSearchList,
|
||||
navigateToSearch,
|
||||
navigateToFiles,
|
||||
};
|
||||
};
|
||||
|
@ -1,14 +1,26 @@
|
||||
import { IFolder } from '@/interfaces/database/file-manager';
|
||||
import fileManagerService from '@/services/file-manager-service';
|
||||
import { useMutation, useQueryClient } from '@tanstack/react-query';
|
||||
import { useMutation, useQuery, useQueryClient } from '@tanstack/react-query';
|
||||
import { message } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useSearchParams } from 'umi';
|
||||
import { useSetPaginationParams } from './route-hook';
|
||||
|
||||
export const enum FileApiAction {
|
||||
UploadFile = 'uploadFile',
|
||||
FetchFileList = 'fetchFileList',
|
||||
MoveFile = 'moveFile',
|
||||
CreateFolder = 'createFolder',
|
||||
FetchParentFolderList = 'fetchParentFolderList',
|
||||
}
|
||||
|
||||
export const useGetFolderId = () => {
|
||||
const [searchParams] = useSearchParams();
|
||||
const id = searchParams.get('folderId') as string;
|
||||
|
||||
return id ?? '';
|
||||
};
|
||||
|
||||
export const useUploadFile = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const { t } = useTranslation();
|
||||
@ -46,3 +58,81 @@ export const useUploadFile = () => {
|
||||
|
||||
return { data, loading, uploadFile: mutateAsync };
|
||||
};
|
||||
|
||||
export interface IMoveFileBody {
|
||||
src_file_ids: string[];
|
||||
dest_file_id: string; // target folder id
|
||||
}
|
||||
|
||||
export const useMoveFile = () => {
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.MoveFile],
|
||||
mutationFn: async (params: IMoveFileBody) => {
|
||||
const { data } = await fileManagerService.moveFile(params);
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.operated'));
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, moveFile: mutateAsync };
|
||||
};
|
||||
|
||||
export const useCreateFolder = () => {
|
||||
const { setPaginationParams } = useSetPaginationParams();
|
||||
const queryClient = useQueryClient();
|
||||
const { t } = useTranslation();
|
||||
|
||||
const {
|
||||
data,
|
||||
isPending: loading,
|
||||
mutateAsync,
|
||||
} = useMutation({
|
||||
mutationKey: [FileApiAction.CreateFolder],
|
||||
mutationFn: async (params: { parentId: string; name: string }) => {
|
||||
const { data } = await fileManagerService.createFolder({
|
||||
...params,
|
||||
type: 'folder',
|
||||
});
|
||||
if (data.code === 0) {
|
||||
message.success(t('message.created'));
|
||||
setPaginationParams(1);
|
||||
queryClient.invalidateQueries({
|
||||
queryKey: [FileApiAction.FetchFileList],
|
||||
});
|
||||
}
|
||||
return data.code;
|
||||
},
|
||||
});
|
||||
|
||||
return { data, loading, createFolder: mutateAsync };
|
||||
};
|
||||
|
||||
export const useFetchParentFolderList = () => {
|
||||
const id = useGetFolderId();
|
||||
const { data } = useQuery<IFolder[]>({
|
||||
queryKey: [FileApiAction.FetchParentFolderList, id],
|
||||
initialData: [],
|
||||
enabled: !!id,
|
||||
queryFn: async () => {
|
||||
const { data } = await fileManagerService.getAllParentFolder({
|
||||
fileId: id,
|
||||
});
|
||||
|
||||
return data?.data?.parent_folders?.toReversed() ?? [];
|
||||
},
|
||||
});
|
||||
|
||||
return data;
|
||||
};
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { Plus } from 'lucide-react';
|
||||
@ -11,9 +12,15 @@ export default function Agent() {
|
||||
return (
|
||||
<section>
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar title="Agents" showDialog={navigateToAgentTemplates}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
<ListFilterBar title="Agents">
|
||||
<Button
|
||||
variant={'tertiary'}
|
||||
size={'sm'}
|
||||
onClick={navigateToAgentTemplates}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</div>
|
||||
<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 max-h-[84vh] overflow-auto px-8">
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { DatasetTable } from './dataset-table';
|
||||
import { useHandleUploadDocument } from './use-upload-document';
|
||||
@ -14,9 +15,15 @@ export default function Dataset() {
|
||||
} = useHandleUploadDocument();
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar title="Files" showDialog={showDocumentUploadModal}>
|
||||
<Upload />
|
||||
Upload file
|
||||
<ListFilterBar title="Files">
|
||||
<Button
|
||||
variant={'tertiary'}
|
||||
size={'sm'}
|
||||
onClick={showDocumentUploadModal}
|
||||
>
|
||||
<Upload />
|
||||
Upload file
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
<DatasetTable></DatasetTable>
|
||||
|
||||
|
@ -1,5 +1,6 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request';
|
||||
import { pick } from 'lodash';
|
||||
import { Plus } from 'lucide-react';
|
||||
@ -51,7 +52,6 @@ export default function Datasets() {
|
||||
<section className="p-8 text-foreground">
|
||||
<ListFilterBar
|
||||
title="Datasets"
|
||||
showDialog={showModal}
|
||||
count={ownerIds.length}
|
||||
FilterPopover={({ children }: PropsWithChildren) => (
|
||||
<DatasetsFilterPopover setOwnerIds={setOwnerIds} ownerIds={ownerIds}>
|
||||
@ -61,7 +61,9 @@ export default function Datasets() {
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
<Button variant={'tertiary'} size={'sm'} onClick={showModal}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
</Button>
|
||||
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">
|
||||
|
@ -17,15 +17,18 @@ import {
|
||||
UseHandleConnectToKnowledgeReturnType,
|
||||
UseRenameCurrentFileReturnType,
|
||||
} from './hooks';
|
||||
import { UseMoveDocumentReturnType } from './use-move-file';
|
||||
|
||||
type IProps = Pick<CellContext<IFile, unknown>, 'row'> &
|
||||
Pick<UseHandleConnectToKnowledgeReturnType, 'showConnectToKnowledgeModal'> &
|
||||
Pick<UseRenameCurrentFileReturnType, 'showFileRenameModal'>;
|
||||
Pick<UseRenameCurrentFileReturnType, 'showFileRenameModal'> &
|
||||
Pick<UseMoveDocumentReturnType, 'showMoveFileModal'>;
|
||||
|
||||
export function ActionCell({
|
||||
row,
|
||||
showConnectToKnowledgeModal,
|
||||
showFileRenameModal,
|
||||
showMoveFileModal,
|
||||
}: IProps) {
|
||||
const { t } = useTranslation();
|
||||
const record = row.original;
|
||||
@ -47,6 +50,10 @@ export function ActionCell({
|
||||
showFileRenameModal(record);
|
||||
}, [record, showFileRenameModal]);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(() => {
|
||||
showMoveFileModal([record.id]);
|
||||
}, [record, showMoveFileModal]);
|
||||
|
||||
return (
|
||||
<section className="flex gap-4 items-center">
|
||||
<Button
|
||||
@ -68,9 +75,7 @@ export function ActionCell({
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent align="end">
|
||||
<DropdownMenuItem
|
||||
onClick={() => navigator.clipboard.writeText(record.id)}
|
||||
>
|
||||
<DropdownMenuItem onClick={handleShowMoveFileModal}>
|
||||
{t('common.move')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
|
@ -0,0 +1,70 @@
|
||||
'use client';
|
||||
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { Input } from '@/components/ui/input';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function CreateFolderForm({ hideModal, onOk }: IModalProps<any>) {
|
||||
const { t } = useTranslation();
|
||||
const FormSchema = z.object({
|
||||
name: z
|
||||
.string()
|
||||
.min(1, {
|
||||
message: t('common.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?.(data.name);
|
||||
if (ret) {
|
||||
hideModal?.();
|
||||
}
|
||||
}
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-6"
|
||||
id={TagRenameId}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="name"
|
||||
render={({ field }) => (
|
||||
<FormItem>
|
||||
<FormLabel>{t('common.name')}</FormLabel>
|
||||
<FormControl>
|
||||
<Input
|
||||
placeholder={t('common.namePlaceholder')}
|
||||
{...field}
|
||||
autoComplete="off"
|
||||
/>
|
||||
</FormControl>
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
36
web/src/pages/files/create-folder-dialog/index.tsx
Normal file
36
web/src/pages/files/create-folder-dialog/index.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import { LoadingButton } from '@/components/ui/loading-button';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { TagRenameId } from '@/pages/add-knowledge/constant';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateFolderForm } from './create-folder-form';
|
||||
|
||||
export function CreateFolderDialog({
|
||||
hideModal,
|
||||
onOk,
|
||||
loading,
|
||||
}: IModalProps<any>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent className="sm:max-w-[425px]">
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('fileManager.newFolder')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<CreateFolderForm hideModal={hideModal} onOk={onOk}></CreateFolderForm>
|
||||
<DialogFooter>
|
||||
<LoadingButton type="submit" form={TagRenameId} loading={loading}>
|
||||
{t('common.save')}
|
||||
</LoadingButton>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
36
web/src/pages/files/file-breadcrumb.tsx
Normal file
36
web/src/pages/files/file-breadcrumb.tsx
Normal file
@ -0,0 +1,36 @@
|
||||
import {
|
||||
Breadcrumb,
|
||||
BreadcrumbItem,
|
||||
BreadcrumbList,
|
||||
BreadcrumbPage,
|
||||
BreadcrumbSeparator,
|
||||
} from '@/components/ui/breadcrumb';
|
||||
import { useNavigate } from 'umi';
|
||||
import { useSelectBreadcrumbItems } from './use-navigate-to-folder';
|
||||
|
||||
export function FileBreadcrumb() {
|
||||
const breadcrumbItems = useSelectBreadcrumbItems();
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Breadcrumb>
|
||||
<BreadcrumbList>
|
||||
{breadcrumbItems.map((x, idx) => (
|
||||
<div key={x.path} className="flex items-center gap-2">
|
||||
{idx !== 0 && <BreadcrumbSeparator />}
|
||||
<BreadcrumbItem
|
||||
key={x.path}
|
||||
onClick={() => navigate(x.path)}
|
||||
className="cursor-pointer"
|
||||
>
|
||||
{idx === breadcrumbItems.length - 1 ? (
|
||||
<BreadcrumbPage>{x.title}</BreadcrumbPage>
|
||||
) : (
|
||||
x.title
|
||||
)}
|
||||
</BreadcrumbItem>
|
||||
</div>
|
||||
))}
|
||||
</BreadcrumbList>
|
||||
</Breadcrumb>
|
||||
);
|
||||
}
|
@ -42,12 +42,11 @@ import { getExtension } from '@/utils/document-util';
|
||||
import { useMemo } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ActionCell } from './action-cell';
|
||||
import {
|
||||
useHandleConnectToKnowledge,
|
||||
useNavigateToOtherFolder,
|
||||
useRenameCurrentFile,
|
||||
} from './hooks';
|
||||
import { useHandleConnectToKnowledge, useRenameCurrentFile } from './hooks';
|
||||
import { LinkToDatasetDialog } from './link-to-dataset-dialog';
|
||||
import { MoveDialog } from './move-dialog';
|
||||
import { useHandleMoveFile } from './use-move-file';
|
||||
import { useNavigateToOtherFolder } from './use-navigate-to-folder';
|
||||
|
||||
export function FilesTable() {
|
||||
const [sorting, setSorting] = React.useState<SortingState>([]);
|
||||
@ -78,6 +77,14 @@ export function FilesTable() {
|
||||
fileRenameLoading,
|
||||
} = useRenameCurrentFile();
|
||||
|
||||
const {
|
||||
showMoveFileModal,
|
||||
moveFileVisible,
|
||||
onMoveFileOk,
|
||||
hideMoveFileModal,
|
||||
moveFileLoading,
|
||||
} = useHandleMoveFile();
|
||||
|
||||
const { pagination, data, loading, setPagination } = useFetchFileList();
|
||||
|
||||
const columns: ColumnDef<IFile>[] = [
|
||||
@ -222,6 +229,7 @@ export function FilesTable() {
|
||||
row={row}
|
||||
showConnectToKnowledgeModal={showConnectToKnowledgeModal}
|
||||
showFileRenameModal={showFileRenameModal}
|
||||
showMoveFileModal={showMoveFileModal}
|
||||
></ActionCell>
|
||||
);
|
||||
},
|
||||
@ -363,6 +371,13 @@ export function FilesTable() {
|
||||
loading={fileRenameLoading}
|
||||
></RenameDialog>
|
||||
)}
|
||||
{moveFileVisible && (
|
||||
<MoveDialog
|
||||
hideModal={hideMoveFileModal}
|
||||
onOk={onMoveFileOk}
|
||||
loading={moveFileLoading}
|
||||
></MoveDialog>
|
||||
)}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
@ -1,10 +1,7 @@
|
||||
import { useSetModalState, useShowDeleteConfirm } from '@/hooks/common-hooks';
|
||||
import {
|
||||
useConnectToKnowledge,
|
||||
useCreateFolder,
|
||||
useDeleteFile,
|
||||
useFetchParentFolderList,
|
||||
useMoveFile,
|
||||
useRenameFile,
|
||||
} from '@/hooks/file-manager-hooks';
|
||||
import { IFile } from '@/interfaces/database/file-manager';
|
||||
@ -35,18 +32,6 @@ export const useGetRowSelection = () => {
|
||||
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 {
|
||||
@ -92,46 +77,6 @@ export type UseRenameCurrentFileReturnType = ReturnType<
|
||||
typeof useRenameCurrentFile
|
||||
>;
|
||||
|
||||
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,
|
||||
@ -222,48 +167,3 @@ export const useHandleBreadcrumbClick = () => {
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
||||
|
@ -1,10 +1,23 @@
|
||||
import { FileUploadDialog } from '@/components/file-upload-dialog';
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
DropdownMenu,
|
||||
DropdownMenuContent,
|
||||
DropdownMenuItem,
|
||||
DropdownMenuSeparator,
|
||||
DropdownMenuTrigger,
|
||||
} from '@/components/ui/dropdown-menu';
|
||||
import { Upload } from 'lucide-react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CreateFolderDialog } from './create-folder-dialog';
|
||||
import { FileBreadcrumb } from './file-breadcrumb';
|
||||
import { FilesTable } from './files-table';
|
||||
import { useHandleCreateFolder } from './use-create-folder';
|
||||
import { useHandleUploadFile } from './use-upload-file';
|
||||
|
||||
export default function Files() {
|
||||
const { t } = useTranslation();
|
||||
const {
|
||||
fileUploadVisible,
|
||||
hideFileUploadModal,
|
||||
@ -13,11 +26,40 @@ export default function Files() {
|
||||
onFileUploadOk,
|
||||
} = useHandleUploadFile();
|
||||
|
||||
const {
|
||||
folderCreateModalVisible,
|
||||
showFolderCreateModal,
|
||||
hideFolderCreateModal,
|
||||
folderCreateLoading,
|
||||
onFolderCreateOk,
|
||||
} = useHandleCreateFolder();
|
||||
|
||||
const leftPanel = (
|
||||
<div>
|
||||
<FileBreadcrumb></FileBreadcrumb>
|
||||
</div>
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar title="Files" showDialog={showFileUploadModal}>
|
||||
<Upload />
|
||||
Upload file
|
||||
<ListFilterBar leftPanel={leftPanel}>
|
||||
<DropdownMenu>
|
||||
<DropdownMenuTrigger asChild>
|
||||
<Button variant={'tertiary'} size={'sm'}>
|
||||
<Upload />
|
||||
{t('knowledgeDetails.addFile')}
|
||||
</Button>
|
||||
</DropdownMenuTrigger>
|
||||
<DropdownMenuContent className="w-56">
|
||||
<DropdownMenuItem onClick={showFileUploadModal}>
|
||||
{t('fileManager.uploadFile')}
|
||||
</DropdownMenuItem>
|
||||
<DropdownMenuSeparator />
|
||||
<DropdownMenuItem onClick={showFolderCreateModal}>
|
||||
{t('fileManager.newFolder')}
|
||||
</DropdownMenuItem>
|
||||
</DropdownMenuContent>
|
||||
</DropdownMenu>
|
||||
</ListFilterBar>
|
||||
<FilesTable></FilesTable>
|
||||
{fileUploadVisible && (
|
||||
@ -27,6 +69,14 @@ export default function Files() {
|
||||
loading={fileUploadLoading}
|
||||
></FileUploadDialog>
|
||||
)}
|
||||
{folderCreateModalVisible && (
|
||||
<CreateFolderDialog
|
||||
loading={folderCreateLoading}
|
||||
visible={folderCreateModalVisible}
|
||||
hideModal={hideFolderCreateModal}
|
||||
onOk={onFolderCreateOk}
|
||||
></CreateFolderDialog>
|
||||
)}
|
||||
</section>
|
||||
);
|
||||
}
|
||||
|
56
web/src/pages/files/move-dialog.tsx
Normal file
56
web/src/pages/files/move-dialog.tsx
Normal file
@ -0,0 +1,56 @@
|
||||
import { Button } from '@/components/ui/button';
|
||||
import {
|
||||
Dialog,
|
||||
DialogContent,
|
||||
DialogFooter,
|
||||
DialogHeader,
|
||||
DialogTitle,
|
||||
} from '@/components/ui/dialog';
|
||||
import SingleTreeSelect, { TreeNode } from '@/components/ui/single-tree-select';
|
||||
import { IModalProps } from '@/interfaces/common';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
export function MoveDialog({ hideModal }: IModalProps<any>) {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const treeData: TreeNode[] = [
|
||||
{
|
||||
id: 1,
|
||||
label: 'Node 1',
|
||||
children: [
|
||||
{ id: 11, label: 'Node 1.1' },
|
||||
{ id: 12, label: 'Node 1.2' },
|
||||
],
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
label: 'Node 2',
|
||||
children: [
|
||||
{
|
||||
id: 21,
|
||||
label: 'Node 2.1',
|
||||
children: [
|
||||
{ id: 211, label: 'Node 2.1.1' },
|
||||
{ id: 212, label: 'Node 2.1.2' },
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
|
||||
return (
|
||||
<Dialog open onOpenChange={hideModal}>
|
||||
<DialogContent>
|
||||
<DialogHeader>
|
||||
<DialogTitle>{t('common.move')}</DialogTitle>
|
||||
</DialogHeader>
|
||||
<div>
|
||||
<SingleTreeSelect treeData={treeData}></SingleTreeSelect>
|
||||
</div>
|
||||
<DialogFooter>
|
||||
<Button type="submit">Save changes</Button>
|
||||
</DialogFooter>
|
||||
</DialogContent>
|
||||
</Dialog>
|
||||
);
|
||||
}
|
33
web/src/pages/files/use-create-folder.ts
Normal file
33
web/src/pages/files/use-create-folder.ts
Normal file
@ -0,0 +1,33 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useCreateFolder } from '@/hooks/use-file-request';
|
||||
import { useCallback } from 'react';
|
||||
import { useGetFolderId } from './hooks';
|
||||
|
||||
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,
|
||||
};
|
||||
};
|
50
web/src/pages/files/use-move-file.ts
Normal file
50
web/src/pages/files/use-move-file.ts
Normal file
@ -0,0 +1,50 @@
|
||||
import { useSetModalState } from '@/hooks/common-hooks';
|
||||
import { useMoveFile } from '@/hooks/use-file-request';
|
||||
import { useCallback, useState } from 'react';
|
||||
|
||||
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],
|
||||
);
|
||||
|
||||
const handleShowMoveFileModal = useCallback(
|
||||
(ids: string[]) => {
|
||||
setSourceFileIds(ids);
|
||||
showMoveFileModal();
|
||||
},
|
||||
[showMoveFileModal],
|
||||
);
|
||||
|
||||
return {
|
||||
initialValue: '',
|
||||
moveFileLoading: loading,
|
||||
onMoveFileOk,
|
||||
moveFileVisible,
|
||||
hideMoveFileModal,
|
||||
showMoveFileModal: handleShowMoveFileModal,
|
||||
};
|
||||
};
|
||||
|
||||
export type UseMoveDocumentReturnType = ReturnType<typeof useHandleMoveFile>;
|
28
web/src/pages/files/use-navigate-to-folder.ts
Normal file
28
web/src/pages/files/use-navigate-to-folder.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { useFetchParentFolderList } from '@/hooks/use-file-request';
|
||||
import { Routes } from '@/routes';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
export const useNavigateToOtherFolder = () => {
|
||||
const { navigateToFiles } = useNavigatePage();
|
||||
|
||||
const navigateToOtherFolder = useCallback(
|
||||
(folderId: string) => {
|
||||
navigateToFiles(folderId);
|
||||
},
|
||||
[navigateToFiles],
|
||||
);
|
||||
|
||||
return navigateToOtherFolder;
|
||||
};
|
||||
|
||||
export const useSelectBreadcrumbItems = () => {
|
||||
const parentFolderList = useFetchParentFolderList();
|
||||
|
||||
return parentFolderList.length === 1
|
||||
? []
|
||||
: parentFolderList.map((x) => ({
|
||||
title: x.name === '/' ? 'root' : x.name,
|
||||
path: `${Routes.Files}?folderId=${x.id}`,
|
||||
}));
|
||||
};
|
@ -1,4 +1,5 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchChatAppList } from '@/hooks/chat-hooks';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { ChatCard } from './chat-card';
|
||||
@ -9,8 +10,10 @@ export default function ChatList() {
|
||||
return (
|
||||
<section className="p-8">
|
||||
<ListFilterBar title="Chat apps">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
<Button variant={'tertiary'} size={'sm'}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
</Button>
|
||||
</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">
|
||||
{chatList.map((x) => {
|
||||
|
@ -1,4 +1,5 @@
|
||||
import ListFilterBar from '@/components/list-filter-bar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { useFetchFlowList } from '@/hooks/flow-hooks';
|
||||
import { Plus } from 'lucide-react';
|
||||
import { SearchCard } from './search-card';
|
||||
@ -10,8 +11,10 @@ export default function SearchList() {
|
||||
<section>
|
||||
<div className="px-8 pt-8">
|
||||
<ListFilterBar title="Search apps">
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
<Button variant={'tertiary'} size={'sm'}>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
Create app
|
||||
</Button>
|
||||
</ListFilterBar>
|
||||
</div>
|
||||
<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 max-h-[84vh] overflow-auto px-8">
|
||||
|
Loading…
x
Reference in New Issue
Block a user