vector-inspector 0.3.7__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.
@@ -0,0 +1,97 @@
1
+ """Factory for creating vector database connections from provider configs."""
2
+
3
+ from typing import Dict, Any
4
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
5
+ from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
6
+ from vector_inspector.core.connections.qdrant_connection import QdrantConnection
7
+ from vector_inspector.core.connections.pinecone_connection import PineconeConnection
8
+ from vector_inspector.core.connections.pgvector_connection import PgVectorConnection
9
+
10
+
11
+ class ProviderFactory:
12
+ """Factory for creating database connections from configuration."""
13
+
14
+ @staticmethod
15
+ def create(
16
+ provider: str, config: Dict[str, Any], credentials: Dict[str, Any] = None
17
+ ) -> VectorDBConnection:
18
+ """Create a connection object for the specified provider.
19
+
20
+ Args:
21
+ provider: Provider type (chromadb, qdrant, pinecone, pgvector)
22
+ config: Provider-specific configuration
23
+ credentials: Optional credentials (API keys, passwords, etc.)
24
+
25
+ Returns:
26
+ VectorDBConnection instance
27
+
28
+ Raises:
29
+ ValueError: If provider is unsupported or configuration is invalid
30
+ """
31
+ credentials = credentials or {}
32
+
33
+ if provider == "chromadb":
34
+ return ProviderFactory._create_chroma(config, credentials)
35
+ elif provider == "qdrant":
36
+ return ProviderFactory._create_qdrant(config, credentials)
37
+ elif provider == "pinecone":
38
+ return ProviderFactory._create_pinecone(config, credentials)
39
+ elif provider == "pgvector":
40
+ return ProviderFactory._create_pgvector(config, credentials)
41
+ else:
42
+ raise ValueError(f"Unsupported provider: {provider}")
43
+
44
+ @staticmethod
45
+ def _create_chroma(config: Dict[str, Any], credentials: Dict[str, Any]) -> ChromaDBConnection:
46
+ """Create a ChromaDB connection."""
47
+ conn_type = config.get("type")
48
+
49
+ if conn_type == "persistent":
50
+ return ChromaDBConnection(path=config.get("path"))
51
+ elif conn_type == "http":
52
+ return ChromaDBConnection(host=config.get("host"), port=config.get("port"))
53
+ else: # ephemeral
54
+ return ChromaDBConnection()
55
+
56
+ @staticmethod
57
+ def _create_qdrant(config: Dict[str, Any], credentials: Dict[str, Any]) -> QdrantConnection:
58
+ """Create a Qdrant connection."""
59
+ conn_type = config.get("type")
60
+ api_key = credentials.get("api_key")
61
+
62
+ if conn_type == "persistent":
63
+ return QdrantConnection(path=config.get("path"))
64
+ elif conn_type == "http":
65
+ return QdrantConnection(
66
+ host=config.get("host"), port=config.get("port"), api_key=api_key
67
+ )
68
+ else: # ephemeral
69
+ return QdrantConnection()
70
+
71
+ @staticmethod
72
+ def _create_pinecone(config: Dict[str, Any], credentials: Dict[str, Any]) -> PineconeConnection:
73
+ """Create a Pinecone connection."""
74
+ api_key = credentials.get("api_key")
75
+ if not api_key:
76
+ raise ValueError("Pinecone requires an API key")
77
+
78
+ return PineconeConnection(api_key=api_key)
79
+
80
+ @staticmethod
81
+ def _create_pgvector(config: Dict[str, Any], credentials: Dict[str, Any]) -> PgVectorConnection:
82
+ """Create a PgVector/Postgres connection."""
83
+ conn_type = config.get("type")
84
+
85
+ if conn_type == "http":
86
+ host = config.get("host", "localhost")
87
+ port = int(config.get("port", 5432))
88
+ database = config.get("database")
89
+ user = config.get("user")
90
+ # Prefer password from credentials
91
+ password = credentials.get("password")
92
+
93
+ return PgVectorConnection(
94
+ host=host, port=port, database=database, user=user, password=password
95
+ )
96
+
97
+ raise ValueError("Unsupported connection type for PgVector profile")
@@ -34,10 +34,22 @@ class SplashWindow(QDialog):
34
34
  layout.addWidget(github)
35
35
 
36
36
  # About info (reuse About dialog text)
37
- from vector_inspector.ui.main_window import get_about_html
37
+ from vector_inspector.utils.version import get_app_version
38
38
 
39
39
  about = QTextBrowser()
40
- about.setHtml(get_about_html())
40
+ version = get_app_version()
41
+ version_html = (
42
+ f"<h2>Vector Inspector {version}</h2>" if version else "<h2>Vector Inspector</h2>"
43
+ )
44
+ about_text = (
45
+ version_html + "<p>A comprehensive desktop application for visualizing, "
46
+ "querying, and managing multiple vector databases simultaneously.</p>"
47
+ '<p><a href="https://github.com/anthonypdawson/vector-inspector" style="color:#2980b9;">GitHub Project Page</a></p>'
48
+ "<hr />"
49
+ "<p>Built with PySide6</p>"
50
+ "<p><b>New:</b> Pinecone support!</p>"
51
+ )
52
+ about.setHtml(about_text)
41
53
  about.setOpenExternalLinks(True)
42
54
  about.setMaximumHeight(160)
43
55
  layout.addWidget(about)
