"use client" import { Trash2Icon } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { OptionCombobox } from "@/components/option-combobox" import type { AIWorkflowDefinition, AIWorkflowNodeSpec } from "@/lib/api/admin" import { cn } from "@/lib/utils" import { VariableSelector } from "./variable-selector" import { createConditionBranchID, isRefValue, normalizeNodeConfig, type WorkflowConditionBranch, type WorkflowVariableRef, } from "./workflow-utils" export type WorkflowBranchSummary = { branchId: string targetNodeId?: string targetName?: string } const CONDITION_OPERATOR_OPTIONS = [ { value: "eq", label: "等于" }, { value: "neq", label: "不等于" }, { value: "contains", label: "包含" }, { value: "not_contains", label: "不包含" }, { value: "gt", label: "大于" }, { value: "gte", label: "大于等于" }, { value: "lt", label: "小于" }, { value: "lte", label: "小于等于" }, { value: "exists", label: "存在" }, { value: "empty", label: "为空" }, ] export function NodeConfigPanel({ node, nodeSpec, nodes, availableVariables, showHeader = true, showConditionBranches = true, onChange, onDelete, }: { node: AIWorkflowDefinition["nodes"][number] | null nodeSpec?: AIWorkflowNodeSpec nodes: AIWorkflowDefinition["nodes"] availableVariables?: WorkflowVariableRef[] branchSummaries?: WorkflowBranchSummary[] showHeader?: boolean showConditionBranches?: boolean onChange: (nodeId: string, data: AIWorkflowDefinition["nodes"][number]["data"]) => void onDelete?: (nodeId: string) => void }) { if (!node) { return (
未选择节点
) } const inputsValues = node.data?.inputsValues ?? {} const inputSchema = nodeSpec?.inputSchema ?? [] const canDelete = node.type !== "start" && node.type !== "end" const config = normalizeNodeConfig(node.data?.config) const branches = config.branches ?? [] const updateData = (data: Partial) => { onChange(node.id, { ...(node.data ?? {}), ...data, }) } const updateConfig = (nextConfig: Record) => updateData({ config: nextConfig }) const updateBranch = (branch: WorkflowConditionBranch) => { const nextBranches = branches.some((item) => item.id === branch.id) ? branches.map((item) => (item.id === branch.id ? branch : item)) : [...branches, branch] updateConfig({ ...config, branches: nextBranches }) } const deleteBranch = (branchId: string) => { updateConfig({ ...config, branches: branches.filter((branch) => branch.id !== branchId) }) } const addBranch = () => { const targetNodeId = nodes.find((item) => item.id !== node.id && item.type !== "start")?.id ?? "" updateBranch({ id: createConditionBranchID(branches), name: "新分支", targetNodeId, condition: { operator: "eq", }, }) } return (
{showHeader ? (
{node.data?.title || nodeSpec?.title || node.type}
{node.id}
{canDelete ? ( ) : null}
) : null}
updateData({ title: event.target.value })} />
{inputSchema.length > 0 ? (
输入
{inputSchema.map((input) => { const value = inputsValues[input.name] return (
{ updateData({ inputsValues: { ...inputsValues, [input.name]: next, }, }) }} />
) })}
) : null} {showConditionBranches && (node.type === "condition" || branches.length > 0) ? ( ) : null}
) } export function ConditionBranchConfigPanel({ node, nodes, branchId, variables, onChange, onDelete, }: { node: AIWorkflowDefinition["nodes"][number] nodes: AIWorkflowDefinition["nodes"] branchId: string variables: WorkflowVariableRef[] onChange: (nodeId: string, data: AIWorkflowDefinition["nodes"][number]["data"]) => void onDelete: (branchId: string) => void }) { const config = normalizeNodeConfig(node.data?.config) const branches = config.branches ?? [] const branch = branches.find((item) => item.id === branchId) const targetOptions = buildTargetOptions(nodes, node.id) if (!branch) { return (
条件分支不存在
) } const updateBranch = (nextBranch: WorkflowConditionBranch) => { onChange(node.id, { ...(node.data ?? {}), config: { ...config, branches: branches.map((item) => (item.id === nextBranch.id ? nextBranch : item)), }, }) } return (
updateBranch({ ...branch, name: event.target.value })} />
updateBranch({ ...branch, targetNodeId })} />
{branch.default ? (
默认分支不需要条件表达式,会在其他条件不匹配时执行。
) : ( )} {branch.default ? null : ( )}
) } function ConditionBranchesEditor({ branches, nodes, currentNodeId, variables, onAdd, onChange, onDelete, }: { branches: WorkflowConditionBranch[] nodes: AIWorkflowDefinition["nodes"] currentNodeId: string variables: WorkflowVariableRef[] onAdd: () => void onChange: (branch: WorkflowConditionBranch) => void onDelete: (branchId: string) => void }) { const targetOptions = buildTargetOptions(nodes, currentNodeId) return (
条件分支
{branches.length === 0 ? (
暂无分支。条件节点需要至少一个默认分支或条件分支。
) : null}
{branches.map((branch) => { return (
onChange({ ...branch, name: event.target.value })} /> {branch.default ? null : ( )}
onChange({ ...branch, targetNodeId })} />
{branch.default ? null : ( )}
) })}
) } function ConditionFields({ branch, variables, onChange, }: { branch: WorkflowConditionBranch variables: WorkflowVariableRef[] onChange: (branch: WorkflowConditionBranch) => void }) { const condition = branch.condition ?? {} return (
onChange({ ...branch, condition: { ...condition, left }, })} />
onChange({ ...branch, condition: { ...condition, operator }, })} />
onChange({ ...branch, condition: { ...condition, right: event.target.value }, })} />
) } function buildTargetOptions(nodes: AIWorkflowDefinition["nodes"], currentNodeId: string) { return nodes .filter((node) => node.id !== currentNodeId && node.type !== "start") .map((node) => ({ value: node.id, label: node.data?.title || node.type || node.id, })) } function stringifyConditionRight(value: unknown) { if (value === undefined || value === null) { return "" } if (typeof value === "string") { return value } return JSON.stringify(value) }