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