@dbx-tools/ui-mastra 0.6.63 → 0.6.65
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.
|
|
28
|
-
"@dbx-tools/shared-genie": "0.6.
|
|
29
|
-
"@dbx-tools/shared-mastra": "0.6.
|
|
30
|
-
"@dbx-tools/shared-model": "0.6.
|
|
31
|
-
"@dbx-tools/ui-appkit": "0.6.
|
|
32
|
-
"@dbx-tools/ui-branding": "0.6.
|
|
27
|
+
"@dbx-tools/shared-core": "0.6.65",
|
|
28
|
+
"@dbx-tools/shared-genie": "0.6.65",
|
|
29
|
+
"@dbx-tools/shared-mastra": "0.6.65",
|
|
30
|
+
"@dbx-tools/shared-model": "0.6.65",
|
|
31
|
+
"@dbx-tools/ui-appkit": "0.6.65",
|
|
32
|
+
"@dbx-tools/ui-branding": "0.6.65",
|
|
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.
|
|
50
|
+
"version": "0.6.65",
|
|
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
|
-
<
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
<
|
|
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
|
);
|
package/src/react/markdown.tsx
CHANGED
|
@@ -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,
|
|
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
|
|
291
|
+
async function _prettySql(sql: string): Promise<string> {
|
|
293
292
|
try {
|
|
294
|
-
|
|
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
|
|
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 =
|
|
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
|
|
354
|
-
if (active)
|
|
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
|
-
}, [
|
|
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 {
|
|
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
|
+
}
|
package/src/support/export.ts
CHANGED
|
@@ -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
|
|
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
|
|
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
|
|
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
|
|
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
|
-
|
|
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:
|
|
43
|
-
let _loading: Promise<
|
|
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<
|
|
47
|
-
_loading ??=
|
|
48
|
-
|
|
49
|
-
|
|
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:
|
|
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,
|