@agent-native/toolkit 0.213.1 → 0.216.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 (222) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/dist/app/agentkit/react/chat.d.ts.map +1 -1
  3. package/dist/app/agentkit/react/chat.js +2 -2
  4. package/dist/app/agentkit/react/chat.js.map +1 -1
  5. package/dist/app/agentkit/react/components.d.ts +4 -1
  6. package/dist/app/agentkit/react/components.d.ts.map +1 -1
  7. package/dist/app/agentkit/react/components.js +73 -13
  8. package/dist/app/agentkit/react/components.js.map +1 -1
  9. package/dist/app/agentkit/react/context.d.ts +19 -1
  10. package/dist/app/agentkit/react/context.d.ts.map +1 -1
  11. package/dist/app/agentkit/react/context.js +7 -1
  12. package/dist/app/agentkit/react/context.js.map +1 -1
  13. package/dist/app/agentkit/react/index.d.ts +2 -0
  14. package/dist/app/agentkit/react/index.d.ts.map +1 -1
  15. package/dist/app/agentkit/react/index.js +2 -0
  16. package/dist/app/agentkit/react/index.js.map +1 -1
  17. package/dist/app/agentkit/react/message-content-order.d.ts +21 -0
  18. package/dist/app/agentkit/react/message-content-order.d.ts.map +1 -0
  19. package/dist/app/agentkit/react/message-content-order.js +173 -0
  20. package/dist/app/agentkit/react/message-content-order.js.map +1 -0
  21. package/dist/app/agentkit/react/root.d.ts +6 -1
  22. package/dist/app/agentkit/react/root.d.ts.map +1 -1
  23. package/dist/app/agentkit/react/root.js +2 -2
  24. package/dist/app/agentkit/react/root.js.map +1 -1
  25. package/dist/app/agentkit/react/styles.css +45 -8
  26. package/dist/app/agentkit/react/transcript-layout.d.ts +51 -0
  27. package/dist/app/agentkit/react/transcript-layout.d.ts.map +1 -0
  28. package/dist/app/agentkit/react/transcript-layout.js +34 -0
  29. package/dist/app/agentkit/react/transcript-layout.js.map +1 -0
  30. package/dist/app/chat/AgentKitAssistantChat.d.ts.map +1 -1
  31. package/dist/app/chat/AgentKitAssistantChat.js +9 -4
  32. package/dist/app/chat/AgentKitAssistantChat.js.map +1 -1
  33. package/dist/app/chat/AgentPanel.d.ts +16 -2
  34. package/dist/app/chat/AgentPanel.d.ts.map +1 -1
  35. package/dist/app/chat/AgentPanel.js +26 -5
  36. package/dist/app/chat/AgentPanel.js.map +1 -1
  37. package/dist/app/chat/chat/index.d.ts +2 -0
  38. package/dist/app/chat/chat/index.d.ts.map +1 -1
  39. package/dist/app/chat/chat/index.js.map +1 -1
  40. package/dist/app/chat/chat/surface-types.d.ts +15 -0
  41. package/dist/app/chat/chat/surface-types.d.ts.map +1 -1
  42. package/dist/app/chat/chat/surface-types.js.map +1 -1
  43. package/dist/app/chat/chat/tool-call-display.d.ts +7 -2
  44. package/dist/app/chat/chat/tool-call-display.d.ts.map +1 -1
  45. package/dist/app/chat/chat/tool-call-display.js +10 -9
  46. package/dist/app/chat/chat/tool-call-display.js.map +1 -1
  47. package/dist/app/chat/chat/widgets/DataChartRenderer.d.ts.map +1 -1
  48. package/dist/app/chat/chat/widgets/DataChartRenderer.js +300 -33
  49. package/dist/app/chat/chat/widgets/DataChartRenderer.js.map +1 -1
  50. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts +3 -2
  51. package/dist/app/chat/chat/widgets/DataChartWidget.d.ts.map +1 -1
  52. package/dist/app/chat/chat/widgets/DataChartWidget.js +18 -9
  53. package/dist/app/chat/chat/widgets/DataChartWidget.js.map +1 -1
  54. package/dist/app/chat/chat/widgets/DataInsightsWidget.js +1 -1
  55. package/dist/app/chat/chat/widgets/DataInsightsWidget.js.map +1 -1
  56. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts +3 -2
  57. package/dist/app/chat/chat/widgets/DataTableWidget.d.ts.map +1 -1
  58. package/dist/app/chat/chat/widgets/DataTableWidget.js +10 -66
  59. package/dist/app/chat/chat/widgets/DataTableWidget.js.map +1 -1
  60. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts +11 -0
  61. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.d.ts.map +1 -0
  62. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js +133 -0
  63. package/dist/app/chat/chat/widgets/DataWidgetDisplayControls.js.map +1 -0
  64. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js +2 -2
  65. package/dist/app/chat/chat/widgets/builtin-tool-renderers.js.map +1 -1
  66. package/dist/app/chat/mcp-apps/McpAppRenderer.d.ts.map +1 -1
  67. package/dist/app/chat/mcp-apps/McpAppRenderer.js +6 -5
  68. package/dist/app/chat/mcp-apps/McpAppRenderer.js.map +1 -1
  69. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts +2 -0
  70. package/dist/app/chat/mcp-apps/mcp-app-theme.d.ts.map +1 -0
  71. package/dist/app/chat/mcp-apps/mcp-app-theme.js +11 -0
  72. package/dist/app/chat/mcp-apps/mcp-app-theme.js.map +1 -0
  73. package/dist/app/i18n/catalogs/ar-SA.d.ts.map +1 -1
  74. package/dist/app/i18n/catalogs/ar-SA.js +9 -0
  75. package/dist/app/i18n/catalogs/ar-SA.js.map +1 -1
  76. package/dist/app/i18n/catalogs/de-DE.d.ts.map +1 -1
  77. package/dist/app/i18n/catalogs/de-DE.js +9 -0
  78. package/dist/app/i18n/catalogs/de-DE.js.map +1 -1
  79. package/dist/app/i18n/catalogs/en-US.d.ts +9 -0
  80. package/dist/app/i18n/catalogs/en-US.d.ts.map +1 -1
  81. package/dist/app/i18n/catalogs/en-US.js +9 -0
  82. package/dist/app/i18n/catalogs/en-US.js.map +1 -1
  83. package/dist/app/i18n/catalogs/es-ES.d.ts.map +1 -1
  84. package/dist/app/i18n/catalogs/es-ES.js +9 -0
  85. package/dist/app/i18n/catalogs/es-ES.js.map +1 -1
  86. package/dist/app/i18n/catalogs/fr-FR.d.ts.map +1 -1
  87. package/dist/app/i18n/catalogs/fr-FR.js +9 -0
  88. package/dist/app/i18n/catalogs/fr-FR.js.map +1 -1
  89. package/dist/app/i18n/catalogs/hi-IN.d.ts.map +1 -1
  90. package/dist/app/i18n/catalogs/hi-IN.js +9 -0
  91. package/dist/app/i18n/catalogs/hi-IN.js.map +1 -1
  92. package/dist/app/i18n/catalogs/ja-JP.d.ts.map +1 -1
  93. package/dist/app/i18n/catalogs/ja-JP.js +9 -0
  94. package/dist/app/i18n/catalogs/ja-JP.js.map +1 -1
  95. package/dist/app/i18n/catalogs/ko-KR.d.ts.map +1 -1
  96. package/dist/app/i18n/catalogs/ko-KR.js +9 -0
  97. package/dist/app/i18n/catalogs/ko-KR.js.map +1 -1
  98. package/dist/app/i18n/catalogs/pt-BR.d.ts.map +1 -1
  99. package/dist/app/i18n/catalogs/pt-BR.js +9 -0
  100. package/dist/app/i18n/catalogs/pt-BR.js.map +1 -1
  101. package/dist/app/i18n/catalogs/zh-CN.d.ts.map +1 -1
  102. package/dist/app/i18n/catalogs/zh-CN.js +9 -0
  103. package/dist/app/i18n/catalogs/zh-CN.js.map +1 -1
  104. package/dist/app/i18n/catalogs/zh-TW.d.ts.map +1 -1
  105. package/dist/app/i18n/catalogs/zh-TW.js +9 -0
  106. package/dist/app/i18n/catalogs/zh-TW.js.map +1 -1
  107. package/dist/app/providers/AppProviders.d.ts +2 -1
  108. package/dist/app/providers/AppProviders.d.ts.map +1 -1
  109. package/dist/app/providers/AppProviders.js +4 -3
  110. package/dist/app/providers/AppProviders.js.map +1 -1
  111. package/dist/app/providers/ThemeFamilySync.d.ts +2 -0
  112. package/dist/app/providers/ThemeFamilySync.d.ts.map +1 -0
  113. package/dist/app/providers/ThemeFamilySync.js +6 -0
  114. package/dist/app/providers/ThemeFamilySync.js.map +1 -0
  115. package/dist/app/settings/account/PreferencesSettings.d.ts.map +1 -1
  116. package/dist/app/settings/account/PreferencesSettings.js +2 -1
  117. package/dist/app/settings/account/PreferencesSettings.js.map +1 -1
  118. package/dist/app/settings/account/ThemeFamilySettings.d.ts +2 -0
  119. package/dist/app/settings/account/ThemeFamilySettings.d.ts.map +1 -0
  120. package/dist/app/settings/account/ThemeFamilySettings.js +31 -0
  121. package/dist/app/settings/account/ThemeFamilySettings.js.map +1 -0
  122. package/dist/app/settings/account/search-entries.d.ts.map +1 -1
  123. package/dist/app/settings/account/search-entries.js +6 -0
  124. package/dist/app/settings/account/search-entries.js.map +1 -1
  125. package/dist/app/shared/DefaultSpinner.d.ts.map +1 -1
  126. package/dist/app/shared/DefaultSpinner.js +4 -5
  127. package/dist/app/shared/DefaultSpinner.js.map +1 -1
  128. package/dist/app/shared/ErrorBoundary.d.ts +1 -0
  129. package/dist/app/shared/ErrorBoundary.d.ts.map +1 -1
  130. package/dist/app/shared/ErrorBoundary.js +27 -12
  131. package/dist/app/shared/ErrorBoundary.js.map +1 -1
  132. package/dist/app/shared/PoweredByBadge.d.ts.map +1 -1
  133. package/dist/app/shared/PoweredByBadge.js +8 -49
  134. package/dist/app/shared/PoweredByBadge.js.map +1 -1
  135. package/dist/app/styles/agent-conversation.css +0 -23
  136. package/dist/app/styles/agent-native.css +1 -1
  137. package/dist/app/styles/theme-families.css +66 -0
  138. package/dist/composer/ComposerContextBanner.d.ts +24 -0
  139. package/dist/composer/ComposerContextBanner.d.ts.map +1 -0
  140. package/dist/composer/ComposerContextBanner.js +9 -0
  141. package/dist/composer/ComposerContextBanner.js.map +1 -0
  142. package/dist/composer/ComposerModePicker.d.ts +26 -0
  143. package/dist/composer/ComposerModePicker.d.ts.map +1 -0
  144. package/dist/composer/ComposerModePicker.js +14 -0
  145. package/dist/composer/ComposerModePicker.js.map +1 -0
  146. package/dist/composer/index.d.ts +2 -0
  147. package/dist/composer/index.d.ts.map +1 -1
  148. package/dist/composer/index.js +2 -0
  149. package/dist/composer/index.js.map +1 -1
  150. package/dist/styles.css +1 -0
  151. package/package.json +3 -3
  152. package/src/app/agentkit/react/chat.tsx +2 -0
  153. package/src/app/agentkit/react/components.interaction.spec.tsx +107 -0
  154. package/src/app/agentkit/react/components.spec.tsx +40 -1
  155. package/src/app/agentkit/react/components.tsx +186 -31
  156. package/src/app/agentkit/react/context.tsx +29 -0
  157. package/src/app/agentkit/react/index.ts +2 -0
  158. package/src/app/agentkit/react/lifecycle.spec.tsx +289 -4
  159. package/src/app/agentkit/react/message-content-order.spec.ts +190 -0
  160. package/src/app/agentkit/react/message-content-order.ts +246 -0
  161. package/src/app/agentkit/react/root.tsx +11 -0
  162. package/src/app/agentkit/react/styles.css +45 -8
  163. package/src/app/agentkit/react/transcript-layout.spec.ts +72 -0
  164. package/src/app/agentkit/react/transcript-layout.ts +97 -0
  165. package/src/app/auth/AuthPage.attribution.spec.tsx +6 -0
  166. package/src/app/chat/AgentKitAssistantChat.spec.tsx +58 -0
  167. package/src/app/chat/AgentKitAssistantChat.tsx +15 -1
  168. package/src/app/chat/AgentPanel.header.spec.ts +72 -14
  169. package/src/app/chat/AgentPanel.page-header-share.spec.tsx +101 -0
  170. package/src/app/chat/AgentPanel.tsx +111 -55
  171. package/src/app/chat/chat/index.ts +2 -0
  172. package/src/app/chat/chat/surface-types.ts +18 -0
  173. package/src/app/chat/chat/tool-call-display.spec.tsx +83 -0
  174. package/src/app/chat/chat/tool-call-display.tsx +40 -8
  175. package/src/app/chat/chat/widgets/DataChartRenderer.spec.tsx +849 -0
  176. package/src/app/chat/chat/widgets/DataChartRenderer.tsx +567 -72
  177. package/src/app/chat/chat/widgets/DataChartWidget.spec.tsx +432 -0
  178. package/src/app/chat/chat/widgets/DataChartWidget.ssr.spec.tsx +25 -0
  179. package/src/app/chat/chat/widgets/DataChartWidget.tsx +58 -33
  180. package/src/app/chat/chat/widgets/DataInsightsWidget.tsx +2 -2
  181. package/src/app/chat/chat/widgets/DataTableWidget.spec.tsx +37 -3
  182. package/src/app/chat/chat/widgets/DataTableWidget.tsx +22 -86
  183. package/src/app/chat/chat/widgets/DataWidgetDisplayControls.tsx +321 -0
  184. package/src/app/chat/chat/widgets/builtin-tool-renderers.tsx +5 -5
  185. package/src/app/chat/mcp-apps/McpAppRenderer.tsx +10 -5
  186. package/src/app/chat/mcp-apps/mcp-app-theme.spec.ts +30 -0
  187. package/src/app/chat/mcp-apps/mcp-app-theme.ts +11 -0
  188. package/src/app/i18n/catalogs/ar-SA.ts +10 -0
  189. package/src/app/i18n/catalogs/de-DE.ts +11 -0
  190. package/src/app/i18n/catalogs/en-US.ts +11 -0
  191. package/src/app/i18n/catalogs/es-ES.ts +12 -0
  192. package/src/app/i18n/catalogs/fr-FR.ts +12 -0
  193. package/src/app/i18n/catalogs/hi-IN.ts +10 -0
  194. package/src/app/i18n/catalogs/ja-JP.ts +11 -0
  195. package/src/app/i18n/catalogs/ko-KR.ts +10 -0
  196. package/src/app/i18n/catalogs/pt-BR.ts +11 -0
  197. package/src/app/i18n/catalogs/zh-CN.ts +9 -0
  198. package/src/app/i18n/catalogs/zh-TW.ts +10 -0
  199. package/src/app/providers/AppProviders.spec.tsx +71 -0
  200. package/src/app/providers/AppProviders.tsx +10 -0
  201. package/src/app/providers/ThemeFamilySync.tsx +6 -0
  202. package/src/app/settings/account/PreferencesSettings.spec.tsx +13 -2
  203. package/src/app/settings/account/PreferencesSettings.tsx +8 -0
  204. package/src/app/settings/account/ThemeFamilySettings.tsx +61 -0
  205. package/src/app/settings/account/search-entries.ts +6 -0
  206. package/src/app/shared/DefaultSpinner.spec.tsx +14 -0
  207. package/src/app/shared/DefaultSpinner.tsx +4 -5
  208. package/src/app/shared/ErrorBoundary.spec.tsx +45 -2
  209. package/src/app/shared/ErrorBoundary.tsx +37 -12
  210. package/src/app/shared/PoweredByBadge.spec.tsx +16 -0
  211. package/src/app/shared/PoweredByBadge.tsx +8 -50
  212. package/src/app/styles/agent-conversation.css +0 -23
  213. package/src/app/styles/agent-native.css +1 -1
  214. package/src/app/styles/agent-native.spec.ts +18 -0
  215. package/src/app/styles/theme-families.css +66 -0
  216. package/src/app/styles/theme-families.spec.ts +29 -0
  217. package/src/composer/ComposerContextBanner.spec.tsx +100 -0
  218. package/src/composer/ComposerContextBanner.tsx +77 -0
  219. package/src/composer/ComposerModePicker.spec.tsx +101 -0
  220. package/src/composer/ComposerModePicker.tsx +98 -0
  221. package/src/composer/index.ts +9 -0
  222. package/src/styles.css +1 -0
