@dogsbay/plugin-ask 0.2.0-beta.84 → 0.2.0-beta.87

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 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
- * Imported via the auto-generated `src/lib/plugin-runtime.ts` in the user's
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
- * Astro view transitions: the document is swapped on navigation, so we mount
10
- * on `astro:page-load` and tear down on `astro:before-swap` to avoid stale
11
- * nodes / duplicate buttons.
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
- const ROOT_ID = "dba-root";
46
- function mount() {
47
- if (typeof document === "undefined")
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; // already mounted
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.addEventListener("submit", async (e) => {
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") {
@@ -1 +1 @@
1
- {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"client.js","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"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"}
@@ -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;AAS5D,YAAY,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAE9C,MAAM,CAAC,OAAO,UAAU,GAAG,CAAC,KAAK,CAAC,EAAE,UAAU,GAAG,aAAa,CA+B7D"}
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;AAIjE,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,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,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"}
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
  /**
@@ -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;AAEzD,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;;;;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,gBAAgB,EAAE,MAAM,CAAC;CAC1B;AAaD;;;GAGG;AACH,wBAAgB,YAAY,CAAC,KAAK,EAAE,OAAO,GAAG,kBAAkB,CAiC/D"}
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
  }
@@ -1 +1 @@
1
- {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"schema.js","sourceRoot":"","sources":["../src/schema.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AA6DH,MAAM,QAAQ,GAAkD;IAC9D,IAAI,EAAE,QAAQ;IACd,WAAW,EAAE,QAAQ;IACrB,KAAK,EAAE,cAAc;IACrB,WAAW,EAAE,iBAAiB;IAC9B,QAAQ,EAAE,cAAc;IACxB,SAAS,EAAE,MAAM;IACjB,gBAAgB,EAAE,EAAE;CACrB,CAAC;AAEF,MAAM,GAAG,GAAG,qBAAqB,CAAC;AAElC;;;GAGG;AACH,MAAM,UAAU,YAAY,CAAC,KAAc;IACzC,IAAI,KAAK,KAAK,SAAS,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC;QAC/F,MAAM,IAAI,KAAK,CACb,GAAG,GAAG,sFAAsF,QAAQ,CAAC,KAAK,CAAC,GAAG,CAC/G,CAAC;IACJ,CAAC;IACD,MAAM,IAAI,GAAG,KAAgC,CAAC;IAE9C,MAAM,QAAQ,GAAG,UAAU,CAAC,IAAI,CAAC,QAAQ,EAAE,UAAU,CAAC,CAAC;IACvD,MAAM,IAAI,GAAG,qBAAqB,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAEtD,MAAM,GAAG,GAAuB,EAAE,GAAG,QAAQ,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC;IAEhE,IAAI,IAAI,CAAC,IAAI,KAAK,SAAS,EAAE,CAAC;QAC5B,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,IAAI,KAAK,QAAQ,EAAE,CAAC;YACrD,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,8CAA8C,CAAC,CAAC;QACxE,CAAC;QACD,GAAG,CAAC,IAAI,GAAG,IAAI,CAAC,IAAI,CAAC;IACvB,CAAC;IACD,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,qBAAqB,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC7G,IAAI,IAAI,CAAC,KAAK,KAAK,SAAS;QAAE,GAAG,CAAC,KAAK,GAAG,qBAAqB,CAAC,IAAI,CAAC,KAAK,EAAE,OAAO,CAAC,CAAC;IACrF,IAAI,IAAI,CAAC,WAAW,KAAK,SAAS;QAAE,GAAG,CAAC,WAAW,GAAG,qBAAqB,CAAC,IAAI,CAAC,WAAW,EAAE,aAAa,CAAC,CAAC;IAC7G,IAAI,IAAI,CAAC,QAAQ,KAAK,SAAS,EAAE,CAAC;QAChC,IAAI,IAAI,CAAC,QAAQ,KAAK,cAAc,IAAI,IAAI,CAAC,QAAQ,KAAK,aAAa,EAAE,CAAC;YACxE,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,6DAA6D,CAAC,CAAC;QACvF,CAAC;QACD,GAAG,CAAC,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC;IAC/B,CAAC;IACD,IAAI,IAAI,CAAC,SAAS,KAAK,SAAS,EAAE,CAAC;QACjC,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,KAAK,MAAM,IAAI,IAAI,CAAC,SAAS,KAAK,UAAU,EAAE,CAAC;YAC5F,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,4DAA4D,CAAC,CAAC;QACtF,CAAC;QACD,GAAG,CAAC,SAAS,GAAG,IAAI,CAAC,SAAS,CAAC;IACjC,CAAC;IACD,IAAI,IAAI,CAAC,gBAAgB,KAAK,SAAS,EAAE,CAAC;QACxC,GAAG,CAAC,gBAAgB,GAAG,qBAAqB,CAAC,IAAI,CAAC,gBAAgB,EAAE,kBAAkB,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,GAAG,CAAC;AACb,CAAC;AAED,SAAS,qBAAqB,CAAC,KAAc,EAAE,KAAa;IAC1D,IAAI,OAAO,KAAK,KAAK,QAAQ,IAAI,KAAK,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;QACrD,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,aAAa,KAAK,8BAA8B,CAAC,CAAC;IAC1E,CAAC;IACD,OAAO,KAAK,CAAC;AACf,CAAC;AAED,SAAS,UAAU,CAAC,KAAc,EAAE,KAAa;IAC/C,MAAM,CAAC,GAAG,qBAAqB,CAAC,KAAK,EAAE,KAAK,CAAC,CAAC;IAC9C,IAAI,CAAC;QACH,kCAAkC;QAClC,IAAI,GAAG,CAAC,CAAC,CAAC,CAAC;IACb,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,GAAG,GAAG,aAAa,KAAK,uCAAuC,CAAC,IAAI,CAAC,CAAC;IACxF,CAAC;IACD,OAAO,CAAC,CAAC;AACX,CAAC;AAED,SAAS,QAAQ,CAAC,KAAc;IAC9B,IAAI,KAAK,KAAK,IAAI;QAAE,OAAO,MAAM,CAAC;IAClC,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,OAAO,CAAC;IACzC,OAAO,OAAO,KAAK,CAAC;AACtB,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dogsbay/plugin-ask",
3
- "version": "0.2.0-beta.84",
3
+ "version": "0.2.0-beta.87",
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.84"
26
+ "@dogsbay/types": "0.2.0-beta.87"
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
- * Imported via the auto-generated `src/lib/plugin-runtime.ts` in the user's
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
- * Astro view transitions: the document is swapped on navigation, so we mount
10
- * on `astro:page-load` and tear down on `astro:before-swap` to avoid stale
11
- * nodes / duplicate buttons.
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
- function mount(): void {
71
- if (typeof document === "undefined") return;
72
- if (document.getElementById(ROOT_ID)) return; // already mounted
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.addEventListener("submit", async (e) => {
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
@@ -5,14 +5,32 @@
5
5
  */
6
6
 
7
7
  #dba-root {
8
- --dba-accent: var(--color-accent, #2563eb);
9
- --dba-accent-contrast: var(--color-accent-contrast, #ffffff);
10
- --dba-surface: var(--color-surface, #ffffff);
11
- --dba-text: var(--color-text, #1f2937);
8
+ --dba-accent: var(--color-primary, #2563eb);
9
+ --dba-accent-contrast: var(--color-primary-foreground, #ffffff);
10
+ --dba-surface: var(--color-background, #ffffff);
11
+ --dba-text: var(--color-foreground, #1f2937);
12
12
  --dba-border: var(--color-border, #e5e7eb);
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-primary, #2563eb);
20
+ --dba-accent-contrast: var(--color-primary-foreground, #ffffff);
21
+ --dba-surface: var(--color-background, #ffffff);
22
+ --dba-text: var(--color-foreground, #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
+ /* The docked form fills the rail column. */
29
+ .dba-panel .dba-form { padding: 0; gap: 0.5rem; }
30
+ .dba-panel .dba-title { font-size: 0.95rem; }
31
+ /* The rail aside scrolls, so let the answer grow rather than scroll internally. */
32
+ .dba-panel .dba-answer { max-height: none; }
33
+
16
34
  .dba-trigger {
17
35
  position: fixed;
18
36
  bottom: 1.25rem;
@@ -32,6 +50,13 @@
32
50
  .dba-trigger:hover { filter: brightness(1.05); }
33
51
  .dba-trigger:focus-visible { outline: 2px solid var(--dba-accent); outline-offset: 2px; }
34
52
 
53
+ /* When a docked panel is present the floating button is only the mobile entry —
54
+ hide it at the breakpoint where the rail (and its docked panel) is visible.
55
+ 1024px matches DocsLayout's `lg:block` rail. */
56
+ @media (min-width: 1024px) {
57
+ #dba-root[data-has-docked] .dba-trigger { display: none; }
58
+ }
59
+
35
60
  .dba-dialog {
36
61
  width: min(40rem, 92vw);
37
62
  max-height: 80vh;