@mastra/playground-ui 51.2.0-alpha.1 → 51.3.0-alpha.0

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 (44) hide show
  1. package/CHANGELOG.md +107 -0
  2. package/dist/components/LogsDataList.cjs.js +1 -0
  3. package/dist/components/LogsDataList.cjs.js.map +1 -1
  4. package/dist/components/LogsDataList.es.js +1 -0
  5. package/dist/components/LogsDataList.es.js.map +1 -1
  6. package/dist/components/MarkdownRenderer.cjs.js +3 -65
  7. package/dist/components/MarkdownRenderer.cjs.js.map +1 -1
  8. package/dist/components/MarkdownRenderer.es.js +1 -67
  9. package/dist/components/MarkdownRenderer.es.js.map +1 -1
  10. package/dist/components/ai/message-reveal.cjs.js +50 -0
  11. package/dist/components/ai/message-reveal.cjs.js.map +1 -0
  12. package/dist/components/ai/message-reveal.d.ts +2 -0
  13. package/dist/components/ai/message-reveal.es.js +44 -0
  14. package/dist/components/ai/message-reveal.es.js.map +1 -0
  15. package/dist/src/ds/components/Arrival/arrival.stories.d.ts +6 -0
  16. package/dist/src/ds/components/Card/card.stories.d.ts +8 -0
  17. package/dist/src/ds/components/Columns/columns.stories.d.ts +7 -0
  18. package/dist/src/ds/components/DataDetailsPanel/data-details-panel.stories.d.ts +9 -0
  19. package/dist/src/ds/components/DataFilter/select-data-filter.stories.d.ts +8 -0
  20. package/dist/src/ds/components/DateRangeTimeline/date-range-timeline.stories.d.ts +8 -0
  21. package/dist/src/ds/components/ErrorState/error-state.stories.d.ts +7 -0
  22. package/dist/src/ds/components/ListSearch/list-search.stories.d.ts +7 -0
  23. package/dist/src/ds/components/LogsDataList/logs-data-list.stories.d.ts +8 -0
  24. package/dist/src/ds/components/MessageScroller/message-scroller.stories.d.ts +7 -0
  25. package/dist/src/ds/components/MetricsLineChart/metrics-line-chart.stories.d.ts +8 -0
  26. package/dist/src/ds/components/PageHeader/page-header.stories.d.ts +8 -0
  27. package/dist/src/ds/components/PageLayout/page-layout.stories.d.ts +8 -0
  28. package/dist/src/ds/components/PendingIndicator/pending-indicator.stories.d.ts +7 -0
  29. package/dist/src/ds/components/PermissionDenied/permission-denied.stories.d.ts +8 -0
  30. package/dist/src/ds/components/PrevNextNav/prev-next-nav.stories.d.ts +8 -0
  31. package/dist/src/ds/components/SessionExpired/session-expired.stories.d.ts +7 -0
  32. package/dist/src/ds/components/Shimmer/shimmer.stories.d.ts +7 -0
  33. package/dist/src/ds/components/StatusBadge/status-badge.stories.d.ts +8 -0
  34. package/dist/src/ds/components/ThreadList/thread-list.stories.d.ts +8 -0
  35. package/dist/src/ds/components/ThreadRail/thread-rail.stories.d.ts +7 -0
  36. package/dist/src/ds/components/TokenBudget/token-budget.stories.d.ts +8 -0
  37. package/dist/src/ds/components/ai/message-reveal/index.d.ts +1 -0
  38. package/dist/src/ds/components/ai/message-reveal/message-reveal.d.ts +22 -0
  39. package/dist/style.css +80 -0
  40. package/dist/use-reveal-Sa456jZb.cjs +70 -0
  41. package/dist/use-reveal-Sa456jZb.cjs.map +1 -0
  42. package/dist/use-reveal-d77hxHz9.js +68 -0
  43. package/dist/use-reveal-d77hxHz9.js.map +1 -0
  44. package/package.json +7 -7
package/CHANGELOG.md CHANGED
@@ -1,5 +1,112 @@
1
1
  # @mastra/playground-ui
2
2
 
