agentic-ui-kit 0.4.0 → 0.5.1

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 CHANGED
@@ -1,10 +1,10 @@
1
1
  "use client";
2
- import { A as salvageJson, C as awaitingToolCalls, O as settleToolCall, S as applyEvent, b as toBlocks, d as isStateRef, f as matchesSchema, g as uiActionSchema, i as parseUiMarkup, v as readEventStream, x as activityTokens } from "./ui-markup-DtMYer7I.js";
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-Ce7xTfGU.js";
3
3
  import { clsx } from "clsx";
4
- import { Autocomplete, Button as Button$1, Dialog, 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";
4
+ import { Autocomplete, Button as Button$1, Dialog, DialogTrigger, 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";
5
5
  import { Fragment, jsx, jsxs } from "react/jsx-runtime";
6
+ import { Component, Fragment as Fragment$1, createContext, createElement, memo, useCallback, useContext, useEffect, useId, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
6
7
  import { AlertTriangle, ArrowDown, ArrowUp, Check, ChevronDown, ChevronRight, ChevronsUpDown, Copy, FileText, Folder, FolderOpen, FolderPlus, Globe, Info, MoreHorizontal, PanelLeft, Pencil, Pin, PinOff, Plus, RefreshCw, Share, ShieldQuestion, Square, ThumbsDown, ThumbsUp, Trash2, Volume2, X } from "lucide-react";
7
- import { Component, Fragment as Fragment$1, createContext, createElement, memo, useCallback, useContext, useEffect, useLayoutEffect, useMemo, useRef, useState, useSyncExternalStore } from "react";
8
8
  import { UNSAFE_PortalProvider } from "react-aria";
9
9
  import { createMathPlugin } from "@streamdown/math";
10
10
  import { Streamdown, defaultRehypePlugins } from "streamdown";
@@ -74,37 +74,6 @@ function Badge({ children, tone = "outline", icon, className }) {
74
74
  children: [icon, children]
75
75
  });
76
76
  }
77
- // src/primitives/segmented-control.tsx
78
- /**
79
- * One of a few options, side by side on a track, like claude.ai's Chat / Cowork switch. Themed with
80
- * `--chat-segment-track` (default: a shade darker than the surface it sits on) and `--chat-segment-selected`
81
- * (default `--chat-raised`).
82
- */
83
- function SegmentedControl({ label, options, value, onChange, className }) {
84
- return /* @__PURE__ */ jsx(ToggleButtonGroup, {
85
- "aria-label": label,
86
- selectionMode: "single",
87
- disallowEmptySelection: true,
88
- selectedKeys: [value],
89
- onSelectionChange: (keys) => {
90
- const next = [...keys][0];
91
- if (next !== void 0) onChange(next);
92
- },
93
- className: clsx("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),
94
- children: options.map((o) => {
95
- const button = /* @__PURE__ */ jsxs(ToggleButton, {
96
- id: o.id,
97
- ...o.iconOnly && { "aria-label": o.label },
98
- className: clsx("inline-flex h-6 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", o.iconOnly ? "w-7" : "px-2", "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)]"),
99
- children: [o.icon, !o.iconOnly && o.label]
100
- }, o.id);
101
- return o.iconOnly ? /* @__PURE__ */ jsx(Tooltip, {
102
- content: o.label,
103
- children: button
104
- }, o.id) : button;
105
- })
106
- });
107
- }
108
77
  // src/primitives/caret.tsx
109
78
  /** The small filled triangle claude.ai uses after expandable labels. Points right; rotate to point down. */
110
79
  function Caret({ className }) {
@@ -182,6 +151,176 @@ function IconSlot({ children, className }) {
182
151
  children
183
152
  });
184
153
  }
154
+ // src/primitives/labels.tsx
155
+ const plural = (count, one, many) => count === 1 ? one : many(count);
156
+ /** The English strings. */
157
+ const defaultLabels = {
158
+ thinking: "Thinking…",
159
+ thought: "Thought",
160
+ thoughtFor: (duration) => `Thought for ${duration}`,
161
+ waitingForYou: " · waiting for you",
162
+ usedTool: (name) => `used ${name}`,
163
+ joinSummaries: (phrases) => {
164
+ const text = phrases.join(", ");
165
+ return text.charAt(0).toUpperCase() + text.slice(1);
166
+ },
167
+ duration: (seconds) => seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`,
168
+ ranCommandLabel: "Ran a command",
169
+ ranCommands: (count) => plural(count, "ran a command", (n) => `ran ${n} commands`),
170
+ searchedWebLabel: "Searched the web",
171
+ readPageLabel: "Read page",
172
+ readPages: (count) => plural(count, "read a page", (n) => `read ${n} pages`),
173
+ searchedWeb: (count) => plural(count, "searched the web", (n) => `searched the web ${n} times`),
174
+ recalledMemoryLabel: "Recalled memory",
175
+ recalledMemory: () => "recalled memory",
176
+ output: "Output",
177
+ noOutput: "(no output)",
178
+ noResults: "No results",
179
+ reasoningHidden: "The model's reasoning is not shown.",
180
+ allowTool: (name) => `Allow ${name}?`,
181
+ allowAction: (label) => /* @__PURE__ */ jsxs(Fragment, { children: [
182
+ "Allow ",
183
+ label,
184
+ "?"
185
+ ] }),
186
+ allow: "Allow",
187
+ deny: "Deny",
188
+ sidebar: "Sidebar",
189
+ closeSidebar: "Close sidebar",
190
+ openSidebar: "Open sidebar",
191
+ expandSidebar: "Expand sidebar",
192
+ collapseSidebar: "Collapse sidebar",
193
+ context: "Context",
194
+ contextUsed: "Context used",
195
+ percentUsed: (percent) => `${percent}% used`,
196
+ freeSpace: "Free space",
197
+ tokenCount: (count) => `${count} tokens`,
198
+ replyPlaceholder: "Reply…",
199
+ replyDirectly: "Or reply directly…",
200
+ message: "Message",
201
+ stop: "Stop",
202
+ send: "Send",
203
+ addFiles: "Add files and more",
204
+ attachments: "Attachments",
205
+ editPastedText: "Edit pasted text",
206
+ viewPastedText: "View pasted text",
207
+ removeAttachment: (name) => `Remove ${name}`,
208
+ pastedText: "Pasted text",
209
+ pasted: "Pasted",
210
+ failed: "Failed",
211
+ file: "File",
212
+ uploadFailed: "Upload failed",
213
+ cancel: "Cancel",
214
+ close: "Close",
215
+ save: "Save",
216
+ chats: "Chats",
217
+ running: "Running",
218
+ unread: "Unread",
219
+ moreOptionsFor: (title) => `More options for ${title}`,
220
+ new: "New",
221
+ viewAll: "View all",
222
+ chatActions: "Chat actions",
223
+ pin: "Pin",
224
+ unpin: "Unpin",
225
+ rename: "Rename",
226
+ addToProject: "Add to project",
227
+ searchProjects: "Search or create a project",
228
+ projects: "Projects",
229
+ newProject: "New project",
230
+ shareChat: "Share chat",
231
+ delete: "Delete",
232
+ justNow: "just now",
233
+ copy: "Copy",
234
+ copied: "Copied",
235
+ retry: "Retry",
236
+ edit: "Edit",
237
+ readAloud: "Read aloud",
238
+ goodResponse: "Good response",
239
+ badResponse: "Bad response",
240
+ somethingWentWrong: "Something went wrong.",
241
+ conversation: "Conversation",
242
+ scrollToBottom: "Scroll to bottom",
243
+ showMore: "Show more",
244
+ showLess: "Show less",
245
+ model: "Model",
246
+ modelButton: (label) => `Model: ${label}`,
247
+ effort: "Effort",
248
+ effortNote: "Higher effort means more thorough responses, but takes longer and uses your limits faster.",
249
+ recommended: "Recommended",
250
+ effortFaster: "Faster",
251
+ effortSmarter: "Smarter",
252
+ moreModels: "More models",
253
+ widget: "Widget",
254
+ visualUpdated: "Updated below ↓",
255
+ visualUpdating: "Updating…",
256
+ drawingWidget: "Drawing"
257
+ };
258
+ const LabelsContext = createContext(defaultLabels);
259
+ /** Merges `labels` over the labels of the enclosing provider (or the defaults). */
260
+ function LabelsProvider({ labels, children }) {
261
+ const parent = useLabels();
262
+ const merged = useMemo(() => labels ? {
263
+ ...parent,
264
+ ...labels
265
+ } : parent, [parent, labels]);
266
+ return /* @__PURE__ */ jsx(LabelsContext.Provider, {
267
+ value: merged,
268
+ children
269
+ });
270
+ }
271
+ /** The active UI strings: the enclosing `ChatTheme`'s `labels` over the English defaults. */
272
+ function useLabels() {
273
+ return useContext(LabelsContext);
274
+ }
275
+ // src/primitives/copy-button.tsx
276
+ /** Copies `text`; shows a check for a moment after. */
277
+ function CopyButton({ text, className }) {
278
+ const icons = useIcons();
279
+ const labels = useLabels();
280
+ const [copied, setCopied] = useState(false);
281
+ return /* @__PURE__ */ jsx(IconButton, {
282
+ label: copied ? labels.copied : labels.copy,
283
+ size: "sm",
284
+ ...className && { className },
285
+ onPress: () => {
286
+ navigator.clipboard?.writeText(text);
287
+ setCopied(true);
288
+ setTimeout(() => setCopied(false), 1500);
289
+ },
290
+ children: copied ? icons.copied : icons.copy
291
+ });
292
+ }
293
+ // src/primitives/segmented-control.tsx
294
+ /**
295
+ * One of a few options, side by side on a track, like claude.ai's Chat / Cowork switch. Themed with
296
+ * `--chat-segment-track` (default: a shade darker than the surface it sits on) and `--chat-segment-selected`
297
+ * (default `--chat-raised`).
298
+ */
299
+ function SegmentedControl({ label, options, value, onChange, className }) {
300
+ return /* @__PURE__ */ jsx(ToggleButtonGroup, {
301
+ "aria-label": label,
302
+ selectionMode: "single",
303
+ disallowEmptySelection: true,
304
+ selectedKeys: [value],
305
+ onSelectionChange: (keys) => {
306
+ const next = [...keys][0];
307
+ if (next !== void 0) onChange(next);
308
+ },
309
+ className: clsx("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),
310
+ children: options.map((o) => {
311
+ const button = /* @__PURE__ */ jsxs(ToggleButton, {
312
+ id: o.id,
313
+ ...o.iconOnly && { "aria-label": o.label },
314
+ className: clsx("inline-flex h-6 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", o.iconOnly ? "w-7" : "px-2", "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)]"),
315
+ children: [o.icon, !o.iconOnly && o.label]
316
+ }, o.id);
317
+ return o.iconOnly ? /* @__PURE__ */ jsx(Tooltip, {
318
+ content: o.label,
319
+ children: button
320
+ }, o.id) : button;
321
+ })
322
+ });
323
+ }
185
324
  // src/primitives/disclosure.tsx
186
325
  /** A row that expands to show more, like "Recalled memory ▸". */
187
326
  function Disclosure({ summary, children, className, summaryClassName, panelClassName, indicator = "caret", trailing, ...props }) {
@@ -559,7 +698,7 @@ function useHighlight(code, language) {
559
698
  }
560
699
  // src/primitives/code-block.tsx
561
700
  /** Monospace code with Shiki highlighting, scrolling past a fixed height. */
562
- function CodeBlock({ code, language, label = language, variant = "plain", className }) {
701
+ function CodeBlock({ code, language, label = language, variant = "plain", copy = variant === "boxed", className }) {
563
702
  const result = useHighlight(code, language);
564
703
  const lines = result?.lines;
565
704
  const themeBackground = useCodeThemes().background === "theme" && variant === "boxed" && !!result;
@@ -571,9 +710,12 @@ function CodeBlock({ code, language, label = language, variant = "plain", classN
571
710
  "data-chat-code-bg": themeBackground ? "theme" : void 0,
572
711
  style: themeBackground ? result.vars : void 0,
573
712
  className: clsx(variant === "boxed" && "my-4 rounded-chat-md border border-chat-code-border bg-chat-code-bg px-4 py-3.5", className),
574
- children: [label && /* @__PURE__ */ jsx("div", {
575
- className: "mb-3 font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle",
576
- children: label
713
+ children: [(label || copy) && /* @__PURE__ */ jsxs("div", {
714
+ className: clsx("flex items-center gap-2 font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle", copy ? "-mt-1.5 -mr-2 mb-1.5" : "mb-3"),
715
+ children: [/* @__PURE__ */ jsx("span", {
716
+ className: "min-w-0 flex-1 truncate",
717
+ children: label
718
+ }), copy && /* @__PURE__ */ jsx(CopyButton, { text: code })]
577
719
  }), /* @__PURE__ */ jsx("pre", {
578
720
  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]"),
579
721
  children: /* @__PURE__ */ jsxs("code", { children: [highlighted ? highlighted.map((line, i) => /* @__PURE__ */ jsxs(Fragment$1, { children: [i > 0 && "\n", line.map((token, j) => /* @__PURE__ */ jsx("span", {
@@ -584,123 +726,6 @@ function CodeBlock({ code, language, label = language, variant = "plain", classN
584
726
  })]
585
727
  });
586
728
  }
587
- // src/primitives/labels.tsx
588
- const plural = (count, one, many) => count === 1 ? one : many(count);
589
- /** The English strings. */
590
- const defaultLabels = {
591
- thinking: "Thinking…",
592
- thought: "Thought",
593
- thoughtFor: (duration) => `Thought for ${duration}`,
594
- waitingForYou: " · waiting for you",
595
- usedTool: (name) => `used ${name}`,
596
- joinSummaries: (phrases) => {
597
- const text = phrases.join(", ");
598
- return text.charAt(0).toUpperCase() + text.slice(1);
599
- },
600
- duration: (seconds) => seconds < 60 ? `${seconds}s` : `${Math.floor(seconds / 60)}m ${seconds % 60}s`,
601
- ranCommandLabel: "Ran a command",
602
- ranCommands: (count) => plural(count, "ran a command", (n) => `ran ${n} commands`),
603
- searchedWebLabel: "Searched the web",
604
- readPageLabel: "Read page",
605
- readPages: (count) => plural(count, "read a page", (n) => `read ${n} pages`),
606
- searchedWeb: (count) => plural(count, "searched the web", (n) => `searched the web ${n} times`),
607
- recalledMemoryLabel: "Recalled memory",
608
- recalledMemory: () => "recalled memory",
609
- output: "Output",
610
- noOutput: "(no output)",
611
- noResults: "No results",
612
- reasoningHidden: "The model's reasoning is not shown.",
613
- allowTool: (name) => `Allow ${name}?`,
614
- allowAction: (label) => /* @__PURE__ */ jsxs(Fragment, { children: [
615
- "Allow ",
616
- label,
617
- "?"
618
- ] }),
619
- allow: "Allow",
620
- deny: "Deny",
621
- sidebar: "Sidebar",
622
- closeSidebar: "Close sidebar",
623
- openSidebar: "Open sidebar",
624
- expandSidebar: "Expand sidebar",
625
- collapseSidebar: "Collapse sidebar",
626
- context: "Context",
627
- contextUsed: "Context used",
628
- percentUsed: (percent) => `${percent}% used`,
629
- freeSpace: "Free space",
630
- tokenCount: (count) => `${count} tokens`,
631
- replyPlaceholder: "Reply…",
632
- replyDirectly: "Or reply directly…",
633
- message: "Message",
634
- stop: "Stop",
635
- send: "Send",
636
- addFiles: "Add files and more",
637
- attachments: "Attachments",
638
- editPastedText: "Edit pasted text",
639
- viewPastedText: "View pasted text",
640
- removeAttachment: (name) => `Remove ${name}`,
641
- pastedText: "Pasted text",
642
- pasted: "Pasted",
643
- failed: "Failed",
644
- file: "File",
645
- uploadFailed: "Upload failed",
646
- cancel: "Cancel",
647
- close: "Close",
648
- save: "Save",
649
- chats: "Chats",
650
- running: "Running",
651
- unread: "Unread",
652
- moreOptionsFor: (title) => `More options for ${title}`,
653
- new: "New",
654
- viewAll: "View all",
655
- chatActions: "Chat actions",
656
- pin: "Pin",
657
- unpin: "Unpin",
658
- rename: "Rename",
659
- addToProject: "Add to project",
660
- searchProjects: "Search or create a project",
661
- projects: "Projects",
662
- newProject: "New project",
663
- shareChat: "Share chat",
664
- delete: "Delete",
665
- justNow: "just now",
666
- copy: "Copy",
667
- copied: "Copied",
668
- retry: "Retry",
669
- edit: "Edit",
670
- readAloud: "Read aloud",
671
- goodResponse: "Good response",
672
- badResponse: "Bad response",
673
- somethingWentWrong: "Something went wrong.",
674
- conversation: "Conversation",
675
- scrollToBottom: "Scroll to bottom",
676
- showMore: "Show more",
677
- showLess: "Show less",
678
- model: "Model",
679
- modelButton: (label) => `Model: ${label}`,
680
- effort: "Effort",
681
- effortNote: "Higher effort means more thorough responses, but takes longer and uses your limits faster.",
682
- recommended: "Recommended",
683
- moreModels: "More models",
684
- widget: "Widget",
685
- drawingWidget: "Drawing"
686
- };
687
- const LabelsContext = createContext(defaultLabels);
688
- /** Merges `labels` over the labels of the enclosing provider (or the defaults). */
689
- function LabelsProvider({ labels, children }) {
690
- const parent = useLabels();
691
- const merged = useMemo(() => labels ? {
692
- ...parent,
693
- ...labels
694
- } : parent, [parent, labels]);
695
- return /* @__PURE__ */ jsx(LabelsContext.Provider, {
696
- value: merged,
697
- children
698
- });
699
- }
700
- /** The active UI strings: the enclosing `ChatTheme`'s `labels` over the English defaults. */
701
- function useLabels() {
702
- return useContext(LabelsContext);
703
- }
704
729
  // src/primitives/chat-theme.tsx
705
730
  /**
706
731
  * Applies a chat theme to part of a page. Menus, tooltips and dialogs opened inside render into this
@@ -1247,6 +1272,112 @@ const SidebarContext = createContext({
1247
1272
  dismiss: () => {}
1248
1273
  });
1249
1274
  const useSidebar = () => useContext(SidebarContext);
1275
+ // src/components/replacements.tsx
1276
+ /**
1277
+ * In-place updates of drawn visuals: a tool call whose input has `replaces: <id>` takes the place of that
1278
+ * earlier call. In the same reply the newest finished call is drawn where the first one was, and the old one
1279
+ * stays (dimmed) until the new one's input is complete. A call replaced from a later reply folds into a link.
1280
+ */
1281
+ /** Ready to take the old one's place: its input is complete and it didn't fail. */
1282
+ const isReady = (p) => p.status !== "streaming" && p.status !== "input" && p.status !== "error";
1283
+ const callsOf = (m) => m.parts.filter((p) => p.type === "tool_call");
1284
+ const replaces = (p) => replacedId(p.input ?? p.partialInput);
1285
+ const titleOf = (p) => {
1286
+ const t = (p.input ?? p.partialInput)?.title;
1287
+ return typeof t === "string" && t ? t : void 0;
1288
+ };
1289
+ /** Ids and titles of the calls seen so far, to resolve `replaces` (an id, or a title as a fallback). */
1290
+ function seen() {
1291
+ const ids = /* @__PURE__ */ new Set();
1292
+ const titles = /* @__PURE__ */ new Map();
1293
+ return {
1294
+ resolve: (call) => {
1295
+ const r = replaces(call);
1296
+ return r === void 0 ? void 0 : resolveReplaces(r, ids, titles);
1297
+ },
1298
+ add: (call) => {
1299
+ ids.add(call.id);
1300
+ const t = titleOf(call);
1301
+ if (t) titles.set(t, call.id);
1302
+ }
1303
+ };
1304
+ }
1305
+ /** One reply's chains: each call's first call (where the chain is drawn) and each first call's chain, in order. */
1306
+ function chainsOf(message) {
1307
+ const first = /* @__PURE__ */ new Map();
1308
+ const chains = /* @__PURE__ */ new Map();
1309
+ const known = seen();
1310
+ for (const call of callsOf(message)) {
1311
+ const r = known.resolve(call);
1312
+ known.add(call);
1313
+ const root = r !== void 0 && first.has(r) ? first.get(r) : call.id;
1314
+ first.set(call.id, root);
1315
+ chains.set(root, [...chains.get(root) ?? [], call]);
1316
+ }
1317
+ return {
1318
+ first,
1319
+ chains
1320
+ };
1321
+ }
1322
+ /** Calls replaced by a finished call in a later reply: replaced id → the newest such call. */
1323
+ function laterReplacements(messages) {
1324
+ const later = /* @__PURE__ */ new Map();
1325
+ const known = seen();
1326
+ for (const m of messages) {
1327
+ if (m.role !== "assistant") continue;
1328
+ const calls = callsOf(m);
1329
+ const own = new Set(calls.map((c) => c.id));
1330
+ for (const call of calls) {
1331
+ const r = known.resolve(call);
1332
+ if (r !== void 0 && !own.has(r) && isReady(call)) later.set(r, call);
1333
+ }
1334
+ calls.forEach(known.add);
1335
+ }
1336
+ return later;
1337
+ }
1338
+ const LaterContext = createContext(/* @__PURE__ */ new Map());
1339
+ function LaterReplacements({ value, children }) {
1340
+ return /* @__PURE__ */ jsx(LaterContext.Provider, {
1341
+ value,
1342
+ children
1343
+ });
1344
+ }
1345
+ /** The call in a later reply that replaces any call of this chain, if there is one. */
1346
+ function useLaterReplacement(chain) {
1347
+ const later = useContext(LaterContext);
1348
+ for (let i = chain.length - 1; i >= 0; i--) {
1349
+ const by = later.get(chain[i].id);
1350
+ if (by) return by;
1351
+ }
1352
+ }
1353
+ /** The DOM id of a drawn tool call, for the "Updated below" link. */
1354
+ const toolAnchor = (id) => `chat-tool-${id}`;
1355
+ /** Draws a chain: the newest finished call (dimmed while a newer one streams), or a link to its later update. */
1356
+ function ToolChain({ chain, draw }) {
1357
+ const labels = useLabels();
1358
+ const later = useLaterReplacement(chain);
1359
+ const shown = chain.findLast(isReady) ?? chain[0];
1360
+ const updating = chain.indexOf(shown) < chain.length - 1 && chain.at(-1).status !== "error";
1361
+ if (later) return /* @__PURE__ */ jsx(Button, {
1362
+ variant: "ghost",
1363
+ size: "sm",
1364
+ className: "self-start text-chat-muted data-hovered:text-chat-fg",
1365
+ onPress: () => document.getElementById(toolAnchor(later.id))?.scrollIntoView({
1366
+ behavior: "smooth",
1367
+ block: "center"
1368
+ }),
1369
+ children: labels.visualUpdated
1370
+ });
1371
+ return /* @__PURE__ */ jsxs("div", {
1372
+ id: toolAnchor(chain[0].id),
1373
+ "aria-busy": updating || void 0,
1374
+ className: updating ? "opacity-60 transition-opacity" : "transition-opacity",
1375
+ children: [/* @__PURE__ */ jsx("div", { children: draw(shown) }, shown.id), updating && /* @__PURE__ */ jsx("p", {
1376
+ className: "chat-shimmer mt-1 font-chat-sans text-(length:--chat-ui-font-size-sm) text-chat-muted",
1377
+ children: labels.visualUpdating
1378
+ })]
1379
+ });
1380
+ }
1250
1381
  // src/components/widget-frame.tsx
1251
1382
  const WidgetHostContext = createContext({});
1252
1383
  /** Sets what widgets below may do. ChatView and ChatTranscript set it from their `widgets` prop. */
@@ -1362,6 +1493,8 @@ const BOOT = `(() => {
1362
1493
  new ResizeObserver(() => {
1363
1494
  const h = Math.ceil(root.getBoundingClientRect().height);
1364
1495
  if (h !== last) { last = h; post("ui/notifications/size-changed", { height: h }); }
1496
+ // The frame grows to fit, a moment behind; only content past the cap gets a scrollbar.
1497
+ document.documentElement.style.overflowY = h > ${WIDGET_MAX_HEIGHT} ? "auto" : "hidden";
1365
1498
  }).observe(root);
1366
1499
  const seen = new Set();
1367
1500
  const report = (text) => { if (!seen.has(text) && seen.size < 10) { seen.add(text); post("notifications/message", { level: "error", logger: "widget", data: text }); } };
@@ -1374,6 +1507,7 @@ function fragmentDoc(csp, theme) {
1374
1507
  <meta http-equiv="Content-Security-Policy" content="${csp}">
1375
1508
  <style>
1376
1509
  :root { ${Object.entries(theme.vars).map(([k, v]) => `${k}: ${v};`).join(" ")} color-scheme: ${theme.scheme}; }
1510
+ html { overflow: hidden; }
1377
1511
  html, body { margin: 0; background: transparent; color: var(--chat-fg); font-family: var(--chat-font-sans);
1378
1512
  font-size: var(--chat-ui-font-size); line-height: 1.5; }
1379
1513
  #widget { display: flow-root; }
@@ -1415,8 +1549,10 @@ function watchTheme(el, onChange) {
1415
1549
  media?.removeEventListener?.("change", onChange);
1416
1550
  };
1417
1551
  }
1418
- function WidgetFrame({ html, document: isDocument = false, streaming = false, title, loadingMessages, allow, csp: request, onPrompt, onOpenLink, onCallTool, onError, toolInput, toolResult, cancelled, className }) {
1552
+ function WidgetFrame({ html, document: isDocument = false, streaming = false, title, loadingMessages, allow, csp: request, onPrompt, onOpenLink, onCallTool, onError, toolInput, toolResult, cancelled, frameUrl: ownFrameUrl, className }) {
1419
1553
  const labels = useLabels();
1554
+ const hostFrameUrl = useWidgetHost().frameUrl;
1555
+ const frameUrl = ownFrameUrl ?? hostFrameUrl;
1420
1556
  const frame = useRef(null);
1421
1557
  const host = useRef(null);
1422
1558
  const [ready, setReady] = useState(false);
@@ -1438,7 +1574,8 @@ function WidgetFrame({ html, document: isDocument = false, streaming = false, ti
1438
1574
  onCallTool,
1439
1575
  onError,
1440
1576
  policy,
1441
- effective
1577
+ effective,
1578
+ doc
1442
1579
  });
1443
1580
  latest.current = {
1444
1581
  onPrompt,
@@ -1446,7 +1583,16 @@ function WidgetFrame({ html, document: isDocument = false, streaming = false, ti
1446
1583
  onCallTool,
1447
1584
  onError,
1448
1585
  policy,
1449
- effective
1586
+ effective,
1587
+ doc
1588
+ };
1589
+ const generation = useRef({
1590
+ doc,
1591
+ n: 0
1592
+ });
1593
+ if (generation.current.doc !== doc) generation.current = {
1594
+ doc,
1595
+ n: generation.current.n + 1
1450
1596
  };
1451
1597
  const send = (message) => frame.current?.contentWindow?.postMessage({
1452
1598
  jsonrpc: "2.0",
@@ -1496,6 +1642,14 @@ function WidgetFrame({ html, document: isDocument = false, streaming = false, ti
1496
1642
  });
1497
1643
  const onMessage = async (e) => {
1498
1644
  if (!frame.current || e.source !== frame.current.contentWindow) return;
1645
+ if (e.data?.type === widgetLoaderMessages.ready) {
1646
+ const html = latest.current.doc;
1647
+ if (html) frame.current.contentWindow?.postMessage({
1648
+ type: widgetLoaderMessages.document,
1649
+ html
1650
+ }, "*");
1651
+ return;
1652
+ }
1499
1653
  const m = e.data ?? {};
1500
1654
  if (typeof m !== "object" || typeof m.method !== "string") return;
1501
1655
  const { onPrompt, onOpenLink, onCallTool, effective } = latest.current;
@@ -1614,13 +1768,13 @@ function WidgetFrame({ html, document: isDocument = false, streaming = false, ti
1614
1768
  ref: frame,
1615
1769
  title: title ?? labels.widget,
1616
1770
  sandbox: "allow-scripts",
1617
- srcDoc: doc,
1771
+ ...frameUrl ? { src: frameUrl } : { srcDoc: doc },
1618
1772
  style: {
1619
1773
  height: height || (streaming ? 120 : 0),
1620
1774
  transition: "height 150ms ease-out"
1621
1775
  },
1622
1776
  className: "block w-full border-0 bg-transparent"
1623
- })]
1777
+ }, frameUrl ? generation.current.n : "srcdoc")]
1624
1778
  });
1625
1779
  }
1626
1780
  /** Pulls the host's abilities from context into frame props. */
@@ -2388,10 +2542,14 @@ function ActivityGroup({ parts, defaultExpanded = false, className }) {
2388
2542
  }) : /* @__PURE__ */ jsx(Disclosure, {
2389
2543
  isExpanded: expanded,
2390
2544
  onExpandedChange: setExpanded,
2391
- summary: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Live, {
2392
- live,
2393
- children: activityHeading(parts, views, labels)
2394
- }), after] }),
2545
+ summary: /* @__PURE__ */ jsxs(Fragment, { children: [
2546
+ /* @__PURE__ */ jsx(Lead, { live }),
2547
+ /* @__PURE__ */ jsx(Live, {
2548
+ live,
2549
+ children: activityHeading(parts, views, labels)
2550
+ }),
2551
+ after
2552
+ ] }),
2395
2553
  summaryClassName: "text-(length:--chat-activity-font-size)",
2396
2554
  panelClassName: "pt-3",
2397
2555
  children: /* @__PURE__ */ jsx("div", {
@@ -2401,6 +2559,17 @@ function ActivityGroup({ parts, defaultExpanded = false, className }) {
2401
2559
  })
2402
2560
  });
