mirror of
https://git.mirrors.martin98.com/https://github.com/infiniflow/ragflow.git
synced 2025-08-15 16:45:56 +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:
parent
c8194f5fd0
commit
1cc17eb611
@ -1,24 +1,57 @@
|
|||||||
import { Filter } from 'lucide-react';
|
import { ChevronDown } from 'lucide-react';
|
||||||
import { PropsWithChildren } from 'react';
|
import React, {
|
||||||
import { Button } from './ui/button';
|
ChangeEventHandler,
|
||||||
|
FunctionComponent,
|
||||||
|
PropsWithChildren,
|
||||||
|
} from 'react';
|
||||||
|
import { Button, ButtonProps } from './ui/button';
|
||||||
import { SearchInput } from './ui/input';
|
import { SearchInput } from './ui/input';
|
||||||
|
|
||||||
interface IProps {
|
interface IProps {
|
||||||
title: string;
|
title: string;
|
||||||
showDialog?: () => void;
|
showDialog?: () => void;
|
||||||
|
FilterPopover?: FunctionComponent<any>;
|
||||||
|
searchString?: string;
|
||||||
|
onSearchChange?: ChangeEventHandler<HTMLInputElement>;
|
||||||
|
count?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const FilterButton = React.forwardRef<
|
||||||
|
HTMLButtonElement,
|
||||||
|
ButtonProps & { count?: number }
|
||||||
|
>(({ count = 0, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<Button variant="outline" size={'sm'} {...props} ref={ref}>
|
||||||
|
Filter <span>{count}</span> <ChevronDown />
|
||||||
|
</Button>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
export default function ListFilterBar({
|
export default function ListFilterBar({
|
||||||
title,
|
title,
|
||||||
children,
|
children,
|
||||||
showDialog,
|
showDialog,
|
||||||
|
FilterPopover,
|
||||||
|
searchString,
|
||||||
|
onSearchChange,
|
||||||
|
count,
|
||||||
}: PropsWithChildren<IProps>) {
|
}: PropsWithChildren<IProps>) {
|
||||||
return (
|
return (
|
||||||
<div className="flex justify-between mb-6">
|
<div className="flex justify-between mb-6">
|
||||||
<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" />
|
{FilterPopover ? (
|
||||||
<SearchInput></SearchInput>
|
<FilterPopover>
|
||||||
|
<FilterButton count={count}></FilterButton>
|
||||||
|
</FilterPopover>
|
||||||
|
) : (
|
||||||
|
<FilterButton></FilterButton>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<SearchInput
|
||||||
|
value={searchString}
|
||||||
|
onChange={onSearchChange}
|
||||||
|
></SearchInput>
|
||||||
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
<Button variant={'tertiary'} size={'sm'} onClick={showDialog}>
|
||||||
{children}
|
{children}
|
||||||
</Button>
|
</Button>
|
||||||
|
117
web/src/components/ui/pagination.tsx
Normal file
117
web/src/components/ui/pagination.tsx
Normal file
@ -0,0 +1,117 @@
|
|||||||
|
import { ChevronLeft, ChevronRight, MoreHorizontal } from 'lucide-react';
|
||||||
|
import * as React from 'react';
|
||||||
|
|
||||||
|
import { ButtonProps, buttonVariants } from '@/components/ui/button';
|
||||||
|
import { cn } from '@/lib/utils';
|
||||||
|
|
||||||
|
const Pagination = ({ className, ...props }: React.ComponentProps<'nav'>) => (
|
||||||
|
<nav
|
||||||
|
role="navigation"
|
||||||
|
aria-label="pagination"
|
||||||
|
className={cn('mx-auto flex w-full justify-center', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
Pagination.displayName = 'Pagination';
|
||||||
|
|
||||||
|
const PaginationContent = React.forwardRef<
|
||||||
|
HTMLUListElement,
|
||||||
|
React.ComponentProps<'ul'>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<ul
|
||||||
|
ref={ref}
|
||||||
|
className={cn('flex flex-row items-center gap-1', className)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
));
|
||||||
|
PaginationContent.displayName = 'PaginationContent';
|
||||||
|
|
||||||
|
const PaginationItem = React.forwardRef<
|
||||||
|
HTMLLIElement,
|
||||||
|
React.ComponentProps<'li'>
|
||||||
|
>(({ className, ...props }, ref) => (
|
||||||
|
<li ref={ref} className={cn('', className)} {...props} />
|
||||||
|
));
|
||||||
|
PaginationItem.displayName = 'PaginationItem';
|
||||||
|
|
||||||
|
type PaginationLinkProps = {
|
||||||
|
isActive?: boolean;
|
||||||
|
} & Pick<ButtonProps, 'size'> &
|
||||||
|
React.ComponentProps<'a'>;
|
||||||
|
|
||||||
|
const PaginationLink = ({
|
||||||
|
className,
|
||||||
|
isActive,
|
||||||
|
size = 'icon',
|
||||||
|
...props
|
||||||
|
}: PaginationLinkProps) => (
|
||||||
|
<a
|
||||||
|
aria-current={isActive ? 'page' : undefined}
|
||||||
|
className={cn(
|
||||||
|
buttonVariants({
|
||||||
|
variant: isActive ? 'outline' : 'ghost',
|
||||||
|
size,
|
||||||
|
}),
|
||||||
|
className,
|
||||||
|
)}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
PaginationLink.displayName = 'PaginationLink';
|
||||||
|
|
||||||
|
const PaginationPrevious = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to previous page"
|
||||||
|
size="default"
|
||||||
|
className={cn('gap-1 pl-2.5', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ChevronLeft className="h-4 w-4" />
|
||||||
|
<span>Previous</span>
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationPrevious.displayName = 'PaginationPrevious';
|
||||||
|
|
||||||
|
const PaginationNext = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<typeof PaginationLink>) => (
|
||||||
|
<PaginationLink
|
||||||
|
aria-label="Go to next page"
|
||||||
|
size="default"
|
||||||
|
className={cn('gap-1 pr-2.5', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<span>Next</span>
|
||||||
|
<ChevronRight className="h-4 w-4" />
|
||||||
|
</PaginationLink>
|
||||||
|
);
|
||||||
|
PaginationNext.displayName = 'PaginationNext';
|
||||||
|
|
||||||
|
const PaginationEllipsis = ({
|
||||||
|
className,
|
||||||
|
...props
|
||||||
|
}: React.ComponentProps<'span'>) => (
|
||||||
|
<span
|
||||||
|
aria-hidden
|
||||||
|
className={cn('flex h-9 w-9 items-center justify-center', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<MoreHorizontal className="h-4 w-4" />
|
||||||
|
<span className="sr-only">More pages</span>
|
||||||
|
</span>
|
||||||
|
);
|
||||||
|
PaginationEllipsis.displayName = 'PaginationEllipsis';
|
||||||
|
|
||||||
|
export {
|
||||||
|
Pagination,
|
||||||
|
PaginationContent,
|
||||||
|
PaginationEllipsis,
|
||||||
|
PaginationItem,
|
||||||
|
PaginationLink,
|
||||||
|
PaginationNext,
|
||||||
|
PaginationPrevious,
|
||||||
|
};
|
@ -2,6 +2,7 @@ import { ResponsePostType } from '@/interfaces/database/base';
|
|||||||
import {
|
import {
|
||||||
IKnowledge,
|
IKnowledge,
|
||||||
IKnowledgeGraph,
|
IKnowledgeGraph,
|
||||||
|
IKnowledgeResult,
|
||||||
IRenameTag,
|
IRenameTag,
|
||||||
ITestingResult,
|
ITestingResult,
|
||||||
} from '@/interfaces/database/knowledge';
|
} from '@/interfaces/database/knowledge';
|
||||||
@ -9,6 +10,7 @@ import i18n from '@/locales/config';
|
|||||||
import kbService, {
|
import kbService, {
|
||||||
deleteKnowledgeGraph,
|
deleteKnowledgeGraph,
|
||||||
getKnowledgeGraph,
|
getKnowledgeGraph,
|
||||||
|
listDataset,
|
||||||
listTag,
|
listTag,
|
||||||
removeTag,
|
removeTag,
|
||||||
renameTag,
|
renameTag,
|
||||||
@ -23,9 +25,12 @@ import {
|
|||||||
} from '@tanstack/react-query';
|
} from '@tanstack/react-query';
|
||||||
import { useDebounce } from 'ahooks';
|
import { useDebounce } from 'ahooks';
|
||||||
import { message } from 'antd';
|
import { message } from 'antd';
|
||||||
import { useState } from 'react';
|
import { useCallback, useState } from 'react';
|
||||||
import { useSearchParams } from 'umi';
|
import { useSearchParams } from 'umi';
|
||||||
import { useHandleSearchChange } from './logic-hooks';
|
import {
|
||||||
|
useGetPaginationWithRouter,
|
||||||
|
useHandleSearchChange,
|
||||||
|
} from './logic-hooks';
|
||||||
import { useSetPaginationParams } from './route-hook';
|
import { useSetPaginationParams } from './route-hook';
|
||||||
|
|
||||||
export const useKnowledgeBaseId = (): string => {
|
export const useKnowledgeBaseId = (): string => {
|
||||||
@ -64,7 +69,7 @@ export const useFetchKnowledgeList = (
|
|||||||
initialData: [],
|
initialData: [],
|
||||||
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const { data } = await kbService.getList();
|
const { data } = await listDataset();
|
||||||
const list = data?.data?.kbs ?? [];
|
const list = data?.data?.kbs ?? [];
|
||||||
return shouldFilterListWithoutDocument
|
return shouldFilterListWithoutDocument
|
||||||
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
? list.filter((x: IKnowledge) => x.chunk_num > 0)
|
||||||
@ -91,6 +96,7 @@ export const useInfiniteFetchKnowledgeList = () => {
|
|||||||
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
|
||||||
const PageSize = 30;
|
const PageSize = 30;
|
||||||
|
|
||||||
const {
|
const {
|
||||||
data,
|
data,
|
||||||
error,
|
error,
|
||||||
@ -102,7 +108,7 @@ export const useInfiniteFetchKnowledgeList = () => {
|
|||||||
} = useInfiniteQuery({
|
} = useInfiniteQuery({
|
||||||
queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString],
|
queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString],
|
||||||
queryFn: async ({ pageParam }) => {
|
queryFn: async ({ pageParam }) => {
|
||||||
const { data } = await kbService.getList({
|
const { data } = await listDataset({
|
||||||
page: pageParam,
|
page: pageParam,
|
||||||
page_size: PageSize,
|
page_size: PageSize,
|
||||||
keywords: debouncedSearchString,
|
keywords: debouncedSearchString,
|
||||||
@ -132,6 +138,67 @@ export const useInfiniteFetchKnowledgeList = () => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const useFetchNextKnowledgeListByPage = () => {
|
||||||
|
const { searchString, handleInputChange } = useHandleSearchChange();
|
||||||
|
const { pagination, setPagination } = useGetPaginationWithRouter();
|
||||||
|
const [ownerIds, setOwnerIds] = useState<string[]>([]);
|
||||||
|
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
|
||||||
|
|
||||||
|
const { data, isFetching: loading } = useQuery<IKnowledgeResult>({
|
||||||
|
queryKey: [
|
||||||
|
'fetchKnowledgeListByPage',
|
||||||
|
{
|
||||||
|
debouncedSearchString,
|
||||||
|
...pagination,
|
||||||
|
ownerIds,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
initialData: {
|
||||||
|
kbs: [],
|
||||||
|
total: 0,
|
||||||
|
},
|
||||||
|
gcTime: 0,
|
||||||
|
queryFn: async () => {
|
||||||
|
const { data } = await listDataset(
|
||||||
|
{
|
||||||
|
keywords: debouncedSearchString,
|
||||||
|
page_size: pagination.pageSize,
|
||||||
|
page: pagination.current,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
owner_ids: ownerIds,
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
return data?.data;
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
|
||||||
|
(e) => {
|
||||||
|
// setPagination({ page: 1 }); // TODO: 这里导致重复请求
|
||||||
|
handleInputChange(e);
|
||||||
|
},
|
||||||
|
[handleInputChange],
|
||||||
|
);
|
||||||
|
|
||||||
|
const handleOwnerIdsChange = useCallback((ids: string[]) => {
|
||||||
|
// setPagination({ page: 1 }); // TODO: 这里导致重复请求
|
||||||
|
setOwnerIds(ids);
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return {
|
||||||
|
...data,
|
||||||
|
searchString,
|
||||||
|
handleInputChange: onInputChange,
|
||||||
|
pagination: { ...pagination, total: data?.total },
|
||||||
|
setPagination,
|
||||||
|
loading,
|
||||||
|
setOwnerIds: handleOwnerIdsChange,
|
||||||
|
ownerIds,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
export const useCreateKnowledge = () => {
|
export const useCreateKnowledge = () => {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const {
|
const {
|
||||||
@ -198,7 +265,7 @@ export const useUpdateKnowledge = (shouldFetchList = false) => {
|
|||||||
message.success(i18n.t(`message.updated`));
|
message.success(i18n.t(`message.updated`));
|
||||||
if (shouldFetchList) {
|
if (shouldFetchList) {
|
||||||
queryClient.invalidateQueries({
|
queryClient.invalidateQueries({
|
||||||
queryKey: ['infiniteFetchKnowledgeList'],
|
queryKey: ['fetchKnowledgeListByPage'],
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] });
|
queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] });
|
||||||
|
@ -23,10 +23,15 @@ export interface IKnowledge {
|
|||||||
update_time: number;
|
update_time: number;
|
||||||
vector_similarity_weight: number;
|
vector_similarity_weight: number;
|
||||||
embd_id: string;
|
embd_id: string;
|
||||||
nickname?: string;
|
nickname: string;
|
||||||
operator_permission: number;
|
operator_permission: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IKnowledgeResult {
|
||||||
|
kbs: IKnowledge[];
|
||||||
|
total: number;
|
||||||
|
}
|
||||||
|
|
||||||
export interface Raptor {
|
export interface Raptor {
|
||||||
use_raptor: boolean;
|
use_raptor: boolean;
|
||||||
}
|
}
|
||||||
|
@ -8,3 +8,13 @@ export interface ITestRetrievalRequestBody {
|
|||||||
highlight?: boolean;
|
highlight?: boolean;
|
||||||
kb_id?: string[];
|
kb_id?: string[];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IFetchKnowledgeListRequestBody {
|
||||||
|
owner_ids?: string[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface IFetchKnowledgeListRequestParams {
|
||||||
|
keywords?: string;
|
||||||
|
page?: number;
|
||||||
|
page_size?: number;
|
||||||
|
}
|
||||||
|
@ -132,7 +132,7 @@ export default function BasicSettingForm() {
|
|||||||
defaultValue={selectedFrameworks}
|
defaultValue={selectedFrameworks}
|
||||||
placeholder="Select frameworks"
|
placeholder="Select frameworks"
|
||||||
variant="inverted"
|
variant="inverted"
|
||||||
maxCount={100}
|
maxCount={0}
|
||||||
{...field}
|
{...field}
|
||||||
/>
|
/>
|
||||||
</FormControl>
|
</FormControl>
|
||||||
|
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 { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
|
||||||
import { Button } from '@/components/ui/button';
|
import { Button } from '@/components/ui/button';
|
||||||
import { Card, CardContent } from '@/components/ui/card';
|
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 { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
|
||||||
import { IKnowledge } from '@/interfaces/database/knowledge';
|
|
||||||
import { formatDate } from '@/utils/date';
|
import { formatDate } from '@/utils/date';
|
||||||
|
import { pick } from 'lodash';
|
||||||
import { ChevronRight, Ellipsis, Plus } from 'lucide-react';
|
import { ChevronRight, Ellipsis, Plus } from 'lucide-react';
|
||||||
import { useMemo } from 'react';
|
import { PropsWithChildren, useCallback } from 'react';
|
||||||
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
import { DatasetCreatingDialog } from './dataset-creating-dialog';
|
||||||
import { DatasetDropdown } from './dataset-dropdown';
|
import { DatasetDropdown } from './dataset-dropdown';
|
||||||
|
import { DatasetsFilterPopover } from './datasets-filter-popover';
|
||||||
|
import { DatasetsPagination } from './datasets-pagination';
|
||||||
import { useSaveKnowledge } from './hooks';
|
import { useSaveKnowledge } from './hooks';
|
||||||
import { useRenameDataset } from './use-rename-dataset';
|
import { useRenameDataset } from './use-rename-dataset';
|
||||||
|
|
||||||
@ -25,23 +27,15 @@ export default function Datasets() {
|
|||||||
const { navigateToDataset } = useNavigatePage();
|
const { navigateToDataset } = useNavigatePage();
|
||||||
|
|
||||||
const {
|
const {
|
||||||
fetchNextPage,
|
kbs,
|
||||||
data,
|
total,
|
||||||
hasNextPage,
|
pagination,
|
||||||
searchString,
|
setPagination,
|
||||||
handleInputChange,
|
handleInputChange,
|
||||||
loading,
|
searchString,
|
||||||
} = useInfiniteFetchKnowledgeList();
|
setOwnerIds,
|
||||||
|
ownerIds,
|
||||||
const nextList: IKnowledge[] = useMemo(() => {
|
} = useFetchNextKnowledgeListByPage();
|
||||||
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]);
|
|
||||||
|
|
||||||
const {
|
const {
|
||||||
datasetRenameLoading,
|
datasetRenameLoading,
|
||||||
@ -52,14 +46,32 @@ export default function Datasets() {
|
|||||||
showDatasetRenameModal,
|
showDatasetRenameModal,
|
||||||
} = useRenameDataset();
|
} = useRenameDataset();
|
||||||
|
|
||||||
|
const handlePageChange = useCallback(
|
||||||
|
(page: number, pageSize?: number) => {
|
||||||
|
setPagination({ page, pageSize });
|
||||||
|
},
|
||||||
|
[setPagination],
|
||||||
|
);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<section className="p-8 text-foreground">
|
<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" />
|
<Plus className="mr-2 h-4 w-4" />
|
||||||
Create dataset
|
Create dataset
|
||||||
</ListFilterBar>
|
</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">
|
<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
|
<Card
|
||||||
key={dataset.id}
|
key={dataset.id}
|
||||||
className="bg-colors-background-inverse-weak flex-1"
|
className="bg-colors-background-inverse-weak flex-1"
|
||||||
@ -99,6 +111,13 @@ export default function Datasets() {
|
|||||||
</Card>
|
</Card>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
|
<div className="mt-8">
|
||||||
|
<DatasetsPagination
|
||||||
|
{...pick(pagination, 'current', 'pageSize')}
|
||||||
|
total={total}
|
||||||
|
onChange={handlePageChange}
|
||||||
|
></DatasetsPagination>
|
||||||
|
</div>
|
||||||
{visible && (
|
{visible && (
|
||||||
<DatasetCreatingDialog
|
<DatasetCreatingDialog
|
||||||
hideModal={hideModal}
|
hideModal={hideModal}
|
||||||
|
@ -16,7 +16,13 @@ export const useRenameDataset = () => {
|
|||||||
const onDatasetRenameOk = useCallback(
|
const onDatasetRenameOk = useCallback(
|
||||||
async (name: string) => {
|
async (name: string) => {
|
||||||
const ret = await saveKnowledgeConfiguration({
|
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,
|
kb_id: dataset.id,
|
||||||
name,
|
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;
|
||||||
|
}
|
@ -1,4 +1,8 @@
|
|||||||
import { IRenameTag } from '@/interfaces/database/knowledge';
|
import { IRenameTag } from '@/interfaces/database/knowledge';
|
||||||
|
import {
|
||||||
|
IFetchKnowledgeListRequestBody,
|
||||||
|
IFetchKnowledgeListRequestParams,
|
||||||
|
} from '@/interfaces/request/knowledge';
|
||||||
import api from '@/utils/api';
|
import api from '@/utils/api';
|
||||||
import registerServer from '@/utils/register-server';
|
import registerServer from '@/utils/register-server';
|
||||||
import request, { post } from '@/utils/request';
|
import request, { post } from '@/utils/request';
|
||||||
@ -54,7 +58,7 @@ const methods = {
|
|||||||
},
|
},
|
||||||
getList: {
|
getList: {
|
||||||
url: kb_list,
|
url: kb_list,
|
||||||
method: 'get',
|
method: 'post',
|
||||||
},
|
},
|
||||||
// document manager
|
// document manager
|
||||||
get_document_list: {
|
get_document_list: {
|
||||||
@ -173,4 +177,9 @@ export function deleteKnowledgeGraph(knowledgeId: string) {
|
|||||||
return request.delete(api.getKnowledgeGraph(knowledgeId));
|
return request.delete(api.getKnowledgeGraph(knowledgeId));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const listDataset = (
|
||||||
|
params?: IFetchKnowledgeListRequestParams,
|
||||||
|
body?: IFetchKnowledgeListRequestBody,
|
||||||
|
) => request.post(api.kb_list, { data: body || {}, params });
|
||||||
|
|
||||||
export default kbService;
|
export default kbService;
|
||||||
|
Loading…
x
Reference in New Issue
Block a user