vector-inspector 0.2.0__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/__init__.py +3 -0
- vector_inspector/__main__.py +4 -0
- vector_inspector/core/__init__.py +1 -0
- vector_inspector/core/connections/__init__.py +7 -0
- vector_inspector/core/connections/base_connection.py +233 -0
- vector_inspector/core/connections/chroma_connection.py +384 -0
- vector_inspector/core/connections/qdrant_connection.py +723 -0
- vector_inspector/core/connections/template_connection.py +346 -0
- vector_inspector/main.py +21 -0
- vector_inspector/services/__init__.py +1 -0
- vector_inspector/services/backup_restore_service.py +286 -0
- vector_inspector/services/filter_service.py +72 -0
- vector_inspector/services/import_export_service.py +287 -0
- vector_inspector/services/settings_service.py +60 -0
- vector_inspector/services/visualization_service.py +116 -0
- vector_inspector/ui/__init__.py +1 -0
- vector_inspector/ui/components/__init__.py +1 -0
- vector_inspector/ui/components/backup_restore_dialog.py +350 -0
- vector_inspector/ui/components/filter_builder.py +370 -0
- vector_inspector/ui/components/item_dialog.py +118 -0
- vector_inspector/ui/components/loading_dialog.py +30 -0
- vector_inspector/ui/main_window.py +288 -0
- vector_inspector/ui/views/__init__.py +1 -0
- vector_inspector/ui/views/collection_browser.py +112 -0
- vector_inspector/ui/views/connection_view.py +423 -0
- vector_inspector/ui/views/metadata_view.py +555 -0
- vector_inspector/ui/views/search_view.py +268 -0
- vector_inspector/ui/views/visualization_view.py +245 -0
- vector_inspector-0.2.0.dist-info/METADATA +382 -0
- vector_inspector-0.2.0.dist-info/RECORD +32 -0
- vector_inspector-0.2.0.dist-info/WHEEL +4 -0
- vector_inspector-0.2.0.dist-info/entry_points.txt +5 -0
|
@@ -0,0 +1,423 @@
|
|
|
1
|
+
"""Connection configuration view."""
|
|
2
|
+
|
|
3
|
+
from PySide6.QtWidgets import (
|
|
4
|
+
QWidget, QVBoxLayout, QHBoxLayout, QLabel,
|
|
5
|
+
QPushButton, QDialog, QFormLayout, QLineEdit,
|
|
6
|
+
QRadioButton, QButtonGroup, QGroupBox, QFileDialog, QComboBox, QApplication, QCheckBox
|
|
7
|
+
)
|
|
8
|
+
from PySide6.QtCore import Signal
|
|
9
|
+
|
|
10
|
+
from vector_inspector.core.connections.base_connection import VectorDBConnection
|
|
11
|
+
from vector_inspector.core.connections.chroma_connection import ChromaDBConnection
|
|
12
|
+
from vector_inspector.core.connections.qdrant_connection import QdrantConnection
|
|
13
|
+
from vector_inspector.ui.components.loading_dialog import LoadingDialog
|
|
14
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class ConnectionDialog(QDialog):
|
|
18
|
+
"""Dialog for configuring database connection."""
|
|
19
|
+
|
|
20
|
+
def __init__(self, parent=None):
|
|
21
|
+
super().__init__(parent)
|
|
22
|
+
self.setWindowTitle("Connect to Vector Database")
|
|
23
|
+
self.setMinimumWidth(450)
|
|
24
|
+
|
|
25
|
+
self.settings_service = SettingsService()
|
|
26
|
+
|
|
27
|
+
self.provider = "chromadb"
|
|
28
|
+
self.connection_type = "persistent"
|
|
29
|
+
self.path = ""
|
|
30
|
+
self.host = "localhost"
|
|
31
|
+
self.port = "8000"
|
|
32
|
+
|
|
33
|
+
self._setup_ui()
|
|
34
|
+
self._load_last_connection()
|
|
35
|
+
|
|
36
|
+
def _setup_ui(self):
|
|
37
|
+
"""Setup dialog UI."""
|
|
38
|
+
layout = QVBoxLayout(self)
|
|
39
|
+
|
|
40
|
+
# Provider selection
|
|
41
|
+
provider_group = QGroupBox("Database Provider")
|
|
42
|
+
provider_layout = QVBoxLayout()
|
|
43
|
+
|
|
44
|
+
self.provider_combo = QComboBox()
|
|
45
|
+
self.provider_combo.addItem("ChromaDB", "chromadb")
|
|
46
|
+
self.provider_combo.addItem("Qdrant", "qdrant")
|
|
47
|
+
self.provider_combo.currentIndexChanged.connect(self._on_provider_changed)
|
|
48
|
+
provider_layout.addWidget(self.provider_combo)
|
|
49
|
+
provider_group.setLayout(provider_layout)
|
|
50
|
+
|
|
51
|
+
layout.addWidget(provider_group)
|
|
52
|
+
|
|
53
|
+
# Connection type selection
|
|
54
|
+
type_group = QGroupBox("Connection Type")
|
|
55
|
+
type_layout = QVBoxLayout()
|
|
56
|
+
|
|
57
|
+
self.button_group = QButtonGroup()
|
|
58
|
+
|
|
59
|
+
self.persistent_radio = QRadioButton("Persistent (Local File)")
|
|
60
|
+
self.persistent_radio.setChecked(True)
|
|
61
|
+
self.persistent_radio.toggled.connect(self._on_type_changed)
|
|
62
|
+
|
|
63
|
+
self.http_radio = QRadioButton("HTTP (Remote Server)")
|
|
64
|
+
|
|
65
|
+
self.ephemeral_radio = QRadioButton("Ephemeral (In-Memory)")
|
|
66
|
+
|
|
67
|
+
self.button_group.addButton(self.persistent_radio)
|
|
68
|
+
self.button_group.addButton(self.http_radio)
|
|
69
|
+
self.button_group.addButton(self.ephemeral_radio)
|
|
70
|
+
|
|
71
|
+
type_layout.addWidget(self.persistent_radio)
|
|
72
|
+
type_layout.addWidget(self.http_radio)
|
|
73
|
+
type_layout.addWidget(self.ephemeral_radio)
|
|
74
|
+
type_group.setLayout(type_layout)
|
|
75
|
+
|
|
76
|
+
layout.addWidget(type_group)
|
|
77
|
+
|
|
78
|
+
# Connection details
|
|
79
|
+
details_group = QGroupBox("Connection Details")
|
|
80
|
+
form_layout = QFormLayout()
|
|
81
|
+
|
|
82
|
+
# Path input (for persistent) + Browse button
|
|
83
|
+
self.path_input = QLineEdit()
|
|
84
|
+
# Default to user's test data folder
|
|
85
|
+
self.path_input.setText("./data/chrome_db")
|
|
86
|
+
path_row_widget = QWidget()
|
|
87
|
+
path_row_layout = QHBoxLayout(path_row_widget)
|
|
88
|
+
path_row_layout.setContentsMargins(0, 0, 0, 0)
|
|
89
|
+
path_row_layout.addWidget(self.path_input)
|
|
90
|
+
browse_button = QPushButton("Browse…")
|
|
91
|
+
browse_button.clicked.connect(self._browse_for_path)
|
|
92
|
+
path_row_layout.addWidget(browse_button)
|
|
93
|
+
form_layout.addRow("Data Path:", path_row_widget)
|
|
94
|
+
|
|
95
|
+
# Host input (for HTTP)
|
|
96
|
+
self.host_input = QLineEdit()
|
|
97
|
+
self.host_input.setText("localhost")
|
|
98
|
+
self.host_input.setEnabled(False)
|
|
99
|
+
form_layout.addRow("Host:", self.host_input)
|
|
100
|
+
|
|
101
|
+
# Port input (for HTTP)
|
|
102
|
+
self.port_input = QLineEdit()
|
|
103
|
+
self.port_input.setText("8000")
|
|
104
|
+
self.port_input.setEnabled(False)
|
|
105
|
+
form_layout.addRow("Port:", self.port_input)
|
|
106
|
+
|
|
107
|
+
# API Key input (for Qdrant Cloud)
|
|
108
|
+
self.api_key_input = QLineEdit()
|
|
109
|
+
self.api_key_input.setEnabled(False)
|
|
110
|
+
self.api_key_input.setEchoMode(QLineEdit.Password)
|
|
111
|
+
self.api_key_row = form_layout.rowCount()
|
|
112
|
+
form_layout.addRow("API Key:", self.api_key_input)
|
|
113
|
+
|
|
114
|
+
details_group.setLayout(form_layout)
|
|
115
|
+
layout.addWidget(details_group)
|
|
116
|
+
|
|
117
|
+
# Auto-connect option
|
|
118
|
+
self.auto_connect_check = QCheckBox("Auto-connect on startup")
|
|
119
|
+
self.auto_connect_check.setChecked(False)
|
|
120
|
+
layout.addWidget(self.auto_connect_check)
|
|
121
|
+
|
|
122
|
+
# Buttons
|
|
123
|
+
button_layout = QHBoxLayout()
|
|
124
|
+
|
|
125
|
+
connect_button = QPushButton("Connect")
|
|
126
|
+
connect_button.clicked.connect(self.accept)
|
|
127
|
+
connect_button.setDefault(True)
|
|
128
|
+
|
|
129
|
+
cancel_button = QPushButton("Cancel")
|
|
130
|
+
cancel_button.clicked.connect(self.reject)
|
|
131
|
+
|
|
132
|
+
button_layout.addStretch()
|
|
133
|
+
button_layout.addWidget(connect_button)
|
|
134
|
+
button_layout.addWidget(cancel_button)
|
|
135
|
+
|
|
136
|
+
layout.addLayout(button_layout)
|
|
137
|
+
|
|
138
|
+
# Resolved absolute path preview
|
|
139
|
+
self.absolute_path_label = QLabel("")
|
|
140
|
+
self.absolute_path_label.setStyleSheet("color: gray; font-size: 11px;")
|
|
141
|
+
layout.addWidget(self.absolute_path_label)
|
|
142
|
+
|
|
143
|
+
# Update preview when inputs change
|
|
144
|
+
self.path_input.textChanged.connect(self._update_absolute_preview)
|
|
145
|
+
self.persistent_radio.toggled.connect(self._update_absolute_preview)
|
|
146
|
+
self._update_absolute_preview()
|
|
147
|
+
|
|
148
|
+
def _on_provider_changed(self):
|
|
149
|
+
"""Handle provider selection change."""
|
|
150
|
+
self.provider = self.provider_combo.currentData()
|
|
151
|
+
|
|
152
|
+
# Update default port based on provider
|
|
153
|
+
if self.provider == "qdrant":
|
|
154
|
+
if self.port_input.text() == "8000":
|
|
155
|
+
self.port_input.setText("6333")
|
|
156
|
+
elif self.provider == "chromadb":
|
|
157
|
+
if self.port_input.text() == "6333":
|
|
158
|
+
self.port_input.setText("8000")
|
|
159
|
+
|
|
160
|
+
# Show/hide API key field
|
|
161
|
+
is_http = self.http_radio.isChecked()
|
|
162
|
+
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
163
|
+
|
|
164
|
+
def _on_type_changed(self):
|
|
165
|
+
"""Handle connection type change."""
|
|
166
|
+
is_persistent = self.persistent_radio.isChecked()
|
|
167
|
+
is_http = self.http_radio.isChecked()
|
|
168
|
+
|
|
169
|
+
self.path_input.setEnabled(is_persistent)
|
|
170
|
+
self.host_input.setEnabled(is_http)
|
|
171
|
+
self.port_input.setEnabled(is_http)
|
|
172
|
+
self.api_key_input.setEnabled(is_http and self.provider == "qdrant")
|
|
173
|
+
|
|
174
|
+
self._update_absolute_preview()
|
|
175
|
+
|
|
176
|
+
def get_connection_config(self):
|
|
177
|
+
"""Get connection configuration from dialog."""
|
|
178
|
+
config = {"provider": self.provider}
|
|
179
|
+
|
|
180
|
+
if self.persistent_radio.isChecked():
|
|
181
|
+
config.update({"type": "persistent", "path": self.path_input.text()})
|
|
182
|
+
elif self.http_radio.isChecked():
|
|
183
|
+
config.update({
|
|
184
|
+
"type": "http",
|
|
185
|
+
"host": self.host_input.text(),
|
|
186
|
+
"port": int(self.port_input.text()),
|
|
187
|
+
"api_key": self.api_key_input.text() if self.api_key_input.text() else None
|
|
188
|
+
})
|
|
189
|
+
else:
|
|
190
|
+
config.update({"type": "ephemeral"})
|
|
191
|
+
|
|
192
|
+
# Save auto-connect preference
|
|
193
|
+
config["auto_connect"] = self.auto_connect_check.isChecked()
|
|
194
|
+
|
|
195
|
+
# Save this configuration for next time
|
|
196
|
+
self.settings_service.save_last_connection(config)
|
|
197
|
+
|
|
198
|
+
return config
|
|
199
|
+
|
|
200
|
+
def _update_absolute_preview(self):
|
|
201
|
+
"""Show resolved absolute path for persistent connections."""
|
|
202
|
+
if not self.persistent_radio.isChecked():
|
|
203
|
+
self.absolute_path_label.setText("")
|
|
204
|
+
return
|
|
205
|
+
rel = self.path_input.text().strip() or "."
|
|
206
|
+
# Resolve relative to project root by searching for pyproject.toml
|
|
207
|
+
from pathlib import Path
|
|
208
|
+
current = Path(__file__).resolve()
|
|
209
|
+
abs_path = None
|
|
210
|
+
for parent in current.parents:
|
|
211
|
+
if (parent / "pyproject.toml").exists():
|
|
212
|
+
abs_path = (parent / rel).resolve()
|
|
213
|
+
break
|
|
214
|
+
if abs_path is None:
|
|
215
|
+
abs_path = Path(rel).resolve()
|
|
216
|
+
self.absolute_path_label.setText(f"Resolved path: {abs_path}")
|
|
217
|
+
|
|
218
|
+
def _browse_for_path(self):
|
|
219
|
+
"""Open a folder chooser to select persistent storage path."""
|
|
220
|
+
# Suggest current resolved path as starting point
|
|
221
|
+
start_dir = None
|
|
222
|
+
from pathlib import Path
|
|
223
|
+
rel = self.path_input.text().strip() or "."
|
|
224
|
+
current = Path(__file__).resolve()
|
|
225
|
+
for parent in current.parents:
|
|
226
|
+
if (parent / "pyproject.toml").exists():
|
|
227
|
+
start_dir = str((parent / rel).resolve())
|
|
228
|
+
break
|
|
229
|
+
if start_dir is None:
|
|
230
|
+
start_dir = str(Path(rel).resolve())
|
|
231
|
+
directory = QFileDialog.getExistingDirectory(self, "Select ChromaDB Data Directory", start_dir)
|
|
232
|
+
if directory:
|
|
233
|
+
# Set as relative to project root if within it, else absolute
|
|
234
|
+
proj_root = None
|
|
235
|
+
for parent in current.parents:
|
|
236
|
+
if (parent / "pyproject.toml").exists():
|
|
237
|
+
proj_root = parent
|
|
238
|
+
break
|
|
239
|
+
dir_path = Path(directory)
|
|
240
|
+
if proj_root and proj_root in dir_path.parents:
|
|
241
|
+
try:
|
|
242
|
+
display_path = str(dir_path.relative_to(proj_root))
|
|
243
|
+
except Exception:
|
|
244
|
+
display_path = str(dir_path)
|
|
245
|
+
else:
|
|
246
|
+
display_path = str(dir_path)
|
|
247
|
+
self.path_input.setText(display_path)
|
|
248
|
+
self._update_absolute_preview()
|
|
249
|
+
|
|
250
|
+
def _load_last_connection(self):
|
|
251
|
+
"""Load and populate the last connection configuration."""
|
|
252
|
+
last_config = self.settings_service.get_last_connection()
|
|
253
|
+
if not last_config:
|
|
254
|
+
return
|
|
255
|
+
|
|
256
|
+
# Set provider
|
|
257
|
+
provider = last_config.get("provider", "chromadb")
|
|
258
|
+
index = self.provider_combo.findData(provider)
|
|
259
|
+
if index >= 0:
|
|
260
|
+
self.provider_combo.setCurrentIndex(index)
|
|
261
|
+
|
|
262
|
+
# Set connection type
|
|
263
|
+
conn_type = last_config.get("type", "persistent")
|
|
264
|
+
if conn_type == "persistent":
|
|
265
|
+
self.persistent_radio.setChecked(True)
|
|
266
|
+
path = last_config.get("path", "")
|
|
267
|
+
if path:
|
|
268
|
+
self.path_input.setText(path)
|
|
269
|
+
elif conn_type == "http":
|
|
270
|
+
self.http_radio.setChecked(True)
|
|
271
|
+
host = last_config.get("host", "localhost")
|
|
272
|
+
port = last_config.get("port", "8000")
|
|
273
|
+
self.host_input.setText(host)
|
|
274
|
+
self.port_input.setText(str(port))
|
|
275
|
+
api_key = last_config.get("api_key")
|
|
276
|
+
if api_key:
|
|
277
|
+
self.api_key_input.setText(api_key)
|
|
278
|
+
elif conn_type == "ephemeral":
|
|
279
|
+
self.ephemeral_radio.setChecked(True)
|
|
280
|
+
|
|
281
|
+
# Set auto-connect checkbox
|
|
282
|
+
auto_connect = last_config.get("auto_connect", False)
|
|
283
|
+
self.auto_connect_check.setChecked(auto_connect)
|
|
284
|
+
|
|
285
|
+
|
|
286
|
+
class ConnectionView(QWidget):
|
|
287
|
+
"""Widget for managing database connection."""
|
|
288
|
+
|
|
289
|
+
connection_changed = Signal(bool)
|
|
290
|
+
connection_created = Signal(VectorDBConnection) # Signal when new connection is created
|
|
291
|
+
|
|
292
|
+
def __init__(self, connection: VectorDBConnection, parent=None):
|
|
293
|
+
super().__init__(parent)
|
|
294
|
+
self.connection = connection
|
|
295
|
+
self.loading_dialog = LoadingDialog("Connecting to database...", self)
|
|
296
|
+
self.settings_service = SettingsService()
|
|
297
|
+
self._setup_ui()
|
|
298
|
+
|
|
299
|
+
# Try to auto-connect if enabled in settings
|
|
300
|
+
self._try_auto_connect()
|
|
301
|
+
|
|
302
|
+
def _setup_ui(self):
|
|
303
|
+
"""Setup widget UI."""
|
|
304
|
+
layout = QVBoxLayout(self)
|
|
305
|
+
layout.setContentsMargins(0, 0, 0, 0)
|
|
306
|
+
|
|
307
|
+
# Connection status group
|
|
308
|
+
group = QGroupBox("Connection")
|
|
309
|
+
group_layout = QVBoxLayout()
|
|
310
|
+
|
|
311
|
+
self.status_label = QLabel("Status: Not connected")
|
|
312
|
+
group_layout.addWidget(self.status_label)
|
|
313
|
+
|
|
314
|
+
# Button layout with both connect and disconnect
|
|
315
|
+
button_layout = QHBoxLayout()
|
|
316
|
+
|
|
317
|
+
self.connect_button = QPushButton("Connect")
|
|
318
|
+
self.connect_button.clicked.connect(self.show_connection_dialog)
|
|
319
|
+
button_layout.addWidget(self.connect_button)
|
|
320
|
+
|
|
321
|
+
self.disconnect_button = QPushButton("Disconnect")
|
|
322
|
+
self.disconnect_button.clicked.connect(self._disconnect)
|
|
323
|
+
self.disconnect_button.setEnabled(False)
|
|
324
|
+
button_layout.addWidget(self.disconnect_button)
|
|
325
|
+
|
|
326
|
+
group_layout.addLayout(button_layout)
|
|
327
|
+
|
|
328
|
+
group.setLayout(group_layout)
|
|
329
|
+
layout.addWidget(group)
|
|
330
|
+
|
|
331
|
+
def show_connection_dialog(self):
|
|
332
|
+
"""Show connection configuration dialog."""
|
|
333
|
+
dialog = ConnectionDialog(self)
|
|
334
|
+
|
|
335
|
+
if dialog.exec() == QDialog.Accepted:
|
|
336
|
+
config = dialog.get_connection_config()
|
|
337
|
+
self._connect_with_config(config)
|
|
338
|
+
|
|
339
|
+
def _connect_with_config(self, config: dict):
|
|
340
|
+
"""Connect to database with given configuration."""
|
|
341
|
+
self.loading_dialog.show_loading("Connecting to database...")
|
|
342
|
+
QApplication.processEvents()
|
|
343
|
+
|
|
344
|
+
provider = config.get("provider", "chromadb")
|
|
345
|
+
conn_type = config.get("type")
|
|
346
|
+
|
|
347
|
+
# Create appropriate connection instance based on provider
|
|
348
|
+
if provider == "qdrant":
|
|
349
|
+
if conn_type == "persistent":
|
|
350
|
+
self.connection = QdrantConnection(path=config.get("path"))
|
|
351
|
+
elif conn_type == "http":
|
|
352
|
+
self.connection = QdrantConnection(
|
|
353
|
+
host=config.get("host"),
|
|
354
|
+
port=config.get("port"),
|
|
355
|
+
api_key=config.get("api_key")
|
|
356
|
+
)
|
|
357
|
+
else: # ephemeral/memory
|
|
358
|
+
self.connection = QdrantConnection()
|
|
359
|
+
else: # chromadb
|
|
360
|
+
if conn_type == "persistent":
|
|
361
|
+
self.connection = ChromaDBConnection(path=config.get("path"))
|
|
362
|
+
elif conn_type == "http":
|
|
363
|
+
self.connection = ChromaDBConnection(
|
|
364
|
+
host=config.get("host"),
|
|
365
|
+
port=config.get("port")
|
|
366
|
+
)
|
|
367
|
+
else: # ephemeral
|
|
368
|
+
self.connection = ChromaDBConnection()
|
|
369
|
+
|
|
370
|
+
# Notify parent that connection instance changed
|
|
371
|
+
self.connection_created.emit(self.connection)
|
|
372
|
+
success = self.connection.connect()
|
|
373
|
+
|
|
374
|
+
if success:
|
|
375
|
+
# Show provider, path/host + collection count for clarity
|
|
376
|
+
details = []
|
|
377
|
+
details.append(f"provider: {provider}")
|
|
378
|
+
if hasattr(self.connection, 'path') and self.connection.path:
|
|
379
|
+
details.append(f"path: {self.connection.path}")
|
|
380
|
+
if hasattr(self.connection, 'host') and self.connection.host:
|
|
381
|
+
port = getattr(self.connection, 'port', None)
|
|
382
|
+
details.append(f"host: {self.connection.host}:{port}")
|
|
383
|
+
collections = self.connection.list_collections()
|
|
384
|
+
count_text = f"collections: {len(collections)}"
|
|
385
|
+
info = ", ".join(details)
|
|
386
|
+
self.status_label.setText(f"Status: Connected ({info}, {count_text})")
|
|
387
|
+
|
|
388
|
+
# Enable disconnect, disable connect
|
|
389
|
+
self.connect_button.setEnabled(False)
|
|
390
|
+
self.disconnect_button.setEnabled(True)
|
|
391
|
+
|
|
392
|
+
# Emit signal which triggers collection browser refresh
|
|
393
|
+
self.connection_changed.emit(True)
|
|
394
|
+
|
|
395
|
+
# Process events to ensure collection browser is updated
|
|
396
|
+
QApplication.processEvents()
|
|
397
|
+
else:
|
|
398
|
+
self.status_label.setText("Status: Connection failed")
|
|
399
|
+
# Enable connect, disable disconnect
|
|
400
|
+
self.connect_button.setEnabled(True)
|
|
401
|
+
self.disconnect_button.setEnabled(False)
|
|
402
|
+
self.connection_changed.emit(False)
|
|
403
|
+
|
|
404
|
+
# Close loading dialog after everything is complete
|
|
405
|
+
self.loading_dialog.hide_loading()
|
|
406
|
+
|
|
407
|
+
def _disconnect(self):
|
|
408
|
+
"""Disconnect from database."""
|
|
409
|
+
self.connection.disconnect()
|
|
410
|
+
self.status_label.setText("Status: Not connected")
|
|
411
|
+
|
|
412
|
+
# Enable connect, disable disconnect
|
|
413
|
+
self.connect_button.setEnabled(True)
|
|
414
|
+
self.disconnect_button.setEnabled(False)
|
|
415
|
+
|
|
416
|
+
self.connection_changed.emit(False)
|
|
417
|
+
|
|
418
|
+
def _try_auto_connect(self):
|
|
419
|
+
"""Try to automatically connect if auto-connect is enabled."""
|
|
420
|
+
last_config = self.settings_service.get_last_connection()
|
|
421
|
+
if last_config and last_config.get("auto_connect", False):
|
|
422
|
+
# Auto-connect is enabled
|
|
423
|
+
self._connect_with_config(last_config)
|