vector-inspector 0.3.6__py3-none-any.whl → 0.3.8__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.
@@ -1,63 +1,29 @@
1
1
  """Updated main window with multi-database support."""
2
2
 
3
3
  from PySide6.QtWidgets import (
4
- QMainWindow,
5
- QWidget,
6
- QVBoxLayout,
7
- QHBoxLayout,
8
- QSplitter,
9
- QTabWidget,
10
- QStatusBar,
11
- QToolBar,
12
4
  QMessageBox,
13
- QInputDialog,
14
5
  QLabel,
15
- QDockWidget,
16
6
  QApplication,
17
7
  QDialog,
8
+ QToolBar,
9
+ QStatusBar,
18
10
  )
19
- from PySide6.QtCore import Qt, Signal, QTimer, QThread
11
+ from PySide6.QtCore import Qt, QTimer
20
12
  from PySide6.QtGui import QAction
21
13
 
22
- from vector_inspector.core.connection_manager import ConnectionManager, ConnectionState
14
+ from vector_inspector.core.connection_manager import ConnectionManager
23
15
  from vector_inspector.core.connections.base_connection import VectorDBConnection
24
- from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
25
- from vector_inspector.core.connections.qdrant_connection import QdrantConnection
26
- from vector_inspector.core.connections.pinecone_connection import PineconeConnection
27
- from vector_inspector.core.connections.pgvector_connection import PgVectorConnection
28
16
  from vector_inspector.services.profile_service import ProfileService
29
17
  from vector_inspector.services.settings_service import SettingsService
18
+ from vector_inspector.ui.main_window_shell import InspectorShell
30
19
  from vector_inspector.ui.components.connection_manager_panel import ConnectionManagerPanel
31
20
  from vector_inspector.ui.components.profile_manager_panel import ProfileManagerPanel
32
- from vector_inspector.ui.views.info_panel import InfoPanel
33
- from vector_inspector.ui.views.metadata_view import MetadataView
34
- from vector_inspector.ui.views.search_view import SearchView
35
- from vector_inspector.ui.components.loading_dialog import LoadingDialog
36
-
37
-
38
- class ConnectionThread(QThread):
39
- """Background thread for connecting to database."""
40
-
41
- finished = Signal(bool, list, str) # success, collections, error_message
42
-
43
- def __init__(self, connection):
44
- super().__init__()
45
- self.connection = connection
46
-
47
- def run(self):
48
- """Connect to database and get collections."""
49
- try:
50
- success = self.connection.connect()
51
- if success:
52
- collections = self.connection.list_collections()
53
- self.finished.emit(True, collections, "")
54
- else:
55
- self.finished.emit(False, [], "Connection failed")
56
- except Exception as e:
57
- self.finished.emit(False, [], str(e))
21
+ from vector_inspector.ui.tabs import InspectorTabs
22
+ from vector_inspector.ui.controllers.connection_controller import ConnectionController
23
+ from vector_inspector.ui.services.dialog_service import DialogService
58
24
 
59
25
 
60
- class MainWindow(QMainWindow):
26
+ class MainWindow(InspectorShell):
61
27
  """Main application window with multi-database support."""
62
28
 
63
29
  def __init__(self):
@@ -67,11 +33,21 @@ class MainWindow(QMainWindow):
67
33
  self.connection_manager = ConnectionManager()
68
34
  self.profile_service = ProfileService()
69
35
  self.settings_service = SettingsService()
70
- self.loading_dialog = LoadingDialog("Loading...", self)
36
+
37
+ # Controller for connection operations
38
+ self.connection_controller = ConnectionController(
39
+ self.connection_manager, self.profile_service, self
40
+ )
71
41
 
72
42
  # State
73
43
  self.visualization_view = None
74
- self._connection_threads = {} # Track connection threads
44
+
45
+ # View references (will be set in _setup_ui)
46
+ self.info_panel = None
47
+ self.metadata_view = None
48
+ self.search_view = None
49
+ self.connection_panel = None
50
+ self.profile_panel = None
75
51
 
