vector-inspector 0.3.5__tar.gz → 0.3.7__tar.gz
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-0.3.5 → vector_inspector-0.3.7}/PKG-INFO +8 -4
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/README.md +7 -3
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/pyproject.toml +1 -1
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/chroma_connection.py +4 -1
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/pgvector_connection.py +108 -93
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/pinecone_connection.py +4 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/qdrant_connection.py +13 -0
- vector_inspector-0.3.7/src/vector_inspector/services/update_service.py +73 -0
- vector_inspector-0.3.7/src/vector_inspector/ui/components/update_details_dialog.py +47 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/main_window.py +74 -2
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/metadata_view.py +72 -0
- vector_inspector-0.3.7/src/vector_inspector/utils/version.py +12 -0
- vector_inspector-0.3.7/tests/test_chroma_connection.py +105 -0
- vector_inspector-0.3.7/tests/test_pgvector_connection.py +91 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_pinecone_connection.py +114 -98
- vector_inspector-0.3.7/tests/test_qdrant_connection.py +128 -0
- vector_inspector-0.3.5/src/vector_inspector/utils/version.py +0 -5
- vector_inspector-0.3.5/tests/test_connections.py +0 -71
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/__main__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/config/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/config/known_embedding_models.json +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/cache_manager.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connection_manager.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/base_connection.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/qdrant_helpers/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/connections/template_connection.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_providers/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_providers/base_provider.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_providers/clip_provider.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_providers/provider_factory.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_providers/sentence_transformer_provider.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/embedding_utils.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/logging.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/core/model_registry.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/main.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/backup_helpers.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/backup_restore_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/credential_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/filter_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/import_export_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/profile_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/settings_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/services/visualization_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/backup_restore_dialog.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/connection_manager_panel.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/filter_builder.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/item_dialog.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/loading_dialog.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/profile_manager_panel.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/components/splash_window.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/dialogs/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/dialogs/cross_db_migration.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/dialogs/embedding_config_dialog.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/dialogs/provider_type_dialog.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/collection_browser.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/connection_view.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/info_panel.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/search_view.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/ui/views/visualization_view.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/utils/__init__.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/src/vector_inspector/utils/lazy_imports.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_backup_helpers.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_backup_restore_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_filter_service.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_runner.py +0 -0
- {vector_inspector-0.3.5 → vector_inspector-0.3.7}/tests/test_settings_service.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.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
|
|
@@ -31,9 +31,13 @@ Requires-Dist: pgvector>=0.4.2
|
|
|
31
31
|
Description-Content-Type: text/markdown
|
|
32
32
|
|
|
33
33
|
# Latest updates
|
|
34
|
-
|
|
35
|
-
-
|
|
36
|
-
-
|
|
34
|
+
* Added automatic update notification system:
|
|
35
|
+
- Checks for new releases on GitHub once per launch/day
|
|
36
|
+
- Shows update indicator in the status bar and Help menu
|
|
37
|
+
- Clickable indicator opens a modal with release notes and update instructions
|
|
38
|
+
- Manual Check for Update in Help menu
|
|
39
|
+
- Version detection now uses installed package metadata for accuracy
|
|
40
|
+
* Fixed bug where data browser row disappeared post-edit
|
|
37
41
|
---
|
|
38
42
|
|
|
39
43
|
# Vector Inspector
|
|
@@ -1,7 +1,11 @@
|
|
|
1
1
|
# Latest updates
|
|
2
|
-
|
|
3
|
-
-
|
|
4
|
-
-
|
|
2
|
+
* Added automatic update notification system:
|
|
3
|
+
- Checks for new releases on GitHub once per launch/day
|
|
4
|
+
- Shows update indicator in the status bar and Help menu
|
|
5
|
+
- Clickable indicator opens a modal with release notes and update instructions
|
|
6
|
+
- Manual Check for Update in Help menu
|
|
7
|
+
- Version detection now uses installed package metadata for accuracy
|
|
8
|
+
* Fixed bug where data browser row disappeared post-edit
|
|
5
9
|
---
|
|
6
10
|
|
|
7
11
|
# Vector Inspector
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vector-inspector"
|
|
3
|
-
version = "0.3.
|
|
3
|
+
version = "0.3.7"
|
|
4
4
|
description = "A comprehensive desktop application for visualizing, querying, and managing vector database data"
|
|
5
5
|
authors = [
|
|
6
6
|
{ name = "Anthony Dawson", email = "anthonypdawson+github@gmail.com" },
|
|
@@ -489,7 +489,7 @@ class ChromaDBConnection(VectorDBConnection):
|
|
|
489
489
|
name: Collection name
|
|
490
490
|
|
|
491
491
|
Returns:
|
|
492
|
-
True if successful, False otherwise
|
|
492
|
+
True if successful or collection does not exist, False otherwise
|
|
493
493
|
"""
|
|
494
494
|
if not self._client:
|
|
495
495
|
return False
|
|
@@ -500,6 +500,9 @@ class ChromaDBConnection(VectorDBConnection):
|
|
|
500
500
|
self._current_collection = None
|
|
501
501
|
return True
|
|
502
502
|
except Exception as e:
|
|
503
|
+
# If the exception is about the collection not existing, treat as success (idempotent)
|
|
504
|
+
if "does not exist" in str(e).lower():
|
|
505
|
+
return True
|
|
503
506
|
log_error("Failed to delete collection: %s", e)
|
|
504
507
|
return False
|
|
505
508
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""PgVector/PostgreSQL connection manager."""
|
|
2
2
|
|
|
3
|
-
from typing import
|
|
3
|
+
from typing import Any
|
|
4
4
|
import json
|
|
5
5
|
import psycopg2
|
|
6
6
|
from psycopg2 import sql
|
|
@@ -36,7 +36,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
36
36
|
self.database = database
|
|
37
37
|
self.user = user
|
|
38
38
|
self.password = password
|
|
39
|
-
self._client
|
|
39
|
+
self._client = None
|
|
40
40
|
# Track how many embeddings were regenerated by the last update operation
|
|
41
41
|
self._last_regenerated_count: int = 0
|
|
42
42
|
|
|
@@ -94,7 +94,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
94
94
|
"""Check if connected to PostgreSQL."""
|
|
95
95
|
return self._client is not None
|
|
96
96
|
|
|
97
|
-
def list_collections(self) ->
|
|
97
|
+
def list_collections(self) -> list[str]:
|
|
98
98
|
"""
|
|
99
99
|
Get list of all vector tables (collections).
|
|
100
100
|
|
|
@@ -117,7 +117,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
117
117
|
log_error("Failed to list collections: %s", e)
|
|
118
118
|
return []
|
|
119
119
|
|
|
120
|
-
def list_databases(self) ->
|
|
120
|
+
def list_databases(self) -> list[str]:
|
|
121
121
|
"""
|
|
122
122
|
List available databases on the server (non-template databases).
|
|
123
123
|
|
|
@@ -155,7 +155,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
155
155
|
except Exception:
|
|
156
156
|
pass
|
|
157
157
|
|
|
158
|
-
def get_collection_info(self, name: str) ->
|
|
158
|
+
def get_collection_info(self, name: str) -> dict[str, Any] | None:
|
|
159
159
|
"""
|
|
160
160
|
Get collection metadata and statistics.
|
|
161
161
|
|
|
@@ -291,10 +291,10 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
291
291
|
def add_items(
|
|
292
292
|
self,
|
|
293
293
|
collection_name: str,
|
|
294
|
-
documents:
|
|
295
|
-
metadatas:
|
|
296
|
-
ids:
|
|
297
|
-
embeddings:
|
|
294
|
+
documents: list[str],
|
|
295
|
+
metadatas: list[dict[str, Any]] | None = None,
|
|
296
|
+
ids: list[str] | None = None,
|
|
297
|
+
embeddings: list[list[float]] | None = None,
|
|
298
298
|
) -> bool:
|
|
299
299
|
"""
|
|
300
300
|
Add items to a collection.
|
|
@@ -312,66 +312,16 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
312
312
|
if not self._client:
|
|
313
313
|
return False
|
|
314
314
|
|
|
315
|
-
# If embeddings weren't provided, try to compute them using
|
|
315
|
+
# If embeddings weren't provided, try to compute them using the helper method
|
|
316
316
|
if not embeddings:
|
|
317
317
|
try:
|
|
318
|
-
|
|
319
|
-
from vector_inspector.core.embedding_utils import (
|
|
320
|
-
load_embedding_model,
|
|
321
|
-
get_embedding_model_for_dimension,
|
|
322
|
-
DEFAULT_MODEL,
|
|
323
|
-
encode_text,
|
|
324
|
-
)
|
|
325
|
-
|
|
326
|
-
model_name = None
|
|
327
|
-
model_type = None
|
|
328
|
-
|
|
329
|
-
# 1) settings
|
|
330
|
-
settings = SettingsService()
|
|
331
|
-
model_info = settings.get_embedding_model(self.database, collection_name)
|
|
332
|
-
if model_info:
|
|
333
|
-
model_name = model_info.get("model")
|
|
334
|
-
model_type = model_info.get("type", "sentence-transformer")
|
|
335
|
-
|
|
336
|
-
# 2) collection metadata
|
|
337
|
-
coll_info = None
|
|
338
|
-
if not model_name:
|
|
339
|
-
coll_info = self.get_collection_info(collection_name)
|
|
340
|
-
if coll_info and coll_info.get("embedding_model"):
|
|
341
|
-
model_name = coll_info.get("embedding_model")
|
|
342
|
-
model_type = coll_info.get("embedding_model_type", "stored")
|
|
343
|
-
|
|
344
|
-
# 3) dimension-based fallback
|
|
345
|
-
loaded_model = None
|
|
346
|
-
if not model_name:
|
|
347
|
-
# Try to get vector dimension
|
|
348
|
-
dim = None
|
|
349
|
-
if not coll_info:
|
|
350
|
-
coll_info = self.get_collection_info(collection_name)
|
|
351
|
-
if coll_info and coll_info.get("vector_dimension"):
|
|
352
|
-
try:
|
|
353
|
-
dim = int(coll_info.get("vector_dimension"))
|
|
354
|
-
except Exception:
|
|
355
|
-
dim = None
|
|
356
|
-
if dim:
|
|
357
|
-
loaded_model, model_name, model_type = get_embedding_model_for_dimension(
|
|
358
|
-
dim
|
|
359
|
-
)
|
|
360
|
-
else:
|
|
361
|
-
model_name, model_type = DEFAULT_MODEL
|
|
362
|
-
|
|
363
|
-
# Load model
|
|
364
|
-
if not loaded_model:
|
|
365
|
-
loaded_model = load_embedding_model(model_name, model_type)
|
|
366
|
-
|
|
367
|
-
# Compute embeddings for all documents
|
|
368
|
-
if model_type != "clip":
|
|
369
|
-
embeddings = loaded_model.encode(documents, show_progress_bar=False).tolist()
|
|
370
|
-
else:
|
|
371
|
-
embeddings = [encode_text(d, loaded_model, model_type) for d in documents]
|
|
318
|
+
embeddings = self.compute_embeddings_for_documents(collection_name, documents)
|
|
372
319
|
except Exception as e:
|
|
373
320
|
log_error("Failed to compute embeddings on add: %s", e)
|
|
374
321
|
return False
|
|
322
|
+
if embeddings is None:
|
|
323
|
+
return False
|
|
324
|
+
|
|
375
325
|
try:
|
|
376
326
|
import uuid
|
|
377
327
|
|
|
@@ -428,7 +378,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
428
378
|
self._client.rollback()
|
|
429
379
|
return False
|
|
430
380
|
|
|
431
|
-
def get_items(self, name: str, ids:
|
|
381
|
+
def get_items(self, name: str, ids: list[str]) -> dict[str, Any]:
|
|
432
382
|
"""
|
|
433
383
|
Retrieve items by IDs.
|
|
434
384
|
|
|
@@ -548,12 +498,12 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
548
498
|
def query_collection(
|
|
549
499
|
self,
|
|
550
500
|
collection_name: str,
|
|
551
|
-
query_texts:
|
|
552
|
-
query_embeddings:
|
|
501
|
+
query_texts: list[str] | None = None,
|
|
502
|
+
query_embeddings: list[list[float]] | None = None,
|
|
553
503
|
n_results: int = 10,
|
|
554
|
-
where:
|
|
555
|
-
where_document:
|
|
556
|
-
) ->
|
|
504
|
+
where: dict[str, Any] | None = None,
|
|
505
|
+
where_document: dict[str, Any] | None = None,
|
|
506
|
+
) -> dict[str, Any] | None:
|
|
557
507
|
"""
|
|
558
508
|
Query a collection for similar vectors.
|
|
559
509
|
|
|
@@ -601,6 +551,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
601
551
|
# 3) dimension-based fallback
|
|
602
552
|
loaded_model = None
|
|
603
553
|
if not model_name:
|
|
554
|
+
# Try to get vector dimension
|
|
604
555
|
dim = None
|
|
605
556
|
coll_info = self.get_collection_info(collection_name)
|
|
606
557
|
if coll_info and coll_info.get("vector_dimension"):
|
|
@@ -613,6 +564,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
613
564
|
dim
|
|
614
565
|
)
|
|
615
566
|
else:
|
|
567
|
+
# Use default model
|
|
616
568
|
model_name, model_type = DEFAULT_MODEL
|
|
617
569
|
|
|
618
570
|
if not loaded_model:
|
|
@@ -636,11 +588,11 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
636
588
|
# so callers receive the top-N results per query (matching SearchView expectations).
|
|
637
589
|
with self._client.cursor() as cur:
|
|
638
590
|
# Prepare containers for per-query results
|
|
639
|
-
per_ids
|
|
640
|
-
per_docs
|
|
641
|
-
per_metas
|
|
642
|
-
per_embeds
|
|
643
|
-
per_dists
|
|
591
|
+
per_ids = []
|
|
592
|
+
per_docs = []
|
|
593
|
+
per_metas = []
|
|
594
|
+
per_embeds = []
|
|
595
|
+
per_dists = []
|
|
644
596
|
|
|
645
597
|
for emb in query_embeddings:
|
|
646
598
|
# Build base query for this single embedding
|
|
@@ -675,11 +627,11 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
675
627
|
colnames = [desc[0] for desc in cur.description]
|
|
676
628
|
|
|
677
629
|
# Build per-query result lists
|
|
678
|
-
ids_q
|
|
679
|
-
docs_q
|
|
680
|
-
metas_q
|
|
681
|
-
embeds_q
|
|
682
|
-
dists_q
|
|
630
|
+
ids_q = []
|
|
631
|
+
docs_q = []
|
|
632
|
+
metas_q = []
|
|
633
|
+
embeds_q = []
|
|
634
|
+
dists_q = []
|
|
683
635
|
|
|
684
636
|
for row in rows:
|
|
685
637
|
row_dict = dict(zip(colnames, row))
|
|
@@ -731,10 +683,10 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
731
683
|
def get_all_items(
|
|
732
684
|
self,
|
|
733
685
|
collection_name: str,
|
|
734
|
-
limit:
|
|
735
|
-
offset:
|
|
736
|
-
where:
|
|
737
|
-
) ->
|
|
686
|
+
limit: int | None = None,
|
|
687
|
+
offset: int | None = None,
|
|
688
|
+
where: dict[str, Any] | None = None,
|
|
689
|
+
) -> dict[str, Any] | None:
|
|
738
690
|
"""
|
|
739
691
|
Get all items from a collection.
|
|
740
692
|
|
|
@@ -835,10 +787,10 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
835
787
|
def update_items(
|
|
836
788
|
self,
|
|
837
789
|
collection_name: str,
|
|
838
|
-
ids:
|
|
839
|
-
documents:
|
|
840
|
-
metadatas:
|
|
841
|
-
embeddings:
|
|
790
|
+
ids: list[str],
|
|
791
|
+
documents: list[str] | None = None,
|
|
792
|
+
metadatas: list[dict[str, Any]] | None = None,
|
|
793
|
+
embeddings: list[list[float]] | None = None,
|
|
842
794
|
) -> bool:
|
|
843
795
|
"""
|
|
844
796
|
Update items in a collection.
|
|
@@ -1002,8 +954,8 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
1002
954
|
def delete_items(
|
|
1003
955
|
self,
|
|
1004
956
|
collection_name: str,
|
|
1005
|
-
ids:
|
|
1006
|
-
where:
|
|
957
|
+
ids: list[str] | None = None,
|
|
958
|
+
where: dict[str, Any] | None = None,
|
|
1007
959
|
) -> bool:
|
|
1008
960
|
"""
|
|
1009
961
|
Delete items from a collection.
|
|
@@ -1034,7 +986,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
1034
986
|
self._client.rollback()
|
|
1035
987
|
return False
|
|
1036
988
|
|
|
1037
|
-
def get_connection_info(self) ->
|
|
989
|
+
def get_connection_info(self) -> dict[str, Any]:
|
|
1038
990
|
"""
|
|
1039
991
|
Get information about the current connection.
|
|
1040
992
|
|
|
@@ -1050,7 +1002,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
1050
1002
|
"connected": self.is_connected,
|
|
1051
1003
|
}
|
|
1052
1004
|
|
|
1053
|
-
def _get_table_schema(self, table_name: str) ->
|
|
1005
|
+
def _get_table_schema(self, table_name: str) -> dict[str, str]:
|
|
1054
1006
|
"""
|
|
1055
1007
|
Get the schema (column names and types) for a table.
|
|
1056
1008
|
|
|
@@ -1081,7 +1033,7 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
1081
1033
|
log_error("Failed to get table schema: %s", e)
|
|
1082
1034
|
return {}
|
|
1083
1035
|
|
|
1084
|
-
def _parse_vector(self, vector_str: Any) ->
|
|
1036
|
+
def _parse_vector(self, vector_str: Any) -> list[float]:
|
|
1085
1037
|
"""
|
|
1086
1038
|
Parse pgvector string format to Python list.
|
|
1087
1039
|
|
|
@@ -1098,3 +1050,66 @@ class PgVectorConnection(VectorDBConnection):
|
|
|
1098
1050
|
vector_str = vector_str.strip("[]")
|
|
1099
1051
|
return [float(x) for x in vector_str.split(",")]
|
|
1100
1052
|
return []
|
|
1053
|
+
|
|
1054
|
+
def compute_embeddings_for_documents(
|
|
1055
|
+
self, collection_name: str, documents: list[str]
|
|
1056
|
+
) -> list[list[float]] | None:
|
|
1057
|
+
"""
|
|
1058
|
+
Compute embeddings for a list of documents using the configured/default model for the collection.
|
|
1059
|
+
Returns a list of embeddings, or None on failure.
|
|
1060
|
+
"""
|
|
1061
|
+
try:
|
|
1062
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
1063
|
+
from vector_inspector.core.embedding_utils import (
|
|
1064
|
+
load_embedding_model,
|
|
1065
|
+
get_embedding_model_for_dimension,
|
|
1066
|
+
DEFAULT_MODEL,
|
|
1067
|
+
encode_text,
|
|
1068
|
+
)
|
|
1069
|
+
|
|
1070
|
+
model_name = None
|
|
1071
|
+
model_type = None
|
|
1072
|
+
|
|
1073
|
+
# 1) settings
|
|
1074
|
+
settings = SettingsService()
|
|
1075
|
+
model_info = settings.get_embedding_model(self.database, collection_name)
|
|
1076
|
+
if model_info:
|
|
1077
|
+
model_name = model_info.get("model")
|
|
1078
|
+
model_type = model_info.get("type", "sentence-transformer")
|
|
1079
|
+
|
|
1080
|
+
# 2) collection metadata
|
|
1081
|
+
if not model_name:
|
|
1082
|
+
coll_info = self.get_collection_info(collection_name)
|
|
1083
|
+
if coll_info and coll_info.get("embedding_model"):
|
|
1084
|
+
model_name = coll_info.get("embedding_model")
|
|
1085
|
+
model_type = coll_info.get("embedding_model_type", "stored")
|
|
1086
|
+
|
|
1087
|
+
# 3) dimension-based fallback
|
|
1088
|
+
loaded_model = None
|
|
1089
|
+
if not model_name:
|
|
1090
|
+
# Try to get vector dimension
|
|
1091
|
+
dim = None
|
|
1092
|
+
coll_info = self.get_collection_info(collection_name)
|
|
1093
|
+
if coll_info and coll_info.get("vector_dimension"):
|
|
1094
|
+
try:
|
|
1095
|
+
dim = int(coll_info.get("vector_dimension"))
|
|
1096
|
+
except Exception:
|
|
1097
|
+
dim = None
|
|
1098
|
+
if dim:
|
|
1099
|
+
loaded_model, model_name, model_type = get_embedding_model_for_dimension(dim)
|
|
1100
|
+
else:
|
|
1101
|
+
model_name, model_type = DEFAULT_MODEL
|
|
1102
|
+
|
|
1103
|
+
# Load model
|
|
1104
|
+
if not loaded_model:
|
|
1105
|
+
loaded_model = load_embedding_model(model_name, model_type)
|
|
1106
|
+
|
|
1107
|
+
# Compute embeddings for all documents
|
|
1108
|
+
if model_type != "clip":
|
|
1109
|
+
embeddings = loaded_model.encode(documents, show_progress_bar=False).tolist()
|
|
1110
|
+
else:
|
|
1111
|
+
embeddings = [encode_text(d, loaded_model, model_type) for d in documents]
|
|
1112
|
+
return embeddings
|
|
1113
|
+
except Exception as e:
|
|
1114
|
+
log_error("Failed to compute embeddings: %s", e)
|
|
1115
|
+
return None
|
|
@@ -239,6 +239,10 @@ class PineconeConnection(VectorDBConnection):
|
|
|
239
239
|
log_error("Embeddings are required for Pinecone and computing them failed: %s", e)
|
|
240
240
|
return False
|
|
241
241
|
|
|
242
|
+
if not embeddings:
|
|
243
|
+
log_error("Embeddings are required for Pinecone but none were provided or computed")
|
|
244
|
+
return False
|
|
245
|
+
|
|
242
246
|
index = self._get_index(collection_name)
|
|
243
247
|
if not index:
|
|
244
248
|
return False
|
|
@@ -512,6 +512,19 @@ class QdrantConnection(VectorDBConnection):
|
|
|
512
512
|
"""
|
|
513
513
|
if not self._client:
|
|
514
514
|
return False
|
|
515
|
+
# Reject empty document lists early
|
|
516
|
+
if not documents:
|
|
517
|
+
return False
|
|
518
|
+
|
|
519
|
+
# If embeddings provided, ensure counts match
|
|
520
|
+
if embeddings is not None and len(embeddings) != len(documents):
|
|
521
|
+
log_error(
|
|
522
|
+
"Embeddings length (%d) does not match documents length (%d) for collection %s",
|
|
523
|
+
len(embeddings),
|
|
524
|
+
len(documents),
|
|
525
|
+
collection_name,
|
|
526
|
+
)
|
|
527
|
+
return False
|
|
515
528
|
|
|
516
529
|
# If embeddings not provided, compute using model resolution helper
|
|
517
530
|
if not embeddings and documents:
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import json
|
|
3
|
+
import time
|
|
4
|
+
import requests
|
|
5
|
+
from typing import Optional, Dict
|
|
6
|
+
|
|
7
|
+
GITHUB_API_URL = "https://api.github.com/repos/anthonypdawson/vector-inspector/releases/latest"
|
|
8
|
+
CACHE_FILE = os.path.expanduser("~/.vector_inspector_update_cache.json")
|
|
9
|
+
CACHE_TTL = 24 * 60 * 60 # 1 day in seconds
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class UpdateService:
|
|
13
|
+
@staticmethod
|
|
14
|
+
def get_latest_release(force_refresh: bool = False) -> Optional[Dict]:
|
|
15
|
+
"""
|
|
16
|
+
Fetch the latest release info from GitHub, with caching and rate limit handling.
|
|
17
|
+
Returns None on error or if rate limited.
|
|
18
|
+
"""
|
|
19
|
+
now = int(time.time())
|
|
20
|
+
# Check cache for rate limit state or valid release
|
|
21
|
+
if os.path.exists(CACHE_FILE):
|
|
22
|
+
try:
|
|
23
|
+
with open(CACHE_FILE, "r", encoding="utf-8") as f:
|
|
24
|
+
cache = json.load(f)
|
|
25
|
+
# If rate limited, respect the reset time
|
|
26
|
+
if cache.get("rate_limited_until", 0) > now:
|
|
27
|
+
return None
|
|
28
|
+
if not force_refresh and now - cache.get("timestamp", 0) < CACHE_TTL:
|
|
29
|
+
return cache.get("release")
|
|
30
|
+
except Exception:
|
|
31
|
+
pass
|
|
32
|
+
try:
|
|
33
|
+
resp = requests.get(GITHUB_API_URL, timeout=5)
|
|
34
|
+
if resp.status_code == 200:
|
|
35
|
+
release = resp.json()
|
|
36
|
+
with open(CACHE_FILE, "w", encoding="utf-8") as f:
|
|
37
|
+
json.dump({"timestamp": now, "release": release}, f)
|
|
38
|
+
return release
|
|
39
|
+
elif resp.status_code == 403:
|
|
40
|
+
# Check for rate limit headers
|
|
41
|
+
reset = resp.headers.get("X-RateLimit-Reset")
|
|
42
|
+
if reset:
|
|
43
|
+
rate_limited_until = int(reset)
|
|
44
|
+
else:
|
|
45
|
+
# Default to 1 hour if no header
|
|
46
|
+
rate_limited_until = now + 3600
|
|
47
|
+
with open(CACHE_FILE, "w", encoding="utf-8") as f:
|
|
48
|
+
json.dump({"rate_limited_until": rate_limited_until}, f)
|
|
49
|
+
return None
|
|
50
|
+
except Exception:
|
|
51
|
+
pass
|
|
52
|
+
return None
|
|
53
|
+
|
|
54
|
+
@staticmethod
|
|
55
|
+
def compare_versions(current_version: str, latest_version: str) -> bool:
|
|
56
|
+
"""
|
|
57
|
+
Returns True if latest_version is newer than current_version.
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
def parse(v):
|
|
61
|
+
return [int(x) for x in v.strip("v").split(".") if x.isdigit()]
|
|
62
|
+
|
|
63
|
+
return parse(latest_version) > parse(current_version)
|
|
64
|
+
|
|
65
|
+
@staticmethod
|
|
66
|
+
def get_update_instructions() -> Dict[str, str]:
|
|
67
|
+
"""
|
|
68
|
+
Returns update instructions for both PyPI and GitHub.
|
|
69
|
+
"""
|
|
70
|
+
return {
|
|
71
|
+
"pip": "pip install --upgrade vector-inspector",
|
|
72
|
+
"github": "https://github.com/anthonypdawson/vector-inspector/releases/latest",
|
|
73
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QPushButton, QTextEdit, QHBoxLayout
|
|
2
|
+
from PySide6.QtCore import Qt
|
|
3
|
+
from PySide6.QtGui import QDesktopServices, QCursor
|
|
4
|
+
from PySide6.QtCore import QUrl
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class UpdateDetailsDialog(QDialog):
|
|
8
|
+
def __init__(
|
|
9
|
+
self, version: str, release_notes: str, pip_command: str, github_url: str, parent=None
|
|
10
|
+
):
|
|
11
|
+
super().__init__(parent)
|
|
12
|
+
self.setWindowTitle(f"Update Available: v{version}")
|
|
13
|
+
self.setMinimumWidth(500)
|
|
14
|
+
layout = QVBoxLayout(self)
|
|
15
|
+
|
|
16
|
+
title = QLabel(f"<b>New version available: v{version}</b>")
|
|
17
|
+
title.setAlignment(Qt.AlignCenter)
|
|
18
|
+
layout.addWidget(title)
|
|
19
|
+
|
|
20
|
+
notes_label = QLabel("<b>Release Notes:</b>")
|
|
21
|
+
layout.addWidget(notes_label)
|
|
22
|
+
|
|
23
|
+
notes = QTextEdit()
|
|
24
|
+
notes.setReadOnly(True)
|
|
25
|
+
notes.setPlainText(release_notes)
|
|
26
|
+
layout.addWidget(notes)
|
|
27
|
+
|
|
28
|
+
# Add vertical space before update instructions
|
|
29
|
+
from PySide6.QtWidgets import QSpacerItem, QSizePolicy
|
|
30
|
+
|
|
31
|
+
layout.addSpacing(16)
|
|
32
|
+
|
|
33
|
+
pip_label = QLabel(f"<b>Update with pip:</b> <code>{pip_command}</code>")
|
|
34
|
+
layout.addWidget(pip_label)
|
|
35
|
+
|
|
36
|
+
btn_layout = QHBoxLayout()
|
|
37
|
+
github_btn = QPushButton("View on GitHub")
|
|
38
|
+
github_btn.clicked.connect(lambda: QDesktopServices.openUrl(QUrl(github_url)))
|
|
39
|
+
btn_layout.addWidget(github_btn)
|
|
40
|
+
|
|
41
|
+
close_btn = QPushButton("Close")
|
|
42
|
+
close_btn.clicked.connect(self.accept)
|
|
43
|
+
btn_layout.addWidget(close_btn)
|
|
44
|
+
|
|
45
|
+
layout.addLayout(btn_layout)
|
|
46
|
+
|
|
47
|
+
self.setLayout(layout)
|
|
@@ -211,10 +211,28 @@ class MainWindow(QMainWindow):
|
|
|
211
211
|
|
|
212
212
|
# Help menu
|
|
213
213
|
help_menu = menubar.addMenu("&Help")
|
|
214
|
-
|
|
215
214
|
about_action = QAction("&About", self)
|
|
216
215
|
about_action.triggered.connect(self._show_about)
|
|
217
216
|
help_menu.addAction(about_action)
|
|
217
|
+
check_update_action = QAction("Check for Update", self)
|
|
218
|
+
check_update_action.triggered.connect(self._check_for_update_from_menu)
|
|
219
|
+
help_menu.addAction(check_update_action)
|
|
220
|
+
|
|
221
|
+
def _check_for_update_from_menu(self):
|
|
222
|
+
from vector_inspector.services.update_service import UpdateService
|
|
223
|
+
from vector_inspector.utils.version import get_app_version
|
|
224
|
+
from PySide6.QtWidgets import QMessageBox
|
|
225
|
+
|
|
226
|
+
latest = UpdateService.get_latest_release(force_refresh=True)
|
|
227
|
+
if latest:
|
|
228
|
+
current_version = get_app_version()
|
|
229
|
+
latest_version = latest.get("tag_name")
|
|
230
|
+
if latest_version and UpdateService.compare_versions(current_version, latest_version):
|
|
231
|
+
# Show update modal
|
|
232
|
+
self._latest_release = latest
|
|
233
|
+
self._on_update_indicator_clicked(None)
|
|
234
|
+
return
|
|
235
|
+
QMessageBox.information(self, "Check for Update", "No update available.")
|
|
218
236
|
|
|
219
237
|
def _setup_toolbar(self):
|
|
220
238
|
"""Setup application toolbar."""
|
|
@@ -233,7 +251,7 @@ class MainWindow(QMainWindow):
|
|
|
233
251
|
toolbar.addAction(refresh_action)
|
|
234
252
|
|
|
235
253
|
def _setup_statusbar(self):
|
|
236
|
-
"""Setup status bar with connection breadcrumb."""
|
|
254
|
+
"""Setup status bar with connection breadcrumb and update indicator."""
|
|
237
255
|
status_bar = QStatusBar()
|
|
238
256
|
self.setStatusBar(status_bar)
|
|
239
257
|
|
|
@@ -241,8 +259,62 @@ class MainWindow(QMainWindow):
|
|
|
241
259
|
self.breadcrumb_label = QLabel("No active connection")
|
|
242
260
|
self.statusBar().addPermanentWidget(self.breadcrumb_label)
|
|
243
261
|
|
|
262
|
+
# Update indicator label (hidden by default)
|
|
263
|
+
self.update_indicator = QLabel()
|
|
264
|
+
self.update_indicator.setText("")
|
|
265
|
+
self.update_indicator.setStyleSheet(
|
|
266
|
+
"color: #2980b9; font-weight: bold; text-decoration: underline;"
|
|
267
|
+
)
|
|
268
|
+
self.update_indicator.setVisible(False)
|
|
269
|
+
self.update_indicator.setCursor(Qt.PointingHandCursor)
|
|
270
|
+
self.statusBar().addPermanentWidget(self.update_indicator)
|
|
271
|
+
|
|
244
272
|
self.statusBar().showMessage("Ready")
|
|
245
273
|
|
|
274
|
+
# Connect click event
|
|
275
|
+
self.update_indicator.mousePressEvent = self._on_update_indicator_clicked
|
|
276
|
+
|
|
277
|
+
# Check for updates on launch
|
|
278
|
+
from vector_inspector.services.update_service import UpdateService
|
|
279
|
+
from vector_inspector.utils.version import get_app_version
|
|
280
|
+
import threading
|
|
281
|
+
|
|
282
|
+
from PySide6.QtCore import QTimer
|
|
283
|
+
|
|
284
|
+
def check_updates():
|
|
285
|
+
latest = UpdateService.get_latest_release()
|
|
286
|
+
if latest:
|
|
287
|
+
current_version = get_app_version()
|
|
288
|
+
latest_version = latest.get("tag_name")
|
|
289
|
+
if latest_version and UpdateService.compare_versions(
|
|
290
|
+
current_version, latest_version
|
|
291
|
+
):
|
|
292
|
+
|
|
293
|
+
def show_update():
|
|
294
|
+
self._latest_release = latest
|
|
295
|
+
self.update_indicator.setText(f"Update available: v{latest_version}")
|
|
296
|
+
self.update_indicator.setVisible(True)
|
|
297
|
+
|
|
298
|
+
QTimer.singleShot(0, show_update)
|
|
299
|
+
|
|
300
|
+
threading.Thread(target=check_updates, daemon=True).start()
|
|
301
|
+
|
|
302
|
+
def _on_update_indicator_clicked(self, event):
|
|
303
|
+
# Show update details dialog
|
|
304
|
+
if not hasattr(self, "_latest_release"):
|
|
305
|
+
return
|
|
306
|
+
from vector_inspector.ui.components.update_details_dialog import UpdateDetailsDialog
|
|
307
|
+
from vector_inspector.services.update_service import UpdateService
|
|
308
|
+
|
|
309
|
+
latest = self._latest_release
|
|
310
|
+
version = latest.get("tag_name", "?")
|
|
311
|
+
notes = latest.get("body", "")
|
|
312
|
+
instructions = UpdateService.get_update_instructions()
|
|
313
|
+
pip_cmd = instructions["pip"]
|
|
314
|
+
github_url = instructions["github"]
|
|
315
|
+
dlg = UpdateDetailsDialog(version, notes, pip_cmd, github_url, self)
|
|
316
|
+
dlg.exec()
|
|
317
|
+
|
|
246
318
|
def _connect_signals(self):
|
|
247
319
|
"""Connect signals between components."""
|
|
248
320
|
# Connection manager signals
|