From b2d28fdcf9e39fda1d1c9120aa78e4a2e83d2747 Mon Sep 17 00:00:00 2001 From: mlogclub Date: Mon, 27 Jul 2026 20:37:36 +0800 Subject: [PATCH] refactor: implement workflow usage and versions dialogs in the dashboard --- .../_components/workflow-usage-dialog.tsx | 82 +++++++++ .../_components/workflow-versions-dialog.tsx | 114 ++++++++++++ .../_components/workflow-workbench.tsx | 156 +++------------- web/app/dashboard/ai-workflows/page.tsx | 171 ++++++++++++++++-- 4 files changed, 375 insertions(+), 148 deletions(-) create mode 100644 web/app/dashboard/ai-workflows/_components/workflow-usage-dialog.tsx create mode 100644 web/app/dashboard/ai-workflows/_components/workflow-versions-dialog.tsx diff --git a/web/app/dashboard/ai-workflows/_components/workflow-usage-dialog.tsx b/web/app/dashboard/ai-workflows/_components/workflow-usage-dialog.tsx new file mode 100644 index 0000000..fdb3134 --- /dev/null +++ b/web/app/dashboard/ai-workflows/_components/workflow-usage-dialog.tsx @@ -0,0 +1,82 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" + +import { toast } from "sonner" + +import { ProjectDialog } from "@/components/project-dialog" +import { Badge } from "@/components/ui/badge" +import { + fetchAIWorkflowUsage, + type AIWorkflow, + type AIWorkflowUsage, +} from "@/lib/api/admin" + +export function WorkflowUsageDialog({ + workflow, + open, + onOpenChange, +}: { + workflow: AIWorkflow | null + open: boolean + onOpenChange: (open: boolean) => void +}) { + const [usage, setUsage] = useState([]) + const [loading, setLoading] = useState(false) + + const load = useCallback(async () => { + if (!workflow || !open) return + setLoading(true) + try { + setUsage((await fetchAIWorkflowUsage(workflow.id)) ?? []) + } catch (error) { + toast.error(error instanceof Error ? error.message : "加载使用情况失败") + } finally { + setLoading(false) + } + }, [open, workflow]) + + useEffect(() => { + void load() + }, [load]) + + return ( + + {loading ? ( +
+ 正在加载使用情况… +
+ ) : usage.length ? ( +
+ {usage.map((item) => ( +
+
+
{item.aiAgentName}
+
+ 固定关联 v{item.workflowVersion} +
+
+ + {item.enabled ? "启用" : "已停用"} + +
+ ))} +
+ ) : ( +
+ 暂未被任何 Agent 使用 +
+ )} +
+ ) +} diff --git a/web/app/dashboard/ai-workflows/_components/workflow-versions-dialog.tsx b/web/app/dashboard/ai-workflows/_components/workflow-versions-dialog.tsx new file mode 100644 index 0000000..fe2b74b --- /dev/null +++ b/web/app/dashboard/ai-workflows/_components/workflow-versions-dialog.tsx @@ -0,0 +1,114 @@ +"use client" + +import { useCallback, useEffect, useState } from "react" + +import { toast } from "sonner" + +import { ProjectDialog } from "@/components/project-dialog" +import { Badge } from "@/components/ui/badge" +import { Button } from "@/components/ui/button" +import { + fetchAIWorkflowVersions, + restoreAIWorkflowVersion, + type AIWorkflow, + type AIWorkflowVersion, +} from "@/lib/api/admin" +import { formatDateTime } from "@/lib/utils" + +export function WorkflowVersionsDialog({ + workflow, + open, + onOpenChange, + onRestored, +}: { + workflow: AIWorkflow | null + open: boolean + onOpenChange: (open: boolean) => void + onRestored: () => void +}) { + const [versions, setVersions] = useState([]) + const [loading, setLoading] = useState(false) + const [restoringId, setRestoringId] = useState(null) + + const load = useCallback(async () => { + if (!workflow || !open) return + setLoading(true) + try { + const page = await fetchAIWorkflowVersions({ + workflowId: workflow.id, + limit: 50, + }) + setVersions(page.results ?? []) + } catch (error) { + toast.error(error instanceof Error ? error.message : "加载版本历史失败") + } finally { + setLoading(false) + } + }, [open, workflow]) + + useEffect(() => { + void load() + }, [load]) + + async function restore(version: AIWorkflowVersion) { + if (!workflow || restoringId !== null) return + setRestoringId(version.id) + try { + await restoreAIWorkflowVersion(workflow.id, version.id) + onRestored() + toast.success(`已将 v${version.version} 恢复为草稿`) + } catch (error) { + toast.error(error instanceof Error ? error.message : "恢复失败") + } finally { + setRestoringId(null) + } + } + + return ( + + {loading ? ( +
+ 正在加载版本历史… +
+ ) : versions.length ? ( +
+ {versions.map((version) => ( +
+ v{version.version} +
+
+ {formatDateTime(version.publishedAt || version.createdAt)} +
+
+ 发布人:{version.publishedByName || "-"} +
+
+ +
+ ))} +
+ ) : ( +
+ 尚未发布版本 +
+ )} +
+ ) +} diff --git a/web/app/dashboard/ai-workflows/_components/workflow-workbench.tsx b/web/app/dashboard/ai-workflows/_components/workflow-workbench.tsx index 70077ab..fa4e942 100644 --- a/web/app/dashboard/ai-workflows/_components/workflow-workbench.tsx +++ b/web/app/dashboard/ai-workflows/_components/workflow-workbench.tsx @@ -2,8 +2,6 @@ import { useCallback, useEffect, useState } from "react" -import { ArrowLeftIcon } from "lucide-react" -import { useRouter } from "next/navigation" import { toast } from "sonner" import { Badge } from "@/components/ui/badge" @@ -16,28 +14,16 @@ import { DialogTitle, } from "@/components/ui/dialog" import { Input } from "@/components/ui/input" -import { - Tabs, - TabsContent, - TabsList, - TabsTrigger, -} from "@/components/ui/tabs" import { Textarea } from "@/components/ui/textarea" import { createAIWorkflow, fetchAIWorkflow, fetchAIWorkflowDefaultDefinition, - fetchAIWorkflowUsage, - fetchAIWorkflowVersions, publishAIWorkflow, - restoreAIWorkflowVersion, updateAIWorkflow, type AIWorkflow, type AIWorkflowDefinition, - type AIWorkflowUsage, - type AIWorkflowVersion, } from "@/lib/api/admin" -import { formatDateTime } from "@/lib/utils" import { OfficialWorkflowEditor } from "./official-workflow-editor" @@ -68,23 +54,18 @@ const emptyDefinition: AIWorkflowDefinition = { type WorkflowWorkbenchProps = { workflowID?: number - onClose?: () => void onSaved?: () => void } export function WorkflowWorkbench({ workflowID, - onClose, onSaved, }: WorkflowWorkbenchProps) { - const router = useRouter() const [active, setActive] = useState(null) const [name, setName] = useState("") const [description, setDescription] = useState("") const [definition, setDefinition] = useState(emptyDefinition) - const [versions, setVersions] = useState([]) - const [usage, setUsage] = useState([]) const [saving, setSaving] = useState(false) const [dirty, setDirty] = useState(false) const [loaded, setLoaded] = useState(false) @@ -100,17 +81,11 @@ export function WorkflowWorkbench({ return } - const [item, versionPage, uses] = await Promise.all([ - fetchAIWorkflow(workflowID), - fetchAIWorkflowVersions({ workflowId: workflowID, limit: 50 }), - fetchAIWorkflowUsage(workflowID), - ]) + const item = await fetchAIWorkflow(workflowID) setActive(item) setName(item.name) setDescription(item.description) setDefinition(item.draftDefinition) - setVersions(versionPage.results ?? []) - setUsage(uses ?? []) setDirty(false) setLoaded(true) }, [workflowID]) @@ -200,7 +175,18 @@ export function WorkflowWorkbench({ setSaving(true) try { const version = await publishAIWorkflow(active.id, definition) - await load() + setActive((current) => + current + ? { + ...current, + draftDefinition: definition, + publishedVersionId: version.id, + updatedAt: version.updatedAt, + } + : current + ) + setDirty(false) + onSaved?.() toast.success(`已发布 v${version.version}`) } catch (error) { toast.error(error instanceof Error ? error.message : "发布失败") @@ -209,17 +195,6 @@ export function WorkflowWorkbench({ } } - async function restore(version: AIWorkflowVersion) { - if (!active) return - try { - await restoreAIWorkflowVersion(active.id, version.id) - await load() - toast.success(`已将 v${version.version} 恢复为草稿`) - } catch (error) { - toast.error(error instanceof Error ? error.message : "恢复失败") - } - } - return (
@@ -260,100 +235,23 @@ export function WorkflowWorkbench({ 发布版本
-