@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
package/dist/index.js ADDED
@@ -0,0 +1,2926 @@
1
+ import { DarkModeContext, setTheme, useDarkMode, useConversations, AUTH_MODES, OKLCH_RE, applyBrandColor, useThemeMode, parseChatError } from './chunk-2WFDP7G5.js';
2
+ export { AUTH_MODES, THEME_STORAGE_KEY, buildThemeInitScript, parseChatError, setTheme, useConversations } from './chunk-2WFDP7G5.js';
3
+ import { detectCharts } from './chunk-44HBZYKP.js';
4
+ import { useChat } from '@ai-sdk/react';
5
+ import { getToolName, DefaultChatTransport, isToolUIPart } from 'ai';
6
+ import * as React from 'react';
7
+ import { createContext, lazy, memo, useContext, Component, useState, useMemo, Suspense, useEffect, useRef, useCallback } from 'react';
8
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
9
+ import { isActionToolResult, RESOLVED_STATUSES } from '@useatlas/types/action';
10
+ import { FileDown, FileSpreadsheet, TableProperties, Star, Moon, Sun, Monitor, ArrowLeft, XIcon, Columns3, Link2, Sparkles, Search, Eye, ArrowRight, Trash2 } from 'lucide-react';
11
+ import ReactMarkdown from 'react-markdown';
12
+ import remarkGfm from 'remark-gfm';
13
+ import { cva } from 'class-variance-authority';
14
+ import { Slot, ScrollArea as ScrollArea$1, ToggleGroup as ToggleGroup$1, DropdownMenu as DropdownMenu$1, Dialog, Separator as Separator$1, AlertDialog as AlertDialog$1 } from 'radix-ui';
15
+ import { clsx } from 'clsx';
16
+ import { twMerge } from 'tailwind-merge';
17
+
18
+ var AtlasUIContext = createContext(null);
19
+ function useAtlasConfig() {
20
+ const ctx = useContext(AtlasUIContext);
21
+ if (!ctx) throw new Error("useAtlasConfig must be used within <AtlasUIProvider>");
22
+ return ctx;
23
+ }
24
+ function AtlasUIProvider({
25
+ config,
26
+ children
27
+ }) {
28
+ const isCrossOrigin = typeof window !== "undefined" && config.apiUrl !== "" && !config.apiUrl.startsWith(window.location.origin);
29
+ return /* @__PURE__ */ jsx(AtlasUIContext.Provider, { value: { ...config, isCrossOrigin }, children });
30
+ }
31
+ var ActionAuthContext = createContext(null);
32
+ function useActionAuth() {
33
+ return useContext(ActionAuthContext);
34
+ }
35
+ function ActionAuthProvider({
36
+ getHeaders,
37
+ getCredentials,
38
+ children
39
+ }) {
40
+ const value = useMemo(
41
+ () => ({ getHeaders, getCredentials }),
42
+ [getHeaders, getCredentials]
43
+ );
44
+ return /* @__PURE__ */ jsx(ActionAuthContext.Provider, { value, children });
45
+ }
46
+ function ErrorBanner({ error, authMode }) {
47
+ const info = useMemo(() => parseChatError(error, authMode), [error, authMode]);
48
+ const [countdown, setCountdown] = useState(info.retryAfterSeconds ?? 0);
49
+ useEffect(() => {
50
+ if (!info.retryAfterSeconds) return;
51
+ setCountdown(info.retryAfterSeconds);
52
+ const interval = setInterval(() => {
53
+ setCountdown((prev) => {
54
+ if (prev <= 1) {
55
+ clearInterval(interval);
56
+ return 0;
57
+ }
58
+ return prev - 1;
59
+ });
60
+ }, 1e3);
61
+ return () => clearInterval(interval);
62
+ }, [info.retryAfterSeconds]);
63
+ const detail = info.retryAfterSeconds && countdown > 0 ? `Try again in ${countdown} second${countdown !== 1 ? "s" : ""}.` : info.detail;
64
+ return /* @__PURE__ */ jsxs("div", { className: "mb-2 rounded-lg border border-red-300 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400 px-4 py-3 text-sm", children: [
65
+ /* @__PURE__ */ jsx("p", { className: "font-medium", children: info.title }),
66
+ detail && /* @__PURE__ */ jsx("p", { className: "mt-1 text-xs opacity-80", children: detail })
67
+ ] });
68
+ }
69
+ function ApiKeyBar({
70
+ apiKey,
71
+ onSave
72
+ }) {
73
+ const [editing, setEditing] = useState(!apiKey);
74
+ const [draft, setDraft] = useState(apiKey);
75
+ if (!editing && apiKey) {
76
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs dark:border-zinc-700 dark:bg-zinc-900", children: [
77
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-500 dark:text-zinc-400", children: "API key configured" }),
78
+ /* @__PURE__ */ jsx(
79
+ "button",
80
+ {
81
+ onClick: () => {
82
+ setDraft(apiKey);
83
+ setEditing(true);
84
+ },
85
+ className: "rounded border border-zinc-200 px-2 py-0.5 text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
86
+ children: "Change"
87
+ }
88
+ )
89
+ ] });
90
+ }
91
+ return /* @__PURE__ */ jsxs(
92
+ "form",
93
+ {
94
+ onSubmit: (e) => {
95
+ e.preventDefault();
96
+ if (draft.trim()) {
97
+ onSave(draft.trim());
98
+ setEditing(false);
99
+ }
100
+ },
101
+ className: "flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 dark:border-zinc-700 dark:bg-zinc-900",
102
+ children: [
103
+ /* @__PURE__ */ jsx(
104
+ "input",
105
+ {
106
+ type: "password",
107
+ value: draft,
108
+ onChange: (e) => setDraft(e.target.value),
109
+ placeholder: "Enter your API key...",
110
+ className: "flex-1 bg-transparent text-xs text-zinc-900 placeholder-zinc-400 outline-none dark:text-zinc-100 dark:placeholder-zinc-600",
111
+ autoFocus: true
112
+ }
113
+ ),
114
+ /* @__PURE__ */ jsx(
115
+ "button",
116
+ {
117
+ type: "submit",
118
+ disabled: !draft.trim(),
119
+ className: "rounded border border-zinc-200 px-2 py-0.5 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 disabled:opacity-40 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
120
+ children: "Save"
121
+ }
122
+ ),
123
+ apiKey && /* @__PURE__ */ jsx(
124
+ "button",
125
+ {
126
+ type: "button",
127
+ onClick: () => setEditing(false),
128
+ className: "text-xs text-zinc-400 hover:text-zinc-600 dark:hover:text-zinc-300",
129
+ children: "Cancel"
130
+ }
131
+ )
132
+ ]
133
+ }
134
+ );
135
+ }
136
+ function ManagedAuthCard() {
137
+ const { authClient } = useAtlasConfig();
138
+ const [view, setView] = useState("login");
139
+ const [email, setEmail] = useState("");
140
+ const [password, setPassword] = useState("");
141
+ const [name, setName] = useState("");
142
+ const [error, setError] = useState("");
143
+ const [loading, setLoading] = useState(false);
144
+ async function handleLogin(e) {
145
+ e.preventDefault();
146
+ setError("");
147
+ setLoading(true);
148
+ try {
149
+ const res = await authClient.signIn.email({ email, password });
150
+ if (res.error) setError(res.error.message ?? "Sign in failed");
151
+ } catch (err) {
152
+ console.error("Sign in error:", err);
153
+ setError(err instanceof TypeError ? "Unable to reach the server" : "Sign in failed");
154
+ } finally {
155
+ setLoading(false);
156
+ }
157
+ }
158
+ async function handleSignup(e) {
159
+ e.preventDefault();
160
+ setError("");
161
+ setLoading(true);
162
+ try {
163
+ const res = await authClient.signUp.email({ email, password, name: name || email.split("@")[0] });
164
+ if (res.error) setError(res.error.message ?? "Sign up failed");
165
+ } catch (err) {
166
+ console.error("Sign up error:", err);
167
+ setError(err instanceof TypeError ? "Unable to reach the server" : "Sign up failed");
168
+ } finally {
169
+ setLoading(false);
170
+ }
171
+ }
172
+ return /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs("div", { className: "w-full max-w-sm space-y-4 rounded-lg border border-zinc-200 bg-zinc-50 p-6 dark:border-zinc-700 dark:bg-zinc-900", children: [
173
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
174
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-zinc-900 dark:text-zinc-100", children: view === "login" ? "Sign in to Atlas" : "Create an account" }),
175
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-zinc-500 dark:text-zinc-400", children: view === "login" ? "Enter your credentials to continue" : "Set up your Atlas account" })
176
+ ] }),
177
+ /* @__PURE__ */ jsxs("form", { onSubmit: view === "login" ? handleLogin : handleSignup, className: "space-y-3", children: [
178
+ view === "signup" && /* @__PURE__ */ jsx(
179
+ "input",
180
+ {
181
+ type: "text",
182
+ value: name,
183
+ onChange: (e) => setName(e.target.value),
184
+ placeholder: "Name (optional)",
185
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-600"
186
+ }
187
+ ),
188
+ /* @__PURE__ */ jsx(
189
+ "input",
190
+ {
191
+ type: "email",
192
+ value: email,
193
+ onChange: (e) => setEmail(e.target.value),
194
+ placeholder: "Email",
195
+ required: true,
196
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-600"
197
+ }
198
+ ),
199
+ /* @__PURE__ */ jsx(
200
+ "input",
201
+ {
202
+ type: "password",
203
+ value: password,
204
+ onChange: (e) => setPassword(e.target.value),
205
+ placeholder: "Password",
206
+ required: true,
207
+ minLength: 8,
208
+ className: "w-full rounded-lg border border-zinc-200 bg-white px-3 py-2 text-sm text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-600"
209
+ }
210
+ ),
211
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 dark:text-red-400", children: error }),
212
+ /* @__PURE__ */ jsx(
213
+ "button",
214
+ {
215
+ type: "submit",
216
+ disabled: loading,
217
+ className: "w-full rounded-lg bg-blue-600 px-4 py-2 text-sm font-medium text-white transition-colors hover:bg-blue-500 disabled:opacity-40",
218
+ children: loading ? "..." : view === "login" ? "Sign in" : "Create account"
219
+ }
220
+ )
221
+ ] }),
222
+ /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-zinc-500 dark:text-zinc-400", children: view === "login" ? /* @__PURE__ */ jsxs(Fragment, { children: [
223
+ "No account?",
224
+ " ",
225
+ /* @__PURE__ */ jsx("button", { onClick: () => {
226
+ setView("signup");
227
+ setError("");
228
+ }, className: "text-blue-600 hover:underline dark:text-blue-400", children: "Create one" })
229
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
230
+ "Already have an account?",
231
+ " ",
232
+ /* @__PURE__ */ jsx("button", { onClick: () => {
233
+ setView("login");
234
+ setError("");
235
+ }, className: "text-blue-600 hover:underline dark:text-blue-400", children: "Sign in" })
236
+ ] }) })
237
+ ] }) });
238
+ }
239
+ var DELAY_1 = { animationDelay: "150ms" };
240
+ var DELAY_2 = { animationDelay: "300ms" };
241
+ function TypingIndicator() {
242
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-start", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 rounded-xl bg-zinc-100 px-4 py-3 dark:bg-zinc-800", children: [
243
+ /* @__PURE__ */ jsx("span", { className: "h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400 dark:bg-zinc-500" }),
244
+ /* @__PURE__ */ jsx(
245
+ "span",
246
+ {
247
+ className: "h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400 dark:bg-zinc-500",
248
+ style: DELAY_1
249
+ }
250
+ ),
251
+ /* @__PURE__ */ jsx(
252
+ "span",
253
+ {
254
+ className: "h-1.5 w-1.5 animate-bounce rounded-full bg-zinc-400 dark:bg-zinc-500",
255
+ style: DELAY_2
256
+ }
257
+ )
258
+ ] }) });
259
+ }
260
+
261
+ // src/lib/helpers.ts
262
+ function getToolArgs(part) {
263
+ if (part == null || typeof part !== "object") return {};
264
+ const input = part.input;
265
+ if (input == null || typeof input !== "object") return {};
266
+ return input;
267
+ }
268
+ function getToolResult(part) {
269
+ if (part == null || typeof part !== "object") return null;
270
+ return part.output ?? null;
271
+ }
272
+ function isToolComplete(part) {
273
+ if (part == null || typeof part !== "object") return false;
274
+ return part.state === "output-available";
275
+ }
276
+ function toCsvString(columns, rows) {
277
+ const escape = (v) => {
278
+ const s = v == null ? "" : String(v);
279
+ return s.includes(",") || s.includes('"') || s.includes("\n") ? `"${s.replace(/"/g, '""')}"` : s;
280
+ };
281
+ const header = columns.map(escape).join(",");
282
+ const body = rows.map((row) => columns.map((col) => escape(row[col])).join(","));
283
+ return [header, ...body].join("\n");
284
+ }
285
+ function downloadCSV(csv, filename = "atlas-results.csv") {
286
+ let url = null;
287
+ try {
288
+ const blob = new Blob([csv], { type: "text/csv" });
289
+ url = URL.createObjectURL(blob);
290
+ const a = document.createElement("a");
291
+ a.href = url;
292
+ a.download = filename;
293
+ a.click();
294
+ } catch (err) {
295
+ console.error("CSV download failed:", err);
296
+ window.alert("CSV download failed");
297
+ } finally {
298
+ if (url) {
299
+ const blobUrl = url;
300
+ setTimeout(() => URL.revokeObjectURL(blobUrl), 1e4);
301
+ }
302
+ }
303
+ }
304
+ var ISO_DATE_RE = /^\d{4}-\d{2}-\d{2}(T\d{2}:\d{2}(:\d{2}(\.\d+)?)?(Z|[+-]\d{2}:\d{2})?)?$/;
305
+ function coerceExcelCell(v) {
306
+ if (v == null) return "";
307
+ if (typeof v === "number" || typeof v === "boolean") return v;
308
+ if (typeof v === "string" && ISO_DATE_RE.test(v) && !isNaN(Date.parse(v))) {
309
+ return new Date(v);
310
+ }
311
+ return String(v);
312
+ }
313
+ async function downloadExcel(columns, rows, filename = "atlas-results.xlsx") {
314
+ let XLSX;
315
+ try {
316
+ XLSX = await import(
317
+ 'xlsx'
318
+ /* webpackIgnore: true */
319
+ );
320
+ } catch (err) {
321
+ console.error("Failed to load xlsx library:", err);
322
+ window.alert("Excel export is unavailable. The spreadsheet library failed to load.");
323
+ return;
324
+ }
325
+ let url = null;
326
+ try {
327
+ const data = rows.map((row) => {
328
+ const obj = {};
329
+ for (const col of columns) {
330
+ obj[col] = coerceExcelCell(row[col]);
331
+ }
332
+ return obj;
333
+ });
334
+ const ws = XLSX.utils.json_to_sheet(data, { header: columns });
335
+ const wb = XLSX.utils.book_new();
336
+ XLSX.utils.book_append_sheet(wb, ws, "Results");
337
+ const wbOut = XLSX.write(wb, { bookType: "xlsx", type: "array" });
338
+ const blob = new Blob([wbOut], {
339
+ type: "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
340
+ });
341
+ url = URL.createObjectURL(blob);
342
+ const a = document.createElement("a");
343
+ a.href = url;
344
+ a.download = filename;
345
+ a.click();
346
+ } catch (err) {
347
+ console.error("Excel download failed:", err);
348
+ const detail = err instanceof Error ? err.message : "Unknown error";
349
+ window.alert(`Excel download failed: ${detail}
350
+
351
+ You can try the CSV download as an alternative.`);
352
+ } finally {
353
+ if (url) {
354
+ const blobUrl = url;
355
+ setTimeout(() => URL.revokeObjectURL(blobUrl), 1e4);
356
+ }
357
+ }
358
+ }
359
+ var SUGGESTIONS_RE = /<suggestions>\s*([\s\S]*?)\s*<\/suggestions>/g;
360
+ function parseSuggestions(content) {
361
+ const suggestions = [];
362
+ let match;
363
+ while ((match = SUGGESTIONS_RE.exec(content)) !== null) {
364
+ const lines = match[1].split("\n").map((l) => l.trim()).filter(Boolean);
365
+ suggestions.push(...lines);
366
+ }
367
+ SUGGESTIONS_RE.lastIndex = 0;
368
+ if (suggestions.length === 0) return { text: content, suggestions: [] };
369
+ const text = content.replace(SUGGESTIONS_RE, "").trimEnd();
370
+ SUGGESTIONS_RE.lastIndex = 0;
371
+ return { text, suggestions: suggestions.slice(0, 5) };
372
+ }
373
+ function normalizeList(data, keyName) {
374
+ if (!data) return [];
375
+ if (Array.isArray(data)) return data;
376
+ return Object.entries(data).map(([key, value]) => ({ ...value, [keyName]: key }));
377
+ }
378
+ function formatCell(value) {
379
+ if (value == null) return "\u2014";
380
+ if (typeof value === "number") {
381
+ return Number.isInteger(value) ? value.toLocaleString() : value.toLocaleString(void 0, { maximumFractionDigits: 2 });
382
+ }
383
+ return String(value);
384
+ }
385
+ function ExploreCard({ part }) {
386
+ const args = getToolArgs(part);
387
+ const result = getToolResult(part);
388
+ const done = isToolComplete(part);
389
+ const [open, setOpen] = useState(false);
390
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900", children: [
391
+ /* @__PURE__ */ jsxs(
392
+ "button",
393
+ {
394
+ onClick: () => done && setOpen(!open),
395
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/60 dark:hover:bg-zinc-800/60",
396
+ children: [
397
+ /* @__PURE__ */ jsx("span", { className: "font-mono text-green-400", children: "$" }),
398
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate font-mono text-zinc-700 dark:text-zinc-300", children: String(args.command ?? "") }),
399
+ done ? /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-600", children: open ? "\u25BE" : "\u25B8" }) : /* @__PURE__ */ jsx("span", { className: "animate-pulse text-zinc-500", children: "running..." })
400
+ ]
401
+ }
402
+ ),
403
+ open && done && /* @__PURE__ */ jsx("div", { className: "border-t border-zinc-100 bg-white px-3 py-2 dark:border-zinc-800 dark:bg-zinc-950", children: /* @__PURE__ */ jsx("pre", { className: "max-h-60 overflow-auto whitespace-pre-wrap font-mono text-xs leading-relaxed text-zinc-500 dark:text-zinc-400", children: result != null ? typeof result === "string" ? result : JSON.stringify(result, null, 2) : "(no output received)" }) })
404
+ ] });
405
+ }
406
+ function LoadingCard({ label }) {
407
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 flex items-center gap-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900", children: [
408
+ /* @__PURE__ */ jsx("span", { className: "inline-block h-3 w-3 animate-spin rounded-full border-2 border-zinc-300 border-t-zinc-600 dark:border-zinc-600 dark:border-t-zinc-300" }),
409
+ label
410
+ ] });
411
+ }
412
+ function DataTable({
413
+ columns,
414
+ rows,
415
+ maxRows = 10
416
+ }) {
417
+ const [sortCol, setSortCol] = useState(null);
418
+ const [sortDir, setSortDir] = useState("asc");
419
+ const hasMore = rows.length > maxRows;
420
+ const cell = (row, colIdx) => {
421
+ if (Array.isArray(row)) return row[colIdx];
422
+ return row[columns[colIdx]];
423
+ };
424
+ const handleSort = (colIdx) => {
425
+ if (sortCol === colIdx) {
426
+ if (sortDir === "asc") {
427
+ setSortDir("desc");
428
+ } else {
429
+ setSortCol(null);
430
+ setSortDir("asc");
431
+ }
432
+ } else {
433
+ setSortCol(colIdx);
434
+ setSortDir("asc");
435
+ }
436
+ };
437
+ const sorted = sortCol !== null ? [...rows].sort((a, b) => {
438
+ const av = cell(a, sortCol);
439
+ const bv = cell(b, sortCol);
440
+ if (av == null && bv == null) return 0;
441
+ if (av == null) return 1;
442
+ if (bv == null) return -1;
443
+ const aStr = String(av).trim();
444
+ const bStr = String(bv).trim();
445
+ if (aStr === "" && bStr === "") return 0;
446
+ if (aStr === "") return 1;
447
+ if (bStr === "") return -1;
448
+ const an = Number(aStr), bn = Number(bStr);
449
+ if (!isNaN(an) && !isNaN(bn)) {
450
+ return sortDir === "asc" ? an - bn : bn - an;
451
+ }
452
+ const cmp = aStr.localeCompare(bStr);
453
+ return sortDir === "asc" ? cmp : -cmp;
454
+ }) : rows;
455
+ const display = sorted.slice(0, maxRows);
456
+ return /* @__PURE__ */ jsxs("div", { className: "relative rounded-lg border border-zinc-200 dark:border-zinc-700", children: [
457
+ /* @__PURE__ */ jsx("div", { className: "overflow-x-auto [&::-webkit-scrollbar]:h-1.5 [&::-webkit-scrollbar-thumb]:rounded-full [&::-webkit-scrollbar-thumb]:bg-zinc-300 dark:[&::-webkit-scrollbar-thumb]:bg-zinc-600", children: /* @__PURE__ */ jsxs("table", { className: "min-w-full text-xs", children: [
458
+ /* @__PURE__ */ jsx("thead", { children: /* @__PURE__ */ jsx("tr", { className: "border-b border-zinc-200 bg-zinc-100/80 dark:border-zinc-700 dark:bg-zinc-800/80", children: columns.map((col, i) => /* @__PURE__ */ jsxs(
459
+ "th",
460
+ {
461
+ onClick: () => handleSort(i),
462
+ className: "group cursor-pointer select-none whitespace-nowrap px-3 py-2 text-left font-medium text-zinc-500 transition-colors hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200",
463
+ children: [
464
+ col,
465
+ sortCol === i ? sortDir === "asc" ? " \u25B2" : " \u25BC" : ""
466
+ ]
467
+ },
468
+ i
469
+ )) }) }),
470
+ /* @__PURE__ */ jsx("tbody", { children: display.map((row, i) => /* @__PURE__ */ jsx(
471
+ "tr",
472
+ {
473
+ className: i % 2 === 0 ? "bg-zinc-100/60 dark:bg-zinc-900/60" : "bg-zinc-50/30 dark:bg-zinc-900/30",
474
+ children: columns.map((_, j) => /* @__PURE__ */ jsx("td", { className: "whitespace-nowrap px-3 py-1.5 text-zinc-700 dark:text-zinc-300", children: formatCell(cell(row, j)) }, j))
475
+ },
476
+ i
477
+ )) })
478
+ ] }) }),
479
+ hasMore && /* @__PURE__ */ jsxs("div", { className: "border-t border-zinc-200 px-3 py-1.5 text-xs text-zinc-500 dark:border-zinc-700", children: [
480
+ "Showing ",
481
+ maxRows,
482
+ " of ",
483
+ rows.length,
484
+ " rows"
485
+ ] })
486
+ ] });
487
+ }
488
+ function CopyButton({ text, label = "Copy" }) {
489
+ const [state, setState] = useState("idle");
490
+ return /* @__PURE__ */ jsx(
491
+ "button",
492
+ {
493
+ onClick: async () => {
494
+ try {
495
+ await navigator.clipboard.writeText(text);
496
+ setState("copied");
497
+ setTimeout(() => setState("idle"), 2e3);
498
+ } catch (err) {
499
+ console.warn("Clipboard write failed:", err);
500
+ setState("failed");
501
+ setTimeout(() => setState("idle"), 2e3);
502
+ }
503
+ },
504
+ className: "rounded border border-zinc-200 px-2 py-1 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
505
+ children: state === "copied" ? "Copied!" : state === "failed" ? "Failed" : label
506
+ }
507
+ );
508
+ }
509
+ var _cache = null;
510
+ var SQL_BLOCK_STYLE = {
511
+ margin: 0,
512
+ borderRadius: "0.5rem",
513
+ fontSize: "0.75rem",
514
+ padding: "0.75rem 1rem"
515
+ };
516
+ function SQLBlock({ sql }) {
517
+ const dark = useContext(DarkModeContext);
518
+ const [mod, setMod] = useState(_cache);
519
+ useEffect(() => {
520
+ if (_cache) return;
521
+ Promise.all([
522
+ import('react-syntax-highlighter'),
523
+ import('react-syntax-highlighter/dist/esm/styles/prism')
524
+ ]).then(([sh, styles]) => {
525
+ _cache = { Prism: sh.Prism, oneDark: styles.oneDark, oneLight: styles.oneLight };
526
+ setMod(_cache);
527
+ });
528
+ }, []);
529
+ return /* @__PURE__ */ jsxs("div", { className: "relative", children: [
530
+ mod ? /* @__PURE__ */ jsx(
531
+ mod.Prism,
532
+ {
533
+ language: "sql",
534
+ style: dark ? mod.oneDark : mod.oneLight,
535
+ customStyle: SQL_BLOCK_STYLE,
536
+ children: sql
537
+ }
538
+ ) : /* @__PURE__ */ jsx("pre", { className: "overflow-x-auto rounded-lg bg-zinc-100 p-3 text-xs dark:bg-zinc-800", children: /* @__PURE__ */ jsx("code", { children: sql }) }),
539
+ /* @__PURE__ */ jsx("div", { className: "absolute right-2 top-2", children: /* @__PURE__ */ jsx(CopyButton, { text: sql, label: "Copy SQL" }) })
540
+ ] });
541
+ }
542
+ var ResultChart = lazy(() => import('./result-chart-NFAJ4IQ5.js').then((m) => ({ default: m.ResultChart })));
543
+ var ChartFallback = /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-lg bg-zinc-100 dark:bg-zinc-800" });
544
+ function toStringRows(columns, rows) {
545
+ return rows.map((row) => columns.map((col) => row[col] == null ? "" : String(row[col])));
546
+ }
547
+ function SQLResultCard({ part }) {
548
+ const dark = useContext(DarkModeContext);
549
+ const args = getToolArgs(part);
550
+ const result = getToolResult(part);
551
+ const done = isToolComplete(part);
552
+ const [open, setOpen] = useState(true);
553
+ const [sqlOpen, setSqlOpen] = useState(false);
554
+ const [viewMode, setViewMode] = useState("both");
555
+ const columns = useMemo(
556
+ () => done && result?.success ? result.columns ?? [] : [],
557
+ [done, result]
558
+ );
559
+ const rows = useMemo(
560
+ () => done && result?.success ? result.rows ?? [] : [],
561
+ [done, result]
562
+ );
563
+ const sql = String(args.sql ?? "");
564
+ const stringRows = useMemo(() => toStringRows(columns, rows), [columns, rows]);
565
+ const chartResult = useMemo(
566
+ () => columns.length > 0 ? detectCharts(columns, stringRows) : { chartable: false, columns: [] },
567
+ [columns, stringRows]
568
+ );
569
+ if (!done) return /* @__PURE__ */ jsx(LoadingCard, { label: "Executing query..." });
570
+ if (!result) {
571
+ return /* @__PURE__ */ jsx("div", { className: "my-2 rounded-lg border border-yellow-300 bg-yellow-50 text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400 px-3 py-2 text-xs", children: "Query completed but no result was returned." });
572
+ }
573
+ if (!result.success) {
574
+ return /* @__PURE__ */ jsx("div", { className: "my-2 rounded-lg border border-red-300 bg-red-50 text-red-700 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400 px-3 py-2 text-xs", children: "Query failed. Check the query and try again." });
575
+ }
576
+ const hasData = columns.length > 0 && rows.length > 0;
577
+ const showChart = chartResult.chartable && (viewMode === "chart" || viewMode === "both");
578
+ const showTable = viewMode === "table" || viewMode === "both" || !chartResult.chartable;
579
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900", children: [
580
+ /* @__PURE__ */ jsxs(
581
+ "button",
582
+ {
583
+ onClick: () => setOpen(!open),
584
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/60 dark:hover:bg-zinc-800/60",
585
+ children: [
586
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-blue-100 text-blue-700 dark:bg-blue-600/20 dark:text-blue-400 px-1.5 py-0.5 font-medium", children: "SQL" }),
587
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-zinc-500 dark:text-zinc-400", children: String(args.explanation ?? "Query result") }),
588
+ /* @__PURE__ */ jsxs("span", { className: "text-zinc-500", children: [
589
+ rows.length,
590
+ " row",
591
+ rows.length !== 1 ? "s" : "",
592
+ result.truncated ? "+" : ""
593
+ ] }),
594
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-600", children: open ? "\u25BE" : "\u25B8" })
595
+ ]
596
+ }
597
+ ),
598
+ open && /* @__PURE__ */ jsxs("div", { className: "border-t border-zinc-100 dark:border-zinc-800", children: [
599
+ hasData && chartResult.chartable && /* @__PURE__ */ jsx("div", { className: "flex gap-1 px-3 pt-2", children: ["chart", "both", "table"].map((mode) => /* @__PURE__ */ jsx(
600
+ "button",
601
+ {
602
+ onClick: () => setViewMode(mode),
603
+ className: `rounded px-2.5 py-1 text-xs font-medium transition-colors ${viewMode === mode ? "bg-zinc-200 text-zinc-800 dark:bg-zinc-700 dark:text-zinc-200" : "text-zinc-500 hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200"}`,
604
+ children: mode === "chart" ? "Chart" : mode === "both" ? "Both" : "Table"
605
+ },
606
+ mode
607
+ )) }),
608
+ hasData && showChart && /* @__PURE__ */ jsx("div", { className: "px-3 py-2", children: /* @__PURE__ */ jsx(Suspense, { fallback: ChartFallback, children: /* @__PURE__ */ jsx(ResultChart, { headers: columns, rows: stringRows, dark, detectionResult: chartResult }) }) }),
609
+ hasData && showTable && /* @__PURE__ */ jsx(DataTable, { columns, rows }),
610
+ !hasData && /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs text-zinc-500 dark:text-zinc-400", children: "Query returned 0 rows." }),
611
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-wrap items-center gap-2 px-3 py-2", children: [
612
+ sql && /* @__PURE__ */ jsx(
613
+ "button",
614
+ {
615
+ onClick: () => setSqlOpen(!sqlOpen),
616
+ className: "rounded border border-zinc-200 px-2 py-1.5 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
617
+ children: sqlOpen ? "Hide SQL" : "Show SQL"
618
+ }
619
+ ),
620
+ hasData && /* @__PURE__ */ jsxs(
621
+ "button",
622
+ {
623
+ onClick: () => downloadCSV(toCsvString(columns, rows)),
624
+ className: "inline-flex items-center gap-1.5 rounded border border-zinc-200 px-2 py-1.5 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
625
+ title: "Download CSV",
626
+ children: [
627
+ /* @__PURE__ */ jsx(FileDown, { className: "size-3.5" }),
628
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "CSV" })
629
+ ]
630
+ }
631
+ ),
632
+ hasData && /* @__PURE__ */ jsxs(
633
+ "button",
634
+ {
635
+ onClick: () => {
636
+ downloadExcel(columns, rows).catch((err) => {
637
+ console.warn("Excel download failed:", err);
638
+ });
639
+ },
640
+ className: "inline-flex items-center gap-1.5 rounded border border-zinc-200 px-2 py-1.5 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
641
+ title: "Download Excel",
642
+ children: [
643
+ /* @__PURE__ */ jsx(FileSpreadsheet, { className: "size-3.5" }),
644
+ /* @__PURE__ */ jsx("span", { className: "hidden sm:inline", children: "Excel" })
645
+ ]
646
+ }
647
+ )
648
+ ] }),
649
+ sqlOpen && sql && /* @__PURE__ */ jsx("div", { className: "px-3 pb-2", children: /* @__PURE__ */ jsx(SQLBlock, { sql }) })
650
+ ] })
651
+ ] });
652
+ }
653
+ var STATUS_CONFIG = {
654
+ pending_approval: {
655
+ label: "Pending Approval",
656
+ classes: "bg-yellow-100 text-yellow-700 dark:bg-yellow-600/20 dark:text-yellow-400"
657
+ },
658
+ approved: {
659
+ label: "Approved",
660
+ classes: "bg-green-100 text-green-700 dark:bg-green-600/20 dark:text-green-400"
661
+ },
662
+ executed: {
663
+ label: "Executed",
664
+ classes: "bg-green-100 text-green-700 dark:bg-green-600/20 dark:text-green-400"
665
+ },
666
+ auto_approved: {
667
+ label: "Auto-approved",
668
+ classes: "bg-green-100 text-green-700 dark:bg-green-600/20 dark:text-green-400"
669
+ },
670
+ denied: {
671
+ label: "Denied",
672
+ classes: "bg-red-100 text-red-700 dark:bg-red-600/20 dark:text-red-400"
673
+ },
674
+ failed: {
675
+ label: "Failed",
676
+ classes: "bg-red-100 text-red-700 dark:bg-red-600/20 dark:text-red-400"
677
+ },
678
+ rolled_back: {
679
+ label: "Rolled Back",
680
+ classes: "bg-zinc-100 text-zinc-700 dark:bg-zinc-600/20 dark:text-zinc-400"
681
+ },
682
+ timed_out: {
683
+ label: "Timed Out",
684
+ classes: "bg-zinc-100 text-zinc-700 dark:bg-zinc-600/20 dark:text-zinc-400"
685
+ }
686
+ };
687
+ function ActionStatusBadge({ status }) {
688
+ const config = STATUS_CONFIG[status] ?? {
689
+ label: status.replace(/_/g, " "),
690
+ classes: "bg-zinc-100 text-zinc-700 dark:bg-zinc-600/20 dark:text-zinc-400"
691
+ };
692
+ return /* @__PURE__ */ jsx("span", { className: `rounded px-1.5 py-0.5 text-xs font-medium ${config.classes}`, children: config.label });
693
+ }
694
+ function safeStringify(value) {
695
+ try {
696
+ return JSON.stringify(value, null, 2);
697
+ } catch (err) {
698
+ console.warn("Could not serialize action details:", err);
699
+ return "[Unable to display]";
700
+ }
701
+ }
702
+ function borderColor(status) {
703
+ switch (status) {
704
+ case "pending_approval":
705
+ return "border-yellow-300 dark:border-yellow-900/50";
706
+ case "approved":
707
+ case "executed":
708
+ case "auto_approved":
709
+ return "border-green-300 dark:border-green-900/50";
710
+ case "denied":
711
+ case "failed":
712
+ return "border-red-300 dark:border-red-900/50";
713
+ default:
714
+ return "border-zinc-200 dark:border-zinc-700";
715
+ }
716
+ }
717
+ function ActionApprovalCard({ part }) {
718
+ const { apiUrl } = useAtlasConfig();
719
+ const actionAuth = useActionAuth();
720
+ const args = getToolArgs(part);
721
+ const rawResult = getToolResult(part);
722
+ const done = isToolComplete(part);
723
+ const [cardState, setCardState] = useState({ phase: "idle" });
724
+ const [open, setOpen] = useState(false);
725
+ const [denyReason, setDenyReason] = useState("");
726
+ const [showDenyInput, setShowDenyInput] = useState(false);
727
+ if (!done) return /* @__PURE__ */ jsx(LoadingCard, { label: "Requesting action approval..." });
728
+ if (!isActionToolResult(rawResult)) {
729
+ return /* @__PURE__ */ jsx("div", { className: "my-2 rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-2 text-xs text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400", children: "Action result (unexpected format)" });
730
+ }
731
+ const toolResult = rawResult;
732
+ const effectiveStatus = cardState.phase === "resolved" ? cardState.status : toolResult.status;
733
+ const isPending = effectiveStatus === "pending_approval" && cardState.phase !== "submitting";
734
+ const isSubmitting = cardState.phase === "submitting";
735
+ const resolvedResult = cardState.phase === "resolved" ? cardState.result : toolResult.result;
736
+ async function callAction(endpoint, body) {
737
+ if (!actionAuth) {
738
+ console.warn("ActionApprovalCard: No ActionAuthProvider found. API calls will be sent without authentication.");
739
+ }
740
+ const headers = {
741
+ "Content-Type": "application/json",
742
+ ...actionAuth?.getHeaders() ?? {}
743
+ };
744
+ const credentials = actionAuth?.getCredentials() ?? "same-origin";
745
+ const res = await fetch(
746
+ `${apiUrl}/api/v1/actions/${toolResult.actionId}/${endpoint}`,
747
+ {
748
+ method: "POST",
749
+ headers,
750
+ credentials,
751
+ body: body ? JSON.stringify(body) : void 0
752
+ }
753
+ );
754
+ if (res.status === 409) {
755
+ let data2;
756
+ try {
757
+ data2 = await res.json();
758
+ } catch {
759
+ throw new Error("Action was already resolved, but the response could not be read. Refresh the page.");
760
+ }
761
+ const status = typeof data2.status === "string" ? data2.status : "failed";
762
+ const label = status.replace(/_/g, " ");
763
+ throw new Error(`This action was already ${label} by another user or policy.`);
764
+ }
765
+ if (!res.ok) {
766
+ const text = await res.text().catch(() => "Unknown error");
767
+ throw new Error(`Server responded ${res.status}: ${text}`);
768
+ }
769
+ let data;
770
+ try {
771
+ data = await res.json();
772
+ } catch {
773
+ throw new Error("Action succeeded, but the response could not be read. Refresh the page.");
774
+ }
775
+ if (typeof data.status !== "string") {
776
+ throw new Error("Action succeeded, but the server returned an invalid status. Refresh the page.");
777
+ }
778
+ setCardState({ phase: "resolved", status: data.status, result: data.result });
779
+ }
780
+ async function handleApprove() {
781
+ setCardState({ phase: "submitting", action: "approve" });
782
+ try {
783
+ await callAction("approve");
784
+ } catch (err) {
785
+ console.error("Action approval failed:", err);
786
+ const message = err instanceof TypeError ? "Network error \u2014 could not reach the server." : err instanceof Error ? err.message : String(err);
787
+ setCardState({ phase: "error", message });
788
+ }
789
+ }
790
+ async function handleDeny() {
791
+ setCardState({ phase: "submitting", action: "deny" });
792
+ try {
793
+ await callAction("deny", denyReason.trim() ? { reason: denyReason.trim() } : void 0);
794
+ } catch (err) {
795
+ console.error("Action approval failed:", err);
796
+ const message = err instanceof TypeError ? "Network error \u2014 could not reach the server." : err instanceof Error ? err.message : String(err);
797
+ setCardState({ phase: "error", message });
798
+ }
799
+ }
800
+ return /* @__PURE__ */ jsxs("div", { className: `my-2 overflow-hidden rounded-lg border ${borderColor(effectiveStatus)} bg-zinc-50 dark:bg-zinc-900`, children: [
801
+ /* @__PURE__ */ jsxs(
802
+ "button",
803
+ {
804
+ onClick: () => setOpen(!open),
805
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/60 dark:hover:bg-zinc-800/60",
806
+ children: [
807
+ /* @__PURE__ */ jsx(ActionStatusBadge, { status: effectiveStatus }),
808
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-zinc-500 dark:text-zinc-400", children: toolResult.summary ?? String(args.description ?? "Action") }),
809
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-600", children: open ? "\u25BE" : "\u25B8" })
810
+ ]
811
+ }
812
+ ),
813
+ open && /* @__PURE__ */ jsxs("div", { className: "border-t border-zinc-100 px-3 py-2 dark:border-zinc-800", children: [
814
+ toolResult.details && /* @__PURE__ */ jsx("pre", { className: "mb-2 max-h-48 overflow-auto rounded bg-zinc-100 p-2 text-xs text-zinc-600 dark:bg-zinc-800 dark:text-zinc-400", children: safeStringify(toolResult.details) }),
815
+ resolvedResult != null && /* @__PURE__ */ jsxs("div", { className: "mb-2 rounded bg-green-50 p-2 text-xs text-green-700 dark:bg-green-900/20 dark:text-green-400", children: [
816
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Result: " }),
817
+ typeof resolvedResult === "string" ? resolvedResult : safeStringify(resolvedResult)
818
+ ] }),
819
+ toolResult.error && /* @__PURE__ */ jsxs("div", { className: "mb-2 rounded bg-red-50 p-2 text-xs text-red-700 dark:bg-red-900/20 dark:text-red-400", children: [
820
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Error: " }),
821
+ toolResult.error
822
+ ] }),
823
+ toolResult.reason && RESOLVED_STATUSES.has(effectiveStatus) && /* @__PURE__ */ jsxs("div", { className: "mb-2 text-xs text-zinc-500 dark:text-zinc-400", children: [
824
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "Reason: " }),
825
+ toolResult.reason
826
+ ] })
827
+ ] }),
828
+ (isPending || isSubmitting || cardState.phase === "error") && /* @__PURE__ */ jsxs("div", { className: "border-t border-zinc-100 px-3 py-2 dark:border-zinc-800", children: [
829
+ cardState.phase === "error" && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-red-600 dark:text-red-400", children: cardState.message }),
830
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
831
+ /* @__PURE__ */ jsxs(
832
+ "button",
833
+ {
834
+ onClick: handleApprove,
835
+ disabled: isSubmitting,
836
+ className: "inline-flex items-center gap-1.5 rounded bg-blue-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-blue-500 disabled:opacity-40",
837
+ children: [
838
+ isSubmitting && cardState.action === "approve" && /* @__PURE__ */ jsx("span", { className: "inline-block h-3 w-3 animate-spin rounded-full border-2 border-white/30 border-t-white" }),
839
+ "Approve"
840
+ ]
841
+ }
842
+ ),
843
+ !showDenyInput ? /* @__PURE__ */ jsx(
844
+ "button",
845
+ {
846
+ onClick: () => setShowDenyInput(true),
847
+ disabled: isSubmitting,
848
+ className: "rounded border border-zinc-300 px-3 py-1.5 text-xs font-medium text-zinc-600 transition-colors hover:border-zinc-400 hover:text-zinc-800 disabled:opacity-40 dark:border-zinc-600 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
849
+ children: "Deny"
850
+ }
851
+ ) : /* @__PURE__ */ jsxs("div", { className: "flex flex-1 items-center gap-2", children: [
852
+ /* @__PURE__ */ jsx(
853
+ "input",
854
+ {
855
+ value: denyReason,
856
+ onChange: (e) => setDenyReason(e.target.value),
857
+ placeholder: "Reason (optional)",
858
+ className: "flex-1 rounded border border-zinc-200 bg-white px-2 py-1 text-xs text-zinc-900 placeholder-zinc-400 outline-none focus:border-red-400 dark:border-zinc-700 dark:bg-zinc-800 dark:text-zinc-100 dark:placeholder-zinc-600",
859
+ disabled: isSubmitting
860
+ }
861
+ ),
862
+ /* @__PURE__ */ jsxs(
863
+ "button",
864
+ {
865
+ onClick: handleDeny,
866
+ disabled: isSubmitting,
867
+ className: "inline-flex items-center gap-1.5 rounded bg-red-600 px-3 py-1.5 text-xs font-medium text-white transition-colors hover:bg-red-500 disabled:opacity-40",
868
+ children: [
869
+ isSubmitting && cardState.action === "deny" && /* @__PURE__ */ jsx("span", { className: "inline-block h-3 w-3 animate-spin rounded-full border-2 border-white/30 border-t-white" }),
870
+ "Confirm Deny"
871
+ ]
872
+ }
873
+ ),
874
+ /* @__PURE__ */ jsx(
875
+ "button",
876
+ {
877
+ onClick: () => {
878
+ setShowDenyInput(false);
879
+ setDenyReason("");
880
+ },
881
+ disabled: isSubmitting,
882
+ className: "text-xs text-zinc-400 hover:text-zinc-600 disabled:opacity-40 dark:hover:text-zinc-300",
883
+ children: "Cancel"
884
+ }
885
+ )
886
+ ] })
887
+ ] })
888
+ ] })
889
+ ] });
890
+ }
891
+ var ResultChart2 = lazy(() => import('./result-chart-NFAJ4IQ5.js').then((m) => ({ default: m.ResultChart })));
892
+ var ALLOWED_IMAGE_MIME = /* @__PURE__ */ new Set(["image/png", "image/jpeg"]);
893
+ var PythonErrorBoundary = class extends Component {
894
+ constructor(props) {
895
+ super(props);
896
+ this.state = { hasError: false };
897
+ }
898
+ static getDerivedStateFromError(error) {
899
+ return { hasError: true, error };
900
+ }
901
+ componentDidCatch(error, info) {
902
+ console.error("PythonResultCard rendering failed:", error, info.componentStack);
903
+ }
904
+ render() {
905
+ if (this.state.hasError) {
906
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 rounded-lg border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400", children: [
907
+ "Python result could not be rendered: ",
908
+ this.state.error?.message ?? "unknown error"
909
+ ] });
910
+ }
911
+ return this.props.children;
912
+ }
913
+ };
914
+ function PythonResultCard({ part }) {
915
+ return /* @__PURE__ */ jsx(PythonErrorBoundary, { children: /* @__PURE__ */ jsx(PythonResultCardInner, { part }) });
916
+ }
917
+ function PythonResultCardInner({ part }) {
918
+ const dark = useContext(DarkModeContext);
919
+ const args = getToolArgs(part);
920
+ const raw = getToolResult(part);
921
+ const done = isToolComplete(part);
922
+ const [open, setOpen] = useState(true);
923
+ if (!done) return /* @__PURE__ */ jsx(LoadingCard, { label: "Running Python..." });
924
+ if (!raw || typeof raw !== "object" || Array.isArray(raw)) {
925
+ return /* @__PURE__ */ jsx("div", { className: "my-2 rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-2 text-xs text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400", children: "Python executed but returned an unexpected result format." });
926
+ }
927
+ const result = raw;
928
+ if (!result.success) {
929
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 overflow-hidden rounded-lg border border-red-300 bg-red-50 dark:border-red-900/50 dark:bg-red-950/20", children: [
930
+ /* @__PURE__ */ jsx("div", { className: "px-3 py-2 text-xs font-medium text-red-700 dark:text-red-400", children: "Python execution failed" }),
931
+ /* @__PURE__ */ jsx("pre", { className: "border-t border-red-200 px-3 py-2 text-xs whitespace-pre-wrap text-red-600 dark:border-red-900/50 dark:text-red-300", children: String(result.error ?? "Unknown error") }),
932
+ !!result.output && /* @__PURE__ */ jsx("pre", { className: "border-t border-red-200 px-3 py-2 text-xs whitespace-pre-wrap text-red-500 dark:border-red-900/50 dark:text-red-400", children: String(result.output) })
933
+ ] });
934
+ }
935
+ const output = result.output ? String(result.output) : null;
936
+ const table = result.table;
937
+ const charts = Array.isArray(result.charts) ? result.charts : void 0;
938
+ const rechartsCharts = Array.isArray(result.rechartsCharts) ? result.rechartsCharts : void 0;
939
+ const hasTable = table && Array.isArray(table.columns) && Array.isArray(table.rows) && table.columns.length > 0 && table.rows.length > 0;
940
+ const hasCharts = charts && charts.length > 0;
941
+ const hasRechartsCharts = rechartsCharts && rechartsCharts.length > 0;
942
+ const safeCharts = hasCharts ? charts.filter((c) => c.base64 && ALLOWED_IMAGE_MIME.has(c.mimeType)) : [];
943
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 overflow-hidden rounded-lg border border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-900", children: [
944
+ /* @__PURE__ */ jsxs(
945
+ "button",
946
+ {
947
+ onClick: () => setOpen(!open),
948
+ className: "flex w-full items-center gap-2 px-3 py-2 text-left text-xs transition-colors hover:bg-zinc-100/60 dark:hover:bg-zinc-800/60",
949
+ children: [
950
+ /* @__PURE__ */ jsx("span", { className: "rounded bg-emerald-100 px-1.5 py-0.5 font-medium text-emerald-700 dark:bg-emerald-600/20 dark:text-emerald-400", children: "Python" }),
951
+ /* @__PURE__ */ jsx("span", { className: "flex-1 truncate text-zinc-500 dark:text-zinc-400", children: String(args.explanation ?? "Python result") }),
952
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-400 dark:text-zinc-600", children: open ? "\u25BE" : "\u25B8" })
953
+ ]
954
+ }
955
+ ),
956
+ open && /* @__PURE__ */ jsxs("div", { className: "space-y-2 border-t border-zinc-100 px-3 py-2 dark:border-zinc-800", children: [
957
+ output && /* @__PURE__ */ jsx("pre", { className: "rounded-md bg-zinc-100 px-3 py-2 text-xs whitespace-pre-wrap text-zinc-700 dark:bg-zinc-800 dark:text-zinc-300", children: output }),
958
+ hasTable && /* @__PURE__ */ jsx(DataTable, { columns: table.columns, rows: table.rows }),
959
+ hasRechartsCharts && rechartsCharts.map((chart, i) => /* @__PURE__ */ jsx(RechartsChartSection, { chart, dark }, i)),
960
+ safeCharts.length > 0 && safeCharts.map((chart, i) => /* @__PURE__ */ jsx(ChartImage, { chart, index: i }, i))
961
+ ] })
962
+ ] });
963
+ }
964
+ function ChartImage({ chart, index }) {
965
+ const [failed, setFailed] = useState(false);
966
+ if (failed) {
967
+ return /* @__PURE__ */ jsxs("div", { className: "rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-2 text-xs text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400", children: [
968
+ "Chart ",
969
+ index + 1,
970
+ " failed to render."
971
+ ] });
972
+ }
973
+ return (
974
+ // eslint-disable-next-line @next/next/no-img-element -- @useatlas/react is framework-agnostic
975
+ /* @__PURE__ */ jsx(
976
+ "img",
977
+ {
978
+ src: `data:${chart.mimeType};base64,${chart.base64}`,
979
+ alt: `Python chart ${index + 1}`,
980
+ className: "max-w-full rounded-lg border border-zinc-200 dark:border-zinc-700",
981
+ onError: () => setFailed(true)
982
+ }
983
+ )
984
+ );
985
+ }
986
+ function RechartsChartSection({ chart, dark }) {
987
+ if (!chart.categoryKey || !Array.isArray(chart.valueKeys) || !Array.isArray(chart.data)) {
988
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-2 text-xs text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400", children: "Chart data is incomplete or malformed." });
989
+ }
990
+ const headers = [chart.categoryKey, ...chart.valueKeys];
991
+ const rows = chart.data.map(
992
+ (row) => headers.map((key) => row[key] == null ? "" : String(row[key]))
993
+ );
994
+ const detectionResult = {
995
+ chartable: true,
996
+ columns: headers.map((h, i) => ({
997
+ header: h,
998
+ type: i === 0 ? "categorical" : "numeric",
999
+ index: i,
1000
+ uniqueCount: i === 0 ? chart.data.length : 0
1001
+ })),
1002
+ recommendations: [{
1003
+ type: chart.type,
1004
+ categoryColumn: { header: chart.categoryKey, type: "categorical", index: 0, uniqueCount: chart.data.length },
1005
+ valueColumns: chart.valueKeys.map((k, i) => ({
1006
+ header: k,
1007
+ type: "numeric",
1008
+ index: i + 1,
1009
+ uniqueCount: 0
1010
+ })),
1011
+ reason: "Python-generated chart"
1012
+ }],
1013
+ data: chart.data.map((row) => {
1014
+ const out = {};
1015
+ for (const key of headers) {
1016
+ const val = row[key];
1017
+ out[key] = typeof val === "number" ? val : String(val ?? "");
1018
+ }
1019
+ return out;
1020
+ })
1021
+ };
1022
+ return /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { className: "h-64 animate-pulse rounded-lg bg-zinc-100 dark:bg-zinc-800" }), children: /* @__PURE__ */ jsx(ResultChart2, { headers, rows, dark, detectionResult }) });
1023
+ }
1024
+ var ToolRendererErrorBoundary = class extends Component {
1025
+ constructor(props) {
1026
+ super(props);
1027
+ this.state = { hasError: false };
1028
+ }
1029
+ static getDerivedStateFromError(error) {
1030
+ return { hasError: true, error };
1031
+ }
1032
+ componentDidCatch(error, info) {
1033
+ console.error(
1034
+ `Custom renderer for tool "${this.props.toolName}" failed:`,
1035
+ error,
1036
+ info.componentStack
1037
+ );
1038
+ }
1039
+ render() {
1040
+ if (this.state.hasError) {
1041
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 rounded-lg border border-red-300 bg-red-50 px-3 py-2 text-xs text-red-700 dark:border-red-900/50 dark:bg-red-950/20 dark:text-red-400", children: [
1042
+ "Custom renderer for \u201C",
1043
+ this.props.toolName,
1044
+ "\u201D failed: ",
1045
+ this.state.error?.message ?? "unknown error"
1046
+ ] });
1047
+ }
1048
+ return this.props.children;
1049
+ }
1050
+ };
1051
+ var ToolPart = memo(function ToolPart2({ part, toolRenderers }) {
1052
+ let name;
1053
+ try {
1054
+ name = getToolName(part);
1055
+ } catch (err) {
1056
+ console.warn("Failed to determine tool name:", err);
1057
+ return /* @__PURE__ */ jsx("div", { className: "my-2 rounded-lg border border-yellow-300 bg-yellow-50 px-3 py-2 text-xs text-yellow-700 dark:border-yellow-900/50 dark:bg-yellow-950/20 dark:text-yellow-400", children: "Tool result (unknown type)" });
1058
+ }
1059
+ const CustomRenderer = toolRenderers?.[name];
1060
+ if (CustomRenderer) {
1061
+ const args = getToolArgs(part);
1062
+ const result = getToolResult(part);
1063
+ const isLoading = !isToolComplete(part);
1064
+ return /* @__PURE__ */ jsx(ToolRendererErrorBoundary, { toolName: name, children: /* @__PURE__ */ jsx(CustomRenderer, { toolName: name, args, result, isLoading }) });
1065
+ }
1066
+ switch (name) {
1067
+ case "explore":
1068
+ return /* @__PURE__ */ jsx(ExploreCard, { part });
1069
+ case "executeSQL":
1070
+ return /* @__PURE__ */ jsx(SQLResultCard, { part });
1071
+ case "executePython":
1072
+ return /* @__PURE__ */ jsx(PythonResultCard, { part });
1073
+ default: {
1074
+ const result = getToolResult(part);
1075
+ if (isActionToolResult(result)) {
1076
+ return /* @__PURE__ */ jsx(ActionApprovalCard, { part });
1077
+ }
1078
+ return /* @__PURE__ */ jsxs("div", { className: "my-2 rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2 text-xs text-zinc-500 dark:border-zinc-700 dark:bg-zinc-900", children: [
1079
+ "Tool: ",
1080
+ name
1081
+ ] });
1082
+ }
1083
+ }
1084
+ }, (prev, next) => {
1085
+ if (isToolComplete(prev.part) && isToolComplete(next.part) && prev.toolRenderers === next.toolRenderers) return true;
1086
+ return false;
1087
+ });
1088
+ var _highlighterCache = null;
1089
+ var _loadFailed = false;
1090
+ function LazyCodeBlock({ language, dark, children }) {
1091
+ const [mod, setMod] = useState(_highlighterCache);
1092
+ useEffect(() => {
1093
+ if (_highlighterCache || _loadFailed) return;
1094
+ Promise.all([
1095
+ import('react-syntax-highlighter'),
1096
+ import('react-syntax-highlighter/dist/esm/styles/prism')
1097
+ ]).then(([sh, styles]) => {
1098
+ _highlighterCache = { Prism: sh.Prism, oneDark: styles.oneDark, oneLight: styles.oneLight };
1099
+ setMod(_highlighterCache);
1100
+ }).catch((err) => {
1101
+ console.warn("Syntax highlighter failed to load \u2014 code blocks will use plain text:", err);
1102
+ _loadFailed = true;
1103
+ });
1104
+ }, []);
1105
+ if (!mod) {
1106
+ return /* @__PURE__ */ jsx("pre", { className: "my-2 overflow-x-auto rounded-lg bg-zinc-100 p-3 text-xs dark:bg-zinc-800", children: /* @__PURE__ */ jsx("code", { children }) });
1107
+ }
1108
+ return /* @__PURE__ */ jsx(
1109
+ mod.Prism,
1110
+ {
1111
+ language,
1112
+ style: dark ? mod.oneDark : mod.oneLight,
1113
+ customStyle: CODE_BLOCK_STYLE,
1114
+ children
1115
+ }
1116
+ );
1117
+ }
1118
+ var CODE_BLOCK_STYLE = {
1119
+ margin: "0.5rem 0",
1120
+ borderRadius: "0.5rem",
1121
+ fontSize: "0.75rem"
1122
+ };
1123
+ var mdComponents = {
1124
+ p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "mb-3 leading-relaxed last:mb-0", children }),
1125
+ h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "mb-2 mt-4 text-lg font-bold first:mt-0", children }),
1126
+ h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "mb-2 mt-3 text-base font-semibold first:mt-0", children }),
1127
+ h3: ({ children }) => /* @__PURE__ */ jsx("h3", { className: "mb-1 mt-2 font-semibold first:mt-0", children }),
1128
+ ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "mb-3 list-disc space-y-1 pl-4", children }),
1129
+ ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "mb-3 list-decimal space-y-1 pl-4", children }),
1130
+ strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "font-semibold text-zinc-900 dark:text-zinc-50", children }),
1131
+ blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { className: "my-2 border-l-2 border-zinc-300 pl-3 text-zinc-500 dark:border-zinc-600 dark:text-zinc-400", children }),
1132
+ table: ({ children }) => /* @__PURE__ */ jsx("div", { className: "my-3 overflow-x-auto rounded-lg border border-zinc-200 dark:border-zinc-700", children: /* @__PURE__ */ jsx("table", { className: "min-w-full text-sm", children }) }),
1133
+ thead: ({ children }) => /* @__PURE__ */ jsx("thead", { className: "border-b border-zinc-200 bg-zinc-50 dark:border-zinc-700 dark:bg-zinc-800/50", children }),
1134
+ tbody: ({ children }) => /* @__PURE__ */ jsx("tbody", { className: "divide-y divide-zinc-100 dark:divide-zinc-800", children }),
1135
+ tr: ({ children }) => /* @__PURE__ */ jsx("tr", { children }),
1136
+ th: ({ children }) => /* @__PURE__ */ jsx("th", { className: "px-3 py-2 text-left text-xs font-medium text-zinc-600 dark:text-zinc-300", children }),
1137
+ td: ({ children }) => /* @__PURE__ */ jsx("td", { className: "px-3 py-2 text-zinc-700 dark:text-zinc-300", children }),
1138
+ pre: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children })
1139
+ };
1140
+ var Markdown = memo(function Markdown2({ content }) {
1141
+ const dark = useContext(DarkModeContext);
1142
+ return /* @__PURE__ */ jsx(
1143
+ ReactMarkdown,
1144
+ {
1145
+ remarkPlugins: [remarkGfm],
1146
+ components: {
1147
+ ...mdComponents,
1148
+ code({ className, children, ...props }) {
1149
+ const match = /language-(\w+)/.exec(className || "");
1150
+ if (match) {
1151
+ return /* @__PURE__ */ jsx(LazyCodeBlock, { language: match[1], dark, children: String(children).replace(/\n$/, "") });
1152
+ }
1153
+ return /* @__PURE__ */ jsx(
1154
+ "code",
1155
+ {
1156
+ className: "rounded bg-zinc-200/50 px-1.5 py-0.5 text-xs text-zinc-800 dark:bg-zinc-700/50 dark:text-zinc-200",
1157
+ ...props,
1158
+ children
1159
+ }
1160
+ );
1161
+ }
1162
+ },
1163
+ children: content
1164
+ }
1165
+ );
1166
+ });
1167
+
1168
+ // src/components/chat/starter-prompts.ts
1169
+ var STARTER_PROMPTS = [
1170
+ "What are the top 10 companies by revenue?",
1171
+ "Show me the distribution of account types",
1172
+ "What is the headcount breakdown by department?",
1173
+ "What is total MRR by plan type?"
1174
+ ];
1175
+ function cn(...inputs) {
1176
+ return twMerge(clsx(inputs));
1177
+ }
1178
+ var buttonVariants = cva(
1179
+ "inline-flex shrink-0 items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-all outline-none focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1180
+ {
1181
+ variants: {
1182
+ variant: {
1183
+ default: "bg-primary text-primary-foreground hover:bg-primary/90",
1184
+ destructive: "bg-destructive text-white hover:bg-destructive/90 focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40",
1185
+ outline: "border bg-background shadow-xs hover:bg-accent hover:text-accent-foreground dark:border-input dark:bg-input/30 dark:hover:bg-input/50",
1186
+ secondary: "bg-secondary text-secondary-foreground hover:bg-secondary/80",
1187
+ ghost: "hover:bg-accent hover:text-accent-foreground dark:hover:bg-accent/50",
1188
+ link: "text-primary underline-offset-4 hover:underline"
1189
+ },
1190
+ size: {
1191
+ default: "h-9 px-4 py-2 has-[>svg]:px-3",
1192
+ xs: "h-6 gap-1 rounded-md px-2 text-xs has-[>svg]:px-1.5 [&_svg:not([class*='size-'])]:size-3",
1193
+ sm: "h-8 gap-1.5 rounded-md px-3 has-[>svg]:px-2.5",
1194
+ lg: "h-10 rounded-md px-6 has-[>svg]:px-4",
1195
+ icon: "size-9",
1196
+ "icon-xs": "size-6 rounded-md [&_svg:not([class*='size-'])]:size-3",
1197
+ "icon-sm": "size-8",
1198
+ "icon-lg": "size-10"
1199
+ }
1200
+ },
1201
+ defaultVariants: {
1202
+ variant: "default",
1203
+ size: "default"
1204
+ }
1205
+ }
1206
+ );
1207
+ function Button({
1208
+ className,
1209
+ variant = "default",
1210
+ size = "default",
1211
+ asChild = false,
1212
+ ...props
1213
+ }) {
1214
+ const Comp = asChild ? Slot.Root : "button";
1215
+ return /* @__PURE__ */ jsx(
1216
+ Comp,
1217
+ {
1218
+ "data-slot": "button",
1219
+ "data-variant": variant,
1220
+ "data-size": size,
1221
+ className: cn(buttonVariants({ variant, size, className })),
1222
+ ...props
1223
+ }
1224
+ );
1225
+ }
1226
+ function FollowUpChips({
1227
+ suggestions,
1228
+ onSelect
1229
+ }) {
1230
+ if (suggestions.length === 0) return null;
1231
+ return /* @__PURE__ */ jsx("div", { className: "flex flex-wrap gap-2 pt-1", children: suggestions.map((s, i) => /* @__PURE__ */ jsx(
1232
+ Button,
1233
+ {
1234
+ variant: "outline",
1235
+ size: "sm",
1236
+ className: "h-auto rounded-full px-3 py-1.5 text-xs font-normal text-zinc-600 dark:text-zinc-400",
1237
+ onClick: () => onSelect(s),
1238
+ children: s
1239
+ },
1240
+ `${i}-${s}`
1241
+ )) });
1242
+ }
1243
+ var toggleVariants = cva(
1244
+ "inline-flex items-center justify-center gap-2 rounded-md text-sm font-medium whitespace-nowrap transition-[color,box-shadow] outline-none hover:bg-muted hover:text-muted-foreground focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 disabled:pointer-events-none disabled:opacity-50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 data-[state=on]:bg-accent data-[state=on]:text-accent-foreground dark:aria-invalid:ring-destructive/40 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4",
1245
+ {
1246
+ variants: {
1247
+ variant: {
1248
+ default: "bg-transparent",
1249
+ outline: "border border-input bg-transparent shadow-xs hover:bg-accent hover:text-accent-foreground"
1250
+ },
1251
+ size: {
1252
+ default: "h-9 min-w-9 px-2",
1253
+ sm: "h-8 min-w-8 px-1.5",
1254
+ lg: "h-10 min-w-10 px-2.5"
1255
+ }
1256
+ },
1257
+ defaultVariants: {
1258
+ variant: "default",
1259
+ size: "default"
1260
+ }
1261
+ }
1262
+ );
1263
+ var ToggleGroupContext = React.createContext({
1264
+ size: "default",
1265
+ variant: "default",
1266
+ spacing: 0
1267
+ });
1268
+ function ToggleGroup({
1269
+ className,
1270
+ variant,
1271
+ size,
1272
+ spacing = 0,
1273
+ children,
1274
+ ...props
1275
+ }) {
1276
+ return /* @__PURE__ */ jsx(
1277
+ ToggleGroup$1.Root,
1278
+ {
1279
+ "data-slot": "toggle-group",
1280
+ "data-variant": variant,
1281
+ "data-size": size,
1282
+ "data-spacing": spacing,
1283
+ style: { "--gap": spacing },
1284
+ className: cn(
1285
+ "group/toggle-group flex w-fit items-center gap-[--spacing(var(--gap))] rounded-md data-[spacing=default]:data-[variant=outline]:shadow-xs",
1286
+ className
1287
+ ),
1288
+ ...props,
1289
+ children: /* @__PURE__ */ jsx(ToggleGroupContext.Provider, { value: { variant, size, spacing }, children })
1290
+ }
1291
+ );
1292
+ }
1293
+ function ToggleGroupItem({
1294
+ className,
1295
+ children,
1296
+ variant,
1297
+ size,
1298
+ ...props
1299
+ }) {
1300
+ const context = React.useContext(ToggleGroupContext);
1301
+ return /* @__PURE__ */ jsx(
1302
+ ToggleGroup$1.Item,
1303
+ {
1304
+ "data-slot": "toggle-group-item",
1305
+ "data-variant": context.variant || variant,
1306
+ "data-size": context.size || size,
1307
+ "data-spacing": context.spacing,
1308
+ className: cn(
1309
+ toggleVariants({
1310
+ variant: context.variant || variant,
1311
+ size: context.size || size
1312
+ }),
1313
+ "w-auto min-w-0 shrink-0 px-3 focus:z-10 focus-visible:z-10",
1314
+ "data-[spacing=0]:rounded-none data-[spacing=0]:shadow-none data-[spacing=0]:first:rounded-l-md data-[spacing=0]:last:rounded-r-md data-[spacing=0]:data-[variant=outline]:border-l-0 data-[spacing=0]:data-[variant=outline]:first:border-l",
1315
+ className
1316
+ ),
1317
+ ...props,
1318
+ children
1319
+ }
1320
+ );
1321
+ }
1322
+ var badgeVariants = cva(
1323
+ "inline-flex w-fit shrink-0 items-center justify-center gap-1 overflow-hidden rounded-full border border-transparent px-2 py-0.5 text-xs font-medium whitespace-nowrap transition-[color,box-shadow] focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50 aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40 [&>svg]:pointer-events-none [&>svg]:size-3",
1324
+ {
1325
+ variants: {
1326
+ variant: {
1327
+ default: "bg-primary text-primary-foreground [a&]:hover:bg-primary/90",
1328
+ secondary: "bg-secondary text-secondary-foreground [a&]:hover:bg-secondary/90",
1329
+ destructive: "bg-destructive text-white focus-visible:ring-destructive/20 dark:bg-destructive/60 dark:focus-visible:ring-destructive/40 [a&]:hover:bg-destructive/90",
1330
+ outline: "border-border text-foreground [a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1331
+ ghost: "[a&]:hover:bg-accent [a&]:hover:text-accent-foreground",
1332
+ link: "text-primary underline-offset-4 [a&]:hover:underline"
1333
+ }
1334
+ },
1335
+ defaultVariants: {
1336
+ variant: "default"
1337
+ }
1338
+ }
1339
+ );
1340
+ function Badge({
1341
+ className,
1342
+ variant = "default",
1343
+ asChild = false,
1344
+ ...props
1345
+ }) {
1346
+ const Comp = asChild ? Slot.Root : "span";
1347
+ return /* @__PURE__ */ jsx(
1348
+ Comp,
1349
+ {
1350
+ "data-slot": "badge",
1351
+ "data-variant": variant,
1352
+ className: cn(badgeVariants({ variant }), className),
1353
+ ...props
1354
+ }
1355
+ );
1356
+ }
1357
+ function DeleteConfirmation({
1358
+ onConfirm,
1359
+ onCancel
1360
+ }) {
1361
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-3 py-2 text-xs", children: [
1362
+ /* @__PURE__ */ jsx("span", { className: "text-zinc-500 dark:text-zinc-400", children: "Delete?" }),
1363
+ /* @__PURE__ */ jsx(
1364
+ "button",
1365
+ {
1366
+ onClick: onCancel,
1367
+ className: "rounded px-2 py-0.5 text-zinc-500 transition-colors hover:text-zinc-800 dark:text-zinc-400 dark:hover:text-zinc-200",
1368
+ children: "Cancel"
1369
+ }
1370
+ ),
1371
+ /* @__PURE__ */ jsx(
1372
+ "button",
1373
+ {
1374
+ onClick: onConfirm,
1375
+ className: "rounded bg-red-600 px-2 py-0.5 text-white transition-colors hover:bg-red-500",
1376
+ children: "Delete"
1377
+ }
1378
+ )
1379
+ ] });
1380
+ }
1381
+ function relativeTime(dateStr) {
1382
+ const then = new Date(dateStr).getTime();
1383
+ if (isNaN(then)) return "";
1384
+ const now = Date.now();
1385
+ const diff = now - then;
1386
+ const minutes = Math.floor(diff / 6e4);
1387
+ if (minutes < 1) return "just now";
1388
+ if (minutes < 60) return `${minutes}m ago`;
1389
+ const hours = Math.floor(minutes / 60);
1390
+ if (hours < 24) return `${hours}h ago`;
1391
+ const days = Math.floor(hours / 24);
1392
+ if (days < 7) return `${days}d ago`;
1393
+ return new Date(dateStr).toLocaleDateString(void 0, { month: "short", day: "numeric" });
1394
+ }
1395
+ function ConversationItem({
1396
+ conversation,
1397
+ isActive,
1398
+ onSelect,
1399
+ onDelete,
1400
+ onStar
1401
+ }) {
1402
+ const [confirmDelete, setConfirmDelete] = useState(false);
1403
+ const [deleting, setDeleting] = useState(false);
1404
+ const [starPending, setStarPending] = useState(false);
1405
+ if (confirmDelete) {
1406
+ return /* @__PURE__ */ jsx("div", { className: "rounded-lg border border-red-200 bg-red-50/50 dark:border-red-900/30 dark:bg-red-950/10", children: /* @__PURE__ */ jsx(
1407
+ DeleteConfirmation,
1408
+ {
1409
+ onCancel: () => setConfirmDelete(false),
1410
+ onConfirm: async () => {
1411
+ setDeleting(true);
1412
+ try {
1413
+ const success = await onDelete();
1414
+ if (success) {
1415
+ setConfirmDelete(false);
1416
+ }
1417
+ } catch (err) {
1418
+ console.warn("Failed to delete conversation:", err);
1419
+ } finally {
1420
+ setDeleting(false);
1421
+ }
1422
+ }
1423
+ }
1424
+ ) });
1425
+ }
1426
+ return /* @__PURE__ */ jsxs(
1427
+ "div",
1428
+ {
1429
+ role: "button",
1430
+ tabIndex: 0,
1431
+ onClick: onSelect,
1432
+ onKeyDown: (e) => {
1433
+ if (e.key === "Enter" || e.key === " ") {
1434
+ e.preventDefault();
1435
+ onSelect();
1436
+ }
1437
+ },
1438
+ className: `group flex w-full cursor-pointer items-center gap-2 rounded-lg px-3 py-2.5 text-left text-sm transition-colors ${isActive ? "bg-blue-50 text-blue-700 dark:bg-blue-600/10 dark:text-blue-400" : "text-zinc-700 hover:bg-zinc-100 dark:text-zinc-300 dark:hover:bg-zinc-800"}`,
1439
+ children: [
1440
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
1441
+ /* @__PURE__ */ jsx("p", { className: "truncate text-sm font-medium", children: conversation.title || "New conversation" }),
1442
+ /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-400 dark:text-zinc-500", children: relativeTime(conversation.updatedAt) })
1443
+ ] }),
1444
+ /* @__PURE__ */ jsxs("div", { className: "flex shrink-0 items-center gap-0.5", children: [
1445
+ /* @__PURE__ */ jsx(
1446
+ Button,
1447
+ {
1448
+ variant: "ghost",
1449
+ size: "icon",
1450
+ onClick: async (e) => {
1451
+ e.stopPropagation();
1452
+ if (starPending) return;
1453
+ setStarPending(true);
1454
+ try {
1455
+ await onStar(!conversation.starred);
1456
+ } catch (err) {
1457
+ console.warn("Failed to update star:", err);
1458
+ } finally {
1459
+ setStarPending(false);
1460
+ }
1461
+ },
1462
+ disabled: starPending,
1463
+ className: `size-8 transition-all ${conversation.starred ? "text-amber-400 opacity-100 hover:text-amber-500 dark:text-amber-400 dark:hover:text-amber-300" : "text-zinc-400 opacity-0 hover:text-amber-400 group-hover:opacity-100 dark:hover:text-amber-400"} ${starPending ? "opacity-50" : ""}`,
1464
+ "aria-label": conversation.starred ? "Unstar conversation" : "Star conversation",
1465
+ children: /* @__PURE__ */ jsx(Star, { className: "h-3.5 w-3.5", fill: conversation.starred ? "currentColor" : "none" })
1466
+ }
1467
+ ),
1468
+ /* @__PURE__ */ jsx(
1469
+ Button,
1470
+ {
1471
+ variant: "ghost",
1472
+ size: "icon",
1473
+ onClick: (e) => {
1474
+ e.stopPropagation();
1475
+ setConfirmDelete(true);
1476
+ },
1477
+ disabled: deleting,
1478
+ className: "size-8 shrink-0 text-zinc-400 opacity-0 transition-all hover:bg-red-50 hover:text-red-500 group-hover:opacity-100 dark:hover:bg-red-950/20 dark:hover:text-red-400",
1479
+ "aria-label": "Delete conversation",
1480
+ children: /* @__PURE__ */ jsx(Trash2, { className: "h-3.5 w-3.5" })
1481
+ }
1482
+ )
1483
+ ] })
1484
+ ]
1485
+ }
1486
+ );
1487
+ }
1488
+ function ConversationList({
1489
+ conversations,
1490
+ selectedId,
1491
+ onSelect,
1492
+ onDelete,
1493
+ onStar,
1494
+ showSections = true,
1495
+ emptyMessage
1496
+ }) {
1497
+ if (conversations.length === 0) {
1498
+ return /* @__PURE__ */ jsx("div", { className: "px-3 py-6 text-center text-xs text-zinc-400 dark:text-zinc-500", children: emptyMessage ?? "No conversations yet" });
1499
+ }
1500
+ function renderItems(items) {
1501
+ return items.map((c) => /* @__PURE__ */ jsx(
1502
+ ConversationItem,
1503
+ {
1504
+ conversation: c,
1505
+ isActive: c.id === selectedId,
1506
+ onSelect: () => onSelect(c.id),
1507
+ onDelete: () => onDelete(c.id),
1508
+ onStar: (s) => onStar(c.id, s)
1509
+ },
1510
+ c.id
1511
+ ));
1512
+ }
1513
+ if (!showSections) {
1514
+ return /* @__PURE__ */ jsx("div", { className: "space-y-1", children: renderItems(conversations) });
1515
+ }
1516
+ const starred = conversations.filter((c) => c.starred);
1517
+ const unstarred = conversations.filter((c) => !c.starred);
1518
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1519
+ starred.length > 0 && /* @__PURE__ */ jsxs(Fragment, { children: [
1520
+ /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-2 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500", children: "Starred" }),
1521
+ renderItems(starred),
1522
+ unstarred.length > 0 && /* @__PURE__ */ jsx("div", { className: "px-3 pb-1 pt-3 text-[10px] font-semibold uppercase tracking-wider text-zinc-400 dark:text-zinc-500", children: "Recent" })
1523
+ ] }),
1524
+ renderItems(unstarred)
1525
+ ] });
1526
+ }
1527
+ function ConversationSidebar({
1528
+ conversations,
1529
+ selectedId,
1530
+ loading,
1531
+ onSelect,
1532
+ onDelete,
1533
+ onStar,
1534
+ onNewChat,
1535
+ mobileOpen,
1536
+ onMobileClose
1537
+ }) {
1538
+ const [filter, setFilter] = useState("all");
1539
+ const starredConversations = conversations.filter((c) => c.starred);
1540
+ const filteredConversations = filter === "saved" ? starredConversations : conversations;
1541
+ const sidebar = /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col border-r border-zinc-200 bg-zinc-50 dark:border-zinc-800 dark:bg-zinc-950", children: [
1542
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between border-b border-zinc-200 px-3 py-3 dark:border-zinc-800", children: [
1543
+ /* @__PURE__ */ jsx("span", { className: "text-sm font-medium text-zinc-700 dark:text-zinc-300", children: "History" }),
1544
+ /* @__PURE__ */ jsx(
1545
+ "button",
1546
+ {
1547
+ onClick: onNewChat,
1548
+ className: "rounded-lg border border-zinc-200 px-3 py-1.5 text-xs font-medium text-zinc-600 transition-colors hover:border-zinc-400 hover:text-zinc-900 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
1549
+ children: "+ New"
1550
+ }
1551
+ )
1552
+ ] }),
1553
+ /* @__PURE__ */ jsx("div", { className: "border-b border-zinc-200 px-3 py-2 dark:border-zinc-800", children: /* @__PURE__ */ jsxs(
1554
+ ToggleGroup,
1555
+ {
1556
+ type: "single",
1557
+ size: "sm",
1558
+ value: filter,
1559
+ onValueChange: (val) => {
1560
+ if (val) setFilter(val);
1561
+ },
1562
+ className: "gap-1",
1563
+ children: [
1564
+ /* @__PURE__ */ jsx(ToggleGroupItem, { value: "all", className: "px-2.5 text-xs", children: "All" }),
1565
+ /* @__PURE__ */ jsxs(ToggleGroupItem, { value: "saved", className: "gap-1.5 px-2.5 text-xs", children: [
1566
+ /* @__PURE__ */ jsx(Star, { className: "h-3 w-3", fill: filter === "saved" ? "currentColor" : "none" }),
1567
+ "Saved",
1568
+ starredConversations.length > 0 && /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "h-4 px-1.5 text-[10px] font-semibold", children: starredConversations.length })
1569
+ ] })
1570
+ ]
1571
+ }
1572
+ ) }),
1573
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto p-2", children: loading && conversations.length === 0 ? /* @__PURE__ */ jsx("div", { className: "flex items-center justify-center py-8", children: /* @__PURE__ */ jsx("span", { className: "inline-block h-4 w-4 animate-spin rounded-full border-2 border-zinc-300 border-t-zinc-600 dark:border-zinc-600 dark:border-t-zinc-300" }) }) : /* @__PURE__ */ jsx(
1574
+ ConversationList,
1575
+ {
1576
+ conversations: filteredConversations,
1577
+ selectedId,
1578
+ onSelect,
1579
+ onDelete,
1580
+ onStar,
1581
+ showSections: filter === "all",
1582
+ emptyMessage: filter === "saved" ? "Star conversations to save them here" : void 0
1583
+ }
1584
+ ) })
1585
+ ] });
1586
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
1587
+ /* @__PURE__ */ jsx("div", { className: "hidden w-[280px] shrink-0 md:block", children: sidebar }),
1588
+ mobileOpen && /* @__PURE__ */ jsxs(Fragment, { children: [
1589
+ /* @__PURE__ */ jsx(
1590
+ "div",
1591
+ {
1592
+ className: "fixed inset-0 z-40 bg-black/30 md:hidden",
1593
+ onClick: onMobileClose
1594
+ }
1595
+ ),
1596
+ /* @__PURE__ */ jsx("div", { className: "fixed inset-y-0 left-0 z-50 w-[280px] md:hidden", children: sidebar })
1597
+ ] })
1598
+ ] });
1599
+ }
1600
+ function AlertDialog({
1601
+ ...props
1602
+ }) {
1603
+ return /* @__PURE__ */ jsx(AlertDialog$1.Root, { "data-slot": "alert-dialog", ...props });
1604
+ }
1605
+ function AlertDialogPortal({
1606
+ ...props
1607
+ }) {
1608
+ return /* @__PURE__ */ jsx(AlertDialog$1.Portal, { "data-slot": "alert-dialog-portal", ...props });
1609
+ }
1610
+ function AlertDialogOverlay({
1611
+ className,
1612
+ ...props
1613
+ }) {
1614
+ return /* @__PURE__ */ jsx(
1615
+ AlertDialog$1.Overlay,
1616
+ {
1617
+ "data-slot": "alert-dialog-overlay",
1618
+ className: cn(
1619
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
1620
+ className
1621
+ ),
1622
+ ...props
1623
+ }
1624
+ );
1625
+ }
1626
+ function AlertDialogContent({
1627
+ className,
1628
+ size = "default",
1629
+ ...props
1630
+ }) {
1631
+ return /* @__PURE__ */ jsxs(AlertDialogPortal, { children: [
1632
+ /* @__PURE__ */ jsx(AlertDialogOverlay, {}),
1633
+ /* @__PURE__ */ jsx(
1634
+ AlertDialog$1.Content,
1635
+ {
1636
+ "data-slot": "alert-dialog-content",
1637
+ "data-size": size,
1638
+ className: cn(
1639
+ "group/alert-dialog-content fixed top-[50%] left-[50%] z-50 grid w-full max-w-[calc(100%-2rem)] translate-x-[-50%] translate-y-[-50%] gap-4 rounded-lg border bg-background p-6 shadow-lg duration-200 data-[size=sm]:max-w-xs data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95 data-[size=default]:sm:max-w-lg",
1640
+ className
1641
+ ),
1642
+ ...props
1643
+ }
1644
+ )
1645
+ ] });
1646
+ }
1647
+ function AlertDialogHeader({
1648
+ className,
1649
+ ...props
1650
+ }) {
1651
+ return /* @__PURE__ */ jsx(
1652
+ "div",
1653
+ {
1654
+ "data-slot": "alert-dialog-header",
1655
+ className: cn(
1656
+ "grid grid-rows-[auto_1fr] place-items-center gap-1.5 text-center has-data-[slot=alert-dialog-media]:grid-rows-[auto_auto_1fr] has-data-[slot=alert-dialog-media]:gap-x-6 sm:group-data-[size=default]/alert-dialog-content:place-items-start sm:group-data-[size=default]/alert-dialog-content:text-left sm:group-data-[size=default]/alert-dialog-content:has-data-[slot=alert-dialog-media]:grid-rows-[auto_1fr]",
1657
+ className
1658
+ ),
1659
+ ...props
1660
+ }
1661
+ );
1662
+ }
1663
+ function AlertDialogFooter({
1664
+ className,
1665
+ ...props
1666
+ }) {
1667
+ return /* @__PURE__ */ jsx(
1668
+ "div",
1669
+ {
1670
+ "data-slot": "alert-dialog-footer",
1671
+ className: cn(
1672
+ "flex flex-col-reverse gap-2 group-data-[size=sm]/alert-dialog-content:grid group-data-[size=sm]/alert-dialog-content:grid-cols-2 sm:flex-row sm:justify-end",
1673
+ className
1674
+ ),
1675
+ ...props
1676
+ }
1677
+ );
1678
+ }
1679
+ function AlertDialogTitle({
1680
+ className,
1681
+ ...props
1682
+ }) {
1683
+ return /* @__PURE__ */ jsx(
1684
+ AlertDialog$1.Title,
1685
+ {
1686
+ "data-slot": "alert-dialog-title",
1687
+ className: cn(
1688
+ "text-lg font-semibold sm:group-data-[size=default]/alert-dialog-content:group-has-data-[slot=alert-dialog-media]/alert-dialog-content:col-start-2",
1689
+ className
1690
+ ),
1691
+ ...props
1692
+ }
1693
+ );
1694
+ }
1695
+ function AlertDialogDescription({
1696
+ className,
1697
+ ...props
1698
+ }) {
1699
+ return /* @__PURE__ */ jsx(
1700
+ AlertDialog$1.Description,
1701
+ {
1702
+ "data-slot": "alert-dialog-description",
1703
+ className: cn("text-sm text-muted-foreground", className),
1704
+ ...props
1705
+ }
1706
+ );
1707
+ }
1708
+ function Input({ className, type, ...props }) {
1709
+ return /* @__PURE__ */ jsx(
1710
+ "input",
1711
+ {
1712
+ type,
1713
+ "data-slot": "input",
1714
+ className: cn(
1715
+ "h-9 w-full min-w-0 rounded-md border border-input bg-transparent px-3 py-1 text-base shadow-xs transition-[color,box-shadow] outline-none selection:bg-primary selection:text-primary-foreground file:inline-flex file:h-7 file:border-0 file:bg-transparent file:text-sm file:font-medium file:text-foreground placeholder:text-muted-foreground disabled:pointer-events-none disabled:cursor-not-allowed disabled:opacity-50 md:text-sm dark:bg-input/30",
1716
+ "focus-visible:border-ring focus-visible:ring-[3px] focus-visible:ring-ring/50",
1717
+ "aria-invalid:border-destructive aria-invalid:ring-destructive/20 dark:aria-invalid:ring-destructive/40",
1718
+ className
1719
+ ),
1720
+ ...props
1721
+ }
1722
+ );
1723
+ }
1724
+ function ChangePasswordDialog({
1725
+ open,
1726
+ onComplete
1727
+ }) {
1728
+ const { apiUrl, isCrossOrigin } = useAtlasConfig();
1729
+ const credentials = isCrossOrigin ? "include" : "same-origin";
1730
+ const [currentPassword, setCurrentPassword] = useState("atlas-dev");
1731
+ const [newPassword, setNewPassword] = useState("");
1732
+ const [confirmPassword, setConfirmPassword] = useState("");
1733
+ const [error, setError] = useState("");
1734
+ const [loading, setLoading] = useState(false);
1735
+ async function handleSubmit(e) {
1736
+ e.preventDefault();
1737
+ setError("");
1738
+ if (newPassword.length < 8) {
1739
+ setError("Password must be at least 8 characters.");
1740
+ return;
1741
+ }
1742
+ if (newPassword !== confirmPassword) {
1743
+ setError("Passwords do not match.");
1744
+ return;
1745
+ }
1746
+ if (newPassword === currentPassword) {
1747
+ setError("New password must be different from current password.");
1748
+ return;
1749
+ }
1750
+ setLoading(true);
1751
+ try {
1752
+ const res = await fetch(`${apiUrl}/api/v1/admin/me/password`, {
1753
+ method: "POST",
1754
+ credentials,
1755
+ headers: { "Content-Type": "application/json" },
1756
+ body: JSON.stringify({ currentPassword, newPassword })
1757
+ });
1758
+ if (!res.ok) {
1759
+ const data = await res.json().catch(() => ({}));
1760
+ setError(data.message ?? `Failed (HTTP ${res.status})`);
1761
+ return;
1762
+ }
1763
+ onComplete();
1764
+ } catch (err) {
1765
+ setError(err instanceof Error ? err.message : "Failed to change password");
1766
+ } finally {
1767
+ setLoading(false);
1768
+ }
1769
+ }
1770
+ return /* @__PURE__ */ jsx(AlertDialog, { open, children: /* @__PURE__ */ jsx(AlertDialogContent, { className: "sm:max-w-md", onEscapeKeyDown: (e) => e.preventDefault(), children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
1771
+ /* @__PURE__ */ jsxs(AlertDialogHeader, { children: [
1772
+ /* @__PURE__ */ jsx(AlertDialogTitle, { children: "Change your password" }),
1773
+ /* @__PURE__ */ jsx(AlertDialogDescription, { children: "You're using the default dev password. Please set a new password to continue." })
1774
+ ] }),
1775
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 py-4", children: [
1776
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1777
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Current password" }),
1778
+ /* @__PURE__ */ jsx(
1779
+ Input,
1780
+ {
1781
+ type: "password",
1782
+ value: currentPassword,
1783
+ onChange: (e) => setCurrentPassword(e.target.value),
1784
+ required: true
1785
+ }
1786
+ )
1787
+ ] }),
1788
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1789
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "New password" }),
1790
+ /* @__PURE__ */ jsx(
1791
+ Input,
1792
+ {
1793
+ type: "password",
1794
+ value: newPassword,
1795
+ onChange: (e) => setNewPassword(e.target.value),
1796
+ required: true,
1797
+ minLength: 8,
1798
+ placeholder: "At least 8 characters"
1799
+ }
1800
+ )
1801
+ ] }),
1802
+ /* @__PURE__ */ jsxs("div", { className: "space-y-1", children: [
1803
+ /* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Confirm new password" }),
1804
+ /* @__PURE__ */ jsx(
1805
+ Input,
1806
+ {
1807
+ type: "password",
1808
+ value: confirmPassword,
1809
+ onChange: (e) => setConfirmPassword(e.target.value),
1810
+ required: true,
1811
+ minLength: 8
1812
+ }
1813
+ )
1814
+ ] }),
1815
+ error && /* @__PURE__ */ jsx("p", { className: "text-xs text-red-600 dark:text-red-400", children: error })
1816
+ ] }),
1817
+ /* @__PURE__ */ jsx(AlertDialogFooter, { children: /* @__PURE__ */ jsx(Button, { type: "submit", disabled: loading, children: loading ? "Changing..." : "Change password" }) })
1818
+ ] }) }) });
1819
+ }
1820
+ function Sheet({ ...props }) {
1821
+ return /* @__PURE__ */ jsx(Dialog.Root, { "data-slot": "sheet", ...props });
1822
+ }
1823
+ function SheetPortal({
1824
+ ...props
1825
+ }) {
1826
+ return /* @__PURE__ */ jsx(Dialog.Portal, { "data-slot": "sheet-portal", ...props });
1827
+ }
1828
+ function SheetOverlay({
1829
+ className,
1830
+ ...props
1831
+ }) {
1832
+ return /* @__PURE__ */ jsx(
1833
+ Dialog.Overlay,
1834
+ {
1835
+ "data-slot": "sheet-overlay",
1836
+ className: cn(
1837
+ "fixed inset-0 z-50 bg-black/50 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:animate-in data-[state=open]:fade-in-0",
1838
+ className
1839
+ ),
1840
+ ...props
1841
+ }
1842
+ );
1843
+ }
1844
+ function SheetContent({
1845
+ className,
1846
+ children,
1847
+ side = "right",
1848
+ showCloseButton = true,
1849
+ ...props
1850
+ }) {
1851
+ return /* @__PURE__ */ jsxs(SheetPortal, { children: [
1852
+ /* @__PURE__ */ jsx(SheetOverlay, {}),
1853
+ /* @__PURE__ */ jsxs(
1854
+ Dialog.Content,
1855
+ {
1856
+ "data-slot": "sheet-content",
1857
+ className: cn(
1858
+ "fixed z-50 flex flex-col gap-4 bg-background shadow-lg transition ease-in-out data-[state=closed]:animate-out data-[state=closed]:duration-300 data-[state=open]:animate-in data-[state=open]:duration-500",
1859
+ side === "right" && "inset-y-0 right-0 h-full w-3/4 border-l data-[state=closed]:slide-out-to-right data-[state=open]:slide-in-from-right sm:max-w-sm",
1860
+ side === "left" && "inset-y-0 left-0 h-full w-3/4 border-r data-[state=closed]:slide-out-to-left data-[state=open]:slide-in-from-left sm:max-w-sm",
1861
+ side === "top" && "inset-x-0 top-0 h-auto border-b data-[state=closed]:slide-out-to-top data-[state=open]:slide-in-from-top",
1862
+ side === "bottom" && "inset-x-0 bottom-0 h-auto border-t data-[state=closed]:slide-out-to-bottom data-[state=open]:slide-in-from-bottom",
1863
+ className
1864
+ ),
1865
+ ...props,
1866
+ children: [
1867
+ children,
1868
+ showCloseButton && /* @__PURE__ */ jsxs(Dialog.Close, { className: "absolute top-4 right-4 rounded-xs opacity-70 ring-offset-background transition-opacity hover:opacity-100 focus:ring-2 focus:ring-ring focus:ring-offset-2 focus:outline-hidden disabled:pointer-events-none data-[state=open]:bg-secondary", children: [
1869
+ /* @__PURE__ */ jsx(XIcon, { className: "size-4" }),
1870
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Close" })
1871
+ ] })
1872
+ ]
1873
+ }
1874
+ )
1875
+ ] });
1876
+ }
1877
+ function SheetHeader({ className, ...props }) {
1878
+ return /* @__PURE__ */ jsx(
1879
+ "div",
1880
+ {
1881
+ "data-slot": "sheet-header",
1882
+ className: cn("flex flex-col gap-1.5 p-4", className),
1883
+ ...props
1884
+ }
1885
+ );
1886
+ }
1887
+ function SheetTitle({
1888
+ className,
1889
+ ...props
1890
+ }) {
1891
+ return /* @__PURE__ */ jsx(
1892
+ Dialog.Title,
1893
+ {
1894
+ "data-slot": "sheet-title",
1895
+ className: cn("font-semibold text-foreground", className),
1896
+ ...props
1897
+ }
1898
+ );
1899
+ }
1900
+ function ScrollArea({
1901
+ className,
1902
+ children,
1903
+ viewportRef,
1904
+ ...props
1905
+ }) {
1906
+ return /* @__PURE__ */ jsxs(
1907
+ ScrollArea$1.Root,
1908
+ {
1909
+ "data-slot": "scroll-area",
1910
+ className: cn("relative overflow-hidden", className),
1911
+ ...props,
1912
+ children: [
1913
+ /* @__PURE__ */ jsx(
1914
+ ScrollArea$1.Viewport,
1915
+ {
1916
+ ref: viewportRef,
1917
+ "data-slot": "scroll-area-viewport",
1918
+ className: "size-full rounded-[inherit] transition-[color,box-shadow] outline-none focus-visible:ring-[3px] focus-visible:ring-ring/50 focus-visible:outline-1",
1919
+ children
1920
+ }
1921
+ ),
1922
+ /* @__PURE__ */ jsx(ScrollBar, {}),
1923
+ /* @__PURE__ */ jsx(ScrollArea$1.Corner, {})
1924
+ ]
1925
+ }
1926
+ );
1927
+ }
1928
+ function ScrollBar({
1929
+ className,
1930
+ orientation = "vertical",
1931
+ ...props
1932
+ }) {
1933
+ return /* @__PURE__ */ jsx(
1934
+ ScrollArea$1.ScrollAreaScrollbar,
1935
+ {
1936
+ "data-slot": "scroll-area-scrollbar",
1937
+ orientation,
1938
+ className: cn(
1939
+ "flex touch-none p-px transition-colors select-none",
1940
+ orientation === "vertical" && "h-full w-2.5 border-l border-l-transparent",
1941
+ orientation === "horizontal" && "h-2.5 flex-col border-t border-t-transparent",
1942
+ className
1943
+ ),
1944
+ ...props,
1945
+ children: /* @__PURE__ */ jsx(
1946
+ ScrollArea$1.ScrollAreaThumb,
1947
+ {
1948
+ "data-slot": "scroll-area-thumb",
1949
+ className: "relative flex-1 rounded-full bg-border"
1950
+ }
1951
+ )
1952
+ }
1953
+ );
1954
+ }
1955
+ function Separator({
1956
+ className,
1957
+ orientation = "horizontal",
1958
+ decorative = true,
1959
+ ...props
1960
+ }) {
1961
+ return /* @__PURE__ */ jsx(
1962
+ Separator$1.Root,
1963
+ {
1964
+ "data-slot": "separator",
1965
+ decorative,
1966
+ orientation,
1967
+ className: cn(
1968
+ "shrink-0 bg-border data-[orientation=horizontal]:h-px data-[orientation=horizontal]:w-full data-[orientation=vertical]:h-full data-[orientation=vertical]:w-px",
1969
+ className
1970
+ ),
1971
+ ...props
1972
+ }
1973
+ );
1974
+ }
1975
+ function Table({ className, ...props }) {
1976
+ return /* @__PURE__ */ jsx(
1977
+ "div",
1978
+ {
1979
+ "data-slot": "table-container",
1980
+ className: "relative w-full overflow-x-auto",
1981
+ children: /* @__PURE__ */ jsx(
1982
+ "table",
1983
+ {
1984
+ "data-slot": "table",
1985
+ className: cn("w-full caption-bottom text-sm", className),
1986
+ ...props
1987
+ }
1988
+ )
1989
+ }
1990
+ );
1991
+ }
1992
+ function TableHeader({ className, ...props }) {
1993
+ return /* @__PURE__ */ jsx(
1994
+ "thead",
1995
+ {
1996
+ "data-slot": "table-header",
1997
+ className: cn("[&_tr]:border-b", className),
1998
+ ...props
1999
+ }
2000
+ );
2001
+ }
2002
+ function TableBody({ className, ...props }) {
2003
+ return /* @__PURE__ */ jsx(
2004
+ "tbody",
2005
+ {
2006
+ "data-slot": "table-body",
2007
+ className: cn("[&_tr:last-child]:border-0", className),
2008
+ ...props
2009
+ }
2010
+ );
2011
+ }
2012
+ function TableRow({ className, ...props }) {
2013
+ return /* @__PURE__ */ jsx(
2014
+ "tr",
2015
+ {
2016
+ "data-slot": "table-row",
2017
+ className: cn(
2018
+ "border-b transition-colors hover:bg-muted/50 data-[state=selected]:bg-muted",
2019
+ className
2020
+ ),
2021
+ ...props
2022
+ }
2023
+ );
2024
+ }
2025
+ function TableHead({ className, ...props }) {
2026
+ return /* @__PURE__ */ jsx(
2027
+ "th",
2028
+ {
2029
+ "data-slot": "table-head",
2030
+ className: cn(
2031
+ "h-10 px-2 text-left align-middle font-medium whitespace-nowrap text-foreground [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
2032
+ className
2033
+ ),
2034
+ ...props
2035
+ }
2036
+ );
2037
+ }
2038
+ function TableCell({ className, ...props }) {
2039
+ return /* @__PURE__ */ jsx(
2040
+ "td",
2041
+ {
2042
+ "data-slot": "table-cell",
2043
+ className: cn(
2044
+ "p-2 align-middle whitespace-nowrap [&:has([role=checkbox])]:pr-0 [&>[role=checkbox]]:translate-y-[2px]",
2045
+ className
2046
+ ),
2047
+ ...props
2048
+ }
2049
+ );
2050
+ }
2051
+ function Card({ className, ...props }) {
2052
+ return /* @__PURE__ */ jsx(
2053
+ "div",
2054
+ {
2055
+ "data-slot": "card",
2056
+ className: cn(
2057
+ "flex flex-col gap-6 rounded-xl border bg-card py-6 text-card-foreground shadow-sm",
2058
+ className
2059
+ ),
2060
+ ...props
2061
+ }
2062
+ );
2063
+ }
2064
+ function CardContent({ className, ...props }) {
2065
+ return /* @__PURE__ */ jsx(
2066
+ "div",
2067
+ {
2068
+ "data-slot": "card-content",
2069
+ className: cn("px-6", className),
2070
+ ...props
2071
+ }
2072
+ );
2073
+ }
2074
+ async function parseErrorResponse(r) {
2075
+ try {
2076
+ const body = await r.json();
2077
+ if (typeof body?.message === "string") return body.message;
2078
+ } catch (err) {
2079
+ console.warn("Could not parse error response body as JSON:", err);
2080
+ }
2081
+ return `HTTP ${r.status}`;
2082
+ }
2083
+ function EntityList({
2084
+ entities,
2085
+ search,
2086
+ onSearchChange,
2087
+ typeFilter,
2088
+ onTypeFilterChange,
2089
+ onSelect
2090
+ }) {
2091
+ const filtered = entities.filter((e) => {
2092
+ const matchesSearch = !search || e.table.toLowerCase().includes(search.toLowerCase()) || e.description.toLowerCase().includes(search.toLowerCase());
2093
+ const matchesType = typeFilter === "all" || (typeFilter === "view" ? e.type === "view" : e.type !== "view");
2094
+ return matchesSearch && matchesType;
2095
+ });
2096
+ const tableCount = entities.filter((e) => e.type !== "view").length;
2097
+ const viewCount = entities.filter((e) => e.type === "view").length;
2098
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
2099
+ /* @__PURE__ */ jsxs("div", { className: "space-y-3 px-4 pb-3", children: [
2100
+ /* @__PURE__ */ jsxs("div", { className: "relative", children: [
2101
+ /* @__PURE__ */ jsx(Search, { className: "absolute left-2.5 top-1/2 size-3.5 -translate-y-1/2 text-zinc-400" }),
2102
+ /* @__PURE__ */ jsx(
2103
+ Input,
2104
+ {
2105
+ value: search,
2106
+ onChange: (e) => onSearchChange(e.target.value),
2107
+ placeholder: "Search tables...",
2108
+ className: "h-8 pl-8 text-sm"
2109
+ }
2110
+ )
2111
+ ] }),
2112
+ viewCount > 0 && /* @__PURE__ */ jsxs(
2113
+ ToggleGroup,
2114
+ {
2115
+ type: "single",
2116
+ size: "sm",
2117
+ value: typeFilter,
2118
+ onValueChange: (v) => {
2119
+ if (v) onTypeFilterChange(v);
2120
+ },
2121
+ className: "justify-start",
2122
+ children: [
2123
+ /* @__PURE__ */ jsxs(ToggleGroupItem, { value: "all", className: "h-6 px-2 text-xs", children: [
2124
+ "All (",
2125
+ entities.length,
2126
+ ")"
2127
+ ] }),
2128
+ /* @__PURE__ */ jsxs(ToggleGroupItem, { value: "table", className: "h-6 px-2 text-xs", children: [
2129
+ "Tables (",
2130
+ tableCount,
2131
+ ")"
2132
+ ] }),
2133
+ /* @__PURE__ */ jsxs(ToggleGroupItem, { value: "view", className: "h-6 px-2 text-xs", children: [
2134
+ "Views (",
2135
+ viewCount,
2136
+ ")"
2137
+ ] })
2138
+ ]
2139
+ }
2140
+ )
2141
+ ] }),
2142
+ /* @__PURE__ */ jsx(Separator, {}),
2143
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-y-auto overflow-x-hidden p-2", children: filtered.length === 0 ? /* @__PURE__ */ jsx("p", { className: "px-2 py-8 text-center text-xs text-zinc-400 dark:text-zinc-500", children: search ? "No matching entities" : "No entities found" }) : filtered.map((entity) => /* @__PURE__ */ jsxs(
2144
+ "button",
2145
+ {
2146
+ onClick: () => onSelect(entity.table),
2147
+ className: "flex w-full min-w-0 items-start gap-2 overflow-hidden rounded-md px-2 py-2 text-left transition-colors hover:bg-zinc-100 dark:hover:bg-zinc-800",
2148
+ children: [
2149
+ entity.type === "view" ? /* @__PURE__ */ jsx(Eye, { className: "mt-0.5 size-3.5 shrink-0 text-zinc-400" }) : /* @__PURE__ */ jsx(TableProperties, { className: "mt-0.5 size-3.5 shrink-0 text-zinc-400" }),
2150
+ /* @__PURE__ */ jsxs("div", { className: "min-w-0 flex-1", children: [
2151
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2152
+ /* @__PURE__ */ jsx("span", { className: "truncate text-sm font-medium text-zinc-800 dark:text-zinc-200", children: entity.table }),
2153
+ entity.type === "view" && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "shrink-0 text-[10px] px-1 py-0", children: "view" })
2154
+ ] }),
2155
+ entity.description && /* @__PURE__ */ jsx("p", { className: "mt-0.5 truncate text-xs text-zinc-500 dark:text-zinc-400", children: entity.description }),
2156
+ /* @__PURE__ */ jsxs("div", { className: "mt-1 flex gap-2 text-[10px] text-zinc-400 dark:text-zinc-500", children: [
2157
+ /* @__PURE__ */ jsxs("span", { children: [
2158
+ entity.columnCount,
2159
+ " cols"
2160
+ ] }),
2161
+ entity.joinCount > 0 && /* @__PURE__ */ jsxs("span", { children: [
2162
+ entity.joinCount,
2163
+ " joins"
2164
+ ] })
2165
+ ] })
2166
+ ] }),
2167
+ /* @__PURE__ */ jsx(ArrowRight, { className: "mt-1 size-3 shrink-0 text-zinc-300 dark:text-zinc-600" })
2168
+ ]
2169
+ },
2170
+ entity.table
2171
+ )) })
2172
+ ] });
2173
+ }
2174
+ function EntityDetailView({
2175
+ entity,
2176
+ onBack,
2177
+ onNavigateEntity,
2178
+ onInsertQuery
2179
+ }) {
2180
+ const dimensions = normalizeList(entity.dimensions, "name");
2181
+ const joins = normalizeList(entity.joins, "to");
2182
+ const measures = normalizeList(entity.measures, "name");
2183
+ const patterns = normalizeList(entity.query_patterns, "name");
2184
+ return /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col", children: [
2185
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 px-4 pb-3", children: [
2186
+ /* @__PURE__ */ jsx(Button, { variant: "ghost", size: "icon", className: "size-7", onClick: onBack, children: /* @__PURE__ */ jsx(ArrowLeft, { className: "size-3.5" }) }),
2187
+ /* @__PURE__ */ jsx("div", { className: "min-w-0 flex-1", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1.5", children: [
2188
+ /* @__PURE__ */ jsx("h3", { className: "truncate text-sm font-semibold", children: entity.table }),
2189
+ entity.type === "view" && /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "text-[10px]", children: "view" })
2190
+ ] }) })
2191
+ ] }),
2192
+ /* @__PURE__ */ jsx(Separator, {}),
2193
+ /* @__PURE__ */ jsx(ScrollArea, { className: "flex-1", children: /* @__PURE__ */ jsxs("div", { className: "space-y-5 p-4", children: [
2194
+ entity.description && /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-500 dark:text-zinc-400", children: entity.description }),
2195
+ /* @__PURE__ */ jsxs("section", { children: [
2196
+ /* @__PURE__ */ jsxs("h4", { className: "mb-2 flex items-center gap-1.5 text-xs font-semibold text-zinc-700 dark:text-zinc-300", children: [
2197
+ /* @__PURE__ */ jsx(Columns3, { className: "size-3" }),
2198
+ "Columns (",
2199
+ dimensions.length,
2200
+ ")"
2201
+ ] }),
2202
+ /* @__PURE__ */ jsx("div", { className: "rounded-md border", children: /* @__PURE__ */ jsxs(Table, { className: "table-fixed", children: [
2203
+ /* @__PURE__ */ jsx(TableHeader, { children: /* @__PURE__ */ jsxs(TableRow, { children: [
2204
+ /* @__PURE__ */ jsx(TableHead, { className: "h-7 w-[30%] text-[10px]", children: "Name" }),
2205
+ /* @__PURE__ */ jsx(TableHead, { className: "h-7 w-[15%] text-[10px]", children: "Type" }),
2206
+ /* @__PURE__ */ jsx(TableHead, { className: "h-7 w-[35%] text-[10px] hidden sm:table-cell", children: "Description" }),
2207
+ /* @__PURE__ */ jsx(TableHead, { className: "h-7 w-[20%] text-[10px] hidden sm:table-cell", children: "Samples" })
2208
+ ] }) }),
2209
+ /* @__PURE__ */ jsx(TableBody, { children: dimensions.map((dim) => /* @__PURE__ */ jsxs(TableRow, { children: [
2210
+ /* @__PURE__ */ jsx(TableCell, { className: "py-1.5 font-mono text-[11px]", children: /* @__PURE__ */ jsxs("span", { className: "flex items-center gap-1", children: [
2211
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: dim.name }),
2212
+ dim.primary_key && /* @__PURE__ */ jsx(Badge, { className: "shrink-0 bg-amber-100 text-amber-800 dark:bg-amber-900/30 dark:text-amber-400 text-[9px] px-1 py-0", children: "PK" }),
2213
+ dim.foreign_key && /* @__PURE__ */ jsx(Badge, { className: "shrink-0 bg-blue-100 text-blue-800 dark:bg-blue-900/30 dark:text-blue-400 text-[9px] px-1 py-0", children: "FK" })
2214
+ ] }) }),
2215
+ /* @__PURE__ */ jsx(TableCell, { className: "py-1.5", children: /* @__PURE__ */ jsx(Badge, { variant: "secondary", className: "font-mono text-[9px]", children: dim.type }) }),
2216
+ /* @__PURE__ */ jsx(TableCell, { className: "hidden py-1.5 text-[11px] text-zinc-500 sm:table-cell", children: /* @__PURE__ */ jsx("span", { className: "line-clamp-2", children: dim.description || "\u2014" }) }),
2217
+ /* @__PURE__ */ jsx(TableCell, { className: "hidden py-1.5 text-[11px] text-zinc-400 sm:table-cell", children: /* @__PURE__ */ jsx("span", { className: "line-clamp-1", children: dim.sample_values?.length ? dim.sample_values.slice(0, 3).join(", ") : "\u2014" }) })
2218
+ ] }, dim.name)) })
2219
+ ] }) })
2220
+ ] }),
2221
+ joins.length > 0 && /* @__PURE__ */ jsxs("section", { children: [
2222
+ /* @__PURE__ */ jsxs("h4", { className: "mb-2 flex items-center gap-1.5 text-xs font-semibold text-zinc-700 dark:text-zinc-300", children: [
2223
+ /* @__PURE__ */ jsx(Link2, { className: "size-3" }),
2224
+ "Relationships (",
2225
+ joins.length,
2226
+ ")"
2227
+ ] }),
2228
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: joins.map((join, i) => /* @__PURE__ */ jsx(Card, { className: "shadow-none", children: /* @__PURE__ */ jsxs(CardContent, { className: "px-3 py-2", children: [
2229
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2230
+ /* @__PURE__ */ jsx(Badge, { variant: "outline", className: "shrink-0 text-[9px]", children: join.relationship || "many_to_one" }),
2231
+ /* @__PURE__ */ jsx(
2232
+ "button",
2233
+ {
2234
+ onClick: () => onNavigateEntity(join.to),
2235
+ className: "text-xs font-medium text-blue-600 hover:underline dark:text-blue-400",
2236
+ children: join.to
2237
+ }
2238
+ )
2239
+ ] }),
2240
+ join.description && /* @__PURE__ */ jsx("p", { className: "mt-1 text-[11px] text-zinc-500", children: join.description })
2241
+ ] }) }, i)) })
2242
+ ] }),
2243
+ measures.length > 0 && /* @__PURE__ */ jsxs("section", { children: [
2244
+ /* @__PURE__ */ jsxs("h4", { className: "mb-2 text-xs font-semibold text-zinc-700 dark:text-zinc-300", children: [
2245
+ "Measures (",
2246
+ measures.length,
2247
+ ")"
2248
+ ] }),
2249
+ /* @__PURE__ */ jsx("div", { className: "space-y-1", children: measures.map((m) => /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 rounded px-2 py-1 text-xs", children: [
2250
+ /* @__PURE__ */ jsx("span", { className: "font-medium text-zinc-700 dark:text-zinc-300", children: m.name }),
2251
+ /* @__PURE__ */ jsx("code", { className: "rounded bg-zinc-100 px-1.5 py-0.5 text-[10px] text-zinc-500 dark:bg-zinc-800", children: m.sql })
2252
+ ] }, m.name)) })
2253
+ ] }),
2254
+ patterns.length > 0 && /* @__PURE__ */ jsxs("section", { children: [
2255
+ /* @__PURE__ */ jsxs("h4", { className: "mb-2 flex items-center gap-1.5 text-xs font-semibold text-zinc-700 dark:text-zinc-300", children: [
2256
+ /* @__PURE__ */ jsx(Sparkles, { className: "size-3" }),
2257
+ "Query Patterns"
2258
+ ] }),
2259
+ /* @__PURE__ */ jsx("div", { className: "space-y-1.5", children: patterns.map((p, i) => /* @__PURE__ */ jsxs(
2260
+ "button",
2261
+ {
2262
+ onClick: () => onInsertQuery(p.description),
2263
+ className: "w-full rounded-md border border-zinc-200 bg-zinc-50 px-3 py-2 text-left transition-colors hover:border-zinc-400 hover:bg-zinc-100 dark:border-zinc-700 dark:bg-zinc-900 dark:hover:border-zinc-500 dark:hover:bg-zinc-800",
2264
+ children: [
2265
+ /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-zinc-700 dark:text-zinc-300", children: p.name }),
2266
+ /* @__PURE__ */ jsx("p", { className: "mt-0.5 text-[11px] text-zinc-500 dark:text-zinc-400", children: p.description })
2267
+ ]
2268
+ },
2269
+ `${p.name}-${i}`
2270
+ )) })
2271
+ ] })
2272
+ ] }) })
2273
+ ] });
2274
+ }
2275
+ function SchemaExplorer({
2276
+ open,
2277
+ onOpenChange,
2278
+ onInsertQuery,
2279
+ getHeaders,
2280
+ getCredentials
2281
+ }) {
2282
+ const { apiUrl } = useAtlasConfig();
2283
+ const [entities, setEntities] = useState([]);
2284
+ const [selectedEntity, setSelectedEntity] = useState(null);
2285
+ const [selectedName, setSelectedName] = useState(null);
2286
+ const [loading, setLoading] = useState(false);
2287
+ const [error, setError] = useState(null);
2288
+ const [detailError, setDetailError] = useState(null);
2289
+ const [search, setSearch] = useState("");
2290
+ const [typeFilter, setTypeFilter] = useState("all");
2291
+ const abortRef = useRef(null);
2292
+ useEffect(() => {
2293
+ if (!open) return;
2294
+ setSelectedName(null);
2295
+ setSelectedEntity(null);
2296
+ setDetailError(null);
2297
+ abortRef.current?.abort();
2298
+ setLoading(true);
2299
+ setError(null);
2300
+ const controller = new AbortController();
2301
+ fetch(`${apiUrl}/api/v1/semantic/entities`, {
2302
+ headers: getHeaders(),
2303
+ credentials: getCredentials(),
2304
+ signal: controller.signal
2305
+ }).then(async (r) => {
2306
+ if (!r.ok) throw new Error(await parseErrorResponse(r));
2307
+ return r.json();
2308
+ }).then((data) => {
2309
+ const list = Array.isArray(data?.entities) ? data.entities : [];
2310
+ setEntities(list);
2311
+ }).catch((err) => {
2312
+ if (err instanceof DOMException && err.name === "AbortError") return;
2313
+ console.warn("Schema explorer: failed to fetch entities:", err);
2314
+ setError(err instanceof Error ? err.message : "Failed to load schema");
2315
+ }).finally(() => setLoading(false));
2316
+ return () => controller.abort();
2317
+ }, [open, apiUrl, getHeaders, getCredentials]);
2318
+ function handleSelectEntity(name) {
2319
+ abortRef.current?.abort();
2320
+ const controller = new AbortController();
2321
+ abortRef.current = controller;
2322
+ setSelectedName(name);
2323
+ setSelectedEntity(null);
2324
+ setDetailError(null);
2325
+ fetch(`${apiUrl}/api/v1/semantic/entities/${encodeURIComponent(name)}`, {
2326
+ headers: getHeaders(),
2327
+ credentials: getCredentials(),
2328
+ signal: controller.signal
2329
+ }).then(async (r) => {
2330
+ if (!r.ok) throw new Error(await parseErrorResponse(r));
2331
+ return r.json();
2332
+ }).then((data) => {
2333
+ setSelectedEntity(data?.entity ?? data);
2334
+ }).catch((err) => {
2335
+ if (err instanceof DOMException && err.name === "AbortError") return;
2336
+ console.warn("Schema explorer: failed to load entity:", err);
2337
+ setDetailError(err instanceof Error ? err.message : "Failed to load entity");
2338
+ });
2339
+ }
2340
+ function handleBack() {
2341
+ abortRef.current?.abort();
2342
+ setSelectedName(null);
2343
+ setSelectedEntity(null);
2344
+ setDetailError(null);
2345
+ }
2346
+ function handleInsertQuery(description) {
2347
+ onInsertQuery(description);
2348
+ onOpenChange(false);
2349
+ }
2350
+ return /* @__PURE__ */ jsx(Sheet, { open, onOpenChange, children: /* @__PURE__ */ jsxs(SheetContent, { side: "right", className: "flex w-full flex-col p-0 sm:max-w-xl", children: [
2351
+ /* @__PURE__ */ jsx(SheetHeader, { className: "px-4 pt-4", children: /* @__PURE__ */ jsxs(SheetTitle, { className: "flex items-center gap-2 text-base", children: [
2352
+ /* @__PURE__ */ jsx(TableProperties, { className: "size-4" }),
2353
+ "Schema Explorer"
2354
+ ] }) }),
2355
+ /* @__PURE__ */ jsx(Separator, { className: "mt-3" }),
2356
+ /* @__PURE__ */ jsx("div", { className: "flex-1 overflow-hidden pt-3", children: loading ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsx("p", { className: "text-xs text-zinc-400", children: "Loading schema..." }) }) : error ? /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center px-4", children: /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-red-500", children: error }) }) : selectedName ? detailError ? /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-2 px-4", children: [
2357
+ /* @__PURE__ */ jsx("p", { className: "text-center text-xs text-red-500", children: detailError }),
2358
+ /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "sm", onClick: handleBack, className: "text-xs", children: [
2359
+ /* @__PURE__ */ jsx(ArrowLeft, { className: "mr-1 size-3" }),
2360
+ " Back to list"
2361
+ ] })
2362
+ ] }) : selectedEntity ? /* @__PURE__ */ jsx(
2363
+ EntityDetailView,
2364
+ {
2365
+ entity: selectedEntity,
2366
+ onBack: handleBack,
2367
+ onNavigateEntity: handleSelectEntity,
2368
+ onInsertQuery: handleInsertQuery
2369
+ }
2370
+ ) : /* @__PURE__ */ jsx("div", { className: "flex h-full items-center justify-center", children: /* @__PURE__ */ jsxs("p", { className: "text-xs text-zinc-400", children: [
2371
+ "Loading ",
2372
+ selectedName,
2373
+ "..."
2374
+ ] }) }) : /* @__PURE__ */ jsx(
2375
+ EntityList,
2376
+ {
2377
+ entities,
2378
+ search,
2379
+ onSearchChange: setSearch,
2380
+ typeFilter,
2381
+ onTypeFilterChange: setTypeFilter,
2382
+ onSelect: handleSelectEntity
2383
+ }
2384
+ ) })
2385
+ ] }) });
2386
+ }
2387
+ function DropdownMenu({
2388
+ ...props
2389
+ }) {
2390
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Root, { "data-slot": "dropdown-menu", ...props });
2391
+ }
2392
+ function DropdownMenuTrigger({
2393
+ ...props
2394
+ }) {
2395
+ return /* @__PURE__ */ jsx(
2396
+ DropdownMenu$1.Trigger,
2397
+ {
2398
+ "data-slot": "dropdown-menu-trigger",
2399
+ ...props
2400
+ }
2401
+ );
2402
+ }
2403
+ function DropdownMenuContent({
2404
+ className,
2405
+ sideOffset = 4,
2406
+ ...props
2407
+ }) {
2408
+ return /* @__PURE__ */ jsx(DropdownMenu$1.Portal, { children: /* @__PURE__ */ jsx(
2409
+ DropdownMenu$1.Content,
2410
+ {
2411
+ "data-slot": "dropdown-menu-content",
2412
+ sideOffset,
2413
+ className: cn(
2414
+ "z-50 max-h-(--radix-dropdown-menu-content-available-height) min-w-[8rem] origin-(--radix-dropdown-menu-content-transform-origin) overflow-x-hidden overflow-y-auto rounded-md border bg-popover p-1 text-popover-foreground shadow-md data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2 data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=closed]:zoom-out-95 data-[state=open]:animate-in data-[state=open]:fade-in-0 data-[state=open]:zoom-in-95",
2415
+ className
2416
+ ),
2417
+ ...props
2418
+ }
2419
+ ) });
2420
+ }
2421
+ function DropdownMenuItem({
2422
+ className,
2423
+ inset,
2424
+ variant = "default",
2425
+ ...props
2426
+ }) {
2427
+ return /* @__PURE__ */ jsx(
2428
+ DropdownMenu$1.Item,
2429
+ {
2430
+ "data-slot": "dropdown-menu-item",
2431
+ "data-inset": inset,
2432
+ "data-variant": variant,
2433
+ className: cn(
2434
+ "relative flex cursor-default items-center gap-2 rounded-sm px-2 py-1.5 text-sm outline-hidden select-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50 data-[inset]:pl-8 data-[variant=destructive]:text-destructive data-[variant=destructive]:focus:bg-destructive/10 data-[variant=destructive]:focus:text-destructive dark:data-[variant=destructive]:focus:bg-destructive/20 [&_svg]:pointer-events-none [&_svg]:shrink-0 [&_svg:not([class*='size-'])]:size-4 [&_svg:not([class*='text-'])]:text-muted-foreground data-[variant=destructive]:*:[svg]:text-destructive!",
2435
+ className
2436
+ ),
2437
+ ...props
2438
+ }
2439
+ );
2440
+ }
2441
+ var API_KEY_STORAGE_KEY = "atlas-api-key";
2442
+ var noopAuthClient = {
2443
+ signIn: { email: async () => ({ error: { message: "Not supported" } }) },
2444
+ signUp: { email: async () => ({ error: { message: "Not supported" } }) },
2445
+ signOut: async () => {
2446
+ },
2447
+ useSession: () => ({ data: null, isPending: false })
2448
+ };
2449
+ var MenuIcon = /* @__PURE__ */ jsx("svg", { xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 20 20", fill: "currentColor", className: "h-5 w-5", children: /* @__PURE__ */ jsx("path", { fillRule: "evenodd", d: "M2 4.75A.75.75 0 0 1 2.75 4h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 4.75ZM2 10a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75A.75.75 0 0 1 2 10Zm0 5.25a.75.75 0 0 1 .75-.75h14.5a.75.75 0 0 1 0 1.5H2.75a.75.75 0 0 1-.75-.75Z", clipRule: "evenodd" }) });
2450
+ var AtlasLogo = /* @__PURE__ */ jsxs("svg", { "data-atlas-logo": true, viewBox: "0 0 256 256", fill: "none", className: "h-7 w-7 shrink-0 text-primary", "aria-hidden": "true", children: [
2451
+ /* @__PURE__ */ jsx("path", { d: "M128 24 L232 208 L24 208 Z", stroke: "currentColor", strokeWidth: "14", fill: "none", strokeLinejoin: "round" }),
2452
+ /* @__PURE__ */ jsx("circle", { cx: "128", cy: "28", r: "16", fill: "currentColor" })
2453
+ ] });
2454
+ var THEME_OPTIONS = [
2455
+ { value: "light", label: "Light", icon: Sun },
2456
+ { value: "dark", label: "Dark", icon: Moon },
2457
+ { value: "system", label: "System", icon: Monitor }
2458
+ ];
2459
+ function ThemeToggle() {
2460
+ const mode = useThemeMode();
2461
+ const CurrentIcon = mode === "dark" ? Moon : mode === "light" ? Sun : Monitor;
2462
+ return /* @__PURE__ */ jsxs(DropdownMenu, { children: [
2463
+ /* @__PURE__ */ jsx(DropdownMenuTrigger, { asChild: true, children: /* @__PURE__ */ jsxs(Button, { variant: "ghost", size: "icon", className: "size-11 sm:size-8 text-zinc-500 dark:text-zinc-400", children: [
2464
+ /* @__PURE__ */ jsx(CurrentIcon, { className: "size-4" }),
2465
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Toggle theme" })
2466
+ ] }) }),
2467
+ /* @__PURE__ */ jsx(DropdownMenuContent, { align: "end", children: THEME_OPTIONS.map(({ value, label, icon: Icon }) => /* @__PURE__ */ jsxs(
2468
+ DropdownMenuItem,
2469
+ {
2470
+ onClick: () => setTheme(value),
2471
+ className: mode === value ? "bg-accent" : "",
2472
+ children: [
2473
+ /* @__PURE__ */ jsx(Icon, { className: "mr-2 size-4" }),
2474
+ label
2475
+ ]
2476
+ },
2477
+ value
2478
+ )) })
2479
+ ] });
2480
+ }
2481
+ function SaveButton({
2482
+ conversationId,
2483
+ conversations,
2484
+ onStar
2485
+ }) {
2486
+ const isStarred = conversations.find((c) => c.id === conversationId)?.starred ?? false;
2487
+ const [pending, setPending] = useState(false);
2488
+ async function handleToggle() {
2489
+ setPending(true);
2490
+ try {
2491
+ await onStar(conversationId, !isStarred);
2492
+ } catch (err) {
2493
+ console.warn("Failed to update star:", err);
2494
+ } finally {
2495
+ setPending(false);
2496
+ }
2497
+ }
2498
+ return /* @__PURE__ */ jsxs(
2499
+ Button,
2500
+ {
2501
+ variant: "ghost",
2502
+ size: "xs",
2503
+ onClick: handleToggle,
2504
+ disabled: pending,
2505
+ className: isStarred ? "text-amber-500 hover:text-amber-600 dark:text-amber-400 dark:hover:text-amber-300" : "text-zinc-400 hover:text-amber-500 dark:text-zinc-500 dark:hover:text-amber-400",
2506
+ "aria-label": isStarred ? "Unsave conversation" : "Save conversation",
2507
+ children: [
2508
+ /* @__PURE__ */ jsx(Star, { className: "h-3.5 w-3.5", fill: isStarred ? "currentColor" : "none" }),
2509
+ /* @__PURE__ */ jsx("span", { children: isStarred ? "Saved" : "Save" })
2510
+ ]
2511
+ }
2512
+ );
2513
+ }
2514
+ function AtlasChat(props) {
2515
+ const {
2516
+ apiUrl,
2517
+ apiKey: propApiKey,
2518
+ theme: propTheme = "system",
2519
+ sidebar = false,
2520
+ schemaExplorer: schemaExplorerEnabled = false,
2521
+ authClient = noopAuthClient,
2522
+ toolRenderers
2523
+ } = props;
2524
+ useEffect(() => {
2525
+ setTheme(propTheme);
2526
+ }, [propTheme]);
2527
+ return /* @__PURE__ */ jsx(AtlasUIProvider, { config: { apiUrl, authClient }, children: /* @__PURE__ */ jsx(
2528
+ AtlasChatInner,
2529
+ {
2530
+ propApiKey,
2531
+ sidebar,
2532
+ schemaExplorerEnabled,
2533
+ toolRenderers
2534
+ }
2535
+ ) });
2536
+ }
2537
+ function AtlasChatInner({
2538
+ propApiKey,
2539
+ sidebar,
2540
+ schemaExplorerEnabled,
2541
+ toolRenderers
2542
+ }) {
2543
+ const { apiUrl, isCrossOrigin, authClient } = useAtlasConfig();
2544
+ const dark = useDarkMode();
2545
+ const [input, setInput] = useState("");
2546
+ const [authMode, setAuthMode] = useState(null);
2547
+ const [healthWarning, setHealthWarning] = useState("");
2548
+ const [healthFailed, setHealthFailed] = useState(false);
2549
+ const [apiKey, setApiKey] = useState(propApiKey ?? "");
2550
+ const [conversationId, setConversationId] = useState(null);
2551
+ const [mobileMenuOpen, setMobileMenuOpen] = useState(false);
2552
+ const [loadingConversation, setLoadingConversation] = useState(false);
2553
+ const [passwordChangeRequired, setPasswordChangeRequired] = useState(false);
2554
+ const [schemaExplorerOpen, setSchemaExplorerOpen] = useState(false);
2555
+ const scrollRef = useRef(null);
2556
+ useEffect(() => {
2557
+ if (propApiKey !== void 0) setApiKey(propApiKey);
2558
+ }, [propApiKey]);
2559
+ const managedSession = authClient.useSession();
2560
+ const authResolved = authMode !== null;
2561
+ const isManaged = authMode === "managed";
2562
+ const isSignedIn = isManaged && !!managedSession.data?.user;
2563
+ const getHeaders = useCallback(() => {
2564
+ const headers = {};
2565
+ if (apiKey) headers["Authorization"] = `Bearer ${apiKey}`;
2566
+ return headers;
2567
+ }, [apiKey]);
2568
+ const getCredentials = useCallback(() => {
2569
+ return isCrossOrigin ? "include" : "same-origin";
2570
+ }, [isCrossOrigin]);
2571
+ const convos = useConversations({
2572
+ apiUrl,
2573
+ enabled: sidebar,
2574
+ getHeaders,
2575
+ getCredentials
2576
+ });
2577
+ const refreshConvosRef = useRef(convos.refresh);
2578
+ refreshConvosRef.current = convos.refresh;
2579
+ const conversationIdRef = useRef(conversationId);
2580
+ conversationIdRef.current = conversationId;
2581
+ useEffect(() => {
2582
+ if (!propApiKey) {
2583
+ try {
2584
+ const stored = sessionStorage.getItem(API_KEY_STORAGE_KEY);
2585
+ if (stored) setApiKey(stored);
2586
+ } catch (err) {
2587
+ console.warn("Cannot read API key from sessionStorage:", err);
2588
+ }
2589
+ }
2590
+ async function fetchHealth(attempt) {
2591
+ try {
2592
+ const res = await fetch(`${apiUrl}/api/health`, {
2593
+ credentials: isCrossOrigin ? "include" : "same-origin"
2594
+ });
2595
+ if (!res.ok) {
2596
+ console.warn(`Health check returned ${res.status}`);
2597
+ if (attempt < 2) {
2598
+ await new Promise((r) => setTimeout(r, 2e3));
2599
+ return fetchHealth(attempt + 1);
2600
+ }
2601
+ setHealthWarning("Health check failed \u2014 check server logs. Try refreshing the page.");
2602
+ setHealthFailed(true);
2603
+ setAuthMode("none");
2604
+ return;
2605
+ }
2606
+ const data = await res.json();
2607
+ const mode = data?.checks?.auth?.mode;
2608
+ if (typeof mode === "string" && AUTH_MODES.includes(mode)) {
2609
+ setAuthMode(mode);
2610
+ } else {
2611
+ console.warn("Health check succeeded but returned no valid auth mode:", data);
2612
+ setHealthWarning("Could not determine authentication mode from the server.");
2613
+ setAuthMode("none");
2614
+ }
2615
+ if (typeof data?.brandColor === "string" && OKLCH_RE.test(data.brandColor)) {
2616
+ applyBrandColor(data.brandColor);
2617
+ }
2618
+ } catch (err) {
2619
+ console.warn("Health endpoint unavailable:", err);
2620
+ if (attempt < 2) {
2621
+ await new Promise((r) => setTimeout(r, 2e3));
2622
+ return fetchHealth(attempt + 1);
2623
+ }
2624
+ setHealthWarning("Unable to reach the API server. Try refreshing the page.");
2625
+ setHealthFailed(true);
2626
+ setAuthMode("none");
2627
+ }
2628
+ }
2629
+ fetchHealth(1);
2630
+ }, [apiUrl, isCrossOrigin]);
2631
+ useEffect(() => {
2632
+ if (sidebar) convos.fetchList();
2633
+ }, [authMode, sidebar, convos.fetchList]);
2634
+ useEffect(() => {
2635
+ if (!isManaged || !managedSession.data?.user) return;
2636
+ async function checkPasswordStatus() {
2637
+ try {
2638
+ const res = await fetch(`${apiUrl}/api/v1/admin/me/password-status`, {
2639
+ credentials: isCrossOrigin ? "include" : "same-origin"
2640
+ });
2641
+ if (!res.ok) {
2642
+ console.warn(`Password status check returned HTTP ${res.status}`);
2643
+ return;
2644
+ }
2645
+ const data = await res.json();
2646
+ if (data.passwordChangeRequired) setPasswordChangeRequired(true);
2647
+ } catch (err) {
2648
+ console.warn("Failed to check password status:", err);
2649
+ }
2650
+ }
2651
+ checkPasswordStatus();
2652
+ }, [isManaged, managedSession.data?.user, apiUrl, isCrossOrigin]);
2653
+ const handleSaveApiKey = useCallback((key) => {
2654
+ setApiKey(key);
2655
+ try {
2656
+ sessionStorage.setItem(API_KEY_STORAGE_KEY, key);
2657
+ } catch (err) {
2658
+ console.warn("Could not persist API key to sessionStorage:", err);
2659
+ }
2660
+ }, []);
2661
+ const transport = useMemo(() => {
2662
+ const headers = {};
2663
+ if (apiKey) {
2664
+ headers["Authorization"] = `Bearer ${apiKey}`;
2665
+ }
2666
+ return new DefaultChatTransport({
2667
+ api: `${apiUrl}/api/chat`,
2668
+ headers,
2669
+ credentials: isCrossOrigin ? "include" : void 0,
2670
+ body: () => conversationIdRef.current ? { conversationId: conversationIdRef.current } : {},
2671
+ fetch: (async (input2, init) => {
2672
+ const response = await globalThis.fetch(input2, init);
2673
+ const convId = response.headers.get("x-conversation-id");
2674
+ if (convId && convId !== conversationIdRef.current) {
2675
+ setConversationId(convId);
2676
+ setTimeout(() => {
2677
+ refreshConvosRef.current().catch((err) => {
2678
+ console.warn("Sidebar refresh failed:", err);
2679
+ });
2680
+ }, 500);
2681
+ }
2682
+ return response;
2683
+ })
2684
+ });
2685
+ }, [apiKey, apiUrl, isCrossOrigin]);
2686
+ const { messages, setMessages, sendMessage, status, error } = useChat({ transport });
2687
+ const isLoading = status === "streaming" || status === "submitted";
2688
+ useEffect(() => {
2689
+ const el = scrollRef.current;
2690
+ if (!el) return;
2691
+ const isNearBottom = el.scrollHeight - el.scrollTop - el.clientHeight < 100;
2692
+ if (isNearBottom) el.scrollTop = el.scrollHeight;
2693
+ }, [messages, status]);
2694
+ function handleSend(text) {
2695
+ if (!text.trim()) return;
2696
+ const saved = text;
2697
+ setInput("");
2698
+ sendMessage({ text: saved }).catch((err) => {
2699
+ console.error("Failed to send message:", err);
2700
+ setInput(saved);
2701
+ setHealthWarning("Failed to send message. Please try again.");
2702
+ setTimeout(() => setHealthWarning(""), 5e3);
2703
+ });
2704
+ }
2705
+ async function handleSelectConversation(id) {
2706
+ if (loadingConversation) return;
2707
+ setLoadingConversation(true);
2708
+ try {
2709
+ const uiMessages = await convos.loadConversation(id);
2710
+ if (uiMessages) {
2711
+ setMessages(uiMessages);
2712
+ setConversationId(id);
2713
+ convos.setSelectedId(id);
2714
+ setMobileMenuOpen(false);
2715
+ } else {
2716
+ setHealthWarning("Could not load conversation. It may have been deleted.");
2717
+ setTimeout(() => setHealthWarning(""), 5e3);
2718
+ }
2719
+ } catch (err) {
2720
+ console.warn("Failed to load conversation:", err);
2721
+ setHealthWarning("Failed to load conversation. Please try again.");
2722
+ setTimeout(() => setHealthWarning(""), 5e3);
2723
+ } finally {
2724
+ setLoadingConversation(false);
2725
+ }
2726
+ }
2727
+ function handleNewChat() {
2728
+ setMessages([]);
2729
+ setConversationId(null);
2730
+ convos.setSelectedId(null);
2731
+ setInput("");
2732
+ setMobileMenuOpen(false);
2733
+ }
2734
+ if (!authResolved || isManaged && managedSession.isPending) {
2735
+ return /* @__PURE__ */ jsx(DarkModeContext.Provider, { value: dark, children: /* @__PURE__ */ jsx("div", { className: "atlas-root flex h-dvh items-center justify-center bg-white dark:bg-zinc-950" }) });
2736
+ }
2737
+ const showSidebar = sidebar && convos.available;
2738
+ return /* @__PURE__ */ jsxs(DarkModeContext.Provider, { value: dark, children: [
2739
+ /* @__PURE__ */ jsxs("div", { className: "atlas-root flex h-dvh", children: [
2740
+ showSidebar && /* @__PURE__ */ jsx(
2741
+ ConversationSidebar,
2742
+ {
2743
+ conversations: convos.conversations,
2744
+ selectedId: convos.selectedId,
2745
+ loading: convos.loading,
2746
+ onSelect: handleSelectConversation,
2747
+ onDelete: (id) => convos.deleteConversation(id),
2748
+ onStar: (id, starred) => convos.starConversation(id, starred),
2749
+ onNewChat: handleNewChat,
2750
+ mobileOpen: mobileMenuOpen,
2751
+ onMobileClose: () => setMobileMenuOpen(false)
2752
+ }
2753
+ ),
2754
+ /* @__PURE__ */ jsx("main", { className: "flex flex-1 flex-col overflow-hidden", children: /* @__PURE__ */ jsxs("div", { className: "mx-auto flex w-full max-w-4xl flex-1 flex-col overflow-hidden p-4", children: [
2755
+ /* @__PURE__ */ jsx("header", { className: "mb-4 flex-none border-b border-zinc-100 pb-3 dark:border-zinc-800", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2756
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-3", children: [
2757
+ showSidebar && /* @__PURE__ */ jsx(
2758
+ "button",
2759
+ {
2760
+ onClick: () => setMobileMenuOpen(true),
2761
+ className: "flex size-11 items-center justify-center rounded text-zinc-400 hover:text-zinc-700 md:hidden dark:hover:text-zinc-200",
2762
+ "aria-label": "Open conversation history",
2763
+ children: MenuIcon
2764
+ }
2765
+ ),
2766
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2.5", children: [
2767
+ AtlasLogo,
2768
+ /* @__PURE__ */ jsxs("div", { children: [
2769
+ /* @__PURE__ */ jsx("h1", { className: "text-xl font-semibold tracking-tight", children: "Atlas" }),
2770
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-zinc-500", children: "Ask your data anything" })
2771
+ ] })
2772
+ ] })
2773
+ ] }),
2774
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2", children: [
2775
+ schemaExplorerEnabled && /* @__PURE__ */ jsx(
2776
+ Button,
2777
+ {
2778
+ variant: "ghost",
2779
+ size: "icon",
2780
+ className: "size-11 sm:size-8 text-zinc-500 dark:text-zinc-400",
2781
+ onClick: () => setSchemaExplorerOpen(true),
2782
+ "aria-label": "Open schema explorer",
2783
+ children: /* @__PURE__ */ jsx(TableProperties, { className: "size-4" })
2784
+ }
2785
+ ),
2786
+ /* @__PURE__ */ jsx(ThemeToggle, {}),
2787
+ isSignedIn && /* @__PURE__ */ jsxs(Fragment, { children: [
2788
+ /* @__PURE__ */ jsx("span", { className: "hidden text-xs text-zinc-500 sm:inline dark:text-zinc-400", children: managedSession.data?.user?.email }),
2789
+ /* @__PURE__ */ jsx(
2790
+ "button",
2791
+ {
2792
+ onClick: () => {
2793
+ authClient.signOut().catch((err) => {
2794
+ console.error("Sign out failed:", err);
2795
+ setHealthWarning("Sign out failed. Please try again.");
2796
+ setTimeout(() => setHealthWarning(""), 5e3);
2797
+ });
2798
+ },
2799
+ className: "rounded border border-zinc-200 px-3 py-2 text-xs text-zinc-500 transition-colors hover:border-zinc-400 hover:text-zinc-800 dark:border-zinc-700 dark:text-zinc-400 dark:hover:border-zinc-500 dark:hover:text-zinc-200",
2800
+ children: "Sign out"
2801
+ }
2802
+ )
2803
+ ] })
2804
+ ] })
2805
+ ] }) }),
2806
+ healthWarning && /* @__PURE__ */ jsx("p", { className: "mb-2 text-xs text-zinc-400 dark:text-zinc-500", children: healthWarning }),
2807
+ isManaged && !isSignedIn ? /* @__PURE__ */ jsx(ManagedAuthCard, {}) : /* @__PURE__ */ jsxs(ActionAuthProvider, { getHeaders, getCredentials, children: [
2808
+ authMode === "simple-key" && !propApiKey && /* @__PURE__ */ jsx("div", { className: "mb-3 flex-none", children: /* @__PURE__ */ jsx(ApiKeyBar, { apiKey, onSave: handleSaveApiKey }) }),
2809
+ /* @__PURE__ */ jsx(ScrollArea, { viewportRef: scrollRef, className: "min-h-0 flex-1", children: /* @__PURE__ */ jsxs("div", { "data-atlas-messages": true, className: "space-y-4 pb-4 pr-3", children: [
2810
+ messages.length === 0 && !error && /* @__PURE__ */ jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-6", children: [
2811
+ /* @__PURE__ */ jsxs("div", { className: "text-center", children: [
2812
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-medium text-zinc-500 dark:text-zinc-400", children: "What would you like to know?" }),
2813
+ /* @__PURE__ */ jsx("p", { className: "mt-1 text-sm text-zinc-400 dark:text-zinc-600", children: "Ask a question about your data to get started" })
2814
+ ] }),
2815
+ /* @__PURE__ */ jsx("div", { className: "grid w-full max-w-lg grid-cols-1 gap-2 sm:grid-cols-2", children: STARTER_PROMPTS.map((prompt) => /* @__PURE__ */ jsx(
2816
+ "button",
2817
+ {
2818
+ onClick: () => handleSend(prompt),
2819
+ className: "rounded-lg border border-zinc-200 bg-zinc-50 px-3 py-2.5 text-left text-sm text-zinc-500 transition-colors hover:border-zinc-400 hover:bg-zinc-100 hover:text-zinc-800 dark:border-zinc-800 dark:bg-zinc-900 dark:text-zinc-400 dark:hover:border-zinc-600 dark:hover:bg-zinc-800 dark:hover:text-zinc-200",
2820
+ children: prompt
2821
+ },
2822
+ prompt
2823
+ )) })
2824
+ ] }),
2825
+ messages.map((m, msgIndex) => {
2826
+ if (m.role === "user") {
2827
+ return /* @__PURE__ */ jsx("div", { className: "flex justify-end", children: /* @__PURE__ */ jsx("div", { className: "max-w-[85%] rounded-xl bg-blue-600 px-4 py-3 text-sm text-white", children: m.parts?.map(
2828
+ (part, i) => part.type === "text" ? /* @__PURE__ */ jsx("p", { className: "whitespace-pre-wrap", children: part.text }, i) : null
2829
+ ) }) }, m.id);
2830
+ }
2831
+ const isLastAssistant = m.role === "assistant" && msgIndex === messages.length - 1;
2832
+ const lastTextWithSuggestions = m.parts?.filter((p) => p.type === "text" && !!p.text.trim()).findLast((p) => parseSuggestions(p.text).suggestions.length > 0);
2833
+ const suggestions = lastTextWithSuggestions ? parseSuggestions(lastTextWithSuggestions.text).suggestions : [];
2834
+ return /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
2835
+ m.parts?.map((part, i) => {
2836
+ if (part.type === "text" && part.text.trim()) {
2837
+ const displayText = parseSuggestions(part.text).text;
2838
+ if (!displayText.trim()) return null;
2839
+ return /* @__PURE__ */ jsx("div", { className: "max-w-[90%]", children: /* @__PURE__ */ jsx("div", { className: "rounded-xl bg-zinc-100 px-4 py-3 text-sm text-zinc-800 dark:bg-zinc-800 dark:text-zinc-200", children: /* @__PURE__ */ jsx(Markdown, { content: displayText }) }) }, i);
2840
+ }
2841
+ if (isToolUIPart(part)) {
2842
+ return /* @__PURE__ */ jsx("div", { className: "max-w-[95%]", children: /* @__PURE__ */ jsx(ToolPart, { part, toolRenderers }) }, i);
2843
+ }
2844
+ return null;
2845
+ }),
2846
+ isLastAssistant && !isLoading && /* @__PURE__ */ jsxs(Fragment, { children: [
2847
+ /* @__PURE__ */ jsx(
2848
+ FollowUpChips,
2849
+ {
2850
+ suggestions,
2851
+ onSelect: handleSend
2852
+ }
2853
+ ),
2854
+ conversationId && sidebar && convos.available && /* @__PURE__ */ jsx(
2855
+ SaveButton,
2856
+ {
2857
+ conversationId,
2858
+ conversations: convos.conversations,
2859
+ onStar: convos.starConversation
2860
+ }
2861
+ )
2862
+ ] })
2863
+ ] }, m.id);
2864
+ }),
2865
+ isLoading && messages.length > 0 && /* @__PURE__ */ jsx(TypingIndicator, {})
2866
+ ] }) }),
2867
+ error && /* @__PURE__ */ jsx(ErrorBanner, { error, authMode }),
2868
+ /* @__PURE__ */ jsxs(
2869
+ "form",
2870
+ {
2871
+ "data-atlas-form": true,
2872
+ onSubmit: (e) => {
2873
+ e.preventDefault();
2874
+ handleSend(input);
2875
+ },
2876
+ className: "flex flex-none gap-2 border-t border-zinc-100 pt-4 dark:border-zinc-800",
2877
+ children: [
2878
+ /* @__PURE__ */ jsx(
2879
+ "input",
2880
+ {
2881
+ "data-atlas-input": true,
2882
+ value: input,
2883
+ onChange: (e) => setInput(e.target.value),
2884
+ placeholder: "Ask a question about your data...",
2885
+ className: "min-w-0 flex-1 rounded-lg border border-zinc-200 bg-zinc-50 px-4 py-3 text-base text-zinc-900 placeholder-zinc-400 outline-none focus:border-blue-500 sm:text-sm dark:border-zinc-700 dark:bg-zinc-900 dark:text-zinc-100 dark:placeholder-zinc-600",
2886
+ disabled: isLoading || healthFailed
2887
+ }
2888
+ ),
2889
+ /* @__PURE__ */ jsx(
2890
+ "button",
2891
+ {
2892
+ type: "submit",
2893
+ disabled: isLoading || healthFailed || !input.trim(),
2894
+ className: "shrink-0 rounded-lg bg-blue-600 px-5 py-3 text-sm font-medium text-white transition-colors hover:bg-blue-500 disabled:opacity-40",
2895
+ children: "Ask"
2896
+ }
2897
+ )
2898
+ ]
2899
+ }
2900
+ )
2901
+ ] })
2902
+ ] }) })
2903
+ ] }),
2904
+ schemaExplorerEnabled && /* @__PURE__ */ jsx(
2905
+ SchemaExplorer,
2906
+ {
2907
+ open: schemaExplorerOpen,
2908
+ onOpenChange: setSchemaExplorerOpen,
2909
+ onInsertQuery: (text) => setInput(text),
2910
+ getHeaders,
2911
+ getCredentials
2912
+ }
2913
+ ),
2914
+ /* @__PURE__ */ jsx(
2915
+ ChangePasswordDialog,
2916
+ {
2917
+ open: passwordChangeRequired,
2918
+ onComplete: () => setPasswordChangeRequired(false)
2919
+ }
2920
+ )
2921
+ ] });
2922
+ }
2923
+
2924
+ export { AtlasChat, AtlasUIProvider, useAtlasConfig };
2925
+ //# sourceMappingURL=index.js.map
2926
+ //# sourceMappingURL=index.js.map