3
+ ## 51.3.0-alpha.0
4
+
5
+ ### Minor Changes
6
+
7
+ - Added `@mastra/playground-ui/components/ai/message-reveal`, which paces a whole assistant message — prose, reasoning, tool rows, cards — on one clock, so its parts arrive in the order the model wrote them. ([#22408](https://github.com/mastra-ai/mastra/pull/22408))
8
+
9
+ Before, a renderer paced its own text with `useRevealedText`, which could only slow prose down: everything written between two passages landed at once, so a tool row appeared while the sentence before it was still being typed out.
10
+
11
+ ```tsx
12
+ import { useRevealedParts } from '@mastra/playground-ui/components/ai/message-reveal';
13
+
14
+ const parts = useRevealedParts(message.content.parts, streaming);
15
+
16
+ <MessageFactory message={{ ...message, content: { ...message.content, parts } }} {...renderers} />;
17
+ ```
18
+
19
+ The projection happens above the renderers, so none of them has to know a reveal is running.
20
+
21
+ ### Patch Changes
22
+
23
+ - Added Storybook examples for every published Playground UI component that did not already have coverage. ([#22422](https://github.com/mastra-ai/mastra/pull/22422))
24
+
25
+ - Updated dependencies [[`bae1502`](https://github.com/mastra-ai/mastra/commit/bae150254b06a4da6964d7c137af97f336362359)]:
26
+ - @mastra/core@1.63.1-alpha.0
27
+ - @mastra/client-js@1.42.3-alpha.0
28
+ - @mastra/react@1.4.8-alpha.0
29
+
30
+ ## 51.2.0
31
+
32
+ ### Minor Changes
33
+
34
+ - Refreshed the DataList look: rows no longer draw separators, the sticky header and group subheaders share one lighter, theme-tuned band, and every corner uses the same radius. ([#22383](https://github.com/mastra-ai/mastra/pull/22383))
35
+
36
+ **Every row in a list is now the same height.** Row height used to be whatever the tallest cell happened to be, so a list with an actions column or a roomier cell drifted a few pixels away from the list next to it. The row owns its height now, and skeleton rows match the real ones — no jump when data lands.
37
+
38
+ **Typography follows the content.** Headers and name cells are medium weight instead of semibold, and the letter-spacing tightening is gone. IDs and timestamps left the monospace face — IDs get wider tracking to stay scannable, timestamps get tabular figures so their columns still line up.
39
+
40
+ **Row controls only appear on hover.** Selection checkboxes on unselected rows, and the new `DataList.ActionsCell`, stay hidden until you hover the row or reach it with the keyboard, so a resting list reads as content. The column keeps its width, so nothing shifts. On a touch screen, where nothing ever hovers, they stay visible.
41
+
42
+ ```tsx
43
+ // Trailing row actions — alignment, spacing and the hover reveal come with the cell
44
+ <DataList.RowWrapper>
45
+ <DataList.RowButton colEnd={-2} onClick={open}>
46
+ {cells}
47
+ </DataList.RowButton>
48
+ <DataList.ActionsCell>
49
+ <Button variant="ghost" size="icon-xs" aria-label="Delete">
50
+ <Trash2 />
51
+ </Button>
52
+ </DataList.ActionsCell>
53
+ </DataList.RowWrapper>
54
+ ```
55
+
56
+ **Breaking:** a variant, three props and a component that no longer decide anything are gone.
57
+
58
+ ```tsx
59
+ // Before
60
+ <DataList columns={columns} variant="lined">
61
+ <DataList.RowButton flushLeft flushRight colEnd={-2}>
62
+ <DataList.Cell height="compact">{status}</DataList.Cell>
63
+ <DataList.MonoCell>{path}</DataList.MonoCell>
64
+
65
+ // After — rows are borderless, full-bleed and uniformly tall by default
66
+ <DataList columns={columns}>
67
+ <DataList.RowButton colEnd={-2}>
68
+ <DataList.Cell>{status}</DataList.Cell>
69
+ <DataList.TextCell font="mono">{path}</DataList.TextCell>
70
+ ```
71
+
72
+ - `variant="lined"` existed only to draw row separators. Lists that want row banding can still ask for `variant="striped"`.
73
+ - `height` on cells set row height one cell at a time, which is what let rows drift apart.
74
+ - `flushLeft` / `flushRight` dropped a row margin that no longer exists.
75
+ - `DataList.MonoCell` was a cell that only changed the typeface. `DataList.TextCell font="mono"` replaces it — same rendering, one less component.
76
+
77
+ ### Patch Changes
78
+
79
+ - Added a `Comment` family of compound components for text-only comment threads: `Comment`, `CommentList`, `CommentItem` (with header, author, timestamp, body and actions slots) and `CommentComposer` (input + send button). The root `variant` prop switches between `default`, a flat thread with hover-revealed per-item actions, and `embed`, the same thread on a bordered card surface with a compact composer and no per-item actions. ([#22396](https://github.com/mastra-ai/mastra/pull/22396))
80
+
81
+ - Improved the experiment page in Studio with a horizontal metadata bar showing when the run started, how long it took, the dataset it ran against, and which scorers were used ([#22377](https://github.com/mastra-ai/mastra/pull/22377))
82
+
83
+ - **Composer** — the ring keeps its geometry on the element, so a consumer can restyle its radius and width through `className` like any other component. Its idle edge colour now reads `--composer-ring-edge`, for apps whose chat surface needs a different one; unset, it keeps the current blend. ([#22403](https://github.com/mastra-ai/mastra/pull/22403))
84
+
85
+ **Tool call** — output blocks sit one step above the surface behind them instead of painting a fixed dark grey, so they stay visible on any chat background and in both themes.
86
+
87
+ - Sidebar links read a touch heavier, so they stay legible next to their icons instead of looking washed out. ([#22399](https://github.com/mastra-ai/mastra/pull/22399))
88
+
89
+ - Fixed the span Feedback tab in Studio's observability view showing the same list and count for every span of a trace: it now loads feedback strictly for the selected trace and span pair, so switching spans shows that span's own feedback. Trace-level feedback — including everything produced by the dataset review flows — now lives in a new Feedback tab on the trace panel, next to Details and Evaluations, which lists only records that aren't attached to a span. The span panel tabs also use the same pill styling as the rest of Studio instead of the deprecated underline variant. ([#22401](https://github.com/mastra-ai/mastra/pull/22401))
90
+
91
+ To see each kind of feedback:
92
+
93
+ 1. Run `mastra dev` and open **Observability** in Studio.
94
+ 2. Open a trace and select its **Feedback** tab — it lists trace-level feedback only, such as records left by dataset review.
95
+ 3. Select a span in the trace tree and open the span's **Feedback** tab — it lists only that span's feedback, and the list changes as you select other spans.
96
+
97
+ - Added a `factory` variant to the `SettingsRow` component so settings rows can render the denser card-row layout (row padding, 12px description text) instead of only the studio spacing. ([#22375](https://github.com/mastra-ai/mastra/pull/22375))
98
+
99
+ ```tsx
100
+ <SettingsRow variant="factory" label="Auto-approve tools" description="Run tool calls without asking">
101
+ <Switch checked={autoApprove} onCheckedChange={setAutoApprove} />
102
+ </SettingsRow>
103
+ ```
104
+
105
+ - Updated dependencies [[`7176362`](https://github.com/mastra-ai/mastra/commit/717636281a3339911a05ea2cc8ae38afe4fd2cef), [`9045b8f`](https://github.com/mastra-ai/mastra/commit/9045b8fdf622e1d735b96ddd6500bd32556636d9), [`7677a2c`](https://github.com/mastra-ai/mastra/commit/7677a2cd47729221ca28afc5067d26e22d925b59), [`e3b796d`](https://github.com/mastra-ai/mastra/commit/e3b796d29a63f0d5c97dd815aadec40687346d70), [`f7a7467`](https://github.com/mastra-ai/mastra/commit/f7a74678193921e7ea4790232d707b3237626cac), [`49ccd14`](https://github.com/mastra-ai/mastra/commit/49ccd142268a61fb55ea75bc76287643a21f3677), [`f9c56f3`](https://github.com/mastra-ai/mastra/commit/f9c56f336ee8c250763a438990f8e60a428353c9), [`3855b38`](https://github.com/mastra-ai/mastra/commit/3855b38c4c25af32ab8e298e148becc963abe92c)]:
106
+ - @mastra/core@1.63.0
107
+ - @mastra/client-js@1.42.2
108
+ - @mastra/react@1.4.7
109
+
3
110
  ## 51.2.0-alpha.1
4
111
 
5
112
  ### Patch Changes
@@ -4,6 +4,7 @@ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
4
 
5
5
  const logsDataList = require('../logs-data-list-Xayqbjw4.cjs');
6
6
  const dataListSkeleton = require('../data-list-skeleton-BHBSIQHN.cjs');
7
+ require('react');
7
8
 
8
9
 
9
10
 
@@ -1 +1 @@
1
- {"version":3,"file":"LogsDataList.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
1
+ {"version":3,"file":"LogsDataList.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;;"}
@@ -1,3 +1,4 @@
1
1
  export { L as LogsDataList } from '../logs-data-list-DRXJ89IZ.js';
2
2
  export { D as LogsDataListSkeleton } from '../data-list-skeleton-DDO_OE14.js';
3
+ import 'react';
3
4
  //# sourceMappingURL=LogsDataList.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"LogsDataList.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
1
+ {"version":3,"file":"LogsDataList.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;"}
@@ -3,72 +3,10 @@
3
3
  Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
4
 
5
5
  const markdownRenderer = require('../markdown-renderer-BQugZq4m.cjs');
6
- const React = require('react');
6
+ const useReveal = require('../use-reveal-Sa456jZb.cjs');
7
+
7
8
 
8
- const BUFFER_MS = 1e3;
9
- const CORRECT_MS = 1e3;
10
- const FLOW_MS = 700;
11
- const EASE_MS = 400;
12
- const SLOWEST = 10;
13
- const FASTEST = 60;
14
- const MAX_LAG = 300;
15
- function measure(flow, now, gained) {
16
- const elapsed = Math.max(0, now - flow.at);
17
- flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1e3);
18
- flow.at = Math.max(flow.at, now);
19
- return flow.rate;
20
- }
21
- function prefersReducedMotion() {
22
- if (typeof window === "undefined" || !window.matchMedia) return false;
23
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
24
- }
25
- function wordStops(text) {
26
- const stops = [0];
27
- const word = /\S+/g;
28
- for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);
29
- return stops;
30
- }
31
- function useRevealedText(text, streaming) {
32
- const [calm] = React.useState(prefersReducedMotion);
33
- const stops = React.useMemo(() => wordStops(text), [text]);
34
- const words = stops.length - 1;
35
- const [landed, setLanded] = React.useState(() => streaming && !calm ? 0 : words);
36
- const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));
37
- if (shown !== landed) setLanded(shown);
38
- const chasing = shown < words;
39
- const goal = React.useRef(words);
40
- const cursor = React.useRef(shown);
41
- const flow = React.useRef({ rate: 0, at: 0 });
42
- const pace = React.useRef(SLOWEST);
43
- React.useEffect(() => {
44
- measure(flow.current, performance.now(), Math.max(0, words - goal.current));
45
- goal.current = words;
46
- if (cursor.current < shown || cursor.current > words) cursor.current = shown;
47
- });
48
- React.useEffect(() => {
49
- if (!chasing) return;
50
- let drawn = performance.now();
51
- let frame = requestAnimationFrame(function step(now) {
52
- const elapsed = Math.max(0, now - drawn);
53
- drawn = Math.max(drawn, now);
54
- const arriving = measure(flow.current, now, 0);
55
- const from = cursor.current;
56
- const behind = goal.current - from;
57
- if (behind > 0) {
58
- const held = arriving * BUFFER_MS / 1e3;
59
- const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + (behind - held) * 1e3 / CORRECT_MS));
60
- pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);
61
- cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + elapsed * pace.current / 1e3);
62
- if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));
63
- }
64
- frame = requestAnimationFrame(step);
65
- });
66
- return () => cancelAnimationFrame(frame);
67
- }, [chasing]);
68
- if (!chasing) return text;
69
- return text.slice(0, stops[shown]);
70
- }
71
9
 
72
10
  exports.MarkdownRenderer = markdownRenderer.MarkdownRenderer;
73
- exports.useRevealedText = useRevealedText;
11
+ exports.useRevealedText = useReveal.useRevealedText;
74
12
  //# sourceMappingURL=MarkdownRenderer.cjs.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownRenderer.cjs.js","sources":["../../src/ds/components/MarkdownRenderer/use-reveal.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\n\n/** How much of the reply the reveal aims to be holding back, as time at the speed it is playing. */\nconst BUFFER_MS = 1000;\n\n/** How long it takes to work off a buffer that is too full or too empty. */\nconst CORRECT_MS = 1000;\n\n/** How long a word keeps counting towards the speed the reply is arriving at. */\nconst FLOW_MS = 700;\n\n/** How long the reveal takes to adopt a new speed, so a change of pace is never a jolt. */\nconst EASE_MS = 400;\n\n/** Words per second it will not go below: a short burst between two tool calls still reads briskly. */\nconst SLOWEST = 10;\n\n/** Words per second it will not go past: one word a frame, all a screen can draw. */\nconst FASTEST = 60;\n\n/** Words it will never fall further behind than — about five seconds at full speed. */\nconst MAX_LAG = 300;\n\ninterface Flow {\n /** Words per second the reply is arriving at, as an exponential average. */\n rate: number;\n at: number;\n}\n\n/** Decays what earlier words still count for, then deposits the ones that just arrived. */\nfunction measure(flow: Flow, now: number, gained: number): number {\n // rAF timestamps and performance.now() are sampled at different points of a\n // frame, so a fresh loop can read time slightly behind the last deposit.\n const elapsed = Math.max(0, now - flow.at);\n flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1000);\n flow.at = Math.max(flow.at, now);\n\n return flow.rate;\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Where the reveal can cut the text: before the first word, then after each one. */\nfunction wordStops(text: string): number[] {\n const stops = [0];\n const word = /\\S+/g;\n\n for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);\n\n return stops;\n}\n\n/**\n * A jitter buffer for a streamed reply, counted in words. Chunks reach the\n * browser unevenly — a proxy flushes, a tool call ends, the model changes pace —\n * and drawing each one on arrival makes a reply lurch: ten words at once, then\n * nothing for a fifth of a second.\n *\n * So the reveal runs on its own clock: a frame loop that chunks never interrupt,\n * laying down at most one word per frame however far behind it is. It plays at\n * the speed the reply is arriving at, measured directly, and leans on that only\n * to work the buffer back to `BUFFER_MS` of held text when it drifts. Measuring\n * the speed rather than inferring it from the buffer is what lets the buffer stay\n * shallow: a reply that lasts two seconds still finds its pace in the first few\n * hundred milliseconds, where a deeper buffer would spend the whole reply filling\n * and then sprint to catch up.\n *\n * The reply it paces is whatever the caller hands it, so a caller with more than\n * prose to lay down — a tool row between two paragraphs — paces the whole of it\n * from one place and draws the rest against the same cursor.\n *\n * Nothing here reads `streaming` past the first render: a reply that stops\n * arriving is one whose measured speed decays, which winds the buffer down on its\n * own. Words, not characters, are what a reader sees land, and they are not the\n * same length: an even flow of characters still drops \"and if we do\" in one frame\n * and spends four on \"implementation\".\n */\nexport function useRevealedText(text: string, streaming: boolean): string {\n const [calm] = useState(prefersReducedMotion);\n const stops = useMemo(() => wordStops(text), [text]);\n const words = stops.length - 1;\n\n const [landed, setLanded] = useState(() => (streaming && !calm ? 0 : words));\n\n // Winds the cursor back when a shorter reply replaces the one on screen, which\n // no key can express: same element, same message, new text.\n const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));\n if (shown !== landed) setLanded(shown);\n\n const chasing = shown < words;\n const goal = useRef(words);\n const cursor = useRef(shown);\n const flow = useRef<Flow>({ rate: 0, at: 0 });\n const pace = useRef(SLOWEST);\n\n useEffect(() => {\n measure(flow.current, performance.now(), Math.max(0, words - goal.current));\n goal.current = words;\n\n if (cursor.current < shown || cursor.current > words) cursor.current = shown;\n });\n\n useEffect(() => {\n if (!chasing) return;\n\n let drawn = performance.now();\n let frame = requestAnimationFrame(function step(now) {\n const elapsed = Math.max(0, now - drawn);\n drawn = Math.max(drawn, now);\n\n const arriving = measure(flow.current, now, 0);\n const from = cursor.current;\n const behind = goal.current - from;\n\n if (behind > 0) {\n const held = (arriving * BUFFER_MS) / 1000;\n const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + ((behind - held) * 1000) / CORRECT_MS));\n\n pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);\n cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + (elapsed * pace.current) / 1000);\n\n if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));\n }\n\n frame = requestAnimationFrame(step);\n });\n\n return () => cancelAnimationFrame(frame);\n }, [chasing]);\n\n if (!chasing) return text;\n\n return text.slice(0, stops[shown]);\n}\n"],"names":["useState","useMemo","useRef","useEffect"],"mappings":"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"}
1
+ {"version":3,"file":"MarkdownRenderer.cjs.js","sources":[],"sourcesContent":[],"names":[],"mappings":";;;;;;;;;;"}
@@ -1,69 +1,3 @@
1
1
  export { M as MarkdownRenderer } from '../markdown-renderer-LOqipQki.js';
2
- import { useState, useMemo, useRef, useEffect } from 'react';
3
-
4
- const BUFFER_MS = 1e3;
5
- const CORRECT_MS = 1e3;
6
- const FLOW_MS = 700;
7
- const EASE_MS = 400;
8
- const SLOWEST = 10;
9
- const FASTEST = 60;
10
- const MAX_LAG = 300;
11
- function measure(flow, now, gained) {
12
- const elapsed = Math.max(0, now - flow.at);
13
- flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1e3);
14
- flow.at = Math.max(flow.at, now);
15
- return flow.rate;
16
- }
17
- function prefersReducedMotion() {
18
- if (typeof window === "undefined" || !window.matchMedia) return false;
19
- return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
20
- }
21
- function wordStops(text) {
22
- const stops = [0];
23
- const word = /\S+/g;
24
- for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);
25
- return stops;
26
- }
27
- function useRevealedText(text, streaming) {
28
- const [calm] = useState(prefersReducedMotion);
29
- const stops = useMemo(() => wordStops(text), [text]);
30
- const words = stops.length - 1;
31
- const [landed, setLanded] = useState(() => streaming && !calm ? 0 : words);
32
- const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));
33
- if (shown !== landed) setLanded(shown);
34
- const chasing = shown < words;
35
- const goal = useRef(words);
36
- const cursor = useRef(shown);
37
- const flow = useRef({ rate: 0, at: 0 });
38
- const pace = useRef(SLOWEST);
39
- useEffect(() => {
40
- measure(flow.current, performance.now(), Math.max(0, words - goal.current));
41
- goal.current = words;
42
- if (cursor.current < shown || cursor.current > words) cursor.current = shown;
43
- });
44
- useEffect(() => {
45
- if (!chasing) return;
46
- let drawn = performance.now();
47
- let frame = requestAnimationFrame(function step(now) {
48
- const elapsed = Math.max(0, now - drawn);
49
- drawn = Math.max(drawn, now);
50
- const arriving = measure(flow.current, now, 0);
51
- const from = cursor.current;
52
- const behind = goal.current - from;
53
- if (behind > 0) {
54
- const held = arriving * BUFFER_MS / 1e3;
55
- const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + (behind - held) * 1e3 / CORRECT_MS));
56
- pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);
57
- cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + elapsed * pace.current / 1e3);
58
- if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));
59
- }
60
- frame = requestAnimationFrame(step);
61
- });
62
- return () => cancelAnimationFrame(frame);
63
- }, [chasing]);
64
- if (!chasing) return text;
65
- return text.slice(0, stops[shown]);
66
- }
67
-
68
- export { useRevealedText };
2
+ export { u as useRevealedText } from '../use-reveal-d77hxHz9.js';
69
3
  //# sourceMappingURL=MarkdownRenderer.es.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"MarkdownRenderer.es.js","sources":["../../src/ds/components/MarkdownRenderer/use-reveal.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\n\n/** How much of the reply the reveal aims to be holding back, as time at the speed it is playing. */\nconst BUFFER_MS = 1000;\n\n/** How long it takes to work off a buffer that is too full or too empty. */\nconst CORRECT_MS = 1000;\n\n/** How long a word keeps counting towards the speed the reply is arriving at. */\nconst FLOW_MS = 700;\n\n/** How long the reveal takes to adopt a new speed, so a change of pace is never a jolt. */\nconst EASE_MS = 400;\n\n/** Words per second it will not go below: a short burst between two tool calls still reads briskly. */\nconst SLOWEST = 10;\n\n/** Words per second it will not go past: one word a frame, all a screen can draw. */\nconst FASTEST = 60;\n\n/** Words it will never fall further behind than — about five seconds at full speed. */\nconst MAX_LAG = 300;\n\ninterface Flow {\n /** Words per second the reply is arriving at, as an exponential average. */\n rate: number;\n at: number;\n}\n\n/** Decays what earlier words still count for, then deposits the ones that just arrived. */\nfunction measure(flow: Flow, now: number, gained: number): number {\n // rAF timestamps and performance.now() are sampled at different points of a\n // frame, so a fresh loop can read time slightly behind the last deposit.\n const elapsed = Math.max(0, now - flow.at);\n flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1000);\n flow.at = Math.max(flow.at, now);\n\n return flow.rate;\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Where the reveal can cut the text: before the first word, then after each one. */\nfunction wordStops(text: string): number[] {\n const stops = [0];\n const word = /\\S+/g;\n\n for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);\n\n return stops;\n}\n\n/**\n * A jitter buffer for a streamed reply, counted in words. Chunks reach the\n * browser unevenly — a proxy flushes, a tool call ends, the model changes pace —\n * and drawing each one on arrival makes a reply lurch: ten words at once, then\n * nothing for a fifth of a second.\n *\n * So the reveal runs on its own clock: a frame loop that chunks never interrupt,\n * laying down at most one word per frame however far behind it is. It plays at\n * the speed the reply is arriving at, measured directly, and leans on that only\n * to work the buffer back to `BUFFER_MS` of held text when it drifts. Measuring\n * the speed rather than inferring it from the buffer is what lets the buffer stay\n * shallow: a reply that lasts two seconds still finds its pace in the first few\n * hundred milliseconds, where a deeper buffer would spend the whole reply filling\n * and then sprint to catch up.\n *\n * The reply it paces is whatever the caller hands it, so a caller with more than\n * prose to lay down — a tool row between two paragraphs — paces the whole of it\n * from one place and draws the rest against the same cursor.\n *\n * Nothing here reads `streaming` past the first render: a reply that stops\n * arriving is one whose measured speed decays, which winds the buffer down on its\n * own. Words, not characters, are what a reader sees land, and they are not the\n * same length: an even flow of characters still drops \"and if we do\" in one frame\n * and spends four on \"implementation\".\n */\nexport function useRevealedText(text: string, streaming: boolean): string {\n const [calm] = useState(prefersReducedMotion);\n const stops = useMemo(() => wordStops(text), [text]);\n const words = stops.length - 1;\n\n const [landed, setLanded] = useState(() => (streaming && !calm ? 0 : words));\n\n // Winds the cursor back when a shorter reply replaces the one on screen, which\n // no key can express: same element, same message, new text.\n const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));\n if (shown !== landed) setLanded(shown);\n\n const chasing = shown < words;\n const goal = useRef(words);\n const cursor = useRef(shown);\n const flow = useRef<Flow>({ rate: 0, at: 0 });\n const pace = useRef(SLOWEST);\n\n useEffect(() => {\n measure(flow.current, performance.now(), Math.max(0, words - goal.current));\n goal.current = words;\n\n if (cursor.current < shown || cursor.current > words) cursor.current = shown;\n });\n\n useEffect(() => {\n if (!chasing) return;\n\n let drawn = performance.now();\n let frame = requestAnimationFrame(function step(now) {\n const elapsed = Math.max(0, now - drawn);\n drawn = Math.max(drawn, now);\n\n const arriving = measure(flow.current, now, 0);\n const from = cursor.current;\n const behind = goal.current - from;\n\n if (behind > 0) {\n const held = (arriving * BUFFER_MS) / 1000;\n const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + ((behind - held) * 1000) / CORRECT_MS));\n\n pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);\n cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + (elapsed * pace.current) / 1000);\n\n if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));\n }\n\n frame = requestAnimationFrame(step);\n });\n\n return () => cancelAnimationFrame(frame);\n }, [chasing]);\n\n if (!chasing) return text;\n\n return text.slice(0, stops[shown]);\n}\n"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"MarkdownRenderer.es.js","sources":[],"sourcesContent":[],"names":[],"mappings":";"}
@@ -0,0 +1,50 @@
1
+ 'use strict';
2
+
3
+ Object.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });
4
+
5
+ const useReveal = require('../../use-reveal-Sa456jZb.cjs');
6
+
7
+ const PASSAGE = "\n\n";
8
+ const ROW_MARK = Array.from({ length: 6 }, () => "").join(" ");
9
+ function partProse(part) {
10
+ if (part.type === "text") return part.text;
11
+ if (part.type === "reasoning") return part.reasoning;
12
+ return void 0;
13
+ }
14
+ function messageScript(parts) {
15
+ return parts.map((part) => partProse(part) ?? ROW_MARK).join(PASSAGE);
16
+ }
17
+ function revealedParts(parts, shown) {
18
+ const script = messageScript(parts);
19
+ if (shown.length >= script.length) return parts;
20
+ const revealed = [];
21
+ let read = 0;
22
+ for (const part of parts) {
23
+ const start = read === 0 ? 0 : read + PASSAGE.length;
24
+ if (part.type !== "text" && part.type !== "reasoning") {
25
+ read = start + ROW_MARK.length;
26
+ if (shown.length < read) break;
27
+ revealed.push(part);
28
+ continue;
29
+ }
30
+ const written = part.type === "text" ? part.text : part.reasoning;
31
+ read = start + written.length;
32
+ const text = shown.slice(start, read);
33
+ if (text === written) {
34
+ revealed.push(part);
35
+ continue;
36
+ }
37
+ if (text) revealed.push(part.type === "text" ? { ...part, text } : { ...part, reasoning: text });
38
+ break;
39
+ }
40
+ return revealed;
41
+ }
42
+ function useRevealedParts(parts, streaming) {
43
+ const shown = useReveal.useRevealedText(messageScript(parts), streaming);
44
+ return revealedParts(parts, shown);
45
+ }
46
+
47
+ exports.messageScript = messageScript;
48
+ exports.revealedParts = revealedParts;
49
+ exports.useRevealedParts = useRevealedParts;
50
+ //# sourceMappingURL=message-reveal.cjs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-reveal.cjs.js","sources":["../../../src/ds/components/ai/message-reveal/message-reveal.ts"],"sourcesContent":["import type { MastraDBMessage } from '@mastra/core/agent/message-list';\n\nimport { useRevealedText } from '../../MarkdownRenderer/use-reveal';\n\ntype MessagePart = MastraDBMessage['content']['parts'][number];\n\n/** What separates two passages of prose that were streamed as separate blocks. */\nconst PASSAGE = '\\n\\n';\n\n/**\n * A part that is not prose, written into the reveal's script as words so it takes a\n * beat on the same clock: a burst of parallel tool calls cascades in row by row, at\n * the pace the reply is moving, instead of landing as one block.\n */\nconst ROW_MARK = Array.from({ length: 6 }, () => '').join(' ');\n\n/**\n * The words a part was written as, for the kinds the model writes word by word.\n * Reasoning streams delta by delta exactly like prose, so its passage takes its\n * words' time on the clock rather than one row's beat — a beat would hold the\n * block back whole, then let it land at once and grow unpaced.\n */\nfunction partProse(part: MessagePart): string | undefined {\n if (part.type === 'text') return part.text;\n if (part.type === 'reasoning') return part.reasoning;\n return undefined;\n}\n\n/** What the reveal paces: the whole message, rows and cards written in as beats between the prose. */\nexport function messageScript(parts: MessagePart[]): string {\n return parts.map(part => partProse(part) ?? ROW_MARK).join(PASSAGE);\n}\n\n/**\n * The parts of a message a reveal has reached, `shown` being a prefix of the script.\n *\n * A reply is not only prose: a tool row, a card, a reasoning block sit between its\n * passages, and they were written in that order. Pacing the prose alone would lay a\n * sentence down word by word while the row that follows it is already on screen — so\n * every part waits its turn on the one clock, and the reader is handed the answer in\n * the order it was written.\n */\nexport function revealedParts(parts: MessagePart[], shown: string): MessagePart[] {\n const script = messageScript(parts);\n if (shown.length >= script.length) return parts;\n\n const revealed: MessagePart[] = [];\n let read = 0;\n\n for (const part of parts) {\n const start = read === 0 ? 0 : read + PASSAGE.length;\n\n if (part.type !== 'text' && part.type !== 'reasoning') {\n read = start + ROW_MARK.length;\n if (shown.length < read) break;\n revealed.push(part);\n continue;\n }\n\n const written = part.type === 'text' ? part.text : part.reasoning;\n read = start + written.length;\n\n const text = shown.slice(start, read);\n if (text === written) {\n revealed.push(part);\n continue;\n }\n\n if (text) revealed.push(part.type === 'text' ? { ...part, text } : { ...part, reasoning: text });\n break;\n }\n\n return revealed;\n}\n\n/**\n * A message's parts as far as the reveal has laid them down. Hand the result to\n * `MessageFactory` in place of the message's own parts and the whole reply — prose,\n * reasoning, tool rows, cards — arrives at the pace it was written, from one clock,\n * without a renderer having to know a reveal is running.\n */\nexport function useRevealedParts(parts: MessagePart[], streaming: boolean): MessagePart[] {\n const shown = useRevealedText(messageScript(parts), streaming);\n\n return revealedParts(parts, shown);\n}\n"],"names":["useRevealedText"],"mappings":";;;;;;AAOA,MAAM,OAAA,GAAU,MAAA;AAOhB,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,CAAA,EAAE,EAAG,MAAM,GAAG,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAQ9D,SAAS,UAAU,IAAA,EAAuC;AACxD,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,EAAQ,OAAO,IAAA,CAAK,IAAA;AACtC,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,WAAA,EAAa,OAAO,IAAA,CAAK,SAAA;AAC3C,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,cAAc,KAAA,EAA8B;AAC1D,EAAA,OAAO,KAAA,CAAM,IAAI,CAAA,IAAA,KAAQ,SAAA,CAAU,IAAI,CAAA,IAAK,QAAQ,CAAA,CAAE,IAAA,CAAK,OAAO,CAAA;AACpE;AAWO,SAAS,aAAA,CAAc,OAAsB,KAAA,EAA8B;AAChF,EAAA,MAAM,MAAA,GAAS,cAAc,KAAK,CAAA;AAClC,EAAA,IAAI,KAAA,CAAM,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,KAAA;AAE1C,EAAA,MAAM,WAA0B,EAAC;AACjC,EAAA,IAAI,IAAA,GAAO,CAAA;AAEX,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,IAAA,KAAS,CAAA,GAAI,CAAA,GAAI,OAAO,OAAA,CAAQ,MAAA;AAE9C,IAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,SAAS,WAAA,EAAa;AACrD,MAAA,IAAA,GAAO,QAAQ,QAAA,CAAS,MAAA;AACxB,MAAA,IAAI,KAAA,CAAM,SAAS,IAAA,EAAM;AACzB,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,IAAA,KAAS,MAAA,GAAS,IAAA,CAAK,OAAO,IAAA,CAAK,SAAA;AACxD,IAAA,IAAA,GAAO,QAAQ,OAAA,CAAQ,MAAA;AAEvB,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,IAAI,CAAA;AACpC,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,MAAM,QAAA,CAAS,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS,SAAS,EAAE,GAAG,IAAA,EAAM,IAAA,KAAS,EAAE,GAAG,IAAA,EAAM,SAAA,EAAW,MAAM,CAAA;AAC/F,IAAA;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;AAQO,SAAS,gBAAA,CAAiB,OAAsB,SAAA,EAAmC;AACxF,EAAA,MAAM,KAAA,GAAQA,yBAAA,CAAgB,aAAA,CAAc,KAAK,GAAG,SAAS,CAAA;AAE7D,EAAA,OAAO,aAAA,CAAc,OAAO,KAAK,CAAA;AACnC;;;;;;"}
@@ -0,0 +1,2 @@
1
+ export * from '../../src/ds/components/ai/message-reveal/index'
2
+ export {}
@@ -0,0 +1,44 @@
1
+ import { u as useRevealedText } from '../../use-reveal-d77hxHz9.js';
2
+
3
+ const PASSAGE = "\n\n";
4
+ const ROW_MARK = Array.from({ length: 6 }, () => "").join(" ");
5
+ function partProse(part) {
6
+ if (part.type === "text") return part.text;
7
+ if (part.type === "reasoning") return part.reasoning;
8
+ return void 0;
9
+ }
10
+ function messageScript(parts) {
11
+ return parts.map((part) => partProse(part) ?? ROW_MARK).join(PASSAGE);
12
+ }
13
+ function revealedParts(parts, shown) {
14
+ const script = messageScript(parts);
15
+ if (shown.length >= script.length) return parts;
16
+ const revealed = [];
17
+ let read = 0;
18
+ for (const part of parts) {
19
+ const start = read === 0 ? 0 : read + PASSAGE.length;
20
+ if (part.type !== "text" && part.type !== "reasoning") {
21
+ read = start + ROW_MARK.length;
22
+ if (shown.length < read) break;
23
+ revealed.push(part);
24
+ continue;
25
+ }
26
+ const written = part.type === "text" ? part.text : part.reasoning;
27
+ read = start + written.length;
28
+ const text = shown.slice(start, read);
29
+ if (text === written) {
30
+ revealed.push(part);
31
+ continue;
32
+ }
33
+ if (text) revealed.push(part.type === "text" ? { ...part, text } : { ...part, reasoning: text });
34
+ break;
35
+ }
36
+ return revealed;
37
+ }
38
+ function useRevealedParts(parts, streaming) {
39
+ const shown = useRevealedText(messageScript(parts), streaming);
40
+ return revealedParts(parts, shown);
41
+ }
42
+
43
+ export { messageScript, revealedParts, useRevealedParts };
44
+ //# sourceMappingURL=message-reveal.es.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"message-reveal.es.js","sources":["../../../src/ds/components/ai/message-reveal/message-reveal.ts"],"sourcesContent":["import type { MastraDBMessage } from '@mastra/core/agent/message-list';\n\nimport { useRevealedText } from '../../MarkdownRenderer/use-reveal';\n\ntype MessagePart = MastraDBMessage['content']['parts'][number];\n\n/** What separates two passages of prose that were streamed as separate blocks. */\nconst PASSAGE = '\\n\\n';\n\n/**\n * A part that is not prose, written into the reveal's script as words so it takes a\n * beat on the same clock: a burst of parallel tool calls cascades in row by row, at\n * the pace the reply is moving, instead of landing as one block.\n */\nconst ROW_MARK = Array.from({ length: 6 }, () => '').join(' ');\n\n/**\n * The words a part was written as, for the kinds the model writes word by word.\n * Reasoning streams delta by delta exactly like prose, so its passage takes its\n * words' time on the clock rather than one row's beat — a beat would hold the\n * block back whole, then let it land at once and grow unpaced.\n */\nfunction partProse(part: MessagePart): string | undefined {\n if (part.type === 'text') return part.text;\n if (part.type === 'reasoning') return part.reasoning;\n return undefined;\n}\n\n/** What the reveal paces: the whole message, rows and cards written in as beats between the prose. */\nexport function messageScript(parts: MessagePart[]): string {\n return parts.map(part => partProse(part) ?? ROW_MARK).join(PASSAGE);\n}\n\n/**\n * The parts of a message a reveal has reached, `shown` being a prefix of the script.\n *\n * A reply is not only prose: a tool row, a card, a reasoning block sit between its\n * passages, and they were written in that order. Pacing the prose alone would lay a\n * sentence down word by word while the row that follows it is already on screen — so\n * every part waits its turn on the one clock, and the reader is handed the answer in\n * the order it was written.\n */\nexport function revealedParts(parts: MessagePart[], shown: string): MessagePart[] {\n const script = messageScript(parts);\n if (shown.length >= script.length) return parts;\n\n const revealed: MessagePart[] = [];\n let read = 0;\n\n for (const part of parts) {\n const start = read === 0 ? 0 : read + PASSAGE.length;\n\n if (part.type !== 'text' && part.type !== 'reasoning') {\n read = start + ROW_MARK.length;\n if (shown.length < read) break;\n revealed.push(part);\n continue;\n }\n\n const written = part.type === 'text' ? part.text : part.reasoning;\n read = start + written.length;\n\n const text = shown.slice(start, read);\n if (text === written) {\n revealed.push(part);\n continue;\n }\n\n if (text) revealed.push(part.type === 'text' ? { ...part, text } : { ...part, reasoning: text });\n break;\n }\n\n return revealed;\n}\n\n/**\n * A message's parts as far as the reveal has laid them down. Hand the result to\n * `MessageFactory` in place of the message's own parts and the whole reply — prose,\n * reasoning, tool rows, cards — arrives at the pace it was written, from one clock,\n * without a renderer having to know a reveal is running.\n */\nexport function useRevealedParts(parts: MessagePart[], streaming: boolean): MessagePart[] {\n const shown = useRevealedText(messageScript(parts), streaming);\n\n return revealedParts(parts, shown);\n}\n"],"names":[],"mappings":";;AAOA,MAAM,OAAA,GAAU,MAAA;AAOhB,MAAM,QAAA,GAAW,KAAA,CAAM,IAAA,CAAK,EAAE,MAAA,EAAQ,CAAA,EAAE,EAAG,MAAM,GAAG,CAAA,CAAE,IAAA,CAAK,GAAG,CAAA;AAQ9D,SAAS,UAAU,IAAA,EAAuC;AACxD,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,EAAQ,OAAO,IAAA,CAAK,IAAA;AACtC,EAAA,IAAI,IAAA,CAAK,IAAA,KAAS,WAAA,EAAa,OAAO,IAAA,CAAK,SAAA;AAC3C,EAAA,OAAO,MAAA;AACT;AAGO,SAAS,cAAc,KAAA,EAA8B;AAC1D,EAAA,OAAO,KAAA,CAAM,IAAI,CAAA,IAAA,KAAQ,SAAA,CAAU,IAAI,CAAA,IAAK,QAAQ,CAAA,CAAE,IAAA,CAAK,OAAO,CAAA;AACpE;AAWO,SAAS,aAAA,CAAc,OAAsB,KAAA,EAA8B;AAChF,EAAA,MAAM,MAAA,GAAS,cAAc,KAAK,CAAA;AAClC,EAAA,IAAI,KAAA,CAAM,MAAA,IAAU,MAAA,CAAO,MAAA,EAAQ,OAAO,KAAA;AAE1C,EAAA,MAAM,WAA0B,EAAC;AACjC,EAAA,IAAI,IAAA,GAAO,CAAA;AAEX,EAAA,KAAA,MAAW,QAAQ,KAAA,EAAO;AACxB,IAAA,MAAM,KAAA,GAAQ,IAAA,KAAS,CAAA,GAAI,CAAA,GAAI,OAAO,OAAA,CAAQ,MAAA;AAE9C,IAAA,IAAI,IAAA,CAAK,IAAA,KAAS,MAAA,IAAU,IAAA,CAAK,SAAS,WAAA,EAAa;AACrD,MAAA,IAAA,GAAO,QAAQ,QAAA,CAAS,MAAA;AACxB,MAAA,IAAI,KAAA,CAAM,SAAS,IAAA,EAAM;AACzB,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,MAAM,UAAU,IAAA,CAAK,IAAA,KAAS,MAAA,GAAS,IAAA,CAAK,OAAO,IAAA,CAAK,SAAA;AACxD,IAAA,IAAA,GAAO,QAAQ,OAAA,CAAQ,MAAA;AAEvB,IAAA,MAAM,IAAA,GAAO,KAAA,CAAM,KAAA,CAAM,KAAA,EAAO,IAAI,CAAA;AACpC,IAAA,IAAI,SAAS,OAAA,EAAS;AACpB,MAAA,QAAA,CAAS,KAAK,IAAI,CAAA;AAClB,MAAA;AAAA,IACF;AAEA,IAAA,IAAI,MAAM,QAAA,CAAS,IAAA,CAAK,IAAA,CAAK,IAAA,KAAS,SAAS,EAAE,GAAG,IAAA,EAAM,IAAA,KAAS,EAAE,GAAG,IAAA,EAAM,SAAA,EAAW,MAAM,CAAA;AAC/F,IAAA;AAAA,EACF;AAEA,EAAA,OAAO,QAAA;AACT;AAQO,SAAS,gBAAA,CAAiB,OAAsB,SAAA,EAAmC;AACxF,EAAA,MAAM,KAAA,GAAQ,eAAA,CAAgB,aAAA,CAAc,KAAK,GAAG,SAAS,CAAA;AAE7D,EAAA,OAAO,aAAA,CAAc,OAAO,KAAK,CAAA;AACnC;;;;"}
@@ -0,0 +1,6 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ArrivalScope } from './arrival';
3
+ declare const meta: Meta<typeof ArrivalScope>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ArrivalScope>;
6
+ export declare const ExistingAndNewContent: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Card } from './Card';
3
+ declare const meta: Meta<typeof Card>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Card>;
6
+ export declare const Default: Story;
7
+ export declare const AppearancesAndElevation: Story;
8
+ export declare const InteractiveLinks: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Columns } from './index';
3
+ declare const meta: Meta<typeof Columns>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Columns>;
6
+ export declare const ResponsiveGrid: Story;
7
+ export declare const HorizontallyScrollable: Story;
@@ -0,0 +1,9 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { DataDetailsPanel } from './index';
3
+ declare const meta: Meta<typeof DataDetailsPanel>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof DataDetailsPanel>;
6
+ export declare const Populated: Story;
7
+ export declare const Loading: Story;
8
+ export declare const Empty: Story;
9
+ export declare const Collapsed: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { SelectDataFilter } from './select-data-filter';
3
+ declare const meta: Meta<typeof SelectDataFilter>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof SelectDataFilter>;
6
+ export declare const Default: Story;
7
+ export declare const WithActiveFilters: Story;
8
+ export declare const Disabled: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { DateRangeTimeline } from './DateRangeTimeline';
3
+ declare const meta: Meta<typeof DateRangeTimeline>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof DateRangeTimeline>;
6
+ export declare const Default: Story;
7
+ export declare const SingleDay: Story;
8
+ export declare const MultiYear: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ErrorState } from './ErrorState';
3
+ declare const meta: Meta<typeof ErrorState>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ErrorState>;
6
+ export declare const Default: Story;
7
+ export declare const WithRecoveryAction: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ListSearch } from './list-search';
3
+ declare const meta: Meta<typeof ListSearch>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ListSearch>;
6
+ export declare const Default: Story;
7
+ export declare const PresetValue: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { LogsDataList } from './index';
3
+ declare const meta: Meta<typeof LogsDataList>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof LogsDataList>;
6
+ export declare const Populated: Story;
7
+ export declare const NoMatches: Story;
8
+ export declare const Loading: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { MessageScroller } from './message-scroller';
3
+ declare const meta: Meta<typeof MessageScroller>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof MessageScroller>;
6
+ export declare const Conversation: Story;
7
+ export declare const AutoFollow: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { MetricsLineChart } from './metrics-line-chart';
3
+ declare const meta: Meta<typeof MetricsLineChart>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof MetricsLineChart>;
6
+ export declare const MultipleSeries: Story;
7
+ export declare const SinglePoint: Story;
8
+ export declare const FixedDomain: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PageHeader } from './page-header';
3
+ declare const meta: Meta<typeof PageHeader>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PageHeader>;
6
+ export declare const Default: Story;
7
+ export declare const Loading: Story;
8
+ export declare const SmallerTitle: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PageLayout } from './index';
3
+ declare const meta: Meta<typeof PageLayout>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PageLayout>;
6
+ export declare const FullPage: Story;
7
+ export declare const NarrowSettings: Story;
8
+ export declare const CenteredEmptyState: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PendingIndicator } from './pending-indicator';
3
+ declare const meta: Meta<typeof PendingIndicator>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PendingIndicator>;
6
+ export declare const Default: Story;
7
+ export declare const InConversation: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PermissionDenied } from './PermissionDenied';
3
+ declare const meta: Meta<typeof PermissionDenied>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PermissionDenied>;
6
+ export declare const Default: Story;
7
+ export declare const ResourceSpecific: Story;
8
+ export declare const CustomRecovery: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { PrevNextNav } from './prev-next-nav';
3
+ declare const meta: Meta<typeof PrevNextNav>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof PrevNextNav>;
6
+ export declare const BetweenItems: Story;
7
+ export declare const FirstItem: Story;
8
+ export declare const LastItem: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { SessionExpired } from './SessionExpired';
3
+ declare const meta: Meta<typeof SessionExpired>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof SessionExpired>;
6
+ export declare const Default: Story;
7
+ export declare const CustomCopy: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { Shimmer } from './shimmer';
3
+ declare const meta: Meta<typeof Shimmer>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof Shimmer>;
6
+ export declare const Default: Story;
7
+ export declare const ActiveToSettled: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { StatusBadge } from './StatusBadge';
3
+ declare const meta: Meta<typeof StatusBadge>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof StatusBadge>;
6
+ export declare const Default: Story;
7
+ export declare const Variants: Story;
8
+ export declare const SizesAndPulse: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ThreadList } from './thread-list';
3
+ declare const meta: Meta<typeof ThreadList>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ThreadList>;
6
+ export declare const Default: Story;
7
+ export declare const Embedded: Story;
8
+ export declare const Empty: Story;
@@ -0,0 +1,7 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { ThreadRail } from './thread-rail';
3
+ declare const meta: Meta<typeof ThreadRail>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof ThreadRail>;
6
+ export declare const ConversationTimeline: Story;
7
+ export declare const Empty: Story;
@@ -0,0 +1,8 @@
1
+ import { Meta, StoryObj } from '@storybook/react-vite';
2
+ import { TokenBudget } from './token-budget';
3
+ declare const meta: Meta<typeof TokenBudget>;
4
+ export default meta;
5
+ type Story = StoryObj<typeof TokenBudget>;
6
+ export declare const Default: Story;
7
+ export declare const Budgets: Story;
8
+ export declare const Details: Story;
@@ -0,0 +1 @@
1
+ export * from './message-reveal';
@@ -0,0 +1,22 @@
1
+ import { MastraDBMessage } from '@mastra/core/agent/message-list';
2
+ type MessagePart = MastraDBMessage['content']['parts'][number];
3
+ /** What the reveal paces: the whole message, rows and cards written in as beats between the prose. */
4
+ export declare function messageScript(parts: MessagePart[]): string;
5
+ /**
6
+ * The parts of a message a reveal has reached, `shown` being a prefix of the script.
7
+ *
8
+ * A reply is not only prose: a tool row, a card, a reasoning block sit between its
9
+ * passages, and they were written in that order. Pacing the prose alone would lay a
10
+ * sentence down word by word while the row that follows it is already on screen — so
11
+ * every part waits its turn on the one clock, and the reader is handed the answer in
12
+ * the order it was written.
13
+ */
14
+ export declare function revealedParts(parts: MessagePart[], shown: string): MessagePart[];
15
+ /**
16
+ * A message's parts as far as the reveal has laid them down. Hand the result to
17
+ * `MessageFactory` in place of the message's own parts and the whole reply — prose,
18
+ * reasoning, tool rows, cards — arrives at the pace it was written, from one clock,
19
+ * without a renderer having to know a reveal is running.
20
+ */
21
+ export declare function useRevealedParts(parts: MessagePart[], streaming: boolean): MessagePart[];
22
+ export {};
package/dist/style.css CHANGED
@@ -1621,6 +1621,10 @@
1621
1621
  margin-right: var(--spacing-8);
