nextjs-chatbot-ui 1.2.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,22 +1,31 @@
1
1
  'use client';
2
2
 
3
- import React, { useState } from 'react';
4
- import { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, AdminSetupProps } from '../types/admin';
3
+ import React, { useState, useEffect } from 'react';
4
+ import { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, CollectionColumnMapping, AdminSetupProps } from '../types/admin';
5
5
  import { normalizeConnection, transformConnectionForBackend } from '../utils/connectionHelpers';
6
6
  import clsx from 'clsx';
7
7
 
8
8
  const AdminSetup: React.FC<AdminSetupProps> = ({
9
9
  onSave,
10
10
  onTestConnection,
11
+ onFetchCollections,
11
12
  onFetchColumns,
13
+ onProcessEmbeddings,
12
14
  }) => {
13
15
  const [isModalOpen, setIsModalOpen] = useState(false);
14
- const [currentStep, setCurrentStep] = useState<'connection' | 'columns'>('connection');
16
+ const [currentStep, setCurrentStep] = useState<'connection' | 'collection' | 'columns' | 'processing'>('connection');
15
17
  const [isConnecting, setIsConnecting] = useState(false);
16
18
  const [connectionError, setConnectionError] = useState<string | null>(null);
17
19
  const [connectionSuccess, setConnectionSuccess] = useState(false);
18
- const [availableColumns, setAvailableColumns] = useState<string[]>([]);
19
- const [isLoadingColumns, setIsLoadingColumns] = useState(false);
20
+ const [availableCollections, setAvailableCollections] = useState<string[]>([]);
21
+ const [selectedCollections, setSelectedCollections] = useState<string[]>([]);
22
+ const [collectionColumns, setCollectionColumns] = useState<Record<string, string[]>>({});
23
+ const [selectedColumns, setSelectedColumns] = useState<Record<string, string[]>>({});
24
+ const [isLoadingCollections, setIsLoadingCollections] = useState(false);
25
+ const [loadingColumnsFor, setLoadingColumnsFor] = useState<string[]>([]);
26
+ const [isProcessing, setIsProcessing] = useState(false);
27
+ const [processingStatus, setProcessingStatus] = useState<string>('');
28
+ const [processingError, setProcessingError] = useState<string | null>(null);
20
29
 
21
30
  const [dbType, setDbType] = useState<DatabaseType>('mongodb');
22
31
  const [connection, setConnection] = useState<DatabaseConnection>({
@@ -30,12 +39,6 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
30
39
  ssl: false,
31
40
  });
32
41
 
33
- const [columnSelection, setColumnSelection] = useState<ColumnSelection>({
34
- embeddingColumns: [],
35
- llmColumns: [],
36
- chromaColumns: [],
37
- });
38
-
39
42
  const handleDbTypeChange = (type: DatabaseType) => {
40
43
  setDbType(type);
41
44
  setConnection({
@@ -60,41 +63,31 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
60
63
  setConnectionSuccess(false);
61
64
 
62
65
  try {
63
- // Ensure default ports are set
64
66
  const normalizedConnection = normalizeConnection(connection, dbType);
65
-
66
- // Transform to backend format
67
67
  const backendRequest = transformConnectionForBackend(connection, dbType);
68
68
 
69
69
  let isValid: boolean;
70
70
 
71
71
  if (onTestConnection) {
72
- // Use provided handler - send normalized connection
73
72
  isValid = await onTestConnection(normalizedConnection);
74
73
  } else {
75
- // Default: Try to call backend API
76
74
  try {
77
75
  const response = await fetch('/api/database/test', {
78
76
  method: 'POST',
79
- headers: {
80
- 'Content-Type': 'application/json',
81
- },
82
- body: JSON.stringify(backendRequest), // Send transformed format
77
+ headers: { 'Content-Type': 'application/json' },
78
+ body: JSON.stringify(backendRequest),
83
79
  });
84
80
 
85
81
  if (!response.ok) {
86
82
  const errorData = await response.json().catch(() => ({}));
87
- throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
83
+ throw new Error(errorData.message || `HTTP ${response.status}`);
88
84
  }
89
85
 
90
86
  const data = await response.json();
91
87
  isValid = data.success === true || response.ok;
92
88
  } catch (fetchError: any) {
93
- // If API endpoint doesn't exist, show helpful error
94
89
  if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
95
- throw new Error(
96
- 'Backend API endpoint not found. Please implement POST /api/database/test endpoint or provide onTestConnection handler.'
97
- );
90
+ throw new Error('Backend API endpoint not found. Please implement POST /api/database/test endpoint.');
98
91
  }
99
92
  throw fetchError;
100
93
  }
@@ -105,13 +98,12 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
105
98
  setConnectionSuccess(true);
106
99
  return true;
107
100
  } else {
108
- setConnectionError('Connection failed. Please check your credentials and try again.');
101
+ setConnectionError('Connection failed. Please check your credentials.');
109
102
  setConnectionSuccess(false);
110
103
  return false;
111
104
  }
112
105
  } catch (error: any) {
113
- const errorMessage = error.message || 'Connection failed. Please try again.';
114
- setConnectionError(errorMessage);
106
+ setConnectionError(error.message || 'Connection failed. Please try again.');
115
107
  setConnectionSuccess(false);
116
108
  console.error('Database connection error:', error);
117
109
  return false;
@@ -120,138 +112,299 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
120
112
  }
121
113
  };
122
114
 
123
- const handleFetchColumns = async (): Promise<string[]> => {
124
- setIsLoadingColumns(true);
115
+ const handleFetchCollections = async (): Promise<string[]> => {
116
+ setIsLoadingCollections(true);
125
117
  setConnectionError(null);
126
118
 
127
119
  try {
128
- // Ensure default ports are set
129
120
  const normalizedConnection = normalizeConnection(connection, dbType);
121
+ const backendRequest = transformConnectionForBackend(connection, dbType);
122
+
123
+ let collections: string[];
124
+
125
+ if (onFetchCollections) {
126
+ collections = await onFetchCollections(normalizedConnection);
127
+ } else {
128
+ try {
129
+ const response = await fetch('/api/database/collections', {
130
+ method: 'POST',
131
+ headers: { 'Content-Type': 'application/json' },
132
+ body: JSON.stringify(backendRequest),
133
+ });
134
+
135
+ if (!response.ok) {
136
+ const errorData = await response.json().catch(() => ({}));
137
+ throw new Error(errorData.message || `HTTP ${response.status}`);
138
+ }
139
+
140
+ const data = await response.json();
141
+ collections = data.collections || data.tables || data.columnNames || [];
142
+ } catch (fetchError: any) {
143
+ if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
144
+ throw new Error('Backend API endpoint not found. Please implement POST /api/database/collections endpoint.');
145
+ }
146
+ throw fetchError;
147
+ }
148
+ }
149
+
150
+ if (collections && collections.length > 0) {
151
+ setAvailableCollections(collections);
152
+ setIsLoadingCollections(false);
153
+ return collections;
154
+ } else {
155
+ setConnectionError('No collections found in the database.');
156
+ setIsLoadingCollections(false);
157
+ return [];
158
+ }
159
+ } catch (error: any) {
160
+ setConnectionError(error.message || 'Failed to fetch collections.');
161
+ setIsLoadingCollections(false);
162
+ console.error('Fetch collections error:', error);
163
+ return [];
164
+ }
165
+ };
130
166
 
131
- // Transform to backend format
167
+ const handleFetchColumns = async (collectionName: string): Promise<string[]> => {
168
+ setLoadingColumnsFor(prev => [...prev, collectionName]);
169
+ setConnectionError(null);
170
+
171
+ try {
172
+ const normalizedConnection = normalizeConnection(connection, dbType);
132
173
  const backendRequest = transformConnectionForBackend(connection, dbType);
133
174
 
134
175
  let columns: string[];
135
176
 
136
177
  if (onFetchColumns) {
137
- // Use provided handler
138
- columns = await onFetchColumns(normalizedConnection);
178
+ columns = await onFetchColumns(normalizedConnection, collectionName);
139
179
  } else {
140
- // Default: Try to call backend API
141
180
  try {
142
181
  const response = await fetch('/api/database/columns', {
143
182
  method: 'POST',
144
- headers: {
145
- 'Content-Type': 'application/json',
146
- },
147
- body: JSON.stringify(backendRequest), // Send transformed format
183
+ headers: { 'Content-Type': 'application/json' },
184
+ body: JSON.stringify({
185
+ ...backendRequest,
186
+ collection: collectionName,
187
+ }),
148
188
  });
149
189
 
150
190
  if (!response.ok) {
151
191
  const errorData = await response.json().catch(() => ({}));
152
- throw new Error(errorData.message || `HTTP ${response.status}: ${response.statusText}`);
192
+ throw new Error(errorData.message || `HTTP ${response.status}`);
153
193
  }
154
194
 
155
195
  const data = await response.json();
156
196
  columns = data.columns || data.columnNames || [];
157
197
  } catch (fetchError: any) {
158
- // If API endpoint doesn't exist, show helpful error
159
198
  if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
160
- throw new Error(
161
- 'Backend API endpoint not found. Please implement POST /api/database/columns endpoint or provide onFetchColumns handler.'
162
- );
199
+ throw new Error('Backend API endpoint not found. Please implement POST /api/database/columns endpoint.');
163
200
  }
164
201
  throw fetchError;
165
202
  }
166
203
  }
167
204
 
168
205
  if (columns && columns.length > 0) {
169
- setAvailableColumns(columns);
170
- setIsLoadingColumns(false);
206
+ setCollectionColumns(prev => ({ ...prev, [collectionName]: columns }));
207
+ setSelectedColumns(prev => ({ ...prev, [collectionName]: [] }));
208
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
171
209
  return columns;
172
210
  } else {
173
- setConnectionError('No columns found in the database. Please check your database connection and table selection.');
174
- setIsLoadingColumns(false);
211
+ setConnectionError(`No columns found in ${collectionName}.`);
212
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
175
213
  return [];
176
214
  }
177
215
  } catch (error: any) {
178
- const errorMessage = error.message || 'Failed to fetch columns. Please try again.';
179
- setConnectionError(errorMessage);
180
- setIsLoadingColumns(false);
216
+ setConnectionError(error.message || 'Failed to fetch columns.');
217
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
181
218
  console.error('Fetch columns error:', error);
182
219
  return [];
183
220
  }
184
221
  };
185
222
 
186
- const handleConnectAndNext = async () => {
187
- // Validate connection fields
223
+ const handleConnect = async () => {
188
224
  if (dbType === 'mongodb') {
189
225
  if (!connection.connectionString && (!connection.host || !connection.database)) {
190
226
  setConnectionError('Please provide connection string or host and database');
191
- setConnectionSuccess(false);
192
227
  return;
193
228
  }
194
229
  } else {
195
230
  if (!connection.host || !connection.database || !connection.username || !connection.password) {
196
231
  setConnectionError('Please fill in all required fields');
197
- setConnectionSuccess(false);
198
232
  return;
199
233
  }
200
234
  }
201
235
 
202
- // Clear previous errors
203
236
  setConnectionError(null);
204
237
  setConnectionSuccess(false);
205
238
 
206
- // Test connection first
207
239
  const connectionSuccess = await handleTestConnection();
208
240
 
209
- if (!connectionSuccess) {
210
- // Connection failed, don't proceed
211
- return;
241
+ if (connectionSuccess) {
242
+ const collections = await handleFetchCollections();
243
+ if (collections && collections.length > 0) {
244
+ setCurrentStep('collection');
245
+ }
212
246
  }
247
+ };
213
248
 
214
- // If connection successful, fetch columns
215
- const fetchedColumns = await handleFetchColumns();
216
-
217
- if (fetchedColumns && fetchedColumns.length > 0) {
218
- // Successfully fetched columns, move to next step
219
- setCurrentStep('columns');
220
- setConnectionError(null);
249
+ const handleCollectionToggle = async (collection: string) => {
250
+ if (selectedCollections.includes(collection)) {
251
+ // Deselect collection
252
+ setSelectedCollections(prev => prev.filter(c => c !== collection));
253
+ setCollectionColumns(prev => {
254
+ const newCols = { ...prev };
255
+ delete newCols[collection];
256
+ return newCols;
257
+ });
258
+ setSelectedColumns(prev => {
259
+ const newSelected = { ...prev };
260
+ delete newSelected[collection];
261
+ return newSelected;
262
+ });
221
263
  } else {
222
- // Column fetching failed, show error but keep connection success
223
- // Error is already set in handleFetchColumns
264
+ // Select collection and fetch its columns
265
+ setSelectedCollections(prev => [...prev, collection]);
266
+ await handleFetchColumns(collection);
224
267
  }
225
268
  };
226
269
 
227
- const handleColumnToggle = (column: string, category: keyof ColumnSelection) => {
228
- setColumnSelection((prev) => {
229
- const currentColumns = prev[category];
230
- const isSelected = currentColumns.includes(column);
270
+ const handleColumnToggle = (collection: string, column: string) => {
271
+ setSelectedColumns(prev => {
272
+ const collectionCols = prev[collection] || [];
273
+ const isSelected = collectionCols.includes(column);
231
274
 
232
275
  return {
233
276
  ...prev,
234
- [category]: isSelected
235
- ? currentColumns.filter((c) => c !== column)
236
- : [...currentColumns, column],
277
+ [collection]: isSelected
278
+ ? collectionCols.filter(c => c !== column)
279
+ : [...collectionCols, column],
237
280
  };
238
281
  });
239
282
  };
240
283
 
241
- const handleSave = () => {
284
+ const handleNextToColumns = () => {
285
+ if (selectedCollections.length > 0) {
286
+ setCurrentStep('columns');
287
+ } else {
288
+ setConnectionError('Please select at least one collection.');
289
+ }
290
+ };
291
+
292
+ const handleSave = async () => {
293
+ // Combine all selected columns from all collections
294
+ const allQueryColumns: string[] = [];
295
+ const collections: CollectionColumnMapping[] = [];
296
+
297
+ selectedCollections.forEach(collection => {
298
+ const columns = selectedColumns[collection] || [];
299
+ if (columns.length > 0) {
300
+ allQueryColumns.push(...columns);
301
+ collections.push({
302
+ collection,
303
+ columns,
304
+ });
305
+ }
306
+ });
307
+
242
308
  const config: DatabaseConfig = {
243
309
  connection,
244
- columnSelection,
310
+ columnSelection: {
311
+ embeddingColumns: allQueryColumns,
312
+ llmColumns: [],
313
+ chromaColumns: [],
314
+ },
315
+ collections,
245
316
  };
246
317
 
318
+ // Save configuration first
247
319
  if (onSave) {
248
320
  onSave(config);
249
321
  }
250
-
251
- // Close modal and reset
252
- setIsModalOpen(false);
253
- setCurrentStep('connection');
254
- setConnectionError(null);
322
+
323
+ // Then process embeddings if handler is provided
324
+ if (onProcessEmbeddings) {
325
+ setCurrentStep('processing');
326
+ setIsProcessing(true);
327
+ setProcessingStatus('Fetching data from database...');
328
+ setProcessingError(null);
329
+
330
+ try {
331
+ // Simulate progress updates
332
+ setTimeout(() => setProcessingStatus('Converting data to embeddings...'), 1000);
333
+
334
+ const result = await onProcessEmbeddings(config);
335
+
336
+ if (result.success) {
337
+ setProcessingStatus('Storing embeddings in vector database...');
338
+ setTimeout(() => {
339
+ setProcessingStatus('✓ Successfully processed embeddings and stored in vector database!');
340
+ setTimeout(() => {
341
+ setIsModalOpen(false);
342
+ setCurrentStep('connection');
343
+ setConnectionError(null);
344
+ setIsProcessing(false);
345
+ setProcessingStatus('');
346
+ handleClose();
347
+ }, 2000);
348
+ }, 500);
349
+ } else {
350
+ setProcessingError(result.message || 'Failed to process embeddings');
351
+ setIsProcessing(false);
352
+ }
353
+ } catch (error: any) {
354
+ setProcessingError(error.message || 'An error occurred while processing embeddings');
355
+ setIsProcessing(false);
356
+ }
357
+ } else {
358
+ // If no processing handler, try default API endpoint
359
+ setCurrentStep('processing');
360
+ setIsProcessing(true);
361
+ setProcessingStatus('Fetching data from database...');
362
+ setProcessingError(null);
363
+
364
+ try {
365
+ const response = await fetch('/api/database/process-embeddings', {
366
+ method: 'POST',
367
+ headers: { 'Content-Type': 'application/json' },
368
+ body: JSON.stringify(config),
369
+ });
370
+
371
+ if (!response.ok) {
372
+ const errorData = await response.json().catch(() => ({}));
373
+ throw new Error(errorData.message || `HTTP ${response.status}`);
374
+ }
375
+
376
+ setProcessingStatus('Converting data to embeddings...');
377
+ const data = await response.json();
378
+
379
+ if (data.success) {
380
+ setProcessingStatus('Storing embeddings in vector database...');
381
+ setTimeout(() => {
382
+ setProcessingStatus('✓ Successfully processed embeddings and stored in vector database!');
383
+ setTimeout(() => {
384
+ setIsModalOpen(false);
385
+ setCurrentStep('connection');
386
+ setConnectionError(null);
387
+ setIsProcessing(false);
388
+ setProcessingStatus('');
389
+ handleClose();
390
+ }, 2000);
391
+ }, 500);
392
+ } else {
393
+ setProcessingError(data.message || 'Failed to process embeddings');
394
+ setIsProcessing(false);
395
+ }
396
+ } catch (error: any) {
397
+ if (error.message?.includes('Failed to fetch') || error.message?.includes('404')) {
398
+ // If endpoint doesn't exist, just close modal
399
+ setIsModalOpen(false);
400
+ setCurrentStep('connection');
401
+ setConnectionError(null);
402
+ } else {
403
+ setProcessingError(error.message || 'An error occurred while processing embeddings');
404
+ setIsProcessing(false);
405
+ }
406
+ }
407
+ }
255
408
  };
256
409
 
257
410
  const handleClose = () => {
@@ -259,125 +412,74 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
259
412
  setCurrentStep('connection');
260
413
  setConnectionError(null);
261
414
  setConnectionSuccess(false);
262
- setAvailableColumns([]);
263
- setColumnSelection({
264
- embeddingColumns: [],
265
- llmColumns: [],
266
- chromaColumns: [],
267
- });
415
+ setAvailableCollections([]);
416
+ setSelectedCollections([]);
417
+ setCollectionColumns({});
418
+ setSelectedColumns({});
268
419
  };
269
420
 
270
421
  return (
271
422
  <>
272
- {/* Sidebar Button/Item - This can be integrated into admin sidebar */}
273
423
  <button
274
424
  onClick={() => setIsModalOpen(true)}
275
- className="w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-gray-100 rounded-lg transition-colors"
425
+ className="w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-gray-50 rounded-lg transition-colors text-sm text-gray-700"
276
426
  >
277
- <svg
278
- xmlns="http://www.w3.org/2000/svg"
279
- className="h-5 w-5 text-gray-600"
280
- fill="none"
281
- viewBox="0 0 24 24"
282
- stroke="currentColor"
283
- >
284
- <path
285
- strokeLinecap="round"
286
- strokeLinejoin="round"
287
- strokeWidth={2}
288
- d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
289
- />
427
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
428
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
290
429
  </svg>
291
- <span className="text-sm font-medium text-gray-700">Database Setup</span>
430
+ <span className="font-medium">Database Setup</span>
292
431
  </button>
293
432
 
294
- {/* Modal */}
295
433
  {isModalOpen && (
296
- <div className="fixed inset-0 z-50 flex items-center justify-center bg-black bg-opacity-50">
297
- <div className="bg-white rounded-lg shadow-xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
298
- {/* Modal Header */}
299
- <div className="px-6 py-4 border-b border-gray-200 flex items-center justify-between">
300
- <h2 className="text-xl font-semibold text-gray-900">
301
- {currentStep === 'connection' ? 'Database Connection' : 'Select Columns'}
434
+ <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
435
+ <div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
436
+ {/* Header */}
437
+ <div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
438
+ <h2 className="text-lg font-semibold text-gray-900">
439
+ {currentStep === 'connection' && 'Database Connection'}
440
+ {currentStep === 'collection' && 'Select Collections'}
441
+ {currentStep === 'columns' && 'Select Query Columns'}
442
+ {currentStep === 'processing' && 'Processing Embeddings'}
302
443
  </h2>
303
444
  <button
304
445
  onClick={handleClose}
305
- className="text-gray-400 hover:text-gray-600 transition-colors"
446
+ className="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
306
447
  >
307
- <svg
308
- xmlns="http://www.w3.org/2000/svg"
309
- className="h-6 w-6"
310
- fill="none"
311
- viewBox="0 0 24 24"
312
- stroke="currentColor"
313
- >
314
- <path
315
- strokeLinecap="round"
316
- strokeLinejoin="round"
317
- strokeWidth={2}
318
- d="M6 18L18 6M6 6l12 12"
319
- />
448
+ <svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
449
+ <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
320
450
  </svg>
321
451
  </button>
322
452
  </div>
323
453
 
324
- {/* Modal Content */}
325
- <div className="flex-1 overflow-y-auto px-6 py-4">
326
- {currentStep === 'connection' ? (
327
- <div className="space-y-6">
328
- {/* Database Type Selection */}
454
+ {/* Content */}
455
+ <div className="flex-1 overflow-y-auto px-6 py-6">
456
+ {currentStep === 'connection' && (
457
+ <div className="space-y-5">
458
+ {/* Database Type */}
329
459
  <div>
330
- <label className="block text-sm font-medium text-gray-700 mb-3">
331
- Database Type
332
- </label>
333
- <div className="grid grid-cols-2 gap-4">
460
+ <label className="block text-sm font-medium text-gray-700 mb-2">Database Type</label>
461
+ <div className="flex gap-3">
334
462
  <button
335
463
  onClick={() => handleDbTypeChange('mongodb')}
336
464
  className={clsx(
337
- 'p-4 border-2 rounded-lg transition-all text-left',
465
+ 'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
338
466
  dbType === 'mongodb'
339
- ? 'border-blue-500 bg-blue-50'
340
- : 'border-gray-200 hover:border-gray-300'
467
+ ? 'border-blue-500 bg-blue-50 text-blue-700'
468
+ : 'border-gray-200 text-gray-700 hover:border-gray-300'
341
469
  )}
342
470
  >
343
- <div className="flex items-center gap-3">
344
- <div className={clsx(
345
- 'w-5 h-5 rounded-full border-2 flex items-center justify-center',
346
- dbType === 'mongodb' ? 'border-blue-500' : 'border-gray-300'
347
- )}>
348
- {dbType === 'mongodb' && (
349
- <div className="w-3 h-3 rounded-full bg-blue-500" />
350
- )}
351
- </div>
352
- <div>
353
- <div className="font-semibold text-gray-900">MongoDB</div>
354
- <div className="text-xs text-gray-500">NoSQL Database</div>
355
- </div>
356
- </div>
471
+ MongoDB
357
472
  </button>
358
473
  <button
359
474
  onClick={() => handleDbTypeChange('postgres')}
360
475
  className={clsx(
361
- 'p-4 border-2 rounded-lg transition-all text-left',
476
+ 'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
362
477
  dbType === 'postgres'
363
- ? 'border-blue-500 bg-blue-50'
364
- : 'border-gray-200 hover:border-gray-300'
478
+ ? 'border-blue-500 bg-blue-50 text-blue-700'
479
+ : 'border-gray-200 text-gray-700 hover:border-gray-300'
365
480
  )}
366
481
  >
367
- <div className="flex items-center gap-3">
368
- <div className={clsx(
369
- 'w-5 h-5 rounded-full border-2 flex items-center justify-center',
370
- dbType === 'postgres' ? 'border-blue-500' : 'border-gray-300'
371
- )}>
372
- {dbType === 'postgres' && (
373
- <div className="w-3 h-3 rounded-full bg-blue-500" />
374
- )}
375
- </div>
376
- <div>
377
- <div className="font-semibold text-gray-900">PostgreSQL</div>
378
- <div className="text-xs text-gray-500">SQL Database</div>
379
- </div>
380
- </div>
482
+ PostgreSQL
381
483
  </button>
382
484
  </div>
383
485
  </div>
@@ -386,320 +488,323 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
386
488
  {dbType === 'mongodb' ? (
387
489
  <div className="space-y-4">
388
490
  <div>
389
- <label className="block text-sm font-medium text-gray-700 mb-2">
390
- Connection String (Recommended)
391
- </label>
491
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Connection String</label>
392
492
  <input
393
493
  type="text"
394
494
  value={connection.connectionString || ''}
395
495
  onChange={(e) => handleConnectionChange('connectionString', e.target.value)}
396
- placeholder="mongodb://username:password@host:port/database"
397
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
496
+ placeholder="mongodb://host:port/database"
497
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
398
498
  />
399
- <p className="mt-1 text-xs text-gray-500">
400
- Or fill individual fields below
401
- </p>
402
499
  </div>
403
- <div className="border-t border-gray-200 pt-4">
404
- <div className="grid grid-cols-2 gap-4">
405
- <div>
406
- <label className="block text-sm font-medium text-gray-700 mb-2">
407
- Host *
408
- </label>
409
- <input
410
- type="text"
411
- value={connection.host}
412
- onChange={(e) => handleConnectionChange('host', e.target.value)}
413
- placeholder="localhost"
414
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
415
- />
416
- </div>
417
- <div>
418
- <label className="block text-sm font-medium text-gray-700 mb-2">
419
- Port *
420
- </label>
421
- <input
422
- type="number"
423
- value={connection.port}
424
- onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 27017)}
425
- placeholder="27017"
426
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
427
- />
428
- </div>
429
- </div>
430
- <div className="mt-4">
431
- <label className="block text-sm font-medium text-gray-700 mb-2">
432
- Database Name *
433
- </label>
500
+ <div className="text-xs text-gray-500 text-center">or</div>
501
+ <div className="grid grid-cols-2 gap-3">
502
+ <div>
503
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
434
504
  <input
435
505
  type="text"
436
- value={connection.database}
437
- onChange={(e) => handleConnectionChange('database', e.target.value)}
438
- placeholder="my_database"
439
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
506
+ value={connection.host}
507
+ onChange={(e) => handleConnectionChange('host', e.target.value)}
508
+ placeholder="localhost"
509
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
440
510
  />
441
511
  </div>
442
- <div className="mt-4">
443
- <label className="flex items-center gap-2">
444
- <input
445
- type="checkbox"
446
- checked={connection.ssl || false}
447
- onChange={(e) => handleConnectionChange('ssl', e.target.checked)}
448
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
449
- />
450
- <span className="text-sm text-gray-700">Enable SSL</span>
451
- </label>
512
+ <div>
513
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
514
+ <input
515
+ type="number"
516
+ value={connection.port}
517
+ onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 27017)}
518
+ placeholder="27017"
519
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
520
+ />
452
521
  </div>
453
522
  </div>
523
+ <div>
524
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
525
+ <input
526
+ type="text"
527
+ value={connection.database}
528
+ onChange={(e) => handleConnectionChange('database', e.target.value)}
529
+ placeholder="database_name"
530
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
531
+ />
532
+ </div>
454
533
  </div>
455
534
  ) : (
456
535
  <div className="space-y-4">
457
- <div className="grid grid-cols-2 gap-4">
536
+ <div className="grid grid-cols-2 gap-3">
458
537
  <div>
459
- <label className="block text-sm font-medium text-gray-700 mb-2">
460
- Host *
461
- </label>
538
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
462
539
  <input
463
540
  type="text"
464
541
  value={connection.host}
465
542
  onChange={(e) => handleConnectionChange('host', e.target.value)}
466
543
  placeholder="localhost"
467
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
544
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
468
545
  />
469
546
  </div>
470
547
  <div>
471
- <label className="block text-sm font-medium text-gray-700 mb-2">
472
- Port *
473
- </label>
548
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
474
549
  <input
475
550
  type="number"
476
551
  value={connection.port}
477
552
  onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 5432)}
478
553
  placeholder="5432"
479
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
554
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
480
555
  />
481
556
  </div>
482
557
  </div>
483
558
  <div>
484
- <label className="block text-sm font-medium text-gray-700 mb-2">
485
- Database Name *
486
- </label>
559
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
487
560
  <input
488
561
  type="text"
489
562
  value={connection.database}
490
563
  onChange={(e) => handleConnectionChange('database', e.target.value)}
491
- placeholder="my_database"
492
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
564
+ placeholder="database_name"
565
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
493
566
  />
494
567
  </div>
495
- <div className="grid grid-cols-2 gap-4">
568
+ <div className="grid grid-cols-2 gap-3">
496
569
  <div>
497
- <label className="block text-sm font-medium text-gray-700 mb-2">
498
- Username *
499
- </label>
570
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Username</label>
500
571
  <input
501
572
  type="text"
502
573
  value={connection.username || ''}
503
574
  onChange={(e) => handleConnectionChange('username', e.target.value)}
504
- placeholder="postgres"
505
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
575
+ placeholder="username"
576
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
506
577
  />
507
578
  </div>
508
579
  <div>
509
- <label className="block text-sm font-medium text-gray-700 mb-2">
510
- Password *
511
- </label>
580
+ <label className="block text-sm font-medium text-gray-700 mb-1.5">Password</label>
512
581
  <input
513
582
  type="password"
514
583
  value={connection.password || ''}
515
584
  onChange={(e) => handleConnectionChange('password', e.target.value)}
516
- placeholder="••••••••"
517
- className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
585
+ placeholder="••••••"
586
+ className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
518
587
  />
519
588
  </div>
520
589
  </div>
521
- <div>
522
- <label className="flex items-center gap-2">
523
- <input
524
- type="checkbox"
525
- checked={connection.ssl || false}
526
- onChange={(e) => handleConnectionChange('ssl', e.target.checked)}
527
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
528
- />
529
- <span className="text-sm text-gray-700">Enable SSL</span>
530
- </label>
531
- </div>
532
590
  </div>
533
591
  )}
534
592
 
535
593
  {connectionError && (
536
- <div className="bg-red-50 border border-red-200 rounded-lg p-4">
537
- <p className="text-sm text-red-800">{connectionError}</p>
594
+ <div className="bg-red-50 border border-red-200 rounded-lg p-3">
595
+ <p className="text-sm text-red-700">{connectionError}</p>
538
596
  </div>
539
597
  )}
540
598
  {connectionSuccess && !connectionError && !isConnecting && (
541
- <div className="bg-green-50 border border-green-200 rounded-lg p-4">
542
- <p className="text-sm text-green-800">✓ Connection successful!</p>
599
+ <div className="bg-green-50 border border-green-200 rounded-lg p-3">
600
+ <p className="text-sm text-green-700">✓ Connection successful</p>
543
601
  </div>
544
602
  )}
545
603
  </div>
546
- ) : (
547
- <div className="space-y-6">
548
- {isLoadingColumns ? (
549
- <div className="flex flex-col items-center justify-center py-8">
550
- <div className="animate-spin rounded-full h-8 w-8 border-b-2 border-blue-600 mb-3"></div>
551
- <p className="text-sm text-gray-600">Loading columns...</p>
552
- </div>
553
- ) : availableColumns.length === 0 ? (
554
- <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-4">
555
- <p className="text-sm text-yellow-800">No columns available. Please go back and check your connection.</p>
556
- </div>
557
- ) : (
558
- <>
559
- <div>
560
- <p className="text-sm text-gray-600 mb-2">
561
- Select which columns to use for <strong>Embeddings</strong>, <strong>LLM processing</strong>, and <strong>ChromaDB storage</strong>.
562
- </p>
563
- <p className="text-xs text-gray-500 mb-4">
564
- Found {availableColumns.length} column{availableColumns.length !== 1 ? 's' : ''} in your database.
604
+ )}
605
+
606
+ {currentStep === 'collection' && (
607
+ <div className="space-y-4">
608
+ <div>
609
+ <p className="text-sm text-gray-600 mb-4">Select one or more collections to configure for embeddings:</p>
610
+ {isLoadingCollections ? (
611
+ <div className="flex items-center justify-center py-8">
612
+ <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
613
+ </div>
614
+ ) : (
615
+ <div className="space-y-2 max-h-96 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50/50">
616
+ {availableCollections.map((collection) => {
617
+ const isSelected = selectedCollections.includes(collection);
618
+ const isLoading = loadingColumnsFor.includes(collection);
619
+ const hasColumns = collectionColumns[collection]?.length > 0;
620
+
621
+ return (
622
+ <label
623
+ key={collection}
624
+ className="flex items-center gap-3 cursor-pointer hover:bg-white p-3 rounded-lg border border-gray-200 transition-colors"
625
+ >
626
+ <input
627
+ type="checkbox"
628
+ checked={isSelected}
629
+ onChange={() => handleCollectionToggle(collection)}
630
+ className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
631
+ />
632
+ <div className="flex-1">
633
+ <span className="text-sm font-medium text-gray-900">{collection}</span>
634
+ {isLoading && (
635
+ <span className="ml-2 text-xs text-gray-500">Loading columns...</span>
636
+ )}
637
+ {hasColumns && !isLoading && (
638
+ <span className="ml-2 text-xs text-green-600">
639
+ ({collectionColumns[collection].length} columns)
640
+ </span>
641
+ )}
642
+ </div>
643
+ </label>
644
+ );
645
+ })}
646
+ </div>
647
+ )}
648
+ {selectedCollections.length > 0 && (
649
+ <div className="mt-4 p-3 bg-blue-50 border border-blue-200 rounded-lg">
650
+ <p className="text-sm text-blue-700">
651
+ {selectedCollections.length} collection{selectedCollections.length !== 1 ? 's' : ''} selected
565
652
  </p>
566
653
  </div>
654
+ )}
655
+ </div>
656
+ {connectionError && (
657
+ <div className="bg-red-50 border border-red-200 rounded-lg p-3">
658
+ <p className="text-sm text-red-700">{connectionError}</p>
659
+ </div>
660
+ )}
661
+ </div>
662
+ )}
567
663
 
568
- {/* Embedding Columns */}
569
- <div>
570
- <label className="block text-sm font-medium text-gray-700 mb-2">
571
- Works with Embeddings
572
- </label>
573
- <p className="text-xs text-gray-500 mb-3">Select columns that will be used for embedding generation</p>
574
- <div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
575
- {availableColumns.length === 0 ? (
576
- <p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
577
- ) : (
578
- availableColumns.map((column) => (
579
- <label
580
- key={`embedding-${column}`}
581
- className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
582
- >
583
- <input
584
- type="checkbox"
585
- checked={columnSelection.embeddingColumns.includes(column)}
586
- onChange={() => handleColumnToggle(column, 'embeddingColumns')}
587
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
588
- />
589
- <span className="text-sm text-gray-700">{column}</span>
590
- </label>
591
- ))
592
- )}
593
- </div>
594
- {columnSelection.embeddingColumns.length > 0 && (
595
- <p className="text-xs text-green-600 mt-1">
596
- {columnSelection.embeddingColumns.length} column{columnSelection.embeddingColumns.length !== 1 ? 's' : ''} selected
597
- </p>
598
- )}
599
- </div>
664
+ {currentStep === 'columns' && (
665
+ <div className="space-y-6">
666
+ <div>
667
+ <p className="text-sm text-gray-600 mb-1">
668
+ Select columns that the chatbot will query from {selectedCollections.length} collection{selectedCollections.length !== 1 ? 's' : ''}:
669
+ </p>
670
+ <p className="text-xs text-gray-500">
671
+ These columns will be used to fetch data, convert to embeddings, and store in vector database
672
+ </p>
673
+ </div>
600
674
 
601
- {/* LLM Columns */}
602
- <div>
603
- <label className="block text-sm font-medium text-gray-700 mb-2">
604
- Works with LLM
605
- </label>
606
- <p className="text-xs text-gray-500 mb-3">Select columns that will be processed by the LLM</p>
607
- <div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
608
- {availableColumns.length === 0 ? (
609
- <p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
610
- ) : (
611
- availableColumns.map((column) => (
675
+ {selectedCollections.map((collection) => {
676
+ const columns = collectionColumns[collection] || [];
677
+ const selectedCols = selectedColumns[collection] || [];
678
+
679
+ return (
680
+ <div key={collection} className="border border-gray-200 rounded-lg p-4 bg-gray-50/50">
681
+ <div className="mb-3">
682
+ <h3 className="text-sm font-semibold text-gray-900 mb-1">{collection}</h3>
683
+ <p className="text-xs text-gray-500">
684
+ {columns.length} column{columns.length !== 1 ? 's' : ''} available
685
+ {selectedCols.length > 0 && (
686
+ <span className="text-green-600 ml-2">
687
+ • {selectedCols.length} selected
688
+ </span>
689
+ )}
690
+ </p>
691
+ </div>
692
+ {columns.length > 0 ? (
693
+ <div className="space-y-2 max-h-48 overflow-y-auto">
694
+ {columns.map((column) => (
612
695
  <label
613
- key={`llm-${column}`}
696
+ key={column}
614
697
  className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
615
698
  >
616
699
  <input
617
700
  type="checkbox"
618
- checked={columnSelection.llmColumns.includes(column)}
619
- onChange={() => handleColumnToggle(column, 'llmColumns')}
701
+ checked={selectedCols.includes(column)}
702
+ onChange={() => handleColumnToggle(collection, column)}
620
703
  className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
621
704
  />
622
705
  <span className="text-sm text-gray-700">{column}</span>
623
706
  </label>
624
- ))
625
- )}
626
- </div>
627
- {columnSelection.llmColumns.length > 0 && (
628
- <p className="text-xs text-green-600 mt-1">
629
- {columnSelection.llmColumns.length} column{columnSelection.llmColumns.length !== 1 ? 's' : ''} selected
630
- </p>
707
+ ))}
708
+ </div>
709
+ ) : (
710
+ <p className="text-sm text-gray-500">No columns available</p>
631
711
  )}
632
712
  </div>
713
+ );
714
+ })}
633
715
 
634
- {/* ChromaDB Columns */}
635
- <div>
636
- <label className="block text-sm font-medium text-gray-700 mb-2">
637
- Works with ChromaDB
638
- </label>
639
- <p className="text-xs text-gray-500 mb-3">Select columns that will be stored in ChromaDB</p>
640
- <div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
641
- {availableColumns.length === 0 ? (
642
- <p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
643
- ) : (
644
- availableColumns.map((column) => (
645
- <label
646
- key={`chroma-${column}`}
647
- className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
648
- >
649
- <input
650
- type="checkbox"
651
- checked={columnSelection.chromaColumns.includes(column)}
652
- onChange={() => handleColumnToggle(column, 'chromaColumns')}
653
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
654
- />
655
- <span className="text-sm text-gray-700">{column}</span>
656
- </label>
657
- ))
658
- )}
716
+ {connectionError && (
717
+ <div className="bg-red-50 border border-red-200 rounded-lg p-3">
718
+ <p className="text-sm text-red-700">{connectionError}</p>
719
+ </div>
720
+ )}
721
+ </div>
722
+ )}
723
+
724
+ {currentStep === 'processing' && (
725
+ <div className="space-y-6">
726
+ <div className="flex flex-col items-center justify-center py-12">
727
+ <div className="animate-spin rounded-full h-12 w-12 border-b-2 border-blue-600 mb-4"></div>
728
+ <p className="text-sm font-medium text-gray-900 mb-2">Processing Embeddings</p>
729
+ <p className="text-xs text-gray-600 text-center max-w-sm">{processingStatus}</p>
730
+
731
+ {processingStatus && (
732
+ <div className="mt-4 w-full max-w-sm">
733
+ <div className="flex items-center gap-2 text-xs text-gray-500">
734
+ <div className="flex-1 h-1 bg-gray-200 rounded-full overflow-hidden">
735
+ <div className="h-full bg-blue-600 rounded-full animate-pulse" style={{ width: '60%' }}></div>
736
+ </div>
659
737
  </div>
660
- {columnSelection.chromaColumns.length > 0 && (
661
- <p className="text-xs text-green-600 mt-1">
662
- {columnSelection.chromaColumns.length} column{columnSelection.chromaColumns.length !== 1 ? 's' : ''} selected
663
- </p>
664
- )}
665
738
  </div>
666
- </>
739
+ )}
740
+ </div>
741
+
742
+ {processingError && (
743
+ <div className="bg-red-50 border border-red-200 rounded-lg p-4">
744
+ <p className="text-sm text-red-700 mb-2">Error processing embeddings:</p>
745
+ <p className="text-xs text-red-600">{processingError}</p>
746
+ </div>
747
+ )}
748
+
749
+ {processingStatus.includes('✓') && (
750
+ <div className="bg-green-50 border border-green-200 rounded-lg p-4">
751
+ <p className="text-sm text-green-700">{processingStatus}</p>
752
+ </div>
667
753
  )}
668
754
  </div>
669
755
  )}
670
756
  </div>
671
757
 
672
- {/* Modal Footer */}
673
- <div className="px-6 py-4 border-t border-gray-200 flex items-center justify-between">
758
+ {/* Footer */}
759
+ <div className="px-6 py-4 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between">
674
760
  <button
675
- onClick={currentStep === 'columns' ? () => setCurrentStep('connection') : handleClose}
676
- className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
761
+ onClick={currentStep === 'connection' ? handleClose : currentStep === 'processing' ? undefined : () => {
762
+ if (currentStep === 'columns') {
763
+ setCurrentStep('collection');
764
+ } else {
765
+ setCurrentStep('connection');
766
+ }
767
+ }}
768
+ disabled={currentStep === 'processing'}
769
+ className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
677
770
  >
678
- {currentStep === 'columns' ? 'Back' : 'Cancel'}
771
+ {currentStep === 'connection' ? 'Cancel' : currentStep === 'processing' ? 'Processing...' : 'Back'}
679
772
  </button>
680
773
  <div className="flex gap-3">
681
- {currentStep === 'connection' ? (
774
+ {currentStep === 'connection' && (
682
775
  <button
683
- onClick={handleConnectAndNext}
684
- disabled={isConnecting || isLoadingColumns}
776
+ onClick={handleConnect}
777
+ disabled={isConnecting || isLoadingCollections}
685
778
  className="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
686
779
  >
687
- {isConnecting && (
688
- <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>
689
- )}
690
- {isLoadingColumns ? 'Loading Columns...' : isConnecting ? 'Connecting...' : 'Connect & Next'}
780
+ {isConnecting && <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>}
781
+ {isConnecting ? 'Connecting...' : 'Connect'}
691
782
  </button>
692
- ) : (
783
+ )}
784
+ {currentStep === 'collection' && (
785
+ <button
786
+ onClick={handleNextToColumns}
787
+ disabled={selectedCollections.length === 0}
788
+ className="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
789
+ >
790
+ Next
791
+ </button>
792
+ )}
793
+ {currentStep === 'columns' && (
693
794
  <button
694
795
  onClick={handleSave}
695
- disabled={
696
- columnSelection.embeddingColumns.length === 0 &&
697
- columnSelection.llmColumns.length === 0 &&
698
- columnSelection.chromaColumns.length === 0
699
- }
796
+ disabled={Object.values(selectedColumns).every(cols => cols.length === 0)}
700
797
  className="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors"
701
798
  >
702
- Save Configuration
799
+ Save & Process
800
+ </button>
801
+ )}
802
+ {currentStep === 'processing' && processingError && (
803
+ <button
804
+ onClick={() => setCurrentStep('columns')}
805
+ className="px-6 py-2 bg-gray-600 text-white text-sm font-medium rounded-lg hover:bg-gray-700 transition-colors"
806
+ >
807
+ Go Back
703
808
  </button>
704
809
  )}
705
810
  </div>
package/index.tsx CHANGED
@@ -2,4 +2,4 @@ export { default as Chatbot } from './components/Chatbot';
2
2
  export type { ChatbotConfig, Message, ChatbotProps } from './types';
3
3
 
4
4
  export { default as AdminSetup } from './components/AdminSetup';
5
- export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, AdminSetupProps } from './types/admin';
5
+ export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, CollectionColumnMapping, AdminSetupProps } from './types/admin';
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "nextjs-chatbot-ui",
3
3
 
4
- "version": "1.2.0",
4
+ "version": "1.4.0",
5
5
  "description": "A configurable chatbot UI component for Next.js with Tailwind CSS",
6
6
  "main": "./index.tsx",
7
7
  "module": "./index.tsx",
package/types/admin.ts CHANGED
@@ -1,35 +1,43 @@
1
- export type DatabaseType = 'mongodb' | 'postgres';
2
-
3
- export interface DatabaseConnection {
4
- type: DatabaseType;
5
- host: string;
6
- port: number;
7
- database: string;
8
- username?: string;
9
- password?: string;
10
- connectionString?: string; // For MongoDB connection string
11
- ssl?: boolean;
12
- }
13
-
14
- export interface ColumnSelection {
15
- embeddingColumns: string[];
16
- llmColumns: string[];
17
- chromaColumns: string[];
18
- }
19
-
20
- export interface DatabaseConfig {
21
- connection: DatabaseConnection;
22
- columnSelection: ColumnSelection;
23
- }
24
-
25
- export interface AdminSetupProps {
26
- onSave?: (config: DatabaseConfig) => void;
27
- onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
28
- onFetchColumns?: (connection: DatabaseConnection) => Promise<string[]>;
29
- }
30
-
31
- // Backend request format
32
- export interface BackendConnectionRequest {
33
- databaseType: 'mongodb' | 'postgresql';
34
- connection: Omit<DatabaseConnection, 'type'>;
1
+ export type DatabaseType = 'mongodb' | 'postgres';
2
+
3
+ export interface DatabaseConnection {
4
+ type: DatabaseType;
5
+ host: string;
6
+ port: number;
7
+ database: string;
8
+ username?: string;
9
+ password?: string;
10
+ connectionString?: string; // For MongoDB connection string
11
+ ssl?: boolean;
12
+ }
13
+
14
+ export interface ColumnSelection {
15
+ embeddingColumns: string[];
16
+ llmColumns: string[];
17
+ chromaColumns: string[];
18
+ }
19
+
20
+ export interface CollectionColumnMapping {
21
+ collection: string;
22
+ columns: string[];
23
+ }
24
+
25
+ export interface DatabaseConfig {
26
+ connection: DatabaseConnection;
27
+ columnSelection: ColumnSelection;
28
+ collections?: CollectionColumnMapping[]; // Optional: for multiple collections
29
+ }
30
+
31
+ export interface AdminSetupProps {
32
+ onSave?: (config: DatabaseConfig) => void;
33
+ onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
34
+ onFetchCollections?: (connection: DatabaseConnection) => Promise<string[]>; // For MongoDB collections or PostgreSQL tables
35
+ onFetchColumns?: (connection: DatabaseConnection, collection?: string) => Promise<string[]>; // collection/table name optional
36
+ onProcessEmbeddings?: (config: DatabaseConfig) => Promise<{ success: boolean; message?: string }>; // Process: fetch data -> convert to embeddings -> store in vector DB
37
+ }
38
+
39
+ // Backend request format
40
+ export interface BackendConnectionRequest {
41
+ databaseType: 'mongodb' | 'postgresql';
42
+ connection: Omit<DatabaseConnection, 'type'>;
35
43
  }