vector-inspector 0.2.4__tar.gz → 0.2.5__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.4 → vector_inspector-0.2.5}/PKG-INFO +7 -4
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/README.md +3 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/pyproject.toml +4 -4
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/connections/chroma_connection.py +28 -2
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/connections/qdrant_connection.py +61 -1
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/visualization_service.py +15 -12
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/main_window.py +48 -5
- vector_inspector-0.2.5/src/vector_inspector/ui/views/info_panel.py +311 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/visualization_view.py +6 -2
- vector_inspector-0.2.5/src/vector_inspector/utils/__init__.py +1 -0
- vector_inspector-0.2.5/src/vector_inspector/utils/lazy_imports.py +49 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/__main__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/connections/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/connections/base_connection.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/core/connections/template_connection.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/main.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/backup_restore_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/filter_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/import_export_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/settings_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/backup_restore_dialog.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/filter_builder.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/item_dialog.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/loading_dialog.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/__init__.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/collection_browser.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/connection_view.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/metadata_view.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/search_view.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/tests/test_connections.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/tests/test_filter_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/tests/test_settings_service.py +0 -0
- {vector_inspector-0.2.4 → vector_inspector-0.2.5}/tests/vector_inspector.py +0 -0
|
@@ -1,13 +1,13 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.5
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://vector-inspector.divinedevops.com
|
|
8
|
-
Project-URL: Source, https://github.com/
|
|
9
|
-
Project-URL: Issues, https://github.com/
|
|
10
|
-
Project-URL: Documentation, https://github.com/
|
|
8
|
+
Project-URL: Source, https://github.com/anthonypdawson/vector-inspector
|
|
9
|
+
Project-URL: Issues, https://github.com/anthonypdawson/vector-inspector/issues
|
|
10
|
+
Project-URL: Documentation, https://github.com/anthonypdawson/vector-inspector#readme
|
|
11
11
|
Requires-Python: ==3.12.*
|
|
12
12
|
Requires-Dist: chromadb>=0.4.22
|
|
13
13
|
Requires-Dist: qdrant-client>=1.7.0
|
|
@@ -26,6 +26,9 @@ Description-Content-Type: text/markdown
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
# Vector Inspector
|
|
29
|
+
|
|
30
|
+
> **Disclaimer:** This tool is currently under active development and is **not production ready**. Not all features have been thoroughly tested. Use with caution in critical or production environments.
|
|
31
|
+
|
|
29
32
|

