@nextclaw/agent-chat-ui 0.6.4 → 0.6.5
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 +11 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +431 -196
- package/dist/index.js.map +1 -1
- package/package.json +2 -1
package/dist/index.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as React from "react";
|
|
2
|
-
import { Fragment, forwardRef, memo, useCallback, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
2
|
+
import { Fragment, createContext, forwardRef, memo, useCallback, useContext, useEffect, useId, useImperativeHandle, useLayoutEffect, useMemo, useRef, useState } from "react";
|
|
3
3
|
import { clsx } from "clsx";
|
|
4
4
|
import { twMerge } from "tailwind-merge";
|
|
5
5
|
import { Fragment as Fragment$1, jsx, jsxs } from "react/jsx-runtime";
|
|
@@ -12,7 +12,7 @@ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
|
|
|
12
12
|
import { LexicalComposer } from "@lexical/react/LexicalComposer";
|
|
13
13
|
import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
|
|
14
14
|
import { PlainTextPlugin } from "@lexical/react/LexicalPlainTextPlugin";
|
|
15
|
-
import { $applyNodeReplacement, $createLineBreakNode, $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, $setSelection, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, DecoratorNode, KEY_DOWN_COMMAND, SELECTION_CHANGE_COMMAND, mergeRegister } from "lexical";
|
|
15
|
+
import { $applyNodeReplacement, $createLineBreakNode, $createParagraphNode, $createRangeSelection, $createTextNode, $getRoot, $getSelection, $isElementNode, $isRangeSelection, $isTextNode, $setSelection, BLUR_COMMAND, COMMAND_PRIORITY_EDITOR, COMMAND_PRIORITY_HIGH, DecoratorNode, KEY_DOWN_COMMAND, SELECTION_CHANGE_COMMAND, SKIP_DOM_SELECTION_TAG, mergeRegister } from "lexical";
|
|
16
16
|
import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
|
|
17
17
|
import ReactMarkdown from "react-markdown";
|
|
18
18
|
import remarkGfm from "remark-gfm";
|
|
@@ -1625,12 +1625,12 @@ function writeChatComposerStateToLexicalRoot(nodes, selection) {
|
|
|
1625
1625
|
nextSelection.focus.set(focus.key, focus.offset, focus.type);
|
|
1626
1626
|
$setSelection(nextSelection);
|
|
1627
1627
|
}
|
|
1628
|
-
function syncLexicalEditorFromChatComposerState(editor, nodes, selection) {
|
|
1628
|
+
function syncLexicalEditorFromChatComposerState(editor, nodes, selection, preserveDomSelection = false) {
|
|
1629
1629
|
editor.update(() => {
|
|
1630
1630
|
writeChatComposerStateToLexicalRoot(nodes, selection);
|
|
1631
|
-
});
|
|
1631
|
+
}, preserveDomSelection ? { tag: SKIP_DOM_SELECTION_TAG } : void 0);
|
|
1632
1632
|
}
|
|
1633
|
-
function syncLexicalSelectionFromChatComposerSelection(editor, selection) {
|
|
1633
|
+
function syncLexicalSelectionFromChatComposerSelection(editor, selection, preserveDomSelection = false) {
|
|
1634
1634
|
editor.update(() => {
|
|
1635
1635
|
const nextSelection = $createRangeSelection();
|
|
1636
1636
|
const anchor = buildSelectionPointFromOffset(selection.start);
|
|
@@ -1638,7 +1638,7 @@ function syncLexicalSelectionFromChatComposerSelection(editor, selection) {
|
|
|
1638
1638
|
nextSelection.anchor.set(anchor.key, anchor.offset, anchor.type);
|
|
1639
1639
|
nextSelection.focus.set(focus.key, focus.offset, focus.type);
|
|
1640
1640
|
$setSelection(nextSelection);
|
|
1641
|
-
});
|
|
1641
|
+
}, preserveDomSelection ? { tag: SKIP_DOM_SELECTION_TAG } : void 0);
|
|
1642
1642
|
}
|
|
1643
1643
|
//#endregion
|
|
1644
1644
|
//#region src/components/chat/ui/chat-input-bar/lexical/chat-composer-lexical-operations.ts
|
|
@@ -1949,11 +1949,12 @@ var ChatComposerLexicalOwner = class {
|
|
|
1949
1949
|
const shouldSyncDocument = nextSignature !== this.editorSignatureRef.current;
|
|
1950
1950
|
if (!shouldSyncDocument && !pendingSelection) return;
|
|
1951
1951
|
this.startApplyingExternalUpdate();
|
|
1952
|
+
const preserveDomSelection = editor.getRootElement() !== document.activeElement;
|
|
1952
1953
|
if (shouldSyncDocument) {
|
|
1953
|
-
syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection);
|
|
1954
|
+
syncLexicalEditorFromChatComposerState(editor, nodes, pendingSelection, preserveDomSelection);
|
|
1954
1955
|
this.editorSignatureRef.current = nextSignature;
|
|
1955
1956
|
this.lastPublishedSignatureRef.current = nextSignature;
|
|
1956
|
-
} else if (pendingSelection) syncLexicalSelectionFromChatComposerSelection(editor, pendingSelection);
|
|
1957
|
+
} else if (pendingSelection) syncLexicalSelectionFromChatComposerSelection(editor, pendingSelection, preserveDomSelection);
|
|
1957
1958
|
if (pendingSelection) {
|
|
1958
1959
|
this.selectionRef.current = pendingSelection;
|
|
1959
1960
|
this.pendingSelectionRef.current = null;
|
|
@@ -2780,7 +2781,7 @@ function isRecord$1(value) {
|
|
|
2780
2781
|
function readString(value) {
|
|
2781
2782
|
return typeof value === "string" && value.trim().length > 0 ? value.trim() : void 0;
|
|
2782
2783
|
}
|
|
2783
|
-
function readPositiveInteger(value) {
|
|
2784
|
+
function readPositiveInteger$1(value) {
|
|
2784
2785
|
return Number.isInteger(value) && Number(value) > 0 ? Number(value) : void 0;
|
|
2785
2786
|
}
|
|
2786
2787
|
function readFilePreviewViewer(value) {
|
|
@@ -2800,8 +2801,8 @@ function readFileTarget(record) {
|
|
|
2800
2801
|
type: "file",
|
|
2801
2802
|
payload: {
|
|
2802
2803
|
path,
|
|
2803
|
-
line: readPositiveInteger(payload.line),
|
|
2804
|
-
column: readPositiveInteger(payload.column),
|
|
2804
|
+
line: readPositiveInteger$1(payload.line),
|
|
2805
|
+
column: readPositiveInteger$1(payload.column),
|
|
2805
2806
|
viewer: readFilePreviewViewer(payload.viewer)
|
|
2806
2807
|
}
|
|
2807
2808
|
};
|
|
@@ -2936,6 +2937,114 @@ function ChatInlineDisplay({ display, renderInlineDisplay }) {
|
|
|
2936
2937
|
return /* @__PURE__ */ jsx(ChatInlineDisplayFallback, { display });
|
|
2937
2938
|
}
|
|
2938
2939
|
//#endregion
|
|
2940
|
+
//#region src/components/chat/ui/chat-message-list/mermaid/chat-mermaid-diagram.tsx
|
|
2941
|
+
const STREAMING_RENDER_DELAY_MS = 300;
|
|
2942
|
+
let mermaidModulePromise = null;
|
|
2943
|
+
function loadMermaid() {
|
|
2944
|
+
mermaidModulePromise ??= import("mermaid").then((module) => module.default);
|
|
2945
|
+
return mermaidModulePromise;
|
|
2946
|
+
}
|
|
2947
|
+
function readMermaidTheme() {
|
|
2948
|
+
if (typeof document === "undefined") return "default";
|
|
2949
|
+
const root = document.documentElement;
|
|
2950
|
+
return root.getAttribute("data-theme-appearance") === "dark" || root.classList.contains("dark") ? "dark" : "default";
|
|
2951
|
+
}
|
|
2952
|
+
function useMermaidTheme() {
|
|
2953
|
+
const [theme, setTheme] = useState(readMermaidTheme);
|
|
2954
|
+
useEffect(() => {
|
|
2955
|
+
const root = document.documentElement;
|
|
2956
|
+
const observer = new MutationObserver(() => setTheme(readMermaidTheme()));
|
|
2957
|
+
observer.observe(root, {
|
|
2958
|
+
attributes: true,
|
|
2959
|
+
attributeFilter: ["class", "data-theme-appearance"]
|
|
2960
|
+
});
|
|
2961
|
+
return () => observer.disconnect();
|
|
2962
|
+
}, []);
|
|
2963
|
+
return theme;
|
|
2964
|
+
}
|
|
2965
|
+
function ChatMermaidDiagram({ isStreaming, source, texts }) {
|
|
2966
|
+
const reactId = useId().replace(/[^a-zA-Z0-9_-]/g, "");
|
|
2967
|
+
const renderSequence = useRef(0);
|
|
2968
|
+
const theme = useMermaidTheme();
|
|
2969
|
+
const normalizedSource = source.trim();
|
|
2970
|
+
const renderKey = `${theme}:${normalizedSource}`;
|
|
2971
|
+
const [state, setState] = useState(null);
|
|
2972
|
+
const diagramLabel = texts.mermaidDiagramLabel ?? "Mermaid diagram";
|
|
2973
|
+
const errorLabel = texts.mermaidRenderErrorLabel ?? "Diagram could not be rendered";
|
|
2974
|
+
useEffect(() => {
|
|
2975
|
+
let active = true;
|
|
2976
|
+
const renderDiagram = async () => {
|
|
2977
|
+
try {
|
|
2978
|
+
const mermaid = await loadMermaid();
|
|
2979
|
+
if (!active) return;
|
|
2980
|
+
renderSequence.current += 1;
|
|
2981
|
+
const diagramId = `nextclaw-mermaid-${reactId}-${renderSequence.current}`;
|
|
2982
|
+
mermaid.initialize({
|
|
2983
|
+
securityLevel: "strict",
|
|
2984
|
+
startOnLoad: false,
|
|
2985
|
+
suppressErrorRendering: true,
|
|
2986
|
+
theme
|
|
2987
|
+
});
|
|
2988
|
+
const valid = await mermaid.parse(normalizedSource, { suppressErrors: true });
|
|
2989
|
+
if (!active) return;
|
|
2990
|
+
if (!valid) throw new Error("Invalid Mermaid diagram");
|
|
2991
|
+
const { svg } = await mermaid.render(diagramId, normalizedSource);
|
|
2992
|
+
if (active) setState({
|
|
2993
|
+
key: renderKey,
|
|
2994
|
+
status: "rendered",
|
|
2995
|
+
svg
|
|
2996
|
+
});
|
|
2997
|
+
} catch {
|
|
2998
|
+
if (active && !isStreaming) setState({
|
|
2999
|
+
key: renderKey,
|
|
3000
|
+
status: "error"
|
|
3001
|
+
});
|
|
3002
|
+
}
|
|
3003
|
+
};
|
|
3004
|
+
const timeout = window.setTimeout(() => void renderDiagram(), isStreaming ? STREAMING_RENDER_DELAY_MS : 0);
|
|
3005
|
+
return () => {
|
|
3006
|
+
active = false;
|
|
3007
|
+
window.clearTimeout(timeout);
|
|
3008
|
+
};
|
|
3009
|
+
}, [
|
|
3010
|
+
isStreaming,
|
|
3011
|
+
normalizedSource,
|
|
3012
|
+
reactId,
|
|
3013
|
+
renderKey,
|
|
3014
|
+
theme
|
|
3015
|
+
]);
|
|
3016
|
+
const currentState = state?.key === renderKey ? state : null;
|
|
3017
|
+
if (currentState?.status === "error") return /* @__PURE__ */ jsxs("div", {
|
|
3018
|
+
"data-chat-mermaid-error": "true",
|
|
3019
|
+
className: "my-3 overflow-hidden rounded-xl border border-border bg-muted/25",
|
|
3020
|
+
children: [/* @__PURE__ */ jsx("div", {
|
|
3021
|
+
className: "border-b border-border px-3 py-2 text-xs text-muted-foreground",
|
|
3022
|
+
children: errorLabel
|
|
3023
|
+
}), /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3024
|
+
className: "language-mermaid",
|
|
3025
|
+
texts,
|
|
3026
|
+
children: normalizedSource
|
|
3027
|
+
})]
|
|
3028
|
+
});
|
|
3029
|
+
const renderedState = state?.status === "rendered" ? state : null;
|
|
3030
|
+
if (!renderedState) return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3031
|
+
className: "language-mermaid",
|
|
3032
|
+
texts,
|
|
3033
|
+
children: normalizedSource
|
|
3034
|
+
});
|
|
3035
|
+
return /* @__PURE__ */ jsx("figure", {
|
|
3036
|
+
"aria-label": diagramLabel,
|
|
3037
|
+
"aria-busy": !currentState,
|
|
3038
|
+
"data-chat-mermaid-diagram": "true",
|
|
3039
|
+
"data-chat-mermaid-updating": !currentState || void 0,
|
|
3040
|
+
className: "my-3 min-h-24 overflow-auto rounded-xl border border-border bg-muted/20 p-3",
|
|
3041
|
+
children: /* @__PURE__ */ jsx("div", {
|
|
3042
|
+
className: "min-w-fit [&_svg]:mx-auto [&_svg]:h-auto [&_svg]:max-w-full",
|
|
3043
|
+
dangerouslySetInnerHTML: { __html: renderedState.svg }
|
|
3044
|
+
})
|
|
3045
|
+
});
|
|
3046
|
+
}
|
|
3047
|
+
//#endregion
|
|
2939
3048
|
//#region src/components/chat/ui/chat-message-list/chat-message-file/chat-message-image-preview.tsx
|
|
2940
3049
|
function ChatMessageImageLightbox({ alt, closeLabel, onClose, src }) {
|
|
2941
3050
|
const titleId = useId();
|
|
@@ -3042,7 +3151,7 @@ function ChatMessageImagePreview({ alt, expandLabel, closeLabel, sizeLabel, src
|
|
|
3042
3151
|
}
|
|
3043
3152
|
//#endregion
|
|
3044
3153
|
//#region src/components/chat/ui/chat-message-list/utils/chat-local-resource.utils.ts
|
|
3045
|
-
const
|
|
3154
|
+
const SAFE_BROWSER_LINK_PROTOCOLS = new Set([
|
|
3046
3155
|
"http:",
|
|
3047
3156
|
"https:",
|
|
3048
3157
|
"mailto:",
|
|
@@ -3050,37 +3159,90 @@ const SAFE_LINK_PROTOCOLS = new Set([
|
|
|
3050
3159
|
]);
|
|
3051
3160
|
const URI_SCHEME_PATTERN = /^[a-zA-Z][a-zA-Z\d+.-]*:/;
|
|
3052
3161
|
const WINDOWS_ABSOLUTE_PATH_PATTERN = /^[a-z]:[\\/]/i;
|
|
3162
|
+
const WINDOWS_FILE_URI_PATH_PATTERN = /^\/[a-z]:[\\/]/i;
|
|
3163
|
+
const FILE_URI_PROTOCOL = "file:";
|
|
3053
3164
|
function isSchemeLessResourceHref(href) {
|
|
3054
3165
|
return !href.startsWith("//") && !URI_SCHEME_PATTERN.test(href);
|
|
3055
3166
|
}
|
|
3056
|
-
function
|
|
3057
|
-
|
|
3058
|
-
|
|
3167
|
+
function readPositiveInteger(value) {
|
|
3168
|
+
const parsed = Number(value);
|
|
3169
|
+
return Number.isSafeInteger(parsed) && parsed > 0 ? parsed : void 0;
|
|
3170
|
+
}
|
|
3171
|
+
function readFileUriHref(href) {
|
|
3172
|
+
let url;
|
|
3059
3173
|
try {
|
|
3060
|
-
|
|
3061
|
-
return SAFE_LINK_PROTOCOLS.has(url.protocol) ? href : null;
|
|
3174
|
+
url = new URL(href);
|
|
3062
3175
|
} catch {
|
|
3063
3176
|
return null;
|
|
3064
3177
|
}
|
|
3178
|
+
if (url.protocol !== FILE_URI_PROTOCOL || url.hostname && url.hostname.toLowerCase() !== "localhost") return null;
|
|
3179
|
+
let path;
|
|
3180
|
+
try {
|
|
3181
|
+
path = decodeURIComponent(url.pathname);
|
|
3182
|
+
} catch {
|
|
3183
|
+
return null;
|
|
3184
|
+
}
|
|
3185
|
+
if (WINDOWS_FILE_URI_PATH_PATTERN.test(path)) path = path.slice(1);
|
|
3186
|
+
return path ? {
|
|
3187
|
+
path,
|
|
3188
|
+
query: url.searchParams,
|
|
3189
|
+
fragment: url.hash.slice(1)
|
|
3190
|
+
} : null;
|
|
3065
3191
|
}
|
|
3066
|
-
function
|
|
3067
|
-
|
|
3068
|
-
|
|
3069
|
-
|
|
3070
|
-
const
|
|
3071
|
-
const
|
|
3192
|
+
function readSchemeLessFileHref(href) {
|
|
3193
|
+
const hashIndex = href.indexOf("#");
|
|
3194
|
+
const hrefWithoutFragment = hashIndex >= 0 ? href.slice(0, hashIndex) : href;
|
|
3195
|
+
const fragment = hashIndex >= 0 ? href.slice(hashIndex + 1) : "";
|
|
3196
|
+
const queryIndex = hrefWithoutFragment.indexOf("?");
|
|
3197
|
+
const encodedPath = queryIndex >= 0 ? hrefWithoutFragment.slice(0, queryIndex) : hrefWithoutFragment;
|
|
3198
|
+
const encodedQuery = queryIndex >= 0 ? hrefWithoutFragment.slice(queryIndex + 1) : "";
|
|
3072
3199
|
let path;
|
|
3073
3200
|
try {
|
|
3074
|
-
path = decodeURIComponent(encodedPath
|
|
3201
|
+
path = decodeURIComponent(encodedPath);
|
|
3075
3202
|
} catch {
|
|
3076
3203
|
return null;
|
|
3077
3204
|
}
|
|
3205
|
+
return path ? {
|
|
3206
|
+
path,
|
|
3207
|
+
query: new URLSearchParams(encodedQuery),
|
|
3208
|
+
fragment
|
|
3209
|
+
} : null;
|
|
3210
|
+
}
|
|
3211
|
+
function readLinePosition(fragment) {
|
|
3212
|
+
const match = /^L(\d+)(?:C(\d+))?(?:-L\d+(?:C\d+)?)?$/i.exec(fragment);
|
|
3213
|
+
const line = readPositiveInteger(match?.[1]);
|
|
3214
|
+
return {
|
|
3215
|
+
line,
|
|
3216
|
+
column: line ? readPositiveInteger(match?.[2]) : void 0
|
|
3217
|
+
};
|
|
3218
|
+
}
|
|
3219
|
+
function transformChatResourceHref(href) {
|
|
3220
|
+
if (WINDOWS_ABSOLUTE_PATH_PATTERN.test(href) || isSchemeLessResourceHref(href)) return href;
|
|
3221
|
+
try {
|
|
3222
|
+
const url = new URL(href);
|
|
3223
|
+
if (SAFE_BROWSER_LINK_PROTOCOLS.has(url.protocol)) return href;
|
|
3224
|
+
return url.protocol === FILE_URI_PROTOCOL && readFileUriHref(href) ? href : "";
|
|
3225
|
+
} catch {
|
|
3226
|
+
return "";
|
|
3227
|
+
}
|
|
3228
|
+
}
|
|
3229
|
+
function resolveSafeChatResourceHref(href) {
|
|
3230
|
+
if (!href) return null;
|
|
3231
|
+
return transformChatResourceHref(href) || null;
|
|
3232
|
+
}
|
|
3233
|
+
function isExternalChatResourceHref(href) {
|
|
3234
|
+
return /^https?:\/\//i.test(href);
|
|
3235
|
+
}
|
|
3236
|
+
function parseChatLocalFileAction(href) {
|
|
3237
|
+
const fileHref = href.toLowerCase().startsWith(FILE_URI_PROTOCOL) ? readFileUriHref(href) : readSchemeLessFileHref(href);
|
|
3238
|
+
const path = fileHref?.path;
|
|
3078
3239
|
if (!path || path.startsWith("#") || path.startsWith("//") || !WINDOWS_ABSOLUTE_PATH_PATTERN.test(path) && URI_SCHEME_PATTERN.test(path)) return null;
|
|
3079
|
-
const viewer =
|
|
3240
|
+
const viewer = fileHref.query.get("viewer");
|
|
3241
|
+
const fragmentPosition = readLinePosition(fileHref.fragment);
|
|
3080
3242
|
const lineMatch = /^(.*?)(?::(\d+)(?::(\d+))?)$/.exec(path);
|
|
3081
3243
|
const rawPath = lineMatch?.[1] ?? path;
|
|
3082
|
-
const line =
|
|
3083
|
-
const column =
|
|
3244
|
+
const line = fragmentPosition.line ?? readPositiveInteger(lineMatch?.[2]);
|
|
3245
|
+
const column = fragmentPosition.column ?? (line ? readPositiveInteger(lineMatch?.[3]) : void 0);
|
|
3084
3246
|
return {
|
|
3085
3247
|
path: rawPath,
|
|
3086
3248
|
label: rawPath.split(/[\\/]/).filter(Boolean).pop() ?? rawPath,
|
|
@@ -3180,153 +3342,170 @@ function splitTextNodeByInlineTokens(value, tokens) {
|
|
|
3180
3342
|
}];
|
|
3181
3343
|
}
|
|
3182
3344
|
function transformInlineTokenTextNodes(node, tokens) {
|
|
3183
|
-
if (node.type === "code" || node.type === "inlineCode" || !node.children) return;
|
|
3184
|
-
|
|
3185
|
-
|
|
3186
|
-
|
|
3187
|
-
|
|
3188
|
-
continue;
|
|
3189
|
-
}
|
|
3190
|
-
transformInlineTokenTextNodes(child, tokens);
|
|
3191
|
-
nextChildren.push(child);
|
|
3192
|
-
}
|
|
3193
|
-
node.children = nextChildren;
|
|
3345
|
+
if (node.type === "code" || node.type === "inlineCode" || !node.children) return node;
|
|
3346
|
+
return {
|
|
3347
|
+
...node,
|
|
3348
|
+
children: node.children.flatMap((child) => child.type === "text" && typeof child.value === "string" ? splitTextNodeByInlineTokens(child.value, tokens) : [transformInlineTokenTextNodes(child, tokens)])
|
|
3349
|
+
};
|
|
3194
3350
|
}
|
|
3195
3351
|
function createRemarkInlineTokenPlugin(inlineTokens) {
|
|
3196
3352
|
const tokens = prepareInlineTokens(inlineTokens);
|
|
3197
|
-
return () => (tree) =>
|
|
3198
|
-
if (tokens.length === 0) return;
|
|
3199
|
-
transformInlineTokenTextNodes(tree, tokens);
|
|
3200
|
-
};
|
|
3353
|
+
return () => (tree) => tokens.length > 0 ? transformInlineTokenTextNodes(tree, tokens) : tree;
|
|
3201
3354
|
}
|
|
3202
3355
|
function readStringProp(props, key) {
|
|
3203
3356
|
const value = props[key];
|
|
3204
3357
|
return typeof value === "string" && value.length > 0 ? value : null;
|
|
3205
3358
|
}
|
|
3206
|
-
|
|
3207
|
-
|
|
3208
|
-
const
|
|
3209
|
-
|
|
3210
|
-
|
|
3359
|
+
const ChatMessageMarkdownRuntimeContext = createContext(null);
|
|
3360
|
+
function useChatMessageMarkdownRuntime() {
|
|
3361
|
+
const runtime = useContext(ChatMessageMarkdownRuntimeContext);
|
|
3362
|
+
if (!runtime) throw new Error("Chat message Markdown renderer requires its runtime context");
|
|
3363
|
+
return runtime;
|
|
3364
|
+
}
|
|
3365
|
+
const CHAT_MESSAGE_MARKDOWN_COMPONENTS = {
|
|
3366
|
+
p: function ChatMarkdownParagraph({ node, children }) {
|
|
3367
|
+
const { inline } = useChatMessageMarkdownRuntime();
|
|
3368
|
+
return inline ? /* @__PURE__ */ jsx(Fragment$1, { children }) : /* @__PURE__ */ jsx("p", {
|
|
3211
3369
|
"data-chat-image-row": isSingleLineImageParagraph(node) ? "three-column" : void 0,
|
|
3212
3370
|
children
|
|
3213
|
-
})
|
|
3214
|
-
|
|
3215
|
-
|
|
3216
|
-
|
|
3217
|
-
|
|
3218
|
-
|
|
3219
|
-
|
|
3220
|
-
|
|
3371
|
+
});
|
|
3372
|
+
},
|
|
3373
|
+
span: function ChatMarkdownSpan({ node: _node, children, ...rest }) {
|
|
3374
|
+
const { isUser, onInlineTokenClick } = useChatMessageMarkdownRuntime();
|
|
3375
|
+
const restProps = rest;
|
|
3376
|
+
const kind = readStringProp(restProps, INLINE_TOKEN_KIND_ATTR);
|
|
3377
|
+
const key = readStringProp(restProps, INLINE_TOKEN_KEY_ATTR);
|
|
3378
|
+
const label = readStringProp(restProps, INLINE_TOKEN_LABEL_ATTR);
|
|
3379
|
+
const rawText = readStringProp(restProps, INLINE_TOKEN_RAW_TEXT_ATTR);
|
|
3380
|
+
if (kind && key && label && rawText) return /* @__PURE__ */ jsx(ChatInlineTokenBadge, {
|
|
3381
|
+
kind,
|
|
3382
|
+
label,
|
|
3383
|
+
isUser,
|
|
3384
|
+
onClick: onInlineTokenClick ? () => onInlineTokenClick({
|
|
3221
3385
|
kind,
|
|
3386
|
+
key,
|
|
3222
3387
|
label,
|
|
3223
|
-
|
|
3224
|
-
|
|
3225
|
-
|
|
3226
|
-
|
|
3227
|
-
|
|
3228
|
-
|
|
3229
|
-
|
|
3230
|
-
|
|
3231
|
-
|
|
3232
|
-
|
|
3233
|
-
|
|
3234
|
-
|
|
3235
|
-
|
|
3236
|
-
|
|
3237
|
-
|
|
3238
|
-
const external = safeHref ? isExternalChatResourceHref(safeHref) : false;
|
|
3239
|
-
const localFileAction = external ? null : safeHref ? parseChatLocalFileAction(safeHref) : null;
|
|
3240
|
-
const handleClick = (event) => {
|
|
3241
|
-
if (!safeHref) {
|
|
3242
|
-
event.preventDefault();
|
|
3243
|
-
return;
|
|
3244
|
-
}
|
|
3245
|
-
if (!onFileOpen || !localFileAction) return;
|
|
3246
|
-
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3388
|
+
rawText
|
|
3389
|
+
}) : void 0
|
|
3390
|
+
});
|
|
3391
|
+
return /* @__PURE__ */ jsx("span", {
|
|
3392
|
+
...rest,
|
|
3393
|
+
children
|
|
3394
|
+
});
|
|
3395
|
+
},
|
|
3396
|
+
a: function ChatMarkdownLink({ node: _node, href, children, ...rest }) {
|
|
3397
|
+
const { onFileOpen } = useChatMessageMarkdownRuntime();
|
|
3398
|
+
const safeHref = resolveSafeChatResourceHref(href);
|
|
3399
|
+
const external = safeHref ? isExternalChatResourceHref(safeHref) : false;
|
|
3400
|
+
const localFileAction = external ? null : safeHref ? parseChatLocalFileAction(safeHref) : null;
|
|
3401
|
+
const handleClick = (event) => {
|
|
3402
|
+
if (!safeHref) {
|
|
3247
3403
|
event.preventDefault();
|
|
3248
|
-
|
|
3249
|
-
}
|
|
3250
|
-
|
|
3251
|
-
|
|
3252
|
-
|
|
3253
|
-
|
|
3254
|
-
|
|
3255
|
-
|
|
3256
|
-
|
|
3257
|
-
|
|
3258
|
-
|
|
3259
|
-
|
|
3260
|
-
|
|
3261
|
-
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
if (!onFileOpen || !localFileAction) return;
|
|
3407
|
+
if (event.defaultPrevented || event.button !== 0 || event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
|
|
3408
|
+
event.preventDefault();
|
|
3409
|
+
onFileOpen(localFileAction);
|
|
3410
|
+
};
|
|
3411
|
+
return /* @__PURE__ */ jsx("a", {
|
|
3412
|
+
...rest,
|
|
3413
|
+
className: cn(rest.className, !safeHref && "chat-link-invalid"),
|
|
3414
|
+
href: safeHref ?? "#",
|
|
3415
|
+
"aria-disabled": !safeHref || void 0,
|
|
3416
|
+
onClick: handleClick,
|
|
3417
|
+
target: external ? "_blank" : void 0,
|
|
3418
|
+
rel: external ? "noreferrer noopener" : void 0,
|
|
3419
|
+
children
|
|
3420
|
+
});
|
|
3421
|
+
},
|
|
3422
|
+
table: function ChatMarkdownTable({ node: _node, children, ...rest }) {
|
|
3423
|
+
return /* @__PURE__ */ jsx("div", {
|
|
3262
3424
|
className: "chat-table-wrap",
|
|
3263
3425
|
children: /* @__PURE__ */ jsx("table", {
|
|
3264
3426
|
...rest,
|
|
3265
3427
|
children
|
|
3266
3428
|
})
|
|
3267
|
-
})
|
|
3268
|
-
|
|
3269
|
-
|
|
3270
|
-
|
|
3271
|
-
|
|
3272
|
-
|
|
3273
|
-
|
|
3274
|
-
|
|
3275
|
-
|
|
3276
|
-
|
|
3277
|
-
|
|
3278
|
-
|
|
3279
|
-
|
|
3280
|
-
|
|
3281
|
-
}
|
|
3282
|
-
|
|
3283
|
-
|
|
3284
|
-
|
|
3285
|
-
|
|
3286
|
-
|
|
3287
|
-
|
|
3288
|
-
|
|
3289
|
-
|
|
3290
|
-
|
|
3291
|
-
|
|
3292
|
-
|
|
3293
|
-
|
|
3294
|
-
|
|
3429
|
+
});
|
|
3430
|
+
},
|
|
3431
|
+
input: function ChatMarkdownInput({ node: _node, type, checked, ...rest }) {
|
|
3432
|
+
if (type !== "checkbox") return /* @__PURE__ */ jsx("input", {
|
|
3433
|
+
...rest,
|
|
3434
|
+
type
|
|
3435
|
+
});
|
|
3436
|
+
return /* @__PURE__ */ jsx("input", {
|
|
3437
|
+
...rest,
|
|
3438
|
+
type: "checkbox",
|
|
3439
|
+
checked,
|
|
3440
|
+
readOnly: true,
|
|
3441
|
+
disabled: true,
|
|
3442
|
+
className: "chat-task-checkbox"
|
|
3443
|
+
});
|
|
3444
|
+
},
|
|
3445
|
+
img: function ChatMarkdownImage({ node: _node, src, alt }) {
|
|
3446
|
+
const { resolveFileContentUrl, texts } = useChatMessageMarkdownRuntime();
|
|
3447
|
+
const safeSrc = resolveSafeChatResourceHref(src);
|
|
3448
|
+
if (!safeSrc) return null;
|
|
3449
|
+
const localFileAction = parseChatLocalFileAction(safeSrc);
|
|
3450
|
+
const resolvedSrc = localFileAction && resolveFileContentUrl ? resolveFileContentUrl(localFileAction) : safeSrc;
|
|
3451
|
+
if (!resolvedSrc) return null;
|
|
3452
|
+
return /* @__PURE__ */ jsx(ChatMessageImagePreview, {
|
|
3453
|
+
alt: alt || "",
|
|
3454
|
+
closeLabel: texts.attachmentCloseLabel ?? "Close preview",
|
|
3455
|
+
expandLabel: texts.attachmentExpandLabel ?? "Expand image",
|
|
3456
|
+
sizeLabel: null,
|
|
3457
|
+
src: resolvedSrc
|
|
3458
|
+
});
|
|
3459
|
+
},
|
|
3460
|
+
code: function ChatMarkdownCode({ node: _node, className, children, ...rest }) {
|
|
3461
|
+
const { isStreaming, renderInlineDisplay, texts } = useChatMessageMarkdownRuntime();
|
|
3462
|
+
const plainText = String(children ?? "");
|
|
3463
|
+
if (!className && !plainText.includes("\n")) return /* @__PURE__ */ jsx("code", {
|
|
3464
|
+
...rest,
|
|
3465
|
+
className: cn("chat-inline-code", className),
|
|
3466
|
+
children
|
|
3467
|
+
});
|
|
3468
|
+
const inlineDisplay = isChatInlineDisplayLanguage(className) ? parseChatInlineDisplayDirective(plainText) : null;
|
|
3469
|
+
if (inlineDisplay) return /* @__PURE__ */ jsx(ChatInlineDisplay, {
|
|
3470
|
+
display: inlineDisplay,
|
|
3471
|
+
renderInlineDisplay
|
|
3472
|
+
});
|
|
3473
|
+
if (className?.split(" ").includes("language-mermaid")) return /* @__PURE__ */ jsx(ChatMermaidDiagram, {
|
|
3474
|
+
isStreaming,
|
|
3475
|
+
source: plainText,
|
|
3476
|
+
texts
|
|
3477
|
+
});
|
|
3478
|
+
return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3479
|
+
className,
|
|
3480
|
+
texts,
|
|
3481
|
+
children
|
|
3482
|
+
});
|
|
3483
|
+
}
|
|
3484
|
+
};
|
|
3485
|
+
function ChatMessageMarkdown({ text, role, texts, inline = false, isStreaming = false, inlineTokens, onFileOpen, onInlineTokenClick, resolveFileContentUrl, renderInlineDisplay }) {
|
|
3486
|
+
const isUser = role === "user";
|
|
3487
|
+
const remarkPlugins = inlineTokens?.length ? [remarkGfm, createRemarkInlineTokenPlugin(inlineTokens)] : [remarkGfm];
|
|
3488
|
+
const WrapperTag = inline ? "span" : "div";
|
|
3489
|
+
return /* @__PURE__ */ jsx(ChatMessageMarkdownRuntimeContext.Provider, {
|
|
3490
|
+
value: {
|
|
3491
|
+
inline,
|
|
3492
|
+
isStreaming,
|
|
3493
|
+
isUser,
|
|
3494
|
+
onFileOpen,
|
|
3495
|
+
onInlineTokenClick,
|
|
3496
|
+
renderInlineDisplay,
|
|
3497
|
+
resolveFileContentUrl,
|
|
3498
|
+
texts
|
|
3295
3499
|
},
|
|
3296
|
-
|
|
3297
|
-
|
|
3298
|
-
|
|
3299
|
-
|
|
3300
|
-
|
|
3301
|
-
|
|
3302
|
-
|
|
3303
|
-
|
|
3304
|
-
|
|
3305
|
-
display: inlineDisplay,
|
|
3306
|
-
renderInlineDisplay
|
|
3307
|
-
});
|
|
3308
|
-
return /* @__PURE__ */ jsx(ChatCodeBlock, {
|
|
3309
|
-
className,
|
|
3310
|
-
texts,
|
|
3311
|
-
children
|
|
3312
|
-
});
|
|
3313
|
-
}
|
|
3314
|
-
}), [
|
|
3315
|
-
inline,
|
|
3316
|
-
isUser,
|
|
3317
|
-
onFileOpen,
|
|
3318
|
-
onInlineTokenClick,
|
|
3319
|
-
renderInlineDisplay,
|
|
3320
|
-
resolveFileContentUrl,
|
|
3321
|
-
texts
|
|
3322
|
-
]);
|
|
3323
|
-
return /* @__PURE__ */ jsx(inline ? "span" : "div", {
|
|
3324
|
-
className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
|
|
3325
|
-
children: /* @__PURE__ */ jsx(ReactMarkdown, {
|
|
3326
|
-
skipHtml: true,
|
|
3327
|
-
remarkPlugins,
|
|
3328
|
-
components: markdownComponents,
|
|
3329
|
-
children: trimMarkdown(text)
|
|
3500
|
+
children: /* @__PURE__ */ jsx(WrapperTag, {
|
|
3501
|
+
className: cn("chat-markdown", isUser ? "chat-markdown-user" : "chat-markdown-assistant"),
|
|
3502
|
+
children: /* @__PURE__ */ jsx(ReactMarkdown, {
|
|
3503
|
+
skipHtml: true,
|
|
3504
|
+
remarkPlugins,
|
|
3505
|
+
components: CHAT_MESSAGE_MARKDOWN_COMPONENTS,
|
|
3506
|
+
urlTransform: transformChatResourceHref,
|
|
3507
|
+
children: trimMarkdown(text)
|
|
3508
|
+
})
|
|
3330
3509
|
})
|
|
3331
3510
|
});
|
|
3332
3511
|
}
|
|
@@ -4213,6 +4392,9 @@ function readVisibleLineNumber(line) {
|
|
|
4213
4392
|
const value = line.newLineNumber ?? line.oldLineNumber;
|
|
4214
4393
|
return typeof value === "number" ? String(value) : "";
|
|
4215
4394
|
}
|
|
4395
|
+
function isTargetLine(line, targetLine) {
|
|
4396
|
+
return typeof targetLine === "number" && (line.newLineNumber ?? line.oldLineNumber) === targetLine;
|
|
4397
|
+
}
|
|
4216
4398
|
function readLineKey(prefix, line, index) {
|
|
4217
4399
|
return `${prefix}-${index}-${line.oldLineNumber ?? "x"}-${line.newLineNumber ?? "x"}`;
|
|
4218
4400
|
}
|
|
@@ -4256,25 +4438,34 @@ function getCodeRowTone(line) {
|
|
|
4256
4438
|
if (line.kind === "add") return "bg-emerald-50 text-emerald-950";
|
|
4257
4439
|
return "bg-card text-foreground";
|
|
4258
4440
|
}
|
|
4259
|
-
function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth }) {
|
|
4441
|
+
function FileOperationLineNumberCell({ layout, line, lineNumberColumnWidth, target }) {
|
|
4260
4442
|
return /* @__PURE__ */ jsx("span", {
|
|
4261
4443
|
"data-file-line-number-cell": "true",
|
|
4262
4444
|
style: layout === "compact" ? {
|
|
4263
4445
|
width: lineNumberColumnWidth,
|
|
4264
4446
|
minWidth: lineNumberColumnWidth
|
|
4265
4447
|
} : void 0,
|
|
4266
|
-
className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line)),
|
|
4448
|
+
className: cn(FILE_LINE_NUMBER_CELL_CLASS_NAME, layout === "compact" ? "sticky left-0 z-[1]" : "w-full shrink-0", getLineNumberTone(line), target ? "bg-gray-200 font-medium text-gray-950" : null),
|
|
4267
4449
|
children: readVisibleLineNumber(line)
|
|
4268
4450
|
});
|
|
4269
4451
|
}
|
|
4270
|
-
function FileOperationCodeCell({ language, line }) {
|
|
4452
|
+
function FileOperationCodeCell({ language, line, target, targetColumn, targetRef }) {
|
|
4271
4453
|
const highlightedCode = useMemo(() => chatCodeSyntaxHighlighter.highlight(line.text || " ", language), [language, line.text]);
|
|
4272
|
-
|
|
4454
|
+
const visibleTargetColumn = typeof targetColumn === "number" && Number.isSafeInteger(targetColumn) && targetColumn > 0 ? Math.min(targetColumn, line.text.length + 1) : null;
|
|
4455
|
+
return /* @__PURE__ */ jsxs("span", {
|
|
4456
|
+
ref: visibleTargetColumn ? void 0 : targetRef,
|
|
4273
4457
|
"data-file-code-row": "true",
|
|
4274
4458
|
"data-file-code-language": highlightedCode.language,
|
|
4275
4459
|
"data-highlighted": highlightedCode.highlighted ? "true" : "false",
|
|
4276
|
-
|
|
4277
|
-
|
|
4460
|
+
"data-file-target-column": visibleTargetColumn ?? void 0,
|
|
4461
|
+
className: cn("chat-file-code-syntax hljs relative block min-w-0 flex-1 whitespace-pre px-2.5", readLanguageClassName(highlightedCode.language), getCodeRowTone(line), target ? "bg-gray-100/90" : null),
|
|
4462
|
+
children: [/* @__PURE__ */ jsx("span", { dangerouslySetInnerHTML: { __html: highlightedCode.html } }), visibleTargetColumn ? /* @__PURE__ */ jsx("span", {
|
|
4463
|
+
ref: targetRef,
|
|
4464
|
+
"aria-hidden": "true",
|
|
4465
|
+
"data-file-target-caret": "true",
|
|
4466
|
+
className: "pointer-events-none absolute inset-y-0 w-px bg-primary",
|
|
4467
|
+
style: { left: `calc(0.625rem + ${visibleTargetColumn - 1}ch)` }
|
|
4468
|
+
}) : null]
|
|
4278
4469
|
});
|
|
4279
4470
|
}
|
|
4280
4471
|
function FileOperationLineRow({ language, line, showLineNumbers, lineNumberColumnWidth }) {
|
|
@@ -4291,11 +4482,22 @@ function FileOperationLineRow({ language, line, showLineNumbers, lineNumberColum
|
|
|
4291
4482
|
})]
|
|
4292
4483
|
});
|
|
4293
4484
|
}
|
|
4294
|
-
function FileOperationWorkspaceSurface({ block }) {
|
|
4485
|
+
function FileOperationWorkspaceSurface({ block, targetColumn, targetLine }) {
|
|
4295
4486
|
const showLineNumbers = readHasBlockLineNumbers(block);
|
|
4296
4487
|
const lineNumberColumnWidth = readLineNumberColumnWidth(block);
|
|
4297
4488
|
const codeColumnWidth = readCodeColumnWidth(block);
|
|
4298
4489
|
const language = readBlockLanguage(block);
|
|
4490
|
+
const targetRef = useRef(null);
|
|
4491
|
+
useLayoutEffect(() => {
|
|
4492
|
+
targetRef.current?.scrollIntoView({
|
|
4493
|
+
block: "center",
|
|
4494
|
+
inline: targetColumn ? "center" : "nearest"
|
|
4495
|
+
});
|
|
4496
|
+
}, [
|
|
4497
|
+
block,
|
|
4498
|
+
targetColumn,
|
|
4499
|
+
targetLine
|
|
4500
|
+
]);
|
|
4299
4501
|
return /* @__PURE__ */ jsxs("div", {
|
|
4300
4502
|
"data-file-code-surface": "true",
|
|
4301
4503
|
"data-file-code-surface-layout": "workspace",
|
|
@@ -4310,7 +4512,8 @@ function FileOperationWorkspaceSurface({ block }) {
|
|
|
4310
4512
|
children: [block.lines.map((line, index) => /* @__PURE__ */ jsx(FileOperationLineNumberCell, {
|
|
4311
4513
|
layout: "workspace",
|
|
4312
4514
|
line,
|
|
4313
|
-
lineNumberColumnWidth
|
|
4515
|
+
lineNumberColumnWidth,
|
|
4516
|
+
target: isTargetLine(line, targetLine)
|
|
4314
4517
|
}, readLineKey("gutter", line, index))), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 border-r border-border bg-muted" })]
|
|
4315
4518
|
}) : null, /* @__PURE__ */ jsxs("div", {
|
|
4316
4519
|
"data-file-code-canvas": "true",
|
|
@@ -4319,20 +4522,32 @@ function FileOperationWorkspaceSurface({ block }) {
|
|
|
4319
4522
|
"data-file-code-stack": "true",
|
|
4320
4523
|
className: "min-w-full",
|
|
4321
4524
|
style: { minWidth: codeColumnWidth },
|
|
4322
|
-
children: block.lines.map((line, index) =>
|
|
4323
|
-
|
|
4324
|
-
|
|
4325
|
-
|
|
4326
|
-
|
|
4327
|
-
|
|
4328
|
-
|
|
4329
|
-
|
|
4525
|
+
children: block.lines.map((line, index) => {
|
|
4526
|
+
const target = isTargetLine(line, targetLine);
|
|
4527
|
+
return /* @__PURE__ */ jsx("div", {
|
|
4528
|
+
"data-file-code-canvas-row": "true",
|
|
4529
|
+
"data-file-target-line": target ? "true" : void 0,
|
|
4530
|
+
"aria-current": target ? "location" : void 0,
|
|
4531
|
+
className: FILE_ROW_CLASS_NAME,
|
|
4532
|
+
children: /* @__PURE__ */ jsx(FileOperationCodeCell, {
|
|
4533
|
+
language,
|
|
4534
|
+
line,
|
|
4535
|
+
target,
|
|
4536
|
+
targetColumn: target ? targetColumn : null,
|
|
4537
|
+
targetRef: target ? targetRef : void 0
|
|
4538
|
+
})
|
|
4539
|
+
}, readLineKey("code", line, index));
|
|
4540
|
+
})
|
|
4330
4541
|
}), /* @__PURE__ */ jsx("div", { className: "min-h-0 flex-1 bg-card" })]
|
|
4331
4542
|
})]
|
|
4332
4543
|
});
|
|
4333
4544
|
}
|
|
4334
|
-
function FileOperationCodeSurface({ block, layout = "compact" }) {
|
|
4335
|
-
if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, {
|
|
4545
|
+
function FileOperationCodeSurface({ block, layout = "compact", targetColumn, targetLine }) {
|
|
4546
|
+
if (layout === "workspace") return /* @__PURE__ */ jsx(FileOperationWorkspaceSurface, {
|
|
4547
|
+
block,
|
|
4548
|
+
targetColumn,
|
|
4549
|
+
targetLine
|
|
4550
|
+
});
|
|
4336
4551
|
const showLineNumbers = readHasBlockLineNumbers(block);
|
|
4337
4552
|
const lineNumberColumnWidth = readLineNumberColumnWidth(block);
|
|
4338
4553
|
const codeColumnWidth = readCodeColumnWidth(block);
|
|
@@ -5243,6 +5458,9 @@ const MAX_SUMMARY_SEGMENTS = 3;
|
|
|
5243
5458
|
function isToolCardPart(part) {
|
|
5244
5459
|
return part.type === "tool-card";
|
|
5245
5460
|
}
|
|
5461
|
+
function isGroupableToolPart(part) {
|
|
5462
|
+
return part?.type === "reasoning" || part?.type === "tool-card" && !part.card.panelApp;
|
|
5463
|
+
}
|
|
5246
5464
|
function resolveToolActivityFamily(toolName) {
|
|
5247
5465
|
const lowered = toolName.toLowerCase();
|
|
5248
5466
|
if (lowered === "list_dir") return "directory";
|
|
@@ -5321,17 +5539,29 @@ function formatToolActivityGroupLabel(params) {
|
|
|
5321
5539
|
if (hiddenCount > 0) parts.push(`+${hiddenCount}`);
|
|
5322
5540
|
return parts.join(" · ");
|
|
5323
5541
|
}
|
|
5324
|
-
|
|
5325
|
-
|
|
5326
|
-
|
|
5327
|
-
|
|
5328
|
-
|
|
5542
|
+
function collectToolGroupParts(parts, startIndex) {
|
|
5543
|
+
const candidateParts = [];
|
|
5544
|
+
let index = startIndex;
|
|
5545
|
+
let lastToolOffset = -1;
|
|
5546
|
+
while (true) {
|
|
5547
|
+
const candidate = parts[index];
|
|
5548
|
+
if (!isGroupableToolPart(candidate)) break;
|
|
5549
|
+
candidateParts.push(candidate);
|
|
5550
|
+
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5551
|
+
index += 1;
|
|
5552
|
+
}
|
|
5553
|
+
return {
|
|
5554
|
+
candidateParts,
|
|
5555
|
+
lastToolOffset
|
|
5556
|
+
};
|
|
5557
|
+
}
|
|
5558
|
+
/** Groups tool cards across reasoning; content and interactive panel apps remain stable boundaries. */
|
|
5329
5559
|
function groupConsecutiveToolParts(parts, labels) {
|
|
5330
5560
|
const blocks = [];
|
|
5331
5561
|
let index = 0;
|
|
5332
5562
|
while (index < parts.length) {
|
|
5333
5563
|
const part = parts[index];
|
|
5334
|
-
if (part.type !== "tool-card") {
|
|
5564
|
+
if (part.type !== "tool-card" || part.card.panelApp) {
|
|
5335
5565
|
blocks.push({
|
|
5336
5566
|
kind: "part",
|
|
5337
5567
|
key: `part-${index}`,
|
|
@@ -5342,14 +5572,7 @@ function groupConsecutiveToolParts(parts, labels) {
|
|
|
5342
5572
|
continue;
|
|
5343
5573
|
}
|
|
5344
5574
|
const startIndex = index;
|
|
5345
|
-
const candidateParts =
|
|
5346
|
-
let lastToolOffset = -1;
|
|
5347
|
-
while (index < parts.length && (parts[index]?.type === "tool-card" || parts[index]?.type === "reasoning")) {
|
|
5348
|
-
const candidate = parts[index];
|
|
5349
|
-
candidateParts.push(candidate);
|
|
5350
|
-
if (candidate.type === "tool-card") lastToolOffset = candidateParts.length - 1;
|
|
5351
|
-
index += 1;
|
|
5352
|
-
}
|
|
5575
|
+
const { candidateParts, lastToolOffset } = collectToolGroupParts(parts, startIndex);
|
|
5353
5576
|
const toolParts = candidateParts.filter(isToolCardPart);
|
|
5354
5577
|
if (toolParts.length === 1) {
|
|
5355
5578
|
blocks.push({
|
|
@@ -5464,6 +5687,7 @@ function renderChatMessagePart({ index, isInProgress, isLastPart, isUser, onAtta
|
|
|
5464
5687
|
text,
|
|
5465
5688
|
role,
|
|
5466
5689
|
texts,
|
|
5690
|
+
isStreaming: isInProgress && isLastPart,
|
|
5467
5691
|
inlineTokens,
|
|
5468
5692
|
onFileOpen,
|
|
5469
5693
|
onInlineTokenClick,
|
|
@@ -5666,14 +5890,25 @@ function ChatMessageActionCopy({ message, texts }) {
|
|
|
5666
5890
|
}).filter((text) => !!text && text.trim().length > 0).join("\n\n");
|
|
5667
5891
|
}, [message.parts]);
|
|
5668
5892
|
const { copied, copy } = useCopyFeedback({ text: messageText });
|
|
5893
|
+
const label = copied ? texts.copiedMessageLabel : texts.copyMessageLabel;
|
|
5894
|
+
const { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } = ChatUiPrimitives;
|
|
5669
5895
|
if (!messageText) return null;
|
|
5670
|
-
return /* @__PURE__ */ jsx(
|
|
5671
|
-
|
|
5672
|
-
|
|
5673
|
-
|
|
5674
|
-
|
|
5675
|
-
|
|
5676
|
-
|
|
5896
|
+
return /* @__PURE__ */ jsx(TooltipProvider, {
|
|
5897
|
+
delayDuration: 250,
|
|
5898
|
+
children: /* @__PURE__ */ jsxs(Tooltip, { children: [/* @__PURE__ */ jsx(TooltipTrigger, {
|
|
5899
|
+
asChild: true,
|
|
5900
|
+
children: /* @__PURE__ */ jsx("button", {
|
|
5901
|
+
type: "button",
|
|
5902
|
+
onClick: () => void copy(),
|
|
5903
|
+
className: "flex items-center justify-center rounded-md p-1 text-muted-foreground transition-colors hover:bg-accent hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/35",
|
|
5904
|
+
"aria-label": label,
|
|
5905
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "h-3.5 w-3.5" }) : /* @__PURE__ */ jsx(Copy, { className: "h-3.5 w-3.5" })
|
|
5906
|
+
})
|
|
5907
|
+
}), /* @__PURE__ */ jsx(TooltipContent, {
|
|
5908
|
+
side: "top",
|
|
5909
|
+
className: "text-xs",
|
|
5910
|
+
children: label
|
|
5911
|
+
})] })
|
|
5677
5912
|
});
|
|
5678
5913
|
}
|
|
5679
5914
|
//#endregion
|
|
@@ -5824,10 +6059,10 @@ function ChatMessageList({ className, isSending, layout = "card", messages, onAt
|
|
|
5824
6059
|
" · ",
|
|
5825
6060
|
message.timestampLabel
|
|
5826
6061
|
]
|
|
5827
|
-
}),
|
|
6062
|
+
}), /* @__PURE__ */ jsx(ChatMessageActionCopy, {
|
|
5828
6063
|
message,
|
|
5829
6064
|
texts
|
|
5830
|
-
})
|
|
6065
|
+
})] })
|
|
5831
6066
|
})]
|
|
5832
6067
|
}),
|
|
5833
6068
|
isUser ? /* @__PURE__ */ jsx(ChatMessageAvatar, { role: message.role }) : null
|