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
|
@@ -1,9 +1,21 @@
|
|
|
1
1
|
"""Connection configuration view."""
|
|
2
2
|
|
|
3
3
|
from PySide6.QtWidgets import (
|
|
4
|
-
QWidget,
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
QWidget,
|
|
5
|
+
QVBoxLayout,
|
|
6
|
+
QHBoxLayout,
|
|
7
|
+
QLabel,
|
|
8
|
+
QPushButton,
|
|
9
|
+
QDialog,
|
|
10
|
+
QFormLayout,
|
|
11
|
+
QLineEdit,
|
|
12
|
+
QRadioButton,
|
|
13
|
+
QButtonGroup,
|
|
14
|
+
QGroupBox,
|
|
15
|
+
QFileDialog,
|
|
16
|
+
QComboBox,
|
|
17
|
+
QApplication,
|
|
18
|
+
QCheckBox,
|
|
7
19
|
)
|
|
8
20
|
from PySide6.QtCore import Signal, QThread
|
|
9
21
|
|
|
@@ -11,19 +23,20 @@ from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
|
11
23
|
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
12
24
|
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
13
25
|
from vector_inspector.core.connections.pinecone_connection import PineconeConnection
|
|
26
|
+
from vector_inspector.core.connections.pgvector_connection import PgVectorConnection
|
|
14
27
|
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
15
28
|
from vector_inspector.services.settings_service import SettingsService
|
|
16
29
|
|
|
17
30
|
|
|
18
31
|
class ConnectionThread(QThread):
|
|
19
32
|
"""Background thread for connecting to database."""
|
|
20
|
-
|
|
33
|
+
|
|
21
34
|
finished = Signal(bool, list) # success, collections
|
|
22
|
-
|
|
35
|
+
|
|
23
36
|
def __init__(self, connection):
|
|
24
37
|
super().__init__()
|
|
25
38
|
self.connection = connection
|
|
26
|
-
|
|
39
|
+
|
|
27
40
|
def run(self):
|
|
28
41
|
"""Connect to database and get collections."""
|
|
29
42
|
try:
|
|
@@ -39,70 +52,71 @@ class ConnectionThread(QThread):
|
|
|
39
52
|
|
|
40
53
|
class ConnectionDialog(QDialog):
|
|
41
54
|
"""Dialog for configuring database connection."""
|
|
42
|
-
|
|
55
|
+
|
|
43
56
|
def __init__(self, parent=None):
|
|
44
57
|
super().__init__(parent)
|
|
45
58
|
self.setWindowTitle("Connect to Vector Database")
|
|
46
59
|
self.setMinimumWidth(450)
|
|
47
|
-
|
|
60
|
+
|
|
48
61
|
self.settings_service = SettingsService()
|
|
49
|
-
|
|
62
|
+
|
|
50
63
|
self.provider = "chromadb"
|
|
51
64
|
self.connection_type = "persistent"
|
|
52
65
|
self.path = ""
|
|
53
66
|
self.host = "localhost"
|
|
54
67
|
self.port = "8000"
|
|
55
|
-
|
|
68
|
+
|
|
56
69
|
self._setup_ui()
|
|
57
70
|
self._load_last_connection()
|
|
58
|
-
|
|
71
|
+
|
|
59
72
|
def _setup_ui(self):
|
|
60
73
|
"""Setup dialog UI."""
|
|
61
74
|
layout = QVBoxLayout(self)
|
|
62
|
-
|
|
75
|
+
|
|
63
76
|
# Provider selection
|
|
64
77
|
provider_group = QGroupBox("Database Provider")
|
|
65
78
|
provider_layout = QVBoxLayout()
|
|
66
|
-
|
|
79
|
+
|
|
67
80
|
self.provider_combo = QComboBox()
|
|
68
81
|
self.provider_combo.addItem("ChromaDB", "chromadb")
|
|
69
82
|
self.provider_combo.addItem("Qdrant", "qdrant")
|
|
70
83
|
self.provider_combo.addItem("Pinecone", "pinecone")
|
|
84
|
+
self.provider_combo.addItem("PgVector/PostgreSQL", "pgvector")
|
|
71
85
|
self.provider_combo.currentIndexChanged.connect(self._on_provider_changed)
|
|
72
86
|
provider_layout.addWidget(self.provider_combo)
|
|
73
87
|
provider_group.setLayout(provider_layout)
|
|
74
|
-
|
|
88
|
+
|
|
75
89
|
layout.addWidget(provider_group)
|
|
76
|
-
|
|
90
|
+
|
|
77
91
|
# Connection type selection
|
|
78
92
|
type_group = QGroupBox("Connection Type")
|
|
79
93
|
type_layout = QVBoxLayout()
|
|
80
|
-
|
|
94
|
+
|
|
81
95
|
self.button_group = QButtonGroup()
|
|
82
|
-
|
|
96
|
+
|
|
83
97
|
self.persistent_radio = QRadioButton("Persistent (Local File)")
|
|
84
98
|
self.persistent_radio.setChecked(True)
|
|
85
99
|
self.persistent_radio.toggled.connect(self._on_type_changed)
|
|
86
|
-
|
|
100
|
+
|
|
87
101
|
self.http_radio = QRadioButton("HTTP (Remote Server)")
|
|
88
|
-
|
|
102
|
+
|
|
89
103
|
self.ephemeral_radio = QRadioButton("Ephemeral (In-Memory)")
|
|
90
|
-
|
|
104
|
+
|
|
91
105
|
self.button_group.addButton(self.persistent_radio)
|
|
92
106
|
self.button_group.addButton(self.http_radio)
|
|
93
107
|
self.button_group.addButton(self.ephemeral_radio)
|
|
94
|
-
|
|
108
|
+
|
|
95
109
|
type_layout.addWidget(self.persistent_radio)
|
|
96
110
|
type_layout.addWidget(self.http_radio)
|
|
97
111
|
type_layout.addWidget(self.ephemeral_radio)
|
|
98
112
|
type_group.setLayout(type_layout)
|
|
99
|
-
|
|
113
|
+
|
|
100
114
|
layout.addWidget(type_group)
|
|
101
|
-
|
|
115
|
+
|
|
102
116
|
# Connection details
|
|
103
117
|
details_group = QGroupBox("Connection Details")
|
|
104
118
|
form_layout = QFormLayout()
|
|
105
|
-
|
|
119
|
+
|
|
106
120
|
# Path input (for persistent) + Browse button
|
|
107
121
|
self.path_input = QLineEdit()
|
|
108
122
|
# Default to user's test data folder
|
|
@@ -115,74 +129,105 @@ class ConnectionDialog(QDialog):
|
|
|
115
129
|
browse_button.clicked.connect(self._browse_for_path)
|
|
116
130
|
path_row_layout.addWidget(browse_button)
|
|
117
131
|
form_layout.addRow("Data Path:", path_row_widget)
|
|
118
|
-
|
|
119
|
-
# Host input (for HTTP)
|
|
132
|
+
|
|
133
|
+
# Host input (for HTTP/PgVector)
|
|
120
134
|
self.host_input = QLineEdit()
|
|
121
135
|
self.host_input.setText("localhost")
|
|
122
136
|
self.host_input.setEnabled(False)
|
|
123
137
|
form_layout.addRow("Host:", self.host_input)
|
|
124
|
-
|
|
125
|
-
# Port input (for HTTP)
|
|
138
|
+
|
|
139
|
+
# Port input (for HTTP/PgVector)
|
|
126
140
|
self.port_input = QLineEdit()
|
|
127
141
|
self.port_input.setText("8000")
|
|
128
142
|
self.port_input.setEnabled(False)
|
|
129
143
|
form_layout.addRow("Port:", self.port_input)
|
|
130
|
-
|
|
144
|
+
|
|
145
|
+
# Database input (for PgVector)
|
|
146
|
+
self.database_input = QLineEdit()
|
|
147
|
+
self.database_input.setText("subtitles")
|
|
148
|
+
self.database_input.setEnabled(False)
|
|
149
|
+
form_layout.addRow("Database:", self.database_input)
|
|
150
|
+
|
|
151
|
+
# User input (for PgVector)
|
|
152
|
+
self.user_input = QLineEdit()
|
|
153
|
+
self.user_input.setText("postgres")
|
|
154
|
+
self.user_input.setEnabled(False)
|
|
155
|
+
form_layout.addRow("User:", self.user_input)
|
|
156
|
+
|
|
157
|
+
# Password input (for PgVector)
|
|
158
|
+
self.password_input = QLineEdit()
|
|
159
|
+
self.password_input.setText("postgres")
|
|
160
|
+
self.password_input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
161
|
+
self.password_input.setEnabled(False)
|
|
162
|
+
form_layout.addRow("Password:", self.password_input)
|
|
163
|
+
|
|
131
164
|
# API Key input (for Qdrant Cloud)
|
|
132
165
|
self.api_key_input = QLineEdit()
|
|
133
166
|
self.api_key_input.setEnabled(False)
|
|
134
|
-
self.api_key_input.setEchoMode(QLineEdit.Password)
|
|
167
|
+
self.api_key_input.setEchoMode(QLineEdit.EchoMode.Password)
|
|
135
168
|
self.api_key_row = form_layout.rowCount()
|
|
136
169
|
form_layout.addRow("API Key:", self.api_key_input)
|
|
137
|
-
|
|
170
|
+
|
|
138
171
|
details_group.setLayout(form_layout)
|
|
139
172
|
layout.addWidget(details_group)
|
|
140
|
-
|
|
173
|
+
|
|
141
174
|
# Auto-connect option
|
|
142
175
|
self.auto_connect_check = QCheckBox("Auto-connect on startup")
|
|
143
176
|
self.auto_connect_check.setChecked(False)
|
|
144
177
|
layout.addWidget(self.auto_connect_check)
|
|
145
|
-
|
|
178
|
+
|
|
146
179
|
# Buttons
|
|
147
180
|
button_layout = QHBoxLayout()
|
|
148
|
-
|
|
181
|
+
|
|
149
182
|
connect_button = QPushButton("Connect")
|
|
150
183
|
connect_button.clicked.connect(self.accept)
|
|
151
184
|
connect_button.setDefault(True)
|
|
152
|
-
|
|
185
|
+
|
|
153
186
|
cancel_button = QPushButton("Cancel")
|
|
154
187
|
cancel_button.clicked.connect(self.reject)
|
|
155
|
-
|
|
188
|
+
|
|
156
189
|
button_layout.addStretch()
|
|
157
190
|
button_layout.addWidget(connect_button)
|
|
158
191
|
button_layout.addWidget(cancel_button)
|
|
159
|
-
|
|
192
|
+
|
|
160
193
|
layout.addLayout(button_layout)
|
|
161
194
|
|
|
162
195
|
# Resolved absolute path preview
|
|
163
196
|
self.absolute_path_label = QLabel("")
|
|
164
197
|
self.absolute_path_label.setStyleSheet("color: gray; font-size: 11px;")
|
|
165
198
|
layout.addWidget(self.absolute_path_label)
|
|
166
|
-
|
|
199
|
+
|
|
167
200
|
# Update preview when inputs change
|
|
168
201
|
self.path_input.textChanged.connect(self._update_absolute_preview)
|
|
169
202
|
self.persistent_radio.toggled.connect(self._update_absolute_preview)
|
|
170
203
|
self._update_absolute_preview()
|
|
171
|
-
|
|
204
|
+
|
|
172
205
|
def _on_provider_changed(self):
|
|
173
206
|
"""Handle provider selection change."""
|
|
174
207
|
self.provider = self.provider_combo.currentData()
|
|
175
|
-
|
|
208
|
+
|
|
176
209
|
# Update default port based on provider
|
|
177
|
-
if self.provider == "qdrant":
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
210
|
+
if self.provider == "qdrant" and self.port_input.text() == "8000":
|
|
211
|
+
self.port_input.setText("6333")
|
|
212
|
+
elif self.provider == "chromadb" and self.port_input.text() == "6333":
|
|
213
|
+
self.port_input.setText("8000")
|
|
214
|
+
|
|
215
|
+
# Enable/disable fields for PgVector
|
|
216
|
+
if self.provider == "pgvector":
|
|
217
|
+
self.persistent_radio.setEnabled(False)
|
|
218
|
+
self.http_radio.setEnabled(False)
|
|
219
|
+
self.ephemeral_radio.setEnabled(False)
|
|
220
|
+
self.path_input.setEnabled(False)
|
|
221
|
+
self.host_input.setEnabled(True)
|
|
222
|
+
self.port_input.setEnabled(True)
|
|
223
|
+
self.database_input.setEnabled(True)
|
|
224
|
+
self.user_input.setEnabled(True)
|
|
225
|
+
self.password_input.setEnabled(True)
|
|
226
|
+
self.api_key_input.setEnabled(False)
|
|
227
|
+
# Set default port for PostgreSQL if not set
|
|
228
|
+
if self.port_input.text() in ("8000", "6333"):
|
|
229
|
+
self.port_input.setText("5432")
|
|
230
|
+
elif self.provider == "pinecone":
|
|
186
231
|
self.persistent_radio.setEnabled(False)
|
|
187
232
|
self.http_radio.setEnabled(True)
|
|
188
233
|
self.http_radio.setChecked(True)
|
|
@@ -191,6 +236,9 @@ class ConnectionDialog(QDialog):
|
|
|
191
236
|
self.host_input.setEnabled(False)
|
|
192
237
|
self.port_input.setEnabled(False)
|
|
193
238
|
self.api_key_input.setEnabled(True)
|
|
239
|
+
self.database_input.setEnabled(False)
|
|
240
|
+
self.user_input.setEnabled(False)
|
|
241
|
+
self.password_input.setEnabled(False)
|
|
194
242
|
else:
|
|
195
243
|
self.persistent_radio.setEnabled(True)
|
|
196
244
|
self.http_radio.setEnabled(True)
|
|
@@ -200,57 +248,84 @@ class ConnectionDialog(QDialog):
|
|
|
200
248
|
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
201
249
|
# Update path/host/port based on connection type
|
|
202
250
|
self._on_type_changed()
|
|
203
|
-
|
|
251
|
+
# Disable PgVector fields for other providers
|
|
252
|
+
self.database_input.setEnabled(False)
|
|
253
|
+
self.user_input.setEnabled(False)
|
|
254
|
+
self.password_input.setEnabled(False)
|
|
255
|
+
|
|
204
256
|
def _on_type_changed(self):
|
|
205
257
|
"""Handle connection type change."""
|
|
206
258
|
is_persistent = self.persistent_radio.isChecked()
|
|
207
259
|
is_http = self.http_radio.isChecked()
|
|
208
|
-
|
|
260
|
+
|
|
209
261
|
# Pinecone always uses API key, no path/host/port
|
|
210
262
|
if self.provider == "pinecone":
|
|
211
263
|
self.path_input.setEnabled(False)
|
|
212
264
|
self.host_input.setEnabled(False)
|
|
213
265
|
self.port_input.setEnabled(False)
|
|
214
266
|
self.api_key_input.setEnabled(True)
|
|
267
|
+
self.database_input.setEnabled(False)
|
|
268
|
+
self.user_input.setEnabled(False)
|
|
269
|
+
self.password_input.setEnabled(False)
|
|
270
|
+
elif self.provider == "pgvector":
|
|
271
|
+
self.path_input.setEnabled(False)
|
|
272
|
+
self.host_input.setEnabled(True)
|
|
273
|
+
self.port_input.setEnabled(True)
|
|
274
|
+
self.database_input.setEnabled(True)
|
|
275
|
+
self.user_input.setEnabled(True)
|
|
276
|
+
self.password_input.setEnabled(True)
|
|
277
|
+
self.api_key_input.setEnabled(False)
|
|
215
278
|
else:
|
|
216
279
|
self.path_input.setEnabled(is_persistent)
|
|
217
280
|
self.host_input.setEnabled(is_http)
|
|
218
281
|
self.port_input.setEnabled(is_http)
|
|
219
282
|
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
283
|
+
self.database_input.setEnabled(False)
|
|
284
|
+
self.user_input.setEnabled(False)
|
|
285
|
+
self.password_input.setEnabled(False)
|
|
220
286
|
|
|
221
287
|
self._update_absolute_preview()
|
|
222
|
-
|
|
288
|
+
|
|
223
289
|
def get_connection_config(self):
|
|
224
290
|
"""Get connection configuration from dialog."""
|
|
225
291
|
# Get current provider from combo box to ensure it's up to date
|
|
226
292
|
self.provider = self.provider_combo.currentData()
|
|
227
|
-
|
|
293
|
+
|
|
228
294
|
config = {"provider": self.provider}
|
|
229
|
-
|
|
230
|
-
# Pinecone only needs API key
|
|
295
|
+
|
|
231
296
|
if self.provider == "pinecone":
|
|
232
|
-
config.update({
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
297
|
+
config.update({"type": "cloud", "api_key": self.api_key_input.text()})
|
|
298
|
+
elif self.provider == "pgvector":
|
|
299
|
+
config.update(
|
|
300
|
+
{
|
|
301
|
+
"type": "pgvector",
|
|
302
|
+
"host": self.host_input.text(),
|
|
303
|
+
"port": int(self.port_input.text()),
|
|
304
|
+
"database": self.database_input.text(),
|
|
305
|
+
"user": self.user_input.text(),
|
|
306
|
+
"password": self.password_input.text(),
|
|
307
|
+
}
|
|
308
|
+
)
|
|
236
309
|
elif self.persistent_radio.isChecked():
|
|
237
310
|
config.update({"type": "persistent", "path": self.path_input.text()})
|
|
238
311
|
elif self.http_radio.isChecked():
|
|
239
|
-
config.update(
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
312
|
+
config.update(
|
|
313
|
+
{
|
|
314
|
+
"type": "http",
|
|
315
|
+
"host": self.host_input.text(),
|
|
316
|
+
"port": int(self.port_input.text()),
|
|
317
|
+
"api_key": self.api_key_input.text() if self.api_key_input.text() else None,
|
|
318
|
+
}
|
|
319
|
+
)
|
|
245
320
|
else:
|
|
246
321
|
config.update({"type": "ephemeral"})
|
|
247
|
-
|
|
322
|
+
|
|
248
323
|
# Save auto-connect preference
|
|
249
324
|
config["auto_connect"] = self.auto_connect_check.isChecked()
|
|
250
|
-
|
|
325
|
+
|
|
251
326
|
# Save this configuration for next time
|
|
252
327
|
self.settings_service.save_last_connection(config)
|
|
253
|
-
|
|
328
|
+
|
|
254
329
|
return config
|
|
255
330
|
|
|
256
331
|
def _update_absolute_preview(self):
|
|
@@ -261,6 +336,7 @@ class ConnectionDialog(QDialog):
|
|
|
261
336
|
rel = self.path_input.text().strip() or "."
|
|
262
337
|
# Resolve relative to project root by searching for pyproject.toml
|
|
263
338
|
from pathlib import Path
|
|
339
|
+
|
|
264
340
|
current = Path(__file__).resolve()
|
|
265
341
|
abs_path = None
|
|
266
342
|
for parent in current.parents:
|
|
@@ -276,6 +352,7 @@ class ConnectionDialog(QDialog):
|
|
|
276
352
|
# Suggest current resolved path as starting point
|
|
277
353
|
start_dir = None
|
|
278
354
|
from pathlib import Path
|
|
355
|
+
|
|
279
356
|
rel = self.path_input.text().strip() or "."
|
|
280
357
|
current = Path(__file__).resolve()
|
|
281
358
|
for parent in current.parents:
|
|
@@ -284,7 +361,9 @@ class ConnectionDialog(QDialog):
|
|
|
284
361
|
break
|
|
285
362
|
if start_dir is None:
|
|
286
363
|
start_dir = str(Path(rel).resolve())
|
|
287
|
-
directory = QFileDialog.getExistingDirectory(
|
|
364
|
+
directory = QFileDialog.getExistingDirectory(
|
|
365
|
+
self, "Select ChromaDB Data Directory", start_dir
|
|
366
|
+
)
|
|
288
367
|
if directory:
|
|
289
368
|
# Set as relative to project root if within it, else absolute
|
|
290
369
|
proj_root = None
|
|
@@ -302,19 +381,19 @@ class ConnectionDialog(QDialog):
|
|
|
302
381
|
display_path = str(dir_path)
|
|
303
382
|
self.path_input.setText(display_path)
|
|
304
383
|
self._update_absolute_preview()
|
|
305
|
-
|
|
384
|
+
|
|
306
385
|
def _load_last_connection(self):
|
|
307
386
|
"""Load and populate the last connection configuration."""
|
|
308
387
|
last_config = self.settings_service.get_last_connection()
|
|
309
388
|
if not last_config:
|
|
310
389
|
return
|
|
311
|
-
|
|
390
|
+
|
|
312
391
|
# Set provider
|
|
313
392
|
provider = last_config.get("provider", "chromadb")
|
|
314
393
|
index = self.provider_combo.findData(provider)
|
|
315
394
|
if index >= 0:
|
|
316
395
|
self.provider_combo.setCurrentIndex(index)
|
|
317
|
-
|
|
396
|
+
|
|
318
397
|
# Set connection type
|
|
319
398
|
conn_type = last_config.get("type", "persistent")
|
|
320
399
|
if conn_type == "cloud":
|
|
@@ -323,6 +402,13 @@ class ConnectionDialog(QDialog):
|
|
|
323
402
|
api_key = last_config.get("api_key")
|
|
324
403
|
if api_key:
|
|
325
404
|
self.api_key_input.setText(api_key)
|
|
405
|
+
elif conn_type == "pgvector":
|
|
406
|
+
# PgVector connection
|
|
407
|
+
self.host_input.setText(last_config.get("host", "localhost"))
|
|
408
|
+
self.port_input.setText(str(last_config.get("port", "5432")))
|
|
409
|
+
self.database_input.setText(last_config.get("database", "subtitles"))
|
|
410
|
+
self.user_input.setText(last_config.get("user", "postgres"))
|
|
411
|
+
self.password_input.setText(last_config.get("password", "postgres"))
|
|
326
412
|
elif conn_type == "persistent":
|
|
327
413
|
self.persistent_radio.setChecked(True)
|
|
328
414
|
path = last_config.get("path", "")
|
|
@@ -339,7 +425,7 @@ class ConnectionDialog(QDialog):
|
|
|
339
425
|
self.api_key_input.setText(api_key)
|
|
340
426
|
elif conn_type == "ephemeral":
|
|
341
427
|
self.ephemeral_radio.setChecked(True)
|
|
342
|
-
|
|
428
|
+
|
|
343
429
|
# Set auto-connect checkbox
|
|
344
430
|
auto_connect = last_config.get("auto_connect", False)
|
|
345
431
|
self.auto_connect_check.setChecked(auto_connect)
|
|
@@ -347,10 +433,10 @@ class ConnectionDialog(QDialog):
|
|
|
347
433
|
|
|
348
434
|
class ConnectionView(QWidget):
|
|
349
435
|
"""Widget for managing database connection."""
|
|
350
|
-
|
|
436
|
+
|
|
351
437
|
connection_changed = Signal(bool)
|
|
352
438
|
connection_created = Signal(VectorDBConnection) # Signal when new connection is created
|
|
353
|
-
|
|
439
|
+
|
|
354
440
|
def __init__(self, connection: VectorDBConnection, parent=None):
|
|
355
441
|
super().__init__(parent)
|
|
356
442
|
self.connection = connection
|
|
@@ -358,61 +444,64 @@ class ConnectionView(QWidget):
|
|
|
358
444
|
self.settings_service = SettingsService()
|
|
359
445
|
self.connection_thread = None
|
|
360
446
|
self._setup_ui()
|
|
361
|
-
|
|
447
|
+
|
|
362
448
|
# Try to auto-connect if enabled in settings
|
|
363
449
|
self._try_auto_connect()
|
|
364
|
-
|
|
450
|
+
|
|
365
451
|
def _setup_ui(self):
|
|
366
452
|
"""Setup widget UI."""
|
|
367
453
|
layout = QVBoxLayout(self)
|
|
368
454
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
369
|
-
|
|
455
|
+
|
|
370
456
|
# Connection status group
|
|
371
457
|
group = QGroupBox("Connection")
|
|
372
458
|
group_layout = QVBoxLayout()
|
|
373
|
-
|
|
459
|
+
|
|
374
460
|
self.status_label = QLabel("Status: Not connected")
|
|
375
461
|
group_layout.addWidget(self.status_label)
|
|
376
|
-
|
|
462
|
+
|
|
377
463
|
# Button layout with both connect and disconnect
|
|
378
464
|
button_layout = QHBoxLayout()
|
|
379
|
-
|
|
465
|
+
|
|
380
466
|
self.connect_button = QPushButton("Connect")
|
|
381
467
|
self.connect_button.clicked.connect(self.show_connection_dialog)
|
|
382
468
|
button_layout.addWidget(self.connect_button)
|
|
383
|
-
|
|
469
|
+
|
|
384
470
|
self.disconnect_button = QPushButton("Disconnect")
|
|
385
471
|
self.disconnect_button.clicked.connect(self._disconnect)
|
|
386
472
|
self.disconnect_button.setEnabled(False)
|
|
387
473
|
button_layout.addWidget(self.disconnect_button)
|
|
388
|
-
|
|
474
|
+
|
|
389
475
|
group_layout.addLayout(button_layout)
|
|
390
|
-
|
|
476
|
+
|
|
391
477
|
group.setLayout(group_layout)
|
|
392
478
|
layout.addWidget(group)
|
|
393
|
-
|
|
479
|
+
|
|
394
480
|
def show_connection_dialog(self):
|
|
395
481
|
"""Show connection configuration dialog."""
|
|
396
482
|
dialog = ConnectionDialog(self)
|
|
397
|
-
|
|
398
|
-
if dialog.exec() == QDialog.Accepted:
|
|
483
|
+
|
|
484
|
+
if dialog.exec() == QDialog.DialogCode.Accepted:
|
|
399
485
|
config = dialog.get_connection_config()
|
|
400
486
|
self._connect_with_config(config)
|
|
401
|
-
|
|
487
|
+
|
|
402
488
|
def _connect_with_config(self, config: dict):
|
|
403
489
|
"""Connect to database with given configuration."""
|
|
404
490
|
self.loading_dialog.show_loading("Connecting to database...")
|
|
405
|
-
|
|
491
|
+
|
|
406
492
|
provider = config.get("provider", "chromadb")
|
|
407
493
|
conn_type = config.get("type")
|
|
408
|
-
|
|
494
|
+
|
|
409
495
|
# Create appropriate connection instance based on provider
|
|
410
496
|
if provider == "pinecone":
|
|
411
497
|
api_key = config.get("api_key")
|
|
412
498
|
if not api_key:
|
|
413
499
|
self.loading_dialog.hide_loading()
|
|
414
500
|
from PySide6.QtWidgets import QMessageBox
|
|
415
|
-
|
|
501
|
+
|
|
502
|
+
QMessageBox.warning(
|
|
503
|
+
self, "Missing API Key", "Pinecone requires an API key to connect."
|
|
504
|
+
)
|
|
416
505
|
return
|
|
417
506
|
self.connection = PineconeConnection(api_key=api_key)
|
|
418
507
|
elif provider == "qdrant":
|
|
@@ -420,61 +509,71 @@ class ConnectionView(QWidget):
|
|
|
420
509
|
self.connection = QdrantConnection(path=config.get("path"))
|
|
421
510
|
elif conn_type == "http":
|
|
422
511
|
self.connection = QdrantConnection(
|
|
423
|
-
host=config.get("host"),
|
|
424
|
-
port=config.get("port"),
|
|
425
|
-
api_key=config.get("api_key")
|
|
512
|
+
host=config.get("host"), port=config.get("port"), api_key=config.get("api_key")
|
|
426
513
|
)
|
|
427
514
|
else: # ephemeral/memory
|
|
428
515
|
self.connection = QdrantConnection()
|
|
516
|
+
elif provider == "pgvector":
|
|
517
|
+
self.connection = PgVectorConnection(
|
|
518
|
+
host=config.get("host", "localhost"),
|
|
519
|
+
port=config.get("port", 5432),
|
|
520
|
+
database=config.get("database", "subtitles"),
|
|
521
|
+
user=config.get("user", "postgres"),
|
|
522
|
+
password=config.get("password", "postgres"),
|
|
523
|
+
)
|
|
429
524
|
else: # chromadb
|
|
430
525
|
if conn_type == "persistent":
|
|
431
526
|
self.connection = ChromaDBConnection(path=config.get("path"))
|
|
432
527
|
elif conn_type == "http":
|
|
433
528
|
self.connection = ChromaDBConnection(
|
|
434
|
-
host=config.get("host"),
|
|
435
|
-
port=config.get("port")
|
|
529
|
+
host=config.get("host"), port=config.get("port")
|
|
436
530
|
)
|
|
437
531
|
else: # ephemeral
|
|
438
532
|
self.connection = ChromaDBConnection()
|
|
439
|
-
|
|
533
|
+
|
|
440
534
|
# Store config for later use
|
|
441
535
|
self._pending_config = config
|
|
442
|
-
|
|
536
|
+
|
|
443
537
|
# Notify parent that connection instance changed
|
|
444
538
|
self.connection_created.emit(self.connection)
|
|
445
|
-
|
|
539
|
+
|
|
446
540
|
# Start background thread to connect
|
|
447
541
|
self.connection_thread = ConnectionThread(self.connection)
|
|
448
542
|
self.connection_thread.finished.connect(self._on_connection_finished)
|
|
449
543
|
self.connection_thread.start()
|
|
450
|
-
|
|
544
|
+
|
|
451
545
|
def _on_connection_finished(self, success: bool, collections: list):
|
|
452
546
|
"""Handle connection thread completion."""
|
|
453
547
|
self.loading_dialog.hide_loading()
|
|
454
|
-
|
|
548
|
+
|
|
455
549
|
if success:
|
|
456
550
|
config = self._pending_config
|
|
457
551
|
provider = config.get("provider", "chromadb")
|
|
458
|
-
|
|
552
|
+
|
|
459
553
|
# Show provider, path/host + collection count for clarity
|
|
460
|
-
details = []
|
|
461
|
-
|
|
462
|
-
if
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
554
|
+
details = [f"provider: {provider}"]
|
|
555
|
+
# Show path for persistent ChromaDB/Qdrant
|
|
556
|
+
if provider in ("chromadb", "qdrant") and hasattr(self.connection, "path"):
|
|
557
|
+
path = getattr(self.connection, "path", None)
|
|
558
|
+
if path:
|
|
559
|
+
details.append(f"path: {path}")
|
|
560
|
+
# Show host/port for HTTP or PgVector
|
|
561
|
+
if provider in ("qdrant", "chromadb", "pgvector") and hasattr(self.connection, "host"):
|
|
562
|
+
host = getattr(self.connection, "host", None)
|
|
563
|
+
port = getattr(self.connection, "port", None)
|
|
564
|
+
if host:
|
|
565
|
+
details.append(f"host: {host}:{port}")
|
|
467
566
|
count_text = f"collections: {len(collections)}"
|
|
468
567
|
info = ", ".join(details)
|
|
469
568
|
self.status_label.setText(f"Status: Connected ({info}, {count_text})")
|
|
470
|
-
|
|
569
|
+
|
|
471
570
|
# Enable disconnect, disable connect
|
|
472
571
|
self.connect_button.setEnabled(False)
|
|
473
572
|
self.disconnect_button.setEnabled(True)
|
|
474
|
-
|
|
573
|
+
|
|
475
574
|
# Emit signal which triggers collection browser refresh
|
|
476
575
|
self.connection_changed.emit(True)
|
|
477
|
-
|
|
576
|
+
|
|
478
577
|
# Process events to ensure collection browser is updated
|
|
479
578
|
QApplication.processEvents()
|
|
480
579
|
else:
|
|
@@ -483,18 +582,18 @@ class ConnectionView(QWidget):
|
|
|
483
582
|
self.connect_button.setEnabled(True)
|
|
484
583
|
self.disconnect_button.setEnabled(False)
|
|
485
584
|
self.connection_changed.emit(False)
|
|
486
|
-
|
|
585
|
+
|
|
487
586
|
def _disconnect(self):
|
|
488
587
|
"""Disconnect from database."""
|
|
489
588
|
self.connection.disconnect()
|
|
490
589
|
self.status_label.setText("Status: Not connected")
|
|
491
|
-
|
|
590
|
+
|
|
492
591
|
# Enable connect, disable disconnect
|
|
493
592
|
self.connect_button.setEnabled(True)
|
|
494
593
|
self.disconnect_button.setEnabled(False)
|
|
495
|
-
|
|
594
|
+
|
|
496
595
|
self.connection_changed.emit(False)
|
|
497
|
-
|
|
596
|
+
|
|
498
597
|
def _try_auto_connect(self):
|
|
499
598
|
"""Try to automatically connect if auto-connect is enabled."""
|
|
500
599
|
last_config = self.settings_service.get_last_connection()
|