vector-inspector 0.3.9__py3-none-any.whl → 0.3.12__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.
Files changed (33) hide show
  1. vector_inspector/__init__.py +10 -1
  2. vector_inspector/core/connection_manager.py +91 -19
  3. vector_inspector/core/connections/base_connection.py +43 -43
  4. vector_inspector/core/connections/chroma_connection.py +1 -1
  5. vector_inspector/core/connections/pgvector_connection.py +12 -172
  6. vector_inspector/core/connections/pinecone_connection.py +596 -99
  7. vector_inspector/core/connections/qdrant_connection.py +35 -44
  8. vector_inspector/core/embedding_utils.py +14 -5
  9. vector_inspector/core/logging.py +3 -1
  10. vector_inspector/extensions/__init__.py +6 -0
  11. vector_inspector/extensions/telemetry_settings_panel.py +25 -0
  12. vector_inspector/main.py +45 -2
  13. vector_inspector/services/backup_restore_service.py +228 -15
  14. vector_inspector/services/settings_service.py +79 -19
  15. vector_inspector/services/telemetry_service.py +88 -0
  16. vector_inspector/ui/components/backup_restore_dialog.py +215 -101
  17. vector_inspector/ui/components/connection_manager_panel.py +155 -14
  18. vector_inspector/ui/dialogs/cross_db_migration.py +126 -99
  19. vector_inspector/ui/dialogs/settings_dialog.py +13 -6
  20. vector_inspector/ui/loading_screen.py +169 -0
  21. vector_inspector/ui/main_window.py +44 -19
  22. vector_inspector/ui/services/dialog_service.py +1 -0
  23. vector_inspector/ui/views/collection_browser.py +36 -34
  24. vector_inspector/ui/views/connection_view.py +7 -1
  25. vector_inspector/ui/views/info_panel.py +118 -52
  26. vector_inspector/ui/views/metadata_view.py +30 -31
  27. vector_inspector/ui/views/search_view.py +20 -19
  28. vector_inspector/ui/views/visualization_view.py +18 -15
  29. {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/METADATA +19 -37
  30. {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/RECORD +33 -29
  31. {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/WHEEL +1 -1
  32. vector_inspector-0.3.12.dist-info/licenses/LICENSE +1 -0
  33. {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/entry_points.txt +0 -0
@@ -4,9 +4,19 @@ from typing import Optional, List, Dict, Any
4
4
  from pathlib import Path
5
5
  import tempfile
6
6
  from PySide6.QtWidgets import (
7
- QDialog, QVBoxLayout, QHBoxLayout, QLabel, QComboBox,
8
- QPushButton, QProgressBar, QTextEdit, QGroupBox, QFormLayout,
9
- QSpinBox, QCheckBox, QMessageBox
7
+ QDialog,
8
+ QVBoxLayout,
9
+ QHBoxLayout,
10
+ QLabel,
11
+ QComboBox,
12
+ QPushButton,
13
+ QProgressBar,
14
+ QTextEdit,
15
+ QGroupBox,
16
+ QFormLayout,
17
+ QSpinBox,
18
+ QCheckBox,
19
+ QMessageBox,
10
20
  )
11
21
  from PySide6.QtCore import QThread, Signal
12
22
 
@@ -17,17 +27,17 @@ from vector_inspector.core.logging import log_info, log_error
17
27
 
18
28
  class MigrationThread(QThread):
19
29
  """Background thread for migrating data between databases using backup/restore."""
20
-
30
+
21
31
  progress = Signal(int, str) # progress percentage, status message
22
32
  finished = Signal(bool, str) # success, message
23
-
33
+
24
34
  def __init__(
25
35
  self,
26
36
  source_conn: ConnectionInstance,
27
37
  target_conn: ConnectionInstance,
28
38
  source_collection: str,
29
39
  target_collection: str,
30
- include_embeddings: bool
40
+ include_embeddings: bool,
31
41
  ):
32
42
  super().__init__()
33
43
  self.source_conn = source_conn
@@ -37,11 +47,11 @@ class MigrationThread(QThread):
37
47
  self.include_embeddings = include_embeddings
38
48
  self._cancelled = False
39
49
  self.backup_service = BackupRestoreService()
40
-
50
+
41
51
  def cancel(self):
42
52
  """Cancel the migration."""
43
53
  self._cancelled = True
44
-
54
+
45
55
  def run(self):
46
56
  """Run the migration using backup and restore."""
47
57
  temp_backup_path = None
@@ -49,92 +59,106 @@ class MigrationThread(QThread):
49
59
  if self._cancelled:
50
60
  self.finished.emit(False, "Migration cancelled by user.")
51
61
  return
52
-
62
+
53
63
  # Ensure connections are active
54
- if not self.source_conn.connection.is_connected:
64
+ if not self.source_conn.is_connected:
55
65
  self.finished.emit(False, "Source connection is not active.")
56
66
  return
57
-
58
- if not self.target_conn.connection.is_connected:
67
+
68
+ if not self.target_conn.is_connected:
59
69
  self.finished.emit(False, "Target connection is not active.")
60
70
  return
61
-
71
+
62
72
  # Create temporary directory for backup
63
73
  temp_dir = tempfile.mkdtemp(prefix="vector_migration_")
64
-
74
+
65
75
  # Step 1: Create backup of source collection
66
76
  self.progress.emit(10, f"Creating backup of {self.source_collection}...")
67
-
77
+
68
78
  temp_backup_path = self.backup_service.backup_collection(
69
79
  self.source_conn.connection,
70
80
  self.source_collection,
71
81
  temp_dir,
72
- include_embeddings=self.include_embeddings
82
+ include_embeddings=self.include_embeddings,
83
+ connection_id=self.source_conn.id,
73
84
  )
74
-
85
+
75
86
  if not temp_backup_path:
76
87
  self.finished.emit(False, "Failed to create backup.")
77
88
  return
78
-
89
+
79
90
  if self._cancelled:
80
91
  self.finished.emit(False, "Migration cancelled by user.")
81
92
  return
82
-
93
+
83
94
  # Step 2: Restore to target collection
84
95
  self.progress.emit(50, f"Restoring to {self.target_collection}...")
85
-
96
+
86
97
  # Verify target connection before restore
87
- if not self.target_conn.connection.is_connected:
98
+ if not self.target_conn.is_connected:
88
99
  # Try to reconnect
89
- if not self.target_conn.connection.connect():
100
+ if not self.target_conn.connect():
90
101
  self.finished.emit(False, "Target connection lost. Please try again.")
91
102
  return
92
-
103
+
93
104
  # Check if target collection exists
94
105
  target_exists = self.target_collection in self.target_conn.collections
95
-
106
+
96
107
  success = self.backup_service.restore_collection(
97
108
  self.target_conn.connection,
98
109
  temp_backup_path,
99
110
  collection_name=self.target_collection,
100
- overwrite=target_exists
111
+ overwrite=target_exists,
112
+ connection_id=self.target_conn.id,
101
113
  )
102
-
114
+
103
115
  if self._cancelled:
104
116
  self.finished.emit(False, "Migration cancelled by user.")
105
117
  return
106
-
118
+
107
119
  if success:
108
120
  self.progress.emit(100, f"Migration complete!")
109
- self.finished.emit(True, f"Successfully migrated {self.source_collection} to {self.target_collection}")
121
+ self.finished.emit(
122
+ True,
123
+ f"Successfully migrated {self.source_collection} to {self.target_collection}",
124
+ )
110
125
  else:
111
126
  # Clean up target collection on failure
112
127
  try:
113
- if self.target_collection in self.target_conn.connection.list_collections():
128
+ if self.target_collection in self.target_conn.list_collections():
114
129
  self.progress.emit(90, "Cleaning up failed migration...")
115
- log_info("Cleaning up failed migration: deleting target collection '%s'", self.target_collection)
116
- self.target_conn.connection.delete_collection(self.target_collection)
130
+ log_info(
131
+ "Cleaning up failed migration: deleting target collection '%s'",
132
+ self.target_collection,
133
+ )
134
+ self.target_conn.delete_collection(self.target_collection)
117
135
  except Exception as cleanup_error:
118
136
  log_error("Warning: Failed to clean up target collection: %s", cleanup_error)
119
-
120
- self.finished.emit(False, "Failed to restore to target collection. Target collection cleaned up.")
121
-
137
+
138
+ self.finished.emit(
139
+ False, "Failed to restore to target collection. Target collection cleaned up."
140
+ )
141
+
122
142
  except Exception as e:
123
143
  import traceback
144
+
124
145
  error_details = traceback.format_exc()
125
146
  log_error("Migration error details:\n%s", error_details)
126
-
147
+
127
148
  # Clean up target collection on exception
128
149
  try:
129
- if self.target_conn and self.target_conn.connection.is_connected:
130
- if self.target_collection in self.target_conn.connection.list_collections():
131
- log_info("Cleaning up failed migration: deleting target collection '%s'", self.target_collection)
132
- self.target_conn.connection.delete_collection(self.target_collection)
150
+ if self.target_conn and self.target_conn.is_connected:
151
+ if self.target_collection in self.target_conn.list_collections():
152
+ log_info(
153
+ "Cleaning up failed migration: deleting target collection '%s'",
154
+ self.target_collection,
155
+ )
156
+ self.target_conn.delete_collection(self.target_collection)
133
157
  except Exception as cleanup_error:
134
- log_error("Warning: Failed to clean up target collection: %s", cleanup_error)
135
-
158
+ log_error("Warning: Failed to clean up target collection: %s", cleanup_error)
159
+
136
160
  self.finished.emit(False, f"Migration error: {str(e)}")
137
-
161
+
138
162
  finally:
139
163
  # Clean up temporary backup file
140
164
  if temp_backup_path:
@@ -150,112 +174,112 @@ class MigrationThread(QThread):
150
174
 
151
175
  class CrossDatabaseMigrationDialog(QDialog):
152
176
  """Dialog for migrating data between vector databases."""
153
-
177
+
154
178
  def __init__(self, connection_manager: ConnectionManager, parent=None):
155
179
  super().__init__(parent)
156
180
  self.connection_manager = connection_manager
157
181
  self.migration_thread: Optional[MigrationThread] = None
158
-
182
+
159
183
  self.setWindowTitle("Cross-Database Migration")
160
184
  self.setMinimumWidth(600)
161
185
  self.setMinimumHeight(400)
162
-
186
+
163
187
  self._setup_ui()
164
188
  self._populate_connections()
165
-
189
+
166
190
  def _setup_ui(self):
167
191
  """Setup the UI."""
168
192
  layout = QVBoxLayout(self)
169
-
193
+
170
194
  # Source section
171
195
  source_group = QGroupBox("Source")
172
196
  source_layout = QFormLayout()
173
-
197
+
174
198
  self.source_connection_combo = QComboBox()
175
199
  self.source_connection_combo.currentIndexChanged.connect(self._on_source_connection_changed)
176
200
  source_layout.addRow("Connection:", self.source_connection_combo)
177
-
201
+
178
202
  self.source_collection_combo = QComboBox()
179
203
  source_layout.addRow("Collection:", self.source_collection_combo)
180
-
204
+
181
205
  source_group.setLayout(source_layout)
182
206
  layout.addWidget(source_group)
183
-
207
+
184
208
  # Target section
185
209
  target_group = QGroupBox("Target")
186
210
  target_layout = QFormLayout()
187
-
211
+
188
212
  self.target_connection_combo = QComboBox()
189
213
  self.target_connection_combo.currentIndexChanged.connect(self._on_target_connection_changed)
190
214
  target_layout.addRow("Connection:", self.target_connection_combo)
191
-
215
+
192
216
  self.target_collection_combo = QComboBox()
193
217
  self.target_collection_combo.setEditable(True)
194
218
  target_layout.addRow("Collection:", self.target_collection_combo)
195
-
219
+
196
220
  self.create_new_check = QCheckBox("Create new collection if it doesn't exist")
197
221
  self.create_new_check.setChecked(True)
198
222
  target_layout.addRow("", self.create_new_check)
199
-
223
+
200
224
  target_group.setLayout(target_layout)
201
225
  layout.addWidget(target_group)
202
-
226
+
203
227
  # Options
204
228
  options_group = QGroupBox("Options")
205
229
  options_layout = QFormLayout()
206
-
230
+
207
231
  self.include_embeddings_check = QCheckBox("Include Embeddings")
208
232
  self.include_embeddings_check.setChecked(True)
209
233
  options_layout.addRow("", self.include_embeddings_check)
210
-
234
+
211
235
  options_group.setLayout(options_layout)
212
236
  layout.addWidget(options_group)
213
-
237
+
214
238
  # Progress section
215
239
  self.progress_bar = QProgressBar()
216
240
  self.progress_bar.setRange(0, 100)
217
241
  self.progress_bar.setValue(0)
218
242
  layout.addWidget(self.progress_bar)
219
-
243
+
220
244
  self.status_text = QTextEdit()
221
245
  self.status_text.setReadOnly(True)
222
246
  self.status_text.setMaximumHeight(100)
223
247
  layout.addWidget(self.status_text)
224
-
248
+
225
249
  # Buttons
226
250
  button_layout = QHBoxLayout()
227
-
251
+
228
252
  self.start_button = QPushButton("Start Migration")
229
253
  self.start_button.clicked.connect(self._start_migration)
230
254
  button_layout.addWidget(self.start_button)
231
-
255
+
232
256
  self.cancel_button = QPushButton("Cancel")
233
257
  self.cancel_button.clicked.connect(self._cancel_migration)
234
258
  self.cancel_button.setEnabled(False)
235
259
  button_layout.addWidget(self.cancel_button)
236
-
260
+
237
261
  self.close_button = QPushButton("Close")
238
262
  self.close_button.clicked.connect(self.close)
239
263
  button_layout.addWidget(self.close_button)
240
-
264
+
241
265
  layout.addLayout(button_layout)
242
-
266
+
243
267
  def _populate_connections(self):
244
268
  """Populate connection dropdowns."""
245
269
  connections = self.connection_manager.get_all_connections()
246
-
270
+
247
271
  self.source_connection_combo.clear()
248
272
  self.target_connection_combo.clear()
249
-
273
+
250
274
  for conn in connections:
251
275
  self.source_connection_combo.addItem(conn.get_display_name(), conn.id)
252
276
  self.target_connection_combo.addItem(conn.get_display_name(), conn.id)
253
-
277
+
254
278
  # Populate collections for first connection
255
279
  if connections:
256
280
  self._on_source_connection_changed(0)
257
281
  self._on_target_connection_changed(0)
258
-
282
+
259
283
  def _on_source_connection_changed(self, index: int):
260
284
  """Handle source connection change."""
261
285
  connection_id = self.source_connection_combo.currentData()
@@ -264,7 +288,7 @@ class CrossDatabaseMigrationDialog(QDialog):
264
288
  if instance:
265
289
  self.source_collection_combo.clear()
266
290
  self.source_collection_combo.addItems(instance.collections)
267
-
291
+
268
292
  def _on_target_connection_changed(self, index: int):
269
293
  """Handle target connection change."""
270
294
  connection_id = self.target_connection_combo.currentData()
@@ -273,17 +297,19 @@ class CrossDatabaseMigrationDialog(QDialog):
273
297
  if instance:
274
298
  self.target_collection_combo.clear()
275
299
  self.target_collection_combo.addItems(instance.collections)
276
-
300
+
277
301
  def _start_migration(self):
278
302
  """Start the migration."""
279
303
  # Validate selection
280
304
  source_conn_id = self.source_connection_combo.currentData()
281
305
  target_conn_id = self.target_connection_combo.currentData()
282
-
306
+
283
307
  if not source_conn_id or not target_conn_id:
284
- QMessageBox.warning(self, "Invalid Selection", "Please select both source and target connections.")
308
+ QMessageBox.warning(
309
+ self, "Invalid Selection", "Please select both source and target connections."
310
+ )
285
311
  return
286
-
312
+
287
313
  if source_conn_id == target_conn_id:
288
314
  source_coll = self.source_collection_combo.currentText()
289
315
  target_coll = self.target_collection_combo.currentText()
@@ -291,37 +317,39 @@ class CrossDatabaseMigrationDialog(QDialog):
291
317
  QMessageBox.warning(
292
318
  self,
293
319
  "Invalid Selection",
294
- "Source and target cannot be the same collection in the same connection."
320
+ "Source and target cannot be the same collection in the same connection.",
295
321
  )
296
322
  return
297
-
323
+
298
324
  source_conn = self.connection_manager.get_connection(source_conn_id)
299
325
  target_conn = self.connection_manager.get_connection(target_conn_id)
300
-
326
+
301
327
  if not source_conn or not target_conn:
302
328
  QMessageBox.warning(self, "Error", "Failed to get connection instances.")
303
329
  return
304
-
330
+
305
331
  source_collection = self.source_collection_combo.currentText()
306
332
  target_collection = self.target_collection_combo.currentText().strip()
307
-
333
+
308
334
  if not source_collection or not target_collection:
309
- QMessageBox.warning(self, "Invalid Selection", "Please select both source and target collections.")
335
+ QMessageBox.warning(
336
+ self, "Invalid Selection", "Please select both source and target collections."
337
+ )
310
338
  return
311
-
339
+
312
340
  # Check if target collection exists
313
341
  target_exists = target_collection in target_conn.collections
314
-
342
+
315
343
  # If target doesn't exist and we're not set to create, warn user
316
344
  if not target_exists and not self.create_new_check.isChecked():
317
345
  QMessageBox.warning(
318
346
  self,
319
347
  "Collection Does Not Exist",
320
348
  f"Target collection '{target_collection}' does not exist.\n"
321
- "Please check 'Create new collection' to allow automatic creation during migration."
349
+ "Please check 'Create new collection' to allow automatic creation during migration.",
322
350
  )
323
351
  return
324
-
352
+
325
353
  # Confirm
326
354
  action = "create and migrate" if not target_exists else "migrate"
327
355
  reply = QMessageBox.question(
@@ -330,55 +358,54 @@ class CrossDatabaseMigrationDialog(QDialog):
330
358
  f"Migrate data from:\n {source_conn.name}/{source_collection}\n"
331
359
  f"to:\n {target_conn.name}/{target_collection}\n\n"
332
360
  f"This will {action} all data. Continue?",
333
- QMessageBox.Yes | QMessageBox.No
361
+ QMessageBox.Yes | QMessageBox.No,
334
362
  )
335
-
363
+
336
364
  if reply != QMessageBox.Yes:
337
365
  return
338
-
366
+
339
367
  # Start migration thread
340
368
  self.migration_thread = MigrationThread(
341
369
  source_conn=source_conn,
342
370
  target_conn=target_conn,
343
371
  source_collection=source_collection,
344
372
  target_collection=target_collection,
345
- include_embeddings=self.include_embeddings_check.isChecked()
373
+ include_embeddings=self.include_embeddings_check.isChecked(),
346
374
  )
347
-
375
+
348
376
  self.migration_thread.progress.connect(self._on_migration_progress)
349
377
  self.migration_thread.finished.connect(self._on_migration_finished)
350
-
378
+
351
379
  self.start_button.setEnabled(False)
352
380
  self.cancel_button.setEnabled(True)
353
381
  self.close_button.setEnabled(False)
354
-
382
+
355
383
  self.status_text.clear()
356
384
  self.progress_bar.setValue(0)
357
-
385
+
358
386
  self.migration_thread.start()
359
-
387
+
360
388
  def _cancel_migration(self):
361
389
  """Cancel the migration."""
362
390
  if self.migration_thread:
363
391
  self.migration_thread.cancel()
364
392
  self.status_text.append("Cancelling migration...")
365
-
393
+
366
394
  def _on_migration_progress(self, progress: int, message: str):
367
395
  """Handle migration progress update."""
368
396
  self.progress_bar.setValue(progress)
369
397
  self.status_text.append(message)
370
-
398
+
371
399
  def _on_migration_finished(self, success: bool, message: str):
372
400
  """Handle migration completion."""
373
401
  self.status_text.append(message)
374
-
402
+
375
403
  if success:
376
404
  QMessageBox.information(self, "Success", message)
377
405
  else:
378
406
  QMessageBox.warning(self, "Failed", message)
379
-
407
+
380
408
  self.start_button.setEnabled(True)
381
409
  self.cancel_button.setEnabled(False)
382
410
  self.close_button.setEnabled(True)
383
411
  self.migration_thread = None
384
-
@@ -1,17 +1,15 @@
1
1
  from PySide6.QtWidgets import (
2
+ QCheckBox,
2
3
  QDialog,
3
- QVBoxLayout,
4
4
  QHBoxLayout,
5
5
  QLabel,
6
- QCheckBox,
7
- QComboBox,
8
- QSpinBox,
9
6
  QPushButton,
7
+ QSpinBox,
8
+ QVBoxLayout,
10
9
  )
11
- from PySide6.QtCore import Qt
12
10
 
13
- from vector_inspector.services.settings_service import SettingsService
14
11
  from vector_inspector.extensions import settings_panel_hook
12
+ from vector_inspector.services.settings_service import SettingsService
15
13
 
16
14
 
17
15
  class SettingsDialog(QDialog):
@@ -47,6 +45,10 @@ class SettingsDialog(QDialog):
47
45
  self.restore_geometry_checkbox = QCheckBox("Restore window size/position on startup")
48
46
  layout.addWidget(self.restore_geometry_checkbox)
49
47
 
48
+ # Loading screen
49
+ self.hide_splash_checkbox = QCheckBox("Hide loading screen on startup")
50
+ layout.addWidget(self.hide_splash_checkbox)
51
+
50
52
  # Buttons
51
53
  btn_layout = QHBoxLayout()
52
54
  self.apply_btn = QPushButton("Apply")
@@ -81,6 +83,9 @@ class SettingsDialog(QDialog):
81
83
  self.restore_geometry_checkbox.stateChanged.connect(
82
84
  lambda s: self.settings.set_window_restore_geometry(bool(s))
83
85
  )
86
+ self.hide_splash_checkbox.stateChanged.connect(
87
+ lambda s: self.settings.set("hide_loading_screen", bool(s))
88
+ )
84
89
 
85
90
  # Container for programmatic sections
86
91
  self._extra_sections = []
@@ -107,6 +112,7 @@ class SettingsDialog(QDialog):
107
112
  self.default_results.setValue(self.settings.get_default_n_results())
108
113
  self.auto_embed_checkbox.setChecked(self.settings.get_auto_generate_embeddings())
109
114
  self.restore_geometry_checkbox.setChecked(self.settings.get_window_restore_geometry())
115
+ self.hide_splash_checkbox.setChecked(self.settings.get("hide_loading_screen", False))
110
116
 
111
117
  def _apply(self):
112
118
  # Values are already applied on change; ensure persistence and close
@@ -121,4 +127,5 @@ class SettingsDialog(QDialog):
121
127
  self.default_results.setValue(10)
122
128
  self.auto_embed_checkbox.setChecked(True)
123
129
  self.restore_geometry_checkbox.setChecked(True)
130
+ self.hide_splash_checkbox.setChecked(False)
124
131
  self._apply()