Skip to content

Repository files navigation

Catchup

Catchup is a local-first Chrome side-panel extension focused on one question: “I zoned out. What just happened?”

While a Google Meet is running, Catchup keeps an ephemeral, timestamped 10-minute transcript. Google Meet's visible live captions are the preferred source. If usable captions do not appear, Catchup automatically falls back to browser-local Whisper transcription. The four primary actions explain only the last 30 seconds, 1 minute, 3 minutes, or 5 minutes.

There is no Catchup backend, hosted proxy, account, authentication, database, telemetry service, or server to deploy. Build the extension and load dist/ directly into Chrome.

Architecture

Google Meet
  ├─ visible live captions
  │    -> MeetCaptionsProvider content script
  │
  └─ captions unavailable or stopped
       -> chrome.tabCapture
       -> LocalWhisperProvider (Transformers.js, WebGPU or local WASM/CPU)

MeetCaptionsProvider | LocalWhisperProvider
  -> TranscriptProviderManager
  -> normalized timestamped TranscriptSegments
  -> 10-minute in-memory rolling buffer
  -> exact user-selected time window
  -> Local WebLLM | local Ollama | user's Gemini API | user's OpenAI-compatible API
  -> side-panel explanation

Both transcript providers implement the same abstraction and emit:

interface TranscriptProvider {
  start(): Promise<void>;
  stop(): Promise<void>;
  onSegment(callback: (segment: TranscriptSegment) => void): () => void;
  onStatus(callback: (status: TranscriptProviderStatus) => void): () => void;
  getStatus(): TranscriptProviderStatus;
}

interface TranscriptSegment {
  id: string;
  startTime: number;
  endTime: number;
  text: string;
  speaker?: string;
}

The transcript buffer and explanation providers do not know which transcription source produced a segment.

Privacy model

  • The content script reads only the live-caption text visibly rendered in the active meet.google.com page. It does not call undocumented Meet network APIs or Google's internal transcript APIs.
  • If Meet captions work, Catchup does not start tab audio capture or Whisper.
  • Whisper fallback processes captured tab audio locally. Raw audio chunks are held only long enough for transcription and are never written to disk.
  • Timestamped segments live in a rolling in-memory buffer and disappear with the offscreen extension session.
  • The default explanation provider is a local WebLLM/WebGPU model. No API key is required.
  • Ollama requests go directly to the configured local Ollama instance.
  • A transcript leaves the machine only after the user explicitly selects Gemini or an OpenAI-compatible provider and requests an explanation.
  • Cloud requests contain only the selected 30-second, 1-minute, 3-minute, or 5-minute transcript window—not the full meeting transcript.
  • User API settings are stored only in chrome.storage.local. Catchup does not log transcripts, API keys, or provider request bodies.

Only model weights and locally configured provider settings may be cached. Catchup does not persist meeting audio or transcript history.

Prerequisites

  • Node.js 20 or newer
  • Google Chrome 116 or newer
  • An internet connection for npm install and the first download of each selected local model

Meet captions are the lightest transcription path and do not require WebGPU. Local Whisper prefers WebGPU but has a slower local CPU/WASM fallback. Local explanations require WebGPU; Ollama or a user-configured API can be used when browser WebGPU is unavailable.

Build and install the unpacked extension

From this directory:

npm install
npm test
npm run build

Then:

  1. Open chrome://extensions.
  2. Enable Developer mode.
  3. Click Load unpacked and select this project's dist directory.
  4. Pin Catchup if desired.
  5. Open or reload a meeting at https://meet.google.com/ after loading the extension.
  6. Enable Google Meet live captions using Meet's normal caption control.
  7. While the Meet tab is active, click the Catchup toolbar icon. This opens the side panel and grants temporary tab-audio access in case Whisper fallback is needed.
  8. Click Start capture in the side panel.
  9. The status will show Using Meet captions when caption text is detected, or Using local Whisper if captions remain unavailable.
  10. Click Explain last 30 sec, 1 min, 3 min, or 5 min.

No server process should be started. After source changes, rebuild and click Reload for Catchup on chrome://extensions; reload any already-open Meet tab so the updated content script is present.

