vector-inspector 0.2.3__tar.gz → 0.2.4__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.2.3 → vector_inspector-0.2.4}/PKG-INFO +1 -1
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/pyproject.toml +1 -1
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/import_export_service.py +30 -14
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/metadata_view.py +99 -26
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/README.md +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/__main__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/base_connection.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/chroma_connection.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/qdrant_connection.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/template_connection.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/main.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/backup_restore_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/filter_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/settings_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/visualization_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/backup_restore_dialog.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/filter_builder.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/item_dialog.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/loading_dialog.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/main_window.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/__init__.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/collection_browser.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/connection_view.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/search_view.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/visualization_view.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/tests/test_connections.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/tests/test_filter_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/tests/test_settings_service.py +0 -0
- {vector_inspector-0.2.3 → vector_inspector-0.2.4}/tests/vector_inspector.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.4
|
|
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
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vector-inspector"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.4"
|
|
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" },
|
|
@@ -5,6 +5,7 @@ import csv
|
|
|
5
5
|
from typing import Dict, Any, List, Optional
|
|
6
6
|
from pathlib import Path
|
|
7
7
|
import pandas as pd
|
|
8
|
+
import numpy as np
|
|
8
9
|
|
|
9
10
|
|
|
10
11
|
class ImportExportService:
|
|
@@ -36,9 +37,13 @@ class ImportExportService:
|
|
|
36
37
|
"document": documents[i] if i < len(documents) else None,
|
|
37
38
|
"metadata": metadatas[i] if i < len(metadatas) else {},
|
|
38
39
|
}
|
|
39
|
-
# Optionally include embeddings
|
|
40
|
-
if embeddings and i < len(embeddings):
|
|
41
|
-
|
|
40
|
+
# Optionally include embeddings (convert numpy arrays to lists)
|
|
41
|
+
if len(embeddings) > 0 and i < len(embeddings):
|
|
42
|
+
embedding = embeddings[i]
|
|
43
|
+
# Convert numpy array to list if needed
|
|
44
|
+
if isinstance(embedding, np.ndarray):
|
|
45
|
+
embedding = embedding.tolist()
|
|
46
|
+
item["embedding"] = embedding
|
|
42
47
|
export_data.append(item)
|
|
43
48
|
|
|
44
49
|
# Write to file
|
|
@@ -82,9 +87,13 @@ class ImportExportService:
|
|
|
82
87
|
for key, value in metadatas[i].items():
|
|
83
88
|
row[f"metadata_{key}"] = value
|
|
84
89
|
|
|
85
|
-
# Optionally add embeddings
|
|
86
|
-
if include_embeddings and embeddings and i < len(embeddings):
|
|
87
|
-
|
|
90
|
+
# Optionally add embeddings (convert numpy arrays to lists)
|
|
91
|
+
if include_embeddings and len(embeddings) > 0 and i < len(embeddings):
|
|
92
|
+
embedding = embeddings[i]
|
|
93
|
+
# Convert numpy array to list if needed
|
|
94
|
+
if isinstance(embedding, np.ndarray):
|
|
95
|
+
embedding = embedding.tolist()
|
|
96
|
+
row["embedding"] = json.dumps(embedding)
|
|
88
97
|
|
|
89
98
|
rows.append(row)
|
|
90
99
|
|
|
@@ -118,17 +127,24 @@ class ImportExportService:
|
|
|
118
127
|
# Prepare data for DataFrame
|
|
119
128
|
df_data = {
|
|
120
129
|
"id": ids,
|
|
121
|
-
"document": documents if documents else [None] * len(ids),
|
|
130
|
+
"document": documents if len(documents) > 0 else [None] * len(ids),
|
|
122
131
|
}
|
|
123
132
|
|
|
124
133
|
# Add metadata fields as separate columns
|
|
125
|
-
if metadatas and metadatas[0]:
|
|
134
|
+
if len(metadatas) > 0 and metadatas[0]:
|
|
126
135
|
for key in metadatas[0].keys():
|
|
127
136
|
df_data[f"metadata_{key}"] = [m.get(key) if m else None for m in metadatas]
|
|
128
137
|
|
|
129
|
-
# Add embeddings as a column
|
|
130
|
-
if embeddings:
|
|
131
|
-
|
|
138
|
+
# Add embeddings as a column (convert numpy arrays to lists for compatibility)
|
|
139
|
+
if len(embeddings) > 0:
|
|
140
|
+
# Convert numpy arrays to lists if needed
|
|
141
|
+
embedding_list = []
|
|
142
|
+
for emb in embeddings:
|
|
143
|
+
if isinstance(emb, np.ndarray):
|
|
144
|
+
embedding_list.append(emb.tolist())
|
|
145
|
+
else:
|
|
146
|
+
embedding_list.append(emb)
|
|
147
|
+
df_data["embedding"] = embedding_list
|
|
132
148
|
|
|
133
149
|
# Create DataFrame and save
|
|
134
150
|
df = pd.DataFrame(df_data)
|
|
@@ -173,7 +189,7 @@ class ImportExportService:
|
|
|
173
189
|
"metadatas": metadatas,
|
|
174
190
|
}
|
|
175
191
|
|
|
176
|
-
if embeddings:
|
|
192
|
+
if len(embeddings) > 0:
|
|
177
193
|
result["embeddings"] = embeddings
|
|
178
194
|
|
|
179
195
|
return result
|
|
@@ -227,7 +243,7 @@ class ImportExportService:
|
|
|
227
243
|
"metadatas": metadatas,
|
|
228
244
|
}
|
|
229
245
|
|
|
230
|
-
if embeddings:
|
|
246
|
+
if len(embeddings) > 0:
|
|
231
247
|
result["embeddings"] = embeddings
|
|
232
248
|
|
|
233
249
|
return result
|
|
@@ -277,7 +293,7 @@ class ImportExportService:
|
|
|
277
293
|
"metadatas": metadatas,
|
|
278
294
|
}
|
|
279
295
|
|
|
280
|
-
if embeddings:
|
|
296
|
+
if len(embeddings) > 0:
|
|
281
297
|
result["embeddings"] = embeddings
|
|
282
298
|
|
|
283
299
|
return result
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/metadata_view.py
RENAMED
|
@@ -15,6 +15,7 @@ from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
|
15
15
|
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
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
18
19
|
from PySide6.QtWidgets import QApplication
|
|
19
20
|
|
|
20
21
|
|
|
@@ -29,6 +30,7 @@ class MetadataView(QWidget):
|
|
|
29
30
|
self.page_size = 50
|
|
30
31
|
self.current_page = 0
|
|
31
32
|
self.loading_dialog = LoadingDialog("Loading data...", self)
|
|
33
|
+
self.settings_service = SettingsService()
|
|
32
34
|
|
|
33
35
|
# Debounce timer for filter changes
|
|
34
36
|
self.filter_reload_timer = QTimer()
|
|
@@ -426,33 +428,41 @@ class MetadataView(QWidget):
|
|
|
426
428
|
QMessageBox.warning(self, "Error", "Failed to update item.")
|
|
427
429
|
|
|
428
430
|
def _export_data(self, format_type: str):
|
|
429
|
-
"""Export
|
|
431
|
+
"""Export current table data to file (visible rows or selected rows)."""
|
|
430
432
|
if not self.current_collection:
|
|
431
433
|
QMessageBox.warning(self, "No Collection", "Please select a collection first.")
|
|
432
434
|
return
|
|
433
|
-
|
|
434
|
-
# Get all data (not just current page)
|
|
435
|
-
self.loading_dialog.show_loading("Exporting data...")
|
|
436
|
-
QApplication.processEvents()
|
|
437
435
|
|
|
438
|
-
|
|
439
|
-
# Get filter if active
|
|
440
|
-
where_filter = None
|
|
441
|
-
if self.filter_group.isChecked() and self.filter_builder.has_filters():
|
|
442
|
-
where_filter = self.filter_builder.get_filter()
|
|
443
|
-
|
|
444
|
-
# Fetch all data
|
|
445
|
-
all_data = self.connection.get_all_items(
|
|
446
|
-
self.current_collection,
|
|
447
|
-
where=where_filter
|
|
448
|
-
)
|
|
449
|
-
finally:
|
|
450
|
-
self.loading_dialog.hide_loading()
|
|
451
|
-
|
|
452
|
-
if not all_data or not all_data.get("ids"):
|
|
436
|
+
if not self.current_data or not self.current_data.get("ids"):
|
|
453
437
|
QMessageBox.warning(self, "No Data", "No data to export.")
|
|
454
438
|
return
|
|
455
439
|
|
|
440
|
+
# Check if there are selected rows
|
|
441
|
+
selected_rows = self.table.selectionModel().selectedRows()
|
|
442
|
+
|
|
443
|
+
if selected_rows:
|
|
444
|
+
# Export only selected rows
|
|
445
|
+
export_data = {
|
|
446
|
+
"ids": [],
|
|
447
|
+
"documents": [],
|
|
448
|
+
"metadatas": [],
|
|
449
|
+
"embeddings": []
|
|
450
|
+
}
|
|
451
|
+
|
|
452
|
+
for index in selected_rows:
|
|
453
|
+
row = index.row()
|
|
454
|
+
if row < len(self.current_data["ids"]):
|
|
455
|
+
export_data["ids"].append(self.current_data["ids"][row])
|
|
456
|
+
if "documents" in self.current_data and row < len(self.current_data["documents"]):
|
|
457
|
+
export_data["documents"].append(self.current_data["documents"][row])
|
|
458
|
+
if "metadatas" in self.current_data and row < len(self.current_data["metadatas"]):
|
|
459
|
+
export_data["metadatas"].append(self.current_data["metadatas"][row])
|
|
460
|
+
if "embeddings" in self.current_data and row < len(self.current_data["embeddings"]):
|
|
461
|
+
export_data["embeddings"].append(self.current_data["embeddings"][row])
|
|
462
|
+
else:
|
|
463
|
+
# Export all visible data from current table
|
|
464
|
+
export_data = self.current_data
|
|
465
|
+
|
|
456
466
|
# Select file path
|
|
457
467
|
file_filters = {
|
|
458
468
|
"json": "JSON Files (*.json)",
|
|
@@ -460,10 +470,14 @@ class MetadataView(QWidget):
|
|
|
460
470
|
"parquet": "Parquet Files (*.parquet)"
|
|
461
471
|
}
|
|
462
472
|
|
|
473
|
+
# Get last used directory from settings
|
|
474
|
+
last_dir = self.settings_service.get("last_import_export_dir", "")
|
|
475
|
+
default_path = f"{last_dir}/{self.current_collection}.{format_type}" if last_dir else f"{self.current_collection}.{format_type}"
|
|
476
|
+
|
|
463
477
|
file_path, _ = QFileDialog.getSaveFileName(
|
|
464
478
|
self,
|
|
465
479
|
f"Export to {format_type.upper()}",
|
|
466
|
-
|
|
480
|
+
default_path,
|
|
467
481
|
file_filters[format_type]
|
|
468
482
|
)
|
|
469
483
|
|
|
@@ -475,17 +489,21 @@ class MetadataView(QWidget):
|
|
|
475
489
|
success = False
|
|
476
490
|
|
|
477
491
|
if format_type == "json":
|
|
478
|
-
success = service.export_to_json(
|
|
492
|
+
success = service.export_to_json(export_data, file_path)
|
|
479
493
|
elif format_type == "csv":
|
|
480
|
-
success = service.export_to_csv(
|
|
494
|
+
success = service.export_to_csv(export_data, file_path)
|
|
481
495
|
elif format_type == "parquet":
|
|
482
|
-
success = service.export_to_parquet(
|
|
496
|
+
success = service.export_to_parquet(export_data, file_path)
|
|
483
497
|
|
|
484
498
|
if success:
|
|
499
|
+
# Save the directory for next time
|
|
500
|
+
from pathlib import Path
|
|
501
|
+
self.settings_service.set("last_import_export_dir", str(Path(file_path).parent))
|
|
502
|
+
|
|
485
503
|
QMessageBox.information(
|
|
486
504
|
self,
|
|
487
505
|
"Export Successful",
|
|
488
|
-
f"Exported {len(
|
|
506
|
+
f"Exported {len(export_data['ids'])} items to {file_path}"
|
|
489
507
|
)
|
|
490
508
|
else:
|
|
491
509
|
QMessageBox.warning(self, "Export Failed", "Failed to export data.")
|
|
@@ -503,10 +521,13 @@ class MetadataView(QWidget):
|
|
|
503
521
|
"parquet": "Parquet Files (*.parquet)"
|
|
504
522
|
}
|
|
505
523
|
|
|
524
|
+
# Get last used directory from settings
|
|
525
|
+
last_dir = self.settings_service.get("last_import_export_dir", "")
|
|
526
|
+
|
|
506
527
|
file_path, _ = QFileDialog.getOpenFileName(
|
|
507
528
|
self,
|
|
508
529
|
f"Import from {format_type.upper()}",
|
|
509
|
-
|
|
530
|
+
last_dir,
|
|
510
531
|
file_filters[format_type]
|
|
511
532
|
)
|
|
512
533
|
|
|
@@ -531,6 +552,54 @@ class MetadataView(QWidget):
|
|
|
531
552
|
if not imported_data:
|
|
532
553
|
QMessageBox.warning(self, "Import Failed", "Failed to parse import file.")
|
|
533
554
|
return
|
|
555
|
+
|
|
556
|
+
# Handle Qdrant-specific requirements (similar to backup/restore)
|
|
557
|
+
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
558
|
+
if isinstance(self.connection, QdrantConnection):
|
|
559
|
+
# Check if embeddings are missing and need to be generated
|
|
560
|
+
if not imported_data.get("embeddings"):
|
|
561
|
+
self.loading_dialog.setLabelText("Generating embeddings for Qdrant...")
|
|
562
|
+
QApplication.processEvents()
|
|
563
|
+
try:
|
|
564
|
+
from sentence_transformers import SentenceTransformer
|
|
565
|
+
model = SentenceTransformer("all-MiniLM-L6-v2")
|
|
566
|
+
documents = imported_data.get("documents", [])
|
|
567
|
+
imported_data["embeddings"] = model.encode(documents, show_progress_bar=False).tolist()
|
|
568
|
+
except Exception as e:
|
|
569
|
+
QMessageBox.warning(self, "Import Failed",
|
|
570
|
+
f"Qdrant requires embeddings. Failed to generate: {e}")
|
|
571
|
+
return
|
|
572
|
+
|
|
573
|
+
# Convert IDs to Qdrant-compatible format (integers or UUIDs)
|
|
574
|
+
# Store original IDs in metadata
|
|
575
|
+
original_ids = imported_data.get("ids", [])
|
|
576
|
+
qdrant_ids = []
|
|
577
|
+
metadatas = imported_data.get("metadatas", [])
|
|
578
|
+
|
|
579
|
+
for i, orig_id in enumerate(original_ids):
|
|
580
|
+
# Try to convert to integer, otherwise use index
|
|
581
|
+
try:
|
|
582
|
+
# If it's like "doc_123", extract the number
|
|
583
|
+
if isinstance(orig_id, str) and "_" in orig_id:
|
|
584
|
+
qdrant_id = int(orig_id.split("_")[-1])
|
|
585
|
+
else:
|
|
586
|
+
qdrant_id = int(orig_id)
|
|
587
|
+
except (ValueError, AttributeError):
|
|
588
|
+
# Use index as ID if can't convert
|
|
589
|
+
qdrant_id = i
|
|
590
|
+
|
|
591
|
+
qdrant_ids.append(qdrant_id)
|
|
592
|
+
|
|
593
|
+
# Store original ID in metadata
|
|
594
|
+
if i < len(metadatas):
|
|
595
|
+
if metadatas[i] is None:
|
|
596
|
+
metadatas[i] = {}
|
|
597
|
+
metadatas[i]["original_id"] = orig_id
|
|
598
|
+
else:
|
|
599
|
+
metadatas.append({"original_id": orig_id})
|
|
600
|
+
|
|
601
|
+
imported_data["ids"] = qdrant_ids
|
|
602
|
+
imported_data["metadatas"] = metadatas
|
|
534
603
|
|
|
535
604
|
# Add items to collection
|
|
536
605
|
success = self.connection.add_items(
|
|
@@ -544,6 +613,10 @@ class MetadataView(QWidget):
|
|
|
544
613
|
self.loading_dialog.hide_loading()
|
|
545
614
|
|
|
546
615
|
if success:
|
|
616
|
+
# Save the directory for next time
|
|
617
|
+
from pathlib import Path
|
|
618
|
+
self.settings_service.set("last_import_export_dir", str(Path(file_path).parent))
|
|
619
|
+
|
|
547
620
|
QMessageBox.information(
|
|
548
621
|
self,
|
|
549
622
|
"Import Successful",
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/core/connections/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/filter_service.py
RENAMED
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/services/settings_service.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/components/item_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/connection_view.py
RENAMED
|
File without changes
|
{vector_inspector-0.2.3 → vector_inspector-0.2.4}/src/vector_inspector/ui/views/search_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|