lemonade-sdk 9.1.1__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.
- lemonade/__init__.py +5 -0
- lemonade/api.py +180 -0
- lemonade/cache.py +92 -0
- lemonade/cli.py +173 -0
- lemonade/common/__init__.py +0 -0
- lemonade/common/build.py +176 -0
- lemonade/common/cli_helpers.py +139 -0
- lemonade/common/exceptions.py +98 -0
- lemonade/common/filesystem.py +368 -0
- lemonade/common/inference_engines.py +408 -0
- lemonade/common/network.py +93 -0
- lemonade/common/printing.py +110 -0
- lemonade/common/status.py +471 -0
- lemonade/common/system_info.py +1411 -0
- lemonade/common/test_helpers.py +28 -0
- lemonade/profilers/__init__.py +1 -0
- lemonade/profilers/agt_power.py +437 -0
- lemonade/profilers/hwinfo_power.py +429 -0
- lemonade/profilers/memory_tracker.py +259 -0
- lemonade/profilers/profiler.py +58 -0
- lemonade/sequence.py +363 -0
- lemonade/state.py +159 -0
- lemonade/tools/__init__.py +1 -0
- lemonade/tools/accuracy.py +432 -0
- lemonade/tools/adapter.py +114 -0
- lemonade/tools/bench.py +302 -0
- lemonade/tools/flm/__init__.py +1 -0
- lemonade/tools/flm/utils.py +305 -0
- lemonade/tools/huggingface/bench.py +187 -0
- lemonade/tools/huggingface/load.py +235 -0
- lemonade/tools/huggingface/utils.py +359 -0
- lemonade/tools/humaneval.py +264 -0
- lemonade/tools/llamacpp/bench.py +255 -0
- lemonade/tools/llamacpp/load.py +222 -0
- lemonade/tools/llamacpp/utils.py +1260 -0
- lemonade/tools/management_tools.py +319 -0
- lemonade/tools/mmlu.py +319 -0
- lemonade/tools/oga/__init__.py +0 -0
- lemonade/tools/oga/bench.py +120 -0
- lemonade/tools/oga/load.py +804 -0
- lemonade/tools/oga/migration.py +403 -0
- lemonade/tools/oga/utils.py +462 -0
- lemonade/tools/perplexity.py +147 -0
- lemonade/tools/prompt.py +263 -0
- lemonade/tools/report/__init__.py +0 -0
- lemonade/tools/report/llm_report.py +203 -0
- lemonade/tools/report/table.py +899 -0
- lemonade/tools/server/__init__.py +0 -0
- lemonade/tools/server/flm.py +133 -0
- lemonade/tools/server/llamacpp.py +320 -0
- lemonade/tools/server/serve.py +2123 -0
- lemonade/tools/server/static/favicon.ico +0 -0
- lemonade/tools/server/static/index.html +279 -0
- lemonade/tools/server/static/js/chat.js +1059 -0
- lemonade/tools/server/static/js/model-settings.js +183 -0
- lemonade/tools/server/static/js/models.js +1395 -0
- lemonade/tools/server/static/js/shared.js +556 -0
- lemonade/tools/server/static/logs.html +191 -0
- lemonade/tools/server/static/styles.css +2654 -0
- lemonade/tools/server/static/webapp.html +321 -0
- lemonade/tools/server/tool_calls.py +153 -0
- lemonade/tools/server/tray.py +664 -0
- lemonade/tools/server/utils/macos_tray.py +226 -0
- lemonade/tools/server/utils/port.py +77 -0
- lemonade/tools/server/utils/thread.py +85 -0
- lemonade/tools/server/utils/windows_tray.py +408 -0
- lemonade/tools/server/webapp.py +34 -0
- lemonade/tools/server/wrapped_server.py +559 -0
- lemonade/tools/tool.py +374 -0
- lemonade/version.py +1 -0
- lemonade_install/__init__.py +1 -0
- lemonade_install/install.py +239 -0
- lemonade_sdk-9.1.1.dist-info/METADATA +276 -0
- lemonade_sdk-9.1.1.dist-info/RECORD +84 -0
- lemonade_sdk-9.1.1.dist-info/WHEEL +5 -0
- lemonade_sdk-9.1.1.dist-info/entry_points.txt +5 -0
- lemonade_sdk-9.1.1.dist-info/licenses/LICENSE +201 -0
- lemonade_sdk-9.1.1.dist-info/licenses/NOTICE.md +47 -0
- lemonade_sdk-9.1.1.dist-info/top_level.txt +3 -0
- lemonade_server/cli.py +805 -0
- lemonade_server/model_manager.py +758 -0
- lemonade_server/pydantic_models.py +159 -0
- lemonade_server/server_models.json +643 -0
- lemonade_server/settings.py +39 -0
|
@@ -0,0 +1,556 @@
|
|
|
1
|
+
// Configure MathJax
|
|
2
|
+
window.MathJax = {
|
|
3
|
+
tex: {
|
|
4
|
+
inlineMath: [['\\(', '\\)'], ['$', '$']],
|
|
5
|
+
displayMath: [['\\[', '\\]'], ['$$', '$$']],
|
|
6
|
+
processEscapes: true,
|
|
7
|
+
processEnvironments: true
|
|
8
|
+
},
|
|
9
|
+
options: {
|
|
10
|
+
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
|
|
11
|
+
}
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
// Configure marked.js for safe HTML rendering
|
|
15
|
+
marked.setOptions({
|
|
16
|
+
breaks: true,
|
|
17
|
+
gfm: true,
|
|
18
|
+
sanitize: false,
|
|
19
|
+
smartLists: true,
|
|
20
|
+
smartypants: true
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
// Function to unescape JSON strings
|
|
24
|
+
function unescapeJsonString(str) {
|
|
25
|
+
try {
|
|
26
|
+
return str.replace(/\\n/g, '\n')
|
|
27
|
+
.replace(/\\t/g, '\t')
|
|
28
|
+
.replace(/\\r/g, '\r')
|
|
29
|
+
.replace(/\\"/g, '"')
|
|
30
|
+
.replace(/\\\\/g, '\\');
|
|
31
|
+
} catch (error) {
|
|
32
|
+
console.error('Error unescaping string:', error);
|
|
33
|
+
return str;
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Function to safely render markdown with MathJax support
|
|
38
|
+
function renderMarkdown(text) {
|
|
39
|
+
try {
|
|
40
|
+
const html = marked.parse(text);
|
|
41
|
+
// Trigger MathJax to process the new content
|
|
42
|
+
if (window.MathJax && window.MathJax.typesetPromise) {
|
|
43
|
+
// Use a timeout to ensure DOM is updated before typesetting
|
|
44
|
+
setTimeout(() => {
|
|
45
|
+
window.MathJax.typesetPromise();
|
|
46
|
+
}, 0);
|
|
47
|
+
}
|
|
48
|
+
return html;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
console.error('Error rendering markdown:', error);
|
|
51
|
+
return text; // fallback to plain text
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
// Display an error message in the banner
|
|
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
|
+
|
|
70
|
+
const banner = document.getElementById('error-banner');
|
|
71
|
+
if (!banner) return;
|
|
72
|
+
const msgEl = document.getElementById('error-banner-msg');
|
|
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
|
+
|
|
96
|
+
if (msgEl) {
|
|
97
|
+
msgEl.innerHTML = fullMsg;
|
|
98
|
+
} else {
|
|
99
|
+
banner.innerHTML = fullMsg;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
banner.style.backgroundColor = backgroundColor;
|
|
103
|
+
banner.style.color = color;
|
|
104
|
+
banner.style.display = 'flex';
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function hideErrorBanner() {
|
|
108
|
+
const banner = document.getElementById('error-banner');
|
|
109
|
+
if (banner) banner.style.display = 'none';
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Helper fetch wrappers that surface server error details
|
|
113
|
+
async function httpRequest(url, options = {}) {
|
|
114
|
+
const resp = await fetch(url, options);
|
|
115
|
+
if (!resp.ok) {
|
|
116
|
+
let detail = resp.statusText || 'Request failed';
|
|
117
|
+
try {
|
|
118
|
+
const contentType = resp.headers.get('content-type') || '';
|
|
119
|
+
if (contentType.includes('application/json')) {
|
|
120
|
+
const data = await resp.json();
|
|
121
|
+
if (data && data.detail) detail = data.detail;
|
|
122
|
+
} else {
|
|
123
|
+
const text = await resp.text();
|
|
124
|
+
if (text) detail = text.trim();
|
|
125
|
+
}
|
|
126
|
+
} catch (_) {}
|
|
127
|
+
throw new Error(detail);
|
|
128
|
+
}
|
|
129
|
+
return resp;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
async function httpJson(url, options = {}) {
|
|
133
|
+
const resp = await httpRequest(url, options);
|
|
134
|
+
return await resp.json();
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
// Centralized function to update the status indicator
|
|
138
|
+
function updateStatusIndicator(text, state = 'default') {
|
|
139
|
+
const statusText = document.getElementById('model-status-text');
|
|
140
|
+
const statusLight = document.getElementById('status-light');
|
|
141
|
+
const indicator = document.getElementById('model-status-indicator');
|
|
142
|
+
|
|
143
|
+
if (statusText) {
|
|
144
|
+
statusText.textContent = text;
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
if (statusLight) {
|
|
148
|
+
// Set the status light class based on state
|
|
149
|
+
switch (state) {
|
|
150
|
+
case 'loading':
|
|
151
|
+
statusLight.className = 'status-light loading';
|
|
152
|
+
break;
|
|
153
|
+
case 'loaded':
|
|
154
|
+
case 'online':
|
|
155
|
+
statusLight.className = 'status-light online';
|
|
156
|
+
break;
|
|
157
|
+
case 'offline':
|
|
158
|
+
statusLight.className = 'status-light offline';
|
|
159
|
+
break;
|
|
160
|
+
case 'error':
|
|
161
|
+
statusLight.className = 'status-light offline'; // Use offline styling for errors
|
|
162
|
+
break;
|
|
163
|
+
default:
|
|
164
|
+
statusLight.className = 'status-light';
|
|
165
|
+
break;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
if (indicator) {
|
|
170
|
+
// Also update the indicator container class for consistent styling
|
|
171
|
+
switch (state) {
|
|
172
|
+
case 'loading':
|
|
173
|
+
indicator.className = 'model-status-indicator loading';
|
|
174
|
+
break;
|
|
175
|
+
case 'loaded':
|
|
176
|
+
indicator.className = 'model-status-indicator loaded';
|
|
177
|
+
break;
|
|
178
|
+
case 'online':
|
|
179
|
+
indicator.className = 'model-status-indicator online';
|
|
180
|
+
break;
|
|
181
|
+
case 'offline':
|
|
182
|
+
indicator.className = 'model-status-indicator offline';
|
|
183
|
+
break;
|
|
184
|
+
case 'error':
|
|
185
|
+
indicator.className = 'model-status-indicator offline';
|
|
186
|
+
break;
|
|
187
|
+
default:
|
|
188
|
+
indicator.className = 'model-status-indicator';
|
|
189
|
+
break;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// Make status update function globally accessible
|
|
195
|
+
window.updateStatusIndicator = updateStatusIndicator;
|
|
196
|
+
|
|
197
|
+
// Centralized model loading function that can be used across tabs
|
|
198
|
+
async function loadModelStandardized(modelId, options = {}) {
|
|
199
|
+
const {
|
|
200
|
+
loadButton = null, // Optional load button to update
|
|
201
|
+
onLoadingStart = null, // Optional callback for custom loading UI
|
|
202
|
+
onLoadingEnd = null, // Optional callback for custom cleanup
|
|
203
|
+
onSuccess = null, // Optional callback on successful load
|
|
204
|
+
onError = null // Optional callback on error
|
|
205
|
+
} = options;
|
|
206
|
+
|
|
207
|
+
// Store original states for restoration on error
|
|
208
|
+
const originalStatusText = document.getElementById('model-status-text')?.textContent || '';
|
|
209
|
+
|
|
210
|
+
// Track this load operation as active to prevent polling interference
|
|
211
|
+
if (window.activeOperations) {
|
|
212
|
+
window.activeOperations.add(modelId);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
try {
|
|
216
|
+
// Update load button if provided
|
|
217
|
+
if (loadButton) {
|
|
218
|
+
loadButton.disabled = true;
|
|
219
|
+
loadButton.textContent = '⏳';
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Update status indicator to show loading state
|
|
223
|
+
updateStatusIndicator(`Loading ${modelId}...`, 'loading');
|
|
224
|
+
|
|
225
|
+
// Update chat dropdown and send button to show loading state
|
|
226
|
+
const modelSelect = document.getElementById('model-select');
|
|
227
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
228
|
+
if (modelSelect && sendBtn) {
|
|
229
|
+
// Ensure the model exists in the dropdown options
|
|
230
|
+
let modelOption = modelSelect.querySelector(`option[value="${modelId}"]`);
|
|
231
|
+
if (!modelOption && window.installedModels && window.installedModels.has(modelId)) {
|
|
232
|
+
// Add the model to the dropdown if it doesn't exist but is installed
|
|
233
|
+
modelOption = document.createElement('option');
|
|
234
|
+
modelOption.value = modelId;
|
|
235
|
+
modelOption.textContent = modelId;
|
|
236
|
+
modelSelect.appendChild(modelOption);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Set the dropdown to the new model and disable it
|
|
240
|
+
if (modelOption) {
|
|
241
|
+
modelSelect.value = modelId;
|
|
242
|
+
}
|
|
243
|
+
modelSelect.disabled = true;
|
|
244
|
+
sendBtn.disabled = true;
|
|
245
|
+
sendBtn.textContent = 'Loading...';
|
|
246
|
+
|
|
247
|
+
// Update the loading option text
|
|
248
|
+
const loadingOption = modelSelect.querySelector('option[value=""]');
|
|
249
|
+
if (loadingOption) {
|
|
250
|
+
loadingOption.textContent = `Loading ${modelId}...`;
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// Call custom loading start callback
|
|
255
|
+
if (onLoadingStart) {
|
|
256
|
+
onLoadingStart(modelId);
|
|
257
|
+
}
|
|
258
|
+
|
|
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
|
+
|
|
268
|
+
await httpRequest(getServerBaseUrl() + '/api/v1/load', {
|
|
269
|
+
method: 'POST',
|
|
270
|
+
headers: { 'Content-Type': 'application/json' },
|
|
271
|
+
body: JSON.stringify(loadPayload)
|
|
272
|
+
});
|
|
273
|
+
|
|
274
|
+
// Update model status indicator after successful load
|
|
275
|
+
if (window.updateModelStatusIndicator) {
|
|
276
|
+
await window.updateModelStatusIndicator();
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// Update chat dropdown value
|
|
280
|
+
if (window.updateModelSelectValue) {
|
|
281
|
+
window.updateModelSelectValue();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Update attachment button state
|
|
285
|
+
if (window.updateAttachmentButtonState) {
|
|
286
|
+
window.updateAttachmentButtonState();
|
|
287
|
+
}
|
|
288
|
+
|
|
289
|
+
// Reset load button if provided
|
|
290
|
+
if (loadButton) {
|
|
291
|
+
loadButton.disabled = false;
|
|
292
|
+
loadButton.textContent = '🚀';
|
|
293
|
+
loadButton.classList.remove('loading');
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
// Reset chat controls
|
|
297
|
+
if (modelSelect && sendBtn) {
|
|
298
|
+
modelSelect.disabled = false;
|
|
299
|
+
sendBtn.disabled = false;
|
|
300
|
+
sendBtn.textContent = 'Send';
|
|
301
|
+
|
|
302
|
+
// Reset the default option text
|
|
303
|
+
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
304
|
+
if (defaultOption) {
|
|
305
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
306
|
+
}
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Call custom loading end callback
|
|
310
|
+
if (onLoadingEnd) {
|
|
311
|
+
onLoadingEnd(modelId, true);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Call success callback
|
|
315
|
+
if (onSuccess) {
|
|
316
|
+
onSuccess(modelId);
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// Remove from active operations on success
|
|
320
|
+
if (window.activeOperations) {
|
|
321
|
+
window.activeOperations.delete(modelId);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
return true;
|
|
325
|
+
|
|
326
|
+
} catch (error) {
|
|
327
|
+
console.error('Error loading model:', error);
|
|
328
|
+
|
|
329
|
+
// Reset load button if provided
|
|
330
|
+
if (loadButton) {
|
|
331
|
+
loadButton.disabled = false;
|
|
332
|
+
loadButton.textContent = '🚀';
|
|
333
|
+
loadButton.classList.remove('loading');
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
// Reset status indicator on error
|
|
337
|
+
updateStatusIndicator(originalStatusText, 'error');
|
|
338
|
+
|
|
339
|
+
// Reset chat controls on error
|
|
340
|
+
const modelSelect = document.getElementById('model-select');
|
|
341
|
+
const sendBtn = document.getElementById('toggle-btn');
|
|
342
|
+
if (modelSelect && sendBtn) {
|
|
343
|
+
modelSelect.disabled = false;
|
|
344
|
+
sendBtn.disabled = false;
|
|
345
|
+
sendBtn.textContent = 'Send';
|
|
346
|
+
|
|
347
|
+
// Reset dropdown value
|
|
348
|
+
if (window.updateModelSelectValue) {
|
|
349
|
+
window.updateModelSelectValue();
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
// Reset the default option text
|
|
353
|
+
const defaultOption = modelSelect.querySelector('option[value=""]');
|
|
354
|
+
if (defaultOption) {
|
|
355
|
+
defaultOption.textContent = 'Click to select a model ▼';
|
|
356
|
+
}
|
|
357
|
+
}
|
|
358
|
+
|
|
359
|
+
// Call custom loading end callback
|
|
360
|
+
if (onLoadingEnd) {
|
|
361
|
+
onLoadingEnd(modelId, false);
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
// Call error callback and always show default error banner as fallback
|
|
365
|
+
if (onError) {
|
|
366
|
+
onError(error, modelId);
|
|
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);
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Make standardized load function globally accessible
|
|
381
|
+
window.loadModelStandardized = loadModelStandardized;
|
|
382
|
+
|
|
383
|
+
// Tab switching logic
|
|
384
|
+
function showTab(tab, updateHash = true) {
|
|
385
|
+
document.getElementById('tab-chat').classList.remove('active');
|
|
386
|
+
document.getElementById('tab-models').classList.remove('active');
|
|
387
|
+
document.getElementById('tab-model-settings').classList.remove('active');
|
|
388
|
+
document.getElementById('content-chat').classList.remove('active');
|
|
389
|
+
document.getElementById('content-models').classList.remove('active');
|
|
390
|
+
document.getElementById('content-settings').classList.remove('active');
|
|
391
|
+
|
|
392
|
+
if (tab === 'chat') {
|
|
393
|
+
document.getElementById('tab-chat').classList.add('active');
|
|
394
|
+
document.getElementById('content-chat').classList.add('active');
|
|
395
|
+
if (updateHash) {
|
|
396
|
+
window.location.hash = 'llm-chat';
|
|
397
|
+
}
|
|
398
|
+
} else if (tab === 'models') {
|
|
399
|
+
document.getElementById('tab-models').classList.add('active');
|
|
400
|
+
document.getElementById('content-models').classList.add('active');
|
|
401
|
+
if (updateHash) {
|
|
402
|
+
window.location.hash = 'model-management';
|
|
403
|
+
}
|
|
404
|
+
// Ensure model management UI is refreshed with latest data when tab is shown
|
|
405
|
+
// Use setTimeout to ensure this runs after any pending initialization
|
|
406
|
+
setTimeout(() => {
|
|
407
|
+
if (window.refreshModelMgmtUI) {
|
|
408
|
+
window.refreshModelMgmtUI();
|
|
409
|
+
}
|
|
410
|
+
}, 0);
|
|
411
|
+
} else if (tab === 'settings') {
|
|
412
|
+
document.getElementById('tab-model-settings').classList.add('active');
|
|
413
|
+
document.getElementById('content-settings').classList.add('active');
|
|
414
|
+
if (updateHash) {
|
|
415
|
+
window.location.hash = 'model-settings';
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
// Handle hash changes for anchor navigation
|
|
421
|
+
function handleHashChange() {
|
|
422
|
+
const hash = window.location.hash.slice(1); // Remove the # symbol
|
|
423
|
+
if (hash === 'llm-chat') {
|
|
424
|
+
showTab('chat', false);
|
|
425
|
+
} else if (hash === 'model-management') {
|
|
426
|
+
showTab('models', false);
|
|
427
|
+
} else if (hash === 'model-settings') {
|
|
428
|
+
showTab('settings', false);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
// Initialize tab based on URL hash on page load
|
|
433
|
+
function initializeTabFromHash() {
|
|
434
|
+
const hash = window.location.hash.slice(1);
|
|
435
|
+
if (hash === 'llm-chat') {
|
|
436
|
+
showTab('chat', false);
|
|
437
|
+
} else if (hash === 'model-management') {
|
|
438
|
+
showTab('models', false);
|
|
439
|
+
} else if (hash === 'model-settings') {
|
|
440
|
+
showTab('settings', false);
|
|
441
|
+
}
|
|
442
|
+
// If no hash or unrecognized hash, keep default (chat tab is already active)
|
|
443
|
+
}
|
|
444
|
+
|
|
445
|
+
// Listen for hash changes
|
|
446
|
+
window.addEventListener('hashchange', handleHashChange);
|
|
447
|
+
|
|
448
|
+
// Initialize on page load
|
|
449
|
+
document.addEventListener('DOMContentLoaded', initializeTabFromHash);
|
|
450
|
+
|
|
451
|
+
// Handle image load failures for app logos
|
|
452
|
+
function handleImageFailure(img) {
|
|
453
|
+
const logoItem = img.closest('.app-logo-item');
|
|
454
|
+
if (logoItem) {
|
|
455
|
+
logoItem.classList.add('image-failed');
|
|
456
|
+
}
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// Set up image error handlers when DOM is loaded
|
|
460
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
461
|
+
const logoImages = document.querySelectorAll('.app-logo-img');
|
|
462
|
+
logoImages.forEach(function(img) {
|
|
463
|
+
let imageLoaded = false;
|
|
464
|
+
|
|
465
|
+
img.addEventListener('load', function() {
|
|
466
|
+
imageLoaded = true;
|
|
467
|
+
});
|
|
468
|
+
|
|
469
|
+
img.addEventListener('error', function() {
|
|
470
|
+
if (!imageLoaded) {
|
|
471
|
+
handleImageFailure(this);
|
|
472
|
+
}
|
|
473
|
+
});
|
|
474
|
+
|
|
475
|
+
// Also check if image is already broken (cached failure)
|
|
476
|
+
if (img.complete && img.naturalWidth === 0) {
|
|
477
|
+
handleImageFailure(img);
|
|
478
|
+
}
|
|
479
|
+
|
|
480
|
+
// Timeout fallback for slow connections (5 seconds)
|
|
481
|
+
setTimeout(function() {
|
|
482
|
+
if (!imageLoaded && !img.complete) {
|
|
483
|
+
handleImageFailure(img);
|
|
484
|
+
}
|
|
485
|
+
}, 5000);
|
|
486
|
+
});
|
|
487
|
+
});
|
|
488
|
+
|
|
489
|
+
// Helper to get server base URL
|
|
490
|
+
function getServerBaseUrl() {
|
|
491
|
+
const port = window.SERVER_PORT || 8000;
|
|
492
|
+
const host = window.location.hostname || 'localhost';
|
|
493
|
+
return `http://${host}:${port}`;
|
|
494
|
+
}
|
|
495
|
+
|
|
496
|
+
// Check if current model supports vision
|
|
497
|
+
function isVisionModel(modelId) {
|
|
498
|
+
const allModels = window.SERVER_MODELS || {};
|
|
499
|
+
const modelData = allModels[modelId];
|
|
500
|
+
if (modelData && modelData.labels && Array.isArray(modelData.labels)) {
|
|
501
|
+
return modelData.labels.some(label => label.toLowerCase() === 'vision');
|
|
502
|
+
}
|
|
503
|
+
return false;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
// Helper function to create model name with labels (moved from models.js for chat use)
|
|
507
|
+
function createModelNameWithLabels(modelId, allModels) {
|
|
508
|
+
// Create container for model name and labels
|
|
509
|
+
const container = document.createElement('div');
|
|
510
|
+
container.className = 'model-labels-container';
|
|
511
|
+
|
|
512
|
+
// Add model name
|
|
513
|
+
const nameSpan = document.createElement('span');
|
|
514
|
+
nameSpan.textContent = modelId;
|
|
515
|
+
container.appendChild(nameSpan);
|
|
516
|
+
|
|
517
|
+
// Add labels if they exist
|
|
518
|
+
const modelData = allModels[modelId];
|
|
519
|
+
if (modelData && modelData.labels && Array.isArray(modelData.labels)) {
|
|
520
|
+
modelData.labels.forEach(label => {
|
|
521
|
+
const labelLower = label.toLowerCase();
|
|
522
|
+
|
|
523
|
+
// Skip "hot" labels since they have their own section
|
|
524
|
+
if (labelLower === 'hot') {
|
|
525
|
+
return;
|
|
526
|
+
}
|
|
527
|
+
|
|
528
|
+
const labelSpan = document.createElement('span');
|
|
529
|
+
let labelClass = 'other';
|
|
530
|
+
if (labelLower === 'vision') {
|
|
531
|
+
labelClass = 'vision';
|
|
532
|
+
} else if (labelLower === 'embeddings') {
|
|
533
|
+
labelClass = 'embeddings';
|
|
534
|
+
} else if (labelLower === 'reasoning') {
|
|
535
|
+
labelClass = 'reasoning';
|
|
536
|
+
} else if (labelLower === 'reranking') {
|
|
537
|
+
labelClass = 'reranking';
|
|
538
|
+
} else if (labelLower === 'coding') {
|
|
539
|
+
labelClass = 'coding';
|
|
540
|
+
} else if (labelLower === 'tool-calling') {
|
|
541
|
+
labelClass = 'tool-calling';
|
|
542
|
+
}
|
|
543
|
+
labelSpan.className = `model-label ${labelClass}`;
|
|
544
|
+
labelSpan.textContent = label;
|
|
545
|
+
container.appendChild(labelSpan);
|
|
546
|
+
});
|
|
547
|
+
}
|
|
548
|
+
|
|
549
|
+
return container;
|
|
550
|
+
}
|
|
551
|
+
|
|
552
|
+
// Initialize everything when DOM is loaded
|
|
553
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
554
|
+
// Model status and browser management is now handled by models.js
|
|
555
|
+
// This shared initialization only handles truly shared functionality
|
|
556
|
+
});
|