76
52
  self.setWindowTitle("Vector Inspector")
77
53
  self.setGeometry(100, 100, 1600, 900)
@@ -101,62 +77,36 @@ class MainWindow(QMainWindow):
101
77
  print(f"[SplashWindow] Failed to show splash: {e}")
102
78
 
103
79
  def _setup_ui(self):
104
- """Setup the main UI layout."""
105
- # Central widget with splitter
106
- central_widget = QWidget()
107
- self.setCentralWidget(central_widget)
108
-
109
- layout = QHBoxLayout(central_widget)
110
- layout.setContentsMargins(5, 5, 5, 5)
111
-
112
- # Main splitter (left panel | right tabs)
113
- main_splitter = QSplitter(Qt.Horizontal)
114
-
115
- # Left panel - Connections and Profiles
116
- left_panel = QWidget()
117
- left_layout = QVBoxLayout(left_panel)
118
- left_layout.setContentsMargins(0, 0, 0, 0)
119
-
120
- # Create tab widget for connections and profiles
121
- self.left_tabs = QTabWidget()
122
-
123
- # Connection manager panel
80
+ """Setup the main UI layout using InspectorShell."""
81
+ # Left panels - Connections and Profiles
124
82
  self.connection_panel = ConnectionManagerPanel(self.connection_manager)
125
- self.left_tabs.addTab(self.connection_panel, "Active")
83
+ self.add_left_panel(self.connection_panel, "Active")
126
84
 
127
- # Profile manager panel
128
85
  self.profile_panel = ProfileManagerPanel(self.profile_service)
129
- self.left_tabs.addTab(self.profile_panel, "Profiles")
130
-
131
- left_layout.addWidget(self.left_tabs)
86
+ self.add_left_panel(self.profile_panel, "Profiles")
132
87
 
133
- # Right panel - Tabbed views
134
- self.tab_widget = QTabWidget()
88
+ # Main content tabs using TabRegistry
89
+ tab_defs = InspectorTabs.get_standard_tabs()
135
90
 
136
- # Create views (they'll be updated when collection changes)
137
- self.info_panel = InfoPanel(None) # Will be set later
138
- self.metadata_view = MetadataView(None) # Will be set later
139
- self.search_view = SearchView(None) # Will be set later
91
+ for i, tab_def in enumerate(tab_defs):
92
+ widget = InspectorTabs.create_tab_widget(tab_def, connection=None)
93
+ self.add_main_tab(widget, tab_def.title)
140
94
 
141
- self.tab_widget.addTab(self.info_panel, "Info")
142
- self.tab_widget.addTab(self.metadata_view, "Data Browser")
143
- self.tab_widget.addTab(self.search_view, "Search")
144
- self.tab_widget.addTab(QWidget(), "Visualization") # Placeholder
95
+ # Store references to views (except placeholder)
96
+ if i == InspectorTabs.INFO_TAB:
97
+ self.info_panel = widget
98
+ elif i == InspectorTabs.DATA_TAB:
99
+ self.metadata_view = widget
100
+ elif i == InspectorTabs.SEARCH_TAB:
101
+ self.search_view = widget
102
+ # Visualization is lazy-loaded, so it's a placeholder for now
145
103
 
146
104
  # Set Info tab as default
147
- self.tab_widget.setCurrentIndex(0)
105
+ self.set_main_tab_active(InspectorTabs.INFO_TAB)
148
106
 
149
107
  # Connect to tab change to lazy load visualization
150
108
  self.tab_widget.currentChanged.connect(self._on_tab_changed)
151
109
 
152
- # Add panels to splitter
153
- main_splitter.addWidget(left_panel)
154
- main_splitter.addWidget(self.tab_widget)
155
- main_splitter.setStretchFactor(0, 1)
156
- main_splitter.setStretchFactor(1, 4)
157
-
158
- layout.addWidget(main_splitter)
159
-
160
110
  def _setup_menu_bar(self):
161
111
  """Setup application menu bar."""
