diff --git a/src/components/AgentSettingsPanel.tsx b/src/components/AgentSettingsPanel.tsx new file mode 100644 index 0000000..84cb8a2 --- /dev/null +++ b/src/components/AgentSettingsPanel.tsx @@ -0,0 +1,376 @@ +import type { ReactNode } from "react"; +import { useTranslation } from "../i18n"; +import type { TranslationKey } from "../i18n/zh"; +import { getAgentSettings, setAgentSettings } from "../lib/agent-settings"; +import type { AgentSettings, Hook, PermissionRule } from "../types"; + +interface AgentSettingsPanelProps { + rawOther: unknown; + onChange: (nextRawOther: unknown) => void; +} + +const THINKING_LEVELS = ["low", "medium", "high", "max"] as const; +const THINKING_LABELS: Record<(typeof THINKING_LEVELS)[number], TranslationKey> = { + low: "thinkingLow", + medium: "thinkingMedium", + high: "thinkingHigh", + max: "thinkingMax", +}; + +const PERMISSION_DECISIONS = ["allow", "deny", "ask"] as const; +const PERMISSION_LABELS: Record<(typeof PERMISSION_DECISIONS)[number], TranslationKey> = { + allow: "permissionAllow", + deny: "permissionDeny", + ask: "permissionAsk", +}; + +const COMMON_EVENTS = ["PreToolUse", "PostToolUse"] as const; + +export function AgentSettingsPanel({ rawOther, onChange }: AgentSettingsPanelProps) { + const { t } = useTranslation(); + const settings = getAgentSettings(rawOther); + + const update = (patch: Partial) => { + onChange(setAgentSettings(rawOther, patch)); + }; + + const updateThinking = (patch: Partial) => { + update({ thinking: { ...settings.thinking, ...patch } }); + }; + + const updateLoopControl = (patch: Partial) => { + update({ loop_control: { ...settings.loop_control, ...patch } }); + }; + + const updateBackground = (patch: Partial) => { + update({ background: { ...settings.background, ...patch } }); + }; + + const setRules = (rules: PermissionRule[]) => { + update({ permission: { rules } }); + }; + + const setHooks = (hooks: Hook[]) => { + update({ hooks }); + }; + + const thinkingEnabled = settings.thinking?.enabled ?? true; + + return ( +
+

{t("agentSettings")}

+ + + updateThinking({ enabled: checked })} + /> +
+ {t("thinkingLevel")} + ({ + key: lvl, + label: t(THINKING_LABELS[lvl]), + }))} + value={settings.thinking?.effort ?? "medium"} + onChange={(effort) => + updateThinking({ effort: effort as AgentSettings["thinking"]["effort"] }) + } + disabled={!thinkingEnabled} + /> +
+ updateThinking({ keep: checked ? "all" : false })} + /> +

{t("thinkingContextHint")}

+
+ + + updateLoopControl({ max_retries_per_step: v })} + /> + updateLoopControl({ reserved_context_size: v })} + /> + + + + updateBackground({ max_running_tasks: v })} + /> + updateBackground({ keep_alive_on_exit: checked })} + /> + + + +
+ {(settings.permission?.rules ?? []).map((rule, idx) => ( +
+ + { + const rules = [...(settings.permission?.rules ?? [])]; + rules[idx] = { ...rule, pattern: e.target.value }; + setRules(rules); + }} + /> + +
+ ))} +
+
+ + +
+
+ + +
+ {(settings.hooks ?? []).map((hook, idx) => ( +
+ { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, event: e.target.value }; + setHooks(hooks); + }} + /> + + {COMMON_EVENTS.map((e) => ( + + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, matcher: e.target.value }; + setHooks(hooks); + }} + /> + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { ...hook, command: e.target.value }; + setHooks(hooks); + }} + /> + { + const hooks = [...(settings.hooks ?? [])]; + hooks[idx] = { + ...hook, + timeout: e.target.value === "" ? undefined : Number(e.target.value), + }; + setHooks(hooks); + }} + /> + +
+ ))} +
+ +
+
+ ); +} + +function Card({ title, children }: { title: string; children: ReactNode }) { + return ( +
+

{title}

+ {children} +
+ ); +} + +function Checkbox({ + label, + checked, + disabled, + onChange, +}: { + label: string; + checked: boolean; + disabled?: boolean; + onChange: (checked: boolean) => void; +}) { + return ( + + ); +} + +function NumberField({ + label, + value, + onChange, +}: { + label: string; + value?: number; + onChange: (value: number | undefined) => void; +}) { + return ( + + ); +} + +function Segmented({ + options, + value, + onChange, + disabled, +}: { + options: { key: string; label: string }[]; + value: string; + onChange: (value: string) => void; + disabled?: boolean; +}) { + return ( +
+ {options.map((opt) => ( + + ))} +
+ ); +}