Update workflow
This commit is contained in:
+1
-1
Submodule docs updated: abf6a84eba...1dee786c06
@@ -0,0 +1,35 @@
|
|||||||
|
"use client"
|
||||||
|
|
||||||
|
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"
|
||||||
|
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 { formatDateTime } from "@/lib/utils"
|
||||||
|
import { createAIWorkflow, fetchAIWorkflow, fetchAIWorkflowNodeSpecs, fetchAIWorkflowUsage, fetchAIWorkflowVersions, publishAIWorkflow, restoreAIWorkflowVersion, updateAIWorkflow, validateAIWorkflow, type AIWorkflow, type AIWorkflowDefinition, type AIWorkflowNodeSpec, type AIWorkflowUsage, type AIWorkflowVersion } from "@/lib/api/admin"
|
||||||
|
|
||||||
|
import { WorkflowEditor } from "./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 function WorkflowWorkbench({ workflowID, onClose, onSaved }: { workflowID?: number; onClose?: () => void; onSaved?: () => void }) {
|
||||||
|
const router = useRouter()
|
||||||
|
const [active, setActive] = useState<AIWorkflow | null>(null); const [nodeSpecs, setNodeSpecs] = useState<AIWorkflowNodeSpec[]>([])
|
||||||
|
const [name, setName] = useState(""); const [description, setDescription] = useState(""); const [definition, setDefinition] = useState<AIWorkflowDefinition>(emptyDefinition)
|
||||||
|
const [versions, setVersions] = useState<AIWorkflowVersion[]>([]); const [usage, setUsage] = useState<AIWorkflowUsage[]>([]); const [saving, setSaving] = useState(false); const [dirty, setDirty] = useState(false)
|
||||||
|
const [metadataOpen, setMetadataOpen] = useState(false); const [metadataName, setMetadataName] = useState(""); const [metadataDescription, setMetadataDescription] = useState("")
|
||||||
|
const load = useCallback(async () => { const specs = await fetchAIWorkflowNodeSpecs(); setNodeSpecs(specs ?? []); if (!workflowID) return; const [item, versionPage, uses] = await Promise.all([fetchAIWorkflow(workflowID), fetchAIWorkflowVersions({ workflowId: workflowID, limit: 50 }), fetchAIWorkflowUsage(workflowID)]); setActive(item); setName(item.name); setDescription(item.description); setDefinition(item.draftDefinition); setVersions(versionPage.results ?? []); setUsage(uses ?? []); setDirty(false) }, [workflowID])
|
||||||
|
useEffect(() => { void load().catch((error) => toast.error(error instanceof Error ? error.message : "加载工作流失败")) }, [load])
|
||||||
|
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 load() } else { const created = await createAIWorkflow({ name: name.trim(), description: description.trim(), definition }); setActive(created); setName(created.name); setDescription(created.description); setDefinition(created.draftDefinition) }; onSaved?.(); 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 load(); 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 load(); toast.success(`已将 v${version.version} 恢复为草稿`) } catch (error) { toast.error(error instanceof Error ? error.message : "恢复失败") } }
|
||||||
|
return <div className="flex h-full min-h-0 flex-col overflow-hidden bg-background"><header className="shrink-0 border-b px-6 py-4"><div className="flex items-center gap-4"><Button variant="ghost" size="icon" onClick={() => router.push("/dashboard/ai-workflows")}><ArrowLeftIcon className="size-4" /></Button><div className="min-w-0 flex-1"><div className="flex items-center gap-2"><h1 className="truncate text-lg font-semibold">{name || "新建工作流"}</h1><Badge variant={active?.publishedVersionId ? "secondary" : "outline"}>{active?.publishedVersionId ? "已发布" : "草稿"}</Badge>{dirty ? <span className="text-xs text-amber-600">未保存</span> : null}</div><p className="mt-1 line-clamp-1 text-sm text-muted-foreground">{description || "暂未填写业务说明"}</p></div><div className="flex shrink-0 gap-2"><Button variant="ghost" onClick={openMetadata}>编辑信息</Button><Button variant="outline" disabled={saving} onClick={() => void validateAIWorkflow(definition).then((result) => toast[result.valid ? "success" : "error"](result.valid ? "校验通过" : `发现 ${result.errors.length} 个问题`))}>校验</Button><Button variant="outline" disabled={saving} onClick={() => void save()}>保存草稿</Button><Button disabled={saving || !active} onClick={() => void publish()}>发布版本</Button></div></div></header><Tabs defaultValue="editor" className="flex min-h-0 flex-1 flex-col"><div className="shrink-0 border-b px-6"><TabsList className="h-11 bg-transparent"><TabsTrigger value="editor">编辑画布</TabsTrigger><TabsTrigger value="versions">版本历史 ({versions.length})</TabsTrigger><TabsTrigger value="usage">使用情况 ({usage.length})</TabsTrigger></TabsList></div><TabsContent value="editor" className="min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden"><WorkflowEditor definition={definition} nodeSpecs={nodeSpecs} onDefinitionChange={(next) => { setDefinition(next); setDirty(true) }} onSaveDraft={() => void save()} onPublish={() => void publish()} saveDraftDisabled={saving} publishDisabled={saving || !active} /></TabsContent><TabsContent value="versions" className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-6">{versions.length ? versions.map((version) => <div key={version.id} className="mb-3 flex items-center gap-4 rounded-lg border p-4"><Badge>v{version.version}</Badge><div className="min-w-0 flex-1"><div className="text-sm font-medium">发布于 {formatDateTime(version.publishedAt || version.createdAt)}</div><div className="text-xs text-muted-foreground">发布人:{version.publishedByName || "-"}</div></div><Button variant="outline" size="sm" onClick={() => void restore(version)}>恢复为草稿</Button></div>) : <p className="text-sm text-muted-foreground">尚未发布版本。</p>}</TabsContent><TabsContent value="usage" className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-6">{usage.length ? usage.map((item) => <div key={`${item.aiAgentId}-${item.workflowVersionId}`} className="mb-3 flex items-center justify-between rounded-lg border p-4"><div><div className="font-medium">{item.aiAgentName}</div><div className="mt-1 text-sm text-muted-foreground">固定关联 v{item.workflowVersion}</div></div><Badge variant={item.enabled ? "secondary" : "outline"}>{item.enabled ? "启用" : "已停用"}</Badge></div>) : <p className="text-sm text-muted-foreground">暂未被任何 Agent 使用。</p>}</TabsContent></Tabs><Dialog open={metadataOpen} onOpenChange={setMetadataOpen}><DialogContent><DialogHeader><DialogTitle>编辑工作流信息</DialogTitle></DialogHeader><div className="space-y-4"><Input value={metadataName} onChange={(event) => setMetadataName(event.target.value)} placeholder="工作流名称" /><Textarea value={metadataDescription} onChange={(event) => setMetadataDescription(event.target.value)} placeholder="适用场景、目标与业务边界" /></div><DialogFooter><Button variant="outline" onClick={() => setMetadataOpen(false)}>取消</Button><Button onClick={applyMetadata}>确认</Button></DialogFooter></DialogContent></Dialog></div>
|
||||||
|
}
|
||||||
@@ -1,110 +1,33 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useCallback, useEffect, useMemo, useState } from "react"
|
import { useState } from "react"
|
||||||
import { ArrowLeftIcon, PlusIcon, SearchIcon } from "lucide-react"
|
import { GitBranchIcon } from "lucide-react"
|
||||||
import { toast } from "sonner"
|
|
||||||
|
|
||||||
|
import { DashboardCrudPage } from "@/components/dashboard/crud"
|
||||||
|
import { ProjectDialog } from "@/components/project-dialog"
|
||||||
import { Badge } from "@/components/ui/badge"
|
import { Badge } from "@/components/ui/badge"
|
||||||
import { Button } from "@/components/ui/button"
|
import { deleteAIWorkflow, fetchAIWorkflows, type AIWorkflow, type CreateAIWorkflowPayload } from "@/lib/api/admin"
|
||||||
import { Dialog, DialogContent, DialogFooter, DialogHeader, DialogTitle } from "@/components/ui/dialog"
|
import { formatDateTime } from "@/lib/utils"
|
||||||
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"
|
import { WorkflowWorkbench } from "./_components/workflow-workbench"
|
||||||
|
|
||||||
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() {
|
export default function DashboardAIWorkflowsPage() {
|
||||||
const [items, setItems] = useState<AIWorkflow[]>([])
|
const [reloadKey, setReloadKey] = useState(0)
|
||||||
const [nodeSpecs, setNodeSpecs] = useState<AIWorkflowNodeSpec[]>([])
|
return <DashboardCrudPage<AIWorkflow, CreateAIWorkflowPayload>
|
||||||
const [active, setActive] = useState<AIWorkflow | null>(null)
|
filters={[{ name: "name", label: "工作流名称", placeholder: "搜索工作流名称", defaultValue: "", trim: true, className: "w-full sm:w-72" }]}
|
||||||
const [name, setName] = useState("")
|
columns={[
|
||||||
const [description, setDescription] = useState("")
|
{ key: "workflow", label: "工作流", render: (item) => <div className="flex items-start gap-3"><div className="mt-0.5 flex size-8 items-center justify-center rounded-md bg-muted text-muted-foreground"><GitBranchIcon className="size-4" /></div><div className="min-w-0"><div className="font-medium">{item.name}</div><div className="mt-1 line-clamp-2 text-sm text-muted-foreground">{item.description || "暂无业务说明"}</div></div></div> },
|
||||||
const [definition, setDefinition] = useState<AIWorkflowDefinition>(emptyDefinition)
|
{ key: "status", label: "状态", render: (item) => <Badge variant={item.publishedVersionId ? "secondary" : "outline"}>{item.publishedVersionId ? "已发布" : "草稿"}</Badge> },
|
||||||
const [versions, setVersions] = useState<AIWorkflowVersion[]>([])
|
{ key: "version", label: "当前版本", render: (item) => item.publishedVersionId ? `已发布 #${item.publishedVersionId}` : "-" },
|
||||||
const [usage, setUsage] = useState<AIWorkflowUsage[]>([])
|
{ key: "updatedAt", label: "更新时间", render: (item) => formatDateTime(item.updatedAt) },
|
||||||
const [query, setQuery] = useState("")
|
]}
|
||||||
const [saving, setSaving] = useState(false)
|
fetchList={(query) => fetchAIWorkflows({ name: typeof query.name === "string" ? query.name : undefined, page: Number(query.page), limit: Number(query.limit) })}
|
||||||
const [dirty, setDirty] = useState(false)
|
getItemId={(item) => item.id}
|
||||||
const [metadataOpen, setMetadataOpen] = useState(false)
|
createItem={async () => undefined}
|
||||||
const [metadataName, setMetadataName] = useState("")
|
updateItem={async () => undefined}
|
||||||
const [metadataDescription, setMetadataDescription] = useState("")
|
deleteItem={(item) => deleteAIWorkflow(item.id)}
|
||||||
|
reloadKey={reloadKey}
|
||||||
const loadList = useCallback(async () => {
|
renderEditDialog={({ open, item, itemId, onOpenChange }) => <ProjectDialog open={open} onOpenChange={onOpenChange} title={item ? "编辑工作流" : "新建工作流"} size="xxl" defaultFullscreen allowFullscreen bodyScrollable={false} headerClassName="sr-only"><WorkflowWorkbench workflowID={itemId ?? undefined} onSaved={() => setReloadKey((value) => value + 1)} /></ProjectDialog>}
|
||||||
const [page, specs] = await Promise.all([fetchAIWorkflows({ limit: 100 }), fetchAIWorkflowNodeSpecs()])
|
labels={{ refresh: "刷新", create: "创建工作流", query: "查询", loading: "正在加载工作流…", empty: "暂无工作流", actions: "操作", edit: "编辑", delete: "删除", processing: "处理中…", moreActions: (item) => `${item.name} 的更多操作`, loadFailed: "加载工作流失败", saveFailed: "保存工作流失败", deleteFailed: "删除工作流失败", created: (payload) => `已创建 ${payload.name}`, updated: (item) => `已更新 ${item.name}`, deleted: (item) => `已删除 ${item.name}` }}
|
||||||
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 <div className="h-full min-h-0 overflow-hidden bg-background"><div className="flex h-full min-h-0 overflow-hidden">
|
|
||||||
<aside className="flex w-80 shrink-0 flex-col border-r bg-muted/15">
|
|
||||||
<div className="shrink-0 space-y-3 border-b p-4"><div className="flex items-center justify-between"><div><h1 className="text-base font-semibold">工作流管理</h1><p className="mt-1 text-xs text-muted-foreground">独立维护,发布版本供 Agent 固定关联</p></div><Button size="icon" onClick={create}><PlusIcon className="size-4" /></Button></div><div className="relative"><SearchIcon className="absolute left-3 top-2.5 size-4 text-muted-foreground" /><Input value={query} onChange={(event) => setQuery(event.target.value)} className="pl-9" placeholder="搜索工作流" /></div></div>
|
|
||||||
<div className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-2">{visible.length ? visible.map((item) => <button key={item.id} type="button" onClick={() => void select(item.id)} className={cn("mb-1 w-full rounded-lg border p-3 text-left transition-colors", active?.id === item.id ? "border-primary/30 bg-primary/5" : "border-transparent hover:bg-muted")}><div className="truncate font-medium">{item.name}</div><div className="mt-1 line-clamp-2 text-xs text-muted-foreground">{item.description || "暂无业务说明"}</div><div className="mt-3 flex items-center justify-between text-xs"><Badge variant={item.publishedVersionId ? "secondary" : "outline"}>{item.publishedVersionId ? "已发布" : "草稿"}</Badge><span className="text-muted-foreground">{formatDateTime(item.updatedAt)}</span></div></button>) : <div className="p-8 text-center text-sm text-muted-foreground">没有匹配的工作流</div>}</div>
|
|
||||||
</aside>
|
|
||||||
<main className="flex min-w-0 flex-1 flex-col overflow-hidden">{active || name ? <>
|
|
||||||
<header className="shrink-0 border-b bg-background px-6 py-4"><div className="flex items-center gap-4"><Button variant="ghost" size="icon" className="md:hidden" onClick={create}><ArrowLeftIcon className="size-4" /></Button><div className="min-w-0 flex-1"><div className="flex items-center gap-2"><h2 className="truncate text-lg font-semibold">{name || "未命名工作流"}</h2><Badge variant={active?.publishedVersionId ? "secondary" : "outline"}>{active?.publishedVersionId ? "已发布" : "草稿"}</Badge>{dirty ? <span className="text-xs text-amber-600">未保存</span> : null}</div><p className="mt-1 line-clamp-1 text-sm text-muted-foreground">{description || "暂未填写业务说明"}</p></div><div className="flex shrink-0 gap-2"><Button variant="ghost" onClick={openMetadata}>编辑信息</Button><Button variant="outline" disabled={saving} onClick={() => void validateAIWorkflow(definition).then((result) => toast[result.valid ? "success" : "error"](result.valid ? "校验通过" : `发现 ${result.errors.length} 个问题`))}>校验</Button><Button variant="outline" disabled={saving} onClick={() => void save()}>保存草稿</Button><Button disabled={saving || !active} onClick={() => void publish()}>发布版本</Button></div></div></header>
|
|
||||||
<Tabs defaultValue="editor" className="flex min-h-0 flex-1 flex-col"><div className="shrink-0 border-b px-6"><TabsList className="h-11 bg-transparent"><TabsTrigger value="editor">编辑画布</TabsTrigger><TabsTrigger value="versions">版本历史 ({versions.length})</TabsTrigger><TabsTrigger value="usage">使用情况 ({usage.length})</TabsTrigger></TabsList></div><TabsContent value="editor" className="min-h-0 flex-1 overflow-hidden data-[state=inactive]:hidden"><WorkflowEditor definition={definition} nodeSpecs={nodeSpecs} onDefinitionChange={(next) => { setDefinition(next); setDirty(true) }} onSaveDraft={() => void save()} onPublish={() => void publish()} saveDraftDisabled={saving} publishDisabled={saving || !active} /></TabsContent><TabsContent value="versions" className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-6">{versions.length ? versions.map((version) => <div key={version.id} className="mb-3 flex items-center gap-4 rounded-lg border p-4"><Badge>v{version.version}</Badge><div className="min-w-0 flex-1"><div className="text-sm font-medium">发布于 {formatDateTime(version.publishedAt || version.createdAt)}</div><div className="text-xs text-muted-foreground">发布人:{version.publishedByName || "-"} · 指纹 {version.definitionHash.slice(0, 10)}</div></div><Button variant="outline" size="sm" onClick={() => void restore(version)}>恢复为草稿</Button></div>) : <p className="text-sm text-muted-foreground">尚未发布版本。</p>}</TabsContent><TabsContent value="usage" className="min-h-0 flex-1 overflow-y-auto overscroll-contain p-6">{usage.length ? usage.map((item) => <div key={`${item.aiAgentId}-${item.workflowVersionId}`} className="mb-3 flex items-center justify-between rounded-lg border p-4"><div><div className="font-medium">{item.aiAgentName}</div><div className="mt-1 text-sm text-muted-foreground">固定关联 v{item.workflowVersion}</div></div><Badge variant={item.enabled ? "secondary" : "outline"}>{item.enabled ? "启用" : "已停用"}</Badge></div>) : <p className="text-sm text-muted-foreground">暂未被任何 Agent 使用。</p>}</TabsContent></Tabs>
|
|
||||||
</> : <div className="flex flex-1 items-center justify-center"><div className="text-center"><h2 className="font-semibold">创建第一个工作流</h2><p className="mt-2 text-sm text-muted-foreground">从空白画布开始,发布后再关联给 Agent。</p><Button className="mt-4" onClick={create}>创建工作流</Button></div></div>}</main>
|
|
||||||
<Dialog open={metadataOpen} onOpenChange={setMetadataOpen}><DialogContent><DialogHeader><DialogTitle>编辑工作流信息</DialogTitle></DialogHeader><div className="space-y-4"><div className="space-y-2"><label className="text-sm font-medium">名称</label><Input value={metadataName} onChange={(event) => setMetadataName(event.target.value)} placeholder="工作流名称" /></div><div className="space-y-2"><label className="text-sm font-medium">业务说明</label><Textarea value={metadataDescription} onChange={(event) => setMetadataDescription(event.target.value)} placeholder="适用场景、目标与业务边界" /></div></div><DialogFooter><Button variant="outline" onClick={() => setMetadataOpen(false)}>取消</Button><Button onClick={applyMetadata}>确认</Button></DialogFooter></DialogContent></Dialog>
|
|
||||||
</div></div>
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user