lemonade-sdk 8.1.4__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/cli.py +47 -5
- lemonade/common/inference_engines.py +13 -4
- lemonade/common/status.py +4 -4
- lemonade/common/system_info.py +544 -1
- lemonade/profilers/agt_power.py +437 -0
- lemonade/profilers/hwinfo_power.py +429 -0
- lemonade/tools/accuracy.py +143 -48
- lemonade/tools/adapter.py +6 -1
- lemonade/tools/bench.py +26 -8
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +303 -0
- 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 +393 -33
- lemonade/tools/oga/bench.py +5 -26
- lemonade/tools/oga/load.py +60 -121
- lemonade/tools/oga/migration.py +403 -0
- lemonade/tools/report/table.py +76 -8
- lemonade/tools/server/flm.py +133 -0
- lemonade/tools/server/llamacpp.py +220 -553
- lemonade/tools/server/serve.py +684 -168
- lemonade/tools/server/static/js/chat.js +666 -342
- lemonade/tools/server/static/js/model-settings.js +24 -3
- lemonade/tools/server/static/js/models.js +597 -73
- lemonade/tools/server/static/js/shared.js +79 -14
- lemonade/tools/server/static/logs.html +191 -0
- lemonade/tools/server/static/styles.css +491 -66
- lemonade/tools/server/static/webapp.html +83 -31
- lemonade/tools/server/tray.py +158 -38
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/{system_tray.py → windows_tray.py} +13 -0
- lemonade/tools/server/webapp.py +4 -1
- lemonade/tools/server/wrapped_server.py +559 -0
- lemonade/version.py +1 -1
- lemonade_install/install.py +54 -611
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/METADATA +29 -72
- lemonade_sdk-8.2.2.dist-info/RECORD +83 -0
- lemonade_server/cli.py +145 -37
- lemonade_server/model_manager.py +521 -37
- lemonade_server/pydantic_models.py +28 -1
- lemonade_server/server_models.json +246 -92
- lemonade_server/settings.py +39 -39
- lemonade/tools/quark/__init__.py +0 -0
- lemonade/tools/quark/quark_load.py +0 -173
- lemonade/tools/quark/quark_quantize.py +0 -439
- lemonade_sdk-8.1.4.dist-info/RECORD +0 -77
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/top_level.txt +0 -0
|
@@ -54,15 +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') {
|
|
62
|
+
// If DOM isn't ready, wait for it
|
|
63
|
+
if (document.readyState === 'loading') {
|
|
64
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
65
|
+
showBanner(msg, type);
|
|
66
|
+
});
|
|
67
|
+
return;
|
|
68
|
+
}
|
|
69
|
+
|
|
57
70
|
const banner = document.getElementById('error-banner');
|
|
58
71
|
if (!banner) return;
|
|
59
72
|
const msgEl = document.getElementById('error-banner-msg');
|
|
60
|
-
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
|
+
|
|
61
96
|
if (msgEl) {
|
|
62
|
-
msgEl.
|
|
97
|
+
msgEl.innerHTML = fullMsg;
|
|
63
98
|
} else {
|
|
64
|
-
banner.
|
|
99
|
+
banner.innerHTML = fullMsg;
|
|
65
100
|
}
|
|
101
|
+
|
|
102
|
+
banner.style.backgroundColor = backgroundColor;
|
|
103
|
+
banner.style.color = color;
|
|
66
104
|
banner.style.display = 'flex';
|
|
67
105
|
}
|
|
68
106
|
|
|
@@ -169,11 +207,16 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
169
207
|
// Store original states for restoration on error
|
|
170
208
|
const originalStatusText = document.getElementById('model-status-text')?.textContent || '';
|
|
171
209
|
|
|
210
|
+
// Track this load operation as active to prevent polling interference
|
|
211
|
+
if (window.activeOperations) {
|
|
212
|
+
window.activeOperations.add(modelId);
|
|
213
|
+
}
|
|
214
|
+
|
|
172
215
|
try {
|
|
173
216
|
// Update load button if provided
|
|
174
217
|
if (loadButton) {
|
|
175
218
|
loadButton.disabled = true;
|
|
176
|
-
loadButton.textContent = '
|
|
219
|
+
loadButton.textContent = '⏳';
|
|
177
220
|
}
|
|
178
221
|
|
|
179
222
|
// Update status indicator to show loading state
|
|
@@ -181,7 +224,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
181
224
|
|
|
182
225
|
// Update chat dropdown and send button to show loading state
|
|
183
226
|
const modelSelect = document.getElementById('model-select');
|
|
184
|
-
const sendBtn = document.getElementById('
|
|
227
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
185
228
|
if (modelSelect && sendBtn) {
|
|
186
229
|
// Ensure the model exists in the dropdown options
|
|
187
230
|
let modelOption = modelSelect.querySelector(`option[value="${modelId}"]`);
|
|
@@ -214,10 +257,18 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
214
257
|
}
|
|
215
258
|
|
|
216
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
|
+
|
|
217
268
|
await httpRequest(getServerBaseUrl() + '/api/v1/load', {
|
|
218
269
|
method: 'POST',
|
|
219
270
|
headers: { 'Content-Type': 'application/json' },
|
|
220
|
-
body: JSON.stringify(
|
|
271
|
+
body: JSON.stringify(loadPayload)
|
|
221
272
|
});
|
|
222
273
|
|
|
223
274
|
// Update model status indicator after successful load
|
|
@@ -238,7 +289,8 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
238
289
|
// Reset load button if provided
|
|
239
290
|
if (loadButton) {
|
|
240
291
|
loadButton.disabled = false;
|
|
241
|
-
loadButton.textContent = '
|
|
292
|
+
loadButton.textContent = '🚀';
|
|
293
|
+
loadButton.classList.remove('loading');
|
|
242
294
|
}
|
|
243
295
|
|
|
244
296
|
// Reset chat controls
|
|
@@ -250,7 +302,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
250
302
|
// Reset the default option text
|
|
251
303
|
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
252
304
|
if (defaultOption) {
|
|
253
|
-
defaultOption.textContent = '
|
|
305
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
254
306
|
}
|
|
255
307
|
}
|
|
256
308
|
|
|
@@ -264,6 +316,11 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
264
316
|
onSuccess(modelId);
|
|
265
317
|
}
|
|
266
318
|
|
|
319
|
+
// Remove from active operations on success
|
|
320
|
+
if (window.activeOperations) {
|
|
321
|
+
window.activeOperations.delete(modelId);
|
|
322
|
+
}
|
|
323
|
+
|
|
267
324
|
return true;
|
|
268
325
|
|
|
269
326
|
} catch (error) {
|
|
@@ -272,7 +329,8 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
272
329
|
// Reset load button if provided
|
|
273
330
|
if (loadButton) {
|
|
274
331
|
loadButton.disabled = false;
|
|
275
|
-
loadButton.textContent = '
|
|
332
|
+
loadButton.textContent = '🚀';
|
|
333
|
+
loadButton.classList.remove('loading');
|
|
276
334
|
}
|
|
277
335
|
|
|
278
336
|
// Reset status indicator on error
|
|
@@ -280,7 +338,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
280
338
|
|
|
281
339
|
// Reset chat controls on error
|
|
282
340
|
const modelSelect = document.getElementById('model-select');
|
|
283
|
-
const sendBtn = document.getElementById('
|
|
341
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
284
342
|
if (modelSelect && sendBtn) {
|
|
285
343
|
modelSelect.disabled = false;
|
|
286
344
|
sendBtn.disabled = false;
|
|
@@ -294,7 +352,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
294
352
|
// Reset the default option text
|
|
295
353
|
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
296
354
|
if (defaultOption) {
|
|
297
|
-
defaultOption.textContent = '
|
|
355
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
298
356
|
}
|
|
299
357
|
}
|
|
300
358
|
|
|
@@ -303,11 +361,16 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
303
361
|
onLoadingEnd(modelId, false);
|
|
304
362
|
}
|
|
305
363
|
|
|
306
|
-
// Call error callback
|
|
364
|
+
// Call error callback and always show default error banner as fallback
|
|
307
365
|
if (onError) {
|
|
308
366
|
onError(error, modelId);
|
|
309
|
-
}
|
|
310
|
-
|
|
367
|
+
}
|
|
368
|
+
// Always show error banner to ensure user sees the error
|
|
369
|
+
showErrorBanner('Failed to load model: ' + error.message);
|
|
370
|
+
|
|
371
|
+
// Remove from active operations on error too
|
|
372
|
+
if (window.activeOperations) {
|
|
373
|
+
window.activeOperations.delete(modelId);
|
|
311
374
|
}
|
|
312
375
|
|
|
313
376
|
return false;
|
|
@@ -474,6 +537,8 @@ function createModelNameWithLabels(modelId, allModels) {
|
|
|
474
537
|
labelClass = 'reranking';
|
|
475
538
|
} else if (labelLower === 'coding') {
|
|
476
539
|
labelClass = 'coding';
|
|
540
|
+
} else if (labelLower === 'tool-calling') {
|
|
541
|
+
labelClass = 'tool-calling';
|
|
477
542
|
}
|
|
478
543
|
labelSpan.className = `model-label ${labelClass}`;
|
|
479
544
|
labelSpan.textContent = label;
|
|
@@ -0,0 +1,191 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html lang="en">
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="UTF-8">
|
|
5
|
+
<title>Lemonade Server Logs</title>
|
|
6
|
+
<style>
|
|
7
|
+
body {
|
|
8
|
+
font-family: monospace;
|
|
9
|
+
background: #1e1e1e;
|
|
10
|
+
color: #d4d4d4;
|
|
11
|
+
margin: 0;
|
|
12
|
+
padding: 0;
|
|
13
|
+
}
|
|
14
|
+
#log-container {
|
|
15
|
+
padding: 10px;
|
|
16
|
+
height: 100vh;
|
|
17
|
+
overflow-y: auto;
|
|
18
|
+
white-space: pre-wrap;
|
|
19
|
+
}
|
|
20
|
+
</style>
|
|
21
|
+
</head>
|
|
22
|
+
<body>
|
|
23
|
+
<div id="log-container"></div>
|
|
24
|
+
|
|
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'
|
|
38
|
+
}
|
|
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
|
+
}
|
|
142
|
+
|
|
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
|
|
161
|
+
const line = document.createElement("div");
|
|
162
|
+
line.textContent = stripAnsi(logLine);
|
|
163
|
+
logContainer.appendChild(line);
|
|
164
|
+
|
|
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
|
|
172
|
+
const msg = document.createElement("div");
|
|
173
|
+
msg.textContent = `[Connection error: ${error.message}]`;
|
|
174
|
+
msg.style.color = "red";
|
|
175
|
+
logContainer.appendChild(msg);
|
|
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>
|
|
190
|
+
</body>
|
|
191
|
+
</html>
|