@nextclaw/agent-chat-ui 0.5.4-beta.0 → 0.6.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 CHANGED
@@ -399,6 +399,10 @@ const INPUT_SURFACE_PANEL_DESKTOP_SHRINK_RATIO = .82;
399
399
  const INPUT_SURFACE_PANEL_DESKTOP_MIN_WIDTH = 560;
400
400
  const INPUT_SURFACE_PANEL_MAX_HEIGHT = createChatPopoverAvailableHeightLimit("24rem");
401
401
  const INPUT_SURFACE_PANEL_MIN_HEIGHT = createChatPopoverAvailableHeightLimit("240px");
402
+ function itemMatchesFilter(item, filter) {
403
+ if (!filter.sectionKeys || filter.sectionKeys.length === 0) return true;
404
+ return Boolean(item.sectionKey && filter.sectionKeys.includes(item.sectionKey));
405
+ }
402
406
  const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref) {
403
407
  const { Popover, PopoverAnchor, PopoverContent } = ChatUiPrimitives;
404
408
  const { elementRef: anchorRef, width: panelWidth } = useElementWidth();
@@ -407,12 +411,30 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
407
411
  index: 0,
408
412
  itemsSignature: ""
409
413
  });
410
- const { isOpen, isLoading, items, texts, onSelectItem, onOpenChange, onDetailsPointerDown } = props;
411
- const itemsSignature = useMemo(() => items.map((item) => item.key).join(""), [items]);
412
- const hasItemSections = useMemo(() => items.some((item) => Boolean(item.sectionLabel?.trim())), [items]);
414
+ const { isOpen, isLoading, filterOptions, items, texts, onSelectItem, onOpenChange, onDetailsPointerDown } = props;
415
+ const [activeFilterKey, setActiveFilterKey] = useState(filterOptions?.[0]?.key ?? null);
416
+ const resolvedActiveFilterKey = useMemo(() => {
417
+ if (!filterOptions?.length) return null;
418
+ return filterOptions.some((filter) => filter.key === activeFilterKey) ? activeFilterKey : filterOptions[0].key;
419
+ }, [activeFilterKey, filterOptions]);
420
+ const activeFilter = useMemo(() => filterOptions?.find((filter) => filter.key === resolvedActiveFilterKey) ?? null, [filterOptions, resolvedActiveFilterKey]);
421
+ const filterViews = useMemo(() => filterOptions?.map((filter) => ({
422
+ ...filter,
423
+ count: items.filter((item) => itemMatchesFilter(item, filter)).length
424
+ })) ?? [], [filterOptions, items]);
425
+ const visibleItems = useMemo(() => activeFilter ? items.filter((item) => itemMatchesFilter(item, activeFilter)) : items, [activeFilter, items]);
426
+ const itemsSignature = useMemo(() => visibleItems.map((item) => item.key).join(""), [visibleItems]);
427
+ const hasItemSections = useMemo(() => visibleItems.some((item) => Boolean(item.sectionLabel?.trim())), [visibleItems]);
413
428
  const activeIndex = activeState.itemsSignature === itemsSignature ? activeState.index : 0;
414
- const activeIndexInRange = items.length === 0 ? 0 : Math.min(activeIndex, items.length - 1);
415
- const activeItem = items[activeIndexInRange] ?? null;
429
+ const activeIndexInRange = visibleItems.length === 0 ? 0 : Math.min(activeIndex, visibleItems.length - 1);
430
+ const activeItem = visibleItems[activeIndexInRange] ?? null;
431
+ const handleFilterSelect = useCallback((filterKey) => {
432
+ setActiveFilterKey(filterKey);
433
+ setActiveState({
434
+ index: 0,
435
+ itemsSignature: ""
436
+ });
437
+ }, []);
416
438
  const setActiveIndexForCurrentItems = useCallback((nextIndex) => {
417
439
  setActiveState((currentState) => {
418
440
  const currentIndex = currentState.itemsSignature === itemsSignature ? currentState.index : 0;
@@ -433,35 +455,35 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
433
455
  onOpenChange(false);
434
456
  return true;
435
457
  }
436
- if (items.length === 0) return false;
458
+ if (visibleItems.length === 0) return false;
437
459
  if (event.key === "ArrowDown") {
438
460
  event.preventDefault();
439
- setActiveIndexForCurrentItems((index) => (index + 1) % items.length);
461
+ setActiveIndexForCurrentItems((index) => (index + 1) % visibleItems.length);
440
462
  return true;
441
463
  }
442
464
  if (event.key === "ArrowUp") {
443
465
  event.preventDefault();
444
- setActiveIndexForCurrentItems((index) => (index - 1 + items.length) % items.length);
466
+ setActiveIndexForCurrentItems((index) => (index - 1 + visibleItems.length) % visibleItems.length);
445
467
  return true;
446
468
  }
447
469
  if (event.key === "Enter" && !event.shiftKey || event.key === "Tab") {
448
470
  event.preventDefault();
449
- onSelectItem(items[activeIndexInRange]);
471
+ onSelectItem(visibleItems[activeIndexInRange]);
450
472
  return true;
451
473
  }
452
474
  return false;
453
475
  } }), [
454
476
  activeIndexInRange,
455
477
  isOpen,
456
- items,
457
478
  onOpenChange,
458
479
  onSelectItem,
459
- setActiveIndexForCurrentItems
480
+ setActiveIndexForCurrentItems,
481
+ visibleItems
460
482
  ]);
461
483
  useActiveItemScroll({
462
484
  containerRef: listRef,
463
485
  activeIndex: activeIndexInRange,
464
- itemCount: items.length,
486
+ itemCount: visibleItems.length,
465
487
  isEnabled: isOpen && !isLoading,
466
488
  getItemSelector: (index) => `[data-input-surface-index="${index}"]`
467
489
  });
