vector-inspector 0.2.6__py3-none-any.whl → 0.2.7__py3-none-any.whl
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.
- vector_inspector/core/cache_manager.py +159 -0
- vector_inspector/core/connection_manager.py +277 -0
- vector_inspector/core/connections/chroma_connection.py +90 -5
- vector_inspector/core/connections/qdrant_connection.py +62 -8
- vector_inspector/core/embedding_utils.py +140 -0
- vector_inspector/services/backup_restore_service.py +3 -29
- vector_inspector/services/credential_service.py +130 -0
- vector_inspector/services/filter_service.py +1 -1
- vector_inspector/services/profile_service.py +409 -0
- vector_inspector/services/settings_service.py +19 -0
- vector_inspector/ui/components/connection_manager_panel.py +320 -0
- vector_inspector/ui/components/profile_manager_panel.py +518 -0
- vector_inspector/ui/dialogs/__init__.py +5 -0
- vector_inspector/ui/dialogs/cross_db_migration.py +364 -0
- vector_inspector/ui/dialogs/embedding_config_dialog.py +176 -0
- vector_inspector/ui/main_window.py +425 -190
- vector_inspector/ui/views/info_panel.py +225 -55
- vector_inspector/ui/views/metadata_view.py +71 -3
- vector_inspector/ui/views/search_view.py +43 -3
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/METADATA +3 -1
- vector_inspector-0.2.7.dist-info/RECORD +45 -0
- vector_inspector-0.2.6.dist-info/RECORD +0 -35
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/WHEEL +0 -0
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,364 @@
|
|
|
1
|
+
"""Cross-database operations for migrating data between vector databases."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, List, Dict, Any
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
import tempfile
|
|
6
|
+
from PySide6.QtWidgets import (
|
|
7
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
|
|
8
|
+
QPushButton, QProgressBar, QTextEdit, QGroupBox, QFormLayout,
|
|
9
|
+
QSpinBox, QCheckBox, QMessageBox
|
|
10
|
+
)
|
|
11
|
+
from PySide6.QtCore import QThread, Signal
|
|
12
|
+
|
|
13
|
+
from vector_inspector.core.connection_manager import ConnectionManager, ConnectionInstance
|
|
14
|
+
from vector_inspector.services.backup_restore_service import BackupRestoreService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class MigrationThread(QThread):
|
|
18
|
+
"""Background thread for migrating data between databases using backup/restore."""
|
|
19
|
+
|
|
20
|
+
progress = Signal(int, str) # progress percentage, status message
|
|
21
|
+
finished = Signal(bool, str) # success, message
|
|
22
|
+
|
|
23
|
+
def __init__(
|
|
24
|
+
self,
|
|
25
|
+
source_conn: ConnectionInstance,
|
|
26
|
+
target_conn: ConnectionInstance,
|
|
27
|
+
source_collection: str,
|
|
28
|
+
target_collection: str,
|
|
29
|
+
include_embeddings: bool
|
|
30
|
+
):
|
|
31
|
+
super().__init__()
|
|
32
|
+
self.source_conn = source_conn
|
|
33
|
+
self.target_conn = target_conn
|
|
34
|
+
self.source_collection = source_collection
|
|
35
|
+
self.target_collection = target_collection
|
|
36
|
+
self.include_embeddings = include_embeddings
|
|
37
|
+
self._cancelled = False
|
|
38
|
+
self.backup_service = BackupRestoreService()
|
|
39
|
+
|
|
40
|
+
def cancel(self):
|
|
41
|
+
"""Cancel the migration."""
|
|
42
|
+
self._cancelled = True
|
|
43
|
+
|
|
44
|
+
def run(self):
|
|
45
|
+
"""Run the migration using backup and restore."""
|
|
46
|
+
temp_backup_path = None
|
|
47
|
+
try:
|
|
48
|
+
if self._cancelled:
|
|
49
|
+
self.finished.emit(False, "Migration cancelled by user.")
|
|
50
|
+
return
|
|
51
|
+
|
|
52
|
+
# Ensure connections are active
|
|
53
|
+
if not self.source_conn.connection.is_connected:
|
|
54
|
+
self.finished.emit(False, "Source connection is not active.")
|
|
55
|
+
return
|
|
56
|
+
|
|
57
|
+
if not self.target_conn.connection.is_connected:
|
|
58
|
+
self.finished.emit(False, "Target connection is not active.")
|
|
59
|
+
return
|
|
60
|
+
|
|
61
|
+
# Create temporary directory for backup
|
|
62
|
+
temp_dir = tempfile.mkdtemp(prefix="vector_migration_")
|
|
63
|
+
|
|
64
|
+
# Step 1: Create backup of source collection
|
|
65
|
+
self.progress.emit(10, f"Creating backup of {self.source_collection}...")
|
|
66
|
+
|
|
67
|
+
temp_backup_path = self.backup_service.backup_collection(
|
|
68
|
+
self.source_conn.connection,
|
|
69
|
+
self.source_collection,
|
|
70
|
+
temp_dir,
|
|
71
|
+
include_embeddings=self.include_embeddings
|
|
72
|
+
)
|
|
73
|
+
|
|
74
|
+
if not temp_backup_path:
|
|
75
|
+
self.finished.emit(False, "Failed to create backup.")
|
|
76
|
+
return
|
|
77
|
+
|
|
78
|
+
if self._cancelled:
|
|
79
|
+
self.finished.emit(False, "Migration cancelled by user.")
|
|
80
|
+
return
|
|
81
|
+
|
|
82
|
+
# Step 2: Restore to target collection
|
|
83
|
+
self.progress.emit(50, f"Restoring to {self.target_collection}...")
|
|
84
|
+
|
|
85
|
+
# Verify target connection before restore
|
|
86
|
+
if not self.target_conn.connection.is_connected:
|
|
87
|
+
# Try to reconnect
|
|
88
|
+
if not self.target_conn.connection.connect():
|
|
89
|
+
self.finished.emit(False, "Target connection lost. Please try again.")
|
|
90
|
+
return
|
|
91
|
+
|
|
92
|
+
# Check if target collection exists
|
|
93
|
+
target_exists = self.target_collection in self.target_conn.collections
|
|
94
|
+
|
|
95
|
+
success = self.backup_service.restore_collection(
|
|
96
|
+
self.target_conn.connection,
|
|
97
|
+
temp_backup_path,
|
|
98
|
+
collection_name=self.target_collection,
|
|
99
|
+
overwrite=target_exists
|
|
100
|
+
)
|
|
101
|
+
|
|
102
|
+
if self._cancelled:
|
|
103
|
+
self.finished.emit(False, "Migration cancelled by user.")
|
|
104
|
+
return
|
|
105
|
+
|
|
106
|
+
if success:
|
|
107
|
+
self.progress.emit(100, f"Migration complete!")
|
|
108
|
+
self.finished.emit(True, f"Successfully migrated {self.source_collection} to {self.target_collection}")
|
|
109
|
+
else:
|
|
110
|
+
self.finished.emit(False, "Failed to restore to target collection.")
|
|
111
|
+
|
|
112
|
+
except Exception as e:
|
|
113
|
+
import traceback
|
|
114
|
+
error_details = traceback.format_exc()
|
|
115
|
+
print(f"Migration error details:\n{error_details}")
|
|
116
|
+
self.finished.emit(False, f"Migration error: {str(e)}")
|
|
117
|
+
|
|
118
|
+
finally:
|
|
119
|
+
# Clean up temporary backup file
|
|
120
|
+
if temp_backup_path:
|
|
121
|
+
try:
|
|
122
|
+
Path(temp_backup_path).unlink()
|
|
123
|
+
# Also remove temp directory if empty
|
|
124
|
+
temp_dir = Path(temp_backup_path).parent
|
|
125
|
+
if temp_dir.exists() and not list(temp_dir.iterdir()):
|
|
126
|
+
temp_dir.rmdir()
|
|
127
|
+
except Exception:
|
|
128
|
+
pass # Ignore cleanup errors
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
class CrossDatabaseMigrationDialog(QDialog):
|
|
132
|
+
"""Dialog for migrating data between vector databases."""
|
|
133
|
+
|
|
134
|
+
def __init__(self, connection_manager: ConnectionManager, parent=None):
|
|
135
|
+
super().__init__(parent)
|
|
136
|
+
self.connection_manager = connection_manager
|
|
137
|
+
self.migration_thread: Optional[MigrationThread] = None
|
|
138
|
+
|
|
139
|
+
self.setWindowTitle("Cross-Database Migration")
|
|
140
|
+
self.setMinimumWidth(600)
|
|
141
|
+
self.setMinimumHeight(400)
|
|
142
|
+
|
|
143
|
+
self._setup_ui()
|
|
144
|
+
self._populate_connections()
|
|
145
|
+
|
|
146
|
+
def _setup_ui(self):
|
|
147
|
+
"""Setup the UI."""
|
|
148
|
+
layout = QVBoxLayout(self)
|
|
149
|
+
|
|
150
|
+
# Source section
|
|
151
|
+
source_group = QGroupBox("Source")
|
|
152
|
+
source_layout = QFormLayout()
|
|
153
|
+
|
|
154
|
+
self.source_connection_combo = QComboBox()
|
|
155
|
+
self.source_connection_combo.currentIndexChanged.connect(self._on_source_connection_changed)
|
|
156
|
+
source_layout.addRow("Connection:", self.source_connection_combo)
|
|
157
|
+
|
|
158
|
+
self.source_collection_combo = QComboBox()
|
|
159
|
+
source_layout.addRow("Collection:", self.source_collection_combo)
|
|
160
|
+
|
|
161
|
+
source_group.setLayout(source_layout)
|
|
162
|
+
layout.addWidget(source_group)
|
|
163
|
+
|
|
164
|
+
# Target section
|
|
165
|
+
target_group = QGroupBox("Target")
|
|
166
|
+
target_layout = QFormLayout()
|
|
167
|
+
|
|
168
|
+
self.target_connection_combo = QComboBox()
|
|
169
|
+
self.target_connection_combo.currentIndexChanged.connect(self._on_target_connection_changed)
|
|
170
|
+
target_layout.addRow("Connection:", self.target_connection_combo)
|
|
171
|
+
|
|
172
|
+
self.target_collection_combo = QComboBox()
|
|
173
|
+
self.target_collection_combo.setEditable(True)
|
|
174
|
+
target_layout.addRow("Collection:", self.target_collection_combo)
|
|
175
|
+
|
|
176
|
+
self.create_new_check = QCheckBox("Create new collection if it doesn't exist")
|
|
177
|
+
self.create_new_check.setChecked(True)
|
|
178
|
+
target_layout.addRow("", self.create_new_check)
|
|
179
|
+
|
|
180
|
+
target_group.setLayout(target_layout)
|
|
181
|
+
layout.addWidget(target_group)
|
|
182
|
+
|
|
183
|
+
# Options
|
|
184
|
+
options_group = QGroupBox("Options")
|
|
185
|
+
options_layout = QFormLayout()
|
|
186
|
+
|
|
187
|
+
self.include_embeddings_check = QCheckBox("Include Embeddings")
|
|
188
|
+
self.include_embeddings_check.setChecked(True)
|
|
189
|
+
options_layout.addRow("", self.include_embeddings_check)
|
|
190
|
+
|
|
191
|
+
options_group.setLayout(options_layout)
|
|
192
|
+
layout.addWidget(options_group)
|
|
193
|
+
|
|
194
|
+
# Progress section
|
|
195
|
+
self.progress_bar = QProgressBar()
|
|
196
|
+
self.progress_bar.setRange(0, 100)
|
|
197
|
+
self.progress_bar.setValue(0)
|
|
198
|
+
layout.addWidget(self.progress_bar)
|
|
199
|
+
|
|
200
|
+
self.status_text = QTextEdit()
|
|
201
|
+
self.status_text.setReadOnly(True)
|
|
202
|
+
self.status_text.setMaximumHeight(100)
|
|
203
|
+
layout.addWidget(self.status_text)
|
|
204
|
+
|
|
205
|
+
# Buttons
|
|
206
|
+
button_layout = QHBoxLayout()
|
|
207
|
+
|
|
208
|
+
self.start_button = QPushButton("Start Migration")
|
|
209
|
+
self.start_button.clicked.connect(self._start_migration)
|
|
210
|
+
button_layout.addWidget(self.start_button)
|
|
211
|
+
|
|
212
|
+
self.cancel_button = QPushButton("Cancel")
|
|
213
|
+
self.cancel_button.clicked.connect(self._cancel_migration)
|
|
214
|
+
self.cancel_button.setEnabled(False)
|
|
215
|
+
button_layout.addWidget(self.cancel_button)
|
|
216
|
+
|
|
217
|
+
self.close_button = QPushButton("Close")
|
|
218
|
+
self.close_button.clicked.connect(self.close)
|
|
219
|
+
button_layout.addWidget(self.close_button)
|
|
220
|
+
|
|
221
|
+
layout.addLayout(button_layout)
|
|
222
|
+
|
|
223
|
+
def _populate_connections(self):
|
|
224
|
+
"""Populate connection dropdowns."""
|
|
225
|
+
connections = self.connection_manager.get_all_connections()
|
|
226
|
+
|
|
227
|
+
self.source_connection_combo.clear()
|
|
228
|
+
self.target_connection_combo.clear()
|
|
229
|
+
|
|
230
|
+
for conn in connections:
|
|
231
|
+
self.source_connection_combo.addItem(conn.get_display_name(), conn.id)
|
|
232
|
+
self.target_connection_combo.addItem(conn.get_display_name(), conn.id)
|
|
233
|
+
|
|
234
|
+
# Populate collections for first connection
|
|
235
|
+
if connections:
|
|
236
|
+
self._on_source_connection_changed(0)
|
|
237
|
+
self._on_target_connection_changed(0)
|
|
238
|
+
|
|
239
|
+
def _on_source_connection_changed(self, index: int):
|
|
240
|
+
"""Handle source connection change."""
|
|
241
|
+
connection_id = self.source_connection_combo.currentData()
|
|
242
|
+
if connection_id:
|
|
243
|
+
instance = self.connection_manager.get_connection(connection_id)
|
|
244
|
+
if instance:
|
|
245
|
+
self.source_collection_combo.clear()
|
|
246
|
+
self.source_collection_combo.addItems(instance.collections)
|
|
247
|
+
|
|
248
|
+
def _on_target_connection_changed(self, index: int):
|
|
249
|
+
"""Handle target connection change."""
|
|
250
|
+
connection_id = self.target_connection_combo.currentData()
|
|
251
|
+
if connection_id:
|
|
252
|
+
instance = self.connection_manager.get_connection(connection_id)
|
|
253
|
+
if instance:
|
|
254
|
+
self.target_collection_combo.clear()
|
|
255
|
+
self.target_collection_combo.addItems(instance.collections)
|
|
256
|
+
|
|
257
|
+
def _start_migration(self):
|
|
258
|
+
"""Start the migration."""
|
|
259
|
+
# Validate selection
|
|
260
|
+
source_conn_id = self.source_connection_combo.currentData()
|
|
261
|
+
target_conn_id = self.target_connection_combo.currentData()
|
|
262
|
+
|
|
263
|
+
if not source_conn_id or not target_conn_id:
|
|
264
|
+
QMessageBox.warning(self, "Invalid Selection", "Please select both source and target connections.")
|
|
265
|
+
return
|
|
266
|
+
|
|
267
|
+
if source_conn_id == target_conn_id:
|
|
268
|
+
source_coll = self.source_collection_combo.currentText()
|
|
269
|
+
target_coll = self.target_collection_combo.currentText()
|
|
270
|
+
if source_coll == target_coll:
|
|
271
|
+
QMessageBox.warning(
|
|
272
|
+
self,
|
|
273
|
+
"Invalid Selection",
|
|
274
|
+
"Source and target cannot be the same collection in the same connection."
|
|
275
|
+
)
|
|
276
|
+
return
|
|
277
|
+
|
|
278
|
+
source_conn = self.connection_manager.get_connection(source_conn_id)
|
|
279
|
+
target_conn = self.connection_manager.get_connection(target_conn_id)
|
|
280
|
+
|
|
281
|
+
if not source_conn or not target_conn:
|
|
282
|
+
QMessageBox.warning(self, "Error", "Failed to get connection instances.")
|
|
283
|
+
return
|
|
284
|
+
|
|
285
|
+
source_collection = self.source_collection_combo.currentText()
|
|
286
|
+
target_collection = self.target_collection_combo.currentText().strip()
|
|
287
|
+
|
|
288
|
+
if not source_collection or not target_collection:
|
|
289
|
+
QMessageBox.warning(self, "Invalid Selection", "Please select both source and target collections.")
|
|
290
|
+
return
|
|
291
|
+
|
|
292
|
+
# Check if target collection exists
|
|
293
|
+
target_exists = target_collection in target_conn.collections
|
|
294
|
+
|
|
295
|
+
# If target doesn't exist and we're not set to create, warn user
|
|
296
|
+
if not target_exists and not self.create_new_check.isChecked():
|
|
297
|
+
QMessageBox.warning(
|
|
298
|
+
self,
|
|
299
|
+
"Collection Does Not Exist",
|
|
300
|
+
f"Target collection '{target_collection}' does not exist.\n"
|
|
301
|
+
"Please check 'Create new collection' to allow automatic creation during migration."
|
|
302
|
+
)
|
|
303
|
+
return
|
|
304
|
+
|
|
305
|
+
# Confirm
|
|
306
|
+
action = "create and migrate" if not target_exists else "migrate"
|
|
307
|
+
reply = QMessageBox.question(
|
|
308
|
+
self,
|
|
309
|
+
"Confirm Migration",
|
|
310
|
+
f"Migrate data from:\n {source_conn.name}/{source_collection}\n"
|
|
311
|
+
f"to:\n {target_conn.name}/{target_collection}\n\n"
|
|
312
|
+
f"This will {action} all data. Continue?",
|
|
313
|
+
QMessageBox.Yes | QMessageBox.No
|
|
314
|
+
)
|
|
315
|
+
|
|
316
|
+
if reply != QMessageBox.Yes:
|
|
317
|
+
return
|
|
318
|
+
|
|
319
|
+
# Start migration thread
|
|
320
|
+
self.migration_thread = MigrationThread(
|
|
321
|
+
source_conn=source_conn,
|
|
322
|
+
target_conn=target_conn,
|
|
323
|
+
source_collection=source_collection,
|
|
324
|
+
target_collection=target_collection,
|
|
325
|
+
include_embeddings=self.include_embeddings_check.isChecked()
|
|
326
|
+
)
|
|
327
|
+
|
|
328
|
+
self.migration_thread.progress.connect(self._on_migration_progress)
|
|
329
|
+
self.migration_thread.finished.connect(self._on_migration_finished)
|
|
330
|
+
|
|
331
|
+
self.start_button.setEnabled(False)
|
|
332
|
+
self.cancel_button.setEnabled(True)
|
|
333
|
+
self.close_button.setEnabled(False)
|
|
334
|
+
|
|
335
|
+
self.status_text.clear()
|
|
336
|
+
self.progress_bar.setValue(0)
|
|
337
|
+
|
|
338
|
+
self.migration_thread.start()
|
|
339
|
+
|
|
340
|
+
def _cancel_migration(self):
|
|
341
|
+
"""Cancel the migration."""
|
|
342
|
+
if self.migration_thread:
|
|
343
|
+
self.migration_thread.cancel()
|
|
344
|
+
self.status_text.append("Cancelling migration...")
|
|
345
|
+
|
|
346
|
+
def _on_migration_progress(self, progress: int, message: str):
|
|
347
|
+
"""Handle migration progress update."""
|
|
348
|
+
self.progress_bar.setValue(progress)
|
|
349
|
+
self.status_text.append(message)
|
|
350
|
+
|
|
351
|
+
def _on_migration_finished(self, success: bool, message: str):
|
|
352
|
+
"""Handle migration completion."""
|
|
353
|
+
self.status_text.append(message)
|
|
354
|
+
|
|
355
|
+
if success:
|
|
356
|
+
QMessageBox.information(self, "Success", message)
|
|
357
|
+
else:
|
|
358
|
+
QMessageBox.warning(self, "Failed", message)
|
|
359
|
+
|
|
360
|
+
self.start_button.setEnabled(True)
|
|
361
|
+
self.cancel_button.setEnabled(False)
|
|
362
|
+
self.close_button.setEnabled(True)
|
|
363
|
+
self.migration_thread = None
|
|
364
|
+
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
"""Dialog for configuring embedding models for collections."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
from PySide6.QtWidgets import (
|
|
5
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
|
6
|
+
QComboBox, QPushButton, QGroupBox, QTextEdit,
|
|
7
|
+
QMessageBox
|
|
8
|
+
)
|
|
9
|
+
from PySide6.QtCore import Qt
|
|
10
|
+
|
|
11
|
+
from vector_inspector.core.embedding_utils import get_available_models_for_dimension, DIMENSION_TO_MODEL
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EmbeddingConfigDialog(QDialog):
|
|
15
|
+
"""Dialog for selecting embedding model for a collection."""
|
|
16
|
+
|
|
17
|
+
def __init__(self, collection_name: str, vector_dimension: int,
|
|
18
|
+
current_model: Optional[str] = None,
|
|
19
|
+
current_type: Optional[str] = None,
|
|
20
|
+
parent=None):
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
self.collection_name = collection_name
|
|
23
|
+
self.vector_dimension = vector_dimension
|
|
24
|
+
self.current_model = current_model
|
|
25
|
+
self.current_type = current_type
|
|
26
|
+
self.selected_model = None
|
|
27
|
+
self.selected_type = None
|
|
28
|
+
|
|
29
|
+
self.setWindowTitle(f"Configure Embedding Model - {collection_name}")
|
|
30
|
+
self.setMinimumWidth(500)
|
|
31
|
+
self._setup_ui()
|
|
32
|
+
|
|
33
|
+
def _setup_ui(self):
|
|
34
|
+
"""Setup dialog UI."""
|
|
35
|
+
layout = QVBoxLayout(self)
|
|
36
|
+
|
|
37
|
+
# Info section
|
|
38
|
+
info_group = QGroupBox("Collection Information")
|
|
39
|
+
info_layout = QVBoxLayout()
|
|
40
|
+
|
|
41
|
+
info_layout.addWidget(QLabel(f"<b>Collection:</b> {self.collection_name}"))
|
|
42
|
+
info_layout.addWidget(QLabel(f"<b>Vector Dimension:</b> {self.vector_dimension}"))
|
|
43
|
+
|
|
44
|
+
if self.current_model:
|
|
45
|
+
info_layout.addWidget(QLabel(f"<b>Current Model:</b> {self.current_model} ({self.current_type})"))
|
|
46
|
+
else:
|
|
47
|
+
warning = QLabel("⚠️ No embedding model configured - using automatic detection")
|
|
48
|
+
warning.setStyleSheet("color: orange;")
|
|
49
|
+
info_layout.addWidget(warning)
|
|
50
|
+
|
|
51
|
+
info_group.setLayout(info_layout)
|
|
52
|
+
layout.addWidget(info_group)
|
|
53
|
+
|
|
54
|
+
# Model selection section
|
|
55
|
+
model_group = QGroupBox("Embedding Model Selection")
|
|
56
|
+
model_layout = QVBoxLayout()
|
|
57
|
+
|
|
58
|
+
# Get available models for this dimension
|
|
59
|
+
available_models = get_available_models_for_dimension(self.vector_dimension)
|
|
60
|
+
|
|
61
|
+
if available_models:
|
|
62
|
+
model_layout.addWidget(QLabel(f"Available models for {self.vector_dimension}-dimensional vectors:"))
|
|
63
|
+
|
|
64
|
+
self.model_combo = QComboBox()
|
|
65
|
+
for model_name, model_type, description in available_models:
|
|
66
|
+
display_text = f"{model_name} ({model_type}) - {description}"
|
|
67
|
+
self.model_combo.addItem(display_text, (model_name, model_type))
|
|
68
|
+
|
|
69
|
+
# Set current selection if it matches
|
|
70
|
+
if self.current_model and self.current_type:
|
|
71
|
+
for i in range(self.model_combo.count()):
|
|
72
|
+
model_name, model_type = self.model_combo.itemData(i)
|
|
73
|
+
if model_name == self.current_model and model_type == self.current_type:
|
|
74
|
+
self.model_combo.setCurrentIndex(i)
|
|
75
|
+
break
|
|
76
|
+
|
|
77
|
+
model_layout.addWidget(self.model_combo)
|
|
78
|
+
|
|
79
|
+
# Description area
|
|
80
|
+
desc_label = QLabel("<b>About the selected model:</b>")
|
|
81
|
+
model_layout.addWidget(desc_label)
|
|
82
|
+
|
|
83
|
+
self.description_text = QTextEdit()
|
|
84
|
+
self.description_text.setReadOnly(True)
|
|
85
|
+
self.description_text.setMaximumHeight(100)
|
|
86
|
+
self.description_text.setStyleSheet("background-color: #f5f5f5; border: 1px solid #ccc; color: #000000;")
|
|
87
|
+
model_layout.addWidget(self.description_text)
|
|
88
|
+
|
|
89
|
+
# Update description when selection changes
|
|
90
|
+
self.model_combo.currentIndexChanged.connect(self._update_description)
|
|
91
|
+
self._update_description()
|
|
92
|
+
|
|
93
|
+
else:
|
|
94
|
+
# No known models for this dimension
|
|
95
|
+
warning = QLabel(f"⚠️ No pre-configured models available for {self.vector_dimension} dimensions.")
|
|
96
|
+
warning.setWordWrap(True)
|
|
97
|
+
model_layout.addWidget(warning)
|
|
98
|
+
|
|
99
|
+
# Show all available dimensions
|
|
100
|
+
dims_text = "Available dimensions: " + ", ".join(str(d) for d in sorted(DIMENSION_TO_MODEL.keys()))
|
|
101
|
+
model_layout.addWidget(QLabel(dims_text))
|
|
102
|
+
|
|
103
|
+
self.model_combo = None
|
|
104
|
+
|
|
105
|
+
model_group.setLayout(model_layout)
|
|
106
|
+
layout.addWidget(model_group)
|
|
107
|
+
|
|
108
|
+
# Buttons
|
|
109
|
+
button_layout = QHBoxLayout()
|
|
110
|
+
button_layout.addStretch()
|
|
111
|
+
|
|
112
|
+
self.save_btn = QPushButton("Save Configuration")
|
|
113
|
+
self.save_btn.clicked.connect(self.accept)
|
|
114
|
+
self.save_btn.setEnabled(available_models and len(available_models) > 0)
|
|
115
|
+
|
|
116
|
+
self.clear_btn = QPushButton("Clear Configuration")
|
|
117
|
+
self.clear_btn.clicked.connect(self._clear_config)
|
|
118
|
+
self.clear_btn.setEnabled(self.current_model is not None)
|
|
119
|
+
|
|
120
|
+
cancel_btn = QPushButton("Cancel")
|
|
121
|
+
cancel_btn.clicked.connect(self.reject)
|
|
122
|
+
|
|
123
|
+
button_layout.addWidget(self.save_btn)
|
|
124
|
+
button_layout.addWidget(self.clear_btn)
|
|
125
|
+
button_layout.addWidget(cancel_btn)
|
|
126
|
+
|
|
127
|
+
layout.addLayout(button_layout)
|
|
128
|
+
|
|
129
|
+
def _update_description(self):
|
|
130
|
+
"""Update the description text based on selected model."""
|
|
131
|
+
if not self.model_combo:
|
|
132
|
+
return
|
|
133
|
+
|
|
134
|
+
model_name, model_type = self.model_combo.currentData()
|
|
135
|
+
|
|
136
|
+
descriptions = {
|
|
137
|
+
"sentence-transformer": (
|
|
138
|
+
"Sentence-Transformers are text-only embedding models optimized for semantic similarity. "
|
|
139
|
+
"They work well for text search, clustering, and classification tasks."
|
|
140
|
+
),
|
|
141
|
+
"clip": (
|
|
142
|
+
"CLIP (Contrastive Language-Image Pre-training) is a multi-modal model that can embed both "
|
|
143
|
+
"text and images into the same vector space. This allows text queries to find semantically "
|
|
144
|
+
"similar images, and vice versa. Perfect for image search with text descriptions."
|
|
145
|
+
)
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
desc = descriptions.get(model_type, "Embedding model for vector similarity search.")
|
|
149
|
+
self.description_text.setPlainText(
|
|
150
|
+
f"Model: {model_name}\n"
|
|
151
|
+
f"Type: {model_type}\n"
|
|
152
|
+
f"Dimension: {self.vector_dimension}\n\n"
|
|
153
|
+
f"{desc}"
|
|
154
|
+
)
|
|
155
|
+
|
|
156
|
+
def _clear_config(self):
|
|
157
|
+
"""Clear the embedding model configuration."""
|
|
158
|
+
reply = QMessageBox.question(
|
|
159
|
+
self,
|
|
160
|
+
"Clear Configuration",
|
|
161
|
+
"This will remove the custom embedding model configuration and use automatic detection. Continue?",
|
|
162
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
163
|
+
)
|
|
164
|
+
|
|
165
|
+
if reply == QMessageBox.StandardButton.Yes:
|
|
166
|
+
self.selected_model = None
|
|
167
|
+
self.selected_type = None
|
|
168
|
+
self.done(2) # Custom code for "clear"
|
|
169
|
+
|
|
170
|
+
def get_selection(self) -> Optional[Tuple[str, str]]:
|
|
171
|
+
"""Get the selected model and type."""
|
|
172
|
+
if not self.model_combo:
|
|
173
|
+
return None
|
|
174
|
+
|
|
175
|
+
model_name, model_type = self.model_combo.currentData()
|
|
176
|
+
return (model_name, model_type)
|