vector-inspector 0.3.2__py3-none-any.whl → 0.3.3__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.
Files changed (32) hide show
  1. vector_inspector/core/connection_manager.py +55 -49
  2. vector_inspector/core/connections/base_connection.py +41 -41
  3. vector_inspector/core/connections/chroma_connection.py +110 -86
  4. vector_inspector/core/connections/pinecone_connection.py +168 -182
  5. vector_inspector/core/connections/qdrant_connection.py +109 -126
  6. vector_inspector/core/connections/qdrant_helpers/__init__.py +4 -0
  7. vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +35 -0
  8. vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +51 -0
  9. vector_inspector/core/connections/template_connection.py +55 -65
  10. vector_inspector/core/embedding_utils.py +32 -32
  11. vector_inspector/core/logging.py +27 -0
  12. vector_inspector/core/model_registry.py +4 -3
  13. vector_inspector/main.py +6 -2
  14. vector_inspector/services/backup_helpers.py +63 -0
  15. vector_inspector/services/backup_restore_service.py +73 -152
  16. vector_inspector/services/credential_service.py +33 -40
  17. vector_inspector/services/import_export_service.py +70 -67
  18. vector_inspector/services/profile_service.py +92 -94
  19. vector_inspector/services/settings_service.py +68 -48
  20. vector_inspector/services/visualization_service.py +40 -39
  21. vector_inspector/ui/components/splash_window.py +57 -0
  22. vector_inspector/ui/dialogs/cross_db_migration.py +6 -5
  23. vector_inspector/ui/main_window.py +200 -146
  24. vector_inspector/ui/views/info_panel.py +208 -127
  25. vector_inspector/ui/views/metadata_view.py +8 -7
  26. vector_inspector/ui/views/search_view.py +97 -75
  27. vector_inspector/ui/views/visualization_view.py +140 -97
  28. vector_inspector/utils/version.py +5 -0
  29. {vector_inspector-0.3.2.dist-info → vector_inspector-0.3.3.dist-info}/METADATA +3 -1
  30. {vector_inspector-0.3.2.dist-info → vector_inspector-0.3.3.dist-info}/RECORD +32 -25
  31. {vector_inspector-0.3.2.dist-info → vector_inspector-0.3.3.dist-info}/WHEEL +0 -0
  32. {vector_inspector-0.3.2.dist-info → vector_inspector-0.3.3.dist-info}/entry_points.txt +0 -0
@@ -2,8 +2,14 @@
2
2
 
3
3
  from typing import Optional, Dict, Any
4
4
  from PySide6.QtWidgets import (
5
- QWidget, QVBoxLayout, QHBoxLayout, QLabel,
6
- QGroupBox, QScrollArea, QFrame, QPushButton
5
+ QWidget,
6
+ QVBoxLayout,
7
+ QHBoxLayout,
8
+ QLabel,
9
+ QGroupBox,
10
+ QScrollArea,
11
+ QFrame,
12
+ QPushButton,
7
13
  )
8
14
  from PySide6.QtCore import Qt, QObject
9
15
  from PySide6.QtWidgets import QDialog
@@ -14,11 +20,12 @@ from vector_inspector.core.connections.chroma_connection import ChromaDBConnecti
14
20
  from vector_inspector.core.connections.qdrant_connection import QdrantConnection
15
21
  from vector_inspector.core.connections.pinecone_connection import PineconeConnection
16
22
  from vector_inspector.core.cache_manager import get_cache_manager
23
+ from vector_inspector.core.logging import log_info
17
24
 
18
25
 
19
26
  class InfoPanel(QWidget):
20
27
  """Panel for displaying database and collection information."""
21
-
28
+
22
29
  def __init__(self, connection: VectorDBConnection, parent=None):
23
30
  super().__init__(parent)
24
31
  self.connection = connection
@@ -27,127 +34,166 @@ class InfoPanel(QWidget):
27
34
  self.current_database: str = ""
