feat: enhance variable handling and UI components with business labels and descriptions
This commit is contained in:
@@ -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[] = []
|
||||
|
||||
Reference in New Issue
Block a user