@dbx-tools/ui-mastra 0.9.141 → 0.9.144
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 +8 -0
- package/index.ts +1 -1
- package/package.json +6 -6
- package/src/react/bubbles.tsx +0 -7
- package/src/react/chat-transcript.tsx +10 -3
- package/src/react/chat-view.tsx +17 -1
- package/src/react/embed-slots.tsx +3 -2
- package/src/react/markdown.tsx +16 -1
- package/src/react/mastra-chat.tsx +2 -0
- package/src/react/suggestions.ts +47 -26
- package/src/react/types.ts +5 -1
- package/src/support/chart-theme.ts +8 -5
- package/src/support/genie-client.ts +5 -0
- package/src/support/mastra-client.ts +6 -0
package/README.md
CHANGED
|
@@ -210,6 +210,14 @@ Configure the client with these options:
|
|
|
210
210
|
id. Set `enableFeedback={false}` to hide them. Viewers with `CAN_MANAGE` on
|
|
211
211
|
the experiment also get an experiment debug link at the end of the action row.
|
|
212
212
|
|
|
213
|
+
Post-turn follow-ups follow the policy published by AppKit-Mastra. Native Genie
|
|
214
|
+
questions and persisted `data-follow-up-suggestions` parts use the same
|
|
215
|
+
near-duplicate filter and source round-robin. After a turn settles, the latest
|
|
216
|
+
questions render through `SuggestionPills` at the end of the scrollable
|
|
217
|
+
transcript. They disappear when the next user turn starts and remain available
|
|
218
|
+
after history hydration because generated suggestions are persisted on the
|
|
219
|
+
assistant message.
|
|
220
|
+
|
|
213
221
|
## Use direct Genie chat
|
|
214
222
|
|
|
215
223
|
Use `genieSpace` when the application needs the same chat shell without a
|
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.144";
|
|
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";
|
package/package.json
CHANGED
|
@@ -30,11 +30,11 @@
|
|
|
30
30
|
},
|
|
31
31
|
"dependencies": {
|
|
32
32
|
"@databricks/appkit-ui": "0.81.0",
|
|
33
|
-
"@dbx-tools/shared-core": "^0.9.
|
|
34
|
-
"@dbx-tools/shared-genie": "^0.9.
|
|
35
|
-
"@dbx-tools/shared-mastra": "^0.9.
|
|
36
|
-
"@dbx-tools/shared-model": "^0.9.
|
|
37
|
-
"@dbx-tools/ui": "^0.9.
|
|
33
|
+
"@dbx-tools/shared-core": "^0.9.144",
|
|
34
|
+
"@dbx-tools/shared-genie": "^0.9.144",
|
|
35
|
+
"@dbx-tools/shared-mastra": "^0.9.144",
|
|
36
|
+
"@dbx-tools/shared-model": "^0.9.144",
|
|
37
|
+
"@dbx-tools/ui": "^0.9.144",
|
|
38
38
|
"@mastra/client-js": "1.50.0",
|
|
39
39
|
"@mastra/core": "1.71.0",
|
|
40
40
|
"@tanstack/react-table": "^8.21.3",
|
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"publishConfig": {
|
|
52
52
|
"access": "public"
|
|
53
53
|
},
|
|
54
|
-
"version": "0.9.
|
|
54
|
+
"version": "0.9.144",
|
|
55
55
|
"dbxToolsConfig": {
|
|
56
56
|
"tags": [
|
|
57
57
|
"ui"
|
package/src/react/bubbles.tsx
CHANGED
|
@@ -35,8 +35,6 @@ import { MarkdownWithEmbeds } from "./embed-slots.tsx";
|
|
|
35
35
|
import { ExportMenu } from "./export-menu.tsx";
|
|
36
36
|
import { FeedbackControls } from "./feedback-controls.tsx";
|
|
37
37
|
import { ToolMarkdown } from "./markdown.tsx";
|
|
38
|
-
import { SuggestionPills } from "./suggestion-pills.tsx";
|
|
39
|
-
import { collectSuggestions } from "./suggestions.ts";
|
|
40
38
|
import { ToolSessionPill, humanizeToolName } from "./tool-pill.tsx";
|
|
41
39
|
import type {
|
|
42
40
|
ApprovalDecision,
|
|
@@ -316,8 +314,6 @@ type AssistantBubbleProps = {
|
|
|
316
314
|
status: ChatStatus;
|
|
317
315
|
events?: ToolEvent[];
|
|
318
316
|
regenerate?: () => void;
|
|
319
|
-
/** Click handler for tool-suggested follow-up questions. */
|
|
320
|
-
onSuggestionClick?: (question: string) => void;
|
|
321
317
|
/** Approve / deny handler for inline {@link ToolApprovalCard}s. */
|
|
322
318
|
onResolveToolApproval?: (decision: ApprovalDecision) => void | Promise<void>;
|
|
323
319
|
/**
|
|
@@ -353,7 +349,6 @@ export const AssistantBubble = ({
|
|
|
353
349
|
status,
|
|
354
350
|
events,
|
|
355
351
|
regenerate,
|
|
356
|
-
onSuggestionClick,
|
|
357
352
|
onResolveToolApproval,
|
|
358
353
|
externalApprovals,
|
|
359
354
|
onExport,
|
|
@@ -396,7 +391,6 @@ export const AssistantBubble = ({
|
|
|
396
391
|
// streaming target - either the agent has returned to `ready` /
|
|
397
392
|
// `error`, or a newer message has taken over the `isLast` slot.
|
|
398
393
|
const isStreamingThisBubble = isLast && (status === "streaming" || status === "submitted");
|
|
399
|
-
const suggestions = isStreamingThisBubble ? [] : collectSuggestions(events);
|
|
400
394
|
const showResponseStatus = shouldShowAssistantResponseStatus(
|
|
401
395
|
isLast,
|
|
402
396
|
status,
|
|
@@ -528,7 +522,6 @@ export const AssistantBubble = ({
|
|
|
528
522
|
)}
|
|
529
523
|
</div>
|
|
530
524
|
)}
|
|
531
|
-
<SuggestionPills questions={suggestions} onSelect={onSuggestionClick} className="mt-1" />
|
|
532
525
|
</ItemContent>
|
|
533
526
|
</Item>
|
|
534
527
|
);
|
|
@@ -16,6 +16,7 @@ import { ArrowDownIcon, MessageSquareIcon, RefreshCwIcon, TriangleAlertIcon } fr
|
|
|
16
16
|
import React, { useCallback, useEffect, useLayoutEffect, useRef, useState } from "react";
|
|
17
17
|
import { AssistantBubble, UserBubble } from "./bubbles.tsx";
|
|
18
18
|
import { genieFeedbackTarget } from "./chat-feedback.ts";
|
|
19
|
+
import { SuggestionPills } from "./suggestion-pills.tsx";
|
|
19
20
|
import type { ChatViewProps } from "./types.ts";
|
|
20
21
|
import { messageText } from "../support/export.ts";
|
|
21
22
|
|
|
@@ -148,8 +149,9 @@ type ChatTranscriptProps = {
|
|
|
148
149
|
messages: ChatViewProps["messages"];
|
|
149
150
|
status: ChatViewProps["status"];
|
|
150
151
|
error: ChatViewProps["error"];
|
|
151
|
-
sendMessage: ChatViewProps["sendMessage"];
|
|
152
152
|
suggestions: NonNullable<ChatViewProps["suggestions"]>;
|
|
153
|
+
followUpQuestions: string[];
|
|
154
|
+
onSelectFollowUp: (question: string) => void;
|
|
153
155
|
toolEventsByMessage: NonNullable<ChatViewProps["toolEventsByMessage"]>;
|
|
154
156
|
regenerate: ChatViewProps["regenerate"];
|
|
155
157
|
isLoadingMore: NonNullable<ChatViewProps["isLoadingMore"]>;
|
|
@@ -169,8 +171,9 @@ export const ChatTranscript = ({
|
|
|
169
171
|
messages,
|
|
170
172
|
status,
|
|
171
173
|
error,
|
|
172
|
-
sendMessage,
|
|
173
174
|
suggestions,
|
|
175
|
+
followUpQuestions,
|
|
176
|
+
onSelectFollowUp,
|
|
174
177
|
toolEventsByMessage,
|
|
175
178
|
regenerate,
|
|
176
179
|
isLoadingMore,
|
|
@@ -252,7 +255,6 @@ export const ChatTranscript = ({
|
|
|
252
255
|
status={status}
|
|
253
256
|
events={toolEventsByMessage[message.id]}
|
|
254
257
|
regenerate={regenerate}
|
|
255
|
-
onSuggestionClick={(text) => sendMessage({ text })}
|
|
256
258
|
onResolveToolApproval={onResolveToolApproval}
|
|
257
259
|
externalApprovals={pendingApprovalsByMessage[message.id]}
|
|
258
260
|
{...(onExportMessage
|
|
@@ -274,6 +276,11 @@ export const ChatTranscript = ({
|
|
|
274
276
|
}
|
|
275
277
|
return <UserBubble key={message.id} message={message} />;
|
|
276
278
|
})}
|
|
279
|
+
<SuggestionPills
|
|
280
|
+
questions={followUpQuestions}
|
|
281
|
+
onSelect={onSelectFollowUp}
|
|
282
|
+
className="pb-2 pt-1"
|
|
283
|
+
/>
|
|
277
284
|
{showWaitingIndicator && (
|
|
278
285
|
<div
|
|
279
286
|
role="status"
|
package/src/react/chat-view.tsx
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { ChatComposer } from "./chat-composer.tsx";
|
|
2
2
|
import { ChatThreadLayout } from "./chat-thread-layout.tsx";
|
|
3
3
|
import { ChatTranscript, useChatTranscriptController } from "./chat-transcript.tsx";
|
|
4
|
+
import { collectSuggestions } from "./suggestions.ts";
|
|
4
5
|
import type { ChatViewProps } from "./types.ts";
|
|
5
6
|
|
|
6
7
|
/**
|
|
@@ -23,6 +24,8 @@ export const ChatView = ({
|
|
|
23
24
|
onStop,
|
|
24
25
|
className,
|
|
25
26
|
suggestions = [],
|
|
27
|
+
followUpSuggestions = "none",
|
|
28
|
+
followUpSuggestionLimit = 3,
|
|
26
29
|
toolEventsByMessage = {},
|
|
27
30
|
models,
|
|
28
31
|
model,
|
|
@@ -76,6 +79,18 @@ export const ChatView = ({
|
|
|
76
79
|
hasMore,
|
|
77
80
|
isLoadingHistory,
|
|
78
81
|
});
|
|
82
|
+
const latestMessage = messages[messages.length - 1];
|
|
83
|
+
const latestAssistant = latestMessage?.role === "assistant" ? latestMessage : undefined;
|
|
84
|
+
const running = status === "submitted" || status === "streaming";
|
|
85
|
+
const followUpQuestions =
|
|
86
|
+
latestAssistant && !running
|
|
87
|
+
? collectSuggestions(
|
|
88
|
+
toolEventsByMessage[latestAssistant.id],
|
|
89
|
+
latestAssistant,
|
|
90
|
+
followUpSuggestions,
|
|
91
|
+
followUpSuggestionLimit,
|
|
92
|
+
)
|
|
93
|
+
: [];
|
|
79
94
|
|
|
80
95
|
return (
|
|
81
96
|
<ChatThreadLayout
|
|
@@ -104,8 +119,9 @@ export const ChatView = ({
|
|
|
104
119
|
messages={messages}
|
|
105
120
|
status={status}
|
|
106
121
|
error={error}
|
|
107
|
-
sendMessage={sendMessage}
|
|
108
122
|
suggestions={suggestions}
|
|
123
|
+
followUpQuestions={followUpQuestions}
|
|
124
|
+
onSelectFollowUp={(text) => sendMessage({ text })}
|
|
109
125
|
toolEventsByMessage={toolEventsByMessage}
|
|
110
126
|
regenerate={regenerate}
|
|
111
127
|
isLoadingMore={isLoadingMore}
|
|
@@ -105,12 +105,13 @@ const ChartSlot = ({ chartId }: { chartId: string }) => {
|
|
|
105
105
|
// planner spec before rendering. Memoized on the resolved option and
|
|
106
106
|
// the theme, so a light/dark switch recolors the chart in place.
|
|
107
107
|
const option = useMemo(
|
|
108
|
-
() => (chart?.result ? normalizeChartOption(chart.result.option, chrome) : undefined),
|
|
108
|
+
() => (chart?.result && chrome ? normalizeChartOption(chart.result.option, chrome) : undefined),
|
|
109
109
|
[chart?.result, chrome],
|
|
110
110
|
);
|
|
111
|
+
const waiting = loading || chrome === undefined;
|
|
111
112
|
// Settled 404 (unknown / TTL-expired id) -> small "expired" notice.
|
|
112
113
|
// Hard-error / non-terminal payloads render nothing.
|
|
113
|
-
if (!option && !
|
|
114
|
+
if (!option && !waiting) {
|
|
114
115
|
return !error && chart === undefined ? <ExpiredSlot type="chart" /> : null;
|
|
115
116
|
}
|
|
116
117
|
// One frame for both states: the chart's footprint is known ahead of
|
package/src/react/markdown.tsx
CHANGED
|
@@ -7,6 +7,7 @@ import {
|
|
|
7
7
|
TableHead,
|
|
8
8
|
TableHeader,
|
|
9
9
|
TableRow,
|
|
10
|
+
Spinner,
|
|
10
11
|
Tooltip,
|
|
11
12
|
TooltipContent,
|
|
12
13
|
TooltipTrigger,
|
|
@@ -180,6 +181,20 @@ const MARKDOWN_COMPONENTS = {
|
|
|
180
181
|
},
|
|
181
182
|
};
|
|
182
183
|
|
|
184
|
+
/** Hold streaming tables until their rows and right-aligned values are complete. */
|
|
185
|
+
const STREAMING_MARKDOWN_COMPONENTS = {
|
|
186
|
+
...MARKDOWN_COMPONENTS,
|
|
187
|
+
table: () => (
|
|
188
|
+
<div
|
|
189
|
+
role="status"
|
|
190
|
+
className="my-2 flex h-10 items-center gap-2 rounded-md border border-border/70 bg-muted/30 px-3 text-xs text-muted-foreground"
|
|
191
|
+
>
|
|
192
|
+
<Spinner className="size-3" />
|
|
193
|
+
<span>Building table...</span>
|
|
194
|
+
</div>
|
|
195
|
+
),
|
|
196
|
+
};
|
|
197
|
+
|
|
183
198
|
/**
|
|
184
199
|
* Markdown component map for tool-detail copy (Genie summaries, SQL
|
|
185
200
|
* descriptions). Same cell/section overrides, but tables stay static
|
|
@@ -258,7 +273,7 @@ export const AssistantMarkdown = ({
|
|
|
258
273
|
animate?: boolean;
|
|
259
274
|
}) => (
|
|
260
275
|
<Streamdown
|
|
261
|
-
components={MARKDOWN_COMPONENTS}
|
|
276
|
+
components={animate ? STREAMING_MARKDOWN_COMPONENTS : MARKDOWN_COMPONENTS}
|
|
262
277
|
plugins={SHIKI_PLUGIN}
|
|
263
278
|
controls={false}
|
|
264
279
|
linkSafety={LINK_SAFETY}
|
|
@@ -1436,6 +1436,8 @@ export const useMastraChat = <TValues extends Record<string, unknown> = Record<s
|
|
|
1436
1436
|
regenerate,
|
|
1437
1437
|
onStop: stop,
|
|
1438
1438
|
suggestions,
|
|
1439
|
+
followUpSuggestions: mastraClient.followUpSuggestions,
|
|
1440
|
+
followUpSuggestionLimit: mastraClient.followUpSuggestionLimit,
|
|
1439
1441
|
pendingApprovalsByMessage: activeSession.pendingApprovalsByMessage,
|
|
1440
1442
|
onResolveToolApproval: handleApproval,
|
|
1441
1443
|
// Picker is opt-in: only hand ChatView the catalogue + change
|
package/src/react/suggestions.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
import {
|
|
2
|
+
FOLLOW_UP_SUGGESTIONS_PART_TYPE,
|
|
3
|
+
FollowUpSuggestionsPartDataSchema,
|
|
4
|
+
type FollowUpSuggestionMode,
|
|
5
|
+
} from "@dbx-tools/shared-mastra/wire";
|
|
6
|
+
import type { UIMessage } from "ai";
|
|
1
7
|
import type { ToolEvent, ToolProgress } from "./types.ts";
|
|
2
8
|
|
|
3
9
|
// Suggested follow-up question extraction: dedupe + cap the
|
|
@@ -9,7 +15,7 @@ import type { ToolEvent, ToolProgress } from "./types.ts";
|
|
|
9
15
|
* assistant message - several Genie queries each emitting a handful
|
|
10
16
|
* would otherwise flood the bubble.
|
|
11
17
|
*/
|
|
12
|
-
const
|
|
18
|
+
const MAX_STARTER_SUGGESTIONS = 4;
|
|
13
19
|
|
|
14
20
|
/**
|
|
15
21
|
* Token-set Jaccard threshold above which two suggestions are treated
|
|
@@ -46,14 +52,14 @@ function tokenSimilarity(a: Set<string>, b: Set<string>): number {
|
|
|
46
52
|
* its *top* question before any source contributes a second -
|
|
47
53
|
* favoring breadth over depth. Near-duplicates (see
|
|
48
54
|
* {@link SUGGESTION_SIMILARITY}) are skipped and the result is capped
|
|
49
|
-
* at
|
|
55
|
+
* at the caller's limit. Exact-duplicate questions across
|
|
50
56
|
* sources also fold via the similarity check.
|
|
51
57
|
*/
|
|
52
|
-
function pickSuggestions(lists: string[][]): string[] {
|
|
58
|
+
function pickSuggestions(lists: string[][], limit: number): string[] {
|
|
53
59
|
const accepted: string[] = [];
|
|
54
60
|
const acceptedTokens: Set<string>[] = [];
|
|
55
61
|
const consider = (question: string): void => {
|
|
56
|
-
if (accepted.length >=
|
|
62
|
+
if (accepted.length >= limit) return;
|
|
57
63
|
const tokens = suggestionTokens(question);
|
|
58
64
|
if (tokens.size === 0) return;
|
|
59
65
|
const isDuplicate = acceptedTokens.some(
|
|
@@ -65,7 +71,7 @@ function pickSuggestions(lists: string[][]): string[] {
|
|
|
65
71
|
};
|
|
66
72
|
|
|
67
73
|
const maxLen = lists.reduce((m, l) => Math.max(m, l.length), 0);
|
|
68
|
-
for (let i = 0; i < maxLen && accepted.length <
|
|
74
|
+
for (let i = 0; i < maxLen && accepted.length < limit; i++) {
|
|
69
75
|
for (const list of lists) {
|
|
70
76
|
const question = list[i];
|
|
71
77
|
if (question !== undefined) consider(question);
|
|
@@ -76,34 +82,49 @@ function pickSuggestions(lists: string[][]): string[] {
|
|
|
76
82
|
|
|
77
83
|
/**
|
|
78
84
|
* Dedupe + cap a single flat list of suggested questions (e.g. the
|
|
79
|
-
* initial Genie sample questions).
|
|
80
|
-
*
|
|
81
|
-
* and follow-up suggestions behave identically.
|
|
85
|
+
* initial Genie sample questions). Uses the same similarity dedupe as
|
|
86
|
+
* the follow-up extractor.
|
|
82
87
|
*/
|
|
83
88
|
export const dedupeSuggestions = (questions: string[] | undefined): string[] =>
|
|
84
|
-
questions && questions.length > 0 ? pickSuggestions([questions]) : [];
|
|
89
|
+
questions && questions.length > 0 ? pickSuggestions([questions], MAX_STARTER_SUGGESTIONS) : [];
|
|
85
90
|
|
|
86
91
|
/**
|
|
87
92
|
* Build the short, deduped list of suggested follow-up questions for
|
|
88
|
-
* an assistant message. Within each tool event the
|
|
89
|
-
*
|
|
90
|
-
* the
|
|
91
|
-
* {@link pickSuggestions}.
|
|
93
|
+
* an assistant message. Within each Genie tool event the last suggested
|
|
94
|
+
* progress entry wins; persisted generic data parts are merged according to
|
|
95
|
+
* the server-published source policy.
|
|
92
96
|
*/
|
|
93
|
-
export const collectSuggestions = (
|
|
94
|
-
|
|
95
|
-
|
|
97
|
+
export const collectSuggestions = (
|
|
98
|
+
events: ToolEvent[] | undefined,
|
|
99
|
+
message: UIMessage,
|
|
100
|
+
mode: FollowUpSuggestionMode,
|
|
101
|
+
limit: number,
|
|
102
|
+
): string[] => {
|
|
103
|
+
if (mode === "none") return [];
|
|
96
104
|
// One ordered question list per event that emitted any.
|
|
97
105
|
const lists: string[][] = [];
|
|
98
|
-
|
|
99
|
-
const
|
|
100
|
-
.
|
|
101
|
-
|
|
102
|
-
(
|
|
103
|
-
p
|
|
104
|
-
|
|
105
|
-
|
|
106
|
+
if (mode === "genie" || mode === "auto") {
|
|
107
|
+
for (const event of events ?? []) {
|
|
108
|
+
const last = [...(event.progress ?? [])]
|
|
109
|
+
.reverse()
|
|
110
|
+
.find(
|
|
111
|
+
(p): p is Extract<ToolProgress, { type: "suggested_questions" }> =>
|
|
112
|
+
p.type === "suggested_questions",
|
|
113
|
+
);
|
|
114
|
+
if (last && last.questions.length > 0) lists.push(last.questions);
|
|
115
|
+
}
|
|
106
116
|
}
|
|
107
|
-
|
|
108
|
-
|
|
117
|
+
for (const part of message.parts) {
|
|
118
|
+
if (part.type !== FOLLOW_UP_SUGGESTIONS_PART_TYPE) continue;
|
|
119
|
+
const parsed = FollowUpSuggestionsPartDataSchema.safeParse(part.data);
|
|
120
|
+
if (!parsed.success) continue;
|
|
121
|
+
if (
|
|
122
|
+
(mode === "generated" && parsed.data.source !== "model") ||
|
|
123
|
+
(mode === "genie" && parsed.data.source !== "genie")
|
|
124
|
+
) {
|
|
125
|
+
continue;
|
|
126
|
+
}
|
|
127
|
+
lists.push(parsed.data.questions);
|
|
128
|
+
}
|
|
129
|
+
return pickSuggestions(lists, Math.min(10, Math.max(1, limit)));
|
|
109
130
|
};
|
package/src/react/types.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { ToolProgressEvent } from "@dbx-tools/shared-mastra/wire";
|
|
1
|
+
import type { FollowUpSuggestionMode, ToolProgressEvent } from "@dbx-tools/shared-mastra/wire";
|
|
2
2
|
import type { ReasoningEffort, ServingEndpointSummary } from "@dbx-tools/shared-model";
|
|
3
3
|
import type { FileUIPart, UIMessage } from "ai";
|
|
4
4
|
import type { ReactNode } from "react";
|
|
@@ -171,6 +171,10 @@ export type ChatViewProps = {
|
|
|
171
171
|
* questions when the caller doesn't pass an explicit list.
|
|
172
172
|
*/
|
|
173
173
|
suggestions?: string[];
|
|
174
|
+
/** Enabled post-turn follow-up sources published by the server. */
|
|
175
|
+
followUpSuggestions?: FollowUpSuggestionMode;
|
|
176
|
+
/** Maximum post-turn follow-up questions shown under one response. */
|
|
177
|
+
followUpSuggestionLimit?: number;
|
|
174
178
|
toolEventsByMessage?: Record<string, ToolEvent[]>;
|
|
175
179
|
/** Available model endpoints. Pass an empty array (or omit) to hide the picker. */
|
|
176
180
|
models?: ChatModelOption[];
|
|
@@ -110,12 +110,15 @@ const THEME_ATTRIBUTES = ["class", "style", "data-theme", "data-brand"];
|
|
|
110
110
|
* structurally before being stored, so an unrelated attribute change
|
|
111
111
|
* does not hand callers a new object and re-render every chart.
|
|
112
112
|
*
|
|
113
|
-
* Returns
|
|
114
|
-
*
|
|
115
|
-
*
|
|
113
|
+
* Returns `undefined` in the browser until the chart element is mounted and its
|
|
114
|
+
* inherited tokens have been read. This prevents Echarts from drawing once
|
|
115
|
+
* with fallback colors and immediately drawing again with the resolved theme.
|
|
116
|
+
* Server rendering retains {@link LIGHT_CHART_CHROME}.
|
|
116
117
|
*/
|
|
117
|
-
export function useChartChrome(ref: RefObject<Element | null>): ChartChrome {
|
|
118
|
-
const [chrome, setChrome] = useState<ChartChrome>(
|
|
118
|
+
export function useChartChrome(ref: RefObject<Element | null>): ChartChrome | undefined {
|
|
119
|
+
const [chrome, setChrome] = useState<ChartChrome | undefined>(() =>
|
|
120
|
+
typeof window === "undefined" ? LIGHT_CHART_CHROME : undefined,
|
|
121
|
+
);
|
|
119
122
|
useEffect(() => {
|
|
120
123
|
const read = () => {
|
|
121
124
|
const next = resolveChartChrome(ref.current);
|
|
@@ -28,6 +28,7 @@ export class GenieChatClient extends MastraPluginClient {
|
|
|
28
28
|
override readonly feedbackMode = "none";
|
|
29
29
|
override readonly threadRename = false;
|
|
30
30
|
override readonly attachments = false;
|
|
31
|
+
override readonly followUpSuggestions: "none" | "genie";
|
|
31
32
|
/** Thread ids known to be Genie conversations (listed, loaded, or announced). */
|
|
32
33
|
readonly #conversations = new Set<string>();
|
|
33
34
|
/** Transcript message id to Genie message id, for regenerate cleanup. */
|
|
@@ -36,6 +37,10 @@ export class GenieChatClient extends MastraPluginClient {
|
|
|
36
37
|
constructor(config: MastraClientConfig, alias: string) {
|
|
37
38
|
super(config);
|
|
38
39
|
this.alias = alias;
|
|
40
|
+
this.followUpSuggestions =
|
|
41
|
+
config.followUpSuggestions === "genie" || config.followUpSuggestions === "auto"
|
|
42
|
+
? "genie"
|
|
43
|
+
: "none";
|
|
39
44
|
}
|
|
40
45
|
|
|
41
46
|
#url(...parts: string[]): string {
|
|
@@ -91,6 +91,10 @@ export class MastraPluginClient extends MastraClient {
|
|
|
91
91
|
readonly chatAlwaysAvailable: boolean;
|
|
92
92
|
/** Whether turns continue server-side after the initiating stream disconnects. */
|
|
93
93
|
readonly backgroundTurns: boolean;
|
|
94
|
+
/** Enabled post-turn follow-up sources. */
|
|
95
|
+
readonly followUpSuggestions: "none" | "genie" | "generated" | "auto";
|
|
96
|
+
/** Maximum follow-up questions rendered after one turn. */
|
|
97
|
+
readonly followUpSuggestionLimit: number;
|
|
94
98
|
/** Whether {@link renameThread} is supported by this client's thread store. */
|
|
95
99
|
readonly threadRename: boolean = true;
|
|
96
100
|
/** Whether a chat may attach files to a turn. */
|
|
@@ -109,6 +113,8 @@ export class MastraPluginClient extends MastraClient {
|
|
|
109
113
|
this.feedbackMode = config.feedbackMode ?? "none";
|
|
110
114
|
this.chatAlwaysAvailable = config.chatAlwaysAvailable ?? false;
|
|
111
115
|
this.backgroundTurns = config.backgroundTurns ?? false;
|
|
116
|
+
this.followUpSuggestions = config.followUpSuggestions ?? "none";
|
|
117
|
+
this.followUpSuggestionLimit = config.followUpSuggestionLimit ?? 3;
|
|
112
118
|
}
|
|
113
119
|
|
|
114
120
|
/**
|