nextjs-chatbot-ui 1.3.0 → 1.4.1

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.
package/README.md CHANGED
@@ -1,6 +1,27 @@
1
- # Chatbot UI Component
1
+ # nextjs-chatbot-ui
2
2
 
3
- A beautiful, configurable chatbot UI component for Next.js applications with Tailwind CSS. Inspired by Sendbird's modern chat interface.
3
+ A beautiful, configurable chatbot UI component for Next.js with Tailwind CSS. Perfect for customer support, AI assistants, and chat applications.
4
+
5
+ ## ⚡ Super Simple Usage (Just 2 Files!)
6
+
7
+ ```javascript
8
+ // app/page.tsx
9
+ 'use client';
10
+ import { Chatbot } from 'nextjs-chatbot-ui';
11
+ export default function Home() {
12
+ return <Chatbot config={{ backendUrl: '/api/chat' }} />;
13
+ }
14
+ ```
15
+
16
+ ```javascript
17
+ // app/api/chat/route.ts
18
+ export async function POST(req: Request) {
19
+ const { message } = await req.json();
20
+ return Response.json({ message: `You said: ${message}` });
21
+ }
22
+ ```
23
+
24
+ **Done!** See `SIMPLE_SETUP.md` for more examples.
4
25
 
5
26
  ## Features
6
27
 
@@ -33,6 +54,34 @@ This package requires:
33
54
  npm install nextjs-chatbot-ui
34
55
  ```
35
56
 
57
+ ## Quick Start (Minimal Code)
58
+
59
+ ### Simplest Usage (Just 2 files!)
60
+
61
+ **1. Add to your page:**
62
+ ```javascript
63
+ // app/page.tsx
64
+ 'use client';
65
+ import { Chatbot } from 'nextjs-chatbot-ui';
66
+
67
+ export default function Home() {
68
+ return <Chatbot config={{ backendUrl: '/api/chat' }} />;
69
+ }
70
+ ```
71
+
72
+ **2. Create API endpoint:**
73
+ ```javascript
74
+ // app/api/chat/route.ts
75
+ import { NextResponse } from 'next/server';
76
+
77
+ export async function POST(req: Request) {
78
+ const { message } = await req.json();
79
+ return NextResponse.json({ message: `You said: ${message}` });
80
+ }
81
+ ```
82
+
83
+ **Done!** See `SIMPLE_SETUP.md` for more examples.
84
+
36
85
  ## Usage
37
86
 
38
87
  ### Basic Setup
@@ -230,7 +279,9 @@ interface ChatbotConfig {
230
279
 
231
280
  ### Backend API Requirements
232
281
 
233
- Your backend should accept POST requests with the following format:
282
+ **✅ Works with ANY backend!** Express, FastAPI, Flask, Django, NestJS, Go, or any other framework.
283
+
284
+ Your backend just needs to implement **one POST endpoint** that accepts and returns JSON:
234
285
 
235
286
  **Request:**
236
287
  ```json
@@ -243,7 +294,7 @@ Your backend should accept POST requests with the following format:
243
294
  }
244
295
  ```
245
296
 
246
- **Response:**
297
+ **Response (either format works):**
247
298
  ```json
248
299
  {
249
300
  "message": "Bot's response text",
@@ -260,7 +311,10 @@ or
260
311
  }
