import React, { useState, useEffect } from "react"; import type { Phase } from "../../types"; const api = window.api; const PHASES: Phase[] = ["research", "plan", "implement"]; const PHASE_LABELS: Record = { research: "Research", plan: "Plan", implement: "Implement", }; export function ModelSettings() { // ── Global default ─────────────────────────────────────────── const [model, setModel] = useState(null); const [draft, setDraft] = useState(""); const [saveStatus, setSaveStatus] = useState<"idle" | "saved">("idle"); // ── Per-phase overrides ────────────────────────────────────── const [phaseModels, setPhaseModels] = useState>({ research: null, plan: null, implement: null, }); const [phaseDrafts, setPhaseDrafts] = useState>({ research: "", plan: "", implement: "", }); const [phaseStatus, setPhaseStatus] = useState>({ research: "idle", plan: "idle", implement: "idle", }); useEffect(() => { api .getSettings(["model", "model.research", "model.plan", "model.implement"]) .then((settings) => { const saved = settings["model"] ?? ""; setModel(saved); setDraft(saved); const pm: Record = { research: settings["model.research"], plan: settings["model.plan"], implement: settings["model.implement"], }; setPhaseModels(pm); setPhaseDrafts({ research: pm.research ?? "", plan: pm.plan ?? "", implement: pm.implement ?? "", }); }); }, []); // ── Global handlers ────────────────────────────────────────── const handleSave = async () => { const trimmed = draft.trim(); if (trimmed) { await api.setSetting("model", trimmed); setModel(trimmed); } else { await api.deleteSetting("model"); setModel(""); } setSaveStatus("saved"); setTimeout(() => setSaveStatus("idle"), 1500); }; const handleReset = async () => { await api.deleteSetting("model"); setModel(""); setDraft(""); setSaveStatus("saved"); setTimeout(() => setSaveStatus("idle"), 1500); }; // ── Per-phase handlers ─────────────────────────────────────── const handlePhaseSave = async (phase: Phase) => { const trimmed = phaseDrafts[phase].trim(); if (trimmed) { await api.setSetting(`model.${phase}`, trimmed); setPhaseModels((prev) => ({ ...prev, [phase]: trimmed })); } else { await api.deleteSetting(`model.${phase}`); setPhaseModels((prev) => ({ ...prev, [phase]: null })); } setPhaseStatus((prev) => ({ ...prev, [phase]: "saved" })); setTimeout( () => setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" })), 1500 ); }; const handlePhaseReset = async (phase: Phase) => { await api.deleteSetting(`model.${phase}`); setPhaseModels((prev) => ({ ...prev, [phase]: null })); setPhaseDrafts((prev) => ({ ...prev, [phase]: "" })); setPhaseStatus((prev) => ({ ...prev, [phase]: "saved" })); setTimeout( () => setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" })), 1500 ); }; if (model === null) { return (
Loading...
); } const isDirty = draft.trim() !== model; return (
{/* ── Global default ──────────────────────────────────── */}
Default Model
Fallback model used for all phases unless a per-phase override is set. Leave blank to use the SDK's built-in default.
{ setDraft(e.target.value); setSaveStatus("idle"); }} onKeyDown={(e) => { if (e.key === "Enter") handleSave(); }} spellCheck={false} />
{model && ( )} {model && custom}
{/* ── Per-phase overrides ─────────────────────────────── */}
Phase Overrides
Use a different model for a specific phase. Leave blank to inherit the default above. Takes effect on the next message in any session.
{PHASES.map((phase) => { const saved = phaseModels[phase]; const phaseDirty = phaseDrafts[phase].trim() !== (saved ?? ""); return (
{PHASE_LABELS[phase]} { setPhaseDrafts((prev) => ({ ...prev, [phase]: e.target.value, })); setPhaseStatus((prev) => ({ ...prev, [phase]: "idle" })); }} onKeyDown={(e) => { if (e.key === "Enter") handlePhaseSave(phase); }} spellCheck={false} />
{saved && ( )} {saved && ( custom )}
); })}
); }