feat: LEXAI-13 API encryption, LEXAI-11 context menu, LEXAI-12 tone analysis

- LEXAI-13: TweetNaCl secretbox encryption for API keys in Options + background
  - getOrCreateEncKey() generates per-device 32-byte key stored in chrome.storage.local
  - Keys saved as nonce+ciphertext (base64) under apiKeyEnc
  - Backward compat: falls back to plaintext apiKey if no encrypted key found
  - Lock icon 🔒 shown in Options label when key is encrypted
- LEXAI-11: Right-click context menu with Fix, Rephrase, Shorten, Expand, Tone
  - Registered via onInstalled in background service worker
  - Sends lexai-context-menu message to content script
  - Content script listener added at bottom of main()
- LEXAI-12: Tone analysis button added to floating toolbar (teal #2dd4bf)
  - Analysis-only: showModal called with null originalText = no Replace button
  - Tone system prompt updated to pure analysis (no rewrite)
This commit is contained in:
Forge
2026-03-06 15:06:43 +08:00
parent 004819472d
commit 0273356f4d
3 changed files with 192 additions and 29 deletions

View File

@@ -186,6 +186,7 @@ export default defineContentScript({
{ label: '↺ Rephrase', action: 'rephrase', color: '#89b4fa' },
{ label: '↓ Shorten', action: 'shorten', color: '#fab387' },
{ label: '↑ Expand', action: 'expand', color: '#cba6f7' },
{ label: '🎭 Tone', action: 'tone', color: '#2dd4bf' },
];
actions.forEach(({ label, action, color }) => {
@@ -340,7 +341,13 @@ export default defineContentScript({
if (response?.error) {
showModal(`${response.error}`, null, snapStart, snapEnd, snapElement, snapRange);
} else {
showModal(response?.result ?? '(no result)', textToProcess, snapStart, snapEnd, snapElement, snapRange);
// Tone analysis is display-only — no Replace button (pass null for originalText)
const isToneAction = action === 'tone';
showModal(
response?.result ?? '(no result)',
isToneAction ? null : textToProcess,
snapStart, snapEnd, snapElement, snapRange,
);
}
} catch (err) {
hideToolbar();
@@ -653,5 +660,18 @@ export default defineContentScript({
}
}
});
// ─── Context menu trigger from background ─────────────────────────────────
chrome.runtime.onMessage.addListener((message) => {
if (message.type === 'lexai-context-menu') {
selectedText = message.text as string;
// For context menu, we have no DOM selection positions — zero them out
storedStart = -1;
storedEnd = -1;
storedElement = null;
storedRange = null;
runAction(message.action as string);
}
});
},
});