nextjs-chatbot-ui 1.2.0 → 1.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/components/AdminSetup.tsx +489 -384
- package/index.tsx +1 -1
- package/package.json +1 -1
- package/types/admin.ts +42 -34
|
@@ -1,22 +1,31 @@
|
|
|
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
|
|
|
8
8
|
const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
9
9
|
onSave,
|
|
10
10
|
onTestConnection,
|
|
11
|
+
onFetchCollections,
|
|
11
12
|
onFetchColumns,
|
|
13
|
+
onProcessEmbeddings,
|
|
12
14
|
}) => {
|
|
13
15
|
const [isModalOpen, setIsModalOpen] = useState(false);
|
|
14
|
-
const [currentStep, setCurrentStep] = useState<'connection' | 'columns'>('connection');
|
|
16
|
+
const [currentStep, setCurrentStep] = useState<'connection' | 'collection' | 'columns' | 'processing'>('connection');
|
|
15
17
|
const [isConnecting, setIsConnecting] = useState(false);
|
|
16
18
|
const [connectionError, setConnectionError] = useState<string | null>(null);
|
|
17
19
|
const [connectionSuccess, setConnectionSuccess] = useState(false);
|
|
18
|
-
const [
|
|
19
|
-
const [
|
|
20
|
+
const [availableCollections, setAvailableCollections] = useState<string[]>([]);
|
|
21
|
+
const [selectedCollections, setSelectedCollections] = useState<string[]>([]);
|
|
22
|
+
const [collectionColumns, setCollectionColumns] = useState<Record<string, string[]>>({});
|
|
23
|
+
const [selectedColumns, setSelectedColumns] = useState<Record<string, string[]>>({});
|
|
24
|
+
const [isLoadingCollections, setIsLoadingCollections] = useState(false);
|
|
25
|
+
const [loadingColumnsFor, setLoadingColumnsFor] = useState<string[]>([]);
|
|
26
|
+
const [isProcessing, setIsProcessing] = useState(false);
|
|
27
|
+
const [processingStatus, setProcessingStatus] = useState<string>('');
|
|
28
|
+
const [processingError, setProcessingError] = useState<string | null>(null);
|
|
20
29
|
|
|
21
30
|
const [dbType, setDbType] = useState<DatabaseType>('mongodb');
|
|
22
31
|
const [connection, setConnection] = useState<DatabaseConnection>({
|
|
@@ -30,12 +39,6 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
30
39
|
ssl: false,
|
|
31
40
|
});
|
|
32
41
|
|
|
33
|
-
const [columnSelection, setColumnSelection] = useState<ColumnSelection>({
|
|
34
|
-
embeddingColumns: [],
|
|
35
|
-
llmColumns: [],
|
|
36
|
-
chromaColumns: [],
|
|
37
|
-
});
|
|
38
|
-
|
|
39
42
|
const handleDbTypeChange = (type: DatabaseType) => {
|
|
40
43
|
setDbType(type);
|
|
41
44
|
setConnection({
|
|
@@ -60,41 +63,31 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
60
63
|
setConnectionSuccess(false);
|
|
61
64
|
|
|
62
65
|
try {
|
|
63
|
-
// Ensure default ports are set
|
|
64
66
|
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
65
|
-
|
|
66
|
-
// Transform to backend format
|
|
67
67
|
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
68
68
|
|
|
69
69
|
let isValid: boolean;
|
|
70
70
|
|
|
71
71
|
if (onTestConnection) {
|
|
72
|
-
// Use provided handler - send normalized connection
|
|
73
72
|
isValid = await onTestConnection(normalizedConnection);
|
|
74
73
|
} else {
|
|
75
|
-
// Default: Try to call backend API
|
|
76
74
|
try {
|
|
77
75
|
const response = await fetch('/api/database/test', {
|
|
78
76
|
method: 'POST',
|
|
79
|
-
headers: {
|
|
80
|
-
|
|
81
|
-
},
|
|
82
|
-
body: JSON.stringify(backendRequest), // Send transformed format
|
|
77
|
+
headers: { 'Content-Type': 'application/json' },
|
|
78
|
+
body: JSON.stringify(backendRequest),
|
|
83
79
|
});
|
|
84
80
|
|
|
85
81
|
if (!response.ok) {
|
|
86
82
|
const errorData = await response.json().catch(() => ({}));
|
|
87
|
-
throw new Error(errorData.message || `HTTP ${response.status}
|
|
83
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
88
84
|
}
|
|
89
85
|
|
|
90
86
|
const data = await response.json();
|
|
91
87
|
isValid = data.success === true || response.ok;
|
|
92
88
|
} catch (fetchError: any) {
|
|
93
|
-
// If API endpoint doesn't exist, show helpful error
|
|
94
89
|
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
|
-
);
|
|
90
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/test endpoint.');
|
|
98
91
|
}
|
|
99
92
|
throw fetchError;
|
|
100
93
|
}
|
|
@@ -105,13 +98,12 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
105
98
|
setConnectionSuccess(true);
|
|
106
99
|
return true;
|
|
107
100
|
} else {
|
|
108
|
-
setConnectionError('Connection failed. Please check your credentials
|
|
101
|
+
setConnectionError('Connection failed. Please check your credentials.');
|
|
109
102
|
setConnectionSuccess(false);
|
|
110
103
|
return false;
|
|
111
104
|
}
|
|
112
105
|
} catch (error: any) {
|
|
113
|
-
|
|
114
|
-
setConnectionError(errorMessage);
|
|
106
|
+
setConnectionError(error.message || 'Connection failed. Please try again.');
|
|
115
107
|
setConnectionSuccess(false);
|
|
116
108
|
console.error('Database connection error:', error);
|
|
117
109
|
return false;
|
|
@@ -120,138 +112,299 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
120
112
|
}
|
|
121
113
|
};
|
|
122
114
|
|
|
123
|
-
const
|
|
124
|
-
|
|
115
|
+
const handleFetchCollections = async (): Promise<string[]> => {
|
|
116
|
+
setIsLoadingCollections(true);
|
|
125
117
|
setConnectionError(null);
|
|
126
118
|
|
|
127
119
|
try {
|
|
128
|
-
// Ensure default ports are set
|
|
129
120
|
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
121
|
+
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
122
|
+
|
|
123
|
+
let collections: string[];
|
|
124
|
+
|
|
125
|
+
if (onFetchCollections) {
|
|
126
|
+
collections = await onFetchCollections(normalizedConnection);
|
|
127
|
+
} else {
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch('/api/database/collections', {
|
|
130
|
+
method: 'POST',
|
|
131
|
+
headers: { 'Content-Type': 'application/json' },
|
|
132
|
+
body: JSON.stringify(backendRequest),
|
|
133
|
+
});
|
|
134
|
+
|
|
135
|
+
if (!response.ok) {
|
|
136
|
+
const errorData = await response.json().catch(() => ({}));
|
|
137
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
const data = await response.json();
|
|
141
|
+
collections = data.collections || data.tables || data.columnNames || [];
|
|
142
|
+
} catch (fetchError: any) {
|
|
143
|
+
if (fetchError.message?.includes('Failed to fetch') || fetchError.message?.includes('404')) {
|
|
144
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/collections endpoint.');
|
|
145
|
+
}
|
|
146
|
+
throw fetchError;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (collections && collections.length > 0) {
|
|
151
|
+
setAvailableCollections(collections);
|
|
152
|
+
setIsLoadingCollections(false);
|
|
153
|
+
return collections;
|
|
154
|
+
} else {
|
|
155
|
+
setConnectionError('No collections found in the database.');
|
|
156
|
+
setIsLoadingCollections(false);
|
|
157
|
+
return [];
|
|
158
|
+
}
|
|
159
|
+
} catch (error: any) {
|
|
160
|
+
setConnectionError(error.message || 'Failed to fetch collections.');
|
|
161
|
+
setIsLoadingCollections(false);
|
|
162
|
+
console.error('Fetch collections error:', error);
|
|
163
|
+
return [];
|
|
164
|
+
}
|
|
165
|
+
};
|
|
130
166
|
|
|
131
|
-
|
|
167
|
+
const handleFetchColumns = async (collectionName: string): Promise<string[]> => {
|
|
168
|
+
setLoadingColumnsFor(prev => [...prev, collectionName]);
|
|
169
|
+
setConnectionError(null);
|
|
170
|
+
|
|
171
|
+
try {
|
|
172
|
+
const normalizedConnection = normalizeConnection(connection, dbType);
|
|
132
173
|
const backendRequest = transformConnectionForBackend(connection, dbType);
|
|
133
174
|
|
|
134
175
|
let columns: string[];
|
|
135
176
|
|
|
136
177
|
if (onFetchColumns) {
|
|
137
|
-
|
|
138
|
-
columns = await onFetchColumns(normalizedConnection);
|
|
178
|
+
columns = await onFetchColumns(normalizedConnection, collectionName);
|
|
139
179
|
} else {
|
|
140
|
-
// Default: Try to call backend API
|
|
141
180
|
try {
|
|
142
181
|
const response = await fetch('/api/database/columns', {
|
|
143
182
|
method: 'POST',
|
|
144
|
-
headers: {
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
183
|
+
headers: { 'Content-Type': 'application/json' },
|
|
184
|
+
body: JSON.stringify({
|
|
185
|
+
...backendRequest,
|
|
186
|
+
collection: collectionName,
|
|
187
|
+
}),
|
|
148
188
|
});
|
|
149
189
|
|
|
150
190
|
if (!response.ok) {
|
|
151
191
|
const errorData = await response.json().catch(() => ({}));
|
|
152
|
-
throw new Error(errorData.message || `HTTP ${response.status}
|
|
192
|
+
throw new Error(errorData.message || `HTTP ${response.status}`);
|
|
153
193
|
}
|
|
154
194
|
|
|
155
195
|
const data = await response.json();
|
|
156
196
|
columns = data.columns || data.columnNames || [];
|
|
157
197
|
} catch (fetchError: any) {
|
|
158
|
-
// If API endpoint doesn't exist, show helpful error
|
|
159
198
|
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
|
-
);
|
|
199
|
+
throw new Error('Backend API endpoint not found. Please implement POST /api/database/columns endpoint.');
|
|
163
200
|
}
|
|
164
201
|
throw fetchError;
|
|
165
202
|
}
|
|
166
203
|
}
|
|
167
204
|
|
|
168
205
|
if (columns && columns.length > 0) {
|
|
169
|
-
|
|
170
|
-
|
|
206
|
+
setCollectionColumns(prev => ({ ...prev, [collectionName]: columns }));
|
|
207
|
+
setSelectedColumns(prev => ({ ...prev, [collectionName]: [] }));
|
|
208
|
+
setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
|
|
171
209
|
return columns;
|
|
172
210
|
} else {
|
|
173
|
-
setConnectionError(
|
|
174
|
-
|
|
211
|
+
setConnectionError(`No columns found in ${collectionName}.`);
|
|
212
|
+
setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
|
|
175
213
|
return [];
|
|
176
214
|
}
|
|
177
215
|
} catch (error: any) {
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
setIsLoadingColumns(false);
|
|
216
|
+
setConnectionError(error.message || 'Failed to fetch columns.');
|
|
217
|
+
setLoadingColumnsFor(prev => prev.filter(c => c !== collectionName));
|
|
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
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
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
|
+
});
|
|
221
263
|
} else {
|
|
222
|
-
//
|
|
223
|
-
|
|
264
|
+
// Select collection and fetch its columns
|
|
265
|
+
setSelectedCollections(prev => [...prev, collection]);
|
|
266
|
+
await handleFetchColumns(collection);
|
|
224
267
|
}
|
|
225
268
|
};
|
|
226
269
|
|
|
227
|
-
const handleColumnToggle = (
|
|
228
|
-
|
|
229
|
-
const
|
|
230
|
-
const isSelected =
|
|
270
|
+
const handleColumnToggle = (collection: string, column: string) => {
|
|
271
|
+
setSelectedColumns(prev => {
|
|
272
|
+
const collectionCols = prev[collection] || [];
|
|
273
|
+
const isSelected = collectionCols.includes(column);
|
|
231
274
|
|
|
232
275
|
return {
|
|
233
276
|
...prev,
|
|
234
|
-
[
|
|
235
|
-
?
|
|
236
|
-
: [...
|
|
277
|
+
[collection]: isSelected
|
|
278
|
+
? collectionCols.filter(c => c !== column)
|
|
279
|
+
: [...collectionCols, column],
|
|
237
280
|
};
|
|
238
281
|
});
|
|
239
282
|
};
|
|
240
283
|
|
|
241
|
-
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
|
+
|
|
242
308
|
const config: DatabaseConfig = {
|
|
243
309
|
connection,
|
|
244
|
-
columnSelection
|
|
310
|
+
columnSelection: {
|
|
311
|
+
embeddingColumns: allQueryColumns,
|
|
312
|
+
llmColumns: [],
|
|
313
|
+
chromaColumns: [],
|
|
314
|
+
},
|
|
315
|
+
collections,
|
|
245
316
|
};
|
|
246
317
|
|
|
318
|
+
// Save configuration first
|
|
247
319
|
if (onSave) {
|
|
248
320
|
onSave(config);
|
|
249
321
|
}
|
|
250
|
-
|
|
251
|
-
//
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
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
|
+
}
|
|
255
408
|
};
|
|
256
409
|
|
|
257
410
|
const handleClose = () => {
|
|
@@ -259,125 +412,74 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
259
412
|
setCurrentStep('connection');
|
|
260
413
|
setConnectionError(null);
|
|
261
414
|
setConnectionSuccess(false);
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
chromaColumns: [],
|
|
267
|
-
});
|
|
415
|
+
setAvailableCollections([]);
|
|
416
|
+
setSelectedCollections([]);
|
|
417
|
+
setCollectionColumns({});
|
|
418
|
+
setSelectedColumns({});
|
|
268
419
|
};
|
|
269
420
|
|
|
270
421
|
return (
|
|
271
422
|
<>
|
|
272
|
-
{/* Sidebar Button/Item - This can be integrated into admin sidebar */}
|
|
273
423
|
<button
|
|
274
424
|
onClick={() => setIsModalOpen(true)}
|
|
275
|
-
className="w-full flex items-center gap-3 px-4 py-2.5 text-left hover:bg-gray-
|
|
425
|
+
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
426
|
>
|
|
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
|
-
/>
|
|
427
|
+
<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">
|
|
428
|
+
<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
429
|
</svg>
|
|
291
|
-
<span className="
|
|
430
|
+
<span className="font-medium">Database Setup</span>
|
|
292
431
|
</button>
|
|
293
432
|
|
|
294
|
-
{/* Modal */}
|
|
295
433
|
{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'
|
|
434
|
+
<div className="fixed inset-0 z-50 flex items-center justify-center bg-black/50 backdrop-blur-sm">
|
|
435
|
+
<div className="bg-white rounded-xl shadow-2xl w-full max-w-2xl max-h-[90vh] overflow-hidden flex flex-col">
|
|
436
|
+
{/* Header */}
|
|
437
|
+
<div className="px-6 py-4 border-b border-gray-100 flex items-center justify-between bg-gray-50/50">
|
|
438
|
+
<h2 className="text-lg font-semibold text-gray-900">
|
|
439
|
+
{currentStep === 'connection' && 'Database Connection'}
|
|
440
|
+
{currentStep === 'collection' && 'Select Collections'}
|
|
441
|
+
{currentStep === 'columns' && 'Select Query Columns'}
|
|
442
|
+
{currentStep === 'processing' && 'Processing Embeddings'}
|
|
302
443
|
</h2>
|
|
303
444
|
<button
|
|
304
445
|
onClick={handleClose}
|
|
305
|
-
className="text-gray-400 hover:text-gray-600 transition-colors"
|
|
446
|
+
className="text-gray-400 hover:text-gray-600 transition-colors p-1 rounded hover:bg-gray-100"
|
|
306
447
|
>
|
|
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
|
-
/>
|
|
448
|
+
<svg xmlns="http://www.w3.org/2000/svg" className="h-5 w-5" fill="none" viewBox="0 0 24 24" stroke="currentColor">
|
|
449
|
+
<path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" />
|
|
320
450
|
</svg>
|
|
321
451
|
</button>
|
|
322
452
|
</div>
|
|
323
453
|
|
|
324
|
-
{/*
|
|
325
|
-
<div className="flex-1 overflow-y-auto px-6 py-
|
|
326
|
-
{currentStep === 'connection'
|
|
327
|
-
<div className="space-y-
|
|
328
|
-
{/* Database Type
|
|
454
|
+
{/* Content */}
|
|
455
|
+
<div className="flex-1 overflow-y-auto px-6 py-6">
|
|
456
|
+
{currentStep === 'connection' && (
|
|
457
|
+
<div className="space-y-5">
|
|
458
|
+
{/* Database Type */}
|
|
329
459
|
<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">
|
|
460
|
+
<label className="block text-sm font-medium text-gray-700 mb-2">Database Type</label>
|
|
461
|
+
<div className="flex gap-3">
|
|
334
462
|
<button
|
|
335
463
|
onClick={() => handleDbTypeChange('mongodb')}
|
|
336
464
|
className={clsx(
|
|
337
|
-
'
|
|
465
|
+
'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
|
|
338
466
|
dbType === 'mongodb'
|
|
339
|
-
? 'border-blue-500 bg-blue-50'
|
|
340
|
-
: 'border-gray-200 hover:border-gray-300'
|
|
467
|
+
? 'border-blue-500 bg-blue-50 text-blue-700'
|
|
468
|
+
: 'border-gray-200 text-gray-700 hover:border-gray-300'
|
|
341
469
|
)}
|
|
342
470
|
>
|
|
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>
|
|
471
|
+
MongoDB
|
|
357
472
|
</button>
|
|
358
473
|
<button
|
|
359
474
|
onClick={() => handleDbTypeChange('postgres')}
|
|
360
475
|
className={clsx(
|
|
361
|
-
'
|
|
476
|
+
'flex-1 px-4 py-3 rounded-lg border-2 transition-all text-sm font-medium',
|
|
362
477
|
dbType === 'postgres'
|
|
363
|
-
? 'border-blue-500 bg-blue-50'
|
|
364
|
-
: 'border-gray-200 hover:border-gray-300'
|
|
478
|
+
? 'border-blue-500 bg-blue-50 text-blue-700'
|
|
479
|
+
: 'border-gray-200 text-gray-700 hover:border-gray-300'
|
|
365
480
|
)}
|
|
366
481
|
>
|
|
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>
|
|
482
|
+
PostgreSQL
|
|
381
483
|
</button>
|
|
382
484
|
</div>
|
|
383
485
|
</div>
|
|
@@ -386,320 +488,323 @@ const AdminSetup: React.FC<AdminSetupProps> = ({
|
|
|
386
488
|
{dbType === 'mongodb' ? (
|
|
387
489
|
<div className="space-y-4">
|
|
388
490
|
<div>
|
|
389
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
390
|
-
Connection String (Recommended)
|
|
391
|
-
</label>
|
|
491
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Connection String</label>
|
|
392
492
|
<input
|
|
393
493
|
type="text"
|
|
394
494
|
value={connection.connectionString || ''}
|
|
395
495
|
onChange={(e) => handleConnectionChange('connectionString', e.target.value)}
|
|
396
|
-
placeholder="mongodb://
|
|
397
|
-
className="w-full px-
|
|
496
|
+
placeholder="mongodb://host:port/database"
|
|
497
|
+
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
498
|
/>
|
|
399
|
-
<p className="mt-1 text-xs text-gray-500">
|
|
400
|
-
Or fill individual fields below
|
|
401
|
-
</p>
|
|
402
499
|
</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>
|
|
500
|
+
<div className="text-xs text-gray-500 text-center">or</div>
|
|
501
|
+
<div className="grid grid-cols-2 gap-3">
|
|
502
|
+
<div>
|
|
503
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
|
|
434
504
|
<input
|
|
435
505
|
type="text"
|
|
436
|
-
value={connection.
|
|
437
|
-
onChange={(e) => handleConnectionChange('
|
|
438
|
-
placeholder="
|
|
439
|
-
className="w-full px-
|
|
506
|
+
value={connection.host}
|
|
507
|
+
onChange={(e) => handleConnectionChange('host', e.target.value)}
|
|
508
|
+
placeholder="localhost"
|
|
509
|
+
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
510
|
/>
|
|
441
511
|
</div>
|
|
442
|
-
<div
|
|
443
|
-
<label className="
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
</label>
|
|
512
|
+
<div>
|
|
513
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
|
|
514
|
+
<input
|
|
515
|
+
type="number"
|
|
516
|
+
value={connection.port}
|
|
517
|
+
onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 27017)}
|
|
518
|
+
placeholder="27017"
|
|
519
|
+
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"
|
|
520
|
+
/>
|
|
452
521
|
</div>
|
|
453
522
|
</div>
|
|
523
|
+
<div>
|
|
524
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
|
|
525
|
+
<input
|
|
526
|
+
type="text"
|
|
527
|
+
value={connection.database}
|
|
528
|
+
onChange={(e) => handleConnectionChange('database', e.target.value)}
|
|
529
|
+
placeholder="database_name"
|
|
530
|
+
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"
|
|
531
|
+
/>
|
|
532
|
+
</div>
|
|
454
533
|
</div>
|
|
455
534
|
) : (
|
|
456
535
|
<div className="space-y-4">
|
|
457
|
-
<div className="grid grid-cols-2 gap-
|
|
536
|
+
<div className="grid grid-cols-2 gap-3">
|
|
458
537
|
<div>
|
|
459
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
460
|
-
Host *
|
|
461
|
-
</label>
|
|
538
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Host</label>
|
|
462
539
|
<input
|
|
463
540
|
type="text"
|
|
464
541
|
value={connection.host}
|
|
465
542
|
onChange={(e) => handleConnectionChange('host', e.target.value)}
|
|
466
543
|
placeholder="localhost"
|
|
467
|
-
className="w-full px-
|
|
544
|
+
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
545
|
/>
|
|
469
546
|
</div>
|
|
470
547
|
<div>
|
|
471
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
472
|
-
Port *
|
|
473
|
-
</label>
|
|
548
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Port</label>
|
|
474
549
|
<input
|
|
475
550
|
type="number"
|
|
476
551
|
value={connection.port}
|
|
477
552
|
onChange={(e) => handleConnectionChange('port', parseInt(e.target.value) || 5432)}
|
|
478
553
|
placeholder="5432"
|
|
479
|
-
className="w-full px-
|
|
554
|
+
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
555
|
/>
|
|
481
556
|
</div>
|
|
482
557
|
</div>
|
|
483
558
|
<div>
|
|
484
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
485
|
-
Database Name *
|
|
486
|
-
</label>
|
|
559
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Database</label>
|
|
487
560
|
<input
|
|
488
561
|
type="text"
|
|
489
562
|
value={connection.database}
|
|
490
563
|
onChange={(e) => handleConnectionChange('database', e.target.value)}
|
|
491
|
-
placeholder="
|
|
492
|
-
className="w-full px-
|
|
564
|
+
placeholder="database_name"
|
|
565
|
+
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
566
|
/>
|
|
494
567
|
</div>
|
|
495
|
-
<div className="grid grid-cols-2 gap-
|
|
568
|
+
<div className="grid grid-cols-2 gap-3">
|
|
496
569
|
<div>
|
|
497
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
498
|
-
Username *
|
|
499
|
-
</label>
|
|
570
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Username</label>
|
|
500
571
|
<input
|
|
501
572
|
type="text"
|
|
502
573
|
value={connection.username || ''}
|
|
503
574
|
onChange={(e) => handleConnectionChange('username', e.target.value)}
|
|
504
|
-
placeholder="
|
|
505
|
-
className="w-full px-
|
|
575
|
+
placeholder="username"
|
|
576
|
+
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
577
|
/>
|
|
507
578
|
</div>
|
|
508
579
|
<div>
|
|
509
|
-
<label className="block text-sm font-medium text-gray-700 mb-
|
|
510
|
-
Password *
|
|
511
|
-
</label>
|
|
580
|
+
<label className="block text-sm font-medium text-gray-700 mb-1.5">Password</label>
|
|
512
581
|
<input
|
|
513
582
|
type="password"
|
|
514
583
|
value={connection.password || ''}
|
|
515
584
|
onChange={(e) => handleConnectionChange('password', e.target.value)}
|
|
516
|
-
placeholder="
|
|
517
|
-
className="w-full px-
|
|
585
|
+
placeholder="••••••"
|
|
586
|
+
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
587
|
/>
|
|
519
588
|
</div>
|
|
520
589
|
</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
590
|
</div>
|
|
533
591
|
)}
|
|
534
592
|
|
|
535
593
|
{connectionError && (
|
|
536
|
-
<div className="bg-red-50 border border-red-200 rounded-lg p-
|
|
537
|
-
<p className="text-sm text-red-
|
|
594
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
595
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
538
596
|
</div>
|
|
539
597
|
)}
|
|
540
598
|
{connectionSuccess && !connectionError && !isConnecting && (
|
|
541
|
-
<div className="bg-green-50 border border-green-200 rounded-lg p-
|
|
542
|
-
<p className="text-sm text-green-
|
|
599
|
+
<div className="bg-green-50 border border-green-200 rounded-lg p-3">
|
|
600
|
+
<p className="text-sm text-green-700">✓ Connection successful</p>
|
|
543
601
|
</div>
|
|
544
602
|
)}
|
|
545
603
|
</div>
|
|
546
|
-
)
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
552
|
-
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
|
|
562
|
-
|
|
563
|
-
|
|
564
|
-
|
|
604
|
+
)}
|
|
605
|
+
|
|
606
|
+
{currentStep === 'collection' && (
|
|
607
|
+
<div className="space-y-4">
|
|
608
|
+
<div>
|
|
609
|
+
<p className="text-sm text-gray-600 mb-4">Select one or more collections to configure for embeddings:</p>
|
|
610
|
+
{isLoadingCollections ? (
|
|
611
|
+
<div className="flex items-center justify-center py-8">
|
|
612
|
+
<div className="animate-spin rounded-full h-6 w-6 border-b-2 border-blue-600"></div>
|
|
613
|
+
</div>
|
|
614
|
+
) : (
|
|
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
|
|
565
652
|
</p>
|
|
566
653
|
</div>
|
|
654
|
+
)}
|
|
655
|
+
</div>
|
|
656
|
+
{connectionError && (
|
|
657
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
658
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
659
|
+
</div>
|
|
660
|
+
)}
|
|
661
|
+
</div>
|
|
662
|
+
)}
|
|
567
663
|
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
|
|
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>
|
|
664
|
+
{currentStep === 'columns' && (
|
|
665
|
+
<div className="space-y-6">
|
|
666
|
+
<div>
|
|
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>
|
|
673
|
+
</div>
|
|
600
674
|
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
<div className="
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
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) => (
|
|
612
695
|
<label
|
|
613
|
-
key={
|
|
696
|
+
key={column}
|
|
614
697
|
className="flex items-center gap-2 cursor-pointer hover:bg-white p-2 rounded transition-colors"
|
|
615
698
|
>
|
|
616
699
|
<input
|
|
617
700
|
type="checkbox"
|
|
618
|
-
checked={
|
|
619
|
-
onChange={() => handleColumnToggle(
|
|
701
|
+
checked={selectedCols.includes(column)}
|
|
702
|
+
onChange={() => handleColumnToggle(collection, column)}
|
|
620
703
|
className="rounded border-gray-300 text-blue-600 focus:ring-blue-500"
|
|
621
704
|
/>
|
|
622
705
|
<span className="text-sm text-gray-700">{column}</span>
|
|
623
706
|
</label>
|
|
624
|
-
))
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
<p className="text-xs text-green-600 mt-1">
|
|
629
|
-
{columnSelection.llmColumns.length} column{columnSelection.llmColumns.length !== 1 ? 's' : ''} selected
|
|
630
|
-
</p>
|
|
707
|
+
))}
|
|
708
|
+
</div>
|
|
709
|
+
) : (
|
|
710
|
+
<p className="text-sm text-gray-500">No columns available</p>
|
|
631
711
|
)}
|
|
632
712
|
</div>
|
|
713
|
+
);
|
|
714
|
+
})}
|
|
633
715
|
|
|
634
|
-
|
|
635
|
-
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
<span className="text-sm text-gray-700">{column}</span>
|
|
656
|
-
</label>
|
|
657
|
-
))
|
|
658
|
-
)}
|
|
716
|
+
{connectionError && (
|
|
717
|
+
<div className="bg-red-50 border border-red-200 rounded-lg p-3">
|
|
718
|
+
<p className="text-sm text-red-700">{connectionError}</p>
|
|
719
|
+
</div>
|
|
720
|
+
)}
|
|
721
|
+
</div>
|
|
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>
|
|
659
737
|
</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
738
|
</div>
|
|
666
|
-
|
|
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>
|
|
667
753
|
)}
|
|
668
754
|
</div>
|
|
669
755
|
)}
|
|
670
756
|
</div>
|
|
671
757
|
|
|
672
|
-
{/*
|
|
673
|
-
<div className="px-6 py-4 border-t border-gray-
|
|
758
|
+
{/* Footer */}
|
|
759
|
+
<div className="px-6 py-4 border-t border-gray-100 bg-gray-50/50 flex items-center justify-between">
|
|
674
760
|
<button
|
|
675
|
-
onClick={currentStep === '
|
|
676
|
-
|
|
761
|
+
onClick={currentStep === 'connection' ? handleClose : currentStep === 'processing' ? undefined : () => {
|
|
762
|
+
if (currentStep === 'columns') {
|
|
763
|
+
setCurrentStep('collection');
|
|
764
|
+
} else {
|
|
765
|
+
setCurrentStep('connection');
|
|
766
|
+
}
|
|
767
|
+
}}
|
|
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"
|
|
677
770
|
>
|
|
678
|
-
{currentStep === '
|
|
771
|
+
{currentStep === 'connection' ? 'Cancel' : currentStep === 'processing' ? 'Processing...' : 'Back'}
|
|
679
772
|
</button>
|
|
680
773
|
<div className="flex gap-3">
|
|
681
|
-
{currentStep === 'connection'
|
|
774
|
+
{currentStep === 'connection' && (
|
|
682
775
|
<button
|
|
683
|
-
onClick={
|
|
684
|
-
disabled={isConnecting ||
|
|
776
|
+
onClick={handleConnect}
|
|
777
|
+
disabled={isConnecting || isLoadingCollections}
|
|
685
778
|
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
779
|
>
|
|
687
|
-
{isConnecting &&
|
|
688
|
-
|
|
689
|
-
)}
|
|
690
|
-
{isLoadingColumns ? 'Loading Columns...' : isConnecting ? 'Connecting...' : 'Connect & Next'}
|
|
780
|
+
{isConnecting && <div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white"></div>}
|
|
781
|
+
{isConnecting ? 'Connecting...' : 'Connect'}
|
|
691
782
|
</button>
|
|
692
|
-
)
|
|
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
|
+
)}
|
|
793
|
+
{currentStep === 'columns' && (
|
|
693
794
|
<button
|
|
694
795
|
onClick={handleSave}
|
|
695
|
-
disabled={
|
|
696
|
-
columnSelection.embeddingColumns.length === 0 &&
|
|
697
|
-
columnSelection.llmColumns.length === 0 &&
|
|
698
|
-
columnSelection.chromaColumns.length === 0
|
|
699
|
-
}
|
|
796
|
+
disabled={Object.values(selectedColumns).every(cols => cols.length === 0)}
|
|
700
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"
|
|
701
798
|
>
|
|
702
|
-
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
|
|
703
808
|
</button>
|
|
704
809
|
)}
|
|
705
810
|
</div>
|
package/index.tsx
CHANGED
|
@@ -2,4 +2,4 @@ export { default as Chatbot } from './components/Chatbot';
|
|
|
2
2
|
export type { ChatbotConfig, Message, ChatbotProps } from './types';
|
|
3
3
|
|
|
4
4
|
export { default as AdminSetup } from './components/AdminSetup';
|
|
5
|
-
export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, AdminSetupProps } from './types/admin';
|
|
5
|
+
export type { DatabaseType, DatabaseConnection, ColumnSelection, DatabaseConfig, CollectionColumnMapping, AdminSetupProps } from './types/admin';
|
package/package.json
CHANGED
package/types/admin.ts
CHANGED
|
@@ -1,35 +1,43 @@
|
|
|
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
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export interface
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
}
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
connection:
|
|
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 CollectionColumnMapping {
|
|
21
|
+
collection: string;
|
|
22
|
+
columns: string[];
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
export interface DatabaseConfig {
|
|
26
|
+
connection: DatabaseConnection;
|
|
27
|
+
columnSelection: ColumnSelection;
|
|
28
|
+
collections?: CollectionColumnMapping[]; // Optional: for multiple collections
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface AdminSetupProps {
|
|
32
|
+
onSave?: (config: DatabaseConfig) => void;
|
|
33
|
+
onTestConnection?: (connection: DatabaseConnection) => Promise<boolean>;
|
|
34
|
+
onFetchCollections?: (connection: DatabaseConnection) => Promise<string[]>; // For MongoDB collections or PostgreSQL tables
|
|
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
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// Backend request format
|
|
40
|
+
export interface BackendConnectionRequest {
|
|
41
|
+
databaseType: 'mongodb' | 'postgresql';
|
|
42
|
+
connection: Omit<DatabaseConnection, 'type'>;
|
|
35
43
|
}
|