lemonade-sdk 8.1.11__py3-none-any.whl → 8.2.2__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.
Potentially problematic release.
This version of lemonade-sdk might be problematic. Click here for more details.
- lemonade/cache.py +6 -1
- lemonade/common/status.py +4 -4
- lemonade/common/system_info.py +0 -26
- lemonade/tools/accuracy.py +143 -48
- lemonade/tools/adapter.py +6 -1
- lemonade/tools/bench.py +26 -8
- lemonade/tools/flm/utils.py +70 -22
- lemonade/tools/huggingface/bench.py +6 -1
- lemonade/tools/llamacpp/bench.py +146 -27
- lemonade/tools/llamacpp/load.py +30 -2
- lemonade/tools/llamacpp/utils.py +317 -21
- lemonade/tools/oga/bench.py +5 -26
- lemonade/tools/oga/load.py +49 -123
- lemonade/tools/oga/migration.py +403 -0
- lemonade/tools/report/table.py +76 -8
- lemonade/tools/server/flm.py +2 -6
- lemonade/tools/server/llamacpp.py +43 -2
- lemonade/tools/server/serve.py +354 -18
- lemonade/tools/server/static/js/chat.js +15 -77
- lemonade/tools/server/static/js/model-settings.js +24 -3
- lemonade/tools/server/static/js/models.js +440 -37
- lemonade/tools/server/static/js/shared.js +61 -8
- lemonade/tools/server/static/logs.html +157 -13
- lemonade/tools/server/static/styles.css +204 -0
- lemonade/tools/server/static/webapp.html +39 -1
- lemonade/version.py +1 -1
- lemonade_install/install.py +33 -579
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/METADATA +6 -4
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/RECORD +38 -37
- lemonade_server/cli.py +10 -0
- lemonade_server/model_manager.py +172 -11
- lemonade_server/pydantic_models.py +3 -0
- lemonade_server/server_models.json +102 -66
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/top_level.txt +0 -0
|
@@ -54,23 +54,53 @@ function renderMarkdown(text) {
|
|
|
54
54
|
|
|
55
55
|
// Display an error message in the banner
|
|
56
56
|
function showErrorBanner(msg) {
|
|
57
|
+
showBanner(msg, 'error');
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
// Display a banner with a specific type (error, warning, success)
|
|
61
|
+
function showBanner(msg, type = 'error') {
|
|
57
62
|
// If DOM isn't ready, wait for it
|
|
58
63
|
if (document.readyState === 'loading') {
|
|
59
64
|
document.addEventListener('DOMContentLoaded', () => {
|
|
60
|
-
|
|
65
|
+
showBanner(msg, type);
|
|
61
66
|
});
|
|
62
67
|
return;
|
|
63
68
|
}
|
|
64
|
-
|
|
69
|
+
|
|
65
70
|
const banner = document.getElementById('error-banner');
|
|
66
71
|
if (!banner) return;
|
|
67
72
|
const msgEl = document.getElementById('error-banner-msg');
|
|
68
|
-
const
|
|
73
|
+
const logsUrl = window.location.origin + '/static/logs.html';
|
|
74
|
+
|
|
75
|
+
// Determine the full message and styling based on type
|
|
76
|
+
let fullMsg = msg;
|
|
77
|
+
let backgroundColor, color;
|
|
78
|
+
|
|
79
|
+
switch(type) {
|
|
80
|
+
case 'success':
|
|
81
|
+
backgroundColor = '#27ae60'; // green
|
|
82
|
+
color = '#ffffff';
|
|
83
|
+
break;
|
|
84
|
+
case 'warning':
|
|
85
|
+
backgroundColor = '#8d5803ff'; // yellow/orange
|
|
86
|
+
color = '#ffffff';
|
|
87
|
+
break;
|
|
88
|
+
case 'error':
|
|
89
|
+
default:
|
|
90
|
+
backgroundColor = '#b10819ff'; // red
|
|
91
|
+
color = '#ffffff';
|
|
92
|
+
fullMsg = `${msg}<br>Check the Lemonade Server logs <a href="${logsUrl}" target="_blank" rel="noopener noreferrer">on the browser</a> or via the system tray app for more information.`;
|
|
93
|
+
break;
|
|
94
|
+
}
|
|
95
|
+
|
|
69
96
|
if (msgEl) {
|
|
70
|
-
msgEl.
|
|
97
|
+
msgEl.innerHTML = fullMsg;
|
|
71
98
|
} else {
|
|
72
|
-
banner.
|
|
99
|
+
banner.innerHTML = fullMsg;
|
|
73
100
|
}
|
|
101
|
+
|
|
102
|
+
banner.style.backgroundColor = backgroundColor;
|
|
103
|
+
banner.style.color = color;
|
|
74
104
|
banner.style.display = 'flex';
|
|
75
105
|
}
|
|
76
106
|
|
|
@@ -177,6 +207,11 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
177
207
|
// Store original states for restoration on error
|
|
178
208
|
const originalStatusText = document.getElementById('model-status-text')?.textContent || '';
|
|
179
209
|
|
|
210
|
+
// Track this load operation as active to prevent polling interference
|
|
211
|
+
if (window.activeOperations) {
|
|
212
|
+
window.activeOperations.add(modelId);
|
|
213
|
+
}
|
|
214
|
+
|
|
180
215
|
try {
|
|
181
216
|
// Update load button if provided
|
|
182
217
|
if (loadButton) {
|
|
@@ -189,7 +224,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
189
224
|
|
|
190
225
|
// Update chat dropdown and send button to show loading state
|
|
191
226
|
const modelSelect = document.getElementById('model-select');
|
|
192
|
-
const sendBtn = document.getElementById('
|
|
227
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
193
228
|
if (modelSelect && sendBtn) {
|
|
194
229
|
// Ensure the model exists in the dropdown options
|
|
195
230
|
let modelOption = modelSelect.querySelector(`option[value="${modelId}"]`);
|
|
@@ -222,10 +257,18 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
222
257
|
}
|
|
223
258
|
|
|
224
259
|
// Make the API call to load the model
|
|
260
|
+
// Include mmproj if the model has it defined
|
|
261
|
+
const loadPayload = { model_name: modelId };
|
|
262
|
+
const allModels = window.SERVER_MODELS || {};
|
|
263
|
+
const modelData = allModels[modelId];
|
|
264
|
+
if (modelData && modelData.mmproj) {
|
|
265
|
+
loadPayload.mmproj = modelData.mmproj;
|
|
266
|
+
}
|
|
267
|
+
|
|
225
268
|
await httpRequest(getServerBaseUrl() + '/api/v1/load', {
|
|
226
269
|
method: 'POST',
|
|
227
270
|
headers: { 'Content-Type': 'application/json' },
|
|
228
|
-
body: JSON.stringify(
|
|
271
|
+
body: JSON.stringify(loadPayload)
|
|
229
272
|
});
|
|
230
273
|
|
|
231
274
|
// Update model status indicator after successful load
|
|
@@ -273,6 +316,11 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
273
316
|
onSuccess(modelId);
|
|
274
317
|
}
|
|
275
318
|
|
|
319
|
+
// Remove from active operations on success
|
|
320
|
+
if (window.activeOperations) {
|
|
321
|
+
window.activeOperations.delete(modelId);
|
|
322
|
+
}
|
|
323
|
+
|
|
276
324
|
return true;
|
|
277
325
|
|
|
278
326
|
} catch (error) {
|
|
@@ -290,7 +338,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
290
338
|
|
|
291
339
|
// Reset chat controls on error
|
|
292
340
|
const modelSelect = document.getElementById('model-select');
|
|
293
|
-
const sendBtn = document.getElementById('
|
|
341
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
294
342
|
if (modelSelect && sendBtn) {
|
|
295
343
|
modelSelect.disabled = false;
|
|
296
344
|
sendBtn.disabled = false;
|
|
@@ -320,6 +368,11 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
320
368
|
// Always show error banner to ensure user sees the error
|
|
321
369
|
showErrorBanner('Failed to load model: ' + error.message);
|
|
322
370
|
|
|
371
|
+
// Remove from active operations on error too
|
|
372
|
+
if (window.activeOperations) {
|
|
373
|
+
window.activeOperations.delete(modelId);
|
|
374
|
+
}
|
|
375
|
+
|
|
323
376
|
return false;
|
|
324
377
|
}
|
|
325
378
|
}
|
|
@@ -22,26 +22,170 @@
|
|
|
22
22
|
<body>
|
|
23
23
|
<div id="log-container"></div>
|
|
24
24
|
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
25
|
+
<script>
|
|
26
|
+
/**
|
|
27
|
+
* LogStreamer - Backwards-compatible log streaming client
|
|
28
|
+
* Supports both WebSocket (Python server) and SSE (C++ server)
|
|
29
|
+
* Tries WebSocket first, falls back to SSE if unavailable
|
|
30
|
+
*/
|
|
31
|
+
class LogStreamer {
|
|
32
|
+
constructor(baseUrl, onMessage, onError = null) {
|
|
33
|
+
this.baseUrl = baseUrl;
|
|
34
|
+
this.onMessage = onMessage;
|
|
35
|
+
this.onError = onError;
|
|
36
|
+
this.connection = null;
|
|
37
|
+
this.type = null; // 'websocket' or 'sse'
|
|
28
38
|
}
|
|
29
|
-
|
|
30
|
-
|
|
39
|
+
|
|
40
|
+
async connect() {
|
|
41
|
+
// Try WebSocket first (Python server)
|
|
42
|
+
try {
|
|
43
|
+
await this.connectWebSocket();
|
|
44
|
+
console.log('[LogStreamer] Connected via WebSocket');
|
|
45
|
+
return;
|
|
46
|
+
} catch (wsError) {
|
|
47
|
+
console.log('[LogStreamer] WebSocket failed, trying SSE...', wsError);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// Fallback to SSE (C++ server)
|
|
51
|
+
try {
|
|
52
|
+
this.connectSSE();
|
|
53
|
+
console.log('[LogStreamer] Connected via SSE');
|
|
54
|
+
} catch (sseError) {
|
|
55
|
+
console.error('[LogStreamer] Both WebSocket and SSE failed', sseError);
|
|
56
|
+
if (this.onError) {
|
|
57
|
+
this.onError(new Error('Unable to connect to log stream'));
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
connectWebSocket() {
|
|
63
|
+
return new Promise((resolve, reject) => {
|
|
64
|
+
const wsUrl = this.baseUrl
|
|
65
|
+
.replace('http://', 'ws://')
|
|
66
|
+
.replace('https://', 'wss://') + '/api/v1/logs/ws';
|
|
67
|
+
|
|
68
|
+
const ws = new WebSocket(wsUrl);
|
|
69
|
+
let connectionTimeout = setTimeout(() => {
|
|
70
|
+
reject(new Error('WebSocket connection timeout'));
|
|
71
|
+
ws.close();
|
|
72
|
+
}, 3000); // 3 second timeout
|
|
73
|
+
|
|
74
|
+
ws.onopen = () => {
|
|
75
|
+
clearTimeout(connectionTimeout);
|
|
76
|
+
this.connection = ws;
|
|
77
|
+
this.type = 'websocket';
|
|
78
|
+
resolve();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
ws.onmessage = (event) => {
|
|
82
|
+
this.onMessage(event.data);
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
ws.onerror = (error) => {
|
|
86
|
+
clearTimeout(connectionTimeout);
|
|
87
|
+
if (this.onError && this.connection) {
|
|
88
|
+
this.onError(error);
|
|
89
|
+
}
|
|
90
|
+
reject(error);
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
ws.onclose = () => {
|
|
94
|
+
if (this.type === 'websocket') {
|
|
95
|
+
console.log('[LogStreamer] WebSocket closed');
|
|
96
|
+
if (this.onError) {
|
|
97
|
+
this.onError(new Error('WebSocket connection closed'));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
};
|
|
101
|
+
});
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
connectSSE() {
|
|
105
|
+
const sseUrl = this.baseUrl + '/api/v1/logs/stream';
|
|
106
|
+
const eventSource = new EventSource(sseUrl);
|
|
107
|
+
|
|
108
|
+
eventSource.onopen = () => {
|
|
109
|
+
this.connection = eventSource;
|
|
110
|
+
this.type = 'sse';
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
eventSource.onmessage = (event) => {
|
|
114
|
+
this.onMessage(event.data);
|
|
115
|
+
};
|
|
116
|
+
|
|
117
|
+
eventSource.onerror = (error) => {
|
|
118
|
+
console.error('[LogStreamer] SSE error:', error);
|
|
119
|
+
if (this.onError) {
|
|
120
|
+
this.onError(error);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
disconnect() {
|
|
126
|
+
if (!this.connection) return;
|
|
127
|
+
|
|
128
|
+
if (this.type === 'websocket') {
|
|
129
|
+
this.connection.close();
|
|
130
|
+
} else if (this.type === 'sse') {
|
|
131
|
+
this.connection.close();
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
this.connection = null;
|
|
135
|
+
this.type = null;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
getConnectionType() {
|
|
139
|
+
return this.type;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
31
142
|
|
|
32
|
-
|
|
143
|
+
// Utility functions
|
|
144
|
+
function stripAnsi(str) {
|
|
145
|
+
return str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, '');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function isNearBottom() {
|
|
149
|
+
const threshold = 50; // px from bottom
|
|
150
|
+
return logContainer.scrollTop + logContainer.clientHeight >= logContainer.scrollHeight - threshold;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// Initialize log streaming
|
|
154
|
+
const logContainer = document.getElementById("log-container");
|
|
155
|
+
const baseUrl = `${location.protocol}//${location.host}`;
|
|
156
|
+
|
|
157
|
+
const logStreamer = new LogStreamer(
|
|
158
|
+
baseUrl,
|
|
159
|
+
(logLine) => {
|
|
160
|
+
// Handle incoming log line
|
|
33
161
|
const line = document.createElement("div");
|
|
34
|
-
line.textContent = stripAnsi(
|
|
162
|
+
line.textContent = stripAnsi(logLine);
|
|
35
163
|
logContainer.appendChild(line);
|
|
36
|
-
logContainer.scrollTop = logContainer.scrollHeight; // auto scroll
|
|
37
|
-
};
|
|
38
164
|
|
|
39
|
-
|
|
165
|
+
// Only autoscroll if the user is already at (or near) the bottom
|
|
166
|
+
if (isNearBottom()) {
|
|
167
|
+
logContainer.scrollTop = logContainer.scrollHeight;
|
|
168
|
+
}
|
|
169
|
+
},
|
|
170
|
+
(error) => {
|
|
171
|
+
// Handle error
|
|
40
172
|
const msg = document.createElement("div");
|
|
41
|
-
msg.textContent =
|
|
173
|
+
msg.textContent = `[Connection error: ${error.message}]`;
|
|
42
174
|
msg.style.color = "red";
|
|
43
175
|
logContainer.appendChild(msg);
|
|
44
|
-
}
|
|
45
|
-
|
|
176
|
+
}
|
|
177
|
+
);
|
|
178
|
+
|
|
179
|
+
// Connect to log stream
|
|
180
|
+
logStreamer.connect();
|
|
181
|
+
|
|
182
|
+
// Show connection type in console
|
|
183
|
+
setTimeout(() => {
|
|
184
|
+
const type = logStreamer.getConnectionType();
|
|
185
|
+
if (type) {
|
|
186
|
+
console.log(`[LogViewer] Connected via ${type === 'websocket' ? 'WebSocket' : 'Server-Sent Events'}`);
|
|
187
|
+
}
|
|
188
|
+
}, 100);
|
|
189
|
+
</script>
|
|
46
190
|
</body>
|
|
47
191
|
</html>
|
|
@@ -1288,6 +1288,62 @@ button:disabled {
|
|
|
1288
1288
|
transform: translateY(-1px);
|
|
1289
1289
|
box-shadow: 0 2px 8px rgba(91, 141, 184, 0.3);
|
|
1290
1290
|
}
|
|
1291
|
+
.checkpoint-input-group {
|
|
1292
|
+
display: flex;
|
|
1293
|
+
align-items: stretch;
|
|
1294
|
+
flex: 1;
|
|
1295
|
+
border-radius: 6px;
|
|
1296
|
+
overflow: hidden;
|
|
1297
|
+
transition: box-shadow 0.2s ease;
|
|
1298
|
+
min-width: 0;
|
|
1299
|
+
}
|
|
1300
|
+
|
|
1301
|
+
.checkpoint-input-group:focus-within {
|
|
1302
|
+
box-shadow: 0 2px 12px rgba(230,184,0,0.25);
|
|
1303
|
+
}
|
|
1304
|
+
|
|
1305
|
+
.checkpoint-input-group #register-checkpoint {
|
|
1306
|
+
border-radius: 6px 0 0 6px;
|
|
1307
|
+
border: 1px solid #d5d5d5;
|
|
1308
|
+
border-right: none;
|
|
1309
|
+
padding: 0.6em 0.8em;
|
|
1310
|
+
font-size: 0.95em;
|
|
1311
|
+
background: #fff;
|
|
1312
|
+
color: #222;
|
|
1313
|
+
flex: 1;
|
|
1314
|
+
transition: border-color 0.2s ease, box-shadow 0.2s ease;
|
|
1315
|
+
outline: none;
|
|
1316
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.06);
|
|
1317
|
+
box-sizing: border-box;
|
|
1318
|
+
min-width: 0;
|
|
1319
|
+
}
|
|
1320
|
+
|
|
1321
|
+
.checkpoint-input-group #register-checkpoint:focus {
|
|
1322
|
+
border-color: #e6b800;
|
|
1323
|
+
box-shadow: 0 2px 12px rgba(230,184,0,0.25);
|
|
1324
|
+
}
|
|
1325
|
+
|
|
1326
|
+
.folder-select-btn {
|
|
1327
|
+
background: #f8f9fa;
|
|
1328
|
+
border: 1px solid #d5d5d5;
|
|
1329
|
+
border-left: none;
|
|
1330
|
+
border-radius: 0 6px 6px 0;
|
|
1331
|
+
padding: 0.6em 0.8em;
|
|
1332
|
+
cursor: pointer;
|
|
1333
|
+
transition: all 0.2s ease;
|
|
1334
|
+
font-size: 1rem;
|
|
1335
|
+
color: #666;
|
|
1336
|
+
min-width: 48px;
|
|
1337
|
+
display: flex;
|
|
1338
|
+
align-items: center;
|
|
1339
|
+
justify-content: center;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
.folder-select-btn:hover {
|
|
1343
|
+
background: #e9ecef;
|
|
1344
|
+
color: #333;
|
|
1345
|
+
}
|
|
1346
|
+
/* */
|
|
1291
1347
|
|
|
1292
1348
|
#register-mmproj, #register-checkpoint {
|
|
1293
1349
|
border-radius: 6px;
|
|
@@ -1960,6 +2016,154 @@ button:disabled {
|
|
|
1960
2016
|
opacity: 0.8;
|
|
1961
2017
|
}
|
|
1962
2018
|
|
|
2019
|
+
/* Migration banner - reuses error-banner structure with warning color */
|
|
2020
|
+
.migration-banner {
|
|
2021
|
+
position: fixed;
|
|
2022
|
+
top: 10px;
|
|
2023
|
+
left: 50%;
|
|
2024
|
+
transform: translateX(-50%);
|
|
2025
|
+
background-color: #ffa500;
|
|
2026
|
+
color: #222;
|
|
2027
|
+
padding: 0.6em 1.2em;
|
|
2028
|
+
border-radius: 6px;
|
|
2029
|
+
box-shadow: 0 2px 8px rgba(0,0,0,0.2);
|
|
2030
|
+
z-index: 10000;
|
|
2031
|
+
font-weight: 600;
|
|
2032
|
+
display: none;
|
|
2033
|
+
animation: fadeIn 0.2s ease;
|
|
2034
|
+
align-items: center;
|
|
2035
|
+
gap: 0.8em;
|
|
2036
|
+
}
|
|
2037
|
+
|
|
2038
|
+
.migration-action-btn {
|
|
2039
|
+
background: #222;
|
|
2040
|
+
color: #ffa500;
|
|
2041
|
+
border: none;
|
|
2042
|
+
padding: 0.4em 0.8em;
|
|
2043
|
+
border-radius: 4px;
|
|
2044
|
+
cursor: pointer;
|
|
2045
|
+
font-weight: 600;
|
|
2046
|
+
transition: background 0.2s;
|
|
2047
|
+
}
|
|
2048
|
+
|
|
2049
|
+
.migration-action-btn:hover {
|
|
2050
|
+
background: #444;
|
|
2051
|
+
}
|
|
2052
|
+
|
|
2053
|
+
.migration-banner .close-btn {
|
|
2054
|
+
background: none;
|
|
2055
|
+
border: none;
|
|
2056
|
+
color: #222;
|
|
2057
|
+
font-size: 1.2em;
|
|
2058
|
+
margin-left: 0.8em;
|
|
2059
|
+
cursor: pointer;
|
|
2060
|
+
padding: 0;
|
|
2061
|
+
line-height: 1;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
/* Modal styles */
|
|
2065
|
+
.modal {
|
|
2066
|
+
position: fixed;
|
|
2067
|
+
z-index: 10001;
|
|
2068
|
+
left: 0;
|
|
2069
|
+
top: 0;
|
|
2070
|
+
width: 100%;
|
|
2071
|
+
height: 100%;
|
|
2072
|
+
background-color: rgba(0,0,0,0.5);
|
|
2073
|
+
display: flex;
|
|
2074
|
+
align-items: center;
|
|
2075
|
+
justify-content: center;
|
|
2076
|
+
}
|
|
2077
|
+
|
|
2078
|
+
.modal-content {
|
|
2079
|
+
background: #fff;
|
|
2080
|
+
border-radius: 8px;
|
|
2081
|
+
max-width: 600px;
|
|
2082
|
+
width: 90%;
|
|
2083
|
+
max-height: 80vh;
|
|
2084
|
+
display: flex;
|
|
2085
|
+
flex-direction: column;
|
|
2086
|
+
box-shadow: 0 4px 20px rgba(0,0,0,0.3);
|
|
2087
|
+
}
|
|
2088
|
+
|
|
2089
|
+
.modal-header {
|
|
2090
|
+
display: flex;
|
|
2091
|
+
justify-content: space-between;
|
|
2092
|
+
align-items: center;
|
|
2093
|
+
padding: 1.2rem 1.5rem;
|
|
2094
|
+
border-bottom: 1px solid #e0e0e0;
|
|
2095
|
+
}
|
|
2096
|
+
|
|
2097
|
+
.modal-header h2 {
|
|
2098
|
+
margin: 0;
|
|
2099
|
+
font-size: 1.3rem;
|
|
2100
|
+
}
|
|
2101
|
+
|
|
2102
|
+
.modal-close {
|
|
2103
|
+
background: none;
|
|
2104
|
+
border: none;
|
|
2105
|
+
font-size: 1.5rem;
|
|
2106
|
+
cursor: pointer;
|
|
2107
|
+
color: #666;
|
|
2108
|
+
padding: 0;
|
|
2109
|
+
}
|
|
2110
|
+
|
|
2111
|
+
.modal-body {
|
|
2112
|
+
padding: 1.5rem;
|
|
2113
|
+
overflow-y: auto;
|
|
2114
|
+
flex: 1;
|
|
2115
|
+
}
|
|
2116
|
+
|
|
2117
|
+
.migration-model-list {
|
|
2118
|
+
margin: 1rem 0;
|
|
2119
|
+
border: 1px solid #e0e0e0;
|
|
2120
|
+
border-radius: 4px;
|
|
2121
|
+
max-height: 300px;
|
|
2122
|
+
overflow-y: auto;
|
|
2123
|
+
}
|
|
2124
|
+
|
|
2125
|
+
.migration-model-item {
|
|
2126
|
+
padding: 0.75rem 1rem;
|
|
2127
|
+
border-bottom: 1px solid #f0f0f0;
|
|
2128
|
+
display: flex;
|
|
2129
|
+
justify-content: space-between;
|
|
2130
|
+
}
|
|
2131
|
+
|
|
2132
|
+
.migration-model-item:last-child {
|
|
2133
|
+
border-bottom: none;
|
|
2134
|
+
}
|
|
2135
|
+
|
|
2136
|
+
.migration-summary {
|
|
2137
|
+
margin-top: 1rem;
|
|
2138
|
+
padding: 0.75rem;
|
|
2139
|
+
background: #f8f9fa;
|
|
2140
|
+
border-radius: 4px;
|
|
2141
|
+
text-align: center;
|
|
2142
|
+
}
|
|
2143
|
+
|
|
2144
|
+
.modal-footer {
|
|
2145
|
+
padding: 1rem 1.5rem;
|
|
2146
|
+
border-top: 1px solid #e0e0e0;
|
|
2147
|
+
display: flex;
|
|
2148
|
+
justify-content: flex-end;
|
|
2149
|
+
gap: 0.75rem;
|
|
2150
|
+
}
|
|
2151
|
+
|
|
2152
|
+
.delete-btn {
|
|
2153
|
+
padding: 0.6em 1.5em;
|
|
2154
|
+
background: var(--danger-primary);
|
|
2155
|
+
color: white;
|
|
2156
|
+
border: none;
|
|
2157
|
+
border-radius: 4px;
|
|
2158
|
+
font-weight: 600;
|
|
2159
|
+
cursor: pointer;
|
|
2160
|
+
transition: background 0.2s;
|
|
2161
|
+
}
|
|
2162
|
+
|
|
2163
|
+
.delete-btn:hover {
|
|
2164
|
+
background: var(--danger-hover);
|
|
2165
|
+
}
|
|
2166
|
+
|
|
1963
2167
|
/* === Model Settings === */
|
|
1964
2168
|
.model-settings-container {
|
|
1965
2169
|
max-width: 600px;
|
|
@@ -29,6 +29,11 @@
|
|
|
29
29
|
<span id="error-banner-msg"></span>
|
|
30
30
|
<button class="close-btn" onclick="hideErrorBanner()">×</button>
|
|
31
31
|
</div>
|
|
32
|
+
<div id="migration-banner" class="migration-banner" style="display:none;">
|
|
33
|
+
<span id="migration-banner-msg"></span>
|
|
34
|
+
<button class="migration-action-btn" onclick="showMigrationModal()">Clean Up Now</button>
|
|
35
|
+
<button class="close-btn" onclick="hideMigrationBanner()">×</button>
|
|
36
|
+
</div>
|
|
32
37
|
<main class="main">
|
|
33
38
|
<div class="tab-content-wrapper">
|
|
34
39
|
<div class="tab-container">
|
|
@@ -135,6 +140,12 @@
|
|
|
135
140
|
<input type="number" id="setting-repeat-penalty" min="0.5" max="2" step="0.05" placeholder="default" />
|
|
136
141
|
<span class="setting-description">Penalty for repeating tokens (1 = no penalty, >1 = less repetition)</span>
|
|
137
142
|
</div>
|
|
143
|
+
<div class="setting-field">
|
|
144
|
+
<label for="enable-thinking">Enable Thinking:</label>
|
|
145
|
+
<input type="checkbox" id="enable-thinking">
|
|
146
|
+
<br>
|
|
147
|
+
<span class="setting-description">Determines whether hybrid reasoning models, such as Qwen3, will use thinking.</span>
|
|
148
|
+
</div>
|
|
138
149
|
<div class="setting-actions">
|
|
139
150
|
<button id="reset-settings-btn" class="reset-btn">Reset to Defaults</button>
|
|
140
151
|
</div>
|
|
@@ -207,7 +218,11 @@
|
|
|
207
218
|
Checkpoint
|
|
208
219
|
<span class="tooltip-icon" data-tooltip="Specify the model checkpoint path from Hugging Face (e.g., org-name/model-name:variant).">ⓘ</span>
|
|
209
220
|
</label>
|
|
221
|
+
<div class="checkpoint-input-group">
|
|
210
222
|
<input type="text" id="register-checkpoint" name="checkpoint" placeholder="unsloth/gemma-3-12b-it-GGUF:Q4_0" class="register-textbox" autocomplete="off">
|
|
223
|
+
<button type="button" id="select-folder-btn" class="folder-select-btn" title="Select local folder">📁</button>
|
|
224
|
+
</div>
|
|
225
|
+
<input type="file" id="folder-input" webkitdirectory directory style="display: none;">
|
|
211
226
|
</div>
|
|
212
227
|
<div class="register-form-row">
|
|
213
228
|
<label class="register-label">
|
|
@@ -258,10 +273,33 @@
|
|
|
258
273
|
</footer>
|
|
259
274
|
|
|
260
275
|
<!-- External libraries -->
|
|
261
|
-
<script src="https://cdn.jsdelivr.net/npm/openai@
|
|
276
|
+
<script src="https://cdn.jsdelivr.net/npm/openai@6.7.0/dist/openai.min.js"></script>
|
|
262
277
|
<script src="https://cdn.jsdelivr.net/npm/marked@9.1.0/marked.min.js"></script>
|
|
263
278
|
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
|
264
279
|
|
|
280
|
+
<!-- Migration Cleanup Modal -->
|
|
281
|
+
<div id="migration-modal" class="modal" style="display:none;">
|
|
282
|
+
<div class="modal-content">
|
|
283
|
+
<div class="modal-header">
|
|
284
|
+
<h2>Clean Up Incompatible Models</h2>
|
|
285
|
+
<button class="modal-close" onclick="hideMigrationModal()">×</button>
|
|
286
|
+
</div>
|
|
287
|
+
<div class="modal-body">
|
|
288
|
+
<p>The following RyzenAI models are incompatible with RyzenAI 1.6 and can be safely deleted:</p>
|
|
289
|
+
<p class="migration-instructions">
|
|
290
|
+
After deleting, you can re-download compatible Ryzen AI 1.6 models from the <em>OGA NPU</em> and <em>OGA Hybrid</em> tabs.</p>
|
|
291
|
+
<div id="migration-model-list" class="migration-model-list"></div>
|
|
292
|
+
<div class="migration-summary">
|
|
293
|
+
<strong>Total space to free: <span id="migration-total-size"></span></strong>
|
|
294
|
+
</div>
|
|
295
|
+
</div>
|
|
296
|
+
<div class="modal-footer">
|
|
297
|
+
<button class="cancel-btn" onclick="hideMigrationModal()">Cancel</button>
|
|
298
|
+
<button class="delete-btn" onclick="deleteIncompatibleModels()">Delete All</button>
|
|
299
|
+
</div>
|
|
300
|
+
</div>
|
|
301
|
+
</div>
|
|
302
|
+
|
|
265
303
|
<!-- Application JavaScript -->
|
|
266
304
|
<script src="/static/js/shared.js"></script>
|
|
267
305
|
<script src="/static/js/models.js"></script>
|
lemonade/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "8.
|
|
1
|
+
__version__ = "8.2.2"
|