@mastra/playground-ui 49.0.0 → 49.1.0-alpha.1
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/CHANGELOG.md +55 -0
- package/dist/components/MainSidebar.cjs.js +2 -1
- package/dist/components/MainSidebar.cjs.js.map +1 -1
- package/dist/components/MainSidebar.es.js +2 -1
- package/dist/components/MainSidebar.es.js.map +1 -1
- package/dist/components/MarkdownRenderer.cjs.js +1 -1
- package/dist/components/MarkdownRenderer.es.js +1 -1
- package/dist/components/ai/plan.cjs.js +1 -1
- package/dist/components/ai/plan.es.js +1 -1
- package/dist/components/ai/task-list.cjs.js +49 -34
- package/dist/components/ai/task-list.cjs.js.map +1 -1
- package/dist/components/ai/task-list.es.js +50 -34
- package/dist/components/ai/task-list.es.js.map +1 -1
- package/dist/markdown-renderer-BU7tImQt.cjs +280 -0
- package/dist/markdown-renderer-BU7tImQt.cjs.map +1 -0
- package/dist/markdown-renderer-Bi4HR-Me.js +278 -0
- package/dist/markdown-renderer-Bi4HR-Me.js.map +1 -0
- package/dist/markdown-renderer.css +7 -21
- package/dist/src/ds/components/MainSidebar/main-sidebar-nav-item-classes.d.ts +1 -1
- package/dist/src/ds/components/MarkdownRenderer/arriving.d.ts +19 -0
- package/dist/src/ds/components/MarkdownRenderer/markdown-renderer.d.ts +7 -1
- package/dist/src/ds/components/MarkdownRenderer/markdown-renderer.stories.d.ts +2 -0
- package/dist/src/ds/components/MarkdownRenderer/use-reveal.d.ts +22 -0
- package/dist/src/ds/components/ai/task-list/task-list.d.ts +3 -9
- package/dist/style.css +9 -465
- package/package.json +7 -7
- package/dist/markdown-renderer-BDe_1JLH.js +0 -210
- package/dist/markdown-renderer-BDe_1JLH.js.map +0 -1
- package/dist/markdown-renderer-WHy5Cyu2.cjs +0 -212
- package/dist/markdown-renderer-WHy5Cyu2.cjs.map +0 -1
- package/dist/src/ds/components/MarkdownRenderer/word-spans.d.ts +0 -26
|
@@ -1,210 +0,0 @@
|
|
|
1
|
-
import { jsx } from 'react/jsx-runtime';
|
|
2
|
-
import { memo } from 'react';
|
|
3
|
-
import Markdown from 'react-markdown';
|
|
4
|
-
import remarkGfm from 'remark-gfm';
|
|
5
|
-
import remend from 'remend';
|
|
6
|
-
import { C as CodeBlock } from './code-block-V4wpmbud.js';
|
|
7
|
-
import { c as cn } from './cn-BakJzhuv.js';
|
|
8
|
-
|
|
9
|
-
import './markdown-renderer.css';const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
|
|
10
|
-
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
11
|
-
const LIST_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
12
|
-
const INDENTED = /^(?:\t| {2,})/;
|
|
13
|
-
const DEFINITION = /^ {0,3}\[[^\]]+\]:/;
|
|
14
|
-
function fenceOpenedBy(line) {
|
|
15
|
-
return FENCE_OPEN.exec(line)?.[1];
|
|
16
|
-
}
|
|
17
|
-
function closesFence(line, marker) {
|
|
18
|
-
return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;
|
|
19
|
-
}
|
|
20
|
-
function startsBlock(line, inList) {
|
|
21
|
-
if (INDENTED.test(line)) return false;
|
|
22
|
-
return !(inList && LIST_ITEM.test(line));
|
|
23
|
-
}
|
|
24
|
-
function splitBlocks(markdown) {
|
|
25
|
-
const lines = markdown.split("\n");
|
|
26
|
-
const starts = [0];
|
|
27
|
-
let fence;
|
|
28
|
-
let started = false;
|
|
29
|
-
let broken = false;
|
|
30
|
-
let inList = false;
|
|
31
|
-
for (const [index, line] of lines.entries()) {
|
|
32
|
-
if (fence) {
|
|
33
|
-
if (closesFence(line, fence)) fence = void 0;
|
|
34
|
-
continue;
|
|
35
|
-
}
|
|
36
|
-
if (line.trim() === "") {
|
|
37
|
-
broken = started;
|
|
38
|
-
continue;
|
|
39
|
-
}
|
|
40
|
-
if (DEFINITION.test(line)) return [markdown];
|
|
41
|
-
if (broken && startsBlock(line, inList)) {
|
|
42
|
-
starts.push(index);
|
|
43
|
-
started = false;
|
|
44
|
-
}
|
|
45
|
-
if (!started) inList = LIST_ITEM.test(line);
|
|
46
|
-
started = true;
|
|
47
|
-
broken = false;
|
|
48
|
-
fence = fenceOpenedBy(line);
|
|
49
|
-
}
|
|
50
|
-
return starts.map((start, position) => {
|
|
51
|
-
const end = starts[position + 1];
|
|
52
|
-
const block = lines.slice(start, end ?? lines.length).join("\n");
|
|
53
|
-
return end === void 0 ? block : `${block}
|
|
54
|
-
`;
|
|
55
|
-
});
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
const OPAQUE_TAGS = /* @__PURE__ */ new Set(["code", "pre"]);
|
|
59
|
-
const ANIMATED_TAIL_CHARS = 120;
|
|
60
|
-
function textOf(nodes) {
|
|
61
|
-
let text = "";
|
|
62
|
-
for (const node of nodes) {
|
|
63
|
-
if (node.type === "text") text += node.value;
|
|
64
|
-
else if (node.type === "element") text += textOf(node.children);
|
|
65
|
-
}
|
|
66
|
-
return text;
|
|
67
|
-
}
|
|
68
|
-
function rehypeWordSpans({ complete = false } = {}) {
|
|
69
|
-
return (tree) => {
|
|
70
|
-
const reply = textOf(tree.children);
|
|
71
|
-
const total = reply.length;
|
|
72
|
-
const animateFrom = total - ANIMATED_TAIL_CHARS;
|
|
73
|
-
const pendingFrom = complete ? Number.POSITIVE_INFINITY : total - (/\S*$/.exec(reply)?.[0].length ?? 0);
|
|
74
|
-
let offset = 0;
|
|
75
|
-
const wordProperties = (startsAt, endsAt) => {
|
|
76
|
-
if (startsAt >= pendingFrom) return { className: ["mastra-markdown-word-pending"] };
|
|
77
|
-
if (endsAt > animateFrom) return { className: ["mastra-markdown-word"] };
|
|
78
|
-
return {};
|
|
79
|
-
};
|
|
80
|
-
const wordSpans = (value) => value.split(/(\s+)/).flatMap((chunk) => {
|
|
81
|
-
if (!chunk) return [];
|
|
82
|
-
const startsAt = offset;
|
|
83
|
-
offset += chunk.length;
|
|
84
|
-
if (/^\s+$/.test(chunk)) return [{ type: "text", value: chunk }];
|
|
85
|
-
return [
|
|
86
|
-
{
|
|
87
|
-
type: "element",
|
|
88
|
-
tagName: "span",
|
|
89
|
-
properties: wordProperties(startsAt, offset),
|
|
90
|
-
children: [{ type: "text", value: chunk }]
|
|
91
|
-
}
|
|
92
|
-
];
|
|
93
|
-
});
|
|
94
|
-
const wrapNodes = (nodes) => nodes.flatMap((node) => {
|
|
95
|
-
if (node.type === "text") return wordSpans(node.value);
|
|
96
|
-
if (node.type === "element") {
|
|
97
|
-
if (OPAQUE_TAGS.has(node.tagName)) offset += textOf(node.children).length;
|
|
98
|
-
else node.children = wrapNodes(node.children);
|
|
99
|
-
}
|
|
100
|
-
return [node];
|
|
101
|
-
});
|
|
102
|
-
tree.children = wrapNodes(tree.children);
|
|
103
|
-
};
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
const MarkdownRenderer = memo(function MarkdownRenderer2({
|
|
107
|
-
children,
|
|
108
|
-
className,
|
|
109
|
-
externalLinkTarget = "tab",
|
|
110
|
-
streaming = false
|
|
111
|
-
}) {
|
|
112
|
-
const blocks = splitBlocks(decodeEscapedNewlines(children));
|
|
113
|
-
const last = blocks.length - 1;
|
|
114
|
-
const components = externalLinkTarget === "window" ? WINDOW_COMPONENTS : COMPONENTS;
|
|
115
|
-
return /* @__PURE__ */ jsx("div", { className: cn("mastra-markdown", streaming && "mastra-markdown-streaming", className), children: blocks.map((block, index) => {
|
|
116
|
-
const growing = streaming && index === last;
|
|
117
|
-
return /* @__PURE__ */ jsx(
|
|
118
|
-
MarkdownBlock,
|
|
119
|
-
{
|
|
120
|
-
content: growing ? remend(block, REMEND_OPTIONS) : block,
|
|
121
|
-
rehypePlugins: wordSpansFor(streaming, growing),
|
|
122
|
-
components
|
|
123
|
-
},
|
|
124
|
-
index
|
|
125
|
-
);
|
|
126
|
-
}) });
|
|
127
|
-
});
|
|
128
|
-
const MarkdownBlock = memo(function MarkdownBlock2({
|
|
129
|
-
components,
|
|
130
|
-
content,
|
|
131
|
-
rehypePlugins
|
|
132
|
-
}) {
|
|
133
|
-
return /* @__PURE__ */ jsx(Markdown, { remarkPlugins: REMARK_PLUGINS, rehypePlugins, components, children: content });
|
|
134
|
-
});
|
|
135
|
-
const NO_WORD_SPANS = [];
|
|
136
|
-
const GROWING_WORD_SPANS = [[rehypeWordSpans, { complete: false }]];
|
|
137
|
-
const SETTLED_WORD_SPANS = [[rehypeWordSpans, { complete: true }]];
|
|
138
|
-
function wordSpansFor(streaming, growing) {
|
|
139
|
-
if (!streaming) return NO_WORD_SPANS;
|
|
140
|
-
return growing ? GROWING_WORD_SPANS : SETTLED_WORD_SPANS;
|
|
141
|
-
}
|
|
142
|
-
function decodeEscapedNewlines(text) {
|
|
143
|
-
return text.includes("\n") ? text : text.replace(/\\n/g, "\n");
|
|
144
|
-
}
|
|
145
|
-
function languageOf(node) {
|
|
146
|
-
const classNames = node.properties.className;
|
|
147
|
-
if (!Array.isArray(classNames)) return void 0;
|
|
148
|
-
const language = classNames.find((entry) => typeof entry === "string" && entry.startsWith("language-"));
|
|
149
|
-
return typeof language === "string" ? language.slice("language-".length) : void 0;
|
|
150
|
-
}
|
|
151
|
-
function fencedCode(node) {
|
|
152
|
-
const child = node?.children.find((entry) => entry.type === "element" && entry.tagName === "code");
|
|
153
|
-
if (child?.type !== "element") return void 0;
|
|
154
|
-
const code = child.children.map((entry) => entry.type === "text" ? entry.value : "").join("");
|
|
155
|
-
return { code: code.replace(/\n$/, ""), language: languageOf(child) };
|
|
156
|
-
}
|
|
157
|
-
function MarkdownCodeBlock({ node, children }) {
|
|
158
|
-
const fenced = fencedCode(node);
|
|
159
|
-
if (!fenced) return /* @__PURE__ */ jsx("pre", { children });
|
|
160
|
-
return /* @__PURE__ */ jsx(
|
|
161
|
-
CodeBlock,
|
|
162
|
-
{
|
|
163
|
-
code: fenced.code,
|
|
164
|
-
lang: fenced.language,
|
|
165
|
-
overflow: "scroll",
|
|
166
|
-
className: "bg-surface1 my-3",
|
|
167
|
-
copyMessage: "Copied code to clipboard"
|
|
168
|
-
}
|
|
169
|
-
);
|
|
170
|
-
}
|
|
171
|
-
const POPUP_WINDOW_FEATURES = "popup=yes,width=720,height=800,resizable=yes,scrollbars=yes";
|
|
172
|
-
function isPlainLeftClick(event) {
|
|
173
|
-
return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
174
|
-
}
|
|
175
|
-
function markdownLink(externalLinkTarget) {
|
|
176
|
-
return function MarkdownLink({ href, title, children }) {
|
|
177
|
-
const isExternal = /^https?:\/\//i.test(href ?? "");
|
|
178
|
-
const openInPopup = (event) => {
|
|
179
|
-
if (!isExternal || externalLinkTarget !== "window" || !isPlainLeftClick(event)) return;
|
|
180
|
-
const popup = window.open(href, "_blank", POPUP_WINDOW_FEATURES);
|
|
181
|
-
if (!popup) return;
|
|
182
|
-
popup.opener = null;
|
|
183
|
-
event.preventDefault();
|
|
184
|
-
};
|
|
185
|
-
return /* @__PURE__ */ jsx(
|
|
186
|
-
"a",
|
|
187
|
-
{
|
|
188
|
-
href,
|
|
189
|
-
title,
|
|
190
|
-
onClick: openInPopup,
|
|
191
|
-
target: isExternal ? "_blank" : void 0,
|
|
192
|
-
rel: isExternal ? "noopener noreferrer" : void 0,
|
|
193
|
-
children
|
|
194
|
-
}
|
|
195
|
-
);
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
const REMARK_PLUGINS = [remarkGfm];
|
|
199
|
-
const REMEND_OPTIONS = { katex: false, linkMode: "text-only" };
|
|
200
|
-
const COMPONENTS = {
|
|
201
|
-
pre: MarkdownCodeBlock,
|
|
202
|
-
a: markdownLink("tab")
|
|
203
|
-
};
|
|
204
|
-
const WINDOW_COMPONENTS = {
|
|
205
|
-
...COMPONENTS,
|
|
206
|
-
a: markdownLink("window")
|
|
207
|
-
};
|
|
208
|
-
|
|
209
|
-
export { MarkdownRenderer as M };
|
|
210
|
-
//# sourceMappingURL=markdown-renderer-BDe_1JLH.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-renderer-BDe_1JLH.js","sources":["../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/word-spans.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import type { ExtraProps } from 'react-markdown';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Load-bearing: `MarkdownCodeBlock` reads a fence's source from its direct text children. */\nconst OPAQUE_TAGS = new Set(['code', 'pre']);\n\n/** Further than this from the end of the reply, a word was already on screen. */\nconst ANIMATED_TAIL_CHARS = 120;\n\nfunction textOf(nodes: MarkdownChild[]): string {\n let text = '';\n\n for (const node of nodes) {\n if (node.type === 'text') text += node.value;\n else if (node.type === 'element') text += textOf(node.children);\n }\n\n return text;\n}\n\n/**\n * One span per word so the fade stays pure CSS: an animation runs when an\n * element mounts, and again when a class hands it an animation-name it did not\n * have. Settled words keep their span — dropping the wrapper would reshape the\n * child list and re-key every sibling after it.\n *\n * The word still being typed is held invisible instead of faded: React reuses\n * its span as characters land, so a mount animation would never cover them.\n * Marking it complete swaps the class, which starts the fade on that same span.\n * Emphasis splits such a word across nodes (`Hel**lo**`), so its boundary comes\n * from the whole text, not from each text node. Only the block still growing has\n * one at all — `complete` lets the settled ones reveal their last word.\n *\n * Only the tail is marked. A restructure — a paragraph becoming a list item\n * once the next character lands — remounts the subtree, so the window bounds\n * that re-fade rather than removing it.\n */\nexport function rehypeWordSpans({ complete = false }: { complete?: boolean } = {}) {\n return (tree: { children: MarkdownChild[] }) => {\n const reply = textOf(tree.children);\n const total = reply.length;\n const animateFrom = total - ANIMATED_TAIL_CHARS;\n const pendingFrom = complete ? Number.POSITIVE_INFINITY : total - (/\\S*$/.exec(reply)?.[0].length ?? 0);\n let offset = 0;\n\n const wordProperties = (startsAt: number, endsAt: number) => {\n if (startsAt >= pendingFrom) return { className: ['mastra-markdown-word-pending'] };\n if (endsAt > animateFrom) return { className: ['mastra-markdown-word'] };\n return {};\n };\n\n const wordSpans = (value: string): MarkdownChild[] =>\n value.split(/(\\s+)/).flatMap<MarkdownChild>(chunk => {\n if (!chunk) return [];\n\n const startsAt = offset;\n offset += chunk.length;\n if (/^\\s+$/.test(chunk)) return [{ type: 'text', value: chunk }];\n\n return [\n {\n type: 'element',\n tagName: 'span',\n properties: wordProperties(startsAt, offset),\n children: [{ type: 'text', value: chunk }],\n },\n ];\n });\n\n const wrapNodes = (nodes: MarkdownChild[]): MarkdownChild[] =>\n nodes.flatMap<MarkdownChild>(node => {\n if (node.type === 'text') return wordSpans(node.value);\n\n if (node.type === 'element') {\n if (OPAQUE_TAGS.has(node.tagName)) offset += textOf(node.children).length;\n else node.children = wrapNodes(node.children);\n }\n\n return [node];\n });\n\n tree.children = wrapNodes(tree.children);\n };\n}\n","import { memo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { splitBlocks } from './blocks';\nimport { rehypeWordSpans } from './word-spans';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is still being written: fade each word in as it lands. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const blocks = splitBlocks(decodeEscapedNewlines(children));\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n return (\n <div className={cn('mastra-markdown', streaming && 'mastra-markdown-streaming', className)}>\n {blocks.map((block, index) => {\n const growing = streaming && index === last;\n\n return (\n <MarkdownBlock\n key={index}\n content={growing ? remend(block, REMEND_OPTIONS) : block}\n rehypePlugins={wordSpansFor(streaming, growing)}\n components={components}\n />\n );\n })}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n rehypePlugins,\n}: {\n components: Components;\n content: string;\n rehypePlugins: Options['rehypePlugins'];\n}) {\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst NO_WORD_SPANS: Options['rehypePlugins'] = [];\nconst GROWING_WORD_SPANS: Options['rehypePlugins'] = [[rehypeWordSpans, { complete: false }]];\nconst SETTLED_WORD_SPANS: Options['rehypePlugins'] = [[rehypeWordSpans, { complete: true }]];\n\n/** Module constants, so a block that keeps its plugins keeps its memo too. */\nfunction wordSpansFor(streaming: boolean, growing: boolean): Options['rehypePlugins'] {\n if (!streaming) return NO_WORD_SPANS;\n\n return growing ? GROWING_WORD_SPANS : SETTLED_WORD_SPANS;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({ node, children }: { node?: MarkdownNode; children?: ReactNode }) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className=\"bg-surface1 my-3\"\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["MarkdownRenderer","MarkdownBlock"],"mappings":"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"}
|
|
@@ -1,212 +0,0 @@
|
|
|
1
|
-
'use strict';
|
|
2
|
-
|
|
3
|
-
require('./markdown-renderer.css');const jsxRuntime = require('react/jsx-runtime');
|
|
4
|
-
const React = require('react');
|
|
5
|
-
const Markdown = require('react-markdown');
|
|
6
|
-
const remarkGfm = require('remark-gfm');
|
|
7
|
-
const remend = require('remend');
|
|
8
|
-
const codeBlock = require('./code-block-BZUY8Fj9.cjs');
|
|
9
|
-
const utils_cn = require('./cn-vA5sWo9W.cjs');
|
|
10
|
-
|
|
11
|
-
const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;
|
|
12
|
-
const FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \t]*$/;
|
|
13
|
-
const LIST_ITEM = /^ {0,3}(?:[-*+]|\d{1,9}[.)])(?:[ \t]|$)/;
|
|
14
|
-
const INDENTED = /^(?:\t| {2,})/;
|
|
15
|
-
const DEFINITION = /^ {0,3}\[[^\]]+\]:/;
|
|
16
|
-
function fenceOpenedBy(line) {
|
|
17
|
-
return FENCE_OPEN.exec(line)?.[1];
|
|
18
|
-
}
|
|
19
|
-
function closesFence(line, marker) {
|
|
20
|
-
return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;
|
|
21
|
-
}
|
|
22
|
-
function startsBlock(line, inList) {
|
|
23
|
-
if (INDENTED.test(line)) return false;
|
|
24
|
-
return !(inList && LIST_ITEM.test(line));
|
|
25
|
-
}
|
|
26
|
-
function splitBlocks(markdown) {
|
|
27
|
-
const lines = markdown.split("\n");
|
|
28
|
-
const starts = [0];
|
|
29
|
-
let fence;
|
|
30
|
-
let started = false;
|
|
31
|
-
let broken = false;
|
|
32
|
-
let inList = false;
|
|
33
|
-
for (const [index, line] of lines.entries()) {
|
|
34
|
-
if (fence) {
|
|
35
|
-
if (closesFence(line, fence)) fence = void 0;
|
|
36
|
-
continue;
|
|
37
|
-
}
|
|
38
|
-
if (line.trim() === "") {
|
|
39
|
-
broken = started;
|
|
40
|
-
continue;
|
|
41
|
-
}
|
|
42
|
-
if (DEFINITION.test(line)) return [markdown];
|
|
43
|
-
if (broken && startsBlock(line, inList)) {
|
|
44
|
-
starts.push(index);
|
|
45
|
-
started = false;
|
|
46
|
-
}
|
|
47
|
-
if (!started) inList = LIST_ITEM.test(line);
|
|
48
|
-
started = true;
|
|
49
|
-
broken = false;
|
|
50
|
-
fence = fenceOpenedBy(line);
|
|
51
|
-
}
|
|
52
|
-
return starts.map((start, position) => {
|
|
53
|
-
const end = starts[position + 1];
|
|
54
|
-
const block = lines.slice(start, end ?? lines.length).join("\n");
|
|
55
|
-
return end === void 0 ? block : `${block}
|
|
56
|
-
`;
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
|
|
60
|
-
const OPAQUE_TAGS = /* @__PURE__ */ new Set(["code", "pre"]);
|
|
61
|
-
const ANIMATED_TAIL_CHARS = 120;
|
|
62
|
-
function textOf(nodes) {
|
|
63
|
-
let text = "";
|
|
64
|
-
for (const node of nodes) {
|
|
65
|
-
if (node.type === "text") text += node.value;
|
|
66
|
-
else if (node.type === "element") text += textOf(node.children);
|
|
67
|
-
}
|
|
68
|
-
return text;
|
|
69
|
-
}
|
|
70
|
-
function rehypeWordSpans({ complete = false } = {}) {
|
|
71
|
-
return (tree) => {
|
|
72
|
-
const reply = textOf(tree.children);
|
|
73
|
-
const total = reply.length;
|
|
74
|
-
const animateFrom = total - ANIMATED_TAIL_CHARS;
|
|
75
|
-
const pendingFrom = complete ? Number.POSITIVE_INFINITY : total - (/\S*$/.exec(reply)?.[0].length ?? 0);
|
|
76
|
-
let offset = 0;
|
|
77
|
-
const wordProperties = (startsAt, endsAt) => {
|
|
78
|
-
if (startsAt >= pendingFrom) return { className: ["mastra-markdown-word-pending"] };
|
|
79
|
-
if (endsAt > animateFrom) return { className: ["mastra-markdown-word"] };
|
|
80
|
-
return {};
|
|
81
|
-
};
|
|
82
|
-
const wordSpans = (value) => value.split(/(\s+)/).flatMap((chunk) => {
|
|
83
|
-
if (!chunk) return [];
|
|
84
|
-
const startsAt = offset;
|
|
85
|
-
offset += chunk.length;
|
|
86
|
-
if (/^\s+$/.test(chunk)) return [{ type: "text", value: chunk }];
|
|
87
|
-
return [
|
|
88
|
-
{
|
|
89
|
-
type: "element",
|
|
90
|
-
tagName: "span",
|
|
91
|
-
properties: wordProperties(startsAt, offset),
|
|
92
|
-
children: [{ type: "text", value: chunk }]
|
|
93
|
-
}
|
|
94
|
-
];
|
|
95
|
-
});
|
|
96
|
-
const wrapNodes = (nodes) => nodes.flatMap((node) => {
|
|
97
|
-
if (node.type === "text") return wordSpans(node.value);
|
|
98
|
-
if (node.type === "element") {
|
|
99
|
-
if (OPAQUE_TAGS.has(node.tagName)) offset += textOf(node.children).length;
|
|
100
|
-
else node.children = wrapNodes(node.children);
|
|
101
|
-
}
|
|
102
|
-
return [node];
|
|
103
|
-
});
|
|
104
|
-
tree.children = wrapNodes(tree.children);
|
|
105
|
-
};
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
const MarkdownRenderer = React.memo(function MarkdownRenderer2({
|
|
109
|
-
children,
|
|
110
|
-
className,
|
|
111
|
-
externalLinkTarget = "tab",
|
|
112
|
-
streaming = false
|
|
113
|
-
}) {
|
|
114
|
-
const blocks = splitBlocks(decodeEscapedNewlines(children));
|
|
115
|
-
const last = blocks.length - 1;
|
|
116
|
-
const components = externalLinkTarget === "window" ? WINDOW_COMPONENTS : COMPONENTS;
|
|
117
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: utils_cn.cn("mastra-markdown", streaming && "mastra-markdown-streaming", className), children: blocks.map((block, index) => {
|
|
118
|
-
const growing = streaming && index === last;
|
|
119
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
120
|
-
MarkdownBlock,
|
|
121
|
-
{
|
|
122
|
-
content: growing ? remend(block, REMEND_OPTIONS) : block,
|
|
123
|
-
rehypePlugins: wordSpansFor(streaming, growing),
|
|
124
|
-
components
|
|
125
|
-
},
|
|
126
|
-
index
|
|
127
|
-
);
|
|
128
|
-
}) });
|
|
129
|
-
});
|
|
130
|
-
const MarkdownBlock = React.memo(function MarkdownBlock2({
|
|
131
|
-
components,
|
|
132
|
-
content,
|
|
133
|
-
rehypePlugins
|
|
134
|
-
}) {
|
|
135
|
-
return /* @__PURE__ */ jsxRuntime.jsx(Markdown, { remarkPlugins: REMARK_PLUGINS, rehypePlugins, components, children: content });
|
|
136
|
-
});
|
|
137
|
-
const NO_WORD_SPANS = [];
|
|
138
|
-
const GROWING_WORD_SPANS = [[rehypeWordSpans, { complete: false }]];
|
|
139
|
-
const SETTLED_WORD_SPANS = [[rehypeWordSpans, { complete: true }]];
|
|
140
|
-
function wordSpansFor(streaming, growing) {
|
|
141
|
-
if (!streaming) return NO_WORD_SPANS;
|
|
142
|
-
return growing ? GROWING_WORD_SPANS : SETTLED_WORD_SPANS;
|
|
143
|
-
}
|
|
144
|
-
function decodeEscapedNewlines(text) {
|
|
145
|
-
return text.includes("\n") ? text : text.replace(/\\n/g, "\n");
|
|
146
|
-
}
|
|
147
|
-
function languageOf(node) {
|
|
148
|
-
const classNames = node.properties.className;
|
|
149
|
-
if (!Array.isArray(classNames)) return void 0;
|
|
150
|
-
const language = classNames.find((entry) => typeof entry === "string" && entry.startsWith("language-"));
|
|
151
|
-
return typeof language === "string" ? language.slice("language-".length) : void 0;
|
|
152
|
-
}
|
|
153
|
-
function fencedCode(node) {
|
|
154
|
-
const child = node?.children.find((entry) => entry.type === "element" && entry.tagName === "code");
|
|
155
|
-
if (child?.type !== "element") return void 0;
|
|
156
|
-
const code = child.children.map((entry) => entry.type === "text" ? entry.value : "").join("");
|
|
157
|
-
return { code: code.replace(/\n$/, ""), language: languageOf(child) };
|
|
158
|
-
}
|
|
159
|
-
function MarkdownCodeBlock({ node, children }) {
|
|
160
|
-
const fenced = fencedCode(node);
|
|
161
|
-
if (!fenced) return /* @__PURE__ */ jsxRuntime.jsx("pre", { children });
|
|
162
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
163
|
-
codeBlock.CodeBlock,
|
|
164
|
-
{
|
|
165
|
-
code: fenced.code,
|
|
166
|
-
lang: fenced.language,
|
|
167
|
-
overflow: "scroll",
|
|
168
|
-
className: "bg-surface1 my-3",
|
|
169
|
-
copyMessage: "Copied code to clipboard"
|
|
170
|
-
}
|
|
171
|
-
);
|
|
172
|
-
}
|
|
173
|
-
const POPUP_WINDOW_FEATURES = "popup=yes,width=720,height=800,resizable=yes,scrollbars=yes";
|
|
174
|
-
function isPlainLeftClick(event) {
|
|
175
|
-
return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;
|
|
176
|
-
}
|
|
177
|
-
function markdownLink(externalLinkTarget) {
|
|
178
|
-
return function MarkdownLink({ href, title, children }) {
|
|
179
|
-
const isExternal = /^https?:\/\//i.test(href ?? "");
|
|
180
|
-
const openInPopup = (event) => {
|
|
181
|
-
if (!isExternal || externalLinkTarget !== "window" || !isPlainLeftClick(event)) return;
|
|
182
|
-
const popup = window.open(href, "_blank", POPUP_WINDOW_FEATURES);
|
|
183
|
-
if (!popup) return;
|
|
184
|
-
popup.opener = null;
|
|
185
|
-
event.preventDefault();
|
|
186
|
-
};
|
|
187
|
-
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
188
|
-
"a",
|
|
189
|
-
{
|
|
190
|
-
href,
|
|
191
|
-
title,
|
|
192
|
-
onClick: openInPopup,
|
|
193
|
-
target: isExternal ? "_blank" : void 0,
|
|
194
|
-
rel: isExternal ? "noopener noreferrer" : void 0,
|
|
195
|
-
children
|
|
196
|
-
}
|
|
197
|
-
);
|
|
198
|
-
};
|
|
199
|
-
}
|
|
200
|
-
const REMARK_PLUGINS = [remarkGfm];
|
|
201
|
-
const REMEND_OPTIONS = { katex: false, linkMode: "text-only" };
|
|
202
|
-
const COMPONENTS = {
|
|
203
|
-
pre: MarkdownCodeBlock,
|
|
204
|
-
a: markdownLink("tab")
|
|
205
|
-
};
|
|
206
|
-
const WINDOW_COMPONENTS = {
|
|
207
|
-
...COMPONENTS,
|
|
208
|
-
a: markdownLink("window")
|
|
209
|
-
};
|
|
210
|
-
|
|
211
|
-
exports.MarkdownRenderer = MarkdownRenderer;
|
|
212
|
-
//# sourceMappingURL=markdown-renderer-WHy5Cyu2.cjs.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-renderer-WHy5Cyu2.cjs","sources":["../src/ds/components/MarkdownRenderer/blocks.ts","../src/ds/components/MarkdownRenderer/word-spans.ts","../src/ds/components/MarkdownRenderer/markdown-renderer.tsx"],"sourcesContent":["const FENCE_OPEN = /^ {0,3}(`{3,}|~{3,})/;\nconst FENCE_CLOSE = /^ {0,3}(`{3,}|~{3,})[ \\t]*$/;\nconst LIST_ITEM = /^ {0,3}(?:[-*+]|\\d{1,9}[.)])(?:[ \\t]|$)/;\nconst INDENTED = /^(?:\\t| {2,})/;\nconst DEFINITION = /^ {0,3}\\[[^\\]]+\\]:/;\n\nfunction fenceOpenedBy(line: string): string | undefined {\n return FENCE_OPEN.exec(line)?.[1];\n}\n\n// Both are runs of one character, so `startsWith` is \"same marker, at least as long\".\nfunction closesFence(line: string, marker: string): boolean {\n return FENCE_CLOSE.exec(line)?.[1]?.startsWith(marker) ?? false;\n}\n\nfunction startsBlock(line: string, inList: boolean): boolean {\n if (INDENTED.test(line)) return false;\n\n return !(inList && LIST_ITEM.test(line));\n}\n\n/**\n * Cuts markdown into top-level blocks so a growing reply re-parses its last\n * block instead of all of them. Rejoining the result returns the input, and\n * every block renders as it would have inside the whole.\n *\n * Each rule only ever merges: over-merging costs a re-parse, while cutting a\n * construct in two changes what it renders as. Link reference and footnote\n * definitions resolve across blank lines, which no line-level rule can see, so\n * reaching one outside a fence abandons the split for the whole text.\n */\nexport function splitBlocks(markdown: string): string[] {\n const lines = markdown.split('\\n');\n const starts = [0];\n let fence: string | undefined;\n let started = false;\n let broken = false;\n let inList = false;\n\n for (const [index, line] of lines.entries()) {\n if (fence) {\n if (closesFence(line, fence)) fence = undefined;\n continue;\n }\n\n if (line.trim() === '') {\n broken = started;\n continue;\n }\n\n if (DEFINITION.test(line)) return [markdown];\n\n if (broken && startsBlock(line, inList)) {\n starts.push(index);\n started = false;\n }\n\n if (!started) inList = LIST_ITEM.test(line);\n\n started = true;\n broken = false;\n fence = fenceOpenedBy(line);\n }\n\n return starts.map((start, position) => {\n const end = starts[position + 1];\n const block = lines.slice(start, end ?? lines.length).join('\\n');\n\n return end === undefined ? block : `${block}\\n`;\n });\n}\n","import type { ExtraProps } from 'react-markdown';\n\ntype MarkdownElement = NonNullable<ExtraProps['node']>;\ntype MarkdownChild = MarkdownElement['children'][number];\n\n/** Load-bearing: `MarkdownCodeBlock` reads a fence's source from its direct text children. */\nconst OPAQUE_TAGS = new Set(['code', 'pre']);\n\n/** Further than this from the end of the reply, a word was already on screen. */\nconst ANIMATED_TAIL_CHARS = 120;\n\nfunction textOf(nodes: MarkdownChild[]): string {\n let text = '';\n\n for (const node of nodes) {\n if (node.type === 'text') text += node.value;\n else if (node.type === 'element') text += textOf(node.children);\n }\n\n return text;\n}\n\n/**\n * One span per word so the fade stays pure CSS: an animation runs when an\n * element mounts, and again when a class hands it an animation-name it did not\n * have. Settled words keep their span — dropping the wrapper would reshape the\n * child list and re-key every sibling after it.\n *\n * The word still being typed is held invisible instead of faded: React reuses\n * its span as characters land, so a mount animation would never cover them.\n * Marking it complete swaps the class, which starts the fade on that same span.\n * Emphasis splits such a word across nodes (`Hel**lo**`), so its boundary comes\n * from the whole text, not from each text node. Only the block still growing has\n * one at all — `complete` lets the settled ones reveal their last word.\n *\n * Only the tail is marked. A restructure — a paragraph becoming a list item\n * once the next character lands — remounts the subtree, so the window bounds\n * that re-fade rather than removing it.\n */\nexport function rehypeWordSpans({ complete = false }: { complete?: boolean } = {}) {\n return (tree: { children: MarkdownChild[] }) => {\n const reply = textOf(tree.children);\n const total = reply.length;\n const animateFrom = total - ANIMATED_TAIL_CHARS;\n const pendingFrom = complete ? Number.POSITIVE_INFINITY : total - (/\\S*$/.exec(reply)?.[0].length ?? 0);\n let offset = 0;\n\n const wordProperties = (startsAt: number, endsAt: number) => {\n if (startsAt >= pendingFrom) return { className: ['mastra-markdown-word-pending'] };\n if (endsAt > animateFrom) return { className: ['mastra-markdown-word'] };\n return {};\n };\n\n const wordSpans = (value: string): MarkdownChild[] =>\n value.split(/(\\s+)/).flatMap<MarkdownChild>(chunk => {\n if (!chunk) return [];\n\n const startsAt = offset;\n offset += chunk.length;\n if (/^\\s+$/.test(chunk)) return [{ type: 'text', value: chunk }];\n\n return [\n {\n type: 'element',\n tagName: 'span',\n properties: wordProperties(startsAt, offset),\n children: [{ type: 'text', value: chunk }],\n },\n ];\n });\n\n const wrapNodes = (nodes: MarkdownChild[]): MarkdownChild[] =>\n nodes.flatMap<MarkdownChild>(node => {\n if (node.type === 'text') return wordSpans(node.value);\n\n if (node.type === 'element') {\n if (OPAQUE_TAGS.has(node.tagName)) offset += textOf(node.children).length;\n else node.children = wrapNodes(node.children);\n }\n\n return [node];\n });\n\n tree.children = wrapNodes(tree.children);\n };\n}\n","import { memo } from 'react';\nimport type { MouseEvent, MouseEventHandler, ReactNode } from 'react';\nimport Markdown from 'react-markdown';\nimport type { Components, ExtraProps, Options } from 'react-markdown';\nimport remarkGfm from 'remark-gfm';\nimport remend from 'remend';\n\nimport { splitBlocks } from './blocks';\nimport { rehypeWordSpans } from './word-spans';\nimport { CodeBlock } from '@/ds/components/CodeBlock';\nimport { cn } from '@/lib/utils';\n\nimport './markdown-renderer.css';\n\nexport type MarkdownExternalLinkTarget = 'tab' | 'window';\n\nexport interface MarkdownRendererProps {\n children: string;\n className?: string;\n externalLinkTarget?: MarkdownExternalLinkTarget;\n /** The text is still being written: fade each word in as it lands. */\n streaming?: boolean;\n}\n\n/**\n * Renders a markdown string. Agent output can carry attacker-influenced text\n * (file contents, tool output, web pages): react-markdown escapes raw HTML and\n * drops dangerous link schemes, so nothing here reaches the DOM as markup.\n *\n * react-markdown re-parses on every render, and a streaming reply re-renders\n * its whole transcript on every delta. Memoizing spares the settled messages;\n * rendering block by block — streaming or not — spares every block of the live\n * one but the last, and lets a reply settle without remounting what is already\n * on screen.\n */\nexport const MarkdownRenderer = memo(function MarkdownRenderer({\n children,\n className,\n externalLinkTarget = 'tab',\n streaming = false,\n}: MarkdownRendererProps) {\n const blocks = splitBlocks(decodeEscapedNewlines(children));\n const last = blocks.length - 1;\n const components = externalLinkTarget === 'window' ? WINDOW_COMPONENTS : COMPONENTS;\n\n return (\n <div className={cn('mastra-markdown', streaming && 'mastra-markdown-streaming', className)}>\n {blocks.map((block, index) => {\n const growing = streaming && index === last;\n\n return (\n <MarkdownBlock\n key={index}\n content={growing ? remend(block, REMEND_OPTIONS) : block}\n rehypePlugins={wordSpansFor(streaming, growing)}\n components={components}\n />\n );\n })}\n </div>\n );\n});\n\n/** Keyed by position at the call site: a content key remounts on every character. */\nconst MarkdownBlock = memo(function MarkdownBlock({\n components,\n content,\n rehypePlugins,\n}: {\n components: Components;\n content: string;\n rehypePlugins: Options['rehypePlugins'];\n}) {\n return (\n <Markdown remarkPlugins={REMARK_PLUGINS} rehypePlugins={rehypePlugins} components={components}>\n {content}\n </Markdown>\n );\n});\n\nconst NO_WORD_SPANS: Options['rehypePlugins'] = [];\nconst GROWING_WORD_SPANS: Options['rehypePlugins'] = [[rehypeWordSpans, { complete: false }]];\nconst SETTLED_WORD_SPANS: Options['rehypePlugins'] = [[rehypeWordSpans, { complete: true }]];\n\n/** Module constants, so a block that keeps its plugins keeps its memo too. */\nfunction wordSpansFor(streaming: boolean, growing: boolean): Options['rehypePlugins'] {\n if (!streaming) return NO_WORD_SPANS;\n\n return growing ? GROWING_WORD_SPANS : SETTLED_WORD_SPANS;\n}\n\n// Agent networks emit their text with literal `\\n`. Only unescape when the text\n// has no real newline, otherwise a `\"a\\nb\"` inside a code fence gets shredded.\nfunction decodeEscapedNewlines(text: string): string {\n return text.includes('\\n') ? text : text.replace(/\\\\n/g, '\\n');\n}\n\ntype MarkdownNode = NonNullable<ExtraProps['node']>;\n\nfunction languageOf(node: MarkdownNode): string | undefined {\n const classNames = node.properties.className;\n if (!Array.isArray(classNames)) return undefined;\n\n const language = classNames.find(entry => typeof entry === 'string' && entry.startsWith('language-'));\n return typeof language === 'string' ? language.slice('language-'.length) : undefined;\n}\n\nfunction fencedCode(node: MarkdownNode | undefined): { code: string; language?: string } | undefined {\n const child = node?.children.find(entry => entry.type === 'element' && entry.tagName === 'code');\n if (child?.type !== 'element') return undefined;\n\n const code = child.children.map(entry => (entry.type === 'text' ? entry.value : '')).join('');\n return { code: code.replace(/\\n$/, ''), language: languageOf(child) };\n}\n\nfunction MarkdownCodeBlock({ node, children }: { node?: MarkdownNode; children?: ReactNode }) {\n const fenced = fencedCode(node);\n if (!fenced) return <pre>{children}</pre>;\n\n return (\n <CodeBlock\n code={fenced.code}\n lang={fenced.language}\n overflow=\"scroll\"\n className=\"bg-surface1 my-3\"\n copyMessage=\"Copied code to clipboard\"\n />\n );\n}\n\nconst POPUP_WINDOW_FEATURES = 'popup=yes,width=720,height=800,resizable=yes,scrollbars=yes';\n\nfunction isPlainLeftClick(event: MouseEvent): boolean {\n return event.button === 0 && !event.metaKey && !event.ctrlKey && !event.shiftKey && !event.altKey;\n}\n\nfunction markdownLink(externalLinkTarget: MarkdownExternalLinkTarget): NonNullable<Components['a']> {\n return function MarkdownLink({ href, title, children }) {\n const isExternal = /^https?:\\/\\//i.test(href ?? '');\n\n const openInPopup: MouseEventHandler<HTMLAnchorElement> = event => {\n if (!isExternal || externalLinkTarget !== 'window' || !isPlainLeftClick(event)) return;\n\n const popup = window.open(href, '_blank', POPUP_WINDOW_FEATURES);\n if (!popup) return;\n\n popup.opener = null;\n event.preventDefault();\n };\n\n return (\n <a\n href={href}\n title={title}\n onClick={openInPopup}\n target={isExternal ? '_blank' : undefined}\n rel={isExternal ? 'noopener noreferrer' : undefined}\n >\n {children}\n </a>\n );\n };\n}\n\nconst REMARK_PLUGINS = [remarkGfm];\n\n// Links stay text until their URL lands: remend's placeholder href would render\n// a live anchor to nowhere. No math is rendered here, so pairing `$$` would only\n// turn one literal into another.\nconst REMEND_OPTIONS = { katex: false, linkMode: 'text-only' } as const;\n\n// Elements are listed one by one: react-markdown also passes its `node`, which\n// React would forward to the DOM as a stray attribute. Everything else is\n// styled from markdown-renderer.css.\nconst COMPONENTS: Components = {\n pre: MarkdownCodeBlock,\n a: markdownLink('tab'),\n};\n\nconst WINDOW_COMPONENTS: Components = {\n ...COMPONENTS,\n a: markdownLink('window'),\n};\n"],"names":["memo","MarkdownRenderer","jsx","cn","MarkdownBlock","CodeBlock"],"mappings":"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"}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { ExtraProps } from 'react-markdown';
|
|
2
|
-
type MarkdownElement = NonNullable<ExtraProps['node']>;
|
|
3
|
-
type MarkdownChild = MarkdownElement['children'][number];
|
|
4
|
-
/**
|
|
5
|
-
* One span per word so the fade stays pure CSS: an animation runs when an
|
|
6
|
-
* element mounts, and again when a class hands it an animation-name it did not
|
|
7
|
-
* have. Settled words keep their span — dropping the wrapper would reshape the
|
|
8
|
-
* child list and re-key every sibling after it.
|
|
9
|
-
*
|
|
10
|
-
* The word still being typed is held invisible instead of faded: React reuses
|
|
11
|
-
* its span as characters land, so a mount animation would never cover them.
|
|
12
|
-
* Marking it complete swaps the class, which starts the fade on that same span.
|
|
13
|
-
* Emphasis splits such a word across nodes (`Hel**lo**`), so its boundary comes
|
|
14
|
-
* from the whole text, not from each text node. Only the block still growing has
|
|
15
|
-
* one at all — `complete` lets the settled ones reveal their last word.
|
|
16
|
-
*
|
|
17
|
-
* Only the tail is marked. A restructure — a paragraph becoming a list item
|
|
18
|
-
* once the next character lands — remounts the subtree, so the window bounds
|
|
19
|
-
* that re-fade rather than removing it.
|
|
20
|
-
*/
|
|
21
|
-
export declare function rehypeWordSpans({ complete }?: {
|
|
22
|
-
complete?: boolean;
|
|
23
|
-
}): (tree: {
|
|
24
|
-
children: MarkdownChild[];
|
|
25
|
-
}) => void;
|
|
26
|
-
export {};
|