@@ -0,0 +1 @@
1
+ """UI controllers for managing application logic."""
@@ -0,0 +1,177 @@
1
+ """Controller for managing connection lifecycle and threading."""
2
+
3
+ from typing import Dict, Optional
4
+ from PySide6.QtCore import QObject, Signal, QThread
5
+ from PySide6.QtWidgets import QMessageBox, QWidget
6
+
7
+ from vector_inspector.core.connection_manager import ConnectionManager, ConnectionState
8
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
9
+ from vector_inspector.core.provider_factory import ProviderFactory
10
+ from vector_inspector.services.profile_service import ProfileService
11
+ from vector_inspector.ui.components.loading_dialog import LoadingDialog
12
+
13
+
14
+ class ConnectionThread(QThread):
15
+ """Background thread for connecting to database."""
16
+
17
+ finished = Signal(bool, list, str) # success, collections, error_message
18
+
19
+ def __init__(self, connection: VectorDBConnection):
20
+ super().__init__()
21
+ self.connection = connection
22
+
23
+ def run(self):
24
+ """Connect to database and get collections."""
25
+ try:
26
+ success = self.connection.connect()
27
+ if success:
28
+ collections = self.connection.list_collections()
29
+ self.finished.emit(True, collections, "")
30
+ else:
31
+ self.finished.emit(False, [], "Connection failed")
32
+ except Exception as e:
33
+ self.finished.emit(False, [], str(e))
34
+
35
+
36
+ class ConnectionController(QObject):
37
+ """Controller for managing connection operations and lifecycle.
38
+
39
+ This handles:
40
+ - Creating connections from profiles
41
+ - Starting connection threads
42
+ - Handling connection results
43
+ - Managing loading dialogs
44
+ - Emitting signals for UI updates
45
+ """
46
+
47
+ connection_completed = Signal(
48
+ str, bool, list, str
49
+ ) # connection_id, success, collections, error
50
+
51
+ def __init__(
52
+ self,
53
+ connection_manager: ConnectionManager,
54
+ profile_service: ProfileService,
55
+ parent: Optional[QWidget] = None,
56
+ ):
57
+ super().__init__(parent)
58
+ self.connection_manager = connection_manager
59
+ self.profile_service = profile_service
60
+ self.parent_widget = parent
61
+
62
+ # State
63
+ self._connection_threads: Dict[str, ConnectionThread] = {}
64
+ self.loading_dialog = LoadingDialog("Loading...", parent)
65
+
66
+ def connect_to_profile(self, profile_id: str) -> bool:
67
+ """Connect to a profile.
68
+
69
+ Args:
70
+ profile_id: ID of the profile to connect to
71
+
72
+ Returns:
73
+ True if connection initiated successfully, False otherwise
74
+ """
75
+ profile_data = self.profile_service.get_profile_with_credentials(profile_id)
76
+ if not profile_data:
77
+ QMessageBox.warning(self.parent_widget, "Error", "Profile not found.")
78
+ return False
79
+
80
+ # Check connection limit
81
+ if self.connection_manager.get_connection_count() >= ConnectionManager.MAX_CONNECTIONS:
82
+ QMessageBox.warning(
83
+ self.parent_widget,
84
+ "Connection Limit",
85
+ f"Maximum number of connections ({ConnectionManager.MAX_CONNECTIONS}) reached. "
86
+ "Please close a connection first.",
87
+ )
88
+ return False
89
+
90
+ # Create connection
91
+ provider = profile_data["provider"]
92
+ config = profile_data["config"]
93
+ credentials = profile_data.get("credentials", {})
94
+
95
+ try:
96
+ # Create connection object using factory
97
+ connection = ProviderFactory.create(provider, config, credentials)
98
+
99
+ # Register with connection manager, using profile_id as connection_id for persistence
100
+ connection_id = self.connection_manager.create_connection(
101
+ name=profile_data["name"],
102
+ provider=provider,
103
+ connection=connection,
104
+ config=config,
105
+ connection_id=profile_data["id"],
106
+ )
107
+
108
+ # Update state to connecting
109
+ self.connection_manager.update_connection_state(
110
+ connection_id, ConnectionState.CONNECTING
111
+ )
112
+
113
+ # Connect in background thread
114
+ thread = ConnectionThread(connection)
115
+ thread.finished.connect(
116
+ lambda success, collections, error: self._on_connection_finished(
117
+ connection_id, success, collections, error
118
+ )
119
+ )
120
+ self._connection_threads[connection_id] = thread
121
+ thread.start()
122
+
123
+ # Show loading dialog
124
+ self.loading_dialog.show_loading(f"Connecting to {profile_data['name']}...")
125
+ return True
126
+
127
+ except Exception as e:
128
+ QMessageBox.critical(
129
+ self.parent_widget, "Connection Error", f"Failed to create connection: {e}"
130
+ )
131
+ return False
132
+
133
+ def _on_connection_finished(
134
+ self, connection_id: str, success: bool, collections: list, error: str
135
+ ):
136
+ """Handle connection thread completion."""
137
+ self.loading_dialog.hide_loading()
138
+
139
+ # Clean up thread
140
+ thread = self._connection_threads.pop(connection_id, None)
141
+ if thread:
142
+ thread.wait() # Wait for thread to fully finish
143
+ thread.deleteLater()
144
+
145
+ if success:
146
+ # Update state to connected
147
+ self.connection_manager.update_connection_state(
148
+ connection_id, ConnectionState.CONNECTED
149
+ )
150
+
151
+ # Mark connection as opened first (will show in UI)
152
+ self.connection_manager.mark_connection_opened(connection_id)
153
+
154
+ # Then update collections (UI item now exists to receive them)
155
+ self.connection_manager.update_collections(connection_id, collections)
156
+ else:
157
+ # Update state to error
158
+ self.connection_manager.update_connection_state(
159
+ connection_id, ConnectionState.ERROR, error
160
+ )
161
+
162
+ QMessageBox.warning(
163
+ self.parent_widget, "Connection Failed", f"Failed to connect: {error}"
164
+ )
165
+
166
+ # Remove the failed connection
167
+ self.connection_manager.close_connection(connection_id)
168
+
169
+ # Emit signal for UI updates
170
+ self.connection_completed.emit(connection_id, success, collections, error)
171
+
172
+ def cleanup(self):
173
+ """Clean up connection threads on shutdown."""
174
+ for thread in list(self._connection_threads.values()):
175
+ if thread.isRunning():
176
+ thread.quit()
177
+ thread.wait(1000) # Wait up to 1 second
@@ -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()
@@ -303,17 +253,7 @@ class MainWindow(QMainWindow):
303
253
  # Show update details dialog
304
254
  if not hasattr(self, "_latest_release"):
305
255
  return
