feat(quick-pane): refine spotlight workflow

This commit is contained in:
2026-08-25 19:14:30 +02:00
parent dd23898769
commit 67063464e7
11 changed files with 418 additions and 113 deletions
+208
View File
@@ -1,4 +1,5 @@
import { invoke } from "@tauri-apps/api/core";
import { emitTo } from "@tauri-apps/api/event";
import { getCurrentWindow } from "@tauri-apps/api/window";
import { open } from "@tauri-apps/plugin-dialog";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
@@ -14,7 +15,9 @@ import type {
DirectorySession,
DirectoryWorkspace,
Model,
QuickPaneSettings,
RuntimeView,
UiState,
} from "./workspace/types";
import { useWorkspace } from "./workspace/useWorkspace";
@@ -22,6 +25,39 @@ const minimumUiScale = 0.9,
maximumUiScale = 1.4;
const clamp = (value: number) =>
Math.min(maximumUiScale, Math.max(minimumUiScale, value));
const defaultQuickPaneSettings: QuickPaneSettings = {
workspace: "",
provider: "",
model: "",
followUpProvider: "",
followUpModel: "",
instructions:
"Answer quickly. Do not use subagents. Prefer read-only inspection. Escalate mutations, multi-step work, or work likely to exceed budget.",
maximumSeconds: 60,
maximumToolCalls: 6,
};
function validateQuickPaneSettings(settings: QuickPaneSettings) {
if (!settings.workspace.startsWith("/"))
return "Workspace must be an absolute path.";
if (!settings.provider || !settings.model)
return "Quick provider and model are required.";
if (!settings.followUpProvider || !settings.followUpModel)
return "Follow-up provider and model are required.";
if (!settings.instructions.trim())
return "Supplemental instructions are required.";
if (
!Number.isInteger(settings.maximumSeconds) ||
settings.maximumSeconds < 1 ||
settings.maximumSeconds > 600
)
return "Seconds must be an integer from 1 to 600.";
if (
!Number.isInteger(settings.maximumToolCalls) ||
settings.maximumToolCalls < 1 ||
settings.maximumToolCalls > 64
)
return "Tool calls must be an integer from 1 to 64.";
}
let submissionId = 0;
type LocalOperation = {
id: number;
@@ -119,6 +155,12 @@ export default function App() {
() => new Set(),
);
const [restoreTabFocus, setRestoreTabFocus] = useState(false);
const [uiState, setUiState] = useState<UiState>();
const [quickPaneSettings, setQuickPaneSettings] = useState<QuickPaneSettings>(
defaultQuickPaneSettings,
);
const [quickPaneSettingsStatus, setQuickPaneSettingsStatus] = useState("");
const [quickPaneSettingsError, setQuickPaneSettingsError] = useState<string>();
const setRuntimeStatus = (runtimeId: string, detail: string) =>
setRuntimeStatuses((current) => ({ ...current, [runtimeId]: detail }));
const clearRuntimeStatus = (runtimeId: string) =>
@@ -161,6 +203,28 @@ export default function App() {
};
}, []);
useEffect(() => {
let active = true;
void invoke<UiState>("load_ui_state")
.then((loaded) => {
if (!active) return;
setUiState(loaded);
setQuickPaneSettings({
...defaultQuickPaneSettings,
...loaded.quickPane,
});
})
.catch((error) => {
if (active)
setQuickPaneSettingsError(
`Could not load Quick Pane settings: ${String(error)}`,
);
});
return () => {
active = false;
};
}, []);
const selectedRuntimeId = selected?.summary.runtimeId;
useEffect(() => {
if (!selectedRuntimeId) return;
@@ -181,6 +245,32 @@ export default function App() {
});
}, [state.runtimesById]);
async function saveQuickPaneSettings() {
const validationError = validateQuickPaneSettings(quickPaneSettings);
if (validationError) {
setQuickPaneSettingsError(validationError);
return;
}
if (!uiState) return;
try {
const current = await invoke<UiState>("load_ui_state");
const next = { ...current, quickPane: quickPaneSettings };
await invoke("save_ui_state", { state: next });
void emitTo(
"quick-pane",
"quick-pane-settings-saved",
quickPaneSettings,
).catch(() => undefined);
setUiState(next);
setQuickPaneSettingsError(undefined);
setQuickPaneSettingsStatus("Quick Pane settings saved.");
} catch (error) {
setQuickPaneSettingsError(
`Could not save Quick Pane settings: ${String(error)}`,
);
}
}
async function createRuntime(path = state.selectedDirectoryPath) {
if (!path || !mountedRef.current) return;
const operationId = startOperation("creating", "Starting a new Pi session…");
@@ -763,6 +853,124 @@ export default function App() {
Used by Ctrl+Super+Space. Only currently open directories are listed.
</small>
</label>
<form
className="quick-pane-settings"
onSubmit={(event) => {
event.preventDefault();
void saveQuickPaneSettings();
}}
>
<h2>Quick Pane</h2>
<label>
Workspace
<input
value={quickPaneSettings.workspace}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
workspace: event.currentTarget.value,
})
}
/>
</label>
<label>
Quick provider
<input
value={quickPaneSettings.provider}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
provider: event.currentTarget.value,
})
}
/>
</label>
<label>
Quick model
<input
value={quickPaneSettings.model}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
model: event.currentTarget.value,
})
}
/>
</label>
<label>
Follow-up provider
<input
value={quickPaneSettings.followUpProvider}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
followUpProvider: event.currentTarget.value,
})
}
/>
</label>
<label>
Follow-up model
<input
value={quickPaneSettings.followUpModel}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
followUpModel: event.currentTarget.value,
})
}
/>
</label>
<label>
Maximum seconds
<input
type="number"
min="1"
max="600"
value={quickPaneSettings.maximumSeconds}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
maximumSeconds: Number(event.currentTarget.value),
})
}
/>
</label>
<label>
Maximum tool calls
<input
type="number"
min="1"
max="64"
value={quickPaneSettings.maximumToolCalls}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
maximumToolCalls: Number(event.currentTarget.value),
})
}
/>
</label>
<label>
Supplemental instructions
<textarea
value={quickPaneSettings.instructions}
onChange={(event) =>
setQuickPaneSettings({
...quickPaneSettings,
instructions: event.currentTarget.value,
})
}
/>
</label>
<button type="submit" disabled={!uiState}>
Save Quick Pane settings
</button>
{quickPaneSettingsStatus && (
<p role="status">{quickPaneSettingsStatus}</p>
)}
{quickPaneSettingsError && <p role="alert">{quickPaneSettingsError}</p>}
</form>
{selected ? (
<>
<p>{selected.summary.worktreePath}</p>