vector-inspector 0.2.7__py3-none-any.whl → 0.3.1__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/config/__init__.py +4 -0
- vector_inspector/config/known_embedding_models.json +432 -0
- vector_inspector/core/connections/__init__.py +2 -1
- vector_inspector/core/connections/base_connection.py +42 -1
- vector_inspector/core/connections/chroma_connection.py +47 -11
- vector_inspector/core/connections/pinecone_connection.py +768 -0
- vector_inspector/core/embedding_providers/__init__.py +14 -0
- vector_inspector/core/embedding_providers/base_provider.py +128 -0
- vector_inspector/core/embedding_providers/clip_provider.py +260 -0
- vector_inspector/core/embedding_providers/provider_factory.py +176 -0
- vector_inspector/core/embedding_providers/sentence_transformer_provider.py +203 -0
- vector_inspector/core/embedding_utils.py +69 -42
- vector_inspector/core/model_registry.py +205 -0
- vector_inspector/services/backup_restore_service.py +16 -0
- vector_inspector/services/settings_service.py +117 -1
- vector_inspector/ui/components/connection_manager_panel.py +7 -0
- vector_inspector/ui/components/profile_manager_panel.py +61 -14
- vector_inspector/ui/dialogs/__init__.py +2 -1
- vector_inspector/ui/dialogs/cross_db_migration.py +20 -1
- vector_inspector/ui/dialogs/embedding_config_dialog.py +166 -27
- vector_inspector/ui/dialogs/provider_type_dialog.py +189 -0
- vector_inspector/ui/main_window.py +33 -2
- vector_inspector/ui/views/connection_view.py +55 -10
- vector_inspector/ui/views/info_panel.py +83 -36
- vector_inspector/ui/views/search_view.py +1 -1
- vector_inspector/ui/views/visualization_view.py +19 -5
- {vector_inspector-0.2.7.dist-info → vector_inspector-0.3.1.dist-info}/METADATA +1 -1
- vector_inspector-0.3.1.dist-info/RECORD +55 -0
- vector_inspector-0.2.7.dist-info/RECORD +0 -45
- {vector_inspector-0.2.7.dist-info → vector_inspector-0.3.1.dist-info}/WHEEL +0 -0
- {vector_inspector-0.2.7.dist-info → vector_inspector-0.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,189 @@
|
|
|
1
|
+
"""Dialog for selecting embedding provider type (Step 1 of model selection)."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional, Tuple
|
|
4
|
+
from PySide6.QtWidgets import (
|
|
5
|
+
QDialog, QVBoxLayout, QHBoxLayout, QLabel,
|
|
6
|
+
QPushButton, QRadioButton, QButtonGroup, QGroupBox,
|
|
7
|
+
QScrollArea, QWidget
|
|
8
|
+
)
|
|
9
|
+
from PySide6.QtCore import Qt
|
|
10
|
+
|
|
11
|
+
from vector_inspector.core.model_registry import get_model_registry
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ProviderTypeDialog(QDialog):
|
|
15
|
+
"""Dialog for selecting the provider/type category before choosing specific model."""
|
|
16
|
+
|
|
17
|
+
# Provider categories with display info
|
|
18
|
+
PROVIDER_CATEGORIES = [
|
|
19
|
+
{
|
|
20
|
+
"id": "sentence-transformer",
|
|
21
|
+
"name": "🤗 Sentence Transformers",
|
|
22
|
+
"description": "Local models from HuggingFace\nNo API key required, runs on your machine",
|
|
23
|
+
"filter_type": "sentence-transformer",
|
|
24
|
+
"icon": "📦"
|
|
25
|
+
},
|
|
26
|
+
{
|
|
27
|
+
"id": "clip",
|
|
28
|
+
"name": "🖼️ CLIP Models",
|
|
29
|
+
"description": "Multimodal embeddings (text + images)\nLocal models, no API key required",
|
|
30
|
+
"filter_type": "clip",
|
|
31
|
+
"icon": "🎨"
|
|
32
|
+
},
|
|
33
|
+
{
|
|
34
|
+
"id": "openai",
|
|
35
|
+
"name": "☁️ OpenAI API",
|
|
36
|
+
"description": "Cloud-based embeddings\nRequires OpenAI API key",
|
|
37
|
+
"filter_type": "openai",
|
|
38
|
+
"icon": "🔑"
|
|
39
|
+
},
|
|
40
|
+
{
|
|
41
|
+
"id": "cohere",
|
|
42
|
+
"name": "☁️ Cohere API",
|
|
43
|
+
"description": "Cloud-based embeddings\nRequires Cohere API key",
|
|
44
|
+
"filter_type": "cohere",
|
|
45
|
+
"icon": "🔑"
|
|
46
|
+
},
|
|
47
|
+
{
|
|
48
|
+
"id": "vertex-ai",
|
|
49
|
+
"name": "☁️ Google Vertex AI",
|
|
50
|
+
"description": "Cloud-based embeddings\nRequires Google Cloud credentials",
|
|
51
|
+
"filter_type": "vertex-ai",
|
|
52
|
+
"icon": "🔑"
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
"id": "voyage",
|
|
56
|
+
"name": "☁️ Voyage AI",
|
|
57
|
+
"description": "Cloud-based embeddings\nRequires Voyage API key",
|
|
58
|
+
"filter_type": "voyage",
|
|
59
|
+
"icon": "🔑"
|
|
60
|
+
},
|
|
61
|
+
{
|
|
62
|
+
"id": "custom",
|
|
63
|
+
"name": "✏️ Custom Model",
|
|
64
|
+
"description": "Enter your own model name\nFor models not in the registry",
|
|
65
|
+
"filter_type": None, # Special case
|
|
66
|
+
"icon": "⚙️"
|
|
67
|
+
}
|
|
68
|
+
]
|
|
69
|
+
|
|
70
|
+
def __init__(self, collection_name: str, vector_dimension: int, parent=None):
|
|
71
|
+
super().__init__(parent)
|
|
72
|
+
self.collection_name = collection_name
|
|
73
|
+
self.vector_dimension = vector_dimension
|
|
74
|
+
self.selected_type = None
|
|
75
|
+
|
|
76
|
+
self.setWindowTitle("Select Embedding Provider Type")
|
|
77
|
+
self.setMinimumWidth(550)
|
|
78
|
+
self.setMinimumHeight(500)
|
|
79
|
+
self._setup_ui()
|
|
80
|
+
|
|
81
|
+
def _setup_ui(self):
|
|
82
|
+
"""Setup dialog UI."""
|
|
83
|
+
layout = QVBoxLayout(self)
|
|
84
|
+
|
|
85
|
+
# Header
|
|
86
|
+
header = QLabel(f"<h3>Select Provider Type</h3>")
|
|
87
|
+
layout.addWidget(header)
|
|
88
|
+
|
|
89
|
+
info = QLabel(f"<b>Collection:</b> {self.collection_name}<br>"
|
|
90
|
+
f"<b>Vector Dimension:</b> {self.vector_dimension}")
|
|
91
|
+
layout.addWidget(info)
|
|
92
|
+
|
|
93
|
+
layout.addWidget(QLabel("<i>Choose the type of embedding provider to use:</i>"))
|
|
94
|
+
|
|
95
|
+
# Scroll area for provider options
|
|
96
|
+
scroll = QScrollArea()
|
|
97
|
+
scroll.setWidgetResizable(True)
|
|
98
|
+
scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
|
|
99
|
+
|
|
100
|
+
scroll_widget = QWidget()
|
|
101
|
+
scroll_layout = QVBoxLayout(scroll_widget)
|
|
102
|
+
scroll_layout.setSpacing(10)
|
|
103
|
+
|
|
104
|
+
# Radio button group
|
|
105
|
+
self.button_group = QButtonGroup(self)
|
|
106
|
+
|
|
107
|
+
# Get registry to count models
|
|
108
|
+
registry = get_model_registry()
|
|
109
|
+
|
|
110
|
+
# Create radio buttons for each provider type
|
|
111
|
+
for i, category in enumerate(self.PROVIDER_CATEGORIES):
|
|
112
|
+
provider_id = category["id"]
|
|
113
|
+
|
|
114
|
+
# Count available models for this type + dimension
|
|
115
|
+
if provider_id == "custom":
|
|
116
|
+
count_text = "Enter manually"
|
|
117
|
+
else:
|
|
118
|
+
filter_type = category["filter_type"]
|
|
119
|
+
models = registry.get_models_by_dimension(self.vector_dimension)
|
|
120
|
+
matching = [m for m in models if m.type == filter_type]
|
|
121
|
+
count = len(matching)
|
|
122
|
+
|
|
123
|
+
if count == 0:
|
|
124
|
+
continue # Skip categories with no models for this dimension
|
|
125
|
+
|
|
126
|
+
count_text = f"{count} model{'s' if count != 1 else ''} available"
|
|
127
|
+
|
|
128
|
+
# Create group box for this option
|
|
129
|
+
group = QGroupBox()
|
|
130
|
+
group_layout = QVBoxLayout()
|
|
131
|
+
|
|
132
|
+
# Radio button
|
|
133
|
+
radio = QRadioButton(category["name"])
|
|
134
|
+
radio.setProperty("provider_id", provider_id)
|
|
135
|
+
self.button_group.addButton(radio, i)
|
|
136
|
+
group_layout.addWidget(radio)
|
|
137
|
+
|
|
138
|
+
# Description
|
|
139
|
+
desc_label = QLabel(category["description"])
|
|
140
|
+
desc_label.setStyleSheet("color: gray; margin-left: 25px;")
|
|
141
|
+
desc_label.setWordWrap(True)
|
|
142
|
+
group_layout.addWidget(desc_label)
|
|
143
|
+
|
|
144
|
+
# Count
|
|
145
|
+
count_label = QLabel(f"<b>{count_text}</b>")
|
|
146
|
+
count_label.setStyleSheet("margin-left: 25px; color: #0066cc;")
|
|
147
|
+
group_layout.addWidget(count_label)
|
|
148
|
+
|
|
149
|
+
group.setLayout(group_layout)
|
|
150
|
+
scroll_layout.addWidget(group)
|
|
151
|
+
|
|
152
|
+
scroll_layout.addStretch()
|
|
153
|
+
scroll.setWidget(scroll_widget)
|
|
154
|
+
layout.addWidget(scroll)
|
|
155
|
+
|
|
156
|
+
# Buttons
|
|
157
|
+
button_layout = QHBoxLayout()
|
|
158
|
+
button_layout.addStretch()
|
|
159
|
+
|
|
160
|
+
cancel_btn = QPushButton("Cancel")
|
|
161
|
+
cancel_btn.clicked.connect(self.reject)
|
|
162
|
+
|
|
163
|
+
self.next_btn = QPushButton("Next →")
|
|
164
|
+
self.next_btn.clicked.connect(self._on_next)
|
|
165
|
+
self.next_btn.setEnabled(False)
|
|
166
|
+
self.next_btn.setDefault(True)
|
|
167
|
+
|
|
168
|
+
# Enable Next when selection is made
|
|
169
|
+
self.button_group.buttonClicked.connect(lambda: self.next_btn.setEnabled(True))
|
|
170
|
+
|
|
171
|
+
button_layout.addWidget(cancel_btn)
|
|
172
|
+
button_layout.addWidget(self.next_btn)
|
|
173
|
+
|
|
174
|
+
layout.addLayout(button_layout)
|
|
175
|
+
|
|
176
|
+
def _on_next(self):
|
|
177
|
+
"""Handle Next button click."""
|
|
178
|
+
selected_button = self.button_group.checkedButton()
|
|
179
|
+
if selected_button:
|
|
180
|
+
self.selected_type = selected_button.property("provider_id")
|
|
181
|
+
self.accept()
|
|
182
|
+
|
|
183
|
+
def get_selected_type(self) -> Optional[str]:
|
|
184
|
+
"""Get the selected provider type ID.
|
|
185
|
+
|
|
186
|
+
Returns:
|
|
187
|
+
Provider type ID or None if cancelled
|
|
188
|
+
"""
|
|
189
|
+
return self.selected_type
|
|
@@ -12,6 +12,7 @@ from vector_inspector.core.connection_manager import ConnectionManager, Connecti
|
|
|
12
12
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
13
13
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
14
14
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
15
|
+
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
15
16
|
from vector_inspector.services.profile_service import ProfileService
|
|
16
17
|
from vector_inspector.services.settings_service import SettingsService
|
|
17
18
|
from vector_inspector.ui.components.connection_manager_panel import ConnectionManagerPanel
|
|
@@ -219,6 +220,7 @@ class MainWindow(QMainWindow):
|
|
|
219
220
|
self.connection_manager.active_connection_changed.connect(self._on_active_connection_changed)
|
|
220
221
|
self.connection_manager.active_collection_changed.connect(self._on_active_collection_changed)
|
|
221
222
|
self.connection_manager.collections_updated.connect(self._on_collections_updated)
|
|
223
|
+
self.connection_manager.connection_opened.connect(self._on_connection_opened)
|
|
222
224
|
|
|
223
225
|
# Connection panel signals
|
|
224
226
|
self.connection_panel.collection_selected.connect(self._on_collection_selected_from_panel)
|
|
@@ -277,17 +279,36 @@ class MainWindow(QMainWindow):
|
|
|
277
279
|
|
|
278
280
|
# Update views if this is the active connection
|
|
279
281
|
if connection_id == self.connection_manager.get_active_connection_id():
|
|
282
|
+
# Show loading immediately when collection changes
|
|
280
283
|
if collection_name:
|
|
281
|
-
self.
|
|
284
|
+
self.loading_dialog.show_loading(f"Loading collection '{collection_name}'...")
|
|
285
|
+
QApplication.processEvents()
|
|
286
|
+
try:
|
|
287
|
+
self._update_views_for_collection(collection_name)
|
|
288
|
+
finally:
|
|
289
|
+
self.loading_dialog.hide_loading()
|
|
282
290
|
else:
|
|
283
291
|
# Clear collection from views
|
|
284
|
-
self.
|
|
292
|
+
self.loading_dialog.show_loading("Clearing collection...")
|
|
293
|
+
QApplication.processEvents()
|
|
294
|
+
try:
|
|
295
|
+
self._update_views_for_collection(None)
|
|
296
|
+
finally:
|
|
297
|
+
self.loading_dialog.hide_loading()
|
|
285
298
|
|
|
286
299
|
def _on_collections_updated(self, connection_id: str, collections: list):
|
|
287
300
|
"""Handle collections list updated."""
|
|
288
301
|
# UI automatically updates via connection_manager_panel
|
|
289
302
|
pass
|
|
290
303
|
|
|
304
|
+
def _on_connection_opened(self, connection_id: str):
|
|
305
|
+
"""Handle connection successfully opened."""
|
|
306
|
+
# If this is the active connection, refresh the info panel
|
|
307
|
+
if connection_id == self.connection_manager.get_active_connection_id():
|
|
308
|
+
instance = self.connection_manager.get_connection(connection_id)
|
|
309
|
+
if instance and instance.connection:
|
|
310
|
+
self.info_panel.refresh_database_info()
|
|
311
|
+
|
|
291
312
|
def _on_collection_selected_from_panel(self, connection_id: str, collection_name: str):
|
|
292
313
|
"""Handle collection selection from connection panel."""
|
|
293
314
|
# Show loading dialog while switching collections
|
|
@@ -378,6 +399,8 @@ class MainWindow(QMainWindow):
|
|
|
378
399
|
connection = self._create_chroma_connection(config, credentials)
|
|
379
400
|
elif provider == "qdrant":
|
|
380
401
|
connection = self._create_qdrant_connection(config, credentials)
|
|
402
|
+
elif provider == "pinecone":
|
|
403
|
+
connection = self._create_pinecone_connection(config, credentials)
|
|
381
404
|
else:
|
|
382
405
|
QMessageBox.warning(self, "Error", f"Unsupported provider: {provider}")
|
|
383
406
|
return
|
|
@@ -439,6 +462,14 @@ class MainWindow(QMainWindow):
|
|
|
439
462
|
else: # ephemeral
|
|
440
463
|
return QdrantConnection()
|
|
441
464
|
|
|
465
|
+
def _create_pinecone_connection(self, config: dict, credentials: dict) -> PineconeConnection:
|
|
466
|
+
"""Create a Pinecone connection."""
|
|
467
|
+
api_key = credentials.get("api_key")
|
|
468
|
+
if not api_key:
|
|
469
|
+
raise ValueError("Pinecone requires an API key")
|
|
470
|
+
|
|
471
|
+
return PineconeConnection(api_key=api_key)
|
|
472
|
+
|
|
442
473
|
def _on_connection_finished(self, connection_id: str, success: bool, collections: list, error: str):
|
|
443
474
|
"""Handle connection thread completion."""
|
|
444
475
|
self.loading_dialog.hide_loading()
|
|
@@ -10,6 +10,7 @@ from PySide6.QtCore import Signal, QThread
|
|
|
10
10
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
11
11
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
12
12
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
13
|
+
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
13
14
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
14
15
|
from vector_inspector.services.settings_service import SettingsService
|
|
15
16
|
|
|
@@ -66,6 +67,7 @@ class ConnectionDialog(QDialog):
|
|
|
66
67
|
self.provider_combo = QComboBox()
|
|
67
68
|
self.provider_combo.addItem("ChromaDB", "chromadb")
|
|
68
69
|
self.provider_combo.addItem("Qdrant", "qdrant")
|
|
70
|
+
self.provider_combo.addItem("Pinecone", "pinecone")
|
|
69
71
|
self.provider_combo.currentIndexChanged.connect(self._on_provider_changed)
|
|
70
72
|
provider_layout.addWidget(self.provider_combo)
|
|
71
73
|
provider_group.setLayout(provider_layout)
|
|
@@ -179,19 +181,42 @@ class ConnectionDialog(QDialog):
|
|
|
179
181
|
if self.port_input.text() == "6333":
|
|
180
182
|
self.port_input.setText("8000")
|
|
181
183
|
|
|
182
|
-
#
|
|
183
|
-
|
|
184
|
-
|
|
184
|
+
# For Pinecone, hide persistent/HTTP options and only show API key
|
|
185
|
+
if self.provider == "pinecone":
|
|
186
|
+
self.persistent_radio.setEnabled(False)
|
|
187
|
+
self.http_radio.setEnabled(True)
|
|
188
|
+
self.http_radio.setChecked(True)
|
|
189
|
+
self.ephemeral_radio.setEnabled(False)
|
|
190
|
+
self.path_input.setEnabled(False)
|
|
191
|
+
self.host_input.setEnabled(False)
|
|
192
|
+
self.port_input.setEnabled(False)
|
|
193
|
+
self.api_key_input.setEnabled(True)
|
|
194
|
+
else:
|
|
195
|
+
self.persistent_radio.setEnabled(True)
|
|
196
|
+
self.http_radio.setEnabled(True)
|
|
197
|
+
self.ephemeral_radio.setEnabled(True)
|
|
198
|
+
# Show/hide API key field
|
|
199
|
+
is_http = self.http_radio.isChecked()
|
|
200
|
+
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
201
|
+
# Update path/host/port based on connection type
|
|
202
|
+
self._on_type_changed()
|
|
185
203
|
|
|
186
204
|
def _on_type_changed(self):
|
|
187
205
|
"""Handle connection type change."""
|
|
188
206
|
is_persistent = self.persistent_radio.isChecked()
|
|
189
207
|
is_http = self.http_radio.isChecked()
|
|
190
208
|
|
|
191
|
-
|
|
192
|
-
self.
|
|
193
|
-
|
|
194
|
-
|
|
209
|
+
# Pinecone always uses API key, no path/host/port
|
|
210
|
+
if self.provider == "pinecone":
|
|
211
|
+
self.path_input.setEnabled(False)
|
|
212
|
+
self.host_input.setEnabled(False)
|
|
213
|
+
self.port_input.setEnabled(False)
|
|
214
|
+
self.api_key_input.setEnabled(True)
|
|
215
|
+
else:
|
|
216
|
+
self.path_input.setEnabled(is_persistent)
|
|
217
|
+
self.host_input.setEnabled(is_http)
|
|
218
|
+
self.port_input.setEnabled(is_http)
|
|
219
|
+
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
195
220
|
|
|
196
221
|
self._update_absolute_preview()
|
|
197
222
|
|
|
@@ -202,7 +227,13 @@ class ConnectionDialog(QDialog):
|
|
|
202
227
|
|
|
203
228
|
config = {"provider": self.provider}
|
|
204
229
|
|
|
205
|
-
|
|
230
|
+
# Pinecone only needs API key
|
|
231
|
+
if self.provider == "pinecone":
|
|
232
|
+
config.update({
|
|
233
|
+
"type": "cloud",
|
|
234
|
+
"api_key": self.api_key_input.text()
|
|
235
|
+
})
|
|
236
|
+
elif self.persistent_radio.isChecked():
|
|
206
237
|
config.update({"type": "persistent", "path": self.path_input.text()})
|
|
207
238
|
elif self.http_radio.isChecked():
|
|
208
239
|
config.update({
|
|
@@ -286,7 +317,13 @@ class ConnectionDialog(QDialog):
|
|
|
286
317
|
|
|
287
318
|
# Set connection type
|
|
288
319
|
conn_type = last_config.get("type", "persistent")
|
|
289
|
-
if conn_type == "
|
|
320
|
+
if conn_type == "cloud":
|
|
321
|
+
# Pinecone cloud connection
|
|
322
|
+
self.http_radio.setChecked(True)
|
|
323
|
+
api_key = last_config.get("api_key")
|
|
324
|
+
if api_key:
|
|
325
|
+
self.api_key_input.setText(api_key)
|
|
326
|
+
elif conn_type == "persistent":
|
|
290
327
|
self.persistent_radio.setChecked(True)
|
|
291
328
|
path = last_config.get("path", "")
|
|
292
329
|
if path:
|
|
@@ -370,7 +407,15 @@ class ConnectionView(QWidget):
|
|
|
370
407
|
conn_type = config.get("type")
|
|
371
408
|
|
|
372
409
|
# Create appropriate connection instance based on provider
|
|
373
|
-
if provider == "
|
|
410
|
+
if provider == "pinecone":
|
|
411
|
+
api_key = config.get("api_key")
|
|
412
|
+
if not api_key:
|
|
413
|
+
self.loading_dialog.hide_loading()
|
|
414
|
+
from PySide6.QtWidgets import QMessageBox
|
|
415
|
+
QMessageBox.warning(self, "Missing API Key", "Pinecone requires an API key to connect.")
|
|
416
|
+
return
|
|
417
|
+
self.connection = PineconeConnection(api_key=api_key)
|
|
418
|
+
elif provider == "qdrant":
|
|
374
419
|
if conn_type == "persistent":
|
|
375
420
|
self.connection = QdrantConnection(path=config.get("path"))
|
|
376
421
|
elif conn_type == "http":
|
|
@@ -7,10 +7,12 @@ from PySide6.QtWidgets import (
|
|
|
7
7
|
)
|
|
8
8
|
from PySide6.QtCore import Qt, QObject
|
|
9
9
|
from PySide6.QtWidgets import QDialog
|
|
10
|
+
from PySide6.QtWidgets import QApplication
|
|
10
11
|
|
|
11
12
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
12
13
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
13
14
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
15
|
+
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
14
16
|
from vector_inspector.core.cache_manager import get_cache_manager
|
|
15
17
|
|
|
16
18
|
|
|
@@ -199,6 +201,15 @@ class InfoPanel(QWidget):
|
|
|
199
201
|
self._update_label(self.api_key_label, "Present (hidden)")
|
|
200
202
|
else:
|
|
201
203
|
self._update_label(self.api_key_label, "Not configured")
|
|
204
|
+
|
|
205
|
+
elif isinstance(self.connection, PineconeConnection):
|
|
206
|
+
self._update_label(self.connection_type_label, "Cloud")
|
|
207
|
+
self._update_label(self.endpoint_label, "Pinecone Cloud")
|
|
208
|
+
|
|
209
|
+
if self.connection.api_key:
|
|
210
|
+
self._update_label(self.api_key_label, "Present (hidden)")
|
|
211
|
+
else:
|
|
212
|
+
self._update_label(self.api_key_label, "Not configured")
|
|
202
213
|
else:
|
|
203
214
|
self._update_label(self.connection_type_label, "Unknown")
|
|
204
215
|
self._update_label(self.endpoint_label, "N/A")
|
|
@@ -317,6 +328,18 @@ class InfoPanel(QWidget):
|
|
|
317
328
|
opt = config["optimizer_config"]
|
|
318
329
|
details_list.append(f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}")
|
|
319
330
|
|
|
331
|
+
elif isinstance(self.connection, PineconeConnection):
|
|
332
|
+
details_list.append("• Provider: Pinecone")
|
|
333
|
+
details_list.append("• Supports: Vectors, Metadata")
|
|
334
|
+
details_list.append("• Cloud-hosted vector database")
|
|
335
|
+
# Add Pinecone-specific info if available
|
|
336
|
+
if "host" in collection_info:
|
|
337
|
+
details_list.append(f"• Host: {collection_info['host']}")
|
|
338
|
+
if "status" in collection_info:
|
|
339
|
+
details_list.append(f"• Status: {collection_info['status']}")
|
|
340
|
+
if "spec" in collection_info:
|
|
341
|
+
details_list.append(f"• Spec: {collection_info['spec']}")
|
|
342
|
+
|
|
320
343
|
if details_list:
|
|
321
344
|
self.provider_details_label.setText("\n".join(details_list))
|
|
322
345
|
self.provider_details_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
|
|
@@ -358,18 +381,24 @@ class InfoPanel(QWidget):
|
|
|
358
381
|
# Check if stored in collection metadata
|
|
359
382
|
if 'embedding_model' in collection_info:
|
|
360
383
|
model_name = collection_info['embedding_model']
|
|
361
|
-
model_type = collection_info.get('embedding_model_type', '
|
|
384
|
+
model_type = collection_info.get('embedding_model_type', 'stored')
|
|
362
385
|
self.embedding_model_label.setText(f"{model_name} ({model_type})")
|
|
363
386
|
self.embedding_model_label.setStyleSheet("color: lightgreen;")
|
|
364
387
|
return
|
|
365
388
|
|
|
389
|
+
# Try to get from connection using the helper method
|
|
390
|
+
if self.connection and self.current_collection:
|
|
391
|
+
detected_model = self.connection.get_embedding_model(self.current_collection, self.connection_id)
|
|
392
|
+
if detected_model:
|
|
393
|
+
self.embedding_model_label.setText(f"{detected_model} (detected)")
|
|
394
|
+
self.embedding_model_label.setStyleSheet("color: lightgreen;")
|
|
395
|
+
return
|
|
396
|
+
|
|
366
397
|
# Check user settings
|
|
367
398
|
settings = SettingsService()
|
|
368
|
-
|
|
369
|
-
collection_key = f"{self.connection_id}:{self.current_collection}"
|
|
399
|
+
model_info = settings.get_embedding_model(self.connection_id, self.current_collection)
|
|
370
400
|
|
|
371
|
-
if
|
|
372
|
-
model_info = collection_models[collection_key]
|
|
401
|
+
if model_info:
|
|
373
402
|
model_name = model_info['model']
|
|
374
403
|
model_type = model_info.get('type', 'unknown')
|
|
375
404
|
self.embedding_model_label.setText(f"{model_name} ({model_type})")
|
|
@@ -385,11 +414,21 @@ class InfoPanel(QWidget):
|
|
|
385
414
|
if not self.current_collection:
|
|
386
415
|
return
|
|
387
416
|
|
|
388
|
-
|
|
417
|
+
# Show loading immediately; preparing can touch DB/registry
|
|
418
|
+
from ..components.loading_dialog import LoadingDialog
|
|
419
|
+
loading = LoadingDialog("Preparing model configuration...", self)
|
|
420
|
+
loading.show_loading("Preparing model configuration...")
|
|
421
|
+
QApplication.processEvents()
|
|
422
|
+
|
|
423
|
+
from ..dialogs import ProviderTypeDialog, EmbeddingConfigDialog
|
|
389
424
|
from ...services.settings_service import SettingsService
|
|
390
425
|
|
|
391
426
|
# Get current collection info
|
|
392
|
-
|
|
427
|
+
try:
|
|
428
|
+
collection_info = self.connection.get_collection_info(self.current_collection)
|
|
429
|
+
finally:
|
|
430
|
+
# Hide loading before presenting dialogs
|
|
431
|
+
loading.hide_loading()
|
|
393
432
|
if not collection_info:
|
|
394
433
|
return
|
|
395
434
|
|
|
@@ -399,8 +438,6 @@ class InfoPanel(QWidget):
|
|
|
399
438
|
|
|
400
439
|
# Get current configuration if any
|
|
401
440
|
settings = SettingsService()
|
|
402
|
-
collection_models = settings.get('collection_embedding_models', {})
|
|
403
|
-
collection_key = f"{self.connection_id}:{self.current_collection}"
|
|
404
441
|
|
|
405
442
|
current_model = None
|
|
406
443
|
current_type = None
|
|
@@ -408,37 +445,49 @@ class InfoPanel(QWidget):
|
|
|
408
445
|
# Check metadata first
|
|
409
446
|
if 'embedding_model' in collection_info:
|
|
410
447
|
current_model = collection_info['embedding_model']
|
|
411
|
-
current_type = collection_info.get('embedding_model_type')
|
|
448
|
+
current_type = collection_info.get('embedding_model_type', 'stored')
|
|
412
449
|
# Then check settings
|
|
413
|
-
|
|
414
|
-
model_info =
|
|
415
|
-
|
|
416
|
-
|
|
450
|
+
else:
|
|
451
|
+
model_info = settings.get_embedding_model(self.connection_id, self.current_collection)
|
|
452
|
+
if model_info:
|
|
453
|
+
current_model = model_info.get('model')
|
|
454
|
+
current_type = model_info.get('type')
|
|
455
|
+
|
|
456
|
+
# Step 1: Provider Type Selection
|
|
457
|
+
type_dialog = ProviderTypeDialog(
|
|
458
|
+
self.current_collection,
|
|
459
|
+
vector_dim,
|
|
460
|
+
self
|
|
461
|
+
)
|
|
462
|
+
|
|
463
|
+
type_result = type_dialog.exec()
|
|
464
|
+
if type_result != QDialog.DialogCode.Accepted:
|
|
465
|
+
return # User cancelled
|
|
417
466
|
|
|
418
|
-
|
|
419
|
-
|
|
467
|
+
provider_type = type_dialog.get_selected_type()
|
|
468
|
+
if not provider_type:
|
|
469
|
+
return
|
|
470
|
+
|
|
471
|
+
# Step 2: Model Selection (filtered by provider type)
|
|
472
|
+
model_dialog = EmbeddingConfigDialog(
|
|
420
473
|
self.current_collection,
|
|
421
474
|
vector_dim,
|
|
475
|
+
provider_type,
|
|
422
476
|
current_model,
|
|
423
477
|
current_type,
|
|
424
478
|
self
|
|
425
479
|
)
|
|
426
480
|
|
|
427
|
-
|
|
481
|
+
# Optionally show brief loading while populating models
|
|
482
|
+
# (dialog itself handles content; only show if provider lists are large)
|
|
483
|
+
result = model_dialog.exec()
|
|
428
484
|
|
|
429
485
|
if result == QDialog.DialogCode.Accepted:
|
|
430
|
-
# Save the configuration
|
|
431
|
-
selection =
|
|
486
|
+
# Save the configuration using the new SettingsService method
|
|
487
|
+
selection = model_dialog.get_selection()
|
|
432
488
|
if selection:
|
|
433
489
|
model_name, model_type = selection
|
|
434
|
-
|
|
435
|
-
if collection_key not in collection_models:
|
|
436
|
-
collection_models[collection_key] = {}
|
|
437
|
-
|
|
438
|
-
collection_models[collection_key]['model'] = model_name
|
|
439
|
-
collection_models[collection_key]['type'] = model_type
|
|
440
|
-
|
|
441
|
-
settings.set('collection_embedding_models', collection_models)
|
|
490
|
+
settings.save_embedding_model(self.connection_id, self.current_collection, model_name, model_type)
|
|
442
491
|
|
|
443
492
|
# Refresh display
|
|
444
493
|
self._update_embedding_model_display(collection_info)
|
|
@@ -446,12 +495,10 @@ class InfoPanel(QWidget):
|
|
|
446
495
|
print(f"✓ Configured embedding model for '{self.current_collection}': {model_name} ({model_type})")
|
|
447
496
|
|
|
448
497
|
elif result == 2: # Clear configuration
|
|
449
|
-
# Remove from settings
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
print(f"✓ Cleared embedding model configuration for '{self.current_collection}'")
|
|
498
|
+
# Remove from settings using the new SettingsService method
|
|
499
|
+
settings.remove_embedding_model(self.connection_id, self.current_collection)
|
|
500
|
+
|
|
501
|
+
# Refresh display
|
|
502
|
+
self._update_embedding_model_display(collection_info)
|
|
503
|
+
|
|
504
|
+
print(f"✓ Cleared embedding model configuration for '{self.current_collection}'")
|
|
@@ -202,7 +202,7 @@ class SearchView(QWidget):
|
|
|
202
202
|
QApplication.processEvents()
|
|
203
203
|
|
|
204
204
|
try:
|
|
205
|
-
#
|
|
205
|
+
# Always pass query_texts; provider handles embedding if needed
|
|
206
206
|
results = self.connection.query_collection(
|
|
207
207
|
self.current_collection,
|
|
208
208
|
query_texts=[query_text],
|
|
@@ -13,6 +13,7 @@ import numpy as np
|
|
|
13
13
|
|
|
14
14
|
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
15
15
|
from vector_inspector.services.visualization_service import VisualizationService
|
|
16
|
+
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
16
17
|
|
|
17
18
|
|
|
18
19
|
class VisualizationThread(QThread):
|
|
@@ -106,6 +107,9 @@ class VisualizationView(QWidget):
|
|
|
106
107
|
self.status_label.setMaximumHeight(30)
|
|
107
108
|
layout.addWidget(self.status_label)
|
|
108
109
|
|
|
110
|
+
# Loading dialog for data fetch and reduction
|
|
111
|
+
self.loading_dialog = LoadingDialog("Loading visualization...", self)
|
|
112
|
+
|
|
109
113
|
def set_collection(self, collection_name: str):
|
|
110
114
|
"""Set the current collection to visualize."""
|
|
111
115
|
self.current_collection = collection_name
|
|
@@ -119,12 +123,17 @@ class VisualizationView(QWidget):
|
|
|
119
123
|
QMessageBox.warning(self, "No Collection", "Please select a collection first.")
|
|
120
124
|
return
|
|
121
125
|
|
|
122
|
-
# Load data with embeddings
|
|
126
|
+
# Load data with embeddings (show loading immediately)
|
|
127
|
+
self.loading_dialog.show_loading("Loading data for visualization...")
|
|
128
|
+
QApplication.processEvents()
|
|
123
129
|
sample_size = self.sample_spin.value()
|
|
124
|
-
|
|
125
|
-
self.
|
|
126
|
-
|
|
127
|
-
|
|
130
|
+
try:
|
|
131
|
+
data = self.connection.get_all_items(
|
|
132
|
+
self.current_collection,
|
|
133
|
+
limit=sample_size
|
|
134
|
+
)
|
|
135
|
+
finally:
|
|
136
|
+
self.loading_dialog.hide_loading()
|
|
128
137
|
|
|
129
138
|
if data is None or not data or "embeddings" not in data or data["embeddings"] is None or len(data["embeddings"]) == 0:
|
|
130
139
|
QMessageBox.warning(
|
|
@@ -152,10 +161,14 @@ class VisualizationView(QWidget):
|
|
|
152
161
|
)
|
|
153
162
|
self.visualization_thread.finished.connect(self._on_reduction_finished)
|
|
154
163
|
self.visualization_thread.error.connect(self._on_reduction_error)
|
|
164
|
+
# Show loading during reduction
|
|
165
|
+
self.loading_dialog.show_loading("Reducing dimensions...")
|
|
166
|
+
QApplication.processEvents()
|
|
155
167
|
self.visualization_thread.start()
|
|
156
168
|
|
|
157
169
|
def _on_reduction_finished(self, reduced_data: Any):
|
|
158
170
|
"""Handle dimensionality reduction completion."""
|
|
171
|
+
self.loading_dialog.hide_loading()
|
|
159
172
|
self.reduced_data = reduced_data
|
|
160
173
|
self._create_plot()
|
|
161
174
|
self.generate_button.setEnabled(True)
|
|
@@ -163,6 +176,7 @@ class VisualizationView(QWidget):
|
|
|
163
176
|
|
|
164
177
|
def _on_reduction_error(self, error_msg: str):
|
|
165
178
|
"""Handle dimensionality reduction error."""
|
|
179
|
+
self.loading_dialog.hide_loading()
|
|
166
180
|
print(f"Error: Visualization failed: {error_msg}")
|
|
167
181
|
QMessageBox.warning(self, "Error", f"Visualization failed: {error_msg}")
|
|
168
182
|
self.generate_button.setEnabled(True)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: vector-inspector
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.1
|
|
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
|