feat: enhance variable handling and UI components with business labels and descriptions

This commit is contained in:
mlogclub
2026-06-29 18:37:31 +08:00
parent 295c7b4bea
commit 87760d5e89
8 changed files with 166 additions and 85 deletions
@@ -15,6 +15,8 @@ import {
createConditionBranchID,
isRefValue,
normalizeNodeConfig,
refField,
refNodeId,
type WorkflowConditionBranch,
type WorkflowVariableRef,
} from "./workflow-utils"
@@ -383,6 +385,11 @@ function ConditionFields({
compact?: boolean
}) {
const condition = branch.condition ?? {}
const selectedVariable = isRefValue(condition.left)
? variables.find((item) => item.nodeId === refNodeId(condition.left) && item.field === refField(condition.left))
: undefined
const valueOptions = selectedVariable?.valueOptions ?? []
const rightDisabled = ["exists", "empty"].includes(condition.operator ?? "")
return (
<div className={cn("space-y-3", compact && "space-y-2")}>
@@ -414,17 +421,37 @@ function ConditionFields({
})}
/>
</div>
<div className={cn("space-y-1.5", compact && "space-y-0", ["exists", "empty"].includes(condition.operator ?? "") && "opacity-50")}>
<div className={cn("space-y-1.5", compact && "space-y-0", rightDisabled && "opacity-50")}>
{compact ? null : <Label className="text-xs text-slate-500"></Label>}
<Input
value={stringifyConditionRight(condition.right)}
disabled={["exists", "empty"].includes(condition.operator ?? "")}
className={cardInputClassName}
onChange={(event) => onChange({
...branch,
condition: { ...condition, right: event.target.value },
})}
/>
{valueOptions.length > 0 && !rightDisabled ? (
<OptionCombobox
value={stringifyConditionRight(condition.right)}
options={valueOptions.map((option) => ({
value: stringifyConditionRight(option.value),
label: option.label || stringifyConditionRight(option.value),
description: option.description,
}))}
placeholder="选择取值"
triggerClassName={cardComboboxClassName}
onChange={(nextValue) => {
const selectedOption = valueOptions.find((option) => stringifyConditionRight(option.value) === nextValue)
onChange({
...branch,
condition: { ...condition, right: selectedOption?.value ?? nextValue },
})
}}
/>
) : (
<Input
value={stringifyConditionRight(condition.right)}
disabled={rightDisabled}
className={cardInputClassName}
onChange={(event) => onChange({
...branch,
condition: { ...condition, right: event.target.value },
})}
/>
)}
</div>
</div>
</div>
@@ -3,6 +3,7 @@
import { OptionCombobox } from "@/components/option-combobox"
import {
buildVariableOption,
createRefValue,
refField,
refNodeId,
@@ -24,11 +25,7 @@ export function VariableSelector({
triggerClassName?: string
}) {
const selected = value ? `${refNodeId(value)}.${refField(value)}` : ""
const options = variables.map((variable) => ({
value: `${variable.nodeId}.${variable.field}`,
label: `${variable.nodeName}.${variable.label || variable.field}`,
description: variable.description,
}))
const options = variables.map(buildVariableOption)
return (
<OptionCombobox
@@ -158,7 +158,7 @@ export function WorkflowConfigPanel({
</Button>
</div>
{panelDescription ? (
<div className="mt-1 pl-9 text-xs leading-5 text-slate-500">
<div className="mt-1 text-xs leading-5 text-slate-500">
{panelDescription}
</div>
) : null}
@@ -226,6 +226,26 @@ describe("getAvailableVariables", () => {
})
})
describe("workflow variable display helpers", () => {
it("builds business-first variable options with technical details", async () => {
const { buildVariableOption } = await loadModule()
assert.deepEqual(plain(buildVariableOption({
nodeId: "start_1",
nodeName: "开始",
field: "userMessage",
label: "用户消息",
type: "string",
description: "客户本轮发送的消息内容",
})), {
value: "start_1.userMessage",
label: "开始 / 用户消息",
subtitle: "start_1.userMessage · string",
description: "客户本轮发送的消息内容",
})
})
})
describe("workflow definition mutations", () => {
it("updates node data without changing unrelated nodes", async () => {
const { updateWorkflowNodeData } = await loadModule()
@@ -65,6 +65,13 @@ export type WorkflowVariableRef = {
valueOptions?: WorkflowVariableValueOption[]
}
export type WorkflowVariableOption = {
value: string
label: string
subtitle: string
description: string
}
export type WorkflowNodeSpec = AIWorkflowNodeSpec
export type WorkflowDraftValidation = {
@@ -92,6 +99,17 @@ export function refField(value: WorkflowValue | undefined): string {
return isRefValue(value) ? value.content[1] : ""
}
export function buildVariableOption(variable: WorkflowVariableRef): WorkflowVariableOption {
const ref = `${variable.nodeId}.${variable.field}`
const label = `${variable.nodeName} / ${variable.label || variable.field}`
return {
value: ref,
label,
subtitle: [ref, variable.type].filter(Boolean).join(" · "),
description: variable.description,
}
}
export function getNodeTitle(
node: AIWorkflowDefinition["nodes"][number] | undefined,
specs: AIWorkflowNodeSpec[] = []