nextjs-chatbot-ui 1.1.2 → 1.3.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.
- package/components/AdminSetup.tsx +265 -355
- package/package.json +2 -1
- package/types/admin.ts +36 -29
- package/utils/connectionHelpers.ts +40 -0
|
@@ -2,19 +2,24 @@
|
|
|
2
2
|
|
|
3
3
|
import React, { useState } from 'react';
|
|
4
4
|
import { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, AdminSetupProps } from '../types/admin';
|
|
5
|
+
import { normalizeConnection, transformConnectionForBackend } from '../utils/connectionHelpers';
|
|
5
6
|
import clsx from 'clsx';
|
|
6
7
|
|
|
7
8
|
const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
8
9
|
onSave,
|
|
9
10
|
onTestConnection,
|
|
11
|
+
onFetchCollections,
|
|
10
12
|
onFetchColumns,
|
|
11
13
|
}) => {
|
|
12
14
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
13
|
-
const [currentStep, setCurrentStep] = useState<'connection' | 'columns'>('connection');
|
|
15
|
+
const [currentStep, setCurrentStep] = useState<'connection' | 'collection' | 'columns'>('connection');
|
|
14
16
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
15
17
|
const [connectionError, setConnectionError] = useState<string | null>(null);
|
|
16
18
|
const [connectionSuccess, setConnectionSuccess] = useState(false);
|
|
19
|
+
const [availableCollections, setAvailableCollections] = useState<string[]>([]);
|
|
20
|
+
const [selectedCollection, setSelectedCollection] = useState<string>('');
|
|
17
21
|
const [availableColumns, setAvailableColumns] = useState<string[]>([]);
|
|
22
|
+
const [isLoadingCollections, setIsLoadingCollections] = useState(false);
|
|
18
23
|
const [isLoadingColumns, setIsLoadingColumns] = useState(false);
|
|
19
24
|
|
|
20
25
|
const [dbType, setDbType] = useState<DatabaseType>('mongodb');
|
|
@@ -59,35 +64,31 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
59
64
|
setConnectionSuccess(false);
|
|
60
65
|
|
|
61
66
|
try {
|
|
67
|
+
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
68
|
+
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
69
|
+
|
|
62
70
|
let isValid: boolean;
|
|
63
71
|
|
|
64
72
|
if (onTestConnection) {
|
|
65
|
-
|
|
66
|
-
isValid = await onTestConnection(connection);
|
|
73
|
+
isValid = await onTestConnection(normalizedConnection);
|
|
67
74
|
} else {
|
|
68
|
-
// Default: Try to call backend API
|
|
69
75
|
try {
|
|
70
76
|
const response = await fetch('/api/database/test', {
|
|
71
77
|
method: 'POST',
|
|
72
|
-
headers: {
|
|
73
|
-
|
|
74
|
-
},
|
|
75
|
-
body: JSON.stringify(connection),
|
|
78
|
+
headers: { 'Content-Type': 'application/json' },
|
|
79
|
+
body: JSON.stringify(backendRequest),
|
|
76
80
|
});
|
|
77
81
|
|
|
78
82
|
if (!response.ok) {
|
|
79
83
|
const errorData = await response.json().catch(() => ({}));
|
|
80
|
-
throw new Error(errorData.message || `HTTP ${response.status}
|
|
84
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
81
85
|
}
|
|
82
86
|
|
|
83
87
|
const data = await response.json();
|
|
84
88
|
isValid = data.success === true || response.ok;
|
|
85
89
|
} catch (fetchError: any) {
|
|
86
|
-
// If API endpoint doesn't exist, show helpful error
|
|
87
90
|
if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
|
|
88
|
-
throw new Error(
|
|
89
|
-
'Backend API endpoint not found. Please implement POST /api/database/test endpoint or provide onTestConnection handler.'
|
|
90
|
-
);
|
|
91
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/test endpoint.');
|
|
91
92
|
}
|
|
92
93
|
throw fetchError;
|
|
93
94
|
}
|
|
@@ -98,13 +99,12 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
98
99
|
setConnectionSuccess(true);
|
|
99
100
|
return true;
|
|
100
101
|
} else {
|
|
101
|
-
setConnectionError('Connection failed. Please check your credentials
|
|
102
|
+
setConnectionError('Connection failed. Please check your credentials.');
|
|
102
103
|
setConnectionSuccess(false);
|
|
103
104
|
return false;
|
|
104
105
|
}
|
|
105
106
|
} catch (error: any) {
|
|
106
|
-
|
|
107
|
-
setConnectionError(errorMessage);
|
|
107
|
+
setConnectionError(error.message || 'Connection failed. Please try again.');
|
|
108
108
|
setConnectionSuccess(false);
|
|
109
109
|
console.error('Database connection error:', error);
|
|
110
110
|
return false;
|
|
@@ -113,40 +113,91 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
113
113
|
}
|
|
114
114
|
};
|
|
115
115
|
|
|
116
|
-
const
|
|
116
|
+
const handleFetchCollections = async (): Promise<string[]> => {
|
|
117
|
+
setIsLoadingCollections(true);
|
|
118
|
+
setConnectionError(null);
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
122
|
+
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
123
|
+
|
|
124
|
+
let collections: string[];
|
|
125
|
+
|
|
126
|
+
if (onFetchCollections) {
|
|
127
|
+
collections = await onFetchCollections(normalizedConnection);
|
|
128
|
+
} else {
|
|
129
|
+
try {
|
|
130
|
+
const response = await fetch('/api/database/collections', {
|
|
131
|
+
method: 'POST',
|
|
132
|
+
headers: { 'Content-Type': 'application/json' },
|
|
133
|
+
body: JSON.stringify(backendRequest),
|
|
134
|
+
});
|
|
135
|
+
|
|
136
|
+
if (!response.ok) {
|
|
137
|
+
const errorData = await response.json().catch(() => ({}));
|
|
138
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
const data = await response.json();
|
|
142
|
+
collections = data.collections || data.tables || data.columnNames || [];
|
|
143
|
+
} catch (fetchError: any) {
|
|
144
|
+
if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
|
|
145
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/collections endpoint.');
|
|
146
|
+
}
|
|
147
|
+
throw fetchError;
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
if (collections && collections.length > 0) {
|
|
152
|
+
setAvailableCollections(collections);
|
|
153
|
+
setIsLoadingCollections(false);
|
|
154
|
+
return collections;
|
|
155
|
+
} else {
|
|
156
|
+
setConnectionError('No collections found in the database.');
|
|
157
|
+
setIsLoadingCollections(false);
|
|
158
|
+
return [];
|
|
159
|
+
}
|
|
160
|
+
} catch (error: any) {
|
|
161
|
+
setConnectionError(error.message || 'Failed to fetch collections.');
|
|
162
|
+
setIsLoadingCollections(false);
|
|
163
|
+
console.error('Fetch collections error:', error);
|
|
164
|
+
return [];
|
|
165
|
+
}
|
|
166
|
+
};
|
|
167
|
+
|
|
168
|
+
const handleFetchColumns = async (collectionName?: string): Promise<string[]> => {
|
|
117
169
|
setIsLoadingColumns(true);
|
|
118
170
|
setConnectionError(null);
|
|
119
171
|
|
|
120
172
|
try {
|
|
173
|
+
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
174
|
+
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
175
|
+
|
|
121
176
|
let columns: string[];
|
|
122
177
|
|
|
123
178
|
if (onFetchColumns) {
|
|
124
|
-
|
|
125
|
-
columns = await onFetchColumns(connection);
|
|
179
|
+
columns = await onFetchColumns(normalizedConnection, collectionName || selectedCollection);
|
|
126
180
|
} else {
|
|
127
|
-
// Default: Try to call backend API
|
|
128
181
|
try {
|
|
129
182
|
const response = await fetch('/api/database/columns', {
|
|
130
183
|
method: 'POST',
|
|
131
|
-
headers: {
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
184
|
+
headers: { 'Content-Type': 'application/json' },
|
|
185
|
+
body: JSON.stringify({
|
|
186
|
+
...backendRequest,
|
|
187
|
+
collection: collectionName || selectedCollection,
|
|
188
|
+
}),
|
|
135
189
|
});
|
|
136
190
|
|
|
137
191
|
if (!response.ok) {
|
|
138
192
|
const errorData = await response.json().catch(() => ({}));
|
|
139
|
-
throw new Error(errorData.message || `HTTP ${response.status}
|
|
193
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
140
194
|
}
|
|
141
195
|
|
|
142
196
|
const data = await response.json();
|
|
143
197
|
columns = data.columns || data.columnNames || [];
|
|
144
198
|
} catch (fetchError: any) {
|
|
145
|
-
// If API endpoint doesn't exist, show helpful error
|
|
146
199
|
if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
|
|
147
|
-
throw new Error(
|
|
148
|
-
'Backend API endpoint not found. Please implement POST /api/database/columns endpoint or provide onFetchColumns handler.'
|
|
149
|
-
);
|
|
200
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/columns endpoint.');
|
|
150
201
|
}
|
|
151
202
|
throw fetchError;
|
|
152
203
|
}
|
|
@@ -157,57 +208,49 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
157
208
|
setIsLoadingColumns(false);
|
|
158
209
|
return columns;
|
|
159
210
|
} else {
|
|
160
|
-
setConnectionError('No columns found in the
|
|
211
|
+
setConnectionError('No columns found in the selected collection.');
|
|
161
212
|
setIsLoadingColumns(false);
|
|
162
213
|
return [];
|
|
163
214
|
}
|
|
164
215
|
} catch (error: any) {
|
|
165
|
-
|
|
166
|
-
setConnectionError(errorMessage);
|
|
216
|
+
setConnectionError(error.message || 'Failed to fetch columns.');
|
|
167
217
|
setIsLoadingColumns(false);
|
|
168
218
|
console.error('Fetch columns error:', error);
|
|
169
219
|
return [];
|
|
170
220
|
}
|
|
171
221
|
};
|
|
172
222
|
|
|
173
|
-
const
|
|
174
|
-
// Validate connection fields
|
|
223
|
+
const handleConnect = async () => {
|
|
175
224
|
if (dbType === 'mongodb') {
|
|
176
225
|
if (!connection.connectionString && (!connection.host || !connection.database)) {
|
|
177
226
|
setConnectionError('Please provide connection string or host and database');
|
|
178
|
-
setConnectionSuccess(false);
|
|
179
227
|
return;
|
|
180
228
|
}
|
|
181
229
|
} else {
|
|
182
230
|
if (!connection.host || !connection.database || !connection.username || !connection.password) {
|
|
183
231
|
setConnectionError('Please fill in all required fields');
|
|
184
|
-
setConnectionSuccess(false);
|
|
185
232
|
return;
|
|
186
233
|
}
|
|
187
234
|
}
|
|
188
235
|
|
|
189
|
-
// Clear previous errors
|
|
190
236
|
setConnectionError(null);
|
|
191
237
|
setConnectionSuccess(false);
|
|
192
238
|
|
|
193
|
-
// Test connection first
|
|
194
239
|
const connectionSuccess = await handleTestConnection();
|
|
195
240
|
|
|
196
|
-
if (
|
|
197
|
-
|
|
198
|
-
|
|
241
|
+
if (connectionSuccess) {
|
|
242
|
+
const collections = await handleFetchCollections();
|
|
243
|
+
if (collections && collections.length > 0) {
|
|
244
|
+
setCurrentStep('collection');
|
|
245
|
+
}
|
|
199
246
|
}
|
|
247
|
+
};
|
|
200
248
|
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
if (
|
|
205
|
-
// Successfully fetched columns, move to next step
|
|
249
|
+
const handleCollectionSelect = async (collection: string) => {
|
|
250
|
+
setSelectedCollection(collection);
|
|
251
|
+
const columns = await handleFetchColumns(collection);
|
|
252
|
+
if (columns && columns.length > 0) {
|
|
206
253
|
setCurrentStep('columns');
|
|
207
|
-
setConnectionError(null);
|
|
208
|
-
} else {
|
|
209
|
-
// Column fetching failed, show error but keep connection success
|
|
210
|
-
// Error is already set in handleFetchColumns
|
|
211
254
|
}
|
|
212
255
|
};
|
|
213
256
|
|
|
@@ -235,7 +278,6 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
235
278
|
onSave(config);
|
|
236
279
|
}
|
|
237
280
|
|
|
238
|
-
// Close modal and reset
|
|
239
281
|
setIsModalOpen(false);
|
|
240
282
|
setCurrentStep('connection');
|
|
241
283
|
setConnectionError(null);
|
|
@@ -246,6 +288,8 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
246
288
|
setCurrentStep('connection');
|
|
247
289
|
setConnectionError(null);
|
|
248
290
|
setConnectionSuccess(false);
|
|
291
|
+
setAvailableCollections([]);
|
|
292
|
+
setSelectedCollection('');
|
|
249
293
|
setAvailableColumns([]);
|
|
250
294
|
setColumnSelection({
|
|
251
295
|
embeddingColumns: [],
|
|
@@ -256,115 +300,65 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
256
300
|
|
|
257
301
|
return (
|
|
258
302
|
<>
|
|
259
|
-
{/* Sidebar Button/Item - This can be integrated into admin sidebar */}
|
|
260
303
|
<button
|
|
261
304
|
onClick={() => setIsModalOpen(true)}
|
|
262
|
-
className="w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-gray-
|
|
305
|
+
className="w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-gray-50 rounded-lg transition-colors text-sm text-gray-700"
|
|
263
306
|
>
|
|
264
|
-
<svg
|
|
265
|
-
|
|
266
|
-
className="h-5 w-5 text-gray-600"
|
|
267
|
-
fill="none"
|
|
268
|
-
viewBox="0 0 24 24"
|
|
269
|
-
stroke="currentColor"
|
|
270
|
-
>
|
|
271
|
-
<path
|
|
272
|
-
strokeLinecap="round"
|
|
273
|
-
strokeLinejoin="round"
|
|
274
|
-
strokeWidth={2}
|
|
275
|
-
d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4"
|
|
276
|
-
/>
|
|
307
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
308
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M4 7v10c0 2.21 3.582 4 8 4s8-1.79 8-4V7M4 7c0 2.21 3.582 4 8 4s8-1.79 8-4M4 7c0-2.21 3.582-4 8-4s8 1.79 8 4m0 5c0 2.21-3.582 4-8 4s-8-1.79-8-4" />
|
|
277
309
|
</svg>
|
|
278
|
-
<span className="
|
|
310
|
+
<span className="font-medium">Database Setup</span>
|
|
279
311
|
</button>
|
|
280
312
|
|
|
281
|
-
{/* Modal */}
|
|
282
313
|
{isModalOpen && (
|
|
283
|
-
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black
|
|
284
|
-
<div className="bg-white rounded-
|
|
285
|
-
{/*
|
|
286
|
-
<div className="px-6 py-4 border-b border-gray-
|
|
287
|
-
<h2 className="text-
|
|
288
|
-
{currentStep === 'connection'
|
|
314
|
+
<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">
|
|
316
|
+
{/* Header */}
|
|
317
|
+
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
|
|
318
|
+
<h2 className="text-lg font-semibold text-gray-900">
|
|
319
|
+
{currentStep === 'connection' && 'Database Connection'}
|
|
320
|
+
{currentStep === 'collection' && 'Select Collection'}
|
|
321
|
+
{currentStep === 'columns' && 'Configure Columns'}
|
|
289
322
|
</h2>
|
|
290
323
|
<button
|
|
291
324
|
onClick={handleClose}
|
|
292
|
-
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
325
|
+
className="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
|
293
326
|
>
|
|
294
|
-
<svg
|
|
295
|
-
|
|
296
|
-
className="h-6 w-6"
|
|
297
|
-
fill="none"
|
|
298
|
-
viewBox="0 0 24 24"
|
|
299
|
-
stroke="currentColor"
|
|
300
|
-
>
|
|
301
|
-
<path
|
|
302
|
-
strokeLinecap="round"
|
|
303
|
-
strokeLinejoin="round"
|
|
304
|
-
strokeWidth={2}
|
|
305
|
-
d="M6 18L18 6M6 6l12 12"
|
|
306
|
-
/>
|
|
327
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
328
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
307
329
|
</svg>
|
|
308
330
|
</button>
|
|
309
331
|
</div>
|
|
310
332
|
|
|
311
|
-
{/*
|
|
312
|
-
<div className="flex-1 overflow-y-auto px-6 py-
|
|
313
|
-
{currentStep === 'connection'
|
|
314
|
-
<div className="space-y-
|
|
315
|
-
{/* Database Type
|
|
333
|
+
{/* Content */}
|
|
334
|
+
<div className="flex-1 overflow-y-auto px-6 py-6">
|
|
335
|
+
{currentStep === 'connection' && (
|
|
336
|
+
<div className="space-y-5">
|
|
337
|
+
{/* Database Type */}
|
|
316
338
|
<div>
|
|
317
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
318
|
-
|
|
319
|
-
</label>
|
|
320
|
-
<div className="grid grid-cols-2 gap-4">
|
|
339
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">Database Type</label>
|
|
340
|
+
<div className="flex gap-3">
|
|
321
341
|
<button
|
|
322
342
|
onClick={() => handleDbTypeChange('mongodb')}
|
|
323
343
|
className={clsx(
|
|
324
|
-
'
|
|
344
|
+
'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
|
|
325
345
|
dbType === 'mongodb'
|
|
326
|
-
? 'border-blue-500 bg-blue-50'
|
|
327
|
-
: 'border-gray-200 hover:border-gray-300'
|
|
346
|
+
? 'border-blue-500 bg-blue-50 text-blue-700'
|
|
347
|
+
: 'border-gray-200 text-gray-700 hover:border-gray-300'
|
|
328
348
|
)}
|
|
329
349
|
>
|
|
330
|
-
|
|
331
|
-
<div className={clsx(
|
|
332
|
-
'w-5 h-5 rounded-full border-2 flex items-center justify-center',
|
|
333
|
-
dbType === 'mongodb' ? 'border-blue-500' : 'border-gray-300'
|
|
334
|
-
)}>
|
|
335
|
-
{dbType === 'mongodb' && (
|
|
336
|
-
<div className="w-3 h-3 rounded-full bg-blue-500" />
|
|
337
|
-
)}
|
|
338
|
-
</div>
|
|
339
|
-
<div>
|
|
340
|
-
<div className="font-semibold text-gray-900">MongoDB</div>
|
|
341
|
-
<div className="text-xs text-gray-500">NoSQL Database</div>
|
|
342
|
-
</div>
|
|
343
|
-
</div>
|
|
350
|
+
MongoDB
|
|
344
351
|
</button>
|
|
345
352
|
<button
|
|
346
353
|
onClick={() => handleDbTypeChange('postgres')}
|
|
347
354
|
className={clsx(
|
|
348
|
-
'
|
|
355
|
+
'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
|
|
349
356
|
dbType === 'postgres'
|
|
350
|
-
? 'border-blue-500 bg-blue-50'
|
|
351
|
-
: 'border-gray-200 hover:border-gray-300'
|
|
357
|
+
? 'border-blue-500 bg-blue-50 text-blue-700'
|
|
358
|
+
: 'border-gray-200 text-gray-700 hover:border-gray-300'
|
|
352
359
|
)}
|
|
353
360
|
>
|
|
354
|
-
|
|
355
|
-
<div className={clsx(
|
|
356
|
-
'w-5 h-5 rounded-full border-2 flex items-center justify-center',
|
|
357
|
-
dbType === 'postgres' ? 'border-blue-500' : 'border-gray-300'
|
|
358
|
-
)}>
|
|
359
|
-
{dbType === 'postgres' && (
|
|
360
|
-
<div className="w-3 h-3 rounded-full bg-blue-500" />
|
|
361
|
-
)}
|
|
362
|
-
</div>
|
|
363
|
-
<div>
|
|
364
|
-
<div className="font-semibold text-gray-900">PostgreSQL</div>
|
|
365
|
-
<div className="text-xs text-gray-500">SQL Database</div>
|
|
366
|
-
</div>
|
|
367
|
-
</div>
|
|
361
|
+
PostgreSQL
|
|
368
362
|
</button>
|
|
369
363
|
</div>
|
|
370
364
|
</div>
|
|
@@ -373,317 +367,233 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
373
367
|
{dbType === 'mongodb' ? (
|
|
374
368
|
<div className="space-y-4">
|
|
375
369
|
<div>
|
|
376
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
377
|
-
Connection String (Recommended)
|
|
378
|
-
</label>
|
|
370
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Connection String</label>
|
|
379
371
|
<input
|
|
380
372
|
type="text"
|
|
381
373
|
value={connection.connectionString || ''}
|
|
382
374
|
onChange={(e) => handleConnectionChange('connectionString', e.target.value)}
|
|
383
|
-
placeholder="mongodb://
|
|
384
|
-
className="w-full px-
|
|
375
|
+
placeholder="mongodb://host:port/database"
|
|
376
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
385
377
|
/>
|
|
386
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
387
|
-
Or fill individual fields below
|
|
388
|
-
</p>
|
|
389
378
|
</div>
|
|
390
|
-
<div className="
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
Host *
|
|
395
|
-
</label>
|
|
396
|
-
<input
|
|
397
|
-
type="text"
|
|
398
|
-
value={connection.host}
|
|
399
|
-
onChange={(e) => handleConnectionChange('host', e.target.value)}
|
|
400
|
-
placeholder="localhost"
|
|
401
|
-
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
402
|
-
/>
|
|
403
|
-
</div>
|
|
404
|
-
<div>
|
|
405
|
-
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
406
|
-
Port *
|
|
407
|
-
</label>
|
|
408
|
-
<input
|
|
409
|
-
type="number"
|
|
410
|
-
value={connection.port}
|
|
411
|
-
onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 27017)}
|
|
412
|
-
placeholder="27017"
|
|
413
|
-
className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent"
|
|
414
|
-
/>
|
|
415
|
-
</div>
|
|
416
|
-
</div>
|
|
417
|
-
<div className="mt-4">
|
|
418
|
-
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
419
|
-
Database Name *
|
|
420
|
-
</label>
|
|
379
|
+
<div className="text-xs text-gray-500 text-center">or</div>
|
|
380
|
+
<div className="grid grid-cols-2 gap-3">
|
|
381
|
+
<div>
|
|
382
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
|
|
421
383
|
<input
|
|
422
384
|
type="text"
|
|
423
|
-
value={connection.
|
|
424
|
-
onChange={(e) => handleConnectionChange('
|
|
425
|
-
placeholder="
|
|
426
|
-
className="w-full px-
|
|
385
|
+
value={connection.host}
|
|
386
|
+
onChange={(e) => handleConnectionChange('host', e.target.value)}
|
|
387
|
+
placeholder="localhost"
|
|
388
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
427
389
|
/>
|
|
428
390
|
</div>
|
|
429
|
-
<div
|
|
430
|
-
<label className="
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
</label>
|
|
391
|
+
<div>
|
|
392
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
|
|
393
|
+
<input
|
|
394
|
+
type="number"
|
|
395
|
+
value={connection.port}
|
|
396
|
+
onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 27017)}
|
|
397
|
+
placeholder="27017"
|
|
398
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
399
|
+
/>
|
|
439
400
|
</div>
|
|
440
401
|
</div>
|
|
402
|
+
<div>
|
|
403
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
|
|
404
|
+
<input
|
|
405
|
+
type="text"
|
|
406
|
+
value={connection.database}
|
|
407
|
+
onChange={(e) => handleConnectionChange('database', e.target.value)}
|
|
408
|
+
placeholder="database_name"
|
|
409
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
410
|
+
/>
|
|
411
|
+
</div>
|
|
441
412
|
</div>
|
|
442
413
|
) : (
|
|
443
414
|
<div className="space-y-4">
|
|
444
|
-
<div className="grid grid-cols-2 gap-
|
|
415
|
+
<div className="grid grid-cols-2 gap-3">
|
|
445
416
|
<div>
|
|
446
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
447
|
-
Host *
|
|
448
|
-
</label>
|
|
417
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
|
|
449
418
|
<input
|
|
450
419
|
type="text"
|
|
451
420
|
value={connection.host}
|
|
452
421
|
onChange={(e) => handleConnectionChange('host', e.target.value)}
|
|
453
422
|
placeholder="localhost"
|
|
454
|
-
className="w-full px-
|
|
423
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
455
424
|
/>
|
|
456
425
|
</div>
|
|
457
426
|
<div>
|
|
458
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
459
|
-
Port *
|
|
460
|
-
</label>
|
|
427
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
|
|
461
428
|
<input
|
|
462
429
|
type="number"
|
|
463
430
|
value={connection.port}
|
|
464
431
|
onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 5432)}
|
|
465
432
|
placeholder="5432"
|
|
466
|
-
className="w-full px-
|
|
433
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
467
434
|
/>
|
|
468
435
|
</div>
|
|
469
436
|
</div>
|
|
470
437
|
<div>
|
|
471
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
472
|
-
Database Name *
|
|
473
|
-
</label>
|
|
438
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
|
|
474
439
|
<input
|
|
475
440
|
type="text"
|
|
476
441
|
value={connection.database}
|
|
477
442
|
onChange={(e) => handleConnectionChange('database', e.target.value)}
|
|
478
|
-
placeholder="
|
|
479
|
-
className="w-full px-
|
|
443
|
+
placeholder="database_name"
|
|
444
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
480
445
|
/>
|
|
481
446
|
</div>
|
|
482
|
-
<div className="grid grid-cols-2 gap-
|
|
447
|
+
<div className="grid grid-cols-2 gap-3">
|
|
483
448
|
<div>
|
|
484
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
485
|
-
Username *
|
|
486
|
-
</label>
|
|
449
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Username</label>
|
|
487
450
|
<input
|
|
488
451
|
type="text"
|
|
489
452
|
value={connection.username || ''}
|
|
490
453
|
onChange={(e) => handleConnectionChange('username', e.target.value)}
|
|
491
|
-
placeholder="
|
|
492
|
-
className="w-full px-
|
|
454
|
+
placeholder="username"
|
|
455
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
493
456
|
/>
|
|
494
457
|
</div>
|
|
495
458
|
<div>
|
|
496
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
497
|
-
Password *
|
|
498
|
-
</label>
|
|
459
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Password</label>
|
|
499
460
|
<input
|
|
500
461
|
type="password"
|
|
501
462
|
value={connection.password || ''}
|
|
502
463
|
onChange={(e) => handleConnectionChange('password', e.target.value)}
|
|
503
|
-
placeholder="
|
|
504
|
-
className="w-full px-
|
|
464
|
+
placeholder="••••••"
|
|
465
|
+
className="w-full px-3 py-2 text-sm border border-gray-300 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500"
|
|
505
466
|
/>
|
|
506
467
|
</div>
|
|
507
468
|
</div>
|
|
508
|
-
<div>
|
|
509
|
-
<label className="flex items-center gap-2">
|
|
510
|
-
<input
|
|
511
|
-
type="checkbox"
|
|
512
|
-
checked={connection.ssl || false}
|
|
513
|
-
onChange={(e) => handleConnectionChange('ssl', e.target.checked)}
|
|
514
|
-
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
515
|
-
/>
|
|
516
|
-
<span className="text-sm text-gray-700">Enable SSL</span>
|
|
517
|
-
</label>
|
|
518
|
-
</div>
|
|
519
469
|
</div>
|
|
520
470
|
)}
|
|
521
471
|
|
|
522
472
|
{connectionError && (
|
|
523
|
-
<div className="bg-red-50 border border-red-200 rounded-lg p-
|
|
524
|
-
<p className="text-sm text-red-
|
|
473
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
474
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
525
475
|
</div>
|
|
526
476
|
)}
|
|
527
477
|
{connectionSuccess && !connectionError && !isConnecting && (
|
|
528
|
-
<div className="bg-green-50 border border-green-200 rounded-lg p-
|
|
529
|
-
<p className="text-sm text-green-
|
|
478
|
+
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
|
|
479
|
+
<p className="text-sm text-green-700">✓ Connection successful</p>
|
|
530
480
|
</div>
|
|
531
481
|
)}
|
|
532
482
|
</div>
|
|
533
|
-
)
|
|
534
|
-
|
|
483
|
+
)}
|
|
484
|
+
|
|
485
|
+
{currentStep === 'collection' && (
|
|
486
|
+
<div className="space-y-4">
|
|
487
|
+
<div>
|
|
488
|
+
<p className="text-sm text-gray-600 mb-4">Select a collection to configure for embeddings:</p>
|
|
489
|
+
{isLoadingCollections ? (
|
|
490
|
+
<div className="flex items-center justify-center py-8">
|
|
491
|
+
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
|
492
|
+
</div>
|
|
493
|
+
) : (
|
|
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>
|
|
506
|
+
)}
|
|
507
|
+
</div>
|
|
508
|
+
{connectionError && (
|
|
509
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
510
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
511
|
+
</div>
|
|
512
|
+
)}
|
|
513
|
+
</div>
|
|
514
|
+
)}
|
|
515
|
+
|
|
516
|
+
{currentStep === 'columns' && (
|
|
517
|
+
<div className="space-y-5">
|
|
518
|
+
<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>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
535
523
|
{isLoadingColumns ? (
|
|
536
|
-
<div className="flex
|
|
537
|
-
<div className="animate-spin rounded-full h-
|
|
538
|
-
<p className="text-sm text-gray-600">Loading columns...</p>
|
|
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>
|
|
539
526
|
</div>
|
|
540
527
|
) : availableColumns.length === 0 ? (
|
|
541
|
-
<div className="bg-yellow-50 border border-yellow-200 rounded-lg p-
|
|
542
|
-
<p className="text-sm text-yellow-
|
|
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>
|
|
543
530
|
</div>
|
|
544
531
|
) : (
|
|
545
|
-
|
|
546
|
-
<
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
{availableColumns.length === 0 ? (
|
|
563
|
-
<p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
|
|
564
|
-
) : (
|
|
565
|
-
availableColumns.map((column) => (
|
|
566
|
-
<label
|
|
567
|
-
key={`embedding-${column}`}
|
|
568
|
-
className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
|
|
569
|
-
>
|
|
570
|
-
<input
|
|
571
|
-
type="checkbox"
|
|
572
|
-
checked={columnSelection.embeddingColumns.includes(column)}
|
|
573
|
-
onChange={() => handleColumnToggle(column, 'embeddingColumns')}
|
|
574
|
-
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
575
|
-
/>
|
|
576
|
-
<span className="text-sm text-gray-700">{column}</span>
|
|
577
|
-
</label>
|
|
578
|
-
))
|
|
579
|
-
)}
|
|
580
|
-
</div>
|
|
581
|
-
{columnSelection.embeddingColumns.length > 0 && (
|
|
582
|
-
<p className="text-xs text-green-600 mt-1">
|
|
583
|
-
{columnSelection.embeddingColumns.length} column{columnSelection.embeddingColumns.length !== 1 ? 's' : ''} selected
|
|
584
|
-
</p>
|
|
585
|
-
)}
|
|
586
|
-
</div>
|
|
587
|
-
|
|
588
|
-
{/* LLM Columns */}
|
|
589
|
-
<div>
|
|
590
|
-
<label className="block text-sm font-medium text-gray-700 mb-2">
|
|
591
|
-
Works with LLM
|
|
592
|
-
</label>
|
|
593
|
-
<p className="text-xs text-gray-500 mb-3">Select columns that will be processed by the LLM</p>
|
|
594
|
-
<div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
|
|
595
|
-
{availableColumns.length === 0 ? (
|
|
596
|
-
<p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
|
|
597
|
-
) : (
|
|
598
|
-
availableColumns.map((column) => (
|
|
599
|
-
<label
|
|
600
|
-
key={`llm-${column}`}
|
|
601
|
-
className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
|
|
602
|
-
>
|
|
603
|
-
<input
|
|
604
|
-
type="checkbox"
|
|
605
|
-
checked={columnSelection.llmColumns.includes(column)}
|
|
606
|
-
onChange={() => handleColumnToggle(column, 'llmColumns')}
|
|
607
|
-
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
608
|
-
/>
|
|
609
|
-
<span className="text-sm text-gray-700">{column}</span>
|
|
610
|
-
</label>
|
|
611
|
-
))
|
|
612
|
-
)}
|
|
613
|
-
</div>
|
|
614
|
-
{columnSelection.llmColumns.length > 0 && (
|
|
615
|
-
<p className="text-xs text-green-600 mt-1">
|
|
616
|
-
{columnSelection.llmColumns.length} column{columnSelection.llmColumns.length !== 1 ? 's' : ''} selected
|
|
617
|
-
</p>
|
|
618
|
-
)}
|
|
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
|
+
))}
|
|
619
549
|
</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
|
+
)}
|
|
620
557
|
|
|
621
|
-
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
</label>
|
|
626
|
-
<p className="text-xs text-gray-500 mb-3">Select columns that will be stored in ChromaDB</p>
|
|
627
|
-
<div className="grid grid-cols-2 gap-2 max-h-40 overflow-y-auto border border-gray-200 rounded-lg p-3 bg-gray-50">
|
|
628
|
-
{availableColumns.length === 0 ? (
|
|
629
|
-
<p className="text-sm text-gray-500 col-span-2 text-center py-2">No columns available</p>
|
|
630
|
-
) : (
|
|
631
|
-
availableColumns.map((column) => (
|
|
632
|
-
<label
|
|
633
|
-
key={`chroma-${column}`}
|
|
634
|
-
className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
|
|
635
|
-
>
|
|
636
|
-
<input
|
|
637
|
-
type="checkbox"
|
|
638
|
-
checked={columnSelection.chromaColumns.includes(column)}
|
|
639
|
-
onChange={() => handleColumnToggle(column, 'chromaColumns')}
|
|
640
|
-
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
641
|
-
/>
|
|
642
|
-
<span className="text-sm text-gray-700">{column}</span>
|
|
643
|
-
</label>
|
|
644
|
-
))
|
|
645
|
-
)}
|
|
646
|
-
</div>
|
|
647
|
-
{columnSelection.chromaColumns.length > 0 && (
|
|
648
|
-
<p className="text-xs text-green-600 mt-1">
|
|
649
|
-
{columnSelection.chromaColumns.length} column{columnSelection.chromaColumns.length !== 1 ? 's' : ''} selected
|
|
650
|
-
</p>
|
|
651
|
-
)}
|
|
652
|
-
</div>
|
|
653
|
-
</>
|
|
558
|
+
{connectionError && (
|
|
559
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
560
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
561
|
+
</div>
|
|
654
562
|
)}
|
|
655
563
|
</div>
|
|
656
564
|
)}
|
|
657
565
|
</div>
|
|
658
566
|
|
|
659
|
-
{/*
|
|
660
|
-
<div className="px-6 py-4 border-t border-gray-
|
|
567
|
+
{/* Footer */}
|
|
568
|
+
<div className="px-6 py-4 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between">
|
|
661
569
|
<button
|
|
662
|
-
onClick={currentStep === '
|
|
570
|
+
onClick={currentStep === 'connection' ? handleClose : () => {
|
|
571
|
+
if (currentStep === 'columns') {
|
|
572
|
+
setCurrentStep('collection');
|
|
573
|
+
setAvailableColumns([]);
|
|
574
|
+
} else {
|
|
575
|
+
setCurrentStep('connection');
|
|
576
|
+
}
|
|
577
|
+
}}
|
|
663
578
|
className="px-4 py-2 text-sm font-medium text-gray-700 hover:bg-gray-100 rounded-lg transition-colors"
|
|
664
579
|
>
|
|
665
|
-
{currentStep === '
|
|
580
|
+
{currentStep === 'connection' ? 'Cancel' : 'Back'}
|
|
666
581
|
</button>
|
|
667
582
|
<div className="flex gap-3">
|
|
668
|
-
{currentStep === 'connection'
|
|
583
|
+
{currentStep === 'connection' && (
|
|
669
584
|
<button
|
|
670
|
-
onClick={
|
|
671
|
-
disabled={isConnecting ||
|
|
585
|
+
onClick={handleConnect}
|
|
586
|
+
disabled={isConnecting || isLoadingCollections}
|
|
672
587
|
className="px-6 py-2 bg-blue-600 text-white text-sm font-medium rounded-lg hover:bg-blue-700 disabled:opacity-50 disabled:cursor-not-allowed transition-colors flex items-center gap-2"
|
|
673
588
|
>
|
|
674
|
-
{isConnecting &&
|
|
675
|
-
|
|
676
|
-
)}
|
|
677
|
-
{isLoadingColumns ? 'Loading Columns...' : isConnecting ? 'Connecting...' : 'Connect & Next'}
|
|
589
|
+
{isConnecting && <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>}
|
|
590
|
+
{isConnecting ? 'Connecting...' : 'Connect'}
|
|
678
591
|
</button>
|
|
679
|
-
)
|
|
592
|
+
)}
|
|
593
|
+
{currentStep === 'columns' && (
|
|
680
594
|
<button
|
|
681
595
|
onClick={handleSave}
|
|
682
|
-
disabled={
|
|
683
|
-
columnSelection.embeddingColumns.length === 0 &&
|
|
684
|
-
columnSelection.llmColumns.length === 0 &&
|
|
685
|
-
columnSelection.chromaColumns.length === 0
|
|
686
|
-
}
|
|
596
|
+
disabled={columnSelection.embeddingColumns.length === 0}
|
|
687
597
|
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"
|
|
688
598
|
>
|
|
689
599
|
Save Configuration
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nextjs-chatbot-ui",
|
|
3
3
|
|
|
4
|
-
"version": "1.
|
|
4
|
+
"version": "1.3.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",
|
|
@@ -29,6 +29,7 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"components",
|
|
31
31
|
"types",
|
|
32
|
+
"utils",
|
|
32
33
|
"index.tsx",
|
|
33
34
|
"README.md"
|
|
34
35
|
],
|
package/types/admin.ts
CHANGED
|
@@ -1,29 +1,36 @@
|
|
|
1
|
-
export type DatabaseType = 'mongodb' | 'postgres';
|
|
2
|
-
|
|
3
|
-
export interface DatabaseConnection {
|
|
4
|
-
type: DatabaseType;
|
|
5
|
-
host: string;
|
|
6
|
-
port: number;
|
|
7
|
-
database: string;
|
|
8
|
-
username?: string;
|
|
9
|
-
password?: string;
|
|
10
|
-
connectionString?: string; // For MongoDB connection string
|
|
11
|
-
ssl?: boolean;
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export interface ColumnSelection {
|
|
15
|
-
embeddingColumns: string[];
|
|
16
|
-
llmColumns: string[];
|
|
17
|
-
chromaColumns: string[];
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
export interface DatabaseConfig {
|
|
21
|
-
connection: DatabaseConnection;
|
|
22
|
-
columnSelection: ColumnSelection;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface AdminSetupProps {
|
|
26
|
-
onSave?: (config: DatabaseConfig) => void;
|
|
27
|
-
onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
|
|
28
|
-
|
|
29
|
-
|
|
1
|
+
export type DatabaseType = 'mongodb' | 'postgres';
|
|
2
|
+
|
|
3
|
+
export interface DatabaseConnection {
|
|
4
|
+
type: DatabaseType;
|
|
5
|
+
host: string;
|
|
6
|
+
port: number;
|
|
7
|
+
database: string;
|
|
8
|
+
username?: string;
|
|
9
|
+
password?: string;
|
|
10
|
+
connectionString?: string; // For MongoDB connection string
|
|
11
|
+
ssl?: boolean;
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
export interface ColumnSelection {
|
|
15
|
+
embeddingColumns: string[];
|
|
16
|
+
llmColumns: string[];
|
|
17
|
+
chromaColumns: string[];
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface DatabaseConfig {
|
|
21
|
+
connection: DatabaseConnection;
|
|
22
|
+
columnSelection: ColumnSelection;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface AdminSetupProps {
|
|
26
|
+
onSave?: (config: DatabaseConfig) => void;
|
|
27
|
+
onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
|
|
28
|
+
onFetchCollections?: (connection: DatabaseConnection) => Promise<string[]>; // For MongoDB collections or PostgreSQL tables
|
|
29
|
+
onFetchColumns?: (connection: DatabaseConnection, collection?: string) => Promise<string[]>; // collection/table name optional
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Backend request format
|
|
33
|
+
export interface BackendConnectionRequest {
|
|
34
|
+
databaseType: 'mongodb' | 'postgresql';
|
|
35
|
+
connection: Omit<DatabaseConnection, 'type'>;
|
|
36
|
+
}
|
|
@@ -0,0 +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
|
+
}
|