vector-inspector 0.3.7__py3-none-any.whl → 0.3.9__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,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)
@@ -196,6 +196,9 @@ class MetadataView(QWidget):
196
196
  self.table.setAlternatingRowColors(True)
197
197
  self.table.horizontalHeader().setStretchLastSection(True)
198
198
  self.table.doubleClicked.connect(self._on_row_double_clicked)
199
+ # Enable context menu
200
+ self.table.setContextMenuPolicy(Qt.CustomContextMenu)
201
+ self.table.customContextMenuRequested.connect(self._show_context_menu)
199
202
  layout.addWidget(self.table)
200
203
 
201
204
  # Status bar
@@ -962,6 +965,48 @@ class MetadataView(QWidget):
962
965
  else:
963
966
  QMessageBox.warning(self, "Export Failed", "Failed to export data.")
964
967
 
968
+ def _show_context_menu(self, position):
969
+ """Show context menu for table rows."""
970
+ # Get the item at the position
971
+ item = self.table.itemAt(position)
972
+ if not item:
973
+ return
974
+
975
+ row = item.row()
976
+ if row < 0 or row >= self.table.rowCount():
977
+ return
978
+
979
+ # Create context menu
980
+ menu = QMenu(self)
981
+
982
+ # Add standard "Edit" action
983
+ edit_action = menu.addAction("✏️ Edit")
984
+ edit_action.triggered.connect(
985
+ lambda: self._on_row_double_clicked(self.table.model().index(row, 0))
986
+ )
987
+
988
+ # Call extension hooks to add custom menu items
989
+ try:
990
+ from vector_inspector.extensions import table_context_menu_hook
991
+
992
+ table_context_menu_hook.trigger(
993
+ menu=menu,
994
+ table=self.table,
995
+ row=row,
996
+ data={
997
+ "current_data": self.current_data,
998
+ "collection_name": self.current_collection,
999
+ "database_name": self.current_database,
1000
+ "connection": self.connection,
1001
+ "view_type": "metadata",
1002
+ },
1003
+ )
1004
+ except Exception as e:
1005
+ log_info("Extension hook error: %s", e)
1006
+
1007
+ # Show menu
1008
+ menu.exec(self.table.viewport().mapToGlobal(position))
1009
+
965
1010
  def _import_data(self, format_type: str):
966
1011
  """Import data from file into collection."""
967
1012
  if not self.current_collection:
