@ciridae/ui 1.0.5 → 1.0.9
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/README.md +3 -2
- package/dist/chat/conversation-source.d.ts +1 -3
- package/dist/chat/message-text.js +6 -6
- package/dist/chat/use-chat-conversation.d.ts +7 -2
- package/dist/chat/use-chat-conversation.js +2 -1
- package/dist/components/activity-feed.d.ts +0 -1
- package/dist/components/activity-feed.js +6 -7
- package/dist/components/agent-work.d.ts +5 -1
- package/dist/components/agent-work.js +27 -13
- package/dist/components/app-frame.d.ts +3 -17
- package/dist/components/app-frame.js +11 -30
- package/dist/components/app-navigation.d.ts +5 -6
- package/dist/components/app-navigation.js +65 -40
- package/dist/components/badge.d.ts +0 -4
- package/dist/components/badge.js +17 -27
- package/dist/components/bulk-action-bar.js +2 -2
- package/dist/components/button.js +41 -50
- package/dist/components/calendar.js +5 -8
- package/dist/components/centered-page.js +4 -4
- package/dist/components/chart.js +3 -4
- package/dist/components/chat-client-source.js +55 -34
- package/dist/components/chat-page.js +1 -1
- package/dist/components/chat-sheet.d.ts +30 -20
- package/dist/components/chat-sheet.js +109 -51
- package/dist/components/chat.d.ts +27 -56
- package/dist/components/chat.js +151 -129
- package/dist/components/checkbox-item.js +4 -4
- package/dist/components/checkbox-list.js +6 -6
- package/dist/components/chip.js +2 -2
- package/dist/components/collapsible.js +3 -3
- package/dist/components/color-theme.d.ts +3 -6
- package/dist/components/color-theme.js +10 -11
- package/dist/components/column-settings.js +3 -3
- package/dist/components/command-menu.d.ts +1 -2
- package/dist/components/command-menu.js +56 -77
- package/dist/components/command-palette.js +5 -2
- package/dist/components/copyable-id.js +5 -5
- package/dist/components/data-display.d.ts +1 -7
- package/dist/components/data-display.js +32 -55
- package/dist/components/data-grid.d.ts +4 -7
- package/dist/components/data-grid.js +106 -90
- package/dist/components/date-range-picker.js +4 -5
- package/dist/components/document-viewer.js +23 -23
- package/dist/components/drawer.d.ts +2 -1
- package/dist/components/drawer.js +45 -20
- package/dist/components/dropzone.js +13 -13
- package/dist/components/editable-field.d.ts +2 -12
- package/dist/components/editable-field.js +13 -26
- package/dist/components/email-input.js +2 -3
- package/dist/components/environment-frame.js +7 -7
- package/dist/components/expandable-text.js +2 -2
- package/dist/components/feedback-screenshot.js +7 -7
- package/dist/components/feedback-sheet.js +8 -8
- package/dist/components/filter-bar.js +41 -27
- package/dist/components/form-controls.d.ts +3 -6
- package/dist/components/form-controls.js +15 -22
- package/dist/components/form-layout.d.ts +6 -6
- package/dist/components/form-layout.js +10 -16
- package/dist/components/glass-menu.d.ts +2 -0
- package/dist/components/glass-menu.js +14 -18
- package/dist/components/gradient-edge.d.ts +0 -1
- package/dist/components/gradient-edge.js +8 -9
- package/dist/components/icon.js +1 -1
- package/dist/components/input.js +18 -17
- package/dist/components/keycap.d.ts +2 -6
- package/dist/components/keycap.js +4 -12
- package/dist/components/layout.js +11 -10
- package/dist/components/lightbox.js +8 -8
- package/dist/components/link.js +1 -1
- package/dist/components/locale.d.ts +23 -7
- package/dist/components/locale.js +19 -7
- package/dist/components/log-entry.d.ts +2 -14
- package/dist/components/log-entry.js +11 -22
- package/dist/components/log-list.js +1 -1
- package/dist/components/mention-input.js +13 -14
- package/dist/components/menu.d.ts +7 -4
- package/dist/components/menu.js +23 -10
- package/dist/components/meter.d.ts +2 -3
- package/dist/components/meter.js +12 -16
- package/dist/components/navigation.d.ts +0 -7
- package/dist/components/navigation.js +3 -12
- package/dist/components/notification-tray.js +7 -7
- package/dist/components/overlays.d.ts +5 -2
- package/dist/components/overlays.js +24 -24
- package/dist/components/panel.js +3 -3
- package/dist/components/progress.js +2 -2
- package/dist/components/radio-card.d.ts +1 -1
- package/dist/components/radio-card.js +9 -9
- package/dist/components/record.d.ts +2 -4
- package/dist/components/record.js +19 -32
- package/dist/components/scroll-area.js +10 -10
- package/dist/components/segmented-bar.js +9 -9
- package/dist/components/select.d.ts +3 -5
- package/dist/components/select.js +26 -44
- package/dist/components/settings.js +1 -1
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +1 -1
- package/dist/components/shell.js +69 -24
- package/dist/components/side-panel.js +3 -3
- package/dist/components/sidebar.d.ts +0 -5
- package/dist/components/sidebar.js +25 -33
- package/dist/components/single-line-text.js +3 -4
- package/dist/components/site-banner.js +4 -4
- package/dist/components/skeleton.js +19 -22
- package/dist/components/skeletons.d.ts +0 -3
- package/dist/components/skeletons.js +5 -10
- package/dist/components/stages.d.ts +6 -3
- package/dist/components/stages.js +77 -105
- package/dist/components/stat-card.d.ts +2 -5
- package/dist/components/stat-card.js +23 -31
- package/dist/components/status-banner.js +6 -6
- package/dist/components/status.js +3 -3
- package/dist/components/steps.js +2 -2
- package/dist/components/switch.js +1 -1
- package/dist/components/tabs.js +15 -18
- package/dist/components/theme-customizer.d.ts +5 -8
- package/dist/components/theme-customizer.js +9 -41
- package/dist/components/theme-toggle.d.ts +0 -1
- package/dist/components/theme-toggle.js +6 -11
- package/dist/components/toast.d.ts +1 -7
- package/dist/components/toast.js +19 -22
- package/dist/components/user-chip.js +8 -8
- package/dist/components/wizard.js +6 -4
- package/dist/preset.d.ts +1 -2
- package/dist/preset.js +39 -89
- package/dist/styled-system/css/conditions.mjs +1 -1
- package/dist/styled-system/css/css.mjs +1 -1
- package/dist/styled-system/styles.css +805 -1279
- package/dist/styled-system/tokens/index.mjs +74 -1926
- package/dist/styled-system/tokens/tokens.d.ts +18 -33
- package/dist/styled-system/types/conditions.d.ts +0 -74
- package/dist/styled-system/types/prop-type.d.ts +15 -40
- package/dist/styled-system/types/style-props.d.ts +705 -705
- package/dist/theme/color.d.ts +0 -1
- package/dist/theme/color.js +0 -16
- package/dist/theme/derive.d.ts +3 -3
- package/dist/theme/derive.js +9 -32
- package/dist/theme/index.d.ts +1 -2
- package/dist/theme/index.js +3 -5
- package/dist/theme/options.d.ts +2 -4
- package/dist/theme/options.js +0 -1
- package/dist/theme/{palettes.d.ts → palette.d.ts} +12 -23
- package/dist/theme/palette.js +127 -0
- package/dist/theme/tokens.d.ts +8 -13
- package/dist/theme/tokens.js +38 -92
- package/package.json +1 -1
- package/dist/theme/palettes.js +0 -254
package/dist/components/chat.js
CHANGED
|
@@ -14,17 +14,9 @@ import { LogGroupHeader } from "./log-entry.js";
|
|
|
14
14
|
import { MentionInput } from "./mention-input.js";
|
|
15
15
|
import { useResolvedFileUrl } from "./resolved-file-url.js";
|
|
16
16
|
export { MessageText, splitSources, } from "../chat/message-text.js";
|
|
17
|
-
/*
|
|
18
|
-
* Three composers share one behaviour. `page` is the wide card of a chat page. `sheet` is the
|
|
19
|
-
* composer at the foot of the agent sheet: one soft card holding the text, which grows line by
|
|
20
|
-
* line to six before it scrolls, over a bar with the tools as bare icons on the left and a small
|
|
21
|
-
* round send on the right; what goes with the message, such as files, waits as chips over the
|
|
22
|
-
* text. `feed` is a comment field under an activity feed: a pill with its send inside, which
|
|
23
|
-
* grows into a soft card past one line; Enter breaks the line there, and Cmd/Ctrl+Enter sends.
|
|
24
|
-
*/
|
|
25
17
|
const pageComposer = css({
|
|
26
18
|
display: "flex",
|
|
27
|
-
width: "
|
|
19
|
+
width: "full",
|
|
28
20
|
flexDirection: "column",
|
|
29
21
|
overflow: "hidden",
|
|
30
22
|
maxWidth: "chat.composer",
|
|
@@ -38,9 +30,9 @@ const pageComposer = css({
|
|
|
38
30
|
"& > [data-attachments]": { paddingInline: "page.inline", paddingTop: "page.inline" },
|
|
39
31
|
});
|
|
40
32
|
const pageInput = css({
|
|
41
|
-
width: "
|
|
33
|
+
width: "full",
|
|
42
34
|
minHeight: "chat.input",
|
|
43
|
-
maxHeight: "calc(10lh + 2 * token(spacing.page.inline))",
|
|
35
|
+
maxHeight: "[calc(10lh + 2 * token(spacing.page.inline))]",
|
|
44
36
|
padding: "page.inline",
|
|
45
37
|
fontSize: "body",
|
|
46
38
|
lineHeight: "body",
|
|
@@ -50,7 +42,7 @@ const sheetComposer = css({
|
|
|
50
42
|
display: "flex",
|
|
51
43
|
flexDirection: "column",
|
|
52
44
|
gap: "layout.contentGap",
|
|
53
|
-
width: "
|
|
45
|
+
width: "full",
|
|
54
46
|
minWidth: "0",
|
|
55
47
|
padding: "panel.default",
|
|
56
48
|
border: "1px solid token(colors.border.control)",
|
|
@@ -58,17 +50,15 @@ const sheetComposer = css({
|
|
|
58
50
|
background: "bg.field",
|
|
59
51
|
_focusWithin: { borderColor: "border.focus", boxShadow: "focus" },
|
|
60
52
|
});
|
|
61
|
-
// Wrapped lines take the body's spacing. The margins give back the leading past one line of
|
|
62
|
-
// type, so a one-line composer stays the kit's height, its text box exactly one em tall.
|
|
53
|
+
// Wrapped lines take the body's spacing. The margins give back the leading past one line of type, so a one-line composer stays the kit's height, its text box exactly one em tall.
|
|
63
54
|
const sheetInput = css({
|
|
64
|
-
maxHeight: "6lh",
|
|
65
|
-
marginBlock: "calc((1em - 1lh) / 2)",
|
|
55
|
+
maxHeight: "[6lh]",
|
|
56
|
+
marginBlock: "[calc((1em - 1lh) / 2)]",
|
|
66
57
|
fontSize: "body",
|
|
67
58
|
fontWeight: "medium",
|
|
68
59
|
lineHeight: "body",
|
|
69
60
|
});
|
|
70
|
-
//
|
|
71
|
-
// round, smaller than any other icon button.
|
|
61
|
+
// The send is a 26px round, smaller than any other icon button.
|
|
72
62
|
const sheetBar = css({
|
|
73
63
|
display: "flex",
|
|
74
64
|
alignItems: "center",
|
|
@@ -87,7 +77,7 @@ const feedComposer = css({
|
|
|
87
77
|
display: "flex",
|
|
88
78
|
alignItems: "flex-end",
|
|
89
79
|
gap: "layout.tightGap",
|
|
90
|
-
width: "
|
|
80
|
+
width: "full",
|
|
91
81
|
minWidth: "0",
|
|
92
82
|
});
|
|
93
83
|
// The tools beside the feed's pill: as tall as its send, so they stay level with its last line.
|
|
@@ -96,7 +86,7 @@ const feedTools = css({
|
|
|
96
86
|
flexShrink: "0",
|
|
97
87
|
alignItems: "center",
|
|
98
88
|
gap: "layout.tightGap",
|
|
99
|
-
height: "calc(token(sizes.control.compact) + 2 * token(spacing.control.compactBlock))",
|
|
89
|
+
height: "[calc(token(sizes.control.compact) + 2 * token(spacing.control.compactBlock))]",
|
|
100
90
|
});
|
|
101
91
|
const feedColumn = css({ display: "grid", flex: "1", minWidth: "0", gap: "layout.tightGap" });
|
|
102
92
|
// The pill's 1px stroke sits inside its padding, so a one-line pill is the kit's height: the send
|
|
@@ -106,13 +96,13 @@ const feedBox = css({
|
|
|
106
96
|
alignItems: "flex-end",
|
|
107
97
|
gap: "layout.controlGap",
|
|
108
98
|
minWidth: "0",
|
|
109
|
-
paddingBlock: "calc(token(spacing.control.compactBlock) - token(sizes.border.hairline))",
|
|
99
|
+
paddingBlock: "[calc(token(spacing.control.compactBlock) - token(sizes.border.hairline))]",
|
|
110
100
|
paddingInlineStart: "control.compactInline",
|
|
111
101
|
paddingInlineEnd: "control.compactBlock",
|
|
112
102
|
border: "1px solid token(colors.border.control)",
|
|
113
103
|
// Half the one-line height: a pill at one line, a soft card past it.
|
|
114
|
-
borderRadius: "calc((token(sizes.control.compact) + 2 * token(spacing.control.compactBlock)) / 2)",
|
|
115
|
-
background: "token(gradients.card)",
|
|
104
|
+
borderRadius: "[calc((token(sizes.control.compact) + 2 * token(spacing.control.compactBlock)) / 2)]",
|
|
105
|
+
background: "[token(gradients.card)]",
|
|
116
106
|
_focusWithin: { borderColor: "border.focus", boxShadow: "focus" },
|
|
117
107
|
"& > [data-actions]": {
|
|
118
108
|
display: "flex",
|
|
@@ -125,17 +115,15 @@ const feedBox = css({
|
|
|
125
115
|
height: "control.compact",
|
|
126
116
|
},
|
|
127
117
|
});
|
|
128
|
-
// Each line keeps the one-line pill's rhythm, so the first wrap only adds a line.
|
|
129
|
-
// take the body's spacing, as the sheet's do.
|
|
118
|
+
// Each line keeps the one-line pill's rhythm, so the first wrap only adds a line.
|
|
130
119
|
const feedInput = css({
|
|
131
120
|
flex: "1",
|
|
132
|
-
paddingBlock: "calc((token(sizes.control.compact) - 1lh) / 2)",
|
|
133
|
-
maxHeight: "calc(5lh + token(sizes.control.compact))",
|
|
121
|
+
paddingBlock: "[calc((token(sizes.control.compact) - 1lh) / 2)]",
|
|
122
|
+
maxHeight: "[calc(5lh + token(sizes.control.compact))]",
|
|
134
123
|
fontSize: "body",
|
|
135
124
|
fontWeight: "medium",
|
|
136
125
|
lineHeight: "body",
|
|
137
126
|
});
|
|
138
|
-
/* A tool in the sheet composer's bar: a bare icon, with no face of its own. */
|
|
139
127
|
const composerTool = css({
|
|
140
128
|
display: "inline-flex",
|
|
141
129
|
padding: "none",
|
|
@@ -149,11 +137,7 @@ const composerTool = css({
|
|
|
149
137
|
_disabled: { opacity: "disabled", cursor: "not-allowed" },
|
|
150
138
|
"&[aria-pressed=true]": { color: "fg.primary" },
|
|
151
139
|
});
|
|
152
|
-
/*
|
|
153
|
-
* Dictation's level meter, in the composer's bar while the host listens: a bar per sample,
|
|
154
|
-
* newest last, spread across the room the bar leaves it. Bars move with no transition, since one
|
|
155
|
-
* would blur the motion of a meter updated several times a second.
|
|
156
|
-
*/
|
|
140
|
+
/* Bars move with no transition, since one would blur the motion of a meter updated several times a second. */
|
|
157
141
|
const dictationWaveform = css({
|
|
158
142
|
display: "flex",
|
|
159
143
|
flex: "1",
|
|
@@ -165,17 +149,16 @@ const dictationWaveform = css({
|
|
|
165
149
|
overflow: "hidden",
|
|
166
150
|
"& > span": {
|
|
167
151
|
flex: "1",
|
|
168
|
-
minWidth: "1px",
|
|
152
|
+
minWidth: "[1px]",
|
|
169
153
|
// A spacing token, so it is read through token(); a sizes property would print its name.
|
|
170
|
-
maxWidth: "token(spacing.layout.tightGap)",
|
|
171
|
-
height: "
|
|
154
|
+
maxWidth: "[token(spacing.layout.tightGap)]",
|
|
155
|
+
height: "full",
|
|
172
156
|
borderRadius: "round",
|
|
173
157
|
background: "fg.subtle",
|
|
174
158
|
transformOrigin: "center",
|
|
175
159
|
},
|
|
176
160
|
"&[data-listening] > span": { background: "fg.secondary" },
|
|
177
161
|
});
|
|
178
|
-
/** How short a silent bar of the dictation waveform is, as a share of its full height. */
|
|
179
162
|
const silentWaveformLevel = 0.12;
|
|
180
163
|
const composerFooter = css({
|
|
181
164
|
display: "flex",
|
|
@@ -198,10 +181,10 @@ const actionGroup = css({
|
|
|
198
181
|
gap: "control.iconGap",
|
|
199
182
|
flexWrap: "wrap",
|
|
200
183
|
_phone: {
|
|
201
|
-
maxWidth: "
|
|
184
|
+
maxWidth: "full",
|
|
202
185
|
flexWrap: "nowrap",
|
|
203
186
|
overflowX: "auto",
|
|
204
|
-
scrollbarWidth: "none",
|
|
187
|
+
scrollbarWidth: "[none]",
|
|
205
188
|
},
|
|
206
189
|
});
|
|
207
190
|
const chatAction = css({
|
|
@@ -222,9 +205,8 @@ const chatAction = css({
|
|
|
222
205
|
_hover: { color: "fg.primary", borderColor: "fg.muted" },
|
|
223
206
|
});
|
|
224
207
|
/*
|
|
225
|
-
*
|
|
226
|
-
*
|
|
227
|
-
* it on its side, that corner is small, so a run of messages reads as one stack.
|
|
208
|
+
* Where a bubble joins the one above or below it on its side, that corner is small, so a run of
|
|
209
|
+
* messages reads as one stack.
|
|
228
210
|
*/
|
|
229
211
|
const bubble = cva({
|
|
230
212
|
base: {
|
|
@@ -245,11 +227,11 @@ const bubble = cva({
|
|
|
245
227
|
// Wrapped lines take the body's spacing. The kit sets one line at the type's own height
|
|
246
228
|
// with its 1px stroke in the layout, which the edge's transparent border already counts,
|
|
247
229
|
// so the padding gives back the extra leading and a one-line bubble stays the kit's 36px.
|
|
248
|
-
paddingBlock: "calc(token(spacing.field.block) - (1lh - 1em) / 2)",
|
|
230
|
+
paddingBlock: "[calc(token(spacing.field.block) - (1lh - 1em) / 2)]",
|
|
249
231
|
lineHeight: "body",
|
|
250
|
-
background: "token(gradients.selected)",
|
|
232
|
+
background: "[token(gradients.selected)]",
|
|
251
233
|
whiteSpace: "pre-wrap",
|
|
252
|
-
"&::before": { background: "token(gradients.border.selected)" },
|
|
234
|
+
"&::before": { background: "[token(gradients.border.selected)]" },
|
|
253
235
|
"&[data-group-position=first], &[data-group-position=middle]": {
|
|
254
236
|
borderEndEndRadius: "keycap",
|
|
255
237
|
},
|
|
@@ -270,11 +252,10 @@ const bubble = cva({
|
|
|
270
252
|
},
|
|
271
253
|
},
|
|
272
254
|
});
|
|
273
|
-
/* An answer in a thread of `plain` replies: text on the sheet itself, the thread's full width. */
|
|
274
255
|
const plainReply = css({
|
|
275
256
|
display: "grid",
|
|
276
257
|
gap: "layout.contentGap",
|
|
277
|
-
width: "
|
|
258
|
+
width: "full",
|
|
278
259
|
minWidth: "0",
|
|
279
260
|
color: "fg.primary",
|
|
280
261
|
fontSize: "body",
|
|
@@ -284,7 +265,7 @@ const plainReply = css({
|
|
|
284
265
|
/** A line about the conversation rather than in it, centered between bubbles. */
|
|
285
266
|
const systemLine = css({
|
|
286
267
|
alignSelf: "center",
|
|
287
|
-
maxWidth: "85%",
|
|
268
|
+
maxWidth: "[85%]",
|
|
288
269
|
color: "fg.muted",
|
|
289
270
|
fontSize: "code",
|
|
290
271
|
lineHeight: "compact",
|
|
@@ -294,15 +275,13 @@ const systemLine = css({
|
|
|
294
275
|
});
|
|
295
276
|
/*
|
|
296
277
|
* The thread: bubbles 4px apart and groups 12px apart, lines centered between them. Compact
|
|
297
|
-
* keeps 8px above and below and between groups.
|
|
298
|
-
* edges, an answer's parts 12px apart and one turn 16px from the next.
|
|
278
|
+
* keeps 8px above and below and between groups.
|
|
299
279
|
*/
|
|
300
280
|
const thread = css({
|
|
301
281
|
display: "flex",
|
|
302
282
|
flexDirection: "column",
|
|
303
283
|
gap: "layout.tightGap",
|
|
304
284
|
padding: "panel.default",
|
|
305
|
-
// Every line in the thread is set in the medium weight: bubbles, replies, cited cards and meta.
|
|
306
285
|
fontWeight: "medium",
|
|
307
286
|
// The thread follows its newest entry itself: the browser's scroll anchoring would hold the
|
|
308
287
|
// view on what was there as a reply lands and pull it off the foot.
|
|
@@ -330,18 +309,21 @@ const thread = css({
|
|
|
330
309
|
const latestSlot = css({
|
|
331
310
|
position: "sticky",
|
|
332
311
|
bottom: "layout.controlGap",
|
|
333
|
-
zIndex: "1",
|
|
312
|
+
zIndex: "[1]",
|
|
334
313
|
display: "flex",
|
|
335
314
|
alignItems: "flex-end",
|
|
336
315
|
justifyContent: "center",
|
|
337
316
|
height: "0",
|
|
338
|
-
marginTop: "calc(token(spacing.layout.tightGap) * -1)",
|
|
317
|
+
marginTop: "[calc(token(spacing.layout.tightGap) * -1)]",
|
|
339
318
|
pointerEvents: "none",
|
|
340
319
|
"& > *": { pointerEvents: "auto", boxShadow: "card" },
|
|
341
320
|
});
|
|
342
321
|
/** How close to the bottom the reader must be for new content to keep the thread at its foot. */
|
|
343
322
|
const followDistancePx = 96;
|
|
344
|
-
/*
|
|
323
|
+
/* The thread's one live region, which reads each finished reply aloud to a screen reader. */
|
|
324
|
+
const visuallyHidden = css({ srOnly: true });
|
|
325
|
+
/** Past about this many characters an announced reply is cut, so a long one does not hold the reader. */
|
|
326
|
+
const announcedReplyMaximumLength = 300;
|
|
345
327
|
const sourceRow = css({
|
|
346
328
|
display: "flex",
|
|
347
329
|
flexWrap: "wrap",
|
|
@@ -353,7 +335,7 @@ const sourceRow = css({
|
|
|
353
335
|
fontSize: "code",
|
|
354
336
|
lineHeight: "compact",
|
|
355
337
|
"[data-avatar] > &": {
|
|
356
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
338
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
357
339
|
},
|
|
358
340
|
"[data-reply-style=plain] > * > &": { paddingInline: "none" },
|
|
359
341
|
"& a": { color: "fg.secondary", textDecoration: "none", _hover: { color: "fg.primary" } },
|
|
@@ -362,26 +344,22 @@ const sourceCardList = css({
|
|
|
362
344
|
display: "grid",
|
|
363
345
|
gap: "layout.contentGap",
|
|
364
346
|
"[data-avatar] > &": {
|
|
365
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
347
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
366
348
|
},
|
|
367
349
|
});
|
|
368
|
-
/*
|
|
369
|
-
* A source a reply cites, as a card: what it is under its icon, what it says, and when or by
|
|
370
|
-
* whom. `yellow` marks a rule or a value the reply weighs, on the warm wash.
|
|
371
|
-
*/
|
|
372
350
|
const sourceCard = css({
|
|
373
351
|
display: "grid",
|
|
374
352
|
gap: "control.iconGap",
|
|
375
353
|
padding: "panel.default",
|
|
376
354
|
border: "1px solid token(colors.border.divider)",
|
|
377
355
|
borderRadius: "action",
|
|
378
|
-
background: "token(gradients.card)",
|
|
356
|
+
background: "[token(gradients.card)]",
|
|
379
357
|
color: "fg.primary",
|
|
380
358
|
fontSize: "code",
|
|
381
359
|
fontWeight: "medium",
|
|
382
360
|
lineHeight: "log",
|
|
383
361
|
textDecoration: "none",
|
|
384
|
-
"&[data-tone=
|
|
362
|
+
"&[data-tone=warning]": { background: "bg.warningTint" },
|
|
385
363
|
"& > [data-head]": {
|
|
386
364
|
display: "flex",
|
|
387
365
|
alignItems: "center",
|
|
@@ -391,28 +369,26 @@ const sourceCard = css({
|
|
|
391
369
|
lineHeight: "tight",
|
|
392
370
|
"& > svg": { flexShrink: "0", fontSize: "icon.small" },
|
|
393
371
|
},
|
|
394
|
-
"&[data-tone=
|
|
372
|
+
"&[data-tone=warning] > [data-head]": { color: "accent.yellowIcon" },
|
|
395
373
|
"& > [data-meta]": { color: "fg.subtle", lineHeight: "tight" },
|
|
396
374
|
"&:is(a)": { _hover: { borderColor: "border.selected" }, _focusVisible: { focusRing: "outer" } },
|
|
397
375
|
});
|
|
398
|
-
/* Buttons under a reply for what it suggests doing, always shown. */
|
|
399
376
|
const suggestedActionRow = css({
|
|
400
377
|
display: "flex",
|
|
401
378
|
flexWrap: "wrap",
|
|
402
379
|
alignItems: "center",
|
|
403
380
|
gap: "layout.tightGap",
|
|
404
381
|
"[data-avatar] > &": {
|
|
405
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
382
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
406
383
|
},
|
|
407
384
|
});
|
|
408
|
-
/* A reply's closing line, such as what it read, quieter than the reply. */
|
|
409
385
|
const footnote = css({
|
|
410
386
|
margin: "none",
|
|
411
387
|
color: "fg.subtle",
|
|
412
388
|
fontSize: "code",
|
|
413
389
|
lineHeight: "log",
|
|
414
390
|
"[data-avatar] > &": {
|
|
415
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
391
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
416
392
|
},
|
|
417
393
|
});
|
|
418
394
|
/* Files sent with a message, as tiles over its bubble: image thumbnails, or a file's icon and name. */
|
|
@@ -423,7 +399,7 @@ const attachmentTiles = css({
|
|
|
423
399
|
maxWidth: "chat.bubble",
|
|
424
400
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
425
401
|
"[data-avatar] > &": {
|
|
426
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
402
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
427
403
|
},
|
|
428
404
|
});
|
|
429
405
|
/* Prompts to start from, stacked flush over the composer of an empty conversation. */
|
|
@@ -433,14 +409,16 @@ const suggestionList = css({
|
|
|
433
409
|
alignItems: "flex-start",
|
|
434
410
|
justifyContent: "flex-end",
|
|
435
411
|
gap: "layout.tightGap",
|
|
436
|
-
minHeight: "
|
|
412
|
+
minHeight: "full",
|
|
437
413
|
padding: "panel.default",
|
|
438
414
|
});
|
|
439
415
|
const suggestion = css({
|
|
440
416
|
display: "inline-flex",
|
|
441
417
|
alignItems: "center",
|
|
442
|
-
maxWidth: "
|
|
443
|
-
|
|
418
|
+
maxWidth: "full",
|
|
419
|
+
// A prompt longer than the row wraps inside the chip, which grows to hold it.
|
|
420
|
+
minHeight: "control.small",
|
|
421
|
+
paddingBlock: "control.compactBlock",
|
|
444
422
|
paddingInline: "control.compactInline",
|
|
445
423
|
border: "1px solid token(colors.border.control)",
|
|
446
424
|
borderRadius: "action",
|
|
@@ -464,7 +442,6 @@ const entryBlock = css({
|
|
|
464
442
|
"&[data-group-position=single], &[data-group-position=first]": {
|
|
465
443
|
marginTop: "layout.controlGap",
|
|
466
444
|
},
|
|
467
|
-
// A plain reply's parts, such as its cards and buttons under its text, sit 12px apart.
|
|
468
445
|
"&[data-side=assistant]": { "[data-reply-style=plain] > &": { gap: "layout.contentGap" } },
|
|
469
446
|
// A message's own controls, such as Copy, show while it is hovered or focused.
|
|
470
447
|
"& [data-entry-actions]": {
|
|
@@ -472,18 +449,19 @@ const entryBlock = css({
|
|
|
472
449
|
alignItems: "center",
|
|
473
450
|
alignSelf: "center",
|
|
474
451
|
flexShrink: "0",
|
|
475
|
-
opacity: "0",
|
|
476
|
-
|
|
452
|
+
opacity: "[0]",
|
|
453
|
+
transitionProperty: "[opacity]",
|
|
454
|
+
transitionDuration: "fast",
|
|
477
455
|
},
|
|
478
|
-
"&:hover [data-entry-actions], &:focus-within [data-entry-actions]": { opacity: "1" },
|
|
479
|
-
"@media (hover: none)": { "& [data-entry-actions]": { opacity: "1" } },
|
|
456
|
+
"&:hover [data-entry-actions], &:focus-within [data-entry-actions]": { opacity: "[1]" },
|
|
457
|
+
"@media (hover: none)": { "& [data-entry-actions]": { opacity: "[1]" } },
|
|
480
458
|
});
|
|
481
459
|
// Full width, so a bubble's maximum is a share of the thread rather than of its own text.
|
|
482
460
|
const entryRow = css({
|
|
483
461
|
display: "flex",
|
|
484
462
|
alignItems: "flex-end",
|
|
485
463
|
gap: "control.iconGap",
|
|
486
|
-
width: "
|
|
464
|
+
width: "full",
|
|
487
465
|
minWidth: "0",
|
|
488
466
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
489
467
|
// A plain reply's Copy sits at the top of its text, which may run long.
|
|
@@ -500,7 +478,7 @@ const entryFooter = css({
|
|
|
500
478
|
lineHeight: "compact",
|
|
501
479
|
fontVariantNumeric: "tabular-nums",
|
|
502
480
|
"[data-avatar] > &": {
|
|
503
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
481
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
504
482
|
},
|
|
505
483
|
});
|
|
506
484
|
const attachmentList = css({
|
|
@@ -508,18 +486,15 @@ const attachmentList = css({
|
|
|
508
486
|
flexWrap: "wrap",
|
|
509
487
|
gap: "layout.tightGap",
|
|
510
488
|
marginBlockStart: "layout.tightGap",
|
|
511
|
-
opacity: "0.8",
|
|
489
|
+
opacity: "[0.8]",
|
|
512
490
|
fontSize: "code",
|
|
513
|
-
"& a": { color: "inherit" },
|
|
491
|
+
"& a": { color: "[inherit]" },
|
|
514
492
|
});
|
|
515
|
-
/*
|
|
516
|
-
* The agent at work, before its reply: what it is doing beside a spinner, over three lines of
|
|
517
|
-
* the reply to come. The lines stand in for the reply's text, so each is that text's bar height.
|
|
518
|
-
*/
|
|
493
|
+
/* The lines stand in for the reply's text, so each is that text's bar height. */
|
|
519
494
|
const thinking = css({
|
|
520
495
|
display: "grid",
|
|
521
496
|
gap: "layout.contentGap",
|
|
522
|
-
width: "
|
|
497
|
+
width: "full",
|
|
523
498
|
minWidth: "0",
|
|
524
499
|
"& > [data-status]": {
|
|
525
500
|
display: "flex",
|
|
@@ -533,31 +508,30 @@ const thinking = css({
|
|
|
533
508
|
},
|
|
534
509
|
"& > [data-line]": {
|
|
535
510
|
display: "block",
|
|
536
|
-
height: "calc(token(fontSizes.body) * 0.75)",
|
|
511
|
+
height: "[calc(token(fontSizes.body) * 0.75)]",
|
|
537
512
|
borderRadius: "round",
|
|
538
|
-
background: "token(gradients.skeletonFill)",
|
|
513
|
+
background: "[token(gradients.skeletonFill)]",
|
|
539
514
|
},
|
|
540
|
-
"& > [data-line]:nth-of-type(1)": { width: "98%" },
|
|
541
|
-
"& > [data-line]:nth-of-type(2)": { width: "87%" },
|
|
542
|
-
"& > [data-line]:nth-of-type(3)": { width: "66%" },
|
|
515
|
+
"& > [data-line]:nth-of-type(1)": { width: "[98%]" },
|
|
516
|
+
"& > [data-line]:nth-of-type(2)": { width: "[87%]" },
|
|
517
|
+
"& > [data-line]:nth-of-type(3)": { width: "[66%]" },
|
|
543
518
|
});
|
|
544
|
-
/* A reply the agent could not finish: a notice of why, with Try again under it. */
|
|
545
519
|
const failure = css({
|
|
546
520
|
display: "grid",
|
|
547
521
|
justifyItems: "start",
|
|
548
522
|
gap: "layout.contentGap",
|
|
549
|
-
width: "
|
|
523
|
+
width: "full",
|
|
550
524
|
minWidth: "0",
|
|
551
525
|
});
|
|
552
526
|
const failureNotice = css({
|
|
553
527
|
display: "flex",
|
|
554
528
|
gap: "layout.controlGap",
|
|
555
|
-
width: "
|
|
529
|
+
width: "full",
|
|
556
530
|
paddingBlock: "panel.default",
|
|
557
531
|
paddingInline: "control.tabInline",
|
|
558
532
|
borderRadius: "action",
|
|
559
|
-
background: "token(gradients.selected)",
|
|
560
|
-
"&::before": { background: "token(gradients.border.raised)" },
|
|
533
|
+
background: "[token(gradients.selected)]",
|
|
534
|
+
"&::before": { background: "[token(gradients.border.raised)]" },
|
|
561
535
|
"& > svg": { flexShrink: "0", fontSize: "icon.small", color: "accent.red" },
|
|
562
536
|
"& > [data-copy]": { display: "grid", gap: "layout.tightGap", minWidth: "0" },
|
|
563
537
|
"& [data-title]": {
|
|
@@ -568,12 +542,8 @@ const failureNotice = css({
|
|
|
568
542
|
},
|
|
569
543
|
"& [data-description]": { color: "fg.secondary", fontSize: "code", lineHeight: "log" },
|
|
570
544
|
});
|
|
571
|
-
/*
|
|
572
|
-
* The steps the agent took on its way to a reply, folded into one quiet line naming the first and
|
|
573
|
-
* how many more; open, a row for each, its state's icon beside what it did.
|
|
574
|
-
*/
|
|
575
545
|
const stepList = css({
|
|
576
|
-
width: "
|
|
546
|
+
width: "full",
|
|
577
547
|
minWidth: "0",
|
|
578
548
|
color: "fg.subtle",
|
|
579
549
|
fontSize: "code",
|
|
@@ -582,17 +552,17 @@ const stepList = css({
|
|
|
582
552
|
display: "inline-flex",
|
|
583
553
|
alignItems: "center",
|
|
584
554
|
gap: "control.iconGap",
|
|
585
|
-
maxWidth: "
|
|
555
|
+
maxWidth: "full",
|
|
586
556
|
borderRadius: "control",
|
|
587
557
|
cursor: "pointer",
|
|
588
558
|
listStyle: "none",
|
|
589
559
|
"&::-webkit-details-marker": { display: "none" },
|
|
590
560
|
_hover: { color: "fg.primary" },
|
|
591
561
|
_focusVisible: { focusRing: "outer" },
|
|
592
|
-
"& > svg": { flexShrink: "0", transitionProperty: "rotate", transitionDuration: "fast" },
|
|
562
|
+
"& > svg": { flexShrink: "0", transitionProperty: "[rotate]", transitionDuration: "fast" },
|
|
593
563
|
"& > span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
594
564
|
},
|
|
595
|
-
"&[open] > summary > svg": { rotate: "90deg" },
|
|
565
|
+
"&[open] > summary > svg": { rotate: "[90deg]" },
|
|
596
566
|
"& > ol": {
|
|
597
567
|
display: "grid",
|
|
598
568
|
gap: "layout.controlGap",
|
|
@@ -644,7 +614,6 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
644
614
|
}
|
|
645
615
|
: undefined;
|
|
646
616
|
const input = (_jsx(MentionInput, { ...inputProps, "aria-label": ariaLabel, autoFocus: autoFocus, className: cx(variant === "page" ? pageInput : variant === "sheet" ? sheetInput : feedInput, inputProps?.className), value: currentValue, onValueChange: setValue, triggers: mentions, mentionMark: mentionMark, listLabel: mentionsLabel, placeholder: placeholder, submitOn: variant === "feed" ? "mod+enter" : "enter", onSubmit: send }));
|
|
647
|
-
// The sheet's send is the bar's small round one; the feed's sits in its pill, 32 round.
|
|
648
617
|
const sendButton = (_jsx(IconButton, { "aria-label": submitLabel, disabled: !canSend, onClick: send, variant: variant === "page" ? "raised" : "primary", "data-send-button": "", children: _jsx(ArrowUpIcon, { className: icon({ size: "small" }), weight: "bold" }) }));
|
|
649
618
|
const waiting = attachments ? _jsx("div", { "data-attachments": "", children: attachments }) : null;
|
|
650
619
|
const waveform = dictation && (_jsx(ChatDictationWaveform, { levels: dictation.levels, listening: dictation.listening }));
|
|
@@ -656,34 +625,27 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
656
625
|
}
|
|
657
626
|
return (_jsxs("div", { ...props, className: cx(pageComposer, className), "data-ciridae-ui": "", onPasteCapture: pasteFiles, children: [waiting, input, _jsxs("div", { className: composerFooter, children: [_jsx("div", { className: actionGroup, children: tools }), waveform, _jsxs("div", { className: actionGroup, children: [actions, sendButton] })] })] }));
|
|
658
627
|
}
|
|
659
|
-
/** A tool for the sheet composer's bar, such as the paperclip: its icon alone. */
|
|
660
628
|
export function ChatComposerTool({ className, ...props }) {
|
|
661
629
|
return (_jsx("button", { type: "button", ...props, className: cx(composerTool, className), "data-ciridae-ui": "" }));
|
|
662
630
|
}
|
|
663
|
-
/**
|
|
664
|
-
* Dictation's waveform: a bar per level, each as tall as its level; flat while `listening` is
|
|
665
|
-
* off, such as while the host turns what it heard into text.
|
|
666
|
-
*/
|
|
631
|
+
/** Flat while `listening` is off, such as while the host turns what it heard into text. */
|
|
667
632
|
export function ChatDictationWaveform({ levels, listening, className, ...props }) {
|
|
668
633
|
return (_jsx("div", { ...props, className: cx(dictationWaveform, className), "data-ciridae-ui": "", "data-listening": listening || undefined, "aria-hidden": "true", children: levels.map((level, index) => (_jsx("span", { style: {
|
|
669
634
|
transform: `scaleY(${listening ? Math.max(silentWaveformLevel, level) : silentWaveformLevel})`,
|
|
670
635
|
} }, index))) }));
|
|
671
636
|
}
|
|
672
|
-
/** A message bubble: the user's raised on the right, another author's filled on the left. */
|
|
673
637
|
export function ChatBubble({ side, groupPosition, className, ...props }) {
|
|
674
638
|
return (_jsx("div", { ...props, className: cx(bubble({ side }), className), "data-ciridae-ui": "", "data-edge": side === "user" ? "gradient" : undefined, "data-group-position": groupPosition }));
|
|
675
639
|
}
|
|
676
|
-
/** A centered line between bubbles: a finished tool call, a decision, something the page did. */
|
|
677
640
|
export function ChatSystemLine({ tone, className, ...props }) {
|
|
678
641
|
return (_jsx("div", { ...props, className: cx(systemLine, className), "data-ciridae-ui": "", "data-tone": tone === "error" ? "error" : undefined }));
|
|
679
642
|
}
|
|
680
643
|
export function ChatAction({ className, ...props }) {
|
|
681
644
|
return (_jsx("button", { ...props, className: cx(chatAction, className), "data-ciridae-ui": "", type: props.type ?? "button" }));
|
|
682
645
|
}
|
|
683
|
-
/** A source cited in a reply, as a card of what it says under its name. */
|
|
684
646
|
export function ChatSourceCard({ label, icon, children, meta, tone = "neutral", href, className, }) {
|
|
685
647
|
const content = (_jsxs(_Fragment, { children: [_jsxs("span", { "data-head": "", children: [icon, label] }), _jsx("span", { children: children }), meta && _jsx("span", { "data-meta": "", children: meta })] }));
|
|
686
|
-
return href ? (_jsx("a", { href: href, className: cx(sourceCard, className), "data-ciridae-ui": "", "data-tone": tone, children: content })) : (_jsx("div", { className: cx(sourceCard, className), "data-ciridae-ui": "", "data-tone": tone, children: content }));
|
|
648
|
+
return href ? (_jsx("a", { href: href, target: "_blank", rel: "noreferrer", className: cx(sourceCard, className), "data-ciridae-ui": "", "data-tone": tone, children: content })) : (_jsx("div", { className: cx(sourceCard, className), "data-ciridae-ui": "", "data-tone": tone, children: content }));
|
|
687
649
|
}
|
|
688
650
|
/*
|
|
689
651
|
* A conversation in time order. Close messages from one author read as a group: bubbles 4px
|
|
@@ -708,12 +670,14 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
708
670
|
.reverse()
|
|
709
671
|
.find((entry) => entry.kind === "message" && entry.side === "user");
|
|
710
672
|
const latest = useFollowLatest(rootRef, newestUserMessage?.id);
|
|
673
|
+
const replyBodies = useRef(new Map());
|
|
674
|
+
const replyAnnouncement = useFinishedReplyAnnouncement(entries, replyBodies, (entry) => authorOf(entry)?.name);
|
|
711
675
|
return (_jsxs("div", { ref: rootRef, className: cx(thread, className), "data-ciridae-ui": "", "data-density": density === "compact" ? "compact" : undefined, "data-reply-style": replyStyle, children: [entries.map((entry, index) => {
|
|
712
676
|
const previous = entries[index - 1];
|
|
713
677
|
const newDay = dayLabels && (!previous || !isSameDay(previous.at, entry.at));
|
|
714
678
|
const content = renderEntry(entry, index);
|
|
715
679
|
return newDay ? (_jsxs(Fragment, { children: [_jsx(DayHeading, { at: entry.at }), content] }, entry.id)) : (content);
|
|
716
|
-
}), _jsx("div", { className: latestSlot, children: latest.away && (_jsx(IconButton, { "aria-label": labels.chat.scrollToLatest, size: "compact", variant: "raised", onClick: latest.scrollToLatest, children: _jsx(ArrowDownIcon, { className: icon({ size: "small" }) }) })) })] }));
|
|
680
|
+
}), _jsx("div", { className: latestSlot, children: latest.away && (_jsx(IconButton, { "aria-label": labels.chat.scrollToLatest, size: "compact", variant: "raised", onClick: latest.scrollToLatest, children: _jsx(ArrowDownIcon, { className: icon({ size: "small" }) }) })) }), _jsx("div", { className: visuallyHidden, role: "status", "aria-live": "polite", "aria-atomic": "true", children: replyAnnouncement })] }));
|
|
717
681
|
function renderEntry(entry, index) {
|
|
718
682
|
if (entry.kind === "line") {
|
|
719
683
|
return (_jsx(ChatSystemLine, { tone: entry.tone, children: entry.text }, entry.id));
|
|
@@ -729,18 +693,21 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
729
693
|
: null;
|
|
730
694
|
const sourceLinks = message?.sources?.filter((source) => source.quote === undefined) ?? [];
|
|
731
695
|
const sourceCards = message?.sources?.filter((source) => source.quote !== undefined) ?? [];
|
|
732
|
-
// A plain reply drops the assistant's author and time; the user's messages keep their time.
|
|
733
696
|
const footer = closesGroup &&
|
|
734
697
|
entry.kind !== "typing" &&
|
|
735
698
|
entry.kind !== "steps" &&
|
|
736
699
|
(!plain || side === "user" || message?.sending);
|
|
737
|
-
return (_jsxs("div", { className: entryBlock, "data-side": side, "data-avatar": avatar || undefined, "data-group-position": position, children: [tiles && _jsx(AttachmentTiles, { attachments: tiles, resolveUrl: resolveAttachmentUrl }), !(tiles && message?.body === "") && (_jsxs("div", { className: entryRow, title: message ? formatDateTime(entry.at) : undefined, children: [avatar && (_jsx("span", { className: avatarSlot, children: closesGroup && author && (_jsx(Avatar, { kind: author.kind, src: author.src, label: author.name, size: "small", "aria-hidden": "true" })) })), _jsx(ThreadEntryContent, { entry: entry, side: side, groupPosition: position, renderCustom: renderCustom, attachmentTiles: attachmentTiles, plain: plain
|
|
700
|
+
return (_jsxs("div", { className: entryBlock, "data-side": side, "data-avatar": avatar || undefined, "data-group-position": position, children: [tiles && _jsx(AttachmentTiles, { attachments: tiles, resolveUrl: resolveAttachmentUrl }), !(tiles && message?.body === "") && (_jsxs("div", { className: entryRow, title: message ? formatDateTime(entry.at) : undefined, children: [avatar && (_jsx("span", { className: avatarSlot, children: closesGroup && author && (_jsx(Avatar, { kind: author.kind, src: author.src, label: author.name, size: "small", "aria-hidden": "true" })) })), _jsx(ThreadEntryContent, { entry: entry, side: side, groupPosition: position, renderCustom: renderCustom, attachmentTiles: attachmentTiles, plain: plain, bodyRef: (element) => {
|
|
701
|
+
if (element)
|
|
702
|
+
replyBodies.current.set(entry.id, element);
|
|
703
|
+
else
|
|
704
|
+
replyBodies.current.delete(entry.id);
|
|
705
|
+
} }), message?.actions && !message.sending && (_jsx("span", { "data-entry-actions": "", children: message.actions }))] })), sourceCards.length > 0 && (_jsx("div", { className: sourceCardList, children: sourceCards.map((source) => (_jsx(ChatSourceCard, { label: source.label, icon: source.icon, meta: source.meta, tone: source.tone, href: source.href, children: source.quote }, source.id))) })), sourceLinks.length > 0 && (_jsx("span", { className: sourceRow, children: sourceLinks.map((source) => source.href ? (_jsx("a", { href: source.href, target: "_blank", rel: "noreferrer", children: source.label }, source.id)) : (_jsx("span", { children: source.label }, source.id))) })), message?.suggestedActions && (_jsx("div", { className: suggestedActionRow, children: message.suggestedActions })), message?.footnote && _jsx("p", { className: footnote, children: message.footnote }), footer && (_jsx("span", { className: entryFooter, children: message?.sending ? (_jsxs(_Fragment, { children: [_jsx(Spinner, { "aria-hidden": "true", size: "compact" }), labels.chat.sending] })) : (_jsxs("span", { children: [author && (_jsxs(_Fragment, { children: [author.label ?? author.name, " · "] })), _jsx(MessageTime, { at: entry.at, format: formatTime, dated: !dayLabels })] })) }))] }, entry.id));
|
|
738
706
|
}
|
|
739
707
|
}
|
|
740
|
-
/** A day's heading in a thread that runs over days: Today, Yesterday or the weekday, then the date. */
|
|
741
708
|
function DayHeading({ at }) {
|
|
742
709
|
const { labels, formatDate } = useCiridaeLocale();
|
|
743
|
-
return (_jsx(LogGroupHeader, { role: "separator", "data-day-heading": "", label: isToday(at)
|
|
710
|
+
return (_jsx(LogGroupHeader, { role: "separator", "data-day-heading": "", density: "panel", label: isToday(at)
|
|
744
711
|
? labels.dates.today
|
|
745
712
|
: isYesterday(at)
|
|
746
713
|
? labels.dates.yesterday
|
|
@@ -768,14 +735,15 @@ function AttachmentTile({ attachment, resolveUrl, onOpen, }) {
|
|
|
768
735
|
const content = image ? (_jsx("img", { src: url, alt: attachment.name })) : (_jsxs(_Fragment, { children: [fileTypeIcon({ name: attachment.name, type: attachment.mediaType ?? "" }), _jsx("span", { children: attachment.name })] }));
|
|
769
736
|
return onOpen ? (_jsx("button", { type: "button", className: fileSquare, "data-image": image || undefined, "aria-label": labels.lightbox.preview(attachment.name), title: attachment.name, onClick: onOpen, children: content })) : (_jsx("span", { className: fileSquare, "data-image": image || undefined, title: attachment.name, children: content }));
|
|
770
737
|
}
|
|
771
|
-
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, plain, }) {
|
|
738
|
+
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, plain, bodyRef, }) {
|
|
772
739
|
switch (entry.kind) {
|
|
773
740
|
case "message": {
|
|
774
741
|
const attachmentNames = !tiles && entry.attachments && entry.attachments.length > 0 && (_jsx("span", { className: attachmentList, children: entry.attachments.map((attachment) => attachment.url ? (_jsx("a", { href: attachment.url, target: "_blank", rel: "noreferrer", children: attachment.name }, attachment.id)) : (_jsx("span", { children: attachment.name }, attachment.id))) }));
|
|
742
|
+
const busy = entry.sending || entry.streaming || undefined;
|
|
775
743
|
if (plain && side === "assistant") {
|
|
776
|
-
return (_jsxs("div", { className: plainReply, "aria-busy":
|
|
744
|
+
return (_jsxs("div", { ref: bodyRef, className: plainReply, "aria-busy": busy, children: [entry.body, attachmentNames] }));
|
|
777
745
|
}
|
|
778
|
-
return (_jsxs(ChatBubble, { side: side, groupPosition: groupPosition, "aria-busy":
|
|
746
|
+
return (_jsxs(ChatBubble, { ref: bodyRef, side: side, groupPosition: groupPosition, "aria-busy": busy, children: [entry.body, attachmentNames] }));
|
|
779
747
|
}
|
|
780
748
|
case "typing":
|
|
781
749
|
return _jsx(AgentThinking, { label: entry.label });
|
|
@@ -787,27 +755,77 @@ function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachme
|
|
|
787
755
|
return renderCustom?.(entry.data);
|
|
788
756
|
}
|
|
789
757
|
}
|
|
790
|
-
/** The agent at work: what it is doing beside a spinner, over lines standing in for its reply. */
|
|
791
758
|
function AgentThinking({ label }) {
|
|
792
759
|
const { labels } = useCiridaeLocale();
|
|
793
|
-
return (_jsxs("div", { className: thinking,
|
|
760
|
+
return (_jsxs("div", { className: thinking, children: [_jsxs("div", { "data-status": "", children: [_jsx(Spinner, { "aria-hidden": "true" }), _jsx("span", { children: label ?? labels.common.working })] }), _jsx("span", { "data-line": "" }), _jsx("span", { "data-line": "" }), _jsx("span", { "data-line": "" })] }));
|
|
794
761
|
}
|
|
795
|
-
/** The steps behind a reply, folded into a line naming the first and how many more. */
|
|
796
762
|
function AgentSteps({ steps }) {
|
|
763
|
+
const { labels } = useCiridaeLocale();
|
|
797
764
|
const [first] = steps;
|
|
798
765
|
if (!first)
|
|
799
766
|
return null;
|
|
800
|
-
return (_jsxs("details", { className: stepList, children: [_jsxs("summary", { children: [_jsx(CaretRightIcon, { className: cx(icon({ size: "compact" }), directional) }),
|
|
767
|
+
return (_jsxs("details", { className: stepList, children: [_jsxs("summary", { children: [_jsx(CaretRightIcon, { className: cx(icon({ size: "compact" }), directional) }), _jsx("span", { children: labels.chat.foldedSteps(first.title, steps.length - 1) })] }), _jsx("ol", { children: steps.map((step) => (_jsxs("li", { "data-status": step.status, children: [step.status === "running" ? (_jsx(Spinner, { "aria-hidden": "true" })) : step.status === "failed" ? (_jsx(WarningCircleIcon, { "aria-hidden": "true" })) : step.kind === "tool" ? (_jsx(WrenchIcon, { "aria-hidden": "true" })) : (_jsx(LightbulbIcon, { "aria-hidden": "true" })), _jsx("span", { "data-title": "", children: step.title }), _jsx("span", { className: visuallyHidden, children: labels.chat.stepStatus[step.status] }), step.detail && _jsx("span", { "data-detail": "", children: step.detail })] }, step.id))) })] }));
|
|
801
768
|
}
|
|
802
|
-
/** A reply that did not finish: a notice of why, and Try again under it. */
|
|
803
769
|
function FailureNotice({ title, message, onRetry, }) {
|
|
804
770
|
const { labels } = useCiridaeLocale();
|
|
805
771
|
return (_jsxs("div", { className: failure, children: [_jsxs("div", { className: failureNotice, role: "alert", "data-ciridae-ui": "", "data-edge": "gradient", children: [_jsx(WarningCircleIcon, { "aria-hidden": "true" }), _jsxs("div", { "data-copy": "", children: [_jsx("span", { "data-title": "", children: title ?? labels.chat.couldNotAnswer }), message && _jsx("span", { "data-description": "", children: message })] })] }), _jsx(Button, { variant: "secondary", size: "medium", icon: _jsx(ArrowClockwiseIcon, { className: icon({ size: "small" }) }), onClick: onRetry, children: labels.common.tryAgain })] }));
|
|
806
772
|
}
|
|
807
|
-
/** The side an entry sits on: the agent's work, its steps and failures are the assistant's. */
|
|
808
773
|
function entrySide(entry) {
|
|
809
774
|
return entry.kind === "message" || entry.kind === "custom" ? entry.side : "assistant";
|
|
810
775
|
}
|
|
776
|
+
/**
|
|
777
|
+
* What the thread's live region says: the newest assistant reply that has finished since the
|
|
778
|
+
* last render, as its author and its text. A reply finishes as it appears without `streaming`, or
|
|
779
|
+
* as its `streaming` turns off. Replies already there when the thread mounts, or when another
|
|
780
|
+
* conversation replaces the entries (its first entry changes), are never announced.
|
|
781
|
+
*/
|
|
782
|
+
function useFinishedReplyAnnouncement(entries, replyBodies, authorNameOf) {
|
|
783
|
+
const { labels } = useCiridaeLocale();
|
|
784
|
+
const [announcement, setAnnouncement] = useState("");
|
|
785
|
+
// Every assistant reply the thread has drawn and whether it was still arriving, for the
|
|
786
|
+
// conversation whose first entry is `conversationKey`.
|
|
787
|
+
const drawnRepliesRef = useRef(null);
|
|
788
|
+
useEffect(() => {
|
|
789
|
+
const replies = entries.filter((entry) => entry.kind === "message" && entry.side === "assistant");
|
|
790
|
+
const conversationKey = entries[0]?.id;
|
|
791
|
+
const drawnReplies = drawnRepliesRef.current;
|
|
792
|
+
if (drawnReplies?.conversationKey !== conversationKey) {
|
|
793
|
+
drawnRepliesRef.current = {
|
|
794
|
+
conversationKey,
|
|
795
|
+
streamingByReplyId: new Map(replies.map((reply) => [reply.id, reply.streaming ?? false])),
|
|
796
|
+
};
|
|
797
|
+
if (drawnReplies)
|
|
798
|
+
setAnnouncement("");
|
|
799
|
+
return;
|
|
800
|
+
}
|
|
801
|
+
let newestFinishedReply;
|
|
802
|
+
for (const reply of replies) {
|
|
803
|
+
const streaming = reply.streaming ?? false;
|
|
804
|
+
if (!streaming && drawnReplies.streamingByReplyId.get(reply.id) !== false) {
|
|
805
|
+
newestFinishedReply = reply;
|
|
806
|
+
}
|
|
807
|
+
drawnReplies.streamingByReplyId.set(reply.id, streaming);
|
|
808
|
+
}
|
|
809
|
+
if (!newestFinishedReply)
|
|
810
|
+
return;
|
|
811
|
+
const text = readReplyTextToAnnounce(replyBodies.current.get(newestFinishedReply.id));
|
|
812
|
+
if (!text)
|
|
813
|
+
return;
|
|
814
|
+
const authorName = authorNameOf(newestFinishedReply);
|
|
815
|
+
setAnnouncement(authorName ? labels.chat.replyAnnouncement(authorName, text) : text);
|
|
816
|
+
}, [entries, replyBodies, authorNameOf, labels.chat]);
|
|
817
|
+
return announcement;
|
|
818
|
+
}
|
|
819
|
+
/**
|
|
820
|
+
* A reply's text as one line, cut at about `announcedReplyMaximumLength` characters. `innerText`,
|
|
821
|
+
* since `textContent` runs one paragraph's last word into the next one's first.
|
|
822
|
+
*/
|
|
823
|
+
function readReplyTextToAnnounce(body) {
|
|
824
|
+
const text = (body?.innerText ?? "").replace(/\s+/g, " ").trim();
|
|
825
|
+
if (text.length <= announcedReplyMaximumLength)
|
|
826
|
+
return text;
|
|
827
|
+
return `${text.slice(0, announcedReplyMaximumLength).trimEnd()}…`;
|
|
828
|
+
}
|
|
811
829
|
/* A message's time, which turns into its clock time while hovered. */
|
|
812
830
|
const messageTime = css({
|
|
813
831
|
"& > [data-exact]": { display: "none" },
|
|
@@ -893,12 +911,16 @@ function useFollowLatest(rootRef, sentMessageId) {
|
|
|
893
911
|
scroller.scrollTop = scroller.scrollHeight;
|
|
894
912
|
};
|
|
895
913
|
// Content that grew since the last look keeps a reader at the foot there, whichever of its
|
|
896
|
-
// resize or the scroll it caused is seen first; a reader's own scroll leaves the height.
|
|
914
|
+
// resize or the scroll it caused is seen first; a reader's own scroll leaves the height. So
|
|
915
|
+
// does a view that changed size, as when the composer grows a line or takes a file.
|
|
897
916
|
let lastHeight = scroller.scrollHeight;
|
|
917
|
+
let lastViewHeight = scroller.clientHeight;
|
|
898
918
|
const follow = () => {
|
|
899
919
|
const grew = scroller.scrollHeight !== lastHeight;
|
|
920
|
+
const viewResized = scroller.clientHeight !== lastViewHeight;
|
|
900
921
|
lastHeight = scroller.scrollHeight;
|
|
901
|
-
|
|
922
|
+
lastViewHeight = scroller.clientHeight;
|
|
923
|
+
if ((grew || viewResized) && atFootRef.current)
|
|
902
924
|
return toFoot();
|
|
903
925
|
const distance = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
|
|
904
926
|
atFootRef.current = distance < followDistancePx;
|