From 7eb5ea38143bfd28f55286b8ff1ecb3cf6788535 Mon Sep 17 00:00:00 2001 From: balibabu Date: Mon, 21 Jul 2025 16:28:06 +0800 Subject: [PATCH] Feat: Display agent history versions #3221 (#8942) ### What problem does this PR solve? Feat: Display agent history versions #3221 ### Type of change - [x] New Feature (non-breaking change which adds functionality) --- web/src/hooks/use-agent-request.ts | 43 ++++++ web/src/pages/agent/canvas/index.tsx | 2 +- .../pages/agent/canvas/node/switch-node.tsx | 2 +- web/src/pages/agent/index.tsx | 11 +- web/src/pages/agent/version-dialog/index.tsx | 131 ++++++++++++++++++ web/tailwind.css | 1 + 6 files changed, 187 insertions(+), 3 deletions(-) create mode 100644 web/src/pages/agent/version-dialog/index.tsx diff --git a/web/src/hooks/use-agent-request.ts b/web/src/hooks/use-agent-request.ts index 2a081b67f..ea6f2c3df 100644 --- a/web/src/hooks/use-agent-request.ts +++ b/web/src/hooks/use-agent-request.ts @@ -33,6 +33,8 @@ export const enum AgentApiAction { TestDbConnect = 'testDbConnect', DebugSingle = 'debugSingle', FetchInputForm = 'fetchInputForm', + FetchVersionList = 'fetchVersionList', + FetchVersion = 'fetchVersion', } export const EmptyDsl = { @@ -396,3 +398,44 @@ export const useFetchInputForm = (componentId?: string) => { return data; }; + +export const useFetchVersionList = () => { + const { id } = useParams(); + const { data, isFetching: loading } = useQuery< + Array<{ created_at: string; title: string; id: string }> + >({ + queryKey: [AgentApiAction.FetchVersionList], + initialData: [], + gcTime: 0, + queryFn: async () => { + const { data } = await flowService.getListVersion({}, id); + + return data?.data ?? []; + }, + }); + + return { data, loading }; +}; + +export const useFetchVersion = ( + version_id?: string, +): { + data?: IFlow; + loading: boolean; +} => { + const { data, isFetching: loading } = useQuery({ + queryKey: [AgentApiAction.FetchVersion, version_id], + initialData: undefined, + gcTime: 0, + enabled: !!version_id, // Only call API when both values are provided + queryFn: async () => { + if (!version_id) return undefined; + + const { data } = await flowService.getVersion({}, version_id); + + return data?.data ?? undefined; + }, + }); + + return { data, loading }; +}; diff --git a/web/src/pages/agent/canvas/index.tsx b/web/src/pages/agent/canvas/index.tsx index 9bbfd310e..41a196c13 100644 --- a/web/src/pages/agent/canvas/index.tsx +++ b/web/src/pages/agent/canvas/index.tsx @@ -56,7 +56,7 @@ import { SwitchNode } from './node/switch-node'; import { TemplateNode } from './node/template-node'; import { ToolNode } from './node/tool-node'; -const nodeTypes: NodeTypes = { +export const nodeTypes: NodeTypes = { ragNode: RagNode, categorizeNode: CategorizeNode, beginNode: BeginNode, diff --git a/web/src/pages/agent/canvas/node/switch-node.tsx b/web/src/pages/agent/canvas/node/switch-node.tsx index 2910cb39e..796239c33 100644 --- a/web/src/pages/agent/canvas/node/switch-node.tsx +++ b/web/src/pages/agent/canvas/node/switch-node.tsx @@ -64,7 +64,7 @@ const ConditionBlock = ({ function InnerSwitchNode({ id, data, selected }: NodeProps) { const { positions } = useBuildSwitchHandlePositions({ data, id }); return ( - + Run app - @@ -159,6 +165,9 @@ export default function Agent() { isAgent > )} + {versionDialogVisible && ( + + )} ); } diff --git a/web/src/pages/agent/version-dialog/index.tsx b/web/src/pages/agent/version-dialog/index.tsx new file mode 100644 index 000000000..b2ed56f33 --- /dev/null +++ b/web/src/pages/agent/version-dialog/index.tsx @@ -0,0 +1,131 @@ +import { Button } from '@/components/ui/button'; +import { Card, CardContent } from '@/components/ui/card'; +import { + Dialog, + DialogContent, + DialogHeader, + DialogTitle, +} from '@/components/ui/dialog'; +import { Spin } from '@/components/ui/spin'; +import { + useFetchVersion, + useFetchVersionList, +} from '@/hooks/use-agent-request'; +import { IModalProps } from '@/interfaces/common'; +import { cn } from '@/lib/utils'; +import { formatDate } from '@/utils/date'; +import { downloadJsonFile } from '@/utils/file-util'; +import { + Background, + ConnectionMode, + ReactFlow, + ReactFlowProvider, +} from '@xyflow/react'; +import { ArrowDownToLine } from 'lucide-react'; +import { ReactNode, useCallback, useEffect, useState } from 'react'; +import { useTranslation } from 'react-i18next'; +import { nodeTypes } from '../canvas'; + +export function VersionDialog({ + hideModal, +}: IModalProps & { initialName?: string; title?: ReactNode }) { + const { t } = useTranslation(); + const { data, loading } = useFetchVersionList(); + const [selectedId, setSelectedId] = useState(''); + const { data: agent, loading: versionLoading } = useFetchVersion(selectedId); + + const handleClick = useCallback( + (id: string) => () => { + setSelectedId(id); + }, + [], + ); + + const downloadFile = useCallback(() => { + const graph = agent?.dsl.graph; + if (graph) { + downloadJsonFile(graph, agent?.title); + } + }, [agent?.dsl.graph, agent?.title]); + + useEffect(() => { + if (data.length > 0) { + setSelectedId(data[0].id); + } + }, [data]); + + return ( + + + + {t('flow.historyversion')} + +
+
+ {loading ? ( + + ) : ( +
    + {data.map((x) => ( +
  • + {x.title} +
  • + ))} +
+ )} +
+ +
+ {versionLoading ? ( + + ) : ( + + +
+
+
{agent?.title}
+

+ {formatDate(agent?.create_date)} +

+
+ +
+ + ({ + ...x, + type: 'default', + })) || [] + } + fitView + nodeTypes={nodeTypes} + edgeTypes={{}} + zoomOnScroll={true} + panOnDrag={true} + zoomOnDoubleClick={false} + preventScrolling={true} + minZoom={0.1} + > + + + +
+
+ )} +
+
+
+
+ ); +} diff --git a/web/tailwind.css b/web/tailwind.css index 881cb20a5..b2132b0cc 100644 --- a/web/tailwind.css +++ b/web/tailwind.css @@ -199,6 +199,7 @@ --background-checked: rgba(76, 164, 231, 1); --background-highlight: rgba(76, 164, 231, 0.1); + --background-agent: rgba(22, 22, 24, 1); --input-border: rgba(255, 255, 255, 0.2);