@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
@@ -1491,6 +1491,9 @@ const messages: ToolkitAgentChatTranslation = {
1491
1491
  "tool.worked": "काम किया",
1492
1492
  "tool.workedFor": "{{duration}} तक काम किया",
1493
1493
  "widget.chart": "चार्ट",
1494
+ "widget.chartActions": "चार्ट कार्रवाइयाँ",
1495
+ "widget.dataSources": "डेटा स्रोत",
1496
+ "widget.tableActions": "टेबल कार्रवाइयाँ",
1494
1497
  "widget.dataChart": "डेटा चार्ट",
1495
1498
  "widget.dataInsights": "डेटा इनसाइट्स",
1496
1499
  "widget.dataTable": "डेटा तालिका",
@@ -2063,6 +2066,12 @@ const messages: ToolkitAgentChatTranslation = {
2063
2066
  "इस बदलाव की पुष्टि करने के निर्देशों के लिए अपना ईमेल देखें।",
2064
2067
  "settingsShell.account.languageAndRegion": "भाषा और क्षेत्र",
2065
2068
  "settingsShell.account.languageDescription": "आपके सभी डिवाइस पर लागू होता है।",
2069
+ "settingsShell.account.appearance": "रूप-रंग",
2070
+ "settingsShell.account.themeFamily": "थीम शैली",
2071
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2072
+ "settingsShell.account.themeFamilyOriginal": "ऐप के मूल रंग",
2073
+ "settingsShell.account.themeFamilySaveError":
2074
+ "थीम शैली अपडेट नहीं की जा सकी। फिर कोशिश करें।",
2066
2075
  "settingsShell.account.manage": "प्रबंधित करें",
2067
2076
  "settingsShell.account.name": "नाम",
2068
2077
  "settingsShell.account.nameDescription":
@@ -1536,6 +1536,9 @@ const messages: ToolkitAgentChatTranslation = {
1536
1536
  "tool.worked": "作業しました",
1537
1537
  "tool.workedFor": "{{duration}} 作業しました",
1538
1538
  "widget.chart": "チャート",
1539
+ "widget.chartActions": "チャート操作",
1540
+ "widget.dataSources": "データソース",
1541
+ "widget.tableActions": "テーブル操作",
1539
1542
  "widget.dataChart": "データチャート",
1540
1543
  "widget.dataInsights": "データインサイト",
1541
1544
  "widget.dataTable": "データテーブル",
@@ -2093,6 +2096,12 @@ const messages: ToolkitAgentChatTranslation = {
2093
2096
  "settingsShell.account.languageAndRegion": "言語と地域",
2094
2097
  "settingsShell.account.languageDescription":
2095
2098
  "すべてのデバイスに適用されます。",
2099
+ "settingsShell.account.appearance": "外観",
2100
+ "settingsShell.account.themeFamily": "テーマ",
2101
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2102
+ "settingsShell.account.themeFamilyOriginal": "アプリのオリジナルカラー",
2103
+ "settingsShell.account.themeFamilySaveError":
2104
+ "テーマを更新できませんでした。もう一度お試しください。",
2096
2105
  "settingsShell.account.manage": "管理",
2097
2106
  "settingsShell.account.name": "名前",
2098
2107
  "settingsShell.account.nameDescription":
@@ -1510,6 +1510,9 @@ const messages: ToolkitAgentChatTranslation = {
1510
1510
  "tool.worked": "작업함",
1511
1511
  "tool.workedFor": "{{duration}} 동안 작업함",
1512
1512
  "widget.chart": "차트",
1513
+ "widget.chartActions": "차트 작업",
1514
+ "widget.dataSources": "데이터 소스",
1515
+ "widget.tableActions": "표 작업",
1513
1516
  "widget.dataChart": "데이터 차트",
1514
1517
  "widget.dataInsights": "데이터 인사이트",
1515
1518
  "widget.dataTable": "데이터 테이블",
@@ -2057,6 +2060,12 @@ const messages: ToolkitAgentChatTranslation = {
2057
2060
  "이메일을 확인하고 안내에 따라 이 변경을 확정하세요.",
2058
2061
  "settingsShell.account.languageAndRegion": "언어 및 지역",
2059
2062
  "settingsShell.account.languageDescription": "모든 기기에 적용됩니다.",
2063
+ "settingsShell.account.appearance": "모양",
2064
+ "settingsShell.account.themeFamily": "테마 스타일",
2065
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2066
+ "settingsShell.account.themeFamilyOriginal": "앱의 원래 색상",
2067
+ "settingsShell.account.themeFamilySaveError":
2068
+ "테마 스타일을 업데이트하지 못했습니다. 다시 시도하세요.",
2060
2069
  "settingsShell.account.manage": "관리",
2061
2070
  "settingsShell.account.name": "이름",
2062
2071
  "settingsShell.account.nameDescription":
@@ -1055,6 +1055,9 @@ const messages: ToolkitAgentChatTranslation = {
1055
1055
  "tool.worked": "Trabalhou",
1056
1056
  "tool.workedFor": "Trabalhou por {{duration}}",
1057
1057
  "widget.chart": "Gráfico",
1058
+ "widget.chartActions": "Ações do gráfico",
1059
+ "widget.dataSources": "Fontes de dados",
1060
+ "widget.tableActions": "Ações da tabela",
1058
1061
  "widget.dataChart": "Gráfico de dados",
1059
1062
  "widget.dataInsights": "Insights de dados",
1060
1063
  "widget.dataTable": "Tabela de dados",
@@ -2220,6 +2223,12 @@ const messages: ToolkitAgentChatTranslation = {
2220
2223
  "settingsShell.account.languageAndRegion": "Idioma e região",
2221
2224
  "settingsShell.account.languageDescription":
2222
2225
  "Vale para todos os seus dispositivos.",
2226
+ "settingsShell.account.appearance": "Aparência",
2227
+ "settingsShell.account.themeFamily": "Família do tema",
2228
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
2229
+ "settingsShell.account.themeFamilyOriginal": "Cores originais do app",
2230
+ "settingsShell.account.themeFamilySaveError":
2231
+ "Não foi possível atualizar o tema. Tente novamente.",
2223
2232
  "settingsShell.account.manage": "Gerenciar",
2224
2233
  "settingsShell.account.name": "Nome",
2225
2234
  "settingsShell.account.nameDescription":
@@ -1419,6 +1419,9 @@ const messages: ToolkitAgentChatTranslation = {
1419
1419
  "tool.worked": "已处理",
1420
1420
  "tool.workedFor": "处理了 {{duration}}",
1421
1421
  "widget.chart": "图表",
1422
+ "widget.chartActions": "图表操作",
1423
+ "widget.dataSources": "数据来源",
1424
+ "widget.tableActions": "表格操作",
1422
1425
  "widget.dataChart": "数据图表",
1423
1426
  "widget.dataInsights": "数据洞察",
1424
1427
  "widget.dataTable": "数据表",
@@ -1940,6 +1943,11 @@ const messages: ToolkitAgentChatTranslation = {
1940
1943
  "settingsShell.account.emailChangeSent": "请查看您的邮箱,按说明确认此更改。",
1941
1944
  "settingsShell.account.languageAndRegion": "语言和地区",
1942
1945
  "settingsShell.account.languageDescription": "适用于您的所有设备。",
1946
+ "settingsShell.account.appearance": "外观",
1947
+ "settingsShell.account.themeFamily": "主题样式",
1948
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
1949
+ "settingsShell.account.themeFamilyOriginal": "原应用配色",
1950
+ "settingsShell.account.themeFamilySaveError": "无法更新主题样式。请重试。",
1943
1951
  "settingsShell.account.manage": "管理",
1944
1952
  "settingsShell.account.name": "姓名",
1945
1953
  "settingsShell.account.nameDescription":
@@ -1424,6 +1424,9 @@ const messages: ToolkitAgentChatTranslation = {
1424
1424
  "tool.worked": "已處理",
1425
1425
  "tool.workedFor": "處理了 {{duration}}",
1426
1426
  "widget.chart": "圖表",
1427
+ "widget.chartActions": "圖表操作",
1428
+ "widget.dataSources": "資料來源",
1429
+ "widget.tableActions": "表格操作",
1427
1430
  "widget.dataChart": "資料圖表",
1428
1431
  "widget.dataInsights": "資料洞察",
1429
1432
  "widget.dataTable": "資料表",
@@ -1950,6 +1953,12 @@ const messages: ToolkitAgentChatTranslation = {
1950
1953
  "請查看您的電子郵件,依照說明確認此變更。",
1951
1954
  "settingsShell.account.languageAndRegion": "語言和地區",
1952
1955
  "settingsShell.account.languageDescription": "套用至您的所有裝置。",
1956
+ "settingsShell.account.appearance": "外觀",
1957
+ "settingsShell.account.themeFamily": "主題樣式",
1958
+ "settingsShell.account.themeFamilyAgentNative": "Agent-Native",
1959
+ "settingsShell.account.themeFamilyOriginal": "原始應用程式配色",
1960
+ "settingsShell.account.themeFamilySaveError":
1961
+ "無法更新主題樣式,請再試一次。",
1953
1962
  "settingsShell.account.manage": "管理",
1954
1963
  "settingsShell.account.name": "姓名",
1955
1964
  "settingsShell.account.nameDescription":
@@ -9,12 +9,17 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
9
9
  import { resetAgentEngineReadinessForTests } from "../../../../core/src/client/agent-engine-readiness.js";
10
10
 
11
11
  const useSessionMock = vi.fn();
12
+ const useThemeFamilyMock = vi.fn();
12
13
  vi.mock("@agent-native/core/client/use-session", async (importOriginal) => ({
13
14
  ...(await importOriginal<
14
15
  typeof import("@agent-native/core/client/use-session")
15
16
  >()),
16
17
  useSession: () => useSessionMock(),
17
18
  }));
19
+ vi.mock("@agent-native/core/client/ui", async (importOriginal) => ({
20
+ ...(await importOriginal<typeof import("@agent-native/core/client/ui")>()),
21
+ useThemeFamily: () => useThemeFamilyMock(),
22
+ }));
18
23
  vi.mock("@agent-native/core/client/action-query-cache", () => ({
19
24
  ActionQueryCacheGate: ({ children }: { children: React.ReactNode }) => (
20
25
  <div data-testid="action-query-cache-gate">{children}</div>
@@ -48,9 +53,34 @@ let originalLocation: Location;
48
53
  let originalParent: Window;
49
54
  let originalFetch: typeof window.fetch;
50
55
  let originalModelContext: PropertyDescriptor | undefined;
56
+ let originalLocalStorage: PropertyDescriptor | undefined;
51
57
  let originalDocumentTitle: string;
52
58
  let replaceMock: ReturnType<typeof vi.fn>;
53
59
 
60
+ function createTestLocalStorage(): Storage {
61
+ const values = new Map<string, string>();
62
+ return {
63
+ get length() {
64
+ return values.size;
65
+ },
66
+ clear() {
67
+ values.clear();
68
+ },
69
+ getItem(key) {
70
+ return values.get(String(key)) ?? null;
71
+ },
72
+ key(index) {
73
+ return [...values.keys()][index] ?? null;
74
+ },
75
+ removeItem(key) {
76
+ values.delete(String(key));
77
+ },
78
+ setItem(key, value) {
79
+ values.set(String(key), String(value));
80
+ },
81
+ };
82
+ }
83
+
54
84
  beforeEach(() => {
55
85
  resetAgentEngineReadinessForTests();
56
86
  container = document.createElement("div");
@@ -64,6 +94,14 @@ beforeEach(() => {
64
94
  document,
65
95
  "modelContext",
66
96
  );
97
+ originalLocalStorage = Object.getOwnPropertyDescriptor(
98
+ window,
99
+ "localStorage",
100
+ );
101
+ Object.defineProperty(window, "localStorage", {
102
+ configurable: true,
103
+ value: createTestLocalStorage(),
104
+ });
67
105
  Object.defineProperty(window, "fetch", {
68
106
  configurable: true,
69
107
  value: vi
@@ -102,6 +140,11 @@ afterEach(() => {
102
140
  configurable: true,
103
141
  value: originalFetch,
104
142
  });
143
+ if (originalLocalStorage) {
144
+ Object.defineProperty(window, "localStorage", originalLocalStorage);
145
+ } else {
146
+ delete (window as Window & { localStorage?: Storage }).localStorage;
147
+ }
105
148
  if (originalModelContext) {
106
149
  Object.defineProperty(document, "modelContext", originalModelContext);
107
150
  } else {
@@ -115,6 +158,7 @@ function renderProviders(props: {
115
158
  isPublicPath?: boolean;
116
159
  sessionBypass?: boolean;
117
160
  skipFirstRunOnboarding?: boolean;
161
+ enableThemeFamily?: boolean;
118
162
  disableWebMcp?: boolean;
119
163
  children?: React.ReactNode;
120
164
  }) {
@@ -183,6 +227,33 @@ const SIGNED_IN_SESSION = {
183
227
  };
184
228
 
185
229
  describe("AppProviders session gate", () => {
230
+ it("mounts theme-family sync by default in an authenticated app shell", async () => {
231
+ useSessionMock.mockReturnValue(SIGNED_IN_SESSION);
232
+
233
+ renderProviders({ skipFirstRunOnboarding: true });
234
+ await act(async () => {
235
+ await import("./ThemeFamilySync.js");
236
+ });
237
+
238
+ expect(useThemeFamilyMock).toHaveBeenCalledOnce();
239
+ });
240
+
241
+ it("allows an authenticated app shell to disable theme-family sync", () => {
242
+ useSessionMock.mockReturnValue(SIGNED_IN_SESSION);
243
+
244
+ renderProviders({ skipFirstRunOnboarding: true, enableThemeFamily: false });
245
+
246
+ expect(useThemeFamilyMock).not.toHaveBeenCalled();
247
+ });
248
+
249
+ it("does not load theme-family settings on public routes", () => {
250
+ useSessionMock.mockReturnValue(SIGNED_OUT_SESSION);
251
+
252
+ renderProviders({ isPublicPath: true });
253
+
254
+ expect(useThemeFamilyMock).not.toHaveBeenCalled();
255
+ });
256
+
186
257
  it("uses Toolkit's theme-aware toaster by default", () => {
187
258
  useSessionMock.mockReturnValue(SIGNED_OUT_SESSION);
188
259
 
@@ -95,6 +95,8 @@ export interface AppProvidersProps {
95
95
  */
96
96
  ownsDocument?: boolean;
97
97
 
98
+ enableThemeFamily?: boolean;
99
+
98
100
  documentTitleFallback?: string;
99
101
 
100
102
  children: React.ReactNode;
@@ -397,6 +399,8 @@ function AgentEngineReadinessBootstrap() {
397
399
  return null;
398
400
  }
399
401
 
402
+ const ThemeFamilySync = React.lazy(() => import("./ThemeFamilySync.js"));
403
+
400
404
  function ProvidersInner({
401
405
  queryClient,
402
406
  defaultTheme = "system",
@@ -507,6 +511,7 @@ export function AppProviders({
507
511
  i18n,
508
512
  documentTitleFallback,
509
513
  ownsDocument = true,
514
+ enableThemeFamily = true,
510
515
  children,
511
516
  }: AppProvidersProps) {
512
517
  const fallback = clientOnlyFallback ?? (
@@ -543,6 +548,11 @@ export function AppProviders({
543
548
  children
544
549
  ) : (
545
550
  <>
551
+ {ownsDocument && enableThemeFamily ? (
552
+ <React.Suspense fallback={null}>
553
+ <ThemeFamilySync />
554
+ </React.Suspense>
555
+ ) : null}
546
556
  <AgentEngineReadinessBootstrap />
547
557
  <FirstRunOnboardingStartupGate
548
558
  suppressSurface={skipFirstRunOnboarding}
@@ -0,0 +1,6 @@
1
+ import { useThemeFamily } from "@agent-native/core/client/ui";
2
+
3
+ export default function ThemeFamilySync() {
4
+ useThemeFamily();
5
+ return null;
6
+ }
@@ -25,6 +25,9 @@ vi.mock("../VoiceTranscriptionSection.js", () => ({
25
25
  <div data-testid="voice" data-compact={String(!!compact)} />
26
26
  ),
27
27
  }));
28
+ vi.mock("./ThemeFamilySettings.js", () => ({
29
+ ThemeFamilySettings: () => <div data-testid="theme-family-settings" />,
30
+ }));
28
31
 
29
32
  import { AgentNativeI18nProvider } from "@agent-native/core/client/i18n";
30
33
 
@@ -70,12 +73,14 @@ describe("PreferencesSettings", () => {
70
73
  const groups = [...container.querySelectorAll("section")];
71
74
  expect(groups.map((group) => group.id)).toEqual([
72
75
  "language-region",
76
+ "appearance",
73
77
  "voice-input",
74
78
  ]);
75
79
  expect(groups[0]!.querySelector("h2")?.textContent).toBe(
76
80
  "Language and region",
77
81
  );
78
- expect(groups[1]!.querySelector("h2")?.textContent).toBe("Voice input");
82
+ expect(groups[1]!.querySelector("h2")?.textContent).toBe("Appearance");
83
+ expect(groups[2]!.querySelector("h2")?.textContent).toBe("Voice input");
79
84
 
80
85
  const language = container.querySelector("#interface-language");
81
86
  expect(language?.textContent).toContain("Interface language");
@@ -90,8 +95,14 @@ describe("PreferencesSettings", () => {
90
95
  "Used for timestamps and scheduled automations.",
91
96
  );
92
97
 
98
+ const themeFamily = container.querySelector("#theme-family");
99
+ expect(themeFamily?.textContent).toContain("Theme family");
100
+ expect(
101
+ themeFamily?.querySelector('[data-testid="theme-family-settings"]'),
102
+ ).not.toBeNull();
103
+
93
104
  expect(
94
- groups[1]!
105
+ groups[2]!
95
106
  .querySelector('[data-testid="voice"]')
96
107
  ?.getAttribute("data-compact"),
97
108
  ).toBe("true");
@@ -4,6 +4,7 @@ import { LanguagePicker } from "../../shared/index.js";
4
4
  import { SchedulingTimezoneField } from "../SchedulingTimezoneField.js";
5
5
  import { SettingsGroup, SettingsRow } from "../SettingsRow.js";
6
6
  import { VoiceTranscriptionSection } from "../VoiceTranscriptionSection.js";
7
+ import { ThemeFamilySettings } from "./ThemeFamilySettings.js";
7
8
 
8
9
  const key = (name: string) => `agentChat.settingsShell.account.${name}`;
9
10
 
@@ -37,6 +38,13 @@ export function PreferencesSettings() {
37
38
  control={<SchedulingTimezoneField compact />}
38
39
  />
39
40
  </SettingsGroup>
41
+ <SettingsGroup id="appearance" title={t(key("appearance"))}>
42
+ <SettingsRow
43
+ id="theme-family"
44
+ label={t(key("themeFamily"))}
45
+ control={<ThemeFamilySettings />}
46
+ />
47
+ </SettingsGroup>
40
48
  <SettingsGroup id="voice-input" title={t(key("voiceInput"))}>
41
49
  <VoiceTranscriptionSection compact />
42
50
  </SettingsGroup>
@@ -0,0 +1,61 @@
1
+ import { useT } from "@agent-native/core/client/i18n";
2
+ import { useThemeFamily, type ThemeFamily } from "@agent-native/core/client/ui";
3
+ import {
4
+ Select,
5
+ SelectContent,
6
+ SelectItem,
7
+ SelectTrigger,
8
+ SelectValue,
9
+ } from "@agent-native/toolkit/ui/select";
10
+ import { useState } from "react";
11
+
12
+ const key = (name: string) => `agentChat.settingsShell.account.${name}`;
13
+
14
+ export function ThemeFamilySettings() {
15
+ const t = useT();
16
+ const { family, status, setFamily } = useThemeFamily();
17
+ const [saving, setSaving] = useState(false);
18
+ const [saveFailed, setSaveFailed] = useState(false);
19
+
20
+ async function changeFamily(value: string) {
21
+ if (value !== "agent-native" && value !== "original") return;
22
+ if (value === family) return;
23
+
24
+ setSaving(true);
25
+ setSaveFailed(false);
26
+ try {
27
+ await setFamily(value as ThemeFamily);
28
+ } catch {
29
+ setSaveFailed(true);
30
+ } finally {
31
+ setSaving(false);
32
+ }
33
+ }
34
+
35
+ return (
36
+ <div className="w-full sm:w-56">
37
+ <Select
38
+ value={family}
39
+ onValueChange={(value) => void changeFamily(value)}
40
+ disabled={saving || status === "loading"}
41
+ >
42
+ <SelectTrigger aria-label={t(key("themeFamily"))}>
43
+ <SelectValue />
44
+ </SelectTrigger>
45
+ <SelectContent>
46
+ <SelectItem value="agent-native">
47
+ {t(key("themeFamilyAgentNative"))}
48
+ </SelectItem>
49
+ <SelectItem value="original">
50
+ {t(key("themeFamilyOriginal"))}
51
+ </SelectItem>
52
+ </SelectContent>
53
+ </Select>
54
+ {saveFailed || status === "error" ? (
55
+ <p className="mt-2 text-xs text-destructive" role="alert">
56
+ {t(key("themeFamilySaveError"))}
57
+ </p>
58
+ ) : null}
59
+ </div>
60
+ );
61
+ }
@@ -33,6 +33,12 @@ export const PREFERENCES_SEARCH_ENTRIES: readonly SettingsPageSearchEntry[] = [
33
33
  keywords: "language locale translation",
34
34
  anchor: "interface-language",
35
35
  },
36
+ {
37
+ id: "theme-family",
38
+ labelKey: key("themeFamily"),
39
+ keywords: "theme appearance style colors",
40
+ anchor: "theme-family",
41
+ },
36
42
  {
37
43
  id: "timezone",
38
44
  labelKey: key("timezone"),
@@ -0,0 +1,14 @@
1
+ import { renderToStaticMarkup } from "react-dom/server";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { DefaultSpinner } from "./DefaultSpinner.js";
5
+
6
+ describe("DefaultSpinner theme styles", () => {
7
+ it("uses the selected dark theme instead of the operating system preference", () => {
8
+ const html = renderToStaticMarkup(<DefaultSpinner />);
9
+
10
+ expect(html).toContain("html.dark");
11
+ expect(html).toContain('html[data-theme="dark"]');
12
+ expect(html).not.toContain("prefers-color-scheme");
13
+ });
14
+ });
@@ -117,11 +117,10 @@ export function DefaultSpinner({
117
117
  background: hsl(var(--background, 0 0% 100%));
118
118
  color: hsl(var(--foreground, 240 10% 3.9%));
119
119
  }
120
- @media (prefers-color-scheme: dark) {
121
- html {
122
- background: hsl(var(--background, 240 10% 3.9%));
123
- color: hsl(var(--foreground, 0 0% 98%));
124
- }
120
+ html.dark,
121
+ html[data-theme="dark"]:not(.light) {
122
+ background: hsl(var(--background, 240 10% 3.9%));
123
+ color: hsl(var(--foreground, 0 0% 98%));
125
124
  }
126
125
  `}</style>
127
126
  </div>
@@ -1,6 +1,18 @@
1
- import { describe, expect, it } from "vitest";
1
+ import { describe, expect, it, vi } from "vitest";
2
2
 
3
- import { isExpectedRouteNotFound } from "./ErrorBoundary.js";
3
+ import {
4
+ isExpectedRouteNotFound,
5
+ resolveErrorBoundaryThemeClass,
6
+ } from "./ErrorBoundary.js";
7
+
8
+ function themeRoot(className: string, dataTheme: string | null) {
9
+ return {
10
+ classList: {
11
+ contains: (name: string) => className.split(" ").includes(name),
12
+ },
13
+ getAttribute: () => dataTheme,
14
+ } as unknown as Pick<HTMLElement, "classList" | "getAttribute">;
15
+ }
4
16
 
5
17
  describe("isExpectedRouteNotFound", () => {
6
18
  it("recognizes React Router 404 responses", () => {
@@ -26,3 +38,34 @@ describe("isExpectedRouteNotFound", () => {
26
38
  ).toBe(false);
27
39
  });
28
40
  });
41
+
42
+ describe("resolveErrorBoundaryThemeClass", () => {
43
+ it("honors an explicit data theme before storage or the system preference", () => {
44
+ const readStoredTheme = vi.fn(() => "dark");
45
+ const readSystemTheme = vi.fn(() => true);
46
+
47
+ expect(
48
+ resolveErrorBoundaryThemeClass(
49
+ themeRoot("", "light"),
50
+ readStoredTheme,
51
+ readSystemTheme,
52
+ ),
53
+ ).toBe("light");
54
+ expect(readStoredTheme).not.toHaveBeenCalled();
55
+ expect(readSystemTheme).not.toHaveBeenCalled();
56
+ });
57
+
58
+ it("uses the system theme only when no explicit theme is selected", () => {
59
+ const readStoredTheme = vi.fn(() => null);
60
+ const readSystemTheme = vi.fn(() => true);
61
+
62
+ expect(
63
+ resolveErrorBoundaryThemeClass(
64
+ themeRoot("", null),
65
+ readStoredTheme,
66
+ readSystemTheme,
67
+ ),
68
+ ).toBe("dark");
69
+ expect(readSystemTheme).toHaveBeenCalledOnce();
70
+ });
71
+ });
@@ -196,21 +196,46 @@ function useErrorCopy() {
196
196
  function useApplyThemeClass() {
197
197
  useEffect(() => {
198
198
  const root = document.documentElement;
199
- if (root.classList.contains("dark") || root.classList.contains("light"))
200
- return;
201
- try {
202
- const stored = localStorage.getItem("theme");
203
- if (stored === "dark") {
204
- root.classList.add("dark");
205
- } else if (stored === "light") {
206
- root.classList.add("light");
207
- } else if (window.matchMedia("(prefers-color-scheme: dark)").matches) {
208
- root.classList.add("dark");
209
- }
210
- } catch {}
199
+ let storageUnreadable = false;
200
+ const theme = resolveErrorBoundaryThemeClass(
201
+ root,
202
+ () => {
203
+ try {
204
+ return localStorage.getItem("theme");
205
+ } catch {
206
+ storageUnreadable = true;
207
+ return null;
208
+ }
209
+ },
210
+ () =>
211
+ window.matchMedia?.("(prefers-color-scheme: dark)").matches ?? false,
212
+ );
213
+ if (storageUnreadable) {
214
+ root.setAttribute("data-theme-status", "unreadable");
215
+ } else {
216
+ root.removeAttribute("data-theme-status");
217
+ }
218
+ if (theme) root.classList.add(theme);
211
219
  }, []);
212
220
  }
213
221
 
222
+ export function resolveErrorBoundaryThemeClass(
223
+ root: Pick<HTMLElement, "classList" | "getAttribute">,
224
+ readStoredTheme: () => string | null,
225
+ readSystemTheme: () => boolean,
226
+ ): "dark" | "light" | undefined {
227
+ if (root.classList.contains("dark") || root.classList.contains("light")) {
228
+ return undefined;
229
+ }
230
+
231
+ const dataTheme = root.getAttribute("data-theme");
232
+ if (dataTheme === "dark" || dataTheme === "light") return dataTheme;
233
+
234
+ const storedTheme = readStoredTheme();
235
+ if (storedTheme === "dark" || storedTheme === "light") return storedTheme;
236
+ return readSystemTheme() ? "dark" : undefined;
237
+ }
238
+
214
239
  function errorMessageOf(error: unknown): string {
215
240
  if (error instanceof Error) return error.message;
216
241
  return typeof error === "string" ? error : "";
@@ -0,0 +1,16 @@
1
+ import { renderToStaticMarkup } from "react-dom/server";
2
+ import { describe, expect, it } from "vitest";
3
+
4
+ import { PoweredByBadge } from "./PoweredByBadge.js";
5
+
6
+ describe("PoweredByBadge theme styles", () => {
7
+ it("follows the selected theme classes and data attribute", () => {
8
+ const html = renderToStaticMarkup(<PoweredByBadge />);
9
+
10
+ expect(html).toContain("html.dark");
11
+ expect(html).toContain('html[data-theme="dark"]');
12
+ expect(html).toContain("html.light");
13
+ expect(html).toContain('html[data-theme="light"]');
14
+ expect(html).not.toContain("prefers-color-scheme");
15
+ });
16
+ });