feat(quick-pane): persist default thinking level

This commit is contained in:
2026-08-28 14:14:46 +02:00
parent 1ce03ef986
commit 5642d9be91
10 changed files with 164 additions and 26 deletions
+2 -2
View File
@@ -278,7 +278,7 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
assert.match(source, /new ResizeObserver\(resizeToContent\)/); assert.match(source, /new ResizeObserver\(resizeToContent\)/);
assert.match(source, /invoke\("resize_quick_pane", \{ height \}\)/); assert.match(source, /invoke\("resize_quick_pane", \{ height \}\)/);
assert.doesNotMatch(source, /onCloseRequested/); assert.doesNotMatch(source, /onCloseRequested/);
assert.match(source, /className="quick-pane-drag-strip"/); assert.match(source, /className="quick-pane-drag-region"/);
assert.match(source, /startDragging\(\)/); assert.match(source, /startDragging\(\)/);
assert.match(source, /className="quick-pane-request"/); assert.match(source, /className="quick-pane-request"/);
assert.match(source, /className=\{`quick-pane-exchange/); assert.match(source, /className=\{`quick-pane-exchange/);
@@ -322,7 +322,7 @@ test("Quick Pane uses native dimensions and request-first compact layout", async
stylesheet, stylesheet,
/\.quick-pane-composer[\s\S]*?height: 120px[\s\S]*?min-height: 110px[\s\S]*?max-height: 130px/, /\.quick-pane-composer[\s\S]*?height: 120px[\s\S]*?min-height: 110px[\s\S]*?max-height: 130px/,
); );
assert.match(stylesheet, /\.quick-pane-drag-strip[\s\S]*?height: 8px/); assert.match(stylesheet, /\.quick-pane-drag-region[\s\S]*?height: 24px/);
assert.match( assert.match(
stylesheet, stylesheet,
/\.quick-pane-composer-actions[\s\S]*?position: absolute/, /\.quick-pane-composer-actions[\s\S]*?position: absolute/,
+24 -1
View File
@@ -12,6 +12,7 @@ const MAX_DRAFT_BYTES: usize = 128 * 1024;
const MAX_TOTAL_DRAFT_BYTES: usize = 1024 * 1024; const MAX_TOTAL_DRAFT_BYTES: usize = 1024 * 1024;
const MAX_TOTAL_STRING_BYTES: usize = 1536 * 1024; const MAX_TOTAL_STRING_BYTES: usize = 1536 * 1024;
const MAX_FILE_BYTES: usize = 2 * 1024 * 1024; const MAX_FILE_BYTES: usize = 2 * 1024 * 1024;
const DEFAULT_THINKING_LEVEL: &str = "medium";
#[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)] #[derive(Clone, Debug, Default, Deserialize, PartialEq, Serialize)]
#[serde(rename_all = "camelCase", deny_unknown_fields)] #[serde(rename_all = "camelCase", deny_unknown_fields)]
@@ -47,6 +48,12 @@ pub struct QuickPaneSettings {
pub follow_up_model: String, pub follow_up_model: String,
pub instructions: String, pub instructions: String,
pub maximum_tool_calls: u32, pub maximum_tool_calls: u32,
#[serde(default = "default_thinking_level")]
pub default_thinking_level: String,
}
fn default_thinking_level() -> String {
DEFAULT_THINKING_LEVEL.to_owned()
} }
impl Default for QuickPaneSettings { impl Default for QuickPaneSettings {
@@ -59,6 +66,7 @@ impl Default for QuickPaneSettings {
follow_up_model: String::new(), follow_up_model: String::new(),
instructions: "Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.".to_owned(), instructions: "Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.".to_owned(),
maximum_tool_calls: 6, maximum_tool_calls: 6,
default_thinking_level: default_thinking_level(),
} }
} }
} }
@@ -233,6 +241,12 @@ pub fn validate(state: &UiStateV1) -> Result<(), String> {
if !(1..=64).contains(&quick.maximum_tool_calls) { if !(1..=64).contains(&quick.maximum_tool_calls) {
return Err("Quick-pane tool-call limit is outside the supported range".to_owned()); return Err("Quick-pane tool-call limit is outside the supported range".to_owned());
} }
if !matches!(
quick.default_thinking_level.as_str(),
"off" | "minimal" | "low" | "medium" | "high" | "xhigh" | "max"
) {
return Err("Quick-pane default thinking level is invalid".to_owned());
}
total_string_bytes = total_string_bytes total_string_bytes = total_string_bytes
.saturating_add(quick.workspace.len()) .saturating_add(quick.workspace.len())
.saturating_add(quick.provider.len()) .saturating_add(quick.provider.len())
@@ -449,6 +463,12 @@ mod tests {
state.quick_pane.follow_up_provider = "provider".into(); state.quick_pane.follow_up_provider = "provider".into();
state.quick_pane.follow_up_model = "follow-up-model".into(); state.quick_pane.follow_up_model = "follow-up-model".into();
assert!(validate(&state).is_ok()); assert!(validate(&state).is_ok());
state.quick_pane.default_thinking_level = "high".into();
save_to(&root, &state).unwrap();
assert_eq!(load_from(&root).unwrap().quick_pane.default_thinking_level, "high");
state.quick_pane.default_thinking_level = "unsupported".into();
assert!(validate(&state).is_err());
state.quick_pane.default_thinking_level = default_thinking_level();
state.quick_pane.maximum_tool_calls = 0; state.quick_pane.maximum_tool_calls = 0;
assert!(validate(&state).is_err()); assert!(validate(&state).is_err());
fs::remove_dir_all(root).unwrap(); fs::remove_dir_all(root).unwrap();
@@ -465,8 +485,11 @@ mod tests {
) )
.unwrap(); .unwrap();
let state = load_from(&root).unwrap(); let state = load_from(&root).unwrap();
assert_eq!(state.quick_pane.default_thinking_level, DEFAULT_THINKING_LEVEL);
save_to(&root, &state).unwrap(); save_to(&root, &state).unwrap();
assert!(!fs::read_to_string(&path).unwrap().contains("maximumSeconds")); let saved = fs::read_to_string(&path).unwrap();
assert!(!saved.contains("maximumSeconds"));
assert!(saved.contains("\"defaultThinkingLevel\": \"medium\""));
fs::remove_dir_all(root).unwrap(); fs::remove_dir_all(root).unwrap();
} }
+16
View File
@@ -147,6 +147,7 @@ describe("App command progress and errors", () => {
followUpModel: "gpt-5", followUpModel: "gpt-5",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
mocks.invoke.mockImplementation((command: string) => { mocks.invoke.mockImplementation((command: string) => {
@@ -189,6 +190,10 @@ describe("App command progress and errors", () => {
await user.click( await user.click(
screen.getByRole("option", { name: /Claude.*anthropic\/claude\/sonnet/i }), screen.getByRole("option", { name: /Claude.*anthropic\/claude\/sonnet/i }),
); );
await user.selectOptions(
screen.getByRole("combobox", { name: "Default thinking" }),
"high",
);
await user.click( await user.click(
screen.getByRole("button", { name: "Save Quick Pane settings" }), screen.getByRole("button", { name: "Save Quick Pane settings" }),
); );
@@ -200,6 +205,7 @@ describe("App command progress and errors", () => {
...loaded.quickPane, ...loaded.quickPane,
provider: "openrouter", provider: "openrouter",
model: "anthropic/claude/sonnet", model: "anthropic/claude/sonnet",
defaultThinkingLevel: "high",
}, },
}, },
}), }),
@@ -222,6 +228,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}); });
if (command === "create_quick_runtime") { if (command === "create_quick_runtime") {
@@ -283,6 +290,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
const first = deferred<{ const first = deferred<{
@@ -357,6 +365,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
const staleSnapshot = deferred<{ const staleSnapshot = deferred<{
@@ -450,6 +459,7 @@ describe("App command progress and errors", () => {
followUpModel: "model", followUpModel: "model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
const snapshot = deferred<unknown>(); const snapshot = deferred<unknown>();
@@ -487,6 +497,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
let openCount = 0; let openCount = 0;
@@ -546,6 +557,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
let runtimeCount = 0; let runtimeCount = 0;
@@ -620,6 +632,7 @@ describe("App command progress and errors", () => {
followUpModel: "old-model", followUpModel: "old-model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
const oldSnapshot = deferred<{ const oldSnapshot = deferred<{
@@ -693,6 +706,7 @@ describe("App command progress and errors", () => {
followUpModel: "model", followUpModel: "model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
let runtimeCount = 0; let runtimeCount = 0;
@@ -764,6 +778,7 @@ describe("App command progress and errors", () => {
followUpModel: "model", followUpModel: "model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
let createCount = 0; let createCount = 0;
@@ -819,6 +834,7 @@ describe("App command progress and errors", () => {
followUpModel: "model", followUpModel: "model",
instructions: "Stay concise.", instructions: "Stay concise.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
const nextSnapshot = deferred<unknown>(); const nextSnapshot = deferred<unknown>();
+50 -2
View File
@@ -6,13 +6,18 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import "./App.css"; import "./App.css";
import { ConversationWorkspace } from "./components/ConversationWorkspace"; import { ConversationWorkspace } from "./components/ConversationWorkspace";
import { ModelSelector } from "./components/ModelSelector"; import { ModelSelector } from "./components/ModelSelector";
import { ThinkingSelector } from "./components/ThinkingSelector";
import { DirectorySidebar } from "./components/DirectorySidebar"; import { DirectorySidebar } from "./components/DirectorySidebar";
import { ExtensionDialog } from "./components/ExtensionDialog"; import { ExtensionDialog } from "./components/ExtensionDialog";
import { CommandFormDialog } from "./components/CommandFormDialog"; import { CommandFormDialog } from "./components/CommandFormDialog";
import type { RpcCommand } from "./commands/rpc"; import type { RpcCommand } from "./commands/rpc";
import { SessionPicker } from "./components/SessionPicker"; import { SessionPicker } from "./components/SessionPicker";
import { SessionTabs } from "./components/SessionTabs"; import { SessionTabs } from "./components/SessionTabs";
import { modelIdentity, reconcileModelIdentity } from "./model-catalog"; import {
modelIdentity,
reconcileModelIdentity,
reconcileThinkingLevel,
} from "./model-catalog";
import type { import type {
DirectorySession, DirectorySession,
DirectoryWorkspace, DirectoryWorkspace,
@@ -37,6 +42,7 @@ const defaultQuickPaneSettings: QuickPaneSettings = {
instructions: instructions:
"Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.", "Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}; };
function validateQuickPaneSettings(settings: QuickPaneSettings) { function validateQuickPaneSettings(settings: QuickPaneSettings) {
if (!settings.workspace.startsWith("/")) if (!settings.workspace.startsWith("/"))
@@ -53,6 +59,8 @@ function validateQuickPaneSettings(settings: QuickPaneSettings) {
settings.maximumToolCalls > 64 settings.maximumToolCalls > 64
) )
return "Tool calls must be an integer from 1 to 64."; return "Tool calls must be an integer from 1 to 64.";
if (!settings.defaultThinkingLevel)
return "Default thinking level is required.";
} }
let submissionId = 0; let submissionId = 0;
type LocalOperation = { type LocalOperation = {
@@ -164,6 +172,15 @@ export default function App() {
const [modelCatalogError, setModelCatalogError] = useState<string>(); const [modelCatalogError, setModelCatalogError] = useState<string>();
const [catalogWorkspace, setCatalogWorkspace] = useState<string>(); const [catalogWorkspace, setCatalogWorkspace] = useState<string>();
const modelCatalogRequest = useRef(0); const modelCatalogRequest = useRef(0);
const quickPaneModel = useMemo(
() =>
modelCatalog.find(
(model) =>
model.provider === quickPaneSettings.provider &&
model.id === quickPaneSettings.model,
),
[modelCatalog, quickPaneSettings.model, quickPaneSettings.provider],
);
const catalogProbeRuntimeIds = useRef(new Set<string>()); const catalogProbeRuntimeIds = useRef(new Set<string>());
const retainedCatalogProbeRuntimeIds = useRef(new Set<string>()); const retainedCatalogProbeRuntimeIds = useRef(new Set<string>());
const retainedCatalogProbeErrors = useRef(new Map<string, unknown>()); const retainedCatalogProbeErrors = useRef(new Map<string, unknown>());
@@ -291,18 +308,30 @@ export default function App() {
provider: current.followUpProvider, provider: current.followUpProvider,
modelId: current.followUpModel, modelId: current.followUpModel,
}); });
const selectedQuickModel = freshModels.find(
(model) =>
model.provider === quickModel?.provider &&
model.id === quickModel?.modelId,
);
const next = { const next = {
...current, ...current,
provider: quickModel?.provider ?? "", provider: quickModel?.provider ?? "",
model: quickModel?.modelId ?? "", model: quickModel?.modelId ?? "",
followUpProvider: followUpModel?.provider ?? "", followUpProvider: followUpModel?.provider ?? "",
followUpModel: followUpModel?.modelId ?? "", followUpModel: followUpModel?.modelId ?? "",
defaultThinkingLevel: selectedQuickModel
? reconcileThinkingLevel(
selectedQuickModel,
current.defaultThinkingLevel,
)
: current.defaultThinkingLevel,
}; };
if ( if (
next.provider === current.provider && next.provider === current.provider &&
next.model === current.model && next.model === current.model &&
next.followUpProvider === current.followUpProvider && next.followUpProvider === current.followUpProvider &&
next.followUpModel === current.followUpModel next.followUpModel === current.followUpModel &&
next.defaultThinkingLevel === current.defaultThinkingLevel
) )
return current; return current;
setQuickPaneSettingsStatus( setQuickPaneSettingsStatus(
@@ -1082,12 +1111,31 @@ export default function App() {
...current, ...current,
provider: identity.provider, provider: identity.provider,
model: identity.modelId, model: identity.modelId,
defaultThinkingLevel: reconcileThinkingLevel(
model,
current.defaultThinkingLevel,
),
})); }));
return true; return true;
}} }}
/> />
<small>Provider comes from selected catalog model.</small> <small>Provider comes from selected catalog model.</small>
</div> </div>
<div className="quick-pane-model-setting">
<ThinkingSelector
label="Default thinking"
model={quickPaneModel}
value={quickPaneSettings.defaultThinkingLevel}
disabled={modelCatalogStatus !== "ready" || !quickPaneModel}
onChange={(defaultThinkingLevel) =>
setQuickPaneSettings((current) => ({
...current,
defaultThinkingLevel,
}))
}
/>
<small>Used when a Quick Pane opens.</small>
</div>
<div className="quick-pane-model-setting"> <div className="quick-pane-model-setting">
<ModelSelector <ModelSelector
label="Follow-up model" label="Follow-up model"
+4 -2
View File
@@ -4,6 +4,7 @@ import "./ThinkingSelector.css";
type ThinkingSelectorProps = { type ThinkingSelectorProps = {
model?: Model; model?: Model;
label?: string;
value: string; value: string;
disabled?: boolean; disabled?: boolean;
className?: string; className?: string;
@@ -13,6 +14,7 @@ type ThinkingSelectorProps = {
export function ThinkingSelector({ export function ThinkingSelector({
model, model,
label = "Thinking",
value, value,
disabled = false, disabled = false,
className, className,
@@ -24,10 +26,10 @@ export function ThinkingSelector({
return ( return (
<label className={classes}> <label className={classes}>
<span>Thinking</span> <span>{label}</span>
<select <select
className={controlClassName} className={controlClassName}
aria-label="Thinking" aria-label={label}
value={selected} value={selected}
disabled={disabled} disabled={disabled}
onChange={(event) => onChange(event.currentTarget.value)} onChange={(event) => onChange(event.currentTarget.value)}
+37 -4
View File
@@ -45,6 +45,7 @@ const settings: QuickPaneSettings = {
followUpModel: "full/model", followUpModel: "full/model",
instructions: "Stay read-only.", instructions: "Stay read-only.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}; };
const uiState: UiState = { const uiState: UiState = {
version: 1, version: 1,
@@ -112,6 +113,7 @@ function bridgeMock(
activeSnapshot?: ( activeSnapshot?: (
index: number, index: number,
) => RuntimeSnapshot | Promise<RuntimeSnapshot>, ) => RuntimeSnapshot | Promise<RuntimeSnapshot>,
loadedState = uiState,
) { ) {
let runtimeCount = 0; let runtimeCount = 0;
const submitted = new Set<string>(); const submitted = new Set<string>();
@@ -121,7 +123,7 @@ function bridgeMock(
command: string, command: string,
payload?: { runtimeId?: string; agentId?: string; message?: string }, payload?: { runtimeId?: string; agentId?: string; message?: string },
) => { ) => {
if (command === "load_ui_state") return Promise.resolve(uiState); if (command === "load_ui_state") return Promise.resolve(loadedState);
if (command === "create_quick_runtime") { if (command === "create_quick_runtime") {
runtimeCount += 1; runtimeCount += 1;
return Promise.resolve({ return Promise.resolve({
@@ -232,7 +234,7 @@ describe("QuickPane", () => {
expect( expect(
Array.from(pane?.children ?? []).map((element) => element.classList[0]), Array.from(pane?.children ?? []).map((element) => element.classList[0]),
).toEqual([ ).toEqual([
"quick-pane-drag-strip", "quick-pane-drag-region",
"quick-pane-toolbar", "quick-pane-toolbar",
"quick-pane-composer", "quick-pane-composer",
"quick-pane-exchange", "quick-pane-exchange",
@@ -278,15 +280,18 @@ describe("QuickPane", () => {
} }
}); });
it("starts native dragging only from the strip above toolbar controls", async () => { it("starts native dragging from whole visible region, never toolbar controls", async () => {
bridgeMock([defaultModels]); bridgeMock([defaultModels]);
const { container } = render(<QuickPane />); const { container } = render(<QuickPane />);
await preparedTrigger(); await preparedTrigger();
fireEvent.pointerDown(container.querySelector(".quick-pane-drag-strip")!); const dragRegion = container.querySelector(".quick-pane-drag-region")!;
expect(dragRegion.querySelector(".quick-pane-drag-grip")).toBeTruthy();
fireEvent.pointerDown(dragRegion);
expect(mocks.startDragging).toHaveBeenCalledTimes(1); expect(mocks.startDragging).toHaveBeenCalledTimes(1);
fireEvent.pointerDown( fireEvent.pointerDown(
screen.getByRole("button", { name: "Close Quick Pane" }), screen.getByRole("button", { name: "Close Quick Pane" }),
); );
fireEvent.pointerDown(screen.getByRole("combobox", { name: "Thinking" }));
expect(mocks.startDragging).toHaveBeenCalledTimes(1); expect(mocks.startDragging).toHaveBeenCalledTimes(1);
}); });
@@ -422,6 +427,33 @@ describe("QuickPane", () => {
expect(input.selectionDirection).toBe("none"); expect(input.selectionDirection).toBe("none");
}); });
it("applies persisted default thinking level when supported", async () => {
const thinkingModels: Model[] = [
{
provider: "provider",
id: "quick/model",
reasoning: true,
thinkingLevelMap: { high: "high" },
},
];
bridgeMock([thinkingModels], undefined, {
...uiState,
quickPane: { ...settings, defaultThinkingLevel: "high" },
});
render(<QuickPane />);
await preparedTrigger();
expect(screen.getByRole("combobox", { name: "Thinking" })).toHaveValue(
"high",
);
await ask("Inspect");
await waitFor(() =>
expect(mocks.invoke).toHaveBeenCalledWith("set_thinking_level", {
agentId: "agent-1",
level: "high",
}),
);
});
it("derives thinking choices from selected model metadata and falls back to Off", async () => { it("derives thinking choices from selected model metadata and falls back to Off", async () => {
const thinkingModels: Model[] = [ const thinkingModels: Model[] = [
{ {
@@ -448,6 +480,7 @@ describe("QuickPane", () => {
.getAllByRole("option") .getAllByRole("option")
.map((option) => option.textContent), .map((option) => option.textContent),
).toEqual(["Off", "High"]); ).toEqual(["Off", "High"]);
expect(thinking).toHaveValue("off");
await userEvent.selectOptions(thinking, "high"); await userEvent.selectOptions(thinking, "high");
await ask("Inspect"); await ask("Inspect");
await waitFor(() => await waitFor(() =>
+12 -3
View File
@@ -39,6 +39,7 @@ const defaults: QuickPaneSettings = {
instructions: instructions:
"Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.", "Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or complex work.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}; };
type Runtime = { agentId: string; runtimeId: string }; type Runtime = { agentId: string; runtimeId: string };
@@ -170,6 +171,8 @@ function settingsError(settings: QuickPaneSettings) {
settings.maximumToolCalls > 64 settings.maximumToolCalls > 64
) )
return "Tool calls must be an integer from 1 to 64."; return "Tool calls must be an integer from 1 to 64.";
if (!settings.defaultThinkingLevel)
return "Default thinking level is required.";
} }
function currentTurnAssistantText( function currentTurnAssistantText(
@@ -223,7 +226,9 @@ export default function QuickPane() {
const [requestActive, setRequestActive] = useState(false); const [requestActive, setRequestActive] = useState(false);
const [chatReplacing, setChatReplacing] = useState(false); const [chatReplacing, setChatReplacing] = useState(false);
const [promoting, setPromoting] = useState(false); const [promoting, setPromoting] = useState(false);
const [thinkingLevel, setThinkingLevel] = useState("medium"); const [thinkingLevel, setThinkingLevel] = useState(
defaults.defaultThinkingLevel,
);
const [models, setModels] = useState<Model[]>([]); const [models, setModels] = useState<Model[]>([]);
const [catalogStatus, setCatalogStatus] = useState< const [catalogStatus, setCatalogStatus] = useState<
"loading" | "ready" | "error" "loading" | "ready" | "error"
@@ -605,6 +610,7 @@ export default function QuickPane() {
const nextSettings = { ...defaults, ...state.quickPane }; const nextSettings = { ...defaults, ...state.quickPane };
settingsRef.current = nextSettings; settingsRef.current = nextSettings;
setSettings(nextSettings); setSettings(nextSettings);
setThinkingLevel(nextSettings.defaultThinkingLevel);
setSettingsLoaded(true); setSettingsLoaded(true);
const configuredModel = { const configuredModel = {
provider: nextSettings.provider, provider: nextSettings.provider,
@@ -815,6 +821,7 @@ export default function QuickPane() {
const nextSettings = { ...defaults, ...event.payload }; const nextSettings = { ...defaults, ...event.payload };
settingsRef.current = nextSettings; settingsRef.current = nextSettings;
setSettings(nextSettings); setSettings(nextSettings);
setThinkingLevel(nextSettings.defaultThinkingLevel);
const configuredModel = { const configuredModel = {
provider: nextSettings.provider, provider: nextSettings.provider,
modelId: nextSettings.model, modelId: nextSettings.model,
@@ -1129,14 +1136,16 @@ export default function QuickPane() {
return ( return (
<main className="quick-pane" ref={paneRef}> <main className="quick-pane" ref={paneRef}>
<div <div
className="quick-pane-drag-strip" className="quick-pane-drag-region"
aria-hidden="true" aria-hidden="true"
onPointerDown={() => onPointerDown={() =>
void getCurrentWindow() void getCurrentWindow()
.startDragging() .startDragging()
.catch(() => undefined) .catch(() => undefined)
} }
/> >
<span className="quick-pane-drag-grip" />
</div>
<header className="quick-pane-toolbar"> <header className="quick-pane-toolbar">
<div className="quick-pane-primary-controls"> <div className="quick-pane-primary-controls">
<ModelSelector <ModelSelector
+17 -12
View File
@@ -47,11 +47,13 @@ body {
align-items: center; align-items: center;
} }
.quick-pane-drag-strip { .quick-pane-drag-region {
position: relative; position: relative;
height: 8px; display: grid;
flex: 0 0 8px; height: 24px;
cursor: move; flex: 0 0 24px;
place-items: center;
cursor: grab;
touch-action: none; touch-action: none;
border-bottom: 1px solid #70522f; border-bottom: 1px solid #70522f;
background: linear-gradient( background: linear-gradient(
@@ -62,17 +64,20 @@ body {
); );
} }
.quick-pane-drag-strip::after { .quick-pane-drag-region:active {
position: absolute; cursor: grabbing;
top: 2px; }
left: 50%;
.quick-pane-drag-grip {
width: 44px; width: 44px;
height: 3px; height: 5px;
border-radius: 999px; border-radius: 999px;
background: #f0a347; background: repeating-linear-gradient(
90deg,
#f0a347 0 4px,
transparent 4px 7px
);
box-shadow: 0 0 8px rgba(240, 163, 71, 0.45); box-shadow: 0 0 8px rgba(240, 163, 71, 0.45);
content: "";
transform: translateX(-50%);
} }
.quick-pane-toolbar { .quick-pane-toolbar {
+1
View File
@@ -36,6 +36,7 @@ const settings: UiState = {
followUpModel: "model", followUpModel: "model",
instructions: "Stay read-only.", instructions: "Stay read-only.",
maximumToolCalls: 6, maximumToolCalls: 6,
defaultThinkingLevel: "medium",
}, },
}; };
+1
View File
@@ -239,6 +239,7 @@ export type QuickPaneSettings = {
followUpModel: string; followUpModel: string;
instructions: string; instructions: string;
maximumToolCalls: number; maximumToolCalls: number;
defaultThinkingLevel: string;
}; };
export type UiState = { export type UiState = {