@dbx-tools/ui-mastra 0.6.219 → 0.6.220
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 +3 -1
- package/index.ts +2 -2
- package/package.json +7 -7
- package/src/react/bubbles.tsx +5 -1
- package/src/react/chat-approvals.ts +38 -12
- package/src/react/chat-composer.tsx +2 -2
- package/src/react/mastra-assistant.tsx +113 -11
- package/src/react/mastra-chat.tsx +7 -0
- package/src/react/types.ts +4 -0
- package/src/support/thread-sessions.ts +9 -0
package/README.md
CHANGED
|
@@ -121,7 +121,9 @@ lock size, `launcher={false}` when every launch comes from
|
|
|
121
121
|
the conversation pills. With the header enabled those controls move into it and
|
|
122
122
|
the conversation pills remain on their own second row. Replace `icon`, `closeIcon`,
|
|
123
123
|
`newConversationIcon`, `historyIcon`, and `launcher.icon`. Below
|
|
124
|
-
`mobileBreakpoint` the panel becomes a
|
|
124
|
+
`mobileBreakpoint` the panel becomes a focus-contained modal overlay with Escape
|
|
125
|
+
dismissal while the covered application is inert. A `storageKey` retains
|
|
126
|
+
horizontal and vertical sizes independently.
|
|
125
127
|
|
|
126
128
|
## Render A Drop-In Chat
|
|
127
129
|
|
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.6.
|
|
6
|
+
export const PACKAGE_VERSION = "0.6.220";
|
|
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";
|
|
@@ -73,5 +73,5 @@ export type { MastraStreamResponse, MastraMemoryThread, ByIdFetchState } from ".
|
|
|
73
73
|
export { modelStorageKey, readStoredModel, storeSelectedModel } from "./src/support/model-selection.ts";
|
|
74
74
|
export type { MastraRequestContextInput, MastraRequestContextSource, MastraRequestContextSnapshot } from "./src/support/request-context.ts";
|
|
75
75
|
export { DEFAULT_THREAD_SESSION_KEY } from "./src/support/thread-sessions.ts";
|
|
76
|
-
export type { SessionQueuedSteer, ThreadSession } from "./src/support/thread-sessions.ts";
|
|
76
|
+
export type { SessionQueuedSteer, SessionRun, ThreadSession } from "./src/support/thread-sessions.ts";
|
|
77
77
|
export { THREAD_TAB_SEED_MAX } from "./src/support/thread-tabs.ts";
|
package/package.json
CHANGED
|
@@ -31,12 +31,12 @@
|
|
|
31
31
|
"react-dom": "~19.2.4"
|
|
32
32
|
},
|
|
33
33
|
"dependencies": {
|
|
34
|
-
"@dbx-tools/shared-core": "0.6.
|
|
35
|
-
"@dbx-tools/shared-genie": "0.6.
|
|
36
|
-
"@dbx-tools/shared-mastra": "0.6.
|
|
37
|
-
"@dbx-tools/shared-model": "0.6.
|
|
38
|
-
"@dbx-tools/ui-appkit": "0.6.
|
|
39
|
-
"@dbx-tools/ui-branding": "0.6.
|
|
34
|
+
"@dbx-tools/shared-core": "0.6.220",
|
|
35
|
+
"@dbx-tools/shared-genie": "0.6.220",
|
|
36
|
+
"@dbx-tools/shared-mastra": "0.6.220",
|
|
37
|
+
"@dbx-tools/shared-model": "0.6.220",
|
|
38
|
+
"@dbx-tools/ui-appkit": "0.6.220",
|
|
39
|
+
"@dbx-tools/ui-branding": "0.6.220",
|
|
40
40
|
"@mastra/client-js": "1.50.0",
|
|
41
41
|
"@tanstack/react-table": "^8.21.3",
|
|
42
42
|
"ai": "^5.0.0",
|
|
@@ -52,7 +52,7 @@
|
|
|
52
52
|
"publishConfig": {
|
|
53
53
|
"access": "public"
|
|
54
54
|
},
|
|
55
|
-
"version": "0.6.
|
|
55
|
+
"version": "0.6.220",
|
|
56
56
|
"type": "module",
|
|
57
57
|
"exports": {
|
|
58
58
|
"./react": "./src/react/index.ts",
|
package/src/react/bubbles.tsx
CHANGED
|
@@ -135,6 +135,7 @@ const EmailApprovalPreview = ({ email }: { email: EmailApprovalInput }) => {
|
|
|
135
135
|
* {@link ApprovalDecision} to the parent resume handler.
|
|
136
136
|
*/
|
|
137
137
|
const ToolApprovalCard = ({
|
|
138
|
+
messageId,
|
|
138
139
|
toolName,
|
|
139
140
|
toolCallId,
|
|
140
141
|
runId,
|
|
@@ -142,6 +143,7 @@ const ToolApprovalCard = ({
|
|
|
142
143
|
onResolve,
|
|
143
144
|
disabled,
|
|
144
145
|
}: {
|
|
146
|
+
messageId: string;
|
|
145
147
|
toolName: string;
|
|
146
148
|
toolCallId: string;
|
|
147
149
|
runId?: string;
|
|
@@ -158,9 +160,10 @@ const ToolApprovalCard = ({
|
|
|
158
160
|
setDecision(approved ? "approved" : "denied");
|
|
159
161
|
Promise.resolve(
|
|
160
162
|
approved
|
|
161
|
-
? onResolve({ approved: true, toolName, toolCallId, runId, input })
|
|
163
|
+
? onResolve({ approved: true, messageId, toolName, toolCallId, runId, input })
|
|
162
164
|
: onResolve({
|
|
163
165
|
approved: false,
|
|
166
|
+
messageId,
|
|
164
167
|
toolName,
|
|
165
168
|
toolCallId,
|
|
166
169
|
runId,
|
|
@@ -428,6 +431,7 @@ export const AssistantBubble = ({
|
|
|
428
431
|
{pendingApprovals.map((p) => (
|
|
429
432
|
<ToolApprovalCard
|
|
430
433
|
key={p.toolCallId}
|
|
434
|
+
messageId={message.id}
|
|
431
435
|
toolName={p.toolName}
|
|
432
436
|
toolCallId={p.toolCallId}
|
|
433
437
|
runId={p.runId}
|
|
@@ -3,7 +3,7 @@ import { useCallback } from "react";
|
|
|
3
3
|
import type { ThreadSessionReader, ThreadSessionUpdater } from "./chat-sessions.ts";
|
|
4
4
|
import type { ApprovalDecision } from "./types.ts";
|
|
5
5
|
import type { MastraPluginClient, MastraStreamResponse } from "../support/mastra-client.ts";
|
|
6
|
-
import { DEFAULT_THREAD_SESSION_KEY } from "../support/thread-sessions.ts";
|
|
6
|
+
import { DEFAULT_THREAD_SESSION_KEY, type ThreadSession } from "../support/thread-sessions.ts";
|
|
7
7
|
|
|
8
8
|
const logger = log.logger("ui-mastra/chat");
|
|
9
9
|
|
|
@@ -22,6 +22,35 @@ interface UseChatApprovalsOptions {
|
|
|
22
22
|
updateSession: ThreadSessionUpdater;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
+
/** Resolve one approval against the exact run and assistant message that created it. */
|
|
26
|
+
export function resolveApprovalContinuation(
|
|
27
|
+
session: ThreadSession,
|
|
28
|
+
decision: ApprovalDecision,
|
|
29
|
+
):
|
|
30
|
+
| {
|
|
31
|
+
assistantId: string;
|
|
32
|
+
runId: string;
|
|
33
|
+
requestContext: ThreadSession["runRequestContext"];
|
|
34
|
+
}
|
|
35
|
+
| undefined {
|
|
36
|
+
const runId = decision.runId ?? session.runId;
|
|
37
|
+
if (!runId) return undefined;
|
|
38
|
+
const run = session.runs[runId];
|
|
39
|
+
const assistantId =
|
|
40
|
+
decision.messageId ??
|
|
41
|
+
run?.assistantId ??
|
|
42
|
+
Object.entries(session.pendingApprovalsByMessage).find(([, approvals]) =>
|
|
43
|
+
approvals.some((approval) => approval.toolCallId === decision.toolCallId),
|
|
44
|
+
)?.[0] ??
|
|
45
|
+
session.assistantId;
|
|
46
|
+
if (!assistantId) return undefined;
|
|
47
|
+
return {
|
|
48
|
+
assistantId,
|
|
49
|
+
runId,
|
|
50
|
+
requestContext: run?.requestContext,
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
|
|
25
54
|
/** Resume an approval-gated tool call and remove its pending UI state. */
|
|
26
55
|
export function useChatApprovals({
|
|
27
56
|
activeKey,
|
|
@@ -33,22 +62,19 @@ export function useChatApprovals({
|
|
|
33
62
|
}: UseChatApprovalsOptions) {
|
|
34
63
|
return useCallback(
|
|
35
64
|
async (decision: ApprovalDecision) => {
|
|
36
|
-
const {
|
|
65
|
+
const { toolCallId, toolName } = decision;
|
|
37
66
|
const session = getSession(activeKey);
|
|
38
|
-
const
|
|
39
|
-
|
|
40
|
-
approvals.some((approval) => approval.toolCallId === toolCallId),
|
|
41
|
-
)?.[0] ?? session.assistantId;
|
|
42
|
-
const runId = decisionRunId ?? session.runId;
|
|
43
|
-
if (!runId || !assistantId) {
|
|
67
|
+
const continuation = resolveApprovalContinuation(session, decision);
|
|
68
|
+
if (!continuation) {
|
|
44
69
|
logger.warn("approval missing runId or assistantId, cannot resume", {
|
|
45
70
|
tool: toolName,
|
|
46
71
|
toolCallId,
|
|
47
|
-
hasRunId: Boolean(runId),
|
|
48
|
-
hasAssistantId: Boolean(assistantId),
|
|
72
|
+
hasRunId: Boolean(decision.runId ?? session.runId),
|
|
73
|
+
hasAssistantId: Boolean(decision.messageId ?? session.assistantId),
|
|
49
74
|
});
|
|
50
75
|
return;
|
|
51
76
|
}
|
|
77
|
+
const { assistantId, runId, requestContext } = continuation;
|
|
52
78
|
|
|
53
79
|
updateSession(activeKey, (current) => {
|
|
54
80
|
const existing = current.pendingApprovalsByMessage[assistantId];
|
|
@@ -79,7 +105,7 @@ export function useChatApprovals({
|
|
|
79
105
|
runId,
|
|
80
106
|
toolCallId,
|
|
81
107
|
threadId: streamThreadId,
|
|
82
|
-
requestContext
|
|
108
|
+
requestContext,
|
|
83
109
|
signal,
|
|
84
110
|
})
|
|
85
111
|
: mastraClient.declineToolCallStream(agentId, {
|
|
@@ -87,7 +113,7 @@ export function useChatApprovals({
|
|
|
87
113
|
toolCallId,
|
|
88
114
|
threadId: streamThreadId,
|
|
89
115
|
reason: decision.reason,
|
|
90
|
-
requestContext
|
|
116
|
+
requestContext,
|
|
91
117
|
signal,
|
|
92
118
|
}),
|
|
93
119
|
);
|
|
@@ -394,9 +394,9 @@ export const ChatComposer = ({
|
|
|
394
394
|
>
|
|
395
395
|
<SelectTrigger
|
|
396
396
|
size="sm"
|
|
397
|
-
aria-label="Model"
|
|
398
397
|
className="h-7 w-auto max-w-[180px] gap-1 truncate border-0 bg-transparent px-2 text-xs shadow-none hover:bg-accent [&_svg]:size-3"
|
|
399
398
|
>
|
|
399
|
+
<span className="sr-only">Model:</span>
|
|
400
400
|
<SelectValue placeholder={defaultOptionLabel} />
|
|
401
401
|
</SelectTrigger>
|
|
402
402
|
<SelectContent>
|
|
@@ -430,7 +430,7 @@ export const ChatComposer = ({
|
|
|
430
430
|
size="icon-sm"
|
|
431
431
|
variant="default"
|
|
432
432
|
disabled={!input.trim() || isLoadingHistory}
|
|
433
|
-
aria-label={isRunning ? "
|
|
433
|
+
aria-label={isRunning ? "Queue message" : "Send message"}
|
|
434
434
|
className="shrink-0 rounded-full"
|
|
435
435
|
>
|
|
436
436
|
<SendIcon className="size-3" />
|
|
@@ -15,6 +15,7 @@ import {
|
|
|
15
15
|
useCallback,
|
|
16
16
|
useContext,
|
|
17
17
|
useEffect,
|
|
18
|
+
useLayoutEffect,
|
|
18
19
|
useMemo,
|
|
19
20
|
useRef,
|
|
20
21
|
useState,
|
|
@@ -157,6 +158,15 @@ const panelClosedClasses: Record<MastraAssistantSide, string> = {
|
|
|
157
158
|
left: "-translate-x-full",
|
|
158
159
|
};
|
|
159
160
|
|
|
161
|
+
const FOCUSABLE_SELECTOR = [
|
|
162
|
+
"a[href]",
|
|
163
|
+
"button:not([disabled])",
|
|
164
|
+
"input:not([disabled])",
|
|
165
|
+
"select:not([disabled])",
|
|
166
|
+
"textarea:not([disabled])",
|
|
167
|
+
'[tabindex]:not([tabindex="-1"])',
|
|
168
|
+
].join(",");
|
|
169
|
+
|
|
160
170
|
const resizeHandleClasses: Record<MastraAssistantSide, string> = {
|
|
161
171
|
top: "-bottom-1 inset-x-0 h-2 cursor-row-resize",
|
|
162
172
|
right: "-left-1 inset-y-0 w-2 cursor-col-resize",
|
|
@@ -167,6 +177,8 @@ const resizeHandleClasses: Record<MastraAssistantSide, string> = {
|
|
|
167
177
|
type ResizeState = {
|
|
168
178
|
pointer: number;
|
|
169
179
|
size: number;
|
|
180
|
+
bodyCursor: string;
|
|
181
|
+
bodyUserSelect: string;
|
|
170
182
|
} | null;
|
|
171
183
|
|
|
172
184
|
/**
|
|
@@ -201,21 +213,27 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
201
213
|
...rootProps
|
|
202
214
|
}: MastraAssistantProps<TValues>) {
|
|
203
215
|
const rootRef = useRef<HTMLDivElement>(null);
|
|
216
|
+
const panelRef = useRef<HTMLElement>(null);
|
|
217
|
+
const previousFocusRef = useRef<HTMLElement | null>(null);
|
|
204
218
|
const [internalOpen, setInternalOpen] = useState(defaultOpen);
|
|
205
|
-
const
|
|
219
|
+
const requestedOpen = openProp ?? internalOpen;
|
|
220
|
+
const isOpen = available && requestedOpen;
|
|
206
221
|
const [launchContext, setLaunchContext] = useState<
|
|
207
222
|
MastraRequestContextInput<TValues> | undefined
|
|
208
223
|
>();
|
|
209
224
|
const requestContext = launchContext ?? requestContextProp;
|
|
210
225
|
const resizeOptions = useMemo(() => (resizable === true ? {} : resizable || {}), [resizable]);
|
|
211
226
|
const vertical = sideIsVertical(side);
|
|
227
|
+
const sizeStorageKey = resizeOptions.storageKey
|
|
228
|
+
? `${resizeOptions.storageKey}:${vertical ? "vertical" : "horizontal"}`
|
|
229
|
+
: undefined;
|
|
212
230
|
const defaultSize = resizeOptions.defaultSize ?? (vertical ? 360 : 432);
|
|
213
|
-
const [panelSize, setPanelSize] = useState(
|
|
214
|
-
() => readStoredSize(resizeOptions.storageKey) ?? defaultSize,
|
|
215
|
-
);
|
|
231
|
+
const [panelSize, setPanelSize] = useState(() => readStoredSize(sizeStorageKey) ?? defaultSize);
|
|
216
232
|
const [availableSize, setAvailableSize] = useState(0);
|
|
217
233
|
const [isNarrow, setIsNarrow] = useState(false);
|
|
218
234
|
const resizeState = useRef<ResizeState>(null);
|
|
235
|
+
const sizeConfigRef = useRef(sizeStorageKey);
|
|
236
|
+
const skipSizePersistenceRef = useRef(false);
|
|
219
237
|
|
|
220
238
|
const setOpen = useCallback(
|
|
221
239
|
(next: boolean) => {
|
|
@@ -232,7 +250,7 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
232
250
|
[setOpen],
|
|
233
251
|
);
|
|
234
252
|
const close = useCallback(() => setOpen(false), [setOpen]);
|
|
235
|
-
const toggle = useCallback(() => setOpen(!
|
|
253
|
+
const toggle = useCallback(() => setOpen(!requestedOpen), [requestedOpen, setOpen]);
|
|
236
254
|
|
|
237
255
|
const controller = useMemo<MastraAssistantController<TValues>>(
|
|
238
256
|
() => ({
|
|
@@ -268,13 +286,76 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
268
286
|
}, [mobileBreakpoint, vertical]);
|
|
269
287
|
|
|
270
288
|
useEffect(() => {
|
|
271
|
-
if (!
|
|
289
|
+
if (!isNarrow || !isOpen) return;
|
|
290
|
+
const panel = panelRef.current;
|
|
291
|
+
if (!panel) return;
|
|
292
|
+
previousFocusRef.current =
|
|
293
|
+
document.activeElement instanceof HTMLElement ? document.activeElement : null;
|
|
294
|
+
const focusFrame = window.requestAnimationFrame(() => panel.focus());
|
|
295
|
+
const handleKeyDown = (event: KeyboardEvent) => {
|
|
296
|
+
if (event.key === "Escape") {
|
|
297
|
+
event.preventDefault();
|
|
298
|
+
close();
|
|
299
|
+
return;
|
|
300
|
+
}
|
|
301
|
+
if (event.key !== "Tab") return;
|
|
302
|
+
const focusable = [...panel.querySelectorAll<HTMLElement>(FOCUSABLE_SELECTOR)].filter(
|
|
303
|
+
(element) => !element.hidden && element.getClientRects().length > 0,
|
|
304
|
+
);
|
|
305
|
+
if (focusable.length === 0) {
|
|
306
|
+
event.preventDefault();
|
|
307
|
+
panel.focus();
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
const first = focusable[0]!;
|
|
311
|
+
const last = focusable.at(-1)!;
|
|
312
|
+
if (event.shiftKey && document.activeElement === first) {
|
|
313
|
+
event.preventDefault();
|
|
314
|
+
last.focus();
|
|
315
|
+
} else if (!event.shiftKey && document.activeElement === last) {
|
|
316
|
+
event.preventDefault();
|
|
317
|
+
first.focus();
|
|
318
|
+
}
|
|
319
|
+
};
|
|
320
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
321
|
+
return () => {
|
|
322
|
+
window.cancelAnimationFrame(focusFrame);
|
|
323
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
324
|
+
const previous = previousFocusRef.current;
|
|
325
|
+
previousFocusRef.current = null;
|
|
326
|
+
window.requestAnimationFrame(() => {
|
|
327
|
+
window.requestAnimationFrame(() => {
|
|
328
|
+
if (previous?.isConnected) {
|
|
329
|
+
previous.focus();
|
|
330
|
+
} else {
|
|
331
|
+
rootRef.current
|
|
332
|
+
?.querySelector<HTMLElement>("[data-mastra-assistant-launcher]")
|
|
333
|
+
?.focus();
|
|
334
|
+
}
|
|
335
|
+
});
|
|
336
|
+
});
|
|
337
|
+
};
|
|
338
|
+
}, [close, isNarrow, isOpen]);
|
|
339
|
+
|
|
340
|
+
useLayoutEffect(() => {
|
|
341
|
+
if (sizeConfigRef.current === sizeStorageKey) return;
|
|
342
|
+
sizeConfigRef.current = sizeStorageKey;
|
|
343
|
+
skipSizePersistenceRef.current = true;
|
|
344
|
+
setPanelSize(readStoredSize(sizeStorageKey) ?? defaultSize);
|
|
345
|
+
}, [defaultSize, sizeStorageKey]);
|
|
346
|
+
|
|
347
|
+
useEffect(() => {
|
|
348
|
+
if (!sizeStorageKey || typeof window === "undefined") return;
|
|
349
|
+
if (skipSizePersistenceRef.current) {
|
|
350
|
+
skipSizePersistenceRef.current = false;
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
272
353
|
try {
|
|
273
|
-
window.localStorage.setItem(
|
|
354
|
+
window.localStorage.setItem(sizeStorageKey, String(panelSize));
|
|
274
355
|
} catch {
|
|
275
356
|
// Size persistence is best-effort; the panel remains usable without storage.
|
|
276
357
|
}
|
|
277
|
-
}, [panelSize,
|
|
358
|
+
}, [panelSize, sizeStorageKey]);
|
|
278
359
|
|
|
279
360
|
useEffect(() => {
|
|
280
361
|
const move = (event: PointerEvent) => {
|
|
@@ -292,17 +373,21 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
292
373
|
);
|
|
293
374
|
};
|
|
294
375
|
const finish = () => {
|
|
376
|
+
const current = resizeState.current;
|
|
377
|
+
if (!current) return;
|
|
295
378
|
resizeState.current = null;
|
|
296
|
-
document.body.style.
|
|
297
|
-
document.body.style.
|
|
379
|
+
document.body.style.userSelect = current.bodyUserSelect;
|
|
380
|
+
document.body.style.cursor = current.bodyCursor;
|
|
298
381
|
};
|
|
299
382
|
window.addEventListener("pointermove", move);
|
|
300
383
|
window.addEventListener("pointerup", finish);
|
|
301
384
|
window.addEventListener("pointercancel", finish);
|
|
385
|
+
window.addEventListener("blur", finish);
|
|
302
386
|
return () => {
|
|
303
387
|
window.removeEventListener("pointermove", move);
|
|
304
388
|
window.removeEventListener("pointerup", finish);
|
|
305
389
|
window.removeEventListener("pointercancel", finish);
|
|
390
|
+
window.removeEventListener("blur", finish);
|
|
306
391
|
finish();
|
|
307
392
|
};
|
|
308
393
|
}, [availableSize, resizeOptions, side, vertical]);
|
|
@@ -314,6 +399,8 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
314
399
|
resizeState.current = {
|
|
315
400
|
pointer: vertical ? event.clientY : event.clientX,
|
|
316
401
|
size: panelSize,
|
|
402
|
+
bodyCursor: document.body.style.cursor,
|
|
403
|
+
bodyUserSelect: document.body.style.userSelect,
|
|
317
404
|
};
|
|
318
405
|
document.body.style.userSelect = "none";
|
|
319
406
|
document.body.style.cursor = vertical ? "row-resize" : "col-resize";
|
|
@@ -414,9 +501,14 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
414
501
|
|
|
415
502
|
const panel = (
|
|
416
503
|
<aside
|
|
504
|
+
key="assistant-panel"
|
|
505
|
+
ref={panelRef}
|
|
506
|
+
role={isNarrow ? "dialog" : undefined}
|
|
507
|
+
aria-modal={isNarrow && isOpen ? true : undefined}
|
|
417
508
|
aria-label={typeof title === "string" ? title : "Assistant"}
|
|
418
509
|
aria-hidden={!isOpen}
|
|
419
510
|
inert={!isOpen ? true : undefined}
|
|
511
|
+
tabIndex={isNarrow ? -1 : undefined}
|
|
420
512
|
style={panelStyle}
|
|
421
513
|
className={cn(
|
|
422
514
|
"relative z-40 flex min-h-0 min-w-0 flex-col bg-background text-foreground shadow-xl",
|
|
@@ -471,6 +563,7 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
471
563
|
const launchButton =
|
|
472
564
|
available && !isOpen && launcherOptions ? (
|
|
473
565
|
<Button
|
|
566
|
+
data-mastra-assistant-launcher
|
|
474
567
|
type="button"
|
|
475
568
|
size="icon"
|
|
476
569
|
aria-label={launcherOptions.label ?? "Open assistant"}
|
|
@@ -486,7 +579,16 @@ export function MastraAssistant<TValues extends Record<string, unknown> = Record
|
|
|
486
579
|
</Button>
|
|
487
580
|
) : null;
|
|
488
581
|
|
|
489
|
-
const content =
|
|
582
|
+
const content = (
|
|
583
|
+
<div
|
|
584
|
+
key="assistant-content"
|
|
585
|
+
aria-hidden={isNarrow && isOpen ? true : undefined}
|
|
586
|
+
inert={isNarrow && isOpen ? true : undefined}
|
|
587
|
+
className={cn("min-h-0 min-w-0 flex-1", contentClassName)}
|
|
588
|
+
>
|
|
589
|
+
{children}
|
|
590
|
+
</div>
|
|
591
|
+
);
|
|
490
592
|
const rootDirection = vertical ? "flex-col" : "flex-row";
|
|
491
593
|
const panelFirst = side === "top" || side === "left";
|
|
492
594
|
|
|
@@ -477,6 +477,13 @@ export const useMastraChat = <TValues extends Record<string, unknown> = Record<s
|
|
|
477
477
|
runId,
|
|
478
478
|
runRequestContext: requestContext,
|
|
479
479
|
lastRequestContext: requestContext,
|
|
480
|
+
runs: {
|
|
481
|
+
...session.runs,
|
|
482
|
+
[runId]: {
|
|
483
|
+
assistantId,
|
|
484
|
+
...(requestContext ? { requestContext } : {}),
|
|
485
|
+
},
|
|
486
|
+
},
|
|
480
487
|
}));
|
|
481
488
|
// Capture this run's thread + model at send time and pass them per
|
|
482
489
|
// call, so a run keeps its own routing even if the user switches
|
package/src/react/types.ts
CHANGED
|
@@ -352,6 +352,8 @@ export type ApprovalDecision =
|
|
|
352
352
|
approved: true;
|
|
353
353
|
toolName: string;
|
|
354
354
|
toolCallId: string;
|
|
355
|
+
/** Assistant UI message that owns the approval card. */
|
|
356
|
+
messageId?: string;
|
|
355
357
|
/**
|
|
356
358
|
* Mastra run id required to resume the suspended workflow. Native
|
|
357
359
|
* suspended-run discovery restores it for persisted approvals.
|
|
@@ -363,6 +365,8 @@ export type ApprovalDecision =
|
|
|
363
365
|
approved: false;
|
|
364
366
|
toolName: string;
|
|
365
367
|
toolCallId: string;
|
|
368
|
+
/** Assistant UI message that owns the approval card. */
|
|
369
|
+
messageId?: string;
|
|
366
370
|
runId?: string;
|
|
367
371
|
input: unknown;
|
|
368
372
|
reason: string;
|
|
@@ -9,6 +9,12 @@ export type SessionQueuedSteer = QueuedSteer & {
|
|
|
9
9
|
requestContext?: MastraRequestContextSnapshot;
|
|
10
10
|
};
|
|
11
11
|
|
|
12
|
+
/** UI target and immutable application context captured for one Mastra run. */
|
|
13
|
+
export type SessionRun = {
|
|
14
|
+
assistantId: string;
|
|
15
|
+
requestContext?: MastraRequestContextSnapshot;
|
|
16
|
+
};
|
|
17
|
+
|
|
12
18
|
/** Session-scoped transcript + stream state for one conversation thread. */
|
|
13
19
|
export type ThreadSession = {
|
|
14
20
|
messages: UIMessage[];
|
|
@@ -20,6 +26,8 @@ export type ThreadSession = {
|
|
|
20
26
|
runToken: number;
|
|
21
27
|
assistantId: string | null;
|
|
22
28
|
runId: string | null;
|
|
29
|
+
/** Live runs retained by id so older approval cards resume their own context. */
|
|
30
|
+
runs: Record<string, SessionRun>;
|
|
23
31
|
historyLoaded: boolean;
|
|
24
32
|
hasMoreHistory: boolean;
|
|
25
33
|
historyPage: number;
|
|
@@ -45,6 +53,7 @@ export function createThreadSession(): ThreadSession {
|
|
|
45
53
|
runToken: 0,
|
|
46
54
|
assistantId: null,
|
|
47
55
|
runId: null,
|
|
56
|
+
runs: {},
|
|
48
57
|
historyLoaded: false,
|
|
49
58
|
hasMoreHistory: false,
|
|
50
59
|
historyPage: 0,
|