vector-inspector 0.3.4__py3-none-any.whl → 0.3.6__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- vector_inspector/core/connections/base_connection.py +86 -1
- vector_inspector/core/connections/chroma_connection.py +23 -3
- vector_inspector/core/connections/pgvector_connection.py +1100 -0
- vector_inspector/core/connections/pinecone_connection.py +24 -4
- vector_inspector/core/connections/qdrant_connection.py +224 -189
- vector_inspector/core/embedding_providers/provider_factory.py +33 -38
- vector_inspector/core/embedding_utils.py +2 -2
- vector_inspector/services/backup_restore_service.py +41 -33
- vector_inspector/ui/components/connection_manager_panel.py +96 -77
- vector_inspector/ui/components/profile_manager_panel.py +315 -121
- vector_inspector/ui/dialogs/embedding_config_dialog.py +79 -58
- vector_inspector/ui/main_window.py +22 -0
- vector_inspector/ui/views/connection_view.py +215 -116
- vector_inspector/ui/views/info_panel.py +6 -6
- vector_inspector/ui/views/metadata_view.py +466 -187
- {vector_inspector-0.3.4.dist-info → vector_inspector-0.3.6.dist-info}/METADATA +7 -6
- {vector_inspector-0.3.4.dist-info → vector_inspector-0.3.6.dist-info}/RECORD +19 -18
- {vector_inspector-0.3.4.dist-info → vector_inspector-0.3.6.dist-info}/WHEEL +0 -0
- {vector_inspector-0.3.4.dist-info → vector_inspector-0.3.6.dist-info}/entry_points.txt +0 -0
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional, Tuple
|
|
4
4
|
from PySide6.QtWidgets import (
|
|
5
|
-
QDialog,
|
|
6
|
-
|
|
7
|
-
|
|
5
|
+
QDialog,
|
|
6
|
+
QVBoxLayout,
|
|
7
|
+
QHBoxLayout,
|
|
8
|
+
QLabel,
|
|
9
|
+
QComboBox,
|
|
10
|
+
QPushButton,
|
|
11
|
+
QGroupBox,
|
|
12
|
+
QTextEdit,
|
|
13
|
+
QMessageBox,
|
|
14
|
+
QLineEdit,
|
|
15
|
+
QFormLayout,
|
|
8
16
|
)
|
|
9
17
|
from PySide6.QtCore import Qt
|
|
10
18
|
|
|
@@ -14,12 +22,16 @@ from vector_inspector.core.model_registry import get_model_registry
|
|
|
14
22
|
|
|
15
23
|
class EmbeddingConfigDialog(QDialog):
|
|
16
24
|
"""Dialog for selecting embedding model for a collection."""
|
|
17
|
-
|
|
18
|
-
def __init__(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
25
|
+
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
collection_name: str,
|
|
29
|
+
vector_dimension: int,
|
|
30
|
+
provider_type: Optional[str] = None,
|
|
31
|
+
current_model: Optional[str] = None,
|
|
32
|
+
current_type: Optional[str] = None,
|
|
33
|
+
parent=None,
|
|
34
|
+
):
|
|
23
35
|
super().__init__(parent)
|
|
24
36
|
self.collection_name = collection_name
|
|
25
37
|
self.vector_dimension = vector_dimension
|
|
@@ -28,7 +40,7 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
28
40
|
self.current_type = current_type
|
|
29
41
|
self.selected_model = None
|
|
30
42
|
self.selected_type = None
|
|
31
|
-
|
|
43
|
+
|
|
32
44
|
# Determine title based on provider type
|
|
33
45
|
if provider_type == "custom":
|
|
34
46
|
title = "Enter Custom Model"
|
|
@@ -39,17 +51,17 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
39
51
|
"openai": "OpenAI API",
|
|
40
52
|
"cohere": "Cohere API",
|
|
41
53
|
"vertex-ai": "Google Vertex AI",
|
|
42
|
-
"voyage": "Voyage AI"
|
|
54
|
+
"voyage": "Voyage AI",
|
|
43
55
|
}
|
|
44
56
|
type_name = type_names.get(provider_type, provider_type.title())
|
|
45
57
|
title = f"Select Model: {type_name}"
|
|
46
58
|
else:
|
|
47
59
|
title = f"Configure Embedding Model - {collection_name}"
|
|
48
|
-
|
|
60
|
+
|
|
49
61
|
self.setWindowTitle(title)
|
|
50
62
|
self.setMinimumWidth(500)
|
|
51
63
|
self._setup_ui()
|
|
52
|
-
|
|
64
|
+
|
|
53
65
|
def _setup_ui(self):
|
|
54
66
|
"""Setup dialog UI."""
|
|
55
67
|
layout = QVBoxLayout(self)
|
|
@@ -58,54 +70,57 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
58
70
|
if self.provider_type == "custom":
|
|
59
71
|
self._setup_custom_ui(layout)
|
|
60
72
|
return
|
|
61
|
-
|
|
73
|
+
|
|
62
74
|
# Info section
|
|
63
75
|
info_group = QGroupBox("Collection Information")
|
|
64
76
|
info_layout = QVBoxLayout()
|
|
65
|
-
|
|
77
|
+
|
|
66
78
|
info_layout.addWidget(QLabel(f"<b>Collection:</b> {self.collection_name}"))
|
|
67
79
|
info_layout.addWidget(QLabel(f"<b>Vector Dimension:</b> {self.vector_dimension}"))
|
|
68
|
-
|
|
80
|
+
|
|
69
81
|
if self.current_model:
|
|
70
|
-
info_layout.addWidget(
|
|
82
|
+
info_layout.addWidget(
|
|
83
|
+
QLabel(f"<b>Current Model:</b> {self.current_model} ({self.current_type})")
|
|
84
|
+
)
|
|
71
85
|
else:
|
|
72
86
|
warning = QLabel("⚠️ No embedding model configured - using automatic detection")
|
|
73
87
|
warning.setStyleSheet("color: orange;")
|
|
74
88
|
info_layout.addWidget(warning)
|
|
75
|
-
|
|
89
|
+
|
|
76
90
|
info_group.setLayout(info_layout)
|
|
77
91
|
layout.addWidget(info_group)
|
|
78
|
-
|
|
92
|
+
|
|
79
93
|
# Model selection section
|
|
80
94
|
model_group = QGroupBox("Embedding Model Selection")
|
|
81
95
|
model_layout = QVBoxLayout()
|
|
82
|
-
|
|
96
|
+
|
|
83
97
|
# Get available models for this dimension, filtered by provider type
|
|
84
98
|
if self.provider_type:
|
|
85
99
|
registry = get_model_registry()
|
|
86
100
|
registry_models = registry.get_models_by_dimension(self.vector_dimension)
|
|
87
101
|
filtered_models = [m for m in registry_models if m.type == self.provider_type]
|
|
88
102
|
available_models = [(m.name, m.type, m.description) for m in filtered_models]
|
|
89
|
-
|
|
103
|
+
|
|
90
104
|
# Add custom models from settings
|
|
91
105
|
try:
|
|
92
|
-
from
|
|
106
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
107
|
+
|
|
93
108
|
settings = SettingsService()
|
|
94
109
|
custom_models = settings.get_custom_embedding_models(self.vector_dimension)
|
|
95
110
|
for model in custom_models:
|
|
96
111
|
if model["type"] == self.provider_type:
|
|
97
|
-
available_models.append(
|
|
98
|
-
model["name"],
|
|
99
|
-
|
|
100
|
-
f"{model['description']} (custom)"
|
|
101
|
-
))
|
|
112
|
+
available_models.append(
|
|
113
|
+
(model["name"], model["type"], f"{model['description']} (custom)")
|
|
114
|
+
)
|
|
102
115
|
except Exception:
|
|
103
116
|
pass
|
|
104
117
|
else:
|
|
105
118
|
available_models = get_available_models_for_dimension(self.vector_dimension)
|
|
106
|
-
|
|
119
|
+
|
|
107
120
|
if available_models:
|
|
108
|
-
model_layout.addWidget(
|
|
121
|
+
model_layout.addWidget(
|
|
122
|
+
QLabel(f"Available models for {self.vector_dimension}-dimensional vectors:")
|
|
123
|
+
)
|
|
109
124
|
|
|
110
125
|
self.model_combo = QComboBox()
|
|
111
126
|
for model_name, model_type, description in available_models:
|
|
@@ -129,7 +144,9 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
129
144
|
self.description_text = QTextEdit()
|
|
130
145
|
self.description_text.setReadOnly(True)
|
|
131
146
|
self.description_text.setMaximumHeight(100)
|
|
132
|
-
self.description_text.setStyleSheet(
|
|
147
|
+
self.description_text.setStyleSheet(
|
|
148
|
+
"background-color: #f5f5f5; border: 1px solid #ccc; color: #000000;"
|
|
149
|
+
)
|
|
133
150
|
model_layout.addWidget(self.description_text)
|
|
134
151
|
|
|
135
152
|
# Update description when selection changes
|
|
@@ -139,7 +156,9 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
139
156
|
else:
|
|
140
157
|
# No models for this type + dimension
|
|
141
158
|
type_name = self.provider_type or "any type"
|
|
142
|
-
warning = QLabel(
|
|
159
|
+
warning = QLabel(
|
|
160
|
+
f"⚠️ No models of type '{type_name}' available for {self.vector_dimension} dimensions."
|
|
161
|
+
)
|
|
143
162
|
warning.setWordWrap(True)
|
|
144
163
|
model_layout.addWidget(warning)
|
|
145
164
|
|
|
@@ -149,32 +168,32 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
149
168
|
model_layout.addWidget(QLabel(dims_text))
|
|
150
169
|
|
|
151
170
|
self.model_combo = None
|
|
152
|
-
|
|
171
|
+
|
|
153
172
|
model_group.setLayout(model_layout)
|
|
154
173
|
layout.addWidget(model_group)
|
|
155
|
-
|
|
174
|
+
|
|
156
175
|
# Buttons
|
|
157
176
|
button_layout = QHBoxLayout()
|
|
158
177
|
button_layout.addStretch()
|
|
159
|
-
|
|
178
|
+
|
|
160
179
|
self.save_btn = QPushButton("Save Configuration")
|
|
161
180
|
self.save_btn.clicked.connect(self._on_save)
|
|
162
181
|
# Always enabled - user can choose from combo OR enter custom
|
|
163
182
|
self.save_btn.setEnabled(True)
|
|
164
|
-
|
|
183
|
+
|
|
165
184
|
self.clear_btn = QPushButton("Clear Configuration")
|
|
166
185
|
self.clear_btn.clicked.connect(self._clear_config)
|
|
167
186
|
self.clear_btn.setEnabled(self.current_model is not None)
|
|
168
|
-
|
|
187
|
+
|
|
169
188
|
cancel_btn = QPushButton("Cancel")
|
|
170
189
|
cancel_btn.clicked.connect(self.reject)
|
|
171
|
-
|
|
190
|
+
|
|
172
191
|
button_layout.addWidget(self.save_btn)
|
|
173
192
|
button_layout.addWidget(self.clear_btn)
|
|
174
193
|
button_layout.addWidget(cancel_btn)
|
|
175
|
-
|
|
194
|
+
|
|
176
195
|
layout.addLayout(button_layout)
|
|
177
|
-
|
|
196
|
+
|
|
178
197
|
def _setup_custom_ui(self, layout):
|
|
179
198
|
"""Setup UI for custom model entry."""
|
|
180
199
|
# Info section
|
|
@@ -194,14 +213,18 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
194
213
|
custom_layout.addRow("Model Name:", self.custom_name_input)
|
|
195
214
|
|
|
196
215
|
self.custom_type_combo = QComboBox()
|
|
197
|
-
self.custom_type_combo.addItems(
|
|
216
|
+
self.custom_type_combo.addItems(
|
|
217
|
+
["sentence-transformer", "clip", "openai", "cohere", "vertex-ai", "voyage", "custom"]
|
|
218
|
+
)
|
|
198
219
|
custom_layout.addRow("Model Type:", self.custom_type_combo)
|
|
199
220
|
|
|
200
221
|
self.custom_desc_input = QLineEdit()
|
|
201
222
|
self.custom_desc_input.setPlaceholderText("Brief description (optional)")
|
|
202
223
|
custom_layout.addRow("Description:", self.custom_desc_input)
|
|
203
224
|
|
|
204
|
-
custom_note = QLabel(
|
|
225
|
+
custom_note = QLabel(
|
|
226
|
+
"💡 Custom models will be saved and available for future use with this dimension."
|
|
227
|
+
)
|
|
205
228
|
custom_note.setWordWrap(True)
|
|
206
229
|
custom_note.setStyleSheet("color: #666; font-size: 10px; padding: 4px;")
|
|
207
230
|
custom_layout.addRow(custom_note)
|
|
@@ -227,7 +250,7 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
227
250
|
|
|
228
251
|
# No combo or description for custom mode
|
|
229
252
|
self.model_combo = None
|
|
230
|
-
|
|
253
|
+
|
|
231
254
|
def _save_custom_model(self):
|
|
232
255
|
"""Save custom model entry."""
|
|
233
256
|
custom_name = self.custom_name_input.text().strip()
|
|
@@ -240,27 +263,28 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
240
263
|
|
|
241
264
|
# Save custom model to registry
|
|
242
265
|
from vector_inspector.services.settings_service import SettingsService
|
|
266
|
+
|
|
243
267
|
settings = SettingsService()
|
|
244
268
|
|
|
245
269
|
settings.add_custom_embedding_model(
|
|
246
270
|
model_name=custom_name,
|
|
247
271
|
dimension=self.vector_dimension,
|
|
248
272
|
model_type=custom_type,
|
|
249
|
-
description=custom_desc if custom_desc else f"Custom {custom_type} model"
|
|
273
|
+
description=custom_desc if custom_desc else f"Custom {custom_type} model",
|
|
250
274
|
)
|
|
251
275
|
|
|
252
276
|
# Set selection to custom model
|
|
253
277
|
self.selected_model = custom_name
|
|
254
278
|
self.selected_type = custom_type
|
|
255
279
|
self.accept()
|
|
256
|
-
|
|
280
|
+
|
|
257
281
|
def _update_description(self):
|
|
258
282
|
"""Update the description text based on selected model."""
|
|
259
283
|
if not self.model_combo:
|
|
260
284
|
return
|
|
261
|
-
|
|
285
|
+
|
|
262
286
|
model_name, model_type = self.model_combo.currentData()
|
|
263
|
-
|
|
287
|
+
|
|
264
288
|
descriptions = {
|
|
265
289
|
"sentence-transformer": (
|
|
266
290
|
"Sentence-Transformers are text-only embedding models optimized for semantic similarity. "
|
|
@@ -270,17 +294,14 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
270
294
|
"CLIP (Contrastive Language-Image Pre-training) is a multi-modal model that can embed both "
|
|
271
295
|
"text and images into the same vector space. This allows text queries to find semantically "
|
|
272
296
|
"similar images, and vice versa. Perfect for image search with text descriptions."
|
|
273
|
-
)
|
|
297
|
+
),
|
|
274
298
|
}
|
|
275
|
-
|
|
299
|
+
|
|
276
300
|
desc = descriptions.get(model_type, "Embedding model for vector similarity search.")
|
|
277
301
|
self.description_text.setPlainText(
|
|
278
|
-
f"Model: {model_name}\n"
|
|
279
|
-
f"Type: {model_type}\n"
|
|
280
|
-
f"Dimension: {self.vector_dimension}\n\n"
|
|
281
|
-
f"{desc}"
|
|
302
|
+
f"Model: {model_name}\nType: {model_type}\nDimension: {self.vector_dimension}\n\n{desc}"
|
|
282
303
|
)
|
|
283
|
-
|
|
304
|
+
|
|
284
305
|
def _on_save(self):
|
|
285
306
|
"""Handle save button click."""
|
|
286
307
|
if self.model_combo and self.model_combo.currentData():
|
|
@@ -291,23 +312,23 @@ class EmbeddingConfigDialog(QDialog):
|
|
|
291
312
|
else:
|
|
292
313
|
QMessageBox.warning(self, "No Selection", "Please select a model from the list.")
|
|
293
314
|
return
|
|
294
|
-
|
|
315
|
+
|
|
295
316
|
self.accept()
|
|
296
|
-
|
|
317
|
+
|
|
297
318
|
def _clear_config(self):
|
|
298
319
|
"""Clear the embedding model configuration."""
|
|
299
320
|
reply = QMessageBox.question(
|
|
300
321
|
self,
|
|
301
322
|
"Clear Configuration",
|
|
302
323
|
"This will remove the custom embedding model configuration and use automatic detection. Continue?",
|
|
303
|
-
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No
|
|
324
|
+
QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
|
|
304
325
|
)
|
|
305
|
-
|
|
326
|
+
|
|
306
327
|
if reply == QMessageBox.StandardButton.Yes:
|
|
307
328
|
self.selected_model = None
|
|
308
329
|
self.selected_type = None
|
|
309
330
|
self.done(2) # Custom code for "clear"
|
|
310
|
-
|
|
331
|
+
|
|
311
332
|
def get_selection(self) -> Optional[Tuple[str, str]]:
|
|
312
333
|
"""Get the selected model and type (from either combo or custom entry)."""
|
|
313
334
|
if self.selected_model and self.selected_type:
|
|
@@ -24,6 +24,7 @@ from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
|
24
24
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
25
25
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
26
26
|
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
27
|
+
from vector_inspector.core.connections.pgvector_connection import PgVectorConnection
|
|
27
28
|
from vector_inspector.services.profile_service import ProfileService
|
|
28
29
|
from vector_inspector.services.settings_service import SettingsService
|
|
29
30
|
from vector_inspector.ui.components.connection_manager_panel import ConnectionManagerPanel
|
|
@@ -433,6 +434,8 @@ class MainWindow(QMainWindow):
|
|
|
433
434
|
connection = self._create_qdrant_connection(config, credentials)
|
|
434
435
|
elif provider == "pinecone":
|
|
435
436
|
connection = self._create_pinecone_connection(config, credentials)
|
|
437
|
+
elif provider == "pgvector":
|
|
438
|
+
connection = self._create_pgvector_connection(config, credentials)
|
|
436
439
|
else:
|
|
437
440
|
QMessageBox.warning(self, "Error", f"Unsupported provider: {provider}")
|
|
438
441
|
return
|
|
@@ -500,6 +503,25 @@ class MainWindow(QMainWindow):
|
|
|
500
503
|
|
|
501
504
|
return PineconeConnection(api_key=api_key)
|
|
502
505
|
|
|
506
|
+
def _create_pgvector_connection(self, config: dict, credentials: dict) -> PgVectorConnection:
|
|
507
|
+
"""Create a PgVector/Postgres connection from profile config/credentials."""
|
|
508
|
+
conn_type = config.get("type")
|
|
509
|
+
|
|
510
|
+
# We expect HTTP-style profile for pgvector (host/port + db creds)
|
|
511
|
+
if conn_type == "http":
|
|
512
|
+
host = config.get("host", "localhost")
|
|
513
|
+
port = int(config.get("port", 5432))
|
|
514
|
+
database = config.get("database")
|
|
515
|
+
user = config.get("user")
|
|
516
|
+
# Prefer password from credentials
|
|
517
|
+
password = credentials.get("password")
|
|
518
|
+
|
|
519
|
+
return PgVectorConnection(
|
|
520
|
+
host=host, port=port, database=database, user=user, password=password
|
|
521
|
+
)
|
|
522
|
+
|
|
523
|
+
raise ValueError("Unsupported connection type for PgVector profile")
|
|
524
|
+
|
|
503
525
|
def _on_connection_finished(
|
|
504
526
|
self, connection_id: str, success: bool, collections: list, error: str
|
|
505
527
|
):
|