ThunderAI/claude-spec/01-architecture.md

8.7 KiB

Architecture

Extension Structure (Manifest V2)

ThunderAI runs as a standard Thunderbird WebExtension with three main execution contexts:

Background Page  →  mzta-background.html / mzta-background.js
Popup            →  popup/mzta-popup.html / popup/mzta-popup.js
Options Page     →  options/mzta-options.html / options/mzta-options.js
Feature Pages    →  pages/*/
Content Script   →  js/lib/diff.js (injected into chatgpt.com)
Web Workers      →  js/workers/model-worker-*.js (one per API provider)

Data Flow: User Action → AI Response

User clicks popup or presses Ctrl+Alt+A
       ↓
popup/mzta-popup.js   (renders prompt list, handles selection)
       ↓  (sendMessage to background)
mzta-background.js    (orchestrates everything)
       ↓
js/mzta-placeholders.js  (resolves {%placeholder%} values from email data)
       ↓
js/mzta-prompts.js       (builds final prompt string)
       ↓
 ┌─────────────────────────────────────────┐
 │  Based on connection_type:              │
 │  chatgpt_web   → js/mzta-chatgpt.js    │ (opens ChatGPT window)
 │  chatgpt_api   → Web Worker (openai)   │
 │  ollama_api    → Web Worker (ollama)   │
 │  google_gemini → Web Worker (gemini)   │
 │  anthropic     → Web Worker (anthropic)│
 │  openai_comp   → Web Worker (comp)     │
 └─────────────────────────────────────────┘
       ↓
 Result returned to background
       ↓
js/mzta-compose-script.js  (inserts text into Thunderbird compose window)

Data Flow: Inline Summary on Message Display

The summarize_display_mode preference ('inline' or 'webchat') controls where the summary is displayed. The summarize_auto preference controls when it is triggered.

  • summarize_auto = 2 (automatic) always generates inline, regardless of summarize_display_mode.
  • summarize_auto = 3 (on receive) pre-caches the summary silently when the email arrives via onNewMailReceived. When the user later opens the message, the cache hit triggers an instant display.
  • summarize_auto = 1 (manual button) respects summarize_display_mode:
    • 'inline' → button click triggers inline generation
    • 'webchat' → button click opens the AI chat window via _openSummaryWebchat()
  • Context menu summarize also respects summarize_display_mode:
    • 'inline' with a single message → generates inline via _generateSummaryForMessage()
    • 'webchat' or multiple messages → opens the AI chat window via openChatGPT()
User opens/selects a message in Thunderbird
       ↓
mzta-compose-script.js  (sends "initSummary" to background)
       ↓
mzta-background.js      (checks summarize_auto + summarize_display_mode prefs)
       ↓
  ┌──────────────────────────────────────────────────────────┐
  │ summarize_auto = 0 → do nothing                          │
  │ summarize_auto = 1 → show "click to generate" button     │
  │   display_mode = inline  → click triggers inline gen     │
  │   display_mode = webchat → click opens chat window       │
  │ summarize_auto = 2 → generate immediately (always inline)│
  │ summarize_auto = 3 → cache hit (pre-cached on receive)   │
  └──────────────────────────────────────────────────────────┘
       ↓  (if generating inline)
  taSummaryStore         (check cache / set processing)
       ↓  (cache miss)
  mzta-special-commands  (via Web Worker, NOT chatgpt_web)
       ↓
  taSummaryStore         (save result via taStorage)
       ↓
  mzta-compose-script.js (render summary banner in message body)

Data Flow: Background Summary on Email Receive (summarize_auto = 3)

When summarize_auto = 3, a summary is generated silently when a new email arrives. The flow mirrors add_tags_auto:

New email arrives
       ↓
browser.messages.onNewMailReceived
       ↓
newEmailListener  (checks _process_incoming, which includes summarize_auto === 3)
       ↓
processEmails({ summarizeOnReceive: true })
       ↓  (single loop — shared with addTagsAuto / spamFilter)
_generateSummaryForMessage(headerMessageId, null, { messageData })
  ← tabId is null → no UI messages sent, silent pre-cache
       ↓
taSummaryStore.saveSummary()
       ↓
[later] user opens the message → initSummary → cache hit → showSummary instantly

Key Modules

File Role
mzta-background.js Main orchestrator: listens for messages, coordinates all features
js/mzta-menus.js Context menu creation and management
js/mzta-prompts.js Prompt definitions (built-in) and custom prompt loading
js/mzta-placeholders.js Placeholder definitions and resolution logic
js/mzta-utils.js General utilities (email parsing, storage helpers, etc.)
js/mzta-utils-prompt.js Prompt-specific utilities (text truncation, lang injection, buildSummaryPrompt() for unified summary prompt assembly)
js/mzta-compose-script.js Content script for compose and message display: injects AI response into compose window, renders summary/spam banners in message display
js/mzta-chatgpt.js ChatGPT Web integration (opens browser window, reads DOM)
js/mzta-special-commands.js Handles special prompt actions (add_tags, calendar, task)
js/mzta-spamreport.js Spam filter logic
js/mzta-i18n.js i18n helper (wraps browser.i18n.getMessage)
js/mzta-logger.js Debug logging (gated by do_debug pref)
js/mzta-store.js Storage abstraction helpers
js/mzta-storage.js Unified per-message storage layer (taStorage class) for summary, spam, and translation data
js/mzta-summarystore.js Summary-specific storage wrapper (taSummaryStore class) with caching, truncation, and processing-state tracking
js/mzta-working-status.js Visual status indicator during AI processing
js/mzta-addatags-exclusion-list.js Tag exclusion list management
js/mzta-placeholders-autocomplete.js Autocomplete for placeholders in prompt editor

API Modules (js/api/)

Each file handles HTTP communication for one provider:

File Provider
anthropic.js Claude (Anthropic) API
google_gemini.js Google Gemini API
ollama.js Ollama (self-hosted)
openai_comp.js OpenAI-compatible APIs
openai_comp_configs.js Pre-configured providers (DeepSeek, Grok, Mistral, OpenRouter, Perplexity)
openai_responses.js OpenAI Responses API

Web Workers (js/workers/)

Each API provider has a dedicated Web Worker so API calls don't block the UI:

  • model-worker-anthropic.js
  • model-worker-google_gemini.js
  • model-worker-ollama.js
  • model-worker-openai_comp.js
  • model-worker-openai_responses.js

Workers receive a message with the prompt and settings, make the API call, and post back the result.

Feature Pages (pages/)

Each subdirectory is a self-contained settings/UI page for a specific feature:

Directory Feature
addtags/ Auto-tagging configuration
customprompts/ Custom prompt editor
customdataplaceholders/ Custom placeholder editor
get-calendar-event/ Calendar event extraction settings
get-task/ Task creation settings
spamfilter/ Spam filter settings
summarize/ Email summarization settings
onboarding/ First-run welcome page
_lib/ Shared libraries used by pages

Storage

All preferences are stored via browser.storage.local. The keys and default values are defined in options/mzta-options-default.js (prefs_default export). Custom prompts and custom placeholders are stored separately in storage under their own keys.

Per-Message Data Storage

Per-message data (summaries, spam reports, translations) is stored via js/mzta-storage.js (taStorage class). Each record is keyed by msg:<headerMessageId> in messenger.storage.local and follows schema version 1. Records contain optional fields: summary, spam, translation, plus metadata (v, ts). The taStorage class provides typed read/write/delete methods per field, automatic record cleanup when all fields are removed, and age-based cleanup.

js/mzta-summarystore.js (taSummaryStore class) wraps taStorage for summary-specific operations: load/save/remove summaries, track in-flight generation state via browser.storage.session, enforce a 100-entry cache limit with oldest-first truncation, and store error states.