refactor: unify system tools as resources

This commit is contained in:
Xin Wang
2026-08-04 17:05:26 +08:00
parent d1b05f16c7
commit 74a8be2357
26 changed files with 788 additions and 507 deletions
@@ -184,7 +184,6 @@ function blankPromptForm(name: string): AssistantForm {
actions: [],
openingMessage: null,
},
systemTools: [],
visionEnabled: false,
visionModelResourceId: "",
toolIds: [],
@@ -480,7 +479,6 @@ export function AssistantPage(props: AssistantPageProps) {
actions: a.startup?.actions ?? [],
openingMessage: a.startup?.openingMessage ?? null,
},
systemTools: a.systemTools ?? [],
visionEnabled: a.visionEnabled,
visionModelResourceId: a.visionModelResourceId ?? "",
toolIds: a.toolIds ?? [],
@@ -557,7 +555,6 @@ export function AssistantPage(props: AssistantPageProps) {
actions: [],
openingMessage: null,
},
systemTools: [],
visionEnabled: false,
visionModelResourceId: null,
modelResourceIds: {},
@@ -617,7 +614,6 @@ export function AssistantPage(props: AssistantPageProps) {
enableInterrupt: form.enableInterrupt,
turnConfig: form.turnConfig,
startup: form.startup,
systemTools: form.runtimeMode === "pipeline" ? form.systemTools : [],
visionEnabled: form.visionEnabled,
visionModelResourceId: form.visionModelResourceId || null,
modelResourceIds: {
@@ -1304,10 +1300,16 @@ export function AssistantPage(props: AssistantPageProps) {
vision: visionModelOptionsFor(""),
}}
toolOptions={tools
.filter(
(tool) => tool.status === "active" && tool.type !== "system",
)
.map((tool) => ({ value: tool.id, label: tool.name }))}
.filter((tool) => tool.status === "active")
.map((tool) => ({
value: tool.id,
label: tool.name,
toolType: tool.type,
systemKind:
tool.definition.type === "system"
? tool.definition.config.kind
: undefined,
}))}
knowledgeOptions={kbOptions}
onBack={() => router.push("/assistants")}
onSave={() => void handleSaveWorkflow()}
@@ -57,12 +57,14 @@ import {
type ClientToolResponseWaitMode,
type HttpToolDefinition,
type McpServer,
type SystemToolKind,
type Tool,
type ToolParameter,
type ToolExecutionMode,
type ToolStatus,
type ToolUpsert,
} from "@/lib/api";
import { SYSTEM_TOOL_OPTIONS } from "@/lib/system-tools";
type ToolKind = "system" | "http" | "client";
type HttpMethod = HttpToolDefinition["config"]["method"];
@@ -86,6 +88,7 @@ type ToolForm = {
type: ToolKind;
description: string;
status: ToolStatus;
systemKind: SystemToolKind;
messageType: "none" | "custom";
customMessage: string;
captureReason: boolean;
@@ -115,6 +118,7 @@ function blankForm(): ToolForm {
type: "system",
description: "",
status: "active",
systemKind: "end_conversation",
messageType: "none",
customMessage: "",
captureReason: true,
@@ -148,6 +152,7 @@ function formFromTool(tool: Tool): ToolForm {
base.description = tool.description;
base.status = tool.status;
if (tool.definition.type === "system") {
base.systemKind = tool.definition.config.kind;
base.messageType = tool.definition.config.messageType;
base.customMessage = tool.definition.config.customMessage;
base.captureReason = tool.definition.config.captureReason;
@@ -246,9 +251,13 @@ function payloadFromForm(form: ToolForm): ToolUpsert {
schemaVersion: 1,
type: "system",
config: {
kind: "end_conversation",
kind: form.systemKind,
messageType: form.messageType,
customMessage: form.messageType === "custom" ? form.customMessage : "",
customMessage:
form.systemKind === "end_conversation" &&
form.messageType === "custom"
? form.customMessage
: "",
captureReason: form.captureReason,
},
},
@@ -871,52 +880,80 @@ function SystemToolFields({
return (
<div className="space-y-4">
<Field label="系统动作">
<Select value="end_conversation" disabled>
<Select
value={form.systemKind}
onValueChange={(systemKind: SystemToolKind) =>
setForm((current) => ({
...current,
systemKind,
functionName: systemKind,
}))
}
>
<SelectTrigger className="w-full border-hairline-strong bg-background">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="end_conversation">结束对话</SelectItem>
{SYSTEM_TOOL_OPTIONS.map((option) => (
<SelectItem key={option.value} value={option.value}>
{option.label}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field label="结束语">
<Select
value={form.messageType}
onValueChange={(messageType: "none" | "custom") =>
setForm((current) => ({ ...current, messageType }))
}
>
<SelectTrigger className="w-full border-hairline-strong bg-background"><SelectValue /></SelectTrigger>
<SelectContent>
<SelectItem value="none">不播放固定结束语</SelectItem>
<SelectItem value="custom">自定义结束语</SelectItem>
</SelectContent>
</Select>
</Field>
{form.messageType === "custom" && (
<Field label="自定义结束语">
<Textarea
value={form.customMessage}
onChange={(event) =>
setForm((current) => ({ ...current, customMessage: event.target.value }))
}
rows={3}
/>
</Field>
<p className="text-xs leading-5 text-muted-foreground">
{SYSTEM_TOOL_OPTIONS.find((option) => option.value === form.systemKind)
?.description ?? "由平台执行的会话控制能力。"}
</p>
{form.systemKind === "end_conversation" && (
<>
<Field label="结束语">
<Select
value={form.messageType}
onValueChange={(messageType: "none" | "custom") =>
setForm((current) => ({ ...current, messageType }))
}
>
<SelectTrigger className="w-full border-hairline-strong bg-background">
<SelectValue />
</SelectTrigger>
<SelectContent>
<SelectItem value="none">不播放固定结束语</SelectItem>
<SelectItem value="custom">自定义结束语</SelectItem>
</SelectContent>
</Select>
</Field>
{form.messageType === "custom" && (
<Field label="自定义结束语">
<Textarea
value={form.customMessage}
onChange={(event) =>
setForm((current) => ({
...current,
customMessage: event.target.value,
}))
}
rows={3}
/>
</Field>
)}
<div className="flex items-center justify-between gap-4 rounded-lg border border-hairline-strong px-4 py-3">
<div>
<div className="font-medium text-foreground">记录结束原因</div>
<div className="mt-0.5 text-xs text-muted-foreground">
要求模型在调用时提供 reason 参数
</div>
</div>
<Switch
checked={form.captureReason}
onCheckedChange={(captureReason) =>
setForm((current) => ({ ...current, captureReason }))
}
/>
</div>
</>
)}
<div className="flex items-center justify-between gap-4 rounded-lg border border-hairline-strong px-4 py-3">
<div>
<div className="font-medium text-foreground">记录结束原因</div>
<div className="mt-0.5 text-xs text-muted-foreground">要求模型在调用时提供 reason 参数</div>
</div>
<Switch
checked={form.captureReason}
onCheckedChange={(captureReason) =>
setForm((current) => ({ ...current, captureReason }))
}
/>
</div>
</div>
);
}