@agent-native/toolkit 0.213.1 → 0.214.0

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.
Files changed (198) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/components.js +10 -4
  4. package/dist/app/agentkit/react/components.js.map +1 -1
  5. package/dist/app/agentkit/react/context.d.ts +13 -1
  6. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/context.js +3 -1
  8. package/dist/app/agentkit/react/context.js.map +1 -1
  9. package/dist/app/agentkit/react/index.d.ts +1 -0
  10. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/index.js +1 -0
  12. package/dist/app/agentkit/react/index.js.map +1 -1
  13. package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
  14. package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
  15. package/dist/app/agentkit/react/message-content-order.js +173 -0
  16. package/dist/app/agentkit/react/message-content-order.js.map +1 -0
  17. package/dist/app/agentkit/react/root.d.ts +3 -1
  18. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  19. package/dist/app/agentkit/react/root.js +2 -2
  20. package/dist/app/agentkit/react/root.js.map +1 -1
  21. package/dist/app/agentkit/react/styles.css +2 -2
  22. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  23. package/dist/app/chat/AgentKitAssistantChat.js +7 -2
  24. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  25. package/dist/app/chat/AgentPanel.d.ts +14 -2
  26. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  27. package/dist/app/chat/AgentPanel.js +22 -5
  28. package/dist/app/chat/AgentPanel.js.map +1 -1
  29. package/dist/app/chat/chat/surface-types.d.ts +8 -0
  30. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  31. package/dist/app/chat/chat/surface-types.js.map +1 -1
  32. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  33. package/dist/app/chat/chat/widgets/DataChartRenderer.js +127 -30
  34. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  35. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
  36. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  37. package/dist/app/chat/chat/widgets/DataChartWidget.js +14 -9
  38. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  39. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
  40. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  41. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
  42. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
  43. package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
  44. package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
  45. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
  46. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
  47. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +131 -0
  48. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
  49. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  50. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  51. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  52. package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
  53. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  54. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
  55. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
  56. package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
  57. package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
  58. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  59. package/dist/app/i18n/catalogs/ar-SA.js +8 -0
  60. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  61. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  62. package/dist/app/i18n/catalogs/de-DE.js +8 -0
  63. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  64. package/dist/app/i18n/catalogs/en-US.d.ts +8 -0
  65. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  66. package/dist/app/i18n/catalogs/en-US.js +8 -0
  67. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  68. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  69. package/dist/app/i18n/catalogs/es-ES.js +8 -0
  70. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  71. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  72. package/dist/app/i18n/catalogs/fr-FR.js +8 -0
  73. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  74. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  75. package/dist/app/i18n/catalogs/hi-IN.js +8 -0
  76. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  77. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  78. package/dist/app/i18n/catalogs/ja-JP.js +8 -0
  79. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  80. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/ko-KR.js +8 -0
  82. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  83. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/pt-BR.js +8 -0
  85. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  86. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/zh-CN.js +8 -0
  88. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  89. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/zh-TW.js +8 -0
  91. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  92. package/dist/app/providers/AppProviders.d.ts +2 -1
  93. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  94. package/dist/app/providers/AppProviders.js +4 -3
  95. package/dist/app/providers/AppProviders.js.map +1 -1
  96. package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
  97. package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
  98. package/dist/app/providers/ThemeFamilySync.js +6 -0
  99. package/dist/app/providers/ThemeFamilySync.js.map +1 -0
  100. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  101. package/dist/app/settings/account/PreferencesSettings.js +2 -1
  102. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  103. package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
  104. package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
  105. package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
  106. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
  107. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  108. package/dist/app/settings/account/search-entries.js +6 -0
  109. package/dist/app/settings/account/search-entries.js.map +1 -1
  110. package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
  111. package/dist/app/shared/DefaultSpinner.js +4 -5
  112. package/dist/app/shared/DefaultSpinner.js.map +1 -1
  113. package/dist/app/shared/ErrorBoundary.d.ts +1 -0
  114. package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
  115. package/dist/app/shared/ErrorBoundary.js +27 -12
  116. package/dist/app/shared/ErrorBoundary.js.map +1 -1
  117. package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
  118. package/dist/app/shared/PoweredByBadge.js +8 -49
  119. package/dist/app/shared/PoweredByBadge.js.map +1 -1
  120. package/dist/app/styles/agent-conversation.css +0 -23
  121. package/dist/app/styles/agent-native.css +1 -1
  122. package/dist/app/styles/theme-families.css +66 -0
  123. package/dist/composer/ComposerContextBanner.d.ts +22 -0
  124. package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
  125. package/dist/composer/ComposerContextBanner.js +9 -0
  126. package/dist/composer/ComposerContextBanner.js.map +1 -0
  127. package/dist/composer/ComposerModePicker.d.ts +26 -0
  128. package/dist/composer/ComposerModePicker.d.ts.map +1 -0
  129. package/dist/composer/ComposerModePicker.js +14 -0
  130. package/dist/composer/ComposerModePicker.js.map +1 -0
  131. package/dist/composer/index.d.ts +2 -0
  132. package/dist/composer/index.d.ts.map +1 -1
  133. package/dist/composer/index.js +2 -0
  134. package/dist/composer/index.js.map +1 -1
  135. package/dist/styles.css +1 -0
  136. package/package.json +3 -3
  137. package/src/app/agentkit/react/components.spec.tsx +12 -0
  138. package/src/app/agentkit/react/components.tsx +41 -22
  139. package/src/app/agentkit/react/context.tsx +17 -0
  140. package/src/app/agentkit/react/index.ts +1 -0
  141. package/src/app/agentkit/react/lifecycle.spec.tsx +116 -1
  142. package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
  143. package/src/app/agentkit/react/message-content-order.ts +246 -0
  144. package/src/app/agentkit/react/root.tsx +4 -0
  145. package/src/app/agentkit/react/styles.css +2 -2
  146. package/src/app/chat/AgentKitAssistantChat.spec.tsx +19 -0
  147. package/src/app/chat/AgentKitAssistantChat.tsx +10 -1
  148. package/src/app/chat/AgentPanel.header.spec.ts +72 -14
  149. package/src/app/chat/AgentPanel.tsx +97 -53
  150. package/src/app/chat/chat/surface-types.ts +8 -0
  151. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +217 -0
  152. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +306 -68
  153. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +329 -0
  154. package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
  155. package/src/app/chat/chat/widgets/DataChartWidget.tsx +48 -33
  156. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
  157. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
  158. package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
  159. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +301 -0
  160. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
  161. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
  162. package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
  163. package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
  164. package/src/app/i18n/catalogs/ar-SA.ts +9 -0
  165. package/src/app/i18n/catalogs/de-DE.ts +9 -0
  166. package/src/app/i18n/catalogs/en-US.ts +9 -0
  167. package/src/app/i18n/catalogs/es-ES.ts +10 -0
  168. package/src/app/i18n/catalogs/fr-FR.ts +10 -0
  169. package/src/app/i18n/catalogs/hi-IN.ts +9 -0
  170. package/src/app/i18n/catalogs/ja-JP.ts +9 -0
  171. package/src/app/i18n/catalogs/ko-KR.ts +9 -0
  172. package/src/app/i18n/catalogs/pt-BR.ts +9 -0
  173. package/src/app/i18n/catalogs/zh-CN.ts +8 -0
  174. package/src/app/i18n/catalogs/zh-TW.ts +9 -0
  175. package/src/app/providers/AppProviders.spec.tsx +71 -0
  176. package/src/app/providers/AppProviders.tsx +10 -0
  177. package/src/app/providers/ThemeFamilySync.tsx +6 -0
  178. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
  179. package/src/app/settings/account/PreferencesSettings.tsx +8 -0
  180. package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
  181. package/src/app/settings/account/search-entries.ts +6 -0
  182. package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
  183. package/src/app/shared/DefaultSpinner.tsx +4 -5
  184. package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
  185. package/src/app/shared/ErrorBoundary.tsx +37 -12
  186. package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
  187. package/src/app/shared/PoweredByBadge.tsx +8 -50
  188. package/src/app/styles/agent-conversation.css +0 -23
  189. package/src/app/styles/agent-native.css +1 -1
  190. package/src/app/styles/agent-native.spec.ts +18 -0
  191. package/src/app/styles/theme-families.css +66 -0
  192. package/src/app/styles/theme-families.spec.ts +29 -0
  193. package/src/composer/ComposerContextBanner.spec.tsx +74 -0
  194. package/src/composer/ComposerContextBanner.tsx +72 -0
  195. package/src/composer/ComposerModePicker.spec.tsx +101 -0
  196. package/src/composer/ComposerModePicker.tsx +98 -0
  197. package/src/composer/index.ts +9 -0
  198. package/src/styles.css +1 -0
