@dbx-tools/ui-mastra 0.1.24 → 0.3.2

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
@@ -26,19 +26,19 @@
26
26
  "shiki": "^3.0.0",
27
27
  "sql-formatter": "^15.6.9",
28
28
  "streamdown": "^2.5.0",
29
- "@dbx-tools/shared-core": "0.1.24",
30
- "@dbx-tools/shared-genie": "0.1.24",
31
- "@dbx-tools/shared-mastra": "0.1.24",
32
- "@dbx-tools/shared-model": "0.1.24",
33
- "@dbx-tools/ui-appkit": "0.1.24",
34
- "@dbx-tools/ui-branding": "0.1.24"
29
+ "@dbx-tools/shared-core": "0.3.2",
30
+ "@dbx-tools/shared-genie": "0.3.2",
31
+ "@dbx-tools/ui-appkit": "0.3.2",
32
+ "@dbx-tools/shared-mastra": "0.3.2",
33
+ "@dbx-tools/shared-model": "0.3.2",
34
+ "@dbx-tools/ui-branding": "0.3.2"
35
35
  },
36
36
  "main": "index.ts",
37
37
  "license": "UNLICENSED",
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "version": "0.1.24",
41
+ "version": "0.3.2",
42
42
  "types": "index.ts",
43
43
  "type": "module",
