@dbx-tools/ui-mastra 0.3.17 → 0.3.18

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 CHANGED
@@ -20,7 +20,9 @@ Key features:
20
20
  routes for history, threads, model lists, suggestions, feedback, charts, and
21
21
  statement data.
22
22
  - Tool-approval support for suspended Mastra `requireApproval` calls, including
23
- direct resumed-stream handling.
23
+ direct resumed-stream handling. An email-shaped input (the `send_email` tool)
24
+ renders as a formatted To / Cc / Subject / Markdown-body preview rather than
25
+ raw JSON.
24
26
  - Inline embed rendering for `[chart:<id>]` and `[data:<id>]` markers produced by
25
27
  the server plugin.
26
28
  - Conversation sidebar with new, select, rename, delete, active-thread, and
package/package.json CHANGED
@@ -26,19 +26,19 @@
26
26
  "shiki": "^3.0.0",
27
27
  "sql-formatter": "^15.6.9",
28
28
  "streamdown": "^2.5.0",
29
- "@dbx-tools/shared-core": "0.3.17",
30
- "@dbx-tools/shared-genie": "0.3.17",
31
- "@dbx-tools/shared-model": "0.3.17",
32
- "@dbx-tools/shared-mastra": "0.3.17",
33
- "@dbx-tools/ui-appkit": "0.3.17",
34
- "@dbx-tools/ui-branding": "0.3.17"
29
+ "@dbx-tools/shared-model": "0.3.18",
30
+ "@dbx-tools/shared-core": "0.3.18",
31
+ "@dbx-tools/ui-appkit": "0.3.18",
32
+ "@dbx-tools/shared-mastra": "0.3.18",
33
+ "@dbx-tools/shared-genie": "0.3.18",
34
+ "@dbx-tools/ui-branding": "0.3.18"
35
35
  },
36
36
  "main": "index.ts",
37
37
  "license": "UNLICENSED",
38
38
  "publishConfig": {
39
39
  "access": "public"
40
40
  },
41
- "version": "0.3.17",
41
+ "version": "0.3.18",
42
42
  "types": "index.ts",
43
43
  "type": "module",
44
44
  "exports": {
@@ -25,6 +25,7 @@ import {
25
25
  } from "lucide-react";
26
26
  import { useState } from "react";
27
27
  import { MarkdownWithEmbeds } from "./embed-slots";
28
+ import { ToolMarkdown } from "./markdown";
28
29
  import { ExportMenu } from "./export-menu";
29
30
  import { FeedbackControls } from "./feedback-controls";
30
31
  import { SuggestionPills } from "./suggestion-pills";
@@ -95,6 +96,74 @@ const RoleAvatar = ({ role }: { role: UIMessage["role"] }) => (
95
96
  </Avatar>
96
97
  );
97
98
 
99
+ /** A tool-call input shaped like an outbound email (the `send_email` tool). */
100
+ type EmailApprovalInput = {
101
+ to?: string | string[];
102
+ cc?: string | string[];
103
+ subject?: string;
104
+ body?: string;
105
+ };
106
+
107
+ /** Comma-join one-or-many addresses; empty string when none. */
108
+ const joinAddresses = (value: string | string[] | undefined): string =>
109
+ (Array.isArray(value) ? value : value ? [value] : []).filter(Boolean).join(", ");
110
+
111
+ /**
112
+ * Recognize an approval input that is an email draft (the `send_email` tool),
113
+ * so its card can render a formatted preview instead of raw JSON. Structural,
114
+ * not name-based, so a renamed email tool still previews.
115
+ */
116
+ const asEmailInput = (input: unknown): EmailApprovalInput | null => {
117
+ if (!input || typeof input !== "object") return null;
118
+ const e = input as EmailApprovalInput;
119
+ const hasBody = typeof e.body === "string";
120
+ const hasSubject = typeof e.subject === "string";
121
+ const hasTo = typeof e.to === "string" || Array.isArray(e.to);
122
+ return hasBody && (hasSubject || hasTo) ? e : null;
123
+ };
124
+
125
+ /**
126
+ * Labeled To / Cc / Subject / Body preview of an email awaiting approval, with
127
+ * the body rendered as Markdown (headings, tables, lists) rather than the raw
128
+ * source. Mirrors `ui-email`'s `EmailPreview` without a dependency on that
129
+ * optional package, so the `send_email` approval card reads like an email
130
+ * instead of a JSON blob.
131
+ */
132
+ const EmailApprovalPreview = ({ email }: { email: EmailApprovalInput }) => {
133
+ const to = joinAddresses(email.to);
134
+ const cc = joinAddresses(email.cc);
135
+ return (
136
+ <dl className="space-y-1 rounded bg-background/40 p-2 text-[11px]">
137
+ {to && (
138
+ <div className="flex gap-2">
139
+ <dt className="w-14 shrink-0 text-muted-foreground">To</dt>
140
+ <dd className="min-w-0 flex-1 truncate">{to}</dd>
141
+ </div>
142
+ )}
143
+ {cc && (
144
+ <div className="flex gap-2">
145
+ <dt className="w-14 shrink-0 text-muted-foreground">Cc</dt>
146
+ <dd className="min-w-0 flex-1 truncate">{cc}</dd>
147
+ </div>
148
+ )}
149
+ {email.subject && (
150
+ <div className="flex gap-2">
151
+ <dt className="w-14 shrink-0 text-muted-foreground">Subject</dt>
152
+ <dd className="min-w-0 flex-1 truncate font-medium">{email.subject}</dd>
153
+ </div>
154
+ )}
155
+ {email.body && (
156
+ <div className="flex gap-2">
157
+ <dt className="w-14 shrink-0 text-muted-foreground">Body</dt>
158
+ <dd className="min-w-0 flex-1 break-words">
159
+ <ToolMarkdown>{email.body}</ToolMarkdown>
160
+ </dd>
161
+ </div>
162
+ )}
163
+ </dl>
164
+ );
165
+ };
166
+
98
167
  /**
99
168
  * Inline approval prompt rendered when a `requireApproval: true` tool is
100
169
  * paused in the agent loop. Approve / deny flows through
@@ -144,9 +213,13 @@ const ToolApprovalCard = ({
144
213
  <MessageSquareIcon className="size-3.5" />
145
214
  <span>Approval needed: {humanizeToolName(toolName)}</span>
146
215
  </div>
147
- <pre className="overflow-x-auto rounded bg-background/40 p-2 text-[11px]">
148
- {JSON.stringify(input, null, 2)}
149
- </pre>
216
+ {asEmailInput(input) ? (
217
+ <EmailApprovalPreview email={asEmailInput(input)!} />
218
+ ) : (
219
+ <pre className="overflow-x-auto rounded bg-background/40 p-2 text-[11px]">
220
+ {JSON.stringify(input, null, 2)}
221
+ </pre>
222
+ )}
150
223
  {expired ? (
151
224
  <div className="mt-3 flex items-center gap-1.5 text-xs font-medium text-muted-foreground">
152
225
  <MessageSquareIcon className="size-3.5" />