nextjs-chatbot-ui 1.3.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,7 +1,7 @@
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
 
@@ -10,17 +10,22 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
10
10
  onTestConnection,
11
11
  onFetchCollections,
12
12
  onFetchColumns,
13
+ onProcessEmbeddings,
13
14
  }) => {
14
15
  const [isModalOpen, setIsModalOpen] = useState(false);
15
- const [currentStep, setCurrentStep] = useState<'connection' | 'collection' | 'columns'>('connection');
16
+ const [currentStep, setCurrentStep] = useState<'connection' | 'collection' | 'columns' | 'processing'>('connection');
16
17
  const [isConnecting, setIsConnecting] = useState(false);
17
18
  const [connectionError, setConnectionError] = useState<string | null>(null);
18
19
  const [connectionSuccess, setConnectionSuccess] = useState(false);
19
20
  const [availableCollections, setAvailableCollections] = useState<string[]>([]);
20
- const [selectedCollection, setSelectedCollection] = useState<string>('');
21
- const [availableColumns, setAvailableColumns] = useState<string[]>([]);
21
+ const [selectedCollections, setSelectedCollections] = useState<string[]>([]);
22
+ const [collectionColumns, setCollectionColumns] = useState<Record<string, string[]>>({});
23
+ const [selectedColumns, setSelectedColumns] = useState<Record<string, string[]>>({});
22
24
  const [isLoadingCollections, setIsLoadingCollections] = useState(false);
23
- const [isLoadingColumns, setIsLoadingColumns] = 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);
24
29
 
25
30
  const [dbType, setDbType] = useState<DatabaseType>('mongodb');
26
31
  const [connection, setConnection] = useState<DatabaseConnection>({
@@ -34,12 +39,6 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
34
39
  ssl: false,
35
40
  });
36
41
 
37
- const [columnSelection, setColumnSelection] = useState<ColumnSelection>({
38
- embeddingColumns: [],
39
- llmColumns: [],
40
- chromaColumns: [],
41
- });
42
-
43
42
  const handleDbTypeChange = (type: DatabaseType) => {
44
43
  setDbType(type);
45
44
  setConnection({
@@ -165,8 +164,8 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
165
164
  }
166
165
  };
167
166
 
168
- const handleFetchColumns = async (collectionName?: string): Promise<string[]> => {
169
- setIsLoadingColumns(true);
167
+ const handleFetchColumns = async (collectionName: string): Promise<string[]> => {
168
+ setLoadingColumnsFor(prev => [...prev, collectionName]);
170
169
  setConnectionError(null);
171
170
 
172
171
  try {
@@ -176,7 +175,7 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
176
175
  let columns: string[];
177
176
 
178
177
  if (onFetchColumns) {
179
- columns = await onFetchColumns(normalizedConnection, collectionName || selectedCollection);
178
+ columns = await onFetchColumns(normalizedConnection, collectionName);
180
179
  } else {
181
180
  try {
182
181
  const response = await fetch('/api/database/columns', {
@@ -184,7 +183,7 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
184
183
  headers: { 'Content-Type': 'application/json' },
185
184
  body: JSON.stringify({
186
185
  ...backendRequest,
187
- collection: collectionName || selectedCollection,
186
+ collection: collectionName,
188
187
  }),
189
188
  });
190
189
 
@@ -204,17 +203,18 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
204
203
  }
205
204
 
206
205
  if (columns && columns.length > 0) {
207
- setAvailableColumns(columns);
208
- setIsLoadingColumns(false);
206
+ setCollectionColumns(prev => ({ ...prev, [collectionName]: columns }));
207
+ setSelectedColumns(prev => ({ ...prev, [collectionName]: [] }));
208
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
209
209
  return columns;
210
210
  } else {
211
- setConnectionError('No columns found in the selected collection.');
212
- setIsLoadingColumns(false);
211
+ setConnectionError(`No columns found in ${collectionName}.`);
212
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
213
213
  return [];
214
214
  }
215
215
  } catch (error: any) {
216
216
  setConnectionError(error.message || 'Failed to fetch columns.');
217
- setIsLoadingColumns(false);
217
+ setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
218
218
  console.error('Fetch columns error:', error);
219
219
  return [];
220
220
  }
@@ -246,41 +246,165 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
246
246
  }
247
247
  };
248
248
 
249
- const handleCollectionSelect = async (collection: string) => {
250
- setSelectedCollection(collection);
251
- const columns = await handleFetchColumns(collection);
252
- if (columns && columns.length > 0) {
253
- setCurrentStep('columns');
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
+ });
263
+ } else {
264
+ // Select collection and fetch its columns
265
+ setSelectedCollections(prev => [...prev, collection]);
266
+ await handleFetchColumns(collection);
254
267
  }
255
268
  };
