vector-inspector 0.3.9__py3-none-any.whl → 0.3.12__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 +10 -1
- vector_inspector/core/connection_manager.py +91 -19
- vector_inspector/core/connections/base_connection.py +43 -43
- vector_inspector/core/connections/chroma_connection.py +1 -1
- vector_inspector/core/connections/pgvector_connection.py +12 -172
- vector_inspector/core/connections/pinecone_connection.py +596 -99
- vector_inspector/core/connections/qdrant_connection.py +35 -44
- vector_inspector/core/embedding_utils.py +14 -5
- vector_inspector/core/logging.py +3 -1
- vector_inspector/extensions/__init__.py +6 -0
- vector_inspector/extensions/telemetry_settings_panel.py +25 -0
- vector_inspector/main.py +45 -2
- vector_inspector/services/backup_restore_service.py +228 -15
- vector_inspector/services/settings_service.py +79 -19
- vector_inspector/services/telemetry_service.py +88 -0
- vector_inspector/ui/components/backup_restore_dialog.py +215 -101
- vector_inspector/ui/components/connection_manager_panel.py +155 -14
- vector_inspector/ui/dialogs/cross_db_migration.py +126 -99
- vector_inspector/ui/dialogs/settings_dialog.py +13 -6
- vector_inspector/ui/loading_screen.py +169 -0
- vector_inspector/ui/main_window.py +44 -19
- vector_inspector/ui/services/dialog_service.py +1 -0
- vector_inspector/ui/views/collection_browser.py +36 -34
- vector_inspector/ui/views/connection_view.py +7 -1
- vector_inspector/ui/views/info_panel.py +118 -52
- vector_inspector/ui/views/metadata_view.py +30 -31
- vector_inspector/ui/views/search_view.py +20 -19
- vector_inspector/ui/views/visualization_view.py +18 -15
- {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/METADATA +19 -37
- {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/RECORD +33 -29
- {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/WHEEL +1 -1
- vector_inspector-0.3.12.dist-info/licenses/LICENSE +1 -0
- {vector_inspector-0.3.9.dist-info → vector_inspector-0.3.12.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
from PySide6.QtCore import QPropertyAnimation, Qt
|
|
4
|
+
from PySide6.QtGui import QFont, QPixmap
|
|
5
|
+
from PySide6.QtWidgets import QCheckBox, QLabel, QVBoxLayout, QWidget
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class LoadingScreen(QWidget):
|
|
9
|
+
def __init__(self, logo_path, version, app_name, tagline, loading_text):
|
|
10
|
+
super().__init__()
|
|
11
|
+
self.setWindowFlag(Qt.FramelessWindowHint)
|
|
12
|
+
self.setWindowFlag(Qt.WindowStaysOnTopHint)
|
|
13
|
+
self.setAttribute(Qt.WA_TranslucentBackground)
|
|
14
|
+
|
|
15
|
+
# Main layout
|
|
16
|
+
layout = QVBoxLayout(self)
|
|
17
|
+
layout.setContentsMargins(32, 32, 32, 32)
|
|
18
|
+
layout.setSpacing(12)
|
|
19
|
+
|
|
20
|
+
# Container with background
|
|
21
|
+
container = QWidget()
|
|
22
|
+
container.setStyleSheet("background-color: #222; border-radius: 10px; color: #fff;")
|
|
23
|
+
container_layout = QVBoxLayout(container)
|
|
24
|
+
container_layout.setContentsMargins(32, 32, 32, 32)
|
|
25
|
+
container_layout.setSpacing(12)
|
|
26
|
+
|
|
27
|
+
# Logo
|
|
28
|
+
if os.path.exists(logo_path):
|
|
29
|
+
pixmap = QPixmap(logo_path).scaled(
|
|
30
|
+
128, 128, Qt.KeepAspectRatio, Qt.SmoothTransformation
|
|
31
|
+
)
|
|
32
|
+
logo_label = QLabel()
|
|
33
|
+
logo_label.setPixmap(pixmap)
|
|
34
|
+
logo_label.setAlignment(Qt.AlignCenter)
|
|
35
|
+
container_layout.addWidget(logo_label)
|
|
36
|
+
|
|
37
|
+
# App name
|
|
38
|
+
app_name_label = QLabel(app_name)
|
|
39
|
+
app_name_label.setFont(QFont("Segoe UI", 16, QFont.Bold))
|
|
40
|
+
app_name_label.setAlignment(Qt.AlignCenter)
|
|
41
|
+
app_name_label.setStyleSheet("color: #fff;")
|
|
42
|
+
container_layout.addWidget(app_name_label)
|
|
43
|
+
|
|
44
|
+
# Tagline
|
|
45
|
+
tagline_label = QLabel(tagline)
|
|
46
|
+
tagline_label.setFont(QFont("Segoe UI", 10))
|
|
47
|
+
tagline_label.setAlignment(Qt.AlignCenter)
|
|
48
|
+
tagline_label.setStyleSheet("color: #aaa;")
|
|
49
|
+
container_layout.addWidget(tagline_label)
|
|
50
|
+
|
|
51
|
+
# Version
|
|
52
|
+
version_label = QLabel(version)
|
|
53
|
+
version_label.setFont(QFont("Segoe UI", 10))
|
|
54
|
+
version_label.setAlignment(Qt.AlignCenter)
|
|
55
|
+
version_label.setStyleSheet("color: #aaa;")
|
|
56
|
+
container_layout.addWidget(version_label)
|
|
57
|
+
|
|
58
|
+
# Loading indicator
|
|
59
|
+
self.loading_label = QLabel(loading_text)
|
|
60
|
+
self.loading_label.setFont(QFont("Segoe UI", 10))
|
|
61
|
+
self.loading_label.setAlignment(Qt.AlignCenter)
|
|
62
|
+
self.loading_label.setStyleSheet("color: #6cf;")
|
|
63
|
+
container_layout.addWidget(self.loading_label)
|
|
64
|
+
|
|
65
|
+
# Skip loading screen checkbox
|
|
66
|
+
self.skip_loading_checkbox = QCheckBox("Don't show this again")
|
|
67
|
+
self.skip_loading_checkbox.setFont(QFont("Segoe UI", 9))
|
|
68
|
+
self.skip_loading_checkbox.setStyleSheet("color: #aaa; margin-top: 10px;")
|
|
69
|
+
self.skip_loading_checkbox.stateChanged.connect(self._on_skip_changed)
|
|
70
|
+
container_layout.addWidget(self.skip_loading_checkbox, alignment=Qt.AlignCenter)
|
|
71
|
+
|
|
72
|
+
layout.addWidget(container)
|
|
73
|
+
self.setLayout(layout)
|
|
74
|
+
self.resize(400, 400)
|
|
75
|
+
|
|
76
|
+
# Center on screen
|
|
77
|
+
self._center_on_screen()
|
|
78
|
+
|
|
79
|
+
def _center_on_screen(self):
|
|
80
|
+
"""Center the loading screen on the primary screen."""
|
|
81
|
+
from PySide6.QtWidgets import QApplication
|
|
82
|
+
|
|
83
|
+
screen = QApplication.primaryScreen().geometry()
|
|
84
|
+
size = self.geometry()
|
|
85
|
+
self.move((screen.width() - size.width()) // 2, (screen.height() - size.height()) // 2)
|
|
86
|
+
|
|
87
|
+
def set_loading_text(self, text):
|
|
88
|
+
self.loading_label.setText(text)
|
|
89
|
+
|
|
90
|
+
def _on_skip_changed(self, state):
|
|
91
|
+
"""Save the skip loading screen preference."""
|
|
92
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
93
|
+
|
|
94
|
+
settings = SettingsService()
|
|
95
|
+
settings.set("hide_loading_screen", state == Qt.CheckState.Checked.value)
|
|
96
|
+
|
|
97
|
+
def fade_out(self, duration=500):
|
|
98
|
+
"""Fade out the loading screen and close after animation."""
|
|
99
|
+
animation = QPropertyAnimation(self, b"windowOpacity")
|
|
100
|
+
animation.setDuration(duration)
|
|
101
|
+
animation.setStartValue(1.0)
|
|
102
|
+
animation.setEndValue(0.0)
|
|
103
|
+
animation.finished.connect(self.close)
|
|
104
|
+
animation.start()
|
|
105
|
+
self._fade_animation = animation # Prevent garbage collection
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def show_loading_screen(app_name, version, tagline, loading_text="Initializing providers…", logo_path=None):
|
|
109
|
+
"""Show the loading screen if not disabled in settings.
|
|
110
|
+
|
|
111
|
+
This is a convenience function that handles checking settings, finding the logo,
|
|
112
|
+
creating the loading screen, and showing it. Both vector-inspector and vector-studio
|
|
113
|
+
should use this function to avoid code duplication.
|
|
114
|
+
|
|
115
|
+
Args:
|
|
116
|
+
app_name: Name of the application (e.g., "Vector Inspector", "Vector Studio")
|
|
117
|
+
version: Version string (e.g., "v0.3.11")
|
|
118
|
+
tagline: Tagline to display under app name
|
|
119
|
+
loading_text: Initial loading message
|
|
120
|
+
logo_path: Optional explicit path to logo. If None, will look for vector-inspector's logo.
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
LoadingScreen instance if shown, None otherwise.
|
|
124
|
+
"""
|
|
125
|
+
from vector_inspector.services.settings_service import SettingsService
|
|
126
|
+
|
|
127
|
+
# Check if user wants to skip loading screen
|
|
128
|
+
settings = SettingsService()
|
|
129
|
+
show_loading = not settings.get("hide_loading_screen", False)
|
|
130
|
+
|
|
131
|
+
if not show_loading:
|
|
132
|
+
return None
|
|
133
|
+
|
|
134
|
+
# Find logo path if not provided
|
|
135
|
+
if logo_path is None:
|
|
136
|
+
# Default to vector-inspector's logo
|
|
137
|
+
module_dir = os.path.dirname(os.path.abspath(__file__))
|
|
138
|
+
# Navigate from ui/loading_screen.py to vector_inspector/assets/logo.png
|
|
139
|
+
logo_path = os.path.join(os.path.dirname(module_dir), "assets", "logo.png")
|
|
140
|
+
|
|
141
|
+
# Create and show loading screen
|
|
142
|
+
loading = LoadingScreen(
|
|
143
|
+
logo_path=logo_path,
|
|
144
|
+
version=version,
|
|
145
|
+
app_name=app_name,
|
|
146
|
+
tagline=tagline,
|
|
147
|
+
loading_text=loading_text,
|
|
148
|
+
)
|
|
149
|
+
loading.show()
|
|
150
|
+
|
|
151
|
+
# Force the loading screen to render
|
|
152
|
+
from PySide6.QtWidgets import QApplication
|
|
153
|
+
QApplication.instance().processEvents()
|
|
154
|
+
|
|
155
|
+
return loading
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
# Example usage (to be called from main.py):
|
|
159
|
+
# from vector_inspector.ui.loading_screen import show_loading_screen
|
|
160
|
+
#
|
|
161
|
+
# loading = show_loading_screen(
|
|
162
|
+
# app_name="Vector Inspector",
|
|
163
|
+
# version="v0.3.11",
|
|
164
|
+
# tagline="The missing toolset for your vector data"
|
|
165
|
+
# )
|
|
166
|
+
# if loading:
|
|
167
|
+
# loading.set_loading_text("Loading main window...")
|
|
168
|
+
# # ... do initialization ...
|
|
169
|
+
# loading.fade_out()
|
|
@@ -1,26 +1,28 @@
|
|
|
1
1
|
"""Updated main window with multi-database support."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
5
|
+
from PySide6.QtCore import QByteArray, Qt, QTimer
|
|
6
|
+
from PySide6.QtGui import QAction
|
|
3
7
|
from PySide6.QtWidgets import (
|
|
4
|
-
QMessageBox,
|
|
5
|
-
QLabel,
|
|
6
8
|
QApplication,
|
|
7
9
|
QDialog,
|
|
8
|
-
|
|
10
|
+
QLabel,
|
|
11
|
+
QMessageBox,
|
|
9
12
|
QStatusBar,
|
|
13
|
+
QToolBar,
|
|
10
14
|
)
|
|
11
|
-
from PySide6.QtCore import Qt, QTimer, QByteArray
|
|
12
|
-
from PySide6.QtGui import QAction
|
|
13
15
|
|
|
14
|
-
from vector_inspector.core.connection_manager import ConnectionManager
|
|
15
|
-
from vector_inspector.core.
|
|
16
|
+
from vector_inspector.core.connection_manager import ConnectionInstance, ConnectionManager
|
|
17
|
+
from vector_inspector.core.logging import log_error
|
|
16
18
|
from vector_inspector.services.profile_service import ProfileService
|
|
17
19
|
from vector_inspector.services.settings_service import SettingsService
|
|
18
|
-
from vector_inspector.ui.main_window_shell import InspectorShell
|
|
19
20
|
from vector_inspector.ui.components.connection_manager_panel import ConnectionManagerPanel
|
|
20
21
|
from vector_inspector.ui.components.profile_manager_panel import ProfileManagerPanel
|
|
21
|
-
from vector_inspector.ui.tabs import InspectorTabs
|
|
22
22
|
from vector_inspector.ui.controllers.connection_controller import ConnectionController
|
|
23
|
+
from vector_inspector.ui.main_window_shell import InspectorShell
|
|
23
24
|
from vector_inspector.ui.services.dialog_service import DialogService
|
|
25
|
+
from vector_inspector.ui.tabs import InspectorTabs
|
|
24
26
|
|
|
25
27
|
|
|
26
28
|
class MainWindow(InspectorShell):
|
|
@@ -199,9 +201,10 @@ class MainWindow(InspectorShell):
|
|
|
199
201
|
help_menu.addAction(check_update_action)
|
|
200
202
|
|
|
201
203
|
def _check_for_update_from_menu(self):
|
|
204
|
+
from PySide6.QtWidgets import QMessageBox
|
|
205
|
+
|
|
202
206
|
from vector_inspector.services.update_service import UpdateService
|
|
203
207
|
from vector_inspector.utils.version import get_app_version
|
|
204
|
-
from PySide6.QtWidgets import QMessageBox
|
|
205
208
|
|
|
206
209
|
latest = UpdateService.get_latest_release(force_refresh=True)
|
|
207
210
|
if latest:
|
|
@@ -255,12 +258,13 @@ class MainWindow(InspectorShell):
|
|
|
255
258
|
self.update_indicator.mousePressEvent = self._on_update_indicator_clicked
|
|
256
259
|
|
|
257
260
|
# Check for updates on launch
|
|
258
|
-
from vector_inspector.services.update_service import UpdateService
|
|
259
|
-
from vector_inspector.utils.version import get_app_version
|
|
260
261
|
import threading
|
|
261
262
|
|
|
262
263
|
from PySide6.QtCore import QTimer
|
|
263
264
|
|
|
265
|
+
from vector_inspector.services.update_service import UpdateService
|
|
266
|
+
from vector_inspector.utils.version import get_app_version
|
|
267
|
+
|
|
264
268
|
def check_updates():
|
|
265
269
|
latest = UpdateService.get_latest_release()
|
|
266
270
|
if latest:
|
|
@@ -339,6 +343,28 @@ class MainWindow(InspectorShell):
|
|
|
339
343
|
# Show update details dialog
|
|
340
344
|
if not hasattr(self, "_latest_release"):
|
|
341
345
|
return
|
|
346
|
+
|
|
347
|
+
# Track that user clicked on update available
|
|
348
|
+
try:
|
|
349
|
+
from vector_inspector.services.telemetry_service import TelemetryService
|
|
350
|
+
from vector_inspector.utils.version import get_app_version
|
|
351
|
+
|
|
352
|
+
telemetry = TelemetryService(self.settings_service)
|
|
353
|
+
if telemetry.is_enabled():
|
|
354
|
+
latest_version = self._latest_release.get("tag_name", "unknown")
|
|
355
|
+
event_data = {
|
|
356
|
+
"hwid": telemetry.get_hwid(),
|
|
357
|
+
"event_name": "update_clicked",
|
|
358
|
+
"app_version": get_app_version(),
|
|
359
|
+
"client_type": "vector-inspector",
|
|
360
|
+
"metadata": {"latest_version": latest_version},
|
|
361
|
+
}
|
|
362
|
+
telemetry.queue_event(event_data)
|
|
363
|
+
telemetry.send_batch()
|
|
364
|
+
except Exception as e:
|
|
365
|
+
# Don't let telemetry errors break the update flow
|
|
366
|
+
log_error(f"Telemetry error: {e}")
|
|
367
|
+
|
|
342
368
|
DialogService.show_update_details(self._latest_release, self)
|
|
343
369
|
|
|
344
370
|
def _connect_signals(self):
|
|
@@ -382,9 +408,8 @@ class MainWindow(InspectorShell):
|
|
|
382
408
|
|
|
383
409
|
# Get active connection
|
|
384
410
|
active = self.connection_manager.get_active_connection()
|
|
385
|
-
conn = active.connection if active else None
|
|
386
411
|
|
|
387
|
-
self.visualization_view = VisualizationView(
|
|
412
|
+
self.visualization_view = VisualizationView(active)
|
|
388
413
|
# Replace placeholder with actual view
|
|
389
414
|
self.remove_main_tab(InspectorTabs.VISUALIZATION_TAB)
|
|
390
415
|
self.add_main_tab(
|
|
@@ -405,7 +430,7 @@ class MainWindow(InspectorShell):
|
|
|
405
430
|
self.breadcrumb_label.setText(instance.get_breadcrumb())
|
|
406
431
|
|
|
407
432
|
# Update all views with new connection
|
|
408
|
-
self._update_views_with_connection(instance
|
|
433
|
+
self._update_views_with_connection(instance)
|
|
409
434
|
|
|
410
435
|
# If there's an active collection, update views with it
|
|
411
436
|
if instance.active_collection:
|
|
@@ -455,7 +480,7 @@ class MainWindow(InspectorShell):
|
|
|
455
480
|
# If this is the active connection, refresh the info panel
|
|
456
481
|
if connection_id == self.connection_manager.get_active_connection_id():
|
|
457
482
|
instance = self.connection_manager.get_connection(connection_id)
|
|
458
|
-
if instance and instance.
|
|
483
|
+
if instance and instance.is_connected:
|
|
459
484
|
self.info_panel.refresh_database_info()
|
|
460
485
|
|
|
461
486
|
def _on_collection_selected_from_panel(self, connection_id: str, collection_name: str):
|
|
@@ -473,7 +498,7 @@ class MainWindow(InspectorShell):
|
|
|
473
498
|
finally:
|
|
474
499
|
self.connection_controller.loading_dialog.hide_loading()
|
|
475
500
|
|
|
476
|
-
def _update_views_with_connection(self, connection:
|
|
501
|
+
def _update_views_with_connection(self, connection: Optional[ConnectionInstance]):
|
|
477
502
|
"""Update all views with a new connection."""
|
|
478
503
|
# Clear current collection when switching connections
|
|
479
504
|
self.info_panel.current_collection = None
|
|
@@ -528,12 +553,12 @@ class MainWindow(InspectorShell):
|
|
|
528
553
|
def _refresh_active_connection(self):
|
|
529
554
|
"""Refresh collections for the active connection."""
|
|
530
555
|
active = self.connection_manager.get_active_connection()
|
|
531
|
-
if not active or not active.
|
|
556
|
+
if not active or not active.is_connected:
|
|
532
557
|
QMessageBox.information(self, "No Connection", "No active connection to refresh.")
|
|
533
558
|
return
|
|
534
559
|
|
|
535
560
|
try:
|
|
536
|
-
collections = active.
|
|
561
|
+
collections = active.list_collections()
|
|
537
562
|
self.connection_manager.update_collections(active.id, collections)
|
|
538
563
|
self.statusBar().showMessage(f"Refreshed collections ({len(collections)} found)", 3000)
|
|
539
564
|
|
|
@@ -55,6 +55,7 @@ class DialogService:
|
|
|
55
55
|
|
|
56
56
|
from vector_inspector.ui.components.backup_restore_dialog import BackupRestoreDialog
|
|
57
57
|
|
|
58
|
+
# Pass through the connection object (ConnectionInstance expected by UI)
|
|
58
59
|
dialog = BackupRestoreDialog(connection, collection_name or "", parent)
|
|
59
60
|
return dialog.exec()
|
|
60
61
|
|
|
@@ -1,84 +1,88 @@
|
|
|
1
1
|
"""Collection browser for listing and selecting collections."""
|
|
2
2
|
|
|
3
|
+
from PySide6.QtCore import Qt, Signal
|
|
4
|
+
from PySide6.QtGui import QAction
|
|
3
5
|
from PySide6.QtWidgets import (
|
|
4
|
-
|
|
5
|
-
|
|
6
|
+
QGroupBox,
|
|
7
|
+
QLabel,
|
|
8
|
+
QListWidget,
|
|
9
|
+
QListWidgetItem,
|
|
10
|
+
QMenu,
|
|
11
|
+
QVBoxLayout,
|
|
12
|
+
QWidget,
|
|
6
13
|
)
|
|
7
|
-
from PySide6.QtCore import Signal, Qt
|
|
8
|
-
from PySide6.QtGui import QAction
|
|
9
14
|
|
|
10
|
-
from vector_inspector.core.
|
|
15
|
+
from vector_inspector.core.connection_manager import ConnectionInstance
|
|
11
16
|
|
|
12
17
|
|
|
13
18
|
class CollectionBrowser(QWidget):
|
|
14
19
|
"""Widget for browsing and selecting collections."""
|
|
15
|
-
|
|
20
|
+
|
|
16
21
|
collection_selected = Signal(str)
|
|
17
|
-
|
|
18
|
-
def __init__(self, connection:
|
|
22
|
+
|
|
23
|
+
def __init__(self, connection: Optional[ConnectionInstance] = None, parent=None):
|
|
19
24
|
super().__init__(parent)
|
|
25
|
+
# Expects a ConnectionInstance wrapper.
|
|
20
26
|
self.connection = connection
|
|
21
27
|
self._setup_ui()
|
|
22
|
-
|
|
28
|
+
|
|
23
29
|
def _setup_ui(self):
|
|
24
30
|
"""Setup widget UI."""
|
|
25
31
|
layout = QVBoxLayout(self)
|
|
26
32
|
layout.setContentsMargins(0, 0, 0, 0)
|
|
27
|
-
|
|
33
|
+
|
|
28
34
|
group = QGroupBox("Collections")
|
|
29
35
|
group_layout = QVBoxLayout()
|
|
30
|
-
|
|
36
|
+
|
|
31
37
|
self.collection_list = QListWidget()
|
|
32
38
|
self.collection_list.itemClicked.connect(self._on_collection_clicked)
|
|
33
39
|
self.collection_list.setContextMenuPolicy(Qt.CustomContextMenu)
|
|
34
40
|
self.collection_list.customContextMenuRequested.connect(self._show_context_menu)
|
|
35
|
-
|
|
41
|
+
|
|
36
42
|
group_layout.addWidget(self.collection_list)
|
|
37
|
-
|
|
43
|
+
|
|
38
44
|
self.info_label = QLabel("No collections")
|
|
39
45
|
self.info_label.setWordWrap(True)
|
|
40
46
|
self.info_label.setStyleSheet("color: gray; font-size: 10px;")
|
|
41
47
|
group_layout.addWidget(self.info_label)
|
|
42
|
-
|
|
48
|
+
|
|
43
49
|
group.setLayout(group_layout)
|
|
44
50
|
layout.addWidget(group)
|
|
45
|
-
|
|
51
|
+
|
|
46
52
|
def refresh(self):
|
|
47
53
|
"""Refresh collection list."""
|
|
48
54
|
self.collection_list.clear()
|
|
49
|
-
|
|
55
|
+
|
|
50
56
|
if not self.connection.is_connected:
|
|
51
57
|
self.info_label.setText("Not connected")
|
|
52
58
|
return
|
|
53
|
-
|
|
59
|
+
|
|
54
60
|
collections = self.connection.list_collections()
|
|
55
|
-
|
|
61
|
+
|
|
56
62
|
if not collections:
|
|
57
63
|
# Show more context for persistent connections
|
|
58
64
|
if self.connection.path:
|
|
59
|
-
self.info_label.setText(
|
|
60
|
-
f"No collections found at {self.connection.path}"
|
|
61
|
-
)
|
|
65
|
+
self.info_label.setText(f"No collections found at {self.connection.path}")
|
|
62
66
|
else:
|
|
63
67
|
self.info_label.setText("No collections found")
|
|
64
68
|
return
|
|
65
|
-
|
|
69
|
+
|
|
66
70
|
for collection_name in collections:
|
|
67
71
|
item = QListWidgetItem(collection_name)
|
|
68
72
|
self.collection_list.addItem(item)
|
|
69
|
-
|
|
73
|
+
|
|
70
74
|
self.info_label.setText(f"{len(collections)} collection(s)")
|
|
71
|
-
|
|
75
|
+
|
|
72
76
|
def clear(self):
|
|
73
77
|
"""Clear collection list."""
|
|
74
78
|
self.collection_list.clear()
|
|
75
79
|
self.info_label.setText("No collections")
|
|
76
|
-
|
|
80
|
+
|
|
77
81
|
def _on_collection_clicked(self, item: QListWidgetItem):
|
|
78
82
|
"""Handle collection selection."""
|
|
79
83
|
collection_name = item.text()
|
|
80
84
|
self.collection_selected.emit(collection_name)
|
|
81
|
-
|
|
85
|
+
|
|
82
86
|
# Show collection info
|
|
83
87
|
info = self.connection.get_collection_info(collection_name)
|
|
84
88
|
if info:
|
|
@@ -87,24 +91,22 @@ class CollectionBrowser(QWidget):
|
|
|
87
91
|
fields_str = ", ".join(fields[:3])
|
|
88
92
|
if len(fields) > 3:
|
|
89
93
|
fields_str += "..."
|
|
90
|
-
self.info_label.setText(
|
|
91
|
-
|
|
92
|
-
)
|
|
93
|
-
|
|
94
|
+
self.info_label.setText(f"{count} items | Fields: {fields_str if fields else 'None'}")
|
|
95
|
+
|
|
94
96
|
def _show_context_menu(self, position):
|
|
95
97
|
"""Show context menu for collections."""
|
|
96
98
|
item = self.collection_list.itemAt(position)
|
|
97
99
|
if not item:
|
|
98
100
|
return
|
|
99
|
-
|
|
101
|
+
|
|
100
102
|
menu = QMenu(self)
|
|
101
|
-
|
|
103
|
+
|
|
102
104
|
delete_action = QAction("Delete Collection", self)
|
|
103
105
|
delete_action.triggered.connect(lambda: self._delete_collection(item.text()))
|
|
104
106
|
menu.addAction(delete_action)
|
|
105
|
-
|
|
107
|
+
|
|
106
108
|
menu.exec(self.collection_list.mapToGlobal(position))
|
|
107
|
-
|
|
109
|
+
|
|
108
110
|
def _delete_collection(self, collection_name: str):
|
|
109
111
|
"""Delete a collection."""
|
|
110
112
|
# TODO: Add confirmation dialog
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
"""Connection configuration view."""
|
|
2
2
|
|
|
3
|
+
from typing import Optional
|
|
4
|
+
|
|
3
5
|
from PySide6.QtWidgets import (
|
|
4
6
|
QWidget,
|
|
5
7
|
QVBoxLayout,
|
|
@@ -437,8 +439,12 @@ class ConnectionView(QWidget):
|
|
|
437
439
|
connection_changed = Signal(bool)
|
|
438
440
|
connection_created = Signal(VectorDBConnection) # Signal when new connection is created
|
|
439
441
|
|
|
440
|
-
def __init__(self, connection: VectorDBConnection, parent=None):
|
|
442
|
+
def __init__(self, connection: Optional[VectorDBConnection] = None, parent=None):
|
|
441
443
|
super().__init__(parent)
|
|
444
|
+
# This view may be constructed without an active connection; it manages
|
|
445
|
+
# creation of `VectorDBConnection` instances. Keep `self.connection` as
|
|
446
|
+
# the low-level `VectorDBConnection` when present.
|
|
447
|
+
self._raw_connection = None
|
|
442
448
|
self.connection = connection
|
|
443
449
|
self.loading_dialog = LoadingDialog("Connecting to database...", self)
|
|
444
450
|
self.settings_service = SettingsService()
|