@dust-tt/sparkle 0.2.637 → 0.2.638-rc-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/dist/cjs/index.js +1 -1
- package/dist/esm/components/LoadingBlock.js +1 -1
- package/dist/esm/components/LoadingBlock.js.map +1 -1
- package/dist/esm/components/markdown/Markdown.d.ts +14 -0
- package/dist/esm/components/markdown/Markdown.d.ts.map +1 -1
- package/dist/esm/components/markdown/Markdown.js +18 -7
- package/dist/esm/components/markdown/Markdown.js.map +1 -1
- package/dist/esm/components/streaming/BlockStreamer.d.ts +8 -0
- package/dist/esm/components/streaming/BlockStreamer.d.ts.map +1 -0
- package/dist/esm/components/streaming/BlockStreamer.js +40 -0
- package/dist/esm/components/streaming/BlockStreamer.js.map +1 -0
- package/dist/esm/components/streaming/StreamingListItem.d.ts +14 -0
- package/dist/esm/components/streaming/StreamingListItem.d.ts.map +1 -0
- package/dist/esm/components/streaming/StreamingListItem.js +93 -0
- package/dist/esm/components/streaming/StreamingListItem.js.map +1 -0
- package/dist/esm/components/streaming/StreamingMarkdown.d.ts +5 -0
- package/dist/esm/components/streaming/StreamingMarkdown.d.ts.map +1 -0
- package/dist/esm/components/streaming/StreamingMarkdown.js +80 -0
- package/dist/esm/components/streaming/StreamingMarkdown.js.map +1 -0
- package/dist/esm/components/streaming/StreamingParagraph.d.ts +14 -0
- package/dist/esm/components/streaming/StreamingParagraph.d.ts.map +1 -0
- package/dist/esm/components/streaming/StreamingParagraph.js +41 -0
- package/dist/esm/components/streaming/StreamingParagraph.js.map +1 -0
- package/dist/esm/components/streaming/index.d.ts +3 -0
- package/dist/esm/components/streaming/index.d.ts.map +1 -0
- package/dist/esm/components/streaming/index.js +2 -0
- package/dist/esm/components/streaming/index.js.map +1 -0
- package/dist/esm/components/streaming/markdownComponents.d.ts +11 -0
- package/dist/esm/components/streaming/markdownComponents.d.ts.map +1 -0
- package/dist/esm/components/streaming/markdownComponents.js +85 -0
- package/dist/esm/components/streaming/markdownComponents.js.map +1 -0
- package/dist/esm/components/streaming/types.d.ts +70 -0
- package/dist/esm/components/streaming/types.d.ts.map +1 -0
- package/dist/esm/components/streaming/types.js +10 -0
- package/dist/esm/components/streaming/types.js.map +1 -0
- package/dist/esm/components/streaming/utils.d.ts +18 -0
- package/dist/esm/components/streaming/utils.d.ts.map +1 -0
- package/dist/esm/components/streaming/utils.js +97 -0
- package/dist/esm/components/streaming/utils.js.map +1 -0
- package/dist/esm/index.d.ts +1 -0
- package/dist/esm/index.d.ts.map +1 -1
- package/dist/esm/index.js +1 -0
- package/dist/esm/index.js.map +1 -1
- package/dist/esm/stories/StreamingMarkdown.stories.d.ts +12 -0
- package/dist/esm/stories/StreamingMarkdown.stories.d.ts.map +1 -0
- package/dist/esm/stories/StreamingMarkdown.stories.js +620 -0
- package/dist/esm/stories/StreamingMarkdown.stories.js.map +1 -0
- package/dist/esm/styles/global.css +43 -0
- package/dist/esm/styles/tailwind.css +43 -0
- package/dist/sparkle.css +56 -0
- package/package.json +2 -1
- package/src/components/LoadingBlock.tsx +1 -1
- package/src/components/markdown/Markdown.tsx +35 -12
- package/src/components/streaming/BlockStreamer.tsx +61 -0
- package/src/components/streaming/StreamingListItem.tsx +176 -0
- package/src/components/streaming/StreamingMarkdown.tsx +126 -0
- package/src/components/streaming/StreamingParagraph.tsx +104 -0
- package/src/components/streaming/index.ts +2 -0
- package/src/components/streaming/markdownComponents.tsx +270 -0
- package/src/components/streaming/types.ts +72 -0
- package/src/components/streaming/utils.ts +126 -0
- package/src/index.ts +1 -0
- package/src/stories/StreamingMarkdown.stories.tsx +1454 -0
- package/src/styles/global.css +43 -0
- package/src/styles/tailwind.css +43 -0
|
@@ -0,0 +1,1454 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from "@storybook/react";
|
|
2
|
+
import type { Root } from "mdast";
|
|
3
|
+
import React from "react";
|
|
4
|
+
import type { Components } from "react-markdown";
|
|
5
|
+
import { visit } from "unist-util-visit";
|
|
6
|
+
|
|
7
|
+
import { Markdown, StreamingMarkdown } from "../index_with_tw_base";
|
|
8
|
+
|
|
9
|
+
// Mock types and components for citations and mentions (only for Storybook demos)
|
|
10
|
+
type MarkdownCitation = {
|
|
11
|
+
href?: string;
|
|
12
|
+
title: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
type CitationsContextType = {
|
|
16
|
+
references: {
|
|
17
|
+
[key: string]: MarkdownCitation;
|
|
18
|
+
};
|
|
19
|
+
updateActiveReferences: (doc: MarkdownCitation, index: number) => void;
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
const CitationsContext = React.createContext<CitationsContextType>({
|
|
23
|
+
references: {},
|
|
24
|
+
updateActiveReferences: () => null,
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
// Mock CiteBlock component for Storybook
|
|
28
|
+
interface CiteBlockProps {
|
|
29
|
+
references?: string;
|
|
30
|
+
children?: React.ReactNode;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function CiteBlock(props: CiteBlockProps) {
|
|
34
|
+
const { references, updateActiveReferences } =
|
|
35
|
+
React.useContext(CitationsContext);
|
|
36
|
+
const refs = props.references
|
|
37
|
+
? (
|
|
38
|
+
JSON.parse(props.references) as { counter: number; ref: string }[]
|
|
39
|
+
).filter((r) => r.ref in references)
|
|
40
|
+
: undefined;
|
|
41
|
+
|
|
42
|
+
React.useEffect(() => {
|
|
43
|
+
if (refs) {
|
|
44
|
+
refs.forEach((r) => {
|
|
45
|
+
const document = references[r.ref];
|
|
46
|
+
updateActiveReferences(document, r.counter);
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
}, [refs, references, updateActiveReferences]);
|
|
50
|
+
|
|
51
|
+
if (!refs || refs.length === 0) {
|
|
52
|
+
return null;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return (
|
|
56
|
+
<sup>
|
|
57
|
+
{refs.map((r, idx) => (
|
|
58
|
+
<span key={r.ref}>
|
|
59
|
+
<span
|
|
60
|
+
className="s-inline-flex s-h-4 s-w-4 s-cursor-pointer s-items-center s-justify-center s-rounded-full s-bg-primary-600 s-text-xs s-font-medium s-text-primary-200 dark:s-bg-primary-600-night dark:s-text-primary-200-night"
|
|
61
|
+
title={references[r.ref]?.title}
|
|
62
|
+
>
|
|
63
|
+
{r.counter}
|
|
64
|
+
</span>
|
|
65
|
+
{idx < refs.length - 1 && ","}
|
|
66
|
+
</span>
|
|
67
|
+
))}
|
|
68
|
+
</sup>
|
|
69
|
+
);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Mock MentionBlock component for Storybook
|
|
73
|
+
function MentionBlock({
|
|
74
|
+
agentName,
|
|
75
|
+
agentSId,
|
|
76
|
+
onClick,
|
|
77
|
+
}: {
|
|
78
|
+
agentName: string;
|
|
79
|
+
agentSId: string;
|
|
80
|
+
onClick?: (agentSId: string) => void;
|
|
81
|
+
}) {
|
|
82
|
+
return (
|
|
83
|
+
<span
|
|
84
|
+
className="s-inline-block s-cursor-pointer s-font-medium s-text-highlight dark:s-text-highlight-night"
|
|
85
|
+
onClick={() => onClick?.(agentSId)}
|
|
86
|
+
>
|
|
87
|
+
@{agentName}
|
|
88
|
+
</span>
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// Mock directive functions for Storybook
|
|
93
|
+
function getCiteDirective() {
|
|
94
|
+
return () => {
|
|
95
|
+
let refCounter = 1;
|
|
96
|
+
const refSeen: { [ref: string]: number } = {};
|
|
97
|
+
|
|
98
|
+
return (tree: Root) => {
|
|
99
|
+
visit(tree, ["textDirective"], (node: any) => {
|
|
100
|
+
if (node.name === "cite" && node.children[0]?.value) {
|
|
101
|
+
const references = node.children[0]?.value
|
|
102
|
+
.split(",")
|
|
103
|
+
.map((ref: string) => ({
|
|
104
|
+
counter: refSeen[ref] || (refSeen[ref] = refCounter++),
|
|
105
|
+
ref,
|
|
106
|
+
}));
|
|
107
|
+
|
|
108
|
+
node.data = node.data || {};
|
|
109
|
+
node.data.hName = "sup";
|
|
110
|
+
node.data.hProperties = { references: JSON.stringify(references) };
|
|
111
|
+
}
|
|
112
|
+
});
|
|
113
|
+
};
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
function getMentionDirective() {
|
|
118
|
+
return () => (tree: Root) => {
|
|
119
|
+
visit(tree, ["textDirective"], (node: any) => {
|
|
120
|
+
if (node.name === "mention" && node.children?.[0]) {
|
|
121
|
+
node.data = node.data || {};
|
|
122
|
+
node.data.hName = "mention";
|
|
123
|
+
node.data.hProperties = {
|
|
124
|
+
agentSId: node.attributes?.sId,
|
|
125
|
+
agentName: node.children[0].value,
|
|
126
|
+
};
|
|
127
|
+
}
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
const meta: Meta<typeof StreamingMarkdown> = {
|
|
133
|
+
title: "Components/StreamingMarkdown",
|
|
134
|
+
component: StreamingMarkdown,
|
|
135
|
+
parameters: {
|
|
136
|
+
layout: "padded",
|
|
137
|
+
docs: {
|
|
138
|
+
description: {
|
|
139
|
+
component:
|
|
140
|
+
"Streaming-aware Markdown that animates only newly appended text.",
|
|
141
|
+
},
|
|
142
|
+
},
|
|
143
|
+
},
|
|
144
|
+
argTypes: {
|
|
145
|
+
animationName: { control: "text" },
|
|
146
|
+
animationDuration: { control: "text" },
|
|
147
|
+
animationTimingFunction: { control: "text" },
|
|
148
|
+
animationCurve: {
|
|
149
|
+
control: "select",
|
|
150
|
+
options: ["linear", "accelerate", "accelerate-fast", "custom"],
|
|
151
|
+
description: "Animation curve type for opacity transition",
|
|
152
|
+
},
|
|
153
|
+
},
|
|
154
|
+
args: {
|
|
155
|
+
animationDuration: "600ms",
|
|
156
|
+
animationTimingFunction: "ease-out",
|
|
157
|
+
animationCurve: "linear",
|
|
158
|
+
},
|
|
159
|
+
};
|
|
160
|
+
|
|
161
|
+
export default meta;
|
|
162
|
+
|
|
163
|
+
export const AnimationCurveComparison: Story = {
|
|
164
|
+
parameters: {
|
|
165
|
+
docs: {
|
|
166
|
+
description: {
|
|
167
|
+
story:
|
|
168
|
+
"Compare different animation curves for the fade-in effect. Accelerate curves start slower and speed up, reducing the time spent at low opacity.",
|
|
169
|
+
},
|
|
170
|
+
},
|
|
171
|
+
},
|
|
172
|
+
render: (args) => {
|
|
173
|
+
const testContent = `This text demonstrates different animation curves. Notice how the **accelerate** curve starts slowly then speeds up, spending less time at low opacity levels. The *accelerate-fast* option is even more aggressive, quickly jumping to higher opacity values.`;
|
|
174
|
+
|
|
175
|
+
const [restart, setRestart] = React.useState(0);
|
|
176
|
+
|
|
177
|
+
return (
|
|
178
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 24 }}>
|
|
179
|
+
<button
|
|
180
|
+
onClick={() => setRestart((r) => r + 1)}
|
|
181
|
+
style={{
|
|
182
|
+
width: "fit-content",
|
|
183
|
+
padding: "8px 16px",
|
|
184
|
+
borderRadius: 6,
|
|
185
|
+
border: "1px solid #ddd",
|
|
186
|
+
background: "#f7f7f7",
|
|
187
|
+
cursor: "pointer",
|
|
188
|
+
}}
|
|
189
|
+
>
|
|
190
|
+
🔄 Restart Animations
|
|
191
|
+
</button>
|
|
192
|
+
|
|
193
|
+
<div
|
|
194
|
+
style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}
|
|
195
|
+
>
|
|
196
|
+
<div
|
|
197
|
+
style={{ border: "1px solid #ddd", padding: 16, borderRadius: 8 }}
|
|
198
|
+
>
|
|
199
|
+
<h3 style={{ marginTop: 0, color: "#666" }}>Linear (Default)</h3>
|
|
200
|
+
<StreamingMarkdown
|
|
201
|
+
key={`linear-${restart}`}
|
|
202
|
+
{...args}
|
|
203
|
+
content={testContent}
|
|
204
|
+
animationCurve="linear"
|
|
205
|
+
animationDuration="4000ms"
|
|
206
|
+
/>
|
|
207
|
+
</div>
|
|
208
|
+
|
|
209
|
+
<div
|
|
210
|
+
style={{ border: "1px solid #ddd", padding: 16, borderRadius: 8 }}
|
|
211
|
+
>
|
|
212
|
+
<h3 style={{ marginTop: 0, color: "#666" }}>
|
|
213
|
+
Accelerate (Slow start)
|
|
214
|
+
</h3>
|
|
215
|
+
<StreamingMarkdown
|
|
216
|
+
key={`accelerate-${restart}`}
|
|
217
|
+
{...args}
|
|
218
|
+
content={testContent}
|
|
219
|
+
animationCurve="accelerate"
|
|
220
|
+
animationDuration="4000ms"
|
|
221
|
+
/>
|
|
222
|
+
</div>
|
|
223
|
+
|
|
224
|
+
<div
|
|
225
|
+
style={{ border: "1px solid #ddd", padding: 16, borderRadius: 8 }}
|
|
226
|
+
>
|
|
227
|
+
<h3 style={{ marginTop: 0, color: "#666" }}>Accelerate Fast</h3>
|
|
228
|
+
<StreamingMarkdown
|
|
229
|
+
key={`accelerate-fast-${restart}`}
|
|
230
|
+
{...args}
|
|
231
|
+
content={testContent}
|
|
232
|
+
animationCurve="accelerate-fast"
|
|
233
|
+
animationDuration="4000ms"
|
|
234
|
+
/>
|
|
235
|
+
</div>
|
|
236
|
+
|
|
237
|
+
<div
|
|
238
|
+
style={{ border: "1px solid #ddd", padding: 16, borderRadius: 8 }}
|
|
239
|
+
>
|
|
240
|
+
<h3 style={{ marginTop: 0, color: "#666" }}>
|
|
241
|
+
Short Duration (600ms)
|
|
242
|
+
</h3>
|
|
243
|
+
<StreamingMarkdown
|
|
244
|
+
key={`short-${restart}`}
|
|
245
|
+
{...args}
|
|
246
|
+
content={testContent}
|
|
247
|
+
animationCurve="accelerate"
|
|
248
|
+
animationDuration="600ms"
|
|
249
|
+
/>
|
|
250
|
+
</div>
|
|
251
|
+
</div>
|
|
252
|
+
</div>
|
|
253
|
+
);
|
|
254
|
+
},
|
|
255
|
+
};
|
|
256
|
+
|
|
257
|
+
export const MathStreamingTest: Story = {
|
|
258
|
+
parameters: {
|
|
259
|
+
docs: {
|
|
260
|
+
description: {
|
|
261
|
+
story:
|
|
262
|
+
"Test that block math does not block streaming of subsequent content.",
|
|
263
|
+
},
|
|
264
|
+
},
|
|
265
|
+
},
|
|
266
|
+
render: (args) => {
|
|
267
|
+
const testContent = `# Math Streaming Test
|
|
268
|
+
|
|
269
|
+
This text should appear and animate immediately.
|
|
270
|
+
|
|
271
|
+
## Block Math Below
|
|
272
|
+
|
|
273
|
+
The following block math should not prevent the text after it from streaming:
|
|
274
|
+
|
|
275
|
+
$$
|
|
276
|
+
\\begin{align}
|
|
277
|
+
E &= mc^2 \\\\
|
|
278
|
+
F &= ma \\\\
|
|
279
|
+
\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\epsilon_0}
|
|
280
|
+
\\end{align}
|
|
281
|
+
$$
|
|
282
|
+
|
|
283
|
+
## Text After Math
|
|
284
|
+
|
|
285
|
+
This text should stream in smoothly even while the math block above is incomplete.
|
|
286
|
+
|
|
287
|
+
Here's some inline math too: $a^2 + b^2 = c^2$ which should also stream properly.
|
|
288
|
+
|
|
289
|
+
### More Content
|
|
290
|
+
|
|
291
|
+
- List item 1
|
|
292
|
+
- List item 2 with **bold**
|
|
293
|
+
- List item 3 with \`code\`
|
|
294
|
+
|
|
295
|
+
The streaming should work continuously throughout.`;
|
|
296
|
+
|
|
297
|
+
const [content, setContent] = React.useState<string>("");
|
|
298
|
+
const [isStreaming, setIsStreaming] = React.useState(false);
|
|
299
|
+
|
|
300
|
+
const startStreaming = () => {
|
|
301
|
+
setContent("");
|
|
302
|
+
setIsStreaming(true);
|
|
303
|
+
let i = 0;
|
|
304
|
+
const chunkSize = 5; // Small chunks to see the effect clearly
|
|
305
|
+
const interval = setInterval(() => {
|
|
306
|
+
if (i >= testContent.length) {
|
|
307
|
+
clearInterval(interval);
|
|
308
|
+
setIsStreaming(false);
|
|
309
|
+
return;
|
|
310
|
+
}
|
|
311
|
+
const next = testContent.slice(i, i + chunkSize);
|
|
312
|
+
i += chunkSize;
|
|
313
|
+
setContent((prev) => prev + next);
|
|
314
|
+
}, 50);
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
return (
|
|
318
|
+
<div>
|
|
319
|
+
<div style={{ marginBottom: 20 }}>
|
|
320
|
+
<button
|
|
321
|
+
onClick={startStreaming}
|
|
322
|
+
style={{
|
|
323
|
+
padding: "8px 16px",
|
|
324
|
+
borderRadius: 4,
|
|
325
|
+
border: "1px solid #ccc",
|
|
326
|
+
cursor: "pointer",
|
|
327
|
+
}}
|
|
328
|
+
>
|
|
329
|
+
Start Streaming
|
|
330
|
+
</button>
|
|
331
|
+
<span style={{ marginLeft: 10 }}>
|
|
332
|
+
{isStreaming ? "🔴 Streaming..." : "⭐ Ready"}
|
|
333
|
+
</span>
|
|
334
|
+
</div>
|
|
335
|
+
<div style={{ border: "1px solid #ddd", padding: 20, borderRadius: 8 }}>
|
|
336
|
+
<StreamingMarkdown {...args} content={content} />
|
|
337
|
+
</div>
|
|
338
|
+
<div style={{ marginTop: 10, fontSize: 12, color: "#666" }}>
|
|
339
|
+
Content length: {content.length} / {testContent.length}
|
|
340
|
+
</div>
|
|
341
|
+
</div>
|
|
342
|
+
);
|
|
343
|
+
},
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export const StaticVsStreaming: Story = {
|
|
347
|
+
parameters: {
|
|
348
|
+
docs: {
|
|
349
|
+
description: {
|
|
350
|
+
story: "Compare static and streaming rendering side by side.",
|
|
351
|
+
},
|
|
352
|
+
},
|
|
353
|
+
},
|
|
354
|
+
render: (args) => {
|
|
355
|
+
const sampleContent = `
|
|
356
|
+
## Comparison Demo
|
|
357
|
+
|
|
358
|
+
### Math Example
|
|
359
|
+
Einstein's famous equation: $E = mc^2$
|
|
360
|
+
|
|
361
|
+
### Citations
|
|
362
|
+
This is referenced material :cite[r1] with multiple sources :cite[r2,r3].
|
|
363
|
+
|
|
364
|
+
### Task List
|
|
365
|
+
- [x] Completed task
|
|
366
|
+
- [ ] Pending task
|
|
367
|
+
- [ ] Another pending task
|
|
368
|
+
|
|
369
|
+
### Mentions
|
|
370
|
+
Ping :mention[assistant]{sId:bot-1} for help.
|
|
371
|
+
`;
|
|
372
|
+
|
|
373
|
+
return (
|
|
374
|
+
<div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 20 }}>
|
|
375
|
+
<div>
|
|
376
|
+
<h3>Static (isStreaming=false)</h3>
|
|
377
|
+
<CitationsContext.Provider
|
|
378
|
+
value={{
|
|
379
|
+
references: mockReferences,
|
|
380
|
+
updateActiveReferences: () => {},
|
|
381
|
+
}}
|
|
382
|
+
>
|
|
383
|
+
<StreamingMarkdown
|
|
384
|
+
{...args}
|
|
385
|
+
content={sampleContent}
|
|
386
|
+
isStreaming={false}
|
|
387
|
+
additionalMarkdownComponents={
|
|
388
|
+
{
|
|
389
|
+
sup: CiteBlock,
|
|
390
|
+
mention: MentionBlock,
|
|
391
|
+
} as Components
|
|
392
|
+
}
|
|
393
|
+
additionalMarkdownPlugins={[
|
|
394
|
+
getCiteDirective(),
|
|
395
|
+
getMentionDirective(),
|
|
396
|
+
]}
|
|
397
|
+
/>
|
|
398
|
+
</CitationsContext.Provider>
|
|
399
|
+
</div>
|
|
400
|
+
<div>
|
|
401
|
+
<h3>Streaming (isStreaming=true)</h3>
|
|
402
|
+
<CitationsContext.Provider
|
|
403
|
+
value={{
|
|
404
|
+
references: mockReferences,
|
|
405
|
+
updateActiveReferences: () => {},
|
|
406
|
+
}}
|
|
407
|
+
>
|
|
408
|
+
<StreamingMarkdown
|
|
409
|
+
{...args}
|
|
410
|
+
content={sampleContent}
|
|
411
|
+
isStreaming={true}
|
|
412
|
+
additionalMarkdownComponents={
|
|
413
|
+
{
|
|
414
|
+
sup: CiteBlock,
|
|
415
|
+
mention: MentionBlock,
|
|
416
|
+
} as Components
|
|
417
|
+
}
|
|
418
|
+
additionalMarkdownPlugins={[
|
|
419
|
+
getCiteDirective(),
|
|
420
|
+
getMentionDirective(),
|
|
421
|
+
]}
|
|
422
|
+
/>
|
|
423
|
+
</CitationsContext.Provider>
|
|
424
|
+
</div>
|
|
425
|
+
</div>
|
|
426
|
+
);
|
|
427
|
+
},
|
|
428
|
+
};
|
|
429
|
+
type Story = StoryObj<typeof StreamingMarkdown>;
|
|
430
|
+
|
|
431
|
+
// Utility: simple seeded RNG for reproducible chunk sizes
|
|
432
|
+
function makeRng(seed: number) {
|
|
433
|
+
let s = seed >>> 0;
|
|
434
|
+
return () => {
|
|
435
|
+
// xorshift32
|
|
436
|
+
s ^= s << 13;
|
|
437
|
+
s ^= s >>> 17;
|
|
438
|
+
s ^= s << 5;
|
|
439
|
+
return ((s >>> 0) % 10000) / 10000;
|
|
440
|
+
};
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const ADVANCED_MD = `# 🚀 Enhanced Streaming Markdown Demo
|
|
444
|
+
|
|
445
|
+
This document demonstrates **all new features** including directives, math, and citations.
|
|
446
|
+
|
|
447
|
+
## 🔢 Math Support
|
|
448
|
+
|
|
449
|
+
### Inline Math
|
|
450
|
+
|
|
451
|
+
The quadratic formula is $ax^2 + bx + c = 0$ and its solution is $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$.
|
|
452
|
+
|
|
453
|
+
### Block Math
|
|
454
|
+
|
|
455
|
+
$$
|
|
456
|
+
\\begin{align}
|
|
457
|
+
\\nabla \\times \\vec{\\mathbf{B}} -\\, \\frac{1}{c}\\, \\frac{\\partial\\vec{\\mathbf{E}}}{\\partial t} &= \\frac{4\\pi}{c}\\vec{\\mathbf{j}} \\\\
|
|
458
|
+
\\nabla \\cdot \\vec{\\mathbf{E}} &= 4 \\pi \\rho \\\\
|
|
459
|
+
\\nabla \\times \\vec{\\mathbf{E}}\\, +\\, \\frac{1}{c}\\, \\frac{\\partial\\vec{\\mathbf{B}}}{\\partial t} &= \\vec{\\mathbf{0}} \\\\
|
|
460
|
+
\\nabla \\cdot \\vec{\\mathbf{B}} &= 0
|
|
461
|
+
\\end{align}
|
|
462
|
+
$$
|
|
463
|
+
|
|
464
|
+
## 📚 Citations and References
|
|
465
|
+
|
|
466
|
+
According to recent research :cite[r1], streaming markdown improves user experience. Further studies :cite[r2,r3] confirm these findings.
|
|
467
|
+
|
|
468
|
+
The implementation details :cite[r1] show significant performance gains, especially when combined with modern React patterns :cite[r2].
|
|
469
|
+
|
|
470
|
+
## 💬 Mentions
|
|
471
|
+
|
|
472
|
+
You can mention agents like :mention[assistant]{sId:assistant-123} or :mention[helper]{sId:helper-456} directly in the text.
|
|
473
|
+
|
|
474
|
+
## ✅ Task Lists
|
|
475
|
+
|
|
476
|
+
- [ ] Implement streaming support
|
|
477
|
+
- [x] Add directive system
|
|
478
|
+
- [x] Support math rendering
|
|
479
|
+
- [ ] Add custom visualizations
|
|
480
|
+
- [x] Basic charts
|
|
481
|
+
- [ ] Interactive graphs
|
|
482
|
+
- [x] Test all features
|
|
483
|
+
`;
|
|
484
|
+
|
|
485
|
+
const LONG_MD = `# 🚀 Comprehensive Streaming Markdown Demo
|
|
486
|
+
|
|
487
|
+
Welcome to the **complete showcase** of our streaming markdown renderer! This document demonstrates *every* supported feature with rich examples.
|
|
488
|
+
|
|
489
|
+
## 📖 Long Form Content
|
|
490
|
+
|
|
491
|
+
The streaming markdown renderer excels at displaying long-form content with smooth, progressive animation. As you read this paragraph, notice how the text appears naturally, character by character, creating a dynamic reading experience. This first paragraph contains **bold text**, *italicized content*, and even \`inline code\` to demonstrate that formatting is preserved throughout the streaming process. The animation maintains readability while adding visual interest, making it perfect for displaying AI-generated responses, real-time documentation, or any content that benefits from a progressive reveal. Whether you're building a chatbot interface, a documentation system, or an interactive tutorial, the streaming effect helps guide the reader's attention and creates a sense of immediacy and engagement with the content.
|
|
492
|
+
|
|
493
|
+
Furthermore, the streaming system handles complex markdown structures with ease. This second lengthy paragraph showcases how the renderer maintains performance even with dense content that includes multiple formatting styles. Consider how ***combined bold and italic text*** flows seamlessly alongside regular prose, and how [links to external resources](https://example.com) appear without interrupting the animation flow. The system is intelligent enough to handle edge cases like \`multiple\` instances of \`inline code\` within the same sentence, or even ~~strikethrough text that has been deprecated~~. The streaming animation respects word boundaries, ensuring that text remains readable throughout the animation process. This is particularly important for accessibility, as users can still read and comprehend the content even while it's being streamed in, without experiencing jarring visual artifacts or layout shifts that might disturb the reading experience.
|
|
494
|
+
|
|
495
|
+
Finally, this third substantial paragraph demonstrates the renderer's ability to handle technical content with precision. When dealing with mathematical expressions like $E = mc^2$ or more complex formulas such as $\\int_{0}^{\\infty} e^{-x^2} dx = \\frac{\\sqrt{\\pi}}{2}$, the streaming animation gracefully incorporates these elements without breaking the flow. The renderer also excels at presenting mixed content types within a single paragraph, seamlessly transitioning between regular text, **formatted sections**, [hyperlinks with descriptive text](https://github.com), and technical notations. This capability makes it ideal for educational content, technical documentation, or any scenario where rich, formatted text needs to be presented progressively. The underlying architecture ensures that even as content streams in, all interactive elements remain functional, links remain clickable, and the overall document structure remains intact and navigable throughout the entire streaming process.
|
|
496
|
+
|
|
497
|
+
## 📝 Text Formatting
|
|
498
|
+
|
|
499
|
+
This paragraph demonstrates basic text with **bold emphasis**, *italic text*, ***bold italic combination***, and \`inline code snippets\`. We can also use ~~strikethrough text~~ and combine multiple styles for ***\`formatted code\`*** within emphasis.
|
|
500
|
+
|
|
501
|
+
## 🧮 Mathematical Expressions
|
|
502
|
+
|
|
503
|
+
### Inline Math
|
|
504
|
+
|
|
505
|
+
The quadratic formula is $x = \\frac{-b \\pm \\sqrt{b^2 - 4ac}}{2a}$, which gives us the roots of a quadratic equation. We can also write simple expressions like $e^{i\\pi} + 1 = 0$ (Euler's identity) or $\\sum_{i=1}^{n} i = \\frac{n(n+1)}{2}$ inline with text.
|
|
506
|
+
|
|
507
|
+
### Block Math
|
|
508
|
+
|
|
509
|
+
Here's the famous Gaussian integral:
|
|
510
|
+
|
|
511
|
+
$$
|
|
512
|
+
\\int_{-\\infty}^{\\infty} e^{-x^2} dx = \\sqrt{\\pi}
|
|
513
|
+
$$
|
|
514
|
+
|
|
515
|
+
And Maxwell's equations in differential form:
|
|
516
|
+
|
|
517
|
+
$$
|
|
518
|
+
\\begin{align}
|
|
519
|
+
\\nabla \\cdot \\vec{E} &= \\frac{\\rho}{\\epsilon_0} \\\\
|
|
520
|
+
\\nabla \\cdot \\vec{B} &= 0 \\\\
|
|
521
|
+
\\nabla \\times \\vec{E} &= -\\frac{\\partial \\vec{B}}{\\partial t} \\\\
|
|
522
|
+
\\nabla \\times \\vec{B} &= \\mu_0 \\vec{J} + \\mu_0 \\epsilon_0 \\frac{\\partial \\vec{E}}{\\partial t}
|
|
523
|
+
\\end{align}
|
|
524
|
+
$$
|
|
525
|
+
|
|
526
|
+
Matrix multiplication example:
|
|
527
|
+
|
|
528
|
+
$$
|
|
529
|
+
\\begin{bmatrix}
|
|
530
|
+
a & b \\\\
|
|
531
|
+
c & d
|
|
532
|
+
\\end{bmatrix}
|
|
533
|
+
\\begin{bmatrix}
|
|
534
|
+
x \\\\
|
|
535
|
+
y
|
|
536
|
+
\\end{bmatrix}
|
|
537
|
+
=
|
|
538
|
+
\\begin{bmatrix}
|
|
539
|
+
ax + by \\\\
|
|
540
|
+
cx + dy
|
|
541
|
+
\\end{bmatrix}
|
|
542
|
+
$$
|
|
543
|
+
|
|
544
|
+
## 🎯 Custom Directives
|
|
545
|
+
|
|
546
|
+
### Citations
|
|
547
|
+
|
|
548
|
+
Here's a statement with a citation :cite[r1], and another with multiple citations :cite[r2,r3]. These custom directives demonstrate how we can extend the markdown syntax with domain-specific features :cite[r1].
|
|
549
|
+
|
|
550
|
+
### Mentions
|
|
551
|
+
|
|
552
|
+
You can mention agents like :mention[dust]{sId="assistant"} or :mention[gpt4]{sId="gpt4-helper"} in your text. These mentions are interactive and can trigger actions when clicked.
|
|
553
|
+
|
|
554
|
+
### Links and References
|
|
555
|
+
|
|
556
|
+
Here are different types of links:
|
|
557
|
+
- [External link to Dust](https://dust.tt)
|
|
558
|
+
- [Link with title](https://github.com "Visit GitHub")
|
|
559
|
+
- Direct URL: https://example.com
|
|
560
|
+
- Reference-style link: [Dust Platform][dust-ref]
|
|
561
|
+
|
|
562
|
+
[dust-ref]: https://dust.tt "Dust - AI Platform"
|
|
563
|
+
|
|
564
|
+
## 📋 Lists Showcase
|
|
565
|
+
|
|
566
|
+
### Unordered Lists with Nesting
|
|
567
|
+
|
|
568
|
+
- First level item with regular text
|
|
569
|
+
- Second item with **bold** and *italic* formatting
|
|
570
|
+
- Nested item 2.1 with \`inline code\`
|
|
571
|
+
- Nested item 2.2 with a [link](https://example.com)
|
|
572
|
+
- Deep nesting level 3.1
|
|
573
|
+
- Deep nesting level 3.2 with ***combined formatting***
|
|
574
|
+
- Back to level 2
|
|
575
|
+
- Back to first level
|
|
576
|
+
- Another first level item with multiple lines of text that demonstrates how longer content wraps properly in list items
|
|
577
|
+
|
|
578
|
+
### Ordered Lists
|
|
579
|
+
|
|
580
|
+
1. First ordered item
|
|
581
|
+
2. Second item with **emphasis**
|
|
582
|
+
1. Nested ordered 2.1
|
|
583
|
+
2. Nested ordered 2.2
|
|
584
|
+
1. Deep nested 2.2.1
|
|
585
|
+
2. Deep nested 2.2.2
|
|
586
|
+
3. Nested ordered 2.3
|
|
587
|
+
3. Third item with mixed content
|
|
588
|
+
4. Fourth item
|
|
589
|
+
|
|
590
|
+
### Mixed Lists
|
|
591
|
+
|
|
592
|
+
1. Ordered at top level
|
|
593
|
+
- Unordered nested under ordered
|
|
594
|
+
- Another unordered item
|
|
595
|
+
1. Ordered under unordered
|
|
596
|
+
2. Second ordered item
|
|
597
|
+
2. Back to top level ordered
|
|
598
|
+
- Another mixed nested list
|
|
599
|
+
- Deeper unordered
|
|
600
|
+
1. Even deeper ordered
|
|
601
|
+
|
|
602
|
+
### Task Lists
|
|
603
|
+
|
|
604
|
+
- [ ] Unchecked task item
|
|
605
|
+
- [x] Completed task item
|
|
606
|
+
- [ ] Task with **formatted text**
|
|
607
|
+
- [x] Nested completed task
|
|
608
|
+
- [ ] Nested incomplete task
|
|
609
|
+
|
|
610
|
+
## 💻 Code Blocks
|
|
611
|
+
|
|
612
|
+
### Inline Code
|
|
613
|
+
|
|
614
|
+
Here's some \`inline code\` in the middle of a sentence, and \`const x = 42\` with actual code.
|
|
615
|
+
|
|
616
|
+
### JavaScript/TypeScript
|
|
617
|
+
|
|
618
|
+
\`\`\`javascript
|
|
619
|
+
// Fibonacci sequence generator
|
|
620
|
+
function* fibonacci() {
|
|
621
|
+
let [a, b] = [0, 1];
|
|
622
|
+
while (true) {
|
|
623
|
+
yield a;
|
|
624
|
+
[a, b] = [b, a + b];
|
|
625
|
+
}
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
// Usage example
|
|
629
|
+
const fib = fibonacci();
|
|
630
|
+
for (let i = 0; i < 10; i++) {
|
|
631
|
+
console.log(\`F(\${i}) = \${fib.next().value}\`);
|
|
632
|
+
}
|
|
633
|
+
\`\`\`
|
|
634
|
+
|
|
635
|
+
### Python
|
|
636
|
+
|
|
637
|
+
\`\`\`python
|
|
638
|
+
# Python example with classes
|
|
639
|
+
class DataProcessor:
|
|
640
|
+
def __init__(self, data):
|
|
641
|
+
self.data = data
|
|
642
|
+
self.processed = False
|
|
643
|
+
|
|
644
|
+
def process(self):
|
|
645
|
+
"""Process the data with transformations"""
|
|
646
|
+
if not self.processed:
|
|
647
|
+
self.data = [x * 2 for x in self.data if x > 0]
|
|
648
|
+
self.processed = True
|
|
649
|
+
return self.data
|
|
650
|
+
|
|
651
|
+
# Usage
|
|
652
|
+
processor = DataProcessor([1, -2, 3, 4, -5])
|
|
653
|
+
result = processor.process()
|
|
654
|
+
print(f"Processed: {result}")
|
|
655
|
+
\`\`\`
|
|
656
|
+
|
|
657
|
+
### React Component (JSX)
|
|
658
|
+
|
|
659
|
+
\`\`\`jsx
|
|
660
|
+
import React, { useState, useEffect } from 'react';
|
|
661
|
+
|
|
662
|
+
const StreamingDemo = ({ content }) => {
|
|
663
|
+
const [displayText, setDisplayText] = useState('');
|
|
664
|
+
|
|
665
|
+
useEffect(() => {
|
|
666
|
+
let index = 0;
|
|
667
|
+
const timer = setInterval(() => {
|
|
668
|
+
if (index < content.length) {
|
|
669
|
+
setDisplayText(content.slice(0, ++index));
|
|
670
|
+
} else {
|
|
671
|
+
clearInterval(timer);
|
|
672
|
+
}
|
|
673
|
+
}, 50);
|
|
674
|
+
|
|
675
|
+
return () => clearInterval(timer);
|
|
676
|
+
}, [content]);
|
|
677
|
+
|
|
678
|
+
return (
|
|
679
|
+
<div className="streaming-text">
|
|
680
|
+
{displayText}
|
|
681
|
+
</div>
|
|
682
|
+
);
|
|
683
|
+
};
|
|
684
|
+
|
|
685
|
+
export default StreamingDemo;
|
|
686
|
+
\`\`\`
|
|
687
|
+
|
|
688
|
+
### Shell/Bash
|
|
689
|
+
|
|
690
|
+
\`\`\`bash
|
|
691
|
+
#!/bin/bash
|
|
692
|
+
|
|
693
|
+
# System information script
|
|
694
|
+
echo "System Information"
|
|
695
|
+
echo "=================="
|
|
696
|
+
echo "Hostname: $(hostname)"
|
|
697
|
+
echo "OS: $(uname -s)"
|
|
698
|
+
echo "Kernel: $(uname -r)"
|
|
699
|
+
echo "CPU: $(sysctl -n machdep.cpu.brand_string 2>/dev/null || lscpu | grep 'Model name')"
|
|
700
|
+
echo "Memory: $(free -h 2>/dev/null || vm_stat | grep 'Pages free')"
|
|
701
|
+
echo "Disk Usage:"
|
|
702
|
+
df -h | head -5
|
|
703
|
+
\`\`\`
|
|
704
|
+
|
|
705
|
+
### JSON
|
|
706
|
+
|
|
707
|
+
\`\`\`json
|
|
708
|
+
{
|
|
709
|
+
"name": "streaming-markdown",
|
|
710
|
+
"version": "1.0.0",
|
|
711
|
+
"features": {
|
|
712
|
+
"streaming": true,
|
|
713
|
+
"syntaxHighlight": true,
|
|
714
|
+
"animations": {
|
|
715
|
+
"fadeIn": "600ms",
|
|
716
|
+
"timing": "ease-out"
|
|
717
|
+
}
|
|
718
|
+
},
|
|
719
|
+
"supported": ["lists", "code", "tables", "images", "math", "directives"]
|
|
720
|
+
}
|
|
721
|
+
\`\`\`
|
|
722
|
+
|
|
723
|
+
### Mermaid Diagram
|
|
724
|
+
|
|
725
|
+
\`\`\`mermaid
|
|
726
|
+
graph TD
|
|
727
|
+
A[Start] --> B{Is Streaming?}
|
|
728
|
+
B -->|Yes| C[Stream Characters]
|
|
729
|
+
B -->|No| D[Render Static]
|
|
730
|
+
C --> E[Apply Animation]
|
|
731
|
+
E --> F[Display Content]
|
|
732
|
+
D --> F
|
|
733
|
+
F --> G[End]
|
|
734
|
+
|
|
735
|
+
style A fill:#f9f,stroke:#333,stroke-width:2px
|
|
736
|
+
style G fill:#9f9,stroke:#333,stroke-width:2px
|
|
737
|
+
style C fill:#bbf,stroke:#333,stroke-width:2px
|
|
738
|
+
\`\`\`
|
|
739
|
+
|
|
740
|
+
### CSV Data (Downloadable)
|
|
741
|
+
|
|
742
|
+
\`\`\`csv
|
|
743
|
+
Name,Department,Score,Status
|
|
744
|
+
Alice Johnson,Engineering,95,Active
|
|
745
|
+
Bob Smith,Marketing,87,Active
|
|
746
|
+
Charlie Brown,Sales,92,Active
|
|
747
|
+
Diana Prince,HR,88,Active
|
|
748
|
+
Eve Adams,Engineering,91,Active
|
|
749
|
+
\`\`\`
|
|
750
|
+
|
|
751
|
+
## 📊 Tables
|
|
752
|
+
|
|
753
|
+
### Basic Table
|
|
754
|
+
|
|
755
|
+
| Feature | Status | Priority | Notes |
|
|
756
|
+
|---------|--------|----------|-------|
|
|
757
|
+
| Streaming | ✅ Done | High | Core functionality |
|
|
758
|
+
| Lists | ✅ Done | High | With nesting support |
|
|
759
|
+
| Code blocks | ✅ Done | Medium | With syntax highlighting |
|
|
760
|
+
| Tables | ✅ Done | Low | Basic support |
|
|
761
|
+
| Images | ✅ Done | Low | With loading animation |
|
|
762
|
+
|
|
763
|
+
### Complex Table with Formatting
|
|
764
|
+
|
|
765
|
+
| Language | **Typing** | *Performance* | \`hello()\` | Rating |
|
|
766
|
+
|----------|------------|---------------|-------------|--------|
|
|
767
|
+
| TypeScript | Static | ⚡ Fast | \`console.log\` | ⭐⭐⭐⭐⭐ |
|
|
768
|
+
| Python | Dynamic | 🐢 Moderate | \`print()\` | ⭐⭐⭐⭐ |
|
|
769
|
+
| Rust | Static | 🚀 Very Fast | \`println!\` | ⭐⭐⭐⭐⭐ |
|
|
770
|
+
| Ruby | Dynamic | 🐌 Slow | \`puts\` | ⭐⭐⭐ |
|
|
771
|
+
|
|
772
|
+
## 🎨 Blockquotes
|
|
773
|
+
|
|
774
|
+
> This is a simple blockquote with a single paragraph.
|
|
775
|
+
|
|
776
|
+
> ### Blockquote with Heading
|
|
777
|
+
>
|
|
778
|
+
> This blockquote contains multiple elements:
|
|
779
|
+
> - A list item
|
|
780
|
+
> - Another item with **bold text**
|
|
781
|
+
>
|
|
782
|
+
> And even a code block:
|
|
783
|
+
> \`\`\`js
|
|
784
|
+
> const quote = "Nested code in quote";
|
|
785
|
+
> \`\`\`
|
|
786
|
+
|
|
787
|
+
> > Nested blockquotes are also supported
|
|
788
|
+
> > > And can go multiple levels deep
|
|
789
|
+
> > > > Even deeper if needed
|
|
790
|
+
|
|
791
|
+
## 🖼️ Images
|
|
792
|
+
|
|
793
|
+
### Single Image
|
|
794
|
+
|
|
795
|
+

|
|
796
|
+
|
|
797
|
+
### Multiple Images
|
|
798
|
+
|
|
799
|
+

|
|
800
|
+

|
|
801
|
+

|
|
802
|
+
|
|
803
|
+
## 🔤 Special Characters & Emojis
|
|
804
|
+
|
|
805
|
+
Special characters: & < > " ' © ® ™ € £ ¥ ° ± × ÷ ≤ ≥ ≠
|
|
806
|
+
|
|
807
|
+
Math symbols: ∑ ∏ ∫ √ ∞ α β γ δ ε θ λ μ π σ φ ω
|
|
808
|
+
|
|
809
|
+
Emojis: 😀 🎉 🚀 💻 📱 🌟 ⚡ 🔥 💡 🎨 🏆 ✅ ❌ ⚠️ 📝
|
|
810
|
+
|
|
811
|
+
## ➖ Horizontal Rules
|
|
812
|
+
|
|
813
|
+
Above this line
|
|
814
|
+
|
|
815
|
+
---
|
|
816
|
+
|
|
817
|
+
Between these lines
|
|
818
|
+
|
|
819
|
+
***
|
|
820
|
+
|
|
821
|
+
Another separator
|
|
822
|
+
|
|
823
|
+
___
|
|
824
|
+
|
|
825
|
+
Below this line
|
|
826
|
+
|
|
827
|
+
## 🔄 Escaping & Special Cases
|
|
828
|
+
|
|
829
|
+
Escaping special characters: \\*not italic\\*, \\**not bold\\**, \\[not a link\\](url)
|
|
830
|
+
|
|
831
|
+
HTML entities: <tag> & "quotes" 'apostrophe'
|
|
832
|
+
|
|
833
|
+
Preserving spacing: Multiple spaces between words
|
|
834
|
+
|
|
835
|
+
## 🎯 Advanced Combinations
|
|
836
|
+
|
|
837
|
+
### List with Everything
|
|
838
|
+
|
|
839
|
+
1. **First item** with [a link](https://example.com) and \`code\`
|
|
840
|
+
- Nested with *italic* and ~~strikethrough~~
|
|
841
|
+
- Another with emoji 🎉 and **bold**
|
|
842
|
+
1. Deep ordered with \`code block\`:
|
|
843
|
+
\`\`\`js
|
|
844
|
+
const nested = true;
|
|
845
|
+
\`\`\`
|
|
846
|
+
2. And an image:
|
|
847
|
+

|
|
848
|
+
- Back to unordered
|
|
849
|
+
2. Second main item with a table:
|
|
850
|
+
|
|
851
|
+
| Col1 | Col2 |
|
|
852
|
+
|------|------|
|
|
853
|
+
| A | B |
|
|
854
|
+
|
|
855
|
+
3. Third item with blockquote:
|
|
856
|
+
> Quoted text in a list
|
|
857
|
+
|
|
858
|
+
## 📚 Long Prose Section
|
|
859
|
+
|
|
860
|
+
Lorem ipsum dolor sit amet, consectetur adipiscing elit. **Sed do eiusmod** tempor incididunt ut labore et dolore magna aliqua. *Ut enim ad minim veniam*, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat.
|
|
861
|
+
|
|
862
|
+
Duis aute irure dolor in ***reprehenderit in voluptate*** velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum. This demonstrates how longer paragraphs stream in naturally, maintaining readability while the content appears progressively.
|
|
863
|
+
|
|
864
|
+
### Technical Documentation Style
|
|
865
|
+
|
|
866
|
+
The \`StreamingMarkdown\` component accepts the following props:
|
|
867
|
+
|
|
868
|
+
- \`content\` (string, required): The markdown content to render
|
|
869
|
+
- \`animationName\` (string, optional): CSS animation name for streaming effect
|
|
870
|
+
- \`animationDuration\` (string, optional): Duration of the fade-in animation
|
|
871
|
+
- \`animationTimingFunction\` (string, optional): CSS timing function
|
|
872
|
+
- \`codeStyle\` (object, optional): Syntax highlighting theme
|
|
873
|
+
|
|
874
|
+
## 🎬 Conclusion
|
|
875
|
+
|
|
876
|
+
This comprehensive demo showcases **all supported markdown features** in our streaming renderer:
|
|
877
|
+
|
|
878
|
+
1. ✅ Text formatting and emphasis
|
|
879
|
+
2. ✅ Multiple list types with deep nesting
|
|
880
|
+
3. ✅ Code blocks with syntax highlighting
|
|
881
|
+
4. ✅ Tables with complex content
|
|
882
|
+
5. ✅ Blockquotes with nesting
|
|
883
|
+
6. ✅ Images with loading animations
|
|
884
|
+
7. ✅ Links and references
|
|
885
|
+
8. ✅ Special characters and emojis
|
|
886
|
+
9. ✅ Horizontal rules
|
|
887
|
+
10. ✅ Mixed and nested content
|
|
888
|
+
|
|
889
|
+
The streaming animation ensures a smooth, progressive reveal of content while maintaining full markdown compatibility.
|
|
890
|
+
|
|
891
|
+
---
|
|
892
|
+
|
|
893
|
+
*Thank you for exploring our streaming markdown renderer!* 🎉`;
|
|
894
|
+
|
|
895
|
+
const mockReferences = {
|
|
896
|
+
r1: {
|
|
897
|
+
title: "Streaming Markdown Performance Study",
|
|
898
|
+
href: "https://example.com/study1",
|
|
899
|
+
authors: ["John Doe", "Jane Smith"],
|
|
900
|
+
year: 2024,
|
|
901
|
+
},
|
|
902
|
+
r2: {
|
|
903
|
+
title: "React Patterns for Real-time UIs",
|
|
904
|
+
href: "https://example.com/study2",
|
|
905
|
+
authors: ["Alice Johnson"],
|
|
906
|
+
year: 2023,
|
|
907
|
+
},
|
|
908
|
+
r3: {
|
|
909
|
+
title: "User Experience in AI Interfaces",
|
|
910
|
+
href: "https://example.com/study3",
|
|
911
|
+
authors: ["Bob Wilson", "Carol White"],
|
|
912
|
+
year: 2024,
|
|
913
|
+
},
|
|
914
|
+
};
|
|
915
|
+
|
|
916
|
+
export const EnhancedFeaturesDemo: Story = {
|
|
917
|
+
parameters: {
|
|
918
|
+
docs: {
|
|
919
|
+
description: {
|
|
920
|
+
story:
|
|
921
|
+
"Demonstrates new features: math rendering, citations, mentions, task lists, and custom directives.",
|
|
922
|
+
},
|
|
923
|
+
},
|
|
924
|
+
},
|
|
925
|
+
render: (args) => {
|
|
926
|
+
const [content, setContent] = React.useState<string>("");
|
|
927
|
+
const [restart, setRestart] = React.useState<number>(0);
|
|
928
|
+
const {
|
|
929
|
+
streamingSpeedMs = 30,
|
|
930
|
+
chunkMin = 10,
|
|
931
|
+
chunkMax = 50,
|
|
932
|
+
seed = 42,
|
|
933
|
+
} = args as any;
|
|
934
|
+
|
|
935
|
+
React.useEffect(() => {
|
|
936
|
+
const full = ADVANCED_MD.trim();
|
|
937
|
+
setContent("");
|
|
938
|
+
let i = 0;
|
|
939
|
+
const rand = makeRng(Number(seed) || 42);
|
|
940
|
+
const min = Math.max(1, Number(chunkMin) || 1);
|
|
941
|
+
const max = Math.max(min, Number(chunkMax) || min);
|
|
942
|
+
const step = Math.max(10, Number(streamingSpeedMs) || 30);
|
|
943
|
+
const id = setInterval(() => {
|
|
944
|
+
if (i >= full.length) {
|
|
945
|
+
clearInterval(id);
|
|
946
|
+
return;
|
|
947
|
+
}
|
|
948
|
+
const r = rand();
|
|
949
|
+
const size = Math.floor(min + r * (max - min));
|
|
950
|
+
const next = full.slice(i, i + size);
|
|
951
|
+
i += size;
|
|
952
|
+
setContent((c) => c + next);
|
|
953
|
+
}, step);
|
|
954
|
+
return () => clearInterval(id);
|
|
955
|
+
}, [streamingSpeedMs, chunkMin, chunkMax, seed, restart]);
|
|
956
|
+
|
|
957
|
+
const handleMentionClick = React.useCallback((agentSId: string) => {
|
|
958
|
+
console.log("Mention clicked:", agentSId);
|
|
959
|
+
alert(`Clicked mention: ${agentSId}`);
|
|
960
|
+
}, []);
|
|
961
|
+
|
|
962
|
+
const citationsContextValue: CitationsContextType = React.useMemo(
|
|
963
|
+
() => ({
|
|
964
|
+
references: mockReferences,
|
|
965
|
+
updateActiveReferences: (doc, index) => {
|
|
966
|
+
console.log("Citation active:", doc.title, index);
|
|
967
|
+
},
|
|
968
|
+
}),
|
|
969
|
+
[]
|
|
970
|
+
);
|
|
971
|
+
|
|
972
|
+
const additionalComponents: Components = React.useMemo(
|
|
973
|
+
() => ({
|
|
974
|
+
sup: CiteBlock,
|
|
975
|
+
mention: (props: { agentName: string; agentSId: string }) => (
|
|
976
|
+
<MentionBlock
|
|
977
|
+
agentName={props.agentName}
|
|
978
|
+
agentSId={props.agentSId}
|
|
979
|
+
onClick={handleMentionClick}
|
|
980
|
+
/>
|
|
981
|
+
),
|
|
982
|
+
}),
|
|
983
|
+
[handleMentionClick]
|
|
984
|
+
);
|
|
985
|
+
|
|
986
|
+
const additionalPlugins = React.useMemo(
|
|
987
|
+
() => [getCiteDirective(), getMentionDirective()],
|
|
988
|
+
[]
|
|
989
|
+
);
|
|
990
|
+
|
|
991
|
+
return (
|
|
992
|
+
<div style={{ display: "grid", gap: 12 }}>
|
|
993
|
+
<div>
|
|
994
|
+
<button
|
|
995
|
+
type="button"
|
|
996
|
+
onClick={() => setRestart((r) => r + 1)}
|
|
997
|
+
style={{
|
|
998
|
+
padding: "6px 10px",
|
|
999
|
+
borderRadius: 6,
|
|
1000
|
+
border: "1px solid var(--sb-border, #ddd)",
|
|
1001
|
+
background: "var(--sb-bg, #f7f7f7)",
|
|
1002
|
+
cursor: "pointer",
|
|
1003
|
+
}}
|
|
1004
|
+
>
|
|
1005
|
+
Restart Streaming
|
|
1006
|
+
</button>
|
|
1007
|
+
</div>
|
|
1008
|
+
<CitationsContext.Provider value={citationsContextValue}>
|
|
1009
|
+
<StreamingMarkdown
|
|
1010
|
+
key={restart}
|
|
1011
|
+
{...args}
|
|
1012
|
+
content={content}
|
|
1013
|
+
isStreaming={true}
|
|
1014
|
+
additionalMarkdownComponents={additionalComponents}
|
|
1015
|
+
additionalMarkdownPlugins={additionalPlugins}
|
|
1016
|
+
/>
|
|
1017
|
+
</CitationsContext.Provider>
|
|
1018
|
+
</div>
|
|
1019
|
+
);
|
|
1020
|
+
},
|
|
1021
|
+
args: {
|
|
1022
|
+
streamingSpeedMs: 30,
|
|
1023
|
+
chunkMin: 10,
|
|
1024
|
+
chunkMax: 50,
|
|
1025
|
+
seed: 42,
|
|
1026
|
+
} as any,
|
|
1027
|
+
argTypes: {
|
|
1028
|
+
streamingSpeedMs: {
|
|
1029
|
+
control: { type: "range", min: 10, max: 500, step: 10 },
|
|
1030
|
+
description: "Speed of streaming (ms between chunks)",
|
|
1031
|
+
},
|
|
1032
|
+
chunkMin: {
|
|
1033
|
+
control: { type: "range", min: 1, max: 50, step: 1 },
|
|
1034
|
+
description: "Minimum chunk size",
|
|
1035
|
+
},
|
|
1036
|
+
chunkMax: {
|
|
1037
|
+
control: { type: "range", min: 10, max: 200, step: 5 },
|
|
1038
|
+
description: "Maximum chunk size",
|
|
1039
|
+
},
|
|
1040
|
+
seed: {
|
|
1041
|
+
control: { type: "number" },
|
|
1042
|
+
description: "Random seed",
|
|
1043
|
+
},
|
|
1044
|
+
isStreaming: {
|
|
1045
|
+
control: "boolean",
|
|
1046
|
+
description: "Enable streaming animations",
|
|
1047
|
+
},
|
|
1048
|
+
} as any,
|
|
1049
|
+
};
|
|
1050
|
+
|
|
1051
|
+
export const ComprehensiveDemo: Story = {
|
|
1052
|
+
parameters: {
|
|
1053
|
+
docs: {
|
|
1054
|
+
description: {
|
|
1055
|
+
story:
|
|
1056
|
+
"Complete demonstration of all supported markdown features with streaming animation. Includes text formatting, lists (nested, ordered, unordered), code blocks with syntax highlighting, tables, blockquotes, images, links, special characters, emojis, and more.",
|
|
1057
|
+
},
|
|
1058
|
+
},
|
|
1059
|
+
},
|
|
1060
|
+
render: (args) => {
|
|
1061
|
+
const [content, setContent] = React.useState<string>("");
|
|
1062
|
+
const [restart, setRestart] = React.useState<number>(0);
|
|
1063
|
+
const {
|
|
1064
|
+
streamingSpeedMs = 50,
|
|
1065
|
+
chunkMin = 15,
|
|
1066
|
+
chunkMax = 80,
|
|
1067
|
+
seed = 42,
|
|
1068
|
+
} = args as any;
|
|
1069
|
+
|
|
1070
|
+
React.useEffect(() => {
|
|
1071
|
+
const full = LONG_MD.trim();
|
|
1072
|
+
setContent("");
|
|
1073
|
+
let i = 0;
|
|
1074
|
+
const rand = makeRng(Number(seed) || 42);
|
|
1075
|
+
const min = Math.max(1, Number(chunkMin) || 1);
|
|
1076
|
+
const max = Math.max(min, Number(chunkMax) || min);
|
|
1077
|
+
const step = Math.max(10, Number(streamingSpeedMs) || 120);
|
|
1078
|
+
const id = setInterval(() => {
|
|
1079
|
+
if (i >= full.length) {
|
|
1080
|
+
clearInterval(id);
|
|
1081
|
+
return;
|
|
1082
|
+
}
|
|
1083
|
+
// Pick a variable chunk size between [min, max]
|
|
1084
|
+
const r = rand();
|
|
1085
|
+
const size = Math.floor(min + r * (max - min));
|
|
1086
|
+
const next = full.slice(i, i + size);
|
|
1087
|
+
i += size;
|
|
1088
|
+
setContent((c) => c + next);
|
|
1089
|
+
}, step);
|
|
1090
|
+
return () => clearInterval(id);
|
|
1091
|
+
}, [streamingSpeedMs, chunkMin, chunkMax, seed, restart]);
|
|
1092
|
+
|
|
1093
|
+
return (
|
|
1094
|
+
<div style={{ display: "grid", gap: 12 }}>
|
|
1095
|
+
<div>
|
|
1096
|
+
<button
|
|
1097
|
+
type="button"
|
|
1098
|
+
onClick={() => {
|
|
1099
|
+
setRestart((r) => r + 1);
|
|
1100
|
+
}}
|
|
1101
|
+
style={{
|
|
1102
|
+
padding: "6px 10px",
|
|
1103
|
+
borderRadius: 6,
|
|
1104
|
+
border: "1px solid var(--sb-border, #ddd)",
|
|
1105
|
+
background: "var(--sb-bg, #f7f7f7)",
|
|
1106
|
+
cursor: "pointer",
|
|
1107
|
+
}}
|
|
1108
|
+
>
|
|
1109
|
+
Restart Streaming
|
|
1110
|
+
</button>
|
|
1111
|
+
</div>
|
|
1112
|
+
<StreamingMarkdown key={restart} {...args} content={content} />
|
|
1113
|
+
</div>
|
|
1114
|
+
);
|
|
1115
|
+
},
|
|
1116
|
+
args: {
|
|
1117
|
+
streamingSpeedMs: 50,
|
|
1118
|
+
chunkMin: 15,
|
|
1119
|
+
chunkMax: 80,
|
|
1120
|
+
seed: 42,
|
|
1121
|
+
} as any,
|
|
1122
|
+
argTypes: {
|
|
1123
|
+
streamingSpeedMs: {
|
|
1124
|
+
control: { type: "range", min: 10, max: 500, step: 10 },
|
|
1125
|
+
description: "Speed of streaming (ms between chunks)",
|
|
1126
|
+
},
|
|
1127
|
+
chunkMin: {
|
|
1128
|
+
control: { type: "range", min: 1, max: 50, step: 1 },
|
|
1129
|
+
description: "Minimum chunk size",
|
|
1130
|
+
},
|
|
1131
|
+
chunkMax: {
|
|
1132
|
+
control: { type: "range", min: 10, max: 200, step: 5 },
|
|
1133
|
+
description: "Maximum chunk size",
|
|
1134
|
+
},
|
|
1135
|
+
seed: {
|
|
1136
|
+
control: { type: "number" },
|
|
1137
|
+
description: "Random seed",
|
|
1138
|
+
},
|
|
1139
|
+
} as any,
|
|
1140
|
+
};
|
|
1141
|
+
|
|
1142
|
+
export const StreamingVsProductionComparison: Story = {
|
|
1143
|
+
parameters: {
|
|
1144
|
+
docs: {
|
|
1145
|
+
description: {
|
|
1146
|
+
story:
|
|
1147
|
+
"Side-by-side comparison of the new StreamingMarkdown component with the current production Markdown component, showing the same comprehensive content with streaming animation.",
|
|
1148
|
+
},
|
|
1149
|
+
},
|
|
1150
|
+
},
|
|
1151
|
+
render: (args) => {
|
|
1152
|
+
const [content, setContent] = React.useState<string>("");
|
|
1153
|
+
const [restart, setRestart] = React.useState<number>(0);
|
|
1154
|
+
const {
|
|
1155
|
+
streamingSpeedMs = 30,
|
|
1156
|
+
chunkMin = 10,
|
|
1157
|
+
chunkMax = 50,
|
|
1158
|
+
seed = 42,
|
|
1159
|
+
} = args as any;
|
|
1160
|
+
|
|
1161
|
+
React.useEffect(() => {
|
|
1162
|
+
const full = LONG_MD.trim();
|
|
1163
|
+
setContent("");
|
|
1164
|
+
let i = 0;
|
|
1165
|
+
const rand = makeRng(Number(seed) || 42);
|
|
1166
|
+
const min = Math.max(1, Number(chunkMin) || 1);
|
|
1167
|
+
const max = Math.max(min, Number(chunkMax) || min);
|
|
1168
|
+
const step = Math.max(10, Number(streamingSpeedMs) || 120);
|
|
1169
|
+
const id = setInterval(() => {
|
|
1170
|
+
if (i >= full.length) {
|
|
1171
|
+
clearInterval(id);
|
|
1172
|
+
return;
|
|
1173
|
+
}
|
|
1174
|
+
// Pick a variable chunk size between [min, max]
|
|
1175
|
+
const r = rand();
|
|
1176
|
+
const size = Math.floor(min + r * (max - min));
|
|
1177
|
+
const next = full.slice(i, i + size);
|
|
1178
|
+
i += size;
|
|
1179
|
+
setContent((c) => c + next);
|
|
1180
|
+
}, step);
|
|
1181
|
+
return () => clearInterval(id);
|
|
1182
|
+
}, [streamingSpeedMs, chunkMin, chunkMax, seed, restart]);
|
|
1183
|
+
|
|
1184
|
+
const handleMentionClick = React.useCallback((agentSId: string) => {
|
|
1185
|
+
console.log("Mention clicked:", agentSId);
|
|
1186
|
+
alert(`Clicked mention: ${agentSId}`);
|
|
1187
|
+
}, []);
|
|
1188
|
+
|
|
1189
|
+
const citationsContextValue: CitationsContextType = React.useMemo(
|
|
1190
|
+
() => ({
|
|
1191
|
+
references: mockReferences,
|
|
1192
|
+
updateActiveReferences: (doc, index) => {
|
|
1193
|
+
console.log("Citation active:", doc.title, index);
|
|
1194
|
+
},
|
|
1195
|
+
}),
|
|
1196
|
+
[]
|
|
1197
|
+
);
|
|
1198
|
+
|
|
1199
|
+
const additionalComponents: Components = React.useMemo(
|
|
1200
|
+
() => ({
|
|
1201
|
+
sup: CiteBlock,
|
|
1202
|
+
mention: (props: { agentName: string; agentSId: string }) => (
|
|
1203
|
+
<MentionBlock
|
|
1204
|
+
agentName={props.agentName}
|
|
1205
|
+
agentSId={props.agentSId}
|
|
1206
|
+
onClick={handleMentionClick}
|
|
1207
|
+
/>
|
|
1208
|
+
),
|
|
1209
|
+
}),
|
|
1210
|
+
[handleMentionClick]
|
|
1211
|
+
);
|
|
1212
|
+
|
|
1213
|
+
const additionalPlugins = React.useMemo(
|
|
1214
|
+
() => [getCiteDirective(), getMentionDirective()],
|
|
1215
|
+
[]
|
|
1216
|
+
);
|
|
1217
|
+
|
|
1218
|
+
// Determine if content is still streaming
|
|
1219
|
+
const isStillStreaming = content.length < LONG_MD.trim().length;
|
|
1220
|
+
|
|
1221
|
+
return (
|
|
1222
|
+
<div style={{ display: "flex", flexDirection: "column", gap: 20 }}>
|
|
1223
|
+
<div style={{ display: "flex", alignItems: "center", gap: 10 }}>
|
|
1224
|
+
<button
|
|
1225
|
+
type="button"
|
|
1226
|
+
onClick={() => setRestart((r) => r + 1)}
|
|
1227
|
+
style={{
|
|
1228
|
+
padding: "8px 16px",
|
|
1229
|
+
borderRadius: 6,
|
|
1230
|
+
border: "1px solid var(--sb-border, #ddd)",
|
|
1231
|
+
background: "var(--sb-bg, #f7f7f7)",
|
|
1232
|
+
cursor: "pointer",
|
|
1233
|
+
fontWeight: 500,
|
|
1234
|
+
}}
|
|
1235
|
+
>
|
|
1236
|
+
🔄 Restart Streaming
|
|
1237
|
+
</button>
|
|
1238
|
+
<span
|
|
1239
|
+
style={{
|
|
1240
|
+
display: "inline-flex",
|
|
1241
|
+
alignItems: "center",
|
|
1242
|
+
gap: 6,
|
|
1243
|
+
padding: "4px 10px",
|
|
1244
|
+
borderRadius: 20,
|
|
1245
|
+
background: isStillStreaming ? "#fef3c7" : "#d1fae5",
|
|
1246
|
+
color: isStillStreaming ? "#92400e" : "#065f46",
|
|
1247
|
+
fontSize: 14,
|
|
1248
|
+
fontWeight: 500,
|
|
1249
|
+
}}
|
|
1250
|
+
>
|
|
1251
|
+
{isStillStreaming ? (
|
|
1252
|
+
<>
|
|
1253
|
+
<span
|
|
1254
|
+
style={{
|
|
1255
|
+
display: "inline-block",
|
|
1256
|
+
width: 8,
|
|
1257
|
+
height: 8,
|
|
1258
|
+
borderRadius: "50%",
|
|
1259
|
+
background: "#f59e0b",
|
|
1260
|
+
animation: "pulse 1.5s infinite",
|
|
1261
|
+
}}
|
|
1262
|
+
></span>
|
|
1263
|
+
Streaming...{" "}
|
|
1264
|
+
{Math.round((content.length / LONG_MD.trim().length) * 100)}%
|
|
1265
|
+
</>
|
|
1266
|
+
) : (
|
|
1267
|
+
<>
|
|
1268
|
+
<span
|
|
1269
|
+
style={{
|
|
1270
|
+
display: "inline-block",
|
|
1271
|
+
width: 8,
|
|
1272
|
+
height: 8,
|
|
1273
|
+
borderRadius: "50%",
|
|
1274
|
+
background: "#10b981",
|
|
1275
|
+
}}
|
|
1276
|
+
></span>
|
|
1277
|
+
Complete
|
|
1278
|
+
</>
|
|
1279
|
+
)}
|
|
1280
|
+
</span>
|
|
1281
|
+
</div>
|
|
1282
|
+
|
|
1283
|
+
<div
|
|
1284
|
+
style={{
|
|
1285
|
+
display: "grid",
|
|
1286
|
+
gridTemplateColumns: "1fr 1fr",
|
|
1287
|
+
gap: 20,
|
|
1288
|
+
minHeight: 600,
|
|
1289
|
+
}}
|
|
1290
|
+
>
|
|
1291
|
+
{/* New StreamingMarkdown */}
|
|
1292
|
+
<div
|
|
1293
|
+
style={{
|
|
1294
|
+
border: "2px solid #3b82f6",
|
|
1295
|
+
borderRadius: 8,
|
|
1296
|
+
padding: 16,
|
|
1297
|
+
background: "var(--sb-bg-secondary, #fafafa)",
|
|
1298
|
+
position: "relative",
|
|
1299
|
+
overflow: "auto",
|
|
1300
|
+
}}
|
|
1301
|
+
>
|
|
1302
|
+
<div
|
|
1303
|
+
style={{
|
|
1304
|
+
position: "sticky",
|
|
1305
|
+
top: -16,
|
|
1306
|
+
background: "var(--sb-bg-secondary, #fafafa)",
|
|
1307
|
+
padding: "8px 0 12px",
|
|
1308
|
+
marginTop: -8,
|
|
1309
|
+
marginBottom: 16,
|
|
1310
|
+
borderBottom: "1px solid #e5e7eb",
|
|
1311
|
+
zIndex: 10,
|
|
1312
|
+
}}
|
|
1313
|
+
>
|
|
1314
|
+
<h3
|
|
1315
|
+
style={{
|
|
1316
|
+
margin: 0,
|
|
1317
|
+
color: "#3b82f6",
|
|
1318
|
+
fontSize: 16,
|
|
1319
|
+
fontWeight: 600,
|
|
1320
|
+
display: "flex",
|
|
1321
|
+
alignItems: "center",
|
|
1322
|
+
gap: 8,
|
|
1323
|
+
}}
|
|
1324
|
+
>
|
|
1325
|
+
✨ NEW: StreamingMarkdown
|
|
1326
|
+
{isStillStreaming && (
|
|
1327
|
+
<span
|
|
1328
|
+
style={{
|
|
1329
|
+
fontSize: 12,
|
|
1330
|
+
background: "#3b82f6",
|
|
1331
|
+
color: "white",
|
|
1332
|
+
padding: "2px 8px",
|
|
1333
|
+
borderRadius: 12,
|
|
1334
|
+
}}
|
|
1335
|
+
>
|
|
1336
|
+
with animation
|
|
1337
|
+
</span>
|
|
1338
|
+
)}
|
|
1339
|
+
</h3>
|
|
1340
|
+
<p style={{ margin: "4px 0 0", fontSize: 12, color: "#6b7280" }}>
|
|
1341
|
+
Token-based streaming with character animation
|
|
1342
|
+
</p>
|
|
1343
|
+
</div>
|
|
1344
|
+
<CitationsContext.Provider value={citationsContextValue}>
|
|
1345
|
+
<StreamingMarkdown
|
|
1346
|
+
key={`streaming-${restart}`}
|
|
1347
|
+
{...args}
|
|
1348
|
+
content={content}
|
|
1349
|
+
isStreaming={isStillStreaming}
|
|
1350
|
+
additionalMarkdownComponents={additionalComponents}
|
|
1351
|
+
additionalMarkdownPlugins={additionalPlugins}
|
|
1352
|
+
/>
|
|
1353
|
+
</CitationsContext.Provider>
|
|
1354
|
+
</div>
|
|
1355
|
+
|
|
1356
|
+
{/* Current Production Markdown */}
|
|
1357
|
+
<div
|
|
1358
|
+
style={{
|
|
1359
|
+
border: "2px solid #6b7280",
|
|
1360
|
+
borderRadius: 8,
|
|
1361
|
+
padding: 16,
|
|
1362
|
+
background: "var(--sb-bg-secondary, #fafafa)",
|
|
1363
|
+
position: "relative",
|
|
1364
|
+
overflow: "auto",
|
|
1365
|
+
}}
|
|
1366
|
+
>
|
|
1367
|
+
<div
|
|
1368
|
+
style={{
|
|
1369
|
+
position: "sticky",
|
|
1370
|
+
top: -16,
|
|
1371
|
+
background: "var(--sb-bg-secondary, #fafafa)",
|
|
1372
|
+
padding: "8px 0 12px",
|
|
1373
|
+
marginTop: -8,
|
|
1374
|
+
marginBottom: 16,
|
|
1375
|
+
borderBottom: "1px solid #e5e7eb",
|
|
1376
|
+
zIndex: 10,
|
|
1377
|
+
}}
|
|
1378
|
+
>
|
|
1379
|
+
<h3
|
|
1380
|
+
style={{
|
|
1381
|
+
margin: 0,
|
|
1382
|
+
color: "#6b7280",
|
|
1383
|
+
fontSize: 16,
|
|
1384
|
+
fontWeight: 600,
|
|
1385
|
+
display: "flex",
|
|
1386
|
+
alignItems: "center",
|
|
1387
|
+
gap: 8,
|
|
1388
|
+
}}
|
|
1389
|
+
>
|
|
1390
|
+
📋 CURRENT: Production Markdown
|
|
1391
|
+
{isStillStreaming && (
|
|
1392
|
+
<span
|
|
1393
|
+
style={{
|
|
1394
|
+
fontSize: 12,
|
|
1395
|
+
background: "#6b7280",
|
|
1396
|
+
color: "white",
|
|
1397
|
+
padding: "2px 8px",
|
|
1398
|
+
borderRadius: 12,
|
|
1399
|
+
}}
|
|
1400
|
+
>
|
|
1401
|
+
isStreaming={isStillStreaming}
|
|
1402
|
+
</span>
|
|
1403
|
+
)}
|
|
1404
|
+
</h3>
|
|
1405
|
+
<p style={{ margin: "4px 0 0", fontSize: 12, color: "#6b7280" }}>
|
|
1406
|
+
Currently used in production for agent messages
|
|
1407
|
+
</p>
|
|
1408
|
+
</div>
|
|
1409
|
+
<CitationsContext.Provider value={citationsContextValue}>
|
|
1410
|
+
<Markdown
|
|
1411
|
+
key={`production-${restart}`}
|
|
1412
|
+
content={content}
|
|
1413
|
+
isStreaming={isStillStreaming}
|
|
1414
|
+
additionalMarkdownComponents={additionalComponents}
|
|
1415
|
+
additionalMarkdownPlugins={additionalPlugins}
|
|
1416
|
+
/>
|
|
1417
|
+
</CitationsContext.Provider>
|
|
1418
|
+
</div>
|
|
1419
|
+
</div>
|
|
1420
|
+
|
|
1421
|
+
<style>{`
|
|
1422
|
+
@keyframes pulse {
|
|
1423
|
+
0%, 100% { opacity: 1; }
|
|
1424
|
+
50% { opacity: 0.5; }
|
|
1425
|
+
}
|
|
1426
|
+
`}</style>
|
|
1427
|
+
</div>
|
|
1428
|
+
);
|
|
1429
|
+
},
|
|
1430
|
+
args: {
|
|
1431
|
+
streamingSpeedMs: 30,
|
|
1432
|
+
chunkMin: 10,
|
|
1433
|
+
chunkMax: 50,
|
|
1434
|
+
seed: 42,
|
|
1435
|
+
} as any,
|
|
1436
|
+
argTypes: {
|
|
1437
|
+
streamingSpeedMs: {
|
|
1438
|
+
control: { type: "range", min: 10, max: 500, step: 10 },
|
|
1439
|
+
description: "Speed of streaming (ms between chunks)",
|
|
1440
|
+
},
|
|
1441
|
+
chunkMin: {
|
|
1442
|
+
control: { type: "range", min: 1, max: 50, step: 1 },
|
|
1443
|
+
description: "Minimum chunk size",
|
|
1444
|
+
},
|
|
1445
|
+
chunkMax: {
|
|
1446
|
+
control: { type: "range", min: 10, max: 200, step: 5 },
|
|
1447
|
+
description: "Maximum chunk size",
|
|
1448
|
+
},
|
|
1449
|
+
seed: {
|
|
1450
|
+
control: { type: "number" },
|
|
1451
|
+
description: "Random seed for chunk sizes",
|
|
1452
|
+
},
|
|
1453
|
+
} as any,
|
|
1454
|
+
};
|