Add branch summary handling to NodeConfigPanel and WorkflowEditor
This commit is contained in:
@@ -11,6 +11,7 @@ import { VariableSelector } from "./variable-selector"
|
|||||||
import type {
|
import type {
|
||||||
WorkflowNodeSpec,
|
WorkflowNodeSpec,
|
||||||
WorkflowVariableRef,
|
WorkflowVariableRef,
|
||||||
|
WorkflowVariableSpec,
|
||||||
WorkflowVariableSelector,
|
WorkflowVariableSelector,
|
||||||
} from "./workflow-utils"
|
} from "./workflow-utils"
|
||||||
|
|
||||||
@@ -21,15 +22,24 @@ type WorkflowNodeData = Record<string, unknown> & {
|
|||||||
inputs?: Record<string, WorkflowVariableSelector>
|
inputs?: Record<string, WorkflowVariableSelector>
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type WorkflowBranchSummary = {
|
||||||
|
edgeId: string
|
||||||
|
targetName: string
|
||||||
|
conditionLabel: string
|
||||||
|
isDefault: boolean
|
||||||
|
}
|
||||||
|
|
||||||
export function NodeConfigPanel({
|
export function NodeConfigPanel({
|
||||||
node,
|
node,
|
||||||
nodeSpec,
|
nodeSpec,
|
||||||
availableVariables,
|
availableVariables,
|
||||||
|
branchSummaries = [],
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
node: Node<WorkflowNodeData> | null
|
node: Node<WorkflowNodeData> | null
|
||||||
nodeSpec?: WorkflowNodeSpec
|
nodeSpec?: WorkflowNodeSpec
|
||||||
availableVariables: WorkflowVariableRef[]
|
availableVariables: WorkflowVariableRef[]
|
||||||
|
branchSummaries?: WorkflowBranchSummary[]
|
||||||
onChange: (nodeId: string, data: WorkflowNodeData) => void
|
onChange: (nodeId: string, data: WorkflowNodeData) => void
|
||||||
}) {
|
}) {
|
||||||
if (!node) {
|
if (!node) {
|
||||||
@@ -46,6 +56,7 @@ export function NodeConfigPanel({
|
|||||||
node={node}
|
node={node}
|
||||||
nodeSpec={nodeSpec}
|
nodeSpec={nodeSpec}
|
||||||
availableVariables={availableVariables}
|
availableVariables={availableVariables}
|
||||||
|
branchSummaries={branchSummaries}
|
||||||
onChange={onChange}
|
onChange={onChange}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
@@ -55,11 +66,13 @@ function NodeConfigForm({
|
|||||||
node,
|
node,
|
||||||
nodeSpec,
|
nodeSpec,
|
||||||
availableVariables,
|
availableVariables,
|
||||||
|
branchSummaries,
|
||||||
onChange,
|
onChange,
|
||||||
}: {
|
}: {
|
||||||
node: Node<WorkflowNodeData>
|
node: Node<WorkflowNodeData>
|
||||||
nodeSpec?: WorkflowNodeSpec
|
nodeSpec?: WorkflowNodeSpec
|
||||||
availableVariables: WorkflowVariableRef[]
|
availableVariables: WorkflowVariableRef[]
|
||||||
|
branchSummaries: WorkflowBranchSummary[]
|
||||||
onChange: (nodeId: string, data: WorkflowNodeData) => void
|
onChange: (nodeId: string, data: WorkflowNodeData) => void
|
||||||
}) {
|
}) {
|
||||||
const [name, setName] = useState(node.data.name ?? "")
|
const [name, setName] = useState(node.data.name ?? "")
|
||||||
@@ -70,6 +83,7 @@ function NodeConfigForm({
|
|||||||
const [error, setError] = useState("")
|
const [error, setError] = useState("")
|
||||||
const inputSchema = nodeSpec?.inputSchema ?? []
|
const inputSchema = nodeSpec?.inputSchema ?? []
|
||||||
const outputSchema = nodeSpec?.outputSchema ?? []
|
const outputSchema = nodeSpec?.outputSchema ?? []
|
||||||
|
const isConditionNode = node.data.nodeType === "condition"
|
||||||
|
|
||||||
const commitChange = (next: Partial<WorkflowNodeData>) => {
|
const commitChange = (next: Partial<WorkflowNodeData>) => {
|
||||||
onChange(node.id, {
|
onChange(node.id, {
|
||||||
@@ -106,6 +120,10 @@ function NodeConfigForm({
|
|||||||
onBlur={() => commitChange({ name: name.trim() || node.data.nodeType || node.id })}
|
onBlur={() => commitChange({ name: name.trim() || node.data.nodeType || node.id })}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
|
{isConditionNode ? (
|
||||||
|
<ConditionNodePanel branchSummaries={branchSummaries} outputSchema={outputSchema} />
|
||||||
|
) : (
|
||||||
|
<>
|
||||||
{inputSchema.length > 0 ? (
|
{inputSchema.length > 0 ? (
|
||||||
<div className="space-y-3">
|
<div className="space-y-3">
|
||||||
<div className="text-sm font-medium">输入映射</div>
|
<div className="text-sm font-medium">输入映射</div>
|
||||||
@@ -182,6 +200,66 @@ function NodeConfigForm({
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : null}
|
) : null}
|
||||||
|
</>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function ConditionNodePanel({
|
||||||
|
branchSummaries,
|
||||||
|
outputSchema,
|
||||||
|
}: {
|
||||||
|
branchSummaries: WorkflowBranchSummary[]
|
||||||
|
outputSchema: WorkflowVariableSpec[]
|
||||||
|
}) {
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
{/* <div className="rounded-md border bg-muted/30 p-3 text-xs text-muted-foreground">
|
||||||
|
条件节点只负责分流;每条出口连线承载自己的判断条件,没有条件的出口会作为默认分支。
|
||||||
|
</div> */}
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm font-medium">出口分支</div>
|
||||||
|
{branchSummaries.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
{branchSummaries.map((branch) => (
|
||||||
|
<div key={branch.edgeId} className="rounded-md border bg-background p-2">
|
||||||
|
<div className="flex items-center justify-between gap-2 text-xs">
|
||||||
|
<span className="min-w-0 truncate font-medium">{branch.targetName}</span>
|
||||||
|
<span className="shrink-0 rounded-sm bg-muted px-1.5 py-0.5 text-muted-foreground">
|
||||||
|
{branch.isDefault ? "默认" : "条件"}
|
||||||
|
</span>
|
||||||
|
</div>
|
||||||
|
<div className="mt-1 line-clamp-2 text-xs text-muted-foreground">
|
||||||
|
{branch.conditionLabel}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
) : (
|
||||||
|
<div className="rounded-md border border-dashed p-2 text-xs text-muted-foreground">
|
||||||
|
当前还没有出口连线。
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
{outputSchema.length > 0 ? (
|
||||||
|
<div className="space-y-2">
|
||||||
|
<div className="text-sm font-medium">输出变量</div>
|
||||||
|
<div className="space-y-1 rounded-md border bg-background p-2">
|
||||||
|
{outputSchema.map((output) => (
|
||||||
|
<div key={output.name} className="space-y-0.5 rounded-sm px-1 py-0.5">
|
||||||
|
<div className="flex items-center justify-between gap-2 text-xs">
|
||||||
|
<span className="truncate font-medium">{output.name}</span>
|
||||||
|
<span className="shrink-0 text-muted-foreground">{output.type}</span>
|
||||||
|
</div>
|
||||||
|
{output.description ? (
|
||||||
|
<div className="text-xs text-muted-foreground">{output.description}</div>
|
||||||
|
) : null}
|
||||||
|
</div>
|
||||||
|
))}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ import {
|
|||||||
type WorkflowEditorEdge,
|
type WorkflowEditorEdge,
|
||||||
type WorkflowEditorNode,
|
type WorkflowEditorNode,
|
||||||
} from "./workflow-utils"
|
} from "./workflow-utils"
|
||||||
import { NodeConfigPanel } from "./node-config-panel"
|
import { NodeConfigPanel, type WorkflowBranchSummary } from "./node-config-panel"
|
||||||
import { VariableSelector } from "./variable-selector"
|
import { VariableSelector } from "./variable-selector"
|
||||||
|
|
||||||
type WorkflowNodeData = Record<string, unknown> & {
|
type WorkflowNodeData = Record<string, unknown> & {
|
||||||
@@ -207,6 +207,10 @@ export function WorkflowEditor({
|
|||||||
() => (propertyPanelNode ? getAvailableVariables(draft, propertyPanelNode.id, nodeSpecs) : []),
|
() => (propertyPanelNode ? getAvailableVariables(draft, propertyPanelNode.id, nodeSpecs) : []),
|
||||||
[draft, nodeSpecs, propertyPanelNode]
|
[draft, nodeSpecs, propertyPanelNode]
|
||||||
)
|
)
|
||||||
|
const propertyPanelBranchSummaries = useMemo(
|
||||||
|
() => (propertyPanelNode ? getBranchSummaries(nodes, edges, propertyPanelNode.id) : []),
|
||||||
|
[edges, nodes, propertyPanelNode]
|
||||||
|
)
|
||||||
const propertyPanelEdgeVariables = useMemo(
|
const propertyPanelEdgeVariables = useMemo(
|
||||||
() => (propertyPanelEdge ? getEdgeConditionVariables(draft, propertyPanelEdge.source, nodeSpecs) : []),
|
() => (propertyPanelEdge ? getEdgeConditionVariables(draft, propertyPanelEdge.source, nodeSpecs) : []),
|
||||||
[draft, nodeSpecs, propertyPanelEdge]
|
[draft, nodeSpecs, propertyPanelEdge]
|
||||||
@@ -591,6 +595,7 @@ export function WorkflowEditor({
|
|||||||
node={propertyPanelNode}
|
node={propertyPanelNode}
|
||||||
nodeSpec={propertyPanelNodeSpec}
|
nodeSpec={propertyPanelNodeSpec}
|
||||||
availableVariables={propertyPanelAvailableVariables}
|
availableVariables={propertyPanelAvailableVariables}
|
||||||
|
branchSummaries={propertyPanelBranchSummaries}
|
||||||
onChange={updateNodeData}
|
onChange={updateNodeData}
|
||||||
/>
|
/>
|
||||||
) : null}
|
) : null}
|
||||||
@@ -703,6 +708,50 @@ const conditionOperators = [
|
|||||||
{ value: "lte", label: "小于等于" },
|
{ value: "lte", label: "小于等于" },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
function getBranchSummaries(
|
||||||
|
nodes: WorkflowFlowNode[],
|
||||||
|
edges: WorkflowFlowEdge[],
|
||||||
|
nodeId: string
|
||||||
|
): WorkflowBranchSummary[] {
|
||||||
|
return edges
|
||||||
|
.filter((edge) => edge.source === nodeId)
|
||||||
|
.map((edge) => {
|
||||||
|
const target = nodes.find((node) => node.id === edge.target)
|
||||||
|
const condition = (edge.data as WorkflowEditorEdge["data"] | undefined)?.condition
|
||||||
|
return {
|
||||||
|
edgeId: edge.id,
|
||||||
|
targetName: target?.data.name ?? target?.data.title ?? edge.target,
|
||||||
|
conditionLabel: condition ? formatConditionLabel(condition) : "无条件匹配",
|
||||||
|
isDefault: !condition,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatConditionLabel(condition: NonNullable<WorkflowEdgeCondition>) {
|
||||||
|
const left = condition.left?.nodeId && condition.left.field
|
||||||
|
? `${condition.left.nodeId}.${condition.left.field}`
|
||||||
|
: "未选择变量"
|
||||||
|
const operator = conditionOperators.find((item) => item.value === condition.operator)?.label
|
||||||
|
?? condition.operator
|
||||||
|
?? "未选择判断方式"
|
||||||
|
|
||||||
|
if (["exists", "not_exists", "truthy", "falsy"].includes(condition.operator ?? "")) {
|
||||||
|
return `${left} ${operator}`
|
||||||
|
}
|
||||||
|
|
||||||
|
return `${left} ${operator} ${formatConditionRight(condition.right)}`
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatConditionRight(value: unknown) {
|
||||||
|
if (value === undefined || value === null || value === "") {
|
||||||
|
return "未填写比较值"
|
||||||
|
}
|
||||||
|
if (typeof value === "object") {
|
||||||
|
return JSON.stringify(value)
|
||||||
|
}
|
||||||
|
return String(value)
|
||||||
|
}
|
||||||
|
|
||||||
function EdgeConditionPanel({
|
function EdgeConditionPanel({
|
||||||
edge,
|
edge,
|
||||||
variables,
|
variables,
|
||||||
|
|||||||
Reference in New Issue
Block a user