mindroot 9.7.0__py3-none-any.whl → 9.8.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- mindroot/coreplugins/admin/router.py +6 -0
- mindroot/coreplugins/admin/settings_router.py +80 -0
- mindroot/coreplugins/admin/static/js/model-preferences-v2.js +587 -0
- mindroot/coreplugins/admin/templates/admin.jinja2 +4 -4
- mindroot/coreplugins/admin/templates/model-preferences-v2.jinja2 +27 -0
- mindroot/coreplugins/jwt_auth/middleware.py +1 -1
- mindroot/lib/providers/__init__.py +46 -0
- mindroot/lib/providers/model_preferences_v2.py +165 -0
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/METADATA +1 -1
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/RECORD +14 -11
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/WHEEL +0 -0
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/entry_points.txt +0 -0
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/licenses/LICENSE +0 -0
- {mindroot-9.7.0.dist-info → mindroot-9.8.0.dist-info}/top_level.txt +0 -0
|
@@ -29,6 +29,12 @@ async def get_admin_html():
|
|
|
29
29
|
html = await render('admin', {"log_id": log_id})
|
|
30
30
|
return html
|
|
31
31
|
|
|
32
|
+
@admin_router.get("/admin/model-preferences-v2", response_class=HTMLResponse)
|
|
33
|
+
async def get_model_preferences_v2_html():
|
|
34
|
+
"""Serve the new Model Preferences V2 page"""
|
|
35
|
+
html = await render('model-preferences-v2', {})
|
|
36
|
+
return html
|
|
37
|
+
|
|
32
38
|
@admin_router.post("/admin/get-version-info")
|
|
33
39
|
async def get_version_info():
|
|
34
40
|
"""Get version information, trying git first, then falling back to cached file."""
|
|
@@ -10,6 +10,12 @@ from lib.db.organize_models import organize_for_display
|
|
|
10
10
|
from copy import deepcopy
|
|
11
11
|
from .service_models import get_service_models_from_providers
|
|
12
12
|
|
|
13
|
+
# Import the new v2 preferences system with try/except for backward compatibility
|
|
14
|
+
try:
|
|
15
|
+
from lib.providers.model_preferences_v2 import ModelPreferencesV2
|
|
16
|
+
except ImportError:
|
|
17
|
+
ModelPreferencesV2 = None
|
|
18
|
+
|
|
13
19
|
router = APIRouter()
|
|
14
20
|
|
|
15
21
|
SETTINGS_FILE_PATH = 'data/preferred_models.json'
|
|
@@ -160,3 +166,77 @@ async def get_organized_models():
|
|
|
160
166
|
async def get_equivalent_flags():
|
|
161
167
|
return read_equivalent_flags()
|
|
162
168
|
|
|
169
|
+
# NEW V2 ENDPOINTS - These are additive and don't affect existing functionality
|
|
170
|
+
|
|
171
|
+
@router.get('/settings_v2', response_model=Dict[str, List[List[str]]])
|
|
172
|
+
async def get_settings_v2():
|
|
173
|
+
"""Get preferences in new v2 format: {service: [[provider, model], ...]}"""
|
|
174
|
+
if ModelPreferencesV2 is None:
|
|
175
|
+
raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
|
|
176
|
+
|
|
177
|
+
try:
|
|
178
|
+
prefs_manager = ModelPreferencesV2()
|
|
179
|
+
return prefs_manager.get_preferences()
|
|
180
|
+
except Exception as e:
|
|
181
|
+
print(f"Error getting v2 preferences: {e}")
|
|
182
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
183
|
+
|
|
184
|
+
@router.post('/settings_v2')
|
|
185
|
+
async def save_settings_v2(request: Request):
|
|
186
|
+
"""Save preferences in new v2 format."""
|
|
187
|
+
if ModelPreferencesV2 is None:
|
|
188
|
+
raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
|
|
189
|
+
|
|
190
|
+
try:
|
|
191
|
+
body = await request.json()
|
|
192
|
+
print(f"Received v2 preferences: {body}")
|
|
193
|
+
|
|
194
|
+
# Validate format: should be {service: [[provider, model], ...]}
|
|
195
|
+
if not isinstance(body, dict):
|
|
196
|
+
raise ValueError("Preferences must be a dictionary")
|
|
197
|
+
|
|
198
|
+
for service, provider_model_pairs in body.items():
|
|
199
|
+
if not isinstance(provider_model_pairs, list):
|
|
200
|
+
raise ValueError(f"Service '{service}' must have a list of provider/model pairs")
|
|
201
|
+
|
|
202
|
+
for pair in provider_model_pairs:
|
|
203
|
+
if not isinstance(pair, list) or len(pair) != 2:
|
|
204
|
+
raise ValueError(f"Each provider/model pair must be a list of exactly 2 items")
|
|
205
|
+
if not all(isinstance(item, str) for item in pair):
|
|
206
|
+
raise ValueError(f"Provider and model names must be strings")
|
|
207
|
+
|
|
208
|
+
prefs_manager = ModelPreferencesV2()
|
|
209
|
+
prefs_manager.save_preferences(body)
|
|
210
|
+
|
|
211
|
+
return {"success": True, "message": "Preferences saved successfully"}
|
|
212
|
+
|
|
213
|
+
except Exception as e:
|
|
214
|
+
print(f"Error saving v2 preferences: {e}")
|
|
215
|
+
raise HTTPException(status_code=422, detail=str(e))
|
|
216
|
+
|
|
217
|
+
@router.post('/migrate_settings')
|
|
218
|
+
async def migrate_settings():
|
|
219
|
+
"""One-time migration from old format to new v2 format."""
|
|
220
|
+
if ModelPreferencesV2 is None:
|
|
221
|
+
raise HTTPException(status_code=501, detail="Model Preferences V2 not available")
|
|
222
|
+
|
|
223
|
+
try:
|
|
224
|
+
# Get old format preferences
|
|
225
|
+
old_preferences = read_settings()
|
|
226
|
+
|
|
227
|
+
# Convert to new format
|
|
228
|
+
prefs_manager = ModelPreferencesV2()
|
|
229
|
+
new_preferences = prefs_manager.migrate_from_old_format(old_preferences)
|
|
230
|
+
|
|
231
|
+
# Save new format
|
|
232
|
+
prefs_manager.save_preferences(new_preferences)
|
|
233
|
+
|
|
234
|
+
return {
|
|
235
|
+
"success": True,
|
|
236
|
+
"message": f"Migrated {len(old_preferences)} old preferences to new format",
|
|
237
|
+
"migrated_preferences": new_preferences
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
except Exception as e:
|
|
241
|
+
print(f"Error migrating preferences: {e}")
|
|
242
|
+
raise HTTPException(status_code=500, detail=str(e))
|
|
@@ -0,0 +1,587 @@
|
|
|
1
|
+
import { LitElement, html, css } from './lit-core.min.js';
|
|
2
|
+
import { BaseEl } from './base.js';
|
|
3
|
+
|
|
4
|
+
class ModelPreferencesV2 extends BaseEl {
|
|
5
|
+
static properties = {
|
|
6
|
+
preferences: { type: Object },
|
|
7
|
+
serviceModels: { type: Object },
|
|
8
|
+
loading: { type: Boolean },
|
|
9
|
+
saving: { type: Boolean },
|
|
10
|
+
selectedService: { type: String },
|
|
11
|
+
availableServices: { type: Array }
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
static styles = css`
|
|
15
|
+
:host {
|
|
16
|
+
display: block;
|
|
17
|
+
width: 100%;
|
|
18
|
+
height: 100%;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
.preferences-v2-container {
|
|
22
|
+
display: flex;
|
|
23
|
+
flex-direction: column;
|
|
24
|
+
width: 100%;
|
|
25
|
+
max-width: 1200px;
|
|
26
|
+
margin: 0 auto;
|
|
27
|
+
gap: 20px;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
.section {
|
|
31
|
+
background: rgb(10, 10, 25);
|
|
32
|
+
border-radius: 8px;
|
|
33
|
+
padding: 1rem;
|
|
34
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
.section h3 {
|
|
38
|
+
margin: 0 0 1rem 0;
|
|
39
|
+
color: #fff;
|
|
40
|
+
display: flex;
|
|
41
|
+
align-items: center;
|
|
42
|
+
gap: 0.5rem;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
.service-selector {
|
|
46
|
+
display: flex;
|
|
47
|
+
gap: 10px;
|
|
48
|
+
align-items: center;
|
|
49
|
+
margin-bottom: 1rem;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
.service-selector select {
|
|
53
|
+
background: #2a2a40;
|
|
54
|
+
color: #fff;
|
|
55
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
56
|
+
padding: 0.5rem;
|
|
57
|
+
border-radius: 4px;
|
|
58
|
+
min-width: 150px;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
.provider-list {
|
|
62
|
+
display: flex;
|
|
63
|
+
flex-direction: column;
|
|
64
|
+
gap: 10px;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
.provider-item {
|
|
68
|
+
display: flex;
|
|
69
|
+
align-items: center;
|
|
70
|
+
gap: 10px;
|
|
71
|
+
padding: 0.75rem;
|
|
72
|
+
background: rgba(0, 0, 0, 0.2);
|
|
73
|
+
border-radius: 4px;
|
|
74
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
.provider-item.dragging {
|
|
78
|
+
opacity: 0.5;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.drag-handle {
|
|
82
|
+
cursor: grab;
|
|
83
|
+
color: #888;
|
|
84
|
+
font-size: 1.2em;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
.drag-handle:active {
|
|
88
|
+
cursor: grabbing;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
.provider-info {
|
|
92
|
+
flex: 1;
|
|
93
|
+
display: flex;
|
|
94
|
+
align-items: center;
|
|
95
|
+
gap: 10px;
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
.provider-name {
|
|
99
|
+
font-weight: 500;
|
|
100
|
+
color: #fff;
|
|
101
|
+
min-width: 120px;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
.model-select {
|
|
105
|
+
background: #2a2a40;
|
|
106
|
+
color: #fff;
|
|
107
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
108
|
+
padding: 0.5rem;
|
|
109
|
+
border-radius: 4px;
|
|
110
|
+
min-width: 200px;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
.order-controls {
|
|
114
|
+
display: flex;
|
|
115
|
+
flex-direction: column;
|
|
116
|
+
gap: 2px;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.order-btn {
|
|
120
|
+
background: #2a2a40;
|
|
121
|
+
color: #fff;
|
|
122
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
123
|
+
padding: 0.25rem 0.5rem;
|
|
124
|
+
border-radius: 2px;
|
|
125
|
+
cursor: pointer;
|
|
126
|
+
font-size: 0.8em;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
.order-btn:hover {
|
|
130
|
+
background: #3a3a50;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
.order-btn:disabled {
|
|
134
|
+
opacity: 0.5;
|
|
135
|
+
cursor: not-allowed;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
.remove-btn {
|
|
139
|
+
background: #402a2a;
|
|
140
|
+
color: #fff;
|
|
141
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
142
|
+
padding: 0.5rem;
|
|
143
|
+
border-radius: 4px;
|
|
144
|
+
cursor: pointer;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
.remove-btn:hover {
|
|
148
|
+
background: #503a3a;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
.add-provider {
|
|
152
|
+
display: flex;
|
|
153
|
+
gap: 10px;
|
|
154
|
+
align-items: center;
|
|
155
|
+
margin-top: 1rem;
|
|
156
|
+
padding: 1rem;
|
|
157
|
+
background: rgba(0, 0, 0, 0.1);
|
|
158
|
+
border-radius: 4px;
|
|
159
|
+
border: 1px dashed rgba(255, 255, 255, 0.2);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
.add-provider select {
|
|
163
|
+
background: #2a2a40;
|
|
164
|
+
color: #fff;
|
|
165
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
166
|
+
padding: 0.5rem;
|
|
167
|
+
border-radius: 4px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.add-btn {
|
|
171
|
+
background: #2a402a;
|
|
172
|
+
color: #fff;
|
|
173
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
174
|
+
padding: 0.5rem 1rem;
|
|
175
|
+
border-radius: 4px;
|
|
176
|
+
cursor: pointer;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
.add-btn:hover {
|
|
180
|
+
background: #3a503a;
|
|
181
|
+
}
|
|
182
|
+
|
|
183
|
+
.actions {
|
|
184
|
+
display: flex;
|
|
185
|
+
gap: 10px;
|
|
186
|
+
justify-content: flex-end;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
.save-btn {
|
|
190
|
+
background: #2a402a;
|
|
191
|
+
color: #fff;
|
|
192
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
193
|
+
padding: 0.75rem 1.5rem;
|
|
194
|
+
border-radius: 4px;
|
|
195
|
+
cursor: pointer;
|
|
196
|
+
font-weight: 500;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
.save-btn:hover {
|
|
200
|
+
background: #3a503a;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
.save-btn:disabled {
|
|
204
|
+
opacity: 0.5;
|
|
205
|
+
cursor: not-allowed;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
.migrate-btn {
|
|
209
|
+
background: #403a2a;
|
|
210
|
+
color: #fff;
|
|
211
|
+
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
212
|
+
padding: 0.75rem 1.5rem;
|
|
213
|
+
border-radius: 4px;
|
|
214
|
+
cursor: pointer;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
.migrate-btn:hover {
|
|
218
|
+
background: #504a3a;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
.loading {
|
|
222
|
+
text-align: center;
|
|
223
|
+
color: #888;
|
|
224
|
+
padding: 2rem;
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
.error {
|
|
228
|
+
color: #ff6b6b;
|
|
229
|
+
background: rgba(255, 107, 107, 0.1);
|
|
230
|
+
padding: 1rem;
|
|
231
|
+
border-radius: 4px;
|
|
232
|
+
margin-bottom: 1rem;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
.success {
|
|
236
|
+
color: #51cf66;
|
|
237
|
+
background: rgba(81, 207, 102, 0.1);
|
|
238
|
+
padding: 1rem;
|
|
239
|
+
border-radius: 4px;
|
|
240
|
+
margin-bottom: 1rem;
|
|
241
|
+
}
|
|
242
|
+
`;
|
|
243
|
+
|
|
244
|
+
constructor() {
|
|
245
|
+
super();
|
|
246
|
+
this.preferences = {};
|
|
247
|
+
this.serviceModels = {};
|
|
248
|
+
this.loading = true;
|
|
249
|
+
this.saving = false;
|
|
250
|
+
this.selectedService = '';
|
|
251
|
+
this.availableServices = [];
|
|
252
|
+
this.fetchData();
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
async fetchData() {
|
|
256
|
+
this.loading = true;
|
|
257
|
+
try {
|
|
258
|
+
await Promise.all([
|
|
259
|
+
this.fetchPreferences(),
|
|
260
|
+
this.fetchServiceModels()
|
|
261
|
+
]);
|
|
262
|
+
this.updateAvailableServices();
|
|
263
|
+
} catch (error) {
|
|
264
|
+
console.error('Error fetching data:', error);
|
|
265
|
+
} finally {
|
|
266
|
+
this.loading = false;
|
|
267
|
+
}
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
async fetchPreferences() {
|
|
271
|
+
try {
|
|
272
|
+
const response = await fetch('/settings_v2');
|
|
273
|
+
if (response.ok) {
|
|
274
|
+
this.preferences = await response.json();
|
|
275
|
+
} else {
|
|
276
|
+
console.log('V2 preferences not available, using empty preferences');
|
|
277
|
+
this.preferences = {};
|
|
278
|
+
}
|
|
279
|
+
} catch (error) {
|
|
280
|
+
console.error('Error fetching v2 preferences:', error);
|
|
281
|
+
this.preferences = {};
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
async fetchServiceModels() {
|
|
286
|
+
try {
|
|
287
|
+
const response = await fetch('/service-models');
|
|
288
|
+
this.serviceModels = await response.json();
|
|
289
|
+
} catch (error) {
|
|
290
|
+
console.error('Error fetching service models:', error);
|
|
291
|
+
this.serviceModels = {};
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
updateAvailableServices() {
|
|
296
|
+
const services = new Set();
|
|
297
|
+
|
|
298
|
+
// Add services from current preferences
|
|
299
|
+
Object.keys(this.preferences).forEach(service => services.add(service));
|
|
300
|
+
|
|
301
|
+
// Add services from available service models
|
|
302
|
+
Object.keys(this.serviceModels).forEach(service => services.add(service));
|
|
303
|
+
|
|
304
|
+
this.availableServices = Array.from(services).sort();
|
|
305
|
+
|
|
306
|
+
if (!this.selectedService && this.availableServices.length > 0) {
|
|
307
|
+
this.selectedService = this.availableServices[0];
|
|
308
|
+
}
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
handleServiceChange(event) {
|
|
312
|
+
this.selectedService = event.target.value;
|
|
313
|
+
}
|
|
314
|
+
|
|
315
|
+
moveProviderUp(index) {
|
|
316
|
+
if (index === 0) return;
|
|
317
|
+
|
|
318
|
+
const servicePrefs = [...(this.preferences[this.selectedService] || [])];
|
|
319
|
+
[servicePrefs[index - 1], servicePrefs[index]] = [servicePrefs[index], servicePrefs[index - 1]];
|
|
320
|
+
|
|
321
|
+
this.preferences = {
|
|
322
|
+
...this.preferences,
|
|
323
|
+
[this.selectedService]: servicePrefs
|
|
324
|
+
};
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
moveProviderDown(index) {
|
|
328
|
+
const servicePrefs = this.preferences[this.selectedService] || [];
|
|
329
|
+
if (index === servicePrefs.length - 1) return;
|
|
330
|
+
|
|
331
|
+
const newPrefs = [...servicePrefs];
|
|
332
|
+
[newPrefs[index], newPrefs[index + 1]] = [newPrefs[index + 1], newPrefs[index]];
|
|
333
|
+
|
|
334
|
+
this.preferences = {
|
|
335
|
+
...this.preferences,
|
|
336
|
+
[this.selectedService]: newPrefs
|
|
337
|
+
};
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
removeProvider(index) {
|
|
341
|
+
const servicePrefs = [...(this.preferences[this.selectedService] || [])];
|
|
342
|
+
servicePrefs.splice(index, 1);
|
|
343
|
+
|
|
344
|
+
this.preferences = {
|
|
345
|
+
...this.preferences,
|
|
346
|
+
[this.selectedService]: servicePrefs
|
|
347
|
+
};
|
|
348
|
+
}
|
|
349
|
+
|
|
350
|
+
updateProviderModel(index, newModel) {
|
|
351
|
+
const servicePrefs = [...(this.preferences[this.selectedService] || [])];
|
|
352
|
+
if (servicePrefs[index]) {
|
|
353
|
+
servicePrefs[index] = [servicePrefs[index][0], newModel];
|
|
354
|
+
|
|
355
|
+
this.preferences = {
|
|
356
|
+
...this.preferences,
|
|
357
|
+
[this.selectedService]: servicePrefs
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
addProvider() {
|
|
363
|
+
const providerSelect = this.shadowRoot.querySelector('.add-provider-select');
|
|
364
|
+
const modelSelect = this.shadowRoot.querySelector('.add-model-select');
|
|
365
|
+
|
|
366
|
+
const provider = providerSelect.value;
|
|
367
|
+
const model = modelSelect.value;
|
|
368
|
+
|
|
369
|
+
if (!provider || !model) return;
|
|
370
|
+
|
|
371
|
+
const servicePrefs = [...(this.preferences[this.selectedService] || [])];
|
|
372
|
+
|
|
373
|
+
// Check if this provider/model combination already exists
|
|
374
|
+
const exists = servicePrefs.some(([p, m]) => p === provider && m === model);
|
|
375
|
+
if (exists) return;
|
|
376
|
+
|
|
377
|
+
servicePrefs.push([provider, model]);
|
|
378
|
+
|
|
379
|
+
this.preferences = {
|
|
380
|
+
...this.preferences,
|
|
381
|
+
[this.selectedService]: servicePrefs
|
|
382
|
+
};
|
|
383
|
+
|
|
384
|
+
// Reset selects
|
|
385
|
+
providerSelect.value = '';
|
|
386
|
+
modelSelect.value = '';
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
async savePreferences() {
|
|
390
|
+
this.saving = true;
|
|
391
|
+
try {
|
|
392
|
+
const response = await fetch('/settings_v2', {
|
|
393
|
+
method: 'POST',
|
|
394
|
+
headers: {
|
|
395
|
+
'Content-Type': 'application/json'
|
|
396
|
+
},
|
|
397
|
+
body: JSON.stringify(this.preferences)
|
|
398
|
+
});
|
|
399
|
+
|
|
400
|
+
if (response.ok) {
|
|
401
|
+
this.showMessage('Preferences saved successfully!', 'success');
|
|
402
|
+
} else {
|
|
403
|
+
const error = await response.text();
|
|
404
|
+
this.showMessage(`Error saving preferences: ${error}`, 'error');
|
|
405
|
+
}
|
|
406
|
+
} catch (error) {
|
|
407
|
+
this.showMessage(`Error saving preferences: ${error.message}`, 'error');
|
|
408
|
+
} finally {
|
|
409
|
+
this.saving = false;
|
|
410
|
+
}
|
|
411
|
+
}
|
|
412
|
+
|
|
413
|
+
async migrateFromOld() {
|
|
414
|
+
try {
|
|
415
|
+
const response = await fetch('/migrate_settings', {
|
|
416
|
+
method: 'POST'
|
|
417
|
+
});
|
|
418
|
+
|
|
419
|
+
if (response.ok) {
|
|
420
|
+
const result = await response.json();
|
|
421
|
+
this.showMessage(result.message, 'success');
|
|
422
|
+
await this.fetchPreferences();
|
|
423
|
+
} else {
|
|
424
|
+
const error = await response.text();
|
|
425
|
+
this.showMessage(`Migration failed: ${error}`, 'error');
|
|
426
|
+
}
|
|
427
|
+
} catch (error) {
|
|
428
|
+
this.showMessage(`Migration failed: ${error.message}`, 'error');
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
showMessage(message, type) {
|
|
433
|
+
// Simple message display - could be enhanced with a proper notification system
|
|
434
|
+
const messageEl = document.createElement('div');
|
|
435
|
+
messageEl.className = type;
|
|
436
|
+
messageEl.textContent = message;
|
|
437
|
+
|
|
438
|
+
const container = this.shadowRoot.querySelector('.preferences-v2-container');
|
|
439
|
+
container.insertBefore(messageEl, container.firstChild);
|
|
440
|
+
|
|
441
|
+
setTimeout(() => {
|
|
442
|
+
messageEl.remove();
|
|
443
|
+
}, 5000);
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
getAvailableProviders() {
|
|
447
|
+
if (!this.selectedService || !this.serviceModels[this.selectedService]) {
|
|
448
|
+
return [];
|
|
449
|
+
}
|
|
450
|
+
return Object.keys(this.serviceModels[this.selectedService]);
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
getAvailableModels(provider) {
|
|
454
|
+
if (!this.selectedService || !this.serviceModels[this.selectedService] || !provider) {
|
|
455
|
+
return [];
|
|
456
|
+
}
|
|
457
|
+
return this.serviceModels[this.selectedService][provider] || [];
|
|
458
|
+
}
|
|
459
|
+
|
|
460
|
+
_render() {
|
|
461
|
+
if (this.loading) {
|
|
462
|
+
return html`<div class="loading">Loading preferences...</div>`;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
const currentServicePrefs = this.preferences[this.selectedService] || [];
|
|
466
|
+
const availableProviders = this.getAvailableProviders();
|
|
467
|
+
|
|
468
|
+
return html`
|
|
469
|
+
<div class="preferences-v2-container">
|
|
470
|
+
<div class="section">
|
|
471
|
+
<h3>
|
|
472
|
+
<span class="material-icons">tune</span>
|
|
473
|
+
Model Preferences V2 (Ordered Fallback)
|
|
474
|
+
</h3>
|
|
475
|
+
|
|
476
|
+
<div class="service-selector">
|
|
477
|
+
<label>Service:</label>
|
|
478
|
+
<select @change=${this.handleServiceChange} .value=${this.selectedService}>
|
|
479
|
+
${this.availableServices.map(service => html`
|
|
480
|
+
<option value="${service}">${service}</option>
|
|
481
|
+
`)}
|
|
482
|
+
</select>
|
|
483
|
+
</div>
|
|
484
|
+
|
|
485
|
+
${this.selectedService ? html`
|
|
486
|
+
<div class="provider-list">
|
|
487
|
+
${currentServicePrefs.map((providerModel, index) => {
|
|
488
|
+
const [provider, model] = providerModel;
|
|
489
|
+
const availableModels = this.getAvailableModels(provider);
|
|
490
|
+
|
|
491
|
+
return html`
|
|
492
|
+
<div class="provider-item">
|
|
493
|
+
<span class="drag-handle material-icons">drag_indicator</span>
|
|
494
|
+
|
|
495
|
+
<div class="provider-info">
|
|
496
|
+
<span class="provider-name">${provider}</span>
|
|
497
|
+
<select
|
|
498
|
+
class="model-select"
|
|
499
|
+
.value=${model}
|
|
500
|
+
@change=${(e) => this.updateProviderModel(index, e.target.value)}
|
|
501
|
+
>
|
|
502
|
+
${availableModels.map(availableModel => html`
|
|
503
|
+
<option value="${availableModel}" ?selected=${availableModel === model}>
|
|
504
|
+
${availableModel}
|
|
505
|
+
</option>
|
|
506
|
+
`)}
|
|
507
|
+
</select>
|
|
508
|
+
</div>
|
|
509
|
+
|
|
510
|
+
<div class="order-controls">
|
|
511
|
+
<button
|
|
512
|
+
class="order-btn"
|
|
513
|
+
?disabled=${index === 0}
|
|
514
|
+
@click=${() => this.moveProviderUp(index)}
|
|
515
|
+
>↑</button>
|
|
516
|
+
<button
|
|
517
|
+
class="order-btn"
|
|
518
|
+
?disabled=${index === currentServicePrefs.length - 1}
|
|
519
|
+
@click=${() => this.moveProviderDown(index)}
|
|
520
|
+
>↓</button>
|
|
521
|
+
</div>
|
|
522
|
+
|
|
523
|
+
<button class="remove-btn" @click=${() => this.removeProvider(index)}>
|
|
524
|
+
<span class="material-icons">delete</span>
|
|
525
|
+
</button>
|
|
526
|
+
</div>
|
|
527
|
+
`;
|
|
528
|
+
})}
|
|
529
|
+
</div>
|
|
530
|
+
|
|
531
|
+
<div class="add-provider">
|
|
532
|
+
<label>Add Provider:</label>
|
|
533
|
+
<select class="add-provider-select">
|
|
534
|
+
<option value="">Select Provider</option>
|
|
535
|
+
${availableProviders.map(provider => html`
|
|
536
|
+
<option value="${provider}">${provider}</option>
|
|
537
|
+
`)}
|
|
538
|
+
</select>
|
|
539
|
+
|
|
540
|
+
<label>Model:</label>
|
|
541
|
+
<select class="add-model-select">
|
|
542
|
+
<option value="">Select Model</option>
|
|
543
|
+
</select>
|
|
544
|
+
|
|
545
|
+
<button class="add-btn" @click=${this.addProvider}>Add</button>
|
|
546
|
+
</div>
|
|
547
|
+
` : html`<p>Select a service to configure preferences.</p>`}
|
|
548
|
+
</div>
|
|
549
|
+
|
|
550
|
+
<div class="actions">
|
|
551
|
+
<button class="migrate-btn" @click=${this.migrateFromOld}>
|
|
552
|
+
Migrate from Old Format
|
|
553
|
+
</button>
|
|
554
|
+
<button
|
|
555
|
+
class="save-btn"
|
|
556
|
+
?disabled=${this.saving}
|
|
557
|
+
@click=${this.savePreferences}
|
|
558
|
+
>
|
|
559
|
+
${this.saving ? 'Saving...' : 'Save Preferences'}
|
|
560
|
+
</button>
|
|
561
|
+
</div>
|
|
562
|
+
</div>
|
|
563
|
+
`;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
updated(changedProperties) {
|
|
567
|
+
super.updated(changedProperties);
|
|
568
|
+
|
|
569
|
+
// Update model dropdown when provider changes
|
|
570
|
+
if (changedProperties.has('selectedService')) {
|
|
571
|
+
const providerSelect = this.shadowRoot.querySelector('.add-provider-select');
|
|
572
|
+
const modelSelect = this.shadowRoot.querySelector('.add-model-select');
|
|
573
|
+
|
|
574
|
+
if (providerSelect && modelSelect) {
|
|
575
|
+
providerSelect.addEventListener('change', (e) => {
|
|
576
|
+
const selectedProvider = e.target.value;
|
|
577
|
+
const availableModels = this.getAvailableModels(selectedProvider);
|
|
578
|
+
|
|
579
|
+
modelSelect.innerHTML = '<option value="">Select Model</option>' +
|
|
580
|
+
availableModels.map(model => `<option value="${model}">${model}</option>`).join('');
|
|
581
|
+
});
|
|
582
|
+
}
|
|
583
|
+
}
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
customElements.define('model-preferences-v2', ModelPreferencesV2);
|
|
@@ -20,7 +20,7 @@
|
|
|
20
20
|
<script type="module" src="/admin/static/js/persona-editor.js"></script>
|
|
21
21
|
<script type="module" src="/admin/static/js/plugin-toggle.js"></script>
|
|
22
22
|
<script type="module" src="/admin/static/js/agent-editor.js"></script>
|
|
23
|
-
<script type="module" src="/admin/static/js/model-preferences.js"></script>
|
|
23
|
+
<script type="module" src="/admin/static/js/model-preferences-v2.js"></script>
|
|
24
24
|
<script type="module" src="/admin/static/js/plugin-manager.js"></script>
|
|
25
25
|
<script type="module" src="/admin/static/js/server-control.js"></script>
|
|
26
26
|
<script type="module" src="/admin/static/js/about-info.js"></script>
|
|
@@ -119,11 +119,11 @@
|
|
|
119
119
|
|
|
120
120
|
<details>
|
|
121
121
|
<summary>
|
|
122
|
-
<span class="material-icons">
|
|
122
|
+
<span class="material-icons">tune</span>
|
|
123
123
|
<span>Model Preferences</span>
|
|
124
124
|
</summary>
|
|
125
125
|
<div class="details-content">
|
|
126
|
-
<model-preferences theme="dark" scope="local"></model-preferences>
|
|
126
|
+
<model-preferences-v2 theme="dark" scope="local"></model-preferences-v2>
|
|
127
127
|
</div>
|
|
128
128
|
</details>
|
|
129
129
|
</details>
|
|
@@ -367,4 +367,4 @@ document.addEventListener('DOMContentLoaded', function() {
|
|
|
367
367
|
{% block footer %}
|
|
368
368
|
{% endblock %}
|
|
369
369
|
</body>
|
|
370
|
-
</html>
|
|
370
|
+
</html>
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
6
|
+
<title>Model Preferences V2</title>
|
|
7
|
+
<link rel="stylesheet" href="/admin/static/css/reset.css">
|
|
8
|
+
<link rel="stylesheet" href="/admin/static/css/default.css">
|
|
9
|
+
<link rel="stylesheet" href="/admin/static/css/admin-custom.css">
|
|
10
|
+
<link rel="stylesheet" href="https://fonts.googleapis.com/icon?family=Material+Icons">
|
|
11
|
+
<script type="module" src="/admin/static/js/model-preferences-v2.js"></script>
|
|
12
|
+
</head>
|
|
13
|
+
<body>
|
|
14
|
+
<header class="admin-header">
|
|
15
|
+
<a href="/admin" class="logo">
|
|
16
|
+
<img src="/imgs/logo.png" class="logo"/>
|
|
17
|
+
</a>
|
|
18
|
+
<nav>
|
|
19
|
+
<a href="/admin">← Back to Admin</a>
|
|
20
|
+
</nav>
|
|
21
|
+
</header>
|
|
22
|
+
|
|
23
|
+
<div class="admin-container">
|
|
24
|
+
<model-preferences-v2 theme="dark" scope="local"></model-preferences-v2>
|
|
25
|
+
</div>
|
|
26
|
+
</body>
|
|
27
|
+
</html>
|
|
@@ -178,7 +178,7 @@ async def middleware(request: Request, call_next):
|
|
|
178
178
|
filename = path_parts[-1]
|
|
179
179
|
print(f"Checking for static file: {plugin_name} {static_part} {filename}")
|
|
180
180
|
if static_part == 'static':
|
|
181
|
-
if filename.endswith('.js') or filename.endswith('.css') or filename.endswith('.png') or filename.endswith('.mp4'):
|
|
181
|
+
if filename.endswith('.js') or filename.endswith('.css') or filename.endswith('.png') or filename.endswith('.mp4') or filename.endswith('.gif'):
|
|
182
182
|
print('Static file requested:', filename)
|
|
183
183
|
return await call_next(request)
|
|
184
184
|
except Exception as e:
|
|
@@ -11,6 +11,12 @@ import sys
|
|
|
11
11
|
import nanoid
|
|
12
12
|
from termcolor import colored
|
|
13
13
|
|
|
14
|
+
# Import the new v2 preferences system with try/except for backward compatibility
|
|
15
|
+
try:
|
|
16
|
+
from .model_preferences_v2 import ModelPreferencesV2
|
|
17
|
+
except ImportError:
|
|
18
|
+
ModelPreferencesV2 = None
|
|
19
|
+
|
|
14
20
|
class ProviderManager:
|
|
15
21
|
|
|
16
22
|
def __init__(self):
|
|
@@ -71,6 +77,16 @@ class ProviderManager:
|
|
|
71
77
|
|
|
72
78
|
need_model = await uses_models(name)
|
|
73
79
|
|
|
80
|
+
# DEBUG: Check what we have for model selection
|
|
81
|
+
print(f"\n=== MODEL SELECTION DEBUG for {name} ===")
|
|
82
|
+
print(f"args[0]: {args[0] if len(args) > 0 else 'N/A'}")
|
|
83
|
+
print(f"kwargs.get('model'): {kwargs.get('model', 'N/A')}")
|
|
84
|
+
if context and hasattr(context, 'agent') and context.agent:
|
|
85
|
+
print(f"Agent service_models: {context.agent.get('service_models', 'N/A')}")
|
|
86
|
+
else:
|
|
87
|
+
print("No agent context or service_models")
|
|
88
|
+
print("=== END DEBUG ===")
|
|
89
|
+
|
|
74
90
|
if (len(args) > 0 and args[0] is None) and not 'model' in kwargs or ('model' in kwargs and kwargs['model'] is None):
|
|
75
91
|
print("No model specified, checking service_models")
|
|
76
92
|
if context is not None and context.agent is not None and 'service_models' in context.agent:
|
|
@@ -86,6 +102,36 @@ class ProviderManager:
|
|
|
86
102
|
else:
|
|
87
103
|
print("did not find service_models in agent")
|
|
88
104
|
print('context.agent:', context.agent)
|
|
105
|
+
|
|
106
|
+
# NEW V2 PREFERENCES LOGIC - Only as fallback when no agent-specific model
|
|
107
|
+
if ModelPreferencesV2 is not None:
|
|
108
|
+
try:
|
|
109
|
+
print("No agent-specific model found, trying V2 system preferences...")
|
|
110
|
+
prefs_manager = ModelPreferencesV2()
|
|
111
|
+
ordered_providers = prefs_manager.get_ordered_providers_for_service(name)
|
|
112
|
+
|
|
113
|
+
if ordered_providers:
|
|
114
|
+
print(f"Found V2 preferences for {name}: {ordered_providers}")
|
|
115
|
+
|
|
116
|
+
for provider_name, model_name in ordered_providers:
|
|
117
|
+
# Check if this provider is available for this function
|
|
118
|
+
if name in self.functions:
|
|
119
|
+
for func_info in self.functions[name]:
|
|
120
|
+
if func_info['provider'] == provider_name:
|
|
121
|
+
try:
|
|
122
|
+
print(f"Trying V2 provider {provider_name} with model {model_name}")
|
|
123
|
+
# Set the model as first argument if needed
|
|
124
|
+
if len(args) > 0 and (args[0] is None or not args[0]):
|
|
125
|
+
args = (model_name, *args[1:])
|
|
126
|
+
elif 'model' not in kwargs:
|
|
127
|
+
kwargs['model'] = model_name
|
|
128
|
+
|
|
129
|
+
return await func_info['implementation'](*args, **kwargs)
|
|
130
|
+
except Exception as e:
|
|
131
|
+
print(f"V2 provider {provider_name} failed: {e}, trying next...")
|
|
132
|
+
continue
|
|
133
|
+
except Exception as e:
|
|
134
|
+
print(f"V2 preferences failed: {e}, continuing with existing logic")
|
|
89
135
|
else:
|
|
90
136
|
print("Found possible model in zeroth arg:")
|
|
91
137
|
if len(args) > 0:
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import json
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
from typing import Dict, List, Tuple, Optional
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
|
|
7
|
+
class ModelPreferencesV2:
|
|
8
|
+
"""New model preferences system supporting ordered provider/model pairs for fallback selection."""
|
|
9
|
+
|
|
10
|
+
def __init__(self):
|
|
11
|
+
# Use current working directory for data (supports multiple installations)
|
|
12
|
+
self.data_dir = Path.cwd() / 'data'
|
|
13
|
+
self.preferences_file = self.data_dir / 'preferred_models_v2.json'
|
|
14
|
+
|
|
15
|
+
# Locate template file relative to this script's location
|
|
16
|
+
script_dir = Path(__file__).parent.parent.parent / 'coreplugins' / 'admin'
|
|
17
|
+
self.template_file = script_dir / 'default_preferred_models.json'
|
|
18
|
+
|
|
19
|
+
def ensure_preferences_exist(self) -> None:
|
|
20
|
+
"""Copy template preferences file to data directory if it doesn't exist."""
|
|
21
|
+
# Ensure data directory exists
|
|
22
|
+
self.data_dir.mkdir(exist_ok=True)
|
|
23
|
+
|
|
24
|
+
# If preferences file doesn't exist, copy from template
|
|
25
|
+
if not self.preferences_file.exists():
|
|
26
|
+
if self.template_file.exists():
|
|
27
|
+
shutil.copy2(self.template_file, self.preferences_file)
|
|
28
|
+
else:
|
|
29
|
+
# Create minimal default if template doesn't exist
|
|
30
|
+
default_prefs = {
|
|
31
|
+
"stream_chat": [
|
|
32
|
+
["ah_anthropic", "claude-3-5-sonnet-20240620"],
|
|
33
|
+
["ah_openai", "gpt-4o"]
|
|
34
|
+
],
|
|
35
|
+
"text_to_image": [
|
|
36
|
+
["ah_flux", "flux-dev"]
|
|
37
|
+
]
|
|
38
|
+
}
|
|
39
|
+
self.save_preferences(default_prefs)
|
|
40
|
+
|
|
41
|
+
def get_preferences(self) -> Dict[str, List[List[str]]]:
|
|
42
|
+
"""Get preferences in new format: {service: [[provider, model], ...]}"""
|
|
43
|
+
self.ensure_preferences_exist()
|
|
44
|
+
|
|
45
|
+
try:
|
|
46
|
+
with open(self.preferences_file, 'r') as f:
|
|
47
|
+
return json.load(f)
|
|
48
|
+
except (FileNotFoundError, json.JSONDecodeError) as e:
|
|
49
|
+
print(f"Error reading preferences v2: {e}")
|
|
50
|
+
return {}
|
|
51
|
+
|
|
52
|
+
def save_preferences(self, preferences: Dict[str, List[List[str]]]) -> None:
|
|
53
|
+
"""Save preferences in new format."""
|
|
54
|
+
|
|
55
|
+
# Ensure data directory exists before saving
|
|
56
|
+
self.data_dir.mkdir(exist_ok=True)
|
|
57
|
+
|
|
58
|
+
try:
|
|
59
|
+
with open(self.preferences_file, 'w') as f:
|
|
60
|
+
json.dump(preferences, f, indent=2)
|
|
61
|
+
except Exception as e:
|
|
62
|
+
print(f"Error saving preferences v2: {e}")
|
|
63
|
+
raise
|
|
64
|
+
|
|
65
|
+
def get_ordered_providers_for_service(self, service_name: str) -> List[Tuple[str, str]]:
|
|
66
|
+
"""Get ordered list of (provider, model) pairs for a service."""
|
|
67
|
+
preferences = self.get_preferences()
|
|
68
|
+
|
|
69
|
+
if service_name not in preferences:
|
|
70
|
+
return []
|
|
71
|
+
|
|
72
|
+
# Convert list of lists to list of tuples
|
|
73
|
+
return [(provider, model) for provider, model in preferences[service_name]]
|
|
74
|
+
|
|
75
|
+
def migrate_from_old_format(self, old_preferences: List[Dict]) -> Dict[str, List[List[str]]]:
|
|
76
|
+
"""Convert old format preferences to new format.
|
|
77
|
+
|
|
78
|
+
Old format: [{"service_or_command_name": "stream_chat", "flag": "default", "model": "claude-3"}]
|
|
79
|
+
New format: {"stream_chat": [["ah_anthropic", "claude-3.5-sonnet"]]}
|
|
80
|
+
"""
|
|
81
|
+
new_preferences = {}
|
|
82
|
+
|
|
83
|
+
for old_pref in old_preferences:
|
|
84
|
+
service = old_pref.get('service_or_command_name')
|
|
85
|
+
model = old_pref.get('model')
|
|
86
|
+
|
|
87
|
+
if not service or not model:
|
|
88
|
+
continue
|
|
89
|
+
|
|
90
|
+
# For migration, we'll need to guess the provider based on model name
|
|
91
|
+
# This is a best-effort conversion
|
|
92
|
+
provider = self._guess_provider_from_model(model)
|
|
93
|
+
|
|
94
|
+
if service not in new_preferences:
|
|
95
|
+
new_preferences[service] = []
|
|
96
|
+
|
|
97
|
+
# Add if not already present
|
|
98
|
+
provider_model_pair = [provider, model]
|
|
99
|
+
if provider_model_pair not in new_preferences[service]:
|
|
100
|
+
new_preferences[service].append(provider_model_pair)
|
|
101
|
+
|
|
102
|
+
return new_preferences
|
|
103
|
+
|
|
104
|
+
def _guess_provider_from_model(self, model_name: str) -> str:
|
|
105
|
+
"""Best-effort guess of provider based on model name."""
|
|
106
|
+
model_lower = model_name.lower()
|
|
107
|
+
|
|
108
|
+
# Common model name patterns
|
|
109
|
+
if 'claude' in model_lower:
|
|
110
|
+
return 'ah_anthropic'
|
|
111
|
+
elif 'gpt' in model_lower or 'chatgpt' in model_lower:
|
|
112
|
+
return 'ah_openai'
|
|
113
|
+
elif 'gemini' in model_lower:
|
|
114
|
+
return 'mr_gemini'
|
|
115
|
+
elif 'llama' in model_lower:
|
|
116
|
+
return 'ah_together' # or another llama provider
|
|
117
|
+
elif 'flux' in model_lower:
|
|
118
|
+
return 'ah_flux'
|
|
119
|
+
elif 'stable' in model_lower or 'sd' in model_lower:
|
|
120
|
+
return 'ah_stability'
|
|
121
|
+
else:
|
|
122
|
+
# Default fallback
|
|
123
|
+
return 'unknown_provider'
|
|
124
|
+
|
|
125
|
+
def add_preference(self, service: str, provider: str, model: str, position: Optional[int] = None) -> None:
|
|
126
|
+
"""Add a provider/model preference for a service at specified position (or end)."""
|
|
127
|
+
preferences = self.get_preferences()
|
|
128
|
+
|
|
129
|
+
if service not in preferences:
|
|
130
|
+
preferences[service] = []
|
|
131
|
+
|
|
132
|
+
provider_model_pair = [provider, model]
|
|
133
|
+
|
|
134
|
+
# Remove if already exists
|
|
135
|
+
preferences[service] = [pm for pm in preferences[service] if pm != provider_model_pair]
|
|
136
|
+
|
|
137
|
+
# Add at specified position or end
|
|
138
|
+
if position is not None and 0 <= position <= len(preferences[service]):
|
|
139
|
+
preferences[service].insert(position, provider_model_pair)
|
|
140
|
+
else:
|
|
141
|
+
preferences[service].append(provider_model_pair)
|
|
142
|
+
|
|
143
|
+
self.save_preferences(preferences)
|
|
144
|
+
|
|
145
|
+
def remove_preference(self, service: str, provider: str, model: str) -> bool:
|
|
146
|
+
"""Remove a provider/model preference for a service. Returns True if removed."""
|
|
147
|
+
preferences = self.get_preferences()
|
|
148
|
+
|
|
149
|
+
if service not in preferences:
|
|
150
|
+
return False
|
|
151
|
+
|
|
152
|
+
provider_model_pair = [provider, model]
|
|
153
|
+
|
|
154
|
+
if provider_model_pair in preferences[service]:
|
|
155
|
+
preferences[service].remove(provider_model_pair)
|
|
156
|
+
self.save_preferences(preferences)
|
|
157
|
+
return True
|
|
158
|
+
|
|
159
|
+
return False
|
|
160
|
+
|
|
161
|
+
def reorder_preferences(self, service: str, ordered_pairs: List[List[str]]) -> None:
|
|
162
|
+
"""Set the complete ordered list of provider/model pairs for a service."""
|
|
163
|
+
preferences = self.get_preferences()
|
|
164
|
+
preferences[service] = ordered_pairs
|
|
165
|
+
self.save_preferences(preferences)
|
|
@@ -22,10 +22,10 @@ mindroot/coreplugins/admin/plugin_router_fixed.py,sha256=LQAOOi4WdYBP4udOkVciOny
|
|
|
22
22
|
mindroot/coreplugins/admin/plugin_router_new_not_working.py,sha256=IkmVLv9T28s0kz-rdjqdgQQIEvAYic0zjTS0HXvsi9A,5865
|
|
23
23
|
mindroot/coreplugins/admin/plugin_routes.py,sha256=qthJjndFxd_PelKf2OuKXukfv1X-v-O53hDRpNLtqJA,3918
|
|
24
24
|
mindroot/coreplugins/admin/registry_settings_routes.py,sha256=x739599dkE_A5uM1qQkmouo3Yzm28y7cCa2HsIpfi6U,4869
|
|
25
|
-
mindroot/coreplugins/admin/router.py,sha256=
|
|
25
|
+
mindroot/coreplugins/admin/router.py,sha256=MuGRPO-v8B7QkbnOOjiRvmzrXzDS-xFtHyjj_qzJfQ0,5923
|
|
26
26
|
mindroot/coreplugins/admin/server_router.py,sha256=aZ5v7QBcK1LJ2yiciUA4elY26iSGl-8m03L1h3ogvP4,4533
|
|
27
27
|
mindroot/coreplugins/admin/service_models.py,sha256=53Whq4X1Ci3D4FuzG_Eyrmz66LxnyQek-mOYw38qQm8,3355
|
|
28
|
-
mindroot/coreplugins/admin/settings_router.py,sha256=
|
|
28
|
+
mindroot/coreplugins/admin/settings_router.py,sha256=C3zhQuumSvoZXkhsCsQ3H0B3AARtyRLXBo8aiArwWsk,8794
|
|
29
29
|
mindroot/coreplugins/admin/static/logo.png,sha256=YejLvBHILjTPkOBYE2t37BD8sTtXbPmLh1Zo-x8pbok,18260
|
|
30
30
|
mindroot/coreplugins/admin/static/css/admin-custom.css,sha256=GZ2gxqyhzdjm1KBOQpiYl18YOOW39aoPN3Akb9gDgYU,14404
|
|
31
31
|
mindroot/coreplugins/admin/static/css/dark.css,sha256=wOODR6WTWKhx0Cq8GDpY9THWwVC4LJBLc68PpYKIPzA,15597
|
|
@@ -48,6 +48,7 @@ mindroot/coreplugins/admin/static/js/markdown-renderer.js,sha256=SepuejgMmYSRm_o
|
|
|
48
48
|
mindroot/coreplugins/admin/static/js/mcp-manager.js,sha256=o8UlP-E-Lxm4mEKUY-uqOhe-SflxIIVPfHQJqXkqDWo,8786
|
|
49
49
|
mindroot/coreplugins/admin/static/js/mcp-publisher.js,sha256=Ut6wx3nUT5SfZ8TI0SmyuW8P46OJ52fkftscEg8KcKo,24330
|
|
50
50
|
mindroot/coreplugins/admin/static/js/missing-commands.js,sha256=adNF9GWN981_KX7H25eOTw9QKlKIwTAsm8PGGkG5EcE,7949
|
|
51
|
+
mindroot/coreplugins/admin/static/js/model-preferences-v2.js,sha256=qK5kYnMDu61xnflRw3nqOTNEkNHcxhOvQ9t3DNODfEk,15691
|
|
51
52
|
mindroot/coreplugins/admin/static/js/model-preferences.js,sha256=J0G7gcGACaPyslWJO42urf5wbZZsqO0LyPicAu-uV_Y,3365
|
|
52
53
|
mindroot/coreplugins/admin/static/js/notification.js,sha256=296rVCr6MNtzvzdzW3bGiMa231-BnWJtwZZ_sDWX-3c,5633
|
|
53
54
|
mindroot/coreplugins/admin/static/js/persona-editor.js,sha256=bVp-d1y3HPocmVkHpqsVh0HiRUf77vJ43kargj1iPHk,9830
|
|
@@ -435,7 +436,8 @@ mindroot/coreplugins/admin/static/js/lit-html/node/directives/until.js,sha256=j1
|
|
|
435
436
|
mindroot/coreplugins/admin/static/js/lit-html/node/directives/until.js.map,sha256=7xiwSZ7_fGtr5XwW-10Dzs8n9QE2VUfXaZ0Sd6d82L0,6567
|
|
436
437
|
mindroot/coreplugins/admin/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
|
|
437
438
|
mindroot/coreplugins/admin/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
|
|
438
|
-
mindroot/coreplugins/admin/templates/admin.jinja2,sha256=
|
|
439
|
+
mindroot/coreplugins/admin/templates/admin.jinja2,sha256=H_oDqoWWk0Da0Jre67LIKvB3h30fmjcZz2T5knUyz0k,13272
|
|
440
|
+
mindroot/coreplugins/admin/templates/model-preferences-v2.jinja2,sha256=5J3rXYmtp_yMTFCk85SYN03gc4lbidF0Nip6YcqcIW4,891
|
|
439
441
|
mindroot/coreplugins/agent/agent.py,sha256=X-EmtrpEpdfo-iUw9gj7mLveRVzAApsDWPTwMAuv7Ww,20715
|
|
440
442
|
mindroot/coreplugins/agent/cmd_start_example.py,sha256=Mdcd9st6viI6-M7a0-zqkw3IxR9FAxIiZ_8G-tLdIJk,1416
|
|
441
443
|
mindroot/coreplugins/agent/command_parser.py,sha256=dgMqtVLPQWE2BU7iyjqwKGy5Gh74jcZkiy1JDs07t4E,13166
|
|
@@ -1700,7 +1702,7 @@ mindroot/coreplugins/index/static/js/lit-html/node/directives/until.js.map,sha25
|
|
|
1700
1702
|
mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js,sha256=NLe0NJ-6jqjVDUrT_DzmSpREsRaLo1yarzdYcV_5xHY,181
|
|
1701
1703
|
mindroot/coreplugins/index/static/js/lit-html/node/directives/when.js.map,sha256=tOonih_-EaqrunhNGshA9xN--WIVdGikjg8MkVp0itQ,1534
|
|
1702
1704
|
mindroot/coreplugins/jwt_auth/__init__.py,sha256=qFCBnx0oAKTtMSXiPEa7VXOIlWDTU-5CY0XvodgSUlM,79
|
|
1703
|
-
mindroot/coreplugins/jwt_auth/middleware.py,sha256=
|
|
1705
|
+
mindroot/coreplugins/jwt_auth/middleware.py,sha256=aOrKIQMDzLQArlizkGC3aRmUcPQuZRfKFUaceAgIYm8,10880
|
|
1704
1706
|
mindroot/coreplugins/jwt_auth/mod.py,sha256=XqvAwBQVga-foEkziDJnQtISq1NwYcXXuvVRKeewfAQ,2070
|
|
1705
1707
|
mindroot/coreplugins/jwt_auth/role_checks.py,sha256=bruZIIBSOvXNWB1YZ2s5btrbbXNf18w6MdORpJByV60,1555
|
|
1706
1708
|
mindroot/coreplugins/jwt_auth/router.py,sha256=ecXYao_UG33UjQF15Hi-tf_X0eFsqLEldyqGpt7JNSw,1162
|
|
@@ -1839,10 +1841,11 @@ mindroot/lib/plugins/loader_with_l8n.py,sha256=4-kNgbk-GMN8UkCiSjg-29FGDtbml8Hhy
|
|
|
1839
1841
|
mindroot/lib/plugins/manifest.py,sha256=Bquvg3xFg9dWxDFfRpHWei4kc8aWWxFQUP1nLDxIJtw,12078
|
|
1840
1842
|
mindroot/lib/plugins/mapping.py,sha256=B3nxbStLzyYj8CEuEfxWXMbCu_dOtK_zVTbcCBw5mMc,838
|
|
1841
1843
|
mindroot/lib/plugins/paths.py,sha256=vPDjEF9NU1xHMIcnN4_Hf1gjyleyXAAfRcf86WXt4M4,4312
|
|
1842
|
-
mindroot/lib/providers/__init__.py,sha256=
|
|
1844
|
+
mindroot/lib/providers/__init__.py,sha256=CxqC9iaRUTx6q2i4rfLAjGEmjy7t6d86SoyTYCF8zYY,14534
|
|
1843
1845
|
mindroot/lib/providers/commands.py,sha256=wxR9tfwjdu7lbjJZQ1vNOWH-vEdhcpOxkaBf2G9OKv0,735
|
|
1844
1846
|
mindroot/lib/providers/hooks.py,sha256=9cJoJRhZyzFmLBJNTYGaVijA7B6hAm2mJu9I2ij4WMo,359
|
|
1845
1847
|
mindroot/lib/providers/missing.py,sha256=G6ZM25ZEVgsk4uWwgf0GygJ1SWEnstwfw7xsR9QEI_E,2457
|
|
1848
|
+
mindroot/lib/providers/model_preferences_v2.py,sha256=BNqOnnNLDzPCUg34CNfQYOZ_gsJgmgMMePNzBygSMCA,6756
|
|
1846
1849
|
mindroot/lib/providers/services.py,sha256=nTPUbz_KjxAENg4s9ymZnztH4Z7dhBjM6bvSlIxMQQs,598
|
|
1847
1850
|
mindroot/lib/providers/backup/__init__.py,sha256=8iL9fylkE7SaFap4oj9D2eikN_4EJp5IaANiPF75qWM,12186
|
|
1848
1851
|
mindroot/lib/utils/backoff.py,sha256=P059hqyTtYM_unNQP_iHSdYNhWe3Hz6VqbJ_vX19eoI,5520
|
|
@@ -1858,9 +1861,9 @@ mindroot/protocols/services/stream_chat.py,sha256=fMnPfwaB5fdNMBLTEg8BXKAGvrELKH
|
|
|
1858
1861
|
mindroot/registry/__init__.py,sha256=40Xy9bmPHsgdIrOzbtBGzf4XMqXVi9P8oZTJhn0r654,151
|
|
1859
1862
|
mindroot/registry/component_manager.py,sha256=WZFNPg4SNvpqsM5NFiC2DpgmrJQCyR9cNhrCBpp30Qk,995
|
|
1860
1863
|
mindroot/registry/data_access.py,sha256=81In5TwETpaqnnY1_-tBQM7rfWvUxZUZkG7lEelRUfU,5321
|
|
1861
|
-
mindroot-9.
|
|
1862
|
-
mindroot-9.
|
|
1863
|
-
mindroot-9.
|
|
1864
|
-
mindroot-9.
|
|
1865
|
-
mindroot-9.
|
|
1866
|
-
mindroot-9.
|
|
1864
|
+
mindroot-9.8.0.dist-info/licenses/LICENSE,sha256=8plAmZh8y9ccuuqFFz4kp7G-cO_qsPgAOoHNvabSB4U,1070
|
|
1865
|
+
mindroot-9.8.0.dist-info/METADATA,sha256=SKtDN2fVOx-GsuOwXDfguiNx8fFBdNKY8q2m9EaheL4,1034
|
|
1866
|
+
mindroot-9.8.0.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
1867
|
+
mindroot-9.8.0.dist-info/entry_points.txt,sha256=0bpyjMccLttx6VcjDp6zfJPN0Kk0rffor6IdIbP0j4c,50
|
|
1868
|
+
mindroot-9.8.0.dist-info/top_level.txt,sha256=gwKm7DmNjhdrCJTYCrxa9Szne4lLpCtrEBltfsX-Mm8,9
|
|
1869
|
+
mindroot-9.8.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|