2403
2561
  }
2562
+ /** The host's mark left of the summary (`icons.activity` while working, `icons.activityDone` after), if set. */
2563
+ function Lead({ live }) {
2564
+ const icons = useIcons();
2565
+ const icon = live ? icons.activity : icons.activityDone;
2566
+ if (!icon) return null;
2567
+ return /* @__PURE__ */ jsx("span", {
2568
+ "aria-hidden": true,
2569
+ className: "chat-activity-icon mr-1.5 inline-flex size-[1.15em] shrink-0 items-center self-center [&_svg]:size-full",
2570
+ children: icon
2571
+ });
2572
+ }
2404
2573
  const SHIMMER_MS = 2e3;
2405
2574
  /**
2406
2575
  * Sweeps a highlight across a label while its step is running. The animation starts at the phase of a
@@ -2425,10 +2594,14 @@ function SingleStep({ part, live, after, expanded, onExpandedChange }) {
2425
2594
  return /* @__PURE__ */ jsx(Disclosure, {
2426
2595
  isExpanded: expanded,
2427
2596
  onExpandedChange,
2428
- summary: /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsx(Live, {
2429
- live,
2430
- children: summary
2431
- }), after] }),
2597
+ summary: /* @__PURE__ */ jsxs(Fragment, { children: [
2598
+ /* @__PURE__ */ jsx(Lead, { live }),
2599
+ /* @__PURE__ */ jsx(Live, {
2600
+ live,
2601
+ children: summary
2602
+ }),
2603
+ after
2604
+ ] }),
2432
2605
  summaryClassName: "text-(length:--chat-activity-font-size)",
