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.

Files changed (38) hide show
  1. lemonade/cache.py +6 -1
  2. lemonade/common/status.py +4 -4
  3. lemonade/common/system_info.py +0 -26
  4. lemonade/tools/accuracy.py +143 -48
  5. lemonade/tools/adapter.py +6 -1
  6. lemonade/tools/bench.py +26 -8
  7. lemonade/tools/flm/utils.py +70 -22
  8. lemonade/tools/huggingface/bench.py +6 -1
  9. lemonade/tools/llamacpp/bench.py +146 -27
  10. lemonade/tools/llamacpp/load.py +30 -2
  11. lemonade/tools/llamacpp/utils.py +317 -21
  12. lemonade/tools/oga/bench.py +5 -26
  13. lemonade/tools/oga/load.py +49 -123
  14. lemonade/tools/oga/migration.py +403 -0
  15. lemonade/tools/report/table.py +76 -8
  16. lemonade/tools/server/flm.py +2 -6
  17. lemonade/tools/server/llamacpp.py +43 -2
  18. lemonade/tools/server/serve.py +354 -18
  19. lemonade/tools/server/static/js/chat.js +15 -77
  20. lemonade/tools/server/static/js/model-settings.js +24 -3
  21. lemonade/tools/server/static/js/models.js +440 -37
  22. lemonade/tools/server/static/js/shared.js +61 -8
  23. lemonade/tools/server/static/logs.html +157 -13
  24. lemonade/tools/server/static/styles.css +204 -0
  25. lemonade/tools/server/static/webapp.html +39 -1
  26. lemonade/version.py +1 -1
  27. lemonade_install/install.py +33 -579
  28. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/METADATA +6 -4
  29. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/RECORD +38 -37
  30. lemonade_server/cli.py +10 -0
  31. lemonade_server/model_manager.py +172 -11
  32. lemonade_server/pydantic_models.py +3 -0
  33. lemonade_server/server_models.json +102 -66
  34. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/WHEEL +0 -0
  35. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/entry_points.txt +0 -0
  36. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/LICENSE +0 -0
  37. {lemonade_sdk-8.1.11.dist-info → lemonade_sdk-8.2.2.dist-info}/licenses/NOTICE.md +0 -0
  38. {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
- showErrorBanner(msg);
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 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
+
69
96
  if (msgEl) {
70
- msgEl.textContent = fullMsg;
97
+ msgEl.innerHTML = fullMsg;
71
98
  } else {
72
- banner.textContent = fullMsg;
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('send-btn');
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({ model_name: modelId })
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('send-btn');
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
- <script>
26
- function stripAnsi(str) {
27
- return str.replace(/\x1B\[[0-9;]*[A-Za-z]/g, '');
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
- const logContainer = document.getElementById("log-container");
30
- const ws = new WebSocket(`ws://${location.host}/api/v1/logs/ws`);
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
- ws.onmessage = (event) => {
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(event.data);
162
+ line.textContent = stripAnsi(logLine);
35
163
  logContainer.appendChild(line);
36
- logContainer.scrollTop = logContainer.scrollHeight; // auto scroll
37
- };
38
164
 
39
- ws.onclose = () => {
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 = "[Disconnected from log stream]";
173
+ msg.textContent = `[Connection error: ${error.message}]`;
42
174
  msg.style.color = "red";
43
175
  logContainer.appendChild(msg);
44
- };
45
- </script>
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()">&times;</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()">&times;</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@4.21.0/dist/openai.min.js"></script>
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()">&times;</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.11"
1
+ __version__ = "8.2.2"