28
35
  self.cache_manager = get_cache_manager()
29
36
  self._setup_ui()
30
-
37
+
31
38
  def _setup_ui(self):
32
39
  """Setup widget UI."""
33
40
  layout = QVBoxLayout(self)
34
-
41
+
35
42
  # Create scroll area for content
36
43
  scroll = QScrollArea()
37
44
  scroll.setWidgetResizable(True)
38
45
  scroll.setHorizontalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOff)
39
-
46
+
40
47
  # Container for all info sections
41
48
  container = QWidget()
42
49
  container_layout = QVBoxLayout(container)
43
50
  container_layout.setSpacing(10)
44
-
51
+
45
52
  # Database Information Section
46
53
  self.db_group = QGroupBox("Database Information")
47
54
  db_layout = QVBoxLayout()
48
-
55
+
49
56
  self.provider_label = self._create_info_row("Provider:", "Not connected")
50
57
  self.connection_type_label = self._create_info_row("Connection Type:", "N/A")
51
58
  self.endpoint_label = self._create_info_row("Endpoint:", "N/A")
52
59
  self.api_key_label = self._create_info_row("API Key:", "N/A")
53
60
  self.status_label = self._create_info_row("Status:", "Disconnected")
54
61
  self.collections_count_label = self._create_info_row("Total Collections:", "0")
55
-
62
+
56
63
  db_layout.addWidget(self.provider_label)
57
64
  db_layout.addWidget(self.connection_type_label)
58
65
  db_layout.addWidget(self.endpoint_label)
59
66
  db_layout.addWidget(self.api_key_label)
60
67
  db_layout.addWidget(self.status_label)
61
68
  db_layout.addWidget(self.collections_count_label)
62
-
69
+
63
70
  self.db_group.setLayout(db_layout)
64
71
  container_layout.addWidget(self.db_group)
65
-
72
+
66
73
  # Collection Information Section
67
74
  self.collection_group = QGroupBox("Collection Information")
68
75
  collection_layout = QVBoxLayout()
69
-
76
+
70
77
  self.collection_name_label = self._create_info_row("Name:", "No collection selected")
71
78
  self.vector_dim_label = self._create_info_row("Vector Dimension:", "N/A")
72
79
  self.distance_metric_label = self._create_info_row("Distance Metric:", "N/A")
73
80
  self.total_points_label = self._create_info_row("Total Points:", "0")
74
-
81
+
75
82
  # Embedding model row with configure button
76
83
  embedding_row = QWidget()
77
84
  embedding_layout = QHBoxLayout(embedding_row)
78
85
  embedding_layout.setContentsMargins(0, 2, 0, 2)
79
-
86
+
80
87
  embedding_label = QLabel("<b>Embedding Model:</b>")
81
88
  embedding_label.setMinimumWidth(150)
82
89
  self.embedding_model_label = QLabel("Auto-detect")
83
90
  self.embedding_model_label.setStyleSheet("color: gray;")
84
91
  self.embedding_model_label.setWordWrap(True)
85
-
92
+
86
93
  self.configure_embedding_btn = QPushButton("Configure...")
87
94
  self.configure_embedding_btn.setMaximumWidth(100)
88
95
  self.configure_embedding_btn.clicked.connect(self._configure_embedding_model)
89
96
  self.configure_embedding_btn.setEnabled(False)
90
-
97
+
98
+ self.clear_embedding_btn = QPushButton("Reset to Auto-Detect")
99
+ self.clear_embedding_btn.setMaximumWidth(140)
100
+ self.clear_embedding_btn.setToolTip(
101
+ "Remove custom embedding model and use automatic detection based on collection dimension."
102
+ )
103
+ self.clear_embedding_btn.clicked.connect(self._clear_embedding_model)
104
+ self.clear_embedding_btn.setEnabled(False)
105
+
91
106
  embedding_layout.addWidget(embedding_label)
92
107
  embedding_layout.addWidget(self.embedding_model_label, 1)