162
112
  menubar = self.menuBar()
@@ -211,10 +161,28 @@ class MainWindow(QMainWindow):
211
161
 
212
162
  # Help menu
213
163
  help_menu = menubar.addMenu("&Help")
214
-
215
164
  about_action = QAction("&About", self)
216
165
  about_action.triggered.connect(self._show_about)
217
166
  help_menu.addAction(about_action)
167
+ check_update_action = QAction("Check for Update", self)
168
+ check_update_action.triggered.connect(self._check_for_update_from_menu)
169
+ help_menu.addAction(check_update_action)
170
+
171
+ def _check_for_update_from_menu(self):
172
+ from vector_inspector.services.update_service import UpdateService
173
+ from vector_inspector.utils.version import get_app_version
174
+ from PySide6.QtWidgets import QMessageBox
175
+
176
+ latest = UpdateService.get_latest_release(force_refresh=True)
177
+ if latest:
178
+ current_version = get_app_version()
179
+ latest_version = latest.get("tag_name")
180
+ if latest_version and UpdateService.compare_versions(current_version, latest_version):
181
+ # Show update modal
182
+ self._latest_release = latest
183
+ self._on_update_indicator_clicked(None)
184
+ return
185
+ QMessageBox.information(self, "Check for Update", "No update available.")
218
186
 
219
187
  def _setup_toolbar(self):
220
188
  """Setup application toolbar."""
@@ -233,7 +201,7 @@ class MainWindow(QMainWindow):
233
201
  toolbar.addAction(refresh_action)
234
202
 
235
203
  def _setup_statusbar(self):
236
- """Setup status bar with connection breadcrumb."""
204
+ """Setup status bar with connection breadcrumb and update indicator."""
237
205
  status_bar = QStatusBar()
238
206
  self.setStatusBar(status_bar)
239
207
 
@@ -241,8 +209,52 @@ class MainWindow(QMainWindow):
241
209
  self.breadcrumb_label = QLabel("No active connection")
242
210
  self.statusBar().addPermanentWidget(self.breadcrumb_label)
243
211
 
212
+ # Update indicator label (hidden by default)
213
+ self.update_indicator = QLabel()
214
+ self.update_indicator.setText("")
215
+ self.update_indicator.setStyleSheet(
216
+ "color: #2980b9; font-weight: bold; text-decoration: underline;"
217
+ )
218
+ self.update_indicator.setVisible(False)
219
+ self.update_indicator.setCursor(Qt.PointingHandCursor)
220
+ self.statusBar().addPermanentWidget(self.update_indicator)
221
+
244
222
  self.statusBar().showMessage("Ready")
245
223
 
224
+ # Connect click event
225
+ self.update_indicator.mousePressEvent = self._on_update_indicator_clicked
226
+
227
+ # Check for updates on launch
228
+ from vector_inspector.services.update_service import UpdateService
229
+ from vector_inspector.utils.version import get_app_version
230
+ import threading
231
+
232
+ from PySide6.QtCore import QTimer
233
+
234
+ def check_updates():
235
+ latest = UpdateService.get_latest_release()
236
+ if latest:
237
+ current_version = get_app_version()
238
+ latest_version = latest.get("tag_name")
239
+ if latest_version and UpdateService.compare_versions(
240
+ current_version, latest_version
241
+ ):
242
+
243
+ def show_update():
244
+ self._latest_release = latest
245
+ self.update_indicator.setText(f"Update available: v{latest_version}")
246
+ self.update_indicator.setVisible(True)
247
+
248
+ QTimer.singleShot(0, show_update)
249
+
250
+ threading.Thread(target=check_updates, daemon=True).start()
251
+
252
+ def _on_update_indicator_clicked(self, event):
253
+ # Show update details dialog
254
+ if not hasattr(self, "_latest_release"):
255
+ return
256
+ DialogService.show_update_details(self._latest_release, self)
257
+
246
258
  def _connect_signals(self):
247
259
  """Connect signals between components."""
248
260
  # Connection manager signals