@@ -1,19 +1,28 @@
1
1
  import { useT } from "@agent-native/core/client/i18n";
2
+ import { DATA_WIDGET_MAX_CHART_POINTS } from "@agent-native/core/data-widgets";
2
3
 
3
4
  import type { DataChartWidget as DataChartWidgetData } from "./data-widget-types.js";
4
5
 
5
6
  const DEFAULT_COLORS = [
6
- "hsl(var(--primary))",
7
- "hsl(199 89% 48%)",
8
- "hsl(151 55% 42%)",
9
- "hsl(35 92% 50%)",
7
+ "hsl(var(--chart-1, var(--primary)))",
8
+ "hsl(var(--chart-2, 199 89% 48%))",
9
+ "hsl(var(--chart-3, 151 55% 42%))",
10
+ "hsl(var(--chart-4, 35 92% 50%))",
10
11
  ];
11
12
 
12
13
  const WIDTH = 640;
13
- const HEIGHT = 240;
14
+ const HEIGHT = 360;
14
15
  const MARGIN = { top: 14, right: 18, bottom: 34, left: 46 };
15
- const PLOT_WIDTH = WIDTH - MARGIN.left - MARGIN.right;
16
- const PLOT_HEIGHT = HEIGHT - MARGIN.top - MARGIN.bottom;
16
+ const Y_AXIS_TITLE_X = 14;
17
+ const VALUE_AXIS_TICK_LABEL_INSET = 8;
18
+ const VALUE_AXIS_TITLE_TICK_GAP = 8;
19
+ const MAX_LABEL_LENGTH = 16;
20
+ const MIN_PLOT_WIDTH = 32;
21
+ const LABEL_FONT_FAMILIES = {
22
+ sans: "var(--font-sans, ui-sans-serif, system-ui, sans-serif)",
23
+ serif: "ui-serif, Georgia, Cambria, serif",
24
+ mono: "ui-monospace, SFMono-Regular, Menlo, monospace",
25
+ } as const;
17
26
 
