From fa03b66c617e97d0c7cfe9c56c6463c526190899 Mon Sep 17 00:00:00 2001 From: Garry Tan Date: Sun, 29 Mar 2026 23:44:31 -0700 Subject: [PATCH] =?UTF-8?q?feat:=20LLM-based=20page=20cleanup=20=E2=80=94?= =?UTF-8?q?=20agent=20analyzes=20page=20semantically?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Instead of brittle CSS selectors, the cleanup button now sends a prompt to the sidebar agent (which IS an LLM). The agent: 1. Runs deterministic $B cleanup --all as a quick first pass 2. Takes a snapshot to see what's left 3. Analyzes the page semantically to identify remaining clutter 4. Removes elements intelligently, preserving site branding This means cleanup works correctly on any site without site-specific selectors. The LLM understands that "Your Daily Puzzles" is clutter, "ADVERTISEMENT" is junk, but the SF Chronicle masthead should stay. Co-Authored-By: Claude Opus 4.6 (1M context) --- extension/sidepanel.js | 52 +++++++++++++++++++++++++++++++++--------- 1 file changed, 41 insertions(+), 11 deletions(-) diff --git a/extension/sidepanel.js b/extension/sidepanel.js index a1f13180..bfc28e88 100644 --- a/extension/sidepanel.js +++ b/extension/sidepanel.js @@ -1153,29 +1153,59 @@ inspectorSendBtn.addEventListener('click', () => { async function runCleanup(...buttons) { if (!serverUrl || !serverToken) { - // Don't spam errors, buttons should already be disabled return; } buttons.forEach(b => b?.classList.add('loading')); + + // Smart cleanup: send a chat message to the sidebar agent (an LLM). + // The agent snapshots the page, understands it semantically, and removes + // clutter intelligently. Much better than brittle CSS selectors. + const cleanupPrompt = [ + 'Clean up this page for reading. First run a quick deterministic pass:', + '$B cleanup --all', + '', + 'Then take a snapshot to see what\'s left:', + '$B snapshot -i', + '', + 'Look at the snapshot and identify remaining non-content elements:', + '- Ad placeholders, "ADVERTISEMENT" labels, sponsored content', + '- Cookie/consent banners, newsletter popups, login walls', + '- Audio/podcast player widgets, video autoplay', + '- Sidebar widgets (puzzles, games, "most popular", recommendations)', + '- Social share buttons, follow prompts, "See more on Google"', + '- Floating chat widgets, feedback buttons', + '- Navigation drawers, mega-menus (unless they ARE the page content)', + '- Empty whitespace from removed ads', + '', + 'KEEP: the site header/masthead/logo, article headline, article body,', + 'article images, author byline, date. The page should still look like', + 'the site it is, just without the crap.', + '', + 'For each element to remove, run JavaScript via $B to hide it:', + '$B eval "document.querySelector(\'SELECTOR\').style.display=\'none\'"', + '', + 'Also unlock scrolling if the page is scroll-locked:', + '$B eval "document.body.style.overflow=\'auto\';document.documentElement.style.overflow=\'auto\'"', + ].join('\n'); + try { - const resp = await fetch(`${serverUrl}/command`, { + // Send as a sidebar command (spawns the agent) + const resp = await fetch(`${serverUrl}/sidebar-command`, { method: 'POST', - headers: { ...authHeaders(), 'Content-Type': 'application/json' }, - body: JSON.stringify({ command: 'cleanup', args: ['--all'] }), - signal: AbortSignal.timeout(15000), + headers: authHeaders(), + body: JSON.stringify({ message: cleanupPrompt }), + signal: AbortSignal.timeout(5000), }); - const text = await resp.text(); if (resp.ok) { - addChatEntry({ type: 'notification', message: text || 'Page cleaned up' }); - if (typeof inspectorShowEmpty === 'function') inspectorShowEmpty(); + addChatEntry({ type: 'notification', message: 'Cleaning up page (agent is analyzing...)' }); } else { - const err = JSON.parse(text).error || 'Cleanup failed'; - addChatEntry({ type: 'notification', message: 'Error: ' + err }); + addChatEntry({ type: 'notification', message: 'Failed to start cleanup' }); } } catch (err) { addChatEntry({ type: 'notification', message: 'Cleanup failed: ' + err.message }); } finally { - buttons.forEach(b => b?.classList.remove('loading')); + // Remove loading after a short delay (agent runs async) + setTimeout(() => buttons.forEach(b => b?.classList.remove('loading')), 2000); } }