|
|
30
33
|
[](https://pepy.tech/projects/vector-inspector)
|
|
31
34
|
|
|
@@ -1,5 +1,8 @@
|
|
|
1
1
|
|
|
2
2
|
# Vector Inspector
|
|
3
|
+
|
|
4
|
+
> **Disclaimer:** This tool is currently under active development and is **not production ready**. Not all features have been thoroughly tested. Use with caution in critical or production environments.
|
|
5
|
+
|
|
3
6
|

|
|
4
7
|
[](https://pepy.tech/projects/vector-inspector)
|
|
5
8
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "vector-inspector"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.5"
|
|
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" },
|
|
@@ -28,9 +28,9 @@ text = "MIT"
|
|
|
28
28
|
|
|
29
29
|
[project.urls]
|
|
30
30
|
Homepage = "https://vector-inspector.divinedevops.com"
|
|
31
|
-
Source = "https://github.com/
|
|
32
|
-
Issues = "https://github.com/
|
|
33
|
-
Documentation = "https://github.com/
|
|
31
|
+
Source = "https://github.com/anthonypdawson/vector-inspector"
|
|
32
|
+
Issues = "https://github.com/anthonypdawson/vector-inspector/issues"
|
|
33
|
+
Documentation = "https://github.com/anthonypdawson/vector-inspector#readme"
|
|
34
34
|
|
|
35
35
|
[project.scripts]
|
|
36
36
|
vector-inspector = "vector_inspector.main:main"
|
|
@@ -125,16 +125,42 @@ class ChromaDBConnection(VectorDBConnection):
|
|
|
125
125
|
|
|
126
126
|
try:
|
|
127
127
|
count = collection.count()
|
|
128
|
-
# Get a sample to determine metadata fields
|
|
129
|
-
sample = collection.get(limit=1, include=["metadatas"])
|
|
128
|
+
# Get a sample to determine metadata fields and vector dimensions
|
|
129
|
+
sample = collection.get(limit=1, include=["metadatas", "embeddings"])
|
|
130
130
|
metadata_fields = []
|
|
131
|
+
vector_dimension = "Unknown"
|
|
132
|
+
|
|
131
133
|
if sample and sample["metadatas"]:
|
|
132
134
|
metadata_fields = list(sample["metadatas"][0].keys()) if sample["metadatas"][0] else []
|
|
133
135
|
|
|
136
|
+
# Determine vector dimensions from embeddings
|
|
137
|
+
embeddings = sample.get("embeddings") if sample else None
|
|
138
|
+
if embeddings is not None and len(embeddings) > 0 and embeddings[0] is not None:
|
|
139
|
+
vector_dimension = len(embeddings[0])
|
|
140
|
+
|
|
141
|
+
# ChromaDB uses cosine distance by default (or can be configured)
|
|
142
|
+
# Try to get metadata from collection if available
|
|
143
|
+
distance_metric = "Cosine (default)"
|
|
144
|
+
try:
|
|
145
|
+
# ChromaDB collections may have metadata about distance function
|
|
146
|
+
col_metadata = collection.metadata
|
|
147
|
+
if col_metadata and "hnsw:space" in col_metadata:
|
|
148
|
+
space = col_metadata["hnsw:space"]
|
|
149
|
+
if space == "l2":
|
|
150
|
+
distance_metric = "Euclidean (L2)"
|
|
151
|
+
elif space == "ip":
|
|
152
|
+
distance_metric = "Inner Product"
|
|
153
|
+
elif space == "cosine":
|
|
154
|
+
distance_metric = "Cosine"
|
|
155
|
+
except:
|
|
156
|
+
pass # Use default if unable to determine
|
|
157
|
+
|
|
134
158
|
return {
|
|
135
159
|
"name": name,
|
|
136
160
|
"count": count,
|
|
137
161
|
"metadata_fields": metadata_fields,
|
|
162
|
+
"vector_dimension": vector_dimension,
|
|
163
|
+
"distance_metric": distance_metric,
|
|
138
164
|
}
|
|
139
165
|
except Exception as e:
|
|
140
166
|
print(f"Failed to get collection info: {e}")
|
|
@@ -191,11 +191,71 @@ class QdrantConnection(VectorDBConnection):
|
|
|
191
191
|
# Extract metadata fields, excluding 'document' if present
|
|
192
192
|
metadata_fields = [k for k in point.payload.keys() if k != 'document']
|
|
193
193
|
|
|
194
|
-
|
|
194
|
+
# Extract vector configuration
|
|
195
|
+
vector_dimension = "Unknown"
|
|
196
|
+
distance_metric = "Unknown"
|
|
197
|
+
config_details = {}
|
|
198
|
+
|
|
199
|
+
if collection_info.config:
|
|
200
|
+
# Get vector parameters
|
|
201
|
+
if hasattr(collection_info.config, 'params'):
|
|
202
|
+
params = collection_info.config.params
|
|
203
|
+
if hasattr(params, 'vectors'):
|
|
204
|
+
vectors = params.vectors
|
|
205
|
+
# Handle both dict and object access
|
|
206
|
+
if isinstance(vectors, dict):
|
|
207
|
+
# Named vectors
|
|
208
|
+
first_vector = next(iter(vectors.values()), None)
|
|
209
|
+
if first_vector:
|
|
210
|
+
vector_dimension = getattr(first_vector, 'size', 'Unknown')
|
|
211
|
+
distance = getattr(first_vector, 'distance', None)
|
|
212
|
+
else:
|
|
213
|
+
# Single vector config
|
|
214
|
+
vector_dimension = getattr(vectors, 'size', 'Unknown')
|
|
215
|
+
distance = getattr(vectors, 'distance', None)
|
|
216
|
+
|
|
217
|
+
# Map distance enum to readable name
|
|
218
|
+
if distance:
|
|
219
|
+
distance_str = str(distance)
|
|
220
|
+
if 'COSINE' in distance_str.upper():
|
|
221
|
+
distance_metric = "Cosine"
|
|
222
|
+
elif 'EUCLID' in distance_str.upper():
|
|
223
|
+
distance_metric = "Euclidean"
|
|
224
|
+
elif 'DOT' in distance_str.upper():
|
|
225
|
+
distance_metric = "Dot Product"
|
|
226
|
+
elif 'MANHATTAN' in distance_str.upper():
|
|
227
|
+
distance_metric = "Manhattan"
|
|
228
|
+
else:
|
|
229
|
+
distance_metric = distance_str
|
|
230
|
+
|
|
231
|
+
# Get HNSW config if available
|
|
232
|
+
if hasattr(collection_info.config, 'hnsw_config'):
|
|
233
|
+
hnsw = collection_info.config.hnsw_config
|
|
234
|
+
config_details['hnsw_config'] = {
|
|
235
|
+
'm': getattr(hnsw, 'm', None),
|
|
236
|
+
'ef_construct': getattr(hnsw, 'ef_construct', None),
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
# Get optimizer config if available
|
|
240
|
+
if hasattr(collection_info.config, 'optimizer_config'):
|
|
241
|
+
opt = collection_info.config.optimizer_config
|
|
242
|
+
config_details['optimizer_config'] = {
|
|
243
|
+
'indexing_threshold': getattr(opt, 'indexing_threshold', None),
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
result = {
|
|
195
247
|
"name": name,
|
|
196
248
|
"count": collection_info.points_count,
|
|
197
249
|
"metadata_fields": metadata_fields,
|
|
250
|
+
"vector_dimension": vector_dimension,
|
|
251
|
+
"distance_metric": distance_metric,
|
|
198
252
|
}
|
|
253
|
+
|
|
254
|
+
if config_details:
|
|
255
|
+
result['config'] = config_details
|
|
256
|
+
|
|
257
|
+
return result
|
|
258
|
+
|
|
199
259
|
except Exception as e:
|
|
200
260
|
print(f"Failed to get collection info: {e}")
|
|
201
261
|
return None
|
|
@@ -1,10 +1,6 @@
|
|
|
1
1
|
"""Visualization service for dimensionality reduction."""
|
|
2
2
|
|
|
3
|
-
from typing import Optional, List, Tuple
|
|
4
|
-
import numpy as np
|
|
5
|
-
from sklearn.decomposition import PCA
|
|
6
|
-
from sklearn.manifold import TSNE
|
|
7
|
-
import umap
|
|
3
|
+
from typing import Optional, List, Tuple, Any
|
|
8
4
|
|
|
9
5
|
|
|
10
6
|
class VisualizationService:
|
|
@@ -16,7 +12,7 @@ class VisualizationService:
|
|
|
16
12
|
method: str = "pca",
|
|
17
13
|
n_components: int = 2,
|
|
18
14
|
**kwargs
|
|
19
|
-
) -> Optional[
|
|
15
|
+
) -> Optional[Any]:
|
|
20
16
|
"""
|
|
21
17
|
Reduce dimensionality of embeddings.
|
|
22
18
|
|
|
@@ -33,13 +29,19 @@ class VisualizationService:
|
|
|
33
29
|
return None
|
|
34
30
|
|
|
35
31
|
try:
|
|
32
|
+
# Lazy import numpy and models
|
|
33
|
+
from vector_inspector.utils.lazy_imports import get_numpy, get_sklearn_model
|
|
34
|
+
np = get_numpy()
|
|
35
|
+
|
|
36
36
|
X = np.array(embeddings)
|
|
37
37
|
|
|
38
|
-
if method == "pca":
|
|
38
|
+
if method.lower() == "pca":
|
|
39
|
+
PCA = get_sklearn_model('PCA')
|
|
39
40
|
reducer = PCA(n_components=n_components)
|
|
40
41
|
reduced = reducer.fit_transform(X)
|
|
41
42
|
|
|
42
|
-
elif method == "
|
|
43
|
+
elif method.lower() == "t-sne":
|
|
44
|
+
TSNE = get_sklearn_model('TSNE')
|
|
43
45
|
perplexity = kwargs.get("perplexity", min(30, len(embeddings) - 1))
|
|
44
46
|
reducer = TSNE(
|
|
45
47
|
n_components=n_components,
|
|
@@ -48,9 +50,10 @@ class VisualizationService:
|
|
|
48
50
|
)
|
|
49
51
|
reduced = reducer.fit_transform(X)
|
|
50
52
|
|
|
51
|
-
elif method == "umap":
|
|
53
|
+
elif method.lower() == "umap":
|
|
54
|
+
UMAP = get_sklearn_model('UMAP')
|
|
52
55
|
n_neighbors = kwargs.get("n_neighbors", min(15, len(embeddings) - 1))
|
|
53
|
-
reducer =
|
|
56
|
+
reducer = UMAP(
|
|
54
57
|
n_components=n_components,
|
|
55
58
|
n_neighbors=n_neighbors,
|
|
56
59
|
random_state=42
|
|
@@ -69,11 +72,11 @@ class VisualizationService:
|
|
|
69
72
|
|
|
70
73
|
@staticmethod
|
|
71
74
|
def prepare_plot_data(
|
|
72
|
-
reduced_embeddings:
|
|
75
|
+
reduced_embeddings: Any,
|
|
73
76
|
labels: Optional[List[str]] = None,
|
|
74
77
|
metadata: Optional[List[dict]] = None,
|
|
75
78
|
color_by: Optional[str] = None
|
|
76
|
-
) -> Tuple[
|
|
79
|
+
) -> Tuple[Any, List[str], List[str]]:
|
|
77
80
|
"""
|
|
78
81
|
Prepare data for plotting.
|
|
79
82
|
|
|
@@ -12,9 +12,9 @@ from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
|
12
12
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
13
13
|
from vector_inspector.ui.views.connection_view import ConnectionView
|
|
14
14
|
from vector_inspector.ui.views.collection_browser import CollectionBrowser
|
|
15
|
+
from vector_inspector.ui.views.info_panel import InfoPanel
|
|
15
16
|
from vector_inspector.ui.views.metadata_view import MetadataView
|
|
16
17
|
from vector_inspector.ui.views.search_view import SearchView
|
|
17
|
-
from vector_inspector.ui.views.visualization_view import VisualizationView
|
|
18
18
|
from vector_inspector.ui.components.backup_restore_dialog import BackupRestoreDialog
|
|
19
19
|
|
|
20
20
|
|
|
@@ -63,13 +63,21 @@ class MainWindow(QMainWindow):
|
|
|
63
63
|
# Right panel - Tabbed views
|
|
64
64
|
self.tab_widget = QTabWidget()
|
|
65
65
|
|
|
66
|
+
self.info_panel = InfoPanel(self.connection)
|
|
66
67
|
self.metadata_view = MetadataView(self.connection)
|
|
67
68
|
self.search_view = SearchView(self.connection)
|
|
68
|
-
self.visualization_view =
|
|
69
|
+
self.visualization_view = None # Lazy loaded
|
|
69
70
|
|
|
71
|
+
self.tab_widget.addTab(self.info_panel, "Info")
|
|
70
72
|
self.tab_widget.addTab(self.metadata_view, "Data Browser")
|
|
71
73
|
self.tab_widget.addTab(self.search_view, "Search")
|
|
72
|
-
self.tab_widget.addTab(
|
|
74
|
+
self.tab_widget.addTab(QWidget(), "Visualization") # Placeholder
|
|
75
|
+
|
|
76
|
+
# Set Info tab as default
|
|
77
|
+
self.tab_widget.setCurrentIndex(0)
|
|
78
|
+
|
|
79
|
+
# Connect to tab change to lazy load visualization
|
|
80
|
+
self.tab_widget.currentChanged.connect(self._on_tab_changed)
|
|
73
81
|
|
|
74
82
|
# Add panels to splitter
|
|
75
83
|
main_splitter.addWidget(left_panel)
|
|
@@ -167,14 +175,34 @@ class MainWindow(QMainWindow):
|
|
|
167
175
|
self.connection_view.connection_created.connect(self._on_connection_created)
|
|
168
176
|
self.collection_browser.collection_selected.connect(self._on_collection_selected)
|
|
169
177
|
|
|
178
|
+
def _on_tab_changed(self, index: int):
|
|
179
|
+
"""Handle tab change - lazy load visualization tab."""
|
|
180
|
+
if index == 3 and self.visualization_view is None:
|
|
181
|
+
# Lazy load visualization view
|
|
182
|
+
from vector_inspector.ui.views.visualization_view import VisualizationView
|
|
183
|
+
self.visualization_view = VisualizationView(self.connection)
|
|
184
|
+
# Replace placeholder with actual view
|
|
185
|
+
self.tab_widget.removeTab(3)
|
|
186
|
+
self.tab_widget.insertTab(3, self.visualization_view, "Visualization")
|
|
187
|
+
self.tab_widget.setCurrentIndex(3)
|
|
188
|
+
# Set collection if one is already selected
|
|
189
|
+
if self.current_collection:
|
|
190
|
+
self.visualization_view.set_collection(self.current_collection)
|
|
191
|
+
|
|
170
192
|
def _on_connection_created(self, new_connection: VectorDBConnection):
|
|
171
193
|
"""Handle when a new connection instance is created."""
|
|
172
194
|
self.connection = new_connection
|
|
173
195
|
# Update all views with new connection
|
|
174
196
|
self.collection_browser.connection = new_connection
|
|
197
|
+
self.info_panel.connection = new_connection
|
|
175
198
|
self.metadata_view.connection = new_connection
|
|
176
199
|
self.search_view.connection = new_connection
|
|
177
|
-
|
|
200
|
+
# Only update visualization if it's been created
|
|
201
|
+
if self.visualization_view is not None:
|
|
202
|
+
self.visualization_view.connection = new_connection
|
|
203
|
+
# Refresh the collection browser to show new database's collections
|
|
204
|
+
if new_connection.is_connected:
|
|
205
|
+
self.collection_browser.refresh()
|
|
178
206
|
|
|
179
207
|
def _on_connect(self):
|
|
180
208
|
"""Handle connect action."""
|
|
@@ -187,6 +215,11 @@ class MainWindow(QMainWindow):
|
|
|
187
215
|
self.statusBar.showMessage("Disconnected")
|
|
188
216
|
self.connection_changed.emit(False)
|
|
189
217
|
self.collection_browser.clear()
|
|
218
|
+
# Clear info panel
|
|
219
|
+
self.info_panel.refresh_database_info()
|
|
220
|
+
else:
|
|
221
|
+
# Always clear collection browser on disconnect
|
|
222
|
+
self.collection_browser.clear()
|
|
190
223
|
|
|
191
224
|
def _on_connection_status_changed(self, connected: bool):
|
|
192
225
|
"""Handle connection status change."""
|
|
@@ -194,9 +227,14 @@ class MainWindow(QMainWindow):
|
|
|
194
227
|
self.statusBar.showMessage("Connected")
|
|
195
228
|
self.connection_changed.emit(True)
|
|
196
229
|
self._on_refresh_collections()
|
|
230
|
+
# Refresh info panel with new connection
|
|
231
|
+
self.info_panel.refresh_database_info()
|
|
197
232
|
else:
|
|
198
233
|
self.statusBar.showMessage("Connection failed")
|
|
199
234
|
self.connection_changed.emit(False)
|
|
235
|
+
# Clear info panel and collection browser
|
|
236
|
+
self.info_panel.refresh_database_info()
|
|
237
|
+
self.collection_browser.clear()
|
|
200
238
|
|
|
201
239
|
def _on_collection_selected(self, collection_name: str):
|
|
202
240
|
"""Handle collection selection."""
|
|
@@ -204,14 +242,19 @@ class MainWindow(QMainWindow):
|
|
|
204
242
|
self.statusBar.showMessage(f"Collection: {collection_name}")
|
|
205
243
|
|
|
206
244
|
# Update all views with new collection
|
|
245
|
+
self.info_panel.set_collection(collection_name)
|
|
207
246
|
self.metadata_view.set_collection(collection_name)
|
|
208
247
|
self.search_view.set_collection(collection_name)
|
|
209
|
-
|
|
248
|
+
# Only update visualization if it's been created
|
|
249
|
+
if self.visualization_view is not None:
|
|
250
|
+
self.visualization_view.set_collection(collection_name)
|
|
210
251
|
|
|
211
252
|
def _on_refresh_collections(self):
|
|
212
253
|
"""Refresh collection list."""
|
|
213
254
|
if self.connection.is_connected:
|
|
214
255
|
self.collection_browser.refresh()
|
|
256
|
+
# Also refresh database info (collection count may have changed)
|
|
257
|
+
self.info_panel.refresh_database_info()
|
|
215
258
|
|
|
216
259
|
def _on_new_collection(self):
|
|
217
260
|
"""Create a new collection."""
|
|
@@ -0,0 +1,311 @@
|
|
|
1
|
+
"""Information panel for displaying database and collection metadata."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Dict, Any
|
|
4
|
+
from PySide6.QtWidgets import (
|
|
5
|
+
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
6
|
+
QGroupBox, QScrollArea, QFrame
|
|
7
|
+
)
|
|
8
|
+
from PySide6.QtCore import Qt, QObject
|
|
9
|
+
|
|
10
|
+
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
11
|
+
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
12
|
+
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class InfoPanel(QWidget):
|
|
16
|
+
"""Panel for displaying database and collection information."""
|
|
17
|
+
|
|
18
|
+
def __init__(self, connection: VectorDBConnection, parent=None):
|
|
19
|
+
super().__init__(parent)
|
|
20
|
+
self.connection = connection
|
|
21
|
+
self.current_collection: str = ""
|
|
22
|
+
self._setup_ui()
|
|
23
|
+
|
|
24
|
+
def _setup_ui(self):
|
|
25
|
+
"""Setup widget UI."""
|
|
26
|
+
layout = QVBoxLayout(self)
|
|
27
|
+
|
|
28
|
+
# Create scroll area for content
|
|
29
|
+
scroll = QScrollArea()
|
|
30
|
+
scroll.setWidgetResizable(True)
|
|
31
|
+
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
32
|
+
|
|
33
|
+
# Container for all info sections
|
|
34
|
+
container = QWidget()
|
|
35
|
+
container_layout = QVBoxLayout(container)
|
|
36
|
+
container_layout.setSpacing(10)
|
|
37
|
+
|
|
38
|
+
# Database Information Section
|
|
39
|
+
self.db_group = QGroupBox("Database Information")
|
|
40
|
+
db_layout = QVBoxLayout()
|
|
41
|
+
|
|
42
|
+
self.provider_label = self._create_info_row("Provider:", "Not connected")
|
|
43
|
+
self.connection_type_label = self._create_info_row("Connection Type:", "N/A")
|
|
44
|
+
self.endpoint_label = self._create_info_row("Endpoint:", "N/A")
|
|
45
|
+
self.api_key_label = self._create_info_row("API Key:", "N/A")
|
|
46
|
+
self.status_label = self._create_info_row("Status:", "Disconnected")
|
|
47
|
+
self.collections_count_label = self._create_info_row("Total Collections:", "0")
|
|
48
|
+
|
|
49
|
+
db_layout.addWidget(self.provider_label)
|
|
50
|
+
db_layout.addWidget(self.connection_type_label)
|
|
51
|
+
db_layout.addWidget(self.endpoint_label)
|
|
52
|
+
db_layout.addWidget(self.api_key_label)
|
|
53
|
+
db_layout.addWidget(self.status_label)
|
|
54
|
+
db_layout.addWidget(self.collections_count_label)
|
|
55
|
+
|
|
56
|
+
self.db_group.setLayout(db_layout)
|
|
57
|
+
container_layout.addWidget(self.db_group)
|
|
58
|
+
|
|
59
|
+
# Collections List Section
|
|
60
|
+
self.collections_group = QGroupBox("Available Collections")
|
|
61
|
+
collections_layout = QVBoxLayout()
|
|
62
|
+
|
|
63
|
+
self.collections_list_label = QLabel("No collections")
|
|
64
|
+
self.collections_list_label.setWordWrap(True)
|
|
65
|
+
self.collections_list_label.setStyleSheet("color: gray; padding: 10px;")
|
|
66
|
+
collections_layout.addWidget(self.collections_list_label)
|
|
67
|
+
|
|
68
|
+
self.collections_group.setLayout(collections_layout)
|
|
69
|
+
container_layout.addWidget(self.collections_group)
|
|
70
|
+
|
|
71
|
+
# Collection Information Section
|
|
72
|
+
self.collection_group = QGroupBox("Collection Information")
|
|
73
|
+
collection_layout = QVBoxLayout()
|
|
74
|
+
|
|
75
|
+
self.collection_name_label = self._create_info_row("Name:", "No collection selected")
|
|
76
|
+
self.vector_dim_label = self._create_info_row("Vector Dimension:", "N/A")
|
|
77
|
+
self.distance_metric_label = self._create_info_row("Distance Metric:", "N/A")
|
|
78
|
+
self.total_points_label = self._create_info_row("Total Points:", "0")
|
|
79
|
+
|
|
80
|
+
collection_layout.addWidget(self.collection_name_label)
|
|
81
|
+
collection_layout.addWidget(self.vector_dim_label)
|
|
82
|
+
collection_layout.addWidget(self.distance_metric_label)
|
|
83
|
+
collection_layout.addWidget(self.total_points_label)
|
|
84
|
+
|
|
85
|
+
# Payload Schema subsection
|
|
86
|
+
schema_label = QLabel("<b>Payload Schema:</b>")
|
|
87
|
+
collection_layout.addWidget(schema_label)
|
|
88
|
+
|
|
89
|
+
self.schema_label = QLabel("N/A")
|
|
90
|
+
self.schema_label.setWordWrap(True)
|
|
91
|
+
self.schema_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
92
|
+
collection_layout.addWidget(self.schema_label)
|
|
93
|
+
|
|
94
|
+
# Provider-specific details
|
|
95
|
+
provider_details_label = QLabel("<b>Provider-Specific Details:</b>")
|
|
96
|
+
collection_layout.addWidget(provider_details_label)
|
|
97
|
+
|
|
98
|
+
self.provider_details_label = QLabel("N/A")
|
|
99
|
+
self.provider_details_label.setWordWrap(True)
|
|
100
|
+
self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
101
|
+
collection_layout.addWidget(self.provider_details_label)
|
|
102
|
+
|
|
103
|
+
self.collection_group.setLayout(collection_layout)
|
|
104
|
+
container_layout.addWidget(self.collection_group)
|
|
105
|
+
|
|
106
|
+
# Add stretch to push content to top
|
|
107
|
+
container_layout.addStretch()
|
|
108
|
+
|
|
109
|
+
scroll.setWidget(container)
|
|
110
|
+
layout.addWidget(scroll)
|
|
111
|
+
|
|
112
|
+
# Initial state
|
|
113
|
+
self.refresh_database_info()
|
|
114
|
+
|
|
115
|
+
def _create_info_row(self, label: str, value: str) -> QWidget:
|
|
116
|
+
"""Create a row with label and value."""
|
|
117
|
+
row = QWidget()
|
|
118
|
+
row_layout = QHBoxLayout(row)
|
|
119
|
+
row_layout.setContentsMargins(0, 2, 0, 2)
|
|
120
|
+
|
|
121
|
+
label_widget = QLabel(f"<b>{label}</b>")
|
|
122
|
+
label_widget.setMinimumWidth(150)
|
|
123
|
+
row_layout.addWidget(label_widget)
|
|
124
|
+
|
|
125
|
+
value_widget = QLabel(value)
|
|
126
|
+
value_widget.setWordWrap(True)
|
|
127
|
+
value_widget.setStyleSheet("color: white;")
|
|
128
|
+
row_layout.addWidget(value_widget, stretch=1)
|
|
129
|
+
|
|
130
|
+
# Store value widget for later updates (use setProperty for type safety)
|
|
131
|
+
row.setProperty("value_label", value_widget)
|
|
132
|
+
|
|
133
|
+
return row
|
|
134
|
+
|
|
135
|
+
def refresh_database_info(self):
|
|
136
|
+
"""Refresh database connection information."""
|
|
137
|
+
if not self.connection or not self.connection.is_connected:
|
|
138
|
+
self._update_label(self.provider_label, "Not connected")
|
|
139
|
+
self._update_label(self.connection_type_label, "N/A")
|
|
140
|
+
self._update_label(self.endpoint_label, "N/A")
|
|
141
|
+
self._update_label(self.api_key_label, "N/A")
|
|
142
|
+
self._update_label(self.status_label, "Disconnected")
|
|
143
|
+
self._update_label(self.collections_count_label, "0")
|
|
144
|
+
self.collections_list_label.setText("No collections")
|
|
145
|
+
self.collections_list_label.setStyleSheet("color: gray; padding: 10px;")
|
|
146
|
+
# Also clear collection info
|
|
147
|
+
self._update_label(self.collection_name_label, "No collection selected")
|
|
148
|
+
self._update_label(self.vector_dim_label, "N/A")
|
|
149
|
+
self._update_label(self.distance_metric_label, "N/A")
|
|
150
|
+
self._update_label(self.total_points_label, "0")
|
|
151
|
+
self.schema_label.setText("N/A")
|
|
152
|
+
self.provider_details_label.setText("N/A")
|
|
153
|
+
return
|
|
154
|
+
|
|
155
|
+
# Get provider name
|
|
156
|
+
provider_name = self.connection.__class__.__name__.replace("Connection", "")
|
|
157
|
+
self._update_label(self.provider_label, provider_name)
|
|
158
|
+
|
|
159
|
+
# Get connection details
|
|
160
|
+
if isinstance(self.connection, ChromaDBConnection):
|
|
161
|
+
if self.connection.path:
|
|
162
|
+
self._update_label(self.connection_type_label, "Persistent (Local)")
|
|
163
|
+
self._update_label(self.endpoint_label, self.connection.path)
|
|
164
|
+
elif self.connection.host and self.connection.port:
|
|
165
|
+
self._update_label(self.connection_type_label, "HTTP (Remote)")
|
|
166
|
+
self._update_label(self.endpoint_label, f"{self.connection.host}:{self.connection.port}")
|
|
167
|
+
else:
|
|
168
|
+
self._update_label(self.connection_type_label, "Ephemeral (In-Memory)")
|
|
169
|
+
self._update_label(self.endpoint_label, "N/A")
|
|
170
|
+
self._update_label(self.api_key_label, "Not required")
|
|
171
|
+
|
|
172
|
+
elif isinstance(self.connection, QdrantConnection):
|
|
173
|
+
if self.connection.path:
|
|
174
|
+
self._update_label(self.connection_type_label, "Embedded (Local)")
|
|
175
|
+
self._update_label(self.endpoint_label, self.connection.path)
|
|
176
|
+
elif self.connection.url:
|
|
177
|
+
self._update_label(self.connection_type_label, "Remote (URL)")
|
|
178
|
+
self._update_label(self.endpoint_label, self.connection.url)
|
|
179
|
+
elif self.connection.host:
|
|
180
|
+
self._update_label(self.connection_type_label, "Remote (Host)")
|
|
181
|
+
self._update_label(self.endpoint_label, f"{self.connection.host}:{self.connection.port}")
|
|
182
|
+
else:
|
|
183
|
+
self._update_label(self.connection_type_label, "In-Memory")
|
|
184
|
+
self._update_label(self.endpoint_label, "N/A")
|
|
185
|
+
|
|
186
|
+
if self.connection.api_key:
|
|
187
|
+
self._update_label(self.api_key_label, "Present (hidden)")
|
|
188
|
+
else:
|
|
189
|
+
self._update_label(self.api_key_label, "Not configured")
|
|
190
|
+
else:
|
|
191
|
+
self._update_label(self.connection_type_label, "Unknown")
|
|
192
|
+
self._update_label(self.endpoint_label, "N/A")
|
|
193
|
+
self._update_label(self.api_key_label, "Unknown")
|
|
194
|
+
|
|
195
|
+
# Status
|
|
196
|
+
self._update_label(self.status_label, "Connected" if self.connection.is_connected else "Disconnected")
|
|
197
|
+
|
|
198
|
+
# List collections
|
|
199
|
+
try:
|
|
200
|
+
collections = self.connection.list_collections()
|
|
201
|
+
self._update_label(self.collections_count_label, str(len(collections)))
|
|
202
|
+
|
|
203
|
+
if collections:
|
|
204
|
+
collections_text = "\n".join([f"• {name}" for name in sorted(collections)])
|
|
205
|
+
self.collections_list_label.setText(collections_text)
|
|
206
|
+
self.collections_list_label.setStyleSheet("color: white; padding: 10px; font-family: monospace;")
|
|
207
|
+
else:
|
|
208
|
+
self.collections_list_label.setText("No collections found")
|
|
209
|
+
self.collections_list_label.setStyleSheet("color: gray; padding: 10px;")
|
|
210
|
+
except Exception as e:
|
|
211
|
+
self._update_label(self.collections_count_label, "Error")
|
|
212
|
+
self.collections_list_label.setText(f"Error loading collections: {str(e)}")
|
|
213
|
+
self.collections_list_label.setStyleSheet("color: red; padding: 10px;")
|
|
214
|
+
|
|
215
|
+
def refresh_collection_info(self):
|
|
216
|
+
"""Refresh collection-specific information."""
|
|
217
|
+
if not self.current_collection or not self.connection or not self.connection.is_connected:
|
|
218
|
+
self._update_label(self.collection_name_label, "No collection selected")
|
|
219
|
+
self._update_label(self.vector_dim_label, "N/A")
|
|
220
|
+
self._update_label(self.distance_metric_label, "N/A")
|
|
221
|
+
self._update_label(self.total_points_label, "0")
|
|
222
|
+
self.schema_label.setText("N/A")
|
|
223
|
+
self.provider_details_label.setText("N/A")
|
|
224
|
+
return
|
|
225
|
+
|
|
226
|
+
try:
|
|
227
|
+
# Get collection info
|
|
228
|
+
collection_info = self.connection.get_collection_info(self.current_collection)
|
|
229
|
+
|
|
230
|
+
if not collection_info:
|
|
231
|
+
self._update_label(self.collection_name_label, self.current_collection)
|
|
232
|
+
self._update_label(self.vector_dim_label, "Unable to retrieve")
|
|
233
|
+
self._update_label(self.distance_metric_label, "Unable to retrieve")
|
|
234
|
+
self._update_label(self.total_points_label, "Unable to retrieve")
|
|
235
|
+
self.schema_label.setText("Unable to retrieve collection info")
|
|
236
|
+
self.provider_details_label.setText("N/A")
|
|
237
|
+
return
|
|
238
|
+
|
|
239
|
+
# Update basic info
|
|
240
|
+
self._update_label(self.collection_name_label, self.current_collection)
|
|
241
|
+
|
|
242
|
+
# Vector dimension
|
|
243
|
+
vector_dim = collection_info.get("vector_dimension", "Unknown")
|
|
244
|
+
self._update_label(self.vector_dim_label, str(vector_dim))
|
|
245
|
+
|
|
246
|
+
# Distance metric
|
|
247
|
+
distance = collection_info.get("distance_metric", "Unknown")
|
|
248
|
+
self._update_label(self.distance_metric_label, distance)
|
|
249
|
+
|
|
250
|
+
# Total points
|
|
251
|
+
count = collection_info.get("count", 0)
|
|
252
|
+
self._update_label(self.total_points_label, f"{count:,}")
|
|
253
|
+
|
|
254
|
+
# Metadata schema
|
|
255
|
+
metadata_fields = collection_info.get("metadata_fields", [])
|
|
256
|
+
if metadata_fields:
|
|
257
|
+
schema_text = "\n".join([f"• {field}" for field in sorted(metadata_fields)])
|
|
258
|
+
self.schema_label.setText(schema_text)
|
|
259
|
+
self.schema_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
260
|
+
else:
|
|
261
|
+
self.schema_label.setText("No metadata fields found")
|
|
262
|
+
self.schema_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
263
|
+
|
|
264
|
+
# Provider-specific details
|
|
265
|
+
details_list = []
|
|
266
|
+
|
|
267
|
+
if isinstance(self.connection, ChromaDBConnection):
|
|
268
|
+
details_list.append("• Provider: ChromaDB")
|
|
269
|
+
details_list.append("• Supports: Documents, Metadata, Embeddings")
|
|
270
|
+
details_list.append("• Default embedding: all-MiniLM-L6-v2")
|
|
271
|
+
|
|
272
|
+
elif isinstance(self.connection, QdrantConnection):
|
|
273
|
+
details_list.append("• Provider: Qdrant")
|
|
274
|
+
details_list.append("• Supports: Points, Payload, Vectors")
|
|
275
|
+
# Get additional Qdrant-specific info if available
|
|
276
|
+
if "config" in collection_info:
|
|
277
|
+
config = collection_info["config"]
|
|
278
|
+
if "hnsw_config" in config:
|
|
279
|
+
hnsw = config["hnsw_config"]
|
|
280
|
+
details_list.append(f"• HNSW M: {hnsw.get('m', 'N/A')}")
|
|
281
|
+
details_list.append(f"• HNSW ef_construct: {hnsw.get('ef_construct', 'N/A')}")
|
|
282
|
+
if "optimizer_config" in config:
|
|
283
|
+
opt = config["optimizer_config"]
|
|
284
|
+
details_list.append(f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}")
|
|
285
|
+
|
|
286
|
+
if details_list:
|
|
287
|
+
self.provider_details_label.setText("\n".join(details_list))
|
|
288
|
+
self.provider_details_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
289
|
+
else:
|
|
290
|
+
self.provider_details_label.setText("No additional details available")
|
|
291
|
+
self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
|
|
292
|
+
|
|
293
|
+
except Exception as e:
|
|
294
|
+
self._update_label(self.collection_name_label, self.current_collection)
|
|
295
|
+
self._update_label(self.vector_dim_label, "Error")
|
|
296
|
+
self._update_label(self.distance_metric_label, "Error")
|
|
297
|
+
self._update_label(self.total_points_label, "Error")
|
|
298
|
+
self.schema_label.setText(f"Error: {str(e)}")
|
|
299
|
+
self.schema_label.setStyleSheet("color: red; padding-left: 20px;")
|
|
300
|
+
self.provider_details_label.setText("N/A")
|
|
301
|
+
|
|
302
|
+
def set_collection(self, collection_name: str):
|
|
303
|
+
"""Set the current collection and refresh its information."""
|
|
304
|
+
self.current_collection = collection_name
|
|
305
|
+
self.refresh_collection_info()
|
|
306
|
+
|
|
307
|
+
def _update_label(self, row_widget: QWidget, value: str):
|
|
308
|
+
"""Update the value label in an info row."""
|
|
309
|
+
value_label = row_widget.property("value_label")
|
|
310
|
+
if value_label and isinstance(value_label, QLabel):
|
|
311
|
+
value_label.setText(value)
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
"""Vector visualization view with dimensionality reduction."""
|
|
2
2
|
|
|
3
|
+
from __future__ import annotations
|
|
3
4
|
from typing import Optional, Dict, Any
|
|
4
5
|
import traceback
|
|
5
6
|
from PySide6.QtWidgets import (
|
|
@@ -8,7 +9,6 @@ from PySide6.QtWidgets import (
|
|
|
8
9
|
)
|
|
9
10
|
from PySide6.QtCore import Qt, QThread, Signal
|
|
10
11
|
from PySide6.QtWebEngineWidgets import QWebEngineView
|
|
11
|
-
import plotly.graph_objects as go
|
|
12
12
|
import numpy as np
|
|
13
13
|
|
|
14
14
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
@@ -154,7 +154,7 @@ class VisualizationView(QWidget):
|
|
|
154
154
|
self.visualization_thread.error.connect(self._on_reduction_error)
|
|
155
155
|
self.visualization_thread.start()
|
|
156
156
|
|
|
157
|
-
def _on_reduction_finished(self, reduced_data:
|
|
157
|
+
def _on_reduction_finished(self, reduced_data: Any):
|
|
158
158
|
"""Handle dimensionality reduction completion."""
|
|
159
159
|
self.reduced_data = reduced_data
|
|
160
160
|
self._create_plot()
|
|
@@ -172,6 +172,10 @@ class VisualizationView(QWidget):
|
|
|
172
172
|
"""Create plotly visualization."""
|
|
173
173
|
if self.reduced_data is None or self.current_data is None:
|
|
174
174
|
return
|
|
175
|
+
|
|
176
|
+
# Lazy import plotly
|
|
177
|
+
from vector_inspector.utils.lazy_imports import get_plotly
|
|
178
|
+
go = get_plotly()
|
|
175
179
|
|
|
176
180
|
ids = self.current_data.get("ids", [])
|
|
177
181
|
documents = self.current_data.get("documents", [])
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
"""Utilities for Vector Inspector."""
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"""Lazy import utilities for performance optimization."""
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
_plotly_cache = None
|
|
6
|
+
_sklearn_cache = {}
|
|
7
|
+
_numpy_cache = None
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
def get_plotly():
|
|
11
|
+
"""Lazy import plotly graph_objects."""
|
|
12
|
+
global _plotly_cache
|
|
13
|
+
if _plotly_cache is None:
|
|
14
|
+
import plotly.graph_objects as go
|
|
15
|
+
_plotly_cache = go
|
|
16
|
+
return _plotly_cache
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
def get_numpy():
|
|
20
|
+
"""Lazy import numpy."""
|
|
21
|
+
global _numpy_cache
|
|
22
|
+
if _numpy_cache is None:
|
|
23
|
+
import numpy as np
|
|
24
|
+
_numpy_cache = np
|
|
25
|
+
return _numpy_cache
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def get_sklearn_model(model_name: str) -> Any:
|
|
29
|
+
"""
|
|
30
|
+
Lazy import sklearn models.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
model_name: Name of the model ('PCA', 'TSNE', 'UMAP')
|
|
34
|
+
|
|
35
|
+
Returns:
|
|
36
|
+
The model class
|
|
37
|
+
"""
|
|
38
|
+
global _sklearn_cache
|
|
39
|
+
if model_name not in _sklearn_cache:
|
|
40
|
+
if model_name == 'PCA':
|
|
41
|
+
from sklearn.decomposition import PCA
|
|
42
|
+
_sklearn_cache['PCA'] = PCA
|
|
43
|
+
elif model_name == 'TSNE':
|
|
44
|
+
from sklearn.manifold import TSNE
|
|
45
|
+
_sklearn_cache['TSNE'] = TSNE
|
|
46
|
+
elif model_name == 'UMAP':
|
|
47
|
+
import umap
|
|
48
|
+
_sklearn_cache['UMAP'] = umap.UMAP
|
|
49
|
+
return _sklearn_cache[model_name]
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/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
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/filter_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/services/settings_service.py
RENAMED
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/components/item_dialog.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/connection_view.py
RENAMED
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/metadata_view.py
RENAMED
|
File without changes
|
{vector_inspector-0.2.4 → vector_inspector-0.2.5}/src/vector_inspector/ui/views/search_view.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|