@dbx-tools/ui-mastra 0.9.108 → 0.9.111
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 +151 -11
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.111";
|
|
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.111",
|
|
33
|
+
"@dbx-tools/shared-genie": "^0.9.111",
|
|
34
|
+
"@dbx-tools/shared-mastra": "^0.9.111",
|
|
35
|
+
"@dbx-tools/shared-model": "^0.9.111",
|
|
36
|
+
"@dbx-tools/ui": "^0.9.111",
|
|
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.111",
|
|
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:
|
|
@@ -22,6 +22,9 @@ import type { ToolEvent, ToolProgress } from "./types.ts";
|
|
|
22
22
|
* `Execute Command` rather than `Mastra Workspace Execute Command`.
|
|
23
23
|
*/
|
|
24
24
|
const MASTRA_WORKSPACE_TOOL_PREFIX = "mastra_workspace_";
|
|
25
|
+
const TOOL_LABELS: Readonly<Record<string, string>> = {
|
|
26
|
+
execute_typescript: "Run Workflow",
|
|
27
|
+
};
|
|
25
28
|
|
|
26
29
|
/**
|
|
27
30
|
* Turn a snake/camel tool id into a Title Cased label the user can
|
|
@@ -38,6 +41,8 @@ const MASTRA_WORKSPACE_TOOL_PREFIX = "mastra_workspace_";
|
|
|
38
41
|
* `myCoolTool` -> `My Cool Tool`
|
|
39
42
|
*/
|
|
40
43
|
export const humanizeToolName = (toolName: string): string => {
|
|
44
|
+
const explicitLabel = TOOL_LABELS[toolName];
|
|
45
|
+
if (explicitLabel) return explicitLabel;
|
|
41
46
|
const label = toolName.startsWith(MASTRA_WORKSPACE_TOOL_PREFIX)
|
|
42
47
|
? toolName.slice(MASTRA_WORKSPACE_TOOL_PREFIX.length)
|
|
43
48
|
: toolName;
|
|
@@ -398,6 +403,108 @@ const RawToolPayload = ({ label, value }: { label: "Request" | "Response"; value
|
|
|
398
403
|
</Collapsible>
|
|
399
404
|
);
|
|
400
405
|
|
|
406
|
+
export type ToolInputPresentation = {
|
|
407
|
+
label: "Code" | "Command" | "Output";
|
|
408
|
+
language: "typescript" | "javascript" | "python" | "sql" | "json" | "bash" | "text";
|
|
409
|
+
source: string;
|
|
410
|
+
};
|
|
411
|
+
|
|
412
|
+
const inputRecord = (value: unknown): Record<string, unknown> | undefined =>
|
|
413
|
+
typeof value === "object" && value !== null && !Array.isArray(value)
|
|
414
|
+
? (value as Record<string, unknown>)
|
|
415
|
+
: undefined;
|
|
416
|
+
|
|
417
|
+
/** Detect the most useful syntax grammar for a code-like tool request. */
|
|
418
|
+
export function detectToolSourceLanguage(
|
|
419
|
+
source: string,
|
|
420
|
+
fallback: ToolInputPresentation["language"],
|
|
421
|
+
): ToolInputPresentation["language"] {
|
|
422
|
+
const trimmed = source.trim();
|
|
423
|
+
if (/^(?:select|with|insert|update|delete|merge|create|alter)\b/i.test(trimmed)) return "sql";
|
|
424
|
+
if (/^(?:from\s+\S+\s+import|import\s+\S+|def\s+\w+|class\s+\w+):?/m.test(trimmed)) {
|
|
425
|
+
return "python";
|
|
426
|
+
}
|
|
427
|
+
if (/^(?:const|let|var|interface|type|export|import)\b/m.test(trimmed)) return "typescript";
|
|
428
|
+
if (/^(?:#!.*\b(?:ba|z|k)?sh\b|(?:cd|ls|cat|grep|find|bun|npm|pnpm|yarn)\s)/m.test(trimmed)) {
|
|
429
|
+
return "bash";
|
|
430
|
+
}
|
|
431
|
+
if ((trimmed.startsWith("{") || trimmed.startsWith("[")) && trimmed.length > 1) {
|
|
432
|
+
try {
|
|
433
|
+
JSON.parse(trimmed);
|
|
434
|
+
return "json";
|
|
435
|
+
} catch {
|
|
436
|
+
// Keep the tool-specific fallback for incomplete source.
|
|
437
|
+
}
|
|
438
|
+
}
|
|
439
|
+
return fallback;
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
/** Extract a compact code or command preview from a tool request payload. */
|
|
443
|
+
export function toolInputPresentation(
|
|
444
|
+
toolName: string,
|
|
445
|
+
input: unknown,
|
|
446
|
+
): ToolInputPresentation | undefined {
|
|
447
|
+
const record = inputRecord(input);
|
|
448
|
+
if (!record) return undefined;
|
|
449
|
+
if (typeof record.command === "string" && toolName.endsWith("execute_command")) {
|
|
450
|
+
return {
|
|
451
|
+
label: "Command",
|
|
452
|
+
language: detectToolSourceLanguage(record.command, "bash"),
|
|
453
|
+
source: record.command,
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (typeof record.code === "string") {
|
|
457
|
+
return {
|
|
458
|
+
label: "Code",
|
|
459
|
+
language: detectToolSourceLanguage(record.code, "typescript"),
|
|
460
|
+
source: record.code,
|
|
461
|
+
};
|
|
462
|
+
}
|
|
463
|
+
return undefined;
|
|
464
|
+
}
|
|
465
|
+
|
|
466
|
+
/** Extract Code Mode's completed result into its own compact output panel. */
|
|
467
|
+
export function toolOutputPresentation(
|
|
468
|
+
toolName: string,
|
|
469
|
+
output: unknown,
|
|
470
|
+
): ToolInputPresentation | undefined {
|
|
471
|
+
if (toolName !== "execute_typescript") return undefined;
|
|
472
|
+
const record = inputRecord(output);
|
|
473
|
+
if (!record || record.success !== true || !("result" in record)) return undefined;
|
|
474
|
+
const result = record.result;
|
|
475
|
+
const source = typeof result === "string" ? result : formatRawToolPayload(result);
|
|
476
|
+
return {
|
|
477
|
+
label: "Output",
|
|
478
|
+
language: typeof result === "string" ? detectToolSourceLanguage(result, "text") : "json",
|
|
479
|
+
source,
|
|
480
|
+
};
|
|
481
|
+
}
|
|
482
|
+
|
|
483
|
+
const ToolInputSource = ({ presentation }: { presentation: ToolInputPresentation }) => {
|
|
484
|
+
const lineCount = presentation.source.split("\n").length;
|
|
485
|
+
return (
|
|
486
|
+
<Collapsible className="rounded border border-border/60 bg-background/40">
|
|
487
|
+
<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">
|
|
488
|
+
<ChevronDownIcon className="size-3 shrink-0 transition-transform group-data-[state=closed]:-rotate-90" />
|
|
489
|
+
<span className="font-medium text-foreground/80">{presentation.label}</span>
|
|
490
|
+
<span className="rounded bg-muted px-1.5 py-0.5 text-[10px] uppercase leading-none tracking-wide">
|
|
491
|
+
{presentation.language}
|
|
492
|
+
</span>
|
|
493
|
+
<span className="ml-auto text-[10px] tabular-nums">
|
|
494
|
+
{lineCount} {lineCount === 1 ? "line" : "lines"}
|
|
495
|
+
</span>
|
|
496
|
+
</CollapsibleTrigger>
|
|
497
|
+
<CollapsibleContent>
|
|
498
|
+
<SourceBlock
|
|
499
|
+
source={presentation.source}
|
|
500
|
+
language={presentation.language}
|
|
501
|
+
className="max-h-64 border-t border-border/50 px-2 py-1.5 text-foreground"
|
|
502
|
+
/>
|
|
503
|
+
</CollapsibleContent>
|
|
504
|
+
</Collapsible>
|
|
505
|
+
);
|
|
506
|
+
};
|
|
507
|
+
|
|
401
508
|
type ToolStatusProgress = Extract<ToolProgress, { type: "tool_status" }>;
|
|
402
509
|
|
|
403
510
|
export type WebSearchProgressGroup = {
|
|
@@ -488,6 +595,41 @@ const hasExpandableDetails = (event: ToolEvent): boolean => {
|
|
|
488
595
|
);
|
|
489
596
|
};
|
|
490
597
|
|
|
598
|
+
const ToolCallDetails = ({
|
|
599
|
+
event,
|
|
600
|
+
showQuestion = false,
|
|
601
|
+
}: {
|
|
602
|
+
event: ToolEvent;
|
|
603
|
+
showQuestion?: boolean;
|
|
604
|
+
}) => {
|
|
605
|
+
const question = askGenieQuestion(event);
|
|
606
|
+
const summary = summarizeProgress(event.progress ?? []);
|
|
607
|
+
const inputPresentation =
|
|
608
|
+
"input" in event ? toolInputPresentation(event.toolName, event.input) : undefined;
|
|
609
|
+
const outputPresentation =
|
|
610
|
+
"output" in event ? toolOutputPresentation(event.toolName, event.output) : undefined;
|
|
611
|
+
|
|
612
|
+
return (
|
|
613
|
+
<div className="flex flex-col gap-2">
|
|
614
|
+
{showQuestion && question ? (
|
|
615
|
+
<p className="break-words px-0.5 text-xs italic text-foreground/80">{question}</p>
|
|
616
|
+
) : null}
|
|
617
|
+
{inputPresentation ? (
|
|
618
|
+
<ToolInputSource presentation={inputPresentation} />
|
|
619
|
+
) : "input" in event ? (
|
|
620
|
+
<RawToolPayload label="Request" value={event.input} />
|
|
621
|
+
) : null}
|
|
622
|
+
<WebSearchProgressDetails event={event} />
|
|
623
|
+
<ToolProgressDetails summary={summary} omitQuestion />
|
|
624
|
+
{outputPresentation ? (
|
|
625
|
+
<ToolInputSource presentation={outputPresentation} />
|
|
626
|
+
) : "output" in event ? (
|
|
627
|
+
<RawToolPayload label="Response" value={event.output} />
|
|
628
|
+
) : null}
|
|
629
|
+
</div>
|
|
630
|
+
);
|
|
631
|
+
};
|
|
632
|
+
|
|
491
633
|
/**
|
|
492
634
|
* One row inside {@link ToolSessionPill}. Always-visible header
|
|
493
635
|
* shows the status icon, the tool's verb (`Called Ask Genie`,
|
|
@@ -506,7 +648,6 @@ const ToolCallRow = ({ event }: { event: ToolEvent }) => {
|
|
|
506
648
|
const isRunning = event.status === "running";
|
|
507
649
|
const isError = event.status === "error";
|
|
508
650
|
const question = askGenieQuestion(event);
|
|
509
|
-
const summary = summarizeProgress(event.progress ?? []);
|
|
510
651
|
const expandable = hasExpandableDetails(event);
|
|
511
652
|
// Inner rows defer to the live wire status when running (e.g.
|
|
512
653
|
// "Executing query") so users tracking the open pill see the
|
|
@@ -553,11 +694,8 @@ const ToolCallRow = ({ event }: { event: ToolEvent }) => {
|
|
|
553
694
|
{header}
|
|
554
695
|
</CollapsibleTrigger>
|
|
555
696
|
<CollapsibleContent>
|
|
556
|
-
<div className="
|
|
557
|
-
|
|
558
|
-
<WebSearchProgressDetails event={event} />
|
|
559
|
-
<ToolProgressDetails summary={summary} omitQuestion />
|
|
560
|
-
{"output" in event ? <RawToolPayload label="Response" value={event.output} /> : null}
|
|
697
|
+
<div className="px-2 pb-2">
|
|
698
|
+
<ToolCallDetails event={event} />
|
|
561
699
|
</div>
|
|
562
700
|
</CollapsibleContent>
|
|
563
701
|
</Collapsible>
|
|
@@ -648,10 +786,12 @@ export const ToolSessionPill = ({ events }: { events: ToolEvent[] }) => {
|
|
|
648
786
|
)}
|
|
649
787
|
</CollapsibleTrigger>
|
|
650
788
|
<CollapsibleContent>
|
|
651
|
-
<div className="mt-1.5 flex flex-col gap-1">
|
|
652
|
-
{events.
|
|
653
|
-
<
|
|
654
|
-
)
|
|
789
|
+
<div className={cn("mt-1.5", events.length > 1 && "flex flex-col gap-1")}>
|
|
790
|
+
{events.length === 1 ? (
|
|
791
|
+
<ToolCallDetails event={latest} showQuestion />
|
|
792
|
+
) : (
|
|
793
|
+
events.map((event) => <ToolCallRow key={event.id} event={event} />)
|
|
794
|
+
)}
|
|
655
795
|
</div>
|
|
656
796
|
</CollapsibleContent>
|
|
657
797
|
</Collapsible>
|