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