@nextclaw/agent-chat-ui 0.6.1 → 0.6.3
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 +66 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1219 -314
- 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, Loader2, MessageSquare, Minus, Paperclip, Puzzle, Search, Send, Settings, Sparkles, Star, Terminal, User, 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: "/"
|
|
@@ -688,7 +689,7 @@ function ChatInputSurfaceHost(props) {
|
|
|
688
689
|
}
|
|
689
690
|
//#endregion
|
|
690
691
|
//#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
|
|
692
|
+
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
693
|
variants: {
|
|
693
694
|
variant: {
|
|
694
695
|
default: "bg-primary text-primary-foreground hover:bg-primary-600 active:bg-primary-700 shadow-sm",
|
|
@@ -1068,7 +1069,7 @@ function AccessoryIcon({ icon }) {
|
|
|
1068
1069
|
return icon === "paperclip" ? /* @__PURE__ */ jsx(Paperclip, { className: "h-4 w-4" }) : /* @__PURE__ */ jsx(ToolbarIcon, { icon });
|
|
1069
1070
|
}
|
|
1070
1071
|
const TRIGGER_WIDTH_BY_KEY = {
|
|
1071
|
-
model: "min-w-0 max-w-
|
|
1072
|
+
model: "min-w-0 max-w-[18rem]",
|
|
1072
1073
|
"session-type": "shrink-0",
|
|
1073
1074
|
thinking: "shrink-0"
|
|
1074
1075
|
};
|
|
@@ -1095,11 +1096,11 @@ function ToolbarSelectTriggerContent({ item }) {
|
|
|
1095
1096
|
children: [
|
|
1096
1097
|
/* @__PURE__ */ jsx(ToolbarIcon, { icon: item.icon }),
|
|
1097
1098
|
/* @__PURE__ */ jsx("span", {
|
|
1098
|
-
className: "nextclaw-chat-toolbar-mobile-label truncate
|
|
1099
|
+
className: "nextclaw-chat-toolbar-mobile-label truncate sm:hidden [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
|
|
1099
1100
|
children: mobileSelectedLabel
|
|
1100
1101
|
}),
|
|
1101
1102
|
/* @__PURE__ */ jsx("span", {
|
|
1102
|
-
className: "nextclaw-chat-toolbar-label hidden truncate
|
|
1103
|
+
className: "nextclaw-chat-toolbar-label hidden truncate sm:inline [@container_nextclaw-chat-input-bar_(max-width:440px)]:hidden",
|
|
1103
1104
|
children: item.selectedLabel
|
|
1104
1105
|
})
|
|
1105
1106
|
]
|
|
@@ -1217,12 +1218,12 @@ function ToolbarSearchableSelect({ item }) {
|
|
|
1217
1218
|
type: "button",
|
|
1218
1219
|
"aria-label": actionLabel,
|
|
1219
1220
|
"aria-pressed": isActive,
|
|
1220
|
-
className: "mr-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-card hover:text-
|
|
1221
|
+
className: "mr-1 inline-flex h-7 w-7 shrink-0 items-center justify-center rounded-md text-muted-foreground/70 transition-colors hover:bg-card hover:text-foreground focus:outline-none focus:ring-1 focus:ring-primary/40",
|
|
1221
1222
|
onClick: (event) => {
|
|
1222
1223
|
event.stopPropagation();
|
|
1223
1224
|
action.onToggle(option.value, !isActive);
|
|
1224
1225
|
},
|
|
1225
|
-
children: /* @__PURE__ */ jsx(Star, { className: `h-3.5 w-3.5 ${isActive ? "fill-
|
|
1226
|
+
children: /* @__PURE__ */ jsx(Star, { className: `h-3.5 w-3.5 ${isActive ? "fill-current text-foreground" : ""}` })
|
|
1226
1227
|
})
|
|
1227
1228
|
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
1228
1229
|
side: "left",
|
|
@@ -1274,7 +1275,7 @@ function ToolbarSelect({ item }) {
|
|
|
1274
1275
|
})]
|
|
1275
1276
|
});
|
|
1276
1277
|
}
|
|
1277
|
-
function ChatInputBarToolbar({ actions, accessories, selects, skillPicker }) {
|
|
1278
|
+
function ChatInputBarToolbar({ actions, accessories, selects, skillPicker, trailingSelects = [] }) {
|
|
1278
1279
|
const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
|
|
1279
1280
|
return /* @__PURE__ */ jsxs("div", {
|
|
1280
1281
|
className: "flex flex-wrap items-end justify-between gap-2 px-3 pb-3",
|
|
@@ -1308,7 +1309,13 @@ function ChatInputBarToolbar({ actions, accessories, selects, skillPicker }) {
|
|
|
1308
1309
|
})] }) }, item.key);
|
|
1309
1310
|
})
|
|
1310
1311
|
]
|
|
1311
|
-
}), /* @__PURE__ */
|
|
1312
|
+
}), /* @__PURE__ */ jsxs("div", {
|
|
1313
|
+
className: "flex shrink-0 items-end gap-1",
|
|
1314
|
+
children: [trailingSelects.length > 0 ? /* @__PURE__ */ jsx("div", {
|
|
1315
|
+
className: "nextclaw-chat-toolbar-trailing-selects flex min-w-0 items-center gap-1",
|
|
1316
|
+
children: trailingSelects.map((item) => /* @__PURE__ */ jsx(ToolbarSelect, { item }, item.key))
|
|
1317
|
+
}) : null, /* @__PURE__ */ jsx(ChatInputBarActions, { ...actions })]
|
|
1318
|
+
})]
|
|
1312
1319
|
});
|
|
1313
1320
|
}
|
|
1314
1321
|
//#endregion
|
|
@@ -2413,15 +2420,15 @@ function resolveInlineTokenTone(kind) {
|
|
|
2413
2420
|
if (kind === "panel_app") return "panel_app";
|
|
2414
2421
|
return "default";
|
|
2415
2422
|
}
|
|
2416
|
-
function resolveInlineTokenBadgeClassName(tone, isUser) {
|
|
2417
|
-
if (tone === "skill") return
|
|
2418
|
-
if (tone === "panel_app") return isUser ? "border-primary-foreground/
|
|
2419
|
-
return isUser ? "border-primary-foreground/
|
|
2423
|
+
function resolveInlineTokenBadgeClassName(tone, isUser, interactive) {
|
|
2424
|
+
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");
|
|
2425
|
+
if (tone === "panel_app") return isUser ? "border-primary-foreground/40 bg-primary-foreground/22 text-primary-foreground" : "border-border bg-muted text-foreground";
|
|
2426
|
+
return isUser ? "border-primary-foreground/40 bg-primary-foreground/22 text-primary-foreground" : "border-border bg-muted text-muted-foreground";
|
|
2420
2427
|
}
|
|
2421
2428
|
function resolveInlineTokenIconClassName(tone, isUser) {
|
|
2422
|
-
if (tone === "skill") return
|
|
2423
|
-
if (tone === "panel_app") return isUser ? "text-primary-foreground/
|
|
2424
|
-
return isUser ? "text-primary-foreground/
|
|
2429
|
+
if (tone === "skill") return "text-current opacity-80";
|
|
2430
|
+
if (tone === "panel_app") return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2431
|
+
return isUser ? "text-primary-foreground/80" : "text-muted-foreground";
|
|
2425
2432
|
}
|
|
2426
2433
|
function renderInlineTokenIcon(tone) {
|
|
2427
2434
|
return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
|
|
@@ -2432,18 +2439,32 @@ function renderInlineTokenIcon(tone) {
|
|
|
2432
2439
|
className: "h-3 w-3"
|
|
2433
2440
|
});
|
|
2434
2441
|
}
|
|
2435
|
-
function ChatInlineTokenBadge({ kind, label, isUser }) {
|
|
2442
|
+
function ChatInlineTokenBadge({ kind, label, isUser, onClick }) {
|
|
2436
2443
|
const tone = resolveInlineTokenTone(kind);
|
|
2437
|
-
|
|
2438
|
-
|
|
2444
|
+
const interactive = Boolean(onClick);
|
|
2445
|
+
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));
|
|
2446
|
+
const content = /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
2447
|
+
className: cn("inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center", resolveInlineTokenIconClassName(tone, isUser)),
|
|
2448
|
+
children: renderInlineTokenIcon(tone)
|
|
2449
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
2450
|
+
className: "truncate",
|
|
2451
|
+
children: label
|
|
2452
|
+
})] });
|
|
2453
|
+
if (interactive) return /* @__PURE__ */ jsx("button", {
|
|
2454
|
+
type: "button",
|
|
2455
|
+
className,
|
|
2439
2456
|
title: label,
|
|
2440
|
-
|
|
2441
|
-
|
|
2442
|
-
|
|
2443
|
-
|
|
2444
|
-
|
|
2445
|
-
|
|
2446
|
-
|
|
2457
|
+
onClick: (event) => {
|
|
2458
|
+
event.preventDefault();
|
|
2459
|
+
event.stopPropagation();
|
|
2460
|
+
onClick?.();
|
|
2461
|
+
},
|
|
2462
|
+
children: content
|
|
2463
|
+
});
|
|
2464
|
+
return /* @__PURE__ */ jsx("span", {
|
|
2465
|
+
className,
|
|
2466
|
+
title: label,
|
|
2467
|
+
children: content
|
|
2447
2468
|
});
|
|
2448
2469
|
}
|
|
2449
2470
|
//#endregion
|
|
@@ -2649,7 +2670,7 @@ const REGISTERED_LANGUAGES = [
|
|
|
2649
2670
|
aliases: ["patch"]
|
|
2650
2671
|
}
|
|
2651
2672
|
];
|
|
2652
|
-
function escapeHtml(value) {
|
|
2673
|
+
function escapeHtml$1(value) {
|
|
2653
2674
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2654
2675
|
}
|
|
2655
2676
|
function normalizeLanguage(value) {
|
|
@@ -2680,7 +2701,7 @@ var ChatCodeSyntaxHighlighter = class {
|
|
|
2680
2701
|
return this.createPlainTextResult(code, normalizedLanguage || "text");
|
|
2681
2702
|
};
|
|
2682
2703
|
this.createPlainTextResult = (code, language) => ({
|
|
2683
|
-
html: escapeHtml(code),
|
|
2704
|
+
html: escapeHtml$1(code),
|
|
2684
2705
|
language,
|
|
2685
2706
|
highlighted: false
|
|
2686
2707
|
});
|
|
@@ -3048,7 +3069,7 @@ function readStringProp(props, key) {
|
|
|
3048
3069
|
const value = props[key];
|
|
3049
3070
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3050
3071
|
}
|
|
3051
|
-
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, renderInlineDisplay }) {
|
|
3072
|
+
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, onInlineTokenClick, renderInlineDisplay }) {
|
|
3052
3073
|
const isUser = role === "user";
|
|
3053
3074
|
const remarkPlugins = useMemo(() => inlineTokens && inlineTokens.length > 0 ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm], [inlineTokens]);
|
|
3054
3075
|
const markdownComponents = useMemo(() => ({
|
|
@@ -3062,7 +3083,13 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3062
3083
|
if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
|
|
3063
3084
|
kind,
|
|
3064
3085
|
label,
|
|
3065
|
-
isUser
|
|
3086
|
+
isUser,
|
|
3087
|
+
onClick: onInlineTokenClick ? () => onInlineTokenClick({
|
|
3088
|
+
kind,
|
|
3089
|
+
key,
|
|
3090
|
+
label,
|
|
3091
|
+
rawText
|
|
3092
|
+
}) : void 0
|
|
3066
3093
|
});
|
|
3067
3094
|
return /* @__PURE__ */ jsx("span", {
|
|
3068
3095
|
...rest,
|
|
@@ -3146,6 +3173,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3146
3173
|
inline,
|
|
3147
3174
|
isUser,
|
|
3148
3175
|
onFileOpen,
|
|
3176
|
+
onInlineTokenClick,
|
|
3149
3177
|
renderInlineDisplay,
|
|
3150
3178
|
texts
|
|
3151
3179
|
]);
|
|
@@ -3162,16 +3190,16 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3162
3190
|
//#endregion
|
|
3163
3191
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
|
|
3164
3192
|
const FILE_CATEGORY_TILE_CLASSES = {
|
|
3165
|
-
archive: "
|
|
3166
|
-
audio: "
|
|
3167
|
-
code: "
|
|
3168
|
-
data: "
|
|
3169
|
-
document: "
|
|
3170
|
-
generic: "
|
|
3171
|
-
image: "
|
|
3172
|
-
pdf: "
|
|
3173
|
-
sheet: "
|
|
3174
|
-
video: "
|
|
3193
|
+
archive: "bg-muted text-muted-foreground",
|
|
3194
|
+
audio: "bg-muted text-muted-foreground",
|
|
3195
|
+
code: "bg-muted text-muted-foreground",
|
|
3196
|
+
data: "bg-muted text-muted-foreground",
|
|
3197
|
+
document: "bg-muted text-muted-foreground",
|
|
3198
|
+
generic: "bg-muted text-muted-foreground",
|
|
3199
|
+
image: "bg-muted text-muted-foreground",
|
|
3200
|
+
pdf: "bg-muted text-muted-foreground",
|
|
3201
|
+
sheet: "bg-muted text-muted-foreground",
|
|
3202
|
+
video: "bg-muted text-muted-foreground"
|
|
3175
3203
|
};
|
|
3176
3204
|
const CODE_EXTENSIONS = new Set([
|
|
3177
3205
|
"c",
|
|
@@ -3381,77 +3409,166 @@ function readFileCategoryLabel(category, texts) {
|
|
|
3381
3409
|
}
|
|
3382
3410
|
function renderMetaLine(categoryLabel, sizeLabel, isUser) {
|
|
3383
3411
|
return /* @__PURE__ */ jsx("div", {
|
|
3384
|
-
className: cn("mt-
|
|
3412
|
+
className: cn("mt-0.5 text-[11px] leading-4", isUser ? "text-foreground/55" : "text-muted-foreground"),
|
|
3385
3413
|
children: sizeLabel ? `${categoryLabel} · ${sizeLabel}` : categoryLabel
|
|
3386
3414
|
});
|
|
3387
3415
|
}
|
|
3388
3416
|
function renderActionPill(label, isUser, isInteractive) {
|
|
3389
3417
|
return /* @__PURE__ */ jsx("span", {
|
|
3390
|
-
className: cn("inline-flex items-center rounded-
|
|
3418
|
+
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"),
|
|
3391
3419
|
children: label
|
|
3392
3420
|
});
|
|
3393
3421
|
}
|
|
3394
|
-
function
|
|
3422
|
+
function renderActionControl(params) {
|
|
3423
|
+
const { label, href, isUser, onOpen } = params;
|
|
3424
|
+
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");
|
|
3425
|
+
if (onOpen) return /* @__PURE__ */ jsx("button", {
|
|
3426
|
+
type: "button",
|
|
3427
|
+
className,
|
|
3428
|
+
onClick: (event) => {
|
|
3429
|
+
event.preventDefault();
|
|
3430
|
+
event.stopPropagation();
|
|
3431
|
+
onOpen();
|
|
3432
|
+
},
|
|
3433
|
+
children: label
|
|
3434
|
+
});
|
|
3435
|
+
if (!href) return renderActionPill(label, isUser, false);
|
|
3395
3436
|
return /* @__PURE__ */ jsx("a", {
|
|
3396
3437
|
href,
|
|
3397
3438
|
target: "_blank",
|
|
3398
3439
|
rel: "noreferrer",
|
|
3399
|
-
className
|
|
3440
|
+
className,
|
|
3400
3441
|
children: label
|
|
3401
3442
|
});
|
|
3402
3443
|
}
|
|
3403
3444
|
function FileCategoryGlyph({ category, isUser }) {
|
|
3404
3445
|
const Icon = FILE_CATEGORY_ICONS[category];
|
|
3405
3446
|
return /* @__PURE__ */ jsx("div", {
|
|
3406
|
-
className: cn("flex h-
|
|
3447
|
+
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]),
|
|
3407
3448
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3408
|
-
className: cn("h-
|
|
3409
|
-
strokeWidth: 2
|
|
3449
|
+
className: cn("h-4 w-4", isUser ? "text-foreground/70" : "text-current"),
|
|
3450
|
+
strokeWidth: 2
|
|
3410
3451
|
})
|
|
3411
3452
|
});
|
|
3412
3453
|
}
|
|
3413
|
-
function
|
|
3414
|
-
const
|
|
3454
|
+
function ChatMessageImageLightbox({ alt, closeLabel, onClose, src }) {
|
|
3455
|
+
const titleId = useId();
|
|
3456
|
+
useEffect(() => {
|
|
3457
|
+
const previousOverflow = document.body.style.overflow;
|
|
3458
|
+
document.body.style.overflow = "hidden";
|
|
3459
|
+
const onKeyDown = (event) => {
|
|
3460
|
+
if (event.key === "Escape") {
|
|
3461
|
+
event.preventDefault();
|
|
3462
|
+
onClose();
|
|
3463
|
+
}
|
|
3464
|
+
};
|
|
3465
|
+
window.addEventListener("keydown", onKeyDown);
|
|
3466
|
+
return () => {
|
|
3467
|
+
document.body.style.overflow = previousOverflow;
|
|
3468
|
+
window.removeEventListener("keydown", onKeyDown);
|
|
3469
|
+
};
|
|
3470
|
+
}, [onClose]);
|
|
3471
|
+
return createPortal(/* @__PURE__ */ jsxs("div", {
|
|
3472
|
+
role: "dialog",
|
|
3473
|
+
"aria-modal": "true",
|
|
3474
|
+
"aria-labelledby": titleId,
|
|
3475
|
+
className: "fixed inset-0 z-[var(--z-modal,10050)] flex items-center justify-center bg-black/80 p-4 backdrop-blur-[2px]",
|
|
3476
|
+
"data-testid": "chat-message-image-lightbox",
|
|
3477
|
+
onClick: onClose,
|
|
3478
|
+
children: [
|
|
3479
|
+
/* @__PURE__ */ jsx("span", {
|
|
3480
|
+
id: titleId,
|
|
3481
|
+
className: "sr-only",
|
|
3482
|
+
children: alt
|
|
3483
|
+
}),
|
|
3484
|
+
/* @__PURE__ */ jsx("button", {
|
|
3485
|
+
type: "button",
|
|
3486
|
+
"aria-label": closeLabel,
|
|
3487
|
+
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",
|
|
3488
|
+
onClick: (event) => {
|
|
3489
|
+
event.stopPropagation();
|
|
3490
|
+
onClose();
|
|
3491
|
+
},
|
|
3492
|
+
children: /* @__PURE__ */ jsx(X, {
|
|
3493
|
+
className: "h-4 w-4",
|
|
3494
|
+
strokeWidth: 2
|
|
3495
|
+
})
|
|
3496
|
+
}),
|
|
3497
|
+
/* @__PURE__ */ jsx("img", {
|
|
3498
|
+
src,
|
|
3499
|
+
alt,
|
|
3500
|
+
className: "max-h-[min(92vh,100%)] max-w-[min(96vw,100%)] object-contain shadow-2xl",
|
|
3501
|
+
onClick: (event) => event.stopPropagation()
|
|
3502
|
+
})
|
|
3503
|
+
]
|
|
3504
|
+
}), document.body);
|
|
3505
|
+
}
|
|
3506
|
+
function ChatMessageImagePreview({ expandLabel, closeLabel, file, sizeLabel }) {
|
|
3507
|
+
const [isExpanded, setIsExpanded] = useState(false);
|
|
3415
3508
|
if (!file.dataUrl) return null;
|
|
3416
|
-
|
|
3417
|
-
|
|
3418
|
-
|
|
3419
|
-
|
|
3420
|
-
|
|
3421
|
-
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
children:
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3509
|
+
const openLightbox = () => setIsExpanded(true);
|
|
3510
|
+
return /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
3511
|
+
className: "group/image relative w-fit max-w-full overflow-hidden rounded-xl",
|
|
3512
|
+
children: [
|
|
3513
|
+
/* @__PURE__ */ jsx("button", {
|
|
3514
|
+
type: "button",
|
|
3515
|
+
className: "block w-fit max-w-full text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
|
|
3516
|
+
onClick: openLightbox,
|
|
3517
|
+
onDoubleClick: (event) => {
|
|
3518
|
+
event.preventDefault();
|
|
3519
|
+
openLightbox();
|
|
3520
|
+
},
|
|
3521
|
+
"aria-label": expandLabel,
|
|
3522
|
+
children: /* @__PURE__ */ jsx("img", {
|
|
3523
|
+
src: file.dataUrl,
|
|
3524
|
+
alt: file.label,
|
|
3525
|
+
className: "block h-auto max-h-[26rem] max-w-full rounded-xl bg-transparent object-contain"
|
|
3526
|
+
})
|
|
3527
|
+
}),
|
|
3528
|
+
/* @__PURE__ */ jsx("button", {
|
|
3529
|
+
type: "button",
|
|
3530
|
+
"aria-label": expandLabel,
|
|
3531
|
+
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",
|
|
3532
|
+
onClick: (event) => {
|
|
3533
|
+
event.preventDefault();
|
|
3534
|
+
event.stopPropagation();
|
|
3535
|
+
openLightbox();
|
|
3536
|
+
},
|
|
3537
|
+
children: /* @__PURE__ */ jsx(Expand, {
|
|
3538
|
+
className: "h-3.5 w-3.5",
|
|
3539
|
+
strokeWidth: 2
|
|
3540
|
+
})
|
|
3541
|
+
}),
|
|
3542
|
+
sizeLabel ? /* @__PURE__ */ jsx("div", {
|
|
3543
|
+
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",
|
|
3544
|
+
children: /* @__PURE__ */ jsx("span", {
|
|
3545
|
+
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",
|
|
3434
3546
|
children: sizeLabel
|
|
3435
|
-
})
|
|
3436
|
-
})
|
|
3437
|
-
|
|
3438
|
-
})
|
|
3547
|
+
})
|
|
3548
|
+
}) : null
|
|
3549
|
+
]
|
|
3550
|
+
}), isExpanded ? /* @__PURE__ */ jsx(ChatMessageImageLightbox, {
|
|
3551
|
+
alt: file.label,
|
|
3552
|
+
closeLabel,
|
|
3553
|
+
onClose: () => setIsExpanded(false),
|
|
3554
|
+
src: file.dataUrl
|
|
3555
|
+
}) : null] });
|
|
3439
3556
|
}
|
|
3440
3557
|
function renderFileCardHeader(params) {
|
|
3441
3558
|
const { category, file, categoryLabel, sizeLabel, isUser, action } = params;
|
|
3442
3559
|
return /* @__PURE__ */ jsxs("div", {
|
|
3443
|
-
className: "flex items-center gap-
|
|
3560
|
+
className: "flex items-center gap-2.5 px-2.5 py-2",
|
|
3444
3561
|
children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
|
|
3445
3562
|
category,
|
|
3446
3563
|
isUser
|
|
3447
3564
|
}), /* @__PURE__ */ jsx("div", {
|
|
3448
3565
|
className: "min-w-0 flex-1",
|
|
3449
3566
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3450
|
-
className: "flex items-center gap-
|
|
3567
|
+
className: "flex items-center gap-2",
|
|
3451
3568
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3452
3569
|
className: "min-w-0 flex-1",
|
|
3453
3570
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3454
|
-
className: "truncate text-[
|
|
3571
|
+
className: "truncate text-[13px] font-medium leading-5",
|
|
3455
3572
|
children: file.label
|
|
3456
3573
|
}), renderMetaLine(categoryLabel, sizeLabel, isUser)]
|
|
3457
3574
|
}), action]
|
|
@@ -3460,7 +3577,7 @@ function renderFileCardHeader(params) {
|
|
|
3460
3577
|
});
|
|
3461
3578
|
}
|
|
3462
3579
|
function renderInlineMediaCard(params) {
|
|
3463
|
-
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel } = params;
|
|
3580
|
+
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel, onOpen } = params;
|
|
3464
3581
|
if (!file.dataUrl || category !== "audio" && category !== "video") return null;
|
|
3465
3582
|
const mediaMimeType = resolveRenderableMimeType(file);
|
|
3466
3583
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -3471,9 +3588,14 @@ function renderInlineMediaCard(params) {
|
|
|
3471
3588
|
categoryLabel,
|
|
3472
3589
|
sizeLabel,
|
|
3473
3590
|
isUser,
|
|
3474
|
-
action:
|
|
3591
|
+
action: renderActionControl({
|
|
3592
|
+
label: actionLabel,
|
|
3593
|
+
href: file.dataUrl,
|
|
3594
|
+
isUser,
|
|
3595
|
+
onOpen: onOpen ? () => onOpen(file) : void 0
|
|
3596
|
+
})
|
|
3475
3597
|
}), /* @__PURE__ */ jsx("div", {
|
|
3476
|
-
className: "px-
|
|
3598
|
+
className: "px-2.5 pb-2.5",
|
|
3477
3599
|
children: category === "audio" ? /* @__PURE__ */ jsx("audio", {
|
|
3478
3600
|
controls: true,
|
|
3479
3601
|
preload: "metadata",
|
|
@@ -3484,7 +3606,7 @@ function renderInlineMediaCard(params) {
|
|
|
3484
3606
|
...mediaMimeType ? { type: mediaMimeType } : {}
|
|
3485
3607
|
})
|
|
3486
3608
|
}) : /* @__PURE__ */ jsx("div", {
|
|
3487
|
-
className: cn("overflow-hidden rounded-
|
|
3609
|
+
className: cn("overflow-hidden rounded-lg", isUser ? "bg-black/10" : "bg-muted ring-1 ring-border/70"),
|
|
3488
3610
|
children: /* @__PURE__ */ jsx("video", {
|
|
3489
3611
|
controls: true,
|
|
3490
3612
|
preload: "metadata",
|
|
@@ -3500,20 +3622,22 @@ function renderInlineMediaCard(params) {
|
|
|
3500
3622
|
})]
|
|
3501
3623
|
});
|
|
3502
3624
|
}
|
|
3503
|
-
function ChatMessageFile({ file, isUser = false, texts }) {
|
|
3625
|
+
function ChatMessageFile({ file, isUser = false, texts, onOpen }) {
|
|
3504
3626
|
const { category, sizeLabel } = buildChatMessageFileMeta(file);
|
|
3505
3627
|
const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
|
|
3506
3628
|
const renderAsAudio = category === "audio" && Boolean(file.dataUrl);
|
|
3507
3629
|
const renderAsVideo = category === "video" && Boolean(file.dataUrl);
|
|
3508
3630
|
const isInteractive = Boolean(file.dataUrl);
|
|
3509
3631
|
const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
|
|
3632
|
+
const expandLabel = texts?.attachmentExpandLabel ?? "Expand image";
|
|
3633
|
+
const closeLabel = texts?.attachmentCloseLabel ?? "Close preview";
|
|
3510
3634
|
const categoryLabel = readFileCategoryLabel(category, texts);
|
|
3511
|
-
const shellClasses = cn("block overflow-hidden rounded-
|
|
3512
|
-
if (renderAsImage) return
|
|
3635
|
+
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"));
|
|
3636
|
+
if (renderAsImage) return /* @__PURE__ */ jsx(ChatMessageImagePreview, {
|
|
3637
|
+
expandLabel,
|
|
3638
|
+
closeLabel,
|
|
3513
3639
|
file,
|
|
3514
|
-
|
|
3515
|
-
sizeLabel,
|
|
3516
|
-
isUser
|
|
3640
|
+
sizeLabel
|
|
3517
3641
|
});
|
|
3518
3642
|
if (renderAsAudio || renderAsVideo) return renderInlineMediaCard({
|
|
3519
3643
|
category,
|
|
@@ -3522,7 +3646,8 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3522
3646
|
sizeLabel,
|
|
3523
3647
|
isUser,
|
|
3524
3648
|
shellClasses,
|
|
3525
|
-
actionLabel
|
|
3649
|
+
actionLabel,
|
|
3650
|
+
onOpen
|
|
3526
3651
|
});
|
|
3527
3652
|
const content = renderFileCardHeader({
|
|
3528
3653
|
category,
|
|
@@ -3536,6 +3661,12 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3536
3661
|
className: shellClasses,
|
|
3537
3662
|
children: content
|
|
3538
3663
|
});
|
|
3664
|
+
if (onOpen) return /* @__PURE__ */ jsx("button", {
|
|
3665
|
+
type: "button",
|
|
3666
|
+
className: cn(shellClasses, "group w-full text-left"),
|
|
3667
|
+
onClick: () => onOpen(file),
|
|
3668
|
+
children: content
|
|
3669
|
+
});
|
|
3539
3670
|
return /* @__PURE__ */ jsx("a", {
|
|
3540
3671
|
href: file.dataUrl,
|
|
3541
3672
|
target: "_blank",
|
|
@@ -3666,16 +3797,64 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
|
|
|
3666
3797
|
};
|
|
3667
3798
|
}
|
|
3668
3799
|
//#endregion
|
|
3800
|
+
//#region src/components/chat/ui/chat-message-list/chat-process-meta-row.tsx
|
|
3801
|
+
/**
|
|
3802
|
+
* Shared process-row metrics.
|
|
3803
|
+
* Collapsed process/tool rows should feel like ordinary body text lines.
|
|
3804
|
+
*/
|
|
3805
|
+
const CHAT_PROCESS_LEADING_COL_CLASS = "inline-flex h-[1.15em] w-[1.15em] shrink-0 items-center justify-center text-current";
|
|
3806
|
+
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";
|
|
3807
|
+
function ChatProcessLeadingIcon({ children, className }) {
|
|
3808
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3809
|
+
className: cn(CHAT_PROCESS_LEADING_COL_CLASS, className),
|
|
3810
|
+
children
|
|
3811
|
+
});
|
|
3812
|
+
}
|
|
3813
|
+
function ChatProcessMetaRow({ children, className, interactive = false, onClick, role, tabIndex, onKeyDown }) {
|
|
3814
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3815
|
+
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),
|
|
3816
|
+
onClick,
|
|
3817
|
+
role,
|
|
3818
|
+
tabIndex,
|
|
3819
|
+
onKeyDown,
|
|
3820
|
+
children
|
|
3821
|
+
});
|
|
3822
|
+
}
|
|
3823
|
+
//#endregion
|
|
3824
|
+
//#region src/components/chat/ui/chat-message-list/chat-collapsible-meta-summary.tsx
|
|
3825
|
+
/**
|
|
3826
|
+
* Shared collapsible meta-row for process / tool-group / reasoning summaries.
|
|
3827
|
+
* Rendered as a plain button-like row (not <summary>) so browsers never inject
|
|
3828
|
+
* a default disclosure label such as "详情" / "Details".
|
|
3829
|
+
*/
|
|
3830
|
+
function ChatCollapsibleMetaSummary({ label, openGroup, open = false, className, labelClassName, onClick }) {
|
|
3831
|
+
return /* @__PURE__ */ jsxs("button", {
|
|
3832
|
+
type: "button",
|
|
3833
|
+
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),
|
|
3834
|
+
"aria-expanded": open,
|
|
3835
|
+
onClick,
|
|
3836
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
3837
|
+
className: cn("min-w-0 shrink truncate", labelClassName),
|
|
3838
|
+
children: label
|
|
3839
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
3840
|
+
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"),
|
|
3841
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
3842
|
+
className: cn("h-[1.05em] w-[1.05em] transition-transform", open && "rotate-90"),
|
|
3843
|
+
strokeWidth: 2.25
|
|
3844
|
+
})
|
|
3845
|
+
})]
|
|
3846
|
+
});
|
|
3847
|
+
}
|
|
3848
|
+
//#endregion
|
|
3669
3849
|
//#region src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
|
|
3670
|
-
function
|
|
3671
|
-
const
|
|
3672
|
-
|
|
3673
|
-
return label;
|
|
3850
|
+
function formatReasoningLabel(label, text, characterCountTemplate) {
|
|
3851
|
+
const characterCount = Array.from(text).length;
|
|
3852
|
+
return (characterCountTemplate ?? `${label} · {count}`).split("{count}").join(String(characterCount));
|
|
3674
3853
|
}
|
|
3675
|
-
function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
3854
|
+
function ChatReasoningBlock({ label, text, characterCountTemplates, isUser, isInProgress }) {
|
|
3676
3855
|
const { isOpen, onSummaryClick } = useReasoningBlockOpenState({ isInProgress });
|
|
3677
3856
|
const scrollRef = useRef(null);
|
|
3678
|
-
const displayLabel =
|
|
3857
|
+
const displayLabel = formatReasoningLabel(label, text, isInProgress ? characterCountTemplates?.inProgress : characterCountTemplates?.completed);
|
|
3679
3858
|
const { onScroll } = useStickyBottomScroll({
|
|
3680
3859
|
scrollRef,
|
|
3681
3860
|
resetKey: `${displayLabel}:${isInProgress ? "streaming" : "idle"}`,
|
|
@@ -3684,36 +3863,42 @@ function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
|
3684
3863
|
contentVersion: text,
|
|
3685
3864
|
stickyThresholdPx: 20
|
|
3686
3865
|
});
|
|
3687
|
-
return /* @__PURE__ */ jsxs("
|
|
3688
|
-
className: "
|
|
3689
|
-
|
|
3690
|
-
|
|
3691
|
-
|
|
3692
|
-
|
|
3693
|
-
|
|
3694
|
-
|
|
3866
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
3867
|
+
className: "group/reasoning",
|
|
3868
|
+
children: [/* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
3869
|
+
openGroup: "reasoning",
|
|
3870
|
+
open: isOpen,
|
|
3871
|
+
label: displayLabel,
|
|
3872
|
+
labelClassName: isUser ? "text-primary-100" : void 0,
|
|
3873
|
+
onClick: onSummaryClick
|
|
3874
|
+
}), isOpen ? /* @__PURE__ */ jsx("div", {
|
|
3695
3875
|
ref: scrollRef,
|
|
3696
3876
|
onScroll,
|
|
3697
3877
|
"data-reasoning-scroll": "true",
|
|
3698
|
-
className: cn("mt-
|
|
3878
|
+
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"),
|
|
3699
3879
|
children: /* @__PURE__ */ jsx("pre", {
|
|
3700
3880
|
className: "min-w-0 whitespace-pre-wrap break-all p-2 text-[11px]",
|
|
3701
3881
|
children: text
|
|
3702
3882
|
})
|
|
3703
|
-
})]
|
|
3883
|
+
}) : null]
|
|
3704
3884
|
});
|
|
3705
3885
|
}
|
|
3706
3886
|
//#endregion
|
|
3707
3887
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-root.tsx
|
|
3888
|
+
/**
|
|
3889
|
+
* Default tool presentation is an inline process row, not a heavy card.
|
|
3890
|
+
* Expanded details sit under the overview text column with clear clearance
|
|
3891
|
+
* from any workflow rail in the icon column.
|
|
3892
|
+
*/
|
|
3708
3893
|
function ToolCardRoot({ children, className }) {
|
|
3709
3894
|
return /* @__PURE__ */ jsx("div", {
|
|
3710
|
-
className: cn("my-
|
|
3895
|
+
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),
|
|
3711
3896
|
children
|
|
3712
3897
|
});
|
|
3713
3898
|
}
|
|
3714
3899
|
function ToolCardContent({ children, className }) {
|
|
3715
3900
|
return /* @__PURE__ */ jsx("div", {
|
|
3716
|
-
className: cn("
|
|
3901
|
+
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),
|
|
3717
3902
|
children
|
|
3718
3903
|
});
|
|
3719
3904
|
}
|
|
@@ -3721,12 +3906,12 @@ function ToolCardContent({ children, className }) {
|
|
|
3721
3906
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-status.tsx
|
|
3722
3907
|
const STATUS_STYLES = {
|
|
3723
3908
|
running: {
|
|
3724
|
-
text: "text-
|
|
3909
|
+
text: "text-muted-foreground",
|
|
3725
3910
|
icon: Loader2,
|
|
3726
3911
|
spin: true
|
|
3727
3912
|
},
|
|
3728
3913
|
success: {
|
|
3729
|
-
text: "text-
|
|
3914
|
+
text: "text-muted-foreground/75",
|
|
3730
3915
|
icon: Check,
|
|
3731
3916
|
spin: false
|
|
3732
3917
|
},
|
|
@@ -3736,20 +3921,23 @@ const STATUS_STYLES = {
|
|
|
3736
3921
|
spin: false
|
|
3737
3922
|
},
|
|
3738
3923
|
cancelled: {
|
|
3739
|
-
text: "text-muted-foreground",
|
|
3924
|
+
text: "text-muted-foreground/70",
|
|
3740
3925
|
icon: Minus,
|
|
3741
3926
|
spin: false
|
|
3742
3927
|
}
|
|
3743
3928
|
};
|
|
3744
|
-
function ToolStatusLabel({ card }) {
|
|
3929
|
+
function ToolStatusLabel({ card, iconOnly = false }) {
|
|
3745
3930
|
const style = STATUS_STYLES[card.statusTone] || STATUS_STYLES.cancelled;
|
|
3746
3931
|
const Icon = style.icon;
|
|
3932
|
+
const showLabel = !iconOnly && (card.statusTone === "running" || card.statusTone === "error");
|
|
3747
3933
|
return /* @__PURE__ */ jsxs("span", {
|
|
3748
|
-
className: cn("inline-flex items-center gap-1
|
|
3934
|
+
className: cn("inline-flex shrink-0 items-center gap-1 text-[0.925rem] font-normal leading-[1.72]", style.text),
|
|
3935
|
+
"aria-label": iconOnly ? card.statusLabel : void 0,
|
|
3749
3936
|
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3750
|
-
|
|
3751
|
-
|
|
3752
|
-
|
|
3937
|
+
"aria-hidden": "true",
|
|
3938
|
+
className: cn("h-[1.05em] w-[1.05em]", style.spin && "animate-spin"),
|
|
3939
|
+
strokeWidth: 2.25
|
|
3940
|
+
}), showLabel ? card.statusLabel : null]
|
|
3753
3941
|
});
|
|
3754
3942
|
}
|
|
3755
3943
|
//#endregion
|
|
@@ -3758,12 +3946,12 @@ function resolveToolCardActionView(action) {
|
|
|
3758
3946
|
if (action.kind === "show-content") return {
|
|
3759
3947
|
icon: Eye,
|
|
3760
3948
|
label: action.label,
|
|
3761
|
-
toneClassName: "border-border bg-
|
|
3949
|
+
toneClassName: "border-border/70 bg-transparent text-muted-foreground hover:bg-muted/60 hover:text-foreground focus-visible:ring-primary/35"
|
|
3762
3950
|
};
|
|
3763
3951
|
return {
|
|
3764
3952
|
icon: ArrowUpRight,
|
|
3765
3953
|
label: action.label ?? (action.sessionKind === "child" ? "Open child session" : "Open session"),
|
|
3766
|
-
toneClassName: "border-border bg-
|
|
3954
|
+
toneClassName: "border-border/70 bg-transparent text-muted-foreground hover:bg-muted/60 hover:text-foreground focus-visible:ring-primary/35"
|
|
3767
3955
|
};
|
|
3768
3956
|
}
|
|
3769
3957
|
function ToolCardActionButton({ action, onAction }) {
|
|
@@ -3781,12 +3969,12 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3781
3969
|
children: /* @__PURE__ */ jsx("button", {
|
|
3782
3970
|
type: "button",
|
|
3783
3971
|
onClick: handleClick,
|
|
3784
|
-
className: cn("inline-flex h-
|
|
3972
|
+
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),
|
|
3785
3973
|
"aria-label": view.label,
|
|
3786
3974
|
title: view.label,
|
|
3787
3975
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3788
3976
|
className: "h-3.5 w-3.5",
|
|
3789
|
-
strokeWidth: 2.
|
|
3977
|
+
strokeWidth: 2.25
|
|
3790
3978
|
})
|
|
3791
3979
|
})
|
|
3792
3980
|
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
@@ -3796,44 +3984,82 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3796
3984
|
})] })
|
|
3797
3985
|
});
|
|
3798
3986
|
}
|
|
3799
|
-
function
|
|
3987
|
+
function humanizeToolName(toolName) {
|
|
3988
|
+
return toolName.replace(/[_-]+/g, " ").replace(/\s+/g, " ").trim();
|
|
3989
|
+
}
|
|
3990
|
+
function handleToolHeaderKeyDown(event, onToggle) {
|
|
3991
|
+
if (event.key !== "Enter" && event.key !== " ") return;
|
|
3992
|
+
event.preventDefault();
|
|
3993
|
+
onToggle();
|
|
3994
|
+
}
|
|
3995
|
+
function ToolHeaderSummary({ label, summary }) {
|
|
3996
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
3997
|
+
className: "min-w-0 shrink truncate",
|
|
3998
|
+
children: [label, summary ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsx("span", {
|
|
3999
|
+
className: "mx-1.5 select-none text-muted-foreground/45",
|
|
4000
|
+
children: "·"
|
|
4001
|
+
}), /* @__PURE__ */ jsx("span", {
|
|
4002
|
+
title: summary,
|
|
4003
|
+
children: summary
|
|
4004
|
+
})] }) : null]
|
|
4005
|
+
});
|
|
4006
|
+
}
|
|
4007
|
+
function ToolHeaderChevron({ expanded }) {
|
|
4008
|
+
return /* @__PURE__ */ jsx("span", {
|
|
4009
|
+
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"),
|
|
4010
|
+
children: /* @__PURE__ */ jsx(ChevronRight, {
|
|
4011
|
+
className: cn("h-[1.05em] w-[1.05em] transition-transform", expanded && "rotate-90"),
|
|
4012
|
+
strokeWidth: 2.25
|
|
4013
|
+
})
|
|
4014
|
+
});
|
|
4015
|
+
}
|
|
4016
|
+
function ToolHeaderChangeSummary({ additions, deletions }) {
|
|
4017
|
+
if (additions === 0 && deletions === 0) return null;
|
|
4018
|
+
const accessibleLabel = [additions > 0 ? `+${additions}` : null, deletions > 0 ? `-${deletions}` : null].filter(Boolean).join(" ");
|
|
4019
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4020
|
+
className: "inline-flex shrink-0 items-center gap-1.5 tabular-nums text-muted-foreground/75",
|
|
4021
|
+
"aria-label": accessibleLabel,
|
|
4022
|
+
children: [additions > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
4023
|
+
className: "transition-colors group-hover/process-row:text-emerald-600",
|
|
4024
|
+
children: ["+", additions]
|
|
4025
|
+
}) : null, deletions > 0 ? /* @__PURE__ */ jsxs("span", {
|
|
4026
|
+
className: "transition-colors group-hover/process-row:text-rose-600",
|
|
4027
|
+
children: ["-", deletions]
|
|
4028
|
+
}) : null]
|
|
4029
|
+
});
|
|
4030
|
+
}
|
|
4031
|
+
function ToolCardHeader({ card, toolLabel, changeSummary, icon: Icon, expanded, canExpand, hideSummary = false, actionSlot, onToggle }) {
|
|
3800
4032
|
const summaryPart = hideSummary ? "" : card.summary?.replace(/^(command|path|args|query|input):\s*/i, "") ?? "";
|
|
3801
|
-
|
|
3802
|
-
|
|
3803
|
-
|
|
3804
|
-
|
|
3805
|
-
|
|
3806
|
-
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
className: "
|
|
3811
|
-
children:
|
|
3812
|
-
className: "
|
|
3813
|
-
|
|
3814
|
-
})
|
|
3815
|
-
|
|
3816
|
-
|
|
3817
|
-
|
|
3818
|
-
|
|
3819
|
-
|
|
3820
|
-
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
}) : /* @__PURE__ */ jsx(ChevronRight, {
|
|
3832
|
-
className: "h-4 w-4 text-muted-foreground",
|
|
3833
|
-
strokeWidth: 3
|
|
3834
|
-
}))
|
|
3835
|
-
]
|
|
3836
|
-
})]
|
|
4033
|
+
const displayLabel = toolLabel ?? humanizeToolName(card.toolName);
|
|
4034
|
+
return /* @__PURE__ */ jsxs(ChatProcessMetaRow, {
|
|
4035
|
+
interactive: canExpand,
|
|
4036
|
+
onClick: canExpand ? () => onToggle() : void 0,
|
|
4037
|
+
role: canExpand ? "button" : void 0,
|
|
4038
|
+
tabIndex: canExpand ? 0 : void 0,
|
|
4039
|
+
onKeyDown: canExpand ? (event) => handleToolHeaderKeyDown(event, onToggle) : void 0,
|
|
4040
|
+
children: [
|
|
4041
|
+
/* @__PURE__ */ jsx(ChatProcessLeadingIcon, {
|
|
4042
|
+
className: "relative z-[1] rounded-sm bg-card",
|
|
4043
|
+
children: /* @__PURE__ */ jsx(Icon, {
|
|
4044
|
+
className: "h-[1.05em] w-[1.05em]",
|
|
4045
|
+
strokeWidth: 2.25
|
|
4046
|
+
})
|
|
4047
|
+
}),
|
|
4048
|
+
/* @__PURE__ */ jsx(ToolHeaderSummary, {
|
|
4049
|
+
label: displayLabel,
|
|
4050
|
+
summary: summaryPart
|
|
4051
|
+
}),
|
|
4052
|
+
changeSummary ? /* @__PURE__ */ jsx(ToolHeaderChangeSummary, { ...changeSummary }) : null,
|
|
4053
|
+
/* @__PURE__ */ jsx(ToolStatusLabel, {
|
|
4054
|
+
card,
|
|
4055
|
+
iconOnly: Boolean(toolLabel)
|
|
4056
|
+
}),
|
|
4057
|
+
canExpand ? /* @__PURE__ */ jsx(ToolHeaderChevron, { expanded }) : null,
|
|
4058
|
+
actionSlot ? /* @__PURE__ */ jsx("span", {
|
|
4059
|
+
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",
|
|
4060
|
+
children: actionSlot
|
|
4061
|
+
}) : null
|
|
4062
|
+
]
|
|
3837
4063
|
});
|
|
3838
4064
|
}
|
|
3839
4065
|
function ToolCardHeaderAction({ action, onAction }) {
|
|
@@ -4076,7 +4302,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4076
4302
|
className: cn("overflow-hidden bg-card", !isFirst && "border-t border-border"),
|
|
4077
4303
|
children: [
|
|
4078
4304
|
showMetaRow ? /* @__PURE__ */ jsxs("div", {
|
|
4079
|
-
className: cn("flex items-center justify-between gap-4 border-b border-border px-
|
|
4305
|
+
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"),
|
|
4080
4306
|
children: [/* @__PURE__ */ jsx("div", {
|
|
4081
4307
|
className: "min-w-0 flex-1",
|
|
4082
4308
|
children: showPathRow ? /* @__PURE__ */ jsx(TooltipProvider, {
|
|
@@ -4086,7 +4312,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4086
4312
|
children: /* @__PURE__ */ jsx("button", {
|
|
4087
4313
|
type: "button",
|
|
4088
4314
|
onClick: handlePathClick,
|
|
4089
|
-
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"),
|
|
4315
|
+
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"),
|
|
4090
4316
|
title: block.path,
|
|
4091
4317
|
children: block.path
|
|
4092
4318
|
})
|
|
@@ -4112,28 +4338,28 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4112
4338
|
children: /* @__PURE__ */ jsx("div", {
|
|
4113
4339
|
className: "overflow-x-auto custom-scrollbar",
|
|
4114
4340
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4115
|
-
className: "min-w-max whitespace-pre bg-card px-
|
|
4341
|
+
className: "min-w-max whitespace-pre bg-card px-3 py-2 font-mono text-[11px] leading-5 text-foreground",
|
|
4116
4342
|
children: block.rawText
|
|
4117
4343
|
})
|
|
4118
4344
|
})
|
|
4119
4345
|
}) : null,
|
|
4120
4346
|
block.truncated && !previewBlock ? /* @__PURE__ */ jsx("div", {
|
|
4121
|
-
className: "border-t border-border bg-muted px-
|
|
4347
|
+
className: "border-t border-border bg-muted px-3 py-2 text-[10px] text-muted-foreground",
|
|
4122
4348
|
children: "Showing a shortened diff preview."
|
|
4123
4349
|
}) : null
|
|
4124
4350
|
]
|
|
4125
4351
|
});
|
|
4126
4352
|
}
|
|
4127
|
-
function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
4353
|
+
function ToolCardFileOperationContent({ card, className, onFileOpen, showPathRow = true }) {
|
|
4128
4354
|
const blocks = card.fileOperation?.blocks ?? [];
|
|
4129
4355
|
const output = card.output?.trim() ?? "";
|
|
4130
4356
|
if (blocks.length === 0 && !output) return null;
|
|
4131
4357
|
return /* @__PURE__ */ jsxs("div", {
|
|
4132
|
-
className: cn("overflow-hidden bg-card", className),
|
|
4358
|
+
className: cn("overflow-hidden rounded-md border border-border/70 bg-card", className),
|
|
4133
4359
|
children: [blocks.map((block, index) => {
|
|
4134
4360
|
return /* @__PURE__ */ jsx(FileOperationBlock, {
|
|
4135
4361
|
block,
|
|
4136
|
-
showPathRow
|
|
4362
|
+
showPathRow,
|
|
4137
4363
|
isFirst: index === 0,
|
|
4138
4364
|
onFileOpen
|
|
4139
4365
|
}, block.key);
|
|
@@ -4145,7 +4371,7 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4145
4371
|
children: /* @__PURE__ */ jsx("div", {
|
|
4146
4372
|
className: "overflow-x-auto custom-scrollbar",
|
|
4147
4373
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4148
|
-
className: "min-w-max whitespace-pre bg-card px-
|
|
4374
|
+
className: "min-w-max whitespace-pre bg-card px-3 py-2 font-mono text-[11px] leading-5 text-foreground",
|
|
4149
4375
|
children: output
|
|
4150
4376
|
})
|
|
4151
4377
|
})
|
|
@@ -4153,9 +4379,279 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4153
4379
|
});
|
|
4154
4380
|
}
|
|
4155
4381
|
//#endregion
|
|
4382
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/terminal/terminal-ansi.utils.ts
|
|
4383
|
+
const ANSI_ESCAPE_PREFIX = String.fromCharCode(27);
|
|
4384
|
+
const ANSI_PATTERN = new RegExp(`${ANSI_ESCAPE_PREFIX}\\[(.*?)([ -/]*[@-~])`, "g");
|
|
4385
|
+
function hasAnsiSequences(value) {
|
|
4386
|
+
return value.includes(`${ANSI_ESCAPE_PREFIX}[`);
|
|
4387
|
+
}
|
|
4388
|
+
function stripAnsiSequences(value) {
|
|
4389
|
+
return value.replace(new RegExp(`${ANSI_ESCAPE_PREFIX}\\[[0-?]*[ -/]*[@-~]`, "g"), "");
|
|
4390
|
+
}
|
|
4391
|
+
function escapeHtml(value) {
|
|
4392
|
+
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
4393
|
+
}
|
|
4394
|
+
const DEFAULT_STYLE = {
|
|
4395
|
+
bold: false,
|
|
4396
|
+
dim: false,
|
|
4397
|
+
italic: false,
|
|
4398
|
+
underline: false,
|
|
4399
|
+
inverse: false
|
|
4400
|
+
};
|
|
4401
|
+
const SGR_STYLE_PATCHES = {
|
|
4402
|
+
1: { bold: true },
|
|
4403
|
+
2: { dim: true },
|
|
4404
|
+
3: { italic: true },
|
|
4405
|
+
4: { underline: true },
|
|
4406
|
+
7: { inverse: true },
|
|
4407
|
+
22: {
|
|
4408
|
+
bold: false,
|
|
4409
|
+
dim: false
|
|
4410
|
+
},
|
|
4411
|
+
23: { italic: false },
|
|
4412
|
+
24: { underline: false },
|
|
4413
|
+
27: { inverse: false },
|
|
4414
|
+
39: { fg: void 0 },
|
|
4415
|
+
49: { bg: void 0 }
|
|
4416
|
+
};
|
|
4417
|
+
const BASIC_FG = {
|
|
4418
|
+
30: "black",
|
|
4419
|
+
31: "red",
|
|
4420
|
+
32: "green",
|
|
4421
|
+
33: "yellow",
|
|
4422
|
+
34: "blue",
|
|
4423
|
+
35: "magenta",
|
|
4424
|
+
36: "cyan",
|
|
4425
|
+
37: "white",
|
|
4426
|
+
90: "bright-black",
|
|
4427
|
+
91: "bright-red",
|
|
4428
|
+
92: "bright-green",
|
|
4429
|
+
93: "bright-yellow",
|
|
4430
|
+
94: "bright-blue",
|
|
4431
|
+
95: "bright-magenta",
|
|
4432
|
+
96: "bright-cyan",
|
|
4433
|
+
97: "bright-white"
|
|
4434
|
+
};
|
|
4435
|
+
const BASIC_BG = {
|
|
4436
|
+
40: "black",
|
|
4437
|
+
41: "red",
|
|
4438
|
+
42: "green",
|
|
4439
|
+
43: "yellow",
|
|
4440
|
+
44: "blue",
|
|
4441
|
+
45: "magenta",
|
|
4442
|
+
46: "cyan",
|
|
4443
|
+
47: "white",
|
|
4444
|
+
100: "bright-black",
|
|
4445
|
+
101: "bright-red",
|
|
4446
|
+
102: "bright-green",
|
|
4447
|
+
103: "bright-yellow",
|
|
4448
|
+
104: "bright-blue",
|
|
4449
|
+
105: "bright-magenta",
|
|
4450
|
+
106: "bright-cyan",
|
|
4451
|
+
107: "bright-white"
|
|
4452
|
+
};
|
|
4453
|
+
function resolveExtendedColor(params, index) {
|
|
4454
|
+
const mode = params[index + 1];
|
|
4455
|
+
if (mode === 5 && typeof params[index + 2] === "number") return {
|
|
4456
|
+
color: `ansi-256-${params[index + 2]}`,
|
|
4457
|
+
consumed: 2
|
|
4458
|
+
};
|
|
4459
|
+
if (mode === 2 && typeof params[index + 2] === "number" && typeof params[index + 3] === "number" && typeof params[index + 4] === "number") return {
|
|
4460
|
+
color: `rgb(${params[index + 2]},${params[index + 3]},${params[index + 4]})`,
|
|
4461
|
+
consumed: 4
|
|
4462
|
+
};
|
|
4463
|
+
return null;
|
|
4464
|
+
}
|
|
4465
|
+
function applySgr(style, params) {
|
|
4466
|
+
let next = { ...style };
|
|
4467
|
+
for (let index = 0; index < params.length; index += 1) {
|
|
4468
|
+
const code = params[index];
|
|
4469
|
+
if (code === 0) return { ...DEFAULT_STYLE };
|
|
4470
|
+
const stylePatch = SGR_STYLE_PATCHES[code];
|
|
4471
|
+
if (stylePatch) {
|
|
4472
|
+
next = {
|
|
4473
|
+
...next,
|
|
4474
|
+
...stylePatch
|
|
4475
|
+
};
|
|
4476
|
+
continue;
|
|
4477
|
+
}
|
|
4478
|
+
if (code === 38 || code === 48) {
|
|
4479
|
+
const extendedColor = resolveExtendedColor(params, index);
|
|
4480
|
+
if (extendedColor) {
|
|
4481
|
+
next = code === 38 ? {
|
|
4482
|
+
...next,
|
|
4483
|
+
fg: extendedColor.color
|
|
4484
|
+
} : {
|
|
4485
|
+
...next,
|
|
4486
|
+
bg: extendedColor.color
|
|
4487
|
+
};
|
|
4488
|
+
index += extendedColor.consumed;
|
|
4489
|
+
}
|
|
4490
|
+
continue;
|
|
4491
|
+
}
|
|
4492
|
+
if (BASIC_FG[code]) {
|
|
4493
|
+
next = {
|
|
4494
|
+
...next,
|
|
4495
|
+
fg: BASIC_FG[code]
|
|
4496
|
+
};
|
|
4497
|
+
continue;
|
|
4498
|
+
}
|
|
4499
|
+
if (BASIC_BG[code]) next = {
|
|
4500
|
+
...next,
|
|
4501
|
+
bg: BASIC_BG[code]
|
|
4502
|
+
};
|
|
4503
|
+
}
|
|
4504
|
+
return next;
|
|
4505
|
+
}
|
|
4506
|
+
function styleToClassName(style) {
|
|
4507
|
+
const classes = ["ansi"];
|
|
4508
|
+
if (style.bold) classes.push("ansi-bold");
|
|
4509
|
+
if (style.dim) classes.push("ansi-dim");
|
|
4510
|
+
if (style.italic) classes.push("ansi-italic");
|
|
4511
|
+
if (style.underline) classes.push("ansi-underline");
|
|
4512
|
+
if (style.inverse) classes.push("ansi-inverse");
|
|
4513
|
+
if (style.fg) if (style.fg.startsWith("rgb(")) classes.push("ansi-fg-custom");
|
|
4514
|
+
else if (style.fg.startsWith("ansi-256-")) {
|
|
4515
|
+
classes.push("ansi-fg-256");
|
|
4516
|
+
classes.push(style.fg);
|
|
4517
|
+
} else classes.push(`ansi-fg-${style.fg}`);
|
|
4518
|
+
if (style.bg) if (style.bg.startsWith("rgb(")) classes.push("ansi-bg-custom");
|
|
4519
|
+
else if (style.bg.startsWith("ansi-256-")) {
|
|
4520
|
+
classes.push("ansi-bg-256");
|
|
4521
|
+
classes.push(style.bg);
|
|
4522
|
+
} else classes.push(`ansi-bg-${style.bg}`);
|
|
4523
|
+
return classes.join(" ");
|
|
4524
|
+
}
|
|
4525
|
+
function styleToInlineStyle(style) {
|
|
4526
|
+
const parts = [];
|
|
4527
|
+
if (style.fg?.startsWith("rgb(")) parts.push(`color:${style.fg}`);
|
|
4528
|
+
if (style.bg?.startsWith("rgb(")) parts.push(`background-color:${style.bg}`);
|
|
4529
|
+
return parts.length > 0 ? parts.join(";") : void 0;
|
|
4530
|
+
}
|
|
4531
|
+
function openSpan(style) {
|
|
4532
|
+
const className = styleToClassName(style);
|
|
4533
|
+
const inline = styleToInlineStyle(style);
|
|
4534
|
+
return inline ? `<span class="${className}" style="${inline}">` : `<span class="${className}">`;
|
|
4535
|
+
}
|
|
4536
|
+
/**
|
|
4537
|
+
* Lightweight ANSI SGR -> HTML converter for terminal output.
|
|
4538
|
+
* Handles common color/style codes without adding a dependency.
|
|
4539
|
+
*/
|
|
4540
|
+
function ansiToHtml(value) {
|
|
4541
|
+
if (!value) return "";
|
|
4542
|
+
if (!hasAnsiSequences(value)) return escapeHtml(value);
|
|
4543
|
+
let html = "";
|
|
4544
|
+
let lastIndex = 0;
|
|
4545
|
+
let style = { ...DEFAULT_STYLE };
|
|
4546
|
+
let spanOpen = false;
|
|
4547
|
+
const flushText = (text) => {
|
|
4548
|
+
if (!text) return;
|
|
4549
|
+
if (!spanOpen) {
|
|
4550
|
+
html += openSpan(style);
|
|
4551
|
+
spanOpen = true;
|
|
4552
|
+
}
|
|
4553
|
+
html += escapeHtml(text);
|
|
4554
|
+
};
|
|
4555
|
+
const closeSpan = () => {
|
|
4556
|
+
if (spanOpen) {
|
|
4557
|
+
html += "</span>";
|
|
4558
|
+
spanOpen = false;
|
|
4559
|
+
}
|
|
4560
|
+
};
|
|
4561
|
+
value.replace(ANSI_PATTERN, (match, body, terminator, offset) => {
|
|
4562
|
+
flushText(value.slice(lastIndex, offset));
|
|
4563
|
+
lastIndex = offset + match.length;
|
|
4564
|
+
if (terminator !== "m") return match;
|
|
4565
|
+
const params = body.trim().length === 0 ? [0] : body.split(";").map((part) => Number.parseInt(part, 10)).filter((part) => Number.isFinite(part));
|
|
4566
|
+
closeSpan();
|
|
4567
|
+
style = applySgr(style, params.length > 0 ? params : [0]);
|
|
4568
|
+
return match;
|
|
4569
|
+
});
|
|
4570
|
+
flushText(value.slice(lastIndex));
|
|
4571
|
+
closeSpan();
|
|
4572
|
+
return html;
|
|
4573
|
+
}
|
|
4574
|
+
//#endregion
|
|
4575
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/terminal/terminal-panes.tsx
|
|
4576
|
+
const MAX_TERMINAL_TEXT_CHARS = 12e4;
|
|
4577
|
+
function clampTerminalText(value) {
|
|
4578
|
+
if (value.length <= MAX_TERMINAL_TEXT_CHARS) return value;
|
|
4579
|
+
return `${value.slice(0, MAX_TERMINAL_TEXT_CHARS)}\n…`;
|
|
4580
|
+
}
|
|
4581
|
+
function TerminalCode({ code, language, preferAnsi = false, className }) {
|
|
4582
|
+
const source = clampTerminalText(code);
|
|
4583
|
+
const content = useMemo(() => {
|
|
4584
|
+
if (preferAnsi && hasAnsiSequences(source)) return {
|
|
4585
|
+
html: ansiToHtml(source),
|
|
4586
|
+
language: "ansi",
|
|
4587
|
+
highlighted: true
|
|
4588
|
+
};
|
|
4589
|
+
const plain = stripAnsiSequences(source);
|
|
4590
|
+
return chatCodeSyntaxHighlighter.highlight(plain, language);
|
|
4591
|
+
}, [
|
|
4592
|
+
language,
|
|
4593
|
+
preferAnsi,
|
|
4594
|
+
source
|
|
4595
|
+
]);
|
|
4596
|
+
return /* @__PURE__ */ jsx("code", {
|
|
4597
|
+
className: cn("hljs chat-terminal-code", `language-${content.language}`, className),
|
|
4598
|
+
"data-highlighted": content.highlighted ? "true" : "false",
|
|
4599
|
+
dangerouslySetInnerHTML: { __html: content.html }
|
|
4600
|
+
});
|
|
4601
|
+
}
|
|
4602
|
+
/**
|
|
4603
|
+
* Compact terminal-style surface for command tools.
|
|
4604
|
+
* Style only — no decorative window chrome.
|
|
4605
|
+
*/
|
|
4606
|
+
function ChatTerminalSurface({ command, output, emptyLabel, isRunning, hasOutput, isError = false }) {
|
|
4607
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4608
|
+
className: cn("chat-terminal-surface", isError ? "chat-terminal-surface-error" : void 0),
|
|
4609
|
+
"data-testid": "chat-terminal-surface",
|
|
4610
|
+
children: /* @__PURE__ */ jsxs("div", {
|
|
4611
|
+
className: "chat-terminal-body custom-scrollbar",
|
|
4612
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4613
|
+
className: "chat-terminal-line chat-terminal-line-command",
|
|
4614
|
+
children: [/* @__PURE__ */ jsx("span", {
|
|
4615
|
+
className: "chat-terminal-gutter",
|
|
4616
|
+
"aria-hidden": "true",
|
|
4617
|
+
children: "$"
|
|
4618
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4619
|
+
className: "chat-terminal-line-content",
|
|
4620
|
+
children: command ? /* @__PURE__ */ jsxs("pre", {
|
|
4621
|
+
className: "chat-terminal-pre",
|
|
4622
|
+
children: [/* @__PURE__ */ jsx(TerminalCode, {
|
|
4623
|
+
code: command,
|
|
4624
|
+
language: "bash"
|
|
4625
|
+
}), isRunning && !hasOutput ? /* @__PURE__ */ jsx("span", {
|
|
4626
|
+
className: "chat-terminal-caret",
|
|
4627
|
+
"aria-hidden": "true"
|
|
4628
|
+
}) : null]
|
|
4629
|
+
}) : /* @__PURE__ */ jsx("div", { className: "chat-terminal-skeleton" })
|
|
4630
|
+
})]
|
|
4631
|
+
}), (hasOutput || !isRunning) && /* @__PURE__ */ jsx("div", {
|
|
4632
|
+
className: "chat-terminal-output-block",
|
|
4633
|
+
children: hasOutput ? /* @__PURE__ */ jsxs("pre", {
|
|
4634
|
+
className: cn("chat-terminal-pre chat-terminal-output", isError ? "chat-terminal-pre-error" : void 0),
|
|
4635
|
+
children: [/* @__PURE__ */ jsx(TerminalCode, {
|
|
4636
|
+
code: output,
|
|
4637
|
+
language: isError ? "text" : "shell",
|
|
4638
|
+
preferAnsi: true,
|
|
4639
|
+
className: isError ? "chat-terminal-code-error" : void 0
|
|
4640
|
+
}), isRunning ? /* @__PURE__ */ jsx("span", {
|
|
4641
|
+
className: "chat-terminal-caret",
|
|
4642
|
+
"aria-hidden": "true"
|
|
4643
|
+
}) : null]
|
|
4644
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
4645
|
+
className: "chat-terminal-empty",
|
|
4646
|
+
children: emptyLabel
|
|
4647
|
+
})
|
|
4648
|
+
})]
|
|
4649
|
+
})
|
|
4650
|
+
});
|
|
4651
|
+
}
|
|
4652
|
+
//#endregion
|
|
4156
4653
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
|
|
4157
4654
|
const TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
|
|
4158
|
-
const ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
4159
4655
|
function readNonEmptyString(value) {
|
|
4160
4656
|
if (typeof value !== "string") return null;
|
|
4161
4657
|
return value.length > 0 ? value : null;
|
|
@@ -4163,9 +4659,6 @@ function readNonEmptyString(value) {
|
|
|
4163
4659
|
function isRecord(value) {
|
|
4164
4660
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4165
4661
|
}
|
|
4166
|
-
function stripAnsi(value) {
|
|
4167
|
-
return value.replace(ANSI_ESCAPE_PATTERN, "");
|
|
4168
|
-
}
|
|
4169
4662
|
function isStructuredTerminalRecord(record) {
|
|
4170
4663
|
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;
|
|
4171
4664
|
}
|
|
@@ -4180,26 +4673,35 @@ function extractTerminalOutputFromRecord(record) {
|
|
|
4180
4673
|
function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
4181
4674
|
if (isRecord(structuredOutput)) {
|
|
4182
4675
|
const terminalOutput = extractTerminalOutputFromRecord(structuredOutput);
|
|
4183
|
-
if (terminalOutput) return
|
|
4676
|
+
if (terminalOutput) return terminalOutput;
|
|
4184
4677
|
if (isStructuredTerminalRecord(structuredOutput)) return "";
|
|
4185
4678
|
}
|
|
4186
4679
|
if (!rawOutput) return "";
|
|
4187
4680
|
const trimmed = rawOutput.trim();
|
|
4188
|
-
if (!trimmed.startsWith("{")) return
|
|
4681
|
+
if (!trimmed.startsWith("{")) return rawOutput;
|
|
4189
4682
|
try {
|
|
4190
4683
|
const parsed = JSON.parse(trimmed);
|
|
4191
|
-
if (!isRecord(parsed)) return
|
|
4684
|
+
if (!isRecord(parsed)) return rawOutput;
|
|
4192
4685
|
const terminalOutput = extractTerminalOutputFromRecord(parsed);
|
|
4193
|
-
if (terminalOutput) return
|
|
4686
|
+
if (terminalOutput) return terminalOutput;
|
|
4194
4687
|
if (isStructuredTerminalRecord(parsed)) return "";
|
|
4195
|
-
return
|
|
4688
|
+
return rawOutput;
|
|
4196
4689
|
} catch {
|
|
4197
|
-
return
|
|
4690
|
+
return rawOutput;
|
|
4198
4691
|
}
|
|
4199
4692
|
}
|
|
4200
4693
|
function shouldAutoExpandRunningFileOperation(toolName) {
|
|
4201
4694
|
return toolName === "write_file" || toolName === "edit_file";
|
|
4202
4695
|
}
|
|
4696
|
+
function countFileOperationChanges(blocks) {
|
|
4697
|
+
return blocks.reduce((totals, block) => ({
|
|
4698
|
+
additions: totals.additions + block.lines.filter((line) => line.kind === "add").length,
|
|
4699
|
+
deletions: totals.deletions + block.lines.filter((line) => line.kind === "remove").length
|
|
4700
|
+
}), {
|
|
4701
|
+
additions: 0,
|
|
4702
|
+
deletions: 0
|
|
4703
|
+
});
|
|
4704
|
+
}
|
|
4203
4705
|
function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning = true, expandOnError = false, statusTone }) {
|
|
4204
4706
|
const [expanded, setExpanded] = useState(false);
|
|
4205
4707
|
const [hasUserToggled, setHasUserToggled] = useState(false);
|
|
@@ -4258,47 +4760,20 @@ function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning
|
|
|
4258
4760
|
onToggle
|
|
4259
4761
|
};
|
|
4260
4762
|
}
|
|
4261
|
-
function
|
|
4262
|
-
|
|
4263
|
-
|
|
4264
|
-
|
|
4265
|
-
|
|
4266
|
-
|
|
4267
|
-
body: "text-foreground"
|
|
4268
|
-
},
|
|
4269
|
-
output: {
|
|
4270
|
-
shell: "border-border bg-card",
|
|
4271
|
-
header: "border-border bg-muted/55 text-muted-foreground",
|
|
4272
|
-
dot: "bg-primary/70",
|
|
4273
|
-
body: "text-foreground"
|
|
4274
|
-
},
|
|
4275
|
-
error: {
|
|
4276
|
-
shell: "border-rose-200/80 bg-rose-50/85",
|
|
4277
|
-
header: "border-rose-200/80 bg-rose-100/80 text-rose-700",
|
|
4278
|
-
dot: "bg-rose-500/80",
|
|
4279
|
-
body: "text-rose-950/85"
|
|
4280
|
-
}
|
|
4281
|
-
}[tone];
|
|
4282
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
4283
|
-
className: cn("overflow-hidden rounded-md border shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]", style.shell),
|
|
4284
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
4285
|
-
className: cn("flex items-center gap-2 border-b px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.16em]", style.header),
|
|
4286
|
-
children: [/* @__PURE__ */ jsx("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }), /* @__PURE__ */ jsx("span", { children: label })]
|
|
4287
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
4288
|
-
className: "w-full overflow-hidden",
|
|
4289
|
-
children: /* @__PURE__ */ jsx("pre", {
|
|
4290
|
-
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),
|
|
4291
|
-
children
|
|
4292
|
-
})
|
|
4293
|
-
})]
|
|
4294
|
-
});
|
|
4763
|
+
function extractTerminalMeta(structuredOutput) {
|
|
4764
|
+
if (!isRecord(structuredOutput)) return {};
|
|
4765
|
+
return {
|
|
4766
|
+
exitCode: typeof structuredOutput.exitCode === "number" ? structuredOutput.exitCode : typeof structuredOutput.exit_code === "number" ? structuredOutput.exit_code : null,
|
|
4767
|
+
workingDir: readNonEmptyString(structuredOutput.workingDir) ?? readNonEmptyString(structuredOutput.cwd) ?? readNonEmptyString(structuredOutput.working_directory)
|
|
4768
|
+
};
|
|
4295
4769
|
}
|
|
4296
|
-
function TerminalExecutionView({ card }) {
|
|
4770
|
+
function TerminalExecutionView({ card, toolLabel }) {
|
|
4297
4771
|
const output = normalizeTerminalOutput(card.output, card.outputData);
|
|
4298
4772
|
const isRunning = card.statusTone === "running";
|
|
4299
4773
|
const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
|
|
4300
4774
|
const hasOutput = output.trim().length > 0;
|
|
4301
4775
|
const canExpand = isRunning || hasOutput || Boolean(commandPart?.trim());
|
|
4776
|
+
const meta = extractTerminalMeta(card.outputData);
|
|
4302
4777
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4303
4778
|
canExpand,
|
|
4304
4779
|
isRunning,
|
|
@@ -4308,38 +4783,29 @@ function TerminalExecutionView({ card }) {
|
|
|
4308
4783
|
});
|
|
4309
4784
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4310
4785
|
card,
|
|
4786
|
+
toolLabel,
|
|
4311
4787
|
icon: Terminal,
|
|
4312
4788
|
expanded,
|
|
4313
4789
|
canExpand,
|
|
4314
4790
|
onToggle
|
|
4315
|
-
}), expanded && /* @__PURE__ */
|
|
4316
|
-
className: "
|
|
4317
|
-
children: /* @__PURE__ */
|
|
4318
|
-
|
|
4319
|
-
|
|
4320
|
-
|
|
4321
|
-
|
|
4322
|
-
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
children: [commandPart, isRunning && !output && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
|
|
4327
|
-
}) : /* @__PURE__ */ jsx("div", { className: "h-3 w-32 bg-muted rounded animate-pulse mt-2" })
|
|
4328
|
-
})]
|
|
4329
|
-
})
|
|
4330
|
-
}), (hasOutput || !isRunning) && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4331
|
-
className: hasOutput ? void 0 : "bg-muted/20 py-2",
|
|
4332
|
-
children: /* @__PURE__ */ jsxs("pre", {
|
|
4333
|
-
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"),
|
|
4334
|
-
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" })]
|
|
4791
|
+
}), expanded && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4792
|
+
className: "bg-transparent py-0",
|
|
4793
|
+
children: /* @__PURE__ */ jsx(ChatTerminalSurface, {
|
|
4794
|
+
command: commandPart,
|
|
4795
|
+
output,
|
|
4796
|
+
emptyLabel: card.emptyLabel,
|
|
4797
|
+
isRunning,
|
|
4798
|
+
hasOutput,
|
|
4799
|
+
isError: card.statusTone === "error",
|
|
4800
|
+
exitCode: meta.exitCode,
|
|
4801
|
+
workingDir: meta.workingDir
|
|
4335
4802
|
})
|
|
4336
|
-
})] })
|
|
4803
|
+
})] });
|
|
4337
4804
|
}
|
|
4338
|
-
function FileOperationView({ card, onFileOpen }) {
|
|
4805
|
+
function FileOperationView({ card, toolLabel, onFileOpen }) {
|
|
4339
4806
|
const output = card.output?.trim() ?? "";
|
|
4340
4807
|
const isRunning = card.statusTone === "running";
|
|
4341
|
-
const
|
|
4342
|
-
const hasContent = hasStructuredPreview || Boolean(output);
|
|
4808
|
+
const hasContent = Boolean(card.fileOperation?.blocks.length) || Boolean(output);
|
|
4343
4809
|
const previewBlocks = card.fileOperation?.blocks ?? [];
|
|
4344
4810
|
const previewLineCount = previewBlocks.reduce((count, block) => count + block.lines.length, 0);
|
|
4345
4811
|
const previewCharCount = previewBlocks.reduce((count, block) => {
|
|
@@ -4354,22 +4820,26 @@ function FileOperationView({ card, onFileOpen }) {
|
|
|
4354
4820
|
expandOnError: hasContent,
|
|
4355
4821
|
statusTone: card.statusTone
|
|
4356
4822
|
});
|
|
4823
|
+
const isEdit = card.toolName === "edit_file" || card.toolName === "write_file" || card.toolName === "apply_patch" || card.toolName === "file_change";
|
|
4357
4824
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4358
4825
|
card,
|
|
4359
|
-
|
|
4826
|
+
toolLabel,
|
|
4827
|
+
changeSummary: isEdit ? countFileOperationChanges(previewBlocks) : void 0,
|
|
4828
|
+
icon: isEdit ? Code2 : FileText,
|
|
4360
4829
|
expanded,
|
|
4361
4830
|
canExpand: hasContent || isRunning,
|
|
4362
|
-
hideSummary:
|
|
4831
|
+
hideSummary: false,
|
|
4363
4832
|
onToggle
|
|
4364
|
-
}), expanded && hasContent
|
|
4365
|
-
className: "
|
|
4833
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4834
|
+
className: "bg-transparent py-0",
|
|
4366
4835
|
children: /* @__PURE__ */ jsx(ToolCardFileOperationContent, {
|
|
4367
4836
|
card,
|
|
4368
|
-
onFileOpen
|
|
4837
|
+
onFileOpen,
|
|
4838
|
+
showPathRow: true
|
|
4369
4839
|
})
|
|
4370
|
-
})] });
|
|
4840
|
+
}) : null] });
|
|
4371
4841
|
}
|
|
4372
|
-
function SearchSnippetView({ card }) {
|
|
4842
|
+
function SearchSnippetView({ card, toolLabel, icon: Icon = Search }) {
|
|
4373
4843
|
const isRunning = card.statusTone === "running";
|
|
4374
4844
|
const output = card.output?.trim() ?? "";
|
|
4375
4845
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
@@ -4380,24 +4850,69 @@ function SearchSnippetView({ card }) {
|
|
|
4380
4850
|
});
|
|
4381
4851
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4382
4852
|
card,
|
|
4383
|
-
|
|
4853
|
+
toolLabel,
|
|
4854
|
+
icon: Icon,
|
|
4384
4855
|
expanded,
|
|
4385
4856
|
canExpand: !!output || isRunning,
|
|
4386
4857
|
onToggle
|
|
4387
|
-
}), expanded && output && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4388
|
-
className: "
|
|
4389
|
-
children:
|
|
4390
|
-
|
|
4858
|
+
}), expanded && output && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4859
|
+
className: "py-0",
|
|
4860
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
4861
|
+
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",
|
|
4862
|
+
children: output
|
|
4863
|
+
})
|
|
4864
|
+
})] });
|
|
4391
4865
|
}
|
|
4392
|
-
|
|
4866
|
+
//#endregion
|
|
4867
|
+
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-generic.tsx
|
|
4868
|
+
function GenericToolSection({ label, tone, children }) {
|
|
4869
|
+
const style = {
|
|
4870
|
+
input: {
|
|
4871
|
+
dot: "bg-muted-foreground/60",
|
|
4872
|
+
body: "text-foreground"
|
|
4873
|
+
},
|
|
4874
|
+
output: {
|
|
4875
|
+
dot: "bg-primary/70",
|
|
4876
|
+
body: "text-foreground"
|
|
4877
|
+
},
|
|
4878
|
+
error: {
|
|
4879
|
+
dot: "bg-rose-500/80",
|
|
4880
|
+
body: "text-rose-950/85"
|
|
4881
|
+
}
|
|
4882
|
+
}[tone];
|
|
4883
|
+
return /* @__PURE__ */ jsxs("section", {
|
|
4884
|
+
className: "overflow-hidden rounded-md border border-border/70 bg-muted/20",
|
|
4885
|
+
children: [/* @__PURE__ */ jsxs("div", {
|
|
4886
|
+
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",
|
|
4887
|
+
children: [/* @__PURE__ */ jsx("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }), /* @__PURE__ */ jsx("span", {
|
|
4888
|
+
className: "normal-case tracking-normal",
|
|
4889
|
+
children: label
|
|
4890
|
+
})]
|
|
4891
|
+
}), /* @__PURE__ */ jsx("div", {
|
|
4892
|
+
className: "w-full overflow-hidden",
|
|
4893
|
+
children: /* @__PURE__ */ jsx("pre", {
|
|
4894
|
+
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),
|
|
4895
|
+
children
|
|
4896
|
+
})
|
|
4897
|
+
})]
|
|
4898
|
+
});
|
|
4899
|
+
}
|
|
4900
|
+
function buildToolActionSlot(card, onToolAction, renderToolAgent) {
|
|
4901
|
+
const agentAction = card.agentId && renderToolAgent ? renderToolAgent(card.agentId) : null;
|
|
4902
|
+
const toolAction = card.action && onToolAction ? /* @__PURE__ */ jsx(ToolCardHeaderAction, {
|
|
4903
|
+
action: card.action,
|
|
4904
|
+
onAction: onToolAction
|
|
4905
|
+
}) : null;
|
|
4906
|
+
return agentAction || toolAction ? /* @__PURE__ */ jsxs(Fragment$1, { children: [agentAction, toolAction] }) : void 0;
|
|
4907
|
+
}
|
|
4908
|
+
function GenericToolCard({ card, toolLabel, icon: Icon = Wrench, onToolAction, renderToolAgent }) {
|
|
4393
4909
|
const input = card.input?.trim() ?? "";
|
|
4394
4910
|
const output = card.output?.trim() ?? "";
|
|
4395
4911
|
const isRunning = card.statusTone === "running";
|
|
4396
4912
|
const hasInputSection = input.length > 0;
|
|
4397
4913
|
const hasOutputSection = output.length > 0;
|
|
4398
4914
|
const hasContent = hasInputSection || hasOutputSection;
|
|
4399
|
-
const
|
|
4400
|
-
const outputLabel = card.outputLabel?.trim() || "Output";
|
|
4915
|
+
const actionSlot = buildToolActionSlot(card, onToolAction, renderToolAgent);
|
|
4401
4916
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4402
4917
|
canExpand: hasContent || isRunning,
|
|
4403
4918
|
isRunning,
|
|
@@ -4406,30 +4921,28 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
|
|
|
4406
4921
|
});
|
|
4407
4922
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4408
4923
|
card,
|
|
4409
|
-
|
|
4924
|
+
toolLabel,
|
|
4925
|
+
icon: Icon,
|
|
4410
4926
|
expanded,
|
|
4411
4927
|
canExpand: hasContent || isRunning,
|
|
4412
|
-
actionSlot
|
|
4413
|
-
action: card.action,
|
|
4414
|
-
onAction: onToolAction
|
|
4415
|
-
}) : null] }) : void 0,
|
|
4928
|
+
actionSlot,
|
|
4416
4929
|
onToggle
|
|
4417
|
-
}), expanded && hasContent
|
|
4418
|
-
className: "bg-transparent
|
|
4930
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsxs(ToolCardContent, {
|
|
4931
|
+
className: "bg-transparent py-0",
|
|
4419
4932
|
children: [
|
|
4420
|
-
hasInputSection
|
|
4421
|
-
label: inputLabel,
|
|
4933
|
+
hasInputSection ? /* @__PURE__ */ jsx(GenericToolSection, {
|
|
4934
|
+
label: card.inputLabel?.trim() || "Input",
|
|
4422
4935
|
tone: "input",
|
|
4423
4936
|
children: input
|
|
4424
|
-
}),
|
|
4425
|
-
hasInputSection && hasOutputSection
|
|
4426
|
-
hasOutputSection
|
|
4427
|
-
label: outputLabel,
|
|
4937
|
+
}) : null,
|
|
4938
|
+
hasInputSection && hasOutputSection ? /* @__PURE__ */ jsx("div", { className: "h-2" }) : null,
|
|
4939
|
+
hasOutputSection ? /* @__PURE__ */ jsx(GenericToolSection, {
|
|
4940
|
+
label: card.outputLabel?.trim() || "Output",
|
|
4428
4941
|
tone: card.statusTone === "error" ? "error" : "output",
|
|
4429
4942
|
children: output
|
|
4430
|
-
})
|
|
4943
|
+
}) : null
|
|
4431
4944
|
]
|
|
4432
|
-
})] });
|
|
4945
|
+
}) : null] });
|
|
4433
4946
|
}
|
|
4434
4947
|
//#endregion
|
|
4435
4948
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-panel-app.tsx
|
|
@@ -4451,34 +4964,370 @@ function isTerminalTool(name) {
|
|
|
4451
4964
|
const lowered = name.toLowerCase();
|
|
4452
4965
|
return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
|
|
4453
4966
|
}
|
|
4454
|
-
function
|
|
4967
|
+
function isFileTool(name) {
|
|
4455
4968
|
const lowered = name.toLowerCase();
|
|
4456
4969
|
return lowered === "read_file" || lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
4457
4970
|
}
|
|
4971
|
+
function isFileWriteTool(name) {
|
|
4972
|
+
const lowered = name.toLowerCase();
|
|
4973
|
+
return lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
4974
|
+
}
|
|
4458
4975
|
function isSearchTool(name) {
|
|
4459
4976
|
const lowered = name.toLowerCase();
|
|
4460
4977
|
return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
|
|
4461
4978
|
}
|
|
4462
|
-
|
|
4979
|
+
const BUILT_IN_TOOL_PRESENTATIONS = {
|
|
4980
|
+
list_dir: {
|
|
4981
|
+
statusKind: "directory",
|
|
4982
|
+
icon: FolderTree
|
|
4983
|
+
},
|
|
4984
|
+
web_fetch: {
|
|
4985
|
+
statusKind: "web",
|
|
4986
|
+
icon: Globe2
|
|
4987
|
+
},
|
|
4988
|
+
message: {
|
|
4989
|
+
statusKind: "message",
|
|
4990
|
+
icon: Send
|
|
4991
|
+
},
|
|
4992
|
+
sessions_list: {
|
|
4993
|
+
statusKind: "session",
|
|
4994
|
+
icon: MessageSquare
|
|
4995
|
+
},
|
|
4996
|
+
sessions_history: {
|
|
4997
|
+
statusKind: "session",
|
|
4998
|
+
icon: MessageSquare
|
|
4999
|
+
},
|
|
5000
|
+
sessions_request: {
|
|
5001
|
+
statusKind: "session",
|
|
5002
|
+
icon: MessageSquare
|
|
5003
|
+
},
|
|
5004
|
+
spawn: {
|
|
5005
|
+
statusKind: "agent",
|
|
5006
|
+
icon: Bot
|
|
5007
|
+
},
|
|
5008
|
+
sessions_spawn: {
|
|
5009
|
+
statusKind: "agent",
|
|
5010
|
+
icon: Bot
|
|
5011
|
+
},
|
|
5012
|
+
subagents: {
|
|
5013
|
+
statusKind: "agent",
|
|
5014
|
+
icon: Bot
|
|
5015
|
+
},
|
|
5016
|
+
memory_search: {
|
|
5017
|
+
statusKind: "memory",
|
|
5018
|
+
icon: Brain
|
|
5019
|
+
},
|
|
5020
|
+
memory_get: {
|
|
5021
|
+
statusKind: "memory",
|
|
5022
|
+
icon: Brain
|
|
5023
|
+
},
|
|
5024
|
+
cron: {
|
|
5025
|
+
statusKind: "schedule",
|
|
5026
|
+
icon: CalendarClock
|
|
5027
|
+
},
|
|
5028
|
+
gateway: {
|
|
5029
|
+
statusKind: "system",
|
|
5030
|
+
icon: Settings
|
|
5031
|
+
},
|
|
5032
|
+
view_image: {
|
|
5033
|
+
statusKind: "image",
|
|
5034
|
+
icon: ImageIcon
|
|
5035
|
+
},
|
|
5036
|
+
show_file: {
|
|
5037
|
+
statusKind: "display",
|
|
5038
|
+
icon: Eye
|
|
5039
|
+
},
|
|
5040
|
+
show_url: {
|
|
5041
|
+
statusKind: "display",
|
|
5042
|
+
icon: Eye
|
|
5043
|
+
},
|
|
5044
|
+
show_panel_app: {
|
|
5045
|
+
statusKind: "display",
|
|
5046
|
+
icon: Eye
|
|
5047
|
+
},
|
|
5048
|
+
show_content: {
|
|
5049
|
+
statusKind: "display",
|
|
5050
|
+
icon: Eye
|
|
5051
|
+
}
|
|
5052
|
+
};
|
|
5053
|
+
function resolveBuiltInToolPresentation(name) {
|
|
5054
|
+
return BUILT_IN_TOOL_PRESENTATIONS[name.toLowerCase()] ?? null;
|
|
5055
|
+
}
|
|
5056
|
+
function ChatToolCard({ card, toolStatusLabels, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
|
|
4463
5057
|
if (card.panelApp) return /* @__PURE__ */ jsx(PanelAppInlineToolCard, {
|
|
4464
5058
|
card,
|
|
4465
5059
|
onToolAction,
|
|
4466
5060
|
renderPanelAppCard
|
|
4467
5061
|
});
|
|
4468
|
-
|
|
4469
|
-
|
|
5062
|
+
const builtInPresentation = resolveBuiltInToolPresentation(card.toolName);
|
|
5063
|
+
const builtInLabel = builtInPresentation ? toolStatusLabels?.builtIn?.[builtInPresentation.statusKind]?.[card.statusTone] : void 0;
|
|
5064
|
+
if (isTerminalTool(card.toolName)) return /* @__PURE__ */ jsx(TerminalExecutionView, {
|
|
5065
|
+
card,
|
|
5066
|
+
toolLabel: toolStatusLabels?.terminal[card.statusTone]
|
|
5067
|
+
});
|
|
5068
|
+
if (isFileTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, {
|
|
4470
5069
|
card,
|
|
5070
|
+
toolLabel: isFileWriteTool(card.toolName) ? toolStatusLabels?.fileEdit[card.statusTone] : toolStatusLabels?.fileRead[card.statusTone],
|
|
4471
5071
|
onFileOpen
|
|
4472
5072
|
});
|
|
4473
|
-
if (isSearchTool(card.toolName)) return /* @__PURE__ */ jsx(SearchSnippetView, {
|
|
5073
|
+
if (isSearchTool(card.toolName)) return /* @__PURE__ */ jsx(SearchSnippetView, {
|
|
5074
|
+
card,
|
|
5075
|
+
toolLabel: builtInLabel ?? toolStatusLabels?.search[card.statusTone],
|
|
5076
|
+
icon: builtInPresentation?.icon
|
|
5077
|
+
});
|
|
4474
5078
|
return /* @__PURE__ */ jsx(GenericToolCard, {
|
|
4475
5079
|
card,
|
|
5080
|
+
toolLabel: builtInLabel,
|
|
5081
|
+
icon: builtInPresentation?.icon,
|
|
4476
5082
|
onToolAction,
|
|
4477
5083
|
renderToolAgent
|
|
4478
5084
|
});
|
|
4479
5085
|
}
|
|
4480
5086
|
//#endregion
|
|
5087
|
+
//#region src/components/chat/ui/chat-message-list/chat-tool-activity-group.tsx
|
|
5088
|
+
function ChatToolActivityGroup({ group, isUser, reasoningCharacterCountTemplates, toolStatusLabels, onToolAction, onFileOpen, renderToolAgent, renderPanelAppCard }) {
|
|
5089
|
+
const [open, setOpen] = useState(false);
|
|
5090
|
+
const toolCount = group.parts.filter((part) => part.type === "tool-card").length;
|
|
5091
|
+
const showWorkflowRail = open && toolCount > 1;
|
|
5092
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5093
|
+
className: "group/tool-activity",
|
|
5094
|
+
children: [/* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
5095
|
+
openGroup: "tool-activity",
|
|
5096
|
+
open,
|
|
5097
|
+
label: group.label,
|
|
5098
|
+
onClick: () => setOpen((current) => !current)
|
|
5099
|
+
}), open ? /* @__PURE__ */ jsx("div", {
|
|
5100
|
+
className: "text-[0.925rem] leading-[1.72]",
|
|
5101
|
+
children: group.parts.map((part, index) => {
|
|
5102
|
+
const isLast = index === group.parts.length - 1;
|
|
5103
|
+
return /* @__PURE__ */ jsxs("div", {
|
|
5104
|
+
className: "relative min-w-0",
|
|
5105
|
+
children: [showWorkflowRail ? /* @__PURE__ */ jsx("div", {
|
|
5106
|
+
"aria-hidden": "true",
|
|
5107
|
+
"data-tool-workflow-rail": "true",
|
|
5108
|
+
className: cn("pointer-events-none absolute left-[0.575em] w-px -translate-x-1/2 bg-border/70", index === 0 ? "bottom-0 top-[0.86em]" : isLast ? "top-0 h-[0.86em]" : "inset-y-0")
|
|
5109
|
+
}) : null, part.type === "tool-card" ? /* @__PURE__ */ jsx(ChatToolCard, {
|
|
5110
|
+
card: part.card,
|
|
5111
|
+
toolStatusLabels,
|
|
5112
|
+
onToolAction,
|
|
5113
|
+
onFileOpen,
|
|
5114
|
+
renderToolAgent,
|
|
5115
|
+
renderPanelAppCard
|
|
5116
|
+
}) : /* @__PURE__ */ jsx("div", {
|
|
5117
|
+
className: "pl-[calc(1.15em+0.375rem)]",
|
|
5118
|
+
children: /* @__PURE__ */ jsx(ChatReasoningBlock, {
|
|
5119
|
+
label: part.label,
|
|
5120
|
+
text: part.text,
|
|
5121
|
+
characterCountTemplates: reasoningCharacterCountTemplates,
|
|
5122
|
+
isUser,
|
|
5123
|
+
isInProgress: false
|
|
5124
|
+
})
|
|
5125
|
+
})]
|
|
5126
|
+
}, `tool-group-item-${group.startIndex + index}`);
|
|
5127
|
+
})
|
|
5128
|
+
}) : null]
|
|
5129
|
+
});
|
|
5130
|
+
}
|
|
5131
|
+
//#endregion
|
|
5132
|
+
//#region src/components/chat/ui/chat-message-list/chat-tool-activity-group.utils.ts
|
|
5133
|
+
const MAX_SUMMARY_SEGMENTS = 3;
|
|
5134
|
+
function isToolCardPart(part) {
|
|
5135
|
+
return part.type === "tool-card";
|
|
5136
|
+
}
|
|
5137
|
+
function resolveToolActivityFamily(toolName) {
|
|
5138
|
+
const lowered = toolName.toLowerCase();
|
|
5139
|
+
if (lowered === "list_dir") return "directory";
|
|
5140
|
+
if (lowered.includes("show_content") || lowered.includes("panel_app") || lowered.includes("panel")) return "panel";
|
|
5141
|
+
if (lowered === "spawn" || lowered.includes("subagent") || lowered.includes("session_request") || lowered.includes("agent") && !lowered.includes("read")) return "agent";
|
|
5142
|
+
if (lowered.includes("web_") || lowered.includes("_web") || lowered.includes("url") || lowered.includes("fetch") || lowered.includes("http")) return "web";
|
|
5143
|
+
if (lowered === "grep_search" || lowered === "find_files" || lowered === "search_files" || lowered.includes("search") || lowered.includes("grep")) return "search";
|
|
5144
|
+
if (lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered === "terminal" || lowered.includes("run_")) return "bash";
|
|
5145
|
+
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";
|
|
5146
|
+
if (lowered === "read_file" || lowered === "read" || lowered === "view_file" || lowered.includes("read_file") || lowered.startsWith("read_")) return "read";
|
|
5147
|
+
return "other";
|
|
5148
|
+
}
|
|
5149
|
+
function toneRank(tone) {
|
|
5150
|
+
if (tone === "error") return 0;
|
|
5151
|
+
if (tone === "cancelled") return 1;
|
|
5152
|
+
if (tone === "running") return 2;
|
|
5153
|
+
return 3;
|
|
5154
|
+
}
|
|
5155
|
+
function resolveActivityUnitKeys(card, family, callIndex) {
|
|
5156
|
+
if (family !== "read" && family !== "edit" && family !== "directory") return [`call:${callIndex}`];
|
|
5157
|
+
const paths = card.fileOperation?.blocks.map(({ path }) => path.trim()).filter(Boolean);
|
|
5158
|
+
const summary = card.summary?.replace(/^(path|file):\s*/i, "").trim();
|
|
5159
|
+
return paths?.length ? paths : [summary || `call:${callIndex}`];
|
|
5160
|
+
}
|
|
5161
|
+
/**
|
|
5162
|
+
* Natural category+count phrases only.
|
|
5163
|
+
* Examples: `Read 3 files`, `运行 2 条命令`, `搜索 2 次`.
|
|
5164
|
+
* No command/path payload.
|
|
5165
|
+
*/
|
|
5166
|
+
function formatSegment(segment, labels) {
|
|
5167
|
+
const template = labels.segmentTemplates[segment.family];
|
|
5168
|
+
const body = segment.count === 1 ? template.one : template.other.split("{count}").join(String(segment.count));
|
|
5169
|
+
if (segment.tone === "error") return `${body} ${labels.failedLabel}`;
|
|
5170
|
+
if (segment.tone === "cancelled") return `${body} ${labels.cancelledLabel}`;
|
|
5171
|
+
return body;
|
|
5172
|
+
}
|
|
5173
|
+
function buildToolActivitySegments(cards) {
|
|
5174
|
+
const order = [];
|
|
5175
|
+
const byFamily = /* @__PURE__ */ new Map();
|
|
5176
|
+
for (const [callIndex, card] of cards.entries()) {
|
|
5177
|
+
const family = resolveToolActivityFamily(card.toolName);
|
|
5178
|
+
const unitKeys = resolveActivityUnitKeys(card, family, callIndex);
|
|
5179
|
+
const existing = byFamily.get(family);
|
|
5180
|
+
if (!existing) {
|
|
5181
|
+
order.push(family);
|
|
5182
|
+
byFamily.set(family, {
|
|
5183
|
+
units: new Set(unitKeys),
|
|
5184
|
+
tone: card.statusTone
|
|
5185
|
+
});
|
|
5186
|
+
continue;
|
|
5187
|
+
}
|
|
5188
|
+
unitKeys.forEach((unitKey) => existing.units.add(unitKey));
|
|
5189
|
+
if (toneRank(card.statusTone) < toneRank(existing.tone)) existing.tone = card.statusTone;
|
|
5190
|
+
}
|
|
5191
|
+
const segments = order.map((family) => {
|
|
5192
|
+
const entry = byFamily.get(family);
|
|
5193
|
+
return {
|
|
5194
|
+
family,
|
|
5195
|
+
count: entry.units.size,
|
|
5196
|
+
tone: entry.tone
|
|
5197
|
+
};
|
|
5198
|
+
});
|
|
5199
|
+
segments.sort((left, right) => {
|
|
5200
|
+
const toneDelta = toneRank(left.tone) - toneRank(right.tone);
|
|
5201
|
+
if (toneDelta !== 0) return toneDelta;
|
|
5202
|
+
return order.indexOf(left.family) - order.indexOf(right.family);
|
|
5203
|
+
});
|
|
5204
|
+
return segments;
|
|
5205
|
+
}
|
|
5206
|
+
function formatToolActivityGroupLabel(params) {
|
|
5207
|
+
const { labels, segments } = params;
|
|
5208
|
+
if (segments.length === 0) return "";
|
|
5209
|
+
const visible = segments.slice(0, MAX_SUMMARY_SEGMENTS);
|
|
5210
|
+
const hiddenCount = segments.length - visible.length;
|
|
5211
|
+
const parts = visible.map((segment) => formatSegment(segment, labels));
|
|
5212
|
+
if (hiddenCount > 0) parts.push(`+${hiddenCount}`);
|
|
5213
|
+
return parts.join(" · ");
|
|
5214
|
+
}
|
|
5215
|
+
/**
|
|
5216
|
+
* Group tool cards separated only by reasoning into one visible workflow.
|
|
5217
|
+
* Markdown, files and unknown parts still break the run.
|
|
5218
|
+
* Single tool-cards stay ungrouped so they keep their native card UI.
|
|
5219
|
+
*/
|
|
5220
|
+
function groupConsecutiveToolParts(parts, labels) {
|
|
5221
|
+
const blocks = [];
|
|
5222
|
+
let index = 0;
|
|
5223
|
+
while (index < parts.length) {
|
|
5224
|
+
const part = parts[index];
|
|
5225
|
+
if (part.type !== "tool-card") {
|
|
5226
|
+
blocks.push({
|
|
5227
|
+
kind: "part",
|
|
5228
|
+
key: `part-${index}`,
|
|
5229
|
+
index,
|
|
5230
|
+
part
|
|
5231
|
+
});
|
|
5232
|
+
index += 1;
|
|
5233
|
+
continue;
|
|
5234
|
+
}
|
|
5235
|
+
const startIndex = index;
|
|
5236
|
+
const candidateParts = [];
|
|
5237
|
+
let lastToolOffset = -1;
|
|
5238
|
+
while (index < parts.length && (parts[index]?.type === "tool-card" || parts[index]?.type === "reasoning")) {
|
|
5239
|
+
const candidate = parts[index];
|
|
5240
|
+
candidateParts.push(candidate);
|
|
5241
|
+
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5242
|
+
index += 1;
|
|
5243
|
+
}
|
|
5244
|
+
const toolParts = candidateParts.filter(isToolCardPart);
|
|
5245
|
+
if (toolParts.length === 1) {
|
|
5246
|
+
blocks.push({
|
|
5247
|
+
kind: "part",
|
|
5248
|
+
key: `part-${startIndex}`,
|
|
5249
|
+
index: startIndex,
|
|
5250
|
+
part
|
|
5251
|
+
});
|
|
5252
|
+
index = startIndex + 1;
|
|
5253
|
+
continue;
|
|
5254
|
+
}
|
|
5255
|
+
const groupedParts = candidateParts.slice(0, lastToolOffset + 1);
|
|
5256
|
+
index = startIndex + groupedParts.length;
|
|
5257
|
+
const segments = buildToolActivitySegments(toolParts.map((toolPart) => toolPart.card));
|
|
5258
|
+
const label = formatToolActivityGroupLabel({
|
|
5259
|
+
segments,
|
|
5260
|
+
labels
|
|
5261
|
+
});
|
|
5262
|
+
blocks.push({
|
|
5263
|
+
kind: "tool-group",
|
|
5264
|
+
key: `tool-group-${startIndex}-${index - 1}`,
|
|
5265
|
+
group: {
|
|
5266
|
+
key: `tool-group-${startIndex}-${index - 1}`,
|
|
5267
|
+
startIndex,
|
|
5268
|
+
endIndex: index - 1,
|
|
5269
|
+
parts: groupedParts,
|
|
5270
|
+
segments,
|
|
5271
|
+
label
|
|
5272
|
+
}
|
|
5273
|
+
});
|
|
5274
|
+
}
|
|
5275
|
+
return blocks;
|
|
5276
|
+
}
|
|
5277
|
+
//#endregion
|
|
4481
5278
|
//#region src/components/chat/ui/chat-message-list/chat-message.tsx
|
|
5279
|
+
const DEFAULT_TOOL_ACTIVITY_LABELS = {
|
|
5280
|
+
segmentTemplates: {
|
|
5281
|
+
read: {
|
|
5282
|
+
one: "Read 1 file",
|
|
5283
|
+
other: "Read {count} files"
|
|
5284
|
+
},
|
|
5285
|
+
edit: {
|
|
5286
|
+
one: "Edit 1 file",
|
|
5287
|
+
other: "Edit {count} files"
|
|
5288
|
+
},
|
|
5289
|
+
directory: {
|
|
5290
|
+
one: "View 1 directory",
|
|
5291
|
+
other: "View {count} directories"
|
|
5292
|
+
},
|
|
5293
|
+
search: {
|
|
5294
|
+
one: "Search 1 time",
|
|
5295
|
+
other: "Search {count} times"
|
|
5296
|
+
},
|
|
5297
|
+
bash: {
|
|
5298
|
+
one: "Run 1 command",
|
|
5299
|
+
other: "Run {count} commands"
|
|
5300
|
+
},
|
|
5301
|
+
web: {
|
|
5302
|
+
one: "Open 1 page",
|
|
5303
|
+
other: "Open {count} pages"
|
|
5304
|
+
},
|
|
5305
|
+
agent: {
|
|
5306
|
+
one: "Start 1 subtask",
|
|
5307
|
+
other: "Start {count} subtasks"
|
|
5308
|
+
},
|
|
5309
|
+
panel: {
|
|
5310
|
+
one: "Show 1 result",
|
|
5311
|
+
other: "Show {count} results"
|
|
5312
|
+
},
|
|
5313
|
+
other: {
|
|
5314
|
+
one: "Use 1 tool",
|
|
5315
|
+
other: "Use {count} tools"
|
|
5316
|
+
}
|
|
5317
|
+
},
|
|
5318
|
+
failedLabel: "failed",
|
|
5319
|
+
cancelledLabel: "cancelled"
|
|
5320
|
+
};
|
|
5321
|
+
function resolveToolActivityLabels(texts) {
|
|
5322
|
+
return {
|
|
5323
|
+
segmentTemplates: {
|
|
5324
|
+
...DEFAULT_TOOL_ACTIVITY_LABELS.segmentTemplates,
|
|
5325
|
+
...texts.toolActivitySegmentTemplates ?? {}
|
|
5326
|
+
},
|
|
5327
|
+
failedLabel: texts.toolActivityFailedLabel ?? DEFAULT_TOOL_ACTIVITY_LABELS.failedLabel,
|
|
5328
|
+
cancelledLabel: texts.toolActivityCancelledLabel ?? DEFAULT_TOOL_ACTIVITY_LABELS.cancelledLabel
|
|
5329
|
+
};
|
|
5330
|
+
}
|
|
4482
5331
|
function isMessageInProgress(status) {
|
|
4483
5332
|
return status === "pending" || status === "streaming";
|
|
4484
5333
|
}
|
|
@@ -4498,7 +5347,7 @@ function splitAssistantProcess(message) {
|
|
|
4498
5347
|
finalParts: message.parts.slice(lastProcessPartIndex + 1)
|
|
4499
5348
|
};
|
|
4500
5349
|
}
|
|
4501
|
-
function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFileOpen, onToolAction, part, renderInlineDisplay, renderPanelAppCard, renderToolAgent, role, texts }) {
|
|
5350
|
+
function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolAction, part, renderInlineDisplay, renderPanelAppCard, renderToolAgent, role, texts }) {
|
|
4502
5351
|
const { type } = part;
|
|
4503
5352
|
if (type === "markdown") {
|
|
4504
5353
|
const { inlineTokens, text } = part;
|
|
@@ -4508,6 +5357,7 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4508
5357
|
texts,
|
|
4509
5358
|
inlineTokens,
|
|
4510
5359
|
onFileOpen,
|
|
5360
|
+
onInlineTokenClick,
|
|
4511
5361
|
renderInlineDisplay
|
|
4512
5362
|
}, `markdown-${index}`);
|
|
4513
5363
|
}
|
|
@@ -4516,29 +5366,29 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4516
5366
|
return /* @__PURE__ */ jsx(ChatReasoningBlock, {
|
|
4517
5367
|
label,
|
|
4518
5368
|
text,
|
|
5369
|
+
characterCountTemplates: texts.reasoningCharacterCountTemplates,
|
|
4519
5370
|
isUser,
|
|
4520
5371
|
isInProgress: isInProgress && isLastPart
|
|
4521
5372
|
}, `reasoning-${index}`);
|
|
4522
5373
|
}
|
|
4523
5374
|
if (type === "tool-card") {
|
|
4524
5375
|
const { card } = part;
|
|
4525
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
|
|
4532
|
-
|
|
4533
|
-
})
|
|
4534
|
-
}, `tool-${index}`);
|
|
5376
|
+
return /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(ChatToolCard, {
|
|
5377
|
+
card,
|
|
5378
|
+
toolStatusLabels: texts.toolStatusLabels,
|
|
5379
|
+
onToolAction,
|
|
5380
|
+
onFileOpen,
|
|
5381
|
+
renderToolAgent,
|
|
5382
|
+
renderPanelAppCard
|
|
5383
|
+
}) }, `tool-${index}`);
|
|
4535
5384
|
}
|
|
4536
5385
|
if (type === "file") {
|
|
4537
5386
|
const { file } = part;
|
|
4538
5387
|
return /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
4539
5388
|
file,
|
|
4540
5389
|
isUser,
|
|
4541
|
-
texts
|
|
5390
|
+
texts,
|
|
5391
|
+
onOpen: onAttachmentOpen
|
|
4542
5392
|
}, `file-${index}`);
|
|
4543
5393
|
}
|
|
4544
5394
|
if (type === "unknown") {
|
|
@@ -4560,49 +5410,102 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4560
5410
|
}
|
|
4561
5411
|
return null;
|
|
4562
5412
|
}
|
|
4563
|
-
|
|
5413
|
+
function renderMessageParts(params) {
|
|
5414
|
+
const { isInProgress, isUser, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolAction, parts, renderInlineDisplay, renderPanelAppCard, renderToolAgent, role, texts, indexOffset = 0 } = params;
|
|
5415
|
+
return groupConsecutiveToolParts(parts, resolveToolActivityLabels(texts)).map((block) => {
|
|
5416
|
+
if (block.kind === "tool-group") return /* @__PURE__ */ jsx(ChatToolActivityGroup, {
|
|
5417
|
+
group: block.group,
|
|
5418
|
+
isUser,
|
|
5419
|
+
reasoningCharacterCountTemplates: texts.reasoningCharacterCountTemplates,
|
|
5420
|
+
toolStatusLabels: texts.toolStatusLabels,
|
|
5421
|
+
onToolAction,
|
|
5422
|
+
onFileOpen,
|
|
5423
|
+
renderToolAgent,
|
|
5424
|
+
renderPanelAppCard
|
|
5425
|
+
}, block.key);
|
|
5426
|
+
return renderChatMessagePart({
|
|
5427
|
+
part: block.part,
|
|
5428
|
+
index: indexOffset + block.index,
|
|
5429
|
+
role,
|
|
5430
|
+
isUser,
|
|
5431
|
+
isInProgress,
|
|
5432
|
+
isLastPart: indexOffset + block.index === indexOffset + parts.length - 1,
|
|
5433
|
+
texts,
|
|
5434
|
+
onToolAction,
|
|
5435
|
+
onFileOpen,
|
|
5436
|
+
onAttachmentOpen,
|
|
5437
|
+
onInlineTokenClick,
|
|
5438
|
+
renderInlineDisplay,
|
|
5439
|
+
renderToolAgent,
|
|
5440
|
+
renderPanelAppCard
|
|
5441
|
+
});
|
|
5442
|
+
});
|
|
5443
|
+
}
|
|
5444
|
+
const ChatMessage = memo(function ChatMessage({ message, texts, onToolAction, onFileOpen, onAttachmentOpen, onInlineTokenClick, renderInlineDisplay, renderToolAgent, renderPanelAppCard }) {
|
|
4564
5445
|
const { role } = message;
|
|
4565
5446
|
const isUser = role === "user";
|
|
4566
5447
|
const isInProgress = isMessageInProgress(message.status);
|
|
4567
5448
|
const processSplit = splitAssistantProcess(message);
|
|
4568
|
-
const
|
|
4569
|
-
part,
|
|
4570
|
-
index,
|
|
4571
|
-
role,
|
|
4572
|
-
isUser,
|
|
4573
|
-
isInProgress,
|
|
4574
|
-
isLastPart: index === message.parts.length - 1,
|
|
4575
|
-
texts,
|
|
4576
|
-
onToolAction,
|
|
4577
|
-
onFileOpen,
|
|
4578
|
-
renderInlineDisplay,
|
|
4579
|
-
renderToolAgent,
|
|
4580
|
-
renderPanelAppCard
|
|
4581
|
-
});
|
|
5449
|
+
const [processOpen, setProcessOpen] = useState(false);
|
|
4582
5450
|
return /* @__PURE__ */ jsx("div", {
|
|
4583
|
-
className: cn("inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm has-[[data-chat-message-wide-content=true]]:w-full", isUser ? "border-primary bg-primary py-
|
|
5451
|
+
className: cn("inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm has-[[data-chat-message-wide-content=true]]:w-full", isUser ? "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"),
|
|
4584
5452
|
children: /* @__PURE__ */ jsx("div", {
|
|
4585
|
-
className: "space-y-
|
|
4586
|
-
children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("
|
|
4587
|
-
className: "group/process
|
|
4588
|
-
children: [/* @__PURE__ */
|
|
4589
|
-
className: "
|
|
4590
|
-
children:
|
|
4591
|
-
|
|
4592
|
-
|
|
4593
|
-
|
|
4594
|
-
|
|
4595
|
-
|
|
4596
|
-
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
5453
|
+
className: "space-y-0",
|
|
5454
|
+
children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("div", {
|
|
5455
|
+
className: "group/process",
|
|
5456
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
5457
|
+
className: "mb-2 border-b border-border/60 pb-2",
|
|
5458
|
+
children: /* @__PURE__ */ jsx(ChatCollapsibleMetaSummary, {
|
|
5459
|
+
openGroup: "process",
|
|
5460
|
+
open: processOpen,
|
|
5461
|
+
label: message.processSummary?.label,
|
|
5462
|
+
onClick: () => setProcessOpen((current) => !current)
|
|
5463
|
+
})
|
|
5464
|
+
}), processOpen ? /* @__PURE__ */ jsx("div", {
|
|
5465
|
+
className: "space-y-0",
|
|
5466
|
+
children: renderMessageParts({
|
|
5467
|
+
parts: processSplit.processParts,
|
|
5468
|
+
role,
|
|
5469
|
+
isUser,
|
|
5470
|
+
isInProgress,
|
|
5471
|
+
texts,
|
|
5472
|
+
onToolAction,
|
|
5473
|
+
onFileOpen,
|
|
5474
|
+
onAttachmentOpen,
|
|
5475
|
+
onInlineTokenClick,
|
|
5476
|
+
renderInlineDisplay,
|
|
5477
|
+
renderToolAgent,
|
|
5478
|
+
renderPanelAppCard
|
|
5479
|
+
})
|
|
5480
|
+
}) : null]
|
|
5481
|
+
}), renderMessageParts({
|
|
5482
|
+
parts: processSplit.finalParts,
|
|
5483
|
+
role,
|
|
5484
|
+
isUser,
|
|
5485
|
+
isInProgress,
|
|
5486
|
+
texts,
|
|
5487
|
+
onToolAction,
|
|
5488
|
+
onFileOpen,
|
|
5489
|
+
onAttachmentOpen,
|
|
5490
|
+
onInlineTokenClick,
|
|
5491
|
+
renderInlineDisplay,
|
|
5492
|
+
renderToolAgent,
|
|
5493
|
+
renderPanelAppCard,
|
|
5494
|
+
indexOffset: processSplit.processParts.length
|
|
5495
|
+
})] }) : renderMessageParts({
|
|
5496
|
+
parts: message.parts,
|
|
5497
|
+
role,
|
|
5498
|
+
isUser,
|
|
5499
|
+
isInProgress,
|
|
5500
|
+
texts,
|
|
5501
|
+
onToolAction,
|
|
5502
|
+
onFileOpen,
|
|
5503
|
+
onAttachmentOpen,
|
|
5504
|
+
onInlineTokenClick,
|
|
5505
|
+
renderInlineDisplay,
|
|
5506
|
+
renderToolAgent,
|
|
5507
|
+
renderPanelAppCard
|
|
5508
|
+
})
|
|
4606
5509
|
})
|
|
4607
5510
|
});
|
|
4608
5511
|
});
|
|
@@ -4702,7 +5605,7 @@ function ChatTypingIndicator({ label }) {
|
|
|
4702
5605
|
})]
|
|
4703
5606
|
});
|
|
4704
5607
|
}
|
|
4705
|
-
function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAction, renderInlineDisplay, renderPanelAppCard, renderToolAgent, texts }) {
|
|
5608
|
+
function ChatMessageList({ className, isSending, messages, onAttachmentOpen, onFileOpen, onInlineTokenClick, onToolAction, renderInlineDisplay, renderPanelAppCard, renderToolAgent, texts }) {
|
|
4706
5609
|
const visibleMessages = messages.filter(hasRenderableMessageContent);
|
|
4707
5610
|
const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
|
|
4708
5611
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -4721,6 +5624,8 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
|
|
|
4721
5624
|
texts,
|
|
4722
5625
|
onToolAction,
|
|
4723
5626
|
onFileOpen,
|
|
5627
|
+
onAttachmentOpen,
|
|
5628
|
+
onInlineTokenClick,
|
|
4724
5629
|
renderInlineDisplay,
|
|
4725
5630
|
renderToolAgent,
|
|
4726
5631
|
renderPanelAppCard
|