261
312
  ```
262
313
 
263
- **Note:** The `suggestedReplies` or `suggestions` field is optional and will display as clickable buttons below the bot's message, similar to Sendbird's interface.
314
+ **Note:**
315
+ - The `suggestedReplies` or `suggestions` field is optional
316
+ - The component accepts either `message` or `response` in the response
317
+ - See `BACKEND_INTEGRATION.md` for examples with different backend frameworks (Express, FastAPI, Flask, Django, NestJS, Go, etc.)
264
318
 
265
319
  ### Example Implementation
266
320
 
@@ -500,6 +554,45 @@ If the database connection is not establishing:
500
554
 
501
555
  5. **Verify database credentials**: Ensure your database host, port, username, password, and database name are correct.
502
556
 
557
+ ## RAG Chatbot Architecture
558
+
559
+ This package supports RAG (Retrieval-Augmented Generation) chatbot architecture for answering questions about your database.
560
+
561
+ ### Architecture Flow
562
+
563
+ ```
564
+ React UI → Express API → MongoDB + ChromaDB → OpenAI
565
+ ```
566
+
567
+ 1. **Setup Phase**: Use `AdminSetup` component to connect to database, select collections/columns, and process embeddings
568
+ 2. **Query Phase**: Use `Chatbot` component to ask questions, which are answered using vector search and LLM generation
569
+
570
+ ### Quick Start with RAG
571
+
572
+ 1. **Set up backend** (see `server.js` or `example-nextjs-api-routes.md`):
573
+ - Express server with MongoDB, ChromaDB, and OpenAI integration
574
+ - Endpoints: `/api/search`, `/api/database/process-embeddings`
575
+
576
+ 2. **Configure chatbot**:
577
+ ```javascript
578
+ const config: ChatbotConfig = {
579
+ backendUrl: '/api/search', // RAG search endpoint
580
+ labels: {
581
+ title: 'RAG Chatbot',
582
+ placeholder: 'Ask me anything about your data...',
583
+ },
584
+ };
585
+ ```
586
+
587
+ 3. **Use AdminSetup** to:
588
+ - Connect to your database
589
+ - Select collections and columns
590
+ - Process embeddings (fetches data → converts to embeddings → stores in ChromaDB)
591
+
592
+ 4. **Query your data** using the Chatbot component
593
+
594
+ For complete RAG architecture documentation, see `RAG_ARCHITECTURE.md`.
595
+
503
596
  ## License
504
597
 
505
598
  MIT
@@ -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>
@@ -0,0 +1,86 @@
1
+ import { useState } from 'react';
2
+ import type { DatabaseConfig, DatabaseConnection } from '../types/admin';
3
+
4
+ /**
5
+ * Simplified hook for admin setup
6
+ * Handles all API calls automatically
7
+ */
8
+ export function useAdminSetup() {
9
+ const [isLoading, setIsLoading] = useState(false);
10
+
11
+ const handleTestConnection = async (connection: DatabaseConnection): Promise<boolean> => {
12
+ setIsLoading(true);
13
+ try {
14
+ const response = await fetch('/api/database/test', {
15
+ method: 'POST',
16
+ headers: { 'Content-Type': 'application/json' },
17
+ body: JSON.stringify(connection),
18
+ });
19
+ const data = await response.json();
20
+ return data.success === true;
21
+ } catch (error) {
22
+ return false;
23
+ } finally {
24
+ setIsLoading(false);
25
+ }
26
+ };
27
+
28
+ const handleFetchCollections = async (connection: DatabaseConnection): Promise<string[]> => {
29
+ setIsLoading(true);
30
+ try {
31
+ const response = await fetch('/api/database/collections', {
32
+ method: 'POST',
33
+ headers: { 'Content-Type': 'application/json' },
34
+ body: JSON.stringify(connection),
35
+ });
36
+ const data = await response.json();
37
+ return data.collections || data.tables || [];
38
+ } catch (error) {
39
+ return [];
40
+ } finally {
41
+ setIsLoading(false);
42
+ }
43
+ };
44
+
45
+ const handleFetchColumns = async (connection: DatabaseConnection, collection?: string): Promise<string[]> => {
46
+ setIsLoading(true);
47
+ try {
48
+ const response = await fetch('/api/database/columns', {
49
+ method: 'POST',
50
+ headers: { 'Content-Type': 'application/json' },
51
+ body: JSON.stringify({ ...connection, collection }),
52
+ });
53
+ const data = await response.json();
54
+ return data.columns || [];
55
+ } catch (error) {
56
+ return [];
57
+ } finally {
58
+ setIsLoading(false);
59
+ }
60
+ };
61
+
62
+ const handleProcessEmbeddings = async (config: DatabaseConfig): Promise<{ success: boolean; message?: string }> => {
63
+ setIsLoading(true);
64
+ try {
65
+ const response = await fetch('/api/database/process-embeddings', {
66
+ method: 'POST',
67
+ headers: { 'Content-Type': 'application/json' },
68
+ body: JSON.stringify(config),
69
+ });
70
+ const data = await response.json();
71
+ return { success: data.success || false, message: data.message };
72
+ } catch (error: any) {
73
+ return { success: false, message: error.message };
74
+ } finally {
75
+ setIsLoading(false);
76
+ }
77
+ };
78
+
79
+ return {
80
+ handleTestConnection,
81
+ handleFetchCollections,
82
+ handleFetchColumns,
83
+ handleProcessEmbeddings,
84
+ isLoading,
85
+ };
86
+ }
@@ -0,0 +1,27 @@
1
+ import type { ChatbotConfig } from '../types';
2
+
3
+ /**
4
+ * Simplified hook for chatbot integration
5
+ * Handles all the configuration automatically
6
+ */
7
+ export function useChatbot(options?: {
8
+ apiUrl?: string;
9
+ title?: string;
10
+ primaryColor?: string;
11
+ }) {
12
+ const config: ChatbotConfig = {
13
+ backendUrl: options?.apiUrl || '/api/chat',
14
+ labels: {
15
+ title: options?.title || 'Chat Support',
16
+ placeholder: 'Type your message...',
17
+ sendButton: 'Send',
18
+ welcomeMessage: 'Hello! How can I help you?',
19
+ },
20
+ position: 'bottom-right',
21
+ primaryColor: options?.primaryColor || '#6B46C1',
22
+ autoOpen: false,
23
+ showTimestamp: true,
24
+ };
25
+
26
+ return { config };
27
+ }
package/index.tsx CHANGED
@@ -1,5 +1,9 @@
1
- export { default as Chatbot } from './components/Chatbot';
2
- export type { ChatbotConfig, Message, ChatbotProps } from './types';
3
-
4
- export { default as AdminSetup } from './components/AdminSetup';
5
- export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, AdminSetupProps } from './types/admin';
1
+ export { default as Chatbot } from './components/Chatbot';
2
+ export type { ChatbotConfig, Message, ChatbotProps } from './types';
3
+
4
+ export { default as AdminSetup } from './components/AdminSetup';
5
+ export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, CollectionColumnMapping, AdminSetupProps } from './types/admin';
6
+
7
+ // Simplified hooks for easier usage
8
+ export { useChatbot } from './hooks/useChatbot';
9
+ export { useAdminSetup } from './hooks/useAdminSetup';
package/package.json CHANGED
@@ -1,7 +1,6 @@
1
1
  {
2
2
  "name": "nextjs-chatbot-ui",
3
-
4
- "version": "1.3.0",
3
+ "version": "1.4.1",
5
4
  "description": "A configurable chatbot UI component for Next.js with Tailwind CSS",
6
5
  "main": "./index.tsx",
7
6
  "module": "./index.tsx",
@@ -24,18 +23,28 @@
24
23
  "import": "./types/index.ts",
25
24
  "require": "./types/index.ts",
26
25
  "default": "./types/index.ts"
26
+ },
27
+ "./hooks/useChatbot": {
28
+ "import": "./hooks/useChatbot.ts",
29
+ "require": "./hooks/useChatbot.ts",
30
+ "default": "./hooks/useChatbot.ts"
31
+ },
32
+ "./hooks/useAdminSetup": {
33
+ "import": "./hooks/useAdminSetup.ts",
34
+ "require": "./hooks/useAdminSetup.ts",
35
+ "default": "./hooks/useAdminSetup.ts"
27
36
  }
28
37
  },
29
38
  "files": [
30
39
  "components",
31
40
  "types",
32
41
  "utils",
42
+ "hooks",
33
43
  "index.tsx",
34
44
  "README.md"
35
45
  ],
36
46
  "scripts": {
37
47
  "build": "echo 'Package is ready to use. Source files are included.'",
38
- "dev": "next dev",
39
48
  "prepare": "npm run build",
40
49
  "type-check": "tsc --noEmit"
41
50
  },
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
+ }