vector-inspector 0.2.6__py3-none-any.whl → 0.2.7__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.
- vector_inspector/core/cache_manager.py +159 -0
- vector_inspector/core/connection_manager.py +277 -0
- vector_inspector/core/connections/chroma_connection.py +90 -5
- vector_inspector/core/connections/qdrant_connection.py +62 -8
- vector_inspector/core/embedding_utils.py +140 -0
- vector_inspector/services/backup_restore_service.py +3 -29
- vector_inspector/services/credential_service.py +130 -0
- vector_inspector/services/filter_service.py +1 -1
- vector_inspector/services/profile_service.py +409 -0
- vector_inspector/services/settings_service.py +19 -0
- vector_inspector/ui/components/connection_manager_panel.py +320 -0
- vector_inspector/ui/components/profile_manager_panel.py +518 -0
- vector_inspector/ui/dialogs/__init__.py +5 -0
- vector_inspector/ui/dialogs/cross_db_migration.py +364 -0
- vector_inspector/ui/dialogs/embedding_config_dialog.py +176 -0
- vector_inspector/ui/main_window.py +425 -190
- vector_inspector/ui/views/info_panel.py +225 -55
- vector_inspector/ui/views/metadata_view.py +71 -3
- vector_inspector/ui/views/search_view.py +43 -3
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/METADATA +3 -1
- vector_inspector-0.2.7.dist-info/RECORD +45 -0
- vector_inspector-0.2.6.dist-info/RECORD +0 -35
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/WHEEL +0 -0
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.2.7.dist-info}/entry_points.txt +0 -0
|
@@ -3,13 +3,15 @@
|
|
|
3
3
|
from typing import Optional, Dict, Any
|
|
4
4
|
from PySide6.QtWidgets import (
|
|
5
5
|
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
6
|
-
QGroupBox, QScrollArea, QFrame
|
|
6
|
+
QGroupBox, QScrollArea, QFrame, QPushButton
|
|
7
7
|
)
|
|
8
8
|
from PySide6.QtCore import Qt, QObject
|
|
9
|
+
from PySide6.QtWidgets import QDialog
|
|
9
10
|
|
|
10
11
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
11
12
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
12
13
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
14
|
+
from vector_inspector.core.cache_manager import get_cache_manager
|
|
13
15
|
|
|
14
16
|
|
|
15
17
|
class InfoPanel(QWidget):
|
|
@@ -18,7 +20,10 @@ class InfoPanel(QWidget):
|
|
|
18
20
|
def __init__(self, connection: VectorDBConnection, parent=None):
|
|
19
21
|
super().__init__(parent)
|
|
20
22
|
self.connection = connection
|
|
23
|
+
self.connection_id: str = "" # Will be set when collection is set
|
|
21
24
|
self.current_collection: str = ""
|
|
25
|
+
self.current_database: str = ""
|
|
26
|
+
self.cache_manager = get_cache_manager()
|
|
22
27
|
self._setup_ui()
|
|
23
28
|
|
|
24
29
|
def _setup_ui(self):
|
|
@@ -65,10 +70,31 @@ class InfoPanel(QWidget):
|
|
|
65
70
|
self.distance_metric_label = self._create_info_row("Distance Metric:", "N/A")
|
|
66
71
|
self.total_points_label = self._create_info_row("Total Points:", "0")
|
|
67
72
|
|
|
73
|
+
# Embedding model row with configure button
|
|
74
|
+
embedding_row = QWidget()
|
|
75
|
+
embedding_layout = QHBoxLayout(embedding_row)
|
|
76
|
+
embedding_layout.setContentsMargins(0, 2, 0, 2)
|
|
77
|
+
|
|
78
|
+
embedding_label = QLabel("<b>Embedding Model:</b>")
|
|
79
|
+
embedding_label.setMinimumWidth(150)
|
|
80
|
+
self.embedding_model_label = QLabel("Auto-detect")
|
|
81
|
+
self.embedding_model_label.setStyleSheet("color: gray;")
|
|
82
|
+
self.embedding_model_label.setWordWrap(True)
|
|
83
|
+
|
|
84
|
+
self.configure_embedding_btn = QPushButton("Configure...")
|
|
85
|
+
self.configure_embedding_btn.setMaximumWidth(100)
|
|
86
|
+
self.configure_embedding_btn.clicked.connect(self._configure_embedding_model)
|
|
87
|
+
self.configure_embedding_btn.setEnabled(False)
|
|
88
|
+
|
|
89
|
+
embedding_layout.addWidget(embedding_label)
|
|
90
|
+
embedding_layout.addWidget(self.embedding_model_label, 1)
|
|
91
|
+
embedding_layout.addWidget(self.configure_embedding_btn)
|
|
92
|
+
|
|
68
93
|
collection_layout.addWidget(self.collection_name_label)
|
|
69
94
|
collection_layout.addWidget(self.vector_dim_label)
|
|
70
95
|
collection_layout.addWidget(self.distance_metric_label)
|
|
71
96
|
collection_layout.addWidget(self.total_points_label)
|
|
97
|
+
collection_layout.addWidget(embedding_row)
|
|
72
98
|
|
|
73
99
|
# Payload Schema subsection
|
|
74
100
|
schema_label = QLabel("<b>Payload Schema:</b>")
|
|
@@ -200,7 +226,7 @@ class InfoPanel(QWidget):
|
|
|
200
226
|
return
|
|
201
227
|
|
|
202
228
|
try:
|
|
203
|
-
# Get collection info
|
|
229
|
+
# Get collection info from database
|
|
204
230
|
collection_info = self.connection.get_collection_info(self.current_collection)
|
|
205
231
|
|
|
206
232
|
if not collection_info:
|
|
@@ -212,59 +238,18 @@ class InfoPanel(QWidget):
|
|
|
212
238
|
self.provider_details_label.setText("N/A")
|
|
213
239
|
return
|
|
214
240
|
|
|
215
|
-
#
|
|
216
|
-
self.
|
|
217
|
-
|
|
218
|
-
# Vector dimension
|
|
219
|
-
vector_dim = collection_info.get("vector_dimension", "Unknown")
|
|
220
|
-
self._update_label(self.vector_dim_label, str(vector_dim))
|
|
221
|
-
|
|
222
|
-
# Distance metric
|
|
223
|
-
distance = collection_info.get("distance_metric", "Unknown")
|
|
224
|
-
self._update_label(self.distance_metric_label, distance)
|
|
225
|
-
|
|
226
|
-
# Total points
|
|
227
|
-
count = collection_info.get("count", 0)
|
|
228
|
-
self._update_label(self.total_points_label, f"{count:,}")
|
|
241
|
+
# Display the info
|
|
242
|
+
self._display_collection_info(collection_info)
|
|
229
243
|
|
|
230
|
-
#
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
# Provider-specific details
|
|
241
|
-
details_list = []
|
|
242
|
-
|
|
243
|
-
if isinstance(self.connection, ChromaDBConnection):
|
|
244
|
-
details_list.append("• Provider: ChromaDB")
|
|
245
|
-
details_list.append("• Supports: Documents, Metadata, Embeddings")
|
|
246
|
-
details_list.append("• Default embedding: all-MiniLM-L6-v2")
|
|
247
|
-
|
|
248
|
-
elif isinstance(self.connection, QdrantConnection):
|
|
249
|
-
details_list.append("• Provider: Qdrant")
|
|
250
|
-
details_list.append("• Supports: Points, Payload, Vectors")
|
|
251
|
-
# Get additional Qdrant-specific info if available
|
|
252
|
-
if "config" in collection_info:
|
|
253
|
-
config = collection_info["config"]
|
|
254
|
-
if "hnsw_config" in config:
|
|
255
|
-
hnsw = config["hnsw_config"]
|
|
256
|
-
details_list.append(f"• HNSW M: {hnsw.get('m', 'N/A')}")
|
|
257
|
-
details_list.append(f"• HNSW ef_construct: {hnsw.get('ef_construct', 'N/A')}")
|
|
258
|
-
if "optimizer_config" in config:
|
|
259
|
-
opt = config["optimizer_config"]
|
|
260
|
-
details_list.append(f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}")
|
|
261
|
-
|
|
262
|
-
if details_list:
|
|
263
|
-
self.provider_details_label.setText("\n".join(details_list))
|
|
264
|
-
self.provider_details_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
265
|
-
else:
|
|
266
|
-
self.provider_details_label.setText("No additional details available")
|
|
267
|
-
self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
244
|
+
# Save to cache
|
|
245
|
+
if self.current_database and self.current_collection:
|
|
246
|
+
print(f"[InfoPanel] Saving collection info to cache: db='{self.current_database}', coll='{self.current_collection}'")
|
|
247
|
+
self.cache_manager.update(
|
|
248
|
+
self.current_database,
|
|
249
|
+
self.current_collection,
|
|
250
|
+
user_inputs={'collection_info': collection_info}
|
|
251
|
+
)
|
|
252
|
+
print(f"[InfoPanel] ✓ Saved collection info to cache.")
|
|
268
253
|
|
|
269
254
|
except Exception as e:
|
|
270
255
|
self._update_label(self.collection_name_label, self.current_collection)
|
|
@@ -275,9 +260,89 @@ class InfoPanel(QWidget):
|
|
|
275
260
|
self.schema_label.setStyleSheet("color: red; padding-left: 20px;")
|
|
276
261
|
self.provider_details_label.setText("N/A")
|
|
277
262
|
|
|
278
|
-
def
|
|
263
|
+
def _display_collection_info(self, collection_info: Dict[str, Any]):
|
|
264
|
+
"""Display collection information (from cache or fresh query)."""
|
|
265
|
+
# Update basic info
|
|
266
|
+
self._update_label(self.collection_name_label, self.current_collection)
|
|
267
|
+
|
|
268
|
+
# Vector dimension
|
|
269
|
+
vector_dim = collection_info.get("vector_dimension", "Unknown")
|
|
270
|
+
self._update_label(self.vector_dim_label, str(vector_dim))
|
|
271
|
+
|
|
272
|
+
# Enable configure button if we have a valid dimension
|
|
273
|
+
self.configure_embedding_btn.setEnabled(
|
|
274
|
+
vector_dim != "Unknown" and isinstance(vector_dim, int)
|
|
275
|
+
)
|
|
276
|
+
|
|
277
|
+
# Update embedding model display
|
|
278
|
+
self._update_embedding_model_display(collection_info)
|
|
279
|
+
|
|
280
|
+
# Distance metric
|
|
281
|
+
distance = collection_info.get("distance_metric", "Unknown")
|
|
282
|
+
self._update_label(self.distance_metric_label, distance)
|
|
283
|
+
|
|
284
|
+
# Total points
|
|
285
|
+
count = collection_info.get("count", 0)
|
|
286
|
+
self._update_label(self.total_points_label, f"{count:,}")
|
|
287
|
+
|
|
288
|
+
# Metadata schema
|
|
289
|
+
metadata_fields = collection_info.get("metadata_fields", [])
|
|
290
|
+
if metadata_fields:
|
|
291
|
+
schema_text = "\n".join([f"• {field}" for field in sorted(metadata_fields)])
|
|
292
|
+
self.schema_label.setText(schema_text)
|
|
293
|
+
self.schema_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
294
|
+
else:
|
|
295
|
+
self.schema_label.setText("No metadata fields found")
|
|
296
|
+
self.schema_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
297
|
+
|
|
298
|
+
# Provider-specific details
|
|
299
|
+
details_list = []
|
|
300
|
+
|
|
301
|
+
if isinstance(self.connection, ChromaDBConnection):
|
|
302
|
+
details_list.append("• Provider: ChromaDB")
|
|
303
|
+
details_list.append("• Supports: Documents, Metadata, Embeddings")
|
|
304
|
+
details_list.append("• Default embedding: all-MiniLM-L6-v2")
|
|
305
|
+
|
|
306
|
+
elif isinstance(self.connection, QdrantConnection):
|
|
307
|
+
details_list.append("• Provider: Qdrant")
|
|
308
|
+
details_list.append("• Supports: Points, Payload, Vectors")
|
|
309
|
+
# Get additional Qdrant-specific info if available
|
|
310
|
+
if "config" in collection_info:
|
|
311
|
+
config = collection_info["config"]
|
|
312
|
+
if "hnsw_config" in config:
|
|
313
|
+
hnsw = config["hnsw_config"]
|
|
314
|
+
details_list.append(f"• HNSW M: {hnsw.get('m', 'N/A')}")
|
|
315
|
+
details_list.append(f"• HNSW ef_construct: {hnsw.get('ef_construct', 'N/A')}")
|
|
316
|
+
if "optimizer_config" in config:
|
|
317
|
+
opt = config["optimizer_config"]
|
|
318
|
+
details_list.append(f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}")
|
|
319
|
+
|
|
320
|
+
if details_list:
|
|
321
|
+
self.provider_details_label.setText("\n".join(details_list))
|
|
322
|
+
self.provider_details_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
323
|
+
else:
|
|
324
|
+
self.provider_details_label.setText("No additional details available")
|
|
325
|
+
self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
326
|
+
|
|
327
|
+
def set_collection(self, collection_name: str, database_name: str = ""):
|
|
279
328
|
"""Set the current collection and refresh its information."""
|
|
280
329
|
self.current_collection = collection_name
|
|
330
|
+
# Always update database_name if provided
|
|
331
|
+
if database_name:
|
|
332
|
+
self.current_database = database_name
|
|
333
|
+
self.connection_id = database_name # database_name is the connection ID
|
|
334
|
+
|
|
335
|
+
print(f"[InfoPanel] Setting collection: db='{self.current_database}', coll='{collection_name}'")
|
|
336
|
+
|
|
337
|
+
# Check cache first for collection info
|
|
338
|
+
cached = self.cache_manager.get(self.current_database, self.current_collection)
|
|
339
|
+
if cached and hasattr(cached, 'user_inputs') and cached.user_inputs.get('collection_info'):
|
|
340
|
+
print(f"[InfoPanel] ✓ Cache HIT! Loading collection info from cache.")
|
|
341
|
+
collection_info = cached.user_inputs['collection_info']
|
|
342
|
+
self._display_collection_info(collection_info)
|
|
343
|
+
return
|
|
344
|
+
|
|
345
|
+
print(f"[InfoPanel] ✗ Cache MISS. Loading collection info from database...")
|
|
281
346
|
self.refresh_collection_info()
|
|
282
347
|
|
|
283
348
|
def _update_label(self, row_widget: QWidget, value: str):
|
|
@@ -285,3 +350,108 @@ class InfoPanel(QWidget):
|
|
|
285
350
|
value_label = row_widget.property("value_label")
|
|
286
351
|
if value_label and isinstance(value_label, QLabel):
|
|
287
352
|
value_label.setText(value)
|
|
353
|
+
|
|
354
|
+
def _update_embedding_model_display(self, collection_info: Dict[str, Any]):
|
|
355
|
+
"""Update the embedding model label based on current configuration."""
|
|
356
|
+
from ...services.settings_service import SettingsService
|
|
357
|
+
|
|
358
|
+
# Check if stored in collection metadata
|
|
359
|
+
if 'embedding_model' in collection_info:
|
|
360
|
+
model_name = collection_info['embedding_model']
|
|
361
|
+
model_type = collection_info.get('embedding_model_type', 'unknown')
|
|
362
|
+
self.embedding_model_label.setText(f"{model_name} ({model_type})")
|
|
363
|
+
self.embedding_model_label.setStyleSheet("color: lightgreen;")
|
|
364
|
+
return
|
|
365
|
+
|
|
366
|
+
# Check user settings
|
|
367
|
+
settings = SettingsService()
|
|
368
|
+
collection_models = settings.get('collection_embedding_models', {})
|
|
369
|
+
collection_key = f"{self.connection_id}:{self.current_collection}"
|
|
370
|
+
|
|
371
|
+
if collection_key in collection_models:
|
|
372
|
+
model_info = collection_models[collection_key]
|
|
373
|
+
model_name = model_info['model']
|
|
374
|
+
model_type = model_info.get('type', 'unknown')
|
|
375
|
+
self.embedding_model_label.setText(f"{model_name} ({model_type})")
|
|
376
|
+
self.embedding_model_label.setStyleSheet("color: lightblue;")
|
|
377
|
+
return
|
|
378
|
+
|
|
379
|
+
# No configuration - using auto-detect
|
|
380
|
+
self.embedding_model_label.setText("Auto-detect (dimension-based)")
|
|
381
|
+
self.embedding_model_label.setStyleSheet("color: orange;")
|
|
382
|
+
|
|
383
|
+
def _configure_embedding_model(self):
|
|
384
|
+
"""Open dialog to configure embedding model for current collection."""
|
|
385
|
+
if not self.current_collection:
|
|
386
|
+
return
|
|
387
|
+
|
|
388
|
+
from ..dialogs.embedding_config_dialog import EmbeddingConfigDialog
|
|
389
|
+
from ...services.settings_service import SettingsService
|
|
390
|
+
|
|
391
|
+
# Get current collection info
|
|
392
|
+
collection_info = self.connection.get_collection_info(self.current_collection)
|
|
393
|
+
if not collection_info:
|
|
394
|
+
return
|
|
395
|
+
|
|
396
|
+
vector_dim = collection_info.get("vector_dimension")
|
|
397
|
+
if not vector_dim or vector_dim == "Unknown":
|
|
398
|
+
return
|
|
399
|
+
|
|
400
|
+
# Get current configuration if any
|
|
401
|
+
settings = SettingsService()
|
|
402
|
+
collection_models = settings.get('collection_embedding_models', {})
|
|
403
|
+
collection_key = f"{self.connection_id}:{self.current_collection}"
|
|
404
|
+
|
|
405
|
+
current_model = None
|
|
406
|
+
current_type = None
|
|
407
|
+
|
|
408
|
+
# Check metadata first
|
|
409
|
+
if 'embedding_model' in collection_info:
|
|
410
|
+
current_model = collection_info['embedding_model']
|
|
411
|
+
current_type = collection_info.get('embedding_model_type')
|
|
412
|
+
# Then check settings
|
|
413
|
+
elif collection_key in collection_models:
|
|
414
|
+
model_info = collection_models[collection_key]
|
|
415
|
+
current_model = model_info.get('model')
|
|
416
|
+
current_type = model_info.get('type')
|
|
417
|
+
|
|
418
|
+
# Open dialog
|
|
419
|
+
dialog = EmbeddingConfigDialog(
|
|
420
|
+
self.current_collection,
|
|
421
|
+
vector_dim,
|
|
422
|
+
current_model,
|
|
423
|
+
current_type,
|
|
424
|
+
self
|
|
425
|
+
)
|
|
426
|
+
|
|
427
|
+
result = dialog.exec()
|
|
428
|
+
|
|
429
|
+
if result == QDialog.DialogCode.Accepted:
|
|
430
|
+
# Save the configuration
|
|
431
|
+
selection = dialog.get_selection()
|
|
432
|
+
if selection:
|
|
433
|
+
model_name, model_type = selection
|
|
434
|
+
|
|
435
|
+
if collection_key not in collection_models:
|
|
436
|
+
collection_models[collection_key] = {}
|
|
437
|
+
|
|
438
|
+
collection_models[collection_key]['model'] = model_name
|
|
439
|
+
collection_models[collection_key]['type'] = model_type
|
|
440
|
+
|
|
441
|
+
settings.set('collection_embedding_models', collection_models)
|
|
442
|
+
|
|
443
|
+
# Refresh display
|
|
444
|
+
self._update_embedding_model_display(collection_info)
|
|
445
|
+
|
|
446
|
+
print(f"✓ Configured embedding model for '{self.current_collection}': {model_name} ({model_type})")
|
|
447
|
+
|
|
448
|
+
elif result == 2: # Clear configuration
|
|
449
|
+
# Remove from settings
|
|
450
|
+
if collection_key in collection_models:
|
|
451
|
+
del collection_models[collection_key]
|
|
452
|
+
settings.set('collection_embedding_models', collection_models)
|
|
453
|
+
|
|
454
|
+
# Refresh display
|
|
455
|
+
self._update_embedding_model_display(collection_info)
|
|
456
|
+
|
|
457
|
+
print(f"✓ Cleared embedding model configuration for '{self.current_collection}'")
|
|
@@ -16,6 +16,7 @@ from vector_inspector.ui.components.filter_builder import FilterBuilder
|
|
|
16
16
|
from vector_inspector.services.import_export_service import ImportExportService
|
|
17
17
|
from vector_inspector.services.filter_service import apply_client_side_filters
|
|
18
18
|
from vector_inspector.services.settings_service import SettingsService
|
|
19
|
+
from vector_inspector.core.cache_manager import get_cache_manager, CacheEntry
|
|
19
20
|
from PySide6.QtWidgets import QApplication
|
|
20
21
|
|
|
21
22
|
|
|
@@ -57,12 +58,14 @@ class MetadataView(QWidget):
|
|
|
57
58
|
super().__init__(parent)
|
|
58
59
|
self.connection = connection
|
|
59
60
|
self.current_collection: str = ""
|
|
61
|
+
self.current_database: str = ""
|
|
60
62
|
self.current_data: Optional[Dict[str, Any]] = None
|
|
61
63
|
self.page_size = 50
|
|
62
64
|
self.current_page = 0
|
|
63
65
|
self.loading_dialog = LoadingDialog("Loading data...", self)
|
|
64
66
|
self.settings_service = SettingsService()
|
|
65
67
|
self.load_thread: Optional[DataLoadThread] = None
|
|
68
|
+
self.cache_manager = get_cache_manager()
|
|
66
69
|
|
|
67
70
|
# Debounce timer for filter changes
|
|
68
71
|
self.filter_reload_timer = QTimer()
|
|
@@ -107,8 +110,9 @@ class MetadataView(QWidget):
|
|
|
107
110
|
controls_layout.addStretch()
|
|
108
111
|
|
|
109
112
|
# Refresh button
|
|
110
|
-
self.refresh_button = QPushButton("Refresh")
|
|
111
|
-
self.refresh_button.clicked.connect(self.
|
|
113
|
+
self.refresh_button = QPushButton("🔄 Refresh")
|
|
114
|
+
self.refresh_button.clicked.connect(self._refresh_data)
|
|
115
|
+
self.refresh_button.setToolTip("Refresh data and clear cache")
|
|
112
116
|
controls_layout.addWidget(self.refresh_button)
|
|
113
117
|
|
|
114
118
|
# Add/Delete buttons
|
|
@@ -172,9 +176,40 @@ class MetadataView(QWidget):
|
|
|
172
176
|
self.status_label.setStyleSheet("color: gray;")
|
|
173
177
|
layout.addWidget(self.status_label)
|
|
174
178
|
|
|
175
|
-
def set_collection(self, collection_name: str):
|
|
179
|
+
def set_collection(self, collection_name: str, database_name: str = ""):
|
|
176
180
|
"""Set the current collection to display."""
|
|
177
181
|
self.current_collection = collection_name
|
|
182
|
+
# Always update database_name if provided (even if empty string on first call)
|
|
183
|
+
if database_name: # Only update if non-empty
|
|
184
|
+
self.current_database = database_name
|
|
185
|
+
|
|
186
|
+
# 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()}")
|
|
189
|
+
|
|
190
|
+
# Check cache first
|
|
191
|
+
cached = self.cache_manager.get(self.current_database, self.current_collection)
|
|
192
|
+
if cached and cached.data:
|
|
193
|
+
print(f"[MetadataView] ✓ Cache HIT! Loading from cache.")
|
|
194
|
+
# Restore from cache
|
|
195
|
+
self.current_page = 0
|
|
196
|
+
self.current_data = cached.data
|
|
197
|
+
self._populate_table(cached.data)
|
|
198
|
+
self._update_pagination_controls()
|
|
199
|
+
self._update_filter_fields(cached.data)
|
|
200
|
+
|
|
201
|
+
# Restore UI state
|
|
202
|
+
if cached.scroll_position:
|
|
203
|
+
self.table.verticalScrollBar().setValue(cached.scroll_position)
|
|
204
|
+
if cached.search_query:
|
|
205
|
+
# Restore filter state if applicable
|
|
206
|
+
pass
|
|
207
|
+
|
|
208
|
+
self.status_label.setText(f"✓ Loaded from cache - {len(cached.data.get('ids', []))} items")
|
|
209
|
+
return
|
|
210
|
+
|
|
211
|
+
print(f"[MetadataView] ✗ Cache MISS. Loading from database...")
|
|
212
|
+
# Not in cache, load from database
|
|
178
213
|
self.current_page = 0
|
|
179
214
|
|
|
180
215
|
# Update filter builder with supported operators
|
|
@@ -246,6 +281,19 @@ class MetadataView(QWidget):
|
|
|
246
281
|
|
|
247
282
|
# Update filter builder with available metadata fields
|
|
248
283
|
self._update_filter_fields(data)
|
|
284
|
+
|
|
285
|
+
# Save to cache
|
|
286
|
+
if self.current_database and self.current_collection:
|
|
287
|
+
print(f"[MetadataView] Saving to cache: db='{self.current_database}', coll='{self.current_collection}'")
|
|
288
|
+
cache_entry = CacheEntry(
|
|
289
|
+
data=data,
|
|
290
|
+
scroll_position=self.table.verticalScrollBar().value(),
|
|
291
|
+
search_query=self.filter_builder.to_dict() if hasattr(self.filter_builder, 'to_dict') else ""
|
|
292
|
+
)
|
|
293
|
+
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
|
+
else:
|
|
296
|
+
print(f"[MetadataView] ✗ NOT saving to cache - db='{self.current_database}', coll='{self.current_collection}'")
|
|
249
297
|
|
|
250
298
|
def _on_load_error(self, error_msg: str):
|
|
251
299
|
"""Handle error from background thread."""
|
|
@@ -365,6 +413,9 @@ class MetadataView(QWidget):
|
|
|
365
413
|
)
|
|
366
414
|
|
|
367
415
|
if success:
|
|
416
|
+
# Invalidate cache after adding item
|
|
417
|
+
if self.current_database and self.current_collection:
|
|
418
|
+
self.cache_manager.invalidate(self.current_database, self.current_collection)
|
|
368
419
|
QMessageBox.information(self, "Success", "Item added successfully.")
|
|
369
420
|
self._load_data()
|
|
370
421
|
else:
|
|
@@ -399,6 +450,9 @@ class MetadataView(QWidget):
|
|
|
399
450
|
if reply == QMessageBox.Yes:
|
|
400
451
|
success = self.connection.delete_items(self.current_collection, ids=ids_to_delete)
|
|
401
452
|
if success:
|
|
453
|
+
# Invalidate cache after deletion
|
|
454
|
+
if self.current_database and self.current_collection:
|
|
455
|
+
self.cache_manager.invalidate(self.current_database, self.current_collection)
|
|
402
456
|
QMessageBox.information(self, "Success", "Items deleted successfully.")
|
|
403
457
|
self._load_data()
|
|
404
458
|
else:
|
|
@@ -422,6 +476,13 @@ class MetadataView(QWidget):
|
|
|
422
476
|
self.current_page = 0
|
|
423
477
|
self._load_data()
|
|
424
478
|
|
|
479
|
+
def _refresh_data(self):
|
|
480
|
+
"""Refresh data and invalidate cache."""
|
|
481
|
+
if self.current_database and self.current_collection:
|
|
482
|
+
self.cache_manager.invalidate(self.current_database, self.current_collection)
|
|
483
|
+
self.current_page = 0
|
|
484
|
+
self._load_data()
|
|
485
|
+
|
|
425
486
|
def _on_row_double_clicked(self, index):
|
|
426
487
|
"""Handle double-click on a row to edit item."""
|
|
427
488
|
if not self.current_collection or not self.current_data:
|
|
@@ -462,6 +523,9 @@ class MetadataView(QWidget):
|
|
|
462
523
|
)
|
|
463
524
|
|
|
464
525
|
if success:
|
|
526
|
+
# Invalidate cache after updating item
|
|
527
|
+
if self.current_database and self.current_collection:
|
|
528
|
+
self.cache_manager.invalidate(self.current_database, self.current_collection)
|
|
465
529
|
QMessageBox.information(self, "Success", "Item updated successfully.")
|
|
466
530
|
self._load_data()
|
|
467
531
|
else:
|
|
@@ -653,6 +717,10 @@ class MetadataView(QWidget):
|
|
|
653
717
|
self.loading_dialog.hide_loading()
|
|
654
718
|
|
|
655
719
|
if success:
|
|
720
|
+
# Invalidate cache after import
|
|
721
|
+
if self.current_database and self.current_collection:
|
|
722
|
+
self.cache_manager.invalidate(self.current_database, self.current_collection)
|
|
723
|
+
|
|
656
724
|
# Save the directory for next time
|
|
657
725
|
from pathlib import Path
|
|
658
726
|
self.settings_service.set("last_import_export_dir", str(Path(file_path).parent))
|
|
@@ -12,6 +12,7 @@ from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
|
12
12
|
from vector_inspector.ui.components.filter_builder import FilterBuilder
|
|
13
13
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
14
14
|
from vector_inspector.services.filter_service import apply_client_side_filters
|
|
15
|
+
from vector_inspector.core.cache_manager import get_cache_manager, CacheEntry
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class SearchView(QWidget):
|
|
@@ -21,8 +22,10 @@ class SearchView(QWidget):
|
|
|
21
22
|
super().__init__(parent)
|
|
22
23
|
self.connection = connection
|
|
23
24
|
self.current_collection: str = ""
|
|
25
|
+
self.current_database: str = ""
|
|
24
26
|
self.search_results: Optional[Dict[str, Any]] = None
|
|
25
27
|
self.loading_dialog = LoadingDialog("Searching...", self)
|
|
28
|
+
self.cache_manager = get_cache_manager()
|
|
26
29
|
|
|
27
30
|
self._setup_ui()
|
|
28
31
|
|
|
@@ -112,12 +115,30 @@ class SearchView(QWidget):
|
|
|
112
115
|
|
|
113
116
|
layout.addWidget(splitter)
|
|
114
117
|
|
|
115
|
-
def set_collection(self, collection_name: str):
|
|
118
|
+
def set_collection(self, collection_name: str, database_name: str = ""):
|
|
116
119
|
"""Set the current collection to search."""
|
|
117
120
|
self.current_collection = collection_name
|
|
121
|
+
# Always update database_name if provided (even if empty string on first call)
|
|
122
|
+
if database_name: # Only update if non-empty
|
|
123
|
+
self.current_database = database_name
|
|
124
|
+
|
|
125
|
+
print(f"[SearchView] Setting collection: db='{self.current_database}', coll='{collection_name}'")
|
|
126
|
+
|
|
127
|
+
# Check cache first
|
|
128
|
+
cached = self.cache_manager.get(self.current_database, self.current_collection)
|
|
129
|
+
if cached:
|
|
130
|
+
print(f"[SearchView] ✓ Cache HIT! Restoring search state.")
|
|
131
|
+
# Restore search query and results from cache
|
|
132
|
+
if cached.search_query:
|
|
133
|
+
self.query_input.setPlainText(cached.search_query)
|
|
134
|
+
if cached.search_results:
|
|
135
|
+
self.search_results = cached.search_results
|
|
136
|
+
self._display_results(cached.search_results)
|
|
137
|
+
return
|
|
138
|
+
|
|
139
|
+
print(f"[SearchView] ✗ Cache MISS or no cached search.")
|
|
140
|
+
# Not in cache, clear form
|
|
118
141
|
self.search_results = None
|
|
119
|
-
|
|
120
|
-
# Clear search form inputs
|
|
121
142
|
self.query_input.clear()
|
|
122
143
|
self.results_table.setRowCount(0)
|
|
123
144
|
self.results_status.setText(f"Collection: {collection_name}")
|
|
@@ -196,6 +217,12 @@ class SearchView(QWidget):
|
|
|
196
217
|
self.results_table.setRowCount(0)
|
|
197
218
|
return
|
|
198
219
|
|
|
220
|
+
# Check if results have the expected structure
|
|
221
|
+
if not results.get("ids") or not isinstance(results["ids"], list) or len(results["ids"]) == 0:
|
|
222
|
+
self.results_status.setText("No results found or query failed")
|
|
223
|
+
self.results_table.setRowCount(0)
|
|
224
|
+
return
|
|
225
|
+
|
|
199
226
|
# Apply client-side filters if any
|
|
200
227
|
if client_filters and results:
|
|
201
228
|
# Restructure results for filtering
|
|
@@ -219,6 +246,19 @@ class SearchView(QWidget):
|
|
|
219
246
|
self.search_results = results
|
|
220
247
|
self._display_results(results)
|
|
221
248
|
|
|
249
|
+
# Save to cache
|
|
250
|
+
if self.current_database and self.current_collection:
|
|
251
|
+
self.cache_manager.update(
|
|
252
|
+
self.current_database,
|
|
253
|
+
self.current_collection,
|
|
254
|
+
search_query=query_text,
|
|
255
|
+
search_results=results,
|
|
256
|
+
user_inputs={
|
|
257
|
+
'n_results': n_results,
|
|
258
|
+
'filters': self.filter_builder.to_dict() if hasattr(self.filter_builder, 'to_dict') else {}
|
|
259
|
+
}
|
|
260
|
+
)
|
|
261
|
+
|
|
222
262
|
def _display_results(self, results: Dict[str, Any]):
|
|
223
263
|
"""Display search results in table."""
|
|
224
264
|
ids = results.get("ids", [[]])[0]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.7
|
|
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
|
|
@@ -22,6 +22,8 @@ Requires-Dist: sentence-transformers>=2.2.0
|
|
|
22
22
|
Requires-Dist: fastembed>=0.7.4
|
|
23
23
|
Requires-Dist: pyarrow>=14.0.0
|
|
24
24
|
Requires-Dist: pinecone>=8.0.0
|
|
25
|
+
Requires-Dist: keyring>=25.7.0
|
|
26
|
+
Requires-Dist: hf-xet>=1.2.0
|
|
25
27
|
Description-Content-Type: text/markdown
|
|
26
28
|
|
|
27
29
|
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
vector_inspector-0.2.7.dist-info/METADATA,sha256=mahmP5eIlgej1osckY_twV3dQhSnX7BT20B1ivrsiu4,9684
|
|
2
|
+
vector_inspector-0.2.7.dist-info/WHEEL,sha256=tsUv_t7BDeJeRHaSrczbGeuK-TtDpGsWi_JfpzD255I,90
|
|
3
|
+
vector_inspector-0.2.7.dist-info/entry_points.txt,sha256=u96envMI2NFImZUJDFutiiWl7ZoHrrev9joAgtyvTxo,80
|
|
4
|
+
vector_inspector/__init__.py,sha256=Q8XbXn98o0eliQWPePhy-aGUz2KNnVg7bQq-sBPl7zQ,119
|
|
5
|
+
vector_inspector/__main__.py,sha256=Vdhw8YA1K3wPMlbJQYL5WqvRzAKVeZ16mZQFO9VRmCo,62
|
|
6
|
+
vector_inspector/core/__init__.py,sha256=hjOqiJwF1P0rXjiOKhK4qDTvBY7G3m4kq8taH-gKrFM,57
|
|
7
|
+
vector_inspector/core/cache_manager.py,sha256=cHdbIYR-eS9vLLTqvq4Xejyi5Z7Fm9DqMhb_PMZjnjY,5695
|
|
8
|
+
vector_inspector/core/connection_manager.py,sha256=WwiVedHWTfqIuJKV7D52bYEupmpnnSPKcgG1odJCJjs,10003
|
|
9
|
+
vector_inspector/core/connections/__init__.py,sha256=cCwDy69Jy8ajiFQICcWfPnGoMtfrq69brzXJ4sVYCQ0,271
|
|
10
|
+
vector_inspector/core/connections/base_connection.py,sha256=PiwVlrk-eUKSka6gmE2GSyml0xe48PrIWAcKhBJMBv8,7131
|
|
11
|
+
vector_inspector/core/connections/chroma_connection.py,sha256=eBMrJQg_J01mWrEmKdK_p3-zJRNA7sOuFWd_bYit3y8,18298
|
|
12
|
+
vector_inspector/core/connections/qdrant_connection.py,sha256=m0EFgSbWRVh3uv_JkbB0WEotu20G967TrHvwJrtedO4,31861
|
|
13
|
+
vector_inspector/core/connections/template_connection.py,sha256=wsJiE4ma3cLUXk2eW5rnLMS5wG8JTekgEn46lHHQNoc,10642
|
|
14
|
+
vector_inspector/core/embedding_utils.py,sha256=LJK3YXwng7nO3csSkPHrixXIXBFf06tspq1ZSxSGgsM,4933
|
|
15
|
+
vector_inspector/main.py,sha256=puu1Fur298j6H8fG3_wF85RMhi4tjLZ0Are16kloMqM,479
|
|
16
|
+
vector_inspector/services/__init__.py,sha256=QLgH7oybjHuEYDFNiBgmJxvSpgAzHEuBEPXa3SKJb_I,67
|
|
17
|
+
vector_inspector/services/backup_restore_service.py,sha256=3lZ25FXG6DHQtJNiaIe8ZJgvxMRWuPrIJIBKPGWe7gA,10618
|
|
18
|
+
vector_inspector/services/credential_service.py,sha256=pdpglvLABnYQRvklgK38iPyG91IZ9Nm8WQFT9V0LofM,4354
|
|
19
|
+
vector_inspector/services/filter_service.py,sha256=xDrMxNWsYzRcR1n0Fd-yp6Fo-4aLbVIDkhj2GKmrw5o,2370
|
|
20
|
+
vector_inspector/services/import_export_service.py,sha256=OPCrBXBewCznu5o8wFGvduU0jGZAcBvp_Fpv15kdoJ4,10712
|
|
21
|
+
vector_inspector/services/profile_service.py,sha256=Uhl9urxeSRI0p-mfaYgBrMI99byNIxJSodcXOgD_ybw,13408
|
|
22
|
+
vector_inspector/services/settings_service.py,sha256=g07fyPPgI4VvidJ_xgpuj5id-M1pDXvIh6_ZXnkUqAI,2914
|
|
23
|
+
vector_inspector/services/visualization_service.py,sha256=mHI4qxT-V4R1kcOhE508vFTZ0HcmQICHvJ7dIoRracQ,4373
|
|
24
|
+
vector_inspector/ui/__init__.py,sha256=262ZiXO6Luk8vZnhCIoYxOtGiny0bXK-BTKjxUNBx-w,43
|
|
25
|
+
vector_inspector/ui/components/__init__.py,sha256=S-GWU1P820dJ6mHmeeBEy-CGF9fjpBeNf8vrbhRlFMk,30
|
|
26
|
+
vector_inspector/ui/components/backup_restore_dialog.py,sha256=CrZ2u8vXzggv3aBkYR4FulpY74oZWMLW5BHU4dMiWug,13073
|
|
27
|
+
vector_inspector/ui/components/connection_manager_panel.py,sha256=6d_uNaaNaayYz4HV3LndiQsYFISpsSCwj4-sN-Qt8uc,12963
|
|
28
|
+
vector_inspector/ui/components/filter_builder.py,sha256=NSR_hp-rzUZVAca6dIJhTxZA3igOKFM1g-YXiYPhFos,13360
|
|
29
|
+
vector_inspector/ui/components/item_dialog.py,sha256=VMwehEjQ6xrdxWygR9J-hHsLfzOVb_E3ePUGYO_c7XA,3951
|
|
30
|
+
vector_inspector/ui/components/loading_dialog.py,sha256=YEKYGU-R-Zz4CjXSArJtkNxgTy4O9hI5Bbt6qlIzD8U,1018
|
|
31
|
+
vector_inspector/ui/components/profile_manager_panel.py,sha256=mXs2vOazUbmqzF7SNABtdKmeys6RjwtcgKaEKYK5MuI,18702
|
|
32
|
+
vector_inspector/ui/dialogs/__init__.py,sha256=U49PF3Jn57qkdjJ0hjMnhvQKv2xeB5e2SretF_24T_U,136
|
|
33
|
+
vector_inspector/ui/dialogs/cross_db_migration.py,sha256=fIhnD5kJeqSPe5bwf2KFkjk3dQY_kda0zZVsPHtB1w8,14500
|
|
34
|
+
vector_inspector/ui/dialogs/embedding_config_dialog.py,sha256=g-MVuVE4_ZAZjA-yVa6ZaFU2-vrEQnVYiHqEmjoBqmM,7358
|
|
35
|
+
vector_inspector/ui/main_window.py,sha256=OPuAODir7LPUenNetNYwhwQ49p_zFA3axx014yQhJSg,24554
|
|
36
|
+
vector_inspector/ui/views/__init__.py,sha256=FeMtVzSbVFBMjdwLQSQqD0FRW4ieJ4ZKXtTBci2e_bw,30
|
|
37
|
+
vector_inspector/ui/views/collection_browser.py,sha256=oG9_YGPoVuMs-f_zSd4EcITmEU9caxvwuubsFUrNf-c,3991
|
|
38
|
+
vector_inspector/ui/views/connection_view.py,sha256=CpHPSDee3RhH3_wNu62RI3Fn5ffBIGt_Va5iC0p1IoY,18116
|
|
39
|
+
vector_inspector/ui/views/info_panel.py,sha256=tNKlWsb5KalxstOmC0xYY5UH4pW4qY_ysJcRrPQMcWk,21624
|
|
40
|
+
vector_inspector/ui/views/metadata_view.py,sha256=dIe7aH47xd6TBK2g-WogR2Ts0enHGeSAEUlu48sdTF8,30535
|
|
41
|
+
vector_inspector/ui/views/search_view.py,sha256=IMPZE7SPGoNuVbbffIjv1HxM-qZpmx0L1qtB9EYizjI,12474
|
|
42
|
+
vector_inspector/ui/views/visualization_view.py,sha256=uPOUpKJ00eFqCCohYEfMD8D9lNv2cy2FymPHouXK0Go,9163
|
|
43
|
+
vector_inspector/utils/__init__.py,sha256=jhHBQC8C8bfhNlf6CAt07ejjStp_YAyleaYr2dm0Dk0,38
|
|
44
|
+
vector_inspector/utils/lazy_imports.py,sha256=2XZ3ZnwTvZ5vvrh36nJ_TUjwwkgjoAED6i6P9yctvt0,1211
|
|
45
|
+
vector_inspector-0.2.7.dist-info/RECORD,,
|