@@ -1,18 +1,20 @@
1
- import { requestAgentSidebarOpen } from "@agent-native/core/client/agent-chat";
2
- import { startAgentChatViewTransition } from "@agent-native/core/client/agent-chat";
3
- import { appPath } from "@agent-native/core/client/api-path";
4
1
  import {
5
2
  DEFAULT_LOCALE,
6
3
  useOptionalLocale,
7
4
  useT,
8
5
  } from "@agent-native/core/client/i18n";
9
6
  import { cn } from "@agent-native/toolkit/utils";
10
- import { IconDownload, IconExternalLink, IconTable } from "@tabler/icons-react";
7
+ import { IconDownload, IconTable } from "@tabler/icons-react";
11
8
 
12
9
  import { downloadFile, toCSVTable } from "../../../db-admin/export-utils.js";
13
- import type { DataTableWidget as DataTableWidgetData } from "./data-widget-types.js";
14
-
15
- const SAFE_ACTION_PROTOCOLS = new Set(["http:", "https:", "mailto:"]);
10
+ import type {
11
+ DataTableWidget as DataTableWidgetData,
12
+ DataWidgetDisplay,
13
+ } from "./data-widget-types.js";
14
+ import {
15
+ DataWidgetDisplayControls,
16
+ DataWidgetSourceChips,
17
+ } from "./DataWidgetDisplayControls.js";
16
18
 
