diff --git a/extension/sidepanel.css b/extension/sidepanel.css index 9f33ee28..8c01d41e 100644 --- a/extension/sidepanel.css +++ b/extension/sidepanel.css @@ -590,6 +590,59 @@ body::after { } /* ─── Command Bar ─────────────────────────────────────── */ +/* ─── Quick Actions Toolbar ─────────────────────────────── */ + +.quick-actions { + display: flex; + gap: 6px; + padding: 4px 8px; + background: var(--bg-surface); + border-top: 1px solid var(--border-subtle); + flex-shrink: 0; +} + +.quick-action-btn { + display: flex; + align-items: center; + gap: 4px; + height: 26px; + padding: 0 10px; + background: none; + border: 1px solid var(--zinc-600); + border-radius: var(--radius-sm); + color: var(--text-label); + font-family: var(--font-system); + font-size: 11px; + cursor: pointer; + transition: all 150ms; +} + +.quick-action-btn:hover { + background: rgba(255, 255, 255, 0.05); + color: var(--text-body); + border-color: var(--zinc-400); +} + +.quick-action-btn:active { + transform: scale(0.96); +} + +.quick-action-btn.loading { + pointer-events: none; + opacity: 0.5; +} + +.quick-action-btn.loading::after { + content: ''; + display: inline-block; + width: 10px; + height: 10px; + border: 2px solid var(--zinc-600); + border-top-color: var(--amber-400); + border-radius: 50%; + animation: spin 0.6s linear infinite; +} + .command-bar { display: flex; align-items: center; diff --git a/extension/sidepanel.html b/extension/sidepanel.html index 5d82c2b8..c51f7df2 100644 --- a/extension/sidepanel.html +++ b/extension/sidepanel.html @@ -136,6 +136,12 @@ Browser co-pilot — controls this browser, reports back to your workspace + +
+ + +
+
diff --git a/extension/sidepanel.js b/extension/sidepanel.js index f1365c88..81438ff2 100644 --- a/extension/sidepanel.js +++ b/extension/sidepanel.js @@ -1149,73 +1149,74 @@ inspectorSendBtn.addEventListener('click', () => { chrome.runtime.sendMessage({ type: 'sidebar-command', message }); }); -// ─── Cleanup Button ───────────────────────────────────────────── +// ─── Quick Action Helpers (shared between chat toolbar + inspector) ── + +async function runCleanup(...buttons) { + if (!serverUrl || !serverToken) { + addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); + return; + } + buttons.forEach(b => b?.classList.add('loading')); + try { + const resp = await fetch(`${serverUrl}/command`, { + method: 'POST', + headers: { ...authHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ command: 'cleanup', args: ['--all'] }), + signal: AbortSignal.timeout(15000), + }); + const text = await resp.text(); + if (resp.ok) { + addChatEntry({ type: 'notification', message: text || 'Page cleaned up' }); + if (typeof inspectorShowEmpty === 'function') inspectorShowEmpty(); + } else { + const err = JSON.parse(text).error || 'Cleanup failed'; + addChatEntry({ type: 'notification', message: 'Error: ' + err }); + } + } catch (err) { + addChatEntry({ type: 'notification', message: 'Cleanup failed: ' + err.message }); + } finally { + buttons.forEach(b => b?.classList.remove('loading')); + } +} + +async function runScreenshot(...buttons) { + if (!serverUrl || !serverToken) { + addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); + return; + } + buttons.forEach(b => b?.classList.add('loading')); + try { + const resp = await fetch(`${serverUrl}/command`, { + method: 'POST', + headers: { ...authHeaders(), 'Content-Type': 'application/json' }, + body: JSON.stringify({ command: 'screenshot', args: [] }), + signal: AbortSignal.timeout(15000), + }); + const text = await resp.text(); + if (resp.ok) { + addChatEntry({ type: 'notification', message: text || 'Screenshot saved' }); + } else { + const err = JSON.parse(text).error || 'Screenshot failed'; + addChatEntry({ type: 'notification', message: 'Error: ' + err }); + } + } catch (err) { + addChatEntry({ type: 'notification', message: 'Screenshot failed: ' + err.message }); + } finally { + buttons.forEach(b => b?.classList.remove('loading')); + } +} + +// ─── Wire up all cleanup/screenshot buttons (inspector + chat toolbar) ── const inspectorCleanupBtn = document.getElementById('inspector-cleanup-btn'); -if (inspectorCleanupBtn) { - inspectorCleanupBtn.addEventListener('click', async () => { - if (inspectorCleanupBtn.classList.contains('loading')) return; - if (!serverUrl || !serverToken) { - addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); - return; - } - inspectorCleanupBtn.classList.add('loading'); - try { - const resp = await fetch(`${serverUrl}/command`, { - method: 'POST', - headers: { ...authHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify({ command: 'cleanup', args: ['--all'] }), - signal: AbortSignal.timeout(15000), - }); - const text = await resp.text(); - if (resp.ok) { - addChatEntry({ type: 'notification', message: text || 'Page cleaned up' }); - // Reset inspector — selected element may have been removed - inspectorShowEmpty(); - } else { - const err = JSON.parse(text).error || 'Cleanup failed'; - addChatEntry({ type: 'notification', message: 'Error: ' + err }); - } - } catch (err) { - addChatEntry({ type: 'notification', message: 'Cleanup failed: ' + err.message }); - } finally { - inspectorCleanupBtn.classList.remove('loading'); - } - }); -} - -// ─── Screenshot Button ────────────────────────────────────────── - const inspectorScreenshotBtn = document.getElementById('inspector-screenshot-btn'); -if (inspectorScreenshotBtn) { - inspectorScreenshotBtn.addEventListener('click', async () => { - if (inspectorScreenshotBtn.classList.contains('loading')) return; - if (!serverUrl || !serverToken) { - addChatEntry({ type: 'notification', message: 'Not connected to browse server' }); - return; - } - inspectorScreenshotBtn.classList.add('loading'); - try { - const resp = await fetch(`${serverUrl}/command`, { - method: 'POST', - headers: { ...authHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify({ command: 'screenshot', args: [] }), - signal: AbortSignal.timeout(15000), - }); - const text = await resp.text(); - if (resp.ok) { - addChatEntry({ type: 'notification', message: text || 'Screenshot saved' }); - } else { - const err = JSON.parse(text).error || 'Screenshot failed'; - addChatEntry({ type: 'notification', message: 'Error: ' + err }); - } - } catch (err) { - addChatEntry({ type: 'notification', message: 'Screenshot failed: ' + err.message }); - } finally { - inspectorScreenshotBtn.classList.remove('loading'); - } - }); -} +const chatCleanupBtn = document.getElementById('chat-cleanup-btn'); +const chatScreenshotBtn = document.getElementById('chat-screenshot-btn'); + +if (inspectorCleanupBtn) inspectorCleanupBtn.addEventListener('click', () => runCleanup(inspectorCleanupBtn, chatCleanupBtn)); +if (inspectorScreenshotBtn) inspectorScreenshotBtn.addEventListener('click', () => runScreenshot(inspectorScreenshotBtn, chatScreenshotBtn)); +if (chatCleanupBtn) chatCleanupBtn.addEventListener('click', () => runCleanup(chatCleanupBtn, inspectorCleanupBtn)); +if (chatScreenshotBtn) chatScreenshotBtn.addEventListener('click', () => runScreenshot(chatScreenshotBtn, inspectorScreenshotBtn)); // ─── Section Toggles ────────────────────────────────────────────