@nextclaw/agent-chat-ui 0.2.19 → 0.2.21

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 CHANGED
@@ -166,6 +166,13 @@ type ChatFileOperationBlockViewModel = {
166
166
  rawText?: string;
167
167
  truncated?: boolean;
168
168
  };
169
+ type ChatToolActionViewModel = {
170
+ kind: "open-session";
171
+ sessionId: string;
172
+ sessionKind: "child" | "session";
173
+ label?: string;
174
+ parentSessionId?: string;
175
+ };
169
176
  type ChatToolPartViewModel = {
170
177
  kind: "call" | "result";
171
178
  toolName: string;
@@ -180,6 +187,7 @@ type ChatToolPartViewModel = {
180
187
  titleLabel: string;
181
188
  outputLabel: string;
182
189
  emptyLabel: string;
190
+ action?: ChatToolActionViewModel;
183
191
  fileOperation?: {
184
192
  blocks: ChatFileOperationBlockViewModel[];
185
193
  };
@@ -246,6 +254,7 @@ type ChatMessageListProps = {
246
254
  hasAssistantDraft: boolean;
247
255
  texts: ChatMessageTexts;
248
256
  className?: string;
257
+ onToolAction?: (action: ChatToolActionViewModel) => void;
249
258
  };
250
259
 
251
260
  type ChatInputBarHandle = {
@@ -320,4 +329,4 @@ declare function resolveChatComposerSlashTrigger(nodes: ChatComposerNode[], sele
320
329
  end: number;
321
330
  } | null;
322
331
 
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 };
332
+ 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 ChatToolActionViewModel, 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
@@ -290,13 +290,20 @@ function ChatSlashMenu(props) {
290
290
  ] })
291
291
  }
292
292
  ),
293
- /* @__PURE__ */ jsx5("div", { className: "max-w-[320px] p-3.5", children: activeItem ? /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
293
+ /* @__PURE__ */ jsx5("div", { className: "min-w-0 p-2.5", children: activeItem ? /* @__PURE__ */ jsxs2("div", { className: "space-y-3", children: [
294
294
  /* @__PURE__ */ jsxs2("div", { className: "flex items-center gap-2", children: [
295
295
  /* @__PURE__ */ jsx5("span", { className: "inline-flex rounded-full bg-primary/10 px-2 py-0.5 text-[11px] font-semibold text-primary", children: activeItem.subtitle }),
296
296
  /* @__PURE__ */ jsx5("span", { className: "text-sm font-semibold text-gray-900", children: activeItem.title })
297
297
  ] }),
298
298
  /* @__PURE__ */ jsx5("p", { className: "text-xs leading-5 text-gray-600", children: activeItem.description }),
299
- /* @__PURE__ */ jsx5("div", { className: "space-y-1", children: activeItem.detailLines.map((line) => /* @__PURE__ */ jsx5("div", { className: "rounded-md bg-gray-50 px-2 py-1 text-[11px] text-gray-600", children: line }, line)) }),
299
+ /* @__PURE__ */ jsx5("div", { className: "space-y-1", children: activeItem.detailLines.map((line) => /* @__PURE__ */ jsx5(
300
+ "div",
301
+ {
302
+ className: "min-w-0 break-all rounded-md bg-gray-50 px-2 py-1 text-[11px] leading-5 text-gray-600",
303
+ children: line
304
+ },
305
+ line
306
+ )) }),
300
307
  /* @__PURE__ */ jsx5("div", { className: "pt-1 text-[11px] text-gray-500", children: texts.slashSkillHintLabel })
301
308
  ] }) : /* @__PURE__ */ jsx5("div", { className: "text-xs text-gray-500", children: texts.slashHintLabel }) })
302
309
  ] })
@@ -2752,7 +2759,7 @@ function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
2752
2759
 
2753
2760
  // src/components/chat/ui/chat-message-list/tool-card/tool-card-views.tsx
2754
2761
  import { Terminal, FileText, Code2, Search as Search2, Globe } from "lucide-react";
2755
- import { useState as useState7, useEffect as useEffect7, useRef as useRef6 } from "react";
2762
+ import { useState as useState7, useEffect as useEffect8, useRef as useRef8 } from "react";
2756
2763
 
2757
2764
  // src/components/chat/ui/chat-message-list/tool-card/tool-card-root.tsx
2758
2765
  import { jsx as jsx18 } from "react/jsx-runtime";
@@ -2833,18 +2840,230 @@ function ToolCardHeader({
2833
2840
  );
2834
2841
  }
2835
2842
 