1622
1622
  }
1623
1623
 
1624
+ .mr-12 {
1625
+ margin-right: var(--spacing-12);
1626
+ }
1627
+
1624
1628
  .-mb-12 {
1625
1629
  margin-bottom: calc(var(--spacing-12) * -1);
1626
1630
  }
@@ -1706,6 +1710,14 @@
1706
1710
  margin-left: var(--spacing-8);
1707
1711
  }
1708
1712
 
1713
+ .ml-10 {
1714
+ margin-left: var(--spacing-10);
1715
+ }
1716
+
1717
+ .ml-12 {
1718
+ margin-left: var(--spacing-12);
1719
+ }
1720
+
1709
1721
  .ml-\[14px\] {
1710
1722
  margin-left: 14px;
1711
1723
  }
@@ -1988,6 +2000,10 @@
1988
2000
  height: var(--spacing-40);
1989
2001
  }
1990
2002
 
2003
+ .h-56 {
2004
+ height: var(--spacing-56);
2005
+ }
2006
+
1991
2007
  .h-60 {
1992
2008
  height: var(--spacing-60);
1993
2009
  }
@@ -1996,10 +2012,18 @@
1996
2012
  height: var(--spacing-64);
1997
2013
  }
1998
2014
 
2015
+ .h-72 {
2016
+ height: var(--spacing-72);
2017
+ }
2018
+
1999
2019
  .h-80 {
2000
2020
  height: var(--spacing-80);
2001
2021
  }