17
19
  function formatCell(
18
20
  value: unknown,
@@ -42,60 +44,6 @@ function formatCell(
42
44
  return text;
43
45
  }
44
46
 
45
- function normalizeActionHref(href: string): string | null {
46
- const trimmed = href.trim();
47
- if (!trimmed) return null;
48
- if (trimmed.startsWith("/") && !trimmed.startsWith("//")) {
49
- return appPath(trimmed);
50
- }
51
-
52
- const base =
53
- typeof window === "undefined"
54
- ? "http://agent-native.local/"
55
- : window.location.href;
56
- try {
57
- const url = new URL(trimmed, base);
58
- if (!SAFE_ACTION_PROTOCOLS.has(url.protocol)) return null;
59
-
60
- if (
61
- typeof window !== "undefined" &&
62
- url.origin === window.location.origin
63
- ) {
64
- return appPath(`${url.pathname}${url.search}${url.hash}`);
65
- }
66
-
67
- return url.toString();
68
- } catch {
69
- return null;
70
- }
71
- }
72
-
73
- function isSameAppHref(href: string): boolean {
74
- if (typeof window === "undefined") return false;
75
- try {
76
- const url = new URL(href, window.location.href);
77
- return (
78
- url.origin === window.location.origin && url.pathname.startsWith("/")
79
- );
80
- } catch {
81
- return false;
82
- }
83
- }
84
-
85
- function navigateSameAppHref(href: string): void {
86
- if (typeof window === "undefined") return;
87
- const url = new URL(href, window.location.href);
88
- startAgentChatViewTransition(() => {
89
- requestAgentSidebarOpen();
90
- window.history.pushState(
91
- window.history.state,
92
- "",
93
- `${url.pathname}${url.search}${url.hash}`,
94
- );
95
- window.dispatchEvent(new PopStateEvent("popstate"));
96
- });
97
- }
98
-
99
47
  function slugifyFilenamePart(value: string | undefined): string {
100
48
  const slug = (value ?? "data-table")
101
49
  .trim()
@@ -108,14 +56,20 @@ function slugifyFilenamePart(value: string | undefined): string {
108
56
  export function DataTableWidget({
109
57
  table,
110
58
  action,
59
+ display,
111
60
  }: {
112
61
  table: DataTableWidgetData;
113
62
  action?: { label: string; href: string };
63
+ display?: DataWidgetDisplay;
114
64
  }) {
115
65
  const t = useT();
116
66
  const locale = useOptionalLocale()?.locale ?? DEFAULT_LOCALE;
117
67
  const rows = table.rows ?? [];
118
- const actionHref = action ? normalizeActionHref(action.href) : null;
68
+ const resolvedDisplay = display
69
+ ? { ...display, primaryAction: display.primaryAction ?? action }
70
+ : action
71
+ ? { primaryAction: action }
72
+ : undefined;
119
73
  const canExportCsv = table.columns.length > 0;
120
74
  const exportCsv = () => {
121
75
  if (!canExportCsv) return;
@@ -151,30 +105,11 @@ export function DataTableWidget({
151
105
  {table.truncated ? ` ${t("agentChat.widget.sampled")}` : ""}
152
106
  </div>
153
107
  </div>
154
- {action && actionHref ? (
155
- <a
156
- href={actionHref}
157
- onClick={(event) => {
158
- if (
159
- event.defaultPrevented ||
160
- event.button !== 0 ||
161
- event.metaKey ||
162
- event.ctrlKey ||
163
- event.shiftKey ||
164
- event.altKey ||
165
- !isSameAppHref(actionHref)
166
- ) {
167
- return;
168
- }
169
- event.preventDefault();
170
- navigateSameAppHref(actionHref);
171
- }}
172
- className="inline-flex shrink-0 items-center gap-1 rounded-md border border-border px-2 py-1 text-[11px] font-medium text-foreground no-underline hover:bg-muted hover:no-underline"
173
- >
174
- {action.label}
175
- <IconExternalLink className="h-3 w-3" />
176
- </a>
177
- ) : null}
108
+ <DataWidgetDisplayControls
109
+ display={resolvedDisplay}
110
+ variant="secondary"
111
+ kind="table"
112
+ />
178
113
  <button
179
114
  type="button"
180
115
  onClick={exportCsv}
@@ -240,6 +175,7 @@ export function DataTableWidget({
240
175
  </tbody>
241
176
  </table>
242
177
  </div>
178
+ <DataWidgetSourceChips sources={display?.sources ?? []} />
243
179
  </div>
244
180
  );
245
181
  }
@@ -0,0 +1,301 @@
1
+ import {
2
+ requestAgentSidebarOpen,
3
+ startAgentChatViewTransition,
4
+ } from "@agent-native/core/client/agent-chat";
5
+ import { appPath } from "@agent-native/core/client/api-path";
6
+ import { useT } from "@agent-native/core/client/i18n";
7
+ import { writeClipboardText } from "@agent-native/toolkit/clipboard";
8
+ import {
9
+ DropdownMenu,
10
+ DropdownMenuContent,
11
+ DropdownMenuItem,
12
+ DropdownMenuTrigger,
13
+ } from "@agent-native/toolkit/ui/dropdown-menu";
14
+ import {
15
+ IconCamera,
16
+ IconCompass,
17
+ IconCopy,
18
+ IconDotsVertical,
19
+ IconExternalLink,
20
+ IconNotebook,
21
+ } from "@tabler/icons-react";
22
+ import { forwardRef } from "react";
23
+
24
+ import { toast } from "../../../../hooks/use-toast.js";
25
+ import type {
26
+ DataWidgetAction,
27
+ DataWidgetActionIcon,
28
+ DataWidgetDataSource,
29
+ DataWidgetDisplay,
30
+ DataWidgetDisplayAction,
31
+ } from "./data-widget-types.js";
32
+
33
+ const SAFE_ACTION_PROTOCOLS = new Set(["http:", "https:", "mailto:"]);
34
+
35
+ function normalizeActionHref(href: string): string | null {
36
+ const trimmed = href.trim();
37
+ if (!trimmed) return null;
38
+
39
+ const hasWindow = typeof window !== "undefined";
40
+ const isRelative =
41
+ !/^[a-z][a-z\d+.-]*:/i.test(trimmed) && !trimmed.startsWith("//");
42
+ const base = hasWindow ? window.location.href : "http://agent-native.local/";
43
+ let url: URL;
44
+ try {
45
+ url = new URL(trimmed, base);
46
+ } catch (error) {
47
+ if (error instanceof TypeError) return null;
48
+ throw error;
49
+ }
50
+
51
+ if (!SAFE_ACTION_PROTOCOLS.has(url.protocol)) return null;
52
+
53
+ if (!hasWindow && isRelative) {
54
+ return appPath(`${url.pathname}${url.search}${url.hash}`);
55
+ }
56
+
57
+ if (hasWindow && url.origin === window.location.origin) {
58
+ return appPath(`${url.pathname}${url.search}${url.hash}`);
59
+ }
60
+
61
+ return url.toString();
62
+ }
63
+
64
+ function navigateSameAppHref(href: string): void {
65
+ if (typeof window === "undefined") return;
66
+ const url = new URL(href, window.location.href);
67
+ startAgentChatViewTransition(() => {
68
+ requestAgentSidebarOpen();
69
+ window.history.pushState(
70
+ window.history.state,
71
+ "",
72
+ `${url.pathname}${url.search}${url.hash}`,
73
+ );
74
+ window.dispatchEvent(new PopStateEvent("popstate"));
75
+ });
76
+ }
77
+
78
+ const WidgetActionLink = forwardRef<
79
+ HTMLAnchorElement,
80
+ {
81
+ href: string;
82
+ label: string;
83
+ className: string;
84
+ icon?: DataWidgetActionIcon;
85
+ }
86
+ >(function WidgetActionLink({ href, label, className, icon }, ref) {
87
+ return (
88
+ <a
89
+ ref={ref}
90
+ href={href}
91
+ onClick={(event) => {
92
+ if (
93
+ event.defaultPrevented ||
94
+ event.button !== 0 ||
95
+ event.metaKey ||
96
+ event.ctrlKey ||
97
+ event.shiftKey ||
98
+ event.altKey ||
99
+ typeof window === "undefined"
100
+ ) {
101
+ return;
102
+ }
103
+
104
+ const url = new URL(href, window.location.href);
105
+ if (url.origin !== window.location.origin) return;
106
+
107
+ event.preventDefault();
108
+ navigateSameAppHref(href);
109
+ }}
110
+ className={className}
111
+ >
112
+ <span className="truncate">{label}</span>
113
+ {icon === "explore" ? (
114
+ <IconCompass aria-hidden="true" className="size-3.5 shrink-0" />
115
+ ) : icon === "camera" ? (
116
+ <IconCamera aria-hidden="true" className="size-3.5 shrink-0" />
117
+ ) : icon === "notebook" ? (
118
+ <IconNotebook aria-hidden="true" className="size-3.5 shrink-0" />
119
+ ) : (
120
+ <IconExternalLink aria-hidden="true" className="size-3.5 shrink-0" />
121
+ )}
122
+ </a>
123
+ );
124
+ });
125
+
126
+ type PreparedDisplayAction =
127
+ | {
128
+ type: "link";
129
+ action: Extract<DataWidgetDisplayAction, { type: "link" }>;
130
+ href: string;
131
+ key: string;
132
+ }
133
+ | {
134
+ type: "copy";
135
+ action: Extract<DataWidgetDisplayAction, { type: "copy" }>;
136
+ href: null;
137
+ key: string;
138
+ };
139
+
140
+ export function DataWidgetDisplayControls({
141
+ display,
142
+ chartActions = [],
143
+ variant = "primary",
144
+ kind = "chart",
145
+ }: {
146
+ display?: DataWidgetDisplay;
147
+ chartActions?: DataWidgetAction[];
148
+ variant?: "primary" | "secondary";
149
+ kind?: "chart" | "table";
150
+ }) {
151
+ const t = useT();
152
+ const copyText = async (text: string) => {
153
+ let copied = false;
154
+ try {
155
+ copied = await writeClipboardText(text);
156
+ } catch {
157
+ copied = false;
158
+ }
159
+
160
+ toast({
161
+ title: copied
162
+ ? t("agentChat.common.copied")
163
+ : t("agentChat.recovery.copyFailed"),
164
+ });
165
+ };
166
+ const displayActions: DataWidgetDisplayAction[] = [
167
+ ...(display?.actions ?? []),
168
+ ...chartActions.map((action) => ({ ...action, type: "link" as const })),
169
+ ];
170
+ const primary = display?.primaryAction
171
+ ? {
172
+ ...display.primaryAction,
173
+ href: normalizeActionHref(display.primaryAction.href),
174
+ }
175
+ : null;
176
+ const actions: PreparedDisplayAction[] = [];
177
+ for (const [index, action] of displayActions.entries()) {
178
+ if (action.type === "link") {
179
+ const href = normalizeActionHref(action.href);
180
+ if (href) {
181
+ actions.push({
182
+ type: "link",
183
+ action,
184
+ href,
185
+ key: action.id ?? `link-${index}`,
186
+ });
187
+ }
188
+ continue;
189
+ }
190
+ actions.push({
191
+ type: "copy",
192
+ action,
193
+ href: null,
194
+ key: action.id ?? `copy-${index}`,
195
+ });
196
+ }
197
+ const actionsLabel =
198
+ kind === "table"
199
+ ? t("agentChat.widget.tableActions", { defaultValue: "Table actions" })
200
+ : t("agentChat.widget.chartActions", { defaultValue: "Chart actions" });
201
+
202
+ return (
203
+ <div className="flex shrink-0 items-center gap-1.5">
204
+ {primary?.href ? (
205
+ <WidgetActionLink
206
+ href={primary.href}
207
+ label={primary.label}
208
+ icon={primary.icon}
209
+ className={
210
+ variant === "secondary"
211
+ ? "inline-flex max-w-44 items-center gap-1.5 truncate rounded-md border border-border px-2 py-1 text-[11px] font-medium text-foreground no-underline hover:bg-muted hover:no-underline"
212
+ : "inline-flex max-w-44 items-center gap-1.5 truncate rounded-md bg-primary px-2 py-1 text-[11px] font-medium text-primary-foreground no-underline hover:bg-primary/90 hover:no-underline"
213
+ }
214
+ />
215
+ ) : null}
216
+ {actions.length > 0 ? (
217
+ <DropdownMenu>
218
+ <DropdownMenuTrigger asChild>
219
+ <button
220
+ type="button"
221
+ aria-label={actionsLabel}
222
+ title={actionsLabel}
223
+ className="inline-flex size-8 shrink-0 items-center justify-center rounded-md border border-border text-muted-foreground transition-colors hover:bg-muted hover:text-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
224
+ >
225
+ <IconDotsVertical aria-hidden="true" className="size-4" />
226
+ </button>
227
+ </DropdownMenuTrigger>
228
+ <DropdownMenuContent align="end">
229
+ {actions.map((item) =>
230
+ item.type === "copy" ? (
231
+ <DropdownMenuItem
232
+ key={item.key}
233
+ onSelect={() => void copyText(item.action.text)}
234
+ >
235
+ <IconCopy aria-hidden="true" className="size-4" />
236
+ {item.action.label}
237
+ </DropdownMenuItem>
238
+ ) : (
239
+ <DropdownMenuItem key={item.key} asChild>
240
+ <WidgetActionLink
241
+ href={item.href}
242
+ label={item.action.label}
243
+ icon={
244
+ item.action.type === "link" ? item.action.icon : undefined
245
+ }
246
+ className="flex w-full items-center justify-between gap-3"
247
+ />
248
+ </DropdownMenuItem>
249
+ ),
250
+ )}
251
+ </DropdownMenuContent>
252
+ </DropdownMenu>
253
+ ) : null}
254
+ </div>
255
+ );
256
+ }
257
+
258
+ export function DataWidgetSourceChips({
259
+ sources,
260
+ }: {
261
+ sources: DataWidgetDataSource[];
262
+ }) {
263
+ const t = useT();
264
+
265
+ if (sources.length === 0) return null;
266
+
267
+ return (
268
+ <div
269
+ aria-label={t("agentChat.widget.dataSources", {
270
+ defaultValue: "Data sources",
271
+ })}
272
+ className="flex flex-wrap items-center gap-1.5 border-t border-border px-3 py-2"
273
+ role="group"
274
+ >
275
+ <span className="text-[10px] font-medium text-muted-foreground">
276
+ {t("agentChat.widget.dataSources", { defaultValue: "Data sources" })}
277
+ </span>
278
+ {sources.map((source, index) => {
279
+ const href = source.href ? normalizeActionHref(source.href) : null;
280
+ const className =
281
+ "inline-flex max-w-52 items-center truncate rounded-full border border-border bg-muted/30 px-2 py-0.5 text-[10px] font-medium text-muted-foreground no-underline hover:bg-muted hover:text-foreground hover:no-underline";
282
+
283
+ return href ? (
284
+ <WidgetActionLink
285
+ key={source.id ?? `${source.label}-${index}`}
286
+ href={href}
287
+ label={source.label}
288
+ className={className}
289
+ />
290
+ ) : (
291
+ <span
292
+ key={source.id ?? `${source.label}-${index}`}
293
+ className={className}
294
+ >
295
+ {source.label}
296
+ </span>
297
+ );
298
+ })}
299
+ </div>
300
+ );
301
+ }
@@ -282,17 +282,17 @@ function renderDataWidget(context: ToolRendererContext) {
282
282
  if (widget === DATA_TABLE_WIDGET && result.table) {
283
283
  return (
284
284
  <Suspense fallback={<BuiltinToolRendererSkeleton />}>
285
- <LazyDataTableWidget
286
- table={result.table}
287
- action={result.display?.primaryAction}
288
- />
285
+ <LazyDataTableWidget table={result.table} display={result.display} />
289
286
  </Suspense>
290
287
  );
291
288
  }
292
289
  if (widget === DATA_CHART_WIDGET && result.chartSeries) {
293
290
  return (
294
291
  <Suspense fallback={<BuiltinToolRendererSkeleton />}>
295
- <LazyDataChartWidget chart={result.chartSeries} />
292
+ <LazyDataChartWidget
293
+ chart={result.chartSeries}
294
+ display={result.display}
295
+ />
296
296
  </Suspense>
297
297
  );
298
298
  }
@@ -36,6 +36,7 @@ import {
36
36
  SESSION_REPLAY_BLOCK_PROPS,
37
37
  SESSION_REPLAY_MASK_PROPS,
38
38
  } from "../session-replay-privacy.js";
39
+ import { resolveMcpAppTheme } from "./mcp-app-theme.js";
39
40
 
40
41
  type CallToolResult = Parameters<
41
42
  InstanceType<typeof AppBridge>["sendToolResult"]
@@ -810,11 +811,15 @@ function buildHostContext(app: AgentMcpAppPayload, maxHeight: number) {
810
811
  : null;
811
812
  const cssVar = (name: string) => root?.getPropertyValue(name).trim() || "";
812
813
  const theme =
813
- typeof document !== "undefined" &&
814
- (document.documentElement.classList.contains("dark") ||
815
- window.matchMedia?.("(prefers-color-scheme: dark)").matches)
816
- ? "dark"
817
- : "light";
814
+ typeof document === "undefined"
815
+ ? "light"
816
+ : resolveMcpAppTheme(
817
+ document.documentElement,
818
+ () =>
819
+ typeof window !== "undefined" &&
820
+ (window.matchMedia?.("(prefers-color-scheme: dark)").matches ??
821
+ false),
822
+ );
818
823
  return {
819
824
  toolInfo: app.tool
820
825
  ? {
@@ -0,0 +1,30 @@
1
+ import { describe, expect, it, vi } from "vitest";
2
+
3
+ import { resolveMcpAppTheme } from "./mcp-app-theme.js";
4
+
5
+ function themeRoot(className: string, dataTheme: string | null) {
6
+ return {
7
+ classList: {
8
+ contains: (name: string) => className.split(" ").includes(name),
9
+ },
10
+ getAttribute: () => dataTheme,
11
+ } as unknown as Pick<HTMLElement, "classList" | "getAttribute">;
12
+ }
13
+
14
+ describe("resolveMcpAppTheme", () => {
15
+ it("keeps the selected host theme when the operating system prefers another theme", () => {
16
+ const readSystemTheme = vi.fn(() => true);
17
+ expect(
18
+ resolveMcpAppTheme(themeRoot("light", "light"), readSystemTheme),
19
+ ).toBe("light");
20
+ expect(resolveMcpAppTheme(themeRoot("dark", "dark"), readSystemTheme)).toBe(
21
+ "dark",
22
+ );
23
+ expect(readSystemTheme).not.toHaveBeenCalled();
24
+ });
25
+
26
+ it("uses the system preference only when no selected theme is present", () => {
27
+ expect(resolveMcpAppTheme(themeRoot("", null), () => true)).toBe("dark");
28
+ expect(resolveMcpAppTheme(themeRoot("", null), () => false)).toBe("light");
29
+ });
30
+ });
@@ -0,0 +1,11 @@
1
+ export function resolveMcpAppTheme(
2
+ root: Pick<HTMLElement, "classList" | "getAttribute">,
3
+ readSystemTheme: () => boolean,
4
+ ): "dark" | "light" {
5
+ if (root.classList.contains("dark")) return "dark";
6
+ if (root.classList.contains("light")) return "light";
7
+
8
+ const dataTheme = root.getAttribute("data-theme");
9
+ if (dataTheme === "dark" || dataTheme === "light") return dataTheme;
10
+ return readSystemTheme() ? "dark" : "light";
11
+ }
@@ -1522,6 +1522,9 @@ const messages: ToolkitAgentChatTranslation = {
1522
1522
  "tool.worked": "عمل",
1523
1523
  "tool.workedFor": "عمل لمدة {{duration}}",
1524
1524
  "widget.chart": "مخطط",
1525
+ "widget.chartActions": "إجراءات المخطط",
1526
+ "widget.dataSources": "مصادر البيانات",
1527
+ "widget.tableActions": "إجراءات الجدول",
1525
1528
  "widget.dataChart": "مخطط بيانات",
1526
1529
  "widget.dataInsights": "رؤى البيانات",
1527
1530
  "widget.dataTable": "جدول بيانات",
@@ -2246,6 +2249,12 @@ const messages: ToolkitAgentChatTranslation = {
2246
2249
  "تحقق من بريدك الإلكتروني للاطلاع على تعليمات تأكيد هذا التغيير.",
2247
2250
  "settingsShell.account.languageAndRegion": "اللغة والمنطقة",
2248
2251
  "settingsShell.account.languageDescription": "يُطبَّق على جميع أجهزتك.",
2252
+ "settingsShell.account.appearance": "المظهر",
2253
+ "settingsShell.account.themeFamily": "نمط السمة",
2254
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2255
+ "settingsShell.account.themeFamilyOriginal": "الألوان الأصلية للتطبيق",
2256
+ "settingsShell.account.themeFamilySaveError":
2257
+ "تعذّر تحديث نمط السمة. حاول مرة أخرى.",
2249
2258
  "settingsShell.account.manage": "إدارة",
2250
2259
  "settingsShell.account.name": "الاسم",
2251
2260
  "settingsShell.account.nameDescription":
@@ -1068,6 +1068,9 @@ const messages: ToolkitAgentChatTranslation = {
1068
1068
  "tool.worked": "Verarbeitet",
1069
1069
  "tool.workedFor": "{{duration}} verarbeitet",
1070
1070
  "widget.chart": "Diagramm",
1071
+ "widget.chartActions": "Diagrammaktionen",
1072
+ "widget.dataSources": "Datenquellen",
1073
+ "widget.tableActions": "Tabellenaktionen",
1071
1074
  "widget.dataChart": "Datendiagramm",
1072
1075
  "widget.dataInsights": "Datenerkenntnisse",
1073
1076
  "widget.dataTable": "Datentabelle",
@@ -2204,6 +2207,12 @@ const messages: ToolkitAgentChatTranslation = {
2204
2207
  "Prüfe deine E-Mails, um die Änderung zu bestätigen.",
2205
2208
  "settingsShell.account.languageAndRegion": "Sprache und Region",
2206
2209
  "settingsShell.account.languageDescription": "Gilt auf allen deinen Geräten.",
2210
+ "settingsShell.account.appearance": "Darstellung",
2211
+ "settingsShell.account.themeFamily": "Farbschema",
2212
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2213
+ "settingsShell.account.themeFamilyOriginal": "Originalfarben der App",
2214
+ "settingsShell.account.themeFamilySaveError":
2215
+ "Das Farbschema konnte nicht aktualisiert werden. Versuch es noch einmal.",
2207
2216
  "settingsShell.account.manage": "Verwalten",
2208
2217
  "settingsShell.account.name": "Name",
2209
2218
  "settingsShell.account.nameDescription":
@@ -1513,6 +1513,9 @@ const messages = {
1513
1513
  "tool.worked": "Worked",
1514
1514
  "tool.workedFor": "Worked for {{duration}}",
1515
1515
  "widget.chart": "Chart",
1516
+ "widget.chartActions": "Chart actions",
1517
+ "widget.dataSources": "Data sources",
1518
+ "widget.tableActions": "Table actions",
1516
1519
  "widget.dataChart": "Data chart",
1517
1520
  "widget.dataInsights": "Data insights",
1518
1521
  "widget.dataTable": "Data table",
@@ -2095,6 +2098,12 @@ const messages = {
2095
2098
  "Check your email for instructions to confirm this change.",
2096
2099
  "settingsShell.account.languageAndRegion": "Language and region",
2097
2100
  "settingsShell.account.languageDescription": "Applies on all your devices.",
2101
+ "settingsShell.account.appearance": "Appearance",
2102
+ "settingsShell.account.themeFamily": "Theme family",
2103
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2104
+ "settingsShell.account.themeFamilyOriginal": "Original app colors",
2105
+ "settingsShell.account.themeFamilySaveError":
2106
+ "Could not update the theme family. Try again.",
2098
2107
  "settingsShell.account.manage": "Manage",
2099
2108
  "settingsShell.account.name": "Name",
2100
2109
  "settingsShell.account.nameDescription":
@@ -1063,6 +1063,9 @@ const messages: ToolkitAgentChatTranslation = {
1063
1063
  "tool.worked": "Trabajó",
1064
1064
  "tool.workedFor": "Trabajó durante {{duration}}",
1065
1065
  "widget.chart": "Gráfico",
1066
+ "widget.chartActions": "Acciones del gráfico",
1067
+ "widget.dataSources": "Fuentes de datos",
1068
+ "widget.tableActions": "Acciones de la tabla",
1066
1069
  "widget.dataChart": "Gráfico de datos",
1067
1070
  "widget.dataInsights": "Información sobre los datos",
1068
1071
  "widget.dataTable": "Tabla de datos",
@@ -2231,6 +2234,13 @@ const messages: ToolkitAgentChatTranslation = {
2231
2234
  "settingsShell.account.languageAndRegion": "Idioma y región",
2232
2235
  "settingsShell.account.languageDescription":
2233
2236
  "Se aplica en todos tus dispositivos.",
2237
+ "settingsShell.account.appearance": "Apariencia",
2238
+ "settingsShell.account.themeFamily": "Familia de tema",
2239
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2240
+ "settingsShell.account.themeFamilyOriginal":
2241
+ "Colores originales de la aplicación",
2242
+ "settingsShell.account.themeFamilySaveError":
2243
+ "No se pudo actualizar el tema. Inténtalo de nuevo.",
2234
2244
  "settingsShell.account.manage": "Gestionar",
2235
2245
  "settingsShell.account.name": "Nombre",
2236
2246
  "settingsShell.account.nameDescription":
@@ -1068,6 +1068,9 @@ const messages: ToolkitAgentChatTranslation = {
1068
1068
  "tool.worked": "A travaillé",
1069
1069
  "tool.workedFor": "A travaillé pendant {{duration}}",
1070
1070
  "widget.chart": "Graphique",
1071
+ "widget.chartActions": "Actions du graphique",
1072
+ "widget.dataSources": "Sources de données",
1073
+ "widget.tableActions": "Actions du tableau",
1071
1074
  "widget.dataChart": "Graphique de données",
1072
1075
  "widget.dataInsights": "Informations sur les données",
1073
1076
  "widget.dataTable": "Tableau de données",
@@ -2245,6 +2248,13 @@ const messages: ToolkitAgentChatTranslation = {
2245
2248
  "settingsShell.account.languageAndRegion": "Langue et région",
2246
2249
  "settingsShell.account.languageDescription":
2247
2250
  "S’applique sur tous vos appareils.",
2251
+ "settingsShell.account.appearance": "Apparence",
2252
+ "settingsShell.account.themeFamily": "Famille de thème",
2253
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2254
+ "settingsShell.account.themeFamilyOriginal":
2255
+ "Couleurs d’origine de l’application",
2256
+ "settingsShell.account.themeFamilySaveError":
2257
+ "Impossible de mettre à jour le thème. Réessayez.",
2248
2258
  "settingsShell.account.manage": "Gérer",
2249
2259
  "settingsShell.account.name": "Nom",
2250
2260
  "settingsShell.account.nameDescription":