mirror of https://github.com/garrytan/gstack.git
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:
parent
8d656285a6
commit
7e31568c78
|
|
@ -221,6 +221,13 @@ body::after {
|
||||||
color: #000;
|
color: #000;
|
||||||
border-bottom-right-radius: var(--radius-sm);
|
border-bottom-right-radius: var(--radius-sm);
|
||||||
}
|
}
|
||||||
|
.chat-notification {
|
||||||
|
text-align: center;
|
||||||
|
font-size: 11px;
|
||||||
|
color: var(--text-meta);
|
||||||
|
padding: 4px 12px;
|
||||||
|
font-family: var(--font-mono);
|
||||||
|
}
|
||||||
.chat-bubble.assistant {
|
.chat-bubble.assistant {
|
||||||
align-self: flex-start;
|
align-self: flex-start;
|
||||||
background: var(--bg-surface);
|
background: var(--bg-surface);
|
||||||
|
|
@ -808,6 +815,56 @@ footer {
|
||||||
line-height: 1;
|
line-height: 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* ─── Action Buttons (Cleanup, Screenshot) ─────────────────── */
|
||||||
|
|
||||||
|
.inspector-action-btn {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 28px;
|
||||||
|
width: 28px;
|
||||||
|
padding: 0;
|
||||||
|
background: none;
|
||||||
|
border: 1px solid var(--zinc-600);
|
||||||
|
border-radius: var(--radius-sm);
|
||||||
|
color: var(--text-label);
|
||||||
|
font-size: 14px;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: all 150ms;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-action-btn:hover {
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
color: var(--text-body);
|
||||||
|
border-color: var(--zinc-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-action-btn:active {
|
||||||
|
transform: scale(0.95);
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-action-btn.loading {
|
||||||
|
pointer-events: none;
|
||||||
|
opacity: 0.5;
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
.inspector-action-btn.loading::after {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
width: 12px;
|
||||||
|
height: 12px;
|
||||||
|
border: 2px solid var(--zinc-600);
|
||||||
|
border-top-color: var(--amber-400);
|
||||||
|
border-radius: 50%;
|
||||||
|
animation: spin 0.6s linear infinite;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes spin {
|
||||||
|
to { transform: rotate(360deg); }
|
||||||
|
}
|
||||||
|
|
||||||
.inspector-selected {
|
.inspector-selected {
|
||||||
font-family: var(--font-mono);
|
font-family: var(--font-mono);
|
||||||
font-size: 11px;
|
font-size: 11px;
|
||||||
|
|
|
||||||
|
|
@ -60,6 +60,9 @@
|
||||||
</button>
|
</button>
|
||||||
<span class="inspector-selected" id="inspector-selected"></span>
|
<span class="inspector-selected" id="inspector-selected"></span>
|
||||||
<span class="inspector-mode-badge" id="inspector-mode-badge" style="display:none"></span>
|
<span class="inspector-mode-badge" id="inspector-mode-badge" style="display:none"></span>
|
||||||
|
<div style="flex:1"></div>
|
||||||
|
<button id="inspector-cleanup-btn" class="inspector-action-btn" title="Remove ads, banners, popups">🧹</button>
|
||||||
|
<button id="inspector-screenshot-btn" class="inspector-action-btn" title="Take a screenshot">📸</button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Inspector content area -->
|
<!-- Inspector content area -->
|
||||||
|
|
|
||||||
|
|
@ -135,6 +135,16 @@ function addChatEntry(entry) {
|
||||||
return;
|
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
|
// Agent streaming events
|
||||||
if (entry.role === 'agent') {
|
if (entry.role === 'agent') {
|
||||||
handleAgentEvent(entry);
|
handleAgentEvent(entry);
|
||||||
|
|
@ -1139,6 +1149,74 @@ inspectorSendBtn.addEventListener('click', () => {
|
||||||
chrome.runtime.sendMessage({ type: 'sidebar-command', message });
|
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 ────────────────────────────────────────────
|
// ─── Section Toggles ────────────────────────────────────────────
|
||||||
|
|
||||||
document.querySelectorAll('.inspector-section-toggle').forEach(toggle => {
|
document.querySelectorAll('.inspector-section-toggle').forEach(toggle => {
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
{
|
{
|
||||||
"name": "gstack",
|
"name": "gstack",
|
||||||
"version": "0.13.8.0",
|
"version": "0.13.9.0",
|
||||||
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
"description": "Garry's Stack — Claude Code skills + fast headless browser. One repo, one install, entire AI engineering workflow.",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue