@iota-uz/sdk 0.1.1 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,13 +1,14 @@
1
+ import React, { createContext, memo, lazy, forwardRef, useState, useRef, useImperativeHandle, useEffect, isValidElement, cloneElement, useMemo, useContext, useCallback, useId, Suspense, Component, Children } from 'react';
2
+ import { X, Paperclip, PaperPlaneRight, CircleNotch, MagnifyingGlass, FileXls, CaretLeft, CaretRight, Copy, PencilSimple, Download, Question, ArrowsClockwise, ArrowDown, ChartBar, FileText, Lightbulb, WarningCircle, ArrowClockwise, Check, Warning, Info, XCircle, CheckCircle } from '@phosphor-icons/react';
3
+ import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
4
+ import { Prism } from 'react-syntax-highlighter';
5
+ import { vscDarkPlus, vs } from 'react-syntax-highlighter/dist/esm/styles/prism';
1
6
  import ReactMarkdown from 'react-markdown';
2
7
  import remarkGfm from 'remark-gfm';
3
- import rehypeSanitize from 'rehype-sanitize';
4
- import { Prism } from 'react-syntax-highlighter';
5
- import { tomorrow } from 'react-syntax-highlighter/dist/esm/styles/prism';
6
- import { jsx, jsxs, Fragment } from 'react/jsx-runtime';
7
- import { createContext, lazy, forwardRef, useState, useRef, useImperativeHandle, useEffect, memo, useContext, useCallback, Suspense, useMemo } from 'react';
8
- import { X, Paperclip, PaperPlaneRight, CircleNotch, MagnifyingGlass, CaretLeft, CaretRight, Copy, PencilSimple, ArrowsClockwise, ArrowDown, ChartBar, FileText, Lightbulb } from '@phosphor-icons/react';
9
8
  import { formatDistanceToNow } from 'date-fns';
10
- import { ResponsiveContainer, AreaChart, CartesianGrid, XAxis, YAxis, Tooltip, Legend, Area, PieChart, Pie, Cell, LineChart, Line, BarChart, Bar } from 'recharts';
9
+ import { createPortal } from 'react-dom';
10
+ import ReactApexChart from 'react-apexcharts';
11
+ import ApexCharts from 'apexcharts';
11
12
  import { motion, AnimatePresence } from 'framer-motion';
12
13
 
13
14
  var __defProp = Object.defineProperty;
@@ -20,50 +21,333 @@ var __export = (target, all) => {
20
21
  __defProp(target, name, { get: all[name], enumerable: true });
21
22
  };
22
23
 
24
+ // ui/src/bichat/utils/citationProcessor.ts
25
+ function processCitations(content, citations) {
26
+ if (!citations || citations.length === 0) {
27
+ return { content, citations: [] };
28
+ }
29
+ const sortedCitations = [...citations].sort((a, b) => b.startIndex - a.startIndex);
30
+ let processedContent = content;
31
+ const processedCitations = [];
32
+ sortedCitations.forEach((citation, index) => {
33
+ const { startIndex, endIndex } = citation;
34
+ if (startIndex < 0 || endIndex > processedContent.length || startIndex >= endIndex) {
35
+ console.warn("[citationProcessor] Invalid citation indices:", {
36
+ startIndex,
37
+ endIndex,
38
+ contentLength: processedContent.length
39
+ });
40
+ return;
41
+ }
42
+ const displayIndex = citations.length - index;
43
+ const before = processedContent.slice(0, startIndex);
44
+ const after = processedContent.slice(endIndex);
45
+ processedContent = `${before}[${displayIndex}]${after}`;
46
+ processedCitations.unshift({
47
+ ...citation,
48
+ displayIndex
49
+ });
50
+ });
51
+ return {
52
+ content: processedContent,
53
+ citations: processedCitations
54
+ };
55
+ }
56
+ var init_citationProcessor = __esm({
57
+ "ui/src/bichat/utils/citationProcessor.ts"() {
58
+ }
59
+ });
60
+ var TableExportButton;
61
+ var init_TableExportButton = __esm({
62
+ "ui/src/bichat/components/TableExportButton.tsx"() {
63
+ TableExportButton = memo(function TableExportButton2({
64
+ onClick,
65
+ disabled = false,
66
+ label = "Export",
67
+ disabledTooltip = "Please wait..."
68
+ }) {
69
+ return /* @__PURE__ */ jsxs(
70
+ "button",
71
+ {
72
+ type: "button",
73
+ onClick,
74
+ disabled,
75
+ className: "inline-flex items-center gap-1 px-2 py-1 text-xs font-medium text-green-600 dark:text-green-500 opacity-60 hover:opacity-90 disabled:opacity-30 disabled:cursor-not-allowed transition-opacity",
76
+ "aria-label": label,
77
+ title: disabled ? disabledTooltip : label,
78
+ children: [
79
+ /* @__PURE__ */ jsx(FileXls, { size: 16, weight: "fill" }),
80
+ /* @__PURE__ */ jsx("span", { children: label })
81
+ ]
82
+ }
83
+ );
84
+ });
85
+ }
86
+ });
87
+ var DEFAULT_EXPORT_MESSAGE, TableWithExport;
88
+ var init_TableWithExport = __esm({
89
+ "ui/src/bichat/components/TableWithExport.tsx"() {
90
+ init_TableExportButton();
91
+ DEFAULT_EXPORT_MESSAGE = "Export the table above to Excel";
92
+ TableWithExport = memo(function TableWithExport2({
93
+ children,
94
+ sendMessage,
95
+ disabled = false,
96
+ exportMessage = DEFAULT_EXPORT_MESSAGE,
97
+ exportLabel = "Export"
98
+ }) {
99
+ const handleExport = useCallback(() => {
100
+ sendMessage?.(exportMessage);
101
+ }, [sendMessage, exportMessage]);
102
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
103
+ /* @__PURE__ */ jsx("div", { className: "markdown-table-wrapper overflow-x-auto", children: /* @__PURE__ */ jsx("table", { className: "markdown-table w-full border-collapse", children }) }),
104
+ sendMessage && /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-1", children: /* @__PURE__ */ jsx(TableExportButton, { onClick: handleExport, disabled, label: exportLabel }) })
105
+ ] });
106
+ });
107
+ }
108
+ });
109
+
110
+ // ui/src/bichat/components/CodeBlock.tsx
111
+ var CodeBlock_exports = {};
112
+ __export(CodeBlock_exports, {
113
+ CodeBlock: () => MemoizedCodeBlock,
114
+ default: () => CodeBlock_default
115
+ });
116
+ function normalizeLanguage(lang) {
117
+ if (!lang) return "text";
118
+ return languageMap[lang.toLowerCase()] || lang.toLowerCase();
119
+ }
120
+ function CodeBlock({
121
+ language,
122
+ value,
123
+ inline,
124
+ copyLabel = "Copy",
125
+ copiedLabel = "Copied!"
126
+ }) {
127
+ const [copied, setCopied] = useState(false);
128
+ const [isDarkMode, setIsDarkMode] = useState(getInitialDarkMode);
129
+ const [isLoaded, setIsLoaded] = useState(false);
130
+ const copyTimeoutRef = useRef(null);
131
+ const normalizedLanguage = normalizeLanguage(language);
132
+ useEffect(() => {
133
+ setIsDarkMode(document.documentElement.classList.contains("dark"));
134
+ setIsLoaded(true);
135
+ const observer = new MutationObserver(() => {
136
+ setIsDarkMode(document.documentElement.classList.contains("dark"));
137
+ });
138
+ observer.observe(document.documentElement, {
139
+ attributes: true,
140
+ attributeFilter: ["class"]
141
+ });
142
+ return () => observer.disconnect();
143
+ }, []);
144
+ useEffect(() => {
145
+ return () => {
146
+ if (copyTimeoutRef.current !== null) {
147
+ clearTimeout(copyTimeoutRef.current);
148
+ }
149
+ };
150
+ }, []);
151
+ const handleCopy = async () => {
152
+ try {
153
+ await navigator.clipboard.writeText(value);
154
+ setCopied(true);
155
+ if (copyTimeoutRef.current !== null) {
156
+ clearTimeout(copyTimeoutRef.current);
157
+ }
158
+ copyTimeoutRef.current = window.setTimeout(() => {
159
+ setCopied(false);
160
+ copyTimeoutRef.current = null;
161
+ }, 2e3);
162
+ } catch (err) {
163
+ console.error("Failed to copy:", err);
164
+ }
165
+ };
166
+ if (inline) {
167
+ return /* @__PURE__ */ jsx("code", { className: "px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 text-red-600 dark:text-red-400 rounded text-sm font-mono", children: value });
168
+ }
169
+ if (!isLoaded) {
170
+ return /* @__PURE__ */ jsx("pre", { className: "bg-gray-100 dark:bg-gray-800 rounded-lg p-4 overflow-x-auto my-4 border border-gray-300 dark:border-gray-700", children: /* @__PURE__ */ jsx("code", { className: "text-gray-700 dark:text-gray-300 text-sm font-mono", children: value }) });
171
+ }
172
+ return /* @__PURE__ */ jsxs("div", { className: "relative group my-4 rounded-lg overflow-hidden border border-gray-300 dark:border-gray-700", children: [
173
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-4 py-2 bg-gray-200 dark:bg-gray-800 border-b border-gray-300 dark:border-gray-700", children: [
174
+ /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-600 dark:text-gray-400 font-medium uppercase", children: normalizedLanguage }),
175
+ /* @__PURE__ */ jsx(
176
+ "button",
177
+ {
178
+ onClick: handleCopy,
179
+ className: "text-xs text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-white transition-colors flex items-center gap-1.5",
180
+ title: copyLabel,
181
+ "aria-live": "polite",
182
+ children: copied ? /* @__PURE__ */ jsxs(Fragment, { children: [
183
+ /* @__PURE__ */ jsx(Check, { size: 16, className: "w-4 h-4" }),
184
+ /* @__PURE__ */ jsx("span", { children: copiedLabel })
185
+ ] }) : /* @__PURE__ */ jsxs(Fragment, { children: [
186
+ /* @__PURE__ */ jsx(Copy, { size: 16, className: "w-4 h-4" }),
187
+ /* @__PURE__ */ jsx("span", { children: copyLabel })
188
+ ] })
189
+ }
190
+ )
191
+ ] }),
192
+ /* @__PURE__ */ jsx(
193
+ Prism,
194
+ {
195
+ language: normalizedLanguage,
196
+ style: isDarkMode ? vscDarkPlus : vs,
197
+ customStyle: {
198
+ margin: 0,
199
+ borderRadius: 0,
200
+ fontSize: "0.875rem",
201
+ lineHeight: "1.5",
202
+ padding: "1rem"
203
+ },
204
+ showLineNumbers: false,
205
+ wrapLines: true,
206
+ codeTagProps: {
207
+ style: {
208
+ fontFamily: '"JetBrains Mono", "Fira Code", "Menlo", monospace'
209
+ }
210
+ },
211
+ children: value
212
+ }
213
+ )
214
+ ] });
215
+ }
216
+ var getInitialDarkMode, languageMap, MemoizedCodeBlock, CodeBlock_default;
217
+ var init_CodeBlock = __esm({
218
+ "ui/src/bichat/components/CodeBlock.tsx"() {
219
+ getInitialDarkMode = () => {
220
+ if (typeof document === "undefined") return false;
221
+ return document.documentElement.classList.contains("dark");
222
+ };
223
+ languageMap = {
224
+ js: "javascript",
225
+ ts: "typescript",
226
+ jsx: "jsx",
227
+ tsx: "tsx",
228
+ py: "python",
229
+ rb: "ruby",
230
+ yml: "yaml",
231
+ yaml: "yaml",
232
+ sh: "bash",
233
+ bash: "bash",
234
+ json: "json",
235
+ xml: "xml",
236
+ html: "html",
237
+ css: "css",
238
+ sql: "sql",
239
+ go: "go",
240
+ java: "java",
241
+ cpp: "cpp",
242
+ c: "c",
243
+ csharp: "csharp",
244
+ php: "php"
245
+ };
246
+ MemoizedCodeBlock = memo(CodeBlock);
247
+ MemoizedCodeBlock.displayName = "CodeBlock";
248
+ CodeBlock_default = MemoizedCodeBlock;
249
+ }
250
+ });
251
+
23
252
  // ui/src/bichat/components/MarkdownRenderer.tsx
24
253
  var MarkdownRenderer_exports = {};
25
254
  __export(MarkdownRenderer_exports, {
26
- MarkdownRenderer: () => MarkdownRenderer
255
+ MarkdownRenderer: () => MemoizedMarkdownRenderer,
256
+ default: () => MarkdownRenderer_default
27
257
  });
