radiant-docs 0.1.39 → 0.1.41
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/package.json +1 -1
- package/template/astro.config.mjs +38 -7
- package/template/package-lock.json +19 -7
- package/template/package.json +3 -3
- package/template/public/favicon.svg +16 -8
- package/template/scripts/generate-robots-txt.mjs +29 -1
- package/template/scripts/remove-assistant-for-non-pro.mjs +28 -0
- package/template/scripts/stamp-image-versions.mjs +59 -33
- package/template/src/components/Footer.astro +2 -1
- package/template/src/components/Header.astro +10 -8
- package/template/src/components/LogoLink.astro +2 -1
- package/template/src/components/MdxPage.astro +15 -4
- package/template/src/components/PagePagination.astro +61 -0
- package/template/src/components/SidebarDropdown.astro +12 -8
- package/template/src/components/SidebarGroup.astro +1 -1
- package/template/src/components/SidebarMenu.astro +1 -1
- package/template/src/components/SidebarSegmented.astro +6 -5
- package/template/src/components/TableOfContents.astro +4 -13
- package/template/src/components/chat/AskAiWidget.tsx +274 -39
- package/template/src/components/chat/AssistantDocsWidget.astro +16 -0
- package/template/src/components/chat/AssistantDocsWidget.tsx +402 -0
- package/template/src/components/chat/AssistantEmbedPanel.tsx +1693 -0
- package/template/src/components/chat/AssistantEmbedPanelPage.astro +95 -0
- package/template/src/components/endpoint/PlaygroundForm.astro +2 -1
- package/template/src/components/user/Callout.astro +10 -4
- package/template/src/components/user/CodeBlock.astro +1 -1
- package/template/src/components/user/CodeGroup.astro +16 -1
- package/template/src/components/user/ComponentPreviewBlock.astro +1 -0
- package/template/src/components/user/Image.astro +43 -53
- package/template/src/layouts/Layout.astro +104 -35
- package/template/src/lib/assistant-chrome-defaults.ts +74 -0
- package/template/src/lib/assistant-chrome.ts +39 -0
- package/template/src/lib/assistant-embed-script.ts +897 -0
- package/template/src/lib/assistant-panel-config.ts +80 -0
- package/template/src/lib/base-path.ts +98 -0
- package/template/src/lib/component-error.ts +49 -10
- package/template/src/lib/favicon.ts +31 -0
- package/template/src/lib/mdx/remark-resolve-internal-links.ts +128 -18
- package/template/src/lib/pagefind.ts +62 -14
- package/template/src/lib/routes.ts +49 -1
- package/template/src/lib/static-asset-url.ts +3 -1
- package/template/src/lib/theme-css.ts +176 -0
- package/template/src/lib/utils.ts +12 -4
- package/template/src/lib/validation.ts +754 -37
- package/template/src/pages/-/assistant/embed.js.ts +15 -0
- package/template/src/pages/-/assistant/panel.astro +5 -0
- package/template/src/pages/404.astro +6 -5
- package/template/src/pages/[...slug].astro +68 -6
- package/template/src/styles/global.css +62 -1
|
@@ -0,0 +1,1693 @@
|
|
|
1
|
+
import type { JSX } from "preact";
|
|
2
|
+
import { useEffect, useRef, useState } from "preact/hooks";
|
|
3
|
+
import { Icon } from "@iconify/react";
|
|
4
|
+
import Prism from "prismjs";
|
|
5
|
+
import "prismjs/components/prism-markup.js";
|
|
6
|
+
import "prismjs/components/prism-clike.js";
|
|
7
|
+
import "prismjs/components/prism-javascript.js";
|
|
8
|
+
import "prismjs/components/prism-typescript.js";
|
|
9
|
+
import "prismjs/components/prism-jsx.js";
|
|
10
|
+
import "prismjs/components/prism-tsx.js";
|
|
11
|
+
import "prismjs/components/prism-json.js";
|
|
12
|
+
import "prismjs/components/prism-markdown.js";
|
|
13
|
+
import "prismjs/components/prism-bash.js";
|
|
14
|
+
import "prismjs/components/prism-python.js";
|
|
15
|
+
import "prismjs/components/prism-yaml.js";
|
|
16
|
+
import "prismjs/components/prism-sql.js";
|
|
17
|
+
import "prismjs/components/prism-rust.js";
|
|
18
|
+
import "prismjs/components/prism-go.js";
|
|
19
|
+
import "prismjs/components/prism-java.js";
|
|
20
|
+
import "prismjs/components/prism-markup-templating.js";
|
|
21
|
+
import "prismjs/components/prism-php.js";
|
|
22
|
+
import "prismjs/components/prism-ruby.js";
|
|
23
|
+
import "prismjs/components/prism-css.js";
|
|
24
|
+
import "prismjs/components/prism-diff.js";
|
|
25
|
+
import "prism-themes/themes/prism-one-light.css";
|
|
26
|
+
import { type Plugin, unified } from "unified";
|
|
27
|
+
import remarkParse from "remark-parse";
|
|
28
|
+
import remarkGfm from "remark-gfm";
|
|
29
|
+
import remarkRehype from "remark-rehype";
|
|
30
|
+
import rehypeStringify from "rehype-stringify";
|
|
31
|
+
|
|
32
|
+
type AssistantLinkTarget = "current" | "blank";
|
|
33
|
+
|
|
34
|
+
type HastNode = {
|
|
35
|
+
type?: string;
|
|
36
|
+
tagName?: string;
|
|
37
|
+
properties?: Record<string, unknown>;
|
|
38
|
+
children?: HastNode[];
|
|
39
|
+
};
|
|
40
|
+
|
|
41
|
+
type AssistantEmbedPanelProps = {
|
|
42
|
+
apiPath: string;
|
|
43
|
+
docsTitle: string;
|
|
44
|
+
isChatAvailable: boolean;
|
|
45
|
+
canSendChatRequest: boolean;
|
|
46
|
+
launcherThemeColor?: string;
|
|
47
|
+
launcherThemeColors?: AssistantColorByMode;
|
|
48
|
+
launcherIconColor?: string;
|
|
49
|
+
launcherIconColors?: AssistantColorByMode;
|
|
50
|
+
launcherIconImageSrc?: string;
|
|
51
|
+
emptyStateHeading?: string;
|
|
52
|
+
emptyStateQuestions?: string[];
|
|
53
|
+
unavailableMessage?: string;
|
|
54
|
+
devProxyToken?: string;
|
|
55
|
+
panelSurface?: "iframe" | "inline";
|
|
56
|
+
linkTarget?: AssistantLinkTarget;
|
|
57
|
+
allowApiPathQueryOverride?: boolean;
|
|
58
|
+
openSignal?: number;
|
|
59
|
+
onRequestClose?: () => void;
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
type AssistantColorByMode = {
|
|
63
|
+
light: string;
|
|
64
|
+
dark: string;
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
type ChatMessage = {
|
|
68
|
+
id: string;
|
|
69
|
+
role: "user" | "assistant";
|
|
70
|
+
content: string;
|
|
71
|
+
};
|
|
72
|
+
|
|
73
|
+
type AssistantStreamEvent = {
|
|
74
|
+
type?: string;
|
|
75
|
+
delta?: string;
|
|
76
|
+
};
|
|
77
|
+
|
|
78
|
+
type ChatInputKeyEvent = JSX.TargetedKeyboardEvent<HTMLTextAreaElement>;
|
|
79
|
+
type ChatViewportWheelEvent = JSX.TargetedWheelEvent<HTMLDivElement>;
|
|
80
|
+
|
|
81
|
+
const STORAGE_KEY = "docs:assistant-embed-panel:v1";
|
|
82
|
+
const MARKDOWN_HTML_CACHE_LIMIT = 300;
|
|
83
|
+
const markdownHtmlCache = new Map<string, string>();
|
|
84
|
+
const PRISM_LANGUAGE_ALIAS: Record<string, string> = {
|
|
85
|
+
js: "javascript",
|
|
86
|
+
ts: "typescript",
|
|
87
|
+
yml: "yaml",
|
|
88
|
+
sh: "bash",
|
|
89
|
+
shell: "bash",
|
|
90
|
+
shellscript: "bash",
|
|
91
|
+
md: "markdown",
|
|
92
|
+
mdx: "markdown",
|
|
93
|
+
};
|
|
94
|
+
|
|
95
|
+
const PRISM_LANGUAGE_LABEL: Record<string, string> = {
|
|
96
|
+
javascript: "JavaScript",
|
|
97
|
+
typescript: "TypeScript",
|
|
98
|
+
jsx: "JSX",
|
|
99
|
+
tsx: "TSX",
|
|
100
|
+
json: "JSON",
|
|
101
|
+
markdown: "Markdown",
|
|
102
|
+
bash: "Bash",
|
|
103
|
+
shell: "Shell",
|
|
104
|
+
python: "Python",
|
|
105
|
+
yaml: "YAML",
|
|
106
|
+
sql: "SQL",
|
|
107
|
+
rust: "Rust",
|
|
108
|
+
go: "Go",
|
|
109
|
+
java: "Java",
|
|
110
|
+
css: "CSS",
|
|
111
|
+
html: "HTML",
|
|
112
|
+
diff: "Diff",
|
|
113
|
+
mdx: "MDX",
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
function normalizeRelTokens(value: unknown): string[] {
|
|
117
|
+
if (Array.isArray(value)) {
|
|
118
|
+
return value
|
|
119
|
+
.flatMap((entry) => (typeof entry === "string" ? entry.split(/\s+/) : []))
|
|
120
|
+
.filter(Boolean);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
if (typeof value === "string") {
|
|
124
|
+
return value.split(/\s+/).filter(Boolean);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
return [];
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
function visitHastNodes(
|
|
131
|
+
node: HastNode,
|
|
132
|
+
visitor: (node: HastNode) => void,
|
|
133
|
+
): void {
|
|
134
|
+
visitor(node);
|
|
135
|
+
if (!Array.isArray(node.children)) return;
|
|
136
|
+
for (const child of node.children) {
|
|
137
|
+
visitHastNodes(child, visitor);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const rehypeOpenLinksInNewTab: Plugin<[], HastNode> = () => {
|
|
142
|
+
return (tree) => {
|
|
143
|
+
visitHastNodes(tree, (node) => {
|
|
144
|
+
if (node.type !== "element" || node.tagName !== "a") return;
|
|
145
|
+
|
|
146
|
+
const props = (node.properties ??= {});
|
|
147
|
+
props.target = "_blank";
|
|
148
|
+
|
|
149
|
+
const relTokens = normalizeRelTokens(props.rel);
|
|
150
|
+
if (!relTokens.includes("noopener")) relTokens.push("noopener");
|
|
151
|
+
if (!relTokens.includes("noreferrer")) relTokens.push("noreferrer");
|
|
152
|
+
props.rel = relTokens.join(" ");
|
|
153
|
+
});
|
|
154
|
+
};
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
function clamp(value: number, min: number, max: number) {
|
|
158
|
+
return Math.min(Math.max(value, min), max);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function createMessageId(): string {
|
|
162
|
+
if (typeof crypto !== "undefined" && "randomUUID" in crypto) {
|
|
163
|
+
return crypto.randomUUID();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
return `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
function readPersistedMessages(): ChatMessage[] {
|
|
170
|
+
if (typeof window === "undefined") {
|
|
171
|
+
return [];
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
try {
|
|
175
|
+
const raw = window.localStorage.getItem(STORAGE_KEY);
|
|
176
|
+
if (!raw) {
|
|
177
|
+
return [];
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
const parsed = JSON.parse(raw) as { messages?: unknown };
|
|
181
|
+
if (!Array.isArray(parsed.messages)) {
|
|
182
|
+
return [];
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return parsed.messages
|
|
186
|
+
.filter((message): message is ChatMessage => {
|
|
187
|
+
if (!message || typeof message !== "object") {
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
const candidate = message as Partial<ChatMessage>;
|
|
191
|
+
return (
|
|
192
|
+
(candidate.role === "user" || candidate.role === "assistant") &&
|
|
193
|
+
typeof candidate.id === "string" &&
|
|
194
|
+
typeof candidate.content === "string"
|
|
195
|
+
);
|
|
196
|
+
})
|
|
197
|
+
.map((message) => ({
|
|
198
|
+
id: message.id,
|
|
199
|
+
role: message.role,
|
|
200
|
+
content: message.content,
|
|
201
|
+
}));
|
|
202
|
+
} catch {
|
|
203
|
+
return [];
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
function escapeHtml(value: string): string {
|
|
208
|
+
return value
|
|
209
|
+
.replaceAll("&", "&")
|
|
210
|
+
.replaceAll("<", "<")
|
|
211
|
+
.replaceAll(">", ">")
|
|
212
|
+
.replaceAll('"', """)
|
|
213
|
+
.replaceAll("'", "'");
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
function resolvePrismLanguage(rawLanguage: string): string {
|
|
217
|
+
const normalized = rawLanguage.trim().toLowerCase();
|
|
218
|
+
return PRISM_LANGUAGE_ALIAS[normalized] ?? normalized;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function resolvePrismLanguageLabel(language: string): string {
|
|
222
|
+
const normalized = language.trim().toLowerCase();
|
|
223
|
+
if (!normalized) {
|
|
224
|
+
return "";
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
return (
|
|
228
|
+
PRISM_LANGUAGE_LABEL[normalized] ??
|
|
229
|
+
normalized
|
|
230
|
+
.replace(/[-_]+/g, " ")
|
|
231
|
+
.replace(/\b\w/g, (char) => char.toUpperCase())
|
|
232
|
+
);
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
function resolvePrismGrammar(language: string) {
|
|
236
|
+
return (
|
|
237
|
+
Prism.languages[language] ??
|
|
238
|
+
Prism.languages[PRISM_LANGUAGE_ALIAS[language] ?? ""]
|
|
239
|
+
);
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
function ensureCodeBlockCopyButton(preElement: HTMLPreElement): void {
|
|
243
|
+
const existingButton = Array.from(preElement.children).find(
|
|
244
|
+
(child) =>
|
|
245
|
+
child instanceof HTMLButtonElement &&
|
|
246
|
+
child.classList.contains("ask-ai-copy-code-button"),
|
|
247
|
+
);
|
|
248
|
+
|
|
249
|
+
if (existingButton instanceof HTMLButtonElement) {
|
|
250
|
+
return;
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
const copyButton = document.createElement("button");
|
|
254
|
+
copyButton.type = "button";
|
|
255
|
+
copyButton.className = "ask-ai-copy-code-button";
|
|
256
|
+
copyButton.setAttribute("data-copy-code", "true");
|
|
257
|
+
copyButton.setAttribute("aria-label", "Copy code to clipboard");
|
|
258
|
+
copyButton.setAttribute("title", "Copy code");
|
|
259
|
+
|
|
260
|
+
const copyIcon = document.createElement("span");
|
|
261
|
+
copyIcon.className = "ask-ai-copy-icon";
|
|
262
|
+
copyIcon.setAttribute("aria-hidden", "true");
|
|
263
|
+
|
|
264
|
+
const checkIcon = document.createElement("span");
|
|
265
|
+
checkIcon.className = "ask-ai-copy-check-icon";
|
|
266
|
+
checkIcon.setAttribute("aria-hidden", "true");
|
|
267
|
+
|
|
268
|
+
copyButton.append(copyIcon, checkIcon);
|
|
269
|
+
preElement.append(copyButton);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
async function copyToClipboard(text: string): Promise<boolean> {
|
|
273
|
+
if (
|
|
274
|
+
typeof navigator !== "undefined" &&
|
|
275
|
+
navigator.clipboard &&
|
|
276
|
+
typeof navigator.clipboard.writeText === "function"
|
|
277
|
+
) {
|
|
278
|
+
try {
|
|
279
|
+
await navigator.clipboard.writeText(text);
|
|
280
|
+
return true;
|
|
281
|
+
} catch {
|
|
282
|
+
// Fall through to legacy copy fallback.
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (typeof document === "undefined") {
|
|
287
|
+
return false;
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
try {
|
|
291
|
+
const textarea = document.createElement("textarea");
|
|
292
|
+
textarea.value = text;
|
|
293
|
+
textarea.setAttribute("readonly", "true");
|
|
294
|
+
textarea.style.position = "fixed";
|
|
295
|
+
textarea.style.top = "-9999px";
|
|
296
|
+
textarea.style.opacity = "0";
|
|
297
|
+
document.body.append(textarea);
|
|
298
|
+
textarea.select();
|
|
299
|
+
const didCopy = document.execCommand("copy");
|
|
300
|
+
textarea.remove();
|
|
301
|
+
return didCopy;
|
|
302
|
+
} catch {
|
|
303
|
+
return false;
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
function unwrapEscapedFenceCodeBlock(rawCode: string): {
|
|
308
|
+
language: string;
|
|
309
|
+
code: string;
|
|
310
|
+
} | null {
|
|
311
|
+
const normalizedCode = rawCode.replaceAll("\r\n", "\n").trim();
|
|
312
|
+
if (!normalizedCode) {
|
|
313
|
+
return null;
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
const lines = normalizedCode.split("\n");
|
|
317
|
+
if (lines.length < 2) {
|
|
318
|
+
return null;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
const openingLine = lines[0]?.trim() ?? "";
|
|
322
|
+
const openingMatch = openingLine.match(/^(`{3,})([^\s`]*)?(?:\s+[\s\S]*)?$/);
|
|
323
|
+
if (!openingMatch) {
|
|
324
|
+
return null;
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
const fenceToken = openingMatch[1];
|
|
328
|
+
const closingLine = lines.at(-1)?.trim() ?? "";
|
|
329
|
+
if (closingLine !== fenceToken) {
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
const languageToken = (openingMatch[2] ?? "").trim();
|
|
334
|
+
if (!languageToken) {
|
|
335
|
+
return null;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
return {
|
|
339
|
+
language: resolvePrismLanguage(languageToken),
|
|
340
|
+
code: lines.slice(1, -1).join("\n"),
|
|
341
|
+
};
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
function highlightCodeBlocksInHtml(html: string): string {
|
|
345
|
+
if (typeof document === "undefined" || !html.trim()) {
|
|
346
|
+
return html;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
const container = document.createElement("div");
|
|
351
|
+
container.innerHTML = html;
|
|
352
|
+
|
|
353
|
+
const codeNodes = container.querySelectorAll("pre > code");
|
|
354
|
+
codeNodes.forEach((codeNode) => {
|
|
355
|
+
const getLanguageClassName = (element: Element | null) =>
|
|
356
|
+
Array.from(element?.classList ?? []).find(
|
|
357
|
+
(className) =>
|
|
358
|
+
className.startsWith("language-") || className.startsWith("lang-"),
|
|
359
|
+
);
|
|
360
|
+
|
|
361
|
+
const setLanguageClass = (language: string) => {
|
|
362
|
+
Array.from(codeNode.classList).forEach((className) => {
|
|
363
|
+
if (
|
|
364
|
+
className.startsWith("language-") ||
|
|
365
|
+
className.startsWith("lang-")
|
|
366
|
+
) {
|
|
367
|
+
codeNode.classList.remove(className);
|
|
368
|
+
}
|
|
369
|
+
});
|
|
370
|
+
codeNode.classList.add(`language-${language}`);
|
|
371
|
+
|
|
372
|
+
const preParent = codeNode.parentElement;
|
|
373
|
+
if (preParent instanceof HTMLPreElement) {
|
|
374
|
+
Array.from(preParent.classList).forEach((className) => {
|
|
375
|
+
if (
|
|
376
|
+
className.startsWith("language-") ||
|
|
377
|
+
className.startsWith("lang-")
|
|
378
|
+
) {
|
|
379
|
+
preParent.classList.remove(className);
|
|
380
|
+
}
|
|
381
|
+
});
|
|
382
|
+
preParent.classList.add(`language-${language}`);
|
|
383
|
+
const languageLabel = resolvePrismLanguageLabel(language);
|
|
384
|
+
if (languageLabel) {
|
|
385
|
+
preParent.setAttribute("data-language", languageLabel);
|
|
386
|
+
} else {
|
|
387
|
+
preParent.removeAttribute("data-language");
|
|
388
|
+
}
|
|
389
|
+
|
|
390
|
+
ensureCodeBlockCopyButton(preParent);
|
|
391
|
+
}
|
|
392
|
+
};
|
|
393
|
+
|
|
394
|
+
const codeLanguageClassName = getLanguageClassName(codeNode);
|
|
395
|
+
const preLanguageClassName = getLanguageClassName(codeNode.parentElement);
|
|
396
|
+
let rawLanguage =
|
|
397
|
+
codeLanguageClassName?.replace(/^(language-|lang-)/, "") ??
|
|
398
|
+
preLanguageClassName?.replace(/^(language-|lang-)/, "") ??
|
|
399
|
+
"";
|
|
400
|
+
|
|
401
|
+
let rawCode = codeNode.textContent ?? "";
|
|
402
|
+
if (!rawLanguage) {
|
|
403
|
+
const unwrappedFence = unwrapEscapedFenceCodeBlock(rawCode);
|
|
404
|
+
if (unwrappedFence) {
|
|
405
|
+
rawLanguage = unwrappedFence.language || rawLanguage;
|
|
406
|
+
rawCode = unwrappedFence.code;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
if (!rawLanguage) {
|
|
411
|
+
return;
|
|
412
|
+
}
|
|
413
|
+
|
|
414
|
+
const resolvedLanguage = resolvePrismLanguage(rawLanguage);
|
|
415
|
+
const grammar =
|
|
416
|
+
resolvePrismGrammar(resolvedLanguage) ??
|
|
417
|
+
resolvePrismGrammar(rawLanguage);
|
|
418
|
+
if (!grammar) {
|
|
419
|
+
return;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
setLanguageClass(resolvedLanguage);
|
|
423
|
+
try {
|
|
424
|
+
const highlighted = Prism.highlight(rawCode, grammar, resolvedLanguage);
|
|
425
|
+
codeNode.innerHTML = highlighted;
|
|
426
|
+
} catch (error) {
|
|
427
|
+
console.error("Assistant embed code highlighting failed", {
|
|
428
|
+
language: resolvedLanguage,
|
|
429
|
+
error,
|
|
430
|
+
});
|
|
431
|
+
}
|
|
432
|
+
});
|
|
433
|
+
|
|
434
|
+
return container.innerHTML;
|
|
435
|
+
} catch (error) {
|
|
436
|
+
console.error(
|
|
437
|
+
"Assistant embed markdown highlighting pipeline failed",
|
|
438
|
+
error,
|
|
439
|
+
);
|
|
440
|
+
return html;
|
|
441
|
+
}
|
|
442
|
+
}
|
|
443
|
+
|
|
444
|
+
function renderMarkdownToHtml(
|
|
445
|
+
markdown: string,
|
|
446
|
+
linkTarget: AssistantLinkTarget,
|
|
447
|
+
): string {
|
|
448
|
+
const normalizedMarkdown = markdown.replaceAll("\r\n", "\n");
|
|
449
|
+
const cacheKey = `${linkTarget}\0${normalizedMarkdown}`;
|
|
450
|
+
const cached = markdownHtmlCache.get(cacheKey);
|
|
451
|
+
if (cached !== undefined) {
|
|
452
|
+
return cached;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
let html = "";
|
|
456
|
+
|
|
457
|
+
try {
|
|
458
|
+
const processor = unified()
|
|
459
|
+
.use(remarkParse)
|
|
460
|
+
.use(remarkGfm)
|
|
461
|
+
.use(remarkRehype, { allowDangerousHtml: false });
|
|
462
|
+
|
|
463
|
+
if (linkTarget === "blank") {
|
|
464
|
+
processor.use(rehypeOpenLinksInNewTab);
|
|
465
|
+
}
|
|
466
|
+
|
|
467
|
+
html = String(
|
|
468
|
+
processor.use(rehypeStringify).processSync(normalizedMarkdown),
|
|
469
|
+
);
|
|
470
|
+
} catch {
|
|
471
|
+
html = escapeHtml(normalizedMarkdown).replaceAll("\n", "<br/>");
|
|
472
|
+
}
|
|
473
|
+
|
|
474
|
+
html = highlightCodeBlocksInHtml(html);
|
|
475
|
+
|
|
476
|
+
markdownHtmlCache.set(cacheKey, html);
|
|
477
|
+
if (markdownHtmlCache.size > MARKDOWN_HTML_CACHE_LIMIT) {
|
|
478
|
+
const oldestKey = markdownHtmlCache.keys().next().value;
|
|
479
|
+
if (oldestKey) {
|
|
480
|
+
markdownHtmlCache.delete(oldestKey);
|
|
481
|
+
}
|
|
482
|
+
}
|
|
483
|
+
|
|
484
|
+
return html;
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
function extractErrorMessage(rawBody: string): string {
|
|
488
|
+
if (!rawBody.trim()) {
|
|
489
|
+
return "";
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
try {
|
|
493
|
+
const parsed = JSON.parse(rawBody) as { error?: unknown; detail?: unknown };
|
|
494
|
+
if (typeof parsed.error === "string" && parsed.error.trim()) {
|
|
495
|
+
if (typeof parsed.detail === "string" && parsed.detail.trim()) {
|
|
496
|
+
return `${parsed.error}: ${parsed.detail}`;
|
|
497
|
+
}
|
|
498
|
+
return parsed.error;
|
|
499
|
+
}
|
|
500
|
+
} catch {
|
|
501
|
+
// Fall back to the plain response body.
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
return rawBody.trim();
|
|
505
|
+
}
|
|
506
|
+
|
|
507
|
+
function postParentMessage(type: string) {
|
|
508
|
+
if (typeof window === "undefined") {
|
|
509
|
+
return;
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
window.parent.postMessage({ type }, "*");
|
|
513
|
+
}
|
|
514
|
+
|
|
515
|
+
function getApiPath(
|
|
516
|
+
apiPath: string,
|
|
517
|
+
allowApiPathQueryOverride: boolean,
|
|
518
|
+
): string {
|
|
519
|
+
if (typeof window === "undefined") {
|
|
520
|
+
return apiPath;
|
|
521
|
+
}
|
|
522
|
+
|
|
523
|
+
if (allowApiPathQueryOverride) {
|
|
524
|
+
try {
|
|
525
|
+
const configuredApiUrl = new URLSearchParams(window.location.search).get(
|
|
526
|
+
"apiUrl",
|
|
527
|
+
);
|
|
528
|
+
if (configuredApiUrl && configuredApiUrl.trim().length > 0) {
|
|
529
|
+
return configuredApiUrl;
|
|
530
|
+
}
|
|
531
|
+
} catch {
|
|
532
|
+
// Fall back to the server-rendered value.
|
|
533
|
+
}
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
return apiPath;
|
|
537
|
+
}
|
|
538
|
+
|
|
539
|
+
export function AssistantPanelIcon({
|
|
540
|
+
color,
|
|
541
|
+
imageSrc,
|
|
542
|
+
className,
|
|
543
|
+
style,
|
|
544
|
+
}: {
|
|
545
|
+
color?: string;
|
|
546
|
+
imageSrc?: string;
|
|
547
|
+
className: string;
|
|
548
|
+
style?: JSX.CSSProperties;
|
|
549
|
+
}) {
|
|
550
|
+
if (!imageSrc) return null;
|
|
551
|
+
|
|
552
|
+
return (
|
|
553
|
+
<span
|
|
554
|
+
className={className}
|
|
555
|
+
style={color ? { ...style, color } : style}
|
|
556
|
+
aria-hidden="true"
|
|
557
|
+
>
|
|
558
|
+
<img src={imageSrc} alt="" />
|
|
559
|
+
</span>
|
|
560
|
+
);
|
|
561
|
+
}
|
|
562
|
+
|
|
563
|
+
export default function AssistantEmbedPanel({
|
|
564
|
+
apiPath,
|
|
565
|
+
docsTitle,
|
|
566
|
+
isChatAvailable,
|
|
567
|
+
canSendChatRequest,
|
|
568
|
+
launcherThemeColor,
|
|
569
|
+
launcherThemeColors,
|
|
570
|
+
launcherIconColor,
|
|
571
|
+
launcherIconColors,
|
|
572
|
+
launcherIconImageSrc,
|
|
573
|
+
emptyStateHeading = "How can I help?",
|
|
574
|
+
emptyStateQuestions = [],
|
|
575
|
+
unavailableMessage = "Chat assistant is not available.",
|
|
576
|
+
devProxyToken,
|
|
577
|
+
panelSurface = "iframe",
|
|
578
|
+
linkTarget = "current",
|
|
579
|
+
allowApiPathQueryOverride = true,
|
|
580
|
+
openSignal = 0,
|
|
581
|
+
onRequestClose,
|
|
582
|
+
}: AssistantEmbedPanelProps) {
|
|
583
|
+
const [messages, setMessages] = useState<ChatMessage[]>(
|
|
584
|
+
canSendChatRequest ? readPersistedMessages : [],
|
|
585
|
+
);
|
|
586
|
+
const [input, setInput] = useState("");
|
|
587
|
+
const [isBusy, setIsBusy] = useState(false);
|
|
588
|
+
const [isAwaitingFirstToken, setIsAwaitingFirstToken] = useState(false);
|
|
589
|
+
const [errorMessage, setErrorMessage] = useState("");
|
|
590
|
+
const [showUnavailableState, setShowUnavailableState] =
|
|
591
|
+
useState(!isChatAvailable);
|
|
592
|
+
const [emptyStateAnimationKey, setEmptyStateAnimationKey] = useState(0);
|
|
593
|
+
const activeRequestAbortRef = useRef<AbortController | null>(null);
|
|
594
|
+
const scrollViewportRef = useRef<HTMLDivElement | null>(null);
|
|
595
|
+
const inputRef = useRef<HTMLTextAreaElement | null>(null);
|
|
596
|
+
const resolvedApiPathRef = useRef(
|
|
597
|
+
getApiPath(apiPath, allowApiPathQueryOverride),
|
|
598
|
+
);
|
|
599
|
+
const visibleEmptyStateQuestions = emptyStateQuestions
|
|
600
|
+
.map((question) => question.trim())
|
|
601
|
+
.filter((question) => question.length > 0)
|
|
602
|
+
.slice(0, 3);
|
|
603
|
+
const launcherThemeColorLight =
|
|
604
|
+
launcherThemeColors?.light ?? launcherThemeColor ?? "#171717";
|
|
605
|
+
const launcherThemeColorDark =
|
|
606
|
+
launcherThemeColors?.dark ?? launcherThemeColor ?? "#171717";
|
|
607
|
+
const launcherIconColorLight =
|
|
608
|
+
launcherIconColors?.light ?? launcherIconColor ?? "#ffffff";
|
|
609
|
+
const launcherIconColorDark =
|
|
610
|
+
launcherIconColors?.dark ?? launcherIconColor ?? "#ffffff";
|
|
611
|
+
|
|
612
|
+
useEffect(() => {
|
|
613
|
+
return () => {
|
|
614
|
+
activeRequestAbortRef.current?.abort();
|
|
615
|
+
activeRequestAbortRef.current = null;
|
|
616
|
+
};
|
|
617
|
+
}, []);
|
|
618
|
+
|
|
619
|
+
useEffect(() => {
|
|
620
|
+
if (typeof window === "undefined") {
|
|
621
|
+
return;
|
|
622
|
+
}
|
|
623
|
+
|
|
624
|
+
try {
|
|
625
|
+
window.localStorage.setItem(STORAGE_KEY, JSON.stringify({ messages }));
|
|
626
|
+
} catch {
|
|
627
|
+
// Ignore storage failures in private mode or constrained embeds.
|
|
628
|
+
}
|
|
629
|
+
}, [messages]);
|
|
630
|
+
|
|
631
|
+
useEffect(() => {
|
|
632
|
+
const viewport = scrollViewportRef.current;
|
|
633
|
+
if (!viewport) {
|
|
634
|
+
return;
|
|
635
|
+
}
|
|
636
|
+
viewport.scrollTop = viewport.scrollHeight;
|
|
637
|
+
}, [messages, isAwaitingFirstToken]);
|
|
638
|
+
|
|
639
|
+
const resizeChatInputTextarea = () => {
|
|
640
|
+
const textarea = inputRef.current;
|
|
641
|
+
if (!textarea || typeof window === "undefined") {
|
|
642
|
+
return;
|
|
643
|
+
}
|
|
644
|
+
|
|
645
|
+
textarea.style.height = "auto";
|
|
646
|
+
|
|
647
|
+
const computedStyle = window.getComputedStyle(textarea);
|
|
648
|
+
const lineHeightRaw = Number.parseFloat(computedStyle.lineHeight);
|
|
649
|
+
const paddingTopRaw = Number.parseFloat(computedStyle.paddingTop);
|
|
650
|
+
const paddingBottomRaw = Number.parseFloat(computedStyle.paddingBottom);
|
|
651
|
+
|
|
652
|
+
const lineHeight = Number.isFinite(lineHeightRaw) ? lineHeightRaw : 20;
|
|
653
|
+
const paddingTop = Number.isFinite(paddingTopRaw) ? paddingTopRaw : 0;
|
|
654
|
+
const paddingBottom = Number.isFinite(paddingBottomRaw)
|
|
655
|
+
? paddingBottomRaw
|
|
656
|
+
: 0;
|
|
657
|
+
|
|
658
|
+
const minHeight = lineHeight + paddingTop + paddingBottom;
|
|
659
|
+
const measuredScrollHeight = Math.max(textarea.scrollHeight, minHeight);
|
|
660
|
+
const maxHeight = lineHeight * 11.2 + paddingTop + paddingBottom;
|
|
661
|
+
const nextHeight = Math.min(measuredScrollHeight, maxHeight);
|
|
662
|
+
|
|
663
|
+
textarea.style.height = `${nextHeight}px`;
|
|
664
|
+
textarea.style.overflowY =
|
|
665
|
+
measuredScrollHeight > maxHeight ? "auto" : "hidden";
|
|
666
|
+
};
|
|
667
|
+
|
|
668
|
+
useEffect(() => {
|
|
669
|
+
resizeChatInputTextarea();
|
|
670
|
+
}, [input]);
|
|
671
|
+
|
|
672
|
+
useEffect(() => {
|
|
673
|
+
if (typeof window === "undefined") {
|
|
674
|
+
return;
|
|
675
|
+
}
|
|
676
|
+
|
|
677
|
+
const handlePanelMessage = (event: MessageEvent) => {
|
|
678
|
+
if (
|
|
679
|
+
!event.data ||
|
|
680
|
+
typeof event.data !== "object" ||
|
|
681
|
+
event.data.type !== "assistant-embed:panel-opened"
|
|
682
|
+
) {
|
|
683
|
+
return;
|
|
684
|
+
}
|
|
685
|
+
|
|
686
|
+
setEmptyStateAnimationKey((previous) => previous + 1);
|
|
687
|
+
};
|
|
688
|
+
|
|
689
|
+
window.addEventListener("message", handlePanelMessage);
|
|
690
|
+
return () => {
|
|
691
|
+
window.removeEventListener("message", handlePanelMessage);
|
|
692
|
+
};
|
|
693
|
+
}, []);
|
|
694
|
+
|
|
695
|
+
useEffect(() => {
|
|
696
|
+
setEmptyStateAnimationKey((previous) => previous + 1);
|
|
697
|
+
}, [openSignal]);
|
|
698
|
+
|
|
699
|
+
const handleStartNewChat = () => {
|
|
700
|
+
activeRequestAbortRef.current?.abort();
|
|
701
|
+
activeRequestAbortRef.current = null;
|
|
702
|
+
setMessages([]);
|
|
703
|
+
setInput("");
|
|
704
|
+
setIsBusy(false);
|
|
705
|
+
setIsAwaitingFirstToken(false);
|
|
706
|
+
setErrorMessage("");
|
|
707
|
+
setShowUnavailableState(false);
|
|
708
|
+
};
|
|
709
|
+
|
|
710
|
+
const handleRequestClose = () => {
|
|
711
|
+
if (onRequestClose) {
|
|
712
|
+
onRequestClose();
|
|
713
|
+
return;
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
postParentMessage("assistant-embed:close");
|
|
717
|
+
};
|
|
718
|
+
|
|
719
|
+
const handleUnavailableBack = () => {
|
|
720
|
+
activeRequestAbortRef.current?.abort();
|
|
721
|
+
activeRequestAbortRef.current = null;
|
|
722
|
+
setMessages([]);
|
|
723
|
+
setInput("");
|
|
724
|
+
setIsBusy(false);
|
|
725
|
+
setIsAwaitingFirstToken(false);
|
|
726
|
+
setErrorMessage("");
|
|
727
|
+
setShowUnavailableState(false);
|
|
728
|
+
};
|
|
729
|
+
|
|
730
|
+
const submitPrompt = async (rawPrompt: string) => {
|
|
731
|
+
const prompt = rawPrompt.trim();
|
|
732
|
+
if (!prompt || isBusy || showUnavailableState) {
|
|
733
|
+
return;
|
|
734
|
+
}
|
|
735
|
+
|
|
736
|
+
if (!isChatAvailable || !canSendChatRequest) {
|
|
737
|
+
setShowUnavailableState(true);
|
|
738
|
+
return;
|
|
739
|
+
}
|
|
740
|
+
|
|
741
|
+
setErrorMessage("");
|
|
742
|
+
setIsBusy(true);
|
|
743
|
+
setIsAwaitingFirstToken(true);
|
|
744
|
+
|
|
745
|
+
const userMessage: ChatMessage = {
|
|
746
|
+
id: createMessageId(),
|
|
747
|
+
role: "user",
|
|
748
|
+
content: prompt,
|
|
749
|
+
};
|
|
750
|
+
const assistantId = createMessageId();
|
|
751
|
+
const nextConversation = [...messages, userMessage];
|
|
752
|
+
|
|
753
|
+
setInput("");
|
|
754
|
+
setMessages(nextConversation);
|
|
755
|
+
|
|
756
|
+
activeRequestAbortRef.current?.abort();
|
|
757
|
+
const abortController = new AbortController();
|
|
758
|
+
activeRequestAbortRef.current = abortController;
|
|
759
|
+
|
|
760
|
+
try {
|
|
761
|
+
const requestHeaders: Record<string, string> = {
|
|
762
|
+
"Content-Type": "application/json",
|
|
763
|
+
};
|
|
764
|
+
|
|
765
|
+
if (devProxyToken && devProxyToken.trim().length > 0) {
|
|
766
|
+
requestHeaders["x-ask-ai-dev-token"] = devProxyToken;
|
|
767
|
+
}
|
|
768
|
+
|
|
769
|
+
const response = await fetch(resolvedApiPathRef.current, {
|
|
770
|
+
method: "POST",
|
|
771
|
+
headers: requestHeaders,
|
|
772
|
+
body: JSON.stringify({
|
|
773
|
+
messages: nextConversation.map((message) => ({
|
|
774
|
+
role: message.role,
|
|
775
|
+
content: message.content,
|
|
776
|
+
})),
|
|
777
|
+
}),
|
|
778
|
+
signal: abortController.signal,
|
|
779
|
+
});
|
|
780
|
+
|
|
781
|
+
if (!response.ok) {
|
|
782
|
+
const rawBody = await response.text();
|
|
783
|
+
const detail = extractErrorMessage(rawBody);
|
|
784
|
+
throw new Error(
|
|
785
|
+
detail || `Request failed with status ${response.status}`,
|
|
786
|
+
);
|
|
787
|
+
}
|
|
788
|
+
|
|
789
|
+
if (!response.body) {
|
|
790
|
+
throw new Error("Response body is empty");
|
|
791
|
+
}
|
|
792
|
+
|
|
793
|
+
const reader = response.body.getReader();
|
|
794
|
+
const decoder = new TextDecoder();
|
|
795
|
+
let buffer = "";
|
|
796
|
+
let streamDone = false;
|
|
797
|
+
let hasReceivedFirstTextDelta = false;
|
|
798
|
+
|
|
799
|
+
while (!streamDone) {
|
|
800
|
+
const { done, value } = await reader.read();
|
|
801
|
+
if (done) {
|
|
802
|
+
break;
|
|
803
|
+
}
|
|
804
|
+
|
|
805
|
+
buffer += decoder.decode(value, { stream: true });
|
|
806
|
+
|
|
807
|
+
while (true) {
|
|
808
|
+
const eventBreakIndex = buffer.indexOf("\n\n");
|
|
809
|
+
if (eventBreakIndex === -1) {
|
|
810
|
+
break;
|
|
811
|
+
}
|
|
812
|
+
|
|
813
|
+
const rawEvent = buffer.slice(0, eventBreakIndex);
|
|
814
|
+
buffer = buffer.slice(eventBreakIndex + 2);
|
|
815
|
+
|
|
816
|
+
const dataPayload = rawEvent
|
|
817
|
+
.split("\n")
|
|
818
|
+
.filter((line) => line.startsWith("data:"))
|
|
819
|
+
.map((line) => line.slice(5).trimStart())
|
|
820
|
+
.join("\n");
|
|
821
|
+
|
|
822
|
+
if (!dataPayload) {
|
|
823
|
+
continue;
|
|
824
|
+
}
|
|
825
|
+
|
|
826
|
+
if (dataPayload === "[DONE]") {
|
|
827
|
+
streamDone = true;
|
|
828
|
+
break;
|
|
829
|
+
}
|
|
830
|
+
|
|
831
|
+
let parsed: AssistantStreamEvent;
|
|
832
|
+
try {
|
|
833
|
+
parsed = JSON.parse(dataPayload) as AssistantStreamEvent;
|
|
834
|
+
} catch {
|
|
835
|
+
continue;
|
|
836
|
+
}
|
|
837
|
+
|
|
838
|
+
if (
|
|
839
|
+
parsed.type === "text-delta" &&
|
|
840
|
+
typeof parsed.delta === "string" &&
|
|
841
|
+
parsed.delta.length > 0
|
|
842
|
+
) {
|
|
843
|
+
if (!hasReceivedFirstTextDelta) {
|
|
844
|
+
hasReceivedFirstTextDelta = true;
|
|
845
|
+
setIsAwaitingFirstToken(false);
|
|
846
|
+
}
|
|
847
|
+
|
|
848
|
+
setMessages((previous) => {
|
|
849
|
+
const existingAssistantMessage = previous.find(
|
|
850
|
+
(message) => message.id === assistantId,
|
|
851
|
+
);
|
|
852
|
+
|
|
853
|
+
if (!existingAssistantMessage) {
|
|
854
|
+
return [
|
|
855
|
+
...previous,
|
|
856
|
+
{
|
|
857
|
+
id: assistantId,
|
|
858
|
+
role: "assistant",
|
|
859
|
+
content: parsed.delta ?? "",
|
|
860
|
+
},
|
|
861
|
+
];
|
|
862
|
+
}
|
|
863
|
+
|
|
864
|
+
return previous.map((message) =>
|
|
865
|
+
message.id === assistantId
|
|
866
|
+
? {
|
|
867
|
+
...message,
|
|
868
|
+
content: `${message.content}${parsed.delta ?? ""}`,
|
|
869
|
+
}
|
|
870
|
+
: message,
|
|
871
|
+
);
|
|
872
|
+
});
|
|
873
|
+
}
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
} catch (error) {
|
|
877
|
+
if (abortController.signal.aborted) {
|
|
878
|
+
return;
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
const resolvedError =
|
|
882
|
+
error instanceof Error ? error.message : String(error);
|
|
883
|
+
setErrorMessage(resolvedError || "Failed to get response.");
|
|
884
|
+
} finally {
|
|
885
|
+
if (activeRequestAbortRef.current === abortController) {
|
|
886
|
+
activeRequestAbortRef.current = null;
|
|
887
|
+
}
|
|
888
|
+
setIsBusy(false);
|
|
889
|
+
setIsAwaitingFirstToken(false);
|
|
890
|
+
}
|
|
891
|
+
};
|
|
892
|
+
|
|
893
|
+
const handleSubmit = async (event: Event) => {
|
|
894
|
+
event.preventDefault();
|
|
895
|
+
await submitPrompt(input);
|
|
896
|
+
};
|
|
897
|
+
|
|
898
|
+
const handleChatInputKeyDown = (event: ChatInputKeyEvent) => {
|
|
899
|
+
if (event.key !== "Enter" || event.shiftKey || event.isComposing) {
|
|
900
|
+
return;
|
|
901
|
+
}
|
|
902
|
+
|
|
903
|
+
event.preventDefault();
|
|
904
|
+
const form = event.currentTarget.form;
|
|
905
|
+
if (form) {
|
|
906
|
+
form.requestSubmit();
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
|
|
910
|
+
const handleRenderedMarkdownClick = (
|
|
911
|
+
event: JSX.TargetedMouseEvent<HTMLDivElement>,
|
|
912
|
+
) => {
|
|
913
|
+
const targetElement = event.target instanceof Element ? event.target : null;
|
|
914
|
+
const copyButton = targetElement?.closest(
|
|
915
|
+
".ask-ai-copy-code-button",
|
|
916
|
+
) as HTMLButtonElement | null;
|
|
917
|
+
|
|
918
|
+
if (!copyButton) {
|
|
919
|
+
return;
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
event.preventDefault();
|
|
923
|
+
event.stopPropagation();
|
|
924
|
+
|
|
925
|
+
if (copyButton.dataset.copying === "true") {
|
|
926
|
+
return;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
const preElement = copyButton.closest("pre");
|
|
930
|
+
const codeElement = preElement?.querySelector("code");
|
|
931
|
+
if (!codeElement) {
|
|
932
|
+
return;
|
|
933
|
+
}
|
|
934
|
+
|
|
935
|
+
const codeText = codeElement.textContent ?? "";
|
|
936
|
+
copyButton.dataset.copying = "true";
|
|
937
|
+
|
|
938
|
+
void (async () => {
|
|
939
|
+
const didCopy = await copyToClipboard(codeText);
|
|
940
|
+
if (didCopy) {
|
|
941
|
+
copyButton.dataset.copied = "true";
|
|
942
|
+
} else {
|
|
943
|
+
delete copyButton.dataset.copied;
|
|
944
|
+
}
|
|
945
|
+
|
|
946
|
+
window.setTimeout(() => {
|
|
947
|
+
delete copyButton.dataset.copied;
|
|
948
|
+
delete copyButton.dataset.copying;
|
|
949
|
+
}, 1200);
|
|
950
|
+
})();
|
|
951
|
+
};
|
|
952
|
+
|
|
953
|
+
const handleThreadViewportWheel = (event: ChatViewportWheelEvent) => {
|
|
954
|
+
const eventTargetElement =
|
|
955
|
+
event.target instanceof Element ? event.target : null;
|
|
956
|
+
const codeBlockPre = eventTargetElement?.closest(
|
|
957
|
+
".ask-ai-markdown pre",
|
|
958
|
+
) as HTMLPreElement | null;
|
|
959
|
+
const codeBlockScroller =
|
|
960
|
+
(eventTargetElement?.closest(
|
|
961
|
+
".ask-ai-markdown pre > code",
|
|
962
|
+
) as HTMLElement | null) ??
|
|
963
|
+
(codeBlockPre?.querySelector(":scope > code") as HTMLElement | null);
|
|
964
|
+
|
|
965
|
+
if (!codeBlockScroller) {
|
|
966
|
+
return;
|
|
967
|
+
}
|
|
968
|
+
|
|
969
|
+
const horizontalIntentDelta =
|
|
970
|
+
Math.abs(event.deltaX) > 0.01
|
|
971
|
+
? event.deltaX
|
|
972
|
+
: event.shiftKey
|
|
973
|
+
? event.deltaY
|
|
974
|
+
: 0;
|
|
975
|
+
|
|
976
|
+
if (Math.abs(horizontalIntentDelta) <= 0.01) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
|
|
980
|
+
event.preventDefault();
|
|
981
|
+
|
|
982
|
+
const maxScrollLeft = Math.max(
|
|
983
|
+
0,
|
|
984
|
+
codeBlockScroller.scrollWidth - codeBlockScroller.clientWidth,
|
|
985
|
+
);
|
|
986
|
+
|
|
987
|
+
if (maxScrollLeft > 0) {
|
|
988
|
+
codeBlockScroller.scrollLeft = clamp(
|
|
989
|
+
codeBlockScroller.scrollLeft + horizontalIntentDelta,
|
|
990
|
+
0,
|
|
991
|
+
maxScrollLeft,
|
|
992
|
+
);
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
|
|
996
|
+
const panelClassName = [
|
|
997
|
+
"relative flex min-h-0 flex-col overflow-hidden text-neutral-900 shadow-2xl dark:text-neutral-50",
|
|
998
|
+
panelSurface === "inline"
|
|
999
|
+
? "h-full rounded-2xl"
|
|
1000
|
+
: "h-dvh sm:h-screen sm:rounded-2xl",
|
|
1001
|
+
].join(" ");
|
|
1002
|
+
|
|
1003
|
+
return (
|
|
1004
|
+
<div className={panelClassName}>
|
|
1005
|
+
<header className="flex items-center justify-between gap-3 px-2 pt-2 pb-1">
|
|
1006
|
+
<div className="flex min-w-0 items-center gap-2 ">
|
|
1007
|
+
<AssistantPanelIcon
|
|
1008
|
+
color={launcherIconColor}
|
|
1009
|
+
imageSrc={launcherIconImageSrc}
|
|
1010
|
+
className="assistant-embed-header-icon inline-flex size-9 shrink-0 items-center justify-center rounded-md dark:border-[0.5px] border-neutral-900/7 bg-radial from-neutral-700/5 dark:from-white/7 to-neutral-900/7 dark:to-white/5 to-60% text-neutral-900 dark:border-white/7 dark:text-neutral-50 [&_img]:block [&_img]:size-5.5 [&_img]:object-contain [&_svg]:static [&_svg]:block [&_svg]:size-5 [&_svg]:transform-none [&_svg]:opacity-100"
|
|
1011
|
+
/>
|
|
1012
|
+
<div className="min-w-0 space-y-px">
|
|
1013
|
+
<p className="truncate text-sm font-medium leading-3.5 text-neutral-900 dark:text-neutral-50">
|
|
1014
|
+
{docsTitle}
|
|
1015
|
+
</p>
|
|
1016
|
+
<p className="text-xs leading-3 text-neutral-500/80 dark:text-neutral-400">
|
|
1017
|
+
Assistant
|
|
1018
|
+
</p>
|
|
1019
|
+
</div>
|
|
1020
|
+
</div>
|
|
1021
|
+
{messages.length > 0 ? (
|
|
1022
|
+
<button
|
|
1023
|
+
type="button"
|
|
1024
|
+
className="ml-auto inline-flex items-center gap-1.5 rounded-md border border-neutral-900/8 px-2 py-1 text-[12px] text-neutral-500 transition hover:bg-neutral-900/4 disabled:cursor-not-allowed disabled:opacity-50 dark:border-white/10 dark:bg-white/5 dark:text-neutral-300 dark:hover:bg-white/10 cursor-pointer"
|
|
1025
|
+
onClick={handleStartNewChat}
|
|
1026
|
+
aria-label="Clear chat"
|
|
1027
|
+
title="Clear chat"
|
|
1028
|
+
>
|
|
1029
|
+
<Icon
|
|
1030
|
+
icon="lucide:trash-2"
|
|
1031
|
+
className="size-3.5 -ml-px"
|
|
1032
|
+
aria-hidden="true"
|
|
1033
|
+
/>
|
|
1034
|
+
Clear
|
|
1035
|
+
</button>
|
|
1036
|
+
) : null}
|
|
1037
|
+
<button
|
|
1038
|
+
type="button"
|
|
1039
|
+
className="inline-flex items-center justify-center gap-1 rounded-md size-9 text-[13px] text-neutral-500 hover:bg-neutral-900/5 dark:text-neutral-300 dark:hover:bg-white/10 transition cursor-pointer"
|
|
1040
|
+
onClick={handleRequestClose}
|
|
1041
|
+
aria-label="Close chat"
|
|
1042
|
+
title="Close"
|
|
1043
|
+
>
|
|
1044
|
+
<Icon icon="lucide:x" className="size-5" aria-hidden="true" />
|
|
1045
|
+
</button>
|
|
1046
|
+
</header>
|
|
1047
|
+
|
|
1048
|
+
{!showUnavailableState ? (
|
|
1049
|
+
<>
|
|
1050
|
+
<div
|
|
1051
|
+
ref={scrollViewportRef}
|
|
1052
|
+
className={`mask-t-from-[calc(100%-1rem)] flex-1 overflow-y-auto overscroll-contain [scrollbar-width:none] [&::-webkit-scrollbar]:hidden px-4 py-4 ${messages.length > 0 ? "pb-60" : ""} mb-10 space-y-6`}
|
|
1053
|
+
onWheel={handleThreadViewportWheel}
|
|
1054
|
+
>
|
|
1055
|
+
{messages.length === 0 ? (
|
|
1056
|
+
<div
|
|
1057
|
+
key={emptyStateAnimationKey}
|
|
1058
|
+
className="flex min-h-full flex-col items-center justify-center px-1. pb-28. pt-8. text-center"
|
|
1059
|
+
>
|
|
1060
|
+
<AssistantPanelIcon
|
|
1061
|
+
color={launcherIconColor}
|
|
1062
|
+
imageSrc={launcherIconImageSrc}
|
|
1063
|
+
className="assistant-empty-state-item assistant-embed-header-icon inline-flex size-16 shrink-0 items-center justify-center rounded-xl border-[0.5px] border-neutral-900/7 bg-white/90 text-neutral-900 shadow-[0_.5px_1px_rgba(0,0,0,0.15),0_5px_12px_-4px_rgba(0,0,0,0.08)] dark:border-white/10 dark:bg-white/5 dark:text-neutral-50 dark:shadow-none [&_img]:block [&_img]:size-10 [&_img]:object-contain [&_svg]:static [&_svg]:block [&_svg]:size-9 [&_svg]:transform-none [&_svg]:opacity-100"
|
|
1064
|
+
style={
|
|
1065
|
+
{
|
|
1066
|
+
"--assistant-empty-state-delay": "200ms",
|
|
1067
|
+
} as JSX.CSSProperties
|
|
1068
|
+
}
|
|
1069
|
+
/>
|
|
1070
|
+
<p
|
|
1071
|
+
className="assistant-empty-state-item mt-3 text-3xl font-[450] leading-9 text-neutral-900 dark:text-neutral-50"
|
|
1072
|
+
style={
|
|
1073
|
+
{
|
|
1074
|
+
"--assistant-empty-state-delay": "300ms",
|
|
1075
|
+
} as JSX.CSSProperties
|
|
1076
|
+
}
|
|
1077
|
+
>
|
|
1078
|
+
{emptyStateHeading}
|
|
1079
|
+
</p>
|
|
1080
|
+
{visibleEmptyStateQuestions.length > 0 ? (
|
|
1081
|
+
<div className="mt-10 flex w-full flex-col items-center gap-2">
|
|
1082
|
+
{visibleEmptyStateQuestions.map((question, index) => (
|
|
1083
|
+
<button
|
|
1084
|
+
key={question}
|
|
1085
|
+
type="button"
|
|
1086
|
+
className="assistant-empty-state-item w-fit rounded-full border-neutral-900/8 bg-white/90 px-3.5 py-2 text-left text-[13px] leading-5 text-neutral-700 shadow-[0px_1px_3px_0px_rgba(0,0,0,0.04),0px_0px_0px_1px_rgba(0,0,0,0.06)_inset,0px_-1px_0px_0px_rgba(0,0,0,0.06)_inset] transition hover:bg-white dark:bg-white/4 dark:text-neutral-200 dark:hover:bg-white/8 cursor-pointer dark:shadow-[0_1px_3px_0_rgba(0,0,0,0.04),inset_0_1px_0_0_rgba(255,255,255,0.04),inset_0_0_0_1px_rgba(0,0,0,0.06),inset_0_-1px_0_0_rgba(0,0,0,0.06),inset_0_0_0_1px_rgba(196,196,196,0.07)]"
|
|
1087
|
+
style={
|
|
1088
|
+
{
|
|
1089
|
+
"--assistant-empty-state-delay": `${400 + index * 100}ms`,
|
|
1090
|
+
} as JSX.CSSProperties
|
|
1091
|
+
}
|
|
1092
|
+
disabled={isBusy}
|
|
1093
|
+
onClick={() => {
|
|
1094
|
+
void submitPrompt(question);
|
|
1095
|
+
}}
|
|
1096
|
+
>
|
|
1097
|
+
{question}
|
|
1098
|
+
</button>
|
|
1099
|
+
))}
|
|
1100
|
+
</div>
|
|
1101
|
+
) : null}
|
|
1102
|
+
</div>
|
|
1103
|
+
) : null}
|
|
1104
|
+
|
|
1105
|
+
{messages.map((message) => {
|
|
1106
|
+
if (
|
|
1107
|
+
message.role === "assistant" &&
|
|
1108
|
+
message.content.length === 0
|
|
1109
|
+
) {
|
|
1110
|
+
return null;
|
|
1111
|
+
}
|
|
1112
|
+
const isUser = message.role === "user";
|
|
1113
|
+
return (
|
|
1114
|
+
<div
|
|
1115
|
+
key={message.id}
|
|
1116
|
+
className={isUser ? "text-right" : "text-left"}
|
|
1117
|
+
>
|
|
1118
|
+
<div
|
|
1119
|
+
className={[
|
|
1120
|
+
"ask-ai-markdown prose-rules text-[15px]! max-w-full min-w-0 wrap-break-word prose-code:text-neutral-700 dark:prose-code:text-neutral-200 prose-pre:shadow-xs prose-pre:text-neutral-700! dark:prose-pre:text-neutral-100! prose-pre:border prose-pre:border-neutral-200 dark:prose-pre:border-neutral-800 prose-pre:rounded-xl!",
|
|
1121
|
+
isUser
|
|
1122
|
+
? "inline-block ml-2 px-3 py-1.5 rounded-2xl rounded-br-sm bg-neutral-900/5 text-neutral-700/85 dark:bg-neutral-800 dark:text-neutral-100 *:text-left"
|
|
1123
|
+
: "block w-full bg-transparent text-neutral-900 dark:text-neutral-100",
|
|
1124
|
+
].join(" ")}
|
|
1125
|
+
onClick={handleRenderedMarkdownClick}
|
|
1126
|
+
dangerouslySetInnerHTML={{
|
|
1127
|
+
__html: renderMarkdownToHtml(message.content, linkTarget),
|
|
1128
|
+
}}
|
|
1129
|
+
/>
|
|
1130
|
+
</div>
|
|
1131
|
+
);
|
|
1132
|
+
})}
|
|
1133
|
+
|
|
1134
|
+
{isBusy && isAwaitingFirstToken ? (
|
|
1135
|
+
<div className="text-left">
|
|
1136
|
+
<span
|
|
1137
|
+
className="inline-block text-sm text-transparent bg-clip-text bg-[linear-gradient(110deg,var(--color-neutral-400)_50%,var(--color-neutral-300)_60%,var(--color-neutral-400)_70%)] dark:bg-[linear-gradient(110deg,#a3a3a3_30%,#ffffff_45%,#a3a3a3_60%)] bg-[length:200%_100%]"
|
|
1138
|
+
style={{
|
|
1139
|
+
animation: "ask-ai-thinking-shimmer 3.4s linear infinite",
|
|
1140
|
+
}}
|
|
1141
|
+
>
|
|
1142
|
+
Thinking
|
|
1143
|
+
</span>
|
|
1144
|
+
</div>
|
|
1145
|
+
) : null}
|
|
1146
|
+
|
|
1147
|
+
{errorMessage ? (
|
|
1148
|
+
<p className="text-sm text-red-600 dark:text-red-300">
|
|
1149
|
+
{errorMessage}
|
|
1150
|
+
</p>
|
|
1151
|
+
) : null}
|
|
1152
|
+
</div>
|
|
1153
|
+
|
|
1154
|
+
<form
|
|
1155
|
+
className="absolute z-10 bottom-0 inset-x-0 px-3 pb-3"
|
|
1156
|
+
onSubmit={(event) => {
|
|
1157
|
+
void handleSubmit(event);
|
|
1158
|
+
}}
|
|
1159
|
+
>
|
|
1160
|
+
<div className="flex gap-2 rounded-[24px] bg-white dark:bg-(--rd-code-surface) shadow-[0px_1px_3px_0px_rgba(0,0,0,0.04),0px_0px_0px_1px_rgba(0,0,0,0.06)_inset,0px_-1px_0px_0px_rgba(0,0,0,0.06)_inset] dark:shadow-[0_1px_3px_0_rgba(0,0,0,0.04),inset_0_1px_0_0_rgba(255,255,255,0.04),inset_0_0_0_1px_rgba(0,0,0,0.06),inset_0_-1px_0_0_rgba(0,0,0,0.06),inset_0_0_0_1px_rgba(196,196,196,0.07)]">
|
|
1161
|
+
<textarea
|
|
1162
|
+
ref={inputRef}
|
|
1163
|
+
value={input}
|
|
1164
|
+
onInput={(event) => {
|
|
1165
|
+
setInput((event.target as HTMLTextAreaElement).value);
|
|
1166
|
+
}}
|
|
1167
|
+
onKeyDown={handleChatInputKeyDown}
|
|
1168
|
+
placeholder="Ask a question..."
|
|
1169
|
+
className="text-sm my-auto min-w-0 flex-1 bg-transparent pl-4 py-2.5 text-neutral-900 placeholder:text-neutral-400 focus:outline-none dark:text-white dark:placeholder:text-neutral-400 leading-5 resize-none [scrollbar-width:none] [&::-webkit-scrollbar]:hidden"
|
|
1170
|
+
disabled={isBusy}
|
|
1171
|
+
rows={1}
|
|
1172
|
+
/>
|
|
1173
|
+
<div className="p-1 pl-0 mt-auto">
|
|
1174
|
+
<button
|
|
1175
|
+
type="submit"
|
|
1176
|
+
className="assistant-embed-send-button flex items-center justify-center rounded-full size-9 disabled:opacity-90 hover:opacity-95 cursor-pointer"
|
|
1177
|
+
style={
|
|
1178
|
+
{
|
|
1179
|
+
"--assistant-embed-send-theme-light":
|
|
1180
|
+
launcherThemeColorLight,
|
|
1181
|
+
"--assistant-embed-send-theme-dark":
|
|
1182
|
+
launcherThemeColorDark,
|
|
1183
|
+
"--assistant-embed-send-foreground-light":
|
|
1184
|
+
launcherIconColorLight,
|
|
1185
|
+
"--assistant-embed-send-foreground-dark":
|
|
1186
|
+
launcherIconColorDark,
|
|
1187
|
+
} as JSX.CSSProperties
|
|
1188
|
+
}
|
|
1189
|
+
disabled={isBusy || input.trim().length === 0}
|
|
1190
|
+
aria-label="Send message"
|
|
1191
|
+
>
|
|
1192
|
+
<Icon
|
|
1193
|
+
icon="lucide:arrow-up"
|
|
1194
|
+
className="size-4.5 **:stroke-[2.2]"
|
|
1195
|
+
aria-hidden="true"
|
|
1196
|
+
/>
|
|
1197
|
+
</button>
|
|
1198
|
+
</div>
|
|
1199
|
+
</div>
|
|
1200
|
+
</form>
|
|
1201
|
+
</>
|
|
1202
|
+
) : (
|
|
1203
|
+
<div className="flex flex-1 items-center justify-center px-5 text-center">
|
|
1204
|
+
<div className="flex max-w-64 flex-col items-center gap-3">
|
|
1205
|
+
<AssistantPanelIcon
|
|
1206
|
+
color={launcherIconColor}
|
|
1207
|
+
imageSrc={launcherIconImageSrc}
|
|
1208
|
+
className="assistant-embed-unavailable-icon inline-flex size-11 items-center justify-center rounded-full border border-neutral-900/7 bg-neutral-100 text-neutral-900 dark:border-white/10 dark:bg-white/5 dark:text-neutral-50 [&_img]:block [&_img]:size-5 [&_img]:object-contain [&_svg]:static [&_svg]:block [&_svg]:size-5 [&_svg]:transform-none [&_svg]:opacity-100"
|
|
1209
|
+
/>
|
|
1210
|
+
<p className="text-sm text-neutral-500 dark:text-neutral-300">
|
|
1211
|
+
{unavailableMessage}
|
|
1212
|
+
</p>
|
|
1213
|
+
<button
|
|
1214
|
+
type="button"
|
|
1215
|
+
className="mt-1 inline-flex items-center gap-1.5 rounded-md border border-neutral-900/8 bg-white px-2.5 py-1.5 text-[13px] font-medium text-neutral-700 shadow-xs transition hover:bg-neutral-50 dark:border-white/10 dark:bg-white/5 dark:text-neutral-200 dark:hover:bg-white/10 cursor-pointer"
|
|
1216
|
+
onClick={handleUnavailableBack}
|
|
1217
|
+
>
|
|
1218
|
+
<Icon
|
|
1219
|
+
icon="lucide:arrow-left"
|
|
1220
|
+
className="size-3.5"
|
|
1221
|
+
aria-hidden="true"
|
|
1222
|
+
/>
|
|
1223
|
+
Back
|
|
1224
|
+
</button>
|
|
1225
|
+
</div>
|
|
1226
|
+
</div>
|
|
1227
|
+
)}
|
|
1228
|
+
|
|
1229
|
+
<style>{`
|
|
1230
|
+
@keyframes assistant-empty-state-enter {
|
|
1231
|
+
0% {
|
|
1232
|
+
opacity: 0;
|
|
1233
|
+
filter: blur(8px);
|
|
1234
|
+
transform: translateY(12px);
|
|
1235
|
+
}
|
|
1236
|
+
100% {
|
|
1237
|
+
opacity: 1;
|
|
1238
|
+
filter: blur(0);
|
|
1239
|
+
transform: translateY(0);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
.assistant-empty-state-item {
|
|
1244
|
+
animation: assistant-empty-state-enter 840ms cubic-bezier(0.22, 1, 0.36, 1) both;
|
|
1245
|
+
animation-delay: var(--assistant-empty-state-delay, 0ms);
|
|
1246
|
+
will-change: opacity, filter, transform;
|
|
1247
|
+
}
|
|
1248
|
+
|
|
1249
|
+
@keyframes ask-ai-thinking-shimmer {
|
|
1250
|
+
0% { background-position: 200% 0; }
|
|
1251
|
+
100% { background-position: -200% 0; }
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
.prose-rules pre[class*="language-"],
|
|
1255
|
+
.prose-rules code[class*="language-"] {
|
|
1256
|
+
font-family: var(--font-mono);
|
|
1257
|
+
font-size: 13px;
|
|
1258
|
+
line-height: inherit;
|
|
1259
|
+
}
|
|
1260
|
+
|
|
1261
|
+
.ask-ai-markdown pre {
|
|
1262
|
+
position: relative;
|
|
1263
|
+
width: 100%;
|
|
1264
|
+
max-width: 100%;
|
|
1265
|
+
min-width: 0;
|
|
1266
|
+
box-sizing: border-box;
|
|
1267
|
+
padding-top: 2.25rem;
|
|
1268
|
+
background: var(--color-neutral-50) !important;
|
|
1269
|
+
overflow-x: hidden;
|
|
1270
|
+
overflow-y: hidden;
|
|
1271
|
+
white-space: pre;
|
|
1272
|
+
}
|
|
1273
|
+
|
|
1274
|
+
.dark .ask-ai-markdown pre {
|
|
1275
|
+
background: var(--rd-code-surface) !important;
|
|
1276
|
+
border-color: var(--color-neutral-800) !important;
|
|
1277
|
+
}
|
|
1278
|
+
|
|
1279
|
+
.ask-ai-markdown pre[data-language]::before {
|
|
1280
|
+
content: "";
|
|
1281
|
+
position: absolute;
|
|
1282
|
+
top: 0.52rem;
|
|
1283
|
+
left: 0.75rem;
|
|
1284
|
+
width: 16px;
|
|
1285
|
+
height: 16px;
|
|
1286
|
+
background-color: var(--color-neutral-500);
|
|
1287
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='m9.4 16.6-4.6-4.6 4.6-4.6L8 6l-6 6 6 6zM14.6 16.6 19.2 12l-4.6-4.6L16 6l6 6-6 6z'/%3E%3C/svg%3E");
|
|
1288
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24'%3E%3Cpath fill='black' d='m9.4 16.6-4.6-4.6 4.6-4.6L8 6l-6 6 6 6zM14.6 16.6 19.2 12l-4.6-4.6L16 6l6 6-6 6z'/%3E%3C/svg%3E");
|
|
1289
|
+
-webkit-mask-repeat: no-repeat;
|
|
1290
|
+
mask-repeat: no-repeat;
|
|
1291
|
+
-webkit-mask-position: center;
|
|
1292
|
+
mask-position: center;
|
|
1293
|
+
-webkit-mask-size: contain;
|
|
1294
|
+
mask-size: contain;
|
|
1295
|
+
pointer-events: none;
|
|
1296
|
+
user-select: none;
|
|
1297
|
+
}
|
|
1298
|
+
|
|
1299
|
+
.dark .ask-ai-markdown pre[data-language]::before {
|
|
1300
|
+
background-color: var(--color-neutral-400);
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
.ask-ai-markdown pre[data-language]::after {
|
|
1304
|
+
content: attr(data-language);
|
|
1305
|
+
position: absolute;
|
|
1306
|
+
top: 0.6rem;
|
|
1307
|
+
left: 2.2rem;
|
|
1308
|
+
font-family: var(--font-sans);
|
|
1309
|
+
font-size: 14px;
|
|
1310
|
+
line-height: 1;
|
|
1311
|
+
letter-spacing: 0.01em;
|
|
1312
|
+
color: var(--color-neutral-500);
|
|
1313
|
+
pointer-events: none;
|
|
1314
|
+
user-select: none;
|
|
1315
|
+
}
|
|
1316
|
+
|
|
1317
|
+
.dark .ask-ai-markdown pre[data-language]::after {
|
|
1318
|
+
color: var(--color-neutral-400);
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.ask-ai-markdown .ask-ai-copy-code-button {
|
|
1322
|
+
position: absolute;
|
|
1323
|
+
top: 0.3rem;
|
|
1324
|
+
right: 0.46rem;
|
|
1325
|
+
z-index: 2;
|
|
1326
|
+
appearance: none;
|
|
1327
|
+
display: inline-flex;
|
|
1328
|
+
align-items: center;
|
|
1329
|
+
justify-content: center;
|
|
1330
|
+
width: 1.75rem;
|
|
1331
|
+
height: 1.75rem;
|
|
1332
|
+
border: 1px solid
|
|
1333
|
+
color-mix(in oklab, var(--color-neutral-200) 80%, transparent);
|
|
1334
|
+
background: color-mix(in oklab, #fff 80%, transparent);
|
|
1335
|
+
backdrop-filter: blur(4px);
|
|
1336
|
+
color: color-mix(
|
|
1337
|
+
in oklab,
|
|
1338
|
+
var(--color-neutral-500) 80%,
|
|
1339
|
+
transparent
|
|
1340
|
+
);
|
|
1341
|
+
border-radius: 0.375rem;
|
|
1342
|
+
outline: none;
|
|
1343
|
+
box-shadow: none;
|
|
1344
|
+
cursor: pointer;
|
|
1345
|
+
transition: background-color 150ms ease, color 150ms ease,
|
|
1346
|
+
border-color 150ms ease, transform 120ms ease;
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
.dark .ask-ai-markdown .ask-ai-copy-code-button {
|
|
1350
|
+
border-color: color-mix(
|
|
1351
|
+
in oklab,
|
|
1352
|
+
var(--color-neutral-700) 50%,
|
|
1353
|
+
transparent
|
|
1354
|
+
);
|
|
1355
|
+
background: var(--rd-code-surface);
|
|
1356
|
+
color: var(--color-neutral-400);
|
|
1357
|
+
}
|
|
1358
|
+
|
|
1359
|
+
.ask-ai-markdown .ask-ai-copy-code-button:hover {
|
|
1360
|
+
background: var(--color-neutral-50);
|
|
1361
|
+
color: var(--color-neutral-600);
|
|
1362
|
+
}
|
|
1363
|
+
|
|
1364
|
+
.dark .ask-ai-markdown .ask-ai-copy-code-button:hover {
|
|
1365
|
+
background: var(--color-neutral-800);
|
|
1366
|
+
color: var(--color-neutral-200);
|
|
1367
|
+
}
|
|
1368
|
+
|
|
1369
|
+
.ask-ai-markdown .ask-ai-copy-code-button:active {
|
|
1370
|
+
transform: translateY(0.5px);
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
.ask-ai-markdown .ask-ai-copy-code-button:focus-visible,
|
|
1374
|
+
.dark .ask-ai-markdown .ask-ai-copy-code-button:focus-visible {
|
|
1375
|
+
outline: none;
|
|
1376
|
+
box-shadow: none;
|
|
1377
|
+
}
|
|
1378
|
+
|
|
1379
|
+
.ask-ai-markdown .ask-ai-copy-icon,
|
|
1380
|
+
.ask-ai-markdown .ask-ai-copy-check-icon {
|
|
1381
|
+
position: absolute;
|
|
1382
|
+
width: 14px;
|
|
1383
|
+
height: 14px;
|
|
1384
|
+
transition: transform 250ms cubic-bezier(0.22, 1, 0.36, 1),
|
|
1385
|
+
opacity 250ms cubic-bezier(0.22, 1, 0.36, 1);
|
|
1386
|
+
will-change: transform, opacity;
|
|
1387
|
+
}
|
|
1388
|
+
|
|
1389
|
+
.ask-ai-markdown .ask-ai-copy-icon {
|
|
1390
|
+
opacity: 1;
|
|
1391
|
+
transform: scale(1) rotate(0deg);
|
|
1392
|
+
background-color: currentColor;
|
|
1393
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Crect x='9' y='9' width='13' height='13' rx='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E");
|
|
1394
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Crect x='9' y='9' width='13' height='13' rx='2'/%3E%3Cpath d='M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1'/%3E%3C/svg%3E");
|
|
1395
|
+
-webkit-mask-repeat: no-repeat;
|
|
1396
|
+
mask-repeat: no-repeat;
|
|
1397
|
+
-webkit-mask-position: center;
|
|
1398
|
+
mask-position: center;
|
|
1399
|
+
-webkit-mask-size: contain;
|
|
1400
|
+
mask-size: contain;
|
|
1401
|
+
}
|
|
1402
|
+
|
|
1403
|
+
.ask-ai-markdown .ask-ai-copy-check-icon {
|
|
1404
|
+
opacity: 0;
|
|
1405
|
+
transform: scale(0.25) rotate(6deg);
|
|
1406
|
+
background-color: color-mix(
|
|
1407
|
+
in oklab,
|
|
1408
|
+
var(--color-green-700) 80%,
|
|
1409
|
+
transparent
|
|
1410
|
+
);
|
|
1411
|
+
-webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
|
|
1412
|
+
mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='black' stroke-width='2'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' d='M20 6L9 17l-5-5'/%3E%3C/svg%3E");
|
|
1413
|
+
-webkit-mask-repeat: no-repeat;
|
|
1414
|
+
mask-repeat: no-repeat;
|
|
1415
|
+
-webkit-mask-position: center;
|
|
1416
|
+
mask-position: center;
|
|
1417
|
+
-webkit-mask-size: contain;
|
|
1418
|
+
mask-size: contain;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1421
|
+
.dark .ask-ai-markdown .ask-ai-copy-check-icon {
|
|
1422
|
+
background-color: color-mix(
|
|
1423
|
+
in oklab,
|
|
1424
|
+
var(--color-green-400) 90%,
|
|
1425
|
+
transparent
|
|
1426
|
+
);
|
|
1427
|
+
}
|
|
1428
|
+
|
|
1429
|
+
.ask-ai-markdown .ask-ai-copy-code-button[data-copied="true"] .ask-ai-copy-icon {
|
|
1430
|
+
opacity: 0;
|
|
1431
|
+
transform: scale(0.5) rotate(-6deg);
|
|
1432
|
+
}
|
|
1433
|
+
|
|
1434
|
+
.ask-ai-markdown .ask-ai-copy-code-button[data-copied="true"] .ask-ai-copy-check-icon {
|
|
1435
|
+
opacity: 1;
|
|
1436
|
+
transform: scale(1.1) rotate(0deg);
|
|
1437
|
+
}
|
|
1438
|
+
|
|
1439
|
+
.ask-ai-markdown pre > code {
|
|
1440
|
+
display: block;
|
|
1441
|
+
width: 100%;
|
|
1442
|
+
min-width: 100%;
|
|
1443
|
+
background: transparent !important;
|
|
1444
|
+
white-space: inherit;
|
|
1445
|
+
word-break: normal;
|
|
1446
|
+
overflow-wrap: normal;
|
|
1447
|
+
overflow-x: auto;
|
|
1448
|
+
overflow-y: hidden;
|
|
1449
|
+
-webkit-overflow-scrolling: touch;
|
|
1450
|
+
scrollbar-width: none;
|
|
1451
|
+
-ms-overflow-style: none;
|
|
1452
|
+
}
|
|
1453
|
+
|
|
1454
|
+
.dark .ask-ai-markdown pre > code {
|
|
1455
|
+
scrollbar-width: none;
|
|
1456
|
+
}
|
|
1457
|
+
|
|
1458
|
+
.ask-ai-markdown pre[class*="language-"] {
|
|
1459
|
+
background: var(--color-neutral-50) !important;
|
|
1460
|
+
}
|
|
1461
|
+
|
|
1462
|
+
.ask-ai-markdown pre[class*="language-"] > code[class*="language-"] {
|
|
1463
|
+
background: transparent !important;
|
|
1464
|
+
}
|
|
1465
|
+
|
|
1466
|
+
.ask-ai-markdown :not(pre) > code[class*="language-"] {
|
|
1467
|
+
background: var(--color-neutral-100);
|
|
1468
|
+
}
|
|
1469
|
+
|
|
1470
|
+
.dark .ask-ai-markdown pre[class*="language-"] {
|
|
1471
|
+
background: var(--rd-code-surface) !important;
|
|
1472
|
+
border-color: var(--color-neutral-800) !important;
|
|
1473
|
+
}
|
|
1474
|
+
|
|
1475
|
+
.dark .ask-ai-markdown pre[class*="language-"] > code[class*="language-"] {
|
|
1476
|
+
background: transparent !important;
|
|
1477
|
+
}
|
|
1478
|
+
|
|
1479
|
+
.dark .ask-ai-markdown pre[class*="language-"],
|
|
1480
|
+
.dark .ask-ai-markdown code[class*="language-"] {
|
|
1481
|
+
color: var(--color-neutral-100);
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
.dark .ask-ai-markdown :not(pre) > code[class*="language-"] {
|
|
1485
|
+
background: var(--color-neutral-800);
|
|
1486
|
+
}
|
|
1487
|
+
|
|
1488
|
+
.dark .ask-ai-markdown .token.comment,
|
|
1489
|
+
.dark .ask-ai-markdown .token.prolog,
|
|
1490
|
+
.dark .ask-ai-markdown .token.doctype,
|
|
1491
|
+
.dark .ask-ai-markdown .token.cdata {
|
|
1492
|
+
color: #7f848e;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1495
|
+
.dark .ask-ai-markdown .token.punctuation {
|
|
1496
|
+
color: #abb2bf;
|
|
1497
|
+
}
|
|
1498
|
+
|
|
1499
|
+
.dark .ask-ai-markdown .token.property,
|
|
1500
|
+
.dark .ask-ai-markdown .token.tag,
|
|
1501
|
+
.dark .ask-ai-markdown .token.boolean,
|
|
1502
|
+
.dark .ask-ai-markdown .token.number,
|
|
1503
|
+
.dark .ask-ai-markdown .token.constant,
|
|
1504
|
+
.dark .ask-ai-markdown .token.symbol,
|
|
1505
|
+
.dark .ask-ai-markdown .token.deleted {
|
|
1506
|
+
color: #d19a66;
|
|
1507
|
+
}
|
|
1508
|
+
|
|
1509
|
+
.dark .ask-ai-markdown .token.selector,
|
|
1510
|
+
.dark .ask-ai-markdown .token.attr-name,
|
|
1511
|
+
.dark .ask-ai-markdown .token.string,
|
|
1512
|
+
.dark .ask-ai-markdown .token.char,
|
|
1513
|
+
.dark .ask-ai-markdown .token.builtin,
|
|
1514
|
+
.dark .ask-ai-markdown .token.inserted {
|
|
1515
|
+
color: #98c379;
|
|
1516
|
+
}
|
|
1517
|
+
|
|
1518
|
+
.dark .ask-ai-markdown .token.operator,
|
|
1519
|
+
.dark .ask-ai-markdown .token.entity,
|
|
1520
|
+
.dark .ask-ai-markdown .token.url,
|
|
1521
|
+
.dark .ask-ai-markdown .language-css .token.string,
|
|
1522
|
+
.dark .ask-ai-markdown .style .token.string {
|
|
1523
|
+
color: #56b6c2;
|
|
1524
|
+
}
|
|
1525
|
+
|
|
1526
|
+
.dark .ask-ai-markdown .token.atrule,
|
|
1527
|
+
.dark .ask-ai-markdown .token.attr-value,
|
|
1528
|
+
.dark .ask-ai-markdown .token.keyword {
|
|
1529
|
+
color: #c678dd;
|
|
1530
|
+
}
|
|
1531
|
+
|
|
1532
|
+
.dark .ask-ai-markdown .token.function,
|
|
1533
|
+
.dark .ask-ai-markdown .token.class-name {
|
|
1534
|
+
color: #e5c07b;
|
|
1535
|
+
}
|
|
1536
|
+
|
|
1537
|
+
.dark .ask-ai-markdown .token.regex,
|
|
1538
|
+
.dark .ask-ai-markdown .token.important,
|
|
1539
|
+
.dark .ask-ai-markdown .token.variable {
|
|
1540
|
+
color: #e06c75;
|
|
1541
|
+
}
|
|
1542
|
+
|
|
1543
|
+
.dark .ask-ai-markdown .token.attr-value > .token.punctuation.attr-equals,
|
|
1544
|
+
.dark
|
|
1545
|
+
.ask-ai-markdown
|
|
1546
|
+
.token.special-attr
|
|
1547
|
+
> .token.attr-value
|
|
1548
|
+
> .token.value.css {
|
|
1549
|
+
color: #abb2bf;
|
|
1550
|
+
}
|
|
1551
|
+
|
|
1552
|
+
.dark .ask-ai-markdown .language-css .token.selector {
|
|
1553
|
+
color: #d19a66;
|
|
1554
|
+
}
|
|
1555
|
+
|
|
1556
|
+
.dark .ask-ai-markdown .language-css .token.property {
|
|
1557
|
+
color: #abb2bf;
|
|
1558
|
+
}
|
|
1559
|
+
|
|
1560
|
+
.dark .ask-ai-markdown .language-css .token.function,
|
|
1561
|
+
.dark .ask-ai-markdown .language-css .token.url > .token.function {
|
|
1562
|
+
color: #56b6c2;
|
|
1563
|
+
}
|
|
1564
|
+
|
|
1565
|
+
.dark .ask-ai-markdown .language-css .token.url > .token.string.url {
|
|
1566
|
+
color: #98c379;
|
|
1567
|
+
}
|
|
1568
|
+
|
|
1569
|
+
.dark .ask-ai-markdown .language-css .token.important,
|
|
1570
|
+
.dark .ask-ai-markdown .language-css .token.atrule .token.rule {
|
|
1571
|
+
color: #c678dd;
|
|
1572
|
+
}
|
|
1573
|
+
|
|
1574
|
+
.dark .ask-ai-markdown .language-javascript .token.operator {
|
|
1575
|
+
color: #c678dd;
|
|
1576
|
+
}
|
|
1577
|
+
|
|
1578
|
+
.dark
|
|
1579
|
+
.ask-ai-markdown
|
|
1580
|
+
.language-javascript
|
|
1581
|
+
.token.template-string
|
|
1582
|
+
> .token.interpolation
|
|
1583
|
+
> .token.interpolation-punctuation.punctuation {
|
|
1584
|
+
color: #e06c75;
|
|
1585
|
+
}
|
|
1586
|
+
|
|
1587
|
+
.dark .ask-ai-markdown .language-json .token.operator {
|
|
1588
|
+
color: #abb2bf;
|
|
1589
|
+
}
|
|
1590
|
+
|
|
1591
|
+
.dark .ask-ai-markdown .language-json .token.null.keyword {
|
|
1592
|
+
color: #d19a66;
|
|
1593
|
+
}
|
|
1594
|
+
|
|
1595
|
+
.dark .ask-ai-markdown .language-markdown .token.url,
|
|
1596
|
+
.dark .ask-ai-markdown .language-markdown .token.url > .token.operator,
|
|
1597
|
+
.dark
|
|
1598
|
+
.ask-ai-markdown
|
|
1599
|
+
.language-markdown
|
|
1600
|
+
.token.url-reference.url
|
|
1601
|
+
> .token.string {
|
|
1602
|
+
color: #abb2bf;
|
|
1603
|
+
}
|
|
1604
|
+
|
|
1605
|
+
.dark .ask-ai-markdown .language-markdown .token.url > .token.content {
|
|
1606
|
+
color: #56b6c2;
|
|
1607
|
+
}
|
|
1608
|
+
|
|
1609
|
+
.dark .ask-ai-markdown .language-markdown .token.url > .token.url,
|
|
1610
|
+
.dark .ask-ai-markdown .language-markdown .token.url-reference.url {
|
|
1611
|
+
color: #98c379;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1614
|
+
.dark .ask-ai-markdown .language-markdown .token.blockquote.punctuation,
|
|
1615
|
+
.dark .ask-ai-markdown .language-markdown .token.hr.punctuation {
|
|
1616
|
+
color: #7f848e;
|
|
1617
|
+
}
|
|
1618
|
+
|
|
1619
|
+
.dark .ask-ai-markdown .language-markdown .token.code-snippet {
|
|
1620
|
+
color: #98c379;
|
|
1621
|
+
}
|
|
1622
|
+
|
|
1623
|
+
.dark .ask-ai-markdown .language-markdown .token.bold .token.content {
|
|
1624
|
+
color: #e5c07b;
|
|
1625
|
+
}
|
|
1626
|
+
|
|
1627
|
+
.dark .ask-ai-markdown .language-markdown .token.italic .token.content {
|
|
1628
|
+
color: #c678dd;
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
.dark .ask-ai-markdown .language-markdown .token.strike .token.content,
|
|
1632
|
+
.dark
|
|
1633
|
+
.ask-ai-markdown
|
|
1634
|
+
.language-markdown
|
|
1635
|
+
.token.strike
|
|
1636
|
+
.token.punctuation,
|
|
1637
|
+
.dark .ask-ai-markdown .language-markdown .token.list.punctuation,
|
|
1638
|
+
.dark
|
|
1639
|
+
.ask-ai-markdown
|
|
1640
|
+
.language-markdown
|
|
1641
|
+
.token.title.important
|
|
1642
|
+
> .token.punctuation {
|
|
1643
|
+
color: #e06c75;
|
|
1644
|
+
}
|
|
1645
|
+
|
|
1646
|
+
.ask-ai-markdown pre > code::-webkit-scrollbar {
|
|
1647
|
+
width: 0;
|
|
1648
|
+
height: 0;
|
|
1649
|
+
display: none;
|
|
1650
|
+
}
|
|
1651
|
+
|
|
1652
|
+
.assistant-embed-send-button {
|
|
1653
|
+
--assistant-embed-send-theme: var(
|
|
1654
|
+
--assistant-embed-send-theme-light
|
|
1655
|
+
);
|
|
1656
|
+
--assistant-embed-send-foreground: var(
|
|
1657
|
+
--assistant-embed-send-foreground-light
|
|
1658
|
+
);
|
|
1659
|
+
color: var(--assistant-embed-send-foreground);
|
|
1660
|
+
background: linear-gradient(
|
|
1661
|
+
to bottom,
|
|
1662
|
+
color-mix(
|
|
1663
|
+
in oklab,
|
|
1664
|
+
var(--assistant-embed-send-theme) 88%,
|
|
1665
|
+
white
|
|
1666
|
+
),
|
|
1667
|
+
color-mix(
|
|
1668
|
+
in oklab,
|
|
1669
|
+
var(--assistant-embed-send-theme) 90%,
|
|
1670
|
+
black
|
|
1671
|
+
)
|
|
1672
|
+
);
|
|
1673
|
+
}
|
|
1674
|
+
|
|
1675
|
+
.dark .assistant-embed-send-button,
|
|
1676
|
+
[data-theme="dark"] .assistant-embed-send-button {
|
|
1677
|
+
--assistant-embed-send-theme: var(
|
|
1678
|
+
--assistant-embed-send-theme-dark
|
|
1679
|
+
);
|
|
1680
|
+
--assistant-embed-send-foreground: var(
|
|
1681
|
+
--assistant-embed-send-foreground-dark
|
|
1682
|
+
);
|
|
1683
|
+
}
|
|
1684
|
+
|
|
1685
|
+
@media (prefers-reduced-motion: reduce) {
|
|
1686
|
+
.assistant-empty-state-item {
|
|
1687
|
+
animation: none;
|
|
1688
|
+
}
|
|
1689
|
+
}
|
|
1690
|
+
`}</style>
|
|
1691
|
+
</div>
|
|
1692
|
+
);
|
|
1693
|
+
}
|