@@ -255,6 +267,9 @@ class MainWindow(QMainWindow):
255
267
  self.connection_manager.collections_updated.connect(self._on_collections_updated)
256
268
  self.connection_manager.connection_opened.connect(self._on_connection_opened)
257
269
 
270
+ # Connection controller signals
271
+ self.connection_controller.connection_completed.connect(self._on_connection_completed)
272
+
258
273
  # Connection panel signals
259
274
  self.connection_panel.collection_selected.connect(self._on_collection_selected_from_panel)
260
275
  self.connection_panel.add_connection_btn.clicked.connect(self._new_connection_from_profile)
@@ -262,9 +277,20 @@ class MainWindow(QMainWindow):
262
277
  # Profile panel signals
263
278
  self.profile_panel.connect_profile.connect(self._connect_to_profile)
264
279
 
280
+ def _on_connection_completed(
281
+ self, connection_id: str, success: bool, collections: list, error: str
282
+ ):
283
+ """Handle connection completed event from controller."""
284
+ if success:
285
+ # Switch to Active connections tab
286
+ self.set_left_panel_active(0)
287
+ self.statusBar().showMessage(
288
+ f"Connected successfully ({len(collections)} collections)", 5000
289
+ )
290
+
265
291
  def _on_tab_changed(self, index: int):
266
292
  """Handle tab change - lazy load visualization tab."""
267
- if index == 3 and self.visualization_view is None:
293
+ if index == InspectorTabs.VISUALIZATION_TAB and self.visualization_view is None:
268
294
  # Lazy load visualization view
269
295
  from vector_inspector.ui.views.visualization_view import VisualizationView
270
296
 
@@ -274,9 +300,11 @@ class MainWindow(QMainWindow):
274
300
 
275
301
  self.visualization_view = VisualizationView(conn)
276
302
  # Replace placeholder with actual view
277
- self.tab_widget.removeTab(3)
278
- self.tab_widget.insertTab(3, self.visualization_view, "Visualization")
279
- self.tab_widget.setCurrentIndex(3)
303
+ self.remove_main_tab(InspectorTabs.VISUALIZATION_TAB)
304
+ self.add_main_tab(
305
+ self.visualization_view, "Visualization", InspectorTabs.VISUALIZATION_TAB
306
+ )
307
+ self.set_main_tab_active(InspectorTabs.VISUALIZATION_TAB)
280
308
 
281
309
  # Set collection if one is already selected
282
310
  if active and active.active_collection:
@@ -314,20 +342,22 @@ class MainWindow(QMainWindow):
314
342
  if connection_id == self.connection_manager.get_active_connection_id():
315
343
  # Show loading immediately when collection changes
316
344
  if collection_name:
317
- self.loading_dialog.show_loading(f"Loading collection '{collection_name}'...")
345
+ self.connection_controller.loading_dialog.show_loading(
346
+ f"Loading collection '{collection_name}'..."
347
+ )
318
348
  QApplication.processEvents()
319
349
  try:
320
350
  self._update_views_for_collection(collection_name)
321
351
  finally:
322
- self.loading_dialog.hide_loading()
352
+ self.connection_controller.loading_dialog.hide_loading()
323
353
  else:
324
354
  # Clear collection from views
325
- self.loading_dialog.show_loading("Clearing collection...")
355
+ self.connection_controller.loading_dialog.show_loading("Clearing collection...")
326
356
  QApplication.processEvents()
327
357
  try:
328
358
  self._update_views_for_collection(None)
329
359
  finally:
330
- self.loading_dialog.hide_loading()
360
+ self.connection_controller.loading_dialog.hide_loading()
331
361
 
332
362
  def _on_collections_updated(self, connection_id: str, collections: list):
333
363
  """Handle collections list updated."""
@@ -345,7 +375,9 @@ class MainWindow(QMainWindow):
345
375
  def _on_collection_selected_from_panel(self, connection_id: str, collection_name: str):
346
376
  """Handle collection selection from connection panel."""
347
377
  # Show loading dialog while switching collections