28
- function MarkdownRenderer({ content }) {
29
- return /* @__PURE__ */ jsx("div", { className: "prose prose-sm max-w-none", children: /* @__PURE__ */ jsx(
30
- ReactMarkdown,
31
- {
32
- remarkPlugins: [remarkGfm],
33
- rehypePlugins: [rehypeSanitize],
34
- components: {
35
- code({ className, children, ...props }) {
36
- const match = /language-(\w+)/.exec(className || "");
37
- const isInline = !match;
38
- return !isInline && match ? /* @__PURE__ */ jsx(
39
- Prism,
258
+ function MarkdownRenderer({
259
+ content,
260
+ citations,
261
+ sendMessage,
262
+ sendDisabled = false,
263
+ copyLabel = "Copy",
264
+ copiedLabel = "Copied!",
265
+ exportLabel = "Export"
266
+ }) {
267
+ const processed = useMemo(() => {
268
+ return processCitations(content, citations);
269
+ }, [content, citations]);
270
+ const components = {
271
+ // Remove <pre> wrapper for code blocks - CodeBlock provides its own container
272
+ pre: ({ children }) => /* @__PURE__ */ jsx(Fragment, { children }),
273
+ code({ inline, className, children }) {
274
+ const match = /language-(\w+)/.exec(className || "");
275
+ const language = match ? match[1] : "";
276
+ const value = String(children).replace(/\n$/, "");
277
+ const isInline = inline === true;
278
+ if (isInline) {
279
+ return /* @__PURE__ */ jsx("code", { className: "px-1.5 py-0.5 bg-gray-100 dark:bg-gray-800 text-red-600 dark:text-red-400 rounded text-sm font-mono", children: value });
280
+ }
281
+ return /* @__PURE__ */ jsx(
282
+ Suspense,
283
+ {
284
+ fallback: /* @__PURE__ */ jsx("pre", { className: "bg-gray-100 dark:bg-gray-800 rounded-lg p-4 overflow-x-auto my-4", children: /* @__PURE__ */ jsx("code", { className: "text-sm font-mono", children: value }) }),
285
+ children: /* @__PURE__ */ jsx(
286
+ CodeBlock2,
40
287
  {
41
- style: tomorrow,
42
- language: match[1],
43
- PreTag: "div",
44
- children: String(children).replace(/\n$/, "")
288
+ language,
289
+ value,
290
+ inline: false,
291
+ copyLabel,
292
+ copiedLabel
45
293
  }
46
- ) : /* @__PURE__ */ jsx("code", { className, ...props, children });
294
+ )
47
295
  }
48
- },
49
- children: content
50
- }
51
- ) });
296
+ );
297
+ },
298
+ p: ({ children }) => /* @__PURE__ */ jsx("p", { className: "markdown-p my-2", children }),
299
+ a: ({ href, children }) => /* @__PURE__ */ jsx(
300
+ "a",
301
+ {
302
+ href,
303
+ target: "_blank",
304
+ rel: "noopener noreferrer",
305
+ className: "markdown-link text-[var(--bichat-primary)] hover:underline",
306
+ children
307
+ }
308
+ ),
309
+ h1: ({ children }) => /* @__PURE__ */ jsx("h1", { className: "markdown-h1 text-2xl font-bold mt-6 mb-3", children }),
310
+ h2: ({ children }) => /* @__PURE__ */ jsx("h2", { className: "markdown-h2 text-xl font-bold mt-5 mb-2", children }),
311
+ h3: ({ children }) => /* @__PURE__ */ jsx("h3", { className: "markdown-h3 text-lg font-semibold mt-4 mb-2", children }),
312
+ h4: ({ children }) => /* @__PURE__ */ jsx("h4", { className: "markdown-h4 text-base font-semibold mt-3 mb-1", children }),
313
+ h5: ({ children }) => /* @__PURE__ */ jsx("h5", { className: "markdown-h5 text-sm font-semibold mt-2 mb-1", children }),
314
+ h6: ({ children }) => /* @__PURE__ */ jsx("h6", { className: "markdown-h6 text-sm font-medium mt-2 mb-1", children }),
315
+ ul: ({ children }) => /* @__PURE__ */ jsx("ul", { className: "markdown-ul list-disc list-inside my-2 space-y-1", children }),
316
+ ol: ({ children }) => /* @__PURE__ */ jsx("ol", { className: "markdown-ol list-decimal list-inside my-2 space-y-1", children }),
317
+ li: ({ children }) => /* @__PURE__ */ jsx("li", { className: "markdown-li", children }),
318
+ blockquote: ({ children }) => /* @__PURE__ */ jsx("blockquote", { className: "markdown-blockquote border-l-4 border-gray-300 dark:border-gray-600 pl-4 my-2 italic text-gray-600 dark:text-gray-400", children }),
319
+ table: ({ children }) => /* @__PURE__ */ jsx(
320
+ TableWithExport,
321
+ {
322
+ sendMessage,
323
+ disabled: sendDisabled,
324
+ exportLabel,
325
+ children
326
+ }
327
+ ),
328
+ thead: ({ children }) => /* @__PURE__ */ jsx("thead", { className: "markdown-thead bg-gray-100 dark:bg-gray-800", children }),
329
+ tbody: ({ children }) => /* @__PURE__ */ jsx("tbody", { className: "markdown-tbody", children }),
330
+ tr: ({ children }) => /* @__PURE__ */ jsx("tr", { className: "markdown-tr border-b border-gray-200 dark:border-gray-700", children }),
331
+ th: ({ children }) => /* @__PURE__ */ jsx("th", { className: "markdown-th px-3 py-2 text-left text-sm font-semibold text-gray-700 dark:text-gray-300", children }),
332
+ td: ({ children }) => /* @__PURE__ */ jsx("td", { className: "markdown-td px-3 py-2 text-sm text-gray-600 dark:text-gray-400", children }),
333
+ hr: () => /* @__PURE__ */ jsx("hr", { className: "markdown-hr my-4 border-gray-200 dark:border-gray-700" }),
334
+ strong: ({ children }) => /* @__PURE__ */ jsx("strong", { className: "markdown-strong font-semibold", children }),
335
+ em: ({ children }) => /* @__PURE__ */ jsx("em", { className: "markdown-em italic", children })
336
+ };
337
+ return /* @__PURE__ */ jsx("div", { className: "markdown-content", children: /* @__PURE__ */ jsx(ReactMarkdown, { remarkPlugins: [remarkGfm], components, children: processed.content }) });
52
338
  }
339
+ var CodeBlock2, MemoizedMarkdownRenderer, MarkdownRenderer_default;
53
340
  var init_MarkdownRenderer = __esm({
54
341
  "ui/src/bichat/components/MarkdownRenderer.tsx"() {
342
+ init_citationProcessor();
343
+ init_TableWithExport();
344
+ CodeBlock2 = lazy(() => Promise.resolve().then(() => (init_CodeBlock(), CodeBlock_exports)).then((module) => ({ default: module.CodeBlock })));
345
+ MemoizedMarkdownRenderer = memo(MarkdownRenderer);
346
+ MemoizedMarkdownRenderer.displayName = "MarkdownRenderer";
347
+ MarkdownRenderer_default = MemoizedMarkdownRenderer;
55
348
  }
56
349
  });
57
350
 
58
- // ui/src/bichat/types/index.ts
59
- var MessageRole = /* @__PURE__ */ ((MessageRole2) => {
60
- MessageRole2["User"] = "user";
61
- MessageRole2["Assistant"] = "assistant";
62
- MessageRole2["System"] = "system";
63
- MessageRole2["Tool"] = "tool";
64
- return MessageRole2;
65
- })(MessageRole || {});
66
-
67
351
  // ui/src/bichat/utils/RateLimiter.ts
68
352
  var RateLimiter = class {
69
353
  constructor(config) {
@@ -111,6 +395,24 @@ var DEFAULT_RATE_LIMIT_CONFIG = {
111
395
  windowMs: 6e4
112
396
  // 1 minute
113
397
  };
398
+ function generateTempId(prefix) {
399
+ return `${prefix}-${Date.now()}-${Math.random().toString(36).slice(2, 11)}`;
400
+ }
401
+ function createPendingTurn(sessionId, content, attachments = []) {
402
+ const now = (/* @__PURE__ */ new Date()).toISOString();
403
+ return {
404
+ id: generateTempId("turn"),
405
+ sessionId,
406
+ userTurn: {
407
+ id: generateTempId("user"),
408
+ content,
409
+ attachments,
410
+ createdAt: now
411
+ },
412
+ // No assistantTurn yet - it will be added when streaming completes
413
+ createdAt: now
414
+ };
415
+ }
114
416
  function ChatSessionProvider({
115
417
  dataSource,
116
418
  sessionId,
@@ -118,7 +420,7 @@ function ChatSessionProvider({
118
420
  children
119
421
  }) {
120
422
  const [message, setMessage] = useState("");
121
- const [messages, setMessages] = useState([]);
423
+ const [turns, setTurns] = useState([]);
122
424
  const [loading, setLoading] = useState(false);
123
425
  const [error, setError] = useState(null);
124
426
  const [currentSessionId, setCurrentSessionId] = useState(sessionId);
@@ -139,7 +441,7 @@ function ChatSessionProvider({
139
441
  useEffect(() => {
140
442
  if (!currentSessionId || currentSessionId === "new") {
141
443
  setSession(null);
142
- setMessages([]);
444
+ setTurns([]);
143
445
  setPendingQuestion(null);
144
446
  setFetching(false);
145
447
  return;
@@ -151,7 +453,7 @@ function ChatSessionProvider({
151
453
  if (cancelled) return;
152
454
  if (state) {
153
455
  setSession(state.session);
154
- setMessages(state.messages);
456
+ setTurns(state.turns);
155
457
  setPendingQuestion(state.pendingQuestion || null);
156
458
  } else {
157
459
  setError("Session not found");
@@ -184,14 +486,8 @@ function ChatSessionProvider({
184
486
  setError(null);
185
487
  setStreamingContent("");
186
488
  abortControllerRef.current = new AbortController();
187
- const tempUserMessage = {
188
- id: `temp-user-${Date.now()}`,
189
- sessionId: currentSessionId || "new",
190
- role: "user" /* User */,
191
- content,
192
- createdAt: (/* @__PURE__ */ new Date()).toISOString()
193
- };
194
- setMessages((prev) => [...prev, tempUserMessage]);
489
+ const tempTurn = createPendingTurn(currentSessionId || "new", content, attachments);
490
+ setTurns((prev) => [...prev, tempTurn]);
195
491
  try {
196
492
  let activeSessionId = currentSessionId;
197
493
  let shouldNavigateAfter = false;
@@ -229,7 +525,7 @@ function ChatSessionProvider({
229
525
  const state = await dataSource.fetchSession(finalSessionId);
230
526
  if (state) {
231
527
  setSession(state.session);
232
- setMessages(state.messages);
528
+ setTurns(state.turns);
233
529
  setPendingQuestion(state.pendingQuestion || null);
234
530
  }
235
531
  }
@@ -246,7 +542,7 @@ function ChatSessionProvider({
246
542
  setMessage(content);
247
543
  return;
248
544
  }
249
- setMessages((prev) => prev.filter((m) => m.id !== tempUserMessage.id));
545
+ setTurns((prev) => prev.filter((t) => t.id !== tempTurn.id));
250
546
  const errorMessage = err instanceof Error ? err.message : "Failed to send message";
251
547
  setError(errorMessage);
252
548
  console.error("Send message error:", err);
@@ -295,32 +591,29 @@ function ChatSessionProvider({
295
591
  };
296
592
  }, [messageQueue]);
297
593
  const handleRegenerate = useCallback(
298
- async (messageId) => {
594
+ async (turnId) => {
299
595
  if (!currentSessionId || currentSessionId === "new") return;
300
- const messageIndex = messages.findIndex((m) => m.id === messageId);
301
- if (messageIndex <= 0) return;
596
+ const turn = turns.find((t) => t.id === turnId);
597
+ if (!turn) return;
302
598
  setLoading(true);
303
599
  setError(null);
304
600
  try {
305
- const userMessage = messages[messageIndex - 1];
306
- if (userMessage && userMessage.role === "user" /* User */) {
307
- await sendMessageDirect(userMessage.content, []);
308
- }
601
+ await sendMessageDirect(turn.userTurn.content, turn.userTurn.attachments);
309
602
  } catch (err) {
310
- const errorMessage = err instanceof Error ? err.message : "Failed to regenerate message";
603
+ const errorMessage = err instanceof Error ? err.message : "Failed to regenerate response";
311
604
  setError(errorMessage);
312
605
  console.error("Regenerate error:", err);
313
606
  } finally {
314
607
  setLoading(false);
315
608
  }
316
609
  },
317
- [messages, currentSessionId, sendMessageDirect]
610
+ [turns, currentSessionId, sendMessageDirect]
318
611
  );
319
612
  const handleEdit = useCallback(
320
- async (messageId, newContent) => {
613
+ async (turnId, newContent) => {
321
614
  if (!currentSessionId || currentSessionId === "new") {
322
615
  setMessage(newContent);
323
- setMessages((prev) => prev.filter((m) => m.id !== messageId));
616
+ setTurns((prev) => prev.filter((t) => t.id !== turnId));
324
617
  return;
325
618
  }
326
619
  setLoading(true);
@@ -356,7 +649,7 @@ function ChatSessionProvider({
356
649
  try {
357
650
  const state = await dataSource.fetchSession(currentSessionId);
358
651
  if (state) {
359
- setMessages(state.messages);
652
+ setTurns(state.turns);
360
653
  setPendingQuestion(state.pendingQuestion || null);
361
654
  } else {
362
655
  setPendingQuestion(previousPendingQuestion);
@@ -400,7 +693,7 @@ function ChatSessionProvider({
400
693
  const value = {
401
694
  // State
402
695
  message,
403
- messages,
696
+ turns,
404
697
  loading,
405
698
  error,
406
699
  currentSessionId,
@@ -594,7 +887,7 @@ function ChatHeader({ session, onBack, logoSlot, actionsSlot }) {
594
887
  "button",
595
888
  {
596
889
  onClick: onBack,
597
- className: "p-2 hover:bg-gray-100 dark:hover:bg-gray-700 rounded-lg transition-colors",
890
+ className: "p-2 hover:bg-gray-100 dark:hover:bg-gray-700 active:bg-gray-200 dark:active:bg-gray-600 rounded-lg transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
598
891
  "aria-label": t("chat.goBack"),
599
892
  children: /* @__PURE__ */ jsx("svg", { className: "w-5 h-5", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: /* @__PURE__ */ jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M15 19l-7-7 7-7" }) })
600
893
  }
@@ -676,25 +969,70 @@ function AttachmentGrid({
676
969
  attachments,
677
970
  onRemove,
678
971
  onView,
679
- className = ""
972
+ className = "",
973
+ readonly = false,
974
+ maxDisplay,
975
+ maxCapacity = 10,
976
+ emptyMessage = "No images attached",
977
+ showCount = false
680
978
  }) {
681
- if (attachments.length === 0) return null;
979
+ const displayedAttachments = useMemo(
980
+ () => maxDisplay && attachments.length > maxDisplay ? attachments.slice(0, maxDisplay) : attachments,
981
+ [attachments, maxDisplay]
982
+ );
983
+ const isAtMaxCapacity = attachments.length >= maxCapacity;
984
+ if (displayedAttachments.length === 0) {
985
+ if (!showCount) return null;
986
+ return /* @__PURE__ */ jsx("div", { className: "text-center text-gray-500 dark:text-gray-400 py-4", children: emptyMessage });
987
+ }
988
+ const isEditable = !readonly && !!onRemove;
989
+ const isViewable = !readonly && !!onView;
990
+ return /* @__PURE__ */ jsxs("div", { className: `space-y-2 ${className}`, children: [
991
+ showCount && /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-600 dark:text-gray-400", children: [
992
+ displayedAttachments.length,
993
+ " image",
994
+ displayedAttachments.length !== 1 ? "s" : "",
995
+ " attached"
996
+ ] }),
997
+ /* @__PURE__ */ jsx("div", { className: "grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2", children: displayedAttachments.map((attachment, index) => /* @__PURE__ */ jsx(
998
+ MemoizedAttachmentItem,
999
+ {
1000
+ attachment,
1001
+ index,
1002
+ onRemove: isEditable ? onRemove : void 0,
1003
+ onView: isViewable ? onView : void 0
1004
+ },
1005
+ `${attachment.filename}-${index}`
1006
+ )) }),
1007
+ maxDisplay && attachments.length > maxDisplay && /* @__PURE__ */ jsxs("div", { className: "text-sm text-gray-500 dark:text-gray-400", children: [
1008
+ "+",
1009
+ attachments.length - maxDisplay,
1010
+ " more"
1011
+ ] }),
1012
+ isAtMaxCapacity && isEditable && /* @__PURE__ */ jsxs("div", { className: "text-sm text-amber-600 dark:text-amber-400", children: [
1013
+ "Maximum ",
1014
+ maxCapacity,
1015
+ " images"
1016
+ ] })
1017
+ ] });
1018
+ }
1019
+ function AttachmentItem({ attachment, index, onRemove, onView }) {
682
1020
  const isEditable = !!onRemove;
683
1021
  const isViewable = !!onView;
684
- return /* @__PURE__ */ jsx("div", { className: `grid grid-cols-2 sm:grid-cols-3 md:grid-cols-4 gap-2 ${className}`, children: attachments.map((attachment, index) => /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
1022
+ return /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
685
1023
  /* @__PURE__ */ jsx(
686
1024
  "img",
687
1025
  {
688
1026
  src: attachment.preview,
689
1027
  alt: attachment.filename,
690
- className: `w-full h-24 object-cover rounded-lg border border-gray-200 dark:border-gray-700 ${isViewable ? "cursor-pointer hover:opacity-80 transition-opacity" : ""}`,
691
- onClick: () => isViewable && onView(index),
1028
+ className: `w-full h-24 object-cover rounded-lg border border-gray-200 dark:border-gray-700 ${isViewable ? "cursor-pointer hover:opacity-80 transition-opacity duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900" : ""}`,
1029
+ onClick: () => isViewable && onView?.(index),
692
1030
  role: isViewable ? "button" : void 0,
693
1031
  tabIndex: isViewable ? 0 : void 0,
694
1032
  onKeyDown: (e) => {
695
1033
  if (isViewable && (e.key === "Enter" || e.key === " ")) {
696
1034
  e.preventDefault();
697
- onView(index);
1035
+ onView?.(index);
698
1036
  }
699
1037
  }
700
1038
  }
@@ -705,203 +1043,600 @@ function AttachmentGrid({
705
1043
  type: "button",
706
1044
  onClick: (e) => {
707
1045
  e.stopPropagation();
708
- onRemove(index);
1046
+ onRemove?.(index);
709
1047
  },
710
- className: "absolute top-1 right-1 p-1 bg-red-500 hover:bg-red-600 text-white rounded-full opacity-0 group-hover:opacity-100 transition-opacity shadow-md",
1048
+ className: "absolute top-1 right-1 p-1 bg-red-500 hover:bg-red-600 active:bg-red-700 text-white rounded-full opacity-0 group-hover:opacity-100 transition-all duration-150 shadow-md focus-visible:opacity-100 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-white/50",
711
1049
  "aria-label": `Remove ${attachment.filename}`,
712
1050
  children: /* @__PURE__ */ jsx(X, { size: 16, weight: "bold" })
713
1051
  }
714
1052
  ),
715
1053
  /* @__PURE__ */ jsxs("div", { className: "mt-1 px-1", children: [
716
- /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-600 dark:text-gray-400 truncate", title: attachment.filename, children: attachment.filename.length > 20 ? `${attachment.filename.substring(0, 20)}...` : attachment.filename }),
1054
+ /* @__PURE__ */ jsx(
1055
+ "div",
1056
+ {
1057
+ className: "text-xs text-gray-600 dark:text-gray-400 truncate",
1058
+ title: attachment.filename,
1059
+ children: attachment.filename.length > 20 ? `${attachment.filename.substring(0, 20)}...` : attachment.filename
1060
+ }
1061
+ ),
717
1062
  /* @__PURE__ */ jsx("div", { className: "text-xs text-gray-500 dark:text-gray-500", children: formatFileSize(attachment.sizeBytes) })
718
1063
  ] })
719
- ] }, index)) });
1064
+ ] });
1065
+ }
1066
+ var MemoizedAttachmentItem = React.memo(
1067
+ AttachmentItem,
1068
+ (prevProps, nextProps) => {
1069
+ return prevProps.attachment.base64Data === nextProps.attachment.base64Data && prevProps.attachment.filename === nextProps.attachment.filename && prevProps.attachment.preview === nextProps.attachment.preview && prevProps.index === nextProps.index && prevProps.onRemove === nextProps.onRemove && prevProps.onView === nextProps.onView;
1070
+ }
1071
+ );
1072
+ var MemoizedAttachmentGrid = React.memo(AttachmentGrid);
1073
+ MemoizedAttachmentGrid.displayName = "AttachmentGrid";
1074
+ var AttachmentGrid_default = MemoizedAttachmentGrid;
1075
+ function useModalLock(isOpen) {
1076
+ useEffect(() => {
1077
+ if (!isOpen) return;
1078
+ const originalOverflow = document.body.style.overflow;
1079
+ document.body.style.overflow = "hidden";
1080
+ return () => {
1081
+ document.body.style.overflow = originalOverflow;
1082
+ };
1083
+ }, [isOpen]);
1084
+ }
1085
+ function useFocusTrap(containerRef, isActive, restoreFocusOnDeactivate) {
1086
+ useEffect(() => {
1087
+ if (!isActive || !containerRef.current) return;
1088
+ const container = containerRef.current;
1089
+ const previouslyFocused = document.activeElement;
1090
+ const getFocusableElements = () => {
1091
+ const selector = [
1092
+ "button:not([disabled])",
1093
+ "[href]",
1094
+ "input:not([disabled])",
1095
+ "select:not([disabled])",
1096
+ "textarea:not([disabled])",
1097
+ '[tabindex]:not([tabindex="-1"])'
1098
+ ].join(", ");
1099
+ return Array.from(container.querySelectorAll(selector));
1100
+ };
1101
+ const focusableElements = getFocusableElements();
1102
+ if (focusableElements.length > 0) {
1103
+ focusableElements[0].focus();
1104
+ }
1105
+ const handleTabKey = (e) => {
1106
+ if (e.key !== "Tab") return;
1107
+ const focusableElements2 = getFocusableElements();
1108
+ if (focusableElements2.length === 0) return;
1109
+ const firstElement = focusableElements2[0];
1110
+ const lastElement = focusableElements2[focusableElements2.length - 1];
1111
+ if (e.shiftKey) {
1112
+ if (document.activeElement === firstElement) {
1113
+ e.preventDefault();
1114
+ lastElement.focus();
1115
+ }
1116
+ } else {
1117
+ if (document.activeElement === lastElement) {
1118
+ e.preventDefault();
1119
+ firstElement.focus();
1120
+ }
1121
+ }
1122
+ };
1123
+ container.addEventListener("keydown", handleTabKey);
1124
+ return () => {
1125
+ container.removeEventListener("keydown", handleTabKey);
1126
+ if (restoreFocusOnDeactivate) {
1127
+ restoreFocusOnDeactivate.focus();
1128
+ } else if (previouslyFocused instanceof HTMLElement) {
1129
+ previouslyFocused.focus();
1130
+ }
1131
+ };
1132
+ }, [containerRef, isActive, restoreFocusOnDeactivate]);
720
1133
  }
721
- function ImageModal({ images, initialIndex, onClose }) {
722
- const [currentIndex, setCurrentIndex] = useState(initialIndex);
723
- const handlePrevious = () => {
724
- setCurrentIndex((prev) => prev > 0 ? prev - 1 : images.length - 1);
1134
+ function SpinnerLoader({
1135
+ size = "md",
1136
+ message
1137
+ }) {
1138
+ const sizeMap = {
1139
+ sm: 16,
1140
+ md: 32,
1141
+ lg: 48
725
1142
  };
726
- const handleNext = () => {
727
- setCurrentIndex((prev) => prev < images.length - 1 ? prev + 1 : 0);
1143
+ const sizeClasses5 = {
1144
+ sm: "h-4 w-4",
1145
+ md: "h-8 w-8",
1146
+ lg: "h-12 w-12"
1147
+ };
1148
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center", role: "status", "aria-live": "polite", children: [
1149
+ /* @__PURE__ */ jsx(
1150
+ CircleNotch,
1151
+ {
1152
+ size: sizeMap[size],
1153
+ className: `${sizeClasses5[size]} animate-spin text-[var(--bichat-primary)]`
1154
+ }
1155
+ ),
1156
+ message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: message })
1157
+ ] });
1158
+ }
1159
+ function DotsLoader({
1160
+ size = "md",
1161
+ message
1162
+ }) {
1163
+ const dotSizeClasses = {
1164
+ sm: "w-1.5 h-1.5",
1165
+ md: "w-2 h-2",
1166
+ lg: "w-3 h-3"
1167
+ };
1168
+ const gapClasses2 = {
1169
+ sm: "gap-0.5",
1170
+ md: "gap-1",
1171
+ lg: "gap-1.5"
1172
+ };
1173
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center", role: "status", "aria-live": "polite", children: [
1174
+ /* @__PURE__ */ jsx("div", { className: `flex ${gapClasses2[size]}`, children: [0, 1, 2].map((index) => /* @__PURE__ */ jsx(
1175
+ "div",
1176
+ {
1177
+ className: `${dotSizeClasses[size]} bg-[var(--bichat-primary)] rounded-full animate-bounce`,
1178
+ style: { animationDelay: `${index * 0.15}s` }
1179
+ },
1180
+ index
1181
+ )) }),
1182
+ message && /* @__PURE__ */ jsx("p", { className: "mt-3 text-sm text-gray-600 dark:text-gray-400", children: message })
1183
+ ] });
1184
+ }
1185
+ function PulseLoader({
1186
+ size = "md",
1187
+ message
1188
+ }) {
1189
+ const sizeClasses5 = {
1190
+ sm: "h-4 w-4",
1191
+ md: "h-8 w-8",
1192
+ lg: "h-12 w-12"
728
1193
  };
1194
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center", role: "status", "aria-live": "polite", children: [
1195
+ /* @__PURE__ */ jsx("div", { className: `${sizeClasses5[size]} bg-[var(--bichat-primary)] rounded-full animate-pulse` }),
1196
+ message && /* @__PURE__ */ jsx("p", { className: "mt-2 text-sm text-gray-600 dark:text-gray-400", children: message })
1197
+ ] });
1198
+ }
1199
+ function LoadingSpinner({ variant = "spinner", size = "md", message }) {
1200
+ switch (variant) {
1201
+ case "dots":
1202
+ return /* @__PURE__ */ jsx(DotsLoader, { size, message });
1203
+ case "pulse":
1204
+ return /* @__PURE__ */ jsx(PulseLoader, { size, message });
1205
+ case "spinner":
1206
+ default:
1207
+ return /* @__PURE__ */ jsx(SpinnerLoader, { size, message });
1208
+ }
1209
+ }
1210
+ var MemoizedLoadingSpinner = memo(LoadingSpinner);
1211
+ MemoizedLoadingSpinner.displayName = "LoadingSpinner";
1212
+ var LoadingSpinner_default = MemoizedLoadingSpinner;
1213
+ function ImageModal({
1214
+ isOpen,
1215
+ onClose,
1216
+ attachment,
1217
+ allAttachments,
1218
+ currentIndex = 0,
1219
+ onNavigate
1220
+ }) {
1221
+ const modalRef = useRef(null);
1222
+ const [isImageLoaded, setIsImageLoaded] = useState(false);
1223
+ const [imageError, setImageError] = useState(false);
1224
+ const hasMultipleImages = allAttachments && allAttachments.length > 1;
1225
+ const canNavigatePrev = hasMultipleImages && currentIndex > 0;
1226
+ const canNavigateNext = hasMultipleImages && currentIndex < (allAttachments?.length || 1) - 1;
1227
+ useModalLock(isOpen);
1228
+ useFocusTrap(modalRef, isOpen);
729
1229
  useEffect(() => {
1230
+ if (!isOpen) return;
730
1231
  const handleKeyDown = (e) => {
731
1232
  if (e.key === "Escape") {
732
1233
  onClose();
733
- } else if (e.key === "ArrowLeft") {
734
- handlePrevious();
735
- } else if (e.key === "ArrowRight") {
736
- handleNext();
1234
+ } else if (e.key === "ArrowLeft" && onNavigate && canNavigatePrev) {
1235
+ onNavigate("prev");
1236
+ } else if (e.key === "ArrowRight" && onNavigate && canNavigateNext) {
1237
+ onNavigate("next");
737
1238
  }
738
1239
  };
739
- window.addEventListener("keydown", handleKeyDown);
740
- return () => window.removeEventListener("keydown", handleKeyDown);
741
- }, [onClose]);
1240
+ document.addEventListener("keydown", handleKeyDown);
1241
+ return () => document.removeEventListener("keydown", handleKeyDown);
1242
+ }, [isOpen, onClose, onNavigate, canNavigatePrev, canNavigateNext]);
742
1243
  useEffect(() => {
743
- document.body.style.overflow = "hidden";
744
- return () => {
745
- document.body.style.overflow = "";
746
- };
747
- }, []);
748
- return /* @__PURE__ */ jsxs(
749
- "div",
750
- {
751
- className: "fixed inset-0 z-50 bg-black/90 flex items-center justify-center",
752
- onClick: onClose,
753
- role: "dialog",
754
- "aria-modal": "true",
755
- "aria-label": "Image viewer",
756
- children: [
757
- /* @__PURE__ */ jsx(
758
- "button",
759
- {
760
- onClick: onClose,
761
- className: "absolute top-4 right-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
762
- "aria-label": "Close image viewer",
763
- children: /* @__PURE__ */ jsx(X, { size: 24, weight: "bold" })
764
- }
765
- ),
766
- images.length > 1 && /* @__PURE__ */ jsxs(Fragment, { children: [
767
- /* @__PURE__ */ jsx(
768
- "button",
769
- {
770
- onClick: (e) => {
771
- e.stopPropagation();
772
- handlePrevious();
773
- },
774
- className: "absolute left-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
775
- "aria-label": "Previous image",
776
- children: /* @__PURE__ */ jsx(CaretLeft, { size: 32, weight: "bold" })
777
- }
778
- ),
779
- /* @__PURE__ */ jsx(
780
- "button",
1244
+ setIsImageLoaded(false);
1245
+ setImageError(false);
1246
+ }, [attachment]);
1247
+ if (!isOpen) return null;
1248
+ const previewUrl = attachment.preview || createDataUrl(attachment.base64Data, attachment.mimeType);
1249
+ return createPortal(
1250
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1251
+ /* @__PURE__ */ jsx(
1252
+ "div",
1253
+ {
1254
+ className: "fixed inset-0 bg-black/90 transition-opacity duration-200 z-40",
1255
+ onClick: onClose,
1256
+ "aria-hidden": "true"
1257
+ }
1258
+ ),
1259
+ /* @__PURE__ */ jsx(
1260
+ "div",
1261
+ {
1262
+ className: "fixed inset-0 flex items-center justify-center z-50 p-4",
1263
+ role: "dialog",
1264
+ "aria-modal": "true",
1265
+ "aria-labelledby": "modal-image-title",
1266
+ "aria-describedby": "modal-image-description",
1267
+ children: /* @__PURE__ */ jsxs(
1268
+ "div",
781
1269
  {
782
- onClick: (e) => {
783
- e.stopPropagation();
784
- handleNext();
785
- },
786
- className: "absolute right-4 p-2 text-white hover:bg-white/10 rounded-lg transition-colors",
787
- "aria-label": "Next image",
788
- children: /* @__PURE__ */ jsx(CaretRight, { size: 32, weight: "bold" })
1270
+ ref: modalRef,
1271
+ className: "relative flex flex-col items-center justify-center w-full h-full",
1272
+ children: [
1273
+ /* @__PURE__ */ jsx(
1274
+ "button",
1275
+ {
1276
+ onClick: onClose,
1277
+ className: "absolute top-4 right-4 z-50 flex items-center justify-center w-10 h-10 bg-white/10 hover:bg-white/20 text-white rounded-full transition-colors duration-200",
1278
+ "aria-label": "Close modal",
1279
+ type: "button",
1280
+ children: /* @__PURE__ */ jsx(X, { size: 24, weight: "bold" })
1281
+ }
1282
+ ),
1283
+ /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center w-full h-full max-w-4xl", children: [
1284
+ !isImageLoaded && !imageError && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 flex items-center justify-center", children: /* @__PURE__ */ jsx(LoadingSpinner_default, {}) }),
1285
+ imageError && /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center text-white", children: [
1286
+ /* @__PURE__ */ jsx("p", { className: "text-lg font-medium mb-2", children: "Failed to load image" }),
1287
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-300", children: attachment.filename })
1288
+ ] }),
1289
+ /* @__PURE__ */ jsx(
1290
+ "img",
1291
+ {
1292
+ src: previewUrl,
1293
+ alt: attachment.filename,
1294
+ className: `
1295
+ max-w-4xl max-h-screen object-contain
1296
+ transition-opacity duration-200
1297
+ ${isImageLoaded ? "opacity-100" : "opacity-0"}
1298
+ `,
1299
+ onLoad: () => setIsImageLoaded(true),
1300
+ onError: () => setImageError(true),
1301
+ loading: "lazy"
1302
+ }
1303
+ )
1304
+ ] }),
1305
+ isImageLoaded && !imageError && /* @__PURE__ */ jsxs("div", { className: "absolute bottom-0 left-0 right-0 flex flex-col items-center text-white text-center pb-4 bg-gradient-to-t from-black/50 to-transparent pt-8", children: [
1306
+ /* @__PURE__ */ jsx("p", { id: "modal-image-title", className: "text-lg font-medium", children: attachment.filename }),
1307
+ /* @__PURE__ */ jsxs("div", { id: "modal-image-description", className: "text-sm text-gray-300 space-x-2", children: [
1308
+ /* @__PURE__ */ jsx("span", { children: formatFileSize(attachment.sizeBytes) }),
1309
+ /* @__PURE__ */ jsx("span", { children: "\u2022" }),
1310
+ /* @__PURE__ */ jsx("span", { children: attachment.mimeType })
1311
+ ] })
1312
+ ] }),
1313
+ hasMultipleImages && /* @__PURE__ */ jsx(
1314
+ "button",
1315
+ {
1316
+ onClick: () => onNavigate?.("prev"),
1317
+ disabled: !canNavigatePrev,
1318
+ className: `
1319
+ absolute left-4 z-40 flex items-center justify-center w-12 h-12
1320
+ rounded-full transition-all duration-200
1321
+ ${canNavigatePrev ? "bg-white/10 hover:bg-white/20 text-white cursor-pointer" : "bg-white/5 text-white/30 cursor-not-allowed"}
1322
+ `,
1323
+ "aria-label": "Previous image",
1324
+ type: "button",
1325
+ children: /* @__PURE__ */ jsx(CaretLeft, { size: 28, weight: "bold" })
1326
+ }
1327
+ ),
1328
+ hasMultipleImages && /* @__PURE__ */ jsx(
1329
+ "button",
1330
+ {
1331
+ onClick: () => onNavigate?.("next"),
1332
+ disabled: !canNavigateNext,
1333
+ className: `
1334
+ absolute right-4 z-40 flex items-center justify-center w-12 h-12
1335
+ rounded-full transition-all duration-200
1336
+ ${canNavigateNext ? "bg-white/10 hover:bg-white/20 text-white cursor-pointer" : "bg-white/5 text-white/30 cursor-not-allowed"}
1337
+ `,
1338
+ "aria-label": "Next image",
1339
+ type: "button",
1340
+ children: /* @__PURE__ */ jsx(CaretRight, { size: 28, weight: "bold" })
1341
+ }
1342
+ ),
1343
+ hasMultipleImages && /* @__PURE__ */ jsxs("div", { className: "absolute top-4 left-4 bg-white/10 text-white px-3 py-1 rounded-full text-sm", children: [
1344
+ currentIndex + 1,
1345
+ " / ",
1346
+ allAttachments?.length
1347
+ ] })
1348
+ ]
789
1349
  }
790
1350
  )
791
- ] }),
792
- /* @__PURE__ */ jsx(
793
- "img",
794
- {
795
- src: images[currentIndex].preview,
796
- alt: images[currentIndex].filename,
797
- className: "max-w-[90vw] max-h-[90vh] object-contain",
798
- onClick: (e) => e.stopPropagation()
799
- }
800
- ),
801
- /* @__PURE__ */ jsxs(
802
- "div",
803
- {
804
- className: "absolute bottom-4 left-1/2 transform -translate-x-1/2 text-white text-sm bg-black/50 px-4 py-2 rounded-lg backdrop-blur",
805
- onClick: (e) => e.stopPropagation(),
806
- children: [
807
- /* @__PURE__ */ jsx("div", { className: "text-center font-medium mb-1", children: images[currentIndex].filename }),
808
- images.length > 1 && /* @__PURE__ */ jsxs("div", { className: "text-center text-xs opacity-80", children: [
809
- currentIndex + 1,
810
- " / ",
811
- images.length
812
- ] })
813
- ]
814
- }
815
- )
816
- ]
817
- }
1351
+ }
1352
+ )
1353
+ ] }),
1354
+ document.body
818
1355
  );
819
1356
  }
820
- function UserTurnView({ message }) {
821
- const { handleEdit, handleCopy } = useChat();
1357
+ var ImageModal_default = ImageModal;
1358
+ var defaultClassNames = {
1359
+ root: "flex gap-3 justify-end group",
1360
+ wrapper: "flex-1 flex flex-col items-end max-w-[75%]",
1361
+ avatar: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-sm",
1362
+ bubble: "bg-primary-600 text-white rounded-2xl rounded-br-sm px-4 py-3",
1363
+ content: "text-[15px] whitespace-pre-wrap break-words leading-relaxed",
1364
+ attachments: "mb-2 w-full",
1365
+ actions: "flex items-center gap-1 mt-2 opacity-0 group-hover:opacity-100 transition-opacity duration-150",
1366
+ actionButton: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 active:bg-gray-200 dark:active:bg-gray-700 rounded-md transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
1367
+ timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
1368
+ };
1369
+ function mergeClassNames(defaults, overrides) {
1370
+ if (!overrides) return defaults;
1371
+ return {
1372
+ root: overrides.root ?? defaults.root,
1373
+ wrapper: overrides.wrapper ?? defaults.wrapper,
1374
+ avatar: overrides.avatar ?? defaults.avatar,
1375
+ bubble: overrides.bubble ?? defaults.bubble,
1376
+ content: overrides.content ?? defaults.content,
1377
+ attachments: overrides.attachments ?? defaults.attachments,
1378
+ actions: overrides.actions ?? defaults.actions,
1379
+ actionButton: overrides.actionButton ?? defaults.actionButton,
1380
+ timestamp: overrides.timestamp ?? defaults.timestamp
1381
+ };
1382
+ }
1383
+ function UserMessage({
1384
+ turn,
1385
+ turnId,
1386
+ initials = "U",
1387
+ slots,
1388
+ classNames: classNameOverrides,
1389
+ onCopy,
1390
+ onEdit,
1391
+ hideAvatar = false,
1392
+ hideActions = false,
1393
+ hideTimestamp = false
1394
+ }) {
822
1395
  const [selectedImageIndex, setSelectedImageIndex] = useState(null);
823
- const handleCopyClick = async () => {
824
- if (handleCopy) {
825
- await handleCopy(message.content);
1396
+ const [isEditing, setIsEditing] = useState(false);
1397
+ const [draftContent, setDraftContent] = useState("");
1398
+ const classes = mergeClassNames(defaultClassNames, classNameOverrides);
1399
+ const imageAttachments = turn.attachments.filter((a) => a.mimeType.startsWith("image/")).map((a) => ({
1400
+ filename: a.filename,
1401
+ mimeType: a.mimeType,
1402
+ sizeBytes: a.sizeBytes,
1403
+ base64Data: a.base64Data || "",
1404
+ preview: a.base64Data ? `data:${a.mimeType};base64,${a.base64Data}` : ""
1405
+ }));
1406
+ const handleCopyClick = useCallback(async () => {
1407
+ if (onCopy) {
1408
+ await onCopy(turn.content);
826
1409
  } else {
827
1410
  try {
828
- await navigator.clipboard.writeText(message.content);
1411
+ await navigator.clipboard.writeText(turn.content);
829
1412
  } catch (err) {
830
1413
  console.error("Failed to copy:", err);
831
1414
  }
832
1415
  }
833
- };
834
- const handleEditClick = () => {
835
- if (handleEdit) {
836
- const newContent = prompt("Edit message:", message.content);
837
- if (newContent && newContent !== message.content) {
838
- handleEdit(message.id, newContent);
839
- }
1416
+ }, [onCopy, turn.content]);
1417
+ const handleEditClick = useCallback(() => {
1418
+ if (onEdit && turnId) {
1419
+ setDraftContent(turn.content);
1420
+ setIsEditing(true);
1421
+ }
1422
+ }, [onEdit, turnId, turn.content]);
1423
+ const handleEditCancel = useCallback(() => {
1424
+ setIsEditing(false);
1425
+ setDraftContent("");
1426
+ }, []);
1427
+ const handleEditSave = useCallback(() => {
1428
+ if (!onEdit || !turnId) return;
1429
+ const newContent = draftContent;
1430
+ if (!newContent.trim()) return;
1431
+ if (newContent === turn.content) {
1432
+ setIsEditing(false);
1433
+ return;
840
1434
  }
1435
+ onEdit(turnId, newContent);
1436
+ setIsEditing(false);
1437
+ }, [onEdit, turnId, draftContent, turn.content]);
1438
+ const handleNavigate = useCallback(
1439
+ (direction) => {
1440
+ if (selectedImageIndex === null) return;
1441
+ if (direction === "prev" && selectedImageIndex > 0) {
1442
+ setSelectedImageIndex(selectedImageIndex - 1);
1443
+ } else if (direction === "next" && selectedImageIndex < imageAttachments.length - 1) {
1444
+ setSelectedImageIndex(selectedImageIndex + 1);
1445
+ }
1446
+ },
1447
+ [selectedImageIndex, imageAttachments.length]
1448
+ );
1449
+ const currentAttachment = selectedImageIndex !== null ? imageAttachments[selectedImageIndex] : null;
1450
+ const timestamp = formatDistanceToNow(new Date(turn.createdAt), { addSuffix: true });
1451
+ const avatarSlotProps = { initials };
1452
+ const contentSlotProps = { content: turn.content };
1453
+ const attachmentsSlotProps = {
1454
+ attachments: imageAttachments,
1455
+ onView: (index) => setSelectedImageIndex(index)
841
1456
  };
842
- return /* @__PURE__ */ jsxs("div", { className: "flex gap-3 justify-end group", children: [
843
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col items-end max-w-[75%]", children: [
844
- message.attachments && message.attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-2 w-full", children: /* @__PURE__ */ jsx(
845
- AttachmentGrid,
846
- {
847
- attachments: message.attachments,
848
- onView: (index) => setSelectedImageIndex(index)
849
- }
850
- ) }),
851
- message.content && /* @__PURE__ */ jsx("div", { className: "bg-primary-600 text-white rounded-2xl rounded-br-sm px-4 py-3", children: /* @__PURE__ */ jsx("div", { className: "text-[15px] whitespace-pre-wrap break-words leading-relaxed", children: message.content }) }),
852
- /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 mt-2 opacity-0 group-hover:opacity-100 transition-opacity duration-150", children: [
853
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500 mr-1", children: formatDistanceToNow(new Date(message.createdAt), { addSuffix: true }) }),
1457
+ const actionsSlotProps = {
1458
+ onCopy: handleCopyClick,
1459
+ onEdit: onEdit && turnId ? handleEditClick : void 0,
1460
+ timestamp,
1461
+ canCopy: true,
1462
+ canEdit: !!onEdit && !!turnId
1463
+ };
1464
+ const renderSlot = (slot, props, defaultContent) => {
1465
+ if (slot === void 0) return defaultContent;
1466
+ if (typeof slot === "function") return slot(props);
1467
+ return slot;
1468
+ };
1469
+ return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
1470
+ /* @__PURE__ */ jsxs("div", { className: classes.wrapper, children: [
1471
+ imageAttachments.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.attachments, children: renderSlot(
1472
+ slots?.attachments,
1473
+ attachmentsSlotProps,
854
1474
  /* @__PURE__ */ jsx(
855
- "button",
1475
+ AttachmentGrid_default,
856
1476
  {
857
- onClick: handleCopyClick,
858
- className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
859
- "aria-label": "Copy message",
860
- title: "Copy",
861
- children: /* @__PURE__ */ jsx(Copy, { size: 14, weight: "regular" })
1477
+ attachments: imageAttachments,
1478
+ onView: (index) => setSelectedImageIndex(index)
862
1479
  }
863
- ),
864
- handleEdit && /* @__PURE__ */ jsx(
865
- "button",
1480
+ )
1481
+ ) }),
1482
+ turn.content && /* @__PURE__ */ jsx("div", { className: classes.bubble, children: /* @__PURE__ */ jsx("div", { className: classes.content, children: isEditing ? /* @__PURE__ */ jsxs("div", { className: "space-y-2", children: [
1483
+ /* @__PURE__ */ jsx(
1484
+ "textarea",
866
1485
  {
867
- onClick: handleEditClick,
868
- className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
869
- "aria-label": "Edit message",
870
- title: "Edit",
871
- children: /* @__PURE__ */ jsx(PencilSimple, { size: 14, weight: "regular" })
1486
+ value: draftContent,
1487
+ onChange: (e) => setDraftContent(e.target.value),
1488
+ className: "w-full min-h-[80px] resize-y rounded-lg px-3 py-2 bg-white/10 text-white placeholder-white/70 outline-none focus:ring-2 focus:ring-white/30",
1489
+ "aria-label": "Edit message"
872
1490
  }
873
- )
874
- ] })
1491
+ ),
1492
+ /* @__PURE__ */ jsxs("div", { className: "flex justify-end gap-2", children: [
1493
+ /* @__PURE__ */ jsx(
1494
+ "button",
1495
+ {
1496
+ type: "button",
1497
+ onClick: handleEditCancel,
1498
+ className: "px-3 py-1.5 rounded-lg bg-white/10 hover:bg-white/15 transition-colors text-sm font-medium",
1499
+ children: "Cancel"
1500
+ }
1501
+ ),
1502
+ /* @__PURE__ */ jsx(
1503
+ "button",
1504
+ {
1505
+ type: "button",
1506
+ onClick: handleEditSave,
1507
+ className: "px-3 py-1.5 rounded-lg bg-white/20 hover:bg-white/25 transition-colors text-sm font-medium disabled:opacity-50 disabled:cursor-not-allowed",
1508
+ disabled: !draftContent.trim() || draftContent === turn.content,
1509
+ children: "Save"
1510
+ }
1511
+ )
1512
+ ] })
1513
+ ] }) : renderSlot(slots?.content, contentSlotProps, turn.content) }) }),
1514
+ !hideActions && /* @__PURE__ */ jsx("div", { className: classes.actions, children: renderSlot(
1515
+ slots?.actions,
1516
+ actionsSlotProps,
1517
+ /* @__PURE__ */ jsxs(Fragment, { children: [
1518
+ !hideTimestamp && /* @__PURE__ */ jsx("span", { className: classes.timestamp, children: timestamp }),
1519
+ /* @__PURE__ */ jsx(
1520
+ "button",
1521
+ {
1522
+ onClick: handleCopyClick,
1523
+ className: classes.actionButton,
1524
+ "aria-label": "Copy message",
1525
+ title: "Copy",
1526
+ children: /* @__PURE__ */ jsx(Copy, { size: 14, weight: "regular" })
1527
+ }
1528
+ ),
1529
+ onEdit && turnId && /* @__PURE__ */ jsx(
1530
+ "button",
1531
+ {
1532
+ onClick: handleEditClick,
1533
+ className: classes.actionButton,
1534
+ "aria-label": "Edit message",
1535
+ title: "Edit",
1536
+ disabled: isEditing,
1537
+ children: /* @__PURE__ */ jsx(PencilSimple, { size: 14, weight: "regular" })
1538
+ }
1539
+ )
1540
+ ] })
1541
+ ) })
875
1542
  ] }),
876
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-sm", children: "U" }),
877
- selectedImageIndex !== null && message.attachments && /* @__PURE__ */ jsx(
878
- ImageModal,
1543
+ !hideAvatar && /* @__PURE__ */ jsx("div", { className: classes.avatar, children: renderSlot(slots?.avatar, avatarSlotProps, initials) }),
1544
+ currentAttachment && /* @__PURE__ */ jsx(
1545
+ ImageModal_default,
879
1546
  {
880
- images: message.attachments,
881
- initialIndex: selectedImageIndex,
882
- onClose: () => setSelectedImageIndex(null)
1547
+ isOpen: selectedImageIndex !== null,
1548
+ onClose: () => setSelectedImageIndex(null),
1549
+ attachment: currentAttachment,
1550
+ allAttachments: imageAttachments,
1551
+ currentIndex: selectedImageIndex ?? 0,
1552
+ onNavigate: handleNavigate
883
1553
  }
884
1554
  )
885
1555
  ] });
886
1556
  }
1557
+ function UserTurnView({
1558
+ turn,
1559
+ slots,
1560
+ classNames,
1561
+ initials = "U",
1562
+ hideAvatar,
1563
+ hideActions,
1564
+ hideTimestamp
1565
+ }) {
1566
+ const { handleEdit, handleCopy } = useChat();
1567
+ return /* @__PURE__ */ jsx(
1568
+ UserMessage,
1569
+ {
1570
+ turn: turn.userTurn,
1571
+ turnId: turn.id,
1572
+ initials,
1573
+ slots,
1574
+ classNames,
1575
+ onCopy: handleCopy,
1576
+ onEdit: handleEdit,
1577
+ hideAvatar,
1578
+ hideActions,
1579
+ hideTimestamp
1580
+ }
1581
+ );
1582
+ }
1583
+ function toBase64(str) {
1584
+ const bytes = new TextEncoder().encode(str);
1585
+ let binary = "";
1586
+ for (let i = 0; i < bytes.length; i++) {
1587
+ binary += String.fromCharCode(bytes[i]);
1588
+ }
1589
+ return btoa(binary);
1590
+ }
887
1591
  function CodeOutputsPanel({ outputs }) {
888
1592
  if (!outputs || outputs.length === 0) return null;
889
1593
  return /* @__PURE__ */ jsxs("div", { className: "mb-2 p-3 bg-gray-50 dark:bg-gray-900/50 rounded-lg border border-gray-200 dark:border-gray-700", children: [
890
1594
  /* @__PURE__ */ jsx("div", { className: "text-xs font-semibold text-gray-600 dark:text-gray-400 mb-2", children: "Code Output" }),
891
1595
  /* @__PURE__ */ jsx("div", { className: "space-y-2", children: outputs.map((output, index) => /* @__PURE__ */ jsxs("div", { children: [
892
- output.type === "image" && /* @__PURE__ */ jsx(
893
- "img",
894
- {
895
- src: output.content.preview,
896
- alt: "Code output",
897
- className: "max-w-full rounded border border-gray-300 dark:border-gray-600"
898
- }
899
- ),
900
- output.type === "text" && /* @__PURE__ */ jsx("pre", { className: "text-xs bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsx("code", { className: "text-gray-900 dark:text-gray-100", children: output.content }) }),
901
- output.type === "error" && /* @__PURE__ */ jsx("div", { className: "text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 p-2 rounded border border-red-200 dark:border-red-800", children: output.content })
1596
+ output.type === "image" && /* @__PURE__ */ jsxs("div", { className: "relative group", children: [
1597
+ /* @__PURE__ */ jsx(
1598
+ "img",
1599
+ {
1600
+ src: output.content.startsWith("data:") ? output.content : `data:${output.mimeType || "image/png"};base64,${output.content}`,
1601
+ alt: output.filename || "Code output",
1602
+ className: "max-w-full rounded border border-gray-300 dark:border-gray-600"
1603
+ }
1604
+ ),
1605
+ output.filename && /* @__PURE__ */ jsx("div", { className: "absolute bottom-0 left-0 right-0 bg-gradient-to-t from-black/60 to-transparent p-2 opacity-0 group-hover:opacity-100 transition-opacity", children: /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between text-white text-xs", children: [
1606
+ /* @__PURE__ */ jsx("span", { className: "truncate", children: output.filename }),
1607
+ output.sizeBytes && /* @__PURE__ */ jsx("span", { className: "text-gray-300", children: formatFileSize(output.sizeBytes) })
1608
+ ] }) })
1609
+ ] }),
1610
+ output.type === "text" && /* @__PURE__ */ jsxs("div", { children: [
1611
+ /* @__PURE__ */ jsx("pre", { className: "text-xs bg-white dark:bg-gray-800 p-2 rounded overflow-x-auto border border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsx("code", { className: "text-gray-900 dark:text-gray-100", children: output.content }) }),
1612
+ output.filename && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 mt-1 text-xs", children: [
1613
+ /* @__PURE__ */ jsxs(
1614
+ "a",
1615
+ {
1616
+ href: `data:${output.mimeType || "text/plain"};base64,${toBase64(output.content)}`,
1617
+ download: output.filename,
1618
+ className: "flex items-center gap-1 text-blue-600 dark:text-blue-400 hover:underline",
1619
+ children: [
1620
+ /* @__PURE__ */ jsx(Download, { size: 12, weight: "bold" }),
1621
+ output.filename
1622
+ ]
1623
+ }
1624
+ ),
1625
+ output.sizeBytes && /* @__PURE__ */ jsxs("span", { className: "text-gray-500 dark:text-gray-400", children: [
1626
+ "(",
1627
+ formatFileSize(output.sizeBytes),
1628
+ ")"
1629
+ ] })
1630
+ ] })
1631
+ ] }),
1632
+ output.type === "error" && /* @__PURE__ */ jsxs("div", { className: "text-xs text-red-600 dark:text-red-400 bg-red-50 dark:bg-red-900/20 p-2 rounded border border-red-200 dark:border-red-800", children: [
1633
+ /* @__PURE__ */ jsx("div", { className: "font-semibold mb-1", children: "Error" }),
1634
+ /* @__PURE__ */ jsx("pre", { className: "whitespace-pre-wrap", children: output.content })
1635
+ ] })
902
1636
  ] }, index)) })
903
1637
  ] });
904
1638
  }
