@ciridae/ui 1.0.2 → 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/app/auth.d.ts +10 -0
- package/dist/app/auth.js +60 -0
- package/dist/app/error-boundary.d.ts +18 -0
- package/dist/app/error-boundary.js +45 -0
- package/dist/app/feedback.d.ts +12 -0
- package/dist/app/feedback.js +128 -0
- package/dist/app/index.d.ts +8 -0
- package/dist/app/index.js +8 -0
- package/dist/app/request-errors.d.ts +63 -0
- package/dist/app/request-errors.js +172 -0
- package/dist/app/sentry.d.ts +18 -0
- package/dist/app/sentry.js +52 -0
- package/dist/app/telemetry.d.ts +7 -0
- package/dist/app/telemetry.js +28 -0
- package/dist/app/trail.d.ts +9 -0
- package/dist/app/trail.js +18 -0
- package/dist/chat/chat-query-keys.d.ts +3 -3
- package/dist/chat/contract.d.ts +15 -15
- package/dist/chat/conversation-items-query-options.d.ts +114 -114
- package/dist/chat/conversation-source.d.ts +89 -0
- package/dist/chat/conversation-source.js +1 -0
- package/dist/chat/describe-tool-call.d.ts +3 -3
- package/dist/chat/describe-tool-call.js +3 -3
- package/dist/chat/message-text.d.ts +1 -1
- package/dist/chat/message-text.js +116 -48
- package/dist/chat/use-conversation-list.d.ts +6 -6
- package/dist/ciridae.css +16 -4
- package/dist/components/activity-feed.d.ts +3 -2
- package/dist/components/activity-feed.js +1 -0
- package/dist/components/agent-work.d.ts +3 -3
- package/dist/components/app-frame.d.ts +91 -0
- package/dist/components/app-frame.js +134 -0
- package/dist/components/app-navigation.d.ts +30 -3
- package/dist/components/app-navigation.js +105 -14
- package/dist/components/badge.d.ts +30 -5
- package/dist/components/badge.js +134 -15
- package/dist/components/brand-icons.d.ts +3 -3
- package/dist/components/bulk-action-bar.d.ts +1 -1
- package/dist/components/bulk-action-bar.js +3 -3
- package/dist/components/button.d.ts +12 -5
- package/dist/components/button.js +77 -44
- package/dist/components/calendar.d.ts +1 -1
- package/dist/components/calendar.js +44 -17
- package/dist/components/centered-page.d.ts +1 -1
- package/dist/components/centered-page.js +2 -2
- package/dist/components/chart.d.ts +22 -2
- package/dist/components/chart.js +251 -51
- package/dist/components/chat-client-source.d.ts +16 -0
- package/dist/components/chat-client-source.js +297 -0
- package/dist/components/chat-page.d.ts +9 -8
- package/dist/components/chat-page.js +7 -11
- package/dist/components/chat-sheet-controller.d.ts +11 -14
- package/dist/components/chat-sheet-controller.js +3 -3
- package/dist/components/chat-sheet.d.ts +18 -11
- package/dist/components/chat-sheet.js +188 -289
- package/dist/components/chat.d.ts +109 -19
- package/dist/components/chat.js +520 -124
- package/dist/components/checkbox-item.d.ts +6 -3
- package/dist/components/checkbox-item.js +9 -5
- package/dist/components/checkbox-list.d.ts +1 -1
- package/dist/components/chip.d.ts +6 -1
- package/dist/components/chip.js +27 -1
- package/dist/components/collapsible.d.ts +1 -1
- package/dist/components/color-theme.d.ts +1 -1
- package/dist/components/column-settings.d.ts +1 -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 +7 -3
- package/dist/components/command-palette.js +20 -11
- package/dist/components/copyable-id.d.ts +22 -5
- package/dist/components/copyable-id.js +46 -6
- package/dist/components/data-display.d.ts +39 -8
- package/dist/components/data-display.js +138 -19
- package/dist/components/data-grid.d.ts +23 -11
- package/dist/components/data-grid.js +61 -26
- package/dist/components/date-range-picker.d.ts +1 -1
- package/dist/components/date-range-picker.js +9 -20
- package/dist/components/date-time.d.ts +1 -1
- package/dist/components/dialog-height-context.d.ts +1 -1
- package/dist/components/document-viewer.d.ts +2 -2
- package/dist/components/drawer.d.ts +11 -6
- package/dist/components/drawer.js +91 -54
- package/dist/components/dropzone.d.ts +4 -4
- package/dist/components/editable-field.d.ts +12 -3
- package/dist/components/editable-field.js +57 -27
- package/dist/components/email-input.d.ts +1 -1
- package/dist/components/email-input.js +6 -5
- package/dist/components/environment-frame.d.ts +2 -2
- package/dist/components/environment-frame.js +37 -6
- package/dist/components/expandable-text.d.ts +1 -1
- package/dist/components/feedback-screenshot.d.ts +1 -1
- package/dist/components/feedback-sheet.d.ts +18 -3
- package/dist/components/feedback-sheet.js +216 -55
- package/dist/components/field-context.d.ts +1 -1
- package/dist/components/filter-bar.d.ts +3 -3
- package/dist/components/filter-bar.js +43 -29
- package/dist/components/form-controls.d.ts +12 -3
- package/dist/components/form-controls.js +46 -22
- package/dist/components/form-layout.d.ts +14 -10
- 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/hotkeys.d.ts +2 -2
- package/dist/components/icon.d.ts +1 -1
- package/dist/components/input.d.ts +7 -5
- package/dist/components/input.js +29 -17
- package/dist/components/keyboard-shortcuts.d.ts +2 -2
- package/dist/components/keycap.d.ts +10 -6
- package/dist/components/keycap.js +57 -19
- package/dist/components/layout.d.ts +13 -13
- package/dist/components/lightbox.d.ts +9 -3
- package/dist/components/lightbox.js +13 -9
- package/dist/components/link.d.ts +1 -1
- package/dist/components/locale.d.ts +42 -2
- package/dist/components/locale.js +43 -3
- package/dist/components/log-entry.d.ts +40 -0
- package/dist/components/log-entry.js +146 -0
- package/dist/components/log-list.d.ts +3 -3
- package/dist/components/mention-input.d.ts +2 -2
- package/dist/components/mention-input.js +4 -3
- package/dist/components/menu.d.ts +7 -3
- package/dist/components/menu.js +34 -37
- package/dist/components/meter.d.ts +10 -2
- 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.d.ts +1 -1
- package/dist/components/notification-tray.js +5 -4
- package/dist/components/overlays.d.ts +6 -4
- package/dist/components/overlays.js +69 -40
- package/dist/components/page-frame.d.ts +4 -4
- package/dist/components/page-frame.js +1 -1
- package/dist/components/panel.d.ts +4 -4
- package/dist/components/panel.js +18 -12
- package/dist/components/profile-header.d.ts +1 -1
- package/dist/components/progress.d.ts +1 -1
- package/dist/components/project-avatar.d.ts +1 -1
- package/dist/components/radio-card.d.ts +2 -2
- package/dist/components/radio-card.js +10 -9
- package/dist/components/record.d.ts +29 -5
- package/dist/components/record.js +147 -18
- package/dist/components/resolved-file-url.d.ts +19 -0
- package/dist/components/resolved-file-url.js +49 -0
- package/dist/components/scroll-area.d.ts +1 -1
- package/dist/components/secret-dialog.d.ts +1 -1
- package/dist/components/segmented-bar.d.ts +4 -2
- package/dist/components/segmented-bar.js +16 -6
- package/dist/components/select.d.ts +9 -6
- package/dist/components/select.js +103 -127
- package/dist/components/settings.d.ts +4 -4
- package/dist/components/shell-skeleton.d.ts +1 -1
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +21 -21
- package/dist/components/shell.js +43 -43
- package/dist/components/side-panel.d.ts +3 -3
- package/dist/components/sidebar.d.ts +13 -8
- 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 +8 -7
- package/dist/components/skeleton.js +28 -16
- package/dist/components/skeletons.d.ts +17 -5
- package/dist/components/skeletons.js +71 -8
- package/dist/components/stages.d.ts +83 -11
- package/dist/components/stages.js +512 -156
- package/dist/components/stat-card.d.ts +12 -4
- package/dist/components/stat-card.js +92 -37
- package/dist/components/status-banner.d.ts +1 -1
- package/dist/components/status-banner.js +1 -1
- package/dist/components/status.d.ts +2 -2
- package/dist/components/steps.d.ts +1 -1
- package/dist/components/switch.d.ts +1 -1
- package/dist/components/tabs.d.ts +8 -8
- package/dist/components/tabs.js +26 -13
- package/dist/components/theme-customizer.d.ts +2 -2
- package/dist/components/theme-toggle.d.ts +4 -0
- package/dist/components/theme-toggle.js +67 -0
- package/dist/components/toast.d.ts +10 -3
- package/dist/components/toast.js +78 -29
- package/dist/components/use-new-version-available.d.ts +6 -0
- package/dist/components/use-new-version-available.js +29 -0
- package/dist/components/user-chip.d.ts +3 -3
- package/dist/components/wizard.d.ts +2 -2
- package/dist/feedback/contract.d.ts +136 -136
- package/dist/feedback/feedback-controller.d.ts +6 -6
- package/dist/feedback/generated/zod.gen.d.ts +28 -28
- package/dist/feedback/operator-contract.d.ts +11 -11
- package/dist/feedback/operator-controller.d.ts +32 -13
- package/dist/feedback/operator-controller.js +44 -11
- package/dist/preset.js +289 -33
- package/dist/styled-system/styles.css +3294 -1079
- 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 +40 -3
|
@@ -2,32 +2,33 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "react/jsx-run
|
|
|
2
2
|
/*
|
|
3
3
|
* The Ask Ciridae sheet: the agent's conversation about the page the user is on, as a chat.
|
|
4
4
|
* Docked, it is an aside beside the page inside `AppShell`; as a drawer, it is the lib's sheet
|
|
5
|
-
* over the page. The host owns the
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
5
|
+
* over the page. The host owns the conversation's `source` (the lib's chat client through
|
|
6
|
+
* `useChatClientSource`, or the app's own hooks), the lines the page adds to the thread and what
|
|
7
|
+
* a mention does on the page; the sheet owns the thread, the composer, the files waiting to go
|
|
8
|
+
* with a message and the in-flight states. Files dropped on the conversation, pasted
|
|
9
|
+
* into it or picked with the paperclip upload as they arrive. `ChatConversation` is the same
|
|
10
|
+
* thread and composer without the sheet, such as for a full page.
|
|
10
11
|
*
|
|
11
12
|
* `variant="agent"`, the default, is one long-running thread with the main agent: the tasks it
|
|
12
13
|
* runs post cards into the thread under day labels, and a line over the thread sums up what it
|
|
13
14
|
* is working on and what waits on the user. `variant="conversations"` is a chat per question.
|
|
14
15
|
*/
|
|
15
16
|
import { ArrowsOutSimpleIcon, ArrowSquareOutIcon, PaperclipIcon, SidebarSimpleIcon, SparkleIcon, XIcon, } from "@phosphor-icons/react";
|
|
16
|
-
import {
|
|
17
|
+
import { useRef, useState, } from "react";
|
|
17
18
|
import { css, cx } from "#primitives/css";
|
|
18
19
|
import { parseMentions } from "../chat/contract.js";
|
|
19
|
-
import { describeApprovalRequest } from "../chat/describe-tool-call.js";
|
|
20
20
|
import { MessageText, splitSources } from "../chat/message-text.js";
|
|
21
|
-
import { useChatConversation } from "../chat/use-chat-conversation.js";
|
|
22
21
|
import { AgentTaskCard, AgentWork } from "./agent-work.js";
|
|
23
|
-
import {
|
|
24
|
-
import { ChatComposer, ChatSuggestions, ChatThread } from "./chat.js";
|
|
22
|
+
import { IconButton } from "./button.js";
|
|
23
|
+
import { ChatComposer, ChatComposerTool, ChatSuggestions, ChatSystemLine, ChatThread, } from "./chat.js";
|
|
25
24
|
import { CopyButton } from "./copyable-id.js";
|
|
26
25
|
import { Drawer, DrawerBody } from "./drawer.js";
|
|
27
|
-
import { Dropzone, DropzoneFileList } from "./dropzone.js";
|
|
28
|
-
import { icon } from "./icon.js";
|
|
26
|
+
import { Dropzone, DropzoneFileList, useDropzonePicker } from "./dropzone.js";
|
|
27
|
+
import { icon, Spinner } from "./icon.js";
|
|
28
|
+
import { ShortcutHint } from "./keycap.js";
|
|
29
29
|
import { heading } from "./layout.js";
|
|
30
30
|
import { useCiridaeLocale } from "./locale.js";
|
|
31
|
+
export { useChatClientConversations, useChatClientSource, } from "./chat-client-source.js";
|
|
31
32
|
/* The docked aside, drawn like the shell's main panel beside it. */
|
|
32
33
|
const docked = css({
|
|
33
34
|
display: "flex",
|
|
@@ -125,19 +126,20 @@ function useDockedWidth(asideRef) {
|
|
|
125
126
|
}
|
|
126
127
|
const dockedHeader = css({
|
|
127
128
|
display: "flex",
|
|
129
|
+
flexShrink: "0",
|
|
128
130
|
alignItems: "center",
|
|
129
131
|
justifyContent: "space-between",
|
|
130
|
-
gap: "layout.
|
|
131
|
-
|
|
132
|
+
gap: "layout.controlGap",
|
|
133
|
+
height: "shell.header",
|
|
132
134
|
paddingInlineStart: "panel.comfortable",
|
|
133
135
|
paddingInlineEnd: "panel.default",
|
|
134
|
-
borderBottom: "1px solid token(colors.border.
|
|
136
|
+
borderBottom: "1px solid token(colors.border.divider)",
|
|
135
137
|
});
|
|
136
138
|
const headerActions = css({ display: "flex", alignItems: "center", gap: "layout.controlGap" });
|
|
137
139
|
const titleLine = css({
|
|
138
140
|
display: "inline-flex",
|
|
139
141
|
alignItems: "center",
|
|
140
|
-
gap: "
|
|
142
|
+
gap: "layout.controlGap",
|
|
141
143
|
"& svg": { color: "fg.muted" },
|
|
142
144
|
});
|
|
143
145
|
const contextRow = css({
|
|
@@ -145,75 +147,32 @@ const contextRow = css({
|
|
|
145
147
|
alignItems: "center",
|
|
146
148
|
flexWrap: "wrap",
|
|
147
149
|
gap: "layout.controlGap",
|
|
148
|
-
paddingBlock: "layout.
|
|
150
|
+
paddingBlock: "layout.contentGap",
|
|
149
151
|
paddingInline: "panel.comfortable",
|
|
150
|
-
borderBottom: "1px solid token(colors.border.
|
|
151
|
-
color: "fg.
|
|
152
|
+
borderBottom: "1px solid token(colors.border.divider)",
|
|
153
|
+
color: "fg.subtle",
|
|
152
154
|
fontSize: "code",
|
|
155
|
+
fontWeight: "medium",
|
|
156
|
+
lineHeight: "tight",
|
|
153
157
|
});
|
|
158
|
+
// A record the agent reads: the raised face under its gradient edge.
|
|
154
159
|
const contextChip = css({
|
|
155
160
|
display: "inline-flex",
|
|
156
161
|
alignItems: "center",
|
|
157
162
|
gap: "control.iconGap",
|
|
158
163
|
height: "control.shortcut",
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
borderRadius: "
|
|
163
|
-
background: "
|
|
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))",
|
|
167
|
+
borderRadius: "field",
|
|
168
|
+
background: "token(gradients.raised)",
|
|
164
169
|
color: "fg.primary",
|
|
170
|
+
"&::before": { background: "token(gradients.border.raised)" },
|
|
165
171
|
"& svg": { color: "fg.muted" },
|
|
166
172
|
});
|
|
167
173
|
/* The thread's scrolling area, between the header and the composer. */
|
|
168
174
|
const threadScroll = css({ flex: "1", minHeight: "0", padding: "none", overflowY: "auto" });
|
|
169
|
-
|
|
170
|
-
const card = css({
|
|
171
|
-
display: "grid",
|
|
172
|
-
gap: "control.iconGap",
|
|
173
|
-
width: "100%",
|
|
174
|
-
maxWidth: "chat.bubble",
|
|
175
|
-
paddingBlock: "control.block",
|
|
176
|
-
paddingInline: "control.compactInline",
|
|
177
|
-
border: "1px solid token(colors.border.control)",
|
|
178
|
-
borderRadius: "field",
|
|
179
|
-
background: "bg.card",
|
|
180
|
-
fontSize: "caption",
|
|
181
|
-
lineHeight: "log",
|
|
182
|
-
});
|
|
183
|
-
const cardLabel = css({ color: "fg.muted", fontSize: "code", lineHeight: "compact" });
|
|
184
|
-
const argumentList = css({
|
|
185
|
-
display: "grid",
|
|
186
|
-
gridTemplateColumns: "auto minmax(0, 1fr)",
|
|
187
|
-
columnGap: "layout.controlGap",
|
|
188
|
-
margin: "none",
|
|
189
|
-
fontSize: "code",
|
|
190
|
-
lineHeight: "compact",
|
|
191
|
-
"& > dt": { color: "fg.muted", whiteSpace: "nowrap" },
|
|
192
|
-
"& > dd": { margin: "none", overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" },
|
|
193
|
-
});
|
|
194
|
-
const cardActions = css({ display: "flex", justifyContent: "flex-end", gap: "layout.controlGap" });
|
|
195
|
-
// Option rows run to the card's edges, split by its hairlines.
|
|
196
|
-
const optionList = css({
|
|
197
|
-
marginInline: "calc(token(spacing.control.compactInline) * -1)",
|
|
198
|
-
marginBlockEnd: "calc(token(spacing.control.block) * -1)",
|
|
199
|
-
"& > button": {
|
|
200
|
-
display: "block",
|
|
201
|
-
width: "100%",
|
|
202
|
-
paddingBlock: "control.compactBlock",
|
|
203
|
-
paddingInline: "control.compactInline",
|
|
204
|
-
border: "0",
|
|
205
|
-
borderTop: "1px solid token(colors.border.control)",
|
|
206
|
-
background: "transparent",
|
|
207
|
-
color: "fg.primary",
|
|
208
|
-
font: "inherit",
|
|
209
|
-
textAlign: "start",
|
|
210
|
-
cursor: "pointer",
|
|
211
|
-
_hover: { background: "bg.hover" },
|
|
212
|
-
_focusVisible: { focusRing: "inset" },
|
|
213
|
-
"&:last-child": { borderBottomLeftRadius: "field", borderBottomRightRadius: "field" },
|
|
214
|
-
},
|
|
215
|
-
});
|
|
216
|
-
const optionDetail = css({ display: "block", color: "fg.muted", fontSize: "code" });
|
|
175
|
+
const centered = css({ display: "grid", placeItems: "center", height: "100%" });
|
|
217
176
|
/* The thread and composer, the drop target for files; it fills what the sheet or page gives it. */
|
|
218
177
|
const conversationRoot = css({
|
|
219
178
|
display: "flex",
|
|
@@ -225,44 +184,44 @@ const composerFooter = css({
|
|
|
225
184
|
display: "grid",
|
|
226
185
|
gap: "layout.tightGap",
|
|
227
186
|
flexShrink: "0",
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
borderTop: "
|
|
231
|
-
"&[data-layout=wide]": { borderTop: "0", paddingBlockEnd: "panel.default" },
|
|
187
|
+
padding: "panel.comfortable",
|
|
188
|
+
borderTop: "1px solid token(colors.border.divider)",
|
|
189
|
+
"&[data-layout=wide]": { borderTop: "0" },
|
|
232
190
|
});
|
|
191
|
+
/* Over the composer, what goes with it: an error. */
|
|
192
|
+
const footerStack = css({ display: "grid", gap: "layout.tightGap", minWidth: "0" });
|
|
233
193
|
/* Full page: the thread and composer in one readable column down the middle. */
|
|
234
194
|
const wideColumn = css({ width: "100%", maxWidth: "chat.thread", marginInline: "auto" });
|
|
235
|
-
const hiddenInput = css({ display: "none" });
|
|
236
195
|
/* In a drawer, the context row and the conversation stack under its header. */
|
|
237
196
|
const drawerStack = css({ display: "flex", flex: "1", minHeight: "0", flexDirection: "column" });
|
|
238
|
-
|
|
239
|
-
const maximumArgumentRows = 3;
|
|
240
|
-
export function ChatSheet({ open, onOpenChange, title, context, placement = "drawer", onPlacementChange, onOpenFullPage, ...conversationProps }) {
|
|
197
|
+
export function ChatSheet({ open, onOpenChange, title, context, placement = "drawer", onPlacementChange, onOpenFullPage, shortcut, ...conversationProps }) {
|
|
241
198
|
const { variant = "agent", tasks } = conversationProps;
|
|
242
199
|
const { labels } = useCiridaeLocale();
|
|
243
200
|
const asideRef = useRef(null);
|
|
244
201
|
const dockedWidth = useDockedWidth(asideRef);
|
|
245
202
|
const titleContent = (_jsxs("span", { className: titleLine, children: [_jsx(SparkleIcon, { className: icon({ size: "small" }), weight: "fill" }), title] }));
|
|
246
|
-
const actions = (_jsxs(_Fragment, { children: [onOpenFullPage && (_jsx(IconButton, { "aria-label": labels.chat.openFullPage, variant: "ghost", size: "small", onClick: onOpenFullPage, children: _jsx(ArrowsOutSimpleIcon, { className: icon({ size: "small" }) }) })), onPlacementChange &&
|
|
203
|
+
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 &&
|
|
247
204
|
(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 }) })))] }));
|
|
248
|
-
const contextContent = (_jsxs(_Fragment, { children: [context && context.length > 0 && (_jsxs("div", { className: contextRow, children: [
|
|
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 })] }));
|
|
249
206
|
if (placement === "docked") {
|
|
250
207
|
if (!open)
|
|
251
208
|
return null;
|
|
252
|
-
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(), css({ lineHeight: "tight" })), children: titleContent }), _jsxs("div", { className: headerActions, children: [actions, _jsx(IconButton, { "aria-label": labels.common.close,
|
|
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 })] })] }));
|
|
253
210
|
}
|
|
254
211
|
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 })] }) }));
|
|
255
212
|
}
|
|
256
213
|
/* The files for the next message: each uploads as it is added; send waits until all have. */
|
|
257
|
-
function useComposerFiles(
|
|
214
|
+
function useComposerFiles(upload, onUploadError) {
|
|
258
215
|
const [entries, setEntries] = useState([]);
|
|
259
216
|
const remove = (file) => setEntries((existing) => existing.filter((entry) => entry.file !== file));
|
|
260
217
|
const add = (files) => {
|
|
218
|
+
if (!upload)
|
|
219
|
+
return;
|
|
261
220
|
setEntries((existing) => [...existing, ...files.map((file) => ({ file, attachment: null }))]);
|
|
262
221
|
for (const file of files) {
|
|
263
|
-
|
|
222
|
+
upload(file).then((attachment) => setEntries((existing) => existing.map((entry) => (entry.file === file ? { file, attachment } : entry))), (error) => {
|
|
264
223
|
remove(file);
|
|
265
|
-
|
|
224
|
+
onUploadError(error);
|
|
266
225
|
});
|
|
267
226
|
}
|
|
268
227
|
};
|
|
@@ -279,54 +238,53 @@ function useComposerFiles(client) {
|
|
|
279
238
|
* The thread and the composer: everything that reads or writes the conversation. `layout="wide"`
|
|
280
239
|
* sets both in one readable column for a full page; the sheet uses `sheet`.
|
|
281
240
|
*/
|
|
282
|
-
export function ChatConversation({
|
|
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", }) {
|
|
283
242
|
const { labels } = useCiridaeLocale();
|
|
284
243
|
const assistantName = ownAssistantName ?? labels.chat.assistantName;
|
|
285
|
-
const
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
const [
|
|
292
|
-
const
|
|
293
|
-
const
|
|
294
|
-
|
|
244
|
+
const [ownDraft, setOwnDraft] = useState("");
|
|
245
|
+
const currentDraft = draft ?? ownDraft;
|
|
246
|
+
const setDraft = (next) => {
|
|
247
|
+
setOwnDraft(next);
|
|
248
|
+
onDraftChange?.(next);
|
|
249
|
+
};
|
|
250
|
+
const [sending, setSending] = useState(false);
|
|
251
|
+
const [composerError, setComposerError] = useState(null);
|
|
252
|
+
const showError = (error) => setComposerError(error instanceof Error ? error.message : String(error));
|
|
253
|
+
const files = useComposerFiles(source.upload, showError);
|
|
254
|
+
const send = async (text) => {
|
|
255
|
+
setSending(true);
|
|
256
|
+
setComposerError(null);
|
|
295
257
|
try {
|
|
296
|
-
await
|
|
258
|
+
await source.send({ text, attachments: files.attachments });
|
|
259
|
+
files.clear();
|
|
297
260
|
}
|
|
298
261
|
catch (error) {
|
|
299
|
-
|
|
262
|
+
// The composer cleared as it sent: the text comes back, with the files still waiting.
|
|
263
|
+
setDraft(text);
|
|
264
|
+
showError(error);
|
|
265
|
+
}
|
|
266
|
+
finally {
|
|
267
|
+
setSending(false);
|
|
300
268
|
}
|
|
301
269
|
};
|
|
302
|
-
const renderReply = (content, plain) => (_jsx(MessageText, { content: content, plain: plain, onMentionSelect: onMentionSelect, mentionHref: mentionHref }));
|
|
303
270
|
const entries = buildThread({
|
|
304
271
|
labels: labels.chat,
|
|
305
|
-
|
|
306
|
-
pendingMessages: state.pendingMessages,
|
|
307
|
-
response: state.response,
|
|
308
|
-
isResponseActive: state.isResponseActive,
|
|
272
|
+
source,
|
|
309
273
|
events,
|
|
310
274
|
tasks: variant === "agent" ? (tasks ?? []) : [],
|
|
311
|
-
|
|
312
|
-
renderReply,
|
|
275
|
+
renderReply: (content, plain) => (_jsx(MessageText, { content: content, plain: plain, onMentionSelect: onMentionSelect, mentionHref: mentionHref })),
|
|
313
276
|
replyActions: copyReplies ? (content) => _jsx(CopyReplyButton, { content: content }) : undefined,
|
|
314
|
-
|
|
315
|
-
retryPending: (message) => {
|
|
316
|
-
state.discardPendingMessage(message.localId);
|
|
317
|
-
void send(message.content, message.attachments);
|
|
318
|
-
},
|
|
319
|
-
retryMessage: (message) => void send(message.content, message.attachments),
|
|
277
|
+
taskCard: (task) => _jsx(AgentTaskCard, { task: task, onReply: onTaskReply }),
|
|
320
278
|
});
|
|
321
279
|
const wide = layout === "wide";
|
|
322
|
-
const showSuggestions = entries.length === 0 && !
|
|
323
|
-
return (_jsxs(Dropzone, { onFiles: files.add, className: conversationRoot, children: [_jsx(ScrollBody, { className: threadScroll, children: showSuggestions ? (_jsx(ChatSuggestions, { className: cx(wide && wideColumn), suggestions: suggestions, onSelect: (suggestion) => void send(suggestion
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
280
|
+
const showSuggestions = entries.length === 0 && !source.loading && suggestions !== undefined && suggestions.length > 0;
|
|
281
|
+
return (_jsxs(Dropzone, { onFiles: files.add, disabled: !source.upload, className: conversationRoot, children: [_jsx(ScrollBody, { className: threadScroll, children: source.loading && entries.length === 0 ? (_jsx("div", { className: centered, children: _jsx(Spinner, { label: labels.common.loading }) })) : showSuggestions ? (_jsx(ChatSuggestions, { className: cx(wide && wideColumn), suggestions: suggestions, onSelect: (suggestion) => void send(suggestion) })) : (_jsx(ChatThread, { className: cx(wide && wideColumn), entries: entries, replyStyle: "plain", attachmentTiles: true, dayLabels: true, resolveAttachmentUrl: source.resolveAttachmentUrl, assistant: { name: assistantName, kind: "agent" }, renderCustom: (content) => content })) }), _jsx("div", { className: composerFooter, "data-layout": layout, children: _jsxs("div", { className: cx(footerStack, wide && wideColumn), children: [composerError && (_jsx(ChatSystemLine, { tone: "error", role: "alert", children: composerError })), _jsx(ChatComposer, { variant: "sheet", placeholder: placeholder, autoFocus: true, value: currentDraft, onValueChange: setDraft, inputProps: { ref: composerRef }, mentions: mentions, mentionMark: mentionMark, sendDisabled: files.uploading || sending, sendsWithoutText: files.attachments.length > 0, onPasteFiles: source.upload ? files.add : undefined, attachments: files.files.length > 0 && (_jsx(DropzoneFileList, { variant: "tiles", files: files.files, pending: files.uploading, onRemove: files.remove })), onSend: (content) => void send(content), tools: _jsxs(_Fragment, { children: [source.upload && _jsx(AttachFilesButton, {}), composerTools] }), dictation: dictation })] }) })] }));
|
|
282
|
+
}
|
|
283
|
+
/** The paperclip: the enclosing `Dropzone`'s file picker. */
|
|
284
|
+
function AttachFilesButton() {
|
|
285
|
+
const { labels } = useCiridaeLocale();
|
|
286
|
+
const picker = useDropzonePicker();
|
|
287
|
+
return (_jsx(ChatComposerTool, { "aria-label": labels.chat.attachFile, disabled: picker.disabled, onClick: picker.openPicker, children: _jsx(PaperclipIcon, { className: icon({ size: "small" }) }) }));
|
|
330
288
|
}
|
|
331
289
|
/** Copies a reply's text, its mentions as their labels, and shows a tick for a moment. */
|
|
332
290
|
function CopyReplyButton({ content }) {
|
|
@@ -334,88 +292,37 @@ function CopyReplyButton({ content }) {
|
|
|
334
292
|
.map((part) => (typeof part === "string" ? part : part.label))
|
|
335
293
|
.join("") }));
|
|
336
294
|
}
|
|
337
|
-
/**
|
|
338
|
-
|
|
295
|
+
/**
|
|
296
|
+
* The source's items, the page's events, the agent's tasks and how the latest turn stands, in one
|
|
297
|
+
* timeline: each run of the agent's steps folded into one entry, and at the latest turn's foot its
|
|
298
|
+
* thinking, failure or stop.
|
|
299
|
+
*/
|
|
300
|
+
function buildThread({ labels, source: { items, status, failureMessage, retry }, events, tasks, renderReply, replyActions, taskCard, }) {
|
|
339
301
|
const entries = [];
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
})
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
const { sources } = splitSources(content);
|
|
355
|
-
entries.push({
|
|
356
|
-
kind: "message",
|
|
357
|
-
id: item.id,
|
|
358
|
-
side: item.role,
|
|
359
|
-
at,
|
|
360
|
-
// Files sent alone keep an empty body, so the thread draws their tiles with no bubble.
|
|
361
|
-
body: content.trim() ? renderReply(content, item.role === "user") : "",
|
|
362
|
-
attachments: toAttachments(item.attachments),
|
|
363
|
-
sources: item.role === "assistant"
|
|
364
|
-
? sources.map((source) => ({
|
|
365
|
-
id: source.id,
|
|
366
|
-
label: source.label,
|
|
367
|
-
href: source.href,
|
|
368
|
-
}))
|
|
369
|
-
: undefined,
|
|
370
|
-
actions: item.role === "assistant" && !turnUnfinished ? replyActions?.(content) : undefined,
|
|
371
|
-
});
|
|
372
|
-
break;
|
|
373
|
-
}
|
|
374
|
-
case "approval_request":
|
|
375
|
-
if (item.decision === null && waitingOnUser) {
|
|
376
|
-
entries.push({
|
|
377
|
-
kind: "custom",
|
|
378
|
-
id: item.id,
|
|
379
|
-
side: "assistant",
|
|
380
|
-
at,
|
|
381
|
-
data: { kind: "approval", item },
|
|
382
|
-
});
|
|
383
|
-
}
|
|
384
|
-
else if (item.decision !== null) {
|
|
385
|
-
entries.push({
|
|
386
|
-
kind: "line",
|
|
387
|
-
id: item.id,
|
|
388
|
-
at,
|
|
389
|
-
text: `${describeApprovalRequest(item.tool)} · ${item.decision === "allow" ? "allowed" : "denied"}`,
|
|
390
|
-
});
|
|
391
|
-
}
|
|
392
|
-
break;
|
|
393
|
-
case "question":
|
|
394
|
-
if (item.answers === null && waitingOnUser) {
|
|
395
|
-
entries.push({
|
|
396
|
-
kind: "custom",
|
|
397
|
-
id: item.id,
|
|
398
|
-
side: "assistant",
|
|
399
|
-
at,
|
|
400
|
-
data: { kind: "question", item },
|
|
401
|
-
});
|
|
402
|
-
}
|
|
403
|
-
else if (item.answers !== null) {
|
|
404
|
-
entries.push({
|
|
405
|
-
kind: "line",
|
|
406
|
-
id: item.id,
|
|
407
|
-
at,
|
|
408
|
-
text: labels.answered(item.answers.flat().filter(Boolean).join(", ")),
|
|
409
|
-
});
|
|
410
|
-
}
|
|
411
|
-
break;
|
|
412
|
-
// What the agent runs on its way to a reply stays behind its typing.
|
|
413
|
-
case "function_call":
|
|
414
|
-
case "function_call_output":
|
|
415
|
-
case "reasoning":
|
|
416
|
-
break;
|
|
302
|
+
// The latest turn is everything after the user's last message; its replies are done once it is.
|
|
303
|
+
let latestTurnStart = -1;
|
|
304
|
+
items.forEach((item, index) => {
|
|
305
|
+
if (item.kind === "message" && item.role === "user")
|
|
306
|
+
latestTurnStart = index;
|
|
307
|
+
});
|
|
308
|
+
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
|
+
let stepRun = null;
|
|
311
|
+
for (const [index, item] of items.entries()) {
|
|
312
|
+
if (item.kind === "step") {
|
|
313
|
+
stepRun ?? (stepRun = { id: `steps-${item.id}`, at: item.at, steps: [] });
|
|
314
|
+
stepRun.steps.push({ ...item.step, id: item.id });
|
|
315
|
+
continue;
|
|
417
316
|
}
|
|
317
|
+
if (stepRun)
|
|
318
|
+
entries.push({ kind: "steps", ...stepRun });
|
|
319
|
+
stepRun = null;
|
|
320
|
+
const inUnfinishedTurn = latestTurnUnfinished && index > latestTurnStart;
|
|
321
|
+
entries.push(...toEntries(item, inUnfinishedTurn));
|
|
418
322
|
}
|
|
323
|
+
const runningSteps = status === "running" ? stepRun : null;
|
|
324
|
+
if (stepRun && !runningSteps)
|
|
325
|
+
entries.push({ kind: "steps", ...stepRun });
|
|
419
326
|
for (const event of events) {
|
|
420
327
|
entries.push({
|
|
421
328
|
kind: "line",
|
|
@@ -432,7 +339,7 @@ function buildThread({ items, pendingMessages, response, isResponseActive, event
|
|
|
432
339
|
id: `task-${task.id}`,
|
|
433
340
|
side: "assistant",
|
|
434
341
|
at: Date.parse(task.ask.askedAt),
|
|
435
|
-
data:
|
|
342
|
+
data: taskCard(task),
|
|
436
343
|
});
|
|
437
344
|
}
|
|
438
345
|
if ((task.status === "done" || task.status === "failed") && task.summary) {
|
|
@@ -447,98 +354,90 @@ function buildThread({ items, pendingMessages, response, isResponseActive, event
|
|
|
447
354
|
}
|
|
448
355
|
}
|
|
449
356
|
entries.sort((first, second) => first.at - second.at);
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
357
|
+
const lastItem = items[items.length - 1];
|
|
358
|
+
const at = Math.max(lastItem?.at ?? 0, entries[entries.length - 1]?.at ?? 0) || Date.now();
|
|
359
|
+
const newestStep = runningSteps?.steps[runningSteps.steps.length - 1];
|
|
360
|
+
appendTurnEnding(entries, {
|
|
361
|
+
status,
|
|
362
|
+
retry,
|
|
363
|
+
failureMessage,
|
|
364
|
+
lastItem,
|
|
365
|
+
at,
|
|
366
|
+
labels,
|
|
367
|
+
doing: newestStep && `${newestStep.title}…`,
|
|
368
|
+
});
|
|
369
|
+
return entries;
|
|
370
|
+
function toEntries(item, inUnfinishedTurn) {
|
|
371
|
+
switch (item.kind) {
|
|
372
|
+
case "message": {
|
|
373
|
+
const assistant = item.role === "assistant";
|
|
374
|
+
const message = {
|
|
375
|
+
kind: "message",
|
|
376
|
+
id: item.id,
|
|
377
|
+
side: item.role,
|
|
378
|
+
at: item.at,
|
|
379
|
+
// Files sent alone keep an empty body, so the thread draws their tiles with no bubble.
|
|
380
|
+
body: item.text.trim() ? renderReply(item.text, !assistant) : "",
|
|
381
|
+
attachments: item.attachments,
|
|
382
|
+
sending: item.sending,
|
|
383
|
+
sources: assistant
|
|
384
|
+
? [
|
|
385
|
+
...splitSources(item.text).sources.map((source) => ({
|
|
386
|
+
id: source.id,
|
|
387
|
+
label: source.label,
|
|
388
|
+
href: source.href,
|
|
389
|
+
})),
|
|
390
|
+
...(item.sources ?? []),
|
|
391
|
+
]
|
|
392
|
+
: undefined,
|
|
393
|
+
actions: assistant && !inUnfinishedTurn ? replyActions?.(item.text) : undefined,
|
|
394
|
+
suggestedActions: item.suggestedActions,
|
|
395
|
+
footnote: item.footnote,
|
|
396
|
+
};
|
|
397
|
+
if (!item.failure)
|
|
398
|
+
return [message];
|
|
399
|
+
return [
|
|
400
|
+
message,
|
|
401
|
+
{
|
|
402
|
+
kind: "failure",
|
|
403
|
+
id: `failure-${item.id}`,
|
|
404
|
+
at: item.at,
|
|
405
|
+
title: labels.notSent,
|
|
406
|
+
message: item.failure.message,
|
|
407
|
+
onRetry: item.failure.retry,
|
|
408
|
+
},
|
|
409
|
+
];
|
|
410
|
+
}
|
|
411
|
+
case "line":
|
|
412
|
+
return [{ kind: "line", id: item.id, at: item.at, text: item.text, tone: item.tone }];
|
|
413
|
+
case "card":
|
|
414
|
+
return [{ kind: "custom", id: item.id, side: "assistant", at: item.at, data: item.content }];
|
|
492
415
|
}
|
|
493
416
|
}
|
|
494
|
-
const last = entries[entries.length - 1];
|
|
495
|
-
if (isResponseActive &&
|
|
496
|
-
response?.status !== "requires_action" &&
|
|
497
|
-
!(last?.kind === "message" && last.side === "assistant")) {
|
|
498
|
-
entries.push({ kind: "typing", id: "typing", at: last?.at ?? Date.now() });
|
|
499
|
-
}
|
|
500
|
-
return entries;
|
|
501
417
|
}
|
|
502
|
-
/**
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
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
|
+
function appendTurnEnding(entries, { status, retry, failureMessage, lastItem, at, labels, doing, }) {
|
|
423
|
+
switch (status) {
|
|
424
|
+
case "running":
|
|
425
|
+
// Once its reply or a card waiting on the user is there, the agent is no longer thinking.
|
|
426
|
+
if (lastItem?.kind !== "card" &&
|
|
427
|
+
!(lastItem?.kind === "message" && lastItem.role === "assistant"))
|
|
428
|
+
entries.push({ kind: "typing", id: "typing", at, label: doing });
|
|
429
|
+
return;
|
|
430
|
+
case "failed": {
|
|
431
|
+
const message = failureMessage ?? labels.failed;
|
|
432
|
+
entries.push(retry
|
|
433
|
+
? { kind: "failure", id: "turn-failure", at, message, onRetry: retry }
|
|
434
|
+
: { kind: "line", id: "turn-failure", at, text: message, tone: "error" });
|
|
435
|
+
return;
|
|
519
436
|
}
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
return (item.type === "message" &&
|
|
526
|
-
item.role === "user" &&
|
|
527
|
-
item.content === message.content &&
|
|
528
|
-
Date.parse(item.created_at) >= message.sentAt - 60000);
|
|
529
|
-
}
|
|
530
|
-
/** A call's JSON arguments as name and value pairs, values shortened to one line. */
|
|
531
|
-
function listArgumentRows(argumentsJson) {
|
|
532
|
-
try {
|
|
533
|
-
const parsed = JSON.parse(argumentsJson);
|
|
534
|
-
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed))
|
|
535
|
-
return [];
|
|
536
|
-
return Object.entries(parsed).map(([name, value]) => [
|
|
537
|
-
name.replace(/[_-]+/g, " "),
|
|
538
|
-
typeof value === "string" ? value : JSON.stringify(value),
|
|
539
|
-
]);
|
|
540
|
-
}
|
|
541
|
-
catch {
|
|
542
|
-
return [];
|
|
437
|
+
case "stopped":
|
|
438
|
+
entries.push({ kind: "line", id: "turn-stopped", at, text: labels.stopped });
|
|
439
|
+
return;
|
|
440
|
+
case "idle":
|
|
441
|
+
return;
|
|
543
442
|
}
|
|
544
443
|
}
|