@dbx-tools/ui-mastra 0.6.63 → 0.6.64

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json CHANGED
@@ -24,12 +24,12 @@
24
24
  "typescript": "^5.9.3"
25
25
  },
26
26
  "dependencies": {
27
- "@dbx-tools/shared-core": "0.6.63",
28
- "@dbx-tools/shared-genie": "0.6.63",
29
- "@dbx-tools/shared-mastra": "0.6.63",
30
- "@dbx-tools/shared-model": "0.6.63",
31
- "@dbx-tools/ui-appkit": "0.6.63",
32
- "@dbx-tools/ui-branding": "0.6.63",
27
+ "@dbx-tools/shared-core": "0.6.64",
28
+ "@dbx-tools/shared-genie": "0.6.64",
29
+ "@dbx-tools/shared-mastra": "0.6.64",
30
+ "@dbx-tools/shared-model": "0.6.64",
31
+ "@dbx-tools/ui-appkit": "0.6.64",
32
+ "@dbx-tools/ui-branding": "0.6.64",
33
33
  "@mastra/client-js": "^1.28.0",
34
34
  "@tanstack/react-table": "^8.21.3",
35
35
  "ai": "^5.0.0",
@@ -47,7 +47,7 @@
47
47
  "publishConfig": {
48
48
  "access": "public"
49
49
  },
50
- "version": "0.6.63",
50
+ "version": "0.6.64",
51
51
  "type": "module",
52
52
  "exports": {
53
53
  "./react": "./src/react/index.ts",
@@ -1,14 +1,15 @@
1
1
  import { marker as markers, type ParsedMarker } from "@dbx-tools/shared-mastra";
2
2
  import { Spinner } from "@dbx-tools/ui-appkit/react";
3
- import ReactECharts from "echarts-for-react";
4
3
  import { ClockIcon } from "lucide-react";
5
- import { useMemo, useRef } from "react";
4
+ import { lazy, Suspense, useMemo, useRef } from "react";
6
5
  import { DataGrid, humanizeLabel } from "./data-grid.tsx";
7
6
  import { AssistantMarkdown } from "./markdown.tsx";
8
7
  import { normalizeChartOption } from "../support/chart-option.ts";
9
8
  import { useChartChrome } from "../support/chart-theme.ts";
10
9
  import { useChartFetch, useStatementFetch } from "../support/mastra-client.ts";
11
10
 
11
+ const ReactECharts = lazy(() => import("echarts-for-react"));
12
+
12
13
  // Inline embed slots: chart / data tables resolved from `[chart:<id>]`
13
14
  // and `[data:<id>]` markers in the assistant's prose, plus the splitter
14
15
  // that interleaves those slots with the surrounding markdown.
@@ -32,6 +33,16 @@ import { useChartFetch, useStatementFetch } from "../support/mastra-client.ts";
32
33
  const CHART_FRAME_CLASSES = "not-prose my-3 max-w-full rounded border border-border bg-card p-2";
33
34
  const CHART_HEIGHT_PX = 320;
34
35
 
36
+ /** Fixed chart placeholder shared by data loading and renderer loading. */
37
+ const ChartLoading = () => (
38
+ <div
39
+ className="flex items-center justify-center"
40
+ style={{ height: CHART_HEIGHT_PX, width: "100%" }}
41
+ >
42
+ <Spinner className="size-5 text-muted-foreground" />
43
+ </div>
44
+ );
45
+
35
46
  /**
36
47
  * Compact inline notice rendered in place of an embed - a chart, data
37
48
  * table, or any future `[<type>:<id>]` marker - whose backing cache
@@ -107,19 +118,16 @@ const ChartSlot = ({ chartId }: { chartId: string }) => {
107
118
  return (
108
119
  <div ref={frameRef} className={CHART_FRAME_CLASSES}>
109
120
  {option ? (
110
- <ReactECharts
111
- option={option}
112
- style={{ height: CHART_HEIGHT_PX, width: "100%" }}
113
- notMerge
114
- lazyUpdate
115
- />
121
+ <Suspense fallback={<ChartLoading />}>
122
+ <ReactECharts
123
+ option={option}
124
+ style={{ height: CHART_HEIGHT_PX, width: "100%" }}
125
+ notMerge
126
+ lazyUpdate
127
+ />
128
+ </Suspense>
116
129
  ) : (
117
- <div
118
- className="flex items-center justify-center"
119
- style={{ height: CHART_HEIGHT_PX, width: "100%" }}
120
- >
121
- <Spinner className="size-5 text-muted-foreground" />
122
- </div>
130
+ <ChartLoading />
123
131
  )}
124
132
  </div>
125
133
  );
@@ -13,8 +13,7 @@ import {
13
13
  cn,
14
14
  } from "@dbx-tools/ui-appkit/react";
15
15
  import { CheckIcon, CopyIcon } from "lucide-react";
16
- import React, { useEffect, useMemo, useRef, useState } from "react";
17
- import { format as formatSql } from "sql-formatter";
16
+ import React, { useEffect, useRef, useState } from "react";
18
17
  import { Streamdown } from "streamdown";
19
18
  import { DataGrid, TABLE_WRAPPER_CLASSES, colorizeDelta, type DataRow } from "./data-grid.tsx";
20
19
  import { copyText } from "../support/clipboard.ts";
@@ -289,9 +288,10 @@ export const ToolMarkdown = ({ children }: { children: string }) => (
289
288
  * constructs or a partial query), so we fall back to the raw string
290
289
  * rather than dropping the preview.
291
290
  */
292
- function prettySql(sql: string): string {
291
+ async function _prettySql(sql: string): Promise<string> {
293
292
  try {
294
- return formatSql(sql, { language: "spark", keywordCase: "upper" });
293
+ const { format } = await import("sql-formatter");
294
+ return format(sql, { language: "spark", keywordCase: "upper" });
295
295
  } catch {
296
296
  return sql;
297
297
  }
@@ -335,7 +335,7 @@ const CopyButton = ({ value, className }: { value: string; className?: string })
335
335
 
336
336
  /**
337
337
  * Render a SQL string as a syntax-highlighted code block. The query is
338
- * first run through {@link prettySql} so one-line Genie output reads as
338
+ * first run through {@link _prettySql} so one-line Genie output reads as
339
339
  * formatted SQL, then highlighted to minimal inline HTML via
340
340
  * {@link highlightToHtml} (shiki). Unlike Streamdown's code renderer,
341
341
  * this emits a plain `<pre><code>` with only per-token color spans - no
@@ -345,18 +345,23 @@ const CopyButton = ({ value, className }: { value: string; className?: string })
345
345
  * the tokens are ready to avoid a flash of empty space.
346
346
  */
347
347
  export const SqlBlock = ({ sql }: { sql: string }) => {
348
- const formatted = useMemo(() => prettySql(sql), [sql]);
348
+ const [formatted, setFormatted] = useState(sql);
349
349
  const [html, setHtml] = useState<string | null>(null);
350
350
  useEffect(() => {
351
351
  let active = true;
352
+ setFormatted(sql);
352
353
  setHtml(null);
353
- void highlightToHtml(formatted, "sql").then((result) => {
354
- if (active) setHtml(result);
354
+ void _prettySql(sql).then((source) => {
355
+ if (!active) return;
356
+ setFormatted(source);
357
+ void highlightToHtml(source, "sql").then((result) => {
358
+ if (active) setHtml(result);
359
+ });
355
360
  });
356
361
  return () => {
357
362
  active = false;
358
363
  };
359
- }, [formatted]);
364
+ }, [sql]);
360
365
  return (
361
366
  <div className="group relative">
362
367
  <CopyButton
@@ -15,7 +15,7 @@ import type {
15
15
  ToolEvent,
16
16
  ToolProgress,
17
17
  } from "./types.ts";
18
- import { exportChat, type EmbedResolver, type ExportFormat } from "../support/export.ts";
18
+ import type { EmbedResolver, ExportFormat } from "../support/export.ts";
19
19
  import {
20
20
  useMastraClient,
21
21
  useMastraDefaultModel,
@@ -36,6 +36,8 @@ import {
36
36
  type ThreadSession,
37
37
  } from "../support/thread-sessions.ts";
38
38
 
39
+ const _loadChatExport = () => import("../support/export.ts");
40
+
39
41
  // Self-contained drop-in chat. `useMastraChat` drives the conversation
40
42
  // over `@mastra/client-js`: `agent.stream()` returns a Response
41
43
  // augmented with `processDataStream()`, which pushes typed Mastra
@@ -1274,6 +1276,7 @@ export const useMastraChat = (
1274
1276
  const title =
1275
1277
  (activeThreadId && threads.find((t) => t.id === activeThreadId)?.title) || "Conversation";
1276
1278
  try {
1279
+ const { exportChat } = await _loadChatExport();
1277
1280
  await exportChat({
1278
1281
  messages: getSession(activeKey).messages,
1279
1282
  format,
@@ -1294,6 +1297,7 @@ export const useMastraChat = (
1294
1297
  const exportMessage = useCallback(
1295
1298
  async (message: UIMessage, format: ExportFormat) => {
1296
1299
  try {
1300
+ const { exportChat } = await _loadChatExport();
1297
1301
  await exportChat({
1298
1302
  messages: [message],
1299
1303
  format,
@@ -0,0 +1,26 @@
1
+ /**
2
+ * Fine-grained Shiki runtime containing only the chat's supported languages.
3
+ *
4
+ * This module is itself dynamically imported, so its grammar, theme, and WASM
5
+ * dependencies remain off the network until a supported code block appears.
6
+ */
7
+ import { createHighlighterCore } from "shiki/core";
8
+ import { createOnigurumaEngine } from "shiki/engine/oniguruma";
9
+ import bash from "shiki/langs/bash.mjs";
10
+ import javascript from "shiki/langs/javascript.mjs";
11
+ import json from "shiki/langs/json.mjs";
12
+ import markdown from "shiki/langs/markdown.mjs";
13
+ import python from "shiki/langs/python.mjs";
14
+ import sql from "shiki/langs/sql.mjs";
15
+ import typescript from "shiki/langs/typescript.mjs";
16
+ import yaml from "shiki/langs/yaml.mjs";
17
+ import githubLight from "shiki/themes/github-light.mjs";
18
+
19
+ /** Create the shared highlighter with the exact configured language set. */
20
+ export function createHighlighter() {
21
+ return createHighlighterCore({
22
+ themes: [githubLight],
23
+ langs: [sql, python, typescript, javascript, json, bash, yaml, markdown],
24
+ engine: createOnigurumaEngine(import("shiki/wasm")),
25
+ });
26
+ }
@@ -26,8 +26,7 @@
26
26
  import { string } from "@dbx-tools/shared-core";
27
27
  import { marker as markers, type Chart, type StatementData } from "@dbx-tools/shared-mastra";
28
28
  import type { UIMessage } from "ai";
29
- import * as echarts from "echarts";
30
- import { marked } from "marked";
29
+ import type { EChartsCoreOption } from "echarts";
31
30
  import { normalizeChartOption } from "./chart-option.ts";
32
31
  import { LIGHT_CHART_CHROME } from "./chart-theme.ts";
33
32
  import { downloadFile } from "./download.ts";
@@ -271,7 +270,9 @@ async function messageBodyHtml(message: UIMessage, resolver: EmbedResolver): Pro
271
270
  if (seg.kind === "text") {
272
271
  if (!seg.text.trim()) continue;
273
272
  parts.push(
274
- isAssistant ? markdownToHtml(seg.text) : `<p class="plain">${escapeHtml(seg.text)}</p>`,
273
+ isAssistant
274
+ ? await _markdownToHtml(seg.text)
275
+ : `<p class="plain">${escapeHtml(seg.text)}</p>`,
275
276
  );
276
277
  continue;
277
278
  }
@@ -287,7 +288,8 @@ async function messageBodyHtml(message: UIMessage, resolver: EmbedResolver): Pro
287
288
  }
288
289
 
289
290
  /** Render a markdown fragment to an HTML string (GFM tables, line breaks). */
290
- function markdownToHtml(md: string): string {
291
+ async function _markdownToHtml(md: string): Promise<string> {
292
+ const { marked } = await import("marked");
291
293
  return marked.parse(md, { async: false, gfm: true, breaks: true }) as string;
292
294
  }
293
295
 
@@ -309,6 +311,7 @@ async function chartSvg(resolver: EmbedResolver, id: string): Promise<string | n
309
311
  try {
310
312
  const chart = await resolver.chart(id);
311
313
  if (!chart?.result) return null;
314
+ const echarts = await import("echarts");
312
315
  const instance = echarts.init(null, undefined, {
313
316
  renderer: "svg",
314
317
  ssr: true,
@@ -317,7 +320,7 @@ async function chartSvg(resolver: EmbedResolver, id: string): Promise<string | n
317
320
  });
318
321
  try {
319
322
  instance.setOption(
320
- normalizeChartOption(chart.result.option, LIGHT_CHART_CHROME) as echarts.EChartsCoreOption,
323
+ normalizeChartOption(chart.result.option, LIGHT_CHART_CHROME) as EChartsCoreOption,
321
324
  );
322
325
  return instance.renderToSVGString();
323
326
  } finally {
@@ -1,10 +1,6 @@
1
1
  import { string } from "@dbx-tools/shared-core";
2
- import {
3
- createHighlighter,
4
- type BundledLanguage,
5
- type BundledTheme,
6
- type Highlighter,
7
- } from "shiki";
2
+ import type { BundledLanguage, BundledTheme } from "shiki";
3
+ import type { HighlighterCore } from "shiki/core";
8
4
  import type { CodeHighlighterPlugin } from "streamdown";
9
5
 
10
6
  // Streamdown 2.x ships syntax highlighting as an opt-in plugin and has
@@ -39,20 +35,22 @@ const THEME: BundledTheme = "github-light";
39
35
  /** Languages we can tokenize, as a set for O(1) support checks. */
40
36
  const SUPPORTED = new Set<BundledLanguage>(LANGUAGES);
41
37
 
42
- let _highlighter: Highlighter | null = null;
43
- let _loading: Promise<Highlighter> | null = null;
38
+ let _highlighter: HighlighterCore | null = null;
39
+ let _loading: Promise<HighlighterCore> | null = null;
44
40
 
45
41
  /** Lazily create the shared shiki highlighter (singleton, loaded once). */
46
- function loadHighlighter(): Promise<Highlighter> {
47
- _loading ??= createHighlighter({ themes: [THEME], langs: LANGUAGES }).then((h) => {
48
- _highlighter = h;
49
- return h;
50
- });
42
+ function loadHighlighter(): Promise<HighlighterCore> {
43
+ _loading ??= import("./_shiki-runtime.ts")
44
+ .then(({ createHighlighter }) => createHighlighter())
45
+ .then((highlighter) => {
46
+ _highlighter = highlighter;
47
+ return highlighter;
48
+ });
51
49
  return _loading;
52
50
  }
53
51
 
54
52
  /** Tokenize `code` with the active theme into Streamdown's result shape. */
55
- function highlightTokens(h: Highlighter, code: string, language: BundledLanguage) {
53
+ function highlightTokens(h: HighlighterCore, code: string, language: BundledLanguage) {
56
54
  const { tokens, fg, bg, rootStyle } = h.codeToTokens(code, {
57
55
  lang: language,
58
56
  theme: THEME,