@@ -487,59 +509,78 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
487
509
  children: /* @__PURE__ */ jsxs("div", {
488
510
  className: "grid min-h-0 flex-1 grid-cols-[minmax(220px,300px)_minmax(0,1fr)]",
489
511
  style: { minHeight: INPUT_SURFACE_PANEL_MIN_HEIGHT },
490
- children: [/* @__PURE__ */ jsx("div", {
491
- ref: listRef,
492
- role: "listbox",
493
- "aria-label": texts.sectionLabel,
494
- className: "custom-scrollbar min-h-0 overflow-y-auto overscroll-contain border-r border-gray-200 p-2.5",
495
- children: isLoading ? /* @__PURE__ */ jsx("div", {
496
- className: "p-2 text-xs text-gray-500",
497
- children: texts.loadingLabel
498
- }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
499
- className: "mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
500
- children: texts.sectionLabel
501
- }) : null, items.length === 0 ? /* @__PURE__ */ jsx("div", {
502
- className: "px-2 text-xs text-gray-400",
503
- children: texts.emptyLabel
504
- }) : /* @__PURE__ */ jsx("div", {
505
- className: "space-y-1",
506
- children: items.map((item, index) => {
507
- const { key, sectionKey, sectionLabel, title, subtitle } = item;
508
- const isActive = index === activeIndexInRange;
509
- const previousItem = items[index - 1];
510
- return /* @__PURE__ */ jsxs("div", {
511
- className: "space-y-1",
512
- children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
513
- className: "px-2 pb-0.5 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
514
- children: sectionLabel
515
- }) : null, /* @__PURE__ */ jsxs("button", {
516
- type: "button",
517
- role: "option",
518
- "aria-selected": isActive,
519
- "data-input-surface-index": index,
520
- onPointerMove: (event) => {
521
- if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
522
- },
523
- onPointerDown: (event) => {
524
- if (event.button > 0) return;
525
- event.preventDefault();
526
- onSelectItem(item);
527
- },
528
- onClick: (event) => {
529
- if (event.detail === 0) onSelectItem(item);
530
- },
531
- className: `flex w-full items-start gap-2 rounded-lg px-2 py-1.5 text-left transition ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-50"}`,
532
- children: [/* @__PURE__ */ jsx("span", {
533
- className: "truncate text-xs font-semibold",
534
- children: title
535
- }), /* @__PURE__ */ jsx("span", {
536
- className: "truncate text-xs text-gray-500",
537
- children: subtitle
538
- })]
512
+ children: [/* @__PURE__ */ jsxs("div", {
513
+ className: "flex min-h-0 flex-col border-r border-gray-200",
514
+ children: [!isLoading && filterViews.length > 0 ? /* @__PURE__ */ jsx("div", {
515
+ className: "flex shrink-0 gap-1 overflow-x-auto px-2.5 pb-2 pt-2.5",
516
+ children: filterViews.map(({ count, key, label }) => {
517
+ const isActive = key === resolvedActiveFilterKey;
518
+ return /* @__PURE__ */ jsxs("button", {
519
+ type: "button",
520
+ "aria-pressed": isActive,
521
+ onPointerDown: (event) => event.preventDefault(),
522
+ onClick: () => handleFilterSelect(key),
523
+ className: `inline-flex shrink-0 items-center gap-1 rounded-full border px-2 py-1 text-[11px] font-semibold transition focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 ${isActive ? "border-primary/30 bg-primary/10 text-primary" : "border-gray-200 bg-white text-gray-600 hover:border-gray-300 hover:bg-gray-50"}`,
524
+ children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
525
+ className: isActive ? "text-primary/70" : "text-gray-400",
526
+ children: count
539
527
  })]
540
528
  }, key);
541
529
  })
542
- })] })
530
+ }) : null, /* @__PURE__ */ jsx("div", {
531
+ ref: listRef,
532
+ role: "listbox",
533
+ "aria-label": texts.sectionLabel,
534
+ className: `custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain ${!isLoading && filterViews.length > 0 ? "px-2.5 pb-2.5" : "p-2.5"}`,
535
+ children: isLoading ? /* @__PURE__ */ jsx("div", {
536
+ className: "p-2 text-xs text-gray-500",
537
+ children: texts.loadingLabel
538
+ }) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
539
+ className: "mb-2 px-2 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
540
+ children: texts.sectionLabel
541
+ }) : null, visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", {
542
+ className: "px-2 text-xs text-gray-400",
543
+ children: texts.emptyLabel
544
+ }) : /* @__PURE__ */ jsx("div", {
545
+ className: "space-y-1",
546
+ children: visibleItems.map((item, index) => {
547
+ const { key, sectionKey, sectionLabel, title, subtitle } = item;
548
+ const isActive = index === activeIndexInRange;
549
+ const previousItem = visibleItems[index - 1];
550
+ return /* @__PURE__ */ jsxs("div", {
551
+ className: "space-y-1",
552
+ children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
553
+ className: "px-2 pb-0.5 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
554
+ children: sectionLabel
555
+ }) : null, /* @__PURE__ */ jsxs("button", {
556
+ type: "button",
557
+ role: "option",
558
+ "aria-selected": isActive,
559
+ "data-input-surface-index": index,
560
+ onPointerMove: (event) => {
561
+ if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
562
+ },
563
+ onPointerDown: (event) => {
564
+ if (event.button > 0) return;
565
+ event.preventDefault();
566
+ onSelectItem(item);
567
+ },
568
+ onClick: (event) => {
569
+ if (event.detail === 0) onSelectItem(item);
570
+ },
571
+ className: `flex w-full items-start gap-2 rounded-lg px-2 py-1.5 text-left transition ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-50"}`,
572
+ children: [/* @__PURE__ */ jsx("span", {
573
+ className: "truncate text-xs font-semibold",
574
+ children: title
575
+ }), /* @__PURE__ */ jsx("span", {
576
+ className: "truncate text-xs text-gray-500",
577
+ children: subtitle
578
+ })]
579
+ })]
580
+ }, key);
581
+ })
582
+ })] })
583
+ })]
543
584
  }), /* @__PURE__ */ jsx("div", {
544
585
  className: "custom-scrollbar min-h-0 min-w-0 overflow-y-auto overscroll-contain p-2.5",
545
586
  onPointerDown: onDetailsPointerDown,
@@ -634,6 +675,7 @@ function ChatInputSurfaceHost(props) {
634
675
  ref: menuRef,
635
676
  isOpen,
636
677
  isLoading: inputSurface.isLoading,
678
+ filterOptions: inputSurface.filterOptions,
637
679
  items: inputSurface.items,
638
680
  texts: inputSurface.texts,
639
681
  onSelectItem: (item) => {
@@ -775,7 +817,14 @@ function ChatInputBarActions({ sendError, sendErrorDetailsLabel, isSending, canS
775
817
  })] })]
776
818
  }) : null, /* @__PURE__ */ jsxs("div", {
777
819
  className: "flex items-center gap-2",
778
- children: [contextWindow ? /* @__PURE__ */ jsx(ContextWindowIndicator, { contextWindow }) : null, isSending ? canStopGeneration ? /* @__PURE__ */ jsx(ChatButton, {
820
+ children: [contextWindow ? /* @__PURE__ */ jsx(ContextWindowIndicator, { contextWindow }) : null, !isSending || !sendDisabled ? /* @__PURE__ */ jsx(ChatButton, {
821
+ size: "icon",
822
+ className: "h-8 w-8 rounded-full",
823
+ "aria-label": sendButtonLabel,
824
+ onClick: () => void onSend(),
825
+ disabled: sendDisabled,
826
+ children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-5 w-5" })
827
+ }) : canStopGeneration ? /* @__PURE__ */ jsx(ChatButton, {
779
828
  size: "icon",
780
829
  variant: "outline",
781
830
  className: "h-8 w-8 rounded-full",
@@ -799,14 +848,7 @@ function ChatInputBarActions({ sendError, sendErrorDetailsLabel, isSending, canS
799
848
  className: "text-xs",
800
849
  children: stopHint
801
850
  })
802
- })] }) }) : /* @__PURE__ */ jsx(ChatButton, {
803
- size: "icon",
804
- className: "h-8 w-8 rounded-full",
805
- "aria-label": sendButtonLabel,
806
- onClick: () => void onSend(),
807
- disabled: sendDisabled,
808
- children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-5 w-5" })
809
- })]
851
+ })] }) })]
810
852
  })]
