@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.
Files changed (65) hide show
  1. package/dist/cjs/index.js +1 -1
  2. package/dist/esm/components/LoadingBlock.js +1 -1
  3. package/dist/esm/components/LoadingBlock.js.map +1 -1
  4. package/dist/esm/components/markdown/Markdown.d.ts +14 -0
  5. package/dist/esm/components/markdown/Markdown.d.ts.map +1 -1
  6. package/dist/esm/components/markdown/Markdown.js +18 -7
  7. package/dist/esm/components/markdown/Markdown.js.map +1 -1
  8. package/dist/esm/components/streaming/BlockStreamer.d.ts +8 -0
  9. package/dist/esm/components/streaming/BlockStreamer.d.ts.map +1 -0
  10. package/dist/esm/components/streaming/BlockStreamer.js +40 -0
  11. package/dist/esm/components/streaming/BlockStreamer.js.map +1 -0
  12. package/dist/esm/components/streaming/StreamingListItem.d.ts +14 -0
  13. package/dist/esm/components/streaming/StreamingListItem.d.ts.map +1 -0
  14. package/dist/esm/components/streaming/StreamingListItem.js +93 -0
  15. package/dist/esm/components/streaming/StreamingListItem.js.map +1 -0
  16. package/dist/esm/components/streaming/StreamingMarkdown.d.ts +5 -0
  17. package/dist/esm/components/streaming/StreamingMarkdown.d.ts.map +1 -0
  18. package/dist/esm/components/streaming/StreamingMarkdown.js +80 -0
  19. package/dist/esm/components/streaming/StreamingMarkdown.js.map +1 -0
  20. package/dist/esm/components/streaming/StreamingParagraph.d.ts +14 -0
  21. package/dist/esm/components/streaming/StreamingParagraph.d.ts.map +1 -0
  22. package/dist/esm/components/streaming/StreamingParagraph.js +41 -0
  23. package/dist/esm/components/streaming/StreamingParagraph.js.map +1 -0
  24. package/dist/esm/components/streaming/index.d.ts +3 -0
  25. package/dist/esm/components/streaming/index.d.ts.map +1 -0
  26. package/dist/esm/components/streaming/index.js +2 -0
  27. package/dist/esm/components/streaming/index.js.map +1 -0
  28. package/dist/esm/components/streaming/markdownComponents.d.ts +11 -0
  29. package/dist/esm/components/streaming/markdownComponents.d.ts.map +1 -0
  30. package/dist/esm/components/streaming/markdownComponents.js +85 -0
  31. package/dist/esm/components/streaming/markdownComponents.js.map +1 -0
  32. package/dist/esm/components/streaming/types.d.ts +70 -0
  33. package/dist/esm/components/streaming/types.d.ts.map +1 -0
  34. package/dist/esm/components/streaming/types.js +10 -0
  35. package/dist/esm/components/streaming/types.js.map +1 -0
  36. package/dist/esm/components/streaming/utils.d.ts +18 -0
  37. package/dist/esm/components/streaming/utils.d.ts.map +1 -0
  38. package/dist/esm/components/streaming/utils.js +97 -0
  39. package/dist/esm/components/streaming/utils.js.map +1 -0
  40. package/dist/esm/index.d.ts +1 -0
  41. package/dist/esm/index.d.ts.map +1 -1
  42. package/dist/esm/index.js +1 -0
  43. package/dist/esm/index.js.map +1 -1
  44. package/dist/esm/stories/StreamingMarkdown.stories.d.ts +12 -0
  45. package/dist/esm/stories/StreamingMarkdown.stories.d.ts.map +1 -0
  46. package/dist/esm/stories/StreamingMarkdown.stories.js +620 -0
  47. package/dist/esm/stories/StreamingMarkdown.stories.js.map +1 -0
  48. package/dist/esm/styles/global.css +43 -0
  49. package/dist/esm/styles/tailwind.css +43 -0
  50. package/dist/sparkle.css +56 -0
  51. package/package.json +2 -1
  52. package/src/components/LoadingBlock.tsx +1 -1
  53. package/src/components/markdown/Markdown.tsx +35 -12
  54. package/src/components/streaming/BlockStreamer.tsx +61 -0
  55. package/src/components/streaming/StreamingListItem.tsx +176 -0
  56. package/src/components/streaming/StreamingMarkdown.tsx +126 -0
  57. package/src/components/streaming/StreamingParagraph.tsx +104 -0
  58. package/src/components/streaming/index.ts +2 -0
  59. package/src/components/streaming/markdownComponents.tsx +270 -0
  60. package/src/components/streaming/types.ts +72 -0
  61. package/src/components/streaming/utils.ts +126 -0
  62. package/src/index.ts +1 -0
  63. package/src/stories/StreamingMarkdown.stories.tsx +1454 -0
  64. package/src/styles/global.css +43 -0
  65. 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
+ ![Sample Image](https://via.placeholder.com/600x300/4A90E2/FFFFFF?text=Streaming+Markdown)
796
+
797
+ ### Multiple Images
798
+
799
+ ![Small Image 1](https://via.placeholder.com/200x150/E94B3C/FFFFFF?text=Image+1)
800
+ ![Small Image 2](https://via.placeholder.com/200x150/6DBD28/FFFFFF?text=Image+2)
801
+ ![Small Image 3](https://via.placeholder.com/200x150/F39C12/FFFFFF?text=Image+3)
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: &lt;tag&gt; &amp; &quot;quotes&quot; &apos;apostrophe&apos;
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
+ ![Tiny](https://via.placeholder.com/100x50)
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
+ };