Merge 859066a324 into a97d433902
This commit is contained in:
commit
4c72cd7d10
5 changed files with 634 additions and 2 deletions
|
|
@ -18,7 +18,10 @@ import {
|
||||||
DevicesSection,
|
DevicesSection,
|
||||||
ExecSection,
|
ExecSection,
|
||||||
LauncherSection,
|
LauncherSection,
|
||||||
|
RoutingSection,
|
||||||
RuntimeSection,
|
RuntimeSection,
|
||||||
|
SubTurnSection,
|
||||||
|
ToolSecuritySection,
|
||||||
} from "@/components/config/config-sections"
|
} from "@/components/config/config-sections"
|
||||||
import {
|
import {
|
||||||
type CoreConfigForm,
|
type CoreConfigForm,
|
||||||
|
|
@ -27,6 +30,7 @@ import {
|
||||||
type LauncherForm,
|
type LauncherForm,
|
||||||
buildFormFromConfig,
|
buildFormFromConfig,
|
||||||
parseCIDRText,
|
parseCIDRText,
|
||||||
|
parseFloatField,
|
||||||
parseIntField,
|
parseIntField,
|
||||||
parseMultilineList,
|
parseMultilineList,
|
||||||
} from "@/components/config/form-model"
|
} from "@/components/config/form-model"
|
||||||
|
|
@ -180,6 +184,62 @@ export function ConfigPage() {
|
||||||
"Cron exec timeout",
|
"Cron exec timeout",
|
||||||
{ min: 0 },
|
{ 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
|
||||||
|
if (form.routingEnabled && !form.routingLightModel.trim()) {
|
||||||
|
throw new Error("Light Model is required when Smart Routing is enabled.")
|
||||||
|
}
|
||||||
|
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 =
|
||||||
|
form.filterSensitiveData && form.filterMinLength.trim()
|
||||||
|
? parseIntField(form.filterMinLength, "Filter min length", {
|
||||||
|
min: 0,
|
||||||
|
})
|
||||||
|
: undefined
|
||||||
|
|
||||||
const execConfigPatch: Record<string, unknown> = {
|
const execConfigPatch: Record<string, unknown> = {
|
||||||
enabled: form.execEnabled,
|
enabled: form.execEnabled,
|
||||||
}
|
}
|
||||||
|
|
@ -208,6 +268,10 @@ export function ConfigPage() {
|
||||||
defaults: {
|
defaults: {
|
||||||
workspace,
|
workspace,
|
||||||
restrict_to_workspace: form.restrictToWorkspace,
|
restrict_to_workspace: form.restrictToWorkspace,
|
||||||
|
allow_read_outside_workspace: form.allowReadOutsideWorkspace,
|
||||||
|
steering_mode: form.steeringMode,
|
||||||
|
temperature,
|
||||||
|
max_media_size: maxMediaSize,
|
||||||
tool_feedback: {
|
tool_feedback: {
|
||||||
enabled: form.toolFeedbackEnabled,
|
enabled: form.toolFeedbackEnabled,
|
||||||
max_args_length: toolFeedbackMaxArgsLength,
|
max_args_length: toolFeedbackMaxArgsLength,
|
||||||
|
|
@ -217,12 +281,28 @@ export function ConfigPage() {
|
||||||
max_tool_iterations: maxToolIterations,
|
max_tool_iterations: maxToolIterations,
|
||||||
summarize_message_threshold: summarizeMessageThreshold,
|
summarize_message_threshold: summarizeMessageThreshold,
|
||||||
summarize_token_percent: summarizeTokenPercent,
|
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.trim() || undefined,
|
||||||
|
threshold: routingThreshold,
|
||||||
|
}
|
||||||
|
: { enabled: false },
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
session: {
|
session: {
|
||||||
dm_scope: dmScope,
|
dm_scope: dmScope,
|
||||||
},
|
},
|
||||||
tools: {
|
tools: {
|
||||||
|
filter_sensitive_data: form.filterSensitiveData,
|
||||||
|
filter_min_length: filterMinLength,
|
||||||
cron: {
|
cron: {
|
||||||
allow_command: form.allowCommand,
|
allow_command: form.allowCommand,
|
||||||
exec_timeout_minutes: cronExecTimeoutMinutes,
|
exec_timeout_minutes: cronExecTimeoutMinutes,
|
||||||
|
|
@ -237,6 +317,12 @@ export function ConfigPage() {
|
||||||
enabled: form.devicesEnabled,
|
enabled: form.devicesEnabled,
|
||||||
monitor_usb: form.monitorUSB,
|
monitor_usb: form.monitorUSB,
|
||||||
},
|
},
|
||||||
|
voice: {
|
||||||
|
echo_transcription: form.voiceEchoTranscription,
|
||||||
|
},
|
||||||
|
gateway: {
|
||||||
|
log_level: form.gatewayLogLevel,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
||||||
setBaseline(form)
|
setBaseline(form)
|
||||||
|
|
@ -322,10 +408,16 @@ export function ConfigPage() {
|
||||||
|
|
||||||
<AgentDefaultsSection form={form} onFieldChange={updateField} />
|
<AgentDefaultsSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
|
<SubTurnSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
|
<RoutingSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<RuntimeSection form={form} onFieldChange={updateField} />
|
<RuntimeSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<ExecSection form={form} onFieldChange={updateField} />
|
<ExecSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
|
<ToolSecuritySection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<CronSection form={form} onFieldChange={updateField} />
|
<CronSection form={form} onFieldChange={updateField} />
|
||||||
|
|
||||||
<LauncherSection
|
<LauncherSection
|
||||||
|
|
|
||||||
|
|
@ -6,6 +6,8 @@ import {
|
||||||
type CoreConfigForm,
|
type CoreConfigForm,
|
||||||
DM_SCOPE_OPTIONS,
|
DM_SCOPE_OPTIONS,
|
||||||
type LauncherForm,
|
type LauncherForm,
|
||||||
|
LOG_LEVEL_OPTIONS,
|
||||||
|
STEERING_MODE_OPTIONS,
|
||||||
} from "@/components/config/form-model"
|
} from "@/components/config/form-model"
|
||||||
import { Field, SwitchCardField } from "@/components/shared-form"
|
import { Field, SwitchCardField } from "@/components/shared-form"
|
||||||
import { Button } from "@/components/ui/button"
|
import { Button } from "@/components/ui/button"
|
||||||
|
|
@ -70,6 +72,9 @@ export function AgentDefaultsSection({
|
||||||
onFieldChange,
|
onFieldChange,
|
||||||
}: AgentDefaultsSectionProps) {
|
}: AgentDefaultsSectionProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const selectedSteeringModeOption = STEERING_MODE_OPTIONS.find(
|
||||||
|
(o) => o.value === form.steeringMode,
|
||||||
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ConfigSectionCard title={t("pages.config.sections.agent")}>
|
<ConfigSectionCard title={t("pages.config.sections.agent")}>
|
||||||
|
|
@ -192,6 +197,225 @@ export function AgentDefaultsSection({
|
||||||
}
|
}
|
||||||
/>
|
/>
|
||||||
</Field>
|
</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>
|
||||||
|
{selectedSteeringModeOption
|
||||||
|
? t(
|
||||||
|
selectedSteeringModeOption.labelKey,
|
||||||
|
selectedSteeringModeOption.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>
|
</ConfigSectionCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
@ -389,6 +613,9 @@ interface RuntimeSectionProps {
|
||||||
|
|
||||||
export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
|
const selectedLogLevelOption = LOG_LEVEL_OPTIONS.find(
|
||||||
|
(o) => o.value === form.gatewayLogLevel,
|
||||||
|
)
|
||||||
const selectedDmScopeOption = DM_SCOPE_OPTIONS.find(
|
const selectedDmScopeOption = DM_SCOPE_OPTIONS.find(
|
||||||
(scope) => scope.value === form.dmScope,
|
(scope) => scope.value === form.dmScope,
|
||||||
)
|
)
|
||||||
|
|
@ -453,6 +680,86 @@ export function RuntimeSection({ form, onFieldChange }: RuntimeSectionProps) {
|
||||||
/>
|
/>
|
||||||
</Field>
|
</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>
|
||||||
|
{selectedLogLevelOption
|
||||||
|
? t(
|
||||||
|
selectedLogLevelOption.labelKey,
|
||||||
|
selectedLogLevelOption.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>
|
</ConfigSectionCard>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,28 @@ export interface CoreConfigForm {
|
||||||
heartbeatInterval: string
|
heartbeatInterval: string
|
||||||
devicesEnabled: boolean
|
devicesEnabled: boolean
|
||||||
monitorUSB: 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 {
|
export interface LauncherForm {
|
||||||
|
|
@ -62,6 +84,27 @@ export const DM_SCOPE_OPTIONS = [
|
||||||
},
|
},
|
||||||
] as const
|
] 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" },
|
||||||
|
{ value: "fatal", labelKey: "pages.config.log_level_fatal", labelDefault: "Fatal" },
|
||||||
|
] as const
|
||||||
|
|
||||||
export const EMPTY_FORM: CoreConfigForm = {
|
export const EMPTY_FORM: CoreConfigForm = {
|
||||||
workspace: "",
|
workspace: "",
|
||||||
restrictToWorkspace: true,
|
restrictToWorkspace: true,
|
||||||
|
|
@ -85,6 +128,28 @@ export const EMPTY_FORM: CoreConfigForm = {
|
||||||
heartbeatInterval: "30",
|
heartbeatInterval: "30",
|
||||||
devicesEnabled: false,
|
devicesEnabled: false,
|
||||||
monitorUSB: true,
|
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 = {
|
export const EMPTY_LAUNCHER_FORM: LauncherForm = {
|
||||||
|
|
@ -129,6 +194,10 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||||
const cron = asRecord(tools.cron)
|
const cron = asRecord(tools.cron)
|
||||||
const exec = asRecord(tools.exec)
|
const exec = asRecord(tools.exec)
|
||||||
const toolFeedback = asRecord(defaults.tool_feedback)
|
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 {
|
return {
|
||||||
workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
|
workspace: asString(defaults.workspace) || EMPTY_FORM.workspace,
|
||||||
|
|
@ -212,6 +281,64 @@ export function buildFormFromConfig(config: unknown): CoreConfigForm {
|
||||||
devices.monitor_usb === undefined
|
devices.monitor_usb === undefined
|
||||||
? EMPTY_FORM.monitorUSB
|
? EMPTY_FORM.monitorUSB
|
||||||
: asBool(devices.monitor_usb),
|
: 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 +360,24 @@ export function parseIntField(
|
||||||
return value
|
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[] {
|
export function parseCIDRText(raw: string): string[] {
|
||||||
if (!raw.trim()) {
|
if (!raw.trim()) {
|
||||||
return []
|
return []
|
||||||
|
|
|
||||||
|
|
@ -501,13 +501,57 @@
|
||||||
"allowed_cidrs": "Allowed Network CIDRs",
|
"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_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",
|
"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. Values \u2264 0 use the default (3).",
|
||||||
|
"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. Leave empty or set to 0 to use the system default (5 minutes).",
|
||||||
|
"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). Values \u2264 0 use the default (0.35). 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",
|
||||||
|
"log_level_fatal": "Fatal",
|
||||||
"sections": {
|
"sections": {
|
||||||
"agent": "Agent",
|
"agent": "Agent",
|
||||||
"runtime": "Runtime",
|
"runtime": "Runtime",
|
||||||
"exec": "Run Commands",
|
"exec": "Run Commands",
|
||||||
"cron": "Cron Tasks",
|
"cron": "Cron Tasks",
|
||||||
"launcher": "Service",
|
"launcher": "Service",
|
||||||
"devices": "Devices"
|
"devices": "Devices",
|
||||||
|
"subturn": "Sub-Turns",
|
||||||
|
"routing": "Smart Routing",
|
||||||
|
"tool_security": "Tool Security"
|
||||||
},
|
},
|
||||||
"open_raw": "Raw Config",
|
"open_raw": "Raw Config",
|
||||||
"back_to_visual": "Visual Config",
|
"back_to_visual": "Visual Config",
|
||||||
|
|
|
||||||
|
|
@ -501,13 +501,57 @@
|
||||||
"allowed_cidrs": "允许访问网段",
|
"allowed_cidrs": "允许访问网段",
|
||||||
"allowed_cidrs_hint": "仅允许这些 CIDR 网段的客户端访问服务。可按行或逗号分隔;留空表示允许所有来源。",
|
"allowed_cidrs_hint": "仅允许这些 CIDR 网段的客户端访问服务。可按行或逗号分隔;留空表示允许所有来源。",
|
||||||
"allowed_cidrs_placeholder": "192.168.1.0/24\n10.0.0.0/8",
|
"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 或留空将使用默认值(3)。",
|
||||||
|
"subturn_max_concurrent": "最大并发数",
|
||||||
|
"subturn_max_concurrent_hint": "可同时运行的子轮次最大数量。",
|
||||||
|
"subturn_default_timeout": "默认超时(分钟)",
|
||||||
|
"subturn_default_timeout_hint": "派生子轮次的默认超时时间(分钟)。留空或设为 0 使用系统默认值(5 分钟)。",
|
||||||
|
"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]。留空或设为 0 使用系统默认值(当前为 0.35)。评分高于此值的请求使用主模型。",
|
||||||
|
"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": "错误",
|
||||||
|
"log_level_fatal": "致命",
|
||||||
"sections": {
|
"sections": {
|
||||||
"agent": "智能体",
|
"agent": "智能体",
|
||||||
"runtime": "运行时",
|
"runtime": "运行时",
|
||||||
"exec": "运行命令",
|
"exec": "运行命令",
|
||||||
"cron": "定时任务",
|
"cron": "定时任务",
|
||||||
"launcher": "服务参数",
|
"launcher": "服务参数",
|
||||||
"devices": "设备"
|
"devices": "设备",
|
||||||
|
"subturn": "子轮次",
|
||||||
|
"routing": "智能路由",
|
||||||
|
"tool_security": "工具安全"
|
||||||
},
|
},
|
||||||
"open_raw": "原始配置",
|
"open_raw": "原始配置",
|
||||||
"back_to_visual": "可视化配置",
|
"back_to_visual": "可视化配置",
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue