跳到正文
GitHub·· 10 天前AI 评分30

AI Summary Helper:用自选 LLM 总结网页并推送到 Kindle 的开源扩展

philffm/ai-summary-helper

AI 导读

AI Summary Helper 是一款开源浏览器扩展与 bookmarklet,可用自选 LLM 总结任意网页,支持 OpenAI、Gemini、Mistral、DeepSeek 或本地 Ollama,也可用无需 API key 的 byPhil Cloud。

正文

AI Summary Helper — Summarize the web. Keep what matters.

Chrome Web Store Bookmarklet Website

License: MIT Version Manifest V3 Vanilla JS, no framework Privacy: your key, your data

You are on the hunt for interesting articles around the web, open 100 tabs and end up… not reading them. Sounds familiar?

AI Summary Helper turns that pile into a reading habit. Summarize any page with the AI of your choice, follow your favorite sites in a built-in RSS reader, ask follow-up questions, and keep everything in a searchable archive that lives on your device. Then send a curated digest to your Kindle or another device, so you read what you picked on purpose.

AI Summary Helper screenshot

Pick how you want to use it

🧩 Browser extension 🔖 Bookmarklet
Install Chrome Web Store, Firefox & Safari builds below Nothing: drag one link to your bookmarks bar
Works on Chrome, Edge, Firefox, Safari, Android Any browser and OS, even iOS
Best for Daily reading: history, search, RSS, highlights Quick use on devices where extensions can't run
Get it Add to Chrome Create your bookmarklet

How it works

Walkthrough of the six steps: summarize any page, ask follow-up questions, highlight what matters, follow your sites with RSS, search and connect your archive, send a digest to Kindle or LocalSend

What you get

  • Summaries with your model. The byPhil API gives you access to most LLM models with no API key of your own. Or bring your own key for OpenAI, Gemini, Mistral, DeepSeek, or run Ollama locally.
  • Conversational summaries. Ask follow-up questions; answers cite the page and jump to the exact passage. Pin the good ones into the summary.
  • Built-in RSS reader. Follow sites, filter by source, tag, date or mood, import OPML, get an optional AI recap of the day.
  • Highlights and ghost highlights. Mark text yourself or let the AI mark the key passages. Start a summary straight from the on-page highlights panel.
  • A searchable archive. On-device full-text search, a knowledge graph of your saves, and an analytics report of your reading.
  • Read it later, on purpose. "Summarize & Close" saves a tab with a reminder; the digest sends a varied selection to Kindle or LocalSend.
  • Your language. 40+ summary languages (including Traditional and Simplified Chinese) and a localized interface.

Kindle sending limit: The free tier includes 3 Kindle sends; unlimited Kindle & LocalSend sends require the Support Pass.

AI Summary Helper on Product Hunt

Bookmarklet in action

Demo

Extension vs bookmarklet

Bookmarklet Browser Extension
OpenAI ✅ ✅
Mistral AI ✅ ✅
DeepSeek ✅ ✅
Gemini ✅ ✅
Ollama (Local) ✅ ✅
byPhil Cloud (no API key) ✅ ✅
Custom Prompt ✅ ✅ Base + custom prompt per request
Cross Platform ✅ ❌
Text Highlighting ❌ ✅
Article History & Archive ❌ ✅
Save for Later (Tab Close) ❌ ✅
Context Menu Actions ❌ ✅
Backup & Restore ❌ ✅

Quick start for contributors

git clone https://github.com/philffm/ai-summary-helper.git
cd ai-summary-helper
npm ci
npm run build          # syncs plugin/src → plugin/dev/aish-extension-<platform>
npm test               # jsdom test suite (run `npm test -- 12 37` for single tests)

Load plugin/dev/aish-extension-chrome/ via chrome://extensions → Developer mode → Load unpacked. The extension is vanilla ES modules, no framework and no bundler. The marketing site is plain HTML assembled by npm run site:build from site-src/.

Install and build

Chrome / Edge / Opera / Brave

Install from the Chrome Web Store, or build it yourself: run npm run build, open chrome://extensions, enable Developer mode, click Load unpacked and pick plugin/dev/aish-extension-chrome/.

