diff options
| -rw-r--r-- | src/background.ts | 68 | ||||
| -rw-r--r-- | src/manifest.json | 20 | ||||
| -rw-r--r-- | src/options/options.html | 35 | ||||
| -rw-r--r-- | src/options/options.ts | 27 | ||||
| -rw-r--r-- | src/popup/popup.html | 37 | ||||
| -rw-r--r-- | src/popup/popup.ts | 18 |
6 files changed, 192 insertions, 13 deletions
diff --git a/src/background.ts b/src/background.ts new file mode 100644 index 0000000..997dd68 --- /dev/null +++ b/src/background.ts @@ -0,0 +1,68 @@ +type Identity = { + id?: string; + email?: string; + name?: string; +}; + +type ComposeContext = { + account: Identity; + compose: { + subject?: string; + to?: string[]; + cc?: string[]; + bcc?: string[]; + bodyPlain?: string; + bodyHTML?: string; + identityId?: string; + }; +}; + +async function getComposeContext(): Promise<ComposeContext> { + const data: ComposeContext = { account: {}, compose: {} }; + + try { + const tabs = await browser.tabs.query({ active: true, currentWindow: true }); + const tabId = tabs[0]?.id; + if (!tabId) return data; + + const details: any = await (browser as any).compose.getComposeDetails(tabId); + + data.compose.subject = details?.subject ?? ""; + data.compose.to = details?.to ?? []; + data.compose.cc = details?.cc ?? []; + data.compose.bcc = details?.bcc ?? []; + data.compose.identityId = details?.identityId ?? undefined; + data.compose.bodyPlain = details?.plainTextBody ?? ""; + data.compose.bodyHTML = details?.body ?? ""; + + const accounts: any[] = (browser as any).accounts?.list + ? await (browser as any).accounts.list() + : []; + + if (data.compose.identityId) { + for (const acc of accounts) { + for (const ident of acc.identities) { + if (ident.id === data.compose.identityId) { + data.account = { + id: ident.id, + email: ident.email, + name: ident.name, + }; + } + } + } + } + } catch (err) { + console.warn("getComposeContext failed:", err); + } + + return data; +} + +// Handle requests from popup/options +browser.runtime.onMessage.addListener(async (message: any) => { + if (message?.type === "getComposeContext") { + return await getComposeContext(); + } + return { error: "unknown message" }; +}); diff --git a/src/manifest.json b/src/manifest.json index e61aad4..fb7c621 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -1,5 +1,5 @@ { - "manifest_version": 3, + "manifest_version": 2, "name": "Thunderbird AI Compose", "version": "0.1.0", "description": "Thunderbird Mail Extension that helps you compose emails using AI", @@ -7,13 +7,15 @@ "compose", "messagesRead", "accountsRead", - "storage", - "commands" + "storage" ], "background": { - "service_worker": "background.js" + "scripts": [ + "browser-polyfill.js", + "background.js" + ] }, - "action": { + "compose_action": { "default_title": "Write with AI", "default_popup": "popup/popup.html" }, @@ -21,14 +23,6 @@ "page": "options/options.html", "open_in_tab": false }, - "commands": { - "open-popup": { - "suggested_key": { - "default": "Alt+I" - }, - "description": "Open AI composer popup" - } - }, "applications": { "gecko": { "id": "[email protected]" diff --git a/src/options/options.html b/src/options/options.html new file mode 100644 index 0000000..bf70f4c --- /dev/null +++ b/src/options/options.html @@ -0,0 +1,35 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <title>AI Compose Settings</title> + <style> + body { + font-family: sans-serif; + padding: 10px; + } + label { + display: block; + margin: 10px 0 4px; + } + input { + width: 100%; + padding: 6px; + } + button { + margin-top: 10px; + padding: 6px 12px; + } + </style> + </head> + <body> + <h2>Settings</h2> + <label>Proxy Endpoint <input id="endpoint" type="url" /></label> + <label>Auth Token <input id="token" type="text" /></label> + <button id="save">Save</button> + <p id="msg"></p> + + <script src="../browser-polyfill.js"></script> + <script src="options.js"></script> + </body> +</html> diff --git a/src/options/options.ts b/src/options/options.ts new file mode 100644 index 0000000..8574ab8 --- /dev/null +++ b/src/options/options.ts @@ -0,0 +1,27 @@ +type Settings = { + proxyEndpoint?: string; + proxyToken?: string; +}; + +const endpointInput: HTMLInputElement = document.getElementById("endpoint") as HTMLInputElement; +const tokenInput: HTMLInputElement = document.getElementById("token") as HTMLInputElement; +const saveBtn: HTMLButtonElement = document.getElementById("save") as HTMLButtonElement; +const msg: HTMLElement = document.getElementById("msg") as HTMLElement; + +async function loadSettings(): Promise<void> { + const settings: Settings = await browser.storage.local.get(["proxyEndpoint", "proxyToken"]); + endpointInput.value = settings.proxyEndpoint ?? ""; + tokenInput.value = settings.proxyToken ?? ""; +} + +async function saveSettings(): Promise<void> { + await browser.storage.local.set({ + proxyEndpoint: endpointInput.value.trim(), + proxyToken: tokenInput.value.trim(), + }); + msg.textContent = "Saved!"; + setTimeout(() => (msg.textContent = ""), 2000); +} + +saveBtn.addEventListener("click", saveSettings); +loadSettings(); diff --git a/src/popup/popup.html b/src/popup/popup.html new file mode 100644 index 0000000..808bc9e --- /dev/null +++ b/src/popup/popup.html @@ -0,0 +1,37 @@ +<!doctype html> +<html> + <head> + <meta charset="utf-8" /> + <title>AI Prompt</title> + <style> + body { + font-family: sans-serif; + padding: 10px; + } + textarea { + width: 100%; + height: 120px; + } + button { + margin-top: 8px; + padding: 6px 12px; + } + pre { + background: #f5f5f5; + padding: 6px; + max-height: 200px; + overflow: auto; + } + </style> + </head> + <body> + <h3>Enter Prompt</h3> + <textarea id="prompt"></textarea> + <br /> + <button id="send">Show Compose Context</button> + <pre id="output">No data yet</pre> + + <script src="../browser-polyfill.js"></script> + <script src="popup.js"></script> + </body> +</html> diff --git a/src/popup/popup.ts b/src/popup/popup.ts new file mode 100644 index 0000000..78866e9 --- /dev/null +++ b/src/popup/popup.ts @@ -0,0 +1,18 @@ +type Payload = { + prompt: string; + context: any; +}; + +const promptEl: HTMLTextAreaElement = document.getElementById("prompt") as HTMLTextAreaElement; +const sendBtn: HTMLButtonElement = document.getElementById("send") as HTMLButtonElement; +const outputEl: HTMLElement = document.getElementById("output") as HTMLElement; + +sendBtn.addEventListener("click", async (): Promise<void> => { + const ctx: any = await browser.runtime.sendMessage({ type: "getComposeContext" }); + const payload: Payload = { + prompt: promptEl.value, + context: ctx, + }; + outputEl.textContent = JSON.stringify(payload, null, 2); + alert(JSON.stringify(payload, null, 2)); +}); |
