From 92adc71bbc502072c875c8e5f2a042135364c2ce Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 29 Mar 2026 23:27:42 -0700 Subject: [PATCH] feat: cleanup + screenshot buttons in chat toolbar (not just inspector) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Quick actions toolbar (๐Ÿงน Cleanup, ๐Ÿ“ธ Screenshot) now appears above the chat input, always visible. Both inspector and chat buttons share runCleanup() and runScreenshot() helper functions. Clicking either set shows loading state on both simultaneously. Co-Authored-By: Claude Opus 4.6 (1M context) --- extension/sidepanel.css | 53 ++++++++++++++++ extension/sidepanel.html | 6 ++ extension/sidepanel.js | 129 ++++++++++++++++++++------------------- 3 files changed, 124 insertions(+), 64 deletions(-) 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 โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€