blume 2.0.0 → 2.0.1

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.
Files changed (203) hide show
  1. package/AGENTS.md +1 -1
  2. package/CHANGELOG.md +28 -0
  3. package/README.md +2 -2
  4. package/dist/cli/{chunk-f7t03s3g.js → chunk-27g6wdth.js} +2 -2
  5. package/dist/cli/{chunk-mnqj32sj.js → chunk-2hn4b8z7.js} +13 -13
  6. package/dist/cli/chunk-2hn4b8z7.js.map +12 -0
  7. package/dist/cli/{chunk-by2290sx.js → chunk-5shv93fd.js} +2 -2
  8. package/dist/cli/{chunk-a9kptbw5.js → chunk-6crbhc3x.js} +3 -3
  9. package/dist/cli/{chunk-a9kptbw5.js.map → chunk-6crbhc3x.js.map} +1 -1
  10. package/dist/cli/{chunk-mwt1k8n7.js → chunk-6hsn950k.js} +20 -20
  11. package/dist/cli/chunk-6hsn950k.js.map +10 -0
  12. package/dist/cli/{chunk-11j0384y.js → chunk-6vm74dry.js} +13 -13
  13. package/dist/cli/{chunk-11j0384y.js.map → chunk-6vm74dry.js.map} +3 -3
  14. package/dist/cli/{chunk-j8mw0za6.js → chunk-79jhk4py.js} +8 -8
  15. package/dist/cli/{chunk-j8mw0za6.js.map → chunk-79jhk4py.js.map} +2 -2
  16. package/dist/cli/{chunk-nk3ts2xk.js → chunk-82bbrxdn.js} +2 -2
  17. package/dist/cli/{chunk-2q1dwty4.js → chunk-ah61y8py.js} +8 -8
  18. package/dist/cli/{chunk-2q1dwty4.js.map → chunk-ah61y8py.js.map} +4 -4
  19. package/dist/cli/{chunk-zxccj738.js → chunk-ce574jw2.js} +1 -1
  20. package/dist/cli/{chunk-y3e45rc8.js → chunk-ch6g3ar0.js} +3 -3
  21. package/dist/cli/{chunk-beat36xx.js → chunk-dh8cwk36.js} +5 -5
  22. package/dist/cli/{chunk-beat36xx.js.map → chunk-dh8cwk36.js.map} +2 -2
  23. package/dist/cli/{chunk-1w8dp3qb.js → chunk-epjnccmv.js} +13 -13
  24. package/dist/cli/{chunk-ernrthtr.js → chunk-f2z5v128.js} +13 -13
  25. package/dist/cli/{chunk-zg2gtj10.js → chunk-fs23ddbb.js} +2 -2
  26. package/dist/cli/{chunk-7ez8ny0t.js → chunk-fxypxtvm.js} +2 -2
  27. package/dist/cli/{chunk-tzne8qfq.js → chunk-fz5wtpmh.js} +13 -13
  28. package/dist/cli/{chunk-b5aj94ah.js → chunk-hdpx1tax.js} +4 -4
  29. package/dist/cli/{chunk-d80hr03s.js → chunk-jwyddg7y.js} +9 -9
  30. package/dist/cli/{chunk-d80hr03s.js.map → chunk-jwyddg7y.js.map} +2 -2
  31. package/dist/cli/{chunk-fh5hj5jt.js → chunk-kdp5q7ke.js} +15 -15
  32. package/dist/cli/{chunk-6k8vp3ta.js → chunk-kpf8rrjc.js} +9 -9
  33. package/dist/cli/{chunk-6k8vp3ta.js.map → chunk-kpf8rrjc.js.map} +3 -3
  34. package/dist/cli/{chunk-5a2z0198.js → chunk-m3vmjgmq.js} +9 -9
  35. package/dist/cli/{chunk-5a2z0198.js.map → chunk-m3vmjgmq.js.map} +2 -2
  36. package/dist/cli/{chunk-bctazmbk.js → chunk-mb2919y2.js} +4 -4
  37. package/dist/cli/{chunk-79njf86q.js → chunk-q5163e60.js} +13 -13
  38. package/dist/cli/{chunk-xaz13gwg.js → chunk-qkqwkpte.js} +196 -208
  39. package/dist/cli/{chunk-xaz13gwg.js.map → chunk-qkqwkpte.js.map} +48 -48
  40. package/dist/cli/{chunk-sqn5t4q0.js → chunk-qs4q5p4e.js} +3 -3
  41. package/dist/cli/{chunk-bw22s759.js → chunk-qwsrynx5.js} +1 -1
  42. package/dist/cli/{chunk-z01ze5c1.js → chunk-s1p84fyh.js} +15 -15
  43. package/dist/cli/{chunk-pnnvybbk.js → chunk-s6jhgk0q.js} +5 -5
  44. package/dist/cli/{chunk-pnnvybbk.js.map → chunk-s6jhgk0q.js.map} +2 -2
  45. package/dist/cli/{chunk-f2972sbt.js → chunk-vtk4a6dg.js} +1 -1
  46. package/dist/cli/{chunk-z1f5arsg.js → chunk-wgm7m9qk.js} +21 -21
  47. package/dist/cli/{chunk-z1f5arsg.js.map → chunk-wgm7m9qk.js.map} +4 -4
  48. package/dist/cli/{chunk-bnbmcwfb.js → chunk-wm7js3j9.js} +5 -5
  49. package/dist/cli/{chunk-bnbmcwfb.js.map → chunk-wm7js3j9.js.map} +3 -3
  50. package/dist/cli/{chunk-d1tadaw7.js → chunk-yt5n7ppj.js} +3 -3
  51. package/dist/cli/{chunk-pat2zzwc.js → chunk-yw7dm696.js} +1 -1
  52. package/dist/cli/{chunk-pat2zzwc.js.map → chunk-yw7dm696.js.map} +1 -1
  53. package/dist/cli/{chunk-88cpgt6h.js → chunk-zxcczpyx.js} +1 -1
  54. package/dist/cli/{chunk-41za066z.js → chunk-zxh4d9vy.js} +4 -4
  55. package/dist/cli/index.js +17 -17
  56. package/dist/types/ai/agent-readability.d.ts +1 -1
  57. package/dist/types/ai/api/paths.d.ts +1 -1
  58. package/dist/types/ai/ask-context.d.ts +7 -7
  59. package/dist/types/ai/ask.d.ts +43 -43
  60. package/dist/types/ai/index.d.ts +3 -3
  61. package/dist/types/ai/openapi-components.d.ts +1 -1
  62. package/dist/types/ai/serializers.d.ts +1 -1
  63. package/dist/types/ai/visibility.d.ts +1 -1
  64. package/dist/types/core/config-input.d.ts +17 -17
  65. package/dist/types/core/config.d.ts +3 -3
  66. package/dist/types/core/data.d.ts +3 -3
  67. package/dist/types/core/i18n-ui.d.ts +6 -8
  68. package/dist/types/core/schema.d.ts +5 -5
  69. package/dist/types/core/unrecognized-keys.d.ts +1 -1
  70. package/dist/types/search/documents.d.ts +1 -1
  71. package/dist/types/search/orama-index.d.ts +1 -1
  72. package/docs/02-deployment.mdx +4 -4
  73. package/docs/03-upgrading.mdx +22 -9
  74. package/docs/04-migrating.mdx +4 -4
  75. package/docs/08-faq.mdx +3 -3
  76. package/docs/advanced/custom-pages.mdx +2 -2
  77. package/docs/advanced/skills.mdx +1 -1
  78. package/docs/cli/audit.mdx +3 -3
  79. package/docs/cli/doctor.mdx +3 -3
  80. package/docs/cli/evals.mdx +7 -7
  81. package/docs/cli/index.mdx +2 -2
  82. package/docs/cli/translate.mdx +8 -8
  83. package/docs/configuration/{ask-ai.mdx → assistant.mdx} +19 -19
  84. package/docs/configuration/customization.mdx +2 -2
  85. package/docs/configuration/index.mdx +2 -2
  86. package/docs/configuration/meta.ts +1 -1
  87. package/docs/configuration/search.mdx +1 -1
  88. package/docs/content/i18n.mdx +2 -2
  89. package/docs/content/islands.mdx +1 -1
  90. package/docs/discoverability/agent-discovery.mdx +1 -1
  91. package/docs/discoverability/index.mdx +1 -1
  92. package/docs/index.mdx +2 -2
  93. package/package.json +1 -1
  94. package/skills/blume/SKILL.md +5 -5
  95. package/skills/blume-migrate/SKILL.md +2 -2
  96. package/src/ai/agent-readability.ts +4 -4
  97. package/src/ai/api/paths.ts +1 -1
  98. package/src/ai/ask-context.ts +7 -7
  99. package/src/ai/ask-data.ts +2 -2
  100. package/src/ai/ask.ts +84 -71
  101. package/src/ai/cors.ts +3 -3
  102. package/src/ai/index.ts +16 -16
  103. package/src/ai/openapi-components.ts +1 -1
  104. package/src/ai/serializers.ts +1 -1
  105. package/src/ai/visibility.ts +1 -1
  106. package/src/astro/generate.ts +19 -18
  107. package/src/astro/module-types.ts +1 -1
  108. package/src/astro/runtime-deps.ts +6 -6
  109. package/src/astro/templates.ts +26 -26
  110. package/src/blume-modules.d.ts +2 -2
  111. package/src/cli/commands/audit.ts +1 -1
  112. package/src/cli/commands/doctor.ts +7 -5
  113. package/src/cli/commands/eval.ts +3 -3
  114. package/src/cli/commands/migrate.ts +2 -2
  115. package/src/cli/commands/translate.ts +3 -3
  116. package/src/cli/commands/upgrade.ts +2 -2
  117. package/src/cli/required-secrets.ts +3 -3
  118. package/src/components/copy-feedback.ts +1 -1
  119. package/src/components/islands/{AskAI.astro → Assistant.astro} +10 -10
  120. package/src/components/islands/{ask-ai.tsx → assistant.tsx} +23 -23
  121. package/src/components/islands/hooks.ts +14 -12
  122. package/src/components/layout/Header.astro +10 -10
  123. package/src/components/layout/PageLayout.astro +6 -6
  124. package/src/components/layout/Pagination.astro +7 -7
  125. package/src/components/layout/ReferenceLayout.astro +1 -1
  126. package/src/components/layout/RootLayout.astro +6 -6
  127. package/src/components/layout/Search.astro +13 -13
  128. package/src/components/layout/analytics-client.ts +1 -1
  129. package/src/components/layout/drawer-inert.ts +1 -1
  130. package/src/components/openapi/description.ts +2 -2
  131. package/src/core/code-fences.ts +1 -1
  132. package/src/core/config-input.ts +19 -19
  133. package/src/core/config.ts +3 -3
  134. package/src/core/data.ts +3 -3
  135. package/src/core/i18n-ui.ts +35 -9
  136. package/src/core/request-body.ts +1 -1
  137. package/src/core/schema.ts +25 -20
  138. package/src/core/server-features.ts +2 -2
  139. package/src/core/ui-packs/ar.ts +4 -5
  140. package/src/core/ui-packs/bg.ts +4 -5
  141. package/src/core/ui-packs/bn.ts +4 -5
  142. package/src/core/ui-packs/ca.ts +4 -5
  143. package/src/core/ui-packs/cs.ts +4 -5
  144. package/src/core/ui-packs/da.ts +4 -5
  145. package/src/core/ui-packs/de.ts +4 -5
  146. package/src/core/ui-packs/el.ts +4 -5
  147. package/src/core/ui-packs/es.ts +4 -5
  148. package/src/core/ui-packs/fa.ts +4 -5
  149. package/src/core/ui-packs/fi.ts +4 -5
  150. package/src/core/ui-packs/fr.ts +4 -5
  151. package/src/core/ui-packs/he.ts +4 -5
  152. package/src/core/ui-packs/hi.ts +4 -5
  153. package/src/core/ui-packs/hr.ts +4 -5
  154. package/src/core/ui-packs/hu.ts +4 -5
  155. package/src/core/ui-packs/id.ts +4 -5
  156. package/src/core/ui-packs/it.ts +4 -5
  157. package/src/core/ui-packs/ja.ts +4 -5
  158. package/src/core/ui-packs/ko.ts +4 -5
  159. package/src/core/ui-packs/nl.ts +4 -5
  160. package/src/core/ui-packs/no.ts +4 -5
  161. package/src/core/ui-packs/pl.ts +4 -5
  162. package/src/core/ui-packs/pt-br.ts +4 -5
  163. package/src/core/ui-packs/pt.ts +4 -5
  164. package/src/core/ui-packs/ro.ts +4 -5
  165. package/src/core/ui-packs/ru.ts +4 -5
  166. package/src/core/ui-packs/sk.ts +4 -5
  167. package/src/core/ui-packs/sr.ts +4 -5
  168. package/src/core/ui-packs/sv.ts +4 -5
  169. package/src/core/ui-packs/th.ts +4 -5
  170. package/src/core/ui-packs/tr.ts +4 -5
  171. package/src/core/ui-packs/uk.ts +4 -5
  172. package/src/core/ui-packs/vi.ts +4 -5
  173. package/src/core/ui-packs/zh-tw.ts +4 -5
  174. package/src/core/ui-packs/zh.ts +4 -5
  175. package/src/core/unrecognized-keys.ts +1 -1
  176. package/src/registry/eject.ts +19 -18
  177. package/src/search/documents.ts +2 -2
  178. package/src/search/orama-index.ts +1 -1
  179. package/src/translate/report.ts +1 -1
  180. package/src/upgrade/upgrade.ts +1 -1
  181. package/dist/cli/chunk-mnqj32sj.js.map +0 -12
  182. package/dist/cli/chunk-mwt1k8n7.js.map +0 -10
  183. /package/dist/cli/{chunk-f7t03s3g.js.map → chunk-27g6wdth.js.map} +0 -0
  184. /package/dist/cli/{chunk-by2290sx.js.map → chunk-5shv93fd.js.map} +0 -0
  185. /package/dist/cli/{chunk-nk3ts2xk.js.map → chunk-82bbrxdn.js.map} +0 -0
  186. /package/dist/cli/{chunk-zxccj738.js.map → chunk-ce574jw2.js.map} +0 -0
  187. /package/dist/cli/{chunk-y3e45rc8.js.map → chunk-ch6g3ar0.js.map} +0 -0
  188. /package/dist/cli/{chunk-1w8dp3qb.js.map → chunk-epjnccmv.js.map} +0 -0
  189. /package/dist/cli/{chunk-ernrthtr.js.map → chunk-f2z5v128.js.map} +0 -0
  190. /package/dist/cli/{chunk-zg2gtj10.js.map → chunk-fs23ddbb.js.map} +0 -0
  191. /package/dist/cli/{chunk-7ez8ny0t.js.map → chunk-fxypxtvm.js.map} +0 -0
  192. /package/dist/cli/{chunk-tzne8qfq.js.map → chunk-fz5wtpmh.js.map} +0 -0
  193. /package/dist/cli/{chunk-b5aj94ah.js.map → chunk-hdpx1tax.js.map} +0 -0
  194. /package/dist/cli/{chunk-fh5hj5jt.js.map → chunk-kdp5q7ke.js.map} +0 -0
  195. /package/dist/cli/{chunk-bctazmbk.js.map → chunk-mb2919y2.js.map} +0 -0
  196. /package/dist/cli/{chunk-79njf86q.js.map → chunk-q5163e60.js.map} +0 -0
  197. /package/dist/cli/{chunk-sqn5t4q0.js.map → chunk-qs4q5p4e.js.map} +0 -0
  198. /package/dist/cli/{chunk-bw22s759.js.map → chunk-qwsrynx5.js.map} +0 -0
  199. /package/dist/cli/{chunk-z01ze5c1.js.map → chunk-s1p84fyh.js.map} +0 -0
  200. /package/dist/cli/{chunk-f2972sbt.js.map → chunk-vtk4a6dg.js.map} +0 -0
  201. /package/dist/cli/{chunk-d1tadaw7.js.map → chunk-yt5n7ppj.js.map} +0 -0
  202. /package/dist/cli/{chunk-88cpgt6h.js.map → chunk-zxcczpyx.js.map} +0 -0
  203. /package/dist/cli/{chunk-41za066z.js.map → chunk-zxh4d9vy.js.map} +0 -0
