@gntik-ai/chat 0.5.0 → 0.9.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/dist/index.js CHANGED
@@ -1,10 +1,1015 @@
1
1
  'use client';
2
- import { Avatar, Badge, Button, Collapsible, CollapsiblePanel, CollapsibleTrigger, IconButton, Popover, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Radio, RadioGroup, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectTrigger, SimpleTooltip, Spinner, StatusTag, Textarea, TooltipProvider, cn, fileKind, getInitials, interpolate, useI18n } from "@gntik-ai/ui";
3
- import { AlertCircle, ArrowDown, ArrowUp, Check, CircleAlert, Copy, ExternalLink, File, FileArchive, FileAudio, FileCode, FileImage, FileSpreadsheet, FileText, FileVideo, Globe, Paperclip, RefreshCw, RotateCcw, Sparkles, Square, ThumbsDown, ThumbsUp, Upload, Wrench, X } from "lucide-react";
2
+ import { Children, Component, createRef, isValidElement, memo, useCallback, useEffect, useId, useRef, useState } from "react";
3
+ import { Alert, Avatar, Badge, Button, Card, Checkbox, Collapsible, CollapsiblePanel, CollapsibleTrigger, Heading, IconButton, Input, Link, Popover, PopoverContent, PopoverDescription, PopoverTitle, PopoverTrigger, Radio, RadioGroup, Select, SelectContent, SelectGroup, SelectGroupLabel, SelectItem, SelectTrigger, SimpleSelect, SimpleTooltip, Spinner, Stack, StatusTag, Text, Textarea, TooltipProvider, cn, fileKind, getInitials, interpolate, useI18n } from "@gntik-ai/ui";
4
4
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
5
- import { Children, isValidElement, memo, useCallback, useEffect, useId, useRef, useState } from "react";
5
+ import { AlertCircle, ArrowDown, ArrowUp, Check, CircleAlert, Copy, ExternalLink, File, FileArchive, FileAudio, FileCode, FileImage, FileSpreadsheet, FileText, FileVideo, Globe, Paperclip, RefreshCw, RotateCcw, Sparkles, Square, ThumbsDown, ThumbsUp, Upload, Wrench, X } from "lucide-react";
6
6
  import ReactMarkdown, { defaultUrlTransform } from "react-markdown";
7
7
  import remarkGfm from "remark-gfm";
