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