feat: add workflow restoration functionality and enhance workflow editor actions
This commit is contained in:
@@ -6,7 +6,6 @@ import {
|
|||||||
ArrowUpIcon,
|
ArrowUpIcon,
|
||||||
BotMessageSquareIcon,
|
BotMessageSquareIcon,
|
||||||
BrainCircuitIcon,
|
BrainCircuitIcon,
|
||||||
CheckCircle2Icon,
|
|
||||||
DatabaseIcon,
|
DatabaseIcon,
|
||||||
GitBranchIcon,
|
GitBranchIcon,
|
||||||
HistoryIcon,
|
HistoryIcon,
|
||||||
@@ -165,6 +164,7 @@ export function AIAgentConfigWorkbench({
|
|||||||
const [directTools, setDirectTools] = useState<DirectToolItem[]>([])
|
const [directTools, setDirectTools] = useState<DirectToolItem[]>([])
|
||||||
|
|
||||||
const [definition, setDefinition] = useState<AIWorkflowDefinition>(fallbackDefinition)
|
const [definition, setDefinition] = useState<AIWorkflowDefinition>(fallbackDefinition)
|
||||||
|
const [workflowEditorKey, setWorkflowEditorKey] = useState(0)
|
||||||
|
|
||||||
const [aiConfigs, setAIConfigs] = useState<AIConfig[]>([])
|
const [aiConfigs, setAIConfigs] = useState<AIConfig[]>([])
|
||||||
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([])
|
const [knowledgeBases, setKnowledgeBases] = useState<KnowledgeBase[]>([])
|
||||||
@@ -181,6 +181,11 @@ export function AIAgentConfigWorkbench({
|
|||||||
setCurrentAgentId(agentId ?? null)
|
setCurrentAgentId(agentId ?? null)
|
||||||
}, [agentId])
|
}, [agentId])
|
||||||
|
|
||||||
|
const replaceWorkflowDefinition = useCallback((nextDefinition: AIWorkflowDefinition) => {
|
||||||
|
setDefinition(nextDefinition)
|
||||||
|
setWorkflowEditorKey((current) => current + 1)
|
||||||
|
}, [])
|
||||||
|
|
||||||
const loadData = useCallback(async () => {
|
const loadData = useCallback(async () => {
|
||||||
setLoading(true)
|
setLoading(true)
|
||||||
try {
|
try {
|
||||||
@@ -227,7 +232,7 @@ export function AIAgentConfigWorkbench({
|
|||||||
setSelectedTeamIds([])
|
setSelectedTeamIds([])
|
||||||
setSelectedSkillIds([])
|
setSelectedSkillIds([])
|
||||||
setDirectTools([])
|
setDirectTools([])
|
||||||
setDefinition(defaultDefinition ?? fallbackDefinition)
|
replaceWorkflowDefinition(defaultDefinition ?? fallbackDefinition)
|
||||||
setValidation(null)
|
setValidation(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
@@ -259,14 +264,14 @@ export function AIAgentConfigWorkbench({
|
|||||||
setSelectedTeamIds((agentDetail.teams ?? []).map((team) => team.id))
|
setSelectedTeamIds((agentDetail.teams ?? []).map((team) => team.id))
|
||||||
setSelectedSkillIds(agentDetail.skillIds ?? [])
|
setSelectedSkillIds(agentDetail.skillIds ?? [])
|
||||||
setDirectTools(agentDetail.directTools ?? [])
|
setDirectTools(agentDetail.directTools ?? [])
|
||||||
setDefinition(workflowDetail.draftDefinition ?? defaultDefinition ?? fallbackDefinition)
|
replaceWorkflowDefinition(workflowDetail.draftDefinition ?? defaultDefinition ?? fallbackDefinition)
|
||||||
setValidation(null)
|
setValidation(null)
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
toast.error(error instanceof Error ? error.message : "Failed to load Agent config")
|
toast.error(error instanceof Error ? error.message : "Failed to load Agent config")
|
||||||
} finally {
|
} finally {
|
||||||
setLoading(false)
|
setLoading(false)
|
||||||
}
|
}
|
||||||
}, [currentAgentId])
|
}, [currentAgentId, replaceWorkflowDefinition])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
void loadData()
|
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() {
|
async function publishWorkflow() {
|
||||||
if (!currentAgentId) return
|
if (!currentAgentId) return
|
||||||
setSavingWorkflow(true)
|
setSavingWorkflow(true)
|
||||||
@@ -539,39 +559,7 @@ export function AIAgentConfigWorkbench({
|
|||||||
</div>
|
</div>
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
{activeSection === "workflow" ? (
|
{activeSection === "workflow" ? (
|
||||||
<>
|
null
|
||||||
{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>
|
|
||||||
</>
|
|
||||||
) : (
|
) : (
|
||||||
<>
|
<>
|
||||||
<Button
|
<Button
|
||||||
@@ -824,9 +812,18 @@ export function AIAgentConfigWorkbench({
|
|||||||
|
|
||||||
{activeSection === "workflow" ? (
|
{activeSection === "workflow" ? (
|
||||||
<WorkflowEditor
|
<WorkflowEditor
|
||||||
|
key={workflowEditorKey}
|
||||||
definition={definition}
|
definition={definition}
|
||||||
nodeSpecs={nodeSpecs}
|
nodeSpecs={nodeSpecs}
|
||||||
onDefinitionChange={setDefinition}
|
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}
|
) : null}
|
||||||
|
|
||||||
|
|||||||
@@ -36,6 +36,9 @@ import {
|
|||||||
PanelLeftOpenIcon,
|
PanelLeftOpenIcon,
|
||||||
PlusIcon,
|
PlusIcon,
|
||||||
Redo2Icon,
|
Redo2Icon,
|
||||||
|
RotateCcwIcon,
|
||||||
|
SaveIcon,
|
||||||
|
SendIcon,
|
||||||
Undo2Icon,
|
Undo2Icon,
|
||||||
} from "lucide-react"
|
} from "lucide-react"
|
||||||
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react"
|
||||||
@@ -192,10 +195,26 @@ export function WorkflowEditor({
|
|||||||
definition,
|
definition,
|
||||||
nodeSpecs,
|
nodeSpecs,
|
||||||
onDefinitionChange,
|
onDefinitionChange,
|
||||||
|
onRestoreDefault,
|
||||||
|
restoreDefaultDisabled = false,
|
||||||
|
onValidate,
|
||||||
|
validateDisabled = false,
|
||||||
|
onSaveDraft,
|
||||||
|
saveDraftDisabled = false,
|
||||||
|
onPublish,
|
||||||
|
publishDisabled = false,
|
||||||
}: {
|
}: {
|
||||||
definition: AIWorkflowDefinition
|
definition: AIWorkflowDefinition
|
||||||
nodeSpecs: AIWorkflowNodeSpec[]
|
nodeSpecs: AIWorkflowNodeSpec[]
|
||||||
onDefinitionChange: (definition: AIWorkflowDefinition) => void
|
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>(
|
const [nodes, setNodes, onNodesChange] = useNodesState<WorkflowFlowNode>(
|
||||||
toFlowNodes(definition)
|
toFlowNodes(definition)
|
||||||
@@ -943,11 +962,21 @@ export function WorkflowEditor({
|
|||||||
</ReactFlow>
|
</ReactFlow>
|
||||||
<div className="absolute left-3 top-3 z-20 flex items-center gap-2">
|
<div className="absolute left-3 top-3 z-20 flex items-center gap-2">
|
||||||
<WorkflowValidationBadge errors={validation.errors} valid={validation.valid} />
|
<WorkflowValidationBadge errors={validation.errors} valid={validation.valid} />
|
||||||
|
<WorkflowCanvasActions
|
||||||
|
onValidate={onValidate}
|
||||||
|
validateDisabled={validateDisabled}
|
||||||
|
onSaveDraft={onSaveDraft}
|
||||||
|
saveDraftDisabled={saveDraftDisabled}
|
||||||
|
onPublish={onPublish}
|
||||||
|
publishDisabled={publishDisabled}
|
||||||
|
/>
|
||||||
<WorkflowHistoryControls
|
<WorkflowHistoryControls
|
||||||
canUndo={historyAvailability.canUndo}
|
canUndo={historyAvailability.canUndo}
|
||||||
canRedo={historyAvailability.canRedo}
|
canRedo={historyAvailability.canRedo}
|
||||||
onUndo={undoWorkflowEdit}
|
onUndo={undoWorkflowEdit}
|
||||||
onRedo={redoWorkflowEdit}
|
onRedo={redoWorkflowEdit}
|
||||||
|
onRestoreDefault={onRestoreDefault}
|
||||||
|
restoreDefaultDisabled={restoreDefaultDisabled}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
{propertyPanelNode || propertyPanelEdge ? (
|
{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({
|
function WorkflowHistoryControls({
|
||||||
canUndo,
|
canUndo,
|
||||||
canRedo,
|
canRedo,
|
||||||
onUndo,
|
onUndo,
|
||||||
onRedo,
|
onRedo,
|
||||||
|
onRestoreDefault,
|
||||||
|
restoreDefaultDisabled,
|
||||||
}: {
|
}: {
|
||||||
canUndo: boolean
|
canUndo: boolean
|
||||||
canRedo: boolean
|
canRedo: boolean
|
||||||
onUndo: () => void
|
onUndo: () => void
|
||||||
onRedo: () => void
|
onRedo: () => void
|
||||||
|
onRestoreDefault?: () => void
|
||||||
|
restoreDefaultDisabled?: boolean
|
||||||
}) {
|
}) {
|
||||||
return (
|
return (
|
||||||
<div className="flex overflow-hidden rounded-md border bg-background/95 shadow-sm">
|
<div className="flex overflow-hidden rounded-md border bg-background/95 shadow-sm">
|
||||||
@@ -1646,6 +1743,20 @@ function WorkflowHistoryControls({
|
|||||||
>
|
>
|
||||||
<Redo2Icon className="size-3.5" />
|
<Redo2Icon className="size-3.5" />
|
||||||
</Button>
|
</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>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user