How Meet caption transcription works

The Meet-only content script starts a MutationObserver when Catchup starts. It waits for caption containers that may appear later, extracts caption text and available speaker names, and normalizes them into timestamped segments.

Meet updates partial captions repeatedly. Catchup therefore:

  • waits briefly for a meaningful partial to settle;
  • updates a stable segment ID when the same caption grows;
  • merges Meet's rolling suffix/prefix text;
  • deduplicates immediate DOM rerenders;
  • detects caption containers dynamically instead of querying once at page load;
  • reports unavailable, available-but-waiting, active, stopped, and error states.

All Meet DOM selectors are isolated in MeetCaptionsProvider. The current implementation supports Meet's caption jsname structure plus semantic fixture selectors, so future Meet DOM changes can be handled in one file.

Catchup gives captions 12 seconds to produce usable text before starting Whisper. It does not switch after a one- or two-second delay. If previously active captions disappear or stop updating for the configured stale interval, the manager activates Whisper. If caption text resumes later, the manager prefers Meet captions again and stops tab audio capture.

Caption timestamps are approximate segment times, not word-level alignment. Caption language, availability, and recognition quality are controlled by Google Meet.

Local Whisper fallback

Whisper fallback uses onnx-community/whisper-tiny.en through Transformers.js. It starts only after the caption grace period expires or active captions stop. The model downloads on first use and is cached by Chrome for later sessions. Expect roughly 160 MB of model weights plus browser cache/runtime overhead.

Catchup checks for a real WebGPU adapter before selecting GPU transcription. If Chrome exposes navigator.gpu but cannot provide an adapter, Catchup skips that path and loads the packaged WASM/CPU runtime. The CPU fallback uses the compatible full-precision tiny model because the current quantized Whisper decoder is rejected by the bundled ONNX Runtime optimizer.

Captured tab audio is split into short standalone in-memory chunks. Audio is routed back to the local output so starting tab capture does not mute the meeting. Raw chunks are discarded after local transcription.

The included Whisper model is English-only. Noisy audio, overlapping speakers, specialized names, and low-powered CPU fallback can reduce accuracy or cause transcription to lag behind real time. Tab capture normally includes the Meet tab's output, not the user's separate microphone input; Meet captions may include the user's speech when Meet itself captions it.

Rolling transcript and catch-up windows

The in-memory TranscriptBuffer keeps segments overlapping the most recent 10 minutes and preserves speaker names when present. getWindow(seconds) uses the request time and returns only segments overlapping that recent range.

The side panel sends that already-filtered array to the selected ExplanationProvider. For example, Explain last 1 min cannot expose an older part of the rolling transcript because the provider never receives it.

The explanation prompt requests:

  • a short title;
  • 2–5 concise explanatory bullets;
  • explicit decisions or conclusions when present;
  • useful concepts or terms when relevant;
  • an insufficient-transcript response instead of invented context.

Local WebGPU explanations (default)

Open Settings, select Local WebGPU, choose a model, and click Download / load model. An explanation request also loads the model automatically when needed.

Available browser-sized options:

  • SmolLM2 360M: default and lowest memory requirement, about 580 MB runtime VRAM
  • Qwen 2.5 0.5B: about 1.1 GB runtime VRAM
  • Llama 3.2 1B: about 1.2 GB runtime VRAM

Weights can be hundreds of megabytes and the first load may take time. WebLLM caches them in Chrome. Inference stays on the user's GPU after model files are downloaded. Catchup never silently switches a local explanation request to a cloud provider.

Ollama

  1. Install and start Ollama.

  2. Pull the configured model, for example:

    ollama pull llama3.2:3b
  3. Allow the extension origin. If needed, launch Ollama with:

    OLLAMA_ORIGINS=chrome-extension://* ollama serve

    For tighter access, replace the wildcard with Catchup's exact chrome-extension://<extension-id> origin shown on chrome://extensions.

  4. In Catchup Settings, select Ollama.

  5. Keep the default base URL http://127.0.0.1:11434 or enter the address of the local instance.

  6. Enter an installed model name, save, and request an explanation.