18
27
  function numericValue(
19
28
  row: Record<string, unknown>,
@@ -34,9 +43,83 @@ function formatTick(value: number): string {
34
43
  return value.toFixed(1);
35
44
  }
36
45
 
46
+ function formatAxisTick(value: number, interval: number): string {
47
+ const [mantissa, exponent = "0"] = interval
48
+ .toString()
49
+ .toLowerCase()
50
+ .split("e");
51
+ const decimalPlaces = Math.max(
52
+ 0,
53
+ (mantissa.split(".")[1]?.length ?? 0) - Number(exponent),
54
+ );
55
+ if (Math.abs(value) >= 1000) {
56
+ return value.toLocaleString(undefined, {
57
+ minimumFractionDigits: decimalPlaces,
58
+ maximumFractionDigits: decimalPlaces,
59
+ });
60
+ }
61
+ return value.toFixed(decimalPlaces);
62
+ }
63
+
37
64
  function labelFor(value: unknown): string {
38
- const label = value == null ? "" : String(value);
39
- return label.length > 16 ? `${label.slice(0, 15)}...` : label;
65
+ const characters = Array.from(value == null ? "" : String(value));
66
+ return characters.length > MAX_LABEL_LENGTH
67
+ ? `${characters.slice(0, MAX_LABEL_LENGTH - 3).join("")}...`
68
+ : characters.join("");
69
+ }
70
+
71
+ function estimateTextWidth(text: string, fontSize: number): number {
72
+ return Array.from(text).reduce((width, character) => {
73
+ const codePoint = character.codePointAt(0) ?? 0;
74
+ return width + (codePoint >= 0x2e80 ? fontSize : fontSize * 0.65);
75
+ }, 0);
76
+ }
77
+
78
+ function formatCompactTick(
79
+ value: number,
80
+ fontSize: number,
81
+ maxWidth: number,
82
+ ): string {
83
+ for (let fractionDigits = 15; fractionDigits >= 0; fractionDigits -= 1) {
84
+ const [mantissa = "0", exponent = "0"] = value
85
+ .toExponential(fractionDigits)
86
+ .split("e");
87
+ const label = `${mantissa.replace(/\.?0+$/, "")}e${exponent}`;
88
+ if (estimateTextWidth(label, fontSize) <= maxWidth) return label;
89
+ }
90
+ return value.toExponential(0);
91
+ }
92
+
93
+ function maximumHorizontalLabelWidth(
94
+ categoryMargin: number,
95
+ includeValueLabels: boolean,
96
+ ): number {
97
+ let lower = 0;
98
+ let upper = WIDTH;
99
+ while (lower < upper) {
100
+ const candidate = Math.ceil((lower + upper) / 2);
101
+ const axisInset = Math.ceil(candidate / 2) + 2;
102
+ const valueLabelInset = includeValueLabels ? candidate + 13 : 0;
103
+ const leftMargin = Math.max(categoryMargin, axisInset) + valueLabelInset;
104
+ const rightMargin = Math.max(MARGIN.right, axisInset, valueLabelInset);
105
+ if (WIDTH - leftMargin - rightMargin >= MIN_PLOT_WIDTH) {
106
+ lower = candidate;
107
+ } else {
108
+ upper = candidate - 1;
109
+ }
110
+ }
111
+ return lower;
112
+ }
113
+
114
+ function formatHorizontalValueLabel(
115
+ value: number,
116
+ fontSize: number,
117
+ maxWidth: number,
118
+ ): string {
119
+ const label = formatTick(value);
120
+ return estimateTextWidth(label, fontSize) > maxWidth
121
+ ? formatCompactTick(value, fontSize, maxWidth)
122
+ : label;
40
123
  }
41
124
 
42
125
  function rangeFor(chart: DataChartWidgetData) {
@@ -61,76 +144,388 @@ function rangeFor(chart: DataChartWidgetData) {
61
144
  return { min, max };
62
145
  }
63
146
 
64
- function ChartBody({ chart }: { chart: DataChartWidgetData }) {
147
+ function ChartBody({
148
+ chart,
149
+ labelFontSize,
150
+ }: {
151
+ chart: DataChartWidgetData;
152
+ labelFontSize: number;
153
+ }) {
154
+ const categoryGapRatio = chart.horizontalCategoryGapRatio;
155
+ if (
156
+ categoryGapRatio !== undefined &&
157
+ (!Number.isFinite(categoryGapRatio) ||
158
+ categoryGapRatio < 0 ||
159
+ categoryGapRatio > 0.9)
160
+ ) {
161
+ throw new Error("Horizontal category gap ratio must be between 0 and 0.9");
162
+ }
65
163
  const { min, max } = rangeFor(chart);
66
- const yFor = (value: number) =>
67
- MARGIN.top + ((max - value) / (max - min)) * PLOT_HEIGHT;
164
+ const isHorizontalBar = chart.type === "bar" && chart.layout === "horizontal";
165
+ const tickInterval = chart.valueAxisTickInterval;
166
+ const gridTickValues =
167
+ tickInterval === undefined
168
+ ? Array.from({ length: 4 }, (_, index) => min + ((max - min) * index) / 3)
169
+ : (() => {
170
+ if (!Number.isFinite(tickInterval) || tickInterval < 1e-12) {
171
+ throw new Error(
172
+ "Chart value-axis interval must be finite and positive",
173
+ );
174
+ }
175
+ if (!Number.isFinite(max - min)) {
176
+ throw new Error("Chart value-axis range must be finite");
177
+ }
178
+ const firstTick = Math.ceil(min / tickInterval - 1e-9);
179
+ const lastTick = Math.floor(max / tickInterval + 1e-9);
180
+ const tickCount = lastTick - firstTick + 1;
181
+ if (
182
+ !Number.isSafeInteger(firstTick) ||
183
+ !Number.isSafeInteger(lastTick) ||
184
+ !Number.isSafeInteger(tickCount) ||
185
+ tickCount <= 0 ||
186
+ tickCount > 100
187
+ ) {
188
+ throw new Error("Chart value-axis interval exceeds the tick limit");
189
+ }
190
+ return Array.from(
191
+ { length: tickCount },
192
+ (_, index) => (firstTick + index) * tickInterval,
193
+ );
194
+ })();
195
+ const axisTitleFontSize = chart.axisTitleFontSize ?? 11;
196
+ const horizontalCategoryWidth = isHorizontalBar
197
+ ? Math.max(
198
+ 0,
199
+ ...chart.data.map((row) =>
200
+ estimateTextWidth(labelFor(row[chart.xKey]), labelFontSize),
201
+ ),
202
+ )
203
+ : 0;
204
+ const categoryMargin =
205
+ Math.max(MARGIN.left, horizontalCategoryWidth + 14) +
206
+ (chart.yAxisTitle ? 20 : 0);
207
+ const categoryLabelX = categoryMargin - 8;
208
+ const horizontalTextWidthLimit = isHorizontalBar
209
+ ? maximumHorizontalLabelWidth(
210
+ categoryMargin,
211
+ Boolean(chart.showValueLabels),
212
+ )
213
+ : 0;
214
+ const verticalValueTickMargin = isHorizontalBar
215
+ ? 0
216
+ : chart.yAxisTitle
217
+ ? Y_AXIS_TITLE_X +
218
+ axisTitleFontSize / 2 +
219
+ VALUE_AXIS_TITLE_TICK_GAP +
220
+ VALUE_AXIS_TICK_LABEL_INSET
221
+ : VALUE_AXIS_TICK_LABEL_INSET * 2;
222
+ const valueTickLabels = gridTickValues.map((value) => {
223
+ const label =
224
+ tickInterval === undefined
225
+ ? formatTick(value)
226
+ : formatAxisTick(value, tickInterval);
227
+ const maximumWidth = isHorizontalBar
228
+ ? horizontalTextWidthLimit
229
+ : WIDTH - MARGIN.right - MIN_PLOT_WIDTH - verticalValueTickMargin;
230
+ return estimateTextWidth(label, labelFontSize) > maximumWidth
231
+ ? formatCompactTick(value, labelFontSize, maximumWidth)
232
+ : label;
233
+ });
234
+ const valueTickLabelWidth = Math.max(
235
+ 0,
236
+ ...valueTickLabels.map((label) => estimateTextWidth(label, labelFontSize)),
237
+ );
238
+ const customValueTickLabelWidth = isHorizontalBar ? 0 : valueTickLabelWidth;
239
+ const horizontalValueTickLabelInset = isHorizontalBar
240
+ ? Math.ceil(valueTickLabelWidth / 2) + 2
241
+ : 0;
242
+ const customValueTickWidth = isHorizontalBar ? 0 : customValueTickLabelWidth;
243
+ const horizontalPositiveValueWidth =
244
+ isHorizontalBar && chart.showValueLabels
245
+ ? Math.max(
246
+ 0,
247
+ ...chart.data.flatMap((row) =>
248
+ chart.series
249
+ .map((series) => numericValue(row, series.key))
250
+ .filter((value): value is number => value !== null && value >= 0)
251
+ .map((value) =>
252
+ estimateTextWidth(
253
+ formatHorizontalValueLabel(
254
+ value,
255
+ chart.valueLabelFontSize ?? 11,
256
+ horizontalTextWidthLimit,
257
+ ),
258
+ chart.valueLabelFontSize ?? 11,
259
+ ),
260
+ ),
261
+ ),
262
+ )
263
+ : 0;
264
+ const horizontalNegativeValueWidth =
265
+ isHorizontalBar && chart.showValueLabels
266
+ ? Math.max(
267
+ 0,
268
+ ...chart.data.flatMap((row) =>
269
+ chart.series
270
+ .map((series) => numericValue(row, series.key))
271
+ .filter((value): value is number => value !== null && value < 0)
272
+ .map((value) =>
273
+ estimateTextWidth(
274
+ formatHorizontalValueLabel(
275
+ value,
276
+ chart.valueLabelFontSize ?? 11,
277
+ horizontalTextWidthLimit,
278
+ ),
279
+ chart.valueLabelFontSize ?? 11,
280
+ ),
281
+ ),
282
+ ),
283
+ )
284
+ : 0;
285
+ const margin = {
286
+ ...MARGIN,
287
+ left:
288
+ Math.max(
289
+ categoryMargin,
290
+ customValueTickWidth + verticalValueTickMargin,
291
+ horizontalValueTickLabelInset,
292
+ ) +
293
+ (horizontalNegativeValueWidth > 0
294
+ ? horizontalNegativeValueWidth + 13
295
+ : 0),
296
+ right: Math.max(
297
+ MARGIN.right,
298
+ horizontalPositiveValueWidth + 13,
299
+ horizontalValueTickLabelInset,
300
+ ),
301
+ bottom: MARGIN.bottom + (chart.xAxisTitle ? 16 : 0),
302
+ };
303
+ const plotWidth = WIDTH - margin.left - margin.right;
304
+ const plotHeight = HEIGHT - margin.top - margin.bottom;
305
+ const xForValue = (value: number) =>
306
+ margin.left + ((value - min) / (max - min)) * plotWidth;
307
+ const yForValue = (value: number) =>
308
+ margin.top + ((max - value) / (max - min)) * plotHeight;
68
309
  const xForPoint = (index: number) =>
69
310
  chart.data.length <= 1
70
- ? MARGIN.left + PLOT_WIDTH / 2
71
- : MARGIN.left + (index / (chart.data.length - 1)) * PLOT_WIDTH;
72
- const gridTicks = Array.from({ length: 4 }, (_, index) => {
73
- const value = min + ((max - min) * index) / 3;
74
- return { value, y: yFor(value) };
75
- });
311
+ ? margin.left + plotWidth / 2
312
+ : margin.left + (index / (chart.data.length - 1)) * plotWidth;
313
+ const yForRow = (index: number) =>
314
+ margin.top + ((index + 0.5) * plotHeight) / Math.max(1, chart.data.length);
315
+ const axisLabelMinSpacing = isHorizontalBar
316
+ ? valueTickLabelWidth + 8
317
+ : labelFontSize + 4;
318
+ const visibleAxisLabelCount = Math.max(
319
+ 1,
320
+ Math.floor(
321
+ (isHorizontalBar ? plotWidth : plotHeight) / axisLabelMinSpacing,
322
+ ) + 1,
323
+ );
324
+ const axisLabelStride = Math.max(
325
+ 1,
326
+ Math.ceil(
327
+ (gridTickValues.length - 1) / Math.max(1, visibleAxisLabelCount - 1),
328
+ ),
329
+ );
330
+ const axisLabelIndexes = new Set(
331
+ gridTickValues.flatMap((_, index) =>
332
+ index % axisLabelStride === 0 &&
333
+ (visibleAxisLabelCount > 1 || index === 0)
334
+ ? [index]
335
+ : [],
336
+ ),
337
+ );
338
+ const finalAxisLabelIndex = gridTickValues.length - 1;
339
+ if (
340
+ visibleAxisLabelCount > 1 &&
341
+ finalAxisLabelIndex >= 0 &&
342
+ !axisLabelIndexes.has(finalAxisLabelIndex)
343
+ ) {
344
+ if (isHorizontalBar) {
345
+ axisLabelIndexes.add(finalAxisLabelIndex);
346
+ } else {
347
+ const precedingAxisLabelIndex =
348
+ Math.floor(finalAxisLabelIndex / axisLabelStride) * axisLabelStride;
349
+ const tickSpacing = plotHeight / Math.max(1, finalAxisLabelIndex);
350
+ const finalLabelSpacing =
351
+ (finalAxisLabelIndex - precedingAxisLabelIndex) * tickSpacing;
352
+ if (finalLabelSpacing < axisLabelMinSpacing) {
353
+ axisLabelIndexes.delete(precedingAxisLabelIndex);
354
+ }
355
+ axisLabelIndexes.add(finalAxisLabelIndex);
356
+ }
357
+ }
358
+ const axisLabelPosition = (index: number) =>
359
+ isHorizontalBar
360
+ ? xForValue(gridTickValues[index]!)
361
+ : yForValue(gridTickValues[index]!);
362
+ while (true) {
363
+ const selectedAxisLabelIndexes = [...axisLabelIndexes].sort(
364
+ (first, second) => first - second,
365
+ );
366
+ const crowdedPair = selectedAxisLabelIndexes.findIndex(
367
+ (currentIndex, position) => {
368
+ if (position === 0) return false;
369
+ const precedingIndex = selectedAxisLabelIndexes[position - 1]!;
370
+ const centerSpacing = Math.abs(
371
+ axisLabelPosition(currentIndex) - axisLabelPosition(precedingIndex),
372
+ );
373
+ const requiredSpacing = isHorizontalBar
374
+ ? (estimateTextWidth(valueTickLabels[currentIndex]!, labelFontSize) +
375
+ estimateTextWidth(
376
+ valueTickLabels[precedingIndex]!,
377
+ labelFontSize,
378
+ )) /
379
+ 2 +
380
+ 8
381
+ : labelFontSize + 4;
382
+ return centerSpacing < requiredSpacing;
383
+ },
384
+ );
385
+ if (crowdedPair < 0) break;
386
+ const precedingIndex = selectedAxisLabelIndexes[crowdedPair - 1]!;
387
+ const currentIndex = selectedAxisLabelIndexes[crowdedPair]!;
388
+ axisLabelIndexes.delete(
389
+ currentIndex === finalAxisLabelIndex ? precedingIndex : currentIndex,
390
+ );
391
+ }
392
+ const gridTicks = gridTickValues.map((value, index) => ({
393
+ value,
394
+ index,
395
+ x: xForValue(value),
396
+ y: yForValue(value),
397
+ }));
76
398
  const xTickEvery = Math.max(1, Math.ceil(chart.data.length / 6));
77
- const zeroY = yFor(Math.max(min, Math.min(max, 0)));
399
+ const zero = Math.max(min, Math.min(max, 0));
400
+ const zeroX = xForValue(zero);
401
+ const zeroY = yForValue(zero);
402
+ const axisTitleFontWeight = chart.axisTitleFontWeight ?? 500;
403
+ const valueLabelFontSize = chart.valueLabelFontSize ?? 11;
404
+ const valueLabelFontWeight = chart.valueLabelFontWeight ?? 400;
78
405
 
79
406
  const axes = (
80
407
  <g aria-hidden="true">
81
- {gridTicks.map((tick) => (
82
- <g key={tick.y}>
83
- <line
84
- x1={MARGIN.left}
85
- x2={WIDTH - MARGIN.right}
86
- y1={tick.y}
87
- y2={tick.y}
88
- stroke="hsl(var(--border))"
89
- strokeDasharray="3 3"
90
- />
408
+ {gridTicks.map((tick) =>
409
+ isHorizontalBar ? (
410
+ <g key={tick.x}>
411
+ <line
412
+ x1={tick.x}
413
+ x2={tick.x}
414
+ y1={margin.top}
415
+ y2={HEIGHT - margin.bottom}
416
+ stroke="hsl(var(--chart-grid, var(--border)))"
417
+ strokeDasharray={tickInterval === undefined ? "3 3" : undefined}
418
+ />
419
+ {axisLabelIndexes.has(tick.index) ? (
420
+ <text
421
+ x={tick.x}
422
+ y={HEIGHT - margin.bottom + 14}
423
+ fill="hsl(var(--muted-foreground))"
424
+ fontSize={labelFontSize}
425
+ textAnchor="middle"
426
+ >
427
+ {valueTickLabels[tick.index]}
428
+ </text>
429
+ ) : null}
430
+ </g>
431
+ ) : (
432
+ <g key={tick.y}>
433
+ <line
434
+ x1={margin.left}
435
+ x2={WIDTH - margin.right}
436
+ y1={tick.y}
437
+ y2={tick.y}
438
+ stroke="hsl(var(--chart-grid, var(--border)))"
439
+ strokeDasharray={tickInterval === undefined ? "3 3" : undefined}
440
+ />
441
+ {axisLabelIndexes.has(tick.index) ? (
442
+ <text
443
+ x={margin.left - VALUE_AXIS_TICK_LABEL_INSET}
444
+ y={tick.y + 4}
445
+ fill="hsl(var(--muted-foreground))"
446
+ fontSize={labelFontSize}
447
+ textAnchor="end"
448
+ >
449
+ {valueTickLabels[tick.index]}
450
+ </text>
451
+ ) : null}
452
+ </g>
453
+ ),
454
+ )}
455
+ <line
456
+ x1={isHorizontalBar ? zeroX : margin.left}
457
+ x2={isHorizontalBar ? zeroX : WIDTH - margin.right}
458
+ y1={isHorizontalBar ? margin.top : zeroY}
459
+ y2={isHorizontalBar ? HEIGHT - margin.bottom : zeroY}
460
+ stroke="hsl(var(--chart-grid, var(--border)))"
461
+ />
462
+ {chart.data.map((row, index) =>
463
+ isHorizontalBar ? (
91
464
  <text
92
- x={MARGIN.left - 8}
93
- y={tick.y + 4}
465
+ key={`${index}-${String(row[chart.xKey] ?? "")}`}
466
+ x={categoryLabelX}
467
+ y={yForRow(index) + labelFontSize * 0.35}
94
468
  fill="hsl(var(--muted-foreground))"
95
- fontSize="11"
469
+ fontSize={labelFontSize}
96
470
  textAnchor="end"
97
471
  >
98
- {formatTick(tick.value)}
472
+ {labelFor(row[chart.xKey])}
99
473
  </text>
100
- </g>
101
- ))}
102
- <line
103
- x1={MARGIN.left}
104
- x2={WIDTH - MARGIN.right}
105
- y1={zeroY}
106
- y2={zeroY}
107
- stroke="hsl(var(--border))"
108
- />
109
- {chart.data.map((row, index) =>
110
- index % xTickEvery === 0 || index === chart.data.length - 1 ? (
474
+ ) : index % xTickEvery === 0 || index === chart.data.length - 1 ? (
111
475
  <text
112
476
  key={`${index}-${String(row[chart.xKey] ?? "")}`}
113
477
  x={
114
478
  chart.type === "bar"
115
- ? MARGIN.left + (index + 0.5) * (PLOT_WIDTH / chart.data.length)
479
+ ? margin.left + (index + 0.5) * (plotWidth / chart.data.length)
116
480
  : xForPoint(index)
117
481
  }
118
482
  y={HEIGHT - 12}
119
483
  fill="hsl(var(--muted-foreground))"
120
- fontSize="11"
484
+ fontSize={labelFontSize}
121
485
  textAnchor="middle"
122
486
  >
123
487
  {labelFor(row[chart.xKey])}
124
488
  </text>
125
489
  ) : null,
126
490
  )}
491
+ {chart.xAxisTitle ? (
492
+ <text
493
+ x={margin.left + plotWidth / 2}
494
+ y={HEIGHT - 4}
495
+ fill="hsl(var(--foreground))"
496
+ fontSize={axisTitleFontSize}
497
+ fontWeight={axisTitleFontWeight}
498
+ textAnchor="middle"
499
+ >
500
+ {chart.xAxisTitle}
501
+ </text>
502
+ ) : null}
503
+ {chart.yAxisTitle ? (
504
+ <text
505
+ x={Y_AXIS_TITLE_X}
506
+ y={margin.top + plotHeight / 2}
507
+ transform={`rotate(-90 ${Y_AXIS_TITLE_X} ${margin.top + plotHeight / 2})`}
508
+ fill="hsl(var(--foreground))"
509
+ fontSize={axisTitleFontSize}
510
+ fontWeight={axisTitleFontWeight}
511
+ textAnchor="middle"
512
+ >
513
+ {chart.yAxisTitle}
514
+ </text>
515
+ ) : null}
127
516
  </g>
128
517
  );
129
518
 
130
519
  if (chart.type === "bar") {
131
- const slotWidth = PLOT_WIDTH / Math.max(1, chart.data.length);
132
- const groupWidth = slotWidth * 0.72;
133
- const barWidth = groupWidth / Math.max(1, chart.series.length);
520
+ const slotSize =
521
+ (isHorizontalBar ? plotHeight : plotWidth) /
522
+ Math.max(1, chart.data.length);
523
+ const groupSize =
524
+ slotSize *
525
+ (isHorizontalBar && categoryGapRatio !== undefined
526
+ ? 1 - categoryGapRatio
527
+ : 0.72);
528
+ const barSize = groupSize / Math.max(1, chart.series.length);
134
529
  return (
135
530
  <>
136
531
  {axes}
@@ -141,23 +536,65 @@ function ChartBody({ chart }: { chart: DataChartWidgetData }) {
141
536
  const color =
142
537
  series.color ??
143
538
  DEFAULT_COLORS[seriesIndex % DEFAULT_COLORS.length];
144
- const y = yFor(Math.max(value, 0));
145
- const height = Math.max(1, Math.abs(zeroY - yFor(value)));
539
+ const valueX = xForValue(value);
540
+ const valueY = yForValue(value);
541
+ const x = isHorizontalBar
542
+ ? Math.min(zeroX, valueX)
543
+ : margin.left +
544
+ rowIndex * slotSize +
545
+ (slotSize - groupSize) / 2 +
546
+ seriesIndex * barSize;
547
+ const y = isHorizontalBar
548
+ ? margin.top +
549
+ rowIndex * slotSize +
550
+ (slotSize - groupSize) / 2 +
551
+ seriesIndex * barSize
552
+ : yForValue(Math.max(value, 0));
553
+ const width = isHorizontalBar
554
+ ? Math.max(2, Math.abs(valueX - zeroX))
555
+ : Math.max(2, barSize - 2);
556
+ const height = isHorizontalBar
557
+ ? Math.max(0, barSize - Math.min(2, barSize * 0.1))
558
+ : Math.max(1, Math.abs(zeroY - valueY));
146
559
  return (
147
- <rect
148
- key={`${rowIndex}-${series.key}`}
149
- x={
150
- MARGIN.left +
151
- rowIndex * slotWidth +
152
- (slotWidth - groupWidth) / 2 +
153
- seriesIndex * barWidth
154
- }
155
- y={y}
156
- width={Math.max(2, barWidth - 2)}
157
- height={height}
158
- rx="3"
159
- fill={color}
160
- />
560
+ <g key={`${rowIndex}-${series.key}`}>
561
+ <rect
562
+ x={x}
563
+ y={y}
564
+ width={width}
565
+ height={height}
566
+ rx="3"
567
+ fill={color}
568
+ />
569
+ {chart.showValueLabels ? (
570
+ <text
571
+ x={
572
+ isHorizontalBar
573
+ ? valueX + (value < 0 ? -5 : 5)
574
+ : x + width / 2
575
+ }
576
+ y={
577
+ isHorizontalBar
578
+ ? y + height / 2 + valueLabelFontSize * 0.35
579
+ : y - 5
580
+ }
581
+ fill="hsl(var(--muted-foreground))"
582
+ fontSize={valueLabelFontSize}
583
+ fontWeight={valueLabelFontWeight}
584
+ textAnchor={
585
+ isHorizontalBar ? (value < 0 ? "end" : "start") : "middle"
586
+ }
587
+ >
588
+ {isHorizontalBar
589
+ ? formatHorizontalValueLabel(
590
+ value,
591
+ valueLabelFontSize,
592
+ horizontalTextWidthLimit,
593
+ )
594
+ : formatTick(value)}
595
+ </text>
596
+ ) : null}
597
+ </g>
161
598
  );
162
599
  }),
163
600
  )}
@@ -171,9 +608,12 @@ function ChartBody({ chart }: { chart: DataChartWidgetData }) {
171
608
  const value = numericValue(row, series.key);
172
609
  return value === null
173
610
  ? null
174
- : { x: xForPoint(rowIndex), y: yFor(value) };
611
+ : { x: xForPoint(rowIndex), y: yForValue(value), value };
175
612
  })
176
- .filter((point): point is { x: number; y: number } => point !== null);
613
+ .filter(
614
+ (point): point is { x: number; y: number; value: number } =>
615
+ point !== null,
616
+ );
177
617
  const color =
178
618
  series.color ?? DEFAULT_COLORS[seriesIndex % DEFAULT_COLORS.length];
179
619
  const linePath = points
@@ -221,6 +661,35 @@ function ChartBody({ chart }: { chart: DataChartWidgetData }) {
221
661
  strokeWidth="2"
222
662
  />
223
663
  ))}
664
+ {chart.showValueLabels
665
+ ? points.map((point, index) => {
666
+ const abovePoint =
667
+ point.y - valueLabelFontSize - 5 >= margin.top;
668
+ return (
669
+ <text
670
+ key={`value-${index}`}
671
+ x={point.x}
672
+ y={
673
+ abovePoint
674
+ ? point.y - 5
675
+ : point.y + valueLabelFontSize + 5
676
+ }
677
+ fill="hsl(var(--muted-foreground))"
678
+ fontSize={valueLabelFontSize}
679
+ fontWeight={valueLabelFontWeight}
680
+ textAnchor={
681
+ point.x <= margin.left
682
+ ? "start"
683
+ : point.x >= WIDTH - margin.right
684
+ ? "end"
685
+ : "middle"
686
+ }
687
+ >
688
+ {formatTick(point.value)}
689
+ </text>
690
+ );
691
+ })
692
+ : null}
224
693
  </g>
225
694
  ))}