256
269
 
257
- const handleColumnToggle = (column: string, category: keyof ColumnSelection) => {
258
- setColumnSelection((prev) => {
259
- const currentColumns = prev[category];
260
- 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);
261
274
 
262
275
  return {
263
276
  ...prev,
264
- [category]: isSelected
265
- ? currentColumns.filter((c) => c !== column)
266
- : [...currentColumns, column],
277
+ [collection]: isSelected
278
+ ? collectionCols.filter(c => c !== column)
279
+ : [...collectionCols, column],
267
280
  };
268
281
  });
269
282
  };
270
283
 
271
- 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
+
272
308
  const config: DatabaseConfig = {
273
309
  connection,
274
- columnSelection,
310
+ columnSelection: {
311
+ embeddingColumns: allQueryColumns,
312
+ llmColumns: [],
313
+ chromaColumns: [],
314
+ },
315
+ collections,
275
316
  };
276
317
 
318
+ // Save configuration first
277
319
  if (onSave) {
278
320
  onSave(config);
279
321
  }
280
-
281
- setIsModalOpen(false);
282
- setCurrentStep('connection');
283
- 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
+ }
284
408
  };
285
409
 
286
410
  const handleClose = () => {
@@ -289,13 +413,9 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
289
413
  setConnectionError(null);
290
414
  setConnectionSuccess(false);
291
415
  setAvailableCollections([]);
292
- setSelectedCollection('');
293
- setAvailableColumns([]);
294
- setColumnSelection({
295
- embeddingColumns: [],
296
- llmColumns: [],
297
- chromaColumns: [],
298
- });
416
+ setSelectedCollections([]);
417
+ setCollectionColumns({});
418
+ setSelectedColumns({});
299
419
  };
300
420
 
