feat: add Quick Prompt global hotkey popup for one-off AI questions

Add a system-wide global hotkey (Win+Shift+A / Cmd+Shift+A) that summons
a floating, frameless popup window for quick AI interactions from any app.

Main process:
- GlobalHotkeyService for registering/unregistering system-wide shortcuts
- Frameless, transparent, always-on-top BrowserWindow factory
- IPC handlers for send, discard, close, continue-in-aryx, cancel
- Session event routing from sidecar to quick prompt window
- Settings persistence for default model, hotkey, and reasoning effort

Renderer (separate lightweight entry):
- QuickPromptApp with session state, streaming, keyboard shortcuts
- QuickPromptInput with model selector trigger and cancel support
- QuickPromptResponse with streamed markdown and thinking blocks
- QuickPromptActions (Discard / Close / Continue in Aryx)
- ModelSelector dropdown with tier badges and reasoning effort
- Glass Command Bar aesthetic with animated gradient border

Settings:
- Quick Prompt section in SettingsPanel with enable toggle, hotkey
  display, default model selector, and reasoning effort picker

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
David Kaya
2026-04-13 11:23:14 +02:00
co-authored by Copilot
parent ec92b61663
commit b038019954
24 changed files with 1404 additions and 11 deletions
@@ -0,0 +1,89 @@
import electron from 'electron';
import type { BrowserWindow as BrowserWindowType } from 'electron';
import { join } from 'node:path';
import { resolveWindowIconPath } from '@main/windows/appIcon';
const { app, BrowserWindow, screen } = electron;
export function createQuickPromptWindow(): BrowserWindowType {
const window = new BrowserWindow({
width: 680,
height: 72,
show: false,
frame: false,
transparent: true,
resizable: false,
skipTaskbar: true,
alwaysOnTop: true,
maximizable: false,
minimizable: false,
fullscreenable: false,
title: 'Aryx Quick Prompt',
icon: resolveWindowIconPath({
appPath: app.getAppPath(),
platform: process.platform,
}),
webPreferences: {
preload: join(__dirname, '../preload/quickprompt.js'),
contextIsolation: true,
nodeIntegration: false,
},
});
const rendererUrl = process.env.ELECTRON_RENDERER_URL;
if (rendererUrl) {
void window.loadURL(`${rendererUrl}/quickprompt.html`);
} else {
void window.loadFile(join(__dirname, '../../dist/renderer/quickprompt.html'));
}
window.on('blur', () => {
if (window.isVisible()) {
window.webContents.send('quick-prompt:hide');
window.hide();
}
});
return window;
}
export function toggleQuickPromptWindow(window: BrowserWindowType): void {
if (window.isVisible()) {
window.webContents.send('quick-prompt:hide');
window.hide();
return;
}
centerOnActiveDisplay(window);
window.webContents.send('quick-prompt:show');
window.show();
window.focus();
}
export function showQuickPromptWindow(window: BrowserWindowType): void {
centerOnActiveDisplay(window);
window.webContents.send('quick-prompt:show');
window.show();
window.focus();
}
export function hideQuickPromptWindow(window: BrowserWindowType): void {
if (!window.isVisible()) return;
window.webContents.send('quick-prompt:hide');
window.hide();
}
function centerOnActiveDisplay(window: BrowserWindowType): void {
const cursorPoint = screen.getCursorScreenPoint();
const activeDisplay = screen.getDisplayNearestPoint(cursorPoint);
const { x, y, width, height } = activeDisplay.workArea;
const [windowWidth] = window.getSize();
const windowX = Math.round(x + (width - windowWidth) / 2);
// Position in the upper-third of the screen for command-bar feel
const windowY = Math.round(y + height * 0.25);
window.setPosition(windowX, windowY);
}