@@ -8,6 +8,7 @@ from PySide6.QtWidgets import (
8
8
  QTextEdit,
9
9
  QPushButton,
10
10
  QLabel,
11
+ QSizePolicy,
11
12
  QSpinBox,
12
13
  QTableWidget,
13
14
  QTableWidgetItem,
@@ -15,8 +16,10 @@ from PySide6.QtWidgets import (
15
16
  QSplitter,
16
17
  QCheckBox,
17
18
  QApplication,
19
+ QMenu,
18
20
  )
19
21
  from PySide6.QtCore import Qt
22
+ from PySide6.QtGui import QFontMetrics
20
23
 
21
24
  from vector_inspector.core.connections.base_connection import VectorDBConnection
22
25
  from vector_inspector.ui.components.filter_builder import FilterBuilder
@@ -31,6 +34,19 @@ class SearchView(QWidget):
31
34
 
32
35
  def __init__(self, connection: VectorDBConnection, parent=None):
33
36
  super().__init__(parent)
37
+ # Initialize all UI attributes to None to avoid AttributeError
38
+ self.breadcrumb_label = None
39
+ self.query_input = None
40
+ self.results_table = None
41
+ self.results_status = None
42
+ self.refresh_button = None
43
+ self.n_results_spin = None
44
+ self.filter_builder = None
45
+ self.filter_group = None
46
+ self.search_button = None
47
+ self.loading_dialog = None
48
+ self.cache_manager = None
49
+
34
50
  self.connection = connection
35
51
  self.current_collection: str = ""
36
52
  self.current_database: str = ""
@@ -42,8 +58,32 @@ class SearchView(QWidget):
42
58
 
43
59
  def _setup_ui(self):
44
60
  """Setup widget UI."""
61
+ # Assign all UI attributes at the top to avoid NoneType errors
62
+ self.breadcrumb_label = QLabel("")
63
+ self.query_input = QTextEdit()
64
+ self.results_table = QTableWidget()
65
+ self.results_status = QLabel("No search performed")
66
+ self.refresh_button = QPushButton("Refresh")
67
+ self.n_results_spin = QSpinBox()
68
+ self.filter_builder = FilterBuilder()
69
+ self.filter_group = QGroupBox("Advanced Metadata Filters")
70
+ self.search_button = QPushButton("Search")
71
+
45
72
  layout = QVBoxLayout(self)
46
73
 
74
+ # Breadcrumb bar (for pro features)
75
+ self.breadcrumb_label.setStyleSheet(
76
+ "color: #2980b9; font-weight: bold; padding: 2px 0 4px 0;"
77
+ )
78
+ # Configure breadcrumb label sizing
79
+ self.breadcrumb_label.setWordWrap(False)
80
+ self.breadcrumb_label.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
81
+ # Store full breadcrumb text for tooltip and eliding
82
+ self._full_breadcrumb = ""
83
+ # Elide mode: 'left' or 'middle'
84
+ self._elide_mode = "left"
85
+ layout.addWidget(self.breadcrumb_label)
86
+
47
87
  # Create splitter for query and results
48
88
  splitter = QSplitter(Qt.Vertical)
49
89
 
@@ -56,7 +96,6 @@ class SearchView(QWidget):
56
96
 
57
97
  # Query input
58
98
  query_group_layout.addWidget(QLabel("Enter search text:"))
59
- self.query_input = QTextEdit()
60
99
  self.query_input.setMaximumHeight(100)
61
100
  self.query_input.setPlaceholderText("Enter text to search for similar vectors...")
62
101
  query_group_layout.addWidget(self.query_input)
@@ -65,7 +104,6 @@ class SearchView(QWidget):
65
104
  controls_layout = QHBoxLayout()
66
105
 
67
106
  controls_layout.addWidget(QLabel("Results:"))
68
- self.n_results_spin = QSpinBox()
69
107
  self.n_results_spin.setMinimum(1)
70
108
  self.n_results_spin.setMaximum(100)
71
109
  self.n_results_spin.setValue(10)
@@ -73,9 +111,13 @@ class SearchView(QWidget):
73
111
 
74
112
  controls_layout.addStretch()
75
113
 
76
- self.search_button = QPushButton("Search")
77
114
  self.search_button.clicked.connect(self._perform_search)
78
115
  self.search_button.setDefault(True)
116
+
117
+ self.refresh_button.setToolTip("Reset search input and results")
118
+ self.refresh_button.clicked.connect(self._refresh_search)
119
+ controls_layout.addWidget(self.refresh_button)
120
+
79
121
  controls_layout.addWidget(self.search_button)
80
122
 
81
123
  query_group_layout.addLayout(controls_layout)
@@ -83,18 +125,15 @@ class SearchView(QWidget):
83
125
  query_layout.addWidget(query_group)
84
126
 
85
127
  # Advanced filters section
86
- filter_group = QGroupBox("Advanced Metadata Filters")
87
- filter_group.setCheckable(True)
88
- filter_group.setChecked(False)
128
+ self.filter_group.setCheckable(True)
129
+ self.filter_group.setChecked(False)
89
130
  filter_group_layout = QVBoxLayout()
90
131
 
91
- # Filter builder
92
- self.filter_builder = FilterBuilder()
132
+ # Filter builder (already created at top)
93
133
  filter_group_layout.addWidget(self.filter_builder)
94
134
 
95
- filter_group.setLayout(filter_group_layout)
96
- query_layout.addWidget(filter_group)
97
- self.filter_group = filter_group
135
+ self.filter_group.setLayout(filter_group_layout)
136
+ query_layout.addWidget(self.filter_group)
98
137
 
99
138
  splitter.addWidget(query_widget)
100
139
 
@@ -106,12 +145,13 @@ class SearchView(QWidget):
106
145
  results_group = QGroupBox("Search Results")
107
146
  results_group_layout = QVBoxLayout()
108
147
 
109
- self.results_table = QTableWidget()
110
148
  self.results_table.setSelectionBehavior(QTableWidget.SelectRows)
111
149
  self.results_table.setAlternatingRowColors(True)
150
+ # Enable context menu
151
+ self.results_table.setContextMenuPolicy(Qt.CustomContextMenu)
152
+ self.results_table.customContextMenuRequested.connect(self._show_context_menu)
112
153
  results_group_layout.addWidget(self.results_table)
113
154
 
114
- self.results_status = QLabel("No search performed")
115
155
  self.results_status.setStyleSheet("color: gray;")
116
156
  results_group_layout.addWidget(self.results_status)
117
157
 
@@ -125,6 +165,57 @@ class SearchView(QWidget):
125
165
  splitter.setStretchFactor(1, 2)
126
166
 
127
167
  layout.addWidget(splitter)
168
+ self.setLayout(layout)
169
+
170
+ def set_breadcrumb(self, text: str):
171
+ """Set the breadcrumb indicator (for pro features)."""
172
+ # Keep the full breadcrumb for tooltip and compute an elided
173
+ # display that fits the current label width (elide from the left).
174
+ self._full_breadcrumb = text or ""
175
+ self.breadcrumb_label.setToolTip(self._full_breadcrumb)
176
+ self._update_breadcrumb_display()
177
+
178
+ def _update_breadcrumb_display(self):
179
+ """Compute and apply an elided breadcrumb display based on label width."""
180
+ if not hasattr(self, "breadcrumb_label") or self.breadcrumb_label is None:
181
+ return
182
+
183
+ fm = QFontMetrics(self.breadcrumb_label.font())
184
+ avail_width = max(10, self.breadcrumb_label.width())
185
+ if not self._full_breadcrumb:
186
+ self.breadcrumb_label.setText("")
187
+ return
188
+
189
+ # Choose elide mode from settings
190
+ elide_flag = Qt.ElideLeft if self._elide_mode == "left" else Qt.ElideMiddle
191
+ elided = fm.elidedText(self._full_breadcrumb, elide_flag, avail_width)
192
+ self.breadcrumb_label.setText(elided)
193
+
194
+ def set_elide_mode(self, mode: str):
195
+ """Set elide mode ('left' or 'middle') and refresh display."""
196
+ if mode not in ("left", "middle"):
197
+ mode = "left"
198
+ self._elide_mode = mode
199
+ self._update_breadcrumb_display()
200
+
201
+ def resizeEvent(self, event):
202
+ """Handle resize to recompute breadcrumb eliding."""
203
+ try:
204
+ super().resizeEvent(event)
205
+ finally:
206
+ self._update_breadcrumb_display()
207
+
208
+ def clear_breadcrumb(self):
209
+ """Clear the breadcrumb indicator."""
210
+ self.breadcrumb_label.setText("")
211
+
212
+ def _refresh_search(self):
213
+ """Reset search input, results, and breadcrumb."""
214
+ self.query_input.clear()
215
+ self.results_table.setRowCount(0)
216
+ self.results_status.setText("No search performed")
217
+ self.clear_breadcrumb()
218
+ self.search_results = None
128
219
 
129
220
  def set_collection(self, collection_name: str, database_name: str = ""):
130
221
  """Set the current collection to search."""
@@ -139,6 +230,11 @@ class SearchView(QWidget):
139
230
  collection_name,
140
231
  )
