@adecore/agents-react 0.28.0 → 0.29.1
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/chat/ui/intelligent-ui/UiBlockFrame.js +19 -9
- package/dist/chat/ui/intelligent-ui/UiFallbackPart.js +4 -2
- package/dist/chat/ui/intelligent-ui/UiReply.js +3 -3
- package/dist/chat/ui/intelligent-ui/renderers/chart.js +2 -2
- package/dist/chat/ui/intelligent-ui/renderers/choices.js +13 -8
- package/dist/chat/ui/intelligent-ui/renderers/content.js +15 -14
- package/dist/chat/ui/intelligent-ui/renderers/data.js +19 -6
- package/dist/chat/ui/intelligent-ui/renderers/inputs.js +10 -9
- package/dist/chat/ui/intelligent-ui/renderers/links.js +5 -3
- package/dist/chat/ui/intelligent-ui/renderers/map.js +7 -7
- package/dist/chat/ui/intelligent-ui/renderers/structure.js +5 -5
- package/dist/chat/ui/intelligent-ui/renderers/text.js +25 -14
- package/dist/chat/ui/intelligent-ui/tones.d.ts +2 -0
- package/dist/chat/ui/intelligent-ui/tones.js +16 -0
- package/dist/theme.css +38 -0
- package/package.json +8 -8
- package/src/chat/ui/intelligent-ui/UiBlockFrame.tsx +26 -13
- package/src/chat/ui/intelligent-ui/UiFallbackPart.tsx +7 -4
- package/src/chat/ui/intelligent-ui/UiReply.tsx +3 -3
- package/src/chat/ui/intelligent-ui/renderers/chart.tsx +2 -2
- package/src/chat/ui/intelligent-ui/renderers/choices.tsx +33 -19
- package/src/chat/ui/intelligent-ui/renderers/content.tsx +36 -23
- package/src/chat/ui/intelligent-ui/renderers/data.tsx +61 -19
- package/src/chat/ui/intelligent-ui/renderers/inputs.tsx +21 -14
- package/src/chat/ui/intelligent-ui/renderers/links.tsx +7 -4
- package/src/chat/ui/intelligent-ui/renderers/map.tsx +15 -12
- package/src/chat/ui/intelligent-ui/renderers/structure.tsx +6 -6
- package/src/chat/ui/intelligent-ui/renderers/text.tsx +63 -42
- package/src/chat/ui/intelligent-ui/tones.ts +18 -0
- package/src/theme.css +38 -0
|
@@ -18,10 +18,18 @@ const SourceNumbers = createContext<ReadonlyMap<string, number>>(new Map());
|
|
|
18
18
|
|
|
19
19
|
/* Plain text in the same measure until the node closes, so highlighting never makes it jump. */
|
|
20
20
|
export function CodeBlockRenderer({ node }: UiRendererProps<UiProps<'CodeBlock'>>) {
|
|
21
|
+
const { language } = node.props;
|
|
21
22
|
return (
|
|
22
|
-
<
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
<div className="chat-ui-code chat-ui-flat flex flex-col rounded-xl bg-ui-tile p-1.5">
|
|
24
|
+
{language !== undefined && language !== '' && (
|
|
25
|
+
<div className="chat-ui-flat-inner flex px-1.5 pt-0.5 pb-1.5">
|
|
26
|
+
<span className="rounded-md bg-ui-inset px-1.5 font-mono text-2xs text-text shadow-[inset_0_0_0_1px_var(--border)]">{language}</span>
|
|
27
|
+
</div>
|
|
28
|
+
)}
|
|
29
|
+
<CodeStreamingContext.Provider value={!node.complete}>
|
|
30
|
+
<CodeBlock code={uiNodeText(node).replace(/^\n|\n$/g, '')} lang={language ?? 'text'} />
|
|
31
|
+
</CodeStreamingContext.Provider>
|
|
32
|
+
</div>
|
|
25
33
|
);
|
|
26
34
|
}
|
|
27
35
|
|
|
@@ -50,7 +58,7 @@ function AttachedImage({
|
|
|
50
58
|
const host = useChatHost();
|
|
51
59
|
const source = host.attachments.useUrl(scopeId, chatId, attachmentId);
|
|
52
60
|
if (!cell) {
|
|
53
|
-
return <ImageThumb source={source} alt={alt} className="max-h-70 max-w-full object-contain" />;
|
|
61
|
+
return <ImageThumb source={source} alt={alt} className="max-h-70 max-w-full rounded-xl object-contain" />;
|
|
54
62
|
}
|
|
55
63
|
return (
|
|
56
64
|
<>
|
|
@@ -77,7 +85,7 @@ export function ImageRenderer({ node, children, context }: UiRendererProps<UiPro
|
|
|
77
85
|
) : (
|
|
78
86
|
<AttachedImage scopeId={context.scopeId} chatId={context.chatId} attachmentId={attachment} alt={name} cell={cell} unnamed={caption === ''} />
|
|
79
87
|
)}
|
|
80
|
-
{caption !== '' && <figcaption className=
|
|
88
|
+
{caption !== '' && <figcaption className="px-0.5 text-2xs wrap-anywhere text-text-muted">{children}</figcaption>}
|
|
81
89
|
{cell && caption === '' && attachment === undefined && (
|
|
82
90
|
<figcaption className="text-xs wrap-anywhere text-text-faint">{alt ?? t('blocks.image.unavailable')}</figcaption>
|
|
83
91
|
)}
|
|
@@ -90,7 +98,7 @@ export function GalleryRenderer({ node, children }: UiRendererProps<UiProps<'Gal
|
|
|
90
98
|
const { t } = useTranslation('agent-chat');
|
|
91
99
|
return (
|
|
92
100
|
<GalleryContext value>
|
|
93
|
-
<div role="group" aria-label={t('blocks.gallery.label', { count: uiChildrenOf(node, 'Image').length })} className="@container
|
|
101
|
+
<div role="group" aria-label={t('blocks.gallery.label', { count: uiChildrenOf(node, 'Image').length })} className="@container">
|
|
94
102
|
<div className="chat-ui-nodes grid grid-cols-2 gap-2 @min-[420px]:grid-cols-3">{children}</div>
|
|
95
103
|
</div>
|
|
96
104
|
</GalleryContext>
|
|
@@ -129,7 +137,7 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
|
|
|
129
137
|
body = (
|
|
130
138
|
// The frame keeps the picture's shape inside its tallest height: past it the width follows, so a portrait picture is not letterboxed.
|
|
131
139
|
<div
|
|
132
|
-
className="relative max-h-96 overflow-hidden rounded-
|
|
140
|
+
className="relative max-h-96 overflow-hidden rounded-xl bg-ui-tile"
|
|
133
141
|
style={{ aspectRatio: aspect, width: `min(100%, ${COMPARE_MAX_HEIGHT * aspect}px)` }}
|
|
134
142
|
>
|
|
135
143
|
<Wipe
|
|
@@ -163,14 +171,14 @@ export function CompareRenderer({ node, children, context }: UiRendererProps<UiP
|
|
|
163
171
|
body = (
|
|
164
172
|
<>
|
|
165
173
|
<ImageThumb source={shown.source} alt={shown.name} className="max-h-70 max-w-full object-contain" />
|
|
166
|
-
<span className="
|
|
174
|
+
<span className="text-2xs text-text-faint">{t('blocks.compare.missing')}</span>
|
|
167
175
|
</>
|
|
168
176
|
);
|
|
169
177
|
}
|
|
170
178
|
return (
|
|
171
|
-
<figure className="flex flex-col items-start gap-1.5
|
|
179
|
+
<figure className="flex w-full flex-col items-start gap-1.5">
|
|
172
180
|
{body}
|
|
173
|
-
{caption !== '' && <figcaption className="text-
|
|
181
|
+
{caption !== '' && <figcaption className="px-0.5 text-2xs text-text-muted">{children}</figcaption>}
|
|
174
182
|
</figure>
|
|
175
183
|
);
|
|
176
184
|
}
|
|
@@ -204,7 +212,7 @@ export function ModelRenderer({ node, children, context }: UiRendererProps<UiPro
|
|
|
204
212
|
let body: ReactNode;
|
|
205
213
|
if (View !== undefined && source.url !== null) {
|
|
206
214
|
body = (
|
|
207
|
-
<div role="group" aria-label={label} className="relative h-80 w-full overflow-hidden rounded-
|
|
215
|
+
<div role="group" aria-label={label} className="relative h-80 w-full overflow-hidden rounded-xl bg-ui-tile">
|
|
208
216
|
<ErrorBoundary label={t('blocks.model.crashed')} resetKeys={[attachment, source.url]} compact>
|
|
209
217
|
<View
|
|
210
218
|
scopeId={context.scopeId}
|
|
@@ -219,10 +227,10 @@ export function ModelRenderer({ node, children, context }: UiRendererProps<UiPro
|
|
|
219
227
|
</div>
|
|
220
228
|
);
|
|
221
229
|
} else if (View !== undefined && source.failure === null) {
|
|
222
|
-
body = <div aria-busy="true" aria-label={label} className="h-80 w-full rounded-
|
|
230
|
+
body = <div aria-busy="true" aria-label={label} className="h-80 w-full rounded-xl bg-ui-tile" />;
|
|
223
231
|
} else {
|
|
224
232
|
body = (
|
|
225
|
-
<div className="flex w-full min-w-0 items-center gap-2 rounded-
|
|
233
|
+
<div className="flex w-full min-w-0 items-center gap-2 rounded-xl bg-ui-tile px-3 py-2 text-xs">
|
|
226
234
|
<Icon icon={Box} size={14} className="shrink-0 text-text-muted" />
|
|
227
235
|
<span className="min-w-0 truncate text-text">{alt ?? attachment}</span>
|
|
228
236
|
<span className="ml-auto shrink-0 text-text-faint">{source.failure === null ? t('blocks.model.label') : t('blocks.model.unavailable')}</span>
|
|
@@ -230,9 +238,9 @@ export function ModelRenderer({ node, children, context }: UiRendererProps<UiPro
|
|
|
230
238
|
);
|
|
231
239
|
}
|
|
232
240
|
return (
|
|
233
|
-
<figure className="flex flex-col items-start gap-1.5
|
|
241
|
+
<figure className="flex w-full flex-col items-start gap-1.5">
|
|
234
242
|
{body}
|
|
235
|
-
{caption !== '' && <figcaption className="text-
|
|
243
|
+
{caption !== '' && <figcaption className="px-0.5 text-2xs text-text-muted">{children}</figcaption>}
|
|
236
244
|
</figure>
|
|
237
245
|
);
|
|
238
246
|
}
|
|
@@ -243,8 +251,8 @@ export function SourcesRenderer({ node, children }: UiRendererProps<UiProps<'Sou
|
|
|
243
251
|
const numbers = new Map(uiChildrenOf(node, 'Source').map((source, index) => [source.id, index + 1]));
|
|
244
252
|
return (
|
|
245
253
|
<SourceNumbers value={numbers}>
|
|
246
|
-
<section aria-labelledby={headingId} className="flex flex-col">
|
|
247
|
-
<span id={headingId} className="px-
|
|
254
|
+
<section aria-labelledby={headingId} className="chat-ui-flat flex flex-col rounded-xl bg-ui-tile p-1.5">
|
|
255
|
+
<span id={headingId} className="chat-ui-flat-inner px-1.5 pt-0.5 pb-1 text-2xs font-medium text-text-muted">
|
|
248
256
|
{t('blocks.sources')}
|
|
249
257
|
</span>
|
|
250
258
|
<ol className="chat-ui-nodes flex flex-col">{children}</ol>
|
|
@@ -305,10 +313,12 @@ export function SourceRenderer({ node, context }: UiRendererProps<UiProps<'Sourc
|
|
|
305
313
|
const [measureDomain, domainCut] = useTruncated<HTMLSpanElement>();
|
|
306
314
|
const face = (
|
|
307
315
|
<>
|
|
308
|
-
<span className="
|
|
309
|
-
<span className="min-w-0 grow
|
|
310
|
-
|
|
311
|
-
{
|
|
316
|
+
<span className="grid size-5 shrink-0 place-items-center rounded-md bg-ui-inset text-2xs font-semibold text-text tabular-nums">{number}</span>
|
|
317
|
+
<span className="flex min-w-0 grow flex-col">
|
|
318
|
+
<span className="font-medium wrap-anywhere text-text">{title}</span>
|
|
319
|
+
<span ref={measureDomain} className="truncate text-text-muted">
|
|
320
|
+
{uiUrlDomain(url)}
|
|
321
|
+
</span>
|
|
312
322
|
</span>
|
|
313
323
|
</>
|
|
314
324
|
);
|
|
@@ -324,13 +334,16 @@ export function SourceRenderer({ node, context }: UiRendererProps<UiProps<'Sourc
|
|
|
324
334
|
}
|
|
325
335
|
>
|
|
326
336
|
{openUrl === undefined ? (
|
|
327
|
-
<span
|
|
337
|
+
<span
|
|
338
|
+
tabIndex={domainCut ? 0 : undefined}
|
|
339
|
+
className="chat-ui-flat-inner flex min-h-8 items-start gap-2.5 rounded-lg px-1.5 py-1.5 text-2xs"
|
|
340
|
+
>
|
|
328
341
|
{face}
|
|
329
342
|
</span>
|
|
330
343
|
) : (
|
|
331
344
|
<button
|
|
332
345
|
type="button"
|
|
333
|
-
className="flex min-h-8 w-full items-start gap-2 rounded-
|
|
346
|
+
className="chat-ui-flat-inner flex min-h-8 w-full items-start gap-2.5 rounded-lg px-1.5 py-1.5 text-left text-2xs hover:bg-text/4"
|
|
334
347
|
onClick={() => openUrl(url)}
|
|
335
348
|
>
|
|
336
349
|
{face}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
+
import { createContext, useContext } from 'react';
|
|
1
2
|
import clsx from 'clsx';
|
|
2
3
|
import { useTranslation } from 'react-i18next';
|
|
3
4
|
import { ArrowDown, ArrowRight, ArrowUp } from 'lucide-react';
|
|
4
5
|
import type { UiProps } from '@adecore/intelligent-ui';
|
|
5
|
-
import { Button, Icon, KeyValueList, Pill } from '@adecore/ui';
|
|
6
|
+
import { Button, Icon, KeyValueList, Pill, Tooltip } from '@adecore/ui';
|
|
6
7
|
import { formatBytes, formatDuration, formatMoment, formatNumber, formatPercent, formatRounded } from '@adecore/ui/format';
|
|
7
8
|
import { useBlockLocal } from '../block-local';
|
|
8
9
|
import { uiChildrenOf, uiWithUnit } from '../node-text';
|
|
9
10
|
import type { UiLiveValue, UiRendererProps } from '../render-context';
|
|
10
11
|
import { isNumericColumn, UI_TABLE_ROWS, uiTableCell, uiTableColumns, type UiTableCell, type UiTableColumn } from '../table-data';
|
|
11
|
-
import {
|
|
12
|
+
import { UI_TONE_PILL } from '../tones';
|
|
12
13
|
import { UiFallbackPart } from '../UiFallbackPart';
|
|
13
14
|
|
|
14
15
|
/* EntityList draws a hairline between its rows from this many on. */
|
|
15
16
|
const DIVIDED_FROM = 7;
|
|
16
17
|
|
|
18
|
+
/* Up to this many entries an EntityList is a row of tiles; a longer one is a list in one tile. */
|
|
19
|
+
const TILED_UP_TO = 4;
|
|
20
|
+
|
|
21
|
+
const EntityTiles = createContext(false);
|
|
22
|
+
|
|
17
23
|
/*
|
|
18
24
|
* A number that live readings keep up to date. Keyed by its text, so a value that changed mounts
|
|
19
25
|
* again and lights up once, even when it changes again before the last light settled.
|
|
@@ -31,7 +37,7 @@ export function StatsRenderer({ children }: UiRendererProps<UiProps<'Stats'>>) {
|
|
|
31
37
|
return <div className="chat-ui-nodes grid grid-cols-[repeat(auto-fit,minmax(128px,1fr))] gap-2">{children}</div>;
|
|
32
38
|
}
|
|
33
39
|
|
|
34
|
-
/* The arrow and the number say which way a value went; only a tone says whether that is good. */
|
|
40
|
+
/* The arrow and the number say which way a value went; only a tone says whether that is good. The tag says the change, its tooltip from what. */
|
|
35
41
|
export function StatRenderer({ node, context }: UiRendererProps<UiProps<'Stat'>>) {
|
|
36
42
|
const { t } = useTranslation('agent-chat');
|
|
37
43
|
const { label, value, previous, unit, tone } = node.props;
|
|
@@ -41,36 +47,63 @@ export function StatRenderer({ node, context }: UiRendererProps<UiProps<'Stat'>>
|
|
|
41
47
|
const from = previous === undefined ? '' : uiWithUnit(formatRounded(previous, 2), unit);
|
|
42
48
|
// A change from zero has no percentage.
|
|
43
49
|
const percent = change === null || previous === undefined || previous === 0 ? null : formatPercent(Math.abs((change / previous) * 100));
|
|
50
|
+
const said = percent === null ? t('blocks.stat.from', { previous: from }) : t('blocks.stat.change', { change: percent, previous: from });
|
|
44
51
|
return (
|
|
45
|
-
<div className="flex min-w-0 flex-col gap-
|
|
46
|
-
<span className="truncate text-
|
|
52
|
+
<div className="flex min-w-0 flex-col items-start gap-1.5 rounded-xl bg-ui-tile p-3">
|
|
53
|
+
<span className="max-w-full truncate text-2xs text-text-muted">{label}</span>
|
|
47
54
|
<LiveFigure
|
|
48
55
|
text={uiWithUnit(formatRounded(value, 2), unit)}
|
|
49
56
|
previous={typeof live?.previous === 'number' ? uiWithUnit(formatRounded(live.previous, 2), unit) : undefined}
|
|
50
57
|
live={live}
|
|
51
|
-
className="text-lg font-semibold tabular-nums"
|
|
58
|
+
className="max-w-full truncate text-lg font-semibold tabular-nums"
|
|
52
59
|
/>
|
|
53
60
|
{change !== null && (
|
|
54
|
-
<
|
|
55
|
-
<
|
|
56
|
-
|
|
57
|
-
|
|
61
|
+
<Tooltip label={said}>
|
|
62
|
+
<span tabIndex={0} className="max-w-full rounded-md">
|
|
63
|
+
<Pill
|
|
64
|
+
shape="tag"
|
|
65
|
+
size="sm"
|
|
66
|
+
tone={tone === undefined ? 'muted' : UI_TONE_PILL[tone]}
|
|
67
|
+
icon={<Icon icon={arrow} size={12} className="shrink-0" />}
|
|
68
|
+
>
|
|
69
|
+
<span aria-hidden className="tabular-nums">
|
|
70
|
+
{percent ?? from}
|
|
71
|
+
</span>
|
|
72
|
+
<span className="sr-only">{said}</span>
|
|
73
|
+
</Pill>
|
|
58
74
|
</span>
|
|
59
|
-
</
|
|
75
|
+
</Tooltip>
|
|
60
76
|
)}
|
|
61
77
|
</div>
|
|
62
78
|
);
|
|
63
79
|
}
|
|
64
80
|
|
|
65
81
|
export function EntityListRenderer({ node, children }: UiRendererProps<UiProps<'EntityList'>>) {
|
|
82
|
+
const count = uiChildrenOf(node, 'Entry').length;
|
|
83
|
+
if (count <= TILED_UP_TO) {
|
|
84
|
+
return (
|
|
85
|
+
<EntityTiles value>
|
|
86
|
+
<div className="chat-ui-nodes grid grid-cols-[repeat(auto-fit,minmax(140px,1fr))] gap-2">{children}</div>
|
|
87
|
+
</EntityTiles>
|
|
88
|
+
);
|
|
89
|
+
}
|
|
66
90
|
return (
|
|
67
|
-
<KeyValueList.Root divided={
|
|
91
|
+
<KeyValueList.Root divided={count >= DIVIDED_FROM} className="chat-ui-nodes chat-ui-flat rounded-xl bg-ui-tile px-3 py-2">
|
|
68
92
|
{children}
|
|
69
93
|
</KeyValueList.Root>
|
|
70
94
|
);
|
|
71
95
|
}
|
|
72
96
|
|
|
73
97
|
export function EntryRenderer({ node, children }: UiRendererProps<UiProps<'Entry'>>) {
|
|
98
|
+
const tiled = useContext(EntityTiles);
|
|
99
|
+
if (tiled) {
|
|
100
|
+
return (
|
|
101
|
+
<div className="flex min-w-0 flex-col gap-0.5 rounded-xl bg-ui-tile px-3 py-2">
|
|
102
|
+
<span className="truncate text-2xs text-text-muted">{node.props.label}</span>
|
|
103
|
+
<span className="text-2xs font-semibold wrap-break-word text-text">{children}</span>
|
|
104
|
+
</div>
|
|
105
|
+
);
|
|
106
|
+
}
|
|
74
107
|
return (
|
|
75
108
|
<KeyValueList.Item>
|
|
76
109
|
<KeyValueList.Name>{node.props.label}</KeyValueList.Name>
|
|
@@ -129,19 +162,23 @@ export function TableRenderer({ node, context }: UiRendererProps<UiProps<'Table'
|
|
|
129
162
|
const shown = all ? rows : rows.slice(0, UI_TABLE_ROWS);
|
|
130
163
|
return (
|
|
131
164
|
<div className="flex flex-col items-start gap-1.5">
|
|
132
|
-
<div className="max-h-80 w-full overflow-auto rounded-
|
|
165
|
+
<div className="chat-ui-flat max-h-80 w-full overflow-auto rounded-xl bg-ui-tile px-1.5 pb-1.5">
|
|
133
166
|
<table
|
|
134
167
|
aria-label={t('blocks.table.label', { columns: columns.map((column) => column.title).join(', ') })}
|
|
135
|
-
className=
|
|
168
|
+
className={clsx(
|
|
169
|
+
'w-full border-separate border-spacing-0 text-xs tabular-nums',
|
|
170
|
+
'[&_tbody_tr:first-child_td:first-child]:rounded-tl-lg [&_tbody_tr:first-child_td:last-child]:rounded-tr-lg',
|
|
171
|
+
'[&_tbody_tr:last-child_td:first-child]:rounded-bl-lg [&_tbody_tr:last-child_td:last-child]:rounded-br-lg'
|
|
172
|
+
)}
|
|
136
173
|
>
|
|
137
|
-
<thead className="sticky top-0 z-1 bg-
|
|
174
|
+
<thead className="sticky top-0 z-1 bg-ui-tile">
|
|
138
175
|
<tr>
|
|
139
176
|
{columns.map((column) => (
|
|
140
177
|
<th
|
|
141
178
|
key={column.key}
|
|
142
179
|
scope="col"
|
|
143
180
|
className={clsx(
|
|
144
|
-
'h-8 px-2 font-medium whitespace-nowrap text-text-muted',
|
|
181
|
+
'h-8 px-2 text-2xs font-medium whitespace-nowrap text-text-muted',
|
|
145
182
|
isNumericColumn(column) ? 'text-right' : 'text-left'
|
|
146
183
|
)}
|
|
147
184
|
>
|
|
@@ -153,11 +190,16 @@ export function TableRenderer({ node, context }: UiRendererProps<UiProps<'Table'
|
|
|
153
190
|
<tbody>
|
|
154
191
|
{shown.map((row, index) => (
|
|
155
192
|
// Rows only ever grow at the end while a block streams, so the place of a row is a stable key.
|
|
156
|
-
<tr key={index}
|
|
157
|
-
{columns.map((column) => (
|
|
193
|
+
<tr key={index}>
|
|
194
|
+
{columns.map((column, at) => (
|
|
158
195
|
<td
|
|
159
196
|
key={column.key}
|
|
160
|
-
className={clsx(
|
|
197
|
+
className={clsx(
|
|
198
|
+
'h-9 bg-ui-inset px-2 text-text',
|
|
199
|
+
index > 0 && 'border-t border-border-soft',
|
|
200
|
+
at === 0 && 'font-medium',
|
|
201
|
+
isNumericColumn(column) ? 'text-right whitespace-nowrap' : 'text-left'
|
|
202
|
+
)}
|
|
161
203
|
>
|
|
162
204
|
<Cell
|
|
163
205
|
value={row[column.key]}
|
|
@@ -23,7 +23,7 @@ const ChecklistContext = createContext<ChecklistState>({ values: [], enabled: fa
|
|
|
23
23
|
function ChecklistGroup({ pending, children }: { pending: boolean; children: ReactNode }) {
|
|
24
24
|
const { t } = useTranslation('agent-chat');
|
|
25
25
|
return (
|
|
26
|
-
<div role="group" aria-label={t('blocks.checklist')} className={clsx('chat-ui-nodes flex flex-col', pending && PENDING)}>
|
|
26
|
+
<div role="group" aria-label={t('blocks.checklist')} className={clsx('chat-ui-nodes flex flex-col gap-1.5', pending && PENDING)}>
|
|
27
27
|
{children}
|
|
28
28
|
</div>
|
|
29
29
|
);
|
|
@@ -42,10 +42,11 @@ export function ChecklistRenderer({ node, children, context }: UiRendererProps<U
|
|
|
42
42
|
);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
-
/* The whole
|
|
45
|
+
/* The whole tile is the label, ringed in the accent while checked; a link inside it keeps its own click and checks nothing. */
|
|
46
46
|
export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>>) {
|
|
47
47
|
const list = useContext(ChecklistContext);
|
|
48
48
|
const { value } = node.props;
|
|
49
|
+
const checked = list.values.includes(value);
|
|
49
50
|
const toggleRow = (event: MouseEvent<HTMLDivElement>): void => {
|
|
50
51
|
if (list.enabled && !(event.target as Element).closest('button, a')) {
|
|
51
52
|
list.toggle(value);
|
|
@@ -53,10 +54,14 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
|
|
|
53
54
|
};
|
|
54
55
|
return (
|
|
55
56
|
<div
|
|
56
|
-
className={clsx(
|
|
57
|
+
className={clsx(
|
|
58
|
+
'flex min-h-10 items-start gap-3 rounded-xl bg-ui-tile px-3 py-2.5 text-xs text-text',
|
|
59
|
+
checked && 'ring-2 ring-accent ring-inset',
|
|
60
|
+
list.enabled && 'hover:bg-surface-active'
|
|
61
|
+
)}
|
|
57
62
|
onClick={toggleRow}
|
|
58
63
|
>
|
|
59
|
-
<Checkbox checked={
|
|
64
|
+
<Checkbox className="mt-0.5" checked={checked} onCheckedChange={() => list.toggle(value)} label={uiNodeLabel(node)} disabled={!list.enabled} />
|
|
60
65
|
<div className="min-w-0 grow">{children}</div>
|
|
61
66
|
</div>
|
|
62
67
|
);
|
|
@@ -65,8 +70,10 @@ export function ItemRenderer({ node, children }: UiRendererProps<UiProps<'Item'>
|
|
|
65
70
|
export function SwitchRenderer({ node, children, context }: UiRendererProps<UiProps<'Switch'>>) {
|
|
66
71
|
const binding = bindingOf(node, context);
|
|
67
72
|
return (
|
|
68
|
-
<div
|
|
69
|
-
|
|
73
|
+
<div
|
|
74
|
+
className={clsx('chat-ui-flat flex min-h-11 items-center justify-between gap-3 rounded-xl bg-ui-tile px-3 py-2 text-xs', !node.complete && PENDING)}
|
|
75
|
+
>
|
|
76
|
+
<span className="min-w-0 font-medium text-text">{children}</span>
|
|
70
77
|
<Switch
|
|
71
78
|
checked={binding?.value ?? node.props.value}
|
|
72
79
|
onCheckedChange={(next) => binding?.onValueChange(next)}
|
|
@@ -92,7 +99,7 @@ export function SliderRenderer({ node, context }: UiRendererProps<UiProps<'Slide
|
|
|
92
99
|
unit={uiWithUnit('', unit)}
|
|
93
100
|
label={uiNodeLabel(node)}
|
|
94
101
|
disabled={binding === null}
|
|
95
|
-
className={clsx('
|
|
102
|
+
className={clsx('chat-ui-flat rounded-xl bg-ui-tile p-3', !node.complete && PENDING)}
|
|
96
103
|
/>
|
|
97
104
|
);
|
|
98
105
|
}
|
|
@@ -108,7 +115,7 @@ export function SegmentedRenderer({ node, context }: UiRendererProps<UiProps<'Se
|
|
|
108
115
|
const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
|
|
109
116
|
const picked = options.find((option) => option.value === current)?.id ?? '';
|
|
110
117
|
return (
|
|
111
|
-
<div className={clsx(
|
|
118
|
+
<div className={clsx(!node.complete && PENDING)}>
|
|
112
119
|
<Segmented
|
|
113
120
|
value={picked}
|
|
114
121
|
options={options}
|
|
@@ -145,8 +152,8 @@ export function TextFieldRenderer({ node, children, context }: UiRendererProps<U
|
|
|
145
152
|
const countId = `${id}-count`;
|
|
146
153
|
const counting = value.length >= maxLength * COUNT_FROM;
|
|
147
154
|
return (
|
|
148
|
-
<div className={clsx('flex flex-col gap-1
|
|
149
|
-
<label htmlFor={id} className="text-
|
|
155
|
+
<div className={clsx('flex flex-col gap-1.5', !node.complete && PENDING)}>
|
|
156
|
+
<label htmlFor={id} className="px-0.5 text-2xs text-text-muted">
|
|
150
157
|
{children}
|
|
151
158
|
</label>
|
|
152
159
|
<Input
|
|
@@ -157,7 +164,7 @@ export function TextFieldRenderer({ node, children, context }: UiRendererProps<U
|
|
|
157
164
|
placeholder={placeholder}
|
|
158
165
|
disabled={binding === null}
|
|
159
166
|
aria-describedby={counting ? countId : undefined}
|
|
160
|
-
className="
|
|
167
|
+
className="border-transparent bg-ui-tile"
|
|
161
168
|
onChange={(event) => binding?.onValueChange(event.target.value)}
|
|
162
169
|
/>
|
|
163
170
|
{counting && (
|
|
@@ -180,8 +187,8 @@ export function SelectRenderer({ node, context }: UiRendererProps<UiProps<'Selec
|
|
|
180
187
|
const options = uiChildrenOf(node, 'Option').map((option, index) => ({ id: String(index), label: uiNodeLabel(option), value: option.props.value }));
|
|
181
188
|
const label = uiOwnLabel(node);
|
|
182
189
|
return (
|
|
183
|
-
<div className={clsx('flex flex-col items-start gap-1
|
|
184
|
-
{label !== '' && <span className="text-
|
|
190
|
+
<div className={clsx('flex flex-col items-start gap-1.5', !node.complete && PENDING)}>
|
|
191
|
+
{label !== '' && <span className="px-0.5 text-2xs text-text-muted">{label}</span>}
|
|
185
192
|
<Select
|
|
186
193
|
value={options.find((option) => option.value === current)?.id ?? null}
|
|
187
194
|
items={options.map((option) => ({ value: option.id, label: option.label }))}
|
|
@@ -204,7 +211,7 @@ export function ButtonRenderer({ node, children, context }: UiRendererProps<UiPr
|
|
|
204
211
|
const label = uiNodeLabel(node);
|
|
205
212
|
const [measure, truncated] = useTruncated<HTMLSpanElement>();
|
|
206
213
|
return (
|
|
207
|
-
<div className={clsx(
|
|
214
|
+
<div className={clsx(!node.complete && PENDING)}>
|
|
208
215
|
<Tooltip label={label} disabled={!truncated}>
|
|
209
216
|
<Button variant="secondary" size="sm" className="max-w-full" disabled={!enabled} onClick={() => node.onAction?.()}>
|
|
210
217
|
<span ref={measure} className="min-w-0 truncate">
|
|
@@ -4,12 +4,15 @@ import { useTranslation } from 'react-i18next';
|
|
|
4
4
|
import { FileDiff, GitCommitHorizontal, SquareDashed } from 'lucide-react';
|
|
5
5
|
import type { UiProps } from '@adecore/intelligent-ui';
|
|
6
6
|
import { FileIcon, Icon, Tooltip } from '@adecore/ui';
|
|
7
|
-
import { CHIP_IN_MESSAGE
|
|
7
|
+
import { CHIP_IN_MESSAGE } from '../../chips';
|
|
8
8
|
import { uiReasonText } from '../node-text';
|
|
9
9
|
import type { UiLink, UiLinkTarget, UiRenderContext, UiRendererProps } from '../render-context';
|
|
10
10
|
|
|
11
11
|
const PLAIN: UiLink = { state: 'plain' };
|
|
12
12
|
|
|
13
|
+
/* A link in a block is a quiet card on its tile, unlike a mention in a message, which wears the accent. */
|
|
14
|
+
const UI_CHIP = 'bg-ui-inset font-mono text-text shadow-[inset_0_0_0_1px_var(--border)]';
|
|
15
|
+
|
|
13
16
|
function baseName(path: string): string {
|
|
14
17
|
const trimmed = path.replace(/\/+$/, '');
|
|
15
18
|
return trimmed.slice(trimmed.lastIndexOf('/') + 1) || path;
|
|
@@ -96,11 +99,11 @@ export function UiLinkChip({ target, context }: { target: UiLinkTarget; context:
|
|
|
96
99
|
const { openLink } = context;
|
|
97
100
|
const chip =
|
|
98
101
|
openLink === undefined ? (
|
|
99
|
-
<span className={clsx(CHIP_IN_MESSAGE,
|
|
102
|
+
<span className={clsx(CHIP_IN_MESSAGE, UI_CHIP, 'transition-opacity duration-120 starting:opacity-0')}>{face}</span>
|
|
100
103
|
) : (
|
|
101
104
|
<button
|
|
102
105
|
type="button"
|
|
103
|
-
className={clsx(CHIP_IN_MESSAGE,
|
|
106
|
+
className={clsx(CHIP_IN_MESSAGE, UI_CHIP, 'transition-opacity duration-120 hover:bg-surface-hover starting:opacity-0')}
|
|
104
107
|
onClick={() => openLink(target)}
|
|
105
108
|
>
|
|
106
109
|
{face}
|
|
@@ -115,7 +118,7 @@ function LinkRow({ target, context, described, children }: { target: UiLinkTarge
|
|
|
115
118
|
return <UiLinkChip target={target} context={context} />;
|
|
116
119
|
}
|
|
117
120
|
return (
|
|
118
|
-
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2
|
|
121
|
+
<div className="flex min-w-0 flex-wrap items-baseline gap-x-2 text-xs">
|
|
119
122
|
<span className="max-w-full min-w-0">
|
|
120
123
|
<UiLinkChip target={target} context={context} />
|
|
121
124
|
</span>
|
|
@@ -34,8 +34,8 @@ function NumberChip({ number, selected }: { number: number; selected: boolean })
|
|
|
34
34
|
<span
|
|
35
35
|
aria-hidden
|
|
36
36
|
className={clsx(
|
|
37
|
-
'mt-px flex size-5 shrink-0 items-center justify-center rounded-
|
|
38
|
-
selected ? 'bg-accent text-accent-text' : 'bg-
|
|
37
|
+
'mt-px flex size-5 shrink-0 items-center justify-center rounded-md text-2xs font-semibold tabular-nums',
|
|
38
|
+
selected ? 'bg-accent text-accent-text' : 'bg-ui-inset text-text'
|
|
39
39
|
)}
|
|
40
40
|
>
|
|
41
41
|
{number}
|
|
@@ -50,7 +50,7 @@ function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; n
|
|
|
50
50
|
<>
|
|
51
51
|
<NumberChip number={number} selected={selected} />
|
|
52
52
|
<span className="flex min-w-0 grow flex-col gap-0.5">
|
|
53
|
-
<span className="text-
|
|
53
|
+
<span className="text-xs font-medium wrap-break-word text-text">{listed.name}</span>
|
|
54
54
|
{detail !== undefined && detail !== '' && <span className="text-xs wrap-break-word text-text-muted">{detail}</span>}
|
|
55
55
|
{address !== undefined && address !== '' && <span className="text-xs wrap-break-word text-text-faint">{address}</span>}
|
|
56
56
|
{listed.point === undefined && <span className="text-xs text-text-faint">{t('blocks.map.lookingUp')}</span>}
|
|
@@ -58,13 +58,18 @@ function PlaceRow({ listed, number, selected, onPick }: { listed: ListedPlace; n
|
|
|
58
58
|
</span>
|
|
59
59
|
</>
|
|
60
60
|
);
|
|
61
|
-
const row = 'flex w-full min-h-
|
|
61
|
+
const row = 'flex h-full w-full min-h-12 items-start gap-2.5 rounded-xl bg-ui-tile px-3 py-2 text-left';
|
|
62
62
|
return (
|
|
63
63
|
<li>
|
|
64
64
|
{onPick === null ? (
|
|
65
|
-
<div className={clsx(row, selected && '
|
|
65
|
+
<div className={clsx(row, selected && 'ring-2 ring-accent ring-inset')}>{body}</div>
|
|
66
66
|
) : (
|
|
67
|
-
<button
|
|
67
|
+
<button
|
|
68
|
+
type="button"
|
|
69
|
+
aria-pressed={selected}
|
|
70
|
+
className={clsx(row, 'hover:bg-surface-active', selected && 'ring-2 ring-accent ring-inset')}
|
|
71
|
+
onClick={onPick}
|
|
72
|
+
>
|
|
68
73
|
{body}
|
|
69
74
|
</button>
|
|
70
75
|
)}
|
|
@@ -150,7 +155,7 @@ function MapFrame({ markers, source, selected, onPick }: FrameProps) {
|
|
|
150
155
|
}
|
|
151
156
|
return (
|
|
152
157
|
<>
|
|
153
|
-
<div ref={setElement} className="relative h-50 overflow-hidden rounded-
|
|
158
|
+
<div ref={setElement} className="relative h-50 overflow-hidden rounded-xl bg-ui-tile @min-[420px]:h-60">
|
|
154
159
|
{body}
|
|
155
160
|
{waiting && (
|
|
156
161
|
<div className="pointer-events-none absolute inset-0 grid place-items-center">
|
|
@@ -200,9 +205,9 @@ export function MapRenderer({ node, children, context }: UiRendererProps<UiProps
|
|
|
200
205
|
<MapFrame markers={markers} source={null} selected={selectedId} onPick={onPick} />
|
|
201
206
|
);
|
|
202
207
|
return (
|
|
203
|
-
<div className={clsx('@container flex flex-col gap-1.5
|
|
208
|
+
<div className={clsx('@container flex flex-col gap-1.5', !node.complete && PENDING)}>
|
|
204
209
|
{frame}
|
|
205
|
-
<ol aria-label={t('blocks.map.label', { count: places.length })} className="
|
|
210
|
+
<ol aria-label={t('blocks.map.label', { count: places.length })} className="grid gap-1.5 @min-[420px]:grid-cols-2">
|
|
206
211
|
{places.map((listed, index) => {
|
|
207
212
|
const { value } = listed.node.props;
|
|
208
213
|
return (
|
|
@@ -216,9 +221,7 @@ export function MapRenderer({ node, children, context }: UiRendererProps<UiProps
|
|
|
216
221
|
);
|
|
217
222
|
})}
|
|
218
223
|
</ol>
|
|
219
|
-
{node.omitted !== undefined && node.omitted > 0 && (
|
|
220
|
-
<p className="px-2 text-xs text-text-faint">{t('blocks.map.omitted', { count: node.omitted })}</p>
|
|
221
|
-
)}
|
|
224
|
+
{node.omitted !== undefined && node.omitted > 0 && <p className="text-2xs text-text-faint">{t('blocks.map.omitted', { count: node.omitted })}</p>}
|
|
222
225
|
{/* A place the catalog refused draws as its text, which is all its node renders. */}
|
|
223
226
|
<div className="chat-ui-nodes flex flex-col gap-1.5 empty:hidden">{children}</div>
|
|
224
227
|
</div>
|
|
@@ -22,7 +22,7 @@ export function TabsRenderer({ node, children, context }: UiRendererProps<UiProp
|
|
|
22
22
|
const value = picked !== null && tabs.some((tab) => tab.id === picked) ? picked : (tabs[0]?.id ?? null);
|
|
23
23
|
return (
|
|
24
24
|
<Tabs.Root value={value} onValueChange={(next) => setPicked(String(next))} className="flex flex-col gap-3">
|
|
25
|
-
<Tabs.List className="
|
|
25
|
+
<Tabs.List variant="pill" overflow="scroll" className="w-fit max-w-full rounded-xl bg-ui-tile p-0.75">
|
|
26
26
|
{tabs.map((tab) => (
|
|
27
27
|
<Tabs.Tab key={tab.id} value={tab.id} data-ui-reveal={tab.id}>
|
|
28
28
|
{tab.props.title}
|
|
@@ -46,24 +46,24 @@ export function SectionsRenderer({ node, children, context }: UiRendererProps<Ui
|
|
|
46
46
|
const [touched, setTouched] = useBlockLocal(context, node.id, () => false);
|
|
47
47
|
return (
|
|
48
48
|
<SectionsContext value={{ touched, touch: () => setTouched(true) }}>
|
|
49
|
-
<div className="chat-ui-nodes flex flex-col
|
|
49
|
+
<div className="chat-ui-nodes flex flex-col gap-1.5">{children}</div>
|
|
50
50
|
</SectionsContext>
|
|
51
51
|
);
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
/* Arrives open until the person folds a section of the set themselves; the content
|
|
54
|
+
/* Arrives open until the person folds a section of the set themselves; the content keeps one margin on both sides. */
|
|
55
55
|
export function SectionRenderer({ node, children, context }: UiRendererProps<UiProps<'Section'>>) {
|
|
56
56
|
const fold = useContext(SectionsContext);
|
|
57
57
|
const [open, setOpen] = useBlockLocal(context, node.id, () => !fold.touched);
|
|
58
58
|
const contentId = useId();
|
|
59
59
|
return (
|
|
60
|
-
<section>
|
|
60
|
+
<section className="rounded-xl bg-ui-tile">
|
|
61
61
|
<button
|
|
62
62
|
type="button"
|
|
63
63
|
data-ui-reveal={node.id}
|
|
64
64
|
aria-expanded={open}
|
|
65
65
|
aria-controls={contentId}
|
|
66
|
-
className="flex h-
|
|
66
|
+
className="flex min-h-10 w-full items-center gap-2 rounded-xl px-3 text-left text-xs font-medium text-text hover:bg-text/4"
|
|
67
67
|
onClick={() => {
|
|
68
68
|
fold.touch();
|
|
69
69
|
setOpen(!open);
|
|
@@ -76,7 +76,7 @@ export function SectionRenderer({ node, children, context }: UiRendererProps<UiP
|
|
|
76
76
|
/>
|
|
77
77
|
<span className="min-w-0 truncate">{node.props.title}</span>
|
|
78
78
|
</button>
|
|
79
|
-
<div id={contentId} hidden={!open} className="chat-ui-nodes flex flex-col gap-3
|
|
79
|
+
<div id={contentId} hidden={!open} className="chat-ui-section-body chat-ui-nodes flex flex-col gap-3 px-3 pb-3">
|
|
80
80
|
{children}
|
|
81
81
|
</div>
|
|
82
82
|
</section>
|