Files
LexAI/docs/ARCHITECTURE.md
john kevin asprec acea99d7ad
Some checks failed
CI — Test & Build / Test & Build (push) Failing after 39s
feat: Implement Prompt Builder functionality in Popup and Options
- Added a new "Prompt Builder" tab in the Popup for generating AI prompts with customizable parameters.
- Introduced new state variables for managing prompt styles, personas, formats, and models.
- Enhanced the Options page to fetch and display models based on the provided API key.
- Updated the actions and types to include the new 'prompt' action and its associated parameters.
- Implemented migration logic for legacy plaintext API keys to encrypted storage.
- Updated the getSystemPrompt function to incorporate prompt parameters for better instruction generation.
- Added tests for the new functionality, including context menu entries and prompt generation logic.
2026-07-15 15:27:41 +08:00

5.0 KiB
Raw Blame History

Architecture — LexAI

The current system and its important boundaries. Update in the same change that alters behavior; log the reason in DECISIONS.md.

System at a glance

  • Shape: Browser extension (Chrome, Manifest V3) with three cooperating contexts message-passed over chrome.runtime. No backend.
  • Stack: WXT ^0.20 (wraps Vite), React 18 + TypeScript (Options/Popup pages only), tweetnacl/tweetnacl-util for key encryption, Zustand installed but unused.
  • Data stores: chrome.storage.local (provider, model, apiKeyEnc, encKey, legacy plaintext apiKey). No server, no DB.
  • Hosting / deploy: Chrome Web Store. Build output .output/chrome-mv3/.
  • Build / release: Gitea CI (.gitea/workflows/) → typecheck → test → build → zip to Gitea registry; v*.*.* tag → Chrome Web Store deploy.

Component map

entrypoints/content.ts   (content script, injected into <all_urls>)
  • Detects selection: textarea/input (selectionStart/End) vs contenteditable/DOM (Range API)
  • Renders floating toolbar + result modal + toasts (inline-styled, appended to document.body)
  • Snapshots selection state BEFORE any async call; Replace uses the snapshot
  • Sends { type: 'ANALYZE_TEXT', payload: {text, action, style} } to background

entrypoints/background.ts  (service worker — the LLM proxy)
  • onMessage: ANALYZE_TEXT and COPY_AS  (returns true to keep async channel open)
  • Reads provider/apiKey/apiKeyEnc/encKey/model from chrome.storage.local
  • Decrypts key (tweetnacl secretbox), routes to the correct provider fetch
  • Registers right-click context menus (action × style) on install

entrypoints/options/Options.tsx  (React settings page)
  • Provider + model + API key form; encrypts key → apiKeyEnc/encKey in storage

entrypoints/popup/Popup.tsx  (React toolbar popup)
  • Standalone text box → same ANALYZE_TEXT flow; shows config status; links to Options
Component Responsibility Owns (paths) Talks to Notes
content script selection, toolbar/modal UI, replace entrypoints/content.ts background via messages DOM-timing-sensitive; snapshot before await
background SW LLM proxy, key decrypt, routing, context menus entrypoints/background.ts provider APIs, storage only context allowed to fetch providers
options page provider/model/key config + encrypt entrypoints/options/Options.tsx storage React
popup standalone analyze + status entrypoints/popup/Popup.tsx background via messages React
shared utils reference notes, future shared code src/utils/** see REFERENCE_NOTES.md

Boundaries and contracts

  • CORS/key boundary: Content script and popup must not call provider APIs. All provider fetch and key handling live in background.ts. Route through ANALYZE_TEXT / COPY_AS.
  • Message contract:
    • ANALYZE_TEXT accepts both { payload: {text, action, style} } and flat { text, action, style } — keep both if you touch the handler.
    • actiongrammar | rephrase | shorten | expand | explain. Context menu/popup emit fix, normalized to grammar by getSystemPrompt.
    • The onMessage listener must return true to keep the async channel open; removing it silently breaks every response.
  • Provider layer: Each provider is duplicated — callX (system prompt from action) and callXWithPrompt (arbitrary prompt, used by COPY_AS). Request-shape changes usually need both. (Known smell — see DECISIONS + TASKS.)
  • DOM guard: Every injected node carries data-lexai="true"; handlers check closest('[data-lexai="true"]') to avoid self-triggering. z-index: 2147483647 keeps UI above host pages.

Data model (essentials)

  • Storage keys: provider, model, apiKeyEnc, encKey, apiKey (legacy plaintext, back-compat only).
  • Sensitive data: the LLM API key. Prefer the encrypted path; never log it; never transmit except to the user's selected provider. Don't drop the plaintext fallback without a migration.

Cross-cutting concerns

  • Config/secrets: provider list + default models + endpoints currently duplicated across Options.tsx and background.ts (drift risk — see TASKS).
  • Observability: intentional [LexAI …] console logs in content.ts replace path (should be gated behind a DEV flag — see TASKS).
  • Testing: Vitest (jsdom) unit + Playwright e2e. Unit tests currently exercise the chrome.storage mock rather than importing real handlers; e2e has [EXTENSION_ID] placeholders and won't pass as-is (see TASKS/EVALS).

Known constraints and debt

  • Tailwind inactive; all UI is inline style objects (WXT PostCSS never wired). Do not assume Tailwind classes work.
  • No shadow DOM; UI injected directly into document.body, isolated only by data-lexai + max z-index.
  • Version bump is a manual two-file edit (package.json + wxt.config.ts).

Record non-obvious choices in DECISIONS.md; keep exposure current in attacksurface.md.