vector-inspector 0.2.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. vector_inspector/__init__.py +3 -0
  2. vector_inspector/__main__.py +4 -0
  3. vector_inspector/core/__init__.py +1 -0
  4. vector_inspector/core/connections/__init__.py +7 -0
  5. vector_inspector/core/connections/base_connection.py +233 -0
  6. vector_inspector/core/connections/chroma_connection.py +384 -0
  7. vector_inspector/core/connections/qdrant_connection.py +723 -0
  8. vector_inspector/core/connections/template_connection.py +346 -0
  9. vector_inspector/main.py +21 -0
  10. vector_inspector/services/__init__.py +1 -0
  11. vector_inspector/services/backup_restore_service.py +286 -0
  12. vector_inspector/services/filter_service.py +72 -0
  13. vector_inspector/services/import_export_service.py +287 -0
  14. vector_inspector/services/settings_service.py +60 -0
  15. vector_inspector/services/visualization_service.py +116 -0
  16. vector_inspector/ui/__init__.py +1 -0
  17. vector_inspector/ui/components/__init__.py +1 -0
  18. vector_inspector/ui/components/backup_restore_dialog.py +350 -0
  19. vector_inspector/ui/components/filter_builder.py +370 -0
  20. vector_inspector/ui/components/item_dialog.py +118 -0
  21. vector_inspector/ui/components/loading_dialog.py +30 -0
  22. vector_inspector/ui/main_window.py +288 -0
  23. vector_inspector/ui/views/__init__.py +1 -0
  24. vector_inspector/ui/views/collection_browser.py +112 -0
  25. vector_inspector/ui/views/connection_view.py +423 -0
  26. vector_inspector/ui/views/metadata_view.py +555 -0
  27. vector_inspector/ui/views/search_view.py +268 -0
  28. vector_inspector/ui/views/visualization_view.py +245 -0
  29. vector_inspector-0.2.0.dist-info/METADATA +382 -0
  30. vector_inspector-0.2.0.dist-info/RECORD +32 -0
  31. vector_inspector-0.2.0.dist-info/WHEEL +4 -0
  32. vector_inspector-0.2.0.dist-info/entry_points.txt +5 -0
