@dbx-tools/ui-mastra 0.3.6 → 0.3.7
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 +33 -31
- package/src/react/mastra-chat.tsx +6 -7
- package/src/support/export.ts +32 -9
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/shared-mastra": "0.3.
|
|
32
|
-
"@dbx-tools/ui-appkit": "0.3.
|
|
33
|
-
"@dbx-tools/shared-model": "0.3.
|
|
34
|
-
"@dbx-tools/ui-branding": "0.3.
|
|
29
|
+
"@dbx-tools/shared-core": "0.3.7",
|
|
30
|
+
"@dbx-tools/shared-genie": "0.3.7",
|
|
31
|
+
"@dbx-tools/shared-mastra": "0.3.7",
|
|
32
|
+
"@dbx-tools/ui-appkit": "0.3.7",
|
|
33
|
+
"@dbx-tools/shared-model": "0.3.7",
|
|
34
|
+
"@dbx-tools/ui-branding": "0.3.7"
|
|
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.7",
|
|
42
42
|
"types": "index.ts",
|
|
43
43
|
"type": "module",
|
|
44
44
|
"exports": {
|
package/src/react/chat-view.tsx
CHANGED
|
@@ -349,10 +349,14 @@ export const ChatView = ({
|
|
|
349
349
|
if (isMobile) setMobileDrawerOpen((open) => !open);
|
|
350
350
|
else toggleDesktopSidebar();
|
|
351
351
|
};
|
|
352
|
-
//
|
|
353
|
-
// clear
|
|
354
|
-
// user is typing.
|
|
355
|
-
|
|
352
|
+
// The top bar carries only the sidebar toggle; the model picker, export, and
|
|
353
|
+
// clear controls live in a toolbar row below the composer, closer to where
|
|
354
|
+
// the user is typing. The toggle is a mobile hamburger, or a desktop "show"
|
|
355
|
+
// affordance while the inline sidebar is collapsed - so the bar renders only
|
|
356
|
+
// when that toggle would actually be visible (an open desktop sidebar has its
|
|
357
|
+
// own hide button, leaving nothing for the bar to hold).
|
|
358
|
+
const showSidebarToggle = showSidebar && (isMobile || !desktopSidebarOpen);
|
|
359
|
+
const showHeader = showSidebarToggle;
|
|
356
360
|
const showComposerToolbar = showModelDisplay || showExport || showClear;
|
|
357
361
|
|
|
358
362
|
// Clear confirmation is an AppKit `AlertDialog` (a real modal), plus an
|
|
@@ -446,33 +450,31 @@ export const ChatView = ({
|
|
|
446
450
|
))}
|
|
447
451
|
<div className="flex h-full min-w-0 flex-1 flex-col">
|
|
448
452
|
{showHeader && (
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
)}
|
|
475
|
-
</div>
|
|
453
|
+
/*
|
|
454
|
+
* Slim top bar holding the sidebar toggle. On mobile the toggle is
|
|
455
|
+
* a persistent hamburger (the overlay drawer has no always-visible
|
|
456
|
+
* hide button); on desktop it's a "show" affordance rendered only
|
|
457
|
+
* while the inline sidebar is collapsed (an open sidebar has its
|
|
458
|
+
* own hide button). `showHeader` already tracks that visibility, so
|
|
459
|
+
* the bar never renders empty.
|
|
460
|
+
*/
|
|
461
|
+
<div className="mx-auto flex w-full max-w-4xl items-center gap-2 px-3 pb-2 pt-1 text-xs text-muted-foreground md:gap-3 md:px-6">
|
|
462
|
+
<Tooltip>
|
|
463
|
+
<TooltipTrigger asChild>
|
|
464
|
+
<Button
|
|
465
|
+
type="button"
|
|
466
|
+
variant="ghost"
|
|
467
|
+
size="icon-sm"
|
|
468
|
+
onClick={toggleSidebar}
|
|
469
|
+
aria-label={sidebarOpen ? "Hide conversations" : "Show conversations"}
|
|
470
|
+
>
|
|
471
|
+
<PanelLeftIcon className="size-4" />
|
|
472
|
+
</Button>
|
|
473
|
+
</TooltipTrigger>
|
|
474
|
+
<TooltipContent>
|
|
475
|
+
{sidebarOpen ? "Hide conversations" : "Show conversations"}
|
|
476
|
+
</TooltipContent>
|
|
477
|
+
</Tooltip>
|
|
476
478
|
</div>
|
|
477
479
|
)}
|
|
478
480
|
<div className="relative flex flex-1 flex-col overflow-hidden">
|
|
@@ -46,10 +46,9 @@ import type {
|
|
|
46
46
|
// `{ runId, payload: { toolCallId, toolName, args } }`. We surface that
|
|
47
47
|
// as an out-of-band entry in `pendingApprovalsByMessage` and wire
|
|
48
48
|
// `onResolveToolApproval` to {@link MastraPluginClient.approveToolCallStream}
|
|
49
|
-
// / `declineToolCallStream
|
|
50
|
-
// `@mastra/client-js` bug on resumed approval streams
|
|
51
|
-
//
|
|
52
|
-
// chunk handler.
|
|
49
|
+
// / `declineToolCallStream` - both of which read SSE directly (avoiding a
|
|
50
|
+
// stock `@mastra/client-js` bug on resumed approval streams) and return a
|
|
51
|
+
// fresh stream Response we run through the same chunk handler.
|
|
53
52
|
//
|
|
54
53
|
// On mount the transcript hydrates with the most recent page of thread
|
|
55
54
|
// history from the Mastra plugin's `/history` endpoint; scrolling near
|
|
@@ -1256,9 +1255,9 @@ export const useMastraChat = (
|
|
|
1256
1255
|
[activeKey, mastraClient, updateSession],
|
|
1257
1256
|
);
|
|
1258
1257
|
|
|
1259
|
-
// Merge optimistic rows
|
|
1260
|
-
// the server
|
|
1261
|
-
//
|
|
1258
|
+
// Merge optimistic rows over the server list, newest first, dropping any
|
|
1259
|
+
// optimistic entry the server already returns so a thread is never listed
|
|
1260
|
+
// twice.
|
|
1262
1261
|
const sidebarThreads = useMemo<ThreadSummary[]>(() => {
|
|
1263
1262
|
// Title overlay precedence per row: a manual rename always wins;
|
|
1264
1263
|
// otherwise a provisional first-message title fills an untitled row
|
package/src/support/export.ts
CHANGED
|
@@ -162,13 +162,20 @@ function printViaHiddenIframe(html: string, downloadName: string): void {
|
|
|
162
162
|
return;
|
|
163
163
|
}
|
|
164
164
|
// Settle so charts / fonts lay out, then print. `afterprint` removes the
|
|
165
|
-
// iframe once the dialog closes
|
|
166
|
-
// fire it)
|
|
165
|
+
// iframe once the dialog closes; a 60s timer backstops browsers that don't
|
|
166
|
+
// fire it. The backstop is armed BEFORE `print()` so a throw (sandboxed /
|
|
167
|
+
// policy-restricted frames can reject `print()`) still cleans up, and the
|
|
168
|
+
// export falls back to a file download rather than silently vanishing.
|
|
167
169
|
win.addEventListener("afterprint", cleanup);
|
|
168
170
|
win.setTimeout(() => {
|
|
169
|
-
win.focus();
|
|
170
|
-
win.print();
|
|
171
171
|
window.setTimeout(cleanup, 60000);
|
|
172
|
+
try {
|
|
173
|
+
win.focus();
|
|
174
|
+
win.print();
|
|
175
|
+
} catch {
|
|
176
|
+
cleanup();
|
|
177
|
+
downloadTextFile(downloadName, html, "text/html;charset=utf-8");
|
|
178
|
+
}
|
|
172
179
|
}, PRINT_SETTLE_MS);
|
|
173
180
|
};
|
|
174
181
|
|
|
@@ -478,6 +485,20 @@ function downloadTextFile(filename: string, content: string, mime: string): void
|
|
|
478
485
|
window.setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
479
486
|
}
|
|
480
487
|
|
|
488
|
+
/**
|
|
489
|
+
* A brand color / font value safe to interpolate into a `<style>` block.
|
|
490
|
+
* Brand colors are hex-validated upstream, but the font stack is only checked
|
|
491
|
+
* for non-blankness, so strip the characters that could close a declaration or
|
|
492
|
+
* rule (`{ } ; < > \` plus HTML-comment openers) to prevent CSS injection into
|
|
493
|
+
* the export document. Falls back to `fallback` when the value is blank.
|
|
494
|
+
*/
|
|
495
|
+
function cssValue(value: string | undefined, fallback: string): string {
|
|
496
|
+
const trimmed = value?.trim();
|
|
497
|
+
if (!trimmed) return fallback;
|
|
498
|
+
const safe = trimmed.replace(/[{}<>;\\]/g, "");
|
|
499
|
+
return safe || fallback;
|
|
500
|
+
}
|
|
501
|
+
|
|
481
502
|
/**
|
|
482
503
|
* Build the inline stylesheet for the exported document, folding in optional
|
|
483
504
|
* brand color / font overrides (falling back to the neutral slate/blue theme
|
|
@@ -497,11 +518,13 @@ function downloadTextFile(filename: string, content: string, mime: string): void
|
|
|
497
518
|
* out; the visible page margin is re-supplied as body padding.
|
|
498
519
|
*/
|
|
499
520
|
function buildDocumentCss(brand?: ExportBrand): string {
|
|
500
|
-
const fg = brand?.foreground
|
|
501
|
-
const primary = brand?.primary
|
|
502
|
-
const link = brand?.accent
|
|
503
|
-
const font =
|
|
504
|
-
brand?.fontSans
|
|
521
|
+
const fg = cssValue(brand?.foreground, "#0f172a");
|
|
522
|
+
const primary = cssValue(brand?.primary, "#0f172a");
|
|
523
|
+
const link = cssValue(brand?.accent, "#2563eb");
|
|
524
|
+
const font = cssValue(
|
|
525
|
+
brand?.fontSans,
|
|
526
|
+
'system-ui, -apple-system, "Segoe UI", Roboto, sans-serif',
|
|
527
|
+
);
|
|
505
528
|
return `
|
|
506
529
|
:root { color-scheme: light; }
|
|
507
530
|
* { box-sizing: border-box; }
|