2433
2606
  panelClassName: "pt-3",
2434
2607
  children: /* @__PURE__ */ jsx("div", {
@@ -2729,21 +2902,6 @@ function formatRelativeTime(iso, now = Date.now(), labels = defaultLabels) {
2729
2902
  function messageText(message) {
2730
2903
  return message.parts.map((p) => p.type === "text" ? p.text : p.type === "custom" ? p.text ?? "" : "").filter(Boolean).join("\n\n");
2731
2904
  }
2732
- function CopyButton({ text }) {
2733
- const icons = useIcons();
2734
- const labels = useLabels();
2735
- const [copied, setCopied] = useState(false);
2736
- return /* @__PURE__ */ jsx(IconButton, {
2737
- label: copied ? labels.copied : labels.copy,
2738
- size: "sm",
2739
- onPress: () => {
2740
- navigator.clipboard?.writeText(text);
2741
- setCopied(true);
2742
- setTimeout(() => setCopied(false), 1500);
2743
- },
2744
- children: copied ? icons.copied : icons.copy
2745
- });
2746
- }
2747
2905
  const timeClass = "px-1 font-chat-sans text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle";
2748
2906
  /** The user's turn: a bubble on the right, with time, retry, edit and copy under it on hover. */
2749
2907
  function UserMessage({ message, onRetry, onEdit, actions, copy = true, time = true, className }) {
@@ -2838,6 +2996,7 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
2838
2996
  const streaming = message.status === "streaming";
2839
2997
  const views = useToolViews();
2840
2998
  const blocks = toBlocks(message.parts, (part) => views[part.name]?.Block !== void 0);
2999
+ const { first, chains } = chainsOf(message);
2841
3000
  return /* @__PURE__ */ jsxs("div", {
2842
3001
  className: clsx("group/message flex flex-col gap-4", className),
2843
3002
  children: [
@@ -2852,8 +3011,14 @@ function AssistantMessage({ message, onRetry, onFeedback, onReadAloud, renderPar
2852
3011
  })] }, block.id);
2853
3012
  }
2854
3013
  if (block.kind === "tool") {
2855
- const ToolBlock = views[block.part.name].Block;
2856
- return /* @__PURE__ */ jsx(ToolBlock, { part: block.part }, block.id);
3014
+ if (first.get(block.part.id) !== block.part.id) return null;
3015
+ return /* @__PURE__ */ jsx(ToolChain, {
3016
+ chain: chains.get(block.part.id) ?? [block.part],
3017
+ draw: (part) => {
3018
+ const ToolBlock = (views[part.name] ?? views[block.part.name]).Block;
3019
+ return /* @__PURE__ */ jsx(ToolBlock, { part });
3020
+ }
3021
+ }, block.id);
2857
3022
  }
2858
3023
  const part = block.part;
2859
3024
  if (part.type === "text") return /* @__PURE__ */ jsx(Markdown, {
@@ -3251,6 +3416,115 @@ function AttachButton({ children, label, icon, className, menuClassName }) {
3251
3416
  children
3252
3417
  })] });
3253
3418
  }
