feat(quick-pane): refine spotlight workflow
This commit is contained in:
@@ -203,6 +203,9 @@ pub async fn get_workspace(socket_path: &str) -> Result<Workspace, String> {
|
||||
pub async fn get_workspace_summary(socket_path: &str) -> Result<WorkspaceSummary, String> {
|
||||
typed_request(socket_path, "get_workspace_summary", None).await
|
||||
}
|
||||
pub async fn get_model_catalog(socket_path: &str) -> Result<Value, String> {
|
||||
request(socket_path, "get_model_catalog", None, None).await
|
||||
}
|
||||
pub async fn create_session_runtime(
|
||||
socket_path: &str,
|
||||
worktree_path: &str,
|
||||
|
||||
@@ -122,6 +122,11 @@ async fn get_workspace_summary() -> Result<bridge::WorkspaceSummary, String> {
|
||||
bridge::get_workspace_summary(&socket_path()?).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn get_model_catalog() -> Result<Value, String> {
|
||||
bridge::get_model_catalog(&socket_path()?).await
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
async fn create_session_runtime(worktree_path: String) -> Result<bridge::RuntimeResult, String> {
|
||||
bridge::create_session_runtime(&socket_path()?, &worktree_path).await
|
||||
@@ -410,6 +415,7 @@ pub fn run() {
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
get_workspace,
|
||||
get_workspace_summary,
|
||||
get_model_catalog,
|
||||
create_session_runtime,
|
||||
create_quick_runtime,
|
||||
open_session_runtime,
|
||||
|
||||
@@ -1043,6 +1043,44 @@ select:focus-visible {
|
||||
color: #b9afa4;
|
||||
font-weight: 400;
|
||||
}
|
||||
.quick-pane-settings {
|
||||
display: grid;
|
||||
gap: 8px;
|
||||
max-width: 560px;
|
||||
margin-top: 20px;
|
||||
padding-top: 14px;
|
||||
border-top: 1px solid #4b4b4b;
|
||||
}
|
||||
.quick-pane-settings h2 {
|
||||
margin: 0;
|
||||
font-size: 15px;
|
||||
}
|
||||
.quick-pane-settings label {
|
||||
display: grid;
|
||||
gap: 4px;
|
||||
color: #ddd8d0;
|
||||
font-size: 12px;
|
||||
font-weight: 700;
|
||||
}
|
||||
.quick-pane-settings input,
|
||||
.quick-pane-settings textarea {
|
||||
width: 100%;
|
||||
padding: 6px 8px;
|
||||
border: 1px solid #70522f;
|
||||
border-radius: 4px;
|
||||
color: #f2ece4;
|
||||
background: #2c2a27;
|
||||
}
|
||||
.quick-pane-settings textarea {
|
||||
min-height: 90px;
|
||||
resize: vertical;
|
||||
}
|
||||
.quick-pane-settings button {
|
||||
justify-self: start;
|
||||
}
|
||||
.quick-pane-settings p {
|
||||
margin: 0;
|
||||
}
|
||||
.extension-widget {
|
||||
padding: 5px 8px;
|
||||
border-left: 3px solid #f0a347;
|
||||
|
||||
+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>
|
||||
|
||||
+103
-112
@@ -1,9 +1,12 @@
|
||||
import { invoke } from "@tauri-apps/api/core";
|
||||
import { listen } from "@tauri-apps/api/event";
|
||||
import { getCurrentWindow } from "@tauri-apps/api/window";
|
||||
import { useEffect, useMemo, useRef, useState } from "react";
|
||||
import { ExtensionDialog } from "../components/ExtensionDialog";
|
||||
import "./quick-pane.css";
|
||||
import type {
|
||||
Extension,
|
||||
Model,
|
||||
QuickPaneSettings,
|
||||
RuntimeSnapshot,
|
||||
UiState,
|
||||
@@ -61,9 +64,11 @@ function assistantText(snapshot: RuntimeSnapshot) {
|
||||
|
||||
export default function QuickPane() {
|
||||
const [settings, setSettings] = useState<QuickPaneSettings>(defaults);
|
||||
const [uiState, setUiState] = useState<UiState>();
|
||||
const [settingsLoaded, setSettingsLoaded] = useState(false);
|
||||
const [request, setRequest] = useState("");
|
||||
const [showSettings, setShowSettings] = useState(false);
|
||||
const [thinkingLevel, setThinkingLevel] = useState("medium");
|
||||
const [models, setModels] = useState<Model[]>([]);
|
||||
const [selectedModel, setSelectedModel] = useState("");
|
||||
const [status, setStatus] = useState("Loading Quick Pane settings…");
|
||||
const [error, setError] = useState<string>();
|
||||
const [runtime, setRuntime] = useState<Runtime>();
|
||||
@@ -71,16 +76,26 @@ export default function QuickPane() {
|
||||
const [escalation, setEscalation] = useState<string>();
|
||||
const [extension, setExtension] = useState<Extension>();
|
||||
const runtimeRef = useRef<Runtime | undefined>(undefined);
|
||||
const requestRef = useRef(request);
|
||||
const phaseRef = useRef<"idle" | "starting" | "running">("idle");
|
||||
const invalid = useMemo(() => settingsError(settings), [settings]);
|
||||
|
||||
const dismissPane = () => getCurrentWindow().hide();
|
||||
|
||||
useEffect(() => {
|
||||
let active = true;
|
||||
invoke<UiState>("load_ui_state")
|
||||
.then((state) => {
|
||||
if (!active) return;
|
||||
setUiState(state);
|
||||
setSettings({ ...defaults, ...state.quickPane });
|
||||
const nextSettings = { ...defaults, ...state.quickPane };
|
||||
setSettings(nextSettings);
|
||||
setSettingsLoaded(true);
|
||||
setSelectedModel(`${nextSettings.provider}/${nextSettings.model}`);
|
||||
setStatus("");
|
||||
return invoke<{ models?: Model[] }>("get_model_catalog");
|
||||
})
|
||||
.then((catalog) => {
|
||||
if (active) setModels(catalog?.models ?? []);
|
||||
})
|
||||
.catch(
|
||||
(reason) =>
|
||||
@@ -96,6 +111,40 @@ export default function QuickPane() {
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
const paneWindow = getCurrentWindow();
|
||||
const settingsListener = listen<QuickPaneSettings>(
|
||||
"quick-pane-settings-saved",
|
||||
(event) => {
|
||||
const nextSettings = { ...defaults, ...event.payload };
|
||||
setSettings(nextSettings);
|
||||
setSelectedModel(`${nextSettings.provider}/${nextSettings.model}`);
|
||||
},
|
||||
);
|
||||
const onKeyDown = (event: KeyboardEvent) => {
|
||||
if (event.key === "Escape") void dismissPane();
|
||||
};
|
||||
window.addEventListener("keydown", onKeyDown);
|
||||
const focusListener = paneWindow.onFocusChanged((event) => {
|
||||
if (
|
||||
!event.payload &&
|
||||
phaseRef.current === "idle" &&
|
||||
!requestRef.current.trim()
|
||||
)
|
||||
void dismissPane();
|
||||
});
|
||||
const closeListener = paneWindow.onCloseRequested((event) => {
|
||||
event.preventDefault();
|
||||
void dismissPane();
|
||||
});
|
||||
return () => {
|
||||
window.removeEventListener("keydown", onKeyDown);
|
||||
void settingsListener.then((unlisten) => unlisten());
|
||||
void focusListener.then((unlisten) => unlisten());
|
||||
void closeListener.then((unlisten) => unlisten());
|
||||
};
|
||||
}, []);
|
||||
|
||||
useEffect(() => {
|
||||
if (!runtime) return;
|
||||
let active = true;
|
||||
@@ -154,22 +203,6 @@ export default function QuickPane() {
|
||||
return () => window.clearTimeout(timeout);
|
||||
}, [runtime, settings.maximumSeconds]);
|
||||
|
||||
async function save() {
|
||||
if (!uiState) return;
|
||||
if (invalid) return setError(invalid);
|
||||
try {
|
||||
await invoke("save_ui_state", {
|
||||
state: { ...uiState, quickPane: settings },
|
||||
});
|
||||
setUiState({ ...uiState, quickPane: settings });
|
||||
setError(undefined);
|
||||
setShowSettings(false);
|
||||
setStatus("Quick Pane settings saved.");
|
||||
} catch (reason) {
|
||||
setError(`Could not save settings: ${String(reason)}`);
|
||||
}
|
||||
}
|
||||
|
||||
async function run() {
|
||||
if (invalid || !request.trim())
|
||||
return setError(invalid ?? "Enter a request.");
|
||||
@@ -182,6 +215,7 @@ export default function QuickPane() {
|
||||
setAnswer("");
|
||||
setEscalation(undefined);
|
||||
setExtension(undefined);
|
||||
phaseRef.current = "starting";
|
||||
setStatus("Starting advisory quick agent…");
|
||||
try {
|
||||
const result = await invoke<{ runtime: Runtime }>(
|
||||
@@ -192,20 +226,27 @@ export default function QuickPane() {
|
||||
);
|
||||
runtimeRef.current = result.runtime;
|
||||
setRuntime(result.runtime);
|
||||
const [provider, modelId] = selectedModel.split("/", 2);
|
||||
await invoke("set_model", {
|
||||
agentId: result.runtime.agentId,
|
||||
provider: settings.provider,
|
||||
modelId: settings.model,
|
||||
provider: provider || settings.provider,
|
||||
modelId: modelId || settings.model,
|
||||
});
|
||||
await invoke("set_thinking_level", {
|
||||
agentId: result.runtime.agentId,
|
||||
level: thinkingLevel,
|
||||
});
|
||||
const guardrails = `[Quick Pane advisory mode: target ${settings.maximumSeconds}s and ${settings.maximumToolCalls} read-only tool calls. Do not use subagents. Do not mutate files or system state. If request needs mutation, multi-step work, subagents, or more budget, start final response with [ESCALATE] and explain why.]\n${settings.instructions}\n\nUser request: ${request}`;
|
||||
await invoke("submit_prompt", {
|
||||
agentId: result.runtime.agentId,
|
||||
message: guardrails,
|
||||
});
|
||||
phaseRef.current = "running";
|
||||
setStatus(
|
||||
"Quick request running. Advisory limits are not trusted enforcement.",
|
||||
);
|
||||
} catch (reason) {
|
||||
phaseRef.current = "idle";
|
||||
setError(`Could not start quick request: ${String(reason)}`);
|
||||
setStatus("");
|
||||
}
|
||||
@@ -251,105 +292,55 @@ export default function QuickPane() {
|
||||
}
|
||||
|
||||
return (
|
||||
<main
|
||||
style={{
|
||||
padding: 18,
|
||||
fontFamily: "system-ui",
|
||||
color: "#e7e7e7",
|
||||
background: "#1d2027",
|
||||
minHeight: "100vh",
|
||||
}}
|
||||
>
|
||||
<main className="quick-pane">
|
||||
<header style={{ display: "flex", justifyContent: "space-between" }}>
|
||||
<strong>Quick Pane</strong>
|
||||
<span>
|
||||
<button onClick={() => setShowSettings(!showSettings)}>
|
||||
Settings
|
||||
</button>
|
||||
<button onClick={() => getCurrentWindow().hide()}>Close</button>
|
||||
</span>
|
||||
<button onClick={dismissPane}>Close</button>
|
||||
</header>
|
||||
{showSettings && (
|
||||
<section>
|
||||
<label>
|
||||
Workspace{" "}
|
||||
<input
|
||||
value={settings.workspace}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, workspace: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Quick provider/model{" "}
|
||||
<input
|
||||
value={settings.provider}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, provider: e.target.value })
|
||||
}
|
||||
/>
|
||||
<input
|
||||
value={settings.model}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, model: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Follow-up provider/model{" "}
|
||||
<input
|
||||
value={settings.followUpProvider}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, followUpProvider: e.target.value })
|
||||
}
|
||||
/>
|
||||
<input
|
||||
value={settings.followUpModel}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, followUpModel: e.target.value })
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<label>
|
||||
Seconds/tools{" "}
|
||||
<input
|
||||
type="number"
|
||||
value={settings.maximumSeconds}
|
||||
onChange={(e) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
maximumSeconds: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
<input
|
||||
type="number"
|
||||
value={settings.maximumToolCalls}
|
||||
onChange={(e) =>
|
||||
setSettings({
|
||||
...settings,
|
||||
maximumToolCalls: Number(e.target.value),
|
||||
})
|
||||
}
|
||||
/>
|
||||
</label>
|
||||
<textarea
|
||||
value={settings.instructions}
|
||||
onChange={(e) =>
|
||||
setSettings({ ...settings, instructions: e.target.value })
|
||||
}
|
||||
/>
|
||||
<button onClick={save}>Save</button>
|
||||
</section>
|
||||
)}
|
||||
<textarea
|
||||
autoFocus
|
||||
value={request}
|
||||
onChange={(e) => setRequest(e.target.value)}
|
||||
onChange={(e) => {
|
||||
requestRef.current = e.target.value;
|
||||
setRequest(e.target.value);
|
||||
}}
|
||||
placeholder="Ask a quick question or give a quick instruction"
|
||||
style={{ width: "100%", minHeight: 90, marginTop: 14 }}
|
||||
/>
|
||||
<button disabled={!uiState || !!invalid || !request.trim()} onClick={run}>
|
||||
<label>
|
||||
Model
|
||||
<select
|
||||
value={selectedModel}
|
||||
onChange={(event) => setSelectedModel(event.currentTarget.value)}
|
||||
>
|
||||
<option value={`${settings.provider}/${settings.model}`}>
|
||||
{settings.provider}/{settings.model}
|
||||
</option>
|
||||
{models.map((model) => (
|
||||
<option
|
||||
key={`${model.provider}/${model.id}`}
|
||||
value={`${model.provider}/${model.id}`}
|
||||
>
|
||||
{model.provider}/{model.id ?? model.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</label>
|
||||
<label>
|
||||
Thinking
|
||||
<select
|
||||
value={thinkingLevel}
|
||||
onChange={(event) => setThinkingLevel(event.currentTarget.value)}
|
||||
>
|
||||
<option value="low">Low</option>
|
||||
<option value="medium">Medium</option>
|
||||
<option value="high">High</option>
|
||||
</select>
|
||||
</label>
|
||||
<button
|
||||
disabled={!settingsLoaded || !!invalid || !request.trim()}
|
||||
onClick={run}
|
||||
>
|
||||
Ask
|
||||
</button>
|
||||
{(escalation || error) && (
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
:root { color-scheme: dark; }
|
||||
body { margin: 0; background: transparent; }
|
||||
.quick-pane {
|
||||
box-sizing: border-box;
|
||||
min-height: 100vh;
|
||||
padding: 18px;
|
||||
color: #f4f2ee;
|
||||
font: 14px/1.4 system-ui, sans-serif;
|
||||
background: radial-gradient(circle at top, #46494f, #23252a 62%);
|
||||
border: 1px solid rgba(255,255,255,.16);
|
||||
border-radius: 14px;
|
||||
box-shadow: 0 22px 70px rgba(0,0,0,.55);
|
||||
}
|
||||
.quick-pane header { display:flex; align-items:center; justify-content:space-between; margin-bottom:12px; color:#c9c7c2; font-size:12px; letter-spacing:.08em; text-transform:uppercase; }
|
||||
.quick-pane button, .quick-pane select { border:0; border-radius:7px; padding:7px 10px; color:inherit; background:#45484e; }
|
||||
.quick-pane textarea { box-sizing:border-box; width:100%; min-height:92px; padding:14px; border:0; border-radius:10px; outline:1px solid rgba(255,255,255,.14); color:#fff; background:rgba(0,0,0,.24); font:inherit; font-size:16px; resize:none; }
|
||||
.quick-pane textarea:focus { outline:2px solid #8aa9d6; background:rgba(0,0,0,.38); }
|
||||
.quick-pane > label { display:inline-flex; align-items:center; gap:7px; margin:10px 8px 10px 0; color:#c9c7c2; }
|
||||
.quick-pane > pre, .quick-pane > p { margin:10px 0 0; padding:10px 12px; border-radius:9px; white-space:pre-wrap; background:rgba(0,0,0,.22); }
|
||||
.quick-pane [role="alert"] { color:#ffb8bd; background:rgba(119,42,52,.42); }
|
||||
@@ -42,7 +42,10 @@ export async function loadPresentation() {
|
||||
return invoke<UiState>("load_ui_state");
|
||||
}
|
||||
export async function savePresentation(state: UiState) {
|
||||
await invoke("save_ui_state", { state });
|
||||
const current = await loadPresentation();
|
||||
await invoke("save_ui_state", {
|
||||
state: { ...state, quickPane: current.quickPane },
|
||||
});
|
||||
}
|
||||
export function legacyPreferences(storage: Pick<Storage, "getItem">) {
|
||||
const path = storage.getItem(selectedPathKey) ?? undefined;
|
||||
|
||||
Reference in New Issue
Block a user