Feat: Bind data to the agent module of the home page #3221 (#7385)

### What problem does this PR solve?

Feat: Bind data to the agent module of the home page #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu
2025-04-29 09:50:54 +08:00
committed by GitHub
parent c7310f7fb2
commit 5bb1c383ac
26 changed files with 260 additions and 187 deletions

View File

@ -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<typeof useFetchDocumentList>,
'documents' | 'setPagination' | 'pagination'
'documents' | 'setPagination' | 'pagination' | 'loading'
> &
Pick<UseRowSelectionType, 'rowSelection' | 'setRowSelection'>;
@ -46,6 +48,7 @@ export function DatasetTable({
setPagination,
rowSelection,
setRowSelection,
loading,
}: DatasetTableProps) {
const [sorting, setSorting] = React.useState<SortingState>([]);
const [columnFilters, setColumnFilters] = React.useState<ColumnFiltersState>(
@ -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({
</TableRow>
))}
</TableHeader>
<TableBody>
{table.getRowModel().rows?.length ? (
<TableBody className="relative">
{loading ? (
<TableSkeleton columnsLength={columns.length}></TableSkeleton>
) : table.getRowModel().rows?.length ? (
table.getRowModel().rows.map((row) => (
<TableRow
key={row.id}
@ -191,22 +182,13 @@ export function DatasetTable({
{pagination?.total} row(s) selected.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={pagination.total}
onChange={(page, pageSize) => {
setPagination({ page, pageSize });
}}
></RAGFlowPagination>
</div>
</div>
{changeParserVisible && (

View File

@ -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}
></DatasetTable>
{documentUploadVisible && (
<FileUploadDialog

View File

@ -35,16 +35,6 @@ export function useDatasetTableColumns({
keyPrefix: 'knowledgeDetails',
});
// const onShowRenameModal = (record: IDocumentInfo) => {
// setCurrentRecord(record);
// showRenameModal();
// };
// const onShowSetMetaModal = useCallback(() => {
// setRecord();
// showSetMetaModal();
// }, [setRecord, showSetMetaModal]);
const { navigateToChunkParsedResult } = useNavigatePage();
const { setDocumentStatus } = useSetDocumentStatus();

View File

@ -31,7 +31,7 @@ export function SideBar() {
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<h3 className="text-lg font-semibold mb-2">{data.name}</h3>
<h3 className="text-lg font-semibold mb-2 line-clamp-1">{data.name}</h3>
<div className="text-sm opacity-80">
{data.doc_num} files | {data.chunk_num} chunks
</div>

View File

@ -26,17 +26,21 @@ export function DatasetCard({
return (
<Card
key={dataset.id}
className="bg-colors-background-inverse-weak flex-1"
className="bg-colors-background-inverse-weak w-40"
onClick={navigateToDataset(dataset.id)}
>
<CardContent className="p-4">
<section className="flex justify-between mb-4">
<div className="flex gap-2">
<Avatar className="w-[70px] h-[70px] rounded-lg">
<CardContent className="p-2.5 pt-1">
<section className="flex justify-between mb-2">
<div className="flex gap-2 items-center">
<Avatar className="size-6 rounded-lg">
<AvatarImage src={dataset.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
<AvatarFallback className="rounded-lg ">CN</AvatarFallback>
</Avatar>
{owner && <Badge className="h-5">{owner}</Badge>}
{owner && (
<Badge className="h-5 rounded-sm px-1 bg-background-badge text-text-badge">
{owner}
</Badge>
)}
</div>
<DatasetDropdown
showDatasetRenameModal={showDatasetRenameModal}
@ -48,13 +52,13 @@ export function DatasetCard({
</DatasetDropdown>
</section>
<div className="flex justify-between items-end">
<div>
<div className="w-full">
<h3 className="text-lg font-semibold mb-2 line-clamp-1">
{dataset.name}
</h3>
<p className="text-sm opacity-80">{dataset.doc_num} files</p>
<p className="text-sm opacity-80">
Created {formatDate(dataset.update_time)}
<p className="text-xs opacity-80">{dataset.doc_num} files</p>
<p className="text-xs opacity-80">
{formatDate(dataset.update_time)}
</p>
</div>
</div>

View File

@ -1,96 +0,0 @@
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>
);
}

View File

@ -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 (
<section className="p-8 text-foreground">
<section className="py-8 text-foreground">
<ListFilterBar
title="Datasets"
searchString={searchString}
@ -61,13 +61,14 @@ export default function Datasets() {
value={filterValue}
filters={owners}
onChange={handleFilterSubmit}
className="px-8"
>
<Button variant={'tertiary'} size={'sm'} onClick={showModal}>
<Plus className="mr-2 h-4 w-4" />
{t('knowledgeList.createKnowledgeBase')}
</Button>
</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="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8">
{kbs.map((dataset) => {
return (
<DatasetCard
@ -78,12 +79,12 @@ export default function Datasets() {
);
})}
</div>
<div className="mt-8">
<DatasetsPagination
<div className="mt-8 px-8">
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={total}
onChange={handlePageChange}
></DatasetsPagination>
></RAGFlowPagination>
</div>
{visible && (
<DatasetCreatingDialog

View File

@ -73,7 +73,7 @@ export function ActionCell({
</Button>
<ConfirmDeleteDialog>
<Button variant="ghost" size={'icon'}>
<Trash2 />
<Trash2 className="text-text-delete-red" />
</Button>
</ConfirmDeleteDialog>
{isSupportedPreviewDocumentType(extension) && (

View File

@ -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.
</div>
<div className="space-x-2">
<Button
variant="outline"
size="sm"
onClick={() => table.previousPage()}
disabled={!table.getCanPreviousPage()}
>
Previous
</Button>
<Button
variant="outline"
size="sm"
onClick={() => table.nextPage()}
disabled={!table.getCanNextPage()}
>
Next
</Button>
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={total}
onChange={(page, pageSize) => {
setPagination({ page, pageSize });
}}
></RAGFlowPagination>
</div>
</div>
{connectToKnowledgeVisible && (

View File

@ -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,

View File

@ -69,7 +69,7 @@ function LinkToDatasetForm({
name="knowledgeIds"
render={({ field }) => (
<FormItem>
<FormLabel>Name</FormLabel>
<FormLabel>{t('common.name')}</FormLabel>
<FormControl>
<MultiSelect
options={options}

View File

@ -28,7 +28,7 @@ export function useBulkOperateFile({
label: t('common.move'),
icon: <FolderInput />,
onClick: () => {
showMoveFileModal(selectedIds);
showMoveFileModal(selectedIds, true);
},
},
{

View File

@ -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<UseRowSelectionType, 'clearRowSelection'>) => {
const {
visible: moveFileVisible,
hideModal: hideMoveFileModal,
@ -10,6 +13,7 @@ export const useHandleMoveFile = () => {
} = useSetModalState();
const { moveFile, loading } = useMoveFile();
const [sourceFileIds, setSourceFileIds] = useState<string[]>([]);
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();
},

View File

@ -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) => <ApplicationCard key={x.id} app={x}></ApplicationCard>);
}

View File

@ -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 (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard w-64">
<CardContent className="p-4 flex items-center gap-6">
<Avatar className="size-14 rounded-lg">
<AvatarImage src={app.avatar === null ? '' : app.avatar} />
<AvatarFallback className="rounded-lg">CN</AvatarFallback>
</Avatar>
<div className="flex-1">
<h3 className="text-lg font-semibold line-clamp-1 mb-1">
{app.title}
</h3>
<p className="text-sm opacity-80">{formatDate(app.update_time)}</p>
</div>
</CardContent>
</Card>
);
}

View File

@ -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: <MessageSquare className="h-6 w-6" />,
update_time: '11/24/2024',
avatar: <MessageSquare className="h-6 w-6" />,
},
{
id: 2,
title: 'Search app 01',
type: 'Search app',
date: '11/24/2024',
icon: <Search className="h-6 w-6" />,
update_time: '11/24/2024',
avatar: <Search className="h-6 w-6" />,
},
{
id: 3,
title: 'Chatbot 01',
type: 'Chat app',
date: '11/24/2024',
icon: <MessageSquare className="h-6 w-6" />,
update_time: '11/24/2024',
avatar: <MessageSquare className="h-6 w-6" />,
},
{
id: 4,
title: 'Workflow 01',
type: 'Agent',
date: '11/24/2024',
icon: <Cpu className="h-6 w-6" />,
update_time: '11/24/2024',
avatar: <Cpu className="h-6 w-6" />,
},
];
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"
></Segmented>
</div>
<div className="grid grid-cols-4 gap-6">
{[...Array(12)].map((_, i) => {
const app = applications[i % 4];
return (
<Card
key={i}
className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard"
>
<CardContent className="p-4 flex items-center gap-6">
<div className="w-[70px] h-[70px] rounded-xl flex items-center justify-center bg-gradient-to-br from-[#45A7FA] via-[#AE63E3] to-[#4433FF]">
{app.icon}
</div>
<div className="flex-1">
<h3 className="text-lg font-semibold">{app.title}</h3>
<p className="text-sm opacity-80">{app.type}</p>
<p className="text-sm opacity-80">{app.date}</p>
</div>
<Button variant="icon" size="icon">
<ChevronRight className="h-6 w-6" />
</Button>
</CardContent>
</Card>
);
})}
<div className="flex flex-wrap gap-4">
{val === Routes.Agents ||
[...Array(12)].map((_, i) => {
const app = applications[i % 4];
return <ApplicationCard key={i} app={app}></ApplicationCard>;
})}
{val === Routes.Agents && <Agents></Agents>}
</div>
</section>
);

View File

@ -28,7 +28,7 @@ export function Datasets() {
</div>
) : (
<div className="flex gap-4 flex-1">
{kbs.slice(0, 4).map((dataset) => (
{kbs.slice(0, 6).map((dataset) => (
<DatasetCard
key={dataset.id}
dataset={dataset}