348
- self.loading_dialog.show_loading(f"Loading collection '{collection_name}'...")
378
+ self.connection_controller.loading_dialog.show_loading(
379
+ f"Loading collection '{collection_name}'..."
380
+ )
349
381
  QApplication.processEvents()
350
382
 
351
383
  try:
@@ -353,7 +385,7 @@ class MainWindow(QMainWindow):
353
385
  # Just update the views
354
386
  self._update_views_for_collection(collection_name)
355
387
  finally:
356
- self.loading_dialog.hide_loading()
388
+ self.connection_controller.loading_dialog.hide_loading()
357
389
 
358
390
  def _update_views_with_connection(self, connection: VectorDBConnection):
359
391
  """Update all views with a new connection."""
@@ -392,176 +424,20 @@ class MainWindow(QMainWindow):
392
424
 
393
425
  def _new_connection_from_profile(self):
394
426
  """Show dialog to create new connection (switches to Profiles tab)."""
395
- self.left_tabs.setCurrentIndex(1) # Switch to Profiles tab
396
- QMessageBox.information(
397
- self,
398
- "Connect to Profile",
399
- "Select a profile from the list and click 'Connect', or click '+' to create a new profile.",
400
- )
427
+ self.set_left_panel_active(1) # Switch to Profiles tab
428
+ DialogService.show_profile_editor_prompt(self)
401
429
 
402
430
  def _show_profile_editor(self):
403
431
  """Show profile editor to create new profile."""
404
- self.left_tabs.setCurrentIndex(1) # Switch to Profiles tab
432
+ self.set_left_panel_active(1) # Switch to Profiles tab
405
433
  self.profile_panel._create_profile()
406
434
 
407
435
  def _connect_to_profile(self, profile_id: str):
