@dbx-tools/ui-mastra 0.3.21 → 0.3.23
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/README.md +1 -1
- package/package.json +7 -7
- package/src/react/bubbles.tsx +6 -21
- package/src/react/chat-view.tsx +136 -151
- package/src/react/data-grid.tsx +6 -16
- package/src/react/embed-slots.tsx +5 -11
- package/src/react/feedback-controls.tsx +1 -6
- package/src/react/markdown.tsx +5 -19
- package/src/react/mastra-chat.tsx +37 -85
- package/src/react/suggestion-pills.tsx +1 -5
- package/src/react/thread-sidebar.tsx +2 -5
- package/src/react/tool-pill.tsx +11 -41
- package/src/react/types.ts +1 -4
- package/src/support/chart-option.ts +2 -5
- package/src/support/export.ts +13 -45
- package/src/support/mastra-client.ts +9 -19
- package/src/support/thread-sessions.ts +1 -4
- package/test/thread-sessions.test.ts +101 -0
- package/test/tsconfig.json +14 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { error as errorUtil } from "@dbx-tools/shared-core";
|
|
2
2
|
import {
|
|
3
3
|
feedback,
|
|
4
4
|
override,
|
|
@@ -17,8 +17,8 @@ import {
|
|
|
17
17
|
type MastraUpdateThreadResponse,
|
|
18
18
|
type StatementData,
|
|
19
19
|
} from "@dbx-tools/shared-mastra";
|
|
20
|
-
import { error as errorUtil } from "@dbx-tools/shared-core";
|
|
21
20
|
import type { ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
21
|
+
import { usePluginClientConfig } from "@dbx-tools/ui-appkit/react";
|
|
22
22
|
import { MastraClient } from "@mastra/client-js";
|
|
23
23
|
import { useCallback, useEffect, useMemo, useState } from "react";
|
|
24
24
|
import { asMastraStreamResponse, type MastraStreamResponse } from "./mastra-stream";
|
|
@@ -59,8 +59,7 @@ export class MastraPluginClient extends MastraClient {
|
|
|
59
59
|
|
|
60
60
|
constructor(config: MastraClientConfig) {
|
|
61
61
|
super({
|
|
62
|
-
baseUrl:
|
|
63
|
-
typeof window !== "undefined" ? window.location.origin : "http://localhost",
|
|
62
|
+
baseUrl: typeof window !== "undefined" ? window.location.origin : "http://localhost",
|
|
64
63
|
apiPrefix: config.basePath,
|
|
65
64
|
credentials: "include",
|
|
66
65
|
headers: {},
|
|
@@ -261,9 +260,7 @@ export class MastraPluginClient extends MastraClient {
|
|
|
261
260
|
"DELETE",
|
|
262
261
|
wire.MastraClearHistoryResponseSchema,
|
|
263
262
|
{
|
|
264
|
-
...(options.threadId
|
|
265
|
-
? { headers: { [thread.THREAD_ID_HEADER]: options.threadId } }
|
|
266
|
-
: {}),
|
|
263
|
+
...(options.threadId ? { headers: { [thread.THREAD_ID_HEADER]: options.threadId } } : {}),
|
|
267
264
|
...(options.signal ? { signal: options.signal } : {}),
|
|
268
265
|
},
|
|
269
266
|
);
|
|
@@ -551,8 +548,7 @@ export const useMastraModels = (
|
|
|
551
548
|
// lists a vectoriser. If the server didn't tag tasks at all,
|
|
552
549
|
// pass everything through so we don't show an empty list.
|
|
553
550
|
const llms = endpoints.filter(
|
|
554
|
-
(e) =>
|
|
555
|
-
e.task !== "llm/v1/embeddings" && (!e.task || e.task.startsWith("llm/v1/")),
|
|
551
|
+
(e) => e.task !== "llm/v1/embeddings" && (!e.task || e.task.startsWith("llm/v1/")),
|
|
556
552
|
);
|
|
557
553
|
setModels(llms.length > 0 ? llms : endpoints);
|
|
558
554
|
})
|
|
@@ -709,8 +705,7 @@ export const useMastraThreads = (
|
|
|
709
705
|
setError(null);
|
|
710
706
|
})
|
|
711
707
|
.catch((e: unknown) => {
|
|
712
|
-
if (controller.signal.aborted || (e as { name?: string }).name === "AbortError")
|
|
713
|
-
return;
|
|
708
|
+
if (controller.signal.aborted || (e as { name?: string }).name === "AbortError") return;
|
|
714
709
|
setError(errorUtil.toError(e));
|
|
715
710
|
setThreads([]);
|
|
716
711
|
})
|
|
@@ -766,8 +761,7 @@ export interface ByIdFetchState<T> {
|
|
|
766
761
|
* everything. Keyed by `<type>:<id>` (e.g. `chart:abc123`).
|
|
767
762
|
*/
|
|
768
763
|
type FetchCacheEntry<T> =
|
|
769
|
-
|
|
770
|
-
| { kind: "ready"; data: T | undefined };
|
|
764
|
+
{ kind: "pending"; promise: Promise<T | undefined> } | { kind: "ready"; data: T | undefined };
|
|
771
765
|
|
|
772
766
|
const fetchCache = new Map<string, FetchCacheEntry<unknown>>();
|
|
773
767
|
|
|
@@ -902,8 +896,7 @@ function useByIdFetch<T>(
|
|
|
902
896
|
* stays stable - inlining a fresh closure would refire the
|
|
903
897
|
* effect on every render.
|
|
904
898
|
*/
|
|
905
|
-
const chartIsTerminal = (c: Chart): boolean =>
|
|
906
|
-
c.result !== undefined || c.error !== undefined;
|
|
899
|
+
const chartIsTerminal = (c: Chart): boolean => c.result !== undefined || c.error !== undefined;
|
|
907
900
|
|
|
908
901
|
/**
|
|
909
902
|
* `isTerminal` for {@link useStatementFetch}. A statement
|
|
@@ -955,9 +948,6 @@ export const useStatementFetch = (
|
|
|
955
948
|
): ByIdFetchState<StatementData> => {
|
|
956
949
|
const client = useMastraClient();
|
|
957
950
|
const key = statementId ? `data:${statementId}` : undefined;
|
|
958
|
-
const fetcher = useCallback(
|
|
959
|
-
() => client.statement(statementId as string),
|
|
960
|
-
[client, statementId],
|
|
961
|
-
);
|
|
951
|
+
const fetcher = useCallback(() => client.statement(statementId as string), [client, statementId]);
|
|
962
952
|
return useByIdFetch<StatementData>(key, fetcher, statementIsTerminal);
|
|
963
953
|
};
|
|
@@ -57,10 +57,7 @@ export function isSessionRunning(session: ThreadSession): boolean {
|
|
|
57
57
|
}
|
|
58
58
|
|
|
59
59
|
/** Append a steer to the queue (oldest first). Returns a new array. */
|
|
60
|
-
export function enqueueSteer(
|
|
61
|
-
queue: QueuedSteer[],
|
|
62
|
-
steer: QueuedSteer,
|
|
63
|
-
): QueuedSteer[] {
|
|
60
|
+
export function enqueueSteer(queue: QueuedSteer[], steer: QueuedSteer): QueuedSteer[] {
|
|
64
61
|
return [...queue, steer];
|
|
65
62
|
}
|
|
66
63
|
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import { describe, it } from "node:test";
|
|
3
|
+
import type { QueuedSteer, ToolEvent } from "../src/react/types";
|
|
4
|
+
import {
|
|
5
|
+
enqueueSteer,
|
|
6
|
+
removeSteer,
|
|
7
|
+
reorderSteers,
|
|
8
|
+
terminateRunningToolEvents,
|
|
9
|
+
} from "../src/support/thread-sessions";
|
|
10
|
+
|
|
11
|
+
const event = (id: string, status: ToolEvent["status"]): ToolEvent => ({
|
|
12
|
+
id,
|
|
13
|
+
toolName: "ask_genie",
|
|
14
|
+
status,
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
describe("terminateRunningToolEvents", () => {
|
|
18
|
+
it("settles running pills to done", () => {
|
|
19
|
+
const next = terminateRunningToolEvents({
|
|
20
|
+
msg1: [event("a", "running"), event("b", "done")],
|
|
21
|
+
msg2: [event("c", "running")],
|
|
22
|
+
});
|
|
23
|
+
assert.deepEqual(
|
|
24
|
+
next.msg1!.map((e) => e.status),
|
|
25
|
+
["done", "done"],
|
|
26
|
+
);
|
|
27
|
+
assert.deepEqual(
|
|
28
|
+
next.msg2!.map((e) => e.status),
|
|
29
|
+
["done"],
|
|
30
|
+
);
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
it("leaves already-terminal pills untouched and returns the same ref when nothing ran", () => {
|
|
34
|
+
const input = {
|
|
35
|
+
msg1: [event("a", "done"), event("b", "error")],
|
|
36
|
+
};
|
|
37
|
+
const next = terminateRunningToolEvents(input);
|
|
38
|
+
// No running pills, so the map is returned unchanged (identity) to let
|
|
39
|
+
// callers skip a needless state update.
|
|
40
|
+
assert.equal(next, input);
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
it("handles an empty map", () => {
|
|
44
|
+
const input = {};
|
|
45
|
+
assert.equal(terminateRunningToolEvents(input), input);
|
|
46
|
+
});
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
describe("steer queue", () => {
|
|
50
|
+
const steer = (id: string, text: string): QueuedSteer => ({ id, text });
|
|
51
|
+
|
|
52
|
+
it("enqueues oldest-first without mutating the input", () => {
|
|
53
|
+
const q0: QueuedSteer[] = [];
|
|
54
|
+
const q1 = enqueueSteer(q0, steer("a", "first"));
|
|
55
|
+
const q2 = enqueueSteer(q1, steer("b", "second"));
|
|
56
|
+
assert.deepEqual(
|
|
57
|
+
q2.map((s) => s.id),
|
|
58
|
+
["a", "b"],
|
|
59
|
+
);
|
|
60
|
+
assert.equal(q0.length, 0);
|
|
61
|
+
assert.equal(q1.length, 1);
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
it("removes by id and leaves the rest in order", () => {
|
|
65
|
+
const q = [steer("a", "1"), steer("b", "2"), steer("c", "3")];
|
|
66
|
+
assert.deepEqual(
|
|
67
|
+
removeSteer(q, "b").map((s) => s.id),
|
|
68
|
+
["a", "c"],
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
it("removing an unknown id is a no-op copy", () => {
|
|
73
|
+
const q = [steer("a", "1")];
|
|
74
|
+
assert.deepEqual(removeSteer(q, "zzz"), q);
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it("reorders to match the given id order", () => {
|
|
78
|
+
const q = [steer("a", "1"), steer("b", "2"), steer("c", "3")];
|
|
79
|
+
assert.deepEqual(
|
|
80
|
+
reorderSteers(q, ["c", "a", "b"]).map((s) => s.id),
|
|
81
|
+
["c", "a", "b"],
|
|
82
|
+
);
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
it("appends any current steer missing from the order, and ignores unknown ids", () => {
|
|
86
|
+
const q = [steer("a", "1"), steer("b", "2"), steer("c", "3")];
|
|
87
|
+
// "b" omitted + a stale "zzz" present: zzz ignored, b appended after the rest.
|
|
88
|
+
assert.deepEqual(
|
|
89
|
+
reorderSteers(q, ["c", "zzz", "a"]).map((s) => s.id),
|
|
90
|
+
["c", "a", "b"],
|
|
91
|
+
);
|
|
92
|
+
});
|
|
93
|
+
|
|
94
|
+
it("never duplicates when the order repeats an id", () => {
|
|
95
|
+
const q = [steer("a", "1"), steer("b", "2")];
|
|
96
|
+
assert.deepEqual(
|
|
97
|
+
reorderSteers(q, ["a", "a", "b"]).map((s) => s.id),
|
|
98
|
+
["a", "b"],
|
|
99
|
+
);
|
|
100
|
+
});
|
|
101
|
+
});
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// ~~ Generated by projen. To modify, edit .projenrc.js and run "pnpm exec projen".
|
|
2
|
+
{
|
|
3
|
+
"extends": "../tsconfig.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"rootDir": ".."
|
|
7
|
+
},
|
|
8
|
+
"include": [
|
|
9
|
+
"**/*.ts"
|
|
10
|
+
],
|
|
11
|
+
"exclude": [
|
|
12
|
+
"node_modules"
|
|
13
|
+
]
|
|
14
|
+
}
|