93
108
  embedding_layout.addWidget(self.configure_embedding_btn)
94
-
109
+ embedding_layout.addWidget(self.clear_embedding_btn)
110
+
111
+ # Add the embedding row and other collection info widgets to the collection layout
95
112
  collection_layout.addWidget(self.collection_name_label)
96
113
  collection_layout.addWidget(self.vector_dim_label)
97
114
  collection_layout.addWidget(self.distance_metric_label)
98
115
  collection_layout.addWidget(self.total_points_label)
99
116
  collection_layout.addWidget(embedding_row)
100
-
117
+
101
118
  # Payload Schema subsection
102
119
  schema_label = QLabel("<b>Payload Schema:</b>")
103
120
  collection_layout.addWidget(schema_label)
104
-
121
+
105
122
  self.schema_label = QLabel("N/A")
106
123
  self.schema_label.setWordWrap(True)
107
124
  self.schema_label.setStyleSheet("color: gray; padding-left: 20px;")
108
125
  collection_layout.addWidget(self.schema_label)
109
-
126
+
110
127
  # Provider-specific details
111
128
  provider_details_label = QLabel("<b>Provider-Specific Details:</b>")
112
129
  collection_layout.addWidget(provider_details_label)
113
-
130
+
114
131
  self.provider_details_label = QLabel("N/A")
115
132
  self.provider_details_label.setWordWrap(True)
116
133
  self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
117
134
  collection_layout.addWidget(self.provider_details_label)
118
-
135
+
119
136
  self.collection_group.setLayout(collection_layout)
120
137
  container_layout.addWidget(self.collection_group)
121
-
138
+
122
139
  # Add stretch to push content to top
123
140
  container_layout.addStretch()
124
-
141
+
125
142
  scroll.setWidget(container)
126
143
  layout.addWidget(scroll)
127
-
144
+
128
145
  # Initial state
129
146
  self.refresh_database_info()
130
-
147
+
148
+ def _clear_embedding_model(self):
149
+ """Clear the embedding model configuration for this collection (reset to autodetect)."""
150
+ from ...services.settings_service import SettingsService
151
+
152
+ settings = SettingsService()
153
+ settings.remove_embedding_model(self.connection_id, self.current_collection)
154
+ # Refresh display (force reload collection info)
155
+ if self.current_collection:
156
+ self.set_collection(self.current_collection, self.current_database)
157
+ log_info(
158
+ "✓ Cleared embedding model configuration for '%s' (via info panel button)",
159
+ self.current_collection,
160
+ )
161
+
162
+ def _update_clear_button_state(self):
163
+ """Update the clear button state based on current configuration."""
164
+ from ...services.settings_service import SettingsService
165
+
166
+ if not self.connection_id or not self.current_collection:
167
+ self.clear_embedding_btn.setEnabled(False)
168
+ return
169
+
170
+ # Check if there's a user-configured model in settings
171
+ settings = SettingsService()
172
+ model_info = settings.get_embedding_model(self.connection_id, self.current_collection)
173
+
174
+ # Enable button if there's a user-configured model
175
+ self.clear_embedding_btn.setEnabled(model_info is not None)
176
+
131
177
  def _create_info_row(self, label: str, value: str) -> QWidget:
132
178
  """Create a row with label and value."""
133
179
  row = QWidget()
134
180
  row_layout = QHBoxLayout(row)
135
181
  row_layout.setContentsMargins(0, 2, 0, 2)
136
-
182
+
137
183
  label_widget = QLabel(f"<b>{label}</b>")
138
184
  label_widget.setMinimumWidth(150)
139
185
  row_layout.addWidget(label_widget)
140
-
186
+
141
187
  value_widget = QLabel(value)
142
188
  value_widget.setWordWrap(True)
143
189
  value_widget.setStyleSheet("color: white;")
144
190
  row_layout.addWidget(value_widget, stretch=1)
