@agent-native/toolkit 0.217.0 → 0.219.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 (206) hide show
  1. package/agent-native.eject.json +21 -0
  2. package/dist/app/agent-page/AgentContextTab.d.ts.map +1 -1
  3. package/dist/app/agent-page/AgentContextTab.js +1 -0
  4. package/dist/app/agent-page/AgentContextTab.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  6. package/dist/app/agentkit/react/components.js +1 -1
  7. package/dist/app/agentkit/react/components.js.map +1 -1
  8. package/dist/app/agentkit/react/context.d.ts +4 -1
  9. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  10. package/dist/app/agentkit/react/context.js +3 -1
  11. package/dist/app/agentkit/react/context.js.map +1 -1
  12. package/dist/app/agentkit/react/data-chart-layout.d.ts +30 -0
  13. package/dist/app/agentkit/react/data-chart-layout.d.ts.map +1 -0
  14. package/dist/app/agentkit/react/data-chart-layout.js +2 -0
  15. package/dist/app/agentkit/react/data-chart-layout.js.map +1 -0
  16. package/dist/app/agentkit/react/index.d.ts +1 -0
  17. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  18. package/dist/app/agentkit/react/index.js +1 -0
  19. package/dist/app/agentkit/react/index.js.map +1 -1
  20. package/dist/app/agentkit/react/root.d.ts +3 -1
  21. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  22. package/dist/app/agentkit/react/root.js +2 -2
  23. package/dist/app/agentkit/react/root.js.map +1 -1
  24. package/dist/app/agentkit/react/styles.css +31 -6
  25. package/dist/app/agentkit/react/transcript-layout.d.ts +2 -0
  26. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -1
  27. package/dist/app/agentkit/react/transcript-layout.js.map +1 -1
  28. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  29. package/dist/app/chat/AgentKitAssistantChat.js +2 -2
  30. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  31. package/dist/app/chat/AgentPanel.js +6 -6
  32. package/dist/app/chat/AgentPanel.js.map +1 -1
  33. package/dist/app/chat/MultiTabAssistantChat.d.ts.map +1 -1
  34. package/dist/app/chat/MultiTabAssistantChat.js +53 -95
  35. package/dist/app/chat/MultiTabAssistantChat.js.map +1 -1
  36. package/dist/app/chat/agentkit-chat/action-widget.d.ts.map +1 -1
  37. package/dist/app/chat/agentkit-chat/action-widget.js +4 -1
  38. package/dist/app/chat/agentkit-chat/action-widget.js.map +1 -1
  39. package/dist/app/chat/agentkit-chat/rail.d.ts +1 -1
  40. package/dist/app/chat/agentkit-chat/rail.d.ts.map +1 -1
  41. package/dist/app/chat/agentkit-chat/rail.js +1 -1
  42. package/dist/app/chat/agentkit-chat/rail.js.map +1 -1
  43. package/dist/app/chat/chat/index.d.ts +2 -1
  44. package/dist/app/chat/chat/index.d.ts.map +1 -1
  45. package/dist/app/chat/chat/index.js +1 -1
  46. package/dist/app/chat/chat/index.js.map +1 -1
  47. package/dist/app/chat/chat/surface-types.d.ts +3 -0
  48. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  49. package/dist/app/chat/chat/surface-types.js.map +1 -1
  50. package/dist/app/chat/chat/tool-call-display.d.ts +5 -1
  51. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  52. package/dist/app/chat/chat/tool-call-display.js +6 -4
  53. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  54. package/dist/app/chat/chat/tool-render-registry.d.ts +4 -0
  55. package/dist/app/chat/chat/tool-render-registry.d.ts.map +1 -1
  56. package/dist/app/chat/chat/tool-render-registry.js.map +1 -1
  57. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts +3 -1
  58. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  59. package/dist/app/chat/chat/widgets/DataChartRenderer.js +116 -26
  60. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  61. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -1
  62. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  63. package/dist/app/chat/chat/widgets/DataChartWidget.js +21 -2
  64. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  65. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts +3 -1
  66. package/dist/app/chat/chat/widgets/DataInsightsWidget.d.ts.map +1 -1
  67. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +2 -2
  68. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  69. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +1 -1
  70. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -1
  71. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +3 -1
  72. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -1
  73. package/dist/app/chat/chat/widgets/builtin-tool-renderers.d.ts.map +1 -1
  74. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  75. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  76. package/dist/app/chat/chat-first/chat-history.d.ts +13 -2
  77. package/dist/app/chat/chat-first/chat-history.d.ts.map +1 -1
  78. package/dist/app/chat/chat-first/chat-history.js +24 -3
  79. package/dist/app/chat/chat-first/chat-history.js.map +1 -1
  80. package/dist/app/chat/conversation/AgentConversation.d.ts.map +1 -1
  81. package/dist/app/chat/conversation/AgentConversation.js +6 -1
  82. package/dist/app/chat/conversation/AgentConversation.js.map +1 -1
  83. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts +6 -0
  84. package/dist/app/chat/ownership-history/ChatOwnerAvatar.d.ts.map +1 -0
  85. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js +9 -0
  86. package/dist/app/chat/ownership-history/ChatOwnerAvatar.js.map +1 -0
  87. package/dist/app/chat/ownership-history/index.d.ts +11 -0
  88. package/dist/app/chat/ownership-history/index.d.ts.map +1 -0
  89. package/dist/app/chat/ownership-history/index.js +22 -0
  90. package/dist/app/chat/ownership-history/index.js.map +1 -0
  91. package/dist/app/chat/ownership-history/rows.d.ts +27 -0
  92. package/dist/app/chat/ownership-history/rows.d.ts.map +1 -0
  93. package/dist/app/chat/ownership-history/rows.js +101 -0
  94. package/dist/app/chat/ownership-history/rows.js.map +1 -0
  95. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts +52 -0
  96. package/dist/app/chat/ownership-history/useOwnershipChatHistory.d.ts.map +1 -0
  97. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js +497 -0
  98. package/dist/app/chat/ownership-history/useOwnershipChatHistory.js.map +1 -0
  99. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  100. package/dist/app/i18n/catalogs/ar-SA.js +3 -0
  101. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  102. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  103. package/dist/app/i18n/catalogs/de-DE.js +3 -0
  104. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  105. package/dist/app/i18n/catalogs/en-US.d.ts +3 -0
  106. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  107. package/dist/app/i18n/catalogs/en-US.js +3 -0
  108. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  109. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  110. package/dist/app/i18n/catalogs/es-ES.js +3 -0
  111. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  112. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  113. package/dist/app/i18n/catalogs/fr-FR.js +3 -0
  114. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  115. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  116. package/dist/app/i18n/catalogs/hi-IN.js +3 -0
  117. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  118. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  119. package/dist/app/i18n/catalogs/ja-JP.js +3 -0
  120. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  121. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  122. package/dist/app/i18n/catalogs/ko-KR.js +3 -0
  123. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  124. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  125. package/dist/app/i18n/catalogs/pt-BR.js +3 -0
  126. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  127. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  128. package/dist/app/i18n/catalogs/zh-CN.js +3 -0
  129. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  130. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  131. package/dist/app/i18n/catalogs/zh-TW.js +3 -0
  132. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  133. package/dist/app/shared/CommandMenu.d.ts +2 -0
  134. package/dist/app/shared/CommandMenu.d.ts.map +1 -1
  135. package/dist/app/shared/CommandMenu.js +11 -6
  136. package/dist/app/shared/CommandMenu.js.map +1 -1
  137. package/dist/app/sharing/ShareButton.d.ts.map +1 -1
  138. package/dist/app/sharing/ShareButton.js +9 -8
  139. package/dist/app/sharing/ShareButton.js.map +1 -1
  140. package/dist/chat-history/ChatHistoryList.d.ts +16 -3
  141. package/dist/chat-history/ChatHistoryList.d.ts.map +1 -1
  142. package/dist/chat-history/ChatHistoryList.js +25 -15
  143. package/dist/chat-history/ChatHistoryList.js.map +1 -1
  144. package/dist/chat-history.css +109 -9
  145. package/dist/composer/TiptapComposer.js +1 -1
  146. package/dist/composer/TiptapComposer.js.map +1 -1
  147. package/package.json +10 -3
  148. package/src/app/agent-page/AgentContextTab.spec.ts +50 -2
  149. package/src/app/agent-page/AgentContextTab.tsx +1 -0
  150. package/src/app/agentkit/react/components.interaction.spec.tsx +2 -0
  151. package/src/app/agentkit/react/components.spec.tsx +11 -6
  152. package/src/app/agentkit/react/components.tsx +3 -0
  153. package/src/app/agentkit/react/context.tsx +6 -0
  154. package/src/app/agentkit/react/data-chart-layout.ts +25 -0
  155. package/src/app/agentkit/react/index.ts +1 -0
  156. package/src/app/agentkit/react/root.tsx +4 -0
  157. package/src/app/agentkit/react/styles.css +31 -6
  158. package/src/app/agentkit/react/transcript-layout.ts +2 -0
  159. package/src/app/chat/AgentKitAssistantChat.spec.tsx +201 -27
  160. package/src/app/chat/AgentKitAssistantChat.tsx +3 -0
  161. package/src/app/chat/AgentPanel.tsx +8 -8
  162. package/src/app/chat/MultiTabAssistantChat.spec.tsx +270 -3
  163. package/src/app/chat/MultiTabAssistantChat.tsx +81 -158
  164. package/src/app/chat/agentkit-chat/action-widget.spec.tsx +62 -0
  165. package/src/app/chat/agentkit-chat/action-widget.tsx +4 -0
  166. package/src/app/chat/agentkit-chat/rail.ts +11 -0
  167. package/src/app/chat/chat/index.ts +10 -0
  168. package/src/app/chat/chat/surface-types.ts +3 -0
  169. package/src/app/chat/chat/tool-call-display.spec.tsx +29 -0
  170. package/src/app/chat/chat/tool-call-display.tsx +17 -1
  171. package/src/app/chat/chat/tool-render-registry.tsx +5 -0
  172. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +377 -17
  173. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +232 -63
  174. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +67 -0
  175. package/src/app/chat/chat/widgets/DataChartWidget.tsx +39 -5
  176. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +13 -2
  177. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +6 -4
  178. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -1
  179. package/src/app/chat/chat-first/chat-history.spec.tsx +79 -0
  180. package/src/app/chat/chat-first/chat-history.tsx +75 -22
  181. package/src/app/chat/conversation/AgentConversation.spec.tsx +51 -1
  182. package/src/app/chat/conversation/AgentConversation.tsx +11 -2
  183. package/src/app/chat/ownership-history/ChatOwnerAvatar.tsx +27 -0
  184. package/src/app/chat/ownership-history/index.tsx +67 -0
  185. package/src/app/chat/ownership-history/ownership-history.spec.tsx +3146 -0
  186. package/src/app/chat/ownership-history/rows.ts +156 -0
  187. package/src/app/chat/ownership-history/useOwnershipChatHistory.ts +709 -0
  188. package/src/app/i18n/catalogs/ar-SA.ts +3 -0
  189. package/src/app/i18n/catalogs/de-DE.ts +3 -0
  190. package/src/app/i18n/catalogs/en-US.ts +3 -0
  191. package/src/app/i18n/catalogs/es-ES.ts +3 -0
  192. package/src/app/i18n/catalogs/fr-FR.ts +3 -0
  193. package/src/app/i18n/catalogs/hi-IN.ts +3 -0
  194. package/src/app/i18n/catalogs/ja-JP.ts +3 -0
  195. package/src/app/i18n/catalogs/ko-KR.ts +3 -0
  196. package/src/app/i18n/catalogs/pt-BR.ts +3 -0
  197. package/src/app/i18n/catalogs/zh-CN.ts +3 -0
  198. package/src/app/i18n/catalogs/zh-TW.ts +3 -0
  199. package/src/app/shared/CommandMenu.spec.tsx +175 -6
  200. package/src/app/shared/CommandMenu.tsx +24 -7
  201. package/src/app/sharing/ShareButton.tsx +12 -10
  202. package/src/chat-history/ChatHistoryList.spec.tsx +51 -0
  203. package/src/chat-history/ChatHistoryList.tsx +87 -26
  204. package/src/chat-history/ChatHistoryRailSections.spec.tsx +12 -4
  205. package/src/chat-history.css +109 -9
  206. package/src/composer/TiptapComposer.tsx +1 -1
