feat: refactor workflow branch selection logic and introduce context provider
This commit is contained in:
@@ -20,25 +20,16 @@ import {
|
|||||||
syncConditionBranchTargetsFromEdges,
|
syncConditionBranchTargetsFromEdges,
|
||||||
} from "./workflow-utils"
|
} from "./workflow-utils"
|
||||||
|
|
||||||
export type SelectedWorkflowBranch = {
|
|
||||||
nodeId: string
|
|
||||||
branchId: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useFlowgramEditorProps({
|
export function useFlowgramEditorProps({
|
||||||
definition,
|
definition,
|
||||||
nodeSpecs,
|
nodeSpecs,
|
||||||
selectedBranch,
|
|
||||||
readonly = false,
|
readonly = false,
|
||||||
onDefinitionChange,
|
onDefinitionChange,
|
||||||
onSelectBranch,
|
|
||||||
}: {
|
}: {
|
||||||
definition: AIWorkflowDefinition
|
definition: AIWorkflowDefinition
|
||||||
nodeSpecs: AIWorkflowNodeSpec[]
|
nodeSpecs: AIWorkflowNodeSpec[]
|
||||||
selectedBranch?: SelectedWorkflowBranch | null
|
|
||||||
readonly?: boolean
|
readonly?: boolean
|
||||||
onDefinitionChange?: (definition: AIWorkflowDefinition) => void
|
onDefinitionChange?: (definition: AIWorkflowDefinition) => void
|
||||||
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
|
|
||||||
}) {
|
}) {
|
||||||
return useMemo<FreeLayoutProps>(
|
return useMemo<FreeLayoutProps>(
|
||||||
() => {
|
() => {
|
||||||
@@ -59,7 +50,7 @@ export function useFlowgramEditorProps({
|
|||||||
materials: {
|
materials: {
|
||||||
renderDefaultNode: FlowgramNodeRenderer,
|
renderDefaultNode: FlowgramNodeRenderer,
|
||||||
},
|
},
|
||||||
nodeRegistries: buildFlowgramNodeRegistries(nodeSpecs, selectedBranch, onSelectBranch),
|
nodeRegistries: buildFlowgramNodeRegistries(nodeSpecs),
|
||||||
nodeEngine: {
|
nodeEngine: {
|
||||||
enable: true,
|
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,
|
normalizeNodeConfig,
|
||||||
type WorkflowConditionBranch,
|
type WorkflowConditionBranch,
|
||||||
} from "./workflow-utils"
|
} from "./workflow-utils"
|
||||||
import type { SelectedWorkflowBranch } from "./flowgram-editor-provider"
|
import { useWorkflowBranchSelection } from "./workflow-branch-selection"
|
||||||
|
|
||||||
export function buildFlowgramNodeRegistries(
|
export function buildFlowgramNodeRegistries(nodeSpecs: AIWorkflowNodeSpec[]): WorkflowNodeRegistry[] {
|
||||||
nodeSpecs: AIWorkflowNodeSpec[],
|
|
||||||
selectedBranch?: SelectedWorkflowBranch | null,
|
|
||||||
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
|
|
||||||
): WorkflowNodeRegistry[] {
|
|
||||||
const seen = new Set<string>()
|
const seen = new Set<string>()
|
||||||
const specs = nodeSpecs.length > 0
|
const specs = nodeSpecs.length > 0
|
||||||
? nodeSpecs
|
? nodeSpecs
|
||||||
@@ -45,8 +41,6 @@ export function buildFlowgramNodeRegistries(
|
|||||||
<FlowgramNodeForm
|
<FlowgramNodeForm
|
||||||
nodeType={spec.type}
|
nodeType={spec.type}
|
||||||
fallbackTitle={spec.title || spec.type}
|
fallbackTitle={spec.title || spec.type}
|
||||||
selectedBranch={selectedBranch}
|
|
||||||
onSelectBranch={onSelectBranch}
|
|
||||||
/>
|
/>
|
||||||
),
|
),
|
||||||
},
|
},
|
||||||
@@ -56,13 +50,9 @@ export function buildFlowgramNodeRegistries(
|
|||||||
function FlowgramNodeForm({
|
function FlowgramNodeForm({
|
||||||
nodeType,
|
nodeType,
|
||||||
fallbackTitle,
|
fallbackTitle,
|
||||||
selectedBranch,
|
|
||||||
onSelectBranch,
|
|
||||||
}: {
|
}: {
|
||||||
nodeType: string
|
nodeType: string
|
||||||
fallbackTitle: string
|
fallbackTitle: string
|
||||||
selectedBranch?: SelectedWorkflowBranch | null
|
|
||||||
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
|
|
||||||
}) {
|
}) {
|
||||||
const { node } = useNodeRender()
|
const { node } = useNodeRender()
|
||||||
const nodeId = String(node.id ?? "")
|
const nodeId = String(node.id ?? "")
|
||||||
@@ -72,8 +62,6 @@ function FlowgramNodeForm({
|
|||||||
<ConditionNodeForm
|
<ConditionNodeForm
|
||||||
fallbackTitle={fallbackTitle}
|
fallbackTitle={fallbackTitle}
|
||||||
nodeId={nodeId}
|
nodeId={nodeId}
|
||||||
selectedBranch={selectedBranch}
|
|
||||||
onSelectBranch={onSelectBranch}
|
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@@ -108,14 +96,12 @@ function defaultPortsForNodeType(type: string) {
|
|||||||
function ConditionNodeForm({
|
function ConditionNodeForm({
|
||||||
fallbackTitle,
|
fallbackTitle,
|
||||||
nodeId,
|
nodeId,
|
||||||
selectedBranch,
|
|
||||||
onSelectBranch,
|
|
||||||
}: {
|
}: {
|
||||||
fallbackTitle: string
|
fallbackTitle: string
|
||||||
nodeId: string
|
nodeId: string
|
||||||
selectedBranch?: SelectedWorkflowBranch | null
|
|
||||||
onSelectBranch?: (branch: SelectedWorkflowBranch | null) => void
|
|
||||||
}) {
|
}) {
|
||||||
|
const { selectedBranch, onSelectBranch } = useWorkflowBranchSelection()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex w-full flex-col">
|
<div className="flex w-full flex-col">
|
||||||
<Field<string> name="title">
|
<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-[#dfe4ff] text-[#3327b9] ring-1 ring-inset ring-[#4e40e5]/45 before:w-1"
|
||||||
: "bg-[#eef1f7] hover:bg-[#e6eaff]",
|
: "bg-[#eef1f7] hover:bg-[#e6eaff]",
|
||||||
].join(" ")}
|
].join(" ")}
|
||||||
onClick={(event) => {
|
onPointerDownCapture={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
onSelectBranch?.({ nodeId, branchId: branch.id })
|
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">
|
<span className="min-w-0 flex-1 truncate font-medium text-foreground/90">
|
||||||
{branch.name || (branch.default ? "默认分支" : branch.id)}
|
{branch.name || (branch.default ? "默认分支" : branch.id)}
|
||||||
@@ -170,6 +162,12 @@ function ConditionNodeForm({
|
|||||||
type="button"
|
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"
|
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}`}
|
aria-label={`删除条件 ${branch.name || branch.id}`}
|
||||||
|
onPointerDownCapture={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
}}
|
||||||
|
onMouseDownCapture={(event) => {
|
||||||
|
event.stopPropagation()
|
||||||
|
}}
|
||||||
onClick={(event) => {
|
onClick={(event) => {
|
||||||
event.stopPropagation()
|
event.stopPropagation()
|
||||||
deleteBranch(branch.id)
|
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 { Button } from "@/components/ui/button"
|
||||||
import type { AIWorkflowDefinition, AIWorkflowNodeSpec } from "@/lib/api/admin"
|
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 { ConditionBranchConfigPanel, NodeConfigPanel } from "./node-config-panel"
|
||||||
import {
|
import {
|
||||||
getAvailableVariables,
|
getAvailableVariables,
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
"use client"
|
"use client"
|
||||||
|
|
||||||
import { useEffect, useMemo, useState, type ReactNode } from "react"
|
import { useCallback, useEffect, useMemo, useRef, useState, type ReactNode } from "react"
|
||||||
|
|
||||||
import {
|
import {
|
||||||
EditorRenderer,
|
EditorRenderer,
|
||||||
@@ -16,7 +16,11 @@ import {
|
|||||||
|
|
||||||
import type { AIWorkflowDefinition, AIWorkflowNodeSpec } from "@/lib/api/admin"
|
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 { WorkflowConfigPanel } from "./workflow-config-sidebar"
|
||||||
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
|
import { WorkflowEditorToolbar } from "./workflow-editor-toolbar"
|
||||||
import { WorkflowNodePalette } from "./workflow-node-palette"
|
import { WorkflowNodePalette } from "./workflow-node-palette"
|
||||||
@@ -66,6 +70,7 @@ export function WorkflowEditor({
|
|||||||
const [localDefinition, setLocalDefinition] = useState(definition)
|
const [localDefinition, setLocalDefinition] = useState(definition)
|
||||||
const [selectedNodeId, setSelectedNodeId] = useState("")
|
const [selectedNodeId, setSelectedNodeId] = useState("")
|
||||||
const [selectedBranch, setSelectedBranch] = useState<SelectedWorkflowBranch | null>(null)
|
const [selectedBranch, setSelectedBranch] = useState<SelectedWorkflowBranch | null>(null)
|
||||||
|
const branchSelectAtRef = useRef(0)
|
||||||
|
|
||||||
const validation = useMemo(
|
const validation = useMemo(
|
||||||
() => validateWorkflowDefinition(localDefinition, nodeSpecs),
|
() => validateWorkflowDefinition(localDefinition, nodeSpecs),
|
||||||
@@ -75,22 +80,30 @@ export function WorkflowEditor({
|
|||||||
const editorProps = useFlowgramEditorProps({
|
const editorProps = useFlowgramEditorProps({
|
||||||
definition: localDefinition,
|
definition: localDefinition,
|
||||||
nodeSpecs,
|
nodeSpecs,
|
||||||
selectedBranch,
|
|
||||||
onDefinitionChange: (next) => {
|
onDefinitionChange: (next) => {
|
||||||
setLocalDefinition(next)
|
setLocalDefinition(next)
|
||||||
onDefinitionChange(next)
|
onDefinitionChange(next)
|
||||||
},
|
},
|
||||||
onSelectBranch: (branch) => {
|
})
|
||||||
|
|
||||||
|
const handleSelectBranch = useCallback(
|
||||||
|
(branch: SelectedWorkflowBranch | null) => {
|
||||||
if (!branch) {
|
if (!branch) {
|
||||||
setSelectedBranch(null)
|
setSelectedBranch(null)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
branchSelectAtRef.current = Date.now()
|
||||||
setSelectedNodeId(branch.nodeId)
|
setSelectedNodeId(branch.nodeId)
|
||||||
setSelectedBranch(branch)
|
setSelectedBranch(branch)
|
||||||
},
|
},
|
||||||
})
|
[]
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
<WorkflowBranchSelectionProvider
|
||||||
|
selectedBranch={selectedBranch}
|
||||||
|
onSelectBranch={handleSelectBranch}
|
||||||
|
>
|
||||||
<FreeLayoutEditorProvider {...editorProps}>
|
<FreeLayoutEditorProvider {...editorProps}>
|
||||||
<WorkflowEditorInner
|
<WorkflowEditorInner
|
||||||
definition={localDefinition}
|
definition={localDefinition}
|
||||||
@@ -105,9 +118,11 @@ export function WorkflowEditor({
|
|||||||
}}
|
}}
|
||||||
onSelectNode={(nodeId) => {
|
onSelectNode={(nodeId) => {
|
||||||
setSelectedNodeId(nodeId)
|
setSelectedNodeId(nodeId)
|
||||||
|
if (!nodeId || Date.now() - branchSelectAtRef.current > 160) {
|
||||||
setSelectedBranch(null)
|
setSelectedBranch(null)
|
||||||
|
}
|
||||||
}}
|
}}
|
||||||
onSelectBranch={setSelectedBranch}
|
onSelectBranch={handleSelectBranch}
|
||||||
onUndo={onUndo}
|
onUndo={onUndo}
|
||||||
undoDisabled={undoDisabled}
|
undoDisabled={undoDisabled}
|
||||||
onRedo={onRedo}
|
onRedo={onRedo}
|
||||||
@@ -122,6 +137,7 @@ export function WorkflowEditor({
|
|||||||
publishDisabled={publishDisabled}
|
publishDisabled={publishDisabled}
|
||||||
/>
|
/>
|
||||||
</FreeLayoutEditorProvider>
|
</FreeLayoutEditorProvider>
|
||||||
|
</WorkflowBranchSelectionProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user