@useatlas/react 0.0.1

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 (98) hide show
  1. package/README.md +95 -0
  2. package/dist/chunk-2WFDP7G5.js +231 -0
  3. package/dist/chunk-2WFDP7G5.js.map +1 -0
  4. package/dist/chunk-44HBZYKP.js +224 -0
  5. package/dist/chunk-44HBZYKP.js.map +1 -0
  6. package/dist/chunk-5SEVKHS5.cjs +229 -0
  7. package/dist/chunk-5SEVKHS5.cjs.map +1 -0
  8. package/dist/chunk-UIRB6L36.cjs +249 -0
  9. package/dist/chunk-UIRB6L36.cjs.map +1 -0
  10. package/dist/hooks.cjs +251 -0
  11. package/dist/hooks.cjs.map +1 -0
  12. package/dist/hooks.d.cts +132 -0
  13. package/dist/hooks.d.ts +132 -0
  14. package/dist/hooks.js +237 -0
  15. package/dist/hooks.js.map +1 -0
  16. package/dist/index.cjs +2976 -0
  17. package/dist/index.cjs.map +1 -0
  18. package/dist/index.d.cts +69 -0
  19. package/dist/index.d.ts +69 -0
  20. package/dist/index.js +2926 -0
  21. package/dist/index.js.map +1 -0
  22. package/dist/result-chart-NFAJ4IQ5.js +398 -0
  23. package/dist/result-chart-NFAJ4IQ5.js.map +1 -0
  24. package/dist/result-chart-YLCKBNV4.cjs +400 -0
  25. package/dist/result-chart-YLCKBNV4.cjs.map +1 -0
  26. package/dist/styles.css +59 -0
  27. package/dist/use-dark-mode-rFxawUv1.d.cts +123 -0
  28. package/dist/use-dark-mode-rFxawUv1.d.ts +123 -0
  29. package/dist/widget.css +2 -0
  30. package/dist/widget.js +445 -0
  31. package/package.json +113 -0
  32. package/src/components/__tests__/tool-renderers.test.tsx +239 -0
  33. package/src/components/actions/action-approval-card.tsx +296 -0
  34. package/src/components/actions/action-status-badge.tsx +50 -0
  35. package/src/components/admin/change-password-dialog.tsx +128 -0
  36. package/src/components/atlas-chat.tsx +656 -0
  37. package/src/components/chart/chart-detection.ts +318 -0
  38. package/src/components/chart/result-chart.tsx +590 -0
  39. package/src/components/chat/api-key-bar.tsx +66 -0
  40. package/src/components/chat/copy-button.tsx +25 -0
  41. package/src/components/chat/data-table.tsx +104 -0
  42. package/src/components/chat/error-banner.tsx +32 -0
  43. package/src/components/chat/explore-card.tsx +41 -0
  44. package/src/components/chat/follow-up-chips.tsx +29 -0
  45. package/src/components/chat/loading-card.tsx +10 -0
  46. package/src/components/chat/managed-auth-card.tsx +116 -0
  47. package/src/components/chat/markdown.tsx +146 -0
  48. package/src/components/chat/python-result-card.tsx +245 -0
  49. package/src/components/chat/sql-block.tsx +54 -0
  50. package/src/components/chat/sql-result-card.tsx +163 -0
  51. package/src/components/chat/starter-prompts.ts +6 -0
  52. package/src/components/chat/tool-part.tsx +106 -0
  53. package/src/components/chat/typing-indicator.tsx +22 -0
  54. package/src/components/conversations/conversation-item.tsx +135 -0
  55. package/src/components/conversations/conversation-list.tsx +69 -0
  56. package/src/components/conversations/conversation-sidebar.tsx +113 -0
  57. package/src/components/conversations/delete-confirmation.tsx +27 -0
  58. package/src/components/schema-explorer/schema-explorer.tsx +517 -0
  59. package/src/components/ui/alert-dialog.tsx +196 -0
  60. package/src/components/ui/badge.tsx +48 -0
  61. package/src/components/ui/button.tsx +64 -0
  62. package/src/components/ui/card.tsx +92 -0
  63. package/src/components/ui/dialog.tsx +158 -0
  64. package/src/components/ui/dropdown-menu.tsx +257 -0
  65. package/src/components/ui/input.tsx +21 -0
  66. package/src/components/ui/label.tsx +24 -0
  67. package/src/components/ui/scroll-area.tsx +62 -0
  68. package/src/components/ui/separator.tsx +28 -0
  69. package/src/components/ui/sheet.tsx +143 -0
  70. package/src/components/ui/table.tsx +116 -0
  71. package/src/components/ui/toggle-group.tsx +83 -0
  72. package/src/components/ui/toggle.tsx +47 -0
  73. package/src/context.tsx +85 -0
  74. package/src/env.d.ts +9 -0
  75. package/src/hooks/__tests__/provider.test.tsx +83 -0
  76. package/src/hooks/__tests__/use-atlas-auth.test.tsx +283 -0
  77. package/src/hooks/__tests__/use-atlas-chat.test.tsx +157 -0
  78. package/src/hooks/__tests__/use-atlas-conversations.test.tsx +159 -0
  79. package/src/hooks/__tests__/use-atlas-theme.test.tsx +56 -0
  80. package/src/hooks/index.ts +47 -0
  81. package/src/hooks/provider.tsx +77 -0
  82. package/src/hooks/theme-init-script.ts +17 -0
  83. package/src/hooks/use-atlas-auth.ts +131 -0
  84. package/src/hooks/use-atlas-chat.ts +102 -0
  85. package/src/hooks/use-atlas-conversations.ts +61 -0
  86. package/src/hooks/use-atlas-theme.ts +34 -0
  87. package/src/hooks/use-conversations.ts +189 -0
  88. package/src/hooks/use-dark-mode.ts +150 -0
  89. package/src/index.ts +36 -0
  90. package/src/lib/action-types.ts +11 -0
  91. package/src/lib/helpers.ts +198 -0
  92. package/src/lib/tool-renderer-types.ts +76 -0
  93. package/src/lib/types.ts +29 -0
  94. package/src/lib/utils.ts +6 -0
  95. package/src/styles.css +59 -0
  96. package/src/test-setup.ts +55 -0
  97. package/src/widget-entry.ts +20 -0
  98. package/src/widget.css +12 -0
