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

@@ -0,0 +1,491 @@
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
+ const banner = document.getElementById('error-banner');
58
+ if (!banner) return;
59
+ const msgEl = document.getElementById('error-banner-msg');
60
+ const fullMsg = msg + '\nCheck the Lemonade Server logs via the system tray app for more information.';
61
+ if (msgEl) {
62
+ msgEl.textContent = fullMsg;
63
+ } else {
64
+ banner.textContent = fullMsg;
65
+ }
66
+ banner.style.display = 'flex';
67
+ }
68
+
69
+ function hideErrorBanner() {
70
+ const banner = document.getElementById('error-banner');
71
+ if (banner) banner.style.display = 'none';
72
+ }
73
+
74
+ // Helper fetch wrappers that surface server error details
75
+ async function httpRequest(url, options = {}) {
76
+ const resp = await fetch(url, options);
77
+ if (!resp.ok) {
78
+ let detail = resp.statusText || 'Request failed';
79
+ try {
80
+ const contentType = resp.headers.get('content-type') || '';
81
+ if (contentType.includes('application/json')) {
82
+ const data = await resp.json();
83
+ if (data && data.detail) detail = data.detail;
84
+ } else {
85
+ const text = await resp.text();
86
+ if (text) detail = text.trim();
87
+ }
88
+ } catch (_) {}
89
+ throw new Error(detail);
90
+ }
91
+ return resp;
92
+ }
93
+
94
+ async function httpJson(url, options = {}) {
95
+ const resp = await httpRequest(url, options);
96
+ return await resp.json();
97
+ }
98
+
99
+ // Centralized function to update the status indicator
100
+ function updateStatusIndicator(text, state = 'default') {
101
+ const statusText = document.getElementById('model-status-text');
102
+ const statusLight = document.getElementById('status-light');
103
+ const indicator = document.getElementById('model-status-indicator');
104
+
105
+ if (statusText) {
106
+ statusText.textContent = text;
107
+ }
108
+
109
+ if (statusLight) {
110
+ // Set the status light class based on state
111
+ switch (state) {
112
+ case 'loading':
113
+ statusLight.className = 'status-light loading';
114
+ break;
115
+ case 'loaded':
116
+ case 'online':
117
+ statusLight.className = 'status-light online';
118
+ break;
119
+ case 'offline':
120
+ statusLight.className = 'status-light offline';
121
+ break;
122
+ case 'error':
123
+ statusLight.className = 'status-light offline'; // Use offline styling for errors
124
+ break;
125
+ default:
126
+ statusLight.className = 'status-light';
127
+ break;
128
+ }
129
+ }
130
+
131
+ if (indicator) {
132
+ // Also update the indicator container class for consistent styling
133
+ switch (state) {
134
+ case 'loading':
135
+ indicator.className = 'model-status-indicator loading';
136
+ break;
137
+ case 'loaded':
138
+ indicator.className = 'model-status-indicator loaded';
139
+ break;
140
+ case 'online':
141
+ indicator.className = 'model-status-indicator online';
142
+ break;
143
+ case 'offline':
144
+ indicator.className = 'model-status-indicator offline';
145
+ break;
146
+ case 'error':
147
+ indicator.className = 'model-status-indicator offline';
148
+ break;
149
+ default:
150
+ indicator.className = 'model-status-indicator';
151
+ break;
152
+ }
153
+ }
154
+ }
155
+
156
+ // Make status update function globally accessible
157
+ window.updateStatusIndicator = updateStatusIndicator;
158
+
159
+ // Centralized model loading function that can be used across tabs
160
+ async function loadModelStandardized(modelId, options = {}) {
161
+ const {
162
+ loadButton = null, // Optional load button to update
163
+ onLoadingStart = null, // Optional callback for custom loading UI
164
+ onLoadingEnd = null, // Optional callback for custom cleanup
165
+ onSuccess = null, // Optional callback on successful load
166
+ onError = null // Optional callback on error
167
+ } = options;
168
+
169
+ // Store original states for restoration on error
170
+ const originalStatusText = document.getElementById('model-status-text')?.textContent || '';
171
+
172
+ try {
173
+ // Update load button if provided
174
+ if (loadButton) {
175
+ loadButton.disabled = true;
176
+ loadButton.textContent = '⌛';
177
+ }
178
+
179
+ // Update status indicator to show loading state
180
+ updateStatusIndicator(`Loading ${modelId}...`, 'loading');
181
+
182
+ // Update chat dropdown and send button to show loading state
183
+ const modelSelect = document.getElementById('model-select');
184
+ const sendBtn = document.getElementById('send-btn');
185
+ if (modelSelect && sendBtn) {
186
+ // Ensure the model exists in the dropdown options
187
+ let modelOption = modelSelect.querySelector(`option[value="${modelId}"]`);
188
+ if (!modelOption && window.installedModels && window.installedModels.has(modelId)) {
189
+ // Add the model to the dropdown if it doesn't exist but is installed
190
+ modelOption = document.createElement('option');
191
+ modelOption.value = modelId;
192
+ modelOption.textContent = modelId;
193
+ modelSelect.appendChild(modelOption);
194
+ }
195
+
196
+ // Set the dropdown to the new model and disable it
197
+ if (modelOption) {
198
+ modelSelect.value = modelId;
199
+ }
200
+ modelSelect.disabled = true;
201
+ sendBtn.disabled = true;
202
+ sendBtn.textContent = 'Loading...';
203
+
204
+ // Update the loading option text
205
+ const loadingOption = modelSelect.querySelector('option[value=""]');
206
+ if (loadingOption) {
207
+ loadingOption.textContent = `Loading ${modelId}...`;
208
+ }
209
+ }
210
+
211
+ // Call custom loading start callback
212
+ if (onLoadingStart) {
213
+ onLoadingStart(modelId);
214
+ }
215
+
216
+ // Make the API call to load the model
217
+ await httpRequest(getServerBaseUrl() + '/api/v1/load', {
218
+ method: 'POST',
219
+ headers: { 'Content-Type': 'application/json' },
220
+ body: JSON.stringify({ model_name: modelId })
221
+ });
222
+
223
+ // Update model status indicator after successful load
224
+ if (window.updateModelStatusIndicator) {
225
+ await window.updateModelStatusIndicator();
226
+ }
227
+
228
+ // Update chat dropdown value
229
+ if (window.updateModelSelectValue) {
230
+ window.updateModelSelectValue();
231
+ }
232
+
233
+ // Update attachment button state
234
+ if (window.updateAttachmentButtonState) {
235
+ window.updateAttachmentButtonState();
236
+ }
237
+
238
+ // Reset load button if provided
239
+ if (loadButton) {
240
+ loadButton.disabled = false;
241
+ loadButton.textContent = 'Load';
242
+ }
243
+
244
+ // Reset chat controls
245
+ if (modelSelect && sendBtn) {
246
+ modelSelect.disabled = false;
247
+ sendBtn.disabled = false;
248
+ sendBtn.textContent = 'Send';
249
+
250
+ // Reset the default option text
251
+ const defaultOption = modelSelect.querySelector('option[value=""]');
252
+ if (defaultOption) {
253
+ defaultOption.textContent = 'Pick a model';
254
+ }
255
+ }
256
+
257
+ // Call custom loading end callback
258
+ if (onLoadingEnd) {
259
+ onLoadingEnd(modelId, true);
260
+ }
261
+
262
+ // Call success callback
263
+ if (onSuccess) {
264
+ onSuccess(modelId);
265
+ }
266
+
267
+ return true;
268
+
269
+ } catch (error) {
270
+ console.error('Error loading model:', error);
271
+
272
+ // Reset load button if provided
273
+ if (loadButton) {
274
+ loadButton.disabled = false;
275
+ loadButton.textContent = 'Load';
276
+ }
277
+
278
+ // Reset status indicator on error
279
+ updateStatusIndicator(originalStatusText, 'error');
280
+
281
+ // Reset chat controls on error
282
+ const modelSelect = document.getElementById('model-select');
283
+ const sendBtn = document.getElementById('send-btn');
284
+ if (modelSelect && sendBtn) {
285
+ modelSelect.disabled = false;
286
+ sendBtn.disabled = false;
287
+ sendBtn.textContent = 'Send';
288
+
289
+ // Reset dropdown value
290
+ if (window.updateModelSelectValue) {
291
+ window.updateModelSelectValue();
292
+ }
293
+
294
+ // Reset the default option text
295
+ const defaultOption = modelSelect.querySelector('option[value=""]');
296
+ if (defaultOption) {
297
+ defaultOption.textContent = 'Pick a model';
298
+ }
299
+ }
300
+
301
+ // Call custom loading end callback
302
+ if (onLoadingEnd) {
303
+ onLoadingEnd(modelId, false);
304
+ }
305
+
306
+ // Call error callback or show default error
307
+ if (onError) {
308
+ onError(error, modelId);
309
+ } else {
310
+ showErrorBanner('Failed to load model: ' + error.message);
311
+ }
312
+
313
+ return false;
314
+ }
315
+ }
316
+
317
+ // Make standardized load function globally accessible
318
+ window.loadModelStandardized = loadModelStandardized;
319
+
320
+ // Tab switching logic
321
+ function showTab(tab, updateHash = true) {
322
+ document.getElementById('tab-chat').classList.remove('active');
323
+ document.getElementById('tab-models').classList.remove('active');
324
+ document.getElementById('tab-model-settings').classList.remove('active');
325
+ document.getElementById('content-chat').classList.remove('active');
326
+ document.getElementById('content-models').classList.remove('active');
327
+ document.getElementById('content-settings').classList.remove('active');
328
+
329
+ if (tab === 'chat') {
330
+ document.getElementById('tab-chat').classList.add('active');
331
+ document.getElementById('content-chat').classList.add('active');
332
+ if (updateHash) {
333
+ window.location.hash = 'llm-chat';
334
+ }
335
+ } else if (tab === 'models') {
336
+ document.getElementById('tab-models').classList.add('active');
337
+ document.getElementById('content-models').classList.add('active');
338
+ if (updateHash) {
339
+ window.location.hash = 'model-management';
340
+ }
341
+ // Ensure model management UI is refreshed with latest data when tab is shown
342
+ // Use setTimeout to ensure this runs after any pending initialization
343
+ setTimeout(() => {
344
+ if (window.refreshModelMgmtUI) {
345
+ window.refreshModelMgmtUI();
346
+ }
347
+ }, 0);
348
+ } else if (tab === 'settings') {
349
+ document.getElementById('tab-model-settings').classList.add('active');
350
+ document.getElementById('content-settings').classList.add('active');
351
+ if (updateHash) {
352
+ window.location.hash = 'model-settings';
353
+ }
354
+ }
355
+ }
356
+
357
+ // Handle hash changes for anchor navigation
358
+ function handleHashChange() {
359
+ const hash = window.location.hash.slice(1); // Remove the # symbol
360
+ if (hash === 'llm-chat') {
361
+ showTab('chat', false);
362
+ } else if (hash === 'model-management') {
363
+ showTab('models', false);
364
+ } else if (hash === 'model-settings') {
365
+ showTab('settings', false);
366
+ }
367
+ }
368
+
369
+ // Initialize tab based on URL hash on page load
370
+ function initializeTabFromHash() {
371
+ const hash = window.location.hash.slice(1);
372
+ if (hash === 'llm-chat') {
373
+ showTab('chat', false);
374
+ } else if (hash === 'model-management') {
375
+ showTab('models', false);
376
+ } else if (hash === 'model-settings') {
377
+ showTab('settings', false);
378
+ }
379
+ // If no hash or unrecognized hash, keep default (chat tab is already active)
380
+ }
381
+
382
+ // Listen for hash changes
383
+ window.addEventListener('hashchange', handleHashChange);
384
+
385
+ // Initialize on page load
386
+ document.addEventListener('DOMContentLoaded', initializeTabFromHash);
387
+
388
+ // Handle image load failures for app logos
389
+ function handleImageFailure(img) {
390
+ const logoItem = img.closest('.app-logo-item');
391
+ if (logoItem) {
392
+ logoItem.classList.add('image-failed');
393
+ }
394
+ }
395
+
396
+ // Set up image error handlers when DOM is loaded
397
+ document.addEventListener('DOMContentLoaded', function() {
398
+ const logoImages = document.querySelectorAll('.app-logo-img');
399
+ logoImages.forEach(function(img) {
400
+ let imageLoaded = false;
401
+
402
+ img.addEventListener('load', function() {
403
+ imageLoaded = true;
404
+ });
405
+
406
+ img.addEventListener('error', function() {
407
+ if (!imageLoaded) {
408
+ handleImageFailure(this);
409
+ }
410
+ });
411
+
412
+ // Also check if image is already broken (cached failure)
413
+ if (img.complete && img.naturalWidth === 0) {
414
+ handleImageFailure(img);
415
+ }
416
+
417
+ // Timeout fallback for slow connections (5 seconds)
418
+ setTimeout(function() {
419
+ if (!imageLoaded && !img.complete) {
420
+ handleImageFailure(img);
421
+ }
422
+ }, 5000);
423
+ });
424
+ });
425
+
426
+ // Helper to get server base URL
427
+ function getServerBaseUrl() {
428
+ const port = window.SERVER_PORT || 8000;
429
+ const host = window.location.hostname || 'localhost';
430
+ return `http://${host}:${port}`;
431
+ }
432
+
433
+ // Check if current model supports vision
434
+ function isVisionModel(modelId) {
435
+ const allModels = window.SERVER_MODELS || {};
436
+ const modelData = allModels[modelId];
437
+ if (modelData && modelData.labels && Array.isArray(modelData.labels)) {
438
+ return modelData.labels.some(label => label.toLowerCase() === 'vision');
439
+ }
440
+ return false;
441
+ }
442
+
443
+ // Helper function to create model name with labels (moved from models.js for chat use)
444
+ function createModelNameWithLabels(modelId, allModels) {
445
+ // Create container for model name and labels
446
+ const container = document.createElement('div');
447
+ container.className = 'model-labels-container';
448
+
449
+ // Add model name
450
+ const nameSpan = document.createElement('span');
451
+ nameSpan.textContent = modelId;
452
+ container.appendChild(nameSpan);
453
+
454
+ // Add labels if they exist
455
+ const modelData = allModels[modelId];
456
+ if (modelData && modelData.labels && Array.isArray(modelData.labels)) {
457
+ modelData.labels.forEach(label => {
458
+ const labelLower = label.toLowerCase();
459
+
460
+ // Skip "hot" labels since they have their own section
461
+ if (labelLower === 'hot') {
462
+ return;
463
+ }
464
+
465
+ const labelSpan = document.createElement('span');
466
+ let labelClass = 'other';
467
+ if (labelLower === 'vision') {
468
+ labelClass = 'vision';
469
+ } else if (labelLower === 'embeddings') {
470
+ labelClass = 'embeddings';
471
+ } else if (labelLower === 'reasoning') {
472
+ labelClass = 'reasoning';
473
+ } else if (labelLower === 'reranking') {
474
+ labelClass = 'reranking';
475
+ } else if (labelLower === 'coding') {
476
+ labelClass = 'coding';
477
+ }
478
+ labelSpan.className = `model-label ${labelClass}`;
479
+ labelSpan.textContent = label;
480
+ container.appendChild(labelSpan);
481
+ });
482
+ }
483
+
484
+ return container;
485
+ }
486
+
487
+ // Initialize everything when DOM is loaded
488
+ document.addEventListener('DOMContentLoaded', function() {
489
+ // Model status and browser management is now handled by models.js
490
+ // This shared initialization only handles truly shared functionality
491
+ });