vector-inspector 0.3.11__py3-none-any.whl → 0.3.12__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/__init__.py +1 -1
- vector_inspector/core/connection_manager.py +91 -19
- vector_inspector/core/connections/base_connection.py +43 -43
- vector_inspector/core/connections/chroma_connection.py +1 -1
- vector_inspector/core/connections/pgvector_connection.py +11 -171
- vector_inspector/core/connections/pinecone_connection.py +596 -99
- vector_inspector/core/connections/qdrant_connection.py +35 -44
- vector_inspector/core/embedding_utils.py +14 -5
- vector_inspector/core/logging.py +3 -1
- vector_inspector/main.py +42 -15
- vector_inspector/services/backup_restore_service.py +228 -15
- vector_inspector/services/settings_service.py +71 -19
- vector_inspector/ui/components/backup_restore_dialog.py +215 -101
- vector_inspector/ui/components/connection_manager_panel.py +155 -14
- vector_inspector/ui/dialogs/cross_db_migration.py +126 -99
- vector_inspector/ui/dialogs/settings_dialog.py +13 -6
- vector_inspector/ui/loading_screen.py +169 -0
- vector_inspector/ui/main_window.py +44 -19
- vector_inspector/ui/services/dialog_service.py +1 -0
- vector_inspector/ui/views/collection_browser.py +36 -34
- vector_inspector/ui/views/connection_view.py +7 -1
- vector_inspector/ui/views/info_panel.py +118 -52
- vector_inspector/ui/views/metadata_view.py +30 -31
- vector_inspector/ui/views/search_view.py +20 -19
- vector_inspector/ui/views/visualization_view.py +18 -15
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/METADATA +17 -4
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/RECORD +30 -28
- vector_inspector-0.3.12.dist-info/licenses/LICENSE +1 -0
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/WHEEL +0 -0
- {vector_inspector-0.3.11.dist-info → vector_inspector-0.3.12.dist-info}/entry_points.txt +0 -0
|
@@ -1,33 +1,34 @@
|
|
|
1
1
|
"""Information panel for displaying database and collection metadata."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
4
6
|
from PySide6.QtWidgets import (
|
|
5
|
-
|
|
6
|
-
|
|
7
|
+
QApplication,
|
|
8
|
+
QDialog,
|
|
9
|
+
QGroupBox,
|
|
7
10
|
QHBoxLayout,
|
|
8
11
|
QLabel,
|
|
9
|
-
QGroupBox,
|
|
10
|
-
QScrollArea,
|
|
11
|
-
QFrame,
|
|
12
12
|
QPushButton,
|
|
13
|
+
QScrollArea,
|
|
14
|
+
QVBoxLayout,
|
|
15
|
+
QWidget,
|
|
13
16
|
)
|
|
14
|
-
from PySide6.QtCore import Qt, QObject
|
|
15
|
-
from PySide6.QtWidgets import QDialog
|
|
16
|
-
from PySide6.QtWidgets import QApplication
|
|
17
17
|
|
|
18
|
-
from vector_inspector.core.
|
|
18
|
+
from vector_inspector.core.cache_manager import get_cache_manager
|
|
19
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
19
20
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
20
|
-
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
21
21
|
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
22
|
-
from vector_inspector.core.
|
|
22
|
+
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
23
23
|
from vector_inspector.core.logging import log_info
|
|
24
24
|
|
|
25
25
|
|
|
26
26
|
class InfoPanel(QWidget):
|
|
27
27
|
"""Panel for displaying database and collection information."""
|
|
28
28
|
|
|
29
|
-
def __init__(self, connection:
|
|
29
|
+
def __init__(self, connection: Optional[ConnectionInstance] = None, parent=None):
|
|
30
30
|
super().__init__(parent)
|
|
31
|
+
# Expects a ConnectionInstance wrapper.
|
|
31
32
|
self.connection = connection
|
|
32
33
|
self.connection_id: str = "" # Will be set when collection is set
|
|
33
34
|
self.current_collection: str = ""
|
|
@@ -149,8 +150,29 @@ class InfoPanel(QWidget):
|
|
|
149
150
|
"""Clear the embedding model configuration for this collection (reset to autodetect)."""
|
|
150
151
|
from vector_inspector.services.settings_service import SettingsService
|
|
151
152
|
|
|
153
|
+
# Ensure we have a valid connection_id
|
|
154
|
+
effective_connection_id = self.connection_id or (
|
|
155
|
+
self.connection.id if self.connection else None
|
|
156
|
+
)
|
|
157
|
+
|
|
158
|
+
if not effective_connection_id:
|
|
159
|
+
log_info("Cannot clear embedding model: no connection_id available")
|
|
160
|
+
return
|
|
161
|
+
|
|
152
162
|
settings = SettingsService()
|
|
153
|
-
settings.remove_embedding_model(
|
|
163
|
+
settings.remove_embedding_model(
|
|
164
|
+
self.connection.name if self.connection else "",
|
|
165
|
+
self.current_collection,
|
|
166
|
+
)
|
|
167
|
+
|
|
168
|
+
# Clear cache to ensure fresh collection info on next load
|
|
169
|
+
if effective_connection_id and self.current_collection:
|
|
170
|
+
self.cache_manager.invalidate(effective_connection_id, self.current_collection)
|
|
171
|
+
log_info(
|
|
172
|
+
"Cleared cache for collection after clearing embedding model: %s",
|
|
173
|
+
self.current_collection,
|
|
174
|
+
)
|
|
175
|
+
|
|
154
176
|
# Refresh display (force reload collection info)
|
|
155
177
|
if self.current_collection:
|
|
156
178
|
self.set_collection(self.current_collection, self.current_database)
|
|
@@ -163,13 +185,21 @@ class InfoPanel(QWidget):
|
|
|
163
185
|
"""Update the clear button state based on current configuration."""
|
|
164
186
|
from vector_inspector.services.settings_service import SettingsService
|
|
165
187
|
|
|
166
|
-
|
|
188
|
+
# Ensure we have valid identifiers
|
|
189
|
+
effective_connection_id = self.connection_id or (
|
|
190
|
+
self.connection.id if self.connection else None
|
|
191
|
+
)
|
|
192
|
+
|
|
193
|
+
if not effective_connection_id or not self.current_collection:
|
|
167
194
|
self.clear_embedding_btn.setEnabled(False)
|
|
168
195
|
return
|
|
169
196
|
|
|
170
197
|
# Check if there's a user-configured model in settings
|
|
171
198
|
settings = SettingsService()
|
|
172
|
-
model_info = settings.get_embedding_model(
|
|
199
|
+
model_info = settings.get_embedding_model(
|
|
200
|
+
self.connection.name if self.connection else "",
|
|
201
|
+
self.current_collection,
|
|
202
|
+
)
|
|
173
203
|
|
|
174
204
|
# Enable button if there's a user-configured model
|
|
175
205
|
self.clear_embedding_btn.setEnabled(model_info is not None)
|
|
@@ -213,50 +243,48 @@ class InfoPanel(QWidget):
|
|
|
213
243
|
return
|
|
214
244
|
|
|
215
245
|
# Get provider name
|
|
216
|
-
|
|
246
|
+
# Extract the underlying database connection from ConnectionInstance wrapper.
|
|
247
|
+
backend = getattr(self.connection, "database", self.connection)
|
|
248
|
+
provider_name = (
|
|
249
|
+
backend.__class__.__name__.replace("Connection", "") if backend else "Unknown"
|
|
250
|
+
)
|
|
217
251
|
self._update_label(self.provider_label, provider_name)
|
|
218
252
|
|
|
219
253
|
# Get connection details
|
|
220
|
-
if isinstance(
|
|
221
|
-
if
|
|
254
|
+
if isinstance(backend, ChromaDBConnection):
|
|
255
|
+
if getattr(backend, "path", None):
|
|
222
256
|
self._update_label(self.connection_type_label, "Persistent (Local)")
|
|
223
|
-
self._update_label(self.endpoint_label,
|
|
224
|
-
elif
|
|
257
|
+
self._update_label(self.endpoint_label, backend.path)
|
|
258
|
+
elif getattr(backend, "host", None) and getattr(backend, "port", None):
|
|
225
259
|
self._update_label(self.connection_type_label, "HTTP (Remote)")
|
|
226
|
-
self._update_label(
|
|
227
|
-
self.endpoint_label, f"{self.connection.host}:{self.connection.port}"
|
|
228
|
-
)
|
|
260
|
+
self._update_label(self.endpoint_label, f"{backend.host}:{backend.port}")
|
|
229
261
|
else:
|
|
230
262
|
self._update_label(self.connection_type_label, "Ephemeral (In-Memory)")
|
|
231
263
|
self._update_label(self.endpoint_label, "N/A")
|
|
232
264
|
self._update_label(self.api_key_label, "Not required")
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
if self.connection.path:
|
|
265
|
+
elif isinstance(backend, QdrantConnection):
|
|
266
|
+
if getattr(backend, "path", None):
|
|
236
267
|
self._update_label(self.connection_type_label, "Embedded (Local)")
|
|
237
|
-
self._update_label(self.endpoint_label,
|
|
238
|
-
elif
|
|
268
|
+
self._update_label(self.endpoint_label, backend.path)
|
|
269
|
+
elif getattr(backend, "url", None):
|
|
239
270
|
self._update_label(self.connection_type_label, "Remote (URL)")
|
|
240
|
-
self._update_label(self.endpoint_label,
|
|
241
|
-
elif
|
|
271
|
+
self._update_label(self.endpoint_label, backend.url)
|
|
272
|
+
elif getattr(backend, "host", None):
|
|
242
273
|
self._update_label(self.connection_type_label, "Remote (Host)")
|
|
243
|
-
self._update_label(
|
|
244
|
-
self.endpoint_label, f"{self.connection.host}:{self.connection.port}"
|
|
245
|
-
)
|
|
274
|
+
self._update_label(self.endpoint_label, f"{backend.host}:{backend.port}")
|
|
246
275
|
else:
|
|
247
276
|
self._update_label(self.connection_type_label, "In-Memory")
|
|
248
277
|
self._update_label(self.endpoint_label, "N/A")
|
|
249
|
-
|
|
250
|
-
if self.connection.api_key:
|
|
278
|
+
if getattr(backend, "api_key", None):
|
|
251
279
|
self._update_label(self.api_key_label, "Present (hidden)")
|
|
252
280
|
else:
|
|
253
281
|
self._update_label(self.api_key_label, "Not configured")
|
|
254
282
|
|
|
255
|
-
elif isinstance(
|
|
283
|
+
elif isinstance(backend, PineconeConnection):
|
|
284
|
+
# backend already assigned above
|
|
256
285
|
self._update_label(self.connection_type_label, "Cloud")
|
|
257
286
|
self._update_label(self.endpoint_label, "Pinecone Cloud")
|
|
258
|
-
|
|
259
|
-
if self.connection.api_key:
|
|
287
|
+
if getattr(backend, "api_key", None):
|
|
260
288
|
self._update_label(self.api_key_label, "Present (hidden)")
|
|
261
289
|
else:
|
|
262
290
|
self._update_label(self.api_key_label, "Not configured")
|
|
@@ -274,7 +302,7 @@ class InfoPanel(QWidget):
|
|
|
274
302
|
try:
|
|
275
303
|
collections = self.connection.list_collections()
|
|
276
304
|
self._update_label(self.collections_count_label, str(len(collections)))
|
|
277
|
-
except Exception
|
|
305
|
+
except Exception:
|
|
278
306
|
self._update_label(self.collections_count_label, "Error")
|
|
279
307
|
|
|
280
308
|
def refresh_collection_info(self):
|
|
@@ -323,11 +351,11 @@ class InfoPanel(QWidget):
|
|
|
323
351
|
self._update_label(self.vector_dim_label, "Error")
|
|
324
352
|
self._update_label(self.distance_metric_label, "Error")
|
|
325
353
|
self._update_label(self.total_points_label, "Error")
|
|
326
|
-
self.schema_label.setText(f"Error: {
|
|
354
|
+
self.schema_label.setText(f"Error: {e!s}")
|
|
327
355
|
self.schema_label.setStyleSheet("color: red; padding-left: 20px;")
|
|
328
356
|
self.provider_details_label.setText("N/A")
|
|
329
357
|
|
|
330
|
-
def _display_collection_info(self, collection_info:
|
|
358
|
+
def _display_collection_info(self, collection_info: dict[str, Any]):
|
|
331
359
|
"""Display collection information (from cache or fresh query)."""
|
|
332
360
|
# Update basic info
|
|
333
361
|
self._update_label(self.collection_name_label, self.current_collection)
|
|
@@ -395,6 +423,11 @@ class InfoPanel(QWidget):
|
|
|
395
423
|
details_list.append("• Provider: Pinecone")
|
|
396
424
|
details_list.append("• Supports: Vectors, Metadata")
|
|
397
425
|
details_list.append("• Cloud-hosted vector database")
|
|
426
|
+
# Check if using Pinecone-hosted embedding model
|
|
427
|
+
if collection_info.get("embedding_model_type") == "pinecone-hosted":
|
|
428
|
+
hosted_model = collection_info.get("embedding_model", "Unknown")
|
|
429
|
+
details_list.append(f"• Hosted Model: {hosted_model}")
|
|
430
|
+
details_list.append("• Supports text-based queries (no local embedding needed)")
|
|
398
431
|
# Add Pinecone-specific info if available
|
|
399
432
|
if "host" in collection_info:
|
|
400
433
|
details_list.append(f"• Host: {collection_info['host']}")
|
|
@@ -443,7 +476,7 @@ class InfoPanel(QWidget):
|
|
|
443
476
|
if value_label and isinstance(value_label, QLabel):
|
|
444
477
|
value_label.setText(value)
|
|
445
478
|
|
|
446
|
-
def _update_embedding_model_display(self, collection_info:
|
|
479
|
+
def _update_embedding_model_display(self, collection_info: dict[str, Any]):
|
|
447
480
|
"""Update the embedding model label based on current configuration."""
|
|
448
481
|
from vector_inspector.services.settings_service import SettingsService
|
|
449
482
|
|
|
@@ -459,20 +492,28 @@ class InfoPanel(QWidget):
|
|
|
459
492
|
self.clear_embedding_btn.setEnabled(True)
|
|
460
493
|
return
|
|
461
494
|
|
|
495
|
+
# Ensure we have a valid connection_id for settings lookup
|
|
496
|
+
# Fallback to connection.id if connection_id not set
|
|
497
|
+
effective_connection_id = self.connection_id or (
|
|
498
|
+
self.connection.id if self.connection else None
|
|
499
|
+
)
|
|
500
|
+
|
|
462
501
|
# Try to get from connection using the helper method
|
|
463
502
|
if self.connection and self.current_collection:
|
|
464
|
-
detected_model = self.connection.get_embedding_model(
|
|
465
|
-
self.current_collection, self.connection_id
|
|
466
|
-
)
|
|
503
|
+
detected_model = self.connection.get_embedding_model(self.current_collection)
|
|
467
504
|
if detected_model:
|
|
468
505
|
self.embedding_model_label.setText(f"{detected_model} (detected)")
|
|
469
506
|
self.embedding_model_label.setStyleSheet("color: lightgreen;")
|
|
470
507
|
self.clear_embedding_btn.setEnabled(False)
|
|
471
508
|
return
|
|
472
509
|
|
|
473
|
-
# Check user settings
|
|
510
|
+
# Check user settings directly
|
|
474
511
|
settings = SettingsService()
|
|
475
|
-
|
|
512
|
+
profile_name = self.connection.name if self.connection else ""
|
|
513
|
+
model_info = settings.get_embedding_model(
|
|
514
|
+
profile_name,
|
|
515
|
+
self.current_collection,
|
|
516
|
+
)
|
|
476
517
|
|
|
477
518
|
if model_info:
|
|
478
519
|
model_name = model_info["model"]
|
|
@@ -492,6 +533,14 @@ class InfoPanel(QWidget):
|
|
|
492
533
|
if not self.current_collection:
|
|
493
534
|
return
|
|
494
535
|
|
|
536
|
+
# Ensure we have a valid connection_id
|
|
537
|
+
effective_connection_id = self.connection_id or (
|
|
538
|
+
self.connection.id if self.connection else None
|
|
539
|
+
)
|
|
540
|
+
|
|
541
|
+
if not effective_connection_id:
|
|
542
|
+
return
|
|
543
|
+
|
|
495
544
|
# Show loading immediately; preparing can touch DB/registry
|
|
496
545
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
497
546
|
|
|
@@ -499,8 +548,8 @@ class InfoPanel(QWidget):
|
|
|
499
548
|
loading.show_loading("Preparing model configuration...")
|
|
500
549
|
QApplication.processEvents()
|
|
501
550
|
|
|
502
|
-
from vector_inspector.ui.dialogs import ProviderTypeDialog, EmbeddingConfigDialog
|
|
503
551
|
from vector_inspector.services.settings_service import SettingsService
|
|
552
|
+
from vector_inspector.ui.dialogs import EmbeddingConfigDialog, ProviderTypeDialog
|
|
504
553
|
|
|
505
554
|
# Get current collection info
|
|
506
555
|
try:
|
|
@@ -527,7 +576,10 @@ class InfoPanel(QWidget):
|
|
|
527
576
|
current_type = collection_info.get("embedding_model_type", "stored")
|
|
528
577
|
# Then check settings
|
|
529
578
|
else:
|
|
530
|
-
model_info = settings.get_embedding_model(
|
|
579
|
+
model_info = settings.get_embedding_model(
|
|
580
|
+
self.connection.name if self.connection else "",
|
|
581
|
+
self.current_collection,
|
|
582
|
+
)
|
|
531
583
|
if model_info:
|
|
532
584
|
current_model = model_info.get("model")
|
|
533
585
|
current_type = model_info.get("type")
|
|
@@ -558,9 +610,20 @@ class InfoPanel(QWidget):
|
|
|
558
610
|
if selection:
|
|
559
611
|
model_name, model_type = selection
|
|
560
612
|
settings.save_embedding_model(
|
|
561
|
-
self.
|
|
613
|
+
self.connection.name if self.connection else "",
|
|
614
|
+
self.current_collection,
|
|
615
|
+
model_name,
|
|
616
|
+
model_type,
|
|
562
617
|
)
|
|
563
618
|
|
|
619
|
+
# Clear cache to ensure fresh collection info on next load
|
|
620
|
+
if effective_connection_id and self.current_collection:
|
|
621
|
+
self.cache_manager.invalidate(effective_connection_id, self.current_collection)
|
|
622
|
+
log_info(
|
|
623
|
+
"Cleared cache for collection after configuring embedding model: %s",
|
|
624
|
+
self.current_collection,
|
|
625
|
+
)
|
|
626
|
+
|
|
564
627
|
# Update the display immediately to show new model
|
|
565
628
|
self.embedding_model_label.setText(f"{model_name} ({model_type})")
|
|
566
629
|
self.embedding_model_label.setStyleSheet("color: lightblue;")
|
|
@@ -577,7 +640,10 @@ class InfoPanel(QWidget):
|
|
|
577
640
|
|
|
578
641
|
elif result == 2: # Clear configuration
|
|
579
642
|
# Remove from settings using the new SettingsService method
|
|
580
|
-
settings.remove_embedding_model(
|
|
643
|
+
settings.remove_embedding_model(
|
|
644
|
+
self.connection.name if self.connection else "",
|
|
645
|
+
self.current_collection,
|
|
646
|
+
)
|
|
581
647
|
|
|
582
648
|
# Refresh display
|
|
583
649
|
self._update_embedding_model_display(collection_info)
|
|
@@ -1,38 +1,36 @@
|
|
|
1
1
|
"""Metadata browsing and data view."""
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
import math
|
|
4
|
+
from typing import Any, Optional
|
|
5
|
+
|
|
6
|
+
from PySide6.QtCore import Qt, QThread, QTimer, Signal
|
|
4
7
|
from PySide6.QtWidgets import (
|
|
5
|
-
|
|
6
|
-
QVBoxLayout,
|
|
7
|
-
QHBoxLayout,
|
|
8
|
-
QTableWidget,
|
|
9
|
-
QTableWidgetItem,
|
|
10
|
-
QPushButton,
|
|
8
|
+
QApplication,
|
|
11
9
|
QCheckBox,
|
|
12
|
-
QLabel,
|
|
13
|
-
QSpinBox,
|
|
14
|
-
QLineEdit,
|
|
15
|
-
QComboBox,
|
|
16
|
-
QGroupBox,
|
|
17
|
-
QHeaderView,
|
|
18
|
-
QMessageBox,
|
|
19
10
|
QDialog,
|
|
20
11
|
QFileDialog,
|
|
12
|
+
QGroupBox,
|
|
13
|
+
QHBoxLayout,
|
|
14
|
+
QLabel,
|
|
21
15
|
QMenu,
|
|
16
|
+
QMessageBox,
|
|
17
|
+
QPushButton,
|
|
18
|
+
QSpinBox,
|
|
19
|
+
QTableWidget,
|
|
20
|
+
QTableWidgetItem,
|
|
21
|
+
QVBoxLayout,
|
|
22
|
+
QWidget,
|
|
22
23
|
)
|
|
23
|
-
from PySide6.QtCore import Qt, QTimer, QThread, Signal
|
|
24
|
-
import math
|
|
25
24
|
|
|
26
|
-
from vector_inspector.core.
|
|
27
|
-
from vector_inspector.
|
|
28
|
-
from vector_inspector.
|
|
29
|
-
from vector_inspector.ui.components.filter_builder import FilterBuilder
|
|
30
|
-
from vector_inspector.services.import_export_service import ImportExportService
|
|
25
|
+
from vector_inspector.core.cache_manager import CacheEntry, get_cache_manager
|
|
26
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
27
|
+
from vector_inspector.core.logging import log_info
|
|
31
28
|
from vector_inspector.services.filter_service import apply_client_side_filters
|
|
29
|
+
from vector_inspector.services.import_export_service import ImportExportService
|
|
32
30
|
from vector_inspector.services.settings_service import SettingsService
|
|
33
|
-
from vector_inspector.
|
|
34
|
-
from
|
|
35
|
-
from vector_inspector.
|
|
31
|
+
from vector_inspector.ui.components.filter_builder import FilterBuilder
|
|
32
|
+
from vector_inspector.ui.components.item_dialog import ItemDialog
|
|
33
|
+
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
36
34
|
|
|
37
35
|
|
|
38
36
|
class DataLoadThread(QThread):
|
|
@@ -66,12 +64,13 @@ class DataLoadThread(QThread):
|
|
|
66
64
|
class MetadataView(QWidget):
|
|
67
65
|
"""View for browsing collection data and metadata."""
|
|
68
66
|
|
|
69
|
-
def __init__(self, connection:
|
|
67
|
+
def __init__(self, connection: Optional[ConnectionInstance] = None, parent=None):
|
|
70
68
|
super().__init__(parent)
|
|
69
|
+
# Expects a ConnectionInstance wrapper.
|
|
71
70
|
self.connection = connection
|
|
72
71
|
self.current_collection: str = ""
|
|
73
72
|
self.current_database: str = ""
|
|
74
|
-
self.current_data: Optional[
|
|
73
|
+
self.current_data: Optional[dict[str, Any]] = None
|
|
75
74
|
self.page_size = 50
|
|
76
75
|
self.current_page = 0
|
|
77
76
|
self.loading_dialog = LoadingDialog("Loading data...", self)
|
|
@@ -308,7 +307,7 @@ class MetadataView(QWidget):
|
|
|
308
307
|
self.load_thread.error.connect(self._on_load_error)
|
|
309
308
|
self.load_thread.start()
|
|
310
309
|
|
|
311
|
-
def _on_data_loaded(self, data:
|
|
310
|
+
def _on_data_loaded(self, data: dict[str, Any]):
|
|
312
311
|
"""Handle data loaded from background thread."""
|
|
313
312
|
# If no data returned
|
|
314
313
|
if not data:
|
|
@@ -358,7 +357,7 @@ class MetadataView(QWidget):
|
|
|
358
357
|
data=full_data,
|
|
359
358
|
scroll_position=self.table.verticalScrollBar().value(),
|
|
360
359
|
search_query=(
|
|
361
|
-
|
|
360
|
+
self.filter_builder.to_dict()
|
|
362
361
|
if callable(getattr(self.filter_builder, "to_dict", None))
|
|
363
362
|
else ""
|
|
364
363
|
),
|
|
@@ -401,7 +400,7 @@ class MetadataView(QWidget):
|
|
|
401
400
|
data=data,
|
|
402
401
|
scroll_position=self.table.verticalScrollBar().value(),
|
|
403
402
|
search_query=(
|
|
404
|
-
|
|
403
|
+
self.filter_builder.to_dict()
|
|
405
404
|
if callable(getattr(self.filter_builder, "to_dict", None))
|
|
406
405
|
else ""
|
|
407
406
|
),
|
|
@@ -422,7 +421,7 @@ class MetadataView(QWidget):
|
|
|
422
421
|
self.status_label.setText(f"Failed to load data: {error_msg}")
|
|
423
422
|
self.table.setRowCount(0)
|
|
424
423
|
|
|
425
|
-
def _update_filter_fields(self, data:
|
|
424
|
+
def _update_filter_fields(self, data: dict[str, Any]):
|
|
426
425
|
"""Update filter builder with available metadata field names."""
|
|
427
426
|
field_names = []
|
|
428
427
|
|
|
@@ -441,7 +440,7 @@ class MetadataView(QWidget):
|
|
|
441
440
|
if field_names:
|
|
442
441
|
self.filter_builder.set_available_fields(field_names)
|
|
443
442
|
|
|
444
|
-
def _populate_table(self, data:
|
|
443
|
+
def _populate_table(self, data: dict[str, Any]):
|
|
445
444
|
"""Populate table with data."""
|
|
446
445
|
ids = data.get("ids", [])
|
|
447
446
|
documents = data.get("documents", [])
|
|
@@ -1,38 +1,38 @@
|
|
|
1
1
|
"""Search interface for similarity queries."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any, Optional
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import Qt
|
|
6
|
+
from PySide6.QtGui import QFontMetrics
|
|
4
7
|
from PySide6.QtWidgets import (
|
|
5
|
-
|
|
6
|
-
|
|
8
|
+
QApplication,
|
|
9
|
+
QGroupBox,
|
|
7
10
|
QHBoxLayout,
|
|
8
|
-
QTextEdit,
|
|
9
|
-
QPushButton,
|
|
10
11
|
QLabel,
|
|
12
|
+
QMenu,
|
|
13
|
+
QPushButton,
|
|
11
14
|
QSizePolicy,
|
|
12
15
|
QSpinBox,
|
|
16
|
+
QSplitter,
|
|
13
17
|
QTableWidget,
|
|
14
18
|
QTableWidgetItem,
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
QApplication,
|
|
19
|
-
QMenu,
|
|
19
|
+
QTextEdit,
|
|
20
|
+
QVBoxLayout,
|
|
21
|
+
QWidget,
|
|
20
22
|
)
|
|
21
|
-
from PySide6.QtCore import Qt
|
|
22
|
-
from PySide6.QtGui import QFontMetrics
|
|
23
23
|
|
|
24
|
-
from vector_inspector.core.
|
|
24
|
+
from vector_inspector.core.cache_manager import get_cache_manager
|
|
25
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
26
|
+
from vector_inspector.core.logging import log_info
|
|
27
|
+
from vector_inspector.services.filter_service import apply_client_side_filters
|
|
25
28
|
from vector_inspector.ui.components.filter_builder import FilterBuilder
|
|
26
29
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
27
|
-
from vector_inspector.services.filter_service import apply_client_side_filters
|
|
28
|
-
from vector_inspector.core.cache_manager import get_cache_manager, CacheEntry
|
|
29
|
-
from vector_inspector.core.logging import log_info
|
|
30
30
|
|
|
31
31
|
|
|
32
32
|
class SearchView(QWidget):
|
|
33
33
|
"""View for performing similarity searches."""
|
|
34
34
|
|
|
35
|
-
def __init__(self, connection:
|
|
35
|
+
def __init__(self, connection: Optional[ConnectionInstance] = None, parent=None):
|
|
36
36
|
super().__init__(parent)
|
|
37
37
|
# Initialize all UI attributes to None to avoid AttributeError
|
|
38
38
|
self.breadcrumb_label = None
|
|
@@ -47,10 +47,11 @@ class SearchView(QWidget):
|
|
|
47
47
|
self.loading_dialog = None
|
|
48
48
|
self.cache_manager = None
|
|
49
49
|
|
|
50
|
+
# Expects a ConnectionInstance wrapper.
|
|
50
51
|
self.connection = connection
|
|
51
52
|
self.current_collection: str = ""
|
|
52
53
|
self.current_database: str = ""
|
|
53
|
-
self.search_results: Optional[
|
|
54
|
+
self.search_results: Optional[dict[str, Any]] = None
|
|
54
55
|
self.loading_dialog = LoadingDialog("Searching...", self)
|
|
55
56
|
self.cache_manager = get_cache_manager()
|
|
56
57
|
|
|
@@ -414,7 +415,7 @@ class SearchView(QWidget):
|
|
|
414
415
|
if not menu.isEmpty():
|
|
415
416
|
menu.exec(self.results_table.viewport().mapToGlobal(position))
|
|
416
417
|
|
|
417
|
-
def _display_results(self, results:
|
|
418
|
+
def _display_results(self, results: dict[str, Any]):
|
|
418
419
|
"""Display search results in table."""
|
|
419
420
|
ids = results.get("ids", [[]])[0]
|
|
420
421
|
documents = results.get("documents", [[]])[0]
|
|
@@ -1,28 +1,30 @@
|
|
|
1
1
|
"""Vector visualization view with dimensionality reduction."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
|
-
|
|
4
|
+
|
|
5
5
|
import traceback
|
|
6
|
+
from typing import Any, Optional
|
|
7
|
+
|
|
8
|
+
import numpy as np
|
|
9
|
+
from PySide6.QtCore import QThread, Signal
|
|
10
|
+
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
6
11
|
from PySide6.QtWidgets import (
|
|
7
|
-
|
|
8
|
-
QVBoxLayout,
|
|
9
|
-
QHBoxLayout,
|
|
10
|
-
QPushButton,
|
|
11
|
-
QLabel,
|
|
12
|
+
QApplication,
|
|
12
13
|
QComboBox,
|
|
13
|
-
QSpinBox,
|
|
14
14
|
QGroupBox,
|
|
15
|
+
QHBoxLayout,
|
|
16
|
+
QLabel,
|
|
15
17
|
QMessageBox,
|
|
16
|
-
|
|
18
|
+
QPushButton,
|
|
19
|
+
QSpinBox,
|
|
20
|
+
QVBoxLayout,
|
|
21
|
+
QWidget,
|
|
17
22
|
)
|
|
18
|
-
from PySide6.QtCore import QThread, Signal
|
|
19
|
-
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
20
|
-
import numpy as np
|
|
21
23
|
|
|
22
|
-
from vector_inspector.core.
|
|
24
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
25
|
+
from vector_inspector.core.logging import log_error
|
|
23
26
|
from vector_inspector.services.visualization_service import VisualizationService
|
|
24
27
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
25
|
-
from vector_inspector.core.logging import log_error
|
|
26
28
|
|
|
27
29
|
|
|
28
30
|
class VisualizationThread(QThread):
|
|
@@ -55,8 +57,9 @@ class VisualizationThread(QThread):
|
|
|
55
57
|
class VisualizationView(QWidget):
|
|
56
58
|
"""View for visualizing vectors in 2D/3D."""
|
|
57
59
|
|
|
58
|
-
def __init__(self, connection:
|
|
60
|
+
def __init__(self, connection: Optional[ConnectionInstance] = None, parent=None):
|
|
59
61
|
super().__init__(parent)
|
|
62
|
+
# Expects a ConnectionInstance wrapper.
|
|
60
63
|
self.connection = connection
|
|
61
64
|
self.current_collection: str = ""
|
|
62
65
|
self.current_data: dict[str, Any] | None = None
|
|
@@ -296,8 +299,8 @@ class VisualizationView(QWidget):
|
|
|
296
299
|
webbrowser.open(f"file://{self._last_temp_html}")
|
|
297
300
|
|
|
298
301
|
def cleanup_temp_html(self):
|
|
299
|
-
import os
|
|
300
302
|
import contextlib
|
|
303
|
+
import os
|
|
301
304
|
|
|
302
305
|
for f in getattr(self, "temp_html_files", []):
|
|
303
306
|
with contextlib.suppress(Exception):
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.12
|
|
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
|
|
@@ -26,14 +26,19 @@ Requires-Dist: keyring>=25.7.0
|
|
|
26
26
|
Requires-Dist: hf-xet>=1.2.0
|
|
27
27
|
Requires-Dist: pymilvus>=2.6.6
|
|
28
28
|
Requires-Dist: lancedb>=0.27.0
|
|
29
|
-
Requires-Dist: psycopg2>=2.9.11
|
|
29
|
+
Requires-Dist: psycopg2-binary>=2.9.11
|
|
30
30
|
Requires-Dist: pgvector>=0.4.2
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
33
|
# Latest updates
|
|
34
34
|
|
|
35
|
-
|
|
36
|
-
-
|
|
35
|
+
## Pinecone Hosted Model Support Improvements
|
|
36
|
+
- Direct Text Search: Added full support for Pinecone indexes with integrated (hosted) embedding models. You can now perform semantic search using plain text queries—no local embedding required.
|
|
37
|
+
- Accurate Model Detection: The app now detects and displays the hosted model name (e.g., llama-text-embed-v2) in the info panel for Pinecone indexes.
|
|
38
|
+
- Robust Query Handling: The search logic now uses Pinecone’s search() API for hosted models, with correct response parsing and error handling.
|
|
39
|
+
- Future-Proof: Retained the ability to generate embeddings via Pinecone’s inference API for future features or advanced workflows.
|
|
40
|
+
- Improved Error Handling: Added better checks and debug logging for Pinecone API responses.
|
|
41
|
+
- These changes make Pinecone integration seamless and future-ready for both text and vector search scenarios.
|
|
37
42
|
---
|
|
38
43
|
|
|
39
44
|
# Vector Inspector
|
|
@@ -49,6 +54,14 @@ Description-Content-Type: text/markdown
|
|
|
49
54
|
|
|
50
55
|
A comprehensive desktop application for visualizing, querying, and managing vector database data. Similar to SQL database viewers, Vector Inspector provides an intuitive GUI for exploring vector embeddings, metadata, and performing similarity searches across multiple vector database providers.
|
|
51
56
|
|
|
57
|
+
<p align="center">
|
|
58
|
+
<a href="site/images/demo.gif" target="_blank">
|
|
59
|
+
<img src="site/images/demo.gif" alt="Vector Inspector Demo" width="600"/>
|
|
60
|
+
</a>
|
|
61
|
+
</p>
|
|
62
|
+
|
|
63
|
+
**Quick Demo:** See Vector Inspector in action!
|
|
64
|
+
|
|
52
65
|
## Overview
|
|
53
66
|
|
|
54
67
|
Vector Inspector bridges the gap between vector databases and user-friendly data exploration tools. While vector databases are powerful for semantic search and AI applications, they often lack the intuitive inspection and management tools that traditional SQL databases have. This project aims to provide that missing layer.
|