@nextclaw/agent-chat-ui 0.2.18 → 0.2.20
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 +22 -1
- package/dist/index.js +686 -242
- package/package.json +1 -1
package/dist/index.d.ts
CHANGED
|
@@ -151,17 +151,38 @@ type ChatInputBarProps = {
|
|
|
151
151
|
toolbar: ChatInputBarToolbarProps;
|
|
152
152
|
};
|
|
153
153
|
type ChatMessageRole = "user" | "assistant" | "tool" | "system" | "message";
|
|
154
|
+
type ChatFileOperationLineViewModel = {
|
|
155
|
+
kind: "context" | "add" | "remove";
|
|
156
|
+
text: string;
|
|
157
|
+
oldLineNumber?: number;
|
|
158
|
+
newLineNumber?: number;
|
|
159
|
+
};
|
|
160
|
+
type ChatFileOperationBlockViewModel = {
|
|
161
|
+
key: string;
|
|
162
|
+
path: string;
|
|
163
|
+
display?: "preview" | "diff";
|
|
164
|
+
caption?: string;
|
|
165
|
+
lines: ChatFileOperationLineViewModel[];
|
|
166
|
+
rawText?: string;
|
|
167
|
+
truncated?: boolean;
|
|
168
|
+
};
|
|
154
169
|
type ChatToolPartViewModel = {
|
|
155
170
|
kind: "call" | "result";
|
|
156
171
|
toolName: string;
|
|
157
172
|
summary?: string;
|
|
173
|
+
inputLabel?: string;
|
|
174
|
+
input?: string;
|
|
158
175
|
output?: string;
|
|
176
|
+
outputData?: unknown;
|
|
159
177
|
hasResult: boolean;
|
|
160
178
|
statusTone: "running" | "success" | "error" | "cancelled";
|
|
161
179
|
statusLabel: string;
|
|
162
180
|
titleLabel: string;
|
|
163
181
|
outputLabel: string;
|
|
164
182
|
emptyLabel: string;
|
|
183
|
+
fileOperation?: {
|
|
184
|
+
blocks: ChatFileOperationBlockViewModel[];
|
|
185
|
+
};
|
|
165
186
|
};
|
|
166
187
|
type ChatInlineTokenViewModel = {
|
|
167
188
|
kind: string;
|
|
@@ -299,4 +320,4 @@ declare function resolveChatComposerSlashTrigger(nodes: ChatComposerNode[], sele
|
|
|
299
320
|
end: number;
|
|
300
321
|
} | null;
|
|
301
322
|
|
|
302
|
-
export { type ChatComposerNode, type ChatComposerSelection, type ChatComposerTextNode, type ChatComposerTokenKind, type ChatComposerTokenNode, type ChatInlineContentSegmentViewModel, type ChatInlineHint, type ChatInlineTokenViewModel, ChatInputBar, type ChatInputBarActionsProps, type ChatInputBarHandle, type ChatInputBarProps, type ChatInputBarToolbarProps, ChatMessageList, type ChatMessageListProps, type ChatMessagePartViewModel, type ChatMessageRole, type ChatMessageTexts, type ChatMessageViewModel, type ChatSelectedItem, type ChatSkillPickerOption, type ChatSkillPickerProps, type ChatSlashItem, type ChatSlashMenuProps, type ChatTexts, type ChatToolPartViewModel, type ChatToolbarAccessory, type ChatToolbarAccessoryIcon, type ChatToolbarIcon, type ChatToolbarSelect, type ChatToolbarSelectGroup, type ChatToolbarSelectOption, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
|
|
323
|
+
export { type ChatComposerNode, type ChatComposerSelection, type ChatComposerTextNode, type ChatComposerTokenKind, type ChatComposerTokenNode, type ChatFileOperationBlockViewModel, type ChatFileOperationLineViewModel, type ChatInlineContentSegmentViewModel, type ChatInlineHint, type ChatInlineTokenViewModel, ChatInputBar, type ChatInputBarActionsProps, type ChatInputBarHandle, type ChatInputBarProps, type ChatInputBarToolbarProps, ChatMessageList, type ChatMessageListProps, type ChatMessagePartViewModel, type ChatMessageRole, type ChatMessageTexts, type ChatMessageViewModel, type ChatSelectedItem, type ChatSkillPickerOption, type ChatSkillPickerProps, type ChatSlashItem, type ChatSlashMenuProps, type ChatTexts, type ChatToolPartViewModel, type ChatToolbarAccessory, type ChatToolbarAccessoryIcon, type ChatToolbarIcon, type ChatToolbarSelect, type ChatToolbarSelectGroup, type ChatToolbarSelectOption, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
|
package/dist/index.js
CHANGED
|
@@ -2314,8 +2314,8 @@ function ChatInlineTokenBadge({
|
|
|
2314
2314
|
"span",
|
|
2315
2315
|
{
|
|
2316
2316
|
className: cn(
|
|
2317
|
-
"mx-[2px] inline-flex h-7 max-w-full items-center gap-1.5 rounded-
|
|
2318
|
-
isSkill ? isUser ? "border-
|
|
2317
|
+
"mx-[2px] inline-flex h-7 max-w-full items-center gap-1.5 rounded-xl border px-2.5 align-baseline text-[11px] font-medium shadow-[0_0_0_1px_rgba(15,23,42,0.03)]",
|
|
2318
|
+
isSkill ? isUser ? "border-emerald-200/35 bg-emerald-400/18 text-emerald-50/95" : "border-emerald-200/70 bg-emerald-50 text-emerald-700" : isUser ? "border-white/30 bg-white/18 text-white" : "border-slate-200/80 bg-slate-100 text-slate-700"
|
|
2319
2319
|
),
|
|
2320
2320
|
title: label,
|
|
2321
2321
|
children: [
|
|
@@ -2324,7 +2324,7 @@ function ChatInlineTokenBadge({
|
|
|
2324
2324
|
{
|
|
2325
2325
|
className: cn(
|
|
2326
2326
|
"inline-flex h-3.5 w-3.5 shrink-0 items-center justify-center",
|
|
2327
|
-
isSkill ? isUser ? "text-
|
|
2327
|
+
isSkill ? isUser ? "text-emerald-100/90" : "text-emerald-600" : isUser ? "text-white/70" : "text-slate-500"
|
|
2328
2328
|
),
|
|
2329
2329
|
children: /* @__PURE__ */ jsx15(ChatInlineSkillIcon, {})
|
|
2330
2330
|
}
|
|
@@ -2752,7 +2752,7 @@ function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
|
|
|
2752
2752
|
|
|
2753
2753
|
// src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
|
|
2754
2754
|
import { Terminal, FileText, Code2, Search as Search2, Globe } from "lucide-react";
|
|
2755
|
-
import { useState as useState7, useEffect as
|
|
2755
|
+
import { useState as useState7, useEffect as useEffect8, useRef as useRef8 } from "react";
|
|
2756
2756
|
|
|
2757
2757
|
// src/components/chat/ui/chat-message-list/tool-card/tool-card-root.tsx
|
|
2758
2758
|
import { jsx as jsx18 } from "react/jsx-runtime";
|
|
@@ -2801,9 +2801,10 @@ function ToolCardHeader({
|
|
|
2801
2801
|
icon: Icon2,
|
|
2802
2802
|
expanded,
|
|
2803
2803
|
canExpand,
|
|
2804
|
+
hideSummary = false,
|
|
2804
2805
|
onToggle
|
|
2805
2806
|
}) {
|
|
2806
|
-
const summaryPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
|
|
2807
|
+
const summaryPart = hideSummary ? "" : card.summary?.replace(/^(command|path|args|query|input):\s*/i, "") ?? "";
|
|
2807
2808
|
return /* @__PURE__ */ jsxs12(
|
|
2808
2809
|
"div",
|
|
2809
2810
|
{
|
|
@@ -2832,56 +2833,607 @@ function ToolCardHeader({
|
|
|
2832
2833
|
);
|
|
2833
2834
|
}
|
|
2834
2835
|
|
|
2835
|
-
// src/components/chat/
|
|
2836
|
-
import {
|
|
2837
|
-
|
|
2836
|
+
// src/components/chat/hooks/use-sticky-bottom-scroll.ts
|
|
2837
|
+
import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef6 } from "react";
|
|
2838
|
+
var DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
2839
|
+
function scrollElementToBottom(element) {
|
|
2840
|
+
element.scrollTop = element.scrollHeight;
|
|
2841
|
+
}
|
|
2842
|
+
function queueScrollToBottom(params) {
|
|
2843
|
+
const element = params.scrollRef.current;
|
|
2844
|
+
if (!element) {
|
|
2845
|
+
return;
|
|
2846
|
+
}
|
|
2847
|
+
if (params.scheduledScrollFrameRef.current !== null) {
|
|
2848
|
+
cancelAnimationFrame(params.scheduledScrollFrameRef.current);
|
|
2849
|
+
}
|
|
2850
|
+
params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
|
|
2851
|
+
params.scheduledScrollFrameRef.current = null;
|
|
2852
|
+
const currentElement = params.scrollRef.current;
|
|
2853
|
+
if (!currentElement) {
|
|
2854
|
+
return;
|
|
2855
|
+
}
|
|
2856
|
+
params.isProgrammaticScrollRef.current = true;
|
|
2857
|
+
scrollElementToBottom(currentElement);
|
|
2858
|
+
});
|
|
2859
|
+
}
|
|
2860
|
+
function useStickyBottomScroll(params) {
|
|
2861
|
+
const isStickyRef = useRef6(true);
|
|
2862
|
+
const isProgrammaticScrollRef = useRef6(false);
|
|
2863
|
+
const previousResetKeyRef = useRef6(null);
|
|
2864
|
+
const pendingInitialScrollRef = useRef6(false);
|
|
2865
|
+
const scheduledScrollFrameRef = useRef6(null);
|
|
2866
|
+
const onScroll = () => {
|
|
2867
|
+
if (isProgrammaticScrollRef.current) {
|
|
2868
|
+
isProgrammaticScrollRef.current = false;
|
|
2869
|
+
return;
|
|
2870
|
+
}
|
|
2871
|
+
const element = params.scrollRef.current;
|
|
2872
|
+
if (!element) {
|
|
2873
|
+
return;
|
|
2874
|
+
}
|
|
2875
|
+
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
2876
|
+
isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
|
|
2877
|
+
};
|
|
2878
|
+
useEffect7(() => {
|
|
2879
|
+
if (previousResetKeyRef.current === params.resetKey) {
|
|
2880
|
+
return;
|
|
2881
|
+
}
|
|
2882
|
+
previousResetKeyRef.current = params.resetKey;
|
|
2883
|
+
isStickyRef.current = true;
|
|
2884
|
+
pendingInitialScrollRef.current = true;
|
|
2885
|
+
}, [params.resetKey]);
|
|
2886
|
+
useEffect7(() => {
|
|
2887
|
+
const scheduledScrollFrame = scheduledScrollFrameRef.current;
|
|
2888
|
+
return () => {
|
|
2889
|
+
if (scheduledScrollFrame !== null) {
|
|
2890
|
+
cancelAnimationFrame(scheduledScrollFrame);
|
|
2891
|
+
}
|
|
2892
|
+
};
|
|
2893
|
+
}, []);
|
|
2894
|
+
useLayoutEffect2(() => {
|
|
2895
|
+
if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
|
|
2896
|
+
return;
|
|
2897
|
+
}
|
|
2898
|
+
const element = params.scrollRef.current;
|
|
2899
|
+
if (!element) {
|
|
2900
|
+
return;
|
|
2901
|
+
}
|
|
2902
|
+
pendingInitialScrollRef.current = false;
|
|
2903
|
+
queueScrollToBottom({
|
|
2904
|
+
scrollRef: params.scrollRef,
|
|
2905
|
+
scheduledScrollFrameRef,
|
|
2906
|
+
isProgrammaticScrollRef
|
|
2907
|
+
});
|
|
2908
|
+
}, [params.hasContent, params.isLoading, params.scrollRef]);
|
|
2909
|
+
useLayoutEffect2(() => {
|
|
2910
|
+
if (!isStickyRef.current || !params.hasContent) {
|
|
2911
|
+
return;
|
|
2912
|
+
}
|
|
2913
|
+
const element = params.scrollRef.current;
|
|
2914
|
+
if (!element) {
|
|
2915
|
+
return;
|
|
2916
|
+
}
|
|
2917
|
+
queueScrollToBottom({
|
|
2918
|
+
scrollRef: params.scrollRef,
|
|
2919
|
+
scheduledScrollFrameRef,
|
|
2920
|
+
isProgrammaticScrollRef
|
|
2921
|
+
});
|
|
2922
|
+
}, [params.contentVersion, params.hasContent, params.scrollRef]);
|
|
2923
|
+
return { onScroll };
|
|
2924
|
+
}
|
|
2925
|
+
|
|
2926
|
+
// src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
|
|
2927
|
+
import { Fragment as Fragment4, useRef as useRef7 } from "react";
|
|
2928
|
+
|
|
2929
|
+
// src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
|
|
2930
|
+
import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
|
|
2931
|
+
var FILE_ROW_CLASS_NAME = "h-5 font-mono text-[11px] leading-5";
|
|
2932
|
+
var FILE_GUTTER_NUMBER_CELL_CLASS_NAME = "sticky left-0 z-[1] flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
|
|
2933
|
+
function formatLineNumber(value) {
|
|
2934
|
+
return typeof value === "number" ? String(value) : "";
|
|
2935
|
+
}
|
|
2936
|
+
function readVisibleLineNumber(line) {
|
|
2937
|
+
return formatLineNumber(line.newLineNumber ?? line.oldLineNumber);
|
|
2938
|
+
}
|
|
2939
|
+
function readLineKey(prefix, line, index) {
|
|
2940
|
+
return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
|
|
2941
|
+
}
|
|
2942
|
+
function hasVisibleLineNumber(line) {
|
|
2943
|
+
return typeof line.newLineNumber === "number" || typeof line.oldLineNumber === "number";
|
|
2944
|
+
}
|
|
2945
|
+
function readHasBlockLineNumbers(block) {
|
|
2946
|
+
return block.lines.some(hasVisibleLineNumber);
|
|
2947
|
+
}
|
|
2948
|
+
function readLineNumberColumnWidth(block) {
|
|
2949
|
+
const maxDigits = block.lines.reduce((currentMax, line) => {
|
|
2950
|
+
if (!hasVisibleLineNumber(line)) {
|
|
2951
|
+
return currentMax;
|
|
2952
|
+
}
|
|
2953
|
+
return Math.max(currentMax, readVisibleLineNumber(line).length);
|
|
2954
|
+
}, 0);
|
|
2955
|
+
const width = Math.max(6.5, Math.min(8, maxDigits + 3.5));
|
|
2956
|
+
return `${width}ch`;
|
|
2957
|
+
}
|
|
2958
|
+
function buildRowTemplateColumns(params) {
|
|
2959
|
+
if (!params.showLineNumbers) {
|
|
2960
|
+
return void 0;
|
|
2961
|
+
}
|
|
2962
|
+
return {
|
|
2963
|
+
gridTemplateColumns: `${params.lineNumberColumnWidth} minmax(0, 1fr)`
|
|
2964
|
+
};
|
|
2965
|
+
}
|
|
2966
|
+
function getLineNumberTone(line) {
|
|
2967
|
+
if (line.kind === "remove") {
|
|
2968
|
+
return "border-r border-rose-200 bg-rose-50 text-rose-700";
|
|
2969
|
+
}
|
|
2970
|
+
if (line.kind === "add") {
|
|
2971
|
+
return "border-r border-emerald-200 bg-emerald-50 text-emerald-700";
|
|
2972
|
+
}
|
|
2973
|
+
return "border-r border-stone-200 bg-stone-100 text-stone-500";
|
|
2974
|
+
}
|
|
2975
|
+
function getCodeRowTone(line) {
|
|
2976
|
+
if (line.kind === "remove") {
|
|
2977
|
+
return "bg-rose-50 text-rose-950";
|
|
2978
|
+
}
|
|
2979
|
+
if (line.kind === "add") {
|
|
2980
|
+
return "bg-emerald-50 text-emerald-950";
|
|
2981
|
+
}
|
|
2982
|
+
return "bg-white text-amber-950/80";
|
|
2983
|
+
}
|
|
2984
|
+
function FileOperationLineRow({
|
|
2985
|
+
line,
|
|
2986
|
+
showLineNumbers,
|
|
2987
|
+
lineNumberColumnWidth
|
|
2988
|
+
}) {
|
|
2989
|
+
return /* @__PURE__ */ jsxs13(
|
|
2990
|
+
"div",
|
|
2991
|
+
{
|
|
2992
|
+
"data-file-line-row": "true",
|
|
2993
|
+
className: cn("grid w-max min-w-full", FILE_ROW_CLASS_NAME),
|
|
2994
|
+
style: buildRowTemplateColumns({
|
|
2995
|
+
showLineNumbers,
|
|
2996
|
+
lineNumberColumnWidth
|
|
2997
|
+
}),
|
|
2998
|
+
children: [
|
|
2999
|
+
showLineNumbers ? /* @__PURE__ */ jsx21(
|
|
3000
|
+
"span",
|
|
3001
|
+
{
|
|
3002
|
+
"data-file-line-number-cell": "true",
|
|
3003
|
+
style: { width: lineNumberColumnWidth },
|
|
3004
|
+
className: cn(
|
|
3005
|
+
FILE_GUTTER_NUMBER_CELL_CLASS_NAME,
|
|
3006
|
+
getLineNumberTone(line)
|
|
3007
|
+
),
|
|
3008
|
+
children: readVisibleLineNumber(line)
|
|
3009
|
+
}
|
|
3010
|
+
) : null,
|
|
3011
|
+
/* @__PURE__ */ jsx21(
|
|
3012
|
+
"span",
|
|
3013
|
+
{
|
|
3014
|
+
"data-file-code-row": "true",
|
|
3015
|
+
className: cn(
|
|
3016
|
+
"block min-w-full whitespace-pre px-2.5",
|
|
3017
|
+
getCodeRowTone(line)
|
|
3018
|
+
),
|
|
3019
|
+
children: line.text || " "
|
|
3020
|
+
}
|
|
3021
|
+
)
|
|
3022
|
+
]
|
|
3023
|
+
}
|
|
3024
|
+
);
|
|
3025
|
+
}
|
|
3026
|
+
function FileOperationLinesGrid({
|
|
3027
|
+
block
|
|
3028
|
+
}) {
|
|
3029
|
+
const showLineNumbers = readHasBlockLineNumbers(block);
|
|
3030
|
+
const lineNumberColumnWidth = readLineNumberColumnWidth(block);
|
|
3031
|
+
return /* @__PURE__ */ jsx21("div", { className: "overflow-x-auto custom-scrollbar-amber bg-white", children: block.lines.map((line, index) => /* @__PURE__ */ jsx21(
|
|
3032
|
+
FileOperationLineRow,
|
|
3033
|
+
{
|
|
3034
|
+
line,
|
|
3035
|
+
showLineNumbers,
|
|
3036
|
+
lineNumberColumnWidth
|
|
3037
|
+
},
|
|
3038
|
+
readLineKey("row", line, index)
|
|
3039
|
+
)) });
|
|
3040
|
+
}
|
|
3041
|
+
|
|
3042
|
+
// src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
|
|
3043
|
+
import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
|
|
3044
|
+
function isPreviewBlock(block) {
|
|
3045
|
+
return block.display === "preview";
|
|
3046
|
+
}
|
|
3047
|
+
function readFileOperationContentVersion(block) {
|
|
3048
|
+
if (block.rawText) {
|
|
3049
|
+
return block.rawText;
|
|
3050
|
+
}
|
|
3051
|
+
return block.lines.map(
|
|
3052
|
+
(line) => [
|
|
3053
|
+
line.kind,
|
|
3054
|
+
line.oldLineNumber ?? "",
|
|
3055
|
+
line.newLineNumber ?? "",
|
|
3056
|
+
line.text
|
|
3057
|
+
].join(":")
|
|
3058
|
+
).join("\n");
|
|
3059
|
+
}
|
|
3060
|
+
function getCaptionTone(part) {
|
|
3061
|
+
if (/^\+\d+$/.test(part)) {
|
|
3062
|
+
return "text-emerald-700";
|
|
3063
|
+
}
|
|
3064
|
+
if (/^-\d+$/.test(part)) {
|
|
3065
|
+
return "text-rose-700";
|
|
3066
|
+
}
|
|
3067
|
+
return "text-stone-500";
|
|
3068
|
+
}
|
|
3069
|
+
function renderCaption(caption) {
|
|
3070
|
+
const parts = caption.split("\xB7").map((part) => part.trim()).filter((part) => /^\+\d+$/.test(part) || /^-\d+$/.test(part));
|
|
3071
|
+
if (parts.length === 0) {
|
|
3072
|
+
return null;
|
|
3073
|
+
}
|
|
3074
|
+
return /* @__PURE__ */ jsx22("div", { className: "flex shrink-0 items-center gap-x-1 whitespace-nowrap text-[10px] font-medium uppercase tracking-[0.08em]", children: parts.map((part, index) => /* @__PURE__ */ jsxs14(Fragment4, { children: [
|
|
3075
|
+
index > 0 ? /* @__PURE__ */ jsx22("span", { className: "text-stone-300", children: "\xB7" }) : null,
|
|
3076
|
+
/* @__PURE__ */ jsx22("span", { className: cn(getCaptionTone(part)), children: part })
|
|
3077
|
+
] }, `${part}-${index}`)) });
|
|
3078
|
+
}
|
|
3079
|
+
function StickyFileOperationScrollArea({
|
|
3080
|
+
children,
|
|
3081
|
+
contentVersion,
|
|
3082
|
+
resetKey,
|
|
3083
|
+
className,
|
|
3084
|
+
scrollKind
|
|
3085
|
+
}) {
|
|
3086
|
+
const scrollRef = useRef7(null);
|
|
3087
|
+
const { onScroll } = useStickyBottomScroll({
|
|
3088
|
+
scrollRef,
|
|
3089
|
+
resetKey,
|
|
3090
|
+
isLoading: false,
|
|
3091
|
+
hasContent: true,
|
|
3092
|
+
contentVersion,
|
|
3093
|
+
stickyThresholdPx: 20
|
|
3094
|
+
});
|
|
3095
|
+
return /* @__PURE__ */ jsx22(
|
|
3096
|
+
"div",
|
|
3097
|
+
{
|
|
3098
|
+
ref: scrollRef,
|
|
3099
|
+
onScroll,
|
|
3100
|
+
"data-file-scroll-kind": scrollKind,
|
|
3101
|
+
className: cn(
|
|
3102
|
+
"overflow-y-auto overscroll-contain bg-white custom-scrollbar-amber",
|
|
3103
|
+
className
|
|
3104
|
+
),
|
|
3105
|
+
children
|
|
3106
|
+
}
|
|
3107
|
+
);
|
|
3108
|
+
}
|
|
3109
|
+
function FileOperationBlock({
|
|
3110
|
+
block,
|
|
3111
|
+
showPathRow,
|
|
3112
|
+
isFirst
|
|
3113
|
+
}) {
|
|
3114
|
+
const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
|
|
3115
|
+
const previewBlock = isPreviewBlock(block);
|
|
3116
|
+
const showMetaRow = showPathRow || Boolean(block.caption);
|
|
3117
|
+
return /* @__PURE__ */ jsxs14(
|
|
3118
|
+
"section",
|
|
3119
|
+
{
|
|
3120
|
+
className: cn(
|
|
3121
|
+
"overflow-hidden bg-white",
|
|
3122
|
+
!isFirst && "border-t border-stone-200/80"
|
|
3123
|
+
),
|
|
3124
|
+
children: [
|
|
3125
|
+
showMetaRow ? /* @__PURE__ */ jsxs14(
|
|
3126
|
+
"div",
|
|
3127
|
+
{
|
|
3128
|
+
className: cn(
|
|
3129
|
+
"flex items-center justify-between gap-4 border-b border-stone-200/80 px-4 text-stone-700",
|
|
3130
|
+
showPathRow ? "py-2" : "py-1.5"
|
|
3131
|
+
),
|
|
3132
|
+
children: [
|
|
3133
|
+
/* @__PURE__ */ jsx22("div", { className: "min-w-0 flex-1", children: showPathRow ? /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 250, children: /* @__PURE__ */ jsxs14(Tooltip, { children: [
|
|
3134
|
+
/* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
|
|
3135
|
+
"div",
|
|
3136
|
+
{
|
|
3137
|
+
className: "truncate whitespace-nowrap font-mono text-[12px] font-medium text-stone-700",
|
|
3138
|
+
title: block.path,
|
|
3139
|
+
children: block.path
|
|
3140
|
+
}
|
|
3141
|
+
) }),
|
|
3142
|
+
/* @__PURE__ */ jsx22(
|
|
3143
|
+
TooltipContent,
|
|
3144
|
+
{
|
|
3145
|
+
side: "top",
|
|
3146
|
+
className: "max-w-[420px] text-xs font-mono break-all",
|
|
3147
|
+
children: block.path
|
|
3148
|
+
}
|
|
3149
|
+
)
|
|
3150
|
+
] }) }) : null }),
|
|
3151
|
+
block.caption ? renderCaption(block.caption) : null
|
|
3152
|
+
]
|
|
3153
|
+
}
|
|
3154
|
+
) : null,
|
|
3155
|
+
block.lines.length > 0 ? /* @__PURE__ */ jsx22(
|
|
3156
|
+
StickyFileOperationScrollArea,
|
|
3157
|
+
{
|
|
3158
|
+
resetKey: `block:${block.key}`,
|
|
3159
|
+
contentVersion: readFileOperationContentVersion(block),
|
|
3160
|
+
className: "max-h-72",
|
|
3161
|
+
scrollKind: "block",
|
|
3162
|
+
children: /* @__PURE__ */ jsx22(FileOperationLinesGrid, { block })
|
|
3163
|
+
}
|
|
3164
|
+
) : block.rawText ? /* @__PURE__ */ jsx22(
|
|
3165
|
+
StickyFileOperationScrollArea,
|
|
3166
|
+
{
|
|
3167
|
+
resetKey: `raw:${block.key}`,
|
|
3168
|
+
contentVersion: block.rawText,
|
|
3169
|
+
className: "max-h-72",
|
|
3170
|
+
scrollKind: "raw",
|
|
3171
|
+
children: /* @__PURE__ */ jsx22("div", { className: "overflow-x-auto custom-scrollbar-amber", children: /* @__PURE__ */ jsx22("pre", { className: "min-w-max whitespace-pre bg-white px-4 py-2 font-mono text-[11px] leading-5 text-amber-950/80", children: block.rawText }) })
|
|
3172
|
+
}
|
|
3173
|
+
) : null,
|
|
3174
|
+
block.truncated && !previewBlock ? /* @__PURE__ */ jsx22("div", { className: "border-t border-stone-200/85 bg-stone-50 px-4 py-2 text-[10px] text-stone-500", children: "Showing a shortened diff preview." }) : null
|
|
3175
|
+
]
|
|
3176
|
+
}
|
|
3177
|
+
);
|
|
3178
|
+
}
|
|
3179
|
+
function ToolCardFileOperationContent({
|
|
3180
|
+
card,
|
|
3181
|
+
className
|
|
3182
|
+
}) {
|
|
3183
|
+
const blocks = card.fileOperation?.blocks ?? [];
|
|
2838
3184
|
const output = card.output?.trim() ?? "";
|
|
2839
|
-
|
|
2840
|
-
|
|
2841
|
-
|
|
2842
|
-
|
|
3185
|
+
if (blocks.length === 0 && !output) {
|
|
3186
|
+
return null;
|
|
3187
|
+
}
|
|
3188
|
+
return /* @__PURE__ */ jsxs14("div", { className: cn("overflow-hidden bg-white", className), children: [
|
|
3189
|
+
blocks.map((block, index) => {
|
|
3190
|
+
return /* @__PURE__ */ jsx22(
|
|
3191
|
+
FileOperationBlock,
|
|
3192
|
+
{
|
|
3193
|
+
block,
|
|
3194
|
+
showPathRow: true,
|
|
3195
|
+
isFirst: index === 0
|
|
3196
|
+
},
|
|
3197
|
+
block.key
|
|
3198
|
+
);
|
|
3199
|
+
}),
|
|
3200
|
+
output ? /* @__PURE__ */ jsx22(
|
|
3201
|
+
StickyFileOperationScrollArea,
|
|
3202
|
+
{
|
|
3203
|
+
resetKey: `output:${card.toolName}:${card.summary ?? "none"}`,
|
|
3204
|
+
contentVersion: output,
|
|
3205
|
+
className: cn(
|
|
3206
|
+
"max-h-56",
|
|
3207
|
+
blocks.length > 0 && "border-t border-stone-200/80"
|
|
3208
|
+
),
|
|
3209
|
+
scrollKind: "output",
|
|
3210
|
+
children: /* @__PURE__ */ jsx22("div", { className: "overflow-x-auto custom-scrollbar-amber", children: /* @__PURE__ */ jsx22("pre", { className: "min-w-max whitespace-pre bg-white px-4 py-2 font-mono text-[11px] leading-5 text-amber-950/80", children: output }) })
|
|
3211
|
+
}
|
|
3212
|
+
) : null
|
|
3213
|
+
] });
|
|
3214
|
+
}
|
|
3215
|
+
|
|
3216
|
+
// src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
|
|
3217
|
+
import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
|
|
3218
|
+
var TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
|
|
3219
|
+
var ANSI_ESCAPE_PREFIX = String.fromCharCode(27);
|
|
3220
|
+
var ANSI_ESCAPE_PATTERN = new RegExp(`${ANSI_ESCAPE_PREFIX}\\[[0-?]*[ -/]*[@-~]`, "g");
|
|
3221
|
+
function readNonEmptyString(value) {
|
|
3222
|
+
if (typeof value !== "string") {
|
|
3223
|
+
return null;
|
|
3224
|
+
}
|
|
3225
|
+
return value.length > 0 ? value : null;
|
|
3226
|
+
}
|
|
3227
|
+
function isRecord(value) {
|
|
3228
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
3229
|
+
}
|
|
3230
|
+
function stripAnsi(value) {
|
|
3231
|
+
return value.replace(ANSI_ESCAPE_PATTERN, "");
|
|
3232
|
+
}
|
|
3233
|
+
function isStructuredTerminalRecord(record) {
|
|
3234
|
+
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;
|
|
3235
|
+
}
|
|
3236
|
+
function extractTerminalOutputFromRecord(record) {
|
|
3237
|
+
const aggregatedOutput = readNonEmptyString(record.aggregated_output) ?? readNonEmptyString(record.combinedOutput) ?? readNonEmptyString(record.output);
|
|
3238
|
+
if (aggregatedOutput) {
|
|
3239
|
+
return aggregatedOutput;
|
|
3240
|
+
}
|
|
3241
|
+
const stdout = readNonEmptyString(record.stdout);
|
|
3242
|
+
const stderr = readNonEmptyString(record.stderr);
|
|
3243
|
+
if (!stdout && !stderr) {
|
|
3244
|
+
return null;
|
|
3245
|
+
}
|
|
3246
|
+
return [stdout, stderr].filter((value) => Boolean(value)).join("\n");
|
|
3247
|
+
}
|
|
3248
|
+
function normalizeTerminalOutput(rawOutput, structuredOutput) {
|
|
3249
|
+
if (isRecord(structuredOutput)) {
|
|
3250
|
+
const terminalOutput = extractTerminalOutputFromRecord(structuredOutput);
|
|
3251
|
+
if (terminalOutput) {
|
|
3252
|
+
return stripAnsi(terminalOutput);
|
|
3253
|
+
}
|
|
3254
|
+
if (isStructuredTerminalRecord(structuredOutput)) {
|
|
3255
|
+
return "";
|
|
3256
|
+
}
|
|
3257
|
+
}
|
|
3258
|
+
if (!rawOutput) {
|
|
3259
|
+
return "";
|
|
3260
|
+
}
|
|
3261
|
+
const trimmed = rawOutput.trim();
|
|
3262
|
+
if (!trimmed.startsWith("{")) {
|
|
3263
|
+
return stripAnsi(rawOutput);
|
|
3264
|
+
}
|
|
3265
|
+
try {
|
|
3266
|
+
const parsed = JSON.parse(trimmed);
|
|
3267
|
+
if (!isRecord(parsed)) {
|
|
3268
|
+
return stripAnsi(rawOutput);
|
|
3269
|
+
}
|
|
3270
|
+
const terminalOutput = extractTerminalOutputFromRecord(parsed);
|
|
3271
|
+
if (terminalOutput) {
|
|
3272
|
+
return stripAnsi(terminalOutput);
|
|
3273
|
+
}
|
|
3274
|
+
if (isStructuredTerminalRecord(parsed)) {
|
|
3275
|
+
return "";
|
|
3276
|
+
}
|
|
3277
|
+
return stripAnsi(rawOutput);
|
|
3278
|
+
} catch {
|
|
3279
|
+
return stripAnsi(rawOutput);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
function useToolCardExpandedState({
|
|
3283
|
+
canExpand,
|
|
3284
|
+
isRunning,
|
|
3285
|
+
autoExpandWhileRunning = true,
|
|
3286
|
+
expandOnError = false,
|
|
3287
|
+
statusTone
|
|
3288
|
+
}) {
|
|
3289
|
+
const [expanded, setExpanded] = useState7(false);
|
|
2843
3290
|
const [hasUserToggled, setHasUserToggled] = useState7(false);
|
|
2844
|
-
const
|
|
2845
|
-
|
|
2846
|
-
|
|
3291
|
+
const expandTimerRef = useRef8(null);
|
|
3292
|
+
const prevRunningRef = useRef8(isRunning);
|
|
3293
|
+
const isFirstRenderRef = useRef8(true);
|
|
3294
|
+
useEffect8(() => {
|
|
3295
|
+
return () => {
|
|
3296
|
+
if (expandTimerRef.current !== null) {
|
|
3297
|
+
window.clearTimeout(expandTimerRef.current);
|
|
3298
|
+
}
|
|
3299
|
+
};
|
|
3300
|
+
}, []);
|
|
3301
|
+
useEffect8(() => {
|
|
3302
|
+
if (expandOnError && statusTone === "error" && canExpand && !hasUserToggled) {
|
|
3303
|
+
if (expandTimerRef.current !== null) {
|
|
3304
|
+
window.clearTimeout(expandTimerRef.current);
|
|
3305
|
+
expandTimerRef.current = null;
|
|
3306
|
+
}
|
|
2847
3307
|
setExpanded(true);
|
|
2848
|
-
|
|
3308
|
+
prevRunningRef.current = isRunning;
|
|
3309
|
+
isFirstRenderRef.current = false;
|
|
3310
|
+
return;
|
|
2849
3311
|
}
|
|
2850
|
-
|
|
2851
|
-
|
|
2852
|
-
|
|
2853
|
-
|
|
3312
|
+
if (autoExpandWhileRunning && isRunning && canExpand && !hasUserToggled && !expanded && (isFirstRenderRef.current || !prevRunningRef.current)) {
|
|
3313
|
+
expandTimerRef.current = window.setTimeout(() => {
|
|
3314
|
+
setExpanded(true);
|
|
3315
|
+
expandTimerRef.current = null;
|
|
3316
|
+
}, TOOL_CARD_AUTO_EXPAND_DELAY_MS);
|
|
3317
|
+
}
|
|
3318
|
+
if (!isRunning) {
|
|
3319
|
+
if (expandTimerRef.current !== null) {
|
|
3320
|
+
window.clearTimeout(expandTimerRef.current);
|
|
3321
|
+
expandTimerRef.current = null;
|
|
3322
|
+
}
|
|
3323
|
+
if (prevRunningRef.current && !hasUserToggled) {
|
|
3324
|
+
setExpanded(false);
|
|
3325
|
+
}
|
|
2854
3326
|
}
|
|
2855
3327
|
prevRunningRef.current = isRunning;
|
|
2856
|
-
|
|
3328
|
+
isFirstRenderRef.current = false;
|
|
3329
|
+
}, [autoExpandWhileRunning, canExpand, expandOnError, expanded, hasUserToggled, isRunning, statusTone]);
|
|
2857
3330
|
const onToggle = () => {
|
|
2858
|
-
if (!
|
|
2859
|
-
|
|
3331
|
+
if (!canExpand) {
|
|
3332
|
+
return;
|
|
3333
|
+
}
|
|
3334
|
+
if (expandTimerRef.current !== null) {
|
|
3335
|
+
window.clearTimeout(expandTimerRef.current);
|
|
3336
|
+
expandTimerRef.current = null;
|
|
3337
|
+
}
|
|
3338
|
+
setExpanded((current) => !current);
|
|
2860
3339
|
setHasUserToggled(true);
|
|
2861
3340
|
};
|
|
3341
|
+
return { expanded, onToggle };
|
|
3342
|
+
}
|
|
3343
|
+
function GenericToolSection({
|
|
3344
|
+
label,
|
|
3345
|
+
tone,
|
|
3346
|
+
children
|
|
3347
|
+
}) {
|
|
3348
|
+
const tones = {
|
|
3349
|
+
input: {
|
|
3350
|
+
shell: "border-stone-200/80 bg-stone-50/90",
|
|
3351
|
+
header: "border-stone-200/80 bg-stone-100/85 text-stone-500",
|
|
3352
|
+
dot: "bg-stone-400/80",
|
|
3353
|
+
body: "text-stone-700"
|
|
3354
|
+
},
|
|
3355
|
+
output: {
|
|
3356
|
+
shell: "border-amber-200/70 bg-white/90",
|
|
3357
|
+
header: "border-amber-200/70 bg-amber-50/90 text-amber-700",
|
|
3358
|
+
dot: "bg-amber-500/80",
|
|
3359
|
+
body: "text-amber-950/80"
|
|
3360
|
+
},
|
|
3361
|
+
error: {
|
|
3362
|
+
shell: "border-rose-200/80 bg-rose-50/85",
|
|
3363
|
+
header: "border-rose-200/80 bg-rose-100/80 text-rose-700",
|
|
3364
|
+
dot: "bg-rose-500/80",
|
|
3365
|
+
body: "text-rose-950/85"
|
|
3366
|
+
}
|
|
3367
|
+
};
|
|
3368
|
+
const style = tones[tone];
|
|
3369
|
+
return /* @__PURE__ */ jsxs15(
|
|
3370
|
+
"section",
|
|
3371
|
+
{
|
|
3372
|
+
className: cn(
|
|
3373
|
+
"overflow-hidden rounded-md border shadow-[inset_0_1px_0_rgba(255,255,255,0.45)]",
|
|
3374
|
+
style.shell
|
|
3375
|
+
),
|
|
3376
|
+
children: [
|
|
3377
|
+
/* @__PURE__ */ jsxs15(
|
|
3378
|
+
"div",
|
|
3379
|
+
{
|
|
3380
|
+
className: cn(
|
|
3381
|
+
"flex items-center gap-2 border-b px-3 py-2 text-[10px] font-semibold uppercase tracking-[0.16em]",
|
|
3382
|
+
style.header
|
|
3383
|
+
),
|
|
3384
|
+
children: [
|
|
3385
|
+
/* @__PURE__ */ jsx23("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }),
|
|
3386
|
+
/* @__PURE__ */ jsx23("span", { children: label })
|
|
3387
|
+
]
|
|
3388
|
+
}
|
|
3389
|
+
),
|
|
3390
|
+
/* @__PURE__ */ jsx23("div", { className: "w-full overflow-hidden", children: /* @__PURE__ */ jsx23(
|
|
3391
|
+
"pre",
|
|
3392
|
+
{
|
|
3393
|
+
className: cn(
|
|
3394
|
+
"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-amber",
|
|
3395
|
+
style.body
|
|
3396
|
+
),
|
|
3397
|
+
children
|
|
3398
|
+
}
|
|
3399
|
+
) })
|
|
3400
|
+
]
|
|
3401
|
+
}
|
|
3402
|
+
);
|
|
3403
|
+
}
|
|
3404
|
+
function TerminalExecutionView({ card }) {
|
|
3405
|
+
const output = normalizeTerminalOutput(card.output, card.outputData);
|
|
3406
|
+
const isRunning = card.statusTone === "running";
|
|
3407
|
+
const hasContent = !!(card.summary?.trim() || output.trim().length > 0);
|
|
3408
|
+
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3409
|
+
canExpand: output.trim().length > 0 || isRunning,
|
|
3410
|
+
isRunning,
|
|
3411
|
+
expandOnError: hasContent,
|
|
3412
|
+
statusTone: card.statusTone
|
|
3413
|
+
});
|
|
2862
3414
|
const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
|
|
2863
|
-
return /* @__PURE__ */
|
|
2864
|
-
/* @__PURE__ */
|
|
3415
|
+
return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
|
|
3416
|
+
/* @__PURE__ */ jsx23(
|
|
2865
3417
|
ToolCardHeader,
|
|
2866
3418
|
{
|
|
2867
3419
|
card,
|
|
2868
3420
|
icon: Terminal,
|
|
2869
3421
|
expanded,
|
|
2870
|
-
canExpand:
|
|
3422
|
+
canExpand: output.trim().length > 0 || isRunning,
|
|
2871
3423
|
onToggle
|
|
2872
3424
|
}
|
|
2873
3425
|
),
|
|
2874
|
-
expanded && /* @__PURE__ */
|
|
2875
|
-
/* @__PURE__ */
|
|
2876
|
-
/* @__PURE__ */
|
|
2877
|
-
/* @__PURE__ */
|
|
3426
|
+
expanded && /* @__PURE__ */ jsxs15(Fragment5, { children: [
|
|
3427
|
+
/* @__PURE__ */ jsx23("div", { className: "px-3 pb-2 font-mono w-full max-h-48 overflow-y-auto custom-scrollbar-amber min-h-0 text-[12px]", children: /* @__PURE__ */ jsxs15("div", { className: "flex items-start gap-2 leading-relaxed", children: [
|
|
3428
|
+
/* @__PURE__ */ jsx23("span", { className: "text-amber-500/50 font-medium shrink-0 select-none mt-[1px]", children: "$" }),
|
|
3429
|
+
/* @__PURE__ */ jsx23("div", { className: "flex-1 min-w-0", children: commandPart ? /* @__PURE__ */ jsxs15("div", { className: "text-amber-950/80 break-words whitespace-pre-wrap tracking-tight font-medium inline-block", children: [
|
|
2878
3430
|
commandPart,
|
|
2879
|
-
isRunning && !output && /* @__PURE__ */
|
|
2880
|
-
] }) : /* @__PURE__ */
|
|
3431
|
+
isRunning && !output && /* @__PURE__ */ jsx23("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
|
|
3432
|
+
] }) : /* @__PURE__ */ jsx23("div", { className: "h-3 w-32 bg-amber-200/30 rounded animate-pulse mt-2" }) })
|
|
2881
3433
|
] }) }),
|
|
2882
|
-
output && /* @__PURE__ */
|
|
3434
|
+
output && /* @__PURE__ */ jsx23(ToolCardContent, { children: /* @__PURE__ */ jsxs15("pre", { className: "font-mono text-[12px] text-amber-950/70 whitespace-pre-wrap break-all w-full max-w-full max-h-64 overflow-y-auto overflow-x-hidden min-w-0 custom-scrollbar-amber leading-relaxed px-0", children: [
|
|
2883
3435
|
output,
|
|
2884
|
-
isRunning && /* @__PURE__ */
|
|
3436
|
+
isRunning && /* @__PURE__ */ jsx23("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
|
|
2885
3437
|
] }) })
|
|
2886
3438
|
] })
|
|
2887
3439
|
] });
|
|
@@ -2889,75 +3441,50 @@ function TerminalExecutionView({ card }) {
|
|
|
2889
3441
|
function FileOperationView({ card }) {
|
|
2890
3442
|
const output = card.output?.trim() ?? "";
|
|
2891
3443
|
const isRunning = card.statusTone === "running";
|
|
2892
|
-
const
|
|
2893
|
-
const
|
|
2894
|
-
const
|
|
2895
|
-
|
|
2896
|
-
|
|
2897
|
-
|
|
2898
|
-
|
|
2899
|
-
|
|
2900
|
-
|
|
2901
|
-
|
|
2902
|
-
|
|
2903
|
-
|
|
2904
|
-
|
|
2905
|
-
|
|
2906
|
-
|
|
2907
|
-
|
|
2908
|
-
|
|
2909
|
-
|
|
2910
|
-
|
|
2911
|
-
|
|
2912
|
-
|
|
2913
|
-
] }, idx);
|
|
2914
|
-
}
|
|
2915
|
-
if (line.startsWith("-") && !line.startsWith("---")) {
|
|
2916
|
-
return /* @__PURE__ */ jsxs13("div", { className: "bg-rose-500/10 text-rose-700 px-2 py-0.5 w-full break-all whitespace-pre-wrap", children: [
|
|
2917
|
-
/* @__PURE__ */ jsx21("span", { className: "select-none opacity-40 mr-2 w-3 inline-block shrink-0", children: "-" }),
|
|
2918
|
-
/* @__PURE__ */ jsx21("span", { className: "line-through decoration-rose-400/50", children: line.slice(1) })
|
|
2919
|
-
] }, idx);
|
|
2920
|
-
}
|
|
2921
|
-
}
|
|
2922
|
-
return /* @__PURE__ */ jsx21("div", { className: "py-0.5 text-amber-950/80 w-full break-all whitespace-pre-wrap", children: /* @__PURE__ */ jsx21("span", { children: line }) }, idx);
|
|
2923
|
-
};
|
|
2924
|
-
const lines = output.split("\n");
|
|
2925
|
-
const maxLines = 15;
|
|
2926
|
-
const isLong = lines.length > maxLines;
|
|
2927
|
-
const displayLines = !expanded && isLong ? lines.slice(0, maxLines) : lines;
|
|
2928
|
-
return /* @__PURE__ */ jsxs13(ToolCardRoot, { children: [
|
|
2929
|
-
/* @__PURE__ */ jsx21(
|
|
3444
|
+
const hasStructuredPreview = Boolean(card.fileOperation?.blocks.length);
|
|
3445
|
+
const hasContent = hasStructuredPreview || Boolean(output);
|
|
3446
|
+
const previewBlocks = card.fileOperation?.blocks ?? [];
|
|
3447
|
+
const previewLineCount = previewBlocks.reduce((count, block) => count + block.lines.length, 0);
|
|
3448
|
+
const previewCharCount = previewBlocks.reduce((count, block) => {
|
|
3449
|
+
if (block.rawText) {
|
|
3450
|
+
return count + block.rawText.length;
|
|
3451
|
+
}
|
|
3452
|
+
return count + block.lines.reduce((lineCount, line) => lineCount + line.text.length + 1, 0);
|
|
3453
|
+
}, 0);
|
|
3454
|
+
const shouldAutoExpandWhileRunning = !(isRunning && card.toolName === "write_file" && (previewLineCount > 24 || previewCharCount > 1200));
|
|
3455
|
+
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3456
|
+
canExpand: hasContent || isRunning,
|
|
3457
|
+
isRunning,
|
|
3458
|
+
autoExpandWhileRunning: shouldAutoExpandWhileRunning,
|
|
3459
|
+
expandOnError: hasContent,
|
|
3460
|
+
statusTone: card.statusTone
|
|
3461
|
+
});
|
|
3462
|
+
const isEdit = card.toolName === "edit_file" || card.toolName === "write_file" || card.toolName === "apply_patch" || card.toolName === "file_change";
|
|
3463
|
+
return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
|
|
3464
|
+
/* @__PURE__ */ jsx23(
|
|
2930
3465
|
ToolCardHeader,
|
|
2931
3466
|
{
|
|
2932
3467
|
card,
|
|
2933
3468
|
icon: isEdit ? Code2 : FileText,
|
|
2934
3469
|
expanded,
|
|
2935
|
-
canExpand:
|
|
3470
|
+
canExpand: hasContent || isRunning,
|
|
3471
|
+
hideSummary: expanded && hasStructuredPreview,
|
|
2936
3472
|
onToggle
|
|
2937
3473
|
}
|
|
2938
3474
|
),
|
|
2939
|
-
expanded &&
|
|
3475
|
+
expanded && hasContent && /* @__PURE__ */ jsx23(ToolCardContent, { className: "border-t border-amber-200/20 bg-transparent px-0 pt-0 pb-0", children: /* @__PURE__ */ jsx23(ToolCardFileOperationContent, { card }) })
|
|
2940
3476
|
] });
|
|
2941
3477
|
}
|
|
2942
3478
|
function SearchSnippetView({ card }) {
|
|
2943
3479
|
const isRunning = card.statusTone === "running";
|
|
2944
|
-
const [expanded, setExpanded] = useState7(isRunning);
|
|
2945
|
-
const [hasUserToggled, setHasUserToggled] = useState7(false);
|
|
2946
|
-
const prevRunningRef = useRef6(isRunning);
|
|
2947
3480
|
const output = card.output?.trim() ?? "";
|
|
2948
|
-
|
|
2949
|
-
|
|
2950
|
-
|
|
2951
|
-
|
|
2952
|
-
|
|
2953
|
-
|
|
2954
|
-
|
|
2955
|
-
if (!output && !isRunning) return;
|
|
2956
|
-
setExpanded(!expanded);
|
|
2957
|
-
setHasUserToggled(true);
|
|
2958
|
-
};
|
|
2959
|
-
return /* @__PURE__ */ jsxs13(ToolCardRoot, { children: [
|
|
2960
|
-
/* @__PURE__ */ jsx21(
|
|
3481
|
+
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3482
|
+
canExpand: !!output || isRunning,
|
|
3483
|
+
isRunning,
|
|
3484
|
+
statusTone: card.statusTone
|
|
3485
|
+
});
|
|
3486
|
+
return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
|
|
3487
|
+
/* @__PURE__ */ jsx23(
|
|
2961
3488
|
ToolCardHeader,
|
|
2962
3489
|
{
|
|
2963
3490
|
card,
|
|
@@ -2967,51 +3494,58 @@ function SearchSnippetView({ card }) {
|
|
|
2967
3494
|
onToggle
|
|
2968
3495
|
}
|
|
2969
3496
|
),
|
|
2970
|
-
expanded && output && /* @__PURE__ */
|
|
3497
|
+
expanded && output && /* @__PURE__ */ jsx23(ToolCardContent, { children: /* @__PURE__ */ jsx23("pre", { className: "font-mono text-[12px] text-amber-950/70 whitespace-pre-wrap break-all w-full max-w-full max-h-64 overflow-y-auto overflow-x-hidden min-w-0 custom-scrollbar-amber leading-relaxed", children: output }) })
|
|
2971
3498
|
] });
|
|
2972
3499
|
}
|
|
2973
3500
|
function GenericToolCard({ card }) {
|
|
3501
|
+
const input = card.input?.trim() ?? "";
|
|
2974
3502
|
const output = card.output?.trim() ?? "";
|
|
2975
3503
|
const isRunning = card.statusTone === "running";
|
|
2976
|
-
const
|
|
2977
|
-
const
|
|
2978
|
-
const
|
|
2979
|
-
const
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
2984
|
-
|
|
2985
|
-
}
|
|
2986
|
-
|
|
2987
|
-
|
|
2988
|
-
setExpanded(!expanded);
|
|
2989
|
-
setHasUserToggled(true);
|
|
2990
|
-
};
|
|
2991
|
-
return /* @__PURE__ */ jsxs13(ToolCardRoot, { children: [
|
|
2992
|
-
/* @__PURE__ */ jsx21(
|
|
3504
|
+
const hasInputSection = input.length > 0;
|
|
3505
|
+
const hasOutputSection = output.length > 0;
|
|
3506
|
+
const hasContent = hasInputSection || hasOutputSection;
|
|
3507
|
+
const inputLabel = card.inputLabel?.trim() || "Input";
|
|
3508
|
+
const outputLabel = card.outputLabel?.trim() || "Output";
|
|
3509
|
+
const { expanded, onToggle } = useToolCardExpandedState({
|
|
3510
|
+
canExpand: hasContent || isRunning,
|
|
3511
|
+
isRunning,
|
|
3512
|
+
statusTone: card.statusTone
|
|
3513
|
+
});
|
|
3514
|
+
return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
|
|
3515
|
+
/* @__PURE__ */ jsx23(
|
|
2993
3516
|
ToolCardHeader,
|
|
2994
3517
|
{
|
|
2995
3518
|
card,
|
|
2996
3519
|
icon: Globe,
|
|
2997
3520
|
expanded,
|
|
2998
|
-
canExpand:
|
|
3521
|
+
canExpand: hasContent || isRunning,
|
|
2999
3522
|
onToggle
|
|
3000
3523
|
}
|
|
3001
3524
|
),
|
|
3002
|
-
expanded &&
|
|
3525
|
+
expanded && hasContent && /* @__PURE__ */ jsxs15(ToolCardContent, { className: "bg-transparent px-2.5 py-2.5", children: [
|
|
3526
|
+
hasInputSection && /* @__PURE__ */ jsx23(GenericToolSection, { label: inputLabel, tone: "input", children: input }),
|
|
3527
|
+
hasInputSection && hasOutputSection && /* @__PURE__ */ jsx23("div", { className: "h-2" }),
|
|
3528
|
+
hasOutputSection && /* @__PURE__ */ jsx23(
|
|
3529
|
+
GenericToolSection,
|
|
3530
|
+
{
|
|
3531
|
+
label: outputLabel,
|
|
3532
|
+
tone: card.statusTone === "error" ? "error" : "output",
|
|
3533
|
+
children: output
|
|
3534
|
+
}
|
|
3535
|
+
)
|
|
3536
|
+
] })
|
|
3003
3537
|
] });
|
|
3004
3538
|
}
|
|
3005
3539
|
|
|
3006
3540
|
// src/components/chat/ui/chat-message-list/chat-tool-card.tsx
|
|
3007
|
-
import { jsx as
|
|
3541
|
+
import { jsx as jsx24 } from "react/jsx-runtime";
|
|
3008
3542
|
function isTerminalTool(name) {
|
|
3009
3543
|
const lowered = name.toLowerCase();
|
|
3010
|
-
return lowered === "exec" || lowered === "execute_command" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
|
|
3544
|
+
return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
|
|
3011
3545
|
}
|
|
3012
3546
|
function isFileEditTool(name) {
|
|
3013
3547
|
const lowered = name.toLowerCase();
|
|
3014
|
-
return lowered === "read_file" || lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch";
|
|
3548
|
+
return lowered === "read_file" || lowered === "write_file" || lowered === "edit_file" || lowered === "apply_patch" || lowered === "file_change";
|
|
3015
3549
|
}
|
|
3016
3550
|
function isSearchTool(name) {
|
|
3017
3551
|
const lowered = name.toLowerCase();
|
|
@@ -3019,48 +3553,48 @@ function isSearchTool(name) {
|
|
|
3019
3553
|
}
|
|
3020
3554
|
function ChatToolCard({ card }) {
|
|
3021
3555
|
if (isTerminalTool(card.toolName)) {
|
|
3022
|
-
return /* @__PURE__ */
|
|
3556
|
+
return /* @__PURE__ */ jsx24(TerminalExecutionView, { card });
|
|
3023
3557
|
}
|
|
3024
3558
|
if (isFileEditTool(card.toolName)) {
|
|
3025
|
-
return /* @__PURE__ */
|
|
3559
|
+
return /* @__PURE__ */ jsx24(FileOperationView, { card });
|
|
3026
3560
|
}
|
|
3027
3561
|
if (isSearchTool(card.toolName)) {
|
|
3028
|
-
return /* @__PURE__ */
|
|
3562
|
+
return /* @__PURE__ */ jsx24(SearchSnippetView, { card });
|
|
3029
3563
|
}
|
|
3030
|
-
return /* @__PURE__ */
|
|
3564
|
+
return /* @__PURE__ */ jsx24(GenericToolCard, { card });
|
|
3031
3565
|
}
|
|
3032
3566
|
|
|
3033
3567
|
// src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
|
|
3034
|
-
import { jsx as
|
|
3568
|
+
import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
|
|
3035
3569
|
function ChatUnknownPart(props) {
|
|
3036
|
-
return /* @__PURE__ */
|
|
3037
|
-
/* @__PURE__ */
|
|
3570
|
+
return /* @__PURE__ */ jsxs16("div", { className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600", children: [
|
|
3571
|
+
/* @__PURE__ */ jsxs16("div", { className: "font-semibold text-gray-700", children: [
|
|
3038
3572
|
props.label,
|
|
3039
3573
|
": ",
|
|
3040
3574
|
props.rawType
|
|
3041
3575
|
] }),
|
|
3042
|
-
props.text ? /* @__PURE__ */
|
|
3576
|
+
props.text ? /* @__PURE__ */ jsx25("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
|
|
3043
3577
|
] });
|
|
3044
3578
|
}
|
|
3045
3579
|
|
|
3046
3580
|
// src/components/chat/ui/chat-message-list/chat-message.tsx
|
|
3047
|
-
import { jsx as
|
|
3581
|
+
import { jsx as jsx26 } from "react/jsx-runtime";
|
|
3048
3582
|
var ChatMessage = memo(function ChatMessage2(props) {
|
|
3049
3583
|
const { message, texts } = props;
|
|
3050
3584
|
const { role } = message;
|
|
3051
3585
|
const isUser = role === "user";
|
|
3052
3586
|
const isMessageInProgress = message.status === "pending" || message.status === "streaming";
|
|
3053
|
-
return /* @__PURE__ */
|
|
3587
|
+
return /* @__PURE__ */ jsx26(
|
|
3054
3588
|
"div",
|
|
3055
3589
|
{
|
|
3056
3590
|
className: cn(
|
|
3057
3591
|
"inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm",
|
|
3058
3592
|
isUser ? "border-primary bg-primary py-3 text-white" : role === "assistant" ? "border-gray-200 bg-white pb-3 pt-4 text-gray-900" : "border-orange-200/80 bg-orange-50/70 py-3 text-gray-900"
|
|
3059
3593
|
),
|
|
3060
|
-
children: /* @__PURE__ */
|
|
3594
|
+
children: /* @__PURE__ */ jsx26("div", { className: "space-y-2", children: message.parts.map((part, index) => {
|
|
3061
3595
|
const { type } = part;
|
|
3062
3596
|
if (type === "markdown") {
|
|
3063
|
-
return /* @__PURE__ */
|
|
3597
|
+
return /* @__PURE__ */ jsx26(
|
|
3064
3598
|
ChatMessageMarkdown,
|
|
3065
3599
|
{
|
|
3066
3600
|
text: part.text,
|
|
@@ -3071,7 +3605,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
|
|
|
3071
3605
|
);
|
|
3072
3606
|
}
|
|
3073
3607
|
if (type === "inline-content") {
|
|
3074
|
-
return /* @__PURE__ */
|
|
3608
|
+
return /* @__PURE__ */ jsx26(
|
|
3075
3609
|
ChatMessageInlineContent,
|
|
3076
3610
|
{
|
|
3077
3611
|
segments: part.segments,
|
|
@@ -3082,7 +3616,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
|
|
|
3082
3616
|
);
|
|
3083
3617
|
}
|
|
3084
3618
|
if (type === "reasoning") {
|
|
3085
|
-
return /* @__PURE__ */
|
|
3619
|
+
return /* @__PURE__ */ jsx26(
|
|
3086
3620
|
ChatReasoningBlock,
|
|
3087
3621
|
{
|
|
3088
3622
|
label: part.label,
|
|
@@ -3094,10 +3628,10 @@ var ChatMessage = memo(function ChatMessage2(props) {
|
|
|
3094
3628
|
);
|
|
3095
3629
|
}
|
|
3096
3630
|
if (type === "tool-card") {
|
|
3097
|
-
return /* @__PURE__ */
|
|
3631
|
+
return /* @__PURE__ */ jsx26("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx26(ChatToolCard, { card: part.card }) }, `tool-${index}`);
|
|
3098
3632
|
}
|
|
3099
3633
|
if (type === "file") {
|
|
3100
|
-
return /* @__PURE__ */
|
|
3634
|
+
return /* @__PURE__ */ jsx26(
|
|
3101
3635
|
ChatMessageFile,
|
|
3102
3636
|
{
|
|
3103
3637
|
file: part.file,
|
|
@@ -3107,7 +3641,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
|
|
|
3107
3641
|
);
|
|
3108
3642
|
}
|
|
3109
3643
|
if (type === "unknown") {
|
|
3110
|
-
return /* @__PURE__ */
|
|
3644
|
+
return /* @__PURE__ */ jsx26(
|
|
3111
3645
|
ChatUnknownPart,
|
|
3112
3646
|
{
|
|
3113
3647
|
label: part.label,
|
|
@@ -3124,9 +3658,9 @@ var ChatMessage = memo(function ChatMessage2(props) {
|
|
|
3124
3658
|
});
|
|
3125
3659
|
|
|
3126
3660
|
// src/components/chat/ui/chat-message-list/chat-message-meta.tsx
|
|
3127
|
-
import { jsxs as
|
|
3661
|
+
import { jsxs as jsxs17 } from "react/jsx-runtime";
|
|
3128
3662
|
function ChatMessageMeta(props) {
|
|
3129
|
-
return /* @__PURE__ */
|
|
3663
|
+
return /* @__PURE__ */ jsxs17(
|
|
3130
3664
|
"div",
|
|
3131
3665
|
{
|
|
3132
3666
|
className: cn(
|
|
@@ -3145,7 +3679,7 @@ function ChatMessageMeta(props) {
|
|
|
3145
3679
|
// src/components/chat/ui/chat-message-list/chat-message-action-copy.tsx
|
|
3146
3680
|
import { useMemo as useMemo6 } from "react";
|
|
3147
3681
|
import { Check as Check5, Copy as Copy2 } from "lucide-react";
|
|
3148
|
-
import { jsx as
|
|
3682
|
+
import { jsx as jsx27 } from "react/jsx-runtime";
|
|
3149
3683
|
function ChatMessageActionCopy({
|
|
3150
3684
|
message,
|
|
3151
3685
|
texts
|
|
@@ -3159,7 +3693,7 @@ function ChatMessageActionCopy({
|
|
|
3159
3693
|
}, [message.parts]);
|
|
3160
3694
|
const { copied, copy } = useCopyFeedback({ text: messageText });
|
|
3161
3695
|
if (!messageText) return null;
|
|
3162
|
-
return /* @__PURE__ */
|
|
3696
|
+
return /* @__PURE__ */ jsx27(
|
|
3163
3697
|
"button",
|
|
3164
3698
|
{
|
|
3165
3699
|
type: "button",
|
|
@@ -3167,13 +3701,13 @@ function ChatMessageActionCopy({
|
|
|
3167
3701
|
className: "text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-md hover:bg-gray-100 flex items-center justify-center",
|
|
3168
3702
|
"aria-label": copied ? texts.copiedMessageLabel : texts.copyMessageLabel,
|
|
3169
3703
|
title: copied ? texts.copiedMessageLabel : texts.copyMessageLabel,
|
|
3170
|
-
children: copied ? /* @__PURE__ */
|
|
3704
|
+
children: copied ? /* @__PURE__ */ jsx27(Check5, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx27(Copy2, { className: "h-3.5 w-3.5" })
|
|
3171
3705
|
}
|
|
3172
3706
|
);
|
|
3173
3707
|
}
|
|
3174
3708
|
|
|
3175
3709
|
// src/components/chat/ui/chat-message-list/chat-message-list.tsx
|
|
3176
|
-
import { Fragment as
|
|
3710
|
+
import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
|
|
3177
3711
|
var INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
|
|
3178
3712
|
function hasRenderableText(value) {
|
|
3179
3713
|
const trimmed = value.trim();
|
|
@@ -3191,10 +3725,10 @@ function hasRenderableMessageContent(message) {
|
|
|
3191
3725
|
});
|
|
3192
3726
|
}
|
|
3193
3727
|
function ChatMessageTypingFooter() {
|
|
3194
|
-
return /* @__PURE__ */
|
|
3195
|
-
/* @__PURE__ */
|
|
3196
|
-
/* @__PURE__ */
|
|
3197
|
-
/* @__PURE__ */
|
|
3728
|
+
return /* @__PURE__ */ jsx28("div", { className: "flex items-center gap-2 px-1 py-0.5 text-[11px] text-gray-400", children: /* @__PURE__ */ jsxs18("div", { className: "flex space-x-1 items-center h-full", children: [
|
|
3729
|
+
/* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse" }),
|
|
3730
|
+
/* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:200ms]" }),
|
|
3731
|
+
/* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:400ms]" })
|
|
3198
3732
|
] }) });
|
|
3199
3733
|
}
|
|
3200
3734
|
function ChatMessageList(props) {
|
|
@@ -3202,118 +3736,28 @@ function ChatMessageList(props) {
|
|
|
3202
3736
|
const hasRenderableAssistantDraft = visibleMessages.some(
|
|
3203
3737
|
(message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending")
|
|
3204
3738
|
);
|
|
3205
|
-
return /* @__PURE__ */
|
|
3739
|
+
return /* @__PURE__ */ jsxs18("div", { className: cn("space-y-5", props.className), children: [
|
|
3206
3740
|
visibleMessages.map((message) => {
|
|
3207
3741
|
const isUser = message.role === "user";
|
|
3208
3742
|
const isGenerating = !isUser && (message.status === "streaming" || message.status === "pending");
|
|
3209
|
-
return /* @__PURE__ */
|
|
3210
|
-
!isUser ? /* @__PURE__ */
|
|
3211
|
-
/* @__PURE__ */
|
|
3212
|
-
/* @__PURE__ */
|
|
3213
|
-
/* @__PURE__ */
|
|
3214
|
-
/* @__PURE__ */
|
|
3215
|
-
!isUser ? /* @__PURE__ */
|
|
3743
|
+
return /* @__PURE__ */ jsxs18("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
|
|
3744
|
+
!isUser ? /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: message.role }) : null,
|
|
3745
|
+
/* @__PURE__ */ jsxs18("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
|
|
3746
|
+
/* @__PURE__ */ jsx28(ChatMessage, { message, texts: props.texts }),
|
|
3747
|
+
/* @__PURE__ */ jsx28("div", { className: cn("flex items-center gap-2", isUser && "justify-end"), children: isGenerating ? /* @__PURE__ */ jsx28(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
|
|
3748
|
+
/* @__PURE__ */ jsx28(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser }),
|
|
3749
|
+
!isUser ? /* @__PURE__ */ jsx28(ChatMessageActionCopy, { message, texts: props.texts }) : null
|
|
3216
3750
|
] }) })
|
|
3217
3751
|
] }),
|
|
3218
|
-
isUser ? /* @__PURE__ */
|
|
3752
|
+
isUser ? /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: message.role }) : null
|
|
3219
3753
|
] }, message.id);
|
|
3220
3754
|
}),
|
|
3221
|
-
props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */
|
|
3222
|
-
/* @__PURE__ */
|
|
3223
|
-
/* @__PURE__ */
|
|
3755
|
+
props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs18("div", { className: "flex justify-start gap-3", children: [
|
|
3756
|
+
/* @__PURE__ */ jsx28(ChatMessageAvatar, { role: "assistant" }),
|
|
3757
|
+
/* @__PURE__ */ jsx28("div", { className: "rounded-2xl border border-gray-200 bg-white px-4 py-3 text-sm text-gray-500 shadow-sm", children: props.texts.typingLabel })
|
|
3224
3758
|
] }) : null
|
|
3225
3759
|
] });
|
|
3226
3760
|
}
|
|
3227
|
-
|
|
3228
|
-
// src/components/chat/hooks/use-sticky-bottom-scroll.ts
|
|
3229
|
-
import { useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef7 } from "react";
|
|
3230
|
-
var DEFAULT_STICKY_THRESHOLD_PX = 10;
|
|
3231
|
-
function scrollElementToBottom(element) {
|
|
3232
|
-
element.scrollTop = element.scrollHeight;
|
|
3233
|
-
}
|
|
3234
|
-
function queueScrollToBottom(params) {
|
|
3235
|
-
const element = params.scrollRef.current;
|
|
3236
|
-
if (!element) {
|
|
3237
|
-
return;
|
|
3238
|
-
}
|
|
3239
|
-
if (params.scheduledScrollFrameRef.current !== null) {
|
|
3240
|
-
cancelAnimationFrame(params.scheduledScrollFrameRef.current);
|
|
3241
|
-
}
|
|
3242
|
-
params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
|
|
3243
|
-
params.scheduledScrollFrameRef.current = null;
|
|
3244
|
-
const currentElement = params.scrollRef.current;
|
|
3245
|
-
if (!currentElement) {
|
|
3246
|
-
return;
|
|
3247
|
-
}
|
|
3248
|
-
params.isProgrammaticScrollRef.current = true;
|
|
3249
|
-
scrollElementToBottom(currentElement);
|
|
3250
|
-
});
|
|
3251
|
-
}
|
|
3252
|
-
function useStickyBottomScroll(params) {
|
|
3253
|
-
const isStickyRef = useRef7(true);
|
|
3254
|
-
const isProgrammaticScrollRef = useRef7(false);
|
|
3255
|
-
const previousResetKeyRef = useRef7(null);
|
|
3256
|
-
const pendingInitialScrollRef = useRef7(false);
|
|
3257
|
-
const scheduledScrollFrameRef = useRef7(null);
|
|
3258
|
-
const onScroll = () => {
|
|
3259
|
-
if (isProgrammaticScrollRef.current) {
|
|
3260
|
-
isProgrammaticScrollRef.current = false;
|
|
3261
|
-
return;
|
|
3262
|
-
}
|
|
3263
|
-
const element = params.scrollRef.current;
|
|
3264
|
-
if (!element) {
|
|
3265
|
-
return;
|
|
3266
|
-
}
|
|
3267
|
-
const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
|
|
3268
|
-
isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
|
|
3269
|
-
};
|
|
3270
|
-
useEffect8(() => {
|
|
3271
|
-
if (previousResetKeyRef.current === params.resetKey) {
|
|
3272
|
-
return;
|
|
3273
|
-
}
|
|
3274
|
-
previousResetKeyRef.current = params.resetKey;
|
|
3275
|
-
isStickyRef.current = true;
|
|
3276
|
-
pendingInitialScrollRef.current = true;
|
|
3277
|
-
}, [params.resetKey]);
|
|
3278
|
-
useEffect8(() => {
|
|
3279
|
-
const scheduledScrollFrame = scheduledScrollFrameRef.current;
|
|
3280
|
-
return () => {
|
|
3281
|
-
if (scheduledScrollFrame !== null) {
|
|
3282
|
-
cancelAnimationFrame(scheduledScrollFrame);
|
|
3283
|
-
}
|
|
3284
|
-
};
|
|
3285
|
-
}, []);
|
|
3286
|
-
useLayoutEffect2(() => {
|
|
3287
|
-
if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
|
|
3288
|
-
return;
|
|
3289
|
-
}
|
|
3290
|
-
const element = params.scrollRef.current;
|
|
3291
|
-
if (!element) {
|
|
3292
|
-
return;
|
|
3293
|
-
}
|
|
3294
|
-
pendingInitialScrollRef.current = false;
|
|
3295
|
-
queueScrollToBottom({
|
|
3296
|
-
scrollRef: params.scrollRef,
|
|
3297
|
-
scheduledScrollFrameRef,
|
|
3298
|
-
isProgrammaticScrollRef
|
|
3299
|
-
});
|
|
3300
|
-
}, [params.hasContent, params.isLoading, params.scrollRef]);
|
|
3301
|
-
useLayoutEffect2(() => {
|
|
3302
|
-
if (!isStickyRef.current || !params.hasContent) {
|
|
3303
|
-
return;
|
|
3304
|
-
}
|
|
3305
|
-
const element = params.scrollRef.current;
|
|
3306
|
-
if (!element) {
|
|
3307
|
-
return;
|
|
3308
|
-
}
|
|
3309
|
-
queueScrollToBottom({
|
|
3310
|
-
scrollRef: params.scrollRef,
|
|
3311
|
-
scheduledScrollFrameRef,
|
|
3312
|
-
isProgrammaticScrollRef
|
|
3313
|
-
});
|
|
3314
|
-
}, [params.contentVersion, params.hasContent, params.scrollRef]);
|
|
3315
|
-
return { onScroll };
|
|
3316
|
-
}
|
|
3317
3761
|
export {
|
|
3318
3762
|
ChatInputBar,
|
|
3319
3763
|
ChatMessageList,
|