@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.d.ts +92 -14
- package/dist/index.js +1099 -443
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
package/dist/index.js
CHANGED
|
@@ -1,42 +1,10 @@
|
|
|
1
1
|
'use client';
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
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 {
|
|
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$
|
|
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$
|
|
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/
|
|
649
|
-
/**
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
const
|
|
655
|
-
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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$
|
|
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
|
-
|
|
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
|
-
|
|
1885
|
-
|
|
1886
|
-
|
|
1887
|
-
|
|
1888
|
-
|
|
1889
|
-
|
|
1890
|
-
|
|
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
|
-
|
|
1894
|
-
|
|
1895
|
-
|
|
1896
|
-
|
|
1897
|
-
|
|
1898
|
-
|
|
1899
|
-
|
|
1900
|
-
|
|
1901
|
-
|
|
1902
|
-
|
|
1903
|
-
|
|
1904
|
-
|
|
1905
|
-
|
|
1906
|
-
|
|
1907
|
-
|
|
1908
|
-
|
|
1909
|
-
|
|
1910
|
-
|
|
1911
|
-
|
|
1912
|
-
|
|
1913
|
-
|
|
1914
|
-
|
|
1915
|
-
|
|
1916
|
-
|
|
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$
|
|
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$
|
|
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$
|
|
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:
|
|
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$
|
|
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$
|
|
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
|