229 lines
12 KiB
Markdown
229 lines
12 KiB
Markdown
# 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 and display 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: Inline Translation on Message Display
|
|
|
|
The `translate_auto` preference controls when translation is triggered.
|
|
Translation always renders inline (webchat mode has been removed).
|
|
|
|
The target language is determined by `translate_lang` (fallback on `default_chatgpt_lang`).
|
|
|
|
```
|
|
User opens/selects a message in Thunderbird
|
|
↓
|
|
mzta-compose-script.js (sends "initTranslation" to background)
|
|
↓
|
|
mzta-background.js (checks translate + translate_auto prefs)
|
|
↓
|
|
┌──────────────────────────────────────────────────────────┐
|
|
│ translate_auto = 0 → do nothing │
|
|
│ translate_auto = 1 → show "click to translate" button │
|
|
│ translate_auto = 2 → generate immediately │
|
|
└──────────────────────────────────────────────────────────┘
|
|
↓
|
|
taTranslationStore (check cache / set processing)
|
|
↓ (cache miss)
|
|
mzta-special-commands (via Web Worker, NOT chatgpt_web)
|
|
↓
|
|
taTranslationStore (save result via taStorage)
|
|
↓
|
|
mzta-compose-script.js (render translation 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 / translateOnReceive)
|
|
_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
|
|
```
|
|
|
|
### Data Flow: Background Translation on Email Receive (translate_auto = 3)
|
|
|
|
When `translate_auto = 3`, a translation is generated silently when a new email arrives. Mirrors the summarize on-receive flow:
|
|
|
|
```
|
|
New email arrives
|
|
↓
|
|
browser.messages.onNewMailReceived
|
|
↓
|
|
newEmailListener (checks _process_incoming, which includes translate_auto === 3)
|
|
↓
|
|
processEmails({ translateOnReceive: true })
|
|
↓ (single loop — shared with addTagsAuto / spamFilter / summarizeOnReceive)
|
|
_generateTranslationForMessage(headerMessageId, null, { messageData })
|
|
← tabId is null → no UI messages sent, silent pre-cache
|
|
↓
|
|
taTranslationStore.saveTranslation()
|
|
↓
|
|
[later] user opens the message → initTranslation → cache hit → showTranslation 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, `buildTranslationPrompt()` for translation prompt assembly) |
|
|
| `js/mzta-compose-script.js` | Content script for compose and message display: injects AI response into compose window, renders unified toolbar (spam badge, summary/translation trigger buttons) and content panels (spam explanation, summary, translation) in message display via `#mzta-container` |
|
|
| `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-translationstore.js` | Translation-specific storage wrapper (`taTranslationStore` class) with caching, truncation, and processing-state tracking |
|
|
| `js/mzta-working-status.js` | Visual status indicator during AI processing |
|
|
| `js/mzta-addtags-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 |
|
|
| `translate/` | Email translation 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.
|
|
|
|
`js/mzta-translationstore.js` (`taTranslationStore` class) wraps `taStorage` for translation-specific operations: load/save/remove translations, track in-flight generation state via `browser.storage.session`, enforce a 100-entry cache limit with oldest-first truncation, and store error states. Each translation record stores `translated_text`, `lang`, and optional error information.
|