feat: add UI controls for missing config fields

- Add steering mode, temperature, max media size, allow read outside
  workspace to Agent Defaults section
- Add Sub-Turns section (max depth, concurrency, timeouts, token budget)
- Add Smart Routing section (enabled toggle, light model, threshold)
- Add Tool Security section (filter sensitive data, min content length)
- Add voice echo transcription and gateway log level to Runtime section
- Add parseFloatField utility for float validation
- Add English and Chinese translations for all new controls
This commit is contained in:
Kristjan Kruus 2026-03-25 16:55:52 +02:00
parent a97d433902
commit 268cec6c04
5 changed files with 629 additions and 2 deletions

View file

@ -18,7 +18,10 @@ import {
DevicesSection,
ExecSection,
LauncherSection,
RoutingSection,
RuntimeSection,
SubTurnSection,
ToolSecuritySection,
} from "@/components/config/config-sections"
import {
type CoreConfigForm,
@ -27,6 +30,7 @@ import {
type LauncherForm,
buildFormFromConfig,
parseCIDRText,
parseFloatField,
parseIntField,
parseMultilineList,
} from "@/components/config/form-model"
@ -180,6 +184,58 @@ export function ConfigPage() {
"Cron exec timeout",
{ min: 0 },
)
// SubTurn fields
const subturnMaxDepth = parseIntField(
form.subturnMaxDepth,
"SubTurn max depth",
{ min: 0 },
)
const subturnMaxConcurrent = parseIntField(
form.subturnMaxConcurrent,
"SubTurn max concurrent",
{ min: 0 },
)
const subturnDefaultTimeoutMinutes = parseIntField(
form.subturnDefaultTimeoutMinutes,
"SubTurn default timeout",
{ min: 0 },
)
const subturnDefaultTokenBudget = parseIntField(
form.subturnDefaultTokenBudget,
"SubTurn token budget",
{ min: 0 },
)
const subturnConcurrencyTimeoutSec = parseIntField(
form.subturnConcurrencyTimeoutSec,
"SubTurn concurrency timeout",
{ min: 0 },
)
// Routing fields
const routingThreshold = form.routingEnabled
? parseFloatField(form.routingThreshold, "Routing threshold", {
min: 0,
max: 1,
})
: undefined
// Optional numeric fields
const temperature = form.temperature.trim()
? parseFloatField(form.temperature, "Temperature", {
min: 0,
max: 2,
})
: undefined
const maxMediaSize = form.maxMediaSize.trim()
? parseIntField(form.maxMediaSize, "Max media size", { min: 0 })
: undefined
const filterMinLength = parseIntField(
form.filterMinLength,
"Filter min length",
{ min: 0 },
)
const execConfigPatch: Record<string, unknown> = {
enabled: form.execEnabled,
}
@ -208,6 +264,10 @@ export function ConfigPage() {
defaults: {
workspace,
restrict_to_workspace: form.restrictToWorkspace,
allow_read_outside_workspace: form.allowReadOutsideWorkspace,
steering_mode: form.steeringMode,
temperature,
max_media_size: maxMediaSize,
tool_feedback: {
enabled: form.toolFeedbackEnabled,
max_args_length: toolFeedbackMaxArgsLength,
@ -217,12 +277,28 @@ export function ConfigPage() {
max_tool_iterations: maxToolIterations,
summarize_message_threshold: summarizeMessageThreshold,
summarize_token_percent: summarizeTokenPercent,
subturn: {
max_depth: subturnMaxDepth,
max_concurrent: subturnMaxConcurrent,
default_timeout_minutes: subturnDefaultTimeoutMinutes,
default_token_budget: subturnDefaultTokenBudget,
concurrency_timeout_sec: subturnConcurrencyTimeoutSec,
},
routing: form.routingEnabled
? {
enabled: true,
light_model: form.routingLightModel,
threshold: routingThreshold,
}
: { enabled: false },
},
},
session: {
dm_scope: dmScope,
},
tools: {
filter_sensitive_data: form.filterSensitiveData,
filter_min_length: filterMinLength,
cron: {
allow_command: form.allowCommand,
exec_timeout_minutes: cronExecTimeoutMinutes,
@ -237,6 +313,12 @@ export function ConfigPage() {
enabled: form.devicesEnabled,
monitor_usb: form.monitorUSB,
},
voice: {
echo_transcription: form.voiceEchoTranscription,
},
gateway: {
log_level: form.gatewayLogLevel,
},
})
setBaseline(form)
@ -322,10 +404,16 @@ export function ConfigPage() {
<AgentDefaultsSection form={form} onFieldChange={updateField} />
<SubTurnSection form={form} onFieldChange={updateField} />
<RoutingSection form={form} onFieldChange={updateField} />
<RuntimeSection form={form} onFieldChange={updateField} />
<ExecSection form={form} onFieldChange={updateField} />
<ToolSecuritySection form={form} onFieldChange={updateField} />
<CronSection form={form} onFieldChange={updateField} />
<LauncherSection

View file

@ -6,6 +6,8 @@ import {
type CoreConfigForm,
DM_SCOPE_OPTIONS,
type LauncherForm,
LOG_LEVEL_OPTIONS,
STEERING_MODE_OPTIONS,
} from "@/components/config/form-model"
import { Field, SwitchCardField } from "@/components/shared-form"
import { Button } from "@/components/ui/button"
@ -192,6 +194,229 @@ export function AgentDefaultsSection({
}
/>
</Field>
<SwitchCardField
label={t("pages.config.allow_read_outside_workspace")}
hint={t("pages.config.allow_read_outside_workspace_hint")}
layout="setting-row"
checked={form.allowReadOutsideWorkspace}
onCheckedChange={(checked) =>
onFieldChange("allowReadOutsideWorkspace", checked)
}
/>
<Field
label={t("pages.config.steering_mode")}
hint={t("pages.config.steering_mode_hint")}
layout="setting-row"
>
<Select
value={form.steeringMode}
onValueChange={(value) => onFieldChange("steeringMode", value)}
>
<SelectTrigger className="w-full">
<SelectValue>
{STEERING_MODE_OPTIONS.find((o) => o.value === form.steeringMode)
? t(
STEERING_MODE_OPTIONS.find(
(o) => o.value === form.steeringMode,
)!.labelKey,
STEERING_MODE_OPTIONS.find(
(o) => o.value === form.steeringMode,
)!.labelDefault,
)
: form.steeringMode}
</SelectValue>
</SelectTrigger>
<SelectContent>
{STEERING_MODE_OPTIONS.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(opt.labelKey, opt.labelDefault)}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
<Field
label={t("pages.config.temperature")}
hint={t("pages.config.temperature_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
max={2}
step={0.1}
value={form.temperature}
onChange={(e) => onFieldChange("temperature", e.target.value)}
placeholder="0.7"
/>
</Field>
<Field
label={t("pages.config.max_media_size")}
hint={t("pages.config.max_media_size_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.maxMediaSize}
onChange={(e) => onFieldChange("maxMediaSize", e.target.value)}
placeholder="20971520"
/>
</Field>
</ConfigSectionCard>
)
}
interface SubTurnSectionProps {
form: CoreConfigForm
onFieldChange: UpdateCoreField
}
export function SubTurnSection({ form, onFieldChange }: SubTurnSectionProps) {
const { t } = useTranslation()
return (
<ConfigSectionCard
title={t("pages.config.sections.subturn")}
description={t("pages.config.subturn_description")}
>
<Field
label={t("pages.config.subturn_max_depth")}
hint={t("pages.config.subturn_max_depth_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.subturnMaxDepth}
onChange={(e) => onFieldChange("subturnMaxDepth", e.target.value)}
/>
</Field>
<Field
label={t("pages.config.subturn_max_concurrent")}
hint={t("pages.config.subturn_max_concurrent_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.subturnMaxConcurrent}
onChange={(e) =>
onFieldChange("subturnMaxConcurrent", e.target.value)
}
/>
</Field>
<Field
label={t("pages.config.subturn_default_timeout")}
hint={t("pages.config.subturn_default_timeout_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.subturnDefaultTimeoutMinutes}
onChange={(e) =>
onFieldChange("subturnDefaultTimeoutMinutes", e.target.value)
}
/>
</Field>
<Field
label={t("pages.config.subturn_token_budget")}
hint={t("pages.config.subturn_token_budget_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.subturnDefaultTokenBudget}
onChange={(e) =>
onFieldChange("subturnDefaultTokenBudget", e.target.value)
}
/>
</Field>
<Field
label={t("pages.config.subturn_concurrency_timeout")}
hint={t("pages.config.subturn_concurrency_timeout_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.subturnConcurrencyTimeoutSec}
onChange={(e) =>
onFieldChange("subturnConcurrencyTimeoutSec", e.target.value)
}
/>
</Field>
</ConfigSectionCard>
)
}
interface RoutingSectionProps {
form: CoreConfigForm
onFieldChange: UpdateCoreField
}
export function RoutingSection({ form, onFieldChange }: RoutingSectionProps) {
const { t } = useTranslation()
return (
<ConfigSectionCard
title={t("pages.config.sections.routing")}
description={t("pages.config.routing_description")}
>
<SwitchCardField
label={t("pages.config.routing_enabled")}
hint={t("pages.config.routing_enabled_hint")}
layout="setting-row"
checked={form.routingEnabled}
onCheckedChange={(checked) =>
onFieldChange("routingEnabled", checked)
}
/>
{form.routingEnabled && (
<>
<Field
label={t("pages.config.routing_light_model")}
hint={t("pages.config.routing_light_model_hint")}
layout="setting-row"
>
<Input
value={form.routingLightModel}
onChange={(e) =>
onFieldChange("routingLightModel", e.target.value)
}
placeholder="gpt-4o-mini"
/>
</Field>
<Field
label={t("pages.config.routing_threshold")}
hint={t("pages.config.routing_threshold_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
max={1}
step={0.1}
value={form.routingThreshold}
onChange={(e) =>
onFieldChange("routingThreshold", e.target.value)
}
/>
</Field>
</>
)}
</ConfigSectionCard>
)
}
@ -453,6 +678,90 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
/>
</Field>
)}
<SwitchCardField
label={t("pages.config.voice_echo_transcription")}
hint={t("pages.config.voice_echo_transcription_hint")}
layout="setting-row"
checked={form.voiceEchoTranscription}
onCheckedChange={(checked) =>
onFieldChange("voiceEchoTranscription", checked)
}
/>
<Field
label={t("pages.config.gateway_log_level")}
hint={t("pages.config.gateway_log_level_hint")}
layout="setting-row"
>
<Select
value={form.gatewayLogLevel}
onValueChange={(value) => onFieldChange("gatewayLogLevel", value)}
>
<SelectTrigger className="w-full">
<SelectValue>
{LOG_LEVEL_OPTIONS.find((o) => o.value === form.gatewayLogLevel)
? t(
LOG_LEVEL_OPTIONS.find(
(o) => o.value === form.gatewayLogLevel,
)!.labelKey,
LOG_LEVEL_OPTIONS.find(
(o) => o.value === form.gatewayLogLevel,
)!.labelDefault,
)
: form.gatewayLogLevel}
</SelectValue>
</SelectTrigger>
<SelectContent>
{LOG_LEVEL_OPTIONS.map((opt) => (
<SelectItem key={opt.value} value={opt.value}>
{t(opt.labelKey, opt.labelDefault)}
</SelectItem>
))}
</SelectContent>
</Select>
</Field>
</ConfigSectionCard>
)
}
interface ToolSecuritySectionProps {
form: CoreConfigForm
onFieldChange: UpdateCoreField
}
export function ToolSecuritySection({
form,
onFieldChange,
}: ToolSecuritySectionProps) {
const { t } = useTranslation()
return (
<ConfigSectionCard title={t("pages.config.sections.tool_security")}>
<SwitchCardField
label={t("pages.config.filter_sensitive_data")}
hint={t("pages.config.filter_sensitive_data_hint")}
layout="setting-row"
checked={form.filterSensitiveData}
onCheckedChange={(checked) =>
onFieldChange("filterSensitiveData", checked)
}
/>
{form.filterSensitiveData && (
<Field
label={t("pages.config.filter_min_length")}
hint={t("pages.config.filter_min_length_hint")}
layout="setting-row"
>
<Input
type="number"
min={0}
value={form.filterMinLength}
onChange={(e) => onFieldChange("filterMinLength", e.target.value)}
/>
</Field>
)}
</ConfigSectionCard>
)
}

View file

@ -23,6 +23,28 @@ export interface CoreConfigForm {
heartbeatInterval: string
devicesEnabled: boolean
monitorUSB: boolean
// Agent advanced
allowReadOutsideWorkspace: boolean
steeringMode: string
temperature: string
maxMediaSize: string
// SubTurn
subturnMaxDepth: string
subturnMaxConcurrent: string
subturnDefaultTimeoutMinutes: string
subturnDefaultTokenBudget: string
subturnConcurrencyTimeoutSec: string
// Routing
routingEnabled: boolean
routingLightModel: string
routingThreshold: string
// Tool security
filterSensitiveData: boolean
filterMinLength: string
// Voice
voiceEchoTranscription: boolean
// Gateway
gatewayLogLevel: string
}
export interface LauncherForm {
@ -62,6 +84,26 @@ export const DM_SCOPE_OPTIONS = [
},
] as const
export const STEERING_MODE_OPTIONS = [
{
value: "one-at-a-time",
labelKey: "pages.config.steering_mode_one_at_a_time",
labelDefault: "One at a Time",
},
{
value: "all",
labelKey: "pages.config.steering_mode_all",
labelDefault: "All",
},
] as const
export const LOG_LEVEL_OPTIONS = [
{ value: "debug", labelKey: "pages.config.log_level_debug", labelDefault: "Debug" },
{ value: "info", labelKey: "pages.config.log_level_info", labelDefault: "Info" },
{ value: "warn", labelKey: "pages.config.log_level_warn", labelDefault: "Warn" },
{ value: "error", labelKey: "pages.config.log_level_error", labelDefault: "Error" },
] as const
export const EMPTY_FORM: CoreConfigForm = {
workspace: "",
restrictToWorkspace: true,
@ -85,6 +127,28 @@ export const EMPTY_FORM: CoreConfigForm = {
heartbeatInterval: "30",
devicesEnabled: false,
monitorUSB: true,
// Agent advanced
allowReadOutsideWorkspace: false,
steeringMode: "one-at-a-time",
temperature: "",
maxMediaSize: "",
// SubTurn
subturnMaxDepth: "0",
subturnMaxConcurrent: "0",
subturnDefaultTimeoutMinutes: "0",
subturnDefaultTokenBudget: "0",
subturnConcurrencyTimeoutSec: "0",
// Routing
routingEnabled: false,
routingLightModel: "",
routingThreshold: "0.5",
// Tool security
filterSensitiveData: true,
filterMinLength: "8",
// Voice
voiceEchoTranscription: false,
// Gateway
gatewayLogLevel: "info",
}
export const EMPTY_LAUNCHER_FORM: LauncherForm = {
@ -129,6 +193,10 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
const cron = asRecord(tools.cron)
const exec = asRecord(tools.exec)
const toolFeedback = asRecord(defaults.tool_feedback)
const subturn = asRecord(defaults.subturn)
const routing = asRecord(defaults.routing)
const voice = asRecord(root.voice)
const gateway = asRecord(root.gateway)
return {
workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
@ -212,6 +280,64 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
devices.monitor_usb === undefined
? EMPTY_FORM.monitorUSB
: asBool(devices.monitor_usb),
// Agent advanced
allowReadOutsideWorkspace:
defaults.allow_read_outside_workspace === undefined
? EMPTY_FORM.allowReadOutsideWorkspace
: asBool(defaults.allow_read_outside_workspace),
steeringMode: asString(defaults.steering_mode) || EMPTY_FORM.steeringMode,
temperature: asNumberString(defaults.temperature, EMPTY_FORM.temperature),
maxMediaSize: asNumberString(
defaults.max_media_size,
EMPTY_FORM.maxMediaSize,
),
// SubTurn
subturnMaxDepth: asNumberString(
subturn.max_depth,
EMPTY_FORM.subturnMaxDepth,
),
subturnMaxConcurrent: asNumberString(
subturn.max_concurrent,
EMPTY_FORM.subturnMaxConcurrent,
),
subturnDefaultTimeoutMinutes: asNumberString(
subturn.default_timeout_minutes,
EMPTY_FORM.subturnDefaultTimeoutMinutes,
),
subturnDefaultTokenBudget: asNumberString(
subturn.default_token_budget,
EMPTY_FORM.subturnDefaultTokenBudget,
),
subturnConcurrencyTimeoutSec: asNumberString(
subturn.concurrency_timeout_sec,
EMPTY_FORM.subturnConcurrencyTimeoutSec,
),
// Routing
routingEnabled:
routing.enabled === undefined
? EMPTY_FORM.routingEnabled
: asBool(routing.enabled),
routingLightModel: asString(routing.light_model) || EMPTY_FORM.routingLightModel,
routingThreshold: asNumberString(
routing.threshold,
EMPTY_FORM.routingThreshold,
),
// Tool security
filterSensitiveData:
tools.filter_sensitive_data === undefined
? EMPTY_FORM.filterSensitiveData
: asBool(tools.filter_sensitive_data),
filterMinLength: asNumberString(
tools.filter_min_length,
EMPTY_FORM.filterMinLength,
),
// Voice
voiceEchoTranscription:
voice.echo_transcription === undefined
? EMPTY_FORM.voiceEchoTranscription
: asBool(voice.echo_transcription),
// Gateway
gatewayLogLevel: asString(gateway.log_level) || EMPTY_FORM.gatewayLogLevel,
}
}
@ -233,6 +359,24 @@ export function parseIntField(
return value
}
export function parseFloatField(
rawValue: string,
label: string,
options: { min?: number; max?: number } = {},
): number {
const value = Number(rawValue)
if (!Number.isFinite(value)) {
throw new Error(`${label} must be a number.`)
}
if (options.min !== undefined && value < options.min) {
throw new Error(`${label} must be >= ${options.min}.`)
}
if (options.max !== undefined && value > options.max) {
throw new Error(`${label} must be <= ${options.max}.`)
}
return value
}
export function parseCIDRText(raw: string): string[] {
if (!raw.trim()) {
return []

View file

@ -501,13 +501,56 @@
"allowed_cidrs": "Allowed Network CIDRs",
"allowed_cidrs_hint": "Only clients from these CIDR ranges can access the service. One per line or comma-separated. Leave empty to allow all.",
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"allow_read_outside_workspace": "Allow Read Outside Workspace",
"allow_read_outside_workspace_hint": "Allow the agent to read files outside the workspace directory.",
"steering_mode": "Steering Mode",
"steering_mode_hint": "How steering messages are injected into the agent loop. 'One at a Time' queues messages; 'All' delivers all pending messages at once.",
"steering_mode_one_at_a_time": "One at a Time",
"steering_mode_all": "All",
"temperature": "Temperature",
"temperature_hint": "LLM sampling temperature (02). Lower values are more deterministic. Leave empty for provider default.",
"max_media_size": "Max Media Size (bytes)",
"max_media_size_hint": "Maximum upload file size in bytes. Leave empty for the default (20 MB).",
"subturn_description": "Configure sub-turn spawning for background tasks and parallel agent work.",
"subturn_max_depth": "Max Depth",
"subturn_max_depth_hint": "Maximum nesting depth for spawned sub-turns. Set to 0 to disable spawning.",
"subturn_max_concurrent": "Max Concurrent",
"subturn_max_concurrent_hint": "Maximum number of sub-turns that can run in parallel.",
"subturn_default_timeout": "Default Timeout (minutes)",
"subturn_default_timeout_hint": "Default timeout in minutes for spawned sub-turns. Set to 0 for no timeout.",
"subturn_token_budget": "Token Budget",
"subturn_token_budget_hint": "Default token budget for each sub-turn. Set to 0 for unlimited.",
"subturn_concurrency_timeout": "Concurrency Timeout (seconds)",
"subturn_concurrency_timeout_hint": "How long to wait for a concurrency slot before timing out.",
"routing_description": "Route simple tasks to a lighter model to save cost and latency.",
"routing_enabled": "Enable Smart Routing",
"routing_enabled_hint": "When enabled, simple requests are routed to a cheaper model while complex ones use the primary model.",
"routing_light_model": "Light Model",
"routing_light_model_hint": "Model name (from your model list) to use for simple tasks.",
"routing_threshold": "Complexity Threshold",
"routing_threshold_hint": "Complexity score threshold (01). Requests scoring above this use the primary model.",
"filter_sensitive_data": "Filter Sensitive Data",
"filter_sensitive_data_hint": "Automatically filter API keys, tokens, and secrets from tool results before sending to the LLM.",
"filter_min_length": "Filter Min Content Length",
"filter_min_length_hint": "Content shorter than this (in characters) skips filtering for performance.",
"voice_echo_transcription": "Echo Voice Transcription",
"voice_echo_transcription_hint": "Echo the transcribed text back to the chat when processing voice messages.",
"gateway_log_level": "Log Level",
"gateway_log_level_hint": "Minimum log level for the gateway service.",
"log_level_debug": "Debug",
"log_level_info": "Info",
"log_level_warn": "Warn",
"log_level_error": "Error",
"sections": {
"agent": "Agent",
"runtime": "Runtime",
"exec": "Run Commands",
"cron": "Cron Tasks",
"launcher": "Service",
"devices": "Devices"
"devices": "Devices",
"subturn": "Sub-Turns",
"routing": "Smart Routing",
"tool_security": "Tool Security"
},
"open_raw": "Raw Config",
"back_to_visual": "Visual Config",

View file

@ -501,13 +501,56 @@
"allowed_cidrs": "允许访问网段",
"allowed_cidrs_hint": "仅允许这些 CIDR 网段的客户端访问服务。可按行或逗号分隔;留空表示允许所有来源。",
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
"allow_read_outside_workspace": "允许读取工作目录外文件",
"allow_read_outside_workspace_hint": "允许智能体读取工作目录之外的文件。",
"steering_mode": "转向模式",
"steering_mode_hint": "控制转向消息如何注入智能体循环。「逐条」模式逐条排队;「全部」模式一次性传递所有待处理消息。",
"steering_mode_one_at_a_time": "逐条",
"steering_mode_all": "全部",
"temperature": "温度",
"temperature_hint": "LLM 采样温度02。数值越低越确定性。留空使用提供商默认值。",
"max_media_size": "最大媒体大小(字节)",
"max_media_size_hint": "上传文件的最大字节数。留空使用默认值20 MB。",
"subturn_description": "配置子轮次的后台任务派生与并行工作。",
"subturn_max_depth": "最大深度",
"subturn_max_depth_hint": "派生子轮次的最大嵌套深度。设为 0 禁用派生。",
"subturn_max_concurrent": "最大并发数",
"subturn_max_concurrent_hint": "可同时运行的子轮次最大数量。",
"subturn_default_timeout": "默认超时(分钟)",
"subturn_default_timeout_hint": "派生子轮次的默认超时时间(分钟)。设为 0 表示无超时。",
"subturn_token_budget": "Token 预算",
"subturn_token_budget_hint": "每个子轮次的默认 Token 预算。设为 0 表示不限制。",
"subturn_concurrency_timeout": "并发超时(秒)",
"subturn_concurrency_timeout_hint": "等待并发槽位释放的超时时间。",
"routing_description": "将简单任务路由到轻量模型以节省成本和降低延迟。",
"routing_enabled": "启用智能路由",
"routing_enabled_hint": "启用后,简单请求会路由到较便宜的模型,复杂请求使用主模型。",
"routing_light_model": "轻量模型",
"routing_light_model_hint": "用于简单任务的模型名称(来自模型列表)。",
"routing_threshold": "复杂度阈值",
"routing_threshold_hint": "复杂度评分阈值01。评分高于此值的请求使用主模型。",
"filter_sensitive_data": "过滤敏感数据",
"filter_sensitive_data_hint": "自动过滤工具返回结果中的 API 密钥、令牌和机密信息,避免发送给 LLM。",
"filter_min_length": "过滤最小内容长度",
"filter_min_length_hint": "内容长度(字符数)低于此值时跳过过滤以提高性能。",
"voice_echo_transcription": "回显语音转录",
"voice_echo_transcription_hint": "处理语音消息时,将转录文本回显到聊天中。",
"gateway_log_level": "日志级别",
"gateway_log_level_hint": "网关服务的最低日志级别。",
"log_level_debug": "调试",
"log_level_info": "信息",
"log_level_warn": "警告",
"log_level_error": "错误",
"sections": {
"agent": "智能体",
"runtime": "运行时",
"exec": "运行命令",
"cron": "定时任务",
"launcher": "服务参数",
"devices": "设备"
"devices": "设备",
"subturn": "子轮次",
"routing": "智能路由",
"tool_security": "工具安全"
},
"open_raw": "原始配置",
"back_to_visual": "可视化配置",