@@ -0,0 +1,318 @@
1
+ /* Chart detection — pure functions, zero React deps. Kept framework-agnostic for direct unit testing. */
2
+
3
+ export type ColumnType = "numeric" | "date" | "categorical" | "unknown";
4
+
5
+ export type ClassifiedColumn = {
6
+ index: number;
7
+ header: string;
8
+ type: ColumnType;
9
+ uniqueCount: number;
10
+ };
11
+
12
+ export type ChartType = "bar" | "line" | "pie" | "area" | "stacked-bar" | "scatter";
13
+
14
+ export type ChartRecommendation = {
15
+ type: ChartType;
16
+ categoryColumn: ClassifiedColumn;
17
+ valueColumns: [ClassifiedColumn, ...ClassifiedColumn[]];
18
+ reason: string;
19
+ };
20
+
21
+ export type RechartsRow = Record<string, string | number>;
22
+
23
+ type NonChartableResult = {
24
+ chartable: false;
25
+ columns: ClassifiedColumn[];
26
+ };
27
+
28
+ type ChartableResult = {
29
+ chartable: true;
30
+ columns: ClassifiedColumn[];
31
+ recommendations: [ChartRecommendation, ...ChartRecommendation[]];
32
+ data: RechartsRow[];
33
+ };
34
+
35
+ export type ChartDetectionResult = NonChartableResult | ChartableResult;
36
+
37
+ /* ------------------------------------------------------------------ */
38
+ /* Color palettes (Tailwind weights) */
39
+ /* ------------------------------------------------------------------ */
40
+
41
+ export const CHART_COLORS_LIGHT = [
42
+ "#3b82f6", // blue-500
43
+ "#10b981", // emerald-500
44
+ "#f59e0b", // amber-500
45
+ "#ef4444", // red-500
46
+ "#8b5cf6", // violet-500
47
+ "#06b6d4", // cyan-500
48
+ "#f97316", // orange-500
49
+ "#ec4899", // pink-500
50
+ ];
51
+
52
+ export const CHART_COLORS_DARK = [
53
+ "#60a5fa", // blue-400
54
+ "#34d399", // emerald-400
55
+ "#fbbf24", // amber-400
56
+ "#f87171", // red-400
57
+ "#a78bfa", // violet-400
58
+ "#22d3ee", // cyan-400
59
+ "#fb923c", // orange-400
60
+ "#f472b6", // pink-400
61
+ ];
62
+
63
+ /* ------------------------------------------------------------------ */
64
+ /* Column classification */
65
+ /* ------------------------------------------------------------------ */
66
+
67
+ const DATE_HEADER_HINTS = /^(date|month|year|quarter|week|day|period|time|timestamp)$/i;
68
+ const CATEGORICAL_HEADER_HINTS = /^(name|type|category|status|region|country|industry|department|plan|tier|segment|group|label|source|channel)$/i;
69
+ const SKIP_HEADER_HINTS = /^(id|uuid|_id|pk|key)$/i;
70
+
71
+ const ISO_DATE_RE = /^\d{4}-\d{2}/;
72
+ const MONTH_NAME_RE = /^(jan|feb|mar|apr|may|jun|jul|aug|sep|oct|nov|dec)/i;
73
+ const YEAR_ONLY_RE = /^(19|20)\d{2}$/;
74
+ const QUARTER_RE = /^Q[1-4]\s*\d{4}$/i;
75
+
76
+ export function classifyColumn(header: string, values: string[]): ColumnType {
77
+ const nonEmpty = values.filter((v) => v !== "" && v != null);
78
+ if (nonEmpty.length === 0) return "unknown";
79
+
80
+ // Header hint: skip ID-like columns
81
+ if (SKIP_HEADER_HINTS.test(header)) return "unknown";
82
+
83
+ // Numeric check: >80% parse as finite numbers (date check takes priority for overlapping values)
84
+ const numericCount = nonEmpty.filter((v) => {
85
+ const n = Number(v.replace(/,/g, ""));
86
+ return isFinite(n);
87
+ }).length;
88
+ const numericRatio = numericCount / nonEmpty.length;
89
+
90
+ // Date check: >70% match date patterns (>30% when header hints match)
91
+ const dateCount = nonEmpty.filter(
92
+ (v) => ISO_DATE_RE.test(v) || MONTH_NAME_RE.test(v) || YEAR_ONLY_RE.test(v) || QUARTER_RE.test(v),
93
+ ).length;
94
+ const dateRatio = dateCount / nonEmpty.length;
95
+
96
+ // Header hint tiebreaker: if header matches date keywords...
97
+ // (a) ...and at least some values look date-like, trust the header
98
+ // (b) ...and values aren't overwhelmingly numeric (catches year-only values)
99
+ if (DATE_HEADER_HINTS.test(header) && dateRatio > 0.3) return "date";
100
+ if (DATE_HEADER_HINTS.test(header) && numericRatio < 0.9) return "date";
101
+
102
+ if (dateRatio > 0.7) return "date";
103
+ if (numericRatio > 0.8) return "numeric";
104
+
105
+ // Categorical header hint
106
+ if (CATEGORICAL_HEADER_HINTS.test(header)) return "categorical";
107
+
108
+ // Categorical fallback: text values with <50 unique entries (higher cardinality suggests free-text or IDs)
109
+ const unique = new Set(nonEmpty);
110
+ if (unique.size < 50) return "categorical";
111
+
112
+ return "unknown";
113
+ }
114
+
115
+ /* ------------------------------------------------------------------ */
116
+ /* Chart recommendation engine */
117
+ /* ------------------------------------------------------------------ */
118
+
119
+ export function detectCharts(headers: string[], rows: string[][]): ChartDetectionResult {
120
+ if (headers.length === 0 || rows.length < 2) {
121
+ return { chartable: false, columns: [] };
122
+ }
123
+
124
+ // Deduplicate headers so chart dataKey matches transformed data keys
125
+ const seen = new Map<string, number>();
126
+ const dedupedHeaders = headers.map((h) => {
127
+ const count = seen.get(h) ?? 0;
128
+ seen.set(h, count + 1);
129
+ return count > 0 ? `${h}_${count + 1}` : h;
130
+ });
131
+
132
+ const columns: ClassifiedColumn[] = dedupedHeaders.map((header, index) => {
133
+ const values = rows.map((r) => r[index] ?? "");
134
+ const type = classifyColumn(header, values);
135
+ const uniqueCount = new Set(values.filter((v) => v !== "")).size;
136
+ return { index, header, type, uniqueCount };
137
+ });
138
+
139
+ const dateColumns = columns.filter((c) => c.type === "date");
140
+ const numericColumns = columns.filter((c) => c.type === "numeric");
141
+ const categoricalColumns = columns.filter((c) => c.type === "categorical");
142
+
143
+ if (numericColumns.length === 0) {
144
+ return { chartable: false, columns };
145
+ }
146
+
147
+ const recommendations: ChartRecommendation[] = [];
148
+
149
+ // Line: date + numeric (time-series, highest priority)
150
+ if (dateColumns.length >= 1 && numericColumns.length >= 1) {
151
+ recommendations.push({
152
+ type: "line",
153
+ categoryColumn: dateColumns[0],
154
+ valueColumns: numericColumns as [ClassifiedColumn, ...ClassifiedColumn[]],
155
+ reason: `Time-series: ${dateColumns[0].header} vs ${numericColumns.map((c) => c.header).join(", ")}`,
156
+ });
157
+ }
158
+
159
+ // Area: alternative to line for date + numeric (volume/magnitude over time)
160
+ if (dateColumns.length >= 1 && numericColumns.length >= 1) {
161
+ recommendations.push({
162
+ type: "area",
163
+ categoryColumn: dateColumns[0],
164
+ valueColumns: numericColumns as [ClassifiedColumn, ...ClassifiedColumn[]],
165
+ reason: `Volume over time: ${numericColumns.map((c) => c.header).join(", ")} by ${dateColumns[0].header}`,
166
+ });
167
+ }
168
+
169
+ // Stacked bar: categorical + multiple numeric columns (part-to-whole comparison)
170
+ if (categoricalColumns.length >= 1 && numericColumns.length >= 2) {
171
+ recommendations.push({
172
+ type: "stacked-bar",
173
+ categoryColumn: categoricalColumns[0],
174
+ valueColumns: numericColumns as [ClassifiedColumn, ...ClassifiedColumn[]],
175
+ reason: `Stacked: ${numericColumns.map((c) => c.header).join(", ")} by ${categoricalColumns[0].header}`,
176
+ });
177
+ }
178
+
179
+ // Bar: categorical + numeric
180
+ if (categoricalColumns.length >= 1 && numericColumns.length >= 1) {
181
+ recommendations.push({
182
+ type: "bar",
183
+ categoryColumn: categoricalColumns[0],
184
+ valueColumns: numericColumns as [ClassifiedColumn, ...ClassifiedColumn[]],
185
+ reason: `Comparison: ${numericColumns.map((c) => c.header).join(", ")} by ${categoricalColumns[0].header}`,
186
+ });
187
+ }
188
+
189
+ // Pie: first categorical column (2-7 unique values) + first numeric column
190
+ if (categoricalColumns.length >= 1 && numericColumns.length >= 1) {
191
+ const cat = categoricalColumns[0];
192
+ if (cat.uniqueCount >= 2 && cat.uniqueCount <= 7) {
193
+ recommendations.push({
194
+ type: "pie",
195
+ categoryColumn: cat,
196
+ valueColumns: [numericColumns[0]],
197
+ reason: `Distribution: ${numericColumns[0].header} by ${cat.header}`,
198
+ });
199
+ }
200
+ }
201
+
202
+ // Scatter: 2+ numeric columns (correlation analysis)
203
+ if (numericColumns.length >= 2) {
204
+ const [xCol, yCol, ...rest] = numericColumns;
205
+ const scatterValues = rest.length > 0
206
+ ? [yCol, ...rest] as [ClassifiedColumn, ...ClassifiedColumn[]]
207
+ : [yCol] as [ClassifiedColumn, ...ClassifiedColumn[]];
208
+ recommendations.push({
209
+ type: "scatter",
210
+ categoryColumn: xCol,
211
+ valueColumns: scatterValues,
212
+ reason: `Correlation: ${xCol.header} vs ${yCol.header}${rest.length > 0 ? ` (size: ${rest[0].header})` : ""}`,
213
+ });
214
+ }
215
+
216
+ // Fallback: when all columns are numeric, treat first as category axis (often an index or bucket label)
217
+ if (!recommendations.some((r) => r.type === "bar") && numericColumns.length >= 2) {
218
+ const first = columns[0];
219
+ const rest = numericColumns.filter((c) => c.index !== first.index);
220
+ if (rest.length >= 1) {
221
+ recommendations.push({
222
+ type: "bar",
223
+ categoryColumn: first,
224
+ valueColumns: rest as [ClassifiedColumn, ...ClassifiedColumn[]],
225
+ reason: `Fallback: ${rest.map((c) => c.header).join(", ")} by ${first.header}`,
226
+ });
227
+ }
228
+ }
229
+
230
+ // Also allow bar for date columns (as a secondary option after line)
231
+ if (dateColumns.length >= 1 && numericColumns.length >= 1 && !recommendations.some((r) => r.type === "bar")) {
232
+ recommendations.push({
233
+ type: "bar",
234
+ categoryColumn: dateColumns[0],
235
+ valueColumns: numericColumns as [ClassifiedColumn, ...ClassifiedColumn[]],
236
+ reason: `Comparison: ${numericColumns.map((c) => c.header).join(", ")} by ${dateColumns[0].header}`,
237
+ });
238
+ }
239
+
240
+ if (recommendations.length === 0) {
241
+ return { chartable: false, columns };
242
+ }
243
+
244
+ const data = transformData(rows, recommendations[0]);
245
+
246
+ return {
247
+ chartable: true,
248
+ columns,
249
+ recommendations: recommendations as [ChartRecommendation, ...ChartRecommendation[]],
250
+ data,
251
+ };
252
+ }
253
+
254
+ /* ------------------------------------------------------------------ */
255
+ /* Data transform */
256
+ /* ------------------------------------------------------------------ */
257
+
258
+ function parseNumericValue(raw: string): number {
259
+ const cleaned = raw.replace(/[$%,\s]/g, "");
260
+ if (cleaned === "" || cleaned === "-") return 0;
261
+ const num = Number(cleaned);
262
+ return isFinite(num) ? num : 0;
263
+ }
264
+
265
+ function isFiniteNumeric(raw: string): boolean {
266
+ const cleaned = raw.replace(/[$%,\s]/g, "");
267
+ if (cleaned === "" || cleaned === "-") return false;
268
+ return isFinite(Number(cleaned));
269
+ }
270
+
271
+ export function transformData(
272
+ rows: string[][],
273
+ recommendation: ChartRecommendation,
274
+ ): RechartsRow[] {
275
+ const catIdx = recommendation.categoryColumn.index;
276
+ const catHeader = recommendation.categoryColumn.header;
277
+ const valIdxs = recommendation.valueColumns.map((c) => c.index);
278
+
279
+ // Scatter: both axes are numeric — categoryColumn is x, first valueColumn is y, optional z for size
280
+ // Filter out rows where x or y are non-numeric to avoid misleading zero-origin clusters
281
+ if (recommendation.type === "scatter") {
282
+ const yIdx = recommendation.valueColumns[0].index;
283
+ return rows.flatMap((row) => {
284
+ const rawX = row[catIdx] ?? "";
285
+ const rawY = row[yIdx] ?? "";
286
+ if (!isFiniteNumeric(rawX) || !isFiniteNumeric(rawY)) return [];
287
+ const record: RechartsRow = {};
288
+ record[catHeader] = parseNumericValue(rawX);
289
+ for (const vc of recommendation.valueColumns) {
290
+ record[vc.header] = parseNumericValue(row[vc.index] ?? "0");
291
+ }
292
+ return [record];
293
+ });
294
+ }
295
+
296
+ // Cap rows for bar/stacked-bar charts with many categories
297
+ let effectiveRows = rows;
298
+ if ((recommendation.type === "bar" || recommendation.type === "stacked-bar") && rows.length > 30) {
299
+ // Sort by first value column descending, take top 20
300
+ const valIdx = valIdxs[0];
301
+ effectiveRows = [...rows]
302
+ .sort((a, b) => {
303
+ const av = parseNumericValue(a[valIdx] ?? "0");
304
+ const bv = parseNumericValue(b[valIdx] ?? "0");
305
+ return bv - av;
306
+ })
307
+ .slice(0, 20);
308
+ }
309
+
310
+ return effectiveRows.map((row) => {
311
+ const record: RechartsRow = {};
312
+ record[catHeader] = row[catIdx] ?? "";
313
+ for (const vc of recommendation.valueColumns) {
314
+ record[vc.header] = parseNumericValue(row[vc.index] ?? "0");
315
+ }
316
+ return record;
317
+ });
318
+ }