@dogsbay/plugin-ask 0.2.0-beta.84 → 0.2.0-beta.86
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -1
- package/dist/client.js +125 -71
- package/dist/client.js.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +13 -0
- package/dist/index.js.map +1 -1
- package/dist/schema.d.ts +15 -0
- package/dist/schema.d.ts.map +1 -1
- package/dist/schema.js +7 -0
- package/dist/schema.js.map +1 -1
- package/package.json +3 -2
- package/src/RightRailAsk.astro +55 -0
- package/src/client.ts +132 -68
- package/src/styles.css +26 -0
package/README.md
CHANGED
|
@@ -4,6 +4,10 @@ A floating "Ask AI" button for Dogsbay docs sites. It opens a modal, sends the
|
|
|
4
4
|
question to a configured backend, and shows the answer — so a static docs site
|
|
5
5
|
can offer AI search without shipping any secrets.
|
|
6
6
|
|
|
7
|
+
Full setup (proxy, AI Search, CI, security): see
|
|
8
|
+
[docs/ask-ai.md](../../docs/ask-ai.md) (site authors) and
|
|
9
|
+
[docs-dev/ask-ai-architecture.md](../../docs-dev/ask-ai-architecture.md) (architecture/ops).
|
|
10
|
+
|
|
7
11
|
## Install
|
|
8
12
|
|
|
9
13
|
```bash
|
|
@@ -33,7 +37,8 @@ plugins:
|
|
|
33
37
|
| `buttonLabel` | no | `Ask AI` | Floating button text. |
|
|
34
38
|
| `title` | no | `Ask the docs` | Modal heading. |
|
|
35
39
|
| `placeholder` | no | `Ask a question…` | Input placeholder. |
|
|
36
|
-
| `position` | no | `bottom-right` | `bottom-right` or `bottom-left`. |
|
|
40
|
+
| `position` | no | `bottom-right` | Floating-button corner: `bottom-right` or `bottom-left`. |
|
|
41
|
+
| `placement` | no | `auto` | `auto` (dock in the docs right rail when present, else float), `rail`, or `floating`. |
|
|
37
42
|
|
|
38
43
|
## Styling
|
|
39
44
|
|
package/dist/client.js
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client runtime for `@dogsbay/plugin-ask`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Astro project, so it runs for side effects on every page. It injects a
|
|
6
|
-
* floating button + <dialog> at <body> level (outside the article), and on
|
|
7
|
-
* submit streams an answer from the dogsbay-ask proxy.
|
|
4
|
+
* Runs for side effects on every page. Two render targets share one code path:
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* - **Docked panel** — a server-rendered shell (`RightRailAsk.astro`,
|
|
7
|
+
* `[data-dba-panel="rail"]`) that DocsLayout places in its `right-rail`
|
|
8
|
+
* slot. Visible on desktop; hidden < lg because the rail is `hidden lg:block`.
|
|
9
|
+
* - **Floating modal** — a body-level button + `<dialog>` built here. Used when
|
|
10
|
+
* there's no rail (floating placement, wide/API pages) and as the mobile
|
|
11
|
+
* entry when a docked panel exists.
|
|
12
|
+
*
|
|
13
|
+
* Astro view transitions swap the document, so we mount on `astro:page-load`
|
|
14
|
+
* and tear the floating widget down on `astro:before-swap` (the docked shell is
|
|
15
|
+
* part of the swapped document, so it's re-wired each navigation).
|
|
12
16
|
*/
|
|
13
17
|
// @ts-expect-error: virtual module resolved by the Dogsbay Vite alias at build.
|
|
14
18
|
import config from "virtual:dogsbay-plugin-config/@dogsbay/plugin-ask";
|
|
@@ -42,15 +46,110 @@ function sourcesHtml(sources) {
|
|
|
42
46
|
.join("");
|
|
43
47
|
return `<div class="dba-sources"><div class="dba-sources-title">Sources</div><ul>${items}</ul></div>`;
|
|
44
48
|
}
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
49
|
+
/**
|
|
50
|
+
* The one ask flow: POST the question, stream the answer, render Markdown +
|
|
51
|
+
* a Sources list. Shared by the docked panel and the floating dialog.
|
|
52
|
+
*/
|
|
53
|
+
async function runAsk({ input, answer, submit }) {
|
|
54
|
+
const query = input.value.trim();
|
|
55
|
+
if (!query)
|
|
48
56
|
return;
|
|
57
|
+
submit.disabled = true;
|
|
58
|
+
answer.dataset.state = "loading";
|
|
59
|
+
answer.textContent = "…";
|
|
60
|
+
try {
|
|
61
|
+
const res = await fetch(cfg.endpoint, {
|
|
62
|
+
method: "POST",
|
|
63
|
+
headers: { "Content-Type": "application/json" },
|
|
64
|
+
body: JSON.stringify({
|
|
65
|
+
...buildAskRequest(cfg.site, query, cfg.mode),
|
|
66
|
+
...(turnstileToken ? { turnstileToken } : {}),
|
|
67
|
+
}),
|
|
68
|
+
});
|
|
69
|
+
if (!res.ok || !res.body) {
|
|
70
|
+
throw new Error(`Request failed (${res.status})`);
|
|
71
|
+
}
|
|
72
|
+
answer.textContent = "";
|
|
73
|
+
answer.dataset.state = "streaming";
|
|
74
|
+
const reader = res.body.getReader();
|
|
75
|
+
const decoder = new TextDecoder();
|
|
76
|
+
let buf = ""; // line buffer (don't parse a frame split across reads)
|
|
77
|
+
let rawAll = ""; // whole stream, for the trailing `event: chunks` sources
|
|
78
|
+
let answerMd = ""; // accumulated answer markdown
|
|
79
|
+
for (;;) {
|
|
80
|
+
const { value, done } = await reader.read();
|
|
81
|
+
if (done)
|
|
82
|
+
break;
|
|
83
|
+
const text = decoder.decode(value, { stream: true });
|
|
84
|
+
rawAll += text;
|
|
85
|
+
buf += text;
|
|
86
|
+
const nl = buf.lastIndexOf("\n");
|
|
87
|
+
if (nl >= 0) {
|
|
88
|
+
answerMd += extractText(buf.slice(0, nl + 1));
|
|
89
|
+
buf = buf.slice(nl + 1);
|
|
90
|
+
answer.innerHTML = renderMarkdown(answerMd);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
if (buf)
|
|
94
|
+
answerMd += extractText(buf);
|
|
95
|
+
const sources = parseSources(rawAll, cfg.linkBase);
|
|
96
|
+
answer.innerHTML = renderMarkdown(answerMd) + (sources.length ? sourcesHtml(sources) : "");
|
|
97
|
+
answer.dataset.state = "done";
|
|
98
|
+
}
|
|
99
|
+
catch (err) {
|
|
100
|
+
answer.dataset.state = "error";
|
|
101
|
+
answer.textContent = `Sorry — something went wrong. ${err.message}`;
|
|
102
|
+
}
|
|
103
|
+
finally {
|
|
104
|
+
submit.disabled = false;
|
|
105
|
+
// Turnstile tokens are single-use — refresh for the next question.
|
|
106
|
+
turnstileApi()?.reset?.();
|
|
107
|
+
turnstileToken = null;
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
function wireForm(els) {
|
|
111
|
+
els.form.addEventListener("submit", (e) => {
|
|
112
|
+
e.preventDefault();
|
|
113
|
+
void runAsk(els);
|
|
114
|
+
});
|
|
115
|
+
}
|
|
116
|
+
const ROOT_ID = "dba-root";
|
|
117
|
+
/**
|
|
118
|
+
* Wire the server-rendered docked panel if present. Returns whether a docked
|
|
119
|
+
* shell exists on the page (so the caller knows to mark the floating button as
|
|
120
|
+
* a mobile-only entry). Idempotent via a `data-dba-wired` marker.
|
|
121
|
+
*/
|
|
122
|
+
function wireDockedPanel() {
|
|
123
|
+
const panel = document.querySelector('[data-dba-panel="rail"]');
|
|
124
|
+
if (!panel)
|
|
125
|
+
return false;
|
|
126
|
+
if (panel.dataset.dbaWired)
|
|
127
|
+
return true;
|
|
128
|
+
panel.dataset.dbaWired = "1";
|
|
129
|
+
const form = panel.querySelector("[data-dba-form]");
|
|
130
|
+
const input = panel.querySelector(".dba-input");
|
|
131
|
+
const answer = panel.querySelector(".dba-answer");
|
|
132
|
+
const submit = panel.querySelector(".dba-submit");
|
|
133
|
+
if (!form || !input || !answer || !submit)
|
|
134
|
+
return true;
|
|
135
|
+
// Lazy-load Turnstile on first interaction so a page that merely shows the
|
|
136
|
+
// docked panel still sets no third-party cookies until the reader engages.
|
|
137
|
+
input.addEventListener("focus", loadTurnstile, { once: true });
|
|
138
|
+
wireForm({ form, input, answer, submit });
|
|
139
|
+
return true;
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Build + wire the floating button + dialog. `hasDocked` marks the root so CSS
|
|
143
|
+
* can hide the trigger at desktop widths (where the docked panel is visible).
|
|
144
|
+
*/
|
|
145
|
+
function mountFloating(hasDocked) {
|
|
49
146
|
if (document.getElementById(ROOT_ID))
|
|
50
|
-
return;
|
|
147
|
+
return;
|
|
51
148
|
const root = document.createElement("div");
|
|
52
149
|
root.id = ROOT_ID;
|
|
53
150
|
root.dataset.position = cfg.position;
|
|
151
|
+
if (hasDocked)
|
|
152
|
+
root.dataset.hasDocked = "1";
|
|
54
153
|
const button = document.createElement("button");
|
|
55
154
|
button.type = "button";
|
|
56
155
|
button.className = "dba-trigger";
|
|
@@ -63,7 +162,7 @@ function mount() {
|
|
|
63
162
|
? `<div class="cf-turnstile dba-turnstile" data-sitekey="${cfg.turnstileSitekey}" data-appearance="interaction-only" data-callback="__dbaTsCb" data-expired-callback="__dbaTsExpired" data-error-callback="__dbaTsExpired"></div>`
|
|
64
163
|
: "";
|
|
65
164
|
dialog.innerHTML = `
|
|
66
|
-
<form class="dba-form" method="dialog">
|
|
165
|
+
<form class="dba-form" method="dialog" data-dba-form>
|
|
67
166
|
<header class="dba-header">
|
|
68
167
|
<h2 class="dba-title"></h2>
|
|
69
168
|
<button type="button" class="dba-close" aria-label="Close">×</button>
|
|
@@ -76,7 +175,6 @@ function mount() {
|
|
|
76
175
|
<div class="dba-answer" aria-live="polite" tabindex="-1"></div>
|
|
77
176
|
</form>
|
|
78
177
|
`;
|
|
79
|
-
loadTurnstile();
|
|
80
178
|
// textContent (not innerHTML) for user-configurable strings — no injection.
|
|
81
179
|
dialog.querySelector(".dba-title").textContent = cfg.title;
|
|
82
180
|
const input = dialog.querySelector(".dba-input");
|
|
@@ -85,74 +183,30 @@ function mount() {
|
|
|
85
183
|
const form = dialog.querySelector(".dba-form");
|
|
86
184
|
const submit = dialog.querySelector(".dba-submit");
|
|
87
185
|
button.addEventListener("click", () => {
|
|
186
|
+
// Lazy-load Turnstile only when the user engages, so normal page loads set
|
|
187
|
+
// no third-party cookies (Lighthouse-clean) and don't fetch the widget.
|
|
188
|
+
loadTurnstile();
|
|
88
189
|
dialog.showModal();
|
|
89
190
|
input.focus();
|
|
90
191
|
});
|
|
91
192
|
dialog.querySelector(".dba-close").addEventListener("click", () => dialog.close());
|
|
92
|
-
form
|
|
93
|
-
e.preventDefault();
|
|
94
|
-
const query = input.value.trim();
|
|
95
|
-
if (!query)
|
|
96
|
-
return;
|
|
97
|
-
submit.disabled = true;
|
|
98
|
-
answer.dataset.state = "loading";
|
|
99
|
-
answer.textContent = "…";
|
|
100
|
-
try {
|
|
101
|
-
const res = await fetch(cfg.endpoint, {
|
|
102
|
-
method: "POST",
|
|
103
|
-
headers: { "Content-Type": "application/json" },
|
|
104
|
-
body: JSON.stringify({
|
|
105
|
-
...buildAskRequest(cfg.site, query, cfg.mode),
|
|
106
|
-
...(turnstileToken ? { turnstileToken } : {}),
|
|
107
|
-
}),
|
|
108
|
-
});
|
|
109
|
-
if (!res.ok || !res.body) {
|
|
110
|
-
throw new Error(`Request failed (${res.status})`);
|
|
111
|
-
}
|
|
112
|
-
answer.textContent = "";
|
|
113
|
-
answer.dataset.state = "streaming";
|
|
114
|
-
const reader = res.body.getReader();
|
|
115
|
-
const decoder = new TextDecoder();
|
|
116
|
-
let buf = ""; // line buffer (don't parse a frame split across reads)
|
|
117
|
-
let rawAll = ""; // whole stream, for the trailing `event: chunks` sources
|
|
118
|
-
let answerMd = ""; // accumulated answer markdown
|
|
119
|
-
for (;;) {
|
|
120
|
-
const { value, done } = await reader.read();
|
|
121
|
-
if (done)
|
|
122
|
-
break;
|
|
123
|
-
const text = decoder.decode(value, { stream: true });
|
|
124
|
-
rawAll += text;
|
|
125
|
-
buf += text;
|
|
126
|
-
const nl = buf.lastIndexOf("\n");
|
|
127
|
-
if (nl >= 0) {
|
|
128
|
-
answerMd += extractText(buf.slice(0, nl + 1));
|
|
129
|
-
buf = buf.slice(nl + 1);
|
|
130
|
-
answer.innerHTML = renderMarkdown(answerMd);
|
|
131
|
-
}
|
|
132
|
-
}
|
|
133
|
-
if (buf)
|
|
134
|
-
answerMd += extractText(buf);
|
|
135
|
-
const sources = parseSources(rawAll, cfg.linkBase);
|
|
136
|
-
answer.innerHTML = renderMarkdown(answerMd) + (sources.length ? sourcesHtml(sources) : "");
|
|
137
|
-
answer.dataset.state = "done";
|
|
138
|
-
}
|
|
139
|
-
catch (err) {
|
|
140
|
-
answer.dataset.state = "error";
|
|
141
|
-
answer.textContent = `Sorry — something went wrong. ${err.message}`;
|
|
142
|
-
}
|
|
143
|
-
finally {
|
|
144
|
-
submit.disabled = false;
|
|
145
|
-
// Turnstile tokens are single-use — refresh for the next question.
|
|
146
|
-
turnstileApi()?.reset?.();
|
|
147
|
-
turnstileToken = null;
|
|
148
|
-
}
|
|
149
|
-
});
|
|
193
|
+
wireForm({ form, input, answer, submit });
|
|
150
194
|
root.append(button, dialog);
|
|
151
195
|
document.body.appendChild(root);
|
|
152
196
|
}
|
|
197
|
+
function mount() {
|
|
198
|
+
if (typeof document === "undefined")
|
|
199
|
+
return;
|
|
200
|
+
// Wire the docked panel first; its presence decides whether the floating
|
|
201
|
+
// button is a full widget (no rail) or just the mobile entry (rail present).
|
|
202
|
+
const hasDocked = wireDockedPanel();
|
|
203
|
+
mountFloating(hasDocked);
|
|
204
|
+
}
|
|
153
205
|
function unmount() {
|
|
154
206
|
if (typeof document === "undefined")
|
|
155
207
|
return;
|
|
208
|
+
// Only the floating widget is ours to remove; the docked shell is part of the
|
|
209
|
+
// document Astro swaps, and is re-wired by mount() on the next page-load.
|
|
156
210
|
document.getElementById(ROOT_ID)?.remove();
|
|
157
211
|
}
|
|
158
212
|
if (typeof document !== "undefined") {
|
package/dist/client.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AACpD,OAAO,EAAgB,KAAK,UAAU,EAAE,MAAM,aAAa,CAAC;AAa5D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,aAAa,CAwC7D"}
|
package/dist/index.js
CHANGED
|
@@ -25,6 +25,10 @@ const PLUGIN_DIR = path.dirname(fileURLToPath(import.meta.url));
|
|
|
25
25
|
const PACKAGE_ROOT = path.resolve(PLUGIN_DIR, "..");
|
|
26
26
|
const CLIENT_PATH = path.join(PLUGIN_DIR, "client.js");
|
|
27
27
|
const STYLES_PATH = path.join(PACKAGE_ROOT, "src", "styles.css");
|
|
28
|
+
// Server-rendered docked-panel shell. Ships un-built under `src/` (like the
|
|
29
|
+
// stylesheet) and is wired into DocsLayout's `right-rail` slot via the
|
|
30
|
+
// `componentWrappers()` hook when placement allows a rail.
|
|
31
|
+
const RAIL_WRAPPER_PATH = path.join(PACKAGE_ROOT, "src", "RightRailAsk.astro");
|
|
28
32
|
export default function ask(input) {
|
|
29
33
|
const opts = parseOptions(input);
|
|
30
34
|
return {
|
|
@@ -35,6 +39,14 @@ export default function ask(input) {
|
|
|
35
39
|
styles() {
|
|
36
40
|
return [STYLES_PATH];
|
|
37
41
|
},
|
|
42
|
+
componentWrappers() {
|
|
43
|
+
// Contribute the docked panel to DocsLayout's `right-rail` slot unless
|
|
44
|
+
// the site forced the floating-only widget. In "floating" mode the rail
|
|
45
|
+
// stays an inert passthrough and the client renders the modal instead.
|
|
46
|
+
if (opts.placement === "floating")
|
|
47
|
+
return {};
|
|
48
|
+
return { RightRail: RAIL_WRAPPER_PATH };
|
|
49
|
+
},
|
|
38
50
|
defineClientConfig(ctx) {
|
|
39
51
|
// JSON-safe runtime config exposed at
|
|
40
52
|
// virtual:dogsbay-plugin-config/@dogsbay/plugin-ask
|
|
@@ -46,6 +58,7 @@ export default function ask(input) {
|
|
|
46
58
|
title: opts.title,
|
|
47
59
|
placeholder: opts.placeholder,
|
|
48
60
|
position: opts.position,
|
|
61
|
+
placement: opts.placement,
|
|
49
62
|
// Base URL used to turn cited doc paths into links.
|
|
50
63
|
linkBase: ctx.config.site.url ?? "",
|
|
51
64
|
turnstileSitekey: opts.turnstileSitekey,
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAE5D,6EAA6E;AAC7E,oEAAoE;AACpE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AACH,OAAO,IAAI,MAAM,WAAW,CAAC;AAC7B,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AAEzC,OAAO,EAAE,YAAY,EAAmB,MAAM,aAAa,CAAC;AAE5D,6EAA6E;AAC7E,oEAAoE;AACpE,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;AAChE,MAAM,YAAY,GAAG,IAAI,CAAC,OAAO,CAAC,UAAU,EAAE,IAAI,CAAC,CAAC;AACpD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,YAAY,CAAC,CAAC;AACjE,4EAA4E;AAC5E,uEAAuE;AACvE,2DAA2D;AAC3D,MAAM,iBAAiB,GAAG,IAAI,CAAC,IAAI,CAAC,YAAY,EAAE,KAAK,EAAE,oBAAoB,CAAC,CAAC;AAI/E,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAkB;IAC5C,MAAM,IAAI,GAAG,YAAY,CAAC,KAAK,CAAC,CAAC;IAEjC,OAAO;QACL,IAAI,EAAE,qBAAqB;QAE3B,aAAa;YACX,OAAO,CAAC,WAAW,CAAC,CAAC;QACvB,CAAC;QAED,MAAM;YACJ,OAAO,CAAC,WAAW,CAAC,CAAC;QACvB,CAAC;QAED,iBAAiB;YACf,uEAAuE;YACvE,wEAAwE;YACxE,uEAAuE;YACvE,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU;gBAAE,OAAO,EAAE,CAAC;YAC7C,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,CAAC;QAC1C,CAAC;QAED,kBAAkB,CAAC,GAAG;YACpB,sCAAsC;YACtC,oDAAoD;YACpD,OAAO;gBACL,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,KAAK,EAAE,IAAI,CAAC,KAAK;gBACjB,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,QAAQ,EAAE,IAAI,CAAC,QAAQ;gBACvB,SAAS,EAAE,IAAI,CAAC,SAAS;gBACzB,oDAAoD;gBACpD,QAAQ,EAAG,GAAG,CAAC,MAAM,CAAC,IAAyB,CAAC,GAAG,IAAI,EAAE;gBACzD,gBAAgB,EAAE,IAAI,CAAC,gBAAgB;aACxC,CAAC;QACJ,CAAC;KACF,CAAC;AACJ,CAAC"}
|
package/dist/schema.d.ts
CHANGED
|
@@ -9,6 +9,15 @@
|
|
|
9
9
|
*/
|
|
10
10
|
export type AskMode = "answer" | "search";
|
|
11
11
|
export type AskPosition = "bottom-right" | "bottom-left";
|
|
12
|
+
/**
|
|
13
|
+
* Where the widget renders.
|
|
14
|
+
* - `auto` (default) — dock as a panel in the docs right rail when one exists
|
|
15
|
+
* (Dogsbay's `DocsLayout` `right-rail` slot), else fall back to a floating
|
|
16
|
+
* button + dialog.
|
|
17
|
+
* - `rail` — always contribute the docked rail panel (no desktop floating button).
|
|
18
|
+
* - `floating` — the floating button + modal only; never use the rail.
|
|
19
|
+
*/
|
|
20
|
+
export type AskPlacement = "auto" | "rail" | "floating";
|
|
12
21
|
export interface AskOptions {
|
|
13
22
|
/**
|
|
14
23
|
* URL of the dogsbay-ask proxy (the shared Worker). Required.
|
|
@@ -31,6 +40,11 @@ export interface AskOptions {
|
|
|
31
40
|
placeholder?: string;
|
|
32
41
|
/** Corner the floating button sits in. Default "bottom-right". */
|
|
33
42
|
position?: AskPosition;
|
|
43
|
+
/**
|
|
44
|
+
* Where the widget renders: docked rail panel, floating modal, or `auto`.
|
|
45
|
+
* Default "auto". See {@link AskPlacement}.
|
|
46
|
+
*/
|
|
47
|
+
placement?: AskPlacement;
|
|
34
48
|
/**
|
|
35
49
|
* Cloudflare Turnstile sitekey. If set, the widget obtains a Turnstile
|
|
36
50
|
* token and sends it with each request; the proxy verifies it. Omit to
|
|
@@ -46,6 +60,7 @@ export interface ResolvedAskOptions {
|
|
|
46
60
|
title: string;
|
|
47
61
|
placeholder: string;
|
|
48
62
|
position: AskPosition;
|
|
63
|
+
placement: AskPlacement;
|
|
49
64
|
turnstileSitekey: string;
|
|
50
65
|
}
|
|
51
66
|
/**
|
package/dist/schema.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC1C,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"schema.d.ts","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAEH,MAAM,MAAM,OAAO,GAAG,QAAQ,GAAG,QAAQ,CAAC;AAC1C,MAAM,MAAM,WAAW,GAAG,cAAc,GAAG,aAAa,CAAC;AACzD;;;;;;;GAOG;AACH,MAAM,MAAM,YAAY,GAAG,MAAM,GAAG,MAAM,GAAG,UAAU,CAAC;AAExD,MAAM,WAAW,UAAU;IACzB;;;OAGG;IACH,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB;;;;OAIG;IACH,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,2EAA2E;IAC3E,IAAI,CAAC,EAAE,OAAO,CAAC;IACf,+CAA+C;IAC/C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,6CAA6C;IAC7C,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,oDAAoD;IACpD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kEAAkE;IAClE,QAAQ,CAAC,EAAE,WAAW,CAAC;IACvB;;;OAGG;IACH,SAAS,CAAC,EAAE,YAAY,CAAC;IACzB;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,OAAO,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,WAAW,CAAC;IACtB,SAAS,EAAE,YAAY,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAcD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAuC/D"}
|
package/dist/schema.js
CHANGED
|
@@ -13,6 +13,7 @@ const DEFAULTS = {
|
|
|
13
13
|
title: "Ask the docs",
|
|
14
14
|
placeholder: "Ask a question…",
|
|
15
15
|
position: "bottom-right",
|
|
16
|
+
placement: "auto",
|
|
16
17
|
turnstileSitekey: "",
|
|
17
18
|
};
|
|
18
19
|
const PKG = "@dogsbay/plugin-ask";
|
|
@@ -46,6 +47,12 @@ export function parseOptions(input) {
|
|
|
46
47
|
}
|
|
47
48
|
out.position = opts.position;
|
|
48
49
|
}
|
|
50
|
+
if (opts.placement !== undefined) {
|
|
51
|
+
if (opts.placement !== "auto" && opts.placement !== "rail" && opts.placement !== "floating") {
|
|
52
|
+
throw new Error(`${PKG}: options.placement must be "auto", "rail", or "floating".`);
|
|
53
|
+
}
|
|
54
|
+
out.placement = opts.placement;
|
|
55
|
+
}
|
|
49
56
|
if (opts.turnstileSitekey !== undefined) {
|
|
50
57
|
out.turnstileSitekey = requireNonEmptyString(opts.turnstileSitekey, "turnstileSitekey");
|
|
51
58
|
}
|
package/dist/schema.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@dogsbay/plugin-ask",
|
|
3
|
-
"version": "0.2.0-beta.
|
|
3
|
+
"version": "0.2.0-beta.86",
|
|
4
4
|
"description": "Floating \"Ask AI\" widget for Dogsbay docs sites — calls a shared AI Search proxy (dogsbay-ask).",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -18,11 +18,12 @@
|
|
|
18
18
|
"dist",
|
|
19
19
|
"src/styles.css",
|
|
20
20
|
"src/client.ts",
|
|
21
|
+
"src/RightRailAsk.astro",
|
|
21
22
|
"skills",
|
|
22
23
|
"README.md"
|
|
23
24
|
],
|
|
24
25
|
"dependencies": {
|
|
25
|
-
"@dogsbay/types": "0.2.0-beta.
|
|
26
|
+
"@dogsbay/types": "0.2.0-beta.86"
|
|
26
27
|
},
|
|
27
28
|
"devDependencies": {
|
|
28
29
|
"typescript": "^5.7.0",
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
---
|
|
2
|
+
/**
|
|
3
|
+
* Docked "Ask AI" panel shell — rendered into DocsLayout's `right-rail` named
|
|
4
|
+
* slot via the plugin's `componentWrappers()` hook (slot "RightRail").
|
|
5
|
+
*
|
|
6
|
+
* This is the SERVER-RENDERED structure only; behaviour (open, submit, stream,
|
|
7
|
+
* Turnstile) is wired by the client module (`client.ts`), which finds this shell
|
|
8
|
+
* by `[data-dba-panel="rail"]`. Presentational config comes from the same
|
|
9
|
+
* generated module the client uses, resolvable in SSR via the CLI's Vite alias.
|
|
10
|
+
*
|
|
11
|
+
* Must render `<slot />` to satisfy the wrapper contract (the inner slot is
|
|
12
|
+
* empty — this is an additive panel, not a content wrapper). Being a real
|
|
13
|
+
* element child also keeps DocsLayout's empty-rail rule
|
|
14
|
+
* (`aside[data-right-rail]:not(:has(*))`) from hiding the rail.
|
|
15
|
+
*/
|
|
16
|
+
// @ts-expect-error: virtual module resolved by the Dogsbay Vite alias at build.
|
|
17
|
+
import config from "virtual:dogsbay-plugin-config/@dogsbay/plugin-ask";
|
|
18
|
+
|
|
19
|
+
interface RailConfig {
|
|
20
|
+
title: string;
|
|
21
|
+
placeholder: string;
|
|
22
|
+
turnstileSitekey?: string;
|
|
23
|
+
}
|
|
24
|
+
const cfg = config as RailConfig;
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
<section class="dba-panel" data-dba-panel="rail" aria-label={cfg.title}>
|
|
28
|
+
<form class="dba-form" data-dba-form>
|
|
29
|
+
<header class="dba-header">
|
|
30
|
+
<h2 class="dba-title">{cfg.title}</h2>
|
|
31
|
+
</header>
|
|
32
|
+
<div class="dba-inputrow">
|
|
33
|
+
<input
|
|
34
|
+
class="dba-input"
|
|
35
|
+
type="text"
|
|
36
|
+
autocomplete="off"
|
|
37
|
+
enterkeyhint="search"
|
|
38
|
+
placeholder={cfg.placeholder}
|
|
39
|
+
/>
|
|
40
|
+
<button type="submit" class="dba-submit">Ask</button>
|
|
41
|
+
</div>
|
|
42
|
+
{cfg.turnstileSitekey && (
|
|
43
|
+
<div
|
|
44
|
+
class="cf-turnstile dba-turnstile"
|
|
45
|
+
data-sitekey={cfg.turnstileSitekey}
|
|
46
|
+
data-appearance="interaction-only"
|
|
47
|
+
data-callback="__dbaTsCb"
|
|
48
|
+
data-expired-callback="__dbaTsExpired"
|
|
49
|
+
data-error-callback="__dbaTsExpired"
|
|
50
|
+
/>
|
|
51
|
+
)}
|
|
52
|
+
<div class="dba-answer" aria-live="polite" tabindex="-1"></div>
|
|
53
|
+
</form>
|
|
54
|
+
</section>
|
|
55
|
+
<slot />
|
package/src/client.ts
CHANGED
|
@@ -1,20 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Client runtime for `@dogsbay/plugin-ask`.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
* Astro project, so it runs for side effects on every page. It injects a
|
|
6
|
-
* floating button + <dialog> at <body> level (outside the article), and on
|
|
7
|
-
* submit streams an answer from the dogsbay-ask proxy.
|
|
4
|
+
* Runs for side effects on every page. Two render targets share one code path:
|
|
8
5
|
*
|
|
9
|
-
*
|
|
10
|
-
*
|
|
11
|
-
*
|
|
6
|
+
* - **Docked panel** — a server-rendered shell (`RightRailAsk.astro`,
|
|
7
|
+
* `[data-dba-panel="rail"]`) that DocsLayout places in its `right-rail`
|
|
8
|
+
* slot. Visible on desktop; hidden < lg because the rail is `hidden lg:block`.
|
|
9
|
+
* - **Floating modal** — a body-level button + `<dialog>` built here. Used when
|
|
10
|
+
* there's no rail (floating placement, wide/API pages) and as the mobile
|
|
11
|
+
* entry when a docked panel exists.
|
|
12
|
+
*
|
|
13
|
+
* Astro view transitions swap the document, so we mount on `astro:page-load`
|
|
14
|
+
* and tear the floating widget down on `astro:before-swap` (the docked shell is
|
|
15
|
+
* part of the swapped document, so it's re-wired each navigation).
|
|
12
16
|
*/
|
|
13
17
|
// @ts-expect-error: virtual module resolved by the Dogsbay Vite alias at build.
|
|
14
18
|
import config from "virtual:dogsbay-plugin-config/@dogsbay/plugin-ask";
|
|
15
19
|
import { buildAskRequest, extractText, parseSources, type Source } from "./protocol.js";
|
|
16
20
|
import { renderMarkdown, escapeHtml } from "./md.js";
|
|
17
|
-
import type { AskMode, AskPosition } from "./schema.js";
|
|
21
|
+
import type { AskMode, AskPlacement, AskPosition } from "./schema.js";
|
|
18
22
|
|
|
19
23
|
interface RuntimeConfig {
|
|
20
24
|
endpoint: string;
|
|
@@ -24,6 +28,7 @@ interface RuntimeConfig {
|
|
|
24
28
|
title: string;
|
|
25
29
|
placeholder: string;
|
|
26
30
|
position: AskPosition;
|
|
31
|
+
placement: AskPlacement;
|
|
27
32
|
linkBase: string;
|
|
28
33
|
turnstileSitekey: string;
|
|
29
34
|
}
|
|
@@ -65,15 +70,116 @@ function sourcesHtml(sources: Source[]): string {
|
|
|
65
70
|
return `<div class="dba-sources"><div class="dba-sources-title">Sources</div><ul>${items}</ul></div>`;
|
|
66
71
|
}
|
|
67
72
|
|
|
73
|
+
interface AskEls {
|
|
74
|
+
form: HTMLFormElement;
|
|
75
|
+
input: HTMLInputElement;
|
|
76
|
+
answer: HTMLDivElement;
|
|
77
|
+
submit: HTMLButtonElement;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* The one ask flow: POST the question, stream the answer, render Markdown +
|
|
82
|
+
* a Sources list. Shared by the docked panel and the floating dialog.
|
|
83
|
+
*/
|
|
84
|
+
async function runAsk({ input, answer, submit }: AskEls): Promise<void> {
|
|
85
|
+
const query = input.value.trim();
|
|
86
|
+
if (!query) return;
|
|
87
|
+
|
|
88
|
+
submit.disabled = true;
|
|
89
|
+
answer.dataset.state = "loading";
|
|
90
|
+
answer.textContent = "…";
|
|
91
|
+
|
|
92
|
+
try {
|
|
93
|
+
const res = await fetch(cfg.endpoint, {
|
|
94
|
+
method: "POST",
|
|
95
|
+
headers: { "Content-Type": "application/json" },
|
|
96
|
+
body: JSON.stringify({
|
|
97
|
+
...buildAskRequest(cfg.site, query, cfg.mode),
|
|
98
|
+
...(turnstileToken ? { turnstileToken } : {}),
|
|
99
|
+
}),
|
|
100
|
+
});
|
|
101
|
+
if (!res.ok || !res.body) {
|
|
102
|
+
throw new Error(`Request failed (${res.status})`);
|
|
103
|
+
}
|
|
104
|
+
answer.textContent = "";
|
|
105
|
+
answer.dataset.state = "streaming";
|
|
106
|
+
const reader = res.body.getReader();
|
|
107
|
+
const decoder = new TextDecoder();
|
|
108
|
+
let buf = ""; // line buffer (don't parse a frame split across reads)
|
|
109
|
+
let rawAll = ""; // whole stream, for the trailing `event: chunks` sources
|
|
110
|
+
let answerMd = ""; // accumulated answer markdown
|
|
111
|
+
for (;;) {
|
|
112
|
+
const { value, done } = await reader.read();
|
|
113
|
+
if (done) break;
|
|
114
|
+
const text = decoder.decode(value, { stream: true });
|
|
115
|
+
rawAll += text;
|
|
116
|
+
buf += text;
|
|
117
|
+
const nl = buf.lastIndexOf("\n");
|
|
118
|
+
if (nl >= 0) {
|
|
119
|
+
answerMd += extractText(buf.slice(0, nl + 1));
|
|
120
|
+
buf = buf.slice(nl + 1);
|
|
121
|
+
answer.innerHTML = renderMarkdown(answerMd);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
if (buf) answerMd += extractText(buf);
|
|
125
|
+
const sources = parseSources(rawAll, cfg.linkBase);
|
|
126
|
+
answer.innerHTML = renderMarkdown(answerMd) + (sources.length ? sourcesHtml(sources) : "");
|
|
127
|
+
answer.dataset.state = "done";
|
|
128
|
+
} catch (err) {
|
|
129
|
+
answer.dataset.state = "error";
|
|
130
|
+
answer.textContent = `Sorry — something went wrong. ${(err as Error).message}`;
|
|
131
|
+
} finally {
|
|
132
|
+
submit.disabled = false;
|
|
133
|
+
// Turnstile tokens are single-use — refresh for the next question.
|
|
134
|
+
turnstileApi()?.reset?.();
|
|
135
|
+
turnstileToken = null;
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
function wireForm(els: AskEls): void {
|
|
140
|
+
els.form.addEventListener("submit", (e) => {
|
|
141
|
+
e.preventDefault();
|
|
142
|
+
void runAsk(els);
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
|
|
68
146
|
const ROOT_ID = "dba-root";
|
|
69
147
|
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
148
|
+
/**
|
|
149
|
+
* Wire the server-rendered docked panel if present. Returns whether a docked
|
|
150
|
+
* shell exists on the page (so the caller knows to mark the floating button as
|
|
151
|
+
* a mobile-only entry). Idempotent via a `data-dba-wired` marker.
|
|
152
|
+
*/
|
|
153
|
+
function wireDockedPanel(): boolean {
|
|
154
|
+
const panel = document.querySelector<HTMLElement>('[data-dba-panel="rail"]');
|
|
155
|
+
if (!panel) return false;
|
|
156
|
+
if (panel.dataset.dbaWired) return true;
|
|
157
|
+
panel.dataset.dbaWired = "1";
|
|
158
|
+
|
|
159
|
+
const form = panel.querySelector<HTMLFormElement>("[data-dba-form]");
|
|
160
|
+
const input = panel.querySelector<HTMLInputElement>(".dba-input");
|
|
161
|
+
const answer = panel.querySelector<HTMLDivElement>(".dba-answer");
|
|
162
|
+
const submit = panel.querySelector<HTMLButtonElement>(".dba-submit");
|
|
163
|
+
if (!form || !input || !answer || !submit) return true;
|
|
164
|
+
|
|
165
|
+
// Lazy-load Turnstile on first interaction so a page that merely shows the
|
|
166
|
+
// docked panel still sets no third-party cookies until the reader engages.
|
|
167
|
+
input.addEventListener("focus", loadTurnstile, { once: true });
|
|
168
|
+
wireForm({ form, input, answer, submit });
|
|
169
|
+
return true;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Build + wire the floating button + dialog. `hasDocked` marks the root so CSS
|
|
174
|
+
* can hide the trigger at desktop widths (where the docked panel is visible).
|
|
175
|
+
*/
|
|
176
|
+
function mountFloating(hasDocked: boolean): void {
|
|
177
|
+
if (document.getElementById(ROOT_ID)) return;
|
|
73
178
|
|
|
74
179
|
const root = document.createElement("div");
|
|
75
180
|
root.id = ROOT_ID;
|
|
76
181
|
root.dataset.position = cfg.position;
|
|
182
|
+
if (hasDocked) root.dataset.hasDocked = "1";
|
|
77
183
|
|
|
78
184
|
const button = document.createElement("button");
|
|
79
185
|
button.type = "button";
|
|
@@ -88,7 +194,7 @@ function mount(): void {
|
|
|
88
194
|
? `<div class="cf-turnstile dba-turnstile" data-sitekey="${cfg.turnstileSitekey}" data-appearance="interaction-only" data-callback="__dbaTsCb" data-expired-callback="__dbaTsExpired" data-error-callback="__dbaTsExpired"></div>`
|
|
89
195
|
: "";
|
|
90
196
|
dialog.innerHTML = `
|
|
91
|
-
<form class="dba-form" method="dialog">
|
|
197
|
+
<form class="dba-form" method="dialog" data-dba-form>
|
|
92
198
|
<header class="dba-header">
|
|
93
199
|
<h2 class="dba-title"></h2>
|
|
94
200
|
<button type="button" class="dba-close" aria-label="Close">×</button>
|
|
@@ -101,7 +207,6 @@ function mount(): void {
|
|
|
101
207
|
<div class="dba-answer" aria-live="polite" tabindex="-1"></div>
|
|
102
208
|
</form>
|
|
103
209
|
`;
|
|
104
|
-
loadTurnstile();
|
|
105
210
|
|
|
106
211
|
// textContent (not innerHTML) for user-configurable strings — no injection.
|
|
107
212
|
dialog.querySelector<HTMLHeadingElement>(".dba-title")!.textContent = cfg.title;
|
|
@@ -112,73 +217,32 @@ function mount(): void {
|
|
|
112
217
|
const submit = dialog.querySelector<HTMLButtonElement>(".dba-submit")!;
|
|
113
218
|
|
|
114
219
|
button.addEventListener("click", () => {
|
|
220
|
+
// Lazy-load Turnstile only when the user engages, so normal page loads set
|
|
221
|
+
// no third-party cookies (Lighthouse-clean) and don't fetch the widget.
|
|
222
|
+
loadTurnstile();
|
|
115
223
|
dialog.showModal();
|
|
116
224
|
input.focus();
|
|
117
225
|
});
|
|
118
226
|
dialog.querySelector<HTMLButtonElement>(".dba-close")!.addEventListener("click", () => dialog.close());
|
|
119
227
|
|
|
120
|
-
form
|
|
121
|
-
e.preventDefault();
|
|
122
|
-
const query = input.value.trim();
|
|
123
|
-
if (!query) return;
|
|
124
|
-
|
|
125
|
-
submit.disabled = true;
|
|
126
|
-
answer.dataset.state = "loading";
|
|
127
|
-
answer.textContent = "…";
|
|
128
|
-
|
|
129
|
-
try {
|
|
130
|
-
const res = await fetch(cfg.endpoint, {
|
|
131
|
-
method: "POST",
|
|
132
|
-
headers: { "Content-Type": "application/json" },
|
|
133
|
-
body: JSON.stringify({
|
|
134
|
-
...buildAskRequest(cfg.site, query, cfg.mode),
|
|
135
|
-
...(turnstileToken ? { turnstileToken } : {}),
|
|
136
|
-
}),
|
|
137
|
-
});
|
|
138
|
-
if (!res.ok || !res.body) {
|
|
139
|
-
throw new Error(`Request failed (${res.status})`);
|
|
140
|
-
}
|
|
141
|
-
answer.textContent = "";
|
|
142
|
-
answer.dataset.state = "streaming";
|
|
143
|
-
const reader = res.body.getReader();
|
|
144
|
-
const decoder = new TextDecoder();
|
|
145
|
-
let buf = ""; // line buffer (don't parse a frame split across reads)
|
|
146
|
-
let rawAll = ""; // whole stream, for the trailing `event: chunks` sources
|
|
147
|
-
let answerMd = ""; // accumulated answer markdown
|
|
148
|
-
for (;;) {
|
|
149
|
-
const { value, done } = await reader.read();
|
|
150
|
-
if (done) break;
|
|
151
|
-
const text = decoder.decode(value, { stream: true });
|
|
152
|
-
rawAll += text;
|
|
153
|
-
buf += text;
|
|
154
|
-
const nl = buf.lastIndexOf("\n");
|
|
155
|
-
if (nl >= 0) {
|
|
156
|
-
answerMd += extractText(buf.slice(0, nl + 1));
|
|
157
|
-
buf = buf.slice(nl + 1);
|
|
158
|
-
answer.innerHTML = renderMarkdown(answerMd);
|
|
159
|
-
}
|
|
160
|
-
}
|
|
161
|
-
if (buf) answerMd += extractText(buf);
|
|
162
|
-
const sources = parseSources(rawAll, cfg.linkBase);
|
|
163
|
-
answer.innerHTML = renderMarkdown(answerMd) + (sources.length ? sourcesHtml(sources) : "");
|
|
164
|
-
answer.dataset.state = "done";
|
|
165
|
-
} catch (err) {
|
|
166
|
-
answer.dataset.state = "error";
|
|
167
|
-
answer.textContent = `Sorry — something went wrong. ${(err as Error).message}`;
|
|
168
|
-
} finally {
|
|
169
|
-
submit.disabled = false;
|
|
170
|
-
// Turnstile tokens are single-use — refresh for the next question.
|
|
171
|
-
turnstileApi()?.reset?.();
|
|
172
|
-
turnstileToken = null;
|
|
173
|
-
}
|
|
174
|
-
});
|
|
228
|
+
wireForm({ form, input, answer, submit });
|
|
175
229
|
|
|
176
230
|
root.append(button, dialog);
|
|
177
231
|
document.body.appendChild(root);
|
|
178
232
|
}
|
|
179
233
|
|
|
234
|
+
function mount(): void {
|
|
235
|
+
if (typeof document === "undefined") return;
|
|
236
|
+
// Wire the docked panel first; its presence decides whether the floating
|
|
237
|
+
// button is a full widget (no rail) or just the mobile entry (rail present).
|
|
238
|
+
const hasDocked = wireDockedPanel();
|
|
239
|
+
mountFloating(hasDocked);
|
|
240
|
+
}
|
|
241
|
+
|
|
180
242
|
function unmount(): void {
|
|
181
243
|
if (typeof document === "undefined") return;
|
|
244
|
+
// Only the floating widget is ours to remove; the docked shell is part of the
|
|
245
|
+
// document Astro swaps, and is re-wired by mount() on the next page-load.
|
|
182
246
|
document.getElementById(ROOT_ID)?.remove();
|
|
183
247
|
}
|
|
184
248
|
|
package/src/styles.css
CHANGED
|
@@ -13,6 +13,25 @@
|
|
|
13
13
|
--dba-radius: var(--radius, 0.5rem);
|
|
14
14
|
}
|
|
15
15
|
|
|
16
|
+
/* Docked rail panel (RightRailAsk.astro) lives OUTSIDE #dba-root, so it needs
|
|
17
|
+
its own token block to pick up the site theme (same tokens as #dba-root). */
|
|
18
|
+
.dba-panel {
|
|
19
|
+
--dba-accent: var(--color-accent, #2563eb);
|
|
20
|
+
--dba-accent-contrast: var(--color-accent-contrast, #ffffff);
|
|
21
|
+
--dba-surface: var(--color-surface, #ffffff);
|
|
22
|
+
--dba-text: var(--color-text, #1f2937);
|
|
23
|
+
--dba-border: var(--color-border, #e5e7eb);
|
|
24
|
+
--dba-radius: var(--radius, 0.5rem);
|
|
25
|
+
color: var(--dba-text);
|
|
26
|
+
font-size: 0.9rem;
|
|
27
|
+
}
|
|
28
|
+
/* In the narrow rail the form fills the column and stacks the input + button. */
|
|
29
|
+
.dba-panel .dba-form { padding: 0; gap: 0.5rem; }
|
|
30
|
+
.dba-panel .dba-inputrow { flex-direction: column; }
|
|
31
|
+
.dba-panel .dba-title { font-size: 0.95rem; }
|
|
32
|
+
/* The rail aside scrolls, so let the answer grow rather than scroll internally. */
|
|
33
|
+
.dba-panel .dba-answer { max-height: none; }
|
|
34
|
+
|
|
16
35
|
.dba-trigger {
|
|
17
36
|
position: fixed;
|
|
18
37
|
bottom: 1.25rem;
|
|
@@ -32,6 +51,13 @@
|
|
|
32
51
|
.dba-trigger:hover { filter: brightness(1.05); }
|
|
33
52
|
.dba-trigger:focus-visible { outline: 2px solid var(--dba-accent); outline-offset: 2px; }
|
|
34
53
|
|
|
54
|
+
/* When a docked panel is present the floating button is only the mobile entry —
|
|
55
|
+
hide it at the breakpoint where the rail (and its docked panel) is visible.
|
|
56
|
+
1024px matches DocsLayout's `lg:block` rail. */
|
|
57
|
+
@media (min-width: 1024px) {
|
|
58
|
+
#dba-root[data-has-docked] .dba-trigger { display: none; }
|
|
59
|
+
}
|
|
60
|
+
|
|
35
61
|
.dba-dialog {
|
|
36
62
|
width: min(40rem, 92vw);
|
|
37
63
|
max-height: 80vh;
|