@ciridae/ui 1.0.4 → 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 +70 -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 +112 -54
- package/dist/components/chat.d.ts +27 -56
- package/dist/components/chat.js +153 -132
- 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 -7
- package/dist/components/editable-field.js +14 -27
- 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 +41 -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 +820 -1297
- package/dist/styled-system/tokens/index.mjs +78 -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 +39 -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: {
|
|
@@ -243,14 +225,13 @@ const bubble = cva({
|
|
|
243
225
|
user: {
|
|
244
226
|
alignSelf: "flex-end",
|
|
245
227
|
// Wrapped lines take the body's spacing. The kit sets one line at the type's own height
|
|
246
|
-
// with its 1px stroke in the layout,
|
|
247
|
-
// a one-line bubble stays the kit's
|
|
248
|
-
paddingBlock: "calc(token(spacing.field.block)
|
|
249
|
-
paddingInline: "calc(token(spacing.control.tabInline) + token(sizes.border.hairline))",
|
|
228
|
+
// with its 1px stroke in the layout, which the edge's transparent border already counts,
|
|
229
|
+
// so the padding gives back the extra leading and a one-line bubble stays the kit's 36px.
|
|
230
|
+
paddingBlock: "[calc(token(spacing.field.block) - (1lh - 1em) / 2)]",
|
|
250
231
|
lineHeight: "body",
|
|
251
|
-
background: "token(gradients.selected)",
|
|
232
|
+
background: "[token(gradients.selected)]",
|
|
252
233
|
whiteSpace: "pre-wrap",
|
|
253
|
-
"&::before": { background: "token(gradients.border.selected)" },
|
|
234
|
+
"&::before": { background: "[token(gradients.border.selected)]" },
|
|
254
235
|
"&[data-group-position=first], &[data-group-position=middle]": {
|
|
255
236
|
borderEndEndRadius: "keycap",
|
|
256
237
|
},
|
|
@@ -271,11 +252,10 @@ const bubble = cva({
|
|
|
271
252
|
},
|
|
272
253
|
},
|
|
273
254
|
});
|
|
274
|
-
/* An answer in a thread of `plain` replies: text on the sheet itself, the thread's full width. */
|
|
275
255
|
const plainReply = css({
|
|
276
256
|
display: "grid",
|
|
277
257
|
gap: "layout.contentGap",
|
|
278
|
-
width: "
|
|
258
|
+
width: "full",
|
|
279
259
|
minWidth: "0",
|
|
280
260
|
color: "fg.primary",
|
|
281
261
|
fontSize: "body",
|
|
@@ -285,7 +265,7 @@ const plainReply = css({
|
|
|
285
265
|
/** A line about the conversation rather than in it, centered between bubbles. */
|
|
286
266
|
const systemLine = css({
|
|
287
267
|
alignSelf: "center",
|
|
288
|
-
maxWidth: "85%",
|
|
268
|
+
maxWidth: "[85%]",
|
|
289
269
|
color: "fg.muted",
|
|
290
270
|
fontSize: "code",
|
|
291
271
|
lineHeight: "compact",
|
|
@@ -295,15 +275,13 @@ const systemLine = css({
|
|
|
295
275
|
});
|
|
296
276
|
/*
|
|
297
277
|
* The thread: bubbles 4px apart and groups 12px apart, lines centered between them. Compact
|
|
298
|
-
* keeps 8px above and below and between groups.
|
|
299
|
-
* edges, an answer's parts 12px apart and one turn 16px from the next.
|
|
278
|
+
* keeps 8px above and below and between groups.
|
|
300
279
|
*/
|
|
301
280
|
const thread = css({
|
|
302
281
|
display: "flex",
|
|
303
282
|
flexDirection: "column",
|
|
304
283
|
gap: "layout.tightGap",
|
|
305
284
|
padding: "panel.default",
|
|
306
|
-
// Every line in the thread is set in the medium weight: bubbles, replies, cited cards and meta.
|
|
307
285
|
fontWeight: "medium",
|
|
308
286
|
// The thread follows its newest entry itself: the browser's scroll anchoring would hold the
|
|
309
287
|
// view on what was there as a reply lands and pull it off the foot.
|
|
@@ -331,18 +309,21 @@ const thread = css({
|
|
|
331
309
|
const latestSlot = css({
|
|
332
310
|
position: "sticky",
|
|
333
311
|
bottom: "layout.controlGap",
|
|
334
|
-
zIndex: "1",
|
|
312
|
+
zIndex: "[1]",
|
|
335
313
|
display: "flex",
|
|
336
314
|
alignItems: "flex-end",
|
|
337
315
|
justifyContent: "center",
|
|
338
316
|
height: "0",
|
|
339
|
-
marginTop: "calc(token(spacing.layout.tightGap) * -1)",
|
|
317
|
+
marginTop: "[calc(token(spacing.layout.tightGap) * -1)]",
|
|
340
318
|
pointerEvents: "none",
|
|
341
319
|
"& > *": { pointerEvents: "auto", boxShadow: "card" },
|
|
342
320
|
});
|
|
343
321
|
/** How close to the bottom the reader must be for new content to keep the thread at its foot. */
|
|
344
322
|
const followDistancePx = 96;
|
|
345
|
-
/*
|
|
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;
|
|
346
327
|
const sourceRow = css({
|
|
347
328
|
display: "flex",
|
|
348
329
|
flexWrap: "wrap",
|
|
@@ -354,7 +335,7 @@ const sourceRow = css({
|
|
|
354
335
|
fontSize: "code",
|
|
355
336
|
lineHeight: "compact",
|
|
356
337
|
"[data-avatar] > &": {
|
|
357
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
338
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
358
339
|
},
|
|
359
340
|
"[data-reply-style=plain] > * > &": { paddingInline: "none" },
|
|
360
341
|
"& a": { color: "fg.secondary", textDecoration: "none", _hover: { color: "fg.primary" } },
|
|
@@ -363,26 +344,22 @@ const sourceCardList = css({
|
|
|
363
344
|
display: "grid",
|
|
364
345
|
gap: "layout.contentGap",
|
|
365
346
|
"[data-avatar] > &": {
|
|
366
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
347
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
367
348
|
},
|
|
368
349
|
});
|
|
369
|
-
/*
|
|
370
|
-
* A source a reply cites, as a card: what it is under its icon, what it says, and when or by
|
|
371
|
-
* whom. `yellow` marks a rule or a value the reply weighs, on the warm wash.
|
|
372
|
-
*/
|
|
373
350
|
const sourceCard = css({
|
|
374
351
|
display: "grid",
|
|
375
352
|
gap: "control.iconGap",
|
|
376
353
|
padding: "panel.default",
|
|
377
354
|
border: "1px solid token(colors.border.divider)",
|
|
378
355
|
borderRadius: "action",
|
|
379
|
-
background: "token(gradients.card)",
|
|
356
|
+
background: "[token(gradients.card)]",
|
|
380
357
|
color: "fg.primary",
|
|
381
358
|
fontSize: "code",
|
|
382
359
|
fontWeight: "medium",
|
|
383
360
|
lineHeight: "log",
|
|
384
361
|
textDecoration: "none",
|
|
385
|
-
"&[data-tone=
|
|
362
|
+
"&[data-tone=warning]": { background: "bg.warningTint" },
|
|
386
363
|
"& > [data-head]": {
|
|
387
364
|
display: "flex",
|
|
388
365
|
alignItems: "center",
|
|
@@ -392,28 +369,26 @@ const sourceCard = css({
|
|
|
392
369
|
lineHeight: "tight",
|
|
393
370
|
"& > svg": { flexShrink: "0", fontSize: "icon.small" },
|
|
394
371
|
},
|
|
395
|
-
"&[data-tone=
|
|
372
|
+
"&[data-tone=warning] > [data-head]": { color: "accent.yellowIcon" },
|
|
396
373
|
"& > [data-meta]": { color: "fg.subtle", lineHeight: "tight" },
|
|
397
374
|
"&:is(a)": { _hover: { borderColor: "border.selected" }, _focusVisible: { focusRing: "outer" } },
|
|
398
375
|
});
|
|
399
|
-
/* Buttons under a reply for what it suggests doing, always shown. */
|
|
400
376
|
const suggestedActionRow = css({
|
|
401
377
|
display: "flex",
|
|
402
378
|
flexWrap: "wrap",
|
|
403
379
|
alignItems: "center",
|
|
404
380
|
gap: "layout.tightGap",
|
|
405
381
|
"[data-avatar] > &": {
|
|
406
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
382
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
407
383
|
},
|
|
408
384
|
});
|
|
409
|
-
/* A reply's closing line, such as what it read, quieter than the reply. */
|
|
410
385
|
const footnote = css({
|
|
411
386
|
margin: "none",
|
|
412
387
|
color: "fg.subtle",
|
|
413
388
|
fontSize: "code",
|
|
414
389
|
lineHeight: "log",
|
|
415
390
|
"[data-avatar] > &": {
|
|
416
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
391
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
417
392
|
},
|
|
418
393
|
});
|
|
419
394
|
/* Files sent with a message, as tiles over its bubble: image thumbnails, or a file's icon and name. */
|
|
@@ -424,7 +399,7 @@ const attachmentTiles = css({
|
|
|
424
399
|
maxWidth: "chat.bubble",
|
|
425
400
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
426
401
|
"[data-avatar] > &": {
|
|
427
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
402
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
428
403
|
},
|
|
429
404
|
});
|
|
430
405
|
/* Prompts to start from, stacked flush over the composer of an empty conversation. */
|
|
@@ -434,14 +409,16 @@ const suggestionList = css({
|
|
|
434
409
|
alignItems: "flex-start",
|
|
435
410
|
justifyContent: "flex-end",
|
|
436
411
|
gap: "layout.tightGap",
|
|
437
|
-
minHeight: "
|
|
412
|
+
minHeight: "full",
|
|
438
413
|
padding: "panel.default",
|
|
439
414
|
});
|
|
440
415
|
const suggestion = css({
|
|
441
416
|
display: "inline-flex",
|
|
442
417
|
alignItems: "center",
|
|
443
|
-
maxWidth: "
|
|
444
|
-
|
|
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",
|
|
445
422
|
paddingInline: "control.compactInline",
|
|
446
423
|
border: "1px solid token(colors.border.control)",
|
|
447
424
|
borderRadius: "action",
|
|
@@ -465,7 +442,6 @@ const entryBlock = css({
|
|
|
465
442
|
"&[data-group-position=single], &[data-group-position=first]": {
|
|
466
443
|
marginTop: "layout.controlGap",
|
|
467
444
|
},
|
|
468
|
-
// A plain reply's parts, such as its cards and buttons under its text, sit 12px apart.
|
|
469
445
|
"&[data-side=assistant]": { "[data-reply-style=plain] > &": { gap: "layout.contentGap" } },
|
|
470
446
|
// A message's own controls, such as Copy, show while it is hovered or focused.
|
|
471
447
|
"& [data-entry-actions]": {
|
|
@@ -473,18 +449,19 @@ const entryBlock = css({
|
|
|
473
449
|
alignItems: "center",
|
|
474
450
|
alignSelf: "center",
|
|
475
451
|
flexShrink: "0",
|
|
476
|
-
opacity: "0",
|
|
477
|
-
|
|
452
|
+
opacity: "[0]",
|
|
453
|
+
transitionProperty: "[opacity]",
|
|
454
|
+
transitionDuration: "fast",
|
|
478
455
|
},
|
|
479
|
-
"&:hover [data-entry-actions], &:focus-within [data-entry-actions]": { opacity: "1" },
|
|
480
|
-
"@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]" } },
|
|
481
458
|
});
|
|
482
459
|
// Full width, so a bubble's maximum is a share of the thread rather than of its own text.
|
|
483
460
|
const entryRow = css({
|
|
484
461
|
display: "flex",
|
|
485
462
|
alignItems: "flex-end",
|
|
486
463
|
gap: "control.iconGap",
|
|
487
|
-
width: "
|
|
464
|
+
width: "full",
|
|
488
465
|
minWidth: "0",
|
|
489
466
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
490
467
|
// A plain reply's Copy sits at the top of its text, which may run long.
|
|
@@ -501,7 +478,7 @@ const entryFooter = css({
|
|
|
501
478
|
lineHeight: "compact",
|
|
502
479
|
fontVariantNumeric: "tabular-nums",
|
|
503
480
|
"[data-avatar] > &": {
|
|
504
|
-
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
481
|
+
marginInlineStart: "[calc(token(sizes.avatar.small) + token(spacing.control.iconGap))]",
|
|
505
482
|
},
|
|
506
483
|
});
|
|
507
484
|
const attachmentList = css({
|
|
@@ -509,18 +486,15 @@ const attachmentList = css({
|
|
|
509
486
|
flexWrap: "wrap",
|
|
510
487
|
gap: "layout.tightGap",
|
|
511
488
|
marginBlockStart: "layout.tightGap",
|
|
512
|
-
opacity: "0.8",
|
|
489
|
+
opacity: "[0.8]",
|
|
513
490
|
fontSize: "code",
|
|
514
|
-
"& a": { color: "inherit" },
|
|
491
|
+
"& a": { color: "[inherit]" },
|
|
515
492
|
});
|
|
516
|
-
/*
|
|
517
|
-
* The agent at work, before its reply: what it is doing beside a spinner, over three lines of
|
|
518
|
-
* the reply to come. The lines stand in for the reply's text, so each is that text's bar height.
|
|
519
|
-
*/
|
|
493
|
+
/* The lines stand in for the reply's text, so each is that text's bar height. */
|
|
520
494
|
const thinking = css({
|
|
521
495
|
display: "grid",
|
|
522
496
|
gap: "layout.contentGap",
|
|
523
|
-
width: "
|
|
497
|
+
width: "full",
|
|
524
498
|
minWidth: "0",
|
|
525
499
|
"& > [data-status]": {
|
|
526
500
|
display: "flex",
|
|
@@ -534,31 +508,30 @@ const thinking = css({
|
|
|
534
508
|
},
|
|
535
509
|
"& > [data-line]": {
|
|
536
510
|
display: "block",
|
|
537
|
-
height: "calc(token(fontSizes.body) * 0.75)",
|
|
511
|
+
height: "[calc(token(fontSizes.body) * 0.75)]",
|
|
538
512
|
borderRadius: "round",
|
|
539
|
-
background: "token(gradients.skeletonFill)",
|
|
513
|
+
background: "[token(gradients.skeletonFill)]",
|
|
540
514
|
},
|
|
541
|
-
"& > [data-line]:nth-of-type(1)": { width: "98%" },
|
|
542
|
-
"& > [data-line]:nth-of-type(2)": { width: "87%" },
|
|
543
|
-
"& > [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%]" },
|
|
544
518
|
});
|
|
545
|
-
/* A reply the agent could not finish: a notice of why, with Try again under it. */
|
|
546
519
|
const failure = css({
|
|
547
520
|
display: "grid",
|
|
548
521
|
justifyItems: "start",
|
|
549
522
|
gap: "layout.contentGap",
|
|
550
|
-
width: "
|
|
523
|
+
width: "full",
|
|
551
524
|
minWidth: "0",
|
|
552
525
|
});
|
|
553
526
|
const failureNotice = css({
|
|
554
527
|
display: "flex",
|
|
555
528
|
gap: "layout.controlGap",
|
|
556
|
-
width: "
|
|
529
|
+
width: "full",
|
|
557
530
|
paddingBlock: "panel.default",
|
|
558
531
|
paddingInline: "control.tabInline",
|
|
559
532
|
borderRadius: "action",
|
|
560
|
-
background: "token(gradients.selected)",
|
|
561
|
-
"&::before": { background: "token(gradients.border.raised)" },
|
|
533
|
+
background: "[token(gradients.selected)]",
|
|
534
|
+
"&::before": { background: "[token(gradients.border.raised)]" },
|
|
562
535
|
"& > svg": { flexShrink: "0", fontSize: "icon.small", color: "accent.red" },
|
|
563
536
|
"& > [data-copy]": { display: "grid", gap: "layout.tightGap", minWidth: "0" },
|
|
564
537
|
"& [data-title]": {
|
|
@@ -569,12 +542,8 @@ const failureNotice = css({
|
|
|
569
542
|
},
|
|
570
543
|
"& [data-description]": { color: "fg.secondary", fontSize: "code", lineHeight: "log" },
|
|
571
544
|
});
|
|
572
|
-
/*
|
|
573
|
-
* The steps the agent took on its way to a reply, folded into one quiet line naming the first and
|
|
574
|
-
* how many more; open, a row for each, its state's icon beside what it did.
|
|
575
|
-
*/
|
|
576
545
|
const stepList = css({
|
|
577
|
-
width: "
|
|
546
|
+
width: "full",
|
|
578
547
|
minWidth: "0",
|
|
579
548
|
color: "fg.subtle",
|
|
580
549
|
fontSize: "code",
|
|
@@ -583,17 +552,17 @@ const stepList = css({
|
|
|
583
552
|
display: "inline-flex",
|
|
584
553
|
alignItems: "center",
|
|
585
554
|
gap: "control.iconGap",
|
|
586
|
-
maxWidth: "
|
|
555
|
+
maxWidth: "full",
|
|
587
556
|
borderRadius: "control",
|
|
588
557
|
cursor: "pointer",
|
|
589
558
|
listStyle: "none",
|
|
590
559
|
"&::-webkit-details-marker": { display: "none" },
|
|
591
560
|
_hover: { color: "fg.primary" },
|
|
592
561
|
_focusVisible: { focusRing: "outer" },
|
|
593
|
-
"& > svg": { flexShrink: "0", transitionProperty: "rotate", transitionDuration: "fast" },
|
|
562
|
+
"& > svg": { flexShrink: "0", transitionProperty: "[rotate]", transitionDuration: "fast" },
|
|
594
563
|
"& > span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
595
564
|
},
|
|
596
|
-
"&[open] > summary > svg": { rotate: "90deg" },
|
|
565
|
+
"&[open] > summary > svg": { rotate: "[90deg]" },
|
|
597
566
|
"& > ol": {
|
|
598
567
|
display: "grid",
|
|
599
568
|
gap: "layout.controlGap",
|
|
@@ -645,7 +614,6 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
645
614
|
}
|
|
646
615
|
: undefined;
|
|
647
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 }));
|
|
648
|
-
// The sheet's send is the bar's small round one; the feed's sits in its pill, 32 round.
|
|
649
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" }) }));
|
|
650
618
|
const waiting = attachments ? _jsx("div", { "data-attachments": "", children: attachments }) : null;
|
|
651
619
|
const waveform = dictation && (_jsx(ChatDictationWaveform, { levels: dictation.levels, listening: dictation.listening }));
|
|
@@ -657,34 +625,27 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
657
625
|
}
|
|
658
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] })] })] }));
|
|
659
627
|
}
|
|
660
|
-
/** A tool for the sheet composer's bar, such as the paperclip: its icon alone. */
|
|
661
628
|
export function ChatComposerTool({ className, ...props }) {
|
|
662
629
|
return (_jsx("button", { type: "button", ...props, className: cx(composerTool, className), "data-ciridae-ui": "" }));
|
|
663
630
|
}
|
|
664
|
-
/**
|
|
665
|
-
* Dictation's waveform: a bar per level, each as tall as its level; flat while `listening` is
|
|
666
|
-
* off, such as while the host turns what it heard into text.
|
|
667
|
-
*/
|
|
631
|
+
/** Flat while `listening` is off, such as while the host turns what it heard into text. */
|
|
668
632
|
export function ChatDictationWaveform({ levels, listening, className, ...props }) {
|
|
669
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: {
|
|
670
634
|
transform: `scaleY(${listening ? Math.max(silentWaveformLevel, level) : silentWaveformLevel})`,
|
|
671
635
|
} }, index))) }));
|
|
672
636
|
}
|
|
673
|
-
/** A message bubble: the user's raised on the right, another author's filled on the left. */
|
|
674
637
|
export function ChatBubble({ side, groupPosition, className, ...props }) {
|
|
675
638
|
return (_jsx("div", { ...props, className: cx(bubble({ side }), className), "data-ciridae-ui": "", "data-edge": side === "user" ? "gradient" : undefined, "data-group-position": groupPosition }));
|
|
676
639
|
}
|
|
677
|
-
/** A centered line between bubbles: a finished tool call, a decision, something the page did. */
|
|
678
640
|
export function ChatSystemLine({ tone, className, ...props }) {
|
|
679
641
|
return (_jsx("div", { ...props, className: cx(systemLine, className), "data-ciridae-ui": "", "data-tone": tone === "error" ? "error" : undefined }));
|
|
680
642
|
}
|
|
681
643
|
export function ChatAction({ className, ...props }) {
|
|
682
644
|
return (_jsx("button", { ...props, className: cx(chatAction, className), "data-ciridae-ui": "", type: props.type ?? "button" }));
|
|
683
645
|
}
|
|
684
|
-
/** A source cited in a reply, as a card of what it says under its name. */
|
|
685
646
|
export function ChatSourceCard({ label, icon, children, meta, tone = "neutral", href, className, }) {
|
|
686
647
|
const content = (_jsxs(_Fragment, { children: [_jsxs("span", { "data-head": "", children: [icon, label] }), _jsx("span", { children: children }), meta && _jsx("span", { "data-meta": "", children: meta })] }));
|
|
687
|
-
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 }));
|
|
688
649
|
}
|
|
689
650
|
/*
|
|
690
651
|
* A conversation in time order. Close messages from one author read as a group: bubbles 4px
|
|
@@ -709,12 +670,14 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
709
670
|
.reverse()
|
|
710
671
|
.find((entry) => entry.kind === "message" && entry.side === "user");
|
|
711
672
|
const latest = useFollowLatest(rootRef, newestUserMessage?.id);
|
|
673
|
+
const replyBodies = useRef(new Map());
|
|
674
|
+
const replyAnnouncement = useFinishedReplyAnnouncement(entries, replyBodies, (entry) => authorOf(entry)?.name);
|
|
712
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) => {
|
|
713
676
|
const previous = entries[index - 1];
|
|
714
677
|
const newDay = dayLabels && (!previous || !isSameDay(previous.at, entry.at));
|
|
715
678
|
const content = renderEntry(entry, index);
|
|
716
679
|
return newDay ? (_jsxs(Fragment, { children: [_jsx(DayHeading, { at: entry.at }), content] }, entry.id)) : (content);
|
|
717
|
-
}), _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 })] }));
|
|
718
681
|
function renderEntry(entry, index) {
|
|
719
682
|
if (entry.kind === "line") {
|
|
720
683
|
return (_jsx(ChatSystemLine, { tone: entry.tone, children: entry.text }, entry.id));
|
|
@@ -730,18 +693,21 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
730
693
|
: null;
|
|
731
694
|
const sourceLinks = message?.sources?.filter((source) => source.quote === undefined) ?? [];
|
|
732
695
|
const sourceCards = message?.sources?.filter((source) => source.quote !== undefined) ?? [];
|
|
733
|
-
// A plain reply drops the assistant's author and time; the user's messages keep their time.
|
|
734
696
|
const footer = closesGroup &&
|
|
735
697
|
entry.kind !== "typing" &&
|
|
736
698
|
entry.kind !== "steps" &&
|
|
737
699
|
(!plain || side === "user" || message?.sending);
|
|
738
|
-
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));
|
|
739
706
|
}
|
|
740
707
|
}
|
|
741
|
-
/** A day's heading in a thread that runs over days: Today, Yesterday or the weekday, then the date. */
|
|
742
708
|
function DayHeading({ at }) {
|
|
743
709
|
const { labels, formatDate } = useCiridaeLocale();
|
|
744
|
-
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)
|
|
745
711
|
? labels.dates.today
|
|
746
712
|
: isYesterday(at)
|
|
747
713
|
? labels.dates.yesterday
|
|
@@ -769,14 +735,15 @@ function AttachmentTile({ attachment, resolveUrl, onOpen, }) {
|
|
|
769
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 })] }));
|
|
770
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 }));
|
|
771
737
|
}
|
|
772
|
-
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, plain, }) {
|
|
738
|
+
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, plain, bodyRef, }) {
|
|
773
739
|
switch (entry.kind) {
|
|
774
740
|
case "message": {
|
|
775
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;
|
|
776
743
|
if (plain && side === "assistant") {
|
|
777
|
-
return (_jsxs("div", { className: plainReply, "aria-busy":
|
|
744
|
+
return (_jsxs("div", { ref: bodyRef, className: plainReply, "aria-busy": busy, children: [entry.body, attachmentNames] }));
|
|
778
745
|
}
|
|
779
|
-
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] }));
|
|
780
747
|
}
|
|
781
748
|
case "typing":
|
|
782
749
|
return _jsx(AgentThinking, { label: entry.label });
|
|
@@ -788,27 +755,77 @@ function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachme
|
|
|
788
755
|
return renderCustom?.(entry.data);
|
|
789
756
|
}
|
|
790
757
|
}
|
|
791
|
-
/** The agent at work: what it is doing beside a spinner, over lines standing in for its reply. */
|
|
792
758
|
function AgentThinking({ label }) {
|
|
793
759
|
const { labels } = useCiridaeLocale();
|
|
794
|
-
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": "" })] }));
|
|
795
761
|
}
|
|
796
|
-
/** The steps behind a reply, folded into a line naming the first and how many more. */
|
|
797
762
|
function AgentSteps({ steps }) {
|
|
763
|
+
const { labels } = useCiridaeLocale();
|
|
798
764
|
const [first] = steps;
|
|
799
765
|
if (!first)
|
|
800
766
|
return null;
|
|
801
|
-
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))) })] }));
|
|
802
768
|
}
|
|
803
|
-
/** A reply that did not finish: a notice of why, and Try again under it. */
|
|
804
769
|
function FailureNotice({ title, message, onRetry, }) {
|
|
805
770
|
const { labels } = useCiridaeLocale();
|
|
806
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 })] }));
|
|
807
772
|
}
|
|
808
|
-
/** The side an entry sits on: the agent's work, its steps and failures are the assistant's. */
|
|
809
773
|
function entrySide(entry) {
|
|
810
774
|
return entry.kind === "message" || entry.kind === "custom" ? entry.side : "assistant";
|
|
811
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
|
+
}
|
|
812
829
|
/* A message's time, which turns into its clock time while hovered. */
|
|
813
830
|
const messageTime = css({
|
|
814
831
|
"& > [data-exact]": { display: "none" },
|
|
@@ -894,12 +911,16 @@ function useFollowLatest(rootRef, sentMessageId) {
|
|
|
894
911
|
scroller.scrollTop = scroller.scrollHeight;
|
|
895
912
|
};
|
|
896
913
|
// Content that grew since the last look keeps a reader at the foot there, whichever of its
|
|
897
|
-
// 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.
|
|
898
916
|
let lastHeight = scroller.scrollHeight;
|
|
917
|
+
let lastViewHeight = scroller.clientHeight;
|
|
899
918
|
const follow = () => {
|
|
900
919
|
const grew = scroller.scrollHeight !== lastHeight;
|
|
920
|
+
const viewResized = scroller.clientHeight !== lastViewHeight;
|
|
901
921
|
lastHeight = scroller.scrollHeight;
|
|
902
|
-
|
|
922
|
+
lastViewHeight = scroller.clientHeight;
|
|
923
|
+
if ((grew || viewResized) && atFootRef.current)
|
|
903
924
|
return toFoot();
|
|
904
925
|
const distance = scroller.scrollHeight - scroller.scrollTop - scroller.clientHeight;
|
|
905
926
|
atFootRef.current = distance < followDistancePx;
|