306
- from vector_inspector.ui.components.update_details_dialog import UpdateDetailsDialog
307
- from vector_inspector.services.update_service import UpdateService
308
-
309
- latest = self._latest_release
310
- version = latest.get("tag_name", "?")
311
- notes = latest.get("body", "")
312
- instructions = UpdateService.get_update_instructions()
313
- pip_cmd = instructions["pip"]
314
- github_url = instructions["github"]
315
- dlg = UpdateDetailsDialog(version, notes, pip_cmd, github_url, self)
316
- dlg.exec()
256
+ DialogService.show_update_details(self._latest_release, self)
317
257
 
318
258
  def _connect_signals(self):
319
259
  """Connect signals between components."""
@@ -327,6 +267,9 @@ class MainWindow(QMainWindow):
327
267
  self.connection_manager.collections_updated.connect(self._on_collections_updated)
328
268
  self.connection_manager.connection_opened.connect(self._on_connection_opened)
329
269
 
270
+ # Connection controller signals
271
+ self.connection_controller.connection_completed.connect(self._on_connection_completed)
272
+
330
273
  # Connection panel signals
331
274
  self.connection_panel.collection_selected.connect(self._on_collection_selected_from_panel)
332
275
  self.connection_panel.add_connection_btn.clicked.connect(self._new_connection_from_profile)
@@ -334,9 +277,20 @@ class MainWindow(QMainWindow):
334
277
  # Profile panel signals
335
278
  self.profile_panel.connect_profile.connect(self._connect_to_profile)
336
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
+
337
291
  def _on_tab_changed(self, index: int):
338
292
  """Handle tab change - lazy load visualization tab."""
339
- if index == 3 and self.visualization_view is None:
293
+ if index == InspectorTabs.VISUALIZATION_TAB and self.visualization_view is None:
340
294
  # Lazy load visualization view
341
295
  from vector_inspector.ui.views.visualization_view import VisualizationView
342
296
 
@@ -346,9 +300,11 @@ class MainWindow(QMainWindow):
346
300
 
347
301
  self.visualization_view = VisualizationView(conn)
348
302
  # Replace placeholder with actual view
349
- self.tab_widget.removeTab(3)
350
- self.tab_widget.insertTab(3, self.visualization_view, "Visualization")
351
- 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)
352
308
 
353
309
  # Set collection if one is already selected
354
310
  if active and active.active_collection:
@@ -386,20 +342,22 @@ class MainWindow(QMainWindow):
386
342
  if connection_id == self.connection_manager.get_active_connection_id():
387
343
  # Show loading immediately when collection changes
388
344
  if collection_name:
389
- 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
+ )
390
348
  QApplication.processEvents()
391
349
  try:
392
350
  self._update_views_for_collection(collection_name)
393
351
  finally:
394
- self.loading_dialog.hide_loading()
352
+ self.connection_controller.loading_dialog.hide_loading()
395
353
  else:
396
354
  # Clear collection from views
397
- self.loading_dialog.show_loading("Clearing collection...")
355
+ self.connection_controller.loading_dialog.show_loading("Clearing collection...")
398
356
  QApplication.processEvents()
399
357
  try:
400
358
  self._update_views_for_collection(None)
401
359
  finally:
402
- self.loading_dialog.hide_loading()
360
+ self.connection_controller.loading_dialog.hide_loading()
403
361
 
404
362
  def _on_collections_updated(self, connection_id: str, collections: list):
405
363
  """Handle collections list updated."""
@@ -417,7 +375,9 @@ class MainWindow(QMainWindow):
417
375
  def _on_collection_selected_from_panel(self, connection_id: str, collection_name: str):
418
376
  """Handle collection selection from connection panel."""
419
377
  # Show loading dialog while switching collections
420
- 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
+ )
421
381
  QApplication.processEvents()
422
382
 
423
383
  try:
@@ -425,7 +385,7 @@ class MainWindow(QMainWindow):
425
385
  # Just update the views
426
386
  self._update_views_for_collection(collection_name)
427
387
  finally:
428
- self.loading_dialog.hide_loading()
388
+ self.connection_controller.loading_dialog.hide_loading()
429
389
 
430
390
  def _update_views_with_connection(self, connection: VectorDBConnection):
431
391
  """Update all views with a new connection."""
@@ -464,176 +424,20 @@ class MainWindow(QMainWindow):
464
424
 
465
425
  def _new_connection_from_profile(self):
466
426
  """Show dialog to create new connection (switches to Profiles tab)."""
467
- self.left_tabs.setCurrentIndex(1) # Switch to Profiles tab
468
- QMessageBox.information(
469
- self,
470
- "Connect to Profile",
471
- "Select a profile from the list and click 'Connect', or click '+' to create a new profile.",
472
- )
427
+ self.set_left_panel_active(1) # Switch to Profiles tab
428
+ DialogService.show_profile_editor_prompt(self)
473
429
 
474
430
  def _show_profile_editor(self):
475
431
  """Show profile editor to create new profile."""
476
- self.left_tabs.setCurrentIndex(1) # Switch to Profiles tab
432
+ self.set_left_panel_active(1) # Switch to Profiles tab
477
433
  self.profile_panel._create_profile()
478
434
 
479
435
  def _connect_to_profile(self, profile_id: str):