408
- """Connect to a profile."""
409
- profile_data = self.profile_service.get_profile_with_credentials(profile_id)
410
- if not profile_data:
411
- QMessageBox.warning(self, "Error", "Profile not found.")
412
- return
413
-
414
- # Check connection limit
415
- if self.connection_manager.get_connection_count() >= ConnectionManager.MAX_CONNECTIONS:
416
- QMessageBox.warning(
417
- self,
418
- "Connection Limit",
419
- f"Maximum number of connections ({ConnectionManager.MAX_CONNECTIONS}) reached. "
420
- "Please close a connection first.",
421
- )
422
- return
423
-
424
- # Create connection
425
- provider = profile_data["provider"]
426
- config = profile_data["config"]
427
- credentials = profile_data.get("credentials", {})
428
-
429
- try:
430
- # Create connection object
431
- if provider == "chromadb":
432
- connection = self._create_chroma_connection(config, credentials)
433
- elif provider == "qdrant":
434
- connection = self._create_qdrant_connection(config, credentials)
435
- elif provider == "pinecone":
436
- connection = self._create_pinecone_connection(config, credentials)
437
- elif provider == "pgvector":
438
- connection = self._create_pgvector_connection(config, credentials)
439
- else:
440
- QMessageBox.warning(self, "Error", f"Unsupported provider: {provider}")
441
- return
442
-
443
- # Register with connection manager, using profile_id as connection_id for persistence
444
- connection_id = self.connection_manager.create_connection(
445
- name=profile_data["name"],
446
- provider=provider,
447
- connection=connection,
448
- config=config,
449
- connection_id=profile_data["id"],
450
- )
451
-
452
- # Update state to connecting
453
- self.connection_manager.update_connection_state(
454
- connection_id, ConnectionState.CONNECTING
455
- )
456
-
457
- # Connect in background thread
458
- thread = ConnectionThread(connection)
459
- thread.finished.connect(
460
- lambda success, collections, error: self._on_connection_finished(
461
- connection_id, success, collections, error
462
- )
463
- )
464
- self._connection_threads[connection_id] = thread
465
- thread.start()
466
-
467
- # Show loading dialog
468
- self.loading_dialog.show_loading(f"Connecting to {profile_data['name']}...")
469
-
470
- except Exception as e:
471
- QMessageBox.critical(self, "Connection Error", f"Failed to create connection: {e}")
472
-
473
- def _create_chroma_connection(self, config: dict, credentials: dict) -> ChromaDBConnection:
474
- """Create a ChromaDB connection."""
475
- conn_type = config.get("type")
476
-
477
- if conn_type == "persistent":
478
- return ChromaDBConnection(path=config.get("path"))
479
- elif conn_type == "http":
480
- return ChromaDBConnection(host=config.get("host"), port=config.get("port"))
481
- else: # ephemeral
482
- return ChromaDBConnection()
483
-
484
- def _create_qdrant_connection(self, config: dict, credentials: dict) -> QdrantConnection:
485
- """Create a Qdrant connection."""
486
- conn_type = config.get("type")
487
- api_key = credentials.get("api_key")
488
-
489
- if conn_type == "persistent":
490
- return QdrantConnection(path=config.get("path"))
491
- elif conn_type == "http":
492
- return QdrantConnection(
493
- host=config.get("host"), port=config.get("port"), api_key=api_key
494
- )
495
- else: # ephemeral
496
- return QdrantConnection()
497
-
498
- def _create_pinecone_connection(self, config: dict, credentials: dict) -> PineconeConnection:
499
- """Create a Pinecone connection."""
500
- api_key = credentials.get("api_key")
501
- if not api_key:
502
- raise ValueError("Pinecone requires an API key")
503
-
504
- return PineconeConnection(api_key=api_key)
505
-
506
- def _create_pgvector_connection(self, config: dict, credentials: dict) -> PgVectorConnection:
507
- """Create a PgVector/Postgres connection from profile config/credentials."""
508
- conn_type = config.get("type")
509
-
510
- # We expect HTTP-style profile for pgvector (host/port + db creds)
511
- if conn_type == "http":
512
- host = config.get("host", "localhost")
513
- port = int(config.get("port", 5432))
514
- database = config.get("database")
515
- user = config.get("user")
516
- # Prefer password from credentials
517
- password = credentials.get("password")
518
-
519
- return PgVectorConnection(
520
- host=host, port=port, database=database, user=user, password=password
521
- )
522
-
523
- raise ValueError("Unsupported connection type for PgVector profile")
524
-
525
- def _on_connection_finished(
526
- self, connection_id: str, success: bool, collections: list, error: str
527
- ):
528
- """Handle connection thread completion."""
529
- self.loading_dialog.hide_loading()
530
-
531
- # Clean up thread
532
- thread = self._connection_threads.pop(connection_id, None)
533
- if thread:
534
- thread.wait() # Wait for thread to fully finish
535
- thread.deleteLater()
536
-
436
+ """Connect to a profile using the connection controller."""
437
+ success = self.connection_controller.connect_to_profile(profile_id)
537
438
  if success:
538
- # Update state to connected
539
- self.connection_manager.update_connection_state(
540
- connection_id, ConnectionState.CONNECTED
541
- )
542
-
543
- # Mark connection as opened first (will show in UI)
544
- self.connection_manager.mark_connection_opened(connection_id)
545
-
546
- # Then update collections (UI item now exists to receive them)
547
- self.connection_manager.update_collections(connection_id, collections)
548
-
549
- # Switch to Active connections tab
550
- self.left_tabs.setCurrentIndex(0)
551
-
552
- self.statusBar().showMessage(
553
- f"Connected successfully ({len(collections)} collections)", 5000
554
- )
555
- else:
556
- # Update state to error
557
- self.connection_manager.update_connection_state(
558
- connection_id, ConnectionState.ERROR, error
559
- )
560
-
561
- QMessageBox.warning(self, "Connection Failed", f"Failed to connect: {error}")
562
-
563
- # Remove the failed connection
564
- self.connection_manager.close_connection(connection_id)
439
+ # Switch to Active connections tab after initiating connection
440
+ self.set_left_panel_active(0)
565
441
 
566
442
  def _refresh_active_connection(self):
