@dbx-tools/ui-mastra 0.9.70 → 0.9.71
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 +2 -1
- package/package.json +6 -6
- package/src/react/bubbles.tsx +5 -1
- package/src/react/tool-pill.tsx +77 -1
- package/src/react/types.ts +2 -2
- package/src/support/tool-events.ts +25 -4
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.71";
|
|
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";
|
|
@@ -64,6 +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
68
|
export type { ChatStatus, ToolEvent, ToolProgress, ChatModelOption, QueuedSteer, FeedbackValue, FeedbackSubmission, MessageFeedback, ThreadSummary, ThreadPlacement, ChatViewProps, ApprovalDecision, PendingApproval } from "./src/react/types.ts";
|
|
68
69
|
export { LIGHT_CHART_CHROME } from "./src/support/chart-theme.ts";
|
|
69
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.71",
|
|
33
|
+
"@dbx-tools/shared-genie": "^0.9.71",
|
|
34
|
+
"@dbx-tools/shared-mastra": "^0.9.71",
|
|
35
|
+
"@dbx-tools/shared-model": "^0.9.71",
|
|
36
|
+
"@dbx-tools/ui": "^0.9.71",
|
|
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.71",
|
|
54
54
|
"dbxToolsConfig": {
|
|
55
55
|
"tags": [
|
|
56
56
|
"ui"
|
package/src/react/bubbles.tsx
CHANGED
|
@@ -350,7 +350,11 @@ export const AssistantBubble = ({
|
|
|
350
350
|
feedbackValue,
|
|
351
351
|
mlflowExperimentUrl,
|
|
352
352
|
}: AssistantBubbleProps) => {
|
|
353
|
-
const toolEvents = mergeToolEvents(
|
|
353
|
+
const toolEvents = mergeToolEvents(
|
|
354
|
+
toolEventsFromParts(message.parts),
|
|
355
|
+
events,
|
|
356
|
+
isLast && status === "error",
|
|
357
|
+
);
|
|
354
358
|
const reasoning = [getReasoningText(message.parts), genieReasoningText(toolEvents)]
|
|
355
359
|
.filter(Boolean)
|
|
356
360
|
.join("\n\n");
|
package/src/react/tool-pill.tsx
CHANGED
|
@@ -384,6 +384,81 @@ const RawToolPayload = ({ label, value }: { label: "Request" | "Response"; value
|
|
|
384
384
|
</Collapsible>
|
|
385
385
|
);
|
|
386
386
|
|
|
387
|
+
type ToolStatusProgress = Extract<ToolProgress, { type: "tool_status" }>;
|
|
388
|
+
|
|
389
|
+
export type WebSearchProgressGroup = {
|
|
390
|
+
key: string;
|
|
391
|
+
search?: ToolStatusProgress;
|
|
392
|
+
results: ToolStatusProgress[];
|
|
393
|
+
};
|
|
394
|
+
|
|
395
|
+
/** Group each native search action with the result events that follow it. */
|
|
396
|
+
export function webSearchProgressGroups(event: ToolEvent): WebSearchProgressGroup[] {
|
|
397
|
+
if (event.toolName !== "web_search") return [];
|
|
398
|
+
const groups = new Map<string, WebSearchProgressGroup>();
|
|
399
|
+
const ordered: WebSearchProgressGroup[] = [];
|
|
400
|
+
for (const progress of event.progress ?? []) {
|
|
401
|
+
if (progress.type !== "tool_status") continue;
|
|
402
|
+
const key = progress.groupId ?? `ungrouped-${ordered.length}`;
|
|
403
|
+
let group = groups.get(key);
|
|
404
|
+
if (!group) {
|
|
405
|
+
group = { key, results: [] };
|
|
406
|
+
groups.set(key, group);
|
|
407
|
+
ordered.push(group);
|
|
408
|
+
}
|
|
409
|
+
if (progress.status === "search") group.search = progress;
|
|
410
|
+
if (progress.status === "result") group.results.push(progress);
|
|
411
|
+
}
|
|
412
|
+
return ordered;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
/** Query text for one search group, without a "Searching" prefix. */
|
|
416
|
+
export function webSearchQueryLabel(group: WebSearchProgressGroup): string {
|
|
417
|
+
const raw = group.search?.message?.trim() ?? "";
|
|
418
|
+
const stripped = raw.replace(/^Searching:\s*/i, "").replace(/^Searching\s+the\s+web$/i, "");
|
|
419
|
+
return stripped || "the web";
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
/** Grey suffix shown after a search finishes, e.g. `4 results`. */
|
|
423
|
+
export function webSearchResultLabel(group: WebSearchProgressGroup): string | undefined {
|
|
424
|
+
const detail = group.results.at(-1)?.detail?.trim();
|
|
425
|
+
if (!detail) return undefined;
|
|
426
|
+
const count = detail.match(/(\d+)/)?.[1];
|
|
427
|
+
if (!count) return detail;
|
|
428
|
+
const n = Number(count);
|
|
429
|
+
return `${n} ${n === 1 ? "result" : "results"}`;
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
/** One-line search rows rendered between a web-search request and response. */
|
|
433
|
+
const WebSearchProgressDetails = ({ event }: { event: ToolEvent }) => {
|
|
434
|
+
const groups = webSearchProgressGroups(event);
|
|
435
|
+
if (groups.length === 0) return null;
|
|
436
|
+
return (
|
|
437
|
+
<div className="flex flex-col gap-1">
|
|
438
|
+
{groups.map((group) => {
|
|
439
|
+
const query = webSearchQueryLabel(group);
|
|
440
|
+
const results = webSearchResultLabel(group);
|
|
441
|
+
const done = group.results.length > 0 || event.status !== "running";
|
|
442
|
+
return (
|
|
443
|
+
<div
|
|
444
|
+
key={group.key}
|
|
445
|
+
className="flex min-w-0 items-center gap-1.5 text-[11px] leading-4"
|
|
446
|
+
aria-label={done ? [query, results].filter(Boolean).join(", ") : `Searching ${query}`}
|
|
447
|
+
>
|
|
448
|
+
{done ? (
|
|
449
|
+
<CheckIcon className="size-3 shrink-0 text-muted-foreground" aria-hidden />
|
|
450
|
+
) : (
|
|
451
|
+
<Spinner className="size-3 shrink-0 text-primary" aria-hidden />
|
|
452
|
+
)}
|
|
453
|
+
<span className="min-w-0 truncate text-foreground/90">{query}</span>
|
|
454
|
+
{results ? <span className="shrink-0 text-muted-foreground">- {results}</span> : null}
|
|
455
|
+
</div>
|
|
456
|
+
);
|
|
457
|
+
})}
|
|
458
|
+
</div>
|
|
459
|
+
);
|
|
460
|
+
};
|
|
461
|
+
|
|
387
462
|
/**
|
|
388
463
|
* True when a tool event has anything worth expanding for. The
|
|
389
464
|
* question text already rides on the row header (see
|
|
@@ -465,8 +540,9 @@ const ToolCallRow = ({ event }: { event: ToolEvent }) => {
|
|
|
465
540
|
</CollapsibleTrigger>
|
|
466
541
|
<CollapsibleContent>
|
|
467
542
|
<div className="flex flex-col gap-2 px-2 pb-2">
|
|
468
|
-
<ToolProgressDetails summary={summary} omitQuestion />
|
|
469
543
|
{"input" in event ? <RawToolPayload label="Request" value={event.input} /> : null}
|
|
544
|
+
<WebSearchProgressDetails event={event} />
|
|
545
|
+
<ToolProgressDetails summary={summary} omitQuestion />
|
|
470
546
|
{"output" in event ? <RawToolPayload label="Response" value={event.output} /> : null}
|
|
471
547
|
</div>
|
|
472
548
|
</CollapsibleContent>
|
package/src/react/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type {
|
|
1
|
+
import type { ToolProgressEvent } from "@dbx-tools/shared-mastra/wire";
|
|
2
2
|
import type { ReasoningEffort, ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
3
3
|
import type { UIMessage } from "ai";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
@@ -38,7 +38,7 @@ export type ToolEvent = {
|
|
|
38
38
|
* unified flat `{type, ...}` events the `tool-output` chunks
|
|
39
39
|
* carry. New variants should be added there.
|
|
40
40
|
*/
|
|
41
|
-
export type ToolProgress =
|
|
41
|
+
export type ToolProgress = ToolProgressEvent;
|
|
42
42
|
|
|
43
43
|
/**
|
|
44
44
|
* Subset of a Model Serving endpoint surfaced in the model picker. `name`
|
|
@@ -1,4 +1,9 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import {
|
|
2
|
+
GENIE_PROGRESS_PART_TYPE,
|
|
3
|
+
GenieProgressPartDataSchema,
|
|
4
|
+
TOOL_PROGRESS_PART_TYPE,
|
|
5
|
+
ToolProgressPartDataSchema,
|
|
6
|
+
} from "@dbx-tools/shared-mastra/wire";
|
|
2
7
|
import { getToolOrDynamicToolName, isToolOrDynamicToolUIPart, type UIMessage } from "ai";
|
|
3
8
|
import type { ToolEvent } from "../react/types.ts";
|
|
4
9
|
|
|
@@ -54,8 +59,13 @@ export function toolEventsFromParts(parts: UIMessage["parts"]): ToolEvent[] {
|
|
|
54
59
|
});
|
|
55
60
|
const byId = new Map(events.map((event) => [event.id, event]));
|
|
56
61
|
for (const part of parts) {
|
|
57
|
-
|
|
58
|
-
|
|
62
|
+
const progress =
|
|
63
|
+
part.type === GENIE_PROGRESS_PART_TYPE
|
|
64
|
+
? GenieProgressPartDataSchema.safeParse(part.data)
|
|
65
|
+
: part.type === TOOL_PROGRESS_PART_TYPE
|
|
66
|
+
? ToolProgressPartDataSchema.safeParse(part.data)
|
|
67
|
+
: undefined;
|
|
68
|
+
if (!progress) continue;
|
|
59
69
|
if (!progress.success) continue;
|
|
60
70
|
const event = byId.get(progress.data.toolCallId);
|
|
61
71
|
if (!event) continue;
|
|
@@ -68,11 +78,22 @@ export function toolEventsFromParts(parts: UIMessage["parts"]): ToolEvent[] {
|
|
|
68
78
|
export function mergeToolEvents(
|
|
69
79
|
persisted: ToolEvent[],
|
|
70
80
|
live: ToolEvent[] | undefined,
|
|
81
|
+
terminalError = false,
|
|
71
82
|
): ToolEvent[] {
|
|
72
83
|
const merged = new Map(persisted.map((event) => [event.id, event]));
|
|
73
84
|
for (const event of live ?? []) {
|
|
74
85
|
const prior = merged.get(event.id);
|
|
75
86
|
merged.set(event.id, prior ? { ...prior, ...event } : event);
|
|
76
87
|
}
|
|
77
|
-
|
|
88
|
+
const events = [...merged.values()];
|
|
89
|
+
if (!terminalError) return events;
|
|
90
|
+
return events.map((event) =>
|
|
91
|
+
event.status === "running"
|
|
92
|
+
? {
|
|
93
|
+
...event,
|
|
94
|
+
status: "error",
|
|
95
|
+
output: { error: "The assistant stream ended before this tool returned." },
|
|
96
|
+
}
|
|
97
|
+
: event,
|
|
98
|
+
);
|
|
78
99
|
}
|