ChatGPT for your browser history and open tabs. Local-first: everything stays on your machine.
Across indexes content from open tabs, stores semantic memory locally in IndexedDB, and lets you chat with everything you've viewed.
▶ Click here to watch the demo video on YouTube
- Tab Monitoring — auto-detects tabs, URL changes, activation, close
- Content Extraction — full-page text via Mozilla Readability + body fallback
- Semantic Chunking — heading-aware splitting with token overlap (800 target, 150 overlap)
- Pluggable Embeddings — Jina AI (free), Hugging Face (free), OpenAI
- Priority Queue — active > recent > pinned > background, debounced, retry with backoff
- AI Chat — RAG-based answers using Groq (free Llama 3), Hugging Face, OpenAI, Anthropic, or local
- Local-First — all data stored in IndexedDB on your device; no backend required
- Privacy — page content never leaves your machine unless you add an API key
- Node.js 18+
cd extension
npm install
npm run buildLoad in Chrome:
chrome://extensions→ Developer mode → Load unpacked- Select
extension/dist
Open the side panel → click the ⚙ gear icon. Add:
- Embeddings — a Jina AI key (free tier) for semantic search
- AI Chat — a Groq key (free, no credit card) for RAG answers
Without keys, the extension still indexes tabs and returns basic source-based answers.
| Provider | Settings Option | Quality | Cost |
|---|---|---|---|
| Jina AI | embeddingProvider: "jina" |
✅ Semantic | Free (1M tokens/month) |
| Hugging Face | embeddingProvider: "huggingface" |
✅ Semantic | Free (rate-limited) |
| OpenAI | embeddingProvider: "openai" |
✅ Semantic | Paid |
| Provider | Settings Option | Quality | Cost |
|---|---|---|---|
| Groq | llmProvider: "groq" |
✅ Llama 3.3 70B | Free (30 RPM, no credit card) |
| Hugging Face | llmProvider: "huggingface" |
✅ Mistral 7B | Free (rate-limited) |
| OpenAI | llmProvider: "openai" |
✅ GPT-4o-mini | Paid |
| Anthropic | llmProvider: "anthropic" |
✅ Claude | Paid |
| Local | llmProvider: "local" |
Free |
- Extract — Content script runs Mozilla Readability on each page; falls back to
<article>,<main>,<p>tags, thendocument.body.innerText - Chunk — Heading-aware splitting into ~800-token chunks with 150-token overlap
- Embed — Each chunk gets a vector embedding (Jina AI API using your key)
- Store — Chunks + embeddings stored locally in IndexedDB
- Query — User asks a question in the side panel
- Retrieve — Backend embeds the question, searches stored vectors for top-10 similar chunks
- Answer — Backend sends chunks + question to Groq (or configured LLM) for RAG response
Extension (React + TypeScript)
├── Background Service Worker
│ ├── TabMonitor — tab lifecycle tracking
│ ├── QueueManager — priority-based async processing
│ ├── ChunkingPipeline — heading-aware semantic chunking
│ ├── EmbeddingService — embed chunks + store in IndexedDB
│ └── SummarizationService — lazy tab summarization
├── Content Script
│ └── Readability extraction (Mozilla Readability)
├── Side Panel UI (React + Tailwind)
│ ├── ChatView / MessageBubble / ChatInput
│ ├── TabList — indexed tab browser
│ ├── SettingsView — provider + API key config
│ └── useChat — React hook for state management
├── Popup — quick access panel
└── Lib (shared utilities)
├── types.ts — all TypeScript interfaces
├── constants.ts — configuration values
├── embeddingProvider.ts — Jina / OpenAI / HF (direct API)
├── llmProvider.ts — Groq / OpenAI / Anthropic / HF / Local
├── settings.ts — chrome.storage.local settings
└── indexedDB.ts — all local storage
- TabMonitor detects new/updated tab → enqueues in QueueManager
- Content script extracts page text via Readability
- ChunkingPipeline splits into 800-token chunks with 150 overlap
- EmbeddingService sends chunks to chosen provider for embeddings
- Chunks + embeddings stored in IndexedDB
- User types question in ChatView → useChat sends CHAT_MESSAGE to background
- Background embeds the query via chosen embedding provider
- Cosine search over stored vectors returns top similar chunks
- Background sends chunks + question to chosen LLM for RAG answer
- Response returned to extension and displayed
- TabMonitor.initialize() loads tab state from IndexedDB
- Syncs with Chrome's open tabs via tabs.query
- Tabs with "pending" status enqueued for re-indexing
- TabMonitor detects tab removal or URL change
- Removes chunks, embeddings, summaries, and tab state from IndexedDB
Across has explicit timeouts on all external API calls to prevent hanging:
| Service | Timeout | Config |
|---|---|---|
| LLM APIs (Groq, OpenAI, Anthropic, HuggingFace) | 60s | lib/llmProvider.ts |
| Embedding APIs (Jina AI, HuggingFace, OpenAI) | 30s | lib/embeddingProvider.ts |
across/
├── extension/
│ ├── src/
│ │ ├── background/services/ # TabMonitor, QueueManager, Chunking, Embedding, Summarization
│ │ ├── content/ # Readability extraction + fallback
│ │ ├── sidepanel/ # ChatView, MessageBubble, ChatInput, TabList, SettingsView
│ │ ├── popup/ # Quick tab list panel
│ │ └── lib/ # Types, constants, embeddingProvider, llmProvider, settings, indexedDB
│ ├── assets/ # Extension icons
│ ├── scripts/ # build.mjs, dev.mjs
│ ├── manifest.json
│ └── package.json
├── docs/ # Privacy policy (hosted on GitHub Pages)
│ ├── privacy.html
│ └── index.html
├── backend/ # (optional, legacy) remote backend reference
├── AGENTS.md
└── README.md
The extension is local-first. Its privacy policy is hosted on GitHub Pages: https://graffian.github.io/Across/privacy.html
- Jina AI (embeddings): jina.ai/embeddings — sign up, 1M free tokens/month
- Groq (Llama 3 chat): console.groq.com — sign up, free tier, no credit card
- Hugging Face (embeddings + chat): huggingface.co/join — free inference API
MIT