feat: add workflow restoration functionality and enhance workflow editor actions
This commit is contained in:
@@ -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>
|
||||
)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user