From 73d2680b83ccbdbd8dfec2d319533e98b379b830 Mon Sep 17 00:00:00 2001 From: bndw Date: Wed, 4 Mar 2026 21:36:32 -0800 Subject: feat: Thread optional `phase` param into `db/sessions.ts::cre… (+7 more) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - ✅ Thread optional `phase` param into `db/sessions.ts::createSession()` - ✅ Thread optional `phase` param into `ipc/handlers.ts` sessions:create handler - ✅ Thread optional `phase` param into `preload.ts` createSession API - ✅ Update Plan phase system prompt to gracefully handle missing research.md - ✅ Update Implement phase system prompt to gracefully handle missing plan.md - ✅ Create `renderer/src/components/NewSessionModal.tsx` - ✅ Update `App.tsx`: add modal state, split handler, add modal JSX - ✅ Add modal CSS to `globals.css` --- renderer/src/components/settings/ModelSettings.tsx | 156 +++++++++++++++++++-- 1 file changed, 142 insertions(+), 14 deletions(-) (limited to 'renderer/src/components/settings/ModelSettings.tsx') diff --git a/renderer/src/components/settings/ModelSettings.tsx b/renderer/src/components/settings/ModelSettings.tsx index ecfc12c..f0cbda5 100644 --- a/renderer/src/components/settings/ModelSettings.tsx +++ b/renderer/src/components/settings/ModelSettings.tsx @@ -1,21 +1,61 @@ 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() { - // null = not yet loaded from DB + // ── 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"]).then((settings) => { - const saved = settings["model"] ?? ""; - setModel(saved); - setDraft(saved); - }); + 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) { @@ -37,6 +77,34 @@ export function ModelSettings() { 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 (
@@ -49,10 +117,11 @@ export function ModelSettings() { return (
-
Model
+ {/* ── Global default ──────────────────────────────────── */} +
Default Model
- Claude model to use for all phases. Leave blank to use the SDK's - default model. Takes effect on the next message sent in any session. + Fallback model used for all phases unless a per-phase override is set. + Leave blank to use the SDK's built-in default.
@@ -60,7 +129,7 @@ export function ModelSettings() { className="settings-model-input" type="text" value={draft} - placeholder="Default (e.g. claude-sonnet-4-5)" + placeholder="e.g. claude-sonnet-4-5" onChange={(e) => { setDraft(e.target.value); setSaveStatus("idle"); @@ -75,7 +144,7 @@ export function ModelSettings() {
{model && ( )} - {model && ( - custom - )} + {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 + )} +
+
+ ); + })}
); -- cgit v1.2.3