@cerebro-labs/agentnava-sdk 0.50.0 → 0.53.2

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/react.css ADDED
@@ -0,0 +1,409 @@
1
+ /*
2
+ * Default look for @cerebro-labs/agentnava-sdk/react.
3
+ *
4
+ * import '@cerebro-labs/agentnava-sdk/react/styles.css'
5
+ *
6
+ * Theme it by setting these variables on any ancestor (or :root). Fonts are
7
+ * inherited from your page. Every class starts with an-w- so nothing here
8
+ * reaches the rest of your app.
9
+ */
10
+ .an-w,
11
+ .an-reply {
12
+ --an-w-ink: #16181d;
13
+ --an-w-muted: #6b7280;
14
+ --an-w-line: #e5e7eb;
15
+ --an-w-surface: #f8f9fb;
16
+ --an-w-tint: #eef2f7;
17
+ --an-w-accent: #1f2937;
18
+ --an-w-accent-ink: #ffffff;
19
+ --an-w-s0: #2563eb;
20
+ --an-w-s1: #d97706;
21
+ --an-w-s2: #059669;
22
+ --an-w-s3: #7c3aed;
23
+ --an-w-high: #dc2626;
24
+ --an-w-medium: #d97706;
25
+ --an-w-good: #059669;
26
+ --an-w-info: #2563eb;
27
+ --an-w-radius: 8px;
28
+ }
29
+
30
+ .an-reply > * + * {
31
+ margin-top: 8px;
32
+ }
33
+ .an-reply-text {
34
+ margin: 0;
35
+ }
36
+
37
+ .an-w {
38
+ color: var(--an-w-ink);
39
+ font-size: 0.95em;
40
+ line-height: 1.45;
41
+ max-width: 100%;
42
+ }
43
+ .an-w p,
44
+ .an-w figure,
45
+ .an-w dl,
46
+ .an-w ul,
47
+ .an-w ol {
48
+ margin: 0;
49
+ }
50
+
51
+ .an-w-col {
52
+ display: flex;
53
+ flex-direction: column;
54
+ gap: 10px;
55
+ }
56
+ .an-w-gap-small {
57
+ gap: 6px;
58
+ }
59
+ .an-w-gap-large {
60
+ gap: 16px;
61
+ }
62
+ .an-w-row {
63
+ display: flex;
64
+ flex-wrap: wrap;
65
+ gap: 8px;
66
+ }
67
+ .an-w-row > * {
68
+ flex: 1 1 0;
69
+ min-width: 96px;
70
+ }
71
+ .an-w-row.is-scroll {
72
+ flex-wrap: nowrap;
73
+ overflow-x: auto;
74
+ scroll-snap-type: x mandatory;
75
+ }
76
+ .an-w-row.is-scroll > * {
77
+ flex: 0 0 72%;
78
+ scroll-snap-align: start;
79
+ }
80
+
81
+ .an-w-card {
82
+ border: 1px solid var(--an-w-line);
83
+ border-radius: var(--an-w-radius);
84
+ background: var(--an-w-surface);
85
+ overflow: hidden;
86
+ }
87
+ .an-w-card > img {
88
+ display: block;
89
+ width: 100%;
90
+ aspect-ratio: 16 / 9;
91
+ object-fit: cover;
92
+ }
93
+ .an-w-card-body {
94
+ display: flex;
95
+ flex-direction: column;
96
+ gap: 6px;
97
+ padding: 10px 12px;
98
+ }
99
+ .an-w-card-link {
100
+ color: inherit;
101
+ text-decoration: none;
102
+ }
103
+ .an-w-card-link:hover .an-w-card {
104
+ border-color: var(--an-w-muted);
105
+ }
106
+
107
+ .an-w-text.is-title {
108
+ font-size: 1.15em;
109
+ font-weight: 600;
110
+ }
111
+ .an-w-text.is-heading {
112
+ font-weight: 600;
113
+ }
114
+ .an-w-text.is-caption {
115
+ color: var(--an-w-muted);
116
+ font-size: 0.9em;
117
+ }
118
+ .an-w-title {
119
+ color: var(--an-w-muted);
120
+ font-size: 0.9em;
121
+ font-weight: 500;
122
+ margin-bottom: 4px;
123
+ }
124
+
125
+ .an-w-stat {
126
+ display: flex;
127
+ flex-direction: column;
128
+ gap: 1px;
129
+ padding: 8px 10px;
130
+ border-radius: var(--an-w-radius);
131
+ background: var(--an-w-tint);
132
+ }
133
+ .an-w-stat strong {
134
+ font-size: 1.25em;
135
+ font-weight: 600;
136
+ font-variant-numeric: tabular-nums;
137
+ }
138
+ .an-w-stat span {
139
+ font-weight: 500;
140
+ }
141
+ .an-w-stat small {
142
+ color: var(--an-w-muted);
143
+ font-size: 0.85em;
144
+ line-height: 1.3;
145
+ }
146
+
147
+ .an-w-kv dl > div {
148
+ display: flex;
149
+ justify-content: space-between;
150
+ gap: 12px;
151
+ padding: 5px 0;
152
+ border-bottom: 1px solid var(--an-w-line);
153
+ }
154
+ .an-w-kv dt {
155
+ color: var(--an-w-muted);
156
+ }
157
+ .an-w-kv dd {
158
+ margin: 0;
159
+ text-align: right;
160
+ font-variant-numeric: tabular-nums;
161
+ }
162
+ .an-w-kv .is-total {
163
+ border-bottom: 0;
164
+ font-weight: 600;
165
+ }
166
+ .an-w-kv .is-total dt {
167
+ color: var(--an-w-ink);
168
+ }
169
+
170
+ .an-w-table {
171
+ overflow-x: auto;
172
+ }
173
+ .an-w-table table {
174
+ border-collapse: collapse;
175
+ width: 100%;
176
+ }
177
+ .an-w-table th,
178
+ .an-w-table td {
179
+ text-align: left;
180
+ padding: 5px 10px 5px 0;
181
+ border-bottom: 1px solid var(--an-w-line);
182
+ white-space: nowrap;
183
+ }
184
+ .an-w-table th {
185
+ color: var(--an-w-muted);
186
+ font-weight: 500;
187
+ }
188
+ .an-w-table td.is-num {
189
+ text-align: right;
190
+ font-variant-numeric: tabular-nums;
191
+ }
192
+
193
+ .an-w-chart figcaption {
194
+ color: var(--an-w-muted);
195
+ font-size: 0.9em;
196
+ font-weight: 500;
197
+ margin-bottom: 6px;
198
+ }
199
+ .an-w-chart svg {
200
+ display: block;
201
+ width: 100%;
202
+ height: auto;
203
+ overflow: visible;
204
+ }
205
+ .an-w-chart text {
206
+ font-size: 10px;
207
+ fill: var(--an-w-muted);
208
+ }
209
+ .an-w-chart rect.an-w-s0,
210
+ .an-w-chart circle.an-w-s0 {
211
+ fill: var(--an-w-s0);
212
+ }
213
+ .an-w-chart rect.an-w-s1,
214
+ .an-w-chart circle.an-w-s1 {
215
+ fill: var(--an-w-s1);
216
+ }
217
+ .an-w-chart rect.an-w-s2,
218
+ .an-w-chart circle.an-w-s2 {
219
+ fill: var(--an-w-s2);
220
+ }
221
+ .an-w-chart rect.an-w-s3,
222
+ .an-w-chart circle.an-w-s3 {
223
+ fill: var(--an-w-s3);
224
+ }
225
+ .an-w-line {
226
+ fill: none;
227
+ stroke-width: 2;
228
+ }
229
+ .an-w-line.an-w-s0 {
230
+ stroke: var(--an-w-s0);
231
+ }
232
+ .an-w-line.an-w-s1 {
233
+ stroke: var(--an-w-s1);
234
+ }
235
+ .an-w-line.an-w-s2 {
236
+ stroke: var(--an-w-s2);
237
+ }
238
+ .an-w-line.an-w-s3 {
239
+ stroke: var(--an-w-s3);
240
+ }
241
+ .an-w-ref line {
242
+ stroke: var(--an-w-muted);
243
+ stroke-dasharray: 4 3;
244
+ }
245
+ .an-w-legend {
246
+ display: flex;
247
+ flex-wrap: wrap;
248
+ gap: 12px;
249
+ color: var(--an-w-muted);
250
+ font-size: 0.9em;
251
+ margin-top: 4px;
252
+ }
253
+ .an-w-legend i {
254
+ display: inline-block;
255
+ width: 8px;
256
+ height: 8px;
257
+ border-radius: 50%;
258
+ margin-right: 5px;
259
+ }
260
+ .an-w-legend i.an-w-s0 {
261
+ background: var(--an-w-s0);
262
+ }
263
+ .an-w-legend i.an-w-s1 {
264
+ background: var(--an-w-s1);
265
+ }
266
+ .an-w-legend i.an-w-s2 {
267
+ background: var(--an-w-s2);
268
+ }
269
+ .an-w-legend i.an-w-s3 {
270
+ background: var(--an-w-s3);
271
+ }
272
+
273
+ .an-w-image img {
274
+ display: block;
275
+ max-width: 100%;
276
+ border-radius: var(--an-w-radius);
277
+ }
278
+ .an-w-image figcaption {
279
+ color: var(--an-w-muted);
280
+ font-size: 0.9em;
281
+ margin-top: 4px;
282
+ }
283
+
284
+ .an-w-list ul,
285
+ .an-w-list ol {
286
+ list-style: none;
287
+ padding: 0;
288
+ }
289
+ .an-w-list li {
290
+ display: flex;
291
+ align-items: baseline;
292
+ gap: 8px;
293
+ padding: 6px 0;
294
+ border-bottom: 1px solid var(--an-w-line);
295
+ }
296
+ .an-w-list li:last-child {
297
+ border-bottom: 0;
298
+ }
299
+ .an-w-list li > div {
300
+ flex: 1;
301
+ display: flex;
302
+ flex-direction: column;
303
+ }
304
+ .an-w-list li span,
305
+ .an-w-list li small {
306
+ color: var(--an-w-muted);
307
+ }
308
+ .an-w-dot {
309
+ flex: none;
310
+ width: 8px;
311
+ height: 8px;
312
+ border-radius: 50%;
313
+ background: var(--an-w-info);
314
+ }
315
+ .an-w-dot.is-high {
316
+ background: var(--an-w-high);
317
+ }
318
+ .an-w-dot.is-medium {
319
+ background: var(--an-w-medium);
320
+ }
321
+ .an-w-dot.is-good {
322
+ background: var(--an-w-good);
323
+ }
324
+
325
+ .an-w-note {
326
+ display: flex;
327
+ flex-direction: column;
328
+ gap: 2px;
329
+ padding: 8px 10px;
330
+ border-radius: var(--an-w-radius);
331
+ background: var(--an-w-tint);
332
+ }
333
+
334
+ .an-w-badge {
335
+ align-self: flex-start;
336
+ display: inline-block;
337
+ padding: 2px 8px;
338
+ border-radius: 999px;
339
+ font-size: 0.85em;
340
+ font-weight: 500;
341
+ background: var(--an-w-tint);
342
+ }
343
+ .an-w-badge.is-high {
344
+ color: var(--an-w-high);
345
+ }
346
+ .an-w-badge.is-medium {
347
+ color: var(--an-w-medium);
348
+ }
349
+ .an-w-badge.is-good {
350
+ color: var(--an-w-good);
351
+ }
352
+ .an-w-badge.is-info,
353
+ .an-w-badge.is-low {
354
+ color: var(--an-w-info);
355
+ }
356
+
357
+ .an-w-link {
358
+ color: var(--an-w-info);
359
+ font-weight: 500;
360
+ }
361
+
362
+ .an-w-button {
363
+ align-self: flex-start;
364
+ font: inherit;
365
+ padding: 6px 14px;
366
+ border-radius: var(--an-w-radius);
367
+ border: 1px solid var(--an-w-accent);
368
+ cursor: pointer;
369
+ }
370
+ .an-w-button.is-primary {
371
+ background: var(--an-w-accent);
372
+ color: var(--an-w-accent-ink);
373
+ }
374
+ .an-w-button.is-secondary {
375
+ background: transparent;
376
+ color: var(--an-w-ink);
377
+ border-color: var(--an-w-line);
378
+ }
379
+ .an-w-button:disabled,
380
+ .an-w-suggest button:disabled {
381
+ cursor: default;
382
+ opacity: 0.6;
383
+ }
384
+
385
+ .an-w-suggest {
386
+ display: flex;
387
+ flex-wrap: wrap;
388
+ gap: 6px;
389
+ }
390
+ .an-w-suggest button {
391
+ font: inherit;
392
+ padding: 6px 12px;
393
+ border-radius: 999px;
394
+ border: 1px solid var(--an-w-line);
395
+ background: transparent;
396
+ color: var(--an-w-ink);
397
+ cursor: pointer;
398
+ text-align: left;
399
+ }
400
+ .an-w-suggest button:hover:not(:disabled) {
401
+ border-color: var(--an-w-muted);
402
+ }
403
+
404
+ .an-w-divider {
405
+ border: 0;
406
+ border-top: 1px solid var(--an-w-line);
407
+ margin: 2px 0;
408
+ width: 100%;
409
+ }
package/react.d.ts ADDED
@@ -0,0 +1,40 @@
1
+ import { type ReactNode } from 'react';
2
+ import type { Widget, WidgetPart } from './types.ts';
3
+ export { placeWidgets, type ReplyPiece } from './widgets.ts';
4
+ /** Every kind of part an agent may draw. */
5
+ export type WidgetKind = 'Column' | 'Row' | 'Card' | 'Text' | 'Stat' | 'KeyValue' | 'Table' | 'Chart' | 'Image' | 'List' | 'Note' | 'Badge' | 'Link' | 'Button' | 'Suggestions' | 'Divider';
6
+ /** What a replacement for one kind of part receives. */
7
+ export interface PartProps {
8
+ part: WidgetPart;
9
+ /** The part's children, already drawn (containers only). */
10
+ children?: ReactNode;
11
+ /** Send words as the reader's next message (Button, Suggestions). */
12
+ onAsk?: ((text: string) => void) | undefined;
13
+ }
14
+ export interface WidgetViewProps {
15
+ widget: Widget;
16
+ /** Called with a Button's label or a tapped suggestion: send it as the next message. */
17
+ onAsk?: (text: string) => void;
18
+ /** Your own component for any kind of part, in place of the default. */
19
+ parts?: Partial<Record<WidgetKind, (props: PartProps) => ReactNode>>;
20
+ /** ISO currency code for charts and numbers marked as money. Default "USD". */
21
+ currency?: string;
22
+ /** Locale for numbers. Default: the reader's. */
23
+ locale?: string;
24
+ /** Whether a link or picture address may be opened. Default: https:// only. */
25
+ allowUrl?: (url: string, use: 'link' | 'image') => boolean;
26
+ className?: string;
27
+ }
28
+ export interface ReplyViewProps extends Omit<WidgetViewProps, 'widget' | 'className'> {
29
+ /** The reply's words, as `text` events or `done.reply` give them. */
30
+ text: string;
31
+ /** The reply's widgets, as `widget` events or `done.widgets` give them. */
32
+ widgets?: readonly Widget[] | undefined;
33
+ /** How to draw the words. Default: paragraphs. Pass your markdown renderer here. */
34
+ renderText?: (text: string) => ReactNode;
35
+ className?: string;
36
+ }
37
+ /** One widget, drawn from its root part. Draws nothing when there is no root yet. */
38
+ export declare function WidgetView(props: WidgetViewProps): import("react/jsx-runtime").JSX.Element | null;
39
+ /** A whole reply: its words, with each widget drawn where it sits. */
40
+ export declare function ReplyView({ text, widgets, renderText, className, ...widgetProps }: ReplyViewProps): import("react/jsx-runtime").JSX.Element;
package/react.js ADDED
@@ -0,0 +1,200 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
2
+ // Ready-made React views for the widgets an agent draws in its replies.
3
+ //
4
+ // import { ReplyView } from '@cerebro-labs/agentnava-sdk/react'
5
+ // import '@cerebro-labs/agentnava-sdk/react/styles.css'
6
+ //
7
+ // <ReplyView text={reply} widgets={widgets} onAsk={send} />
8
+ //
9
+ // React is optional for the SDK: only this entry needs it, and the rest of the
10
+ // package never imports it. Every kind of part has a default look (styles.css,
11
+ // themed through CSS variables), and any kind can be swapped for your own
12
+ // component with `parts`.
13
+ //
14
+ // A widget is written by the model, so it is drawn defensively: a kind this
15
+ // does not know, or a part missing a field, is skipped; each part is drawn
16
+ // once, even if two containers name it; links and pictures open only over
17
+ // https:// (narrow it further with `allowUrl`); and a widget that throws while
18
+ // drawing is left out rather than breaking the screen around it.
19
+ import { Component, Fragment } from 'react';
20
+ import { placeWidgets } from "./widgets.js";
21
+ export { placeWidgets } from "./widgets.js";
22
+ const str = (v) => (typeof v === 'string' || typeof v === 'number' ? String(v) : '');
23
+ const list = (v) => (Array.isArray(v) ? v : []);
24
+ const TONES = new Set(['high', 'medium', 'low', 'good', 'info']);
25
+ const toneOf = (v) => (typeof v === 'string' && TONES.has(v) ? v : 'info');
26
+ const httpsOnly = (url) => /^https:\/\//i.test(url);
27
+ /** A widget that throws while drawing is left out, never a crash of the screen. */
28
+ class Boundary extends Component {
29
+ state = { failed: false };
30
+ static getDerivedStateFromError() {
31
+ return { failed: true };
32
+ }
33
+ render() {
34
+ return this.state.failed ? null : this.props.children;
35
+ }
36
+ }
37
+ function Chart({ part, ctx }) {
38
+ const series = list(part.series)
39
+ .map((s) => ({
40
+ name: str(s?.name),
41
+ points: list(s?.points).filter((p) => typeof p?.y === 'number' && Number.isFinite(p.y)),
42
+ }))
43
+ .filter((s) => s.points.length > 0);
44
+ if (series.length === 0)
45
+ return null;
46
+ const xs = series[0].points.map((p) => str(p.x));
47
+ const ref = part.reference;
48
+ const refY = typeof ref?.y === 'number' && Number.isFinite(ref.y) ? ref.y : undefined;
49
+ const ys = [...series.flatMap((s) => s.points.map((p) => p.y)), ...(refY === undefined ? [] : [refY])];
50
+ // Zero sits between the extremes, so a negative value draws below the zero
51
+ // line instead of collapsing onto it and reading as 0.
52
+ const lo = Math.min(...ys, 0);
53
+ const span = Math.max(...ys, 0) - lo || 1;
54
+ const W = 320;
55
+ const H = 140;
56
+ const step = W / Math.max(xs.length, 1);
57
+ const fmt = (n) => (part.unit === 'currency' ? ctx.money(n) : part.unit === 'percent' ? `${ctx.number(n)}%` : ctx.number(n));
58
+ const yOf = (y) => H - ((y - lo) / span) * (H - 18);
59
+ const zero = yOf(0);
60
+ const labelled = series.length === 1 && xs.length <= 6;
61
+ return (_jsxs("figure", { className: "an-w-chart", children: [str(part.title) && _jsx("figcaption", { children: str(part.title) }), _jsxs("svg", { viewBox: `0 0 ${W} ${H + 18}`, role: "img", "aria-label": str(part.title) || 'Chart', children: [series.map((s, si) => part.type === 'bar' ? (s.points.map((p, i) => {
62
+ const bw = (step * 0.7) / series.length;
63
+ const x = i * step + step * 0.15 + si * bw;
64
+ const y = yOf(p.y);
65
+ return (_jsxs("g", { children: [_jsx("rect", { className: `an-w-s${si % 4}`, x: x, y: Math.min(y, zero), width: bw, height: Math.abs(zero - y), rx: 2, children: _jsx("title", { children: `${str(p.x)}: ${fmt(p.y)}` }) }), labelled && (_jsx("text", { x: x + bw / 2, y: Math.min(y, zero) - 4, textAnchor: "middle", children: fmt(p.y) }))] }, `${si}-${i}`));
66
+ })) : (_jsxs("g", { children: [_jsx("polyline", { className: `an-w-line an-w-s${si % 4}${part.type === 'area' ? ' is-area' : ''}`, points: s.points.map((p, i) => `${i * step + step / 2},${yOf(p.y)}`).join(' ') }), s.points.map((p, i) => (_jsxs("g", { children: [_jsx("circle", { className: `an-w-s${si % 4}`, cx: i * step + step / 2, cy: yOf(p.y), r: 3, children: _jsx("title", { children: `${str(p.x)}: ${fmt(p.y)}` }) }), labelled && (_jsx("text", { x: i * step + step / 2, y: yOf(p.y) - 7, textAnchor: "middle", children: fmt(p.y) }))] }, i)))] }, si))), lo < 0 && _jsx("line", { x1: 0, x2: W, y1: zero, y2: zero, stroke: "currentColor", strokeOpacity: 0.4 }), refY !== undefined && (_jsxs("g", { className: "an-w-ref", children: [_jsx("line", { x1: 0, x2: W, y1: yOf(refY), y2: yOf(refY) }), str(ref?.label) && (_jsx("text", { x: W, y: yOf(refY) - 4, textAnchor: "end", children: str(ref?.label) }))] })), xs.map((x, i) => (_jsx("text", { x: i * step + step / 2, y: H + 14, textAnchor: "middle", children: x }, i)))] }), series.length > 1 && (_jsx("div", { className: "an-w-legend", children: series.map((s, si) => (_jsxs("span", { children: [_jsx("i", { className: `an-w-s${si % 4}` }), s.name] }, si))) }))] }));
67
+ }
68
+ /** The default look of one part. `kids` is its children, already drawn. */
69
+ function defaultPart(part, kids, ctx) {
70
+ const onAsk = ctx.props.onAsk;
71
+ switch (part.component) {
72
+ case 'Column':
73
+ return _jsx("div", { className: `an-w-col an-w-gap-${['small', 'large'].includes(str(part.gap)) ? str(part.gap) : 'medium'}`, children: kids });
74
+ case 'Row':
75
+ return _jsx("div", { className: `an-w-row${part.scroll === true ? ' is-scroll' : ''}`, children: kids });
76
+ case 'Card': {
77
+ const image = ctx.url(part.image, 'image');
78
+ const href = ctx.url(part.href, 'link');
79
+ const body = (_jsxs("div", { className: "an-w-card", children: [image && _jsx("img", { src: image, alt: str(part.imageAlt), referrerPolicy: "no-referrer", loading: "lazy" }), _jsx("div", { className: "an-w-card-body", children: kids })] }));
80
+ return href ? (_jsx("a", { className: "an-w-card-link", href: href, target: "_blank", rel: "noreferrer noopener", children: body })) : (body);
81
+ }
82
+ case 'Text': {
83
+ if (!str(part.text))
84
+ return null;
85
+ const v = ['title', 'heading', 'caption'].includes(str(part.variant)) ? str(part.variant) : 'body';
86
+ return _jsx("p", { className: `an-w-text is-${v}`, children: str(part.text) });
87
+ }
88
+ case 'Stat':
89
+ if (!str(part.value))
90
+ return null;
91
+ return (_jsxs("div", { className: "an-w-stat", children: [_jsx("strong", { children: str(part.value) }), str(part.label) && _jsx("span", { children: str(part.label) }), str(part.caption) && _jsx("small", { children: str(part.caption) })] }));
92
+ case 'KeyValue': {
93
+ const rows = list(part.rows).filter((r) => str(r?.label) || str(r?.value));
94
+ if (rows.length === 0)
95
+ return null;
96
+ return (_jsxs("div", { className: "an-w-kv", children: [str(part.title) && _jsx("div", { className: "an-w-title", children: str(part.title) }), _jsx("dl", { children: rows.map((r, i) => (_jsxs("div", { className: r?.emphasis ? 'is-total' : undefined, children: [_jsx("dt", { children: str(r?.label) }), _jsx("dd", { children: str(r?.value) })] }, i))) })] }));
97
+ }
98
+ case 'Table': {
99
+ const cols = list(part.columns).map(str);
100
+ const rows = list(part.rows).filter(Array.isArray);
101
+ if (cols.length === 0 && rows.length === 0)
102
+ return null;
103
+ return (_jsxs("div", { className: "an-w-table", children: [str(part.title) && _jsx("div", { className: "an-w-title", children: str(part.title) }), _jsxs("table", { children: [cols.length > 0 && (_jsx("thead", { children: _jsx("tr", { children: cols.map((c, j) => (_jsx("th", { children: c }, j))) }) })), _jsx("tbody", { children: rows.map((r, i) => (_jsx("tr", { children: r.map((c, j) => (_jsx("td", { className: typeof c === 'number' ? 'is-num' : undefined, children: typeof c === 'number' ? ctx.number(c) : str(c) }, j))) }, i))) })] })] }));
104
+ }
105
+ case 'Chart':
106
+ return _jsx(Chart, { part: part, ctx: ctx });
107
+ case 'Image': {
108
+ const url = ctx.url(part.url, 'image');
109
+ return url ? (_jsxs("figure", { className: "an-w-image", children: [_jsx("img", { src: url, alt: str(part.alt), referrerPolicy: "no-referrer", loading: "lazy" }), str(part.caption) && _jsx("figcaption", { children: str(part.caption) })] })) : null;
110
+ }
111
+ case 'List': {
112
+ const items = list(part.items).filter((it) => str(it?.title));
113
+ if (items.length === 0)
114
+ return null;
115
+ const Tag = part.ordered === true ? 'ol' : 'ul';
116
+ return (_jsxs("div", { className: "an-w-list", children: [str(part.title) && _jsx("div", { className: "an-w-title", children: str(part.title) }), _jsx(Tag, { children: items.map((it, i) => (_jsxs("li", { children: [_jsx("i", { className: `an-w-dot is-${toneOf(it?.tone)}`, "aria-hidden": "true" }), _jsxs("div", { children: [_jsx("strong", { children: str(it?.title) }), str(it?.detail) && _jsx("span", { children: str(it?.detail) })] }), str(it?.meta) && _jsx("small", { children: str(it?.meta) })] }, i))) })] }));
117
+ }
118
+ case 'Note':
119
+ if (!str(part.text))
120
+ return null;
121
+ return (_jsxs("div", { className: "an-w-note", children: [str(part.title) && _jsx("strong", { children: str(part.title) }), _jsx("span", { children: str(part.text) })] }));
122
+ case 'Badge':
123
+ return str(part.text) ? _jsx("span", { className: `an-w-badge is-${toneOf(part.tone)}`, children: str(part.text) }) : null;
124
+ case 'Link': {
125
+ const url = ctx.url(part.url, 'link');
126
+ return url && str(part.label) ? (_jsx("a", { className: "an-w-link", href: url, target: "_blank", rel: "noreferrer noopener", children: str(part.label) })) : null;
127
+ }
128
+ case 'Button':
129
+ if (!str(part.label))
130
+ return null;
131
+ return (_jsx("button", { type: "button", className: `an-w-button is-${part.variant === 'secondary' ? 'secondary' : 'primary'}`, disabled: !onAsk, onClick: () => onAsk?.(str(part.label)), children: str(part.label) }));
132
+ case 'Suggestions': {
133
+ const options = list(part.options).map(str).filter(Boolean);
134
+ if (options.length === 0)
135
+ return null;
136
+ return (_jsx("div", { className: "an-w-suggest", children: options.map((o, i) => (_jsx("button", { type: "button", disabled: !onAsk, onClick: () => onAsk?.(o), children: o }, i))) }));
137
+ }
138
+ case 'Divider':
139
+ return _jsx("hr", { className: "an-w-divider" });
140
+ default:
141
+ return null;
142
+ }
143
+ }
144
+ const CONTAINERS = new Set(['Column', 'Row', 'Card']);
145
+ // A plain function, not a component: the set of parts already drawn lives for
146
+ // one render of the widget. As a component, React's development mode would draw
147
+ // each part twice and find every one "already drawn" on its second pass.
148
+ function draw(id, ctx) {
149
+ const part = ctx.byId.get(id);
150
+ // A part listed twice, or a loop of containers, is drawn once.
151
+ if (!part || ctx.seen.has(id))
152
+ return null;
153
+ ctx.seen.add(id);
154
+ const kids = CONTAINERS.has(part.component)
155
+ ? list(part.children)
156
+ .filter((k) => typeof k === 'string')
157
+ .map((k) => _jsx(Fragment, { children: draw(k, ctx) }, k))
158
+ : undefined;
159
+ const own = ctx.props.parts?.[part.component];
160
+ return own ? own({ part, children: kids, onAsk: ctx.props.onAsk }) : defaultPart(part, kids, ctx);
161
+ }
162
+ /** One widget, drawn from its root part. Draws nothing when there is no root yet. */
163
+ export function WidgetView(props) {
164
+ const { widget, currency = 'USD', locale, allowUrl } = props;
165
+ const byId = new Map();
166
+ for (const p of Array.isArray(widget?.components) ? widget.components : []) {
167
+ if (p && typeof p === 'object' && typeof p.id === 'string' && typeof p.component === 'string' && !byId.has(p.id))
168
+ byId.set(p.id, p);
169
+ }
170
+ if (!byId.has('root'))
171
+ return null;
172
+ let moneyFormat;
173
+ try {
174
+ moneyFormat = new Intl.NumberFormat(locale, { style: 'currency', currency, maximumFractionDigits: 0 });
175
+ }
176
+ catch {
177
+ moneyFormat = new Intl.NumberFormat(locale, { maximumFractionDigits: 0 });
178
+ }
179
+ const numberFormat = new Intl.NumberFormat(locale);
180
+ const ctx = {
181
+ byId,
182
+ seen: new Set(),
183
+ props,
184
+ money: (n) => moneyFormat.format(n),
185
+ number: (n) => numberFormat.format(n),
186
+ url: (v, use) => (typeof v === 'string' && httpsOnly(v) && (allowUrl ? allowUrl(v, use) : true) ? v : undefined),
187
+ };
188
+ return (_jsx(Boundary, { children: _jsx("div", { className: `an-w${props.className ? ` ${props.className}` : ''}`, "data-surface": widget.surfaceId, children: draw('root', ctx) }) }));
189
+ }
190
+ function Paragraphs({ text }) {
191
+ return (_jsx(_Fragment, { children: text
192
+ .trim()
193
+ .split(/\n{2,}/)
194
+ .map((p, i) => (_jsx("p", { className: "an-reply-text", children: p.split('\n').map((line, j) => (_jsxs(Fragment, { children: [j > 0 && _jsx("br", {}), line] }, j))) }, i))) }));
195
+ }
196
+ /** A whole reply: its words, with each widget drawn where it sits. */
197
+ export function ReplyView({ text, widgets, renderText, className, ...widgetProps }) {
198
+ return (_jsx("div", { className: `an-reply${className ? ` ${className}` : ''}`, children: placeWidgets(text, widgets ?? []).map((piece, i) => piece.kind === 'text' ? (_jsx(Fragment, { children: renderText ? renderText(piece.text) : _jsx(Paragraphs, { text: piece.text }) }, `t${i}`)) : (_jsx(WidgetView, { widget: piece.widget, ...widgetProps }, piece.widget.surfaceId))) }));
199
+ }
200
+ //# sourceMappingURL=react.js.map