480
- """Connect to a profile."""
481
- profile_data = self.profile_service.get_profile_with_credentials(profile_id)
482
- if not profile_data:
483
- QMessageBox.warning(self, "Error", "Profile not found.")
484
- return
485
-
486
- # Check connection limit
487
- if self.connection_manager.get_connection_count() >= ConnectionManager.MAX_CONNECTIONS:
488
- QMessageBox.warning(
489
- self,
490
- "Connection Limit",
491
- f"Maximum number of connections ({ConnectionManager.MAX_CONNECTIONS}) reached. "
492
- "Please close a connection first.",
493
- )
494
- return
495
-
496
- # Create connection
497
- provider = profile_data["provider"]
498
- config = profile_data["config"]
499
- credentials = profile_data.get("credentials", {})
500
-
501
- try:
502
- # Create connection object
503
- if provider == "chromadb":
504
- connection = self._create_chroma_connection(config, credentials)
505
- elif provider == "qdrant":
506
- connection = self._create_qdrant_connection(config, credentials)
507
- elif provider == "pinecone":
508
- connection = self._create_pinecone_connection(config, credentials)
509
- elif provider == "pgvector":
510
- connection = self._create_pgvector_connection(config, credentials)
511
- else:
512
- QMessageBox.warning(self, "Error", f"Unsupported provider: {provider}")
513
- return
514
-
515
- # Register with connection manager, using profile_id as connection_id for persistence
516
- connection_id = self.connection_manager.create_connection(
517
- name=profile_data["name"],
518
- provider=provider,
519
- connection=connection,
520
- config=config,
521
- connection_id=profile_data["id"],
522
- )
523
-
524
- # Update state to connecting
525
- self.connection_manager.update_connection_state(
526
- connection_id, ConnectionState.CONNECTING
527
- )
528
-
529
- # Connect in background thread
530
- thread = ConnectionThread(connection)
531
- thread.finished.connect(
532
- lambda success, collections, error: self._on_connection_finished(
533
- connection_id, success, collections, error
534
- )
535
- )
536
- self._connection_threads[connection_id] = thread
537
- thread.start()
538
-
539
- # Show loading dialog
540
- self.loading_dialog.show_loading(f"Connecting to {profile_data['name']}...")
541
-
542
- except Exception as e:
543
- QMessageBox.critical(self, "Connection Error", f"Failed to create connection: {e}")
544
-
545
- def _create_chroma_connection(self, config: dict, credentials: dict) -> ChromaDBConnection:
546
- """Create a ChromaDB connection."""
547
- conn_type = config.get("type")
548
-
549
- if conn_type == "persistent":
550
- return ChromaDBConnection(path=config.get("path"))
551
- elif conn_type == "http":
552
- return ChromaDBConnection(host=config.get("host"), port=config.get("port"))
553
- else: # ephemeral
554
- return ChromaDBConnection()
555
-
556
- def _create_qdrant_connection(self, config: dict, credentials: dict) -> QdrantConnection:
557
- """Create a Qdrant connection."""
558
- conn_type = config.get("type")
559
- api_key = credentials.get("api_key")
560
-
561
- if conn_type == "persistent":
562
- return QdrantConnection(path=config.get("path"))
563
- elif conn_type == "http":
564
- return QdrantConnection(
565
- host=config.get("host"), port=config.get("port"), api_key=api_key
566
- )
567
- else: # ephemeral
568
- return QdrantConnection()
569
-
570
- def _create_pinecone_connection(self, config: dict, credentials: dict) -> PineconeConnection:
571
- """Create a Pinecone connection."""
572
- api_key = credentials.get("api_key")
573
- if not api_key:
574
- raise ValueError("Pinecone requires an API key")
575
-
576
- return PineconeConnection(api_key=api_key)
577
-
578
- def _create_pgvector_connection(self, config: dict, credentials: dict) -> PgVectorConnection:
579
- """Create a PgVector/Postgres connection from profile config/credentials."""
580
- conn_type = config.get("type")
581
-
582
- # We expect HTTP-style profile for pgvector (host/port + db creds)
583
- if conn_type == "http":
584
- host = config.get("host", "localhost")
585
- port = int(config.get("port", 5432))
586
- database = config.get("database")
587
- user = config.get("user")
588
- # Prefer password from credentials
589
- password = credentials.get("password")
590
-
591
- return PgVectorConnection(
592
- host=host, port=port, database=database, user=user, password=password
593
- )
594
-
595
- raise ValueError("Unsupported connection type for PgVector profile")
596
-
597
- def _on_connection_finished(
598
- self, connection_id: str, success: bool, collections: list, error: str
599
- ):
600
- """Handle connection thread completion."""
601
- self.loading_dialog.hide_loading()
602
-
603
- # Clean up thread
604
- thread = self._connection_threads.pop(connection_id, None)
605
- if thread:
606
- thread.wait() # Wait for thread to fully finish
607
- thread.deleteLater()
608
-
436
+ """Connect to a profile using the connection controller."""
437
+ success = self.connection_controller.connect_to_profile(profile_id)
609
438
  if success:
610
- # Update state to connected
611
- self.connection_manager.update_connection_state(
612
- connection_id, ConnectionState.CONNECTED
613
- )
614
-
615
- # Mark connection as opened first (will show in UI)
616
- self.connection_manager.mark_connection_opened(connection_id)
617
-
618
- # Then update collections (UI item now exists to receive them)
619
- self.connection_manager.update_collections(connection_id, collections)
620
-
621
- # Switch to Active connections tab
622
- self.left_tabs.setCurrentIndex(0)
623
-
624
- self.statusBar().showMessage(
625
- f"Connected successfully ({len(collections)} collections)", 5000
626
- )
627
- else:
628
- # Update state to error
629
- self.connection_manager.update_connection_state(
630
- connection_id, ConnectionState.ERROR, error
631
- )
632
-
633
- QMessageBox.warning(self, "Connection Failed", f"Failed to connect: {error}")
634
-
635
- # Remove the failed connection
636
- self.connection_manager.close_connection(connection_id)
439
+ # Switch to Active connections tab after initiating connection
440
+ self.set_left_panel_active(0)
637
441
 
638
442
  def _refresh_active_connection(self):
639
443
  """Refresh collections for the active connection."""
@@ -665,13 +469,7 @@ class MainWindow(QMainWindow):
665
469
 
666
470
  def _show_about(self):
667
471
  """Show about dialog."""
668
- from .main_window import get_about_html
669
-
670
- QMessageBox.about(
671
- self,
672
- "About Vector Inspector",
673
- get_about_html(),
674
- )
472
+ DialogService.show_about(self)
675
473
 
676
474
  def _toggle_cache(self, checked: bool):
677
475
  """Toggle caching on/off."""
@@ -681,53 +479,25 @@ class MainWindow(QMainWindow):
681
479
 
682
480
  def _show_migration_dialog(self):
683
481
  """Show cross-database migration dialog."""
