feat: cleanup + screenshot buttons in sidebar inspector toolbar

Two action buttons in the inspector toolbar:
- Cleanup (🧹): POSTs cleanup --all to server, shows spinner, chat
  notification on success, resets inspector state (element may be removed)
- Screenshot (📸): POSTs screenshot to server, shows spinner, chat
  notification with saved file path

Shared infrastructure:
- .inspector-action-btn CSS with loading spinner via ::after pseudo-element
- chat-notification type in addChatEntry() for system messages
- package.json version bump to 0.13.9.0

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
Garry Tan
2026-03-29 23:11:20 -07:00
parent 8d656285a6
commit 7e31568c78
4 changed files with 139 additions and 1 deletions
+78
View File
@@ -135,6 +135,16 @@ function addChatEntry(entry) {
return;
}
// System notifications (cleanup, screenshot, errors)
if (entry.type === 'notification') {
const note = document.createElement('div');
note.className = 'chat-notification';
note.textContent = entry.message;
chatMessages.appendChild(note);
note.scrollIntoView({ behavior: 'smooth', block: 'end' });
return;
}
// Agent streaming events
if (entry.role === 'agent') {
handleAgentEvent(entry);
@@ -1139,6 +1149,74 @@ inspectorSendBtn.addEventListener('click', () => {
chrome.runtime.sendMessage({ type: 'sidebar-command', message });
});
// ─── Cleanup Button ─────────────────────────────────────────────
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');
}
});
}
// ─── Section Toggles ────────────────────────────────────────────
document.querySelectorAll('.inspector-section-toggle').forEach(toggle => {