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