2843
+ // src/components/chat/hooks/use-sticky-bottom-scroll.ts
2844
+ import { useEffect as useEffect7, useLayoutEffect as useLayoutEffect2, useRef as useRef6 } from "react";
2845
+ var DEFAULT_STICKY_THRESHOLD_PX = 10;
2846
+ function scrollElementToBottom(element) {
2847
+ element.scrollTop = element.scrollHeight;
2848
+ }
2849
+ function queueScrollToBottom(params) {
2850
+ const element = params.scrollRef.current;
2851
+ if (!element) {
2852
+ return;
2853
+ }
2854
+ if (params.scheduledScrollFrameRef.current !== null) {
2855
+ cancelAnimationFrame(params.scheduledScrollFrameRef.current);
2856
+ }
2857
+ params.scheduledScrollFrameRef.current = requestAnimationFrame(() => {
2858
+ params.scheduledScrollFrameRef.current = null;
2859
+ const currentElement = params.scrollRef.current;
2860
+ if (!currentElement) {
2861
+ return;
2862
+ }
2863
+ params.isProgrammaticScrollRef.current = true;
2864
+ scrollElementToBottom(currentElement);
2865
+ });
2866
+ }
2867
+ function useStickyBottomScroll(params) {
2868
+ const isStickyRef = useRef6(true);
2869
+ const isProgrammaticScrollRef = useRef6(false);
2870
+ const previousResetKeyRef = useRef6(null);
2871
+ const pendingInitialScrollRef = useRef6(false);
2872
+ const scheduledScrollFrameRef = useRef6(null);
2873
+ const onScroll = () => {
2874
+ if (isProgrammaticScrollRef.current) {
2875
+ isProgrammaticScrollRef.current = false;
2876
+ return;
2877
+ }
2878
+ const element = params.scrollRef.current;
2879
+ if (!element) {
2880
+ return;
2881
+ }
2882
+ const distanceFromBottom = element.scrollHeight - element.scrollTop - element.clientHeight;
2883
+ isStickyRef.current = distanceFromBottom <= (params.stickyThresholdPx ?? DEFAULT_STICKY_THRESHOLD_PX);
2884
+ };
2885
+ useEffect7(() => {
2886
+ if (previousResetKeyRef.current === params.resetKey) {
2887
+ return;
2888
+ }
2889
+ previousResetKeyRef.current = params.resetKey;
2890
+ isStickyRef.current = true;
2891
+ pendingInitialScrollRef.current = true;
2892
+ }, [params.resetKey]);
2893
+ useEffect7(() => {
2894
+ const scheduledScrollFrame = scheduledScrollFrameRef.current;
2895
+ return () => {
2896
+ if (scheduledScrollFrame !== null) {
2897
+ cancelAnimationFrame(scheduledScrollFrame);
2898
+ }
2899
+ };
2900
+ }, []);
2901
+ useLayoutEffect2(() => {
2902
+ if (!pendingInitialScrollRef.current || params.isLoading || !params.hasContent) {
2903
+ return;
2904
+ }
2905
+ const element = params.scrollRef.current;
2906
+ if (!element) {
2907
+ return;
2908
+ }
2909
+ pendingInitialScrollRef.current = false;
2910
+ queueScrollToBottom({
2911
+ scrollRef: params.scrollRef,
2912
+ scheduledScrollFrameRef,
2913
+ isProgrammaticScrollRef
2914
+ });
2915
+ }, [params.hasContent, params.isLoading, params.scrollRef]);
2916
+ useLayoutEffect2(() => {
2917
+ if (!isStickyRef.current || !params.hasContent) {
2918
+ return;
2919
+ }
2920
+ const element = params.scrollRef.current;
2921
+ if (!element) {
2922
+ return;
2923
+ }
2924
+ queueScrollToBottom({
2925
+ scrollRef: params.scrollRef,
2926
+ scheduledScrollFrameRef,
2927
+ isProgrammaticScrollRef
2928
+ });
2929
+ }, [params.contentVersion, params.hasContent, params.scrollRef]);
2930
+ return { onScroll };
2931
+ }
2932
+
2836
2933
  // src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
2837
- import { Fragment as Fragment4 } from "react";
2934
+ import { Fragment as Fragment4, useRef as useRef7 } from "react";
2935
+
2936
+ // src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
2838
2937
  import { jsx as jsx21, jsxs as jsxs13 } from "react/jsx-runtime";
2938
+ var FILE_ROW_CLASS_NAME = "h-5 font-mono text-[11px] leading-5";
2939
+ 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
2940
  function formatLineNumber(value) {
2840
2941
  return typeof value === "number" ? String(value) : "";
2841
2942
  }
2842
2943
  function readVisibleLineNumber(line) {
2843
2944
  return formatLineNumber(line.newLineNumber ?? line.oldLineNumber);
2844
2945
  }
