@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.
Files changed (147) hide show
  1. package/README.md +3 -2
  2. package/dist/chat/conversation-source.d.ts +1 -3
  3. package/dist/chat/message-text.js +6 -6
  4. package/dist/chat/use-chat-conversation.d.ts +7 -2
  5. package/dist/chat/use-chat-conversation.js +2 -1
  6. package/dist/components/activity-feed.d.ts +0 -1
  7. package/dist/components/activity-feed.js +6 -7
  8. package/dist/components/agent-work.d.ts +5 -1
  9. package/dist/components/agent-work.js +27 -13
  10. package/dist/components/app-frame.d.ts +3 -17
  11. package/dist/components/app-frame.js +11 -30
  12. package/dist/components/app-navigation.d.ts +5 -6
  13. package/dist/components/app-navigation.js +70 -40
  14. package/dist/components/badge.d.ts +0 -4
  15. package/dist/components/badge.js +17 -27
  16. package/dist/components/bulk-action-bar.js +2 -2
  17. package/dist/components/button.js +41 -50
  18. package/dist/components/calendar.js +5 -8
  19. package/dist/components/centered-page.js +4 -4
  20. package/dist/components/chart.js +3 -4
  21. package/dist/components/chat-client-source.js +55 -34
  22. package/dist/components/chat-page.js +1 -1
  23. package/dist/components/chat-sheet.d.ts +30 -20
  24. package/dist/components/chat-sheet.js +112 -54
  25. package/dist/components/chat.d.ts +27 -56
  26. package/dist/components/chat.js +153 -132
  27. package/dist/components/checkbox-item.js +4 -4
  28. package/dist/components/checkbox-list.js +6 -6
  29. package/dist/components/chip.js +2 -2
  30. package/dist/components/collapsible.js +3 -3
  31. package/dist/components/color-theme.d.ts +3 -6
  32. package/dist/components/color-theme.js +10 -11
  33. package/dist/components/column-settings.js +3 -3
  34. package/dist/components/command-menu.d.ts +1 -2
  35. package/dist/components/command-menu.js +56 -77
  36. package/dist/components/command-palette.js +5 -2
  37. package/dist/components/copyable-id.js +5 -5
  38. package/dist/components/data-display.d.ts +1 -7
  39. package/dist/components/data-display.js +32 -55
  40. package/dist/components/data-grid.d.ts +4 -7
  41. package/dist/components/data-grid.js +106 -90
  42. package/dist/components/date-range-picker.js +4 -5
  43. package/dist/components/document-viewer.js +23 -23
  44. package/dist/components/drawer.d.ts +2 -1
  45. package/dist/components/drawer.js +45 -20
  46. package/dist/components/dropzone.js +13 -13
  47. package/dist/components/editable-field.d.ts +2 -7
  48. package/dist/components/editable-field.js +14 -27
  49. package/dist/components/email-input.js +2 -3
  50. package/dist/components/environment-frame.js +7 -7
  51. package/dist/components/expandable-text.js +2 -2
  52. package/dist/components/feedback-screenshot.js +7 -7
  53. package/dist/components/feedback-sheet.js +8 -8
  54. package/dist/components/filter-bar.js +41 -27
  55. package/dist/components/form-controls.d.ts +3 -6
  56. package/dist/components/form-controls.js +15 -22
  57. package/dist/components/form-layout.d.ts +6 -6
  58. package/dist/components/form-layout.js +10 -16
  59. package/dist/components/glass-menu.d.ts +2 -0
  60. package/dist/components/glass-menu.js +14 -18
  61. package/dist/components/gradient-edge.d.ts +0 -1
  62. package/dist/components/gradient-edge.js +8 -9
  63. package/dist/components/icon.js +1 -1
  64. package/dist/components/input.js +18 -17
  65. package/dist/components/keycap.d.ts +2 -6
  66. package/dist/components/keycap.js +4 -12
  67. package/dist/components/layout.js +11 -10
  68. package/dist/components/lightbox.js +8 -8
  69. package/dist/components/link.js +1 -1
  70. package/dist/components/locale.d.ts +23 -7
  71. package/dist/components/locale.js +19 -7
  72. package/dist/components/log-entry.d.ts +2 -14
  73. package/dist/components/log-entry.js +11 -22
  74. package/dist/components/log-list.js +1 -1
  75. package/dist/components/mention-input.js +13 -14
  76. package/dist/components/menu.d.ts +7 -4
  77. package/dist/components/menu.js +23 -10
  78. package/dist/components/meter.d.ts +2 -3
  79. package/dist/components/meter.js +12 -16
  80. package/dist/components/navigation.d.ts +0 -7
  81. package/dist/components/navigation.js +3 -12
  82. package/dist/components/notification-tray.js +7 -7
  83. package/dist/components/overlays.d.ts +5 -2
  84. package/dist/components/overlays.js +24 -24
  85. package/dist/components/panel.js +3 -3
  86. package/dist/components/progress.js +2 -2
  87. package/dist/components/radio-card.d.ts +1 -1
  88. package/dist/components/radio-card.js +9 -9
  89. package/dist/components/record.d.ts +2 -4
  90. package/dist/components/record.js +19 -32
  91. package/dist/components/scroll-area.js +10 -10
  92. package/dist/components/segmented-bar.js +9 -9
  93. package/dist/components/select.d.ts +3 -5
  94. package/dist/components/select.js +26 -44
  95. package/dist/components/settings.js +1 -1
  96. package/dist/components/shell-skeleton.js +1 -1
  97. package/dist/components/shell.d.ts +1 -1
  98. package/dist/components/shell.js +69 -24
  99. package/dist/components/side-panel.js +3 -3
  100. package/dist/components/sidebar.d.ts +0 -5
  101. package/dist/components/sidebar.js +25 -33
  102. package/dist/components/single-line-text.js +3 -4
  103. package/dist/components/site-banner.js +4 -4
  104. package/dist/components/skeleton.js +19 -22
  105. package/dist/components/skeletons.d.ts +0 -3
  106. package/dist/components/skeletons.js +5 -10
  107. package/dist/components/stages.d.ts +6 -3
  108. package/dist/components/stages.js +77 -105
  109. package/dist/components/stat-card.d.ts +2 -5
  110. package/dist/components/stat-card.js +23 -31
  111. package/dist/components/status-banner.js +6 -6
  112. package/dist/components/status.js +3 -3
  113. package/dist/components/steps.js +2 -2
  114. package/dist/components/switch.js +1 -1
  115. package/dist/components/tabs.js +15 -18
  116. package/dist/components/theme-customizer.d.ts +5 -8
  117. package/dist/components/theme-customizer.js +9 -41
  118. package/dist/components/theme-toggle.d.ts +0 -1
  119. package/dist/components/theme-toggle.js +6 -11
  120. package/dist/components/toast.d.ts +1 -7
  121. package/dist/components/toast.js +19 -22
  122. package/dist/components/user-chip.js +8 -8
  123. package/dist/components/wizard.js +6 -4
  124. package/dist/preset.d.ts +1 -2
  125. package/dist/preset.js +41 -89
  126. package/dist/styled-system/css/conditions.mjs +1 -1
  127. package/dist/styled-system/css/css.mjs +1 -1
  128. package/dist/styled-system/styles.css +820 -1297
  129. package/dist/styled-system/tokens/index.mjs +78 -1926
  130. package/dist/styled-system/tokens/tokens.d.ts +18 -33
  131. package/dist/styled-system/types/conditions.d.ts +0 -74
  132. package/dist/styled-system/types/prop-type.d.ts +15 -40
  133. package/dist/styled-system/types/style-props.d.ts +705 -705
  134. package/dist/theme/color.d.ts +0 -1
  135. package/dist/theme/color.js +0 -16
  136. package/dist/theme/derive.d.ts +3 -3
  137. package/dist/theme/derive.js +9 -32
  138. package/dist/theme/index.d.ts +1 -2
  139. package/dist/theme/index.js +3 -5
  140. package/dist/theme/options.d.ts +2 -4
  141. package/dist/theme/options.js +0 -1
  142. package/dist/theme/{palettes.d.ts → palette.d.ts} +12 -23
  143. package/dist/theme/palette.js +127 -0
  144. package/dist/theme/tokens.d.ts +8 -13
  145. package/dist/theme/tokens.js +39 -92
  146. package/package.json +1 -1
  147. 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 or arrow keys to widen the sheet,
