# 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 = 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)│ └──────────────────────────────────────────────────────────┘ ↓ (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) ``` ## 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:` 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.