684
- if self.connection_manager.get_connection_count() < 2:
685
- QMessageBox.information(
686
- self,
687
- "Insufficient Connections",
688
- "You need at least 2 active connections to migrate data.\n"
689
- "Please connect to additional databases first.",
690
- )
691
- return
692
-
693
- from vector_inspector.ui.dialogs.cross_db_migration import CrossDatabaseMigrationDialog
694
-
695
- dialog = CrossDatabaseMigrationDialog(self.connection_manager, self)
696
- dialog.exec()
482
+ DialogService.show_migration_dialog(self.connection_manager, self)
697
483
 
698
484
  def _show_backup_restore_dialog(self):
699
485
  """Show backup/restore dialog for the active collection."""
700
- # Check if there's an active connection
486
+ # Get active connection and collection
701
487
  connection = self.connection_manager.get_active_connection()
702
- if not connection:
703
- QMessageBox.information(self, "No Connection", "Please connect to a database first.")
704
- return
705
-
706
- # Get active collection
707
488
  collection_name = self.connection_manager.get_active_collection()
708
- if not collection_name:
709
- # Allow opening dialog without a collection selected (for restore-only)
710
- QMessageBox.information(
711
- self,
712
- "No Collection Selected",
713
- "You can restore backups without a collection selected.\n"
714
- "To create a backup, please select a collection first.",
715
- )
716
489
 
717
- 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)
718
492
 
719
- dialog = BackupRestoreDialog(connection, collection_name or "", self)
720
- if dialog.exec() == QDialog.Accepted:
493
+ if result == QDialog.Accepted:
721
494
  # Refresh collections after restore
722
495
  self._refresh_active_connection()
723
496
 
724
497
  def closeEvent(self, event):
725
498
  """Handle application close."""
726
- # Wait for all connection threads to finish
727
- for thread in list(self._connection_threads.values()):
728
- if thread.isRunning():
729
- thread.quit()
730
- thread.wait(1000) # Wait up to 1 second
499
+ # Clean up connection controller
500
+ self.connection_controller.cleanup()
731
501
 
732
502
  # Clean up temp HTML files from visualization view
733
503
  if self.visualization_view is not None:
@@ -739,20 +509,3 @@ class MainWindow(QMainWindow):
739
509
  self.connection_manager.close_all_connections()
740
510
 
741
511
  event.accept()
