@dbx-tools/appkit-mastra 0.3.20 → 0.3.21

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/README.md CHANGED
@@ -251,6 +251,25 @@ Agents can return `[chart:<id>]` and `[data:<statement_id>]` markers in prose.
251
251
  The embed route resolves them later, which avoids forcing the language model to
252
252
  inline large tables or wait for chart planning before continuing its answer.
253
253
 
254
+ ### Brand The Charts
255
+
256
+ Pass a `brand` to the plugin to theme every generated chart with your brand's
257
+ palette and font; omit it for the default Echarts look.
258
+
259
+ ```ts
260
+ import { brand } from "@dbx-tools/shared-core";
261
+
262
+ plugin.mastra({ agents, storage: true, brand: brand.defaultBrandContext });
263
+ ```
264
+
265
+ `brand` is the portable `BrandContext` shared across the UI, email, and
266
+ libraries, so charts, email, and the chat UI theme from one source. The chart
267
+ planner derives an Echarts theme from it: a series color cycle seeded from
268
+ `colors.primary` / `colors.accent` (plus a colorblind-friendly spread so
269
+ many-series charts stay legible) and a base text style from `typography.sans` /
270
+ `colors.foreground`. Charts render to canvas, so this is applied server-side on
271
+ the Echarts option rather than through the browser `[data-brand]` CSS bridge.
272
+
254
273
  ## Model Selection
255
274
 
256
275
  `model.buildModel()` adapts the generic resolver from
package/package.json CHANGED
@@ -28,22 +28,22 @@
28
28
  "express": "^5.1.0",
29
29
  "pg": "^8.22.0",
30
30
  "zod": "^4.3.6",
31
- "@dbx-tools/appkit": "0.3.20",
32
- "@dbx-tools/core": "0.3.20",
33
- "@dbx-tools/databricks": "0.3.20",
34
- "@dbx-tools/model": "0.3.20",
35
- "@dbx-tools/shared-core": "0.3.20",
36
- "@dbx-tools/genie": "0.3.20",
37
- "@dbx-tools/shared-genie": "0.3.20",
38
- "@dbx-tools/shared-mastra": "0.3.20",
39
- "@dbx-tools/shared-model": "0.3.20"
31
+ "@dbx-tools/core": "0.3.21",
32
+ "@dbx-tools/appkit": "0.3.21",
33
+ "@dbx-tools/databricks": "0.3.21",
34
+ "@dbx-tools/shared-genie": "0.3.21",
35
+ "@dbx-tools/model": "0.3.21",
36
+ "@dbx-tools/shared-core": "0.3.21",
37
+ "@dbx-tools/genie": "0.3.21",
38
+ "@dbx-tools/shared-mastra": "0.3.21",
39
+ "@dbx-tools/shared-model": "0.3.21"
40
40
  },
41
41
  "main": "index.ts",
42
42
  "license": "UNLICENSED",
43
43
  "publishConfig": {
44
44
  "access": "public"
45
45
  },
46
- "version": "0.3.20",
46
+ "version": "0.3.21",
47
47
  "types": "index.ts",
48
48
  "type": "module",
49
49
  "exports": {
package/src/chart.ts CHANGED
@@ -37,7 +37,7 @@ import { z } from "zod";
37
37
  import type { MastraPluginConfig } from "./config";
38
38
  import { buildModel } from "./model";
39
39
  import { model } from "@dbx-tools/shared-model";
40
- import { async, error, hash, log, string } from "@dbx-tools/shared-core";
40
+ import { async, error, hash, log, string, type BrandContext } from "@dbx-tools/shared-core";
41
41
 
42
42
  const logger = log.logger("mastra/chart");
43
43
 
@@ -353,7 +353,7 @@ async function runChartPlanner(
353
353
  ...(abortSignal ? { abortSignal } : {}),
354
354
  });
355
355
  const plan = chartPlanSchema.parse(result.object);
356
- const option = planToEchartsOption(plan, title);
356
+ const option = planToEchartsOption(plan, title, config.brand);
357
357
  return { chartType: plan.chartType, option };
358
358
  }
359
359
 
@@ -551,6 +551,50 @@ export async function fetchChart(
551
551
  }
552
552
  }
553
553
 
554
+ /* ----------------------------- echarts theme ----------------------------- */
555
+
556
+ /**
557
+ * The slice of an Echarts option that carries brand identity: the series
558
+ * color cycle and the base text style (font family + foreground color).
559
+ * Derived from a {@link BrandContext} by {@link brandChartTheme} and merged
560
+ * into every spec by {@link planToEchartsOption}.
561
+ */
562
+ interface ChartTheme {
563
+ color: string[];
564
+ textStyle: { fontFamily: string; color: string };
565
+ }
566
+
567
+ /**
568
+ * Expand two brand hex colors into a legible categorical cycle. Echarts
569
+ * repeats the `color` array across series / categories, so a good default
570
+ * needs several distinct hues, not two. We seed with the brand primary and
571
+ * accent (the identity colors) and follow with a fixed, colorblind-friendly
572
+ * spread so multi-series / many-slice charts stay readable while the first
573
+ * one or two marks still land on-brand.
574
+ */
575
+ function brandColorCycle(primary: string, accent: string): string[] {
576
+ const spread = ["#5B8FF9", "#F6BD16", "#E86452", "#6DC8EC", "#945FB9", "#FF9845", "#1E9493"];
577
+ const seen = new Set<string>();
578
+ return [primary, accent, ...spread].filter((c) => {
579
+ const key = c.toLowerCase();
580
+ if (seen.has(key)) return false;
581
+ seen.add(key);
582
+ return true;
583
+ });
584
+ }
585
+
586
+ /**
587
+ * Derive an Echarts {@link ChartTheme} from a brand context: the primary +
588
+ * accent colors seed the series cycle, the sans stack becomes the base font,
589
+ * and the brand foreground becomes the base text color.
590
+ */
591
+ function brandChartTheme(brand: BrandContext): ChartTheme {
592
+ return {
593
+ color: brandColorCycle(brand.colors.primary, brand.colors.accent),
594
+ textStyle: { fontFamily: brand.typography.sans, color: brand.colors.foreground },
595
+ };
596
+ }
597
+
554
598
  /* ----------------------------- echarts expansion ----------------------------- */
