From 0085e5dc12eda8e7b68023d2e1fc80602d8897d3 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Thu, 28 Aug 2025 14:19:50 -0400 Subject: [PATCH 01/10] Add gpt5 API helper and include site-wide --- _layouts/default.html | 1 + assets/gpt5.js | 92 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+) create mode 100644 assets/gpt5.js diff --git a/_layouts/default.html b/_layouts/default.html index 1ba9e91..0955d8b 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -91,6 +91,7 @@ Last updated: {{ page.last_updated }}

{% endif %} + {% include footer.html %} diff --git a/assets/gpt5.js b/assets/gpt5.js new file mode 100644 index 0000000..40d8262 --- /dev/null +++ b/assets/gpt5.js @@ -0,0 +1,92 @@ + From c1fe8eb99819e4ee46480b6f613f5f68ce6a4d47 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:03:25 -0400 Subject: [PATCH 02/10] Add ES5-safe gpt5 API helper and include site-wide --- assets/gpt5.js | 125 ++++++++++++++++++++++++------------------------- 1 file changed, 60 insertions(+), 65 deletions(-) diff --git a/assets/gpt5.js b/assets/gpt5.js index 40d8262..dbd8d97 100644 --- a/assets/gpt5.js +++ b/assets/gpt5.js @@ -1,92 +1,87 @@ - From bef0fc059d13c423960297c65cd8a5fa2c758905 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:28:19 -0400 Subject: [PATCH 03/10] Add AI Summary block to home; include gpt5-actions --- _layouts/default.html | 4 ++- assets/gpt5-actions.js | 67 ++++++++++++++++++++++++++++++++++++++++++ index.html | 4 +++ 3 files changed, 74 insertions(+), 1 deletion(-) create mode 100644 assets/gpt5-actions.js diff --git a/_layouts/default.html b/_layouts/default.html index 0955d8b..2e12ece 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -91,7 +91,9 @@ Last updated: {{ page.last_updated }}

{% endif %} - + + + {% include footer.html %} diff --git a/assets/gpt5-actions.js b/assets/gpt5-actions.js new file mode 100644 index 0000000..465af44 --- /dev/null +++ b/assets/gpt5-actions.js @@ -0,0 +1,67 @@ +// ES5-safe helpers that use window.gpt5 (from /assets/gpt5.js) +(function () { + if (!window.gpt5) { + console.error("gpt5 helper not found. Include /assets/gpt5.js first."); + return; + } + + function $(id) { return document.getElementById(id); } + + function textFromPage() { + var el = + document.querySelector("main") || + document.querySelector("article") || + document.querySelector(".content") || + document.querySelector(".post-content") || + document.getElementById("content") || + document.body; + var t = el && (el.innerText || el.textContent) || ""; + t = (t || "").replace(/\s+/g, " ").trim(); + if (t.length > 6000) t = t.slice(0, 6000); // keep the demo snappy + return t; + } + + function setBtn(disabled, label) { + var b = $("ai-summary-btn"); + if (b) { + b.disabled = !!disabled; + if (label != null) b.textContent = label; + } + } + + document.addEventListener("DOMContentLoaded", function () { + var btn = $("ai-summary-btn"); + var out = $("ai-summary"); + if (!btn || !out) return; + + btn.addEventListener("click", function () { + var content = textFromPage(); + if (!content) { + out.textContent = "Sorry—no readable content found on this page."; + return; + } + + out.textContent = "Thinking…"; + setBtn(true, "Summarizing…"); + var full = ""; + + gpt5.stream({ + system: + "You are LLbot for OraDigit.com. Summarize for executives and prospects. 5–7 bullet points. Clear, concrete, jargon-light.", + messages: [{ + role: "user", + content: "Summarize this page for a prospective client:\n\n" + content + }], + onToken: function (t) { + full += t; + out.textContent = full; + } + }) + .then(function () { setBtn(false, "AI Summary"); }) + .catch(function (e) { + out.textContent = "Error: " + (e && e.message ? e.message : e); + setBtn(false, "AI Summary"); + }); + }); + }); +})(); diff --git a/index.html b/index.html index 90276ad..731387d 100644 --- a/index.html +++ b/index.html @@ -145,6 +145,10 @@