8
+ //#region src/utils/i18n.ts
9
+ /**
10
+ * English fallbacks for the chat keys added after the first `@gntik-ai/ui` release. The
11
+ * strings live in the ui catalogs (en + es); these keep the pack readable when it runs
12
+ * against an older `@gntik-ai/ui` whose catalog does not have them yet.
13
+ */
14
+ const FALLBACK = {
15
+ "chat.surfaceExampleTitle": "Review your details",
16
+ "chat.surfaceExampleName": "Name",
17
+ "chat.surfaceExampleSubmit": "Submit",
18
+ "chat.surfaceExampleChoice": "Language",
19
+ "chat.surfaceExampleEnglish": "English",
20
+ "chat.surfaceExampleSpanish": "Spanish",
21
+ "chat.surfaceExampleNotify": "Send updates",
22
+ "chat.surfaceExampleBadge": "Draft",
23
+ "chat.surfaceExampleNotice": "Check the information before submitting.",
24
+ "chat.surfaceExampleStatus": "Submitted for {name}",
25
+ "chat.surfaceExamplePatch": "Update name",
26
+ "chat.surfaceExampleRemove": "Remove content",
27
+ "chat.surfaceExampleReset": "Reset",
28
+ "chat.surfaceExampleUnsupported": "Show unsupported content",
29
+ "chat.surfaceExampleRejected": "Unsupported content was rejected.",
30
+ "chat.surfaceExampleLink": "Read the documentation",
31
+ "chat.surfaceUnsupported": "This content cannot be shown",
32
+ "chat.surfaceAgentContent": "Agent content",
33
+ "chat.typing": "{author} is typing",
34
+ "chat.feedback": "Response feedback",
35
+ "chat.feedbackTitle": "What went wrong?",
36
+ "chat.feedbackReason": "Reason",
37
+ "chat.feedbackComment": "Comment (optional)",
38
+ "chat.feedbackPlaceholder": "Tell us more…",
39
+ "chat.feedbackSubmit": "Send feedback",
40
+ "chat.feedbackThanks": "Thanks for your feedback",
41
+ "chat.feedbackCleared": "Feedback removed",
42
+ "chat.reasonInaccurate": "Inaccurate",
43
+ "chat.reasonUnhelpful": "Not helpful",
44
+ "chat.reasonIncomplete": "Incomplete",
45
+ "chat.reasonUnsafe": "Harmful or unsafe",
46
+ "chat.reasonOther": "Other",
47
+ "chat.regenerate": "Regenerate",
48
+ "chat.uploading": "Uploading {name}",
49
+ "chat.uploadFailed": "Upload failed",
50
+ "chat.retryUpload": "Retry uploading {name}",
51
+ "chat.dropFiles": "Drop files to attach",
52
+ "chat.fileTooLarge": "{name} is larger than {max}",
53
+ "chat.fileTypeNotAllowed": "{name} is not an allowed file type",
54
+ "chat.tooManyFiles": "You can attach up to {max} files",
55
+ "chat.model": "Model",
56
+ "chat.selectModel": "Select a model",
57
+ "chat.capabilityVision": "Vision",
58
+ "chat.capabilityTools": "Tools",
59
+ "chat.capabilityReasoning": "Reasoning",
60
+ "chat.capabilityFiles": "Files",
61
+ "chat.capabilityWeb": "Web search",
62
+ "chat.capabilityAudio": "Audio",
63
+ "chat.contextWindow": "{size} context",
64
+ "chat.unavailable": "Unavailable"
65
+ };
66
+ /** `useI18n()` plus `tc()`, a translator for the newer chat keys with English fallbacks. */
67
+ function useChatI18n() {
68
+ const i18n = useI18n();
69
+ const tc = (key, vars) => {
70
+ const out = i18n.t(key, vars);
71
+ return out === key ? interpolate(FALLBACK[key], vars, i18n.locale) : out;
72
+ };
73
+ return {
74
+ ...i18n,
75
+ tc
76
+ };
77
+ }
78
+ //#endregion
79
+ //#region src/AgentSurface/SurfaceFocusBoundary.tsx
80
+ const focusable = "button, a[href], input, select, textarea, [tabindex], [contenteditable=\"true\"]";
81
+ function candidates(element) {
82
+ return [element, ...element.querySelectorAll(focusable)].filter((item) => item instanceof HTMLElement && item.matches(focusable) && item.tabIndex >= 0 && !item.matches(":disabled, [aria-disabled=\"true\"], input[type=\"hidden\"]") && !item.closest("[hidden], [inert], [aria-hidden=\"true\"]") && getComputedStyle(item).display !== "none" && getComputedStyle(item).visibility !== "hidden");
83
+ }
84
+ /** Capture focus before React removes DOM; restore after all deleted surfaces are gone. */
85
+ var SurfaceFocusBoundary = class extends Component {
86
+ host = createRef();
87
+ getSnapshotBeforeUpdate() {
88
+ const host = this.host.current;
89
+ const active = document.activeElement;
90
+ if (!host || !(active instanceof HTMLElement)) return null;
91
+ let frame = host.contains(active) ? active.closest("[data-agent-surface]") : null;
92
+ if (!frame) {
93
+ for (const trigger of host.querySelectorAll("[aria-controls]")) if ((trigger.getAttribute("aria-controls") ?? "").split(/\s+/).some((id) => document.getElementById(id)?.contains(active))) {
94
+ frame = trigger.closest("[data-agent-surface]");
95
+ break;
96
+ }
97
+ }
98
+ if (!frame || this.props.surfaceIds.has(frame.dataset.agentSurface ?? "")) return null;
99
+ const next = [];
100
+ for (const start of [frame, host]) {
101
+ let sibling = start.nextElementSibling;
102
+ while (sibling) {
103
+ next.push(sibling);
104
+ sibling = sibling.nextElementSibling;
105
+ }
106
+ }
107
+ return { siblings: next };
108
+ }
109
+ componentDidUpdate(_previous, _state, snapshot) {
110
+ if (!snapshot) return;
111
+ const restore = () => {
112
+ const next = snapshot.siblings.filter((element) => element.isConnected).flatMap(candidates);
113
+ const composers = [...document.querySelectorAll("textarea")].flatMap(candidates);
114
+ for (const target of [
115
+ ...next,
116
+ ...composers,
117
+ this.host.current
118
+ ]) {
119
+ if (!target) continue;
120
+ target.focus();
121
+ if (document.activeElement === target) return;
122
+ }
123
+ };
124
+ restore();
125
+ queueMicrotask(() => {
126
+ if (document.activeElement === document.body || document.activeElement === this.host.current) restore();
127
+ });
128
+ }
129
+ render() {
130
+ if (!this.props.visible) return null;
131
+ return /* @__PURE__ */ jsx("div", {
132
+ ref: this.host,
133
+ tabIndex: -1,
134
+ role: "group",
135
+ "aria-label": this.props.label,
136
+ className: "space-y-3 outline-focus-ring",
137
+ children: this.props.children
138
+ });
139
+ }
140
+ };
141
+ //#endregion
142
+ //#region src/AgentSurface/pointer.ts
143
+ /** RFC 6901 paths. Throws on malformed paths rather than guessing a model key. */
144
+ function segments(path) {
145
+ if (path === "") return [];
146
+ if (!path.startsWith("/") || /~(?![01])/u.test(path)) throw new Error("Invalid JSON Pointer");
147
+ const keys = path.slice(1).split("/").map((key) => key.replace(/~1/g, "/").replace(/~0/g, "~"));
148
+ if (keys.length > 64 || keys.some((key) => [
149
+ "__proto__",
150
+ "prototype",
151
+ "constructor"
152
+ ].includes(key))) throw new Error("Unsafe JSON Pointer");
153
+ return keys;
154
+ }
155
+ function own(value, key) {
156
+ return value !== null && typeof value === "object" && Object.hasOwn(value, key) ? value[key] : void 0;
157
+ }
158
+ function getPointer(model, path) {
159
+ return segments(path).reduce(own, model);
160
+ }
161
+ /** Copy every ancestor; never mutate the model supplied by a consumer. */
162
+ function setPointer(model, path, value) {
163
+ const keys = segments(path);
164
+ function write(current, index) {
165
+ if (index === keys.length) return value;
166
+ const key = keys[index];
167
+ if (Array.isArray(current)) {
168
+ if (!/^(0|[1-9]\d*)$/.test(key) || Number(key) > current.length) throw new Error("Invalid array index");
169
+ const copy = [...current];
170
+ copy[Number(key)] = write(own(current, key), index + 1);
171
+ return copy;
172
+ }
173
+ const copy = current && typeof current === "object" ? { ...current } : {};
174
+ copy[key] = write(own(current, key), index + 1);
175
+ return copy;
176
+ }
177
+ return write(model, 0);
178
+ }
179
+ //#endregion
180
+ //#region src/AgentSurface/protocol.ts
181
+ /** Supported command envelope. No v0.8 adaptation is performed. */
182
+ const A2UI_VERSION = "v0.9";
183
+ const commands = [
184
+ "createSurface",
185
+ "updateComponents",
186
+ "updateDataModel",
187
+ "deleteSurface"
188
+ ];
189
+ const identifier = (value) => typeof value === "string" && value.length > 0 && value.length <= 128;
190
+ const record = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
191
+ /** JSON-only, bounded input. In particular, reject cyclic JS objects and prototype keys. */
192
+ function validJson(value) {
193
+ let budget = 1e4;
194
+ const ancestors = /* @__PURE__ */ new Set();
195
+ function visit(item, depth) {
196
+ if (--budget < 0 || depth > 64) return false;
197
+ if (item === null || typeof item === "boolean") return true;
198
+ if (typeof item === "number") return Number.isFinite(item);
199
+ if (typeof item === "string") return item.length <= 65536;
200
+ if (typeof item !== "object" || ancestors.has(item)) return false;
201
+ if (!Array.isArray(item) && ![Object.prototype, null].includes(Object.getPrototypeOf(item))) return false;
202
+ ancestors.add(item);
203
+ const valid = Object.entries(item).every(([key, child]) => ![
204
+ "__proto__",
205
+ "prototype",
206
+ "constructor"
207
+ ].includes(key) && visit(child, depth + 1));
208
+ ancestors.delete(item);
209
+ return valid;
210
+ }
211
+ return visit(value, 0);
212
+ }
213
+ function parseMessage(input) {
214
+ const source = record(input) ? input : {};
215
+ const declared = commands.filter((key) => Object.hasOwn(source, key));
216
+ const command = declared[0];
217
+ const payload = command && record(source[command]) ? source[command] : {};
218
+ const surfaceId = identifier(payload.surfaceId) ? payload.surfaceId : "";
219
+ const error = (code) => ({ error: {
220
+ code,
221
+ surfaceId
222
+ } });
223
+ if (source.version !== "v0.9") return error("version");
224
+ if (declared.length !== 1 || !surfaceId || !validJson(input)) return error("message");
225
+ if (command === "createSurface") {
226
+ if (payload.catalogId !== void 0 && !identifier(payload.catalogId)) return error("message");
227
+ return { message: {
228
+ version: A2UI_VERSION,
229
+ createSurface: {
230
+ surfaceId,
231
+ ...typeof payload.catalogId === "string" ? { catalogId: payload.catalogId } : {}
232
+ }
233
+ } };
234
+ }
235
+ if (command === "deleteSurface") return { message: {
236
+ version: A2UI_VERSION,
237
+ deleteSurface: { surfaceId }
238
+ } };
239
+ if (command === "updateDataModel") {
240
+ if (!Object.hasOwn(payload, "value") || payload.path !== void 0 && typeof payload.path !== "string") return error("message");
241
+ return { message: {
242
+ version: A2UI_VERSION,
243
+ updateDataModel: {
244
+ surfaceId,
245
+ path: typeof payload.path === "string" ? payload.path : "",
246
+ value: payload.value
247
+ }
248
+ } };
249
+ }
250
+ if (!Array.isArray(payload.components) || payload.components.length > 256) return error("message");
251
+ const nodes = [];
252
+ const ids = /* @__PURE__ */ new Set();
253
+ for (const node of payload.components) {
254
+ if (!record(node) || !identifier(node.id) || !identifier(node.component) || ids.has(node.id)) return error("message");
255
+ if (node.children !== void 0 && (!Array.isArray(node.children) || node.children.length > 256 || !node.children.every(identifier))) return error("message");
256
+ ids.add(node.id);
257
+ nodes.push(node);
258
+ }
259
+ return { message: {
260
+ version: A2UI_VERSION,
261
+ updateComponents: {
262
+ surfaceId,
263
+ components: nodes
264
+ }
265
+ } };
266
+ }
267
+ /** Missing children are allowed while streaming; repeated parents, cycles and deep trees are not. */
268
+ function validGraph(nodes) {
269
+ if (nodes.size > 256) return false;
270
+ const parents = /* @__PURE__ */ new Set();
271
+ for (const node of nodes.values()) for (const child of node.children ?? []) {
272
+ if (parents.has(child)) return false;
273
+ parents.add(child);
274
+ }
275
+ const visiting = /* @__PURE__ */ new Set();
276
+ function visit(id, depth) {
277
+ if (depth > 32 || visiting.has(id)) return false;
278
+ visiting.add(id);
279
+ const valid = (nodes.get(id)?.children ?? []).every((child) => visit(child, depth + 1));
280
+ visiting.delete(id);
281
+ return valid;
282
+ }
283
+ return [...nodes.keys()].every((id) => visit(id, 0));
284
+ }
285
+ //#endregion
286
+ //#region src/AgentSurface/stream.ts
287
+ const emptyStream = () => ({
288
+ messages: [],
289
+ deleted: false,
290
+ surfaces: /* @__PURE__ */ new Map(),
291
+ blocked: /* @__PURE__ */ new Set(),
292
+ errors: []
293
+ });
294
+ function consume(previous, messages) {
295
+ const base = previous.messages.every((message, index) => messages[index] === message) ? previous : emptyStream();
296
+ const state = {
297
+ messages,
298
+ deleted: base.deleted,
299
+ surfaces: new Map(base.surfaces),
300
+ blocked: new Set(base.blocked),
301
+ errors: [...base.errors]
302
+ };
303
+ for (const input of messages.slice(base.messages.length)) {
304
+ const parsed = parseMessage(input);
305
+ if ("error" in parsed) {
306
+ if (parsed.error.code === "version") {
307
+ state.blocked.add(parsed.error.surfaceId);
308
+ state.surfaces.delete(parsed.error.surfaceId);
309
+ if (previous.surfaces.has(parsed.error.surfaceId)) state.deleted = true;
310
+ }
311
+ state.errors.push(parsed.error);
312
+ continue;
313
+ }
314
+ const message = parsed.message;
315
+ const { surfaceId } = "createSurface" in message ? message.createSurface : "updateComponents" in message ? message.updateComponents : "updateDataModel" in message ? message.updateDataModel : message.deleteSurface;
316
+ if (state.blocked.has(surfaceId)) continue;
317
+ if ("createSurface" in message) {
318
+ if (!state.surfaces.has(surfaceId)) {
319
+ if (state.surfaces.size >= 32) state.errors.push({
320
+ code: "message",
321
+ surfaceId
322
+ });
323
+ else state.surfaces.set(surfaceId, {
324
+ nodes: /* @__PURE__ */ new Map(),
325
+ model: {}
326
+ });
327
+ }
328
+ continue;
329
+ }
330
+ const surface = state.surfaces.get(surfaceId);
331
+ if (!surface) {
332
+ state.errors.push({
333
+ code: "message",
334
+ surfaceId
335
+ });
336
+ continue;
337
+ }
338
+ if ("updateComponents" in message) {
339
+ const nodes = new Map(surface.nodes);
340
+ for (const node of message.updateComponents.components) {
341
+ const old = nodes.get(node.id);
342
+ nodes.set(node.id, {
343
+ ...old?.component === node.component ? old : {},
344
+ ...node
345
+ });
346
+ }
347
+ const invalid = !validGraph(nodes);
348
+ if (invalid) state.errors.push({
349
+ code: "graph",
350
+ surfaceId
351
+ });
352
+ state.surfaces.set(surfaceId, {
353
+ ...surface,
354
+ nodes: invalid ? surface.nodes : nodes,
355
+ invalid
356
+ });
357
+ }
358
+ if ("updateDataModel" in message) try {
359
+ const { path = "", value } = message.updateDataModel;
360
+ state.surfaces.set(surfaceId, {
361
+ ...surface,
362
+ model: setPointer(surface.model, path, value)
363
+ });
364
+ } catch {
365
+ state.errors.push({
366
+ code: "binding",
367
+ surfaceId
368
+ });
369
+ }
370
+ if ("deleteSurface" in message) {
371
+ state.surfaces.delete(surfaceId);
372
+ state.deleted = true;
373
+ }
374
+ }
375
+ state.errors = state.errors.filter((error) => !state.blocked.has(error.surfaceId) || error.code === "version");
376
+ return state;
377
+ }
378
+ //#endregion
379
+ //#region src/utils/useCopy.ts
380
+ /**
381
+ * Copies text to the clipboard and exposes a short-lived `copied` flag for the button label.
382
+ * The reset timer is cleared on unmount and on every new copy.
383
+ */
384
+ function useCopy(resetMs = 1600) {
385
+ const [copied, setCopied] = useState(false);
386
+ const timer = useRef(void 0);
387
+ useEffect(() => () => clearTimeout(timer.current), []);
388
+ return {
389
+ copied,
390
+ copy: useCallback(async (text) => {
391
+ try {
392
+ await navigator.clipboard?.writeText(text);
393
+ } catch {}
394
+ setCopied(true);
395
+ clearTimeout(timer.current);
396
+ timer.current = setTimeout(() => setCopied(false), resetMs);
397
+ }, [resetMs])
398
+ };
399
+ }
400
+ //#endregion
401
+ //#region src/utils/focus.ts
402
+ /** Focus ring shared by every interactive element in the pack (brand rule). */
403
+ const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring";
404
+ //#endregion
405
+ //#region src/Markdown/markdown.variants.ts
406
+ /**
407
+ * Blinking caret drawn after the last block while text streams in. Pure CSS (an ::after on the
408
+ * last child), so it follows paragraphs, list items and code without extra DOM.
409
+ */
410
+ const streamingCaretClass = [
411
+ "[&>:last-child]:after:ms-0.5 [&>:last-child]:after:inline-block [&>:last-child]:after:h-[1.05em] [&>:last-child]:after:w-[2px]",
412
+ "[&>:last-child]:after:translate-y-[2px] [&>:last-child]:after:bg-primary [&>:last-child]:after:content-['']",
413
+ "[&>:last-child]:after:animate-pulse motion-reduce:[&>:last-child]:after:animate-none"
414
+ ].join(" ");
415
+ /** Element classes for Markdown (token colours only). */
416
+ const markdownStyles = {
417
+ root: "min-w-0 text-[14px] leading-[1.7] text-foreground [overflow-wrap:anywhere] [&>:first-child]:mt-0 [&>:last-child]:mb-0",
418
+ h1: "mt-5 mb-2 text-[18px] font-semibold tracking-tight",
419
+ h2: "mt-5 mb-2 text-[16px] font-semibold tracking-tight",
420
+ h3: "mt-4 mb-1.5 text-[14.5px] font-semibold",
421
+ h4: "mt-4 mb-1.5 text-[13.5px] font-semibold text-muted-foreground",
422
+ p: "my-2.5",
423
+ ul: "my-2.5 list-disc space-y-1 ps-5 marker:text-muted-foreground",
424
+ ol: "my-2.5 list-decimal space-y-1 ps-5 marker:text-muted-foreground",
425
+ li: "ps-1 [&>ul]:my-1 [&>ol]:my-1 [&.task-list-item]:list-none [&.task-list-item]:-ms-5",
426
+ blockquote: "my-3 border-s-2 border-primary/50 ps-3.5 text-muted-foreground",
427
+ hr: "my-5 border-border",
428
+ link: `rounded-sm font-medium text-primary-text underline decoration-primary/40 underline-offset-2 hover:decoration-primary ${focusRing}`,
429
+ linkIcon: "ms-0.5 inline-block align-[-1px]",
430
+ inlineCode: "rounded-md border border-border bg-secondary/60 px-1.5 py-px font-mono text-[12.5px]",
431
+ tableWrap: "my-3 overflow-x-auto rounded-lg border border-border",
432
+ table: "w-full border-collapse text-start text-[13px]",
433
+ th: "border-b border-border bg-secondary/50 px-3 py-2 font-semibold",
434
+ td: "border-b border-border px-3 py-2 align-top [tr:last-child_&]:border-b-0",
435
+ checkbox: "me-1.5 align-[-2px] accent-primary",
436
+ image: "text-muted-foreground italic",
437
+ code: {
438
+ root: "my-3 overflow-hidden rounded-lg border border-border bg-secondary/40",
439
+ header: "flex h-9 items-center justify-between border-b border-border bg-secondary/50 ps-3 pe-1.5",
440
+ lang: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
441
+ pre: "overflow-x-auto p-3 font-mono text-[12.5px] leading-6"
442
+ }
443
+ };
444
+ //#endregion
445
+ //#region src/Markdown/CodeBlock.tsx
446
+ const s$1 = markdownStyles.code;
447
+ /** Fenced code with a language label and a copy button. */
448
+ function CodeBlock({ code, language, className }) {
449
+ const { copied, copy } = useCopy();
450
+ const { t } = useI18n();
451
+ return /* @__PURE__ */ jsxs("div", {
452
+ className: className ? `${s$1.root} ${className}` : s$1.root,
453
+ "data-language": language,
454
+ children: [/* @__PURE__ */ jsxs("div", {
455
+ className: s$1.header,
456
+ children: [/* @__PURE__ */ jsx("span", {
457
+ className: s$1.lang,
458
+ children: language || "text"
459
+ }), /* @__PURE__ */ jsx(Button, {
460
+ size: "sm",
461
+ variant: "ghost",
462
+ icon: copied ? Check : Copy,
463
+ "aria-label": copied ? t("chat.copiedCode") : t("chat.copyCode"),
464
+ className: "h-7 px-2",
465
+ onClick: () => void copy(code),
466
+ children: copied ? t("common.copied") : t("common.copy")
467
+ })]
468
+ }), /* @__PURE__ */ jsx("pre", {
469
+ className: s$1.pre,
470
+ children: /* @__PURE__ */ jsx("code", { children: code })
471
+ })]
472
+ });
473
+ }
474
+ //#endregion
475
+ //#region src/Markdown/Markdown.tsx
476
+ const SAFE_PROTOCOL = /^(https?|mailto|tel):/i;
477
+ /**
478
+ * URL policy: relative URLs, anchors and http(s)/mailto/tel only. Everything else
479
+ * (javascript:, data:, vbscript:, …) is dropped, on top of react-markdown's own transform.
480
+ */
481
+ function safeUrl(url) {
482
+ const cleaned = defaultUrlTransform(url);
483
+ if (!cleaned) return "";
484
+ const compact = Array.from(cleaned).filter((c) => c.charCodeAt(0) > 32).join("");
485
+ if (/^[a-z][a-z0-9+.-]*:/i.test(compact) && !SAFE_PROTOCOL.test(compact)) return "";
486
+ return cleaned;
487
+ }
488
+ /**
489
+ * Makes partial streamed markdown render sanely: a fence that has opened but not closed is
490
+ * closed at the end, so the tail renders as code instead of flashing as paragraphs.
491
+ */
492
+ function repairStreamingMarkdown(text) {
493
+ const fences = text.match(/^ {0,3}(`{3,}|~{3,})/gm) ?? [];
494
+ if (fences.length % 2 === 0) return text;
495
+ const opener = (fences[fences.length - 1] ?? "```").trim();
496
+ return `${text}${text.endsWith("\n") ? "" : "\n"}${opener}`;
497
+ }
498
+ const isExternal = (href) => /^https?:\/\//i.test(href);
499
+ /** Drops react-markdown's `node` (hast) prop before spreading onto a DOM element. */
500
+ /** Screen-reader note on links that open a new tab. */
501
+ function NewTabHint() {
502
+ const { t } = useI18n();
503
+ return /* @__PURE__ */ jsxs("span", {
504
+ className: "sr-only",
505
+ children: [" ", t("link.external")]
506
+ });
507
+ }
508
+ /** Remote images can track readers and break layout mid-stream: show them as links. */
509
+ function ImageLink({ href, alt }) {
510
+ const { t } = useI18n();
511
+ const text = t("chat.image", { alt: alt || t("chat.untitled") });
512
+ return href ? /* @__PURE__ */ jsxs("a", {
513
+ href,
514
+ target: "_blank",
515
+ rel: "noopener noreferrer nofollow",
516
+ className: markdownStyles.link,
517
+ children: [text, /* @__PURE__ */ jsx(NewTabHint, {})]
518
+ }) : /* @__PURE__ */ jsx("span", {
519
+ className: markdownStyles.image,
520
+ children: text
521
+ });
522
+ }
523
+ function TaskCheckbox({ checked }) {
524
+ const { t } = useI18n();
525
+ return /* @__PURE__ */ jsx("input", {
526
+ type: "checkbox",
527
+ checked,
528
+ readOnly: true,
529
+ disabled: true,
530
+ "aria-label": checked ? t("chat.taskDone") : t("chat.taskNotDone"),
531
+ className: markdownStyles.checkbox
532
+ });
533
+ }
534
+ function omitNode(props) {
535
+ const { node, ...rest } = props;
536
+ return rest;
537
+ }
538
+ const components = {
539
+ h1: (p) => /* @__PURE__ */ jsx("h1", {
540
+ ...omitNode(p),
541
+ className: markdownStyles.h1
542
+ }),
543
+ h2: (p) => /* @__PURE__ */ jsx("h2", {
544
+ ...omitNode(p),
545
+ className: markdownStyles.h2
546
+ }),
547
+ h3: (p) => /* @__PURE__ */ jsx("h3", {
548
+ ...omitNode(p),
549
+ className: markdownStyles.h3
550
+ }),
551
+ h4: (p) => /* @__PURE__ */ jsx("h4", {
552
+ ...omitNode(p),
553
+ className: markdownStyles.h4
554
+ }),
555
+ h5: (p) => /* @__PURE__ */ jsx("h4", {
556
+ ...omitNode(p),
557
+ className: markdownStyles.h4
558
+ }),
559
+ h6: (p) => /* @__PURE__ */ jsx("h4", {
560
+ ...omitNode(p),
561
+ className: markdownStyles.h4
562
+ }),
563
+ p: (p) => /* @__PURE__ */ jsx("p", {
564
+ ...omitNode(p),
565
+ className: markdownStyles.p
566
+ }),
567
+ ul: (p) => /* @__PURE__ */ jsx("ul", {
568
+ ...omitNode(p),
569
+ className: cn(markdownStyles.ul, p.className === "contains-task-list" && "ps-1")
570
+ }),
571
+ ol: (p) => /* @__PURE__ */ jsx("ol", {
572
+ ...omitNode(p),
573
+ className: markdownStyles.ol
574
+ }),
575
+ li: (p) => /* @__PURE__ */ jsx("li", {
576
+ ...omitNode(p),
577
+ className: cn(markdownStyles.li, p.className)
578
+ }),
579
+ blockquote: (p) => /* @__PURE__ */ jsx("blockquote", {
580
+ ...omitNode(p),
581
+ className: markdownStyles.blockquote
582
+ }),
583
+ hr: () => /* @__PURE__ */ jsx("hr", { className: markdownStyles.hr }),
584
+ table: (p) => /* @__PURE__ */ jsx("div", {
585
+ className: markdownStyles.tableWrap,
586
+ children: /* @__PURE__ */ jsx("table", {
587
+ ...omitNode(p),
588
+ className: markdownStyles.table
589
+ })
590
+ }),
591
+ th: (p) => /* @__PURE__ */ jsx("th", {
592
+ ...omitNode(p),
593
+ className: markdownStyles.th
594
+ }),
595
+ td: (p) => /* @__PURE__ */ jsx("td", {
596
+ ...omitNode(p),
597
+ className: markdownStyles.td
598
+ }),
599
+ a: (props) => {
600
+ const { href, children, ...p } = omitNode(props);
601
+ if (!href) return /* @__PURE__ */ jsx("span", { children });
602
+ if (!isExternal(href)) return /* @__PURE__ */ jsx("a", {
603
+ href,
604
+ className: markdownStyles.link,
605
+ ...p,
606
+ children
607
+ });
608
+ return /* @__PURE__ */ jsxs("a", {
609
+ href,
610
+ target: "_blank",
611
+ rel: "noopener noreferrer nofollow",
612
+ className: markdownStyles.link,
613
+ ...p,
614
+ children: [
615
+ children,
616
+ /* @__PURE__ */ jsx(ExternalLink, {
617
+ size: 12,
618
+ "aria-hidden": true,
619
+ className: markdownStyles.linkIcon
620
+ }),
621
+ /* @__PURE__ */ jsx(NewTabHint, {})
622
+ ]
623
+ });
624
+ },
625
+ img: ({ src, alt }) => /* @__PURE__ */ jsx(ImageLink, {
626
+ href: typeof src === "string" ? src : "",
627
+ alt
628
+ }),
629
+ input: ({ checked, type }) => type === "checkbox" ? /* @__PURE__ */ jsx(TaskCheckbox, { checked: !!checked }) : null,
630
+ code: ({ className, children }) => /* @__PURE__ */ jsx("code", {
631
+ className: cn(markdownStyles.inlineCode, className),
632
+ children
633
+ }),
634
+ pre: ({ children }) => {
635
+ const child = Children.toArray(children)[0];
636
+ if (!isValidElement(child)) return /* @__PURE__ */ jsx("pre", {
637
+ className: markdownStyles.code.pre,
638
+ children
639
+ });
640
+ const language = /language-([\w+#.-]+)/.exec(child.props.className ?? "")?.[1];
641
+ const code = String(Children.toArray(child.props.children).join("")).replace(/\n$/, "");
642
+ return /* @__PURE__ */ jsx(CodeBlock, {
643
+ code,
644
+ language
645
+ });
646
+ }
647
+ };
648
+ const remarkPlugins = [remarkGfm];
649
+ /**
650
+ * Safe markdown for model output: raw HTML is never rendered (skipped, not parsed), links are
651
+ * restricted to http(s)/mailto/tel/relative, external links open in a new tab with a marker,
652
+ * and fenced code gets a copy button. Partial input from a stream is tolerated.
653
+ */
654
+ const Markdown = memo(function Markdown({ children, streaming = false, className }) {
655
+ const source = streaming ? repairStreamingMarkdown(children) : children;
656
+ return /* @__PURE__ */ jsx("div", {
657
+ "data-streaming": streaming || void 0,
658
+ className: cn(markdownStyles.root, streaming && streamingCaretClass, className),
659
+ children: /* @__PURE__ */ jsx(ReactMarkdown, {
660
+ remarkPlugins,
661
+ components,
662
+ skipHtml: true,
663
+ urlTransform: safeUrl,
664
+ children: source
665
+ })
666
+ });
667
+ });
668
+ //#endregion
669
+ //#region src/AgentSurface/model.ts
670
+ const isRecord = (value) => value !== null && typeof value === "object" && !Array.isArray(value);
671
+ function bindingPath(value) {
672
+ if (!isRecord(value) || !Object.hasOwn(value, "path")) return void 0;
673
+ if (typeof value.path !== "string" || Object.keys(value).length !== 1) throw new Error("Invalid binding");
674
+ getPointer({}, value.path);
675
+ return value.path;
676
+ }
677
+ function resolveValue(value, model) {
678
+ const path = bindingPath(value);
679
+ if (path !== void 0) return getPointer(model, path);
680
+ if (Array.isArray(value)) return value.map((item) => resolveValue(item, model));
681
+ if (isRecord(value)) return Object.fromEntries(Object.entries(value).map(([key, item]) => [key, resolveValue(item, model)]));
682
+ return value;
683
+ }
684
+ /** Remove executable/structural properties before even a host validator sees agent input. */
685
+ function safeProperties(input, model) {
686
+ const result = {};
687
+ for (const [key, value] of Object.entries(input)) {
688
+ if (/^on/i.test(key) || [
689
+ "id",
690
+ "component",
691
+ "children",
692
+ "className",
693
+ "style",
694
+ "dangerouslySetInnerHTML",
695
+ "ref",
696
+ "key",
697
+ "render",
698
+ "as",
699
+ "__proto__",
700
+ "constructor",
701
+ "prototype"
702
+ ].includes(key)) continue;
703
+ const resolved = resolveValue(value, model);
704
+ result[key] = /(?:href|src|url)$/i.test(key) && typeof resolved === "string" ? safeUrl(resolved) || void 0 : resolved;
705
+ }
706
+ return result;
707
+ }
708
+ function validAction(value) {
709
+ return value === void 0 || isRecord(value) && typeof value.name === "string" && value.name.trim().length > 0 && value.name.length <= 128 && (value.context === void 0 || isRecord(value.context));
710
+ }
711
+ //#endregion
712
+ //#region src/AgentSurface/AgentSurface.tsx
713
+ function AgentSurface({ messages, catalog, onError, onAction }) {
714
+ const { tc } = useChatI18n();
715
+ const reported = useRef(/* @__PURE__ */ new Set());
716
+ const errors = [];
717
+ const [state, setState] = useState(emptyStream);
718
+ if (state.messages !== messages) setState(consume(state, messages));
719
+ errors.push(...state.errors);
720
+ function renderNode(surfaceId, nodes, id) {
721
+ const node = nodes.get(id);
722
+ if (!node) return null;
723
+ const entry = Object.hasOwn(catalog, node.component) ? catalog[node.component] : void 0;
724
+ const surface = state.surfaces.get(surfaceId);
725
+ let props = null;
726
+ try {
727
+ const input = safeProperties(node, surface.model);
728
+ if (validAction(input.action)) props = entry?.validate(input) ?? null;
729
+ } catch {}
730
+ if (!entry || !props) {
731
+ errors.push({
732
+ code: entry ? "props" : "component",
733
+ surfaceId,
734
+ componentId: id,
735
+ type: node.component
736
+ });
737
+ return /* @__PURE__ */ jsx("p", {
738
+ className: "text-muted-foreground",
739
+ children: tc("chat.surfaceUnsupported")
740
+ }, id);
741
+ }
742
+ const Component = entry.component;
743
+ return /* @__PURE__ */ jsx(Component, {
744
+ props,
745
+ onValueChange: (value) => {
746
+ const path = bindingPath(node.value ?? node.checked);
747
+ if (path === void 0) return;
748
+ setState((current) => {
749
+ const surface = current.surfaces.get(surfaceId);
750
+ if (!surface) return current;
751
+ const surfaces = new Map(current.surfaces);
752
+ surfaces.set(surfaceId, {
753
+ ...surface,
754
+ model: setPointer(surface.model, path, value)
755
+ });
756
+ return {
757
+ ...current,
758
+ surfaces
759
+ };
760
+ });
761
+ },
762
+ onAction: () => {
763
+ if (!isRecord(node.action) || typeof node.action.name !== "string") return;
764
+ const context = resolveValue(node.action.context ?? {}, surface.model);
765
+ if (isRecord(context)) onAction?.({
766
+ name: node.action.name,
767
+ surfaceId,
768
+ context
769
+ });
770
+ },
771
+ children: node.children?.map((child) => renderNode(surfaceId, nodes, child))
772
+ }, id);
773
+ }
774
+ const content = [...state.surfaces].map(([id, surface]) => /* @__PURE__ */ jsxs("section", {
775
+ "data-agent-surface": id,
776
+ role: "group",
777
+ "aria-label": tc("chat.surfaceAgentContent"),
778
+ className: "rounded-lg border border-border bg-background p-3",
779
+ children: [/* @__PURE__ */ jsx("p", {
780
+ className: "mb-3 text-xs font-semibold text-muted-foreground",
781
+ children: tc("chat.surfaceAgentContent")
782
+ }), /* @__PURE__ */ jsx("form", {
783
+ onSubmit: (event) => event.preventDefault(),
784
+ children: surface.invalid ? /* @__PURE__ */ jsx("p", {
785
+ className: "text-muted-foreground",
786
+ children: tc("chat.surfaceUnsupported")
787
+ }) : renderNode(id, surface.nodes, "root")
788
+ })]
789
+ }, id));
790
+ useEffect(() => {
791
+ for (const error of errors) {
792
+ const key = JSON.stringify(error);
793
+ if (!reported.current.has(key)) {
794
+ reported.current.add(key);
795
+ onError?.(error);
796
+ }
797
+ }
798
+ });
799
+ return /* @__PURE__ */ jsx(SurfaceFocusBoundary, {
800
+ surfaceIds: new Set(state.surfaces.keys()),
801
+ visible: state.surfaces.size > 0 || state.deleted,
802
+ label: tc("chat.surfaceAgentContent"),
803
+ children: content
804
+ });
805
+ }
806
+ //#endregion
807
+ //#region src/AgentSurface/catalog-components.tsx
808
+ const text = (value) => typeof value === "string" ? value : "";
809
+ function SurfaceText({ props }) {
810
+ return typeof props.href === "string" && props.href ? /* @__PURE__ */ jsx(Text, { children: /* @__PURE__ */ jsx(Link, {
811
+ href: props.href,
812
+ children: text(props.text)
813
+ }) }) : /* @__PURE__ */ jsx(Text, { children: text(props.text) });
814
+ }
815
+ function SurfaceHeading({ props }) {
816
+ return /* @__PURE__ */ jsx(Heading, {
817
+ level: typeof props.level === "number" ? props.level : 2,
818
+ children: text(props.text)
819
+ });
820
+ }
821
+ function SurfaceCard({ children }) {
822
+ return /* @__PURE__ */ jsx(Card, {
823
+ className: "space-y-3 p-4",
824
+ children
825
+ });
826
+ }
827
+ function SurfaceStack({ props, children }) {
828
+ return /* @__PURE__ */ jsx(Stack, {
829
+ direction: props.direction === "row" ? "row" : "column",
830
+ gap: typeof props.gap === "number" ? props.gap : 3,
831
+ children
832
+ });
833
+ }
834
+ function SurfaceButton({ props, onAction }) {
835
+ return /* @__PURE__ */ jsx(Button, {
836
+ type: "submit",
837
+ className: "motion-reduce:transition-none",
838
+ disabled: props.disabled === true,
839
+ onClick: onAction,
840
+ children: text(props.label)
841
+ });
842
+ }
843
+ function SurfaceInput({ props, onValueChange }) {
844
+ return /* @__PURE__ */ jsxs("label", {
845
+ className: "flex flex-col gap-2 text-sm text-foreground",
846
+ children: [text(props.label), /* @__PURE__ */ jsx(Input, {
847
+ type: "text",
848
+ disabled: props.disabled === true,
849
+ value: text(props.value),
850
+ onValueChange
851
+ })]
852
+ });
853
+ }
854
+ function SurfaceSelect({ props, onValueChange }) {
855
+ const items = Array.isArray(props.options) ? props.options.filter(isRecord).map((item) => ({
856
+ value: text(item.value),
857
+ label: text(item.label)
858
+ })) : [];
859
+ return /* @__PURE__ */ jsx(SimpleSelect, {
860
+ label: text(props.label),
861
+ disabled: props.disabled === true,
862
+ value: typeof props.value === "string" ? props.value : null,
863
+ items,
864
+ onValueChange
865
+ });
866
+ }
867
+ function SurfaceCheckbox({ props, onValueChange }) {
868
+ return /* @__PURE__ */ jsx(Checkbox, {
869
+ label: text(props.label),
870
+ disabled: props.disabled === true,
871
+ checked: props.checked === true,
872
+ onCheckedChange: onValueChange
873
+ });
874
+ }
875
+ function SurfaceAlert({ props }) {
876
+ return /* @__PURE__ */ jsx(Alert, {
877
+ title: text(props.title),
878
+ description: text(props.text)
879
+ });
880
+ }
881
+ function SurfaceBadge({ props }) {
882
+ return /* @__PURE__ */ jsx(Badge, { children: text(props.text) });
883
+ }
884
+ //#endregion
885
+ //#region src/AgentSurface/catalog.ts
886
+ function defineAgentCatalog(entries) {
887
+ return Object.freeze(Object.fromEntries(Object.entries(entries).map(([name, entry]) => [name, Object.freeze({ ...entry })])));
888
+ }
889
+ const string = (value) => typeof value === "string";
890
+ const label = (value) => typeof value === "string" && value.trim().length > 0;
891
+ const optional = (rule) => (value) => value === void 0 || rule(value);
892
+ const boolean = (value) => typeof value === "boolean";
893
+ function fields(rules) {
894
+ return (input) => {
895
+ const output = {};
896
+ for (const [key, rule] of Object.entries(rules)) {
897
+ if (!rule(input[key])) return null;
898
+ if (input[key] !== void 0) output[key] = input[key];
899
+ }
900
+ return output;
901
+ };
902
+ }
903
+ const options = (value) => Array.isArray(value) && value.length <= 100 && value.every((item) => isRecord(item) && label(item.value) && label(item.label)) && new Set(value.map((item) => isRecord(item) ? item.value : null)).size === value.length;
904
+ /** Small text/form catalog. Password, payment and file controls are deliberately absent. */
905
+ const defaultAgentCatalog = defineAgentCatalog({
906
+ Text: {
907
+ component: SurfaceText,
908
+ validate: fields({
909
+ text: string,
910
+ href: optional(string)
911
+ })
912
+ },
913
+ Heading: {
914
+ component: SurfaceHeading,
915
+ validate: fields({
916
+ text: label,
917
+ level: optional((v) => typeof v === "number" && Number.isInteger(v) && v >= 1 && v <= 6)
918
+ })
919
+ },
920
+ Card: {
921
+ component: SurfaceCard,
922
+ validate: fields({})
923
+ },
924
+ Stack: {
925
+ component: SurfaceStack,
926
+ validate: fields({
927
+ direction: optional((v) => v === "row" || v === "column"),
928
+ gap: optional((v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 12)
929
+ })
930
+ },
931
+ Button: {
932
+ component: SurfaceButton,
933
+ validate: fields({
934
+ label,
935
+ disabled: optional(boolean)
936
+ })
937
+ },
938
+ Input: {
939
+ component: SurfaceInput,
940
+ validate: fields({
941
+ label,
942
+ value: optional(string),
943
+ disabled: optional(boolean),
944
+ type: optional((v) => v === "text")
945
+ })
946
+ },
947
+ Select: {
948
+ component: SurfaceSelect,
949
+ validate: (input) => {
950
+ const result = fields({
951
+ label,
952
+ value: optional((v) => v === null || typeof v === "string"),
953
+ options,
954
+ disabled: optional(boolean)
955
+ })(input);
956
+ if (!result || !Array.isArray(result.options)) return null;
957
+ return {
958
+ ...result,
959
+ options: result.options.filter(isRecord).map((item) => ({
960
+ value: item.value,
961
+ label: item.label
962
+ }))
963
+ };
964
+ }
965
+ },
966
+ Checkbox: {
967
+ component: SurfaceCheckbox,
968
+ validate: fields({
969
+ label,
970
+ checked: optional(boolean),
971
+ disabled: optional(boolean)
972
+ })
973
+ },
974
+ Alert: {
975
+ component: SurfaceAlert,
976
+ validate: fields({
977
+ title: label,
978
+ text: optional(string)
979
+ })
980
+ },
981
+ Badge: {
982
+ component: SurfaceBadge,
983
+ validate: fields({ text: label })
984
+ }
985
+ });
986
+ //#endregion
987
+ //#region src/AgentSurface/AgentSurface.doc.ts
988
+ const doc = {
989
+ name: "AgentSurface",
990
+ group: "Display",
991
+ status: "beta",
992
+ description: "Opt-in, placement-neutral renderer for allow-listed agent content. Accepts only A2UI v0.9 command envelopes (A2UI_VERSION). Pass an immutable messages log and a trusted catalog; append createSurface, updateComponents, updateDataModel and deleteSurface messages. Component records have id, component, optional children (child ids), and flat catalog properties; the root id is \"root\". Text/Heading/Badge use text; Text optionally accepts href; Heading accepts level 1–6. Card contains children; Stack accepts direction (row/column) and gap (0–12). Button uses label, optional disabled and action { name, context }; Input uses label, optional text value/disabled and only type text. Select uses label, options [{ value, label }], optional value/disabled; Checkbox uses label, optional checked/disabled. Alert uses title and optional text. { path: \"/user/name\" } reads a JSON Pointer; Input/Select value and Checkbox checked write back to that path. updateDataModel { surfaceId, path?, value } replaces the value at path (default: model root). Every default Button is a submit control: Enter in an Input can activate the first Button, so keep a safe primary action first and avoid destructive actions in that position. Buttons, keyboard activation and implicit form submission emit onAction { name, surfaceId, context } with the current bound values. Replacing the log replays it; keep existing message objects stable when appending. Runtime validation rejects unsupported versions, malformed JSON, prototype keys, cycles, shared children and excessive trees (256 nodes, depth 32, 32 surfaces). Missing child definitions may arrive later. Unknown components and invalid properties show a localized placeholder and report a deduplicated onError diagnostic. The visible, accessible agent-content frame cannot be relabelled by the agent. Deletion or version suppression of rendered content restores focus to the next surviving sibling control, the first focusable textarea in the document, or the labelled container. Place the chat composer as that first textarea or provide a focusable sibling after the renderer; another textarea can otherwise receive focus. defineAgentCatalog accepts trusted host wrappers { component, validate }; validate must return a fresh allow-list of known properties or null. Wrappers receive { props, children, onValueChange, onAction } and must assign explicit kit props. Never spread agent data into elements. Custom URL properties must have names ending in href, src or url (case-insensitive) to receive the Markdown safeUrl policy; text renders as React text nodes. Custom catalogs can reintroduce phishing and executable content: audit their wrappers and validators, never allow passwords, payments, file uploads, arbitrary markup or host-chrome styling. Transport, side-panel placement, v0.8 adapters and Markdown integration belong to consumers or future changes. The live example demonstrates every default entry, model patches, actions, rejection, deletion and reset.",
993
+ primitive: "Kit components (Base UI)",
994
+ pattern: "labelled group containing a form",
995
+ keyboard: [
996
+ ["Tab / Shift+Tab", "Moves between enabled form controls and links"],
997
+ ["Enter / Space", "Activates a Button exactly once; Enter in an Input submits with the first Button"],
998
+ ["Space", "Toggles a Checkbox and updates its bound model path"],
999
+ ["Enter / ArrowDown", "Opens a Select; ArrowUp/ArrowDown navigates options"],
1000
+ ["Enter / Escape", "Selects an option / closes a Select and returns focus"],
1001
+ ["Surface deletion", "Moves focus to a surviving sibling control, the composer, or the labelled container"]
1002
+ ],
1003
+ tokens: [
1004
+ "background",
1005
+ "card",
1006
+ "border",
1007
+ "foreground",
1008
+ "muted-foreground",
1009
+ "focus-ring"
1010
+ ]
1011
+ };
1012
+ //#endregion
8
1013
  //#region src/utils/format.ts
9
1014
  /** "840 ms" below a second, "2.4 s" below a minute, "1 m 05 s" above. */
10
1015
  function formatDuration(ms) {
@@ -37,63 +1042,6 @@ function toJson(value) {
37
1042
  }
38
1043
  }
39
1044
  //#endregion
40
- //#region src/utils/i18n.ts
41
- /**
42
- * English fallbacks for the chat keys added after the first `@gntik-ai/ui` release. The
43
- * strings live in the ui catalogs (en + es); these keep the pack readable when it runs
44
- * against an older `@gntik-ai/ui` whose catalog does not have them yet.
45
- */
46
- const FALLBACK = {
47
- "chat.typing": "{author} is typing",
48
- "chat.feedback": "Response feedback",
49
- "chat.feedbackTitle": "What went wrong?",
50
- "chat.feedbackReason": "Reason",
51
- "chat.feedbackComment": "Comment (optional)",
52
- "chat.feedbackPlaceholder": "Tell us more…",
53
- "chat.feedbackSubmit": "Send feedback",
54
- "chat.feedbackThanks": "Thanks for your feedback",
55
- "chat.feedbackCleared": "Feedback removed",
56
- "chat.reasonInaccurate": "Inaccurate",
57
- "chat.reasonUnhelpful": "Not helpful",
58
- "chat.reasonIncomplete": "Incomplete",
59
- "chat.reasonUnsafe": "Harmful or unsafe",
60
- "chat.reasonOther": "Other",
61
- "chat.regenerate": "Regenerate",
62
- "chat.uploading": "Uploading {name}",
63
- "chat.uploadFailed": "Upload failed",
64
- "chat.retryUpload": "Retry uploading {name}",
65
- "chat.dropFiles": "Drop files to attach",
66
- "chat.fileTooLarge": "{name} is larger than {max}",
67
- "chat.fileTypeNotAllowed": "{name} is not an allowed file type",
68
- "chat.tooManyFiles": "You can attach up to {max} files",
69
- "chat.model": "Model",
70
- "chat.selectModel": "Select a model",
71
- "chat.capabilityVision": "Vision",
72
- "chat.capabilityTools": "Tools",
73
- "chat.capabilityReasoning": "Reasoning",
74
- "chat.capabilityFiles": "Files",
75
- "chat.capabilityWeb": "Web search",
76
- "chat.capabilityAudio": "Audio",
77
- "chat.contextWindow": "{size} context",
78
- "chat.unavailable": "Unavailable"
79
- };
80
- /** `useI18n()` plus `tc()`, a translator for the newer chat keys with English fallbacks. */
81
- function useChatI18n() {
82
- const i18n = useI18n();
83
- const tc = (key, vars) => {
84
- const out = i18n.t(key, vars);
85
- return out === key ? interpolate(FALLBACK[key], vars, i18n.locale) : out;
86
- };
87
- return {
88
- ...i18n,
89
- tc
90
- };
91
- }
92
- //#endregion
93
- //#region src/utils/focus.ts
94
- /** Focus ring shared by every interactive element in the pack (brand rule). */
95
- const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring";
96
- //#endregion
97
1045
  //#region src/Attachment/attachment.variants.ts
98
1046
  /** Slot classes for AttachmentChip / AttachmentList. */
99
1047
  const attachmentStyles = {
@@ -281,7 +1229,7 @@ function validateFiles(files, { accept, maxSize, maxFiles, current = 0 }) {
281
1229
  }
282
1230
  //#endregion
283
1231
  //#region src/Attachment/Attachment.doc.ts
284
- const doc = {
1232
+ const doc$1 = {
285
1233
  name: "AttachmentList",
286
1234
  group: "Display",
287
1235
  status: "beta",
@@ -558,7 +1506,7 @@ function ChatComposer({ value, defaultValue = "", onValueChange, onSubmit, strea
558
1506
  }
559
1507
  //#endregion
560
1508
  //#region src/ChatComposer/ChatComposer.doc.ts
561
- const doc$1 = {
1509
+ const doc$2 = {
562
1510
  name: "ChatComposer",
563
1511
  group: "Forms",
564
1512
  status: "beta",
@@ -624,49 +1572,27 @@ function TypingIndicator({ author, showLabel = false, label, size = "md", announ
624
1572
  "aria-hidden": true,
625
1573
  className: cn(typingIndicatorStyles.dots, sz.dots),
626
1574
  children: [
627
- /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot) }),
628
- /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot, "[animation-delay:150ms]") }),
629
- /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot, "[animation-delay:300ms]") })
630
- ]
631
- }), /* @__PURE__ */ jsx("span", {
632
- className: showLabel ? typingIndicatorStyles.label : "sr-only",
633
- children: text
634
- })]
635
- });
636
- }
637
- //#endregion
638
- //#region src/TypingIndicator/TypingIndicator.doc.ts
639
- const doc$11 = {
640
- name: "TypingIndicator",
641
- group: "Feedback",
642
- status: "beta",
643
- description: "Three pulsing dots shown while a reply is being written, with the text \"Assistant is typing\" for screen readers (or visible with `showLabel`). The dots are decorative and hold still under reduced motion. ChatMessage renders it while a streaming turn is still empty.",
644
- keyboard: [],
645
- tokens: ["muted-foreground"]
646
- };
647
- //#endregion
648
- //#region src/utils/useCopy.ts
649
- /**
650
- * Copies text to the clipboard and exposes a short-lived `copied` flag for the button label.
651
- * The reset timer is cleared on unmount and on every new copy.
652
- */
653
- function useCopy(resetMs = 1600) {
654
- const [copied, setCopied] = useState(false);
655
- const timer = useRef(void 0);
656
- useEffect(() => () => clearTimeout(timer.current), []);
657
- return {
658
- copied,
659
- copy: useCallback(async (text) => {
660
- try {
661
- await navigator.clipboard?.writeText(text);
662
- } catch {}
663
- setCopied(true);
664
- clearTimeout(timer.current);
665
- timer.current = setTimeout(() => setCopied(false), resetMs);
666
- }, [resetMs])
667
- };
1575
+ /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot) }),
1576
+ /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot, "[animation-delay:150ms]") }),
1577
+ /* @__PURE__ */ jsx("span", { className: cn(typingIndicatorStyles.dot, sz.dot, "[animation-delay:300ms]") })
1578
+ ]
1579
+ }), /* @__PURE__ */ jsx("span", {
1580
+ className: showLabel ? typingIndicatorStyles.label : "sr-only",
1581
+ children: text
1582
+ })]
1583
+ });
668
1584
  }