555
599
 
556
600
  /**
@@ -559,10 +603,21 @@ export async function fetchChart(
559
603
  * compact plan shape; tooltip / animation / color / grid defaults
560
604
  * stay consistent across charts and are easy to tune without
561
605
  * retraining model behaviour.
606
+ *
607
+ * When `brand` is set, the resolved spec is themed with the brand's series
608
+ * color cycle and base text style (see {@link brandChartTheme}); otherwise
609
+ * Echarts' defaults apply.
562
610
  */
563
- function planToEchartsOption(plan: ChartPlan, fallbackTitle: string): Record<string, unknown> {
611
+ function planToEchartsOption(
612
+ plan: ChartPlan,
613
+ fallbackTitle: string,
614
+ brand?: BrandContext,
615
+ ): Record<string, unknown> {
564
616
  const baseTitle = plan.title ?? fallbackTitle;
565
617
  const grid = { left: 48, right: 24, top: 56, bottom: 48, containLabel: true };
618
+ const theme = brand ? brandChartTheme(brand) : undefined;
619
+ const themed = (option: Record<string, unknown>): Record<string, unknown> =>
620
+ theme ? { ...theme, ...option } : option;
566
621
 
567
622
  if (plan.chartType === "pie") {
568
623
  // Echarts crashes on null pie slices - filter them out.
@@ -573,7 +628,7 @@ function planToEchartsOption(plan: ChartPlan, fallbackTitle: string): Record<str
573
628
  (d): d is { name: string; value: number } =>
574
629
  d !== null && typeof d === "object" && !Array.isArray(d),
575
630
  );
576
- return {
631
+ return themed({
577
632
  title: { text: baseTitle, left: "center" },
578
633
  tooltip: { trigger: "item" },
579
634
  legend: { bottom: 0 },
@@ -585,14 +640,14 @@ function planToEchartsOption(plan: ChartPlan, fallbackTitle: string): Record<str
585
640
  data: slices,
586
641
  },
587
642
  ],
588
- };
643
+ });
589
644
  }
590
645
 
591
646
  if (plan.chartType === "scatter") {
592
647
  // Echarts crashes on null scatter points - keep only valid
593
648
  // `[x, y]` tuples. Bare numbers / objects / nulls from a
594
649
  // mismatched plan get dropped silently.
595
- return {
650
+ return themed({
596
651
  title: { text: baseTitle, left: "center" },
597
652
  tooltip: { trigger: "item" },
598
653
  legend: { bottom: 0 },
@@ -604,13 +659,13 @@ function planToEchartsOption(plan: ChartPlan, fallbackTitle: string): Record<str
604
659
  type: "scatter",
605
660
  data: s.data.filter((d): d is [number, number] => Array.isArray(d) && d.length === 2),
606
661
  })),
607
- };
662
+ });
608
663
  }
609
664
 
610
665
  // bar / line / area share the same axis layout.
611
666
  const isArea = plan.chartType === "area";
612
667
  const seriesType = plan.chartType === "bar" ? "bar" : "line";
613
- return {
668
+ return themed({
614
669
  title: { text: baseTitle, left: "center" },
615
670
  tooltip: { trigger: "axis" },
616
671
  legend: { bottom: 0 },
@@ -628,7 +683,7 @@ function planToEchartsOption(plan: ChartPlan, fallbackTitle: string): Record<str
628
683
  smooth: seriesType === "line",
629
684
  ...(isArea ? { areaStyle: {} } : {}),
630
685
  })),
631
- };
686
+ });
632
687
  }
633
688
 
634
689
  /* ----------------------------- render_data tool ----------------------------- */
package/src/config.ts CHANGED
@@ -8,6 +8,7 @@
8
8
  */
9
9
 
10
10
  import type { BasePluginConfig } from "@databricks/appkit";
11
+ import type { BrandContext } from "@dbx-tools/shared-core";
11
12
  import type { AgentConfig } from "@mastra/core/agent";
12
13
  import { MASTRA_RESOURCE_ID_KEY, MASTRA_THREAD_ID_KEY } from "@mastra/core/request-context";
13
14
  import type { PgVectorConfig, PostgresStoreConfig } from "@mastra/pg";
@@ -442,4 +443,19 @@ export interface MastraPluginConfig extends BasePluginConfig {
442
443
  * surface.
443
444
  */
444
445
  apiAccess?: "scoped" | "full";
446
+ /**
447
+ * Optional brand context applied to charts produced by the built-in
448
+ * `render_data` / `prepare_chart` tools. When set, the chart planner's
449
+ * Echarts output is themed with the brand's palette (series colors derived
450
+ * from `colors.primary` / `colors.accent`) and sans font
451
+ * (`typography.sans`) instead of Echarts' defaults. Omit for the default
452
+ * Echarts look.
453
+ *
454
+ * Pass the portable {@link BrandContext} shared across the UI and
455
+ * libraries (e.g. `brand.defaultBrandContext` from `@dbx-tools/shared-core`,
456
+ * or a customer brand) - the same object the email add-on and the UI
457
+ * `BrandProvider` consume, so a host themes charts, email, and UI from one
458
+ * source.
459
+ */
460
+ brand?: BrandContext;
445
461
  }