feat: refactor workflow branch selection logic and introduce context provider

This commit is contained in:
mlogclub
2026-06-28 10:35:19 +08:00
parent 64c10ba6dd
commit af9c28033a
5 changed files with 113 additions and 67 deletions
@@ -20,25 +20,16 @@ import {
syncConditionBranchTargetsFromEdges,
} from "./workflow-utils"
export type SelectedWorkflowBranch = {
nodeId: string
branchId: string
}
export function useFlowgramEditorProps({
definition,
nodeSpecs,
selectedBranch,
readonly = false,
onDefinitionChange,
onSelectBranch,
}: {
definition: AIWorkflowDefinition
nodeSpecs: AIWorkflowNodeSpec[]
selectedBranch?: SelectedWorkflowBranch | null
readonly?: boolean
onDefinitionChange?: (definition: AIWorkflowDefinition) => void
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
}) {
return useMemo<FreeLayoutProps>(
() => {
@@ -59,7 +50,7 @@ export function useFlowgramEditorProps({
materials: {
renderDefaultNode: FlowgramNodeRenderer,
},
nodeRegistries: buildFlowgramNodeRegistries(nodeSpecs, selectedBranch, onSelectBranch),
nodeRegistries: buildFlowgramNodeRegistries(nodeSpecs),
nodeEngine: {
enable: true,
},
@@ -123,7 +114,7 @@ export function useFlowgramEditorProps({
],
}
},
[definition, nodeSpecs, onDefinitionChange, onSelectBranch, readonly, selectedBranch]
[definition, nodeSpecs, onDefinitionChange, readonly]
)
}
@@ -8,13 +8,9 @@ import {
normalizeNodeConfig,
type WorkflowConditionBranch,
} from "./workflow-utils"
import type { SelectedWorkflowBranch } from "./flowgram-editor-provider"
import { useWorkflowBranchSelection } from "./workflow-branch-selection"
export function buildFlowgramNodeRegistries(
nodeSpecs: AIWorkflowNodeSpec[],
selectedBranch?: SelectedWorkflowBranch | null,
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
): WorkflowNodeRegistry[] {
export function buildFlowgramNodeRegistries(nodeSpecs: AIWorkflowNodeSpec[]): WorkflowNodeRegistry[] {
const seen = new Set<string>()
const specs = nodeSpecs.length > 0
? nodeSpecs
@@ -45,8 +41,6 @@ export function buildFlowgramNodeRegistries(
<FlowgramNodeForm
nodeType={spec.type}
fallbackTitle={spec.title || spec.type}
selectedBranch={selectedBranch}
onSelectBranch={onSelectBranch}
/>
),
},
@@ -56,13 +50,9 @@ export function buildFlowgramNodeRegistries(
function FlowgramNodeForm({
nodeType,
fallbackTitle,
selectedBranch,
onSelectBranch,
}: {
nodeType: string
fallbackTitle: string
selectedBranch?: SelectedWorkflowBranch | null
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
}) {
const { node } = useNodeRender()
const nodeId = String(node.id ?? "")
@@ -72,8 +62,6 @@ function FlowgramNodeForm({
<ConditionNodeForm
fallbackTitle={fallbackTitle}
nodeId={nodeId}
selectedBranch={selectedBranch}
onSelectBranch={onSelectBranch}
/>
)
}
@@ -108,14 +96,12 @@ function defaultPortsForNodeType(type: string) {
function ConditionNodeForm({
fallbackTitle,
nodeId,
selectedBranch,
onSelectBranch,
}: {
fallbackTitle: string
nodeId: string
selectedBranch?: SelectedWorkflowBranch | null
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
}) {
const { selectedBranch, onSelectBranch } = useWorkflowBranchSelection()
return (
<div className="flex w-full flex-col">
<Field<string> name="title">
@@ -154,10 +140,16 @@ function ConditionNodeForm({
? "bg-[#dfe4ff] text-[#3327b9] ring-1 ring-inset ring-[#4e40e5]/45 before:w-1"
: "bg-[#eef1f7] hover:bg-[#e6eaff]",
].join(" ")}
onClick={(event) => {
onPointerDownCapture={(event) => {
event.stopPropagation()
onSelectBranch?.({ nodeId, branchId: branch.id })
}}
onMouseDownCapture={(event) => {
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
}}
>
<span className="min-w-0 flex-1 truncate font-medium text-foreground/90">
{branch.name || (branch.default ? "默认分支" : branch.id)}
@@ -170,6 +162,12 @@ function ConditionNodeForm({
type="button"
className="flex size-5 shrink-0 items-center justify-center rounded-sm text-muted-foreground transition-colors hover:bg-destructive/10 hover:text-destructive"
aria-label={`删除条件 ${branch.name || branch.id}`}
onPointerDownCapture={(event) => {
event.stopPropagation()
}}
onMouseDownCapture={(event) => {
event.stopPropagation()
}}
onClick={(event) => {
event.stopPropagation()
deleteBranch(branch.id)
@@ -0,0 +1,41 @@
"use client"
import { createContext, useContext, useMemo, type ReactNode } from "react"
export type SelectedWorkflowBranch = {
nodeId: string
branchId: string
}
type WorkflowBranchSelectionContextValue = {
selectedBranch: SelectedWorkflowBranch | null
onSelectBranch: (branch: SelectedWorkflowBranch | null) => void
}
const WorkflowBranchSelectionContext = createContext<WorkflowBranchSelectionContextValue>({
selectedBranch: null,
onSelectBranch: () => {},
})
export function WorkflowBranchSelectionProvider({
selectedBranch,
onSelectBranch,
children,
}: WorkflowBranchSelectionContextValue & {
children: ReactNode
}) {
const value = useMemo(
() => ({ selectedBranch, onSelectBranch }),
[onSelectBranch, selectedBranch]
)
return (
<WorkflowBranchSelectionContext.Provider value={value}>
{children}
</WorkflowBranchSelectionContext.Provider>
)
}
export function useWorkflowBranchSelection() {
return useContext(WorkflowBranchSelectionContext)
}
@@ -5,7 +5,7 @@ import { XIcon } from "lucide-react"
import { Button } from "@/components/ui/button"
import type { AIWorkflowDefinition, AIWorkflowNodeSpec } from "@/lib/api/admin"
import type { SelectedWorkflowBranch } from "./flowgram-editor-provider"
import type { SelectedWorkflowBranch } from "./workflow-branch-selection"
import { ConditionBranchConfigPanel, NodeConfigPanel } from "./node-config-panel"
import {
getAvailableVariables,
@@ -1,6 +1,6 @@
"use client"
import { useEffect, useMemo, useState, type ReactNode } from "react"
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"
import {
EditorRenderer,
@@ -16,7 +16,11 @@ import {
import type { AIWorkflowDefinition, AIWorkflowNodeSpec } from "@/lib/api/admin"
import { useFlowgramEditorProps, type SelectedWorkflowBranch } from "./flowgram-editor-provider"
import { useFlowgramEditorProps } from "./flowgram-editor-provider"
import {
WorkflowBranchSelectionProvider,
type SelectedWorkflowBranch,
} from "./workflow-branch-selection"
import { WorkflowConfigPanel } from "./workflow-config-sidebar"
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
import { WorkflowNodePalette } from "./workflow-node-palette"
@@ -66,6 +70,7 @@ export function WorkflowEditor({
const [localDefinition, setLocalDefinition] = useState(definition)
const [selectedNodeId, setSelectedNodeId] = useState("")
const [selectedBranch, setSelectedBranch] = useState<SelectedWorkflowBranch | null>(null)
const branchSelectAtRef = useRef(0)
const validation = useMemo(
() => validateWorkflowDefinition(localDefinition, nodeSpecs),
@@ -75,53 +80,64 @@ export function WorkflowEditor({
const editorProps = useFlowgramEditorProps({
definition: localDefinition,
nodeSpecs,
selectedBranch,
onDefinitionChange: (next) => {
setLocalDefinition(next)
onDefinitionChange(next)
},
onSelectBranch: (branch) => {
})
const handleSelectBranch = useCallback(
(branch: SelectedWorkflowBranch | null) => {
if (!branch) {
setSelectedBranch(null)
return
}
branchSelectAtRef.current = Date.now()
setSelectedNodeId(branch.nodeId)
setSelectedBranch(branch)
},
})
[]
)
return (
<FreeLayoutEditorProvider {...editorProps}>
<WorkflowEditorInner
definition={localDefinition}
nodeSpecs={nodeSpecs}
selectedNodeId={selectedNodeId}
selectedBranch={selectedBranch}
validation={validation}
toolbarExtra={toolbarExtra}
onDefinitionChange={(next) => {
setLocalDefinition(next)
onDefinitionChange(next)
}}
onSelectNode={(nodeId) => {
setSelectedNodeId(nodeId)
setSelectedBranch(null)
}}
onSelectBranch={setSelectedBranch}
onUndo={onUndo}
undoDisabled={undoDisabled}
onRedo={onRedo}
redoDisabled={redoDisabled}
onRestoreDefault={onRestoreDefault}
restoreDefaultDisabled={restoreDefaultDisabled}
onValidate={onValidate}
validateDisabled={validateDisabled}
onSaveDraft={onSaveDraft}
saveDraftDisabled={saveDraftDisabled}
onPublish={onPublish}
publishDisabled={publishDisabled}
/>
</FreeLayoutEditorProvider>
<WorkflowBranchSelectionProvider
selectedBranch={selectedBranch}
onSelectBranch={handleSelectBranch}
>
<FreeLayoutEditorProvider {...editorProps}>
<WorkflowEditorInner
definition={localDefinition}
nodeSpecs={nodeSpecs}
selectedNodeId={selectedNodeId}
selectedBranch={selectedBranch}
validation={validation}
toolbarExtra={toolbarExtra}
onDefinitionChange={(next) => {
setLocalDefinition(next)
onDefinitionChange(next)
}}
onSelectNode={(nodeId) => {
setSelectedNodeId(nodeId)
if (!nodeId || Date.now() - branchSelectAtRef.current > 160) {
setSelectedBranch(null)
}
}}
onSelectBranch={handleSelectBranch}
onUndo={onUndo}
undoDisabled={undoDisabled}
onRedo={onRedo}
redoDisabled={redoDisabled}
onRestoreDefault={onRestoreDefault}
restoreDefaultDisabled={restoreDefaultDisabled}
onValidate={onValidate}
validateDisabled={validateDisabled}
onSaveDraft={onSaveDraft}
saveDraftDisabled={saveDraftDisabled}
onPublish={onPublish}
publishDisabled={publishDisabled}
/>
</FreeLayoutEditorProvider>
</WorkflowBranchSelectionProvider>
)
}