@nextclaw/agent-chat-ui 0.3.5 → 0.3.6

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
@@ -164,7 +164,13 @@ type ChatFileOperationBlockViewModel = {
164
164
  display?: "preview" | "diff";
165
165
  caption?: string;
166
166
  lines: ChatFileOperationLineViewModel[];
167
+ fullLines?: ChatFileOperationLineViewModel[];
167
168
  rawText?: string;
169
+ beforeText?: string;
170
+ afterText?: string;
171
+ patchText?: string;
172
+ oldStartLine?: number;
173
+ newStartLine?: number;
168
174
  truncated?: boolean;
169
175
  };
170
176
  type ChatToolActionViewModel = {
@@ -175,6 +181,20 @@ type ChatToolActionViewModel = {
175
181
  label?: string;
176
182
  parentSessionId?: string;
177
183
  };
184
+ type ChatFileOpenActionViewModel = {
185
+ path: string;
186
+ label?: string;
187
+ viewMode: "preview" | "diff";
188
+ line?: number;
189
+ column?: number;
190
+ rawText?: string;
191
+ beforeText?: string;
192
+ afterText?: string;
193
+ patchText?: string;
194
+ oldStartLine?: number;
195
+ newStartLine?: number;
196
+ fullLines?: ChatFileOperationLineViewModel[];
197
+ };
178
198
  type ChatToolPartViewModel = {
179
199
  kind: "call" | "result";
180
200
  toolName: string;
@@ -262,6 +282,7 @@ type ChatMessageListProps = {
262
282
  texts: ChatMessageTexts;
263
283
  className?: string;
264
284
  onToolAction?: (action: ChatToolActionViewModel) => void;
285
+ onFileOpen?: (action: ChatFileOpenActionViewModel) => void;
265
286
  renderToolAgent?: (agentId: string) => ReactNode;
266
287
  };
267
288
  //#endregion
@@ -279,6 +300,36 @@ declare const ChatInputBar: _$react.ForwardRefExoticComponent<ChatInputBarProps
279
300
  //#region src/components/chat/ui/chat-message-list/chat-message-list.d.ts
280
301
  declare function ChatMessageList(props: ChatMessageListProps): _$react_jsx_runtime0.JSX.Element;
281
302
  //#endregion
303
+ //#region src/components/chat/ui/chat-message-list/chat-message-markdown.d.ts
304
+ type ChatMessageMarkdownProps = {
305
+ text: string;
306
+ role: ChatMessageRole;
307
+ texts: Pick<ChatMessageTexts, 'copyCodeLabel' | 'copiedCodeLabel'>;
308
+ inline?: boolean;
309
+ onFileOpen?: (action: ChatFileOpenActionViewModel) => void;
310
+ };
311
+ declare function ChatMessageMarkdown({
312
+ text,
313
+ role,
314
+ texts,
315
+ inline,
316
+ onFileOpen
317
+ }: ChatMessageMarkdownProps): _$react_jsx_runtime0.JSX.Element;
318
+ //#endregion
319
+ //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.d.ts
320
+ declare function FileOperationCodeSurface({
321
+ block,
322
+ layout
323
+ }: {
324
+ block: ChatFileOperationBlockViewModel;
325
+ layout?: "compact" | "workspace";
326
+ }): _$react_jsx_runtime0.JSX.Element;
327
+ declare function FileOperationLinesGrid({
328
+ block
329
+ }: {
330
+ block: ChatFileOperationBlockViewModel;
331
+ }): _$react_jsx_runtime0.JSX.Element;
332
+ //#endregion
282
333
  //#region src/components/chat/hooks/use-active-item-scroll.d.ts
283
334
  type UseActiveItemScrollParams = {
284
335
  containerRef: RefObject<HTMLElement>;
@@ -345,4 +396,4 @@ declare function resolveChatComposerSlashTrigger(nodes: ChatComposerNode[], sele
345
396
  end: number;
346
397
  } | null;
347
398
  //#endregion
348
- 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 };
399
+ export { type ChatComposerNode, type ChatComposerSelection, type ChatComposerTextNode, type ChatComposerTokenKind, type ChatComposerTokenNode, type ChatFileOpenActionViewModel, type ChatFileOperationBlockViewModel, type ChatFileOperationLineViewModel, type ChatInlineContentSegmentViewModel, type ChatInlineHint, type ChatInlineTokenViewModel, ChatInputBar, type ChatInputBarActionsProps, type ChatInputBarHandle, type ChatInputBarProps, type ChatInputBarToolbarProps, ChatMessageList, type ChatMessageListProps, ChatMessageMarkdown, 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, FileOperationCodeSurface, FileOperationLinesGrid, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
package/dist/index.js CHANGED
@@ -2047,7 +2047,26 @@ function resolveSafeHref(href) {
2047
2047
  function isExternalHref(href) {
2048
2048
  return /^https?:\/\//i.test(href);
2049
2049
  }
2050
- function ChatMessageMarkdown({ text, role, texts, inline = false }) {
2050
+ function looksLikeLocalFileHref(href) {
2051
+ return href.startsWith("./") || href.startsWith("../") || href.startsWith("/Users/") || href.startsWith("/home/") || href.startsWith("/tmp/") || href.startsWith("/var/") || /^\/.+\.[A-Za-z0-9_-]+(?::\d+(?::\d+)?)?$/.test(href);
2052
+ }
2053
+ function parseLocalFileAction(href) {
2054
+ const normalizedHref = href.split("#")[0]?.split("?")[0] ?? href;
2055
+ const decodedHref = decodeURIComponent(normalizedHref);
2056
+ if (!looksLikeLocalFileHref(decodedHref)) return null;
2057
+ const lineMatch = /^(.*?)(?::(\d+)(?::(\d+))?)$/.exec(decodedHref);
2058
+ const rawPath = lineMatch?.[1] ?? decodedHref;
2059
+ const line = lineMatch?.[2] ? Number(lineMatch[2]) : void 0;
2060
+ const column = lineMatch?.[3] ? Number(lineMatch[3]) : void 0;
2061
+ return {
2062
+ path: rawPath,
2063
+ label: rawPath.split("/").filter(Boolean).pop() ?? rawPath,
2064
+ viewMode: "preview",
2065
+ ...typeof line === "number" ? { line } : {},
2066
+ ...typeof column === "number" ? { column } : {}
2067
+ };
2068
+ }
2069
+ function ChatMessageMarkdown({ text, role, texts, inline = false, onFileOpen }) {
2051
2070
  const isUser = role === "user";
2052
2071
  const markdownComponents = useMemo(() => ({
2053
2072
  p: ({ children }) => inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", { children }),
@@ -2058,9 +2077,17 @@ function ChatMessageMarkdown({ text, role, texts, inline = false }) {
2058
2077
  children
2059
2078
  });
2060
2079
  const external = isExternalHref(safeHref);
2080
+ const localFileAction = external ? null : parseLocalFileAction(safeHref);
2081
+ const handleClick = (event) => {
2082
+ if (!onFileOpen || !localFileAction) return;
2083
+ if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
2084
+ event.preventDefault();
2085
+ onFileOpen(localFileAction);
2086
+ };
2061
2087
  return /* @__PURE__ */ jsx("a", {
2062
2088
  ...rest,
2063
2089
  href: safeHref,
2090
+ onClick: handleClick,
2064
2091
  target: external ? "_blank" : void 0,
2065
2092
  rel: external ? "noreferrer noopener" : void 0,
2066
2093
  children
@@ -2110,7 +2137,11 @@ function ChatMessageMarkdown({ text, role, texts, inline = false }) {
2110
2137
  children
2111
2138
  });
2112
2139
  }
2113
- }), [inline, texts]);
2140
+ }), [
2141
+ inline,
2142
+ onFileOpen,
2143
+ texts
2144
+ ]);
2114
2145
  return /* @__PURE__ */ jsx(inline ? "span" : "div", {
2115
2146
  className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
2116
2147
  children: /* @__PURE__ */ jsx(ReactMarkdown, {
@@ -2158,7 +2189,7 @@ function ChatInlineTokenBadge({ kind, label, isUser }) {
2158
2189
  })]
2159
2190
  });
2160
2191
  }
2161
- function ChatMessageInlineContent({ segments, role, texts }) {
2192
+ function ChatMessageInlineContent({ segments, role, texts, onFileOpen }) {
2162
2193
  const isUser = role === "user";
2163
2194
  return /* @__PURE__ */ jsx("div", {
2164
2195
  className: "whitespace-pre-wrap break-words leading-6",
@@ -2176,7 +2207,8 @@ function ChatMessageInlineContent({ segments, role, texts }) {
2176
2207
  text: segment.text,
2177
2208
  role,
2178
2209
  texts,
2179
- inline: true
2210
+ inline: true,
2211
+ onFileOpen
2180
2212
  }, `markdown-${index}`);
2181
2213
  })
2182
2214
  });