145
-
191
+
146
192
  # Store value widget for later updates (use setProperty for type safety)
147
193
  row.setProperty("value_label", value_widget)
148
-
194
+
149
195
  return row
150
-
196
+
151
197
  def refresh_database_info(self):
152
198
  """Refresh database connection information."""
153
199
  if not self.connection or not self.connection.is_connected:
@@ -165,11 +211,11 @@ class InfoPanel(QWidget):
165
211
  self.schema_label.setText("N/A")
166
212
  self.provider_details_label.setText("N/A")
167
213
  return
168
-
214
+
169
215
  # Get provider name
170
216
  provider_name = self.connection.__class__.__name__.replace("Connection", "")
171
217
  self._update_label(self.provider_label, provider_name)
172
-
218
+
173
219
  # Get connection details
174
220
  if isinstance(self.connection, ChromaDBConnection):
175
221
  if self.connection.path:
@@ -177,12 +223,14 @@ class InfoPanel(QWidget):
177
223
  self._update_label(self.endpoint_label, self.connection.path)
178
224
  elif self.connection.host and self.connection.port:
179
225
  self._update_label(self.connection_type_label, "HTTP (Remote)")
180
- self._update_label(self.endpoint_label, f"{self.connection.host}:{self.connection.port}")
226
+ self._update_label(
227
+ self.endpoint_label, f"{self.connection.host}:{self.connection.port}"
228
+ )
181
229
  else:
182
230
  self._update_label(self.connection_type_label, "Ephemeral (In-Memory)")
183
231
  self._update_label(self.endpoint_label, "N/A")
184
232
  self._update_label(self.api_key_label, "Not required")
185
-
233
+
186
234
  elif isinstance(self.connection, QdrantConnection):
187
235
  if self.connection.path:
188
236
  self._update_label(self.connection_type_label, "Embedded (Local)")
@@ -192,20 +240,22 @@ class InfoPanel(QWidget):
192
240
  self._update_label(self.endpoint_label, self.connection.url)
193
241
  elif self.connection.host:
194
242
  self._update_label(self.connection_type_label, "Remote (Host)")
195
- self._update_label(self.endpoint_label, f"{self.connection.host}:{self.connection.port}")
243
+ self._update_label(
244
+ self.endpoint_label, f"{self.connection.host}:{self.connection.port}"
245
+ )
196
246
  else:
197
247
  self._update_label(self.connection_type_label, "In-Memory")
198
248
  self._update_label(self.endpoint_label, "N/A")
199
-
249
+
200
250
  if self.connection.api_key:
201
251
  self._update_label(self.api_key_label, "Present (hidden)")
202
252
  else:
203
253
  self._update_label(self.api_key_label, "Not configured")
204
-
254
+
205
255
  elif isinstance(self.connection, PineconeConnection):
206
256
  self._update_label(self.connection_type_label, "Cloud")
207
257
  self._update_label(self.endpoint_label, "Pinecone Cloud")
208
-
258
+
209
259
  if self.connection.api_key:
210
260
  self._update_label(self.api_key_label, "Present (hidden)")
211
261
  else:
@@ -214,17 +264,19 @@ class InfoPanel(QWidget):
214
264
  self._update_label(self.connection_type_label, "Unknown")
215
265
  self._update_label(self.endpoint_label, "N/A")
216
266
  self._update_label(self.api_key_label, "Unknown")
217
-
267
+
218
268
  # Status
219
- self._update_label(self.status_label, "Connected" if self.connection.is_connected else "Disconnected")
220
-
269
+ self._update_label(
270
+ self.status_label, "Connected" if self.connection.is_connected else "Disconnected"
271
+ )
272
+
221
273
  # Count collections
222
274
  try:
223
275
  collections = self.connection.list_collections()
224
276
  self._update_label(self.collections_count_label, str(len(collections)))
225
277
  except Exception as e:
226
278
  self._update_label(self.collections_count_label, "Error")