Manage Your Cookie Preferences

} }); +
+ +
+
From 4a1fe1f5e9dcf626a97eae4562c2de8912f58b85 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:58:10 -0400 Subject: [PATCH 04/10] Scope workflows to main / PRs to main; allow manual runs --- .github/workflows/ai-maintenance-manual.yml | 7 +++++-- .github/workflows/ai-maintenance-run.yml | 5 +++-- .github/workflows/ai-maintenance-v3.yml | 7 +++++-- .github/workflows/ai-maintenance.yml | 11 ++++++----- .github/workflows/ai-ops.yml | 10 ++++++---- 5 files changed, 25 insertions(+), 15 deletions(-) diff --git a/.github/workflows/ai-maintenance-manual.yml b/.github/workflows/ai-maintenance-manual.yml index ba5f144..ec03907 100644 --- a/.github/workflows/ai-maintenance-manual.yml +++ b/.github/workflows/ai-maintenance-manual.yml @@ -1,7 +1,10 @@ -name: AI Site Maintenance (manual) +name: AI Site Maintenance (manual) on: + push: + branches: [ main ] + pull_request: + branches: [ main ] workflow_dispatch: - permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance-run.yml b/.github/workflows/ai-maintenance-run.yml index ceeabd8..de0bb6a 100644 --- a/.github/workflows/ai-maintenance-run.yml +++ b/.github/workflows/ai-maintenance-run.yml @@ -1,8 +1,9 @@ -on: +on: push: branches: [ main ] + pull_request: + branches: [ main ] workflow_dispatch: - permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance-v3.yml b/.github/workflows/ai-maintenance-v3.yml index e31f2b2..132811a 100644 --- a/.github/workflows/ai-maintenance-v3.yml +++ b/.github/workflows/ai-maintenance-v3.yml @@ -1,7 +1,10 @@ -name: AI Site Maintenance (v3, manual) +name: AI Site Maintenance (v3, manual) on: + push: + branches: [ main ] + pull_request: + branches: [ main ] workflow_dispatch: - permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance.yml b/.github/workflows/ai-maintenance.yml index 8e73194..14bbddc 100644 --- a/.github/workflows/ai-maintenance.yml +++ b/.github/workflows/ai-maintenance.yml @@ -1,9 +1,10 @@ -name: AI Site Maintenance +name: AI Site Maintenance on: - schedule: - - cron: "17 2 * * *" - workflow_dispatch: {} - + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-ops.yml b/.github/workflows/ai-ops.yml index e70a2a7..9bad832 100644 --- a/.github/workflows/ai-ops.yml +++ b/.github/workflows/ai-ops.yml @@ -1,8 +1,10 @@ -name: AI Ops Issue to PR +name: AI Ops Issue to PR on: - issues: - types: [opened, edited, labeled] - + push: + branches: [ main ] + pull_request: + branches: [ main ] + workflow_dispatch: permissions: contents: write pull-requests: write From e61b3225b852ac650099beb8e344271a3be1f9d6 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 11:58:35 -0400 Subject: [PATCH 05/10] ci: probe push on feature branch From 83d51a6ff3d0e94df48096ee618ab22628697e75 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:07:15 -0400 Subject: [PATCH 06/10] Stop maintenance workflows from auto-running: manual-only triggers --- .github/workflows/ai-maintenance-manual.yml | 5 +---- .github/workflows/ai-maintenance-run.yml | 5 +---- .github/workflows/ai-maintenance-v3.yml | 5 +---- .github/workflows/ai-maintenance.yml | 5 +---- .github/workflows/ai-ops.yml | 5 +---- 5 files changed, 5 insertions(+), 20 deletions(-) diff --git a/.github/workflows/ai-maintenance-manual.yml b/.github/workflows/ai-maintenance-manual.yml index ec03907..4d750db 100644 --- a/.github/workflows/ai-maintenance-manual.yml +++ b/.github/workflows/ai-maintenance-manual.yml @@ -1,10 +1,7 @@ name: AI Site Maintenance (manual) on: - push: - branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: + permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance-run.yml b/.github/workflows/ai-maintenance-run.yml index de0bb6a..6b7d759 100644 --- a/.github/workflows/ai-maintenance-run.yml +++ b/.github/workflows/ai-maintenance-run.yml @@ -1,9 +1,6 @@ on: - push: - branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: + permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance-v3.yml b/.github/workflows/ai-maintenance-v3.yml index 132811a..ed7a92f 100644 --- a/.github/workflows/ai-maintenance-v3.yml +++ b/.github/workflows/ai-maintenance-v3.yml @@ -1,10 +1,7 @@ name: AI Site Maintenance (v3, manual) on: - push: - branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: + permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-maintenance.yml b/.github/workflows/ai-maintenance.yml index 14bbddc..0f49989 100644 --- a/.github/workflows/ai-maintenance.yml +++ b/.github/workflows/ai-maintenance.yml @@ -1,10 +1,7 @@ name: AI Site Maintenance on: - push: - branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: + permissions: contents: write pull-requests: write diff --git a/.github/workflows/ai-ops.yml b/.github/workflows/ai-ops.yml index 9bad832..4c53bf2 100644 --- a/.github/workflows/ai-ops.yml +++ b/.github/workflows/ai-ops.yml @@ -1,10 +1,7 @@ name: AI Ops Issue to PR on: - push: - branches: [ main ] - pull_request: - branches: [ main ] workflow_dispatch: + permissions: contents: write pull-requests: write From dbf89026abd414e306b68ac92e3b7f085ebaabc9 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:07:55 -0400 Subject: [PATCH 07/10] ci: probe push on feature branch (should not run workflows) From dc13e6e7703a8395601764c00017f94f96ff8239 Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:37:45 -0400 Subject: [PATCH 08/10] Polish GPT streaming: spacing fix and Stop button; load gpt5.js?v=3 --- assets/gpt5-actions.js | 76 +++++++++++++++++++++++++----------------- assets/gpt5.js | 45 ++++++++++++++++--------- index.html | 7 ++-- 3 files changed, 80 insertions(+), 48 deletions(-) diff --git a/assets/gpt5-actions.js b/assets/gpt5-actions.js index 465af44..1594fb0 100644 --- a/assets/gpt5-actions.js +++ b/assets/gpt5-actions.js @@ -28,40 +28,54 @@ if (label != null) b.textContent = label; } } + document.addEventListener("DOMContentLoaded", function () { + var btn = document.getElementById("ai-summary-btn"); + var stop = document.getElementById("ai-summary-stop"); + var out = document.getElementById("ai-summary"); + if (!btn || !out || !stop) return; - document.addEventListener("DOMContentLoaded", function () { - var btn = $("ai-summary-btn"); - var out = $("ai-summary"); - if (!btn || !out) return; + function setBtn(disabled, label) { + btn.disabled = !!disabled; + if (label != null) btn.textContent = label; + } + + stop.addEventListener("click", function () { + try { gpt5.abort(); } catch (e) {} + setBtn(false, "AI Summary"); + }); + + btn.addEventListener("click", function () { + var el = + document.querySelector("main") || + document.querySelector("article") || + document.querySelector(".content") || + document.querySelector(".post-content") || + document.getElementById("content") || + document.body; + var content = el && (el.innerText || el.textContent) || ""; + content = (content || "").replace(/\s+/g, " ").trim(); + if (content.length > 6000) content = content.slice(0, 6000); - btn.addEventListener("click", function () { - var content = textFromPage(); - if (!content) { - out.textContent = "Sorry—no readable content found on this page."; - return; - } + if (!content) { + out.textContent = "Sorry—no readable content found on this page."; + return; + } - out.textContent = "Thinking…"; - setBtn(true, "Summarizing…"); - var full = ""; + out.textContent = "Thinking…"; + setBtn(true, "Summarizing…"); + var full = ""; - gpt5.stream({ - system: - "You are LLbot for OraDigit.com. Summarize for executives and prospects. 5–7 bullet points. Clear, concrete, jargon-light.", - messages: [{ - role: "user", - content: "Summarize this page for a prospective client:\n\n" + content - }], - onToken: function (t) { - full += t; - out.textContent = full; - } - }) - .then(function () { setBtn(false, "AI Summary"); }) - .catch(function (e) { - out.textContent = "Error: " + (e && e.message ? e.message : e); - setBtn(false, "AI Summary"); - }); + gpt5.stream({ + system: "You are LLbot for OraDigit.com. Summarize for executives and prospects. 5–7 bullet points. Clear, concrete, jargon-light.", + messages: [{ role: "user", content: "Summarize this page for a prospective client:\n\n" + content }], + onToken: function (t) { full += t; out.textContent = full; } + }) + .then(function () { setBtn(false, "AI Summary"); }) + .catch(function (e) { + out.textContent = "Error: " + (e && e.message ? e.message : e); + setBtn(false, "AI Summary"); }); }); -})(); +}); + + diff --git a/assets/gpt5.js b/assets/gpt5.js index dbd8d97..57ab81a 100644 --- a/assets/gpt5.js +++ b/assets/gpt5.js @@ -1,6 +1,16 @@ -// Minimal client for OraDigit's GPT proxy (SSE). ES5-compatible (no spread / optional chaining). +// Minimal client for OraDigit's GPT proxy (SSE). ES5-compatible. +// Adds smart token spacing and an abort() helper. (function () { var API = "https://auth.oradigit.com/api/chat"; // mapped to chat5 + var _controller = null; + + function needsSpace(prev, next) { + if (!prev || !next) return false; + var a = prev.slice(-1); + var b = next.charAt(0); + // insert a space when two alphanumerics touch (e.g., "Hello" + "How") + return /[A-Za-z0-9]/.test(a) && /[A-Za-z0-9]/.test(b); + } function stream(opts) { if (!opts) opts = {}; @@ -8,10 +18,10 @@ var payload = { messages: opts.messages }; if (opts.system) payload.system = opts.system; - if (opts.model) payload.model = opts.model; + if (opts.model) payload.model = opts.model; - var controller = new AbortController(); - var useSignal = opts.signal || controller.signal; + _controller = new AbortController(); + var useSignal = opts.signal || _controller.signal; return fetch(API, { method: "POST", @@ -27,12 +37,11 @@ }); } - // If SSE, parse events if (ct && ct.indexOf("text/event-stream") !== -1) { var reader = resp.body.getReader(); var decoder = new TextDecoder(); var buffer = ""; - var full = ""; + var full = ""; function pump() { return reader.read().then(function (r) { @@ -46,17 +55,19 @@ var evt = parts[i]; if (!evt.trim()) continue; var lines = evt.split("\n"); - var type = (lines[0] || "").replace(/^event:\s*/, "").trim(); - var data = (lines.slice(1).join("\n") || "").replace(/^data:\s*/, ""); + var type = (lines[0] || "").replace(/^event:\s*/, "").trim(); + var data = (lines.slice(1).join("\n") || "").replace(/^data:\s*/, ""); if (type === "message") { - full += data; - if (typeof opts.onToken === "function") opts.onToken(data); + var chunk = data; + if (needsSpace(full, chunk)) chunk = " " + chunk; + full += chunk; + if (typeof opts.onToken === "function") opts.onToken(chunk); } else if (type === "error") { try { var j = JSON.parse(data); throw new Error(j.message || "Server error"); } catch (e) { throw new Error("Server error"); } } - // ignore: open/info/done (informational) + // ignore: open/info/done } return pump(); }); @@ -64,7 +75,7 @@ return pump(); } - // Non-SSE fallback (shouldn’t happen now) + // Non-SSE fallback return resp.json().then(function (json) { if (json && json.error) throw new Error(json.error); return json; @@ -76,12 +87,16 @@ opts = opts || {}; return stream({ system: opts.system || "You are LLbot for OraDigit.com. Be concise.", - model: opts.model, + model: opts.model, messages: [{ role: "user", content: prompt }], onToken: opts.onToken, - signal: opts.signal + signal: opts.signal }); } - window.gpt5 = { stream: stream, ask: ask }; + function abort() { + if (_controller) try { _controller.abort(); } catch (e) {} + } + + window.gpt5 = { stream: stream, ask: ask, abort: abort }; })(); diff --git a/index.html b/index.html index 731387d..b1a59d1 100644 --- a/index.html +++ b/index.html @@ -145,8 +145,11 @@

Manage Your Cookie Preferences

} }); -
- +
+
+ + +
From f913a7a7cbe6825affc1ac2e99772e45104f394e Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:40:20 -0400 Subject: [PATCH 09/10] init branch for stream polish From 4af51b3064b036d044b031aa8517ed41786a8efd Mon Sep 17 00:00:00 2001 From: Lissan <150966211+DataForSolution@users.noreply.github.com> Date: Sun, 31 Aug 2025 12:59:38 -0400 Subject: [PATCH 10/10] Add highlight-to-summarize feature using gpt5 --- _layouts/default.html | 6 +- assets/css/style.css | 4 + assets/gpt5-select.js | 204 ++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 211 insertions(+), 3 deletions(-) create mode 100644 assets/gpt5-select.js diff --git a/_layouts/default.html b/_layouts/default.html index 2e12ece..923e94c 100644 --- a/_layouts/default.html +++ b/_layouts/default.html @@ -91,9 +91,9 @@ Last updated: {{ page.last_updated }}

