vector-inspector 0.3.11__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.
- vector_inspector/__init__.py +1 -1
- vector_inspector/core/connection_manager.py +91 -19
- vector_inspector/core/connections/base_connection.py +43 -43
- vector_inspector/core/connections/chroma_connection.py +1 -1
- vector_inspector/core/connections/pgvector_connection.py +11 -171
- vector_inspector/core/connections/pinecone_connection.py +596 -99
- vector_inspector/core/connections/qdrant_connection.py +35 -44
- vector_inspector/core/embedding_utils.py +14 -5
- vector_inspector/core/logging.py +3 -1
- vector_inspector/main.py +42 -15
- vector_inspector/services/backup_restore_service.py +228 -15
- vector_inspector/services/settings_service.py +71 -19
- vector_inspector/ui/components/backup_restore_dialog.py +215 -101
- vector_inspector/ui/components/connection_manager_panel.py +155 -14
- vector_inspector/ui/dialogs/cross_db_migration.py +126 -99
- vector_inspector/ui/dialogs/settings_dialog.py +13 -6
- vector_inspector/ui/loading_screen.py +169 -0
- vector_inspector/ui/main_window.py +44 -19
- vector_inspector/ui/services/dialog_service.py +1 -0
- vector_inspector/ui/views/collection_browser.py +36 -34
- vector_inspector/ui/views/connection_view.py +7 -1
- vector_inspector/ui/views/info_panel.py +118 -52
- vector_inspector/ui/views/metadata_view.py +30 -31
- vector_inspector/ui/views/search_view.py +20 -19
- vector_inspector/ui/views/visualization_view.py +18 -15
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/METADATA +17 -4
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/RECORD +30 -28
- vector_inspector-0.3.12.dist-info/licenses/LICENSE +1 -0
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/WHEEL +0 -0
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/entry_points.txt +0 -0
|
@@ -1,15 +1,28 @@
|
|
|
1
1
|
"""Dialog for backup and restore operations."""
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
4
6
|
from PySide6.QtWidgets import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
7
|
+
QApplication,
|
|
8
|
+
QCheckBox,
|
|
9
|
+
QDialog,
|
|
10
|
+
QFileDialog,
|
|
11
|
+
QFormLayout,
|
|
12
|
+
QGroupBox,
|
|
13
|
+
QHBoxLayout,
|
|
14
|
+
QLabel,
|
|
15
|
+
QLineEdit,
|
|
16
|
+
QListWidget,
|
|
17
|
+
QListWidgetItem,
|
|
18
|
+
QMessageBox,
|
|
19
|
+
QPushButton,
|
|
20
|
+
QTabWidget,
|
|
21
|
+
QVBoxLayout,
|
|
22
|
+
QWidget,
|
|
8
23
|
)
|
|
9
|
-
from PySide6.QtCore import Qt
|
|
10
|
-
from pathlib import Path
|
|
11
24
|
|
|
12
|
-
from vector_inspector.core.
|
|
25
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
13
26
|
from vector_inspector.services.backup_restore_service import BackupRestoreService
|
|
14
27
|
from vector_inspector.services.settings_service import SettingsService
|
|
15
28
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
@@ -17,80 +30,84 @@ from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
|
17
30
|
|
|
18
31
|
class BackupRestoreDialog(QDialog):
|
|
19
32
|
"""Dialog for managing backups and restores."""
|
|
20
|
-
|
|
21
|
-
def __init__(self, connection:
|
|
33
|
+
|
|
34
|
+
def __init__(self, connection: ConnectionInstance, collection_name: str = "", parent=None):
|
|
22
35
|
super().__init__(parent)
|
|
36
|
+
# Expects a ConnectionInstance wrapper; services access the underlying
|
|
37
|
+
# raw database connection via `.database` when needed.
|
|
23
38
|
self.connection = connection
|
|
24
39
|
self.collection_name = collection_name
|
|
25
40
|
self.backup_service = BackupRestoreService()
|
|
26
41
|
self.settings_service = SettingsService()
|
|
27
|
-
|
|
42
|
+
|
|
28
43
|
# Load backup directory from settings or use default
|
|
29
44
|
default_backup_dir = str(Path.home() / "vector-viewer-backups")
|
|
30
45
|
self.backup_dir = self.settings_service.get("backup_directory", default_backup_dir)
|
|
31
|
-
|
|
46
|
+
|
|
32
47
|
self.loading_dialog = LoadingDialog("Processing...", self)
|
|
33
|
-
|
|
48
|
+
|
|
34
49
|
self.setWindowTitle("Backup & Restore")
|
|
35
50
|
self.setMinimumSize(600, 500)
|
|
36
|
-
|
|
51
|
+
|
|
37
52
|
self._setup_ui()
|
|
38
53
|
self._refresh_backups_list()
|
|
39
|
-
|
|
54
|
+
|
|
40
55
|
def _setup_ui(self):
|
|
41
56
|
"""Setup dialog UI."""
|
|
42
57
|
layout = QVBoxLayout(self)
|
|
43
|
-
|
|
58
|
+
|
|
44
59
|
# Tabs for backup and restore
|
|
45
60
|
tabs = QTabWidget()
|
|
46
|
-
|
|
61
|
+
|
|
47
62
|
# Backup tab
|
|
48
63
|
backup_tab = self._create_backup_tab()
|
|
49
64
|
tabs.addTab(backup_tab, "Create Backup")
|
|
50
|
-
|
|
65
|
+
|
|
51
66
|
# Restore tab
|
|
52
67
|
restore_tab = self._create_restore_tab()
|
|
53
68
|
tabs.addTab(restore_tab, "Restore from Backup")
|
|
54
|
-
|
|
69
|
+
|
|
55
70
|
layout.addWidget(tabs)
|
|
56
|
-
|
|
71
|
+
|
|
57
72
|
# Close button
|
|
58
73
|
close_button = QPushButton("Close")
|
|
59
74
|
close_button.clicked.connect(self.accept)
|
|
60
75
|
layout.addWidget(close_button, alignment=Qt.AlignRight)
|
|
61
|
-
|
|
76
|
+
|
|
62
77
|
def _create_backup_tab(self) -> QWidget:
|
|
63
78
|
"""Create the backup tab."""
|
|
64
79
|
widget = QWidget()
|
|
65
80
|
layout = QVBoxLayout(widget)
|
|
66
|
-
|
|
81
|
+
|
|
67
82
|
# Collection selection
|
|
68
83
|
collection_group = QGroupBox("Backup Configuration")
|
|
69
84
|
collection_layout = QFormLayout()
|
|
70
|
-
|
|
85
|
+
|
|
71
86
|
# Collection name
|
|
72
|
-
collection_layout.addRow(
|
|
73
|
-
|
|
87
|
+
collection_layout.addRow(
|
|
88
|
+
"Collection:", QLabel(self.collection_name or "No collection selected")
|
|
89
|
+
)
|
|
90
|
+
|
|
74
91
|
# Backup directory
|
|
75
92
|
dir_layout = QHBoxLayout()
|
|
76
93
|
self.backup_dir_input = QLineEdit(self.backup_dir)
|
|
77
94
|
self.backup_dir_input.setReadOnly(True)
|
|
78
95
|
dir_layout.addWidget(self.backup_dir_input)
|
|
79
|
-
|
|
96
|
+
|
|
80
97
|
browse_btn = QPushButton("Browse...")
|
|
81
98
|
browse_btn.clicked.connect(self._select_backup_dir)
|
|
82
99
|
dir_layout.addWidget(browse_btn)
|
|
83
|
-
|
|
100
|
+
|
|
84
101
|
collection_layout.addRow("Backup Directory:", dir_layout)
|
|
85
|
-
|
|
102
|
+
|
|
86
103
|
# Options
|
|
87
104
|
self.include_embeddings_check = QCheckBox("Include embedding vectors (larger file size)")
|
|
88
105
|
self.include_embeddings_check.setChecked(True)
|
|
89
106
|
collection_layout.addRow("Options:", self.include_embeddings_check)
|
|
90
|
-
|
|
107
|
+
|
|
91
108
|
collection_group.setLayout(collection_layout)
|
|
92
109
|
layout.addWidget(collection_group)
|
|
93
|
-
|
|
110
|
+
|
|
94
111
|
# Info label
|
|
95
112
|
info_label = QLabel(
|
|
96
113
|
"Backup will create a compressed archive containing all collection data, "
|
|
@@ -99,38 +116,38 @@ class BackupRestoreDialog(QDialog):
|
|
|
99
116
|
info_label.setWordWrap(True)
|
|
100
117
|
info_label.setStyleSheet("color: gray; font-style: italic;")
|
|
101
118
|
layout.addWidget(info_label)
|
|
102
|
-
|
|
119
|
+
|
|
103
120
|
layout.addStretch()
|
|
104
|
-
|
|
121
|
+
|
|
105
122
|
# Create backup button
|
|
106
123
|
backup_button = QPushButton("Create Backup")
|
|
107
124
|
backup_button.clicked.connect(self._create_backup)
|
|
108
125
|
backup_button.setStyleSheet("QPushButton { font-weight: bold; padding: 8px; }")
|
|
109
126
|
layout.addWidget(backup_button)
|
|
110
|
-
|
|
127
|
+
|
|
111
128
|
return widget
|
|
112
|
-
|
|
129
|
+
|
|
113
130
|
def _create_restore_tab(self) -> QWidget:
|
|
114
131
|
"""Create the restore tab."""
|
|
115
132
|
widget = QWidget()
|
|
116
133
|
layout = QVBoxLayout(widget)
|
|
117
|
-
|
|
134
|
+
|
|
118
135
|
# Backup list
|
|
119
136
|
layout.addWidget(QLabel("Available Backups:"))
|
|
120
|
-
|
|
137
|
+
|
|
121
138
|
self.backups_list = QListWidget()
|
|
122
139
|
self.backups_list.itemSelectionChanged.connect(self._on_backup_selected)
|
|
123
140
|
layout.addWidget(self.backups_list)
|
|
124
|
-
|
|
141
|
+
|
|
125
142
|
# Refresh button
|
|
126
143
|
refresh_btn = QPushButton("Refresh List")
|
|
127
144
|
refresh_btn.clicked.connect(self._refresh_backups_list)
|
|
128
145
|
layout.addWidget(refresh_btn)
|
|
129
|
-
|
|
146
|
+
|
|
130
147
|
# Restore options
|
|
131
148
|
options_group = QGroupBox("Restore Options")
|
|
132
149
|
options_layout = QVBoxLayout()
|
|
133
|
-
|
|
150
|
+
|
|
134
151
|
# New collection name
|
|
135
152
|
name_layout = QHBoxLayout()
|
|
136
153
|
name_layout.addWidget(QLabel("Restore as:"))
|
|
@@ -138,150 +155,156 @@ class BackupRestoreDialog(QDialog):
|
|
|
138
155
|
self.restore_name_input.setPlaceholderText("Leave empty to use original name")
|
|
139
156
|
name_layout.addWidget(self.restore_name_input)
|
|
140
157
|
options_layout.addLayout(name_layout)
|
|
141
|
-
|
|
158
|
+
|
|
142
159
|
# Overwrite checkbox
|
|
143
160
|
self.overwrite_check = QCheckBox("Overwrite if collection exists")
|
|
144
161
|
self.overwrite_check.setChecked(False)
|
|
145
162
|
options_layout.addWidget(self.overwrite_check)
|
|
146
|
-
|
|
163
|
+
|
|
147
164
|
options_group.setLayout(options_layout)
|
|
148
165
|
layout.addWidget(options_group)
|
|
149
|
-
|
|
166
|
+
|
|
150
167
|
# Restore and delete buttons
|
|
151
168
|
button_layout = QHBoxLayout()
|
|
152
|
-
|
|
169
|
+
|
|
153
170
|
self.restore_button = QPushButton("Restore Selected")
|
|
154
171
|
self.restore_button.clicked.connect(self._restore_backup)
|
|
155
172
|
self.restore_button.setEnabled(False)
|
|
156
173
|
self.restore_button.setStyleSheet("QPushButton { font-weight: bold; padding: 8px; }")
|
|
157
174
|
button_layout.addWidget(self.restore_button)
|
|
158
|
-
|
|
175
|
+
|
|
159
176
|
self.delete_backup_button = QPushButton("Delete Selected")
|
|
160
177
|
self.delete_backup_button.clicked.connect(self._delete_backup)
|
|
161
178
|
self.delete_backup_button.setEnabled(False)
|
|
162
179
|
button_layout.addWidget(self.delete_backup_button)
|
|
163
|
-
|
|
180
|
+
|
|
164
181
|
layout.addLayout(button_layout)
|
|
165
|
-
|
|
182
|
+
|
|
166
183
|
return widget
|
|
167
|
-
|
|
184
|
+
|
|
168
185
|
def _select_backup_dir(self):
|
|
169
186
|
"""Select backup directory."""
|
|
170
187
|
dir_path = QFileDialog.getExistingDirectory(
|
|
171
|
-
self,
|
|
172
|
-
"Select Backup Directory",
|
|
173
|
-
self.backup_dir
|
|
188
|
+
self, "Select Backup Directory", self.backup_dir
|
|
174
189
|
)
|
|
175
|
-
|
|
190
|
+
|
|
176
191
|
if dir_path:
|
|
177
192
|
self.backup_dir = dir_path
|
|
178
193
|
self.backup_dir_input.setText(dir_path)
|
|
179
|
-
|
|
194
|
+
|
|
180
195
|
# Save to settings
|
|
181
196
|
self.settings_service.set("backup_directory", dir_path)
|
|
182
|
-
|
|
197
|
+
|
|
183
198
|
self._refresh_backups_list()
|
|
184
|
-
|
|
199
|
+
|
|
185
200
|
def _create_backup(self):
|
|
186
201
|
"""Create a backup of the current collection."""
|
|
187
202
|
if not self.collection_name:
|
|
188
203
|
QMessageBox.warning(self, "No Collection", "No collection selected for backup.")
|
|
189
204
|
return
|
|
190
|
-
|
|
205
|
+
|
|
191
206
|
# Create backup
|
|
192
207
|
include_embeddings = self.include_embeddings_check.isChecked()
|
|
193
|
-
|
|
208
|
+
|
|
194
209
|
self.loading_dialog.show_loading("Creating backup...")
|
|
195
210
|
QApplication.processEvents()
|
|
196
|
-
|
|
211
|
+
|
|
197
212
|
try:
|
|
198
213
|
backup_path = self.backup_service.backup_collection(
|
|
199
|
-
self.connection,
|
|
214
|
+
self.connection.database,
|
|
200
215
|
self.collection_name,
|
|
201
216
|
self.backup_dir,
|
|
202
|
-
include_embeddings=include_embeddings
|
|
217
|
+
include_embeddings=include_embeddings,
|
|
218
|
+
profile_name=self.connection.name,
|
|
203
219
|
)
|
|
204
220
|
finally:
|
|
205
221
|
self.loading_dialog.hide_loading()
|
|
206
|
-
|
|
222
|
+
|
|
207
223
|
if backup_path:
|
|
208
224
|
QMessageBox.information(
|
|
209
|
-
self,
|
|
210
|
-
"Backup Successful",
|
|
211
|
-
f"Backup created successfully:\n{backup_path}"
|
|
225
|
+
self, "Backup Successful", f"Backup created successfully:\n{backup_path}"
|
|
212
226
|
)
|
|
213
227
|
self._refresh_backups_list()
|
|
214
228
|
else:
|
|
215
229
|
QMessageBox.warning(self, "Backup Failed", "Failed to create backup.")
|
|
216
|
-
|
|
230
|
+
|
|
217
231
|
def _refresh_backups_list(self):
|
|
218
232
|
"""Refresh the list of available backups."""
|
|
219
233
|
self.backups_list.clear()
|
|
220
|
-
|
|
234
|
+
|
|
221
235
|
backups = self.backup_service.list_backups(self.backup_dir)
|
|
222
|
-
|
|
236
|
+
|
|
223
237
|
for backup in backups:
|
|
224
238
|
# Format file size
|
|
225
239
|
size_mb = backup["file_size"] / (1024 * 1024)
|
|
226
|
-
|
|
240
|
+
|
|
227
241
|
item_text = (
|
|
228
242
|
f"{backup['collection_name']} - {backup['timestamp']}\n"
|
|
229
243
|
f" Items: {backup['item_count']}, Size: {size_mb:.2f} MB\n"
|
|
230
244
|
f" File: {backup['file_name']}"
|
|
231
245
|
)
|
|
232
|
-
|
|
246
|
+
|
|
233
247
|
item = QListWidgetItem(item_text)
|
|
234
248
|
item.setData(Qt.UserRole, backup["file_path"])
|
|
235
249
|
self.backups_list.addItem(item)
|
|
236
|
-
|
|
250
|
+
|
|
237
251
|
if not backups:
|
|
238
252
|
item = QListWidgetItem("No backups found in directory")
|
|
239
253
|
item.setFlags(Qt.NoItemFlags)
|
|
240
254
|
self.backups_list.addItem(item)
|
|
241
|
-
|
|
255
|
+
|
|
242
256
|
def _on_backup_selected(self):
|
|
243
257
|
"""Handle backup selection."""
|
|
244
258
|
has_selection = len(self.backups_list.selectedItems()) > 0
|
|
245
259
|
self.restore_button.setEnabled(has_selection)
|
|
246
260
|
self.delete_backup_button.setEnabled(has_selection)
|
|
247
|
-
|
|
261
|
+
|
|
248
262
|
def _restore_backup(self):
|
|
249
263
|
"""Restore a backup."""
|
|
250
264
|
selected_items = self.backups_list.selectedItems()
|
|
251
265
|
if not selected_items:
|
|
252
266
|
return
|
|
253
|
-
|
|
267
|
+
|
|
254
268
|
backup_file = selected_items[0].data(Qt.UserRole)
|
|
255
269
|
if not backup_file:
|
|
256
270
|
return
|
|
257
|
-
|
|
271
|
+
|
|
258
272
|
# Read backup metadata to get original collection name
|
|
259
273
|
try:
|
|
260
|
-
import zipfile
|
|
261
274
|
import json
|
|
262
|
-
|
|
263
|
-
|
|
275
|
+
import zipfile
|
|
276
|
+
|
|
277
|
+
with zipfile.ZipFile(backup_file, "r") as zipf:
|
|
278
|
+
metadata_str = zipf.read("metadata.json").decode("utf-8")
|
|
264
279
|
metadata = json.loads(metadata_str)
|
|
265
280
|
original_name = metadata.get("collection_name", "unknown")
|
|
266
281
|
except Exception as e:
|
|
267
282
|
QMessageBox.warning(self, "Error", f"Failed to read backup metadata: {e}")
|
|
268
283
|
return
|
|
269
|
-
|
|
284
|
+
|
|
270
285
|
# Get restore options
|
|
271
286
|
restore_name = self.restore_name_input.text().strip()
|
|
272
287
|
overwrite = self.overwrite_check.isChecked()
|
|
273
|
-
|
|
288
|
+
|
|
274
289
|
# Determine final collection name
|
|
275
290
|
final_name = restore_name if restore_name else original_name
|
|
276
|
-
|
|
291
|
+
|
|
277
292
|
# Build confirmation message
|
|
278
293
|
if restore_name:
|
|
279
294
|
msg = f"Restore backup to NEW collection: '{final_name}'"
|
|
280
295
|
else:
|
|
281
296
|
msg = f"Restore backup to ORIGINAL collection: '{final_name}'"
|
|
282
|
-
|
|
297
|
+
|
|
283
298
|
# Check if collection exists
|
|
284
|
-
|
|
299
|
+
# Extract the underlying database connection from the ConnectionInstance wrapper.
|
|
300
|
+
actual_conn = getattr(self.connection, "database", self.connection)
|
|
301
|
+
if hasattr(actual_conn, "list_collections"):
|
|
302
|
+
try:
|
|
303
|
+
existing_collections = actual_conn.list_collections()
|
|
304
|
+
except Exception:
|
|
305
|
+
existing_collections = getattr(self.connection, "collections", [])
|
|
306
|
+
else:
|
|
307
|
+
existing_collections = getattr(self.connection, "collections", [])
|
|
285
308
|
if final_name in existing_collections:
|
|
286
309
|
if overwrite:
|
|
287
310
|
msg += f"\n\n⚠️ WARNING: This will DELETE and replace the existing collection '{final_name}'!"
|
|
@@ -290,58 +313,149 @@ class BackupRestoreDialog(QDialog):
|
|
|
290
313
|
self,
|
|
291
314
|
"Collection Exists",
|
|
292
315
|
f"Collection '{final_name}' already exists.\n\n"
|
|
293
|
-
f"Check 'Overwrite existing collection' to replace it, or enter a different name."
|
|
316
|
+
f"Check 'Overwrite existing collection' to replace it, or enter a different name.",
|
|
294
317
|
)
|
|
295
318
|
return
|
|
296
|
-
|
|
319
|
+
|
|
297
320
|
# Confirm
|
|
298
|
-
reply = QMessageBox.question(
|
|
299
|
-
|
|
300
|
-
"Confirm Restore",
|
|
301
|
-
msg,
|
|
302
|
-
QMessageBox.Yes | QMessageBox.No
|
|
303
|
-
)
|
|
304
|
-
|
|
321
|
+
reply = QMessageBox.question(self, "Confirm Restore", msg, QMessageBox.Yes | QMessageBox.No)
|
|
322
|
+
|
|
305
323
|
if reply != QMessageBox.Yes:
|
|
306
324
|
return
|
|
307
|
-
|
|
325
|
+
|
|
326
|
+
# If the backup included embeddings, ask user how to handle them
|
|
327
|
+
recompute_choice: bool | None = None
|
|
328
|
+
try:
|
|
329
|
+
import json
|
|
330
|
+
import zipfile
|
|
331
|
+
|
|
332
|
+
with zipfile.ZipFile(backup_file, "r") as zipf:
|
|
333
|
+
metadata_str = zipf.read("metadata.json").decode("utf-8")
|
|
334
|
+
metadata = json.loads(metadata_str)
|
|
335
|
+
include_embeddings = metadata.get("include_embeddings", False)
|
|
336
|
+
has_model = bool(metadata.get("embedding_model"))
|
|
337
|
+
embedding_model = metadata.get("embedding_model", "unknown")
|
|
338
|
+
|
|
339
|
+
if include_embeddings:
|
|
340
|
+
# Present three options: Use stored (default), Recompute, or Omit
|
|
341
|
+
from PySide6.QtWidgets import (
|
|
342
|
+
QDialog,
|
|
343
|
+
QHBoxLayout,
|
|
344
|
+
QLabel,
|
|
345
|
+
QPushButton,
|
|
346
|
+
QRadioButton,
|
|
347
|
+
QVBoxLayout,
|
|
348
|
+
)
|
|
349
|
+
|
|
350
|
+
dialog = QDialog(self)
|
|
351
|
+
dialog.setWindowTitle("Restore Embeddings")
|
|
352
|
+
dialog.setMinimumWidth(500)
|
|
353
|
+
layout = QVBoxLayout(dialog)
|
|
354
|
+
|
|
355
|
+
# Explanation
|
|
356
|
+
if has_model:
|
|
357
|
+
info_text = (
|
|
358
|
+
f"This backup includes {metadata.get('item_count', 0)} embedding vectors.\n"
|
|
359
|
+
f"Recorded model: {embedding_model}\n\n"
|
|
360
|
+
"How would you like to handle the embeddings?"
|
|
361
|
+
)
|
|
362
|
+
else:
|
|
363
|
+
info_text = (
|
|
364
|
+
f"This backup includes {metadata.get('item_count', 0)} embedding vectors,\n"
|
|
365
|
+
"but no embedding model was recorded.\n\n"
|
|
366
|
+
"How would you like to handle the embeddings?"
|
|
367
|
+
)
|
|
368
|
+
|
|
369
|
+
info_label = QLabel(info_text)
|
|
370
|
+
info_label.setWordWrap(True)
|
|
371
|
+
layout.addWidget(info_label)
|
|
372
|
+
|
|
373
|
+
# Radio buttons for options
|
|
374
|
+
use_stored_radio = QRadioButton(
|
|
375
|
+
"Use stored embeddings (recommended for new collections)"
|
|
376
|
+
)
|
|
377
|
+
use_stored_radio.setChecked(True) # Default option
|
|
378
|
+
layout.addWidget(use_stored_radio)
|
|
379
|
+
|
|
380
|
+
recompute_radio = QRadioButton("Recompute embeddings from documents")
|
|
381
|
+
if has_model:
|
|
382
|
+
recompute_radio.setToolTip(f"Will use model: {embedding_model}")
|
|
383
|
+
else:
|
|
384
|
+
recompute_radio.setToolTip(
|
|
385
|
+
"Will attempt using your current embedding configuration"
|
|
386
|
+
)
|
|
387
|
+
layout.addWidget(recompute_radio)
|
|
388
|
+
|
|
389
|
+
omit_radio = QRadioButton("Omit embeddings (documents and metadata only)")
|
|
390
|
+
layout.addWidget(omit_radio)
|
|
391
|
+
|
|
392
|
+
# Buttons
|
|
393
|
+
button_layout = QHBoxLayout()
|
|
394
|
+
ok_button = QPushButton("OK")
|
|
395
|
+
cancel_button = QPushButton("Cancel")
|
|
396
|
+
ok_button.clicked.connect(dialog.accept)
|
|
397
|
+
cancel_button.clicked.connect(dialog.reject)
|
|
398
|
+
button_layout.addStretch()
|
|
399
|
+
button_layout.addWidget(ok_button)
|
|
400
|
+
button_layout.addWidget(cancel_button)
|
|
401
|
+
layout.addLayout(button_layout)
|
|
402
|
+
|
|
403
|
+
# Show dialog and get choice
|
|
404
|
+
if dialog.exec() != QDialog.Accepted:
|
|
405
|
+
return
|
|
406
|
+
|
|
407
|
+
if use_stored_radio.isChecked():
|
|
408
|
+
recompute_choice = None # Use stored embeddings
|
|
409
|
+
elif recompute_radio.isChecked():
|
|
410
|
+
recompute_choice = True # Recompute
|
|
411
|
+
else: # omit_radio.isChecked()
|
|
412
|
+
recompute_choice = False # Omit
|
|
413
|
+
except Exception:
|
|
414
|
+
recompute_choice = None # Default to using stored embeddings
|
|
415
|
+
|
|
308
416
|
self.loading_dialog.show_loading("Restoring backup...")
|
|
309
417
|
QApplication.processEvents()
|
|
310
|
-
|
|
418
|
+
|
|
311
419
|
try:
|
|
312
|
-
# Restore
|
|
420
|
+
# Restore (pass low-level connection to service)
|
|
313
421
|
success = self.backup_service.restore_collection(
|
|
314
|
-
self.connection,
|
|
422
|
+
self.connection.database,
|
|
315
423
|
backup_file,
|
|
316
424
|
collection_name=restore_name if restore_name else None,
|
|
317
|
-
overwrite=overwrite
|
|
425
|
+
overwrite=overwrite,
|
|
426
|
+
recompute_embeddings=recompute_choice,
|
|
427
|
+
profile_name=self.connection.name,
|
|
318
428
|
)
|
|
319
429
|
finally:
|
|
320
430
|
self.loading_dialog.hide_loading()
|
|
321
|
-
|
|
431
|
+
|
|
322
432
|
if success:
|
|
323
|
-
QMessageBox.information(
|
|
433
|
+
QMessageBox.information(
|
|
434
|
+
self,
|
|
435
|
+
"Restore Successful",
|
|
436
|
+
f"Backup restored successfully to collection '{final_name}'.",
|
|
437
|
+
)
|
|
324
438
|
else:
|
|
325
439
|
QMessageBox.warning(self, "Restore Failed", "Failed to restore backup.")
|
|
326
|
-
|
|
440
|
+
|
|
327
441
|
def _delete_backup(self):
|
|
328
442
|
"""Delete a backup file."""
|
|
329
443
|
selected_items = self.backups_list.selectedItems()
|
|
330
444
|
if not selected_items:
|
|
331
445
|
return
|
|
332
|
-
|
|
446
|
+
|
|
333
447
|
backup_file = selected_items[0].data(Qt.UserRole)
|
|
334
448
|
if not backup_file:
|
|
335
449
|
return
|
|
336
|
-
|
|
450
|
+
|
|
337
451
|
# Confirm deletion
|
|
338
452
|
reply = QMessageBox.question(
|
|
339
453
|
self,
|
|
340
454
|
"Confirm Deletion",
|
|
341
455
|
f"Delete this backup file?\n{Path(backup_file).name}",
|
|
342
|
-
QMessageBox.Yes | QMessageBox.No
|
|
456
|
+
QMessageBox.Yes | QMessageBox.No,
|
|
343
457
|
)
|
|
344
|
-
|
|
458
|
+
|
|
345
459
|
if reply == QMessageBox.Yes:
|
|
346
460
|
if self.backup_service.delete_backup(backup_file):
|
|
347
461
|
QMessageBox.information(self, "Deleted", "Backup deleted successfully.")
|