2946
+ function readLineKey(prefix, line, index) {
2947
+ return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
2948
+ }
2949
+ function hasVisibleLineNumber(line) {
2950
+ return typeof line.newLineNumber === "number" || typeof line.oldLineNumber === "number";
2951
+ }
2952
+ function readHasBlockLineNumbers(block) {
2953
+ return block.lines.some(hasVisibleLineNumber);
2954
+ }
2955
+ function readLineNumberColumnWidth(block) {
2956
+ const maxDigits = block.lines.reduce((currentMax, line) => {
2957
+ if (!hasVisibleLineNumber(line)) {
2958
+ return currentMax;
2959
+ }
2960
+ return Math.max(currentMax, readVisibleLineNumber(line).length);
2961
+ }, 0);
2962
+ const width = Math.max(6.5, Math.min(8, maxDigits + 3.5));
2963
+ return `${width}ch`;
2964
+ }
2965
+ function buildRowTemplateColumns(params) {
2966
+ if (!params.showLineNumbers) {
2967
+ return void 0;
2968
+ }
2969
+ return {
2970
+ gridTemplateColumns: `${params.lineNumberColumnWidth} minmax(0, 1fr)`
2971
+ };
2972
+ }
2973
+ function getLineNumberTone(line) {
2974
+ if (line.kind === "remove") {
2975
+ return "border-r border-rose-200 bg-rose-50 text-rose-700";
2976
+ }
2977
+ if (line.kind === "add") {
2978
+ return "border-r border-emerald-200 bg-emerald-50 text-emerald-700";
2979
+ }
2980
+ return "border-r border-stone-200 bg-stone-100 text-stone-500";
2981
+ }
2982
+ function getCodeRowTone(line) {
2983
+ if (line.kind === "remove") {
2984
+ return "bg-rose-50 text-rose-950";
2985
+ }
2986
+ if (line.kind === "add") {
2987
+ return "bg-emerald-50 text-emerald-950";
2988
+ }
2989
+ return "bg-white text-amber-950/80";
2990
+ }
2991
+ function FileOperationLineRow({
2992
+ line,
2993
+ showLineNumbers,
2994
+ lineNumberColumnWidth
2995
+ }) {
2996
+ return /* @__PURE__ */ jsxs13(
2997
+ "div",
2998
+ {
2999
+ "data-file-line-row": "true",
3000
+ className: cn("grid w-max min-w-full", FILE_ROW_CLASS_NAME),
3001
+ style: buildRowTemplateColumns({
3002
+ showLineNumbers,
3003
+ lineNumberColumnWidth
3004
+ }),
3005
+ children: [
3006
+ showLineNumbers ? /* @__PURE__ */ jsx21(
3007
+ "span",
3008
+ {
3009
+ "data-file-line-number-cell": "true",
3010
+ style: { width: lineNumberColumnWidth },
3011
+ className: cn(
3012
+ FILE_GUTTER_NUMBER_CELL_CLASS_NAME,
3013
+ getLineNumberTone(line)
3014
+ ),
3015
+ children: readVisibleLineNumber(line)
3016
+ }
3017
+ ) : null,
3018
+ /* @__PURE__ */ jsx21(
3019
+ "span",
3020
+ {
3021
+ "data-file-code-row": "true",
3022
+ className: cn(
3023
+ "block min-w-full whitespace-pre px-2.5",
3024
+ getCodeRowTone(line)
3025
+ ),
3026
+ children: line.text || " "
3027
+ }
3028
+ )
3029
+ ]
3030
+ }
3031
+ );
3032
+ }
3033
+ function FileOperationLinesGrid({
3034
+ block
3035
+ }) {
3036
+ const showLineNumbers = readHasBlockLineNumbers(block);
3037
+ const lineNumberColumnWidth = readLineNumberColumnWidth(block);
3038
+ return /* @__PURE__ */ jsx21("div", { className: "overflow-x-auto custom-scrollbar-amber bg-white", children: block.lines.map((line, index) => /* @__PURE__ */ jsx21(
3039
+ FileOperationLineRow,
3040
+ {
3041
+ line,
3042
+ showLineNumbers,
3043
+ lineNumberColumnWidth
3044
+ },
3045
+ readLineKey("row", line, index)
3046
+ )) });
3047
+ }
3048
+
3049
+ // src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
3050
+ import { jsx as jsx22, jsxs as jsxs14 } from "react/jsx-runtime";
2845
3051
  function isPreviewBlock(block) {
2846
3052
  return block.display === "preview";
2847
3053
  }
3054
+ function readFileOperationContentVersion(block) {
3055
+ if (block.rawText) {
3056
+ return block.rawText;
3057
+ }
3058
+ return block.lines.map(
3059
+ (line) => [
3060
+ line.kind,
3061
+ line.oldLineNumber ?? "",
3062
+ line.newLineNumber ?? "",
3063
+ line.text
3064
+ ].join(":")
3065
+ ).join("\n");
3066
+ }
2848
3067
  function getCaptionTone(part) {
2849
3068
  if (/^\+\d+$/.test(part)) {
2850
3069
  return "text-emerald-700";
@@ -2859,54 +3078,39 @@ function renderCaption(caption) {
2859
3078
  if (parts.length === 0) {
2860
3079
  return null;
2861
3080
  }
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 })
3081
+ 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: [
3082
+ index > 0 ? /* @__PURE__ */ jsx22("span", { className: "text-stone-300", children: "\xB7" }) : null,
3083
+ /* @__PURE__ */ jsx22("span", { className: cn(getCaptionTone(part)), children: part })
2865
3084
  ] }, `${part}-${index}`)) });
