### 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)tags/v0.18.0
| @@ -1,24 +1,57 @@ | |||
| import { Filter } from 'lucide-react'; | |||
| import { PropsWithChildren } from 'react'; | |||
| import { Button } from './ui/button'; | |||
| import { ChevronDown } from 'lucide-react'; | |||
| import React, { | |||
| ChangeEventHandler, | |||
| FunctionComponent, | |||
| PropsWithChildren, | |||
| } from 'react'; | |||
| import { Button, ButtonProps } from './ui/button'; | |||
| import { SearchInput } from './ui/input'; | |||
| interface IProps { | |||
| title: string; | |||
| 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({ | |||
| title, | |||
| children, | |||
| showDialog, | |||
| FilterPopover, | |||
| searchString, | |||
| onSearchChange, | |||
| count, | |||
| }: PropsWithChildren<IProps>) { | |||
| return ( | |||
| <div className="flex justify-between mb-6"> | |||
| <span className="text-3xl font-bold ">{title}</span> | |||
| <div className="flex gap-4 items-center"> | |||
| <Filter className="size-5" /> | |||
| <SearchInput></SearchInput> | |||
| {FilterPopover ? ( | |||
| <FilterPopover> | |||
| <FilterButton count={count}></FilterButton> | |||
| </FilterPopover> | |||
| ) : ( | |||
| <FilterButton></FilterButton> | |||
| )} | |||
| <SearchInput | |||
| value={searchString} | |||
| onChange={onSearchChange} | |||
| ></SearchInput> | |||
| <Button variant={'tertiary'} size={'sm'} onClick={showDialog}> | |||
| {children} | |||
| </Button> | |||
| @@ -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 { | |||
| IKnowledge, | |||
| IKnowledgeGraph, | |||
| IKnowledgeResult, | |||
| IRenameTag, | |||
| ITestingResult, | |||
| } from '@/interfaces/database/knowledge'; | |||
| @@ -9,6 +10,7 @@ import i18n from '@/locales/config'; | |||
| import kbService, { | |||
| deleteKnowledgeGraph, | |||
| getKnowledgeGraph, | |||
| listDataset, | |||
| listTag, | |||
| removeTag, | |||
| renameTag, | |||
| @@ -23,9 +25,12 @@ import { | |||
| } from '@tanstack/react-query'; | |||
| import { useDebounce } from 'ahooks'; | |||
| import { message } from 'antd'; | |||
| import { useState } from 'react'; | |||
| import { useCallback, useState } from 'react'; | |||
| import { useSearchParams } from 'umi'; | |||
| import { useHandleSearchChange } from './logic-hooks'; | |||
| import { | |||
| useGetPaginationWithRouter, | |||
| useHandleSearchChange, | |||
| } from './logic-hooks'; | |||
| import { useSetPaginationParams } from './route-hook'; | |||
| export const useKnowledgeBaseId = (): string => { | |||
| @@ -64,7 +69,7 @@ export const useFetchKnowledgeList = ( | |||
| initialData: [], | |||
| gcTime: 0, // https://tanstack.com/query/latest/docs/framework/react/guides/caching?from=reactQueryV3 | |||
| queryFn: async () => { | |||
| const { data } = await kbService.getList(); | |||
| const { data } = await listDataset(); | |||
| const list = data?.data?.kbs ?? []; | |||
| return shouldFilterListWithoutDocument | |||
| ? list.filter((x: IKnowledge) => x.chunk_num > 0) | |||
| @@ -91,6 +96,7 @@ export const useInfiniteFetchKnowledgeList = () => { | |||
| const debouncedSearchString = useDebounce(searchString, { wait: 500 }); | |||
| const PageSize = 30; | |||
| const { | |||
| data, | |||
| error, | |||
| @@ -102,7 +108,7 @@ export const useInfiniteFetchKnowledgeList = () => { | |||
| } = useInfiniteQuery({ | |||
| queryKey: ['infiniteFetchKnowledgeList', debouncedSearchString], | |||
| queryFn: async ({ pageParam }) => { | |||
| const { data } = await kbService.getList({ | |||
| const { data } = await listDataset({ | |||
| page: pageParam, | |||
| page_size: PageSize, | |||
| 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 = () => { | |||
| const queryClient = useQueryClient(); | |||
| const { | |||
| @@ -198,7 +265,7 @@ export const useUpdateKnowledge = (shouldFetchList = false) => { | |||
| message.success(i18n.t(`message.updated`)); | |||
| if (shouldFetchList) { | |||
| queryClient.invalidateQueries({ | |||
| queryKey: ['infiniteFetchKnowledgeList'], | |||
| queryKey: ['fetchKnowledgeListByPage'], | |||
| }); | |||
| } else { | |||
| queryClient.invalidateQueries({ queryKey: ['fetchKnowledgeDetail'] }); | |||
| @@ -23,10 +23,15 @@ export interface IKnowledge { | |||
| update_time: number; | |||
| vector_similarity_weight: number; | |||
| embd_id: string; | |||
| nickname?: string; | |||
| nickname: string; | |||
| operator_permission: number; | |||
| } | |||
| export interface IKnowledgeResult { | |||
| kbs: IKnowledge[]; | |||
| total: number; | |||
| } | |||
| export interface Raptor { | |||
| use_raptor: boolean; | |||
| } | |||
| @@ -8,3 +8,13 @@ export interface ITestRetrievalRequestBody { | |||
| highlight?: boolean; | |||
| 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} | |||
| placeholder="Select frameworks" | |||
| variant="inverted" | |||
| maxCount={100} | |||
| maxCount={0} | |||
| {...field} | |||
| /> | |||
| </FormControl> | |||
| @@ -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> | |||
| ); | |||
| } | |||
| @@ -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, | |||
| }); | |||
| @@ -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 { | |||
| IFetchKnowledgeListRequestBody, | |||
| IFetchKnowledgeListRequestParams, | |||
| } from '@/interfaces/request/knowledge'; | |||
| import api from '@/utils/api'; | |||
| import registerServer from '@/utils/register-server'; | |||
| import request, { post } from '@/utils/request'; | |||
| @@ -54,7 +58,7 @@ const methods = { | |||
| }, | |||
| getList: { | |||
| url: kb_list, | |||
| method: 'get', | |||
| method: 'post', | |||
| }, | |||
| // document manager | |||
| get_document_list: { | |||
| @@ -173,4 +177,9 @@ export function deleteKnowledgeGraph(knowledgeId: string) { | |||
| return request.delete(api.getKnowledgeGraph(knowledgeId)); | |||
| } | |||
| export const listDataset = ( | |||
| params?: IFetchKnowledgeListRequestParams, | |||
| body?: IFetchKnowledgeListRequestBody, | |||
| ) => request.post(api.kb_list, { data: body || {}, params }); | |||
| export default kbService; | |||