vector-inspector 0.2.6__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/cache_manager.py +159 -0
- vector_inspector/core/connection_manager.py +277 -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 +137 -16
- vector_inspector/core/connections/pinecone_connection.py +768 -0
- vector_inspector/core/connections/qdrant_connection.py +62 -8
- 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 +167 -0
- vector_inspector/core/model_registry.py +205 -0
- vector_inspector/services/backup_restore_service.py +19 -29
- vector_inspector/services/credential_service.py +130 -0
- vector_inspector/services/filter_service.py +1 -1
- vector_inspector/services/profile_service.py +409 -0
- vector_inspector/services/settings_service.py +136 -1
- vector_inspector/ui/components/connection_manager_panel.py +327 -0
- vector_inspector/ui/components/profile_manager_panel.py +565 -0
- vector_inspector/ui/dialogs/__init__.py +6 -0
- vector_inspector/ui/dialogs/cross_db_migration.py +383 -0
- vector_inspector/ui/dialogs/embedding_config_dialog.py +315 -0
- vector_inspector/ui/dialogs/provider_type_dialog.py +189 -0
- vector_inspector/ui/main_window.py +456 -190
- vector_inspector/ui/views/connection_view.py +55 -10
- vector_inspector/ui/views/info_panel.py +272 -55
- vector_inspector/ui/views/metadata_view.py +71 -3
- vector_inspector/ui/views/search_view.py +44 -4
- vector_inspector/ui/views/visualization_view.py +19 -5
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/METADATA +3 -1
- vector_inspector-0.3.1.dist-info/RECORD +55 -0
- vector_inspector-0.2.6.dist-info/RECORD +0 -35
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/WHEEL +0 -0
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,565 @@
|
|
|
1
|
+
"""Profile management UI for saved connection profiles."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
from PySide6.QtWidgets import (
|
|
5
|
+
QWidget, QVBoxLayout, QHBoxLayout, QListWidget, QListWidgetItem,
|
|
6
|
+
QPushButton, QMenu, QMessageBox, QLabel, QDialog, QFormLayout,
|
|
7
|
+
QLineEdit, QComboBox, QRadioButton, QButtonGroup, QGroupBox,
|
|
8
|
+
QFileDialog, QCheckBox, QProgressDialog
|
|
9
|
+
)
|
|
10
|
+
from PySide6.QtCore import Qt, Signal
|
|
11
|
+
|
|
12
|
+
from vector_inspector.services.profile_service import ProfileService, ConnectionProfile
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class ProfileManagerPanel(QWidget):
|
|
16
|
+
"""Panel for managing saved connection profiles.
|
|
17
|
+
|
|
18
|
+
Signals:
|
|
19
|
+
connect_profile: Emitted when user wants to connect to a profile (profile_id)
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
connect_profile = Signal(str) # profile_id
|
|
23
|
+
|
|
24
|
+
def __init__(self, profile_service: ProfileService, parent=None):
|
|
25
|
+
"""
|
|
26
|
+
Initialize profile manager panel.
|
|
27
|
+
|
|
28
|
+
Args:
|
|
29
|
+
profile_service: The ProfileService instance
|
|
30
|
+
parent: Parent widget
|
|
31
|
+
"""
|
|
32
|
+
super().__init__(parent)
|
|
33
|
+
self.profile_service = profile_service
|
|
34
|
+
|
|
35
|
+
self._setup_ui()
|
|
36
|
+
self._connect_signals()
|
|
37
|
+
self._refresh_profiles()
|
|
38
|
+
|
|
39
|
+
def _setup_ui(self):
|
|
40
|
+
"""Setup the UI."""
|
|
41
|
+
layout = QVBoxLayout(self)
|
|
42
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
43
|
+
|
|
44
|
+
# Header
|
|
45
|
+
header_layout = QHBoxLayout()
|
|
46
|
+
header_label = QLabel("Saved Profiles")
|
|
47
|
+
header_label.setStyleSheet("font-weight: bold; font-size: 12px;")
|
|
48
|
+
header_layout.addWidget(header_label)
|
|
49
|
+
header_layout.addStretch()
|
|
50
|
+
|
|
51
|
+
# New profile button
|
|
52
|
+
self.new_profile_btn = QPushButton("+")
|
|
53
|
+
self.new_profile_btn.setMaximumWidth(30)
|
|
54
|
+
self.new_profile_btn.setToolTip("Create new profile")
|
|
55
|
+
self.new_profile_btn.clicked.connect(self._create_profile)
|
|
56
|
+
header_layout.addWidget(self.new_profile_btn)
|
|
57
|
+
|
|
58
|
+
layout.addLayout(header_layout)
|
|
59
|
+
|
|
60
|
+
# Profile list
|
|
61
|
+
self.profile_list = QListWidget()
|
|
62
|
+
self.profile_list.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
63
|
+
self.profile_list.customContextMenuRequested.connect(self._show_context_menu)
|
|
64
|
+
self.profile_list.itemDoubleClicked.connect(self._on_profile_double_clicked)
|
|
65
|
+
layout.addWidget(self.profile_list)
|
|
66
|
+
|
|
67
|
+
# Action buttons
|
|
68
|
+
button_layout = QHBoxLayout()
|
|
69
|
+
|
|
70
|
+
self.connect_btn = QPushButton("Connect")
|
|
71
|
+
self.connect_btn.clicked.connect(self._connect_selected_profile)
|
|
72
|
+
button_layout.addWidget(self.connect_btn)
|
|
73
|
+
|
|
74
|
+
self.edit_btn = QPushButton("Edit")
|
|
75
|
+
self.edit_btn.clicked.connect(self._edit_selected_profile)
|
|
76
|
+
button_layout.addWidget(self.edit_btn)
|
|
77
|
+
|
|
78
|
+
self.delete_btn = QPushButton("Delete")
|
|
79
|
+
self.delete_btn.clicked.connect(self._delete_selected_profile)
|
|
80
|
+
button_layout.addWidget(self.delete_btn)
|
|
81
|
+
|
|
82
|
+
layout.addLayout(button_layout)
|
|
83
|
+
|
|
84
|
+
def _connect_signals(self):
|
|
85
|
+
"""Connect to profile service signals."""
|
|
86
|
+
self.profile_service.profile_added.connect(self._refresh_profiles)
|
|
87
|
+
self.profile_service.profile_updated.connect(self._refresh_profiles)
|
|
88
|
+
self.profile_service.profile_deleted.connect(self._refresh_profiles)
|
|
89
|
+
|
|
90
|
+
def _refresh_profiles(self):
|
|
91
|
+
"""Refresh the profile list."""
|
|
92
|
+
self.profile_list.clear()
|
|
93
|
+
|
|
94
|
+
profiles = self.profile_service.get_all_profiles()
|
|
95
|
+
for profile in profiles:
|
|
96
|
+
item = QListWidgetItem(f"{profile.name} ({profile.provider})")
|
|
97
|
+
item.setData(Qt.UserRole, profile.id)
|
|
98
|
+
self.profile_list.addItem(item)
|
|
99
|
+
|
|
100
|
+
def _on_profile_double_clicked(self, item: QListWidgetItem):
|
|
101
|
+
"""Handle profile double-click to connect."""
|
|
102
|
+
profile_id = item.data(Qt.UserRole)
|
|
103
|
+
if profile_id:
|
|
104
|
+
self.connect_profile.emit(profile_id)
|
|
105
|
+
|
|
106
|
+
def _connect_selected_profile(self):
|
|
107
|
+
"""Connect to the selected profile."""
|
|
108
|
+
current_item = self.profile_list.currentItem()
|
|
109
|
+
if not current_item:
|
|
110
|
+
QMessageBox.warning(self, "No Selection", "Please select a profile to connect.")
|
|
111
|
+
return
|
|
112
|
+
|
|
113
|
+
profile_id = current_item.data(Qt.UserRole)
|
|
114
|
+
self.connect_profile.emit(profile_id)
|
|
115
|
+
|
|
116
|
+
def _edit_selected_profile(self):
|
|
117
|
+
"""Edit the selected profile."""
|
|
118
|
+
current_item = self.profile_list.currentItem()
|
|
119
|
+
if not current_item:
|
|
120
|
+
QMessageBox.warning(self, "No Selection", "Please select a profile to edit.")
|
|
121
|
+
return
|
|
122
|
+
|
|
123
|
+
profile_id = current_item.data(Qt.UserRole)
|
|
124
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
125
|
+
if not profile:
|
|
126
|
+
return
|
|
127
|
+
|
|
128
|
+
dialog = ProfileEditorDialog(self.profile_service, profile, parent=self)
|
|
129
|
+
dialog.exec()
|
|
130
|
+
|
|
131
|
+
def _delete_selected_profile(self):
|
|
132
|
+
"""Delete the selected profile."""
|
|
133
|
+
current_item = self.profile_list.currentItem()
|
|
134
|
+
if not current_item:
|
|
135
|
+
QMessageBox.warning(self, "No Selection", "Please select a profile to delete.")
|
|
136
|
+
return
|
|
137
|
+
|
|
138
|
+
profile_id = current_item.data(Qt.UserRole)
|
|
139
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
140
|
+
if not profile:
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
reply = QMessageBox.question(
|
|
144
|
+
self,
|
|
145
|
+
"Delete Profile",
|
|
146
|
+
f"Delete profile '{profile.name}'?\n\nThis will also delete any saved credentials.",
|
|
147
|
+
QMessageBox.Yes | QMessageBox.No
|
|
148
|
+
)
|
|
149
|
+
|
|
150
|
+
if reply == QMessageBox.Yes:
|
|
151
|
+
self.profile_service.delete_profile(profile_id)
|
|
152
|
+
|
|
153
|
+
def _create_profile(self):
|
|
154
|
+
"""Create a new profile."""
|
|
155
|
+
dialog = ProfileEditorDialog(self.profile_service, parent=self)
|
|
156
|
+
dialog.exec()
|
|
157
|
+
|
|
158
|
+
def _show_context_menu(self, pos):
|
|
159
|
+
"""Show context menu for profile."""
|
|
160
|
+
item = self.profile_list.itemAt(pos)
|
|
161
|
+
if not item:
|
|
162
|
+
return
|
|
163
|
+
|
|
164
|
+
profile_id = item.data(Qt.UserRole)
|
|
165
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
166
|
+
if not profile:
|
|
167
|
+
return
|
|
168
|
+
|
|
169
|
+
menu = QMenu(self)
|
|
170
|
+
|
|
171
|
+
connect_action = menu.addAction("Connect")
|
|
172
|
+
connect_action.triggered.connect(lambda: self.connect_profile.emit(profile_id))
|
|
173
|
+
|
|
174
|
+
menu.addSeparator()
|
|
175
|
+
|
|
176
|
+
edit_action = menu.addAction("Edit")
|
|
177
|
+
edit_action.triggered.connect(lambda: self._edit_profile(profile_id))
|
|
178
|
+
|
|
179
|
+
duplicate_action = menu.addAction("Duplicate")
|
|
180
|
+
duplicate_action.triggered.connect(lambda: self._duplicate_profile(profile_id))
|
|
181
|
+
|
|
182
|
+
menu.addSeparator()
|
|
183
|
+
|
|
184
|
+
delete_action = menu.addAction("Delete")
|
|
185
|
+
delete_action.triggered.connect(lambda: self._delete_profile(profile_id))
|
|
186
|
+
|
|
187
|
+
menu.exec_(self.profile_list.mapToGlobal(pos))
|
|
188
|
+
|
|
189
|
+
def _edit_profile(self, profile_id: str):
|
|
190
|
+
"""Edit a profile."""
|
|
191
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
192
|
+
if profile:
|
|
193
|
+
dialog = ProfileEditorDialog(self.profile_service, profile, parent=self)
|
|
194
|
+
dialog.exec()
|
|
195
|
+
|
|
196
|
+
def _duplicate_profile(self, profile_id: str):
|
|
197
|
+
"""Duplicate a profile."""
|
|
198
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
199
|
+
if not profile:
|
|
200
|
+
return
|
|
201
|
+
|
|
202
|
+
from PySide6.QtWidgets import QInputDialog
|
|
203
|
+
new_name, ok = QInputDialog.getText(
|
|
204
|
+
self,
|
|
205
|
+
"Duplicate Profile",
|
|
206
|
+
"Enter name for duplicated profile:",
|
|
207
|
+
text=f"{profile.name} (Copy)"
|
|
208
|
+
)
|
|
209
|
+
|
|
210
|
+
if ok and new_name:
|
|
211
|
+
self.profile_service.duplicate_profile(profile_id, new_name)
|
|
212
|
+
|
|
213
|
+
def _delete_profile(self, profile_id: str):
|
|
214
|
+
"""Delete a profile."""
|
|
215
|
+
profile = self.profile_service.get_profile(profile_id)
|
|
216
|
+
if not profile:
|
|
217
|
+
return
|
|
218
|
+
|
|
219
|
+
reply = QMessageBox.question(
|
|
220
|
+
self,
|
|
221
|
+
"Delete Profile",
|
|
222
|
+
f"Delete profile '{profile.name}'?",
|
|
223
|
+
QMessageBox.Yes | QMessageBox.No
|
|
224
|
+
)
|
|
225
|
+
|
|
226
|
+
if reply == QMessageBox.Yes:
|
|
227
|
+
self.profile_service.delete_profile(profile_id)
|
|
228
|
+
|
|
229
|
+
|
|
230
|
+
class ProfileEditorDialog(QDialog):
|
|
231
|
+
"""Dialog for creating/editing connection profiles."""
|
|
232
|
+
|
|
233
|
+
def __init__(self, profile_service: ProfileService, profile: Optional[ConnectionProfile] = None, parent=None):
|
|
234
|
+
"""
|
|
235
|
+
Initialize profile editor dialog.
|
|
236
|
+
|
|
237
|
+
Args:
|
|
238
|
+
profile_service: The ProfileService instance
|
|
239
|
+
profile: Existing profile to edit (None for new profile)
|
|
240
|
+
parent: Parent widget
|
|
241
|
+
"""
|
|
242
|
+
super().__init__(parent)
|
|
243
|
+
self.profile_service = profile_service
|
|
244
|
+
self.profile = profile
|
|
245
|
+
self.is_edit_mode = profile is not None
|
|
246
|
+
|
|
247
|
+
self.setWindowTitle("Edit Profile" if self.is_edit_mode else "New Profile")
|
|
248
|
+
self.setMinimumWidth(500)
|
|
249
|
+
|
|
250
|
+
self._setup_ui()
|
|
251
|
+
|
|
252
|
+
if self.is_edit_mode:
|
|
253
|
+
self._load_profile_data()
|
|
254
|
+
|
|
255
|
+
def _setup_ui(self):
|
|
256
|
+
"""Setup the UI."""
|
|
257
|
+
layout = QVBoxLayout(self)
|
|
258
|
+
|
|
259
|
+
form_layout = QFormLayout()
|
|
260
|
+
|
|
261
|
+
# Profile name
|
|
262
|
+
self.name_input = QLineEdit()
|
|
263
|
+
form_layout.addRow("Profile Name:", self.name_input)
|
|
264
|
+
|
|
265
|
+
# Provider
|
|
266
|
+
self.provider_combo = QComboBox()
|
|
267
|
+
self.provider_combo.addItem("ChromaDB", "chromadb")
|
|
268
|
+
self.provider_combo.addItem("Qdrant", "qdrant")
|
|
269
|
+
self.provider_combo.addItem("Pinecone", "pinecone")
|
|
270
|
+
self.provider_combo.currentIndexChanged.connect(self._on_provider_changed)
|
|
271
|
+
form_layout.addRow("Provider:", self.provider_combo)
|
|
272
|
+
|
|
273
|
+
layout.addLayout(form_layout)
|
|
274
|
+
|
|
275
|
+
# Connection type group
|
|
276
|
+
type_group = QGroupBox("Connection Type")
|
|
277
|
+
type_layout = QVBoxLayout()
|
|
278
|
+
|
|
279
|
+
self.button_group = QButtonGroup()
|
|
280
|
+
|
|
281
|
+
self.persistent_radio = QRadioButton("Persistent (Local File)")
|
|
282
|
+
self.persistent_radio.setChecked(True)
|
|
283
|
+
self.persistent_radio.toggled.connect(self._on_type_changed)
|
|
284
|
+
|
|
285
|
+
self.http_radio = QRadioButton("HTTP (Remote Server)")
|
|
286
|
+
|
|
287
|
+
self.ephemeral_radio = QRadioButton("Ephemeral (In-Memory)")
|
|
288
|
+
|
|
289
|
+
self.button_group.addButton(self.persistent_radio)
|
|
290
|
+
self.button_group.addButton(self.http_radio)
|
|
291
|
+
self.button_group.addButton(self.ephemeral_radio)
|
|
292
|
+
|
|
293
|
+
type_layout.addWidget(self.persistent_radio)
|
|
294
|
+
type_layout.addWidget(self.http_radio)
|
|
295
|
+
type_layout.addWidget(self.ephemeral_radio)
|
|
296
|
+
type_group.setLayout(type_layout)
|
|
297
|
+
|
|
298
|
+
layout.addWidget(type_group)
|
|
299
|
+
|
|
300
|
+
# Connection details
|
|
301
|
+
details_group = QGroupBox("Connection Details")
|
|
302
|
+
details_layout = QFormLayout()
|
|
303
|
+
|
|
304
|
+
# Persistent path
|
|
305
|
+
self.path_layout = QHBoxLayout()
|
|
306
|
+
self.path_input = QLineEdit()
|
|
307
|
+
self.path_browse_btn = QPushButton("Browse...")
|
|
308
|
+
self.path_browse_btn.clicked.connect(self._browse_for_path)
|
|
309
|
+
self.path_layout.addWidget(self.path_input)
|
|
310
|
+
self.path_layout.addWidget(self.path_browse_btn)
|
|
311
|
+
details_layout.addRow("Path:", self.path_layout)
|
|
312
|
+
|
|
313
|
+
# HTTP settings
|
|
314
|
+
self.host_input = QLineEdit("localhost")
|
|
315
|
+
details_layout.addRow("Host:", self.host_input)
|
|
316
|
+
|
|
317
|
+
self.port_input = QLineEdit("8000")
|
|
318
|
+
details_layout.addRow("Port:", self.port_input)
|
|
319
|
+
|
|
320
|
+
self.api_key_input = QLineEdit()
|
|
321
|
+
self.api_key_input.setEchoMode(QLineEdit.Password)
|
|
322
|
+
details_layout.addRow("API Key:", self.api_key_input)
|
|
323
|
+
|
|
324
|
+
details_group.setLayout(details_layout)
|
|
325
|
+
layout.addWidget(details_group)
|
|
326
|
+
|
|
327
|
+
# Test connection button
|
|
328
|
+
self.test_btn = QPushButton("Test Connection")
|
|
329
|
+
self.test_btn.clicked.connect(self._test_connection)
|
|
330
|
+
layout.addWidget(self.test_btn)
|
|
331
|
+
|
|
332
|
+
# Buttons
|
|
333
|
+
button_layout = QHBoxLayout()
|
|
334
|
+
|
|
335
|
+
self.save_btn = QPushButton("Save")
|
|
336
|
+
self.save_btn.clicked.connect(self._save_profile)
|
|
337
|
+
button_layout.addWidget(self.save_btn)
|
|
338
|
+
|
|
339
|
+
self.cancel_btn = QPushButton("Cancel")
|
|
340
|
+
self.cancel_btn.clicked.connect(self.reject)
|
|
341
|
+
button_layout.addWidget(self.cancel_btn)
|
|
342
|
+
|
|
343
|
+
layout.addLayout(button_layout)
|
|
344
|
+
|
|
345
|
+
# Initial state
|
|
346
|
+
self._on_type_changed()
|
|
347
|
+
|
|
348
|
+
def _on_provider_changed(self):
|
|
349
|
+
"""Handle provider change."""
|
|
350
|
+
provider = self.provider_combo.currentData()
|
|
351
|
+
|
|
352
|
+
# Update default port
|
|
353
|
+
if provider == "qdrant":
|
|
354
|
+
if self.port_input.text() == "8000":
|
|
355
|
+
self.port_input.setText("6333")
|
|
356
|
+
elif provider == "chromadb":
|
|
357
|
+
if self.port_input.text() == "6333":
|
|
358
|
+
self.port_input.setText("8000")
|
|
359
|
+
|
|
360
|
+
# For Pinecone, disable persistent/ephemeral modes and only show API key
|
|
361
|
+
if provider == "pinecone":
|
|
362
|
+
self.persistent_radio.setEnabled(False)
|
|
363
|
+
self.http_radio.setEnabled(True)
|
|
364
|
+
self.http_radio.setChecked(True)
|
|
365
|
+
self.ephemeral_radio.setEnabled(False)
|
|
366
|
+
self.path_input.setEnabled(False)
|
|
367
|
+
self.path_browse_btn.setEnabled(False)
|
|
368
|
+
self.host_input.setEnabled(False)
|
|
369
|
+
self.port_input.setEnabled(False)
|
|
370
|
+
self.api_key_input.setEnabled(True)
|
|
371
|
+
else:
|
|
372
|
+
self.persistent_radio.setEnabled(True)
|
|
373
|
+
self.http_radio.setEnabled(True)
|
|
374
|
+
self.ephemeral_radio.setEnabled(True)
|
|
375
|
+
# Show/hide API key field
|
|
376
|
+
is_http = self.http_radio.isChecked()
|
|
377
|
+
self.api_key_input.setEnabled(is_http and provider == "qdrant")
|
|
378
|
+
# Update other fields based on connection type
|
|
379
|
+
self._on_type_changed()
|
|
380
|
+
|
|
381
|
+
def _on_type_changed(self):
|
|
382
|
+
"""Handle connection type change."""
|
|
383
|
+
is_persistent = self.persistent_radio.isChecked()
|
|
384
|
+
is_http = self.http_radio.isChecked()
|
|
385
|
+
|
|
386
|
+
provider = self.provider_combo.currentData()
|
|
387
|
+
|
|
388
|
+
# Pinecone always uses API key, no path/host/port
|
|
389
|
+
if provider == "pinecone":
|
|
390
|
+
self.path_input.setEnabled(False)
|
|
391
|
+
self.path_browse_btn.setEnabled(False)
|
|
392
|
+
self.host_input.setEnabled(False)
|
|
393
|
+
self.port_input.setEnabled(False)
|
|
394
|
+
self.api_key_input.setEnabled(True)
|
|
395
|
+
else:
|
|
396
|
+
# Show/hide relevant fields
|
|
397
|
+
self.path_input.setEnabled(is_persistent)
|
|
398
|
+
self.path_browse_btn.setEnabled(is_persistent)
|
|
399
|
+
self.host_input.setEnabled(is_http)
|
|
400
|
+
self.port_input.setEnabled(is_http)
|
|
401
|
+
self.api_key_input.setEnabled(is_http and provider == "qdrant")
|
|
402
|
+
|
|
403
|
+
def _browse_for_path(self):
|
|
404
|
+
"""Browse for persistent storage path."""
|
|
405
|
+
path = QFileDialog.getExistingDirectory(
|
|
406
|
+
self,
|
|
407
|
+
"Select Database Directory",
|
|
408
|
+
self.path_input.text()
|
|
409
|
+
)
|
|
410
|
+
if path:
|
|
411
|
+
self.path_input.setText(path)
|
|
412
|
+
|
|
413
|
+
def _load_profile_data(self):
|
|
414
|
+
"""Load existing profile data into form."""
|
|
415
|
+
if not self.profile:
|
|
416
|
+
return
|
|
417
|
+
|
|
418
|
+
# Get profile with credentials
|
|
419
|
+
profile_data = self.profile_service.get_profile_with_credentials(self.profile.id)
|
|
420
|
+
if not profile_data:
|
|
421
|
+
return
|
|
422
|
+
|
|
423
|
+
self.name_input.setText(profile_data["name"])
|
|
424
|
+
|
|
425
|
+
# Set provider
|
|
426
|
+
index = self.provider_combo.findData(profile_data["provider"])
|
|
427
|
+
if index >= 0:
|
|
428
|
+
self.provider_combo.setCurrentIndex(index)
|
|
429
|
+
|
|
430
|
+
config = profile_data.get("config", {})
|
|
431
|
+
conn_type = config.get("type", "persistent")
|
|
432
|
+
|
|
433
|
+
# Set connection type
|
|
434
|
+
if conn_type == "cloud":
|
|
435
|
+
# Pinecone cloud connection
|
|
436
|
+
self.http_radio.setChecked(True)
|
|
437
|
+
elif conn_type == "persistent":
|
|
438
|
+
self.persistent_radio.setChecked(True)
|
|
439
|
+
self.path_input.setText(config.get("path", ""))
|
|
440
|
+
elif conn_type == "http":
|
|
441
|
+
self.http_radio.setChecked(True)
|
|
442
|
+
self.host_input.setText(config.get("host", "localhost"))
|
|
443
|
+
self.port_input.setText(str(config.get("port", "8000")))
|
|
444
|
+
else:
|
|
445
|
+
self.ephemeral_radio.setChecked(True)
|
|
446
|
+
|
|
447
|
+
# Load credentials
|
|
448
|
+
credentials = profile_data.get("credentials", {})
|
|
449
|
+
if "api_key" in credentials:
|
|
450
|
+
self.api_key_input.setText(credentials["api_key"])
|
|
451
|
+
|
|
452
|
+
def _test_connection(self):
|
|
453
|
+
"""Test the connection with current settings."""
|
|
454
|
+
# Get config
|
|
455
|
+
config = self._get_config()
|
|
456
|
+
provider = self.provider_combo.currentData()
|
|
457
|
+
|
|
458
|
+
# Create connection
|
|
459
|
+
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
460
|
+
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
461
|
+
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
462
|
+
|
|
463
|
+
try:
|
|
464
|
+
if provider == "pinecone":
|
|
465
|
+
api_key = self.api_key_input.text()
|
|
466
|
+
if not api_key:
|
|
467
|
+
QMessageBox.warning(self, "Missing API Key", "Pinecone requires an API key.")
|
|
468
|
+
return
|
|
469
|
+
conn = PineconeConnection(api_key=api_key)
|
|
470
|
+
elif provider == "chromadb":
|
|
471
|
+
conn = ChromaDBConnection(**self._get_connection_kwargs(config))
|
|
472
|
+
else:
|
|
473
|
+
conn = QdrantConnection(**self._get_connection_kwargs(config))
|
|
474
|
+
|
|
475
|
+
# Test connection
|
|
476
|
+
progress = QProgressDialog("Testing connection...", None, 0, 0, self)
|
|
477
|
+
progress.setWindowModality(Qt.WindowModal)
|
|
478
|
+
progress.show()
|
|
479
|
+
|
|
480
|
+
success = conn.connect()
|
|
481
|
+
progress.close()
|
|
482
|
+
|
|
483
|
+
if success:
|
|
484
|
+
QMessageBox.information(self, "Success", "Connection test successful!")
|
|
485
|
+
conn.disconnect()
|
|
486
|
+
else:
|
|
487
|
+
QMessageBox.warning(self, "Failed", "Connection test failed.")
|
|
488
|
+
except Exception as e:
|
|
489
|
+
QMessageBox.critical(self, "Error", f"Connection test error: {e}")
|
|
490
|
+
|
|
491
|
+
def _get_config(self) -> dict:
|
|
492
|
+
"""Get configuration from form."""
|
|
493
|
+
config = {}
|
|
494
|
+
provider = self.provider_combo.currentData()
|
|
495
|
+
|
|
496
|
+
# Pinecone uses cloud connection type
|
|
497
|
+
if provider == "pinecone":
|
|
498
|
+
config["type"] = "cloud"
|
|
499
|
+
elif self.persistent_radio.isChecked():
|
|
500
|
+
config["type"] = "persistent"
|
|
501
|
+
config["path"] = self.path_input.text()
|
|
502
|
+
elif self.http_radio.isChecked():
|
|
503
|
+
config["type"] = "http"
|
|
504
|
+
config["host"] = self.host_input.text()
|
|
505
|
+
config["port"] = int(self.port_input.text())
|
|
506
|
+
else:
|
|
507
|
+
config["type"] = "ephemeral"
|
|
508
|
+
|
|
509
|
+
return config
|
|
510
|
+
|
|
511
|
+
def _get_connection_kwargs(self, config: dict) -> dict:
|
|
512
|
+
"""Get kwargs for creating connection."""
|
|
513
|
+
kwargs = {}
|
|
514
|
+
|
|
515
|
+
if config["type"] == "persistent":
|
|
516
|
+
kwargs["path"] = config["path"]
|
|
517
|
+
elif config["type"] == "http":
|
|
518
|
+
kwargs["host"] = config["host"]
|
|
519
|
+
kwargs["port"] = config["port"]
|
|
520
|
+
if self.api_key_input.text():
|
|
521
|
+
kwargs["api_key"] = self.api_key_input.text()
|
|
522
|
+
|
|
523
|
+
return kwargs
|
|
524
|
+
|
|
525
|
+
def _save_profile(self):
|
|
526
|
+
"""Save the profile."""
|
|
527
|
+
name = self.name_input.text().strip()
|
|
528
|
+
if not name:
|
|
529
|
+
QMessageBox.warning(self, "Invalid Input", "Please enter a profile name.")
|
|
530
|
+
return
|
|
531
|
+
|
|
532
|
+
provider = self.provider_combo.currentData()
|
|
533
|
+
config = self._get_config()
|
|
534
|
+
|
|
535
|
+
# Get credentials
|
|
536
|
+
credentials = {}
|
|
537
|
+
if provider == "pinecone":
|
|
538
|
+
# Pinecone always requires API key
|
|
539
|
+
if self.api_key_input.text():
|
|
540
|
+
credentials["api_key"] = self.api_key_input.text()
|
|
541
|
+
else:
|
|
542
|
+
QMessageBox.warning(self, "Missing API Key", "Pinecone requires an API key.")
|
|
543
|
+
return
|
|
544
|
+
elif self.api_key_input.text() and self.http_radio.isChecked():
|
|
545
|
+
credentials["api_key"] = self.api_key_input.text()
|
|
546
|
+
|
|
547
|
+
if self.is_edit_mode:
|
|
548
|
+
# Update existing profile
|
|
549
|
+
self.profile_service.update_profile(
|
|
550
|
+
self.profile.id,
|
|
551
|
+
name=name,
|
|
552
|
+
config=config,
|
|
553
|
+
credentials=credentials if credentials else None
|
|
554
|
+
)
|
|
555
|
+
else:
|
|
556
|
+
# Create new profile
|
|
557
|
+
self.profile_service.create_profile(
|
|
558
|
+
name=name,
|
|
559
|
+
provider=provider,
|
|
560
|
+
config=config,
|
|
561
|
+
credentials=credentials if credentials else None
|
|
562
|
+
)
|
|
563
|
+
|
|
564
|
+
self.accept()
|
|
565
|
+
|