@nextclaw/agent-chat-ui 0.6.2 → 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 +65 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1205 -306
- 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",
|
|
@@ -2419,15 +2420,15 @@ function resolveInlineTokenTone(kind) {
|
|
|
2419
2420
|
if (kind === "panel_app") return "panel_app";
|
|
2420
2421
|
return "default";
|
|
2421
2422
|
}
|
|
2422
|
-
function resolveInlineTokenBadgeClassName(tone, isUser) {
|
|
2423
|
-
if (tone === "skill") return
|
|
2424
|
-
if (tone === "panel_app") return isUser ? "border-primary-foreground/
|
|
2425
|
-
return isUser ? "border-primary-foreground/
|
|
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";
|
|
2426
2427
|
}
|
|
2427
2428
|
function resolveInlineTokenIconClassName(tone, isUser) {
|
|
2428
|
-
if (tone === "skill") return
|
|
2429
|
-
if (tone === "panel_app") return isUser ? "text-primary-foreground/
|
|
2430
|
-
return isUser ? "text-primary-foreground/
|
|
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";
|
|
2431
2432
|
}
|
|
2432
2433
|
function renderInlineTokenIcon(tone) {
|
|
2433
2434
|
return tone === "panel_app" ? /* @__PURE__ */ jsx(AppWindow, {
|
|
@@ -2438,18 +2439,32 @@ function renderInlineTokenIcon(tone) {
|
|
|
2438
2439
|
className: "h-3 w-3"
|
|
2439
2440
|
});
|
|
2440
2441
|
}
|
|
2441
|
-
function ChatInlineTokenBadge({ kind, label, isUser }) {
|
|
2442
|
+
function ChatInlineTokenBadge({ kind, label, isUser, onClick }) {
|
|
2442
2443
|
const tone = resolveInlineTokenTone(kind);
|
|
2443
|
-
|
|
2444
|
-
|
|
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,
|
|
2445
2456
|
title: label,
|
|
2446
|
-
|
|
2447
|
-
|
|
2448
|
-
|
|
2449
|
-
|
|
2450
|
-
|
|
2451
|
-
|
|
2452
|
-
|
|
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
|
|
2453
2468
|
});
|
|
2454
2469
|
}
|
|
2455
2470
|
//#endregion
|
|
@@ -2655,7 +2670,7 @@ const REGISTERED_LANGUAGES = [
|
|
|
2655
2670
|
aliases: ["patch"]
|
|
2656
2671
|
}
|
|
2657
2672
|
];
|
|
2658
|
-
function escapeHtml(value) {
|
|
2673
|
+
function escapeHtml$1(value) {
|
|
2659
2674
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
2660
2675
|
}
|
|
2661
2676
|
function normalizeLanguage(value) {
|
|
@@ -2686,7 +2701,7 @@ var ChatCodeSyntaxHighlighter = class {
|
|
|
2686
2701
|
return this.createPlainTextResult(code, normalizedLanguage || "text");
|
|
2687
2702
|
};
|
|
2688
2703
|
this.createPlainTextResult = (code, language) => ({
|
|
2689
|
-
html: escapeHtml(code),
|
|
2704
|
+
html: escapeHtml$1(code),
|
|
2690
2705
|
language,
|
|
2691
2706
|
highlighted: false
|
|
2692
2707
|
});
|
|
@@ -3054,7 +3069,7 @@ function readStringProp(props, key) {
|
|
|
3054
3069
|
const value = props[key];
|
|
3055
3070
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3056
3071
|
}
|
|
3057
|
-
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, renderInlineDisplay }) {
|
|
3072
|
+
function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens, onFileOpen, onInlineTokenClick, renderInlineDisplay }) {
|
|
3058
3073
|
const isUser = role === "user";
|
|
3059
3074
|
const remarkPlugins = useMemo(() => inlineTokens && inlineTokens.length > 0 ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm], [inlineTokens]);
|
|
3060
3075
|
const markdownComponents = useMemo(() => ({
|
|
@@ -3068,7 +3083,13 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3068
3083
|
if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
|
|
3069
3084
|
kind,
|
|
3070
3085
|
label,
|
|
3071
|
-
isUser
|
|
3086
|
+
isUser,
|
|
3087
|
+
onClick: onInlineTokenClick ? () => onInlineTokenClick({
|
|
3088
|
+
kind,
|
|
3089
|
+
key,
|
|
3090
|
+
label,
|
|
3091
|
+
rawText
|
|
3092
|
+
}) : void 0
|
|
3072
3093
|
});
|
|
3073
3094
|
return /* @__PURE__ */ jsx("span", {
|
|
3074
3095
|
...rest,
|
|
@@ -3152,6 +3173,7 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3152
3173
|
inline,
|
|
3153
3174
|
isUser,
|
|
3154
3175
|
onFileOpen,
|
|
3176
|
+
onInlineTokenClick,
|
|
3155
3177
|
renderInlineDisplay,
|
|
3156
3178
|
texts
|
|
3157
3179
|
]);
|
|
@@ -3168,16 +3190,16 @@ function ChatMessageMarkdown({ text, role, texts, inline = false, inlineTokens,
|
|
|
3168
3190
|
//#endregion
|
|
3169
3191
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/meta.ts
|
|
3170
3192
|
const FILE_CATEGORY_TILE_CLASSES = {
|
|
3171
|
-
archive: "
|
|
3172
|
-
audio: "
|
|
3173
|
-
code: "
|
|
3174
|
-
data: "
|
|
3175
|
-
document: "
|
|
3176
|
-
generic: "
|
|
3177
|
-
image: "
|
|
3178
|
-
pdf: "
|
|
3179
|
-
sheet: "
|
|
3180
|
-
video: "
|
|
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"
|
|
3181
3203
|
};
|
|
3182
3204
|
const CODE_EXTENSIONS = new Set([
|
|
3183
3205
|
"c",
|
|
@@ -3387,77 +3409,166 @@ function readFileCategoryLabel(category, texts) {
|
|
|
3387
3409
|
}
|
|
3388
3410
|
function renderMetaLine(categoryLabel, sizeLabel, isUser) {
|
|
3389
3411
|
return /* @__PURE__ */ jsx("div", {
|
|
3390
|
-
className: cn("mt-
|
|
3412
|
+
className: cn("mt-0.5 text-[11px] leading-4", isUser ? "text-foreground/55" : "text-muted-foreground"),
|
|
3391
3413
|
children: sizeLabel ? `${categoryLabel} · ${sizeLabel}` : categoryLabel
|
|
3392
3414
|
});
|
|
3393
3415
|
}
|
|
3394
3416
|
function renderActionPill(label, isUser, isInteractive) {
|
|
3395
3417
|
return /* @__PURE__ */ jsx("span", {
|
|
3396
|
-
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"),
|
|
3397
3419
|
children: label
|
|
3398
3420
|
});
|
|
3399
3421
|
}
|
|
3400
|
-
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);
|
|
3401
3436
|
return /* @__PURE__ */ jsx("a", {
|
|
3402
3437
|
href,
|
|
3403
3438
|
target: "_blank",
|
|
3404
3439
|
rel: "noreferrer",
|
|
3405
|
-
className
|
|
3440
|
+
className,
|
|
3406
3441
|
children: label
|
|
3407
3442
|
});
|
|
3408
3443
|
}
|
|
3409
3444
|
function FileCategoryGlyph({ category, isUser }) {
|
|
3410
3445
|
const Icon = FILE_CATEGORY_ICONS[category];
|
|
3411
3446
|
return /* @__PURE__ */ jsx("div", {
|
|
3412
|
-
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]),
|
|
3413
3448
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3414
|
-
className: cn("h-
|
|
3415
|
-
strokeWidth: 2
|
|
3449
|
+
className: cn("h-4 w-4", isUser ? "text-foreground/70" : "text-current"),
|
|
3450
|
+
strokeWidth: 2
|
|
3416
3451
|
})
|
|
3417
3452
|
});
|
|
3418
3453
|
}
|
|
3419
|
-
function
|
|
3420
|
-
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);
|
|
3421
3508
|
if (!file.dataUrl) return null;
|
|
3422
|
-
|
|
3423
|
-
|
|
3424
|
-
|
|
3425
|
-
|
|
3426
|
-
|
|
3427
|
-
|
|
3428
|
-
|
|
3429
|
-
|
|
3430
|
-
|
|
3431
|
-
|
|
3432
|
-
|
|
3433
|
-
|
|
3434
|
-
|
|
3435
|
-
children:
|
|
3436
|
-
|
|
3437
|
-
|
|
3438
|
-
|
|
3439
|
-
|
|
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",
|
|
3440
3546
|
children: sizeLabel
|
|
3441
|
-
})
|
|
3442
|
-
})
|
|
3443
|
-
|
|
3444
|
-
})
|
|
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] });
|
|
3445
3556
|
}
|
|
3446
3557
|
function renderFileCardHeader(params) {
|
|
3447
3558
|
const { category, file, categoryLabel, sizeLabel, isUser, action } = params;
|
|
3448
3559
|
return /* @__PURE__ */ jsxs("div", {
|
|
3449
|
-
className: "flex items-center gap-
|
|
3560
|
+
className: "flex items-center gap-2.5 px-2.5 py-2",
|
|
3450
3561
|
children: [/* @__PURE__ */ jsx(FileCategoryGlyph, {
|
|
3451
3562
|
category,
|
|
3452
3563
|
isUser
|
|
3453
3564
|
}), /* @__PURE__ */ jsx("div", {
|
|
3454
3565
|
className: "min-w-0 flex-1",
|
|
3455
3566
|
children: /* @__PURE__ */ jsxs("div", {
|
|
3456
|
-
className: "flex items-center gap-
|
|
3567
|
+
className: "flex items-center gap-2",
|
|
3457
3568
|
children: [/* @__PURE__ */ jsxs("div", {
|
|
3458
3569
|
className: "min-w-0 flex-1",
|
|
3459
3570
|
children: [/* @__PURE__ */ jsx("div", {
|
|
3460
|
-
className: "truncate text-[
|
|
3571
|
+
className: "truncate text-[13px] font-medium leading-5",
|
|
3461
3572
|
children: file.label
|
|
3462
3573
|
}), renderMetaLine(categoryLabel, sizeLabel, isUser)]
|
|
3463
3574
|
}), action]
|
|
@@ -3466,7 +3577,7 @@ function renderFileCardHeader(params) {
|
|
|
3466
3577
|
});
|
|
3467
3578
|
}
|
|
3468
3579
|
function renderInlineMediaCard(params) {
|
|
3469
|
-
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel } = params;
|
|
3580
|
+
const { category, file, categoryLabel, sizeLabel, isUser, shellClasses, actionLabel, onOpen } = params;
|
|
3470
3581
|
if (!file.dataUrl || category !== "audio" && category !== "video") return null;
|
|
3471
3582
|
const mediaMimeType = resolveRenderableMimeType(file);
|
|
3472
3583
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -3477,9 +3588,14 @@ function renderInlineMediaCard(params) {
|
|
|
3477
3588
|
categoryLabel,
|
|
3478
3589
|
sizeLabel,
|
|
3479
3590
|
isUser,
|
|
3480
|
-
action:
|
|
3591
|
+
action: renderActionControl({
|
|
3592
|
+
label: actionLabel,
|
|
3593
|
+
href: file.dataUrl,
|
|
3594
|
+
isUser,
|
|
3595
|
+
onOpen: onOpen ? () => onOpen(file) : void 0
|
|
3596
|
+
})
|
|
3481
3597
|
}), /* @__PURE__ */ jsx("div", {
|
|
3482
|
-
className: "px-
|
|
3598
|
+
className: "px-2.5 pb-2.5",
|
|
3483
3599
|
children: category === "audio" ? /* @__PURE__ */ jsx("audio", {
|
|
3484
3600
|
controls: true,
|
|
3485
3601
|
preload: "metadata",
|
|
@@ -3490,7 +3606,7 @@ function renderInlineMediaCard(params) {
|
|
|
3490
3606
|
...mediaMimeType ? { type: mediaMimeType } : {}
|
|
3491
3607
|
})
|
|
3492
3608
|
}) : /* @__PURE__ */ jsx("div", {
|
|
3493
|
-
className: cn("overflow-hidden rounded-
|
|
3609
|
+
className: cn("overflow-hidden rounded-lg", isUser ? "bg-black/10" : "bg-muted ring-1 ring-border/70"),
|
|
3494
3610
|
children: /* @__PURE__ */ jsx("video", {
|
|
3495
3611
|
controls: true,
|
|
3496
3612
|
preload: "metadata",
|
|
@@ -3506,20 +3622,22 @@ function renderInlineMediaCard(params) {
|
|
|
3506
3622
|
})]
|
|
3507
3623
|
});
|
|
3508
3624
|
}
|
|
3509
|
-
function ChatMessageFile({ file, isUser = false, texts }) {
|
|
3625
|
+
function ChatMessageFile({ file, isUser = false, texts, onOpen }) {
|
|
3510
3626
|
const { category, sizeLabel } = buildChatMessageFileMeta(file);
|
|
3511
3627
|
const renderAsImage = isImageFileLike(file) && Boolean(file.dataUrl);
|
|
3512
3628
|
const renderAsAudio = category === "audio" && Boolean(file.dataUrl);
|
|
3513
3629
|
const renderAsVideo = category === "video" && Boolean(file.dataUrl);
|
|
3514
3630
|
const isInteractive = Boolean(file.dataUrl);
|
|
3515
3631
|
const actionLabel = isInteractive ? texts?.attachmentOpenLabel ?? "Open" : texts?.attachmentAttachedLabel ?? "Attached";
|
|
3632
|
+
const expandLabel = texts?.attachmentExpandLabel ?? "Expand image";
|
|
3633
|
+
const closeLabel = texts?.attachmentCloseLabel ?? "Close preview";
|
|
3516
3634
|
const categoryLabel = readFileCategoryLabel(category, texts);
|
|
3517
|
-
const shellClasses = cn("block overflow-hidden rounded-
|
|
3518
|
-
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,
|
|
3519
3639
|
file,
|
|
3520
|
-
|
|
3521
|
-
sizeLabel,
|
|
3522
|
-
isUser
|
|
3640
|
+
sizeLabel
|
|
3523
3641
|
});
|
|
3524
3642
|
if (renderAsAudio || renderAsVideo) return renderInlineMediaCard({
|
|
3525
3643
|
category,
|
|
@@ -3528,7 +3646,8 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3528
3646
|
sizeLabel,
|
|
3529
3647
|
isUser,
|
|
3530
3648
|
shellClasses,
|
|
3531
|
-
actionLabel
|
|
3649
|
+
actionLabel,
|
|
3650
|
+
onOpen
|
|
3532
3651
|
});
|
|
3533
3652
|
const content = renderFileCardHeader({
|
|
3534
3653
|
category,
|
|
@@ -3542,6 +3661,12 @@ function ChatMessageFile({ file, isUser = false, texts }) {
|
|
|
3542
3661
|
className: shellClasses,
|
|
3543
3662
|
children: content
|
|
3544
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
|
+
});
|
|
3545
3670
|
return /* @__PURE__ */ jsx("a", {
|
|
3546
3671
|
href: file.dataUrl,
|
|
3547
3672
|
target: "_blank",
|
|
@@ -3672,16 +3797,64 @@ function useStickyBottomScroll({ contentVersion, hasContent, isLoading, resetKey
|
|
|
3672
3797
|
};
|
|
3673
3798
|
}
|
|
3674
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
|
|
3675
3849
|
//#region src/components/chat/ui/chat-message-list/chat-reasoning-block.tsx
|
|
3676
|
-
function
|
|
3677
|
-
const
|
|
3678
|
-
|
|
3679
|
-
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));
|
|
3680
3853
|
}
|
|
3681
|
-
function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
3854
|
+
function ChatReasoningBlock({ label, text, characterCountTemplates, isUser, isInProgress }) {
|
|
3682
3855
|
const { isOpen, onSummaryClick } = useReasoningBlockOpenState({ isInProgress });
|
|
3683
3856
|
const scrollRef = useRef(null);
|
|
3684
|
-
const displayLabel =
|
|
3857
|
+
const displayLabel = formatReasoningLabel(label, text, isInProgress ? characterCountTemplates?.inProgress : characterCountTemplates?.completed);
|
|
3685
3858
|
const { onScroll } = useStickyBottomScroll({
|
|
3686
3859
|
scrollRef,
|
|
3687
3860
|
resetKey: `${displayLabel}:${isInProgress ? "streaming" : "idle"}`,
|
|
@@ -3690,36 +3863,42 @@ function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
|
3690
3863
|
contentVersion: text,
|
|
3691
3864
|
stickyThresholdPx: 20
|
|
3692
3865
|
});
|
|
3693
|
-
return /* @__PURE__ */ jsxs("
|
|
3694
|
-
className: "
|
|
3695
|
-
|
|
3696
|
-
|
|
3697
|
-
|
|
3698
|
-
|
|
3699
|
-
|
|
3700
|
-
|
|
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", {
|
|
3701
3875
|
ref: scrollRef,
|
|
3702
3876
|
onScroll,
|
|
3703
3877
|
"data-reasoning-scroll": "true",
|
|
3704
|
-
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"),
|
|
3705
3879
|
children: /* @__PURE__ */ jsx("pre", {
|
|
3706
3880
|
className: "min-w-0 whitespace-pre-wrap break-all p-2 text-[11px]",
|
|
3707
3881
|
children: text
|
|
3708
3882
|
})
|
|
3709
|
-
})]
|
|
3883
|
+
}) : null]
|
|
3710
3884
|
});
|
|
3711
3885
|
}
|
|
3712
3886
|
//#endregion
|
|
3713
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
|
+
*/
|
|
3714
3893
|
function ToolCardRoot({ children, className }) {
|
|
3715
3894
|
return /* @__PURE__ */ jsx("div", {
|
|
3716
|
-
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),
|
|
3717
3896
|
children
|
|
3718
3897
|
});
|
|
3719
3898
|
}
|
|
3720
3899
|
function ToolCardContent({ children, className }) {
|
|
3721
3900
|
return /* @__PURE__ */ jsx("div", {
|
|
3722
|
-
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),
|
|
3723
3902
|
children
|
|
3724
3903
|
});
|
|
3725
3904
|
}
|
|
@@ -3727,12 +3906,12 @@ function ToolCardContent({ children, className }) {
|
|
|
3727
3906
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-status.tsx
|
|
3728
3907
|
const STATUS_STYLES = {
|
|
3729
3908
|
running: {
|
|
3730
|
-
text: "text-
|
|
3909
|
+
text: "text-muted-foreground",
|
|
3731
3910
|
icon: Loader2,
|
|
3732
3911
|
spin: true
|
|
3733
3912
|
},
|
|
3734
3913
|
success: {
|
|
3735
|
-
text: "text-
|
|
3914
|
+
text: "text-muted-foreground/75",
|
|
3736
3915
|
icon: Check,
|
|
3737
3916
|
spin: false
|
|
3738
3917
|
},
|
|
@@ -3742,20 +3921,23 @@ const STATUS_STYLES = {
|
|
|
3742
3921
|
spin: false
|
|
3743
3922
|
},
|
|
3744
3923
|
cancelled: {
|
|
3745
|
-
text: "text-muted-foreground",
|
|
3924
|
+
text: "text-muted-foreground/70",
|
|
3746
3925
|
icon: Minus,
|
|
3747
3926
|
spin: false
|
|
3748
3927
|
}
|
|
3749
3928
|
};
|
|
3750
|
-
function ToolStatusLabel({ card }) {
|
|
3929
|
+
function ToolStatusLabel({ card, iconOnly = false }) {
|
|
3751
3930
|
const style = STATUS_STYLES[card.statusTone] || STATUS_STYLES.cancelled;
|
|
3752
3931
|
const Icon = style.icon;
|
|
3932
|
+
const showLabel = !iconOnly && (card.statusTone === "running" || card.statusTone === "error");
|
|
3753
3933
|
return /* @__PURE__ */ jsxs("span", {
|
|
3754
|
-
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,
|
|
3755
3936
|
children: [/* @__PURE__ */ jsx(Icon, {
|
|
3756
|
-
|
|
3757
|
-
|
|
3758
|
-
|
|
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]
|
|
3759
3941
|
});
|
|
3760
3942
|
}
|
|
3761
3943
|
//#endregion
|
|
@@ -3764,12 +3946,12 @@ function resolveToolCardActionView(action) {
|
|
|
3764
3946
|
if (action.kind === "show-content") return {
|
|
3765
3947
|
icon: Eye,
|
|
3766
3948
|
label: action.label,
|
|
3767
|
-
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"
|
|
3768
3950
|
};
|
|
3769
3951
|
return {
|
|
3770
3952
|
icon: ArrowUpRight,
|
|
3771
3953
|
label: action.label ?? (action.sessionKind === "child" ? "Open child session" : "Open session"),
|
|
3772
|
-
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"
|
|
3773
3955
|
};
|
|
3774
3956
|
}
|
|
3775
3957
|
function ToolCardActionButton({ action, onAction }) {
|
|
@@ -3787,12 +3969,12 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3787
3969
|
children: /* @__PURE__ */ jsx("button", {
|
|
3788
3970
|
type: "button",
|
|
3789
3971
|
onClick: handleClick,
|
|
3790
|
-
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),
|
|
3791
3973
|
"aria-label": view.label,
|
|
3792
3974
|
title: view.label,
|
|
3793
3975
|
children: /* @__PURE__ */ jsx(Icon, {
|
|
3794
3976
|
className: "h-3.5 w-3.5",
|
|
3795
|
-
strokeWidth: 2.
|
|
3977
|
+
strokeWidth: 2.25
|
|
3796
3978
|
})
|
|
3797
3979
|
})
|
|
3798
3980
|
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
@@ -3802,44 +3984,82 @@ function ToolCardActionButton({ action, onAction }) {
|
|
|
3802
3984
|
})] })
|
|
3803
3985
|
});
|
|
3804
3986
|
}
|
|
3805
|
-
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 }) {
|
|
3806
4032
|
const summaryPart = hideSummary ? "" : card.summary?.replace(/^(command|path|args|query|input):\s*/i, "") ?? "";
|
|
3807
|
-
|
|
3808
|
-
|
|
3809
|
-
|
|
3810
|
-
|
|
3811
|
-
|
|
3812
|
-
|
|
3813
|
-
|
|
3814
|
-
|
|
3815
|
-
|
|
3816
|
-
className: "
|
|
3817
|
-
children:
|
|
3818
|
-
className: "
|
|
3819
|
-
|
|
3820
|
-
})
|
|
3821
|
-
|
|
3822
|
-
|
|
3823
|
-
|
|
3824
|
-
|
|
3825
|
-
|
|
3826
|
-
|
|
3827
|
-
|
|
3828
|
-
|
|
3829
|
-
|
|
3830
|
-
|
|
3831
|
-
|
|
3832
|
-
|
|
3833
|
-
|
|
3834
|
-
|
|
3835
|
-
|
|
3836
|
-
|
|
3837
|
-
}) : /* @__PURE__ */ jsx(ChevronRight, {
|
|
3838
|
-
className: "h-4 w-4 text-muted-foreground",
|
|
3839
|
-
strokeWidth: 3
|
|
3840
|
-
}))
|
|
3841
|
-
]
|
|
3842
|
-
})]
|
|
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
|
+
]
|
|
3843
4063
|
});
|
|
3844
4064
|
}
|
|
3845
4065
|
function ToolCardHeaderAction({ action, onAction }) {
|
|
@@ -4082,7 +4302,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4082
4302
|
className: cn("overflow-hidden bg-card", !isFirst && "border-t border-border"),
|
|
4083
4303
|
children: [
|
|
4084
4304
|
showMetaRow ? /* @__PURE__ */ jsxs("div", {
|
|
4085
|
-
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"),
|
|
4086
4306
|
children: [/* @__PURE__ */ jsx("div", {
|
|
4087
4307
|
className: "min-w-0 flex-1",
|
|
4088
4308
|
children: showPathRow ? /* @__PURE__ */ jsx(TooltipProvider, {
|
|
@@ -4092,7 +4312,7 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4092
4312
|
children: /* @__PURE__ */ jsx("button", {
|
|
4093
4313
|
type: "button",
|
|
4094
4314
|
onClick: handlePathClick,
|
|
4095
|
-
className: cn("w-full truncate whitespace-nowrap text-left font-mono text-[12px] font-medium text-foreground", onFileOpen && "transition-colors hover:text-primary hover:underline"),
|
|
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"),
|
|
4096
4316
|
title: block.path,
|
|
4097
4317
|
children: block.path
|
|
4098
4318
|
})
|
|
@@ -4118,28 +4338,28 @@ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
|
|
|
4118
4338
|
children: /* @__PURE__ */ jsx("div", {
|
|
4119
4339
|
className: "overflow-x-auto custom-scrollbar",
|
|
4120
4340
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4121
|
-
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",
|
|
4122
4342
|
children: block.rawText
|
|
4123
4343
|
})
|
|
4124
4344
|
})
|
|
4125
4345
|
}) : null,
|
|
4126
4346
|
block.truncated && !previewBlock ? /* @__PURE__ */ jsx("div", {
|
|
4127
|
-
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",
|
|
4128
4348
|
children: "Showing a shortened diff preview."
|
|
4129
4349
|
}) : null
|
|
4130
4350
|
]
|
|
4131
4351
|
});
|
|
4132
4352
|
}
|
|
4133
|
-
function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
4353
|
+
function ToolCardFileOperationContent({ card, className, onFileOpen, showPathRow = true }) {
|
|
4134
4354
|
const blocks = card.fileOperation?.blocks ?? [];
|
|
4135
4355
|
const output = card.output?.trim() ?? "";
|
|
4136
4356
|
if (blocks.length === 0 && !output) return null;
|
|
4137
4357
|
return /* @__PURE__ */ jsxs("div", {
|
|
4138
|
-
className: cn("overflow-hidden bg-card", className),
|
|
4358
|
+
className: cn("overflow-hidden rounded-md border border-border/70 bg-card", className),
|
|
4139
4359
|
children: [blocks.map((block, index) => {
|
|
4140
4360
|
return /* @__PURE__ */ jsx(FileOperationBlock, {
|
|
4141
4361
|
block,
|
|
4142
|
-
showPathRow
|
|
4362
|
+
showPathRow,
|
|
4143
4363
|
isFirst: index === 0,
|
|
4144
4364
|
onFileOpen
|
|
4145
4365
|
}, block.key);
|
|
@@ -4151,7 +4371,7 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4151
4371
|
children: /* @__PURE__ */ jsx("div", {
|
|
4152
4372
|
className: "overflow-x-auto custom-scrollbar",
|
|
4153
4373
|
children: /* @__PURE__ */ jsx("pre", {
|
|
4154
|
-
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",
|
|
4155
4375
|
children: output
|
|
4156
4376
|
})
|
|
4157
4377
|
})
|
|
@@ -4159,9 +4379,279 @@ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
|
|
|
4159
4379
|
});
|
|
4160
4380
|
}
|
|
4161
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
|
|
4162
4653
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
|
|
4163
4654
|
const TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
|
|
4164
|
-
const ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
4165
4655
|
function readNonEmptyString(value) {
|
|
4166
4656
|
if (typeof value !== "string") return null;
|
|
4167
4657
|
return value.length > 0 ? value : null;
|
|
@@ -4169,9 +4659,6 @@ function readNonEmptyString(value) {
|
|
|
4169
4659
|
function isRecord(value) {
|
|
4170
4660
|
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
4171
4661
|
}
|
|
4172
|
-
function stripAnsi(value) {
|
|
4173
|
-
return value.replace(ANSI_ESCAPE_PATTERN, "");
|
|
4174
|
-
}
|
|
4175
4662
|
function isStructuredTerminalRecord(record) {
|
|
4176
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;
|
|
4177
4664
|
}
|
|
@@ -4186,26 +4673,35 @@ function extractTerminalOutputFromRecord(record) {
|
|
|
4186
4673
|
function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
4187
4674
|
if (isRecord(structuredOutput)) {
|
|
4188
4675
|
const terminalOutput = extractTerminalOutputFromRecord(structuredOutput);
|
|
4189
|
-
if (terminalOutput) return
|
|
4676
|
+
if (terminalOutput) return terminalOutput;
|
|
4190
4677
|
if (isStructuredTerminalRecord(structuredOutput)) return "";
|
|
4191
4678
|
}
|
|
4192
4679
|
if (!rawOutput) return "";
|
|
4193
4680
|
const trimmed = rawOutput.trim();
|
|
4194
|
-
if (!trimmed.startsWith("{")) return
|
|
4681
|
+
if (!trimmed.startsWith("{")) return rawOutput;
|
|
4195
4682
|
try {
|
|
4196
4683
|
const parsed = JSON.parse(trimmed);
|
|
4197
|
-
if (!isRecord(parsed)) return
|
|
4684
|
+
if (!isRecord(parsed)) return rawOutput;
|
|
4198
4685
|
const terminalOutput = extractTerminalOutputFromRecord(parsed);
|
|
4199
|
-
if (terminalOutput) return
|
|
4686
|
+
if (terminalOutput) return terminalOutput;
|
|
4200
4687
|
if (isStructuredTerminalRecord(parsed)) return "";
|
|
4201
|
-
return
|
|
4688
|
+
return rawOutput;
|
|
4202
4689
|
} catch {
|
|
4203
|
-
return
|
|
4690
|
+
return rawOutput;
|
|
4204
4691
|
}
|
|
4205
4692
|
}
|
|
4206
4693
|
function shouldAutoExpandRunningFileOperation(toolName) {
|
|
4207
4694
|
return toolName === "write_file" || toolName === "edit_file";
|
|
4208
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
|
+
}
|
|
4209
4705
|
function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning = true, expandOnError = false, statusTone }) {
|
|
4210
4706
|
const [expanded, setExpanded] = useState(false);
|
|
4211
4707
|
const [hasUserToggled, setHasUserToggled] = useState(false);
|
|
@@ -4264,47 +4760,20 @@ function useToolCardExpandedState({ canExpand, isRunning, autoExpandWhileRunning
|
|
|
4264
4760
|
onToggle
|
|
4265
4761
|
};
|
|
4266
4762
|
}
|
|
4267
|
-
function
|
|
4268
|
-
|
|
4269
|
-
|
|
4270
|
-
|
|
4271
|
-
|
|
4272
|
-
|
|
4273
|
-
body: "text-foreground"
|
|
4274
|
-
},
|
|
4275
|
-
output: {
|
|
4276
|
-
shell: "border-border bg-card",
|
|
4277
|
-
header: "border-border bg-muted/55 text-muted-foreground",
|
|
4278
|
-
dot: "bg-primary/70",
|
|
4279
|
-
body: "text-foreground"
|
|
4280
|
-
},
|
|
4281
|
-
error: {
|
|
4282
|
-
shell: "border-rose-200/80 bg-rose-50/85",
|
|
4283
|
-
header: "border-rose-200/80 bg-rose-100/80 text-rose-700",
|
|
4284
|
-
dot: "bg-rose-500/80",
|
|
4285
|
-
body: "text-rose-950/85"
|
|
4286
|
-
}
|
|
4287
|
-
}[tone];
|
|
4288
|
-
return /* @__PURE__ */ jsxs("section", {
|
|
4289
|
-
className: cn("overflow-hidden rounded-md border shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]", style.shell),
|
|
4290
|
-
children: [/* @__PURE__ */ jsxs("div", {
|
|
4291
|
-
className: cn("flex items-center gap-2 border-b px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.16em]", style.header),
|
|
4292
|
-
children: [/* @__PURE__ */ jsx("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }), /* @__PURE__ */ jsx("span", { children: label })]
|
|
4293
|
-
}), /* @__PURE__ */ jsx("div", {
|
|
4294
|
-
className: "w-full overflow-hidden",
|
|
4295
|
-
children: /* @__PURE__ */ jsx("pre", {
|
|
4296
|
-
className: cn("w-full max-w-full min-w-0 max-h-64 overflow-x-auto overflow-y-auto px-3 py-2.5 font-mono text-[12px] leading-relaxed whitespace-pre custom-scrollbar", style.body),
|
|
4297
|
-
children
|
|
4298
|
-
})
|
|
4299
|
-
})]
|
|
4300
|
-
});
|
|
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
|
+
};
|
|
4301
4769
|
}
|
|
4302
|
-
function TerminalExecutionView({ card }) {
|
|
4770
|
+
function TerminalExecutionView({ card, toolLabel }) {
|
|
4303
4771
|
const output = normalizeTerminalOutput(card.output, card.outputData);
|
|
4304
4772
|
const isRunning = card.statusTone === "running";
|
|
4305
4773
|
const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
|
|
4306
4774
|
const hasOutput = output.trim().length > 0;
|
|
4307
4775
|
const canExpand = isRunning || hasOutput || Boolean(commandPart?.trim());
|
|
4776
|
+
const meta = extractTerminalMeta(card.outputData);
|
|
4308
4777
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4309
4778
|
canExpand,
|
|
4310
4779
|
isRunning,
|
|
@@ -4314,38 +4783,29 @@ function TerminalExecutionView({ card }) {
|
|
|
4314
4783
|
});
|
|
4315
4784
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4316
4785
|
card,
|
|
4786
|
+
toolLabel,
|
|
4317
4787
|
icon: Terminal,
|
|
4318
4788
|
expanded,
|
|
4319
4789
|
canExpand,
|
|
4320
4790
|
onToggle
|
|
4321
|
-
}), expanded && /* @__PURE__ */
|
|
4322
|
-
className: "
|
|
4323
|
-
children: /* @__PURE__ */
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4330
|
-
|
|
4331
|
-
|
|
4332
|
-
children: [commandPart, isRunning && !output && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
|
|
4333
|
-
}) : /* @__PURE__ */ jsx("div", { className: "h-3 w-32 bg-muted rounded animate-pulse mt-2" })
|
|
4334
|
-
})]
|
|
4335
|
-
})
|
|
4336
|
-
}), (hasOutput || !isRunning) && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4337
|
-
className: hasOutput ? void 0 : "bg-muted/20 py-2",
|
|
4338
|
-
children: /* @__PURE__ */ jsxs("pre", {
|
|
4339
|
-
className: cn("font-mono text-[12px] text-muted-foreground whitespace-pre-wrap break-all w-full max-w-full max-h-64 overflow-y-auto overflow-x-hidden min-w-0 custom-scrollbar leading-relaxed", hasOutput ? "px-0" : "rounded-md border border-dashed border-border bg-card/70 px-3 py-2 italic"),
|
|
4340
|
-
children: [hasOutput ? output : card.emptyLabel, isRunning && hasOutput && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-primary/60 animate-pulse align-middle" })]
|
|
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
|
|
4341
4802
|
})
|
|
4342
|
-
})] })
|
|
4803
|
+
})] });
|
|
4343
4804
|
}
|
|
4344
|
-
function FileOperationView({ card, onFileOpen }) {
|
|
4805
|
+
function FileOperationView({ card, toolLabel, onFileOpen }) {
|
|
4345
4806
|
const output = card.output?.trim() ?? "";
|
|
4346
4807
|
const isRunning = card.statusTone === "running";
|
|
4347
|
-
const
|
|
4348
|
-
const hasContent = hasStructuredPreview || Boolean(output);
|
|
4808
|
+
const hasContent = Boolean(card.fileOperation?.blocks.length) || Boolean(output);
|
|
4349
4809
|
const previewBlocks = card.fileOperation?.blocks ?? [];
|
|
4350
4810
|
const previewLineCount = previewBlocks.reduce((count, block) => count + block.lines.length, 0);
|
|
4351
4811
|
const previewCharCount = previewBlocks.reduce((count, block) => {
|
|
@@ -4360,22 +4820,26 @@ function FileOperationView({ card, onFileOpen }) {
|
|
|
4360
4820
|
expandOnError: hasContent,
|
|
4361
4821
|
statusTone: card.statusTone
|
|
4362
4822
|
});
|
|
4823
|
+
const isEdit = card.toolName === "edit_file" || card.toolName === "write_file" || card.toolName === "apply_patch" || card.toolName === "file_change";
|
|
4363
4824
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4364
4825
|
card,
|
|
4365
|
-
|
|
4826
|
+
toolLabel,
|
|
4827
|
+
changeSummary: isEdit ? countFileOperationChanges(previewBlocks) : void 0,
|
|
4828
|
+
icon: isEdit ? Code2 : FileText,
|
|
4366
4829
|
expanded,
|
|
4367
4830
|
canExpand: hasContent || isRunning,
|
|
4368
|
-
hideSummary:
|
|
4831
|
+
hideSummary: false,
|
|
4369
4832
|
onToggle
|
|
4370
|
-
}), expanded && hasContent
|
|
4371
|
-
className: "
|
|
4833
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4834
|
+
className: "bg-transparent py-0",
|
|
4372
4835
|
children: /* @__PURE__ */ jsx(ToolCardFileOperationContent, {
|
|
4373
4836
|
card,
|
|
4374
|
-
onFileOpen
|
|
4837
|
+
onFileOpen,
|
|
4838
|
+
showPathRow: true
|
|
4375
4839
|
})
|
|
4376
|
-
})] });
|
|
4840
|
+
}) : null] });
|
|
4377
4841
|
}
|
|
4378
|
-
function SearchSnippetView({ card }) {
|
|
4842
|
+
function SearchSnippetView({ card, toolLabel, icon: Icon = Search }) {
|
|
4379
4843
|
const isRunning = card.statusTone === "running";
|
|
4380
4844
|
const output = card.output?.trim() ?? "";
|
|
4381
4845
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
@@ -4386,24 +4850,69 @@ function SearchSnippetView({ card }) {
|
|
|
4386
4850
|
});
|
|
4387
4851
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4388
4852
|
card,
|
|
4389
|
-
|
|
4853
|
+
toolLabel,
|
|
4854
|
+
icon: Icon,
|
|
4390
4855
|
expanded,
|
|
4391
4856
|
canExpand: !!output || isRunning,
|
|
4392
4857
|
onToggle
|
|
4393
|
-
}), expanded && output && /* @__PURE__ */ jsx(ToolCardContent, {
|
|
4394
|
-
className: "
|
|
4395
|
-
children:
|
|
4396
|
-
|
|
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
|
+
})] });
|
|
4397
4865
|
}
|
|
4398
|
-
|
|
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 }) {
|
|
4399
4909
|
const input = card.input?.trim() ?? "";
|
|
4400
4910
|
const output = card.output?.trim() ?? "";
|
|
4401
4911
|
const isRunning = card.statusTone === "running";
|
|
4402
4912
|
const hasInputSection = input.length > 0;
|
|
4403
4913
|
const hasOutputSection = output.length > 0;
|
|
4404
4914
|
const hasContent = hasInputSection || hasOutputSection;
|
|
4405
|
-
const
|
|
4406
|
-
const outputLabel = card.outputLabel?.trim() || "Output";
|
|
4915
|
+
const actionSlot = buildToolActionSlot(card, onToolAction, renderToolAgent);
|
|
4407
4916
|
const { expanded, onToggle } = useToolCardExpandedState({
|
|
4408
4917
|
canExpand: hasContent || isRunning,
|
|
4409
4918
|
isRunning,
|
|
@@ -4412,30 +4921,28 @@ function GenericToolCard({ card, onToolAction, renderToolAgent }) {
|
|
|
4412
4921
|
});
|
|
4413
4922
|
return /* @__PURE__ */ jsxs(ToolCardRoot, { children: [/* @__PURE__ */ jsx(ToolCardHeader, {
|
|
4414
4923
|
card,
|
|
4415
|
-
|
|
4924
|
+
toolLabel,
|
|
4925
|
+
icon: Icon,
|
|
4416
4926
|
expanded,
|
|
4417
4927
|
canExpand: hasContent || isRunning,
|
|
4418
|
-
actionSlot
|
|
4419
|
-
action: card.action,
|
|
4420
|
-
onAction: onToolAction
|
|
4421
|
-
}) : null] }) : void 0,
|
|
4928
|
+
actionSlot,
|
|
4422
4929
|
onToggle
|
|
4423
|
-
}), expanded && hasContent
|
|
4424
|
-
className: "bg-transparent
|
|
4930
|
+
}), expanded && hasContent ? /* @__PURE__ */ jsxs(ToolCardContent, {
|
|
4931
|
+
className: "bg-transparent py-0",
|
|
4425
4932
|
children: [
|
|
4426
|
-
hasInputSection
|
|
4427
|
-
label: inputLabel,
|
|
4933
|
+
hasInputSection ? /* @__PURE__ */ jsx(GenericToolSection, {
|
|
4934
|
+
label: card.inputLabel?.trim() || "Input",
|
|
4428
4935
|
tone: "input",
|
|
4429
4936
|
children: input
|
|
4430
|
-
}),
|
|
4431
|
-
hasInputSection && hasOutputSection
|
|
4432
|
-
hasOutputSection
|
|
4433
|
-
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",
|
|
4434
4941
|
tone: card.statusTone === "error" ? "error" : "output",
|
|
4435
4942
|
children: output
|
|
4436
|
-
})
|
|
4943
|
+
}) : null
|
|
4437
4944
|
]
|
|
4438
|
-
})] });
|
|
4945
|
+
}) : null] });
|
|
4439
4946
|
}
|
|
4440
4947
|
//#endregion
|
|
4441
4948
|
//#region src/components/chat/ui/chat-message-list/tool-card/tool-card-panel-app.tsx
|
|
@@ -4457,34 +4964,370 @@ function isTerminalTool(name) {
|
|
|
4457
4964
|
const lowered = name.toLowerCase();
|
|
4458
4965
|
return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
|
|
4459
4966
|
}
|
|
4460
|
-
function
|
|
4967
|
+
function isFileTool(name) {
|
|
4461
4968
|
const lowered = name.toLowerCase();
|
|
4462
4969
|
return lowered === "read_file" || lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
4463
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
|
+
}
|
|
4464
4975
|
function isSearchTool(name) {
|
|
4465
4976
|
const lowered = name.toLowerCase();
|
|
4466
4977
|
return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
|
|
4467
4978
|
}
|
|
4468
|
-
|
|
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 }) {
|
|
4469
5057
|
if (card.panelApp) return /* @__PURE__ */ jsx(PanelAppInlineToolCard, {
|
|
4470
5058
|
card,
|
|
4471
5059
|
onToolAction,
|
|
4472
5060
|
renderPanelAppCard
|
|
4473
5061
|
});
|
|
4474
|
-
|
|
4475
|
-
|
|
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, {
|
|
4476
5065
|
card,
|
|
5066
|
+
toolLabel: toolStatusLabels?.terminal[card.statusTone]
|
|
5067
|
+
});
|
|
5068
|
+
if (isFileTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, {
|
|
5069
|
+
card,
|
|
5070
|
+
toolLabel: isFileWriteTool(card.toolName) ? toolStatusLabels?.fileEdit[card.statusTone] : toolStatusLabels?.fileRead[card.statusTone],
|
|
4477
5071
|
onFileOpen
|
|
4478
5072
|
});
|
|
4479
|
-
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
|
+
});
|
|
4480
5078
|
return /* @__PURE__ */ jsx(GenericToolCard, {
|
|
4481
5079
|
card,
|
|
5080
|
+
toolLabel: builtInLabel,
|
|
5081
|
+
icon: builtInPresentation?.icon,
|
|
4482
5082
|
onToolAction,
|
|
4483
5083
|
renderToolAgent
|
|
4484
5084
|
});
|
|
4485
5085
|
}
|
|
4486
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
|
|
4487
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
|
+
}
|
|
4488
5331
|
function isMessageInProgress(status) {
|
|
4489
5332
|
return status === "pending" || status === "streaming";
|
|
4490
5333
|
}
|
|
@@ -4504,7 +5347,7 @@ function splitAssistantProcess(message) {
|
|
|
4504
5347
|
finalParts: message.parts.slice(lastProcessPartIndex + 1)
|
|
4505
5348
|
};
|
|
4506
5349
|
}
|
|
4507
|
-
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 }) {
|
|
4508
5351
|
const { type } = part;
|
|
4509
5352
|
if (type === "markdown") {
|
|
4510
5353
|
const { inlineTokens, text } = part;
|
|
@@ -4514,6 +5357,7 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4514
5357
|
texts,
|
|
4515
5358
|
inlineTokens,
|
|
4516
5359
|
onFileOpen,
|
|
5360
|
+
onInlineTokenClick,
|
|
4517
5361
|
renderInlineDisplay
|
|
4518
5362
|
}, `markdown-${index}`);
|
|
4519
5363
|
}
|
|
@@ -4522,29 +5366,29 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4522
5366
|
return /* @__PURE__ */ jsx(ChatReasoningBlock, {
|
|
4523
5367
|
label,
|
|
4524
5368
|
text,
|
|
5369
|
+
characterCountTemplates: texts.reasoningCharacterCountTemplates,
|
|
4525
5370
|
isUser,
|
|
4526
5371
|
isInProgress: isInProgress && isLastPart
|
|
4527
5372
|
}, `reasoning-${index}`);
|
|
4528
5373
|
}
|
|
4529
5374
|
if (type === "tool-card") {
|
|
4530
5375
|
const { card } = part;
|
|
4531
|
-
return /* @__PURE__ */ jsx("div", {
|
|
4532
|
-
|
|
4533
|
-
|
|
4534
|
-
|
|
4535
|
-
|
|
4536
|
-
|
|
4537
|
-
|
|
4538
|
-
|
|
4539
|
-
})
|
|
4540
|
-
}, `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}`);
|
|
4541
5384
|
}
|
|
4542
5385
|
if (type === "file") {
|
|
4543
5386
|
const { file } = part;
|
|
4544
5387
|
return /* @__PURE__ */ jsx(ChatMessageFile, {
|
|
4545
5388
|
file,
|
|
4546
5389
|
isUser,
|
|
4547
|
-
texts
|
|
5390
|
+
texts,
|
|
5391
|
+
onOpen: onAttachmentOpen
|
|
4548
5392
|
}, `file-${index}`);
|
|
4549
5393
|
}
|
|
4550
5394
|
if (type === "unknown") {
|
|
@@ -4566,49 +5410,102 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onFile
|
|
|
4566
5410
|
}
|
|
4567
5411
|
return null;
|
|
4568
5412
|
}
|
|
4569
|
-
|
|
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 }) {
|
|
4570
5445
|
const { role } = message;
|
|
4571
5446
|
const isUser = role === "user";
|
|
4572
5447
|
const isInProgress = isMessageInProgress(message.status);
|
|
4573
5448
|
const processSplit = splitAssistantProcess(message);
|
|
4574
|
-
const
|
|
4575
|
-
part,
|
|
4576
|
-
index,
|
|
4577
|
-
role,
|
|
4578
|
-
isUser,
|
|
4579
|
-
isInProgress,
|
|
4580
|
-
isLastPart: index === message.parts.length - 1,
|
|
4581
|
-
texts,
|
|
4582
|
-
onToolAction,
|
|
4583
|
-
onFileOpen,
|
|
4584
|
-
renderInlineDisplay,
|
|
4585
|
-
renderToolAgent,
|
|
4586
|
-
renderPanelAppCard
|
|
4587
|
-
});
|
|
5449
|
+
const [processOpen, setProcessOpen] = useState(false);
|
|
4588
5450
|
return /* @__PURE__ */ jsx("div", {
|
|
4589
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"),
|
|
4590
5452
|
children: /* @__PURE__ */ jsx("div", {
|
|
4591
|
-
className: "space-y-
|
|
4592
|
-
children: processSplit ? /* @__PURE__ */ jsxs(Fragment$1, { children: [/* @__PURE__ */ jsxs("
|
|
4593
|
-
className: "group/process
|
|
4594
|
-
children: [/* @__PURE__ */
|
|
4595
|
-
className: "
|
|
4596
|
-
children:
|
|
4597
|
-
|
|
4598
|
-
|
|
4599
|
-
|
|
4600
|
-
|
|
4601
|
-
|
|
4602
|
-
|
|
4603
|
-
|
|
4604
|
-
|
|
4605
|
-
|
|
4606
|
-
|
|
4607
|
-
|
|
4608
|
-
|
|
4609
|
-
|
|
4610
|
-
|
|
4611
|
-
|
|
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
|
+
})
|
|
4612
5509
|
})
|
|
4613
5510
|
});
|
|
4614
5511
|
});
|
|
@@ -4708,7 +5605,7 @@ function ChatTypingIndicator({ label }) {
|
|
|
4708
5605
|
})]
|
|
4709
5606
|
});
|
|
4710
5607
|
}
|
|
4711
|
-
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 }) {
|
|
4712
5609
|
const visibleMessages = messages.filter(hasRenderableMessageContent);
|
|
4713
5610
|
const hasRenderableAssistantDraft = visibleMessages.some((message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending"));
|
|
4714
5611
|
return /* @__PURE__ */ jsxs("div", {
|
|
@@ -4727,6 +5624,8 @@ function ChatMessageList({ className, isSending, messages, onFileOpen, onToolAct
|
|
|
4727
5624
|
texts,
|
|
4728
5625
|
onToolAction,
|
|
4729
5626
|
onFileOpen,
|
|
5627
|
+
onAttachmentOpen,
|
|
5628
|
+
onInlineTokenClick,
|
|
4730
5629
|
renderInlineDisplay,
|
|
4731
5630
|
renderToolAgent,
|
|
4732
5631
|
renderPanelAppCard
|