669
1585
  //#endregion
1586
+ //#region src/TypingIndicator/TypingIndicator.doc.ts
1587
+ const doc$12 = {
1588
+ name: "TypingIndicator",
1589
+ group: "Feedback",
1590
+ status: "beta",
1591
+ description: "Three pulsing dots shown while a reply is being written, with the text \"Assistant is typing\" for screen readers (or visible with `showLabel`). The dots are decorative and hold still under reduced motion. ChatMessage renders it while a streaming turn is still empty.",
1592
+ keyboard: [],
1593
+ tokens: ["muted-foreground"]
1594
+ };
1595
+ //#endregion
670
1596
  //#region src/ChatMessage/chatMessage.variants.ts
671
1597
  /** Slot classes for ChatMessage, per role. */
672
1598
  const chatMessageStyles = {
@@ -877,7 +1803,7 @@ function ChatAnnouncer({ streaming, lastReply, author, maxLength = 600 }) {
877
1803
  }
878
1804
  //#endregion
879
1805
  //#region src/ChatMessage/ChatMessage.doc.ts
880
- const doc$4 = {
1806
+ const doc$5 = {
881
1807
  name: "ChatMessage",
882
1808
  group: "Display",
883
1809
  status: "beta",
@@ -1107,7 +2033,7 @@ function MessageFeedback({ value: valueProp, onFeedback, reasons: reasonsProp, a
1107
2033
  }
1108
2034
  //#endregion
1109
2035
  //#region src/MessageFeedback/MessageFeedback.doc.ts
1110
- const doc$7 = {
2036
+ const doc$8 = {
1111
2037
  name: "MessageFeedback",
1112
2038
  group: "Actions",
1113
2039
  status: "beta",
@@ -1318,7 +2244,7 @@ const DEMO_MODELS = [
1318
2244
  ];
1319
2245
  //#endregion
1320
2246
  //#region src/ModelPicker/ModelPicker.doc.ts
1321
- const doc$8 = {
2247
+ const doc$9 = {
1322
2248
  name: "ModelPicker",
1323
2249
  group: "Forms",
1324
2250
  status: "beta",
@@ -1560,7 +2486,7 @@ function ChatLayout({ children, composer, header, panel, panelLabel: panelLabelP
1560
2486
  }
1561
2487
  //#endregion
1562
2488
  //#region src/ChatLayout/ChatLayout.doc.ts
1563
- const doc$3 = {
2489
+ const doc$4 = {
1564
2490
  name: "ChatLayout",
1565
2491
  group: "Layout",
1566
2492
  status: "beta",
@@ -1648,385 +2574,121 @@ function Citation({ index, source, delay = 200, className }) {
1648
2574
  className: citationStyles.index,
1649
2575
  children: index
1650
2576
  }),
1651
- /* @__PURE__ */ jsx(Globe, {
1652
- size: 12,
1653
- "aria-hidden": true
1654
- }),
1655
- host && /* @__PURE__ */ jsx("span", {
1656
- className: "truncate",
1657
- children: host
1658
- })
1659
- ]
1660
- }),
1661
- /* @__PURE__ */ jsx(PopoverTitle, {
1662
- className: citationStyles.title,
1663
- children: source.title
1664
- }),
1665
- source.snippet && /* @__PURE__ */ jsx(PopoverDescription, {
1666
- className: citationStyles.snippet,
1667
- children: source.snippet
1668
- }),
1669
- href && /* @__PURE__ */ jsxs("a", {
1670
- href,
1671
- target: "_blank",
1672
- rel: "noopener noreferrer nofollow",
1673
- className: citationStyles.open,
1674
- children: [
1675
- t("chat.openSource"),
1676
- /* @__PURE__ */ jsx(ExternalLink, {
1677
- size: 12,
1678
- "aria-hidden": true
1679
- }),
1680
- /* @__PURE__ */ jsxs("span", {
1681
- className: "sr-only",
1682
- children: [" ", t("link.external")]
1683
- })
1684
- ]
1685
- })
1686
- ]
1687
- })] });
1688
- }
1689
- /** Numbered list of the sources cited in a reply, matching the inline markers. */
1690
- function CitationList({ sources, heading: headingProp, className }) {
1691
- const { t } = useI18n();
1692
- const heading = headingProp ?? t("chat.sources");
1693
- const headingId = useId();
1694
- if (sources.length === 0) return null;
1695
- return /* @__PURE__ */ jsxs("section", {
1696
- "aria-labelledby": headingId,
1697
- className,
1698
- children: [/* @__PURE__ */ jsx("h3", {
1699
- id: headingId,
1700
- className: citationStyles.heading,
1701
- children: heading
1702
- }), /* @__PURE__ */ jsx("ol", {
1703
- className: citationStyles.list,
1704
- children: sources.map((src, i) => {
1705
- const href = safeSourceUrl(src.url);
1706
- const host = src.publisher ?? sourceHost(src.url);
1707
- return /* @__PURE__ */ jsxs("li", {
1708
- className: citationStyles.item,
1709
- children: [/* @__PURE__ */ jsx("span", {
1710
- className: citationStyles.index,
1711
- "aria-hidden": true,
1712
- children: i + 1
1713
- }), /* @__PURE__ */ jsxs("div", {
1714
- className: citationStyles.itemBody,
1715
- children: [href ? /* @__PURE__ */ jsxs("a", {
1716
- href,
1717
- target: "_blank",
1718
- rel: "noopener noreferrer nofollow",
1719
- className: citationStyles.itemTitle,
1720
- children: [src.title, /* @__PURE__ */ jsxs("span", {
1721
- className: "sr-only",
1722
- children: [" ", t("link.external")]
1723
- })]
1724
- }) : /* @__PURE__ */ jsx("span", {
1725
- className: "text-[13px] font-medium text-foreground",
1726
- children: src.title
1727
- }), host && /* @__PURE__ */ jsx("div", {
1728
- className: citationStyles.itemHost,
1729
- children: host
1730
- })]
1731
- })]
1732
- }, `${i}-${src.title}`);
1733
- })
1734
- })]
1735
- });
1736
- }
1737
- //#endregion
1738
- //#region src/Citation/Citation.doc.ts
1739
- const doc$5 = {
1740
- name: "Citation",
1741
- group: "Display",
1742
- status: "beta",
1743
- description: "Numbered source references for grounded answers: an inline [n] marker that previews the source (title, host, snippet, link) on hover, focus + Enter or click, and CitationList, the numbered list of sources under a reply. Only http(s) URLs become links.",
1744
- primitive: "@base-ui/react/popover",
1745
- pattern: "dialog (non-modal)",
1746
- keyboard: [
1747
- ["Enter", "On a marker: opens the source preview"],
1748
- ["Space", "On a marker: opens the source preview"],
1749
- ["Escape", "Closes the preview and returns focus to the marker"],
1750
- ["Tab", "Moves to the \"Open source\" link inside an open preview"]
1751
- ],
1752
- tokens: [
1753
- "primary",
1754
- "primary-text",
1755
- "popover",
1756
- "secondary",
1757
- "card",
1758
- "border",
1759
- "muted-foreground",
1760
- "focus-ring"
1761
- ]
1762
- };
1763
- //#endregion
1764
- //#region src/Markdown/markdown.variants.ts
1765
- /**
1766
- * Blinking caret drawn after the last block while text streams in. Pure CSS (an ::after on the
1767
- * last child), so it follows paragraphs, list items and code without extra DOM.
1768
- */
1769
- const streamingCaretClass = [
1770
- "[&>:last-child]:after:ms-0.5 [&>:last-child]:after:inline-block [&>:last-child]:after:h-[1.05em] [&>:last-child]:after:w-[2px]",
1771
- "[&>:last-child]:after:translate-y-[2px] [&>:last-child]:after:bg-primary [&>:last-child]:after:content-['']",
1772
- "[&>:last-child]:after:animate-pulse motion-reduce:[&>:last-child]:after:animate-none"
1773
- ].join(" ");
1774
- /** Element classes for Markdown (token colours only). */
1775
- const markdownStyles = {
1776
- root: "min-w-0 text-[14px] leading-[1.7] text-foreground [overflow-wrap:anywhere] [&>:first-child]:mt-0 [&>:last-child]:mb-0",
1777
- h1: "mt-5 mb-2 text-[18px] font-semibold tracking-tight",
1778
- h2: "mt-5 mb-2 text-[16px] font-semibold tracking-tight",
1779
- h3: "mt-4 mb-1.5 text-[14.5px] font-semibold",
1780
- h4: "mt-4 mb-1.5 text-[13.5px] font-semibold text-muted-foreground",
1781
- p: "my-2.5",
1782
- ul: "my-2.5 list-disc space-y-1 ps-5 marker:text-muted-foreground",
1783
- ol: "my-2.5 list-decimal space-y-1 ps-5 marker:text-muted-foreground",
1784
- li: "ps-1 [&>ul]:my-1 [&>ol]:my-1 [&.task-list-item]:list-none [&.task-list-item]:-ms-5",
1785
- blockquote: "my-3 border-s-2 border-primary/50 ps-3.5 text-muted-foreground",
1786
- hr: "my-5 border-border",
1787
- link: `rounded-sm font-medium text-primary-text underline decoration-primary/40 underline-offset-2 hover:decoration-primary ${focusRing}`,
1788
- linkIcon: "ms-0.5 inline-block align-[-1px]",
1789
- inlineCode: "rounded-md border border-border bg-secondary/60 px-1.5 py-px font-mono text-[12.5px]",
1790
- tableWrap: "my-3 overflow-x-auto rounded-lg border border-border",
1791
- table: "w-full border-collapse text-start text-[13px]",
1792
- th: "border-b border-border bg-secondary/50 px-3 py-2 font-semibold",
1793
- td: "border-b border-border px-3 py-2 align-top [tr:last-child_&]:border-b-0",
1794
- checkbox: "me-1.5 align-[-2px] accent-primary",
1795
- image: "text-muted-foreground italic",
1796
- code: {
1797
- root: "my-3 overflow-hidden rounded-lg border border-border bg-secondary/40",
1798
- header: "flex h-9 items-center justify-between border-b border-border bg-secondary/50 ps-3 pe-1.5",
1799
- lang: "font-mono text-[11px] tracking-wide text-muted-foreground uppercase",
1800
- pre: "overflow-x-auto p-3 font-mono text-[12.5px] leading-6"
1801
- }
1802
- };
1803
- //#endregion
1804
- //#region src/Markdown/CodeBlock.tsx
1805
- const s$1 = markdownStyles.code;
1806
- /** Fenced code with a language label and a copy button. */
1807
- function CodeBlock({ code, language, className }) {
1808
- const { copied, copy } = useCopy();
1809
- const { t } = useI18n();
1810
- return /* @__PURE__ */ jsxs("div", {
1811
- className: className ? `${s$1.root} ${className}` : s$1.root,
1812
- "data-language": language,
1813
- children: [/* @__PURE__ */ jsxs("div", {
1814
- className: s$1.header,
1815
- children: [/* @__PURE__ */ jsx("span", {
1816
- className: s$1.lang,
1817
- children: language || "text"
1818
- }), /* @__PURE__ */ jsx(Button, {
1819
- size: "sm",
1820
- variant: "ghost",
1821
- icon: copied ? Check : Copy,
1822
- "aria-label": copied ? t("chat.copiedCode") : t("chat.copyCode"),
1823
- className: "h-7 px-2",
1824
- onClick: () => void copy(code),
1825
- children: copied ? t("common.copied") : t("common.copy")
1826
- })]
1827
- }), /* @__PURE__ */ jsx("pre", {
1828
- className: s$1.pre,
1829
- children: /* @__PURE__ */ jsx("code", { children: code })
1830
- })]
1831
- });
1832
- }
1833
- //#endregion
1834
- //#region src/Markdown/Markdown.tsx
1835
- const SAFE_PROTOCOL = /^(https?|mailto|tel):/i;
1836
- /**
1837
- * URL policy: relative URLs, anchors and http(s)/mailto/tel only. Everything else
1838
- * (javascript:, data:, vbscript:, …) is dropped, on top of react-markdown's own transform.
1839
- */
1840
- function safeUrl(url) {
1841
- const cleaned = defaultUrlTransform(url);
1842
- if (!cleaned) return "";
1843
- const compact = Array.from(cleaned).filter((c) => c.charCodeAt(0) > 32).join("");
1844
- if (/^[a-z][a-z0-9+.-]*:/i.test(compact) && !SAFE_PROTOCOL.test(compact)) return "";
1845
- return cleaned;
1846
- }
1847
- /**
1848
- * Makes partial streamed markdown render sanely: a fence that has opened but not closed is
1849
- * closed at the end, so the tail renders as code instead of flashing as paragraphs.
1850
- */
1851
- function repairStreamingMarkdown(text) {
1852
- const fences = text.match(/^ {0,3}(`{3,}|~{3,})/gm) ?? [];
1853
- if (fences.length % 2 === 0) return text;
1854
- const opener = (fences[fences.length - 1] ?? "```").trim();
1855
- return `${text}${text.endsWith("\n") ? "" : "\n"}${opener}`;
1856
- }
1857
- const isExternal = (href) => /^https?:\/\//i.test(href);
1858
- /** Drops react-markdown's `node` (hast) prop before spreading onto a DOM element. */
1859
- /** Screen-reader note on links that open a new tab. */
1860
- function NewTabHint() {
1861
- const { t } = useI18n();
1862
- return /* @__PURE__ */ jsxs("span", {
1863
- className: "sr-only",
1864
- children: [" ", t("link.external")]
1865
- });
1866
- }
1867
- /** Remote images can track readers and break layout mid-stream: show them as links. */
1868
- function ImageLink({ href, alt }) {
1869
- const { t } = useI18n();
1870
- const text = t("chat.image", { alt: alt || t("chat.untitled") });
1871
- return href ? /* @__PURE__ */ jsxs("a", {
1872
- href,
1873
- target: "_blank",
1874
- rel: "noopener noreferrer nofollow",
1875
- className: markdownStyles.link,
1876
- children: [text, /* @__PURE__ */ jsx(NewTabHint, {})]
1877
- }) : /* @__PURE__ */ jsx("span", {
1878
- className: markdownStyles.image,
1879
- children: text
1880
- });
2577
+ /* @__PURE__ */ jsx(Globe, {
2578
+ size: 12,
2579
+ "aria-hidden": true
2580
+ }),
2581
+ host && /* @__PURE__ */ jsx("span", {
2582
+ className: "truncate",
2583
+ children: host
2584
+ })
2585
+ ]
2586
+ }),
2587
+ /* @__PURE__ */ jsx(PopoverTitle, {
2588
+ className: citationStyles.title,
2589
+ children: source.title
2590
+ }),
2591
+ source.snippet && /* @__PURE__ */ jsx(PopoverDescription, {
2592
+ className: citationStyles.snippet,
2593
+ children: source.snippet
2594
+ }),
2595
+ href && /* @__PURE__ */ jsxs("a", {
2596
+ href,
2597
+ target: "_blank",
2598
+ rel: "noopener noreferrer nofollow",
2599
+ className: citationStyles.open,
2600
+ children: [
2601
+ t("chat.openSource"),
2602
+ /* @__PURE__ */ jsx(ExternalLink, {
2603
+ size: 12,
2604
+ "aria-hidden": true
2605
+ }),
2606
+ /* @__PURE__ */ jsxs("span", {
2607
+ className: "sr-only",
2608
+ children: [" ", t("link.external")]
2609
+ })
2610
+ ]
2611
+ })
2612
+ ]
2613
+ })] });
1881
2614
  }
