agentic-ui-kit 0.6.0 → 0.7.0
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/index.js +643 -109
- package/dist/styles.css +1 -1
- package/dist/tailwind.css +43 -0
- package/dist/types/react/src/components/attachments.d.ts +4 -1
- package/dist/types/react/src/components/chat-list.d.ts +4 -0
- package/dist/types/react/src/components/context-meter.d.ts +24 -0
- package/dist/types/react/src/components/context-panel.d.ts +5 -0
- package/dist/types/react/src/components/copy-field.d.ts +15 -0
- package/dist/types/react/src/components/markdown.d.ts +19 -2
- package/dist/types/react/src/components/model-picker.d.ts +6 -1
- package/dist/types/react/src/components/tools.d.ts +7 -2
- package/dist/types/react/src/hooks/sidebar-state.d.ts +6 -0
- package/dist/types/react/src/hooks/sse-transport.d.ts +7 -1
- package/dist/types/react/src/hooks/use-chat.d.ts +14 -1
- package/dist/types/react/src/index.d.ts +9 -5
- package/dist/types/react/src/primitives/button.d.ts +11 -3
- package/dist/types/react/src/primitives/cx.d.ts +3 -0
- package/dist/types/react/src/primitives/labels.d.ts +18 -0
- package/dist/types/react/src/primitives/menu.d.ts +23 -5
- package/dist/types/react/src/primitives/overlay.d.ts +13 -1
- package/dist/types/react/src/primitives/segmented-control.d.ts +29 -4
- package/dist/types/react/src/views/app-shell.d.ts +31 -3
- package/dist/types/react/src/views/chat-view.d.ts +6 -2
- package/dist/types/react/src/views/sidebar.d.ts +31 -5
- package/package.json +3 -2
package/dist/index.js
CHANGED
|
@@ -1,13 +1,40 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
import { A as awaitingToolCalls, C as widgetLoaderMessages, D as toBlocks, F as settleToolCall, L as salvageJson, O as activityTokens, T as readEventStream, _ as replacedId, b as resolveReplaces, d as isStateRef, f as matchesSchema, g as uiActionSchema, i as parseUiMarkup, k as applyEvent } from "./src-D01l3nBc.js";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
|
-
import {
|
|
5
|
-
import { Fragment,
|
|
6
|
-
import {
|
|
4
|
+
import { extendTailwindMerge } from "tailwind-merge";
|
|
5
|
+
import { Children, Component, Fragment, createContext, createElement, isValidElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
|
|
6
|
+
import { Autocomplete, Button as Button$1, Dialog, DialogTrigger, DialogTrigger as DialogTrigger$1, Disclosure as Disclosure$1, DisclosurePanel, Focusable, Header, Heading, Input, Keyboard, Link, Menu as Menu$1, MenuItem as MenuItem$1, MenuSection as MenuSection$1, MenuTrigger, MenuTrigger as MenuTrigger$1, Modal as Modal$1, ModalOverlay, Popover as Popover$1, SearchField, Separator, SubmenuTrigger, Text, ToggleButton, ToggleButtonGroup, Tooltip as Tooltip$1, TooltipTrigger, useFilter } from "react-aria-components";
|
|
7
|
+
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
7
8
|
import { AlertTriangle, ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, Info, Mic, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
|
|
8
9
|
import { UNSAFE_PortalProvider } from "react-aria";
|
|
9
10
|
import { createMathPlugin } from "@streamdown/math";
|
|
10
|
-
import { Streamdown, defaultRehypePlugins } from "streamdown";
|
|
11
|
+
import { Streamdown, defaultComponents, defaultRehypePlugins } from "streamdown";
|
|
12
|
+
// src/primitives/cx.ts
|
|
13
|
+
const twMerge = extendTailwindMerge({ extend: { theme: {
|
|
14
|
+
radius: [
|
|
15
|
+
"chat-sm",
|
|
16
|
+
"chat-md",
|
|
17
|
+
"chat-lg",
|
|
18
|
+
"chat-popover"
|
|
19
|
+
],
|
|
20
|
+
shadow: ["chat-popover"],
|
|
21
|
+
animate: [
|
|
22
|
+
"chat-in",
|
|
23
|
+
"chat-out",
|
|
24
|
+
"chat-fade-in",
|
|
25
|
+
"chat-fade-out",
|
|
26
|
+
"chat-drawer-in",
|
|
27
|
+
"chat-drawer-out",
|
|
28
|
+
"chat-drawer-in-right",
|
|
29
|
+
"chat-drawer-out-right",
|
|
30
|
+
"chat-sheet-in",
|
|
31
|
+
"chat-sheet-out"
|
|
32
|
+
]
|
|
33
|
+
} } });
|
|
34
|
+
/** `clsx` plus tailwind-merge: later classes win over earlier ones that set the same property, so a caller's `h-7` or `rounded-none` beats the kit's. */
|
|
35
|
+
function cx(...inputs) {
|
|
36
|
+
return twMerge(clsx(inputs));
|
|
37
|
+
}
|
|
11
38
|
// src/primitives/tooltip.tsx
|
|
12
39
|
function Tooltip({ content, children, placement = "top", delay = 600 }) {
|
|
13
40
|
return /* @__PURE__ */ jsxs(TooltipTrigger, {
|
|
@@ -38,23 +65,88 @@ const variants = {
|
|
|
38
65
|
*/
|
|
39
66
|
danger: "bg-[var(--chat-danger-bg,color-mix(in_srgb,var(--chat-danger)_70%,black))] font-medium text-[var(--chat-danger-fg,#fff)] data-hovered:brightness-110 data-pressed:brightness-95"
|
|
40
67
|
};
|
|
41
|
-
const sizes = {
|
|
68
|
+
const sizes$1 = {
|
|
42
69
|
sm: "h-7 gap-1.5 px-2 text-(length:--chat-ui-font-size) leading-5 [@media(pointer:coarse)]:h-9",
|
|
43
70
|
md: "h-8 gap-2 px-2.5 text-(length:--chat-ui-font-size) leading-5 [@media(pointer:coarse)]:h-10",
|
|
44
71
|
/** Dialog and empty-state actions. */
|
|
45
72
|
lg: "h-9 gap-2 px-3.5 text-(length:--chat-ui-font-size) leading-5 [@media(pointer:coarse)]:h-11"
|
|
46
73
|
};
|
|
47
|
-
|
|
48
|
-
|
|
74
|
+
/** Press handlers dropped from a button that is disabled with a reason; it only looks and announces as disabled. */
|
|
75
|
+
const pressHandlers = [
|
|
76
|
+
"onPress",
|
|
77
|
+
"onPressStart",
|
|
78
|
+
"onPressEnd",
|
|
79
|
+
"onPressChange",
|
|
80
|
+
"onPressUp",
|
|
81
|
+
"onClick"
|
|
82
|
+
];
|
|
83
|
+
/**
|
|
84
|
+
* A disabled button that still explains itself. React Aria's `isDisabled` makes the button unfocusable and
|
|
85
|
+
* hoverable-less, so there'd be nothing to hang a tooltip on. With a reason the button instead stays focusable
|
|
86
|
+
* with `aria-disabled`, loses its press handlers and never submits a form, and the reason is shown as a tooltip
|
|
87
|
+
* and read as its description.
|
|
88
|
+
*/
|
|
89
|
+
function useDisabledReason(props, disabledReason) {
|
|
90
|
+
const id = useId();
|
|
91
|
+
const active = Boolean(props.isDisabled && disabledReason);
|
|
92
|
+
if (!active) return {
|
|
93
|
+
active,
|
|
94
|
+
props,
|
|
95
|
+
reasonId: void 0
|
|
96
|
+
};
|
|
97
|
+
const rest = {
|
|
98
|
+
...props,
|
|
99
|
+
isDisabled: false,
|
|
100
|
+
type: "button"
|
|
101
|
+
};
|
|
102
|
+
for (const h of pressHandlers) delete rest[h];
|
|
103
|
+
rest["aria-disabled"] = true;
|
|
104
|
+
rest["data-disabled"] = true;
|
|
105
|
+
rest["aria-describedby"] = [props["aria-describedby"], id].filter(Boolean).join(" ");
|
|
106
|
+
return {
|
|
107
|
+
active,
|
|
108
|
+
props: rest,
|
|
109
|
+
reasonId: id
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
/** Hover and press styles are dropped while a button is disabled with a reason, to look as disabled as one that is. */
|
|
113
|
+
const noInteraction = (classes) => classes.replace(/data-(hovered|pressed):\S+\s*/g, "");
|
|
114
|
+
function WithReason({ active, reasonId, reason, children }) {
|
|
115
|
+
if (!active) return children;
|
|
116
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
117
|
+
content: reason,
|
|
118
|
+
children
|
|
119
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
120
|
+
id: reasonId,
|
|
121
|
+
hidden: true,
|
|
122
|
+
children: reason
|
|
123
|
+
})] });
|
|
124
|
+
}
|
|
125
|
+
function Button({ variant = "ghost", size = "md", className, disabledReason, ...rest }) {
|
|
126
|
+
const { active, props, reasonId } = useDisabledReason(rest, disabledReason);
|
|
127
|
+
const button = /* @__PURE__ */ jsx(Button$1, {
|
|
49
128
|
...props,
|
|
50
|
-
className:
|
|
129
|
+
className: cx("inline-flex cursor-pointer items-center justify-center rounded-chat-sm font-chat-sans whitespace-nowrap transition-colors select-none [&_svg]:size-4 [&_svg]:shrink-0", "data-disabled:cursor-default data-disabled:opacity-40", focusRing, active ? noInteraction(variants[variant]) : variants[variant], sizes$1[size], className)
|
|
130
|
+
});
|
|
131
|
+
return /* @__PURE__ */ jsx(WithReason, {
|
|
132
|
+
active,
|
|
133
|
+
reasonId,
|
|
134
|
+
reason: disabledReason,
|
|
135
|
+
children: button
|
|
51
136
|
});
|
|
52
137
|
}
|
|
53
|
-
function IconButton({ label, variant = "ghost", size = "md", tooltip = true, className, ...
|
|
138
|
+
function IconButton({ label, variant = "ghost", size = "md", tooltip = true, className, disabledReason, ...rest }) {
|
|
139
|
+
const { active, props, reasonId } = useDisabledReason(rest, disabledReason);
|
|
54
140
|
const button = /* @__PURE__ */ jsx(Button$1, {
|
|
55
141
|
...props,
|
|
56
142
|
"aria-label": label,
|
|
57
|
-
className:
|
|
143
|
+
className: cx("inline-flex shrink-0 cursor-pointer items-center justify-center rounded-chat-sm transition-colors select-none", "data-disabled:cursor-default data-disabled:opacity-40 [&_svg]:size-4", focusRing, active ? noInteraction(variants[variant]) : variants[variant], variant === "ghost" && (active ? "text-chat-muted" : "text-chat-muted data-hovered:text-chat-fg"), size === "sm" ? "size-7 [@media(pointer:coarse)]:size-9" : "size-8 [@media(pointer:coarse)]:size-10", className)
|
|
144
|
+
});
|
|
145
|
+
if (active) return /* @__PURE__ */ jsx(WithReason, {
|
|
146
|
+
active: true,
|
|
147
|
+
reasonId,
|
|
148
|
+
reason: disabledReason,
|
|
149
|
+
children: button
|
|
58
150
|
});
|
|
59
151
|
return tooltip ? /* @__PURE__ */ jsx(Tooltip, {
|
|
60
152
|
content: label,
|
|
@@ -70,7 +162,7 @@ const tones = {
|
|
|
70
162
|
};
|
|
71
163
|
function Badge({ children, tone = "outline", icon, className }) {
|
|
72
164
|
return /* @__PURE__ */ jsxs("span", {
|
|
73
|
-
className:
|
|
165
|
+
className: cx("inline-flex items-center gap-1 rounded-full px-2 py-px font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 whitespace-nowrap [&_svg]:size-3", tones[tone], className),
|
|
74
166
|
children: [icon, children]
|
|
75
167
|
});
|
|
76
168
|
}
|
|
@@ -170,6 +262,9 @@ const defaultLabels = {
|
|
|
170
262
|
duration: (seconds) => seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`,
|
|
171
263
|
ranCommandLabel: "Ran a command",
|
|
172
264
|
ranCommands: (count) => plural(count, "ran a command", (n) => `ran ${n} commands`),
|
|
265
|
+
runCommandAsk: "Run this command?",
|
|
266
|
+
searchWebAsk: "Search the web?",
|
|
267
|
+
readPageAsk: "Read this page?",
|
|
173
268
|
searchedWebLabel: "Searched the web",
|
|
174
269
|
readPageLabel: "Read page",
|
|
175
270
|
readPages: (count) => plural(count, "read a page", (n) => `read ${n} pages`),
|
|
@@ -181,7 +276,7 @@ const defaultLabels = {
|
|
|
181
276
|
noResults: "No results",
|
|
182
277
|
reasoningHidden: "The model's reasoning is not shown.",
|
|
183
278
|
allowTool: (name) => `Allow ${name}?`,
|
|
184
|
-
allowAction: (label) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
279
|
+
allowAction: (label) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
185
280
|
"Allow ",
|
|
186
281
|
label,
|
|
187
282
|
"?"
|
|
@@ -193,9 +288,14 @@ const defaultLabels = {
|
|
|
193
288
|
openSidebar: "Open sidebar",
|
|
194
289
|
expandSidebar: "Expand sidebar",
|
|
195
290
|
collapseSidebar: "Collapse sidebar",
|
|
291
|
+
resizeSidebar: "Resize sidebar",
|
|
292
|
+
withShortcut: (label, keys) => `${label} (${keys})`,
|
|
293
|
+
ctrlKey: "Ctrl",
|
|
196
294
|
context: "Context",
|
|
197
295
|
contextUsed: "Context used",
|
|
198
296
|
percentUsed: (percent) => `${percent}% used`,
|
|
297
|
+
contextSummary: (used, limit, percent) => `Context: ${used} / ${limit} (${percent}%)`,
|
|
298
|
+
contextLoading: "Context: loading",
|
|
199
299
|
freeSpace: "Free space",
|
|
200
300
|
tokenCount: (count) => `${count} tokens`,
|
|
201
301
|
replyPlaceholder: "Reply…",
|
|
@@ -240,6 +340,7 @@ const defaultLabels = {
|
|
|
240
340
|
justNow: "just now",
|
|
241
341
|
copy: "Copy",
|
|
242
342
|
copied: "Copied",
|
|
343
|
+
copyFieldLabel: "Link",
|
|
243
344
|
retry: "Retry",
|
|
244
345
|
edit: "Edit",
|
|
245
346
|
readAloud: "Read aloud",
|
|
@@ -299,27 +400,40 @@ function CopyButton({ text, className }) {
|
|
|
299
400
|
});
|
|
300
401
|
}
|
|
301
402
|
// src/primitives/segmented-control.tsx
|
|
403
|
+
const sizes = {
|
|
404
|
+
sm: {
|
|
405
|
+
button: "h-6",
|
|
406
|
+
iconOnly: "w-7"
|
|
407
|
+
},
|
|
408
|
+
md: {
|
|
409
|
+
button: "h-8",
|
|
410
|
+
iconOnly: "w-9"
|
|
411
|
+
}
|
|
412
|
+
};
|
|
302
413
|
/**
|
|
303
414
|
* One of a few options, side by side on a track, similar to claude.ai's Chat / Cowork switch. Themed with
|
|
304
415
|
* `--chat-segment-track` (default: a shade darker than the surface it sits on) and `--chat-segment-selected`
|
|
305
416
|
* (default `--chat-raised`).
|
|
306
417
|
*/
|
|
307
|
-
function SegmentedControl({ label, options, value, onChange, className }) {
|
|
418
|
+
function SegmentedControl({ label, options, value, onChange, clearable, isDisabled, size = "sm", className }) {
|
|
308
419
|
return /* @__PURE__ */ jsx(ToggleButtonGroup, {
|
|
309
420
|
"aria-label": label,
|
|
310
421
|
selectionMode: "single",
|
|
311
|
-
disallowEmptySelection:
|
|
312
|
-
|
|
422
|
+
disallowEmptySelection: !clearable,
|
|
423
|
+
...isDisabled && { isDisabled },
|
|
424
|
+
selectedKeys: value === null ? [] : [value],
|
|
313
425
|
onSelectionChange: (keys) => {
|
|
314
426
|
const next = [...keys][0];
|
|
315
427
|
if (next !== void 0) onChange(next);
|
|
428
|
+
else if (clearable) onChange(null);
|
|
316
429
|
},
|
|
317
|
-
className:
|
|
430
|
+
className: cx("inline-flex shrink-0 items-center gap-0.5 rounded-chat-md p-0.5", "bg-[var(--chat-segment-track,color-mix(in_srgb,var(--chat-bg)_55%,transparent))]", className),
|
|
318
431
|
children: options.map((o) => {
|
|
319
432
|
const button = /* @__PURE__ */ jsxs(ToggleButton, {
|
|
320
433
|
id: o.id,
|
|
434
|
+
...o.isDisabled && { isDisabled: true },
|
|
321
435
|
...o.iconOnly && { "aria-label": o.label },
|
|
322
|
-
className:
|
|
436
|
+
className: cx("inline-flex cursor-pointer items-center justify-center gap-1.5 rounded-[calc(var(--chat-radius-md)-2px)] font-chat-sans text-(length:--chat-ui-font-size) leading-5 text-chat-muted outline-none transition-colors select-none [&_svg]:size-4", sizes[size].button, o.iconOnly ? sizes[size].iconOnly : "px-2", "data-disabled:cursor-default data-disabled:opacity-40", "data-hovered:text-chat-fg data-focus-visible:ring-2 data-focus-visible:ring-chat-focus", "data-selected:bg-[var(--chat-segment-selected,var(--chat-raised))] data-selected:text-chat-fg data-selected:shadow-[inset_0_0_0_1px_var(--chat-border-strong)]"),
|
|
323
437
|
children: [o.icon, !o.iconOnly && o.label]
|
|
324
438
|
}, o.id);
|
|
325
439
|
return o.iconOnly ? /* @__PURE__ */ jsx(Tooltip, {
|
|
@@ -340,7 +454,7 @@ function Disclosure({ summary, children, className, summaryClassName, panelClass
|
|
|
340
454
|
className: "m-0 font-normal",
|
|
341
455
|
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
342
456
|
slot: "trigger",
|
|
343
|
-
className:
|
|
457
|
+
className: cx("group/trigger flex max-w-full cursor-pointer items-center gap-1.5 rounded-chat-sm text-left text-chat-muted outline-none", "data-hovered:text-chat-fg data-focus-visible:ring-2 data-focus-visible:ring-chat-focus", summaryClassName),
|
|
344
458
|
children: [
|
|
345
459
|
indicator === "chevron" && /* @__PURE__ */ jsx(IconSlot, {
|
|
346
460
|
className: "transition-transform group-aria-expanded/trigger:rotate-90 [&_svg]:size-3.5",
|
|
@@ -367,6 +481,11 @@ function Disclosure({ summary, children, className, summaryClassName, panelClass
|
|
|
367
481
|
});
|
|
368
482
|
}
|
|
369
483
|
// src/primitives/menu.tsx
|
|
484
|
+
/**
|
|
485
|
+
* Wraps a trigger button and a kit `Popover` (or `Modal`) so that pressing the button toggles it, without
|
|
486
|
+
* importing react-aria. The same component as `DialogTrigger`; use whichever name reads better.
|
|
487
|
+
*/
|
|
488
|
+
const PopoverTrigger = DialogTrigger$1;
|
|
370
489
|
/** A search box above a menu that filters its items, like "Search or create a project". */
|
|
371
490
|
function MenuSearch({ placeholder, children, value, onChange }) {
|
|
372
491
|
const { contains } = useFilter({ sensitivity: "base" });
|
|
@@ -385,41 +504,71 @@ function MenuSearch({ placeholder, children, value, onChange }) {
|
|
|
385
504
|
}), children]
|
|
386
505
|
});
|
|
387
506
|
}
|
|
388
|
-
/**
|
|
389
|
-
function
|
|
507
|
+
/** Viewport narrower than `max` px. False on the server and where `matchMedia` is missing. */
|
|
508
|
+
function useNarrower(max) {
|
|
509
|
+
const query = max === false ? null : `(max-width: ${max - .02}px)`;
|
|
510
|
+
return useSyncExternalStore((notify) => {
|
|
511
|
+
if (!query || typeof window === "undefined" || !window.matchMedia) return () => {};
|
|
512
|
+
const list = window.matchMedia(query);
|
|
513
|
+
list.addEventListener("change", notify);
|
|
514
|
+
return () => list.removeEventListener("change", notify);
|
|
515
|
+
}, () => query && typeof window !== "undefined" && window.matchMedia ? window.matchMedia(query).matches : false, () => false);
|
|
516
|
+
}
|
|
517
|
+
/** Floating panel for menus and pickers: surface color, rounded, soft shadow, fades in. On phones it's a bottom sheet. */
|
|
518
|
+
function Popover({ className, offset = 6, sheet = 768, width = "auto", fullWidthOnPhones, children, ...props }) {
|
|
519
|
+
if (useNarrower(sheet)) {
|
|
520
|
+
const { isOpen, onOpenChange } = props;
|
|
521
|
+
return /* @__PURE__ */ jsx(ModalOverlay, {
|
|
522
|
+
...isOpen !== void 0 && { isOpen },
|
|
523
|
+
...onOpenChange && { onOpenChange },
|
|
524
|
+
isDismissable: true,
|
|
525
|
+
className: "fixed inset-0 z-50 flex items-end bg-(--chat-backdrop) data-entering:animate-chat-fade-in data-exiting:animate-chat-fade-out",
|
|
526
|
+
children: /* @__PURE__ */ jsx(Modal$1, {
|
|
527
|
+
className: cx("flex max-h-[85dvh] w-full flex-col overflow-auto rounded-t-chat-lg border-t border-chat-popover-border bg-chat-popover pb-[env(safe-area-inset-bottom)] font-chat-sans text-chat-fg shadow-chat-popover outline-none", "data-entering:animate-chat-sheet-in data-exiting:animate-chat-sheet-out", className),
|
|
528
|
+
children: typeof children === "function" ? children({
|
|
529
|
+
placement: "bottom",
|
|
530
|
+
trigger: null,
|
|
531
|
+
isEntering: false,
|
|
532
|
+
isExiting: false,
|
|
533
|
+
defaultChildren: void 0
|
|
534
|
+
}) : children
|
|
535
|
+
})
|
|
536
|
+
});
|
|
537
|
+
}
|
|
390
538
|
return /* @__PURE__ */ jsx(Popover$1, {
|
|
391
539
|
offset,
|
|
392
540
|
...props,
|
|
393
|
-
className:
|
|
541
|
+
className: cx("rounded-chat-popover border border-chat-popover-border bg-chat-popover font-chat-sans text-chat-fg shadow-chat-popover outline-none", "motion-safe:data-entering:animate-chat-in motion-safe:data-exiting:animate-chat-out", width === "trigger" && "w-(--trigger-width) [&_[role=menu]]:min-w-0", fullWidthOnPhones && "max-sm:w-[calc(100vw-1.5rem)]", className),
|
|
542
|
+
children
|
|
394
543
|
});
|
|
395
544
|
}
|
|
396
545
|
function Menu({ className, ...props }) {
|
|
397
546
|
return /* @__PURE__ */ jsx(Menu$1, {
|
|
398
547
|
...props,
|
|
399
|
-
className:
|
|
548
|
+
className: cx("max-h-[inherit] min-w-52 overflow-auto p-1.5 outline-none", className)
|
|
400
549
|
});
|
|
401
550
|
}
|
|
402
|
-
function MenuItem({ children, icon, description, descriptionPlacement = "below", detail, shortcut, tone = "default", className, ...props }) {
|
|
551
|
+
function MenuItem({ children, icon, description, descriptionPlacement = "below", detail, shortcut, tone = "default", indicator = "check", className, ...props }) {
|
|
403
552
|
const icons = useIcons();
|
|
404
553
|
const textValue = props.textValue ?? (typeof children === "string" ? children : void 0);
|
|
405
554
|
return /* @__PURE__ */ jsx(MenuItem$1, {
|
|
406
555
|
...props,
|
|
407
556
|
...textValue !== void 0 && { textValue },
|
|
408
|
-
className:
|
|
409
|
-
children: ({ isSelected, hasSubmenu, selectionMode }) => /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
557
|
+
className: cx("group flex min-h-7 cursor-pointer items-center gap-2.5 rounded-chat-sm px-2 py-1 text-(length:--chat-ui-font-size) leading-5 text-chat-fg outline-none select-none", "data-focused:bg-chat-popover-hover data-open:bg-chat-popover-hover data-disabled:opacity-40", tone === "danger" && "text-chat-danger", className),
|
|
558
|
+
children: ({ isSelected, hasSubmenu, selectionMode }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
410
559
|
icon && /* @__PURE__ */ jsx("span", {
|
|
411
|
-
className:
|
|
560
|
+
className: cx("flex size-4 shrink-0 items-center [&_svg]:size-4", tone === "danger" ? "text-chat-danger" : "text-chat-fg"),
|
|
412
561
|
children: icon
|
|
413
562
|
}),
|
|
414
563
|
/* @__PURE__ */ jsxs("span", {
|
|
415
|
-
className:
|
|
564
|
+
className: cx("flex min-w-0 flex-1", descriptionPlacement === "beside" ? "items-baseline gap-2" : "flex-col"),
|
|
416
565
|
children: [/* @__PURE__ */ jsx(Text, {
|
|
417
566
|
slot: "label",
|
|
418
|
-
className:
|
|
567
|
+
className: cx("truncate", descriptionPlacement === "beside" && "shrink-0"),
|
|
419
568
|
children
|
|
420
569
|
}), description && /* @__PURE__ */ jsx(Text, {
|
|
421
570
|
slot: "description",
|
|
422
|
-
className:
|
|
571
|
+
className: cx("text-chat-muted", descriptionPlacement === "beside" ? "truncate text-(length:--chat-ui-font-size) leading-5" : "text-(length:--chat-ui-font-size-sm) leading-4"),
|
|
423
572
|
children: description
|
|
424
573
|
})]
|
|
425
574
|
}),
|
|
@@ -431,7 +580,12 @@ function MenuItem({ children, icon, description, descriptionPlacement = "below",
|
|
|
431
580
|
className: "shrink-0 font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle",
|
|
432
581
|
children: shortcut
|
|
433
582
|
}),
|
|
434
|
-
selectionMode !== "none" &&
|
|
583
|
+
selectionMode !== "none" && indicator === "switch" && /* @__PURE__ */ jsx("span", {
|
|
584
|
+
"aria-hidden": true,
|
|
585
|
+
"data-selected": isSelected || void 0,
|
|
586
|
+
className: "relative h-4 w-7 shrink-0 rounded-full bg-chat-border-strong transition-colors after:absolute after:top-0.5 after:left-0.5 after:size-3 after:rounded-full after:bg-chat-bg after:transition-transform data-selected:bg-chat-accent data-selected:after:translate-x-3 data-selected:after:bg-chat-accent-fg"
|
|
587
|
+
}),
|
|
588
|
+
selectionMode !== "none" && indicator === "check" && isSelected && /* @__PURE__ */ jsx(IconSlot, {
|
|
435
589
|
className: "text-chat-check [&_svg]:size-4",
|
|
436
590
|
children: icons.check
|
|
437
591
|
}),
|
|
@@ -458,7 +612,7 @@ function MenuSeparator() {
|
|
|
458
612
|
/** Explanatory text inside a menu, above or below the items. Not focusable. */
|
|
459
613
|
function MenuNote({ children, className }) {
|
|
460
614
|
return /* @__PURE__ */ jsx("div", {
|
|
461
|
-
className:
|
|
615
|
+
className: cx("px-2 py-1.5 text-(length:--chat-ui-font-size) leading-snug text-chat-muted", className),
|
|
462
616
|
children
|
|
463
617
|
});
|
|
464
618
|
}
|
|
@@ -492,7 +646,7 @@ function ContextMenu({ menu, children, className }) {
|
|
|
492
646
|
ref: root,
|
|
493
647
|
onContextMenu,
|
|
494
648
|
onKeyDown,
|
|
495
|
-
className:
|
|
649
|
+
className: cx("relative", className),
|
|
496
650
|
children: [children, /* @__PURE__ */ jsxs(MenuTrigger$1, {
|
|
497
651
|
isOpen: at !== void 0,
|
|
498
652
|
onOpenChange: (open) => !open && setAt(void 0),
|
|
@@ -513,15 +667,23 @@ function ContextMenu({ menu, children, className }) {
|
|
|
513
667
|
});
|
|
514
668
|
}
|
|
515
669
|
// src/primitives/overlay.tsx
|
|
670
|
+
const modalSizes = {
|
|
671
|
+
sm: "max-w-sm",
|
|
672
|
+
md: "max-w-lg",
|
|
673
|
+
lg: "max-w-2xl",
|
|
674
|
+
xl: "max-w-5xl",
|
|
675
|
+
/** Fills the page inside the overlay's padding. */
|
|
676
|
+
full: "h-full max-h-none max-w-none"
|
|
677
|
+
};
|
|
516
678
|
/** A centered dialog over a dimmed page. Escape and clicking outside close it; focus stays inside. */
|
|
517
|
-
function Modal({ isOpen, onOpenChange, title, children, className, ...props }) {
|
|
679
|
+
function Modal({ isOpen, onOpenChange, title, size = "lg", placement = "center", children, className, ...props }) {
|
|
518
680
|
return /* @__PURE__ */ jsx(ModalOverlay, {
|
|
519
681
|
isOpen,
|
|
520
682
|
onOpenChange,
|
|
521
683
|
isDismissable: true,
|
|
522
|
-
className: "fixed inset-0 z-50 flex
|
|
684
|
+
className: cx("fixed inset-0 z-50 flex justify-center bg-(--chat-backdrop) p-4 data-entering:animate-chat-fade-in data-exiting:animate-chat-fade-out", placement === "top" ? "items-start pt-[10dvh]" : "items-center"),
|
|
523
685
|
children: /* @__PURE__ */ jsx(Modal$1, {
|
|
524
|
-
className:
|
|
686
|
+
className: cx("flex max-h-[85dvh] w-full flex-col rounded-chat-lg border border-chat-popover-border bg-chat-popover font-chat-sans text-chat-fg shadow-chat-popover", modalSizes[size], "data-entering:animate-chat-in data-exiting:animate-chat-out", className),
|
|
525
687
|
children: /* @__PURE__ */ jsxs(Dialog, {
|
|
526
688
|
...props,
|
|
527
689
|
className: "flex min-h-0 flex-1 flex-col outline-none",
|
|
@@ -542,7 +704,7 @@ function Drawer({ isOpen, onOpenChange, side = "left", children, className, ...p
|
|
|
542
704
|
isDismissable: true,
|
|
543
705
|
className: "fixed inset-0 z-40 bg-(--chat-backdrop) data-entering:animate-chat-fade-in data-exiting:animate-chat-fade-out",
|
|
544
706
|
children: /* @__PURE__ */ jsx(Modal$1, {
|
|
545
|
-
className:
|
|
707
|
+
className: cx("fixed inset-y-0 w-[min(20rem,85vw)] bg-chat-sidebar text-chat-fg shadow-chat-popover outline-none", side === "left" ? "left-0 data-entering:animate-chat-drawer-in data-exiting:animate-chat-drawer-out" : "right-0 data-entering:animate-chat-drawer-in-right data-exiting:animate-chat-drawer-out-right", className),
|
|
546
708
|
children: /* @__PURE__ */ jsx(Dialog, {
|
|
547
709
|
...props,
|
|
548
710
|
className: "h-full outline-none",
|
|
@@ -559,7 +721,7 @@ function Drawer({ isOpen, onOpenChange, side = "left", children, className, ...p
|
|
|
559
721
|
function Panel({ title, actions, footer, children, variant = "card", className, ...props }) {
|
|
560
722
|
return /* @__PURE__ */ jsxs("section", {
|
|
561
723
|
"aria-label": props["aria-label"],
|
|
562
|
-
className:
|
|
724
|
+
className: cx("flex min-h-0 min-w-0 flex-col overflow-hidden bg-chat-bg font-chat-sans text-chat-fg", variant === "card" && "rounded-chat-md border border-chat-border", className),
|
|
563
725
|
children: [
|
|
564
726
|
(title || actions) && /* @__PURE__ */ jsxs("header", {
|
|
565
727
|
className: "flex h-10 shrink-0 items-center gap-2 border-b border-chat-divider px-3 text-(length:--chat-ui-font-size) leading-5",
|
|
@@ -726,7 +888,7 @@ function CodeBlock({ code, language, label = language, variant = "plain", copy =
|
|
|
726
888
|
}), copy && /* @__PURE__ */ jsx(CopyButton, { text: code })]
|
|
727
889
|
}), /* @__PURE__ */ jsx("pre", {
|
|
728
890
|
className: clsx("overflow-auto font-chat-mono text-(length:--chat-code-font-size) leading-[1.75] whitespace-pre text-chat-fg", variant === "plain" && "max-h-56", "[scrollbar-color:var(--chat-border-strong)_transparent] [scrollbar-width:thin]"),
|
|
729
|
-
children: /* @__PURE__ */ jsxs("code", { children: [highlighted ? highlighted.map((line, i) => /* @__PURE__ */ jsxs(Fragment
|
|
891
|
+
children: /* @__PURE__ */ jsxs("code", { children: [highlighted ? highlighted.map((line, i) => /* @__PURE__ */ jsxs(Fragment, { children: [i > 0 && "\n", line.map((token, j) => /* @__PURE__ */ jsx("span", {
|
|
730
892
|
className: "chat-code-token",
|
|
731
893
|
style: token.htmlStyle,
|
|
732
894
|
children: token.content
|
|
@@ -881,7 +1043,7 @@ const DECLINED = "Declined by the user";
|
|
|
881
1043
|
* retries. With an `AgentProvider` it also runs the agent loop: client tools run in the page, approvals
|
|
882
1044
|
* wait for the user, then the reply continues; every finished tool call reaches its tool's `onResult`.
|
|
883
1045
|
*/
|
|
884
|
-
function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12 }) {
|
|
1046
|
+
function useChat({ transport, conversationId, initialMessages = [], onTitle, agent, maxSteps = 12, resume = false }) {
|
|
885
1047
|
const contextAgent = useAgentRegistry();
|
|
886
1048
|
const registry = agent === void 0 ? contextAgent : agent;
|
|
887
1049
|
const [messages, setMessages] = useState(initialMessages);
|
|
@@ -949,7 +1111,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
949
1111
|
if (ended) commit(ended);
|
|
950
1112
|
}, [commit]);
|
|
951
1113
|
const advanceRef = useRef(() => {});
|
|
952
|
-
const start = useCallback((history, options, continuation) => {
|
|
1114
|
+
const start = useCallback((history, options, continuation, joining) => {
|
|
953
1115
|
const message = history.at(-1);
|
|
954
1116
|
const controller = new AbortController();
|
|
955
1117
|
const current = {
|
|
@@ -965,7 +1127,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
965
1127
|
try {
|
|
966
1128
|
const { transport, conversationId, registry } = latest.current;
|
|
967
1129
|
const tools = registry?.specs();
|
|
968
|
-
|
|
1130
|
+
const events = joining && conversationId !== void 0 && transport.resume ? transport.resume(conversationId, controller.signal) : transport.send({
|
|
969
1131
|
message,
|
|
970
1132
|
history,
|
|
971
1133
|
signal: controller.signal,
|
|
@@ -975,7 +1137,8 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
975
1137
|
...conversationId !== void 0 && { conversationId },
|
|
976
1138
|
...options.model !== void 0 && { model: options.model },
|
|
977
1139
|
...options.effort !== void 0 && { effort: options.effort }
|
|
978
|
-
})
|
|
1140
|
+
});
|
|
1141
|
+
for await (const event of events) {
|
|
979
1142
|
if (controller.signal.aborted) break;
|
|
980
1143
|
if (event.type === "title") {
|
|
981
1144
|
latest.current.onTitle?.(event.title);
|
|
@@ -1147,7 +1310,30 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1147
1310
|
};
|
|
1148
1311
|
start(messages.slice(0, index), options);
|
|
1149
1312
|
}, [start]);
|
|
1150
|
-
|
|
1313
|
+
const joined = useRef(false);
|
|
1314
|
+
useEffect(() => {
|
|
1315
|
+
if (!resume || joined.current || run.current) return;
|
|
1316
|
+
const { transport, conversationId, messages } = latest.current;
|
|
1317
|
+
if (!transport.resume || conversationId === void 0) return;
|
|
1318
|
+
joined.current = true;
|
|
1319
|
+
const last = messages.at(-1);
|
|
1320
|
+
const history = last?.role === "assistant" && last.status === "streaming" ? messages.slice(0, -1) : messages;
|
|
1321
|
+
loop.current = {
|
|
1322
|
+
options: {},
|
|
1323
|
+
step: 0
|
|
1324
|
+
};
|
|
1325
|
+
start(history, {}, void 0, true);
|
|
1326
|
+
}, [resume, start]);
|
|
1327
|
+
const mounted = useRef(false);
|
|
1328
|
+
useEffect(() => {
|
|
1329
|
+
mounted.current = true;
|
|
1330
|
+
return () => {
|
|
1331
|
+
mounted.current = false;
|
|
1332
|
+
queueMicrotask(() => {
|
|
1333
|
+
if (!mounted.current) run.current?.controller.abort();
|
|
1334
|
+
});
|
|
1335
|
+
};
|
|
1336
|
+
}, []);
|
|
1151
1337
|
return {
|
|
1152
1338
|
messages,
|
|
1153
1339
|
busy,
|
|
@@ -1160,7 +1346,7 @@ function useChat({ transport, conversationId, initialMessages = [], onTitle, age
|
|
|
1160
1346
|
}
|
|
1161
1347
|
// src/hooks/sse-transport.ts
|
|
1162
1348
|
/** A `Transport` for backends that stream `StreamEvent`s over server-sent events (see `formatEvent`). */
|
|
1163
|
-
function sseTransport({ url, headers, stopUrl, fetch: fetchImpl = fetch }) {
|
|
1349
|
+
function sseTransport({ url, headers, stopUrl, resumeUrl, fetch: fetchImpl = fetch }) {
|
|
1164
1350
|
const headersFor = async () => ({
|
|
1165
1351
|
"content-type": "application/json",
|
|
1166
1352
|
...Object.fromEntries(new Headers(typeof headers === "function" ? await headers() : headers))
|
|
@@ -1176,6 +1362,20 @@ function sseTransport({ url, headers, stopUrl, fetch: fetchImpl = fetch }) {
|
|
|
1176
1362
|
if (!response.ok || !response.body) throw new Error(`${url} answered ${response.status} ${response.statusText}`.trim());
|
|
1177
1363
|
yield* readEventStream(response.body);
|
|
1178
1364
|
},
|
|
1365
|
+
...resumeUrl && { async *resume(conversationId, signal) {
|
|
1366
|
+
const target = typeof resumeUrl === "function" ? resumeUrl(conversationId) : `${resumeUrl}${resumeUrl.includes("?") ? "&" : "?"}conversationId=${encodeURIComponent(conversationId)}`;
|
|
1367
|
+
const { "content-type": _, ...headers } = await headersFor();
|
|
1368
|
+
const response = await fetchImpl(target, {
|
|
1369
|
+
method: "GET",
|
|
1370
|
+
headers: {
|
|
1371
|
+
...headers,
|
|
1372
|
+
accept: "text/event-stream"
|
|
1373
|
+
},
|
|
1374
|
+
signal
|
|
1375
|
+
});
|
|
1376
|
+
if (!response.ok || !response.body) throw new Error(`${target} answered ${response.status} ${response.statusText}`.trim());
|
|
1377
|
+
yield* readEventStream(response.body);
|
|
1378
|
+
} },
|
|
1179
1379
|
...stopUrl && { async stop(runId) {
|
|
1180
1380
|
await fetchImpl(stopUrl, {
|
|
1181
1381
|
method: "POST",
|
|
@@ -1280,6 +1480,30 @@ const SidebarContext = createContext({
|
|
|
1280
1480
|
dismiss: () => {}
|
|
1281
1481
|
});
|
|
1282
1482
|
const useSidebar = () => useContext(SidebarContext);
|
|
1483
|
+
/** State that the parent may own: `value` makes it controlled, otherwise `initial` seeds it. */
|
|
1484
|
+
function useControllable(value, initial, onChange) {
|
|
1485
|
+
const [inner, setInner] = useState(initial);
|
|
1486
|
+
const current = value ?? inner;
|
|
1487
|
+
const latest = useRef({
|
|
1488
|
+
current,
|
|
1489
|
+
value,
|
|
1490
|
+
onChange
|
|
1491
|
+
});
|
|
1492
|
+
latest.current = {
|
|
1493
|
+
current,
|
|
1494
|
+
value,
|
|
1495
|
+
onChange
|
|
1496
|
+
};
|
|
1497
|
+
return [current, useCallback((next) => {
|
|
1498
|
+
const { current, value, onChange } = latest.current;
|
|
1499
|
+
const resolved = typeof next === "function" ? next(current) : next;
|
|
1500
|
+
if (resolved === current) return;
|
|
1501
|
+
if (value === void 0) setInner(resolved);
|
|
1502
|
+
onChange?.(resolved);
|
|
1503
|
+
}, [])];
|
|
1504
|
+
}
|
|
1505
|
+
/** True on Apple platforms, where the shortcut modifier is ⌘ instead of Ctrl. */
|
|
1506
|
+
const isMacPlatform = () => typeof navigator !== "undefined" && /mac|iphone|ipad|ipod/i.test(navigator.platform || navigator.userAgent);
|
|
1283
1507
|
// src/components/replacements.tsx
|
|
1284
1508
|
/**
|
|
1285
1509
|
* In-place updates of drawn visuals: a tool call whose input has `replaces: <id>` takes the place of that
|
|
@@ -2193,7 +2417,7 @@ function Code({ node, className, children }) {
|
|
|
2193
2417
|
});
|
|
2194
2418
|
}
|
|
2195
2419
|
const components = {
|
|
2196
|
-
pre: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children }),
|
|
2420
|
+
pre: ({ children }) => /* @__PURE__ */ jsx(Fragment$1, { children }),
|
|
2197
2421
|
code: ({ node, className, children }) => /* @__PURE__ */ jsx(Code, {
|
|
2198
2422
|
node,
|
|
2199
2423
|
className,
|
|
@@ -2201,12 +2425,39 @@ const components = {
|
|
|
2201
2425
|
})
|
|
2202
2426
|
};
|
|
2203
2427
|
const LinksContext = createContext(void 0);
|
|
2204
|
-
function MarkdownLinksProvider({ renderLink, children }) {
|
|
2428
|
+
function MarkdownLinksProvider({ renderLink, renderLinkList, children }) {
|
|
2429
|
+
const value = useMemo(() => ({
|
|
2430
|
+
renderLink,
|
|
2431
|
+
renderLinkList
|
|
2432
|
+
}), [renderLink, renderLinkList]);
|
|
2205
2433
|
return /* @__PURE__ */ jsx(LinksContext.Provider, {
|
|
2206
|
-
value
|
|
2434
|
+
value,
|
|
2207
2435
|
children
|
|
2208
2436
|
});
|
|
2209
2437
|
}
|
|
2438
|
+
/** The rendered link an item consists of: an `a` alone, or alone in its paragraph (a loose list). */
|
|
2439
|
+
function soleLink(children) {
|
|
2440
|
+
const nodes = Children.toArray(children).filter((c) => !(typeof c === "string" && !c.trim()));
|
|
2441
|
+
if (nodes.length !== 1 || !isValidElement(nodes[0])) return void 0;
|
|
2442
|
+
const props = nodes[0].props;
|
|
2443
|
+
if (typeof props.href === "string") return {
|
|
2444
|
+
href: props.href,
|
|
2445
|
+
children: props.children
|
|
2446
|
+
};
|
|
2447
|
+
return props.node?.tagName === "p" ? soleLink(props.children) : void 0;
|
|
2448
|
+
}
|
|
2449
|
+
function listOf(ordered, props, renderLinkList) {
|
|
2450
|
+
const Default = ordered ? defaultComponents.ol : defaultComponents.ul;
|
|
2451
|
+
const { children } = props;
|
|
2452
|
+
const links = Children.toArray(children).filter((c) => isValidElement(c)).map((item) => soleLink(item.props.children));
|
|
2453
|
+
const list = /* @__PURE__ */ jsx(Default, { ...props });
|
|
2454
|
+
if (links.length === 0 || links.some((l) => !l)) return list;
|
|
2455
|
+
return renderLinkList({
|
|
2456
|
+
ordered,
|
|
2457
|
+
links,
|
|
2458
|
+
children: list
|
|
2459
|
+
}) ?? list;
|
|
2460
|
+
}
|
|
2210
2461
|
/** Message markdown: GFM, KaTeX math ($…$ and $$…$$), Shiki code blocks, smooth fade-in while streaming. */
|
|
2211
2462
|
function Markdown({ children, streaming = false, className, partId }) {
|
|
2212
2463
|
const text = useSmoothText(children, streaming);
|
|
@@ -2237,22 +2488,26 @@ function BlockScope({ id, streaming, children }) {
|
|
|
2237
2488
|
});
|
|
2238
2489
|
}
|
|
2239
2490
|
const MarkdownBody = memo(function MarkdownBody({ text, animating, fade }) {
|
|
2240
|
-
const renderLink = useContext(LinksContext);
|
|
2241
|
-
const withLinks = useMemo(() =>
|
|
2491
|
+
const { renderLink, renderLinkList } = useContext(LinksContext) ?? {};
|
|
2492
|
+
const withLinks = useMemo(() => ({
|
|
2242
2493
|
...components,
|
|
2243
|
-
a: ({ href, children }) => {
|
|
2494
|
+
...renderLink && { a: ({ href, children }) => {
|
|
2244
2495
|
const custom = href ? renderLink({
|
|
2245
2496
|
href,
|
|
2246
2497
|
children
|
|
2247
2498
|
}) : void 0;
|
|
2248
|
-
return custom !== void 0 ? /* @__PURE__ */ jsx(Fragment, { children: custom }) : /* @__PURE__ */ jsx("a", {
|
|
2499
|
+
return custom !== void 0 ? /* @__PURE__ */ jsx(Fragment$1, { children: custom }) : /* @__PURE__ */ jsx("a", {
|
|
2249
2500
|
href,
|
|
2250
2501
|
target: "_blank",
|
|
2251
2502
|
rel: "noopener noreferrer",
|
|
2252
2503
|
children
|
|
2253
2504
|
});
|
|
2505
|
+
} },
|
|
2506
|
+
...renderLinkList && {
|
|
2507
|
+
ul: (props) => listOf(false, props, renderLinkList),
|
|
2508
|
+
ol: (props) => listOf(true, props, renderLinkList)
|
|
2254
2509
|
}
|
|
2255
|
-
}
|
|
2510
|
+
}), [renderLink, renderLinkList]);
|
|
2256
2511
|
return /* @__PURE__ */ jsx(Streamdown, {
|
|
2257
2512
|
prefix: PREFIX,
|
|
2258
2513
|
mode: animating || fade ? "streaming" : "static",
|
|
@@ -2312,7 +2567,7 @@ function activityLabel(part, labels, view) {
|
|
|
2312
2567
|
*/
|
|
2313
2568
|
function activityHeading(parts, views, labels) {
|
|
2314
2569
|
const waiting = parts.find((p) => p.type === "tool_call" && p.status === "awaiting");
|
|
2315
|
-
if (waiting) return /* @__PURE__ */ jsxs(Fragment, { children: [activityLabel(waiting, labels, views[waiting.name]), labels.waitingForYou] });
|
|
2570
|
+
if (waiting) return /* @__PURE__ */ jsxs(Fragment$1, { children: [activityLabel(waiting, labels, views[waiting.name]), labels.waitingForYou] });
|
|
2316
2571
|
if (parts.some(isWorking)) {
|
|
2317
2572
|
const last = parts.at(-1);
|
|
2318
2573
|
return activityLabel(last, labels, last.type === "tool_call" ? views[last.name] : void 0);
|
|
@@ -2331,6 +2586,7 @@ function stringOf(value) {
|
|
|
2331
2586
|
}
|
|
2332
2587
|
/** A shell command. Input `{ command, description? }`; result a string or `{ stdout, stderr, exitCode }`. */
|
|
2333
2588
|
const commandTool = {
|
|
2589
|
+
ask: (_, labels) => labels.runCommandAsk,
|
|
2334
2590
|
label: (_, labels) => labels.ranCommandLabel,
|
|
2335
2591
|
hint: (part) => stringOf(recordOf(part.input).description),
|
|
2336
2592
|
summary: (n, labels) => labels.ranCommands(n),
|
|
@@ -2363,6 +2619,7 @@ const commandTool = {
|
|
|
2363
2619
|
* part's display `data`, else its `result`.
|
|
2364
2620
|
*/
|
|
2365
2621
|
const webSearchTool = {
|
|
2622
|
+
ask: (_, labels) => labels.searchWebAsk,
|
|
2366
2623
|
label: (_, labels) => labels.searchedWebLabel,
|
|
2367
2624
|
subtitle: (part) => stringOf(recordOf(part.input).query) ?? part.subtitle,
|
|
2368
2625
|
summary: (n, labels) => labels.searchedWeb(n),
|
|
@@ -2410,6 +2667,7 @@ const webSearchTool = {
|
|
|
2410
2667
|
};
|
|
2411
2668
|
/** Reads a web page. Input `{ url }`; the row shows the page's host, the detail links to it. */
|
|
2412
2669
|
const webFetchTool = {
|
|
2670
|
+
ask: (_, labels) => labels.readPageAsk,
|
|
2413
2671
|
label: (_, labels) => labels.readPageLabel,
|
|
2414
2672
|
subtitle: (part) => {
|
|
2415
2673
|
const url = stringOf(recordOf(part.input).url);
|
|
@@ -2550,7 +2808,7 @@ function ActivityGroup({ parts, defaultExpanded = false, className }) {
|
|
|
2550
2808
|
}) : /* @__PURE__ */ jsx(Disclosure, {
|
|
2551
2809
|
isExpanded: expanded,
|
|
2552
2810
|
onExpandedChange: setExpanded,
|
|
2553
|
-
summary: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2811
|
+
summary: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2554
2812
|
/* @__PURE__ */ jsx(Lead, { live }),
|
|
2555
2813
|
/* @__PURE__ */ jsx(Live, {
|
|
2556
2814
|
live,
|
|
@@ -2584,7 +2842,7 @@ const SHIMMER_MS = 2e3;
|
|
|
2584
2842
|
* shared clock, so every live label sweeps in step and a remount does not restart it.
|
|
2585
2843
|
*/
|
|
2586
2844
|
function Live({ live, children }) {
|
|
2587
|
-
return live ? /* @__PURE__ */ jsx(Shimmer, { children }) : /* @__PURE__ */ jsx(Fragment, { children });
|
|
2845
|
+
return live ? /* @__PURE__ */ jsx(Shimmer, { children }) : /* @__PURE__ */ jsx(Fragment$1, { children });
|
|
2588
2846
|
}
|
|
2589
2847
|
function Shimmer({ children }) {
|
|
2590
2848
|
const [delay] = useState(() => `-${Math.round(performance.now() % SHIMMER_MS)}ms`);
|
|
@@ -2602,7 +2860,7 @@ function SingleStep({ part, live, after, expanded, onExpandedChange }) {
|
|
|
2602
2860
|
return /* @__PURE__ */ jsx(Disclosure, {
|
|
2603
2861
|
isExpanded: expanded,
|
|
2604
2862
|
onExpandedChange,
|
|
2605
|
-
summary: /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
2863
|
+
summary: /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
2606
2864
|
/* @__PURE__ */ jsx(Lead, { live }),
|
|
2607
2865
|
/* @__PURE__ */ jsx(Live, {
|
|
2608
2866
|
live,
|
|
@@ -2773,11 +3031,19 @@ function useAttachments({ upload } = {}) {
|
|
|
2773
3031
|
toParts
|
|
2774
3032
|
};
|
|
2775
3033
|
}
|
|
3034
|
+
const IMAGE_EXTENSION = /\.(png|jpe?g|gif|webp|avif|svg|bmp|ico)$/i;
|
|
3035
|
+
/** Whether the card draws a thumbnail: by mime when given, else by `kind`, else by the file extension of the url or name. */
|
|
3036
|
+
function isImage({ mime, kind, url, name }) {
|
|
3037
|
+
if (mime) return mime.startsWith("image/");
|
|
3038
|
+
if (kind) return kind === "image";
|
|
3039
|
+
const path = url && !url.startsWith("data:") ? url.split(/[?#]/)[0] : "";
|
|
3040
|
+
return url?.startsWith("data:image/") === true || IMAGE_EXTENSION.test(path) || IMAGE_EXTENSION.test(name);
|
|
3041
|
+
}
|
|
2776
3042
|
const extension = (name) => /\.([a-z0-9]{1,5})$/i.exec(name)?.[1]?.toUpperCase();
|
|
2777
3043
|
/** A square card: pasted text preview with a PASTED badge, an image thumbnail, or a file with its type. */
|
|
2778
3044
|
function AttachmentCard({ attachment: a, onRemove, onOpen, size = "md", className }) {
|
|
2779
3045
|
const icons = useIcons();
|
|
2780
|
-
const image = a.
|
|
3046
|
+
const image = a.url && isImage(a);
|
|
2781
3047
|
const pasted = a.text !== void 0;
|
|
2782
3048
|
const labels = useLabels();
|
|
2783
3049
|
const uploading = a.status === "uploading";
|
|
@@ -2842,7 +3108,7 @@ function AttachmentRow({ attachments, onRemove, onEditText }) {
|
|
|
2842
3108
|
const [editing, setEditing] = useState();
|
|
2843
3109
|
const labels = useLabels();
|
|
2844
3110
|
if (attachments.length === 0) return null;
|
|
2845
|
-
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx("ul", {
|
|
3111
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("ul", {
|
|
2846
3112
|
"aria-label": labels.attachments,
|
|
2847
3113
|
className: "flex gap-2.5 overflow-x-auto px-0.5 pt-2.5 pb-1 [scrollbar-width:thin]",
|
|
2848
3114
|
children: attachments.map((a) => /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(AttachmentCard, {
|
|
@@ -3012,7 +3278,7 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
|
|
|
3012
3278
|
if (block.kind === "activity") {
|
|
3013
3279
|
const cards = block.parts.filter((p) => p.type === "tool_call" && views[p.name]?.Card);
|
|
3014
3280
|
if (!cards.length) return /* @__PURE__ */ jsx(ActivityGroup, { parts: block.parts }, block.id);
|
|
3015
|
-
return /* @__PURE__ */ jsxs(Fragment
|
|
3281
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(ActivityGroup, { parts: block.parts }), cards.map((p) => {
|
|
3016
3282
|
if (p.type !== "tool_call") return null;
|
|
3017
3283
|
const Card = views[p.name].Card;
|
|
3018
3284
|
return /* @__PURE__ */ jsx(Card, { part: p }, p.id);
|
|
@@ -3054,7 +3320,7 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
|
|
|
3054
3320
|
onPress: onReadAloud,
|
|
3055
3321
|
children: icons.speak
|
|
3056
3322
|
}),
|
|
3057
|
-
onFeedback && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(IconButton, {
|
|
3323
|
+
onFeedback && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(IconButton, {
|
|
3058
3324
|
label: labels.goodResponse,
|
|
3059
3325
|
size: "sm",
|
|
3060
3326
|
onPress: () => onFeedback("good"),
|
|
@@ -3108,7 +3374,7 @@ function ApprovalCard({ approval, onRespond, className }) {
|
|
|
3108
3374
|
children: icons.approval
|
|
3109
3375
|
}), /* @__PURE__ */ jsx("span", {
|
|
3110
3376
|
className: "min-w-0 flex-1 font-medium",
|
|
3111
|
-
children: view.ask?.(part) ?? labels.allowAction(activityLabel(part, labels, view))
|
|
3377
|
+
children: view.ask?.(part, labels) ?? labels.allowAction(view.askLabel?.(part, labels) ?? activityLabel(part, labels, view))
|
|
3112
3378
|
})]
|
|
3113
3379
|
}),
|
|
3114
3380
|
/* @__PURE__ */ jsx("div", {
|
|
@@ -3139,6 +3405,16 @@ function ApprovalCard({ approval, onRespond, className }) {
|
|
|
3139
3405
|
// src/components/context-panel.tsx
|
|
3140
3406
|
const FREE_COLOR = "var(--chat-raised)";
|
|
3141
3407
|
const DEFAULT_COLOR = "var(--chat-subtle)";
|
|
3408
|
+
/** Tokens used (reserved and deferred categories excluded) and tokens still free. */
|
|
3409
|
+
function contextTotals(usage) {
|
|
3410
|
+
const counted = usage.categories.filter((c) => !c.deferred);
|
|
3411
|
+
const used = counted.reduce((sum, c) => sum + (c.reserved ? 0 : c.tokens), 0);
|
|
3412
|
+
const reserved = counted.reduce((sum, c) => sum + (c.reserved ? c.tokens : 0), 0);
|
|
3413
|
+
return {
|
|
3414
|
+
used,
|
|
3415
|
+
free: Math.max(usage.limit - used - reserved, 0)
|
|
3416
|
+
};
|
|
3417
|
+
}
|
|
3142
3418
|
/**
|
|
3143
3419
|
* What fills the context window: a usage line, a segmented bar, one row per category with its share, and
|
|
3144
3420
|
* expandable lists (each MCP tool, each memory file). Deferred categories are listed but not counted.
|
|
@@ -3147,9 +3423,7 @@ function ContextPanel({ usage, title, defaultExpanded = true, className }) {
|
|
|
3147
3423
|
const labels = useLabels();
|
|
3148
3424
|
const counted = usage.categories.filter((c) => !c.deferred);
|
|
3149
3425
|
const deferred = usage.categories.filter((c) => c.deferred);
|
|
3150
|
-
const used
|
|
3151
|
-
const reserved = counted.reduce((sum, c) => sum + (c.reserved ? c.tokens : 0), 0);
|
|
3152
|
-
const free = Math.max(usage.limit - used - reserved, 0);
|
|
3426
|
+
const { used, free } = contextTotals(usage);
|
|
3153
3427
|
const share = (tokens) => formatShare(tokens / usage.limit);
|
|
3154
3428
|
return /* @__PURE__ */ jsxs(Disclosure, {
|
|
3155
3429
|
summary: title ?? labels.context,
|
|
@@ -3274,6 +3548,121 @@ function GroupList({ group }) {
|
|
|
3274
3548
|
function formatShare(fraction) {
|
|
3275
3549
|
return `${(fraction * 100).toFixed(1)}%`;
|
|
3276
3550
|
}
|
|
3551
|
+
// src/components/context-meter.tsx
|
|
3552
|
+
const SIZE = 16;
|
|
3553
|
+
const STROKE = 2;
|
|
3554
|
+
const RADIUS = 7;
|
|
3555
|
+
const CIRCUMFERENCE = 2 * Math.PI * RADIUS;
|
|
3556
|
+
/**
|
|
3557
|
+
* A small progress ring for the composer toolbar, filled to the share of the context window in use (accent,
|
|
3558
|
+
* then warning, then danger). Hover for the numbers; click or Enter opens the ContextPanel above it.
|
|
3559
|
+
*/
|
|
3560
|
+
function ContextMeter({ usage, tooltip, tooltipNote, isLoading = false, warnAt = .75, dangerAt = .9, placement = "top end", className }) {
|
|
3561
|
+
const labels = useLabels();
|
|
3562
|
+
const { used } = contextTotals(usage);
|
|
3563
|
+
const fraction = usage.limit > 0 ? Math.min(used / usage.limit, 1) : 0;
|
|
3564
|
+
const summary = isLoading ? labels.contextLoading : labels.contextSummary(formatTokens(used), formatTokens(usage.limit), Math.round(fraction * 100));
|
|
3565
|
+
const color = fraction >= dangerAt ? "text-chat-danger" : fraction >= warnAt ? "text-chat-warning" : "text-chat-accent";
|
|
3566
|
+
const button = /* @__PURE__ */ jsx(Button, {
|
|
3567
|
+
variant: "ghost",
|
|
3568
|
+
size: "sm",
|
|
3569
|
+
"aria-label": summary,
|
|
3570
|
+
isDisabled: isLoading,
|
|
3571
|
+
className: clsx("size-7 justify-center px-0 text-chat-muted", isLoading && "data-disabled:opacity-100", className),
|
|
3572
|
+
children: /* @__PURE__ */ jsxs("svg", {
|
|
3573
|
+
"aria-hidden": true,
|
|
3574
|
+
viewBox: `0 0 ${SIZE} ${SIZE}`,
|
|
3575
|
+
className: clsx("size-4", isLoading && "motion-safe:animate-spin"),
|
|
3576
|
+
fill: "none",
|
|
3577
|
+
strokeWidth: STROKE,
|
|
3578
|
+
children: [/* @__PURE__ */ jsx("circle", {
|
|
3579
|
+
cx: SIZE / 2,
|
|
3580
|
+
cy: SIZE / 2,
|
|
3581
|
+
r: RADIUS,
|
|
3582
|
+
stroke: "var(--chat-raised)"
|
|
3583
|
+
}), /* @__PURE__ */ jsx("circle", {
|
|
3584
|
+
cx: SIZE / 2,
|
|
3585
|
+
cy: SIZE / 2,
|
|
3586
|
+
r: RADIUS,
|
|
3587
|
+
stroke: "currentColor",
|
|
3588
|
+
strokeLinecap: "round",
|
|
3589
|
+
strokeDasharray: CIRCUMFERENCE,
|
|
3590
|
+
strokeDashoffset: CIRCUMFERENCE * (1 - (isLoading ? .25 : fraction)),
|
|
3591
|
+
transform: `rotate(-90 ${SIZE / 2} ${SIZE / 2})`,
|
|
3592
|
+
className: clsx(color, "transition-[stroke-dashoffset]")
|
|
3593
|
+
})]
|
|
3594
|
+
})
|
|
3595
|
+
});
|
|
3596
|
+
return /* @__PURE__ */ jsxs(DialogTrigger$1, { children: [/* @__PURE__ */ jsx(Tooltip, {
|
|
3597
|
+
content: /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", { children: tooltip ?? summary }), tooltipNote && /* @__PURE__ */ jsx("div", {
|
|
3598
|
+
className: "text-chat-muted",
|
|
3599
|
+
children: tooltipNote
|
|
3600
|
+
})] }),
|
|
3601
|
+
children: button
|
|
3602
|
+
}), /* @__PURE__ */ jsx(Popover, {
|
|
3603
|
+
placement,
|
|
3604
|
+
className: "w-80 overflow-auto p-3",
|
|
3605
|
+
children: /* @__PURE__ */ jsx(Dialog, {
|
|
3606
|
+
"aria-label": labels.context,
|
|
3607
|
+
className: "outline-none",
|
|
3608
|
+
children: /* @__PURE__ */ jsx(ContextPanel, { usage })
|
|
3609
|
+
})
|
|
3610
|
+
})] });
|
|
3611
|
+
}
|
|
3612
|
+
// src/components/copy-field.tsx
|
|
3613
|
+
/** How long "Copied" stays before the button reads "Copy" again. */
|
|
3614
|
+
const COPIED_MS = 2e3;
|
|
3615
|
+
/**
|
|
3616
|
+
* A read-only, selectable field with a Copy button, for share links. The button says "Copied" for about two
|
|
3617
|
+
* seconds (and announces it to screen readers). Where the clipboard is unavailable or refuses, it selects the
|
|
3618
|
+
* text instead so the reader can copy it by hand.
|
|
3619
|
+
*/
|
|
3620
|
+
function CopyField({ value, "aria-label": ariaLabel, onCopy, className }) {
|
|
3621
|
+
const icons = useIcons();
|
|
3622
|
+
const labels = useLabels();
|
|
3623
|
+
const inputRef = useRef(null);
|
|
3624
|
+
const timer = useRef(void 0);
|
|
3625
|
+
const [copied, setCopied] = useState(false);
|
|
3626
|
+
useEffect(() => () => clearTimeout(timer.current), []);
|
|
3627
|
+
const copy = async () => {
|
|
3628
|
+
try {
|
|
3629
|
+
if (!navigator.clipboard) throw new Error("no clipboard");
|
|
3630
|
+
await navigator.clipboard.writeText(value);
|
|
3631
|
+
} catch {
|
|
3632
|
+
inputRef.current?.focus();
|
|
3633
|
+
inputRef.current?.select();
|
|
3634
|
+
return;
|
|
3635
|
+
}
|
|
3636
|
+
setCopied(true);
|
|
3637
|
+
onCopy?.();
|
|
3638
|
+
clearTimeout(timer.current);
|
|
3639
|
+
timer.current = setTimeout(() => setCopied(false), COPIED_MS);
|
|
3640
|
+
};
|
|
3641
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3642
|
+
className: clsx("flex items-center gap-2 font-chat-sans", className),
|
|
3643
|
+
children: [
|
|
3644
|
+
/* @__PURE__ */ jsx("input", {
|
|
3645
|
+
ref: inputRef,
|
|
3646
|
+
readOnly: true,
|
|
3647
|
+
value,
|
|
3648
|
+
"aria-label": ariaLabel ?? labels.copyFieldLabel,
|
|
3649
|
+
onFocus: (e) => e.currentTarget.select(),
|
|
3650
|
+
className: "h-9 min-w-0 flex-1 rounded-chat-md border border-chat-border bg-chat-bg px-3 text-(length:--chat-ui-font-size) text-chat-fg outline-none focus:border-chat-border-strong"
|
|
3651
|
+
}),
|
|
3652
|
+
/* @__PURE__ */ jsxs(Button, {
|
|
3653
|
+
variant: "subtle",
|
|
3654
|
+
size: "lg",
|
|
3655
|
+
onPress: () => void copy(),
|
|
3656
|
+
children: [/* @__PURE__ */ jsx(IconSlot, { children: copied ? icons.copied : icons.copy }), copied ? labels.copied : labels.copy]
|
|
3657
|
+
}),
|
|
3658
|
+
/* @__PURE__ */ jsx("span", {
|
|
3659
|
+
role: "status",
|
|
3660
|
+
className: "sr-only",
|
|
3661
|
+
children: copied ? labels.copied : ""
|
|
3662
|
+
})
|
|
3663
|
+
]
|
|
3664
|
+
});
|
|
3665
|
+
}
|
|
3277
3666
|
// src/components/dictation.tsx
|
|
3278
3667
|
/**
|
|
3279
3668
|
* Voice dictation in the composer, similar to claude.ai's: while listening the words fill the box as they are
|
|
@@ -3773,7 +4162,7 @@ function EffortSlider({ efforts, value, onChange, label, className }) {
|
|
|
3773
4162
|
function EffortButton({ icon, children, className, ...slider }) {
|
|
3774
4163
|
const current = slider.efforts.find((e) => e.id === slider.value) ?? slider.efforts[0];
|
|
3775
4164
|
const labels = useLabels();
|
|
3776
|
-
return /* @__PURE__ */ jsxs(DialogTrigger, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
4165
|
+
return /* @__PURE__ */ jsxs(DialogTrigger$1, { children: [/* @__PURE__ */ jsxs(Button, {
|
|
3777
4166
|
variant: "ghost",
|
|
3778
4167
|
size: "sm",
|
|
3779
4168
|
className: clsx("text-chat-muted data-hovered:text-chat-fg", className),
|
|
@@ -3801,6 +4190,7 @@ function ModelPicker({ models, model, onModelChange, effort, onEffortChange, eff
|
|
|
3801
4190
|
const more = models.filter((m) => m.secondary);
|
|
3802
4191
|
const item = (m) => /* @__PURE__ */ jsx(MenuItem, {
|
|
3803
4192
|
id: m.id,
|
|
4193
|
+
...m.icon && { icon: m.icon },
|
|
3804
4194
|
description: m.disabledReason ?? m.description,
|
|
3805
4195
|
isDisabled: m.disabled ?? false,
|
|
3806
4196
|
children: m.label
|
|
@@ -3815,6 +4205,10 @@ function ModelPicker({ models, model, onModelChange, effort, onEffortChange, eff
|
|
|
3815
4205
|
"aria-label": labels.modelButton(current?.label ?? model),
|
|
3816
4206
|
className: clsx("text-chat-muted data-hovered:text-chat-fg", className),
|
|
3817
4207
|
children: [
|
|
4208
|
+
current?.icon && /* @__PURE__ */ jsx(IconSlot, {
|
|
4209
|
+
className: "text-chat-fg [&_svg]:size-3.5",
|
|
4210
|
+
children: current.icon
|
|
4211
|
+
}),
|
|
3818
4212
|
/* @__PURE__ */ jsx("span", {
|
|
3819
4213
|
className: "min-w-0 truncate text-chat-fg",
|
|
3820
4214
|
children: current?.label ?? model
|
|
@@ -3918,10 +4312,12 @@ function MessageList({ messages, onRetry, onEdit, onFeedback, onReadAloud, actio
|
|
|
3918
4312
|
behavior
|
|
3919
4313
|
});
|
|
3920
4314
|
}, []);
|
|
3921
|
-
const
|
|
4315
|
+
const nearBottom = () => {
|
|
3922
4316
|
const el = scroller.current;
|
|
3923
|
-
|
|
3924
|
-
|
|
4317
|
+
return !el || el.scrollHeight - el.scrollTop - el.clientHeight < STICK_SLOP;
|
|
4318
|
+
};
|
|
4319
|
+
const onScroll = () => {
|
|
4320
|
+
const bottom = nearBottom();
|
|
3925
4321
|
stuck.current = bottom;
|
|
3926
4322
|
setAtBottom(bottom);
|
|
3927
4323
|
};
|
|
@@ -3938,7 +4334,7 @@ function MessageList({ messages, onRetry, onEdit, onFeedback, onReadAloud, actio
|
|
|
3938
4334
|
const jumpedFor = useRef(start === "top" ? lastUser : null);
|
|
3939
4335
|
useLayoutEffect(() => {
|
|
3940
4336
|
if (lastUser === jumpedFor.current) {
|
|
3941
|
-
|
|
4337
|
+
setAtBottom(nearBottom());
|
|
3942
4338
|
return;
|
|
3943
4339
|
}
|
|
3944
4340
|
jumpedFor.current = lastUser;
|
|
@@ -4084,7 +4480,7 @@ function ChatTitleMenu({ pinned, onPin, onRename, projects, onAddToProject, onCr
|
|
|
4084
4480
|
children: labels.shareChat
|
|
4085
4481
|
}),
|
|
4086
4482
|
children,
|
|
4087
|
-
onDelete && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(MenuSeparator, {}), /* @__PURE__ */ jsx(MenuItem, {
|
|
4483
|
+
onDelete && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(MenuSeparator, {}), /* @__PURE__ */ jsx(MenuItem, {
|
|
4088
4484
|
id: "delete",
|
|
4089
4485
|
icon: icons.delete,
|
|
4090
4486
|
shortcut: "D",
|
|
@@ -4186,15 +4582,22 @@ function ChatRow({ chat, active, nested, onSelect, menu }) {
|
|
|
4186
4582
|
role: "img",
|
|
4187
4583
|
"aria-label": labels.running,
|
|
4188
4584
|
className: clsx(markerClass, "animate-pulse bg-chat-accent motion-reduce:animate-none")
|
|
4189
|
-
}) : chat.unread ? /* @__PURE__ */ jsx("span", {
|
|
4585
|
+
}) : chat.unread && !chat.muted ? /* @__PURE__ */ jsx("span", {
|
|
4190
4586
|
role: "img",
|
|
4191
4587
|
"aria-label": labels.unread,
|
|
4192
4588
|
className: clsx(markerClass, "bg-chat-check")
|
|
4193
4589
|
}) : null;
|
|
4194
|
-
const content = /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
4195
|
-
|
|
4196
|
-
|
|
4197
|
-
|
|
4590
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
4591
|
+
/* @__PURE__ */ jsx("span", {
|
|
4592
|
+
className: clsx("min-w-0 flex-1 truncate", chat.unread && !active && !chat.muted && "font-medium", chat.muted && "text-chat-muted"),
|
|
4593
|
+
children: chat.title
|
|
4594
|
+
}),
|
|
4595
|
+
chat.detail != null && /* @__PURE__ */ jsx("span", {
|
|
4596
|
+
className: "shrink-0 text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle tabular-nums",
|
|
4597
|
+
children: chat.detail
|
|
4598
|
+
}),
|
|
4599
|
+
marker
|
|
4600
|
+
] });
|
|
4198
4601
|
const common = {
|
|
4199
4602
|
className: clsx(rowClass, nested && "pl-8", active && "bg-chat-selected", menu && (menuOpen || active ? "pr-8" : "group-hover/item:pr-8 group-focus-within/item:pr-8"), menu && "[@media(hover:none)]:pr-10"),
|
|
4200
4603
|
"aria-current": active ? "page" : void 0,
|
|
@@ -4231,7 +4634,7 @@ function ProjectRow({ project, activeId, row }) {
|
|
|
4231
4634
|
const holdsActive = project.chats.some((c) => c.id === activeId);
|
|
4232
4635
|
return /* @__PURE__ */ jsx("li", { children: /* @__PURE__ */ jsx(Disclosure$1, {
|
|
4233
4636
|
defaultExpanded: project.defaultExpanded ?? holdsActive,
|
|
4234
|
-
children: ({ isExpanded }) => /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Heading, {
|
|
4637
|
+
children: ({ isExpanded }) => /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx(Heading, {
|
|
4235
4638
|
className: "m-0 font-normal",
|
|
4236
4639
|
children: /* @__PURE__ */ jsxs(Button$1, {
|
|
4237
4640
|
slot: "trigger",
|
|
@@ -4290,6 +4693,7 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
|
|
|
4290
4693
|
const labels = useLabels();
|
|
4291
4694
|
const pending = onApprove && approvals?.[0];
|
|
4292
4695
|
const box = (variant) => {
|
|
4696
|
+
if (composer === false) return null;
|
|
4293
4697
|
const element = /* @__PURE__ */ jsx(Composer, {
|
|
4294
4698
|
variant: density === "inline" || short && !empty ? "compact" : variant,
|
|
4295
4699
|
busy,
|
|
@@ -4423,41 +4827,84 @@ function ChatTranscript({ messages, renderPart, widgets, header, notice, footer,
|
|
|
4423
4827
|
});
|
|
4424
4828
|
}
|
|
4425
4829
|
// src/views/app-shell.tsx
|
|
4830
|
+
const DEFAULT_WIDTH = 288;
|
|
4831
|
+
const KEY_STEP = 16;
|
|
4832
|
+
const clampWidth = (px, min, max) => Math.min(max, Math.max(min, Math.round(px)));
|
|
4833
|
+
function readStoredWidth(key) {
|
|
4834
|
+
if (!key) return void 0;
|
|
4835
|
+
try {
|
|
4836
|
+
const n = Number(window.localStorage.getItem(key));
|
|
4837
|
+
return n > 0 ? n : void 0;
|
|
4838
|
+
} catch {
|
|
4839
|
+
return;
|
|
4840
|
+
}
|
|
4841
|
+
}
|
|
4426
4842
|
/**
|
|
4427
4843
|
* A page with a sidebar: pinned and collapsible to an icon rail from tablet width up, a drawer over the
|
|
4428
4844
|
* content on phones. Children get `useSidebar()` and `<SidebarToggle>`.
|
|
4429
4845
|
*/
|
|
4430
|
-
function AppShell({ sidebar, children, defaultCollapsed, className }) {
|
|
4846
|
+
function AppShell({ sidebar, children, defaultCollapsed, collapsed: collapsedProp, onCollapsedChange, scroll = "inner", resizable, width: widthProp, defaultWidth, onWidthChange, persistKey, shortcut = true, className }) {
|
|
4431
4847
|
const drawer = useMediaQuery(DRAWER_QUERY);
|
|
4432
|
-
const
|
|
4848
|
+
const labels = useLabels();
|
|
4849
|
+
const [collapsed, setCollapsed] = useControllable(collapsedProp, () => defaultCollapsed ?? (typeof window !== "undefined" && window.matchMedia("(max-width: 1023.98px)").matches), onCollapsedChange);
|
|
4433
4850
|
const [drawerOpen, setDrawerOpen] = useState(false);
|
|
4434
|
-
const toggle = useCallback(() => drawer ? setDrawerOpen((o) => !o) : setCollapsed((c) => !c), [drawer]);
|
|
4851
|
+
const toggle = useCallback(() => drawer ? setDrawerOpen((o) => !o) : setCollapsed((c) => !c), [drawer, setCollapsed]);
|
|
4435
4852
|
const dismiss = useCallback(() => setDrawerOpen(false), []);
|
|
4853
|
+
const min = resizable?.min ?? 0;
|
|
4854
|
+
const max = resizable?.max ?? Infinity;
|
|
4855
|
+
const [rawWidth, setWidth] = useControllable(widthProp, () => readStoredWidth(persistKey) ?? defaultWidth ?? DEFAULT_WIDTH, (px) => {
|
|
4856
|
+
if (persistKey) try {
|
|
4857
|
+
window.localStorage.setItem(persistKey, String(px));
|
|
4858
|
+
} catch {}
|
|
4859
|
+
onWidthChange?.(px);
|
|
4860
|
+
});
|
|
4861
|
+
const width = resizable ? clampWidth(rawWidth, min, max) : void 0;
|
|
4862
|
+
const shortcutKeys = shortcut ? `${isMacPlatform() ? "⌘" : labels.ctrlKey} B` : void 0;
|
|
4863
|
+
useEffect(() => {
|
|
4864
|
+
if (!shortcut) return;
|
|
4865
|
+
const mac = isMacPlatform();
|
|
4866
|
+
const onKey = (e) => {
|
|
4867
|
+
if (e.defaultPrevented || e.altKey || e.shiftKey || e.key.toLowerCase() !== "b") return;
|
|
4868
|
+
if (mac ? !e.metaKey || e.ctrlKey : !e.ctrlKey || e.metaKey) return;
|
|
4869
|
+
e.preventDefault();
|
|
4870
|
+
toggle();
|
|
4871
|
+
};
|
|
4872
|
+
window.addEventListener("keydown", onKey);
|
|
4873
|
+
return () => window.removeEventListener("keydown", onKey);
|
|
4874
|
+
}, [shortcut, toggle]);
|
|
4436
4875
|
const pinned = useMemo(() => ({
|
|
4437
4876
|
collapsed,
|
|
4438
4877
|
drawer,
|
|
4439
4878
|
drawerOpen,
|
|
4440
4879
|
toggle,
|
|
4441
|
-
dismiss
|
|
4880
|
+
dismiss,
|
|
4881
|
+
...shortcutKeys && { shortcut: shortcutKeys }
|
|
4442
4882
|
}), [
|
|
4443
4883
|
collapsed,
|
|
4444
4884
|
drawer,
|
|
4445
4885
|
drawerOpen,
|
|
4446
4886
|
toggle,
|
|
4447
|
-
dismiss
|
|
4887
|
+
dismiss,
|
|
4888
|
+
shortcutKeys
|
|
4448
4889
|
]);
|
|
4449
|
-
const labels = useLabels();
|
|
4450
4890
|
const inDrawer = useMemo(() => ({
|
|
4451
4891
|
...pinned,
|
|
4452
4892
|
collapsed: false
|
|
4453
4893
|
}), [pinned]);
|
|
4894
|
+
const windowScroll = scroll === "window";
|
|
4454
4895
|
return /* @__PURE__ */ jsxs(SidebarContext.Provider, {
|
|
4455
4896
|
value: pinned,
|
|
4456
4897
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
4457
|
-
className: clsx("flex
|
|
4458
|
-
children: [!drawer && /* @__PURE__ */
|
|
4459
|
-
|
|
4460
|
-
|
|
4898
|
+
className: clsx("flex bg-chat-bg text-chat-fg", windowScroll ? "min-h-dvh" : "h-dvh overflow-hidden", className),
|
|
4899
|
+
children: [!drawer && /* @__PURE__ */ jsxs("aside", {
|
|
4900
|
+
style: width ? { "--chat-sidebar-width": `${width}px` } : void 0,
|
|
4901
|
+
className: clsx("relative shrink-0 border-r border-chat-divider bg-chat-sidebar transition-[width] duration-200 ease-out motion-reduce:transition-none data-resizing:transition-none", windowScroll ? "sticky top-0 h-dvh self-start overflow-clip" : "overflow-hidden", collapsed ? "w-13" : "w-(--chat-sidebar-width,18rem)"),
|
|
4902
|
+
children: [sidebar, resizable && !collapsed && width && /* @__PURE__ */ jsx(ResizeHandle, {
|
|
4903
|
+
width,
|
|
4904
|
+
min,
|
|
4905
|
+
max,
|
|
4906
|
+
onCommit: setWidth
|
|
4907
|
+
})]
|
|
4461
4908
|
}), /* @__PURE__ */ jsx("main", {
|
|
4462
4909
|
className: "flex min-w-0 flex-1 flex-col",
|
|
4463
4910
|
children
|
|
@@ -4473,15 +4920,58 @@ function AppShell({ sidebar, children, defaultCollapsed, className }) {
|
|
|
4473
4920
|
})]
|
|
4474
4921
|
});
|
|
4475
4922
|
}
|
|
4476
|
-
/**
|
|
4923
|
+
/** The draggable right edge of the sidebar. A drag moves the aside's width variable alone and commits on release. */
|
|
4924
|
+
function ResizeHandle({ width, min, max, onCommit }) {
|
|
4925
|
+
const labels = useLabels();
|
|
4926
|
+
const live = useRef(null);
|
|
4927
|
+
const aside = (el) => el.closest("aside");
|
|
4928
|
+
const move = (el, px) => {
|
|
4929
|
+
live.current = clampWidth(px, min, max);
|
|
4930
|
+
aside(el).style.setProperty("--chat-sidebar-width", `${live.current}px`);
|
|
4931
|
+
};
|
|
4932
|
+
const end = (el) => {
|
|
4933
|
+
delete aside(el).dataset.resizing;
|
|
4934
|
+
if (live.current != null) onCommit(live.current);
|
|
4935
|
+
live.current = null;
|
|
4936
|
+
};
|
|
4937
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4938
|
+
role: "separator",
|
|
4939
|
+
"aria-orientation": "vertical",
|
|
4940
|
+
"aria-label": labels.resizeSidebar,
|
|
4941
|
+
"aria-valuenow": width,
|
|
4942
|
+
"aria-valuemin": min,
|
|
4943
|
+
"aria-valuemax": max,
|
|
4944
|
+
tabIndex: 0,
|
|
4945
|
+
className: "group/handle absolute inset-y-0 right-0 z-10 w-1.5 cursor-col-resize touch-none outline-none",
|
|
4946
|
+
onPointerDown: (e) => {
|
|
4947
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
4948
|
+
aside(e.currentTarget).dataset.resizing = "";
|
|
4949
|
+
live.current = null;
|
|
4950
|
+
},
|
|
4951
|
+
onPointerMove: (e) => {
|
|
4952
|
+
if (e.currentTarget.hasPointerCapture(e.pointerId)) move(e.currentTarget, e.clientX - aside(e.currentTarget).getBoundingClientRect().left);
|
|
4953
|
+
},
|
|
4954
|
+
onPointerUp: (e) => end(e.currentTarget),
|
|
4955
|
+
onPointerCancel: (e) => end(e.currentTarget),
|
|
4956
|
+
onKeyDown: (e) => {
|
|
4957
|
+
const step = e.shiftKey ? 64 : KEY_STEP;
|
|
4958
|
+
const next = e.key === "ArrowLeft" ? width - step : e.key === "ArrowRight" ? width + step : e.key === "Home" ? min : e.key === "End" ? max : null;
|
|
4959
|
+
if (next == null) return;
|
|
4960
|
+
e.preventDefault();
|
|
4961
|
+
onCommit(clampWidth(next, min, max));
|
|
4962
|
+
},
|
|
4963
|
+
children: /* @__PURE__ */ jsx("span", { className: "absolute inset-y-0 right-0 w-px bg-transparent transition-colors group-hover/handle:bg-chat-border-strong group-focus-visible/handle:w-0.5 group-focus-visible/handle:bg-chat-focus in-data-resizing:bg-chat-focus" })
|
|
4964
|
+
});
|
|
4965
|
+
}
|
|
4966
|
+
/** Opens the drawer on phones, collapses / expands the pinned sidebar elsewhere. Its label names the shortcut, e.g. "(⌘ B)". */
|
|
4477
4967
|
function SidebarToggle({ drawerOnly = false, className }) {
|
|
4478
4968
|
const icons = useIcons();
|
|
4479
|
-
const { drawer, drawerOpen, collapsed, toggle } = useSidebar();
|
|
4969
|
+
const { drawer, drawerOpen, collapsed, toggle, shortcut } = useSidebar();
|
|
4480
4970
|
const labels = useLabels();
|
|
4481
4971
|
if (drawerOnly && !drawer) return null;
|
|
4482
4972
|
const label = drawer ? drawerOpen ? labels.closeSidebar : labels.openSidebar : collapsed ? labels.expandSidebar : labels.collapseSidebar;
|
|
4483
4973
|
return /* @__PURE__ */ jsx(IconButton, {
|
|
4484
|
-
label,
|
|
4974
|
+
label: shortcut ? labels.withShortcut(label, shortcut) : label,
|
|
4485
4975
|
onPress: toggle,
|
|
4486
4976
|
className,
|
|
4487
4977
|
children: icons.sidebar
|
|
@@ -4489,7 +4979,7 @@ function SidebarToggle({ drawerOnly = false, className }) {
|
|
|
4489
4979
|
}
|
|
4490
4980
|
// src/views/sidebar.tsx
|
|
4491
4981
|
/** The claude.ai sidebar frame. Collapses to an icon rail inside `AppShell`; a drawer on phones. */
|
|
4492
|
-
function Sidebar({ brand, brandIcon, headerActions, nav, children, footer, className }) {
|
|
4982
|
+
function Sidebar({ brand, brandIcon, headerActions, railActions, nav, children, footer, className }) {
|
|
4493
4983
|
const { collapsed } = useSidebar();
|
|
4494
4984
|
return /* @__PURE__ */ jsxs("div", {
|
|
4495
4985
|
className: clsx("flex h-full flex-col font-chat-sans", collapsed ? "w-13" : "w-full", className),
|
|
@@ -4515,8 +5005,12 @@ function Sidebar({ brand, brandIcon, headerActions, nav, children, footer, class
|
|
|
4515
5005
|
/* @__PURE__ */ jsx(SidebarToggle, {})
|
|
4516
5006
|
]
|
|
4517
5007
|
}),
|
|
4518
|
-
|
|
4519
|
-
className:
|
|
5008
|
+
collapsed && railActions && /* @__PURE__ */ jsx("div", {
|
|
5009
|
+
className: "flex shrink-0 flex-col items-center gap-1 pb-2",
|
|
5010
|
+
children: railActions
|
|
5011
|
+
}),
|
|
5012
|
+
nav && /* @__PURE__ */ jsx(SidebarList, {
|
|
5013
|
+
className: clsx("shrink-0 px-2 pb-2", collapsed && "items-center"),
|
|
4520
5014
|
children: nav
|
|
4521
5015
|
}),
|
|
4522
5016
|
!collapsed && /* @__PURE__ */ jsx("div", {
|
|
@@ -4531,32 +5025,65 @@ function Sidebar({ brand, brandIcon, headerActions, nav, children, footer, class
|
|
|
4531
5025
|
]
|
|
4532
5026
|
});
|
|
4533
5027
|
}
|
|
4534
|
-
/**
|
|
4535
|
-
function
|
|
5028
|
+
/** The `<ul>` for `SidebarItem`s outside `Sidebar`'s `nav` slot (e.g. inside a section): no bullets, no padding. */
|
|
5029
|
+
function SidebarList({ children, className }) {
|
|
5030
|
+
return /* @__PURE__ */ jsx("ul", {
|
|
5031
|
+
className: clsx("m-0 flex list-none flex-col gap-px p-0", className),
|
|
5032
|
+
children
|
|
5033
|
+
});
|
|
5034
|
+
}
|
|
5035
|
+
const iconSize = "[&_svg]:size-(--chat-sidebar-icon-size,1rem) [&_svg]:shrink-0";
|
|
5036
|
+
/**
|
|
5037
|
+
* A nav row: icon and label; just the icon with a tooltip when collapsed. Closes the drawer when picked.
|
|
5038
|
+
* Icons are 1rem, or `--chat-sidebar-icon-size`.
|
|
5039
|
+
*/
|
|
5040
|
+
function SidebarItem({ icon, label, onPress, href, active, shortcut, badge, description, trailing, title, className: extra, ...rest }) {
|
|
4536
5041
|
const { collapsed, dismiss } = useSidebar();
|
|
4537
|
-
const
|
|
5042
|
+
const data = Object.fromEntries(Object.entries(rest).filter(([k]) => k.startsWith("data-")));
|
|
5043
|
+
const hasTrailing = !collapsed && trailing != null;
|
|
5044
|
+
const className = clsx("group flex min-h-8 cursor-pointer items-center gap-2.5 rounded-chat-sm text-left text-(length:--chat-ui-font-size) leading-5 text-chat-fg outline-none [@media(pointer:coarse)]:min-h-10", "data-hovered:bg-chat-hover data-pressed:bg-chat-selected data-focus-visible:ring-2 data-focus-visible:ring-chat-focus", iconSize, collapsed ? "relative w-9 justify-center [@media(pointer:coarse)]:w-10" : "w-full px-2", description && !collapsed && "py-1", hasTrailing && "group-hover/item:pr-9 group-focus-within/item:pr-9 [@media(hover:none)]:pr-10", active && "bg-chat-selected");
|
|
4538
5045
|
const press = () => {
|
|
4539
5046
|
onPress?.();
|
|
4540
5047
|
dismiss();
|
|
4541
5048
|
};
|
|
4542
|
-
const
|
|
5049
|
+
const showIfCut = (e) => {
|
|
5050
|
+
const el = e.currentTarget;
|
|
5051
|
+
el.title = el.scrollWidth > el.clientWidth ? label : "";
|
|
5052
|
+
};
|
|
5053
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [
|
|
4543
5054
|
icon,
|
|
4544
|
-
collapsed ? /* @__PURE__ */
|
|
5055
|
+
collapsed ? /* @__PURE__ */ jsxs("span", {
|
|
4545
5056
|
className: "sr-only",
|
|
4546
|
-
children: label
|
|
4547
|
-
}) : /* @__PURE__ */
|
|
4548
|
-
className: "min-w-0 flex-1
|
|
4549
|
-
children:
|
|
5057
|
+
children: [label, (typeof badge === "string" || typeof badge === "number") && ` (${badge})`]
|
|
5058
|
+
}) : /* @__PURE__ */ jsxs("span", {
|
|
5059
|
+
className: "flex min-w-0 flex-1 flex-col",
|
|
5060
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5061
|
+
className: "truncate",
|
|
5062
|
+
onPointerEnter: title ? void 0 : showIfCut,
|
|
5063
|
+
children: label
|
|
5064
|
+
}), description && /* @__PURE__ */ jsx("span", {
|
|
5065
|
+
className: "truncate text-(length:--chat-ui-font-size-sm) leading-4 text-chat-muted",
|
|
5066
|
+
children: description
|
|
5067
|
+
})]
|
|
4550
5068
|
}),
|
|
4551
5069
|
!collapsed && shortcut && /* @__PURE__ */ jsx("kbd", {
|
|
4552
5070
|
className: "font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle opacity-0 group-data-hovered:opacity-100",
|
|
4553
5071
|
children: shortcut
|
|
5072
|
+
}),
|
|
5073
|
+
!collapsed && badge != null && /* @__PURE__ */ jsx("span", {
|
|
5074
|
+
className: "shrink-0 text-(length:--chat-ui-font-size-sm) leading-4 text-chat-muted tabular-nums",
|
|
5075
|
+
children: badge
|
|
5076
|
+
}),
|
|
5077
|
+
collapsed && badge != null && /* @__PURE__ */ jsx("span", {
|
|
5078
|
+
"aria-hidden": true,
|
|
5079
|
+
className: "absolute top-1 right-1 size-2 rounded-full bg-chat-accent"
|
|
4554
5080
|
})
|
|
4555
5081
|
] });
|
|
4556
5082
|
const common = {
|
|
4557
5083
|
className,
|
|
4558
5084
|
onPress: press,
|
|
4559
|
-
"aria-current": active ? "page" : void 0
|
|
5085
|
+
"aria-current": active ? "page" : void 0,
|
|
5086
|
+
...title && { title }
|
|
4560
5087
|
};
|
|
4561
5088
|
const control = href ? /* @__PURE__ */ jsx(Link, {
|
|
4562
5089
|
...common,
|
|
@@ -4566,11 +5093,18 @@ function SidebarItem({ icon, label, onPress, href, active, shortcut }) {
|
|
|
4566
5093
|
...common,
|
|
4567
5094
|
children: content
|
|
4568
5095
|
});
|
|
4569
|
-
return /* @__PURE__ */
|
|
4570
|
-
|
|
4571
|
-
|
|
4572
|
-
children:
|
|
4573
|
-
|
|
5096
|
+
return /* @__PURE__ */ jsxs("li", {
|
|
5097
|
+
...data,
|
|
5098
|
+
className: clsx("group/item relative list-none", extra),
|
|
5099
|
+
children: [collapsed ? /* @__PURE__ */ jsx(Tooltip, {
|
|
5100
|
+
content: label,
|
|
5101
|
+
placement: "right",
|
|
5102
|
+
children: control
|
|
5103
|
+
}) : control, hasTrailing && /* @__PURE__ */ jsx("div", {
|
|
5104
|
+
className: "absolute top-1/2 right-1 flex -translate-y-1/2 items-center opacity-0 group-hover/item:opacity-100 group-focus-within/item:opacity-100 [@media(hover:none)]:opacity-100",
|
|
5105
|
+
children: trailing
|
|
5106
|
+
})]
|
|
5107
|
+
});
|
|
4574
5108
|
}
|
|
4575
5109
|
const initials = (name) => name.split(/\s+/).slice(0, 2).map((w) => w[0]?.toUpperCase() ?? "").join("");
|
|
4576
5110
|
/** The account row at the bottom: avatar, name, plan, and a menu. */
|
|
@@ -4589,7 +5123,7 @@ function SidebarUser({ name, detail, avatarUrl, menu }) {
|
|
|
4589
5123
|
const button = /* @__PURE__ */ jsxs(Button$1, {
|
|
4590
5124
|
"aria-label": collapsed ? name : void 0,
|
|
4591
5125
|
className: clsx("flex h-12 w-full cursor-pointer items-center gap-2.5 rounded-chat-md text-left outline-none", "data-hovered:bg-chat-hover data-pressed:bg-chat-selected data-focus-visible:ring-2 data-focus-visible:ring-chat-focus", collapsed ? "justify-center" : "px-1.5"),
|
|
4592
|
-
children: [avatar, !collapsed && /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("span", {
|
|
5126
|
+
children: [avatar, !collapsed && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
|
|
4593
5127
|
className: "flex min-w-0 flex-1 flex-col",
|
|
4594
5128
|
children: [/* @__PURE__ */ jsx("span", {
|
|
4595
5129
|
className: "truncate text-(length:--chat-ui-font-size) leading-5 text-chat-fg",
|
|
@@ -4609,4 +5143,4 @@ function SidebarUser({ name, detail, avatarUrl, menu }) {
|
|
|
4609
5143
|
children: menu
|
|
4610
5144
|
})] });
|
|
4611
5145
|
}
|
|
4612
|
-
export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextPanel, CopyButton, DRAWER_QUERY, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, SegmentedControl, Sidebar, SidebarItem, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
|
|
5146
|
+
export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextMeter, ContextPanel, CopyButton, CopyField, DRAWER_QUERY, DialogTrigger, DictationButton, DictationControls, Disclosure, Drawer, EffortButton, EffortSlider, EmptyState, IconButton, Markdown, MarkdownLinksProvider, Menu, MenuItem, MenuNote, MenuSearch, MenuSection, MenuSeparator, MenuTrigger, MessageList, Modal, ModelPicker, Panel, PastedTextDialog, Popover, PopoverTrigger, SegmentedControl, Sidebar, SidebarItem, SidebarList, SidebarToggle, SidebarUser, SubmenuTrigger, TokenCounter, ToolViewsProvider, Tooltip, UiComponentsProvider, UiMarkupBlock, UiTree, UserMessage, VoiceMeter, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, encodeWav, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useDictation, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
|