2002
2022
 
2023
+ .h-96 {
2024
+ height: var(--spacing-96);
2025
+ }
2026
+
2003
2027
  .h-\[1\.8rem\] {
2004
2028
  height: 1.8rem;
2005
2029
  }
@@ -2012,6 +2036,22 @@
2012
2036
  height: 30vh;
2013
2037
  }
2014
2038
 
2039
+ .h-\[32rem\] {
2040
+ height: 32rem;
2041
+ }
2042
+
2043
+ .h-\[34rem\] {
2044
+ height: 34rem;
2045
+ }
2046
+
2047
+ .h-\[36rem\] {
2048
+ height: 36rem;
2049
+ }
2050
+
2051
+ .h-\[38rem\] {
2052
+ height: 38rem;
2053
+ }
2054
+
2015
2055
  .h-\[160px\] {
2016
2056
  height: 160px;
2017
2057
  }
@@ -2241,10 +2281,18 @@
2241
2281
  min-height: var(--spacing-60);
2242
2282
  }
2243
2283
 
2284
+ .min-h-64 {
2285
+ min-height: var(--spacing-64);
2286
+ }
2287
+
2244
2288
  .min-h-72 {
2245
2289
  min-height: var(--spacing-72);
2246
2290
  }
2247
2291
 
2292
+ .min-h-\[34rem\] {
2293
+ min-height: 34rem;
2294
+ }
2295
+
2248
2296
  .min-h-\[70cqh\] {
2249
2297
  min-height: 70cqh;
2250
2298
  }
