lemonade-sdk 8.1.8__py3-none-any.whl → 8.1.9__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.

@@ -42,53 +42,90 @@ async function checkModelHealth() {
42
42
  }
43
43
  }
44
44
 
45
+ // Populate the model dropdown with all installed models
46
+ function populateModelDropdown() {
47
+ const indicator = document.getElementById('model-status-indicator');
48
+ const select = document.getElementById('model-select');
49
+ select.innerHTML = '';
50
+
51
+ // Add the default option
52
+ const defaultOption = document.createElement('option');
53
+ defaultOption.value = '';
54
+ defaultOption.textContent = 'Click to select a model ▼';
55
+ select.appendChild(defaultOption);
56
+
57
+ // Add the hidden 'Server Offline' option
58
+ const hiddenOption = document.createElement('option');
59
+ hiddenOption.value = 'server-offline';
60
+ hiddenOption.textContent = 'Server Offline';
61
+ hiddenOption.hidden = true;
62
+ select.appendChild(hiddenOption);
63
+
64
+ // Get all installed models from the global set
65
+ const sortedModels = Array.from(installedModels).sort();
66
+
67
+ // Add options for each installed model
68
+ sortedModels.forEach(modelId => {
69
+ const option = document.createElement('option');
70
+ option.value = modelId;
71
+ option.textContent = modelId;
72
+ select.appendChild(option);
73
+ });
74
+ }
75
+
45
76
  // Update model status indicator
46
77
  async function updateModelStatusIndicator() {
47
78
  const indicator = document.getElementById('model-status-indicator');
48
- const statusText = document.getElementById('model-status-text');
49
- const unloadBtn = document.getElementById('model-unload-btn');
50
-
79
+ const select = document.getElementById('model-select');
80
+ const buttonIcons = document.querySelectorAll('button');
81
+
51
82
  // Fetch both health and installed models
52
83
  const [health] = await Promise.all([
53
84
  checkModelHealth(),
54
85
  fetchInstalledModels()
55
86
  ]);
56
-
57
- // Refresh model dropdown in chat after fetching installed models
58
- if (window.initializeModelDropdown) {
59
- window.initializeModelDropdown();
60
- }
61
-
62
- // Update system message when model status changes
63
- if (window.displaySystemMessage) {
64
- window.displaySystemMessage();
65
- }
66
-
87
+
88
+ // Populate the dropdown with the newly fetched installed models
89
+ populateModelDropdown();
90
+
67
91
  // Refresh model management UI if we're on the models tab
68
92
  const modelsTab = document.getElementById('content-models');
69
93
  if (modelsTab && modelsTab.classList.contains('active')) {
70
94
  // Use the display-only version to avoid re-fetching data we just fetched
71
95
  refreshModelMgmtUIDisplay();
72
96
  }
73
-
74
- // Remove any click handlers
75
- indicator.onclick = null;
76
-
97
+
77
98
  if (health && health.model_loaded) {
78
99
  // Model is loaded - show model name with online status
100
+ indicator.classList.remove('online', 'offline', 'loading');
79
101
  currentLoadedModel = health.model_loaded;
80
- updateStatusIndicator(health.model_loaded, 'loaded');
81
- unloadBtn.style.display = 'block';
82
- } else if (health) {
102
+ indicator.classList.add('loaded');
103
+ select.value = currentLoadedModel;
104
+ select.disabled = false;
105
+ buttonIcons.forEach(btn => btn.disabled = false);
106
+ } else if (health !== null) {
83
107
  // Server is online but no model loaded
108
+ indicator.classList.remove('loaded', 'offline', 'loading');
84
109
  currentLoadedModel = null;
85
- updateStatusIndicator('Server Online', 'online');
86
- unloadBtn.style.display = 'none';
110
+ indicator.classList.add('online');
111
+ select.value = ''; // Set to the "Click to select a model ▼" option
112
+ select.disabled = false;
113
+ buttonIcons.forEach(btn => btn.disabled = false);
87
114
  } else {
88
115
  // Server is offline
116
+ indicator.classList.remove('loaded', 'online', 'loading');
89
117
  currentLoadedModel = null;
90
- updateStatusIndicator('Server Offline', 'offline');
91
- unloadBtn.style.display = 'none';
118
+ // Add the hidden 'Server Offline' option
119
+ const hiddenOption = document.createElement('option');
120
+ hiddenOption.value = 'server-offline';
121
+ hiddenOption.textContent = 'Server Offline';
122
+ hiddenOption.hidden = true;
123
+ select.appendChild(hiddenOption);
124
+ indicator.classList.add('offline');
125
+ select.value = 'server-offline';
126
+ select.disabled = true;
127
+ buttonIcons.forEach(btn => btn.disabled = true);
128
+ return;
92
129
  }
93
130
  }