Firefox Extension

  1. Run npm run build:firefox (or node plugin/scripts/build.js firefox) to sync plugin/src/ into plugin/dev/aish-extension-firefox/ with the Firefox manifest.
  2. Open about:debugging#/runtime/this-firefox in Firefox and click Load Temporary Add-on, then select plugin/dev/aish-extension-firefox/manifest.json.

Safari (iOS / macOS)

  1. Run npm run build:ios (or node plugin/scripts/build.js ios) to sync plugin/src/ into plugin/dev/aish-extension-ios/.
  2. Convert the WebExtension into a native container app:
    xcrun safari-web-extension-converter ./plugin/dev/aish-extension-ios \
    --project-location plugin/dist/ios \
    --app-name "AI Summary Helper" \
    --bundle-identifier "eu.byphil.aisummaryhelper" \
    --copy-resources \
    --force
       
    
  3. In Xcode, ensure the extension target bundle ID starts with the parent app's bundle ID (e.g. eu.byphil.aisummaryhelper.extension), select the same signing team for both targets, and run on a concrete device/simulator (not "Any iOS Device").

Bookmarklet Generator

The bookmarklet generator lives on its own page: ai-summary-helper.byphil.eu/bookmarklet.html (the landing page only teases it). It supports both byPhil Cloud (email magic-code login, no API key) and bring-your-own-key providers (OpenAI, DeepSeek, Mistral, Gemini, Ollama). The generated bookmarklet always inserts the summary on the page and can optionally share it via the system share sheet, Send to Kindle (byPhil Cloud proxy), or Send to LocalSend (direct P2P). It also checks bookmarklet-version.json on each run and warns when it is outdated.

The generator's source lives in site-src/pages/bookmarklet.html (markup) and docs/assets/main.js (the /* ── Bookmarklet generator */ component). GitHub Pages serves from docs/.

Architecture

AISH is a Manifest V3 extension with four runtime parts that talk to each other only through chrome.runtime messages and chrome.storage. There is no server of our own in the loop when you bring your own key or use Ollama: the background worker calls the provider directly.

flowchart LR
  subgraph Page["Web page (any tab)"]
    L["loader.js<br/>~6 KB, static content script"]
    C["content.js<br/>~200 KB bundle, injected on demand"]
  end
  subgraph Ext["Extension"]
    P["Popup / side panel<br/>popup.html + ES modules"]
    B["Background worker<br/>background.js + finalize.js"]
    S[("chrome.storage<br/>sync: settings, local: library")]
  end
  LLM["LLM provider<br/>OpenAI-compatible, Ollama, byphil Cloud"]

  L -- "aish:injectContent" --> B
  B -- "scripting.executeScript" --> C
  P -- "summarize, ping, revealQuote" --> C
  C -- "streamFetch" --> B
  B -- "stream / aiComplete" --> LLM
  B -- "summaryComplete, progress" --> P
  C --- S
  P --- S
  B --- S

Loading

The four parts

  • loader.js (every page). The only script that runs on every page. It checks whether the page has saved highlights (hl:all) or the user selects text, and only then asks the background to inject the real content script. Android and iOS keep the full content.js as a static content script.
  • content.js (on demand). Extracts the readable text (HTML, PDFs), paints highlights, shows the selection tooltip, and drives a summary run. It is injected by the background (ensureContent) when a highlight, a selection, the context menu, a shortcut, or the popup needs it, and is guarded against double injection.
  • Background worker. Streams model output (handleStreamFetch, idle timeout only after the first chunk, so slow local models are fine), runs one-shot completions (aiComplete / aiCancel), polls feeds, hosts audio, and finishes a run whose page went away (finalize.js is bundled as a classic script for the worker).
  • Popup / side panel. Plain HTML plus native ES modules. The main screen and its core modules load first; Feeds, History and Settings load right after the first paint.

