@dbx-tools/ui-mastra 0.9.106 → 0.9.110
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/index.ts +3 -3
- package/package.json +6 -6
- package/src/react/chat-stream.ts +7 -2
- package/src/react/markdown.tsx +27 -0
- package/src/react/mastra-chat.tsx +10 -1
- package/src/react/tool-pill.tsx +117 -3
package/index.ts
CHANGED
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
// Hand edits are overwritten on the next watch; this file is read-only.
|
|
4
4
|
|
|
5
5
|
export const PACKAGE_IDENTIFIER = "@dbx-tools/ui-mastra";
|
|
6
|
-
export const PACKAGE_VERSION = "0.9.
|
|
6
|
+
export const PACKAGE_VERSION = "0.9.110";
|
|
7
7
|
export * as reactBubbles from "./src/react/bubbles.tsx";
|
|
8
8
|
export * as reactChatApprovals from "./src/react/chat-approvals.ts";
|
|
9
9
|
export * as reactChatComposer from "./src/react/chat-composer.tsx";
|
|
@@ -52,7 +52,7 @@ export type { DataRow } from "./src/react/data-grid.tsx";
|
|
|
52
52
|
export { MarkdownWithEmbeds } from "./src/react/embed-slots.tsx";
|
|
53
53
|
export { ExportMenu } from "./src/react/export-menu.tsx";
|
|
54
54
|
export { FeedbackControls } from "./src/react/feedback-controls.tsx";
|
|
55
|
-
export { AssistantMarkdown, ToolMarkdown, SqlBlock, JsonBlock } from "./src/react/markdown.tsx";
|
|
55
|
+
export { AssistantMarkdown, ToolMarkdown, SqlBlock, JsonBlock, SourceBlock } from "./src/react/markdown.tsx";
|
|
56
56
|
export type { MastraAssistantMode, MastraAssistantSide, MastraAssistantLauncherPosition, MastraAssistantResizeOptions, MastraAssistantLauncherOptions, MastraAssistantController, MastraAssistantProps } from "./src/react/mastra-assistant.tsx";
|
|
57
57
|
export { useMastraChat, MastraChat } from "./src/react/mastra-chat.tsx";
|
|
58
58
|
export type { UseMastraChatOptions, MastraChatProps } from "./src/react/mastra-chat.tsx";
|
|
@@ -64,7 +64,7 @@ export type { ThreadSidebarProps } from "./src/react/thread-sidebar.tsx";
|
|
|
64
64
|
export { ThreadActions, ThreadTabs } from "./src/react/thread-tabs.tsx";
|
|
65
65
|
export type { ThreadTabsProps, ThreadActionsProps } from "./src/react/thread-tabs.tsx";
|
|
66
66
|
export { humanizeToolName, ToolSessionPill } from "./src/react/tool-pill.tsx";
|
|
67
|
-
export type { WebSearchProgressGroup } from "./src/react/tool-pill.tsx";
|
|
67
|
+
export type { ToolInputPresentation, WebSearchProgressGroup } from "./src/react/tool-pill.tsx";
|
|
68
68
|
export type { ChatStatus, ToolEvent, ToolProgress, ChatModelOption, QueuedSteer, FeedbackValue, FeedbackSubmission, MessageFeedback, ThreadSummary, ThreadPlacement, ChatViewProps, ApprovalDecision, PendingApproval } from "./src/react/types.ts";
|
|
69
69
|
export { LIGHT_CHART_CHROME } from "./src/support/chart-theme.ts";
|
|
70
70
|
export type { ChartChrome } from "./src/support/chart-theme.ts";
|
package/package.json
CHANGED
|
@@ -29,11 +29,11 @@
|
|
|
29
29
|
"react-dom": "~19.2.4"
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
33
|
-
"@dbx-tools/shared-genie": "^0.9.
|
|
34
|
-
"@dbx-tools/shared-mastra": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
36
|
-
"@dbx-tools/ui": "^0.9.
|
|
32
|
+
"@dbx-tools/shared-core": "^0.9.110",
|
|
33
|
+
"@dbx-tools/shared-genie": "^0.9.110",
|
|
34
|
+
"@dbx-tools/shared-mastra": "^0.9.110",
|
|
35
|
+
"@dbx-tools/shared-model": "^0.9.110",
|
|
36
|
+
"@dbx-tools/ui": "^0.9.110",
|
|
37
37
|
"@mastra/client-js": "1.50.0",
|
|
38
38
|
"@mastra/core": "1.71.0",
|
|
39
39
|
"@tanstack/react-table": "^8.21.3",
|
|
@@ -50,7 +50,7 @@
|
|
|
50
50
|
"publishConfig": {
|
|
51
51
|
"access": "public"
|
|
52
52
|
},
|
|
53
|
-
"version": "0.9.
|
|
53
|
+
"version": "0.9.110",
|
|
54
54
|
"dbxToolsConfig": {
|
|
55
55
|
"tags": [
|
|
56
56
|
"ui"
|
package/src/react/chat-stream.ts
CHANGED
|
@@ -23,6 +23,7 @@ class StreamAborted extends Error {}
|
|
|
23
23
|
/** Stop reading a background run once the AI SDK has delivered its terminal chunk. */
|
|
24
24
|
export function closeOnTerminalChunk(
|
|
25
25
|
stream: ReadableStream<UIMessageChunk>,
|
|
26
|
+
onTerminal?: () => void,
|
|
26
27
|
): ReadableStream<UIMessageChunk> {
|
|
27
28
|
let reader: ReadableStreamDefaultReader<UIMessageChunk> | undefined;
|
|
28
29
|
let cancelled = false;
|
|
@@ -38,6 +39,7 @@ export function closeOnTerminalChunk(
|
|
|
38
39
|
}
|
|
39
40
|
controller.enqueue(value);
|
|
40
41
|
if (value.type === "finish" || value.type === "abort") {
|
|
42
|
+
onTerminal?.();
|
|
41
43
|
controller.close();
|
|
42
44
|
await reader.cancel("terminal AI SDK chunk received").catch(() => undefined);
|
|
43
45
|
return;
|
|
@@ -101,12 +103,15 @@ export function useChatStream({ getSession, updateSession, writeMessages }: UseC
|
|
|
101
103
|
}
|
|
102
104
|
|
|
103
105
|
try {
|
|
106
|
+
let terminalReceived = false;
|
|
104
107
|
const existing = getSession(threadId).messages.find(
|
|
105
108
|
(message) => message.id === assistantId,
|
|
106
109
|
);
|
|
107
110
|
for await (const message of readUIMessageStream({
|
|
108
111
|
...(existing ? { message: existing } : {}),
|
|
109
|
-
stream: closeOnTerminalChunk(stream.stream)
|
|
112
|
+
stream: closeOnTerminalChunk(stream.stream, () => {
|
|
113
|
+
terminalReceived = true;
|
|
114
|
+
}),
|
|
110
115
|
terminateOnError: true,
|
|
111
116
|
})) {
|
|
112
117
|
if (signal.aborted) throw new StreamAborted();
|
|
@@ -115,7 +120,7 @@ export function useChatStream({ getSession, updateSession, writeMessages }: UseC
|
|
|
115
120
|
updateSession(threadId, (current) => ({
|
|
116
121
|
...current,
|
|
117
122
|
runId: runIdRef.current,
|
|
118
|
-
status: "streaming",
|
|
123
|
+
status: terminalReceived ? "ready" : "streaming",
|
|
119
124
|
}));
|
|
120
125
|
}
|
|
121
126
|
} catch (error) {
|
package/src/react/markdown.tsx
CHANGED
|
@@ -418,3 +418,30 @@ export const JsonBlock = ({ json, className }: { json: string; className?: strin
|
|
|
418
418
|
</pre>
|
|
419
419
|
);
|
|
420
420
|
};
|
|
421
|
+
|
|
422
|
+
/** Compact syntax-highlighted source block for tool request previews. */
|
|
423
|
+
export const SourceBlock = ({
|
|
424
|
+
source,
|
|
425
|
+
language,
|
|
426
|
+
className,
|
|
427
|
+
}: {
|
|
428
|
+
source: string;
|
|
429
|
+
language: string;
|
|
430
|
+
className?: string;
|
|
431
|
+
}) => {
|
|
432
|
+
const html = useHighlightedHtml(source, language);
|
|
433
|
+
return (
|
|
434
|
+
<pre
|
|
435
|
+
className={cn(
|
|
436
|
+
"max-w-full overflow-auto whitespace-pre font-mono text-xs leading-snug",
|
|
437
|
+
className,
|
|
438
|
+
)}
|
|
439
|
+
>
|
|
440
|
+
{html === null ? (
|
|
441
|
+
<code>{source}</code>
|
|
442
|
+
) : (
|
|
443
|
+
<code data-dbx-highlighted-code dangerouslySetInnerHTML={{ __html: html }} />
|
|
444
|
+
)}
|
|
445
|
+
</pre>
|
|
446
|
+
);
|
|
447
|
+
};
|
|
@@ -202,6 +202,12 @@ export interface UseMastraChatOptions<
|
|
|
202
202
|
* inlined into the export so it renders reliably offline.
|
|
203
203
|
*/
|
|
204
204
|
enableExport?: boolean;
|
|
205
|
+
/**
|
|
206
|
+
* Show the destructive Clear conversation action. Off by default because
|
|
207
|
+
* thread management already provides new/delete controls and most chats do
|
|
208
|
+
* not need a second way to erase the active conversation.
|
|
209
|
+
*/
|
|
210
|
+
enableClear?: boolean;
|
|
205
211
|
/**
|
|
206
212
|
* Surface per-message feedback controls (thumbs up/down + a comment
|
|
207
213
|
* modal) that log to MLflow as trace assessments.
|
|
@@ -277,6 +283,9 @@ export const useMastraChat = <TValues extends Record<string, unknown> = Record<s
|
|
|
277
283
|
const enableThreads = threadPlacement !== "disabled";
|
|
278
284
|
// Export is opt-in (default off): the host turns it on explicitly.
|
|
279
285
|
const enableExport = options.enableExport === true;
|
|
286
|
+
// Clearing is destructive and redundant with managed thread deletion, so
|
|
287
|
+
// the drop-in exposes it only when a host explicitly opts in.
|
|
288
|
+
const enableClear = options.enableClear === true;
|
|
280
289
|
// Feedback defaults to automatic server capability detection. An explicit
|
|
281
290
|
// false hides it; true keeps it requested but cannot bypass the server gate.
|
|
282
291
|
const enableFeedback = options.enableFeedback !== false;
|
|
@@ -1080,7 +1089,7 @@ export const useMastraChat = <TValues extends Record<string, unknown> = Record<s
|
|
|
1080
1089
|
isLoadingMore,
|
|
1081
1090
|
hasMore: activeSession.hasMoreHistory,
|
|
1082
1091
|
isLoadingHistory,
|
|
1083
|
-
onClear: handleClear,
|
|
1092
|
+
...(enableClear ? { onClear: handleClear } : {}),
|
|
1084
1093
|
threadPlacement,
|
|
1085
1094
|
// Conversation management: hand ChatView the thread list + handlers
|
|
1086
1095
|
// only when enabled, so the sidebar stays hidden for the classic
|
package/src/react/tool-pill.tsx
CHANGED
|
@@ -8,7 +8,7 @@ import {
|
|
|
8
8
|
cn,
|
|
9
9
|
} from "@dbx-tools/ui/react";
|
|
10
10
|
import { CheckIcon, ChevronDownIcon, XIcon } from "lucide-react";
|
|
11
|
-
import { JsonBlock, SqlBlock, ToolMarkdown } from "./markdown.tsx";
|
|
11
|
+
import { JsonBlock, SourceBlock, SqlBlock, ToolMarkdown } from "./markdown.tsx";
|
|
12
12
|
import type { ToolEvent, ToolProgress } from "./types.ts";
|
|
13
13
|
|
|
14
14
|
// Consolidated tool-session pill and its Genie progress detail view:
|
|
@@ -398,6 +398,108 @@ const RawToolPayload = ({ label, value }: { label: "Request" | "Response"; value
|
|
|
398
398
|
</Collapsible>
|
|
399
399
|
);
|
|
400
400
|
|
|
401
|
+
export type ToolInputPresentation = {
|
|
402
|
+
label: "Code" | "Command" | "Output";
|
|
403
|
+
language: "typescript" | "javascript" | "python" | "sql" | "json" | "bash" | "text";
|
|
404
|
+
source: string;
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
const inputRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
408
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
409
|
+
? (value as Record<string, unknown>)
|
|
410
|
+
: undefined;
|
|
411
|
+
|
|
412
|
+
/** Detect the most useful syntax grammar for a code-like tool request. */
|
|
413
|
+
export function detectToolSourceLanguage(
|
|
414
|
+
source: string,
|
|
415
|
+
fallback: ToolInputPresentation["language"],
|
|
416
|
+
): ToolInputPresentation["language"] {
|
|
417
|
+
const trimmed = source.trim();
|
|
418
|
+
if (/^(?:select|with|insert|update|delete|merge|create|alter)\b/i.test(trimmed)) return "sql";
|
|
419
|
+
if (/^(?:from\s+\S+\s+import|import\s+\S+|def\s+\w+|class\s+\w+):?/m.test(trimmed)) {
|
|
420
|
+
return "python";
|
|
421
|
+
}
|
|
422
|
+
if (/^(?:const|let|var|interface|type|export|import)\b/m.test(trimmed)) return "typescript";
|
|
423
|
+
if (/^(?:#!.*\b(?:ba|z|k)?sh\b|(?:cd|ls|cat|grep|find|bun|npm|pnpm|yarn)\s)/m.test(trimmed)) {
|
|
424
|
+
return "bash";
|
|
425
|
+
}
|
|
426
|
+
if ((trimmed.startsWith("{") || trimmed.startsWith("[")) && trimmed.length > 1) {
|
|
427
|
+
try {
|
|
428
|
+
JSON.parse(trimmed);
|
|
429
|
+
return "json";
|
|
430
|
+
} catch {
|
|
431
|
+
// Keep the tool-specific fallback for incomplete source.
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
return fallback;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
/** Extract a compact code or command preview from a tool request payload. */
|
|
438
|
+
export function toolInputPresentation(
|
|
439
|
+
toolName: string,
|
|
440
|
+
input: unknown,
|
|
441
|
+
): ToolInputPresentation | undefined {
|
|
442
|
+
const record = inputRecord(input);
|
|
443
|
+
if (!record) return undefined;
|
|
444
|
+
if (typeof record.command === "string" && toolName.endsWith("execute_command")) {
|
|
445
|
+
return {
|
|
446
|
+
label: "Command",
|
|
447
|
+
language: detectToolSourceLanguage(record.command, "bash"),
|
|
448
|
+
source: record.command,
|
|
449
|
+
};
|
|
450
|
+
}
|
|
451
|
+
if (typeof record.code === "string") {
|
|
452
|
+
return {
|
|
453
|
+
label: "Code",
|
|
454
|
+
language: detectToolSourceLanguage(record.code, "typescript"),
|
|
455
|
+
source: record.code,
|
|
456
|
+
};
|
|
457
|
+
}
|
|
458
|
+
return undefined;
|
|
459
|
+
}
|
|
460
|
+
|
|
461
|
+
/** Extract Code Mode's completed result into its own compact output panel. */
|
|
462
|
+
export function toolOutputPresentation(
|
|
463
|
+
toolName: string,
|
|
464
|
+
output: unknown,
|
|
465
|
+
): ToolInputPresentation | undefined {
|
|
466
|
+
if (toolName !== "execute_typescript") return undefined;
|
|
467
|
+
const record = inputRecord(output);
|
|
468
|
+
if (!record || record.success !== true || !("result" in record)) return undefined;
|
|
469
|
+
const result = record.result;
|
|
470
|
+
const source = typeof result === "string" ? result : formatRawToolPayload(result);
|
|
471
|
+
return {
|
|
472
|
+
label: "Output",
|
|
473
|
+
language: typeof result === "string" ? detectToolSourceLanguage(result, "text") : "json",
|
|
474
|
+
source,
|
|
475
|
+
};
|
|
476
|
+
}
|
|
477
|
+
|
|
478
|
+
const ToolInputSource = ({ presentation }: { presentation: ToolInputPresentation }) => {
|
|
479
|
+
const lineCount = presentation.source.split("\n").length;
|
|
480
|
+
return (
|
|
481
|
+
<Collapsible className="rounded border border-border/60 bg-background/40">
|
|
482
|
+
<CollapsibleTrigger className="group flex w-full items-center gap-1.5 px-2 py-1 text-left text-xs text-muted-foreground hover:text-foreground">
|
|
483
|
+
<ChevronDownIcon className="size-3 shrink-0 transition-transform group-data-[state=closed]:-rotate-90" />
|
|
484
|
+
<span className="font-medium text-foreground/80">{presentation.label}</span>
|
|
485
|
+
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase leading-none tracking-wide">
|
|
486
|
+
{presentation.language}
|
|
487
|
+
</span>
|
|
488
|
+
<span className="ml-auto text-[10px] tabular-nums">
|
|
489
|
+
{lineCount} {lineCount === 1 ? "line" : "lines"}
|
|
490
|
+
</span>
|
|
491
|
+
</CollapsibleTrigger>
|
|
492
|
+
<CollapsibleContent>
|
|
493
|
+
<SourceBlock
|
|
494
|
+
source={presentation.source}
|
|
495
|
+
language={presentation.language}
|
|
496
|
+
className="max-h-64 border-t border-border/50 px-2 py-1.5 text-foreground"
|
|
497
|
+
/>
|
|
498
|
+
</CollapsibleContent>
|
|
499
|
+
</Collapsible>
|
|
500
|
+
);
|
|
501
|
+
};
|
|
502
|
+
|
|
401
503
|
type ToolStatusProgress = Extract<ToolProgress, { type: "tool_status" }>;
|
|
402
504
|
|
|
403
505
|
export type WebSearchProgressGroup = {
|
|
@@ -508,6 +610,10 @@ const ToolCallRow = ({ event }: { event: ToolEvent }) => {
|
|
|
508
610
|
const question = askGenieQuestion(event);
|
|
509
611
|
const summary = summarizeProgress(event.progress ?? []);
|
|
510
612
|
const expandable = hasExpandableDetails(event);
|
|
613
|
+
const inputPresentation =
|
|
614
|
+
"input" in event ? toolInputPresentation(event.toolName, event.input) : undefined;
|
|
615
|
+
const outputPresentation =
|
|
616
|
+
"output" in event ? toolOutputPresentation(event.toolName, event.output) : undefined;
|
|
511
617
|
// Inner rows defer to the live wire status when running (e.g.
|
|
512
618
|
// "Executing query") so users tracking the open pill see the
|
|
513
619
|
// backend's freshest state - not just the static
|
|
@@ -554,10 +660,18 @@ const ToolCallRow = ({ event }: { event: ToolEvent }) => {
|
|
|
554
660
|
</CollapsibleTrigger>
|
|
555
661
|
<CollapsibleContent>
|
|
556
662
|
<div className="flex flex-col gap-2 px-2 pb-2">
|
|
557
|
-
{
|
|
663
|
+
{inputPresentation ? (
|
|
664
|
+
<ToolInputSource presentation={inputPresentation} />
|
|
665
|
+
) : "input" in event ? (
|
|
666
|
+
<RawToolPayload label="Request" value={event.input} />
|
|
667
|
+
) : null}
|
|
558
668
|
<WebSearchProgressDetails event={event} />
|
|
559
669
|
<ToolProgressDetails summary={summary} omitQuestion />
|
|
560
|
-
{
|
|
670
|
+
{outputPresentation ? (
|
|
671
|
+
<ToolInputSource presentation={outputPresentation} />
|
|
672
|
+
) : "output" in event ? (
|
|
673
|
+
<RawToolPayload label="Response" value={event.output} />
|
|
674
|
+
) : null}
|
|
561
675
|
</div>
|
|
562
676
|
</CollapsibleContent>
|
|
563
677
|
</Collapsible>
|