@blaaiz/docs-core 0.1.0
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/LICENSE +21 -0
- package/README.md +103 -0
- package/dist/chunk-3ZX4WIE3.js +2984 -0
- package/dist/chunk-3ZX4WIE3.js.map +1 -0
- package/dist/chunk-JCYR6RPE.js +31 -0
- package/dist/chunk-JCYR6RPE.js.map +1 -0
- package/dist/chunk-ZKOOKLZ3.js +124 -0
- package/dist/chunk-ZKOOKLZ3.js.map +1 -0
- package/dist/cli.js +534 -0
- package/dist/cli.js.map +1 -0
- package/dist/generator.cjs +508 -0
- package/dist/generator.cjs.map +1 -0
- package/dist/generator.d.cts +122 -0
- package/dist/generator.d.ts +122 -0
- package/dist/generator.js +177 -0
- package/dist/generator.js.map +1 -0
- package/dist/index.cjs +3024 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +1182 -0
- package/dist/index.d.ts +1182 -0
- package/dist/index.js +3 -0
- package/dist/index.js.map +1 -0
- package/dist/navigation-CGqFIPlP.d.cts +498 -0
- package/dist/navigation-CGqFIPlP.d.ts +498 -0
- package/dist/openapi-types-CJ6p5Cux.d.cts +78 -0
- package/dist/openapi-types-CJ6p5Cux.d.ts +78 -0
- package/dist/ui/api-try-it.cjs +654 -0
- package/dist/ui/api-try-it.cjs.map +1 -0
- package/dist/ui/api-try-it.d.cts +78 -0
- package/dist/ui/api-try-it.d.ts +78 -0
- package/dist/ui/api-try-it.js +509 -0
- package/dist/ui/api-try-it.js.map +1 -0
- package/dist/ui/ask-ai.cjs +810 -0
- package/dist/ui/ask-ai.cjs.map +1 -0
- package/dist/ui/ask-ai.d.cts +57 -0
- package/dist/ui/ask-ai.d.ts +57 -0
- package/dist/ui/ask-ai.js +808 -0
- package/dist/ui/ask-ai.js.map +1 -0
- package/dist/ui/copy-page.cjs +312 -0
- package/dist/ui/copy-page.cjs.map +1 -0
- package/dist/ui/copy-page.d.cts +33 -0
- package/dist/ui/copy-page.d.ts +33 -0
- package/dist/ui/copy-page.js +183 -0
- package/dist/ui/copy-page.js.map +1 -0
- package/dist/ui/mermaid.cjs +363 -0
- package/dist/ui/mermaid.cjs.map +1 -0
- package/dist/ui/mermaid.d.cts +13 -0
- package/dist/ui/mermaid.d.ts +13 -0
- package/dist/ui/mermaid.js +361 -0
- package/dist/ui/mermaid.js.map +1 -0
- package/dist/ui.cjs +661 -0
- package/dist/ui.cjs.map +1 -0
- package/dist/ui.d.cts +428 -0
- package/dist/ui.d.ts +428 -0
- package/dist/ui.js +537 -0
- package/dist/ui.js.map +1 -0
- package/package.json +145 -0
- package/patches/fumadocs-openapi.patch +173 -0
- package/skills/AGENTS-section.md +36 -0
- package/skills/SKILL.md +363 -0
- package/styles/api-reference.css +1417 -0
- package/styles/ask-ai.css +563 -0
- package/styles/auth.css +462 -0
- package/styles/docs.css +247 -0
- package/styles/home.css +376 -0
- package/templates/init/content/docs/index.mdx.tmpl +52 -0
- package/templates/init/content/docs/meta.json.tmpl +3 -0
- package/templates/init/content/docs.json.tmpl +12 -0
- package/templates/init/content/nav.json.tmpl +7 -0
- package/templates/init/docs.config.ts.tmpl +36 -0
- package/templates/init/env.example.tmpl +15 -0
|
@@ -0,0 +1,808 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
import { useState, useRef, useEffect, useCallback } from 'react';
|
|
3
|
+
import { createPortal } from 'react-dom';
|
|
4
|
+
import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
|
|
5
|
+
|
|
6
|
+
var MAX_MESSAGE_CHARS = 4e3;
|
|
7
|
+
function toRequestMessages(turns) {
|
|
8
|
+
const messages = [];
|
|
9
|
+
for (const turn of turns) {
|
|
10
|
+
if (turn.role === "assistant" && turn.state === "error") {
|
|
11
|
+
continue;
|
|
12
|
+
}
|
|
13
|
+
const content = turn.text.trim().slice(0, MAX_MESSAGE_CHARS);
|
|
14
|
+
if (content === "") {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
messages.push({ role: turn.role, content });
|
|
18
|
+
}
|
|
19
|
+
return messages.slice(-10);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
// src/ui/ask-ai/markdown.ts
|
|
23
|
+
var SCHEME = /^([a-z][a-z0-9+.-]*):/i;
|
|
24
|
+
var FENCE = /^ {0,3}(`{3,})\s*([^\s`]*)/;
|
|
25
|
+
var HEADING = /^ {0,3}(#{1,6})\s+(.*)$/;
|
|
26
|
+
var BULLET_ITEM = /^ {0,3}[-*+][ \t]+(.*)$/;
|
|
27
|
+
var ORDERED_ITEM = /^ {0,3}\d{1,9}[.)][ \t]+(.*)$/;
|
|
28
|
+
function hasControlChars(value) {
|
|
29
|
+
for (const char of value) {
|
|
30
|
+
const code = char.codePointAt(0) ?? 0;
|
|
31
|
+
if (code < 32 || code === 127) {
|
|
32
|
+
return true;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
return false;
|
|
36
|
+
}
|
|
37
|
+
function safeAnswerHref(raw) {
|
|
38
|
+
const href = raw.trim();
|
|
39
|
+
if (href === "" || hasControlChars(href)) {
|
|
40
|
+
return null;
|
|
41
|
+
}
|
|
42
|
+
const scheme = SCHEME.exec(href);
|
|
43
|
+
if (scheme !== null) {
|
|
44
|
+
const name = (scheme[1] ?? "").toLowerCase();
|
|
45
|
+
return name === "http" || name === "https" ? { href, external: true } : null;
|
|
46
|
+
}
|
|
47
|
+
if (href.startsWith("//") || !href.startsWith("/")) {
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
return { href, external: false };
|
|
51
|
+
}
|
|
52
|
+
function pushText(nodes, text) {
|
|
53
|
+
if (text === "") {
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
const last = nodes.at(-1);
|
|
57
|
+
if (last?.kind === "text") {
|
|
58
|
+
nodes[nodes.length - 1] = { kind: "text", text: last.text + text };
|
|
59
|
+
return;
|
|
60
|
+
}
|
|
61
|
+
nodes.push({ kind: "text", text });
|
|
62
|
+
}
|
|
63
|
+
function readLink(source, start) {
|
|
64
|
+
const labelEnd = source.indexOf("]", start + 1);
|
|
65
|
+
if (labelEnd === -1 || source[labelEnd + 1] !== "(") {
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
const targetEnd = source.indexOf(")", labelEnd + 2);
|
|
69
|
+
if (targetEnd === -1) {
|
|
70
|
+
return null;
|
|
71
|
+
}
|
|
72
|
+
const label = source.slice(start + 1, labelEnd);
|
|
73
|
+
const target = source.slice(labelEnd + 2, targetEnd);
|
|
74
|
+
const href = safeAnswerHref(target.split(/\s+/)[0] ?? "");
|
|
75
|
+
const next = targetEnd + 1;
|
|
76
|
+
if (href === null) {
|
|
77
|
+
return { node: { kind: "text", text: label }, next };
|
|
78
|
+
}
|
|
79
|
+
return {
|
|
80
|
+
node: { kind: "link", text: label, href: href.href, external: href.external },
|
|
81
|
+
next
|
|
82
|
+
};
|
|
83
|
+
}
|
|
84
|
+
function parseAnswerInline(source, allowStrong = true) {
|
|
85
|
+
const nodes = [];
|
|
86
|
+
let plain = "";
|
|
87
|
+
let index = 0;
|
|
88
|
+
while (index < source.length) {
|
|
89
|
+
const char = source[index];
|
|
90
|
+
if (char === "`") {
|
|
91
|
+
const end = source.indexOf("`", index + 1);
|
|
92
|
+
if (end > index + 1) {
|
|
93
|
+
pushText(nodes, plain);
|
|
94
|
+
plain = "";
|
|
95
|
+
nodes.push({ kind: "code", text: source.slice(index + 1, end) });
|
|
96
|
+
index = end + 1;
|
|
97
|
+
continue;
|
|
98
|
+
}
|
|
99
|
+
} else if (allowStrong && char === "*" && source[index + 1] === "*") {
|
|
100
|
+
const end = source.indexOf("**", index + 2);
|
|
101
|
+
if (end > index + 2) {
|
|
102
|
+
pushText(nodes, plain);
|
|
103
|
+
plain = "";
|
|
104
|
+
nodes.push({
|
|
105
|
+
kind: "strong",
|
|
106
|
+
children: parseAnswerInline(source.slice(index + 2, end), false)
|
|
107
|
+
});
|
|
108
|
+
index = end + 2;
|
|
109
|
+
continue;
|
|
110
|
+
}
|
|
111
|
+
} else if (char === "[") {
|
|
112
|
+
const link = readLink(source, index);
|
|
113
|
+
if (link !== null) {
|
|
114
|
+
pushText(nodes, plain);
|
|
115
|
+
plain = "";
|
|
116
|
+
if (link.node.kind === "text") {
|
|
117
|
+
pushText(nodes, link.node.text);
|
|
118
|
+
} else {
|
|
119
|
+
nodes.push(link.node);
|
|
120
|
+
}
|
|
121
|
+
index = link.next;
|
|
122
|
+
continue;
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
plain += char ?? "";
|
|
126
|
+
index += 1;
|
|
127
|
+
}
|
|
128
|
+
pushText(nodes, plain);
|
|
129
|
+
return nodes;
|
|
130
|
+
}
|
|
131
|
+
function readItem(line) {
|
|
132
|
+
const bullet = BULLET_ITEM.exec(line);
|
|
133
|
+
if (bullet !== null) {
|
|
134
|
+
return { text: bullet[1] ?? "", ordered: false };
|
|
135
|
+
}
|
|
136
|
+
const ordered = ORDERED_ITEM.exec(line);
|
|
137
|
+
if (ordered !== null) {
|
|
138
|
+
return { text: ordered[1] ?? "", ordered: true };
|
|
139
|
+
}
|
|
140
|
+
return null;
|
|
141
|
+
}
|
|
142
|
+
function startsBlock(line) {
|
|
143
|
+
return line.trim() === "" || FENCE.test(line) || HEADING.test(line) || readItem(line) !== null;
|
|
144
|
+
}
|
|
145
|
+
function parseAnswerMarkdown(source) {
|
|
146
|
+
const lines = source.split(/\r\n|\r|\n/);
|
|
147
|
+
const blocks = [];
|
|
148
|
+
let index = 0;
|
|
149
|
+
while (index < lines.length) {
|
|
150
|
+
const line = lines[index] ?? "";
|
|
151
|
+
if (line.trim() === "") {
|
|
152
|
+
index += 1;
|
|
153
|
+
continue;
|
|
154
|
+
}
|
|
155
|
+
const fence = FENCE.exec(line);
|
|
156
|
+
if (fence !== null) {
|
|
157
|
+
const marker = fence[1] ?? "```";
|
|
158
|
+
const language = fence[2] === void 0 || fence[2] === "" ? void 0 : fence[2];
|
|
159
|
+
const body = [];
|
|
160
|
+
let closed = false;
|
|
161
|
+
index += 1;
|
|
162
|
+
while (index < lines.length) {
|
|
163
|
+
const inner = lines[index] ?? "";
|
|
164
|
+
index += 1;
|
|
165
|
+
if (inner.trimStart().startsWith(marker)) {
|
|
166
|
+
closed = true;
|
|
167
|
+
break;
|
|
168
|
+
}
|
|
169
|
+
body.push(inner);
|
|
170
|
+
}
|
|
171
|
+
blocks.push({ kind: "code", language, text: body.join("\n"), closed });
|
|
172
|
+
continue;
|
|
173
|
+
}
|
|
174
|
+
const heading = HEADING.exec(line);
|
|
175
|
+
if (heading !== null) {
|
|
176
|
+
blocks.push({
|
|
177
|
+
kind: "heading",
|
|
178
|
+
level: (heading[1] ?? "#").length,
|
|
179
|
+
children: parseAnswerInline((heading[2] ?? "").trim())
|
|
180
|
+
});
|
|
181
|
+
index += 1;
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
const first = readItem(line);
|
|
185
|
+
if (first !== null) {
|
|
186
|
+
const texts = [first.text];
|
|
187
|
+
index += 1;
|
|
188
|
+
while (index < lines.length) {
|
|
189
|
+
const next = lines[index] ?? "";
|
|
190
|
+
const item = readItem(next);
|
|
191
|
+
if (item !== null) {
|
|
192
|
+
texts.push(item.text);
|
|
193
|
+
index += 1;
|
|
194
|
+
continue;
|
|
195
|
+
}
|
|
196
|
+
if (startsBlock(next)) {
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
texts[texts.length - 1] = `${texts.at(-1) ?? ""} ${next.trim()}`;
|
|
200
|
+
index += 1;
|
|
201
|
+
}
|
|
202
|
+
blocks.push({
|
|
203
|
+
kind: "list",
|
|
204
|
+
ordered: first.ordered,
|
|
205
|
+
items: texts.map((text) => parseAnswerInline(text))
|
|
206
|
+
});
|
|
207
|
+
continue;
|
|
208
|
+
}
|
|
209
|
+
const paragraph = [line.trim()];
|
|
210
|
+
index += 1;
|
|
211
|
+
while (index < lines.length && !startsBlock(lines[index] ?? "")) {
|
|
212
|
+
paragraph.push((lines[index] ?? "").trim());
|
|
213
|
+
index += 1;
|
|
214
|
+
}
|
|
215
|
+
blocks.push({ kind: "paragraph", children: parseAnswerInline(paragraph.join(" ")) });
|
|
216
|
+
}
|
|
217
|
+
return blocks;
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
// src/ui/ask-ai/stream.ts
|
|
221
|
+
function takeLine(buffer) {
|
|
222
|
+
const match = /\r\n|\n|\r/.exec(buffer);
|
|
223
|
+
if (match === null) {
|
|
224
|
+
return null;
|
|
225
|
+
}
|
|
226
|
+
if (match[0] === "\r" && match.index + 1 === buffer.length) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
line: buffer.slice(0, match.index),
|
|
231
|
+
rest: buffer.slice(match.index + match[0].length)
|
|
232
|
+
};
|
|
233
|
+
}
|
|
234
|
+
function createAnswerFrameSplitter() {
|
|
235
|
+
let buffer = "";
|
|
236
|
+
let event;
|
|
237
|
+
let data = [];
|
|
238
|
+
function addLine(line) {
|
|
239
|
+
if (line.startsWith(":")) {
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
const colon = line.indexOf(":");
|
|
243
|
+
const field = colon === -1 ? line : line.slice(0, colon);
|
|
244
|
+
const raw = colon === -1 ? "" : line.slice(colon + 1);
|
|
245
|
+
const value = raw.startsWith(" ") ? raw.slice(1) : raw;
|
|
246
|
+
if (field === "event") {
|
|
247
|
+
event = value;
|
|
248
|
+
} else if (field === "data") {
|
|
249
|
+
data.push(value);
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
function take() {
|
|
253
|
+
if (event === void 0 && data.length === 0) {
|
|
254
|
+
return null;
|
|
255
|
+
}
|
|
256
|
+
const frame = { event, data: data.join("\n") };
|
|
257
|
+
event = void 0;
|
|
258
|
+
data = [];
|
|
259
|
+
return frame;
|
|
260
|
+
}
|
|
261
|
+
return {
|
|
262
|
+
push(chunk) {
|
|
263
|
+
buffer += chunk;
|
|
264
|
+
const frames = [];
|
|
265
|
+
for (; ; ) {
|
|
266
|
+
const taken = takeLine(buffer);
|
|
267
|
+
if (taken === null) {
|
|
268
|
+
break;
|
|
269
|
+
}
|
|
270
|
+
buffer = taken.rest;
|
|
271
|
+
if (taken.line === "") {
|
|
272
|
+
const frame = take();
|
|
273
|
+
if (frame !== null) {
|
|
274
|
+
frames.push(frame);
|
|
275
|
+
}
|
|
276
|
+
} else {
|
|
277
|
+
addLine(taken.line);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
return frames;
|
|
281
|
+
},
|
|
282
|
+
flush() {
|
|
283
|
+
if (buffer !== "") {
|
|
284
|
+
addLine(buffer);
|
|
285
|
+
buffer = "";
|
|
286
|
+
}
|
|
287
|
+
const frame = take();
|
|
288
|
+
return frame === null ? [] : [frame];
|
|
289
|
+
}
|
|
290
|
+
};
|
|
291
|
+
}
|
|
292
|
+
function parsePayload(data) {
|
|
293
|
+
if (data === "") {
|
|
294
|
+
return void 0;
|
|
295
|
+
}
|
|
296
|
+
try {
|
|
297
|
+
return JSON.parse(data);
|
|
298
|
+
} catch {
|
|
299
|
+
return void 0;
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
function readSources(payload) {
|
|
303
|
+
if (!Array.isArray(payload)) {
|
|
304
|
+
return [];
|
|
305
|
+
}
|
|
306
|
+
const sources = [];
|
|
307
|
+
for (const entry of payload) {
|
|
308
|
+
if (typeof entry !== "object" || entry === null) {
|
|
309
|
+
continue;
|
|
310
|
+
}
|
|
311
|
+
const record = entry;
|
|
312
|
+
const url = record["url"];
|
|
313
|
+
const title = record["title"];
|
|
314
|
+
if (typeof url === "string" && url !== "" && typeof title === "string" && title !== "") {
|
|
315
|
+
sources.push({ url, title });
|
|
316
|
+
}
|
|
317
|
+
}
|
|
318
|
+
return sources;
|
|
319
|
+
}
|
|
320
|
+
function readString(payload, field) {
|
|
321
|
+
if (typeof payload !== "object" || payload === null) {
|
|
322
|
+
return void 0;
|
|
323
|
+
}
|
|
324
|
+
const value = payload[field];
|
|
325
|
+
return typeof value === "string" ? value : void 0;
|
|
326
|
+
}
|
|
327
|
+
function readAnswerEvent(frame) {
|
|
328
|
+
const payload = parsePayload(frame.data);
|
|
329
|
+
switch (frame.event) {
|
|
330
|
+
case "sources":
|
|
331
|
+
return { kind: "sources", sources: readSources(payload) };
|
|
332
|
+
case "text": {
|
|
333
|
+
const text = readString(payload, "text");
|
|
334
|
+
return text === void 0 || text === "" ? null : { kind: "text", text };
|
|
335
|
+
}
|
|
336
|
+
case "done":
|
|
337
|
+
return { kind: "done" };
|
|
338
|
+
case "error":
|
|
339
|
+
return {
|
|
340
|
+
kind: "error",
|
|
341
|
+
message: readString(payload, "message") ?? "The answer stopped unexpectedly."
|
|
342
|
+
};
|
|
343
|
+
default:
|
|
344
|
+
return null;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
var DEFAULT_SUGGESTIONS = [
|
|
348
|
+
"What is this documentation about?",
|
|
349
|
+
"How do I get started?",
|
|
350
|
+
"Show me an example request."
|
|
351
|
+
];
|
|
352
|
+
var MAX_COMPOSER_HEIGHT = 132;
|
|
353
|
+
function sparkleIcon() {
|
|
354
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.7", "aria-hidden": "true", children: /* @__PURE__ */ jsx(
|
|
355
|
+
"path",
|
|
356
|
+
{
|
|
357
|
+
d: "M12 3.5l1.6 4.4 4.4 1.6-4.4 1.6L12 15.5l-1.6-4.4L6 9.5l4.4-1.6L12 3.5ZM18.5 15l.8 2.2 2.2.8-2.2.8-.8 2.2-.8-2.2-2.2-.8 2.2-.8.8-2.2Z",
|
|
358
|
+
strokeLinecap: "round",
|
|
359
|
+
strokeLinejoin: "round"
|
|
360
|
+
}
|
|
361
|
+
) });
|
|
362
|
+
}
|
|
363
|
+
function closeIcon() {
|
|
364
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M6 6l12 12M18 6 6 18", strokeLinecap: "round" }) });
|
|
365
|
+
}
|
|
366
|
+
function newChatIcon() {
|
|
367
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.9", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M20 11.5A8 8 0 1 1 17.6 6M20 4v4h-4", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
368
|
+
}
|
|
369
|
+
function sendIcon() {
|
|
370
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "2", "aria-hidden": "true", children: /* @__PURE__ */ jsx("path", { d: "M12 19V5M5 12l7-7 7 7", strokeLinecap: "round", strokeLinejoin: "round" }) });
|
|
371
|
+
}
|
|
372
|
+
function stopIcon() {
|
|
373
|
+
return /* @__PURE__ */ jsx("svg", { viewBox: "0 0 24 24", fill: "currentColor", "aria-hidden": "true", children: /* @__PURE__ */ jsx("rect", { x: "7", y: "7", width: "10", height: "10", rx: "2" }) });
|
|
374
|
+
}
|
|
375
|
+
function InlineRuns({ nodes }) {
|
|
376
|
+
return /* @__PURE__ */ jsx(Fragment, { children: nodes.map((node, index) => {
|
|
377
|
+
const key = `${String(index)}-${node.kind}`;
|
|
378
|
+
if (node.kind === "code") {
|
|
379
|
+
return /* @__PURE__ */ jsx("code", { className: "askai-inline-code", children: node.text }, key);
|
|
380
|
+
}
|
|
381
|
+
if (node.kind === "strong") {
|
|
382
|
+
return /* @__PURE__ */ jsx("strong", { className: "askai-strong", children: /* @__PURE__ */ jsx(InlineRuns, { nodes: node.children }) }, key);
|
|
383
|
+
}
|
|
384
|
+
if (node.kind === "link") {
|
|
385
|
+
return node.external ? /* @__PURE__ */ jsx("a", { className: "askai-link", href: node.href, target: "_blank", rel: "noreferrer", children: node.text }, key) : /* @__PURE__ */ jsx("a", { className: "askai-link", href: node.href, children: node.text }, key);
|
|
386
|
+
}
|
|
387
|
+
return /* @__PURE__ */ jsx("span", { children: node.text }, key);
|
|
388
|
+
}) });
|
|
389
|
+
}
|
|
390
|
+
function caret() {
|
|
391
|
+
return /* @__PURE__ */ jsx("span", { className: "askai-caret", "aria-hidden": "true" });
|
|
392
|
+
}
|
|
393
|
+
function AnswerBlockView({
|
|
394
|
+
block,
|
|
395
|
+
withCaret
|
|
396
|
+
}) {
|
|
397
|
+
if (block.kind === "code") {
|
|
398
|
+
return /* @__PURE__ */ jsx("pre", { className: "askai-codeblock", "data-language": block.language, children: /* @__PURE__ */ jsx("code", { children: block.text }) });
|
|
399
|
+
}
|
|
400
|
+
if (block.kind === "heading") {
|
|
401
|
+
return /* @__PURE__ */ jsxs("p", { className: "askai-answer-heading", children: [
|
|
402
|
+
/* @__PURE__ */ jsx(InlineRuns, { nodes: block.children }),
|
|
403
|
+
withCaret ? caret() : null
|
|
404
|
+
] });
|
|
405
|
+
}
|
|
406
|
+
if (block.kind === "list") {
|
|
407
|
+
const items = block.items.map((item, index) => /* @__PURE__ */ jsxs("li", { children: [
|
|
408
|
+
/* @__PURE__ */ jsx(InlineRuns, { nodes: item }),
|
|
409
|
+
withCaret && index === block.items.length - 1 ? caret() : null
|
|
410
|
+
] }, String(index)));
|
|
411
|
+
return block.ordered ? /* @__PURE__ */ jsx("ol", { className: "askai-list", children: items }) : /* @__PURE__ */ jsx("ul", { className: "askai-list", children: items });
|
|
412
|
+
}
|
|
413
|
+
return /* @__PURE__ */ jsxs("p", { className: "askai-paragraph", children: [
|
|
414
|
+
/* @__PURE__ */ jsx(InlineRuns, { nodes: block.children }),
|
|
415
|
+
withCaret ? caret() : null
|
|
416
|
+
] });
|
|
417
|
+
}
|
|
418
|
+
function AnswerBody({ text, streaming }) {
|
|
419
|
+
const blocks = parseAnswerMarkdown(text);
|
|
420
|
+
if (blocks.length === 0) {
|
|
421
|
+
return streaming ? /* @__PURE__ */ jsx("p", { className: "askai-paragraph", children: caret() }) : null;
|
|
422
|
+
}
|
|
423
|
+
const last = blocks.length - 1;
|
|
424
|
+
const tailTakesCaret = streaming && blocks[last]?.kind !== "code";
|
|
425
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
426
|
+
blocks.map((block, index) => /* @__PURE__ */ jsx(
|
|
427
|
+
AnswerBlockView,
|
|
428
|
+
{
|
|
429
|
+
block,
|
|
430
|
+
withCaret: tailTakesCaret && index === last
|
|
431
|
+
},
|
|
432
|
+
`${String(index)}-${block.kind}`
|
|
433
|
+
)),
|
|
434
|
+
streaming && !tailTakesCaret ? /* @__PURE__ */ jsx("p", { className: "askai-paragraph", children: caret() }) : null
|
|
435
|
+
] });
|
|
436
|
+
}
|
|
437
|
+
async function readRouteError(response) {
|
|
438
|
+
try {
|
|
439
|
+
const payload = await response.json();
|
|
440
|
+
if (typeof payload === "object" && payload !== null) {
|
|
441
|
+
const message = payload["error"];
|
|
442
|
+
if (typeof message === "string" && message.trim() !== "") {
|
|
443
|
+
return message;
|
|
444
|
+
}
|
|
445
|
+
}
|
|
446
|
+
} catch {
|
|
447
|
+
}
|
|
448
|
+
return `Ask AI could not answer this question (HTTP ${String(response.status)}).`;
|
|
449
|
+
}
|
|
450
|
+
function AskAi({
|
|
451
|
+
endpoint = "/api/ai",
|
|
452
|
+
suggestions = DEFAULT_SUGGESTIONS,
|
|
453
|
+
label = "Ask AI",
|
|
454
|
+
placeholder = "Ask a question\u2026"
|
|
455
|
+
}) {
|
|
456
|
+
const [mounted, setMounted] = useState(false);
|
|
457
|
+
const [open, setOpen] = useState(false);
|
|
458
|
+
const [thread, setThread] = useState([]);
|
|
459
|
+
const [draft, setDraft] = useState("");
|
|
460
|
+
const [busy, setBusy] = useState(false);
|
|
461
|
+
const [shortcutHint, setShortcutHint] = useState("\u2318I");
|
|
462
|
+
const abortRef = useRef(null);
|
|
463
|
+
const threadRef = useRef([]);
|
|
464
|
+
const openRef = useRef(false);
|
|
465
|
+
const scrollRef = useRef(null);
|
|
466
|
+
const composerRef = useRef(null);
|
|
467
|
+
const askCount = useRef(0);
|
|
468
|
+
useEffect(() => {
|
|
469
|
+
setMounted(true);
|
|
470
|
+
const mac = /mac|iphone|ipad/i.test(navigator.userAgent);
|
|
471
|
+
setShortcutHint(mac ? "\u2318I" : "Ctrl I");
|
|
472
|
+
}, []);
|
|
473
|
+
useEffect(() => {
|
|
474
|
+
threadRef.current = thread;
|
|
475
|
+
}, [thread]);
|
|
476
|
+
useEffect(() => {
|
|
477
|
+
openRef.current = open;
|
|
478
|
+
}, [open]);
|
|
479
|
+
const stop = useCallback(() => {
|
|
480
|
+
abortRef.current?.abort();
|
|
481
|
+
}, []);
|
|
482
|
+
const close = useCallback(() => {
|
|
483
|
+
abortRef.current?.abort();
|
|
484
|
+
setOpen(false);
|
|
485
|
+
}, []);
|
|
486
|
+
const ask = useCallback(
|
|
487
|
+
(question, history) => {
|
|
488
|
+
const text = question.trim().slice(0, MAX_MESSAGE_CHARS);
|
|
489
|
+
if (text === "" || abortRef.current !== null) {
|
|
490
|
+
return;
|
|
491
|
+
}
|
|
492
|
+
askCount.current += 1;
|
|
493
|
+
const turn = String(askCount.current);
|
|
494
|
+
const userTurn = { id: `q${turn}`, role: "user", text };
|
|
495
|
+
const answerId = `a${turn}`;
|
|
496
|
+
const answer = {
|
|
497
|
+
id: answerId,
|
|
498
|
+
role: "assistant",
|
|
499
|
+
text: "",
|
|
500
|
+
sources: [],
|
|
501
|
+
state: "streaming",
|
|
502
|
+
error: void 0
|
|
503
|
+
};
|
|
504
|
+
const messages = toRequestMessages([...history, userTurn]);
|
|
505
|
+
const next = [...history, userTurn, answer];
|
|
506
|
+
threadRef.current = next;
|
|
507
|
+
setThread(next);
|
|
508
|
+
setBusy(true);
|
|
509
|
+
const controller = new AbortController();
|
|
510
|
+
abortRef.current = controller;
|
|
511
|
+
const update = (change) => {
|
|
512
|
+
setThread(
|
|
513
|
+
(current) => current.map(
|
|
514
|
+
(entry) => entry.id === answerId && entry.role === "assistant" ? change(entry) : entry
|
|
515
|
+
)
|
|
516
|
+
);
|
|
517
|
+
};
|
|
518
|
+
const run = async () => {
|
|
519
|
+
let answered = "";
|
|
520
|
+
let terminal = false;
|
|
521
|
+
try {
|
|
522
|
+
const response = await fetch(endpoint, {
|
|
523
|
+
method: "POST",
|
|
524
|
+
headers: { "content-type": "application/json" },
|
|
525
|
+
body: JSON.stringify({ messages }),
|
|
526
|
+
signal: controller.signal
|
|
527
|
+
});
|
|
528
|
+
if (!response.ok) {
|
|
529
|
+
const message = await readRouteError(response);
|
|
530
|
+
update((previous) => ({ ...previous, state: "error", error: message }));
|
|
531
|
+
return;
|
|
532
|
+
}
|
|
533
|
+
if (response.body === null) {
|
|
534
|
+
update((previous) => ({
|
|
535
|
+
...previous,
|
|
536
|
+
state: "error",
|
|
537
|
+
error: "Ask AI sent an empty answer."
|
|
538
|
+
}));
|
|
539
|
+
return;
|
|
540
|
+
}
|
|
541
|
+
const reader = response.body.getReader();
|
|
542
|
+
const decoder = new TextDecoder();
|
|
543
|
+
const splitter = createAnswerFrameSplitter();
|
|
544
|
+
for (; ; ) {
|
|
545
|
+
const result = await reader.read();
|
|
546
|
+
const frames = result.done ? splitter.flush() : splitter.push(decoder.decode(result.value, { stream: true }));
|
|
547
|
+
for (const frame of frames) {
|
|
548
|
+
const event = readAnswerEvent(frame);
|
|
549
|
+
if (event === null) {
|
|
550
|
+
continue;
|
|
551
|
+
}
|
|
552
|
+
if (event.kind === "text") {
|
|
553
|
+
answered += event.text;
|
|
554
|
+
const snapshot = answered;
|
|
555
|
+
update((previous) => ({ ...previous, text: snapshot }));
|
|
556
|
+
continue;
|
|
557
|
+
}
|
|
558
|
+
if (event.kind === "sources") {
|
|
559
|
+
const { sources } = event;
|
|
560
|
+
update((previous) => ({ ...previous, sources }));
|
|
561
|
+
continue;
|
|
562
|
+
}
|
|
563
|
+
if (event.kind === "error") {
|
|
564
|
+
const { message } = event;
|
|
565
|
+
update((previous) => ({ ...previous, state: "error", error: message }));
|
|
566
|
+
} else {
|
|
567
|
+
update((previous) => ({ ...previous, state: "done" }));
|
|
568
|
+
}
|
|
569
|
+
terminal = true;
|
|
570
|
+
}
|
|
571
|
+
if (terminal || result.done) {
|
|
572
|
+
break;
|
|
573
|
+
}
|
|
574
|
+
}
|
|
575
|
+
if (!terminal) {
|
|
576
|
+
update(
|
|
577
|
+
(previous) => previous.text === "" ? { ...previous, state: "error", error: "The answer stopped before it started." } : { ...previous, state: "done" }
|
|
578
|
+
);
|
|
579
|
+
}
|
|
580
|
+
} catch {
|
|
581
|
+
if (controller.signal.aborted) {
|
|
582
|
+
update(
|
|
583
|
+
(previous) => previous.text === "" ? { ...previous, state: "error", error: "The question was stopped." } : { ...previous, state: "done" }
|
|
584
|
+
);
|
|
585
|
+
} else {
|
|
586
|
+
update((previous) => ({
|
|
587
|
+
...previous,
|
|
588
|
+
state: "error",
|
|
589
|
+
error: "Ask AI could not be reached. Check your connection, then try again."
|
|
590
|
+
}));
|
|
591
|
+
}
|
|
592
|
+
} finally {
|
|
593
|
+
if (abortRef.current === controller) {
|
|
594
|
+
abortRef.current = null;
|
|
595
|
+
setBusy(false);
|
|
596
|
+
}
|
|
597
|
+
}
|
|
598
|
+
};
|
|
599
|
+
void run();
|
|
600
|
+
},
|
|
601
|
+
[endpoint]
|
|
602
|
+
);
|
|
603
|
+
const retry = useCallback(
|
|
604
|
+
(answerId) => {
|
|
605
|
+
const current = threadRef.current;
|
|
606
|
+
const index = current.findIndex((entry) => entry.id === answerId);
|
|
607
|
+
const question = index > 0 ? current[index - 1] : void 0;
|
|
608
|
+
if (question?.role !== "user") {
|
|
609
|
+
return;
|
|
610
|
+
}
|
|
611
|
+
ask(question.text, current.slice(0, index - 1));
|
|
612
|
+
},
|
|
613
|
+
[ask]
|
|
614
|
+
);
|
|
615
|
+
const newChat = useCallback(() => {
|
|
616
|
+
abortRef.current?.abort();
|
|
617
|
+
threadRef.current = [];
|
|
618
|
+
setThread([]);
|
|
619
|
+
setDraft("");
|
|
620
|
+
}, []);
|
|
621
|
+
const submit = useCallback(() => {
|
|
622
|
+
const text = draft.trim();
|
|
623
|
+
if (text === "" || abortRef.current !== null) {
|
|
624
|
+
return;
|
|
625
|
+
}
|
|
626
|
+
setDraft("");
|
|
627
|
+
ask(text, threadRef.current);
|
|
628
|
+
}, [ask, draft]);
|
|
629
|
+
useEffect(() => {
|
|
630
|
+
const onKey = (event) => {
|
|
631
|
+
if (!(event.metaKey || event.ctrlKey) || event.altKey || event.key.toLowerCase() !== "i") {
|
|
632
|
+
return;
|
|
633
|
+
}
|
|
634
|
+
event.preventDefault();
|
|
635
|
+
if (openRef.current) {
|
|
636
|
+
close();
|
|
637
|
+
} else {
|
|
638
|
+
setOpen(true);
|
|
639
|
+
}
|
|
640
|
+
};
|
|
641
|
+
document.addEventListener("keydown", onKey);
|
|
642
|
+
return () => {
|
|
643
|
+
document.removeEventListener("keydown", onKey);
|
|
644
|
+
};
|
|
645
|
+
}, [close]);
|
|
646
|
+
useEffect(() => {
|
|
647
|
+
if (!open) {
|
|
648
|
+
return;
|
|
649
|
+
}
|
|
650
|
+
const onKey = (event) => {
|
|
651
|
+
if (event.key !== "Escape") {
|
|
652
|
+
return;
|
|
653
|
+
}
|
|
654
|
+
event.stopPropagation();
|
|
655
|
+
if (abortRef.current !== null) {
|
|
656
|
+
stop();
|
|
657
|
+
return;
|
|
658
|
+
}
|
|
659
|
+
setOpen(false);
|
|
660
|
+
};
|
|
661
|
+
document.addEventListener("keydown", onKey, true);
|
|
662
|
+
document.body.style.overflow = "hidden";
|
|
663
|
+
return () => {
|
|
664
|
+
document.removeEventListener("keydown", onKey, true);
|
|
665
|
+
document.body.style.overflow = "";
|
|
666
|
+
};
|
|
667
|
+
}, [open, stop]);
|
|
668
|
+
useEffect(() => {
|
|
669
|
+
if (open) {
|
|
670
|
+
composerRef.current?.focus();
|
|
671
|
+
}
|
|
672
|
+
}, [open]);
|
|
673
|
+
useEffect(() => {
|
|
674
|
+
const el = scrollRef.current;
|
|
675
|
+
if (el) {
|
|
676
|
+
el.scrollTop = el.scrollHeight;
|
|
677
|
+
}
|
|
678
|
+
}, [thread]);
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
const el = composerRef.current;
|
|
681
|
+
if (!el) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
el.style.height = "auto";
|
|
685
|
+
el.style.height = `${String(Math.min(el.scrollHeight, MAX_COMPOSER_HEIGHT))}px`;
|
|
686
|
+
}, [draft, open]);
|
|
687
|
+
const onComposerKeyDown = (event) => {
|
|
688
|
+
if (event.key === "Enter" && !event.shiftKey) {
|
|
689
|
+
event.preventDefault();
|
|
690
|
+
submit();
|
|
691
|
+
}
|
|
692
|
+
};
|
|
693
|
+
if (!mounted) {
|
|
694
|
+
return null;
|
|
695
|
+
}
|
|
696
|
+
const trigger = /* @__PURE__ */ jsxs(
|
|
697
|
+
"button",
|
|
698
|
+
{
|
|
699
|
+
type: "button",
|
|
700
|
+
className: "askai-trigger",
|
|
701
|
+
"aria-label": `${label} (${shortcutHint})`,
|
|
702
|
+
onClick: () => {
|
|
703
|
+
setOpen(true);
|
|
704
|
+
},
|
|
705
|
+
children: [
|
|
706
|
+
/* @__PURE__ */ jsx("span", { className: "askai-trigger-icon", children: sparkleIcon() }),
|
|
707
|
+
/* @__PURE__ */ jsx("span", { className: "askai-trigger-label", children: label }),
|
|
708
|
+
/* @__PURE__ */ jsx("kbd", { className: "askai-kbd", children: shortcutHint })
|
|
709
|
+
]
|
|
710
|
+
}
|
|
711
|
+
);
|
|
712
|
+
const panel = /* @__PURE__ */ jsxs("aside", { className: "askai-panel", role: "dialog", "aria-modal": "false", "aria-label": label, children: [
|
|
713
|
+
/* @__PURE__ */ jsxs("header", { className: "askai-head", children: [
|
|
714
|
+
/* @__PURE__ */ jsx("span", { className: "askai-head-icon", children: sparkleIcon() }),
|
|
715
|
+
/* @__PURE__ */ jsx("h2", { className: "askai-head-title", children: label }),
|
|
716
|
+
/* @__PURE__ */ jsx("span", { className: "askai-head-hint", children: "Esc" }),
|
|
717
|
+
/* @__PURE__ */ jsx(
|
|
718
|
+
"button",
|
|
719
|
+
{
|
|
720
|
+
type: "button",
|
|
721
|
+
className: "askai-head-btn",
|
|
722
|
+
"aria-label": "New chat",
|
|
723
|
+
title: "New chat",
|
|
724
|
+
disabled: thread.length === 0,
|
|
725
|
+
onClick: newChat,
|
|
726
|
+
children: newChatIcon()
|
|
727
|
+
}
|
|
728
|
+
),
|
|
729
|
+
/* @__PURE__ */ jsx("button", { type: "button", className: "askai-head-btn", "aria-label": "Close", onClick: close, children: closeIcon() })
|
|
730
|
+
] }),
|
|
731
|
+
/* @__PURE__ */ jsx("div", { className: "askai-thread", ref: scrollRef, children: thread.length === 0 ? /* @__PURE__ */ jsxs("div", { className: "askai-empty", children: [
|
|
732
|
+
/* @__PURE__ */ jsx("p", { className: "askai-empty-line", children: "Ask a question about this documentation. Answers cite the pages they come from." }),
|
|
733
|
+
/* @__PURE__ */ jsx("div", { className: "askai-chips", children: suggestions.map((suggestion) => /* @__PURE__ */ jsx(
|
|
734
|
+
"button",
|
|
735
|
+
{
|
|
736
|
+
type: "button",
|
|
737
|
+
className: "askai-chip",
|
|
738
|
+
onClick: () => {
|
|
739
|
+
ask(suggestion, threadRef.current);
|
|
740
|
+
},
|
|
741
|
+
children: suggestion
|
|
742
|
+
},
|
|
743
|
+
suggestion
|
|
744
|
+
)) })
|
|
745
|
+
] }) : thread.map(
|
|
746
|
+
(entry) => entry.role === "user" ? /* @__PURE__ */ jsx("div", { className: "askai-question", children: /* @__PURE__ */ jsx("div", { className: "askai-bubble", children: entry.text }) }, entry.id) : /* @__PURE__ */ jsxs("div", { className: "askai-answer", "data-state": entry.state, children: [
|
|
747
|
+
/* @__PURE__ */ jsx(AnswerBody, { text: entry.text, streaming: entry.state === "streaming" }),
|
|
748
|
+
entry.sources.length > 0 && entry.state !== "error" ? /* @__PURE__ */ jsxs("div", { className: "askai-sources", children: [
|
|
749
|
+
/* @__PURE__ */ jsx("span", { className: "askai-sources-label", children: "Sources" }),
|
|
750
|
+
/* @__PURE__ */ jsx("div", { className: "askai-sources-chips", children: entry.sources.map((source) => /* @__PURE__ */ jsx("a", { className: "askai-source", href: source.url, children: source.title }, source.url)) })
|
|
751
|
+
] }) : null,
|
|
752
|
+
entry.error === void 0 ? null : /* @__PURE__ */ jsxs("div", { className: "askai-error", role: "alert", children: [
|
|
753
|
+
/* @__PURE__ */ jsx("span", { className: "askai-error-text", children: entry.error }),
|
|
754
|
+
/* @__PURE__ */ jsx(
|
|
755
|
+
"button",
|
|
756
|
+
{
|
|
757
|
+
type: "button",
|
|
758
|
+
className: "askai-retry",
|
|
759
|
+
disabled: busy,
|
|
760
|
+
onClick: () => {
|
|
761
|
+
retry(entry.id);
|
|
762
|
+
},
|
|
763
|
+
children: "Try again"
|
|
764
|
+
}
|
|
765
|
+
)
|
|
766
|
+
] })
|
|
767
|
+
] }, entry.id)
|
|
768
|
+
) }),
|
|
769
|
+
/* @__PURE__ */ jsxs("div", { className: "askai-composer", children: [
|
|
770
|
+
/* @__PURE__ */ jsx(
|
|
771
|
+
"textarea",
|
|
772
|
+
{
|
|
773
|
+
ref: composerRef,
|
|
774
|
+
className: "askai-input",
|
|
775
|
+
rows: 1,
|
|
776
|
+
value: draft,
|
|
777
|
+
placeholder,
|
|
778
|
+
"aria-label": placeholder,
|
|
779
|
+
maxLength: MAX_MESSAGE_CHARS,
|
|
780
|
+
disabled: busy,
|
|
781
|
+
onChange: (event) => {
|
|
782
|
+
setDraft(event.target.value);
|
|
783
|
+
},
|
|
784
|
+
onKeyDown: onComposerKeyDown
|
|
785
|
+
}
|
|
786
|
+
),
|
|
787
|
+
busy ? /* @__PURE__ */ jsx("button", { type: "button", className: "askai-send", "aria-label": "Stop", onClick: stop, children: stopIcon() }) : /* @__PURE__ */ jsx(
|
|
788
|
+
"button",
|
|
789
|
+
{
|
|
790
|
+
type: "button",
|
|
791
|
+
className: "askai-send",
|
|
792
|
+
"aria-label": "Send",
|
|
793
|
+
disabled: draft.trim() === "",
|
|
794
|
+
onClick: submit,
|
|
795
|
+
children: sendIcon()
|
|
796
|
+
}
|
|
797
|
+
)
|
|
798
|
+
] })
|
|
799
|
+
] });
|
|
800
|
+
return createPortal(
|
|
801
|
+
/* @__PURE__ */ jsx("div", { className: "askai-root", "data-open": open ? "" : void 0, children: open ? panel : trigger }),
|
|
802
|
+
document.body
|
|
803
|
+
);
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
export { AskAi };
|
|
807
|
+
//# sourceMappingURL=ask-ai.js.map
|
|
808
|
+
//# sourceMappingURL=ask-ai.js.map
|