1639
+ var CodeOutputsPanel_default = CodeOutputsPanel;
905
1640
  function StreamingCursor() {
906
1641
  return /* @__PURE__ */ jsx(
907
1642
  "span",
@@ -911,63 +1646,97 @@ function StreamingCursor() {
911
1646
  }
912
1647
  );
913
1648
  }
914
- var COLORS = ["#3b82f6", "#10b981", "#f59e0b", "#ef4444", "#8b5cf6", "#ec4899"];
1649
+ var StreamingCursor_default = StreamingCursor;
1650
+ var DEFAULT_COLORS = ["#008FFB", "#00E396", "#FEB019", "#FF4560", "#775DD0"];
915
1651
  function ChartCard({ chartData }) {
916
- const { type, title, data, xAxisKey = "name", yAxisKey = "value" } = chartData;
917
- const renderChart = () => {
918
- switch (type) {
919
- case "bar":
920
- return /* @__PURE__ */ jsxs(BarChart, { data, children: [
921
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
922
- /* @__PURE__ */ jsx(XAxis, { dataKey: xAxisKey }),
923
- /* @__PURE__ */ jsx(YAxis, {}),
924
- /* @__PURE__ */ jsx(Tooltip, {}),
925
- /* @__PURE__ */ jsx(Legend, {}),
926
- /* @__PURE__ */ jsx(Bar, { dataKey: yAxisKey, fill: "#3b82f6" })
927
- ] });
928
- case "line":
929
- return /* @__PURE__ */ jsxs(LineChart, { data, children: [
930
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
931
- /* @__PURE__ */ jsx(XAxis, { dataKey: xAxisKey }),
932
- /* @__PURE__ */ jsx(YAxis, {}),
933
- /* @__PURE__ */ jsx(Tooltip, {}),
934
- /* @__PURE__ */ jsx(Legend, {}),
935
- /* @__PURE__ */ jsx(Line, { type: "monotone", dataKey: yAxisKey, stroke: "#3b82f6" })
936
- ] });
937
- case "pie":
938
- return /* @__PURE__ */ jsxs(PieChart, { children: [
939
- /* @__PURE__ */ jsx(
940
- Pie,
941
- {
942
- data,
943
- dataKey: yAxisKey,
944
- nameKey: xAxisKey,
945
- cx: "50%",
946
- cy: "50%",
947
- outerRadius: 80,
948
- label: true,
949
- children: data.map((_entry, index) => /* @__PURE__ */ jsx(Cell, { fill: COLORS[index % COLORS.length] }, `cell-${index}`))
950
- }
951
- ),
952
- /* @__PURE__ */ jsx(Tooltip, {}),
953
- /* @__PURE__ */ jsx(Legend, {})
954
- ] });
955
- case "area":
956
- return /* @__PURE__ */ jsxs(AreaChart, { data, children: [
957
- /* @__PURE__ */ jsx(CartesianGrid, { strokeDasharray: "3 3" }),
958
- /* @__PURE__ */ jsx(XAxis, { dataKey: xAxisKey }),
959
- /* @__PURE__ */ jsx(YAxis, {}),
960
- /* @__PURE__ */ jsx(Tooltip, {}),
961
- /* @__PURE__ */ jsx(Legend, {}),
962
- /* @__PURE__ */ jsx(Area, { type: "monotone", dataKey: yAxisKey, stroke: "#3b82f6", fill: "#3b82f6" })
963
- ] });
964
- default:
965
- return /* @__PURE__ */ jsx("div", { children: "Unsupported chart type" });
1652
+ const chartId = useId().replace(/:/g, "_");
1653
+ const [isExporting, setIsExporting] = useState(false);
1654
+ const { chartType, title, series, labels, colors, height = 350 } = chartData;
1655
+ const hasValidData = series && series.length > 0 && series.some((s) => s.data && s.data.length > 0);
1656
+ if (!hasValidData) {
1657
+ return /* @__PURE__ */ jsx("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-[var(--bichat-border)] p-4 my-2 shadow-sm w-full max-w-full", children: /* @__PURE__ */ jsxs("p", { className: "text-gray-500 dark:text-gray-400 text-sm", children: [
1658
+ title && /* @__PURE__ */ jsxs("span", { className: "font-medium", children: [
1659
+ title,
1660
+ ": "
1661
+ ] }),
1662
+ "No data available for chart visualization."
1663
+ ] }) });
1664
+ }
1665
+ const apexSeries = chartType === "pie" || chartType === "donut" ? series[0]?.data ?? [] : series.map((s) => ({ name: s.name, data: s.data }));
1666
+ const xaxisConfig = chartType !== "pie" && chartType !== "donut" ? { categories: (labels ?? []).filter((l) => l !== null) } : {};
1667
+ const labelsConfig = chartType === "pie" || chartType === "donut" ? (labels ?? []).filter((l) => l !== null) : [];
1668
+ const options = {
1669
+ chart: {
1670
+ id: chartId,
1671
+ type: chartType,
1672
+ toolbar: { show: false },
1673
+ animations: { enabled: false }
1674
+ },
1675
+ title: {
1676
+ text: title,
1677
+ align: "left",
1678
+ style: { fontSize: "14px", fontWeight: 600 }
1679
+ },
1680
+ colors: colors?.length ? colors : DEFAULT_COLORS,
1681
+ xaxis: xaxisConfig,
1682
+ labels: labelsConfig,
1683
+ legend: { position: "bottom", horizontalAlign: "center" },
1684
+ dataLabels: { enabled: chartType === "pie" || chartType === "donut" },
1685
+ stroke: {
1686
+ curve: "smooth",
1687
+ width: chartType === "line" || chartType === "area" ? 2 : 0
1688
+ },
1689
+ fill: { opacity: chartType === "area" ? 0.4 : 1 }
1690
+ };
1691
+ const handleExportPNG = async () => {
1692
+ setIsExporting(true);
1693
+ try {
1694
+ const chart = ApexCharts.getChartByID(chartId);
1695
+ if (!chart) {
1696
+ console.error("Chart instance not available");
1697
+ setIsExporting(false);
1698
+ return;
1699
+ }
1700
+ const result = await chart.dataURI({ scale: 2 });
1701
+ if (!("imgURI" in result)) {
1702
+ console.error("Unexpected dataURI result format");
1703
+ return;
1704
+ }
1705
+ const link = document.createElement("a");
1706
+ link.href = result.imgURI;
1707
+ link.download = `${title.replace(/[^a-z0-9]/gi, "_").toLowerCase()}_chart.png`;
1708
+ link.click();
1709
+ } catch (error) {
1710
+ console.error("Failed to export chart:", error);
1711
+ } finally {
1712
+ setIsExporting(false);
966
1713
  }
967
1714
  };
968
- return /* @__PURE__ */ jsxs("div", { className: "border border-[var(--bichat-border)] rounded-lg p-4 bg-white", children: [
969
- title && /* @__PURE__ */ jsx("h3", { className: "text-lg font-semibold mb-4", children: title }),
970
- /* @__PURE__ */ jsx(ResponsiveContainer, { width: "100%", height: 300, children: renderChart() })
1715
+ const chartWidth = 600;
1716
+ return /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 rounded-lg border border-[var(--bichat-border)] p-4 my-2 shadow-sm w-full max-w-[632px] min-w-0 overflow-hidden", children: [
1717
+ /* @__PURE__ */ jsx("div", { className: "w-full min-w-0", children: /* @__PURE__ */ jsx(
1718
+ ReactApexChart,
1719
+ {
1720
+ options,
1721
+ series: apexSeries,
1722
+ type: chartType,
1723
+ width: chartWidth,
1724
+ height
1725
+ }
1726
+ ) }),
1727
+ /* @__PURE__ */ jsx("div", { className: "flex justify-end mt-2", children: /* @__PURE__ */ jsx(
1728
+ "button",
1729
+ {
1730
+ onClick: handleExportPNG,
1731
+ disabled: isExporting,
1732
+ className: "text-sm text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-200 flex items-center gap-1 transition-colors",
1733
+ title: "Download chart as PNG",
1734
+ children: isExporting ? /* @__PURE__ */ jsx("span", { children: "Exporting..." }) : /* @__PURE__ */ jsxs(Fragment, { children: [
1735
+ /* @__PURE__ */ jsx(Download, { className: "w-4 h-4" }),
1736
+ "Download PNG"
1737
+ ] })
1738
+ }
1739
+ ) })
971
1740
  ] });
972
1741
  }
973
1742
  function SourcesPanel({ citations }) {
@@ -981,13 +1750,13 @@ function SourcesPanel({ citations }) {
981
1750
  {
982
1751
  type: "button",
983
1752
  onClick: () => setExpanded(!expanded),
984
- className: "flex items-center gap-2 text-sm text-gray-600 hover:text-gray-800 transition-colors",
1753
+ className: "flex items-center gap-2 text-sm text-gray-600 dark:text-gray-400 hover:text-gray-800 dark:hover:text-gray-200 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 rounded-md p-1 -m-1",
985
1754
  "aria-expanded": expanded,
986
1755
  children: [
987
1756
  /* @__PURE__ */ jsx(
988
1757
  "svg",
989
1758
  {
990
- className: `w-4 h-4 transition-transform ${expanded ? "rotate-90" : ""}`,
1759
+ className: `w-4 h-4 transition-transform duration-150 ${expanded ? "rotate-90" : ""}`,
991
1760
  fill: "none",
992
1761
  stroke: "currentColor",
993
1762
  viewBox: "0 0 24 24",
@@ -1013,22 +1782,22 @@ function SourcesPanel({ citations }) {
1013
1782
  expanded && /* @__PURE__ */ jsx("div", { className: "mt-2 space-y-2", children: citations.map((citation, index) => /* @__PURE__ */ jsx(
1014
1783
  "div",
1015
1784
  {
1016
- className: "p-3 bg-gray-50 rounded-lg text-sm",
1785
+ className: "p-3 bg-gray-50 dark:bg-gray-800/50 rounded-lg text-sm",
1017
1786
  children: /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2", children: [
1018
1787
  /* @__PURE__ */ jsx("span", { className: "flex-shrink-0 w-5 h-5 bg-[var(--bichat-primary)] text-white rounded-full flex items-center justify-center text-xs", children: index + 1 }),
1019
1788
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1020
- /* @__PURE__ */ jsx("div", { className: "font-medium text-gray-900", children: citation.source }),
1789
+ /* @__PURE__ */ jsx("div", { className: "font-medium text-gray-900 dark:text-gray-100", children: citation.title || citation.source }),
1021
1790
  citation.url && /* @__PURE__ */ jsx(
1022
1791
  "a",
1023
1792
  {
1024
1793
  href: citation.url,
1025
1794
  target: "_blank",
1026
1795
  rel: "noopener noreferrer",
1027
- className: "text-[var(--bichat-primary)] hover:underline",
1796
+ className: "text-[var(--bichat-primary)] hover:underline focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 rounded",
1028
1797
  children: citation.url
1029
1798
  }
1030
1799
  ),
1031
- citation.excerpt && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-gray-600 italic", children: [
1800
+ citation.excerpt && /* @__PURE__ */ jsxs("div", { className: "mt-1 text-gray-600 dark:text-gray-400 italic", children: [
1032
1801
  '"',
1033
1802
  citation.excerpt,
1034
1803
  '"'
@@ -1098,218 +1867,516 @@ function DownloadCard({ artifact }) {
1098
1867
  );
1099
1868
  }
1100
1869
  function InlineQuestionForm({ pendingQuestion }) {
1101
- const { handleSubmitQuestionAnswers, handleCancelPendingQuestion } = useChat();
1870
+ const { handleSubmitQuestionAnswers, handleCancelPendingQuestion, loading } = useChat();
1871
+ const [currentStep, setCurrentStep] = useState(0);
1102
1872
  const [answers, setAnswers] = useState({});
1103
- const [textInput, setTextInput] = useState("");
1104
- const handleSubmit = (e) => {
1105
- e.preventDefault();
1106
- if (pendingQuestion.type === "MULTIPLE_CHOICE" && !answers.choice) {
1107
- return;
1873
+ const [otherTexts, setOtherTexts] = useState({});
1874
+ const questions = pendingQuestion.questions;
1875
+ const currentQuestion = questions[currentStep];
1876
+ const isLastStep = currentStep === questions.length - 1;
1877
+ const isFirstStep = currentStep === 0;
1878
+ const totalSteps = questions.length;
1879
+ const currentAnswer = answers[currentQuestion?.id];
1880
+ const currentOtherText = otherTexts[currentQuestion?.id] || "";
1881
+ const handleOptionChange = useCallback(
1882
+ (optionLabel, checked) => {
1883
+ if (!currentQuestion) return;
1884
+ const questionId = currentQuestion.id;
1885
+ const existingAnswer = answers[questionId] || { options: [] };
1886
+ const isOtherOption = optionLabel === "__other__";
1887
+ const isMultiSelect2 = currentQuestion.type === "MULTIPLE_CHOICE";
1888
+ if (isOtherOption) {
1889
+ setAnswers({
1890
+ ...answers,
1891
+ [questionId]: {
1892
+ options: [],
1893
+ customText: checked ? currentOtherText : void 0
1894
+ }
1895
+ });
1896
+ return;
1897
+ }
1898
+ let newOptions;
1899
+ if (isMultiSelect2) {
1900
+ if (!checked) {
1901
+ newOptions = existingAnswer.options.filter((o) => o !== optionLabel);
1902
+ } else if (existingAnswer.options.includes(optionLabel)) {
1903
+ newOptions = existingAnswer.options;
1904
+ } else {
1905
+ newOptions = [...existingAnswer.options, optionLabel];
1906
+ }
1907
+ } else {
1908
+ newOptions = checked ? [optionLabel] : [];
1909
+ }
1910
+ setAnswers({
1911
+ ...answers,
1912
+ [questionId]: {
1913
+ options: newOptions,
1914
+ customText: void 0
1915
+ }
1916
+ });
1917
+ },
1918
+ [currentQuestion, answers, currentOtherText]
1919
+ );
1920
+ const handleOtherTextChange = useCallback(
1921
+ (text) => {
1922
+ if (!currentQuestion) return;
1923
+ const questionId = currentQuestion.id;
1924
+ setOtherTexts({ ...otherTexts, [questionId]: text });
1925
+ setAnswers({
1926
+ ...answers,
1927
+ [questionId]: {
1928
+ options: [],
1929
+ customText: text
1930
+ }
1931
+ });
1932
+ },
1933
+ [currentQuestion, answers, otherTexts]
1934
+ );
1935
+ const isCurrentAnswerValid = () => {
1936
+ if (!currentQuestion) return false;
1937
+ const answer = answers[currentQuestion.id];
1938
+ const required = currentQuestion.required ?? true;
1939
+ if (!answer) return !required;
1940
+ const hasOptionSelection = answer.options.length > 0;
1941
+ const hasOtherSelected = answer.customText !== void 0;
1942
+ const hasOtherText = (answer.customText?.trim().length ?? 0) > 0;
1943
+ if (!hasOptionSelection && !hasOtherSelected) {
1944
+ return !required;
1108
1945
  }
1109
- if (pendingQuestion.type === "FREE_TEXT" && !textInput.trim()) {
1110
- return;
1946
+ if (hasOptionSelection) {
1947
+ return true;
1948
+ }
1949
+ return !required || hasOtherText;
1950
+ };
1951
+ const handleNext = () => {
1952
+ if (!isCurrentAnswerValid()) return;
1953
+ if (isLastStep) {
1954
+ handleSubmitQuestionAnswers(answers);
1955
+ } else {
1956
+ setCurrentStep(currentStep + 1);
1957
+ }
1958
+ };
1959
+ const handleBack = () => {
1960
+ if (!isFirstStep) {
1961
+ setCurrentStep(currentStep - 1);
1111
1962
  }
1112
- const finalAnswers = pendingQuestion.type === "MULTIPLE_CHOICE" ? answers : { answer: textInput };
1113
- handleSubmitQuestionAnswers(finalAnswers);
1114
1963
  };
1115
- return /* @__PURE__ */ jsx("div", { className: "border border-[var(--bichat-border)] rounded-lg p-4 bg-yellow-50", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
1964
+ const handleSubmit = (e) => {
1965
+ e.preventDefault();
1966
+ handleNext();
1967
+ };
1968
+ if (!currentQuestion) return null;
1969
+ const isMultiSelect = currentQuestion.type === "MULTIPLE_CHOICE";
1970
+ const options = currentQuestion.options || [];
1971
+ const isOtherSelected = currentAnswer?.customText !== void 0;
1972
+ return /* @__PURE__ */ jsx("div", { className: "border border-amber-200 dark:border-amber-800 rounded-lg p-4 bg-amber-50 dark:bg-amber-900/20", children: /* @__PURE__ */ jsxs("form", { onSubmit: handleSubmit, children: [
1116
1973
  /* @__PURE__ */ jsxs("div", { className: "flex items-start gap-2 mb-4", children: [
1117
1974
  /* @__PURE__ */ jsx(
1118
- "svg",
1975
+ Question,
1119
1976
  {
1120
- className: "w-5 h-5 text-yellow-600 flex-shrink-0 mt-0.5",
1121
- fill: "currentColor",
1122
- viewBox: "0 0 20 20",
1123
- children: /* @__PURE__ */ jsx(
1124
- "path",
1125
- {
1126
- fillRule: "evenodd",
1127
- d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-8-3a1 1 0 00-.867.5 1 1 0 11-1.731-1A3 3 0 0113 8a3.001 3.001 0 01-2 2.83V11a1 1 0 11-2 0v-1a1 1 0 011-1 1 1 0 100-2zm0 8a1 1 0 100-2 1 1 0 000 2z",
1128
- clipRule: "evenodd"
1129
- }
1130
- )
1977
+ className: "w-5 h-5 text-amber-600 dark:text-amber-400 flex-shrink-0 mt-0.5",
1978
+ weight: "fill"
1131
1979
  }
1132
1980
  ),
1133
1981
  /* @__PURE__ */ jsxs("div", { className: "flex-1", children: [
1134
- /* @__PURE__ */ jsx("h4", { className: "font-medium text-gray-900 mb-2", children: "Question from AI" }),
1135
- /* @__PURE__ */ jsx("p", { className: "text-gray-700", children: pendingQuestion.question })
1982
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between mb-1", children: [
1983
+ /* @__PURE__ */ jsx("h4", { className: "font-medium text-gray-900 dark:text-gray-100", children: "Question from AI" }),
1984
+ totalSteps > 1 && /* @__PURE__ */ jsxs("span", { className: "text-xs text-gray-500 dark:text-gray-400", children: [
1985
+ currentStep + 1,
1986
+ " of ",
1987
+ totalSteps
1988
+ ] })
1989
+ ] }),
1990
+ /* @__PURE__ */ jsx("p", { className: "text-gray-700 dark:text-gray-300", children: currentQuestion.text })
1136
1991
  ] })
1137
1992
  ] }),
1138
- pendingQuestion.type === "MULTIPLE_CHOICE" && pendingQuestion.options && /* @__PURE__ */ jsx("div", { className: "space-y-2 mb-4", children: pendingQuestion.options.map((option, index) => /* @__PURE__ */ jsxs(
1139
- "label",
1993
+ totalSteps > 1 && /* @__PURE__ */ jsx("div", { className: "flex gap-1 mb-4", children: questions.map((_, index) => /* @__PURE__ */ jsx(
1994
+ "div",
1140
1995
  {
1141
- className: "flex items-center gap-2 p-2 hover:bg-yellow-100 rounded cursor-pointer",
1142
- children: [
1143
- /* @__PURE__ */ jsx(
1144
- "input",
1145
- {
1146
- type: "radio",
1147
- name: "choice",
1148
- value: option,
1149
- checked: answers.choice === option,
1150
- onChange: (e) => setAnswers({ ...answers, choice: e.target.value }),
1151
- className: "w-4 h-4 text-[var(--bichat-primary)]"
1152
- }
1153
- ),
1154
- /* @__PURE__ */ jsx("span", { className: "text-gray-900", children: option })
1155
- ]
1996
+ className: `h-1 flex-1 rounded-full transition-colors ${index <= currentStep ? "bg-amber-500 dark:bg-amber-400" : "bg-gray-200 dark:bg-gray-700"}`
1156
1997
  },
1157
1998
  index
1158
1999
  )) }),
1159
- pendingQuestion.type === "FREE_TEXT" && /* @__PURE__ */ jsx("div", { className: "mb-4", children: /* @__PURE__ */ jsx(
1160
- "textarea",
1161
- {
1162
- value: textInput,
1163
- onChange: (e) => setTextInput(e.target.value),
1164
- placeholder: "Type your answer...",
1165
- className: "w-full px-3 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-[var(--bichat-primary)] focus:border-transparent resize-none",
1166
- rows: 3
1167
- }
1168
- ) }),
1169
- /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
1170
- /* @__PURE__ */ jsx(
1171
- "button",
2000
+ /* @__PURE__ */ jsxs("div", { className: "space-y-2 mb-4", children: [
2001
+ options.map((option) => /* @__PURE__ */ jsxs(
2002
+ "label",
2003
+ {
2004
+ className: "flex items-center gap-2 p-2 hover:bg-amber-100 dark:hover:bg-amber-900/30 rounded cursor-pointer",
2005
+ children: [
2006
+ /* @__PURE__ */ jsx(
2007
+ "input",
2008
+ {
2009
+ type: isMultiSelect ? "checkbox" : "radio",
2010
+ name: `question-${currentQuestion.id}`,
2011
+ value: option.value,
2012
+ checked: currentAnswer?.options.includes(option.label) || false,
2013
+ onChange: (e) => handleOptionChange(option.label, e.target.checked),
2014
+ className: "w-4 h-4 text-amber-600 focus:ring-amber-500"
2015
+ }
2016
+ ),
2017
+ /* @__PURE__ */ jsx("span", { className: "text-gray-900 dark:text-gray-100", children: option.label })
2018
+ ]
2019
+ },
2020
+ option.id
2021
+ )),
2022
+ /* @__PURE__ */ jsxs("label", { className: "flex items-center gap-2 p-2 hover:bg-amber-100 dark:hover:bg-amber-900/30 rounded cursor-pointer", children: [
2023
+ /* @__PURE__ */ jsx(
2024
+ "input",
2025
+ {
2026
+ type: isMultiSelect ? "checkbox" : "radio",
2027
+ name: `question-${currentQuestion.id}`,
2028
+ value: "__other__",
2029
+ checked: isOtherSelected,
2030
+ onChange: (e) => handleOptionChange("__other__", e.target.checked),
2031
+ className: "w-4 h-4 text-amber-600 focus:ring-amber-500"
2032
+ }
2033
+ ),
2034
+ /* @__PURE__ */ jsx("span", { className: "text-gray-900 dark:text-gray-100", children: "Other" })
2035
+ ] }),
2036
+ isOtherSelected && /* @__PURE__ */ jsx("div", { className: "ml-6", children: /* @__PURE__ */ jsx(
2037
+ "input",
1172
2038
  {
1173
- type: "submit",
1174
- className: "px-4 py-2 bg-[var(--bichat-primary)] text-white rounded-lg hover:opacity-90 transition-opacity",
1175
- children: "Submit Answer"
2039
+ type: "text",
2040
+ value: currentOtherText,
2041
+ onChange: (e) => handleOtherTextChange(e.target.value),
2042
+ placeholder: "Please specify...",
2043
+ className: "w-full px-3 py-2 border border-gray-300 dark:border-gray-600 rounded-lg bg-white dark:bg-gray-800 text-gray-900 dark:text-gray-100 focus:ring-2 focus:ring-amber-500 focus:border-transparent"
1176
2044
  }
1177
- ),
1178
- /* @__PURE__ */ jsx(
2045
+ ) })
2046
+ ] }),
2047
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between", children: [
2048
+ /* @__PURE__ */ jsx("div", { className: "flex gap-2", children: !isFirstStep && /* @__PURE__ */ jsxs(
1179
2049
  "button",
1180
2050
  {
1181
2051
  type: "button",
1182
- onClick: handleCancelPendingQuestion,
1183
- className: "px-4 py-2 border border-gray-300 rounded-lg hover:bg-gray-50 transition-colors",
1184
- children: "Cancel"
2052
+ onClick: handleBack,
2053
+ className: "flex items-center gap-1 px-3 py-2 text-gray-600 dark:text-gray-400 hover:text-gray-900 dark:hover:text-gray-100 transition-colors",
2054
+ children: [
2055
+ /* @__PURE__ */ jsx(CaretLeft, { size: 16, weight: "bold" }),
2056
+ "Back"
2057
+ ]
1185
2058
  }
1186
- )
2059
+ ) }),
2060
+ /* @__PURE__ */ jsxs("div", { className: "flex gap-2", children: [
2061
+ /* @__PURE__ */ jsx(
2062
+ "button",
2063
+ {
2064
+ type: "button",
2065
+ onClick: handleCancelPendingQuestion,
2066
+ disabled: loading,
2067
+ className: "px-4 py-2 border border-gray-300 dark:border-gray-600 text-gray-700 dark:text-gray-300 rounded-lg hover:bg-gray-50 dark:hover:bg-gray-800 transition-colors disabled:opacity-50",
2068
+ children: "Cancel"
2069
+ }
2070
+ ),
2071
+ /* @__PURE__ */ jsxs(
2072
+ "button",
2073
+ {
2074
+ type: "submit",
2075
+ disabled: loading || !isCurrentAnswerValid(),
2076
+ className: "flex items-center gap-1 px-4 py-2 bg-amber-600 text-white rounded-lg hover:bg-amber-700 transition-colors disabled:opacity-50 disabled:cursor-not-allowed",
2077
+ children: [
2078
+ isLastStep ? "Submit" : "Next",
2079
+ !isLastStep && /* @__PURE__ */ jsx(CaretRight, { size: 16, weight: "bold" })
2080
+ ]
2081
+ }
2082
+ )
2083
+ ] })
1187
2084
  ] })
1188
2085
  ] }) });
1189
2086
  }
1190
2087
  var MarkdownRenderer2 = lazy(
1191
2088
  () => Promise.resolve().then(() => (init_MarkdownRenderer(), MarkdownRenderer_exports)).then((module) => ({ default: module.MarkdownRenderer }))
1192
2089
  );
1193
- function AssistantTurnView({ message }) {
1194
- const { handleCopy, handleRegenerate, pendingQuestion } = useChat();
2090
+ var defaultClassNames2 = {
2091
+ root: "flex gap-3 group",
2092
+ wrapper: "flex-1 flex flex-col gap-3 max-w-[85%]",
2093
+ avatar: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-xs",
2094
+ bubble: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl rounded-bl-sm px-4 py-3",
2095
+ codeOutputs: "",
2096
+ charts: "mb-1 w-full",
2097
+ artifacts: "mb-1 flex flex-wrap gap-2",
2098
+ sources: "",
2099
+ explanation: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4",
2100
+ actions: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-150",
2101
+ actionButton: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 active:bg-gray-200 dark:active:bg-gray-700 rounded-md transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50",
2102
+ timestamp: "text-xs text-gray-400 dark:text-gray-500 mr-1"
2103
+ };
2104
+ function mergeClassNames2(defaults, overrides) {
2105
+ if (!overrides) return defaults;
2106
+ return {
2107
+ root: overrides.root ?? defaults.root,
2108
+ wrapper: overrides.wrapper ?? defaults.wrapper,
2109
+ avatar: overrides.avatar ?? defaults.avatar,
2110
+ bubble: overrides.bubble ?? defaults.bubble,
2111
+ codeOutputs: overrides.codeOutputs ?? defaults.codeOutputs,
2112
+ charts: overrides.charts ?? defaults.charts,
2113
+ artifacts: overrides.artifacts ?? defaults.artifacts,
2114
+ sources: overrides.sources ?? defaults.sources,
2115
+ explanation: overrides.explanation ?? defaults.explanation,
2116
+ actions: overrides.actions ?? defaults.actions,
2117
+ actionButton: overrides.actionButton ?? defaults.actionButton,
2118
+ timestamp: overrides.timestamp ?? defaults.timestamp
2119
+ };
2120
+ }
2121
+ function AssistantMessage({
2122
+ turn,
2123
+ turnId,
2124
+ isStreaming = false,
2125
+ pendingQuestion,
2126
+ slots,
2127
+ classNames: classNameOverrides,
2128
+ onCopy,
2129
+ onRegenerate,
2130
+ onSendMessage,
2131
+ sendDisabled = false,
2132
+ hideAvatar = false,
2133
+ hideActions = false,
2134
+ hideTimestamp = false
2135
+ }) {
1195
2136
  const [explanationExpanded, setExplanationExpanded] = useState(false);
1196
- const hasContent = message.content?.trim().length > 0;
1197
- const hasExplanation = !!message.explanation?.trim();
1198
- const hasPendingQuestion = !!pendingQuestion && pendingQuestion.status === "PENDING" && pendingQuestion.turnId === message.id;
1199
- const handleCopyClick = async () => {
1200
- if (handleCopy) {
1201
- await handleCopy(message.content);
2137
+ const classes = mergeClassNames2(defaultClassNames2, classNameOverrides);
2138
+ const hasContent = turn.content?.trim().length > 0;
2139
+ const hasExplanation = !!turn.explanation?.trim();
2140
+ const hasPendingQuestion = !!pendingQuestion && pendingQuestion.status === "PENDING" && pendingQuestion.turnId === turnId;
2141
+ const handleCopyClick = useCallback(async () => {
2142
+ if (onCopy) {
2143
+ await onCopy(turn.content);
1202
2144
  } else {
1203
2145
  try {
1204
- await navigator.clipboard.writeText(message.content);
2146
+ await navigator.clipboard.writeText(turn.content);
1205
2147
  } catch (err) {
1206
2148
  console.error("Failed to copy:", err);
1207
2149
  }
1208
2150
  }
1209
- };
1210
- const handleRegenerateClick = async () => {
1211
- if (handleRegenerate) {
1212
- await handleRegenerate(message.id);
2151
+ }, [onCopy, turn.content]);
2152
+ const handleRegenerateClick = useCallback(async () => {
2153
+ if (onRegenerate && turnId) {
2154
+ await onRegenerate(turnId);
1213
2155
  }
2156
+ }, [onRegenerate, turnId]);
2157
+ const timestamp = formatDistanceToNow(new Date(turn.createdAt), { addSuffix: true });
2158
+ const avatarSlotProps = { text: "AI" };
2159
+ const contentSlotProps = {
2160
+ content: turn.content,
2161
+ citations: turn.citations,
2162
+ isStreaming
1214
2163
  };
1215
- return /* @__PURE__ */ jsxs("div", { className: "flex gap-3 group", children: [
1216
- /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white font-medium text-xs", children: "AI" }),
1217
- /* @__PURE__ */ jsxs("div", { className: "flex-1 flex flex-col gap-3 max-w-[85%]", children: [
1218
- message.codeOutputs && message.codeOutputs.length > 0 && /* @__PURE__ */ jsx(CodeOutputsPanel, { outputs: message.codeOutputs }),
1219
- message.chartData && /* @__PURE__ */ jsx("div", { className: "mb-1 w-full", children: /* @__PURE__ */ jsx(ChartCard, { chartData: message.chartData }) }),
1220
- message.artifacts && message.artifacts.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-1 flex flex-wrap gap-2", children: message.artifacts.map((artifact, index) => /* @__PURE__ */ jsx(DownloadCard, { artifact }, `${artifact.filename}-${index}`)) }),
1221
- hasContent && /* @__PURE__ */ jsxs("div", { className: "bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 rounded-2xl rounded-bl-sm px-4 py-3", children: [
1222
- /* @__PURE__ */ jsx(
1223
- Suspense,
1224
- {
1225
- fallback: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-400 dark:text-gray-500", children: [
1226
- /* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-gray-300 dark:border-gray-600 border-t-transparent rounded-full animate-spin" }),
1227
- "Loading..."
1228
- ] }),
1229
- children: /* @__PURE__ */ jsx(MarkdownRenderer2, { content: message.content, citations: message.citations })
1230
- }
2164
+ const sourcesSlotProps = {
2165
+ citations: turn.citations || []
2166
+ };
2167
+ const chartsSlotProps = {
2168
+ chartData: turn.chartData
2169
+ };
2170
+ const codeOutputsSlotProps = {
2171
+ outputs: turn.codeOutputs || []
2172
+ };
2173
+ const artifactsSlotProps = {
2174
+ artifacts: turn.artifacts || []
2175
+ };
2176
+ const actionsSlotProps = {
2177
+ onCopy: handleCopyClick,
2178
+ onRegenerate: onRegenerate && turnId ? handleRegenerateClick : void 0,
2179
+ timestamp,
2180
+ canCopy: hasContent,
2181
+ canRegenerate: !!onRegenerate && !!turnId
2182
+ };
2183
+ const explanationSlotProps = {
2184
+ explanation: turn.explanation || "",
2185
+ isExpanded: explanationExpanded,
2186
+ onToggle: () => setExplanationExpanded(!explanationExpanded)
2187
+ };
2188
+ const renderSlot = (slot, props, defaultContent) => {
2189
+ if (slot === void 0) return defaultContent;
2190
+ if (typeof slot === "function") return slot(props);
2191
+ return slot;
2192
+ };
2193
+ return /* @__PURE__ */ jsxs("div", { className: classes.root, children: [
2194
+ !hideAvatar && /* @__PURE__ */ jsx("div", { className: classes.avatar, children: renderSlot(slots?.avatar, avatarSlotProps, "AI") }),
2195
+ /* @__PURE__ */ jsxs("div", { className: classes.wrapper, children: [
2196
+ turn.codeOutputs && turn.codeOutputs.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.codeOutputs, children: renderSlot(
2197
+ slots?.codeOutputs,
2198
+ codeOutputsSlotProps,
2199
+ /* @__PURE__ */ jsx(CodeOutputsPanel_default, { outputs: turn.codeOutputs })
2200
+ ) }),
2201
+ turn.chartData && /* @__PURE__ */ jsx("div", { className: classes.charts, children: renderSlot(slots?.charts, chartsSlotProps, /* @__PURE__ */ jsx(ChartCard, { chartData: turn.chartData })) }),
2202
+ turn.artifacts && turn.artifacts.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.artifacts, children: renderSlot(
2203
+ slots?.artifacts,
2204
+ artifactsSlotProps,
2205
+ turn.artifacts.map((artifact, index) => /* @__PURE__ */ jsx(DownloadCard, { artifact }, `${artifact.filename}-${index}`))
2206
+ ) }),
2207
+ hasContent && /* @__PURE__ */ jsxs("div", { className: classes.bubble, children: [
2208
+ renderSlot(
2209
+ slots?.content,
2210
+ contentSlotProps,
2211
+ /* @__PURE__ */ jsx(
2212
+ Suspense,
2213
+ {
2214
+ fallback: /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 text-sm text-gray-400 dark:text-gray-500", children: [
2215
+ /* @__PURE__ */ jsx("div", { className: "w-4 h-4 border-2 border-gray-300 dark:border-gray-600 border-t-transparent rounded-full animate-spin" }),
2216
+ "Loading..."
2217
+ ] }),
2218
+ children: /* @__PURE__ */ jsx(
2219
+ MarkdownRenderer2,
2220
+ {
2221
+ content: turn.content,
2222
+ citations: turn.citations,
2223
+ sendMessage: onSendMessage,
2224
+ sendDisabled: sendDisabled || isStreaming
2225
+ }
2226
+ )
2227
+ }
2228
+ )
1231
2229
  ),
1232
- message.isStreaming && /* @__PURE__ */ jsx(StreamingCursor, {}),
1233
- message.citations && message.citations.length > 0 && /* @__PURE__ */ jsx(SourcesPanel, { citations: message.citations }),
1234
- hasExplanation && /* @__PURE__ */ jsxs("div", { className: "mt-4 border-t border-gray-100 dark:border-gray-700 pt-4", children: [
1235
- /* @__PURE__ */ jsxs(
2230
+ isStreaming && /* @__PURE__ */ jsx(StreamingCursor_default, {}),
2231
+ turn.citations && turn.citations.length > 0 && /* @__PURE__ */ jsx("div", { className: classes.sources, children: renderSlot(
2232
+ slots?.sources,
2233
+ sourcesSlotProps,
2234
+ /* @__PURE__ */ jsx(SourcesPanel, { citations: turn.citations })
2235
+ ) }),
2236
+ hasExplanation && /* @__PURE__ */ jsx("div", { className: classes.explanation, children: renderSlot(
2237
+ slots?.explanation,
2238
+ explanationSlotProps,
2239
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2240
+ /* @__PURE__ */ jsxs(
2241
+ "button",
2242
+ {
2243
+ type: "button",
2244
+ onClick: () => setExplanationExpanded(!explanationExpanded),
2245
+ className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors duration-150 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 rounded-md p-1 -m-1",
2246
+ "aria-expanded": explanationExpanded,
2247
+ children: [
2248
+ /* @__PURE__ */ jsx(
2249
+ "svg",
2250
+ {
2251
+ className: `w-4 h-4 transition-transform duration-150 ${explanationExpanded ? "rotate-90" : ""}`,
2252
+ fill: "none",
2253
+ stroke: "currentColor",
2254
+ viewBox: "0 0 24 24",
2255
+ children: /* @__PURE__ */ jsx(
2256
+ "path",
2257
+ {
2258
+ strokeLinecap: "round",
2259
+ strokeLinejoin: "round",
2260
+ strokeWidth: 2,
2261
+ d: "M9 5l7 7-7 7"
2262
+ }
2263
+ )
2264
+ }
2265
+ ),
2266
+ /* @__PURE__ */ jsx("span", { className: "font-medium", children: "How I arrived at this" })
2267
+ ]
2268
+ }
2269
+ ),
2270
+ explanationExpanded && /* @__PURE__ */ jsx("div", { className: "pt-3 text-sm text-gray-600 dark:text-gray-400", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsx(MarkdownRenderer2, { content: turn.explanation }) }) })
2271
+ ] })
2272
+ ) })
2273
+ ] }),
2274
+ hasPendingQuestion && pendingQuestion && /* @__PURE__ */ jsx(InlineQuestionForm, { pendingQuestion }),
2275
+ hasContent && !hideActions && /* @__PURE__ */ jsx("div", { className: classes.actions, children: renderSlot(
2276
+ slots?.actions,
2277
+ actionsSlotProps,
2278
+ /* @__PURE__ */ jsxs(Fragment, { children: [
2279
+ !hideTimestamp && /* @__PURE__ */ jsx("span", { className: classes.timestamp, children: timestamp }),
2280
+ /* @__PURE__ */ jsx(
1236
2281
  "button",
1237
2282
  {
1238
- type: "button",
1239
- onClick: () => setExplanationExpanded(!explanationExpanded),
1240
- className: "flex items-center gap-2 text-sm text-gray-500 dark:text-gray-400 hover:text-gray-700 dark:hover:text-gray-300 transition-colors",
1241
- "aria-expanded": explanationExpanded,
1242
- children: [
1243
- /* @__PURE__ */ jsx(
1244
- "svg",
1245
- {
1246
- className: `w-4 h-4 transition-transform duration-150 ${explanationExpanded ? "rotate-90" : ""}`,
1247
- fill: "none",
1248
- stroke: "currentColor",
1249
- viewBox: "0 0 24 24",
1250
- children: /* @__PURE__ */ jsx(
1251
- "path",
1252
- {
1253
- strokeLinecap: "round",
1254
- strokeLinejoin: "round",
1255
- strokeWidth: 2,
1256
- d: "M9 5l7 7-7 7"
1257
- }
1258
- )
1259
- }
1260
- ),
1261
- /* @__PURE__ */ jsx("span", { className: "font-medium", children: "How I arrived at this" })
1262
- ]
2283
+ onClick: handleCopyClick,
2284
+ className: classes.actionButton,
2285
+ "aria-label": "Copy message",
2286
+ title: "Copy",
2287
+ children: /* @__PURE__ */ jsx(Copy, { size: 14, weight: "regular" })
1263
2288
  }
1264
2289
  ),
1265
- explanationExpanded && /* @__PURE__ */ jsx("div", { className: "pt-3 text-sm text-gray-600 dark:text-gray-400", children: /* @__PURE__ */ jsx(Suspense, { fallback: /* @__PURE__ */ jsx("div", { children: "Loading..." }), children: /* @__PURE__ */ jsx(MarkdownRenderer2, { content: message.explanation }) }) })
2290
+ onRegenerate && turnId && /* @__PURE__ */ jsx(
2291
+ "button",
2292
+ {
2293
+ onClick: handleRegenerateClick,
2294
+ className: classes.actionButton,
2295
+ "aria-label": "Regenerate response",
2296
+ title: "Regenerate",
2297
+ children: /* @__PURE__ */ jsx(ArrowsClockwise, { size: 14, weight: "regular" })
2298
+ }
2299
+ )
1266
2300
  ] })
1267
- ] }),
1268
- hasPendingQuestion && /* @__PURE__ */ jsx(InlineQuestionForm, { pendingQuestion }),
1269
- hasContent && /* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1 opacity-0 group-hover:opacity-100 transition-opacity duration-150", children: [
1270
- /* @__PURE__ */ jsx("span", { className: "text-xs text-gray-400 dark:text-gray-500 mr-1", children: formatDistanceToNow(new Date(message.createdAt), { addSuffix: true }) }),
1271
- /* @__PURE__ */ jsx(
1272
- "button",
1273
- {
1274
- onClick: handleCopyClick,
1275
- className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
1276
- "aria-label": "Copy message",
1277
- title: "Copy",
1278
- children: /* @__PURE__ */ jsx(Copy, { size: 14, weight: "regular" })
1279
- }
1280
- ),
1281
- handleRegenerate && /* @__PURE__ */ jsx(
1282
- "button",
1283
- {
1284
- onClick: handleRegenerateClick,
1285
- className: "p-1.5 text-gray-400 hover:text-gray-600 dark:text-gray-500 dark:hover:text-gray-300 hover:bg-gray-100 dark:hover:bg-gray-800 rounded-md transition-colors duration-150",
1286
- "aria-label": "Regenerate message",
1287
- title: "Regenerate",
1288
- children: /* @__PURE__ */ jsx(ArrowsClockwise, { size: 14, weight: "regular" })
1289
- }
1290
- )
1291
- ] })
2301
+ ) })
1292
2302
  ] })
1293
2303
  ] });
1294
2304
  }
1295
- function TurnBubble({
1296
- message,
1297
- renderUserMessage,
1298
- renderAssistantMessage
2305
+ function AssistantTurnView({
2306
+ turn,
2307
+ isStreaming = false,
2308
+ slots,
2309
+ classNames,
2310
+ hideAvatar,
2311
+ hideActions,
2312
+ hideTimestamp
1299
2313
  }) {
1300
- if (message.role === "user" /* User */) {
1301
- if (renderUserMessage) {
1302
- return /* @__PURE__ */ jsx(Fragment, { children: renderUserMessage(message) });
1303
- }
1304
- return /* @__PURE__ */ jsx(UserTurnView, { message });
1305
- }
1306
- if (message.role === "assistant" /* Assistant */) {
1307
- if (renderAssistantMessage) {
1308
- return /* @__PURE__ */ jsx(Fragment, { children: renderAssistantMessage(message) });
2314
+ const { handleCopy, handleRegenerate, pendingQuestion, sendMessage, loading } = useChat();
2315
+ const assistantTurn = turn.assistantTurn;
2316
+ if (!assistantTurn) return null;
2317
+ return /* @__PURE__ */ jsx(
2318
+ AssistantMessage,
2319
+ {
2320
+ turn: assistantTurn,
2321
+ turnId: turn.id,
2322
+ isStreaming,
2323
+ pendingQuestion,
2324
+ slots,
2325
+ classNames,
2326
+ onCopy: handleCopy,
2327
+ onRegenerate: handleRegenerate,
2328
+ onSendMessage: sendMessage,
2329
+ sendDisabled: loading || isStreaming,
2330
+ hideAvatar,
2331
+ hideActions,
2332
+ hideTimestamp
1309
2333
  }
1310
- return /* @__PURE__ */ jsx(AssistantTurnView, { message });
1311
- }
1312
- return null;
2334
+ );
2335
+ }
2336
+ var defaultClassNames3 = {
2337
+ root: "space-y-4",
2338
+ userTurn: "",
2339
+ assistantTurn: ""
2340
+ };
2341
+ function TurnBubble({
2342
+ turn,
2343
+ renderUserTurn,
2344
+ renderAssistantTurn,
2345
+ userTurnProps,
2346
+ assistantTurnProps,
2347
+ userMessageSlots,
2348
+ assistantMessageSlots,
2349
+ userMessageClassNames,
2350
+ assistantMessageClassNames,
2351
+ classNames,
2352
+ isStreaming = false
2353
+ }) {
2354
+ const classes = {
2355
+ root: classNames?.root ?? defaultClassNames3.root,
2356
+ userTurn: classNames?.userTurn ?? defaultClassNames3.userTurn,
2357
+ assistantTurn: classNames?.assistantTurn ?? defaultClassNames3.assistantTurn
2358
+ };
2359
+ return /* @__PURE__ */ jsxs("div", { className: classes.root, "data-turn-id": turn.id, children: [
2360
+ /* @__PURE__ */ jsx("div", { className: classes.userTurn, children: renderUserTurn ? renderUserTurn(turn) : /* @__PURE__ */ jsx(
2361
+ UserTurnView,
2362
+ {
2363
+ turn,
2364
+ slots: userMessageSlots,
2365
+ classNames: userMessageClassNames,
2366
+ ...userTurnProps
2367
+ }
2368
+ ) }),
2369
+ turn.assistantTurn && /* @__PURE__ */ jsx("div", { className: classes.assistantTurn, children: renderAssistantTurn ? renderAssistantTurn(turn) : /* @__PURE__ */ jsx(
2370
+ AssistantTurnView,
2371
+ {
2372
+ turn,
2373
+ isStreaming,
2374
+ slots: assistantMessageSlots,
2375
+ classNames: assistantMessageClassNames,
2376
+ ...assistantTurnProps
2377
+ }
2378
+ ) })
2379
+ ] });
1313
2380
  }
1314
2381
  function ScrollToBottomButton({
1315
2382
  show,
@@ -1324,7 +2391,7 @@ function ScrollToBottomButton({
1324
2391
  exit: { opacity: 0, y: 10 },
1325
2392
  transition: { duration: 0.2 },
1326
2393
  onClick,
1327
- className: "absolute bottom-24 right-8 p-3 bg-white dark:bg-gray-800 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 transition-colors z-10",
2394
+ className: "absolute bottom-24 right-8 p-3 bg-white dark:bg-gray-800 rounded-full shadow-lg border border-gray-200 dark:border-gray-700 hover:bg-gray-50 dark:hover:bg-gray-700 active:bg-gray-100 dark:active:bg-gray-600 transition-colors duration-150 z-10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary-500/50 focus-visible:ring-offset-2 dark:focus-visible:ring-offset-gray-900",
1328
2395
  "aria-label": "Scroll to bottom",
1329
2396
  children: /* @__PURE__ */ jsxs("div", { className: "relative", children: [
1330
2397
  /* @__PURE__ */ jsx(ArrowDown, { size: 20, weight: "bold", className: "text-gray-700 dark:text-gray-300" }),
@@ -1333,14 +2400,15 @@ function ScrollToBottomButton({
1333
2400
  }
1334
2401
  ) });
1335
2402
  }
1336
- function MessageList({ renderUserMessage, renderAssistantMessage }) {
1337
- const { messages, streamingContent, isStreaming } = useChat();
2403
+ var ScrollToBottomButton_default = ScrollToBottomButton;
2404
+ function MessageList({ renderUserTurn, renderAssistantTurn }) {
2405
+ const { turns, streamingContent, isStreaming } = useChat();
1338
2406
  const messagesEndRef = useRef(null);
1339
2407
  const containerRef = useRef(null);
1340
2408
  const [showScrollButton, setShowScrollButton] = useState(false);
1341
2409
  useEffect(() => {
1342
2410
  messagesEndRef.current?.scrollIntoView({ behavior: "smooth" });
1343
- }, [messages.length, streamingContent]);
2411
+ }, [turns.length, streamingContent]);
1344
2412
  useEffect(() => {
1345
2413
  const container = containerRef.current;
1346
2414
  if (!container) return;
@@ -1357,14 +2425,14 @@ function MessageList({ renderUserMessage, renderAssistantMessage }) {
1357
2425
  };
1358
2426
  return /* @__PURE__ */ jsxs("div", { className: "relative flex-1 min-h-0", children: [
1359
2427
  /* @__PURE__ */ jsx("div", { ref: containerRef, className: "h-full overflow-y-auto px-4 py-6", children: /* @__PURE__ */ jsxs("div", { className: "max-w-4xl mx-auto space-y-6", children: [
1360
- messages.map((message) => /* @__PURE__ */ jsx(
2428
+ turns.map((turn) => /* @__PURE__ */ jsx(
1361
2429
  TurnBubble,
1362
2430
  {
1363
- message,
1364
- renderUserMessage,
1365
- renderAssistantMessage
2431
+ turn,
2432
+ renderUserTurn,
2433
+ renderAssistantTurn
1366
2434
  },
1367
- message.id
2435
+ turn.id
1368
2436
  )),
1369
2437
  isStreaming && streamingContent && /* @__PURE__ */ jsxs("div", { className: "flex gap-3", children: [
1370
2438
  /* @__PURE__ */ jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-full bg-primary-600 flex items-center justify-center text-white", children: "AI" }),
@@ -1375,7 +2443,7 @@ function MessageList({ renderUserMessage, renderAssistantMessage }) {
1375
2443
  ] }),
1376
2444
  /* @__PURE__ */ jsx("div", { ref: messagesEndRef })
1377
2445
  ] }) }),
1378
- /* @__PURE__ */ jsx(ScrollToBottomButton, { show: showScrollButton, onClick: scrollToBottom })
2446
+ /* @__PURE__ */ jsx(ScrollToBottomButton_default, { show: showScrollButton, onClick: scrollToBottom })
1379
2447
  ] });
1380
2448
  }
1381
2449
  var MAX_FILES_DEFAULT = 10;
@@ -1524,7 +2592,7 @@ var MessageInput = forwardRef(
1524
2592
  )
1525
2593
  ] }),
1526
2594
  messageQueue.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-3 text-xs text-gray-500 dark:text-gray-400", children: /* @__PURE__ */ jsx("span", { className: "px-2.5 py-1 bg-primary-50 dark:bg-primary-900/30 text-primary-600 dark:text-primary-400 rounded font-medium", children: t("input.messagesQueued", { count: messageQueue.length }) }) }),
1527
- attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(AttachmentGrid, { attachments, onRemove: handleRemoveAttachment }) }),
2595
+ attachments.length > 0 && /* @__PURE__ */ jsx("div", { className: "mb-3", children: /* @__PURE__ */ jsx(AttachmentGrid_default, { attachments, onRemove: handleRemoveAttachment }) }),
1528
2596
  /* @__PURE__ */ jsxs(
1529
2597
  "div",
1530
2598
  {
@@ -1540,7 +2608,7 @@ var MessageInput = forwardRef(
1540
2608
  /* @__PURE__ */ jsxs(
1541
2609
  "div",
1542
2610
  {
1543
- className: `flex items-center gap-2 rounded-2xl p-2.5 bg-white dark:bg-gray-800 border shadow-sm transition-all duration-150 ${isFocused ? "border-primary-400 dark:border-primary-500 ring-3 ring-primary-500/10 dark:ring-primary-500/15" : "border-gray-200 dark:border-gray-700"}`,
2611
+ className: `flex items-center gap-2 rounded-2xl p-2.5 bg-white dark:bg-gray-800 border shadow-sm transition-all duration-150 ${isFocused ? "border-primary-400 dark:border-primary-500 ring-2 ring-primary-500/15 dark:ring-primary-500/20" : "border-gray-200 dark:border-gray-700 hover:border-gray-300 dark:hover:border-gray-600"}`,
1544
2612
  children: [
1545
2613
  /* @__PURE__ */ jsx(
1546
2614
  "button",
@@ -1657,9 +2725,9 @@ function WelcomeContent({
1657
2725
  description = "Your intelligent business analytics assistant. Ask questions about your data, generate reports, or explore insights.",
1658
2726
  disabled = false
1659
2727
  }) {
1660
- const handlePromptClick = (prompt2) => {
2728
+ const handlePromptClick = (prompt) => {
1661
2729
  if (onPromptSelect && !disabled) {
1662
- onPromptSelect(prompt2);
2730
+ onPromptSelect(prompt);
1663
2731
  }
1664
2732
  };
1665
2733
  return /* @__PURE__ */ jsxs(
@@ -1688,26 +2756,26 @@ function WelcomeContent({
1688
2756
  ),
1689
2757
  /* @__PURE__ */ jsxs(motion.div, { variants: itemVariants, children: [
1690
2758
  /* @__PURE__ */ jsx("p", { className: "text-xs font-medium text-gray-400 dark:text-gray-500 uppercase tracking-wide mb-4", children: "Try asking" }),
1691
- /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: EXAMPLE_PROMPTS.map((prompt2, index) => /* @__PURE__ */ jsxs(
2759
+ /* @__PURE__ */ jsx("div", { className: "grid gap-3 sm:grid-cols-2 lg:grid-cols-3", children: EXAMPLE_PROMPTS.map((prompt, index) => /* @__PURE__ */ jsxs(
1692
2760
  motion.button,
1693
2761
  {
1694
- onClick: () => handlePromptClick(prompt2.text),
2762
+ onClick: () => handlePromptClick(prompt.text),
1695
2763
  disabled,
1696
2764
  className: "group flex flex-col items-start text-left p-5 rounded-xl bg-white dark:bg-gray-800 border border-gray-200 dark:border-gray-700 shadow-sm hover:shadow-md hover:border-gray-300 dark:hover:border-gray-600 transition-all duration-150 focus:outline-none focus:ring-2 focus:ring-primary-500/50 focus:ring-offset-2 dark:focus:ring-offset-gray-900 disabled:opacity-50 disabled:cursor-not-allowed",
1697
2765
  variants: itemVariants,
1698
- whileHover: disabled ? {} : { y: -2 },
2766
+ whileHover: disabled ? {} : { y: -2, scale: 1.01 },
1699
2767
  whileTap: disabled ? {} : { scale: 0.98 },
1700
2768
  children: [
1701
- /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-lg bg-gray-100 dark:bg-gray-700 flex items-center justify-center mb-3", children: /* @__PURE__ */ jsx(
1702
- prompt2.icon,
2769
+ /* @__PURE__ */ jsx("div", { className: "w-9 h-9 rounded-lg bg-gray-100 dark:bg-gray-700 group-hover:bg-primary-50 dark:group-hover:bg-primary-900/30 flex items-center justify-center mb-3 transition-colors duration-200", children: /* @__PURE__ */ jsx(
2770
+ prompt.icon,
1703
2771
  {
1704
2772
  size: 18,
1705
2773
  weight: "regular",
1706
- className: "text-gray-600 dark:text-gray-300"
2774
+ className: "text-gray-600 dark:text-gray-300 group-hover:text-primary-600 dark:group-hover:text-primary-400 transition-colors duration-200"
1707
2775
  }
1708
2776
  ) }),
1709
- /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-400 dark:text-gray-500 mb-1.5", children: prompt2.category }),
1710
- /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-700 dark:text-gray-200 leading-relaxed", children: prompt2.text })
2777
+ /* @__PURE__ */ jsx("span", { className: "text-xs font-medium text-gray-400 dark:text-gray-500 mb-1.5", children: prompt.category }),
2778
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-700 dark:text-gray-200 leading-relaxed", children: prompt.text })
1711
2779
  ]
1712
2780
  },
1713
2781
  index
@@ -1717,10 +2785,11 @@ function WelcomeContent({
1717
2785
  }
1718
2786
  );
1719
2787
  }
2788
+ var WelcomeContent_default = WelcomeContent;
1720
2789
  function ChatSessionCore({
1721
2790
  isReadOnly,
1722
- renderUserMessage,
1723
- renderAssistantMessage,
2791
+ renderUserTurn,
2792
+ renderAssistantTurn,
1724
2793
  className = "",
1725
2794
  headerSlot,
1726
2795
  welcomeSlot,
@@ -1731,7 +2800,7 @@ function ChatSessionCore({
1731
2800
  const { t } = useTranslation();
1732
2801
  const {
1733
2802
  session,
1734
- messages,
2803
+ turns,
1735
2804
  fetching,
1736
2805
  error,
1737
2806
  message,
@@ -1751,9 +2820,9 @@ function ChatSessionCore({
1751
2820
  error
1752
2821
  ] }) });
1753
2822
  }
1754
- const showWelcome = !session && messages.length === 0;
1755
- const handlePromptSelect = (prompt2) => {
1756
- setMessage(prompt2);
2823
+ const showWelcome = !session && turns.length === 0;
2824
+ const handlePromptSelect = (prompt) => {
2825
+ setMessage(prompt);
1757
2826
  };
1758
2827
  return /* @__PURE__ */ jsxs(
1759
2828
  "main",
@@ -1761,11 +2830,11 @@ function ChatSessionCore({
1761
2830
  className: `flex-1 flex flex-col overflow-hidden min-h-0 bg-gray-50 dark:bg-gray-900 ${className}`,
1762
2831
  children: [
1763
2832
  headerSlot || /* @__PURE__ */ jsx(ChatHeader, { session, onBack, logoSlot, actionsSlot }),
1764
- showWelcome ? /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center overflow-auto", children: welcomeSlot || /* @__PURE__ */ jsx(WelcomeContent, { onPromptSelect: handlePromptSelect, disabled: loading }) }) : /* @__PURE__ */ jsx(
2833
+ showWelcome ? /* @__PURE__ */ jsx("div", { className: "flex-1 flex items-center justify-center overflow-auto", children: welcomeSlot || /* @__PURE__ */ jsx(WelcomeContent_default, { onPromptSelect: handlePromptSelect, disabled: loading }) }) : /* @__PURE__ */ jsx(
1765
2834
  MessageList,
1766
2835
  {
1767
- renderUserMessage,
1768
- renderAssistantMessage
2836
+ renderUserTurn,
2837
+ renderAssistantTurn
1769
2838
  }
1770
2839
  ),
1771
2840
  !isReadOnly && /* @__PURE__ */ jsx(
@@ -2059,7 +3128,8 @@ function EmptyState({
2059
3128
  }
2060
3129
  );
2061
3130
  }
2062
- var EmptyState_default = memo(EmptyState);
3131
+ var MemoizedEmptyState = memo(EmptyState);
3132
+ MemoizedEmptyState.displayName = "EmptyState";
2063
3133
  var sizeClasses2 = {
2064
3134
  sm: "text-sm",
2065
3135
  md: "text-base",
@@ -2181,7 +3251,7 @@ var EditableText = forwardRef(
2181
3251
  }
2182
3252
  );
2183
3253
  EditableText.displayName = "EditableText";
2184
- var EditableText_default = memo(EditableText);
3254
+ var MemoizedEditableText = memo(EditableText);
2185
3255
  var sizeClasses3 = {
2186
3256
  sm: {
2187
3257
  container: "py-1.5 pl-8 pr-8 text-xs",
@@ -2272,7 +3342,8 @@ function SearchInput({
2272
3342
  )
2273
3343
  ] });
2274
3344
  }
2275
- var SearchInput_default = memo(SearchInput);
3345
+ var MemoizedSearchInput = memo(SearchInput);
3346
+ MemoizedSearchInput.displayName = "SearchInput";
2276
3347
  var variantClasses = {
2277
3348
  text: "rounded",
2278
3349
  circular: "rounded-full",
@@ -2366,60 +3437,698 @@ function ListItemSkeleton({ className = "" }) {
2366
3437
  /* @__PURE__ */ jsx(Skeleton, { variant: "text", height: 16, className: "flex-1" })
2367
3438
  ] });
2368
3439
  }
2369
- var Skeleton_default = memo(Skeleton);
2370
- var ConfigContext = createContext(null);
2371
- function ConfigProvider({ config, useGlobalConfig = false, children }) {
2372
- let resolvedConfig = null;
2373
- if (config) {
2374
- resolvedConfig = config;
2375
- } else if (useGlobalConfig && typeof window !== "undefined") {
2376
- const globalContext = window.__BICHAT_CONTEXT__;
2377
- const globalCSRF = window.__CSRF_TOKEN__;
2378
- if (globalContext) {
2379
- resolvedConfig = {
2380
- user: {
2381
- id: String(globalContext.user?.id || ""),
2382
- email: globalContext.user?.email || "",
2383
- firstName: globalContext.user?.firstName || "",
2384
- lastName: globalContext.user?.lastName || "",
2385
- permissions: globalContext.user?.permissions || []
2386
- },
2387
- tenant: {
2388
- id: globalContext.tenant?.id || "",
2389
- name: globalContext.tenant?.name || ""
2390
- },
2391
- locale: {
2392
- language: globalContext.locale?.language || "en",
2393
- translations: globalContext.locale?.translations || {}
2394
- },
2395
- endpoints: {
2396
- graphQL: globalContext.config?.graphQLEndpoint || "/graphql",
2397
- stream: globalContext.config?.streamEndpoint || "/stream"
2398
- },
2399
- csrfToken: globalCSRF
2400
- };
3440
+ var MemoizedSkeleton = memo(Skeleton);
3441
+ MemoizedSkeleton.displayName = "Skeleton";
3442
+
3443
+ // ui/src/bichat/index.ts
3444
+ init_CodeBlock();
3445
+ init_TableExportButton();
3446
+ init_TableWithExport();
3447
+ var typeConfig = {
3448
+ success: {
3449
+ bgColor: "bg-green-500",
3450
+ darkBgColor: "dark:bg-green-600",
3451
+ icon: /* @__PURE__ */ jsx(CheckCircle, { size: 20, className: "w-5 h-5 flex-shrink-0", weight: "fill" })
3452
+ },
3453
+ error: {
3454
+ bgColor: "bg-red-500",
3455
+ darkBgColor: "dark:bg-red-600",
3456
+ icon: /* @__PURE__ */ jsx(XCircle, { size: 20, className: "w-5 h-5 flex-shrink-0", weight: "fill" })
3457
+ },
3458
+ info: {
3459
+ bgColor: "bg-blue-500",
3460
+ darkBgColor: "dark:bg-blue-600",
3461
+ icon: /* @__PURE__ */ jsx(Info, { size: 20, className: "w-5 h-5 flex-shrink-0", weight: "fill" })
3462
+ },
3463
+ warning: {
3464
+ bgColor: "bg-yellow-500",
3465
+ darkBgColor: "dark:bg-yellow-600",
3466
+ icon: /* @__PURE__ */ jsx(Warning, { size: 20, className: "w-5 h-5 flex-shrink-0", weight: "fill" })
3467
+ }
3468
+ };
3469
+ function Toast({
3470
+ id,
3471
+ type,
3472
+ message,
3473
+ duration = 5e3,
3474
+ onDismiss,
3475
+ dismissLabel = "Dismiss"
3476
+ }) {
3477
+ const config = typeConfig[type];
3478
+ const ariaLive = type === "error" ? "assertive" : "polite";
3479
+ const role = type === "error" || type === "warning" ? "alert" : "status";
3480
+ useEffect(() => {
3481
+ const timer = setTimeout(() => onDismiss(id), duration);
3482
+ return () => clearTimeout(timer);
3483
+ }, [id, duration, onDismiss]);
3484
+ return /* @__PURE__ */ jsxs(
3485
+ motion.div,
3486
+ {
3487
+ initial: { opacity: 0, y: -50, scale: 0.95 },
3488
+ animate: { opacity: 1, y: 0, scale: 1 },
3489
+ exit: { opacity: 0, scale: 0.95 },
3490
+ transition: { duration: 0.2 },
3491
+ className: `flex items-center gap-3 px-4 py-3 rounded-lg shadow-lg backdrop-blur-sm min-w-[300px] max-w-[400px] text-white ${config.bgColor} ${config.darkBgColor}`,
3492
+ role,
3493
+ "aria-live": ariaLive,
3494
+ "aria-atomic": "true",
3495
+ children: [
3496
+ config.icon,
3497
+ /* @__PURE__ */ jsx("p", { className: "flex-1 text-sm font-medium", children: message }),
3498
+ /* @__PURE__ */ jsx(
3499
+ "button",
3500
+ {
3501
+ onClick: () => onDismiss(id),
3502
+ className: "ml-2 text-white hover:bg-white/20 p-1 rounded transition-colors flex-shrink-0",
3503
+ "aria-label": dismissLabel,
3504
+ children: /* @__PURE__ */ jsx(X, { size: 16, className: "w-4 h-4", weight: "bold" })
3505
+ }
3506
+ )
3507
+ ]
2401
3508
  }
3509
+ );
3510
+ }
3511
+ function ToastContainer({ toasts, onDismiss, dismissLabel }) {
3512
+ return /* @__PURE__ */ jsx("div", { className: "fixed top-4 right-4 z-50 flex flex-col gap-2 pointer-events-none", children: /* @__PURE__ */ jsx(AnimatePresence, { children: toasts.map((toast) => /* @__PURE__ */ jsx("div", { className: "pointer-events-auto", children: /* @__PURE__ */ jsx(Toast, { ...toast, onDismiss, dismissLabel }) }, toast.id)) }) });
3513
+ }
3514
+ function ConfirmModalBase({
3515
+ isOpen,
3516
+ title,
3517
+ message,
3518
+ onConfirm,
3519
+ onCancel,
3520
+ confirmText = "Confirm",
3521
+ cancelText = "Cancel",
3522
+ isDanger = false
3523
+ }) {
3524
+ const modalRef = useRef(null);
3525
+ const confirmButtonRef = useRef(null);
3526
+ useFocusTrap(modalRef, isOpen);
3527
+ useEffect(() => {
3528
+ if (!isOpen) return;
3529
+ const handleKeyDown = (e) => {
3530
+ if (e.key === "Escape") {
3531
+ onCancel();
3532
+ } else if (e.key === "Enter") {
3533
+ if (document.activeElement === confirmButtonRef.current) {
3534
+ e.preventDefault();
3535
+ onConfirm();
3536
+ }
3537
+ }
3538
+ };
3539
+ document.addEventListener("keydown", handleKeyDown);
3540
+ return () => document.removeEventListener("keydown", handleKeyDown);
3541
+ }, [isOpen, onConfirm, onCancel, confirmButtonRef]);
3542
+ if (!isOpen) return null;
3543
+ return /* @__PURE__ */ jsxs(Fragment, { children: [
3544
+ /* @__PURE__ */ jsx(
3545
+ "div",
3546
+ {
3547
+ className: "fixed inset-0 bg-black/40 dark:bg-black/60 backdrop-blur-sm transition-opacity z-40",
3548
+ onClick: onCancel,
3549
+ "aria-hidden": "true"
3550
+ }
3551
+ ),
3552
+ /* @__PURE__ */ jsx(
3553
+ "div",
3554
+ {
3555
+ className: "fixed inset-0 flex items-center justify-center z-50",
3556
+ role: "alertdialog",
3557
+ "aria-modal": "true",
3558
+ "aria-labelledby": "confirm-modal-title",
3559
+ "aria-describedby": "confirm-modal-description",
3560
+ children: /* @__PURE__ */ jsxs(
3561
+ "div",
3562
+ {
3563
+ ref: modalRef,
3564
+ className: "bg-white dark:bg-gray-800 rounded-lg shadow-xl max-w-sm w-full mx-4",
3565
+ children: [
3566
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4 border-b border-gray-200 dark:border-gray-700", children: /* @__PURE__ */ jsx(
3567
+ "h2",
3568
+ {
3569
+ id: "confirm-modal-title",
3570
+ className: "text-lg font-semibold text-gray-900 dark:text-white",
3571
+ children: title
3572
+ }
3573
+ ) }),
3574
+ /* @__PURE__ */ jsx("div", { className: "px-6 py-4", children: /* @__PURE__ */ jsx("p", { id: "confirm-modal-description", className: "text-gray-600 dark:text-gray-300", children: message }) }),
3575
+ /* @__PURE__ */ jsxs("div", { className: "px-6 py-4 border-t border-gray-200 dark:border-gray-700 flex justify-end gap-3", children: [
3576
+ /* @__PURE__ */ jsx(
3577
+ "button",
3578
+ {
3579
+ onClick: onCancel,
3580
+ className: "px-4 py-2 rounded-lg bg-gray-100 dark:bg-gray-700 text-gray-900 dark:text-gray-100 hover:bg-gray-200 dark:hover:bg-gray-600 transition-colors font-medium",
3581
+ "aria-label": `Cancel ${title.toLowerCase()}`,
3582
+ "data-testid": "confirm-modal-cancel",
3583
+ children: cancelText
3584
+ }
3585
+ ),
3586
+ /* @__PURE__ */ jsx(
3587
+ "button",
3588
+ {
3589
+ ref: confirmButtonRef,
3590
+ onClick: onConfirm,
3591
+ className: `px-4 py-2 rounded-lg text-white font-medium transition-colors ${isDanger ? "bg-red-600 dark:bg-red-700 hover:bg-red-700 dark:hover:bg-red-800" : "bg-primary-600 dark:bg-primary-700 hover:bg-primary-700 dark:hover:bg-primary-800"}`,
3592
+ "aria-label": `Confirm ${title.toLowerCase()}`,
3593
+ "data-testid": "confirm-modal-confirm",
3594
+ children: confirmText
3595
+ }
3596
+ )
3597
+ ] })
3598
+ ]
3599
+ }
3600
+ )
3601
+ }
3602
+ )
3603
+ ] });
3604
+ }
3605
+ var ConfirmModal = memo(ConfirmModalBase);
3606
+ ConfirmModal.displayName = "ConfirmModal";
3607
+ function hashString(str) {
3608
+ let hash = 0;
3609
+ for (let i = 0; i < str.length; i++) {
3610
+ const char = str.charCodeAt(i);
3611
+ hash = (hash << 5) - hash + char;
3612
+ hash = hash & hash;
2402
3613
  }
2403
- return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: resolvedConfig, children });
3614
+ return Math.abs(hash);
2404
3615
  }
2405
- function useConfig() {
2406
- return useContext(ConfigContext);
3616
+ var colorPalette = [
3617
+ { bg: "bg-blue-500", text: "text-white" },
3618
+ { bg: "bg-green-500", text: "text-white" },
3619
+ { bg: "bg-purple-500", text: "text-white" },
3620
+ { bg: "bg-pink-500", text: "text-white" },
3621
+ { bg: "bg-indigo-500", text: "text-white" },
3622
+ { bg: "bg-teal-500", text: "text-white" },
3623
+ { bg: "bg-orange-500", text: "text-white" },
3624
+ { bg: "bg-cyan-500", text: "text-white" },
3625
+ { bg: "bg-amber-500", text: "text-white" },
3626
+ { bg: "bg-lime-500", text: "text-white" }
3627
+ ];
3628
+ var sizeClasses4 = {
3629
+ sm: "w-8 h-8 text-xs",
3630
+ md: "w-10 h-10 text-sm",
3631
+ lg: "w-12 h-12 text-base"
3632
+ };
3633
+ function UserAvatar({
3634
+ firstName,
3635
+ lastName,
3636
+ initials: providedInitials,
3637
+ size = "md",
3638
+ className = ""
3639
+ }) {
3640
+ const derivedInitials = (() => {
3641
+ const firstChar = firstName?.trim()?.charAt(0) || "";
3642
+ const lastChar = lastName?.trim()?.charAt(0) || "";
3643
+ const combined = `${firstChar}${lastChar}`.trim();
3644
+ return combined || "U";
3645
+ })();
3646
+ const initials = (providedInitials?.trim() || derivedInitials).toUpperCase();
3647
+ const fullName = `${firstName}${lastName}`;
3648
+ const colorIndex = hashString(fullName) % colorPalette.length;
3649
+ const colors = colorPalette[colorIndex];
3650
+ return /* @__PURE__ */ jsx(
3651
+ "div",
3652
+ {
3653
+ className: `
3654
+ ${sizeClasses4[size]}
3655
+ ${colors.bg}
3656
+ ${colors.text}
3657
+ ${className}
3658
+ rounded-full
3659
+ flex
3660
+ items-center
3661
+ justify-center
3662
+ font-semibold
3663
+ flex-shrink-0
3664
+ select-none
3665
+ `,
3666
+ "aria-label": `${firstName} ${lastName}`,
3667
+ title: `${firstName} ${lastName}`,
3668
+ children: initials
3669
+ }
3670
+ );
2407
3671
  }
2408
- function useRequiredConfig() {
2409
- const config = useContext(ConfigContext);
2410
- if (!config) {
2411
- throw new Error(
2412
- "BiChat configuration not found. Wrap your app with <ConfigProvider config={...}> or use useGlobalConfig={true}."
3672
+ var MemoizedUserAvatar = memo(UserAvatar);
3673
+ MemoizedUserAvatar.displayName = "UserAvatar";
3674
+ function PermissionGuard({
3675
+ permissions,
3676
+ mode = "all",
3677
+ hasPermission: hasPermission3,
3678
+ fallback = null,
3679
+ children
3680
+ }) {
3681
+ if (permissions.length === 0) {
3682
+ return /* @__PURE__ */ jsx(Fragment, { children });
3683
+ }
3684
+ const permitted = mode === "all" ? permissions.every((p) => hasPermission3(p)) : permissions.some((p) => hasPermission3(p));
3685
+ return permitted ? /* @__PURE__ */ jsx(Fragment, { children }) : /* @__PURE__ */ jsx(Fragment, { children: fallback });
3686
+ }
3687
+ function DefaultErrorContent({
3688
+ error,
3689
+ onReset,
3690
+ resetLabel = "Try Again",
3691
+ errorTitle = "Something went wrong"
3692
+ }) {
3693
+ return /* @__PURE__ */ jsxs("div", { className: "flex flex-col items-center justify-center p-8 text-center min-h-[200px]", children: [
3694
+ /* @__PURE__ */ jsx(WarningCircle, { size: 48, className: "text-red-500 mb-4", weight: "fill" }),
3695
+ /* @__PURE__ */ jsx("h2", { className: "text-lg font-semibold text-gray-900 dark:text-white mb-2", children: errorTitle }),
3696
+ /* @__PURE__ */ jsx("p", { className: "text-sm text-gray-600 dark:text-gray-400 mb-4 max-w-md", children: error?.message || "An unexpected error occurred. Please try again." }),
3697
+ onReset && /* @__PURE__ */ jsxs(
3698
+ "button",
3699
+ {
3700
+ type: "button",
3701
+ onClick: onReset,
3702
+ className: "flex items-center gap-2 px-4 py-2 bg-primary-600 text-white rounded-lg hover:bg-primary-700 transition-colors",
3703
+ children: [
3704
+ /* @__PURE__ */ jsx(ArrowClockwise, { size: 16, weight: "bold" }),
3705
+ resetLabel
3706
+ ]
3707
+ }
3708
+ )
3709
+ ] });
3710
+ }
3711
+ var ErrorBoundary = class extends Component {
3712
+ constructor(props) {
3713
+ super(props);
3714
+ this.handleReset = () => {
3715
+ this.setState({ hasError: false, error: null });
3716
+ };
3717
+ this.state = { hasError: false, error: null };
3718
+ }
3719
+ static getDerivedStateFromError(error) {
3720
+ return { hasError: true, error };
3721
+ }
3722
+ componentDidCatch(error, errorInfo) {
3723
+ console.error("React Error Boundary caught an error:", error, errorInfo);
3724
+ this.props.onError?.(error, errorInfo);
3725
+ }
3726
+ render() {
3727
+ if (this.state.hasError) {
3728
+ if (this.props.fallback) {
3729
+ if (typeof this.props.fallback === "function") {
3730
+ return this.props.fallback(this.state.error, this.handleReset);
3731
+ }
3732
+ return this.props.fallback;
3733
+ }
3734
+ return /* @__PURE__ */ jsx(DefaultErrorContent, { error: this.state.error, onReset: this.handleReset });
3735
+ }
3736
+ return this.props.children;
3737
+ }
3738
+ };
3739
+ var DEFAULT_MESSAGES = [
3740
+ "Thinking...",
3741
+ "Processing...",
3742
+ "Analyzing...",
3743
+ "Computing...",
3744
+ "Working on it..."
3745
+ ];
3746
+ var prefersReducedMotion2 = () => {
3747
+ if (typeof window === "undefined") return false;
3748
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
3749
+ };
3750
+ var getRandomMessage = (messages, current) => {
3751
+ const available = messages.filter((m) => m !== current);
3752
+ if (available.length === 0) {
3753
+ return current || messages[0] || "Thinking...";
3754
+ }
3755
+ return available[Math.floor(Math.random() * available.length)];
3756
+ };
3757
+ var sizeConfig = {
3758
+ sm: { dot: "w-1.5 h-1.5", gap: "gap-1", text: "text-sm" },
3759
+ md: { dot: "w-2 h-2", gap: "gap-1.5", text: "text-base" },
3760
+ lg: { dot: "w-2.5 h-2.5", gap: "gap-2", text: "text-lg" }
3761
+ };
3762
+ function DotsIndicator({ size = "md" }) {
3763
+ const config = sizeConfig[size];
3764
+ return /* @__PURE__ */ jsxs("div", { className: `flex ${config.gap}`, role: "status", "aria-live": "polite", children: [
3765
+ [0, 1, 2].map((index) => /* @__PURE__ */ jsx(
3766
+ "div",
3767
+ {
3768
+ className: `${config.dot} bg-gray-400 dark:bg-gray-500 rounded-full animate-bounce`,
3769
+ style: { animationDelay: `${index * 0.15}s` }
3770
+ },
3771
+ index
3772
+ )),
3773
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading..." })
3774
+ ] });
3775
+ }
3776
+ function PulseIndicator({ size = "md" }) {
3777
+ const config = sizeConfig[size];
3778
+ return /* @__PURE__ */ jsxs("div", { className: "flex items-center", role: "status", "aria-live": "polite", children: [
3779
+ /* @__PURE__ */ jsx("div", { className: `${config.dot} bg-primary-500 rounded-full animate-pulse` }),
3780
+ /* @__PURE__ */ jsx("span", { className: "sr-only", children: "Loading..." })
3781
+ ] });
3782
+ }
3783
+ function TextIndicator({
3784
+ messages = DEFAULT_MESSAGES,
3785
+ rotationInterval = 3e3,
3786
+ size = "md"
3787
+ }) {
3788
+ const config = sizeConfig[size];
3789
+ const [message, setMessage] = useState(() => messages[Math.floor(Math.random() * messages.length)]);
3790
+ useEffect(() => {
3791
+ if (prefersReducedMotion2()) return;
3792
+ const interval = setInterval(() => {
3793
+ setMessage((prev) => getRandomMessage(messages, prev));
3794
+ }, rotationInterval);
3795
+ return () => clearInterval(interval);
3796
+ }, [messages, rotationInterval]);
3797
+ return /* @__PURE__ */ jsx("div", { role: "status", "aria-live": "polite", className: "overflow-hidden h-6 relative", children: /* @__PURE__ */ jsx(AnimatePresence, { mode: "wait", children: /* @__PURE__ */ jsx(
3798
+ motion.span,
3799
+ {
3800
+ initial: { opacity: 0, y: 10 },
3801
+ animate: { opacity: 1, y: 0 },
3802
+ exit: { opacity: 0, y: -10 },
3803
+ transition: { duration: 0.2 },
3804
+ className: `${config.text} text-gray-500 dark:text-gray-400 block`,
3805
+ children: message
3806
+ },
3807
+ message
3808
+ ) }) });
3809
+ }
3810
+ function TypingIndicator({
3811
+ variant = "dots",
3812
+ messages = DEFAULT_MESSAGES,
3813
+ rotationInterval = 3e3,
3814
+ size = "md",
3815
+ className = ""
3816
+ }) {
3817
+ return /* @__PURE__ */ jsxs("div", { className: `flex items-center ${className}`, children: [
3818
+ variant === "dots" && /* @__PURE__ */ jsx(DotsIndicator, { size }),
3819
+ variant === "pulse" && /* @__PURE__ */ jsx(PulseIndicator, { size }),
3820
+ variant === "text" && /* @__PURE__ */ jsx(TextIndicator, { messages, rotationInterval, size })
3821
+ ] });
3822
+ }
3823
+ var MemoizedTypingIndicator = memo(TypingIndicator);
3824
+ MemoizedTypingIndicator.displayName = "TypingIndicator";
3825
+ function mergeProps(slotProps, childProps) {
3826
+ const overrideProps = { ...childProps };
3827
+ for (const propName in childProps) {
3828
+ const slotPropValue = slotProps[propName];
3829
+ const childPropValue = childProps[propName];
3830
+ const isHandler = /^on[A-Z]/.test(propName);
3831
+ if (isHandler) {
3832
+ if (slotPropValue && childPropValue) {
3833
+ overrideProps[propName] = (...args) => {
3834
+ childPropValue(...args);
3835
+ slotPropValue(...args);
3836
+ };
3837
+ } else if (slotPropValue) {
3838
+ overrideProps[propName] = slotPropValue;
3839
+ }
3840
+ } else if (propName === "style") {
3841
+ overrideProps[propName] = { ...slotPropValue, ...childPropValue };
3842
+ } else if (propName === "className") {
3843
+ overrideProps[propName] = [slotPropValue, childPropValue].filter(Boolean).join(" ");
3844
+ }
3845
+ }
3846
+ return { ...slotProps, ...overrideProps };
3847
+ }
3848
+ var Slot = forwardRef((props, forwardedRef) => {
3849
+ const { children, ...slotProps } = props;
3850
+ if (!isValidElement(children)) {
3851
+ return null;
3852
+ }
3853
+ const childrenRef = children.ref;
3854
+ return cloneElement(children, {
3855
+ ...mergeProps(slotProps, children.props),
3856
+ ref: forwardedRef ? composeRefs(forwardedRef, childrenRef) : childrenRef
3857
+ });
3858
+ });
3859
+ Slot.displayName = "Slot";
3860
+ function composeRefs(...refs) {
3861
+ return (node) => {
3862
+ refs.forEach((ref) => {
3863
+ if (typeof ref === "function") {
3864
+ ref(node);
3865
+ } else if (ref != null) {
3866
+ ref.current = node;
3867
+ }
3868
+ });
3869
+ };
3870
+ }
3871
+ function getValidChildren(children) {
3872
+ return Children.toArray(children).filter(isValidElement);
3873
+ }
3874
+ var TurnContext = createContext(void 0);
3875
+ function useTurnContext() {
3876
+ const context = useContext(TurnContext);
3877
+ if (!context) {
3878
+ throw new Error("Turn components must be used within Turn.Root");
3879
+ }
3880
+ return context;
3881
+ }
3882
+ var TurnRoot = forwardRef((props, ref) => {
3883
+ const { asChild, turnId, children, ...domProps } = props;
3884
+ const Comp = asChild ? Slot : "div";
3885
+ return /* @__PURE__ */ jsx(TurnContext.Provider, { value: { turnId }, children: /* @__PURE__ */ jsx(Comp, { ref, "data-turn-id": turnId, ...domProps, children }) });
3886
+ });
3887
+ TurnRoot.displayName = "Turn.Root";
3888
+ var TurnUser = forwardRef((props, ref) => {
3889
+ const { asChild, children, ...domProps } = props;
3890
+ const Comp = asChild ? Slot : "div";
3891
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-turn-role": "user", ...domProps, children });
3892
+ });
3893
+ TurnUser.displayName = "Turn.User";
3894
+ var TurnAssistant = forwardRef((props, ref) => {
3895
+ const { asChild, children, ...domProps } = props;
3896
+ const Comp = asChild ? Slot : "div";
3897
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-turn-role": "assistant", ...domProps, children });
3898
+ });
3899
+ TurnAssistant.displayName = "Turn.Assistant";
3900
+ var defaultFormatter = (date) => date.toLocaleTimeString(void 0, { hour: "2-digit", minute: "2-digit" });
3901
+ var TurnTimestamp = forwardRef((props, ref) => {
3902
+ const { asChild, date, formatter = defaultFormatter, children, ...domProps } = props;
3903
+ const Comp = asChild ? Slot : "time";
3904
+ const dateObj = date ? typeof date === "string" ? new Date(date) : date : null;
3905
+ const formattedTime = dateObj ? formatter(dateObj) : "";
3906
+ const isoString = dateObj?.toISOString();
3907
+ return /* @__PURE__ */ jsx(Comp, { ref, dateTime: isoString, ...domProps, children: children ?? formattedTime });
3908
+ });
3909
+ TurnTimestamp.displayName = "Turn.Timestamp";
3910
+ var TurnActions = forwardRef((props, ref) => {
3911
+ const { asChild, children, ...domProps } = props;
3912
+ const Comp = asChild ? Slot : "div";
3913
+ return /* @__PURE__ */ jsx(Comp, { ref, role: "group", "aria-label": "Message actions", ...domProps, children });
3914
+ });
3915
+ TurnActions.displayName = "Turn.Actions";
3916
+ var Turn = {
3917
+ Root: TurnRoot,
3918
+ User: TurnUser,
3919
+ Assistant: TurnAssistant,
3920
+ Timestamp: TurnTimestamp,
3921
+ Actions: TurnActions
3922
+ };
3923
+ var AvatarContext = createContext(void 0);
3924
+ function useAvatarContext() {
3925
+ const context = useContext(AvatarContext);
3926
+ if (!context) {
3927
+ throw new Error("Avatar components must be used within Avatar.Root");
3928
+ }
3929
+ return context;
3930
+ }
3931
+ var AvatarRoot = forwardRef((props, ref) => {
3932
+ const { asChild, children, ...domProps } = props;
3933
+ const Comp = asChild ? Slot : "span";
3934
+ const [imageLoadingStatus, setImageLoadingStatus] = useState("idle");
3935
+ return /* @__PURE__ */ jsx(AvatarContext.Provider, { value: { imageLoadingStatus, setImageLoadingStatus }, children: /* @__PURE__ */ jsx(Comp, { ref, ...domProps, children }) });
3936
+ });
3937
+ AvatarRoot.displayName = "Avatar.Root";
3938
+ var AvatarImage = forwardRef((props, ref) => {
3939
+ const { asChild, src, alt, onLoadingStatusChange, onLoad, onError, ...domProps } = props;
3940
+ const { setImageLoadingStatus } = useAvatarContext();
3941
+ const Comp = asChild ? Slot : "img";
3942
+ const handleLoad = (e) => {
3943
+ setImageLoadingStatus("loaded");
3944
+ onLoadingStatusChange?.("loaded");
3945
+ onLoad?.(e);
3946
+ };
3947
+ const handleError = (e) => {
3948
+ setImageLoadingStatus("error");
3949
+ onLoadingStatusChange?.("error");
3950
+ onError?.(e);
3951
+ };
3952
+ if (src) {
3953
+ return /* @__PURE__ */ jsx(
3954
+ Comp,
3955
+ {
3956
+ ref,
3957
+ src,
3958
+ alt: alt || "",
3959
+ onLoad: handleLoad,
3960
+ onError: handleError,
3961
+ ...domProps
3962
+ }
2413
3963
  );
2414
3964
  }
2415
- return config;
3965
+ return null;
3966
+ });
3967
+ AvatarImage.displayName = "Avatar.Image";
3968
+ var AvatarFallback = forwardRef((props, ref) => {
3969
+ const { asChild, delayMs = 0, children, ...domProps } = props;
3970
+ const { imageLoadingStatus } = useAvatarContext();
3971
+ const Comp = asChild ? Slot : "span";
3972
+ const [canRender, setCanRender] = useState(delayMs === 0);
3973
+ if (delayMs > 0 && !canRender) {
3974
+ setTimeout(() => setCanRender(true), delayMs);
3975
+ }
3976
+ if (imageLoadingStatus === "loaded") {
3977
+ return null;
3978
+ }
3979
+ if (!canRender) {
3980
+ return null;
3981
+ }
3982
+ return /* @__PURE__ */ jsx(Comp, { ref, ...domProps, children });
3983
+ });
3984
+ AvatarFallback.displayName = "Avatar.Fallback";
3985
+ var Avatar = {
3986
+ Root: AvatarRoot,
3987
+ Image: AvatarImage,
3988
+ Fallback: AvatarFallback
3989
+ };
3990
+ var BubbleContext = createContext(void 0);
3991
+ function useBubbleContext() {
3992
+ const context = useContext(BubbleContext);
3993
+ if (!context) {
3994
+ throw new Error("Bubble components must be used within Bubble.Root");
3995
+ }
3996
+ return context;
2416
3997
  }
2417
- function hasPermission2(config, permission) {
2418
- if (!config) {
2419
- return false;
3998
+ var BubbleRoot = forwardRef((props, ref) => {
3999
+ const { asChild, variant, children, ...domProps } = props;
4000
+ const Comp = asChild ? Slot : "div";
4001
+ return /* @__PURE__ */ jsx(BubbleContext.Provider, { value: { variant }, children: /* @__PURE__ */ jsx(Comp, { ref, "data-bubble-variant": variant, ...domProps, children }) });
4002
+ });
4003
+ BubbleRoot.displayName = "Bubble.Root";
4004
+ var BubbleContent = forwardRef((props, ref) => {
4005
+ const { asChild, children, ...domProps } = props;
4006
+ const Comp = asChild ? Slot : "div";
4007
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-bubble-part": "content", ...domProps, children });
4008
+ });
4009
+ BubbleContent.displayName = "Bubble.Content";
4010
+ var BubbleHeader = forwardRef((props, ref) => {
4011
+ const { asChild, children, ...domProps } = props;
4012
+ const Comp = asChild ? Slot : "div";
4013
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-bubble-part": "header", ...domProps, children });
4014
+ });
4015
+ BubbleHeader.displayName = "Bubble.Header";
4016
+ var BubbleFooter = forwardRef((props, ref) => {
4017
+ const { asChild, children, ...domProps } = props;
4018
+ const Comp = asChild ? Slot : "div";
4019
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-bubble-part": "footer", ...domProps, children });
4020
+ });
4021
+ BubbleFooter.displayName = "Bubble.Footer";
4022
+ var BubbleMetadata = forwardRef((props, ref) => {
4023
+ const { asChild, children, ...domProps } = props;
4024
+ const Comp = asChild ? Slot : "div";
4025
+ return /* @__PURE__ */ jsx(Comp, { ref, "data-bubble-part": "metadata", ...domProps, children });
4026
+ });
4027
+ BubbleMetadata.displayName = "Bubble.Metadata";
4028
+ var Bubble = {
4029
+ Root: BubbleRoot,
4030
+ Content: BubbleContent,
4031
+ Header: BubbleHeader,
4032
+ Footer: BubbleFooter,
4033
+ Metadata: BubbleMetadata
4034
+ };
4035
+ var ActionButtonContext = createContext(void 0);
4036
+ function useActionButtonContext() {
4037
+ const context = useContext(ActionButtonContext);
4038
+ if (!context) {
4039
+ throw new Error("ActionButton components must be used within ActionButton.Root");
2420
4040
  }
2421
- return config.user.permissions.includes(permission);
4041
+ return context;
2422
4042
  }
4043
+ var ActionButtonRoot = forwardRef((props, ref) => {
4044
+ const { asChild, disabled, children, onMouseEnter, onMouseLeave, onFocus, onBlur, onMouseDown, onMouseUp, ...domProps } = props;
4045
+ const Comp = asChild ? Slot : "button";
4046
+ const [isHovered, setIsHovered] = useState(false);
4047
+ const [isFocused, setIsFocused] = useState(false);
4048
+ const [isPressed, setIsPressed] = useState(false);
4049
+ const handleMouseEnter = (e) => {
4050
+ setIsHovered(true);
4051
+ onMouseEnter?.(e);
4052
+ };
4053
+ const handleMouseLeave = (e) => {
4054
+ setIsHovered(false);
4055
+ setIsPressed(false);
4056
+ onMouseLeave?.(e);
4057
+ };
4058
+ const handleFocus = (e) => {
4059
+ setIsFocused(true);
4060
+ onFocus?.(e);
4061
+ };
4062
+ const handleBlur = (e) => {
4063
+ setIsFocused(false);
4064
+ onBlur?.(e);
4065
+ };
4066
+ const handleMouseDown = (e) => {
4067
+ setIsPressed(true);
4068
+ onMouseDown?.(e);
4069
+ };
4070
+ const handleMouseUp = (e) => {
4071
+ setIsPressed(false);
4072
+ onMouseUp?.(e);
4073
+ };
4074
+ return /* @__PURE__ */ jsx(
4075
+ ActionButtonContext.Provider,
4076
+ {
4077
+ value: {
4078
+ isHovered,
4079
+ isFocused,
4080
+ isPressed,
4081
+ isDisabled: !!disabled
4082
+ },
4083
+ children: /* @__PURE__ */ jsx(
4084
+ Comp,
4085
+ {
4086
+ ref,
4087
+ type: "button",
4088
+ disabled,
4089
+ onMouseEnter: handleMouseEnter,
4090
+ onMouseLeave: handleMouseLeave,
4091
+ onFocus: handleFocus,
4092
+ onBlur: handleBlur,
4093
+ onMouseDown: handleMouseDown,
4094
+ onMouseUp: handleMouseUp,
4095
+ ...domProps,
4096
+ children
4097
+ }
4098
+ )
4099
+ }
4100
+ );
4101
+ });
4102
+ ActionButtonRoot.displayName = "ActionButton.Root";
4103
+ var ActionButtonIcon = forwardRef((props, ref) => {
4104
+ const { asChild, children, ...domProps } = props;
4105
+ const Comp = asChild ? Slot : "span";
4106
+ return /* @__PURE__ */ jsx(Comp, { ref, "aria-hidden": "true", ...domProps, children });
4107
+ });
4108
+ ActionButtonIcon.displayName = "ActionButton.Icon";
4109
+ var ActionButtonLabel = forwardRef((props, ref) => {
4110
+ const { asChild, srOnly = false, children, className, ...domProps } = props;
4111
+ const Comp = asChild ? Slot : "span";
4112
+ const srOnlyClass = srOnly ? "absolute w-px h-px p-0 -m-px overflow-hidden whitespace-nowrap border-0" : "";
4113
+ return /* @__PURE__ */ jsx(Comp, { ref, className: [srOnlyClass, className].filter(Boolean).join(" "), ...domProps, children });
4114
+ });
4115
+ ActionButtonLabel.displayName = "ActionButton.Label";
4116
+ var ActionButtonTooltip = forwardRef((props, ref) => {
4117
+ const { asChild, position = "top", showOnHover = true, children, ...domProps } = props;
4118
+ const context = useActionButtonContext();
4119
+ const Comp = asChild ? Slot : "span";
4120
+ if (showOnHover && !context.isHovered && !context.isFocused) {
4121
+ return null;
4122
+ }
4123
+ return /* @__PURE__ */ jsx(Comp, { ref, role: "tooltip", "data-tooltip-position": position, ...domProps, children });
4124
+ });
4125
+ ActionButtonTooltip.displayName = "ActionButton.Tooltip";
4126
+ var ActionButton = {
4127
+ Root: ActionButtonRoot,
4128
+ Icon: ActionButtonIcon,
4129
+ Label: ActionButtonLabel,
4130
+ Tooltip: ActionButtonTooltip
4131
+ };
2423
4132
  function useStreaming(options = {}) {
2424
4133
  const [content, setContent] = useState("");
2425
4134
  const [isStreaming, setIsStreaming] = useState(false);
@@ -2493,6 +4202,572 @@ function useStreaming(options = {}) {
2493
4202
  reset
2494
4203
  };
2495
4204
  }
4205
+ function generateId() {
4206
+ return Math.random().toString(36).substring(7);
4207
+ }
4208
+ function useToast() {
4209
+ const [toasts, setToasts] = useState([]);
4210
+ const showToast = useCallback(
4211
+ (type, message, duration) => {
4212
+ const id = generateId();
4213
+ setToasts((prev) => [...prev, { id, type, message, duration }]);
4214
+ },
4215
+ []
4216
+ );
4217
+ const dismiss = useCallback((id) => {
4218
+ setToasts((prev) => prev.filter((t) => t.id !== id));
4219
+ }, []);
4220
+ const dismissAll = useCallback(() => {
4221
+ setToasts([]);
4222
+ }, []);
4223
+ return {
4224
+ toasts,
4225
+ success: (msg, duration) => showToast("success", msg, duration),
4226
+ error: (msg, duration) => showToast("error", msg, duration),
4227
+ info: (msg, duration) => showToast("info", msg, duration),
4228
+ warning: (msg, duration) => showToast("warning", msg, duration),
4229
+ dismiss,
4230
+ dismissAll
4231
+ };
4232
+ }
4233
+ function useImageGallery(options = {}) {
4234
+ const { images: initialImages = [], wrap = false, onOpen, onClose, onNavigate } = options;
4235
+ const [isOpen, setIsOpen] = useState(false);
4236
+ const [currentIndex, setCurrentIndex] = useState(0);
4237
+ const [images, setImages] = useState(initialImages);
4238
+ const currentImage = useMemo(() => images[currentIndex], [images, currentIndex]);
4239
+ const hasPrev = useMemo(() => {
4240
+ if (wrap) return images.length > 1;
4241
+ return currentIndex > 0;
4242
+ }, [currentIndex, images.length, wrap]);
4243
+ const hasNext = useMemo(() => {
4244
+ if (wrap) return images.length > 1;
4245
+ return currentIndex < images.length - 1;
4246
+ }, [currentIndex, images.length, wrap]);
4247
+ const open = useCallback(
4248
+ (index, newImages) => {
4249
+ if (newImages) {
4250
+ setImages(newImages);
4251
+ }
4252
+ const targetImages = newImages || images;
4253
+ if (targetImages.length === 0) {
4254
+ setCurrentIndex(0);
4255
+ setIsOpen(true);
4256
+ return;
4257
+ }
4258
+ const safeIndex = Math.max(0, Math.min(index, targetImages.length - 1));
4259
+ setCurrentIndex(safeIndex);
4260
+ setIsOpen(true);
4261
+ onOpen?.(safeIndex);
4262
+ },
4263
+ [images, onOpen]
4264
+ );
4265
+ const close = useCallback(() => {
4266
+ setIsOpen(false);
4267
+ onClose?.();
4268
+ }, [onClose]);
4269
+ const prev = useCallback(() => {
4270
+ if (images.length < 2) return;
4271
+ if (!hasPrev && !wrap) return;
4272
+ setCurrentIndex((current) => {
4273
+ const newIndex = wrap ? (current - 1 + images.length) % images.length : Math.max(0, current - 1);
4274
+ onNavigate?.(newIndex, "prev");
4275
+ return newIndex;
4276
+ });
4277
+ }, [hasPrev, wrap, images.length, onNavigate]);
4278
+ const next = useCallback(() => {
4279
+ if (images.length < 2) return;
4280
+ if (!hasNext && !wrap) return;
4281
+ setCurrentIndex((current) => {
4282
+ const newIndex = wrap ? (current + 1) % images.length : Math.min(images.length - 1, current + 1);
4283
+ onNavigate?.(newIndex, "next");
4284
+ return newIndex;
4285
+ });
4286
+ }, [hasNext, wrap, images.length, onNavigate]);
4287
+ const goTo = useCallback(
4288
+ (index) => {
4289
+ const safeIndex = Math.max(0, Math.min(index, images.length - 1));
4290
+ setCurrentIndex(safeIndex);
4291
+ },
4292
+ [images.length]
4293
+ );
4294
+ const setImagesHandler = useCallback((newImages) => {
4295
+ setImages(newImages);
4296
+ if (newImages.length === 0) {
4297
+ setCurrentIndex(0);
4298
+ } else {
4299
+ setCurrentIndex((current) => Math.min(current, newImages.length - 1));
4300
+ }
4301
+ }, []);
4302
+ return {
4303
+ isOpen,
4304
+ currentIndex,
4305
+ currentImage,
4306
+ images,
4307
+ hasPrev,
4308
+ hasNext,
4309
+ open,
4310
+ close,
4311
+ prev,
4312
+ next,
4313
+ goTo,
4314
+ setImages: setImagesHandler
4315
+ };
4316
+ }
4317
+ function useAutoScroll(options = {}) {
4318
+ const { threshold = 100, smooth = true, onScroll } = options;
4319
+ const containerRef = useRef(null);
4320
+ const [isAtBottom, setIsAtBottom] = useState(true);
4321
+ const [shouldAutoScroll, setShouldAutoScroll] = useState(true);
4322
+ const checkIsAtBottom = useCallback(
4323
+ (container) => {
4324
+ const { scrollTop, scrollHeight, clientHeight } = container;
4325
+ return scrollHeight - scrollTop - clientHeight <= threshold;
4326
+ },
4327
+ [threshold]
4328
+ );
4329
+ const handleScroll = useCallback(
4330
+ (e) => {
4331
+ const container = e.currentTarget;
4332
+ const atBottom = checkIsAtBottom(container);
4333
+ setIsAtBottom(atBottom);
4334
+ setShouldAutoScroll(atBottom);
4335
+ onScroll?.(atBottom);
4336
+ },
4337
+ [checkIsAtBottom, onScroll]
4338
+ );
4339
+ const scrollToBottom = useCallback(
4340
+ (useSmooth) => {
4341
+ const container = containerRef.current;
4342
+ if (!container) return;
4343
+ const shouldSmooth = useSmooth ?? smooth;
4344
+ container.scrollTo({
4345
+ top: container.scrollHeight,
4346
+ behavior: shouldSmooth ? "smooth" : "auto"
4347
+ });
4348
+ setIsAtBottom(true);
4349
+ setShouldAutoScroll(true);
4350
+ },
4351
+ [smooth]
4352
+ );
4353
+ const setAutoScroll = useCallback((enabled) => {
4354
+ setShouldAutoScroll(enabled);
4355
+ if (enabled) {
4356
+ const container = containerRef.current;
4357
+ if (container) {
4358
+ container.scrollTo({
4359
+ top: container.scrollHeight,
4360
+ behavior: "auto"
4361
+ });
4362
+ setIsAtBottom(true);
4363
+ }
4364
+ }
4365
+ }, []);
4366
+ useEffect(() => {
4367
+ const container = containerRef.current;
4368
+ if (!container) return;
4369
+ let rafId = null;
4370
+ const observer = new MutationObserver(() => {
4371
+ if (shouldAutoScroll) {
4372
+ if (rafId === null) {
4373
+ rafId = requestAnimationFrame(() => {
4374
+ container.scrollTo({
4375
+ top: container.scrollHeight,
4376
+ behavior: "instant"
4377
+ });
4378
+ rafId = null;
4379
+ });
4380
+ }
4381
+ }
4382
+ });
4383
+ observer.observe(container, {
4384
+ childList: true,
4385
+ subtree: true,
4386
+ characterData: true
4387
+ });
4388
+ return () => {
4389
+ observer.disconnect();
4390
+ if (rafId !== null) {
4391
+ cancelAnimationFrame(rafId);
4392
+ }
4393
+ };
4394
+ }, [shouldAutoScroll]);
4395
+ return {
4396
+ containerRef,
4397
+ isAtBottom,
4398
+ shouldAutoScroll,
4399
+ scrollToBottom,
4400
+ setAutoScroll,
4401
+ handleScroll
4402
+ };
4403
+ }
4404
+ function useMessageActions(options = {}) {
4405
+ const { onCopy, onCopyError, onRegenerate, onEdit, copiedDuration = 2e3 } = options;
4406
+ const [isCopied, setIsCopied] = useState(false);
4407
+ const [isRegenerating, setIsRegenerating] = useState(false);
4408
+ const [isEditing, setIsEditing] = useState(false);
4409
+ const copiedTimeoutRef = useRef(null);
4410
+ useEffect(() => {
4411
+ return () => {
4412
+ if (copiedTimeoutRef.current) {
4413
+ clearTimeout(copiedTimeoutRef.current);
4414
+ copiedTimeoutRef.current = null;
4415
+ }
4416
+ };
4417
+ }, []);
4418
+ const copy = useCallback(
4419
+ async (content) => {
4420
+ try {
4421
+ await navigator.clipboard.writeText(content);
4422
+ setIsCopied(true);
4423
+ onCopy?.(content);
4424
+ if (copiedTimeoutRef.current) {
4425
+ clearTimeout(copiedTimeoutRef.current);
4426
+ }
4427
+ copiedTimeoutRef.current = setTimeout(() => {
4428
+ setIsCopied(false);
4429
+ copiedTimeoutRef.current = null;
4430
+ }, copiedDuration);
4431
+ } catch (error) {
4432
+ const err = error instanceof Error ? error : new Error("Failed to copy");
4433
+ onCopyError?.(err);
4434
+ throw err;
4435
+ }
4436
+ },
4437
+ [onCopy, onCopyError, copiedDuration]
4438
+ );
4439
+ const regenerate = useCallback(async () => {
4440
+ if (!onRegenerate) return;
4441
+ if (isRegenerating) return;
4442
+ setIsRegenerating(true);
4443
+ try {
4444
+ await onRegenerate();
4445
+ } finally {
4446
+ setIsRegenerating(false);
4447
+ }
4448
+ }, [onRegenerate, isRegenerating]);
4449
+ const edit = useCallback(
4450
+ async (content) => {
4451
+ if (!onEdit) return;
4452
+ if (isEditing) return;
4453
+ setIsEditing(true);
4454
+ try {
4455
+ await onEdit(content);
4456
+ } finally {
4457
+ setIsEditing(false);
4458
+ }
4459
+ },
4460
+ [onEdit, isEditing]
4461
+ );
4462
+ const reset = useCallback(() => {
4463
+ setIsCopied(false);
4464
+ setIsRegenerating(false);
4465
+ setIsEditing(false);
4466
+ if (copiedTimeoutRef.current) {
4467
+ clearTimeout(copiedTimeoutRef.current);
4468
+ copiedTimeoutRef.current = null;
4469
+ }
4470
+ }, []);
4471
+ return {
4472
+ isCopied,
4473
+ isRegenerating,
4474
+ isEditing,
4475
+ copy,
4476
+ regenerate,
4477
+ edit,
4478
+ reset
4479
+ };
4480
+ }
4481
+ var DEFAULT_MAX_FILES = 10;
4482
+ var DEFAULT_MAX_FILE_SIZE = 10 * 1024 * 1024;
4483
+ var DEFAULT_ALLOWED_TYPES = ["image/jpeg", "image/png", "image/gif", "image/webp"];
4484
+ function generateId2() {
4485
+ return `attachment-${Date.now()}-${Math.random().toString(36).substring(7)}`;
4486
+ }
4487
+ function isImageFile(file) {
4488
+ return file.type.startsWith("image/");
4489
+ }
4490
+ async function createImagePreview(file) {
4491
+ return new Promise((resolve, reject) => {
4492
+ const reader = new FileReader();
4493
+ reader.onload = () => resolve(reader.result);
4494
+ reader.onerror = () => reject(new Error("Failed to read file"));
4495
+ reader.readAsDataURL(file);
4496
+ });
4497
+ }
4498
+ function useAttachments(options = {}) {
4499
+ const {
4500
+ maxFiles = DEFAULT_MAX_FILES,
4501
+ maxFileSize = DEFAULT_MAX_FILE_SIZE,
4502
+ allowedTypes = DEFAULT_ALLOWED_TYPES,
4503
+ validate,
4504
+ onAdd,
4505
+ onRemove,
4506
+ onError
4507
+ } = options;
4508
+ const [files, setFiles] = useState([]);
4509
+ const [errors, setErrors] = useState([]);
4510
+ const [isProcessing, setIsProcessing] = useState(false);
4511
+ const removedRef = useRef(null);
4512
+ const isMaxReached = useMemo(() => files.length >= maxFiles, [files.length, maxFiles]);
4513
+ const remainingSlots = useMemo(() => Math.max(0, maxFiles - files.length), [files.length, maxFiles]);
4514
+ const validateFile = useCallback(
4515
+ (file, currentCount) => {
4516
+ if (currentCount >= maxFiles) {
4517
+ return {
4518
+ file,
4519
+ reason: "count",
4520
+ message: `Maximum ${maxFiles} files allowed`
4521
+ };
4522
+ }
4523
+ if (file.size > maxFileSize) {
4524
+ const maxSizeMB = (maxFileSize / (1024 * 1024)).toFixed(1);
4525
+ return {
4526
+ file,
4527
+ reason: "size",
4528
+ message: `File "${file.name}" exceeds ${maxSizeMB}MB limit`
4529
+ };
4530
+ }
4531
+ if (allowedTypes.length > 0 && !allowedTypes.includes(file.type)) {
4532
+ return {
4533
+ file,
4534
+ reason: "type",
4535
+ message: `File type "${file.type || "unknown"}" not allowed`
4536
+ };
4537
+ }
4538
+ if (validate) {
4539
+ const customError = validate(file);
4540
+ if (customError) {
4541
+ return {
4542
+ file,
4543
+ reason: "custom",
4544
+ message: customError
4545
+ };
4546
+ }
4547
+ }
4548
+ return null;
4549
+ },
4550
+ [maxFiles, maxFileSize, allowedTypes, validate]
4551
+ );
4552
+ const add = useCallback(
4553
+ async (newFiles) => {
4554
+ if (isProcessing) {
4555
+ const fileArray2 = Array.from(newFiles);
4556
+ if (fileArray2.length > 0) {
4557
+ const processingErrors = fileArray2.map((file) => ({
4558
+ file,
4559
+ reason: "custom",
4560
+ message: "Please wait for the current files to finish processing."
4561
+ }));
4562
+ setErrors(processingErrors);
4563
+ onError?.(processingErrors);
4564
+ }
4565
+ return;
4566
+ }
4567
+ setIsProcessing(true);
4568
+ const fileArray = Array.from(newFiles);
4569
+ const validationErrors = [];
4570
+ const validFiles = [];
4571
+ let currentCount = files.length;
4572
+ for (const file of fileArray) {
4573
+ const error = validateFile(file, currentCount);
4574
+ if (error) {
4575
+ validationErrors.push(error);
4576
+ continue;
4577
+ }
4578
+ const attachment = {
4579
+ id: generateId2(),
4580
+ filename: file.name,
4581
+ mimeType: file.type,
4582
+ sizeBytes: file.size
4583
+ };
4584
+ if (isImageFile(file)) {
4585
+ try {
4586
+ const preview = await createImagePreview(file);
4587
+ attachment.base64Data = preview;
4588
+ } catch {
4589
+ }
4590
+ }
4591
+ validFiles.push(attachment);
4592
+ currentCount++;
4593
+ }
4594
+ if (validationErrors.length > 0) {
4595
+ setErrors(validationErrors);
4596
+ onError?.(validationErrors);
4597
+ }
4598
+ if (validFiles.length > 0) {
4599
+ setFiles((prev) => [...prev, ...validFiles]);
4600
+ onAdd?.(validFiles);
4601
+ }
4602
+ setIsProcessing(false);
4603
+ },
4604
+ [files.length, validateFile, onAdd, onError, isProcessing]
4605
+ );
4606
+ const remove = useCallback(
4607
+ (fileOrId) => {
4608
+ const id = typeof fileOrId === "string" ? fileOrId : fileOrId.id;
4609
+ setFiles((prev) => {
4610
+ const removed2 = prev.find((f) => f.id === id) ?? null;
4611
+ const next = prev.filter((f) => f.id !== id);
4612
+ removedRef.current = removed2;
4613
+ return next;
4614
+ });
4615
+ const removed = removedRef.current;
4616
+ if (removed) {
4617
+ onRemove?.(removed);
4618
+ removedRef.current = null;
4619
+ }
4620
+ },
4621
+ [onRemove]
4622
+ );
4623
+ const clear = useCallback(() => {
4624
+ setFiles([]);
4625
+ setErrors([]);
4626
+ }, []);
4627
+ const clearErrors = useCallback(() => {
4628
+ setErrors([]);
4629
+ }, []);
4630
+ const setFilesHandler = useCallback((newFiles) => {
4631
+ setFiles(newFiles);
4632
+ }, []);
4633
+ return {
4634
+ files,
4635
+ errors,
4636
+ isProcessing,
4637
+ isMaxReached,
4638
+ remainingSlots,
4639
+ add,
4640
+ remove,
4641
+ clear,
4642
+ clearErrors,
4643
+ setFiles: setFilesHandler
4644
+ };
4645
+ }
4646
+ function useMarkdownCopy(options = {}) {
4647
+ const { copiedDuration = 2e3, onCopy, onError } = options;
4648
+ const [copiedStates, setCopiedStates] = useState(/* @__PURE__ */ new Map());
4649
+ const timeoutsRef = useRef(/* @__PURE__ */ new Map());
4650
+ useEffect(() => {
4651
+ return () => {
4652
+ timeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
4653
+ timeoutsRef.current.clear();
4654
+ };
4655
+ }, []);
4656
+ const isCopied = useCallback(
4657
+ (blockId) => {
4658
+ return copiedStates.get(blockId) ?? false;
4659
+ },
4660
+ [copiedStates]
4661
+ );
4662
+ const copy = useCallback(
4663
+ async (blockId, content, language) => {
4664
+ try {
4665
+ await navigator.clipboard.writeText(content);
4666
+ setCopiedStates((prev) => {
4667
+ const next = new Map(prev);
4668
+ next.set(blockId, true);
4669
+ return next;
4670
+ });
4671
+ onCopy?.(content, language);
4672
+ const existingTimeout = timeoutsRef.current.get(blockId);
4673
+ if (existingTimeout) {
4674
+ clearTimeout(existingTimeout);
4675
+ }
4676
+ const timeout = setTimeout(() => {
4677
+ setCopiedStates((prev) => {
4678
+ const next = new Map(prev);
4679
+ next.set(blockId, false);
4680
+ return next;
4681
+ });
4682
+ timeoutsRef.current.delete(blockId);
4683
+ }, copiedDuration);
4684
+ timeoutsRef.current.set(blockId, timeout);
4685
+ } catch (error) {
4686
+ const err = error instanceof Error ? error : new Error("Failed to copy");
4687
+ onError?.(err);
4688
+ throw err;
4689
+ }
4690
+ },
4691
+ [copiedDuration, onCopy, onError]
4692
+ );
4693
+ const reset = useCallback((blockId) => {
4694
+ setCopiedStates((prev) => {
4695
+ const next = new Map(prev);
4696
+ next.set(blockId, false);
4697
+ return next;
4698
+ });
4699
+ const timeout = timeoutsRef.current.get(blockId);
4700
+ if (timeout) {
4701
+ clearTimeout(timeout);
4702
+ timeoutsRef.current.delete(blockId);
4703
+ }
4704
+ }, []);
4705
+ const resetAll = useCallback(() => {
4706
+ setCopiedStates(/* @__PURE__ */ new Map());
4707
+ timeoutsRef.current.forEach((timeout) => clearTimeout(timeout));
4708
+ timeoutsRef.current.clear();
4709
+ }, []);
4710
+ return {
4711
+ copiedStates,
4712
+ isCopied,
4713
+ copy,
4714
+ reset,
4715
+ resetAll
4716
+ };
4717
+ }
4718
+ var ConfigContext = createContext(null);
4719
+ function ConfigProvider({ config, useGlobalConfig = false, children }) {
4720
+ let resolvedConfig = null;
4721
+ if (config) {
4722
+ resolvedConfig = config;
4723
+ } else if (useGlobalConfig && typeof window !== "undefined") {
4724
+ const globalContext = window.__BICHAT_CONTEXT__;
4725
+ const globalCSRF = window.__CSRF_TOKEN__;
4726
+ if (globalContext) {
4727
+ resolvedConfig = {
4728
+ user: {
4729
+ id: String(globalContext.user?.id || ""),
4730
+ email: globalContext.user?.email || "",
4731
+ firstName: globalContext.user?.firstName || "",
4732
+ lastName: globalContext.user?.lastName || "",
4733
+ permissions: globalContext.user?.permissions || []
4734
+ },
4735
+ tenant: {
4736
+ id: globalContext.tenant?.id || "",
4737
+ name: globalContext.tenant?.name || ""
4738
+ },
4739
+ locale: {
4740
+ language: globalContext.locale?.language || "en",
4741
+ translations: globalContext.locale?.translations || {}
4742
+ },
4743
+ endpoints: {
4744
+ graphQL: globalContext.config?.graphQLEndpoint || "/graphql",
4745
+ stream: globalContext.config?.streamEndpoint || "/stream"
4746
+ },
4747
+ csrfToken: globalCSRF
4748
+ };
4749
+ }
4750
+ }
4751
+ return /* @__PURE__ */ jsx(ConfigContext.Provider, { value: resolvedConfig, children });
4752
+ }
4753
+ function useConfig() {
4754
+ return useContext(ConfigContext);
4755
+ }
4756
+ function useRequiredConfig() {
4757
+ const config = useContext(ConfigContext);
4758
+ if (!config) {
4759
+ throw new Error(
4760
+ "BiChat configuration not found. Wrap your app with <ConfigProvider config={...}> or use useGlobalConfig={true}."
4761
+ );
4762
+ }
4763
+ return config;
4764
+ }
4765
+ function hasPermission2(config, permission) {
4766
+ if (!config) {
4767
+ return false;
4768
+ }
4769
+ return config.user.permissions.includes(permission);
4770
+ }
2496
4771
 
2497
4772
  // ui/src/bichat/theme/themes.ts
2498
4773
  var lightTheme = {
@@ -2725,7 +5000,7 @@ var HttpDataSource = class {
2725
5000
  return data.createChatSession;
2726
5001
  }
2727
5002
  /**
2728
- * Fetch an existing session with messages
5003
+ * Fetch an existing session with turns (turn-based architecture)
2729
5004
  */
2730
5005
  async fetchSession(id) {
2731
5006
  const query = `
@@ -2739,46 +5014,79 @@ var HttpDataSource = class {
2739
5014
  createdAt
2740
5015
  updatedAt
2741
5016
  }
2742
- messages {
5017
+ turns {
2743
5018
  id
2744
5019
  sessionId
2745
- role
2746
- content
2747
5020
  createdAt
2748
- toolCalls {
5021
+ userTurn {
2749
5022
  id
2750
- name
2751
- arguments
5023
+ content
5024
+ attachments {
5025
+ id
5026
+ filename
5027
+ mimeType
5028
+ sizeBytes
5029
+ base64Data
5030
+ }
5031
+ createdAt
2752
5032
  }
2753
- citations {
5033
+ assistantTurn {
2754
5034
  id
2755
- source
2756
- url
2757
- excerpt
2758
- }
2759
- chartData {
2760
- type
2761
- title
2762
- data
2763
- xAxisKey
2764
- yAxisKey
2765
- }
2766
- artifacts {
2767
- type
2768
- filename
2769
- url
2770
- sizeReadable
2771
- rowCount
2772
- description
5035
+ content
5036
+ explanation
5037
+ citations {
5038
+ id
5039
+ type
5040
+ title
5041
+ url
5042
+ startIndex
5043
+ endIndex
5044
+ excerpt
5045
+ source
5046
+ }
5047
+ chartData {
5048
+ chartType
5049
+ title
5050
+ series {
5051
+ name
5052
+ data
5053
+ }
5054
+ labels
5055
+ colors
5056
+ height
5057
+ }
5058
+ artifacts {
5059
+ type
5060
+ filename
5061
+ url
5062
+ sizeReadable
5063
+ rowCount
5064
+ description
5065
+ }
5066
+ codeOutputs {
5067
+ type
5068
+ content
5069
+ filename
5070
+ mimeType
5071
+ sizeBytes
5072
+ }
5073
+ createdAt
2773
5074
  }
2774
- explanation
2775
5075
  }
2776
5076
  pendingQuestion {
2777
5077
  id
2778
5078
  turnId
2779
- question
2780
- type
2781
- options
5079
+ questions {
5080
+ id
5081
+ text
5082
+ type
5083
+ options {
5084
+ id
5085
+ label
5086
+ value
5087
+ }
5088
+ required
5089
+ }
2782
5090
  status
2783
5091
  }
2784
5092
  }
@@ -2954,6 +5262,18 @@ function createHttpDataSource(config) {
2954
5262
  return new HttpDataSource(config);
2955
5263
  }
2956
5264
 
2957
- export { AssistantTurnView, AttachmentGrid, ChartCard, ChatHeader, ChatSession, ChatSessionProvider, CodeOutputsPanel, ConfigProvider, DownloadCard, EditableText_default as EditableText, EmptyState_default as EmptyState, HttpDataSource, ImageModal, InlineQuestionForm, IotaContextProvider, ListItemSkeleton, MarkdownRenderer, MessageInput, MessageList, MessageRole, RateLimiter, ScrollToBottomButton, SearchInput_default as SearchInput, Skeleton_default as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, SourcesPanel, StreamingCursor, ThemeProvider, TurnBubble, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, hasPermission2 as hasConfigPermission, hasPermission, lightTheme, listItemVariants, messageContainerVariants, messageVariants, scaleFadeVariants, staggerContainerVariants, toastVariants, typingDotVariants, useChat, useConfig, useIotaContext, useRequiredConfig, useStreaming, useTheme, useTranslation, validateFileCount, validateImageFile };
5265
+ // ui/src/bichat/index.ts
5266
+ init_citationProcessor();
5267
+
5268
+ // ui/src/bichat/types/index.ts
5269
+ var MessageRole = /* @__PURE__ */ ((MessageRole2) => {
5270
+ MessageRole2["User"] = "user";
5271
+ MessageRole2["Assistant"] = "assistant";
5272
+ MessageRole2["System"] = "system";
5273
+ MessageRole2["Tool"] = "tool";
5274
+ return MessageRole2;
5275
+ })(MessageRole || {});
5276
+
5277
+ export { ActionButton, AssistantMessage, AssistantTurnView, MemoizedAttachmentGrid as AttachmentGrid, Avatar, Bubble, ChartCard, ChatHeader, ChatSession, ChatSessionProvider, MemoizedCodeBlock as CodeBlock, CodeOutputsPanel, ConfigProvider, ConfirmModal, DefaultErrorContent, DownloadCard, MemoizedEditableText as EditableText, MemoizedEmptyState as EmptyState, ErrorBoundary, HttpDataSource, ImageModal, InlineQuestionForm, IotaContextProvider, ListItemSkeleton, MemoizedLoadingSpinner as LoadingSpinner, MemoizedMarkdownRenderer as MarkdownRenderer, MessageInput, MessageList, MessageRole, PermissionGuard, RateLimiter, ScrollToBottomButton, MemoizedSearchInput as SearchInput, MemoizedSkeleton as Skeleton, SkeletonAvatar, SkeletonCard, SkeletonGroup, SkeletonText, Slot, SourcesPanel, StreamingCursor, TableExportButton, TableWithExport, ThemeProvider, Toast, ToastContainer, Turn, TurnBubble, MemoizedTypingIndicator as TypingIndicator, MemoizedUserAvatar as UserAvatar, UserMessage, UserTurnView, WelcomeContent, addCSRFHeader, backdropVariants, buttonVariants, convertToBase64, createDataUrl, createHeadersWithCSRF, createHttpDataSource, darkTheme, dropdownVariants, fadeInUpVariants, fadeInVariants, floatingButtonVariants, formatFileSize, getCSRFToken, getValidChildren, hasPermission2 as hasConfigPermission, hasPermission, lightTheme, listItemVariants, messageContainerVariants, messageVariants, processCitations, scaleFadeVariants, staggerContainerVariants, toastVariants, typingDotVariants, useActionButtonContext, useAttachments, useAutoScroll, useAvatarContext, useBubbleContext, useChat, useConfig, useFocusTrap, useImageGallery, useIotaContext, useMarkdownCopy, useMessageActions, useModalLock, useRequiredConfig, useStreaming, useTheme, useToast, useTranslation, useTurnContext, validateFileCount, validateImageFile };
2958
5278
  //# sourceMappingURL=index.mjs.map
2959
5279
  //# sourceMappingURL=index.mjs.map