@gntik-ai/chat 0.7.0 → 0.10.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,42 +1,10 @@
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, formatBytes, 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/format.ts
9
- /** "840 ms" below a second, "2.4 s" below a minute, "1 m 05 s" above. */
10
- function formatDuration(ms) {
11
- if (ms < 1e3) return `${Math.round(ms)} ms`;
12
- if (ms < 6e4) return `${(ms / 1e3).toFixed(1)} s`;
13
- const m = Math.floor(ms / 6e4);
14
- const s = Math.round(ms % 6e4 / 1e3);
15
- return `${m} m ${String(s).padStart(2, "0")} s`;
16
- }
17
- /** "12 KB", "3.4 MB". */
18
- function formatBytes(bytes) {
19
- if (bytes < 1024) return `${bytes} B`;
20
- if (bytes < 1048576) return `${Math.round(bytes / 1024)} KB`;
21
- return `${(bytes / 1048576).toFixed(1)} MB`;
22
- }
23
- /** Short local time ("14:05") for a message timestamp. */
24
- function formatTime(date) {
25
- return date.toLocaleTimeString(void 0, {
26
- hour: "2-digit",
27
- minute: "2-digit"
28
- });
29
- }
30
- /** Pretty-prints any value as JSON; falls back to String() for cyclic or exotic values. */
31
- function toJson(value) {
32
- if (typeof value === "string") return value;
33
- try {
34
- return JSON.stringify(value, null, 2) ?? String(value);
35
- } catch {
36
- return String(value);
37
- }
38
- }
39
- //#endregion
40
8
  //#region src/utils/i18n.ts
41
9
  /**
42
10
  * English fallbacks for the chat keys added after the first `@gntik-ai/ui` release. The
@@ -44,6 +12,24 @@ function toJson(value) {
44
12
  * against an older `@gntik-ai/ui` whose catalog does not have them yet.
45
13
  */
46
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",
47
33
  "chat.typing": "{author} is typing",
48
34
  "chat.feedback": "Response feedback",
49
35
  "chat.feedbackTitle": "What went wrong?",
@@ -90,10 +76,940 @@ function useChatI18n() {
90
76
  };
91
77
  }
92
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
93
401
  //#region src/utils/focus.ts
94
402
  /** Focus ring shared by every interactive element in the pack (brand rule). */
95
403
  const focusRing = "focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-focus-ring";
96
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
97
1013
  //#region src/Attachment/attachment.variants.ts
98
1014
  /** Slot classes for AttachmentChip / AttachmentList. */