49
- * double-click for its default. A line down its middle shows while it is held or hovered.
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: "none",
58
+ outline: "0",
58
59
  "&::after": {
59
60
  content: '""',
60
61
  position: "absolute",
61
- insetBlock: "0",
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
- const maximum = Math.min(MAX_DOCKED_WIDTH, window.innerWidth / 2);
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
- /** The docked width the person dragged to, or null for the token's default. */
80
- function useDockedWidth(asideRef) {
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": MAX_DOCKED_WIDTH,
90
- "aria-valuenow": width ?? undefined,
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
- setDragging(true);
101
- const move = (moveEvent) => setWidth(clampDockedWidth(startWidth + startX - moveEvent.clientX));
102
- const stop = () => {
103
- setDragging(false);
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
- window.addEventListener("pointermove", move);
108
- window.addEventListener("pointerup", stop);
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 === "ArrowLeft"
165
+ const step = event.key === arrowKeyThatWidens
112
166
  ? RESIZE_KEY_STEP
113
- : event.key === "ArrowRight"
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 = asideRef.current?.getBoundingClientRect().width ?? MIN_DOCKED_WIDTH;
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: "calc(token(spacing.control.block) + token(sizes.border.hairline))",
166
- paddingInlineEnd: "calc(token(spacing.control.iconInline) + token(sizes.border.hairline))",
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: "100%" });
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: "100%", maxWidth: "chat.thread", marginInline: "auto" });
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 { variant = "agent", tasks } = conversationProps;
256
+ const { tasks } = conversationProps;
199
257
  const { labels } = useCiridaeLocale();