141
232
 
233
+ # Guard: if results_table is not yet initialized, do nothing
234
+ if self.results_table is None:
235
+ log_info("[SearchView] set_collection called before UI setup; skipping.")
236
+ return
237
+
142
238
  # Check cache first
143
239
  cached = self.cache_manager.get(self.current_database, self.current_collection)
144
240
  if cached:
@@ -281,6 +377,43 @@ class SearchView(QWidget):
281
377
  },
282
378
  )
283
379
 
380
+ def _show_context_menu(self, position):
381
+ """Show context menu for results table rows."""
382
+ # Get the item at the position
383
+ item = self.results_table.itemAt(position)
384
+ if not item:
385
+ return
386
+
387
+ row = item.row()
388
+ if row < 0 or row >= self.results_table.rowCount():
389
+ return
390
+
391
+ # Create context menu
392
+ menu = QMenu(self)
393
+
394
+ # Call extension hooks to add custom menu items
395
+ try:
396
+ from vector_inspector.extensions import table_context_menu_hook
397
+
398
+ table_context_menu_hook.trigger(
399
+ menu=menu,
400
+ table=self.results_table,
401
+ row=row,
402
+ data={
403
+ "current_data": self.search_results,
404
+ "collection_name": self.current_collection,
405
+ "database_name": self.current_database,
406
+ "connection": self.connection,
407
+ "view_type": "search",
408
+ },
409
+ )
410
+ except Exception as e:
411
+ log_info("Extension hook error: %s", e)
412
+
413
+ # Only show menu if it has items
414
+ if not menu.isEmpty():
415
+ menu.exec(self.results_table.viewport().mapToGlobal(position))
416
+
284
417
  def _display_results(self, results: Dict[str, Any]):