227
-
279
+
228
280
  def refresh_collection_info(self):
229
281
  """Refresh collection-specific information."""
230
282
  if not self.current_collection or not self.connection or not self.connection.is_connected:
@@ -235,11 +287,11 @@ class InfoPanel(QWidget):
235
287
  self.schema_label.setText("N/A")
236
288
  self.provider_details_label.setText("N/A")
237
289
  return
238
-
290
+
239
291
  try:
240
292
  # Get collection info from database
241
293
  collection_info = self.connection.get_collection_info(self.current_collection)
242
-
294
+
243
295
  if not collection_info:
244
296
  self._update_label(self.collection_name_label, self.current_collection)
245
297
  self._update_label(self.vector_dim_label, "Unable to retrieve")
@@ -248,20 +300,24 @@ class InfoPanel(QWidget):
248
300
  self.schema_label.setText("Unable to retrieve collection info")
249
301
  self.provider_details_label.setText("N/A")
250
302
  return
251
-
303
+
252
304
  # Display the info
253
305
  self._display_collection_info(collection_info)
254
-
306
+
255
307
  # Save to cache
256
308
  if self.current_database and self.current_collection:
257
- print(f"[InfoPanel] Saving collection info to cache: db='{self.current_database}', coll='{self.current_collection}'")
309
+ log_info(
310
+ "[InfoPanel] Saving collection info to cache: db='%s', coll='%s'",
311
+ self.current_database,
312
+ self.current_collection,
313
+ )
258
314
  self.cache_manager.update(
259
315
  self.current_database,
260
316
  self.current_collection,
261
- user_inputs={'collection_info': collection_info}
317
+ user_inputs={"collection_info": collection_info},
262
318
  )
263
- print(f"[InfoPanel] ✓ Saved collection info to cache.")
264
-
319
+ log_info("[InfoPanel] ✓ Saved collection info to cache.")
320
+
265
321
  except Exception as e:
266
322
  self._update_label(self.collection_name_label, self.current_collection)
267
323
  self._update_label(self.vector_dim_label, "Error")
@@ -270,50 +326,55 @@ class InfoPanel(QWidget):
270
326
  self.schema_label.setText(f"Error: {str(e)}")
271
327
  self.schema_label.setStyleSheet("color: red; padding-left: 20px;")
272
328
  self.provider_details_label.setText("N/A")
273
-
329
+
274
330
  def _display_collection_info(self, collection_info: Dict[str, Any]):
275
331
  """Display collection information (from cache or fresh query)."""
276
332
  # Update basic info
277
333
  self._update_label(self.collection_name_label, self.current_collection)
278
-
334
+
279
335
  # Vector dimension
280
336
  vector_dim = collection_info.get("vector_dimension", "Unknown")
281
337
  self._update_label(self.vector_dim_label, str(vector_dim))
282
-
338
+
283
339
  # Enable configure button if we have a valid dimension
284
340
  self.configure_embedding_btn.setEnabled(
285
341
  vector_dim != "Unknown" and isinstance(vector_dim, int)
286
342
  )
287
-
343
+
288
344
  # Update embedding model display
289
345
  self._update_embedding_model_display(collection_info)
290
-
346
+
347
+ # Update clear button state
348
+ self._update_clear_button_state()
349
+
291
350
  # Distance metric
292
351
  distance = collection_info.get("distance_metric", "Unknown")
293
352
  self._update_label(self.distance_metric_label, distance)
294
-
353
+
295
354
  # Total points
296
355
  count = collection_info.get("count", 0)
297
356
  self._update_label(self.total_points_label, f"{count:,}")
298
-
357
+
299
358
  # Metadata schema
300
359
  metadata_fields = collection_info.get("metadata_fields", [])
301
360
  if metadata_fields:
302
361
  schema_text = "\n".join([f"• {field}" for field in sorted(metadata_fields)])
303
362
  self.schema_label.setText(schema_text)
