vector-inspector 0.2.0__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 (32) hide show
  1. vector_inspector/__init__.py +3 -0
  2. vector_inspector/__main__.py +4 -0
  3. vector_inspector/core/__init__.py +1 -0
  4. vector_inspector/core/connections/__init__.py +7 -0
  5. vector_inspector/core/connections/base_connection.py +233 -0
  6. vector_inspector/core/connections/chroma_connection.py +384 -0
  7. vector_inspector/core/connections/qdrant_connection.py +723 -0
  8. vector_inspector/core/connections/template_connection.py +346 -0
  9. vector_inspector/main.py +21 -0
  10. vector_inspector/services/__init__.py +1 -0
  11. vector_inspector/services/backup_restore_service.py +286 -0
  12. vector_inspector/services/filter_service.py +72 -0
  13. vector_inspector/services/import_export_service.py +287 -0
  14. vector_inspector/services/settings_service.py +60 -0
  15. vector_inspector/services/visualization_service.py +116 -0
  16. vector_inspector/ui/__init__.py +1 -0
  17. vector_inspector/ui/components/__init__.py +1 -0
  18. vector_inspector/ui/components/backup_restore_dialog.py +350 -0
  19. vector_inspector/ui/components/filter_builder.py +370 -0
  20. vector_inspector/ui/components/item_dialog.py +118 -0
  21. vector_inspector/ui/components/loading_dialog.py +30 -0
  22. vector_inspector/ui/main_window.py +288 -0
  23. vector_inspector/ui/views/__init__.py +1 -0
  24. vector_inspector/ui/views/collection_browser.py +112 -0
  25. vector_inspector/ui/views/connection_view.py +423 -0
  26. vector_inspector/ui/views/metadata_view.py +555 -0
  27. vector_inspector/ui/views/search_view.py +268 -0
  28. vector_inspector/ui/views/visualization_view.py +245 -0
  29. vector_inspector-0.2.0.dist-info/METADATA +382 -0
  30. vector_inspector-0.2.0.dist-info/RECORD +32 -0
  31. vector_inspector-0.2.0.dist-info/WHEEL +4 -0
  32. vector_inspector-0.2.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,350 @@