200
- const asideRef = useRef(null);
201
- const dockedWidth = useDockedWidth(asideRef);
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)))] })), variant === "agent" && tasks && _jsx(AgentWork, { layout: "summary", tasks: tasks })] }));
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-label": typeof title === "string" ? title : undefined, style: dockedWidth.width === null ? undefined : { width: dockedWidth.width }, children: [_jsxs("header", { className: dockedHeader, children: [_jsx("h2", { 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 })] })] }));
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, variant = "agent", tasks, onTaskReply, layout = "sheet", scrollBody: ScrollBody = "div", }) {
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: variant === "agent" ? (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
- taskCard: (task) => _jsx(AgentTaskCard, { task: task, onReply: onTaskReply }),
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
- entries.push(...toEntries(item, inUnfinishedTurn));
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
- /** The microphone's loudness while the host takes dictation, each level 0 to 1, newest last. */
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: "user" | "assistant";
58
+ side: MessageSide;
67
59
  /** Where the bubble sits in its run of messages from one side; see `listMessageGroupPositions`. */
68
- groupPosition?: "single" | "first" | "middle" | "last";
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 declare function ChatSystemLine({ tone, className, ...props }: ComponentProps<"div"> & {
64
+ export type ChatSystemLineProps = ComponentProps<"div"> & {
74
65
  tone?: "muted" | "error";
75
- }): import("react").JSX.Element;
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
- /** `yellow` marks a source the reply weighs, such as one of two rules in conflict. */
86
- tone?: "neutral" | "yellow";
87
- /** Makes the card a link to the source. */
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
- * One entry of a thread: a message from either side, a centered line, the agent at work, the
143
- * steps it took, a failure with its retry, or something of the caller's own, such as an approval
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
- /** What the reply cites: links in a row under it, and the ones with a quote as cards. */
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
- /** Controls beside the message, shown while it is hovered or focused, such as Copy. */
140
+ /** Shown while the message is hovered or focused. */
161
141
  actions?: ReactNode;
162
- /** Buttons under the message for what it suggests doing, always shown. */
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?: "muted" | "error";
148
+ tone?: ChatSystemLineProps["tone"];
170
149
  }
171
- /** The agent at work before its reply: what it is doing, the locale's "Working" by default. */
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;