567
443
  """Refresh collections for the active connection."""
@@ -593,13 +469,7 @@ class MainWindow(QMainWindow):
593
469
 
594
470
  def _show_about(self):
595
471
  """Show about dialog."""
596
- from .main_window import get_about_html
597
-
598
- QMessageBox.about(
599
- self,
600
- "About Vector Inspector",
601
- get_about_html(),
602
- )
472
+ DialogService.show_about(self)
603
473
 
604
474
  def _toggle_cache(self, checked: bool):
605
475
  """Toggle caching on/off."""
@@ -609,53 +479,25 @@ class MainWindow(QMainWindow):
609
479
 
610
480
  def _show_migration_dialog(self):
611
481
  """Show cross-database migration dialog."""
612
- if self.connection_manager.get_connection_count() < 2:
613
- QMessageBox.information(
614
- self,
615
- "Insufficient Connections",
616
- "You need at least 2 active connections to migrate data.\n"
617
- "Please connect to additional databases first.",
618
- )
619
- return
620
-
621
- from vector_inspector.ui.dialogs.cross_db_migration import CrossDatabaseMigrationDialog
622
-
623
- dialog = CrossDatabaseMigrationDialog(self.connection_manager, self)
624
- dialog.exec()
482
+ DialogService.show_migration_dialog(self.connection_manager, self)
625
483
 
626
484
  def _show_backup_restore_dialog(self):
627
485
  """Show backup/restore dialog for the active collection."""
628
- # Check if there's an active connection
486
+ # Get active connection and collection
629
487
  connection = self.connection_manager.get_active_connection()
630
- if not connection:
631
- QMessageBox.information(self, "No Connection", "Please connect to a database first.")
632
- return
633
-
634
- # Get active collection
635
488
  collection_name = self.connection_manager.get_active_collection()
636
- if not collection_name:
637
- # Allow opening dialog without a collection selected (for restore-only)
638
- QMessageBox.information(
639
- self,
640
- "No Collection Selected",
641
- "You can restore backups without a collection selected.\n"
642
- "To create a backup, please select a collection first.",
643
- )
644
489
 
645
- from vector_inspector.ui.components.backup_restore_dialog import BackupRestoreDialog
490
+ # Show dialog
491
+ result = DialogService.show_backup_restore_dialog(connection, collection_name or "", self)
646
492
 
647
- dialog = BackupRestoreDialog(connection, collection_name or "", self)
648
- if dialog.exec() == QDialog.Accepted:
493
+ if result == QDialog.Accepted:
649
494
  # Refresh collections after restore
650
495
  self._refresh_active_connection()
651
496
 
652
497
  def closeEvent(self, event):
653
498
  """Handle application close."""
654
- # Wait for all connection threads to finish
655
- for thread in list(self._connection_threads.values()):
656
- if thread.isRunning():
657
- thread.quit()
658
- thread.wait(1000) # Wait up to 1 second
499
+ # Clean up connection controller
500
+ self.connection_controller.cleanup()
659
501
 
660
502
  # Clean up temp HTML files from visualization view
661
503
  if self.visualization_view is not None:
@@ -667,20 +509,3 @@ class MainWindow(QMainWindow):
667
509
  self.connection_manager.close_all_connections()
668
510
 
669
511
  event.accept()
670
-
671
-
672
- def get_about_html():
673
- from vector_inspector.utils.version import get_app_version
674
-
675
- version = get_app_version()
676
- version_html = (
677
- f"<h2>Vector Inspector {version}</h2>" if version else "<h2>Vector Inspector</h2>"
678
- )
679
- return (
680
- version_html + "<p>A comprehensive desktop application for visualizing, "
681
- "querying, and managing multiple vector databases simultaneously.</p>"
682
- '<p><a href="https://github.com/anthonypdawson/vector-inspector" style="color:#2980b9;">GitHub Project Page</a></p>'
683
- "<hr />"
684
- "<p>Built with PySide6</p>"
685
- "<p><b>New:</b> Pinecone support!</p>"
686
- )