742
-
743
-
744
- def get_about_html():
745
- from vector_inspector.utils.version import get_app_version
746
-
747
- version = get_app_version()
748
- version_html = (
749
- f"<h2>Vector Inspector {version}</h2>" if version else "<h2>Vector Inspector</h2>"
750
- )
751
- return (
752
- version_html + "<p>A comprehensive desktop application for visualizing, "
753
- "querying, and managing multiple vector databases simultaneously.</p>"
754
- '<p><a href="https://github.com/anthonypdawson/vector-inspector" style="color:#2980b9;">GitHub Project Page</a></p>'
755
- "<hr />"
756
- "<p>Built with PySide6</p>"
757
- "<p><b>New:</b> Pinecone support!</p>"
758
- )
@@ -0,0 +1,106 @@
1
+ """Reusable UI shell for Vector Inspector applications."""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QMainWindow,
5
+ QWidget,
6
+ QVBoxLayout,
7
+ QHBoxLayout,
8
+ QSplitter,
9
+ QTabWidget,
10
+ )
11
+ from PySide6.QtCore import Qt
12
+
13
+
14
+ class InspectorShell(QMainWindow):
15
+ """Base shell for Inspector applications with splitter, tab widget, and left panel.
16
+
17
+ This provides the basic UI structure that can be reused by Vector Inspector
18
+ and Vector Fusion Studio. Subclasses customize behavior and add domain logic.
19
+ """
20
+
21
+ def __init__(self):
22
+ super().__init__()
23
+
24
+ # Main UI components that subclasses will interact with
25
+ self.left_tabs = None
26
+ self.tab_widget = None
27
+ self.main_splitter = None
28
+
29
+ self._setup_shell_ui()
30
+
31
+ def _setup_shell_ui(self):
32
+ """Setup the main UI shell layout."""
33
+ # Central widget with splitter
34
+ central_widget = QWidget()
35
+ self.setCentralWidget(central_widget)
36
+
37
+ layout = QHBoxLayout(central_widget)
38
+ layout.setContentsMargins(5, 5, 5, 5)
39
+
40
+ # Main splitter (left panel | right tabs)
41
+ self.main_splitter = QSplitter(Qt.Horizontal)
42
+
43
+ # Left panel container (will hold tabs)
44
+ left_panel = QWidget()
45
+ left_layout = QVBoxLayout(left_panel)
46
+ left_layout.setContentsMargins(0, 0, 0, 0)
47
+
48
+ # Create tab widget for left panel
49
+ self.left_tabs = QTabWidget()
50
+ left_layout.addWidget(self.left_tabs)
51
+
52
+ # Right panel - main content tabs
53
+ self.tab_widget = QTabWidget()
54
+
55
+ # Add panels to splitter
56
+ self.main_splitter.addWidget(left_panel)
57
+ self.main_splitter.addWidget(self.tab_widget)
58
+ self.main_splitter.setStretchFactor(0, 1)
59
+ self.main_splitter.setStretchFactor(1, 4)
60
+
61
+ layout.addWidget(self.main_splitter)
62
+
63
+ def add_left_panel(self, widget: QWidget, title: str, index: int = -1):
64
+ """Add a panel to the left tab widget.
65
+
66
+ Args:
67
+ widget: The panel widget to add
68
+ title: Display title for the tab
69
+ index: Optional position (default appends to end)
70
+ """
71
+ if index < 0:
72
+ self.left_tabs.addTab(widget, title)
73
+ else:
74
+ self.left_tabs.insertTab(index, widget, title)
75
+
76
+ def add_main_tab(self, widget: QWidget, title: str, index: int = -1):
77
+ """Add a tab to the main content area.
78
+
79
+ Args:
80
+ widget: The tab widget to add
81
+ title: Display title for the tab
82
+ index: Optional position (default appends to end)
83
+ """
84
+ if index < 0:
85
+ self.tab_widget.addTab(widget, title)
86
+ else:
87
+ self.tab_widget.insertTab(index, widget, title)
88
+
89
+ def set_left_panel_active(self, index: int):
90
+ """Switch to a specific left panel tab."""
91
+ if 0 <= index < self.left_tabs.count():
92
+ self.left_tabs.setCurrentIndex(index)
93
+
94
+ def set_main_tab_active(self, index: int):
95
+ """Switch to a specific main content tab."""
96
+ if 0 <= index < self.tab_widget.count():
97
+ self.tab_widget.setCurrentIndex(index)
98
+
99
+ def get_main_tab_count(self) -> int:
100
+ """Get the number of main content tabs."""
101
+ return self.tab_widget.count()
102
+
103
+ def remove_main_tab(self, index: int):
104
+ """Remove a main content tab."""
105
+ if 0 <= index < self.tab_widget.count():
106
+ self.tab_widget.removeTab(index)
@@ -0,0 +1 @@
1
+ """UI services for dialog management and utilities."""
@@ -0,0 +1,113 @@
1
+ """Service for managing application dialogs."""
2
+
3
+ from PySide6.QtWidgets import QMessageBox, QDialog, QWidget
4
+ from vector_inspector.core.connection_manager import ConnectionManager
5
+
6
+
7
+ class DialogService:
8
+ """Service for launching application dialogs."""
9
+
10
+ @staticmethod
11
+ def show_about(parent: QWidget = None):
12
+ """Show about dialog."""
13
+ from vector_inspector.utils.version import get_app_version
14
+
15
+ version = get_app_version()
16
+ version_html = (
17
+ f"<h2>Vector Inspector {version}</h2>" if version else "<h2>Vector Inspector</h2>"
18
+ )
19
+ about_text = (
20
+ version_html + "<p>A comprehensive desktop application for visualizing, "
21
+ "querying, and managing multiple vector databases simultaneously.</p>"
22
+ '<p><a href="https://github.com/anthonypdawson/vector-inspector" style="color:#2980b9;">GitHub Project Page</a></p>'
23
+ "<hr />"
24
+ "<p>Built with PySide6</p>"
25
+ "<p><b>New:</b> Pinecone support!</p>"
26
+ )
27
+ QMessageBox.about(parent, "About Vector Inspector", about_text)
28
+
29
+ @staticmethod
30
+ def show_backup_restore_dialog(
31
+ connection, collection_name: str = "", parent: QWidget = None
32
+ ) -> int:
33
+ """Show backup/restore dialog.
34
+
35
+ Args:
36
+ connection: Active connection instance
37
+ collection_name: Optional collection name
38
+ parent: Parent widget
39
+
40
+ Returns:
41
+ QDialog.Accepted or QDialog.Rejected
42
+ """
43
+ if not connection:
44
+ QMessageBox.information(parent, "No Connection", "Please connect to a database first.")
45
+ return QDialog.Rejected
46
+
47
+ # Show info if no collection selected
48
+ if not collection_name:
49
+ QMessageBox.information(
50
+ parent,
51
+ "No Collection Selected",
52
+ "You can restore backups without a collection selected.\n"
53
+ "To create a backup, please select a collection first.",
54
+ )
55
+
56
+ from vector_inspector.ui.components.backup_restore_dialog import BackupRestoreDialog
57
+
58
+ dialog = BackupRestoreDialog(connection, collection_name or "", parent)
59
+ return dialog.exec()
60
+
61
+ @staticmethod
62
+ def show_migration_dialog(connection_manager: ConnectionManager, parent: QWidget = None) -> int:
63
+ """Show cross-database migration dialog.
64
+
65
+ Args:
66
+ connection_manager: Connection manager instance
67
+ parent: Parent widget
68
+
69
+ Returns:
70
+ QDialog.Accepted or QDialog.Rejected
71
+ """
72
+ if connection_manager.get_connection_count() < 2:
73
+ QMessageBox.information(
74
+ parent,
75
+ "Insufficient Connections",
76
+ "You need at least 2 active connections to migrate data.\n"
77
+ "Please connect to additional databases first.",
78
+ )
79
+ return QDialog.Rejected
80
+
81
+ from vector_inspector.ui.dialogs.cross_db_migration import CrossDatabaseMigrationDialog
82
+
83
+ dialog = CrossDatabaseMigrationDialog(connection_manager, parent)
84
+ return dialog.exec()
85
+
86
+ @staticmethod
87
+ def show_profile_editor_prompt(parent: QWidget = None):
88
+ """Show message prompting user to create a new profile."""
89
+ QMessageBox.information(
90
+ parent,
91
+ "Connect to Profile",
92
+ "Select a profile from the list and click 'Connect', or click '+' to create a new profile.",
93
+ )
94
+
95
+ @staticmethod
96
+ def show_update_details(latest_release: dict, parent: QWidget = None):
97
+ """Show update details dialog.
98
+
99
+ Args:
100
+ latest_release: Latest release info from GitHub API
101
+ parent: Parent widget
102
+ """
103
+ from vector_inspector.ui.components.update_details_dialog import UpdateDetailsDialog
104
+ from vector_inspector.services.update_service import UpdateService
105
+
106
+ version = latest_release.get("tag_name", "?")
107
+ notes = latest_release.get("body", "")
108
+ instructions = UpdateService.get_update_instructions()
109
+ pip_cmd = instructions["pip"]
110
+ github_url = instructions["github"]
111
+
112
+ dialog = UpdateDetailsDialog(version, notes, pip_cmd, github_url, parent)
113
+ dialog.exec()
@@ -0,0 +1,64 @@
1
+ """Tab registry for Inspector applications."""
2
+
3
+ from typing import List, Tuple, Type
4
+ from PySide6.QtWidgets import QWidget
5
+
6
+
7
+ class TabDefinition:
8
+ """Definition for a tab in the main content area."""
9
+
10
+ def __init__(self, title: str, widget_class: Type[QWidget], lazy_load: bool = False):
11
+ self.title = title
12
+ self.widget_class = widget_class
13
+ self.lazy_load = lazy_load
14
+
15
+
16
+ class InspectorTabs:
17
+ """Registry of standard Inspector tabs.
18
+
19
+ This allows both Vector Inspector and Vector Fusion Studio to use
20
+ the same tab definitions and add their own custom tabs.
21
+ """
22
+
23
+ # Tab indices (for programmatic access)
24
+ INFO_TAB = 0
25
+ DATA_TAB = 1
26
+ SEARCH_TAB = 2
27
+ VISUALIZATION_TAB = 3
28
+
29
+ @staticmethod
30
+ def get_standard_tabs() -> List[TabDefinition]:
31
+ """Get list of standard Inspector tabs.
32
+
33
+ Returns:
34
+ List of TabDefinition objects
35
+ """
36
+ from vector_inspector.ui.views.info_panel import InfoPanel
37
+ from vector_inspector.ui.views.metadata_view import MetadataView
38
+ from vector_inspector.ui.views.search_view import SearchView
39
+ from vector_inspector.ui.views.visualization_view import VisualizationView
40
+
41
+ return [
42
+ TabDefinition("Info", InfoPanel, lazy_load=False),
43
+ TabDefinition("Data Browser", MetadataView, lazy_load=False),
44
+ TabDefinition("Search", SearchView, lazy_load=False),
45
+ TabDefinition("Visualization", VisualizationView, lazy_load=True),
46
+ ]
47
+
48
+ @staticmethod
49
+ def create_tab_widget(tab_def: TabDefinition, connection=None) -> QWidget:
50
+ """Create a widget instance from a tab definition.
51
+
52
+ Args:
53
+ tab_def: Tab definition
54
+ connection: Optional connection to pass to widget
55
+
56
+ Returns:
57
+ Widget instance
58
+ """
59
+ if tab_def.lazy_load:
60
+ # Return placeholder for lazy-loaded tabs
61
+ return QWidget()
62
+ else:
63
+ # Create widget with connection
64
+ return tab_def.widget_class(connection)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vector-inspector
3
- Version: 0.3.7
3
+ Version: 0.3.8
4
4
  Summary: A comprehensive desktop application for visualizing, querying, and managing vector database data
