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 +98 -5
- package/components/AdminSetup.tsx +312 -104
- package/hooks/useAdminSetup.ts +86 -0
- package/hooks/useChatbot.ts +27 -0
- package/index.tsx +9 -5
- package/package.json +12 -3
- package/types/admin.ts +7 -0
- package/utils/connectionHelpers.ts +40 -40
package/README.md
CHANGED
|
@@ -1,6 +1,27 @@
|
|
|
1
|
-
#
|
|
1
|
+
# nextjs-chatbot-ui
|
|
2
2
|
|
|
3
|
-
A beautiful, configurable chatbot UI component for Next.js
|
|
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
|
-
|
|
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:**
|
|
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 [
|
|
21
|
-
const [
|
|
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 [
|
|
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
|
|
169
|
-
|
|
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
|
|
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
|
|
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
|
-
|
|
208
|
-
|
|
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(
|
|
212
|
-
|
|
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
|
-
|
|
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
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
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 = (
|
|
258
|
-
|
|
259
|
-
const
|
|
260
|
-
const isSelected =
|
|
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
|
-
[
|
|
265
|
-
?
|
|
266
|
-
: [...
|
|
277
|
+
[collection]: isSelected
|
|
278
|
+
? collectionCols.filter(c => c !== column)
|
|
279
|
+
: [...collectionCols, column],
|
|
267
280
|
};
|
|
268
281
|
});
|
|
269
282
|
};
|
|
270
283
|
|
|
271
|
-
const
|
|
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
|
-
|
|
282
|
-
|
|
283
|
-
|
|
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
|
-
|
|
293
|
-
|
|
294
|
-
|
|
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-
|
|
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
|
|
321
|
-
{currentStep === '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
|
|
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
|
-
<
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
|
|
505
|
-
|
|
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-
|
|
665
|
+
<div className="space-y-6">
|
|
518
666
|
<div>
|
|
519
|
-
<p className="text-sm text-gray-600 mb-1">
|
|
520
|
-
|
|
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
|
-
{
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
551
|
-
|
|
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
|
-
|
|
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={
|
|
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
|
|
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
|
+
}
|