3419
+ // src/components/effort-slider.tsx
3420
+ /**
3421
+ * Effort as a slider with one stop per level, lowest (faster) on the left: title and the current level on
3422
+ * top, the level's description under them, "Faster" and "Smarter" under the track. Arrow keys move a stop.
3423
+ */
3424
+ function EffortSlider({ efforts, value, onChange, label, className }) {
3425
+ const labels = useLabels();
3426
+ const id = useId();
3427
+ const index = Math.max(0, efforts.findIndex((e) => e.id === value));
3428
+ const current = efforts[index];
3429
+ const last = Math.max(1, efforts.length - 1);
3430
+ const at = (i) => `${i / last * 100}%`;
3431
+ if (!current) return null;
3432
+ return /* @__PURE__ */ jsxs("div", {
3433
+ className: clsx("flex w-full flex-col gap-1 font-chat-sans", className),
3434
+ children: [
3435
+ /* @__PURE__ */ jsxs("div", {
3436
+ className: "flex items-center gap-2",
3437
+ children: [
3438
+ /* @__PURE__ */ jsx("label", {
3439
+ htmlFor: id,
3440
+ className: "flex-1 text-(length:--chat-ui-font-size) font-medium text-chat-fg",
3441
+ children: label ?? labels.effort
3442
+ }),
3443
+ current.recommended && /* @__PURE__ */ jsx(Badge, { children: labels.recommended }),
3444
+ current.warning && /* @__PURE__ */ jsx(Badge, {
3445
+ tone: "warning",
3446
+ children: current.warning
3447
+ }),
3448
+ /* @__PURE__ */ jsx("output", {
3449
+ htmlFor: id,
3450
+ className: "text-(length:--chat-ui-font-size) text-chat-fg",
3451
+ children: current.label
3452
+ })
3453
+ ]
3454
+ }),
3455
+ current.description && /* @__PURE__ */ jsx("p", {
3456
+ className: "text-(length:--chat-ui-font-size-sm) leading-4 text-chat-muted",
3457
+ children: current.description
3458
+ }),
3459
+ /* @__PURE__ */ jsxs("div", {
3460
+ className: "relative mx-2 mt-2 h-6",
3461
+ children: [
3462
+ /* @__PURE__ */ jsx("div", { className: "absolute top-1/2 right-0 left-0 h-1 -translate-y-1/2 rounded-full bg-chat-border-strong" }),
3463
+ /* @__PURE__ */ jsx("div", {
3464
+ className: "absolute top-1/2 left-0 h-1 -translate-y-1/2 rounded-full bg-chat-accent",
3465
+ style: { width: at(index) }
3466
+ }),
3467
+ efforts.map((e, i) => /* @__PURE__ */ jsx("span", {
3468
+ "aria-hidden": true,
3469
+ className: clsx("absolute top-1/2 size-1.5 -translate-1/2 rounded-full", i <= index ? "bg-chat-accent-fg/70" : "bg-chat-subtle"),
3470
+ style: { left: at(i) }
3471
+ }, e.id)),
3472
+ /* @__PURE__ */ jsx("span", {
3473
+ "aria-hidden": true,
3474
+ className: "pointer-events-none absolute top-1/2 size-4 -translate-1/2 rounded-full border-2 border-chat-accent bg-chat-fg shadow-sm peer-focus-visible:ring-2",
3475
+ style: { left: at(index) }
3476
+ }),
3477
+ /* @__PURE__ */ jsx("input", {
3478
+ id,
3479
+ type: "range",
3480
+ min: 0,
3481
+ max: efforts.length - 1,
3482
+ step: 1,
3483
+ value: index,
3484
+ "aria-valuetext": current.label,
3485
+ onChange: (e) => {
3486
+ const next = efforts[Number(e.target.value)];
3487
+ if (next && next.id !== value) onChange(next.id);
3488
+ },
3489
+ onKeyDown: (e) => {
3490
+ const step = e.key === "ArrowRight" || e.key === "ArrowUp" ? 1 : e.key === "ArrowLeft" || e.key === "ArrowDown" ? -1 : 0;
3491
+ const next = step ? efforts[index + step] : void 0;
3492
+ if (next) {
3493
+ e.preventDefault();
3494
+ onChange(next.id);
3495
+ }
3496
+ },
3497
+ className: "absolute -inset-x-2 inset-y-0 w-[calc(100%+1rem)] cursor-pointer rounded-full opacity-0 focus-visible:opacity-100 focus-visible:outline-2 focus-visible:outline-chat-focus [&::-webkit-slider-thumb]:opacity-0"
3498
+ })
3499
+ ]
3500
+ }),
3501
+ /* @__PURE__ */ jsxs("div", {
3502
+ className: "flex justify-between text-(length:--chat-ui-font-size-sm) leading-4 text-chat-subtle",
3503
+ children: [/* @__PURE__ */ jsx("span", { children: labels.effortFaster }), /* @__PURE__ */ jsx("span", { children: labels.effortSmarter })]
3504
+ })
3505
+ ]
3506
+ });
3507
+ }
3508
+ /** A composer pill ("◎ Medium") that opens the effort slider above it, like chatgpt.com's Think button. */
3509
+ function EffortButton({ icon, children, className, ...slider }) {
3510
+ const current = slider.efforts.find((e) => e.id === slider.value) ?? slider.efforts[0];
3511
+ const labels = useLabels();
3512
+ return /* @__PURE__ */ jsxs(DialogTrigger, { children: [/* @__PURE__ */ jsxs(Button, {
3513
+ variant: "ghost",
3514
+ size: "sm",
3515
+ className: clsx("text-chat-muted data-hovered:text-chat-fg", className),
3516
+ "aria-label": `${labels.effort}: ${current?.label ?? ""}`,
3517
+ children: [icon, children ?? current?.label]
3518
+ }), /* @__PURE__ */ jsx(Popover, {
3519
+ placement: "top end",
3520
+ className: "w-72 p-3",
3521
+ children: /* @__PURE__ */ jsx(Dialog, {
3522
+ "aria-label": labels.effort,
3523
+ className: "outline-none",
3524
+ children: /* @__PURE__ */ jsx(EffortSlider, { ...slider })
3525
+ })
3526
+ })] });
3527
+ }
3254
3528
  // src/components/model-picker.tsx