44
44
  "exports": {
@@ -100,6 +100,7 @@ export const ChatView = ({
100
100
  models,
101
101
  model,
102
102
  onModelChange,
103
+ defaultModelName,
103
104
  onLoadMore,
104
105
  isLoadingMore = false,
105
106
  hasMore = false,
@@ -285,11 +286,22 @@ export const ChatView = ({
285
286
  // otherwise as static text.
286
287
  const showModelDisplay = Boolean(onModelChange);
287
288
  const modelChangeable = Boolean(models && models.length > 0);
288
- // Label the current model by its human-readable name when the catalogue
289
- // carries one (`displayName`), falling back to the raw endpoint id, then
290
- // to "Server default" when no model is pinned.
291
- const currentModelLabel =
292
- models?.find((m) => m.name === model)?.displayName || model || "Server default";
289
+ // Human-readable name for an endpoint id, using the catalogue's
290
+ // `displayName` when present, else the raw id.
291
+ const modelLabel = (name?: string): string | undefined => {
292
+ if (!name) return undefined;
293
+ return models?.find((m) => m.name === name)?.displayName || name;
294
+ };
295
+ // The "Server default" option names the actual endpoint the server falls
296
+ // back to (from `defaultModelName`) when known: "Server default (Claude
297
+ // Sonnet 4.6)". Plain "Server default" when the server didn't report one.
298
+ const defaultOptionLabel = (() => {
299
+ const named = modelLabel(defaultModelName);
300
+ return named ? `Server default (${named})` : "Server default";
301
+ })();
302
+ // Label the current model by its human-readable name when a model is
303
+ // pinned, else the default-option label (which names the server default).
304
+ const currentModelLabel = modelLabel(model) || defaultOptionLabel;
293
305
  const showClear = Boolean(onClear);
294
306
  const showExport = Boolean(onExportConversation);
295
307
  // The conversation sidebar turns on once the host wires both the
@@ -680,10 +692,12 @@ export const ChatView = ({
680
692
  size="sm"
681
693
  className="h-7 w-auto max-w-[200px] gap-1 rounded-full px-2.5 text-xs [&_svg]:size-3"
682
694
  >
683
- <SelectValue placeholder="Server default" />
695
+ <SelectValue placeholder={defaultOptionLabel} />
684
696
  </SelectTrigger>
685
697
  <SelectContent>
686
- <SelectItem value={DEFAULT_MODEL_VALUE}>Server default</SelectItem>
698
+ <SelectItem value={DEFAULT_MODEL_VALUE}>
699
+ {defaultOptionLabel}
700
+ </SelectItem>
687
701
  {models!.map((m) => (
688
702
  <SelectItem key={m.name} value={m.name}>
689
703
  {m.displayName || m.name}
@@ -8,7 +8,7 @@ import {
8
8
  TooltipContent,
9
9
  TooltipTrigger,
10
10
  } from "@dbx-tools/ui-appkit/react";
11
- import { DownloadIcon, FileTextIcon, PrinterIcon } from "lucide-react";
11
+ import { DownloadIcon, FileTextIcon } from "lucide-react";
12
12
  import type { ExportFormat } from "../support/export";
13
13
 
14
14
  // Shared export affordance: a download button that opens a small menu of
@@ -22,7 +22,6 @@ const FORMATS: ReadonlyArray<{
22
22
  Icon: typeof DownloadIcon;
23
23
  }> = [
24
24
  { format: "pdf", label: "PDF", Icon: DownloadIcon },
25
- { format: "print", label: "Print", Icon: PrinterIcon },
26
25
  { format: "markdown", label: "Markdown", Icon: FileTextIcon },
27
26
  ];
28
27
 
@@ -15,6 +15,7 @@ export {
15
15
  useChartFetch,
16
16
  useMastraClient,
17
17
  useMastraConfig,
18
+ useMastraDefaultModel,
18
19
  useMastraModels,
19
20
  useMastraSuggestions,
20
21
  useMastraThreads,
@@ -10,6 +10,7 @@ import { exportChat, type EmbedResolver, type ExportFormat } from "../support/ex
10
10
  import { useBrand } from "@dbx-tools/ui-branding/react";
11
11
  import {
12
12
  useMastraClient,
13
+ useMastraDefaultModel,
13
14
  useMastraModels,
14
15
  useMastraSuggestions,
15
16
  useMastraThreads,
@@ -347,6 +348,10 @@ export const useMastraChat = (
347
348
  // hidden and skips the catalogue fetch entirely.
348
349
  const showModelPicker = Boolean(options.showModelPicker);
349
350
  const { models } = useMastraModels(showModelPicker);
351
+ // The endpoint the active agent falls back to when no model is pinned, so
352
+ // the picker can name its "Server default" option. Fetched only when the
353
+ // picker is shown; `null` when the agent's model is dynamic.
354
+ const defaultModelName = useMastraDefaultModel(agentId, showModelPicker);
350
355
  // Starter suggestions: an explicit `options.suggestions` always
351
356
  // wins (including `[]` to force none) and is rendered verbatim;
352
357
  // otherwise auto-source the agent's Genie space sample questions.
@@ -1333,6 +1338,7 @@ export const useMastraChat = (
1333
1338
  models: showModelPicker ? models : undefined,
1334
1339
  model,
1335
1340
  onModelChange: showModelPicker ? setModel : undefined,
1341
+ defaultModelName: showModelPicker ? (defaultModelName ?? undefined) : undefined,
1336
1342
  onLoadMore: loadOlderHistory,
1337
1343
  isLoadingMore,
1338
1344
  hasMore: activeSession.hasMoreHistory,
@@ -116,6 +116,13 @@ export type ChatViewProps = {
116
116
  /** Currently selected model name; empty string means "use server default". */
117
117
  model?: string;
118
118
  onModelChange?: (model: string) => void;
119
+ /**
120
+ * The serving-endpoint id the server falls back to when no model is pinned.
121
+ * When set, the picker's "Server default" option names it (e.g. "Server
122
+ * default (Claude Sonnet 4.6)"), using the matching `models` entry's
123
+ * `displayName` when available.
124
+ */
125
+ defaultModelName?: string;
119
126
  /**
120
127
  * Optional infinite-scroll-up handler. Fired when the user scrolls
121
128
  * within `TOP_LOAD_MORE_THRESHOLD_PX` of the top of the
@@ -8,7 +8,6 @@
8
8
  * - `"pdf"` - a print-ready HTML document rendered in a hidden iframe
9
9
  * with the browser's print dialog triggered, so the user
10
10
  * saves a real PDF ("Save as PDF") with no popup tab.
11
- * - `"print"` - the same document routed to a paper printer.
12
11
  * - `"markdown"` - a `.md` file download.
13
12
  *
14
13
  * The document can be brand-styled (logo, colors, font) via the optional
@@ -37,10 +36,9 @@ import { normalizeChartOption } from "./chart-option";
37
36
  * Output formats {@link exportChat} can produce.
38
37
  * - `"pdf"` - Save-as-PDF via a hidden print iframe (dialog defaults to
39
38
  * "Save as PDF"); no new tab.
40
- * - `"print"` - same rendered document + print dialog, for a paper printer.
41
39
  * - `"markdown"` - a `.md` file download.
42
40
  */
43
- export type ExportFormat = "pdf" | "print" | "markdown";
41
+ export type ExportFormat = "pdf" | "markdown";
44
42
 
45
43
  /**
46
44
  * Optional brand styling for the exported document. Plain data (no React /
@@ -102,14 +100,13 @@ const CHART_HEIGHT_PX = 380;
102
100
  const PRINT_SETTLE_MS = 300;
103
101
 
104
102
  /**
105
- * Export `messages` as a PDF, a print job, or a Markdown file.
103
+ * Export `messages` as a PDF or a Markdown file.
106
104
  *
107
- * `"pdf"` and `"print"` render the same branded, self-contained HTML
108
- * document and drive it through a hidden `<iframe>` + `print()` - so the
109
- * browser's print dialog (defaulting to "Save as PDF" for `pdf`) opens
110
- * directly, with no popup tab. If the iframe path can't run (e.g. no DOM
111
- * body), the document is downloaded as a self-contained `.html` file so the
112
- * export - charts included - still lands.
105
+ * `"pdf"` renders a branded, self-contained HTML document and drives it
106
+ * through a hidden `<iframe>` + `print()` - so the browser's print dialog
107
+ * (defaulting to "Save as PDF") opens directly, with no popup tab. If the
108
+ * iframe path can't run (e.g. no DOM body), the document is downloaded as a
109
+ * self-contained `.html` file so the export - charts included - still lands.
113
110
  */
114
111
  export async function exportChat(options: ExportChatOptions): Promise<void> {
115
112
  const { messages, format, resolver } = options;
@@ -123,8 +120,8 @@ export async function exportChat(options: ExportChatOptions): Promise<void> {
123
120
  return;
124
121
  }
125
122
 
126
- // pdf / print: build the branded document, then print it from a hidden
127
- // iframe so the Save-as-PDF / print dialog opens without a stray tab.
123
+ // pdf: build the branded document, then print it from a hidden iframe so
124
+ // the Save-as-PDF dialog opens without a stray tab.
128
125
  const html = await buildHtmlDocument(messages, resolver, title, userLabel, options.brand);
129
126
  printViaHiddenIframe(html, `${stem}.html`);
130
127
  }
@@ -174,6 +174,24 @@ export class MastraPluginClient extends MastraClient {
174
174
  return payload.endpoints;
175
175
  }
176
176
 
177
+ /**
178
+ * Fetch the static default serving-endpoint id `agentId` falls back to
179
+ * when no model is pinned, from `GET ${basePath}/default-model`. Returns
180
+ * `null` when the agent resolves its model dynamically at call time (or the
181
+ * agent id is unknown) - there's nothing static to advertise. The picker
182
+ * uses this to name its "Server default" option. Defaults to the server's
183
+ * default agent when `agentId` is omitted.
184
+ */
185
+ async defaultModel(agentId?: string, signal?: AbortSignal): Promise<string | null> {
186
+ const query = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
187
+ const payload = await this.#getJson(
188
+ `${this.basePath}${routes.MASTRA_ROUTES.defaultModel}${query}`,
189
+ wire.DefaultModelResponseSchema,
190
+ signal,
191
+ );
192
+ return payload.model;
193
+ }
194
+
177
195
  /**
178
196
  * Fetch the curated starter questions for `agentId`'s Genie space
179
197
  * from `GET ${basePath}/suggestions`. Empty when the agent has no
@@ -533,6 +551,38 @@ export const useMastraModels = (
533
551
  return { models, loading, error };
534
552
  };
535
553
 
554
+ /**
555
+ * Fetch the static default serving-endpoint id the given agent (or the
556
+ * server's default agent) falls back to when no model is pinned, via
557
+ * `client.defaultModel(agentId)`. `null` while loading, when the agent's
558
+ * model is dynamic, or on error - the picker then shows a plain "Server
559
+ * default". Pass `enabled: false` to skip the fetch (e.g. picker hidden).
560
+ */
561
+ export const useMastraDefaultModel = (
562
+ agentId?: string,
563
+ enabled = true,
564
+ ): string | null => {
565
+ const client = useMastraClient();
566
+ const [model, setModel] = useState<string | null>(null);
567
+
568
+ useEffect(() => {
569
+ if (!enabled) return;
570
+ const controller = new AbortController();
571
+ client
572
+ .defaultModel(agentId, controller.signal)
573
+ .then((m) => {
574
+ if (!controller.signal.aborted) setModel(m);
575
+ })
576
+ .catch(() => {
577
+ // Non-critical: a missing default just yields a plain "Server default".
578
+ if (!controller.signal.aborted) setModel(null);
579
+ });
580
+ return () => controller.abort();
581
+ }, [client, agentId, enabled]);
582
+
583
+ return model;
584
+ };
585
+
536
586
  /**
537
587
  * Fetch the curated starter questions for an agent's Genie space via
538
588
  * `client.suggestions(agentId)`. These are the `sample_questions` an