5
5
  Author-Email: Anthony Dawson <anthonypdawson+github@gmail.com>
6
6
  License: MIT
@@ -31,13 +31,31 @@ Requires-Dist: pgvector>=0.4.2
31
31
  Description-Content-Type: text/markdown
32
32
 
33
33
  # Latest updates
34
- * Added automatic update notification system:
35
- - Checks for new releases on GitHub once per launch/day
36
- - Shows update indicator in the status bar and Help menu
37
- - Clickable indicator opens a modal with release notes and update instructions
38
- - Manual Check for Update in Help menu
39
- - Version detection now uses installed package metadata for accuracy
40
- * Fixed bug where data browser row disappeared post-edit
34
+
35
+ ## Vector Inspector 2026.01 Release Notes
36
+
37
+ ### Major Refactor and Studio-Ready Architecture
38
+ - Refactored main window into modular components:
39
+ - InspectorShell: reusable UI shell (splitter, tabs, layout)
40
+ - ProviderFactory: centralized connection creation
41
+ - DialogService: dialog management
42
+ - ConnectionController: connection lifecycle and threading
43
+ - InspectorTabs: pluggable tab registry
44
+ - MainWindow now inherits from InspectorShell and is fully reusable as a widget
45
+ - Bootstrap logic is separated from UI logic—Studio can host Inspector as a component
46
+ - Tab system is now pluggable: Studio and Inspector can add, remove, or override tabs via TabDefinition
47
+ - All Inspector UI logic is self-contained; Studio can extend without modifying Inspector code
48
+
49
+ ### Data Browser Improvements
50
+ - Added a checkbox: Generate embeddings on edit (default: checked)
51
+ - When unchecked, editing a row skips embedding regeneration
52
+ - Setting is persisted per user
53
+
54
+ ### Developer and Architecture Notes
55
+ - All modules pass syntax checks and are ready for Studio integration
56
+ - No breaking changes for existing Inspector users
57
+ - Inspector is now a true UI module, not just an application
58
+
41
59
  ---
42
60
 
43
61
  # Vector Inspector
@@ -45,7 +63,7 @@ Description-Content-Type: text/markdown
45
63
  > **Disclaimer:** This tool is currently under active development and is **not production ready**. Not all features have been thoroughly tested and code is released frequently. Use with caution in critical or production environments.
46
64
 