811
853
  });
812
854
  }
@@ -1281,11 +1323,11 @@ function buildTokenClassName(tokenKind) {
1281
1323
  "gap-1.5",
1282
1324
  "rounded-lg",
1283
1325
  "border",
1284
- "border-slate-200/80",
1285
- "bg-slate-50",
1326
+ "border-border",
1327
+ "bg-muted",
1286
1328
  "px-2",
1287
1329
  "align-baseline",
1288
- "text-slate-700",
1330
+ "text-foreground",
1289
1331
  "transition-[border-color,background-color,box-shadow,color]",
1290
1332
  "duration-150"
1291
1333
  ].join(" ");
@@ -1310,7 +1352,7 @@ function buildTokenClassName(tokenKind) {
1310
1352
  }
1311
1353
  function ChatComposerTokenChip({ label, tokenKind }) {
1312
1354
  return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
1313
- className: tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-white text-slate-500 ring-1 ring-black/5" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70",
1355
+ className: tokenKind === "file" ? "inline-flex h-4.5 w-4.5 shrink-0 items-center justify-center rounded-md bg-card text-muted-foreground ring-1 ring-border" : "inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center text-primary/70",
1314
1356
  children: tokenKind === "file" ? /* @__PURE__ */ jsx(ImageIcon, {
1315
1357
  "aria-hidden": "true",
1316
1358
  className: "h-3 w-3"
@@ -1322,7 +1364,7 @@ function ChatComposerTokenChip({ label, tokenKind }) {
1322
1364
  className: "h-3 w-3"
1323
1365
  })
1324
1366
  }), /* @__PURE__ */ jsx("span", {
1325
- className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-slate-700" : "truncate",
1367
+ className: tokenKind === "file" ? "min-w-0 flex-1 truncate text-[12px] font-medium text-foreground" : "truncate",
1326
1368
  children: label
1327
1369
  })] });
1328
1370
  }
@@ -1786,7 +1828,6 @@ function deleteChatComposerContent(params) {
1786
1828
  //#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-controller.ts
1787
1829
  function resolveLexicalComposerKeyboardAction(params) {
1788
1830
  const { canStopGeneration, isComposing, isSending, key, shiftKey } = params;
1789
- if (key === "Enter" && !shiftKey && isSending) return { type: "consume" };
1790
1831
  if (key === "Escape") {
1791
1832
  if (isSending && canStopGeneration) return { type: "stop-generation" };
1792
1833
  return { type: "noop" };
@@ -1853,7 +1894,6 @@ function handleLexicalComposerKeyboardCommand(params) {
1853
1894
  });
1854
1895
  if (action.type !== "noop") nativeEvent.preventDefault();
1855
1896
  switch (action.type) {
1856
- case "consume": return true;
1857
1897
  case "stop-generation":
1858
1898
  actions.onStop();
1859
1899
  return true;
@@ -2269,10 +2309,11 @@ function InputBarHint({ hint }) {
2269
2309
  })
2270
2310
  });
2271
2311
  }
