From 36253de8645ad73f749773387075373f57094c39 Mon Sep 17 00:00:00 2001 From: Xin Wang Date: Fri, 14 Aug 2026 08:48:03 +0800 Subject: [PATCH] feat: improve tool configuration UX --- .../assistant-editor/built-in-tools.tsx | 583 +++++++++--- .../assistant-editor/prompt-editor.tsx | 264 ++++-- .../components/pages/ComponentsToolsPage.tsx | 4 +- .../components/tools/tool-binding-editor.tsx | 867 ++++++++++++++---- 4 files changed, 1355 insertions(+), 363 deletions(-) diff --git a/frontend/src/components/assistant-editor/built-in-tools.tsx b/frontend/src/components/assistant-editor/built-in-tools.tsx index d00d2bc..f65a1e6 100644 --- a/frontend/src/components/assistant-editor/built-in-tools.tsx +++ b/frontend/src/components/assistant-editor/built-in-tools.tsx @@ -1,11 +1,25 @@ "use client"; import { useState } from "react"; +import { ChevronDown, ChevronUp, Plus, Settings2, Trash2 } from "lucide-react"; +import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; +import { + Select, + SelectContent, + SelectItem, + SelectTrigger, + SelectValue, +} from "@/components/ui/select"; import { Switch } from "@/components/ui/switch"; import { Textarea } from "@/components/ui/textarea"; -import type { BuiltInTools, DynamicVariableDefinition } from "@/lib/api"; +import type { + BuiltInTools, + DynamicVariableDefinition, + StateExpression, + StateUpdateRule, +} from "@/lib/api"; const DEFAULTS = { endConversation: { @@ -20,41 +34,337 @@ const DEFAULTS = { requestHumanHandoff: { description: "提交人工接管请求。" }, }; +type BuiltInKey = keyof BuiltInTools; +type SimpleExpressionType = "llm" | "constant" | "variable" | "advanced"; + +const ITEMS: Array<{ + key: BuiltInKey; + label: string; + result: string; +}> = [ + { key: "endConversation", label: "结束对话", result: "播报结束语后结束当前会话" }, + { key: "updateState", label: "更新状态", result: "写入当前助手已声明的动态变量" }, + { key: "skipTurn", label: "跳过本轮", result: "本轮保持静默,不生成回复" }, + { key: "requestHumanHandoff", label: "转接人工", result: "发送人工接管请求并记录状态" }, +]; + +function expressionType(expression: StateExpression): SimpleExpressionType { + if ( + expression.type === "llm" || + expression.type === "constant" || + expression.type === "variable" + ) { + return expression.type; + } + return "advanced"; +} + +function defaultExpression( + source: Exclude, + variableName: string, + definition: DynamicVariableDefinition, +): StateExpression { + if (source === "constant") { + return { + type: "constant", + value: + definition.type === "boolean" + ? false + : definition.type === "number" + ? 0 + : "", + }; + } + if (source === "variable") { + return { type: "variable", variableName }; + } + return { + type: "llm", + parameterName: variableName, + valueType: definition.type, + description: `从当前对话中提取并确认 ${variableName}`, + }; +} + +function constantValue( + raw: string, + definition: DynamicVariableDefinition, +): string | number | boolean { + if (definition.type === "number") { + const parsed = Number(raw); + return Number.isFinite(parsed) ? parsed : 0; + } + if (definition.type === "boolean") return raw === "true"; + return raw; +} + function UpdateRulesEditor({ + value, + variables, + onChange, +}: { + value: StateUpdateRule[]; + variables: Record; + onChange: (value: StateUpdateRule[]) => void; +}) { + const variableNames = Object.keys(variables); + + function updateRule(index: number, next: StateUpdateRule) { + onChange(value.map((rule, itemIndex) => (itemIndex === index ? next : rule))); + } + + function addRule() { + const variableName = + variableNames.find((name) => !value.some((rule) => rule.variableName === name)) ?? + variableNames[0]; + if (!variableName) return; + onChange([ + ...value, + { + variableName, + expression: defaultExpression("llm", variableName, variables[variableName]), + }, + ]); + } + + if (!variableNames.length) { + return ( +
+ 请先在“动态变量”中声明可写变量,再配置状态更新规则。 +
+ ); + } + + return ( +
+
+
+
状态更新规则
+

+ 为目标变量选择数据来源;保存时仍会由后端校验变量与表达式。 +

+
+ +
+ + {value.length === 0 && ( +
+ 还没有规则。添加后,模型可在一次调用中原子更新这些变量。 +
+ )} + + {value.map((rule, index) => { + const definition = variables[rule.variableName] ?? variables[variableNames[0]]; + const sourceType = expressionType(rule.expression); + return ( +
+
+ + + +
+ + {rule.expression.type === "llm" && ( + + )} + {rule.expression.type === "variable" && ( + + )} + {rule.expression.type === "constant" && + (definition.type === "boolean" ? ( + + ) : ( + + ))} + {sourceType === "advanced" && ( + updateRule(index, { ...rule, expression })} + /> + )} +
+ ); + })} +
+ ); +} + +function AdvancedExpressionEditor({ value, onChange, }: { - value: NonNullable["updates"]; - onChange: (value: NonNullable["updates"]) => void; + value: StateExpression; + onChange: (value: StateExpression) => void; }) { const [draft, setDraft] = useState(JSON.stringify(value, null, 2)); const [error, setError] = useState(""); function commit() { try { - const parsed = JSON.parse(draft) as unknown; - if (!Array.isArray(parsed)) throw new Error(); + const parsed = JSON.parse(draft) as StateExpression; + if (!parsed || typeof parsed !== "object" || !("type" in parsed)) throw new Error(); setError(""); onChange(parsed); } catch { - setError("更新规则必须是 JSON 数组"); + setError("高级表达式必须是包含 type 的 JSON 对象"); } } return ( -
-
- 更新规则(JSON,支持 constant / variable / llm / if / 运算表达式) -
+