### 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
| 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'; | 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" /> | |||||
| <SearchInput></SearchInput> | |||||
| {FilterPopover ? ( | |||||
| <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> |
| 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, | |||||
| }; |
| import { | import { | ||||
| IKnowledge, | IKnowledge, | ||||
| IKnowledgeGraph, | IKnowledgeGraph, | ||||
| IKnowledgeResult, | |||||
| IRenameTag, | IRenameTag, | ||||
| ITestingResult, | ITestingResult, | ||||
| } from '@/interfaces/database/knowledge'; | } from '@/interfaces/database/knowledge'; | ||||
| import kbService, { | import kbService, { | ||||
| deleteKnowledgeGraph, | deleteKnowledgeGraph, | ||||
| getKnowledgeGraph, | getKnowledgeGraph, | ||||
| listDataset, | |||||
| listTag, | listTag, | ||||
| removeTag, | removeTag, | ||||
| renameTag, | renameTag, | ||||
| } 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 => { | ||||
| 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) | ||||
| const debouncedSearchString = useDebounce(searchString, { wait: 500 }); | const debouncedSearchString = useDebounce(searchString, { wait: 500 }); | ||||
| const PageSize = 30; | const PageSize = 30; | ||||
| const { | const { | ||||
| data, | data, | ||||
| error, | error, | ||||
| } = 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, | ||||
| }; | }; | ||||
| }; | }; | ||||
| 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 { | ||||
| 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'] }); |
| 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; | ||||
| } | } |
| 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; | |||||
| } |
| defaultValue={selectedFrameworks} | defaultValue={selectedFrameworks} | ||||
| placeholder="Select frameworks" | placeholder="Select frameworks" | ||||
| variant="inverted" | variant="inverted" | ||||
| maxCount={100} | |||||
| maxCount={0} | |||||
| {...field} | {...field} | ||||
| /> | /> | ||||
| </FormControl> | </FormControl> |
| 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> | |||||
| ); | |||||
| } |
| 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> | |||||
| ); | |||||
| } |
| 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'; | ||||
| const { navigateToDataset } = useNavigatePage(); | const { navigateToDataset } = useNavigatePage(); | ||||
| const { | const { | ||||
| fetchNextPage, | |||||
| data, | |||||
| hasNextPage, | |||||
| searchString, | |||||
| kbs, | |||||
| total, | |||||
| pagination, | |||||
| setPagination, | |||||
| handleInputChange, | 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 { | const { | ||||
| datasetRenameLoading, | datasetRenameLoading, | ||||
| 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" | ||||
| </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} |
| 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, | ||||
| }); | }); |
| 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; | |||||
| } |
| 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'; | ||||
| }, | }, | ||||
| getList: { | getList: { | ||||
| url: kb_list, | url: kb_list, | ||||
| method: 'get', | |||||
| method: 'post', | |||||
| }, | }, | ||||
| // document manager | // document manager | ||||
| get_document_list: { | get_document_list: { | ||||
| 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; |