304
- self.schema_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
363
+ self.schema_label.setStyleSheet(
364
+ "color: white; padding-left: 20px; font-family: monospace;"
365
+ )
305
366
  else:
306
367
  self.schema_label.setText("No metadata fields found")
307
368
  self.schema_label.setStyleSheet("color: gray; padding-left: 20px;")
308
-
369
+
309
370
  # Provider-specific details
310
371
  details_list = []
311
-
372
+
312
373
  if isinstance(self.connection, ChromaDBConnection):
313
374
  details_list.append("• Provider: ChromaDB")
314
375
  details_list.append("• Supports: Documents, Metadata, Embeddings")
315
376
  details_list.append("• Default embedding: all-MiniLM-L6-v2")
316
-
377
+
317
378
  elif isinstance(self.connection, QdrantConnection):
318
379
  details_list.append("• Provider: Qdrant")
319
380
  details_list.append("• Supports: Points, Payload, Vectors")
@@ -326,8 +387,10 @@ class InfoPanel(QWidget):
326
387
  details_list.append(f"• HNSW ef_construct: {hnsw.get('ef_construct', 'N/A')}")
327
388
  if "optimizer_config" in config:
328
389
  opt = config["optimizer_config"]
329
- details_list.append(f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}")
330
-
390
+ details_list.append(
391
+ f"• Indexing threshold: {opt.get('indexing_threshold', 'N/A')}"
392
+ )
393
+
331
394
  elif isinstance(self.connection, PineconeConnection):
332
395
  details_list.append("• Provider: Pinecone")
333
396
  details_list.append("• Supports: Vectors, Metadata")
@@ -339,14 +402,16 @@ class InfoPanel(QWidget):
339
402
  details_list.append(f"• Status: {collection_info['status']}")
340
403
  if "spec" in collection_info:
341
404
  details_list.append(f"• Spec: {collection_info['spec']}")
342
-
405
+
343
406
  if details_list:
344
407
  self.provider_details_label.setText("\n".join(details_list))
345
- self.provider_details_label.setStyleSheet("color: white; padding-left: 20px; font-family: monospace;")
408
+ self.provider_details_label.setStyleSheet(
409
+ "color: white; padding-left: 20px; font-family: monospace;"
410
+ )
346
411
  else:
347
412
  self.provider_details_label.setText("No additional details available")
348
413
  self.provider_details_label.setStyleSheet("color: gray; padding-left: 20px;")
349
-
414
+
350
415
  def set_collection(self, collection_name: str, database_name: str = ""):
351
416
  """Set the current collection and refresh its information."""
352
417
  self.current_collection = collection_name
@@ -354,75 +419,89 @@ class InfoPanel(QWidget):
354
419
  if database_name:
355
420
  self.current_database = database_name
356
421
  self.connection_id = database_name # database_name is the connection ID
357
-
358
- print(f"[InfoPanel] Setting collection: db='{self.current_database}', coll='{collection_name}'")
359
-
422
+
423
+ log_info(
424
+ "[InfoPanel] Setting collection: db='%s', coll='%s'",
425
+ self.current_database,
426
+ collection_name,
427
+ )
428
+
360
429
  # Check cache first for collection info
361
430
  cached = self.cache_manager.get(self.current_database, self.current_collection)
362
- if cached and hasattr(cached, 'user_inputs') and cached.user_inputs.get('collection_info'):
363
- print(f"[InfoPanel] ✓ Cache HIT! Loading collection info from cache.")
364
- collection_info = cached.user_inputs['collection_info']
431
+ if cached and hasattr(cached, "user_inputs") and cached.user_inputs.get("collection_info"):
432
+ log_info("[InfoPanel] ✓ Cache HIT! Loading collection info from cache.")
433
+ collection_info = cached.user_inputs["collection_info"]
365
434
  self._display_collection_info(collection_info)
366
435
  return
