mirror of
https://github.com/infiniflow/ragflow.git
synced 2025-12-26 00:46:52 +08:00
### What problem does this PR solve? Feat: Filter the knowledge base list using owner #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
149
web/src/pages/datasets/datasets-filter-popover.tsx
Normal file
149
web/src/pages/datasets/datasets-filter-popover.tsx
Normal file
@ -0,0 +1,149 @@
|
||||
import {
|
||||
Popover,
|
||||
PopoverContent,
|
||||
PopoverTrigger,
|
||||
} from '@/components/ui/popover';
|
||||
import { zodResolver } from '@hookform/resolvers/zod';
|
||||
import { PropsWithChildren, useCallback, useEffect } from 'react';
|
||||
import { useForm } from 'react-hook-form';
|
||||
import { z } from 'zod';
|
||||
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Checkbox } from '@/components/ui/checkbox';
|
||||
import {
|
||||
Form,
|
||||
FormControl,
|
||||
FormField,
|
||||
FormItem,
|
||||
FormLabel,
|
||||
FormMessage,
|
||||
} from '@/components/ui/form';
|
||||
import { useFetchNextKnowledgeListByPage } from '@/hooks/knowledge-hooks';
|
||||
import { useSelectOwners } from './use-select-owners';
|
||||
|
||||
const FormSchema = z.object({
|
||||
items: z.array(z.string()).refine((value) => value.some((item) => item), {
|
||||
message: 'You have to select at least one item.',
|
||||
}),
|
||||
});
|
||||
|
||||
type CheckboxReactHookFormMultipleProps = Pick<
|
||||
ReturnType<typeof useFetchNextKnowledgeListByPage>,
|
||||
'setOwnerIds' | 'ownerIds'
|
||||
>;
|
||||
|
||||
function CheckboxReactHookFormMultiple({
|
||||
setOwnerIds,
|
||||
ownerIds,
|
||||
}: CheckboxReactHookFormMultipleProps) {
|
||||
const owners = useSelectOwners();
|
||||
|
||||
const form = useForm<z.infer<typeof FormSchema>>({
|
||||
resolver: zodResolver(FormSchema),
|
||||
defaultValues: {
|
||||
items: [],
|
||||
},
|
||||
});
|
||||
|
||||
function onSubmit(data: z.infer<typeof FormSchema>) {
|
||||
setOwnerIds(data.items);
|
||||
}
|
||||
|
||||
const onReset = useCallback(() => {
|
||||
setOwnerIds([]);
|
||||
}, [setOwnerIds]);
|
||||
|
||||
useEffect(() => {
|
||||
form.setValue('items', ownerIds);
|
||||
}, [form, ownerIds]);
|
||||
|
||||
return (
|
||||
<Form {...form}>
|
||||
<form
|
||||
onSubmit={form.handleSubmit(onSubmit)}
|
||||
className="space-y-8"
|
||||
onReset={() => form.reset()}
|
||||
>
|
||||
<FormField
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={() => (
|
||||
<FormItem>
|
||||
<div className="mb-4">
|
||||
<FormLabel className="text-base">Owner</FormLabel>
|
||||
</div>
|
||||
{owners.map((item) => (
|
||||
<FormField
|
||||
key={item.id}
|
||||
control={form.control}
|
||||
name="items"
|
||||
render={({ field }) => {
|
||||
return (
|
||||
<div className="flex items-center justify-between">
|
||||
<FormItem
|
||||
key={item.id}
|
||||
className="flex flex-row space-x-3 space-y-0 items-center"
|
||||
>
|
||||
<FormControl>
|
||||
<Checkbox
|
||||
checked={field.value?.includes(item.id)}
|
||||
onCheckedChange={(checked) => {
|
||||
return checked
|
||||
? field.onChange([...field.value, item.id])
|
||||
: field.onChange(
|
||||
field.value?.filter(
|
||||
(value) => value !== item.id,
|
||||
),
|
||||
);
|
||||
}}
|
||||
/>
|
||||
</FormControl>
|
||||
<FormLabel className="text-lg">
|
||||
{item.label}
|
||||
</FormLabel>
|
||||
</FormItem>
|
||||
<span className=" text-sm">{item.count}</span>
|
||||
</div>
|
||||
);
|
||||
}}
|
||||
/>
|
||||
))}
|
||||
<FormMessage />
|
||||
</FormItem>
|
||||
)}
|
||||
/>
|
||||
<div className="flex justify-between">
|
||||
<Button
|
||||
type="button"
|
||||
variant={'outline'}
|
||||
size={'sm'}
|
||||
onClick={onReset}
|
||||
>
|
||||
Clear
|
||||
</Button>
|
||||
<Button type="submit" size={'sm'}>
|
||||
Submit
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
</Form>
|
||||
);
|
||||
}
|
||||
|
||||
export function DatasetsFilterPopover({
|
||||
children,
|
||||
setOwnerIds,
|
||||
ownerIds,
|
||||
}: PropsWithChildren & CheckboxReactHookFormMultipleProps) {
|
||||
return (
|
||||
<Popover>
|
||||
<PopoverTrigger asChild>{children}</PopoverTrigger>
|
||||
<PopoverContent>
|
||||
<CheckboxReactHookFormMultiple
|
||||
setOwnerIds={setOwnerIds}
|
||||
ownerIds={ownerIds}
|
||||
></CheckboxReactHookFormMultiple>
|
||||
</PopoverContent>
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
96
web/src/pages/datasets/datasets-pagination.tsx
Normal file
96
web/src/pages/datasets/datasets-pagination.tsx
Normal file
@ -0,0 +1,96 @@
|
||||
import {
|
||||
Pagination,
|
||||
PaginationContent,
|
||||
PaginationEllipsis,
|
||||
PaginationItem,
|
||||
PaginationLink,
|
||||
PaginationNext,
|
||||
PaginationPrevious,
|
||||
} from '@/components/ui/pagination';
|
||||
import { cn } from '@/lib/utils';
|
||||
import { useCallback, useEffect, useMemo, useState } from 'react';
|
||||
|
||||
export type DatasetsPaginationType = {
|
||||
showQuickJumper?: boolean;
|
||||
onChange?(page: number, pageSize?: number): void;
|
||||
total?: number;
|
||||
current?: number;
|
||||
pageSize?: number;
|
||||
};
|
||||
|
||||
export function DatasetsPagination({
|
||||
current = 1,
|
||||
pageSize = 10,
|
||||
total = 0,
|
||||
onChange,
|
||||
}: DatasetsPaginationType) {
|
||||
const [currentPage, setCurrentPage] = useState(1);
|
||||
|
||||
const pages = useMemo(() => {
|
||||
const num = Math.ceil(total / pageSize);
|
||||
console.log('🚀 ~ pages ~ num:', num);
|
||||
return new Array(num).fill(0).map((_, idx) => idx + 1);
|
||||
}, [pageSize, total]);
|
||||
|
||||
const handlePreviousPageChange = useCallback(() => {
|
||||
setCurrentPage((page) => {
|
||||
const previousPage = page - 1;
|
||||
if (previousPage > 0) {
|
||||
return previousPage;
|
||||
}
|
||||
return page;
|
||||
});
|
||||
}, []);
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(page: number) => () => {
|
||||
setCurrentPage(page);
|
||||
},
|
||||
[],
|
||||
);
|
||||
|
||||
const handleNextPageChange = useCallback(() => {
|
||||
setCurrentPage((page) => {
|
||||
const nextPage = page + 1;
|
||||
if (nextPage <= pages.length) {
|
||||
return nextPage;
|
||||
}
|
||||
return page;
|
||||
});
|
||||
}, [pages.length]);
|
||||
|
||||
useEffect(() => {
|
||||
setCurrentPage(current);
|
||||
}, [current]);
|
||||
|
||||
useEffect(() => {
|
||||
onChange?.(currentPage);
|
||||
}, [currentPage, onChange]);
|
||||
|
||||
return (
|
||||
<section className="flex items-center justify-end">
|
||||
<span className="mr-4">Total {total}</span>
|
||||
<Pagination className="w-auto mx-0">
|
||||
<PaginationContent>
|
||||
<PaginationItem>
|
||||
<PaginationPrevious onClick={handlePreviousPageChange} />
|
||||
</PaginationItem>
|
||||
{pages.map((x) => (
|
||||
<PaginationItem
|
||||
key={x}
|
||||
className={cn({ ['bg-red-500']: currentPage === x })}
|
||||
>
|
||||
<PaginationLink onClick={handlePageChange(x)}>{x}</PaginationLink>
|
||||
</PaginationItem>
|
||||
))}
|
||||
<PaginationItem>
|
||||
<PaginationEllipsis />
|
||||
</PaginationItem>
|
||||
<PaginationItem>
|
||||
<PaginationNext onClick={handleNextPageChange} />
|
||||
</PaginationItem>
|
||||
</PaginationContent>
|
||||
</Pagination>
|
||||
</section>
|
||||
);
|
||||
}
|
||||
@ -3,14 +3,16 @@ import { RenameDialog } from '@/components/rename-dialog';
|
||||
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||
import { Button } from '@/components/ui/button';
|
||||
import { Card, CardContent } from '@/components/ui/card';
|
||||
import { useInfiniteFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { useFetchNextKnowledgeListByPage } from '@/hooks/knowledge-hooks';
|
||||
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||
import { IKnowledge } from '@/interfaces/database/knowledge';
|
||||
import { formatDate } from '@/utils/date';
|
||||
import { pick } from 'lodash';
|
||||
import { ChevronRight, Ellipsis, Plus } from 'lucide-react';
|
||||
import { useMemo } from 'react';
|
||||
import { PropsWithChildren, useCallback } from 'react';
|
||||
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
||||
import { DatasetDropdown } from './dataset-dropdown';
|
||||
import { DatasetsFilterPopover } from './datasets-filter-popover';
|
||||
import { DatasetsPagination } from './datasets-pagination';
|
||||
import { useSaveKnowledge } from './hooks';
|
||||
import { useRenameDataset } from './use-rename-dataset';
|
||||
|
||||
@ -25,23 +27,15 @@ export default function Datasets() {
|
||||
const { navigateToDataset } = useNavigatePage();
|
||||
|
||||
const {
|
||||
fetchNextPage,
|
||||
data,
|
||||
hasNextPage,
|
||||
searchString,
|
||||
kbs,
|
||||
total,
|
||||
pagination,
|
||||
setPagination,
|
||||
handleInputChange,
|
||||
loading,
|
||||
} = useInfiniteFetchKnowledgeList();
|
||||
|
||||
const nextList: IKnowledge[] = useMemo(() => {
|
||||
const list =
|
||||
data?.pages?.flatMap((x) => (Array.isArray(x.kbs) ? x.kbs : [])) ?? [];
|
||||
return list;
|
||||
}, [data?.pages]);
|
||||
|
||||
const total = useMemo(() => {
|
||||
return data?.pages.at(-1).total ?? 0;
|
||||
}, [data?.pages]);
|
||||
searchString,
|
||||
setOwnerIds,
|
||||
ownerIds,
|
||||
} = useFetchNextKnowledgeListByPage();
|
||||
|
||||
const {
|
||||
datasetRenameLoading,
|
||||
@ -52,14 +46,32 @@ export default function Datasets() {
|
||||
showDatasetRenameModal,
|
||||
} = useRenameDataset();
|
||||
|
||||
const handlePageChange = useCallback(
|
||||
(page: number, pageSize?: number) => {
|
||||
setPagination({ page, pageSize });
|
||||
},
|
||||
[setPagination],
|
||||
);
|
||||
|
||||
return (
|
||||
<section className="p-8 text-foreground">
|
||||
<ListFilterBar title="Datasets" showDialog={showModal}>
|
||||
<ListFilterBar
|
||||
title="Datasets"
|
||||
showDialog={showModal}
|
||||
count={ownerIds.length}
|
||||
FilterPopover={({ children }: PropsWithChildren) => (
|
||||
<DatasetsFilterPopover setOwnerIds={setOwnerIds} ownerIds={ownerIds}>
|
||||
{children}
|
||||
</DatasetsFilterPopover>
|
||||
)}
|
||||
searchString={searchString}
|
||||
onSearchChange={handleInputChange}
|
||||
>
|
||||
<Plus className="mr-2 h-4 w-4" />
|
||||
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">
|
||||
{nextList.map((dataset) => (
|
||||
{kbs.map((dataset) => (
|
||||
<Card
|
||||
key={dataset.id}
|
||||
className="bg-colors-background-inverse-weak flex-1"
|
||||
@ -99,6 +111,13 @@ export default function Datasets() {
|
||||
</Card>
|
||||
))}
|
||||
</div>
|
||||
<div className="mt-8">
|
||||
<DatasetsPagination
|
||||
{...pick(pagination, 'current', 'pageSize')}
|
||||
total={total}
|
||||
onChange={handlePageChange}
|
||||
></DatasetsPagination>
|
||||
</div>
|
||||
{visible && (
|
||||
<DatasetCreatingDialog
|
||||
hideModal={hideModal}
|
||||
|
||||
@ -16,7 +16,13 @@ export const useRenameDataset = () => {
|
||||
const onDatasetRenameOk = useCallback(
|
||||
async (name: string) => {
|
||||
const ret = await saveKnowledgeConfiguration({
|
||||
...omit(dataset, ['id', 'update_time', 'nickname', 'tenant_avatar']),
|
||||
...omit(dataset, [
|
||||
'id',
|
||||
'update_time',
|
||||
'nickname',
|
||||
'tenant_avatar',
|
||||
'tenant_id',
|
||||
]),
|
||||
kb_id: dataset.id,
|
||||
name,
|
||||
});
|
||||
|
||||
28
web/src/pages/datasets/use-select-owners.ts
Normal file
28
web/src/pages/datasets/use-select-owners.ts
Normal file
@ -0,0 +1,28 @@
|
||||
import { useFetchKnowledgeList } from '@/hooks/knowledge-hooks';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
export type OwnerFilterType = {
|
||||
id: string;
|
||||
label: string;
|
||||
count: number;
|
||||
};
|
||||
|
||||
export function useSelectOwners() {
|
||||
const { list } = useFetchKnowledgeList();
|
||||
|
||||
const owners = useMemo(() => {
|
||||
const ownerList: OwnerFilterType[] = [];
|
||||
list.forEach((x) => {
|
||||
const item = ownerList.find((y) => y.id === x.tenant_id);
|
||||
if (!item) {
|
||||
ownerList.push({ id: x.tenant_id, label: x.nickname, count: 1 });
|
||||
} else {
|
||||
item.count += 1;
|
||||
}
|
||||
});
|
||||
|
||||
return ownerList;
|
||||
}, [list]);
|
||||
|
||||
return owners;
|
||||
}
|
||||
Reference in New Issue
Block a user