3255
3529
  /** "Opus 5.5 High ⌄": the models, an Effort submenu for models that have levels, and More models. */
3256
3530
  function ModelPicker({ models, model, onModelChange, effort, onEffortChange, effortNote, footnote, className }) {
@@ -3371,6 +3645,7 @@ function MessageList({ messages, onRetry, onEdit, onFeedback, onReadAloud, actio
3371
3645
  const content = useRef(null);
3372
3646
  const stuck = useRef(start === "bottom");
3373
3647
  const labels = useLabels();
3648
+ const later = useMemo(() => laterReplacements(messages), [messages]);
3374
3649
  const [atBottom, setAtBottom] = useState(true);
3375
3650
  const toBottom = useCallback((behavior = "auto") => {
3376
3651
  const el = scroller.current;
@@ -3420,22 +3695,25 @@ function MessageList({ messages, onRetry, onEdit, onFeedback, onReadAloud, actio
3420
3695
  role: "log",
3421
3696
  "aria-label": labels.conversation,
3422
3697
  className: "mx-auto flex w-full max-w-[calc(var(--chat-content-width)+2*var(--chat-gutter,var(--chat-content-padding)))] flex-1 flex-col gap-6 px-[var(--chat-gutter,var(--chat-content-padding))] pt-6 pb-4",
3423
- children: [header, messages.map((m) => m.role === "user" ? /* @__PURE__ */ jsx(UserMessage, {
3424
- message: m,
3425
- ...onEdit && { onEdit: () => onEdit(m.id) },
3426
- ...actions?.extra && { actions: actions.extra(m) },
3427
- ...actions?.copy === false && { copy: false },
3428
- ...actions?.time === false && { time: false }
3429
- }, m.id) : /* @__PURE__ */ jsx(AssistantMessage, {
3430
- message: m,
3431
- ...onRetry && { onRetry: () => onRetry(m.id) },
3432
- ...onFeedback && { onFeedback: (f) => onFeedback(m.id, f) },
3433
- ...onReadAloud && { onReadAloud: () => onReadAloud(m.id) },
3434
- ...actions?.extra && { actions: actions.extra(m) },
3435
- ...actions?.copy === false && { copy: false },
3436
- ...actions?.time === false && { time: false },
3437
- ...renderPart && { renderPart }
3438
- }, m.id))]
3698
+ children: [header, /* @__PURE__ */ jsx(LaterReplacements, {
3699
+ value: later,
3700
+ children: messages.map((m) => m.role === "user" ? /* @__PURE__ */ jsx(UserMessage, {
3701
+ message: m,
3702
+ ...onEdit && { onEdit: () => onEdit(m.id) },
3703
+ ...actions?.extra && { actions: actions.extra(m) },
3704
+ ...actions?.copy === false && { copy: false },
3705
+ ...actions?.time === false && { time: false }
3706
+ }, m.id) : /* @__PURE__ */ jsx(AssistantMessage, {
3707
+ message: m,
3708
+ ...onRetry && { onRetry: () => onRetry(m.id) },
3709
+ ...onFeedback && { onFeedback: (f) => onFeedback(m.id, f) },
3710
+ ...onReadAloud && { onReadAloud: () => onReadAloud(m.id) },
3711
+ ...actions?.extra && { actions: actions.extra(m) },
3712
+ ...actions?.copy === false && { copy: false },
3713
+ ...actions?.time === false && { time: false },
3714
+ ...renderPart && { renderPart }
3715
+ }, m.id))
3716
+ })]
3439
3717
  }), footer && /* @__PURE__ */ jsx("div", {
3440
3718
  className: "sticky bottom-0",
3441
3719
  children: footer
@@ -3732,7 +4010,7 @@ const densityVars = {
3732
4010
  }
3733
4011
  };
3734
4012
  /** A whole chat: header, the conversation, the docked composer. An empty chat shows `emptyState`. */
3735
- function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, density = "full", contentWidth, contentPadding, className }) {
4013
+ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove, onRetry, onEdit, onFeedback, onReadAloud, messageActions, renderPart, widgets, header, composer, emptyState, disclaimer, renderComposer, density = "full", contentWidth, contentPadding, className }) {
3736
4014
  const style = {
3737
4015
  ...densityVars[density],
3738
4016
  ...contentWidth && { "--chat-content-width": contentWidth },
@@ -3747,14 +4025,17 @@ function ChatView({ messages, busy = false, onSend, onStop, approvals, onApprove
3747
4025
  const [body, short] = useShortPane();
3748
4026
  const labels = useLabels();
3749
4027
  const pending = onApprove && approvals?.[0];
3750
- const box = (variant) => /* @__PURE__ */ jsx(Composer, {
3751
- variant: density === "inline" || short && !empty ? "compact" : variant,
3752
- busy,
3753
- onSubmit: onSend,
3754
- ...onStop && { onStop },
3755
- ...composer,
3756
- ...pending && { placeholder: labels.replyDirectly }
3757
- });
4028
+ const box = (variant) => {
4029
+ const element = /* @__PURE__ */ jsx(Composer, {
4030
+ variant: density === "inline" || short && !empty ? "compact" : variant,
4031
+ busy,
4032
+ onSubmit: onSend,
4033
+ ...onStop && { onStop },
4034
+ ...composer,
4035
+ ...pending && { placeholder: labels.replyDirectly }
4036
+ });
4037
+ return renderComposer ? renderComposer(element, variant === "centered" ? "empty" : "docked") : element;
4038
+ };
3758
4039
  return /* @__PURE__ */ jsxs("div", {
3759
4040
  "data-chat-density": density,
3760
4041
  style,
@@ -3822,7 +4103,7 @@ function EmptyState({ greeting, icon, children, footer }) {
3822
4103
  /* @__PURE__ */ jsxs("h1", {
3823
4104
  className: "chat-empty-greeting flex items-center justify-center gap-3 text-center font-chat-serif text-[clamp(1.5rem,6cqw,2.5rem)] font-normal text-chat-fg",
3824
4105
  children: [icon && /* @__PURE__ */ jsx("span", {
3825
- className: "flex shrink-0 text-chat-accent [&_svg]:size-[1em]",
4106
+ className: "flex shrink-0 text-[var(--chat-empty-icon,var(--chat-accent))] [&_svg]:size-[1em]",
3826
4107
  children: icon
3827
4108
  }), greeting]
3828
4109
  }),
@@ -3843,12 +4124,13 @@ function ChatTranscript({ messages, renderPart, widgets, header, notice, footer,
3843
4124
  ...contentPadding && { "--chat-content-padding": contentPadding }
3844
4125
  };
3845
4126
  const host = useMemo(() => {
3846
- const { allow, readResource } = { ...widgets };
4127
+ const { allow, readResource, frameUrl } = { ...widgets };
3847
4128
  const onOpenLink = widgets && "onOpenLink" in widgets ? widgets.onOpenLink : openInNewTab;
3848
4129
  return {
3849
4130
  ...allow && { allow },
3850
4131
  ...onOpenLink && { onOpenLink },
3851
- ...readResource && { readResource }
4132
+ ...readResource && { readResource },
4133
+ ...frameUrl && { frameUrl }
3852
4134
  };
3853
4135
  }, [widgets]);
3854
4136
  return /* @__PURE__ */ jsxs("div", {
@@ -3943,17 +4225,27 @@ function SidebarToggle({ drawerOnly = false, className }) {
3943
4225
  }
3944
4226
  // src/views/sidebar.tsx
3945
4227
  /** The claude.ai sidebar frame. Collapses to an icon rail inside `AppShell`; a drawer on phones. */
3946
- function Sidebar({ brand, headerActions, nav, children, footer, className }) {
4228
+ function Sidebar({ brand, brandIcon, headerActions, nav, children, footer, className }) {
3947
4229
  const { collapsed } = useSidebar();
3948
4230
  return /* @__PURE__ */ jsxs("div", {
3949
4231
  className: clsx("flex h-full flex-col font-chat-sans", collapsed ? "w-13" : "w-full", className),
3950
4232
  children: [
4233
+ collapsed && brandIcon && /* @__PURE__ */ jsx("div", {
4234
+ className: "flex h-12 shrink-0 items-center justify-center [&_svg]:size-5",
4235
+ children: brandIcon
4236
+ }),
3951
4237
  /* @__PURE__ */ jsxs("div", {
3952
4238
  className: clsx("flex h-12 shrink-0 items-center gap-1 px-2", collapsed && "justify-center"),
3953
4239
  children: [
3954
- !collapsed && /* @__PURE__ */ jsx("div", {
3955
- className: "min-w-0 flex-1 truncate px-1.5 font-chat-serif text-lg text-chat-fg",
3956
- children: brand
4240
+ !collapsed && /* @__PURE__ */ jsxs("div", {
4241
+ className: "flex min-w-0 flex-1 items-center gap-2 px-1.5 font-chat-serif text-lg text-chat-fg",
4242
+ children: [brandIcon && /* @__PURE__ */ jsx("span", {
4243
+ className: "flex shrink-0 [&_svg]:size-5",
4244
+ children: brandIcon
4245
+ }), /* @__PURE__ */ jsx("span", {
4246
+ className: "min-w-0 truncate",
4247
+ children: brand
4248
+ })]
3957
4249
  }),
3958
4250
  !collapsed && headerActions,
3959
4251
  /* @__PURE__ */ jsx(SidebarToggle, {})
@@ -4053,4 +4345,4 @@ function SidebarUser({ name, detail, avatarUrl, menu }) {
4053
4345
  children: menu
4054
4346
  })] });
4055
4347
  }
4056
- export { ActivityGroup, AgentProvider, AgentRegistry, AppShell, ApprovalCard, AssistantMessage, AttachButton, AttachmentCard, AttachmentRow, Badge, Button, Caret, ChatHeader, ChatList, ChatNotice, ChatTheme, ChatTitleMenu, ChatTranscript, ChatView, CodeBlock, Composer, ContextMenu, ContextPanel, DRAWER_QUERY, Disclosure, Drawer, 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, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };
4348
+ 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, 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, WIDGET_MAX_HEIGHT, WidgetFrame, WidgetHostProvider, WidgetPartView, activityHeading, activityLabel, commandTool, defaultIcons, defaultLabels, defaultToolViews, formatDuration, formatRelativeTime, formatTokens, htmlComponent, intersectAllow, memoryTool, messageText, openInNewTab, readTreeInput, readVisualizeInput, sseTransport, uiMarkupToolView, uiTreeToolView, useAgentContext, useAgentRegistry, useAgentSnapshot, useAgentTool, useAttachments, useChat, useChatTool, useCountUp, useIcons, useLabels, useMediaQuery, useSidebar, useSmoothText, useToolView, useToolViews, useUiAction, useUiMarkupLanguage, useUiState, useWidgetHost, userMessage, webFetchTool, webSearchTool, widgetCsp, widgetToolView };