A summary, end to end

  1. The popup asks the content script to summarize (injected first if needed).
  2. The content script extracts the text and sends a streamFetch to the background, which streams the provider response back as progress.
  3. finalizeSummary (content/finalize.js, shared by the page and the worker) cleans the model output into HTML and pulls out tags, ghost highlights, mood, a paper verdict, and suggested follow-up questions with a short answer each (QUESTIONS: [{"q","a"}]).
  4. The result is saved (articles:index + one record per article) and relayed as summaryComplete.
  5. The popup shows the card and the question chips. Tapping a chip types the stored short answer out like a person, while the background asks the model to continue that answer. The continuation streams in behind the typed text, a "Thinking more…" indicator with a Stop button covers the wait, and Stop keeps the short answer and cancels the request.

Models that ignore the {q, a} format still work: the parser accepts plain question strings and simply shows no short answer.

Storage

All access goes through StorageManager; keys are defined once in modules/storageKeys.js.

Area Where Notes
Settings, flags, language, theme chrome.storage.sync small values only
Library index local articles:index lean list, never holds article text
Article record local articles:rec:<id> content, summary, conversation, suggested, suggestedAnswers
Highlights local hl:all one array for all pages, matched by pageKeyForUrl
Providers and keys local config:services per service: key, model, endpoint
Feeds local feeds:* subscriptions, items, recaps, UI state

Reads are targeted (StorageManager.get([keys]) routes each key to sync or local). getAll() is reserved for backup and first-run migration.

UI, i18n, security

  • i18n. The English text is the key (T('Send me a code'), data-i18n ids in popup.html). node scripts/feed-i18n.mjs extract | merge <dir> | check keeps the 13 locales complete.
  • Rendering. Anything model- or page-derived that reaches innerHTML goes through escapeHtml (modules/textUtils.js).
  • Shared helpers. textUtils (escape, word count), dateUtils (day, week, month), dom (element helpers), suggestions (follow-up parsing), typewriter (human-like typing).

Tests

