@dbx-tools/ui-mastra 0.3.2 → 0.3.4
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 +7 -7
- package/src/react/chat-view.tsx +86 -69
- package/src/support/mastra-client.ts +8 -7
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.3.
|
|
30
|
-
"@dbx-tools/shared-genie": "0.3.
|
|
31
|
-
"@dbx-tools/
|
|
32
|
-
"@dbx-tools/
|
|
33
|
-
"@dbx-tools/
|
|
34
|
-
"@dbx-tools/
|
|
29
|
+
"@dbx-tools/shared-core": "0.3.4",
|
|
30
|
+
"@dbx-tools/shared-genie": "0.3.4",
|
|
31
|
+
"@dbx-tools/shared-mastra": "0.3.4",
|
|
32
|
+
"@dbx-tools/ui-appkit": "0.3.4",
|
|
33
|
+
"@dbx-tools/ui-branding": "0.3.4",
|
|
34
|
+
"@dbx-tools/shared-model": "0.3.4"
|
|
35
35
|
},
|
|
36
36
|
"main": "index.ts",
|
|
37
37
|
"license": "UNLICENSED",
|
|
38
38
|
"publishConfig": {
|
|
39
39
|
"access": "public"
|
|
40
40
|
},
|
|
41
|
-
"version": "0.3.
|
|
41
|
+
"version": "0.3.4",
|
|
42
42
|
"types": "index.ts",
|
|
43
43
|
"type": "module",
|
|
44
44
|
"exports": {
|
package/src/react/chat-view.tsx
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
import {
|
|
2
2
|
Alert,
|
|
3
3
|
AlertDescription,
|
|
4
|
+
AlertDialog,
|
|
5
|
+
AlertDialogAction,
|
|
6
|
+
AlertDialogCancel,
|
|
7
|
+
AlertDialogContent,
|
|
8
|
+
AlertDialogDescription,
|
|
9
|
+
AlertDialogFooter,
|
|
10
|
+
AlertDialogHeader,
|
|
11
|
+
AlertDialogTitle,
|
|
4
12
|
AlertTitle,
|
|
5
13
|
Button,
|
|
6
14
|
Empty,
|
|
@@ -286,22 +294,27 @@ export const ChatView = ({
|
|
|
286
294
|
// otherwise as static text.
|
|
287
295
|
const showModelDisplay = Boolean(onModelChange);
|
|
288
296
|
const modelChangeable = Boolean(models && models.length > 0);
|
|
289
|
-
// Human-readable name for
|
|
290
|
-
// `displayName
|
|
297
|
+
// Human-readable name for a pinned endpoint id, using the catalogue's
|
|
298
|
+
// `displayName`. Returns undefined until the catalogue has an entry for it,
|
|
299
|
+
// so we never fall back to the raw id (no raw-name flash on load).
|
|
291
300
|
const modelLabel = (name?: string): string | undefined => {
|
|
292
301
|
if (!name) return undefined;
|
|
293
|
-
return models?.find((m) => m.name === name)?.displayName
|
|
302
|
+
return models?.find((m) => m.name === name)?.displayName;
|
|
294
303
|
};
|
|
295
|
-
// The
|
|
296
|
-
//
|
|
297
|
-
//
|
|
298
|
-
const defaultOptionLabel =
|
|
299
|
-
|
|
300
|
-
|
|
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
|
+
// The default option shows the server's fallback model by its already-
|
|
305
|
+
// humanized name (`defaultModelName` is the server's `displayName`), else a
|
|
306
|
+
// neutral "Default". No "server default" phrasing, no raw id.
|
|
307
|
+
const defaultOptionLabel = defaultModelName || "Default";
|
|
308
|
+
// Label the current model by its human-readable name when a model is pinned
|
|
309
|
+
// and the catalogue has resolved it; else the default-option label. Never
|
|
310
|
+
// shows a raw endpoint id.
|
|
304
311
|
const currentModelLabel = modelLabel(model) || defaultOptionLabel;
|
|
312
|
+
// Picker entries sorted by their human-readable label (case-insensitive).
|
|
313
|
+
const sortedModels = [...(models ?? [])].sort((a, b) =>
|
|
314
|
+
(a.displayName || a.name).localeCompare(b.displayName || b.name, undefined, {
|
|
315
|
+
sensitivity: "base",
|
|
316
|
+
}),
|
|
317
|
+
);
|
|
305
318
|
const showClear = Boolean(onClear);
|
|
306
319
|
const showExport = Boolean(onExportConversation);
|
|
307
320
|
// The conversation sidebar turns on once the host wires both the
|
|
@@ -336,29 +349,26 @@ export const ChatView = ({
|
|
|
336
349
|
if (isMobile) setMobileDrawerOpen((open) => !open);
|
|
337
350
|
else toggleDesktopSidebar();
|
|
338
351
|
};
|
|
339
|
-
// Top bar carries only the sidebar toggle
|
|
340
|
-
//
|
|
341
|
-
//
|
|
342
|
-
const showHeader =
|
|
343
|
-
const showComposerToolbar = showModelDisplay || showExport;
|
|
352
|
+
// Top bar carries only the sidebar toggle now; the model picker, export, and
|
|
353
|
+
// clear all live in a toolbar row below the composer, closer to where the
|
|
354
|
+
// user is typing.
|
|
355
|
+
const showHeader = showSidebar;
|
|
356
|
+
const showComposerToolbar = showModelDisplay || showExport || showClear;
|
|
344
357
|
|
|
345
|
-
//
|
|
346
|
-
//
|
|
347
|
-
//
|
|
348
|
-
|
|
349
|
-
const [
|
|
358
|
+
// Clear confirmation is an AppKit `AlertDialog` (a real modal), plus an
|
|
359
|
+
// in-flight flag so the DELETE can't be double-fired. `clearing` disables
|
|
360
|
+
// the confirm action while `onClear` runs; the dialog closes on settle.
|
|
361
|
+
const [clearOpen, setClearOpen] = useState(false);
|
|
362
|
+
const [clearing, setClearing] = useState(false);
|
|
350
363
|
|
|
351
|
-
const
|
|
352
|
-
if (
|
|
353
|
-
|
|
354
|
-
setClearState("confirm");
|
|
355
|
-
return;
|
|
356
|
-
}
|
|
357
|
-
setClearState("clearing");
|
|
364
|
+
const handleClearConfirm = async () => {
|
|
365
|
+
if (clearing || !onClear) return;
|
|
366
|
+
setClearing(true);
|
|
358
367
|
try {
|
|
359
368
|
await onClear();
|
|
369
|
+
setClearOpen(false);
|
|
360
370
|
} finally {
|
|
361
|
-
|
|
371
|
+
setClearing(false);
|
|
362
372
|
}
|
|
363
373
|
};
|
|
364
374
|
|
|
@@ -463,44 +473,6 @@ export const ChatView = ({
|
|
|
463
473
|
</Tooltip>
|
|
464
474
|
)}
|
|
465
475
|
</div>
|
|
466
|
-
<div className="flex min-w-0 items-center justify-end gap-2 md:gap-3">
|
|
467
|
-
{showClear && (
|
|
468
|
-
<Tooltip>
|
|
469
|
-
<TooltipTrigger asChild>
|
|
470
|
-
<Button
|
|
471
|
-
type="button"
|
|
472
|
-
variant={clearState === "confirm" ? "destructive" : "outline"}
|
|
473
|
-
size="sm"
|
|
474
|
-
onClick={handleClearClick}
|
|
475
|
-
onBlur={() =>
|
|
476
|
-
// Drop the confirm latch when focus leaves so a
|
|
477
|
-
// half-armed button doesn't sit destructive-red
|
|
478
|
-
// forever after the user clicks away.
|
|
479
|
-
setClearState((s) => (s === "confirm" ? "idle" : s))
|
|
480
|
-
}
|
|
481
|
-
disabled={clearState === "clearing"}
|
|
482
|
-
className="gap-1.5"
|
|
483
|
-
>
|
|
484
|
-
{clearState === "clearing" ? (
|
|
485
|
-
<Spinner className="size-3" />
|
|
486
|
-
) : (
|
|
487
|
-
<Trash2Icon className="size-3" />
|
|
488
|
-
)}
|
|
489
|
-
{clearState === "confirm"
|
|
490
|
-
? "Confirm clear"
|
|
491
|
-
: clearState === "clearing"
|
|
492
|
-
? "Clearing..."
|
|
493
|
-
: "Clear"}
|
|
494
|
-
</Button>
|
|
495
|
-
</TooltipTrigger>
|
|
496
|
-
<TooltipContent>
|
|
497
|
-
{clearState === "confirm"
|
|
498
|
-
? "Click again to confirm; wipes this conversation."
|
|
499
|
-
: "Clear chat history for this thread"}
|
|
500
|
-
</TooltipContent>
|
|
501
|
-
</Tooltip>
|
|
502
|
-
)}
|
|
503
|
-
</div>
|
|
504
476
|
</div>
|
|
505
477
|
)}
|
|
506
478
|
<div className="relative flex flex-1 flex-col overflow-hidden">
|
|
@@ -698,7 +670,7 @@ export const ChatView = ({
|
|
|
698
670
|
<SelectItem value={DEFAULT_MODEL_VALUE}>
|
|
699
671
|
{defaultOptionLabel}
|
|
700
672
|
</SelectItem>
|
|
701
|
-
{
|
|
673
|
+
{sortedModels.map((m) => (
|
|
702
674
|
<SelectItem key={m.name} value={m.name}>
|
|
703
675
|
{m.displayName || m.name}
|
|
704
676
|
</SelectItem>
|
|
@@ -716,6 +688,51 @@ export const ChatView = ({
|
|
|
716
688
|
tooltip="Export conversation"
|
|
717
689
|
/>
|
|
718
690
|
)}
|
|
691
|
+
{showClear && (
|
|
692
|
+
<>
|
|
693
|
+
<Tooltip>
|
|
694
|
+
<TooltipTrigger asChild>
|
|
695
|
+
<Button
|
|
696
|
+
type="button"
|
|
697
|
+
variant="outline"
|
|
698
|
+
size="sm"
|
|
699
|
+
onClick={() => setClearOpen(true)}
|
|
700
|
+
className="h-7 gap-1 rounded-full px-2.5 text-xs [&_svg]:size-3"
|
|
701
|
+
>
|
|
702
|
+
<Trash2Icon className="size-3" />
|
|
703
|
+
Clear
|
|
704
|
+
</Button>
|
|
705
|
+
</TooltipTrigger>
|
|
706
|
+
<TooltipContent>Clear chat history for this thread</TooltipContent>
|
|
707
|
+
</Tooltip>
|
|
708
|
+
<AlertDialog open={clearOpen} onOpenChange={setClearOpen}>
|
|
709
|
+
<AlertDialogContent>
|
|
710
|
+
<AlertDialogHeader>
|
|
711
|
+
<AlertDialogTitle>Clear this conversation?</AlertDialogTitle>
|
|
712
|
+
<AlertDialogDescription>
|
|
713
|
+
This permanently deletes the chat history for this thread. This
|
|
714
|
+
can't be undone.
|
|
715
|
+
</AlertDialogDescription>
|
|
716
|
+
</AlertDialogHeader>
|
|
717
|
+
<AlertDialogFooter>
|
|
718
|
+
<AlertDialogCancel disabled={clearing}>Cancel</AlertDialogCancel>
|
|
719
|
+
<AlertDialogAction
|
|
720
|
+
onClick={(e) => {
|
|
721
|
+
// Keep the dialog open while the DELETE runs; we
|
|
722
|
+
// close it ourselves once `onClear` settles.
|
|
723
|
+
e.preventDefault();
|
|
724
|
+
void handleClearConfirm();
|
|
725
|
+
}}
|
|
726
|
+
disabled={clearing}
|
|
727
|
+
>
|
|
728
|
+
{clearing ? <Spinner className="size-3" /> : null}
|
|
729
|
+
{clearing ? "Clearing..." : "Clear"}
|
|
730
|
+
</AlertDialogAction>
|
|
731
|
+
</AlertDialogFooter>
|
|
732
|
+
</AlertDialogContent>
|
|
733
|
+
</AlertDialog>
|
|
734
|
+
</>
|
|
735
|
+
)}
|
|
719
736
|
</div>
|
|
720
737
|
)}
|
|
721
738
|
</form>
|
|
@@ -175,12 +175,13 @@ export class MastraPluginClient extends MastraClient {
|
|
|
175
175
|
}
|
|
176
176
|
|
|
177
177
|
/**
|
|
178
|
-
* Fetch the static default
|
|
179
|
-
* when no model is pinned, from `GET ${basePath}/default-model`. Returns
|
|
180
|
-
*
|
|
181
|
-
* agent
|
|
182
|
-
*
|
|
183
|
-
* default
|
|
178
|
+
* Fetch the humanized name of the static default model `agentId` falls back
|
|
179
|
+
* to when no model is pinned, from `GET ${basePath}/default-model`. Returns
|
|
180
|
+
* the server-humanized `displayName` (never a raw endpoint id), or `null`
|
|
181
|
+
* when the agent resolves its model dynamically at call time (or the agent
|
|
182
|
+
* id is unknown) - there's nothing static to advertise. The picker uses this
|
|
183
|
+
* to label its default option on load, before the `/models` catalogue
|
|
184
|
+
* arrives. Defaults to the server's default agent when `agentId` is omitted.
|
|
184
185
|
*/
|
|
185
186
|
async defaultModel(agentId?: string, signal?: AbortSignal): Promise<string | null> {
|
|
186
187
|
const query = agentId ? `?agentId=${encodeURIComponent(agentId)}` : "";
|
|
@@ -189,7 +190,7 @@ export class MastraPluginClient extends MastraClient {
|
|
|
189
190
|
wire.DefaultModelResponseSchema,
|
|
190
191
|
signal,
|
|
191
192
|
);
|
|
192
|
-
return payload.model;
|
|
193
|
+
return payload.displayName ?? payload.model;
|
|
193
194
|
}
|
|
194
195
|
|
|
195
196
|
/**
|