Feat: Render the agent list page by page #3221 (#7736)

### What problem does this PR solve?

Feat: Render the agent list page by page #3221

### Type of change


- [x] New Feature (non-breaking change which adds functionality)
This commit is contained in:
balibabu 2025-05-20 16:03:55 +08:00 committed by GitHub
parent 796f4032b8
commit d72468426e
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 248 additions and 59 deletions

View File

@ -1,22 +1,60 @@
import { IFlow } from '@/interfaces/database/flow';
import flowService from '@/services/flow-service';
import { useQuery } from '@tanstack/react-query';
import { useDebounce } from 'ahooks';
import { useCallback } from 'react';
import {
useGetPaginationWithRouter,
useHandleSearchChange,
} from './logic-hooks';
export const enum AgentApiAction {
FetchAgentList = 'fetchAgentList',
}
export const useFetchAgentList = () => {
const { data, isFetching: loading } = useQuery<IFlow[]>({
queryKey: [AgentApiAction.FetchAgentList],
initialData: [],
export const useFetchAgentListByPage = () => {
const { searchString, handleInputChange } = useHandleSearchChange();
const { pagination, setPagination } = useGetPaginationWithRouter();
const debouncedSearchString = useDebounce(searchString, { wait: 500 });
const { data, isFetching: loading } = useQuery<{
kbs: IFlow[];
total: number;
}>({
queryKey: [
AgentApiAction.FetchAgentList,
{
debouncedSearchString,
...pagination,
},
],
initialData: { kbs: [], total: 0 },
gcTime: 0,
queryFn: async () => {
const { data } = await flowService.listCanvas();
const { data } = await flowService.listCanvasTeam({
keywords: debouncedSearchString,
page_size: pagination.pageSize,
page: pagination.current,
});
return data?.data ?? [];
},
});
return { data, loading };
const onInputChange: React.ChangeEventHandler<HTMLInputElement> = useCallback(
(e) => {
// setPagination({ page: 1 }); // TODO: 这里导致重复请求
handleInputChange(e);
},
[handleInputChange],
);
return {
data: data.kbs,
loading,
searchString,
handleInputChange: onInputChange,
pagination: { ...pagination, total: data?.total },
setPagination,
};
};

View File

@ -1,56 +1,47 @@
import { MoreButton } from '@/components/more-button';
import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar';
import { Button } from '@/components/ui/button';
import { Card, CardContent } from '@/components/ui/card';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { IFlow } from '@/interfaces/database/flow';
import { formatPureDate } from '@/utils/date';
import { ChevronRight, Trash2 } from 'lucide-react';
import { formatDate } from '@/utils/date';
import { AgentDropdown } from './agent-dropdown';
import { useRenameAgent } from './use-rename-agent';
interface IProps {
export type DatasetCardProps = {
data: IFlow;
}
} & Pick<ReturnType<typeof useRenameAgent>, 'showDatasetRenameModal'>;
export function AgentCard({ data }: IProps) {
export function AgentCard({ data, showDatasetRenameModal }: DatasetCardProps) {
const { navigateToAgent } = useNavigatePage();
return (
<Card className="bg-colors-background-inverse-weak border-colors-outline-neutral-standard">
<CardContent className="p-4">
<div className="flex justify-between mb-4">
{data.avatar ? (
<div
className="w-[70px] h-[70px] rounded-xl bg-cover"
style={{ backgroundImage: `url(${data.avatar})` }}
/>
) : (
<Avatar className="w-[70px] h-[70px]">
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
<Card key={data.id} className="w-40" onClick={navigateToAgent(data.id)}>
<CardContent className="p-2.5 pt-2 group">
<section className="flex justify-between mb-2">
<div className="flex gap-2 items-center">
<Avatar className="size-6 rounded-lg">
<AvatarImage src={data.avatar} />
<AvatarFallback className="rounded-lg ">CN</AvatarFallback>
</Avatar>
)}
</div>
<h3 className="text-xl font-bold mb-2">{data.title}</h3>
<p>An app that does things An app that does things</p>
<section className="flex justify-between pt-3">
<div>
Search app
<p className="text-sm opacity-80">
{formatPureDate(data.update_time)}
<AgentDropdown
showDatasetRenameModal={showDatasetRenameModal}
dataset={data}
>
<MoreButton></MoreButton>
</AgentDropdown>
</section>
<div className="flex justify-between items-end">
<div className="w-full">
<h3 className="text-lg font-semibold mb-2 line-clamp-1">
{data.title}
</h3>
<p className="text-xs text-text-sub-title">{data.description}</p>
<p className="text-xs text-text-sub-title">
{formatDate(data.update_time)}
</p>
</div>
<div className="space-x-2">
<Button
variant="icon"
size="icon"
onClick={navigateToAgent(data.id)}
>
<ChevronRight className="h-6 w-6" />
</Button>
<Button variant="icon" size="icon">
<Trash2 />
</Button>
</div>
</section>
</CardContent>
</Card>
);

View File

@ -0,0 +1,64 @@
import { ConfirmDeleteDialog } from '@/components/confirm-delete-dialog';
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuSeparator,
DropdownMenuTrigger,
} from '@/components/ui/dropdown-menu';
import { useDeleteKnowledge } from '@/hooks/use-knowledge-request';
import { IFlow } from '@/interfaces/database/flow';
import { PenLine, Trash2 } from 'lucide-react';
import { MouseEventHandler, PropsWithChildren, useCallback } from 'react';
import { useTranslation } from 'react-i18next';
import { useRenameAgent } from './use-rename-agent';
export function AgentDropdown({
children,
showDatasetRenameModal,
dataset,
}: PropsWithChildren &
Pick<ReturnType<typeof useRenameAgent>, 'showDatasetRenameModal'> & {
dataset: IFlow;
}) {
const { t } = useTranslation();
const { deleteKnowledge } = useDeleteKnowledge();
const handleShowDatasetRenameModal: MouseEventHandler<HTMLDivElement> =
useCallback(
(e) => {
e.stopPropagation();
showDatasetRenameModal(dataset);
},
[dataset, showDatasetRenameModal],
);
const handleDelete: MouseEventHandler<HTMLDivElement> = useCallback(() => {
deleteKnowledge(dataset.id);
}, [dataset.id, deleteKnowledge]);
return (
<DropdownMenu>
<DropdownMenuTrigger asChild>{children}</DropdownMenuTrigger>
<DropdownMenuContent>
<DropdownMenuItem onClick={handleShowDatasetRenameModal}>
{t('common.rename')} <PenLine />
</DropdownMenuItem>
<DropdownMenuSeparator />
<ConfirmDeleteDialog onOk={handleDelete}>
<DropdownMenuItem
className="text-text-delete-red"
onSelect={(e) => {
e.preventDefault();
}}
onClick={(e) => {
e.stopPropagation();
}}
>
{t('common.delete')} <Trash2 />
</DropdownMenuItem>
</ConfirmDeleteDialog>
</DropdownMenuContent>
</DropdownMenu>
);
}

View File

@ -1,33 +1,76 @@
import ListFilterBar from '@/components/list-filter-bar';
import { RenameDialog } from '@/components/rename-dialog';
import { Button } from '@/components/ui/button';
import { useFetchFlowList } from '@/hooks/flow-hooks';
import { RAGFlowPagination } from '@/components/ui/ragflow-pagination';
import { useNavigatePage } from '@/hooks/logic-hooks/navigate-hooks';
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
import { pick } from 'lodash';
import { Plus } from 'lucide-react';
import { useCallback } from 'react';
import { AgentCard } from './agent-card';
import { useRenameAgent } from './use-rename-agent';
export default function Agent() {
const { data } = useFetchFlowList();
const { data, pagination, setPagination, searchString, handleInputChange } =
useFetchAgentListByPage();
const { navigateToAgentTemplates } = useNavigatePage();
const {
datasetRenameLoading,
initialDatasetName,
onDatasetRenameOk,
datasetRenameVisible,
hideDatasetRenameModal,
showDatasetRenameModal,
} = useRenameAgent();
const handlePageChange = useCallback(
(page: number, pageSize?: number) => {
setPagination({ page, pageSize });
},
[setPagination],
);
return (
<section>
<div className="px-8 pt-8">
<ListFilterBar title="Agents">
<Button
variant={'tertiary'}
size={'sm'}
onClick={navigateToAgentTemplates}
<ListFilterBar
title="Agents"
searchString={searchString}
onSearchChange={handleInputChange}
>
<Button onClick={navigateToAgentTemplates}>
<Plus className="mr-2 h-4 w-4" />
Create app
Create Agent
</Button>
</ListFilterBar>
</div>
<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 max-h-[84vh] overflow-auto px-8">
<div className="flex flex-wrap gap-4 max-h-[78vh] overflow-auto px-8">
{data.map((x) => {
return <AgentCard key={x.id} data={x}></AgentCard>;
return (
<AgentCard
key={x.id}
data={x}
showDatasetRenameModal={showDatasetRenameModal}
></AgentCard>
);
})}
</div>
<div className="mt-8 px-8">
<RAGFlowPagination
{...pick(pagination, 'current', 'pageSize')}
total={pagination.total}
onChange={handlePageChange}
></RAGFlowPagination>
</div>
{datasetRenameVisible && (
<RenameDialog
hideModal={hideDatasetRenameModal}
onOk={onDatasetRenameOk}
initialName={initialDatasetName}
loading={datasetRenameLoading}
></RenameDialog>
)}
</section>
);
}

View File

@ -0,0 +1,53 @@
import { useSetModalState } from '@/hooks/common-hooks';
import { useUpdateKnowledge } from '@/hooks/use-knowledge-request';
import { IFlow } from '@/interfaces/database/flow';
import { omit } from 'lodash';
import { useCallback, useState } from 'react';
export const useRenameAgent = () => {
const [dataset, setDataset] = useState<IFlow>({} as IFlow);
const {
visible: datasetRenameVisible,
hideModal: hideDatasetRenameModal,
showModal: showDatasetRenameModal,
} = useSetModalState();
const { saveKnowledgeConfiguration, loading } = useUpdateKnowledge(true);
const onDatasetRenameOk = useCallback(
async (name: string) => {
const ret = await saveKnowledgeConfiguration({
...omit(dataset, [
'id',
'update_time',
'nickname',
'tenant_avatar',
'tenant_id',
]),
kb_id: dataset.id,
name,
});
if (ret.code === 0) {
hideDatasetRenameModal();
}
},
[saveKnowledgeConfiguration, dataset, hideDatasetRenameModal],
);
const handleShowDatasetRenameModal = useCallback(
async (record: IFlow) => {
setDataset(record);
showDatasetRenameModal();
},
[showDatasetRenameModal],
);
return {
datasetRenameLoading: loading,
initialDatasetName: dataset?.title,
onDatasetRenameOk,
datasetRenameVisible,
hideDatasetRenameModal,
showDatasetRenameModal: handleShowDatasetRenameModal,
};
};

View File

@ -1,8 +1,8 @@
import { useFetchAgentList } from '@/hooks/use-agent-request';
import { useFetchAgentListByPage } from '@/hooks/use-agent-request';
import { ApplicationCard } from './application-card';
export function Agents() {
const { data } = useFetchAgentList();
const { data } = useFetchAgentListByPage();
return data
.slice(0, 10)