2866
3085
  }
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(
3086
+ function StickyFileOperationScrollArea({
3087
+ children,
3088
+ contentVersion,
3089
+ resetKey,
3090
+ className,
3091
+ scrollKind
3092
+ }) {
3093
+ const scrollRef = useRef7(null);
3094
+ const { onScroll } = useStickyBottomScroll({
3095
+ scrollRef,
3096
+ resetKey,
3097
+ isLoading: false,
3098
+ hasContent: true,
3099
+ contentVersion,
3100
+ stickyThresholdPx: 20
3101
+ });
3102
+ return /* @__PURE__ */ jsx22(
2891
3103
  "div",
2892
3104
  {
3105
+ ref: scrollRef,
3106
+ onScroll,
3107
+ "data-file-scroll-kind": scrollKind,
2893
3108
  className: cn(
2894
- "min-w-full whitespace-pre px-3 py-1 font-mono text-[11px] leading-relaxed",
2895
- rowTone
3109
+ "overflow-y-auto overscroll-contain bg-white custom-scrollbar-amber",
3110
+ className
2896
3111
  ),
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"}`
3112
+ children
3113
+ }
2910
3114
  );
2911
3115
  }
2912
3116
  function FileOperationBlock({
@@ -2917,45 +3121,67 @@ function FileOperationBlock({
2917
3121
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
2918
3122
  const previewBlock = isPreviewBlock(block);
2919
3123
  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
- ] });
3124
+ return /* @__PURE__ */ jsxs14(
3125
+ "section",
3126
+ {
3127
+ className: cn(
3128
+ "overflow-hidden bg-white",
3129
+ !isFirst && "border-t border-stone-200/80"
3130
+ ),
3131
+ children: [
3132
+ showMetaRow ? /* @__PURE__ */ jsxs14(
3133
+ "div",
3134
+ {
3135
+ className: cn(
3136
+ "flex items-center justify-between gap-4 border-b border-stone-200/80 px-4 text-stone-700",
3137
+ showPathRow ? "py-2" : "py-1.5"
3138
+ ),
3139
+ children: [
3140
+ /* @__PURE__ */ jsx22("div", { className: "min-w-0 flex-1", children: showPathRow ? /* @__PURE__ */ jsx22(TooltipProvider, { delayDuration: 250, children: /* @__PURE__ */ jsxs14(Tooltip, { children: [
3141
+ /* @__PURE__ */ jsx22(TooltipTrigger, { asChild: true, children: /* @__PURE__ */ jsx22(
3142
+ "div",
3143
+ {
3144
+ className: "truncate whitespace-nowrap font-mono text-[12px] font-medium text-stone-700",
3145
+ title: block.path,
3146
+ children: block.path
3147
+ }
3148
+ ) }),
3149
+ /* @__PURE__ */ jsx22(
3150
+ TooltipContent,
3151
+ {
3152
+ side: "top",
3153
+ className: "max-w-[420px] text-xs font-mono break-all",
3154
+ children: block.path
3155
+ }
3156
+ )
3157
+ ] }) }) : null }),
3158
+ block.caption ? renderCaption(block.caption) : null
3159
+ ]
3160
+ }
3161
+ ) : null,
3162
+ block.lines.length > 0 ? /* @__PURE__ */ jsx22(
3163
+ StickyFileOperationScrollArea,
3164
+ {
3165
+ resetKey: `block:${block.key}`,
3166
+ contentVersion: readFileOperationContentVersion(block),
3167
+ className: "max-h-72",
3168
+ scrollKind: "block",
3169
+ children: /* @__PURE__ */ jsx22(FileOperationLinesGrid, { block })
3170
+ }
3171
+ ) : block.rawText ? /* @__PURE__ */ jsx22(
3172
+ StickyFileOperationScrollArea,
3173
+ {
3174
+ resetKey: `raw:${block.key}`,
3175
+ contentVersion: block.rawText,
3176
+ className: "max-h-72",
3177
+ scrollKind: "raw",
3178
+ 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 }) })
3179
+ }
3180
+ ) : null,
3181
+ 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
3182
+ ]
3183
+ }
3184
+ );
2959
3185
  }