367
-
368
- print(f"[InfoPanel] ✗ Cache MISS. Loading collection info from database...")
436
+
437
+ log_info("[InfoPanel] ✗ Cache MISS. Loading collection info from database...")
369
438
  self.refresh_collection_info()
370
-
439
+
371
440
  def _update_label(self, row_widget: QWidget, value: str):
372
441
  """Update the value label in an info row."""
373
442
  value_label = row_widget.property("value_label")
374
443
  if value_label and isinstance(value_label, QLabel):
375
444
  value_label.setText(value)
376
-
445
+
377
446
  def _update_embedding_model_display(self, collection_info: Dict[str, Any]):
378
447
  """Update the embedding model label based on current configuration."""
379
448
  from ...services.settings_service import SettingsService
380
-
449
+
381
450
  # Check if stored in collection metadata
382
- if 'embedding_model' in collection_info:
383
- model_name = collection_info['embedding_model']
384
- model_type = collection_info.get('embedding_model_type', 'stored')
451
+ # Default: disable clear button
452
+ self.clear_embedding_btn.setEnabled(False)
453
+
454
+ if "embedding_model" in collection_info:
455
+ model_name = collection_info["embedding_model"]
456
+ model_type = collection_info.get("embedding_model_type", "stored")
385
457
  self.embedding_model_label.setText(f"{model_name} ({model_type})")
386
458
  self.embedding_model_label.setStyleSheet("color: lightgreen;")
459
+ self.clear_embedding_btn.setEnabled(True)
387
460
  return
388
-
461
+
389
462
  # Try to get from connection using the helper method
390
463
  if self.connection and self.current_collection:
391
- detected_model = self.connection.get_embedding_model(self.current_collection, self.connection_id)
464
+ detected_model = self.connection.get_embedding_model(
465
+ self.current_collection, self.connection_id
466
+ )
392
467
  if detected_model:
393
468
  self.embedding_model_label.setText(f"{detected_model} (detected)")
394
469
  self.embedding_model_label.setStyleSheet("color: lightgreen;")
470
+ self.clear_embedding_btn.setEnabled(False)
395
471
  return
396
-
472
+
397
473
  # Check user settings
398
474
  settings = SettingsService()
399
475
  model_info = settings.get_embedding_model(self.connection_id, self.current_collection)
400
-
476
+
401
477
  if model_info:
402
- model_name = model_info['model']
403
- model_type = model_info.get('type', 'unknown')
478
+ model_name = model_info["model"]
479
+ model_type = model_info.get("type", "unknown")
404
480
  self.embedding_model_label.setText(f"{model_name} ({model_type})")
405
481
  self.embedding_model_label.setStyleSheet("color: lightblue;")
482
+ self.clear_embedding_btn.setEnabled(True)
406
483
  return
407
-
484
+
408
485
  # No configuration - using auto-detect
409
486
  self.embedding_model_label.setText("Auto-detect (dimension-based)")
410
487
  self.embedding_model_label.setStyleSheet("color: orange;")
411
-
488
+ self.clear_embedding_btn.setEnabled(False)
489
+
412
490
  def _configure_embedding_model(self):
413
491
  """Open dialog to configure embedding model for current collection."""
414
492
  if not self.current_collection:
415
493
  return
416
-
494
+
417
495
  # Show loading immediately; preparing can touch DB/registry
418
496
  from ..components.loading_dialog import LoadingDialog
497
+
419
498
  loading = LoadingDialog("Preparing model configuration...", self)
420
499
  loading.show_loading("Preparing model configuration...")
421
500
  QApplication.processEvents()
422
501
 
423
502
  from ..dialogs import ProviderTypeDialog, EmbeddingConfigDialog
424
503
  from ...services.settings_service import SettingsService
425
-
504
+
426
505
  # Get current collection info
427
506
  try:
428
507
  collection_info = self.connection.get_collection_info(self.current_collection)
@@ -431,74 +510,76 @@ class InfoPanel(QWidget):
431
510
  loading.hide_loading()