@@ -2501,6 +2549,30 @@
2501
2549
  width: calc(100vw - 2rem);
2502
2550
  }
2503
2551
 
2552
+ .w-\[min\(24rem\,calc\(100vw-2rem\)\)\] {
2553
+ width: min(24rem, 100vw - 2rem);
2554
+ }
2555
+
2556
+ .w-\[min\(34rem\,calc\(100vw-3rem\)\)\] {
2557
+ width: min(34rem, 100vw - 3rem);
2558
+ }
2559
+
2560
+ .w-\[min\(42rem\,calc\(100vw-3rem\)\)\] {
2561
+ width: min(42rem, 100vw - 3rem);
2562
+ }
2563
+
2564
+ .w-\[min\(44rem\,calc\(100vw-2rem\)\)\] {
2565
+ width: min(44rem, 100vw - 2rem);
2566
+ }
2567
+
2568
+ .w-\[min\(48rem\,calc\(100vw-3rem\)\)\] {
2569
+ width: min(48rem, 100vw - 3rem);
2570
+ }
2571
+
2572
+ .w-\[min\(52rem\,calc\(100vw-3rem\)\)\] {
2573
+ width: min(52rem, 100vw - 3rem);
2574
+ }
2575
+
2504
2576
  .w-\[var\(--active-tab-width\)\] {
2505
2577
  width: var(--active-tab-width);
2506
2578
  }