2272
- const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSurface, slashMenu, surface, toolbar: toolbarProps }, ref) {
2312
+ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSurface, slashMenu, surface, toolbar: toolbarProps, topSlot }, ref) {
2273
2313
  const composerRef = useRef(null);
2274
2314
  const resolvedInputSurface = inputSurface ?? (slashMenu ? {
2275
2315
  isLoading: slashMenu.isLoading,
2316
+ filterOptions: slashMenu.filterOptions,
2276
2317
  items: slashMenu.items,
2277
2318
  onSelectItem: slashMenu.onSelectItem,
2278
2319
  texts: {
@@ -2309,6 +2350,10 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSur
2309
2350
  children: /* @__PURE__ */ jsxs("div", {
2310
2351
  className: "overflow-hidden rounded-2xl border border-border bg-card shadow-card",
2311
2352
  children: [
2353
+ topSlot ? /* @__PURE__ */ jsx("div", {
2354
+ className: "px-3 pb-0 pt-2 sm:px-4 sm:pt-2.5",
2355
+ children: topSlot
2356
+ }) : null,
2312
2357
  /* @__PURE__ */ jsx("div", {
2313
2358
  className: "relative",
2314
2359
  children: /* @__PURE__ */ jsx(ChatInputSurfaceHost, {
@@ -2649,7 +2694,7 @@ var ChatCodeSyntaxHighlighter = class {
2649
2694
  const chatCodeSyntaxHighlighter = new ChatCodeSyntaxHighlighter();
2650
2695
  //#endregion
2651
2696
  //#region src/components/chat/ui/chat-message-list/chat-code-block.tsx
2652
- const CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
2697
+ const CODE_LANGUAGE_REGEX$1 = /language-([a-z0-9-]+)/i;
2653
2698
  function flattenNodeText(value) {
2654
2699
  if (typeof value === "string" || typeof value === "number") return String(value);
2655
2700
  if (Array.isArray(value)) return value.map(flattenNodeText).join("");
@@ -2660,7 +2705,7 @@ function normalizeCodeText(value) {
2660
2705
  return content.endsWith("\n") ? content.slice(0, -1) : content;
2661
2706
  }
2662
2707
  function resolveCodeLanguage(className) {
2663
- return (className ? CODE_LANGUAGE_REGEX.exec(className) : null)?.[1]?.toLowerCase() || "text";
2708
+ return (className ? CODE_LANGUAGE_REGEX$1.exec(className) : null)?.[1]?.toLowerCase() || "text";
2664
2709
  }
2665
2710
  function ChatCodeBlock({ className, children, texts }) {
2666
2711
  const language = useMemo(() => resolveCodeLanguage(className), [className]);
@@ -2693,6 +2738,176 @@ function ChatCodeBlock({ className, children, texts }) {
2693
2738
  });
2694
2739
  }
2695
2740
  //#endregion
2741
+ //#region src/components/chat/ui/chat-message-list/utils/chat-inline-display.utils.ts
2742
+ const INLINE_DISPLAY_LANGUAGE = "nextclaw-inline";
2743
+ const CODE_LANGUAGE_REGEX = /language-([a-z0-9-]+)/i;
2744
+ const FILE_PREVIEW_VIEWERS = new Set([
2745
+ "auto",
2746
+ "source",
2747
+ "rendered"
2748
+ ]);
2749
+ function isRecord$1(value) {
2750
+ return typeof value === "object" && value !== null && !Array.isArray(value);
2751
+ }
2752
+ function readString(value) {
2753
+ return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
2754
+ }
2755
+ function readPositiveInteger(value) {
2756
+ return Number.isInteger(value) && Number(value) > 0 ? Number(value) : void 0;
2757
+ }
2758
+ function readFilePreviewViewer(value) {
2759
+ const viewer = readString(value);
2760
+ return viewer && FILE_PREVIEW_VIEWERS.has(viewer) ? viewer : void 0;
2761
+ }
2762
+ function readTargetPayload(record) {
2763
+ const { payload } = record;
2764
+ return isRecord$1(payload) ? payload : null;
2765
+ }
2766
+ function readFileTarget(record) {
2767
+ const payload = readTargetPayload(record);
2768
+ if (!payload) return null;
2769
+ const path = readString(payload.path);
2770
+ if (!path) return null;
2771
+ return {
2772
+ type: "file",
2773
+ payload: {
2774
+ path,
2775
+ line: readPositiveInteger(payload.line),
2776
+ column: readPositiveInteger(payload.column),
2777
+ viewer: readFilePreviewViewer(payload.viewer)
2778
+ }
2779
+ };
2780
+ }
2781
+ function readUrlTarget(record) {
2782
+ const payload = readTargetPayload(record);
2783
+ if (!payload) return null;
2784
+ const url = readString(payload.url);
2785
+ if (!url) return null;
2786
+ return {
2787
+ type: "url",
2788
+ payload: { url }
2789
+ };
2790
+ }
2791
+ function readPanelAppTarget(record) {
2792
+ const payload = readTargetPayload(record);
2793
+ if (!payload) return null;
2794
+ const appId = readString(payload.appId);
2795
+ if (!appId) return null;
2796
+ return {
2797
+ type: "panel_app",
2798
+ payload: { appId }
2799
+ };
2800
+ }
2801
+ function readJsonTarget(record) {
2802
+ const payload = readTargetPayload(record);
2803
+ if (!payload || !("value" in payload)) return null;
2804
+ return {
2805
+ type: "json",
2806
+ payload: { value: payload.value }
2807
+ };
2808
+ }
2809
+ function readInlineDisplayTarget(value) {
2810
+ if (!isRecord$1(value)) return null;
2811
+ const type = readString(value.type);
2812
+ if (type === "file") return readFileTarget(value);
2813
+ if (type === "url") return readUrlTarget(value);
2814
+ if (type === "panel_app") return readPanelAppTarget(value);
2815
+ if (type === "json") return readJsonTarget(value);
2816
+ return null;
2817
+ }
2818
+ function isChatInlineDisplayLanguage(className) {
2819
+ return (className ? CODE_LANGUAGE_REGEX.exec(className) : null)?.[1]?.toLowerCase() === INLINE_DISPLAY_LANGUAGE;
2820
+ }
2821
+ function parseChatInlineDisplayDirective(rawText) {
2822
+ const text = rawText.endsWith("\n") ? rawText.slice(0, -1) : rawText;
2823
+ let parsed;
2824
+ try {
2825
+ parsed = JSON.parse(text);
2826
+ } catch {
2827
+ return null;
2828
+ }
2829
+ if (!isRecord$1(parsed)) return null;
2830
+ const target = readInlineDisplayTarget(parsed.target);
2831
+ if (!target) return null;
2832
+ return {
2833
+ target,
2834
+ title: readString(parsed.title),
2835
+ description: readString(parsed.description)
2836
+ };
2837
+ }
2838
+ function getChatInlineDisplayLabel(display) {
2839
+ if (display.title) return display.title;
2840
+ if (display.target.type === "file") {
2841
+ const segments = display.target.payload.path.split("/").filter(Boolean);
2842
+ return segments[segments.length - 1] ?? display.target.payload.path;
2843
+ }
2844
+ if (display.target.type === "url") return display.target.payload.url;
2845
+ if (display.target.type === "panel_app") return display.target.payload.appId;
2846
+ return "JSON";
2847
+ }
2848
+ function getChatInlineDisplayDetail(display) {
2849
+ if (display.target.type === "file") return display.target.payload.path;
2850
+ if (display.target.type === "url") return display.target.payload.url;
2851
+ if (display.target.type === "panel_app") return display.target.payload.appId;
2852
+ return JSON.stringify(display.target.payload.value, null, 2);
2853
+ }
2854
+ //#endregion
2855
+ //#region src/components/chat/ui/chat-message-list/chat-inline-display.tsx
2856
+ function ChatInlineDisplayIcon({ display }) {
2857
+ const className = "h-3.5 w-3.5 shrink-0 text-muted-foreground";
2858
+ if (display.target.type === "file") return /* @__PURE__ */ jsx(FileText, {
2859
+ className,
2860
+ "aria-hidden": true
2861
+ });
2862
+ if (display.target.type === "url") return /* @__PURE__ */ jsx(Globe, {
2863
+ className,
2864
+ "aria-hidden": true
2865
+ });
2866
+ if (display.target.type === "panel_app") return /* @__PURE__ */ jsx(AppWindow, {
2867
+ className,
2868
+ "aria-hidden": true
2869
+ });
2870
+ return /* @__PURE__ */ jsx(Code2, {
2871
+ className,
2872
+ "aria-hidden": true
2873
+ });
2874
+ }
2875
+ function formatJsonDetail(value) {
2876
+ return value.length > 1200 ? `${value.slice(0, 1200)}\n...` : value;
2877
+ }
2878
+ function ChatInlineDisplayFallback({ display }) {
2879
+ const label = getChatInlineDisplayLabel(display);
2880
+ const detail = getChatInlineDisplayDetail(display);
2881
+ const formattedDetail = display.target.type === "json" ? formatJsonDetail(detail) : detail;
2882
+ return /* @__PURE__ */ jsxs("section", {
2883
+ className: cn("my-2 max-w-full border-l-2 border-border bg-muted/30 px-3 py-2", "text-xs leading-relaxed text-foreground"),
2884
+ "data-nextclaw-inline-display": "true",
2885
+ "data-nextclaw-inline-display-type": display.target.type,
2886
+ children: [
2887
+ /* @__PURE__ */ jsxs("div", {
2888
+ className: "flex min-w-0 items-center gap-2",
2889
+ children: [/* @__PURE__ */ jsx(ChatInlineDisplayIcon, { display }), /* @__PURE__ */ jsx("span", {
2890
+ className: "min-w-0 truncate font-medium",
2891
+ children: label
2892
+ })]
2893
+ }),
2894
+ display.description ? /* @__PURE__ */ jsx("p", {
2895
+ className: "mt-1 line-clamp-2 text-[12px] text-muted-foreground",
2896
+ children: display.description
2897
+ }) : null,
2898
+ /* @__PURE__ */ jsx("pre", {
2899
+ className: cn("mt-1 max-h-40 overflow-auto whitespace-pre-wrap break-words", "font-mono text-[11px] leading-relaxed text-muted-foreground"),
2900
+ children: formattedDetail
2901
+ })
2902
+ ]
2903
+ });
2904
+ }
2905
+ function ChatInlineDisplay({ display, renderInlineDisplay }) {
2906
+ const rendered = renderInlineDisplay?.(display);
2907
+ if (rendered !== void 0 && rendered !== null) return /* @__PURE__ */ jsx(Fragment$1, { children: rendered });
2908
+ return /* @__PURE__ */ jsx(ChatInlineDisplayFallback, { display });
2909
+ }
2910
+ //#endregion
2696
2911
  //#region src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
2697
2912
  const MARKDOWN_MAX_CHARS = 14e4;
2698
2913
  const SAFE_LINK_PROTOCOLS = new Set([
@@ -2848,7 +3063,7 @@ function readStringProp(props, key) {
2848
3063
  const value = props[key];
2849
3064
  return typeof value === "string" && value.length > 0 ? value : null;
2850
3065
  }
2851
- function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen }) {
3066
+ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, renderInlineDisplay }) {
2852
3067
  const isUser = role === "user";
2853
3068
  const remarkPlugins = useMemo(() => inlineTokens && inlineTokens.length > 0 ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm], [inlineTokens]);
2854
3069
  const markdownComponents = useMemo(() => ({
@@ -2869,7 +3084,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
2869
3084
  children
2870
3085
  });
2871
3086
  },
2872
- a: ({ href, children, ...rest }) => {
3087
+ a: ({ node: _node, href, children, ...rest }) => {
2873
3088
  const safeHref = resolveSafeHref(href);
2874
3089
  if (!safeHref) return /* @__PURE__ */ jsx("span", {
2875
3090
  className: "chat-link-invalid",
@@ -2892,14 +3107,14 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
2892
3107
  children
2893
3108
  });
2894
3109
  },
2895
- table: ({ children, ...rest }) => /* @__PURE__ */ jsx("div", {
3110
+ table: ({ node: _node, children, ...rest }) => /* @__PURE__ */ jsx("div", {
2896
3111
  className: "chat-table-wrap",
2897
3112
  children: /* @__PURE__ */ jsx("table", {
2898
3113
  ...rest,
2899
3114
  children
2900
3115
  })
2901
3116
  }),
2902
- input: ({ type, checked, ...rest }) => {
3117
+ input: ({ node: _node, type, checked, ...rest }) => {
2903
3118
  if (type !== "checkbox") return /* @__PURE__ */ jsx("input", {
2904
3119
  ...rest,
2905
3120
  type
@@ -2913,7 +3128,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
2913
3128
  className: "chat-task-checkbox"
2914
3129
  });
2915
3130
  },
2916
- img: ({ src, alt, ...rest }) => {
3131
+ img: ({ node: _node, src, alt, ...rest }) => {
2917
3132
  const safeSrc = resolveSafeHref(src);
2918
3133
  if (!safeSrc) return null;
2919
3134
  return /* @__PURE__ */ jsx("img", {
@@ -2924,12 +3139,18 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
2924
3139
  decoding: "async"
2925
3140
  });
2926
3141
  },
2927
- code: ({ className, children, ...rest }) => {
2928
- if (!className && !String(children ?? "").includes("\n")) return /* @__PURE__ */ jsx("code", {
3142
+ code: ({ node: _node, className, children, ...rest }) => {
3143
+ const plainText = String(children ?? "");
3144
+ if (!className && !plainText.includes("\n")) return /* @__PURE__ */ jsx("code", {
2929
3145
  ...rest,
2930
3146
  className: cn("chat-inline-code", className),
2931
3147
  children
2932
3148
  });
3149
+ const inlineDisplay = isChatInlineDisplayLanguage(className) ? parseChatInlineDisplayDirective(plainText) : null;
3150
+ if (inlineDisplay) return /* @__PURE__ */ jsx(ChatInlineDisplay, {
3151
+ display: inlineDisplay,
3152
+ renderInlineDisplay
3153
+ });
2933
3154
  return /* @__PURE__ */ jsx(ChatCodeBlock, {
2934
3155
  className,
2935
3156
  texts,
@@ -2940,6 +3161,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
2940
3161
  inline,
2941
3162
  isUser,
2942
3163
  onFileOpen,
3164
+ renderInlineDisplay,
2943
3165
  texts
2944
3166
  ]);
2945
3167
  return /* @__PURE__ */ jsx(inline ? "span" : "div", {
@@ -3371,12 +3593,20 @@ function useReasoningBlockOpenState(params) {
3371
3593
  //#region src/components/chat/hooks/use-sticky-bottom-scroll.ts
3372
3594
  const DEFAULT_STICKY_THRESHOLD_PX = 10;
3373
3595
  function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey, scrollRef, stickyThresholdPx }) {
3596
+ const [isAtBottom, setIsAtBottom] = useState(true);
3374
3597
  const isStickyRef = useRef(true);
3375
3598
  const isProgrammaticScrollRef = useRef(false);
3376
3599
  const previousResetKeyRef = useRef(null);
3377
3600
  const pendingInitialScrollRef = useRef(false);
3378
3601
  const scheduledScrollFrameRef = useRef(null);
3379
- const queueScrollToBottom = useCallback(() => {
3602
+ const updateStickyState = useCallback((nextIsAtBottom) => {
3603
+ isStickyRef.current = nextIsAtBottom;
3604
+ setIsAtBottom(nextIsAtBottom);
3605
+ }, []);
3606
+ const resolveIsAtBottom = useCallback((element) => {
3607
+ return element.scrollHeight - element.scrollTop - element.clientHeight <= (stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
3608
+ }, [stickyThresholdPx]);
3609
+ const queueScrollToBottom = useCallback((behavior = "auto") => {
3380
3610
  if (!scrollRef.current) return;
3381
3611
  if (scheduledScrollFrameRef.current !== null) cancelAnimationFrame(scheduledScrollFrameRef.current);
3382
3612
  scheduledScrollFrameRef.current = requestAnimationFrame(() => {
@@ -3384,24 +3614,36 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
3384
3614
  const currentElement = scrollRef.current;
3385
3615
  if (!currentElement) return;
3386
3616
  isProgrammaticScrollRef.current = true;
3387
- currentElement.scrollTop = currentElement.scrollHeight;
3617
+ if (typeof currentElement.scrollTo === "function") currentElement.scrollTo({
3618
+ top: currentElement.scrollHeight,
3619
+ behavior
3620
+ });
3621
+ else currentElement.scrollTop = currentElement.scrollHeight;
3388
3622
  });
3389
3623
  }, [scrollRef]);
3390
- const onScroll = () => {
3624
+ const scrollToBottom = useCallback(() => {
3625
+ updateStickyState(true);
3626
+ queueScrollToBottom();
3627
+ }, [queueScrollToBottom, updateStickyState]);
3628
+ const onScroll = useCallback(() => {
3391
3629
  if (isProgrammaticScrollRef.current) {
3392
3630
  isProgrammaticScrollRef.current = false;
3393
3631
  return;
3394
3632
  }
3395
3633
  const element = scrollRef.current;
3396
3634
  if (!element) return;
3397
- isStickyRef.current = element.scrollHeight - element.scrollTop - element.clientHeight <= (stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
3398
- };
3635
+ updateStickyState(resolveIsAtBottom(element));
3636
+ }, [
3637
+ resolveIsAtBottom,
3638
+ scrollRef,
3639
+ updateStickyState
3640
+ ]);
3399
3641
  useEffect(() => {
3400
3642
  if (previousResetKeyRef.current === resetKey) return;
3401
3643
  previousResetKeyRef.current = resetKey;
3402
- isStickyRef.current = true;
3644
+ updateStickyState(true);
3403
3645
  pendingInitialScrollRef.current = true;
3404
- }, [resetKey]);
3646
+ }, [resetKey, updateStickyState]);
3405
3647
  useEffect(() => {
3406
3648
  return () => {
3407
3649
  const scheduledScrollFrame = scheduledScrollFrameRef.current;
@@ -3432,7 +3674,11 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
3432
3674
  queueScrollToBottom,
3433
3675
  scrollRef
3434
3676
  ]);
3435
- return { onScroll };
3677
+ return {
3678
+ isAtBottom,
3679
+ onScroll,
3680
+ scrollToBottom
3681
+ };
3436
3682
  }
3437
3683
  //#endregion
3438
3684
  //#region src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
@@ -3639,6 +3885,20 @@ function readLineNumberColumnWidth(block) {
3639
3885
  function readCodeColumnWidth(block) {
3640
3886
  return `calc(${block.lines.reduce((currentMax, line) => Math.max(currentMax, Math.max(1, line.text.length)), 1)}ch + 1.25rem)`;
3641
3887
  }
3888
+ function readPathExtension(path) {
3889
+ const fileName = path.split(/[\\/]/).filter(Boolean).pop() ?? path;
3890
+ const cleanName = fileName.split(/[?#]/)[0] ?? fileName;
3891
+ const extensionStart = cleanName.lastIndexOf(".");
3892
+ if (extensionStart <= 0 || extensionStart === cleanName.length - 1) return null;
3893
+ return cleanName.slice(extensionStart + 1).toLowerCase();
3894
+ }
3895
+ function readBlockLanguage(block) {
3896
+ return block.languageHint?.trim() || readPathExtension(block.path) || "text";
3897
+ }
3898
+ function readLanguageClassName(language) {
3899
+ const normalized = language.toLowerCase().replace(/[^a-z0-9-]/g, "-");
3900
+ return normalized ? `language-${normalized}` : "language-text";
3901
+ }
3642
3902
  function getLineNumberTone(line) {
3643
3903
  if (line.kind === "remove") return "border-r border-rose-200 bg-rose-50 text-rose-700";
3644
3904
  if (line.kind === "add") return "border-r border-emerald-200 bg-emerald-50 text-emerald-700";
@@ -3660,14 +3920,17 @@ function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
3660
3920
  children: readVisibleLineNumber(line)
3661
3921
  });
3662
3922
  }
3663
- function FileOperationCodeCell({ line }) {
3923
+ function FileOperationCodeCell({ language, line }) {
3924
+ const highlightedCode = useMemo(() => chatCodeSyntaxHighlighter.highlight(line.text || " ", language), [language, line.text]);
3664
3925
  return /* @__PURE__ */ jsx("span", {
3665
3926
  "data-file-code-row": "true",
3666
- className: cn("block min-w-0 flex-1 whitespace-pre px-2.5", getCodeRowTone(line)),
3667
- children: line.text || " "
3927
+ "data-file-code-language": highlightedCode.language,
3928
+ "data-highlighted": highlightedCode.highlighted ? "true" : "false",
3929
+ className: cn("chat-file-code-syntax hljs block min-w-0 flex-1 whitespace-pre px-2.5", readLanguageClassName(highlightedCode.language), getCodeRowTone(line)),
3930
+ dangerouslySetInnerHTML: { __html: highlightedCode.html }
3668
3931
  });
3669
3932
  }
3670
- function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth }) {
3933
+ function FileOperationLineRow({ language, line, showLineNumbers, lineNumberColumnWidth }) {
3671
3934
  return /* @__PURE__ */ jsxs("div", {
3672
3935
  "data-file-line-row": "true",
3673
3936
  className: FILE_ROW_CLASS_NAME,
@@ -3675,13 +3938,17 @@ function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth })
3675
3938
  layout: "compact",
3676
3939
  line,
3677
3940
  lineNumberColumnWidth
3678
- }) : null, /* @__PURE__ */ jsx(FileOperationCodeCell, { line })]
3941
+ }) : null, /* @__PURE__ */ jsx(FileOperationCodeCell, {
3942
+ language,
3943
+ line
3944
+ })]
3679
3945
  });
3680
3946
  }
3681
3947
  function FileOperationWorkspaceSurface({ block }) {
3682
3948
  const showLineNumbers = readHasBlockLineNumbers(block);
3683
3949
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
3684
3950
  const codeColumnWidth = readCodeColumnWidth(block);
3951
+ const language = readBlockLanguage(block);
3685
3952
  return /* @__PURE__ */ jsxs("div", {
3686
3953
  "data-file-code-surface": "true",
3687
3954
  "data-file-code-surface-layout": "workspace",
@@ -3708,7 +3975,10 @@ function FileOperationWorkspaceSurface({ block }) {
3708
3975
  children: block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
3709
3976
  "data-file-code-canvas-row": "true",
3710
3977
  className: FILE_ROW_CLASS_NAME,
3711
- children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
3978
+ children: /* @__PURE__ */ jsx(FileOperationCodeCell, {
3979
+ language,
3980
+ line
3981
+ })
3712
3982
  }, readLineKey("code", line, index)))
3713
3983
  }), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-card" })]
3714
3984
  })]
@@ -3720,6 +3990,7 @@ function FileOperationCodeSurface({ block, layout = "compact" }) {
3720
3990
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
3721
3991
  const codeColumnWidth = readCodeColumnWidth(block);
3722
3992
  const surfaceMinWidth = showLineNumbers ? `calc(${lineNumberColumnWidth} + ${codeColumnWidth})` : codeColumnWidth;
3993
+ const language = readBlockLanguage(block);
3723
3994
  return /* @__PURE__ */ jsx("div", {
3724
3995
  "data-file-code-surface": "true",
3725
3996
  "data-file-code-surface-layout": "compact",
@@ -3729,6 +4000,7 @@ function FileOperationCodeSurface({ block, layout = "compact" }) {
3729
4000
  className: "min-w-full",
3730
4001
  style: { minWidth: surfaceMinWidth },
3731
4002
  children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
4003
+ language,
3732
4004
  line,
3733
4005
  showLineNumbers,
3734
4006
  lineNumberColumnWidth
@@ -4039,20 +4311,21 @@ function GenericToolSection({ label, tone, children }) {
4039
4311
  function TerminalExecutionView({ card }) {
4040
4312
  const output = normalizeTerminalOutput(card.output, card.outputData);
4041
4313
  const isRunning = card.statusTone === "running";
4042
- const hasContent = !!(card.summary?.trim() || output.trim().length > 0);
4314
+ const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
4315
+ const hasOutput = output.trim().length > 0;
4316
+ const canExpand = isRunning || hasOutput || Boolean(commandPart?.trim());
4043
4317
  const { expanded, onToggle } = useToolCardExpandedState({
4044
- canExpand: output.trim().length > 0 || isRunning,
4318
+ canExpand,
4045
4319
  isRunning,
4046
4320
  autoExpandWhileRunning: false,
4047
- expandOnError: hasContent,
4321
+ expandOnError: canExpand,
4048
4322
  statusTone: card.statusTone
4049
4323
  });
4050
- const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
4051
4324
  return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
4052
4325
  card,
4053
4326
  icon: Terminal,
4054
4327
  expanded,
4055
- canExpand: output.trim().length > 0 || isRunning,
4328
+ canExpand,
4056
4329
  onToggle
4057
4330
  }), expanded && /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("div", {
4058
4331
  className: "px-3 pb-2 font-mono w-full max-h-48 overflow-y-auto custom-scrollbar min-h-0 text-[12px]",
@@ -4069,10 +4342,13 @@ function TerminalExecutionView({ card }) {
4069
4342
  }) : /* @__PURE__ */ jsx("div", { className: "h-3 w-32 bg-muted rounded animate-pulse mt-2" })
4070
4343
  })]
4071
4344
  })
4072
- }), output && /* @__PURE__ */ jsx(ToolCardContent, { children: /* @__PURE__ */ jsxs("pre", {
4073
- className: "font-mono text-[12px] text-muted-foreground whitespace-pre-wrap break-all w-full max-w-full max-h-64 overflow-y-auto overflow-x-hidden min-w-0 custom-scrollbar leading-relaxed px-0",
4074
- children: [output, isRunning && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
4075
- }) })] })] });
4345
+ }), (hasOutput || !isRunning) && /* @__PURE__ */ jsx(ToolCardContent, {
4346
+ className: hasOutput ? void 0 : "bg-muted/20 py-2",
4347
+ children: /* @__PURE__ */ jsxs("pre", {
4348
+ className: cn("font-mono text-[12px] text-muted-foreground whitespace-pre-wrap break-all w-full max-w-full max-h-64 overflow-y-auto overflow-x-hidden min-w-0 custom-scrollbar leading-relaxed", hasOutput ? "px-0" : "rounded-md border border-dashed border-border bg-card/70 px-3 py-2 italic"),
4349
+ children: [hasOutput ? output : card.emptyLabel, isRunning && hasOutput && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
4350
+ })
4351
+ })] })] });
4076
4352
  }