47
65
  [![CI](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/ci-tests.yml)
48
- [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish.yml)
66
+ [![Publish](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml/badge.svg?branch=master)](https://github.com/anthonypdawson/vector-inspector/actions/workflows/publish%20copy.yml)
49
67
 
50
68
  [![PyPI Version](https://img.shields.io/pypi/v/vector-inspector.svg?cacheSeconds=300)](https://pypi.org/project/vector-inspector/)
51
69
  [![PyPI Downloads](https://static.pepy.tech/personalized-badge/vector-inspector?period=total&units=INTERNATIONAL_SYSTEM&left_color=BLACK&right_color=GREEN&left_text=downloads)](https://pepy.tech/projects/vector-inspector)
@@ -1,6 +1,6 @@
1
- vector_inspector-0.3.7.dist-info/METADATA,sha256=X24i5_fg7PgJqWp5pUSR3EB5S2j7_UknoOKjPdYJaBs,10786
2
- vector_inspector-0.3.7.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
- vector_inspector-0.3.7.dist-info/entry_points.txt,sha256=u96envMI2NFImZUJDFutiiWl7ZoHrrev9joAgtyvTxo,80
1
+ vector_inspector-0.3.8.dist-info/METADATA,sha256=efuN0ywBf5QkEwGbRDiOV9YHG9pLNdBWBqnHPDsqUg0,11574
2
+ vector_inspector-0.3.8.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
3
+ vector_inspector-0.3.8.dist-info/entry_points.txt,sha256=u96envMI2NFImZUJDFutiiWl7ZoHrrev9joAgtyvTxo,80
4
4
  vector_inspector/__init__.py,sha256=Q8XbXn98o0eliQWPePhy-aGUz2KNnVg7bQq-sBPl7zQ,119
5
5
  vector_inspector/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
6
6
  vector_inspector/config/__init__.py,sha256=vHkVsXSUdInsfzWSOLPZzaaELa3SGenAgfpY5EYbsYA,95
@@ -26,6 +26,7 @@ vector_inspector/core/embedding_providers/sentence_transformer_provider.py,sha25
26
26
  vector_inspector/core/embedding_utils.py,sha256=UCnJllDS_YPqbOPVo_kxSCUxM64C5tmcH-fuU9IUifQ,5558
27
27
  vector_inspector/core/logging.py,sha256=HQ6_OZgZmaS3OMFOTAqc0oRbZujqo1W0w8OU4viXP1g,845
28
28
  vector_inspector/core/model_registry.py,sha256=fdofceD3iyNpECVC7djTEAaDYgHX_7JQ3ROh5A0plpY,6269
29
+ vector_inspector/core/provider_factory.py,sha256=QFDpJTOBJVYAdOLlY0GxSWl87Yj_UT9ZzOm9cjVsGMU,3924
29
30
  vector_inspector/main.py,sha256=iZeMPH94q6Ma92hWQLJ7on3rwxytUoS0V8n9MkAuEaY,595
30
31
  vector_inspector/services/__init__.py,sha256=QLgH7oybjHuEYDFNiBgmJxvSpgAzHEuBEPXa3SKJb_I,67
31
32
  vector_inspector/services/backup_helpers.py,sha256=aX1ONFegERq6dpoNM1eJrbyE1gWCV3SuUHMyPpnxrYM,2005
@@ -45,13 +46,19 @@ vector_inspector/ui/components/filter_builder.py,sha256=NSR_hp-rzUZVAca6dIJhTxZA
45
46
  vector_inspector/ui/components/item_dialog.py,sha256=VMwehEjQ6xrdxWygR9J-hHsLfzOVb_E3ePUGYO_c7XA,3951
46
47
  vector_inspector/ui/components/loading_dialog.py,sha256=YEKYGU-R-Zz4CjXSArJtkNxgTy4O9hI5Bbt6qlIzD8U,1018
47
48
  vector_inspector/ui/components/profile_manager_panel.py,sha256=U-Ea6KC97ltj7bYtG4h9Okb97SbfBAvH1SusbYHTn1o,27930
48
- vector_inspector/ui/components/splash_window.py,sha256=lnCdva1fys0BQ1k_rEIQuWjQYXdhGZOf8zRweG2VdyM,1904
49
+ vector_inspector/ui/components/splash_window.py,sha256=AYuRRdLfnuS3ueMSCLfpGG4Q348g_uDIGRyhK4ovzzU,2507
49
50
  vector_inspector/ui/components/update_details_dialog.py,sha256=IQl4QZ30tHHOFay68qGOajQ6JuOB85plNoEazzMYS6k,1587
51
+ vector_inspector/ui/controllers/__init__.py,sha256=Wrc4GsLBbIU3FFk9py9URcblNCp-auGAAT1XPi20nWY,53
52
+ vector_inspector/ui/controllers/connection_controller.py,sha256=cx5Vgdn7cleMeJdUewDHd4NXSMmPUOFDwxQQVArfXuE,6494
50
53
  vector_inspector/ui/dialogs/__init__.py,sha256=xtT77L91PFfm3zHYRENHkWHJaKPm1htuUzRXAF53P8w,211
51
54
  vector_inspector/ui/dialogs/cross_db_migration.py,sha256=BaUyic8l5Ywwql2hQyxVrCXHMjGtqerNAQHDYxcbQ54,15872
52
55
  vector_inspector/ui/dialogs/embedding_config_dialog.py,sha256=1K5LBSBXp590BvKwtHx9qgPwGREsn1mJ8cjFGSZHnMA,12926
53
56
  vector_inspector/ui/dialogs/provider_type_dialog.py,sha256=W_FAJuvicwBUJJ7PyvKow9lc8_a5pnE3RIAsh-DVndQ,6809
54
- vector_inspector/ui/main_window.py,sha256=ma7QPSXom3mw9-nwJcOvuQxj_PoVcmM-4daSJEKKZVs,31055
57
+ vector_inspector/ui/main_window.py,sha256=mZfUm29Ur-dfR4pbHXRSaDsQmIHFY1_SfOahHE_o1DI,21493
58
+ vector_inspector/ui/main_window_shell.py,sha256=0o4KxRc4KXu-mJxni9dv74a5DzP4OIvJoLTX7BLqDoo,3425
59
+ vector_inspector/ui/services/__init__.py,sha256=m2DGkhYlcQQGMtNQsup5eKmhCFhOhXHi-g9Hw0GH1vE,55
60
+ vector_inspector/ui/services/dialog_service.py,sha256=1NHWSMvNadcmoh8tgUMSa8N7g8xYDOTaWMr1G8i9e8A,4261
61
+ vector_inspector/ui/tabs.py,sha256=nniOLax93udxFt1t3s-kx1BpguXBEiDUmC1HA9J-scw,2071
55
62
  vector_inspector/ui/views/__init__.py,sha256=FeMtVzSbVFBMjdwLQSQqD0FRW4ieJ4ZKXtTBci2e_bw,30
56
63
  vector_inspector/ui/views/collection_browser.py,sha256=oG9_YGPoVuMs-f_zSd4EcITmEU9caxvwuubsFUrNf-c,3991
57
64
  vector_inspector/ui/views/connection_view.py,sha256=3oGbClqwpVuUD3AIT8TuM-8heDvwMYw7RowHT3b1b8o,23749
@@ -62,4 +69,4 @@ vector_inspector/ui/views/visualization_view.py,sha256=wgkSkOM-ShOHDj1GCUtKnqH87
62
69
  vector_inspector/utils/__init__.py,sha256=jhHBQC8C8bfhNlf6CAt07ejjStp_YAyleaYr2dm0Dk0,38
63
70
  vector_inspector/utils/lazy_imports.py,sha256=2XZ3ZnwTvZ5vvrh36nJ_TUjwwkgjoAED6i6P9yctvt0,1211
64
71
  vector_inspector/utils/version.py,sha256=2Xk9DEKlDRGEszNNiYnK7ps1i3OH56H2uZhR0_yZORs,382
65
- vector_inspector-0.3.7.dist-info/RECORD,,
72
+ vector_inspector-0.3.8.dist-info/RECORD,,