feat: add workflow restoration functionality and enhance workflow editor actions

This commit is contained in:
mlogclub
2026-06-25 19:18:25 +08:00
parent b7056b59b5
commit 2f2e4ad8d0
2 changed files with 145 additions and 37 deletions
@@ -6,7 +6,6 @@ import {
ArrowUpIcon,
BotMessageSquareIcon,
BrainCircuitIcon,
CheckCircle2Icon,
DatabaseIcon,
GitBranchIcon,
HistoryIcon,
@@ -165,6 +164,7 @@ export function AIAgentConfigWorkbench({
const [directTools, setDirectTools] = useState<DirectToolItem[]>([])
const [definition, setDefinition] = useState<AIWorkflowDefinition>(fallbackDefinition)
const [workflowEditorKey, setWorkflowEditorKey] = useState(0)
const [aiConfigs, setAIConfigs] = useState<AIConfig[]>([])
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([])
@@ -181,6 +181,11 @@ export function AIAgentConfigWorkbench({
setCurrentAgentId(agentId ?? null)
}, [agentId])
const replaceWorkflowDefinition = useCallback((nextDefinition: AIWorkflowDefinition) => {
setDefinition(nextDefinition)
setWorkflowEditorKey((current) => current + 1)
}, [])
const loadData = useCallback(async () => {
setLoading(true)
try {
@@ -227,7 +232,7 @@ export function AIAgentConfigWorkbench({
setSelectedTeamIds([])
setSelectedSkillIds([])
setDirectTools([])
setDefinition(defaultDefinition ?? fallbackDefinition)
replaceWorkflowDefinition(defaultDefinition ?? fallbackDefinition)
setValidation(null)
return
}
@@ -259,14 +264,14 @@ export function AIAgentConfigWorkbench({
setSelectedTeamIds((agentDetail.teams ?? []).map((team) => team.id))
setSelectedSkillIds(agentDetail.skillIds ?? [])
setDirectTools(agentDetail.directTools ?? [])
setDefinition(workflowDetail.draftDefinition ?? defaultDefinition ?? fallbackDefinition)
replaceWorkflowDefinition(workflowDetail.draftDefinition ?? defaultDefinition ?? fallbackDefinition)
setValidation(null)
} catch (error) {
toast.error(error instanceof Error ? error.message : "Failed to load Agent config")
} finally {
setLoading(false)
}
}, [currentAgentId])
}, [currentAgentId, replaceWorkflowDefinition])
useEffect(() => {
void loadData()
@@ -463,6 +468,21 @@ export function AIAgentConfigWorkbench({
}
}
async function restoreDefaultWorkflow() {
if (savingWorkflow || loading) return
setSavingWorkflow(true)
try {
const defaultDefinition = await fetchAIWorkflowDefaultDefinition()
replaceWorkflowDefinition(defaultDefinition ?? fallbackDefinition)
setValidation(null)
toast.success("已恢复默认流程,保存草稿或发布后生效")
} catch (error) {
toast.error(error instanceof Error ? error.message : "恢复默认流程失败")
} finally {
setSavingWorkflow(false)
}
}
async function publishWorkflow() {
if (!currentAgentId) return
setSavingWorkflow(true)
@@ -539,39 +559,7 @@ export function AIAgentConfigWorkbench({
</div>
<div className="flex items-center gap-2">
{activeSection === "workflow" ? (
<>
{validation ? (
<Badge variant={validation.valid ? "default" : "destructive"}>
{validation.valid ? "校验通过" : `${validation.errors.length} 个问题`}
</Badge>
) : null}
<Button
type="button"
variant="outline"
disabled={savingWorkflow || loading || !currentAgentId}
onClick={validateWorkflowDraft}
>
<CheckCircle2Icon className="size-4" />
</Button>
<Button
type="button"
variant="outline"
disabled={savingWorkflow || loading || !currentAgentId}
onClick={saveWorkflowDraft}
>
<SaveIcon className="size-4" />
稿
</Button>
<Button
type="button"
disabled={savingWorkflow || loading || !currentAgentId}
onClick={publishWorkflow}
>
<SendIcon className="size-4" />
</Button>
</>
null
) : (
<>
<Button
@@ -824,9 +812,18 @@ export function AIAgentConfigWorkbench({
{activeSection === "workflow" ? (
<WorkflowEditor
key={workflowEditorKey}
definition={definition}
nodeSpecs={nodeSpecs}
onDefinitionChange={setDefinition}
onRestoreDefault={restoreDefaultWorkflow}
restoreDefaultDisabled={savingWorkflow || loading}
onValidate={validateWorkflowDraft}
validateDisabled={savingWorkflow || loading || !currentAgentId}
onSaveDraft={saveWorkflowDraft}
saveDraftDisabled={savingWorkflow || loading || !currentAgentId}
onPublish={publishWorkflow}
publishDisabled={savingWorkflow || loading || !currentAgentId}
/>
) : null}
@@ -36,6 +36,9 @@ import {
PanelLeftOpenIcon,
PlusIcon,
Redo2Icon,
RotateCcwIcon,
SaveIcon,
SendIcon,
Undo2Icon,
} from "lucide-react"
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
@@ -192,10 +195,26 @@ export function WorkflowEditor({
definition,
nodeSpecs,
onDefinitionChange,
onRestoreDefault,
restoreDefaultDisabled = false,
onValidate,
validateDisabled = false,
onSaveDraft,
saveDraftDisabled = false,
onPublish,
publishDisabled = false,
}: {
definition: AIWorkflowDefinition
nodeSpecs: AIWorkflowNodeSpec[]
onDefinitionChange: (definition: AIWorkflowDefinition) => void
onRestoreDefault?: () => void
restoreDefaultDisabled?: boolean
onValidate?: () => void
validateDisabled?: boolean
onSaveDraft?: () => void
saveDraftDisabled?: boolean
onPublish?: () => void
publishDisabled?: boolean
}) {
const [nodes, setNodes, onNodesChange] = useNodesState<WorkflowFlowNode>(
toFlowNodes(definition)
@@ -943,11 +962,21 @@ export function WorkflowEditor({
</ReactFlow>
<div className="absolute left-3 top-3 z-20 flex items-center gap-2">
<WorkflowValidationBadge errors={validation.errors} valid={validation.valid} />
<WorkflowCanvasActions
onValidate={onValidate}
validateDisabled={validateDisabled}
onSaveDraft={onSaveDraft}
saveDraftDisabled={saveDraftDisabled}
onPublish={onPublish}
publishDisabled={publishDisabled}
/>
<WorkflowHistoryControls
canUndo={historyAvailability.canUndo}
canRedo={historyAvailability.canRedo}
onUndo={undoWorkflowEdit}
onRedo={redoWorkflowEdit}
onRestoreDefault={onRestoreDefault}
restoreDefaultDisabled={restoreDefaultDisabled}
/>
</div>
{propertyPanelNode || propertyPanelEdge ? (
@@ -1609,16 +1638,84 @@ function WorkflowValidationBadge({
)
}
function WorkflowCanvasActions({
onValidate,
validateDisabled,
onSaveDraft,
saveDraftDisabled,
onPublish,
publishDisabled,
}: {
onValidate?: () => void
validateDisabled?: boolean
onSaveDraft?: () => void
saveDraftDisabled?: boolean
onPublish?: () => void
publishDisabled?: boolean
}) {
if (!onValidate && !onSaveDraft && !onPublish) {
return null
}
return (
<div className="flex overflow-hidden rounded-md border bg-background/95 shadow-sm">
{onValidate ? (
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 rounded-none px-2 text-xs text-muted-foreground hover:text-foreground"
onClick={onValidate}
disabled={validateDisabled}
>
<CheckCircle2Icon className="size-3.5" />
</Button>
) : null}
{onSaveDraft ? (
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 rounded-none border-l px-2 text-xs text-muted-foreground hover:text-foreground"
onClick={onSaveDraft}
disabled={saveDraftDisabled}
>
<SaveIcon className="size-3.5" />
稿
</Button>
) : null}
{onPublish ? (
<Button
type="button"
variant="ghost"
size="sm"
className="h-7 rounded-none border-l px-2 text-xs font-medium text-foreground hover:text-foreground"
onClick={onPublish}
disabled={publishDisabled}
>
<SendIcon className="size-3.5" />
</Button>
) : null}
</div>
)
}
function WorkflowHistoryControls({
canUndo,
canRedo,
onUndo,
onRedo,
onRestoreDefault,
restoreDefaultDisabled,
}: {
canUndo: boolean
canRedo: boolean
onUndo: () => void
onRedo: () => void
onRestoreDefault?: () => void
restoreDefaultDisabled?: boolean
}) {
return (
<div className="flex overflow-hidden rounded-md border bg-background/95 shadow-sm">
@@ -1646,6 +1743,20 @@ function WorkflowHistoryControls({
>
<Redo2Icon className="size-3.5" />
</Button>
{onRestoreDefault ? (
<Button
type="button"
variant="ghost"
size="icon"
className="size-7 rounded-none border-l text-muted-foreground hover:text-foreground"
onClick={onRestoreDefault}
disabled={restoreDefaultDisabled}
aria-label="恢复默认"
title="恢复默认"
>
<RotateCcwIcon className="size-3.5" />
</Button>
) : null}
</div>
)
}