mirror of
https://github.com/openswarm-ai/openswarm.git
synced 2026-08-18 02:35:41 +02:00
[eric] experimental: quick pill overlay unwired and parked in .claude/slime-parked
This commit is contained in:
@@ -100,7 +100,7 @@ class AppSettings(BaseModel):
|
||||
dictation_sounds: bool = True
|
||||
dictation_haptics: bool = True
|
||||
# 0..1; the cue loudness Eric tuned by ear rides here instead of a hardcode.
|
||||
dictation_sound_volume: float = 0.35
|
||||
dictation_sound_volume: float = 0.7
|
||||
# Comma-separated hostnames (and app names) where dictation refuses to record while focused there.
|
||||
dictation_disabled_surfaces: str = ""
|
||||
# Off = the memory block never reaches any model; the facts stay on disk untouched.
|
||||
@@ -120,8 +120,6 @@ class AppSettings(BaseModel):
|
||||
auto_reveal_sub_agents: bool = True
|
||||
dev_mode: bool = False
|
||||
allow_experimental_updates: bool = False
|
||||
# Quick pill overlay (Alt+Space): a floating command pill with the slime idle skin. Default off.
|
||||
overlay_pill_enabled: bool = False
|
||||
# Notification toggles read by the renderer before firing native notifications.
|
||||
notify_agent_completion: bool = True
|
||||
notify_workflow_runs: bool = True
|
||||
|
||||
@@ -1972,17 +1972,6 @@ app.whenReady().then(async () => {
|
||||
// (module missing, or macOS without the Accessibility grant). Tiers live in voiceHotkey.js.
|
||||
installVoiceHotkey(() => mainWindow);
|
||||
|
||||
// Quick pill overlay (Alt+Space, Settings-gated, default off): submitted text lands in the main
|
||||
// renderer as a prefilled composer draft through the same seam dictation uses.
|
||||
const { initOverlayPill } = require('./overlayPill');
|
||||
initOverlayPill((text) => {
|
||||
try {
|
||||
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||
mainWindow.show();
|
||||
mainWindow.webContents.send('overlay:submit-text', text);
|
||||
}
|
||||
} catch (_) {}
|
||||
});
|
||||
|
||||
// PASSKEY SPIKE (macOS only): turn on the Secure-Enclave/Touch ID WebAuthn authenticator that Electron 42 added. Without this, isUserVerifyingPlatformAuthenticatorAvailable() is hardwired false (why the old reject-shim existed). keychainAccessGroup MUST match the keychain-access-groups entitlement (Y26NUZH4NG.<bundle>.webauthn) or this throws. Windows has no equivalent, so the reject-shim still runs there.
|
||||
if (process.platform === 'darwin' && typeof app.configureWebAuthn === 'function') {
|
||||
@@ -3299,26 +3288,6 @@ ipcMain.handle('get-webview-preload-path', () => {
|
||||
return `file://${path.join(__dirname, 'webview-preload.js')}`;
|
||||
});
|
||||
|
||||
// Quick pill overlay: the renderer pushes the Settings toggle here; show is exposed so the
|
||||
// renderer (and tests) can summon the pill without the OS-level hotkey.
|
||||
ipcMain.handle('overlay:set-enabled', (_e, enabled) => {
|
||||
try {
|
||||
const { setOverlayEnabled } = require('./overlayPill');
|
||||
setOverlayEnabled(Boolean(enabled));
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err && err.message };
|
||||
}
|
||||
});
|
||||
ipcMain.handle('overlay:show', () => {
|
||||
try {
|
||||
const { showOverlay } = require('./overlayPill');
|
||||
showOverlay();
|
||||
return { ok: true };
|
||||
} catch (err) {
|
||||
return { ok: false, error: err && err.message };
|
||||
}
|
||||
});
|
||||
|
||||
// Reveal a user-attached composer file in Finder/Explorer. Reveal-only on an existing path:
|
||||
// showItemInFolder never opens or executes the file, so the worst misuse is popping a Finder window.
|
||||
|
||||
@@ -1,10 +0,0 @@
|
||||
// The pill page's only powers: hand its text up, ask to be hidden, learn when it was shown.
|
||||
'use strict';
|
||||
|
||||
const { contextBridge, ipcRenderer } = require('electron');
|
||||
|
||||
contextBridge.exposeInMainWorld('overlay', {
|
||||
submit: (text) => ipcRenderer.send('overlay:submit', String(text || '')),
|
||||
dismiss: () => ipcRenderer.send('overlay:dismiss'),
|
||||
onShown: (cb) => { ipcRenderer.on('overlay:shown', () => cb()); },
|
||||
});
|
||||
@@ -1,130 +0,0 @@
|
||||
// The quick pill: an Arc/clui-style floating command pill on a global hotkey, with the slime as its
|
||||
// idle skin. macOS NSPanel semantics via type:'panel' (nonactivating), so the pill takes KEYBOARD
|
||||
// focus while the app the user was in STAYS frontmost; no dock bounce, no space switch. The panel is
|
||||
// created hidden at enable time so hotkey-to-visible is a show(), not a window boot.
|
||||
|
||||
'use strict';
|
||||
|
||||
const path = require('path');
|
||||
const { BrowserWindow, globalShortcut, ipcMain, screen } = require('electron');
|
||||
|
||||
const OVERLAY_HOTKEY = 'Alt+Space';
|
||||
const PANEL_W = 560;
|
||||
const PANEL_H = 120;
|
||||
|
||||
let p_panel = null;
|
||||
let p_enabled = false;
|
||||
let p_onSubmit = null;
|
||||
|
||||
function p_html() {
|
||||
// Self-contained page: the slime idles beside a bare input. No remote content, no node access.
|
||||
return `<!doctype html><html><head><meta charset="utf-8"><style>
|
||||
html,body{margin:0;background:transparent;overflow:hidden;font-family:-apple-system,BlinkMacSystemFont,sans-serif;-webkit-user-select:none}
|
||||
.pill{display:flex;align-items:center;gap:12px;margin:10px;padding:14px 18px;border-radius:999px;
|
||||
background:rgba(28,25,33,0.92);backdrop-filter:blur(24px);border:1px solid rgba(255,255,255,0.14);
|
||||
box-shadow:0 18px 44px rgba(0,0,0,0.45)}
|
||||
.slime{width:34px;height:30px;position:relative;flex-shrink:0;animation:idle 2.6s ease-in-out infinite;transform-origin:50% 100%}
|
||||
.slime .body{position:absolute;inset:0;background:linear-gradient(180deg,#9ee87c,#5cc94a);border-radius:52% 52% 46% 46%/60% 60% 42% 42%;box-shadow:inset 0 -4px 8px rgba(0,0,0,0.18)}
|
||||
.slime .eye{position:absolute;top:11px;width:5px;height:7px;background:#1c2416;border-radius:50%;animation:blink 4.2s infinite}
|
||||
.slime .eye.l{left:9px}.slime .eye.r{right:9px}
|
||||
@keyframes idle{0%,100%{transform:scaleY(1) scaleX(1)}50%{transform:scaleY(0.92) scaleX(1.05)}}
|
||||
@keyframes blink{0%,92%,100%{transform:scaleY(1)}95%{transform:scaleY(0.1)}}
|
||||
input{flex:1;border:0;outline:0;background:transparent;color:rgba(255,255,255,0.94);font-size:17px}
|
||||
input::placeholder{color:rgba(255,255,255,0.4)}
|
||||
@media (prefers-reduced-motion: reduce){.slime{animation:none}.slime .eye{animation:none}}
|
||||
</style></head><body>
|
||||
<div class="pill"><div class="slime"><div class="body"></div><div class="eye l"></div><div class="eye r"></div></div>
|
||||
<input id="q" placeholder="Ask OpenSwarm anything…" autofocus></div>
|
||||
<script>
|
||||
const q = document.getElementById('q');
|
||||
q.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter' && q.value.trim()) { window.overlay.submit(q.value.trim()); q.value = ''; }
|
||||
if (e.key === 'Escape') { q.value = ''; window.overlay.dismiss(); }
|
||||
});
|
||||
window.overlay.onShown(() => { q.value = ''; q.focus(); });
|
||||
</script></body></html>`;
|
||||
}
|
||||
|
||||
function p_createPanel() {
|
||||
const panel = new BrowserWindow({
|
||||
width: PANEL_W,
|
||||
height: PANEL_H,
|
||||
show: false,
|
||||
frame: false,
|
||||
transparent: true,
|
||||
resizable: false,
|
||||
movable: true,
|
||||
minimizable: false,
|
||||
maximizable: false,
|
||||
fullscreenable: false,
|
||||
skipTaskbar: true,
|
||||
alwaysOnTop: true,
|
||||
hasShadow: false,
|
||||
// The whole trick: a nonactivating NSPanel takes key WITHOUT activating our app or deactivating theirs.
|
||||
type: 'panel',
|
||||
webPreferences: {
|
||||
preload: path.join(__dirname, 'overlay-preload.js'),
|
||||
contextIsolation: true,
|
||||
nodeIntegration: false,
|
||||
sandbox: true,
|
||||
},
|
||||
});
|
||||
panel.setAlwaysOnTop(true, 'screen-saver');
|
||||
// Re-asserted on every show too: macOS drops this across space changes.
|
||||
panel.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||
panel.loadURL('data:text/html;charset=utf-8,' + encodeURIComponent(p_html()));
|
||||
panel.on('blur', () => { try { panel.hide(); } catch (_) {} });
|
||||
panel.on('closed', () => { p_panel = null; });
|
||||
return panel;
|
||||
}
|
||||
|
||||
function showOverlay() {
|
||||
if (!p_enabled) return;
|
||||
if (!p_panel || p_panel.isDestroyed()) p_panel = p_createPanel();
|
||||
const display = screen.getDisplayNearestPoint(screen.getCursorScreenPoint());
|
||||
const { x, y, width } = display.workArea;
|
||||
p_panel.setPosition(Math.round(x + (width - PANEL_W) / 2), Math.round(y + Math.max(80, display.workArea.height * 0.18)));
|
||||
p_panel.setVisibleOnAllWorkspaces(true, { visibleOnFullScreen: true });
|
||||
p_panel.show();
|
||||
try { p_panel.webContents.send('overlay:shown'); } catch (_) {}
|
||||
try { p_panel.webContents.focus(); } catch (_) {}
|
||||
}
|
||||
|
||||
function hideOverlay() {
|
||||
if (p_panel && !p_panel.isDestroyed()) p_panel.hide();
|
||||
}
|
||||
|
||||
function toggleOverlay() {
|
||||
if (p_panel && !p_panel.isDestroyed() && p_panel.isVisible()) hideOverlay();
|
||||
else showOverlay();
|
||||
}
|
||||
|
||||
/** Wire the hotkey + IPC once; enable/disable flips registration. onSubmit receives the typed text. */
|
||||
function initOverlayPill(onSubmit) {
|
||||
p_onSubmit = onSubmit;
|
||||
ipcMain.on('overlay:submit', (_e, text) => {
|
||||
hideOverlay();
|
||||
try { if (typeof text === 'string' && text.trim() && p_onSubmit) p_onSubmit(text.trim().slice(0, 4000)); } catch (_) {}
|
||||
});
|
||||
ipcMain.on('overlay:dismiss', () => hideOverlay());
|
||||
}
|
||||
|
||||
function setOverlayEnabled(enabled) {
|
||||
const next = Boolean(enabled);
|
||||
if (next === p_enabled) return;
|
||||
p_enabled = next;
|
||||
if (next) {
|
||||
try {
|
||||
globalShortcut.register(OVERLAY_HOTKEY, toggleOverlay);
|
||||
} catch (err) {
|
||||
console.warn('[overlay] hotkey register failed:', err && err.message);
|
||||
}
|
||||
// Pre-create hidden so the first hotkey is a show(), not a window boot.
|
||||
if (!p_panel || p_panel.isDestroyed()) p_panel = p_createPanel();
|
||||
} else {
|
||||
try { globalShortcut.unregister(OVERLAY_HOTKEY); } catch (_) {}
|
||||
if (p_panel && !p_panel.isDestroyed()) { p_panel.destroy(); p_panel = null; }
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { initOverlayPill, setOverlayEnabled, showOverlay, hideOverlay };
|
||||
@@ -92,14 +92,6 @@ contextBridge.exposeInMainWorld('openswarm', {
|
||||
revealBundle: (folderPath) => ipcRenderer.invoke('help:reveal-bundle', folderPath),
|
||||
// Reveal a user-attached file in Finder/Explorer (reveal-only; main checks existence).
|
||||
revealPath: (filePath) => ipcRenderer.invoke('files:reveal', filePath),
|
||||
// Quick pill overlay: toggle the global hotkey, summon for tests, receive submitted text.
|
||||
setOverlayEnabled: (enabled) => ipcRenderer.invoke('overlay:set-enabled', Boolean(enabled)),
|
||||
showOverlay: () => ipcRenderer.invoke('overlay:show'),
|
||||
onOverlaySubmit: (cb) => {
|
||||
const listener = (_event, text) => cb(String(text || ''));
|
||||
ipcRenderer.on('overlay:submit-text', listener);
|
||||
return () => ipcRenderer.removeListener('overlay:submit-text', listener);
|
||||
},
|
||||
// Cmd/Ctrl+1..9: focus the Nth dock tile (0-based index arrives here).
|
||||
onDockShortcut: (cb) => {
|
||||
const listener = (_event, index) => cb(index);
|
||||
|
||||
@@ -211,7 +211,6 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) =
|
||||
const loaded = useAppSelector((s) => s.settings.loaded);
|
||||
const settled = useAppSelector((s) => s.settings.settled);
|
||||
const allowExperimentalUpdates = useAppSelector((s) => s.settings.data.allow_experimental_updates);
|
||||
const overlayPillEnabled = useAppSelector((s) => Boolean((s.settings.data as { overlay_pill_enabled?: boolean }).overlay_pill_enabled));
|
||||
useEffect(() => {
|
||||
dispatch(fetchSettings());
|
||||
dispatch(fetchModels());
|
||||
@@ -278,17 +277,6 @@ const SettingsLoader: React.FC<{ children: React.ReactNode }> = ({ children }) =
|
||||
if (!loaded) return;
|
||||
(window as any).openswarm?.setAllowPrerelease?.(allowExperimentalUpdates);
|
||||
}, [loaded, allowExperimentalUpdates]);
|
||||
useEffect(() => {
|
||||
if (!loaded) return;
|
||||
(window as any).openswarm?.setOverlayEnabled?.(overlayPillEnabled);
|
||||
}, [loaded, overlayPillEnabled]);
|
||||
useEffect(() => {
|
||||
// Overlay submissions land as a prefilled composer draft, the same seam dictation's no-cursor fallback uses.
|
||||
const off = (window as any).openswarm?.onOverlaySubmit?.((text: string) => {
|
||||
window.dispatchEvent(new CustomEvent('openswarm:dictation-fallback', { detail: { text } }));
|
||||
});
|
||||
return () => { off?.(); };
|
||||
}, []);
|
||||
// Hold paint until the settings fetch SETTLES so the user's theme renders first; Electron's ready-to-show relies on this. Settling, not succeeding: a backend that never answers used to leave a blank window forever.
|
||||
if (!settled) return null;
|
||||
return <>{children}</>;
|
||||
|
||||
@@ -50,18 +50,6 @@ const GeneralAdvanced: React.FC<{
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={inlineRowSx} {...settingSelectAttrs('overlay_pill_enabled', 'Quick pill overlay', 'Advanced', 'Alt+Space summons a floating ask-anything pill over any app.')}>
|
||||
<Box sx={{ mr: 3 }}>
|
||||
<Typography sx={labelSx}>Quick pill overlay</Typography>
|
||||
<Typography sx={descSx}>Alt+Space summons a floating pill over whatever you are doing; what you type lands in the composer. The slime keeps you company.</Typography>
|
||||
</Box>
|
||||
<Switch
|
||||
checked={form.overlay_pill_enabled === true}
|
||||
onChange={(e) => setForm({ ...form, overlay_pill_enabled: e.target.checked })}
|
||||
sx={switchSx}
|
||||
/>
|
||||
</Box>
|
||||
|
||||
<Box sx={inlineRowLastSx} {...settingSelectAttrs('allow_experimental_updates', 'Experimental updates', 'Advanced', 'Receive pre-release builds with new features earlier.')}>
|
||||
<Box sx={{ mr: 3 }}>
|
||||
<Typography sx={labelSx}>Experimental updates</Typography>
|
||||
@@ -127,6 +115,7 @@ const GeneralAdvanced: React.FC<{
|
||||
Restart tour
|
||||
</Button>
|
||||
</Box>
|
||||
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -53,7 +53,6 @@ export interface AppSettings {
|
||||
notify_agent_completion?: boolean;
|
||||
notify_workflow_runs?: boolean;
|
||||
allow_experimental_updates: boolean;
|
||||
overlay_pill_enabled: boolean;
|
||||
/** Managed subscription state; surfaces only when user has subscribed via cloud. */
|
||||
connection_mode?: 'own_key' | 'openswarm-pro' | 'free-trial';
|
||||
openswarm_bearer_token?: string | null;
|
||||
@@ -174,7 +173,7 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
dictation_sounds: true,
|
||||
memory_enabled: true,
|
||||
dictation_haptics: true,
|
||||
dictation_sound_volume: 0.35,
|
||||
dictation_sound_volume: 0.7,
|
||||
dictation_disabled_surfaces: '',
|
||||
anthropic_api_key: null,
|
||||
browser_homepage: 'https://duckduckgo.com',
|
||||
@@ -184,7 +183,6 @@ export const DEFAULT_SETTINGS: AppSettings = {
|
||||
auto_reveal_sub_agents: true,
|
||||
dev_mode: false,
|
||||
allow_experimental_updates: false,
|
||||
overlay_pill_enabled: false,
|
||||
};
|
||||
|
||||
const initialState: SettingsState = {
|
||||
|
||||
Reference in New Issue
Block a user