@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.d.ts +54 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +509 -177
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
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
|
|
412
|
-
const
|
|
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 =
|
|
415
|
-
const activeItem =
|
|
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 (
|
|
458
|
+
if (visibleItems.length === 0) return false;
|
|
437
459
|
if (event.key === "ArrowDown") {
|
|
438
460
|
event.preventDefault();
|
|
439
|
-
setActiveIndexForCurrentItems((index) => (index + 1) %
|
|
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 +
|
|
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(
|
|
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:
|
|
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__ */
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
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
|
|
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
|
-
})] }) })
|
|
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-
|
|
1285
|
-
"bg-
|
|
1326
|
+
"border-border",
|
|
1327
|
+
"bg-muted",
|
|
1286
1328
|
"px-2",
|
|
1287
1329
|
"align-baseline",
|
|
1288
|
-
"text-
|
|
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-
|
|
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-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
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
|
-
|
|
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
|
-
|
|
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 {
|
|
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
|
-
|
|
3667
|
-
|
|
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, {
|
|
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, {
|
|
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
|
|
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
|
|
4318
|
+
canExpand,
|
|
4045
4319
|
isRunning,
|
|
4046
4320
|
autoExpandWhileRunning: false,
|
|
4047
|
-
expandOnError:
|
|
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
|
|
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
|
-
}),
|
|
4073
|
-
className:
|
|
4074
|
-
children:
|
|
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
|
-
|
|
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
|
|
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:
|
|
4230
|
-
|
|
4231
|
-
|
|
4232
|
-
|
|
4233
|
-
|
|
4234
|
-
|
|
4235
|
-
|
|
4236
|
-
|
|
4237
|
-
|
|
4238
|
-
|
|
4239
|
-
|
|
4240
|
-
|
|
4241
|
-
|
|
4242
|
-
|
|
4243
|
-
|
|
4244
|
-
|
|
4245
|
-
|
|
4246
|
-
|
|
4247
|
-
|
|
4248
|
-
|
|
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", {
|