226
695
  </>
@@ -229,16 +698,42 @@ function ChartBody({ chart }: { chart: DataChartWidgetData }) {
229
698
 
230
699
  export function DataChartRenderer({ chart }: { chart: DataChartWidgetData }) {
231
700
  const t = useT();
701
+ const renderedChart =
702
+ chart.data.length > DATA_WIDGET_MAX_CHART_POINTS
703
+ ? {
704
+ ...chart,
705
+ data: chart.data.slice(0, DATA_WIDGET_MAX_CHART_POINTS),
706
+ }
707
+ : chart;
708
+ const contentHeight = renderedChart.contentHeight;
709
+ const contentPadding = renderedChart.contentPadding ?? 0;
710
+ const hasContentBox = contentHeight !== undefined || contentPadding > 0;
711
+ const labelFontFamily =
712
+ LABEL_FONT_FAMILIES[renderedChart.labelFontFamily ?? "sans"];
713
+ const labelFontSize = renderedChart.labelFontSize ?? 11;
232
714
  return (
233
- <div className="h-60 w-full min-w-0 overflow-hidden">
715
+ <div
716
+ className="box-border w-full min-w-0 overflow-hidden"
717
+ style={
718
+ hasContentBox
719
+ ? {
720
+ ...(contentHeight !== undefined
721
+ ? { height: `${contentHeight}px` }
722
+ : {}),
723
+ padding: `${contentPadding}px`,
724
+ }
725
+ : undefined
726
+ }
727
+ >
234
728
  <svg
235
- className="agent-data-chart-renderer h-full w-full"
729
+ className={`agent-data-chart-renderer block w-full ${contentHeight !== undefined ? "h-full" : "h-auto"}`}
236
730
  viewBox={`0 0 ${WIDTH} ${HEIGHT}`}
731
+ preserveAspectRatio={contentHeight !== undefined ? "none" : undefined}
732
+ fontFamily={labelFontFamily}
237
733
  role="img"
238
- aria-label={chart.title ?? t("agentChat.widget.dataChart")}
239
- preserveAspectRatio="none"
734
+ aria-label={renderedChart.title ?? t("agentChat.widget.dataChart")}
240
735
  >
241
- <ChartBody chart={chart} />
736
+ <ChartBody chart={renderedChart} labelFontSize={labelFontSize} />
242
737
  </svg>
243
738
  </div>
244
739
  );