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:
parent
a97d433902
commit
268cec6c04
5 changed files with 629 additions and 2 deletions
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 []
|
||||
|
|
|
|||
|
|
@ -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 (0–2). 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 (0–1). 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",
|
||||
|
|
|
|||
|
|
@ -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 采样温度(0–2)。数值越低越确定性。留空使用提供商默认值。",
|
||||
"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": "复杂度评分阈值(0–1)。评分高于此值的请求使用主模型。",
|
||||
"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": "可视化配置",
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue