@nextclaw/agent-chat-ui 0.2.19 → 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.
Files changed (2) hide show
  1. package/dist/index.js +390 -252
  2. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -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 useEffect7, useRef as useRef6 } from "react";
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";
@@ -2833,18 +2833,230 @@ function ToolCardHeader({
2833
2833
  );
2834
2834
  }
2835
2835
 
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
+
2836
2926
  // src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
2837
- import { Fragment as Fragment4 } from "react";
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
2838
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";
2839
2933
  function formatLineNumber(value) {
2840
2934
  return typeof value === "number" ? String(value) : "";
2841
2935
  }
2842
2936
  function readVisibleLineNumber(line) {
2843
2937
  return formatLineNumber(line.newLineNumber ?? line.oldLineNumber);
2844
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";
2845
3044
  function isPreviewBlock(block) {
2846
3045
  return block.display === "preview";
2847
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
+ }
2848
3060
  function getCaptionTone(part) {
2849
3061
  if (/^\+\d+$/.test(part)) {
2850
3062
  return "text-emerald-700";
@@ -2859,54 +3071,39 @@ function renderCaption(caption) {
2859
3071
  if (parts.length === 0) {
2860
3072
  return null;
2861
3073
  }
2862
- return /* @__PURE__ */ jsx21("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__ */ jsxs13(Fragment4, { children: [
2863
- index > 0 ? /* @__PURE__ */ jsx21("span", { className: "text-stone-300", children: "\xB7" }) : null,
2864
- /* @__PURE__ */ jsx21("span", { className: cn(getCaptionTone(part)), children: part })
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 })
2865
3077
  ] }, `${part}-${index}`)) });
2866
3078
  }
2867
- function renderDiffGutterRow(line, index) {
2868
- const gutterTone = line.kind === "add" ? "border-r border-emerald-200 bg-emerald-100 text-emerald-700" : line.kind === "remove" ? "border-r border-rose-200 bg-rose-100 text-rose-700" : "border-r border-stone-200 bg-stone-100 text-stone-500";
2869
- return /* @__PURE__ */ jsx21(
2870
- "div",
2871
- {
2872
- className: "font-mono text-[11px] leading-relaxed",
2873
- children: /* @__PURE__ */ jsx21("span", { className: cn("flex h-full w-11 items-center justify-center px-1 py-1 tabular-nums", gutterTone), children: readVisibleLineNumber(line) })
2874
- },
2875
- `diff-gutter-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`
2876
- );
2877
- }
2878
- function renderPreviewGutterRow(line, index) {
2879
- return /* @__PURE__ */ jsx21(
2880
- "div",
2881
- {
2882
- className: "font-mono text-[11px] leading-relaxed",
2883
- children: /* @__PURE__ */ jsx21("span", { className: "flex h-full w-11 items-center justify-center border-r border-stone-200 bg-stone-100 px-1 py-1.5 tabular-nums text-stone-500", children: readVisibleLineNumber(line) })
2884
- },
2885
- `preview-gutter-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`
2886
- );
2887
- }
2888
- function renderDiffCodeRow(line, index) {
2889
- const rowTone = line.kind === "add" ? "border-l-2 border-emerald-300 bg-emerald-50 text-emerald-950" : line.kind === "remove" ? "border-l-2 border-rose-300 bg-rose-50 text-rose-950" : "border-l-2 border-transparent text-amber-950/80";
2890
- return /* @__PURE__ */ jsx21(
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(
2891
3096
  "div",
2892
3097
  {
3098
+ ref: scrollRef,
3099
+ onScroll,
3100
+ "data-file-scroll-kind": scrollKind,
2893
3101
  className: cn(
2894
- "min-w-full whitespace-pre px-3 py-1 font-mono text-[11px] leading-relaxed",
2895
- rowTone
3102
+ "overflow-y-auto overscroll-contain bg-white custom-scrollbar-amber",
3103
+ className
2896
3104
  ),
2897
- children: line.text || " "
2898
- },
2899
- `diff-code-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`
2900
- );
2901
- }
2902
- function renderPreviewCodeRow(line, index) {
2903
- return /* @__PURE__ */ jsx21(
2904
- "div",
2905
- {
2906
- className: "min-w-full whitespace-pre px-3 py-1.5 font-mono text-[11px] leading-relaxed text-amber-950/85",
2907
- children: line.text || " "
2908
- },
2909
- `preview-code-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`
3105
+ children
3106
+ }
2910
3107
  );
2911
3108
  }
2912
3109
  function FileOperationBlock({
@@ -2917,45 +3114,67 @@ function FileOperationBlock({
2917
3114
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
2918
3115
  const previewBlock = isPreviewBlock(block);
2919
3116
  const showMetaRow = showPathRow || Boolean(block.caption);
2920
- return /* @__PURE__ */ jsxs13("section", { className: cn("overflow-hidden bg-white", !isFirst && "border-t border-stone-200/80"), children: [
2921
- showMetaRow ? /* @__PURE__ */ jsxs13(
2922
- "div",
2923
- {
2924
- className: cn(
2925
- "flex items-center justify-between gap-4 border-b border-stone-200/80 px-4 text-stone-700",
2926
- showPathRow ? "py-3" : "py-2"
2927
- ),
2928
- children: [
2929
- /* @__PURE__ */ jsx21("div", { className: "min-w-0 flex-1", children: showPathRow ? /* @__PURE__ */ jsx21(TooltipProvider, { delayDuration: 250, children: /* @__PURE__ */ jsxs13(Tooltip, { children: [
2930
- /* @__PURE__ */ jsx21(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx21(
2931
- "div",
2932
- {
2933
- className: "truncate whitespace-nowrap font-mono text-[12px] font-medium text-stone-700",
2934
- title: block.path,
2935
- children: block.path
2936
- }
2937
- ) }),
2938
- /* @__PURE__ */ jsx21(TooltipContent, { side: "top", className: "max-w-[420px] text-xs font-mono break-all", children: block.path })
2939
- ] }) }) : null }),
2940
- block.caption ? renderCaption(block.caption) : null
2941
- ]
2942
- }
2943
- ) : null,
2944
- block.lines.length > 0 ? /* @__PURE__ */ jsx21("div", { className: "max-h-72 overflow-auto bg-white custom-scrollbar-amber", children: /* @__PURE__ */ jsxs13(
2945
- "div",
2946
- {
2947
- className: cn(
2948
- "grid min-w-0 max-w-full",
2949
- "grid-cols-[2.75rem_minmax(0,1fr)]"
2950
- ),
2951
- children: [
2952
- /* @__PURE__ */ jsx21("div", { className: "overflow-hidden", children: block.lines.map(previewBlock ? renderPreviewGutterRow : renderDiffGutterRow) }),
2953
- /* @__PURE__ */ jsx21("div", { className: "overflow-x-auto custom-scrollbar-amber bg-white", children: /* @__PURE__ */ jsx21("div", { className: "min-w-max", children: block.lines.map(previewBlock ? renderPreviewCodeRow : renderDiffCodeRow) }) })
2954
- ]
2955
- }
2956
- ) }) : block.rawText ? /* @__PURE__ */ jsx21("pre", { className: "max-h-72 min-w-full w-max overflow-auto bg-white px-4 py-3 font-mono text-[11px] leading-relaxed text-amber-950/80 whitespace-pre custom-scrollbar-amber", children: block.rawText }) : null,
2957
- block.truncated && !previewBlock ? /* @__PURE__ */ jsx21("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
2958
- ] });
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
+ );
2959
3178
  }
2960
3179
  function ToolCardFileOperationContent({
2961
3180
  card,
@@ -2966,9 +3185,9 @@ function ToolCardFileOperationContent({
2966
3185
  if (blocks.length === 0 && !output) {
2967
3186
  return null;
2968
3187
  }
2969
- return /* @__PURE__ */ jsxs13("div", { className: cn("overflow-hidden bg-white", className), children: [
3188
+ return /* @__PURE__ */ jsxs14("div", { className: cn("overflow-hidden bg-white", className), children: [
2970
3189
  blocks.map((block, index) => {
2971
- return /* @__PURE__ */ jsx21(
3190
+ return /* @__PURE__ */ jsx22(
2972
3191
  FileOperationBlock,
2973
3192
  {
2974
3193
  block,
@@ -2978,15 +3197,24 @@ function ToolCardFileOperationContent({
2978
3197
  block.key
2979
3198
  );
2980
3199
  }),
2981
- output ? /* @__PURE__ */ jsx21("pre", { className: cn(
2982
- "max-h-56 min-w-full w-max overflow-auto bg-white px-4 py-3 font-mono text-[11px] leading-relaxed text-amber-950/80 whitespace-pre custom-scrollbar-amber",
2983
- blocks.length > 0 && "border-t border-stone-200/80"
2984
- ), children: output }) : null
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
2985
3213
  ] });
2986
3214
  }
2987
3215
 
2988
3216
  // src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
2989
- import { Fragment as Fragment5, jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
3217
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2990
3218
  var TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
2991
3219
  var ANSI_ESCAPE_PREFIX = String.fromCharCode(27);
2992
3220
  var ANSI_ESCAPE_PATTERN = new RegExp(`${ANSI_ESCAPE_PREFIX}\\[[0-?]*[ -/]*[@-~]`, "g");
@@ -3060,17 +3288,17 @@ function useToolCardExpandedState({
3060
3288
  }) {
3061
3289
  const [expanded, setExpanded] = useState7(false);
3062
3290
  const [hasUserToggled, setHasUserToggled] = useState7(false);
3063
- const expandTimerRef = useRef6(null);
3064
- const prevRunningRef = useRef6(isRunning);
3065
- const isFirstRenderRef = useRef6(true);
3066
- useEffect7(() => {
3291
+ const expandTimerRef = useRef8(null);
3292
+ const prevRunningRef = useRef8(isRunning);
3293
+ const isFirstRenderRef = useRef8(true);
3294
+ useEffect8(() => {
3067
3295
  return () => {
3068
3296
  if (expandTimerRef.current !== null) {
3069
3297
  window.clearTimeout(expandTimerRef.current);
3070
3298
  }
3071
3299
  };
3072
3300
  }, []);
3073
- useEffect7(() => {
3301
+ useEffect8(() => {
3074
3302
  if (expandOnError && statusTone === "error" && canExpand && !hasUserToggled) {
3075
3303
  if (expandTimerRef.current !== null) {
3076
3304
  window.clearTimeout(expandTimerRef.current);
@@ -3138,7 +3366,7 @@ function GenericToolSection({
3138
3366
  }
3139
3367
  };
3140
3368
  const style = tones[tone];
3141
- return /* @__PURE__ */ jsxs14(
3369
+ return /* @__PURE__ */ jsxs15(
3142
3370
  "section",
3143
3371
  {
3144
3372
  className: cn(
@@ -3146,7 +3374,7 @@ function GenericToolSection({
3146
3374
  style.shell
3147
3375
  ),
3148
3376
  children: [
3149
- /* @__PURE__ */ jsxs14(
3377
+ /* @__PURE__ */ jsxs15(
3150
3378
  "div",
3151
3379
  {
3152
3380
  className: cn(
@@ -3154,12 +3382,12 @@ function GenericToolSection({
3154
3382
  style.header
3155
3383
  ),
3156
3384
  children: [
3157
- /* @__PURE__ */ jsx22("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }),
3158
- /* @__PURE__ */ jsx22("span", { children: label })
3385
+ /* @__PURE__ */ jsx23("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }),
3386
+ /* @__PURE__ */ jsx23("span", { children: label })
3159
3387
  ]
3160
3388
  }
3161
3389
  ),
3162
- /* @__PURE__ */ jsx22("div", { className: "w-full overflow-hidden", children: /* @__PURE__ */ jsx22(
3390
+ /* @__PURE__ */ jsx23("div", { className: "w-full overflow-hidden", children: /* @__PURE__ */ jsx23(
3163
3391
  "pre",
3164
3392
  {
3165
3393
  className: cn(
@@ -3184,8 +3412,8 @@ function TerminalExecutionView({ card }) {
3184
3412
  statusTone: card.statusTone
3185
3413
  });
3186
3414
  const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
3187
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3188
- /* @__PURE__ */ jsx22(
3415
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3416
+ /* @__PURE__ */ jsx23(
3189
3417
  ToolCardHeader,
3190
3418
  {
3191
3419
  card,
@@ -3195,17 +3423,17 @@ function TerminalExecutionView({ card }) {
3195
3423
  onToggle
3196
3424
  }
3197
3425
  ),
3198
- expanded && /* @__PURE__ */ jsxs14(Fragment5, { children: [
3199
- /* @__PURE__ */ jsx22("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__ */ jsxs14("div", { className: "flex items-start gap-2 leading-relaxed", children: [
3200
- /* @__PURE__ */ jsx22("span", { className: "text-amber-500/50 font-medium shrink-0 select-none mt-[1px]", children: "$" }),
3201
- /* @__PURE__ */ jsx22("div", { className: "flex-1 min-w-0", children: commandPart ? /* @__PURE__ */ jsxs14("div", { className: "text-amber-950/80 break-words whitespace-pre-wrap tracking-tight font-medium inline-block", children: [
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: [
3202
3430
  commandPart,
3203
- isRunning && !output && /* @__PURE__ */ jsx22("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3204
- ] }) : /* @__PURE__ */ jsx22("div", { className: "h-3 w-32 bg-amber-200/30 rounded animate-pulse mt-2" }) })
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" }) })
3205
3433
  ] }) }),
3206
- output && /* @__PURE__ */ jsx22(ToolCardContent, { children: /* @__PURE__ */ jsxs14("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: [
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: [
3207
3435
  output,
3208
- isRunning && /* @__PURE__ */ jsx22("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3436
+ isRunning && /* @__PURE__ */ jsx23("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3209
3437
  ] }) })
3210
3438
  ] })
3211
3439
  ] });
@@ -3232,8 +3460,8 @@ function FileOperationView({ card }) {
3232
3460
  statusTone: card.statusTone
3233
3461
  });
3234
3462
  const isEdit = card.toolName === "edit_file" || card.toolName === "write_file" || card.toolName === "apply_patch" || card.toolName === "file_change";
3235
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3236
- /* @__PURE__ */ jsx22(
3463
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3464
+ /* @__PURE__ */ jsx23(
3237
3465
  ToolCardHeader,
3238
3466
  {
3239
3467
  card,
@@ -3244,7 +3472,7 @@ function FileOperationView({ card }) {
3244
3472
  onToggle
3245
3473
  }
3246
3474
  ),
3247
- expanded && hasContent && /* @__PURE__ */ jsx22(ToolCardContent, { className: "border-t border-amber-200/20 bg-transparent px-0 pt-0 pb-0", children: /* @__PURE__ */ jsx22(ToolCardFileOperationContent, { card }) })
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 }) })
3248
3476
  ] });
3249
3477
  }
3250
3478
  function SearchSnippetView({ card }) {
@@ -3255,8 +3483,8 @@ function SearchSnippetView({ card }) {
3255
3483
  isRunning,
3256
3484
  statusTone: card.statusTone
3257
3485
  });
3258
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3259
- /* @__PURE__ */ jsx22(
3486
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3487
+ /* @__PURE__ */ jsx23(
3260
3488
  ToolCardHeader,
3261
3489
  {
3262
3490
  card,
@@ -3266,7 +3494,7 @@ function SearchSnippetView({ card }) {
3266
3494
  onToggle
3267
3495
  }
3268
3496
  ),
3269
- expanded && output && /* @__PURE__ */ jsx22(ToolCardContent, { children: /* @__PURE__ */ jsx22("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 }) })
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 }) })
3270
3498
  ] });
3271
3499
  }
3272
3500
  function GenericToolCard({ card }) {
@@ -3283,8 +3511,8 @@ function GenericToolCard({ card }) {
3283
3511
  isRunning,
3284
3512
  statusTone: card.statusTone
3285
3513
  });
3286
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3287
- /* @__PURE__ */ jsx22(
3514
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3515
+ /* @__PURE__ */ jsx23(
3288
3516
  ToolCardHeader,
3289
3517
  {
3290
3518
  card,
@@ -3294,10 +3522,10 @@ function GenericToolCard({ card }) {
3294
3522
  onToggle
3295
3523
  }
3296
3524
  ),
3297
- expanded && hasContent && /* @__PURE__ */ jsxs14(ToolCardContent, { className: "bg-transparent px-2.5 py-2.5", children: [
3298
- hasInputSection && /* @__PURE__ */ jsx22(GenericToolSection, { label: inputLabel, tone: "input", children: input }),
3299
- hasInputSection && hasOutputSection && /* @__PURE__ */ jsx22("div", { className: "h-2" }),
3300
- hasOutputSection && /* @__PURE__ */ jsx22(
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(
3301
3529
  GenericToolSection,
3302
3530
  {
3303
3531
  label: outputLabel,
@@ -3310,7 +3538,7 @@ function GenericToolCard({ card }) {
3310
3538
  }
3311
3539
 
3312
3540
  // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
3313
- import { jsx as jsx23 } from "react/jsx-runtime";
3541
+ import { jsx as jsx24 } from "react/jsx-runtime";
3314
3542
  function isTerminalTool(name) {
3315
3543
  const lowered = name.toLowerCase();
3316
3544
  return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
@@ -3325,48 +3553,48 @@ function isSearchTool(name) {
3325
3553
  }
3326
3554
  function ChatToolCard({ card }) {
3327
3555
  if (isTerminalTool(card.toolName)) {
3328
- return /* @__PURE__ */ jsx23(TerminalExecutionView, { card });
3556
+ return /* @__PURE__ */ jsx24(TerminalExecutionView, { card });
3329
3557
  }
3330
3558
  if (isFileEditTool(card.toolName)) {
3331
- return /* @__PURE__ */ jsx23(FileOperationView, { card });
3559
+ return /* @__PURE__ */ jsx24(FileOperationView, { card });
3332
3560
  }
3333
3561
  if (isSearchTool(card.toolName)) {
3334
- return /* @__PURE__ */ jsx23(SearchSnippetView, { card });
3562
+ return /* @__PURE__ */ jsx24(SearchSnippetView, { card });
3335
3563
  }
3336
- return /* @__PURE__ */ jsx23(GenericToolCard, { card });
3564
+ return /* @__PURE__ */ jsx24(GenericToolCard, { card });
3337
3565
  }
3338
3566
 
3339
3567
  // src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
3340
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
3568
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
3341
3569
  function ChatUnknownPart(props) {
3342
- return /* @__PURE__ */ jsxs15("div", { className: "rounded-lg border border-gray-200 bg-gray-50 px-2.5 py-2 text-xs text-gray-600", children: [
3343
- /* @__PURE__ */ jsxs15("div", { className: "font-semibold text-gray-700", children: [
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: [
3344
3572
  props.label,
3345
3573
  ": ",
3346
3574
  props.rawType
3347
3575
  ] }),
3348
- props.text ? /* @__PURE__ */ jsx24("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
3576
+ props.text ? /* @__PURE__ */ jsx25("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
3349
3577
  ] });
3350
3578
  }
3351
3579
 
3352
3580
  // src/components/chat/ui/chat-message-list/chat-message.tsx
3353
- import { jsx as jsx25 } from "react/jsx-runtime";
3581
+ import { jsx as jsx26 } from "react/jsx-runtime";
3354
3582
  var ChatMessage = memo(function ChatMessage2(props) {
3355
3583
  const { message, texts } = props;
3356
3584
  const { role } = message;
3357
3585
  const isUser = role === "user";
3358
3586
  const isMessageInProgress = message.status === "pending" || message.status === "streaming";
3359
- return /* @__PURE__ */ jsx25(
3587
+ return /* @__PURE__ */ jsx26(
3360
3588
  "div",
3361
3589
  {
3362
3590
  className: cn(
3363
3591
  "inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm",
3364
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"
3365
3593
  ),
3366
- children: /* @__PURE__ */ jsx25("div", { className: "space-y-2", children: message.parts.map((part, index) => {
3594
+ children: /* @__PURE__ */ jsx26("div", { className: "space-y-2", children: message.parts.map((part, index) => {
3367
3595
  const { type } = part;
3368
3596
  if (type === "markdown") {
3369
- return /* @__PURE__ */ jsx25(
3597
+ return /* @__PURE__ */ jsx26(
3370
3598
  ChatMessageMarkdown,
3371
3599
  {
3372
3600
  text: part.text,
@@ -3377,7 +3605,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3377
3605
  );
3378
3606
  }
3379
3607
  if (type === "inline-content") {
3380
- return /* @__PURE__ */ jsx25(
3608
+ return /* @__PURE__ */ jsx26(
3381
3609
  ChatMessageInlineContent,
3382
3610
  {
3383
3611
  segments: part.segments,
@@ -3388,7 +3616,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3388
3616
  );
3389
3617
  }
3390
3618
  if (type === "reasoning") {
3391
- return /* @__PURE__ */ jsx25(
3619
+ return /* @__PURE__ */ jsx26(
3392
3620
  ChatReasoningBlock,
3393
3621
  {
3394
3622
  label: part.label,
@@ -3400,10 +3628,10 @@ var ChatMessage = memo(function ChatMessage2(props) {
3400
3628
  );
3401
3629
  }
3402
3630
  if (type === "tool-card") {
3403
- return /* @__PURE__ */ jsx25("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx25(ChatToolCard, { card: part.card }) }, `tool-${index}`);
3631
+ return /* @__PURE__ */ jsx26("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx26(ChatToolCard, { card: part.card }) }, `tool-${index}`);
3404
3632
  }
3405
3633
  if (type === "file") {
3406
- return /* @__PURE__ */ jsx25(
3634
+ return /* @__PURE__ */ jsx26(
3407
3635
  ChatMessageFile,
3408
3636
  {
3409
3637
  file: part.file,
@@ -3413,7 +3641,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3413
3641
  );
3414
3642
  }
3415
3643
  if (type === "unknown") {
3416
- return /* @__PURE__ */ jsx25(
3644
+ return /* @__PURE__ */ jsx26(
3417
3645
  ChatUnknownPart,
3418
3646
  {
3419
3647
  label: part.label,
@@ -3430,9 +3658,9 @@ var ChatMessage = memo(function ChatMessage2(props) {
3430
3658
  });
3431
3659
 
3432
3660
  // src/components/chat/ui/chat-message-list/chat-message-meta.tsx
3433
- import { jsxs as jsxs16 } from "react/jsx-runtime";
3661
+ import { jsxs as jsxs17 } from "react/jsx-runtime";
3434
3662
  function ChatMessageMeta(props) {
3435
- return /* @__PURE__ */ jsxs16(
3663
+ return /* @__PURE__ */ jsxs17(
3436
3664
  "div",
3437
3665
  {
3438
3666
  className: cn(
@@ -3451,7 +3679,7 @@ function ChatMessageMeta(props) {
3451
3679
  // src/components/chat/ui/chat-message-list/chat-message-action-copy.tsx
3452
3680
  import { useMemo as useMemo6 } from "react";
3453
3681
  import { Check as Check5, Copy as Copy2 } from "lucide-react";
3454
- import { jsx as jsx26 } from "react/jsx-runtime";
3682
+ import { jsx as jsx27 } from "react/jsx-runtime";
3455
3683
  function ChatMessageActionCopy({
3456
3684
  message,
3457
3685
  texts
@@ -3465,7 +3693,7 @@ function ChatMessageActionCopy({
3465
3693
  }, [message.parts]);
3466
3694
  const { copied, copy } = useCopyFeedback({ text: messageText });
3467
3695
  if (!messageText) return null;
3468
- return /* @__PURE__ */ jsx26(
3696
+ return /* @__PURE__ */ jsx27(
3469
3697
  "button",
3470
3698
  {
3471
3699
  type: "button",
@@ -3473,13 +3701,13 @@ function ChatMessageActionCopy({
3473
3701
  className: "text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-md hover:bg-gray-100 flex items-center justify-center",
3474
3702
  "aria-label": copied ? texts.copiedMessageLabel : texts.copyMessageLabel,
3475
3703
  title: copied ? texts.copiedMessageLabel : texts.copyMessageLabel,
3476
- children: copied ? /* @__PURE__ */ jsx26(Check5, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx26(Copy2, { className: "h-3.5 w-3.5" })
3704
+ children: copied ? /* @__PURE__ */ jsx27(Check5, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx27(Copy2, { className: "h-3.5 w-3.5" })
3477
3705
  }
3478
3706
  );
3479
3707
  }
3480
3708
 
3481
3709
  // src/components/chat/ui/chat-message-list/chat-message-list.tsx
3482
- import { Fragment as Fragment6, jsx as jsx27, jsxs as jsxs17 } from "react/jsx-runtime";
3710
+ import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
3483
3711
  var INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
3484
3712
  function hasRenderableText(value) {
3485
3713
  const trimmed = value.trim();
@@ -3497,10 +3725,10 @@ function hasRenderableMessageContent(message) {
3497
3725
  });
3498
3726
  }
3499
3727
  function ChatMessageTypingFooter() {
3500
- return /* @__PURE__ */ jsx27("div", { className: "flex items-center gap-2 px-1 py-0.5 text-[11px] text-gray-400", children: /* @__PURE__ */ jsxs17("div", { className: "flex space-x-1 items-center h-full", children: [
3501
- /* @__PURE__ */ jsx27("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse" }),
3502
- /* @__PURE__ */ jsx27("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:200ms]" }),
3503
- /* @__PURE__ */ jsx27("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:400ms]" })
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]" })
3504
3732
  ] }) });
3505
3733
  }
3506
3734
  function ChatMessageList(props) {
@@ -3508,118 +3736,28 @@ function ChatMessageList(props) {
3508
3736
  const hasRenderableAssistantDraft = visibleMessages.some(
3509
3737
  (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending")
3510
3738
  );
3511
- return /* @__PURE__ */ jsxs17("div", { className: cn("space-y-5", props.className), children: [
3739
+ return /* @__PURE__ */ jsxs18("div", { className: cn("space-y-5", props.className), children: [
3512
3740
  visibleMessages.map((message) => {
3513
3741
  const isUser = message.role === "user";
3514
3742
  const isGenerating = !isUser && (message.status === "streaming" || message.status === "pending");
3515
- return /* @__PURE__ */ jsxs17("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
3516
- !isUser ? /* @__PURE__ */ jsx27(ChatMessageAvatar, { role: message.role }) : null,
3517
- /* @__PURE__ */ jsxs17("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
3518
- /* @__PURE__ */ jsx27(ChatMessage, { message, texts: props.texts }),
3519
- /* @__PURE__ */ jsx27("div", { className: cn("flex items-center gap-2", isUser && "justify-end"), children: isGenerating ? /* @__PURE__ */ jsx27(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs17(Fragment6, { children: [
3520
- /* @__PURE__ */ jsx27(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser }),
3521
- !isUser ? /* @__PURE__ */ jsx27(ChatMessageActionCopy, { message, texts: props.texts }) : null
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
3522
3750
  ] }) })
3523
3751
  ] }),
3524
- isUser ? /* @__PURE__ */ jsx27(ChatMessageAvatar, { role: message.role }) : null
3752
+ isUser ? /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: message.role }) : null
3525
3753
  ] }, message.id);
3526
3754
  }),
3527
- props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs17("div", { className: "flex justify-start gap-3", children: [
3528
- /* @__PURE__ */ jsx27(ChatMessageAvatar, { role: "assistant" }),
3529
- /* @__PURE__ */ jsx27("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 })
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 })
3530
3758
  ] }) : null
3531
3759
  ] });
3532
3760
  }
3533
-
3534
- // src/components/chat/hooks/use-sticky-bottom-scroll.ts
3535
- import { useEffect as useEffect8, useLayoutEffect as useLayoutEffect2, useRef as useRef7 } from "react";
3536
- var DEFAULT_STICKY_THRESHOLD_PX = 10;
3537
- function scrollElementToBottom(element) {
3538
- element.scrollTop = element.scrollHeight;
3539
- }
3540
- function queueScrollToBottom(params) {
3541
- const element = params.scrollRef.current;
3542
- if (!element) {
3543
- return;
3544
- }
3545
- if (params.scheduledScrollFrameRef.current !== null) {
3546
- cancelAnimationFrame(params.scheduledScrollFrameRef.current);
3547
- }
3548
- params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
3549
- params.scheduledScrollFrameRef.current = null;
3550
- const currentElement = params.scrollRef.current;
3551
- if (!currentElement) {
3552
- return;
3553
- }
3554
- params.isProgrammaticScrollRef.current = true;
3555
- scrollElementToBottom(currentElement);
3556
- });
3557
- }
3558
- function useStickyBottomScroll(params) {
3559
- const isStickyRef = useRef7(true);
3560
- const isProgrammaticScrollRef = useRef7(false);
3561
- const previousResetKeyRef = useRef7(null);
3562
- const pendingInitialScrollRef = useRef7(false);
3563
- const scheduledScrollFrameRef = useRef7(null);
3564
- const onScroll = () => {
3565
- if (isProgrammaticScrollRef.current) {
3566
- isProgrammaticScrollRef.current = false;
3567
- return;
3568
- }
3569
- const element = params.scrollRef.current;
3570
- if (!element) {
3571
- return;
3572
- }
3573
- const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
3574
- isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
3575
- };
3576
- useEffect8(() => {
3577
- if (previousResetKeyRef.current === params.resetKey) {
3578
- return;
3579
- }
3580
- previousResetKeyRef.current = params.resetKey;
3581
- isStickyRef.current = true;
3582
- pendingInitialScrollRef.current = true;
3583
- }, [params.resetKey]);
3584
- useEffect8(() => {
3585
- const scheduledScrollFrame = scheduledScrollFrameRef.current;
3586
- return () => {
3587
- if (scheduledScrollFrame !== null) {
3588
- cancelAnimationFrame(scheduledScrollFrame);
3589
- }
3590
- };
3591
- }, []);
3592
- useLayoutEffect2(() => {
3593
- if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
3594
- return;
3595
- }
3596
- const element = params.scrollRef.current;
3597
- if (!element) {
3598
- return;
3599
- }
3600
- pendingInitialScrollRef.current = false;
3601
- queueScrollToBottom({
3602
- scrollRef: params.scrollRef,
3603
- scheduledScrollFrameRef,
3604
- isProgrammaticScrollRef
3605
- });
3606
- }, [params.hasContent, params.isLoading, params.scrollRef]);
3607
- useLayoutEffect2(() => {
3608
- if (!isStickyRef.current || !params.hasContent) {
3609
- return;
3610
- }
3611
- const element = params.scrollRef.current;
3612
- if (!element) {
3613
- return;
3614
- }
3615
- queueScrollToBottom({
3616
- scrollRef: params.scrollRef,
3617
- scheduledScrollFrameRef,
3618
- isProgrammaticScrollRef
3619
- });
3620
- }, [params.contentVersion, params.hasContent, params.scrollRef]);
3621
- return { onScroll };
3622
- }
3623
3761
  export {
3624
3762
  ChatInputBar,
3625
3763
  ChatMessageList,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.2.19",
3
+ "version": "0.2.20",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",