diff --git a/web/src/components/list-filter-bar/index.tsx b/web/src/components/list-filter-bar/index.tsx index d24f6cd4e..a389c3cef 100644 --- a/web/src/components/list-filter-bar/index.tsx +++ b/web/src/components/list-filter-bar/index.tsx @@ -1,3 +1,4 @@ +import { cn } from '@/lib/utils'; import { ChevronDown } from 'lucide-react'; import React, { ChangeEventHandler, @@ -38,7 +39,10 @@ export default function ListFilterBar({ value, onChange, filters, -}: PropsWithChildren>) { + className, +}: PropsWithChildren> & { + className?: string; +}) { const filterCount = useMemo(() => { return typeof value === 'object' && value !== null ? Object.values(value).reduce((pre, cur) => { @@ -48,7 +52,7 @@ export default function ListFilterBar({ }, [value]); return ( -
+
{leftPanel || title}
{showFilter && ( diff --git a/web/src/components/table-skeleton.tsx b/web/src/components/table-skeleton.tsx index b3b74aa56..658eb896c 100644 --- a/web/src/components/table-skeleton.tsx +++ b/web/src/components/table-skeleton.tsx @@ -1,5 +1,5 @@ +import { Loader2 } from 'lucide-react'; import { PropsWithChildren } from 'react'; -import { SkeletonCard } from './skeleton-card'; import { TableCell, TableRow } from './ui/table'; type IProps = { columnsLength: number }; @@ -7,17 +7,22 @@ type IProps = { columnsLength: number }; function Row({ children, columnsLength }: PropsWithChildren & IProps) { return ( - + {children} ); } -export function TableSkeleton({ columnsLength }: { columnsLength: number }) { +export function TableSkeleton({ + columnsLength, + children, +}: PropsWithChildren & IProps) { return ( - + {children || ( + + )} ); } diff --git a/web/src/components/ui/async-tree-select.tsx b/web/src/components/ui/async-tree-select.tsx index 157b20c07..036113569 100644 --- a/web/src/components/ui/async-tree-select.tsx +++ b/web/src/components/ui/async-tree-select.tsx @@ -99,14 +99,15 @@ export function AsyncTreeSelect({
  • -
    - - {x.title} - +
    + {x.title} {x.isLeaf || (
    - +
      {renderNodes()}
    diff --git a/web/src/pages/datasets/datasets-pagination.tsx b/web/src/components/ui/ragflow-pagination.tsx similarity index 56% rename from web/src/pages/datasets/datasets-pagination.tsx rename to web/src/components/ui/ragflow-pagination.tsx index ac9708bf8..e232a7211 100644 --- a/web/src/pages/datasets/datasets-pagination.tsx +++ b/web/src/components/ui/ragflow-pagination.tsx @@ -7,70 +7,100 @@ import { PaginationNext, PaginationPrevious, } from '@/components/ui/pagination'; +import { RAGFlowSelect, RAGFlowSelectOptionType } from '@/components/ui/select'; import { cn } from '@/lib/utils'; import { useCallback, useEffect, useMemo, useState } from 'react'; -export type DatasetsPaginationType = { +export type RAGFlowPaginationType = { showQuickJumper?: boolean; - onChange?(page: number, pageSize?: number): void; + onChange?(page: number, pageSize: number): void; total?: number; current?: number; pageSize?: number; + showSizeChanger?: boolean; }; -export function DatasetsPagination({ +export function RAGFlowPagination({ current = 1, pageSize = 10, total = 0, onChange, -}: DatasetsPaginationType) { + showSizeChanger = true, +}: RAGFlowPaginationType) { const [currentPage, setCurrentPage] = useState(1); + const [currentPageSize, setCurrentPageSize] = useState('10'); + + const sizeChangerOptions: RAGFlowSelectOptionType[] = useMemo(() => { + return [10, 20, 50, 100].map((x) => ({ + label: {x} / page, + value: x.toString(), + })); + }, []); 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 changePage = useCallback( + (page: number) => { + onChange?.(page, Number(currentPageSize)); + }, + [currentPageSize, onChange], + ); + const handlePreviousPageChange = useCallback(() => { setCurrentPage((page) => { const previousPage = page - 1; if (previousPage > 0) { + changePage(previousPage); return previousPage; } + changePage(page); return page; }); - }, []); + }, [changePage]); const handlePageChange = useCallback( (page: number) => () => { + changePage(page); setCurrentPage(page); }, - [], + [changePage], ); const handleNextPageChange = useCallback(() => { setCurrentPage((page) => { const nextPage = page + 1; if (nextPage <= pages.length) { + changePage(nextPage); return nextPage; } + changePage(page); return page; }); - }, [pages.length]); + }, [changePage, pages.length]); + + const handlePageSizeChange = useCallback( + (size: string) => { + onChange?.(currentPage, Number(size)); + setCurrentPageSize(size); + }, + [currentPage, onChange], + ); useEffect(() => { setCurrentPage(current); }, [current]); useEffect(() => { - onChange?.(currentPage); - }, [currentPage, onChange]); + setCurrentPageSize(pageSize.toString()); + }, [pageSize]); return ( -
    +
    Total {total} - + @@ -78,7 +108,7 @@ export function DatasetsPagination({ {pages.map((x) => ( {x} @@ -91,6 +121,13 @@ export function DatasetsPagination({ + {showSizeChanger && ( + + )}
    ); } diff --git a/web/src/components/ui/tooltip.tsx b/web/src/components/ui/tooltip.tsx index 25fe14c05..756900453 100644 --- a/web/src/components/ui/tooltip.tsx +++ b/web/src/components/ui/tooltip.tsx @@ -19,7 +19,7 @@ const TooltipContent = React.forwardRef< ref={ref} sideOffset={sideOffset} className={cn( - 'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2', + 'z-50 overflow-hidden rounded-md border bg-popover px-3 py-1.5 text-sm text-popover-foreground shadow-md animate-in fade-in-0 zoom-in-95 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 max-w-[20vw]', className, )} {...props} diff --git a/web/src/hooks/logic-hooks/use-row-selection.ts b/web/src/hooks/logic-hooks/use-row-selection.ts index 18aff547e..6da303c55 100644 --- a/web/src/hooks/logic-hooks/use-row-selection.ts +++ b/web/src/hooks/logic-hooks/use-row-selection.ts @@ -1,14 +1,19 @@ import { RowSelectionState } from '@tanstack/react-table'; import { isEmpty } from 'lodash'; -import { useMemo, useState } from 'react'; +import { useCallback, useMemo, useState } from 'react'; export function useRowSelection() { const [rowSelection, setRowSelection] = useState({}); + const clearRowSelection = useCallback(() => { + setRowSelection({}); + }, []); + return { rowSelection, setRowSelection, rowSelectionIsEmpty: isEmpty(rowSelection), + clearRowSelection, }; } diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts new file mode 100644 index 000000000..ee8b2144c --- /dev/null +++ b/web/src/hooks/use-agent-request.ts @@ -0,0 +1,22 @@ +import { IFlow } from '@/interfaces/database/flow'; +import flowService from '@/services/flow-service'; +import { useQuery } from '@tanstack/react-query'; + +export const enum AgentApiAction { + FetchAgentList = 'fetchAgentList', +} + +export const useFetchAgentList = () => { + const { data, isFetching: loading } = useQuery({ + queryKey: [AgentApiAction.FetchAgentList], + initialData: [], + gcTime: 0, + queryFn: async () => { + const { data } = await flowService.listCanvas(); + + return data?.data ?? []; + }, + }); + + return { data, loading }; +}; diff --git a/web/src/interfaces/database/flow.ts b/web/src/interfaces/database/flow.ts index 2e9980d60..940f48599 100644 --- a/web/src/interfaces/database/flow.ts +++ b/web/src/interfaces/database/flow.ts @@ -26,7 +26,7 @@ export interface IOperatorNode { } export declare interface IFlow { - avatar?: null | string; + avatar?: string; canvas_type: null; create_date: string; create_time: number; diff --git a/web/src/pages/dataset/dataset/dataset-table.tsx b/web/src/pages/dataset/dataset/dataset-table.tsx index 64a2f9081..155dc0ed5 100644 --- a/web/src/pages/dataset/dataset/dataset-table.tsx +++ b/web/src/pages/dataset/dataset/dataset-table.tsx @@ -15,7 +15,8 @@ import * as React from 'react'; import { ChunkMethodDialog } from '@/components/chunk-method-dialog'; import { RenameDialog } from '@/components/rename-dialog'; -import { Button } from '@/components/ui/button'; +import { TableSkeleton } from '@/components/table-skeleton'; +import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { Table, TableBody, @@ -27,6 +28,7 @@ import { import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection'; import { useFetchDocumentList } from '@/hooks/use-document-request'; import { getExtension } from '@/utils/document-util'; +import { pick } from 'lodash'; import { useMemo } from 'react'; import { SetMetaDialog } from './set-meta-dialog'; import { useChangeDocumentParser } from './use-change-document-parser'; @@ -36,7 +38,7 @@ import { useSaveMeta } from './use-save-meta'; export type DatasetTableProps = Pick< ReturnType, - 'documents' | 'setPagination' | 'pagination' + 'documents' | 'setPagination' | 'pagination' | 'loading' > & Pick; @@ -46,6 +48,7 @@ export function DatasetTable({ setPagination, rowSelection, setRowSelection, + loading, }: DatasetTableProps) { const [sorting, setSorting] = React.useState([]); const [columnFilters, setColumnFilters] = React.useState( @@ -105,20 +108,6 @@ export function DatasetTable({ getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, - onPaginationChange: (updaterOrValue: any) => { - if (typeof updaterOrValue === 'function') { - const nextPagination = updaterOrValue(currentPagination); - setPagination({ - page: nextPagination.pageIndex + 1, - pageSize: nextPagination.pageSize, - }); - } else { - setPagination({ - page: updaterOrValue.pageIndex, - pageSize: updaterOrValue.pageSize, - }); - } - }, manualPagination: true, //we're doing manual "server-side" pagination state: { sorting, @@ -152,8 +141,10 @@ export function DatasetTable({ ))} - - {table.getRowModel().rows?.length ? ( + + {loading ? ( + + ) : table.getRowModel().rows?.length ? ( table.getRowModel().rows.map((row) => (
    - - + { + setPagination({ page, pageSize }); + }} + >
    {changeParserVisible && ( diff --git a/web/src/pages/dataset/dataset/index.tsx b/web/src/pages/dataset/dataset/index.tsx index ef2825457..b1ead66cd 100644 --- a/web/src/pages/dataset/dataset/index.tsx +++ b/web/src/pages/dataset/dataset/index.tsx @@ -38,6 +38,7 @@ export default function Dataset() { setPagination, filterValue, handleFilterSubmit, + loading, } = useFetchDocumentList(); const { filters } = useSelectDatasetFilters(); @@ -93,6 +94,7 @@ export default function Dataset() { setPagination={setPagination} rowSelection={rowSelection} setRowSelection={setRowSelection} + loading={loading} > {documentUploadVisible && ( { - // setCurrentRecord(record); - // showRenameModal(); - // }; - - // const onShowSetMetaModal = useCallback(() => { - // setRecord(); - // showSetMetaModal(); - // }, [setRecord, showSetMetaModal]); - const { navigateToChunkParsedResult } = useNavigatePage(); const { setDocumentStatus } = useSetDocumentStatus(); diff --git a/web/src/pages/dataset/sidebar/index.tsx b/web/src/pages/dataset/sidebar/index.tsx index 0d8673190..14ae88157 100644 --- a/web/src/pages/dataset/sidebar/index.tsx +++ b/web/src/pages/dataset/sidebar/index.tsx @@ -31,7 +31,7 @@ export function SideBar() { CN -

    {data.name}

    +

    {data.name}

    {data.doc_num} files | {data.chunk_num} chunks
    diff --git a/web/src/pages/datasets/dataset-card.tsx b/web/src/pages/datasets/dataset-card.tsx index c17561e23..2c384358a 100644 --- a/web/src/pages/datasets/dataset-card.tsx +++ b/web/src/pages/datasets/dataset-card.tsx @@ -26,17 +26,21 @@ export function DatasetCard({ return ( - -
    -
    - + +
    +
    + - CN + CN - {owner && {owner}} + {owner && ( + + {owner} + + )}
    -
    +

    {dataset.name}

    -

    {dataset.doc_num} files

    -

    - Created {formatDate(dataset.update_time)} +

    {dataset.doc_num} files

    +

    + {formatDate(dataset.update_time)}

    diff --git a/web/src/pages/datasets/index.tsx b/web/src/pages/datasets/index.tsx index 849af1f98..1c06ce521 100644 --- a/web/src/pages/datasets/index.tsx +++ b/web/src/pages/datasets/index.tsx @@ -1,6 +1,7 @@ import ListFilterBar from '@/components/list-filter-bar'; import { RenameDialog } from '@/components/rename-dialog'; import { Button } from '@/components/ui/button'; +import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { useFetchNextKnowledgeListByPage } from '@/hooks/use-knowledge-request'; import { pick } from 'lodash'; import { Plus } from 'lucide-react'; @@ -8,7 +9,6 @@ import { useCallback } from 'react'; import { useTranslation } from 'react-i18next'; import { DatasetCard } from './dataset-card'; import { DatasetCreatingDialog } from './dataset-creating-dialog'; -import { DatasetsPagination } from './datasets-pagination'; import { useSaveKnowledge } from './hooks'; import { useRenameDataset } from './use-rename-dataset'; import { useSelectOwners } from './use-select-owners'; @@ -53,7 +53,7 @@ export default function Datasets() { ); return ( -
    +
    -
    +
    {kbs.map((dataset) => { return ( -
    - + + >
    {visible && ( {isSupportedPreviewDocumentType(extension) && ( diff --git a/web/src/pages/files/files-table.tsx b/web/src/pages/files/files-table.tsx index 862e6ec58..c4f5064d3 100644 --- a/web/src/pages/files/files-table.tsx +++ b/web/src/pages/files/files-table.tsx @@ -19,6 +19,7 @@ import SvgIcon from '@/components/svg-icon'; import { TableEmpty, TableSkeleton } from '@/components/table-skeleton'; import { Button } from '@/components/ui/button'; import { Checkbox } from '@/components/ui/checkbox'; +import { RAGFlowPagination } from '@/components/ui/ragflow-pagination'; import { Table, TableBody, @@ -39,6 +40,7 @@ import { cn } from '@/lib/utils'; import { formatFileSize } from '@/utils/common-util'; import { formatDate } from '@/utils/date'; import { getExtension } from '@/utils/document-util'; +import { pick } from 'lodash'; import { useMemo } from 'react'; import { useTranslation } from 'react-i18next'; import { ActionCell } from './action-cell'; @@ -244,20 +246,7 @@ export function FilesTable({ getFilteredRowModel: getFilteredRowModel(), onColumnVisibilityChange: setColumnVisibility, onRowSelectionChange: setRowSelection, - onPaginationChange: (updaterOrValue: any) => { - if (typeof updaterOrValue === 'function') { - const nextPagination = updaterOrValue(currentPagination); - setPagination({ - page: nextPagination.pageIndex + 1, - pageSize: nextPagination.pageSize, - }); - } else { - setPagination({ - page: updaterOrValue.pageIndex, - pageSize: updaterOrValue.pageSize, - }); - } - }, + manualPagination: true, //we're doing manual "server-side" pagination state: { @@ -326,23 +315,15 @@ export function FilesTable({ {table.getFilteredSelectedRowModel().rows.length} of {total} row(s) selected.
    +
    - - + { + setPagination({ page, pageSize }); + }} + >
    {connectToKnowledgeVisible && ( diff --git a/web/src/pages/files/index.tsx b/web/src/pages/files/index.tsx index 316dcba03..3e09145d6 100644 --- a/web/src/pages/files/index.tsx +++ b/web/src/pages/files/index.tsx @@ -50,16 +50,20 @@ export default function Files() { handleInputChange, } = useFetchFileList(); + const { + rowSelection, + setRowSelection, + rowSelectionIsEmpty, + clearRowSelection, + } = useRowSelection(); + const { showMoveFileModal, moveFileVisible, onMoveFileOk, hideMoveFileModal, moveFileLoading, - } = useHandleMoveFile(); - - const { rowSelection, setRowSelection, rowSelectionIsEmpty } = - useRowSelection(); + } = useHandleMoveFile({ clearRowSelection }); const { list } = useBulkOperateFile({ files, diff --git a/web/src/pages/files/link-to-dataset-dialog.tsx b/web/src/pages/files/link-to-dataset-dialog.tsx index 42ddbb8c0..9394bc8b0 100644 --- a/web/src/pages/files/link-to-dataset-dialog.tsx +++ b/web/src/pages/files/link-to-dataset-dialog.tsx @@ -69,7 +69,7 @@ function LinkToDatasetForm({ name="knowledgeIds" render={({ field }) => ( - Name + {t('common.name')} , onClick: () => { - showMoveFileModal(selectedIds); + showMoveFileModal(selectedIds, true); }, }, { diff --git a/web/src/pages/files/use-move-file.ts b/web/src/pages/files/use-move-file.ts index d73061b77..a04dbccd3 100644 --- a/web/src/pages/files/use-move-file.ts +++ b/web/src/pages/files/use-move-file.ts @@ -1,8 +1,11 @@ import { useSetModalState } from '@/hooks/common-hooks'; +import { UseRowSelectionType } from '@/hooks/logic-hooks/use-row-selection'; import { useMoveFile } from '@/hooks/use-file-request'; -import { useCallback, useState } from 'react'; +import { useCallback, useRef, useState } from 'react'; -export const useHandleMoveFile = () => { +export const useHandleMoveFile = ({ + clearRowSelection, +}: Pick) => { const { visible: moveFileVisible, hideModal: hideMoveFileModal, @@ -10,6 +13,7 @@ export const useHandleMoveFile = () => { } = useSetModalState(); const { moveFile, loading } = useMoveFile(); const [sourceFileIds, setSourceFileIds] = useState([]); + const isBulkRef = useRef(false); const onMoveFileOk = useCallback( async (targetFolderId: string) => { @@ -19,16 +23,19 @@ export const useHandleMoveFile = () => { }); if (ret === 0) { - // setSelectedRowKeys([]); + if (isBulkRef.current) { + clearRowSelection(); + } hideMoveFileModal(); } return ret; }, - [moveFile, hideMoveFileModal, sourceFileIds], + [moveFile, sourceFileIds, hideMoveFileModal, clearRowSelection], ); const handleShowMoveFileModal = useCallback( - (ids: string[]) => { + (ids: string[], isBulk = false) => { + isBulkRef.current = isBulk; setSourceFileIds(ids); showMoveFileModal(); }, diff --git a/web/src/pages/home/agent-list.tsx b/web/src/pages/home/agent-list.tsx new file mode 100644 index 000000000..043862957 --- /dev/null +++ b/web/src/pages/home/agent-list.tsx @@ -0,0 +1,10 @@ +import { useFetchAgentList } from '@/hooks/use-agent-request'; +import { ApplicationCard } from './application-card'; + +export function Agents() { + const { data } = useFetchAgentList(); + + return data + .slice(0, 10) + .map((x) => ); +} diff --git a/web/src/pages/home/application-card.tsx b/web/src/pages/home/application-card.tsx new file mode 100644 index 000000000..a5031f9fb --- /dev/null +++ b/web/src/pages/home/application-card.tsx @@ -0,0 +1,30 @@ +import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; +import { Card, CardContent } from '@/components/ui/card'; +import { formatDate } from '@/utils/date'; + +type ApplicationCardProps = { + app: { + avatar?: string; + title: string; + update_time: number; + }; +}; + +export function ApplicationCard({ app }: ApplicationCardProps) { + return ( + + + + + CN + +
    +

    + {app.title} +

    +

    {formatDate(app.update_time)}

    +
    +
    +
    + ); +} diff --git a/web/src/pages/home/applications.tsx b/web/src/pages/home/applications.tsx index c7906b086..4b1e40a82 100644 --- a/web/src/pages/home/applications.tsx +++ b/web/src/pages/home/applications.tsx @@ -1,62 +1,58 @@ -import { Button } from '@/components/ui/button'; -import { Card, CardContent } from '@/components/ui/card'; import { Segmented, SegmentedValue } from '@/components/ui/segmented'; -import { ChevronRight, Cpu, MessageSquare, Search } from 'lucide-react'; +import { Routes } from '@/routes'; +import { Cpu, MessageSquare, Search } from 'lucide-react'; import { useMemo, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { Agents } from './agent-list'; +import { ApplicationCard } from './application-card'; const applications = [ { id: 1, title: 'Jarvis chatbot', type: 'Chat app', - date: '11/24/2024', - icon: , + update_time: '11/24/2024', + avatar: , }, { id: 2, title: 'Search app 01', type: 'Search app', - date: '11/24/2024', - icon: , + update_time: '11/24/2024', + avatar: , }, { id: 3, title: 'Chatbot 01', type: 'Chat app', - date: '11/24/2024', - icon: , + update_time: '11/24/2024', + avatar: , }, { id: 4, title: 'Workflow 01', type: 'Agent', - date: '11/24/2024', - icon: , + update_time: '11/24/2024', + avatar: , }, ]; export function Applications() { const [val, setVal] = useState('all'); - const options = useMemo(() => { - return [ + const { t } = useTranslation(); + + const options = useMemo( + () => [ { label: 'All', value: 'all', }, - { - label: 'Chat', - value: 'chat', - }, - { - label: 'Search', - value: 'search', - }, - { - label: 'Agent', - value: 'agent', - }, - ]; - }, []); + { value: Routes.Chats, label: t('header.chat') }, + { value: Routes.Searches, label: t('header.search') }, + { value: Routes.Agents, label: t('header.flow') }, + ], + [t], + ); const handleChange = (path: SegmentedValue) => { setVal(path as string); @@ -73,30 +69,13 @@ export function Applications() { className="bg-colors-background-inverse-standard text-colors-text-neutral-standard" >
    -
    - {[...Array(12)].map((_, i) => { - const app = applications[i % 4]; - return ( - - -
    - {app.icon} -
    -
    -

    {app.title}

    -

    {app.type}

    -

    {app.date}

    -
    - -
    -
    - ); - })} +
    + {val === Routes.Agents || + [...Array(12)].map((_, i) => { + const app = applications[i % 4]; + return ; + })} + {val === Routes.Agents && }
    ); diff --git a/web/src/pages/home/datasets.tsx b/web/src/pages/home/datasets.tsx index e9e548328..95bfdc49b 100644 --- a/web/src/pages/home/datasets.tsx +++ b/web/src/pages/home/datasets.tsx @@ -28,7 +28,7 @@ export function Datasets() {
  • ) : (
    - {kbs.slice(0, 4).map((dataset) => ( + {kbs.slice(0, 6).map((dataset) => (