285
418
  """Display search results in table."""
286
419
  ids = results.get("ids", [[]])[0]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: vector-inspector
3
- Version: 0.3.7
3
+ Version: 0.3.9
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,40 @@ 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
+ - Major refactor and studio-ready architecture
36
+ - Refactored main window into modular components (InspectorShell, ProviderFactory, DialogService, ConnectionController, InspectorTabs)
37
+ - MainWindow is reusable as a widget; tab system is pluggable so Studio can extend or override tabs
38
+
39
+ - Data browser improvements
40
+ - Added Generate embeddings on edit (persisted per user)
41
+
42
+ - Settings / Preferences
43
+ - SettingsService persists preferences and exposes typed accessors (breadcrumb, search defaults, auto-embed, window geometry)
44
+ - SettingsService emits a setting_changed Qt signal so UI reacts immediately
45
+ - SettingsDialog (modal) added with add_section API and hook integration for extension panels
46
+ - Breadcrumb controls moved out of core so Pro (Vector Studio) injects them via the settings_panel_hook
47
+
48
+ - Extension hook for settings panels
49
+ - *settings_panel_hook* added to *vector_inspector.extensions*; Vector Studio registers breadcrumb controls at startup
50
+
51
+ - Breadcrumb and UI improvements
52
+ - Breadcrumb label now elides long trails (left/middle) and shows full trail on hover
53
+ - SearchView supports runtime elide-mode changes and responds to settings signals
54
+
55
+ - Window geometry persistence
56
+ - Main window saves/restores geometry when window.restore_geometry is enabled
57
+
58
+ - Pro (Vector Studio) features
59
+ - *Search Similar* (Pro): right-click any row in Data Browser or Search Results to run a vector-to-vector similarity search
60
+ - *table_context_menu* handler hardened for many embedding/id formats and includes fallbacks
61
+ - Vector Studio injects breadcrumb controls into Settings dialog via *settings_panel_hook*
62
+
63
+ - Tests and CI
64
+ - Added *tests/test_settings_injection.py* to assert settings panel hook registration
65
+ - Updated context-menu tests to use *log_info* and *assert* for pytest
66
+ - Local test run: 5 tests passed; GUI-heavy suite ~9s due to PySide6 startup
67
+
41
68
  ---
42
69
 
43
70
  # Vector Inspector
@@ -45,7 +72,7 @@ Description-Content-Type: text/markdown
45
72
  > **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
73
 
47
74
  [![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)
75
+ [![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
76
 
50
77
  [![PyPI Version](https://img.shields.io/pypi/v/vector-inspector.svg?cacheSeconds=300)](https://pypi.org/project/vector-inspector/)
51
78
  [![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)
@@ -74,6 +101,8 @@ Vector Inspector bridges the gap between vector databases and user-friendly data
74
101
 
75
102
  ## Key Features
76
103
 
104
+ > **Note:** Some features listed below may be not started or currently in progress.
105
+
77
106
  ### 1. **Multi-Provider Support**
78
107
  - Connect to vector databases:
79
108
  - ChromaDB (persistent local storage)