4077
4353
  function FileOperationView({ card, onFileOpen }) {
4078
4354
  const output = card.output?.trim() ?? "";
@@ -4218,75 +4494,130 @@ function ChatToolCard({ card, onToolAction, onFileOpen, renderToolAgent, renderP
4218
4494
  }
4219
4495
  //#endregion
4220
4496
  //#region src/components/chat/ui/chat-message-list/chat-message.tsx
4221
- const ChatMessage = memo(function ChatMessage({ message, texts, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
4497
+ function isMessageInProgress(status) {
4498
+ return status === "pending" || status === "streaming";
4499
+ }
4500
+ function isProcessPart(part) {
4501
+ return part.type === "reasoning" || part.type === "tool-card";
4502
+ }
4503
+ function splitAssistantProcess(message) {
4504
+ if (message.role !== "assistant" || !message.processSummary || isMessageInProgress(message.status)) return null;
4505
+ let lastProcessPartIndex = -1;
4506
+ for (let index = message.parts.length - 1; index >= 0; index -= 1) if (isProcessPart(message.parts[index])) {
4507
+ lastProcessPartIndex = index;
4508
+ break;
4509
+ }
4510
+ if (lastProcessPartIndex < 0 || lastProcessPartIndex >= message.parts.length - 1) return null;
4511
+ return {
4512
+ processParts: message.parts.slice(0, lastProcessPartIndex + 1),
4513
+ finalParts: message.parts.slice(lastProcessPartIndex + 1)
4514
+ };
4515
+ }
4516
+ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFileOpen, onToolAction, part, renderInlineDisplay, renderPanelAppCard, renderToolAgent, role, texts }) {
4517
+ const { type } = part;
4518
+ if (type === "markdown") {
4519
+ const { inlineTokens, text } = part;
4520
+ return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
4521
+ text,
4522
+ role,
4523
+ texts,
4524
+ inlineTokens,
4525
+ onFileOpen,
4526
+ renderInlineDisplay
4527
+ }, `markdown-${index}`);
4528
+ }
4529
+ if (type === "reasoning") {
4530
+ const { label, text } = part;
4531
+ return /* @__PURE__ */ jsx(ChatReasoningBlock, {
4532
+ label,
4533
+ text,
4534
+ isUser,
4535
+ isInProgress: isInProgress && isLastPart
4536
+ }, `reasoning-${index}`);
4537
+ }
4538
+ if (type === "tool-card") {
4539
+ const { card } = part;
4540
+ return /* @__PURE__ */ jsx("div", {
4541
+ className: "mt-0.5",
4542
+ children: /* @__PURE__ */ jsx(ChatToolCard, {
4543
+ card,
4544
+ onToolAction,
4545
+ onFileOpen,
4546
+ renderToolAgent,
4547
+ renderPanelAppCard
4548
+ })
4549
+ }, `tool-${index}`);
4550
+ }
4551
+ if (type === "file") {
4552
+ const { file } = part;
4553
+ return /* @__PURE__ */ jsx(ChatMessageFile, {
4554
+ file,
4555
+ isUser,
4556
+ texts
4557
+ }, `file-${index}`);
4558
+ }
4559
+ if (type === "unknown") {
4560
+ const { label, rawType, text } = part;
4561
+ return /* @__PURE__ */ jsxs("div", {
4562
+ className: "rounded-lg border border-border bg-muted/60 px-2.5 py-2 text-xs text-muted-foreground",
4563
+ children: [/* @__PURE__ */ jsxs("div", {
4564
+ className: "font-semibold text-foreground",
4565
+ children: [
4566
+ label,
4567
+ ": ",
4568
+ rawType
4569
+ ]
4570
+ }), text ? /* @__PURE__ */ jsx("pre", {
4571
+ className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-muted-foreground",
4572
+ children: text
4573
+ }) : null]
4574
+ }, `unknown-${index}`);
4575
+ }
4576
+ return null;
4577
+ }
4578
+ const ChatMessage = memo(function ChatMessage({ message, texts, onToolAction, onFileOpen, renderInlineDisplay, renderToolAgent, renderPanelAppCard }) {
4222
4579
  const { role } = message;
4223
4580
  const isUser = role === "user";
4224
- const isMessageInProgress = message.status === "pending" || message.status === "streaming";
4581
+ const isInProgress = isMessageInProgress(message.status);
4582
+ const processSplit = splitAssistantProcess(message);
4583
+ const renderPart = (part, index) => renderChatMessagePart({
4584
+ part,
4585
+ index,
4586
+ role,
4587
+ isUser,
4588
+ isInProgress,
4589
+ isLastPart: index === message.parts.length - 1,
4590
+ texts,
4591
+ onToolAction,
4592
+ onFileOpen,
4593
+ renderInlineDisplay,
4594
+ renderToolAgent,
4595
+ renderPanelAppCard
4596
+ });
4225
4597
  return /* @__PURE__ */ jsx("div", {
4226
- className: cn("inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm", isUser ? "border-primary bg-primary py-3 text-primary-foreground" : role === "assistant" ? "border-border bg-card pb-3 pt-4 text-card-foreground" : "border-border bg-muted/45 py-3 text-foreground"),
4598
+ className: cn("inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm has-[[data-chat-message-wide-content=true]]:w-full", isUser ? "border-primary bg-primary py-3 text-primary-foreground" : role === "assistant" ? "border-border bg-card pb-3 pt-4 text-card-foreground" : "border-border bg-muted/45 py-3 text-foreground"),
4227
4599
  children: /* @__PURE__ */ jsx("div", {
4228
4600
  className: "space-y-2",
4229
- children: message.parts.map((part, index) => {
4230
- const { type } = part;
4231
- if (type === "markdown") {
4232
- const { inlineTokens, text } = part;
4233
- return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
4234
- text,
4235
- role,
4236
- texts,
4237
- inlineTokens,
4238
- onFileOpen
4239
- }, `markdown-${index}`);
4240
- }
4241
- if (type === "reasoning") {
4242
- const { label, text } = part;
4243
- return /* @__PURE__ */ jsx(ChatReasoningBlock, {
4244
- label,
4245
- text,
4246
- isUser,
4247
- isInProgress: isMessageInProgress && index === message.parts.length - 1
4248
- }, `reasoning-${index}`);
4249
- }
4250
- if (type === "tool-card") {
4251
- const { card } = part;
4252
- return /* @__PURE__ */ jsx("div", {
4253
- className: "mt-0.5",
4254
- children: /* @__PURE__ */ jsx(ChatToolCard, {
4255
- card,
4256
- onToolAction,
4257
- onFileOpen,
4258
- renderToolAgent,
4259
- renderPanelAppCard
4260
- })
4261
- }, `tool-${index}`);
4262
- }
4263
- if (type === "file") {
4264
- const { file } = part;
4265
- return /* @__PURE__ */ jsx(ChatMessageFile, {
4266
- file,
4267
- isUser,
4268
- texts
4269
- }, `file-${index}`);
4270
- }
4271
- if (type === "unknown") {
4272
- const { label, rawType, text } = part;
4273
- return /* @__PURE__ */ jsxs("div", {
4274
- className: "rounded-lg border border-border bg-muted/60 px-2.5 py-2 text-xs text-muted-foreground",
4275
- children: [/* @__PURE__ */ jsxs("div", {
4276
- className: "font-semibold text-foreground",
4277
- children: [
4278
- label,
4279
- ": ",
4280
- rawType
4281
- ]
4282
- }), text ? /* @__PURE__ */ jsx("pre", {
4283
- className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-muted-foreground",
4284
- children: text
4285
- }) : null]
4286
- }, `unknown-${index}`);
4287
- }
4288
- return null;
4289
- })
4601
+ children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("details", {
4602
+ className: "group/process text-xs text-muted-foreground",
4603
+ children: [/* @__PURE__ */ jsxs("summary", {
4604
+ className: "flex cursor-pointer list-none items-center gap-1.5 border-b border-border/60 pb-2 font-medium transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35 [&::-webkit-details-marker]:hidden",
4605
+ children: [
4606
+ /* @__PURE__ */ jsx(ChevronRight, {
4607
+ className: "h-3.5 w-3.5 group-open/process:hidden",
4608
+ strokeWidth: 2.5
4609
+ }),
4610
+ /* @__PURE__ */ jsx(ChevronDown, {
4611
+ className: "hidden h-3.5 w-3.5 group-open/process:block",
4612
+ strokeWidth: 2.5
4613
+ }),
4614
+ /* @__PURE__ */ jsx("span", { children: message.processSummary?.label })
4615
+ ]
4616
+ }), /* @__PURE__ */ jsx("div", {
4617
+ className: "space-y-2 pt-2",
4618
+ children: processSplit.processParts.map(renderPart)
4619
+ })]
4620
+ }), processSplit.finalParts.map((part, index) => renderPart(part, processSplit.processParts.length + index))] }) : message.parts.map(renderPart)
4290
4621
  })