npm test runs the jsdom suite in plugin/tests (storage, popup flows, finalize, i18n coverage, loader). plugin/tests/e2e/*.e2e.cjs drive real Chromium with the built extension and need Playwright: node plugin/scripts/build.js chrome first, then run the script.

Under the hood

Project structure and module responsibilities

ai-summary-helper/
├── plugin/                       # Shippable extension code (monorepo split)
│   ├── src/                      # Single source of truth (Chrome MV3, Vanilla JS)
│   │   ├── background.js         # Service worker: context menus, alarms, notifications, side panel
│   │   ├── loader.js             # Tiny static content script: injects content.js only when needed
│   │   ├── content.js            # Full content script (bundled, injected on demand): extraction, highlighting, summary run
│   │   ├── content/              # Content-script parts (extractor, highlighter, finalize, markdown, …)
│   │   ├── popup.html            # Popup UI (header → screens → bottom-nav)
│   │   ├── popup.js              # Popup entry point — wires up all module inits
│   │   ├── styles.css            # Global styles (glassmorphism, light/dark themes)
│   │   ├── api.js                # API helpers
│   │   ├── services.json         # Provider registry (OpenAI, Mistral, Deepseek, Ollama, …)
│   │   ├── prompts.json          # Preset prompt library
│   │   ├── compatible-tools.json # Compatible tools table (generated from readme)
│   │   ├── translations.json     # UI translation strings (source of truth)
│   │   ├── donationMessages.json # Donation message pool
│   │   ├── privacy.md            # Privacy policy
│   │   ├── readme.md             # Extension-specific readme
│   │   ├── _locales/             # i18n messages per locale (ar, de, en, es, fr, hi, it, ja, ko, pt_PT, ru, zh_*)
│   │   ├── icons/                # Extension icons (16/48/128 + svg)
│   │   ├── lib/                  # Vendored libs (e.g. d3.min.js)
│   │   └── modules/              # ES modules (see table below)
│   │
│   ├── platforms/                # Per-platform manifests
│   │   ├── chrome/manifest.json  # Chrome: sidePanel + service_worker
│   │   ├── android/manifest.json # Android: no sidePanel, service_worker
│   │   ├── firefox/manifest.json # Firefox: gecko id, background.scripts (event page)
│   │   └── ios/manifest.json     # Safari/iOS: background.scripts
│   │
│   ├── scripts/
│   │   └── build.js              # Node dev-sync tool (src → dev/<platform>)
│   │
│   ├── build.sh                  # Release build: version bump + zip into prod/
│   │
│   ├── dev/                      # Generated unpacked builds (git-ignored)
│   │   ├── aish-extension-chrome/
│   │   ├── aish-extension-android/
│   │   └── aish-extension-firefox/
│   │
│   └── prod/                     # Generated release zips (git-ignored)
│       ├── aish-extension-chrome-<ver>.zip
│       ├── aish-extension-android-<ver>.zip
│       └── aish-extension-firefox-<ver>.zip
│
├── docs/                         # Marketing website (GitHub Pages publish folder)
│   ├── index.html                # Landing page (hero switch teases the bookmarklet)
│   ├── bookmarklet.html          # Bookmarklet generator page
│   ├── sitemap.xml               # SEO sitemap
│   ├── CNAME                     # Custom domain (ai-summary-helper.byphil.eu)
│   ├── assets/                   # Site JS/CSS + icons (main.js, styles.css, icon.svg, createBookmarklet.svg)
│   ├── bookmarklet-version.json  # Version manifest the bookmarklet checks for updates
│   ├── blog/                     # Marketing blog (static HTML)
│   ├── i18n/                     # Translation config + manifest (locales.json, manifest.json)
│   └── lang/                     # Website translations (generated by translate.mjs)
│
├── assets/                       # Repo-root marketing images (aish.png, demo.gif, createBookmarklet.svg)
│
├── scripts/
│   └── translate.mjs             # Website translation pipeline (docs/ → docs/lang/)
│
├── current_version.json          # Single source of truth for version + language list
├── package.json                  # npm scripts (build, build:chrome, build:firefox, …)
├── readme.md                     # This overview (also feeds compatible-tools.json)
│
└── .github/workflows/
    ├── release.yml               # Tag-triggered: build + version bump + GitHub release
    └── translate.yml             # Auto-translates docs/ content → docs/lang/

plugin/src/modules/ — ES module responsibilities

Module Responsibility
uiManager.js, settingsNav.js, tabbar.js, sheet.js, dropdownMenu.js, confirmDialog.js Screens, bottom nav, settings home, tab bars, sheets with focus trap, menus, dialogs
mainScreen.js, composerState.js, qaView.js, conversation.js Summarize screen: feed, composer states, follow-up thread, prompt building and answer parsing
suggestions.js, typewriter.js Follow-up suggestions with short answers; human-like typing of the short answer
feedManager.js, feedAi.js, feedRollup.js, feedParse.js, feedPlayer.js, feedInsights.js, feedMood.js, feedSentiment.js, feedUtil.js RSS reader, OPML, AI recaps, podcast player, insights, mood
articleManager.js, archiveManager.js, archiveGraph.js, analyticsManager.js, topicsChart.js, historyMood.js, moodView.js, digestBuilder.js, annotationExporter.js History, knowledge graph, analytics, digests and exports
settingsManager.js, modelManager.js, promptManager.js, promptSettings.js, promptBuilder.js, languageManager.js, moodSetting.js, workspaceManager.js Settings, providers and models, prompts, languages, workspaces
authManager.js byphil Cloud sign-in (shared form for onboarding and Account, code boxes, plan card)
storageManager.js, storageKeys.js, pageKey.js Storage abstraction, key registry, page keys for highlights
localIntelligence.js, localSearch.js, tagIntelligence.js, duplicateDetector.js, textMetrics.js, textUtils.js, dateUtils.js, dom.js, log.js On-device search, tags, duplicates and shared helpers
audioManager.js, podcastManager.js, readingTools.js, reader.js, instantRead.js, citation.js, paperInfo.js, sendSheet.js, localSendClient.js Read aloud, podcasts, reader, citations, send to devices
i18n.js, feedI18n.js, languages.js, a11y.js, extensionApi.js Translations, language data, accessibility settings, browser API shim

Build pipeline

  • Dev sync — node plugin/scripts/build.js (bundles content.js and finalize.js; loader.js and the popup modules are copied as they are) (or npm run build) copies plugin/src/ into plugin/dev/aish-extension-<platform>/ and overlays the matching plugin/platforms/<platform>/manifest.json.
  • Release — ./plugin/build.sh bumps the version in current_version.json + all plugin/platforms/*/manifest.json + plugin/src/popup.html, then zips each platform build into plugin/prod/.
  • CI — .github/workflows/release.yml runs plugin/build.sh on tag push, commits the version bump, and creates a GitHub release with the three zips.