@@ -0,0 +1,288 @@
1
+ """Main application window."""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QMainWindow, QWidget, QVBoxLayout, QHBoxLayout,
5
+ QSplitter, QTabWidget, QStatusBar, QToolBar,
6
+ QMessageBox, QInputDialog, QFileDialog
7
+ )
8
+ from PySide6.QtCore import Qt, Signal
9
+ from PySide6.QtGui import QAction
10
+
11
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
12
+ from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
13
+ from vector_inspector.ui.views.connection_view import ConnectionView
14
+ from vector_inspector.ui.views.collection_browser import CollectionBrowser
15
+ from vector_inspector.ui.views.metadata_view import MetadataView
16
+ from vector_inspector.ui.views.search_view import SearchView
17
+ from vector_inspector.ui.views.visualization_view import VisualizationView
18
+ from vector_inspector.ui.components.backup_restore_dialog import BackupRestoreDialog
19
+
20
+
21
+ class MainWindow(QMainWindow):
22
+ """Main application window with all views and controls."""
23
+
24
+ connection_changed = Signal(bool) # Emits True when connected, False when disconnected
25
+
26
+ def __init__(self):
27
+ super().__init__()
28
+ self.connection: VectorDBConnection = ChromaDBConnection()
29
+ self.current_collection: str = ""
30
+
31
+ self.setWindowTitle("Vector Inspector")
32
+ self.setGeometry(100, 100, 1400, 900)
33
+
34
+ self._setup_ui()
35
+ self._setup_menu_bar()
36
+ self._setup_toolbar()
37
+ self._setup_statusbar()
38
+ self._connect_signals()
39
+
40
+ def _setup_ui(self):
41
+ """Setup the main UI layout."""
42
+ # Central widget with splitter
43
+ central_widget = QWidget()
44
+ self.setCentralWidget(central_widget)
45
+
46
+ layout = QHBoxLayout(central_widget)
47
+ layout.setContentsMargins(5, 5, 5, 5)
48
+
49
+ # Main splitter (left panel | right tabs)
50
+ main_splitter = QSplitter(Qt.Horizontal)
51
+
52
+ # Left panel - Connection and Collections
53
+ left_panel = QWidget()
54
+ left_layout = QVBoxLayout(left_panel)
55
+ left_layout.setContentsMargins(0, 0, 0, 0)
56
+
57
+ self.connection_view = ConnectionView(self.connection)
58
+ self.collection_browser = CollectionBrowser(self.connection)
59
+
60
+ left_layout.addWidget(self.connection_view)
61
+ left_layout.addWidget(self.collection_browser)
62
+
63
+ # Right panel - Tabbed views
64
+ self.tab_widget = QTabWidget()
65
+
66
+ self.metadata_view = MetadataView(self.connection)
67
+ self.search_view = SearchView(self.connection)
68
+ self.visualization_view = VisualizationView(self.connection)
69
+
70
+ self.tab_widget.addTab(self.metadata_view, "Data Browser")
71
+ self.tab_widget.addTab(self.search_view, "Search")
72
+ self.tab_widget.addTab(self.visualization_view, "Visualization")
73
+
74
+ # Add panels to splitter
75
+ main_splitter.addWidget(left_panel)
76
+ main_splitter.addWidget(self.tab_widget)
77
+ main_splitter.setStretchFactor(0, 1)
78
+ main_splitter.setStretchFactor(1, 3)
79
+
80
+ layout.addWidget(main_splitter)
81
+
82
+ def _setup_menu_bar(self):
83
+ """Setup application menu bar."""
84
+ menubar = self.menuBar()
85
+
86
+ # File menu
87
+ file_menu = menubar.addMenu("&File")
88
+
89
+ connect_action = QAction("&Connect to Database...", self)
90
+ connect_action.setShortcut("Ctrl+O")
91
+ connect_action.triggered.connect(self._on_connect)
92
+ file_menu.addAction(connect_action)
93
+
94
+ disconnect_action = QAction("&Disconnect", self)
95
+ disconnect_action.triggered.connect(self._on_disconnect)
96
+ file_menu.addAction(disconnect_action)
97
+
98
+ file_menu.addSeparator()
99
+
100
+ exit_action = QAction("E&xit", self)
101
+ exit_action.setShortcut("Ctrl+Q")
102
+ exit_action.triggered.connect(self.close)
103
+ file_menu.addAction(exit_action)
104
+
105
+ # Collection menu
106
+ collection_menu = menubar.addMenu("&Collection")
107
+
108
+ new_collection_action = QAction("&New Collection...", self)
109
+ new_collection_action.setShortcut("Ctrl+N")
110
+ new_collection_action.triggered.connect(self._on_new_collection)
111
+ collection_menu.addAction(new_collection_action)
112
+
113
+ refresh_action = QAction("&Refresh Collections", self)
114
+ refresh_action.setShortcut("F5")
115
+ refresh_action.triggered.connect(self._on_refresh_collections)
116
+ collection_menu.addAction(refresh_action)
117
+
118
+ collection_menu.addSeparator()
119
+
120
+ backup_action = QAction("&Backup/Restore...", self)
121
+ backup_action.setShortcut("Ctrl+B")
122
+ backup_action.triggered.connect(self._on_backup_restore)
123
+ collection_menu.addAction(backup_action)
124
+
125
+ # Help menu
126
+ help_menu = menubar.addMenu("&Help")
127
+
128
+ about_action = QAction("&About", self)
129
+ about_action.triggered.connect(self._show_about)
130
+ help_menu.addAction(about_action)
131
+
132
+ def _setup_toolbar(self):
133
+ """Setup application toolbar."""
134
+ toolbar = QToolBar("Main Toolbar")
135
+ toolbar.setMovable(False)
136
+ self.addToolBar(toolbar)
137
+
138
+ connect_action = QAction("Connect", self)
139
+ connect_action.triggered.connect(self._on_connect)
140
+ toolbar.addAction(connect_action)
141
+
142
+ disconnect_action = QAction("Disconnect", self)
143
+ disconnect_action.triggered.connect(self._on_disconnect)
144
+ toolbar.addAction(disconnect_action)
145
+ toolbar.addSeparator()
146
+
147
+ backup_action = QAction("Backup/Restore", self)
148
+ backup_action.triggered.connect(self._on_backup_restore)
149
+ toolbar.addAction(backup_action)
150
+
151
+
152
+ toolbar.addSeparator()
153
+
154
+ refresh_action = QAction("Refresh", self)
155
+ refresh_action.triggered.connect(self._on_refresh_collections)
156
+ toolbar.addAction(refresh_action)
157
+
158
+ def _setup_statusbar(self):
159
+ """Setup status bar."""
160
+ self.statusBar = QStatusBar()
161
+ self.setStatusBar(self.statusBar)
162
+ self.statusBar.showMessage("Not connected")
163
+
164
+ def _connect_signals(self):
165
+ """Connect signals between components."""
166
+ self.connection_view.connection_changed.connect(self._on_connection_status_changed)
167
+ self.connection_view.connection_created.connect(self._on_connection_created)
168
+ self.collection_browser.collection_selected.connect(self._on_collection_selected)
169
+
170
+ def _on_connection_created(self, new_connection: VectorDBConnection):
171
+ """Handle when a new connection instance is created."""
172
+ self.connection = new_connection
173
+ # Update all views with new connection
174
+ self.collection_browser.connection = new_connection
175
+ self.metadata_view.connection = new_connection
176
+ self.search_view.connection = new_connection
177
+ self.visualization_view.connection = new_connection
178
+
179
+ def _on_connect(self):
180
+ """Handle connect action."""
181
+ self.connection_view.show_connection_dialog()
182
+
183
+ def _on_disconnect(self):
184
+ """Handle disconnect action."""
185
+ if self.connection.is_connected:
186
+ self.connection.disconnect()
187
+ self.statusBar.showMessage("Disconnected")
188
+ self.connection_changed.emit(False)
189
+ self.collection_browser.clear()
190
+
191
+ def _on_connection_status_changed(self, connected: bool):
192
+ """Handle connection status change."""
193
+ if connected:
194
+ self.statusBar.showMessage("Connected")
195
+ self.connection_changed.emit(True)
196
+ self._on_refresh_collections()
197
+ else:
198
+ self.statusBar.showMessage("Connection failed")
199
+ self.connection_changed.emit(False)
200
+
201
+ def _on_collection_selected(self, collection_name: str):
202
+ """Handle collection selection."""
203
+ self.current_collection = collection_name
204
+ self.statusBar.showMessage(f"Collection: {collection_name}")
205
+
206
+ # Update all views with new collection
207
+ self.metadata_view.set_collection(collection_name)
208
+ self.search_view.set_collection(collection_name)
209
+ self.visualization_view.set_collection(collection_name)
210
+
211
+ def _on_refresh_collections(self):
212
+ """Refresh collection list."""
213
+ if self.connection.is_connected:
214
+ self.collection_browser.refresh()
215
+
216
+ def _on_new_collection(self):
217
+ """Create a new collection."""
218
+ if not self.connection.is_connected:
219
+ QMessageBox.warning(self, "Not Connected", "Please connect to a database first.")
220
+ return
221
+
222
+ from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
223
+ from vector_inspector.core.connections.qdrant_connection import QdrantConnection
224
+
225
+ name, ok = QInputDialog.getText(
226
+ self, "New Collection", "Enter collection name:"
227
+ )
228
+
229
+ if ok and name:
230
+ success = False
231
+
232
+ # Handle ChromaDB
233
+ if isinstance(self.connection, ChromaDBConnection):
234
+ collection = self.connection.get_collection(name)
235
+ success = collection is not None
236
+
237
+ # Handle Qdrant
238
+ elif isinstance(self.connection, QdrantConnection):
239
+ # Ask for vector size (required for Qdrant)
240
+ vector_size, ok = QInputDialog.getInt(
241
+ self,
242
+ "Vector Size",
243
+ "Enter vector dimension size:",
244
+ value=384, # Default for sentence transformers
245
+ min=1,
246
+ max=10000
247
+ )
248
+ if ok:
249
+ success = self.connection.create_collection(name, vector_size)
250
+
251
+ if success:
252
+ QMessageBox.information(
253
+ self, "Success", f"Collection '{name}' created successfully."
254
+ )
255
+ self._on_refresh_collections()
256
+ else:
257
+ QMessageBox.warning(
258
+ self, "Error", f"Failed to create collection '{name}'."
259
+ )
260
+
261
+ def _on_backup_restore(self):
262
+ """Open backup/restore dialog."""
263
+ if not self.connection.is_connected:
264
+ QMessageBox.warning(self, "Not Connected", "Please connect to a database first.")
265
+ return
266
+
267
+ dialog = BackupRestoreDialog(
268
+ self.connection,
269
+ self.current_collection,
270
+ self
271
+ )
272
+ dialog.exec()
273
+
274
+ # Refresh collections after dialog closes (in case something was restored)
275
+ self._on_refresh_collections()
276
+
277
+ def _show_about(self):
278
+ """Show about dialog."""
279
+ QMessageBox.about(
280
+ self,
281
+ "About Vector Inspector",
282
+ "<h2>Vector Inspector 0.1.0</h2>"
283
+ "<p>A comprehensive desktop application for visualizing, "
284
+ "querying, and managing vector database data.</p>"
285
+ '<p><a href="https://github.com/anthonypdawson/vector-viewer" style="color:#2980b9;">GitHub Project Page</a></p>'
286
+ "<hr />"
287
+ "<p>Built with PySide6 and ChromaDB</p>"
288
+ )
@@ -0,0 +1 @@
1
+ """Main application views."""
@@ -0,0 +1,112 @@
1
+ """Collection browser for listing and selecting collections."""
2
+
3
+ from PySide6.QtWidgets import (
4
+ QWidget, QVBoxLayout, QListWidget, QListWidgetItem,
5
+ QGroupBox, QLabel, QMenu
6
+ )
7
+ from PySide6.QtCore import Signal, Qt
8
+ from PySide6.QtGui import QAction
9
+
10
+ from vector_inspector.core.connections.base_connection import VectorDBConnection
11
+
12
+
13
+ class CollectionBrowser(QWidget):
14
+ """Widget for browsing and selecting collections."""
15
+
16
+ collection_selected = Signal(str)
17
+
18
+ def __init__(self, connection: VectorDBConnection, parent=None):
19
+ super().__init__(parent)
20
+ self.connection = connection
21
+ self._setup_ui()
22
+
23
+ def _setup_ui(self):
24
+ """Setup widget UI."""
25
+ layout = QVBoxLayout(self)
26
+ layout.setContentsMargins(0, 0, 0, 0)
27
+
28
+ group = QGroupBox("Collections")
29
+ group_layout = QVBoxLayout()
30
+
31
+ self.collection_list = QListWidget()
32
+ self.collection_list.itemClicked.connect(self._on_collection_clicked)
33
+ self.collection_list.setContextMenuPolicy(Qt.CustomContextMenu)
34
+ self.collection_list.customContextMenuRequested.connect(self._show_context_menu)
35
+
36
+ group_layout.addWidget(self.collection_list)
37
+
38
+ self.info_label = QLabel("No collections")
39
+ self.info_label.setWordWrap(True)
40
+ self.info_label.setStyleSheet("color: gray; font-size: 10px;")
41
+ group_layout.addWidget(self.info_label)
42
+
43
+ group.setLayout(group_layout)
44
+ layout.addWidget(group)
45
+
46
+ def refresh(self):
47
+ """Refresh collection list."""
48
+ self.collection_list.clear()
49
+
50
+ if not self.connection.is_connected:
51
+ self.info_label.setText("Not connected")
52
+ return
53
+
54
+ collections = self.connection.list_collections()
55
+
56
+ if not collections:
57
+ # Show more context for persistent connections
58
+ if self.connection.path:
59
+ self.info_label.setText(
60
+ f"No collections found at {self.connection.path}"
61
+ )
62
+ else:
63
+ self.info_label.setText("No collections found")
64
+ return
65
+
66
+ for collection_name in collections:
67
+ item = QListWidgetItem(collection_name)
68
+ self.collection_list.addItem(item)
69
+
70
+ self.info_label.setText(f"{len(collections)} collection(s)")
71
+
72
+ def clear(self):
73
+ """Clear collection list."""
74
+ self.collection_list.clear()
75
+ self.info_label.setText("No collections")
76
+
77
+ def _on_collection_clicked(self, item: QListWidgetItem):
78
+ """Handle collection selection."""
79
+ collection_name = item.text()
80
+ self.collection_selected.emit(collection_name)
81
+
82
+ # Show collection info
83
+ info = self.connection.get_collection_info(collection_name)
84
+ if info:
85
+ count = info.get("count", 0)
86
+ fields = info.get("metadata_fields", [])
87
+ fields_str = ", ".join(fields[:3])
88
+ if len(fields) > 3:
89
+ fields_str += "..."
90
+ self.info_label.setText(
91
+ f"{count} items | Fields: {fields_str if fields else 'None'}"
92
+ )
93
+
94
+ def _show_context_menu(self, position):
95
+ """Show context menu for collections."""
96
+ item = self.collection_list.itemAt(position)
97
+ if not item:
98
+ return
99
+
100
+ menu = QMenu(self)
101
+
102
+ delete_action = QAction("Delete Collection", self)
103
+ delete_action.triggered.connect(lambda: self._delete_collection(item.text()))
104
+ menu.addAction(delete_action)
105
+
106
+ menu.exec(self.collection_list.mapToGlobal(position))
107
+
108
+ def _delete_collection(self, collection_name: str):
109
+ """Delete a collection."""
110
+ # TODO: Add confirmation dialog
111
+ if self.connection.delete_collection(collection_name):
112
+ self.refresh()