orchid-ai 1.0.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.
Files changed (52) hide show
  1. package/README.md +225 -0
  2. package/dist/components/ChatPanel.d.ts +123 -0
  3. package/dist/components/Conversation.d.ts +75 -0
  4. package/dist/components/ErrorBoundary.d.ts +16 -0
  5. package/dist/components/Icon.d.ts +84 -0
  6. package/dist/components/ModelSwitcher.d.ts +24 -0
  7. package/dist/components/SuggestionsPanel.d.ts +27 -0
  8. package/dist/constants.d.ts +353 -0
  9. package/dist/hooks/useAiMerge.d.ts +20 -0
  10. package/dist/hooks/useModelSwitcher.d.ts +65 -0
  11. package/dist/hooks/useStreamingAI.d.ts +29 -0
  12. package/dist/hooks/useSuggestions.d.ts +48 -0
  13. package/dist/index.d.ts +13 -0
  14. package/dist/index.esm.js +3385 -0
  15. package/dist/index.js +3400 -0
  16. package/dist/server/components/ChatPanel.d.ts +123 -0
  17. package/dist/server/components/Conversation.d.ts +75 -0
  18. package/dist/server/components/ErrorBoundary.d.ts +16 -0
  19. package/dist/server/components/Icon.d.ts +84 -0
  20. package/dist/server/components/ModelSwitcher.d.ts +24 -0
  21. package/dist/server/components/SuggestionsPanel.d.ts +27 -0
  22. package/dist/server/constants.d.ts +353 -0
  23. package/dist/server/contextual-service.d.ts +59 -0
  24. package/dist/server/document-processor.d.ts +60 -0
  25. package/dist/server/hooks/useAiMerge.d.ts +20 -0
  26. package/dist/server/hooks/useModelSwitcher.d.ts +65 -0
  27. package/dist/server/hooks/useStreamingAI.d.ts +29 -0
  28. package/dist/server/hooks/useSuggestions.d.ts +48 -0
  29. package/dist/server/index.d.ts +7 -0
  30. package/dist/server/index.esm.js +14008 -0
  31. package/dist/server/index.js +14019 -0
  32. package/dist/server/server/contextual-service.d.ts +59 -0
  33. package/dist/server/server/document-processor.d.ts +60 -0
  34. package/dist/server/server/index.d.ts +7 -0
  35. package/dist/server/server/server.d.ts +32 -0
  36. package/dist/server/server/service.d.ts +267 -0
  37. package/dist/server/server/training-schema.d.ts +17 -0
  38. package/dist/server/server/training.d.ts +231 -0
  39. package/dist/server/server/utils.d.ts +209 -0
  40. package/dist/server/server.d.ts +32 -0
  41. package/dist/server/service.d.ts +267 -0
  42. package/dist/server/training-schema.d.ts +17 -0
  43. package/dist/server/training.d.ts +231 -0
  44. package/dist/server/types/types.d.ts +481 -0
  45. package/dist/server/utils/fileHandler.d.ts +20 -0
  46. package/dist/server/utils/mergeWithAi.d.ts +19 -0
  47. package/dist/server/utils.d.ts +209 -0
  48. package/dist/types/types.d.ts +481 -0
  49. package/dist/utils/fileHandler.d.ts +20 -0
  50. package/dist/utils/mergeWithAi.d.ts +19 -0
  51. package/dist/utils.d.ts +19 -0
  52. package/package.json +137 -0
package/dist/index.js ADDED
@@ -0,0 +1,3400 @@
1
+ 'use strict';
2
+
3
+ var React = require('react');
4
+ var jsxRuntime = require('react/jsx-runtime');
5
+
6
+ const DEFAULT_CONFIG = {
7
+ service: 'claude',
8
+ model: 'claude-sonnet-4-20250514',
9
+ temperature: 0.7,
10
+ maxTokens: 4096,
11
+ chatLevel: 'none',
12
+ supportsImages: true,
13
+ };
14
+ // Default model configurations with compute weights
15
+ const DEFAULT_MODELS = {
16
+ openai: [
17
+ {
18
+ id: 'gpt-4-turbo',
19
+ name: 'GPT-4 Turbo',
20
+ provider: 'openai',
21
+ available: true,
22
+ supportsImages: true,
23
+ computeWeight: 1.0,
24
+ },
25
+ {
26
+ id: 'gpt-4o',
27
+ name: 'GPT-4o',
28
+ provider: 'openai',
29
+ available: true,
30
+ supportsImages: true,
31
+ computeWeight: 0.8,
32
+ },
33
+ {
34
+ id: 'gpt-4o-mini',
35
+ name: 'GPT-4o Mini',
36
+ provider: 'openai',
37
+ available: true,
38
+ supportsImages: true,
39
+ computeWeight: 0.3,
40
+ },
41
+ ],
42
+ claude: [
43
+ {
44
+ id: 'claude-opus-4-20250514',
45
+ name: 'Claude Opus 4',
46
+ provider: 'claude',
47
+ available: true,
48
+ supportsImages: true,
49
+ computeWeight: 1.2,
50
+ },
51
+ {
52
+ id: 'claude-sonnet-4-20250514',
53
+ name: 'Claude Sonnet 4',
54
+ provider: 'claude',
55
+ available: true,
56
+ supportsImages: true,
57
+ computeWeight: 0.6,
58
+ },
59
+ {
60
+ id: 'claude-3-5-haiku-20241022',
61
+ name: 'Claude Haiku 3.5',
62
+ provider: 'claude',
63
+ available: true,
64
+ supportsImages: true,
65
+ computeWeight: 0.2,
66
+ },
67
+ ],
68
+ gemini: [
69
+ {
70
+ id: 'gemini-2.5-flash-lite',
71
+ name: 'Gemini 2.5 Flash Lite',
72
+ provider: 'gemini',
73
+ available: true,
74
+ supportsImages: true,
75
+ computeWeight: 0.2,
76
+ },
77
+ {
78
+ id: 'gemini-2.5-flash',
79
+ name: 'Gemini 2.5 Flash',
80
+ provider: 'gemini',
81
+ available: true,
82
+ supportsImages: true,
83
+ computeWeight: 0.5,
84
+ },
85
+ {
86
+ id: 'gemini-2.5-pro',
87
+ name: 'Gemini 2.5 Pro',
88
+ provider: 'gemini',
89
+ available: true,
90
+ supportsImages: true,
91
+ computeWeight: 1.0,
92
+ },
93
+ ],
94
+ };
95
+ const defaultTheme = {
96
+ // Default blue theme
97
+ primary: '#3b82f6',
98
+ primaryHover: '#2563eb',
99
+ primaryLight: '#eff6ff',
100
+ secondary: '#6b7280',
101
+ secondaryHover: '#4b5563',
102
+ background: '#ffffff',
103
+ backgroundSecondary: '#f9fafb',
104
+ backgroundTertiary: '#f3f4f6',
105
+ textPrimary: '#111827',
106
+ textSecondary: '#6b7280',
107
+ textTertiary: '#9ca3af',
108
+ textInverse: '#ffffff',
109
+ borderPrimary: '#e5e7eb',
110
+ borderSecondary: '#d1d5db',
111
+ success: '#22c55e',
112
+ warning: '#f59e0b',
113
+ error: '#ef4444',
114
+ info: '#3b82f6',
115
+ };
116
+ // Default dark theme
117
+ const defaultDarkTheme = {
118
+ colors: {
119
+ primary: {
120
+ 50: 'rgb(239 246 255)', // blue-50
121
+ 100: 'rgb(219 234 254)', // blue-100
122
+ 400: 'rgb(96 165 250)', // blue-400
123
+ 500: 'rgb(59 130 246)', // blue-500
124
+ 600: 'rgb(37 99 235)', // blue-600
125
+ 700: 'rgb(29 78 216)', // blue-700
126
+ 800: 'rgb(30 64 175)', // blue-800
127
+ 900: 'rgb(30 58 138)', // blue-900
128
+ },
129
+ background: {
130
+ primary: 'rgb(17 24 39)', // gray-900
131
+ secondary: 'rgb(31 41 55)', // gray-800
132
+ tertiary: 'rgb(55 65 81)', // gray-700
133
+ },
134
+ text: {
135
+ primary: 'rgb(249 250 251)', // gray-50
136
+ secondary: 'rgb(209 213 219)', // gray-300
137
+ tertiary: 'rgb(156 163 175)', // gray-400
138
+ inverse: 'rgb(17 24 39)', // gray-900
139
+ },
140
+ border: {
141
+ primary: 'rgb(55 65 81)', // gray-700
142
+ secondary: 'rgb(75 85 99)', // gray-600
143
+ },
144
+ surface: {
145
+ primary: 'rgb(31 41 55)', // gray-800
146
+ secondary: 'rgb(55 65 81)', // gray-700
147
+ tertiary: 'rgb(75 85 99)', // gray-600
148
+ elevated: 'rgb(17 24 39)', // gray-900
149
+ },
150
+ state: {
151
+ error: {
152
+ background: 'rgb(127 29 29 / 0.2)', // red-900/20
153
+ border: 'rgb(153 27 27)', // red-800
154
+ text: 'rgb(248 113 113)', // red-400
155
+ },
156
+ warning: {
157
+ background: 'rgb(146 64 14 / 0.2)', // amber-900/20
158
+ border: 'rgb(180 83 9)', // amber-800
159
+ text: 'rgb(251 191 36)', // amber-400
160
+ },
161
+ success: {
162
+ background: 'rgb(20 83 45 / 0.2)', // green-900/20
163
+ border: 'rgb(22 101 52)', // green-800
164
+ text: 'rgb(74 222 128)', // green-400
165
+ },
166
+ },
167
+ },
168
+ spacing: {
169
+ xs: '0.25rem', // 1
170
+ sm: '0.5rem', // 2
171
+ md: '1rem', // 4
172
+ lg: '1.5rem', // 6
173
+ xl: '2rem', // 8
174
+ },
175
+ borderRadius: {
176
+ sm: '0.25rem',
177
+ md: '0.375rem',
178
+ lg: '0.5rem',
179
+ full: '9999px',
180
+ },
181
+ shadows: {
182
+ sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
183
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
184
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
185
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
186
+ },
187
+ };
188
+ // Default light theme
189
+ const defaultLightTheme = {
190
+ colors: {
191
+ primary: {
192
+ 50: 'rgb(239 246 255)', // blue-50
193
+ 100: 'rgb(219 234 254)', // blue-100
194
+ 400: 'rgb(96 165 250)', // blue-400
195
+ 500: 'rgb(59 130 246)', // blue-500
196
+ 600: 'rgb(37 99 235)', // blue-600
197
+ 700: 'rgb(29 78 216)', // blue-700
198
+ 800: 'rgb(30 64 175)', // blue-800
199
+ 900: 'rgb(30 58 138)', // blue-900
200
+ },
201
+ background: {
202
+ primary: 'rgb(255 255 255)', // white
203
+ secondary: 'rgb(249 250 251)', // gray-50
204
+ tertiary: 'rgb(243 244 246)', // gray-100
205
+ },
206
+ text: {
207
+ primary: 'rgb(17 24 39)', // gray-900
208
+ secondary: 'rgb(75 85 99)', // gray-600
209
+ tertiary: 'rgb(107 114 128)', // gray-500
210
+ inverse: 'rgb(255 255 255)', // white
211
+ },
212
+ border: {
213
+ primary: 'rgb(229 231 235)', // gray-200
214
+ secondary: 'rgb(209 213 219)', // gray-300
215
+ },
216
+ surface: {
217
+ primary: 'rgb(249 250 251)', // gray-50
218
+ secondary: 'rgb(243 244 246)', // gray-100
219
+ tertiary: 'rgb(229 231 235)', // gray-200
220
+ elevated: 'rgb(255 255 255)', // white
221
+ },
222
+ state: {
223
+ error: {
224
+ background: 'rgb(254 242 242)', // red-50
225
+ border: 'rgb(252 165 165)', // red-200
226
+ text: 'rgb(153 27 27)', // red-800
227
+ },
228
+ warning: {
229
+ background: 'rgb(255 251 235)', // amber-50
230
+ border: 'rgb(252 211 77)', // amber-200
231
+ text: 'rgb(146 64 14)', // amber-800
232
+ },
233
+ success: {
234
+ background: 'rgb(240 253 244)', // green-50
235
+ border: 'rgb(167 243 208)', // green-200
236
+ text: 'rgb(22 101 52)', // green-800
237
+ },
238
+ },
239
+ },
240
+ spacing: {
241
+ xs: '0.25rem',
242
+ sm: '0.5rem',
243
+ md: '1rem',
244
+ lg: '1.5rem',
245
+ xl: '2rem',
246
+ },
247
+ borderRadius: {
248
+ sm: '0.25rem',
249
+ md: '0.375rem',
250
+ lg: '0.5rem',
251
+ full: '9999px',
252
+ },
253
+ shadows: {
254
+ sm: '0 1px 2px 0 rgb(0 0 0 / 0.05)',
255
+ md: '0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1)',
256
+ lg: '0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1)',
257
+ xl: '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
258
+ },
259
+ };
260
+
261
+ function useStreamingAI({ userId, serverConfig, formData, chats: externalChats, setChats: externalSetChats, currentChatId: externalCurrentChatId, setCurrentChatId: externalSetCurrentChatId, modelSelection, onUsageTracked, chatLevel, additionalContext, verbose = false, }) {
262
+ // Map of chatId -> chat array
263
+ const [chats, setChats] = React.useState({});
264
+ const [currentChatId, setCurrentChatId] = React.useState(() => Date.now().toString());
265
+ const [isLoading, setIsLoading] = React.useState(false);
266
+ const chatsToUse = React.useMemo(() => (externalChats !== undefined ? externalChats : chats), [externalChats, chats]);
267
+ const setChatsToUse = React.useMemo(() => (externalSetChats !== undefined ? externalSetChats : setChats), [externalSetChats, setChats]);
268
+ const currentChatIdToUse = React.useMemo(() => externalCurrentChatId !== undefined
269
+ ? externalCurrentChatId
270
+ : currentChatId, [externalCurrentChatId, currentChatId]);
271
+ const setCurrentChatIdToUse = React.useMemo(() => externalSetCurrentChatId !== undefined
272
+ ? externalSetCurrentChatId
273
+ : setCurrentChatId, [externalSetCurrentChatId, setCurrentChatId]);
274
+ const log = (...args) => {
275
+ if (verbose) {
276
+ console.log(...args);
277
+ }
278
+ };
279
+ // Helper function to build URL from config
280
+ const buildUrlFromConfig = (config) => {
281
+ const protocol = config.secure !== undefined
282
+ ? config.secure
283
+ ? 'https:'
284
+ : 'http:'
285
+ : window.location.protocol;
286
+ const hostname = config.domain || window.location.hostname;
287
+ const port = config.port
288
+ ? `:${config.port}`
289
+ : window.location.port
290
+ ? `:${window.location.port}`
291
+ : '';
292
+ const suffix = config.suffix || '';
293
+ // If no domain specified, use relative URL (common with webpack proxy)
294
+ if (!config.domain && !config.port && !config.secure) {
295
+ return suffix;
296
+ }
297
+ return `${protocol}//${hostname}${port}${suffix}`;
298
+ };
299
+ // Helper to get current chat
300
+ const chat = chatsToUse[currentChatIdToUse] || [];
301
+ // Final cleanup function to fix any remaining formatting issues
302
+ const finalCleanup = (text) => {
303
+ return (text
304
+ // Fix spaces in email addresses (e.g., "max.ad ams" -> "max.adams")
305
+ .replace(/(\w+)\.(\w+)\s+(\w+)/g, '$1.$2$3')
306
+ // Fix missing spaces between words (e.g., "Company Corp Phone" -> "Company Corp Phone")
307
+ .replace(/([a-z])([A-Z])/g, '$1 $2')
308
+ // Remove the word "json" when it appears at the end of sentences
309
+ .replace(/\s+json\s*$/g, '')
310
+ .replace(/\s+json\s*\./g, '.')
311
+ // Fix any remaining spacing issues
312
+ .replace(/\s+/g, ' ')
313
+ .trim());
314
+ };
315
+ const sendQuery = (query, files, context) => {
316
+ // Allow sending if there's either text OR files
317
+ if (!query.trim() && (!files || files.length === 0)) {
318
+ log('❌ [CLIENT] Nothing to send - no text and no files');
319
+ return;
320
+ }
321
+ // Ensure we have a query when sending files
322
+ const finalQuery = query.trim()
323
+ ? query.trim()
324
+ : files && files.length > 0
325
+ ? 'Analyze the uploaded content and suggest relevant actions.'
326
+ : query;
327
+ const current = chatsToUse[currentChatIdToUse] || [];
328
+ const newMessages = [
329
+ ...current,
330
+ {
331
+ sender: 'user',
332
+ content: [{ type: 'text', content: finalQuery }],
333
+ timestamp: Date.now(),
334
+ files: files,
335
+ },
336
+ {
337
+ sender: 'ai',
338
+ content: [],
339
+ isLoading: true,
340
+ aiStatus: 'thinking',
341
+ timestamp: Date.now(),
342
+ },
343
+ ];
344
+ setChatsToUse((prev) => ({
345
+ ...prev,
346
+ [currentChatIdToUse]: newMessages,
347
+ }));
348
+ setIsLoading(true);
349
+ // Get current chat history (excluding the new messages we just added)
350
+ const chatHistory = current.map((msg) => ({
351
+ role: msg.sender === 'user' ? 'user' : 'assistant',
352
+ content: msg.content
353
+ .filter((block) => block.type === 'text')
354
+ .map((block) => block.content)
355
+ .join('\n'),
356
+ }));
357
+ // Build API URL from config with sensible default
358
+ const apiUrl = serverConfig ? buildUrlFromConfig(serverConfig) : '/api';
359
+ // Always use POST approach now - server will handle all file processing
360
+ if (files && files.length > 0) {
361
+ sendWithFiles(finalQuery, files, chatHistory, apiUrl, context);
362
+ }
363
+ else {
364
+ // For text-only, we'll still use POST for consistency
365
+ sendWithFiles(finalQuery, [], chatHistory, apiUrl, context);
366
+ }
367
+ };
368
+ // Unified POST approach - let server handle all file processing
369
+ const sendWithFiles = async (query, files, chatHistory, apiUrl, context // <-- Per-message context
370
+ ) => {
371
+ try {
372
+ // Convert files to a format the server can process
373
+ const filePromises = files.map(async (file) => {
374
+ return new Promise((resolve, reject) => {
375
+ const reader = new FileReader();
376
+ reader.onload = () => {
377
+ const result = reader.result;
378
+ const base64Data = result.split(',')[1]; // Remove data URL prefix
379
+ resolve({
380
+ name: file.name,
381
+ type: file.type,
382
+ size: file.size,
383
+ data: base64Data,
384
+ });
385
+ };
386
+ reader.onerror = reject;
387
+ reader.readAsDataURL(file);
388
+ });
389
+ });
390
+ const processedFiles = await Promise.all(filePromises);
391
+ // Extract schema, additionalContext, and stayOnPage from serverConfig
392
+ const schema = serverConfig?.schema;
393
+ const serverAdditionalContext = serverConfig?.additionalContext;
394
+ const stayOnPage = serverConfig?.stayOnPage;
395
+ // Build stay-on-page context instruction
396
+ const stayOnPageContext = stayOnPage
397
+ ? 'IMPORTANT: You are embedded within a specific page. Do NOT generate navigation suggestions or routes to other pages. Focus ONLY on helping with the current form/page content. Stay on this page and do not suggest navigating elsewhere.'
398
+ : '';
399
+ // Merge all context sources (stayOnPage context takes priority)
400
+ const mergedContext = [
401
+ stayOnPageContext,
402
+ serverAdditionalContext || '',
403
+ additionalContext ? (typeof additionalContext === 'string' ? additionalContext : JSON.stringify(additionalContext)) : '',
404
+ context || ''
405
+ ].filter(Boolean).join('\n\n');
406
+ const requestBody = {
407
+ query,
408
+ userId,
409
+ chatHistory,
410
+ chatLevel,
411
+ formData: formData || {},
412
+ files: processedFiles,
413
+ // New: Include model selection in request
414
+ modelSelection: modelSelection || {
415
+ provider: DEFAULT_CONFIG.service,
416
+ model: DEFAULT_CONFIG.model,
417
+ capabilities: {
418
+ supportsImages: DEFAULT_CONFIG.supportsImages,
419
+ computeWeight: 1.0,
420
+ },
421
+ },
422
+ ...(schema ? { schema } : {}), // <-- NEW: Include schema from serverConfig!
423
+ ...(mergedContext ? { additionalContext: mergedContext } : {}), // <-- Include merged context
424
+ };
425
+ // Use EventSource-style approach with POST for streaming (bypasses webpack proxy issues)
426
+ return new Promise((resolve, reject) => {
427
+ const xhr = new XMLHttpRequest();
428
+ xhr.open('POST', `${apiUrl}/suggest`, true);
429
+ xhr.setRequestHeader('Content-Type', 'application/json');
430
+ xhr.setRequestHeader('Accept', 'text/event-stream');
431
+ xhr.setRequestHeader('Cache-Control', 'no-cache');
432
+ xhr.setRequestHeader('X-Requested-With', 'XMLHttpRequest');
433
+ let buffer = '';
434
+ let isProcessing = false;
435
+ let processedLines = new Set(); // Track processed lines to prevent duplication
436
+ xhr.onreadystatechange = async () => {
437
+ if (xhr.readyState === 3) {
438
+ // LOADING - receiving data
439
+ if (isProcessing)
440
+ return; // Prevent overlapping processing
441
+ isProcessing = true;
442
+ const newData = xhr.responseText.substring(buffer.length);
443
+ buffer = xhr.responseText;
444
+ if (newData) {
445
+ // Process each line immediately as it arrives
446
+ const lines = newData.split('\n');
447
+ for (const line of lines) {
448
+ // Skip if we've already processed this line
449
+ if (processedLines.has(line))
450
+ continue;
451
+ processedLines.add(line);
452
+ if (line.startsWith('data: ')) {
453
+ try {
454
+ const data = JSON.parse(line.slice(6));
455
+ await handleStreamData(data);
456
+ // Force immediate UI update
457
+ await new Promise((resolve) => setTimeout(resolve, 0));
458
+ }
459
+ catch (e) {
460
+ console.warn('Failed to parse SSE data:', line);
461
+ }
462
+ }
463
+ else if (line.startsWith('event: end')) {
464
+ handleStreamEnd();
465
+ resolve();
466
+ return;
467
+ }
468
+ else if (line.startsWith('event: error')) {
469
+ const errorLine = lines.find((l) => l.startsWith('data: '));
470
+ if (errorLine) {
471
+ const errorData = JSON.parse(errorLine.slice(6));
472
+ handleStreamError(errorData);
473
+ }
474
+ reject(new Error('Stream error'));
475
+ return;
476
+ }
477
+ }
478
+ }
479
+ isProcessing = false;
480
+ }
481
+ else if (xhr.readyState === 4) {
482
+ // DONE - This is crucial for completion detection
483
+ // Process any remaining data in buffer (only unprocessed lines)
484
+ if (buffer) {
485
+ const lines = buffer.split('\n');
486
+ for (const line of lines) {
487
+ // Skip if we've already processed this line
488
+ if (processedLines.has(line))
489
+ continue;
490
+ processedLines.add(line);
491
+ if (line.startsWith('data: ')) {
492
+ try {
493
+ const data = JSON.parse(line.slice(6));
494
+ await handleStreamData(data);
495
+ }
496
+ catch (e) {
497
+ console.warn('Failed to parse final SSE data:', line);
498
+ }
499
+ }
500
+ }
501
+ }
502
+ // Always ensure loading state is cleared when XHR completes
503
+ setIsLoading(false);
504
+ // Also clear the message-level loading state
505
+ setChatsToUse((prev) => {
506
+ const currentChat = prev[currentChatIdToUse] || [];
507
+ const lastIdx = currentChat.length - 1;
508
+ const lastMsg = currentChat[lastIdx];
509
+ if (!lastMsg || lastMsg.sender !== 'ai')
510
+ return prev;
511
+ return {
512
+ ...prev,
513
+ [currentChatIdToUse]: [
514
+ ...currentChat.slice(0, lastIdx),
515
+ {
516
+ ...lastMsg,
517
+ isLoading: false,
518
+ aiStatus: 'none',
519
+ },
520
+ ],
521
+ };
522
+ });
523
+ if (xhr.status >= 200 && xhr.status < 300) {
524
+ resolve();
525
+ }
526
+ else {
527
+ reject(new Error(`HTTP error! status: ${xhr.status}`));
528
+ }
529
+ }
530
+ };
531
+ xhr.onerror = () => {
532
+ console.error('XHR error:', xhr.statusText);
533
+ handleStreamError({
534
+ message: xhr.statusText || 'Network error',
535
+ type: 'connection_error',
536
+ });
537
+ reject(new Error('Network error'));
538
+ };
539
+ // Add a timeout fallback to ensure loading state is cleared
540
+ const timeoutId = setTimeout(() => {
541
+ setIsLoading(false);
542
+ }, 10000); // 10 second timeout
543
+ xhr.send(JSON.stringify(requestBody));
544
+ // Clear timeout when request completes
545
+ const originalResolve = resolve;
546
+ resolve = () => {
547
+ clearTimeout(timeoutId);
548
+ originalResolve();
549
+ };
550
+ });
551
+ }
552
+ catch (error) {
553
+ console.error('Error sending request:', error);
554
+ handleStreamError({
555
+ message: error.message || 'Unknown error',
556
+ type: 'connection_error',
557
+ });
558
+ }
559
+ };
560
+ // Common stream data handler
561
+ const handleStreamData = async (data) => {
562
+ setChatsToUse((prev) => {
563
+ const currentChat = prev[currentChatIdToUse] || [];
564
+ const lastIdx = currentChat.length - 1;
565
+ const lastMsg = currentChat[lastIdx];
566
+ if (!lastMsg || lastMsg.sender !== 'ai')
567
+ return prev;
568
+ let updatedContent = [...(lastMsg.content || [])];
569
+ // Handle text content (simplified - backend handles JSON detection)
570
+ if (data.text) {
571
+ log('📥 [useStreamingAI] Raw text received:', JSON.stringify(data.text));
572
+ // Debug: Log character analysis
573
+ log(`🔍 [useStreamingAI] Character analysis of received text: "${data.text}"`);
574
+ log(`🔍 [useStreamingAI] Length: ${data.text.length}, Contains spaces: ${data.text.includes(' ')}, Contains newlines: ${data.text.includes('\n')}`);
575
+ // Debug: Check for 'd' characters specifically
576
+ if (data.text.includes('d')) {
577
+ // Log each 'd' character with its context
578
+ for (let i = 0; i < data.text.length; i++) {
579
+ if (data.text[i] === 'd') {
580
+ const before = data.text.substring(Math.max(0, i - 2), i);
581
+ const after = data.text.substring(i + 1, Math.min(data.text.length, i + 3));
582
+ log(`🔍 [useStreamingAI] 'd' at position ${i}: "${before}[d]${after}" (char codes: ${data.text.charCodeAt(i - 1)}, ${data.text.charCodeAt(i)}, ${data.text.charCodeAt(i + 1)})`);
583
+ }
584
+ }
585
+ }
586
+ // NO CLEANING - just use the raw text as-is
587
+ const rawText = data.text;
588
+ log('🧹 [useStreamingAI] After cleaning:', JSON.stringify(rawText));
589
+ // Only process if there's still content after cleaning
590
+ if (rawText.trim()) {
591
+ // If last content block is text, append to it; otherwise create new text block
592
+ const lastContentIdx = updatedContent.length - 1;
593
+ if (lastContentIdx >= 0 &&
594
+ updatedContent[lastContentIdx].type === 'text') {
595
+ const currentText = updatedContent[lastContentIdx].content;
596
+ // Add space between words if needed
597
+ let newText = currentText;
598
+ if (data.text.trim()) {
599
+ // For ChatGPT, we need to be more careful about spacing
600
+ // ChatGPT sends individual words with leading spaces, so we need to handle this properly
601
+ // SIMPLIFIED: Just use the text as-is from the backend
602
+ // The backend is already handling spacing correctly for each provider
603
+ newText = currentText + data.text;
604
+ log(`🔧 [useStreamingAI] Using text as-is from backend: "${currentText}" + "${data.text}"`);
605
+ }
606
+ log('📝 [useStreamingAI] Appending to existing text block:');
607
+ log(' Current text:', JSON.stringify(currentText));
608
+ log(' New text:', JSON.stringify(data.text));
609
+ log(' Combined text:', JSON.stringify(newText));
610
+ // Update the text block
611
+ updatedContent[lastContentIdx] = {
612
+ type: 'text',
613
+ content: newText,
614
+ };
615
+ }
616
+ else {
617
+ log('📝 [useStreamingAI] Creating new text block:', JSON.stringify(rawText));
618
+ // Create new text block
619
+ updatedContent.push({
620
+ type: 'text',
621
+ content: rawText,
622
+ });
623
+ }
624
+ }
625
+ }
626
+ // Handle loading suggestions (sent by backend when JSON starts)
627
+ if (data.type === 'loading-suggestions') {
628
+ log('🎯 [useStreamingAI] Received loading suggestions signal');
629
+ // Remove any existing loading-suggestions components
630
+ updatedContent = updatedContent.filter((block) => block.type !== 'loading-suggestions');
631
+ // Add loading suggestions component
632
+ updatedContent.push({
633
+ type: 'loading-suggestions',
634
+ content: data.message || 'Generating suggestions...',
635
+ });
636
+ }
637
+ // Handle suggestions (sent by backend when JSON is complete)
638
+ if (data.suggestions !== undefined) {
639
+ log('🎯 [useStreamingAI] Received suggestions:', data.suggestions);
640
+ // Remove any loading-suggestions components when we get actual suggestions
641
+ updatedContent = updatedContent.filter((block) => block.type !== 'loading-suggestions');
642
+ updatedContent.push({
643
+ type: 'suggestions',
644
+ content: data.suggestions,
645
+ });
646
+ }
647
+ // Handle JSON type - add suggestions if not already handled by text stream
648
+ if (data.type === 'json' &&
649
+ data.suggestions &&
650
+ data.suggestions.length > 0) {
651
+ // Check if we already have these suggestions (from text stream processing)
652
+ const hasSuggestions = updatedContent.some((block) => block.type === 'suggestions');
653
+ if (!hasSuggestions) {
654
+ // Add the suggestions to the content
655
+ updatedContent.push({
656
+ type: 'suggestions',
657
+ content: data.suggestions,
658
+ });
659
+ }
660
+ }
661
+ // Determine if we should still be loading based on the data
662
+ const shouldBeLoading = data.aiStatus === 'typing' ||
663
+ data.aiStatus === 'suggesting' ||
664
+ data.isIntermediate ||
665
+ (data.text && data.aiStatus !== 'none');
666
+ const updatedMsg = {
667
+ ...lastMsg,
668
+ content: updatedContent,
669
+ isLoading: shouldBeLoading,
670
+ aiStatus: data.aiStatus || (data.isIntermediate ? 'suggesting' : 'none'),
671
+ };
672
+ // If this is a completion signal, force loading to false
673
+ if (data.aiStatus === 'none' ||
674
+ data.usage ||
675
+ (data.type === 'text' && data.aiStatus === 'none')) {
676
+ updatedMsg.isLoading = false;
677
+ updatedMsg.aiStatus = 'none';
678
+ // Apply final cleanup to all text blocks when stream completes
679
+ updatedContent = updatedContent.map((block) => {
680
+ if (block.type === 'text') {
681
+ const cleanedContent = finalCleanup(block.content);
682
+ log('🧹 [useStreamingAI] Final cleanup applied:');
683
+ log(' Before:', JSON.stringify(block.content));
684
+ log(' After:', JSON.stringify(cleanedContent));
685
+ return {
686
+ ...block,
687
+ content: cleanedContent,
688
+ };
689
+ }
690
+ return block;
691
+ });
692
+ }
693
+ // Track usage when request completes (only for final responses)
694
+ if (!data.isIntermediate && data.usage && onUsageTracked) {
695
+ const tokens = data.usage.tokens || 0;
696
+ const computeWeight = modelSelection?.capabilities?.computeWeight || 1.0;
697
+ const computeUnits = tokens * computeWeight;
698
+ onUsageTracked(tokens, computeUnits);
699
+ }
700
+ return {
701
+ ...prev,
702
+ [currentChatIdToUse]: [...currentChat.slice(0, lastIdx), updatedMsg],
703
+ };
704
+ });
705
+ // Clear loading state when response is complete
706
+ if (data.aiStatus === 'none') {
707
+ // Explicit end of response
708
+ log('🎯 [useStreamingAI] Setting loading to false (aiStatus: none)');
709
+ setIsLoading(false);
710
+ }
711
+ else if (data.type === 'json' && !data.isIntermediate) {
712
+ // Final JSON response (not intermediate)
713
+ log('🎯 [useStreamingAI] Setting loading to false (final JSON)');
714
+ setIsLoading(false);
715
+ }
716
+ else if (data.type === 'text' && data.aiStatus === 'none') {
717
+ // Text with explicit completion signal
718
+ log('🎯 [useStreamingAI] Setting loading to false (text with aiStatus: none)');
719
+ setIsLoading(false);
720
+ }
721
+ else if (data.usage) {
722
+ // If we have usage data, this is likely the final response
723
+ log('🎯 [useStreamingAI] Setting loading to false (usage data received)');
724
+ setIsLoading(false);
725
+ }
726
+ else if (data.isIntermediate) {
727
+ // Keep loading for intermediate responses
728
+ log('🎯 [useStreamingAI] Setting loading to true (intermediate)');
729
+ setIsLoading(true);
730
+ }
731
+ else if (data.text && data.aiStatus === 'typing') {
732
+ // Keep loading for text streaming
733
+ log('🎯 [useStreamingAI] Keeping loading true (text streaming)');
734
+ setIsLoading(true);
735
+ }
736
+ else if (data.text && !data.aiStatus) {
737
+ // If we have text but no specific status, assume we're done
738
+ log('🎯 [useStreamingAI] Setting loading to false (text with no status)');
739
+ setIsLoading(false);
740
+ }
741
+ };
742
+ // Common stream end handler
743
+ const handleStreamEnd = () => {
744
+ setChatsToUse((prev) => {
745
+ const currentChat = prev[currentChatIdToUse] || [];
746
+ const lastIdx = currentChat.length - 1;
747
+ const lastMsg = currentChat[lastIdx];
748
+ if (!lastMsg || lastMsg.sender !== 'ai')
749
+ return prev;
750
+ return {
751
+ ...prev,
752
+ [currentChatIdToUse]: [
753
+ ...currentChat.slice(0, lastIdx),
754
+ {
755
+ ...lastMsg,
756
+ isLoading: false,
757
+ aiStatus: 'none',
758
+ },
759
+ ],
760
+ };
761
+ });
762
+ setIsLoading(false);
763
+ };
764
+ // Common stream error handler
765
+ const handleStreamError = (errorData) => {
766
+ try {
767
+ // Create a system error message in the chat
768
+ setChatsToUse((prev) => {
769
+ const currentChat = prev[currentChatIdToUse] || [];
770
+ const lastIdx = currentChat.length - 1;
771
+ // Remove the loading AI message and add an error message
772
+ const messagesWithoutLoading = currentChat.slice(0, lastIdx);
773
+ const errorMessage = {
774
+ sender: 'system',
775
+ content: [
776
+ {
777
+ type: 'text',
778
+ content: errorData.message ||
779
+ errorData.error ||
780
+ 'An error occurred while processing your request.',
781
+ },
782
+ ],
783
+ isError: true,
784
+ errorType: errorData.type || 'unknown_error',
785
+ retryAfter: errorData.retryAfter,
786
+ timestamp: Date.now(),
787
+ };
788
+ return {
789
+ ...prev,
790
+ [currentChatIdToUse]: [...messagesWithoutLoading, errorMessage],
791
+ };
792
+ });
793
+ }
794
+ catch (parseError) {
795
+ // Fallback for non-structured errors
796
+ setChatsToUse((prev) => {
797
+ const currentChat = prev[currentChatIdToUse] || [];
798
+ const lastIdx = currentChat.length - 1;
799
+ const messagesWithoutLoading = currentChat.slice(0, lastIdx);
800
+ const errorMessage = {
801
+ sender: 'system',
802
+ content: [
803
+ {
804
+ type: 'text',
805
+ content: '❌ Unable to connect to AI service. Please check your connection and try again.',
806
+ },
807
+ ],
808
+ isError: true,
809
+ errorType: 'connection_error',
810
+ timestamp: Date.now(),
811
+ };
812
+ return {
813
+ ...prev,
814
+ [currentChatIdToUse]: [...messagesWithoutLoading, errorMessage],
815
+ };
816
+ });
817
+ }
818
+ setIsLoading(false);
819
+ };
820
+ // Create a new chat session
821
+ const newChat = () => {
822
+ const newId = Date.now().toString();
823
+ setCurrentChatIdToUse(newId);
824
+ setChatsToUse((prev) => ({ ...prev, [newId]: [] }));
825
+ };
826
+ // Optionally: expose a way to switch chats
827
+ const switchChat = (chatId) => {
828
+ setCurrentChatIdToUse(chatId);
829
+ };
830
+ return {
831
+ chat,
832
+ chats: chatsToUse,
833
+ currentChatId: currentChatIdToUse,
834
+ isLoading,
835
+ sendQuery,
836
+ newChat,
837
+ switchChat,
838
+ };
839
+ }
840
+
841
+ /**
842
+ * Hook for getting AI suggestions based on a query
843
+ *
844
+ * @param userId - User ID for the suggestion request
845
+ * @param formData - Current form data context
846
+ * @param serverConfig - Server configuration
847
+ * @param debounceMs - Debounce delay in milliseconds (default: 300)
848
+ * @param maxSuggestions - Maximum number of suggestions to return (default: 10)
849
+ * @param chatLevel - Chat level to send to the server (default: 'none')
850
+ * @returns Object containing suggestions, loading state, error state, and control functions
851
+ */
852
+ function useSuggestions({ userId, formData, serverConfig, debounceMs = 300, maxSuggestions = 10, chatLevel = 'none', // <-- default to 'none'
853
+ modelSelection, onUsageTracked, context, // <-- Add context here
854
+ }) {
855
+ const [suggestions, setSuggestions] = React.useState([]);
856
+ const [isLoading, setIsLoading] = React.useState(false);
857
+ const [error, setError] = React.useState(null);
858
+ // Build API URL from config
859
+ const buildUrlFromConfig = React.useCallback((config) => {
860
+ const protocol = config.secure !== undefined
861
+ ? config.secure
862
+ ? 'https:'
863
+ : 'http:'
864
+ : window.location.protocol;
865
+ const hostname = config.domain || window.location.hostname;
866
+ const port = config.port
867
+ ? `:${config.port}`
868
+ : window.location.port
869
+ ? `:${window.location.port}`
870
+ : '';
871
+ const suffix = config.suffix || '';
872
+ if (!config.domain && !config.port && !config.secure) {
873
+ return suffix;
874
+ }
875
+ return `${protocol}//${hostname}${port}${suffix}`;
876
+ }, []);
877
+ const getSuggestions = React.useCallback(async (query, additionalContext) => {
878
+ if (!query.trim()) {
879
+ setSuggestions([]);
880
+ return [];
881
+ }
882
+ setIsLoading(true);
883
+ setError(null);
884
+ try {
885
+ const apiUrl = serverConfig ? buildUrlFromConfig(serverConfig) : '/api';
886
+ // Combine context with proper spacing and handle edge cases
887
+ const contextToSend = [context, additionalContext]
888
+ .filter(Boolean)
889
+ .join(' ')
890
+ .trim();
891
+ const response = await fetch(`${apiUrl}/suggest`, {
892
+ method: 'POST',
893
+ headers: {
894
+ 'Content-Type': 'application/json',
895
+ },
896
+ body: JSON.stringify({
897
+ query: query.trim(),
898
+ userId,
899
+ formData: formData || {},
900
+ chatHistory: [], // Empty for standalone suggestions
901
+ isCommandSearch: true, // Flag to indicate this is for command search
902
+ chatLevel, // <-- always send this
903
+ modelSelection: modelSelection || {
904
+ provider: DEFAULT_CONFIG.service,
905
+ model: DEFAULT_CONFIG.model,
906
+ capabilities: {
907
+ supportsImages: DEFAULT_CONFIG.supportsImages,
908
+ computeWeight: 1.0,
909
+ },
910
+ },
911
+ context: contextToSend,
912
+ }),
913
+ });
914
+ if (!response.ok) {
915
+ throw new Error(`HTTP error! status: ${response.status}`);
916
+ }
917
+ // Handle streaming response
918
+ const reader = response.body?.getReader();
919
+ if (!reader) {
920
+ throw new Error('No response body');
921
+ }
922
+ const decoder = new TextDecoder();
923
+ let buffer = '';
924
+ let collectedSuggestions = [];
925
+ while (true) {
926
+ const { done, value } = await reader.read();
927
+ if (done)
928
+ break;
929
+ buffer += decoder.decode(value, { stream: true });
930
+ const lines = buffer.split('\n');
931
+ buffer = lines.pop() || '';
932
+ for (const line of lines) {
933
+ if (line.startsWith('data: ')) {
934
+ try {
935
+ const data = JSON.parse(line.slice(6));
936
+ if (data.suggestions) {
937
+ collectedSuggestions = data.suggestions;
938
+ }
939
+ else if (data.suggestion) {
940
+ collectedSuggestions.push(data.suggestion);
941
+ }
942
+ else if (data.streamedText) {
943
+ // Extract JSON from streamedText for suggestions
944
+ const jsonMatch = data.streamedText.match(/```json\s*(\[[\s\S]*?\])\s*```/);
945
+ if (jsonMatch) {
946
+ try {
947
+ const jsonSuggestions = JSON.parse(jsonMatch[1]);
948
+ if (Array.isArray(jsonSuggestions) &&
949
+ jsonSuggestions.length > 0) {
950
+ collectedSuggestions = jsonSuggestions;
951
+ }
952
+ }
953
+ catch (e) {
954
+ console.warn('Failed to parse JSON from streamedText:', e);
955
+ }
956
+ }
957
+ }
958
+ // Handle usage tracking
959
+ if (data.usage && onUsageTracked) {
960
+ const tokens = data.usage.tokens || 0;
961
+ const computeUnits = data.usage.computeUnits || 0;
962
+ onUsageTracked(tokens, computeUnits);
963
+ }
964
+ }
965
+ catch (e) {
966
+ console.warn('Failed to parse SSE data:', line);
967
+ }
968
+ }
969
+ else if (line.startsWith('event: end')) {
970
+ break;
971
+ }
972
+ else if (line.startsWith('event: error')) {
973
+ const errorLine = lines.find((l) => l.startsWith('data: '));
974
+ if (errorLine) {
975
+ const errorData = JSON.parse(errorLine.slice(6));
976
+ throw new Error(errorData.message || 'Unknown error');
977
+ }
978
+ throw new Error('Unknown error occurred');
979
+ }
980
+ }
981
+ }
982
+ // Apply max suggestions limit
983
+ const limitedSuggestions = collectedSuggestions.slice(0, maxSuggestions);
984
+ setSuggestions(limitedSuggestions);
985
+ return limitedSuggestions;
986
+ }
987
+ catch (err) {
988
+ console.error('Error fetching suggestions:', err);
989
+ // Try to extract a structured error from the response
990
+ if (err instanceof Response) {
991
+ try {
992
+ const errorObj = await err.json();
993
+ setError(errorObj);
994
+ setSuggestions([]);
995
+ return errorObj;
996
+ }
997
+ catch (parseErr) {
998
+ setError(err.statusText || 'Unknown error');
999
+ setSuggestions([]);
1000
+ return new Error(err.statusText || 'Unknown error');
1001
+ }
1002
+ }
1003
+ // If the error is already a structured object (from fetch or thrown by backend)
1004
+ if (err &&
1005
+ typeof err === 'object' &&
1006
+ 'error' in err &&
1007
+ err.error &&
1008
+ typeof err.error === 'object' &&
1009
+ 'message' in err.error) {
1010
+ setError(err.error);
1011
+ setSuggestions([]);
1012
+ return err.error;
1013
+ }
1014
+ // Fallback: just a string or Error
1015
+ setError(err instanceof Error ? err.message : 'Unknown error');
1016
+ setSuggestions([]);
1017
+ return err instanceof Error ? err : new Error('Unknown error');
1018
+ }
1019
+ finally {
1020
+ setIsLoading(false);
1021
+ }
1022
+ }, [
1023
+ userId,
1024
+ formData,
1025
+ serverConfig,
1026
+ maxSuggestions,
1027
+ chatLevel,
1028
+ modelSelection,
1029
+ onUsageTracked,
1030
+ buildUrlFromConfig,
1031
+ context,
1032
+ ]);
1033
+ const clearSuggestions = React.useCallback(() => {
1034
+ setSuggestions([]);
1035
+ setError(null);
1036
+ }, []);
1037
+ return {
1038
+ suggestions,
1039
+ isLoading,
1040
+ error,
1041
+ getSuggestions,
1042
+ clearSuggestions,
1043
+ };
1044
+ }
1045
+ /**
1046
+ * Hook for getting suggestions with automatic debouncing
1047
+ *
1048
+ * @param query - The search query
1049
+ * @param options - Configuration options
1050
+ * @returns Object containing suggestions, loading state, and error state
1051
+ */
1052
+ function useDebouncedSuggestions(query, options) {
1053
+ const { suggestions, isLoading, error, getSuggestions, clearSuggestions } = useSuggestions(options);
1054
+ const { debounceMs = 300 } = options;
1055
+ React.useEffect(() => {
1056
+ if (!query.trim()) {
1057
+ clearSuggestions();
1058
+ return;
1059
+ }
1060
+ const timeoutId = setTimeout(() => {
1061
+ getSuggestions(query);
1062
+ }, debounceMs);
1063
+ return () => clearTimeout(timeoutId);
1064
+ }, [query, getSuggestions, clearSuggestions, debounceMs]);
1065
+ return {
1066
+ suggestions,
1067
+ isLoading,
1068
+ error,
1069
+ };
1070
+ }
1071
+
1072
+ // Size mapping
1073
+ const sizeMap = {
1074
+ xs: 12,
1075
+ sm: 16,
1076
+ md: 20,
1077
+ lg: 24,
1078
+ xl: 32,
1079
+ };
1080
+ // Get numeric size from IconSize
1081
+ const getSize = (size) => {
1082
+ return typeof size === 'number' ? size : sizeMap[size];
1083
+ };
1084
+ // Map action types to icon names
1085
+ const getIconNameForAction = (action) => {
1086
+ const lower = action.toLowerCase();
1087
+ if (lower.includes('create') || lower.includes('new'))
1088
+ return 'plus';
1089
+ if (lower.includes('edit') || lower.includes('update'))
1090
+ return 'pencil';
1091
+ if (lower.includes('delete') || lower.includes('remove'))
1092
+ return 'trash';
1093
+ if (lower.includes('view'))
1094
+ return 'eye';
1095
+ return null;
1096
+ };
1097
+ // Individual icon components
1098
+ const iconComponents = {
1099
+ // UI Icons
1100
+ ai: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", xmlns: "http://www.w3.org/2000/svg", className: `relative left-[0.5px] ${className}`, children: jsxRuntime.jsx("path", { d: "M18.7801 12.4715L13.4471 10.4877L11.4801 5.19677C11.3572 4.91331 11.149 4.62911 10.8748 4.44014C10.6005 4.25116 10.2696 4.06182 9.92912 4.06182C9.59815 4.06182 9.26696 4.25116 8.99273 4.44014C8.71849 4.62911 8.50133 4.91331 8.38786 5.19677L6.42087 10.4877L1.07815 12.4715C0.76609 12.566 0.491783 12.8491 0.293201 13.0381C0.104075 13.3215 0 13.6995 0 13.9829C0 14.3609 0.104075 14.6443 0.293201 14.9278C0.491783 15.2113 0.76609 15.4958 1.07815 15.5903L6.41117 17.5742L8.37816 22.8651C8.49164 23.1485 8.70926 23.4327 8.98349 23.6217C9.25773 23.8107 9.58892 24 9.91989 24C10.2603 24 10.5913 23.8107 10.8655 23.6217C11.1398 23.4327 11.3475 23.1485 11.4704 22.8651L13.4374 17.5742L18.7709 15.5903C19.0924 15.4958 19.3667 15.2127 19.5558 15.0238C19.7544 14.7403 19.849 14.3624 19.849 14.0789C19.849 13.7009 19.7544 13.4175 19.5558 13.134C19.3667 12.8506 19.0924 12.566 18.7709 12.4715H18.7801ZM12.8699 15.9686C12.643 16.0631 12.435 16.2506 12.2743 16.3451C12.1041 16.5341 11.9715 16.7242 11.8864 17.0076L9.92912 22.2985L7.96214 17.0076C7.88649 16.7242 7.75419 16.5341 7.58398 16.3451C7.41376 16.2506 7.21529 16.0631 6.98834 15.9686L1.67379 14.0789L6.98834 12.0932C7.21529 11.9987 7.41376 11.8112 7.58398 11.7167C7.75419 11.5278 7.88649 11.3377 7.96214 11.0542L9.92912 5.76332L11.8961 11.0542C11.9718 11.3377 12.1041 11.5278 12.2743 11.7167C12.4445 11.9057 12.643 11.9987 12.8699 12.0932L18.1942 14.0789L12.8699 15.9686ZM13.2388 3.30704C13.2388 3.02358 13.3237 2.83497 13.4845 2.74048C13.6358 2.55151 13.8439 2.45629 14.0709 2.45629H15.7257V0.85075C15.7257 0.567289 15.8108 0.377209 15.9621 0.188235C16.1229 0.0937486 16.331 0 16.5485 0C16.766 0 16.9742 0.0937486 17.1349 0.188235C17.2862 0.377209 17.3806 0.567289 17.3806 0.85075V2.45629H19.0354C19.2529 2.45629 19.4613 2.55151 19.6126 2.74048C19.7734 2.83497 19.8582 3.02358 19.8582 3.30704C19.8582 3.49601 19.7734 3.68461 19.6126 3.87359C19.4613 4.06256 19.2529 4.06182 19.0354 4.06182H17.3806V5.76332C17.3806 5.9523 17.2862 6.23539 17.1349 6.32987C16.9742 6.51885 16.766 6.61407 16.5485 6.61407C16.331 6.61407 16.1229 6.51885 15.9621 6.32987C15.8108 6.23539 15.7257 5.9523 15.7257 5.76332V4.06182H14.0709C13.8439 4.06182 13.6358 4.06256 13.4845 3.87359C13.3237 3.68461 13.2388 3.49601 13.2388 3.30704ZM24 8.21961C24 8.50307 23.9057 8.69167 23.7544 8.78616C23.5936 8.97513 23.3855 9.07036 23.168 9.07036H22.3451V9.92111C22.3451 10.1101 22.2508 10.2987 22.0995 10.4877C21.9387 10.6766 21.7306 10.6759 21.5131 10.6759C21.2956 10.6759 21.0875 10.6766 20.9267 10.4877C20.7754 10.2987 20.6903 10.1101 20.6903 9.92111V9.07036H19.8582C19.6408 9.07036 19.4326 8.97513 19.2718 8.78616C19.1205 8.69167 19.0354 8.50307 19.0354 8.21961C19.0354 8.03063 19.1205 7.84203 19.2718 7.65306C19.4326 7.46408 19.6408 7.46482 19.8582 7.46482H20.6903V6.61407C20.6903 6.33061 20.7754 6.14201 20.9267 6.04752C21.0875 5.85855 21.2956 5.76332 21.5131 5.76332C21.7306 5.76332 21.9387 5.85855 22.0995 6.04752C22.2508 6.14201 22.3451 6.33061 22.3451 6.61407V7.46482H23.168C23.3855 7.46482 23.5936 7.46408 23.7544 7.65306C23.9057 7.84203 24 8.03063 24 8.21961Z", fill: color }) })),
1101
+ user: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: `lucide lucide-user ${className}`, children: [jsxRuntime.jsx("path", { d: "M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2" }), jsxRuntime.jsx("circle", { cx: "12", cy: "7", r: "4" })] })),
1102
+ system: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: 2, strokeLinecap: "round", strokeLinejoin: "round", className: `lucide lucide-settings ${className}`, children: [jsxRuntime.jsx("path", { d: "M12.22 2h-.44a2 2 0 0 0-2 2v.18a2 2 0 0 1-1 1.73l-.43.25a2 2 0 0 1-2 0l-.15-.08a2 2 0 0 0-2.73.73l-.22.38a2 2 0 0 0 .73 2.73l.15.1a2 2 0 0 1 1 1.72v.51a2 2 0 0 1-1 1.74l-.15.09a2 2 0 0 0-.73 2.73l.22.38a2 2 0 0 0 2.73.73l.15-.08a2 2 0 0 1 2 0l.43.25a2 2 0 0 1 1 1.73V20a2 2 0 0 0 2 2h.44a2 2 0 0 0 2-2v-.18a2 2 0 0 1 1-1.73l.43-.25a2 2 0 0 1 2 0l.15.08a2 2 0 0 0 2.73-.73l.22-.38a2 2 0 0 0-.73-2.73l-.15-.09a2 2 0 0 1-1-1.74v-.5a2 2 0 0 1 1-1.74l.15-.09a2 2 0 0 0 .73-2.73l-.22-.39a2 2 0 0 0-2.73-.73l-.15.08a2 2 0 0 1-2 0l-.43-.25a2 2 0 0 1-1-1.73V4a2 2 0 0 0-2-2z" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })] })),
1103
+ paperclip: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsx("svg", { width: size, height: size, fill: "none", stroke: color, strokeWidth: "2", viewBox: "0 0 24 24", className: className, children: jsxRuntime.jsx("path", { d: "M21.44 11.05l-9.19 9.19a6 6 0 0 1-8.49-8.49l9.19-9.19a4 4 0 0 1 5.66 5.66l-9.2 9.19a2 2 0 0 1-2.83-2.83l8.49-8.48" }) })),
1104
+ image: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, fill: "none", stroke: color, strokeWidth: "2", viewBox: "0 0 24 24", className: className, children: [jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), jsxRuntime.jsx("polyline", { points: "21,15 16,10 5,21" })] })),
1105
+ noImage: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, fill: "none", stroke: color, strokeWidth: "2", viewBox: "0 0 24 24", className: className, children: [jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2" }), jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5" }), jsxRuntime.jsx("polyline", { points: "21,15 16,10 5,21" }), jsxRuntime.jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21" })] })),
1106
+ microphone: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, fill: "none", stroke: color, strokeWidth: "2", viewBox: "0 0 24 24", className: className, children: [jsxRuntime.jsx("path", { d: "M12 1a3 3 0 0 0-3 3v8a3 3 0 0 0 6 0V4a3 3 0 0 0-3-3z" }), jsxRuntime.jsx("path", { d: "M19 10v2a7 7 0 0 1-14 0v-2" }), jsxRuntime.jsx("line", { x1: "12", y1: "19", x2: "12", y2: "23" }), jsxRuntime.jsx("line", { x1: "8", y1: "23", x2: "16", y2: "23" })] })),
1107
+ send: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, fill: "none", stroke: color, strokeWidth: "2", viewBox: "0 0 24 24", className: className, children: [jsxRuntime.jsx("line", { x1: "22", y1: "2", x2: "11", y2: "13" }), jsxRuntime.jsx("polygon", { points: "22,2 15,22 11,13 2,9 22,2" })] })),
1108
+ // Action Icons
1109
+ plus: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [jsxRuntime.jsx("line", { x1: "12", y1: "5", x2: "12", y2: "19" }), jsxRuntime.jsx("line", { x1: "5", y1: "12", x2: "19", y2: "12" })] })),
1110
+ // AI Provider Icons
1111
+ claude: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", shapeRendering: "geometricPrecision", textRendering: "geometricPrecision", imageRendering: "optimizeQuality", fillRule: "evenodd", clipRule: "evenodd", viewBox: "0 0 512 509.64", width: size, height: size, className: className, children: [jsxRuntime.jsx("path", { fill: "#D77655", d: "M115.612 0h280.775C459.974 0 512 52.026 512 115.612v278.415c0 63.587-52.026 115.612-115.613 115.612H115.612C52.026 509.639 0 457.614 0 394.027V115.612C0 52.026 52.026 0 115.612 0z" }), jsxRuntime.jsx("path", { fill: "#FCF2EE", fillRule: "nonzero", d: "M142.27 316.619l73.655-41.326 1.238-3.589-1.238-1.996-3.589-.001-12.31-.759-42.084-1.138-36.498-1.516-35.361-1.896-8.897-1.895-8.34-10.995.859-5.484 7.482-5.03 10.717.935 23.683 1.617 35.537 2.452 25.782 1.517 38.193 3.968h6.064l.86-2.451-2.073-1.517-1.618-1.517-36.776-24.922-39.81-26.338-20.852-15.166-11.273-7.683-5.687-7.204-2.451-15.721 10.237-11.273 13.75.935 3.513.936 13.928 10.716 29.749 23.027 38.848 28.612 5.687 4.727 2.275-1.617.278-1.138-2.553-4.271-21.13-38.193-22.546-38.848-10.035-16.101-2.654-9.655c-.935-3.968-1.617-7.304-1.617-11.374l11.652-15.823 6.445-2.073 15.545 2.073 6.547 5.687 9.655 22.092 15.646 34.78 24.265 47.291 7.103 14.028 3.791 12.992 1.416 3.968 2.449-.001v-2.275l1.997-26.641 3.69-32.707 3.589-42.084 1.239-11.854 5.863-14.206 11.652-7.683 9.099 4.348 7.482 10.716-1.036 6.926-4.449 28.915-8.72 45.294-5.687 30.331h3.313l3.792-3.791 15.342-20.372 25.782-32.227 11.374-12.789 13.27-14.129 8.517-6.724 16.1-.001 11.854 17.617-5.307 18.199-16.581 21.029-13.75 17.819-19.716 26.54-12.309 21.231 1.138 1.694 2.932-.278 44.536-9.479 24.062-4.347 28.714-4.928 12.992 6.066 1.416 6.167-5.106 12.613-30.71 7.583-36.018 7.204-53.636 12.689-.657.48.758.935 24.164 2.275 10.337.556h25.301l47.114 3.514 12.309 8.139 7.381 9.959-1.238 7.583-18.957 9.655-25.579-6.066-59.702-14.205-20.474-5.106-2.83-.001v1.694l17.061 16.682 31.266 28.233 39.152 36.397 1.997 8.999-5.03 7.102-5.307-.758-34.401-25.883-13.27-11.651-30.053-25.302-1.996-.001v2.654l6.926 10.136 36.574 54.975 1.895 16.859-2.653 5.485-9.479 3.311-10.414-1.895-21.408-30.054-22.092-33.844-17.819-30.331-2.173 1.238-10.515 113.261-4.929 5.788-11.374 4.348-9.478-7.204-5.03-11.652 5.03-23.027 6.066-30.052 4.928-23.886 4.449-29.674 2.654-9.858-.177-.657-2.173.278-22.37 30.71-34.021 45.977-26.919 28.815-6.445 2.553-11.173-5.789 1.037-10.337 6.243-9.2 37.257-47.392 22.47-29.371 14.508-16.961-.101-2.451h-.859l-98.954 64.251-17.618 2.275-7.583-7.103.936-11.652 3.589-3.791 29.749-20.474-.101.102.024.101z" })] })),
1112
+ openai: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", shapeRendering: "geometricPrecision", textRendering: "geometricPrecision", imageRendering: "optimizeQuality", fillRule: "evenodd", clipRule: "evenodd", viewBox: "0 0 512 509.639", width: size, height: size, className: className, children: [jsxRuntime.jsx("path", { fill: "#fff", d: "M115.612 0h280.775C459.974 0 512 52.026 512 115.612v278.415c0 63.587-52.026 115.613-115.613 115.613H115.612C52.026 509.64 0 457.614 0 394.027V115.612C0 52.026 52.026 0 115.612 0z" }), jsxRuntime.jsx("path", { fillRule: "nonzero", d: "M412.037 221.764a90.834 90.834 0 004.648-28.67 90.79 90.79 0 00-12.443-45.87c-16.37-28.496-46.738-46.089-79.605-46.089-6.466 0-12.943.683-19.264 2.04a90.765 90.765 0 00-67.881-30.515h-.576c-.059.002-.149.002-.216.002-39.807 0-75.108 25.686-87.346 63.554-25.626 5.239-47.748 21.31-60.682 44.03a91.873 91.873 0 00-12.407 46.077 91.833 91.833 0 0023.694 61.553 90.802 90.802 0 00-4.649 28.67 90.804 90.804 0 0012.442 45.87c16.369 28.504 46.74 46.087 79.61 46.087a91.81 91.81 0 0019.253-2.04 90.783 90.783 0 0067.887 30.516h.576l.234-.001c39.829 0 75.119-25.686 87.357-63.588 25.626-5.242 47.748-21.312 60.682-44.033a91.718 91.718 0 0012.383-46.035 91.83 91.83 0 00-23.693-61.553l-.004-.005zM275.102 413.161h-.094a68.146 68.146 0 01-43.611-15.8 56.936 56.936 0 002.155-1.221l72.54-41.901a11.799 11.799 0 005.962-10.251V241.651l30.661 17.704c.326.163.55.479.596.84v84.693c-.042 37.653-30.554 68.198-68.21 68.273h.001zm-146.689-62.649a68.128 68.128 0 01-9.152-34.085c0-3.904.341-7.817 1.005-11.663.539.323 1.48.897 2.155 1.285l72.54 41.901a11.832 11.832 0 0011.918-.002l88.563-51.137v35.408a1.1 1.1 0 01-.438.94l-73.33 42.339a68.43 68.43 0 01-34.11 9.12 68.359 68.359 0 01-59.15-34.11l-.001.004zm-19.083-158.36a68.044 68.044 0 0135.538-29.934c0 .625-.036 1.731-.036 2.5v83.801l-.001.07a11.79 11.79 0 005.954 10.242l88.564 51.13-30.661 17.704a1.096 1.096 0 01-1.034.093l-73.337-42.375a68.36 68.36 0 01-34.095-59.143 68.412 68.412 0 019.112-34.085l-.004-.003zm251.907 58.621l-88.563-51.137 30.661-17.697a1.097 1.097 0 011.034-.094l73.337 42.339c21.109 12.195 34.132 34.746 34.132 59.132 0 28.604-17.849 54.199-44.686 64.078v-86.308c.004-.032.004-.065.004-.096 0-4.219-2.261-8.119-5.919-10.217zm30.518-45.93c-.539-.331-1.48-.898-2.155-1.286l-72.54-41.901a11.842 11.842 0 00-5.958-1.611c-2.092 0-4.15.558-5.957 1.611l-88.564 51.137v-35.408l-.001-.061a1.1 1.1 0 01.44-.88l73.33-42.303a68.301 68.301 0 0134.108-9.129c37.704 0 68.281 30.577 68.281 68.281a68.69 68.69 0 01-.984 11.545v.005zm-191.843 63.109l-30.668-17.704a1.09 1.09 0 01-.596-.84v-84.692c.016-37.685 30.593-68.236 68.281-68.236a68.332 68.332 0 0143.689 15.804 63.09 63.09 0 00-2.155 1.222l-72.54 41.9a11.794 11.794 0 00-5.961 10.248v.068l-.05 102.23zm16.655-35.91l39.445-22.782 39.444 22.767v45.55l-39.444 22.767-39.445-22.767v-45.535z" })] })),
1113
+ gemini: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { fill: "none", xmlns: "http://www.w3.org/2000/svg", viewBox: "0 0 16 16", width: size, height: size, className: className, children: [jsxRuntime.jsx("path", { d: "M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z", fill: "url(#prefix__paint0_radial_980_20147)" }), jsxRuntime.jsx("defs", { children: jsxRuntime.jsxs("radialGradient", { id: "prefix__paint0_radial_980_20147", cx: "0", cy: "0", r: "1", gradientUnits: "userSpaceOnUse", gradientTransform: "matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)", children: [jsxRuntime.jsx("stop", { offset: ".067", stopColor: "#9168C0" }), jsxRuntime.jsx("stop", { offset: ".343", stopColor: "#5684D1" }), jsxRuntime.jsx("stop", { offset: ".672", stopColor: "#1BA1E3" })] }) })] })),
1114
+ grok: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { xmlns: "http://www.w3.org/2000/svg", shapeRendering: "geometricPrecision", textRendering: "geometricPrecision", imageRendering: "optimizeQuality", fillRule: "evenodd", clipRule: "evenodd", viewBox: "0 0 512 509.641", width: size, height: size, className: className, children: [jsxRuntime.jsx("path", { d: "M115.612 0h280.776C459.975 0 512 52.026 512 115.612v278.416c0 63.587-52.025 115.613-115.612 115.613H115.612C52.026 509.641 0 457.615 0 394.028V115.612C0 52.026 52.026 0 115.612 0z" }), jsxRuntime.jsx("path", { fill: "#fff", d: "M213.235 306.019l178.976-180.002v.169l51.695-51.763c-.924 1.32-1.86 2.605-2.785 3.89-39.281 54.164-58.46 80.649-43.07 146.922l-.09-.101c10.61 45.11-.744 95.137-37.398 131.836-46.216 46.306-120.167 56.611-181.063 14.928l42.462-19.675c38.863 15.278 81.392 8.57 111.947-22.03 30.566-30.6 37.432-75.159 22.065-112.252-2.92-7.025-11.67-8.795-17.792-4.263l-124.947 92.341zm-25.786 22.437l-.033.034L68.094 435.217c7.565-10.429 16.957-20.294 26.327-30.149 26.428-27.803 52.653-55.359 36.654-94.302-21.422-52.112-8.952-113.177 30.724-152.898 41.243-41.254 101.98-51.661 152.706-30.758 11.23 4.172 21.016 10.114 28.638 15.639l-42.359 19.584c-39.44-16.563-84.629-5.299-112.207 22.313-37.298 37.308-44.84 102.003-1.128 143.81z" })] })),
1115
+ pencil: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsx("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: jsxRuntime.jsx("path", { d: "M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z" }) })),
1116
+ trash: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [jsxRuntime.jsx("polyline", { points: "3,6 5,6 21,6" }), jsxRuntime.jsx("path", { d: "M19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2" })] })),
1117
+ eye: ({ size, className = '', color = 'currentColor' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 24 24", fill: "none", stroke: color, strokeWidth: "2", strokeLinecap: "round", strokeLinejoin: "round", className: className, children: [jsxRuntime.jsx("path", { d: "M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z" }), jsxRuntime.jsx("circle", { cx: "12", cy: "12", r: "3" })] })),
1118
+ // Error Icons
1119
+ error: ({ size, className = '', color = '#EF4444' }) => (jsxRuntime.jsx("svg", { className: `w-${size} h-${size} ${className}`, fill: color, viewBox: "0 0 20 20", width: size, height: size, children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) })),
1120
+ warning: ({ size, className = '', color = '#F59E0B' }) => (jsxRuntime.jsx("svg", { className: `w-${size} h-${size} ${className}`, fill: color, viewBox: "0 0 20 20", width: size, height: size, children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z", clipRule: "evenodd" }) })),
1121
+ timeout: ({ size, className = '', color = '#EAB308' }) => (jsxRuntime.jsx("svg", { className: `w-${size} h-${size} ${className}`, fill: color, viewBox: "0 0 20 20", width: size, height: size, children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.414L11 9.586V6z", clipRule: "evenodd" }) })),
1122
+ 'auth-error': ({ size, className = '', color = '#EF4444' }) => (jsxRuntime.jsx("svg", { className: `w-${size} h-${size} ${className}`, fill: color, viewBox: "0 0 20 20", width: size, height: size, children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-2a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z", clipRule: "evenodd" }) })),
1123
+ // File Type Icons
1124
+ pdf: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#DC2626" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#B91C1C", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "2", rx: "1", fill: "white", opacity: "0.9" }), jsxRuntime.jsx("rect", { x: "9", y: "16", width: "10", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("rect", { x: "9", y: "19", width: "12", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("text", { x: "16", y: "25", fontSize: "5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "PDF" })] })),
1125
+ word: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#2563EB" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#1D4ED8", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "2", rx: "1", fill: "white", opacity: "0.9" }), jsxRuntime.jsx("rect", { x: "9", y: "16", width: "10", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("rect", { x: "9", y: "19", width: "12", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("text", { x: "16", y: "25", fontSize: "4.5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "DOC" })] })),
1126
+ excel: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#059669" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#047857", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "8", fill: "white", opacity: "0.2", rx: "1" }), jsxRuntime.jsx("line", { x1: "12", y1: "12", x2: "12", y2: "20", stroke: "white", strokeWidth: "0.5", opacity: "0.6" }), jsxRuntime.jsx("line", { x1: "16", y1: "12", x2: "16", y2: "20", stroke: "white", strokeWidth: "0.5", opacity: "0.6" }), jsxRuntime.jsx("line", { x1: "20", y1: "12", x2: "20", y2: "20", stroke: "white", strokeWidth: "0.5", opacity: "0.6" }), jsxRuntime.jsx("line", { x1: "9", y1: "15", x2: "23", y2: "15", stroke: "white", strokeWidth: "0.5", opacity: "0.6" }), jsxRuntime.jsx("line", { x1: "9", y1: "17", x2: "23", y2: "17", stroke: "white", strokeWidth: "0.5", opacity: "0.6" }), jsxRuntime.jsx("text", { x: "16", y: "25", fontSize: "4.5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "XLS" })] })),
1127
+ csv: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#7C3AED" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#6D28D9", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "1.5", rx: "0.75", fill: "white", opacity: "0.8" }), jsxRuntime.jsx("rect", { x: "9", y: "15", width: "10", height: "1.5", rx: "0.75", fill: "white", opacity: "0.6" }), jsxRuntime.jsx("rect", { x: "9", y: "18", width: "12", height: "1.5", rx: "0.75", fill: "white", opacity: "0.6" }), jsxRuntime.jsx("circle", { cx: "11", cy: "13", r: "0.5", fill: "#7C3AED" }), jsxRuntime.jsx("circle", { cx: "15", cy: "13", r: "0.5", fill: "#7C3AED" }), jsxRuntime.jsx("circle", { cx: "19", cy: "13", r: "0.5", fill: "#7C3AED" }), jsxRuntime.jsx("text", { x: "16", y: "25", fontSize: "4.5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "CSV" })] })),
1128
+ text: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#6B7280" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#4B5563", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "1.5", rx: "0.75", fill: "white", opacity: "0.9" }), jsxRuntime.jsx("rect", { x: "9", y: "15", width: "12", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("rect", { x: "9", y: "18", width: "10", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("rect", { x: "9", y: "21", width: "8", height: "1.5", rx: "0.75", fill: "white", opacity: "0.5" }), jsxRuntime.jsx("text", { x: "16", y: "26", fontSize: "4.5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "TXT" })] })),
1129
+ rtf: ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#EA580C" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#C2410C", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("rect", { x: "9", y: "12", width: "14", height: "2", rx: "1", fill: "white", opacity: "0.9" }), jsxRuntime.jsx("rect", { x: "9", y: "16", width: "10", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("rect", { x: "9", y: "19", width: "12", height: "1.5", rx: "0.75", fill: "white", opacity: "0.7" }), jsxRuntime.jsx("text", { x: "10", y: "14", fontSize: "3", fill: "#EA580C", fontWeight: "bold", children: "B" }), jsxRuntime.jsx("text", { x: "13", y: "14", fontSize: "3", fill: "#EA580C", fontStyle: "italic", children: "I" }), jsxRuntime.jsx("text", { x: "16", y: "25", fontSize: "4.5", fill: "white", textAnchor: "middle", fontWeight: "600", children: "RTF" })] })),
1130
+ 'file-generic': ({ size, className = '' }) => (jsxRuntime.jsxs("svg", { width: size, height: size, viewBox: "0 0 32 32", fill: "none", className: className, children: [jsxRuntime.jsx("path", { d: "M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z", fill: "#9CA3AF" }), jsxRuntime.jsx("path", { d: "M19 2v5h5", stroke: "#6B7280", strokeWidth: "1", fill: "none" }), jsxRuntime.jsx("circle", { cx: "16", cy: "16", r: "3", fill: "white", opacity: "0.8" }), jsxRuntime.jsx("circle", { cx: "16", cy: "16", r: "1", fill: "#9CA3AF" })] })),
1131
+ };
1132
+ // Main Icon component
1133
+ const Icon = ({ name, size = 'md', className = '', color, style, }) => {
1134
+ const numericSize = getSize(size);
1135
+ const IconComponent = iconComponents[name];
1136
+ if (!IconComponent) {
1137
+ console.warn(`Icon "${name}" not found`);
1138
+ return null;
1139
+ }
1140
+ return (jsxRuntime.jsx("span", { className: className, style: style, children: jsxRuntime.jsx(IconComponent, { size: numericSize, className: "", color: color }) }));
1141
+ };
1142
+ /**
1143
+ * Get an icon as an SVG string for vanilla JavaScript applications
1144
+ * @param name - Icon name
1145
+ * @param options - Icon options (size, color, className)
1146
+ * @returns SVG string or null if icon not found
1147
+ */
1148
+ const getIcon = (name, options = {}) => {
1149
+ const { size = 'md', color = 'currentColor', className = '' } = options;
1150
+ const numericSize = getSize(size);
1151
+ const IconComponent = iconComponents[name];
1152
+ if (!IconComponent) {
1153
+ console.warn(`Icon "${name}" not found`);
1154
+ return null;
1155
+ }
1156
+ // For file type icons that don't use color prop, we need special handling
1157
+ const isFileIcon = [
1158
+ 'pdf',
1159
+ 'word',
1160
+ 'excel',
1161
+ 'csv',
1162
+ 'text',
1163
+ 'rtf',
1164
+ 'file-generic',
1165
+ ].includes(name);
1166
+ // Create the icon element with proper props
1167
+ const element = IconComponent({
1168
+ size: numericSize,
1169
+ className,
1170
+ color: isFileIcon ? undefined : color,
1171
+ });
1172
+ // Convert React element to SVG string
1173
+ // Since we're dealing with SVG elements, we can extract their outerHTML
1174
+ if (React.isValidElement(element)) {
1175
+ // For now, return a simplified SVG string based on the icon name and options
1176
+ // This is a basic implementation - you might want to use a proper React-to-string renderer
1177
+ const svgProps = element.props;
1178
+ const svgElement = element.type;
1179
+ if (svgElement === 'svg') {
1180
+ // Build SVG string manually for better control
1181
+ const attrs = [
1182
+ `width="${numericSize}"`,
1183
+ `height="${numericSize}"`,
1184
+ svgProps.viewBox ? `viewBox="${svgProps.viewBox}"` : '',
1185
+ svgProps.fill !== undefined ? `fill="${svgProps.fill}"` : '',
1186
+ svgProps.stroke ? `stroke="${svgProps.stroke}"` : '',
1187
+ svgProps.strokeWidth ? `stroke-width="${svgProps.strokeWidth}"` : '',
1188
+ className ? `class="${className}"` : '',
1189
+ ]
1190
+ .filter(Boolean)
1191
+ .join(' ');
1192
+ // This is a simplified approach - for production you'd want proper serialization
1193
+ return `<svg ${attrs}>${svgProps.children || ''}</svg>`;
1194
+ }
1195
+ }
1196
+ // Fallback: return a basic SVG string for the icon
1197
+ return getBasicSvgString(name, numericSize, color, className);
1198
+ };
1199
+ /**
1200
+ * Fallback function to generate basic SVG strings for icons
1201
+ */
1202
+ const getBasicSvgString = (name, size, color, className) => {
1203
+ const classAttr = className ? ` class="${className}"` : '';
1204
+ // Basic SVG templates for each icon type
1205
+ switch (name) {
1206
+ case 'plus':
1207
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>`;
1208
+ case 'pencil':
1209
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><path d="M17 3a2.828 2.828 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5L17 3z"/></svg>`;
1210
+ case 'trash':
1211
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><polyline points="3,6 5,6 21,6"/><path d="M19,6v14a2,2 0 0,1 -2,2H7a2,2 0 0,1 -2,-2V6m3,0V4a2,2 0 0,1 2,-2h4a2,2 0 0,1 2,2v2"/></svg>`;
1212
+ case 'eye':
1213
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><path d="M1 12s4-8 11-8 11 8 11 8-4 8-11 8-11-8-11-8z"/><circle cx="12" cy="12" r="3"/></svg>`;
1214
+ case 'ai':
1215
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><path d="M12 8V4H8"/><rect width="16" height="12" x="4" y="8" rx="2"/><path d="M2 14h2"/><path d="M20 14h2"/><path d="M15 13v2"/><path d="M9 13v2"/></svg>`;
1216
+ case 'user':
1217
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><path d="M20 21v-2a4 4 0 0 0-4-4H8a4 4 0 0 0-4 4v2"/><circle cx="12" cy="7" r="4"/></svg>`;
1218
+ case 'send':
1219
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><line x1="22" y1="2" x2="11" y2="13"/><polygon points="22,2 15,22 11,13 2,9 22,2"/></svg>`;
1220
+ case 'error':
1221
+ return `<svg width="${size}" height="${size}" fill="#EF4444" viewBox="0 0 20 20"${classAttr}><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z" clip-rule="evenodd"/></svg>`;
1222
+ case 'warning':
1223
+ return `<svg width="${size}" height="${size}" fill="#F59E0B" viewBox="0 0 20 20"${classAttr}><path fill-rule="evenodd" d="M8.257 3.099c.765-1.36 2.722-1.36 3.486 0l5.58 9.92c.75 1.334-.213 2.98-1.742 2.98H4.42c-1.53 0-2.493-1.646-1.743-2.98l5.58-9.92zM11 13a1 1 0 11-2 0 1 1 0 012 0zm-1-8a1 1 0 00-1 1v3a1 1 0 002 0V6a1 1 0 00-1-1z" clip-rule="evenodd"/></svg>`;
1224
+ case 'timeout':
1225
+ return `<svg width="${size}" height="${size}" fill="#EAB308" viewBox="0 0 20 20"${classAttr}><path fill-rule="evenodd" d="M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.414L11 9.586V6z" clip-rule="evenodd"/></svg>`;
1226
+ case 'auth-error':
1227
+ return `<svg width="${size}" height="${size}" fill="#EF4444" viewBox="0 0 20 20"${classAttr}><path fill-rule="evenodd" d="M18 8a6 6 0 01-7.743 5.743L10 14l-1 1-1 1H6v2H2v-4l4.257-4.257A6 6 0 1118 8zm-6-2a1 1 0 100 2 2 2 0 012 2 1 1 0 102 0 4 4 0 00-4-4z" clip-rule="evenodd"/></svg>`;
1228
+ case 'claude':
1229
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 512 509.64"${classAttr}><path fill="#D77655" d="M115.612 0h280.775C459.974 0 512 52.026 512 115.612v278.415c0 63.587-52.026 115.612-115.613 115.612H115.612C52.026 509.639 0 457.614 0 394.027V115.612C0 52.026 52.026 0 115.612 0z"/><path fill="#FCF2EE" fill-rule="nonzero" d="M142.27 316.619l73.655-41.326 1.238-3.589-1.238-1.996-3.589-.001-12.31-.759-42.084-1.138-36.498-1.516-35.361-1.896-8.897-1.895-8.34-10.995.859-5.484 7.482-5.03 10.717.935 23.683 1.617 35.537 2.452 25.782 1.517 38.193 3.968h6.064l.86-2.451-2.073-1.517-1.618-1.517-36.776-24.922-39.81-26.338-20.852-15.166-11.273-7.683-5.687-7.204-2.451-15.721 10.237-11.273 13.75.935 3.513.936 13.928 10.716 29.749 23.027 38.848 28.612 5.687 4.727 2.275-1.617.278-1.138-2.553-4.271-21.13-38.193-22.546-38.848-10.035-16.101-2.654-9.655c-.935-3.968-1.617-7.304-1.617-11.374l11.652-15.823 6.445-2.073 15.545 2.073 6.547 5.687 9.655 22.092 15.646 34.78 24.265 47.291 7.103 14.028 3.791 12.992 1.416 3.968 2.449-.001v-2.275l1.997-26.641 3.69-32.707 3.589-42.084 1.239-11.854 5.863-14.206 11.652-7.683 9.099 4.348 7.482 10.716-1.036 6.926-4.449 28.915-8.72 45.294-5.687 30.331h3.313l3.792-3.791 15.342-20.372 25.782-32.227 11.374-12.789 13.27-14.129 8.517-6.724 16.1-.001 11.854 17.617-5.307 18.199-16.581 21.029-13.75 17.819-19.716 26.54-12.309 21.231 1.138 1.694 2.932-.278 44.536-9.479 24.062-4.347 28.714-4.928 12.992 6.066 1.416 6.167-5.106 12.613-30.71 7.583-36.018 7.204-53.636 12.689-.657.48.758.935 24.164 2.275 10.337.556h25.301l47.114 3.514 12.309 8.139 7.381 9.959-1.238 7.583-18.957 9.655-25.579-6.066-59.702-14.205-20.474-5.106-2.83-.001v1.694l17.061 16.682 31.266 28.233 39.152 36.397 1.997 8.999-5.03 7.102-5.307-.758-34.401-25.883-13.27-11.651-30.053-25.302-1.996-.001v2.654l6.926 10.136 36.574 54.975 1.895 16.859-2.653 5.485-9.479 3.311-10.414-1.895-21.408-30.054-22.092-33.844-17.819-30.331-2.173 1.238-10.515 113.261-4.929 5.788-11.374 4.348-9.478-7.204-5.03-11.652 5.03-23.027 6.066-30.052 4.928-23.886 4.449-29.674 2.654-9.858-.177-.657-2.173.278-22.37 30.71-34.021 45.977-26.919 28.815-6.445 2.553-11.173-5.789 1.037-10.337 6.243-9.2 37.257-47.392 22.47-29.371 14.508-16.961-.101-2.451h-.859l-98.954 64.251-17.618 2.275-7.583-7.103.936-11.652 3.589-3.791 29.749-20.474-.101.102.024.101z"/></svg>`;
1230
+ case 'openai':
1231
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 512 509.639"${classAttr}><path fill="#fff" d="M115.612 0h280.775C459.974 0 512 52.026 512 115.612v278.415c0 63.587-52.026 115.613-115.613 115.613H115.612C52.026 509.64 0 457.614 0 394.027V115.612C0 52.026 52.026 0 115.612 0z"/><path fill-rule="nonzero" d="M412.037 221.764a90.834 90.834 0 004.648-28.67 90.79 90.79 0 00-12.443-45.87c-16.37-28.496-46.738-46.089-79.605-46.089-6.466 0-12.943.683-19.264 2.04a90.765 90.765 0 00-67.881-30.515h-.576c-.059.002-.149.002-.216.002-39.807 0-75.108 25.686-87.346 63.554-25.626 5.239-47.748 21.31-60.682 44.03a91.873 91.873 0 00-12.407 46.077 91.833 91.833 0 0023.694 61.553 90.802 90.802 0 00-4.649 28.67 90.804 90.804 0 0012.442 45.87c16.369 28.504 46.74 46.087 79.61 46.087a91.81 91.81 0 0019.253-2.04 90.783 90.783 0 0067.887 30.516h.576l.234-.001c39.829 0 75.119-25.686 87.357-63.588 25.626-5.242 47.748-21.312 60.682-44.033a91.718 91.718 0 0012.383-46.035 91.83 91.83 0 00-23.693-61.553l-.004-.005zM275.102 413.161h-.094a68.146 68.146 0 01-43.611-15.8 56.936 56.936 0 002.155-1.221l72.54-41.901a11.799 11.799 0 005.962-10.251V241.651l30.661 17.704c.326.163.55.479.596.84v84.693c-.042 37.653-30.554 68.198-68.21 68.273h.001zm-146.689-62.649a68.128 68.128 0 01-9.152-34.085c0-3.904.341-7.817 1.005-11.663.539.323 1.48.897 2.155 1.285l72.54 41.901a11.832 11.832 0 0011.918-.002l88.563-51.137v35.408a1.1 1.1 0 01-.438.94l-73.33 42.339a68.43 68.43 0 01-34.11 9.12 68.359 68.359 0 01-59.15-34.11l-.001.004zm-19.083-158.36a68.044 68.044 0 0135.538-29.934c0 .625-.036 1.731-.036 2.5v83.801l-.001.07a11.79 11.79 0 005.954 10.242l88.564 51.13-30.661 17.704a1.096 1.096 0 01-1.034.093l-73.337-42.375a68.36 68.36 0 01-34.095-59.143 68.412 68.412 0 019.112-34.085l-.004-.003zm251.907 58.621l-88.563-51.137 30.661-17.697a1.097 1.097 0 011.034-.094l73.337 42.339c21.109 12.195 34.132 34.746 34.132 59.132 0 28.604-17.849 54.199-44.686 64.078v-86.308c.004-.032.004-.065.004-.096 0-4.219-2.261-8.119-5.919-10.217zm30.518-45.93c-.539-.331-1.48-.898-2.155-1.286l-72.54-41.901a11.842 11.842 0 00-5.958-1.611c-2.092 0-4.15.558-5.957 1.611l-88.564 51.137v-35.408l-.001-.061a1.1 1.1 0 01.44-.88l73.33-42.303a68.301 68.301 0 0134.108-9.129c37.704 0 68.281 30.577 68.281 68.281a68.69 68.69 0 01-.984 11.545v.005zm-191.843 63.109l-30.668-17.704a1.09 1.09 0 01-.596-.84v-84.692c.016-37.685 30.593-68.236 68.281-68.236a68.332 68.332 0 0143.689 15.804 63.09 63.09 0 00-2.155 1.222l-72.54 41.9a11.794 11.794 0 00-5.961 10.248v.068l-.05 102.23zm16.655-35.91l39.445-22.782 39.444 22.767v45.55l-39.444 22.767-39.445-22.767v-45.535z"/></svg>`;
1232
+ case 'gemini':
1233
+ return `<svg fill="none" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 16 16" width="${size}" height="${size}"${classAttr}><path d="M16 8.016A8.522 8.522 0 008.016 16h-.032A8.521 8.521 0 000 8.016v-.032A8.521 8.521 0 007.984 0h.032A8.522 8.522 0 0016 7.984v.032z" fill="url(#prefix__paint0_radial_980_20147)"/><defs><radialGradient id="prefix__paint0_radial_980_20147" cx="0" cy="0" r="1" gradientUnits="userSpaceOnUse" gradientTransform="matrix(16.1326 5.4553 -43.70045 129.2322 1.588 6.503)"><stop offset=".067" stop-color="#9168C0"/><stop offset=".343" stop-color="#5684D1"/><stop offset=".672" stop-color="#1BA1E3"/></radialGradient></defs></svg>`;
1234
+ case 'grok':
1235
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 512 509.641"${classAttr}><path d="M115.612 0h280.776C459.975 0 512 52.026 512 115.612v278.416c0 63.587-52.025 115.613-115.612 115.613H115.612C52.026 509.641 0 457.615 0 394.028V115.612C0 52.026 52.026 0 115.612 0z"/><path fill="#fff" d="M213.235 306.019l178.976-180.002v.169l51.695-51.763c-.924 1.32-1.86 2.605-2.785 3.89-39.281 54.164-58.46 80.649-43.07 146.922l-.09-.101c10.61 45.11-.744 95.137-37.398 131.836-46.216 46.306-120.167 56.611-181.063 14.928l42.462-19.675c38.863 15.278 81.392 8.57 111.947-22.03 30.566-30.6 37.432-75.159 22.065-112.252-2.92-7.025-11.67-8.795-17.792-4.263l-124.947 92.341zm-25.786 22.437l-.033.034L68.094 435.217c7.565-10.429 16.957-20.294 26.327-30.149 26.428-27.803 52.653-55.359 36.654-94.302-21.422-52.112-8.952-113.177 30.724-152.898 41.243-41.254 101.98-51.661 152.706-30.758 11.23 4.172 21.016 10.114 28.638 15.639l-42.359 19.584c-39.44-16.563-84.629-5.299-112.207 22.313-37.298 37.308-44.84 102.003-1.128 143.81z"/></svg>`;
1236
+ case 'pdf':
1237
+ return `<svg width="${size}" height="${size}" viewBox="0 0 32 32" fill="none"${classAttr}><path d="M6 4a2 2 0 012-2h11l5 5v19a2 2 0 01-2 2H8a2 2 0 01-2-2V4z" fill="#DC2626"/><path d="M19 2v5h5" stroke="#B91C1C" stroke-width="1" fill="none"/><text x="16" y="25" font-size="5" fill="white" text-anchor="middle" font-weight="600">PDF</text></svg>`;
1238
+ case 'image':
1239
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><circle cx="12" cy="12" r="10"/></svg>`;
1240
+ case 'noImage':
1241
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><rect x="3" y="3" width="18" height="18" rx="2" ry="2"/><circle cx="8.5" cy="8.5" r="1.5"/><polyline points="21,15 16,10 5,21"/><line x1="3" y1="3" x2="21" y2="21"/></svg>`;
1242
+ default:
1243
+ return `<svg xmlns="http://www.w3.org/2000/svg" width="${size}" height="${size}" viewBox="0 0 24 24" fill="none" stroke="${color}" stroke-width="2"${classAttr}><circle cx="12" cy="12" r="10"/></svg>`;
1244
+ }
1245
+ };
1246
+ /**
1247
+ * Universal action icon function - works for both React and vanilla JS
1248
+ * @param action - Action type string
1249
+ * @param framework - Target framework ('react' or 'vanilla')
1250
+ * @param options - Icon options (only used for vanilla)
1251
+ * @returns React component or SVG string based on framework
1252
+ */
1253
+ const getActionIcon = (action, framework = 'react', options = {}) => {
1254
+ const iconName = getIconNameForAction(action);
1255
+ if (!iconName)
1256
+ return null;
1257
+ if (framework === 'react') {
1258
+ return (jsxRuntime.jsx(Icon, { name: iconName, size: options.size || 'sm', className: options.className, color: options.color }));
1259
+ }
1260
+ else {
1261
+ return getIcon(iconName, { size: options.size || 'sm', ...options });
1262
+ }
1263
+ };
1264
+ /**
1265
+ * Get action icon as React component (legacy compatibility)
1266
+ * @param action - Action type string
1267
+ * @returns React component or null
1268
+ */
1269
+ const getActionIconReact = (action) => {
1270
+ return getActionIcon(action, 'react');
1271
+ };
1272
+ /**
1273
+ * Get provider icon name for AI providers
1274
+ * @param provider - Provider name (openai, claude, gemini)
1275
+ * @returns Icon name or null if not found
1276
+ */
1277
+ const getProviderIconName = (provider) => {
1278
+ if (!provider)
1279
+ return null;
1280
+ switch (provider.toLowerCase()) {
1281
+ case 'openai':
1282
+ return 'openai';
1283
+ case 'claude':
1284
+ return 'claude';
1285
+ case 'gemini':
1286
+ return 'gemini';
1287
+ case 'grok':
1288
+ return 'grok';
1289
+ default:
1290
+ return null;
1291
+ }
1292
+ };
1293
+
1294
+ class ErrorBoundary extends React.Component {
1295
+ constructor(props) {
1296
+ super(props);
1297
+ this.state = { hasError: false };
1298
+ }
1299
+ static getDerivedStateFromError(error) {
1300
+ return { hasError: true, error: error };
1301
+ }
1302
+ componentDidCatch(error, errorInfo) {
1303
+ console.error('AI Command Center Error Boundary caught an error:', error, errorInfo);
1304
+ }
1305
+ render() {
1306
+ if (this.state.hasError) {
1307
+ return (this.props.fallback || (jsxRuntime.jsx("div", { className: "p-4 bg-red-50 border border-red-200 rounded-lg max-w-md mx-auto", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-3", children: [jsxRuntime.jsx("div", { className: "flex-shrink-0 w-5 h-5 text-red-500 mt-0.5", children: jsxRuntime.jsx("svg", { fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zM8.707 7.293a1 1 0 00-1.414 1.414L8.586 10l-1.293 1.293a1 1 0 101.414 1.414L10 11.414l1.293 1.293a1 1 0 001.414-1.414L11.414 10l1.293-1.293a1 1 0 00-1.414-1.414L10 8.586 8.707 7.293z", clipRule: "evenodd" }) }) }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx("h3", { className: "text-red-800 font-semibold text-sm mb-2", children: "Something went wrong" }), jsxRuntime.jsx("p", { className: "text-red-700 text-sm mb-3 leading-relaxed", children: "The chat interface encountered an error. Please try refreshing the page or contact support if the problem persists." }), this.state.error && (jsxRuntime.jsxs("details", { className: "text-xs text-red-600", children: [jsxRuntime.jsx("summary", { className: "cursor-pointer hover:text-red-800 font-medium mb-1", children: "View error details" }), jsxRuntime.jsx("div", { className: "mt-2 p-2 bg-red-100 rounded border border-red-200 overflow-auto", children: jsxRuntime.jsxs("pre", { className: "whitespace-pre-wrap font-mono text-xs", children: [this.state.error.message, this.state.error.stack && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: ['\n\nStack trace:\n', this.state.error.stack] }))] }) })] })), jsxRuntime.jsxs("div", { className: "mt-3 flex gap-2", children: [jsxRuntime.jsx("button", { onClick: () => window.location.reload(), className: "px-3 py-1.5 bg-red-600 text-white text-xs font-medium rounded hover:bg-red-700 transition-colors", children: "Refresh Page" }), jsxRuntime.jsx("button", { onClick: () => this.setState({ hasError: false, error: undefined }), className: "px-3 py-1.5 bg-gray-100 text-gray-700 text-xs font-medium rounded hover:bg-gray-200 transition-colors", children: "Try Again" })] })] })] }) })));
1308
+ }
1309
+ return this.props.children;
1310
+ }
1311
+ }
1312
+
1313
+ // Helper: determine whether an object contains any displayable (non-empty) values
1314
+ function hasDisplayableFields(value) {
1315
+ if (value === null || value === undefined)
1316
+ return false;
1317
+ if (typeof value === 'string')
1318
+ return value.trim().length > 0;
1319
+ if (typeof value === 'number' || typeof value === 'boolean')
1320
+ return true;
1321
+ if (Array.isArray(value))
1322
+ return value.some((v) => hasDisplayableFields(v));
1323
+ if (typeof value === 'object') {
1324
+ return Object.entries(value).some(([key, v]) => {
1325
+ if (key.startsWith('_') || key.startsWith('$'))
1326
+ return false;
1327
+ return hasDisplayableFields(v);
1328
+ });
1329
+ }
1330
+ return false;
1331
+ }
1332
+ function ObjectViewer({ data, keyName, level = 0, isHighlighted = false, theme, }) {
1333
+ const [isExpanded, setIsExpanded] = React.useState(level === 0); // Auto-expand first level
1334
+ const isObject = data !== null && typeof data === 'object' && !Array.isArray(data);
1335
+ const isArray = Array.isArray(data);
1336
+ const isPrimitive = !isObject && !isArray;
1337
+ // Helper to format field names nicely
1338
+ const formatFieldName = (key) => {
1339
+ // Skip internal fields
1340
+ if (key.startsWith('_') || key.startsWith('$'))
1341
+ return '';
1342
+ // Convert camelCase to Title Case
1343
+ return key
1344
+ .replace(/([A-Z])/g, ' $1') // Add space before capital letters
1345
+ .replace(/^./, (str) => str.toUpperCase()) // Capitalize first letter
1346
+ .trim();
1347
+ };
1348
+ // Helper to format values nicely
1349
+ const formatValue = (value) => {
1350
+ if (value === null)
1351
+ return 'None';
1352
+ if (value === undefined)
1353
+ return 'Not set';
1354
+ if (typeof value === 'string') {
1355
+ // Filter out empty strings
1356
+ if (value === '')
1357
+ return '';
1358
+ return value; // No quotes
1359
+ }
1360
+ if (typeof value === 'boolean')
1361
+ return value ? 'Yes' : 'No';
1362
+ if (typeof value === 'number') {
1363
+ // Check if this looks like a Unix timestamp (milliseconds since epoch)
1364
+ // Typical range: 1000000000000 (2001) to 2000000000000 (2033)
1365
+ if (value > 1000000000000 && value < 2000000000000) {
1366
+ try {
1367
+ const date = new Date(value);
1368
+ // Only format if it's a valid date and not too far in the past/future
1369
+ if (!isNaN(date.getTime()) && date.getFullYear() > 1970 && date.getFullYear() < 2100) {
1370
+ return date.toLocaleDateString('en-US', {
1371
+ year: 'numeric',
1372
+ month: 'short',
1373
+ day: 'numeric',
1374
+ hour: '2-digit',
1375
+ minute: '2-digit',
1376
+ });
1377
+ }
1378
+ }
1379
+ catch (e) {
1380
+ // Fall through to default number formatting
1381
+ }
1382
+ }
1383
+ return value.toString();
1384
+ }
1385
+ return String(value);
1386
+ };
1387
+ // Helper to get array summary
1388
+ const getArraySummary = (arr) => {
1389
+ if (arr.length === 0)
1390
+ return 'No items';
1391
+ if (arr.length === 1)
1392
+ return '1 item';
1393
+ return `${arr.length} items`;
1394
+ };
1395
+ ({
1396
+ color: isHighlighted
1397
+ ? theme?.colors.text.inverse || '#ffffff'
1398
+ : theme?.colors.text.primary || '#f9fafb',
1399
+ });
1400
+ const keyStyle = {
1401
+ color: isHighlighted
1402
+ ? theme?.colors.text.inverse || '#ffffff'
1403
+ : theme?.colors.text.secondary || '#d1d5db',
1404
+ fontWeight: '500',
1405
+ };
1406
+ const valueStyle = {
1407
+ color: isHighlighted
1408
+ ? 'rgba(255, 255, 255, 0.9)'
1409
+ : theme?.colors.text.primary || '#f9fafb',
1410
+ };
1411
+ const metaStyle = {
1412
+ color: isHighlighted
1413
+ ? 'rgba(255, 255, 255, 0.6)'
1414
+ : theme?.colors.text.tertiary || '#9ca3af',
1415
+ fontSize: '11px',
1416
+ };
1417
+ // For root level (level 0), don't show the container, just show contents
1418
+ if (level === 0 && isObject) {
1419
+ const filteredEntries = Object.entries(data).filter(([key, value]) => {
1420
+ // Skip internal fields
1421
+ if (key.startsWith('_') || key.startsWith('$'))
1422
+ return false;
1423
+ // Skip empty values
1424
+ if (value === '' || value === null || value === undefined)
1425
+ return false;
1426
+ return true;
1427
+ });
1428
+ return (jsxRuntime.jsx("div", { className: "space-y-2", children: filteredEntries.map(([key, value]) => (jsxRuntime.jsx(ObjectViewer, { data: value, keyName: key, level: 1, isHighlighted: isHighlighted, theme: theme }, key))) }));
1429
+ }
1430
+ if (isPrimitive) {
1431
+ const formattedKey = formatFieldName(keyName || '');
1432
+ if (!formattedKey)
1433
+ return null; // Skip internal fields
1434
+ // Skip empty values
1435
+ const formattedValue = formatValue(data);
1436
+ if (formattedValue === '')
1437
+ return null;
1438
+ return (jsxRuntime.jsxs("div", { className: "flex items-start gap-2 py-1 text-sm", style: { marginLeft: `${(level - 1) * 16}px` }, children: [jsxRuntime.jsxs("span", { style: keyStyle, className: "min-w-0 flex-shrink-0", children: [formattedKey, ":"] }), jsxRuntime.jsx("span", { style: valueStyle, className: "flex-1 break-words", children: formattedValue })] }));
1439
+ }
1440
+ const formattedKey = formatFieldName(keyName || '');
1441
+ if (!formattedKey && keyName)
1442
+ return null; // Skip internal fields
1443
+ if (isArray) {
1444
+ return (jsxRuntime.jsxs("div", { style: { marginLeft: `${(level - 1) * 16}px` }, children: [jsxRuntime.jsxs("button", { onClick: (e) => {
1445
+ e.stopPropagation();
1446
+ setIsExpanded(!isExpanded);
1447
+ }, className: "flex items-center gap-2 py-1 hover:opacity-80 transition-opacity text-sm w-full text-left", children: [jsxRuntime.jsx("svg", { className: `w-3 h-3 transition-transform flex-shrink-0 ${isExpanded ? 'rotate-90' : ''}`, fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("span", { style: keyStyle, className: "font-medium", children: [formattedKey, ":"] }), jsxRuntime.jsx("span", { style: metaStyle, children: getArraySummary(data) })] }), isExpanded && (jsxRuntime.jsx("div", { className: "mt-1 space-y-1", children: data.map((item, index) => {
1448
+ const isItemObject = item !== null &&
1449
+ typeof item === 'object' &&
1450
+ !Array.isArray(item);
1451
+ if (isItemObject) {
1452
+ return (jsxRuntime.jsx("div", { style: { marginLeft: `${(level - 1) * 16 + 16}px` }, children: jsxRuntime.jsx(ObjectViewer, { data: item, keyName: `Item ${index + 1}`, level: level + 1, isHighlighted: isHighlighted, theme: theme }) }, index));
1453
+ }
1454
+ else {
1455
+ return (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 py-1 text-sm", style: { marginLeft: `${(level - 1) * 16 + 16}px` }, children: [jsxRuntime.jsxs("span", { style: metaStyle, className: "text-xs", children: [index + 1, "."] }), jsxRuntime.jsx("span", { style: valueStyle, children: formatValue(item) })] }, index));
1456
+ }
1457
+ }) }))] }));
1458
+ }
1459
+ // Regular object
1460
+ return (jsxRuntime.jsxs("div", { style: { marginLeft: `${(level - 1) * 16}px` }, children: [jsxRuntime.jsxs("button", { onClick: (e) => {
1461
+ e.stopPropagation();
1462
+ setIsExpanded(!isExpanded);
1463
+ }, className: "flex items-center gap-2 py-1 hover:opacity-80 transition-opacity text-sm w-full text-left", children: [jsxRuntime.jsx("svg", { className: `w-3 h-3 transition-transform flex-shrink-0 ${isExpanded ? 'rotate-90' : ''}`, fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", clipRule: "evenodd" }) }), jsxRuntime.jsx("span", { style: keyStyle, className: "font-medium", children: formattedKey }), jsxRuntime.jsxs("span", { style: metaStyle, children: [Object.entries(data).filter(([key, value]) => {
1464
+ // Skip internal fields
1465
+ if (key.startsWith('_') || key.startsWith('$'))
1466
+ return false;
1467
+ // Skip empty values
1468
+ if (value === '' || value === null || value === undefined)
1469
+ return false;
1470
+ return true;
1471
+ }).length, ' ', "fields"] })] }), isExpanded && (jsxRuntime.jsx("div", { className: "mt-1 space-y-1", children: Object.entries(data)
1472
+ .filter(([key, value]) => {
1473
+ // Skip internal fields
1474
+ if (key.startsWith('_') || key.startsWith('$'))
1475
+ return false;
1476
+ // Skip empty values
1477
+ if (value === '' || value === null || value === undefined)
1478
+ return false;
1479
+ return true;
1480
+ })
1481
+ .map(([key, value]) => (jsxRuntime.jsx(ObjectViewer, { data: value, keyName: key, level: level + 1, isHighlighted: isHighlighted, theme: theme }, key))) }))] }));
1482
+ }
1483
+ function SuggestionCard({ suggestion, onClick, isHighlighted = false, theme, showIcon = true, }) {
1484
+ const [isExpanded, setIsExpanded] = React.useState(false);
1485
+ return (jsxRuntime.jsxs("div", { className: "rounded-lg overflow-hidden transition-all duration-200 cursor-pointer group", style: {
1486
+ backgroundColor: isHighlighted
1487
+ ? theme?.colors.primary[600] || '#2563eb'
1488
+ : theme?.colors.surface.primary || '#1f2937',
1489
+ border: `1px solid ${isHighlighted
1490
+ ? theme?.colors.primary[500] || '#3b82f6'
1491
+ : theme?.colors.border.primary || '#374151'}`,
1492
+ }, onClick: () => onClick(suggestion), children: [jsxRuntime.jsx("div", { className: "p-3", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [showIcon && (jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 rounded-lg flex items-center justify-center transition-colors", style: {
1493
+ backgroundColor: isHighlighted
1494
+ ? theme?.colors.primary[700] || '#1d4ed8'
1495
+ : theme?.colors.surface.secondary || '#374151',
1496
+ }, children: jsxRuntime.jsx("span", { className: "text-sm", style: {
1497
+ color: isHighlighted
1498
+ ? theme?.colors.text.inverse || '#ffffff'
1499
+ : theme?.colors.primary[400] || '#60a5fa',
1500
+ }, children: getActionIconReact(suggestion.actionType) }) })), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 mb-1", children: [jsxRuntime.jsx("span", { className: "inline-flex items-center px-2 py-0.5 rounded text-xs font-medium", style: {
1501
+ backgroundColor: isHighlighted
1502
+ ? theme?.colors.primary[700] || '#1d4ed8'
1503
+ : theme?.colors.surface.secondary || '#374151',
1504
+ color: isHighlighted
1505
+ ? theme?.colors.text.inverse || '#ffffff'
1506
+ : theme?.colors.text.secondary || '#9ca3af',
1507
+ }, children: suggestion.actionType.toUpperCase() }), suggestion.shortcut && (jsxRuntime.jsx("span", { className: "text-xs font-mono", style: { color: theme?.colors.text.tertiary || '#6b7280' }, children: suggestion.shortcut }))] }), jsxRuntime.jsx("div", { className: "font-medium transition-colors mb-1", style: {
1508
+ color: isHighlighted
1509
+ ? theme?.colors.text.inverse || '#ffffff'
1510
+ : theme?.colors.text.primary || '#f9fafb',
1511
+ }, children: suggestion.action }), suggestion.message && (jsxRuntime.jsx("p", { className: "text-sm line-clamp-2", style: {
1512
+ color: isHighlighted
1513
+ ? theme?.colors.text.inverse || '#ffffff'
1514
+ : theme?.colors.text.secondary || '#d1d5db',
1515
+ }, children: suggestion.message }))] }), jsxRuntime.jsx("div", { className: "flex-shrink-0 transition-colors", style: {
1516
+ color: isHighlighted
1517
+ ? theme?.colors.text.inverse || '#ffffff'
1518
+ : theme?.colors.text.tertiary || '#9ca3af',
1519
+ }, children: jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M7.293 14.707a1 1 0 010-1.414L10.586 10 7.293 6.707a1 1 0 011.414-1.414l4 4a1 1 0 010 1.414l-4 4a1 1 0 01-1.414 0z", clipRule: "evenodd" }) }) })] }) }), ((suggestion.formState &&
1520
+ Object.keys(suggestion.formState).length > 0) ||
1521
+ suggestion.path ||
1522
+ (suggestion.message && !isExpanded)) && (jsxRuntime.jsxs("div", { className: "border-t", style: { borderColor: theme?.colors.border.primary || '#374151' }, children: [jsxRuntime.jsxs("button", { onClick: (e) => {
1523
+ e.stopPropagation();
1524
+ setIsExpanded(!isExpanded);
1525
+ }, className: "w-full px-3 py-2 text-xs transition-colors flex items-center justify-center gap-1", style: {
1526
+ color: isHighlighted
1527
+ ? theme?.colors.text.inverse || '#ffffff'
1528
+ : theme?.colors.text.tertiary || '#9ca3af',
1529
+ backgroundColor: isHighlighted
1530
+ ? 'rgba(255, 255, 255, 0.1)'
1531
+ : 'transparent',
1532
+ }, onMouseEnter: (e) => {
1533
+ if (!isHighlighted) {
1534
+ e.currentTarget.style.backgroundColor =
1535
+ theme?.colors.surface.secondary || '#374151';
1536
+ }
1537
+ }, onMouseLeave: (e) => {
1538
+ if (!isHighlighted) {
1539
+ e.currentTarget.style.backgroundColor = 'transparent';
1540
+ }
1541
+ }, children: [jsxRuntime.jsx("span", { children: isExpanded ? 'Show less' : 'Show details' }), jsxRuntime.jsx("svg", { className: `w-3 h-3 transition-transform ${isExpanded ? 'rotate-180' : ''}`, fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M5.293 7.293a1 1 0 011.414 0L10 10.586l3.293-3.293a1 1 0 101.415-1.414l-4 4a1 1 0 01-1.414 0l-4-4a1 1 0 010-1.414z", clipRule: "evenodd" }) })] }), isExpanded && (jsxRuntime.jsxs("div", { className: "px-3 pb-3 space-y-2", style: {
1542
+ backgroundColor: isHighlighted
1543
+ ? 'rgba(255, 255, 255, 0.05)'
1544
+ : theme?.colors.surface.primary || '#1f2937',
1545
+ }, children: [suggestion.message && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-xs mb-2 font-medium", style: {
1546
+ color: isHighlighted
1547
+ ? theme?.colors.text.inverse || '#ffffff'
1548
+ : theme?.colors.text.tertiary || '#9ca3af',
1549
+ }, children: "Description:" }), jsxRuntime.jsx("p", { className: "text-sm leading-relaxed", style: {
1550
+ color: isHighlighted
1551
+ ? theme?.colors.text.inverse || '#ffffff'
1552
+ : theme?.colors.text.secondary || '#d1d5db',
1553
+ }, children: suggestion.message })] })), suggestion.formState &&
1554
+ hasDisplayableFields(suggestion.formState) && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-xs mb-2 font-medium", style: {
1555
+ color: isHighlighted
1556
+ ? theme?.colors.text.inverse || '#ffffff'
1557
+ : theme?.colors.text.tertiary || '#9ca3af',
1558
+ }, children: "Will populate:" }), jsxRuntime.jsx("div", { className: "p-2 rounded border", style: {
1559
+ backgroundColor: isHighlighted
1560
+ ? 'rgba(255, 255, 255, 0.05)'
1561
+ : theme?.colors.surface.primary || '#1f2937',
1562
+ borderColor: theme?.colors.border.primary || '#374151',
1563
+ }, children: jsxRuntime.jsx(ObjectViewer, { data: suggestion.formState, isHighlighted: isHighlighted, theme: theme }) })] })), suggestion.queryParams &&
1564
+ Object.keys(suggestion.queryParams).length > 0 && (jsxRuntime.jsxs("div", { children: [jsxRuntime.jsx("div", { className: "text-xs mb-2 font-medium", style: {
1565
+ color: isHighlighted
1566
+ ? theme?.colors.text.inverse || '#ffffff'
1567
+ : theme?.colors.text.tertiary || '#9ca3af',
1568
+ }, children: "Query parameters:" }), jsxRuntime.jsx("div", { className: "p-2 rounded border", style: {
1569
+ backgroundColor: isHighlighted
1570
+ ? 'rgba(255, 255, 255, 0.05)'
1571
+ : theme?.colors.surface.primary || '#1f2937',
1572
+ borderColor: theme?.colors.border.primary || '#374151',
1573
+ }, children: jsxRuntime.jsx(ObjectViewer, { data: suggestion.queryParams, isHighlighted: isHighlighted, theme: theme }) })] })), suggestion.path && (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-xs", children: [jsxRuntime.jsx("svg", { className: "w-3 h-3", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10.293 3.293a1 1 0 011.414 0l6 6a1 1 0 010 1.414l-6 6a1 1 0 01-1.414-1.414L14.586 11H3a1 1 0 110-2h11.586l-4.293-4.293a1 1 0 010-1.414z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("span", { style: {
1574
+ color: isHighlighted
1575
+ ? theme?.colors.text.inverse || '#ffffff'
1576
+ : theme?.colors.text.secondary || '#d1d5db',
1577
+ }, children: ["Navigate to", ' ', jsxRuntime.jsx("span", { className: "font-medium", children: suggestion.path })] })] }))] }))] }))] }));
1578
+ }
1579
+ function SuggestionsPanel({ query, onSuggestionSelect, onClose, userId, formData, serverConfig, theme, isLoading = false, placeholder = 'Search for commands...', maxSuggestions = 10, showIcons = true, className = '', context, // <-- Add context prop
1580
+ }) {
1581
+ const [suggestions, setSuggestions] = React.useState([]);
1582
+ const [highlightedIndex, setHighlightedIndex] = React.useState(0);
1583
+ const [isLoadingSuggestions, setIsLoadingSuggestions] = React.useState(false);
1584
+ const containerRef = React.useRef(null);
1585
+ // Build API URL from config
1586
+ const buildUrlFromConfig = (config) => {
1587
+ const protocol = config.secure !== undefined
1588
+ ? config.secure
1589
+ ? 'https:'
1590
+ : 'http:'
1591
+ : window.location.protocol;
1592
+ const hostname = config.domain || window.location.hostname;
1593
+ const port = config.port
1594
+ ? `:${config.port}`
1595
+ : window.location.port
1596
+ ? `:${window.location.port}`
1597
+ : '';
1598
+ const suffix = config.suffix || '';
1599
+ if (!config.domain && !config.port && !config.secure) {
1600
+ return suffix;
1601
+ }
1602
+ return `${protocol}//${hostname}${port}${suffix}`;
1603
+ };
1604
+ // Fetch suggestions when query changes
1605
+ React.useEffect(() => {
1606
+ if (!query.trim()) {
1607
+ setSuggestions([]);
1608
+ return;
1609
+ }
1610
+ const timeoutId = setTimeout(() => {
1611
+ fetchSuggestions(query);
1612
+ }, 300); // Debounce requests
1613
+ return () => clearTimeout(timeoutId);
1614
+ }, [query, userId, formData]);
1615
+ // Reset highlighted index when suggestions change
1616
+ React.useEffect(() => {
1617
+ setHighlightedIndex(0);
1618
+ }, [suggestions]);
1619
+ const fetchSuggestions = async (searchQuery) => {
1620
+ setIsLoadingSuggestions(true);
1621
+ try {
1622
+ const apiUrl = serverConfig ? buildUrlFromConfig(serverConfig) : '/api';
1623
+ const response = await fetch(`${apiUrl}/suggest`, {
1624
+ method: 'POST',
1625
+ headers: {
1626
+ 'Content-Type': 'application/json',
1627
+ },
1628
+ body: JSON.stringify({
1629
+ query: searchQuery,
1630
+ userId,
1631
+ formData: formData || {},
1632
+ chatHistory: [], // Empty for standalone suggestions
1633
+ isCommandSearch: true, // Flag to indicate this is for command search
1634
+ ...(context ? { context } : {}), // <-- Add context to request body
1635
+ }),
1636
+ });
1637
+ if (!response.ok) {
1638
+ throw new Error(`HTTP error! status: ${response.status}`);
1639
+ }
1640
+ // Handle streaming response
1641
+ const reader = response.body?.getReader();
1642
+ if (!reader) {
1643
+ throw new Error('No response body');
1644
+ }
1645
+ const decoder = new TextDecoder();
1646
+ let buffer = '';
1647
+ let collectedSuggestions = [];
1648
+ while (true) {
1649
+ const { done, value } = await reader.read();
1650
+ if (done)
1651
+ break;
1652
+ buffer += decoder.decode(value, { stream: true });
1653
+ const lines = buffer.split('\n');
1654
+ buffer = lines.pop() || '';
1655
+ for (const line of lines) {
1656
+ if (line.startsWith('data: ')) {
1657
+ try {
1658
+ const data = JSON.parse(line.slice(6));
1659
+ if (data.suggestions) {
1660
+ collectedSuggestions = data.suggestions;
1661
+ }
1662
+ else if (data.suggestion) {
1663
+ collectedSuggestions.push(data.suggestion);
1664
+ }
1665
+ }
1666
+ catch (e) {
1667
+ console.warn('Failed to parse SSE data:', line);
1668
+ }
1669
+ }
1670
+ else if (line.startsWith('event: end')) {
1671
+ break;
1672
+ }
1673
+ }
1674
+ }
1675
+ // Apply max suggestions limit
1676
+ const limitedSuggestions = collectedSuggestions.slice(0, maxSuggestions);
1677
+ setSuggestions(limitedSuggestions);
1678
+ }
1679
+ catch (error) {
1680
+ console.error('Error fetching suggestions:', error);
1681
+ setSuggestions([]);
1682
+ }
1683
+ finally {
1684
+ setIsLoadingSuggestions(false);
1685
+ }
1686
+ };
1687
+ const handleSuggestionClick = (suggestion) => {
1688
+ onSuggestionSelect(suggestion);
1689
+ };
1690
+ const handleKeyDown = (event) => {
1691
+ if (suggestions.length === 0)
1692
+ return;
1693
+ switch (event.key) {
1694
+ case 'ArrowDown':
1695
+ event.preventDefault();
1696
+ setHighlightedIndex((prev) => prev < suggestions.length - 1 ? prev + 1 : 0);
1697
+ break;
1698
+ case 'ArrowUp':
1699
+ event.preventDefault();
1700
+ setHighlightedIndex((prev) => prev > 0 ? prev - 1 : suggestions.length - 1);
1701
+ break;
1702
+ case 'Enter':
1703
+ event.preventDefault();
1704
+ if (suggestions[highlightedIndex]) {
1705
+ handleSuggestionClick(suggestions[highlightedIndex]);
1706
+ }
1707
+ break;
1708
+ case 'Escape':
1709
+ event.preventDefault();
1710
+ onClose?.();
1711
+ break;
1712
+ }
1713
+ };
1714
+ // Generate custom scrollbar styles based on theme
1715
+ const scrollbarStyles = `
1716
+ .suggestions-scrollbar {
1717
+ scrollbar-width: thin;
1718
+ scrollbar-color: ${theme?.colors.primary[500] || '#3b82f6'} ${theme?.colors.surface.secondary || '#374151'};
1719
+ }
1720
+ .suggestions-scrollbar::-webkit-scrollbar {
1721
+ width: 8px;
1722
+ height: 8px;
1723
+ }
1724
+ .suggestions-scrollbar::-webkit-scrollbar-track {
1725
+ background: ${theme?.colors.surface.secondary || '#374151'};
1726
+ border-radius: 4px;
1727
+ }
1728
+ .suggestions-scrollbar::-webkit-scrollbar-thumb {
1729
+ background: ${theme?.colors.primary[500] || '#3b82f6'};
1730
+ border-radius: 4px;
1731
+ border: 1px solid ${theme?.colors.surface.secondary || '#374151'};
1732
+ }
1733
+ .suggestions-scrollbar::-webkit-scrollbar-thumb:hover {
1734
+ background: ${theme?.colors.primary[600] || '#2563eb'};
1735
+ }
1736
+ `;
1737
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: scrollbarStyles }), jsxRuntime.jsxs("div", { ref: containerRef, className: `rounded-lg overflow-hidden ${className}`, style: {
1738
+ backgroundColor: theme?.colors.surface.elevated || '#1f2937',
1739
+ border: `1px solid ${theme?.colors.border.primary || '#374151'}`,
1740
+ boxShadow: theme?.shadows.xl ||
1741
+ '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
1742
+ }, onKeyDown: handleKeyDown, tabIndex: 0, children: [(isLoading || isLoadingSuggestions) && (jsxRuntime.jsx("div", { className: "p-4 border-b", style: { borderColor: theme?.colors.border.primary || '#374151' }, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm", style: { color: theme?.colors.text.secondary || '#d1d5db' }, children: [jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2", style: { borderColor: theme?.colors.primary[500] || '#3b82f6' } }), jsxRuntime.jsx("span", { children: "Finding suggestions..." })] }) })), suggestions.length > 0 && (jsxRuntime.jsx("div", { className: "max-h-80 overflow-y-auto suggestions-scrollbar", children: jsxRuntime.jsx("div", { className: "p-2 space-y-2", children: suggestions.map((suggestion, index) => (jsxRuntime.jsx(SuggestionCard, { suggestion: suggestion, onClick: handleSuggestionClick, theme: theme, showIcon: showIcons, isHighlighted: index === highlightedIndex }, `${suggestion.actionType}-${suggestion.action}-${index}`))) }) })), !isLoading &&
1743
+ !isLoadingSuggestions &&
1744
+ suggestions.length === 0 &&
1745
+ query.trim() && (jsxRuntime.jsxs("div", { className: "p-8 text-center", children: [jsxRuntime.jsx("svg", { className: "w-6 h-6 mx-auto mb-2", style: { color: theme?.colors.text.tertiary || '#9ca3af' }, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z" }) }), jsxRuntime.jsxs("p", { className: "text-sm", style: { color: theme?.colors.text.secondary || '#d1d5db' }, children: ["No suggestions found for \"", query, "\""] }), jsxRuntime.jsx("p", { className: "text-xs mt-1", style: { color: theme?.colors.text.tertiary || '#9ca3af' }, children: "Try a different search term" })] })), !query.trim() && (jsxRuntime.jsxs("div", { className: "p-8 text-center", children: [jsxRuntime.jsx("svg", { className: "w-6 h-6 mx-auto mb-2", style: { color: theme?.colors.text.tertiary || '#9ca3af' }, fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M9 19V6l12-3v13M9 19c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM22 16c0 1.105-1.343 2-3 2s-3-.895-3-2 1.343-2 3-2 3 .895 3 2zM17 6l-8 2.5" }) }), jsxRuntime.jsx("p", { className: "text-sm", style: { color: theme?.colors.text.secondary || '#d1d5db' }, children: "Start typing to search for commands..." }), jsxRuntime.jsx("p", { className: "text-xs mt-1", style: { color: theme?.colors.text.tertiary || '#9ca3af' }, children: "Use \u2191\u2193 to navigate, Enter to select" })] }))] })] }));
1746
+ }
1747
+
1748
+ const ACCEPTED_FILE_TYPES = {
1749
+ images: {
1750
+ mimeTypes: [
1751
+ 'image/jpeg',
1752
+ 'image/jpg',
1753
+ 'image/png',
1754
+ 'image/gif',
1755
+ 'image/webp',
1756
+ ],
1757
+ extensions: ['.jpg', '.jpeg', '.png', '.gif', '.webp'],
1758
+ description: 'images (JPG, PNG, GIF, WebP)',
1759
+ },
1760
+ pdfs: {
1761
+ mimeTypes: ['application/pdf'],
1762
+ extensions: ['.pdf'],
1763
+ description: 'PDF documents',
1764
+ },
1765
+ textFiles: {
1766
+ mimeTypes: ['text/plain', 'text/rtf'],
1767
+ extensions: ['.txt', '.rtf'],
1768
+ description: 'text files (TXT, RTF)',
1769
+ },
1770
+ wordDocs: {
1771
+ mimeTypes: [
1772
+ 'application/msword',
1773
+ 'application/vnd.openxmlformats-officedocument.wordprocessingml.document',
1774
+ ],
1775
+ extensions: ['.doc', '.docx'],
1776
+ description: 'Word documents (DOC, DOCX)',
1777
+ },
1778
+ spreadsheets: {
1779
+ mimeTypes: [
1780
+ 'application/vnd.ms-excel',
1781
+ 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet',
1782
+ 'text/csv',
1783
+ ],
1784
+ extensions: ['.xls', '.xlsx', '.csv'],
1785
+ description: 'spreadsheets and CSV files',
1786
+ },
1787
+ configFiles: {
1788
+ mimeTypes: ['application/json', 'text/xml', 'application/xml'],
1789
+ extensions: ['.json', '.xml'],
1790
+ description: 'configuration files (JSON, XML)',
1791
+ },
1792
+ };
1793
+ // Currently accepted file types - easy to modify
1794
+ const CURRENT_ACCEPTED_TYPES = [
1795
+ 'images',
1796
+ // 'pdfs',
1797
+ 'textFiles',
1798
+ // 'wordDocs',
1799
+ // 'spreadsheets',
1800
+ 'configFiles',
1801
+ ];
1802
+ /**
1803
+ * File processing utility
1804
+ */
1805
+ const FileHandler = {
1806
+ // Get all accepted MIME types and extensions
1807
+ getAcceptedTypes() {
1808
+ const mimeTypes = [];
1809
+ const extensions = [];
1810
+ const descriptions = [];
1811
+ CURRENT_ACCEPTED_TYPES.forEach((typeKey) => {
1812
+ const config = ACCEPTED_FILE_TYPES[typeKey];
1813
+ if (config) {
1814
+ mimeTypes.push(...config.mimeTypes);
1815
+ extensions.push(...config.extensions);
1816
+ descriptions.push(config.description);
1817
+ }
1818
+ });
1819
+ return { mimeTypes, extensions, descriptions };
1820
+ },
1821
+ // Check if a file is accepted
1822
+ isFileAccepted(file) {
1823
+ const { mimeTypes, extensions } = this.getAcceptedTypes();
1824
+ // Check MIME type
1825
+ if (mimeTypes.some((type) => file.type === type || file.type.startsWith(type.split('/')[0] + '/'))) {
1826
+ return true;
1827
+ }
1828
+ // Check file extension as fallback
1829
+ const fileName = file.name.toLowerCase();
1830
+ return extensions.some((ext) => fileName.endsWith(ext.toLowerCase()));
1831
+ },
1832
+ // Process a list of files and separate accepted from rejected
1833
+ processFiles(files) {
1834
+ const acceptedFiles = [];
1835
+ const rejectedFiles = [];
1836
+ files.forEach((file) => {
1837
+ if (this.isFileAccepted(file)) {
1838
+ acceptedFiles.push(file);
1839
+ }
1840
+ else {
1841
+ rejectedFiles.push(file);
1842
+ }
1843
+ });
1844
+ return { acceptedFiles, rejectedFiles };
1845
+ },
1846
+ // Get user-friendly description of accepted types
1847
+ getAcceptedTypesDescription() {
1848
+ const { descriptions } = this.getAcceptedTypes();
1849
+ return descriptions.join(', ');
1850
+ },
1851
+ // Get accept attribute for HTML file input
1852
+ getInputAcceptAttribute() {
1853
+ const { mimeTypes, extensions } = this.getAcceptedTypes();
1854
+ return [...mimeTypes, ...extensions].join(',');
1855
+ },
1856
+ // Get appropriate icon for file type
1857
+ getFileIcon(file) {
1858
+ const fileName = file.name.toLowerCase();
1859
+ const fileType = file.type.toLowerCase();
1860
+ // PDF files
1861
+ if (fileType === 'application/pdf' || fileName.endsWith('.pdf')) {
1862
+ return jsxRuntime.jsx(Icon, { name: "pdf", size: 40 });
1863
+ }
1864
+ // Word documents
1865
+ if (fileType.includes('word') ||
1866
+ fileName.endsWith('.doc') ||
1867
+ fileName.endsWith('.docx')) {
1868
+ return jsxRuntime.jsx(Icon, { name: "word", size: 40 });
1869
+ }
1870
+ // Excel files
1871
+ if (fileType.includes('excel') ||
1872
+ fileType.includes('spreadsheet') ||
1873
+ fileName.endsWith('.xls') ||
1874
+ fileName.endsWith('.xlsx')) {
1875
+ return jsxRuntime.jsx(Icon, { name: "excel", size: 40 });
1876
+ }
1877
+ // CSV files
1878
+ if (fileType === 'text/csv' || fileName.endsWith('.csv')) {
1879
+ return jsxRuntime.jsx(Icon, { name: "csv", size: 40 });
1880
+ }
1881
+ // RTF files
1882
+ if (fileType === 'text/rtf' || fileName.endsWith('.rtf')) {
1883
+ return jsxRuntime.jsx(Icon, { name: "rtf", size: 40 });
1884
+ }
1885
+ // Plain text files
1886
+ if (fileType === 'text/plain' || fileName.endsWith('.txt')) {
1887
+ return jsxRuntime.jsx(Icon, { name: "text", size: 40 });
1888
+ }
1889
+ // Default generic file icon
1890
+ return jsxRuntime.jsx(Icon, { name: "file-generic", size: 40 });
1891
+ },
1892
+ // Format file size for display
1893
+ formatFileSize(bytes) {
1894
+ if (bytes === 0)
1895
+ return '0 B';
1896
+ const k = 1024;
1897
+ const sizes = ['B', 'KB', 'MB', 'GB'];
1898
+ const i = Math.floor(Math.log(bytes) / Math.log(k));
1899
+ return parseFloat((bytes / Math.pow(k, i)).toFixed(1)) + ' ' + sizes[i];
1900
+ },
1901
+ // Get file type description
1902
+ getFileTypeDescription(file) {
1903
+ const fileName = file.name.toLowerCase();
1904
+ const fileType = file.type.toLowerCase();
1905
+ if (fileType === 'application/pdf' || fileName.endsWith('.pdf'))
1906
+ return 'PDF Document';
1907
+ if (fileType.includes('word') ||
1908
+ fileName.endsWith('.doc') ||
1909
+ fileName.endsWith('.docx'))
1910
+ return 'Word Document';
1911
+ if (fileType.includes('excel') ||
1912
+ fileType.includes('spreadsheet') ||
1913
+ fileName.endsWith('.xls') ||
1914
+ fileName.endsWith('.xlsx'))
1915
+ return 'Excel Spreadsheet';
1916
+ if (fileType === 'text/csv' || fileName.endsWith('.csv'))
1917
+ return 'CSV File';
1918
+ if (fileType === 'text/rtf' || fileName.endsWith('.rtf'))
1919
+ return 'Rich Text Document';
1920
+ if (fileType === 'text/plain' || fileName.endsWith('.txt'))
1921
+ return 'Text File';
1922
+ if (fileType.startsWith('image/'))
1923
+ return 'Image';
1924
+ return 'Document';
1925
+ },
1926
+ };
1927
+
1928
+ function useModelSwitcher({ initialProvider = DEFAULT_CONFIG.service, initialModel = DEFAULT_CONFIG.model, customModels, } = {}) {
1929
+ // Use default models or custom models
1930
+ const availableModels = customModels || DEFAULT_MODELS;
1931
+ // Simple state management - no API calls
1932
+ const [currentModel, setCurrentModel] = React.useState({
1933
+ provider: initialProvider,
1934
+ model: initialModel,
1935
+ });
1936
+ const [usageStats, setUsageStats] = React.useState({
1937
+ requests: 0,
1938
+ totalTokens: 0,
1939
+ totalComputeUnits: 0,
1940
+ modelSwitches: 0,
1941
+ uptime: 0,
1942
+ providers: [],
1943
+ currentProvider: initialProvider,
1944
+ currentModel: initialModel,
1945
+ });
1946
+ // Track session start time for uptime calculation
1947
+ const [sessionStartTime] = React.useState(Date.now());
1948
+ // Switch model function - just updates local state
1949
+ const switchModel = React.useCallback((model, provider) => {
1950
+ const newProvider = provider || currentModel.provider;
1951
+ // Update current model
1952
+ setCurrentModel({
1953
+ provider: newProvider,
1954
+ model: model,
1955
+ });
1956
+ // Update usage stats
1957
+ setUsageStats(prev => ({
1958
+ ...prev,
1959
+ modelSwitches: prev.modelSwitches + 1,
1960
+ currentProvider: newProvider,
1961
+ currentModel: model,
1962
+ }));
1963
+ return { success: true, current: { provider: newProvider, model } };
1964
+ }, [currentModel.provider]);
1965
+ // Update usage stats when requests are made (called from streaming hook)
1966
+ const trackUsage = React.useCallback((tokens, computeUnits) => {
1967
+ setUsageStats(prev => ({
1968
+ ...prev,
1969
+ requests: prev.requests + 1,
1970
+ totalTokens: prev.totalTokens + tokens,
1971
+ totalComputeUnits: prev.totalComputeUnits + computeUnits,
1972
+ uptime: Math.floor((Date.now() - sessionStartTime) / 1000),
1973
+ providers: prev.providers.map(p => p.name === currentModel.provider
1974
+ ? { ...p, requests: p.requests + 1, computeUnits: p.computeUnits + computeUnits }
1975
+ : p).concat(
1976
+ // Add provider if not exists
1977
+ prev.providers.some(p => p.name === currentModel.provider)
1978
+ ? []
1979
+ : [{
1980
+ name: currentModel.provider,
1981
+ requests: 1,
1982
+ computeUnits: computeUnits,
1983
+ models: [currentModel.model],
1984
+ }]),
1985
+ }));
1986
+ }, [currentModel.provider, currentModel.model, sessionStartTime]);
1987
+ // Reset usage stats
1988
+ const resetUsageStats = React.useCallback(() => {
1989
+ setUsageStats({
1990
+ requests: 0,
1991
+ totalTokens: 0,
1992
+ totalComputeUnits: 0,
1993
+ modelSwitches: 0,
1994
+ uptime: 0,
1995
+ providers: [],
1996
+ currentProvider: currentModel.provider,
1997
+ currentModel: currentModel.model,
1998
+ });
1999
+ }, [currentModel.provider, currentModel.model]);
2000
+ // Get current model capabilities
2001
+ const getCurrentCapabilities = React.useCallback(() => {
2002
+ const allModels = Object.values(availableModels).flat();
2003
+ const modelInfo = allModels.find(m => m.id === currentModel.model && m.provider === currentModel.provider);
2004
+ return {
2005
+ supportsImages: modelInfo?.supportsImages || false,
2006
+ computeWeight: modelInfo?.computeWeight || 1.0,
2007
+ };
2008
+ }, [currentModel, availableModels]);
2009
+ // Get current model info for streaming API
2010
+ const getCurrentModelInfo = React.useCallback(() => ({
2011
+ provider: currentModel.provider,
2012
+ model: currentModel.model,
2013
+ capabilities: getCurrentCapabilities(),
2014
+ }), [currentModel, getCurrentCapabilities]);
2015
+ // Flatten models for backward compatibility
2016
+ const models = Object.values(availableModels).flat();
2017
+ const modelsByProvider = availableModels;
2018
+ const currentCapabilities = getCurrentCapabilities();
2019
+ return {
2020
+ // Current state
2021
+ models,
2022
+ modelsByProvider,
2023
+ currentModel,
2024
+ currentCapabilities,
2025
+ usageStats,
2026
+ isLoading: false, // No loading since no API calls
2027
+ error: null,
2028
+ // Actions
2029
+ switchModel,
2030
+ trackUsage,
2031
+ resetUsageStats,
2032
+ getCurrentModelInfo, // New: for passing to streaming API
2033
+ // Backward compatibility
2034
+ modelState: {
2035
+ models,
2036
+ current: currentModel,
2037
+ capabilities: currentCapabilities,
2038
+ usage: usageStats,
2039
+ timestamp: Date.now(),
2040
+ },
2041
+ fetchModelState: () => Promise.resolve(), // No-op
2042
+ fetchModels: () => Promise.resolve(),
2043
+ fetchUsageStats: () => Promise.resolve(),
2044
+ fetchCurrentCapabilities: () => Promise.resolve(),
2045
+ integrationMode: 'local',
2046
+ };
2047
+ }
2048
+
2049
+ /**
2050
+ * ModelSwitcher component for switching between AI models
2051
+ * Now uses the simplified useModelSwitcher hook directly
2052
+ * Uses Tailwind classes for styling
2053
+ */
2054
+ function ModelSwitcher({ className = '', models, defaultModel, showUsageStats = false, // Default to false to match modal design
2055
+ theme, onModelSelectionChange, }) {
2056
+ const [isOpen, setIsOpen] = React.useState(false);
2057
+ const [dropdownPosition, setDropdownPosition] = React.useState('above');
2058
+ const [dropdownStyle, setDropdownStyle] = React.useState({});
2059
+ const [hoveredModel, setHoveredModel] = React.useState(null);
2060
+ const dropdownRef = React.useRef(null);
2061
+ const buttonRef = React.useRef(null);
2062
+ const currentModelRef = React.useRef(null);
2063
+ // Use the simplified model switcher hook
2064
+ const { modelsByProvider, currentModel, currentCapabilities, usageStats, isLoading, error, switchModel, integrationMode, getCurrentModelInfo, } = useModelSwitcher({
2065
+ customModels: models,
2066
+ initialProvider: defaultModel?.provider,
2067
+ initialModel: defaultModel?.model,
2068
+ });
2069
+ const lastModelRef = React.useRef(null);
2070
+ React.useEffect(() => {
2071
+ if (onModelSelectionChange && currentModel && currentCapabilities) {
2072
+ const newModel = {
2073
+ provider: currentModel.provider,
2074
+ model: currentModel.model,
2075
+ capabilities: currentCapabilities,
2076
+ };
2077
+ // Simple shallow compare (customize if needed)
2078
+ const last = lastModelRef.current;
2079
+ if (!last ||
2080
+ last.provider !== newModel.provider ||
2081
+ last.model !== newModel.model ||
2082
+ JSON.stringify(last.capabilities) !==
2083
+ JSON.stringify(newModel.capabilities)) {
2084
+ onModelSelectionChange(newModel);
2085
+ lastModelRef.current = newModel;
2086
+ }
2087
+ }
2088
+ }, [currentModel, currentCapabilities, onModelSelectionChange]);
2089
+ // Handle click outside to close dropdown
2090
+ React.useEffect(() => {
2091
+ const handleClickOutside = (event) => {
2092
+ if (dropdownRef.current &&
2093
+ !dropdownRef.current.contains(event.target)) {
2094
+ setIsOpen(false);
2095
+ }
2096
+ };
2097
+ const handleResize = () => {
2098
+ if (isOpen) {
2099
+ calculateDropdownPosition();
2100
+ }
2101
+ };
2102
+ if (isOpen) {
2103
+ document.addEventListener('mousedown', handleClickOutside);
2104
+ window.addEventListener('resize', handleResize);
2105
+ }
2106
+ return () => {
2107
+ document.removeEventListener('mousedown', handleClickOutside);
2108
+ window.removeEventListener('resize', handleResize);
2109
+ };
2110
+ }, [isOpen]);
2111
+ // Calculate optimal dropdown position using fixed positioning
2112
+ const calculateDropdownPosition = () => {
2113
+ if (!buttonRef.current)
2114
+ return;
2115
+ const buttonRect = buttonRef.current.getBoundingClientRect();
2116
+ const viewportWidth = window.innerWidth;
2117
+ const viewportHeight = window.innerHeight;
2118
+ // Dropdown width is min-w-80 (320px)
2119
+ const dropdownWidth = 320;
2120
+ const dropdownHeight = 300; // Approximate height
2121
+ const margin = 8; // Minimum margin from viewport edge
2122
+ // Calculate horizontal position
2123
+ let left = buttonRect.left;
2124
+ // Check if dropdown would go off right edge
2125
+ if (left + dropdownWidth > viewportWidth - margin) {
2126
+ // Try right-aligning to button
2127
+ const rightAlignedLeft = buttonRect.right - dropdownWidth;
2128
+ if (rightAlignedLeft >= margin) {
2129
+ left = rightAlignedLeft;
2130
+ }
2131
+ else {
2132
+ // Not enough space either way, clamp to viewport
2133
+ left = Math.max(margin, Math.min(left, viewportWidth - dropdownWidth - margin));
2134
+ }
2135
+ }
2136
+ // Calculate vertical position
2137
+ let top;
2138
+ let bottom;
2139
+ const spaceAbove = buttonRect.top;
2140
+ const spaceBelow = viewportHeight - buttonRect.bottom;
2141
+ if (spaceAbove >= dropdownHeight || spaceAbove > spaceBelow) {
2142
+ // Position above
2143
+ bottom = viewportHeight - buttonRect.top + 4; // 4px gap
2144
+ setDropdownPosition('above');
2145
+ }
2146
+ else {
2147
+ // Position below
2148
+ top = buttonRect.bottom + 4; // 4px gap
2149
+ setDropdownPosition('below');
2150
+ }
2151
+ setDropdownStyle({
2152
+ position: 'fixed',
2153
+ left: `${left}px`,
2154
+ top: top !== undefined ? `${top}px` : undefined,
2155
+ bottom: bottom !== undefined ? `${bottom}px` : undefined,
2156
+ right: undefined,
2157
+ });
2158
+ };
2159
+ // Handle dropdown toggle with position calculation
2160
+ const handleToggleDropdown = () => {
2161
+ if (!isOpen) {
2162
+ calculateDropdownPosition();
2163
+ }
2164
+ setIsOpen(!isOpen);
2165
+ // Scroll to current model when opening
2166
+ if (!isOpen) {
2167
+ setTimeout(() => {
2168
+ if (currentModelRef.current) {
2169
+ currentModelRef.current.scrollIntoView({
2170
+ behavior: 'smooth',
2171
+ block: 'nearest',
2172
+ });
2173
+ }
2174
+ }, 100);
2175
+ }
2176
+ };
2177
+ // Simplified model switching
2178
+ const handleModelSwitch = async (modelId, provider) => {
2179
+ try {
2180
+ setIsOpen(false);
2181
+ switchModel(modelId, provider);
2182
+ }
2183
+ catch (err) {
2184
+ console.error('Failed to switch model:', err);
2185
+ }
2186
+ };
2187
+ // Get current model name for display
2188
+ const getCurrentModelName = () => {
2189
+ if (!currentModel)
2190
+ return 'AI Model';
2191
+ // Find the actual model object to get the name
2192
+ const allModels = Object.values(modelsByProvider).flat();
2193
+ const modelObj = allModels.find((m) => m.id === currentModel.model && m.provider === currentModel.provider);
2194
+ return modelObj?.name || currentModel.model;
2195
+ };
2196
+ // Get model-specific usage for tooltip
2197
+ const getModelUsage = (modelId, provider) => {
2198
+ if (!usageStats)
2199
+ return null;
2200
+ const providerStats = usageStats.providers.find((p) => p.name === provider);
2201
+ if (!providerStats)
2202
+ return null;
2203
+ return {
2204
+ requests: providerStats.requests,
2205
+ computeUnits: providerStats.computeUnits,
2206
+ isActive: currentModel?.model === modelId && currentModel?.provider === provider,
2207
+ };
2208
+ };
2209
+ const getProviderIcon = (provider) => {
2210
+ const iconName = getProviderIconName(provider || '') || 'ai';
2211
+ return jsxRuntime.jsx(Icon, { name: iconName, size: "xs" });
2212
+ };
2213
+ const getProviderColorClass = (provider) => {
2214
+ if (!provider)
2215
+ return 'text-gray-500';
2216
+ switch (provider.toLowerCase()) {
2217
+ case 'openai':
2218
+ return 'text-green-500';
2219
+ case 'claude':
2220
+ return 'text-amber-500';
2221
+ case 'gemini':
2222
+ return 'text-blue-500';
2223
+ default:
2224
+ return 'text-gray-500';
2225
+ }
2226
+ };
2227
+ const formatUptime = (seconds) => {
2228
+ const hours = Math.floor(seconds / 3600);
2229
+ const minutes = Math.floor((seconds % 3600) / 60);
2230
+ if (hours > 0) {
2231
+ return `${hours}h ${minutes}m`;
2232
+ }
2233
+ return `${minutes}m`;
2234
+ };
2235
+ if (error) {
2236
+ return (jsxRuntime.jsx("span", { className: `text-xs text-red-500 cursor-help ${className}`, title: error, children: "\u26A0\uFE0F Model Error" }));
2237
+ }
2238
+ // Generate custom scrollbar styles based on theme
2239
+ const scrollbarStyles = `
2240
+ .custom-scrollbar {
2241
+ scrollbar-width: thin;
2242
+ scrollbar-color: ${theme?.colors.primary[500] || '#3b82f6'} ${theme?.colors.surface.secondary || '#f3f4f6'};
2243
+ }
2244
+ .custom-scrollbar::-webkit-scrollbar {
2245
+ width: 8px;
2246
+ height: 8px;
2247
+ }
2248
+ .custom-scrollbar::-webkit-scrollbar-track {
2249
+ background: ${theme?.colors.surface.secondary || '#f3f4f6'};
2250
+ border-radius: 4px;
2251
+ }
2252
+ .custom-scrollbar::-webkit-scrollbar-thumb {
2253
+ background: ${theme?.colors.primary[500] || '#3b82f6'};
2254
+ border-radius: 4px;
2255
+ border: 1px solid ${theme?.colors.surface.secondary || '#f3f4f6'};
2256
+ }
2257
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
2258
+ background: ${theme?.colors.primary[600] || '#2563eb'};
2259
+ }
2260
+ `;
2261
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: scrollbarStyles }), jsxRuntime.jsxs("div", { className: `relative inline-block ${className}`, ref: dropdownRef, children: [jsxRuntime.jsx("button", { ref: buttonRef, onClick: handleToggleDropdown, disabled: isLoading, className: `flex items-center gap-2 text-xs text-gray-500 bg-transparent border-none cursor-pointer p-1 rounded transition-all duration-200 font-inherit hover:text-blue-500 disabled:opacity-50 disabled:cursor-not-allowed`, title: `Switch AI Model (${integrationMode} mode)`, children: currentModel ? (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("span", { className: getProviderColorClass(currentModel.provider), children: getProviderIcon(currentModel.provider) }), jsxRuntime.jsx("span", { children: getCurrentModelName() }), jsxRuntime.jsx(Icon, { name: "plus", size: "xs", className: isLoading ? 'animate-spin' : '' })] })) : (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx(Icon, { name: "ai", size: "xs" }), jsxRuntime.jsx("span", { children: "AI Model" }), isLoading && (jsxRuntime.jsx(Icon, { name: "plus", size: "xs", className: "animate-spin" }))] })) }), isOpen && (jsxRuntime.jsxs("div", { className: "z-50 min-w-80 rounded-lg overflow-hidden", style: {
2262
+ ...dropdownStyle,
2263
+ backgroundColor: theme?.colors.surface.elevated || '#ffffff',
2264
+ borderColor: theme?.colors.border.primary || '#e5e7eb',
2265
+ borderWidth: '1px',
2266
+ borderStyle: 'solid',
2267
+ borderRadius: theme?.borderRadius.lg || '0.5rem',
2268
+ boxShadow: theme?.shadows.xl ||
2269
+ '0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1)',
2270
+ }, children: [showUsageStats && usageStats && (jsxRuntime.jsx("div", { className: "px-3 pt-2 pb-3 border-b", style: {
2271
+ borderColor: theme?.colors.border.primary || '#e5e7eb',
2272
+ }, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-4 text-xs", children: [jsxRuntime.jsxs("span", { className: "font-medium text-blue-600", children: [usageStats.requests, " requests"] }), jsxRuntime.jsxs("span", { className: "font-medium text-green-600", children: [usageStats.modelSwitches, " switches"] }), jsxRuntime.jsxs("span", { className: "font-medium text-purple-600", children: [Math.round(usageStats.totalComputeUnits), " compute units"] }), jsxRuntime.jsxs("span", { className: "font-medium text-orange-600", children: [formatUptime(usageStats.uptime), " uptime"] })] }) })), jsxRuntime.jsx("div", { className: "max-h-64 overflow-y-auto custom-scrollbar", children: jsxRuntime.jsx("div", { className: "py-2", children: Object.entries(modelsByProvider).map(([provider, models]) => (jsxRuntime.jsxs("div", { className: "py-1", children: [jsxRuntime.jsxs("div", { className: "py-3 px-3 flex items-center gap-2 border-b border-opacity-50", style: {
2273
+ borderColor: theme?.colors.border.primary || '#374151',
2274
+ }, children: [jsxRuntime.jsx("span", { className: getProviderColorClass(provider), children: getProviderIcon(provider) }), jsxRuntime.jsx("span", { className: "text-sm font-semibold tracking-wide", style: {
2275
+ color: theme?.colors.text.primary || '#f9fafb',
2276
+ }, children: provider.toUpperCase() })] }), models.map((model) => {
2277
+ const modelUsage = getModelUsage(model.id, provider);
2278
+ const isCurrentModel = currentModel?.model === model.id &&
2279
+ currentModel?.provider === provider;
2280
+ return (jsxRuntime.jsxs("div", { className: "relative", children: [jsxRuntime.jsx("button", { ref: isCurrentModel ? currentModelRef : null, onClick: () => handleModelSwitch(model.id, provider), disabled: isLoading || !model.available, onMouseEnter: (e) => {
2281
+ setHoveredModel(`${provider}-${model.id}`);
2282
+ if (model.available && !isCurrentModel) {
2283
+ e.currentTarget.style.backgroundColor =
2284
+ theme?.colors.surface.secondary || '#f3f4f6';
2285
+ }
2286
+ }, onMouseLeave: (e) => {
2287
+ setHoveredModel(null);
2288
+ if (model.available && !isCurrentModel) {
2289
+ e.currentTarget.style.backgroundColor =
2290
+ 'transparent';
2291
+ }
2292
+ }, className: "w-full text-left py-2 px-4 text-xs border-none bg-transparent cursor-pointer transition-all duration-200 relative", style: {
2293
+ backgroundColor: isCurrentModel
2294
+ ? theme?.colors.surface.secondary || '#f3f4f6'
2295
+ : 'transparent',
2296
+ color: isCurrentModel
2297
+ ? theme?.colors.text.primary || '#111827'
2298
+ : model.available
2299
+ ? theme?.colors.text.primary || '#374151'
2300
+ : theme?.colors.text.tertiary || '#9ca3af',
2301
+ opacity: model.available ? 1 : 0.5,
2302
+ cursor: model.available
2303
+ ? 'pointer'
2304
+ : 'not-allowed',
2305
+ border: isCurrentModel
2306
+ ? `1px solid ${theme?.colors.border.primary || '#d1d5db'}`
2307
+ : '1px solid transparent',
2308
+ borderRadius: theme?.borderRadius.md || '0.375rem',
2309
+ }, title: !model.available
2310
+ ? `Unavailable: ${model.error || 'No API key configured'}`
2311
+ : `Switch to ${model.name} (CW: ${model.computeWeight})`, children: jsxRuntime.jsxs("div", { className: "flex items-center justify-between", children: [jsxRuntime.jsxs("div", { className: "flex-1", children: [jsxRuntime.jsx("div", { className: `font-medium mb-0.5 ${model.available ? '' : 'line-through'}`, children: model.name }), jsxRuntime.jsxs("div", { className: "text-xs flex items-center gap-2", style: {
2312
+ color: theme?.colors.text.tertiary || '#9ca3af',
2313
+ }, children: [jsxRuntime.jsx("span", { children: model.id }), jsxRuntime.jsxs("div", { className: "flex items-center gap-1", children: [jsxRuntime.jsx("div", { className: "w-1.5 h-1.5 rounded-full", style: {
2314
+ backgroundColor: isCurrentModel
2315
+ ? theme?.colors.text.secondary ||
2316
+ '#6b7280'
2317
+ : theme?.colors.text.tertiary ||
2318
+ '#9ca3af',
2319
+ } }), jsxRuntime.jsx("span", { className: "text-xs font-medium", style: {
2320
+ color: isCurrentModel
2321
+ ? theme?.colors.text.secondary ||
2322
+ '#6b7280'
2323
+ : theme?.colors.text.tertiary ||
2324
+ '#9ca3af',
2325
+ }, children: model.computeWeight })] })] })] }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("div", { className: "flex items-center justify-center w-5 h-5", title: model.supportsImages
2326
+ ? 'Supports image input'
2327
+ : 'Text only', children: model.supportsImages ? (
2328
+ // Image support icon
2329
+ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", style: {
2330
+ color: isCurrentModel
2331
+ ? theme?.colors.text.secondary ||
2332
+ '#6b7280'
2333
+ : theme?.colors.text.tertiary ||
2334
+ '#9ca3af',
2335
+ }, children: [jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2", stroke: "currentColor", strokeWidth: "2", fill: "none" }), jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5", fill: "currentColor" }), jsxRuntime.jsx("polyline", { points: "21,15 16,10 5,21", stroke: "currentColor", strokeWidth: "2", fill: "none" })] })) : (
2336
+ // No image support icon (crossed out image)
2337
+ jsxRuntime.jsxs("svg", { width: "14", height: "14", viewBox: "0 0 24 24", fill: "none", style: {
2338
+ color: theme?.colors.text.tertiary ||
2339
+ '#9ca3af',
2340
+ opacity: 0.5,
2341
+ }, children: [jsxRuntime.jsx("rect", { x: "3", y: "3", width: "18", height: "18", rx: "2", ry: "2", stroke: "currentColor", strokeWidth: "2", fill: "none" }), jsxRuntime.jsx("circle", { cx: "8.5", cy: "8.5", r: "1.5", fill: "currentColor" }), jsxRuntime.jsx("polyline", { points: "21,15 16,10 5,21", stroke: "currentColor", strokeWidth: "2", fill: "none" }), jsxRuntime.jsx("line", { x1: "3", y1: "3", x2: "21", y2: "21", stroke: "currentColor", strokeWidth: "2" })] })) }), modelUsage && modelUsage.requests > 0 && (jsxRuntime.jsx("span", { className: "text-xs text-blue-600 bg-blue-50 px-1 py-0.5 rounded", children: modelUsage.requests })), !model.available && (jsxRuntime.jsx("span", { className: "text-xs px-2 py-0.5 rounded", style: {
2342
+ backgroundColor: theme?.colors.surface.secondary ||
2343
+ '#f3f4f6',
2344
+ color: theme?.colors.text.tertiary ||
2345
+ '#9ca3af',
2346
+ }, title: "API key required", children: "Unavailable" }))] })] }) }), hoveredModel === `${provider}-${model.id}` &&
2347
+ modelUsage && (jsxRuntime.jsxs("div", { className: "absolute right-full top-0 mr-2 z-50 bg-gray-900 text-white text-xs rounded p-2 whitespace-nowrap shadow-lg", children: [jsxRuntime.jsx("div", { className: "font-medium mb-0.5", children: model.name }), jsxRuntime.jsxs("div", { className: "text-gray-300", children: [modelUsage.requests, " requests \u2022", ' ', Math.round(modelUsage.computeUnits), " CU", jsxRuntime.jsx("br", {}), modelUsage.isActive ? 'Active' : 'Available', model.supportsImages
2348
+ ? ' • Images supported'
2349
+ : ' • No image support'] }), jsxRuntime.jsx("div", { className: "absolute top-2 right-[-4px] w-2 h-2 bg-gray-900 transform rotate-45" })] }))] }, model.id));
2350
+ })] }, provider))) }) })] }))] })] }));
2351
+ }
2352
+
2353
+ /**
2354
+ * formatTimestamp - formats the timestamp to a human readable format
2355
+ * @param timestamp - The timestamp to format
2356
+ * @returns The formatted timestamp
2357
+ */
2358
+ const formatTimestamp = (timestamp) => {
2359
+ if (!timestamp)
2360
+ return '';
2361
+ const date = new Date(timestamp);
2362
+ const now = new Date();
2363
+ const isToday = date.toDateString() === now.toDateString();
2364
+ if (isToday) {
2365
+ return date.toLocaleTimeString([], {
2366
+ hour: '2-digit',
2367
+ minute: '2-digit',
2368
+ });
2369
+ }
2370
+ else {
2371
+ return date.toLocaleDateString([], {
2372
+ month: 'short',
2373
+ day: 'numeric',
2374
+ hour: '2-digit',
2375
+ minute: '2-digit',
2376
+ });
2377
+ }
2378
+ };
2379
+ /**
2380
+ * Helper to extract text content from message content blocks
2381
+ */
2382
+ const getTextFromMessage$1 = (msg) => {
2383
+ return msg.content
2384
+ .filter((block) => block.type === 'text')
2385
+ .map((block) => block.content)
2386
+ .join('\n');
2387
+ };
2388
+ /**
2389
+ * Conversation component - displays a single chat conversation
2390
+ * @param chat - The chat messages to display
2391
+ * @param onSuggestionClick - Function to handle suggestion clicks
2392
+ * @param theme - Chat theme
2393
+ * @param showProfileBubbles - Whether to show profile bubbles
2394
+ * @param className - Additional CSS classes
2395
+ * @param autoScroll - Whether to auto-scroll to bottom on new messages
2396
+ * @param maxHeight - Maximum height of the conversation container
2397
+ */
2398
+ function Conversation({ chat: externalChat, onSuggestionClick, theme, showProfileBubbles = false, className = '', autoScroll = true, maxHeight = '100%',
2399
+ // AI functionality props
2400
+ userId = 'demo-user', serverConfig, formData, setFormState, onNavigate, chatLevel = 'full',
2401
+ // External chat management
2402
+ chats: externalChats, setChats: externalSetChats, currentChatId: externalCurrentChatId, setCurrentChatId: externalSetCurrentChatId,
2403
+ // Input-related props
2404
+ query: externalQuery = '', setQuery: externalSetQuery, onSend: externalOnSend, isLoading: externalIsLoading = false, attachedFiles: externalAttachedFiles = [], setAttachedFiles: externalSetAttachedFiles,
2405
+ // Model configuration
2406
+ models, defaultModel, showUsageStats, maxFileSize, features = {},
2407
+ // Model selection props
2408
+ currentModelSelection: externalCurrentModelSelection, onModelSelectionChange: externalOnModelSelectionChange,
2409
+ // Show input field (always shown by default)
2410
+ showInput = true,
2411
+ // Initial query handling
2412
+ initialQuery, setInitialQuery,
2413
+ // Clear chat functionality
2414
+ showClearChat = false, onClearChat,
2415
+ // Additional context for AI requests
2416
+ additionalContext, }) {
2417
+ const chatRef = React.useRef(null);
2418
+ // Internal state for AI functionality
2419
+ const [query, setQuery] = React.useState(externalQuery);
2420
+ const [attachedFiles, setAttachedFiles] = React.useState(externalAttachedFiles);
2421
+ const [currentModelSelection, setCurrentModelSelection] = React.useState(externalCurrentModelSelection);
2422
+ // Use the simplified model switcher hook
2423
+ const { trackUsage } = useModelSwitcher({
2424
+ customModels: models,
2425
+ initialProvider: defaultModel?.provider,
2426
+ initialModel: defaultModel?.model,
2427
+ });
2428
+ // Use streaming AI hook for AI functionality
2429
+ const streamingAI = useStreamingAI({
2430
+ userId,
2431
+ serverConfig,
2432
+ formData,
2433
+ chats: externalChats,
2434
+ setChats: externalSetChats,
2435
+ currentChatId: externalCurrentChatId,
2436
+ setCurrentChatId: externalSetCurrentChatId,
2437
+ modelSelection: currentModelSelection,
2438
+ onUsageTracked: trackUsage,
2439
+ chatLevel,
2440
+ additionalContext,
2441
+ });
2442
+ // Handle model selection changes
2443
+ const handleModelSelectionChange = React.useCallback((modelInfo) => {
2444
+ setCurrentModelSelection(modelInfo);
2445
+ externalOnModelSelectionChange?.(modelInfo);
2446
+ }, [externalOnModelSelectionChange]);
2447
+ // Handle sending messages
2448
+ const handleSend = React.useCallback((additionalContext, queryToSend) => {
2449
+ const finalQuery = queryToSend || query;
2450
+ streamingAI.sendQuery(finalQuery, attachedFiles.length > 0 ? attachedFiles : undefined, additionalContext);
2451
+ setQuery('');
2452
+ setAttachedFiles([]); // Clear files after sending
2453
+ externalSetQuery?.('');
2454
+ externalSetAttachedFiles?.([]);
2455
+ }, [query, attachedFiles, streamingAI, externalSetQuery, externalSetAttachedFiles]);
2456
+ // Handle suggestion clicks
2457
+ const handleSuggestionClick = React.useCallback((s) => {
2458
+ if (setFormState && s.formState)
2459
+ setFormState(s.formState, s.actionType);
2460
+ if (s.path && onNavigate)
2461
+ onNavigate(s.path);
2462
+ onSuggestionClick(s);
2463
+ }, [setFormState, onNavigate, onSuggestionClick]);
2464
+ // Use external or internal state
2465
+ const chat = externalChat || streamingAI.chat;
2466
+ const isLoading = externalIsLoading || streamingAI.isLoading;
2467
+ const finalQuery = externalQuery || query;
2468
+ const finalSetQuery = externalSetQuery || setQuery;
2469
+ const finalOnSend = externalOnSend || handleSend;
2470
+ const finalAttachedFiles = externalAttachedFiles.length > 0 ? externalAttachedFiles : attachedFiles;
2471
+ const finalSetAttachedFiles = externalSetAttachedFiles || setAttachedFiles;
2472
+ // Auto-scroll to bottom when new messages arrive
2473
+ React.useEffect(() => {
2474
+ if (autoScroll && chatRef.current) {
2475
+ chatRef.current.scrollTo({
2476
+ top: chatRef.current.scrollHeight,
2477
+ behavior: 'smooth',
2478
+ });
2479
+ }
2480
+ }, [chat, autoScroll]);
2481
+ // Handle initial query auto-send
2482
+ const initialQuerySentRef = React.useRef(false);
2483
+ React.useEffect(() => {
2484
+ if (initialQuery &&
2485
+ !streamingAI.isLoading &&
2486
+ !initialQuerySentRef.current) {
2487
+ // Set the query first
2488
+ setQuery(initialQuery.query || '');
2489
+ // Use a delay to ensure state is updated and component is ready
2490
+ const timer = setTimeout(() => {
2491
+ // Check if we have a meaningful query or context to send
2492
+ const hasQuery = initialQuery.query && initialQuery.query.trim().length > 0;
2493
+ const hasContext = initialQuery.context && initialQuery.context.trim().length > 0;
2494
+ if (hasQuery || hasContext) {
2495
+ handleSend(initialQuery.context, initialQuery.query);
2496
+ initialQuerySentRef.current = true; // Mark as sent
2497
+ }
2498
+ // Always clear the initial query after processing
2499
+ setInitialQuery?.({ query: '', context: '' });
2500
+ }, 200);
2501
+ return () => clearTimeout(timer);
2502
+ }
2503
+ }, [initialQuery, streamingAI.isLoading, handleSend, setInitialQuery]);
2504
+ // Reset the sent flag when initialQuery changes
2505
+ React.useEffect(() => {
2506
+ if (initialQuery) {
2507
+ initialQuerySentRef.current = false;
2508
+ }
2509
+ }, [initialQuery]);
2510
+ // Scrollbar styles - dynamically themed
2511
+ const scrollbarStyles = `
2512
+ .custom-scrollbar::-webkit-scrollbar {
2513
+ width: 6px !important;
2514
+ }
2515
+ .custom-scrollbar::-webkit-scrollbar-track {
2516
+ background: ${theme.colors.background.secondary} !important;
2517
+ border-radius: 3px !important;
2518
+ }
2519
+ .custom-scrollbar::-webkit-scrollbar-thumb {
2520
+ background: ${theme.colors.border.secondary} !important;
2521
+ border-radius: 3px !important;
2522
+ }
2523
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
2524
+ background: ${theme.colors.text.tertiary} !important;
2525
+ }
2526
+ .custom-scrollbar::-webkit-scrollbar-corner {
2527
+ background: ${theme.colors.background.secondary} !important;
2528
+ }
2529
+ /* Firefox scrollbar */
2530
+ .custom-scrollbar {
2531
+ scrollbar-width: thin !important;
2532
+ scrollbar-color: ${theme.colors.border.secondary} ${theme.colors.background.secondary} !important;
2533
+ }
2534
+ `;
2535
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: scrollbarStyles }), jsxRuntime.jsxs("div", { className: "flex flex-col h-full", style: {
2536
+ maxHeight: maxHeight,
2537
+ }, children: [jsxRuntime.jsx("div", { ref: chatRef, className: `overflow-y-auto space-y-4 min-h-0 custom-scrollbar px-3 py-4 flex-1 ${className}`, style: {
2538
+ backgroundColor: theme.colors.background.primary,
2539
+ }, children: jsxRuntime.jsx(ChatBubbles, { chat: chat, onSuggestionClick: handleSuggestionClick, theme: theme, showProfileBubbles: showProfileBubbles }) }), showClearChat && onClearChat && chat.length > 0 && (jsxRuntime.jsx("div", { className: "px-4 pb-2", style: { flexShrink: 0 }, children: jsxRuntime.jsxs("button", { onClick: onClearChat, className: "w-full text-sm py-2 px-3 rounded-lg transition-colors border flex items-center justify-center gap-2", style: {
2540
+ color: theme.colors.text.secondary,
2541
+ backgroundColor: theme.colors.surface.primary,
2542
+ borderColor: theme.colors.border.primary,
2543
+ }, onMouseEnter: (e) => {
2544
+ e.currentTarget.style.color = theme.colors.state.error.text;
2545
+ e.currentTarget.style.backgroundColor = theme.colors.state.error.background;
2546
+ e.currentTarget.style.borderColor = theme.colors.state.error.border;
2547
+ }, onMouseLeave: (e) => {
2548
+ e.currentTarget.style.color = theme.colors.text.secondary;
2549
+ e.currentTarget.style.backgroundColor = theme.colors.surface.primary;
2550
+ e.currentTarget.style.borderColor = theme.colors.border.primary;
2551
+ }, title: "Clear chat history", "aria-label": "Clear chat", children: [jsxRuntime.jsx(Icon, { name: "trash", size: "sm" }), "Clear Chat History"] }) })), showInput && (jsxRuntime.jsx("div", { style: { flexShrink: 0 }, children: jsxRuntime.jsx(ChatInput, { query: finalQuery, setQuery: finalSetQuery, onSend: finalOnSend, isLoading: isLoading, inputRef: React.useRef(null), attachedFiles: finalAttachedFiles, setAttachedFiles: finalSetAttachedFiles, models: models, defaultModel: defaultModel, showUsageStats: showUsageStats, maxFileSize: maxFileSize, features: features, currentModelSelection: currentModelSelection, onModelSelectionChange: handleModelSelectionChange, theme: theme, isOpen: true }) }))] })] }));
2552
+ }
2553
+ /**
2554
+ * ErrorDisplay component - used to display error messages
2555
+ * @param msg - The chat message
2556
+ */
2557
+ function ErrorDisplay({ msg }) {
2558
+ return (jsxRuntime.jsx("div", { className: "flex flex-col gap-3 max-w-full overflow-hidden", children: jsxRuntime.jsxs("div", { className: "flex-1 min-w-0 max-w-md", children: [jsxRuntime.jsx("div", { className: `font-medium break-words ${msg.errorType === 'rate_limit'
2559
+ ? 'text-orange-800 text-base'
2560
+ : msg.errorType === 'timeout_error' ||
2561
+ msg.errorType === 'token_limit'
2562
+ ? 'text-yellow-800 text-base'
2563
+ : 'text-red-800 text-sm'}`, children: getTextFromMessage$1(msg) }), (msg.errorType === 'timeout_error' ||
2564
+ msg.errorType === 'token_limit') && (jsxRuntime.jsxs("div", { className: "mt-3 space-y-2", children: [jsxRuntime.jsx("div", { className: "text-sm text-yellow-700 bg-yellow-50 rounded-lg px-3 py-2 border border-yellow-200 max-w-full", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 flex-shrink-0 mt-0.5", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [jsxRuntime.jsx("div", { className: "font-medium mb-1", children: "What to try:" }), jsxRuntime.jsxs("ul", { className: "text-xs space-y-1 text-yellow-600", children: [jsxRuntime.jsx("li", { children: "\u2022 Try a shorter or simpler request" }), jsxRuntime.jsx("li", { children: "\u2022 Check your internet connection" }), jsxRuntime.jsx("li", { children: "\u2022 Wait a moment and try again" })] })] })] }) }), msg.retryAfter && (jsxRuntime.jsxs("div", { className: "text-xs text-yellow-600 bg-yellow-50 rounded px-2 py-1 border border-yellow-200 max-w-full break-words", children: ["\uD83D\uDCA1 Recommended wait time: ", msg.retryAfter, " seconds"] }))] })), msg.errorType === 'rate_limit' && (jsxRuntime.jsxs("div", { className: "mt-3 space-y-2", children: [msg.retryAfter && (jsxRuntime.jsx("div", { className: "text-sm text-orange-700 bg-orange-50 rounded-lg px-3 py-2 border border-orange-200 max-w-full", children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2", children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 flex-shrink-0", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M10 18a8 8 0 100-16 8 8 0 000 16zm1-12a1 1 0 10-2 0v4a1 1 0 00.293.707l2.828 2.829a1 1 0 101.415-1.414L11 9.586V6z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("span", { className: "font-medium min-w-0 break-words", children: ["Please wait approximately ", msg.retryAfter, " seconds before trying again"] })] }) })), getTextFromMessage$1(msg).includes('organization') && (jsxRuntime.jsx("div", { className: "text-sm text-blue-700 bg-blue-50 rounded-lg px-3 py-2 border border-blue-200 max-w-full", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 flex-shrink-0 mt-0.5", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [jsxRuntime.jsx("div", { className: "font-medium mb-1", children: "What you can do:" }), jsxRuntime.jsxs("ul", { className: "text-xs space-y-1 text-blue-600", children: [jsxRuntime.jsx("li", { children: "\u2022 Wait for the rate limit to reset and try again" }), jsxRuntime.jsx("li", { children: "\u2022 Contact your organization administrator" }), jsxRuntime.jsx("li", { children: "\u2022 Consider upgrading your organization's rate limits" })] })] })] }) })), !getTextFromMessage$1(msg).includes('organization') && (jsxRuntime.jsx("div", { className: "text-xs text-orange-600 bg-orange-50 rounded px-2 py-1 border border-orange-200 max-w-full break-words", children: "\uD83D\uDCA1 Tip: Try shortening your request or wait a moment before trying again" }))] })), msg.errorType === 'auth_error' && (jsxRuntime.jsx("div", { className: "mt-2 text-sm text-red-700 bg-red-50 rounded-lg px-3 py-2 border border-red-200 max-w-full", children: jsxRuntime.jsxs("div", { className: "flex items-start gap-2", children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 flex-shrink-0 mt-0.5", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("div", { className: "min-w-0 flex-1", children: [jsxRuntime.jsx("div", { className: "font-medium mb-1", children: "Authentication Issue:" }), jsxRuntime.jsx("div", { className: "text-xs break-words", children: "Please contact your system administrator to verify your API configuration." })] })] }) }))] }) }));
2565
+ }
2566
+ /**
2567
+ * ChatBubbles component
2568
+ * @param chat - The chat messages
2569
+ * @param onSuggestionClick - Function to handle the suggestion click event
2570
+ * @param theme - Chat theme
2571
+ * @param showProfileBubbles - Whether to show profile bubbles (default: false)
2572
+ */
2573
+ function ChatBubbles({ chat, onSuggestionClick, theme, showProfileBubbles = false, }) {
2574
+ const getProfileIcon = (sender, msg) => {
2575
+ switch (sender) {
2576
+ case 'user':
2577
+ return (jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center", children: jsxRuntime.jsx(Icon, { name: "user", size: "sm", className: "text-blue-600" }) }));
2578
+ case 'system':
2579
+ // For error messages, use appropriate error icon instead of generic system icon
2580
+ if (msg?.isError) {
2581
+ const errorIconName = msg.errorType === 'rate_limit' || msg.errorType === 'token_limit'
2582
+ ? 'warning'
2583
+ : msg.errorType === 'timeout_error'
2584
+ ? 'timeout'
2585
+ : msg.errorType === 'auth_error'
2586
+ ? 'auth-error'
2587
+ : 'error';
2588
+ const bgColor = msg.errorType === 'rate_limit' || msg.errorType === 'token_limit'
2589
+ ? 'bg-orange-100'
2590
+ : msg.errorType === 'timeout_error'
2591
+ ? 'bg-yellow-100'
2592
+ : 'bg-red-100';
2593
+ return (jsxRuntime.jsx("div", { className: `flex-shrink-0 w-8 h-8 ${bgColor} rounded-full flex items-center justify-center`, children: jsxRuntime.jsx(Icon, { name: errorIconName, size: 10 }) }));
2594
+ }
2595
+ // Regular system messages use the system icon
2596
+ return (jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 bg-gray-100 rounded-full flex items-center justify-center", children: jsxRuntime.jsx(Icon, { name: "system", size: "sm", className: "text-gray-900" }) }));
2597
+ default: // AI
2598
+ return (jsxRuntime.jsx("div", { className: "flex-shrink-0 w-8 h-8 bg-blue-100 rounded-full flex items-center justify-center", children: jsxRuntime.jsx(Icon, { name: "ai", size: "sm", className: "text-gray-900" }) }));
2599
+ }
2600
+ };
2601
+ return (jsxRuntime.jsx(jsxRuntime.Fragment, { children: chat.map((msg, idx) => (jsxRuntime.jsx("div", { className: msg.sender === 'user'
2602
+ ? 'flex justify-end'
2603
+ : msg.sender === 'system'
2604
+ ? 'flex justify-start'
2605
+ : 'flex justify-start', children: jsxRuntime.jsxs("div", { className: `flex gap-3 ${msg.sender === 'user' ? 'flex-row-reverse' : 'flex-row'}`, children: [showProfileBubbles && getProfileIcon(msg.sender, msg), jsxRuntime.jsxs("div", { className: `${msg.sender === 'user'
2606
+ ? 'flex flex-col items-end'
2607
+ : 'flex-1 min-w-0'}`, children: [jsxRuntime.jsxs("div", { className: "rounded-lg px-4 py-2 whitespace-pre-wrap break-words", style: {
2608
+ backgroundColor: msg.sender === 'user'
2609
+ ? theme.colors.primary[500]
2610
+ : msg.sender === 'system'
2611
+ ? msg.isError
2612
+ ? theme.colors.state.error.background
2613
+ : theme.colors.primary[900]
2614
+ .replace('rgb(', 'rgba(')
2615
+ .replace(')', ', 0.2)') // 20% opacity
2616
+ : theme.colors.surface.secondary,
2617
+ color: msg.sender === 'user'
2618
+ ? theme.colors.text.inverse
2619
+ : msg.sender === 'system'
2620
+ ? msg.isError
2621
+ ? theme.colors.state.error.text
2622
+ : theme.colors.primary[100]
2623
+ : theme.colors.text.primary,
2624
+ borderColor: msg.sender === 'system' && msg.isError
2625
+ ? theme.colors.state.error.border
2626
+ : msg.sender === 'system'
2627
+ ? theme.colors.primary[800]
2628
+ : 'transparent',
2629
+ borderWidth: msg.sender === 'system' ? '1px' : '0',
2630
+ }, children: [msg.sender === 'system' && msg.isError && (jsxRuntime.jsx(ErrorDisplay, { msg: msg })), msg.sender === 'system' && !msg.isError && (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-blue-600", children: [jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M18 10a8 8 0 11-16 0 8 8 0 0116 0zm-7-4a1 1 0 11-2 0 1 1 0 012 0zM9 9a1 1 0 000 2v3a1 1 0 001 1h1a1 1 0 100-2v-3a1 1 0 00-1-1H9z", clipRule: "evenodd" }) }), getTextFromMessage$1(msg)] })), msg.sender !== 'system' && (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [msg.isLoading && msg.aiStatus !== 'none' && (jsxRuntime.jsxs("div", { className: `flex items-center gap-2 mb-3 ${msg.aiStatus === 'thinking'
2631
+ ? 'text-gray-400 text-sm'
2632
+ : 'text-gray-400 text-sm'} ${msg.sender === 'user' ? '' : 'justify-end'}`, children: [jsxRuntime.jsxs("span", { className: "font-medium text-sm", children: [msg.aiStatus === 'thinking' &&
2633
+ 'Analyzing your request...', msg.aiStatus === 'typing' &&
2634
+ 'Generating response...', msg.aiStatus === 'suggesting' &&
2635
+ 'Finding relevant actions...'] }), jsxRuntime.jsx("div", { className: "animate-spin rounded-full h-4 w-4 border-b-2 border-current" })] })), msg.content.map((contentBlock, blockIdx) => (jsxRuntime.jsxs("div", { children: [contentBlock.type === 'text' &&
2636
+ contentBlock.content && (jsxRuntime.jsx("div", { className: `leading-relaxed ${blockIdx > 0 ? 'mt-3' : ''}`, style: {
2637
+ color: msg.sender === 'user'
2638
+ ? theme.colors.text.inverse // White text on colored background
2639
+ : theme.colors.text.primary, // Primary text color for AI
2640
+ }, children: contentBlock.content })), contentBlock.type === 'loading-suggestions' && (jsxRuntime.jsx("div", { className: `space-y-3 ${blockIdx > 0 ? 'mt-3' : ''}`, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm border-t pt-3", style: {
2641
+ color: theme.colors.text.tertiary,
2642
+ borderColor: theme.colors.border.primary,
2643
+ }, children: [jsxRuntime.jsx("svg", { className: "w-4 h-4 animate-spin", fill: "none", stroke: "currentColor", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { strokeLinecap: "round", strokeLinejoin: "round", strokeWidth: 2, d: "M4 4v5h.582m15.356 2A8.001 8.001 0 004.582 9m0 0H9m11 11v-5h-.581m0 0a8.003 8.003 0 01-15.357-2m15.357 2H15" }) }), jsxRuntime.jsx("span", { className: "font-medium", children: contentBlock.content })] }) })), contentBlock.type === 'suggestions' &&
2644
+ contentBlock.content.length > 0 && (jsxRuntime.jsxs("div", { className: `space-y-3 ${blockIdx > 0 ? 'mt-3' : ''}`, children: [jsxRuntime.jsxs("div", { className: "flex items-center gap-2 text-sm border-t pt-3", style: {
2645
+ color: theme.colors.text.tertiary,
2646
+ borderColor: theme.colors.border.primary,
2647
+ }, children: [jsxRuntime.jsx("svg", { className: "w-4 h-4", fill: "currentColor", viewBox: "0 0 20 20", children: jsxRuntime.jsx("path", { fillRule: "evenodd", d: "M11.49 3.17c-.38-1.56-2.6-1.56-2.98 0a1.532 1.532 0 01-2.286.948c-1.372-.836-2.942.734-2.106 2.106.54.886.061 2.042-.947 2.287-1.561.379-1.561 2.6 0 2.978a1.532 1.532 0 01.947 2.287c-.836 1.372.734 2.942 2.106 2.106a1.532 1.532 0 012.287.947c.379 1.561 2.6 1.561 2.978 0a1.533 1.533 0 012.287-.947c1.372.836 2.942-.734 2.106-2.106a1.533 1.533 0 01.947-2.287c1.561-.379 1.561-2.6 0-2.978a1.532 1.532 0 01-.947-2.287c.836-1.372-.734-2.942-2.106-2.106a1.532 1.532 0 01-2.287-.947zM10 13a3 3 0 100-6 3 3 0 000 6z", clipRule: "evenodd" }) }), jsxRuntime.jsxs("span", { className: "font-medium", children: ["I found ", contentBlock.content.length, " relevant action", contentBlock.content.length !== 1 ? 's' : '', ":"] })] }), jsxRuntime.jsx("div", { className: "space-y-2", children: contentBlock.content.map((suggestion, i) => (jsxRuntime.jsx(SuggestionCard, { suggestion: suggestion, onClick: onSuggestionClick, theme: theme }, i))) }), jsxRuntime.jsx("div", { className: "text-xs italic pt-2 border-t", style: {
2648
+ color: theme.colors.text.tertiary,
2649
+ borderColor: theme.colors.border.primary + '80', // 50% opacity
2650
+ }, children: "\uD83D\uDCA1 Click any suggestion to execute the action" })] }))] }, blockIdx))), msg.sender === 'user' && msg.files && (jsxRuntime.jsx(FileAttachmentDisplay, { files: msg.files }))] }))] }), jsxRuntime.jsx("div", { className: `text-xs mt-1 ${msg.sender === 'user' ? 'text-right' : 'text-left'}`, style: { color: theme.colors.text.tertiary }, children: formatTimestamp(msg.timestamp) })] })] }) }, idx))) }));
2651
+ }
2652
+ /**
2653
+ * FileAttachmentDisplay component - displays attached files in chat messages
2654
+ * @param files - Array of attached files
2655
+ */
2656
+ function FileAttachmentDisplay({ files }) {
2657
+ const [previewFile, setPreviewFile] = React.useState(null);
2658
+ if (!files || files.length === 0)
2659
+ return null;
2660
+ const handleFileClick = (file) => {
2661
+ if (file.type.startsWith('image/')) {
2662
+ setPreviewFile(file);
2663
+ }
2664
+ else if (file.type === 'application/pdf') {
2665
+ // Open PDF in new tab
2666
+ const url = URL.createObjectURL(file);
2667
+ window.open(url, '_blank');
2668
+ }
2669
+ else {
2670
+ // Download other files
2671
+ downloadFile(file);
2672
+ }
2673
+ };
2674
+ const downloadFile = (file) => {
2675
+ const url = URL.createObjectURL(file);
2676
+ const a = document.createElement('a');
2677
+ a.href = url;
2678
+ a.download = file.name;
2679
+ document.body.appendChild(a);
2680
+ a.click();
2681
+ document.body.removeChild(a);
2682
+ URL.revokeObjectURL(url);
2683
+ };
2684
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("div", { className: "mt-2 space-y-2", children: files.map((file, index) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 p-2 bg-white/10 rounded-lg border border-white/20 cursor-pointer hover:bg-white/20 transition-colors group", onClick: () => handleFileClick(file), title: file.type.startsWith('image/')
2685
+ ? 'Click to preview image'
2686
+ : file.type === 'application/pdf'
2687
+ ? 'Click to open PDF'
2688
+ : 'Click to download file', children: [jsxRuntime.jsxs("div", { className: "flex-shrink-0 relative", children: [file.type.startsWith('image/') ? (jsxRuntime.jsx("div", { className: "w-10 h-10 rounded overflow-hidden bg-white/20", children: jsxRuntime.jsx("img", { src: URL.createObjectURL(file), alt: `Attached ${index + 1}`, className: "w-full h-full object-cover" }) })) : (jsxRuntime.jsx("div", { className: "w-10 h-10 rounded bg-white/20 flex items-center justify-center", children: FileHandler.getFileIcon(file) })), jsxRuntime.jsx("div", { className: "absolute inset-0 bg-white/20 rounded opacity-0 group-hover:opacity-100 transition-opacity flex items-center justify-center", children: file.type.startsWith('image/') ? (jsxRuntime.jsx(Icon, { name: "eye", size: "sm", className: "text-white" })) : (jsxRuntime.jsxs("svg", { width: "16", height: "16", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", className: "text-white", children: [jsxRuntime.jsx("path", { d: "M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4" }), jsxRuntime.jsx("polyline", { points: "7,10 12,15 17,10" }), jsxRuntime.jsx("line", { x1: "12", y1: "15", x2: "12", y2: "3" })] })) })] }), jsxRuntime.jsxs("div", { className: "min-w-0 max-w-xs", children: [jsxRuntime.jsx("div", { className: "font-medium text-sm text-white truncate", children: file.name }), jsxRuntime.jsxs("div", { className: "text-xs text-white/70 truncate", children: [FileHandler.getFileTypeDescription(file), " \u2022", ' ', FileHandler.formatFileSize(file.size)] })] }), jsxRuntime.jsx("div", { className: "flex-shrink-0 opacity-0 group-hover:opacity-100 transition-opacity", children: jsxRuntime.jsx("div", { className: "text-xs text-white/70", children: file.type.startsWith('image/')
2689
+ ? 'Preview'
2690
+ : file.type === 'application/pdf'
2691
+ ? 'Open'
2692
+ : 'Download' }) })] }, index))) }), previewFile && (jsxRuntime.jsx("div", { className: "fixed inset-0 z-50 bg-black/80 flex items-center justify-center p-4", onClick: () => setPreviewFile(null), children: jsxRuntime.jsxs("div", { className: "relative max-w-full max-h-full", children: [jsxRuntime.jsx("img", { src: URL.createObjectURL(previewFile), alt: previewFile.name, className: "max-w-full max-h-full object-contain rounded-lg", onClick: (e) => e.stopPropagation() }), jsxRuntime.jsx("button", { className: "absolute top-4 right-4 w-8 h-8 bg-black/50 hover:bg-black/70 text-white rounded-full flex items-center justify-center transition-colors text-xl font-bold", onClick: () => setPreviewFile(null), children: "\u00D7" }), jsxRuntime.jsxs("div", { className: "absolute bottom-4 left-1/2 transform -translate-x-1/2 bg-black/70 text-white p-3 rounded-lg max-w-sm", children: [jsxRuntime.jsx("div", { className: "font-medium text-center", children: previewFile.name }), jsxRuntime.jsxs("div", { className: "text-sm text-gray-300 text-center", children: [FileHandler.getFileTypeDescription(previewFile), " \u2022", ' ', FileHandler.formatFileSize(previewFile.size)] })] })] }) }))] }));
2693
+ }
2694
+ function ChatInput({ query, setQuery, onSend, isLoading, inputRef, attachedFiles = [], setAttachedFiles,
2695
+ // Model configuration
2696
+ models, defaultModel, showUsageStats, maxFileSize, features = {},
2697
+ // Model selection props
2698
+ currentModelSelection, onModelSelectionChange,
2699
+ // Theme props
2700
+ theme, isOpen, }) {
2701
+ const [focused, setFocused] = React.useState(false);
2702
+ const [isDragOver, setIsDragOver] = React.useState(false);
2703
+ const [pasteIndicator, setPasteIndicator] = React.useState(false);
2704
+ const [incompatibleFiles, setIncompatibleFiles] = React.useState([]);
2705
+ const [imageTipDismissed, setImageTipDismissed] = React.useState(false);
2706
+ const fileInputRef = React.useRef(null);
2707
+ const imageInputRef = React.useRef(null);
2708
+ // Compute imageSupported from capabilities
2709
+ const imageSupported = React.useMemo(() => {
2710
+ const supported = currentModelSelection?.capabilities?.supportsImages ?? false;
2711
+ return supported;
2712
+ }, [currentModelSelection]);
2713
+ // In ChatInput, use the flags
2714
+ // Only allow image uploads if features.enableImageUploads !== false
2715
+ const imageUploadsEnabled = features.enableImageUploads !== false;
2716
+ // Only allow file uploads if features.fileUploads !== false
2717
+ const fileUploadsEnabled = features.fileUploads !== false;
2718
+ // Enforce max file size
2719
+ const maxFileSizeBytes = maxFileSize
2720
+ ? parseFileSize(maxFileSize)
2721
+ : 50 * 1024 * 1024;
2722
+ // Helper to parse file size strings like '50mb'
2723
+ function parseFileSize(size) {
2724
+ if (!size)
2725
+ return 50 * 1024 * 1024;
2726
+ const match = size.toLowerCase().match(/(\d+)(kb|mb|gb)?/);
2727
+ if (!match)
2728
+ return 50 * 1024 * 1024;
2729
+ const value = parseInt(match[1], 10);
2730
+ const unit = match[2];
2731
+ if (unit === 'gb')
2732
+ return value * 1024 * 1024 * 1024;
2733
+ if (unit === 'mb')
2734
+ return value * 1024 * 1024;
2735
+ if (unit === 'kb')
2736
+ return value * 1024;
2737
+ return value;
2738
+ }
2739
+ // Centralized file processing handler
2740
+ const handleFileProcessing = (files, source) => {
2741
+ if (!fileUploadsEnabled) {
2742
+ setIncompatibleFiles(['File uploads are disabled']);
2743
+ return;
2744
+ }
2745
+ // Enforce max file size
2746
+ const tooLarge = files.filter((file) => file.size > maxFileSizeBytes);
2747
+ const okFiles = files.filter((file) => file.size <= maxFileSizeBytes);
2748
+ if (tooLarge.length > 0) {
2749
+ setIncompatibleFiles([
2750
+ ...tooLarge.map((f) => `${f.name} (exceeds max size)`),
2751
+ ]);
2752
+ }
2753
+ // Only allow image files if image uploads are enabled
2754
+ const filteredFiles = okFiles.filter((file) => {
2755
+ if (file.type.startsWith('image/')) {
2756
+ return imageUploadsEnabled;
2757
+ }
2758
+ return true;
2759
+ });
2760
+ if (filteredFiles.length > 0 && setAttachedFiles) {
2761
+ setAttachedFiles([...attachedFiles, ...filteredFiles]);
2762
+ if (source === 'paste') {
2763
+ setPasteIndicator(true);
2764
+ setTimeout(() => setPasteIndicator(false), 1000);
2765
+ }
2766
+ }
2767
+ // Show warning for rejected files
2768
+ if (filteredFiles.length < okFiles.length) {
2769
+ setIncompatibleFiles([
2770
+ ...incompatibleFiles,
2771
+ ...okFiles
2772
+ .filter((f) => !filteredFiles.includes(f))
2773
+ .map((f) => `${f.name} (images not allowed)`),
2774
+ ]);
2775
+ setTimeout(() => setIncompatibleFiles([]), 10000);
2776
+ }
2777
+ };
2778
+ // Helper to filter files by type for specific buttons
2779
+ const filterFilesByType = (files, types) => {
2780
+ return files.filter((file) => {
2781
+ return types.some((type) => {
2782
+ // Use FileHandler to check if file is accepted
2783
+ return FileHandler.isFileAccepted(file);
2784
+ });
2785
+ });
2786
+ };
2787
+ // Handle all file uploads (paperclip button)
2788
+ const handleAllFileUpload = (event) => {
2789
+ const files = Array.from(event.target.files || []);
2790
+ handleFileProcessing(files, 'upload');
2791
+ // Reset input
2792
+ if (event.target) {
2793
+ event.target.value = '';
2794
+ }
2795
+ };
2796
+ // Handle image-only uploads (image button)
2797
+ const handleImageUpload = (event) => {
2798
+ if (!imageSupported) {
2799
+ // Show warning if trying to upload images with unsupported model
2800
+ setIncompatibleFiles(['Images not supported by current model']);
2801
+ setTimeout(() => setIncompatibleFiles([]), 5000);
2802
+ return;
2803
+ }
2804
+ const files = Array.from(event.target.files || []);
2805
+ const imageFiles = filterFilesByType(files, ['images']);
2806
+ handleFileProcessing(imageFiles, 'upload');
2807
+ // Reset input
2808
+ if (event.target) {
2809
+ event.target.value = '';
2810
+ }
2811
+ };
2812
+ // Handle audio recording (microphone button) - placeholder for now
2813
+ const handleAudioRecord = () => {
2814
+ // TODO: Implement audio recording functionality
2815
+ console.log('Audio recording not yet implemented');
2816
+ };
2817
+ // Remove attached file
2818
+ const removeFile = (index) => {
2819
+ if (setAttachedFiles) {
2820
+ setAttachedFiles(attachedFiles.filter((_, i) => i !== index));
2821
+ }
2822
+ };
2823
+ // Focus textarea when parent is clicked (but not on icons)
2824
+ const handleContainerClick = (e) => {
2825
+ if (e.target.tagName === 'TEXTAREA')
2826
+ return;
2827
+ inputRef.current?.focus();
2828
+ };
2829
+ // Handle paste events to capture files from clipboard
2830
+ const handlePaste = (event) => {
2831
+ const clipboardData = event.clipboardData;
2832
+ const items = Array.from(clipboardData.items);
2833
+ // Convert clipboard items to files
2834
+ const filePromises = items
2835
+ .filter((item) => item.kind === 'file')
2836
+ .map((item) => {
2837
+ return new Promise((resolve) => {
2838
+ const blob = item.getAsFile();
2839
+ if (blob) {
2840
+ const timestamp = new Date()
2841
+ .toISOString()
2842
+ .slice(0, 19)
2843
+ .replace(/[:.]/g, '-');
2844
+ const extension = blob.type.split('/')[1] || 'unknown';
2845
+ const file = new File([blob], `pasted-file-${timestamp}.${extension}`, {
2846
+ type: blob.type,
2847
+ });
2848
+ resolve(file);
2849
+ }
2850
+ });
2851
+ });
2852
+ // Only prevent default if there are files to paste
2853
+ if (filePromises.length > 0) {
2854
+ event.preventDefault(); // Prevent default paste behavior for files
2855
+ Promise.all(filePromises).then((files) => {
2856
+ handleFileProcessing(files, 'paste');
2857
+ });
2858
+ }
2859
+ // If no files, let the default paste behavior handle text content
2860
+ };
2861
+ // Handle drag and drop for files
2862
+ const handleDragOver = (event) => {
2863
+ event.preventDefault();
2864
+ setIsDragOver(true);
2865
+ };
2866
+ const handleDragLeave = (event) => {
2867
+ event.preventDefault();
2868
+ setIsDragOver(false);
2869
+ };
2870
+ const handleDrop = (event) => {
2871
+ event.preventDefault();
2872
+ setIsDragOver(false);
2873
+ const files = Array.from(event.dataTransfer.files);
2874
+ handleFileProcessing(files, 'drop');
2875
+ };
2876
+ const modelSelectionHandler = React.useCallback((modelInfo) => {
2877
+ if (onModelSelectionChange) {
2878
+ onModelSelectionChange(modelInfo);
2879
+ }
2880
+ }, [onModelSelectionChange]);
2881
+ return (jsxRuntime.jsxs("div", { className: `px-3 py-2 border-t flex flex-col gap-2 transition-all duration-500 delay-500 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`, style: { borderColor: theme.colors.border.primary }, children: [attachedFiles.length > 0 && (jsxRuntime.jsx("div", { className: "flex flex-wrap gap-2 mb-2", children: attachedFiles.map((file, index) => (jsxRuntime.jsxs("div", { className: "relative group", children: [jsxRuntime.jsx("div", { className: "w-16 h-16 rounded-lg border border-gray-200 bg-gray-50 flex items-center justify-center overflow-hidden", children: file.type.startsWith('image/') ? (jsxRuntime.jsx("img", { src: URL.createObjectURL(file), alt: `Attached ${index + 1}`, className: "w-full h-full object-cover" })) : (jsxRuntime.jsx("div", { className: "flex items-center justify-center", children: FileHandler.getFileIcon(file) })) }), jsxRuntime.jsx("button", { onClick: () => removeFile(index), className: "absolute -top-2 -right-2 w-5 h-5 bg-red-500 text-white rounded-full flex items-center justify-center text-xs hover:bg-red-600 transition-colors", title: "Remove file", children: "\u00D7" }), jsxRuntime.jsx("div", { className: "absolute bottom-0 left-1/2 transform -translate-x-1/2 translate-y-full opacity-0 group-hover:opacity-100 transition-opacity duration-200 pointer-events-none z-10 mt-2", children: jsxRuntime.jsxs("div", { className: "bg-gray-900 text-white text-xs rounded-lg px-2 py-1 whitespace-nowrap shadow-lg", children: [jsxRuntime.jsx("div", { className: "font-medium truncate max-w-32", title: file.name, children: file.name }), jsxRuntime.jsxs("div", { className: "text-gray-300", children: [FileHandler.getFileTypeDescription(file), " \u2022", ' ', FileHandler.formatFileSize(file.size)] }), jsxRuntime.jsx("div", { className: "absolute -top-1 left-1/2 transform -translate-x-1/2 w-2 h-2 bg-gray-900 rotate-45" })] }) })] }, index))) })), incompatibleFiles.length > 0 && (jsxRuntime.jsxs("div", { className: "relative flex flex-col gap-1 text-sm text-orange-700 bg-orange-50 border border-orange-200 rounded px-3 py-2 mb-2", children: [jsxRuntime.jsx("button", { onClick: () => setIncompatibleFiles([]), className: "absolute top-1 right-1 w-5 h-5 flex items-center justify-center text-orange-500 hover:text-orange-700 hover:bg-orange-200 rounded-full transition-colors", title: "Dismiss", type: "button", children: "\u00D7" }), jsxRuntime.jsxs("div", { className: "flex items-center gap-2 pr-6", children: [jsxRuntime.jsx("span", { children: "\u26A0\uFE0F" }), jsxRuntime.jsx("span", { className: "font-medium", children: incompatibleFiles.length === 1
2882
+ ? 'File not supported'
2883
+ : 'Some files not supported' })] }), jsxRuntime.jsxs("div", { className: "text-xs", children: [jsxRuntime.jsx("div", { className: "mb-1", children: "These files were skipped:" }), jsxRuntime.jsxs("ul", { className: "list-disc list-inside ml-2 space-y-0.5", children: [incompatibleFiles.slice(0, 3).map((fileName, index) => (jsxRuntime.jsx("li", { className: "truncate", children: fileName }, index))), incompatibleFiles.length > 3 && (jsxRuntime.jsxs("li", { children: ["... and ", incompatibleFiles.length - 3, " more"] }))] }), jsxRuntime.jsxs("div", { className: "mt-1 text-orange-600", children: ["\uD83D\uDCA1 Only ", FileHandler.getAcceptedTypesDescription(), " are supported", incompatibleFiles.some((name) => name.includes('images not supported by current model')) && (jsxRuntime.jsxs("div", { className: "mt-1", children: ["\uD83D\uDD04 ", jsxRuntime.jsx("strong", { children: "Switch to GPT-4O, Claude 3, or Gemini" }), " to enable image uploads"] }))] })] })] })), jsxRuntime.jsxs("div", { className: `relative flex flex-col gap-1 rounded border px-2 pt-1 pb-1 cursor-text transition-all duration-200 border-2 focus-within:shadow-sm ${isDragOver ? 'border-dashed bg-opacity-20' : 'border-transparent'}`, style: {
2884
+ backgroundColor: theme.colors.surface.primary,
2885
+ borderColor: isDragOver
2886
+ ? theme.colors.primary[400]
2887
+ : theme.colors.border.primary,
2888
+ color: theme.colors.text.primary,
2889
+ }, onFocus: () => {
2890
+ // Focus within handling
2891
+ const target = event?.target;
2892
+ if (target) {
2893
+ target.style.borderColor = theme.colors.primary[500];
2894
+ }
2895
+ }, onClick: handleContainerClick, onDragOver: handleDragOver, onDragLeave: handleDragLeave, onDrop: handleDrop, children: [isDragOver && (jsxRuntime.jsx("div", { className: "absolute top-2 left-2 right-2 bottom-2 flex items-center justify-center py-1 bg-blue-100 rounded text-blue-600 text-sm font-medium z-10 pointer-events-none", children: "\uD83D\uDCC2 Drop files here" })), jsxRuntime.jsx("textarea", { ref: inputRef, value: query, onChange: (e) => setQuery(e.target.value), onFocus: () => setFocused(true), onBlur: () => setFocused(false), onKeyDown: (e) => e.key === 'Enter' && !e.shiftKey && (e.preventDefault(), onSend()), onPaste: handlePaste, placeholder: "Describe what you need... (Try: 'I want to know how much time has been updated')", className: "w-full outline-none resize-none min-h-[3.5rem] max-h-32 border-none px-0 bg-transparent", style: {
2896
+ color: theme.colors.text.primary,
2897
+ }, rows: 3 }), jsxRuntime.jsxs("div", { className: "flex gap-2 items-center justify-between", children: [jsxRuntime.jsxs("div", { className: "flex gap-1", children: [features.fileUploads !== false && (jsxRuntime.jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, onMouseEnter: (e) => {
2898
+ e.currentTarget.style.color = theme.colors.primary[500];
2899
+ }, onMouseLeave: (e) => {
2900
+ e.currentTarget.style.color = theme.colors.text.tertiary;
2901
+ }, title: `Attach files - Supports: ${FileHandler.getAcceptedTypesDescription()}`, tabIndex: -1, type: "button", onClick: () => fileInputRef.current?.click(), children: jsxRuntime.jsx(Icon, { name: "paperclip", size: "sm" }) })), features.enableImageUploads !== false &&
2902
+ features.imageAnalysis !== false && (jsxRuntime.jsx("button", { className: `p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors ${!imageSupported ? 'opacity-50 cursor-not-allowed' : ''}`, style: { color: theme.colors.text.tertiary }, onMouseEnter: (e) => {
2903
+ if (imageSupported) {
2904
+ e.currentTarget.style.color = theme.colors.primary[500];
2905
+ }
2906
+ }, onMouseLeave: (e) => {
2907
+ e.currentTarget.style.color = theme.colors.text.tertiary;
2908
+ }, title: imageSupported
2909
+ ? 'Attach images - AI can analyze image content'
2910
+ : "Current model doesn't support images. Switch to GPT-4O, Claude 3, or Gemini to use image attachments.", tabIndex: -1, type: "button", onClick: () => imageSupported && imageInputRef.current?.click(), disabled: !imageSupported, children: jsxRuntime.jsx(Icon, { name: "image", size: "sm" }) })), jsxRuntime.jsx("button", { className: "p-1 cursor-pointer disabled:opacity-50 disabled:cursor-default transition-colors", style: { color: theme.colors.text.tertiary }, title: "Record audio (coming soon)", tabIndex: -1, type: "button", onClick: handleAudioRecord, disabled: true, children: jsxRuntime.jsx(Icon, { name: "microphone", size: "sm" }) })] }), jsxRuntime.jsx("button", { onClick: () => onSend(), disabled: (!query.trim() && attachedFiles.length === 0) || isLoading, className: "rounded-full transition disabled:opacity-50 p-2", style: {
2911
+ color: theme.colors.text.primary,
2912
+ }, onMouseEnter: (e) => {
2913
+ if (!e.currentTarget.disabled) {
2914
+ e.currentTarget.style.backgroundColor =
2915
+ theme.colors.surface.secondary;
2916
+ }
2917
+ }, onMouseLeave: (e) => {
2918
+ if (!e.currentTarget.disabled) {
2919
+ e.currentTarget.style.backgroundColor = 'transparent';
2920
+ }
2921
+ }, title: "Send", tabIndex: -1, type: "button", children: jsxRuntime.jsxs("svg", { width: "20", height: "20", viewBox: "0 0 20 20", fill: "none", xmlns: "http://www.w3.org/2000/svg", children: [jsxRuntime.jsx("g", { clipPath: "url(#clip0_892_5571)", children: jsxRuntime.jsx("path", { d: "M13.3334 10.0001L10.0001 6.66675M10.0001 6.66675L6.66675 10.0001M10.0001 6.66675V13.3334M18.3334 10.0001C18.3334 14.6025 14.6025 18.3334 10.0001 18.3334C5.39771 18.3334 1.66675 14.6025 1.66675 10.0001C1.66675 5.39771 5.39771 1.66675 10.0001 1.66675C14.6025 1.66675 18.3334 5.39771 18.3334 10.0001Z", stroke: "currentColor", strokeWidth: "1.5", strokeLinecap: "round", strokeLinejoin: "round" }) }), jsxRuntime.jsx("defs", { children: jsxRuntime.jsx("clipPath", { id: "clip0_892_5571", children: jsxRuntime.jsx("rect", { width: "20", height: "20", fill: "white" }) }) })] }) })] })] }), jsxRuntime.jsx("input", { ref: fileInputRef, type: "file", accept: FileHandler.getInputAcceptAttribute(), multiple: true, onChange: handleAllFileUpload, className: "hidden", "aria-label": "Upload any file" }), jsxRuntime.jsx("input", { ref: imageInputRef, type: "file", accept: "image/*", multiple: true, onChange: handleImageUpload, className: "hidden", "aria-label": "Upload images" }), features.modelSwitching !== false &&
2922
+ features.imageAnalysis !== false &&
2923
+ features.enableImageUploads !== false &&
2924
+ !imageSupported &&
2925
+ !imageTipDismissed && (jsxRuntime.jsxs("div", { className: "relative px-2 py-1 bg-amber-50 border border-amber-200 rounded text-amber-700 text-xs flex items-center gap-2", children: [jsxRuntime.jsx(Icon, { name: "image", size: "xs", className: "opacity-50" }), jsxRuntime.jsxs("span", { className: "flex-1 pr-4", children: ["\uD83D\uDCA1 ", jsxRuntime.jsx("strong", { children: "Tip:" }), " Switch to GPT-4O, Claude 3, or Gemini in the model switcher below to enable image attachments"] }), jsxRuntime.jsx("button", { onClick: () => setImageTipDismissed(true), className: "absolute top-1 right-1 w-4 h-4 flex items-center justify-center text-amber-500 hover:text-amber-700 hover:bg-amber-200 rounded-full transition-colors text-xs font-bold", title: "Dismiss tip", type: "button", children: "\u00D7" })] })), jsxRuntime.jsxs("div", { className: "flex items-center justify-between text-xs px-0 pt-0", style: { color: theme.colors.text.tertiary }, children: [jsxRuntime.jsx("div", { className: "flex gap-4", children: features.modelSwitching !== false && (jsxRuntime.jsx(ModelSwitcher, { models: models, defaultModel: defaultModel, showUsageStats: features.usageTracking !== false && showUsageStats !== false, theme: theme, onModelSelectionChange: modelSelectionHandler })) }), jsxRuntime.jsx("div", { className: "flex items-center gap-2", children: jsxRuntime.jsxs("span", { children: [jsxRuntime.jsx("kbd", { className: "px-1 rounded", style: {
2926
+ backgroundColor: theme.colors.surface.secondary,
2927
+ color: theme.colors.text.secondary,
2928
+ }, children: "\u23CE" }), ' ', "to send,", ' ', jsxRuntime.jsx("kbd", { className: "px-1 rounded", style: {
2929
+ backgroundColor: theme.colors.surface.secondary,
2930
+ color: theme.colors.text.secondary,
2931
+ }, children: "\u21E7\u23CE" }), ' ', "for new line"] }) })] })] }));
2932
+ }
2933
+
2934
+ // Helper function to detect system theme preference
2935
+ const getSystemThemePreference = () => {
2936
+ if (typeof window !== 'undefined' && window.matchMedia) {
2937
+ return window.matchMedia('(prefers-color-scheme: dark)').matches
2938
+ ? 'dark'
2939
+ : 'light';
2940
+ }
2941
+ return 'dark'; // Default to dark if can't detect
2942
+ };
2943
+ // Helper function to merge custom theme with defaults
2944
+ const mergeTheme = (customTheme, baseTheme) => {
2945
+ return {
2946
+ ...baseTheme,
2947
+ ...customTheme,
2948
+ colors: {
2949
+ ...baseTheme.colors,
2950
+ ...customTheme.colors,
2951
+ primary: {
2952
+ ...baseTheme.colors.primary,
2953
+ ...customTheme.colors?.primary,
2954
+ },
2955
+ background: {
2956
+ ...baseTheme.colors.background,
2957
+ ...customTheme.colors?.background,
2958
+ },
2959
+ text: {
2960
+ ...baseTheme.colors.text,
2961
+ ...customTheme.colors?.text,
2962
+ },
2963
+ border: {
2964
+ ...baseTheme.colors.border,
2965
+ ...customTheme.colors?.border,
2966
+ },
2967
+ surface: {
2968
+ ...baseTheme.colors.surface,
2969
+ ...customTheme.colors?.surface,
2970
+ },
2971
+ state: {
2972
+ ...baseTheme.colors.state,
2973
+ ...customTheme.colors?.state,
2974
+ },
2975
+ },
2976
+ };
2977
+ };
2978
+ /**
2979
+ * Helper to extract text content from message content blocks
2980
+ */
2981
+ const getTextFromMessage = (msg) => {
2982
+ return msg.content
2983
+ .filter((block) => block.type === 'text')
2984
+ .map((block) => block.content)
2985
+ .join('\n');
2986
+ };
2987
+ /**
2988
+ * ChatPanel component
2989
+ * @param isOpen - Whether the chat panel is open
2990
+ * @param setIsOpen - Function to set the chat panel open state
2991
+ * @param onClose - Function to close the chat panel
2992
+ * @param onOpen - Function to open the chat panel
2993
+ * @param userId - User ID - used to identify the user in the streaming server
2994
+ * @param formData - Form data - used to reference the existing form data in the streaming server
2995
+ * @param setFormState - Function to set the form state
2996
+ * @param onNavigate - Function to navigate to a new path
2997
+ * @param theme - Theme
2998
+ * @param showHistory - Whether to show chat history sidebar (default: false)
2999
+ * @param showProfileBubbles - Whether to show profile bubbles in chat (default: false)
3000
+ * @param serverConfig - Flexible server configuration object
3001
+ * @param showFloatingButton - Whether to show the floating button
3002
+ * @param floatingButtonIcon - Icon for the floating button
3003
+ * @param floatingButtonPosition - Position of the floating button
3004
+ * @param floatingButtonSize - Size of the floating button
3005
+ * @param floatingButtonClassName - Class name for the floating button
3006
+ * @param chats - Optional external chat history
3007
+ * @param setChats - Optional function to set external chat history
3008
+ * @param currentChatId - Optional external current chat ID
3009
+ * @param setCurrentChatId - Optional function to set external current chat ID
3010
+ * @param chatLevel - Optional chat level - 'full' or 'basic' or 'none'
3011
+ * @param initialQuery - Optional initial query to send when modal opens
3012
+ * @param setInitialQuery - Optional function to set/clear the initial query
3013
+ */
3014
+ function ChatPanel({ isOpen, setIsOpen, onClose, onOpen, userId, formData, setFormState, onNavigate, theme = defaultTheme, // Legacy support
3015
+ chatTheme, // New theme system
3016
+ themeMode = 'system', // Default to system preference
3017
+ showHistory = false, // Default to hidden
3018
+ showProfileBubbles = false, // Default to hidden
3019
+ modalPosition = 'left', // Default to left position
3020
+ serverConfig, models, defaultModel, showUsageStats, maxFileSize, features = {}, showFloatingButton = false, floatingButtonIcon, floatingButtonPosition = 'bottom-right', floatingButtonSize = 'md', floatingButtonClassName = '', chats, setChats, currentChatId, setCurrentChatId, chatLevel, initialQuery, setInitialQuery, }) {
3021
+ const handleSuggestionClick = (s) => {
3022
+ if (setFormState && s.formState)
3023
+ setFormState(s.formState, s.actionType);
3024
+ if (s.path)
3025
+ onNavigate(s.path);
3026
+ onClose();
3027
+ };
3028
+ const containerRef = React.useRef(null);
3029
+ // SIMPLIFIED ANIMATION STATE
3030
+ const [isVisible, setIsVisible] = React.useState(false);
3031
+ const [isAnimating, setIsAnimating] = React.useState(false);
3032
+ // Handle opening animation
3033
+ React.useEffect(() => {
3034
+ if (isOpen && !isVisible) {
3035
+ setIsVisible(true);
3036
+ setIsAnimating(true);
3037
+ requestAnimationFrame(() => {
3038
+ setTimeout(() => {
3039
+ setIsAnimating(false);
3040
+ }, 300);
3041
+ });
3042
+ }
3043
+ }, [isOpen, isVisible]);
3044
+ // Handle closing animation
3045
+ const handleClose = React.useCallback(() => {
3046
+ if (!isAnimating) {
3047
+ setIsAnimating(true);
3048
+ // Flip external open state immediately so closing classes apply
3049
+ onClose();
3050
+ setIsOpen?.(false);
3051
+ // Keep mounted for duration to allow slide-out
3052
+ setTimeout(() => {
3053
+ setIsVisible(false);
3054
+ setIsAnimating(false);
3055
+ }, 300);
3056
+ }
3057
+ }, [isAnimating, onClose, setIsOpen]);
3058
+ // Handle opening
3059
+ const handleOpen = React.useCallback(() => {
3060
+ if (!isAnimating) {
3061
+ onOpen?.();
3062
+ setIsOpen?.(true);
3063
+ }
3064
+ }, [isAnimating, onOpen, setIsOpen]);
3065
+ // Determine if panel should be rendered
3066
+ const shouldRenderPanel = isOpen || isVisible;
3067
+ // Determine animation classes
3068
+ const getAnimationClasses = () => {
3069
+ if (!isOpen && isVisible) {
3070
+ return modalPosition === 'left' ? '-translate-x-full opacity-0' : 'translate-x-full opacity-0';
3071
+ }
3072
+ else if (isOpen && isVisible) {
3073
+ return 'translate-x-0 opacity-100';
3074
+ }
3075
+ else {
3076
+ return modalPosition === 'left' ? '-translate-x-full opacity-0' : 'translate-x-full opacity-0';
3077
+ }
3078
+ };
3079
+ // Resolve theme mode and create final theme
3080
+ const resolvedThemeMode = React.useMemo(() => {
3081
+ if (themeMode === 'system') {
3082
+ return getSystemThemePreference();
3083
+ }
3084
+ return themeMode;
3085
+ }, [themeMode]);
3086
+ const finalTheme = React.useMemo(() => {
3087
+ const baseTheme = resolvedThemeMode === 'dark' ? defaultDarkTheme : defaultLightTheme;
3088
+ return chatTheme ? mergeTheme(chatTheme, baseTheme) : baseTheme;
3089
+ }, [resolvedThemeMode, chatTheme]);
3090
+ // Generate custom scrollbar styles based on theme
3091
+ const scrollbarStyles = `
3092
+ .custom-scrollbar {
3093
+ scrollbar-width: thin;
3094
+ scrollbar-color: ${finalTheme.colors.primary[500]} ${finalTheme.colors.surface.secondary};
3095
+ }
3096
+ .custom-scrollbar::-webkit-scrollbar {
3097
+ width: 8px;
3098
+ height: 8px;
3099
+ }
3100
+ .custom-scrollbar::-webkit-scrollbar-track {
3101
+ background: ${finalTheme.colors.surface.secondary};
3102
+ border-radius: 4px;
3103
+ }
3104
+ .custom-scrollbar::-webkit-scrollbar-thumb {
3105
+ background: ${finalTheme.colors.primary[500]};
3106
+ border-radius: 4px;
3107
+ border: 1px solid ${finalTheme.colors.surface.secondary};
3108
+ }
3109
+ .custom-scrollbar::-webkit-scrollbar-thumb:hover {
3110
+ background: ${finalTheme.colors.primary[600]};
3111
+ }
3112
+
3113
+ /* Animation classes */
3114
+ .chat-panel-enter {
3115
+ transform: translateX(${modalPosition === 'left' ? '-100%' : '100%'});
3116
+ opacity: 0;
3117
+ }
3118
+
3119
+ .chat-panel-enter-active {
3120
+ transform: translateX(0);
3121
+ opacity: 1;
3122
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
3123
+ }
3124
+
3125
+ .chat-panel-exit {
3126
+ transform: translateX(0);
3127
+ opacity: 1;
3128
+ }
3129
+
3130
+ .chat-panel-exit-active {
3131
+ transform: translateX(${modalPosition === 'left' ? '-100%' : '100%'});
3132
+ opacity: 0;
3133
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
3134
+ }
3135
+
3136
+ /* Background overlay animations */
3137
+ .overlay-enter {
3138
+ opacity: 0;
3139
+ }
3140
+
3141
+ .overlay-enter-active {
3142
+ opacity: 1;
3143
+ transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1);
3144
+ }
3145
+
3146
+ .overlay-exit {
3147
+ opacity: 1;
3148
+ }
3149
+
3150
+ .overlay-exit-active {
3151
+ opacity: 0;
3152
+ transition: opacity 300ms cubic-bezier(0.4, 0, 0.2, 1);
3153
+ }
3154
+
3155
+ /* Close button animations */
3156
+ .close-button-enter {
3157
+ transform: scale(0.8) rotate(-90deg);
3158
+ opacity: 0;
3159
+ }
3160
+
3161
+ .close-button-enter-active {
3162
+ transform: scale(1) rotate(0deg);
3163
+ opacity: 1;
3164
+ transition: all 400ms cubic-bezier(0.34, 1.56, 0.64, 1);
3165
+ }
3166
+
3167
+ .close-button-exit {
3168
+ transform: scale(1) rotate(0deg);
3169
+ opacity: 1;
3170
+ }
3171
+
3172
+ .close-button-exit-active {
3173
+ transform: scale(0.8) rotate(90deg);
3174
+ opacity: 0;
3175
+ transition: all 300ms cubic-bezier(0.4, 0, 0.2, 1);
3176
+ }
3177
+ `;
3178
+ return (jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [jsxRuntime.jsx("style", { children: scrollbarStyles }), jsxRuntime.jsxs(ErrorBoundary, { children: [!isOpen && showFloatingButton && (jsxRuntime.jsx(FloatingChatButton, { onClick: handleOpen, theme: theme, icon: floatingButtonIcon, position: floatingButtonPosition, size: floatingButtonSize, className: floatingButtonClassName })), shouldRenderPanel && (jsxRuntime.jsxs("div", { className: "fixed inset-0 z-50", children: [jsxRuntime.jsx("div", { className: `absolute inset-0 bg-black/20 transition-opacity duration-300 ${isOpen ? 'opacity-100' : 'opacity-0'}`, onClick: handleClose }), jsxRuntime.jsx("div", { className: `fixed z-[60] top-6 transition-all duration-300 ${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-75'}`, style: { ...(modalPosition === 'left'
3179
+ ? { left: showHistory ? 'min(90vw, 700px)' : 'min(80vw, 550px)', marginLeft: '16px' }
3180
+ : { right: showHistory ? 'min(90vw, 700px)' : 'min(80vw, 550px)', marginRight: '16px' }) }, children: jsxRuntime.jsx("button", { onClick: handleClose, className: "w-10 h-10 rounded-full shadow-lg transition-all duration-200 flex items-center justify-center text-xl font-bold hover:scale-110", style: {
3181
+ backgroundColor: finalTheme.colors.surface.elevated,
3182
+ color: finalTheme.colors.text.secondary,
3183
+ border: `2px solid ${finalTheme.colors.border.primary}`,
3184
+ boxShadow: finalTheme.shadows.lg,
3185
+ }, onMouseEnter: (e) => {
3186
+ e.currentTarget.style.backgroundColor =
3187
+ finalTheme.colors.surface.secondary;
3188
+ e.currentTarget.style.color = finalTheme.colors.text.primary;
3189
+ e.currentTarget.style.borderColor =
3190
+ finalTheme.colors.border.secondary;
3191
+ e.currentTarget.style.transform = 'scale(1.05)';
3192
+ }, onMouseLeave: (e) => {
3193
+ e.currentTarget.style.backgroundColor =
3194
+ finalTheme.colors.surface.elevated;
3195
+ e.currentTarget.style.color =
3196
+ finalTheme.colors.text.secondary;
3197
+ e.currentTarget.style.borderColor =
3198
+ finalTheme.colors.border.primary;
3199
+ e.currentTarget.style.transform = 'scale(1)';
3200
+ }, title: "Close", children: "\u00D7" }) }), jsxRuntime.jsxs("div", { className: `fixed top-0 h-full w-full z-50 flex flex-col relative
3201
+ ${modalPosition === 'left' ? 'left-0 rounded-r-lg' : 'right-0 rounded-l-lg'}
3202
+ ${showHistory ? 'sm:w-[90vw] md:w-[700px]' : 'sm:w-[80vw] md:w-[550px]'}
3203
+ transition-all duration-300 ease-out
3204
+ ${getAnimationClasses()}
3205
+ `, style: {
3206
+ backgroundColor: finalTheme.colors.background.primary,
3207
+ color: finalTheme.colors.text.primary,
3208
+ boxShadow: modalPosition === 'left'
3209
+ ? '8px 0 32px rgba(0, 0, 0, 0.3), 4px 0 16px rgba(0, 0, 0, 0.2)'
3210
+ : '-8px 0 32px rgba(0, 0, 0, 0.3), -4px 0 16px rgba(0, 0, 0, 0.2)',
3211
+ }, ref: containerRef, onClick: (e) => e.stopPropagation(), children: [jsxRuntime.jsx("div", { className: `flex items-center justify-between px-6 py-4 border-b min-h-[72px] transition-all duration-500 delay-100 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-2'}`, style: {
3212
+ borderColor: finalTheme.colors.border.primary,
3213
+ backgroundColor: finalTheme.colors.background.primary,
3214
+ }, children: jsxRuntime.jsxs("div", { className: "flex items-center gap-3", children: [jsxRuntime.jsx("span", { className: `inline-flex items-center justify-center rounded-full w-8 h-8 transition-all duration-500 delay-200 ${isOpen ? 'opacity-100 scale-100 rotate-0' : 'opacity-0 scale-75 rotate-12'}`, style: { backgroundColor: finalTheme.colors.primary[600] }, children: jsxRuntime.jsx(Icon, { name: "ai", size: "sm", style: { color: finalTheme.colors.text.primary } }) }), jsxRuntime.jsxs("div", { className: `transition-all duration-500 delay-300 ${isOpen ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-2'}`, children: [jsxRuntime.jsx("div", { className: "font-bold text-lg leading-tight", style: { color: finalTheme.colors.text.primary }, children: "AI Logistics & Customs Expert" }), jsxRuntime.jsx("div", { className: "text-xs", style: { color: finalTheme.colors.text.tertiary }, children: "Ready to assist you with your queries" })] })] }) }), jsxRuntime.jsxs("div", { className: `flex flex-1 min-h-0 h-full transition-all duration-500 delay-200 ${isOpen ? 'opacity-100 scale-100' : 'opacity-0 scale-95'}`, children: [showHistory && (jsxRuntime.jsx(ChatHistorySidebar, { chats: chats || {}, currentChatId: currentChatId || 'default', switchChat: () => { }, newChat: () => { }, theme: finalTheme, isOpen: isOpen })), jsxRuntime.jsx("div", { className: "flex-1 flex flex-col min-h-0 h-full", children: jsxRuntime.jsx("div", { className: `flex-1 px-6 py-4 min-h-0 transition-all duration-500 delay-300 ${isOpen ? 'opacity-100 translate-y-0' : 'opacity-0 translate-y-4'}`, style: {
3215
+ backgroundColor: finalTheme.colors.background.primary,
3216
+ }, children: jsxRuntime.jsx(Conversation, { onSuggestionClick: handleSuggestionClick, theme: finalTheme, showProfileBubbles: showProfileBubbles, autoScroll: true, showInput: true,
3217
+ // AI functionality props
3218
+ userId: userId, serverConfig: serverConfig, formData: formData, setFormState: setFormState, onNavigate: onNavigate, chatLevel: chatLevel,
3219
+ // External chat management
3220
+ chats: chats, setChats: setChats, currentChatId: currentChatId, setCurrentChatId: setCurrentChatId,
3221
+ // Model configuration
3222
+ models: models, defaultModel: defaultModel, showUsageStats: showUsageStats, maxFileSize: maxFileSize, features: features,
3223
+ // Initial query handling
3224
+ initialQuery: initialQuery, setInitialQuery: setInitialQuery }) }) })] })] })] }))] })] }));
3225
+ }
3226
+ /**
3227
+ * ChatHistorySidebar component with animation support
3228
+ */
3229
+ function ChatHistorySidebar({ chats, currentChatId, switchChat, newChat, theme, isOpen, }) {
3230
+ return (jsxRuntime.jsx("div", { className: `flex-shrink-0 flex-grow-0 border-r flex flex-col h-full p-2 w-[150px] transition-all duration-500 delay-400 ${isOpen ? 'opacity-100 translate-x-0' : 'opacity-0 translate-x-4'}`, style: {
3231
+ backgroundColor: theme.colors.background.secondary,
3232
+ borderColor: theme.colors.border.primary,
3233
+ }, children: jsxRuntime.jsxs("div", { className: "flex-1 overflow-y-auto flex flex-col items-center gap-2 p-2 custom-scrollbar", children: [jsxRuntime.jsx("button", { className: "rounded-lg w-full py-2 font-bold text-xl mb-2 shadow transition-colors", style: {
3234
+ backgroundColor: theme.colors.primary[600],
3235
+ color: theme.colors.text.inverse,
3236
+ borderColor: theme.colors.primary[700],
3237
+ }, onMouseEnter: (e) => {
3238
+ e.currentTarget.style.backgroundColor = theme.colors.primary[700];
3239
+ }, onMouseLeave: (e) => {
3240
+ e.currentTarget.style.backgroundColor = theme.colors.primary[600];
3241
+ }, onClick: newChat, children: "+" }), Object.keys(chats)
3242
+ .sort((a, b) => Number(b) - Number(a))
3243
+ .map((chatId) => (jsxRuntime.jsxs("div", { className: "flex items-center gap-2 px-3 py-2 rounded-lg cursor-pointer transition-colors w-full", style: {
3244
+ backgroundColor: chatId === currentChatId
3245
+ ? theme.colors.primary[900]
3246
+ : theme.colors.surface.primary,
3247
+ color: chatId === currentChatId
3248
+ ? theme.colors.primary[100]
3249
+ : theme.colors.text.secondary,
3250
+ fontWeight: chatId === currentChatId ? 'bold' : 'normal',
3251
+ }, onMouseEnter: (e) => {
3252
+ if (chatId !== currentChatId) {
3253
+ e.currentTarget.style.backgroundColor =
3254
+ theme.colors.surface.secondary;
3255
+ }
3256
+ }, onMouseLeave: (e) => {
3257
+ if (chatId !== currentChatId) {
3258
+ e.currentTarget.style.backgroundColor =
3259
+ theme.colors.surface.primary;
3260
+ }
3261
+ }, onClick: () => switchChat(chatId), title: (chats[chatId]?.[0]
3262
+ ? getTextFromMessage(chats[chatId][0])
3263
+ : '')?.slice(0, 30) ||
3264
+ `Chat started at ${new Date(Number(chatId)).toLocaleString()}`, children: [jsxRuntime.jsx("span", { className: "flex items-center justify-center w-7 h-7 rounded-full bg-blue-50", children: jsxRuntime.jsx("svg", { width: "18", height: "18", fill: "none", stroke: "currentColor", strokeWidth: "2", viewBox: "0 0 24 24", children: jsxRuntime.jsx("path", { d: "M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z" }) }) }), jsxRuntime.jsxs("div", { className: "flex-1 min-w-0", children: [jsxRuntime.jsx("div", { className: "truncate font-semibold", children: (chats[chatId]?.[0]
3265
+ ? getTextFromMessage(chats[chatId][0])
3266
+ : '')?.slice(0, 18) || 'New chat' }), jsxRuntime.jsx("div", { className: "text-xs text-gray-400", children: (() => {
3267
+ const now = Date.now();
3268
+ const diff = Math.floor((now - Number(chatId)) / 1000);
3269
+ if (diff < 60)
3270
+ return 'Just now';
3271
+ if (diff < 3600)
3272
+ return `${Math.floor(diff / 60)}m ago`;
3273
+ if (diff < 86400)
3274
+ return `${Math.floor(diff / 3600)}h ago`;
3275
+ return `${Math.floor(diff / 86400)}d ago`;
3276
+ })() })] })] }, chatId)))] }) }));
3277
+ }
3278
+ /**
3279
+ * FloatingChatButton component
3280
+ * @param onClick - Function to handle the click event
3281
+ * @param theme - Theme
3282
+ * @param icon - Icon for the floating button
3283
+ * @param className - Class name for the floating button
3284
+ * @param position - Position of the floating button
3285
+ * @param size - Size of the floating button
3286
+ */
3287
+ function FloatingChatButton({ onClick, theme = defaultTheme, icon, className = '', position = 'bottom-right', size = 'md', }) {
3288
+ // Note: Theme is now handled via Tailwind classes directly
3289
+ // Position classes
3290
+ const positionClasses = {
3291
+ 'bottom-right': 'bottom-6 right-6',
3292
+ 'bottom-left': 'bottom-6 left-6',
3293
+ 'top-right': 'top-6 right-6',
3294
+ 'top-left': 'top-6 left-6',
3295
+ };
3296
+ // Size classes
3297
+ const sizeClasses = {
3298
+ sm: 'w-12 h-12',
3299
+ md: 'w-14 h-14',
3300
+ lg: 'w-16 h-16',
3301
+ };
3302
+ const iconSizes = {
3303
+ sm: { width: 20, height: 20 },
3304
+ md: { width: 24, height: 24 },
3305
+ lg: { width: 28, height: 28 },
3306
+ };
3307
+ return (jsxRuntime.jsx("div", { className: `fixed z-50 ${positionClasses[position]} ${className}`, children: jsxRuntime.jsx("button", { onClick: onClick, className: `
3308
+ ${sizeClasses[size]}
3309
+ bg-blue-600 hover:bg-blue-700
3310
+ text-white rounded-full shadow-lg hover:shadow-xl
3311
+ flex items-center justify-center
3312
+ transition-all duration-200 ease-in-out
3313
+ hover:scale-105 active:scale-95
3314
+ focus:outline-none focus:ring-4 focus:ring-blue-200
3315
+ `, title: "Open AI Assistant", "aria-label": "Open AI Assistant", children: icon || (jsxRuntime.jsx(Icon, { name: "ai", size: iconSizes[size].width, className: "text-white" })) }) }));
3316
+ }
3317
+
3318
+ /**
3319
+ * Generic helper that patches an existing object with an AI-generated payload.
3320
+ *
3321
+ * • Skips undefined / null values so they never blank user input
3322
+ * • Coerces strings → numbers when the draft already stores a number
3323
+ * • Allows per-field key mapping & transform overrides
3324
+ */
3325
+ function mergeWithAi(draft, ai, config = {}) {
3326
+ if (!ai || Object.keys(ai).length === 0)
3327
+ return draft;
3328
+ const { keyMap = {}, transforms = {} } = config;
3329
+ // Clone current state so we don’t mutate in-place
3330
+ const next = { ...draft };
3331
+ for (const rawKey of Object.keys(ai)) {
3332
+ // Map AI key → draft key if necessary
3333
+ const k = keyMap[rawKey] ?? rawKey;
3334
+ const incoming = ai[rawKey];
3335
+ if (incoming == null)
3336
+ continue;
3337
+ // Field-specific transform takes precedence
3338
+ if (k in transforms) {
3339
+ next[k] = transforms[k](incoming, next);
3340
+ continue;
3341
+ }
3342
+ // Primitive coercion: if existing field is numeric, cast to number
3343
+ if (typeof next[k] === 'number') {
3344
+ // @ts-expect-error – numeric cast
3345
+ next[k] = Number(incoming);
3346
+ }
3347
+ else {
3348
+ // @ts-expect-error – dynamic key assignment
3349
+ next[k] = incoming;
3350
+ }
3351
+ }
3352
+ return next;
3353
+ }
3354
+
3355
+ /**
3356
+ * React hook that merges an AI-generated payload into local form state.
3357
+ *
3358
+ * Usage:
3359
+ * const { pending, applyPending } = useAiMerge({ ai, draft, onMerged })
3360
+ * <WarningModal isOpen={!!pending} onConfirm={applyPending} />
3361
+ */
3362
+ function useAiMerge({ ai, draft, onMerged, config }) {
3363
+ const [pending, setPending] = React.useState(null);
3364
+ React.useEffect(() => {
3365
+ if (!ai || Object.keys(ai).length === 0)
3366
+ return;
3367
+ const merged = mergeWithAi(draft, ai, config);
3368
+ // if nothing changed, no need to do anything
3369
+ const changed = JSON.stringify(draft) !== JSON.stringify(merged);
3370
+ if (!changed)
3371
+ return;
3372
+ // naïve ‘dirty’ check – consumer can decide whether to overwrite immediately
3373
+ setPending(ai);
3374
+ }, [ai]);
3375
+ const applyPending = () => {
3376
+ if (!pending)
3377
+ return;
3378
+ const merged = mergeWithAi(draft, pending, config);
3379
+ onMerged(merged);
3380
+ setPending(null);
3381
+ };
3382
+ const clearPending = () => setPending(null);
3383
+ return { pending, applyPending, clearPending };
3384
+ }
3385
+
3386
+ exports.ChatPanel = ChatPanel;
3387
+ exports.CommandIcon = Icon;
3388
+ exports.Conversation = Conversation;
3389
+ exports.ErrorBoundary = ErrorBoundary;
3390
+ exports.ModelSwitcher = ModelSwitcher;
3391
+ exports.SuggestionCard = SuggestionCard;
3392
+ exports.SuggestionsPanel = SuggestionsPanel;
3393
+ exports.defaultCommandTheme = defaultTheme;
3394
+ exports.mergeWithAi = mergeWithAi;
3395
+ exports.useAiMerge = useAiMerge;
3396
+ exports.useDebouncedSuggestions = useDebouncedSuggestions;
3397
+ exports.useModelSwitcher = useModelSwitcher;
3398
+ exports.useStreamingAI = useStreamingAI;
3399
+ exports.useSuggestions = useSuggestions;
3400
+ //# sourceMappingURL=index.js.map