"use client" import { useCallback, useEffect, useMemo, useState } from "react" import { ArrowLeftIcon, PlusIcon, SearchIcon } from "lucide-react" import { toast } from "sonner" import { Badge } from "@/components/ui/badge" import { Button } from "@/components/ui/button" import { Dialog, DialogContent, DialogFooter, DialogHeader, 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 { cn, formatDateTime } from "@/lib/utils" import { createAIWorkflow, fetchAIWorkflow, fetchAIWorkflowNodeSpecs, fetchAIWorkflows, fetchAIWorkflowUsage, fetchAIWorkflowVersions, publishAIWorkflow, restoreAIWorkflowVersion, updateAIWorkflow, validateAIWorkflow, type AIWorkflow, type AIWorkflowDefinition, type AIWorkflowNodeSpec, type AIWorkflowUsage, type AIWorkflowVersion, } from "@/lib/api/admin" import { WorkflowEditor } from "./_components/workflow-editor" const emptyDefinition: AIWorkflowDefinition = { schemaVersion: 2, nodes: [ { id: "start_1", type: "start", meta: { position: { x: 0, y: 80 } }, data: { title: "开始", config: {}, inputsValues: {} } }, { id: "end_1", type: "end", meta: { position: { x: 260, y: 80 } }, data: { title: "结束", config: {}, inputsValues: {} } }, ], edges: [{ sourceNodeID: "start_1", targetNodeID: "end_1", sourcePortID: "edge_start_end" }], } export default function DashboardAIWorkflowsPage() { const [items, setItems] = useState([]) const [nodeSpecs, setNodeSpecs] = useState([]) 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 [query, setQuery] = useState("") const [saving, setSaving] = useState(false) const [dirty, setDirty] = useState(false) const [metadataOpen, setMetadataOpen] = useState(false) const [metadataName, setMetadataName] = useState("") const [metadataDescription, setMetadataDescription] = useState("") const loadList = useCallback(async () => { const [page, specs] = await Promise.all([fetchAIWorkflows({ limit: 100 }), fetchAIWorkflowNodeSpecs()]) setItems(page.results ?? []) setNodeSpecs(specs ?? []) }, []) const select = useCallback(async (id: number) => { const [item, versionPage, uses] = await Promise.all([ fetchAIWorkflow(id), fetchAIWorkflowVersions({ workflowId: id, limit: 50 }), fetchAIWorkflowUsage(id), ]) setActive(item); setName(item.name); setDescription(item.description); setDefinition(item.draftDefinition) setVersions(versionPage.results ?? []); setUsage(uses ?? []); setDirty(false) }, []) useEffect(() => { void loadList().then(async () => { if (!active) { const page = await fetchAIWorkflows({ limit: 1 }) if (page.results?.[0]) await select(page.results[0].id) } }).catch((error) => toast.error(error instanceof Error ? error.message : "加载工作流失败")) }, [active, loadList, select]) const visible = useMemo(() => items.filter((item) => item.name.toLowerCase().includes(query.trim().toLowerCase())), [items, query]) const create = () => { setActive(null); setName(""); setDescription(""); setDefinition(emptyDefinition); setVersions([]); setUsage([]); setDirty(false) } const openMetadata = () => { setMetadataName(name); setMetadataDescription(description); setMetadataOpen(true) } const applyMetadata = () => { setName(metadataName); setDescription(metadataDescription); setDirty(true); setMetadataOpen(false) } async function save() { if (!name.trim()) return toast.error("请填写工作流名称") setSaving(true) try { if (active) { await updateAIWorkflow({ id: active.id, name: name.trim(), description: description.trim(), definition }); await select(active.id) } else { const created = await createAIWorkflow({ name: name.trim(), description: description.trim(), definition }); await loadList(); await select(created.id) } await loadList(); toast.success("草稿已保存") } catch (error) { toast.error(error instanceof Error ? error.message : "保存失败") } finally { setSaving(false) } } async function publish() { if (!active) return toast.error("请先保存草稿") setSaving(true) try { const version = await publishAIWorkflow(active.id, definition); await select(active.id); await loadList(); toast.success(`已发布 v${version.version}`) } catch (error) { toast.error(error instanceof Error ? error.message : "发布失败") } finally { setSaving(false) } } async function restore(version: AIWorkflowVersion) { if (!active) return try { await restoreAIWorkflowVersion(active.id, version.id); await select(active.id); toast.success(`已将 v${version.version} 恢复为草稿`) } catch (error) { toast.error(error instanceof Error ? error.message : "恢复失败") } } return
{active || name ? <>

{name || "未命名工作流"}

{active?.publishedVersionId ? "已发布" : "草稿"}{dirty ? 未保存 : null}

{description || "暂未填写业务说明"}

编辑画布版本历史 ({versions.length})使用情况 ({usage.length})
{ setDefinition(next); setDirty(true) }} onSaveDraft={() => void save()} onPublish={() => void publish()} saveDraftDisabled={saving} publishDisabled={saving || !active} />{versions.length ? versions.map((version) =>
v{version.version}
发布于 {formatDateTime(version.publishedAt || version.createdAt)}
发布人:{version.publishedByName || "-"} · 指纹 {version.definitionHash.slice(0, 10)}
) :

尚未发布版本。

}
{usage.length ? usage.map((item) =>
{item.aiAgentName}
固定关联 v{item.workflowVersion}
{item.enabled ? "启用" : "已停用"}
) :

暂未被任何 Agent 使用。

}
:

创建第一个工作流

从空白画布开始,发布后再关联给 Agent。

}
编辑工作流信息
setMetadataName(event.target.value)} placeholder="工作流名称" />