feat: refactor workflow definition handling and integrate undo/redo functionality in WorkflowEditor

This commit is contained in:
mlogclub
2026-06-28 22:44:54 +08:00
parent e7e0bbf170
commit b83d33186f
3 changed files with 28 additions and 131 deletions
@@ -70,7 +70,6 @@ import {
IMConversationServiceMode, IMConversationServiceMode,
Status, Status,
} from "@/lib/generated/enums" } from "@/lib/generated/enums"
import { useWorkflowDefinitionHistory } from "../../ai-workflows/_components/use-workflow-definition-history"
import { WorkflowEditor } from "../../ai-workflows/_components/workflow-editor" import { WorkflowEditor } from "../../ai-workflows/_components/workflow-editor"
type DirectToolItem = CreateAIAgentPayload["directTools"][number] type DirectToolItem = CreateAIAgentPayload["directTools"][number]
@@ -154,16 +153,8 @@ export function AIAgentConfigWorkbench({
const [selectedSkillIds, setSelectedSkillIds] = useState<number[]>([]) const [selectedSkillIds, setSelectedSkillIds] = useState<number[]>([])
const [directTools, setDirectTools] = useState<DirectToolItem[]>([]) const [directTools, setDirectTools] = useState<DirectToolItem[]>([])
const { const [definition, setDefinition] = useState<AIWorkflowDefinition>(fallbackDefinition)
definition, const [workflowRevision, setWorkflowRevision] = useState(0)
revision: workflowRevision,
canUndo: canUndoWorkflow,
canRedo: canRedoWorkflow,
replace: replaceWorkflowHistory,
update: updateWorkflowDefinition,
undo: undoWorkflowDefinition,
redo: redoWorkflowDefinition,
} = useWorkflowDefinitionHistory(fallbackDefinition)
const [aiConfigs, setAIConfigs] = useState<AIConfig[]>([]) const [aiConfigs, setAIConfigs] = useState<AIConfig[]>([])
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([]) const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([])
@@ -181,8 +172,9 @@ export function AIAgentConfigWorkbench({
}, [agentId]) }, [agentId])
const replaceWorkflowDefinition = useCallback((nextDefinition: AIWorkflowDefinition) => { const replaceWorkflowDefinition = useCallback((nextDefinition: AIWorkflowDefinition) => {
replaceWorkflowHistory(nextDefinition) setDefinition(nextDefinition)
}, [replaceWorkflowHistory]) setWorkflowRevision((current) => current + 1)
}, [])
const loadData = useCallback(async () => { const loadData = useCallback(async () => {
setLoading(true) setLoading(true)
@@ -823,11 +815,8 @@ export function AIAgentConfigWorkbench({
key={workflowRevision} key={workflowRevision}
definition={definition} definition={definition}
nodeSpecs={nodeSpecs} nodeSpecs={nodeSpecs}
onDefinitionChange={updateWorkflowDefinition} onDefinitionChange={setDefinition}
onUndo={undoWorkflowDefinition} historyDisabled={savingWorkflow || loading}
undoDisabled={!canUndoWorkflow || savingWorkflow || loading}
onRedo={redoWorkflowDefinition}
redoDisabled={!canRedoWorkflow || savingWorkflow || loading}
onRestoreDefault={restoreDefaultWorkflow} onRestoreDefault={restoreDefaultWorkflow}
restoreDefaultDisabled={savingWorkflow || loading} restoreDefaultDisabled={savingWorkflow || loading}
onValidate={validateWorkflowDraft} onValidate={validateWorkflowDraft}
@@ -1,89 +0,0 @@
"use client"
import { useCallback, useState } from "react"
import type { AIWorkflowDefinition } from "@/lib/api/admin"
type WorkflowDefinitionHistoryState = {
present: AIWorkflowDefinition
past: AIWorkflowDefinition[]
future: AIWorkflowDefinition[]
revision: number
}
export function useWorkflowDefinitionHistory(initialDefinition: AIWorkflowDefinition) {
const [state, setState] = useState<WorkflowDefinitionHistoryState>({
present: initialDefinition,
past: [],
future: [],
revision: 0,
})
const replace = useCallback((definition: AIWorkflowDefinition) => {
setState((current) => ({
present: definition,
past: [],
future: [],
revision: current.revision + 1,
}))
}, [])
const update = useCallback((definition: AIWorkflowDefinition) => {
setState((current) => {
if (sameWorkflowDefinition(current.present, definition)) {
return current
}
return {
present: definition,
past: [...current.past.slice(-49), current.present],
future: [],
revision: current.revision,
}
})
}, [])
const undo = useCallback(() => {
setState((current) => {
const previous = current.past[current.past.length - 1]
if (!previous) {
return current
}
return {
present: previous,
past: current.past.slice(0, -1),
future: [current.present, ...current.future.slice(0, 49)],
revision: current.revision + 1,
}
})
}, [])
const redo = useCallback(() => {
setState((current) => {
const next = current.future[0]
if (!next) {
return current
}
return {
present: next,
past: [...current.past.slice(-49), current.present],
future: current.future.slice(1),
revision: current.revision + 1,
}
})
}, [])
return {
definition: state.present,
revision: state.revision,
canUndo: state.past.length > 0,
canRedo: state.future.length > 0,
replace,
update,
undo,
redo,
}
}
function sameWorkflowDefinition(left: AIWorkflowDefinition, right: AIWorkflowDefinition) {
return JSON.stringify(left) === JSON.stringify(right)
}
@@ -13,6 +13,7 @@ import {
type WorkflowNodeJSON, type WorkflowNodeJSON,
type WorkflowPortEntity, type WorkflowPortEntity,
useClientContext, useClientContext,
useUndoRedo,
usePlaygroundTools, usePlaygroundTools,
useService, useService,
} from "@flowgram.ai/free-layout-editor" } from "@flowgram.ai/free-layout-editor"
@@ -51,10 +52,7 @@ export function WorkflowEditor({
onDefinitionChange, onDefinitionChange,
onRestoreDefault, onRestoreDefault,
restoreDefaultDisabled = false, restoreDefaultDisabled = false,
onUndo, historyDisabled = false,
undoDisabled = false,
onRedo,
redoDisabled = false,
onValidate, onValidate,
validateDisabled = false, validateDisabled = false,
onSaveDraft, onSaveDraft,
@@ -68,10 +66,7 @@ export function WorkflowEditor({
onDefinitionChange: (definition: AIWorkflowDefinition) => void onDefinitionChange: (definition: AIWorkflowDefinition) => void
onRestoreDefault?: () => void onRestoreDefault?: () => void
restoreDefaultDisabled?: boolean restoreDefaultDisabled?: boolean
onUndo?: () => void historyDisabled?: boolean
undoDisabled?: boolean
onRedo?: () => void
redoDisabled?: boolean
onValidate?: () => void onValidate?: () => void
validateDisabled?: boolean validateDisabled?: boolean
onSaveDraft?: () => void onSaveDraft?: () => void
@@ -136,10 +131,7 @@ export function WorkflowEditor({
} }
}} }}
onSelectBranch={handleSelectBranch} onSelectBranch={handleSelectBranch}
onUndo={onUndo} historyDisabled={historyDisabled}
undoDisabled={undoDisabled}
onRedo={onRedo}
redoDisabled={redoDisabled}
onRestoreDefault={onRestoreDefault} onRestoreDefault={onRestoreDefault}
restoreDefaultDisabled={restoreDefaultDisabled} restoreDefaultDisabled={restoreDefaultDisabled}
onValidate={onValidate} onValidate={onValidate}
@@ -164,10 +156,7 @@ function WorkflowEditorInner({
onDefinitionChange, onDefinitionChange,
onSelectNode, onSelectNode,
onSelectBranch, onSelectBranch,
onUndo, historyDisabled,
undoDisabled,
onRedo,
redoDisabled,
onRestoreDefault, onRestoreDefault,
restoreDefaultDisabled, restoreDefaultDisabled,
onValidate, onValidate,
@@ -186,10 +175,7 @@ function WorkflowEditorInner({
onDefinitionChange: (definition: AIWorkflowDefinition) => void onDefinitionChange: (definition: AIWorkflowDefinition) => void
onSelectNode: (nodeId: string) => void onSelectNode: (nodeId: string) => void
onSelectBranch: (branch: SelectedWorkflowBranch | null) => void onSelectBranch: (branch: SelectedWorkflowBranch | null) => void
onUndo?: () => void historyDisabled?: boolean
undoDisabled?: boolean
onRedo?: () => void
redoDisabled?: boolean
onRestoreDefault?: () => void onRestoreDefault?: () => void
restoreDefaultDisabled?: boolean restoreDefaultDisabled?: boolean
onValidate?: () => void onValidate?: () => void
@@ -201,6 +187,7 @@ function WorkflowEditorInner({
}) { }) {
const context = useClientContext() const context = useClientContext()
const playgroundTools = usePlaygroundTools() const playgroundTools = usePlaygroundTools()
const undoRedo = useUndoRedo()
const workflowDocument = useService(WorkflowDocument) const workflowDocument = useService(WorkflowDocument)
const linesManager = useService(WorkflowLinesManager) const linesManager = useService(WorkflowLinesManager)
const selectService = useService(WorkflowSelectService) const selectService = useService(WorkflowSelectService)
@@ -229,6 +216,16 @@ function WorkflowEditorInner({
onDefinitionChange(context.document.toJSON() as AIWorkflowDefinition) onDefinitionChange(context.document.toJSON() as AIWorkflowDefinition)
} }
const undo = async () => {
await undoRedo.undo()
emitCurrentDefinition()
}
const redo = async () => {
await undoRedo.redo()
emitCurrentDefinition()
}
const openNodeMenuFromPort = useCallback((request: WorkflowPortAddRequest) => { const openNodeMenuFromPort = useCallback((request: WorkflowPortAddRequest) => {
const rootRect = editorRootRef.current?.getBoundingClientRect() const rootRect = editorRootRef.current?.getBoundingClientRect()
setNodeMenu({ setNodeMenu({
@@ -336,10 +333,10 @@ function WorkflowEditorInner({
> >
<WorkflowEditorToolbar <WorkflowEditorToolbar
toolbarExtra={toolbarExtra} toolbarExtra={toolbarExtra}
onUndo={onUndo} onUndo={() => void undo()}
undoDisabled={undoDisabled} undoDisabled={historyDisabled || !undoRedo.canUndo}
onRedo={onRedo} onRedo={() => void redo()}
redoDisabled={redoDisabled} redoDisabled={historyDisabled || !undoRedo.canRedo}
onRestoreDefault={onRestoreDefault} onRestoreDefault={onRestoreDefault}
restoreDefaultDisabled={restoreDefaultDisabled} restoreDefaultDisabled={restoreDefaultDisabled}
onValidate={onValidate} onValidate={onValidate}