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
+2
View File
@@ -36,6 +36,8 @@ const api: ElectronApi = {
setNotificationsEnabled: (enabled) => ipcRenderer.invoke(ipcChannels.setNotificationsEnabled, enabled),
setMinimizeToTray: (enabled) => ipcRenderer.invoke(ipcChannels.setMinimizeToTray, enabled),
setGitAutoRefreshEnabled: (enabled) => ipcRenderer.invoke(ipcChannels.setGitAutoRefreshEnabled, enabled),
getQuickPromptSettings: () => ipcRenderer.invoke(ipcChannels.quickPromptGetSettings),
setQuickPromptSettings: (settings) => ipcRenderer.invoke(ipcChannels.quickPromptSetSettings, settings),
checkForUpdates: () => ipcRenderer.invoke(ipcChannels.checkForUpdates),
installUpdate: () => ipcRenderer.invoke(ipcChannels.installUpdate),
saveMcpServer: (input) => ipcRenderer.invoke(ipcChannels.saveMcpServer, input),
+35
View File
@@ -0,0 +1,35 @@
import electron from 'electron';
import { ipcChannels } from '@shared/contracts/channels';
import type { QuickPromptElectronApi } from '@shared/contracts/ipc';
const { contextBridge, ipcRenderer } = electron;
const api: QuickPromptElectronApi = {
send: (input) => ipcRenderer.invoke(ipcChannels.quickPromptSend, input),
discard: () => ipcRenderer.invoke(ipcChannels.quickPromptDiscard),
close: () => ipcRenderer.invoke(ipcChannels.quickPromptClose),
continueInAryx: () => ipcRenderer.invoke(ipcChannels.quickPromptContinueInAryx),
cancelTurn: () => ipcRenderer.invoke(ipcChannels.quickPromptCancelTurn),
getCapabilities: () => ipcRenderer.invoke(ipcChannels.quickPromptGetCapabilities),
setSettings: (settings) => ipcRenderer.invoke(ipcChannels.quickPromptSetSettings, settings),
onSessionEvent: (listener) => {
const handler = (_event: Electron.IpcRendererEvent, sessionEvent: Parameters<typeof listener>[0]) =>
listener(sessionEvent);
ipcRenderer.on(ipcChannels.quickPromptSessionEvent, handler);
return () => ipcRenderer.off(ipcChannels.quickPromptSessionEvent, handler);
},
onShow: (listener) => {
const handler = () => listener();
ipcRenderer.on(ipcChannels.quickPromptShow, handler);
return () => ipcRenderer.off(ipcChannels.quickPromptShow, handler);
},
onHide: (listener) => {
const handler = () => listener();
ipcRenderer.on(ipcChannels.quickPromptHide, handler);
return () => ipcRenderer.off(ipcChannels.quickPromptHide, handler);
},
};
contextBridge.exposeInMainWorld('quickPromptApi', api);