vector-inspector 0.2.6__py3-none-any.whl → 0.3.1__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/config/__init__.py +4 -0
- vector_inspector/config/known_embedding_models.json +432 -0
- vector_inspector/core/cache_manager.py +159 -0
- vector_inspector/core/connection_manager.py +277 -0
- vector_inspector/core/connections/__init__.py +2 -1
- vector_inspector/core/connections/base_connection.py +42 -1
- vector_inspector/core/connections/chroma_connection.py +137 -16
- vector_inspector/core/connections/pinecone_connection.py +768 -0
- vector_inspector/core/connections/qdrant_connection.py +62 -8
- vector_inspector/core/embedding_providers/__init__.py +14 -0
- vector_inspector/core/embedding_providers/base_provider.py +128 -0
- vector_inspector/core/embedding_providers/clip_provider.py +260 -0
- vector_inspector/core/embedding_providers/provider_factory.py +176 -0
- vector_inspector/core/embedding_providers/sentence_transformer_provider.py +203 -0
- vector_inspector/core/embedding_utils.py +167 -0
- vector_inspector/core/model_registry.py +205 -0
- vector_inspector/services/backup_restore_service.py +19 -29
- vector_inspector/services/credential_service.py +130 -0
- vector_inspector/services/filter_service.py +1 -1
- vector_inspector/services/profile_service.py +409 -0
- vector_inspector/services/settings_service.py +136 -1
- vector_inspector/ui/components/connection_manager_panel.py +327 -0
- vector_inspector/ui/components/profile_manager_panel.py +565 -0
- vector_inspector/ui/dialogs/__init__.py +6 -0
- vector_inspector/ui/dialogs/cross_db_migration.py +383 -0
- vector_inspector/ui/dialogs/embedding_config_dialog.py +315 -0
- vector_inspector/ui/dialogs/provider_type_dialog.py +189 -0
- vector_inspector/ui/main_window.py +456 -190
- vector_inspector/ui/views/connection_view.py +55 -10
- vector_inspector/ui/views/info_panel.py +272 -55
- vector_inspector/ui/views/metadata_view.py +71 -3
- vector_inspector/ui/views/search_view.py +44 -4
- vector_inspector/ui/views/visualization_view.py +19 -5
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/METADATA +3 -1
- vector_inspector-0.3.1.dist-info/RECORD +55 -0
- vector_inspector-0.2.6.dist-info/RECORD +0 -35
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/WHEEL +0 -0
- {vector_inspector-0.2.6.dist-info → vector_inspector-0.3.1.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
"""Service for secure credential storage using system keychains."""
|
|
2
|
+
|
|
3
|
+
from typing import Optional
|
|
4
|
+
import json
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class CredentialService:
|
|
8
|
+
"""Handles secure storage and retrieval of credentials using system keychains.
|
|
9
|
+
|
|
10
|
+
Falls back to in-memory storage if keyring is not available (not recommended for production).
|
|
11
|
+
"""
|
|
12
|
+
|
|
13
|
+
SERVICE_NAME = "vector-inspector"
|
|
14
|
+
|
|
15
|
+
def __init__(self):
|
|
16
|
+
"""Initialize credential service with keyring if available."""
|
|
17
|
+
self._use_keyring = False
|
|
18
|
+
self._memory_store = {} # Fallback in-memory storage
|
|
19
|
+
|
|
20
|
+
try:
|
|
21
|
+
import keyring
|
|
22
|
+
self._keyring = keyring
|
|
23
|
+
self._use_keyring = True
|
|
24
|
+
except ImportError:
|
|
25
|
+
print("Warning: keyring module not available. Credentials will not be persisted securely.")
|
|
26
|
+
self._keyring = None
|
|
27
|
+
|
|
28
|
+
def store_credentials(self, profile_id: str, credentials: dict) -> bool:
|
|
29
|
+
"""
|
|
30
|
+
Store credentials for a profile.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
profile_id: Unique profile identifier
|
|
34
|
+
credentials: Dictionary of credential data (api_key, password, etc.)
|
|
35
|
+
|
|
36
|
+
Returns:
|
|
37
|
+
True if successful, False otherwise
|
|
38
|
+
"""
|
|
39
|
+
try:
|
|
40
|
+
credential_key = f"profile:{profile_id}"
|
|
41
|
+
credential_json = json.dumps(credentials)
|
|
42
|
+
|
|
43
|
+
if self._use_keyring:
|
|
44
|
+
self._keyring.set_password(
|
|
45
|
+
self.SERVICE_NAME,
|
|
46
|
+
credential_key,
|
|
47
|
+
credential_json
|
|
48
|
+
)
|
|
49
|
+
else:
|
|
50
|
+
# Fallback to in-memory (not persistent)
|
|
51
|
+
self._memory_store[credential_key] = credential_json
|
|
52
|
+
|
|
53
|
+
return True
|
|
54
|
+
except Exception as e:
|
|
55
|
+
print(f"Failed to store credentials: {e}")
|
|
56
|
+
return False
|
|
57
|
+
|
|
58
|
+
def get_credentials(self, profile_id: str) -> Optional[dict]:
|
|
59
|
+
"""
|
|
60
|
+
Retrieve credentials for a profile.
|
|
61
|
+
|
|
62
|
+
Args:
|
|
63
|
+
profile_id: Unique profile identifier
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
Dictionary of credential data, or None if not found
|
|
67
|
+
"""
|
|
68
|
+
try:
|
|
69
|
+
credential_key = f"profile:{profile_id}"
|
|
70
|
+
|
|
71
|
+
if self._use_keyring:
|
|
72
|
+
credential_json = self._keyring.get_password(
|
|
73
|
+
self.SERVICE_NAME,
|
|
74
|
+
credential_key
|
|
75
|
+
)
|
|
76
|
+
else:
|
|
77
|
+
# Fallback to in-memory
|
|
78
|
+
credential_json = self._memory_store.get(credential_key)
|
|
79
|
+
|
|
80
|
+
if credential_json:
|
|
81
|
+
return json.loads(credential_json)
|
|
82
|
+
return None
|
|
83
|
+
except Exception as e:
|
|
84
|
+
print(f"Failed to retrieve credentials: {e}")
|
|
85
|
+
return None
|
|
86
|
+
|
|
87
|
+
def delete_credentials(self, profile_id: str) -> bool:
|
|
88
|
+
"""
|
|
89
|
+
Delete stored credentials for a profile.
|
|
90
|
+
|
|
91
|
+
Args:
|
|
92
|
+
profile_id: Unique profile identifier
|
|
93
|
+
|
|
94
|
+
Returns:
|
|
95
|
+
True if successful, False otherwise
|
|
96
|
+
"""
|
|
97
|
+
try:
|
|
98
|
+
credential_key = f"profile:{profile_id}"
|
|
99
|
+
|
|
100
|
+
if self._use_keyring:
|
|
101
|
+
try:
|
|
102
|
+
self._keyring.delete_password(
|
|
103
|
+
self.SERVICE_NAME,
|
|
104
|
+
credential_key
|
|
105
|
+
)
|
|
106
|
+
except self._keyring.errors.PasswordDeleteError:
|
|
107
|
+
# Credential doesn't exist, that's okay
|
|
108
|
+
pass
|
|
109
|
+
else:
|
|
110
|
+
# Fallback to in-memory
|
|
111
|
+
self._memory_store.pop(credential_key, None)
|
|
112
|
+
|
|
113
|
+
return True
|
|
114
|
+
except Exception as e:
|
|
115
|
+
print(f"Failed to delete credentials: {e}")
|
|
116
|
+
return False
|
|
117
|
+
|
|
118
|
+
def is_keyring_available(self) -> bool:
|
|
119
|
+
"""Check if system keyring is available."""
|
|
120
|
+
return self._use_keyring
|
|
121
|
+
|
|
122
|
+
def clear_all_credentials(self):
|
|
123
|
+
"""Clear all stored credentials. Use with caution!"""
|
|
124
|
+
if not self._use_keyring:
|
|
125
|
+
self._memory_store.clear()
|
|
126
|
+
else:
|
|
127
|
+
# For keyring, we'd need to track all profile IDs
|
|
128
|
+
# This is typically not needed, but can be implemented if required
|
|
129
|
+
print("Warning: clear_all_credentials not implemented for keyring backend")
|
|
130
|
+
|
|
@@ -66,7 +66,7 @@ def apply_client_side_filters(data: Dict[str, Any], filters: List[Dict[str, Any]
|
|
|
66
66
|
"metadatas": [metadatas[i] for i in keep_indices if i < len(metadatas)],
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
if embeddings:
|
|
69
|
+
if embeddings is not None and len(embeddings) > 0:
|
|
70
70
|
filtered_data["embeddings"] = [embeddings[i] for i in keep_indices if i < len(embeddings)]
|
|
71
71
|
|
|
72
72
|
return filtered_data
|
|
@@ -0,0 +1,409 @@
|
|
|
1
|
+
"""Service for managing connection profiles."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import uuid
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List, Dict, Any, Optional
|
|
7
|
+
from PySide6.QtCore import QObject, Signal
|
|
8
|
+
|
|
9
|
+
from .credential_service import CredentialService
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class ConnectionProfile:
|
|
13
|
+
"""Represents a saved connection profile."""
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
profile_id: str,
|
|
18
|
+
name: str,
|
|
19
|
+
provider: str,
|
|
20
|
+
config: Dict[str, Any],
|
|
21
|
+
credential_fields: Optional[List[str]] = None
|
|
22
|
+
):
|
|
23
|
+
"""
|
|
24
|
+
Initialize a connection profile.
|
|
25
|
+
|
|
26
|
+
Args:
|
|
27
|
+
profile_id: Unique profile identifier
|
|
28
|
+
name: User-friendly profile name
|
|
29
|
+
provider: Provider type (chromadb, qdrant, etc.)
|
|
30
|
+
config: Non-sensitive configuration (host, port, path, etc.)
|
|
31
|
+
credential_fields: List of field names that contain credentials
|
|
32
|
+
"""
|
|
33
|
+
self.id = profile_id
|
|
34
|
+
self.name = name
|
|
35
|
+
self.provider = provider
|
|
36
|
+
self.config = config
|
|
37
|
+
self.credential_fields = credential_fields or []
|
|
38
|
+
|
|
39
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
40
|
+
"""Convert profile to dictionary (without credentials)."""
|
|
41
|
+
return {
|
|
42
|
+
"id": self.id,
|
|
43
|
+
"name": self.name,
|
|
44
|
+
"provider": self.provider,
|
|
45
|
+
"config": self.config,
|
|
46
|
+
"credential_fields": self.credential_fields
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
@classmethod
|
|
50
|
+
def from_dict(cls, data: Dict[str, Any]) -> "ConnectionProfile":
|
|
51
|
+
"""Create profile from dictionary."""
|
|
52
|
+
return cls(
|
|
53
|
+
profile_id=data["id"],
|
|
54
|
+
name=data["name"],
|
|
55
|
+
provider=data["provider"],
|
|
56
|
+
config=data.get("config", {}),
|
|
57
|
+
credential_fields=data.get("credential_fields", [])
|
|
58
|
+
)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
class ProfileService(QObject):
|
|
62
|
+
"""Manages connection profiles and persistence.
|
|
63
|
+
|
|
64
|
+
Signals:
|
|
65
|
+
profile_added: Emitted when a profile is added (profile_id)
|
|
66
|
+
profile_updated: Emitted when a profile is updated (profile_id)
|
|
67
|
+
profile_deleted: Emitted when a profile is deleted (profile_id)
|
|
68
|
+
"""
|
|
69
|
+
|
|
70
|
+
# Signals
|
|
71
|
+
profile_added = Signal(str) # profile_id
|
|
72
|
+
profile_updated = Signal(str) # profile_id
|
|
73
|
+
profile_deleted = Signal(str) # profile_id
|
|
74
|
+
|
|
75
|
+
def __init__(self):
|
|
76
|
+
"""Initialize profile service."""
|
|
77
|
+
super().__init__()
|
|
78
|
+
self.profiles_dir = Path.home() / ".vector-inspector"
|
|
79
|
+
self.profiles_file = self.profiles_dir / "profiles.json"
|
|
80
|
+
self.credential_service = CredentialService()
|
|
81
|
+
self._profiles: Dict[str, ConnectionProfile] = {}
|
|
82
|
+
self._last_active_connections: List[str] = []
|
|
83
|
+
self._load_profiles()
|
|
84
|
+
|
|
85
|
+
def _load_profiles(self):
|
|
86
|
+
"""Load profiles from disk."""
|
|
87
|
+
try:
|
|
88
|
+
if self.profiles_file.exists():
|
|
89
|
+
with open(self.profiles_file, 'r', encoding='utf-8') as f:
|
|
90
|
+
data = json.load(f)
|
|
91
|
+
|
|
92
|
+
# Load profiles
|
|
93
|
+
for profile_data in data.get("profiles", []):
|
|
94
|
+
profile = ConnectionProfile.from_dict(profile_data)
|
|
95
|
+
self._profiles[profile.id] = profile
|
|
96
|
+
|
|
97
|
+
# Load last active connections
|
|
98
|
+
self._last_active_connections = data.get("last_active_connections", [])
|
|
99
|
+
except Exception as e:
|
|
100
|
+
print(f"Failed to load profiles: {e}")
|
|
101
|
+
self._profiles = {}
|
|
102
|
+
self._last_active_connections = []
|
|
103
|
+
|
|
104
|
+
def _save_profiles(self):
|
|
105
|
+
"""Save profiles to disk."""
|
|
106
|
+
try:
|
|
107
|
+
# Create directory if it doesn't exist
|
|
108
|
+
self.profiles_dir.mkdir(parents=True, exist_ok=True)
|
|
109
|
+
|
|
110
|
+
# Prepare data
|
|
111
|
+
data = {
|
|
112
|
+
"profiles": [profile.to_dict() for profile in self._profiles.values()],
|
|
113
|
+
"last_active_connections": self._last_active_connections
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
# Write to file
|
|
117
|
+
with open(self.profiles_file, 'w', encoding='utf-8') as f:
|
|
118
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
119
|
+
except Exception as e:
|
|
120
|
+
print(f"Failed to save profiles: {e}")
|
|
121
|
+
|
|
122
|
+
def create_profile(
|
|
123
|
+
self,
|
|
124
|
+
name: str,
|
|
125
|
+
provider: str,
|
|
126
|
+
config: Dict[str, Any],
|
|
127
|
+
credentials: Optional[Dict[str, Any]] = None
|
|
128
|
+
) -> str:
|
|
129
|
+
"""
|
|
130
|
+
Create a new connection profile.
|
|
131
|
+
|
|
132
|
+
Args:
|
|
133
|
+
name: Profile name
|
|
134
|
+
provider: Provider type
|
|
135
|
+
config: Connection configuration (non-sensitive)
|
|
136
|
+
credentials: Credential data to store securely (optional)
|
|
137
|
+
|
|
138
|
+
Returns:
|
|
139
|
+
The profile ID
|
|
140
|
+
"""
|
|
141
|
+
profile_id = str(uuid.uuid4())
|
|
142
|
+
credential_fields = list(credentials.keys()) if credentials else []
|
|
143
|
+
|
|
144
|
+
profile = ConnectionProfile(
|
|
145
|
+
profile_id=profile_id,
|
|
146
|
+
name=name,
|
|
147
|
+
provider=provider,
|
|
148
|
+
config=config,
|
|
149
|
+
credential_fields=credential_fields
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
self._profiles[profile_id] = profile
|
|
153
|
+
|
|
154
|
+
# Store credentials if provided
|
|
155
|
+
if credentials:
|
|
156
|
+
self.credential_service.store_credentials(profile_id, credentials)
|
|
157
|
+
|
|
158
|
+
self._save_profiles()
|
|
159
|
+
self.profile_added.emit(profile_id)
|
|
160
|
+
|
|
161
|
+
return profile_id
|
|
162
|
+
|
|
163
|
+
def get_profile(self, profile_id: str) -> Optional[ConnectionProfile]:
|
|
164
|
+
"""Get a profile by ID."""
|
|
165
|
+
return self._profiles.get(profile_id)
|
|
166
|
+
|
|
167
|
+
def get_all_profiles(self) -> List[ConnectionProfile]:
|
|
168
|
+
"""Get all saved profiles."""
|
|
169
|
+
return list(self._profiles.values())
|
|
170
|
+
|
|
171
|
+
def update_profile(
|
|
172
|
+
self,
|
|
173
|
+
profile_id: str,
|
|
174
|
+
name: Optional[str] = None,
|
|
175
|
+
config: Optional[Dict[str, Any]] = None,
|
|
176
|
+
credentials: Optional[Dict[str, Any]] = None
|
|
177
|
+
) -> bool:
|
|
178
|
+
"""
|
|
179
|
+
Update an existing profile.
|
|
180
|
+
|
|
181
|
+
Args:
|
|
182
|
+
profile_id: ID of profile to update
|
|
183
|
+
name: New name (optional)
|
|
184
|
+
config: New configuration (optional)
|
|
185
|
+
credentials: New credentials (optional)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
True if successful, False if profile not found
|
|
189
|
+
"""
|
|
190
|
+
profile = self._profiles.get(profile_id)
|
|
191
|
+
if not profile:
|
|
192
|
+
return False
|
|
193
|
+
|
|
194
|
+
if name is not None:
|
|
195
|
+
profile.name = name
|
|
196
|
+
|
|
197
|
+
if config is not None:
|
|
198
|
+
profile.config = config
|
|
199
|
+
|
|
200
|
+
if credentials is not None:
|
|
201
|
+
profile.credential_fields = list(credentials.keys())
|
|
202
|
+
self.credential_service.store_credentials(profile_id, credentials)
|
|
203
|
+
|
|
204
|
+
self._save_profiles()
|
|
205
|
+
self.profile_updated.emit(profile_id)
|
|
206
|
+
|
|
207
|
+
return True
|
|
208
|
+
|
|
209
|
+
def delete_profile(self, profile_id: str) -> bool:
|
|
210
|
+
"""
|
|
211
|
+
Delete a profile.
|
|
212
|
+
|
|
213
|
+
Args:
|
|
214
|
+
profile_id: ID of profile to delete
|
|
215
|
+
|
|
216
|
+
Returns:
|
|
217
|
+
True if successful, False if profile not found
|
|
218
|
+
"""
|
|
219
|
+
if profile_id not in self._profiles:
|
|
220
|
+
return False
|
|
221
|
+
|
|
222
|
+
# Delete credentials
|
|
223
|
+
self.credential_service.delete_credentials(profile_id)
|
|
224
|
+
|
|
225
|
+
# Remove from profiles
|
|
226
|
+
del self._profiles[profile_id]
|
|
227
|
+
|
|
228
|
+
# Remove from last active connections if present
|
|
229
|
+
if profile_id in self._last_active_connections:
|
|
230
|
+
self._last_active_connections.remove(profile_id)
|
|
231
|
+
|
|
232
|
+
self._save_profiles()
|
|
233
|
+
self.profile_deleted.emit(profile_id)
|
|
234
|
+
|
|
235
|
+
return True
|
|
236
|
+
|
|
237
|
+
def duplicate_profile(self, profile_id: str, new_name: str) -> Optional[str]:
|
|
238
|
+
"""
|
|
239
|
+
Duplicate an existing profile.
|
|
240
|
+
|
|
241
|
+
Args:
|
|
242
|
+
profile_id: ID of profile to duplicate
|
|
243
|
+
new_name: Name for the new profile
|
|
244
|
+
|
|
245
|
+
Returns:
|
|
246
|
+
New profile ID, or None if source profile not found
|
|
247
|
+
"""
|
|
248
|
+
source_profile = self._profiles.get(profile_id)
|
|
249
|
+
if not source_profile:
|
|
250
|
+
return None
|
|
251
|
+
|
|
252
|
+
# Get credentials from source
|
|
253
|
+
credentials = self.credential_service.get_credentials(profile_id)
|
|
254
|
+
|
|
255
|
+
# Create new profile
|
|
256
|
+
new_id = self.create_profile(
|
|
257
|
+
name=new_name,
|
|
258
|
+
provider=source_profile.provider,
|
|
259
|
+
config=source_profile.config.copy(),
|
|
260
|
+
credentials=credentials
|
|
261
|
+
)
|
|
262
|
+
|
|
263
|
+
return new_id
|
|
264
|
+
|
|
265
|
+
def get_profile_with_credentials(self, profile_id: str) -> Optional[Dict[str, Any]]:
|
|
266
|
+
"""
|
|
267
|
+
Get a profile along with its credentials.
|
|
268
|
+
|
|
269
|
+
Args:
|
|
270
|
+
profile_id: ID of profile
|
|
271
|
+
|
|
272
|
+
Returns:
|
|
273
|
+
Dictionary with profile data and credentials, or None if not found
|
|
274
|
+
"""
|
|
275
|
+
profile = self._profiles.get(profile_id)
|
|
276
|
+
if not profile:
|
|
277
|
+
return None
|
|
278
|
+
|
|
279
|
+
credentials = self.credential_service.get_credentials(profile_id)
|
|
280
|
+
|
|
281
|
+
return {
|
|
282
|
+
"id": profile.id,
|
|
283
|
+
"name": profile.name,
|
|
284
|
+
"provider": profile.provider,
|
|
285
|
+
"config": profile.config,
|
|
286
|
+
"credentials": credentials or {}
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
def export_profiles(self, include_credentials: bool = False) -> List[Dict[str, Any]]:
|
|
290
|
+
"""
|
|
291
|
+
Export all profiles for backup/sharing.
|
|
292
|
+
|
|
293
|
+
Args:
|
|
294
|
+
include_credentials: Whether to include credentials (NOT RECOMMENDED)
|
|
295
|
+
|
|
296
|
+
Returns:
|
|
297
|
+
List of profile dictionaries
|
|
298
|
+
"""
|
|
299
|
+
exported = []
|
|
300
|
+
for profile in self._profiles.values():
|
|
301
|
+
data = profile.to_dict()
|
|
302
|
+
if include_credentials:
|
|
303
|
+
credentials = self.credential_service.get_credentials(profile.id)
|
|
304
|
+
if credentials:
|
|
305
|
+
data["credentials"] = credentials
|
|
306
|
+
exported.append(data)
|
|
307
|
+
return exported
|
|
308
|
+
|
|
309
|
+
def import_profiles(
|
|
310
|
+
self,
|
|
311
|
+
profiles_data: List[Dict[str, Any]],
|
|
312
|
+
overwrite: bool = False
|
|
313
|
+
) -> Dict[str, str]:
|
|
314
|
+
"""
|
|
315
|
+
Import profiles from exported data.
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
profiles_data: List of profile dictionaries
|
|
319
|
+
overwrite: Whether to overwrite existing profiles with same ID
|
|
320
|
+
|
|
321
|
+
Returns:
|
|
322
|
+
Dictionary mapping old IDs to new IDs
|
|
323
|
+
"""
|
|
324
|
+
id_mapping = {}
|
|
325
|
+
|
|
326
|
+
for profile_data in profiles_data:
|
|
327
|
+
old_id = profile_data.get("id")
|
|
328
|
+
|
|
329
|
+
# Generate new ID if not overwriting or ID exists
|
|
330
|
+
if not overwrite or old_id in self._profiles:
|
|
331
|
+
new_id = str(uuid.uuid4())
|
|
332
|
+
else:
|
|
333
|
+
new_id = str(old_id) if old_id else str(uuid.uuid4())
|
|
334
|
+
|
|
335
|
+
credentials = profile_data.pop("credentials", None)
|
|
336
|
+
|
|
337
|
+
# Create profile
|
|
338
|
+
profile = ConnectionProfile(
|
|
339
|
+
profile_id=new_id,
|
|
340
|
+
name=profile_data["name"],
|
|
341
|
+
provider=profile_data["provider"],
|
|
342
|
+
config=profile_data.get("config", {}),
|
|
343
|
+
credential_fields=profile_data.get("credential_fields", [])
|
|
344
|
+
)
|
|
345
|
+
|
|
346
|
+
self._profiles[new_id] = profile
|
|
347
|
+
|
|
348
|
+
# Store credentials if provided
|
|
349
|
+
if credentials:
|
|
350
|
+
self.credential_service.store_credentials(new_id, credentials)
|
|
351
|
+
|
|
352
|
+
id_mapping[old_id] = new_id
|
|
353
|
+
|
|
354
|
+
self._save_profiles()
|
|
355
|
+
|
|
356
|
+
return id_mapping
|
|
357
|
+
|
|
358
|
+
def save_last_active_connections(self, connection_ids: List[str]):
|
|
359
|
+
"""
|
|
360
|
+
Save list of last active connection profile IDs for session restore.
|
|
361
|
+
|
|
362
|
+
Args:
|
|
363
|
+
connection_ids: List of profile IDs that were active
|
|
364
|
+
"""
|
|
365
|
+
self._last_active_connections = connection_ids
|
|
366
|
+
self._save_profiles()
|
|
367
|
+
|
|
368
|
+
def get_last_active_connections(self) -> List[str]:
|
|
369
|
+
"""Get list of last active connection profile IDs."""
|
|
370
|
+
return self._last_active_connections.copy()
|
|
371
|
+
|
|
372
|
+
def migrate_legacy_connection(self, config: Dict[str, Any]) -> str:
|
|
373
|
+
"""
|
|
374
|
+
Migrate a legacy single-connection configuration to a profile.
|
|
375
|
+
|
|
376
|
+
Args:
|
|
377
|
+
config: Legacy connection configuration
|
|
378
|
+
|
|
379
|
+
Returns:
|
|
380
|
+
The new profile ID
|
|
381
|
+
"""
|
|
382
|
+
provider = config.get("provider", "chromadb")
|
|
383
|
+
conn_type = config.get("type", "persistent")
|
|
384
|
+
|
|
385
|
+
# Create a name based on connection type
|
|
386
|
+
if conn_type == "persistent":
|
|
387
|
+
name = f"Legacy {provider.title()} (Persistent)"
|
|
388
|
+
elif conn_type == "http":
|
|
389
|
+
host = config.get("host", "localhost")
|
|
390
|
+
name = f"Legacy {provider.title()} ({host})"
|
|
391
|
+
else:
|
|
392
|
+
name = f"Legacy {provider.title()} (Ephemeral)"
|
|
393
|
+
|
|
394
|
+
# Extract credentials if any
|
|
395
|
+
credentials = {}
|
|
396
|
+
if "api_key" in config and config["api_key"]:
|
|
397
|
+
credentials["api_key"] = config["api_key"]
|
|
398
|
+
del config["api_key"] # Remove from config
|
|
399
|
+
|
|
400
|
+
# Create profile
|
|
401
|
+
profile_id = self.create_profile(
|
|
402
|
+
name=name,
|
|
403
|
+
provider=provider,
|
|
404
|
+
config=config,
|
|
405
|
+
credentials=credentials if credentials else None
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
return profile_id
|
|
409
|
+
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
import json
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import Dict, Any, Optional
|
|
5
|
+
from typing import Dict, Any, Optional, List
|
|
6
|
+
from vector_inspector.core.cache_manager import invalidate_cache_on_settings_change
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class SettingsService:
|
|
@@ -49,12 +50,146 @@ class SettingsService:
|
|
|
49
50
|
"""Get a setting value."""
|
|
50
51
|
return self.settings.get(key, default)
|
|
51
52
|
|
|
53
|
+
def get_cache_enabled(self) -> bool:
|
|
54
|
+
"""Get whether caching is enabled (default: True)."""
|
|
55
|
+
return self.settings.get("cache_enabled", True)
|
|
56
|
+
|
|
57
|
+
def set_cache_enabled(self, enabled: bool):
|
|
58
|
+
"""Set whether caching is enabled."""
|
|
59
|
+
self.set("cache_enabled", enabled)
|
|
60
|
+
# Update cache manager state
|
|
61
|
+
from vector_inspector.core.cache_manager import get_cache_manager
|
|
62
|
+
cache = get_cache_manager()
|
|
63
|
+
if enabled:
|
|
64
|
+
cache.enable()
|
|
65
|
+
else:
|
|
66
|
+
cache.disable()
|
|
67
|
+
|
|
52
68
|
def set(self, key: str, value: Any):
|
|
53
69
|
"""Set a setting value."""
|
|
54
70
|
self.settings[key] = value
|
|
55
71
|
self._save_settings()
|
|
72
|
+
# Invalidate cache when settings change (only if cache is enabled)
|
|
73
|
+
if key != "cache_enabled": # Don't invalidate when toggling cache itself
|
|
74
|
+
invalidate_cache_on_settings_change()
|
|
56
75
|
|
|
57
76
|
def clear(self):
|
|
58
77
|
"""Clear all settings."""
|
|
59
78
|
self.settings = {}
|
|
60
79
|
self._save_settings()
|
|
80
|
+
|
|
81
|
+
def save_embedding_model(self, connection_id: str, collection_name: str, model_name: str, model_type: str = "user-configured"):
|
|
82
|
+
"""Save embedding model mapping for a collection.
|
|
83
|
+
|
|
84
|
+
Args:
|
|
85
|
+
connection_id: Connection identifier
|
|
86
|
+
collection_name: Collection name
|
|
87
|
+
model_name: Embedding model name (e.g., 'sentence-transformers/all-MiniLM-L6-v2')
|
|
88
|
+
model_type: Type of configuration ('user-configured', 'auto-detected', 'stored')
|
|
89
|
+
"""
|
|
90
|
+
if "collection_embedding_models" not in self.settings:
|
|
91
|
+
self.settings["collection_embedding_models"] = {}
|
|
92
|
+
|
|
93
|
+
collection_key = f"{connection_id}:{collection_name}"
|
|
94
|
+
self.settings["collection_embedding_models"][collection_key] = {
|
|
95
|
+
"model": model_name,
|
|
96
|
+
"type": model_type,
|
|
97
|
+
"timestamp": self._get_timestamp()
|
|
98
|
+
}
|
|
99
|
+
self._save_settings()
|
|
100
|
+
|
|
101
|
+
def get_embedding_model(self, connection_id: str, collection_name: str) -> Optional[Dict[str, Any]]:
|
|
102
|
+
"""Get embedding model mapping for a collection.
|
|
103
|
+
|
|
104
|
+
Args:
|
|
105
|
+
connection_id: Connection identifier
|
|
106
|
+
collection_name: Collection name
|
|
107
|
+
|
|
108
|
+
Returns:
|
|
109
|
+
Dictionary with 'model', 'type', and 'timestamp' or None
|
|
110
|
+
"""
|
|
111
|
+
collection_models = self.settings.get("collection_embedding_models", {})
|
|
112
|
+
collection_key = f"{connection_id}:{collection_name}"
|
|
113
|
+
return collection_models.get(collection_key)
|
|
114
|
+
|
|
115
|
+
def remove_embedding_model(self, connection_id: str, collection_name: str):
|
|
116
|
+
"""Remove embedding model mapping for a collection.
|
|
117
|
+
|
|
118
|
+
Args:
|
|
119
|
+
connection_id: Connection identifier
|
|
120
|
+
collection_name: Collection name
|
|
121
|
+
"""
|
|
122
|
+
if "collection_embedding_models" not in self.settings:
|
|
123
|
+
return
|
|
124
|
+
|
|
125
|
+
collection_key = f"{connection_id}:{collection_name}"
|
|
126
|
+
self.settings["collection_embedding_models"].pop(collection_key, None)
|
|
127
|
+
self._save_settings()
|
|
128
|
+
|
|
129
|
+
def _get_timestamp(self) -> str:
|
|
130
|
+
"""Get current timestamp as ISO string."""
|
|
131
|
+
from datetime import datetime
|
|
132
|
+
return datetime.now().isoformat()
|
|
133
|
+
|
|
134
|
+
def add_custom_embedding_model(self, model_name: str, dimension: int, model_type: str = "sentence-transformer", description: str = "Custom model"):
|
|
135
|
+
"""Add a custom embedding model to the known models list.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
model_name: Name of the embedding model
|
|
139
|
+
dimension: Vector dimension
|
|
140
|
+
model_type: Type of model (e.g., 'sentence-transformer', 'clip', 'openai')
|
|
141
|
+
description: Brief description of the model
|
|
142
|
+
"""
|
|
143
|
+
if "custom_embedding_models" not in self.settings:
|
|
144
|
+
self.settings["custom_embedding_models"] = []
|
|
145
|
+
|
|
146
|
+
# Check if already exists
|
|
147
|
+
for model in self.settings["custom_embedding_models"]:
|
|
148
|
+
if model["name"] == model_name and model["dimension"] == dimension:
|
|
149
|
+
# Update existing entry
|
|
150
|
+
model["type"] = model_type
|
|
151
|
+
model["description"] = description
|
|
152
|
+
model["last_used"] = self._get_timestamp()
|
|
153
|
+
self._save_settings()
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
# Add new entry
|
|
157
|
+
self.settings["custom_embedding_models"].append({
|
|
158
|
+
"name": model_name,
|
|
159
|
+
"dimension": dimension,
|
|
160
|
+
"type": model_type,
|
|
161
|
+
"description": description,
|
|
162
|
+
"added": self._get_timestamp(),
|
|
163
|
+
"last_used": self._get_timestamp()
|
|
164
|
+
})
|
|
165
|
+
self._save_settings()
|
|
166
|
+
|
|
167
|
+
def get_custom_embedding_models(self, dimension: Optional[int] = None) -> List[Dict[str, Any]]:
|
|
168
|
+
"""Get list of custom embedding models.
|
|
169
|
+
|
|
170
|
+
Args:
|
|
171
|
+
dimension: Optional filter by dimension
|
|
172
|
+
|
|
173
|
+
Returns:
|
|
174
|
+
List of custom model dictionaries
|
|
175
|
+
"""
|
|
176
|
+
models = self.settings.get("custom_embedding_models", [])
|
|
177
|
+
if dimension is not None:
|
|
178
|
+
return [m for m in models if m["dimension"] == dimension]
|
|
179
|
+
return models
|
|
180
|
+
|
|
181
|
+
def remove_custom_embedding_model(self, model_name: str, dimension: int):
|
|
182
|
+
"""Remove a custom embedding model.
|
|
183
|
+
|
|
184
|
+
Args:
|
|
185
|
+
model_name: Name of the model to remove
|
|
186
|
+
dimension: Vector dimension
|
|
187
|
+
"""
|
|
188
|
+
if "custom_embedding_models" not in self.settings:
|
|
189
|
+
return
|
|
190
|
+
|
|
191
|
+
self.settings["custom_embedding_models"] = [
|
|
192
|
+
m for m in self.settings["custom_embedding_models"]
|
|
193
|
+
if not (m["name"] == model_name and m["dimension"] == dimension)
|
|
194
|
+
]
|
|
195
|
+
self._save_settings()
|