94
131
 
@@ -97,9 +134,18 @@ async function unloadModel() {
97
134
  if (!currentLoadedModel) return;
98
135
 
99
136
  try {
137
+ // Set loading state
138
+ const indicator = document.getElementById('model-status-indicator');
139
+ const select = document.getElementById('model-select');
140
+ indicator.classList.remove('loaded', 'online', 'offline');
141
+ indicator.classList.add('loading');
142
+ select.disabled = true;
143
+ select.value = currentLoadedModel; // Keep the selected model visible during unload
144
+
100
145
  await httpRequest(getServerBaseUrl() + '/api/v1/unload', {
101
146
  method: 'POST'
102
147
  });
148
+
103
149
  await updateModelStatusIndicator();
104
150
 
105
151
  // Refresh model list to show updated button states
@@ -109,6 +155,7 @@ async function unloadModel() {
109
155
  } catch (error) {
110
156
  console.error('Error unloading model:', error);
111
157
  showErrorBanner('Failed to unload model: ' + error.message);
158
+ await updateModelStatusIndicator(); // Revert state on error
112
159
  }
113
160
  }
114
161
 
@@ -326,10 +373,14 @@ function createModelItem(modelId, modelData, container) {
326
373
  actions.appendChild(unloadBtn);
327
374
  } else {
328
375
  const loadBtn = document.createElement('button');
376
+ const modelSelect = document.getElementById('model-select');
329
377
  loadBtn.className = 'model-item-btn load';
330
378
  loadBtn.textContent = '🚀';
331
379
  loadBtn.title = 'Load';
332
- loadBtn.onclick = () => loadModel(modelId);
380
+ loadBtn.onclick = () => {
381
+ modelSelect.value = modelId;
382
+ modelSelect.dispatchEvent(new Event('change', { bubbles: true }));
383
+ };
333
384
  actions.appendChild(loadBtn);
334
385
  }
335
386
 
@@ -399,6 +450,15 @@ async function installModel(modelId) {
399
450
 
400
451
  // Load model
401
452
  async function loadModel(modelId) {
453
+ const indicator = document.getElementById('model-status-indicator');
454
+ const select = document.getElementById('model-select');
455
+
456
+ // Set loading state for indicator
457
+ modelSelect.value = 'loading-model';
458
+ indicator.classList.remove('loaded', 'online', 'offline');
459
+ indicator.classList.add('loading');
460
+ select.disabled = true;
461
+
402
462
  // Find the load button and show loading state
403
463
  const modelItems = document.querySelectorAll('.model-item');
404
464
  let loadBtn = null;
@@ -409,6 +469,12 @@ async function loadModel(modelId) {
409
469
  loadBtn = item.querySelector('.model-item-btn.load');
410
470
  }
411
471
  });
472
+
473
+ if (loadBtn) {
474
+ loadBtn.disabled = true;
475
+ loadBtn.textContent = '⏳';
476
+ loadBtn.classList.add('loading');
477
+ }
412
478
 
413
479
  // Use the standardized load function
414
480
  const success = await loadModelStandardized(modelId, {
@@ -494,6 +560,8 @@ function createModelNameWithLabels(modelId, serverModels) {
494
560
  labelClass = 'reranking';
495
561
  } else if (labelLower === 'coding') {
496
562
  labelClass = 'coding';
563
+ } else if (labelLower === 'tool-calling') {
564
+ labelClass = 'tool-calling';
497
565
  }
498
566
  labelSpan.className = `model-label ${labelClass}`;
499
567
  labelSpan.textContent = label;
@@ -514,11 +582,21 @@ document.addEventListener('DOMContentLoaded', async function() {
514
582
  unloadBtn.onclick = unloadModel;
515
583
  }
516
584
 
585
+ const modelSelect = document.getElementById('model-select');
586
+ if (modelSelect) {
587
+ modelSelect.addEventListener('change', async function() {
588
+ const modelId = this.value;
589
+ if (modelId) {
590
+ await loadModel(modelId);
591
+ }
592
+ });
593
+ }
594
+
517
595
  // Initial fetch of model data - this will populate installedModels
518
596
  await updateModelStatusIndicator();
519
597
 
520
598
  // Set up periodic refresh of model status
521
- setInterval(updateModelStatusIndicator, 5000); // Check every 5 seconds
599
+ setInterval(updateModelStatusIndicator, 1000); // Check every 1 seconds
522
600
 
523
601
  // Initialize model browser with hot models
524
602
  displayHotModels();
@@ -683,7 +761,6 @@ async function refreshModelMgmtUI() {
683
761
  }
684
762
  };
685
763
  tdBtn.appendChild(btn);
686
-
687
764
  tr.appendChild(tdName);
688
765
  tr.appendChild(tdBtn);
689
766
  installedTbody.appendChild(tr);
@@ -873,4 +950,4 @@ window.showAddModelForm = showAddModelForm;
873
950
  window.unloadModel = unloadModel;
874
951
  window.installModel = installModel;
875
952
  window.loadModel = loadModel;
876
- window.deleteModel = deleteModel;
953
+ window.deleteModel = deleteModel;
@@ -258,7 +258,7 @@ async function loadModelStandardized(modelId, options = {}) {
258
258
  // Reset the default option text
259
259
  const defaultOption = modelSelect.querySelector('option[value=""]');
260
260
  if (defaultOption) {
261
- defaultOption.textContent = 'Pick a model';
261
+ defaultOption.textContent = 'Click to select a model';
262
262
  }
263
263
  }
264
264
 
@@ -302,7 +302,7 @@ async function loadModelStandardized(modelId, options = {}) {
302
302
  // Reset the default option text
303
303
  const defaultOption = modelSelect.querySelector('option[value=""]');
304
304
  if (defaultOption) {
305
- defaultOption.textContent = 'Pick a model';
305
+ defaultOption.textContent = 'Click to select a model';
306
306
  }
307
307
  }
308
308
 
@@ -482,6 +482,8 @@ function createModelNameWithLabels(modelId, allModels) {
482
482
  labelClass = 'reranking';
483
483
  } else if (labelLower === 'coding') {
484
484
  labelClass = 'coding';
485
+ } else if (labelLower === 'tool-calling') {
486
+ labelClass = 'tool-calling';
485
487
  }
486
488
  labelSpan.className = `model-label ${labelClass}`;
487
489
  labelSpan.textContent = label;
@@ -35,6 +35,12 @@
35
35
  --purple-hover: #744f7e;
36
36
  --purple-light: #f5f1f6;
37
37
 
38
+ /* Status Colors */
39
+ --status-green: #28a745;
40
+ --status-red: #dc3545;
41
+ --status-yellow: #ffc107;
42
+ --status-gray: #6c757d;
43
+
38
44
  /* Transitions */
39
45
  --transition-fast: 0.2s ease;
40
46
  --transition-medium: 0.3s ease;
@@ -229,49 +235,44 @@ body::before {
229
235
  animation: pulse-glow 2s infinite;
230
236
  }
231
237
 
232
- /* Online status (green) */
238
+ /* Online model unloaded status (yellow) */
233
239
  .model-status-indicator.online .status-light {
234
- background: #28a745;
240
+ background: var(--status-yellow);
235
241
  box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
236
242
  }
237
243
 
238
244
  .model-status-indicator.online .status-light::before {
239
- background: #28a745;
245
+ background: var(--status-yellow);
240
246
  }
241
247
 
242
- .model-status-indicator.online .model-status-text {
243
- color: #28a745;
244
- font-weight: 600;
248
+ /* Online model loading status (yellow) */
249
+ .model-status-indicator.loading .status-light {
250
+ background: var(--status-yellow);
251
+ box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
252
+ }
253
+
254
+ .model-status-indicator.loading .status-light::before {
255
+ background: var(--status-yellow);
245
256
  }
246
257
 
247
258
  /* Offline status (red) */
248
259
  .model-status-indicator.offline .status-light {
249
- background: #dc3545;
260
+ background: var(--status-red);
250
261
  box-shadow: 0 0 8px rgba(220, 53, 69, 0.6);
251
262
  }
252
263
 
253
264
  .model-status-indicator.offline .status-light::before {
254
- background: #dc3545;
265
+ background: var(--status-red);
255
266
  }
256
267
 
257
- .model-status-indicator.offline .model-status-text {
258
- color: #dc3545;
259
- font-weight: 600;
260
- }
261
-
262
- /* Model loaded status (same as online but with model name) */
268
+ /* Online model loaded status (with model name) */
263
269
  .model-status-indicator.loaded .status-light {
264
- background: #28a745;
270
+ background: var(--status-green);
265
271
  box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
266
272
  }
267
273
 
268
274
  .model-status-indicator.loaded .status-light::before {
269
- background: #28a745;
270
- }
271
-
272
- .model-status-indicator.loaded .model-status-text {
273
- color: #28a745;
274
- font-weight: 600;
275
+ background: var(--status-green);
275
276
  }
276
277
 
277
278
  @keyframes pulse-glow {
@@ -285,25 +286,99 @@ body::before {
285
286
  }
286
287
  }
287
288
 
288
- .model-status-text {
289
- font-weight: 600;
289
+ /* Base styles for the select element */
290
+ .model-select {
291
+ padding: 0.5rem 0.75rem;
292
+ border: 1px solid #ddd;
293
+ border-radius: 6px;
294
+ background: #fafafa;
290
295
  font-size: 0.9rem;
296
+ min-width: 180px;
297
+ cursor: pointer;
298
+ transition: all var(--transition-fast);
299
+ text-align-last: center;
300
+ -moz-appearance: none;
301
+ -webkit-appearance: none;
302
+ appearance: none;
303
+ }
304
+
305
+ .model-select:focus {
306
+ outline: none;
307
+ border-color: var(--accent-gold);
308
+ box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.2);
309
+ }
310
+
311
+ /* Existing styles for selective coloring based on status */
312
+ .model-status-indicator.loaded .model-select {
313
+ color: var(--status-green); /* Green for loaded model */
314
+ font-weight: bold;
315
+ }
316
+
317
+ .model-status-indicator.loading .model-select {
318
+ color: var(--status-gray); /* Gray for loading */
319
+ font-style: italic;
320
+ cursor: wait;
321
+ }
322
+
323
+ .model-status-indicator.online .model-select {
324
+ color: var(--status-yellow); /* Yellow when online but no model loaded */
325
+ font-weight: bold;
326
+ }
327
+
328
+ .model-status-indicator.offline .model-select {
329
+ color: var(--status-red); /* Red when offline */
330
+ font-weight: bold;
331
+ }
332
+
333
+ /* Ensure options in the list are not affected by the select's styling */
334
+ .model-select option {
335
+ color: var(--text-primary);
336
+ font-weight: normal;
337
+ font-style: normal;
338
+ background-color: white;
339
+ padding: 0.5rem;
340
+ }
341
+
342
+ /* Highlight the selected option ONLY in the open list (if desired) */
343
+ .model-select option:checked {
344
+ background-color: #f0f0f0;
345
+ color: var(--text-primary);
346
+ }
347
+
348
+ .model-select option:checked.server-offline {
349
+ background-color: #f0f0f0;
350
+ color: var(--status-red);
351
+ }
352
+
353
+ .model-select:disabled {
354
+ background: #f5f5f5;
355
+ cursor: not-allowed;
356
+ opacity: 0.7;
357
+ }
358
+ /* When server is offline all buttons are disabled and appear muted */
359
+ button:disabled {
360
+ cursor: not-allowed;
361
+ opacity: 0.6;
362
+ background-color: #cccccc;
363
+ color: #666666;
291
364
  }
292
365
 
293
366
  .model-action-btn {
294
- display: flex;
367
+ display: none; /* Hide by default */
295
368
  align-items: center;
296
369
  justify-content: center;
297
- width: 24px;
298
- height: 24px;
370
+ width: 1.1em;
371
+ height: 1.1em;
299
372
  background: transparent;
300
373
  border: none;
301
374
  border-radius: 50%;
302
375
  cursor: pointer;
303
376
  transition: all var(--transition-fast);
304
- font-size: 0.9rem;
377
+ font-size: 1.5rem;
305
378
  color: #666;
306
- margin-left: 0.2rem;
379
+ margin-left: 0.1rem;
380
+ margin-right: 0.2rem;
381
+ line-height: 1;
307
382
  }
308
383
 
309
384
  .model-action-btn:hover {
@@ -311,6 +386,11 @@ body::before {
311
386
  color: #333;
312
387
  }
313
388
 
389
+ /* Unload button is only visible when a model is loaded */
390
+ .model-status-indicator.loaded .model-action-btn {
391
+ display: flex;
392
+ }
393
+
314
394
  .tab-content {
315
395
  display: none;
316
396
  padding: 2em;
@@ -874,6 +954,10 @@ body::before {
874
954
  background-color: var(--info-primary);
875
955
  }
876
956
 
957
+ .model-label.tool-calling {
958
+ background-color: #FFB74D;
959
+ }
960
+
877
961
  .model-label.other {
878
962
  background-color: var(--success-primary);
879
963
  }
@@ -1106,6 +1190,7 @@ body::before {
1106
1190
 
1107
1191
  #register-model-name:focus {
1108
1192
  border-color: #e6b800;
1193
+ box-shadow: 0 2px 12px rgba(230,184,0,0.25);
1109
1194
  }
1110
1195
 
1111
1196
  .form-input-wrapper {
@@ -2190,43 +2275,4 @@ body::before {
2190
2275
  0 8px 25px rgba(200, 88, 108, 0.2),
2191
2276
  0 3px 10px rgba(0, 0, 0, 0.1),
2192
2277
  inset 0 1px 0 rgba(255, 255, 255, 0.9);
2193
- }
2194
-
2195
- /* Model select dropdown in chat */
2196
- .model-select {
2197
- padding: 0.5rem 0.75rem;
2198
- border: 1px solid #ddd;
2199
- border-radius: 6px;
2200
- background: white;
2201
- font-size: 0.9rem;
2202
- min-width: 180px;
2203
- margin-right: 0.75rem;
2204
- cursor: pointer;
2205
- transition: all var(--transition-fast);
2206
- }
2207
-
2208
- .model-select:focus {
2209
- outline: none;
2210
- border-color: var(--accent-gold);
2211
- box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.2);
2212
- }
2213
-
2214
- .model-select:disabled {
2215
- background: #f5f5f5;
2216
- cursor: not-allowed;
2217
- opacity: 0.7;
2218
- }
2219
-
2220
- .model-select option {
2221
- padding: 0.5rem;
2222
- }
2223
-
2224
- .chat-input-row {
2225
- display: flex;
2226
- align-items: center;
2227
- gap: 0.5rem;
2228
- }
2229
-
2230
- .input-with-indicator {
2231
- flex: 1;
2232
- }
2278
+ }
@@ -32,25 +32,23 @@
32
32
  <div class="tab-container">
33
33
  <div class="tabs">
34
34
  <div class="tab-group">
35
- <button class="tab active" id="tab-chat" onclick="showTab('chat')">LLM Chat</button>
36
- <button class="tab" id="tab-model-settings" onclick="showTab('settings')">Model Settings</button>
37
- <button class="tab" id="tab-models" onclick="showTab('models')">Model Management</button>
38
- </div>
39
-
40
- <!-- Model Status Indicator integrated into tab bar -->
41
- <div class="model-status-indicator" id="model-status-indicator">
42
- <div class="status-light" id="status-light"></div>
43
- <span class="model-status-text" id="model-status-text">Loading...</span>
44
- <button class="model-action-btn" id="model-unload-btn" style="display: none;" title="Unload model">⏏</button>
45
- </div>
46
- </div>
47
- <div class="tab-content active" id="content-chat">
48
- <div class="chat-container">
49
- <div class="chat-history" id="chat-history"></div>
50
- <div class="chat-input-row">
51
- <select id="model-select" class="model-select">
52
- <option value="">Pick a model</option>
53
- </select>
35
+ <button class="tab active" id="tab-chat" onclick="showTab('chat')">LLM Chat</button>
36
+ <button class="tab" id="tab-model-settings" onclick="showTab('settings')">Model Settings</button>
37
+ <button class="tab" id="tab-models" onclick="showTab('models')">Model Management</button>
38
+ </div>
39
+
40
+ <div class="model-status-indicator" id="model-status-indicator">
41
+ <div class="status-light" id="status-light"></div>
42
+ <select id="model-select" class="model-select">
43
+ <option value="">Pick a model</option>
44
+ </select>
45
+ <button class="model-action-btn" id="model-unload-btn" title="Unload model" style="display: flex;">⏏</button>
46
+ </div>
47
+ </div>
48
+ <div class="tab-content active" id="content-chat">
49
+ <div class="chat-container">
50
+ <div class="chat-history" id="chat-history"></div>
51
+ <div class="chat-input-row">
54
52
  <div class="input-with-indicator">
55
53
  <textarea id="chat-input" placeholder="Type your message..." rows="1"></textarea>
56
54
  </div>
@@ -142,7 +140,6 @@
142
140
  </div>
143
141
  <div class="category-content expanded" id="category-hot"></div>
144
142
  </div>
145
-
146
143
  <div class="model-category-section">
147
144
  <div class="section-header">
148
145
  <span class="section-icon">🔧</span>
@@ -155,7 +152,6 @@
155
152
  <div class="subcategory" data-recipe="oga-cpu" onclick="selectRecipe('oga-cpu')">OGA CPU</div>
156
153
  </div>
157
154
  </div>
158
-
159
155
  <div class="model-category-section">
160
156
  <div class="section-header">
161
157
  <span class="section-icon">🏷️</span>
@@ -165,12 +161,12 @@
165
161
  <div class="subcategory" data-label="coding" onclick="selectLabel('coding')">Coding</div>
166
162
  <div class="subcategory" data-label="vision" onclick="selectLabel('vision')">Vision</div>
167
163
  <div class="subcategory" data-label="reasoning" onclick="selectLabel('reasoning')">Reasoning</div>
164
+ <div class="subcategory" data-label="tool-calling" onclick="selectLabel('tool-calling')">Tool Calling</div>
168
165
  <div class="subcategory" data-label="reranking" onclick="selectLabel('reranking')">Reranking</div>
169
166
  <div class="subcategory" data-label="embeddings" onclick="selectLabel('embeddings')">Embeddings</div>
170
167
  <div class="subcategory" data-label="custom" onclick="selectLabel('custom')">Custom</div>
171
168
  </div>
172
169
  </div>
173
-
174
170
  <div class="model-category" data-category="add">
175
171
  <div class="category-header" onclick="showAddModelForm()">
176
172
  <span class="category-icon">➕</span>
@@ -178,7 +174,6 @@
178
174
  </div>
179
175
  </div>
180
176
  </div>
181
-
182
177
  <div class="model-browser-main">
183
178
  <div class="model-list" id="model-list"></div>
184
179
 
@@ -255,3 +250,4 @@
255
250
  <script src="/static/js/chat.js"></script>
256
251
  </body>
257
252
  </html>
253
+ </html>
lemonade/version.py CHANGED
@@ -1 +1 @@
1
- __version__ = "8.1.8"
1
+ __version__ = "8.1.9"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: lemonade-sdk
3
- Version: 8.1.8
3
+ Version: 8.1.9
4
4
  Summary: Lemonade SDK: Your LLM Aide for Validation and Deployment
5
5
  Author-email: lemonade@amd.com
6
6
  Requires-Python: >=3.10, <3.14
@@ -16,7 +16,7 @@ Requires-Dist: numpy
16
16
  Requires-Dist: fasteners
17
17
  Requires-Dist: GitPython>=3.1.40
18
18
  Requires-Dist: psutil>=6.1.1
19
- Requires-Dist: wmi
19
+ Requires-Dist: wmi; platform_system == "Windows"
20
20
  Requires-Dist: py-cpuinfo
21
21
  Requires-Dist: pytz
22
22
  Requires-Dist: zstandard