Catchup calls Ollama's local /api/chat endpoint directly. If Ollama is stopped, inaccessible, or missing the selected model, the side panel shows a provider-specific error.

Gemini API

This optional provider calls Google's Gemini API directly with the user's own API key.

  1. Open Settings and select Gemini.
  2. Keep the default API base URL https://generativelanguage.googleapis.com/v1beta, or enter a compatible endpoint.
  3. Enter a Gemini API key and model name.
  4. Save settings and approve Chrome's narrowly scoped host-access prompt.
  5. Request an explanation.

The key is stored only in chrome.storage.local and sent in the request header directly to the configured Gemini host. It is never bundled, logged, or sent to a Catchup service.

OpenAI-compatible API

This optional provider supports OpenAI, OpenRouter, and other chat-completions-compatible services using user-supplied credentials.

  1. Open Settings and select OpenAI-compatible.
  2. Enter the API base URL, including /v1 when required.
  3. Enter the API key and model name.
  4. Save settings and approve host access for that origin.
  5. Request an explanation.

Catchup sends a request directly to <base URL>/chat/completions. There are no developer-owned keys, production VITE_* secrets, or frontend environment-variable credentials.

Permissions

The Manifest V3 extension requests only the APIs used by this architecture:

  • sidePanel: show the Catchup UI.
  • storage: store local provider settings and user-supplied credentials.
  • offscreen: keep the ephemeral provider manager/buffer alive and host local audio processing.
  • activeTab and tabCapture: capture the current Meet tab only when Whisper fallback is needed. The toolbar click grants temporary access to that tab.
  • https://meet.google.com/*: inject the caption observer only on Meet pages.
  • model-host origins: download local Whisper/WebLLM model data.
  • optional HTTP/HTTPS host access: requested only when the user saves a configured Ollama or cloud-provider origin.

The content script match pattern is restricted to https://meet.google.com/*.

Tests and development-only fixtures

Run the automated suite:

npm test

Tests cover caption detection, speaker extraction, partial mutation settling, rerender deduplication, timestamps, provider status changes, caption-to-Whisper fallback, caption resumption, 10-minute retention, exact catch-up windows, and cloud request privacy.

Test UI is deliberately excluded from production dist/. Build the separate development extension:

npm run build:test

Load dist-test/ as an unpacked extension. Its Local test mode supports two workflows:

  1. Simulated transcript: paste plain-text or timestamped JSON segments, load the buffer, press a catch-up button, and inspect the exact segments passed to the explanation provider.
  2. Meet caption DOM fixture: click Open Meet caption DOM fixture to open a local page where speaker/text updates, partial-caption mutations, duplicate rerenders, and caption removal/restoration can be simulated. The page shows provider state transitions and normalized emitted segments.

The fixture uses the same MeetCaptionsProvider source as production but is emitted only by the test-extension Vite mode.

Development commands

npm run dev        # Vite UI development; Chrome APIs still require extension context
npm test           # unit and provider behavior tests
npm run build      # type-check and create production dist/
npm run build:test # type-check and create development-only dist-test/

The build packages ONNX Runtime's executable JavaScript/WASM inside the extension. Manifest V3 does not load remotely hosted executable modules. Downloaded model weights are data and are cached locally.

There is intentionally no .env setup, proxy command, Docker service, API route, database migration, authentication setup, or backend deployment configuration.

Limitations

  • Google Meet's caption DOM is not a documented public API and may change. Selectors and parsing are isolated in one provider and covered by a local DOM fixture.
  • Catchup does not turn Meet captions on automatically; enable them using Meet's caption control.
  • Local Whisper and small browser LLMs trade accuracy and reasoning quality for local resource use.
  • WebGPU availability and memory limits vary by browser, GPU, driver, operating system, and managed-browser policy.
  • CPU transcription fallback can lag on slower machines. Local explanation generation does not have a CPU fallback.
  • The transcript is intentionally ephemeral. Restarting Chrome or the offscreen extension session can clear it.
  • Catchup has no persistent meeting history, account, shared notes dashboard, analytics, or recovery of speech from before Catchup was started.

About

Instantly understand what you missed in a Google Meet

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages