vector-inspector 0.3.1__py3-none-any.whl → 0.3.3__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/core/connection_manager.py +55 -49
  2. vector_inspector/core/connections/base_connection.py +41 -41
  3. vector_inspector/core/connections/chroma_connection.py +110 -86
  4. vector_inspector/core/connections/pinecone_connection.py +168 -182
  5. vector_inspector/core/connections/qdrant_connection.py +109 -126
  6. vector_inspector/core/connections/qdrant_helpers/__init__.py +4 -0
  7. vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +35 -0
  8. vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +51 -0
  9. vector_inspector/core/connections/template_connection.py +55 -65
  10. vector_inspector/core/embedding_utils.py +32 -32
  11. vector_inspector/core/logging.py +27 -0
  12. vector_inspector/core/model_registry.py +4 -3
  13. vector_inspector/main.py +6 -2
  14. vector_inspector/services/backup_helpers.py +63 -0
  15. vector_inspector/services/backup_restore_service.py +73 -152
  16. vector_inspector/services/credential_service.py +33 -40
  17. vector_inspector/services/import_export_service.py +70 -67
  18. vector_inspector/services/profile_service.py +92 -94
  19. vector_inspector/services/settings_service.py +68 -48
  20. vector_inspector/services/visualization_service.py +40 -39
  21. vector_inspector/ui/components/splash_window.py +57 -0
  22. vector_inspector/ui/dialogs/cross_db_migration.py +6 -5
  23. vector_inspector/ui/main_window.py +200 -146
  24. vector_inspector/ui/views/info_panel.py +208 -127
  25. vector_inspector/ui/views/metadata_view.py +8 -7
  26. vector_inspector/ui/views/search_view.py +97 -75
  27. vector_inspector/ui/views/visualization_view.py +140 -97
  28. vector_inspector/utils/version.py +5 -0
  29. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/METADATA +9 -2
  30. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/RECORD +32 -25
  31. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/WHEEL +0 -0
  32. {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/entry_points.txt +0 -0
@@ -18,6 +18,7 @@ from vector_inspector.services.filter_service import apply_client_side_filters
18
18
  from vector_inspector.services.settings_service import SettingsService
19
19
  from vector_inspector.core.cache_manager import get_cache_manager, CacheEntry
20
20
  from PySide6.QtWidgets import QApplication
21
+ from vector_inspector.core.logging import log_info
21
22
 
22
23
 
23
24
  class DataLoadThread(QThread):
@@ -184,13 +185,13 @@ class MetadataView(QWidget):
184
185
  self.current_database = database_name
185
186
 
186
187
  # Debug: Check cache status
187
- print(f"[MetadataView] Setting collection: db='{self.current_database}', coll='{collection_name}'")
188
- print(f"[MetadataView] Cache enabled: {self.cache_manager.is_enabled()}")
188
+ log_info("[MetadataView] Setting collection: db='%s', coll='%s'", self.current_database, collection_name)
189
+ log_info("[MetadataView] Cache enabled: %s", self.cache_manager.is_enabled())
189
190
 
190
191
  # Check cache first
191
192
  cached = self.cache_manager.get(self.current_database, self.current_collection)
192
193
  if cached and cached.data:
193
- print(f"[MetadataView] ✓ Cache HIT! Loading from cache.")
194
+ log_info("[MetadataView] ✓ Cache HIT! Loading from cache.")
194
195
  # Restore from cache
195
196
  self.current_page = 0
196
197
  self.current_data = cached.data
@@ -208,7 +209,7 @@ class MetadataView(QWidget):
208
209
  self.status_label.setText(f"✓ Loaded from cache - {len(cached.data.get('ids', []))} items")
209
210
  return
210
211
 
211
- print(f"[MetadataView] ✗ Cache MISS. Loading from database...")
212
+ log_info("[MetadataView] ✗ Cache MISS. Loading from database...")
212
213
  # Not in cache, load from database
213
214
  self.current_page = 0
214
215
 
@@ -284,16 +285,16 @@ class MetadataView(QWidget):
284
285
 
285
286
  # Save to cache
286
287
  if self.current_database and self.current_collection:
287
- print(f"[MetadataView] Saving to cache: db='{self.current_database}', coll='{self.current_collection}'")
288
+ log_info("[MetadataView] Saving to cache: db='%s', coll='%s'", self.current_database, self.current_collection)
288
289
  cache_entry = CacheEntry(
289
290
  data=data,
290
291
  scroll_position=self.table.verticalScrollBar().value(),
291
292
  search_query=self.filter_builder.to_dict() if hasattr(self.filter_builder, 'to_dict') else ""
292
293
  )
293
294
  self.cache_manager.set(self.current_database, self.current_collection, cache_entry)
294
- print(f"[MetadataView] ✓ Saved to cache. Total entries: {len(self.cache_manager._cache)}")
295
+ log_info("[MetadataView] ✓ Saved to cache. Total entries: %d", len(self.cache_manager._cache))
295
296
  else:
296
- print(f"[MetadataView] ✗ NOT saving to cache - db='{self.current_database}', coll='{self.current_collection}'")
297
+ log_info("[MetadataView] ✗ NOT saving to cache - db='%s', coll='%s'", self.current_database, self.current_collection)
297
298
 
298
299
  def _on_load_error(self, error_msg: str):
299
300
  """Handle error from background thread."""
@@ -2,9 +2,19 @@
2
2
 
3
3
  from typing import Optional, Dict, Any
4
4
  from PySide6.QtWidgets import (
5
- QWidget, QVBoxLayout, QHBoxLayout, QTextEdit,
6
- QPushButton, QLabel, QSpinBox, QTableWidget,
7
- QTableWidgetItem, QGroupBox, QSplitter, QCheckBox, QApplication
5
+ QWidget,
6
+ QVBoxLayout,
7
+ QHBoxLayout,
8
+ QTextEdit,
9
+ QPushButton,
10
+ QLabel,
11
+ QSpinBox,
12
+ QTableWidget,
13
+ QTableWidgetItem,
14
+ QGroupBox,
15
+ QSplitter,
16
+ QCheckBox,
17
+ QApplication,
8
18
  )
9
19
  from PySide6.QtCore import Qt
10
20
 
@@ -13,11 +23,12 @@ from vector_inspector.ui.components.filter_builder import FilterBuilder
13
23
  from vector_inspector.ui.components.loading_dialog import LoadingDialog
14
24
  from vector_inspector.services.filter_service import apply_client_side_filters
15
25
  from vector_inspector.core.cache_manager import get_cache_manager, CacheEntry
26
+ from vector_inspector.core.logging import log_info
16
27
 
17
28
 
18
29
  class SearchView(QWidget):
19
30
  """View for performing similarity searches."""
20
-
31
+
21
32
  def __init__(self, connection: VectorDBConnection, parent=None):
22
33
  super().__init__(parent)
23
34
  self.connection = connection
@@ -26,108 +37,112 @@ class SearchView(QWidget):
26
37
  self.search_results: Optional[Dict[str, Any]] = None
27
38
  self.loading_dialog = LoadingDialog("Searching...", self)
28
39
  self.cache_manager = get_cache_manager()
29
-
40
+
30
41
  self._setup_ui()
31
-
42
+
32
43
  def _setup_ui(self):
33
44
  """Setup widget UI."""
34
45
  layout = QVBoxLayout(self)
35
-
46
+
36
47
  # Create splitter for query and results
37
48
  splitter = QSplitter(Qt.Vertical)
38
-
49
+
39
50
  # Query section
40
51
  query_widget = QWidget()
41
52
  query_layout = QVBoxLayout(query_widget)
42
-
53
+
43
54
  query_group = QGroupBox("Search Query")
44
55
  query_group_layout = QVBoxLayout()
45
-
56
+
46
57
  # Query input
47
58
  query_group_layout.addWidget(QLabel("Enter search text:"))
48
59
  self.query_input = QTextEdit()
49
60
  self.query_input.setMaximumHeight(100)
50
61
  self.query_input.setPlaceholderText("Enter text to search for similar vectors...")
51
62
  query_group_layout.addWidget(self.query_input)
52
-
63
+
53
64
  # Search controls
54
65
  controls_layout = QHBoxLayout()
55
-
66
+
56
67
  controls_layout.addWidget(QLabel("Results:"))
57
68
  self.n_results_spin = QSpinBox()
58
69
  self.n_results_spin.setMinimum(1)
59
70
  self.n_results_spin.setMaximum(100)
60
71
  self.n_results_spin.setValue(10)
61
72
  controls_layout.addWidget(self.n_results_spin)
62
-
73
+
63
74
  controls_layout.addStretch()
64
-
75
+
65
76
  self.search_button = QPushButton("Search")
66
77
  self.search_button.clicked.connect(self._perform_search)
67
78
  self.search_button.setDefault(True)
68
79
  controls_layout.addWidget(self.search_button)
69
-
80
+
70
81
  query_group_layout.addLayout(controls_layout)
71
82
  query_group.setLayout(query_group_layout)
72
83
  query_layout.addWidget(query_group)
73
-
84
+
74
85
  # Advanced filters section
75
86
  filter_group = QGroupBox("Advanced Metadata Filters")
76
87
  filter_group.setCheckable(True)
77
88
  filter_group.setChecked(False)
78
89
  filter_group_layout = QVBoxLayout()
79
-
90
+
80
91
  # Filter builder
81
92
  self.filter_builder = FilterBuilder()
82
93
  filter_group_layout.addWidget(self.filter_builder)
83
-
94
+
84
95
  filter_group.setLayout(filter_group_layout)
85
96
  query_layout.addWidget(filter_group)
86
97
  self.filter_group = filter_group
87
-
98
+
88
99
  splitter.addWidget(query_widget)
89
-
100
+
90
101
  # Results section
91
102
  results_widget = QWidget()
92
103
  results_layout = QVBoxLayout(results_widget)
93
104
  results_layout.setContentsMargins(0, 0, 0, 0)
94
-
105
+
95
106
  results_group = QGroupBox("Search Results")
96
107
  results_group_layout = QVBoxLayout()
97
-
108
+
98
109
  self.results_table = QTableWidget()
99
110
  self.results_table.setSelectionBehavior(QTableWidget.SelectRows)
100
111
  self.results_table.setAlternatingRowColors(True)
101
112
  results_group_layout.addWidget(self.results_table)
102
-
113
+
103
114
  self.results_status = QLabel("No search performed")
104
115
  self.results_status.setStyleSheet("color: gray;")
105
116
  results_group_layout.addWidget(self.results_status)
106
-
117
+
107
118
  results_group.setLayout(results_group_layout)
108
119
  results_layout.addWidget(results_group)
109
-
120
+
110
121
  splitter.addWidget(results_widget)
111
-
122
+
112
123
  # Set splitter proportions
113
124
  splitter.setStretchFactor(0, 1)
114
125
  splitter.setStretchFactor(1, 2)
115
-
126
+
116
127
  layout.addWidget(splitter)
117
-
128
+
118
129
  def set_collection(self, collection_name: str, database_name: str = ""):
119
130
  """Set the current collection to search."""
120
131
  self.current_collection = collection_name
121
132
  # Always update database_name if provided (even if empty string on first call)
122
133
  if database_name: # Only update if non-empty
123
134
  self.current_database = database_name
124
-
125
- print(f"[SearchView] Setting collection: db='{self.current_database}', coll='{collection_name}'")
126
-
135
+
136
+ log_info(
137
+ "[SearchView] Setting collection: db='%s', coll='%s'",
138
+ self.current_database,
139
+ collection_name,
140
+ )
141
+
127
142
  # Check cache first
128
143
  cached = self.cache_manager.get(self.current_database, self.current_collection)
129
144
  if cached:
130
- print(f"[SearchView] ✓ Cache HIT! Restoring search state.")
145
+ log_info("[SearchView] ✓ Cache HIT! Restoring search state.")
131
146
  # Restore search query and results from cache
132
147
  if cached.search_query:
133
148
  self.query_input.setPlainText(cached.search_query)
@@ -135,37 +150,34 @@ class SearchView(QWidget):
135
150
  self.search_results = cached.search_results
136
151
  self._display_results(cached.search_results)
137
152
  return
138
-
139
- print(f"[SearchView] ✗ Cache MISS or no cached search.")
153
+
154
+ log_info("[SearchView] ✗ Cache MISS or no cached search.")
140
155
  # Not in cache, clear form
141
156
  self.search_results = None
142
157
  self.query_input.clear()
143
158
  self.results_table.setRowCount(0)
144
159
  self.results_status.setText(f"Collection: {collection_name}")
145
-
160
+
146
161
  # Reset filters
147
162
  self.filter_builder._clear_all()
148
163
  self.filter_group.setChecked(False)
149
-
164
+
150
165
  # Update filter builder with supported operators
151
166
  operators = self.connection.get_supported_filter_operators()
152
167
  self.filter_builder.set_operators(operators)
153
-
168
+
154
169
  # Load metadata fields immediately (even if tab is not visible)
155
170
  self._load_metadata_fields()
156
-
171
+
157
172
  def _load_metadata_fields(self):
158
173
  """Load metadata field names from collection for filter builder."""
159
174
  if not self.current_collection:
160
175
  return
161
-
176
+
162
177
  try:
163
178
  # Get a small sample to extract field names
164
- sample_data = self.connection.get_all_items(
165
- self.current_collection,
166
- limit=1
167
- )
168
-
179
+ sample_data = self.connection.get_all_items(self.current_collection, limit=1)
180
+
169
181
  if sample_data and sample_data.get("metadatas"):
170
182
  metadatas = sample_data["metadatas"]
171
183
  if metadatas and len(metadatas) > 0 and metadatas[0]:
@@ -173,21 +185,21 @@ class SearchView(QWidget):
173
185
  self.filter_builder.set_available_fields(field_names)
174
186
  except Exception as e:
175
187
  # Silently ignore errors - fields can still be typed manually
176
- print(f"Note: Could not auto-populate filter fields: {e}")
177
-
188
+ log_info("Note: Could not auto-populate filter fields: %s", e)
189
+
178
190
  def _perform_search(self):
179
191
  """Perform similarity search."""
180
192
  if not self.current_collection:
181
193
  self.results_status.setText("No collection selected")
182
194
  return
183
-
195
+
184
196
  query_text = self.query_input.toPlainText().strip()
185
197
  if not query_text:
186
198
  self.results_status.setText("Please enter search text")
187
199
  return
188
-
200
+
189
201
  n_results = self.n_results_spin.value()
190
-
202
+
191
203
  # Get filters split into server-side and client-side
192
204
  server_filter = None
193
205
  client_filters = []
@@ -196,33 +208,37 @@ class SearchView(QWidget):
196
208
  if server_filter or client_filters:
197
209
  filter_summary = self.filter_builder.get_filter_summary()
198
210
  self.results_status.setText(f"Searching with filters: {filter_summary}")
199
-
211
+
200
212
  # Show loading indicator
201
213
  self.loading_dialog.show_loading("Searching for similar vectors...")
202
214
  QApplication.processEvents()
203
-
215
+
204
216
  try:
205
217
  # Always pass query_texts; provider handles embedding if needed
206
218
  results = self.connection.query_collection(
207
219
  self.current_collection,
208
220
  query_texts=[query_text],
209
221
  n_results=n_results,
210
- where=server_filter
222
+ where=server_filter,
211
223
  )
212
224
  finally:
213
225
  self.loading_dialog.hide_loading()
214
-
226
+
215
227
  if not results:
216
228
  self.results_status.setText("Search failed")
217
229
  self.results_table.setRowCount(0)
218
230
  return
219
-
231
+
220
232
  # Check if results have the expected structure
221
- if not results.get("ids") or not isinstance(results["ids"], list) or len(results["ids"]) == 0:
233
+ if (
234
+ not results.get("ids")
235
+ or not isinstance(results["ids"], list)
236
+ or len(results["ids"]) == 0
237
+ ):
222
238
  self.results_status.setText("No results found or query failed")
223
239
  self.results_table.setRowCount(0)
224
240
  return
225
-
241
+
226
242
  # Apply client-side filters if any
227
243
  if client_filters and results:
228
244
  # Restructure results for filtering
@@ -232,20 +248,24 @@ class SearchView(QWidget):
232
248
  "metadatas": results.get("metadatas", [[]])[0],
233
249
  }
234
250
  filtered = apply_client_side_filters(filter_data, client_filters)
235
-
251
+
236
252
  # Restructure back to query results format
237
253
  results = {
238
254
  "ids": [filtered["ids"]],
239
255
  "documents": [filtered["documents"]],
240
256
  "metadatas": [filtered["metadatas"]],
241
- "distances": [[results.get("distances", [[]])[0][i]
242
- for i, orig_id in enumerate(results.get("ids", [[]])[0])
243
- if orig_id in filtered["ids"]]]
257
+ "distances": [
258
+ [
259
+ results.get("distances", [[]])[0][i]
260
+ for i, orig_id in enumerate(results.get("ids", [[]])[0])
261
+ if orig_id in filtered["ids"]
262
+ ]
263
+ ],
244
264
  }
245
-
265
+
246
266
  self.search_results = results
247
267
  self._display_results(results)
248
-
268
+
249
269
  # Save to cache
250
270
  if self.current_database and self.current_collection:
251
271
  self.cache_manager.update(
@@ -254,55 +274,57 @@ class SearchView(QWidget):
254
274
  search_query=query_text,
255
275
  search_results=results,
256
276
  user_inputs={
257
- 'n_results': n_results,
258
- 'filters': self.filter_builder.to_dict() if hasattr(self.filter_builder, 'to_dict') else {}
259
- }
277
+ "n_results": n_results,
278
+ "filters": self.filter_builder.to_dict()
279
+ if hasattr(self.filter_builder, "to_dict")
280
+ else {},
281
+ },
260
282
  )