@@ -9268,6 +9340,14 @@
9268
9340
  max-height: none;
9269
9341
  }
9270
9342
 
9343
+ .md\:grid-cols-2 {
9344
+ grid-template-columns: repeat(2, minmax(0, 1fr));
9345
+ }
9346
+
9347
+ .md\:grid-cols-3 {
9348
+ grid-template-columns: repeat(3, minmax(0, 1fr));
9349
+ }
9350
+
9271
9351
  .md\:grid-cols-\[13rem_minmax\(0\,1fr\)\] {
9272
9352
  grid-template-columns: 13rem minmax(0, 1fr);
9273
9353
  }
@@ -0,0 +1,70 @@
1
+ 'use strict';
2
+
3
+ const React = require('react');
4
+
5
+ const BUFFER_MS = 1e3;
6
+ const CORRECT_MS = 1e3;
7
+ const FLOW_MS = 700;
8
+ const EASE_MS = 400;
9
+ const SLOWEST = 10;
10
+ const FASTEST = 60;
11
+ const MAX_LAG = 300;
12
+ function measure(flow, now, gained) {
13
+ const elapsed = Math.max(0, now - flow.at);
14
+ flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1e3);
15
+ flow.at = Math.max(flow.at, now);
16
+ return flow.rate;
17
+ }
18
+ function prefersReducedMotion() {
19
+ if (typeof window === "undefined" || !window.matchMedia) return false;
20
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
21
+ }
22
+ function wordStops(text) {
23
+ const stops = [0];
24
+ const word = /\S+/g;
25
+ for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);
26
+ return stops;
27
+ }
28
+ function useRevealedText(text, streaming) {
29
+ const [calm] = React.useState(prefersReducedMotion);
30
+ const stops = React.useMemo(() => wordStops(text), [text]);
31
+ const words = stops.length - 1;
32
+ const [landed, setLanded] = React.useState(() => streaming && !calm ? 0 : words);
33
+ const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));
34
+ if (shown !== landed) setLanded(shown);
35
+ const chasing = shown < words;
36
+ const goal = React.useRef(words);
37
+ const cursor = React.useRef(shown);
38
+ const flow = React.useRef({ rate: 0, at: 0 });
39
+ const pace = React.useRef(SLOWEST);
40
+ React.useEffect(() => {
41
+ measure(flow.current, performance.now(), Math.max(0, words - goal.current));
42
+ goal.current = words;
43
+ if (cursor.current < shown || cursor.current > words) cursor.current = shown;
44
+ });
45
+ React.useEffect(() => {
46
+ if (!chasing) return;
47
+ let drawn = performance.now();
48
+ let frame = requestAnimationFrame(function step(now) {
49
+ const elapsed = Math.max(0, now - drawn);
50
+ drawn = Math.max(drawn, now);
51
+ const arriving = measure(flow.current, now, 0);
52
+ const from = cursor.current;
53
+ const behind = goal.current - from;
54
+ if (behind > 0) {
55
+ const held = arriving * BUFFER_MS / 1e3;
56
+ const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + (behind - held) * 1e3 / CORRECT_MS));
57
+ pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);
58
+ cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + elapsed * pace.current / 1e3);
59
+ if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));
60
+ }
61
+ frame = requestAnimationFrame(step);
62
+ });
63
+ return () => cancelAnimationFrame(frame);
64
+ }, [chasing]);
65
+ if (!chasing) return text;
66
+ return text.slice(0, stops[shown]);
67
+ }
68
+
69
+ exports.useRevealedText = useRevealedText;
70
+ //# sourceMappingURL=use-reveal-Sa456jZb.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-reveal-Sa456jZb.cjs","sources":["../src/ds/components/MarkdownRenderer/use-reveal.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\n\n/** How much of the reply the reveal aims to be holding back, as time at the speed it is playing. */\nconst BUFFER_MS = 1000;\n\n/** How long it takes to work off a buffer that is too full or too empty. */\nconst CORRECT_MS = 1000;\n\n/** How long a word keeps counting towards the speed the reply is arriving at. */\nconst FLOW_MS = 700;\n\n/** How long the reveal takes to adopt a new speed, so a change of pace is never a jolt. */\nconst EASE_MS = 400;\n\n/** Words per second it will not go below: a short burst between two tool calls still reads briskly. */\nconst SLOWEST = 10;\n\n/** Words per second it will not go past: one word a frame, all a screen can draw. */\nconst FASTEST = 60;\n\n/** Words it will never fall further behind than — about five seconds at full speed. */\nconst MAX_LAG = 300;\n\ninterface Flow {\n /** Words per second the reply is arriving at, as an exponential average. */\n rate: number;\n at: number;\n}\n\n/** Decays what earlier words still count for, then deposits the ones that just arrived. */\nfunction measure(flow: Flow, now: number, gained: number): number {\n // rAF timestamps and performance.now() are sampled at different points of a\n // frame, so a fresh loop can read time slightly behind the last deposit.\n const elapsed = Math.max(0, now - flow.at);\n flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1000);\n flow.at = Math.max(flow.at, now);\n\n return flow.rate;\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Where the reveal can cut the text: before the first word, then after each one. */\nfunction wordStops(text: string): number[] {\n const stops = [0];\n const word = /\\S+/g;\n\n for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);\n\n return stops;\n}\n\n/**\n * A jitter buffer for a streamed reply, counted in words. Chunks reach the\n * browser unevenly — a proxy flushes, a tool call ends, the model changes pace —\n * and drawing each one on arrival makes a reply lurch: ten words at once, then\n * nothing for a fifth of a second.\n *\n * So the reveal runs on its own clock: a frame loop that chunks never interrupt,\n * laying down at most one word per frame however far behind it is. It plays at\n * the speed the reply is arriving at, measured directly, and leans on that only\n * to work the buffer back to `BUFFER_MS` of held text when it drifts. Measuring\n * the speed rather than inferring it from the buffer is what lets the buffer stay\n * shallow: a reply that lasts two seconds still finds its pace in the first few\n * hundred milliseconds, where a deeper buffer would spend the whole reply filling\n * and then sprint to catch up.\n *\n * The reply it paces is whatever the caller hands it, so a caller with more than\n * prose to lay down — a tool row between two paragraphs — paces the whole of it\n * from one place and draws the rest against the same cursor.\n *\n * Nothing here reads `streaming` past the first render: a reply that stops\n * arriving is one whose measured speed decays, which winds the buffer down on its\n * own. Words, not characters, are what a reader sees land, and they are not the\n * same length: an even flow of characters still drops \"and if we do\" in one frame\n * and spends four on \"implementation\".\n */\nexport function useRevealedText(text: string, streaming: boolean): string {\n const [calm] = useState(prefersReducedMotion);\n const stops = useMemo(() => wordStops(text), [text]);\n const words = stops.length - 1;\n\n const [landed, setLanded] = useState(() => (streaming && !calm ? 0 : words));\n\n // Winds the cursor back when a shorter reply replaces the one on screen, which\n // no key can express: same element, same message, new text.\n const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));\n if (shown !== landed) setLanded(shown);\n\n const chasing = shown < words;\n const goal = useRef(words);\n const cursor = useRef(shown);\n const flow = useRef<Flow>({ rate: 0, at: 0 });\n const pace = useRef(SLOWEST);\n\n useEffect(() => {\n measure(flow.current, performance.now(), Math.max(0, words - goal.current));\n goal.current = words;\n\n if (cursor.current < shown || cursor.current > words) cursor.current = shown;\n });\n\n useEffect(() => {\n if (!chasing) return;\n\n let drawn = performance.now();\n let frame = requestAnimationFrame(function step(now) {\n const elapsed = Math.max(0, now - drawn);\n drawn = Math.max(drawn, now);\n\n const arriving = measure(flow.current, now, 0);\n const from = cursor.current;\n const behind = goal.current - from;\n\n if (behind > 0) {\n const held = (arriving * BUFFER_MS) / 1000;\n const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + ((behind - held) * 1000) / CORRECT_MS));\n\n pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);\n cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + (elapsed * pace.current) / 1000);\n\n if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));\n }\n\n frame = requestAnimationFrame(step);\n });\n\n return () => cancelAnimationFrame(frame);\n }, [chasing]);\n\n if (!chasing) return text;\n\n return text.slice(0, stops[shown]);\n}\n"],"names":["useState","useMemo","useRef","useEffect"],"mappings":"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"}
@@ -0,0 +1,68 @@
1
+ import { useState, useMemo, useRef, useEffect } from 'react';
2
+
3
+ const BUFFER_MS = 1e3;
4
+ const CORRECT_MS = 1e3;
5
+ const FLOW_MS = 700;
6
+ const EASE_MS = 400;
7
+ const SLOWEST = 10;
8
+ const FASTEST = 60;
9
+ const MAX_LAG = 300;
10
+ function measure(flow, now, gained) {
11
+ const elapsed = Math.max(0, now - flow.at);
12
+ flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1e3);
13
+ flow.at = Math.max(flow.at, now);
14
+ return flow.rate;
15
+ }
16
+ function prefersReducedMotion() {
17
+ if (typeof window === "undefined" || !window.matchMedia) return false;
18
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
19
+ }
20
+ function wordStops(text) {
21
+ const stops = [0];
22
+ const word = /\S+/g;
23
+ for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);
24
+ return stops;
25
+ }
26
+ function useRevealedText(text, streaming) {
27
+ const [calm] = useState(prefersReducedMotion);
28
+ const stops = useMemo(() => wordStops(text), [text]);
29
+ const words = stops.length - 1;
30
+ const [landed, setLanded] = useState(() => streaming && !calm ? 0 : words);
31
+ const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));
32
+ if (shown !== landed) setLanded(shown);
33
+ const chasing = shown < words;
34
+ const goal = useRef(words);
35
+ const cursor = useRef(shown);
36
+ const flow = useRef({ rate: 0, at: 0 });
37
+ const pace = useRef(SLOWEST);
38
+ useEffect(() => {
39
+ measure(flow.current, performance.now(), Math.max(0, words - goal.current));
40
+ goal.current = words;
41
+ if (cursor.current < shown || cursor.current > words) cursor.current = shown;
42
+ });
43
+ useEffect(() => {
44
+ if (!chasing) return;
45
+ let drawn = performance.now();
46
+ let frame = requestAnimationFrame(function step(now) {
47
+ const elapsed = Math.max(0, now - drawn);
48
+ drawn = Math.max(drawn, now);
49
+ const arriving = measure(flow.current, now, 0);
50
+ const from = cursor.current;
51
+ const behind = goal.current - from;
52
+ if (behind > 0) {
53
+ const held = arriving * BUFFER_MS / 1e3;
54
+ const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + (behind - held) * 1e3 / CORRECT_MS));
55
+ pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);
56
+ cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + elapsed * pace.current / 1e3);
57
+ if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));
58
+ }
59
+ frame = requestAnimationFrame(step);
60
+ });
61
+ return () => cancelAnimationFrame(frame);
62
+ }, [chasing]);
63
+ if (!chasing) return text;
64
+ return text.slice(0, stops[shown]);
65
+ }
66
+
67
+ export { useRevealedText as u };
68
+ //# sourceMappingURL=use-reveal-d77hxHz9.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"use-reveal-d77hxHz9.js","sources":["../src/ds/components/MarkdownRenderer/use-reveal.ts"],"sourcesContent":["import { useEffect, useMemo, useRef, useState } from 'react';\n\n/** How much of the reply the reveal aims to be holding back, as time at the speed it is playing. */\nconst BUFFER_MS = 1000;\n\n/** How long it takes to work off a buffer that is too full or too empty. */\nconst CORRECT_MS = 1000;\n\n/** How long a word keeps counting towards the speed the reply is arriving at. */\nconst FLOW_MS = 700;\n\n/** How long the reveal takes to adopt a new speed, so a change of pace is never a jolt. */\nconst EASE_MS = 400;\n\n/** Words per second it will not go below: a short burst between two tool calls still reads briskly. */\nconst SLOWEST = 10;\n\n/** Words per second it will not go past: one word a frame, all a screen can draw. */\nconst FASTEST = 60;\n\n/** Words it will never fall further behind than — about five seconds at full speed. */\nconst MAX_LAG = 300;\n\ninterface Flow {\n /** Words per second the reply is arriving at, as an exponential average. */\n rate: number;\n at: number;\n}\n\n/** Decays what earlier words still count for, then deposits the ones that just arrived. */\nfunction measure(flow: Flow, now: number, gained: number): number {\n // rAF timestamps and performance.now() are sampled at different points of a\n // frame, so a fresh loop can read time slightly behind the last deposit.\n const elapsed = Math.max(0, now - flow.at);\n flow.rate = flow.rate * Math.exp(-elapsed / FLOW_MS) + gained / (FLOW_MS / 1000);\n flow.at = Math.max(flow.at, now);\n\n return flow.rate;\n}\n\nfunction prefersReducedMotion(): boolean {\n if (typeof window === 'undefined' || !window.matchMedia) return false;\n return window.matchMedia('(prefers-reduced-motion: reduce)').matches;\n}\n\n/** Where the reveal can cut the text: before the first word, then after each one. */\nfunction wordStops(text: string): number[] {\n const stops = [0];\n const word = /\\S+/g;\n\n for (let match = word.exec(text); match; match = word.exec(text)) stops.push(match.index + match[0].length);\n\n return stops;\n}\n\n/**\n * A jitter buffer for a streamed reply, counted in words. Chunks reach the\n * browser unevenly — a proxy flushes, a tool call ends, the model changes pace —\n * and drawing each one on arrival makes a reply lurch: ten words at once, then\n * nothing for a fifth of a second.\n *\n * So the reveal runs on its own clock: a frame loop that chunks never interrupt,\n * laying down at most one word per frame however far behind it is. It plays at\n * the speed the reply is arriving at, measured directly, and leans on that only\n * to work the buffer back to `BUFFER_MS` of held text when it drifts. Measuring\n * the speed rather than inferring it from the buffer is what lets the buffer stay\n * shallow: a reply that lasts two seconds still finds its pace in the first few\n * hundred milliseconds, where a deeper buffer would spend the whole reply filling\n * and then sprint to catch up.\n *\n * The reply it paces is whatever the caller hands it, so a caller with more than\n * prose to lay down — a tool row between two paragraphs — paces the whole of it\n * from one place and draws the rest against the same cursor.\n *\n * Nothing here reads `streaming` past the first render: a reply that stops\n * arriving is one whose measured speed decays, which winds the buffer down on its\n * own. Words, not characters, are what a reader sees land, and they are not the\n * same length: an even flow of characters still drops \"and if we do\" in one frame\n * and spends four on \"implementation\".\n */\nexport function useRevealedText(text: string, streaming: boolean): string {\n const [calm] = useState(prefersReducedMotion);\n const stops = useMemo(() => wordStops(text), [text]);\n const words = stops.length - 1;\n\n const [landed, setLanded] = useState(() => (streaming && !calm ? 0 : words));\n\n // Winds the cursor back when a shorter reply replaces the one on screen, which\n // no key can express: same element, same message, new text.\n const shown = calm ? words : Math.min(words, Math.max(landed, words - MAX_LAG));\n if (shown !== landed) setLanded(shown);\n\n const chasing = shown < words;\n const goal = useRef(words);\n const cursor = useRef(shown);\n const flow = useRef<Flow>({ rate: 0, at: 0 });\n const pace = useRef(SLOWEST);\n\n useEffect(() => {\n measure(flow.current, performance.now(), Math.max(0, words - goal.current));\n goal.current = words;\n\n if (cursor.current < shown || cursor.current > words) cursor.current = shown;\n });\n\n useEffect(() => {\n if (!chasing) return;\n\n let drawn = performance.now();\n let frame = requestAnimationFrame(function step(now) {\n const elapsed = Math.max(0, now - drawn);\n drawn = Math.max(drawn, now);\n\n const arriving = measure(flow.current, now, 0);\n const from = cursor.current;\n const behind = goal.current - from;\n\n if (behind > 0) {\n const held = (arriving * BUFFER_MS) / 1000;\n const aim = Math.min(FASTEST, Math.max(SLOWEST, arriving + ((behind - held) * 1000) / CORRECT_MS));\n\n pace.current += (aim - pace.current) * Math.min(1, elapsed / EASE_MS);\n cursor.current = Math.min(goal.current, Math.floor(from) + 1, from + (elapsed * pace.current) / 1000);\n\n if (Math.floor(cursor.current) !== Math.floor(from)) setLanded(Math.floor(cursor.current));\n }\n\n frame = requestAnimationFrame(step);\n });\n\n return () => cancelAnimationFrame(frame);\n }, [chasing]);\n\n if (!chasing) return text;\n\n return text.slice(0, stops[shown]);\n}\n"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@mastra/playground-ui",
3
3
  "type": "module",