301
421
  return (
@@ -312,13 +432,14 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
312
432
 
313
433
  {isModalOpen && (
314
434
  <div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
315
- <div className="bg-white rounded-xl shadow-2xl w-full max-w-lg max-h-[90vh] overflow-hidden flex flex-col">
435
+ <div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
316
436
  {/* Header */}
317
437
  <div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
318
438
  <h2 className="text-lg font-semibold text-gray-900">
319
439
  {currentStep === 'connection' && 'Database Connection'}
320
- {currentStep === 'collection' && 'Select Collection'}
321
- {currentStep === 'columns' && 'Configure Columns'}
440
+ {currentStep === 'collection' && 'Select Collections'}
441
+ {currentStep === 'columns' && 'Select Query Columns'}
442
+ {currentStep === 'processing' && 'Processing Embeddings'}
322
443
  </h2>
323
444
  <button
324
445
  onClick={handleClose}
@@ -485,24 +606,51 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
485
606
  {currentStep === 'collection' && (
486
607
  <div className="space-y-4">
487
608
  <div>
488
- <p className="text-sm text-gray-600 mb-4">Select a collection to configure for embeddings:</p>
609
+ <p className="text-sm text-gray-600 mb-4">Select one or more collections to configure for embeddings:</p>
489
610
  {isLoadingCollections ? (
490
611
  <div className="flex items-center justify-center py-8">
491
612
  <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
492
613
  </div>
493
614
  ) : (
494
- <select
495
- value={selectedCollection}
496
- onChange={(e) => handleCollectionSelect(e.target.value)}
497
- className="w-full px-3 py-2.5 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 bg-white"
498
- >
499
- <option value="">Select a collection...</option>
500
- {availableCollections.map((collection) => (
501
- <option key={collection} value={collection}>
502
- {collection}
503
- </option>
504
- ))}
505
- </select>
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
652
+ </p>
653
+ </div>
506
654
  )}
507
655
  </div>
508
656
  {connectionError && (
@@ -514,46 +662,56 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
514
662
  )}
515
663
 
516
664
  {currentStep === 'columns' && (
517
- <div className="space-y-5">
665
+ <div className="space-y-6">
518
666
  <div>
519
- <p className="text-sm text-gray-600 mb-1">Collection: <span className="font-medium text-gray-900">{selectedCollection}</span></p>
520
- <p className="text-xs text-gray-500">Select columns for embeddings</p>
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>
521
673
  </div>
522
674
 
523
- {isLoadingColumns ? (
524
- <div className="flex items-center justify-center py-8">
525
- <div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
526
- </div>
527
- ) : availableColumns.length === 0 ? (
528
- <div className="bg-yellow-50 border border-yellow-200 rounded-lg p-3">
529
- <p className="text-sm text-yellow-700">No columns found.</p>
530
- </div>
531
- ) : (
532
- <div>
533
- <label className="block text-sm font-medium text-gray-700 mb-3">Embedding Columns</label>
534
- <div className="space-y-2 max-h-64 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50/50">
535
- {availableColumns.map((column) => (
536
- <label
537
- key={column}
538
- className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
539
- >
540
- <input
541
- type="checkbox"
542
- checked={columnSelection.embeddingColumns.includes(column)}
543
- onChange={() => handleColumnToggle(column, 'embeddingColumns')}
544
- className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
545
- />
546
- <span className="text-sm text-gray-700">{column}</span>
547
- </label>
548
- ))}
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) => (
695
+ <label
696
+ key={column}
697
+ className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
698
+ >
699
+ <input
700
+ type="checkbox"
701
+ checked={selectedCols.includes(column)}
702
+ onChange={() => handleColumnToggle(collection, column)}
703
+ className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
704
+ />
705
+ <span className="text-sm text-gray-700">{column}</span>
706
+ </label>
707
+ ))}
708
+ </div>
709
+ ) : (
710
+ <p className="text-sm text-gray-500">No columns available</p>
711
+ )}
549
712
  </div>
550
- {columnSelection.embeddingColumns.length > 0 && (
551
- <p className="text-xs text-green-600 mt-2">
552
- {columnSelection.embeddingColumns.length} column{columnSelection.embeddingColumns.length !== 1 ? 's' : ''} selected
553
- </p>
554
- )}
555
- </div>
556
- )}
713
+ );
714
+ })}
557
715
 
558
716
  {connectionError && (
559
717
  <div className="bg-red-50 border border-red-200 rounded-lg p-3">
@@ -562,22 +720,55 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
562
720
  )}
563
721
  </div>
564
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>
737
+ </div>
738
+ </div>
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>
753
+ )}
754
+ </div>
755
+ )}
565
756
  </div>
566
757
 
567
758
  {/* Footer */}
568
759
  <div className="px-6 py-4 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between">
569
760
  <button