1
+ """Dialog for backup and restore operations."""
2
+
3
+ from typing import Optional
4
+ from PySide6.QtWidgets import (
5
+ QDialog, QVBoxLayout, QHBoxLayout, QTabWidget, QWidget,
6
+ QPushButton, QLabel, QListWidget, QListWidgetItem, QFileDialog,
7
+ QMessageBox, QCheckBox, QLineEdit, QGroupBox, QFormLayout, QApplication
8
+ )
9
+ from PySide6.QtCore import Qt
10
+ from pathlib import Path
11
+
12
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
13
+ from vector_inspector.services.backup_restore_service import BackupRestoreService
14
+ from vector_inspector.services.settings_service import SettingsService
15
+ from vector_inspector.ui.components.loading_dialog import LoadingDialog
16
+
17
+
18
+ class BackupRestoreDialog(QDialog):
19
+ """Dialog for managing backups and restores."""
20
+
21
+ def __init__(self, connection: VectorDBConnection, collection_name: str = "", parent=None):
22
+ super().__init__(parent)
23
+ self.connection = connection
24
+ self.collection_name = collection_name
25
+ self.backup_service = BackupRestoreService()
26
+ self.settings_service = SettingsService()
27
+
28
+ # Load backup directory from settings or use default
29
+ default_backup_dir = str(Path.home() / "vector-viewer-backups")
30
+ self.backup_dir = self.settings_service.get("backup_directory", default_backup_dir)
31
+
32
+ self.loading_dialog = LoadingDialog("Processing...", self)
33
+
34
+ self.setWindowTitle("Backup & Restore")
35
+ self.setMinimumSize(600, 500)
36
+
37
+ self._setup_ui()
38
+ self._refresh_backups_list()
39
+
40
+ def _setup_ui(self):
41
+ """Setup dialog UI."""
42
+ layout = QVBoxLayout(self)
43
+
44
+ # Tabs for backup and restore
45
+ tabs = QTabWidget()
46
+
47
+ # Backup tab
48
+ backup_tab = self._create_backup_tab()
49
+ tabs.addTab(backup_tab, "Create Backup")
50
+
51
+ # Restore tab
52
+ restore_tab = self._create_restore_tab()
53
+ tabs.addTab(restore_tab, "Restore from Backup")
54
+
55
+ layout.addWidget(tabs)
56
+
57
+ # Close button
58
+ close_button = QPushButton("Close")
59
+ close_button.clicked.connect(self.accept)
60
+ layout.addWidget(close_button, alignment=Qt.AlignRight)
61
+
62
+ def _create_backup_tab(self) -> QWidget:
63
+ """Create the backup tab."""
64
+ widget = QWidget()
65
+ layout = QVBoxLayout(widget)
66
+
67
+ # Collection selection
68
+ collection_group = QGroupBox("Backup Configuration")
69
+ collection_layout = QFormLayout()
70
+
71
+ # Collection name
72
+ collection_layout.addRow("Collection:", QLabel(self.collection_name or "No collection selected"))
73
+
74
+ # Backup directory
75
+ dir_layout = QHBoxLayout()
76
+ self.backup_dir_input = QLineEdit(self.backup_dir)
77
+ self.backup_dir_input.setReadOnly(True)
78
+ dir_layout.addWidget(self.backup_dir_input)
79
+
80
+ browse_btn = QPushButton("Browse...")
81
+ browse_btn.clicked.connect(self._select_backup_dir)
82
+ dir_layout.addWidget(browse_btn)
83
+
84
+ collection_layout.addRow("Backup Directory:", dir_layout)
85
+
86
+ # Options
87
+ self.include_embeddings_check = QCheckBox("Include embedding vectors (larger file size)")
88
+ self.include_embeddings_check.setChecked(True)
89
+ collection_layout.addRow("Options:", self.include_embeddings_check)
90
+
91
+ collection_group.setLayout(collection_layout)
92
+ layout.addWidget(collection_group)
93
+
94
+ # Info label
95
+ info_label = QLabel(
96
+ "Backup will create a compressed archive containing all collection data, "
97
+ "metadata, and optionally embedding vectors."
98
+ )
99
+ info_label.setWordWrap(True)
100
+ info_label.setStyleSheet("color: gray; font-style: italic;")
101
+ layout.addWidget(info_label)
102
+
103
+ layout.addStretch()
104
+
105
+ # Create backup button
106
+ backup_button = QPushButton("Create Backup")
107
+ backup_button.clicked.connect(self._create_backup)
108
+ backup_button.setStyleSheet("QPushButton { font-weight: bold; padding: 8px; }")
109
+ layout.addWidget(backup_button)
110
+
111
+ return widget
112
+
113
+ def _create_restore_tab(self) -> QWidget:
114
+ """Create the restore tab."""
115
+ widget = QWidget()
116
+ layout = QVBoxLayout(widget)
117
+
118
+ # Backup list
119
+ layout.addWidget(QLabel("Available Backups:"))
120
+
121
+ self.backups_list = QListWidget()
122
+ self.backups_list.itemSelectionChanged.connect(self._on_backup_selected)
123
+ layout.addWidget(self.backups_list)
124
+
125
+ # Refresh button
126
+ refresh_btn = QPushButton("Refresh List")
127
+ refresh_btn.clicked.connect(self._refresh_backups_list)
128
+ layout.addWidget(refresh_btn)
129
+
130
+ # Restore options
131
+ options_group = QGroupBox("Restore Options")
132
+ options_layout = QVBoxLayout()
133
+
134
+ # New collection name
135
+ name_layout = QHBoxLayout()
136
+ name_layout.addWidget(QLabel("Restore as:"))
137
+ self.restore_name_input = QLineEdit()
138
+ self.restore_name_input.setPlaceholderText("Leave empty to use original name")
139
+ name_layout.addWidget(self.restore_name_input)
140
+ options_layout.addLayout(name_layout)
141
+
142
+ # Overwrite checkbox
143
+ self.overwrite_check = QCheckBox("Overwrite if collection exists")
144
+ self.overwrite_check.setChecked(False)
145
+ options_layout.addWidget(self.overwrite_check)
146
+
147
+ options_group.setLayout(options_layout)
148
+ layout.addWidget(options_group)
149
+
150
+ # Restore and delete buttons
151
+ button_layout = QHBoxLayout()
152
+
153
+ self.restore_button = QPushButton("Restore Selected")
154
+ self.restore_button.clicked.connect(self._restore_backup)
155
+ self.restore_button.setEnabled(False)
156
+ self.restore_button.setStyleSheet("QPushButton { font-weight: bold; padding: 8px; }")
157
+ button_layout.addWidget(self.restore_button)
158
+
159
+ self.delete_backup_button = QPushButton("Delete Selected")
160
+ self.delete_backup_button.clicked.connect(self._delete_backup)
161
+ self.delete_backup_button.setEnabled(False)
162
+ button_layout.addWidget(self.delete_backup_button)
163
+
164
+ layout.addLayout(button_layout)
165
+
166
+ return widget
167
+
168
+ def _select_backup_dir(self):
169
+ """Select backup directory."""
170
+ dir_path = QFileDialog.getExistingDirectory(
171
+ self,
172
+ "Select Backup Directory",
173
+ self.backup_dir
174
+ )
175
+
176
+ if dir_path:
177
+ self.backup_dir = dir_path
178
+ self.backup_dir_input.setText(dir_path)
179
+
180
+ # Save to settings
181
+ self.settings_service.set("backup_directory", dir_path)
182
+
183
+ self._refresh_backups_list()
184
+
185
+ def _create_backup(self):
186
+ """Create a backup of the current collection."""
187
+ if not self.collection_name:
188
+ QMessageBox.warning(self, "No Collection", "No collection selected for backup.")
189
+ return
190
+
191
+ # Create backup
192
+ include_embeddings = self.include_embeddings_check.isChecked()
193
+
194
+ self.loading_dialog.show_loading("Creating backup...")
195
+ QApplication.processEvents()
196
+
197
+ try:
198
+ backup_path = self.backup_service.backup_collection(
199
+ self.connection,
200
+ self.collection_name,
201
+ self.backup_dir,
202
+ include_embeddings=include_embeddings
203
+ )
204
+ finally:
205
+ self.loading_dialog.hide_loading()
206
+
207
+ if backup_path:
208
+ QMessageBox.information(
209
+ self,
210
+ "Backup Successful",
211
+ f"Backup created successfully:\n{backup_path}"
212
+ )
213
+ self._refresh_backups_list()
214
+ else:
215
+ QMessageBox.warning(self, "Backup Failed", "Failed to create backup.")
216
+
217
+ def _refresh_backups_list(self):
218
+ """Refresh the list of available backups."""
219
+ self.backups_list.clear()
220
+
221
+ backups = self.backup_service.list_backups(self.backup_dir)
222
+
223
+ for backup in backups:
224
+ # Format file size
225
+ size_mb = backup["file_size"] / (1024 * 1024)
226
+
227
+ item_text = (
228
+ f"{backup['collection_name']} - {backup['timestamp']}\n"
229
+ f" Items: {backup['item_count']}, Size: {size_mb:.2f} MB\n"
230
+ f" File: {backup['file_name']}"
231
+ )
232
+
233
+ item = QListWidgetItem(item_text)
234
+ item.setData(Qt.UserRole, backup["file_path"])
235
+ self.backups_list.addItem(item)
236
+
237
+ if not backups:
238
+ item = QListWidgetItem("No backups found in directory")
239
+ item.setFlags(Qt.NoItemFlags)
240
+ self.backups_list.addItem(item)
241
+
242
+ def _on_backup_selected(self):
243
+ """Handle backup selection."""
244
+ has_selection = len(self.backups_list.selectedItems()) > 0
245
+ self.restore_button.setEnabled(has_selection)
246
+ self.delete_backup_button.setEnabled(has_selection)
247
+
248
+ def _restore_backup(self):
249
+ """Restore a backup."""
250
+ selected_items = self.backups_list.selectedItems()
251
+ if not selected_items:
252
+ return
253
+
254
+ backup_file = selected_items[0].data(Qt.UserRole)
255
+ if not backup_file:
256
+ return
257
+
258
+ # Read backup metadata to get original collection name
259
+ try:
260
+ import zipfile
261
+ import json
262
+ with zipfile.ZipFile(backup_file, 'r') as zipf:
263
+ metadata_str = zipf.read('metadata.json').decode('utf-8')
264
+ metadata = json.loads(metadata_str)
265
+ original_name = metadata.get("collection_name", "unknown")
266
+ except Exception as e:
267
+ QMessageBox.warning(self, "Error", f"Failed to read backup metadata: {e}")
268
+ return
269
+
270
+ # Get restore options
271
+ restore_name = self.restore_name_input.text().strip()
272
+ overwrite = self.overwrite_check.isChecked()
273
+
274
+ # Determine final collection name
275
+ final_name = restore_name if restore_name else original_name
276
+
277
+ # Build confirmation message
278
+ if restore_name:
279
+ msg = f"Restore backup to NEW collection: '{final_name}'"
280
+ else:
281
+ msg = f"Restore backup to ORIGINAL collection: '{final_name}'"
282
+
283
+ # Check if collection exists
284
+ existing_collections = self.connection.list_collections()
285
+ if final_name in existing_collections:
286
+ if overwrite:
287
+ msg += f"\n\n⚠️ WARNING: This will DELETE and replace the existing collection '{final_name}'!"
288
+ else:
289
+ QMessageBox.warning(
290
+ self,
291
+ "Collection Exists",
292
+ f"Collection '{final_name}' already exists.\n\n"
293
+ f"Check 'Overwrite existing collection' to replace it, or enter a different name."
294
+ )
295
+ return
296
+
297
+ # Confirm
298
+ reply = QMessageBox.question(
299
+ self,
300
+ "Confirm Restore",
301
+ msg,
302
+ QMessageBox.Yes | QMessageBox.No
303
+ )
304
+
305
+ if reply != QMessageBox.Yes:
306
+ return
307
+
308
+ self.loading_dialog.show_loading("Restoring backup...")
309
+ QApplication.processEvents()
310
+
311
+ try:
312
+ # Restore
313
+ success = self.backup_service.restore_collection(
314
+ self.connection,
315
+ backup_file,
316
+ collection_name=restore_name if restore_name else None,
317
+ overwrite=overwrite
318
+ )
319
+ finally:
320
+ self.loading_dialog.hide_loading()
321
+
322
+ if success:
323
+ QMessageBox.information(self, "Restore Successful", f"Backup restored successfully to collection '{final_name}'.")
324
+ else:
325
+ QMessageBox.warning(self, "Restore Failed", "Failed to restore backup.")
326
+
327
+ def _delete_backup(self):
328
+ """Delete a backup file."""
329
+ selected_items = self.backups_list.selectedItems()
330
+ if not selected_items:
331
+ return
332
+
333
+ backup_file = selected_items[0].data(Qt.UserRole)
334
+ if not backup_file:
335
+ return
336
+
337
+ # Confirm deletion
338
+ reply = QMessageBox.question(
339
+ self,
340
+ "Confirm Deletion",
341
+ f"Delete this backup file?\n{Path(backup_file).name}",
342
+ QMessageBox.Yes | QMessageBox.No
343
+ )
344
+
345
+ if reply == QMessageBox.Yes:
346
+ if self.backup_service.delete_backup(backup_file):
347
+ QMessageBox.information(self, "Deleted", "Backup deleted successfully.")
348
+ self._refresh_backups_list()
349
+ else:
350
+ QMessageBox.warning(self, "Delete Failed", "Failed to delete backup.")