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.

Files changed (53) hide show
  1. lemonade/cache.py +6 -1
  2. lemonade/cli.py +47 -5
  3. lemonade/common/inference_engines.py +13 -4
  4. lemonade/common/status.py +4 -4
  5. lemonade/common/system_info.py +544 -1
  6. lemonade/profilers/agt_power.py +437 -0
  7. lemonade/profilers/hwinfo_power.py +429 -0
  8. lemonade/tools/accuracy.py +143 -48
  9. lemonade/tools/adapter.py +6 -1
  10. lemonade/tools/bench.py +26 -8
  11. lemonade/tools/flm/__init__.py +1 -0
  12. lemonade/tools/flm/utils.py +303 -0
  13. lemonade/tools/huggingface/bench.py +6 -1
  14. lemonade/tools/llamacpp/bench.py +146 -27
  15. lemonade/tools/llamacpp/load.py +30 -2
  16. lemonade/tools/llamacpp/utils.py +393 -33
  17. lemonade/tools/oga/bench.py +5 -26
  18. lemonade/tools/oga/load.py +60 -121
  19. lemonade/tools/oga/migration.py +403 -0
  20. lemonade/tools/report/table.py +76 -8
  21. lemonade/tools/server/flm.py +133 -0
  22. lemonade/tools/server/llamacpp.py +220 -553
  23. lemonade/tools/server/serve.py +684 -168
  24. lemonade/tools/server/static/js/chat.js +666 -342
  25. lemonade/tools/server/static/js/model-settings.js +24 -3
  26. lemonade/tools/server/static/js/models.js +597 -73
  27. lemonade/tools/server/static/js/shared.js +79 -14
  28. lemonade/tools/server/static/logs.html +191 -0
  29. lemonade/tools/server/static/styles.css +491 -66
  30. lemonade/tools/server/static/webapp.html +83 -31
  31. lemonade/tools/server/tray.py +158 -38
  32. lemonade/tools/server/utils/macos_tray.py +226 -0
  33. lemonade/tools/server/utils/{system_tray.py → windows_tray.py} +13 -0
  34. lemonade/tools/server/webapp.py +4 -1
  35. lemonade/tools/server/wrapped_server.py +559 -0
  36. lemonade/version.py +1 -1
  37. lemonade_install/install.py +54 -611
  38. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/METADATA +29 -72
  39. lemonade_sdk-8.2.2.dist-info/RECORD +83 -0
  40. lemonade_server/cli.py +145 -37
  41. lemonade_server/model_manager.py +521 -37
  42. lemonade_server/pydantic_models.py +28 -1
  43. lemonade_server/server_models.json +246 -92
  44. lemonade_server/settings.py +39 -39
  45. lemonade/tools/quark/__init__.py +0 -0
  46. lemonade/tools/quark/quark_load.py +0 -173
  47. lemonade/tools/quark/quark_quantize.py +0 -439
  48. lemonade_sdk-8.1.4.dist-info/RECORD +0 -77
  49. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/WHEEL +0 -0
  50. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/entry_points.txt +0 -0
  51. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/LICENSE +0 -0
  52. {lemonade_sdk-8.1.4.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/NOTICE.md +0 -0
  53. {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 fullMsg = msg + '\nCheck the Lemonade Server logs via the system tray app for more information.';
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.textContent = fullMsg;
97
+ msgEl.innerHTML = fullMsg;
63
98
  } else {
64
- banner.textContent = fullMsg;
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('send-btn');
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({ model_name: modelId })
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 = 'Load';
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 = 'Pick a model';
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 = 'Load';
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('send-btn');
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 = 'Pick a model';
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 or show default error
364
+ // Call error callback and always show default error banner as fallback
307
365
  if (onError) {
308
366
  onError(error, modelId);
309
- } else {
310
- showErrorBanner('Failed to load model: ' + error.message);
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>