570
- onClick={currentStep === 'connection' ? handleClose : () => {
761
+ onClick={currentStep === 'connection' ? handleClose : currentStep === 'processing' ? undefined : () => {
571
762
  if (currentStep === 'columns') {
572
763
  setCurrentStep('collection');
573
- setAvailableColumns([]);
574
764
  } else {
575
765
  setCurrentStep('connection');
576
766
  }
577
767
  }}
578
- className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
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"
579
770
  >
580
- {currentStep === 'connection' ? 'Cancel' : 'Back'}
771
+ {currentStep === 'connection' ? 'Cancel' : currentStep === 'processing' ? 'Processing...' : 'Back'}
581
772
  </button>
582
773
  <div className="flex gap-3">
583
774
  {currentStep === 'connection' && (
@@ -590,13 +781,30 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
590
781
  {isConnecting ? 'Connecting...' : 'Connect'}
591
782
  </button>
592
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
+ )}
593
793
  {currentStep === 'columns' && (
594
794
  <button
595
795
  onClick={handleSave}
596
- disabled={columnSelection.embeddingColumns.length === 0}
796
+ disabled={Object.values(selectedColumns).every(cols => cols.length === 0)}
597
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"
598
798
  >
599
- 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
600
808
  </button>
601
809
  )}
602
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.3.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
@@ -17,9 +17,15 @@ export interface ColumnSelection {
17
17
  chromaColumns: string[];
18
18
  }
19
19
 
20
+ export interface CollectionColumnMapping {
21
+ collection: string;
22
+ columns: string[];
23
+ }
24
+
20
25
  export interface DatabaseConfig {
21
26
  connection: DatabaseConnection;
22
27
  columnSelection: ColumnSelection;
28
+ collections?: CollectionColumnMapping[]; // Optional: for multiple collections
23
29
  }
24
30
 
25
31
  export interface AdminSetupProps {
@@ -27,6 +33,7 @@ export interface AdminSetupProps {
27
33
  onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
28
34
  onFetchCollections?: (connection: DatabaseConnection) => Promise<string[]>; // For MongoDB collections or PostgreSQL tables
29
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
30
37
  }
31
38
 
32
39
  // Backend request format
@@ -1,40 +1,40 @@
1
- import { DatabaseConnection, DatabaseType } from '../types/admin';
2
-
3
- export interface BackendConnectionRequest {
4
- databaseType: 'mongodb' | 'postgresql';
5
- connection: Omit<DatabaseConnection, 'type'>;
6
- }
7
-
8
- /**
9
- * Normalizes connection data with defaults
10
- */
11
- export function normalizeConnection(
12
- connection: DatabaseConnection,
13
- dbType: DatabaseType
14
- ): DatabaseConnection {
15
- return {
16
- ...connection,
17
- port: connection.port || (dbType === 'mongodb' ? 27017 : 5432),
18
- host: connection.host || 'localhost',
19
- database: connection.database || '',
20
- };
21
- }
22
-
23
- /**
24
- * Transforms DatabaseConnection to backend format
25
- */
26
- export function transformConnectionForBackend(
27
- connection: DatabaseConnection,
28
- dbType: DatabaseType
29
- ): BackendConnectionRequest {
30
- const normalized = normalizeConnection(connection, dbType);
31
-
32
- const backendConnection = { ...normalized };
33
- // Remove type from connection object
34
- delete (backendConnection as any).type;
35
-
36
- return {
37
- databaseType: dbType === 'postgres' ? 'postgresql' : 'mongodb',
38
- connection: backendConnection,
39
- };
40
- }
1
+ import { DatabaseConnection, DatabaseType } from '../types/admin';
2
+
3
+ export interface BackendConnectionRequest {
4
+ databaseType: 'mongodb' | 'postgresql';
5
+ connection: Omit<DatabaseConnection, 'type'>;
6
+ }
7
+
8
+ /**
9
+ * Normalizes connection data with defaults
10
+ */
11
+ export function normalizeConnection(
12
+ connection: DatabaseConnection,
13
+ dbType: DatabaseType
14
+ ): DatabaseConnection {
15
+ return {
16
+ ...connection,
17
+ port: connection.port || (dbType === 'mongodb' ? 27017 : 5432),
18
+ host: connection.host || 'localhost',
19
+ database: connection.database || '',
20
+ };
21
+ }
22
+
23
+ /**
24
+ * Transforms DatabaseConnection to backend format
25
+ */
26
+ export function transformConnectionForBackend(
27
+ connection: DatabaseConnection,
28
+ dbType: DatabaseType
29
+ ): BackendConnectionRequest {
30
+ const normalized = normalizeConnection(connection, dbType);
31
+
32
+ const backendConnection = { ...normalized };
33
+ // Remove type from connection object
34
+ delete (backendConnection as any).type;
35
+
36
+ return {
37
+ databaseType: dbType === 'postgres' ? 'postgresql' : 'mongodb',
38
+ connection: backendConnection,
39
+ };
40
+ }