lemonade-sdk 8.1.7__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.
- lemonade/cli.py +47 -5
- lemonade/profilers/agt_power.py +437 -0
- lemonade/profilers/hwinfo_power.py +429 -0
- lemonade/tools/llamacpp/utils.py +15 -4
- lemonade/tools/oga/load.py +15 -2
- lemonade/tools/report/table.py +1 -1
- lemonade/tools/server/llamacpp.py +19 -13
- lemonade/tools/server/serve.py +39 -9
- lemonade/tools/server/static/js/chat.js +545 -242
- lemonade/tools/server/static/js/models.js +112 -24
- lemonade/tools/server/static/js/shared.js +15 -5
- lemonade/tools/server/static/styles.css +145 -75
- lemonade/tools/server/static/webapp.html +23 -27
- lemonade/tools/server/wrapped_server.py +8 -0
- lemonade/version.py +1 -1
- lemonade_install/install.py +15 -49
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/METADATA +16 -64
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/RECORD +26 -27
- lemonade_server/cli.py +12 -9
- lemonade_server/model_manager.py +48 -0
- lemonade_server/server_models.json +24 -6
- 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.7.dist-info → lemonade_sdk-8.1.9.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.1.7.dist-info → lemonade_sdk-8.1.9.dist-info}/top_level.txt +0 -0
|
@@ -42,48 +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
|
|
49
|
-
|
|
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
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
61
|
-
|
|
87
|
+
|
|
88
|
+
// Populate the dropdown with the newly fetched installed models
|
|
89
|
+
populateModelDropdown();
|
|
90
|
+
|
|
62
91
|
// Refresh model management UI if we're on the models tab
|
|
63
92
|
const modelsTab = document.getElementById('content-models');
|
|
64
93
|
if (modelsTab && modelsTab.classList.contains('active')) {
|
|
65
94
|
// Use the display-only version to avoid re-fetching data we just fetched
|
|
66
95
|
refreshModelMgmtUIDisplay();
|
|
67
96
|
}
|
|
68
|
-
|
|
69
|
-
// Remove any click handlers
|
|
70
|
-
indicator.onclick = null;
|
|
71
|
-
|
|
97
|
+
|
|
72
98
|
if (health && health.model_loaded) {
|
|
73
99
|
// Model is loaded - show model name with online status
|
|
100
|
+
indicator.classList.remove('online', 'offline', 'loading');
|
|
74
101
|
currentLoadedModel = health.model_loaded;
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
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) {
|
|
78
107
|
// Server is online but no model loaded
|
|
108
|
+
indicator.classList.remove('loaded', 'offline', 'loading');
|
|
79
109
|
currentLoadedModel = null;
|
|
80
|
-
|
|
81
|
-
|
|
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);
|
|
82
114
|
} else {
|
|
83
115
|
// Server is offline
|
|
116
|
+
indicator.classList.remove('loaded', 'online', 'loading');
|
|
84
117
|
currentLoadedModel = null;
|
|
85
|
-
|
|
86
|
-
|
|
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;
|
|
87
129
|
}
|
|
88
130
|
}
|
|
89
131
|
|
|
@@ -92,9 +134,18 @@ async function unloadModel() {
|
|
|
92
134
|
if (!currentLoadedModel) return;
|
|
93
135
|
|
|
94
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
|
+
|
|
95
145
|
await httpRequest(getServerBaseUrl() + '/api/v1/unload', {
|
|
96
146
|
method: 'POST'
|
|
97
147
|
});
|
|
148
|
+
|
|
98
149
|
await updateModelStatusIndicator();
|
|
99
150
|
|
|
100
151
|
// Refresh model list to show updated button states
|
|
@@ -104,6 +155,7 @@ async function unloadModel() {
|
|
|
104
155
|
} catch (error) {
|
|
105
156
|
console.error('Error unloading model:', error);
|
|
106
157
|
showErrorBanner('Failed to unload model: ' + error.message);
|
|
158
|
+
await updateModelStatusIndicator(); // Revert state on error
|
|
107
159
|
}
|
|
108
160
|
}
|
|
109
161
|
|
|
@@ -321,10 +373,14 @@ function createModelItem(modelId, modelData, container) {
|
|
|
321
373
|
actions.appendChild(unloadBtn);
|
|
322
374
|
} else {
|
|
323
375
|
const loadBtn = document.createElement('button');
|
|
376
|
+
const modelSelect = document.getElementById('model-select');
|
|
324
377
|
loadBtn.className = 'model-item-btn load';
|
|
325
378
|
loadBtn.textContent = '🚀';
|
|
326
379
|
loadBtn.title = 'Load';
|
|
327
|
-
loadBtn.onclick = () =>
|
|
380
|
+
loadBtn.onclick = () => {
|
|
381
|
+
modelSelect.value = modelId;
|
|
382
|
+
modelSelect.dispatchEvent(new Event('change', { bubbles: true }));
|
|
383
|
+
};
|
|
328
384
|
actions.appendChild(loadBtn);
|
|
329
385
|
}
|
|
330
386
|
|
|
@@ -394,6 +450,15 @@ async function installModel(modelId) {
|
|
|
394
450
|
|
|
395
451
|
// Load model
|
|
396
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
|
+
|
|
397
462
|
// Find the load button and show loading state
|
|
398
463
|
const modelItems = document.querySelectorAll('.model-item');
|
|
399
464
|
let loadBtn = null;
|
|
@@ -404,6 +469,12 @@ async function loadModel(modelId) {
|
|
|
404
469
|
loadBtn = item.querySelector('.model-item-btn.load');
|
|
405
470
|
}
|
|
406
471
|
});
|
|
472
|
+
|
|
473
|
+
if (loadBtn) {
|
|
474
|
+
loadBtn.disabled = true;
|
|
475
|
+
loadBtn.textContent = '⏳';
|
|
476
|
+
loadBtn.classList.add('loading');
|
|
477
|
+
}
|
|
407
478
|
|
|
408
479
|
// Use the standardized load function
|
|
409
480
|
const success = await loadModelStandardized(modelId, {
|
|
@@ -417,6 +488,7 @@ async function loadModel(modelId) {
|
|
|
417
488
|
},
|
|
418
489
|
onError: (error, failedModelId) => {
|
|
419
490
|
console.error(`Failed to load model ${failedModelId}:`, error);
|
|
491
|
+
showErrorBanner('Failed to load model: ' + error.message);
|
|
420
492
|
}
|
|
421
493
|
});
|
|
422
494
|
}
|
|
@@ -488,6 +560,8 @@ function createModelNameWithLabels(modelId, serverModels) {
|
|
|
488
560
|
labelClass = 'reranking';
|
|
489
561
|
} else if (labelLower === 'coding') {
|
|
490
562
|
labelClass = 'coding';
|
|
563
|
+
} else if (labelLower === 'tool-calling') {
|
|
564
|
+
labelClass = 'tool-calling';
|
|
491
565
|
}
|
|
492
566
|
labelSpan.className = `model-label ${labelClass}`;
|
|
493
567
|
labelSpan.textContent = label;
|
|
@@ -508,11 +582,21 @@ document.addEventListener('DOMContentLoaded', async function() {
|
|
|
508
582
|
unloadBtn.onclick = unloadModel;
|
|
509
583
|
}
|
|
510
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
|
+
|
|
511
595
|
// Initial fetch of model data - this will populate installedModels
|
|
512
596
|
await updateModelStatusIndicator();
|
|
513
597
|
|
|
514
598
|
// Set up periodic refresh of model status
|
|
515
|
-
setInterval(updateModelStatusIndicator,
|
|
599
|
+
setInterval(updateModelStatusIndicator, 1000); // Check every 1 seconds
|
|
516
600
|
|
|
517
601
|
// Initialize model browser with hot models
|
|
518
602
|
displayHotModels();
|
|
@@ -677,7 +761,6 @@ async function refreshModelMgmtUI() {
|
|
|
677
761
|
}
|
|
678
762
|
};
|
|
679
763
|
tdBtn.appendChild(btn);
|
|
680
|
-
|
|
681
764
|
tr.appendChild(tdName);
|
|
682
765
|
tr.appendChild(tdBtn);
|
|
683
766
|
installedTbody.appendChild(tr);
|
|
@@ -699,6 +782,11 @@ async function refreshModelMgmtUI() {
|
|
|
699
782
|
if (window.initializeModelDropdown) {
|
|
700
783
|
window.initializeModelDropdown();
|
|
701
784
|
}
|
|
785
|
+
|
|
786
|
+
// Update system message when installed models change
|
|
787
|
+
if (window.displaySystemMessage) {
|
|
788
|
+
window.displaySystemMessage();
|
|
789
|
+
}
|
|
702
790
|
}
|
|
703
791
|
|
|
704
792
|
// Make refreshModelMgmtUI globally accessible
|
|
@@ -862,4 +950,4 @@ window.showAddModelForm = showAddModelForm;
|
|
|
862
950
|
window.unloadModel = unloadModel;
|
|
863
951
|
window.installModel = installModel;
|
|
864
952
|
window.loadModel = loadModel;
|
|
865
|
-
window.deleteModel = deleteModel;
|
|
953
|
+
window.deleteModel = deleteModel;
|
|
@@ -54,6 +54,14 @@ function renderMarkdown(text) {
|
|
|
54
54
|
|
|
55
55
|
// Display an error message in the banner
|
|
56
56
|
function showErrorBanner(msg) {
|
|
57
|
+
// If DOM isn't ready, wait for it
|
|
58
|
+
if (document.readyState === 'loading') {
|
|
59
|
+
document.addEventListener('DOMContentLoaded', () => {
|
|
60
|
+
showErrorBanner(msg);
|
|
61
|
+
});
|
|
62
|
+
return;
|
|
63
|
+
}
|
|
64
|
+
|
|
57
65
|
const banner = document.getElementById('error-banner');
|
|
58
66
|
if (!banner) return;
|
|
59
67
|
const msgEl = document.getElementById('error-banner-msg');
|
|
@@ -250,7 +258,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
250
258
|
// Reset the default option text
|
|
251
259
|
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
252
260
|
if (defaultOption) {
|
|
253
|
-
defaultOption.textContent = '
|
|
261
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
254
262
|
}
|
|
255
263
|
}
|
|
256
264
|
|
|
@@ -294,7 +302,7 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
294
302
|
// Reset the default option text
|
|
295
303
|
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
296
304
|
if (defaultOption) {
|
|
297
|
-
defaultOption.textContent = '
|
|
305
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
298
306
|
}
|
|
299
307
|
}
|
|
300
308
|
|
|
@@ -303,12 +311,12 @@ async function loadModelStandardized(modelId, options = {}) {
|
|
|
303
311
|
onLoadingEnd(modelId, false);
|
|
304
312
|
}
|
|
305
313
|
|
|
306
|
-
// Call error callback
|
|
314
|
+
// Call error callback and always show default error banner as fallback
|
|
307
315
|
if (onError) {
|
|
308
316
|
onError(error, modelId);
|
|
309
|
-
} else {
|
|
310
|
-
showErrorBanner('Failed to load model: ' + error.message);
|
|
311
317
|
}
|
|
318
|
+
// Always show error banner to ensure user sees the error
|
|
319
|
+
showErrorBanner('Failed to load model: ' + error.message);
|
|
312
320
|
|
|
313
321
|
return false;
|
|
314
322
|
}
|
|
@@ -474,6 +482,8 @@ function createModelNameWithLabels(modelId, allModels) {
|
|
|
474
482
|
labelClass = 'reranking';
|
|
475
483
|
} else if (labelLower === 'coding') {
|
|
476
484
|
labelClass = 'coding';
|
|
485
|
+
} else if (labelLower === 'tool-calling') {
|
|
486
|
+
labelClass = 'tool-calling';
|
|
477
487
|
}
|
|
478
488
|
labelSpan.className = `model-label ${labelClass}`;
|
|
479
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;
|
|
@@ -157,9 +163,8 @@ body::before {
|
|
|
157
163
|
margin-bottom: 2em;
|
|
158
164
|
border-radius: 8px;
|
|
159
165
|
border: 1px solid #e0e0e0;
|
|
160
|
-
max-width: 1000px;
|
|
161
166
|
min-width: 320px;
|
|
162
|
-
width:
|
|
167
|
+
width: 100%;
|
|
163
168
|
margin-left: 1rem;
|
|
164
169
|
margin-right: 1rem;
|
|
165
170
|
}
|
|
@@ -230,49 +235,44 @@ body::before {
|
|
|
230
235
|
animation: pulse-glow 2s infinite;
|
|
231
236
|
}
|
|
232
237
|
|
|
233
|
-
/* Online status (
|
|
238
|
+
/* Online model unloaded status (yellow) */
|
|
234
239
|
.model-status-indicator.online .status-light {
|
|
235
|
-
background:
|
|
240
|
+
background: var(--status-yellow);
|
|
236
241
|
box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
|
|
237
242
|
}
|
|
238
243
|
|
|
239
244
|
.model-status-indicator.online .status-light::before {
|
|
240
|
-
background:
|
|
245
|
+
background: var(--status-yellow);
|
|
241
246
|
}
|
|
242
247
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
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);
|
|
246
256
|
}
|
|
247
257
|
|
|
248
258
|
/* Offline status (red) */
|
|
249
259
|
.model-status-indicator.offline .status-light {
|
|
250
|
-
background:
|
|
260
|
+
background: var(--status-red);
|
|
251
261
|
box-shadow: 0 0 8px rgba(220, 53, 69, 0.6);
|
|
252
262
|
}
|
|
253
263
|
|
|
254
264
|
.model-status-indicator.offline .status-light::before {
|
|
255
|
-
background:
|
|
256
|
-
}
|
|
257
|
-
|
|
258
|
-
.model-status-indicator.offline .model-status-text {
|
|
259
|
-
color: #dc3545;
|
|
260
|
-
font-weight: 600;
|
|
265
|
+
background: var(--status-red);
|
|
261
266
|
}
|
|
262
267
|
|
|
263
|
-
/*
|
|
268
|
+
/* Online model loaded status (with model name) */
|
|
264
269
|
.model-status-indicator.loaded .status-light {
|
|
265
|
-
background:
|
|
270
|
+
background: var(--status-green);
|
|
266
271
|
box-shadow: 0 0 8px rgba(40, 167, 69, 0.6);
|
|
267
272
|
}
|
|
268
273
|
|
|
269
274
|
.model-status-indicator.loaded .status-light::before {
|
|
270
|
-
background:
|
|
271
|
-
}
|
|
272
|
-
|
|
273
|
-
.model-status-indicator.loaded .model-status-text {
|
|
274
|
-
color: #28a745;
|
|
275
|
-
font-weight: 600;
|
|
275
|
+
background: var(--status-green);
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
@keyframes pulse-glow {
|
|
@@ -286,25 +286,99 @@ body::before {
|
|
|
286
286
|
}
|
|
287
287
|
}
|
|
288
288
|
|
|
289
|
-
|
|
290
|
-
|
|
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;
|
|
291
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;
|
|
292
364
|
}
|
|
293
365
|
|
|
294
366
|
.model-action-btn {
|
|
295
|
-
display:
|
|
367
|
+
display: none; /* Hide by default */
|
|
296
368
|
align-items: center;
|
|
297
369
|
justify-content: center;
|
|
298
|
-
width:
|
|
299
|
-
height:
|
|
370
|
+
width: 1.1em;
|
|
371
|
+
height: 1.1em;
|
|
300
372
|
background: transparent;
|
|
301
373
|
border: none;
|
|
302
374
|
border-radius: 50%;
|
|
303
375
|
cursor: pointer;
|
|
304
376
|
transition: all var(--transition-fast);
|
|
305
|
-
font-size:
|
|
377
|
+
font-size: 1.5rem;
|
|
306
378
|
color: #666;
|
|
307
|
-
margin-left: 0.
|
|
379
|
+
margin-left: 0.1rem;
|
|
380
|
+
margin-right: 0.2rem;
|
|
381
|
+
line-height: 1;
|
|
308
382
|
}
|
|
309
383
|
|
|
310
384
|
.model-action-btn:hover {
|
|
@@ -312,6 +386,11 @@ body::before {
|
|
|
312
386
|
color: #333;
|
|
313
387
|
}
|
|
314
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
|
+
|
|
315
394
|
.tab-content {
|
|
316
395
|
display: none;
|
|
317
396
|
padding: 2em;
|
|
@@ -327,15 +406,16 @@ body::before {
|
|
|
327
406
|
.chat-container {
|
|
328
407
|
display: flex;
|
|
329
408
|
flex-direction: column;
|
|
330
|
-
height: calc(100vh - 650px); /* Subtract space for navbar, title, wall of logos, etc */
|
|
331
409
|
min-height: 300px;
|
|
332
|
-
|
|
333
|
-
max-width:
|
|
410
|
+
min-width: 300px;
|
|
411
|
+
max-width: 100%;
|
|
334
412
|
width: 100%;
|
|
335
413
|
margin: 0 auto;
|
|
336
414
|
border: 1px solid #e0e0e0;
|
|
337
415
|
border-radius: 8px;
|
|
338
416
|
background: #fff;
|
|
417
|
+
resize: both;
|
|
418
|
+
overflow: auto;
|
|
339
419
|
}
|
|
340
420
|
|
|
341
421
|
.chat-history {
|
|
@@ -388,6 +468,21 @@ body::before {
|
|
|
388
468
|
align-self: flex-start;
|
|
389
469
|
}
|
|
390
470
|
|
|
471
|
+
.chat-message.system {
|
|
472
|
+
align-items: flex-start;
|
|
473
|
+
}
|
|
474
|
+
|
|
475
|
+
.chat-bubble.system {
|
|
476
|
+
background: linear-gradient(135deg, #f0f8f0 0%, #e8f5e8 100%);
|
|
477
|
+
color: #2d7f47;
|
|
478
|
+
border-bottom-left-radius: 4px;
|
|
479
|
+
align-self: flex-start;
|
|
480
|
+
border: 1px solid #c8e6c9;
|
|
481
|
+
font-style: normal;
|
|
482
|
+
font-weight: 500;
|
|
483
|
+
box-shadow: 0 1px 3px rgba(45, 127, 71, 0.1);
|
|
484
|
+
}
|
|
485
|
+
|
|
391
486
|
/* Markdown styling within chat bubbles */
|
|
392
487
|
.chat-bubble h1,
|
|
393
488
|
.chat-bubble h2,
|
|
@@ -570,7 +665,7 @@ body::before {
|
|
|
570
665
|
align-items: center;
|
|
571
666
|
}
|
|
572
667
|
|
|
573
|
-
|
|
668
|
+
#chat-input {
|
|
574
669
|
flex: 1;
|
|
575
670
|
padding: 0.5em;
|
|
576
671
|
border: 1px solid #ddd;
|
|
@@ -578,6 +673,16 @@ body::before {
|
|
|
578
673
|
background: #fff;
|
|
579
674
|
color: #222;
|
|
580
675
|
margin: 0;
|
|
676
|
+
resize: vertical;
|
|
677
|
+
min-height: 40px;
|
|
678
|
+
font-family: inherit;
|
|
679
|
+
}
|
|
680
|
+
|
|
681
|
+
/* Update placeholder style */
|
|
682
|
+
#chat-input::placeholder {
|
|
683
|
+
color: #aaa;
|
|
684
|
+
opacity: 1;
|
|
685
|
+
font-style: italic;
|
|
581
686
|
}
|
|
582
687
|
|
|
583
688
|
#attachment-indicator {
|
|
@@ -849,6 +954,10 @@ body::before {
|
|
|
849
954
|
background-color: var(--info-primary);
|
|
850
955
|
}
|
|
851
956
|
|
|
957
|
+
.model-label.tool-calling {
|
|
958
|
+
background-color: #FFB74D;
|
|
959
|
+
}
|
|
960
|
+
|
|
852
961
|
.model-label.other {
|
|
853
962
|
background-color: var(--success-primary);
|
|
854
963
|
}
|
|
@@ -1081,6 +1190,7 @@ body::before {
|
|
|
1081
1190
|
|
|
1082
1191
|
#register-model-name:focus {
|
|
1083
1192
|
border-color: #e6b800;
|
|
1193
|
+
box-shadow: 0 2px 12px rgba(230,184,0,0.25);
|
|
1084
1194
|
}
|
|
1085
1195
|
|
|
1086
1196
|
.form-input-wrapper {
|
|
@@ -1897,7 +2007,6 @@ body::before {
|
|
|
1897
2007
|
|
|
1898
2008
|
.category-content {
|
|
1899
2009
|
display: none;
|
|
1900
|
-
padding: 0;
|
|
1901
2010
|
}
|
|
1902
2011
|
|
|
1903
2012
|
.category-content.expanded {
|
|
@@ -2166,43 +2275,4 @@ body::before {
|
|
|
2166
2275
|
0 8px 25px rgba(200, 88, 108, 0.2),
|
|
2167
2276
|
0 3px 10px rgba(0, 0, 0, 0.1),
|
|
2168
2277
|
inset 0 1px 0 rgba(255, 255, 255, 0.9);
|
|
2169
|
-
}
|
|
2170
|
-
|
|
2171
|
-
/* Model select dropdown in chat */
|
|
2172
|
-
.model-select {
|
|
2173
|
-
padding: 0.5rem 0.75rem;
|
|
2174
|
-
border: 1px solid #ddd;
|
|
2175
|
-
border-radius: 6px;
|
|
2176
|
-
background: white;
|
|
2177
|
-
font-size: 0.9rem;
|
|
2178
|
-
min-width: 180px;
|
|
2179
|
-
margin-right: 0.75rem;
|
|
2180
|
-
cursor: pointer;
|
|
2181
|
-
transition: all var(--transition-fast);
|
|
2182
|
-
}
|
|
2183
|
-
|
|
2184
|
-
.model-select:focus {
|
|
2185
|
-
outline: none;
|
|
2186
|
-
border-color: var(--accent-gold);
|
|
2187
|
-
box-shadow: 0 0 0 2px rgba(255, 193, 7, 0.2);
|
|
2188
|
-
}
|
|
2189
|
-
|
|
2190
|
-
.model-select:disabled {
|
|
2191
|
-
background: #f5f5f5;
|
|
2192
|
-
cursor: not-allowed;
|
|
2193
|
-
opacity: 0.7;
|
|
2194
|
-
}
|
|
2195
|
-
|
|
2196
|
-
.model-select option {
|
|
2197
|
-
padding: 0.5rem;
|
|
2198
|
-
}
|
|
2199
|
-
|
|
2200
|
-
.chat-input-row {
|
|
2201
|
-
display: flex;
|
|
2202
|
-
align-items: center;
|
|
2203
|
-
gap: 0.5rem;
|
|
2204
|
-
}
|
|
2205
|
-
|
|
2206
|
-
.input-with-indicator {
|
|
2207
|
-
flex: 1;
|
|
2208
|
-
}
|
|
2278
|
+
}
|