From 3d605293b5e33e734a7d6c6d749ab2f72b020628 Mon Sep 17 00:00:00 2001 From: Bobby Date: Tue, 23 Sep 2025 01:03:19 +0530 Subject: rudimentary popup extension --- src/background.ts | 68 ++++++++++++++++++++++++++++++++++++++++++++++++ src/manifest.json | 20 +++++--------- src/options/options.html | 35 +++++++++++++++++++++++++ src/options/options.ts | 27 +++++++++++++++++++ src/popup/popup.html | 37 ++++++++++++++++++++++++++ src/popup/popup.ts | 18 +++++++++++++ 6 files changed, 192 insertions(+), 13 deletions(-) create mode 100644 src/background.ts create mode 100644 src/options/options.html create mode 100644 src/options/options.ts create mode 100644 src/popup/popup.html create mode 100644 src/popup/popup.ts 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 { + 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": "thunderbird-ai-compose@applications.thatcomputerscientist.com" 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 @@ + + + + + AI Compose Settings + + + +

Settings

+ + + +

+ + + + + 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 { + const settings: Settings = await browser.storage.local.get(["proxyEndpoint", "proxyToken"]); + endpointInput.value = settings.proxyEndpoint ?? ""; + tokenInput.value = settings.proxyToken ?? ""; +} + +async function saveSettings(): Promise { + 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 @@ + + + + + AI Prompt + + + +

Enter Prompt

+ +
+ +
No data yet
+ + + + + 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 => { + 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)); +}); -- cgit v1.2.3