Cross-browser notes

  • plugin/src/ is written against the chrome.* namespace. A tiny shim at the top of content.js, background.js, and popup.js aliases chrome → browser when only browser.* exists (Safari/iOS), so the same code runs on every platform.
  • Firefox uses background.scripts (event page) + a gecko.id; Chrome uses service_worker + sidePanel; Android/iOS omit sidePanel.

Roadmap

Shipped features and checklist

Bookmarklet generator generally ships faster since it is faster to iterate on.

Browser Plugin

  • Summarize page via popup or keyboard shortcut
  • Custom prompt per request + saved default prompt
  • Multi-language summary output
  • Summary length slider
  • Article history with search and graph visualization
  • Text highlighting (yellow / AI ghost highlights) with per-page persistence
  • Enable/disable highlighting toggle in settings
  • Context menu: Highlight selection, Remove all highlights, Summarize page, Summarize & close tab
  • Save for Later — right-click any tab → "Summarize & Close": generates summary, saves with timeframe reminder (tomorrow / weekend / week / research session), closes the tab, shows in history with metadata
  • RSVP Speed-reading overlay — while summarizing on close, the AI output streams word-by-word as a speed-reading display; adjustable speed (slow/medium/fast) saved between sessions
  • Timed reminders — Chrome notifications remind you to revisit saved articles at your chosen timeframe
  • Backup & Restore (v2 format: settings + full article history)
  • Inline mode ("Send to Kindle" friendly)
  • Native side panel support
  • Graph view of article archive (D3.js, keyword-based)
  • Analytics Report — reading activity heatmap, top topics, streaks, model usage stats, per-article or full-archive view
  • Multi-provider support: OpenAI, Mistral, Deepseek, Ollama, byphil Cloud (no API key needed)

Bookmarklet Generator

  • Save API Key in Browser
  • iOS compatibility
  • Select dom element by clicking to make insertion-point be definable by user
  • Add status state
  • Support other providers (on-device? What are some local LLMs we could use for this / API through localhost?)
    • Ollama
  • byPhil Cloud login (email magic code, no API key)
  • Dynamic model list from the byPhil Cloud API
  • Always insert summary on page + optional share (system share / Kindle / LocalSend)
  • Include update mechanism (bookmarklet checks bookmarklet-version.json and warns when outdated)

Troubleshooting

Ollama: “Failed to fetch” (CORS)

It looks like you've hit the classic CORS (Cross-Origin Resource Sharing) wall. Even though the configuration is correct, the browser blocks requests from websites (like arxiv.org) to your local Ollama instance for security reasons because Ollama isn't explicitly saying "I allow requests from this website."

Since the extension's content script runs directly on the page, its "Origin" is the website you are visiting, and Ollama rejects it by default.

To fix this, you need to set the OLLAMA_ORIGINS environment variable. Here is the breakdown based on your operating system:

1. Windows (Most Common Issue)

  1. Quit Ollama entirely. Look for the Ollama icon in your System Tray (bottom right, near the clock), right-click it, and select Quit.
  2. Open the Start Menu, search for "Edit the system environment variables," and open it.
  3. Click Environment Variables.
  4. Under User variables, click New:
    • Variable name: OLLAMA_ORIGINS
    • Variable value: chrome-extension://*,moz-extension://*,safari-web-extension://*
  5. Click OK on all windows.
  6. Crucial: Open a new Terminal or Command Prompt and type ollama serve (or simply relaunch the Ollama app from the Start menu).

