@ciridae/ui 1.0.3 → 1.0.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chat/conversation-source.d.ts +15 -1
- package/dist/chat/describe-tool-call.d.ts +3 -3
- package/dist/chat/describe-tool-call.js +3 -3
- package/dist/chat/message-text.js +2 -2
- package/dist/ciridae.css +16 -4
- package/dist/components/activity-feed.d.ts +1 -0
- package/dist/components/activity-feed.js +1 -0
- package/dist/components/app-frame.d.ts +13 -1
- package/dist/components/app-frame.js +45 -3
- package/dist/components/app-navigation.d.ts +28 -1
- package/dist/components/app-navigation.js +105 -14
- package/dist/components/badge.d.ts +29 -4
- package/dist/components/badge.js +134 -15
- package/dist/components/bulk-action-bar.js +3 -3
- package/dist/components/button.d.ts +10 -3
- package/dist/components/button.js +77 -44
- package/dist/components/calendar.js +44 -17
- package/dist/components/centered-page.js +2 -2
- package/dist/components/chart.d.ts +1 -1
- package/dist/components/chart.js +16 -11
- package/dist/components/chat-client-source.js +47 -6
- package/dist/components/chat-sheet-controller.d.ts +1 -0
- package/dist/components/chat-sheet-controller.js +1 -0
- package/dist/components/chat-sheet.d.ts +10 -2
- package/dist/components/chat-sheet.js +76 -39
- package/dist/components/chat.d.ts +90 -14
- package/dist/components/chat.js +402 -101
- package/dist/components/checkbox-item.d.ts +5 -2
- package/dist/components/checkbox-item.js +9 -5
- package/dist/components/chip.d.ts +5 -0
- package/dist/components/chip.js +27 -1
- package/dist/components/column-settings.js +1 -1
- package/dist/components/command-menu.d.ts +5 -1
- package/dist/components/command-menu.js +98 -32
- package/dist/components/command-palette.d.ts +6 -2
- package/dist/components/command-palette.js +20 -10
- package/dist/components/data-display.d.ts +35 -4
- package/dist/components/data-display.js +138 -19
- package/dist/components/data-grid.d.ts +14 -2
- package/dist/components/data-grid.js +61 -26
- package/dist/components/date-range-picker.js +9 -20
- package/dist/components/drawer.d.ts +5 -0
- package/dist/components/drawer.js +91 -54
- package/dist/components/editable-field.d.ts +12 -3
- package/dist/components/editable-field.js +57 -27
- package/dist/components/email-input.js +6 -5
- package/dist/components/environment-frame.d.ts +1 -1
- package/dist/components/environment-frame.js +37 -6
- package/dist/components/filter-bar.js +39 -28
- package/dist/components/form-controls.d.ts +9 -0
- package/dist/components/form-controls.js +46 -22
- package/dist/components/form-layout.d.ts +7 -3
- package/dist/components/form-layout.js +68 -23
- package/dist/components/glass-menu.d.ts +3 -0
- package/dist/components/glass-menu.js +63 -0
- package/dist/components/gradient-edge.d.ts +9 -0
- package/dist/components/gradient-edge.js +31 -0
- package/dist/components/input.d.ts +4 -2
- package/dist/components/input.js +26 -15
- package/dist/components/keycap.d.ts +9 -5
- package/dist/components/keycap.js +57 -19
- package/dist/components/lightbox.js +2 -1
- package/dist/components/locale.d.ts +10 -0
- package/dist/components/locale.js +18 -2
- package/dist/components/log-entry.d.ts +40 -0
- package/dist/components/log-entry.js +146 -0
- package/dist/components/mention-input.js +4 -3
- package/dist/components/menu.d.ts +6 -2
- package/dist/components/menu.js +34 -37
- package/dist/components/meter.d.ts +9 -1
- package/dist/components/meter.js +64 -6
- package/dist/components/navigation.d.ts +21 -4
- package/dist/components/navigation.js +84 -19
- package/dist/components/notification-tray.js +5 -4
- package/dist/components/overlays.d.ts +3 -1
- package/dist/components/overlays.js +69 -40
- package/dist/components/page-frame.js +1 -1
- package/dist/components/panel.d.ts +1 -1
- package/dist/components/panel.js +18 -12
- package/dist/components/radio-card.js +10 -9
- package/dist/components/record.d.ts +25 -1
- package/dist/components/record.js +147 -18
- package/dist/components/segmented-bar.d.ts +3 -1
- package/dist/components/segmented-bar.js +16 -6
- package/dist/components/select.d.ts +5 -2
- package/dist/components/select.js +103 -127
- package/dist/components/shell-skeleton.js +1 -1
- package/dist/components/shell.d.ts +2 -2
- package/dist/components/shell.js +43 -45
- package/dist/components/sidebar.d.ts +7 -2
- package/dist/components/sidebar.js +55 -27
- package/dist/components/single-line-text.d.ts +2 -0
- package/dist/components/single-line-text.js +16 -0
- package/dist/components/site-banner.d.ts +5 -2
- package/dist/components/site-banner.js +17 -6
- package/dist/components/skeleton.d.ts +7 -6
- package/dist/components/skeleton.js +28 -16
- package/dist/components/skeletons.d.ts +12 -0
- package/dist/components/skeletons.js +71 -8
- package/dist/components/stages.d.ts +78 -6
- package/dist/components/stages.js +512 -156
- package/dist/components/stat-card.d.ts +10 -2
- package/dist/components/stat-card.js +92 -37
- package/dist/components/status-banner.js +1 -1
- package/dist/components/tabs.js +26 -13
- package/dist/components/theme-toggle.d.ts +4 -0
- package/dist/components/theme-toggle.js +67 -0
- package/dist/components/toast.d.ts +9 -2
- package/dist/components/toast.js +78 -29
- package/dist/preset.js +289 -33
- package/dist/styled-system/styles.css +3115 -1044
- package/dist/styled-system/tokens/index.mjs +598 -22
- package/dist/styled-system/tokens/tokens.d.ts +11 -11
- package/dist/styled-system/types/prop-type.d.ts +2 -2
- package/dist/styles.css +16 -4
- package/dist/theme/color.d.ts +3 -0
- package/dist/theme/color.js +5 -0
- package/dist/theme/derive.d.ts +9 -2
- package/dist/theme/derive.js +72 -12
- package/dist/theme/palettes.d.ts +49 -6
- package/dist/theme/palettes.js +84 -15
- package/dist/theme/tokens.d.ts +3 -3
- package/dist/theme/tokens.js +107 -32
- package/package.json +9 -1
|
@@ -28,7 +28,7 @@ export declare function TimeSeriesChart({ series, start, end, formatValue, forma
|
|
|
28
28
|
}): import("react").JSX.Element;
|
|
29
29
|
/**
|
|
30
30
|
* Values by category as bars, side by side per series, or as lines, such as counts per month.
|
|
31
|
-
* Each series takes the next
|
|
31
|
+
* Each series takes the next chart colour; a legend names them once there is more than one.
|
|
32
32
|
*/
|
|
33
33
|
export declare function CategoryChart({ categories, series, variant, formatValue, height, allowDecimals, className, ...props }: ComponentProps<"div"> & {
|
|
34
34
|
categories: readonly string[];
|
package/dist/components/chart.js
CHANGED
|
@@ -20,25 +20,30 @@ const frame = css({
|
|
|
20
20
|
color: "fg.muted",
|
|
21
21
|
fontFamily: "numeric",
|
|
22
22
|
fontSize: "caption",
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
"--series-
|
|
26
|
-
"--series-
|
|
27
|
-
"--series-
|
|
23
|
+
// The series that matters is the trend's blue, a bar the lighter bar blue; the rest take the
|
|
24
|
+
// chart's four categories in their fixed order.
|
|
25
|
+
"--series-0": "token(colors.chart.trend)",
|
|
26
|
+
"--series-1": "token(colors.chart.category1)",
|
|
27
|
+
"--series-2": "token(colors.chart.category2)",
|
|
28
|
+
"--series-3": "token(colors.chart.category3)",
|
|
29
|
+
"--series-4": "token(colors.chart.category4)",
|
|
30
|
+
"&[data-variant=bar]": { "--series-0": "token(colors.chart.bar)" },
|
|
28
31
|
"--chart-grid": "token(colors.border.subtle)",
|
|
29
32
|
"--chart-muted": "token(colors.fg.muted)",
|
|
30
33
|
"--chart-cursor": "token(colors.bg.hover)",
|
|
31
34
|
"& .ts-chart-host": { overflow: "visible" },
|
|
32
35
|
});
|
|
33
|
-
// The tooltip's own React body replaces the chart's default chrome, so it draws the
|
|
36
|
+
// The tooltip's own React body replaces the chart's default chrome, so it draws the popover's
|
|
37
|
+
// glass itself.
|
|
34
38
|
const tooltipBody = css({
|
|
35
39
|
display: "grid",
|
|
36
40
|
gap: "control.trackInset",
|
|
37
41
|
padding: "panel.compact",
|
|
38
42
|
border: "1px solid token(colors.border.control)",
|
|
39
43
|
borderRadius: "action",
|
|
40
|
-
background: "bg.
|
|
41
|
-
|
|
44
|
+
background: "bg.popover",
|
|
45
|
+
backdropFilter: "blur(token(blurs.popover))",
|
|
46
|
+
boxShadow: "soft",
|
|
42
47
|
color: "fg.primary",
|
|
43
48
|
fontFamily: "sans",
|
|
44
49
|
fontSize: "caption",
|
|
@@ -185,7 +190,7 @@ export function TimeSeriesChart({ series, start, end, formatValue, formatTimesta
|
|
|
185
190
|
}
|
|
186
191
|
/**
|
|
187
192
|
* Values by category as bars, side by side per series, or as lines, such as counts per month.
|
|
188
|
-
* Each series takes the next
|
|
193
|
+
* Each series takes the next chart colour; a legend names them once there is more than one.
|
|
189
194
|
*/
|
|
190
195
|
export function CategoryChart({ categories, series, variant = "bar", formatValue = String, height = 176, allowDecimals = true, className, ...props }) {
|
|
191
196
|
const definition = useMemo(() => {
|
|
@@ -257,9 +262,9 @@ export function CategoryChart({ categories, series, variant = "bar", formatValue
|
|
|
257
262
|
tooltip: seriesTooltip(series, formatValue, String),
|
|
258
263
|
});
|
|
259
264
|
}, [allowDecimals, categories, formatValue, series, variant]);
|
|
260
|
-
return (_jsxs("div", { ...props, className: cx(frame, className), "data-ciridae-ui": "", children: [_jsx(Chart, { ariaLabel: series.map((item) => item.label).join(", "), definition: definition, height: height, renderTooltipBody: ({ points, content }) => (_jsx(SeriesTooltip, { content: content, points: points, series: series, formatValue: formatValue })) }), _jsx(SeriesLegend, { series: series })] }));
|
|
265
|
+
return (_jsxs("div", { ...props, className: cx(frame, className), "data-ciridae-ui": "", "data-variant": variant, children: [_jsx(Chart, { ariaLabel: series.map((item) => item.label).join(", "), definition: definition, height: height, renderTooltipBody: ({ points, content }) => (_jsx(SeriesTooltip, { content: content, points: points, series: series, formatValue: formatValue })) }), _jsx(SeriesLegend, { series: series })] }));
|
|
261
266
|
}
|
|
262
|
-
/** Each series index to its
|
|
267
|
+
/** Each series index to its chart colour, in order. */
|
|
263
268
|
function seriesColorScale(count) {
|
|
264
269
|
return scaleOrdinal()
|
|
265
270
|
.domain(Array.from({ length: count }, (_, index) => index))
|
|
@@ -6,7 +6,7 @@ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
|
6
6
|
*/
|
|
7
7
|
import { Fragment, useMemo, useRef, useState } from "react";
|
|
8
8
|
import { css } from "#primitives/css";
|
|
9
|
-
import {
|
|
9
|
+
import { describeToolCall } from "../chat/describe-tool-call.js";
|
|
10
10
|
import { useChatConversation } from "../chat/use-chat-conversation.js";
|
|
11
11
|
import { useConversationList } from "../chat/use-conversation-list.js";
|
|
12
12
|
import { Button } from "./button.js";
|
|
@@ -93,10 +93,16 @@ export function useChatClientSource({ client, conversationId, onConversationCrea
|
|
|
93
93
|
? items.filter((item) => item.type === "message" && item.role === "user" && item.response_id === response.id)
|
|
94
94
|
: [];
|
|
95
95
|
const lastUserMessage = failedTurnMessages[failedTurnMessages.length - 1];
|
|
96
|
+
// How each tool call ended, by its call id; a call with none has not returned yet.
|
|
97
|
+
const callStatuses = new Map(items.flatMap((item) => item.type === "function_call_output" ? [[item.call_id, item.status]] : []));
|
|
98
|
+
const lastItem = items[items.length - 1];
|
|
96
99
|
const conversationItems = [];
|
|
97
100
|
for (const item of items) {
|
|
98
101
|
const converted = toConversationItem(item, {
|
|
99
102
|
waitingOnUser: response?.id === item.response_id && response.status === "requires_action",
|
|
103
|
+
inRunningTurn: status === "running" && response?.id === item.response_id,
|
|
104
|
+
newest: item === lastItem,
|
|
105
|
+
callStatuses,
|
|
100
106
|
toChatAttachment,
|
|
101
107
|
answered: labels.chat.answered,
|
|
102
108
|
decided: (decision) => (decision === "allow" ? labels.chat.allowed : labels.chat.denied),
|
|
@@ -158,8 +164,11 @@ export function useChatClientConversations(client) {
|
|
|
158
164
|
}))), [list.data]);
|
|
159
165
|
return { conversations, loading: list.isPending };
|
|
160
166
|
}
|
|
161
|
-
/**
|
|
162
|
-
|
|
167
|
+
/**
|
|
168
|
+
* An item as the thread draws it. A tool call is a step named by its tool, with how it ended and
|
|
169
|
+
* neither its arguments nor its output; a thought is a step named by its first line.
|
|
170
|
+
*/
|
|
171
|
+
function toConversationItem(item, { waitingOnUser, inRunningTurn, newest, callStatuses, toChatAttachment, answered, decided, decideApproval, answerQuestion, }) {
|
|
163
172
|
const at = Date.parse(item.created_at);
|
|
164
173
|
switch (item.type) {
|
|
165
174
|
case "message":
|
|
@@ -177,7 +186,7 @@ function toConversationItem(item, { waitingOnUser, toChatAttachment, answered, d
|
|
|
177
186
|
kind: "line",
|
|
178
187
|
id: item.id,
|
|
179
188
|
at,
|
|
180
|
-
text: `${
|
|
189
|
+
text: `${describeToolCall(item.tool)} · ${decided(item.decision)}`,
|
|
181
190
|
};
|
|
182
191
|
}
|
|
183
192
|
return waitingOnUser
|
|
@@ -206,16 +215,48 @@ function toConversationItem(item, { waitingOnUser, toChatAttachment, answered, d
|
|
|
206
215
|
}
|
|
207
216
|
: null;
|
|
208
217
|
case "function_call":
|
|
218
|
+
// Only the tool's name and how the call ended: its arguments can hold values, such as a
|
|
219
|
+
// credential or a customer's details, that anyone reading the thread should not see.
|
|
220
|
+
return {
|
|
221
|
+
kind: "step",
|
|
222
|
+
id: item.id,
|
|
223
|
+
at,
|
|
224
|
+
step: {
|
|
225
|
+
kind: "tool",
|
|
226
|
+
title: describeToolCall(item.name),
|
|
227
|
+
// A call that never returned from a finished response did not run to its end.
|
|
228
|
+
status: callStatuses.get(item.call_id) ?? (inRunningTurn ? "running" : "failed"),
|
|
229
|
+
},
|
|
230
|
+
};
|
|
209
231
|
case "function_call_output":
|
|
210
|
-
case "reasoning":
|
|
211
232
|
return null;
|
|
233
|
+
case "reasoning": {
|
|
234
|
+
const [title = "", ...rest] = item.content
|
|
235
|
+
.split("\n")
|
|
236
|
+
.map((line) => line.trim())
|
|
237
|
+
.filter(Boolean);
|
|
238
|
+
if (!title)
|
|
239
|
+
return null;
|
|
240
|
+
return {
|
|
241
|
+
kind: "step",
|
|
242
|
+
id: item.id,
|
|
243
|
+
at,
|
|
244
|
+
step: {
|
|
245
|
+
kind: "reasoning",
|
|
246
|
+
// A thought often opens with its gist in bold, as a heading.
|
|
247
|
+
title: title.replace(/^\*\*(.+)\*\*$/, "$1"),
|
|
248
|
+
detail: rest.join(" ") || undefined,
|
|
249
|
+
status: inRunningTurn && newest ? "running" : "completed",
|
|
250
|
+
},
|
|
251
|
+
};
|
|
252
|
+
}
|
|
212
253
|
}
|
|
213
254
|
}
|
|
214
255
|
/** A tool call waiting for the user: what it would do, its arguments, and Deny or Allow. */
|
|
215
256
|
function ApprovalCard({ item, onDecide, }) {
|
|
216
257
|
const { labels } = useCiridaeLocale();
|
|
217
258
|
const rows = listArgumentRows(item.arguments);
|
|
218
|
-
return (_jsxs("div", { className: card, children: [_jsx("span", { children:
|
|
259
|
+
return (_jsxs("div", { className: card, children: [_jsx("span", { children: describeToolCall(item.tool) }), rows.length > 0 && (_jsxs("dl", { className: argumentList, children: [rows.slice(0, maximumArgumentRows).map(([name, value]) => (_jsxs(Fragment, { children: [_jsx("dt", { children: name }), _jsx("dd", { children: value })] }, name))), rows.length > maximumArgumentRows && (_jsxs("dd", { className: cardLabel, children: ["+", rows.length - maximumArgumentRows] }))] })), _jsxs("div", { className: cardActions, children: [_jsx(Button, { variant: "secondary", onClick: () => void onDecide(item, "deny"), children: labels.chat.deny }), _jsx(Button, { onClick: () => void onDecide(item, "allow"), children: labels.chat.allow })] })] }));
|
|
219
260
|
}
|
|
220
261
|
/** The assistant's questions, one option row per answer; picking one answers that question. */
|
|
221
262
|
function QuestionCard({ item, onAnswer, }) {
|
|
@@ -33,6 +33,7 @@ export declare function useChatSheetController({ hotkey, defaultPlacement, docke
|
|
|
33
33
|
placement: ChatSheetPlacement;
|
|
34
34
|
onPlacementChange: ((next: ChatSheetPlacement) => void) | undefined;
|
|
35
35
|
onOpenFullPage: (() => void) | undefined;
|
|
36
|
+
shortcut: HotkeyChord;
|
|
36
37
|
draft: string;
|
|
37
38
|
onDraftChange: import("react").Dispatch<import("react").SetStateAction<string>>;
|
|
38
39
|
composerRef: import("react").RefObject<HTMLDivElement | null>;
|
|
@@ -123,6 +123,7 @@ export function useChatSheetController({ hotkey = "Mod+J", defaultPlacement = "d
|
|
|
123
123
|
placement,
|
|
124
124
|
onPlacementChange: dockable ? changePlacement : undefined,
|
|
125
125
|
onOpenFullPage: openFullPage,
|
|
126
|
+
shortcut: hotkey,
|
|
126
127
|
draft,
|
|
127
128
|
onDraftChange: setDraft,
|
|
128
129
|
composerRef,
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { type ReactNode, type Ref } from "react";
|
|
2
2
|
import { type AgentTask, type AgentTaskReply } from "./agent-work.js";
|
|
3
|
+
import { type ChatDictation } from "./chat.js";
|
|
3
4
|
import { DrawerBody } from "./drawer.js";
|
|
5
|
+
import type { HotkeyChord } from "./hotkeys.js";
|
|
4
6
|
import type { MentionMark, MentionTrigger } from "./mention-input.js";
|
|
5
7
|
import type { Mention } from "../chat/contract.js";
|
|
6
8
|
import type { ChatConversationSource } from "../chat/conversation-source.js";
|
|
@@ -53,6 +55,10 @@ export type ChatConversationProps = {
|
|
|
53
55
|
suggestions?: readonly string[];
|
|
54
56
|
/** A Copy control in the footer of each reply once its turn has finished, and not failed. */
|
|
55
57
|
copyReplies?: boolean;
|
|
58
|
+
/** More tools in the composer's bar after the paperclip, such as a dictation button. */
|
|
59
|
+
composerTools?: ReactNode;
|
|
60
|
+
/** While the host takes dictation, its levels as the composer's waveform; see `ChatComposer`. */
|
|
61
|
+
dictation?: ChatDictation;
|
|
56
62
|
};
|
|
57
63
|
export type ChatSheetProps = ChatConversationProps & {
|
|
58
64
|
open: boolean;
|
|
@@ -66,13 +72,15 @@ export type ChatSheetProps = ChatConversationProps & {
|
|
|
66
72
|
onPlacementChange?: (placement: ChatSheetPlacement) => void;
|
|
67
73
|
/** Shows a header button that moves the conversation to a full page. */
|
|
68
74
|
onOpenFullPage?: () => void;
|
|
75
|
+
/** The hotkey that opens the sheet, shown as keys in its header. */
|
|
76
|
+
shortcut?: HotkeyChord;
|
|
69
77
|
};
|
|
70
|
-
export declare function ChatSheet({ open, onOpenChange, title, context, placement, onPlacementChange, onOpenFullPage, ...conversationProps }: ChatSheetProps): import("react").JSX.Element | null;
|
|
78
|
+
export declare function ChatSheet({ open, onOpenChange, title, context, placement, onPlacementChange, onOpenFullPage, shortcut, ...conversationProps }: ChatSheetProps): import("react").JSX.Element | null;
|
|
71
79
|
/**
|
|
72
80
|
* The thread and the composer: everything that reads or writes the conversation. `layout="wide"`
|
|
73
81
|
* sets both in one readable column for a full page; the sheet uses `sheet`.
|
|
74
82
|
*/
|
|
75
|
-
export declare function ChatConversation({ source, placeholder, draft, onDraftChange, composerRef, mentions, mentionMark, onMentionSelect, mentionHref, events, assistantName: ownAssistantName, suggestions, copyReplies, variant, tasks, onTaskReply, layout, scrollBody: ScrollBody, }: ChatConversationProps & {
|
|
83
|
+
export declare function ChatConversation({ source, placeholder, draft, onDraftChange, composerRef, mentions, mentionMark, onMentionSelect, mentionHref, events, assistantName: ownAssistantName, suggestions, copyReplies, composerTools, dictation, variant, tasks, onTaskReply, layout, scrollBody: ScrollBody, }: ChatConversationProps & {
|
|
76
84
|
layout?: "sheet" | "wide";
|
|
77
85
|
/** The drawer's body inside a `Drawer`, a plain element otherwise. */
|
|
78
86
|
scrollBody?: typeof DrawerBody | "div";
|
|
@@ -20,11 +20,12 @@ import { parseMentions } from "../chat/contract.js";
|
|
|
20
20
|
import { MessageText, splitSources } from "../chat/message-text.js";
|
|
21
21
|
import { AgentTaskCard, AgentWork } from "./agent-work.js";
|
|
22
22
|
import { IconButton } from "./button.js";
|
|
23
|
-
import { ChatComposer, ChatSuggestions, ChatSystemLine, ChatThread, } from "./chat.js";
|
|
23
|
+
import { ChatComposer, ChatComposerTool, ChatSuggestions, ChatSystemLine, ChatThread, } from "./chat.js";
|
|
24
24
|
import { CopyButton } from "./copyable-id.js";
|
|
25
25
|
import { Drawer, DrawerBody } from "./drawer.js";
|
|
26
26
|
import { Dropzone, DropzoneFileList, useDropzonePicker } from "./dropzone.js";
|
|
27
27
|
import { icon, Spinner } from "./icon.js";
|
|
28
|
+
import { ShortcutHint } from "./keycap.js";
|
|
28
29
|
import { heading } from "./layout.js";
|
|
29
30
|
import { useCiridaeLocale } from "./locale.js";
|
|
30
31
|
export { useChatClientConversations, useChatClientSource, } from "./chat-client-source.js";
|
|
@@ -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
|
-
const headerActions = css({ display: "flex", alignItems: "center", gap: "layout.
|
|
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,23 +147,27 @@ 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. */
|
|
@@ -178,10 +184,9 @@ const composerFooter = css({
|
|
|
178
184
|
display: "grid",
|
|
179
185
|
gap: "layout.tightGap",
|
|
180
186
|
flexShrink: "0",
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
borderTop: "
|
|
184
|
-
"&[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" },
|
|
185
190
|
});
|
|
186
191
|
/* Over the composer, what goes with it: an error. */
|
|
187
192
|
const footerStack = css({ display: "grid", gap: "layout.tightGap", minWidth: "0" });
|
|
@@ -189,19 +194,19 @@ const footerStack = css({ display: "grid", gap: "layout.tightGap", minWidth: "0"
|
|
|
189
194
|
const wideColumn = css({ width: "100%", maxWidth: "chat.thread", marginInline: "auto" });
|
|
190
195
|
/* In a drawer, the context row and the conversation stack under its header. */
|
|
191
196
|
const drawerStack = css({ display: "flex", flex: "1", minHeight: "0", flexDirection: "column" });
|
|
192
|
-
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 }) {
|
|
193
198
|
const { variant = "agent", tasks } = conversationProps;
|
|
194
199
|
const { labels } = useCiridaeLocale();
|
|
195
200
|
const asideRef = useRef(null);
|
|
196
201
|
const dockedWidth = useDockedWidth(asideRef);
|
|
197
202
|
const titleContent = (_jsxs("span", { className: titleLine, children: [_jsx(SparkleIcon, { className: icon({ size: "small" }), weight: "fill" }), title] }));
|
|
198
|
-
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 &&
|
|
199
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 }) })))] }));
|
|
200
|
-
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": "", children: [item.icon, item.label] }, item.label)))] })), variant === "agent" && tasks && _jsx(AgentWork, { layout: "summary", tasks: tasks })] }));
|
|
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 })] }));
|
|
201
206
|
if (placement === "docked") {
|
|
202
207
|
if (!open)
|
|
203
208
|
return null;
|
|
204
|
-
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 })] })] }));
|
|
205
210
|
}
|
|
206
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 })] }) }));
|
|
207
212
|
}
|
|
@@ -233,7 +238,7 @@ function useComposerFiles(upload, onUploadError) {
|
|
|
233
238
|
* The thread and the composer: everything that reads or writes the conversation. `layout="wide"`
|
|
234
239
|
* sets both in one readable column for a full page; the sheet uses `sheet`.
|
|
235
240
|
*/
|
|
236
|
-
export function ChatConversation({ source, placeholder, draft, onDraftChange, composerRef, mentions, mentionMark, onMentionSelect, mentionHref, events = [], assistantName: ownAssistantName, suggestions, copyReplies = false, variant = "agent", tasks, onTaskReply, layout = "sheet", scrollBody: ScrollBody = "div", }) {
|
|
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", }) {
|
|
237
242
|
const { labels } = useCiridaeLocale();
|
|
238
243
|
const assistantName = ownAssistantName ?? labels.chat.assistantName;
|
|
239
244
|
const [ownDraft, setOwnDraft] = useState("");
|
|
@@ -273,13 +278,13 @@ export function ChatConversation({ source, placeholder, draft, onDraftChange, co
|
|
|
273
278
|
});
|
|
274
279
|
const wide = layout === "wide";
|
|
275
280
|
const showSuggestions = entries.length === 0 && !source.loading && suggestions !== undefined && suggestions.length > 0;
|
|
276
|
-
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,
|
|
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 })] }) })] }));
|
|
277
282
|
}
|
|
278
283
|
/** The paperclip: the enclosing `Dropzone`'s file picker. */
|
|
279
284
|
function AttachFilesButton() {
|
|
280
285
|
const { labels } = useCiridaeLocale();
|
|
281
286
|
const picker = useDropzonePicker();
|
|
282
|
-
return (_jsx(
|
|
287
|
+
return (_jsx(ChatComposerTool, { "aria-label": labels.chat.attachFile, disabled: picker.disabled, onClick: picker.openPicker, children: _jsx(PaperclipIcon, { className: icon({ size: "small" }) }) }));
|
|
283
288
|
}
|
|
284
289
|
/** Copies a reply's text, its mentions as their labels, and shows a tick for a moment. */
|
|
285
290
|
function CopyReplyButton({ content }) {
|
|
@@ -289,7 +294,8 @@ function CopyReplyButton({ content }) {
|
|
|
289
294
|
}
|
|
290
295
|
/**
|
|
291
296
|
* The source's items, the page's events, the agent's tasks and how the latest turn stands, in one
|
|
292
|
-
* timeline: the
|
|
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.
|
|
293
299
|
*/
|
|
294
300
|
function buildThread({ labels, source: { items, status, failureMessage, retry }, events, tasks, renderReply, replyActions, taskCard, }) {
|
|
295
301
|
const entries = [];
|
|
@@ -300,10 +306,23 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
300
306
|
latestTurnStart = index;
|
|
301
307
|
});
|
|
302
308
|
const latestTurnUnfinished = status === "running" || status === "failed";
|
|
303
|
-
items
|
|
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;
|
|
316
|
+
}
|
|
317
|
+
if (stepRun)
|
|
318
|
+
entries.push({ kind: "steps", ...stepRun });
|
|
319
|
+
stepRun = null;
|
|
304
320
|
const inUnfinishedTurn = latestTurnUnfinished && index > latestTurnStart;
|
|
305
321
|
entries.push(...toEntries(item, inUnfinishedTurn));
|
|
306
|
-
}
|
|
322
|
+
}
|
|
323
|
+
const runningSteps = status === "running" ? stepRun : null;
|
|
324
|
+
if (stepRun && !runningSteps)
|
|
325
|
+
entries.push({ kind: "steps", ...stepRun });
|
|
307
326
|
for (const event of events) {
|
|
308
327
|
entries.push({
|
|
309
328
|
kind: "line",
|
|
@@ -337,7 +356,16 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
337
356
|
entries.sort((first, second) => first.at - second.at);
|
|
338
357
|
const lastItem = items[items.length - 1];
|
|
339
358
|
const at = Math.max(lastItem?.at ?? 0, entries[entries.length - 1]?.at ?? 0) || Date.now();
|
|
340
|
-
|
|
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
|
+
});
|
|
341
369
|
return entries;
|
|
342
370
|
function toEntries(item, inUnfinishedTurn) {
|
|
343
371
|
switch (item.kind) {
|
|
@@ -353,13 +381,18 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
353
381
|
attachments: item.attachments,
|
|
354
382
|
sending: item.sending,
|
|
355
383
|
sources: assistant
|
|
356
|
-
?
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
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
|
+
]
|
|
361
392
|
: undefined,
|
|
362
393
|
actions: assistant && !inUnfinishedTurn ? replyActions?.(item.text) : undefined,
|
|
394
|
+
suggestedActions: item.suggestedActions,
|
|
395
|
+
footnote: item.footnote,
|
|
363
396
|
};
|
|
364
397
|
if (!item.failure)
|
|
365
398
|
return [message];
|
|
@@ -369,7 +402,8 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
369
402
|
kind: "failure",
|
|
370
403
|
id: `failure-${item.id}`,
|
|
371
404
|
at: item.at,
|
|
372
|
-
|
|
405
|
+
title: labels.notSent,
|
|
406
|
+
message: item.failure.message,
|
|
373
407
|
onRetry: item.failure.retry,
|
|
374
408
|
},
|
|
375
409
|
];
|
|
@@ -381,14 +415,17 @@ function buildThread({ labels, source: { items, status, failureMessage, retry },
|
|
|
381
415
|
}
|
|
382
416
|
}
|
|
383
417
|
}
|
|
384
|
-
/**
|
|
385
|
-
|
|
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, }) {
|
|
386
423
|
switch (status) {
|
|
387
424
|
case "running":
|
|
388
|
-
// Once its reply or a card waiting on the user is there, the agent is no longer
|
|
425
|
+
// Once its reply or a card waiting on the user is there, the agent is no longer thinking.
|
|
389
426
|
if (lastItem?.kind !== "card" &&
|
|
390
427
|
!(lastItem?.kind === "message" && lastItem.role === "assistant"))
|
|
391
|
-
entries.push({ kind: "typing", id: "typing", at });
|
|
428
|
+
entries.push({ kind: "typing", id: "typing", at, label: doing });
|
|
392
429
|
return;
|
|
393
430
|
case "failed": {
|
|
394
431
|
const message = failureMessage ?? labels.failed;
|