4
- "version": "51.2.0-alpha.1",
4
+ "version": "51.3.0-alpha.0",
5
5
  "description": "Mastra Playground components",
6
6
  "sideEffects": [
7
7
  "**/*.css"
@@ -161,8 +161,8 @@
161
161
  "react": ">=19.0.0",
162
162
  "react-dom": ">=19.0.0",
163
163
  "tailwindcss": "^4.0.0",
164
- "@mastra/client-js": "^1.42.2-alpha.1",
165
- "@mastra/react": "1.4.7-alpha.1"
164
+ "@mastra/client-js": "^1.42.3-alpha.0",
165
+ "@mastra/react": "1.4.8-alpha.0"
166
166
  },
167
167
  "devDependencies": {
168
168
  "@storybook/addon-a11y": "^10.4.1",
@@ -202,10 +202,10 @@
202
202
  "vite-plugin-dts": "^4.5.4",
203
203
  "vite-plugin-lib-inject-css": "^2.2.2",
204
204
  "vitest": "4.1.10",
205
- "@mastra/client-js": "^1.42.2-alpha.1",
206
- "@internal/lint": "0.0.126",
207
- "@mastra/core": "1.63.0-alpha.1",
208
- "@mastra/react": "1.4.7-alpha.1",
205
+ "@internal/lint": "0.0.127",
206
+ "@mastra/client-js": "^1.42.3-alpha.0",
207
+ "@mastra/core": "1.63.1-alpha.0",
208
+ "@mastra/react": "1.4.8-alpha.0",
209
209
  "@mastra/memory": "1.28.0"
210
210
  },
211
211
  "homepage": "https://mastra.ai",