2. MacOS

  1. Quit Ollama from the Menu Bar icon.
  2. Open Terminal and run:
    launchctl setenv OLLAMA_ORIGINS "chrome-extension://*,moz-extension://*,safari-web-extension://*"
  3. Restart the Ollama application.
  4. Check it (you should see 200; 403 means Ollama has not picked up the setting yet, so quit and start it again):
    curl -s -o /dev/null -w "%{http_code}\n" http://localhost:11434/api/tags -H "Origin: chrome-extension://test"

Make it permanent. launchctl setenv is forgotten when you restart your Mac. This login item sets it again at every login:

cat > ~/Library/LaunchAgents/com.ollama.origins.plist <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0"><dict>
<key>Label</key><string>com.ollama.origins</string>
<key>ProgramArguments</key><array><string>/bin/sh</string><string>-c</string>
<string>launchctl setenv OLLAMA_ORIGINS "chrome-extension://*,moz-extension://*,safari-web-extension://*"</string></array>
<key>RunAtLoad</key><true/>
</dict></plist>
EOF
launchctl load ~/Library/LaunchAgents/com.ollama.origins.plist

Then quit and restart Ollama once.

Running ollama serve yourself while the Ollama app is still open fails with "address already in use". Quit the app first (or pkill -x ollama; lsof -i :11434 shows what holds the port).

3. Linux (Systemd)

If you are running Ollama as a service:

  1. Run sudo systemctl edit ollama.service.
  2. Add these lines under the [Service] section:
    [Service]
    Environment="OLLAMA_ORIGINS=chrome-extension://*,moz-extension://*,safari-web-extension://*"
  3. Save and exit, then run:
    sudo systemctl daemon-reload
    sudo systemctl restart ollama

🧐 Why is this happening? Browsers follow a "Same-Origin Policy." When the extension tries to fetch localhost:11434, the browser sends a "Preflight" request (an OPTIONS check) to see if the server allows it. If OLLAMA_ORIGINS isn't set, Ollama doesn't include the Access-Control-Allow-Origin header in its response, and the browser kills the request. Setting it to the extension origins above tells Ollama to accept requests from browser extensions only, not from arbitrary websites. * also works but lets every website call your local Ollama.

Use Ollama via HTTPS (Advanced)

If you are running Ollama on a remote server behind an HTTPS proxy (like Nginx, Apache, or Cloudflare), you normally do not need to set OLLAMA_ORIGINS on the server itself. Instead, ensure your proxy is configured to allow CORS headers:

  • Why? Browsers block "Mixed Content" (requesting HTTP from an HTTPS site). Using an HTTPS endpoint for Ollama solves this.
  • How? Add Access-Control-Allow-Origin: * to your proxy configuration settings.

For more detailed guidance, refer to the comprehensive guide on handling CORS settings in Ollama here.

Privacy

Summaries go only to the AI provider you configure (or fully local with Ollama). Your archive, search index and settings stay on your device. Read the full privacy policy.

License

2024 Phil Wornath - MIT License

Third-Party Libraries

This extension vendors the following open-source libraries under plugin/src/lib/. Full license texts: THIRD_PARTY_LICENSES.md

Library Version License Source
pdf.js 4.0.379 Apache-2.0 https://github.com/mozilla/pdf.js
D3.js 7.9.0 ISC https://github.com/d3/d3
AFINN-111 — Apache-2.0 https://github.com/fnielsen/afinn

These libraries are vendored by hand (not via npm), so there's no automatic drift detection. When you replace any lib/* file, bump the version numbers above and in THIRD_PARTY_LICENSES.md — the table is the only record of what's actually shipped.

Compatible Tools

Tools that are compatible with AI Summary Helper. Feel free to add your own tool to the list.

Name Description URL
Reabble Send to Kindle Send your summarized articles to Kindle. https://send.reabble.com/
Web Clipper Clip your summarized web pages to different places (e.g. OneNote, Notion, GitHub etc.) https://clipper.website/
Inoreader RSS Feed Reader https://www.inoreader.com/

来源:GitHub · github.com