@@ -7,7 +7,7 @@ import { createPortal } from "react-dom";
7
7
  import type { UIStrings } from "../../core/i18n-ui.ts";
8
8
  import { copyText } from "../copy-feedback.ts";
9
9
  import { joinBase, prefixBase } from "./base-path.ts";
10
- import { useAskAI } from "./hooks.ts";
10
+ import { useAssistant } from "./hooks.ts";
11
11
 
12
12
  /** A resolved empty-state prompt; `icon` is ready-to-inline SVG (or null). */
13
13
  interface Suggestion {
@@ -16,10 +16,10 @@ interface Suggestion {
16
16
  }
17
17
 
18
18
  /**
19
- * The panel's chrome glyphs, resolved server-side in `AskAI.astro` and passed in
19
+ * The panel's chrome glyphs, resolved server-side in `Assistant.astro` and passed in
20
20
  * as ready-to-inline Lucide bodies so this client island ships no icon data.
21
21
  */
22
- interface AskIcons {
22
+ interface AssistantIcons {
23
23
  arrowUp: string;
24
24
  chat: string;
25
25
  clear: string;
@@ -29,7 +29,7 @@ interface AskIcons {
29
29
 
30
30
  // Empty bodies so the island still renders (iconless) if instantiated without
31
31
  // the Astro wrapper that resolves the real Lucide glyphs.
32
- const EMPTY_ICONS: AskIcons = {
32
+ const EMPTY_ICONS: AssistantIcons = {
33
33
  arrowUp: "",
34
34
  chat: "",
35
35
  clear: "",
@@ -38,7 +38,7 @@ const EMPTY_ICONS: AskIcons = {
38
38
  };
39
39
 
40
40
  // English fallback so the island renders even if no dictionary is passed.
41
- const DEFAULT_ASK: UIStrings["ask"] = {
41
+ const DEFAULT_ASK: UIStrings["assistant"] = {
42
42
  ai: "AI",
43
43
  clear: "Clear conversation",
44
44
  close: "Close",
@@ -49,7 +49,7 @@ const DEFAULT_ASK: UIStrings["ask"] = {
49
49
  placeholder: "Ask a question…",
50
50
  send: "Send",
51
51
  tip: "Tip: You can open and close chat with",
52
- title: "Ask AI",
52
+ title: "Assistant",
53
53
  you: "You",
54
54
  };
55
55
 
@@ -130,15 +130,15 @@ const ICON_BUTTON_CLASS =
130
130
  const ANSWER_CLASS =
131
131
  "prose prose-sm max-w-none text-foreground [&_a]:inline-flex [&_a]:items-center [&_a]:gap-1 [&_a]:rounded-full [&_a]:bg-muted [&_a]:px-2 [&_a]:py-1 [&_a]:align-middle [&_a]:font-medium [&_a]:text-[0.7rem] [&_a]:leading-none [&_a]:text-muted-foreground! [&_a]:no-underline! [&_a:hover]:text-foreground!";
132
132
 
133
- const AskAI = ({
133
+ const Assistant = ({
134
134
  endpoint = DEFAULT_ASK_ENDPOINT,
135
135
  icons = EMPTY_ICONS,
136
136
  strings,
137
137
  suggestions = EMPTY_SUGGESTIONS,
138
138
  }: {
139
139
  endpoint?: string;
140
- icons?: AskIcons;
141
- strings?: UIStrings["ask"];
140
+ icons?: AssistantIcons;
141
+ strings?: UIStrings["assistant"];
142
142
  suggestions?: Suggestion[];
143
143
  }) => {
144
144
  // Merge per key (not `strings ?? …`) so a dictionary from a stale snapshot
@@ -147,14 +147,14 @@ const AskAI = ({
147
147
  const [open, setOpen] = useState(false);
148
148
  const [input, setInput] = useState("");
149
149
  // The streaming client — request shaping, optimistic assistant bubble,
150
- // stale-stream/abort guards, error-body handling — is the public useAskAI
150
+ // stale-stream/abort guards, error-body handling — is the public useAssistant
151
151
  // hook, so the built-in panel and custom UIs share one implementation.
152
152
  const {
153
153
  ask,
154
154
  loading: busy,
155
155
  messages,
156
156
  reset,
157
- } = useAskAI({ endpoint, errorMessage: t.error });
157
+ } = useAssistant({ endpoint, errorMessage: t.error });
158
158
  const inputRef = useRef<HTMLTextAreaElement>(null);
159
159
  const scrollRef = useRef<HTMLDivElement>(null);
160
160
  const triggerRef = useRef<HTMLButtonElement>(null);
@@ -167,7 +167,7 @@ const AskAI = ({
167
167
  // Null until mount (guards SSR), then refreshed on every client-router swap:
168
168
  // the island rides across navigations via transition:persist, but each swap
169
169
  // installs a NEW <body>, discarding the portaled panel with the old one and
170
- // resetting the `data-blume-ask` push attribute to the incoming page's
170
+ // resetting the `data-blume-assistant` push attribute to the incoming page's
171
171
  // server-rendered set. Reading document.body inline in render would NOT
172
172
  // recover from that — it isn't a reactive value, so the memoized portal
173
173
  // keeps its stale (detached) container. State identity is what re-anchors
@@ -183,10 +183,10 @@ const AskAI = ({
183
183
  return () => document.removeEventListener("astro:after-swap", onSwap);
184
184
  }, []);
185
185
 
186
- // The search modal forwards its query so "Ask AI: <query>" carries straight in.
186
+ // The search modal forwards its query so "Assistant: <query>" carries straight in.
187
187
  useEffect(() => {
188
188
  const handler = (event: Event) => {
189
- // SAFETY: `blume:open-ask-ai` is only ever dispatched as a CustomEvent
189
+ // SAFETY: `blume:open-assistant` is only ever dispatched as a CustomEvent
190
190
  // whose optional detail carries the search query.
191
191
  const query = (event as CustomEvent<{ query?: string }>).detail?.query;
192
192
  if (query) {
@@ -194,8 +194,8 @@ const AskAI = ({
194
194
  }
195
195
  setOpen(true);
196
196
  };
197
- window.addEventListener("blume:open-ask-ai", handler);
198
- return () => window.removeEventListener("blume:open-ask-ai", handler);
197
+ window.addEventListener("blume:open-assistant", handler);
198
+ return () => window.removeEventListener("blume:open-assistant", handler);
199
199
  }, []);
200
200
 
201
201
  // ⌘I / Ctrl+I toggles the panel; Escape closes it. Shift/Alt chords are
@@ -226,17 +226,17 @@ const AskAI = ({
226
226
  return () => window.removeEventListener("keydown", onKey);
227
227
  }, [open]);
228
228
 
229
- // Drive the desktop content push from a body attribute (see AskAI.astro CSS).
229
+ // Drive the desktop content push from a body attribute (see Assistant.astro CSS).
230
230
  useEffect(() => {
231
231
  if (open) {
232
232
  returnFocusRef.current =
233
233
  document.activeElement instanceof HTMLElement
234
234
  ? document.activeElement
235
235
  : null;
236
- document.body.dataset.blumeAsk = "open";
236
+ document.body.dataset.blumeAssistant = "open";
237
237
  inputRef.current?.focus();
238
238
  } else {
239
- delete document.body.dataset.blumeAsk;
239
+ delete document.body.dataset.blumeAssistant;
240
240
  // Return focus to the element that opened the panel (or the trigger when
241
241
  // it's gone), so closing doesn't strand keyboard focus in an inert tree.
242
242
  // `returnFocusRef` is only set on open, so initial mount is a no-op.
@@ -249,7 +249,7 @@ const AskAI = ({
249
249
  }
250
250
  }
251
251
  return () => {
252
- delete document.body.dataset.blumeAsk;
252
+ delete document.body.dataset.blumeAssistant;
253
253
  };
254
254
  }, [open]);
255
255
 
@@ -261,7 +261,7 @@ const AskAI = ({
261
261
  // document.body (not portalTarget) so the compiler doesn't flag a state
262
262
  // mutation; by the time this runs for a swap, they are the same element.
263
263
  if (open && portalTarget) {
264
- document.body.dataset.blumeAsk = "open";
264
+ document.body.dataset.blumeAssistant = "open";
265
265
  }
266
266
  // oxlint-disable-next-line react/exhaustive-effect-dependencies -- see above
267
267
  }, [open, portalTarget]);
@@ -387,7 +387,7 @@ const AskAI = ({
387
387
  // The closed panel is only translated off-screen; `inert` drops its
388
388
  // buttons/textarea from the tab order and the accessibility tree.
389
389
  inert={!open}
390
- className={`border-border bg-background fixed inset-y-0 end-0 z-[60] flex w-[var(--blume-ask-width)] flex-col border-s shadow-2xl transition-transform duration-200 ease-out ${
390
+ className={`border-border bg-background fixed inset-y-0 end-0 z-[60] flex w-[var(--blume-assistant-width)] flex-col border-s shadow-2xl transition-transform duration-200 ease-out ${
391
391
  open ? "translate-x-0" : "translate-x-full rtl:-translate-x-full"
392
392
  }`}
393
393
  >
@@ -539,4 +539,4 @@ const AskAI = ({
539
539
  );
540
540
  };
541
541
 
542
- export default AskAI;
542
+ export default Assistant;
@@ -12,7 +12,7 @@ import { joinBase, stripBase } from "./base-path.ts";
12
12
  * project data can't come through context. Instead the layout serializes a small
13
13
  * snapshot into a `<script type="application/json" id="blume-client-data">` tag,
14
14
  * and {@link useBlume}/{@link usePage} read it after mount. {@link useSearch} and
15
- * {@link useAskAI} wrap the generated search client and the Ask AI endpoint.
15
+ * {@link useAssistant} wrap the generated search client and the assistant endpoint.
16
16
  *
17
17
  * Import them from `blume/hooks`:
18
18
  *
@@ -131,14 +131,14 @@ export const useSearch = (): UseSearch => {
131
131
  return { loading, results, search };
132
132
  };
133
133
 
134
- /** A single Ask AI chat message. */
134
+ /** A single assistant chat message. */
135
135
  export interface AskMessage {
136
136
  content: string;
137
137
  role: "assistant" | "user";
138
138
  }
139
139
 
140
- /** State + actions returned by {@link useAskAI}. */
141
- export interface UseAskAI {
140
+ /** State + actions returned by {@link useAssistant}. */
141
+ export interface UseAssistant {
142
142
  ask: (question: string) => Promise<void>;
143
143
  loading: boolean;
144
144
  messages: AskMessage[];
@@ -147,8 +147,8 @@ export interface UseAskAI {
147
147
 
148
148
  const DEFAULT_ASK_ENDPOINT = joinBase(import.meta.env.BASE_URL, "api/ask");
149
149
 
150
- export interface UseAskAIOptions {
151
- /** Existing Ask AI endpoint; defaults to Blume's generated `/api/ask`. */
150
+ export interface UseAssistantOptions {
151
+ /** Existing assistant endpoint; defaults to Blume's generated `/api/ask`. */
152
152
  endpoint?: string;
153
153
  /**
154
154
  * Shown as the assistant's answer when the request fails or throws.
@@ -161,11 +161,11 @@ export interface UseAskAIOptions {
161
161
  /** Shown as the assistant's answer when the request fails or throws. */
162
162
  const ASK_ERROR = "Something went wrong answering that. Please try again.";
163
163
 
164
- /** How the generated Ask AI route opens its missing-credential notice. */
165
- const NOT_CONFIGURED = /^Ask AI is not configured/u;
164
+ /** How the assistant's generated route opens its missing-credential notice. */
165
+ const NOT_CONFIGURED = /^The assistant is not configured/u;
166
166
 
167
167
  /**
168
- * The route's own explanation of a 503 — the generated route's "Ask AI is not
168
+ * The route's own explanation of a 503 — the generated route's "The assistant is not
169
169
  * configured: set AI_GATEWAY_API_KEY." — when the response is that notice, or
170
170
  * null. It names only the variable to set, never a value, so it's safe to
171
171
  * show, and it tells a site owner trying a fresh deploy what's missing instead
@@ -188,10 +188,12 @@ const currentPath = (): string =>
188
188
  stripBase(import.meta.env.BASE_URL, window.location.pathname);
189
189
 
190
190
  /**
191
- * Stream answers from the Ask AI endpoint. Mirrors the built-in Ask AI island so
191
+ * Stream answers from the assistant endpoint. Mirrors the built-in assistant island so
192
192
  * a custom chat UI shares the same grounded, page-aware backend.
193
193
  */
194
- export const useAskAI = (options: UseAskAIOptions = {}): UseAskAI => {
194
+ export const useAssistant = (
195
+ options: UseAssistantOptions = {}
196
+ ): UseAssistant => {
195
197
  const endpoint = options.endpoint ?? DEFAULT_ASK_ENDPOINT;
196
198
  const errorMessage = options.errorMessage ?? ASK_ERROR;
197
199
  const [messages, setMessages] = useState<AskMessage[]>([]);
@@ -208,7 +210,7 @@ export const useAskAI = (options: UseAskAIOptions = {}): UseAskAI => {
208
210
  // preserves a stable `ask` identity for consumers that depend on it. With the
209
211
  // compiler on it's redundant but inert.
210
212
  // oxlint-disable-next-line react-doctor/react-compiler-no-manual-memoization -- see above
211
- const ask = useCallback<UseAskAI["ask"]>(
213
+ const ask = useCallback<UseAssistant["ask"]>(
212
214
  async (question) => {
213
215
  const trimmed = question.trim();
214
216
  if (!trimmed || loading) {
@@ -39,13 +39,13 @@ interface Props {
39
39
  */
40
40
  locale?: string;
41
41
  /**
42
- * Whether the search modal offers an "Ask AI" hand-off. Defaults to whether
43
- * Ask AI is configured, so no page has to pass it; a layout can still opt a
42
+ * Whether the search modal offers a hand-off to the assistant. Defaults to whether
43
+ * the assistant is configured, so no page has to pass it; a layout can still opt a
44
44
  * shell out explicitly.
45
45
  */
46
- askEnabled?: boolean;
47
- /** Localized Ask AI strings for the active locale. */
48
- askStrings?: UIStrings["ask"];
46
+ assistantEnabled?: boolean;
47
+ /** Localized assistant strings for the active locale. */
48
+ assistantStrings?: UIStrings["assistant"];
49
49
  // The mobile menu button toggles the docs sidebar drawer; custom pages
50
50
  // without a sidebar (e.g. a landing page) pass `false` to hide it.
51
51
  hasSidebar?: boolean;
@@ -91,11 +91,11 @@ const {
91
91
  route,
92
92
  searchEnabled,
93
93
  locale,
94
- // `config.ask` is null whenever Ask AI is off, so the header is the one place
94
+ // `config.assistant` is null whenever the assistant is off, so the header is the one place
95
95
  // that has to know — the Ask trigger below and the search modal's hand-off to
96
96
  // it both switch on this, and every page gets both for free.
97
- askEnabled = Boolean(data.config.ask),
98
- askStrings,
97
+ assistantEnabled = Boolean(data.config.assistant),
98
+ assistantStrings,
99
99
  hasSidebar = true,
100
100
  hasDrawer = true,
101
101
  searchStrings,
@@ -250,7 +250,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
250
250
  {
251
251
  searchEnabled && (
252
252
  <SearchSlot
253
- askEnabled={askEnabled}
253
+ assistantEnabled={assistantEnabled}
254
254
  locale={searchLocale}
255
255
  navigation={navigation}
256
256
  popularPages={data.config.search.popular}
@@ -314,7 +314,7 @@ const clickScript = `(()=>{const dr=()=>{const h=document.querySelector("[data-b
314
314
  <span class="inline-flex dark:hidden"><Icon name="sun" size={18} /></span>
315
315
  <span class="hidden dark:inline-flex"><Icon name="moon" size={18} /></span>
316
316
  </button>
317
- {askEnabled && <Ask strings={askStrings} />}
317
+ {assistantEnabled && <Ask strings={assistantStrings} />}
318
318
  </div>
319
319
  </header>
320
320
  <script is:inline set:html={clickScript} />
@@ -78,11 +78,11 @@ interface Props {
78
78
  fontCssVars?: (string | FontHead)[];
79
79
  searchEnabled: boolean;
80
80
  /**
81
- * Opt this page out of the header's Ask AI trigger. Defaults to whether Ask
82
- * AI is configured, so a custom page gets the same trigger the docs pages have
81
+ * Opt this page out of the header's assistant trigger. Defaults to whether the
82
+ * assistant is configured, so a custom page gets the same trigger the docs pages have
83
83
  * without wiring anything up.
84
84
  */
85
- askEnabled?: boolean;
85
+ assistantEnabled?: boolean;
86
86
  /**
87
87
  * Start the header see-through, with its chrome in white, so it can sit over
88
88
  * a dark hero at the top of the page; it returns to the frosted bar as soon
@@ -163,7 +163,7 @@ const {
163
163
  themeMode,
164
164
  fontCssVars,
165
165
  searchEnabled,
166
- askEnabled,
166
+ assistantEnabled,
167
167
  transparentHeader,
168
168
  siteUrl,
169
169
  ogEnabled,
@@ -389,8 +389,8 @@ createIconSprite(Astro.locals);
389
389
  <span hidden id={NEW_TAB_HINT_ID}>{navStrings.opensInNewTab}</span>
390
390
  <Banner banner={banner} strings={strings.banner} />
391
391
  <Header
392
- askEnabled={askEnabled}
393
- askStrings={strings.ask}
392
+ assistantEnabled={assistantEnabled}
393
+ assistantStrings={strings.assistant}
394
394
  hasSidebar={false}
395
395
  locale={locale}
396
396
  localeSwitch={localeSwitch}
@@ -26,19 +26,19 @@ const s = { ...EN_UI.page, ...strings };
26
26
  (prev || next) && (
27
27
  <nav
28
28
  aria-label={s.pagination}
29
- class="mx-auto mt-12 flex max-w-content justify-between gap-4 border-border border-t pt-6 max-md:flex-col"
29
+ class="mx-auto mt-12 flex max-w-content justify-between gap-4 border-border border-t pt-6"
30
30
  >
31
31
  {prev ? (
32
32
  <a
33
- class="flex max-w-[48%] flex-1 items-center gap-2 rounded-blume border border-border px-4 py-3 text-foreground transition-colors hover:border-foreground max-md:max-w-full"
33
+ class="flex max-w-[48%] flex-1 items-center gap-2 rounded-blume border border-border px-4 py-3 text-foreground transition-colors hover:border-foreground"
34
34
  href={withBase(prev.route)}
35
35
  >
36
36
  <Icon class="rtl:-scale-x-100" name="arrow-left" size={16} />
37
37
  <span class="min-w-0">
38
- <span class="block text-muted-foreground text-xs max-md:hidden">
38
+ <span class="block text-muted-foreground text-xs">
39
39
  {s.previous}
40
40
  </span>
41
- <span class="block truncate font-medium">{prev.label}</span>
41
+ <span class="block font-medium max-md:line-clamp-2 max-md:wrap-break-word md:truncate">{prev.label}</span>
42
42
  </span>
43
43
  </a>
44
44
  ) : (
@@ -46,14 +46,14 @@ const s = { ...EN_UI.page, ...strings };
46
46
  )}
47
47
  {next && (
48
48
  <a
49
- class="ms-auto flex max-w-[48%] flex-1 items-center justify-end gap-2 rounded-blume border border-border px-4 py-3 text-end text-foreground transition-colors hover:border-foreground max-md:max-w-full"
49
+ class="ms-auto flex max-w-[48%] flex-1 items-center justify-end gap-2 rounded-blume border border-border px-4 py-3 text-end text-foreground transition-colors hover:border-foreground"
50
50
  href={withBase(next.route)}
51
51
  >
52
52
  <span class="min-w-0">
53
- <span class="block text-muted-foreground text-xs max-md:hidden">
53
+ <span class="block text-muted-foreground text-xs">
54
54
  {s.next}
55
55
  </span>
56
- <span class="block truncate font-medium">{next.label}</span>
56
+ <span class="block font-medium max-md:line-clamp-2 max-md:wrap-break-word md:truncate">{next.label}</span>
57
57
  </span>
58
58
  <Icon class="rtl:-scale-x-100" name="arrow-right" size={16} />
59
59
  </a>
@@ -148,7 +148,7 @@ createIconSprite(Astro.locals);
148
148
  <span hidden id={NEW_TAB_HINT_ID}>{navStrings.opensInNewTab}</span>
149
149
  <Banner banner={banner} strings={strings.banner} />
150
150
  <Header
151
- askStrings={strings.ask}
151
+ assistantStrings={strings.assistant}
152
152
  hasDrawer={false}
153
153
  hasSidebar={false}
154
154
  locale={locale}
@@ -122,10 +122,10 @@ interface Props {
122
122
  canonical?: string | null;
123
123
  editUrl?: string | null;
124
124
  /**
125
- * Opt this page out of the header's Ask AI trigger. Defaults to whether Ask
126
- * AI is configured, so pages never wire the trigger up themselves.
125
+ * Opt this page out of the header's assistant trigger. Defaults to whether the
126
+ * assistant is configured, so pages never wire the trigger up themselves.
127
127
  */
128
- askEnabled?: boolean;
128
+ assistantEnabled?: boolean;
129
129
  /** Show the "Was this page helpful?" rating below the content. */
130
130
  feedback?: boolean;
131
131
  exportPdf?: boolean;
@@ -248,7 +248,7 @@ const {
248
248
  x,
249
249
  canonical,
250
250
  editUrl,
251
- askEnabled,
251
+ assistantEnabled,
252
252
  feedback = false,
253
253
  exportPdf,
254
254
  exportEpub,
@@ -582,8 +582,8 @@ createIconSprite(Astro.locals);
582
582
  <Banner banner={banner} strings={strings.banner} />
583
583
  <VersionBanner notice={versionNotice ?? null} />
584
584
  <HeaderSlot
585
- askEnabled={askEnabled}
586
- askStrings={strings.ask}
585
+ assistantEnabled={assistantEnabled}
586
+ assistantStrings={strings.assistant}
587
587
  layout={layout}
588
588
  locale={locale}
589
589
  localeSwitch={localeSwitch}
@@ -12,7 +12,7 @@ import { flattenPages } from "./nav-utils.ts";
12
12
  // `SearchFn` it returns. Only the configured provider's SDK is bundled.
13
13
 
14
14
  interface Props {
15
- askEnabled?: boolean;
15
+ assistantEnabled?: boolean;
16
16
  navigation?: Navigation;
17
17
  /** Curated empty-state pages; falls back to the first sidebar pages when empty or omitted. */
18
18
  popularPages?: { icon?: string; label: string; route: string }[];
@@ -27,7 +27,7 @@ interface Props {
27
27
  }
28
28
 
29
29
  const {
30
- askEnabled = false,
30
+ assistantEnabled = false,
31
31
  navigation,
32
32
  popularPages,
33
33
  strings,
@@ -60,10 +60,10 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
60
60
 
61
61
  <blume-search
62
62
  class="contents"
63
- data-ask={askEnabled ? "" : undefined}
63
+ data-ask={assistantEnabled ? "" : undefined}
64
64
  data-i18n-all={s.all}
65
- data-i18n-ask={s.askAi}
66
- data-i18n-ask-hint={s.askAiHint}
65
+ data-i18n-ask={s.assistant}
66
+ data-i18n-ask-hint={s.assistantHint}
67
67
  data-i18n-dev={s.devOnly}
68
68
  data-i18n-empty={s.noResults}
69
69
  data-i18n-error={s.error}
@@ -309,7 +309,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
309
309
  loadFailed = false;
310
310
  /** First-open client/index load still in flight. */
311
311
  loading = false;
312
- askEnabled = false;
312
+ assistantEnabled = false;
313
313
  popular: PopularPage[] = [];
314
314
  selectables: Selectable[] = [];
315
315
  selectedIndex = -1;
@@ -323,7 +323,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
323
323
  devOnlyMsg = "Search is available in the production build.";
324
324
  noResultsMsg = "No results found.";
325
325
  errorMsg = "Something went wrong. Please try again.";
326
- askMsg = "Ask AI";
326
+ askMsg = "Assistant";
327
327
  askHintMsg = "Get an instant answer from AI";
328
328
  allMsg = "All";
329
329
  popularMsg = "Popular";
@@ -351,7 +351,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
351
351
  !event.shiftKey &&
352
352
  !event.altKey
353
353
  ) {
354
- // ⌘K toggles, mirroring the Ask AI panel's ⌘I: pressing it with
354
+ // ⌘K toggles, mirroring the assistant panel's ⌘I: pressing it with
355
355
  // the dialog open must close it, not re-showModal an open dialog
356
356
  // (an InvalidStateError on older engines).
357
357
  event.preventDefault();
@@ -394,7 +394,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
394
394
  this.results = this.querySelector("[data-blume-search-results]")!;
395
395
  this.message = this.querySelector("[data-blume-search-message]")!;
396
396
  this.preview = this.querySelector("[data-blume-search-preview]")!;
397
- this.askEnabled = this.hasAttribute("data-ask");
397
+ this.assistantEnabled = this.hasAttribute("data-ask");
398
398
 
399
399
  const popularScript = this.querySelector(
400
400
  "[data-blume-search-popular]"
@@ -636,7 +636,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
636
636
 
637
637
  this.renderFilters(result.sections);
638
638
 
639
- if (this.askEnabled) {
639
+ if (this.assistantEnabled) {
640
640
  const group = this.addGroup(this.askMsg);
641
641
  const ask = this.createAskRow(query);
642
642
  group.appendChild(ask.el);
@@ -650,7 +650,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
650
650
  group.appendChild(item.el);
651
651
  this.selectables.push(item);
652
652
  }
653
- } else if (!this.askEnabled) {
653
+ } else if (!this.assistantEnabled) {
654
654
  this.setMessage(this.noResultsMsg);
655
655
  }
656
656
 
@@ -658,7 +658,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
658
658
  }
659
659
 
660
660
  renderEmpty() {
661
- if (this.askEnabled) {
661
+ if (this.assistantEnabled) {
662
662
  const group = this.addGroup(this.askMsg);
663
663
  const ask = this.createAskRow("");
664
664
  group.appendChild(ask.el);
@@ -874,7 +874,7 @@ const kbd = "rounded border border-border bg-muted px-1 py-0.5 font-mono";
874
874
  const query = this.input.value.trim();
875
875
  this.dialog.close();
876
876
  window.dispatchEvent(
877
- new CustomEvent("blume:open-ask-ai", { detail: { query } })
877
+ new CustomEvent("blume:open-assistant", { detail: { query } })
878
878
  );
879
879
  } else if (item.url) {
880
880
  // Close first so the dialog isn't left open over the transition;
@@ -96,7 +96,7 @@ const trackGlobals = (
96
96
  * @param props Properties every provider receives.
97
97
  * @param local Properties only the `blume:track` CustomEvent carries — free
98
98
  * text a site may bridge to a provider on its own terms, but that must not
99
- * reach third parties unasked (a reader's Ask AI question, for instance).
99
+ * reach third parties unasked (a reader's question to the assistant, for instance).
100
100
  */
101
101
  export const track = (
102
102
  event: string,
@@ -119,7 +119,7 @@ export const syncDrawerInert = (): void => {
119
119
  });
120
120
  document.addEventListener("astro:after-swap", sync);
121
121
  document.addEventListener("keydown", (event) => {
122
- // An open dialog (search, Ask AI) closes itself on Escape first.
122
+ // An open dialog (search, the assistant) closes itself on Escape first.
123
123
  if (
124
124
  event.key !== "Escape" ||
125
125
  !isOpen() ||
@@ -14,7 +14,7 @@ import { Marked } from "marked";
14
14
  * `marked` rather than the site's own Markdown pipeline: the pipeline is async, plugin-laden and
15
15
  * built for whole documents, while these are thousands of short strings per build — a large
16
16
  * reference renders tens of thousands of them. `marked` is synchronous, already a dependency,
17
- * and already how the Ask AI island renders model Markdown.
17
+ * and already how the assistant island renders model Markdown.
18
18
  */
19
19
  const TABLE = /<table>[\s\S]*?<\/table>/gu;
20
20
 
@@ -30,7 +30,7 @@ const escapeHtml = (text: string): string =>
30
30
  text.replaceAll("&", "&amp;").replaceAll("<", "&lt;").replaceAll(">", "&gt;");
31
31
 
32
32
  const markdown = new Marked({
33
- // `breaks` is deliberately NOT set, unlike the Ask AI island. Docstring prose is hard-wrapped at
33
+ // `breaks` is deliberately NOT set, unlike the assistant island. Docstring prose is hard-wrapped at
34
34
  // 72 or 79 columns, so honouring single newlines would break every sentence mid-flow at exactly
35
35
  // the width the source file happened to use.
36
36
  breaks: false,
@@ -1,6 +1,6 @@
1
1
  /**
2
2
  * Fenced-code state for line scanners. Kept free of imports so request-time
3
- * code (the Ask AI grounding excerpts) can share it with the build-time source
3
+ * code (the assistant's grounding excerpts) can share it with the build-time source
4
4
  * scanners without pulling the filesystem and schema modules along.
5
5
  *
6
6
  * CommonMark allows backtick *and* tilde fences, three or more characters