{% endif %} - - - + + + {% include footer.html %} diff --git a/assets/css/style.css b/assets/css/style.css index 3bfffed..f0f55e0 100644 --- a/assets/css/style.css +++ b/assets/css/style.css @@ -1634,3 +1634,7 @@ textarea { resize:vertical; } font-size: 0.95rem; color: #333; } +/* Optional polish for selection summarize UI */ +#gpt5-sel-btn { transition: transform .06s ease; } +#gpt5-sel-btn:active { transform: scale(.98); } +#gpt5-sel-panel { backdrop-filter: saturate(1.2); } diff --git a/assets/gpt5-select.js b/assets/gpt5-select.js new file mode 100644 index 0000000..8059d4d --- /dev/null +++ b/assets/gpt5-select.js @@ -0,0 +1,204 @@ +// Highlight-to-Summarize for OraDigit (ES5-safe) +// Requires window.gpt5 from /assets/gpt5.js +(function () { + if (!window.gpt5) { console.warn("gpt5.js not loaded."); return; } + + var BTN_ID = "gpt5-sel-btn"; + var PANEL_ID = "gpt5-sel-panel"; + var OUT_ID = "gpt5-sel-out"; + var STOP_ID = "gpt5-sel-stop"; + var CLOSE_ID = "gpt5-sel-close"; + var inProgress = false; + + function ensureBtn() { + var b = document.getElementById(BTN_ID); + if (b) return b; + b = document.createElement("button"); + b.id = BTN_ID; + b.type = "button"; + b.textContent = "Summarize"; + b.setAttribute("aria-label", "Summarize selection with AI"); + b.style.position = "fixed"; + b.style.display = "none"; + b.style.zIndex = "2147483647"; + b.style.padding = "6px 10px"; + b.style.borderRadius = "9999px"; + b.style.border = "1px solid #d1d5db"; + b.style.background = "#111"; + b.style.color = "#fff"; + b.style.font = "14px/1 system-ui,-apple-system,Segoe UI,Roboto,Arial"; + b.style.boxShadow = "0 2px 8px rgba(0,0,0,.15)"; + b.onkeydown = function (e) { + if (e.key === "Escape") hideBtn(); + }; + document.body.appendChild(b); + return b; + } + + function ensurePanel() { + var p = document.getElementById(PANEL_ID); + if (p) return p; + + p = document.createElement("div"); + p.id = PANEL_ID; + p.style.position = "fixed"; + p.style.right = "16px"; + p.style.bottom = "16px"; + p.style.maxWidth = "520px"; + p.style.width = "min(92vw,520px)"; + p.style.maxHeight = "60vh"; + p.style.overflow = "auto"; + p.style.background = "#fff"; + p.style.border = "1px solid #e5e7eb"; + p.style.borderRadius = "12px"; + p.style.boxShadow = "0 10px 30px rgba(0,0,0,.12)"; + p.style.padding = "12px"; + p.style.font = "14px/1.5 system-ui,-apple-system,Segoe UI,Roboto,Arial"; + p.style.color = "#111"; + p.style.zIndex = "2147483647"; + + var row = document.createElement("div"); + row.style.display = "flex"; + row.style.alignItems = "center"; + row.style.gap = "8px"; + row.style.marginBottom = "8px"; + + var h = document.createElement("div"); + h.textContent = "AI Summary"; + h.style.fontWeight = "600"; + h.style.flex = "1"; + + var stop = document.createElement("button"); + stop.id = STOP_ID; + stop.type = "button"; + stop.textContent = "Stop"; + stop.style.padding = "6px 10px"; + stop.style.border = "1px solid #d1d5db"; + stop.style.borderRadius = "8px"; + stop.style.background = "#fff"; + stop.onclick = function () { + try { window.gpt5.abort(); } catch (e) {} + inProgress = false; + }; + + var close = document.createElement("button"); + close.id = CLOSE_ID; + close.type = "button"; + close.textContent = "Close"; + close.style.padding = "6px 10px"; + close.style.border = "1px solid #d1d5db"; + close.style.borderRadius = "8px"; + close.style.background = "#fff"; + close.onclick = function () { p.remove(); }; + + row.appendChild(h); + row.appendChild(stop); + row.appendChild(close); + + var out = document.createElement("div"); + out.id = OUT_ID; + out.setAttribute("aria-live", "polite"); + out.style.whiteSpace = "pre-wrap"; + + p.appendChild(row); + p.appendChild(out); + document.body.appendChild(p); + return p; + } + + function getSelectionText() { + var sel = window.getSelection && window.getSelection(); + if (!sel || sel.isCollapsed) return ""; + var s = sel.toString() || ""; + s = s.replace(/\s+/g, " ").trim(); + if (s.length > 6000) s = s.slice(0, 6000); + return s; + } + + function showBtnNearSelection() { + var btn = ensureBtn(); + var sel = window.getSelection && window.getSelection(); + if (!sel || sel.isCollapsed) { hideBtn(); return; } + if (!sel.rangeCount) { hideBtn(); return; } + var r = sel.getRangeAt(0); + var rect = r.getBoundingClientRect(); + if (!rect || (!rect.width && !rect.height)) { hideBtn(); return; } + + // Only show for reasonable lengths + var text = getSelectionText(); + if (text.length < 20) { hideBtn(); return; } + + var x = rect.right + window.scrollX - btn.offsetWidth - 4; + var y = rect.top + window.scrollY - 36; + if (x < 8) x = 8; + if (y < 8) y = rect.bottom + window.scrollY + 8; + + btn.style.left = x + "px"; + btn.style.top = y + "px"; + btn.style.display = "inline-block"; + } + + function hideBtn() { + var b = document.getElementById(BTN_ID); + if (b) b.style.display = "none"; + } + + function summarize(text) { + var panel = ensurePanel(); + var out = document.getElementById(OUT_ID); + var stop = document.getElementById(STOP_ID); + var btn = document.getElementById(BTN_ID); + if (!out) return; + + out.textContent = "Thinking…"; + inProgress = true; + if (btn) btn.disabled = true; + if (stop) stop.disabled = false; + + var full = ""; + window.gpt5.stream({ + system: "You are LLbot for OraDigit.com. Summarize clearly for business stakeholders in 5–7 short bullets.", + messages: [{ role: "user", content: "Summarize this selection:\n\n" + text }], + onToken: function (t) { full += t; out.textContent = full; } + }) + .then(function () { + inProgress = false; + if (btn) btn.disabled = false; + if (stop) stop.disabled = true; + }) + .catch(function (e) { + inProgress = false; + if (btn) btn.disabled = false; + if (stop) stop.disabled = true; + out.textContent = "Error: " + (e && e.message ? e.message : e); + }); + } + + // Button click handler + document.addEventListener("click", function (e) { + var btn = document.getElementById(BTN_ID); + if (btn && e.target === btn) { + var text = getSelectionText(); + hideBtn(); + if (text) summarize(text); + } + }); + + // Show button when user selects text (mouseup / keyup) + document.addEventListener("mouseup", function () { setTimeout(showBtnNearSelection, 0); }); + document.addEventListener("keyup", function (e) { + if (e.key === "Escape") hideBtn(); + else setTimeout(showBtnNearSelection, 0); + }); + + // Optional hotkey: Alt+S to summarize current selection + document.addEventListener("keydown", function (e) { + if (e.altKey && (e.key === "s" || e.key === "S")) { + var t = getSelectionText(); + if (t) summarize(t); + } + }); + + // Hide on scroll (optional) + window.addEventListener("scroll", function () { hideBtn(); }, { passive: true }); +})();