261
-
283
+
262
284
  def _display_results(self, results: Dict[str, Any]):
263
285
  """Display search results in table."""
264
286
  ids = results.get("ids", [[]])[0]
265
287
  documents = results.get("documents", [[]])[0]
266
288
  metadatas = results.get("metadatas", [[]])[0]
267
289
  distances = results.get("distances", [[]])[0]
268
-
290
+
269
291
  if not ids:
270
292
  self.results_table.setRowCount(0)
271
293
  self.results_status.setText("No results found")
272
294
  return
273
-
295
+
274
296
  # Determine columns
275
297
  columns = ["Rank", "Distance", "ID", "Document"]
276
298
  if metadatas and metadatas[0]:
277
299
  metadata_keys = list(metadatas[0].keys())
278
300
  columns.extend(metadata_keys)
279
-
301
+
280
302
  self.results_table.setColumnCount(len(columns))
281
303
  self.results_table.setHorizontalHeaderLabels(columns)
282
304
  self.results_table.setRowCount(len(ids))
283
-
305
+
284
306
  # Populate rows
285
307
  for row, (id_val, doc, meta, dist) in enumerate(zip(ids, documents, metadatas, distances)):
286
308
  # Rank
287
309
  self.results_table.setItem(row, 0, QTableWidgetItem(str(row + 1)))
288
-
310
+
289
311
  # Distance/similarity score
290
312
  self.results_table.setItem(row, 1, QTableWidgetItem(f"{dist:.4f}"))
291
-
313
+
292
314
  # ID
293
315
  self.results_table.setItem(row, 2, QTableWidgetItem(str(id_val)))
294
-
316
+
295
317
  # Document
296
318
  doc_text = str(doc) if doc else ""
297
319
  if len(doc_text) > 150:
298
320
  doc_text = doc_text[:150] + "..."
299
321
  self.results_table.setItem(row, 3, QTableWidgetItem(doc_text))
300
-
322
+
301
323
  # Metadata columns
302
324
  if meta:
303
325
  for col_idx, key in enumerate(metadata_keys, start=4):
304
326
  value = meta.get(key, "")
305
327
  self.results_table.setItem(row, col_idx, QTableWidgetItem(str(value)))
306
-
328
+
307
329
  self.results_table.resizeColumnsToContents()
308
330
  self.results_status.setText(f"Found {len(ids)} results")