2960
3186
  function ToolCardFileOperationContent({
2961
3187
  card,
@@ -2966,9 +3192,9 @@ function ToolCardFileOperationContent({
2966
3192
  if (blocks.length === 0 && !output) {
2967
3193
  return null;
2968
3194
  }
2969
- return /* @__PURE__ */ jsxs13("div", { className: cn("overflow-hidden bg-white", className), children: [
3195
+ return /* @__PURE__ */ jsxs14("div", { className: cn("overflow-hidden bg-white", className), children: [
2970
3196
  blocks.map((block, index) => {
2971
- return /* @__PURE__ */ jsx21(
3197
+ return /* @__PURE__ */ jsx22(
2972
3198
  FileOperationBlock,
2973
3199
  {
2974
3200
  block,
@@ -2978,15 +3204,24 @@ function ToolCardFileOperationContent({
2978
3204
  block.key
2979
3205
  );
2980
3206
  }),
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
3207
+ output ? /* @__PURE__ */ jsx22(
3208
+ StickyFileOperationScrollArea,
3209
+ {
3210
+ resetKey: `output:${card.toolName}:${card.summary ?? "none"}`,
3211
+ contentVersion: output,
3212
+ className: cn(
3213
+ "max-h-56",
3214
+ blocks.length > 0 && "border-t border-stone-200/80"
3215
+ ),
3216
+ scrollKind: "output",
3217
+ 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 }) })
3218
+ }
3219
+ ) : null
2985
3220
  ] });
2986
3221
  }
2987
3222
 
2988
3223
  // 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";
3224
+ import { Fragment as Fragment5, jsx as jsx23, jsxs as jsxs15 } from "react/jsx-runtime";
2990
3225
  var TOOL_CARD_AUTO_EXPAND_DELAY_MS = 200;
2991
3226
  var ANSI_ESCAPE_PREFIX = String.fromCharCode(27);
2992
3227
  var ANSI_ESCAPE_PATTERN = new RegExp(`${ANSI_ESCAPE_PREFIX}\\[[0-?]*[ -/]*[@-~]`, "g");
@@ -3060,17 +3295,17 @@ function useToolCardExpandedState({
3060
3295
  }) {
3061
3296
  const [expanded, setExpanded] = useState7(false);
3062
3297
  const [hasUserToggled, setHasUserToggled] = useState7(false);
3063
- const expandTimerRef = useRef6(null);
3064
- const prevRunningRef = useRef6(isRunning);
3065
- const isFirstRenderRef = useRef6(true);
3066
- useEffect7(() => {
3298
+ const expandTimerRef = useRef8(null);
3299
+ const prevRunningRef = useRef8(isRunning);
3300
+ const isFirstRenderRef = useRef8(true);
3301
+ useEffect8(() => {
3067
3302
  return () => {
3068
3303
  if (expandTimerRef.current !== null) {
3069
3304
  window.clearTimeout(expandTimerRef.current);
3070
3305
  }
3071
3306
  };
3072
3307
  }, []);
3073
- useEffect7(() => {
3308
+ useEffect8(() => {
3074
3309
  if (expandOnError && statusTone === "error" && canExpand && !hasUserToggled) {
3075
3310
  if (expandTimerRef.current !== null) {
3076
3311
  window.clearTimeout(expandTimerRef.current);
@@ -3138,7 +3373,7 @@ function GenericToolSection({
3138
3373
  }
3139
3374
  };
3140
3375
  const style = tones[tone];
3141
- return /* @__PURE__ */ jsxs14(
3376
+ return /* @__PURE__ */ jsxs15(
3142
3377
  "section",
3143
3378
  {
3144
3379
  className: cn(
@@ -3146,7 +3381,7 @@ function GenericToolSection({
3146
3381
  style.shell
3147
3382
  ),
3148
3383
  children: [
3149
- /* @__PURE__ */ jsxs14(
3384
+ /* @__PURE__ */ jsxs15(
3150
3385
  "div",
3151
3386
  {
3152
3387
  className: cn(
@@ -3154,12 +3389,12 @@ function GenericToolSection({
3154
3389
  style.header
3155
3390
  ),
3156
3391
  children: [
3157
- /* @__PURE__ */ jsx22("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }),
3158
- /* @__PURE__ */ jsx22("span", { children: label })
3392
+ /* @__PURE__ */ jsx23("span", { className: cn("h-1.5 w-1.5 rounded-full", style.dot) }),
3393
+ /* @__PURE__ */ jsx23("span", { children: label })
3159
3394
  ]
3160
3395
  }
3161
3396
  ),
3162
- /* @__PURE__ */ jsx22("div", { className: "w-full overflow-hidden", children: /* @__PURE__ */ jsx22(
3397
+ /* @__PURE__ */ jsx23("div", { className: "w-full overflow-hidden", children: /* @__PURE__ */ jsx23(
3163
3398
  "pre",
3164
3399
  {
3165
3400
  className: cn(
@@ -3184,8 +3419,8 @@ function TerminalExecutionView({ card }) {
3184
3419
  statusTone: card.statusTone
3185
3420
  });
3186
3421
  const commandPart = card.summary?.replace(/^(command|path|args|query|input):\s*/i, "");
3187
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3188
- /* @__PURE__ */ jsx22(
3422
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3423
+ /* @__PURE__ */ jsx23(
3189
3424
  ToolCardHeader,
3190
3425
  {
3191
3426
  card,
@@ -3195,17 +3430,17 @@ function TerminalExecutionView({ card }) {
3195
3430
  onToggle
3196
3431
  }
3197
3432
  ),
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: [
3433
+ expanded && /* @__PURE__ */ jsxs15(Fragment5, { children: [
3434
+ /* @__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: [
3435
+ /* @__PURE__ */ jsx23("span", { className: "text-amber-500/50 font-medium shrink-0 select-none mt-[1px]", children: "$" }),
3436
+ /* @__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
3437
  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" }) })
3438
+ isRunning && !output && /* @__PURE__ */ jsx23("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3439
+ ] }) : /* @__PURE__ */ jsx23("div", { className: "h-3 w-32 bg-amber-200/30 rounded animate-pulse mt-2" }) })
3205
3440
  ] }) }),
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: [
3441
+ 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
3442
  output,
3208
- isRunning && /* @__PURE__ */ jsx22("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3443
+ isRunning && /* @__PURE__ */ jsx23("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })
3209
3444
  ] }) })
3210
3445
  ] })
3211
3446
  ] });
@@ -3232,8 +3467,8 @@ function FileOperationView({ card }) {
3232
3467
  statusTone: card.statusTone
3233
3468
  });
3234
3469
  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(
3470
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3471
+ /* @__PURE__ */ jsx23(
3237
3472
  ToolCardHeader,
3238
3473
  {
3239
3474
  card,
@@ -3244,7 +3479,7 @@ function FileOperationView({ card }) {
3244
3479
  onToggle
3245
3480
  }
3246
3481
  ),
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 }) })
3482
+ 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
3483
  ] });
3249
3484
  }
3250
3485
  function SearchSnippetView({ card }) {
@@ -3255,8 +3490,8 @@ function SearchSnippetView({ card }) {
3255
3490
  isRunning,
3256
3491
  statusTone: card.statusTone
3257
3492
  });
3258
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3259
- /* @__PURE__ */ jsx22(
3493
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3494
+ /* @__PURE__ */ jsx23(
3260
3495
  ToolCardHeader,
3261
3496
  {
3262
3497
  card,
@@ -3266,10 +3501,13 @@ function SearchSnippetView({ card }) {
3266
3501
  onToggle
3267
3502
  }
3268
3503
  ),
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 }) })
3504
+ 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
3505
  ] });
3271
3506
  }
3272
- function GenericToolCard({ card }) {
3507
+ function GenericToolCard({
3508
+ card,
3509
+ onToolAction
3510
+ }) {
3273
3511
  const input = card.input?.trim() ?? "";
3274
3512
  const output = card.output?.trim() ?? "";
3275
3513
  const isRunning = card.statusTone === "running";
@@ -3283,8 +3521,8 @@ function GenericToolCard({ card }) {
3283
3521
  isRunning,
3284
3522
  statusTone: card.statusTone
3285
3523
  });
3286
- return /* @__PURE__ */ jsxs14(ToolCardRoot, { children: [
3287
- /* @__PURE__ */ jsx22(
3524
+ return /* @__PURE__ */ jsxs15(ToolCardRoot, { children: [
3525
+ /* @__PURE__ */ jsx23(
3288
3526
  ToolCardHeader,
3289
3527
  {
3290
3528
  card,
@@ -3294,23 +3532,32 @@ function GenericToolCard({ card }) {
3294
3532
  onToggle
3295
3533
  }
3296
3534
  ),
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(
3535
+ expanded && hasContent && /* @__PURE__ */ jsxs15(ToolCardContent, { className: "bg-transparent px-2.5 py-2.5", children: [
3536
+ hasInputSection && /* @__PURE__ */ jsx23(GenericToolSection, { label: inputLabel, tone: "input", children: input }),
3537
+ hasInputSection && hasOutputSection && /* @__PURE__ */ jsx23("div", { className: "h-2" }),
3538
+ hasOutputSection && /* @__PURE__ */ jsx23(
3301
3539
  GenericToolSection,
3302
3540
  {
3303
3541
  label: outputLabel,
3304
3542
  tone: card.statusTone === "error" ? "error" : "output",
3305
3543
  children: output
3306
3544
  }
3307
- )
3545
+ ),
3546
+ card.action && onToolAction ? /* @__PURE__ */ jsx23("div", { className: "pt-2", children: /* @__PURE__ */ jsx23(
3547
+ "button",
3548
+ {
3549
+ type: "button",
3550
+ onClick: () => onToolAction(card.action),
3551
+ className: "rounded-md border border-amber-300/70 bg-white/80 px-3 py-1.5 text-[11px] font-semibold text-amber-900 transition-colors hover:bg-amber-100/70",
3552
+ children: card.action.sessionKind === "child" ? "Open child session" : "Open session"
3553
+ }
3554
+ ) }) : null
3308
3555
  ] })
3309
3556
  ] });
3310
3557
  }
3311
3558
 
3312
3559
  // src/components/chat/ui/chat-message-list/chat-tool-card.tsx
3313
- import { jsx as jsx23 } from "react/jsx-runtime";
3560
+ import { jsx as jsx24 } from "react/jsx-runtime";
3314
3561
  function isTerminalTool(name) {
3315
3562
  const lowered = name.toLowerCase();
3316
3563
  return lowered === "exec" || lowered === "exec_command" || lowered === "execute_command" || lowered === "command_execution" || lowered === "bash" || lowered === "shell" || lowered.includes("run_");
@@ -3323,50 +3570,53 @@ function isSearchTool(name) {
3323
3570
  const lowered = name.toLowerCase();
3324
3571
  return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
3325
3572
  }
3326
- function ChatToolCard({ card }) {
3573
+ function ChatToolCard({
3574
+ card,
3575
+ onToolAction
3576
+ }) {
3327
3577
  if (isTerminalTool(card.toolName)) {
3328
- return /* @__PURE__ */ jsx23(TerminalExecutionView, { card });
3578
+ return /* @__PURE__ */ jsx24(TerminalExecutionView, { card });
3329
3579
  }
3330
3580
  if (isFileEditTool(card.toolName)) {
3331
- return /* @__PURE__ */ jsx23(FileOperationView, { card });
3581
+ return /* @__PURE__ */ jsx24(FileOperationView, { card });
3332
3582
  }
3333
3583
  if (isSearchTool(card.toolName)) {
3334
- return /* @__PURE__ */ jsx23(SearchSnippetView, { card });
3584
+ return /* @__PURE__ */ jsx24(SearchSnippetView, { card });
3335
3585
  }
3336
- return /* @__PURE__ */ jsx23(GenericToolCard, { card });
3586
+ return /* @__PURE__ */ jsx24(GenericToolCard, { card, onToolAction });
3337
3587
  }
3338
3588
 
3339
3589
  // src/components/chat/ui/chat-message-list/chat-unknown-part.tsx
3340
- import { jsx as jsx24, jsxs as jsxs15 } from "react/jsx-runtime";
3590
+ import { jsx as jsx25, jsxs as jsxs16 } from "react/jsx-runtime";
3341
3591
  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: [
3592
+ 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: [
3593
+ /* @__PURE__ */ jsxs16("div", { className: "font-semibold text-gray-700", children: [
3344
3594
  props.label,
3345
3595
  ": ",
3346
3596
  props.rawType
3347
3597
  ] }),
3348
- props.text ? /* @__PURE__ */ jsx24("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
3598
+ props.text ? /* @__PURE__ */ jsx25("pre", { className: "mt-1 whitespace-pre-wrap break-words text-[11px] text-gray-500", children: props.text }) : null
3349
3599
  ] });
3350
3600
  }
3351
3601
 
3352
3602
  // src/components/chat/ui/chat-message-list/chat-message.tsx
3353
- import { jsx as jsx25 } from "react/jsx-runtime";
3603
+ import { jsx as jsx26 } from "react/jsx-runtime";
3354
3604
  var ChatMessage = memo(function ChatMessage2(props) {
3355
- const { message, texts } = props;
3605
+ const { message, texts, onToolAction } = props;
3356
3606
  const { role } = message;
3357
3607
  const isUser = role === "user";
3358
3608
  const isMessageInProgress = message.status === "pending" || message.status === "streaming";
3359
- return /* @__PURE__ */ jsx25(
3609
+ return /* @__PURE__ */ jsx26(
3360
3610
  "div",
3361
3611
  {
3362
3612
  className: cn(
3363
3613
  "inline-block w-fit max-w-full rounded-2xl border px-4 shadow-sm",
3364
3614
  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
3615
  ),
3366
- children: /* @__PURE__ */ jsx25("div", { className: "space-y-2", children: message.parts.map((part, index) => {
3616
+ children: /* @__PURE__ */ jsx26("div", { className: "space-y-2", children: message.parts.map((part, index) => {
3367
3617
  const { type } = part;
3368
3618
  if (type === "markdown") {
3369
- return /* @__PURE__ */ jsx25(
3619
+ return /* @__PURE__ */ jsx26(
3370
3620
  ChatMessageMarkdown,
3371
3621
  {
3372
3622
  text: part.text,
@@ -3377,7 +3627,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3377
3627
  );
3378
3628
  }
3379
3629
  if (type === "inline-content") {
3380
- return /* @__PURE__ */ jsx25(
3630
+ return /* @__PURE__ */ jsx26(
3381
3631
  ChatMessageInlineContent,
3382
3632
  {
3383
3633
  segments: part.segments,
@@ -3388,7 +3638,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3388
3638
  );
3389
3639
  }
3390
3640
  if (type === "reasoning") {
3391
- return /* @__PURE__ */ jsx25(
3641
+ return /* @__PURE__ */ jsx26(
3392
3642
  ChatReasoningBlock,
3393
3643
  {
3394
3644
  label: part.label,
@@ -3400,10 +3650,10 @@ var ChatMessage = memo(function ChatMessage2(props) {
3400
3650
  );
3401
3651
  }
3402
3652
  if (type === "tool-card") {
3403
- return /* @__PURE__ */ jsx25("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx25(ChatToolCard, { card: part.card }) }, `tool-${index}`);
3653
+ return /* @__PURE__ */ jsx26("div", { className: "mt-0.5", children: /* @__PURE__ */ jsx26(ChatToolCard, { card: part.card, onToolAction }) }, `tool-${index}`);
3404
3654
  }
3405
3655
  if (type === "file") {
3406
- return /* @__PURE__ */ jsx25(
3656
+ return /* @__PURE__ */ jsx26(
3407
3657
  ChatMessageFile,
3408
3658
  {
3409
3659
  file: part.file,
@@ -3413,7 +3663,7 @@ var ChatMessage = memo(function ChatMessage2(props) {
3413
3663
  );
3414
3664
  }
3415
3665
  if (type === "unknown") {
3416
- return /* @__PURE__ */ jsx25(
3666
+ return /* @__PURE__ */ jsx26(
3417
3667
  ChatUnknownPart,
3418
3668
  {
3419
3669
  label: part.label,
@@ -3430,9 +3680,9 @@ var ChatMessage = memo(function ChatMessage2(props) {
3430
3680
  });
3431
3681
 
3432
3682
  // src/components/chat/ui/chat-message-list/chat-message-meta.tsx
3433
- import { jsxs as jsxs16 } from "react/jsx-runtime";
3683
+ import { jsxs as jsxs17 } from "react/jsx-runtime";
3434
3684
  function ChatMessageMeta(props) {
3435
- return /* @__PURE__ */ jsxs16(
3685
+ return /* @__PURE__ */ jsxs17(
3436
3686
  "div",
3437
3687
  {
3438
3688
  className: cn(
@@ -3451,7 +3701,7 @@ function ChatMessageMeta(props) {
3451
3701
  // src/components/chat/ui/chat-message-list/chat-message-action-copy.tsx
3452
3702
  import { useMemo as useMemo6 } from "react";
3453
3703
  import { Check as Check5, Copy as Copy2 } from "lucide-react";
3454
- import { jsx as jsx26 } from "react/jsx-runtime";
3704
+ import { jsx as jsx27 } from "react/jsx-runtime";
3455
3705
  function ChatMessageActionCopy({
3456
3706
  message,
3457
3707
  texts
@@ -3465,7 +3715,7 @@ function ChatMessageActionCopy({
3465
3715
  }, [message.parts]);
3466
3716
  const { copied, copy } = useCopyFeedback({ text: messageText });
3467
3717
  if (!messageText) return null;
3468
- return /* @__PURE__ */ jsx26(
3718
+ return /* @__PURE__ */ jsx27(
3469
3719
  "button",
3470
3720
  {
3471
3721
  type: "button",
@@ -3473,13 +3723,13 @@ function ChatMessageActionCopy({
3473
3723
  className: "text-gray-400 hover:text-gray-600 transition-colors p-1 rounded-md hover:bg-gray-100 flex items-center justify-center",
3474
3724
  "aria-label": copied ? texts.copiedMessageLabel : texts.copyMessageLabel,
3475
3725
  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" })
3726
+ children: copied ? /* @__PURE__ */ jsx27(Check5, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx27(Copy2, { className: "h-3.5 w-3.5" })
3477
3727
  }
3478
3728
  );
3479
3729
  }
3480
3730
 
3481
3731
  // 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";
3732
+ import { Fragment as Fragment6, jsx as jsx28, jsxs as jsxs18 } from "react/jsx-runtime";
3483
3733
  var INVISIBLE_ONLY_TEXT_PATTERN = /\u200B|\u200C|\u200D|\u2060|\uFEFF/g;
3484
3734
  function hasRenderableText(value) {
3485
3735
  const trimmed = value.trim();
@@ -3497,10 +3747,10 @@ function hasRenderableMessageContent(message) {
3497
3747
  });
3498
3748
  }
3499
3749
  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]" })
3750
+ 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: [
3751
+ /* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse" }),
3752
+ /* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:200ms]" }),
3753
+ /* @__PURE__ */ jsx28("div", { className: "h-1.5 w-1.5 rounded-full bg-gray-400 animate-pulse [animation-delay:400ms]" })
3504
3754
  ] }) });
3505
3755
  }
3506
3756
  function ChatMessageList(props) {
@@ -3508,118 +3758,35 @@ function ChatMessageList(props) {
3508
3758
  const hasRenderableAssistantDraft = visibleMessages.some(
3509
3759
  (message) => message.role === "assistant" && (message.status === "streaming" || message.status === "pending")
3510
3760
  );
3511
- return /* @__PURE__ */ jsxs17("div", { className: cn("space-y-5", props.className), children: [
3761
+ return /* @__PURE__ */ jsxs18("div", { className: cn("space-y-5", props.className), children: [
3512
3762
  visibleMessages.map((message) => {
3513
3763
  const isUser = message.role === "user";
3514
3764
  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
3765
+ return /* @__PURE__ */ jsxs18("div", { className: cn("flex gap-3", isUser ? "justify-end" : "justify-start"), children: [
3766
+ !isUser ? /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: message.role }) : null,
3767
+ /* @__PURE__ */ jsxs18("div", { className: cn("w-fit max-w-[92%] space-y-2", isUser && "flex flex-col items-end"), children: [
3768
+ /* @__PURE__ */ jsx28(
3769
+ ChatMessage,
3770
+ {
3771
+ message,
3772
+ texts: props.texts,
3773
+ onToolAction: props.onToolAction
3774
+ }
3775
+ ),
3776
+ /* @__PURE__ */ jsx28("div", { className: cn("flex items-center gap-2", isUser && "justify-end"), children: isGenerating ? /* @__PURE__ */ jsx28(ChatMessageTypingFooter, {}) : /* @__PURE__ */ jsxs18(Fragment6, { children: [
3777
+ /* @__PURE__ */ jsx28(ChatMessageMeta, { roleLabel: message.roleLabel, timestampLabel: message.timestampLabel, isUser }),
3778
+ !isUser ? /* @__PURE__ */ jsx28(ChatMessageActionCopy, { message, texts: props.texts }) : null
3522
3779
  ] }) })
3523
3780
  ] }),
3524
- isUser ? /* @__PURE__ */ jsx27(ChatMessageAvatar, { role: message.role }) : null
3781
+ isUser ? /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: message.role }) : null
3525
3782
  ] }, message.id);
3526
3783
  }),
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 })
3784
+ props.isSending && !hasRenderableAssistantDraft ? /* @__PURE__ */ jsxs18("div", { className: "flex justify-start gap-3", children: [
3785
+ /* @__PURE__ */ jsx28(ChatMessageAvatar, { role: "assistant" }),
3786
+ /* @__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
3787
  ] }) : null
3531
3788
  ] });
3532
3789
  }
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
3790
  export {
3624
3791
  ChatInputBar,
3625
3792
  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.21",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",