@@ -2568,7 +2600,7 @@ function ChatReasoningBlock({ label, text, isUser, isInProgress }) {
2568
2600
  ref: scrollRef,
2569
2601
  onScroll,
2570
2602
  "data-reasoning-scroll": "true",
2571
- className: cn("mt-2 w-fit max-w-[500px] max-h-56 overflow-y-auto overscroll-contain rounded-lg custom-scrollbar-amber", isUser ? "bg-primary-700/60" : "bg-gray-100"),
2603
+ className: cn("mt-2 w-fit max-w-[500px] max-h-56 overflow-y-auto rounded-lg custom-scrollbar-amber", isUser ? "bg-primary-700/60" : "bg-gray-100"),
2572
2604
  children: /* @__PURE__ */ jsx("pre", {
2573
2605
  className: "min-w-0 whitespace-pre-wrap break-all p-2 text-[11px]",
2574
2606
  children: text
@@ -2648,15 +2680,15 @@ function ToolCardSessionActionButton({ action, onAction }) {
2648
2680
  function ToolCardHeader({ card, icon: Icon, expanded, canExpand, hideSummary = false, actionSlot, onToggle }) {
2649
2681
  const summaryPart = hideSummary ? "" : card.summary?.replace(/^(command|path|args|query|input):\s*/i, "") ?? "";
2650
2682
  return /* @__PURE__ */ jsxs("div", {
2651
- className: cn("flex items-center justify-between px-3 py-2.5 transition-colors bg-transparent", canExpand ? "cursor-pointer hover:bg-amber-100/30" : ""),
2683
+ className: cn("flex items-center gap-3 px-3 py-2.5 transition-colors bg-transparent", canExpand ? "cursor-pointer hover:bg-amber-100/30" : ""),
2652
2684
  onClick: onToggle,
2653
2685
  children: [/* @__PURE__ */ jsxs("div", {
2654
- className: "flex items-center gap-2 font-mono min-w-0 max-w-[calc(100%-80px)] text-amber-950/80",
2686
+ className: "flex min-w-0 flex-1 items-center gap-2 overflow-hidden font-mono text-amber-950/80",
2655
2687
  children: [/* @__PURE__ */ jsx(Icon, {
2656
2688
  className: "h-4 w-4 text-amber-600/80 shrink-0",
2657
2689
  strokeWidth: 3
2658
2690
  }), /* @__PURE__ */ jsxs("div", {
2659
- className: "flex items-center gap-1.5 min-w-0",
2691
+ className: "flex min-w-0 flex-1 items-center gap-1.5 overflow-hidden",
2660
2692
  children: [/* @__PURE__ */ jsx("span", {
2661
2693
  className: "font-bold shrink-0 tracking-tight",
2662
2694
  children: card.toolName
@@ -2664,7 +2696,7 @@ function ToolCardHeader({ card, icon: Icon, expanded, canExpand, hideSummary = f
2664
2696
  className: "text-amber-300 font-bold select-none shrink-0",
2665
2697
  children: "›"
2666
2698
  }), /* @__PURE__ */ jsx("span", {
2667
- className: "truncate flex-1 min-w-0 font-normal",
2699
+ className: "block min-w-0 flex-1 truncate font-normal",
2668
2700
  title: summaryPart,
2669
2701
  children: summaryPart
2670
2702
  })] })]
@@ -2693,8 +2725,9 @@ function ToolCardHeaderSessionAction({ action, onAction }) {
2693
2725
  }
2694
2726
  //#endregion
2695
2727
  //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation-lines.tsx
2696
- const FILE_ROW_CLASS_NAME = "h-5 font-mono text-[11px] leading-5";
2697
- const 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";
2728
+ const FILE_TEXT_CLASS_NAME = "font-mono text-[11px] leading-5";
2729
+ const FILE_ROW_CLASS_NAME = `h-5 ${FILE_TEXT_CLASS_NAME}`;
2730
+ const FILE_LINE_NUMBER_CELL_CLASS_NAME = "flex h-5 items-center justify-center px-2.5 tabular-nums select-none";
2698
2731
  function formatLineNumber(value) {
2699
2732
  return typeof value === "number" ? String(value) : "";
2700
2733
  }
@@ -2731,6 +2764,22 @@ function getCodeRowTone(line) {
2731
2764
  if (line.kind === "add") return "bg-emerald-50 text-emerald-950";
2732
2765
  return "bg-white text-amber-950/80";
2733
2766
  }
2767
+ function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
2768
+ const lineNumberCellClassName = layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0";
2769
+ return /* @__PURE__ */ jsx("span", {
2770
+ "data-file-line-number-cell": "true",
2771
+ style: layout === "compact" ? { width: lineNumberColumnWidth } : void 0,
2772
+ className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, lineNumberCellClassName, getLineNumberTone(line)),
2773
+ children: readVisibleLineNumber(line)
2774
+ });
2775
+ }
2776
+ function FileOperationCodeCell({ line }) {
2777
+ return /* @__PURE__ */ jsx("span", {
2778
+ "data-file-code-row": "true",
2779
+ className: cn("block min-w-full whitespace-pre px-2.5", getCodeRowTone(line)),
2780
+ children: line.text || " "
2781
+ });
2782
+ }
2734
2783
  function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth }) {
2735
2784
  return /* @__PURE__ */ jsxs("div", {
2736
2785
  "data-file-line-row": "true",
@@ -2739,22 +2788,50 @@ function FileOperationLineRow({ line, showLineNumbers, lineNumberColumnWidth })
2739
2788
  showLineNumbers,
2740
2789
  lineNumberColumnWidth
2741
2790
  }),
2742
- children: [showLineNumbers ? /* @__PURE__ */ jsx("span", {
2743
- "data-file-line-number-cell": "true",
2744
- style: { width: lineNumberColumnWidth },
2745
- className: cn(FILE_GUTTER_NUMBER_CELL_CLASS_NAME, getLineNumberTone(line)),
2746
- children: readVisibleLineNumber(line)
2747
- }) : null, /* @__PURE__ */ jsx("span", {
2748
- "data-file-code-row": "true",
2749
- className: cn("block min-w-full whitespace-pre px-2.5", getCodeRowTone(line)),
2750
- children: line.text || " "
2791
+ children: [showLineNumbers ? /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
2792
+ layout: "compact",
2793
+ line,
2794
+ lineNumberColumnWidth
2795
+ }) : null, /* @__PURE__ */ jsx(FileOperationCodeCell, { line })]
2796
+ });
2797
+ }
2798
+ function FileOperationWorkspaceSurface({ block }) {
2799
+ const showLineNumbers = readHasBlockLineNumbers(block);
2800
+ const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2801
+ return /* @__PURE__ */ jsxs("div", {
2802
+ "data-file-code-surface": "true",
2803
+ "data-file-code-surface-layout": "workspace",
2804
+ className: "flex h-full min-h-full min-w-full bg-white",
2805
+ children: [showLineNumbers ? /* @__PURE__ */ jsxs("div", {
2806
+ "data-file-code-gutter": "true",
2807
+ style: {
2808
+ width: lineNumberColumnWidth,
2809
+ minWidth: lineNumberColumnWidth
2810
+ },
2811
+ className: cn("flex h-full min-h-full shrink-0 flex-col", FILE_TEXT_CLASS_NAME),
2812
+ children: [block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
2813
+ layout: "workspace",
2814
+ line,
2815
+ lineNumberColumnWidth
2816
+ }, readLineKey("gutter", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-r border-stone-200 bg-stone-100" })]
2817
+ }) : null, /* @__PURE__ */ jsxs("div", {
2818
+ "data-file-code-canvas": "true",
2819
+ className: "flex h-full min-h-full min-w-0 flex-1 flex-col bg-white",
2820
+ children: [block.lines.map((line, index) => /* @__PURE__ */ jsx("div", {
2821
+ "data-file-code-canvas-row": "true",
2822
+ className: FILE_ROW_CLASS_NAME,
2823
+ children: /* @__PURE__ */ jsx(FileOperationCodeCell, { line })
2824
+ }, readLineKey("code", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-white" })]
2751
2825
  })]
2752
2826
  });
2753
2827
  }
2754
- function FileOperationLinesGrid({ block }) {
2828
+ function FileOperationCodeSurface({ block, layout = "compact" }) {
2829
+ if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, { block });
2755
2830
  const showLineNumbers = readHasBlockLineNumbers(block);
2756
2831
  const lineNumberColumnWidth = readLineNumberColumnWidth(block);
2757
2832
  return /* @__PURE__ */ jsx("div", {
2833
+ "data-file-code-surface": "true",
2834
+ "data-file-code-surface-layout": "compact",
2758
2835
  className: "overflow-x-auto custom-scrollbar-amber bg-white",
2759
2836
  children: block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineRow, {
2760
2837
  line,
@@ -2763,6 +2840,12 @@ function FileOperationLinesGrid({ block }) {
2763
2840
  }, readLineKey("row", line, index)))
2764
2841
  });
2765
2842
  }
2843
+ function FileOperationLinesGrid({ block }) {
2844
+ return /* @__PURE__ */ jsx(FileOperationCodeSurface, {
2845
+ block,
2846
+ layout: "compact"
2847
+ });
2848
+ }
2766
2849
  //#endregion
2767
2850
  //#region src/components/chat/ui/chat-message-list/tool-card/tool-card-file-operation.tsx
2768
2851
  function isPreviewBlock(block) {
@@ -2796,6 +2879,20 @@ function renderCaption(caption) {
2796
2879
  })] }, `${part}-${index}`))
2797
2880
  });
2798
2881
  }
2882
+ function buildFileOpenAction(block) {
2883
+ return {
2884
+ path: block.path,
2885
+ label: block.path.split("/").filter(Boolean).pop() ?? block.path,
2886
+ viewMode: block.display === "diff" ? "diff" : "preview",
2887
+ ...block.rawText ? { rawText: block.rawText } : {},
2888
+ ...block.beforeText ? { beforeText: block.beforeText } : {},
2889
+ ...block.afterText ? { afterText: block.afterText } : {},
2890
+ ...block.patchText ? { patchText: block.patchText } : {},
2891
+ ...typeof block.oldStartLine === "number" ? { oldStartLine: block.oldStartLine } : {},
2892
+ ...typeof block.newStartLine === "number" ? { newStartLine: block.newStartLine } : {},
2893
+ fullLines: block.fullLines ?? block.lines
2894
+ };
2895
+ }
2799
2896
  function StickyFileOperationScrollArea({ children, contentVersion, resetKey, className, scrollKind }) {
2800
2897
  const scrollRef = useRef(null);
2801
2898
  const { onScroll } = useStickyBottomScroll({
@@ -2810,14 +2907,18 @@ function StickyFileOperationScrollArea({ children, contentVersion, resetKey, cla
2810
2907
  ref: scrollRef,
2811
2908
  onScroll,
2812
2909
  "data-file-scroll-kind": scrollKind,
2813
- className: cn("overflow-y-auto overscroll-contain bg-white custom-scrollbar-amber", className),
2910
+ className: cn("overflow-y-auto bg-white custom-scrollbar-amber", className),
2814
2911
  children
2815
2912
  });
2816
2913
  }
2817
- function FileOperationBlock({ block, showPathRow, isFirst }) {
2914
+ function FileOperationBlock({ block, showPathRow, isFirst, onFileOpen }) {
2818
2915
  const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
2819
2916
  const previewBlock = isPreviewBlock(block);
2820
2917
  const showMetaRow = showPathRow || Boolean(block.caption);
2918
+ const handlePathClick = () => {
2919
+ if (!onFileOpen) return;
2920
+ onFileOpen(buildFileOpenAction(block));
2921
+ };
2821
2922
  return /* @__PURE__ */ jsxs("section", {
2822
2923
  className: cn("overflow-hidden bg-white", !isFirst && "border-t border-stone-200/80"),
2823
2924
  children: [
@@ -2829,8 +2930,10 @@ function FileOperationBlock({ block, showPathRow, isFirst }) {
2829
2930
  delayDuration: 250,
2830
2931
  children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
2831
2932
  asChild: true,
2832
- children: /* @__PURE__ */ jsx("div", {
2833
- className: "truncate whitespace-nowrap font-mono text-[12px] font-medium text-stone-700",
2933
+ children: /* @__PURE__ */ jsx("button", {
2934
+ type: "button",
2935
+ onClick: handlePathClick,
2936
+ className: cn("w-full truncate whitespace-nowrap text-left font-mono text-[12px] font-medium text-stone-700", onFileOpen && "transition-colors hover:text-amber-900 hover:underline"),
2834
2937
  title: block.path,
2835
2938
  children: block.path
2836
2939
  })
@@ -2868,7 +2971,7 @@ function FileOperationBlock({ block, showPathRow, isFirst }) {
2868
2971
  ]
2869
2972
  });
2870
2973
  }
2871
- function ToolCardFileOperationContent({ card, className }) {
2974
+ function ToolCardFileOperationContent({ card, className, onFileOpen }) {
2872
2975
  const blocks = card.fileOperation?.blocks ?? [];
2873
2976
  const output = card.output?.trim() ?? "";
2874
2977
  if (blocks.length === 0 && !output) return null;
@@ -2878,7 +2981,8 @@ function ToolCardFileOperationContent({ card, className }) {
2878
2981
  return /* @__PURE__ */ jsx(FileOperationBlock, {
2879
2982
  block,
2880
2983
  showPathRow: true,
2881
- isFirst: index === 0
2984
+ isFirst: index === 0,
2985
+ onFileOpen
2882
2986
  }, block.key);
2883
2987
  }), output ? /* @__PURE__ */ jsx(StickyFileOperationScrollArea, {
2884
2988
  resetKey: `output:${card.toolName}:${card.summary ?? "none"}`,
@@ -3074,7 +3178,7 @@ function TerminalExecutionView({ card }) {
3074
3178
  children: [output, isRunning && /* @__PURE__ */ jsx("span", { className: "inline-block w-1.5 h-3 ml-1 bg-amber-500/60 animate-pulse align-middle" })]
3075
3179
  }) })] })] });
3076
3180
  }
3077
- function FileOperationView({ card }) {
3181
+ function FileOperationView({ card, onFileOpen }) {
3078
3182
  const output = card.output?.trim() ?? "";
3079
3183
  const isRunning = card.statusTone === "running";
3080
3184
  const hasStructuredPreview = Boolean(card.fileOperation?.blocks.length);
@@ -3102,7 +3206,10 @@ function FileOperationView({ card }) {
3102
3206
  onToggle
3103
3207
  }), expanded && hasContent && /* @__PURE__ */ jsx(ToolCardContent, {
3104
3208
  className: "border-t border-amber-200/20 bg-transparent px-0 pt-0 pb-0",
3105
- children: /* @__PURE__ */ jsx(ToolCardFileOperationContent, { card })
3209
+ children: /* @__PURE__ */ jsx(ToolCardFileOperationContent, {
3210
+ card,
3211
+ onFileOpen
3212
+ })
3106
3213
  })] });
3107
3214
  }
3108
3215
  function SearchSnippetView({ card }) {
@@ -3181,9 +3288,12 @@ function isSearchTool(name) {
3181
3288
  const lowered = name.toLowerCase();
3182
3289
  return lowered === "grep_search" || lowered === "find_files" || lowered.includes("search");
3183
3290
  }
3184
- function ChatToolCard({ card, onToolAction, renderToolAgent }) {
3291
+ function ChatToolCard({ card, onToolAction, onFileOpen, renderToolAgent }) {
3185
3292
  if (isTerminalTool(card.toolName)) return /* @__PURE__ */ jsx(TerminalExecutionView, { card });
3186
- if (isFileEditTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, { card });
3293
+ if (isFileEditTool(card.toolName)) return /* @__PURE__ */ jsx(FileOperationView, {
3294
+ card,
3295
+ onFileOpen
3296
+ });
3187
3297
  if (isSearchTool(card.toolName)) return /* @__PURE__ */ jsx(SearchSnippetView, { card });
3188
3298
  return /* @__PURE__ */ jsx(GenericToolCard, {
3189
3299
  card,
@@ -3212,7 +3322,7 @@ function ChatUnknownPart(props) {
3212
3322
  //#endregion
3213
3323
  //#region src/components/chat/ui/chat-message-list/chat-message.tsx
3214
3324
  const ChatMessage = memo(function ChatMessage(props) {
3215
- const { message, texts, onToolAction, renderToolAgent } = props;
3325
+ const { message, texts, onToolAction, onFileOpen, renderToolAgent } = props;
3216
3326
  const { role } = message;
3217
3327
  const isUser = role === "user";
3218
3328
  const isMessageInProgress = message.status === "pending" || message.status === "streaming";
@@ -3225,12 +3335,14 @@ const ChatMessage = memo(function ChatMessage(props) {
3225
3335
  if (type === "markdown") return /* @__PURE__ */ jsx(ChatMessageMarkdown, {
3226
3336
  text: part.text,
3227
3337
  role,
3228
- texts
3338
+ texts,
3339
+ onFileOpen
3229
3340
  }, `markdown-${index}`);
3230
3341
  if (type === "inline-content") return /* @__PURE__ */ jsx(ChatMessageInlineContent, {
3231
3342
  segments: part.segments,
3232
3343
  role,
3233
- texts
3344
+ texts,
3345
+ onFileOpen
3234
3346
  }, `inline-content-${index}`);
3235
3347
  if (type === "reasoning") return /* @__PURE__ */ jsx(ChatReasoningBlock, {
3236
3348
  label: part.label,
@@ -3243,6 +3355,7 @@ const ChatMessage = memo(function ChatMessage(props) {
3243
3355
  children: /* @__PURE__ */ jsx(ChatToolCard, {
3244
3356
  card: part.card,
3245
3357
  onToolAction,
3358
+ onFileOpen,
3246
3359
  renderToolAgent
3247
3360
  })
3248
3361
  }, `tool-${index}`);
@@ -3339,6 +3452,7 @@ function ChatMessageList(props) {
3339
3452
  message,
3340
3453
  texts: props.texts,
3341
3454
  onToolAction: props.onToolAction,
3455
+ onFileOpen: props.onFileOpen,
3342
3456
  renderToolAgent: props.renderToolAgent
3343
3457
  }), /* @__PURE__ */ jsx("div", {
3344
3458
  className: cn("flex items-center gap-2", isUser && "justify-end"),
@@ -3365,4 +3479,4 @@ function ChatMessageList(props) {
3365
3479
  });
3366
3480
  }
3367
3481
  //#endregion
3368
- export { ChatInputBar, ChatMessageList, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
3482
+ export { ChatInputBar, ChatMessageList, ChatMessageMarkdown, FileOperationCodeSurface, FileOperationLinesGrid, copyText, createChatComposerNodesFromText, createChatComposerTextNode, createChatComposerTokenNode, createEmptyChatComposerNodes, extractChatComposerTokenKeys, normalizeChatComposerNodes, removeChatComposerTokenNodes, replaceChatComposerRange, resolveChatComposerSlashTrigger, serializeChatComposerDocument, serializeChatComposerPlainText, useActiveItemScroll, useCopyFeedback, useElementWidth, useStickyBottomScroll };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@nextclaw/agent-chat-ui",
3
- "version": "0.3.5",
3
+ "version": "0.3.6",
4
4
  "private": false,
5
5
  "description": "Reusable Nextclaw agent chat UI primitives and default skin.",
6
6
  "type": "module",