@@ -1,6 +1,7 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import { DATA_WIDGET_MAX_CHART_POINTS } from "@agent-native/core/data-widgets";
3
3
 
4
+ import type { AgentChatDataChartLayout } from "../../../agentkit/react/data-chart-layout.js";
4
5
  import type { DataChartWidget as DataChartWidgetData } from "./data-widget-types.js";
5
6
 
6
7
  const DEFAULT_COLORS = [
@@ -38,12 +39,25 @@ function numericValue(
38
39
  }
39
40
 
40
41
  function formatTick(value: number): string {
42
+ if (Number.isInteger(value)) {
43
+ return Math.abs(value) >= 1000 ? value.toLocaleString() : String(value);
44
+ }
41
45
  if (Math.abs(value) >= 1000) return value.toLocaleString();
42
- if (Number.isInteger(value)) return String(value);
43
46
  return value.toFixed(1);
44
47
  }
45
48
 
46
- function formatAxisTick(value: number, interval: number): string {
49
+ function formatValueLabel(value: number): string {
50
+ if (Number.isInteger(value)) {
51
+ return Math.abs(value) >= 1000 ? value.toLocaleString() : String(value);
52
+ }
53
+ return formatTick(value);
54
+ }
55
+
56
+ function formatAxisTick(
57
+ value: number,
58
+ interval: number,
59
+ compactInteger: boolean,
60
+ ): string {
47
61
  const [mantissa, exponent = "0"] = interval
48
62
  .toString()
49
63
  .toLowerCase()
@@ -52,6 +66,9 @@ function formatAxisTick(value: number, interval: number): string {
52
66
  0,
53
67
  (mantissa.split(".")[1]?.length ?? 0) - Number(exponent),
54
68
  );
69
+ if (compactInteger && decimalPlaces === 1 && Number.isInteger(value)) {
70
+ return Math.abs(value) >= 1000 ? value.toLocaleString() : String(value);
71
+ }
55
72
  if (Math.abs(value) >= 1000) {
56
73
  return value.toLocaleString(undefined, {
57
74
  minimumFractionDigits: decimalPlaces,
@@ -61,6 +78,41 @@ function formatAxisTick(value: number, interval: number): string {
61
78
  return value.toFixed(decimalPlaces);
62
79
  }
63
80
 
81
+ function lineValueLabelY(
82
+ pointY: number,
83
+ valueLabelFontSize: number,
84
+ plotTop: number,
85
+ ): number {
86
+ const minimumY = valueLabelFontSize;
87
+ const maximumY = HEIGHT - valueLabelFontSize * 0.25;
88
+ const fitsAbove = pointY - valueLabelFontSize - 5 >= plotTop;
89
+ const fitsBelow = pointY + valueLabelFontSize + 5 <= maximumY;
90
+ const labelY =
91
+ fitsAbove || !fitsBelow ? pointY - 5 : pointY + valueLabelFontSize + 5;
92
+
93
+ return Math.min(Math.max(labelY, minimumY), maximumY);
94
+ }
95
+
96
+ function inset(value: number | undefined, name: string, fallback: number) {
97
+ if (value === undefined) return fallback;
98
+ if (!Number.isFinite(value) || value < 0) {
99
+ throw new RangeError(
100
+ `dataChartLayout.plotInsets.${name} must be a non-negative CSS length`,
101
+ );
102
+ }
103
+ return value;
104
+ }
105
+
106
+ function fontSize(value: number | undefined, name: string, fallback: number) {
107
+ if (value === undefined) return fallback;
108
+ if (!Number.isFinite(value) || value <= 0) {
109
+ throw new RangeError(
110
+ `dataChartLayout.${name} must be a positive CSS length`,
111
+ );
112
+ }
113
+ return value;
114
+ }
115
+
64
116
  function labelFor(value: unknown): string {
65
117
  const characters = Array.from(value == null ? "" : String(value));
66
118
  return characters.length > MAX_LABEL_LENGTH
@@ -116,7 +168,7 @@ function formatHorizontalValueLabel(
116
168
  fontSize: number,
117
169
  maxWidth: number,
118
170
  ): string {
119
- const label = formatTick(value);
171
+ const label = formatValueLabel(value);
120
172
  return estimateTextWidth(label, fontSize) > maxWidth
121
173
  ? formatCompactTick(value, fontSize, maxWidth)
122
174
  : label;
@@ -146,9 +198,11 @@ function rangeFor(chart: DataChartWidgetData) {
146
198
 
147
199
  function ChartBody({
148
200
  chart,
201
+ layout,
149
202
  labelFontSize,
150
203
  }: {
151
204
  chart: DataChartWidgetData;
205
+ layout?: AgentChatDataChartLayout;
152
206
  labelFontSize: number;
153
207
  }) {
154
208
  const categoryGapRatio = chart.horizontalCategoryGapRatio;
@@ -163,6 +217,7 @@ function ChartBody({
163
217
  const { min, max } = rangeFor(chart);
164
218
  const isHorizontalBar = chart.type === "bar" && chart.layout === "horizontal";
165
219
  const tickInterval = chart.valueAxisTickInterval;
220
+ const valueLabelFontSize = chart.valueLabelFontSize ?? 11;
166
221
  const gridTickValues =
167
222
  tickInterval === undefined
168
223
  ? Array.from({ length: 4 }, (_, index) => min + ((max - min) * index) / 3)
@@ -223,7 +278,7 @@ function ChartBody({
223
278
  const label =
224
279
  tickInterval === undefined
225
280
  ? formatTick(value)
226
- : formatAxisTick(value, tickInterval);
281
+ : formatAxisTick(value, tickInterval, layout !== undefined);
227
282
  const maximumWidth = isHorizontalBar
228
283
  ? horizontalTextWidthLimit
229
284
  : WIDTH - MARGIN.right - MIN_PLOT_WIDTH - verticalValueTickMargin;
@@ -239,6 +294,27 @@ function ChartBody({
239
294
  const horizontalValueTickLabelInset = isHorizontalBar
240
295
  ? Math.ceil(valueTickLabelWidth / 2) + 2
241
296
  : 0;
297
+ const minimumHorizontalTickBottom = isHorizontalBar
298
+ ? 14 +
299
+ labelFontSize * 0.25 +
300
+ 4 +
301
+ (chart.xAxisTitle ? axisTitleFontSize * 0.8 + 6 : 0)
302
+ : 0;
303
+ const axisTitleBottomInset = chart.xAxisTitle ? 16 : 0;
304
+ const minimumVerticalCategoryBottom =
305
+ isHorizontalBar || layout?.plotInsets?.bottom === undefined
306
+ ? 0
307
+ : labelFontSize + 12 + (chart.xAxisTitle ? axisTitleFontSize + 2 : 0);
308
+ const configuredTopInset = inset(layout?.plotInsets?.top, "top", MARGIN.top);
309
+ const minimumVerticalValueLabelTop =
310
+ chart.type === "bar" && !isHorizontalBar && chart.showValueLabels
311
+ ? valueLabelFontSize + 5
312
+ : 0;
313
+ const configuredBottomInset = inset(
314
+ layout?.plotInsets?.bottom,
315
+ "bottom",
316
+ MARGIN.bottom,
317
+ );
242
318
  const customValueTickWidth = isHorizontalBar ? 0 : customValueTickLabelWidth;
243
319
  const horizontalPositiveValueWidth =
244
320
  isHorizontalBar && chart.showValueLabels
@@ -282,8 +358,28 @@ function ChartBody({
282
358
  ),
283
359
  )
284
360
  : 0;
361
+ const verticalBarValueLabelInset =
362
+ chart.type === "bar" && !isHorizontalBar && chart.showValueLabels
363
+ ? Math.ceil(
364
+ Math.max(
365
+ 0,
366
+ ...chart.data.flatMap((row) =>
367
+ chart.series
368
+ .map((series) => numericValue(row, series.key))
369
+ .filter((value): value is number => value !== null)
370
+ .map((value) =>
371
+ estimateTextWidth(
372
+ formatValueLabel(value),
373
+ valueLabelFontSize,
374
+ ),
375
+ ),
376
+ ),
377
+ ) / 2,
378
+ ) + 4
379
+ : 0;
285
380
  const margin = {
286
381
  ...MARGIN,
382
+ top: Math.max(configuredTopInset, minimumVerticalValueLabelTop),
287
383
  left:
288
384
  Math.max(
289
385
  categoryMargin,
@@ -293,15 +389,40 @@ function ChartBody({
293
389
  (horizontalNegativeValueWidth > 0
294
390
  ? horizontalNegativeValueWidth + 13
295
391
  : 0),
296
- right: Math.max(
297
- MARGIN.right,
298
- horizontalPositiveValueWidth + 13,
299
- horizontalValueTickLabelInset,
300
- ),
301
- bottom: MARGIN.bottom + (chart.xAxisTitle ? 16 : 0),
392
+ right:
393
+ layout?.plotInsets?.right === undefined
394
+ ? Math.max(
395
+ MARGIN.right,
396
+ horizontalPositiveValueWidth + 13,
397
+ horizontalValueTickLabelInset,
398
+ verticalBarValueLabelInset,
399
+ )
400
+ : Math.max(
401
+ inset(layout.plotInsets.right, "right", MARGIN.right),
402
+ horizontalPositiveValueWidth > 0
403
+ ? horizontalPositiveValueWidth + 13
404
+ : 0,
405
+ horizontalValueTickLabelInset,
406
+ verticalBarValueLabelInset,
407
+ ),
408
+ bottom:
409
+ Math.max(
410
+ configuredBottomInset,
411
+ minimumHorizontalTickBottom - axisTitleBottomInset,
412
+ minimumVerticalCategoryBottom - axisTitleBottomInset,
413
+ ) + axisTitleBottomInset,
302
414
  };
303
415
  const plotWidth = WIDTH - margin.left - margin.right;
304
416
  const plotHeight = HEIGHT - margin.top - margin.bottom;
417
+ if (plotWidth <= 0 || plotHeight <= 0) {
418
+ throw new RangeError(
419
+ "dataChartLayout.plotInsets must leave a positive chart plot area",
420
+ );
421
+ }
422
+ const labelColor =
423
+ layout?.labelTone === "foreground"
424
+ ? "hsl(var(--foreground))"
425
+ : "hsl(var(--muted-foreground))";
305
426
  const xForValue = (value: number) =>
306
427
  margin.left + ((value - min) / (max - min)) * plotWidth;
307
428
  const yForValue = (value: number) =>
@@ -400,7 +521,6 @@ function ChartBody({
400
521
  const zeroX = xForValue(zero);
401
522
  const zeroY = yForValue(zero);
402
523
  const axisTitleFontWeight = chart.axisTitleFontWeight ?? 500;
403
- const valueLabelFontSize = chart.valueLabelFontSize ?? 11;
404
524
  const valueLabelFontWeight = chart.valueLabelFontWeight ?? 400;
405
525
 
406
526
  const axes = (
@@ -420,7 +540,7 @@ function ChartBody({
420
540
  <text
421
541
  x={tick.x}
422
542
  y={HEIGHT - margin.bottom + 14}
423
- fill="hsl(var(--muted-foreground))"
543
+ fill={labelColor}
424
544
  fontSize={labelFontSize}
425
545
  textAnchor="middle"
426
546
  >
@@ -440,9 +560,13 @@ function ChartBody({
440
560
  />
441
561
  {axisLabelIndexes.has(tick.index) ? (
442
562
  <text
563
+ data-value-axis-tick="true"
443
564
  x={margin.left - VALUE_AXIS_TICK_LABEL_INSET}
444
- y={tick.y + 4}
445
- fill="hsl(var(--muted-foreground))"
565
+ y={Math.min(
566
+ Math.max(tick.y + 4, labelFontSize),
567
+ HEIGHT - labelFontSize * 0.25,
568
+ )}
569
+ fill={labelColor}
446
570
  fontSize={labelFontSize}
447
571
  textAnchor="end"
448
572
  >
@@ -459,35 +583,64 @@ function ChartBody({
459
583
  y2={isHorizontalBar ? HEIGHT - margin.bottom : zeroY}
460
584
  stroke="hsl(var(--chart-grid, var(--border)))"
461
585
  />
462
- {chart.data.map((row, index) =>
463
- isHorizontalBar ? (
586
+ {chart.data.map((row, index) => {
587
+ if (isHorizontalBar) {
588
+ const label = labelFor(row[chart.xKey]);
589
+ return (
590
+ <text
591
+ key={`${index}-${String(row[chart.xKey] ?? "")}`}
592
+ x={categoryLabelX}
593
+ y={yForRow(index) + labelFontSize * 0.35}
594
+ fill={labelColor}
595
+ fontSize={labelFontSize}
596
+ textAnchor="end"
597
+ >
598
+ {label}
599
+ </text>
600
+ );
601
+ }
602
+ if (index % xTickEvery !== 0 && index !== chart.data.length - 1) {
603
+ return null;
604
+ }
605
+
606
+ const label = labelFor(row[chart.xKey]);
607
+ const categoryLabelY =
608
+ layout?.plotInsets?.bottom === undefined
609
+ ? HEIGHT - 12
610
+ : chart.xAxisTitle
611
+ ? HEIGHT - axisTitleFontSize - 12
612
+ : Math.max(
613
+ HEIGHT - 12,
614
+ HEIGHT - margin.bottom + labelFontSize + 1,
615
+ );
616
+ const x =
617
+ chart.type === "bar"
618
+ ? margin.left + (index + 0.5) * (plotWidth / chart.data.length)
619
+ : xForPoint(index);
620
+ const labelHalfWidth = estimateTextWidth(label, labelFontSize) / 2;
621
+ const leftEndpointOverflows = index === 0 && x - labelHalfWidth < 0;
622
+ const rightEndpointOverflows =
623
+ index === chart.data.length - 1 && x + labelHalfWidth > WIDTH;
624
+
625
+ return (
464
626
  <text
465
627
  key={`${index}-${String(row[chart.xKey] ?? "")}`}
466
- x={categoryLabelX}
467
- y={yForRow(index) + labelFontSize * 0.35}
468
- fill="hsl(var(--muted-foreground))"
628
+ x={x}
629
+ y={categoryLabelY}
630
+ fill={labelColor}
469
631
  fontSize={labelFontSize}
470
- textAnchor="end"
471
- >
472
- {labelFor(row[chart.xKey])}
473
- </text>
474
- ) : index % xTickEvery === 0 || index === chart.data.length - 1 ? (
475
- <text
476
- key={`${index}-${String(row[chart.xKey] ?? "")}`}
477
- x={
478
- chart.type === "bar"
479
- ? margin.left + (index + 0.5) * (plotWidth / chart.data.length)
480
- : xForPoint(index)
632
+ textAnchor={
633
+ leftEndpointOverflows
634
+ ? "start"
635
+ : rightEndpointOverflows
636
+ ? "end"
637
+ : "middle"
481
638
  }
482
- y={HEIGHT - 12}
483
- fill="hsl(var(--muted-foreground))"
484
- fontSize={labelFontSize}
485
- textAnchor="middle"
486
639
  >
487
- {labelFor(row[chart.xKey])}
640
+ {label}
488
641
  </text>
489
- ) : null,
490
- )}
642
+ );
643
+ })}
491
644
  {chart.xAxisTitle ? (
492
645
  <text
493
646
  x={margin.left + plotWidth / 2}
@@ -556,6 +709,21 @@ function ChartBody({
556
709
  const height = isHorizontalBar
557
710
  ? Math.max(0, barSize - Math.min(2, barSize * 0.1))
558
711
  : Math.max(1, Math.abs(zeroY - valueY));
712
+ const valueLabel = isHorizontalBar
713
+ ? formatHorizontalValueLabel(
714
+ value,
715
+ valueLabelFontSize,
716
+ horizontalTextWidthLimit,
717
+ )
718
+ : formatValueLabel(value);
719
+ const valueLabelWidth = estimateTextWidth(
720
+ valueLabel,
721
+ valueLabelFontSize,
722
+ );
723
+ const valueLabelX =
724
+ isHorizontalBar && value >= 0
725
+ ? Math.min(valueX + 5, WIDTH - valueLabelWidth - 4)
726
+ : valueX + (value < 0 ? -5 : 5);
559
727
  return (
560
728
  <g key={`${rowIndex}-${series.key}`}>
561
729
  <rect
@@ -568,30 +736,21 @@ function ChartBody({
568
736
  />
569
737
  {chart.showValueLabels ? (
570
738
  <text
571
- x={
572
- isHorizontalBar
573
- ? valueX + (value < 0 ? -5 : 5)
574
- : x + width / 2
575
- }
739
+ data-value-label="true"
740
+ x={isHorizontalBar ? valueLabelX : x + width / 2}
576
741
  y={
577
742
  isHorizontalBar
578
743
  ? y + height / 2 + valueLabelFontSize * 0.35
579
- : y - 5
744
+ : Math.max(valueLabelFontSize, y - 5)
580
745
  }
581
- fill="hsl(var(--muted-foreground))"
746
+ fill={labelColor}
582
747
  fontSize={valueLabelFontSize}
583
748
  fontWeight={valueLabelFontWeight}
584
749
  textAnchor={
585
750
  isHorizontalBar ? (value < 0 ? "end" : "start") : "middle"
586
751
  }
587
752
  >
588
- {isHorizontalBar
589
- ? formatHorizontalValueLabel(
590
- value,
591
- valueLabelFontSize,
592
- horizontalTextWidthLimit,
593
- )
594
- : formatTick(value)}
753
+ {valueLabel}
595
754
  </text>
596
755
  ) : null}
597
756
  </g>
@@ -663,18 +822,13 @@ function ChartBody({
663
822
  ))}
664
823
  {chart.showValueLabels
665
824
  ? points.map((point, index) => {
666
- const abovePoint =
667
- point.y - valueLabelFontSize - 5 >= margin.top;
668
825
  return (
669
826
  <text
670
827
  key={`value-${index}`}
828
+ data-value-label="true"
671
829
  x={point.x}
672
- y={
673
- abovePoint
674
- ? point.y - 5
675
- : point.y + valueLabelFontSize + 5
676
- }
677
- fill="hsl(var(--muted-foreground))"
830
+ y={lineValueLabelY(point.y, valueLabelFontSize, margin.top)}
831
+ fill={labelColor}
678
832
  fontSize={valueLabelFontSize}
679
833
  fontWeight={valueLabelFontWeight}
680
834
  textAnchor={
@@ -685,7 +839,7 @@ function ChartBody({
685
839
  : "middle"
686
840
  }
687
841
  >
688
- {formatTick(point.value)}
842
+ {formatValueLabel(point.value)}
689
843
  </text>
690
844
  );
691
845
  })
@@ -696,7 +850,13 @@ function ChartBody({
696
850
  );
697
851
  }
698
852
 
699
- export function DataChartRenderer({ chart }: { chart: DataChartWidgetData }) {
853
+ export function DataChartRenderer({
854
+ chart,
855
+ layout,
856
+ }: {
857
+ chart: DataChartWidgetData;
858
+ layout?: AgentChatDataChartLayout;
859
+ }) {
700
860
  const t = useT();
701
861
  const renderedChart =
702
862
  chart.data.length > DATA_WIDGET_MAX_CHART_POINTS
@@ -709,8 +869,13 @@ export function DataChartRenderer({ chart }: { chart: DataChartWidgetData }) {
709
869
  const contentPadding = renderedChart.contentPadding ?? 0;
710
870
  const hasContentBox = contentHeight !== undefined || contentPadding > 0;
711
871
  const labelFontFamily =
872
+ layout?.labelFontFamily ??
712
873
  LABEL_FONT_FAMILIES[renderedChart.labelFontFamily ?? "sans"];
713
- const labelFontSize = renderedChart.labelFontSize ?? 11;
874
+ const labelFontSize = fontSize(
875
+ layout?.labelFontSize,
876
+ "labelFontSize",
877
+ renderedChart.labelFontSize ?? 11,
878
+ );
714
879
  return (
715
880
  <div
716
881
  className="box-border w-full min-w-0 overflow-hidden"
@@ -733,7 +898,11 @@ export function DataChartRenderer({ chart }: { chart: DataChartWidgetData }) {
733
898
  role="img"
734
899
  aria-label={renderedChart.title ?? t("agentChat.widget.dataChart")}
735
900
  >
736
- <ChartBody chart={renderedChart} labelFontSize={labelFontSize} />
901
+ <ChartBody
902
+ chart={renderedChart}
903
+ layout={layout}
904
+ labelFontSize={labelFontSize}
905
+ />
737
906
  </svg>
738
907
  </div>
739
908
  );
@@ -5,6 +5,7 @@ import React, { act } from "react";
5
5
  import { createRoot, type Root } from "react-dom/client";
6
6
  import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
7
7
 
8
+ import type { AgentChatDataChartLayout } from "../../../agentkit/react/data-chart-layout.js";
8
9
  import { createToolkitI18nCatalog } from "../../../i18n.js";
9
10
  import type {
10
11
  DataWidgetAction,
@@ -27,6 +28,7 @@ vi.mock("../../../../hooks/use-toast.js", () => ({ toast: mocks.toast }));
27
28
  vi.mock("./DataChartRenderer.js", () => ({
28
29
  DataChartRenderer: ({
29
30
  chart,
31
+ layout,
30
32
  }: {
31
33
  chart: {
32
34
  contentHeight?: number;
@@ -34,6 +36,7 @@ vi.mock("./DataChartRenderer.js", () => ({
34
36
  labelFontFamily?: string;
35
37
  labelFontSize?: number;
36
38
  };
39
+ layout?: AgentChatDataChartLayout;
37
40
  }) => (
38
41
  <div
39
42
  data-testid="chart-surface"
@@ -41,6 +44,8 @@ vi.mock("./DataChartRenderer.js", () => ({
41
44
  data-content-padding={chart.contentPadding}
42
45
  data-label-font-family={chart.labelFontFamily}
43
46
  data-label-font-size={chart.labelFontSize}
47
+ data-layout-label-font-family={layout?.labelFontFamily}
48
+ data-layout-label-font-size={layout?.labelFontSize}
44
49
  />
45
50
  ),
46
51
  }));
@@ -74,6 +79,7 @@ async function renderWidget(
74
79
  sampled = false,
75
80
  chartDataCount = 1,
76
81
  truncated = false,
82
+ layout?: AgentChatDataChartLayout,
77
83
  ) {
78
84
  const container = document.createElement("div");
79
85
  document.body.appendChild(container);
@@ -115,6 +121,7 @@ async function renderWidget(
115
121
  ],
116
122
  actions: displayActions,
117
123
  }}
124
+ layout={layout}
118
125
  />
119
126
  </AgentNativeI18nProvider>,
120
127
  );
@@ -186,6 +193,66 @@ describe("DataChartWidget display contract", () => {
186
193
  expect(window.location.search).toBe("?tab=chart");
187
194
  });
188
195
 
196
+ it("opts into responsive chart sizing and quiet controls", async () => {
197
+ const container = await renderWidget(
198
+ [
199
+ {
200
+ id: "explore",
201
+ label: "Explore",
202
+ href: "/explore",
203
+ icon: "explore",
204
+ presentation: "icon",
205
+ },
206
+ ],
207
+ undefined,
208
+ false,
209
+ 1,
210
+ false,
211
+ {
212
+ maxWidth: 720,
213
+ labelFontFamily: "IBM Plex Sans",
214
+ labelFontSize: 14,
215
+ titleFontFamily: "IBM Plex Sans",
216
+ titleFontSize: 14,
217
+ titleFontWeight: 400,
218
+ titleLineHeight: 20,
219
+ titleTone: "muted",
220
+ actionsVariant: "quiet",
221
+ },
222
+ );
223
+ const card = container.querySelector<HTMLElement>(".my-4");
224
+ const title = card?.querySelector<HTMLElement>(".border-b .min-w-0");
225
+ const chart = container.querySelector<HTMLElement>(
226
+ '[data-testid="chart-surface"]',
227
+ );
228
+ const primary = Array.from(container.querySelectorAll("a")).find((link) =>
229
+ link.textContent?.includes("Open report"),
230
+ );
231
+ const explore = container.querySelector<HTMLAnchorElement>(
232
+ 'a[aria-label="Explore"]',
233
+ );
234
+
235
+ expect(card?.style.width).toBe("100%");
236
+ expect(card?.style.maxWidth).toBe("720px");
237
+ expect(card?.style.marginInline).toBe("auto");
238
+ expect(title?.style.fontFamily).toContain("IBM Plex Sans");
239
+ expect(title?.style.fontSize).toBe("14px");
240
+ expect(title?.style.fontWeight).toBe("400");
241
+ expect(title?.style.lineHeight).toBe("20px");
242
+ expect(title?.classList.contains("text-muted-foreground")).toBe(true);
243
+ expect(chart?.dataset.layoutLabelFontFamily).toBe("IBM Plex Sans");
244
+ expect(chart?.dataset.layoutLabelFontSize).toBe("14");
245
+ expect(primary?.classList.contains("border")).toBe(false);
246
+ expect(explore?.classList.contains("border")).toBe(false);
247
+ expect(explore?.classList.contains("size-7")).toBe(true);
248
+ const chartActions = container.querySelector<HTMLButtonElement>(
249
+ '[aria-label="Chart actions"]',
250
+ );
251
+ expect(chartActions).not.toBeNull();
252
+ expect(chartActions?.classList.contains("size-7")).toBe(true);
253
+ expect(chartActions?.classList.contains("border")).toBe(false);
254
+ });
255
+
189
256
  it("announces incomplete chart data and stays quiet for complete data", async () => {
190
257
  const cappedContainer = await renderWidget(
191
258
  undefined,
@@ -1,7 +1,8 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
2
  import { DATA_WIDGET_MAX_CHART_POINTS } from "@agent-native/core/data-widgets";
3
- import { lazy, Suspense, useEffect, useState } from "react";
3
+ import { lazy, Suspense, useEffect, useState, type CSSProperties } from "react";
4
4
 
5
+ import type { AgentChatDataChartLayout } from "../../../agentkit/react/data-chart-layout.js";
5
6
  import type {
6
7
  DataChartWidget as DataChartWidgetData,
7
8
  DataWidgetDisplay,
@@ -20,9 +21,11 @@ const LazyDataChartRenderer = lazy(() =>
20
21
  export function DataChartWidget({
21
22
  chart,
22
23
  display,
24
+ layout,
23
25
  }: {
24
26
  chart: DataChartWidgetData;
25
27
  display?: DataWidgetDisplay;
28
+ layout?: AgentChatDataChartLayout;
26
29
  }) {
27
30
  const t = useT();
28
31
  const [mounted, setMounted] = useState(false);
@@ -32,6 +35,16 @@ export function DataChartWidget({
32
35
  chart.sampled === true ||
33
36
  chart.truncated === true ||
34
37
  chart.data.length > DATA_WIDGET_MAX_CHART_POINTS;
38
+ const maxWidth = layout?.maxWidth;
39
+ if (maxWidth !== undefined && (!Number.isFinite(maxWidth) || maxWidth <= 0)) {
40
+ throw new RangeError(
41
+ "dataChartLayout.maxWidth must be a positive CSS length",
42
+ );
43
+ }
44
+ const cardStyle: CSSProperties | undefined =
45
+ maxWidth === undefined
46
+ ? undefined
47
+ : { width: "100%", maxWidth: `${maxWidth}px`, marginInline: "auto" };
35
48
 
36
49
  useEffect(() => {
37
50
  setMounted(true);
@@ -56,22 +69,43 @@ export function DataChartWidget({
56
69
  );
57
70
 
58
71
  return (
59
- <div className="my-4 overflow-hidden rounded-[3px] border border-border bg-background text-foreground shadow-sm">
72
+ <div
73
+ className="my-4 overflow-hidden rounded-[3px] border border-border bg-background text-foreground shadow-sm"
74
+ style={cardStyle}
75
+ >
60
76
  <div className="flex h-12 items-center gap-2 border-b border-border px-3">
61
- <div className="min-w-0 flex-1 truncate text-sm font-medium">
77
+ <div
78
+ className={`min-w-0 flex-1 truncate text-sm font-medium ${layout?.titleTone === "muted" ? "text-muted-foreground" : ""}`}
79
+ style={{
80
+ fontFamily: layout?.titleFontFamily,
81
+ fontSize:
82
+ layout?.titleFontSize === undefined
83
+ ? undefined
84
+ : `${layout.titleFontSize}px`,
85
+ fontWeight: layout?.titleFontWeight,
86
+ lineHeight:
87
+ layout?.titleLineHeight === undefined
88
+ ? undefined
89
+ : `${layout.titleLineHeight}px`,
90
+ color:
91
+ layout?.titleTone === "muted"
92
+ ? "hsl(var(--muted-foreground))"
93
+ : undefined,
94
+ }}
95
+ >
62
96
  {chart.title ?? t("agentChat.widget.dataChart")}
63
97
  </div>
64
98
  <DataWidgetDisplayControls
65
99
  display={display}
66
100
  chartActions={chart.actions}
67
- variant="secondary"
101
+ variant={layout?.actionsVariant ?? "secondary"}
68
102
  />
69
103
  </div>
70
104
  {!mounted ? (
71
105
  fallback
72
106
  ) : (
73
107
  <Suspense fallback={fallback}>
74
- <LazyDataChartRenderer chart={chart} />
108
+ <LazyDataChartRenderer chart={chart} layout={layout} />
75
109
  </Suspense>
76
110
  )}
77
111
  {hasOmittedRows ? (