99
1015
  const attachmentStyles = {
@@ -139,7 +1055,7 @@ function attachmentStatus(a) {
139
1055
  * (a progressbar), error with retry, and a remove button.
140
1056
  */
141
1057
  function AttachmentChip({ attachment: a, variant = "chip", onRemove, onRetry, disabled = false, className, ref }) {
142
- const { t, tc } = useChatI18n();
1058
+ const { t, tc, locale } = useChatI18n();
143
1059
  const status = attachmentStatus(a);
144
1060
  const kind = fileKind(a.name, a.type);
145
1061
  const Icon = status === "error" ? AlertCircle : ICONS[kind];
@@ -165,7 +1081,7 @@ function AttachmentChip({ attachment: a, variant = "chip", onRemove, onRetry, di
165
1081
  children: pct === null ? t("common.loading") : `${pct}%`
166
1082
  }) : a.size !== void 0 ? /* @__PURE__ */ jsx("span", {
167
1083
  className: attachmentStyles.meta,
168
- children: formatBytes(a.size)
1084
+ children: formatBytes(a.size, locale)
169
1085
  }) : null;
170
1086
  const buttons = /* @__PURE__ */ jsxs(Fragment, { children: [status === "error" && onRetry && /* @__PURE__ */ jsx("button", {
171
1087
  type: "button",
@@ -281,7 +1197,7 @@ function validateFiles(files, { accept, maxSize, maxFiles, current = 0 }) {
281
1197
  }
282
1198
  //#endregion
283
1199
  //#region src/Attachment/Attachment.doc.ts
284
- const doc = {
1200
+ const doc$1 = {
285
1201
  name: "AttachmentList",
286
1202
  group: "Display",
287
1203
  status: "beta",
@@ -335,7 +1251,7 @@ const chatComposerStyles = {
335
1251
  * streams the send button becomes Stop (Escape also stops).
336
1252
  */
337
1253
  function ChatComposer({ value, defaultValue = "", onValueChange, onSubmit, streaming = false, onStop, disabled = false, placeholder: placeholderProp, label: labelProp, maxLength, attachments = [], onAttach, onRemoveAttachment, onRetryAttachment, accept, maxSize, maxFiles, onReject, modelPicker, tools, hint, className, ref }) {
338
- const { t, tc } = useChatI18n();
1254
+ const { t, tc, locale } = useChatI18n();
339
1255
  const placeholder = placeholderProp ?? t("chat.placeholder");
340
1256
  const label = labelProp ?? t("chat.messageLabel");
341
1257
  const id = useId();
@@ -381,7 +1297,7 @@ function ChatComposer({ value, defaultValue = "", onValueChange, onSubmit, strea
381
1297
  reason,
382
1298
  message: reason === "type" ? tc("chat.fileTypeNotAllowed", { name: file.name }) : reason === "size" ? tc("chat.fileTooLarge", {
383
1299
  name: file.name,
384
- max: formatBytes(maxSize ?? 0)
1300
+ max: formatBytes(maxSize ?? 0, locale)
385
1301
  }) : tc("chat.tooManyFiles", { max: maxFiles ?? 0 })
386
1302
  }));
387
1303
  setErrors([...new Set(rejections.map((r) => r.message))]);
@@ -558,7 +1474,7 @@ function ChatComposer({ value, defaultValue = "", onValueChange, onSubmit, strea
558
1474
  }
559
1475
  //#endregion
560
1476
  //#region src/ChatComposer/ChatComposer.doc.ts
561
- const doc$1 = {
1477
+ const doc$2 = {
562
1478
  name: "ChatComposer",
563
1479
  group: "Forms",
564
1480
  status: "beta",
@@ -636,7 +1552,7 @@ function TypingIndicator({ author, showLabel = false, label, size = "md", announ
636
1552
  }
637
1553
  //#endregion
638
1554
  //#region src/TypingIndicator/TypingIndicator.doc.ts
639
- const doc$11 = {
1555
+ const doc$12 = {
640
1556
  name: "TypingIndicator",
641
1557
  group: "Feedback",
642
1558
  status: "beta",
@@ -645,26 +1561,30 @@ const doc$11 = {
645
1561
  tokens: ["muted-foreground"]
646
1562
  };
647
1563
  //#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
- };
1564
+ //#region src/utils/format.ts
1565
+ /** "840 ms" below a second, "2.4 s" below a minute, "1 m 05 s" above. */
1566
+ function formatDuration(ms) {
1567
+ if (ms < 1e3) return `${Math.round(ms)} ms`;
1568
+ if (ms < 6e4) return `${(ms / 1e3).toFixed(1)} s`;
1569
+ const m = Math.floor(ms / 6e4);
1570
+ const s = Math.round(ms % 6e4 / 1e3);
1571
+ return `${m} m ${String(s).padStart(2, "0")} s`;
1572
+ }
1573
+ /** Short local time ("14:05") for a message timestamp. */
1574
+ function formatTime(date) {
1575
+ return date.toLocaleTimeString(void 0, {
1576
+ hour: "2-digit",
1577
+ minute: "2-digit"
1578
+ });
1579
+ }
1580
+ /** Pretty-prints any value as JSON; falls back to String() for cyclic or exotic values. */
1581
+ function toJson(value) {
1582
+ if (typeof value === "string") return value;
1583
+ try {
1584
+ return JSON.stringify(value, null, 2) ?? String(value);
1585
+ } catch {
1586
+ return String(value);
1587
+ }
668
1588
  }
669
1589
  //#endregion
670
1590
  //#region src/ChatMessage/chatMessage.variants.ts
@@ -877,7 +1797,7 @@ function ChatAnnouncer({ streaming, lastReply, author, maxLength = 600 }) {
877
1797
  }
878
1798
  //#endregion
879
1799
  //#region src/ChatMessage/ChatMessage.doc.ts
880
- const doc$4 = {
1800
+ const doc$5 = {
881
1801
  name: "ChatMessage",
882
1802
  group: "Display",
883
1803
  status: "beta",
@@ -1107,7 +2027,7 @@ function MessageFeedback({ value: valueProp, onFeedback, reasons: reasonsProp, a
1107
2027
  }
1108
2028
  //#endregion
1109
2029
  //#region src/MessageFeedback/MessageFeedback.doc.ts
1110
- const doc$7 = {
2030
+ const doc$8 = {
1111
2031
  name: "MessageFeedback",
1112
2032
  group: "Actions",
1113
2033
  status: "beta",
@@ -1318,7 +2238,7 @@ const DEMO_MODELS = [
1318
2238
  ];
1319
2239
  //#endregion
1320
2240
  //#region src/ModelPicker/ModelPicker.doc.ts
1321
- const doc$8 = {
2241
+ const doc$9 = {
1322
2242
  name: "ModelPicker",
1323
2243
  group: "Forms",
1324
2244
  status: "beta",
@@ -1560,7 +2480,7 @@ function ChatLayout({ children, composer, header, panel, panelLabel: panelLabelP
1560
2480
  }
1561
2481
  //#endregion
1562
2482
  //#region src/ChatLayout/ChatLayout.doc.ts
1563
- const doc$3 = {
2483
+ const doc$4 = {
1564
2484
  name: "ChatLayout",
1565
2485
  group: "Layout",
1566
2486
  status: "beta",
@@ -1648,385 +2568,121 @@ function Citation({ index, source, delay = 200, className }) {
1648
2568
  className: citationStyles.index,
1649
2569
  children: index
1650
2570
  }),
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
- });
2571
+ /* @__PURE__ */ jsx(Globe, {
2572
+ size: 12,
2573
+ "aria-hidden": true
2574
+ }),
2575
+ host && /* @__PURE__ */ jsx("span", {
2576
+ className: "truncate",
2577
+ children: host
2578
+ })
2579
+ ]
2580
+ }),
2581
+ /* @__PURE__ */ jsx(PopoverTitle, {
2582
+ className: citationStyles.title,
2583
+ children: source.title
2584
+ }),
2585
+ source.snippet && /* @__PURE__ */ jsx(PopoverDescription, {
2586
+ className: citationStyles.snippet,
2587
+ children: source.snippet
2588
+ }),
2589
+ href && /* @__PURE__ */ jsxs("a", {
2590
+ href,
2591
+ target: "_blank",
2592
+ rel: "noopener noreferrer nofollow",
2593
+ className: citationStyles.open,
2594
+ children: [
2595
+ t("chat.openSource"),
2596
+ /* @__PURE__ */ jsx(ExternalLink, {
2597
+ size: 12,
2598
+ "aria-hidden": true
2599
+ }),
2600
+ /* @__PURE__ */ jsxs("span", {
2601
+ className: "sr-only",
2602
+ children: [" ", t("link.external")]
2603
+ })
2604
+ ]
2605
+ })
2606
+ ]
2607
+ })] });
1881
2608
  }
1882
- function TaskCheckbox({ checked }) {
2609
+ /** Numbered list of the sources cited in a reply, matching the inline markers. */
2610
+ function CitationList({ sources, heading: headingProp, className }) {
1883
2611
  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
2612
+ const heading = headingProp ?? t("chat.sources");
2613
+ const headingId = useId();
2614
+ if (sources.length === 0) return null;
2615
+ return /* @__PURE__ */ jsxs("section", {
2616
+ "aria-labelledby": headingId,
2617
+ className,
2618
+ children: [/* @__PURE__ */ jsx("h3", {
2619
+ id: headingId,
2620
+ className: citationStyles.heading,
2621
+ children: heading
2622
+ }), /* @__PURE__ */ jsx("ol", {
2623
+ className: citationStyles.list,
2624
+ children: sources.map((src, i) => {
2625
+ const href = safeSourceUrl(src.url);
2626
+ const host = src.publisher ?? sourceHost(src.url);
2627
+ return /* @__PURE__ */ jsxs("li", {
2628
+ className: citationStyles.item,
2629
+ children: [/* @__PURE__ */ jsx("span", {
2630
+ className: citationStyles.index,
2631
+ "aria-hidden": true,
2632
+ children: i + 1
2633
+ }), /* @__PURE__ */ jsxs("div", {
2634
+ className: citationStyles.itemBody,
2635
+ children: [href ? /* @__PURE__ */ jsxs("a", {
2636
+ href,
2637
+ target: "_blank",
2638
+ rel: "noopener noreferrer nofollow",
2639
+ className: citationStyles.itemTitle,
2640
+ children: [src.title, /* @__PURE__ */ jsxs("span", {
2641
+ className: "sr-only",
2642
+ children: [" ", t("link.external")]
2643
+ })]
2644
+ }) : /* @__PURE__ */ jsx("span", {
2645
+ className: "text-[13px] font-medium text-foreground",
2646
+ children: src.title
2647
+ }), host && /* @__PURE__ */ jsx("div", {
2648
+ className: citationStyles.itemHost,
2649
+ children: host
2650
+ })]
2651
+ })]
2652
+ }, `${i}-${src.title}`);
2653
+ })
2654
+ })]
1891
2655
  });
1892
2656
  }
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
- }
2657
+ //#endregion
2658
+ //#region src/Citation/Citation.doc.ts
2659
+ const doc$6 = {
2660
+ name: "Citation",
2661
+ group: "Display",
2662
+ status: "beta",
2663
+ 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.",
2664
+ primitive: "@base-ui/react/popover",
2665
+ pattern: "dialog (non-modal)",
2666
+ keyboard: [
2667
+ ["Enter", "On a marker: opens the source preview"],
2668
+ ["Space", "On a marker: opens the source preview"],
2669
+ ["Escape", "Closes the preview and returns focus to the marker"],
2670
+ ["Tab", "Moves to the \"Open source\" link inside an open preview"]
2671
+ ],
2672
+ tokens: [
2673
+ "primary",
2674
+ "primary-text",
2675
+ "popover",
2676
+ "secondary",
2677
+ "card",
2678
+ "border",
2679
+ "muted-foreground",
2680
+ "focus-ring"
2681
+ ]
2006
2682
  };
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
2683
  //#endregion
2028
2684
  //#region src/Markdown/Markdown.doc.ts
2029
- const doc$6 = {
2685
+ const doc$7 = {
2030
2686
  name: "Markdown",
2031
2687
  group: "Display",
2032
2688
  status: "beta",
@@ -2114,7 +2770,7 @@ function SuggestionChips({ suggestions, onSelect, layout = "cards", heading, lab
2114
2770
  }
2115
2771
  //#endregion
2116
2772
  //#region src/SuggestionChips/SuggestionChips.doc.ts
2117
- const doc$9 = {
2773
+ const doc$10 = {
2118
2774
  name: "SuggestionChips",
2119
2775
  group: "Actions",
2120
2776
  status: "beta",
@@ -2261,7 +2917,7 @@ function ToolCallCard({ name, status, durationMs, args, result, error, defaultOp
2261
2917
  }
2262
2918
  //#endregion
2263
2919
  //#region src/ToolCallCard/ToolCallCard.doc.ts
2264
- const doc$10 = {
2920
+ const doc$11 = {
2265
2921
  name: "ToolCallCard",
2266
2922
  group: "Display",
2267
2923
  status: "beta",
@@ -2501,7 +3157,7 @@ function ChatDemo({ withPanel = true, tickMs = 30, className }) {
2501
3157
  onAttach: attach,
2502
3158
  onRemoveAttachment: (id) => setFiles((all) => all.filter((f) => f.id !== id)),
2503
3159
  accept: ".pdf,.csv,.md,.txt,.json,image/*",
2504
- maxSize: 10485760,
3160
+ maxSize: 1e7,
2505
3161
  maxFiles: 5,
2506
3162
  modelPicker: /* @__PURE__ */ jsx(ModelPicker, {
2507
3163
  compact: true,
@@ -2577,7 +3233,7 @@ function ChatDemo({ withPanel = true, tickMs = 30, className }) {
2577
3233
  }
2578
3234
  //#endregion
2579
3235
  //#region src/ChatDemo/ChatDemo.doc.ts
2580
- const doc$2 = {
3236
+ const doc$3 = {
2581
3237
  name: "ChatDemo",
2582
3238
  group: "Layout",
2583
3239
  status: "experimental",
@@ -2592,6 +3248,6 @@ const doc$2 = {
2592
3248
  ]
2593
3249
  };
2594
3250
  //#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 };
3251
+ 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
3252
 
2597
3253
  //# sourceMappingURL=index.js.map