432
511
  if not collection_info:
433
512
  return
434
-
513
+
435
514
  vector_dim = collection_info.get("vector_dimension")
436
515
  if not vector_dim or vector_dim == "Unknown":
437
516
  return
438
-
517
+
439
518
  # Get current configuration if any
440
519
  settings = SettingsService()
441
-
520
+
442
521
  current_model = None
443
522
  current_type = None
444
-
523
+
445
524
  # Check metadata first
446
- if 'embedding_model' in collection_info:
447
- current_model = collection_info['embedding_model']
448
- current_type = collection_info.get('embedding_model_type', 'stored')
525
+ if "embedding_model" in collection_info:
526
+ current_model = collection_info["embedding_model"]
527
+ current_type = collection_info.get("embedding_model_type", "stored")
449
528
  # Then check settings
450
529
  else:
451
530
  model_info = settings.get_embedding_model(self.connection_id, self.current_collection)
452
531
  if model_info:
453
- current_model = model_info.get('model')
454
- current_type = model_info.get('type')
455
-
532
+ current_model = model_info.get("model")
533
+ current_type = model_info.get("type")
534
+
456
535
  # Step 1: Provider Type Selection
457
- type_dialog = ProviderTypeDialog(
458
- self.current_collection,
459
- vector_dim,
460
- self
461
- )
462
-
536
+ type_dialog = ProviderTypeDialog(self.current_collection, vector_dim, self)
537
+
463
538
  type_result = type_dialog.exec()
464
539
  if type_result != QDialog.DialogCode.Accepted:
465
540
  return # User cancelled
466
-
541
+
467
542
  provider_type = type_dialog.get_selected_type()
468
543
  if not provider_type:
469
544
  return
470
-
545
+
471
546
  # Step 2: Model Selection (filtered by provider type)
472
547
  model_dialog = EmbeddingConfigDialog(
473
- self.current_collection,
474
- vector_dim,
475
- provider_type,
476
- current_model,
477
- current_type,
478
- self
548
+ self.current_collection, vector_dim, provider_type, current_model, current_type, self
479
549
  )
480
-
550
+
481
551
  # Optionally show brief loading while populating models
482
552
  # (dialog itself handles content; only show if provider lists are large)
483
553
  result = model_dialog.exec()
484
-
554
+
485
555
  if result == QDialog.DialogCode.Accepted:
486
556
  # Save the configuration using the new SettingsService method
487
557
  selection = model_dialog.get_selection()
488
558
  if selection:
489
559
  model_name, model_type = selection
490
- settings.save_embedding_model(self.connection_id, self.current_collection, model_name, model_type)
491
-
492
- # Refresh display
493
- self._update_embedding_model_display(collection_info)
494
-
495
- print(f"✓ Configured embedding model for '{self.current_collection}': {model_name} ({model_type})")
496
-
560
+ settings.save_embedding_model(
561
+ self.connection_id, self.current_collection, model_name, model_type
562
+ )
563
+
564
+ # Update the display immediately to show new model
565
+ self.embedding_model_label.setText(f"{model_name} ({model_type})")
566
+ self.embedding_model_label.setStyleSheet("color: lightblue;")
567
+
568
+ # Enable the clear button
569
+ self._update_clear_button_state()
570
+
571
+ log_info(
572
+ "✓ Configured embedding model for '%s': %s (%s)",
573
+ self.current_collection,
574
+ model_name,
575
+ model_type,
576
+ )
577
+
497
578
  elif result == 2: # Clear configuration
498
579
  # Remove from settings using the new SettingsService method
499
580
  settings.remove_embedding_model(self.connection_id, self.current_collection)
500
-
581
+
501
582
  # Refresh display
502
583
  self._update_embedding_model_display(collection_info)
503
-
504
- print(f"✓ Cleared embedding model configuration for '{self.current_collection}'")
584
+
585
+ log_info("✓ Cleared embedding model configuration for '%s'", self.current_collection)