feat(quick-pane): refine spotlight workflow
This commit is contained in:
+208
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user