vector-inspector 0.3.1__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.
- vector_inspector/core/connection_manager.py +55 -49
- vector_inspector/core/connections/base_connection.py +41 -41
- vector_inspector/core/connections/chroma_connection.py +110 -86
- vector_inspector/core/connections/pinecone_connection.py +168 -182
- vector_inspector/core/connections/qdrant_connection.py +109 -126
- vector_inspector/core/connections/qdrant_helpers/__init__.py +4 -0
- vector_inspector/core/connections/qdrant_helpers/qdrant_embedding_resolver.py +35 -0
- vector_inspector/core/connections/qdrant_helpers/qdrant_filter_builder.py +51 -0
- vector_inspector/core/connections/template_connection.py +55 -65
- vector_inspector/core/embedding_utils.py +32 -32
- vector_inspector/core/logging.py +27 -0
- vector_inspector/core/model_registry.py +4 -3
- vector_inspector/main.py +6 -2
- vector_inspector/services/backup_helpers.py +63 -0
- vector_inspector/services/backup_restore_service.py +73 -152
- vector_inspector/services/credential_service.py +33 -40
- vector_inspector/services/import_export_service.py +70 -67
- vector_inspector/services/profile_service.py +92 -94
- vector_inspector/services/settings_service.py +68 -48
- vector_inspector/services/visualization_service.py +40 -39
- vector_inspector/ui/components/splash_window.py +57 -0
- vector_inspector/ui/dialogs/cross_db_migration.py +6 -5
- vector_inspector/ui/main_window.py +200 -146
- vector_inspector/ui/views/info_panel.py +208 -127
- vector_inspector/ui/views/metadata_view.py +8 -7
- vector_inspector/ui/views/search_view.py +97 -75
- vector_inspector/ui/views/visualization_view.py +140 -97
- vector_inspector/utils/version.py +5 -0
- {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/METADATA +9 -2
- {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/RECORD +32 -25
- {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/WHEEL +0 -0
- {vector_inspector-0.3.1.dist-info → vector_inspector-0.3.3.dist-info}/entry_points.txt +0 -0
|
@@ -4,67 +4,69 @@ import json
|
|
|
4
4
|
from pathlib import Path
|
|
5
5
|
from typing import Dict, Any, Optional, List
|
|
6
6
|
from vector_inspector.core.cache_manager import invalidate_cache_on_settings_change
|
|
7
|
+
from vector_inspector.core.logging import log_error
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
class SettingsService:
|
|
10
11
|
"""Handles loading and saving application settings."""
|
|
11
|
-
|
|
12
|
+
|
|
12
13
|
def __init__(self):
|
|
13
14
|
"""Initialize settings service."""
|
|
14
15
|
self.settings_dir = Path.home() / ".vector-inspector"
|
|
15
16
|
self.settings_file = self.settings_dir / "settings.json"
|
|
16
17
|
self.settings: Dict[str, Any] = {}
|
|
17
18
|
self._load_settings()
|
|
18
|
-
|
|
19
|
+
|
|
19
20
|
def _load_settings(self):
|
|
20
21
|
"""Load settings from file."""
|
|
21
22
|
try:
|
|
22
23
|
if self.settings_file.exists():
|
|
23
|
-
with open(self.settings_file,
|
|
24
|
+
with open(self.settings_file, "r", encoding="utf-8") as f:
|
|
24
25
|
self.settings = json.load(f)
|
|
25
26
|
except Exception as e:
|
|
26
|
-
|
|
27
|
+
log_error("Failed to load settings: %s", e)
|
|
27
28
|
self.settings = {}
|
|
28
|
-
|
|
29
|
+
|
|
29
30
|
def _save_settings(self):
|
|
30
31
|
"""Save settings to file."""
|
|
31
32
|
try:
|
|
32
33
|
# Create settings directory if it doesn't exist
|
|
33
34
|
self.settings_dir.mkdir(parents=True, exist_ok=True)
|
|
34
|
-
|
|
35
|
-
with open(self.settings_file,
|
|
35
|
+
|
|
36
|
+
with open(self.settings_file, "w", encoding="utf-8") as f:
|
|
36
37
|
json.dump(self.settings, f, indent=2, ensure_ascii=False)
|
|
37
38
|
except Exception as e:
|
|
38
|
-
|
|
39
|
-
|
|
39
|
+
log_error("Failed to save settings: %s", e)
|
|
40
|
+
|
|
40
41
|
def get_last_connection(self) -> Optional[Dict[str, Any]]:
|
|
41
42
|
"""Get the last connection configuration."""
|
|
42
43
|
return self.settings.get("last_connection")
|
|
43
|
-
|
|
44
|
+
|
|
44
45
|
def save_last_connection(self, config: Dict[str, Any]):
|
|
45
46
|
"""Save the last connection configuration."""
|
|
46
47
|
self.settings["last_connection"] = config
|
|
47
48
|
self._save_settings()
|
|
48
|
-
|
|
49
|
+
|
|
49
50
|
def get(self, key: str, default: Any = None) -> Any:
|
|
50
51
|
"""Get a setting value."""
|
|
51
52
|
return self.settings.get(key, default)
|
|
52
|
-
|
|
53
|
+
|
|
53
54
|
def get_cache_enabled(self) -> bool:
|
|
54
55
|
"""Get whether caching is enabled (default: True)."""
|
|
55
56
|
return self.settings.get("cache_enabled", True)
|
|
56
|
-
|
|
57
|
+
|
|
57
58
|
def set_cache_enabled(self, enabled: bool):
|
|
58
59
|
"""Set whether caching is enabled."""
|
|
59
60
|
self.set("cache_enabled", enabled)
|
|
60
61
|
# Update cache manager state
|
|
61
62
|
from vector_inspector.core.cache_manager import get_cache_manager
|
|
63
|
+
|
|
62
64
|
cache = get_cache_manager()
|
|
63
65
|
if enabled:
|
|
64
66
|
cache.enable()
|
|
65
67
|
else:
|
|
66
68
|
cache.disable()
|
|
67
|
-
|
|
69
|
+
|
|
68
70
|
def set(self, key: str, value: Any):
|
|
69
71
|
"""Set a setting value."""
|
|
70
72
|
self.settings[key] = value
|
|
@@ -72,15 +74,21 @@ class SettingsService:
|
|
|
72
74
|
# Invalidate cache when settings change (only if cache is enabled)
|
|
73
75
|
if key != "cache_enabled": # Don't invalidate when toggling cache itself
|
|
74
76
|
invalidate_cache_on_settings_change()
|
|
75
|
-
|
|
77
|
+
|
|
76
78
|
def clear(self):
|
|
77
79
|
"""Clear all settings."""
|
|
78
80
|
self.settings = {}
|
|
79
81
|
self._save_settings()
|
|
80
|
-
|
|
81
|
-
def save_embedding_model(
|
|
82
|
+
|
|
83
|
+
def save_embedding_model(
|
|
84
|
+
self,
|
|
85
|
+
connection_id: str,
|
|
86
|
+
collection_name: str,
|
|
87
|
+
model_name: str,
|
|
88
|
+
model_type: str = "user-configured",
|
|
89
|
+
):
|
|
82
90
|
"""Save embedding model mapping for a collection.
|
|
83
|
-
|
|
91
|
+
|
|
84
92
|
Args:
|
|
85
93
|
connection_id: Connection identifier
|
|
86
94
|
collection_name: Collection name
|
|
@@ -89,51 +97,60 @@ class SettingsService:
|
|
|
89
97
|
"""
|
|
90
98
|
if "collection_embedding_models" not in self.settings:
|
|
91
99
|
self.settings["collection_embedding_models"] = {}
|
|
92
|
-
|
|
100
|
+
|
|
93
101
|
collection_key = f"{connection_id}:{collection_name}"
|
|
94
102
|
self.settings["collection_embedding_models"][collection_key] = {
|
|
95
103
|
"model": model_name,
|
|
96
104
|
"type": model_type,
|
|
97
|
-
"timestamp": self._get_timestamp()
|
|
105
|
+
"timestamp": self._get_timestamp(),
|
|
98
106
|
}
|
|
99
107
|
self._save_settings()
|
|
100
|
-
|
|
101
|
-
def get_embedding_model(
|
|
108
|
+
|
|
109
|
+
def get_embedding_model(
|
|
110
|
+
self, connection_id: str, collection_name: str
|
|
111
|
+
) -> Optional[Dict[str, Any]]:
|
|
102
112
|
"""Get embedding model mapping for a collection.
|
|
103
|
-
|
|
113
|
+
|
|
104
114
|
Args:
|
|
105
115
|
connection_id: Connection identifier
|
|
106
116
|
collection_name: Collection name
|
|
107
|
-
|
|
117
|
+
|
|
108
118
|
Returns:
|
|
109
119
|
Dictionary with 'model', 'type', and 'timestamp' or None
|
|
110
120
|
"""
|
|
111
121
|
collection_models = self.settings.get("collection_embedding_models", {})
|
|
112
122
|
collection_key = f"{connection_id}:{collection_name}"
|
|
113
123
|
return collection_models.get(collection_key)
|
|
114
|
-
|
|
124
|
+
|
|
115
125
|
def remove_embedding_model(self, connection_id: str, collection_name: str):
|
|
116
126
|
"""Remove embedding model mapping for a collection.
|
|
117
|
-
|
|
127
|
+
|
|
118
128
|
Args:
|
|
119
129
|
connection_id: Connection identifier
|
|
120
130
|
collection_name: Collection name
|
|
121
131
|
"""
|
|
122
132
|
if "collection_embedding_models" not in self.settings:
|
|
123
133
|
return
|
|
124
|
-
|
|
134
|
+
|
|
125
135
|
collection_key = f"{connection_id}:{collection_name}"
|
|
126
136
|
self.settings["collection_embedding_models"].pop(collection_key, None)
|
|
127
137
|
self._save_settings()
|
|
128
|
-
|
|
138
|
+
|
|
129
139
|
def _get_timestamp(self) -> str:
|
|
130
140
|
"""Get current timestamp as ISO string."""
|
|
131
141
|
from datetime import datetime
|
|
142
|
+
|
|
132
143
|
return datetime.now().isoformat()
|
|
133
|
-
|
|
134
|
-
def add_custom_embedding_model(
|
|
144
|
+
|
|
145
|
+
def add_custom_embedding_model(
|
|
146
|
+
self,
|
|
147
|
+
model_name: str,
|
|
148
|
+
dimension: int,
|
|
149
|
+
model_type: str = "sentence-transformer",
|
|
150
|
+
description: str = "Custom model",
|
|
151
|
+
):
|
|
135
152
|
"""Add a custom embedding model to the known models list.
|
|
136
|
-
|
|
153
|
+
|
|
137
154
|
Args:
|
|
138
155
|
model_name: Name of the embedding model
|
|
139
156
|
dimension: Vector dimension
|
|
@@ -142,7 +159,7 @@ class SettingsService:
|
|
|
142
159
|
"""
|
|
143
160
|
if "custom_embedding_models" not in self.settings:
|
|
144
161
|
self.settings["custom_embedding_models"] = []
|
|
145
|
-
|
|
162
|
+
|
|
146
163
|
# Check if already exists
|
|
147
164
|
for model in self.settings["custom_embedding_models"]:
|
|
148
165
|
if model["name"] == model_name and model["dimension"] == dimension:
|
|
@@ -152,24 +169,26 @@ class SettingsService:
|
|
|
152
169
|
model["last_used"] = self._get_timestamp()
|
|
153
170
|
self._save_settings()
|
|
154
171
|
return
|
|
155
|
-
|
|
172
|
+
|
|
156
173
|
# Add new entry
|
|
157
|
-
self.settings["custom_embedding_models"].append(
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
174
|
+
self.settings["custom_embedding_models"].append(
|
|
175
|
+
{
|
|
176
|
+
"name": model_name,
|
|
177
|
+
"dimension": dimension,
|
|
178
|
+
"type": model_type,
|
|
179
|
+
"description": description,
|
|
180
|
+
"added": self._get_timestamp(),
|
|
181
|
+
"last_used": self._get_timestamp(),
|
|
182
|
+
}
|
|
183
|
+
)
|
|
165
184
|
self._save_settings()
|
|
166
|
-
|
|
185
|
+
|
|
167
186
|
def get_custom_embedding_models(self, dimension: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
168
187
|
"""Get list of custom embedding models.
|
|
169
|
-
|
|
188
|
+
|
|
170
189
|
Args:
|
|
171
190
|
dimension: Optional filter by dimension
|
|
172
|
-
|
|
191
|
+
|
|
173
192
|
Returns:
|
|
174
193
|
List of custom model dictionaries
|
|
175
194
|
"""
|
|
@@ -177,19 +196,20 @@ class SettingsService:
|
|
|
177
196
|
if dimension is not None:
|
|
178
197
|
return [m for m in models if m["dimension"] == dimension]
|
|
179
198
|
return models
|
|
180
|
-
|
|
199
|
+
|
|
181
200
|
def remove_custom_embedding_model(self, model_name: str, dimension: int):
|
|
182
201
|
"""Remove a custom embedding model.
|
|
183
|
-
|
|
202
|
+
|
|
184
203
|
Args:
|
|
185
204
|
model_name: Name of the model to remove
|
|
186
205
|
dimension: Vector dimension
|
|
187
206
|
"""
|
|
188
207
|
if "custom_embedding_models" not in self.settings:
|
|
189
208
|
return
|
|
190
|
-
|
|
209
|
+
|
|
191
210
|
self.settings["custom_embedding_models"] = [
|
|
192
|
-
m
|
|
211
|
+
m
|
|
212
|
+
for m in self.settings["custom_embedding_models"]
|
|
193
213
|
if not (m["name"] == model_name and m["dimension"] == dimension)
|
|
194
214
|
]
|
|
195
215
|
self._save_settings()
|
|
@@ -2,103 +2,96 @@
|
|
|
2
2
|
|
|
3
3
|
from typing import Optional, List, Tuple, Any
|
|
4
4
|
import warnings
|
|
5
|
+
from vector_inspector.core.logging import log_error
|
|
5
6
|
|
|
6
7
|
|
|
7
8
|
class VisualizationService:
|
|
8
9
|
"""Service for vector dimensionality reduction and visualization."""
|
|
9
|
-
|
|
10
|
+
|
|
10
11
|
@staticmethod
|
|
11
12
|
def reduce_dimensions(
|
|
12
|
-
embeddings: List[List[float]],
|
|
13
|
-
method: str = "pca",
|
|
14
|
-
n_components: int = 2,
|
|
15
|
-
**kwargs
|
|
13
|
+
embeddings: List[List[float]], method: str = "pca", n_components: int = 2, **kwargs
|
|
16
14
|
) -> Optional[Any]:
|
|
17
15
|
"""
|
|
18
16
|
Reduce dimensionality of embeddings.
|
|
19
|
-
|
|
17
|
+
|
|
20
18
|
Args:
|
|
21
19
|
embeddings: List of embedding vectors
|
|
22
20
|
method: Reduction method ('pca', 'tsne', or 'umap')
|
|
23
21
|
n_components: Target number of dimensions (2 or 3)
|
|
24
22
|
**kwargs: Additional method-specific parameters
|
|
25
|
-
|
|
23
|
+
|
|
26
24
|
Returns:
|
|
27
25
|
Reduced embeddings as numpy array, or None if failed
|
|
28
26
|
"""
|
|
29
27
|
if embeddings is None or len(embeddings) == 0:
|
|
30
28
|
return None
|
|
31
|
-
|
|
29
|
+
|
|
32
30
|
try:
|
|
33
31
|
# Lazy import numpy and models
|
|
34
32
|
from vector_inspector.utils.lazy_imports import get_numpy, get_sklearn_model
|
|
33
|
+
|
|
35
34
|
np = get_numpy()
|
|
36
|
-
|
|
35
|
+
|
|
37
36
|
X = np.array(embeddings)
|
|
38
|
-
|
|
37
|
+
|
|
39
38
|
if method.lower() == "pca":
|
|
40
|
-
PCA = get_sklearn_model(
|
|
39
|
+
PCA = get_sklearn_model("PCA")
|
|
41
40
|
reducer = PCA(n_components=n_components)
|
|
42
41
|
reduced = reducer.fit_transform(X)
|
|
43
|
-
|
|
42
|
+
|
|
44
43
|
elif method.lower() in ["t-sne", "tsne"]:
|
|
45
|
-
TSNE = get_sklearn_model(
|
|
44
|
+
TSNE = get_sklearn_model("TSNE")
|
|
46
45
|
perplexity = kwargs.get("perplexity", min(30, len(embeddings) - 1))
|
|
47
|
-
reducer = TSNE(
|
|
48
|
-
n_components=n_components,
|
|
49
|
-
perplexity=perplexity,
|
|
50
|
-
random_state=42
|
|
51
|
-
)
|
|
46
|
+
reducer = TSNE(n_components=n_components, perplexity=perplexity, random_state=42)
|
|
52
47
|
reduced = reducer.fit_transform(X)
|
|
53
|
-
|
|
48
|
+
|
|
54
49
|
elif method.lower() == "umap":
|
|
55
|
-
UMAP = get_sklearn_model(
|
|
50
|
+
UMAP = get_sklearn_model("UMAP")
|
|
56
51
|
n_neighbors = kwargs.get("n_neighbors", min(15, len(embeddings) - 1))
|
|
57
52
|
# Suppress n_jobs warning when using random_state
|
|
58
53
|
with warnings.catch_warnings():
|
|
59
54
|
warnings.filterwarnings("ignore", message=".*n_jobs.*overridden.*")
|
|
60
55
|
reducer = UMAP(
|
|
61
|
-
n_components=n_components,
|
|
62
|
-
n_neighbors=n_neighbors,
|
|
63
|
-
random_state=42
|
|
56
|
+
n_components=n_components, n_neighbors=n_neighbors, random_state=42
|
|
64
57
|
)
|
|
65
58
|
reduced = reducer.fit_transform(X)
|
|
66
|
-
|
|
59
|
+
|
|
67
60
|
else:
|
|
68
|
-
|
|
61
|
+
log_error("Unknown method: %s", method)
|
|
69
62
|
return None
|
|
70
|
-
|
|
63
|
+
|
|
71
64
|
return reduced
|
|
72
|
-
|
|
65
|
+
|
|
73
66
|
except Exception as e:
|
|
74
|
-
|
|
67
|
+
log_error("Dimensionality reduction failed: %s", e)
|
|
75
68
|
return None
|
|
76
|
-
|
|
69
|
+
|
|
77
70
|
@staticmethod
|
|
78
71
|
def prepare_plot_data(
|
|
79
72
|
reduced_embeddings: Any,
|
|
80
73
|
labels: Optional[List[str]] = None,
|
|
81
74
|
metadata: Optional[List[dict]] = None,
|
|
82
|
-
color_by: Optional[str] = None
|
|
75
|
+
color_by: Optional[str] = None,
|
|
83
76
|
) -> Tuple[Any, List[str], List[str]]:
|
|
84
77
|
"""
|
|
85
78
|
Prepare data for plotting.
|
|
86
|
-
|
|
79
|
+
|
|
87
80
|
Args:
|
|
88
81
|
reduced_embeddings: Reduced dimension embeddings
|
|
89
82
|
labels: Text labels for each point
|
|
90
83
|
metadata: Metadata dictionaries for each point
|
|
91
84
|
color_by: Metadata field to use for coloring
|
|
92
|
-
|
|
85
|
+
|
|
93
86
|
Returns:
|
|
94
87
|
Tuple of (embeddings, labels, colors)
|
|
95
88
|
"""
|
|
96
89
|
n_points = len(reduced_embeddings)
|
|
97
|
-
|
|
90
|
+
|
|
98
91
|
# Prepare labels
|
|
99
92
|
if labels is None:
|
|
100
93
|
labels = [f"Point {i}" for i in range(n_points)]
|
|
101
|
-
|
|
94
|
+
|
|
102
95
|
# Prepare colors
|
|
103
96
|
colors = ["blue"] * n_points
|
|
104
97
|
if color_by and metadata:
|
|
@@ -108,16 +101,24 @@ class VisualizationService:
|
|
|
108
101
|
value = meta.get(color_by, "unknown")
|
|
109
102
|
values.append(str(value))
|
|
110
103
|
unique_values.add(str(value))
|
|
111
|
-
|
|
104
|
+
|
|
112
105
|
# Map values to colors
|
|
113
106
|
color_map = {}
|
|
114
107
|
color_palette = [
|
|
115
|
-
"red",
|
|
116
|
-
"
|
|
108
|
+
"red",
|
|
109
|
+
"blue",
|
|
110
|
+
"green",
|
|
111
|
+
"orange",
|
|
112
|
+
"purple",
|
|
113
|
+
"cyan",
|
|
114
|
+
"magenta",
|
|
115
|
+
"yellow",
|
|
116
|
+
"pink",
|
|
117
|
+
"brown",
|
|
117
118
|
]
|
|
118
119
|
for i, val in enumerate(sorted(unique_values)):
|
|
119
120
|
color_map[val] = color_palette[i % len(color_palette)]
|
|
120
|
-
|
|
121
|
+
|
|
121
122
|
colors = [color_map[val] for val in values]
|
|
122
|
-
|
|
123
|
+
|
|
123
124
|
return reduced_embeddings, labels, colors
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
from PySide6.QtWidgets import QDialog, QVBoxLayout, QLabel, QCheckBox, QPushButton, QTextBrowser
|
|
2
|
+
from PySide6.QtCore import Qt
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class SplashWindow(QDialog):
|
|
6
|
+
def __init__(self, parent=None):
|
|
7
|
+
super().__init__(parent)
|
|
8
|
+
self.setWindowTitle("Welcome to Vector Inspector!")
|
|
9
|
+
self.setModal(True)
|
|
10
|
+
self.setMinimumWidth(420)
|
|
11
|
+
|
|
12
|
+
layout = QVBoxLayout(self)
|
|
13
|
+
|
|
14
|
+
# Welcome message
|
|
15
|
+
label = QLabel("<h2>Thanks for trying <b>Vector Inspector</b>!</h2>")
|
|
16
|
+
label.setAlignment(Qt.AlignCenter)
|
|
17
|
+
layout.addWidget(label)
|
|
18
|
+
|
|
19
|
+
# Feedback prompt
|
|
20
|
+
feedback = QLabel(
|
|
21
|
+
"If you have thoughts, feature requests, or run into anything confusing,<br>"
|
|
22
|
+
"I’d really appreciate hearing from you. Feedback helps shape the roadmap."
|
|
23
|
+
)
|
|
24
|
+
feedback.setTextFormat(Qt.RichText)
|
|
25
|
+
feedback.setWordWrap(True)
|
|
26
|
+
layout.addWidget(feedback)
|
|
27
|
+
|
|
28
|
+
# GitHub link
|
|
29
|
+
github = QLabel(
|
|
30
|
+
'<a href="https://github.com/anthonypdawson/vector-inspector/issues">Submit feedback or issues on GitHub</a>'
|
|
31
|
+
)
|
|
32
|
+
github.setOpenExternalLinks(True)
|
|
33
|
+
github.setAlignment(Qt.AlignCenter)
|
|
34
|
+
layout.addWidget(github)
|
|
35
|
+
|
|
36
|
+
# About info (reuse About dialog text)
|
|
37
|
+
from vector_inspector.ui.main_window import get_about_html
|
|
38
|
+
|
|
39
|
+
about = QTextBrowser()
|
|
40
|
+
about.setHtml(get_about_html())
|
|
41
|
+
about.setOpenExternalLinks(True)
|
|
42
|
+
about.setMaximumHeight(160)
|
|
43
|
+
layout.addWidget(about)
|
|
44
|
+
|
|
45
|
+
# Do not show again checkbox
|
|
46
|
+
self.hide_checkbox = QCheckBox("Do not show this again")
|
|
47
|
+
layout.addWidget(self.hide_checkbox)
|
|
48
|
+
|
|
49
|
+
# OK button
|
|
50
|
+
ok_btn = QPushButton("OK")
|
|
51
|
+
ok_btn.clicked.connect(self.accept)
|
|
52
|
+
layout.addWidget(ok_btn)
|
|
53
|
+
|
|
54
|
+
layout.addStretch(1)
|
|
55
|
+
|
|
56
|
+
def should_hide(self):
|
|
57
|
+
return self.hide_checkbox.isChecked()
|
|
@@ -12,6 +12,7 @@ from PySide6.QtCore import QThread, Signal
|
|
|
12
12
|
|
|
13
13
|
from vector_inspector.core.connection_manager import ConnectionManager, ConnectionInstance
|
|
14
14
|
from vector_inspector.services.backup_restore_service import BackupRestoreService
|
|
15
|
+
from vector_inspector.core.logging import log_info, log_error
|
|
15
16
|
|
|
16
17
|
|
|
17
18
|
class MigrationThread(QThread):
|
|
@@ -111,26 +112,26 @@ class MigrationThread(QThread):
|
|
|
111
112
|
try:
|
|
112
113
|
if self.target_collection in self.target_conn.connection.list_collections():
|
|
113
114
|
self.progress.emit(90, "Cleaning up failed migration...")
|
|
114
|
-
|
|
115
|
+
log_info("Cleaning up failed migration: deleting target collection '%s'", self.target_collection)
|
|
115
116
|
self.target_conn.connection.delete_collection(self.target_collection)
|
|
116
117
|
except Exception as cleanup_error:
|
|
117
|
-
|
|
118
|
+
log_error("Warning: Failed to clean up target collection: %s", cleanup_error)
|
|
118
119
|
|
|
119
120
|
self.finished.emit(False, "Failed to restore to target collection. Target collection cleaned up.")
|
|
120
121
|
|
|
121
122
|
except Exception as e:
|
|
122
123
|
import traceback
|
|
123
124
|
error_details = traceback.format_exc()
|
|
124
|
-
|
|
125
|
+
log_error("Migration error details:\n%s", error_details)
|
|
125
126
|
|
|
126
127
|
# Clean up target collection on exception
|
|
127
128
|
try:
|
|
128
129
|
if self.target_conn and self.target_conn.connection.is_connected:
|
|
129
130
|
if self.target_collection in self.target_conn.connection.list_collections():
|
|
130
|
-
|
|
131
|
+
log_info("Cleaning up failed migration: deleting target collection '%s'", self.target_collection)
|
|
131
132
|
self.target_conn.connection.delete_collection(self.target_collection)
|
|
132
133
|
except Exception as cleanup_error:
|
|
133
|
-
|
|
134
|
+
log_error("Warning: Failed to clean up target collection: %s", cleanup_error)
|
|
134
135
|
|
|
135
136
|
self.finished.emit(False, f"Migration error: {str(e)}")
|
|
136
137
|
|