@nextclaw/agent-chat-ui 0.6.2 → 0.6.4
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +76 -5
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1601 -509
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -5,7 +5,7 @@ import { twMerge } from "tailwind-merge";
|
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
6
6
|
import * as PopoverPrimitive from "@radix-ui/react-popover";
|
|
7
7
|
import * as SelectPrimitive from "@radix-ui/react-select";
|
|
8
|
-
import { AlertTriangle, AppWindow, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, Globe, ImageIcon, Loader2, Minus, Paperclip, Puzzle, Search, Sparkles, Star, Terminal, User, Wrench } from "lucide-react";
|
|
8
|
+
import { AlertTriangle, AppWindow, ArrowUp, ArrowUpRight, Bot, Brain, BrainCircuit, CalendarClock, Check, ChevronDown, ChevronRight, ChevronUp, Code2, Copy, Expand, ExternalLink, Eye, File, FileArchive, FileAudio2, FileCode2, FileImage, FileJson2, FileSpreadsheet, FileText, FileVideo2, FolderTree, Globe, Globe2, ImageIcon, ListChecks, Loader2, MessageSquare, Minus, Paperclip, Puzzle, Search, Send, Settings, Sparkles, Star, Terminal, User, Workflow, Wrench, X } from "lucide-react";
|
|
9
9
|
import * as TooltipPrimitive from "@radix-ui/react-tooltip";
|
|
10
10
|
import { cva } from "class-variance-authority";
|
|
11
11
|
import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
@@ -29,6 +29,7 @@ import sql from "highlight.js/lib/languages/sql";
|
|
|
29
29
|
import typescript from "highlight.js/lib/languages/typescript";
|
|
30
30
|
import xml from "highlight.js/lib/languages/xml";
|
|
31
31
|
import yaml from "highlight.js/lib/languages/yaml";
|
|
32
|
+
import { createPortal } from "react-dom";
|
|
32
33
|
const CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC = {
|
|
33
34
|
key: "slash",
|
|
34
35
|
marker: "/"
|
|
@@ -512,7 +513,7 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
|
|
|
512
513
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
513
514
|
className: "flex min-h-0 flex-col border-r border-gray-200",
|
|
514
515
|
children: [!isLoading && filterViews.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
515
|
-
className: "flex shrink-0 gap-
|
|
516
|
+
className: "flex shrink-0 gap-0.5 overflow-x-auto px-2 pb-1.5 pt-2",
|
|
516
517
|
children: filterViews.map(({ count, key, label }) => {
|
|
517
518
|
const isActive = key === resolvedActiveFilterKey;
|
|
518
519
|
return /* @__PURE__ */ jsxs("button", {
|
|
@@ -520,9 +521,9 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
|
|
|
520
521
|
"aria-pressed": isActive,
|
|
521
522
|
onPointerDown: (event) => event.preventDefault(),
|
|
522
523
|
onClick: () => handleFilterSelect(key),
|
|
523
|
-
className: `inline-flex shrink-0 items-center gap-1 rounded-
|
|
524
|
+
className: `inline-flex h-7 shrink-0 items-center gap-1 rounded-md px-2 text-[11px] font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/40 ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-600 hover:bg-gray-100 hover:text-gray-900"}`,
|
|
524
525
|
children: [/* @__PURE__ */ jsx("span", { children: label }), /* @__PURE__ */ jsx("span", {
|
|
525
|
-
className: isActive ? "text-
|
|
526
|
+
className: isActive ? "text-gray-500" : "text-gray-400",
|
|
526
527
|
children: count
|
|
527
528
|
})]
|
|
528
529
|
}, key);
|
|
@@ -531,58 +532,52 @@ const ChatInputSurfaceMenu = forwardRef(function ChatInputSurfaceMenu(props, ref
|
|
|
531
532
|
ref: listRef,
|
|
532
533
|
role: "listbox",
|
|
533
534
|
"aria-label": texts.sectionLabel,
|
|
534
|
-
className: `custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain ${!isLoading && filterViews.length > 0 ? "px-2
|
|
535
|
+
className: `custom-scrollbar min-h-0 flex-1 overflow-y-auto overscroll-contain ${!isLoading && filterViews.length > 0 ? "px-2 pb-2" : "p-2"}`,
|
|
535
536
|
children: isLoading ? /* @__PURE__ */ jsx("div", {
|
|
536
537
|
className: "p-2 text-xs text-gray-500",
|
|
537
538
|
children: texts.loadingLabel
|
|
538
539
|
}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [!hasItemSections ? /* @__PURE__ */ jsx("div", {
|
|
539
|
-
className: "mb-
|
|
540
|
+
className: "mb-1 px-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
|
|
540
541
|
children: texts.sectionLabel
|
|
541
542
|
}) : null, visibleItems.length === 0 ? /* @__PURE__ */ jsx("div", {
|
|
542
|
-
className: "px-
|
|
543
|
+
className: "px-1.5 text-xs text-gray-400",
|
|
543
544
|
children: texts.emptyLabel
|
|
544
|
-
}) : /* @__PURE__ */ jsx("div", {
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
565
|
-
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
577
|
-
children: subtitle
|
|
578
|
-
})]
|
|
579
|
-
})]
|
|
580
|
-
}, key);
|
|
581
|
-
})
|
|
582
|
-
})] })
|
|
545
|
+
}) : /* @__PURE__ */ jsx("div", { children: visibleItems.map((item, index) => {
|
|
546
|
+
const { key, sectionKey, sectionLabel, title, subtitle } = item;
|
|
547
|
+
const isActive = index === activeIndexInRange;
|
|
548
|
+
const previousItem = visibleItems[index - 1];
|
|
549
|
+
return /* @__PURE__ */ jsxs("div", { children: [hasItemSections && Boolean(sectionLabel?.trim()) && previousItem?.sectionKey !== sectionKey ? /* @__PURE__ */ jsx("div", {
|
|
550
|
+
className: "px-1.5 pb-1 pt-1.5 text-[11px] font-semibold uppercase tracking-wide text-gray-500",
|
|
551
|
+
children: sectionLabel
|
|
552
|
+
}) : null, /* @__PURE__ */ jsxs("button", {
|
|
553
|
+
type: "button",
|
|
554
|
+
role: "option",
|
|
555
|
+
"aria-selected": isActive,
|
|
556
|
+
"data-input-surface-index": index,
|
|
557
|
+
onPointerMove: (event) => {
|
|
558
|
+
if (event.pointerType !== "touch") setActiveIndexForCurrentItems(index);
|
|
559
|
+
},
|
|
560
|
+
onPointerDown: (event) => {
|
|
561
|
+
if (event.button > 0) return;
|
|
562
|
+
event.preventDefault();
|
|
563
|
+
onSelectItem(item);
|
|
564
|
+
},
|
|
565
|
+
onClick: (event) => {
|
|
566
|
+
if (event.detail === 0) onSelectItem(item);
|
|
567
|
+
},
|
|
568
|
+
className: `flex w-full items-center gap-1.5 rounded-md px-1.5 py-1 text-left leading-4 transition-colors ${isActive ? "bg-gray-100 text-gray-900" : "text-gray-700 hover:bg-gray-100"}`,
|
|
569
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
570
|
+
className: "truncate text-xs font-medium",
|
|
571
|
+
children: title
|
|
572
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
573
|
+
className: "truncate text-xs text-gray-500",
|
|
574
|
+
children: subtitle
|
|
575
|
+
})]
|
|
576
|
+
})] }, key);
|
|
577
|
+
}) })] })
|
|
583
578
|
})]
|
|
584
579
|
}), /* @__PURE__ */ jsx("div", {
|
|
585
|
-
className: "custom-scrollbar min-h-0 min-w-0 overflow-y-auto overscroll-contain p-2.5",
|
|
580
|
+
className: "custom-scrollbar min-h-0 min-w-0 select-text overflow-y-auto overscroll-contain p-2.5",
|
|
586
581
|
onPointerDown: onDetailsPointerDown,
|
|
587
582
|
children: activeItem ? /* @__PURE__ */ jsxs("div", {
|
|
588
583
|
className: "space-y-3",
|
|
@@ -639,15 +634,13 @@ function resolveInputSurfaceTriggerIdentity(currentIdentity, trigger, reason) {
|
|
|
639
634
|
function ChatInputSurfaceHost(props) {
|
|
640
635
|
const { children, inputSurface, onInputSurfaceTriggerChange, onSelectItem, triggerSpecs = [CHAT_INPUT_SURFACE_SLASH_TRIGGER_SPEC] } = props;
|
|
641
636
|
const menuRef = useRef(null);
|
|
637
|
+
const isDetailsInteractionRef = useRef(false);
|
|
642
638
|
const [activeTriggerIdentity, setActiveTriggerIdentity] = useState(null);
|
|
643
639
|
const isOpen = Boolean(inputSurface) && activeTriggerIdentity !== null;
|
|
644
640
|
const setActiveTrigger = useCallback((identity, trigger) => {
|
|
645
641
|
setActiveTriggerIdentity(identity);
|
|
646
642
|
onInputSurfaceTriggerChange?.(identity ? trigger : null);
|
|
647
643
|
}, [onInputSurfaceTriggerChange]);
|
|
648
|
-
const closeInputSurface = useCallback(() => {
|
|
649
|
-
setActiveTrigger(null, null);
|
|
650
|
-
}, [setActiveTrigger]);
|
|
651
644
|
const handleInputSurfaceSnapshotChange = useCallback((nodes, selection, reason) => {
|
|
652
645
|
const trigger = resolveChatComposerActiveInputSurfaceTrigger(nodes, selection, triggerSpecs);
|
|
653
646
|
const nextIdentity = resolveInputSurfaceTriggerIdentity(activeTriggerIdentity, trigger, reason);
|
|
@@ -661,8 +654,8 @@ function ChatInputSurfaceHost(props) {
|
|
|
661
654
|
return menuRef.current?.handleKeyDown(event) ?? false;
|
|
662
655
|
}, []);
|
|
663
656
|
const handleInputSurfaceOpenChange = useCallback((open) => {
|
|
664
|
-
if (!open)
|
|
665
|
-
}, [
|
|
657
|
+
if (!open && !isDetailsInteractionRef.current) setActiveTrigger(null, null);
|
|
658
|
+
}, [setActiveTrigger]);
|
|
666
659
|
return /* @__PURE__ */ jsxs(Fragment$1, { children: [children(useMemo(() => ({
|
|
667
660
|
onInputSurfaceKeyDown: handleInputSurfaceKeyDown,
|
|
668
661
|
onInputSurfaceOpenChange: handleInputSurfaceOpenChange,
|
|
@@ -679,16 +672,21 @@ function ChatInputSurfaceHost(props) {
|
|
|
679
672
|
items: inputSurface.items,
|
|
680
673
|
texts: inputSurface.texts,
|
|
681
674
|
onSelectItem: (item) => {
|
|
682
|
-
|
|
675
|
+
setActiveTrigger(null, null);
|
|
683
676
|
onSelectItem(item);
|
|
684
677
|
},
|
|
685
678
|
onOpenChange: handleInputSurfaceOpenChange,
|
|
686
|
-
onDetailsPointerDown: (
|
|
679
|
+
onDetailsPointerDown: () => {
|
|
680
|
+
isDetailsInteractionRef.current = true;
|
|
681
|
+
requestAnimationFrame(() => {
|
|
682
|
+
isDetailsInteractionRef.current = false;
|
|
683
|
+
});
|
|
684
|
+
}
|
|
687
685
|
}, activeTriggerIdentity) : null] });
|
|
688
686
|
}
|
|
689
687
|
//#endregion
|
|
690
688
|
//#region src/components/chat/default-skin/button.tsx
|
|
691
|
-
const buttonVariants = cva("inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium
|
|
689
|
+
const buttonVariants = cva("inline-flex items-center justify-center whitespace-nowrap rounded-full text-sm font-medium transition-all duration-fast focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border disabled:pointer-events-none disabled:opacity-50", {
|
|
692
690
|
variants: {
|
|
693
691
|
variant: {
|
|
694
692
|
default: "bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm",
|
|
@@ -726,12 +724,6 @@ const ChatButton = React.forwardRef(({ className, variant, size, ...props }, ref
|
|
|
726
724
|
ChatButton.displayName = "ChatButton";
|
|
727
725
|
//#endregion
|
|
728
726
|
//#region src/components/chat/ui/chat-input-bar/chat-input-bar-actions.tsx
|
|
729
|
-
const SEND_ERROR_PREVIEW_MAX_CHARS = 120;
|
|
730
|
-
function buildSendErrorPreview(value) {
|
|
731
|
-
const compact = value.replace(/\s+/g, " ").trim();
|
|
732
|
-
if (compact.length <= SEND_ERROR_PREVIEW_MAX_CHARS) return compact;
|
|
733
|
-
return `${compact.slice(0, SEND_ERROR_PREVIEW_MAX_CHARS - 1)}…`;
|
|
734
|
-
}
|
|
735
727
|
function StopIcon() {
|
|
736
728
|
return /* @__PURE__ */ jsx("span", {
|
|
737
729
|
"aria-hidden": "true",
|
|
@@ -781,75 +773,42 @@ function ContextWindowIndicator({ contextWindow }) {
|
|
|
781
773
|
})] })
|
|
782
774
|
});
|
|
783
775
|
}
|
|
784
|
-
function ChatInputBarActions({
|
|
785
|
-
const {
|
|
786
|
-
const normalizedSendError = sendError?.trim() ?? "";
|
|
787
|
-
const sendErrorPreview = normalizedSendError ? buildSendErrorPreview(normalizedSendError) : "";
|
|
788
|
-
const resolvedSendErrorDetailsLabel = sendErrorDetailsLabel?.trim() || "Details";
|
|
776
|
+
function ChatInputBarActions({ isSending, canStopGeneration, sendDisabled, stopDisabled, stopHint, sendButtonLabel, stopButtonLabel, contextWindow, onSend, onStop }) {
|
|
777
|
+
const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
|
|
789
778
|
return /* @__PURE__ */ jsxs("div", {
|
|
790
|
-
className: "flex shrink-0
|
|
791
|
-
children: [
|
|
792
|
-
|
|
793
|
-
|
|
794
|
-
|
|
795
|
-
|
|
796
|
-
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
|
|
800
|
-
|
|
801
|
-
|
|
802
|
-
|
|
803
|
-
|
|
804
|
-
|
|
805
|
-
|
|
806
|
-
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
className: "w-[min(32rem,calc(100vw-1.5rem))] border-red-100/80 p-0",
|
|
810
|
-
children: [/* @__PURE__ */ jsx("div", {
|
|
811
|
-
className: "border-b border-red-100 bg-red-50/80 px-4 py-2 text-xs font-semibold text-red-700",
|
|
812
|
-
children: resolvedSendErrorDetailsLabel
|
|
813
|
-
}), /* @__PURE__ */ jsx("pre", {
|
|
814
|
-
className: "max-h-80 overflow-auto whitespace-pre-wrap break-words px-4 py-3 text-xs leading-relaxed text-red-700",
|
|
815
|
-
children: normalizedSendError
|
|
816
|
-
})]
|
|
817
|
-
})] })]
|
|
818
|
-
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
819
|
-
className: "flex items-center gap-2",
|
|
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
|
+
className: "flex shrink-0 items-center gap-2",
|
|
780
|
+
children: [contextWindow ? /* @__PURE__ */ jsx(ContextWindowIndicator, { contextWindow }) : null, !isSending || !sendDisabled ? /* @__PURE__ */ jsx(ChatButton, {
|
|
781
|
+
size: "icon",
|
|
782
|
+
className: "h-8 w-8 rounded-full",
|
|
783
|
+
"aria-label": sendButtonLabel,
|
|
784
|
+
onClick: () => void onSend(),
|
|
785
|
+
disabled: sendDisabled,
|
|
786
|
+
children: /* @__PURE__ */ jsx(ArrowUp, { className: "h-5 w-5" })
|
|
787
|
+
}) : canStopGeneration ? /* @__PURE__ */ jsx(ChatButton, {
|
|
788
|
+
size: "icon",
|
|
789
|
+
variant: "outline",
|
|
790
|
+
className: "h-8 w-8 rounded-full",
|
|
791
|
+
"aria-label": stopButtonLabel,
|
|
792
|
+
onClick: () => void onStop(),
|
|
793
|
+
disabled: stopDisabled,
|
|
794
|
+
children: /* @__PURE__ */ jsx(StopIcon, {})
|
|
795
|
+
}) : /* @__PURE__ */ jsx(TooltipProvider, { children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
796
|
+
asChild: true,
|
|
797
|
+
children: /* @__PURE__ */ jsx("span", { children: /* @__PURE__ */ jsx(ChatButton, {
|
|
828
798
|
size: "icon",
|
|
829
799
|
variant: "outline",
|
|
830
800
|
className: "h-8 w-8 rounded-full",
|
|
831
801
|
"aria-label": stopButtonLabel,
|
|
832
|
-
|
|
833
|
-
disabled: stopDisabled,
|
|
802
|
+
disabled: true,
|
|
834
803
|
children: /* @__PURE__ */ jsx(StopIcon, {})
|
|
835
|
-
})
|
|
836
|
-
|
|
837
|
-
|
|
838
|
-
|
|
839
|
-
|
|
840
|
-
|
|
841
|
-
|
|
842
|
-
|
|
843
|
-
children: /* @__PURE__ */ jsx(StopIcon, {})
|
|
844
|
-
}) })
|
|
845
|
-
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
846
|
-
side: "top",
|
|
847
|
-
children: /* @__PURE__ */ jsx("p", {
|
|
848
|
-
className: "text-xs",
|
|
849
|
-
children: stopHint
|
|
850
|
-
})
|
|
851
|
-
})] }) })]
|
|
852
|
-
})]
|
|
804
|
+
}) })
|
|
805
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
806
|
+
side: "top",
|
|
807
|
+
children: /* @__PURE__ */ jsx("p", {
|
|
808
|
+
className: "text-xs",
|
|
809
|
+
children: stopHint
|
|
810
|
+
})
|
|
811
|
+
})] }) })]
|
|
853
812
|
});
|
|
854
813
|
}
|
|
855
814
|
//#endregion
|
|
@@ -1159,16 +1118,16 @@ function ToolbarSearchableSelect({ item }) {
|
|
|
1159
1118
|
children: [/* @__PURE__ */ jsx(ToolbarSelectTriggerContent, { item }), /* @__PURE__ */ jsx(ChevronDown, { className: "ml-1 h-3.5 w-3.5 shrink-0 text-muted-foreground/70 [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden" })]
|
|
1160
1119
|
})
|
|
1161
1120
|
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
1162
|
-
className: `flex flex-col overflow-hidden p-
|
|
1121
|
+
className: `flex flex-col overflow-hidden p-1.5 ${CONTENT_WIDTH_BY_KEY[item.key] ?? ""}`,
|
|
1163
1122
|
style: { maxHeight: TOOLBAR_POPOVER_MAX_HEIGHT },
|
|
1164
1123
|
children: [
|
|
1165
1124
|
/* @__PURE__ */ jsxs("div", {
|
|
1166
|
-
className: "relative mb-
|
|
1125
|
+
className: "relative mb-1.5 shrink-0",
|
|
1167
1126
|
children: [/* @__PURE__ */ jsx(Search, { className: "pointer-events-none absolute left-2.5 top-1/2 h-3.5 w-3.5 -translate-y-1/2 text-muted-foreground/70" }), /* @__PURE__ */ jsx(Input, {
|
|
1168
1127
|
value: query,
|
|
1169
1128
|
onChange: (event) => setQuery(event.currentTarget.value),
|
|
1170
1129
|
placeholder: item.search?.placeholder ?? item.placeholder,
|
|
1171
|
-
className: "h-
|
|
1130
|
+
className: "h-7 rounded-md pl-8 text-xs"
|
|
1172
1131
|
})]
|
|
1173
1132
|
}),
|
|
1174
1133
|
!hasOptions ? item.loading ? /* @__PURE__ */ jsxs("div", {
|
|
@@ -1189,9 +1148,9 @@ function ToolbarSearchableSelect({ item }) {
|
|
|
1189
1148
|
/* @__PURE__ */ jsx("div", {
|
|
1190
1149
|
className: "min-h-0 flex-1 overflow-y-auto overscroll-contain",
|
|
1191
1150
|
children: filteredGroups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", {
|
|
1192
|
-
className: groupIndex > 0 ? "border-t border-border pt-
|
|
1151
|
+
className: groupIndex > 0 ? "border-t border-border pt-0.5" : void 0,
|
|
1193
1152
|
children: [group.label ? /* @__PURE__ */ jsx("div", {
|
|
1194
|
-
className: "px-2
|
|
1153
|
+
className: "px-2 pb-0.5 pt-1 text-[11px] font-semibold uppercase tracking-[0.08em] text-muted-foreground",
|
|
1195
1154
|
children: group.label
|
|
1196
1155
|
}) : null, group.options.map((option) => {
|
|
1197
1156
|
const isSelected = item.value === option.value;
|
|
@@ -1201,7 +1160,7 @@ function ToolbarSearchableSelect({ item }) {
|
|
|
1201
1160
|
className: "group flex items-center gap-1 rounded-md hover:bg-accent",
|
|
1202
1161
|
children: [/* @__PURE__ */ jsxs("button", {
|
|
1203
1162
|
type: "button",
|
|
1204
|
-
className: "flex min-w-0 flex-1 items-center gap-
|
|
1163
|
+
className: "flex min-w-0 flex-1 items-center gap-1.5 rounded-md px-2 py-1.5 text-left leading-4",
|
|
1205
1164
|
onClick: () => {
|
|
1206
1165
|
item.onValueChange(option.value);
|
|
1207
1166
|
setOpen(false);
|
|
@@ -1268,7 +1227,7 @@ function ToolbarSelect({ item }) {
|
|
|
1268
1227
|
children: item.emptyLabel
|
|
1269
1228
|
}) : null : null, groups.map((group, groupIndex) => /* @__PURE__ */ jsxs("div", { children: [groupIndex > 0 ? /* @__PURE__ */ jsx(SelectSeparator, {}) : null, /* @__PURE__ */ jsxs(SelectGroup, { children: [group.label ? /* @__PURE__ */ jsx(SelectLabel, { children: group.label }) : null, group.options.map((option) => /* @__PURE__ */ jsx(SelectItem, {
|
|
1270
1229
|
value: option.value,
|
|
1271
|
-
className: "py-
|
|
1230
|
+
className: "py-1.5",
|
|
1272
1231
|
children: /* @__PURE__ */ jsx(ToolbarSelectOptionContent, { option })
|
|
1273
1232
|
}, option.value))] })] }, group.key))]
|
|
1274
1233
|
})]
|
|
@@ -2293,6 +2252,11 @@ const ChatInputBarTokenizedComposer = forwardRef(function ChatInputBarTokenizedC
|
|
|
2293
2252
|
ChatInputBarTokenizedComposer.displayName = "LexicalChatInputBarTokenizedComposer";
|
|
2294
2253
|
//#endregion
|
|
2295
2254
|
//#region src/components/chat/ui/chat-input-bar/chat-input-bar.tsx
|
|
2255
|
+
const SEND_ERROR_PREVIEW_MAX_CHARS = 120;
|
|
2256
|
+
function buildSendErrorPreview(value) {
|
|
2257
|
+
const compact = value.replace(/\s+/g, " ").trim();
|
|
2258
|
+
return compact.length <= SEND_ERROR_PREVIEW_MAX_CHARS ? compact : `${compact.slice(0, SEND_ERROR_PREVIEW_MAX_CHARS - 1)}…`;
|
|
2259
|
+
}
|
|
2296
2260
|
function InputBarHint({ hint }) {
|
|
2297
2261
|
if (!hint) return null;
|
|
2298
2262
|
if (hint.loading) return /* @__PURE__ */ jsx("div", {
|
|
@@ -2315,7 +2279,44 @@ function InputBarHint({ hint }) {
|
|
|
2315
2279
|
})
|
|
2316
2280
|
});
|
|
2317
2281
|
}
|
|
2318
|
-
|
|
2282
|
+
function ChatInputBarSendError({ sendError, sendErrorDetailsLabel }) {
|
|
2283
|
+
const normalizedSendError = sendError?.trim() ?? "";
|
|
2284
|
+
if (!normalizedSendError) return null;
|
|
2285
|
+
const { Popover, PopoverContent, PopoverTrigger } = ChatUiPrimitives;
|
|
2286
|
+
const sendErrorPreview = buildSendErrorPreview(normalizedSendError);
|
|
2287
|
+
const resolvedSendErrorDetailsLabel = sendErrorDetailsLabel?.trim() || "Details";
|
|
2288
|
+
return /* @__PURE__ */ jsx("div", {
|
|
2289
|
+
className: "px-3 pb-2 sm:px-4",
|
|
2290
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
2291
|
+
"aria-live": "polite",
|
|
2292
|
+
className: "flex min-w-0 items-start justify-between gap-3 rounded-lg border border-red-200/80 bg-red-50/80 px-3 py-2 text-xs text-red-700",
|
|
2293
|
+
role: "status",
|
|
2294
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
2295
|
+
className: "min-w-0 flex-1 truncate leading-5",
|
|
2296
|
+
title: normalizedSendError,
|
|
2297
|
+
children: sendErrorPreview
|
|
2298
|
+
}), /* @__PURE__ */ jsxs(Popover, { children: [/* @__PURE__ */ jsx(PopoverTrigger, {
|
|
2299
|
+
asChild: true,
|
|
2300
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
2301
|
+
type: "button",
|
|
2302
|
+
className: "inline-flex h-7 shrink-0 items-center rounded-md border border-red-200/80 bg-background px-2 text-xs font-medium text-red-700 transition-colors hover:bg-red-100",
|
|
2303
|
+
children: resolvedSendErrorDetailsLabel
|
|
2304
|
+
})
|
|
2305
|
+
}), /* @__PURE__ */ jsxs(PopoverContent, {
|
|
2306
|
+
align: "end",
|
|
2307
|
+
className: "w-[min(32rem,calc(100vw-1.5rem))] border-red-100/80 p-0",
|
|
2308
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
2309
|
+
className: "border-b border-red-100 bg-red-50/80 px-4 py-2 text-xs font-semibold text-red-700",
|
|
2310
|
+
children: resolvedSendErrorDetailsLabel
|
|
2311
|
+
}), /* @__PURE__ */ jsx("pre", {
|
|
2312
|
+
className: "max-h-80 overflow-auto whitespace-pre-wrap break-words px-4 py-3 text-xs leading-relaxed text-red-700",
|
|
2313
|
+
children: normalizedSendError
|
|
2314
|
+
})]
|
|
2315
|
+
})] })]
|
|
2316
|
+
})
|
|
2317
|
+
});
|
|
2318
|
+
}
|
|
2319
|
+
const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSurface, sendError, sendErrorDetailsLabel, slashMenu, surface, toolbar: toolbarProps, topSlot }, ref) {
|
|
2319
2320
|
const composerRef = useRef(null);
|
|
2320
2321
|
const resolvedInputSurface = inputSurface ?? (slashMenu ? {
|
|
2321
2322
|
isLoading: slashMenu.isLoading,
|
|
@@ -2383,6 +2384,10 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSur
|
|
|
2383
2384
|
})
|
|
2384
2385
|
}),
|
|
2385
2386
|
/* @__PURE__ */ jsx(InputBarHint, { hint }),
|
|
2387
|
+
/* @__PURE__ */ jsx(ChatInputBarSendError, {
|
|
2388
|
+
sendError,
|
|
2389
|
+
sendErrorDetailsLabel
|
|
2390
|
+
}),
|
|
2386
2391
|
/* @__PURE__ */ jsx(ChatInputBarToolbar, { ...toolbar })
|
|
2387
2392
|
]
|
|
2388
2393
|
})
|
|
@@ -2392,22 +2397,25 @@ const ChatInputBar = forwardRef(function ChatInputBar({ composer, hint, inputSur
|
|
|
2392
2397
|
ChatInputBar.displayName = "ChatInputBar";
|
|
2393
2398
|
//#endregion
|
|
2394
2399
|
//#region src/components/chat/ui/chat-message-list/chat-message-avatar.tsx
|
|
2395
|
-
function ChatMessageAvatar({ role }) {
|
|
2400
|
+
function ChatMessageAvatar({ role, size = "default" }) {
|
|
2401
|
+
const compact = size === "compact";
|
|
2402
|
+
const frameSize = compact ? "h-7 w-7" : "h-8 w-8";
|
|
2403
|
+
const iconSize = compact ? "h-3.5 w-3.5" : "h-4 w-4";
|
|
2396
2404
|
if (role === "user") return /* @__PURE__ */ jsx("div", {
|
|
2397
2405
|
"data-testid": "chat-message-avatar-user",
|
|
2398
|
-
className: "flex
|
|
2399
|
-
children: /* @__PURE__ */ jsx(User, { className:
|
|
2406
|
+
className: cn("flex shrink-0 items-center justify-center rounded-full bg-primary text-primary-foreground shadow-sm", frameSize),
|
|
2407
|
+
children: /* @__PURE__ */ jsx(User, { className: iconSize })
|
|
2400
2408
|
});
|
|
2401
2409
|
if (role === "tool") return /* @__PURE__ */ jsx("div", {
|
|
2402
2410
|
"data-testid": "chat-message-avatar-tool",
|
|
2403
|
-
className: "flex
|
|
2404
|
-
children: /* @__PURE__ */ jsx(Wrench, { className:
|
|
2411
|
+
className: cn("flex shrink-0 items-center justify-center rounded-full bg-accent text-accent-foreground shadow-sm ring-1 ring-border", frameSize),
|
|
2412
|
+
children: /* @__PURE__ */ jsx(Wrench, { className: iconSize })
|
|
2405
2413
|
});
|
|
2406
2414
|
return /* @__PURE__ */ jsx("div", {
|
|
2407
2415
|
"data-testid": "chat-message-avatar-assistant",
|
|
2408
|
-
className: "flex
|
|
2416
|
+
className: cn("flex shrink-0 items-center justify-center rounded-full bg-muted text-foreground shadow-sm ring-1 ring-border", frameSize),
|
|
2409
2417
|
children: /* @__PURE__ */ jsx(Bot, {
|
|
2410
|
-
className: "h-[18px] w-[18px] text-current",
|
|
2418
|
+
className: cn(compact ? "h-4 w-4" : "h-[18px] w-[18px]", "text-current"),
|
|
2411
2419
|
strokeWidth: 2.5
|
|
2412
2420
|
})
|
|
2413
2421
|
});
|
|
@@ -2419,15 +2427,15 @@ function resolveInlineTokenTone(kind) {
|
|
|
2419
2427
|
if (kind === "panel_app") return "panel_app";
|
|
2420
2428
|
return "default";
|
|
2421
2429
|
}
|
|
2422
|
-
function resolveInlineTokenBadgeClassName(tone, isUser) {
|
|
2423
|
-
if (tone === "skill") return
|
|
2424
|
-
if (tone === "panel_app") return isUser ? "border-primary-foreground/
|
|
2425
|
-
return isUser ? "border-primary-foreground/
|
|
2430
|
+
function resolveInlineTokenBadgeClassName(tone, isUser, interactive) {
|
|
2431
|
+
if (tone === "skill") return cn("border-transparent bg-transparent text-[color:var(--md-link)]", interactive && "cursor-pointer underline-offset-2 hover:underline focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-[color:var(--md-link)]/30", !interactive && "cursor-default");
|
|
2432
|
+
if (tone === "panel_app") return isUser ? "border-primary-foreground/40 bg-primary-foreground/22 text-primary-foreground" : "border-border bg-muted text-foreground";
|
|
2433
|
+
return isUser ? "border-primary-foreground/40 bg-primary-foreground/22 text-primary-foreground" : "border-border bg-muted text-muted-foreground";
|
|
2426
2434
|
}
|
|
2427
2435
|
function resolveInlineTokenIconClassName(tone, isUser) {
|
|
2428
|
-
if (tone === "skill") return
|
|
2429
|
-
if (tone === "panel_app") return isUser ? "text-primary-foreground/
|
|
2430
|
-
return isUser ? "text-primary-foreground/
|
|
2436
|
+
if (tone === "skill") return "text-current opacity-80";
|
|
2437
|
+
if (tone === "panel_app") return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2438
|
+
return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2431
2439
|
}
|
|
2432
2440
|
function renderInlineTokenIcon(tone) {
|
|
2433
2441
|
return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
|
|
@@ -2438,18 +2446,32 @@ function renderInlineTokenIcon(tone) {
|
|
|
2438
2446
|
className: "h-3 w-3"
|
|
2439
2447
|
});
|
|
2440
2448
|
}
|
|
2441
|
-
function ChatInlineTokenBadge({ kind, label, isUser }) {
|
|
2449
|
+
function ChatInlineTokenBadge({ kind, label, isUser, onClick }) {
|
|
2442
2450
|
const tone = resolveInlineTokenTone(kind);
|
|
2443
|
-
|
|
2444
|
-
|
|
2451
|
+
const interactive = Boolean(onClick);
|
|
2452
|
+
const className = cn("nextclaw-chat-inline-token mx-[2px] inline-flex max-w-full items-center gap-1 align-baseline text-[11px] font-medium", tone === "skill" ? "h-auto rounded-none px-0 py-0" : "h-7 rounded-xl border px-2.5", resolveInlineTokenBadgeClassName(tone, isUser, interactive));
|
|
2453
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2454
|
+
className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", resolveInlineTokenIconClassName(tone, isUser)),
|
|
2455
|
+
children: renderInlineTokenIcon(tone)
|
|
2456
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2457
|
+
className: "truncate",
|
|
2458
|
+
children: label
|
|
2459
|
+
})] });
|
|
2460
|
+
if (interactive) return /* @__PURE__ */ jsx("button", {
|
|
2461
|
+
type: "button",
|
|
2462
|
+
className,
|
|
2445
2463
|
title: label,
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
2464
|
+
onClick: (event) => {
|
|
2465
|
+
event.preventDefault();
|
|
2466
|
+
event.stopPropagation();
|
|
2467
|
+
onClick?.();
|
|
2468
|
+
},
|
|
2469
|
+
children: content
|
|
2470
|
+
});
|
|
2471
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2472
|
+
className,
|
|
2473
|
+
title: label,
|
|
2474
|
+
children: content
|
|
2453
2475
|
});
|
|
2454
2476
|
}
|
|
2455
2477
|
//#endregion
|
|
@@ -2655,7 +2677,7 @@ const REGISTERED_LANGUAGES = [
|
|
|
2655
2677
|
aliases: ["patch"]
|
|
2656
2678
|
}
|
|
2657
2679
|
];
|
|
2658
|
-
function escapeHtml(value) {
|
|
2680
|
+
function escapeHtml$1(value) {
|
|
2659
2681
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2660
2682
|
}
|
|
2661
2683
|
function normalizeLanguage(value) {
|
|
@@ -2686,7 +2708,7 @@ var ChatCodeSyntaxHighlighter = class {
|
|
|
2686
2708
|
return this.createPlainTextResult(code, normalizedLanguage || "text");
|
|
2687
2709
|
};
|
|
2688
2710
|
this.createPlainTextResult = (code, language) => ({
|
|
2689
|
-
html: escapeHtml(code),
|
|
2711
|
+
html: escapeHtml$1(code),
|
|
2690
2712
|
language,
|
|
2691
2713
|
highlighted: false
|
|
2692
2714
|
});
|
|
@@ -2914,26 +2936,126 @@ function ChatInlineDisplay({ display, renderInlineDisplay }) {
|
|
|
2914
2936
|
return /* @__PURE__ */ jsx(ChatInlineDisplayFallback, { display });
|
|
2915
2937
|
}
|
|
2916
2938
|
//#endregion
|
|
2917
|
-
//#region src/components/chat/ui/chat-message-list/chat-message-
|
|
2918
|
-
|
|
2939
|
+
//#region src/components/chat/ui/chat-message-list/chat-message-file/chat-message-image-preview.tsx
|
|
2940
|
+
function ChatMessageImageLightbox({ alt, closeLabel, onClose, src }) {
|
|
2941
|
+
const titleId = useId();
|
|
2942
|
+
useEffect(() => {
|
|
2943
|
+
const previousOverflow = document.body.style.overflow;
|
|
2944
|
+
document.body.style.overflow = "hidden";
|
|
2945
|
+
const onKeyDown = (event) => {
|
|
2946
|
+
if (event.key === "Escape") {
|
|
2947
|
+
event.preventDefault();
|
|
2948
|
+
onClose();
|
|
2949
|
+
}
|
|
2950
|
+
};
|
|
2951
|
+
window.addEventListener("keydown", onKeyDown);
|
|
2952
|
+
return () => {
|
|
2953
|
+
document.body.style.overflow = previousOverflow;
|
|
2954
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
2955
|
+
};
|
|
2956
|
+
}, [onClose]);
|
|
2957
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
2958
|
+
role: "dialog",
|
|
2959
|
+
"aria-modal": "true",
|
|
2960
|
+
"aria-labelledby": titleId,
|
|
2961
|
+
className: "fixed inset-0 z-[var(--z-modal,10050)] flex items-center justify-center bg-black/80 p-4 backdrop-blur-[2px]",
|
|
2962
|
+
"data-testid": "chat-message-image-lightbox",
|
|
2963
|
+
onClick: onClose,
|
|
2964
|
+
children: [
|
|
2965
|
+
/* @__PURE__ */ jsx("span", {
|
|
2966
|
+
id: titleId,
|
|
2967
|
+
className: "sr-only",
|
|
2968
|
+
children: alt
|
|
2969
|
+
}),
|
|
2970
|
+
/* @__PURE__ */ jsx("button", {
|
|
2971
|
+
type: "button",
|
|
2972
|
+
"aria-label": closeLabel,
|
|
2973
|
+
className: "absolute right-4 top-4 inline-flex h-9 w-9 items-center justify-center rounded-full bg-black/55 text-white transition-colors hover:bg-black/70 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-white/70",
|
|
2974
|
+
onClick: (event) => {
|
|
2975
|
+
event.stopPropagation();
|
|
2976
|
+
onClose();
|
|
2977
|
+
},
|
|
2978
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
2979
|
+
className: "h-4 w-4",
|
|
2980
|
+
strokeWidth: 2
|
|
2981
|
+
})
|
|
2982
|
+
}),
|
|
2983
|
+
/* @__PURE__ */ jsx("img", {
|
|
2984
|
+
src,
|
|
2985
|
+
alt,
|
|
2986
|
+
className: "max-h-[min(92vh,100%)] max-w-[min(96vw,100%)] object-contain shadow-2xl",
|
|
2987
|
+
onClick: (event) => event.stopPropagation()
|
|
2988
|
+
})
|
|
2989
|
+
]
|
|
2990
|
+
}), document.body);
|
|
2991
|
+
}
|
|
2992
|
+
function ChatMessageImagePreview({ alt, expandLabel, closeLabel, sizeLabel, src }) {
|
|
2993
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
2994
|
+
const openLightbox = () => setIsExpanded(true);
|
|
2995
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("span", {
|
|
2996
|
+
"data-chat-message-image-preview": true,
|
|
2997
|
+
className: "group/image relative block w-fit max-w-[min(100%,32rem)] overflow-hidden rounded-lg",
|
|
2998
|
+
children: [
|
|
2999
|
+
/* @__PURE__ */ jsx("button", {
|
|
3000
|
+
type: "button",
|
|
3001
|
+
className: "block w-fit max-w-[min(100%,32rem)] text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
3002
|
+
onClick: openLightbox,
|
|
3003
|
+
onDoubleClick: (event) => {
|
|
3004
|
+
event.preventDefault();
|
|
3005
|
+
openLightbox();
|
|
3006
|
+
},
|
|
3007
|
+
"aria-label": expandLabel,
|
|
3008
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
3009
|
+
src,
|
|
3010
|
+
alt,
|
|
3011
|
+
className: "block h-auto w-auto max-h-[26rem] max-w-full rounded-lg bg-transparent object-contain"
|
|
3012
|
+
})
|
|
3013
|
+
}),
|
|
3014
|
+
/* @__PURE__ */ jsx("button", {
|
|
3015
|
+
type: "button",
|
|
3016
|
+
"aria-label": expandLabel,
|
|
3017
|
+
className: "absolute right-2 top-2 inline-flex h-7 w-7 items-center justify-center rounded-md bg-black/45 text-white opacity-0 transition-opacity duration-150 hover:bg-black/60 focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-white/70 group-hover/image:opacity-100",
|
|
3018
|
+
onClick: (event) => {
|
|
3019
|
+
event.preventDefault();
|
|
3020
|
+
event.stopPropagation();
|
|
3021
|
+
openLightbox();
|
|
3022
|
+
},
|
|
3023
|
+
children: /* @__PURE__ */ jsx(Expand, {
|
|
3024
|
+
className: "h-3.5 w-3.5",
|
|
3025
|
+
strokeWidth: 2
|
|
3026
|
+
})
|
|
3027
|
+
}),
|
|
3028
|
+
sizeLabel ? /* @__PURE__ */ jsx("span", {
|
|
3029
|
+
className: "pointer-events-none absolute inset-x-0 bottom-0 flex items-end justify-start bg-gradient-to-t from-black/50 via-black/15 to-transparent px-2.5 pb-2 pt-8 opacity-0 transition-opacity duration-150 group-hover/image:opacity-100 group-focus-within/image:opacity-100",
|
|
3030
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3031
|
+
className: "inline-flex items-center rounded-md bg-black/40 px-1.5 py-0.5 text-[10px] font-medium text-white/95 backdrop-blur-sm",
|
|
3032
|
+
children: sizeLabel
|
|
3033
|
+
})
|
|
3034
|
+
}) : null
|
|
3035
|
+
]
|
|
3036
|
+
}), isExpanded ? /* @__PURE__ */ jsx(ChatMessageImageLightbox, {
|
|
3037
|
+
alt,
|
|
3038
|
+
closeLabel,
|
|
3039
|
+
onClose: () => setIsExpanded(false),
|
|
3040
|
+
src
|
|
3041
|
+
}) : null] });
|
|
3042
|
+
}
|
|
3043
|
+
//#endregion
|
|
3044
|
+
//#region src/components/chat/ui/chat-message-list/utils/chat-local-resource.utils.ts
|
|
2919
3045
|
const SAFE_LINK_PROTOCOLS = new Set([
|
|
2920
3046
|
"http:",
|
|
2921
3047
|
"https:",
|
|
2922
3048
|
"mailto:",
|
|
2923
3049
|
"tel:"
|
|
2924
3050
|
]);
|
|
2925
|
-
const
|
|
2926
|
-
const
|
|
2927
|
-
|
|
2928
|
-
|
|
2929
|
-
const PROJECT_RELATIVE_FILE_HREF_PATTERN = new RegExp(`^(?![a-zA-Z][a-zA-Z\\d+.-]*:)(?!//)(?:(?:[^/\\s?#]+/)+[^?#\\s]+\\.[A-Za-z0-9_-]+|[^/\\s?#]+\\.(?:cjs|css|cts|html?|js|json|jsx|mdx?|mjs|mts|tsx?|txt|ya?ml))(?::\\d+(?::\\d+)?)?(?:[?#].*)?$`, "i");
|
|
2930
|
-
function trimMarkdown(value) {
|
|
2931
|
-
if (value.length <= MARKDOWN_MAX_CHARS) return value;
|
|
2932
|
-
return `${value.slice(0, MARKDOWN_MAX_CHARS)}\n\n...`;
|
|
3051
|
+
const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
|
|
3052
|
+
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[a-z]:[\\/]/i;
|
|
3053
|
+
function isSchemeLessResourceHref(href) {
|
|
3054
|
+
return !href.startsWith("//") && !URI_SCHEME_PATTERN.test(href);
|
|
2933
3055
|
}
|
|
2934
|
-
function
|
|
3056
|
+
function resolveSafeChatResourceHref(href) {
|
|
2935
3057
|
if (!href) return null;
|
|
2936
|
-
if (
|
|
3058
|
+
if (WINDOWS_ABSOLUTE_PATH_PATTERN.test(href) || isSchemeLessResourceHref(href)) return href;
|
|
2937
3059
|
try {
|
|
2938
3060
|
const url = new URL(href);
|
|
2939
3061
|
return SAFE_LINK_PROTOCOLS.has(url.protocol) ? href : null;
|
|
@@ -2941,30 +3063,57 @@ function resolveSafeHref(href) {
|
|
|
2941
3063
|
return null;
|
|
2942
3064
|
}
|
|
2943
3065
|
}
|
|
2944
|
-
function
|
|
3066
|
+
function isExternalChatResourceHref(href) {
|
|
2945
3067
|
return /^https?:\/\//i.test(href);
|
|
2946
3068
|
}
|
|
2947
|
-
function
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
|
|
2956
|
-
|
|
3069
|
+
function parseChatLocalFileAction(href) {
|
|
3070
|
+
const normalizedHref = href.split("#")[0] ?? href;
|
|
3071
|
+
const [encodedPath, encodedQuery = ""] = normalizedHref.split("?", 2);
|
|
3072
|
+
let path;
|
|
3073
|
+
try {
|
|
3074
|
+
path = decodeURIComponent(encodedPath ?? normalizedHref);
|
|
3075
|
+
} catch {
|
|
3076
|
+
return null;
|
|
3077
|
+
}
|
|
3078
|
+
if (!path || path.startsWith("#") || path.startsWith("//") || !WINDOWS_ABSOLUTE_PATH_PATTERN.test(path) && URI_SCHEME_PATTERN.test(path)) return null;
|
|
3079
|
+
const viewer = new URLSearchParams(encodedQuery).get("viewer");
|
|
3080
|
+
const lineMatch = /^(.*?)(?::(\d+)(?::(\d+))?)$/.exec(path);
|
|
3081
|
+
const rawPath = lineMatch?.[1] ?? path;
|
|
2957
3082
|
const line = lineMatch?.[2] ? Number(lineMatch[2]) : void 0;
|
|
2958
3083
|
const column = lineMatch?.[3] ? Number(lineMatch[3]) : void 0;
|
|
2959
3084
|
return {
|
|
2960
3085
|
path: rawPath,
|
|
2961
|
-
label: rawPath.split(
|
|
3086
|
+
label: rawPath.split(/[\\/]/).filter(Boolean).pop() ?? rawPath,
|
|
2962
3087
|
viewMode: "preview",
|
|
2963
3088
|
...viewer === "source" || viewer === "rendered" ? { previewViewer: viewer } : {},
|
|
2964
3089
|
...typeof line === "number" ? { line } : {},
|
|
2965
3090
|
...typeof column === "number" ? { column } : {}
|
|
2966
3091
|
};
|
|
2967
3092
|
}
|
|
3093
|
+
//#endregion
|
|
3094
|
+
//#region src/components/chat/ui/chat-message-list/chat-message-markdown.tsx
|
|
3095
|
+
const MARKDOWN_MAX_CHARS = 14e4;
|
|
3096
|
+
const INLINE_TOKEN_KIND_ATTR = "data-chat-inline-token-kind";
|
|
3097
|
+
const INLINE_TOKEN_KEY_ATTR = "data-chat-inline-token-key";
|
|
3098
|
+
const INLINE_TOKEN_LABEL_ATTR = "data-chat-inline-token-label";
|
|
3099
|
+
const INLINE_TOKEN_RAW_TEXT_ATTR = "data-chat-inline-token-raw-text";
|
|
3100
|
+
function trimMarkdown(value) {
|
|
3101
|
+
if (value.length <= MARKDOWN_MAX_CHARS) return value;
|
|
3102
|
+
return `${value.slice(0, MARKDOWN_MAX_CHARS)}\n\n...`;
|
|
3103
|
+
}
|
|
3104
|
+
function isSingleLineImageParagraph(node) {
|
|
3105
|
+
let imageCount = 0;
|
|
3106
|
+
for (const child of node?.children ?? []) {
|
|
3107
|
+
if (child.type === "text") {
|
|
3108
|
+
const text = child.value ?? "";
|
|
3109
|
+
if (text.trim().length > 0 || text.includes("\n") || text.includes("\r")) return false;
|
|
3110
|
+
continue;
|
|
3111
|
+
}
|
|
3112
|
+
if (child.tagName !== "img") return false;
|
|
3113
|
+
imageCount += 1;
|
|
3114
|
+
}
|
|
3115
|
+
return imageCount >= 3;
|
|
3116
|
+
}
|
|
2968
3117
|
function prepareInlineTokens(inlineTokens) {
|
|
2969
3118
|
if (!inlineTokens || inlineTokens.length === 0) return [];
|
|
2970
3119
|
const seenRawTexts = /* @__PURE__ */ new Set();
|
|
@@ -3054,11 +3203,14 @@ function readStringProp(props, key) {
|
|
|
3054
3203
|
const value = props[key];
|
|
3055
3204
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3056
3205
|
}
|
|
3057
|
-
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, renderInlineDisplay }) {
|
|
3206
|
+
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, onInlineTokenClick, resolveFileContentUrl, renderInlineDisplay }) {
|
|
3058
3207
|
const isUser = role === "user";
|
|
3059
3208
|
const remarkPlugins = useMemo(() => inlineTokens && inlineTokens.length > 0 ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm], [inlineTokens]);
|
|
3060
3209
|
const markdownComponents = useMemo(() => ({
|
|
3061
|
-
p: ({ children }) => inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", {
|
|
3210
|
+
p: ({ node, children }) => inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", {
|
|
3211
|
+
"data-chat-image-row": isSingleLineImageParagraph(node) ? "three-column" : void 0,
|
|
3212
|
+
children
|
|
3213
|
+
}),
|
|
3062
3214
|
span: ({ node: _node, children, ...rest }) => {
|
|
3063
3215
|
const restProps = rest;
|
|
3064
3216
|
const kind = readStringProp(restProps, INLINE_TOKEN_KIND_ATTR);
|
|
@@ -3068,7 +3220,13 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3068
3220
|
if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
|
|
3069
3221
|
kind,
|
|
3070
3222
|
label,
|
|
3071
|
-
isUser
|
|
3223
|
+
isUser,
|
|
3224
|
+
onClick: onInlineTokenClick ? () => onInlineTokenClick({
|
|
3225
|
+
kind,
|
|
3226
|
+
key,
|
|
3227
|
+
label,
|
|
3228
|
+
rawText
|
|
3229
|
+
}) : void 0
|
|
3072
3230
|
});
|
|
3073
3231
|
return /* @__PURE__ */ jsx("span", {
|
|
3074
3232
|
...rest,
|
|
@@ -3076,14 +3234,14 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3076
3234
|
});
|
|
3077
3235
|
},
|
|
3078
3236
|
a: ({ node: _node, href, children, ...rest }) => {
|
|
3079
|
-
const safeHref =
|
|
3080
|
-
|
|
3081
|
-
|
|
3082
|
-
children
|
|
3083
|
-
});
|
|
3084
|
-
const external = isExternalHref(safeHref);
|
|
3085
|
-
const localFileAction = external ? null : parseLocalFileAction(safeHref);
|
|
3237
|
+
const safeHref = resolveSafeChatResourceHref(href);
|
|
3238
|
+
const external = safeHref ? isExternalChatResourceHref(safeHref) : false;
|
|
3239
|
+
const localFileAction = external ? null : safeHref ? parseChatLocalFileAction(safeHref) : null;
|
|
3086
3240
|
const handleClick = (event) => {
|
|
3241
|
+
if (!safeHref) {
|
|
3242
|
+
event.preventDefault();
|
|
3243
|
+
return;
|
|
3244
|
+
}
|
|
3087
3245
|
if (!onFileOpen || !localFileAction) return;
|
|
3088
3246
|
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3089
3247
|
event.preventDefault();
|
|
@@ -3091,7 +3249,9 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3091
3249
|
};
|
|
3092
3250
|
return /* @__PURE__ */ jsx("a", {
|
|
3093
3251
|
...rest,
|
|
3094
|
-
|
|
3252
|
+
className: cn(rest.className, !safeHref && "chat-link-invalid"),
|
|
3253
|
+
href: safeHref ?? "#",
|
|
3254
|
+
"aria-disabled": !safeHref || void 0,
|
|
3095
3255
|
onClick: handleClick,
|
|
3096
3256
|
target: external ? "_blank" : void 0,
|
|
3097
3257
|
rel: external ? "noreferrer noopener" : void 0,
|
|
@@ -3119,15 +3279,18 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3119
3279
|
className: "chat-task-checkbox"
|
|
3120
3280
|
});
|
|
3121
3281
|
},
|
|
3122
|
-
img: ({ node: _node, src, alt
|
|
3123
|
-
const safeSrc =
|
|
3282
|
+
img: ({ node: _node, src, alt }) => {
|
|
3283
|
+
const safeSrc = resolveSafeChatResourceHref(src);
|
|
3124
3284
|
if (!safeSrc) return null;
|
|
3125
|
-
|
|
3126
|
-
|
|
3127
|
-
|
|
3285
|
+
const localFileAction = parseChatLocalFileAction(safeSrc);
|
|
3286
|
+
const resolvedSrc = localFileAction && resolveFileContentUrl ? resolveFileContentUrl(localFileAction) : safeSrc;
|
|
3287
|
+
if (!resolvedSrc) return null;
|
|
3288
|
+
return /* @__PURE__ */ jsx(ChatMessageImagePreview, {
|
|
3128
3289
|
alt: alt || "",
|
|
3129
|
-
|
|
3130
|
-
|
|
3290
|
+
closeLabel: texts.attachmentCloseLabel ?? "Close preview",
|
|
3291
|
+
expandLabel: texts.attachmentExpandLabel ?? "Expand image",
|
|
3292
|
+
sizeLabel: null,
|
|
3293
|
+
src: resolvedSrc
|
|
3131
3294
|
});
|
|
3132
3295
|
},
|
|
3133
3296
|
code: ({ node: _node, className, children, ...rest }) => {
|
|
@@ -3152,7 +3315,9 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3152
3315
|
inline,
|
|
3153
3316
|
isUser,
|
|
3154
3317
|
onFileOpen,
|
|
3318
|
+
onInlineTokenClick,
|
|
3155
3319
|
renderInlineDisplay,
|
|
3320
|
+
resolveFileContentUrl,
|
|
3156
3321
|
texts
|
|
3157
3322
|
]);
|
|
3158
3323
|
return /* @__PURE__ */ jsx(inline ? "span" : "div", {
|
|
@@ -3168,16 +3333,16 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3168
3333
|
//#endregion
|
|
3169
3334
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
|
|
3170
3335
|
const FILE_CATEGORY_TILE_CLASSES = {
|
|
3171
|
-
archive: "
|
|
3172
|
-
audio: "
|
|
3173
|
-
code: "
|
|
3174
|
-
data: "
|
|
3175
|
-
document: "
|
|
3176
|
-
generic: "
|
|
3177
|
-
image: "
|
|
3178
|
-
pdf: "
|
|
3179
|
-
sheet: "
|
|
3180
|
-
video: "
|
|
3336
|
+
archive: "bg-muted text-muted-foreground",
|
|
3337
|
+
audio: "bg-muted text-muted-foreground",
|
|
3338
|
+
code: "bg-muted text-muted-foreground",
|
|
3339
|
+
data: "bg-muted text-muted-foreground",
|
|
3340
|
+
document: "bg-muted text-muted-foreground",
|
|
3341
|
+
generic: "bg-muted text-muted-foreground",
|
|
3342
|
+
image: "bg-muted text-muted-foreground",
|
|
3343
|
+
pdf: "bg-muted text-muted-foreground",
|
|
3344
|
+
sheet: "bg-muted text-muted-foreground",
|
|
3345
|
+
video: "bg-muted text-muted-foreground"
|
|
3181
3346
|
};
|
|
3182
3347
|
const CODE_EXTENSIONS = new Set([
|
|
3183
3348
|
"c",
|
|
@@ -3387,77 +3552,63 @@ function readFileCategoryLabel(category, texts) {
|
|
|
3387
3552
|
}
|
|
3388
3553
|
function renderMetaLine(categoryLabel, sizeLabel, isUser) {
|
|
3389
3554
|
return /* @__PURE__ */ jsx("div", {
|
|
3390
|
-
className: cn("mt-
|
|
3555
|
+
className: cn("mt-0.5 text-[11px] leading-4", isUser ? "text-foreground/55" : "text-muted-foreground"),
|
|
3391
3556
|
children: sizeLabel ? `${categoryLabel} · ${sizeLabel}` : categoryLabel
|
|
3392
3557
|
});
|
|
3393
3558
|
}
|
|
3394
3559
|
function renderActionPill(label, isUser, isInteractive) {
|
|
3395
3560
|
return /* @__PURE__ */ jsx("span", {
|
|
3396
|
-
className: cn("inline-flex items-center rounded-
|
|
3561
|
+
className: cn("inline-flex items-center rounded-md px-2 py-0.5 text-[11px] font-medium", isInteractive ? isUser ? "bg-black/5 text-foreground/75" : "bg-muted text-muted-foreground" : isUser ? "bg-black/[0.03] text-foreground/45" : "bg-muted/70 text-muted-foreground/70"),
|
|
3397
3562
|
children: label
|
|
3398
3563
|
});
|
|
3399
3564
|
}
|
|
3400
|
-
function
|
|
3565
|
+
function renderActionControl(params) {
|
|
3566
|
+
const { label, href, isUser, onOpen } = params;
|
|
3567
|
+
const className = cn("inline-flex items-center rounded-md px-2 py-0.5 text-[11px] font-medium transition-colors", isUser ? "bg-black/5 text-foreground/75 hover:bg-black/10 hover:text-foreground" : "bg-muted text-muted-foreground hover:bg-accent hover:text-foreground");
|
|
3568
|
+
if (onOpen) return /* @__PURE__ */ jsx("button", {
|
|
3569
|
+
type: "button",
|
|
3570
|
+
className,
|
|
3571
|
+
onClick: (event) => {
|
|
3572
|
+
event.preventDefault();
|
|
3573
|
+
event.stopPropagation();
|
|
3574
|
+
onOpen();
|
|
3575
|
+
},
|
|
3576
|
+
children: label
|
|
3577
|
+
});
|
|
3578
|
+
if (!href) return renderActionPill(label, isUser, false);
|
|
3401
3579
|
return /* @__PURE__ */ jsx("a", {
|
|
3402
3580
|
href,
|
|
3403
3581
|
target: "_blank",
|
|
3404
3582
|
rel: "noreferrer",
|
|
3405
|
-
className
|
|
3583
|
+
className,
|
|
3406
3584
|
children: label
|
|
3407
3585
|
});
|
|
3408
3586
|
}
|
|
3409
3587
|
function FileCategoryGlyph({ category, isUser }) {
|
|
3410
3588
|
const Icon = FILE_CATEGORY_ICONS[category];
|
|
3411
3589
|
return /* @__PURE__ */ jsx("div", {
|
|
3412
|
-
className: cn("flex h-
|
|
3590
|
+
className: cn("flex h-9 w-9 shrink-0 items-center justify-center rounded-lg", isUser ? "bg-black/5 text-foreground/70" : FILE_CATEGORY_TILE_CLASSES[category]),
|
|
3413
3591
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3414
|
-
className: cn("h-
|
|
3415
|
-
strokeWidth: 2
|
|
3416
|
-
})
|
|
3417
|
-
});
|
|
3418
|
-
}
|
|
3419
|
-
function renderImagePreview(params) {
|
|
3420
|
-
const { file, categoryLabel, sizeLabel, isUser } = params;
|
|
3421
|
-
if (!file.dataUrl) return null;
|
|
3422
|
-
return /* @__PURE__ */ jsx("a", {
|
|
3423
|
-
href: file.dataUrl,
|
|
3424
|
-
target: "_blank",
|
|
3425
|
-
rel: "noreferrer",
|
|
3426
|
-
className: "group block",
|
|
3427
|
-
children: /* @__PURE__ */ jsxs("div", {
|
|
3428
|
-
className: cn("relative overflow-hidden rounded-[1rem]", isUser ? "ring-1 ring-primary-foreground/10" : "bg-muted ring-1 ring-border"),
|
|
3429
|
-
children: [/* @__PURE__ */ jsx("img", {
|
|
3430
|
-
src: file.dataUrl,
|
|
3431
|
-
alt: file.label,
|
|
3432
|
-
className: "block h-auto max-h-[26rem] w-full rounded-[1rem] bg-transparent object-contain transition duration-300 group-hover:scale-[1.01]"
|
|
3433
|
-
}), /* @__PURE__ */ jsxs("div", {
|
|
3434
|
-
className: "pointer-events-none absolute right-3 top-3 flex items-center gap-2",
|
|
3435
|
-
children: [/* @__PURE__ */ jsx("span", {
|
|
3436
|
-
className: cn("inline-flex items-center rounded-full px-2 py-1 text-[10px] font-semibold tracking-[0.18em] text-white backdrop-blur-sm", isUser ? "bg-black/36" : "bg-black/58"),
|
|
3437
|
-
children: categoryLabel
|
|
3438
|
-
}), sizeLabel ? /* @__PURE__ */ jsx("span", {
|
|
3439
|
-
className: cn("inline-flex items-center rounded-full px-2 py-1 text-[10px] font-medium text-white/92 backdrop-blur-sm", isUser ? "bg-black/28" : "bg-black/46"),
|
|
3440
|
-
children: sizeLabel
|
|
3441
|
-
}) : null]
|
|
3442
|
-
})]
|
|
3592
|
+
className: cn("h-4 w-4", isUser ? "text-foreground/70" : "text-current"),
|
|
3593
|
+
strokeWidth: 2
|
|
3443
3594
|
})
|
|
3444
3595
|
});
|
|
3445
3596
|
}
|
|
3446
3597
|
function renderFileCardHeader(params) {
|
|
3447
3598
|
const { category, file, categoryLabel, sizeLabel, isUser, action } = params;
|
|
3448
3599
|
return /* @__PURE__ */ jsxs("div", {
|
|
3449
|
-
className: "flex items-center gap-
|
|
3600
|
+
className: "flex items-center gap-2.5 px-2.5 py-2",
|
|
3450
3601
|
children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
|
|
3451
3602
|
category,
|
|
3452
3603
|
isUser
|
|
3453
3604
|
}), /* @__PURE__ */ jsx("div", {
|
|
3454
3605
|
className: "min-w-0 flex-1",
|
|
3455
3606
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3456
|
-
className: "flex items-center gap-
|
|
3607
|
+
className: "flex items-center gap-2",
|
|
3457
3608
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3458
3609
|
className: "min-w-0 flex-1",
|
|
3459
3610
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3460
|
-
className: "truncate text-[
|
|
3611
|
+
className: "truncate text-[13px] font-medium leading-5",
|
|
3461
3612
|
children: file.label
|
|
3462
3613
|
}), renderMetaLine(categoryLabel, sizeLabel, isUser)]
|
|
3463
3614
|
}), action]
|
|
@@ -3466,7 +3617,7 @@ function renderFileCardHeader(params) {
|
|
|
3466
3617
|
});
|
|
3467
3618
|
}
|
|
3468
3619
|
function renderInlineMediaCard(params) {
|
|
3469
|
-
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel } = params;
|
|
3620
|
+
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel, onOpen } = params;
|
|
3470
3621
|
if (!file.dataUrl || category !== "audio" && category !== "video") return null;
|
|
3471
3622
|
const mediaMimeType = resolveRenderableMimeType(file);
|
|
3472
3623
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -3477,9 +3628,14 @@ function renderInlineMediaCard(params) {
|
|
|
3477
3628
|
categoryLabel,
|
|
3478
3629
|
sizeLabel,
|
|
3479
3630
|
isUser,
|
|
3480
|
-
action:
|
|
3631
|
+
action: renderActionControl({
|
|
3632
|
+
label: actionLabel,
|
|
3633
|
+
href: file.dataUrl,
|
|
3634
|
+
isUser,
|
|
3635
|
+
onOpen: onOpen ? () => onOpen(file) : void 0
|
|
3636
|
+
})
|
|
3481
3637
|
}), /* @__PURE__ */ jsx("div", {
|
|
3482
|
-
className: "px-
|
|
3638
|
+
className: "px-2.5 pb-2.5",
|
|
3483
3639
|
children: category === "audio" ? /* @__PURE__ */ jsx("audio", {
|
|
3484
3640
|
controls: true,
|
|
3485
3641
|
preload: "metadata",
|
|
@@ -3490,7 +3646,7 @@ function renderInlineMediaCard(params) {
|
|
|
3490
3646
|
...mediaMimeType ? { type: mediaMimeType } : {}
|
|
3491
3647
|
})
|
|
3492
3648
|
}) : /* @__PURE__ */ jsx("div", {
|
|
3493
|
-
className: cn("overflow-hidden rounded-
|
|
3649
|
+
className: cn("overflow-hidden rounded-lg", isUser ? "bg-black/10" : "bg-muted ring-1 ring-border/70"),
|
|
3494
3650
|
children: /* @__PURE__ */ jsx("video", {
|
|
3495
3651
|
controls: true,
|
|
3496
3652
|
preload: "metadata",
|
|
@@ -3506,20 +3662,23 @@ function renderInlineMediaCard(params) {
|
|
|
3506
3662
|
})]
|
|
3507
3663
|
});
|
|
3508
3664
|
}
|
|
3509
|
-
function ChatMessageFile({ file, isUser = false, texts }) {
|
|
3665
|
+
function ChatMessageFile({ file, isUser = false, texts, onOpen }) {
|
|
3510
3666
|
const { category, sizeLabel } = buildChatMessageFileMeta(file);
|
|
3511
3667
|
const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
|
|
3512
3668
|
const renderAsAudio = category === "audio" && Boolean(file.dataUrl);
|
|
3513
3669
|
const renderAsVideo = category === "video" && Boolean(file.dataUrl);
|
|
3514
3670
|
const isInteractive = Boolean(file.dataUrl);
|
|
3515
3671
|
const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
|
|
3672
|
+
const expandLabel = texts?.attachmentExpandLabel ?? "Expand image";
|
|
3673
|
+
const closeLabel = texts?.attachmentCloseLabel ?? "Close preview";
|
|
3516
3674
|
const categoryLabel = readFileCategoryLabel(category, texts);
|
|
3517
|
-
const shellClasses = cn("block overflow-hidden rounded-
|
|
3518
|
-
if (renderAsImage) return
|
|
3519
|
-
file,
|
|
3520
|
-
|
|
3675
|
+
const shellClasses = cn("block overflow-hidden rounded-xl border transition-colors", isUser ? "border-black/8 bg-black/[0.03] text-foreground" : "border-border/70 bg-card text-card-foreground", isInteractive && (isUser ? "hover:bg-black/[0.05]" : "hover:bg-muted/50"));
|
|
3676
|
+
if (renderAsImage && file.dataUrl) return /* @__PURE__ */ jsx(ChatMessageImagePreview, {
|
|
3677
|
+
alt: file.label,
|
|
3678
|
+
expandLabel,
|
|
3679
|
+
closeLabel,
|
|
3521
3680
|
sizeLabel,
|
|
3522
|
-
|
|
3681
|
+
src: file.dataUrl
|
|
3523
3682
|
});
|
|
3524
3683
|
if (renderAsAudio || renderAsVideo) return renderInlineMediaCard({
|
|
3525
3684
|
category,
|
|
@@ -3528,7 +3687,8 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3528
3687
|
sizeLabel,
|
|
3529
3688
|
isUser,
|
|
3530
3689
|
shellClasses,
|
|
3531
|
-
actionLabel
|
|
3690
|
+
actionLabel,
|
|
3691
|
+
onOpen
|
|
3532
3692
|
});
|
|
3533
3693
|
const content = renderFileCardHeader({
|
|
3534
3694
|
category,
|
|
@@ -3542,6 +3702,12 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3542
3702
|
className: shellClasses,
|
|
3543
3703
|
children: content
|
|
3544
3704
|
});
|
|
3705
|
+
if (onOpen) return /* @__PURE__ */ jsx("button", {
|
|
3706
|
+
type: "button",
|
|
3707
|
+
className: cn(shellClasses, "group w-full text-left"),
|
|
3708
|
+
onClick: () => onOpen(file),
|
|
3709
|
+
children: content
|
|
3710
|
+
});
|
|
3545
3711
|
return /* @__PURE__ */ jsx("a", {
|
|
3546
3712
|
href: file.dataUrl,
|
|
3547
3713
|
target: "_blank",
|
|
@@ -3551,6 +3717,54 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3551
3717
|
});
|
|
3552
3718
|
}
|
|
3553
3719
|
//#endregion
|
|
3720
|
+
//#region src/components/chat/ui/chat-message-list/chat-message-file/chat-message-image-row.tsx
|
|
3721
|
+
function ChatMessageImageRow({ group, indexOffset, isUser, texts, onOpen }) {
|
|
3722
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3723
|
+
"data-chat-message-image-row": "three-column",
|
|
3724
|
+
"data-chat-message-wide-content": "true",
|
|
3725
|
+
className: "grid w-full grid-cols-3 items-start gap-3",
|
|
3726
|
+
children: group.items.map(({ index, part }) => /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
3727
|
+
file: part.file,
|
|
3728
|
+
isUser,
|
|
3729
|
+
texts,
|
|
3730
|
+
onOpen
|
|
3731
|
+
}, `file-${indexOffset + index}`))
|
|
3732
|
+
});
|
|
3733
|
+
}
|
|
3734
|
+
//#endregion
|
|
3735
|
+
//#region src/components/chat/ui/chat-message-list/chat-message-file/chat-message-image-group.utils.ts
|
|
3736
|
+
function isPreviewableImageBlock(block) {
|
|
3737
|
+
return Boolean(block?.kind === "part" && block.part.type === "file" && block.part.file.isImage && block.part.file.dataUrl);
|
|
3738
|
+
}
|
|
3739
|
+
function groupConsecutiveImageFileBlocks(blocks) {
|
|
3740
|
+
const grouped = [];
|
|
3741
|
+
let index = 0;
|
|
3742
|
+
while (index < blocks.length) {
|
|
3743
|
+
const block = blocks[index];
|
|
3744
|
+
if (!isPreviewableImageBlock(block)) {
|
|
3745
|
+
if (block) grouped.push(block);
|
|
3746
|
+
index += 1;
|
|
3747
|
+
continue;
|
|
3748
|
+
}
|
|
3749
|
+
const startIndex = index;
|
|
3750
|
+
const items = [];
|
|
3751
|
+
while (isPreviewableImageBlock(blocks[index])) {
|
|
3752
|
+
items.push(blocks[index]);
|
|
3753
|
+
index += 1;
|
|
3754
|
+
}
|
|
3755
|
+
if (items.length < 3) {
|
|
3756
|
+
grouped.push(...items);
|
|
3757
|
+
continue;
|
|
3758
|
+
}
|
|
3759
|
+
grouped.push({
|
|
3760
|
+
kind: "image-group",
|
|
3761
|
+
key: `image-group-${startIndex}`,
|
|
3762
|
+
items
|
|
3763
|
+
});
|
|
3764
|
+
}
|
|
3765
|
+
return grouped;
|
|
3766
|
+
}
|
|
3767
|
+
//#endregion
|
|
3554
3768
|
//#region src/components/chat/hooks/use-reasoning-block-open-state.ts
|
|
3555
3769
|
function useReasoningBlockOpenState(params) {
|
|
3556
3770
|
const { isInProgress } = params;
|
|
@@ -3586,7 +3800,6 @@ const DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
|
3586
3800
|
function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey, scrollRef, stickyThresholdPx }) {
|
|
3587
3801
|
const [isAtBottom, setIsAtBottom] = useState(true);
|
|
3588
3802
|
const isStickyRef = useRef(true);
|
|
3589
|
-
const isProgrammaticScrollRef = useRef(false);
|
|
3590
3803
|
const previousResetKeyRef = useRef(null);
|
|
3591
3804
|
const pendingInitialScrollRef = useRef(false);
|
|
3592
3805
|
const scheduledScrollFrameRef = useRef(null);
|
|
@@ -3604,7 +3817,6 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
|
|
|
3604
3817
|
scheduledScrollFrameRef.current = null;
|
|
3605
3818
|
const currentElement = scrollRef.current;
|
|
3606
3819
|
if (!currentElement) return;
|
|
3607
|
-
isProgrammaticScrollRef.current = true;
|
|
3608
3820
|
if (typeof currentElement.scrollTo === "function") currentElement.scrollTo({
|
|
3609
3821
|
top: currentElement.scrollHeight,
|
|
3610
3822
|
behavior
|
|
@@ -3617,10 +3829,6 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
|
|
|
3617
3829
|
queueScrollToBottom();
|
|
3618
3830
|
}, [queueScrollToBottom, updateStickyState]);
|
|
3619
3831
|
const onScroll = useCallback(() => {
|
|
3620
|
-
if (isProgrammaticScrollRef.current) {
|
|
3621
|
-
isProgrammaticScrollRef.current = false;
|
|
3622
|
-
return;
|
|
3623
|
-
}
|
|
3624
3832
|
const element = scrollRef.current;
|
|
3625
3833
|
if (!element) return;
|
|
3626
3834
|
updateStickyState(resolveIsAtBottom(element));
|
|
@@ -3672,67 +3880,174 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
|
|
|
3672
3880
|
};
|
|
3673
3881
|
}
|
|
3674
3882
|
//#endregion
|
|
3883
|
+
//#region src/components/chat/ui/chat-message-list/chat-process-meta-row.tsx
|
|
3884
|
+
/**
|
|
3885
|
+
* Shared process-row metrics.
|
|
3886
|
+
* Collapsed process/tool rows should feel like ordinary body text lines.
|
|
3887
|
+
*/
|
|
3888
|
+
const CHAT_PROCESS_LEADING_COL_CLASS = "inline-flex h-[1.15em] w-[1.15em] shrink-0 items-center justify-center text-current";
|
|
3889
|
+
const CHAT_PROCESS_META_ROW_CLASS = "group/process-row flex w-full min-w-0 items-center gap-1.5 py-0 text-[0.925rem] font-normal leading-[1.72] text-muted-foreground/80";
|
|
3890
|
+
function ChatProcessLeadingIcon({ children, className }) {
|
|
3891
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3892
|
+
className: cn(CHAT_PROCESS_LEADING_COL_CLASS, className),
|
|
3893
|
+
children
|
|
3894
|
+
});
|
|
3895
|
+
}
|
|
3896
|
+
function ChatProcessWorkflowRail({ position }) {
|
|
3897
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3898
|
+
"aria-hidden": "true",
|
|
3899
|
+
"data-tool-workflow-rail": "true",
|
|
3900
|
+
className: cn("pointer-events-none absolute left-[0.575em] w-px -translate-x-1/2 bg-border/70", position === "first" ? "bottom-0 top-[0.86em]" : position === "last" ? "top-0 h-[0.86em]" : "inset-y-0")
|
|
3901
|
+
});
|
|
3902
|
+
}
|
|
3903
|
+
function ChatProcessMetaRow({ children, className, interactive = false, onClick, role, tabIndex, onKeyDown }) {
|
|
3904
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3905
|
+
"data-chat-process-meta-row": "true",
|
|
3906
|
+
className: cn(CHAT_PROCESS_META_ROW_CLASS, interactive ? "cursor-pointer transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35" : null, className),
|
|
3907
|
+
onClick,
|
|
3908
|
+
role,
|
|
3909
|
+
tabIndex,
|
|
3910
|
+
onKeyDown,
|
|
3911
|
+
children
|
|
3912
|
+
});
|
|
3913
|
+
}
|
|
3914
|
+
//#endregion
|
|
3915
|
+
//#region src/components/chat/ui/chat-message-list/chat-collapsible-meta-summary.tsx
|
|
3916
|
+
/**
|
|
3917
|
+
* Shared collapsible meta-row for process / tool-group / reasoning summaries.
|
|
3918
|
+
* Rendered as a plain button-like row (not <summary>) so browsers never inject
|
|
3919
|
+
* a default disclosure label such as "详情" / "Details".
|
|
3920
|
+
*/
|
|
3921
|
+
function ChatCollapsibleMetaSummary({ label, openGroup, open = false, icon: Icon, className, leadingIconClassName, labelClassName, onClick }) {
|
|
3922
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
3923
|
+
type: "button",
|
|
3924
|
+
"data-chat-process-meta-row": "true",
|
|
3925
|
+
className: cn(CHAT_PROCESS_META_ROW_CLASS, "m-0 w-full border-0 bg-transparent p-0 text-left shadow-none transition-colors hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35", className),
|
|
3926
|
+
"aria-expanded": open,
|
|
3927
|
+
onClick,
|
|
3928
|
+
children: [
|
|
3929
|
+
Icon ? /* @__PURE__ */ jsx(ChatProcessLeadingIcon, {
|
|
3930
|
+
className: cn("relative z-[1] rounded-sm", leadingIconClassName),
|
|
3931
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
3932
|
+
className: "h-[1.05em] w-[1.05em]",
|
|
3933
|
+
strokeWidth: 2.25
|
|
3934
|
+
})
|
|
3935
|
+
}) : null,
|
|
3936
|
+
/* @__PURE__ */ jsx("span", {
|
|
3937
|
+
className: cn("min-w-0 shrink truncate", labelClassName),
|
|
3938
|
+
children: label
|
|
3939
|
+
}),
|
|
3940
|
+
/* @__PURE__ */ jsx("span", {
|
|
3941
|
+
className: cn("inline-flex h-[1.15em] w-[1.15em] shrink-0 items-center justify-center text-muted-foreground/80 transition-opacity", open ? "opacity-100" : "opacity-0 group-hover/process-row:opacity-100 focus-visible:opacity-100"),
|
|
3942
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
3943
|
+
className: cn("h-[1.05em] w-[1.05em] transition-transform", open && "rotate-90"),
|
|
3944
|
+
strokeWidth: 2.25
|
|
3945
|
+
})
|
|
3946
|
+
})
|
|
3947
|
+
]
|
|
3948
|
+
});
|
|
3949
|
+
}
|
|
3950
|
+
//#endregion
|
|
3675
3951
|
//#region src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
|
|
3676
|
-
function
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
return label;
|
|
3952
|
+
function formatReasoningLabel(label, text, characterCountTemplate) {
|
|
3953
|
+
const characterCount = Array.from(text).length;
|
|
3954
|
+
return (characterCountTemplate ?? `${label} · {count}`).split("{count}").join(String(characterCount));
|
|
3680
3955
|
}
|
|
3681
|
-
function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
3956
|
+
function ChatReasoningBlock({ label, text, characterCountTemplates, isUser, isInProgress }) {
|
|
3682
3957
|
const { isOpen, onSummaryClick } = useReasoningBlockOpenState({ isInProgress });
|
|
3683
3958
|
const scrollRef = useRef(null);
|
|
3684
|
-
const displayLabel =
|
|
3959
|
+
const displayLabel = formatReasoningLabel(label, text, isInProgress ? characterCountTemplates?.inProgress : characterCountTemplates?.completed);
|
|
3685
3960
|
const { onScroll } = useStickyBottomScroll({
|
|
3686
3961
|
scrollRef,
|
|
3687
|
-
resetKey:
|
|
3962
|
+
resetKey: "reasoning",
|
|
3688
3963
|
isLoading: false,
|
|
3689
3964
|
hasContent: text.length > 0,
|
|
3690
3965
|
contentVersion: text,
|
|
3691
3966
|
stickyThresholdPx: 20
|
|
3692
3967
|
});
|
|
3693
|
-
return /* @__PURE__ */ jsxs("
|
|
3694
|
-
className: "
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
3968
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3969
|
+
className: "group/reasoning",
|
|
3970
|
+
children: [/* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
3971
|
+
openGroup: "reasoning",
|
|
3972
|
+
open: isOpen,
|
|
3973
|
+
icon: Brain,
|
|
3974
|
+
leadingIconClassName: isUser ? "bg-primary" : "bg-card",
|
|
3975
|
+
label: displayLabel,
|
|
3976
|
+
labelClassName: isUser ? "text-primary-100" : void 0,
|
|
3977
|
+
onClick: onSummaryClick
|
|
3978
|
+
}), isOpen ? /* @__PURE__ */ jsx("div", {
|
|
3701
3979
|
ref: scrollRef,
|
|
3702
3980
|
onScroll,
|
|
3703
3981
|
"data-reasoning-scroll": "true",
|
|
3704
|
-
className: cn("mt-
|
|
3982
|
+
className: cn("mt-1 w-fit max-w-[500px] max-h-56 overflow-y-auto rounded-lg custom-scrollbar", isUser ? "bg-primary/70" : "bg-muted"),
|
|
3705
3983
|
children: /* @__PURE__ */ jsx("pre", {
|
|
3706
3984
|
className: "min-w-0 whitespace-pre-wrap break-all p-2 text-[11px]",
|
|
3707
3985
|
children: text
|
|
3708
3986
|
})
|
|
3709
|
-
})]
|
|
3987
|
+
}) : null]
|
|
3710
3988
|
});
|
|
3711
3989
|
}
|
|
3712
3990
|
//#endregion
|
|
3713
3991
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-root.tsx
|
|
3992
|
+
/**
|
|
3993
|
+
* Default tool presentation is an inline process row, not a heavy card.
|
|
3994
|
+
* Expanded details sit under the overview text column with clear clearance
|
|
3995
|
+
* from any workflow rail in the icon column.
|
|
3996
|
+
*/
|
|
3714
3997
|
function ToolCardRoot({ children, className }) {
|
|
3715
3998
|
return /* @__PURE__ */ jsx("div", {
|
|
3716
|
-
className: cn("my-
|
|
3999
|
+
className: cn("relative my-0 flex w-full min-w-0 max-w-full flex-col text-[0.925rem] leading-[1.72] text-muted-foreground", className),
|
|
3717
4000
|
children
|
|
3718
4001
|
});
|
|
3719
4002
|
}
|
|
3720
4003
|
function ToolCardContent({ children, className }) {
|
|
3721
4004
|
return /* @__PURE__ */ jsx("div", {
|
|
3722
|
-
className: cn("
|
|
4005
|
+
className: cn("mt-0.5 w-full max-w-full min-w-0 overflow-hidden pl-[calc(1.15em+0.375rem)] text-[0.925rem] leading-[1.72] text-muted-foreground", className),
|
|
3723
4006
|
children
|
|
3724
4007
|
});
|
|
3725
4008
|
}
|
|
3726
|
-
|
|
3727
|
-
|
|
3728
|
-
|
|
3729
|
-
|
|
3730
|
-
|
|
3731
|
-
|
|
3732
|
-
|
|
4009
|
+
function ToolCardDetailSection({ label, tone, children }) {
|
|
4010
|
+
const style = {
|
|
4011
|
+
input: {
|
|
4012
|
+
dot: "bg-muted-foreground/60",
|
|
4013
|
+
body: "text-foreground"
|
|
4014
|
+
},
|
|
4015
|
+
output: {
|
|
4016
|
+
dot: "bg-primary/70",
|
|
4017
|
+
body: "text-foreground"
|
|
4018
|
+
},
|
|
4019
|
+
error: {
|
|
4020
|
+
dot: "bg-rose-500/80",
|
|
4021
|
+
body: "text-rose-950/85"
|
|
4022
|
+
}
|
|
4023
|
+
}[tone];
|
|
4024
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
4025
|
+
className: "overflow-hidden rounded-md border border-border/70 bg-muted/20",
|
|
4026
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4027
|
+
className: "flex items-center gap-2 border-b border-border/60 px-2.5 py-1.5 text-[10px] font-medium tracking-wide text-muted-foreground",
|
|
4028
|
+
children: [/* @__PURE__ */ jsx("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }), /* @__PURE__ */ jsx("span", {
|
|
4029
|
+
className: "normal-case tracking-normal",
|
|
4030
|
+
children: label
|
|
4031
|
+
})]
|
|
4032
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4033
|
+
className: "w-full overflow-hidden",
|
|
4034
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
4035
|
+
className: cn("w-full max-w-full min-w-0 max-h-64 overflow-x-auto overflow-y-auto px-2.5 py-2 font-mono text-[12px] leading-relaxed whitespace-pre custom-scrollbar", style.body),
|
|
4036
|
+
children
|
|
4037
|
+
})
|
|
4038
|
+
})]
|
|
4039
|
+
});
|
|
4040
|
+
}
|
|
4041
|
+
//#endregion
|
|
4042
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-status.tsx
|
|
4043
|
+
const STATUS_STYLES = {
|
|
4044
|
+
running: {
|
|
4045
|
+
text: "text-muted-foreground",
|
|
4046
|
+
icon: Loader2,
|
|
4047
|
+
spin: true
|
|
3733
4048
|
},
|
|
3734
4049
|
success: {
|
|
3735
|
-
text: "text-
|
|
4050
|
+
text: "text-muted-foreground/75",
|
|
3736
4051
|
icon: Check,
|
|
3737
4052
|
spin: false
|
|
3738
4053
|
},
|
|
@@ -3742,20 +4057,23 @@ const STATUS_STYLES = {
|
|
|
3742
4057
|
spin: false
|
|
3743
4058
|
},
|
|
3744
4059
|
cancelled: {
|
|
3745
|
-
text: "text-muted-foreground",
|
|
4060
|
+
text: "text-muted-foreground/70",
|
|
3746
4061
|
icon: Minus,
|
|
3747
4062
|
spin: false
|
|
3748
4063
|
}
|
|
3749
4064
|
};
|
|
3750
|
-
function ToolStatusLabel({ card }) {
|
|
4065
|
+
function ToolStatusLabel({ card, iconOnly = false }) {
|
|
3751
4066
|
const style = STATUS_STYLES[card.statusTone] || STATUS_STYLES.cancelled;
|
|
3752
4067
|
const Icon = style.icon;
|
|
4068
|
+
const showLabel = !iconOnly && (card.statusTone === "running" || card.statusTone === "error");
|
|
3753
4069
|
return /* @__PURE__ */ jsxs("span", {
|
|
3754
|
-
className: cn("inline-flex items-center gap-1
|
|
4070
|
+
className: cn("inline-flex shrink-0 items-center gap-1 text-[0.925rem] font-normal leading-[1.72]", style.text),
|
|
4071
|
+
"aria-label": iconOnly ? card.statusLabel : void 0,
|
|
3755
4072
|
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
4073
|
+
"aria-hidden": "true",
|
|
4074
|
+
className: cn("h-[1.05em] w-[1.05em]", style.spin && "animate-spin"),
|
|
4075
|
+
strokeWidth: 2.25
|
|
4076
|
+
}), showLabel ? card.statusLabel : null]
|
|
3759
4077
|
});
|
|
3760
4078
|
}
|
|
3761
4079
|
//#endregion
|
|
@@ -3764,12 +4082,12 @@ function resolveToolCardActionView(action) {
|
|
|
3764
4082
|
if (action.kind === "show-content") return {
|
|
3765
4083
|
icon: Eye,
|
|
3766
4084
|
label: action.label,
|
|
3767
|
-
toneClassName: "border-border bg-
|
|
4085
|
+
toneClassName: "border-border/70 bg-transparent text-muted-foreground hover:bg-muted/60 hover:text-foreground focus-visible:ring-primary/35"
|
|
3768
4086
|
};
|
|
3769
4087
|
return {
|
|
3770
4088
|
icon: ArrowUpRight,
|
|
3771
4089
|
label: action.label ?? (action.sessionKind === "child" ? "Open child session" : "Open session"),
|
|
3772
|
-
toneClassName: "border-border bg-
|
|
4090
|
+
toneClassName: "border-border/70 bg-transparent text-muted-foreground hover:bg-muted/60 hover:text-foreground focus-visible:ring-primary/35"
|
|
3773
4091
|
};
|
|
3774
4092
|
}
|
|
3775
4093
|
function ToolCardActionButton({ action, onAction }) {
|
|
@@ -3787,12 +4105,12 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3787
4105
|
children: /* @__PURE__ */ jsx("button", {
|
|
3788
4106
|
type: "button",
|
|
3789
4107
|
onClick: handleClick,
|
|
3790
|
-
className: cn("inline-flex h-
|
|
4108
|
+
className: cn("inline-flex h-6 w-6 items-center justify-center rounded-md border transition-colors", "focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-1", view.toneClassName),
|
|
3791
4109
|
"aria-label": view.label,
|
|
3792
4110
|
title: view.label,
|
|
3793
4111
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3794
4112
|
className: "h-3.5 w-3.5",
|
|
3795
|
-
strokeWidth: 2.
|
|
4113
|
+
strokeWidth: 2.25
|
|
3796
4114
|
})
|
|
3797
4115
|
})
|
|
3798
4116
|
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
@@ -3802,44 +4120,82 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3802
4120
|
})] })
|
|
3803
4121
|
});
|
|
3804
4122
|
}
|
|
3805
|
-
function
|
|
4123
|
+
function humanizeToolName(toolName) {
|
|
4124
|
+
return toolName.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
4125
|
+
}
|
|
4126
|
+
function handleToolHeaderKeyDown(event, onToggle) {
|
|
4127
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
4128
|
+
event.preventDefault();
|
|
4129
|
+
onToggle();
|
|
4130
|
+
}
|
|
4131
|
+
function ToolHeaderSummary({ label, summary }) {
|
|
4132
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4133
|
+
className: "min-w-0 shrink truncate",
|
|
4134
|
+
children: [label, summary ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
4135
|
+
className: "mx-1.5 select-none text-muted-foreground/45",
|
|
4136
|
+
children: "·"
|
|
4137
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4138
|
+
title: summary,
|
|
4139
|
+
children: summary
|
|
4140
|
+
})] }) : null]
|
|
4141
|
+
});
|
|
4142
|
+
}
|
|
4143
|
+
function ToolHeaderChevron({ expanded }) {
|
|
4144
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4145
|
+
className: cn("inline-flex h-[1.15em] w-[1.15em] shrink-0 items-center justify-center text-muted-foreground/80 transition-opacity", expanded ? "opacity-100" : "opacity-0 group-hover/process-row:opacity-100 group-focus-within/process-row:opacity-100"),
|
|
4146
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
4147
|
+
className: cn("h-[1.05em] w-[1.05em] transition-transform", expanded && "rotate-90"),
|
|
4148
|
+
strokeWidth: 2.25
|
|
4149
|
+
})
|
|
4150
|
+
});
|
|
4151
|
+
}
|
|
4152
|
+
function ToolHeaderChangeSummary({ additions, deletions }) {
|
|
4153
|
+
if (additions === 0 && deletions === 0) return null;
|
|
4154
|
+
const accessibleLabel = [additions > 0 ? `+${additions}` : null, deletions > 0 ? `-${deletions}` : null].filter(Boolean).join(" ");
|
|
4155
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4156
|
+
className: "inline-flex shrink-0 items-center gap-1.5 tabular-nums text-muted-foreground/75",
|
|
4157
|
+
"aria-label": accessibleLabel,
|
|
4158
|
+
children: [additions > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
4159
|
+
className: "transition-colors group-hover/process-row:text-emerald-600",
|
|
4160
|
+
children: ["+", additions]
|
|
4161
|
+
}) : null, deletions > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
4162
|
+
className: "transition-colors group-hover/process-row:text-rose-600",
|
|
4163
|
+
children: ["-", deletions]
|
|
4164
|
+
}) : null]
|
|
4165
|
+
});
|
|
4166
|
+
}
|
|
4167
|
+
function ToolCardHeader({ card, toolLabel, changeSummary, icon: Icon, expanded, canExpand, hideSummary = false, actionSlot, onToggle }) {
|
|
3806
4168
|
const summaryPart = hideSummary ? "" : card.summary?.replace(/^(command|path|args|query|input):\s*/i, "") ?? "";
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
className: "
|
|
3817
|
-
children:
|
|
3818
|
-
className: "
|
|
3819
|
-
|
|
3820
|
-
})
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
}) : /* @__PURE__ */ jsx(ChevronRight, {
|
|
3838
|
-
className: "h-4 w-4 text-muted-foreground",
|
|
3839
|
-
strokeWidth: 3
|
|
3840
|
-
}))
|
|
3841
|
-
]
|
|
3842
|
-
})]
|
|
4169
|
+
const displayLabel = toolLabel ?? humanizeToolName(card.toolName);
|
|
4170
|
+
return /* @__PURE__ */ jsxs(ChatProcessMetaRow, {
|
|
4171
|
+
interactive: canExpand,
|
|
4172
|
+
onClick: canExpand ? () => onToggle() : void 0,
|
|
4173
|
+
role: canExpand ? "button" : void 0,
|
|
4174
|
+
tabIndex: canExpand ? 0 : void 0,
|
|
4175
|
+
onKeyDown: canExpand ? (event) => handleToolHeaderKeyDown(event, onToggle) : void 0,
|
|
4176
|
+
children: [
|
|
4177
|
+
/* @__PURE__ */ jsx(ChatProcessLeadingIcon, {
|
|
4178
|
+
className: "relative z-[1] rounded-sm bg-card",
|
|
4179
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4180
|
+
className: "h-[1.05em] w-[1.05em]",
|
|
4181
|
+
strokeWidth: 2.25
|
|
4182
|
+
})
|
|
4183
|
+
}),
|
|
4184
|
+
/* @__PURE__ */ jsx(ToolHeaderSummary, {
|
|
4185
|
+
label: displayLabel,
|
|
4186
|
+
summary: summaryPart
|
|
4187
|
+
}),
|
|
4188
|
+
changeSummary ? /* @__PURE__ */ jsx(ToolHeaderChangeSummary, { ...changeSummary }) : null,
|
|
4189
|
+
/* @__PURE__ */ jsx(ToolStatusLabel, {
|
|
4190
|
+
card,
|
|
4191
|
+
iconOnly: Boolean(toolLabel)
|
|
4192
|
+
}),
|
|
4193
|
+
canExpand ? /* @__PURE__ */ jsx(ToolHeaderChevron, { expanded }) : null,
|
|
4194
|
+
actionSlot ? /* @__PURE__ */ jsx("span", {
|
|
4195
|
+
className: "inline-flex shrink-0 items-center gap-1 opacity-0 transition-opacity group-hover/process-row:opacity-100 group-focus-within/process-row:opacity-100",
|
|
4196
|
+
children: actionSlot
|
|
4197
|
+
}) : null
|
|
4198
|
+
]
|
|
3843
4199
|
});
|
|
3844
4200
|
}
|
|
3845
4201
|
function ToolCardHeaderAction({ action, onAction }) {
|
|
@@ -4082,7 +4438,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4082
4438
|
className: cn("overflow-hidden bg-card", !isFirst && "border-t border-border"),
|
|
4083
4439
|
children: [
|
|
4084
4440
|
showMetaRow ? /* @__PURE__ */ jsxs("div", {
|
|
4085
|
-
className: cn("flex items-center justify-between gap-4 border-b border-border px-
|
|
4441
|
+
className: cn("flex min-h-9 items-center justify-between gap-4 border-b border-border px-3 text-muted-foreground", showPathRow ? "py-2" : "py-1.5"),
|
|
4086
4442
|
children: [/* @__PURE__ */ jsx("div", {
|
|
4087
4443
|
className: "min-w-0 flex-1",
|
|
4088
4444
|
children: showPathRow ? /* @__PURE__ */ jsx(TooltipProvider, {
|
|
@@ -4092,7 +4448,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4092
4448
|
children: /* @__PURE__ */ jsx("button", {
|
|
4093
4449
|
type: "button",
|
|
4094
4450
|
onClick: handlePathClick,
|
|
4095
|
-
className: cn("w-full truncate whitespace-nowrap text-left font-mono text-[12px] font-medium text-foreground", onFileOpen && "transition-colors hover:text-primary hover:underline"),
|
|
4451
|
+
className: cn("w-full truncate whitespace-nowrap text-left font-mono text-[12px] font-medium leading-5 text-foreground", onFileOpen && "transition-colors hover:text-primary hover:underline"),
|
|
4096
4452
|
title: block.path,
|
|
4097
4453
|
children: block.path
|
|
4098
4454
|
})
|
|
@@ -4118,28 +4474,28 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4118
4474
|
children: /* @__PURE__ */ jsx("div", {
|
|
4119
4475
|
className: "overflow-x-auto custom-scrollbar",
|
|
4120
4476
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4121
|
-
className: "min-w-max whitespace-pre bg-card px-
|
|
4477
|
+
className: "min-w-max whitespace-pre bg-card px-3 py-2 font-mono text-[11px] leading-5 text-foreground",
|
|
4122
4478
|
children: block.rawText
|
|
4123
4479
|
})
|
|
4124
4480
|
})
|
|
4125
4481
|
}) : null,
|
|
4126
4482
|
block.truncated && !previewBlock ? /* @__PURE__ */ jsx("div", {
|
|
4127
|
-
className: "border-t border-border bg-muted px-
|
|
4483
|
+
className: "border-t border-border bg-muted px-3 py-2 text-[10px] text-muted-foreground",
|
|
4128
4484
|
children: "Showing a shortened diff preview."
|
|
4129
4485
|
}) : null
|
|
4130
4486
|
]
|
|
4131
4487
|
});
|
|
4132
4488
|
}
|
|
4133
|
-
function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
4489
|
+
function ToolCardFileOperationContent({ card, className, onFileOpen, showPathRow = true }) {
|
|
4134
4490
|
const blocks = card.fileOperation?.blocks ?? [];
|
|
4135
4491
|
const output = card.output?.trim() ?? "";
|
|
4136
4492
|
if (blocks.length === 0 && !output) return null;
|
|
4137
4493
|
return /* @__PURE__ */ jsxs("div", {
|
|
4138
|
-
className: cn("overflow-hidden bg-card", className),
|
|
4494
|
+
className: cn("overflow-hidden rounded-md border border-border/70 bg-card", className),
|
|
4139
4495
|
children: [blocks.map((block, index) => {
|
|
4140
4496
|
return /* @__PURE__ */ jsx(FileOperationBlock, {
|
|
4141
4497
|
block,
|
|
4142
|
-
showPathRow
|
|
4498
|
+
showPathRow,
|
|
4143
4499
|
isFirst: index === 0,
|
|
4144
4500
|
onFileOpen
|
|
4145
4501
|
}, block.key);
|
|
@@ -4151,7 +4507,7 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4151
4507
|
children: /* @__PURE__ */ jsx("div", {
|
|
4152
4508
|
className: "overflow-x-auto custom-scrollbar",
|
|
4153
4509
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4154
|
-
className: "min-w-max whitespace-pre bg-card px-
|
|
4510
|
+
className: "min-w-max whitespace-pre bg-card px-3 py-2 font-mono text-[11px] leading-5 text-foreground",
|
|
4155
4511
|
children: output
|
|
4156
4512
|
})
|
|
4157
4513
|
})
|
|
@@ -4159,9 +4515,279 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4159
4515
|
});
|
|
4160
4516
|
}
|
|
4161
4517
|
//#endregion
|
|
4518
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/terminal/terminal-ansi.utils.ts
|
|
4519
|
+
const ANSI_ESCAPE_PREFIX = String.fromCharCode(27);
|
|
4520
|
+
const ANSI_PATTERN = new RegExp(`${ANSI_ESCAPE_PREFIX}\\[(.*?)([ -/]*[@-~])`, "g");
|
|
4521
|
+
function hasAnsiSequences(value) {
|
|
4522
|
+
return value.includes(`${ANSI_ESCAPE_PREFIX}[`);
|
|
4523
|
+
}
|
|
4524
|
+
function stripAnsiSequences(value) {
|
|
4525
|
+
return value.replace(new RegExp(`${ANSI_ESCAPE_PREFIX}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
|
|
4526
|
+
}
|
|
4527
|
+
function escapeHtml(value) {
|
|
4528
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4529
|
+
}
|
|
4530
|
+
const DEFAULT_STYLE = {
|
|
4531
|
+
bold: false,
|
|
4532
|
+
dim: false,
|
|
4533
|
+
italic: false,
|
|
4534
|
+
underline: false,
|
|
4535
|
+
inverse: false
|
|
4536
|
+
};
|
|
4537
|
+
const SGR_STYLE_PATCHES = {
|
|
4538
|
+
1: { bold: true },
|
|
4539
|
+
2: { dim: true },
|
|
4540
|
+
3: { italic: true },
|
|
4541
|
+
4: { underline: true },
|
|
4542
|
+
7: { inverse: true },
|
|
4543
|
+
22: {
|
|
4544
|
+
bold: false,
|
|
4545
|
+
dim: false
|
|
4546
|
+
},
|
|
4547
|
+
23: { italic: false },
|
|
4548
|
+
24: { underline: false },
|
|
4549
|
+
27: { inverse: false },
|
|
4550
|
+
39: { fg: void 0 },
|
|
4551
|
+
49: { bg: void 0 }
|
|
4552
|
+
};
|
|
4553
|
+
const BASIC_FG = {
|
|
4554
|
+
30: "black",
|
|
4555
|
+
31: "red",
|
|
4556
|
+
32: "green",
|
|
4557
|
+
33: "yellow",
|
|
4558
|
+
34: "blue",
|
|
4559
|
+
35: "magenta",
|
|
4560
|
+
36: "cyan",
|
|
4561
|
+
37: "white",
|
|
4562
|
+
90: "bright-black",
|
|
4563
|
+
91: "bright-red",
|
|
4564
|
+
92: "bright-green",
|
|
4565
|
+
93: "bright-yellow",
|
|
4566
|
+
94: "bright-blue",
|
|
4567
|
+
95: "bright-magenta",
|
|
4568
|
+
96: "bright-cyan",
|
|
4569
|
+
97: "bright-white"
|
|
4570
|
+
};
|
|
4571
|
+
const BASIC_BG = {
|
|
4572
|
+
40: "black",
|
|
4573
|
+
41: "red",
|
|
4574
|
+
42: "green",
|
|
4575
|
+
43: "yellow",
|
|
4576
|
+
44: "blue",
|
|
4577
|
+
45: "magenta",
|
|
4578
|
+
46: "cyan",
|
|
4579
|
+
47: "white",
|
|
4580
|
+
100: "bright-black",
|
|
4581
|
+
101: "bright-red",
|
|
4582
|
+
102: "bright-green",
|
|
4583
|
+
103: "bright-yellow",
|
|
4584
|
+
104: "bright-blue",
|
|
4585
|
+
105: "bright-magenta",
|
|
4586
|
+
106: "bright-cyan",
|
|
4587
|
+
107: "bright-white"
|
|
4588
|
+
};
|
|
4589
|
+
function resolveExtendedColor(params, index) {
|
|
4590
|
+
const mode = params[index + 1];
|
|
4591
|
+
if (mode === 5 && typeof params[index + 2] === "number") return {
|
|
4592
|
+
color: `ansi-256-${params[index + 2]}`,
|
|
4593
|
+
consumed: 2
|
|
4594
|
+
};
|
|
4595
|
+
if (mode === 2 && typeof params[index + 2] === "number" && typeof params[index + 3] === "number" && typeof params[index + 4] === "number") return {
|
|
4596
|
+
color: `rgb(${params[index + 2]},${params[index + 3]},${params[index + 4]})`,
|
|
4597
|
+
consumed: 4
|
|
4598
|
+
};
|
|
4599
|
+
return null;
|
|
4600
|
+
}
|
|
4601
|
+
function applySgr(style, params) {
|
|
4602
|
+
let next = { ...style };
|
|
4603
|
+
for (let index = 0; index < params.length; index += 1) {
|
|
4604
|
+
const code = params[index];
|
|
4605
|
+
if (code === 0) return { ...DEFAULT_STYLE };
|
|
4606
|
+
const stylePatch = SGR_STYLE_PATCHES[code];
|
|
4607
|
+
if (stylePatch) {
|
|
4608
|
+
next = {
|
|
4609
|
+
...next,
|
|
4610
|
+
...stylePatch
|
|
4611
|
+
};
|
|
4612
|
+
continue;
|
|
4613
|
+
}
|
|
4614
|
+
if (code === 38 || code === 48) {
|
|
4615
|
+
const extendedColor = resolveExtendedColor(params, index);
|
|
4616
|
+
if (extendedColor) {
|
|
4617
|
+
next = code === 38 ? {
|
|
4618
|
+
...next,
|
|
4619
|
+
fg: extendedColor.color
|
|
4620
|
+
} : {
|
|
4621
|
+
...next,
|
|
4622
|
+
bg: extendedColor.color
|
|
4623
|
+
};
|
|
4624
|
+
index += extendedColor.consumed;
|
|
4625
|
+
}
|
|
4626
|
+
continue;
|
|
4627
|
+
}
|
|
4628
|
+
if (BASIC_FG[code]) {
|
|
4629
|
+
next = {
|
|
4630
|
+
...next,
|
|
4631
|
+
fg: BASIC_FG[code]
|
|
4632
|
+
};
|
|
4633
|
+
continue;
|
|
4634
|
+
}
|
|
4635
|
+
if (BASIC_BG[code]) next = {
|
|
4636
|
+
...next,
|
|
4637
|
+
bg: BASIC_BG[code]
|
|
4638
|
+
};
|
|
4639
|
+
}
|
|
4640
|
+
return next;
|
|
4641
|
+
}
|
|
4642
|
+
function styleToClassName(style) {
|
|
4643
|
+
const classes = ["ansi"];
|
|
4644
|
+
if (style.bold) classes.push("ansi-bold");
|
|
4645
|
+
if (style.dim) classes.push("ansi-dim");
|
|
4646
|
+
if (style.italic) classes.push("ansi-italic");
|
|
4647
|
+
if (style.underline) classes.push("ansi-underline");
|
|
4648
|
+
if (style.inverse) classes.push("ansi-inverse");
|
|
4649
|
+
if (style.fg) if (style.fg.startsWith("rgb(")) classes.push("ansi-fg-custom");
|
|
4650
|
+
else if (style.fg.startsWith("ansi-256-")) {
|
|
4651
|
+
classes.push("ansi-fg-256");
|
|
4652
|
+
classes.push(style.fg);
|
|
4653
|
+
} else classes.push(`ansi-fg-${style.fg}`);
|
|
4654
|
+
if (style.bg) if (style.bg.startsWith("rgb(")) classes.push("ansi-bg-custom");
|
|
4655
|
+
else if (style.bg.startsWith("ansi-256-")) {
|
|
4656
|
+
classes.push("ansi-bg-256");
|
|
4657
|
+
classes.push(style.bg);
|
|
4658
|
+
} else classes.push(`ansi-bg-${style.bg}`);
|
|
4659
|
+
return classes.join(" ");
|
|
4660
|
+
}
|
|
4661
|
+
function styleToInlineStyle(style) {
|
|
4662
|
+
const parts = [];
|
|
4663
|
+
if (style.fg?.startsWith("rgb(")) parts.push(`color:${style.fg}`);
|
|
4664
|
+
if (style.bg?.startsWith("rgb(")) parts.push(`background-color:${style.bg}`);
|
|
4665
|
+
return parts.length > 0 ? parts.join(";") : void 0;
|
|
4666
|
+
}
|
|
4667
|
+
function openSpan(style) {
|
|
4668
|
+
const className = styleToClassName(style);
|
|
4669
|
+
const inline = styleToInlineStyle(style);
|
|
4670
|
+
return inline ? `<span class="${className}" style="${inline}">` : `<span class="${className}">`;
|
|
4671
|
+
}
|
|
4672
|
+
/**
|
|
4673
|
+
* Lightweight ANSI SGR -> HTML converter for terminal output.
|
|
4674
|
+
* Handles common color/style codes without adding a dependency.
|
|
4675
|
+
*/
|
|
4676
|
+
function ansiToHtml(value) {
|
|
4677
|
+
if (!value) return "";
|
|
4678
|
+
if (!hasAnsiSequences(value)) return escapeHtml(value);
|
|
4679
|
+
let html = "";
|
|
4680
|
+
let lastIndex = 0;
|
|
4681
|
+
let style = { ...DEFAULT_STYLE };
|
|
4682
|
+
let spanOpen = false;
|
|
4683
|
+
const flushText = (text) => {
|
|
4684
|
+
if (!text) return;
|
|
4685
|
+
if (!spanOpen) {
|
|
4686
|
+
html += openSpan(style);
|
|
4687
|
+
spanOpen = true;
|
|
4688
|
+
}
|
|
4689
|
+
html += escapeHtml(text);
|
|
4690
|
+
};
|
|
4691
|
+
const closeSpan = () => {
|
|
4692
|
+
if (spanOpen) {
|
|
4693
|
+
html += "</span>";
|
|
4694
|
+
spanOpen = false;
|
|
4695
|
+
}
|
|
4696
|
+
};
|
|
4697
|
+
value.replace(ANSI_PATTERN, (match, body, terminator, offset) => {
|
|
4698
|
+
flushText(value.slice(lastIndex, offset));
|
|
4699
|
+
lastIndex = offset + match.length;
|
|
4700
|
+
if (terminator !== "m") return match;
|
|
4701
|
+
const params = body.trim().length === 0 ? [0] : body.split(";").map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
|
|
4702
|
+
closeSpan();
|
|
4703
|
+
style = applySgr(style, params.length > 0 ? params : [0]);
|
|
4704
|
+
return match;
|
|
4705
|
+
});
|
|
4706
|
+
flushText(value.slice(lastIndex));
|
|
4707
|
+
closeSpan();
|
|
4708
|
+
return html;
|
|
4709
|
+
}
|
|
4710
|
+
//#endregion
|
|
4711
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/terminal/terminal-panes.tsx
|
|
4712
|
+
const MAX_TERMINAL_TEXT_CHARS = 12e4;
|
|
4713
|
+
function clampTerminalText(value) {
|
|
4714
|
+
if (value.length <= MAX_TERMINAL_TEXT_CHARS) return value;
|
|
4715
|
+
return `${value.slice(0, MAX_TERMINAL_TEXT_CHARS)}\n…`;
|
|
4716
|
+
}
|
|
4717
|
+
function TerminalCode({ code, language, preferAnsi = false, className }) {
|
|
4718
|
+
const source = clampTerminalText(code);
|
|
4719
|
+
const content = useMemo(() => {
|
|
4720
|
+
if (preferAnsi && hasAnsiSequences(source)) return {
|
|
4721
|
+
html: ansiToHtml(source),
|
|
4722
|
+
language: "ansi",
|
|
4723
|
+
highlighted: true
|
|
4724
|
+
};
|
|
4725
|
+
const plain = stripAnsiSequences(source);
|
|
4726
|
+
return chatCodeSyntaxHighlighter.highlight(plain, language);
|
|
4727
|
+
}, [
|
|
4728
|
+
language,
|
|
4729
|
+
preferAnsi,
|
|
4730
|
+
source
|
|
4731
|
+
]);
|
|
4732
|
+
return /* @__PURE__ */ jsx("code", {
|
|
4733
|
+
className: cn("hljs chat-terminal-code", `language-${content.language}`, className),
|
|
4734
|
+
"data-highlighted": content.highlighted ? "true" : "false",
|
|
4735
|
+
dangerouslySetInnerHTML: { __html: content.html }
|
|
4736
|
+
});
|
|
4737
|
+
}
|
|
4738
|
+
/**
|
|
4739
|
+
* Compact terminal-style surface for command tools.
|
|
4740
|
+
* Style only — no decorative window chrome.
|
|
4741
|
+
*/
|
|
4742
|
+
function ChatTerminalSurface({ command, output, emptyLabel, isRunning, hasOutput, isError = false }) {
|
|
4743
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4744
|
+
className: cn("chat-terminal-surface", isError ? "chat-terminal-surface-error" : void 0),
|
|
4745
|
+
"data-testid": "chat-terminal-surface",
|
|
4746
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
4747
|
+
className: "chat-terminal-body custom-scrollbar",
|
|
4748
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4749
|
+
className: "chat-terminal-line chat-terminal-line-command",
|
|
4750
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4751
|
+
className: "chat-terminal-gutter",
|
|
4752
|
+
"aria-hidden": "true",
|
|
4753
|
+
children: "$"
|
|
4754
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4755
|
+
className: "chat-terminal-line-content",
|
|
4756
|
+
children: command ? /* @__PURE__ */ jsxs("pre", {
|
|
4757
|
+
className: "chat-terminal-pre",
|
|
4758
|
+
children: [/* @__PURE__ */ jsx(TerminalCode, {
|
|
4759
|
+
code: command,
|
|
4760
|
+
language: "bash"
|
|
4761
|
+
}), isRunning && !hasOutput ? /* @__PURE__ */ jsx("span", {
|
|
4762
|
+
className: "chat-terminal-caret",
|
|
4763
|
+
"aria-hidden": "true"
|
|
4764
|
+
}) : null]
|
|
4765
|
+
}) : /* @__PURE__ */ jsx("div", { className: "chat-terminal-skeleton" })
|
|
4766
|
+
})]
|
|
4767
|
+
}), (hasOutput || !isRunning) && /* @__PURE__ */ jsx("div", {
|
|
4768
|
+
className: "chat-terminal-output-block",
|
|
4769
|
+
children: hasOutput ? /* @__PURE__ */ jsxs("pre", {
|
|
4770
|
+
className: cn("chat-terminal-pre chat-terminal-output", isError ? "chat-terminal-pre-error" : void 0),
|
|
4771
|
+
children: [/* @__PURE__ */ jsx(TerminalCode, {
|
|
4772
|
+
code: output,
|
|
4773
|
+
language: isError ? "text" : "shell",
|
|
4774
|
+
preferAnsi: true,
|
|
4775
|
+
className: isError ? "chat-terminal-code-error" : void 0
|
|
4776
|
+
}), isRunning ? /* @__PURE__ */ jsx("span", {
|
|
4777
|
+
className: "chat-terminal-caret",
|
|
4778
|
+
"aria-hidden": "true"
|
|
4779
|
+
}) : null]
|
|
4780
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
4781
|
+
className: "chat-terminal-empty",
|
|
4782
|
+
children: emptyLabel
|
|
4783
|
+
})
|
|
4784
|
+
})]
|
|
4785
|
+
})
|
|
4786
|
+
});
|
|
4787
|
+
}
|
|
4788
|
+
//#endregion
|
|
4162
4789
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
|
|
4163
4790
|
const TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
|
|
4164
|
-
const ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
4165
4791
|
function readNonEmptyString(value) {
|
|
4166
4792
|
if (typeof value !== "string") return null;
|
|
4167
4793
|
return value.length > 0 ? value : null;
|
|
@@ -4169,9 +4795,6 @@ function readNonEmptyString(value) {
|
|
|
4169
4795
|
function isRecord(value) {
|
|
4170
4796
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4171
4797
|
}
|
|
4172
|
-
function stripAnsi(value) {
|
|
4173
|
-
return value.replace(ANSI_ESCAPE_PATTERN, "");
|
|
4174
|
-
}
|
|
4175
4798
|
function isStructuredTerminalRecord(record) {
|
|
4176
4799
|
return "command" in record || "workingDir" in record || "exitCode" in record || "durationMs" in record || "stdout" in record || "stderr" in record || "aggregated_output" in record || "combinedOutput" in record;
|
|
4177
4800
|
}
|
|
@@ -4186,26 +4809,35 @@ function extractTerminalOutputFromRecord(record) {
|
|
|
4186
4809
|
function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
4187
4810
|
if (isRecord(structuredOutput)) {
|
|
4188
4811
|
const terminalOutput = extractTerminalOutputFromRecord(structuredOutput);
|
|
4189
|
-
if (terminalOutput) return
|
|
4812
|
+
if (terminalOutput) return terminalOutput;
|
|
4190
4813
|
if (isStructuredTerminalRecord(structuredOutput)) return "";
|
|
4191
4814
|
}
|
|
4192
4815
|
if (!rawOutput) return "";
|
|
4193
4816
|
const trimmed = rawOutput.trim();
|
|
4194
|
-
if (!trimmed.startsWith("{")) return
|
|
4817
|
+
if (!trimmed.startsWith("{")) return rawOutput;
|
|
4195
4818
|
try {
|
|
4196
4819
|
const parsed = JSON.parse(trimmed);
|
|
4197
|
-
if (!isRecord(parsed)) return
|
|
4820
|
+
if (!isRecord(parsed)) return rawOutput;
|
|
4198
4821
|
const terminalOutput = extractTerminalOutputFromRecord(parsed);
|
|
4199
|
-
if (terminalOutput) return
|
|
4822
|
+
if (terminalOutput) return terminalOutput;
|
|
4200
4823
|
if (isStructuredTerminalRecord(parsed)) return "";
|
|
4201
|
-
return
|
|
4824
|
+
return rawOutput;
|
|
4202
4825
|
} catch {
|
|
4203
|
-
return
|
|
4826
|
+
return rawOutput;
|
|
4204
4827
|
}
|
|
4205
4828
|
}
|
|
4206
4829
|
function shouldAutoExpandRunningFileOperation(toolName) {
|
|
4207
4830
|
return toolName === "write_file" || toolName === "edit_file";
|
|
4208
4831
|
}
|
|
4832
|
+
function countFileOperationChanges(blocks) {
|
|
4833
|
+
return blocks.reduce((totals, block) => ({
|
|
4834
|
+
additions: totals.additions + block.lines.filter((line) => line.kind === "add").length,
|
|
4835
|
+
deletions: totals.deletions + block.lines.filter((line) => line.kind === "remove").length
|
|
4836
|
+
}), {
|
|
4837
|
+
additions: 0,
|
|
4838
|
+
deletions: 0
|
|
4839
|
+
});
|
|
4840
|
+
}
|
|
4209
4841
|
function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning = true, expandOnError = false, statusTone }) {
|
|
4210
4842
|
const [expanded, setExpanded] = useState(false);
|
|
4211
4843
|
const [hasUserToggled, setHasUserToggled] = useState(false);
|
|
@@ -4264,47 +4896,20 @@ function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning
|
|
|
4264
4896
|
onToggle
|
|
4265
4897
|
};
|
|
4266
4898
|
}
|
|
4267
|
-
function
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
body: "text-foreground"
|
|
4274
|
-
},
|
|
4275
|
-
output: {
|
|
4276
|
-
shell: "border-border bg-card",
|
|
4277
|
-
header: "border-border bg-muted/55 text-muted-foreground",
|
|
4278
|
-
dot: "bg-primary/70",
|
|
4279
|
-
body: "text-foreground"
|
|
4280
|
-
},
|
|
4281
|
-
error: {
|
|
4282
|
-
shell: "border-rose-200/80 bg-rose-50/85",
|
|
4283
|
-
header: "border-rose-200/80 bg-rose-100/80 text-rose-700",
|
|
4284
|
-
dot: "bg-rose-500/80",
|
|
4285
|
-
body: "text-rose-950/85"
|
|
4286
|
-
}
|
|
4287
|
-
}[tone];
|
|
4288
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
4289
|
-
className: cn("overflow-hidden rounded-md border shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]", style.shell),
|
|
4290
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
4291
|
-
className: cn("flex items-center gap-2 border-b px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.16em]", style.header),
|
|
4292
|
-
children: [/* @__PURE__ */ jsx("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }), /* @__PURE__ */ jsx("span", { children: label })]
|
|
4293
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
4294
|
-
className: "w-full overflow-hidden",
|
|
4295
|
-
children: /* @__PURE__ */ jsx("pre", {
|
|
4296
|
-
className: cn("w-full max-w-full min-w-0 max-h-64 overflow-x-auto overflow-y-auto px-3 py-2.5 font-mono text-[12px] leading-relaxed whitespace-pre custom-scrollbar", style.body),
|
|
4297
|
-
children
|
|
4298
|
-
})
|
|
4299
|
-
})]
|
|
4300
|
-
});
|
|
4899
|
+
function extractTerminalMeta(structuredOutput) {
|
|
4900
|
+
if (!isRecord(structuredOutput)) return {};
|
|
4901
|
+
return {
|
|
4902
|
+
exitCode: typeof structuredOutput.exitCode === "number" ? structuredOutput.exitCode : typeof structuredOutput.exit_code === "number" ? structuredOutput.exit_code : null,
|
|
4903
|
+
workingDir: readNonEmptyString(structuredOutput.workingDir) ?? readNonEmptyString(structuredOutput.cwd) ?? readNonEmptyString(structuredOutput.working_directory)
|
|
4904
|
+
};
|
|
4301
4905
|
}
|
|
4302
|
-
function TerminalExecutionView({ card }) {
|
|
4906
|
+
function TerminalExecutionView({ card, toolLabel }) {
|
|
4303
4907
|
const output = normalizeTerminalOutput(card.output, card.outputData);
|
|
4304
4908
|
const isRunning = card.statusTone === "running";
|
|
4305
4909
|
const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
|
|
4306
4910
|
const hasOutput = output.trim().length > 0;
|
|
4307
4911
|
const canExpand = isRunning || hasOutput || Boolean(commandPart?.trim());
|
|
4912
|
+
const meta = extractTerminalMeta(card.outputData);
|
|
4308
4913
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4309
4914
|
canExpand,
|
|
4310
4915
|
isRunning,
|
|
@@ -4314,38 +4919,32 @@ function TerminalExecutionView({ card }) {
|
|
|
4314
4919
|
});
|
|
4315
4920
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4316
4921
|
card,
|
|
4922
|
+
toolLabel,
|
|
4317
4923
|
icon: Terminal,
|
|
4318
4924
|
expanded,
|
|
4319
4925
|
canExpand,
|
|
4320
4926
|
onToggle
|
|
4321
|
-
}), expanded && /* @__PURE__ */
|
|
4322
|
-
className: "
|
|
4323
|
-
children: /* @__PURE__ */
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
children: [commandPart, isRunning && !output && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
|
|
4333
|
-
}) : /* @__PURE__ */ jsx("div", { className: "h-3 w-32 bg-muted rounded animate-pulse mt-2" })
|
|
4334
|
-
})]
|
|
4927
|
+
}), expanded && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4928
|
+
className: "bg-transparent py-0",
|
|
4929
|
+
children: /* @__PURE__ */ jsx(ChatTerminalSurface, {
|
|
4930
|
+
command: commandPart,
|
|
4931
|
+
output,
|
|
4932
|
+
emptyLabel: card.emptyLabel,
|
|
4933
|
+
isRunning,
|
|
4934
|
+
hasOutput,
|
|
4935
|
+
isError: card.statusTone === "error",
|
|
4936
|
+
exitCode: meta.exitCode,
|
|
4937
|
+
workingDir: meta.workingDir
|
|
4335
4938
|
})
|
|
4336
|
-
})
|
|
4337
|
-
className: hasOutput ? void 0 : "bg-muted/20 py-2",
|
|
4338
|
-
children: /* @__PURE__ */ jsxs("pre", {
|
|
4339
|
-
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"),
|
|
4340
|
-
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" })]
|
|
4341
|
-
})
|
|
4342
|
-
})] })] });
|
|
4939
|
+
})] });
|
|
4343
4940
|
}
|
|
4344
|
-
function FileOperationView({ card, onFileOpen }) {
|
|
4941
|
+
function FileOperationView({ card, toolLabel, onFileOpen }) {
|
|
4942
|
+
const input = card.input?.trim() ?? "";
|
|
4345
4943
|
const output = card.output?.trim() ?? "";
|
|
4346
4944
|
const isRunning = card.statusTone === "running";
|
|
4347
4945
|
const hasStructuredPreview = Boolean(card.fileOperation?.blocks.length);
|
|
4348
|
-
const
|
|
4946
|
+
const showRawInput = Boolean(input) && !hasStructuredPreview;
|
|
4947
|
+
const hasContent = hasStructuredPreview || Boolean(input) || Boolean(output);
|
|
4349
4948
|
const previewBlocks = card.fileOperation?.blocks ?? [];
|
|
4350
4949
|
const previewLineCount = previewBlocks.reduce((count, block) => count + block.lines.length, 0);
|
|
4351
4950
|
const previewCharCount = previewBlocks.reduce((count, block) => {
|
|
@@ -4360,22 +4959,34 @@ function FileOperationView({ card, onFileOpen }) {
|
|
|
4360
4959
|
expandOnError: hasContent,
|
|
4361
4960
|
statusTone: card.statusTone
|
|
4362
4961
|
});
|
|
4962
|
+
const isEdit = card.toolName === "edit_file" || card.toolName === "write_file" || card.toolName === "apply_patch" || card.toolName === "file_change";
|
|
4363
4963
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4364
4964
|
card,
|
|
4365
|
-
|
|
4965
|
+
toolLabel,
|
|
4966
|
+
changeSummary: isEdit ? countFileOperationChanges(previewBlocks) : void 0,
|
|
4967
|
+
icon: isEdit ? Code2 : FileText,
|
|
4366
4968
|
expanded,
|
|
4367
4969
|
canExpand: hasContent || isRunning,
|
|
4368
|
-
hideSummary:
|
|
4970
|
+
hideSummary: false,
|
|
4369
4971
|
onToggle
|
|
4370
|
-
}), expanded && hasContent
|
|
4371
|
-
className: "
|
|
4372
|
-
children:
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4376
|
-
|
|
4972
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsxs(ToolCardContent, {
|
|
4973
|
+
className: "bg-transparent py-0",
|
|
4974
|
+
children: [
|
|
4975
|
+
showRawInput ? /* @__PURE__ */ jsx(ToolCardDetailSection, {
|
|
4976
|
+
label: card.inputLabel?.trim() || "Input",
|
|
4977
|
+
tone: "input",
|
|
4978
|
+
children: input
|
|
4979
|
+
}) : null,
|
|
4980
|
+
showRawInput && output ? /* @__PURE__ */ jsx("div", { className: "h-2" }) : null,
|
|
4981
|
+
hasStructuredPreview || output ? /* @__PURE__ */ jsx(ToolCardFileOperationContent, {
|
|
4982
|
+
card,
|
|
4983
|
+
onFileOpen,
|
|
4984
|
+
showPathRow: true
|
|
4985
|
+
}) : null
|
|
4986
|
+
]
|
|
4987
|
+
}) : null] });
|
|
4377
4988
|
}
|
|
4378
|
-
function SearchSnippetView({ card }) {
|
|
4989
|
+
function SearchSnippetView({ card, toolLabel, icon: Icon = Search }) {
|
|
4379
4990
|
const isRunning = card.statusTone === "running";
|
|
4380
4991
|
const output = card.output?.trim() ?? "";
|
|
4381
4992
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
@@ -4386,24 +4997,37 @@ function SearchSnippetView({ card }) {
|
|
|
4386
4997
|
});
|
|
4387
4998
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4388
4999
|
card,
|
|
4389
|
-
|
|
5000
|
+
toolLabel,
|
|
5001
|
+
icon: Icon,
|
|
4390
5002
|
expanded,
|
|
4391
5003
|
canExpand: !!output || isRunning,
|
|
4392
5004
|
onToggle
|
|
4393
|
-
}), expanded && output && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4394
|
-
className: "
|
|
4395
|
-
children:
|
|
4396
|
-
|
|
5005
|
+
}), expanded && output && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
5006
|
+
className: "py-0",
|
|
5007
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
5008
|
+
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",
|
|
5009
|
+
children: output
|
|
5010
|
+
})
|
|
5011
|
+
})] });
|
|
4397
5012
|
}
|
|
4398
|
-
|
|
5013
|
+
//#endregion
|
|
5014
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-generic.tsx
|
|
5015
|
+
function buildToolActionSlot(card, onToolAction, renderToolAgent) {
|
|
5016
|
+
const agentAction = card.agentId && renderToolAgent ? renderToolAgent(card.agentId) : null;
|
|
5017
|
+
const toolAction = card.action && onToolAction ? /* @__PURE__ */ jsx(ToolCardHeaderAction, {
|
|
5018
|
+
action: card.action,
|
|
5019
|
+
onAction: onToolAction
|
|
5020
|
+
}) : null;
|
|
5021
|
+
return agentAction || toolAction ? /* @__PURE__ */ jsxs(Fragment$1, { children: [agentAction, toolAction] }) : void 0;
|
|
5022
|
+
}
|
|
5023
|
+
function GenericToolCard({ card, toolLabel, icon: Icon = Wrench, onToolAction, renderToolAgent }) {
|
|
4399
5024
|
const input = card.input?.trim() ?? "";
|
|
4400
5025
|
const output = card.output?.trim() ?? "";
|
|
4401
5026
|
const isRunning = card.statusTone === "running";
|
|
4402
5027
|
const hasInputSection = input.length > 0;
|
|
4403
5028
|
const hasOutputSection = output.length > 0;
|
|
4404
5029
|
const hasContent = hasInputSection || hasOutputSection;
|
|
4405
|
-
const
|
|
4406
|
-
const outputLabel = card.outputLabel?.trim() || "Output";
|
|
5030
|
+
const actionSlot = buildToolActionSlot(card, onToolAction, renderToolAgent);
|
|
4407
5031
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4408
5032
|
canExpand: hasContent || isRunning,
|
|
4409
5033
|
isRunning,
|
|
@@ -4412,30 +5036,28 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
|
|
|
4412
5036
|
});
|
|
4413
5037
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4414
5038
|
card,
|
|
4415
|
-
|
|
5039
|
+
toolLabel,
|
|
5040
|
+
icon: Icon,
|
|
4416
5041
|
expanded,
|
|
4417
5042
|
canExpand: hasContent || isRunning,
|
|
4418
|
-
actionSlot
|
|
4419
|
-
action: card.action,
|
|
4420
|
-
onAction: onToolAction
|
|
4421
|
-
}) : null] }) : void 0,
|
|
5043
|
+
actionSlot,
|
|
4422
5044
|
onToggle
|
|
4423
|
-
}), expanded && hasContent
|
|
4424
|
-
className: "bg-transparent
|
|
5045
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsxs(ToolCardContent, {
|
|
5046
|
+
className: "bg-transparent py-0",
|
|
4425
5047
|
children: [
|
|
4426
|
-
hasInputSection
|
|
4427
|
-
label: inputLabel,
|
|
5048
|
+
hasInputSection ? /* @__PURE__ */ jsx(ToolCardDetailSection, {
|
|
5049
|
+
label: card.inputLabel?.trim() || "Input",
|
|
4428
5050
|
tone: "input",
|
|
4429
5051
|
children: input
|
|
4430
|
-
}),
|
|
4431
|
-
hasInputSection && hasOutputSection
|
|
4432
|
-
hasOutputSection
|
|
4433
|
-
label: outputLabel,
|
|
5052
|
+
}) : null,
|
|
5053
|
+
hasInputSection && hasOutputSection ? /* @__PURE__ */ jsx("div", { className: "h-2" }) : null,
|
|
5054
|
+
hasOutputSection ? /* @__PURE__ */ jsx(ToolCardDetailSection, {
|
|
5055
|
+
label: card.outputLabel?.trim() || "Output",
|
|
4434
5056
|
tone: card.statusTone === "error" ? "error" : "output",
|
|
4435
5057
|
children: output
|
|
4436
|
-
})
|
|
5058
|
+
}) : null
|
|
4437
5059
|
]
|
|
4438
|
-
})] });
|
|
5060
|
+
}) : null] });
|
|
4439
5061
|
}
|
|
4440
5062
|
//#endregion
|
|
4441
5063
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-panel-app.tsx
|
|
@@ -4457,34 +5079,364 @@ function isTerminalTool(name) {
|
|
|
4457
5079
|
const lowered = name.toLowerCase();
|
|
4458
5080
|
return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
|
|
4459
5081
|
}
|
|
4460
|
-
function
|
|
5082
|
+
function isFileTool(name) {
|
|
4461
5083
|
const lowered = name.toLowerCase();
|
|
4462
5084
|
return lowered === "read_file" || lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
4463
5085
|
}
|
|
5086
|
+
function isFileWriteTool(name) {
|
|
5087
|
+
const lowered = name.toLowerCase();
|
|
5088
|
+
return lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
5089
|
+
}
|
|
4464
5090
|
function isSearchTool(name) {
|
|
4465
5091
|
const lowered = name.toLowerCase();
|
|
4466
5092
|
return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
|
|
4467
5093
|
}
|
|
4468
|
-
|
|
5094
|
+
const BUILT_IN_TOOL_PRESENTATIONS = {
|
|
5095
|
+
list_dir: {
|
|
5096
|
+
statusKind: "directory",
|
|
5097
|
+
icon: FolderTree
|
|
5098
|
+
},
|
|
5099
|
+
web_fetch: {
|
|
5100
|
+
statusKind: "web",
|
|
5101
|
+
icon: Globe2
|
|
5102
|
+
},
|
|
5103
|
+
message: {
|
|
5104
|
+
statusKind: "message",
|
|
5105
|
+
icon: Send
|
|
5106
|
+
},
|
|
5107
|
+
sessions_list: {
|
|
5108
|
+
statusKind: "session",
|
|
5109
|
+
icon: MessageSquare
|
|
5110
|
+
},
|
|
5111
|
+
sessions_history: {
|
|
5112
|
+
statusKind: "session",
|
|
5113
|
+
icon: MessageSquare
|
|
5114
|
+
},
|
|
5115
|
+
sessions_request: {
|
|
5116
|
+
statusKind: "session",
|
|
5117
|
+
icon: MessageSquare
|
|
5118
|
+
},
|
|
5119
|
+
spawn: {
|
|
5120
|
+
statusKind: "agent",
|
|
5121
|
+
icon: Bot
|
|
5122
|
+
},
|
|
5123
|
+
sessions_spawn: {
|
|
5124
|
+
statusKind: "agent",
|
|
5125
|
+
icon: Bot
|
|
5126
|
+
},
|
|
5127
|
+
subagents: {
|
|
5128
|
+
statusKind: "agent",
|
|
5129
|
+
icon: Bot
|
|
5130
|
+
},
|
|
5131
|
+
memory_search: {
|
|
5132
|
+
statusKind: "memory",
|
|
5133
|
+
icon: Brain
|
|
5134
|
+
},
|
|
5135
|
+
memory_get: {
|
|
5136
|
+
statusKind: "memory",
|
|
5137
|
+
icon: Brain
|
|
5138
|
+
},
|
|
5139
|
+
cron: {
|
|
5140
|
+
statusKind: "schedule",
|
|
5141
|
+
icon: CalendarClock
|
|
5142
|
+
},
|
|
5143
|
+
gateway: {
|
|
5144
|
+
statusKind: "system",
|
|
5145
|
+
icon: Settings
|
|
5146
|
+
},
|
|
5147
|
+
view_image: {
|
|
5148
|
+
statusKind: "image",
|
|
5149
|
+
icon: ImageIcon
|
|
5150
|
+
},
|
|
5151
|
+
show_file: {
|
|
5152
|
+
statusKind: "display",
|
|
5153
|
+
icon: Eye
|
|
5154
|
+
},
|
|
5155
|
+
show_url: {
|
|
5156
|
+
statusKind: "display",
|
|
5157
|
+
icon: Eye
|
|
5158
|
+
},
|
|
5159
|
+
show_panel_app: {
|
|
5160
|
+
statusKind: "display",
|
|
5161
|
+
icon: Eye
|
|
5162
|
+
},
|
|
5163
|
+
show_content: {
|
|
5164
|
+
statusKind: "display",
|
|
5165
|
+
icon: Eye
|
|
5166
|
+
}
|
|
5167
|
+
};
|
|
5168
|
+
function resolveBuiltInToolPresentation(name) {
|
|
5169
|
+
return BUILT_IN_TOOL_PRESENTATIONS[name.toLowerCase()] ?? null;
|
|
5170
|
+
}
|
|
5171
|
+
function ChatToolCard({ card, toolStatusLabels, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
|
|
4469
5172
|
if (card.panelApp) return /* @__PURE__ */ jsx(PanelAppInlineToolCard, {
|
|
4470
5173
|
card,
|
|
4471
5174
|
onToolAction,
|
|
4472
5175
|
renderPanelAppCard
|
|
4473
5176
|
});
|
|
4474
|
-
|
|
4475
|
-
|
|
5177
|
+
const builtInPresentation = resolveBuiltInToolPresentation(card.toolName);
|
|
5178
|
+
const builtInLabel = builtInPresentation ? toolStatusLabels?.builtIn?.[builtInPresentation.statusKind]?.[card.statusTone] : void 0;
|
|
5179
|
+
if (isTerminalTool(card.toolName)) return /* @__PURE__ */ jsx(TerminalExecutionView, {
|
|
5180
|
+
card,
|
|
5181
|
+
toolLabel: toolStatusLabels?.terminal[card.statusTone]
|
|
5182
|
+
});
|
|
5183
|
+
if (isFileTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, {
|
|
4476
5184
|
card,
|
|
5185
|
+
toolLabel: isFileWriteTool(card.toolName) ? toolStatusLabels?.fileEdit[card.statusTone] : toolStatusLabels?.fileRead[card.statusTone],
|
|
4477
5186
|
onFileOpen
|
|
4478
5187
|
});
|
|
4479
|
-
if (isSearchTool(card.toolName)) return /* @__PURE__ */ jsx(SearchSnippetView, {
|
|
5188
|
+
if (isSearchTool(card.toolName)) return /* @__PURE__ */ jsx(SearchSnippetView, {
|
|
5189
|
+
card,
|
|
5190
|
+
toolLabel: builtInLabel ?? toolStatusLabels?.search[card.statusTone],
|
|
5191
|
+
icon: builtInPresentation?.icon
|
|
5192
|
+
});
|
|
4480
5193
|
return /* @__PURE__ */ jsx(GenericToolCard, {
|
|
4481
5194
|
card,
|
|
5195
|
+
toolLabel: builtInLabel,
|
|
5196
|
+
icon: builtInPresentation?.icon,
|
|
4482
5197
|
onToolAction,
|
|
4483
5198
|
renderToolAgent
|
|
4484
5199
|
});
|
|
4485
5200
|
}
|
|
4486
5201
|
//#endregion
|
|
5202
|
+
//#region src/components/chat/ui/chat-message-list/chat-tool-activity-group.tsx
|
|
5203
|
+
function ChatToolActivityGroup({ group, open, isUser, reasoningCharacterCountTemplates, toolStatusLabels, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard, onOpenChange }) {
|
|
5204
|
+
const toolCount = group.parts.filter((part) => part.type === "tool-card").length;
|
|
5205
|
+
const showWorkflowRail = open && toolCount > 1;
|
|
5206
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5207
|
+
className: "group/tool-activity",
|
|
5208
|
+
children: [/* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
5209
|
+
openGroup: "tool-activity",
|
|
5210
|
+
open,
|
|
5211
|
+
icon: Workflow,
|
|
5212
|
+
leadingIconClassName: "bg-card",
|
|
5213
|
+
label: group.label,
|
|
5214
|
+
onClick: () => onOpenChange(!open)
|
|
5215
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
5216
|
+
className: "text-[0.925rem] leading-[1.72]",
|
|
5217
|
+
children: group.parts.map((part, index) => {
|
|
5218
|
+
const isLast = index === group.parts.length - 1;
|
|
5219
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5220
|
+
className: "relative min-w-0",
|
|
5221
|
+
children: [showWorkflowRail ? /* @__PURE__ */ jsx(ChatProcessWorkflowRail, { position: index === 0 ? "first" : isLast ? "last" : "middle" }) : null, part.type === "tool-card" ? /* @__PURE__ */ jsx(ChatToolCard, {
|
|
5222
|
+
card: part.card,
|
|
5223
|
+
toolStatusLabels,
|
|
5224
|
+
onToolAction,
|
|
5225
|
+
onFileOpen,
|
|
5226
|
+
renderToolAgent,
|
|
5227
|
+
renderPanelAppCard
|
|
5228
|
+
}) : /* @__PURE__ */ jsx(ChatReasoningBlock, {
|
|
5229
|
+
label: part.label,
|
|
5230
|
+
text: part.text,
|
|
5231
|
+
characterCountTemplates: reasoningCharacterCountTemplates,
|
|
5232
|
+
isUser,
|
|
5233
|
+
isInProgress: false
|
|
5234
|
+
})]
|
|
5235
|
+
}, `tool-group-item-${group.startIndex + index}`);
|
|
5236
|
+
})
|
|
5237
|
+
}) : null]
|
|
5238
|
+
});
|
|
5239
|
+
}
|
|
5240
|
+
//#endregion
|
|
5241
|
+
//#region src/components/chat/ui/chat-message-list/chat-tool-activity-group.utils.ts
|
|
5242
|
+
const MAX_SUMMARY_SEGMENTS = 3;
|
|
5243
|
+
function isToolCardPart(part) {
|
|
5244
|
+
return part.type === "tool-card";
|
|
5245
|
+
}
|
|
5246
|
+
function resolveToolActivityFamily(toolName) {
|
|
5247
|
+
const lowered = toolName.toLowerCase();
|
|
5248
|
+
if (lowered === "list_dir") return "directory";
|
|
5249
|
+
if (lowered.includes("show_content") || lowered.includes("panel_app") || lowered.includes("panel")) return "panel";
|
|
5250
|
+
if (lowered === "spawn" || lowered.includes("subagent") || lowered.includes("session_request") || lowered.includes("agent") && !lowered.includes("read")) return "agent";
|
|
5251
|
+
if (lowered.includes("web_") || lowered.includes("_web") || lowered.includes("url") || lowered.includes("fetch") || lowered.includes("http")) return "web";
|
|
5252
|
+
if (lowered === "grep_search" || lowered === "find_files" || lowered === "search_files" || lowered.includes("search") || lowered.includes("grep")) return "search";
|
|
5253
|
+
if (lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered === "terminal" || lowered.includes("run_")) return "bash";
|
|
5254
|
+
if (lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change" || lowered === "multi_replace" || lowered.includes("write_file") || lowered.includes("edit_file") || lowered.includes("apply_patch")) return "edit";
|
|
5255
|
+
if (lowered === "read_file" || lowered === "read" || lowered === "view_file" || lowered.includes("read_file") || lowered.startsWith("read_")) return "read";
|
|
5256
|
+
return "other";
|
|
5257
|
+
}
|
|
5258
|
+
function toneRank(tone) {
|
|
5259
|
+
if (tone === "error") return 0;
|
|
5260
|
+
if (tone === "cancelled") return 1;
|
|
5261
|
+
if (tone === "running") return 2;
|
|
5262
|
+
return 3;
|
|
5263
|
+
}
|
|
5264
|
+
function resolveActivityUnitKeys(card, family, callIndex) {
|
|
5265
|
+
if (family !== "read" && family !== "edit" && family !== "directory") return [`call:${callIndex}`];
|
|
5266
|
+
const paths = card.fileOperation?.blocks.map(({ path }) => path.trim()).filter(Boolean);
|
|
5267
|
+
const summary = card.summary?.replace(/^(path|file):\s*/i, "").trim();
|
|
5268
|
+
return paths?.length ? paths : [summary || `call:${callIndex}`];
|
|
5269
|
+
}
|
|
5270
|
+
/**
|
|
5271
|
+
* Natural category+count phrases only.
|
|
5272
|
+
* Examples: `Read 3 files`, `运行 2 条命令`, `搜索 2 次`.
|
|
5273
|
+
* No command/path payload.
|
|
5274
|
+
*/
|
|
5275
|
+
function formatSegment(segment, labels) {
|
|
5276
|
+
const template = labels.segmentTemplates[segment.family];
|
|
5277
|
+
const body = segment.count === 1 ? template.one : template.other.split("{count}").join(String(segment.count));
|
|
5278
|
+
if (segment.tone === "error") return `${body} ${labels.failedLabel}`;
|
|
5279
|
+
if (segment.tone === "cancelled") return `${body} ${labels.cancelledLabel}`;
|
|
5280
|
+
return body;
|
|
5281
|
+
}
|
|
5282
|
+
function buildToolActivitySegments(cards) {
|
|
5283
|
+
const order = [];
|
|
5284
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
5285
|
+
for (const [callIndex, card] of cards.entries()) {
|
|
5286
|
+
const family = resolveToolActivityFamily(card.toolName);
|
|
5287
|
+
const unitKeys = resolveActivityUnitKeys(card, family, callIndex);
|
|
5288
|
+
const existing = byFamily.get(family);
|
|
5289
|
+
if (!existing) {
|
|
5290
|
+
order.push(family);
|
|
5291
|
+
byFamily.set(family, {
|
|
5292
|
+
units: new Set(unitKeys),
|
|
5293
|
+
tone: card.statusTone
|
|
5294
|
+
});
|
|
5295
|
+
continue;
|
|
5296
|
+
}
|
|
5297
|
+
unitKeys.forEach((unitKey) => existing.units.add(unitKey));
|
|
5298
|
+
if (toneRank(card.statusTone) < toneRank(existing.tone)) existing.tone = card.statusTone;
|
|
5299
|
+
}
|
|
5300
|
+
const segments = order.map((family) => {
|
|
5301
|
+
const entry = byFamily.get(family);
|
|
5302
|
+
return {
|
|
5303
|
+
family,
|
|
5304
|
+
count: entry.units.size,
|
|
5305
|
+
tone: entry.tone
|
|
5306
|
+
};
|
|
5307
|
+
});
|
|
5308
|
+
segments.sort((left, right) => {
|
|
5309
|
+
const toneDelta = toneRank(left.tone) - toneRank(right.tone);
|
|
5310
|
+
if (toneDelta !== 0) return toneDelta;
|
|
5311
|
+
return order.indexOf(left.family) - order.indexOf(right.family);
|
|
5312
|
+
});
|
|
5313
|
+
return segments;
|
|
5314
|
+
}
|
|
5315
|
+
function formatToolActivityGroupLabel(params) {
|
|
5316
|
+
const { labels, segments } = params;
|
|
5317
|
+
if (segments.length === 0) return "";
|
|
5318
|
+
const visible = segments.slice(0, MAX_SUMMARY_SEGMENTS);
|
|
5319
|
+
const hiddenCount = segments.length - visible.length;
|
|
5320
|
+
const parts = visible.map((segment) => formatSegment(segment, labels));
|
|
5321
|
+
if (hiddenCount > 0) parts.push(`+${hiddenCount}`);
|
|
5322
|
+
return parts.join(" · ");
|
|
5323
|
+
}
|
|
5324
|
+
/**
|
|
5325
|
+
* Group tool cards separated only by reasoning into one visible workflow.
|
|
5326
|
+
* Markdown, files and unknown parts still break the run.
|
|
5327
|
+
* Single tool-cards stay ungrouped so they keep their native card UI.
|
|
5328
|
+
*/
|
|
5329
|
+
function groupConsecutiveToolParts(parts, labels) {
|
|
5330
|
+
const blocks = [];
|
|
5331
|
+
let index = 0;
|
|
5332
|
+
while (index < parts.length) {
|
|
5333
|
+
const part = parts[index];
|
|
5334
|
+
if (part.type !== "tool-card") {
|
|
5335
|
+
blocks.push({
|
|
5336
|
+
kind: "part",
|
|
5337
|
+
key: `part-${index}`,
|
|
5338
|
+
index,
|
|
5339
|
+
part
|
|
5340
|
+
});
|
|
5341
|
+
index += 1;
|
|
5342
|
+
continue;
|
|
5343
|
+
}
|
|
5344
|
+
const startIndex = index;
|
|
5345
|
+
const candidateParts = [];
|
|
5346
|
+
let lastToolOffset = -1;
|
|
5347
|
+
while (index < parts.length && (parts[index]?.type === "tool-card" || parts[index]?.type === "reasoning")) {
|
|
5348
|
+
const candidate = parts[index];
|
|
5349
|
+
candidateParts.push(candidate);
|
|
5350
|
+
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5351
|
+
index += 1;
|
|
5352
|
+
}
|
|
5353
|
+
const toolParts = candidateParts.filter(isToolCardPart);
|
|
5354
|
+
if (toolParts.length === 1) {
|
|
5355
|
+
blocks.push({
|
|
5356
|
+
kind: "part",
|
|
5357
|
+
key: `part-${startIndex}`,
|
|
5358
|
+
index: startIndex,
|
|
5359
|
+
part
|
|
5360
|
+
});
|
|
5361
|
+
index = startIndex + 1;
|
|
5362
|
+
continue;
|
|
5363
|
+
}
|
|
5364
|
+
const groupedParts = candidateParts.slice(0, lastToolOffset + 1);
|
|
5365
|
+
index = startIndex + groupedParts.length;
|
|
5366
|
+
const segments = buildToolActivitySegments(toolParts.map((toolPart) => toolPart.card));
|
|
5367
|
+
const label = formatToolActivityGroupLabel({
|
|
5368
|
+
segments,
|
|
5369
|
+
labels
|
|
5370
|
+
});
|
|
5371
|
+
blocks.push({
|
|
5372
|
+
kind: "tool-group",
|
|
5373
|
+
key: `tool-group-${startIndex}`,
|
|
5374
|
+
group: {
|
|
5375
|
+
key: `tool-group-${startIndex}`,
|
|
5376
|
+
startIndex,
|
|
5377
|
+
endIndex: index - 1,
|
|
5378
|
+
parts: groupedParts,
|
|
5379
|
+
segments,
|
|
5380
|
+
label
|
|
5381
|
+
}
|
|
5382
|
+
});
|
|
5383
|
+
}
|
|
5384
|
+
return blocks;
|
|
5385
|
+
}
|
|
5386
|
+
//#endregion
|
|
4487
5387
|
//#region src/components/chat/ui/chat-message-list/chat-message.tsx
|
|
5388
|
+
const DEFAULT_TOOL_ACTIVITY_LABELS = {
|
|
5389
|
+
segmentTemplates: {
|
|
5390
|
+
read: {
|
|
5391
|
+
one: "Read 1 file",
|
|
5392
|
+
other: "Read {count} files"
|
|
5393
|
+
},
|
|
5394
|
+
edit: {
|
|
5395
|
+
one: "Edit 1 file",
|
|
5396
|
+
other: "Edit {count} files"
|
|
5397
|
+
},
|
|
5398
|
+
directory: {
|
|
5399
|
+
one: "View 1 directory",
|
|
5400
|
+
other: "View {count} directories"
|
|
5401
|
+
},
|
|
5402
|
+
search: {
|
|
5403
|
+
one: "Search 1 time",
|
|
5404
|
+
other: "Search {count} times"
|
|
5405
|
+
},
|
|
5406
|
+
bash: {
|
|
5407
|
+
one: "Run 1 command",
|
|
5408
|
+
other: "Run {count} commands"
|
|
5409
|
+
},
|
|
5410
|
+
web: {
|
|
5411
|
+
one: "Open 1 page",
|
|
5412
|
+
other: "Open {count} pages"
|
|
5413
|
+
},
|
|
5414
|
+
agent: {
|
|
5415
|
+
one: "Start 1 subtask",
|
|
5416
|
+
other: "Start {count} subtasks"
|
|
5417
|
+
},
|
|
5418
|
+
panel: {
|
|
5419
|
+
one: "Show 1 result",
|
|
5420
|
+
other: "Show {count} results"
|
|
5421
|
+
},
|
|
5422
|
+
other: {
|
|
5423
|
+
one: "Use 1 tool",
|
|
5424
|
+
other: "Use {count} tools"
|
|
5425
|
+
}
|
|
5426
|
+
},
|
|
5427
|
+
failedLabel: "failed",
|
|
5428
|
+
cancelledLabel: "cancelled"
|
|
5429
|
+
};
|
|
5430
|
+
function resolveToolActivityLabels(texts) {
|
|
5431
|
+
return {
|
|
5432
|
+
segmentTemplates: {
|
|
5433
|
+
...DEFAULT_TOOL_ACTIVITY_LABELS.segmentTemplates,
|
|
5434
|
+
...texts.toolActivitySegmentTemplates ?? {}
|
|
5435
|
+
},
|
|
5436
|
+
failedLabel: texts.toolActivityFailedLabel ?? DEFAULT_TOOL_ACTIVITY_LABELS.failedLabel,
|
|
5437
|
+
cancelledLabel: texts.toolActivityCancelledLabel ?? DEFAULT_TOOL_ACTIVITY_LABELS.cancelledLabel
|
|
5438
|
+
};
|
|
5439
|
+
}
|
|
4488
5440
|
function isMessageInProgress(status) {
|
|
4489
5441
|
return status === "pending" || status === "streaming";
|
|
4490
5442
|
}
|
|
@@ -4504,7 +5456,7 @@ function splitAssistantProcess(message) {
|
|
|
4504
5456
|
finalParts: message.parts.slice(lastProcessPartIndex + 1)
|
|
4505
5457
|
};
|
|
4506
5458
|
}
|
|
4507
|
-
function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFileOpen, onToolAction, part, renderInlineDisplay, renderPanelAppCard, renderToolAgent, role, texts }) {
|
|
5459
|
+
function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolAction, part, renderInlineDisplay, renderPanelAppCard, renderToolAgent, resolveFileContentUrl, role, texts }) {
|
|
4508
5460
|
const { type } = part;
|
|
4509
5461
|
if (type === "markdown") {
|
|
4510
5462
|
const { inlineTokens, text } = part;
|
|
@@ -4514,6 +5466,8 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4514
5466
|
texts,
|
|
4515
5467
|
inlineTokens,
|
|
4516
5468
|
onFileOpen,
|
|
5469
|
+
onInlineTokenClick,
|
|
5470
|
+
resolveFileContentUrl,
|
|
4517
5471
|
renderInlineDisplay
|
|
4518
5472
|
}, `markdown-${index}`);
|
|
4519
5473
|
}
|
|
@@ -4522,21 +5476,23 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4522
5476
|
return /* @__PURE__ */ jsx(ChatReasoningBlock, {
|
|
4523
5477
|
label,
|
|
4524
5478
|
text,
|
|
5479
|
+
characterCountTemplates: texts.reasoningCharacterCountTemplates,
|
|
4525
5480
|
isUser,
|
|
4526
5481
|
isInProgress: isInProgress && isLastPart
|
|
4527
5482
|
}, `reasoning-${index}`);
|
|
4528
5483
|
}
|
|
4529
5484
|
if (type === "tool-card") {
|
|
4530
5485
|
const { card } = part;
|
|
4531
|
-
return /* @__PURE__ */
|
|
4532
|
-
className: "
|
|
4533
|
-
children: /* @__PURE__ */ jsx(ChatToolCard, {
|
|
5486
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5487
|
+
className: "relative min-w-0",
|
|
5488
|
+
children: [/* @__PURE__ */ jsx(ChatProcessWorkflowRail, { position: "single" }), /* @__PURE__ */ jsx(ChatToolCard, {
|
|
4534
5489
|
card,
|
|
5490
|
+
toolStatusLabels: texts.toolStatusLabels,
|
|
4535
5491
|
onToolAction,
|
|
4536
5492
|
onFileOpen,
|
|
4537
5493
|
renderToolAgent,
|
|
4538
5494
|
renderPanelAppCard
|
|
4539
|
-
})
|
|
5495
|
+
})]
|
|
4540
5496
|
}, `tool-${index}`);
|
|
4541
5497
|
}
|
|
4542
5498
|
if (type === "file") {
|
|
@@ -4544,7 +5500,8 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4544
5500
|
return /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
4545
5501
|
file,
|
|
4546
5502
|
isUser,
|
|
4547
|
-
texts
|
|
5503
|
+
texts,
|
|
5504
|
+
onOpen: onAttachmentOpen
|
|
4548
5505
|
}, `file-${index}`);
|
|
4549
5506
|
}
|
|
4550
5507
|
if (type === "unknown") {
|
|
@@ -4566,49 +5523,135 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4566
5523
|
}
|
|
4567
5524
|
return null;
|
|
4568
5525
|
}
|
|
4569
|
-
|
|
5526
|
+
function renderMessageParts(params) {
|
|
5527
|
+
const { isInProgress, isUser, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolActivityOpenChange, onToolAction, openToolGroupKeys, parts, renderInlineDisplay, renderPanelAppCard, renderToolAgent, resolveFileContentUrl, role, texts, indexOffset = 0 } = params;
|
|
5528
|
+
return groupConsecutiveImageFileBlocks(groupConsecutiveToolParts(parts, resolveToolActivityLabels(texts))).map((block) => {
|
|
5529
|
+
if (block.kind === "image-group") return /* @__PURE__ */ jsx(ChatMessageImageRow, {
|
|
5530
|
+
group: block,
|
|
5531
|
+
indexOffset,
|
|
5532
|
+
isUser,
|
|
5533
|
+
texts,
|
|
5534
|
+
onOpen: onAttachmentOpen
|
|
5535
|
+
}, `${block.key}-${indexOffset}`);
|
|
5536
|
+
if (block.kind === "tool-group") return /* @__PURE__ */ jsx(ChatToolActivityGroup, {
|
|
5537
|
+
group: block.group,
|
|
5538
|
+
open: openToolGroupKeys.has(block.group.key),
|
|
5539
|
+
isUser,
|
|
5540
|
+
reasoningCharacterCountTemplates: texts.reasoningCharacterCountTemplates,
|
|
5541
|
+
toolStatusLabels: texts.toolStatusLabels,
|
|
5542
|
+
onToolAction,
|
|
5543
|
+
onFileOpen,
|
|
5544
|
+
renderToolAgent,
|
|
5545
|
+
renderPanelAppCard,
|
|
5546
|
+
onOpenChange: (open) => onToolActivityOpenChange(block.group.key, open)
|
|
5547
|
+
}, block.key);
|
|
5548
|
+
return renderChatMessagePart({
|
|
5549
|
+
part: block.part,
|
|
5550
|
+
index: indexOffset + block.index,
|
|
5551
|
+
role,
|
|
5552
|
+
isUser,
|
|
5553
|
+
isInProgress,
|
|
5554
|
+
isLastPart: indexOffset + block.index === indexOffset + parts.length - 1,
|
|
5555
|
+
texts,
|
|
5556
|
+
onToolAction,
|
|
5557
|
+
onFileOpen,
|
|
5558
|
+
onAttachmentOpen,
|
|
5559
|
+
onInlineTokenClick,
|
|
5560
|
+
resolveFileContentUrl,
|
|
5561
|
+
renderInlineDisplay,
|
|
5562
|
+
renderToolAgent,
|
|
5563
|
+
renderPanelAppCard
|
|
5564
|
+
});
|
|
5565
|
+
});
|
|
5566
|
+
}
|
|
5567
|
+
const ChatMessage = memo(function ChatMessage({ layout, message, texts, onToolAction, onFileOpen, onAttachmentOpen, onInlineTokenClick, renderInlineDisplay, renderToolAgent, renderPanelAppCard, resolveFileContentUrl }) {
|
|
4570
5568
|
const { role } = message;
|
|
4571
5569
|
const isUser = role === "user";
|
|
5570
|
+
const isFlat = layout === "flat" && !isUser;
|
|
4572
5571
|
const isInProgress = isMessageInProgress(message.status);
|
|
4573
5572
|
const processSplit = splitAssistantProcess(message);
|
|
4574
|
-
const
|
|
4575
|
-
|
|
4576
|
-
|
|
4577
|
-
|
|
4578
|
-
|
|
4579
|
-
|
|
4580
|
-
|
|
4581
|
-
|
|
4582
|
-
|
|
4583
|
-
|
|
4584
|
-
|
|
4585
|
-
renderToolAgent,
|
|
4586
|
-
renderPanelAppCard
|
|
4587
|
-
});
|
|
5573
|
+
const [processOpen, setProcessOpen] = useState(false);
|
|
5574
|
+
const [openToolGroupKeys, setOpenToolGroupKeys] = useState(() => /* @__PURE__ */ new Set());
|
|
5575
|
+
const handleToolActivityOpenChange = (groupKey, open) => {
|
|
5576
|
+
setOpenToolGroupKeys((current) => {
|
|
5577
|
+
const next = new Set(current);
|
|
5578
|
+
if (open) next.add(groupKey);
|
|
5579
|
+
else next.delete(groupKey);
|
|
5580
|
+
return next;
|
|
5581
|
+
});
|
|
5582
|
+
if (open) setProcessOpen(true);
|
|
5583
|
+
};
|
|
4588
5584
|
return /* @__PURE__ */ jsx("div", {
|
|
4589
|
-
|
|
5585
|
+
"data-chat-message-surface": isFlat ? "flat" : "card",
|
|
5586
|
+
className: cn("max-w-full has-[[data-chat-message-wide-content=true]]:w-full", isFlat ? "block w-full text-foreground" : "inline-block w-fit rounded-2xl border px-4 shadow-sm", !isFlat && (isUser ? "nextclaw-chat-message-user rounded-[1.45rem] border-primary bg-primary py-2.5 text-primary-foreground shadow-none" : role === "assistant" ? "border-border bg-card pb-3 pt-4 text-card-foreground" : "border-border bg-muted/45 py-3 text-foreground")),
|
|
4590
5587
|
children: /* @__PURE__ */ jsx("div", {
|
|
4591
|
-
className: "space-y-
|
|
4592
|
-
children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("
|
|
4593
|
-
className: "group/process
|
|
4594
|
-
children: [/* @__PURE__ */
|
|
4595
|
-
className: "
|
|
4596
|
-
children:
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
5588
|
+
className: "space-y-0",
|
|
5589
|
+
children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
5590
|
+
className: "group/process",
|
|
5591
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
5592
|
+
className: "mb-2 border-b border-border/60 pb-2",
|
|
5593
|
+
children: /* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
5594
|
+
openGroup: "process",
|
|
5595
|
+
open: processOpen,
|
|
5596
|
+
icon: ListChecks,
|
|
5597
|
+
leadingIconClassName: "bg-card",
|
|
5598
|
+
label: message.processSummary?.label,
|
|
5599
|
+
onClick: () => setProcessOpen((current) => !current)
|
|
5600
|
+
})
|
|
5601
|
+
}), processOpen ? /* @__PURE__ */ jsx("div", {
|
|
5602
|
+
className: "space-y-0",
|
|
5603
|
+
children: renderMessageParts({
|
|
5604
|
+
parts: processSplit.processParts,
|
|
5605
|
+
role,
|
|
5606
|
+
isUser,
|
|
5607
|
+
isInProgress,
|
|
5608
|
+
texts,
|
|
5609
|
+
onToolAction,
|
|
5610
|
+
onFileOpen,
|
|
5611
|
+
onAttachmentOpen,
|
|
5612
|
+
onInlineTokenClick,
|
|
5613
|
+
resolveFileContentUrl,
|
|
5614
|
+
openToolGroupKeys,
|
|
5615
|
+
onToolActivityOpenChange: handleToolActivityOpenChange,
|
|
5616
|
+
renderInlineDisplay,
|
|
5617
|
+
renderToolAgent,
|
|
5618
|
+
renderPanelAppCard
|
|
5619
|
+
})
|
|
5620
|
+
}) : null]
|
|
5621
|
+
}), renderMessageParts({
|
|
5622
|
+
parts: processSplit.finalParts,
|
|
5623
|
+
role,
|
|
5624
|
+
isUser,
|
|
5625
|
+
isInProgress,
|
|
5626
|
+
texts,
|
|
5627
|
+
onToolAction,
|
|
5628
|
+
onFileOpen,
|
|
5629
|
+
onAttachmentOpen,
|
|
5630
|
+
onInlineTokenClick,
|
|
5631
|
+
resolveFileContentUrl,
|
|
5632
|
+
openToolGroupKeys,
|
|
5633
|
+
onToolActivityOpenChange: handleToolActivityOpenChange,
|
|
5634
|
+
renderInlineDisplay,
|
|
5635
|
+
renderToolAgent,
|
|
5636
|
+
renderPanelAppCard,
|
|
5637
|
+
indexOffset: processSplit.processParts.length
|
|
5638
|
+
})] }) : renderMessageParts({
|
|
5639
|
+
parts: message.parts,
|
|
5640
|
+
role,
|
|
5641
|
+
isUser,
|
|
5642
|
+
isInProgress,
|
|
5643
|
+
texts,
|
|
5644
|
+
onToolAction,
|
|
5645
|
+
onFileOpen,
|
|
5646
|
+
onAttachmentOpen,
|
|
5647
|
+
onInlineTokenClick,
|
|
5648
|
+
resolveFileContentUrl,
|
|
5649
|
+
openToolGroupKeys,
|
|
5650
|
+
onToolActivityOpenChange: handleToolActivityOpenChange,
|
|
5651
|
+
renderInlineDisplay,
|
|
5652
|
+
renderToolAgent,
|
|
5653
|
+
renderPanelAppCard
|
|
5654
|
+
})
|
|
4612
5655
|
})
|
|
4613
5656
|
});
|
|
4614
5657
|
});
|
|
@@ -4645,7 +5688,6 @@ const TYPING_TEXT_SHEEN_CSS = `
|
|
|
4645
5688
|
background-position: -60% 0;
|
|
4646
5689
|
}
|
|
4647
5690
|
}
|
|
4648
|
-
|
|
4649
5691
|
.nextclaw-chat-typing-indicator__text {
|
|
4650
5692
|
background-image: linear-gradient(
|
|
4651
5693
|
100deg,
|
|
@@ -4699,16 +5741,17 @@ function ChatMessageTypingFooter() {
|
|
|
4699
5741
|
})
|
|
4700
5742
|
});
|
|
4701
5743
|
}
|
|
4702
|
-
function ChatTypingIndicator({ label }) {
|
|
5744
|
+
function ChatTypingIndicator({ label, layout }) {
|
|
4703
5745
|
return /* @__PURE__ */ jsxs("div", {
|
|
4704
|
-
|
|
5746
|
+
"data-chat-message-surface": layout === "flat" ? "flat" : "card",
|
|
5747
|
+
className: cn("text-sm text-muted-foreground", layout === "flat" ? "py-1" : "rounded-2xl border border-border bg-card px-4 py-3 shadow-sm"),
|
|
4705
5748
|
children: [/* @__PURE__ */ jsx("style", { children: TYPING_TEXT_SHEEN_CSS }), /* @__PURE__ */ jsx("span", {
|
|
4706
5749
|
className: "nextclaw-chat-typing-indicator__text",
|
|
4707
5750
|
children: label
|
|
4708
5751
|
})]
|
|
4709
5752
|
});
|
|
4710
5753
|
}
|
|
4711
|
-
function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAction, renderInlineDisplay, renderPanelAppCard, renderToolAgent, texts }) {
|
|
5754
|
+
function ChatMessageList({ className, isSending, layout = "card", messages, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolAction, renderInlineDisplay, renderPanelAppCard, renderToolAgent, resolveFileContentUrl, texts }) {
|
|
4712
5755
|
const visibleMessages = messages.filter(hasRenderableMessageContent);
|
|
4713
5756
|
const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
|
|
4714
5757
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -4716,21 +5759,63 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
|
|
|
4716
5759
|
children: [visibleMessages.map((message) => {
|
|
4717
5760
|
const isUser = message.role === "user";
|
|
4718
5761
|
const isGenerating = !isUser && (message.status === "streaming" || message.status === "pending");
|
|
5762
|
+
const content = /* @__PURE__ */ jsx(ChatMessage, {
|
|
5763
|
+
layout,
|
|
5764
|
+
message,
|
|
5765
|
+
texts,
|
|
5766
|
+
onToolAction,
|
|
5767
|
+
onFileOpen,
|
|
5768
|
+
onAttachmentOpen,
|
|
5769
|
+
onInlineTokenClick,
|
|
5770
|
+
resolveFileContentUrl,
|
|
5771
|
+
renderInlineDisplay,
|
|
5772
|
+
renderToolAgent,
|
|
5773
|
+
renderPanelAppCard
|
|
5774
|
+
});
|
|
5775
|
+
if (layout === "flat" && !isUser) return /* @__PURE__ */ jsxs("article", {
|
|
5776
|
+
"data-chat-message-layout": "flat",
|
|
5777
|
+
className: "w-full min-w-0 space-y-2",
|
|
5778
|
+
children: [
|
|
5779
|
+
/* @__PURE__ */ jsxs("div", {
|
|
5780
|
+
"data-chat-message-header": "flat",
|
|
5781
|
+
className: "flex min-w-0 items-center gap-2.5",
|
|
5782
|
+
children: [/* @__PURE__ */ jsx(ChatMessageAvatar, {
|
|
5783
|
+
role: message.role,
|
|
5784
|
+
size: "compact"
|
|
5785
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
5786
|
+
className: "truncate text-sm font-semibold text-foreground",
|
|
5787
|
+
children: message.roleLabel
|
|
5788
|
+
})]
|
|
5789
|
+
}),
|
|
5790
|
+
/* @__PURE__ */ jsx("div", {
|
|
5791
|
+
"data-chat-message-body": "flat",
|
|
5792
|
+
className: "min-w-0 w-full",
|
|
5793
|
+
children: content
|
|
5794
|
+
}),
|
|
5795
|
+
isGenerating ? /* @__PURE__ */ jsx("div", {
|
|
5796
|
+
className: "w-full",
|
|
5797
|
+
children: /* @__PURE__ */ jsx(ChatMessageTypingFooter, {})
|
|
5798
|
+
}) : /* @__PURE__ */ jsxs("div", {
|
|
5799
|
+
"data-chat-message-footer": "flat",
|
|
5800
|
+
className: "flex items-center gap-2",
|
|
5801
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
5802
|
+
className: "px-1 text-[11px] leading-4 text-muted-foreground",
|
|
5803
|
+
children: message.timestampLabel
|
|
5804
|
+
}), /* @__PURE__ */ jsx(ChatMessageActionCopy, {
|
|
5805
|
+
message,
|
|
5806
|
+
texts
|
|
5807
|
+
})]
|
|
5808
|
+
})
|
|
5809
|
+
]
|
|
5810
|
+
}, message.id);
|
|
4719
5811
|
return /* @__PURE__ */ jsxs("div", {
|
|
5812
|
+
"data-chat-message-layout": "card",
|
|
4720
5813
|
className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"),
|
|
4721
5814
|
children: [
|
|
4722
5815
|
!isUser ? /* @__PURE__ */ jsx(ChatMessageAvatar, { role: message.role }) : null,
|
|
4723
5816
|
/* @__PURE__ */ jsxs("div", {
|
|
4724
5817
|
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"),
|
|
4725
|
-
children: [/* @__PURE__ */ jsx(
|
|
4726
|
-
message,
|
|
4727
|
-
texts,
|
|
4728
|
-
onToolAction,
|
|
4729
|
-
onFileOpen,
|
|
4730
|
-
renderInlineDisplay,
|
|
4731
|
-
renderToolAgent,
|
|
4732
|
-
renderPanelAppCard
|
|
4733
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
5818
|
+
children: [content, /* @__PURE__ */ jsx("div", {
|
|
4734
5819
|
className: cn("flex items-center gap-2", isUser && "justify-end"),
|
|
4735
5820
|
children: isGenerating ? /* @__PURE__ */ jsx(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
4736
5821
|
className: cn("px-1 text-[11px] leading-4 text-muted-foreground", isUser ? "text-right" : "text-left"),
|
|
@@ -4749,8 +5834,15 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
|
|
|
4749
5834
|
]
|
|
4750
5835
|
}, message.id);
|
|
4751
5836
|
}), isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs("div", {
|
|
5837
|
+
"data-chat-message-layout": layout,
|
|
4752
5838
|
className: "flex justify-start gap-3",
|
|
4753
|
-
children: [/* @__PURE__ */ jsx(ChatMessageAvatar, {
|
|
5839
|
+
children: [/* @__PURE__ */ jsx(ChatMessageAvatar, {
|
|
5840
|
+
role: "assistant",
|
|
5841
|
+
size: layout === "flat" ? "compact" : "default"
|
|
5842
|
+
}), /* @__PURE__ */ jsx(ChatTypingIndicator, {
|
|
5843
|
+
label: texts.typingLabel,
|
|
5844
|
+
layout
|
|
5845
|
+
})]
|
|
4754
5846
|
}) : null]
|
|
4755
5847
|
});
|
|
4756
5848
|
}
|