A browser extension that lets you summarize any webpage and ask questions using AI.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Rebrand extension to Lede (page summarizer & chat)

+137 -39
+1 -1
AGENTS.md
··· 21 21 22 22 ## Name, version, shortcuts 23 23 - **Version** lives in the active manifest (`version`) and any browser-specific manifest you ship. Do not duplicate it in `scripts/config.js`. 24 - - **Display name and default shortcut copy** for the settings page come from `CONFIG.EXTENSION` (`NAME`, `SHORTCUT`, `SHORTCUT_MAC`), filled in `options/options.js`. When you change the extension **name** or **suggested keyboard shortcuts**, update the manifest **`name` / `commands`** and the same fields in **`CONFIG.EXTENSION`** so the options tab title and shortcut help stay accurate. 24 + - **Display name, tagline, motto, and default shortcut copy** come from `CONFIG.EXTENSION` (`NAME`, `TAGLINE`, `SETTINGS_MOTTO`, `EMPTY_*`, `SHORTCUT`, `SHORTCUT_MAC`), used in `options/options.js` and `popup/popup.js`. When you change the extension **name** or **suggested keyboard shortcuts**, update the manifest **`name` / `commands`** and the same fields in **`CONFIG.EXTENSION`** so the options tab title and shortcut help stay accurate.
+1 -1
README.md
··· 1 - # WIP Summarizer + Chat AI extension 1 + # Lede — page summarizer & chat (WIP) 2 2 3 3 A browser extension for Chrome and Firefox that lets you ask questions and summarize any webpage using AI (using Ollama or any OpenAI-compatible API). Works best with the GPT-OSS-20b model. 4 4
+1 -1
build.sh
··· 1 1 #!/bin/bash 2 2 3 - # Build script for the Summarizer extension 3 + # Build script for the Lede extension 4 4 # Usage: ./build.sh [chrome|firefox] 5 5 6 6 BROWSER=${1:-chrome}
+3 -3
manifest-chrome.json
··· 1 1 { 2 2 "manifest_version": 3, 3 - "name": "Summarizer", 3 + "name": "Lede", 4 4 "version": "1.0.0", 5 - "description": "Summarize any webpage and use AI chat about what you're reading", 5 + "description": "Lede — page summarizer & chat. Don't bury the main point—summarize and discuss any article.", 6 6 "permissions": [ 7 7 "activeTab", 8 8 "tabs", ··· 17 17 "default": "Ctrl+Shift+U", 18 18 "mac": "Command+Shift+U" 19 19 }, 20 - "description": "Summarize the current page" 20 + "description": "Open Lede for this page" 21 21 } 22 22 }, 23 23 "host_permissions": [
+3 -3
manifest-firefox.json
··· 1 1 { 2 2 "manifest_version": 3, 3 - "name": "Summarizer", 3 + "name": "Lede", 4 4 "version": "1.0.0", 5 - "description": "Summarize any webpage and use AI chat about what you're reading", 5 + "description": "Lede — page summarizer & chat. Don't bury the main point—summarize and discuss any article.", 6 6 "browser_specific_settings": { 7 7 "gecko": { 8 8 "id": "webai-summarizer@anomaly.co", ··· 24 24 "default": "Ctrl+Shift+U", 25 25 "mac": "Command+Shift+U" 26 26 }, 27 - "description": "Summarize the current page" 27 + "description": "Open Lede for this page" 28 28 } 29 29 }, 30 30 "host_permissions": [
+3 -3
manifest.json
··· 1 1 { 2 2 "manifest_version": 3, 3 - "name": "Summarizer", 3 + "name": "Lede", 4 4 "version": "1.0.0", 5 - "description": "Summarize any webpage and use AI chat about what you're reading", 5 + "description": "Lede — page summarizer & chat. Don't bury the main point—summarize and discuss any article.", 6 6 "permissions": [ 7 7 "activeTab", 8 8 "tabs", ··· 17 17 "default": "Ctrl+Shift+U", 18 18 "mac": "Command+Shift+U" 19 19 }, 20 - "description": "Summarize the current page" 20 + "description": "Open Lede for this page" 21 21 } 22 22 }, 23 23 "host_permissions": [
+37 -1
options/options.css
··· 52 52 /* ── Header ── */ 53 53 .page-header { 54 54 display: flex; 55 - align-items: center; 55 + align-items: flex-start; 56 56 gap: 8px; 57 57 margin-bottom: 32px; 58 58 } ··· 74 74 mask: url("../lightning.svg") center / contain no-repeat; 75 75 } 76 76 77 + .page-header-titles { 78 + flex: 1; 79 + min-width: 0; 80 + display: flex; 81 + flex-direction: column; 82 + gap: 6px; 83 + } 84 + 85 + .page-brand { 86 + display: flex; 87 + flex-direction: column; 88 + gap: 2px; 89 + line-height: 1.2; 90 + } 91 + 92 + .page-brand-name { 93 + font-size: 15px; 94 + font-weight: 600; 95 + color: var(--text); 96 + letter-spacing: 0.01em; 97 + } 98 + 99 + .page-brand-tagline { 100 + font-size: 11px; 101 + font-weight: 500; 102 + color: var(--text-secondary); 103 + letter-spacing: 0.02em; 104 + } 105 + 77 106 .page-header h1 { 78 107 font-size: 15px; 79 108 font-weight: 600; 80 109 color: var(--text); 81 110 letter-spacing: 0.01em; 111 + } 112 + 113 + .page-motto { 114 + font-size: 12px; 115 + font-style: italic; 116 + color: var(--text-muted); 117 + line-height: 1.4; 82 118 } 83 119 84 120 .icon-btn {
+11 -2
options/options.html
··· 2 2 <html> 3 3 <head> 4 4 <meta charset="UTF-8" /> 5 - <title>Summarizer — Settings</title> 5 + <title>Lede — Settings</title> 6 6 <link rel="stylesheet" href="options.css" /> 7 7 </head> 8 8 <body> ··· 12 12 <div class="logo-mark"> 13 13 <span class="brand-icon" aria-hidden="true"></span> 14 14 </div> 15 - <h1>Settings</h1> 15 + <div class="page-header-titles"> 16 + <div class="page-brand"> 17 + <span id="opt-brand-name" class="page-brand-name">Lede</span> 18 + <span id="opt-brand-tagline" class="page-brand-tagline">Page summarizer &amp; chat</span> 19 + </div> 20 + <h1>Settings</h1> 21 + <p id="page-motto" class="page-motto"> 22 + Let Lede lead the way. 23 + </p> 24 + </div> 16 25 <button id="theme-btn" class="icon-btn" title="Toggle theme"> 17 26 <svg 18 27 id="theme-icon-light"
+7 -1
options/options.js
··· 295 295 return infoDiv; 296 296 } 297 297 298 - /** Tab title + shortcut help from CONFIG.EXTENSION (keep in sync with manifest name / commands). */ 298 + /** Tab title, brand copy, and shortcut help from CONFIG.EXTENSION (keep in sync with manifest name / commands). */ 299 299 function applyExtensionMetadata() { 300 300 document.title = `${CONFIG.EXTENSION.NAME} — Settings`; 301 + const nameEl = document.getElementById("opt-brand-name"); 302 + const tagEl = document.getElementById("opt-brand-tagline"); 303 + const mottoEl = document.getElementById("page-motto"); 304 + if (nameEl) nameEl.textContent = CONFIG.EXTENSION.NAME; 305 + if (tagEl) tagEl.textContent = CONFIG.EXTENSION.TAGLINE; 306 + if (mottoEl) mottoEl.textContent = CONFIG.EXTENSION.SETTINGS_MOTTO; 301 307 const el = document.getElementById("shortcut-display"); 302 308 if (!el) return; 303 309 const { SHORTCUT, SHORTCUT_MAC } = CONFIG.EXTENSION;
+14
popup/popup.css
··· 141 141 mask: url("../lightning.svg") center / contain no-repeat; 142 142 } 143 143 144 + .brand-stack { 145 + display: flex; 146 + flex-direction: column; 147 + gap: 1px; 148 + line-height: 1.2; 149 + } 150 + 144 151 .logo-text { 145 152 font-size: 13px; 146 153 font-weight: 600; 147 154 color: var(--text); 148 155 letter-spacing: 0.01em; 156 + } 157 + 158 + .brand-tagline { 159 + font-size: 10px; 160 + font-weight: 500; 161 + color: var(--text-muted); 162 + letter-spacing: 0.02em; 149 163 } 150 164 151 165 .icon-btn {
+11 -5
popup/popup.html
··· 17 17 <div class="logo-mark"> 18 18 <span class="brand-icon" aria-hidden="true"></span> 19 19 </div> 20 - <span class="logo-text">Summarizer</span> 20 + <div class="brand-stack"> 21 + <span class="logo-text" id="brand-name">Lede</span> 22 + <span class="brand-tagline" id="brand-tagline">Page summarizer &amp; chat</span> 23 + </div> 21 24 </div> 22 25 <div class="header-right"> 23 26 <!-- Theme toggle: cycles light → dark → system --> ··· 116 119 <div class="initial-icon"> 117 120 <span class="brand-icon" aria-hidden="true"></span> 118 121 </div> 119 - <p class="initial-title">Ready to summarize</p> 120 - <p class="initial-sub"> 121 - Get an AI-powered summary of the page you're reading. 122 + <p class="initial-title" id="initial-title"> 123 + Don't bury the lede 124 + </p> 125 + <p class="initial-sub" id="initial-sub"> 126 + Put the main point first—then ask anything about the 127 + page—with AI. 122 128 </p> 123 129 </div> 124 130 <div id="result" class="result hidden"></div> ··· 130 136 type="text" 131 137 id="chat-input" 132 138 class="chat-input" 133 - placeholder="AI chat — ask about this page…" 139 + placeholder="Ask Lede about this page…" 134 140 /> 135 141 <button id="chat-send" class="chat-send-btn" title="Send"> 136 142 <svg
+23 -6
popup/popup.js
··· 390 390 } 391 391 } 392 392 393 + function applyExtensionBranding() { 394 + const ext = CONFIG.EXTENSION; 395 + const nameEl = document.getElementById("brand-name"); 396 + const tagEl = document.getElementById("brand-tagline"); 397 + const initialTitle = document.getElementById("initial-title"); 398 + const initialSub = document.getElementById("initial-sub"); 399 + const chatInput = document.getElementById("chat-input"); 400 + if (nameEl) nameEl.textContent = ext.NAME; 401 + if (tagEl) tagEl.textContent = ext.TAGLINE; 402 + if (initialTitle) initialTitle.textContent = ext.EMPTY_HEADLINE; 403 + if (initialSub) initialSub.textContent = ext.EMPTY_SUB; 404 + if (chatInput) 405 + chatInput.placeholder = `Ask ${ext.NAME} about this page…`; 406 + } 407 + 393 408 document.addEventListener("DOMContentLoaded", async () => { 409 + applyExtensionBranding(); 410 + 394 411 // Load saved theme and accent before rendering anything 395 412 const { theme, accentColor } = await chrome.storage.sync.get([ 396 413 "theme", ··· 1107 1124 1108 1125 // Legacy debug — uncomment to restore (popup DevTools, not the tab console): 1109 1126 // console.log( 1110 - // "[Summarizer DEBUG] extraction source (from tab):", 1127 + // "[Lede DEBUG] extraction source (from tab):", 1111 1128 // contentExtractionSource, 1112 1129 // ); 1113 1130 // console.log( 1114 - // "[Summarizer DEBUG] text sent to summarize API — page slice:", 1131 + // "[Lede DEBUG] text sent to summarize API — page slice:", 1115 1132 // pageContentForLLM.length, 1116 1133 // "/15000 chars", 1117 1134 // ); 1118 1135 // console.log( 1119 - // "[Summarizer DEBUG] text sent to summarize API (page slice):", 1136 + // "[Lede DEBUG] text sent to summarize API (page slice):", 1120 1137 // pageContentForLLM, 1121 1138 // ); 1122 - // console.log("[Summarizer DEBUG] full summarize API messages:", apiMessages); 1139 + // console.log("[Lede DEBUG] full summarize API messages:", apiMessages); 1123 1140 1124 1141 await chrome.runtime.sendMessage({ action: "ping" }).catch(() => {}); 1125 1142 ··· 1680 1697 <div class="pdf-error-icon">:(</div> 1681 1698 <div class="pdf-error-title">PDF files aren't supported</div> 1682 1699 <div class="pdf-error-message"> 1683 - This extension can't extract text from PDFs. 1700 + Lede can't extract text from PDFs in the browser tab. 1684 1701 To summarize this document, download the file and upload it to a tool like ChatGPT. 1685 1702 </div> 1686 1703 </div> ··· 1706 1723 "old-reddit": { 1707 1724 title: "Old Reddit isn’t supported for thread extraction", 1708 1725 message: 1709 - "Open the same post on www.reddit.com (new Reddit), then open Summarizer again.", 1726 + "Open the same post on www.reddit.com (new Reddit), then open Lede again.", 1710 1727 }, 1711 1728 "non-discussion": { 1712 1729 title: "This Reddit page can’t be summarized with focused extraction yet",
+2 -2
scripts/background.js
··· 151 151 // Create context menu item 152 152 chrome.contextMenus.create({ 153 153 id: "summarize-page", 154 - title: "Summarize this page", 154 + title: "Open Lede for this page", 155 155 contexts: ["page", "selection"], 156 156 }); 157 157 }); ··· 179 179 CONFIG.CACHE.SUGGESTIONS + tabId, 180 180 ]); 181 181 } catch (e) { 182 - console.error("[Summarizer] Error clearing cache:", e); 182 + console.error("[Lede] Error clearing cache:", e); 183 183 } 184 184 } 185 185
+16 -6
scripts/config.js
··· 1 1 /** 2 - * Shared configuration for the Summarizer extension 2 + * Shared configuration for the Lede extension 3 3 * Centralizes all defaults, constants, and settings 4 4 */ 5 5 ··· 87 87 // Prompt templates 88 88 PROMPTS: { 89 89 SYSTEM_SUMMARIZER: 90 - "You are a helpful assistant that summarizes webpages concisely.", 90 + "You are Lede, a helpful assistant that summarizes webpages concisely. Foreground the main point (the lede) before supporting detail.", 91 91 SYSTEM_CHAT: 92 - "You are a helpful assistant answering questions about a webpage. Use the provided page content and summary to give accurate, concise answers. You may use short sentences, sections, and bullet points to answer. Avoid long paragraphs and tables. ONLY answer based on the provided page content and summary, not any external knowledge or information.", 92 + "You are Lede, a helpful assistant answering questions about a webpage. Use the provided page content and summary to give accurate, concise answers. You may use short sentences, sections, and bullet points to answer. Avoid long paragraphs and tables. ONLY answer based on the provided page content and summary, not any external knowledge or information.", 93 93 QUICK_SUMMARY: `Please provide a "Quick Summary" of this webpage. Focus on the main points and key takeaways. Use markdown formatting (headings, bullet points, etc.). 94 94 95 95 The "Quick Summary" should be 3-5 **short** one-sentence bullet points. Each of these bullet points should have key points/takeaways **bolded** so people can quickly scan.`, ··· 110 110 }, 111 111 112 112 /** 113 - * Display strings wired in `options/options.js` (document title + shortcut help). 114 - * Name: keep aligned with manifest `name`. Shortcuts: same chord as manifest `commands.summarize-page.suggested_key`. 113 + * Display strings wired in `options/options.js` and `popup/popup.js` (titles, taglines, empty state). 114 + * Name / tagline: keep aligned with manifest `name` and `description`. Shortcuts: same chord as manifest `commands.summarize-page.suggested_key`. 115 115 * Extension version lives only in the manifest(s); use `chrome.runtime.getManifest().version` in code if needed. 116 116 */ 117 117 EXTENSION: { 118 - NAME: "Summarizer", 118 + NAME: "Lede", 119 + /** Under the wordmark in popup and settings */ 120 + TAGLINE: "Page summarizer & chat", 121 + /** One-line for manifest description and similar */ 122 + FULL_TAGLINE: "Lede — page summarizer & chat", 123 + /** Popup empty state (before summarize) */ 124 + EMPTY_HEADLINE: "Don't bury the lede", 125 + EMPTY_SUB: 126 + "Put the main point first—then ask anything about the page—with AI.", 127 + /** Settings page, under the header block */ 128 + SETTINGS_MOTTO: "Let Lede lead the way.", 119 129 SHORTCUT: "Ctrl+Shift+U", 120 130 SHORTCUT_MAC: "Cmd+Shift+U", 121 131 },
+4 -4
scripts/content.js
··· 515 515 516 516 /** Legacy debug — uncomment body to log to the tab's DevTools (page context). */ 517 517 function logExtractionDebug(source, text) { 518 - // console.log("[Summarizer DEBUG] extraction:", source); 518 + // console.log("[Lede DEBUG] extraction:", source); 519 519 // console.log( 520 - // "[Summarizer DEBUG] raw extracted text (" + text.length + " chars):", 520 + // "[Lede DEBUG] raw extracted text (" + text.length + " chars):", 521 521 // text, 522 522 // ); 523 523 } ··· 529 529 try { 530 530 return window.__webaiTryRedditNew(MAX_LENGTH); 531 531 } catch (err) { 532 - console.error("[Summarizer] Reddit extract error:", err); 532 + console.error("[Lede] Reddit extract error:", err); 533 533 return null; 534 534 } 535 535 } ··· 637 637 extractionSource: useLegacy ? "legacy" : "readability", 638 638 }; 639 639 } catch (error) { 640 - console.error("[Summarizer] Readability error:", error); 640 + console.error("[Lede] Readability error:", error); 641 641 const legacy = extractLegacy(); 642 642 logExtractionDebug("legacy", legacy.text); 643 643 return { ...legacy, extractionSource: "legacy" };