@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
|
@@ -14,7 +14,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
14
14
|
* is working on and what waits on the user. `variant="conversations"` is a chat per question.
|
|
15
15
|
*/
|
|
16
16
|
import { ArrowsOutSimpleIcon, ArrowSquareOutIcon, PaperclipIcon, SidebarSimpleIcon, SparkleIcon, XIcon, } from "@phosphor-icons/react";
|
|
17
|
-
import { useRef, useState, } from "react";
|
|
17
|
+
import { useId, useLayoutEffect, useRef, useState, } from "react";
|
|
18
18
|
import { css, cx } from "#primitives/css";
|
|
19
19
|
import { parseMentions } from "../chat/contract.js";
|
|
20
20
|
import { MessageText, splitSources } from "../chat/message-text.js";
|
|
@@ -45,22 +45,23 @@ const docked = css({
|
|
|
45
45
|
position: "relative",
|
|
46
46
|
});
|
|
47
47
|
/*
|
|
48
|
-
* The gap between the page and the docked sheet: drag
|
|
49
|
-
* double-click for its default. A line down its middle shows
|
|
48
|
+
* The gap between the page and the docked sheet: drag it toward the page, or press the arrow that
|
|
49
|
+
* points there, to widen the sheet; double-click for its default. A line down its middle shows
|
|
50
|
+
* while it is held or hovered.
|
|
50
51
|
*/
|
|
51
52
|
const resizeHandle = css({
|
|
52
53
|
position: "relative",
|
|
53
54
|
flexShrink: "0",
|
|
54
|
-
width: "token(spacing.panel.compact)",
|
|
55
|
+
width: "[token(spacing.panel.compact)]",
|
|
55
56
|
cursor: "col-resize",
|
|
56
57
|
touchAction: "none",
|
|
57
|
-
outline: "
|
|
58
|
+
outline: "0",
|
|
58
59
|
"&::after": {
|
|
59
60
|
content: '""',
|
|
60
61
|
position: "absolute",
|
|
61
|
-
insetBlock: "
|
|
62
|
-
left: "50%",
|
|
63
|
-
width: "token(spacing.control.trackInset)",
|
|
62
|
+
insetBlock: "none",
|
|
63
|
+
left: "[50%]",
|
|
64
|
+
width: "[token(spacing.control.trackInset)]",
|
|
64
65
|
transform: "translateX(-50%)",
|
|
65
66
|
transitionProperty: "background",
|
|
66
67
|
transitionDuration: "fast",
|
|
@@ -72,57 +73,115 @@ const resizeHandle = css({
|
|
|
72
73
|
const MIN_DOCKED_WIDTH = 360;
|
|
73
74
|
const MAX_DOCKED_WIDTH = 720;
|
|
74
75
|
const RESIZE_KEY_STEP = 16;
|
|
76
|
+
/** The widest the docked sheet may be in this window: half of it, and at most `MAX_DOCKED_WIDTH`. */
|
|
77
|
+
function measureMaximumDockedWidth() {
|
|
78
|
+
return Math.floor(Math.min(MAX_DOCKED_WIDTH, window.innerWidth / 2));
|
|
79
|
+
}
|
|
75
80
|
function clampDockedWidth(width) {
|
|
76
|
-
|
|
77
|
-
return Math.round(Math.min(Math.max(width, MIN_DOCKED_WIDTH), maximum));
|
|
81
|
+
return Math.round(Math.min(Math.max(width, MIN_DOCKED_WIDTH), measureMaximumDockedWidth()));
|
|
78
82
|
}
|
|
79
|
-
/**
|
|
80
|
-
|
|
83
|
+
/**
|
|
84
|
+
* The docked width the person dragged or stepped to, or null for the token's default, and the
|
|
85
|
+
* props of the handle that changes it. `asideRef` takes the docked aside, which the handle
|
|
86
|
+
* measures for its value.
|
|
87
|
+
*/
|
|
88
|
+
function useDockedWidth() {
|
|
89
|
+
const [aside, setAside] = useState(null);
|
|
81
90
|
const [width, setWidth] = useState(null);
|
|
91
|
+
const [renderedWidth, setRenderedWidth] = useState(null);
|
|
92
|
+
const [maximumWidth, setMaximumWidth] = useState(MAX_DOCKED_WIDTH);
|
|
93
|
+
const drag = useRef(null);
|
|
82
94
|
const [dragging, setDragging] = useState(false);
|
|
83
|
-
const { labels } = useCiridaeLocale();
|
|
95
|
+
const { labels, dir } = useCiridaeLocale();
|
|
96
|
+
// The sheet docks at the inline end, so the page lies to its left, or to its right in a
|
|
97
|
+
// right-to-left layout; the drag and the arrow keys widen it toward the page.
|
|
98
|
+
const pageIsToTheRight = dir === "rtl";
|
|
99
|
+
const arrowKeyThatWidens = pageIsToTheRight ? "ArrowRight" : "ArrowLeft";
|
|
100
|
+
const arrowKeyThatNarrows = pageIsToTheRight ? "ArrowLeft" : "ArrowRight";
|
|
101
|
+
useLayoutEffect(() => {
|
|
102
|
+
if (!aside)
|
|
103
|
+
return;
|
|
104
|
+
const measureAside = () => setRenderedWidth(Math.round(aside.getBoundingClientRect().width));
|
|
105
|
+
const followWindowWidth = () => {
|
|
106
|
+
setMaximumWidth(measureMaximumDockedWidth());
|
|
107
|
+
// A narrower window can leave a dragged width past its new maximum.
|
|
108
|
+
setWidth((current) => (current === null ? null : clampDockedWidth(current)));
|
|
109
|
+
};
|
|
110
|
+
measureAside();
|
|
111
|
+
followWindowWidth();
|
|
112
|
+
const asideResizes = new ResizeObserver(measureAside);
|
|
113
|
+
asideResizes.observe(aside);
|
|
114
|
+
window.addEventListener("resize", followWindowWidth);
|
|
115
|
+
return () => {
|
|
116
|
+
asideResizes.disconnect();
|
|
117
|
+
window.removeEventListener("resize", followWindowWidth);
|
|
118
|
+
// The handle goes with the aside, so a drag still held ends with them.
|
|
119
|
+
drag.current = null;
|
|
120
|
+
setDragging(false);
|
|
121
|
+
};
|
|
122
|
+
}, [aside]);
|
|
123
|
+
const endDrag = () => {
|
|
124
|
+
drag.current = null;
|
|
125
|
+
setDragging(false);
|
|
126
|
+
};
|
|
127
|
+
// What the reader set, or, until they do, the token's width as the aside draws it.
|
|
128
|
+
const currentWidth = width ?? renderedWidth;
|
|
84
129
|
const handleProps = {
|
|
85
130
|
role: "separator",
|
|
86
131
|
"aria-orientation": "vertical",
|
|
87
132
|
"aria-label": labels.chat.resize,
|
|
88
133
|
"aria-valuemin": MIN_DOCKED_WIDTH,
|
|
89
|
-
"aria-valuemax":
|
|
90
|
-
"aria-valuenow":
|
|
134
|
+
"aria-valuemax": maximumWidth,
|
|
135
|
+
"aria-valuenow": currentWidth ?? undefined,
|
|
136
|
+
"aria-valuetext": currentWidth === null ? undefined : labels.chat.sheetWidth(currentWidth),
|
|
91
137
|
tabIndex: 0,
|
|
92
138
|
"data-dragging": dragging || undefined,
|
|
139
|
+
// The handle holds the pointer while it drags, so the moves and the end of the drag come to
|
|
140
|
+
// it, wherever the pointer goes.
|
|
93
141
|
onPointerDown: (event) => {
|
|
94
|
-
const aside = asideRef.current;
|
|
95
142
|
if (!aside)
|
|
96
143
|
return;
|
|
97
|
-
const startX = event.clientX;
|
|
98
|
-
const startWidth = aside.getBoundingClientRect().width;
|
|
99
144
|
event.currentTarget.setPointerCapture(event.pointerId);
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
window.removeEventListener("pointermove", move);
|
|
105
|
-
window.removeEventListener("pointerup", stop);
|
|
145
|
+
drag.current = {
|
|
146
|
+
pointerId: event.pointerId,
|
|
147
|
+
startX: event.clientX,
|
|
148
|
+
startWidth: aside.getBoundingClientRect().width,
|
|
106
149
|
};
|
|
107
|
-
|
|
108
|
-
|
|
150
|
+
setDragging(true);
|
|
151
|
+
},
|
|
152
|
+
onPointerMove: (event) => {
|
|
153
|
+
const held = drag.current;
|
|
154
|
+
if (held?.pointerId !== event.pointerId)
|
|
155
|
+
return;
|
|
156
|
+
const movedTowardThePage = pageIsToTheRight
|
|
157
|
+
? event.clientX - held.startX
|
|
158
|
+
: held.startX - event.clientX;
|
|
159
|
+
setWidth(clampDockedWidth(held.startWidth + movedTowardThePage));
|
|
109
160
|
},
|
|
161
|
+
onPointerUp: endDrag,
|
|
162
|
+
onPointerCancel: endDrag,
|
|
163
|
+
onLostPointerCapture: endDrag,
|
|
110
164
|
onKeyDown: (event) => {
|
|
111
|
-
const step = event.key ===
|
|
165
|
+
const step = event.key === arrowKeyThatWidens
|
|
112
166
|
? RESIZE_KEY_STEP
|
|
113
|
-
: event.key ===
|
|
167
|
+
: event.key === arrowKeyThatNarrows
|
|
114
168
|
? -RESIZE_KEY_STEP
|
|
115
169
|
: 0;
|
|
170
|
+
if (event.key === "Home" || event.key === "End") {
|
|
171
|
+
event.preventDefault();
|
|
172
|
+
setWidth(event.key === "Home" ? MIN_DOCKED_WIDTH : measureMaximumDockedWidth());
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
116
175
|
if (step === 0)
|
|
117
176
|
return;
|
|
118
177
|
event.preventDefault();
|
|
119
178
|
// Until it is dragged the sheet sits at the token's width, so the first step starts there.
|
|
120
|
-
const rendered =
|
|
179
|
+
const rendered = aside?.getBoundingClientRect().width ?? MIN_DOCKED_WIDTH;
|
|
121
180
|
setWidth((current) => clampDockedWidth((current ?? rendered) + step));
|
|
122
181
|
},
|
|
123
182
|
onDoubleClick: () => setWidth(null),
|
|
124
183
|
};
|
|
125
|
-
return { width, handleProps };
|
|
184
|
+
return { width, asideRef: setAside, handleProps };
|
|
126
185
|
}
|
|
127
186
|
const dockedHeader = css({
|
|
128
187
|
display: "flex",
|
|
@@ -155,24 +214,23 @@ const contextRow = css({
|
|
|
155
214
|
fontWeight: "medium",
|
|
156
215
|
lineHeight: "tight",
|
|
157
216
|
});
|
|
158
|
-
// A record the agent reads: the raised face under its gradient edge.
|
|
159
217
|
const contextChip = css({
|
|
160
218
|
display: "inline-flex",
|
|
161
219
|
alignItems: "center",
|
|
162
220
|
gap: "control.iconGap",
|
|
163
221
|
height: "control.shortcut",
|
|
164
|
-
// The kit counts the 1px edge into the padding.
|
|
165
|
-
paddingInlineStart: "
|
|
166
|
-
paddingInlineEnd: "
|
|
222
|
+
// The kit counts the 1px edge into the padding; the edge's transparent border is that pixel.
|
|
223
|
+
paddingInlineStart: "control.block",
|
|
224
|
+
paddingInlineEnd: "control.iconInline",
|
|
167
225
|
borderRadius: "field",
|
|
168
|
-
background: "token(gradients.raised)",
|
|
226
|
+
background: "[token(gradients.raised)]",
|
|
169
227
|
color: "fg.primary",
|
|
170
|
-
"&::before": { background: "token(gradients.border.raised)" },
|
|
228
|
+
"&::before": { background: "[token(gradients.border.raised)]" },
|
|
171
229
|
"& svg": { color: "fg.muted" },
|
|
172
230
|
});
|
|
173
231
|
/* The thread's scrolling area, between the header and the composer. */
|
|
174
232
|
const threadScroll = css({ flex: "1", minHeight: "0", padding: "none", overflowY: "auto" });
|
|
175
|
-
const centered = css({ display: "grid", placeItems: "center", height: "
|
|
233
|
+
const centered = css({ display: "grid", placeItems: "center", height: "full" });
|
|
176
234
|
/* The thread and composer, the drop target for files; it fills what the sheet or page gives it. */
|
|
177
235
|
const conversationRoot = css({
|
|
178
236
|
display: "flex",
|
|
@@ -191,22 +249,22 @@ const composerFooter = css({
|
|
|
191
249
|
/* Over the composer, what goes with it: an error. */
|
|
192
250
|
const footerStack = css({ display: "grid", gap: "layout.tightGap", minWidth: "0" });
|
|
193
251
|
/* Full page: the thread and composer in one readable column down the middle. */
|
|
194
|
-
const wideColumn = css({ width: "
|
|
252
|
+
const wideColumn = css({ width: "full", maxWidth: "chat.thread", marginInline: "auto" });
|
|
195
253
|
/* In a drawer, the context row and the conversation stack under its header. */
|
|
196
254
|
const drawerStack = css({ display: "flex", flex: "1", minHeight: "0", flexDirection: "column" });
|
|
197
255
|
export function ChatSheet({ open, onOpenChange, title, context, placement = "drawer", onPlacementChange, onOpenFullPage, shortcut, ...conversationProps }) {
|
|
198
|
-
const {
|
|
256
|
+
const { tasks } = conversationProps;
|
|
199
257
|
const { labels } = useCiridaeLocale();
|
|
200
|
-
const
|
|
201
|
-
const
|
|
258
|
+
const dockedWidth = useDockedWidth();
|
|
259
|
+
const dockedTitleId = useId();
|
|
202
260
|
const titleContent = (_jsxs("span", { className: titleLine, children: [_jsx(SparkleIcon, { className: icon({ size: "small" }), weight: "fill" }), title] }));
|
|
203
261
|
const actions = (_jsxs(_Fragment, { children: [shortcut && _jsx(ShortcutHint, { hotkey: shortcut, boxed: true }), onOpenFullPage && (_jsx(IconButton, { "aria-label": labels.chat.openFullPage, variant: "ghost", size: "small", onClick: onOpenFullPage, children: _jsx(ArrowsOutSimpleIcon, { className: icon({ size: "small" }) }) })), onPlacementChange &&
|
|
204
262
|
(placement === "docked" ? (_jsx(IconButton, { "aria-label": labels.chat.undock, variant: "ghost", size: "small", onClick: () => onPlacementChange("drawer"), children: _jsx(ArrowSquareOutIcon, { className: icon({ size: "small" }) }) })) : (_jsx(IconButton, { "aria-label": labels.chat.dock, variant: "ghost", size: "small", onClick: () => onPlacementChange("docked"), children: _jsx(SidebarSimpleIcon, { className: icon({ size: "small" }), mirrored: true }) })))] }));
|
|
205
|
-
const contextContent = (_jsxs(_Fragment, { children: [context && context.length > 0 && (_jsxs("div", { className: contextRow, children: [labels.chat.reading, context.map((item) => (_jsxs("span", { className: contextChip, "data-ciridae-ui": "", "data-edge": "gradient", children: [item.icon, item.label] }, item.label)))] })),
|
|
263
|
+
const contextContent = (_jsxs(_Fragment, { children: [context && context.length > 0 && (_jsxs("div", { className: contextRow, children: [labels.chat.reading, context.map((item) => (_jsxs("span", { className: contextChip, "data-ciridae-ui": "", "data-edge": "gradient", children: [item.icon, item.label] }, item.label)))] })), tasks && _jsx(AgentWork, { layout: "summary", tasks: tasks })] }));
|
|
206
264
|
if (placement === "docked") {
|
|
207
265
|
if (!open)
|
|
208
266
|
return null;
|
|
209
|
-
return (_jsxs(_Fragment, { children: [_jsx("div", { className: resizeHandle, ...dockedWidth.handleProps }), _jsxs("aside", { ref: asideRef, className: docked, "data-ciridae-ui": "", "aria-
|
|
267
|
+
return (_jsxs(_Fragment, { children: [_jsx("div", { className: resizeHandle, ...dockedWidth.handleProps }), _jsxs("aside", { ref: dockedWidth.asideRef, className: docked, "data-ciridae-ui": "", "aria-labelledby": dockedTitleId, style: dockedWidth.width === null ? undefined : { width: dockedWidth.width }, children: [_jsxs("header", { className: dockedHeader, children: [_jsx("h2", { id: dockedTitleId, className: cx(heading({ size: "small" }), css({ lineHeight: "tight" })), children: titleContent }), _jsxs("div", { className: headerActions, children: [actions, _jsx(IconButton, { "aria-label": labels.common.close, onClick: () => onOpenChange(false), children: _jsx(XIcon, { className: icon({ size: "small" }) }) })] })] }), contextContent, _jsx(ChatConversation, { ...conversationProps })] })] }));
|
|
210
268
|
}
|
|
211
269
|
return (_jsx(Drawer, { open: open, onOpenChange: onOpenChange, size: "narrow", title: title, icon: _jsx(SparkleIcon, { className: icon({ size: "small" }), weight: "fill" }), actions: actions, children: _jsxs("div", { className: drawerStack, children: [contextContent, _jsx(ChatConversation, { ...conversationProps, scrollBody: DrawerBody })] }) }));
|
|
212
270
|
}
|
|
@@ -236,9 +294,10 @@ function useComposerFiles(upload, onUploadError) {
|
|
|
236
294
|
}
|
|
237
295
|
/**
|
|
238
296
|
* The thread and the composer: everything that reads or writes the conversation. `layout="wide"`
|
|
239
|
-
* sets both in one readable column for a full page; the sheet uses `sheet`.
|
|
297
|
+
* sets both in one readable column for a full page; the sheet uses `sheet`. Only the `agent`
|
|
298
|
+
* variant takes `tasks`.
|
|
240
299
|
*/
|
|
241
|
-
export function ChatConversation({ source, placeholder, draft, onDraftChange, composerRef, mentions, mentionMark, onMentionSelect, mentionHref, events = [], assistantName: ownAssistantName, suggestions, copyReplies = false, composerTools, dictation,
|
|
300
|
+
export function ChatConversation({ source, placeholder, draft, onDraftChange, composerRef, mentions, mentionMark, onMentionSelect, mentionHref, events = [], assistantName: ownAssistantName, suggestions, copyReplies = false, composerTools, dictation, tasks = [], onTaskReply, layout = "sheet", scrollBody: ScrollBody = "div", }) {
|
|
242
301
|
const { labels } = useCiridaeLocale();
|
|
243
302
|
const assistantName = ownAssistantName ?? labels.chat.assistantName;
|
|
244
303
|
const [ownDraft, setOwnDraft] = useState("");
|
|
@@ -271,10 +330,11 @@ export function ChatConversation({ source, placeholder, draft, onDraftChange, co
|
|
|
271
330
|
labels: labels.chat,
|
|
272
331
|
source,
|
|
273
332
|
events,
|
|
274
|
-
tasks
|
|
333
|
+
tasks,
|
|
275
334
|
renderReply: (content, plain) => (_jsx(MessageText, { content: content, plain: plain, onMentionSelect: onMentionSelect, mentionHref: mentionHref })),
|
|
276
335
|
replyActions: copyReplies ? (content) => _jsx(CopyReplyButton, { content: content }) : undefined,
|
|
277
|
-
|
|
336
|
+
// `tasks` come with `onTaskReply`, so a task to draw always has a reply to send.
|
|
337
|
+
taskCard: (task) => onTaskReply && _jsx(AgentTaskCard, { task: task, onReply: onTaskReply }),
|
|
278
338
|
});
|
|
279
339
|
const wide = layout === "wide";
|
|
280
340
|
const showSuggestions = entries.length === 0 && !source.loading && suggestions !== undefined && suggestions.length > 0;
|
|
@@ -306,7 +366,6 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
306
366
|
latestTurnStart = index;
|
|
307
367
|
});
|
|
308
368
|
const latestTurnUnfinished = status === "running" || status === "failed";
|
|
309
|
-
// The run of steps the items end on, which the running turn draws as what the agent is doing.
|
|
310
369
|
let stepRun = null;
|
|
311
370
|
for (const [index, item] of items.entries()) {
|
|
312
371
|
if (item.kind === "step") {
|
|
@@ -318,7 +377,9 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
318
377
|
entries.push({ kind: "steps", ...stepRun });
|
|
319
378
|
stepRun = null;
|
|
320
379
|
const inUnfinishedTurn = latestTurnUnfinished && index > latestTurnStart;
|
|
321
|
-
|
|
380
|
+
// The running turn's newest item is the one still arriving.
|
|
381
|
+
const newestOfRunningTurn = status === "running" && index === items.length - 1;
|
|
382
|
+
entries.push(...toEntries(item, inUnfinishedTurn, newestOfRunningTurn));
|
|
322
383
|
}
|
|
323
384
|
const runningSteps = status === "running" ? stepRun : null;
|
|
324
385
|
if (stepRun && !runningSteps)
|
|
@@ -367,7 +428,7 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
367
428
|
doing: newestStep && `${newestStep.title}…`,
|
|
368
429
|
});
|
|
369
430
|
return entries;
|
|
370
|
-
function toEntries(item, inUnfinishedTurn) {
|
|
431
|
+
function toEntries(item, inUnfinishedTurn, newestOfRunningTurn) {
|
|
371
432
|
switch (item.kind) {
|
|
372
433
|
case "message": {
|
|
373
434
|
const assistant = item.role === "assistant";
|
|
@@ -380,6 +441,7 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
380
441
|
body: item.text.trim() ? renderReply(item.text, !assistant) : "",
|
|
381
442
|
attachments: item.attachments,
|
|
382
443
|
sending: item.sending,
|
|
444
|
+
streaming: assistant && newestOfRunningTurn,
|
|
383
445
|
sources: assistant
|
|
384
446
|
? [
|
|
385
447
|
...splitSources(item.text).sources.map((source) => ({
|
|
@@ -415,10 +477,6 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
415
477
|
}
|
|
416
478
|
}
|
|
417
479
|
}
|
|
418
|
-
/**
|
|
419
|
-
* The latest turn's foot: the agent thinking while it runs, saying what it is `doing`; its
|
|
420
|
-
* failure with Try again; or Stopped.
|
|
421
|
-
*/
|
|
422
480
|
function appendTurnEnding(entries, { status, retry, failureMessage, lastItem, at, labels, doing, }) {
|
|
423
481
|
switch (status) {
|
|
424
482
|
case "running":
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type ComponentProps, type ReactNode } from "react";
|
|
2
|
-
import { type MessageSide } from "../chat/message-group-position.js";
|
|
2
|
+
import { type MessageGroupPosition, type MessageSide } from "../chat/message-group-position.js";
|
|
3
3
|
import { type MentionMark, type MentionTrigger } from "./mention-input.js";
|
|
4
4
|
import { type ResolveFileUrl } from "./resolved-file-url.js";
|
|
5
5
|
export { MessageText, splitSources, type MessageSource, type MessageTextProps, } from "../chat/message-text.js";
|
|
@@ -26,11 +26,7 @@ export type ChatComposerProps = Omit<ComponentProps<"div">, "onSubmit"> & {
|
|
|
26
26
|
* attach as a drop or the picker would. Rich text pasted with a picture of itself stays text.
|
|
27
27
|
*/
|
|
28
28
|
onPasteFiles?: (files: File[]) => void;
|
|
29
|
-
/**
|
|
30
|
-
* While the host takes dictation: the microphone's levels, drawn as a waveform beside the
|
|
31
|
-
* tools. The host owns the microphone and its controls, such as Cancel and Insert in `actions`,
|
|
32
|
-
* and writes what it heard into the text.
|
|
33
|
-
*/
|
|
29
|
+
/** The host owns the microphone and its controls, such as Cancel and Insert in `actions`, and writes what it heard into the text. */
|
|
34
30
|
dictation?: ChatDictation;
|
|
35
31
|
/**
|
|
36
32
|
* `page` is a chat page's wide card; `sheet` the field at the foot of the agent sheet; `feed`
|
|
@@ -48,47 +44,39 @@ export type ChatComposerProps = Omit<ComponentProps<"div">, "onSubmit"> & {
|
|
|
48
44
|
inputProps?: Omit<ComponentProps<"div">, "children" | "defaultValue" | "onChange" | "onSubmit" | "placeholder">;
|
|
49
45
|
};
|
|
50
46
|
export declare function ChatComposer({ value, defaultValue, onValueChange, onSend, placeholder, ariaLabel: ownAriaLabel, tools, actions, submitLabel: ownSubmitLabel, sendDisabled, sendsWithoutText, attachments, onPasteFiles, dictation, variant, autoFocus, mentions, mentionMark, mentionsLabel, inputProps, className, ...props }: ChatComposerProps): import("react").JSX.Element;
|
|
51
|
-
/** A tool for the sheet composer's bar, such as the paperclip: its icon alone. */
|
|
52
47
|
export declare function ChatComposerTool({ className, ...props }: Omit<ComponentProps<"button">, "aria-label"> & {
|
|
53
48
|
"aria-label": string;
|
|
54
49
|
}): import("react").JSX.Element;
|
|
55
|
-
/**
|
|
50
|
+
/** Each level is 0 to 1, newest last. */
|
|
56
51
|
export type ChatDictation = {
|
|
57
52
|
levels: readonly number[];
|
|
58
53
|
listening: boolean;
|
|
59
54
|
};
|
|
60
|
-
/**
|
|
61
|
-
* Dictation's waveform: a bar per level, each as tall as its level; flat while `listening` is
|
|
62
|
-
* off, such as while the host turns what it heard into text.
|
|
63
|
-
*/
|
|
55
|
+
/** Flat while `listening` is off, such as while the host turns what it heard into text. */
|
|
64
56
|
export declare function ChatDictationWaveform({ levels, listening, className, ...props }: ComponentProps<"div"> & ChatDictation): import("react").JSX.Element;
|
|
65
57
|
export type ChatBubbleProps = ComponentProps<"div"> & {
|
|
66
|
-
side:
|
|
58
|
+
side: MessageSide;
|
|
67
59
|
/** Where the bubble sits in its run of messages from one side; see `listMessageGroupPositions`. */
|
|
68
|
-
groupPosition?:
|
|
60
|
+
groupPosition?: MessageGroupPosition;
|
|
69
61
|
};
|
|
70
|
-
/** A message bubble: the user's raised on the right, another author's filled on the left. */
|
|
71
62
|
export declare function ChatBubble({ side, groupPosition, className, ...props }: ChatBubbleProps): import("react").JSX.Element;
|
|
72
63
|
/** A centered line between bubbles: a finished tool call, a decision, something the page did. */
|
|
73
|
-
export
|
|
64
|
+
export type ChatSystemLineProps = ComponentProps<"div"> & {
|
|
74
65
|
tone?: "muted" | "error";
|
|
75
|
-
}
|
|
66
|
+
};
|
|
67
|
+
export declare function ChatSystemLine({ tone, className, ...props }: ChatSystemLineProps): import("react").JSX.Element;
|
|
76
68
|
export declare function ChatAction({ className, ...props }: ComponentProps<"button">): import("react").JSX.Element;
|
|
77
69
|
export type ChatSourceCardProps = {
|
|
78
|
-
/** The source's name, such as "Vendor portal" or a rule's. */
|
|
79
70
|
label: ReactNode;
|
|
80
71
|
icon?: ReactNode;
|
|
81
|
-
/** What the source says that the reply relies on. */
|
|
82
72
|
children: ReactNode;
|
|
83
|
-
/** Under it, who entered it or when. */
|
|
84
73
|
meta?: ReactNode;
|
|
85
|
-
/** `
|
|
86
|
-
tone?: "neutral" | "
|
|
87
|
-
/**
|
|
74
|
+
/** `warning` marks a source the reply weighs, such as one of two rules in conflict. */
|
|
75
|
+
tone?: "neutral" | "warning";
|
|
76
|
+
/** Opens in a new tab, as an attachment does, so the conversation stays put. */
|
|
88
77
|
href?: string;
|
|
89
78
|
className?: string;
|
|
90
79
|
};
|
|
91
|
-
/** A source cited in a reply, as a card of what it says under its name. */
|
|
92
80
|
export declare function ChatSourceCard({ label, icon, children, meta, tone, href, className, }: ChatSourceCardProps): import("react").JSX.Element;
|
|
93
81
|
/** Who wrote a message: drawn as the avatar beside its group and named in the group's footer. */
|
|
94
82
|
export type ChatAuthor = {
|
|
@@ -109,39 +97,27 @@ export type ChatAttachment = {
|
|
|
109
97
|
url?: string | null;
|
|
110
98
|
mediaType?: string;
|
|
111
99
|
};
|
|
112
|
-
/**
|
|
113
|
-
* A source a reply cites: a link in the row under it, linked when it has an `href`; with a
|
|
114
|
-
* `quote`, a `ChatSourceCard` of what it says.
|
|
115
|
-
*/
|
|
100
|
+
/** Linked when it has an `href`; with a `quote`, drawn as a `ChatSourceCard`. */
|
|
116
101
|
export type ChatSource = {
|
|
117
102
|
id: string;
|
|
118
103
|
label: ReactNode;
|
|
119
104
|
href?: string;
|
|
120
|
-
/** What the source says that the reply relies on, such as a value or a rule. */
|
|
121
105
|
quote?: ReactNode;
|
|
122
|
-
/** Leads the card's label, such as the source's kind as an icon. */
|
|
123
106
|
icon?: ReactNode;
|
|
124
|
-
/** Under the card's quote: who entered it, or when. */
|
|
125
107
|
meta?: ReactNode;
|
|
126
108
|
tone?: ChatSourceCardProps["tone"];
|
|
127
109
|
};
|
|
128
|
-
/** A step the agent took on its way to a reply: a thought, or a tool it called. */
|
|
129
110
|
export type ChatStep = {
|
|
130
111
|
id: string;
|
|
131
112
|
kind: "reasoning" | "tool";
|
|
132
|
-
/** What it did, as one line, such as "Search invoices". */
|
|
133
113
|
title: string;
|
|
134
|
-
/**
|
|
135
|
-
* More about it, such as the rest of the thought. Everyone reading the thread sees it, so never
|
|
136
|
-
* a tool call's raw arguments.
|
|
137
|
-
*/
|
|
114
|
+
/** Everyone reading the thread sees it, so never a tool call's raw arguments. */
|
|
138
115
|
detail?: string;
|
|
139
116
|
status: "running" | "completed" | "failed";
|
|
140
117
|
};
|
|
141
118
|
/**
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
* card, drawn by `renderCustom` in the side's flow.
|
|
119
|
+
* Or something of the caller's own, such as an approval card, drawn by `renderCustom` in the
|
|
120
|
+
* side's flow.
|
|
145
121
|
*/
|
|
146
122
|
export type ChatThreadEntry<Custom = never> = {
|
|
147
123
|
id: string;
|
|
@@ -155,29 +131,30 @@ export type ChatThreadEntry<Custom = never> = {
|
|
|
155
131
|
attachments?: readonly ChatAttachment[];
|
|
156
132
|
/** Still on its way: the footer says Sending in place of the time. */
|
|
157
133
|
sending?: boolean;
|
|
158
|
-
/**
|
|
134
|
+
/**
|
|
135
|
+
* The assistant's reply is still arriving. The thread announces a reply to screen readers
|
|
136
|
+
* once, as this turns false or as it appears without it. Defaults to false.
|
|
137
|
+
*/
|
|
138
|
+
streaming?: boolean;
|
|
159
139
|
sources?: readonly ChatSource[];
|
|
160
|
-
/**
|
|
140
|
+
/** Shown while the message is hovered or focused. */
|
|
161
141
|
actions?: ReactNode;
|
|
162
|
-
/**
|
|
142
|
+
/** Always shown. */
|
|
163
143
|
suggestedActions?: ReactNode;
|
|
164
|
-
/** A closing line under the message, such as what the reply read. */
|
|
165
144
|
footnote?: ReactNode;
|
|
166
145
|
} | {
|
|
167
146
|
kind: "line";
|
|
168
147
|
text: ReactNode;
|
|
169
|
-
tone?: "
|
|
148
|
+
tone?: ChatSystemLineProps["tone"];
|
|
170
149
|
}
|
|
171
|
-
/** The
|
|
150
|
+
/** The locale's "Working" by default. */
|
|
172
151
|
| {
|
|
173
152
|
kind: "typing";
|
|
174
153
|
label?: ReactNode;
|
|
175
154
|
} | {
|
|
176
155
|
kind: "steps";
|
|
177
156
|
steps: readonly ChatStep[];
|
|
178
|
-
}
|
|
179
|
-
/** A reply that did not finish: `title` over why, and Try again under it. */
|
|
180
|
-
| {
|
|
157
|
+
} | {
|
|
181
158
|
kind: "failure";
|
|
182
159
|
title?: ReactNode;
|
|
183
160
|
message?: ReactNode;
|
|
@@ -195,14 +172,8 @@ export type ChatThreadProps<Custom = never> = {
|
|
|
195
172
|
renderCustom?: (data: Custom) => ReactNode;
|
|
196
173
|
/** How a footer writes the time; the time of day, dated once not today, otherwise. */
|
|
197
174
|
formatTime?: (at: number) => string;
|
|
198
|
-
/** `compact` trims the padding around the thread and between groups. */
|
|
199
175
|
density?: "comfortable" | "compact";
|
|
200
|
-
/**
|
|
201
|
-
* `bubble`, the default, draws every message in a bubble, the assistant's beside its author's
|
|
202
|
-
* avatar, with the author and time under each group. `plain` draws the assistant's replies as
|
|
203
|
-
* text on the sheet, the thread's full width, with no avatar and no author or time, as the
|
|
204
|
-
* agent sheet does.
|
|
205
|
-
*/
|
|
176
|
+
/** `plain` draws the assistant's replies as text on the sheet, the thread's full width, with no avatar and no author or time, as the agent sheet does. */
|
|
206
177
|
replyStyle?: "bubble" | "plain";
|
|
207
178
|
/** Draws a message's files as tiles over its bubble, rather than as names inside it. */
|
|
208
179
|
attachmentTiles?: boolean;
|