From 8f17548fc0e1b41d725f2144e209ac8655d2afeb Mon Sep 17 00:00:00 2001 From: Bobby Date: Wed, 24 Sep 2025 16:14:00 +0530 Subject: improved styles. added icons to manifest. generate logic in place --- src/manifest.json | 12 +++++- src/options/options.html | 58 +++++++++++++++++++++++++---- src/popup/popup.html | 92 +++++++++++++++++++++++++++++++++++++--------- src/popup/popup.ts | 96 ++++++++++++++++++++++++++++++++++++------------ 4 files changed, 209 insertions(+), 49 deletions(-) (limited to 'src') diff --git a/src/manifest.json b/src/manifest.json index fb7c621..b0b4635 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -15,9 +15,19 @@ "background.js" ] }, + "icons": { + "16": "icons/16x16.png", + "32": "icons/32x32.png", + "48": "icons/48x48.png", + "128": "icons/128x128.png" + }, "compose_action": { "default_title": "Write with AI", - "default_popup": "popup/popup.html" + "default_popup": "popup/popup.html", + "default_icon": { + "16": "icons/16x16.png", + "32": "icons/32x32.png" + } }, "options_ui": { "page": "options/options.html", diff --git a/src/options/options.html b/src/options/options.html index bf70f4c..d317f1e 100644 --- a/src/options/options.html +++ b/src/options/options.html @@ -5,27 +5,71 @@ AI Compose Settings

Settings

- - + + + +

diff --git a/src/popup/popup.html b/src/popup/popup.html index 808bc9e..c8838ba 100644 --- a/src/popup/popup.html +++ b/src/popup/popup.html @@ -2,34 +2,92 @@ - AI Prompt + AI Compose -

Enter Prompt

- -
- -
No data yet
+

Compose with AI

+ +
+ +
+
diff --git a/src/popup/popup.ts b/src/popup/popup.ts index dc28bdc..e3d4e29 100644 --- a/src/popup/popup.ts +++ b/src/popup/popup.ts @@ -5,9 +5,22 @@ interface Payload { context: ComposeContext; } -const promptEl: HTMLTextAreaElement = document.getElementById("prompt") as HTMLTextAreaElement; -const sendBtn: HTMLButtonElement = document.getElementById("send") as HTMLButtonElement; -const outputEl: HTMLElement = document.getElementById("output") as HTMLElement; +interface Settings { + proxyEndpoint?: string; + proxyToken?: string; +} + +function getElements(): { + promptEl: HTMLTextAreaElement; + sendBtn: HTMLButtonElement; + spinner: HTMLElement; +} { + return { + promptEl: document.getElementById("prompt") as HTMLTextAreaElement, + sendBtn: document.getElementById("send") as HTMLButtonElement, + spinner: document.getElementById("spinner") as HTMLElement, + }; +} async function getActiveComposeTabId(): Promise { const tabs: browser.tabs.Tab[] = (await browser.tabs.query({ @@ -22,26 +35,17 @@ function insertAtReply(body: string, aiText: string, isHtml: boolean): string { const parser: DOMParser = new DOMParser(); const doc: Document = parser.parseFromString(body, "text/html"); const paragraphs: NodeListOf = doc.querySelectorAll("body > p"); - - let target: HTMLParagraphElement | null = null; for (const p of Array.from(paragraphs)) { if (p.textContent && p.textContent.trim().length > 0) { - target = p; - break; + p.insertAdjacentHTML("afterend", aiText); + return doc.body.innerHTML; } } - - if (target) { - target.insertAdjacentHTML("afterend", aiText); - return doc.body.innerHTML; - } - const citeDiv: HTMLElement | null = doc.querySelector("div.moz-cite-prefix"); if (citeDiv) { citeDiv.insertAdjacentHTML("beforebegin", aiText); return doc.body.innerHTML; } - doc.body.insertAdjacentHTML("afterbegin", aiText); return doc.body.innerHTML; } else { @@ -54,6 +58,11 @@ function insertAtReply(body: string, aiText: string, isHtml: boolean): string { } async function handleInsert(): Promise { + const { promptEl, sendBtn, spinner } = getElements(); + spinner.style.display = "block"; + sendBtn.disabled = true; + promptEl.disabled = true; + const ctx: ComposeContext = (await browser.runtime.sendMessage({ type: "getComposeContext", })) as ComposeContext; @@ -63,32 +72,71 @@ async function handleInsert(): Promise { context: ctx, }; + const settings: Settings = await browser.storage.local.get(["proxyEndpoint", "proxyToken"]); + if (!settings.proxyEndpoint) { + alert("Proxy endpoint is not configured in settings."); + spinner.style.display = "none"; + sendBtn.disabled = promptEl.value.trim().length === 0; + promptEl.disabled = false; + return; + } + const tabId: number | undefined = await getActiveComposeTabId(); if (tabId === undefined) { - outputEl.textContent = "No active compose tab."; + alert("No active compose tab."); + spinner.style.display = "none"; + sendBtn.disabled = promptEl.value.trim().length === 0; + promptEl.disabled = false; + return; + } + + let aiResult: string; + try { + const res: Response = await fetch(settings.proxyEndpoint + "/generate", { + method: "POST", + headers: { + "Content-Type": "application/json", + ...(settings.proxyToken ? { Authorization: `Bearer ${settings.proxyToken}` } : {}), + }, + body: JSON.stringify(payload), + }); + + if (!res.ok) { + throw new Error(`Server responded with ${res.status}`); + } + + const data: { response?: string } = (await res.json()) as { response?: string }; + if (!data.response) { + throw new Error("No response field in server reply."); + } + aiResult = data.response; + } catch (err) { + alert("Failed to fetch AI response: " + (err as Error).message); + spinner.style.display = "none"; + sendBtn.disabled = promptEl.value.trim().length === 0; + promptEl.disabled = false; return; } const isHtml: boolean = ctx.compose.isHtml ?? Boolean(ctx.compose.bodyHTML && ctx.compose.bodyHTML.trim()); - const aiResult: string = isHtml - ? "

This response will be inserted by AI

" - : "This response will be inserted by AI"; - - const replyPrefix: string | undefined = - ctx.compose.bodyPlain?.split("\n\n")[0].trim() || undefined; const oldBody: string = isHtml ? (ctx.compose.bodyHTML ?? "") : (ctx.compose.bodyPlain ?? ""); - const newBody: string = insertAtReply(oldBody, aiResult, isHtml); await (browser as any).compose.setComposeDetails(tabId, { body: newBody }); - outputEl.textContent = JSON.stringify(payload, null, 2); + spinner.style.display = "none"; + sendBtn.disabled = promptEl.value.trim().length === 0; + promptEl.disabled = false; } +const { sendBtn, promptEl } = getElements(); sendBtn.addEventListener("click", (): void => { handleInsert().catch((err: unknown): void => { console.error("Popup insert failed:", err); - outputEl.textContent = "Error inserting text. See console."; + alert("Error inserting text. See console for details."); }); }); +promptEl.addEventListener("input", (): void => { + sendBtn.disabled = promptEl.value.trim().length === 0; +}); -- cgit v1.2.3