"use client" import { useEffect, useMemo, useState } from "react" import { Trash2Icon } from "lucide-react" import { Button } from "@/components/ui/button" import { Input } from "@/components/ui/input" import { Label } from "@/components/ui/label" import { Textarea } from "@/components/ui/textarea" 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, showConfigJSON = true, onChange, onDelete, }: { node: AIWorkflowDefinition["nodes"][number] | null nodeSpec?: AIWorkflowNodeSpec nodes: AIWorkflowDefinition["nodes"] availableVariables?: WorkflowVariableRef[] branchSummaries?: WorkflowBranchSummary[] showHeader?: boolean showConditionBranches?: boolean showConfigJSON?: boolean onChange: (nodeId: string, data: AIWorkflowDefinition["nodes"][number]["data"]) => void onDelete?: (nodeId: string) => void }) { const [configText, setConfigText] = useState("{}") useEffect(() => { setConfigText(JSON.stringify(node?.data?.config ?? {}, null, 2)) }, [node?.id, node?.data?.config]) const configError = useMemo(() => { if (!node) { return "" } try { const parsed = JSON.parse(configText || "{}") return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? "" : "配置必须是 JSON 对象" } catch { return "JSON 格式错误" } }, [configText, node]) 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, }, }) }} /> {input.description ? (
{input.description}
) : null}
) })}
) : null} {showConditionBranches && (node.type === "condition" || branches.length > 0) ? ( ) : null} {showConfigJSON ? (