4291
4622
  });
4292
4623
  });
@@ -4386,7 +4717,7 @@ function ChatTypingIndicator({ label }) {
4386
4717
  })]
4387
4718
  });
4388
4719
  }
4389
- function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAction, renderPanelAppCard, renderToolAgent, texts }) {
4720
+ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAction, renderInlineDisplay, renderPanelAppCard, renderToolAgent, texts }) {
4390
4721
  const visibleMessages = messages.filter(hasRenderableMessageContent);
4391
4722
  const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
4392
4723
  return /* @__PURE__ */ jsxs("div", {
@@ -4399,12 +4730,13 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
4399
4730
  children: [
4400
4731
  !isUser ? /* @__PURE__ */ jsx(ChatMessageAvatar, { role: message.role }) : null,
4401
4732
  /* @__PURE__ */ jsxs("div", {
4402
- className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"),
4733
+ className: cn("w-fit max-w-[92%] space-y-2 has-[[data-chat-message-wide-content=true]]:w-full", isUser && "flex flex-col items-end"),
4403
4734
  children: [/* @__PURE__ */ jsx(ChatMessage, {
4404
4735
  message,
4405
4736
  texts,
4406
4737
  onToolAction,
4407
4738
  onFileOpen,
4739
+ renderInlineDisplay,
4408
4740
  renderToolAgent,
4409
4741
  renderPanelAppCard
4410
4742
  }), /* @__PURE__ */ jsx("div", {