1882
- function TaskCheckbox({ checked }) {
2615
+ /** Numbered list of the sources cited in a reply, matching the inline markers. */
2616
+ function CitationList({ sources, heading: headingProp, className }) {
1883
2617
  const { t } = useI18n();
1884
- return /* @__PURE__ */ jsx("input", {
1885
- type: "checkbox",
1886
- checked,
1887
- readOnly: true,
1888
- disabled: true,
1889
- "aria-label": checked ? t("chat.taskDone") : t("chat.taskNotDone"),
1890
- className: markdownStyles.checkbox
2618
+ const heading = headingProp ?? t("chat.sources");
2619
+ const headingId = useId();
2620
+ if (sources.length === 0) return null;
2621
+ return /* @__PURE__ */ jsxs("section", {
2622
+ "aria-labelledby": headingId,
2623
+ className,
2624
+ children: [/* @__PURE__ */ jsx("h3", {
2625
+ id: headingId,
2626
+ className: citationStyles.heading,
2627
+ children: heading
2628
+ }), /* @__PURE__ */ jsx("ol", {
2629
+ className: citationStyles.list,
2630
+ children: sources.map((src, i) => {
2631
+ const href = safeSourceUrl(src.url);
2632
+ const host = src.publisher ?? sourceHost(src.url);
2633
+ return /* @__PURE__ */ jsxs("li", {
2634
+ className: citationStyles.item,
2635
+ children: [/* @__PURE__ */ jsx("span", {
2636
+ className: citationStyles.index,
2637
+ "aria-hidden": true,
2638
+ children: i + 1
2639
+ }), /* @__PURE__ */ jsxs("div", {
2640
+ className: citationStyles.itemBody,
2641
+ children: [href ? /* @__PURE__ */ jsxs("a", {
2642
+ href,
2643
+ target: "_blank",
2644
+ rel: "noopener noreferrer nofollow",
2645
+ className: citationStyles.itemTitle,
2646
+ children: [src.title, /* @__PURE__ */ jsxs("span", {
2647
+ className: "sr-only",
2648
+ children: [" ", t("link.external")]
2649
+ })]
2650
+ }) : /* @__PURE__ */ jsx("span", {
2651
+ className: "text-[13px] font-medium text-foreground",
2652
+ children: src.title
2653
+ }), host && /* @__PURE__ */ jsx("div", {
2654
+ className: citationStyles.itemHost,
2655
+ children: host
2656
+ })]
2657
+ })]
2658
+ }, `${i}-${src.title}`);
2659
+ })
2660
+ })]
1891
2661
  });
1892
2662
  }
1893
- function omitNode(props) {
1894
- const { node, ...rest } = props;
1895
- return rest;
1896
- }
1897
- const components = {
1898
- h1: (p) => /* @__PURE__ */ jsx("h1", {
1899
- ...omitNode(p),
1900
- className: markdownStyles.h1
1901
- }),
1902
- h2: (p) => /* @__PURE__ */ jsx("h2", {
1903
- ...omitNode(p),
1904
- className: markdownStyles.h2
1905
- }),
1906
- h3: (p) => /* @__PURE__ */ jsx("h3", {
1907
- ...omitNode(p),
1908
- className: markdownStyles.h3
1909
- }),
1910
- h4: (p) => /* @__PURE__ */ jsx("h4", {
1911
- ...omitNode(p),
1912
- className: markdownStyles.h4
1913
- }),
1914
- h5: (p) => /* @__PURE__ */ jsx("h4", {
1915
- ...omitNode(p),
1916
- className: markdownStyles.h4
1917
- }),
1918
- h6: (p) => /* @__PURE__ */ jsx("h4", {
1919
- ...omitNode(p),
1920
- className: markdownStyles.h4
1921
- }),
1922
- p: (p) => /* @__PURE__ */ jsx("p", {
1923
- ...omitNode(p),
1924
- className: markdownStyles.p
1925
- }),
1926
- ul: (p) => /* @__PURE__ */ jsx("ul", {
1927
- ...omitNode(p),
1928
- className: cn(markdownStyles.ul, p.className === "contains-task-list" && "ps-1")
1929
- }),
1930
- ol: (p) => /* @__PURE__ */ jsx("ol", {
1931
- ...omitNode(p),
1932
- className: markdownStyles.ol
1933
- }),
1934
- li: (p) => /* @__PURE__ */ jsx("li", {
1935
- ...omitNode(p),
1936
- className: cn(markdownStyles.li, p.className)
1937
- }),
1938
- blockquote: (p) => /* @__PURE__ */ jsx("blockquote", {
1939
- ...omitNode(p),
1940
- className: markdownStyles.blockquote
1941
- }),
1942
- hr: () => /* @__PURE__ */ jsx("hr", { className: markdownStyles.hr }),
1943
- table: (p) => /* @__PURE__ */ jsx("div", {
1944
- className: markdownStyles.tableWrap,
1945
- children: /* @__PURE__ */ jsx("table", {
1946
- ...omitNode(p),
1947
- className: markdownStyles.table
1948
- })
1949
- }),
1950
- th: (p) => /* @__PURE__ */ jsx("th", {
1951
- ...omitNode(p),
1952
- className: markdownStyles.th
1953
- }),
1954
- td: (p) => /* @__PURE__ */ jsx("td", {
1955
- ...omitNode(p),
1956
- className: markdownStyles.td
1957
- }),
1958
- a: (props) => {
1959
- const { href, children, ...p } = omitNode(props);
1960
- if (!href) return /* @__PURE__ */ jsx("span", { children });
1961
- if (!isExternal(href)) return /* @__PURE__ */ jsx("a", {
1962
- href,
1963
- className: markdownStyles.link,
1964
- ...p,
1965
- children
1966
- });
1967
- return /* @__PURE__ */ jsxs("a", {
1968
- href,
1969
- target: "_blank",
1970
- rel: "noopener noreferrer nofollow",
1971
- className: markdownStyles.link,
1972
- ...p,
1973
- children: [
1974
- children,
1975
- /* @__PURE__ */ jsx(ExternalLink, {
1976
- size: 12,
1977
- "aria-hidden": true,
1978
- className: markdownStyles.linkIcon
1979
- }),
1980
- /* @__PURE__ */ jsx(NewTabHint, {})
1981
- ]
1982
- });
1983
- },
1984
- img: ({ src, alt }) => /* @__PURE__ */ jsx(ImageLink, {
1985
- href: typeof src === "string" ? src : "",
1986
- alt
1987
- }),
1988
- input: ({ checked, type }) => type === "checkbox" ? /* @__PURE__ */ jsx(TaskCheckbox, { checked: !!checked }) : null,
1989
- code: ({ className, children }) => /* @__PURE__ */ jsx("code", {
1990
- className: cn(markdownStyles.inlineCode, className),
1991
- children
1992
- }),
1993
- pre: ({ children }) => {
1994
- const child = Children.toArray(children)[0];
1995
- if (!isValidElement(child)) return /* @__PURE__ */ jsx("pre", {
1996
- className: markdownStyles.code.pre,
1997
- children
1998
- });
1999
- const language = /language-([\w+#.-]+)/.exec(child.props.className ?? "")?.[1];
2000
- const code = String(Children.toArray(child.props.children).join("")).replace(/\n$/, "");
2001
- return /* @__PURE__ */ jsx(CodeBlock, {
2002
- code,
2003
- language
2004
- });
2005
- }
2663
+ //#endregion
2664
+ //#region src/Citation/Citation.doc.ts
2665
+ const doc$6 = {
2666
+ name: "Citation",
2667
+ group: "Display",
2668
+ status: "beta",
2669
+ description: "Numbered source references for grounded answers: an inline [n] marker that previews the source (title, host, snippet, link) on hover, focus + Enter or click, and CitationList, the numbered list of sources under a reply. Only http(s) URLs become links.",
2670
+ primitive: "@base-ui/react/popover",
2671
+ pattern: "dialog (non-modal)",
2672
+ keyboard: [
2673
+ ["Enter", "On a marker: opens the source preview"],
2674
+ ["Space", "On a marker: opens the source preview"],
2675
+ ["Escape", "Closes the preview and returns focus to the marker"],
2676
+ ["Tab", "Moves to the \"Open source\" link inside an open preview"]
2677
+ ],
2678
+ tokens: [
2679
+ "primary",
2680
+ "primary-text",
2681
+ "popover",
2682
+ "secondary",
2683
+ "card",
2684
+ "border",
2685
+ "muted-foreground",
2686
+ "focus-ring"
2687
+ ]
2006
2688
  };
2007
- const remarkPlugins = [remarkGfm];
2008
- /**
2009
- * Safe markdown for model output: raw HTML is never rendered (skipped, not parsed), links are
2010
- * restricted to http(s)/mailto/tel/relative, external links open in a new tab with a marker,
2011
- * and fenced code gets a copy button. Partial input from a stream is tolerated.
2012
- */
2013
- const Markdown = memo(function Markdown({ children, streaming = false, className }) {
2014
- const source = streaming ? repairStreamingMarkdown(children) : children;
2015
- return /* @__PURE__ */ jsx("div", {
2016
- "data-streaming": streaming || void 0,
2017
- className: cn(markdownStyles.root, streaming && streamingCaretClass, className),
2018
- children: /* @__PURE__ */ jsx(ReactMarkdown, {
2019
- remarkPlugins,
2020
- components,
2021
- skipHtml: true,
2022
- urlTransform: safeUrl,
2023
- children: source
2024
- })
2025
- });
2026
- });
2027
2689
  //#endregion
2028
2690
  //#region src/Markdown/Markdown.doc.ts
2029
- const doc$6 = {
2691
+ const doc$7 = {
2030
2692
  name: "Markdown",
2031
2693
  group: "Display",
2032
2694
  status: "beta",
@@ -2114,7 +2776,7 @@ function SuggestionChips({ suggestions, onSelect, layout = "cards", heading, lab
2114
2776
  }
2115
2777
  //#endregion
2116
2778
  //#region src/SuggestionChips/SuggestionChips.doc.ts
2117
- const doc$9 = {
2779
+ const doc$10 = {
2118
2780
  name: "SuggestionChips",
2119
2781
  group: "Actions",
2120
2782
  status: "beta",
@@ -2261,7 +2923,7 @@ function ToolCallCard({ name, status, durationMs, args, result, error, defaultOp
2261
2923
  }
2262
2924
  //#endregion
2263
2925
  //#region src/ToolCallCard/ToolCallCard.doc.ts
2264
- const doc$10 = {
2926
+ const doc$11 = {
2265
2927
  name: "ToolCallCard",
2266
2928
  group: "Display",
2267
2929
  status: "beta",
@@ -2577,7 +3239,7 @@ function ChatDemo({ withPanel = true, tickMs = 30, className }) {
2577
3239
  }
2578
3240
  //#endregion
2579
3241
  //#region src/ChatDemo/ChatDemo.doc.ts
2580
- const doc$2 = {
3242
+ const doc$3 = {
2581
3243
  name: "ChatDemo",
2582
3244
  group: "Layout",
2583
3245
  status: "experimental",
@@ -2592,6 +3254,6 @@ const doc$2 = {
2592
3254
  ]
2593
3255
  };
2594
3256
  //#endregion
2595
- export { AttachmentChip, AttachmentList, ChatAnnouncer, ChatComposer, ChatDemo, ChatLayout, ChatMessage, Citation, CitationList, CodeBlock, DEMO_MODELS, JsonView, Markdown, MessageFeedback, ModelPicker, SuggestionChips, ToolCallCard, TypingIndicator, doc as attachmentListDoc, attachmentStatus, attachmentStyles, doc$1 as chatComposerDoc, chatComposerStyles, doc$2 as chatDemoDoc, doc$3 as chatLayoutDoc, chatLayoutStyles, doc$4 as chatMessageDoc, chatMessageStyles, doc$5 as citationDoc, citationStyles, groupModels, doc$6 as markdownDoc, markdownStyles, matchesAccept, doc$7 as messageFeedbackDoc, messageFeedbackStyles, doc$8 as modelPickerDoc, modelPickerStyles, repairStreamingMarkdown, safeSourceUrl, safeUrl, sourceHost, streamingCaret, streamingCaretClass, doc$9 as suggestionChipsDoc, suggestionChipsStyles, doc$10 as toolCallCardDoc, toolCallCardStyles, doc$11 as typingIndicatorDoc, typingIndicatorStyles, useChatScroll, validateFiles };
3257
+ export { A2UI_VERSION, AgentSurface, AttachmentChip, AttachmentList, ChatAnnouncer, ChatComposer, ChatDemo, ChatLayout, ChatMessage, Citation, CitationList, CodeBlock, DEMO_MODELS, JsonView, Markdown, MessageFeedback, ModelPicker, SuggestionChips, ToolCallCard, TypingIndicator, doc as agentSurfaceDoc, doc$1 as attachmentListDoc, attachmentStatus, attachmentStyles, doc$2 as chatComposerDoc, chatComposerStyles, doc$3 as chatDemoDoc, doc$4 as chatLayoutDoc, chatLayoutStyles, doc$5 as chatMessageDoc, chatMessageStyles, doc$6 as citationDoc, citationStyles, defaultAgentCatalog, defineAgentCatalog, groupModels, doc$7 as markdownDoc, markdownStyles, matchesAccept, doc$8 as messageFeedbackDoc, messageFeedbackStyles, doc$9 as modelPickerDoc, modelPickerStyles, repairStreamingMarkdown, safeSourceUrl, safeUrl, sourceHost, streamingCaret, streamingCaretClass, doc$10 as suggestionChipsDoc, suggestionChipsStyles, doc$11 as toolCallCardDoc, toolCallCardStyles, doc$12 as typingIndicatorDoc, typingIndicatorStyles, useChatScroll, validateFiles };
2596
3258
 
2597
3259
  //# sourceMappingURL=index.js.map