@ciridae/ui 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/conversation-source.d.ts +15 -1
- package/dist/chat/describe-tool-call.d.ts +3 -3
- package/dist/chat/describe-tool-call.js +3 -3
- package/dist/chat/message-text.js +2 -2
- package/dist/ciridae.css +16 -4
- package/dist/components/activity-feed.d.ts +1 -0
- package/dist/components/activity-feed.js +1 -0
- package/dist/components/app-frame.d.ts +13 -1
- package/dist/components/app-frame.js +45 -3
- package/dist/components/app-navigation.d.ts +28 -1
- package/dist/components/app-navigation.js +105 -14
- package/dist/components/badge.d.ts +29 -4
- package/dist/components/badge.js +134 -15
- package/dist/components/bulk-action-bar.js +3 -3
- package/dist/components/button.d.ts +10 -3
- package/dist/components/button.js +77 -44
- package/dist/components/calendar.js +44 -17
- package/dist/components/centered-page.js +2 -2
- package/dist/components/chart.d.ts +1 -1
- package/dist/components/chart.js +16 -11
- package/dist/components/chat-client-source.js +47 -6
- package/dist/components/chat-sheet-controller.d.ts +1 -0
- package/dist/components/chat-sheet-controller.js +1 -0
- package/dist/components/chat-sheet.d.ts +10 -2
- package/dist/components/chat-sheet.js +76 -39
- package/dist/components/chat.d.ts +90 -14
- package/dist/components/chat.js +402 -101
- package/dist/components/checkbox-item.d.ts +5 -2
- package/dist/components/checkbox-item.js +9 -5
- package/dist/components/chip.d.ts +5 -0
- package/dist/components/chip.js +27 -1
- package/dist/components/column-settings.js +1 -1
- package/dist/components/command-menu.d.ts +5 -1
- package/dist/components/command-menu.js +98 -32
- package/dist/components/command-palette.d.ts +6 -2
- package/dist/components/command-palette.js +20 -10
- package/dist/components/data-display.d.ts +35 -4
- package/dist/components/data-display.js +138 -19
- package/dist/components/data-grid.d.ts +14 -2
- package/dist/components/data-grid.js +61 -26
- package/dist/components/date-range-picker.js +9 -20
- package/dist/components/drawer.d.ts +5 -0
- package/dist/components/drawer.js +91 -54
- package/dist/components/editable-field.d.ts +12 -3
- package/dist/components/editable-field.js +57 -27
- package/dist/components/email-input.js +6 -5
- package/dist/components/environment-frame.d.ts +1 -1
- package/dist/components/environment-frame.js +37 -6
- package/dist/components/filter-bar.js +39 -28
- package/dist/components/form-controls.d.ts +9 -0
- package/dist/components/form-controls.js +46 -22
- package/dist/components/form-layout.d.ts +7 -3
- package/dist/components/form-layout.js +68 -23
- package/dist/components/glass-menu.d.ts +3 -0
- package/dist/components/glass-menu.js +63 -0
- package/dist/components/gradient-edge.d.ts +9 -0
- package/dist/components/gradient-edge.js +31 -0
- package/dist/components/input.d.ts +4 -2
- package/dist/components/input.js +26 -15
- package/dist/components/keycap.d.ts +9 -5
- package/dist/components/keycap.js +57 -19
- package/dist/components/lightbox.js +2 -1
- package/dist/components/locale.d.ts +10 -0
- package/dist/components/locale.js +18 -2
- package/dist/components/log-entry.d.ts +40 -0
- package/dist/components/log-entry.js +146 -0
- package/dist/components/mention-input.js +4 -3
- package/dist/components/menu.d.ts +6 -2
- package/dist/components/menu.js +34 -37
- package/dist/components/meter.d.ts +9 -1
- package/dist/components/meter.js +64 -6
- package/dist/components/navigation.d.ts +21 -4
- package/dist/components/navigation.js +84 -19
- package/dist/components/notification-tray.js +5 -4
- package/dist/components/overlays.d.ts +3 -1
- package/dist/components/overlays.js +69 -40
- package/dist/components/page-frame.js +1 -1
- package/dist/components/panel.d.ts +1 -1
- package/dist/components/panel.js +18 -12
- package/dist/components/radio-card.js +10 -9
- package/dist/components/record.d.ts +25 -1
- package/dist/components/record.js +147 -18
- package/dist/components/segmented-bar.d.ts +3 -1
- package/dist/components/segmented-bar.js +16 -6
- package/dist/components/select.d.ts +5 -2
- package/dist/components/select.js +103 -127
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +2 -2
- package/dist/components/shell.js +43 -45
- package/dist/components/sidebar.d.ts +7 -2
- package/dist/components/sidebar.js +55 -27
- package/dist/components/single-line-text.d.ts +2 -0
- package/dist/components/single-line-text.js +16 -0
- package/dist/components/site-banner.d.ts +5 -2
- package/dist/components/site-banner.js +17 -6
- package/dist/components/skeleton.d.ts +7 -6
- package/dist/components/skeleton.js +28 -16
- package/dist/components/skeletons.d.ts +12 -0
- package/dist/components/skeletons.js +71 -8
- package/dist/components/stages.d.ts +78 -6
- package/dist/components/stages.js +512 -156
- package/dist/components/stat-card.d.ts +10 -2
- package/dist/components/stat-card.js +92 -37
- package/dist/components/status-banner.js +1 -1
- package/dist/components/tabs.js +26 -13
- package/dist/components/theme-toggle.d.ts +4 -0
- package/dist/components/theme-toggle.js +67 -0
- package/dist/components/toast.d.ts +9 -2
- package/dist/components/toast.js +78 -29
- package/dist/preset.js +289 -33
- package/dist/styled-system/styles.css +3115 -1044
- package/dist/styled-system/tokens/index.mjs +598 -22
- package/dist/styled-system/tokens/tokens.d.ts +11 -11
- package/dist/styled-system/types/prop-type.d.ts +2 -2
- package/dist/styles.css +16 -4
- package/dist/theme/color.d.ts +3 -0
- package/dist/theme/color.js +5 -0
- package/dist/theme/derive.d.ts +9 -2
- package/dist/theme/derive.js +72 -12
- package/dist/theme/palettes.d.ts +49 -6
- package/dist/theme/palettes.js +84 -15
- package/dist/theme/tokens.d.ts +3 -3
- package/dist/theme/tokens.js +107 -32
- package/package.json +9 -1
package/dist/components/chat.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-runtime";
|
|
2
|
-
import { ArrowClockwiseIcon, ArrowDownIcon, ArrowUpIcon } from "@phosphor-icons/react";
|
|
2
|
+
import { ArrowClockwiseIcon, ArrowDownIcon, ArrowUpIcon, CaretRightIcon, LightbulbIcon, WarningCircleIcon, WrenchIcon, } from "@phosphor-icons/react";
|
|
3
3
|
import { isSameDay, isToday, isYesterday } from "date-fns";
|
|
4
4
|
import { Fragment, useEffect, useRef, useState, useSyncExternalStore, } from "react";
|
|
5
5
|
import { css, cva, cx } from "#primitives/css";
|
|
@@ -7,20 +7,20 @@ import { listMessageGroupPositions, } from "../chat/message-group-position.js";
|
|
|
7
7
|
import { Button, IconButton } from "./button.js";
|
|
8
8
|
import { Avatar } from "./data-display.js";
|
|
9
9
|
import { fileSquare, fileTypeIcon } from "./dropzone.js";
|
|
10
|
-
import { icon, Spinner } from "./icon.js";
|
|
10
|
+
import { directional, icon, Spinner } from "./icon.js";
|
|
11
11
|
import { Lightbox } from "./lightbox.js";
|
|
12
12
|
import { useCiridaeLocale } from "./locale.js";
|
|
13
|
+
import { LogGroupHeader } from "./log-entry.js";
|
|
13
14
|
import { MentionInput } from "./mention-input.js";
|
|
14
15
|
import { useResolvedFileUrl } from "./resolved-file-url.js";
|
|
15
16
|
export { MessageText, splitSources, } from "../chat/message-text.js";
|
|
16
17
|
/*
|
|
17
|
-
*
|
|
18
|
-
* composer at the foot of the agent sheet
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* line there, and Cmd/Ctrl+Enter sends.
|
|
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
24
|
*/
|
|
25
25
|
const pageComposer = css({
|
|
26
26
|
display: "flex",
|
|
@@ -47,47 +47,136 @@ const pageInput = css({
|
|
|
47
47
|
_phone: { minHeight: "chat.inputMobile", padding: "panel.comfortable" },
|
|
48
48
|
});
|
|
49
49
|
const sheetComposer = css({
|
|
50
|
+
display: "flex",
|
|
51
|
+
flexDirection: "column",
|
|
52
|
+
gap: "layout.contentGap",
|
|
53
|
+
width: "100%",
|
|
54
|
+
minWidth: "0",
|
|
55
|
+
padding: "panel.default",
|
|
56
|
+
border: "1px solid token(colors.border.control)",
|
|
57
|
+
borderRadius: "field",
|
|
58
|
+
background: "bg.field",
|
|
59
|
+
_focusWithin: { borderColor: "border.focus", boxShadow: "focus" },
|
|
60
|
+
});
|
|
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.
|
|
63
|
+
const sheetInput = css({
|
|
64
|
+
maxHeight: "6lh",
|
|
65
|
+
marginBlock: "calc((1em - 1lh) / 2)",
|
|
66
|
+
fontSize: "body",
|
|
67
|
+
fontWeight: "medium",
|
|
68
|
+
lineHeight: "body",
|
|
69
|
+
});
|
|
70
|
+
// Under the text: the tools on the left, and on the right the actions and the send, a 26px
|
|
71
|
+
// round, smaller than any other icon button.
|
|
72
|
+
const sheetBar = css({
|
|
73
|
+
display: "flex",
|
|
74
|
+
alignItems: "center",
|
|
75
|
+
justifyContent: "space-between",
|
|
76
|
+
gap: "layout.controlGap",
|
|
77
|
+
minWidth: "0",
|
|
78
|
+
"& > [data-tools], & > [data-send]": {
|
|
79
|
+
display: "flex",
|
|
80
|
+
flexShrink: "0",
|
|
81
|
+
alignItems: "center",
|
|
82
|
+
gap: "layout.controlGap",
|
|
83
|
+
},
|
|
84
|
+
"& > [data-send] > [data-send-button]": { width: "control.send", height: "control.send" },
|
|
85
|
+
});
|
|
86
|
+
const feedComposer = css({
|
|
50
87
|
display: "flex",
|
|
51
88
|
alignItems: "flex-end",
|
|
52
89
|
gap: "layout.tightGap",
|
|
53
90
|
width: "100%",
|
|
54
91
|
minWidth: "0",
|
|
55
92
|
});
|
|
56
|
-
// The tools
|
|
57
|
-
const
|
|
93
|
+
// The tools beside the feed's pill: as tall as its send, so they stay level with its last line.
|
|
94
|
+
const feedTools = css({
|
|
58
95
|
display: "flex",
|
|
59
96
|
flexShrink: "0",
|
|
60
97
|
alignItems: "center",
|
|
61
98
|
gap: "layout.tightGap",
|
|
62
|
-
height: "control.compact",
|
|
99
|
+
height: "calc(token(sizes.control.compact) + 2 * token(spacing.control.compactBlock))",
|
|
63
100
|
});
|
|
64
|
-
const
|
|
65
|
-
|
|
101
|
+
const feedColumn = css({ display: "grid", flex: "1", minWidth: "0", gap: "layout.tightGap" });
|
|
102
|
+
// The pill's 1px stroke sits inside its padding, so a one-line pill is the kit's height: the send
|
|
103
|
+
// plus its padding above and below.
|
|
104
|
+
const feedBox = css({
|
|
66
105
|
display: "flex",
|
|
67
106
|
alignItems: "flex-end",
|
|
107
|
+
gap: "layout.controlGap",
|
|
68
108
|
minWidth: "0",
|
|
109
|
+
paddingBlock: "calc(token(spacing.control.compactBlock) - token(sizes.border.hairline))",
|
|
110
|
+
paddingInlineStart: "control.compactInline",
|
|
111
|
+
paddingInlineEnd: "control.compactBlock",
|
|
69
112
|
border: "1px solid token(colors.border.control)",
|
|
70
113
|
// Half the one-line height: a pill at one line, a soft card past it.
|
|
71
|
-
borderRadius: "calc(token(sizes.control.compact) / 2)",
|
|
72
|
-
background: "
|
|
114
|
+
borderRadius: "calc((token(sizes.control.compact) + 2 * token(spacing.control.compactBlock)) / 2)",
|
|
115
|
+
background: "token(gradients.card)",
|
|
73
116
|
_focusWithin: { borderColor: "border.focus", boxShadow: "focus" },
|
|
74
117
|
"& > [data-actions]": {
|
|
75
118
|
display: "flex",
|
|
76
119
|
flexShrink: "0",
|
|
77
120
|
alignItems: "center",
|
|
78
|
-
height: "
|
|
79
|
-
|
|
121
|
+
height: "control.compact",
|
|
122
|
+
},
|
|
123
|
+
"& > [data-actions] > [data-send-button]": {
|
|
124
|
+
width: "control.compact",
|
|
125
|
+
height: "control.compact",
|
|
80
126
|
},
|
|
81
127
|
});
|
|
82
|
-
// Each line keeps the one-line
|
|
83
|
-
|
|
128
|
+
// Each line keeps the one-line pill's rhythm, so the first wrap only adds a line. Wrapped lines
|
|
129
|
+
// take the body's spacing, as the sheet's do.
|
|
130
|
+
const feedInput = css({
|
|
84
131
|
flex: "1",
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
lineHeight: "
|
|
132
|
+
paddingBlock: "calc((token(sizes.control.compact) - 1lh) / 2)",
|
|
133
|
+
maxHeight: "calc(5lh + token(sizes.control.compact))",
|
|
134
|
+
fontSize: "body",
|
|
135
|
+
fontWeight: "medium",
|
|
136
|
+
lineHeight: "body",
|
|
137
|
+
});
|
|
138
|
+
/* A tool in the sheet composer's bar: a bare icon, with no face of its own. */
|
|
139
|
+
const composerTool = css({
|
|
140
|
+
display: "inline-flex",
|
|
141
|
+
padding: "none",
|
|
142
|
+
border: "0",
|
|
143
|
+
borderRadius: "control",
|
|
144
|
+
background: "transparent",
|
|
145
|
+
color: "fg.subtle",
|
|
146
|
+
cursor: "pointer",
|
|
147
|
+
_hover: { color: "fg.primary" },
|
|
148
|
+
_focusVisible: { focusRing: "outer" },
|
|
149
|
+
_disabled: { opacity: "disabled", cursor: "not-allowed" },
|
|
150
|
+
"&[aria-pressed=true]": { color: "fg.primary" },
|
|
90
151
|
});
|
|
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
|
+
*/
|
|
157
|
+
const dictationWaveform = css({
|
|
158
|
+
display: "flex",
|
|
159
|
+
flex: "1",
|
|
160
|
+
alignItems: "center",
|
|
161
|
+
justifyContent: "space-between",
|
|
162
|
+
gap: "control.trackInset",
|
|
163
|
+
minWidth: "0",
|
|
164
|
+
height: "control.shortcut",
|
|
165
|
+
overflow: "hidden",
|
|
166
|
+
"& > span": {
|
|
167
|
+
flex: "1",
|
|
168
|
+
minWidth: "1px",
|
|
169
|
+
// A spacing token, so it is read through token(); a sizes property would print its name.
|
|
170
|
+
maxWidth: "token(spacing.layout.tightGap)",
|
|
171
|
+
height: "100%",
|
|
172
|
+
borderRadius: "round",
|
|
173
|
+
background: "fg.subtle",
|
|
174
|
+
transformOrigin: "center",
|
|
175
|
+
},
|
|
176
|
+
"&[data-listening] > span": { background: "fg.secondary" },
|
|
177
|
+
});
|
|
178
|
+
/** How short a silent bar of the dictation waveform is, as a share of its full height. */
|
|
179
|
+
const silentWaveformLevel = 0.12;
|
|
91
180
|
const composerFooter = css({
|
|
92
181
|
display: "flex",
|
|
93
182
|
alignItems: "center",
|
|
@@ -133,39 +222,48 @@ const chatAction = css({
|
|
|
133
222
|
_hover: { color: "fg.primary", borderColor: "fg.muted" },
|
|
134
223
|
});
|
|
135
224
|
/*
|
|
136
|
-
* A message bubble
|
|
137
|
-
*
|
|
138
|
-
*
|
|
225
|
+
* A message bubble: the user's on the right in the selected neutral face, under its gradient edge;
|
|
226
|
+
* another author's on the left in the neutral fill. Where a bubble joins the one above or below
|
|
227
|
+
* it on its side, that corner is small, so a run of messages reads as one stack.
|
|
139
228
|
*/
|
|
140
229
|
const bubble = cva({
|
|
141
230
|
base: {
|
|
142
231
|
position: "relative",
|
|
143
232
|
maxWidth: "chat.bubble",
|
|
144
|
-
paddingBlock: "
|
|
145
|
-
paddingInline: "control.
|
|
233
|
+
paddingBlock: "field.block",
|
|
234
|
+
paddingInline: "control.tabInline",
|
|
146
235
|
borderRadius: "field",
|
|
147
|
-
|
|
148
|
-
|
|
236
|
+
color: "fg.primary",
|
|
237
|
+
fontSize: "body",
|
|
238
|
+
lineHeight: "compact",
|
|
149
239
|
overflowWrap: "anywhere",
|
|
150
240
|
},
|
|
151
241
|
variants: {
|
|
152
242
|
side: {
|
|
153
243
|
user: {
|
|
154
244
|
alignSelf: "flex-end",
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
245
|
+
// 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, so the padding gives back the extra leading and
|
|
247
|
+
// a one-line bubble stays the kit's height.
|
|
248
|
+
paddingBlock: "calc(token(spacing.field.block) + token(sizes.border.hairline) - (1lh - 1em) / 2)",
|
|
249
|
+
paddingInline: "calc(token(spacing.control.tabInline) + token(sizes.border.hairline))",
|
|
250
|
+
lineHeight: "body",
|
|
251
|
+
background: "token(gradients.selected)",
|
|
159
252
|
whiteSpace: "pre-wrap",
|
|
253
|
+
"&::before": { background: "token(gradients.border.selected)" },
|
|
254
|
+
"&[data-group-position=first], &[data-group-position=middle]": {
|
|
255
|
+
borderEndEndRadius: "keycap",
|
|
256
|
+
},
|
|
160
257
|
"&[data-group-position=middle], &[data-group-position=last]": {
|
|
161
258
|
borderStartEndRadius: "keycap",
|
|
162
259
|
},
|
|
163
260
|
},
|
|
164
261
|
assistant: {
|
|
165
262
|
alignSelf: "flex-start",
|
|
166
|
-
borderEndStartRadius: "keycap",
|
|
167
263
|
background: "bg.fill",
|
|
168
|
-
|
|
264
|
+
"&[data-group-position=first], &[data-group-position=middle]": {
|
|
265
|
+
borderEndStartRadius: "keycap",
|
|
266
|
+
},
|
|
169
267
|
"&[data-group-position=middle], &[data-group-position=last]": {
|
|
170
268
|
borderStartStartRadius: "keycap",
|
|
171
269
|
},
|
|
@@ -173,6 +271,17 @@ const bubble = cva({
|
|
|
173
271
|
},
|
|
174
272
|
},
|
|
175
273
|
});
|
|
274
|
+
/* An answer in a thread of `plain` replies: text on the sheet itself, the thread's full width. */
|
|
275
|
+
const plainReply = css({
|
|
276
|
+
display: "grid",
|
|
277
|
+
gap: "layout.contentGap",
|
|
278
|
+
width: "100%",
|
|
279
|
+
minWidth: "0",
|
|
280
|
+
color: "fg.primary",
|
|
281
|
+
fontSize: "body",
|
|
282
|
+
lineHeight: "body",
|
|
283
|
+
overflowWrap: "anywhere",
|
|
284
|
+
});
|
|
176
285
|
/** A line about the conversation rather than in it, centered between bubbles. */
|
|
177
286
|
const systemLine = css({
|
|
178
287
|
alignSelf: "center",
|
|
@@ -184,16 +293,18 @@ const systemLine = css({
|
|
|
184
293
|
overflowWrap: "anywhere",
|
|
185
294
|
"&[data-tone=error]": { color: "accent.red" },
|
|
186
295
|
});
|
|
187
|
-
const dayLabel = css({ marginBlock: "layout.controlGap", fontWeight: "medium" });
|
|
188
296
|
/*
|
|
189
|
-
* The thread: bubbles 4px apart
|
|
190
|
-
*
|
|
297
|
+
* The thread: bubbles 4px apart and groups 12px apart, lines centered between them. Compact
|
|
298
|
+
* keeps 8px above and below and between groups. Plain replies sit 16px in from the sheet's
|
|
299
|
+
* edges, an answer's parts 12px apart and one turn 16px from the next.
|
|
191
300
|
*/
|
|
192
301
|
const thread = css({
|
|
193
302
|
display: "flex",
|
|
194
303
|
flexDirection: "column",
|
|
195
304
|
gap: "layout.tightGap",
|
|
196
305
|
padding: "panel.default",
|
|
306
|
+
// Every line in the thread is set in the medium weight: bubbles, replies, cited cards and meta.
|
|
307
|
+
fontWeight: "medium",
|
|
197
308
|
// The thread follows its newest entry itself: the browser's scroll anchoring would hold the
|
|
198
309
|
// view on what was there as a reply lands and pull it off the foot.
|
|
199
310
|
overflowAnchor: "none",
|
|
@@ -203,6 +314,15 @@ const thread = css({
|
|
|
203
314
|
marginTop: "layout.tightGap",
|
|
204
315
|
},
|
|
205
316
|
},
|
|
317
|
+
"&[data-reply-style=plain]": {
|
|
318
|
+
padding: "panel.comfortable",
|
|
319
|
+
"& > [data-group-position=middle], & > [data-group-position=last]": {
|
|
320
|
+
marginTop: "layout.controlGap",
|
|
321
|
+
},
|
|
322
|
+
"& > :is([data-group-position=single], [data-group-position=first]):not(:first-child)": {
|
|
323
|
+
marginTop: "layout.contentGap",
|
|
324
|
+
},
|
|
325
|
+
},
|
|
206
326
|
});
|
|
207
327
|
/*
|
|
208
328
|
* Under the thread, held at the foot of the scrolling area while the reader is away from the
|
|
@@ -222,7 +342,7 @@ const latestSlot = css({
|
|
|
222
342
|
});
|
|
223
343
|
/** How close to the bottom the reader must be for new content to keep the thread at its foot. */
|
|
224
344
|
const followDistancePx = 96;
|
|
225
|
-
/* A reply's sources under
|
|
345
|
+
/* A reply's sources under it: small links in a row, and the ones with a quote as cards. */
|
|
226
346
|
const sourceRow = css({
|
|
227
347
|
display: "flex",
|
|
228
348
|
flexWrap: "wrap",
|
|
@@ -230,14 +350,72 @@ const sourceRow = css({
|
|
|
230
350
|
columnGap: "layout.controlGap",
|
|
231
351
|
rowGap: "control.trackInset",
|
|
232
352
|
paddingInline: "layout.tightGap",
|
|
233
|
-
color: "fg.
|
|
353
|
+
color: "fg.subtle",
|
|
234
354
|
fontSize: "code",
|
|
235
355
|
lineHeight: "compact",
|
|
236
|
-
"[data-
|
|
356
|
+
"[data-avatar] > &": {
|
|
237
357
|
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
238
358
|
},
|
|
359
|
+
"[data-reply-style=plain] > * > &": { paddingInline: "none" },
|
|
239
360
|
"& a": { color: "fg.secondary", textDecoration: "none", _hover: { color: "fg.primary" } },
|
|
240
361
|
});
|
|
362
|
+
const sourceCardList = css({
|
|
363
|
+
display: "grid",
|
|
364
|
+
gap: "layout.contentGap",
|
|
365
|
+
"[data-avatar] > &": {
|
|
366
|
+
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
367
|
+
},
|
|
368
|
+
});
|
|
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
|
+
const sourceCard = css({
|
|
374
|
+
display: "grid",
|
|
375
|
+
gap: "control.iconGap",
|
|
376
|
+
padding: "panel.default",
|
|
377
|
+
border: "1px solid token(colors.border.divider)",
|
|
378
|
+
borderRadius: "action",
|
|
379
|
+
background: "token(gradients.card)",
|
|
380
|
+
color: "fg.primary",
|
|
381
|
+
fontSize: "code",
|
|
382
|
+
fontWeight: "medium",
|
|
383
|
+
lineHeight: "log",
|
|
384
|
+
textDecoration: "none",
|
|
385
|
+
"&[data-tone=yellow]": { background: "bg.warningTint" },
|
|
386
|
+
"& > [data-head]": {
|
|
387
|
+
display: "flex",
|
|
388
|
+
alignItems: "center",
|
|
389
|
+
gap: "control.iconGap",
|
|
390
|
+
minWidth: "0",
|
|
391
|
+
color: "fg.subtle",
|
|
392
|
+
lineHeight: "tight",
|
|
393
|
+
"& > svg": { flexShrink: "0", fontSize: "icon.small" },
|
|
394
|
+
},
|
|
395
|
+
"&[data-tone=yellow] > [data-head]": { color: "accent.yellowIcon" },
|
|
396
|
+
"& > [data-meta]": { color: "fg.subtle", lineHeight: "tight" },
|
|
397
|
+
"&:is(a)": { _hover: { borderColor: "border.selected" }, _focusVisible: { focusRing: "outer" } },
|
|
398
|
+
});
|
|
399
|
+
/* Buttons under a reply for what it suggests doing, always shown. */
|
|
400
|
+
const suggestedActionRow = css({
|
|
401
|
+
display: "flex",
|
|
402
|
+
flexWrap: "wrap",
|
|
403
|
+
alignItems: "center",
|
|
404
|
+
gap: "layout.tightGap",
|
|
405
|
+
"[data-avatar] > &": {
|
|
406
|
+
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
407
|
+
},
|
|
408
|
+
});
|
|
409
|
+
/* A reply's closing line, such as what it read, quieter than the reply. */
|
|
410
|
+
const footnote = css({
|
|
411
|
+
margin: "none",
|
|
412
|
+
color: "fg.subtle",
|
|
413
|
+
fontSize: "code",
|
|
414
|
+
lineHeight: "log",
|
|
415
|
+
"[data-avatar] > &": {
|
|
416
|
+
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
417
|
+
},
|
|
418
|
+
});
|
|
241
419
|
/* Files sent with a message, as tiles over its bubble: image thumbnails, or a file's icon and name. */
|
|
242
420
|
const attachmentTiles = css({
|
|
243
421
|
display: "flex",
|
|
@@ -245,7 +423,7 @@ const attachmentTiles = css({
|
|
|
245
423
|
gap: "layout.tightGap",
|
|
246
424
|
maxWidth: "chat.bubble",
|
|
247
425
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
248
|
-
"[data-
|
|
426
|
+
"[data-avatar] > &": {
|
|
249
427
|
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
250
428
|
},
|
|
251
429
|
});
|
|
@@ -287,6 +465,8 @@ const entryBlock = css({
|
|
|
287
465
|
"&[data-group-position=single], &[data-group-position=first]": {
|
|
288
466
|
marginTop: "layout.controlGap",
|
|
289
467
|
},
|
|
468
|
+
// A plain reply's parts, such as its cards and buttons under its text, sit 12px apart.
|
|
469
|
+
"&[data-side=assistant]": { "[data-reply-style=plain] > &": { gap: "layout.contentGap" } },
|
|
290
470
|
// A message's own controls, such as Copy, show while it is hovered or focused.
|
|
291
471
|
"& [data-entry-actions]": {
|
|
292
472
|
display: "inline-flex",
|
|
@@ -307,6 +487,8 @@ const entryRow = css({
|
|
|
307
487
|
width: "100%",
|
|
308
488
|
minWidth: "0",
|
|
309
489
|
"[data-side=user] > &": { justifyContent: "flex-end" },
|
|
490
|
+
// A plain reply's Copy sits at the top of its text, which may run long.
|
|
491
|
+
"[data-reply-style=plain] > [data-side=assistant] > &": { alignItems: "flex-start" },
|
|
310
492
|
});
|
|
311
493
|
const avatarSlot = css({ flexShrink: "0", width: "avatar.small" });
|
|
312
494
|
const entryFooter = css({
|
|
@@ -314,11 +496,11 @@ const entryFooter = css({
|
|
|
314
496
|
alignItems: "center",
|
|
315
497
|
gap: "control.iconGap",
|
|
316
498
|
paddingInline: "layout.tightGap",
|
|
317
|
-
color: "fg.
|
|
499
|
+
color: "fg.subtle",
|
|
318
500
|
fontSize: "code",
|
|
319
501
|
lineHeight: "compact",
|
|
320
502
|
fontVariantNumeric: "tabular-nums",
|
|
321
|
-
"[data-
|
|
503
|
+
"[data-avatar] > &": {
|
|
322
504
|
marginInlineStart: "calc(token(sizes.avatar.small) + token(spacing.control.iconGap))",
|
|
323
505
|
},
|
|
324
506
|
});
|
|
@@ -331,37 +513,107 @@ const attachmentList = css({
|
|
|
331
513
|
fontSize: "code",
|
|
332
514
|
"& a": { color: "inherit" },
|
|
333
515
|
});
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
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
|
+
*/
|
|
520
|
+
const thinking = css({
|
|
521
|
+
display: "grid",
|
|
522
|
+
gap: "layout.contentGap",
|
|
523
|
+
width: "100%",
|
|
524
|
+
minWidth: "0",
|
|
525
|
+
"& > [data-status]": {
|
|
526
|
+
display: "flex",
|
|
527
|
+
alignItems: "center",
|
|
528
|
+
gap: "layout.controlGap",
|
|
529
|
+
minWidth: "0",
|
|
530
|
+
color: "fg.secondary",
|
|
531
|
+
fontSize: "code",
|
|
532
|
+
lineHeight: "tight",
|
|
533
|
+
"& > span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
534
|
+
},
|
|
535
|
+
"& > [data-line]": {
|
|
536
|
+
display: "block",
|
|
537
|
+
height: "calc(token(fontSizes.body) * 0.75)",
|
|
538
|
+
borderRadius: "round",
|
|
539
|
+
background: "token(gradients.skeletonFill)",
|
|
540
|
+
},
|
|
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%" },
|
|
544
|
+
});
|
|
545
|
+
/* A reply the agent could not finish: a notice of why, with Try again under it. */
|
|
546
|
+
const failure = css({
|
|
547
|
+
display: "grid",
|
|
548
|
+
justifyItems: "start",
|
|
549
|
+
gap: "layout.contentGap",
|
|
550
|
+
width: "100%",
|
|
551
|
+
minWidth: "0",
|
|
339
552
|
});
|
|
340
|
-
const
|
|
553
|
+
const failureNotice = css({
|
|
341
554
|
display: "flex",
|
|
342
|
-
flexWrap: "wrap",
|
|
343
|
-
alignItems: "center",
|
|
344
555
|
gap: "layout.controlGap",
|
|
345
|
-
|
|
346
|
-
|
|
556
|
+
width: "100%",
|
|
557
|
+
paddingBlock: "panel.default",
|
|
558
|
+
paddingInline: "control.tabInline",
|
|
559
|
+
borderRadius: "action",
|
|
560
|
+
background: "token(gradients.selected)",
|
|
561
|
+
"&::before": { background: "token(gradients.border.raised)" },
|
|
562
|
+
"& > svg": { flexShrink: "0", fontSize: "icon.small", color: "accent.red" },
|
|
563
|
+
"& > [data-copy]": { display: "grid", gap: "layout.tightGap", minWidth: "0" },
|
|
564
|
+
"& [data-title]": {
|
|
565
|
+
color: "accent.red",
|
|
566
|
+
fontSize: "body",
|
|
567
|
+
fontWeight: "medium",
|
|
568
|
+
lineHeight: "tight",
|
|
569
|
+
},
|
|
570
|
+
"& [data-description]": { color: "fg.secondary", fontSize: "code", lineHeight: "log" },
|
|
347
571
|
});
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
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
|
+
const stepList = css({
|
|
577
|
+
width: "100%",
|
|
578
|
+
minWidth: "0",
|
|
579
|
+
color: "fg.subtle",
|
|
580
|
+
fontSize: "code",
|
|
581
|
+
lineHeight: "tight",
|
|
582
|
+
"& > summary": {
|
|
583
|
+
display: "inline-flex",
|
|
584
|
+
alignItems: "center",
|
|
585
|
+
gap: "control.iconGap",
|
|
586
|
+
maxWidth: "100%",
|
|
587
|
+
borderRadius: "control",
|
|
588
|
+
cursor: "pointer",
|
|
589
|
+
listStyle: "none",
|
|
590
|
+
"&::-webkit-details-marker": { display: "none" },
|
|
591
|
+
_hover: { color: "fg.primary" },
|
|
592
|
+
_focusVisible: { focusRing: "outer" },
|
|
593
|
+
"& > svg": { flexShrink: "0", transitionProperty: "rotate", transitionDuration: "fast" },
|
|
594
|
+
"& > span": { overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
595
|
+
},
|
|
596
|
+
"&[open] > summary > svg": { rotate: "90deg" },
|
|
597
|
+
"& > ol": {
|
|
598
|
+
display: "grid",
|
|
599
|
+
gap: "layout.controlGap",
|
|
600
|
+
margin: "none",
|
|
601
|
+
paddingBlockStart: "layout.contentGap",
|
|
602
|
+
paddingInlineStart: "none",
|
|
603
|
+
listStyle: "none",
|
|
604
|
+
},
|
|
605
|
+
"& li": {
|
|
606
|
+
display: "grid",
|
|
607
|
+
gridTemplateColumns: "token(sizes.mark.inline) minmax(0, 1fr)",
|
|
608
|
+
columnGap: "layout.controlGap",
|
|
609
|
+
alignItems: "start",
|
|
610
|
+
"& > svg": { fontSize: "icon.small" },
|
|
611
|
+
"&[data-status=failed] > svg": { color: "accent.red" },
|
|
612
|
+
"& > [data-title]": { color: "fg.secondary" },
|
|
613
|
+
"& > [data-detail]": { gridColumn: "2", lineHeight: "log", overflowWrap: "anywhere" },
|
|
360
614
|
},
|
|
361
|
-
"& > span:nth-child(2)": { animationDelay: "0.2s" },
|
|
362
|
-
"& > span:nth-child(3)": { animationDelay: "0.4s" },
|
|
363
615
|
});
|
|
364
|
-
export function ChatComposer({ value, defaultValue = "", onValueChange, onSend, placeholder, ariaLabel: ownAriaLabel, tools, actions, submitLabel: ownSubmitLabel, sendDisabled = false, sendsWithoutText = false, attachments, onPasteFiles, variant = "page", autoFocus, mentions, mentionMark, mentionsLabel, inputProps, className, ...props }) {
|
|
616
|
+
export function ChatComposer({ value, defaultValue = "", onValueChange, onSend, placeholder, ariaLabel: ownAriaLabel, tools, actions, submitLabel: ownSubmitLabel, sendDisabled = false, sendsWithoutText = false, attachments, onPasteFiles, dictation, variant = "page", autoFocus, mentions, mentionMark, mentionsLabel, inputProps, className, ...props }) {
|
|
365
617
|
const { labels } = useCiridaeLocale();
|
|
366
618
|
const ariaLabel = ownAriaLabel ?? labels.chat.message;
|
|
367
619
|
const submitLabel = ownSubmitLabel ?? labels.chat.sendMessage;
|
|
@@ -379,7 +631,6 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
379
631
|
onSend?.(currentValue);
|
|
380
632
|
setValue("");
|
|
381
633
|
};
|
|
382
|
-
const sheet = variant !== "page";
|
|
383
634
|
// In the capture phase, ahead of the text field and of an enclosing `Dropzone`, which would
|
|
384
635
|
// take the same files again.
|
|
385
636
|
const pasteFiles = onPasteFiles
|
|
@@ -393,40 +644,59 @@ export function ChatComposer({ value, defaultValue = "", onValueChange, onSend,
|
|
|
393
644
|
onPasteFiles(Array.from(files));
|
|
394
645
|
}
|
|
395
646
|
: undefined;
|
|
396
|
-
const input = (_jsx(MentionInput, { ...inputProps, "aria-label": ariaLabel, autoFocus: autoFocus, className: cx(sheet ? sheetInput :
|
|
397
|
-
// The sheet's send is
|
|
398
|
-
const sendButton = (_jsx(IconButton, { "aria-label": submitLabel, disabled: !canSend, onClick: send, variant:
|
|
647
|
+
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
|
+
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" }) }));
|
|
399
650
|
const waiting = attachments ? _jsx("div", { "data-attachments": "", children: attachments }) : null;
|
|
400
|
-
|
|
401
|
-
|
|
651
|
+
const waveform = dictation && (_jsx(ChatDictationWaveform, { levels: dictation.levels, listening: dictation.listening }));
|
|
652
|
+
if (variant === "sheet") {
|
|
653
|
+
return (_jsxs("div", { ...props, className: cx(sheetComposer, className), "data-ciridae-ui": "", onPasteCapture: pasteFiles, children: [waiting, input, _jsxs("div", { className: sheetBar, children: [_jsx("div", { "data-tools": "", children: tools }), waveform, _jsxs("div", { "data-send": "", children: [actions, sendButton] })] })] }));
|
|
654
|
+
}
|
|
655
|
+
if (variant === "feed") {
|
|
656
|
+
return (_jsxs("div", { ...props, className: cx(feedComposer, className), "data-ciridae-ui": "", onPasteCapture: pasteFiles, children: [tools && _jsx("div", { className: feedTools, children: tools }), _jsxs("div", { className: feedColumn, children: [waiting, _jsxs("div", { className: feedBox, children: [input, waveform, _jsxs("div", { "data-actions": "", children: [actions, sendButton] })] })] })] }));
|
|
402
657
|
}
|
|
403
|
-
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 }), _jsxs("div", { className: actionGroup, children: [actions, sendButton] })] })] }));
|
|
658
|
+
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
|
+
}
|
|
660
|
+
/** A tool for the sheet composer's bar, such as the paperclip: its icon alone. */
|
|
661
|
+
export function ChatComposerTool({ className, ...props }) {
|
|
662
|
+
return (_jsx("button", { type: "button", ...props, className: cx(composerTool, className), "data-ciridae-ui": "" }));
|
|
663
|
+
}
|
|
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
|
+
*/
|
|
668
|
+
export function ChatDictationWaveform({ levels, listening, className, ...props }) {
|
|
669
|
+
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
|
+
transform: `scaleY(${listening ? Math.max(silentWaveformLevel, level) : silentWaveformLevel})`,
|
|
671
|
+
} }, index))) }));
|
|
404
672
|
}
|
|
405
|
-
/** A message bubble
|
|
673
|
+
/** A message bubble: the user's raised on the right, another author's filled on the left. */
|
|
406
674
|
export function ChatBubble({ side, groupPosition, className, ...props }) {
|
|
407
|
-
return (_jsx("div", { ...props, className: cx(bubble({ side }), className), "data-ciridae-ui": "", "data-group-position": groupPosition }));
|
|
675
|
+
return (_jsx("div", { ...props, className: cx(bubble({ side }), className), "data-ciridae-ui": "", "data-edge": side === "user" ? "gradient" : undefined, "data-group-position": groupPosition }));
|
|
408
676
|
}
|
|
409
677
|
/** A centered line between bubbles: a finished tool call, a decision, something the page did. */
|
|
410
678
|
export function ChatSystemLine({ tone, className, ...props }) {
|
|
411
679
|
return (_jsx("div", { ...props, className: cx(systemLine, className), "data-ciridae-ui": "", "data-tone": tone === "error" ? "error" : undefined }));
|
|
412
680
|
}
|
|
413
|
-
/** Three pulsing dots: the assistant is typing. */
|
|
414
|
-
export function ChatTypingDots({ className, ...props }) {
|
|
415
|
-
return (_jsxs("span", { ...props, className: cx(typingDots, className), "data-ciridae-ui": "", "aria-hidden": "true", children: [_jsx("span", {}), _jsx("span", {}), _jsx("span", {})] }));
|
|
416
|
-
}
|
|
417
681
|
export function ChatAction({ className, ...props }) {
|
|
418
682
|
return (_jsx("button", { ...props, className: cx(chatAction, className), "data-ciridae-ui": "", type: props.type ?? "button" }));
|
|
419
683
|
}
|
|
684
|
+
/** A source cited in a reply, as a card of what it says under its name. */
|
|
685
|
+
export function ChatSourceCard({ label, icon, children, meta, tone = "neutral", href, className, }) {
|
|
686
|
+
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 }));
|
|
688
|
+
}
|
|
420
689
|
/*
|
|
421
690
|
* A conversation in time order. Close messages from one author read as a group: bubbles 4px
|
|
422
|
-
* apart, groups
|
|
691
|
+
* apart, groups 12px apart, the author's avatar beside the group's last bubble on the assistant's
|
|
423
692
|
* side and the author and time once under it. The nearest scrolling ancestor follows the newest
|
|
424
693
|
* entry while the reader is at its foot; scrolled up, reading back, a jump to the newest shows
|
|
425
694
|
* instead. A message the user sends always brings the thread to its foot.
|
|
426
695
|
*/
|
|
427
|
-
export function ChatThread({ entries, assistant, renderCustom, formatTime, density = "comfortable", attachmentTiles = false, dayLabels = false, resolveAttachmentUrl, className, }) {
|
|
696
|
+
export function ChatThread({ entries, assistant, renderCustom, formatTime, density = "comfortable", replyStyle = "bubble", attachmentTiles = false, dayLabels = false, resolveAttachmentUrl, className, }) {
|
|
428
697
|
const rootRef = useRef(null);
|
|
429
698
|
const { formatDateTime, labels } = useCiridaeLocale();
|
|
699
|
+
const plain = replyStyle === "plain";
|
|
430
700
|
const authorOf = (entry) => entry.kind === "line" || entrySide(entry) === "user"
|
|
431
701
|
? undefined
|
|
432
702
|
: ((entry.kind === "message" ? entry.author : undefined) ?? assistant);
|
|
@@ -439,11 +709,11 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
439
709
|
.reverse()
|
|
440
710
|
.find((entry) => entry.kind === "message" && entry.side === "user");
|
|
441
711
|
const latest = useFollowLatest(rootRef, newestUserMessage?.id);
|
|
442
|
-
return (_jsxs("div", { ref: rootRef, className: cx(thread, className), "data-ciridae-ui": "", "data-density": density === "compact" ? "compact" : undefined, children: [entries.map((entry, index) => {
|
|
712
|
+
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) => {
|
|
443
713
|
const previous = entries[index - 1];
|
|
444
714
|
const newDay = dayLabels && (!previous || !isSameDay(previous.at, entry.at));
|
|
445
715
|
const content = renderEntry(entry, index);
|
|
446
|
-
return newDay ? (_jsxs(Fragment, { children: [_jsx(
|
|
716
|
+
return newDay ? (_jsxs(Fragment, { children: [_jsx(DayHeading, { at: entry.at }), content] }, entry.id)) : (content);
|
|
447
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" }) }) })) })] }));
|
|
448
718
|
function renderEntry(entry, index) {
|
|
449
719
|
if (entry.kind === "line") {
|
|
@@ -454,20 +724,28 @@ export function ChatThread({ entries, assistant, renderCustom, formatTime, densi
|
|
|
454
724
|
const position = positions[index] ?? undefined;
|
|
455
725
|
const closesGroup = position === "single" || position === "last";
|
|
456
726
|
const message = entry.kind === "message" ? entry : null;
|
|
727
|
+
const avatar = !plain && side === "assistant";
|
|
457
728
|
const tiles = attachmentTiles && message?.attachments && message.attachments.length > 0
|
|
458
729
|
? message.attachments
|
|
459
730
|
: null;
|
|
460
|
-
|
|
731
|
+
const sourceLinks = message?.sources?.filter((source) => source.quote === undefined) ?? [];
|
|
732
|
+
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
|
+
const footer = closesGroup &&
|
|
735
|
+
entry.kind !== "typing" &&
|
|
736
|
+
entry.kind !== "steps" &&
|
|
737
|
+
(!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 }), 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, 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));
|
|
461
739
|
}
|
|
462
740
|
}
|
|
463
|
-
/** A day's heading in a thread that runs over days: Today, Yesterday
|
|
464
|
-
function
|
|
741
|
+
/** A day's heading in a thread that runs over days: Today, Yesterday or the weekday, then the date. */
|
|
742
|
+
function DayHeading({ at }) {
|
|
465
743
|
const { labels, formatDate } = useCiridaeLocale();
|
|
466
|
-
return (_jsx(
|
|
744
|
+
return (_jsx(LogGroupHeader, { role: "separator", "data-day-heading": "", label: isToday(at)
|
|
467
745
|
? labels.dates.today
|
|
468
746
|
: isYesterday(at)
|
|
469
747
|
? labels.dates.yesterday
|
|
470
|
-
: formatDate(at, { weekday: "long", month: "short", day: "numeric" }) }));
|
|
748
|
+
: formatDate(at, { weekday: "long" }), meta: formatDate(at, { month: "short", day: "numeric" }) }));
|
|
471
749
|
}
|
|
472
750
|
/** Prompts to start a conversation from, stacked at the foot of an empty thread. */
|
|
473
751
|
export function ChatSuggestions({ suggestions, onSelect, className }) {
|
|
@@ -491,20 +769,43 @@ function AttachmentTile({ attachment, resolveUrl, onOpen, }) {
|
|
|
491
769
|
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 })] }));
|
|
492
770
|
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 }));
|
|
493
771
|
}
|
|
494
|
-
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, }) {
|
|
495
|
-
const { labels } = useCiridaeLocale();
|
|
772
|
+
function ThreadEntryContent({ entry, side, groupPosition, renderCustom, attachmentTiles: tiles, plain, }) {
|
|
496
773
|
switch (entry.kind) {
|
|
497
|
-
case "message":
|
|
498
|
-
|
|
774
|
+
case "message": {
|
|
775
|
+
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))) }));
|
|
776
|
+
if (plain && side === "assistant") {
|
|
777
|
+
return (_jsxs("div", { className: plainReply, "aria-busy": entry.sending || undefined, children: [entry.body, attachmentNames] }));
|
|
778
|
+
}
|
|
779
|
+
return (_jsxs(ChatBubble, { side: side, groupPosition: groupPosition, "aria-busy": entry.sending || undefined, children: [entry.body, attachmentNames] }));
|
|
780
|
+
}
|
|
499
781
|
case "typing":
|
|
500
|
-
return (
|
|
782
|
+
return _jsx(AgentThinking, { label: entry.label });
|
|
783
|
+
case "steps":
|
|
784
|
+
return _jsx(AgentSteps, { steps: entry.steps });
|
|
501
785
|
case "failure":
|
|
502
|
-
return (
|
|
786
|
+
return _jsx(FailureNotice, { title: entry.title, message: entry.message, onRetry: entry.onRetry });
|
|
503
787
|
case "custom":
|
|
504
788
|
return renderCustom?.(entry.data);
|
|
505
789
|
}
|
|
506
790
|
}
|
|
507
|
-
/** The
|
|
791
|
+
/** The agent at work: what it is doing beside a spinner, over lines standing in for its reply. */
|
|
792
|
+
function AgentThinking({ label }) {
|
|
793
|
+
const { labels } = useCiridaeLocale();
|
|
794
|
+
return (_jsxs("div", { className: thinking, role: "status", 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
|
+
}
|
|
796
|
+
/** The steps behind a reply, folded into a line naming the first and how many more. */
|
|
797
|
+
function AgentSteps({ steps }) {
|
|
798
|
+
const [first] = steps;
|
|
799
|
+
if (!first)
|
|
800
|
+
return null;
|
|
801
|
+
return (_jsxs("details", { className: stepList, children: [_jsxs("summary", { children: [_jsx(CaretRightIcon, { className: cx(icon({ size: "compact" }), directional) }), _jsxs("span", { children: [first.title, steps.length > 1 && ` · +${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 }), step.detail && _jsx("span", { "data-detail": "", children: step.detail })] }, step.id))) })] }));
|
|
802
|
+
}
|
|
803
|
+
/** A reply that did not finish: a notice of why, and Try again under it. */
|
|
804
|
+
function FailureNotice({ title, message, onRetry, }) {
|
|
805
|
+
const { labels } = useCiridaeLocale();
|
|
806
|
+
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
|
+
}
|
|
808
|
+
/** The side an entry sits on: the agent's work, its steps and failures are the assistant's. */
|
|
508
809
|
function entrySide(entry) {
|
|
509
810
|
return entry.kind === "message" || entry.kind === "custom" ? entry.side : "assistant";
|
|
510
811
|
}
|