lemonade-sdk 8.0.3__py3-none-any.whl → 8.0.5__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/api.py +50 -0
- lemonade/common/inference_engines.py +415 -0
- lemonade/common/system_info.py +493 -47
- lemonade/tools/humaneval.py +1 -1
- lemonade/tools/management_tools.py +53 -7
- lemonade/tools/mmlu.py +1 -1
- lemonade/tools/oga/load.py +1 -1
- lemonade/tools/perplexity.py +2 -2
- lemonade/tools/quark/quark_load.py +1 -1
- lemonade/tools/quark/quark_quantize.py +2 -2
- lemonade/tools/server/llamacpp.py +130 -9
- lemonade/tools/server/serve.py +102 -0
- lemonade/tools/server/static/styles.css +458 -55
- lemonade/tools/server/static/webapp.html +322 -35
- lemonade/version.py +1 -1
- lemonade_sdk-8.0.5.dist-info/METADATA +295 -0
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/RECORD +26 -25
- lemonade_server/cli.py +168 -22
- lemonade_server/model_manager.py +12 -2
- lemonade_server/pydantic_models.py +25 -1
- lemonade_server/server_models.json +46 -44
- lemonade_sdk-8.0.3.dist-info/METADATA +0 -183
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/WHEEL +0 -0
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/entry_points.txt +0 -0
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/licenses/LICENSE +0 -0
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/licenses/NOTICE.md +0 -0
- {lemonade_sdk-8.0.3.dist-info → lemonade_sdk-8.0.5.dist-info}/top_level.txt +0 -0
|
@@ -33,7 +33,47 @@
|
|
|
33
33
|
<input type="text" id="chat-input" placeholder="Type your message..." />
|
|
34
34
|
<button id="send-btn">Send</button>
|
|
35
35
|
</div>
|
|
36
|
-
</div>
|
|
36
|
+
</div>
|
|
37
|
+
<!-- App Suggestions Section -->
|
|
38
|
+
<div class="app-suggestions-section">
|
|
39
|
+
<div class="suggestion-text">
|
|
40
|
+
Use Lemonade with your favorite app
|
|
41
|
+
</div>
|
|
42
|
+
<div class="app-logos-grid">
|
|
43
|
+
<a href="https://lemonade-server.ai/docs/server/apps/open-webui/" target="_blank" class="app-logo-item" title="Open WebUI">
|
|
44
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/openwebui.jpg" alt="Open WebUI" class="app-logo-img">
|
|
45
|
+
<span class="app-name">Open WebUI</span>
|
|
46
|
+
</a>
|
|
47
|
+
<a href="https://lemonade-server.ai/docs/server/apps/continue/" target="_blank" class="app-logo-item" title="Continue">
|
|
48
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/continue_dev.png" alt="Continue" class="app-logo-img">
|
|
49
|
+
<span class="app-name">Continue</span>
|
|
50
|
+
</a>
|
|
51
|
+
<a href="https://github.com/amd/gaia" target="_blank" class="app-logo-item" title="Gaia">
|
|
52
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/gaia.ico" alt="Gaia" class="app-logo-img">
|
|
53
|
+
<span class="app-name">Gaia</span>
|
|
54
|
+
</a>
|
|
55
|
+
<a href="https://lemonade-server.ai/docs/server/apps/anythingLLM/" target="_blank" class="app-logo-item" title="AnythingLLM">
|
|
56
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/anything_llm.png" alt="AnythingLLM" class="app-logo-img">
|
|
57
|
+
<span class="app-name">AnythingLLM</span>
|
|
58
|
+
</a>
|
|
59
|
+
<a href="https://lemonade-server.ai/docs/server/apps/ai-dev-gallery/" target="_blank" class="app-logo-item" title="AI Dev Gallery">
|
|
60
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/ai_dev_gallery.webp" alt="AI Dev Gallery" class="app-logo-img">
|
|
61
|
+
<span class="app-name">AI Dev Gallery</span>
|
|
62
|
+
</a>
|
|
63
|
+
<a href="https://lemonade-server.ai/docs/server/apps/lm-eval/" target="_blank" class="app-logo-item" title="LM-Eval">
|
|
64
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/lm_eval.png" alt="LM-Eval" class="app-logo-img">
|
|
65
|
+
<span class="app-name">LM-Eval</span>
|
|
66
|
+
</a>
|
|
67
|
+
<a href="https://lemonade-server.ai/docs/server/apps/codeGPT/" target="_blank" class="app-logo-item" title="CodeGPT">
|
|
68
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/codegpt.jpg" alt="CodeGPT" class="app-logo-img">
|
|
69
|
+
<span class="app-name">CodeGPT</span>
|
|
70
|
+
</a>
|
|
71
|
+
<a href="https://github.com/lemonade-sdk/lemonade/blob/main/docs/server/apps/ai-toolkit.md" target="_blank" class="app-logo-item" title="AI Toolkit">
|
|
72
|
+
<img src="https://raw.githubusercontent.com/lemonade-sdk/assets/refs/heads/main/partner_logos/ai_toolkit.png" alt="AI Toolkit" class="app-logo-img">
|
|
73
|
+
<span class="app-name">AI Toolkit</span>
|
|
74
|
+
</a>
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
37
77
|
</div>
|
|
38
78
|
<div class="tab-content" id="content-models"> <div class="model-mgmt-register-form collapsed"> <h3 class="model-mgmt-form-title" onclick="toggleAddModelForm()">
|
|
39
79
|
Add a Model
|
|
@@ -109,7 +149,66 @@
|
|
|
109
149
|
<div class="copyright">Copyright 2025 AMD</div>
|
|
110
150
|
</footer>
|
|
111
151
|
<script src="https://cdn.jsdelivr.net/npm/openai@4.21.0/dist/openai.min.js"></script>
|
|
112
|
-
<script>
|
|
152
|
+
<script src="https://cdn.jsdelivr.net/npm/marked@9.1.0/marked.min.js"></script>
|
|
153
|
+
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
|
154
|
+
<script id="MathJax-script" async src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js"></script>
|
|
155
|
+
<script>
|
|
156
|
+
// Configure MathJax
|
|
157
|
+
window.MathJax = {
|
|
158
|
+
tex: {
|
|
159
|
+
inlineMath: [['\\(', '\\)'], ['$', '$']],
|
|
160
|
+
displayMath: [['\\[', '\\]'], ['$$', '$$']],
|
|
161
|
+
processEscapes: true,
|
|
162
|
+
processEnvironments: true
|
|
163
|
+
},
|
|
164
|
+
options: {
|
|
165
|
+
skipHtmlTags: ['script', 'noscript', 'style', 'textarea', 'pre']
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
</script>
|
|
169
|
+
<script>
|
|
170
|
+
// Configure marked.js for safe HTML rendering
|
|
171
|
+
marked.setOptions({
|
|
172
|
+
breaks: true,
|
|
173
|
+
gfm: true,
|
|
174
|
+
sanitize: false,
|
|
175
|
+
smartLists: true,
|
|
176
|
+
smartypants: true
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
// Function to unescape JSON strings
|
|
180
|
+
function unescapeJsonString(str) {
|
|
181
|
+
try {
|
|
182
|
+
return str.replace(/\\n/g, '\n')
|
|
183
|
+
.replace(/\\t/g, '\t')
|
|
184
|
+
.replace(/\\r/g, '\r')
|
|
185
|
+
.replace(/\\"/g, '"')
|
|
186
|
+
.replace(/\\\\/g, '\\');
|
|
187
|
+
} catch (error) {
|
|
188
|
+
console.error('Error unescaping string:', error);
|
|
189
|
+
return str;
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
// Function to safely render markdown with MathJax support
|
|
194
|
+
function renderMarkdown(text) {
|
|
195
|
+
try {
|
|
196
|
+
const html = marked.parse(text);
|
|
197
|
+
// Trigger MathJax to process the new content
|
|
198
|
+
if (window.MathJax && window.MathJax.typesetPromise) {
|
|
199
|
+
// Use a timeout to ensure DOM is updated before typesetting
|
|
200
|
+
setTimeout(() => {
|
|
201
|
+
window.MathJax.typesetPromise();
|
|
202
|
+
}, 0);
|
|
203
|
+
}
|
|
204
|
+
return html;
|
|
205
|
+
} catch (error) {
|
|
206
|
+
console.error('Error rendering markdown:', error);
|
|
207
|
+
return text; // fallback to plain text
|
|
208
|
+
}
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
// Tab switching logic
|
|
113
212
|
function showTab(tab, updateHash = true) {
|
|
114
213
|
document.getElementById('tab-chat').classList.remove('active');
|
|
115
214
|
document.getElementById('tab-models').classList.remove('active');
|
|
@@ -163,6 +262,44 @@
|
|
|
163
262
|
form.classList.toggle('collapsed');
|
|
164
263
|
}
|
|
165
264
|
|
|
265
|
+
// Handle image load failures for app logos
|
|
266
|
+
function handleImageFailure(img) {
|
|
267
|
+
const logoItem = img.closest('.app-logo-item');
|
|
268
|
+
if (logoItem) {
|
|
269
|
+
logoItem.classList.add('image-failed');
|
|
270
|
+
}
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
// Set up image error handlers when DOM is loaded
|
|
274
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
275
|
+
const logoImages = document.querySelectorAll('.app-logo-img');
|
|
276
|
+
logoImages.forEach(function(img) {
|
|
277
|
+
let imageLoaded = false;
|
|
278
|
+
|
|
279
|
+
img.addEventListener('load', function() {
|
|
280
|
+
imageLoaded = true;
|
|
281
|
+
});
|
|
282
|
+
|
|
283
|
+
img.addEventListener('error', function() {
|
|
284
|
+
if (!imageLoaded) {
|
|
285
|
+
handleImageFailure(this);
|
|
286
|
+
}
|
|
287
|
+
});
|
|
288
|
+
|
|
289
|
+
// Also check if image is already broken (cached failure)
|
|
290
|
+
if (img.complete && img.naturalWidth === 0) {
|
|
291
|
+
handleImageFailure(img);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
// Timeout fallback for slow connections (5 seconds)
|
|
295
|
+
setTimeout(function() {
|
|
296
|
+
if (!imageLoaded && !img.complete) {
|
|
297
|
+
handleImageFailure(img);
|
|
298
|
+
}
|
|
299
|
+
}, 5000);
|
|
300
|
+
});
|
|
301
|
+
});
|
|
302
|
+
|
|
166
303
|
// Helper to get server base URL
|
|
167
304
|
function getServerBaseUrl() {
|
|
168
305
|
const port = window.SERVER_PORT || 8000;
|
|
@@ -184,18 +321,59 @@
|
|
|
184
321
|
select.innerHTML = '<option>No models available</option>';
|
|
185
322
|
return;
|
|
186
323
|
}
|
|
324
|
+
|
|
325
|
+
// Filter out embedding models from chat interface
|
|
326
|
+
const allModels = window.SERVER_MODELS || {};
|
|
327
|
+
let filteredModels = [];
|
|
187
328
|
let defaultIndex = 0;
|
|
329
|
+
|
|
330
|
+
// Check if model is specified in URL parameters
|
|
331
|
+
const urlModel = new URLSearchParams(window.location.search).get('model');
|
|
332
|
+
let urlModelIndex = -1;
|
|
333
|
+
|
|
188
334
|
data.data.forEach(function(model, index) {
|
|
189
335
|
const modelId = model.id || model.name || model;
|
|
336
|
+
const modelInfo = allModels[modelId] || {};
|
|
337
|
+
const labels = modelInfo.labels || [];
|
|
338
|
+
|
|
339
|
+
// Skip models with "embeddings" or "reranking" label
|
|
340
|
+
if (labels.includes('embeddings') || labels.includes('reranking')) {
|
|
341
|
+
return;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
filteredModels.push(modelId);
|
|
190
345
|
const opt = document.createElement('option');
|
|
191
346
|
opt.value = modelId;
|
|
192
347
|
opt.textContent = modelId;
|
|
348
|
+
|
|
349
|
+
// Check if this model matches the URL parameter
|
|
350
|
+
if (urlModel && modelId === urlModel) {
|
|
351
|
+
urlModelIndex = filteredModels.length - 1;
|
|
352
|
+
}
|
|
353
|
+
|
|
354
|
+
// Default fallback for backwards compatibility
|
|
193
355
|
if (modelId === 'Llama-3.2-1B-Instruct-Hybrid') {
|
|
194
|
-
defaultIndex =
|
|
356
|
+
defaultIndex = filteredModels.length - 1;
|
|
195
357
|
}
|
|
358
|
+
|
|
196
359
|
select.appendChild(opt);
|
|
197
360
|
});
|
|
198
|
-
|
|
361
|
+
|
|
362
|
+
if (filteredModels.length === 0) {
|
|
363
|
+
select.innerHTML = '<option>No chat models available</option>';
|
|
364
|
+
return;
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
// Select the URL-specified model if found, otherwise use default
|
|
368
|
+
if (urlModelIndex !== -1) {
|
|
369
|
+
select.selectedIndex = urlModelIndex;
|
|
370
|
+
console.log(`Selected model from URL parameter: ${urlModel}`);
|
|
371
|
+
} else {
|
|
372
|
+
select.selectedIndex = defaultIndex;
|
|
373
|
+
if (urlModel) {
|
|
374
|
+
console.warn(`Model '${urlModel}' specified in URL not found in available models`);
|
|
375
|
+
}
|
|
376
|
+
}
|
|
199
377
|
} catch (e) {
|
|
200
378
|
const select = document.getElementById('model-select');
|
|
201
379
|
select.innerHTML = `<option>Error loading models: ${e.message}</option>`;
|
|
@@ -217,26 +395,24 @@
|
|
|
217
395
|
|
|
218
396
|
// Add labels if they exist
|
|
219
397
|
const modelData = allModels[modelId];
|
|
220
|
-
if (modelData) {
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
const
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
});
|
|
239
|
-
}
|
|
398
|
+
if (modelData && modelData.labels && Array.isArray(modelData.labels)) {
|
|
399
|
+
modelData.labels.forEach(label => {
|
|
400
|
+
const labelSpan = document.createElement('span');
|
|
401
|
+
const labelLower = label.toLowerCase();
|
|
402
|
+
let labelClass = 'other';
|
|
403
|
+
if (labelLower === 'vision') {
|
|
404
|
+
labelClass = 'vision';
|
|
405
|
+
} else if (labelLower === 'embeddings') {
|
|
406
|
+
labelClass = 'embeddings';
|
|
407
|
+
} else if (labelLower === 'reasoning') {
|
|
408
|
+
labelClass = 'reasoning';
|
|
409
|
+
} else if (labelLower === 'reranking') {
|
|
410
|
+
labelClass = 'reranking';
|
|
411
|
+
}
|
|
412
|
+
labelSpan.className = `model-label ${labelClass}`;
|
|
413
|
+
labelSpan.textContent = label;
|
|
414
|
+
container.appendChild(labelSpan);
|
|
415
|
+
});
|
|
240
416
|
}
|
|
241
417
|
|
|
242
418
|
return container;
|
|
@@ -358,16 +534,110 @@
|
|
|
358
534
|
const modelSelect = document.getElementById('model-select');
|
|
359
535
|
let messages = [];
|
|
360
536
|
|
|
361
|
-
function appendMessage(role, text) {
|
|
537
|
+
function appendMessage(role, text, isMarkdown = false) {
|
|
362
538
|
const div = document.createElement('div');
|
|
363
539
|
div.className = 'chat-message ' + role;
|
|
364
540
|
// Add a bubble for iMessage style
|
|
365
541
|
const bubble = document.createElement('div');
|
|
366
542
|
bubble.className = 'chat-bubble ' + role;
|
|
367
|
-
|
|
543
|
+
|
|
544
|
+
if (role === 'llm' && isMarkdown) {
|
|
545
|
+
bubble.innerHTML = renderMarkdownWithThinkTokens(text);
|
|
546
|
+
} else {
|
|
547
|
+
bubble.textContent = text;
|
|
548
|
+
}
|
|
549
|
+
|
|
368
550
|
div.appendChild(bubble);
|
|
369
551
|
chatHistory.appendChild(div);
|
|
370
552
|
chatHistory.scrollTop = chatHistory.scrollHeight;
|
|
553
|
+
return bubble; // Return the bubble element for streaming updates
|
|
554
|
+
}
|
|
555
|
+
|
|
556
|
+
function updateMessageContent(bubbleElement, text, isMarkdown = false) {
|
|
557
|
+
if (isMarkdown) {
|
|
558
|
+
bubbleElement.innerHTML = renderMarkdownWithThinkTokens(text);
|
|
559
|
+
} else {
|
|
560
|
+
bubbleElement.textContent = text;
|
|
561
|
+
}
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
function renderMarkdownWithThinkTokens(text) {
|
|
565
|
+
// Check if text contains opening think tag
|
|
566
|
+
if (text.includes('<think>')) {
|
|
567
|
+
if (text.includes('</think>')) {
|
|
568
|
+
// Complete think block - handle as before
|
|
569
|
+
const thinkMatch = text.match(/<think>(.*?)<\/think>/s);
|
|
570
|
+
if (thinkMatch) {
|
|
571
|
+
const thinkContent = thinkMatch[1].trim();
|
|
572
|
+
const mainResponse = text.replace(/<think>.*?<\/think>/s, '').trim();
|
|
573
|
+
|
|
574
|
+
// Create collapsible structure
|
|
575
|
+
let html = '';
|
|
576
|
+
if (thinkContent) {
|
|
577
|
+
html += `
|
|
578
|
+
<div class="think-tokens-container">
|
|
579
|
+
<div class="think-tokens-header" onclick="toggleThinkTokens(this)">
|
|
580
|
+
<span class="think-tokens-chevron">▼</span>
|
|
581
|
+
<span class="think-tokens-label">Thinking...</span>
|
|
582
|
+
</div>
|
|
583
|
+
<div class="think-tokens-content">
|
|
584
|
+
${renderMarkdown(thinkContent)}
|
|
585
|
+
</div>
|
|
586
|
+
</div>
|
|
587
|
+
`;
|
|
588
|
+
}
|
|
589
|
+
if (mainResponse) {
|
|
590
|
+
html += `<div class="main-response">${renderMarkdown(mainResponse)}</div>`;
|
|
591
|
+
}
|
|
592
|
+
return html;
|
|
593
|
+
}
|
|
594
|
+
} else {
|
|
595
|
+
// Partial think block - only opening tag found, still being generated
|
|
596
|
+
const thinkMatch = text.match(/<think>(.*)/s);
|
|
597
|
+
if (thinkMatch) {
|
|
598
|
+
const thinkContent = thinkMatch[1];
|
|
599
|
+
const beforeThink = text.substring(0, text.indexOf('<think>'));
|
|
600
|
+
|
|
601
|
+
let html = '';
|
|
602
|
+
if (beforeThink.trim()) {
|
|
603
|
+
html += `<div class="main-response">${renderMarkdown(beforeThink)}</div>`;
|
|
604
|
+
}
|
|
605
|
+
|
|
606
|
+
html += `
|
|
607
|
+
<div class="think-tokens-container">
|
|
608
|
+
<div class="think-tokens-header" onclick="toggleThinkTokens(this)">
|
|
609
|
+
<span class="think-tokens-chevron">▼</span>
|
|
610
|
+
<span class="think-tokens-label">Thinking...</span>
|
|
611
|
+
</div>
|
|
612
|
+
<div class="think-tokens-content">
|
|
613
|
+
${renderMarkdown(thinkContent)}
|
|
614
|
+
</div>
|
|
615
|
+
</div>
|
|
616
|
+
`;
|
|
617
|
+
|
|
618
|
+
return html;
|
|
619
|
+
}
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
|
|
623
|
+
// Fallback to normal markdown rendering
|
|
624
|
+
return renderMarkdown(text);
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
function toggleThinkTokens(header) {
|
|
628
|
+
const container = header.parentElement;
|
|
629
|
+
const content = container.querySelector('.think-tokens-content');
|
|
630
|
+
const chevron = header.querySelector('.think-tokens-chevron');
|
|
631
|
+
|
|
632
|
+
if (content.style.display === 'none') {
|
|
633
|
+
content.style.display = 'block';
|
|
634
|
+
chevron.textContent = '▼';
|
|
635
|
+
container.classList.remove('collapsed');
|
|
636
|
+
} else {
|
|
637
|
+
content.style.display = 'none';
|
|
638
|
+
chevron.textContent = '▶';
|
|
639
|
+
container.classList.add('collapsed');
|
|
640
|
+
}
|
|
371
641
|
}
|
|
372
642
|
|
|
373
643
|
async function sendMessage() {
|
|
@@ -379,8 +649,7 @@
|
|
|
379
649
|
sendBtn.disabled = true;
|
|
380
650
|
// Streaming OpenAI completions (placeholder, adapt as needed)
|
|
381
651
|
let llmText = '';
|
|
382
|
-
appendMessage('llm', '...');
|
|
383
|
-
const llmDiv = chatHistory.lastChild.querySelector('.chat-bubble.llm');
|
|
652
|
+
const llmBubble = appendMessage('llm', '...');
|
|
384
653
|
try {
|
|
385
654
|
// Use the correct endpoint for chat completions
|
|
386
655
|
const resp = await fetch(getServerBaseUrl() + '/api/v1/chat/completions', {
|
|
@@ -395,22 +664,40 @@
|
|
|
395
664
|
if (!resp.body) throw new Error('No stream');
|
|
396
665
|
const reader = resp.body.getReader();
|
|
397
666
|
let decoder = new TextDecoder();
|
|
398
|
-
|
|
667
|
+
llmBubble.textContent = '';
|
|
399
668
|
while (true) {
|
|
400
669
|
const { done, value } = await reader.read();
|
|
401
670
|
if (done) break;
|
|
402
671
|
const chunk = decoder.decode(value);
|
|
403
672
|
if (chunk.trim() === 'data: [DONE]' || chunk.trim() === '[DONE]') continue;
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
673
|
+
|
|
674
|
+
// Handle Server-Sent Events format
|
|
675
|
+
const lines = chunk.split('\n');
|
|
676
|
+
for (const line of lines) {
|
|
677
|
+
if (line.startsWith('data: ')) {
|
|
678
|
+
const jsonStr = line.substring(6).trim();
|
|
679
|
+
if (jsonStr === '[DONE]') continue;
|
|
680
|
+
|
|
681
|
+
try {
|
|
682
|
+
const parsed = JSON.parse(jsonStr);
|
|
683
|
+
if (parsed.choices && parsed.choices[0] && parsed.choices[0].delta && parsed.choices[0].delta.content) {
|
|
684
|
+
llmText += parsed.choices[0].delta.content;
|
|
685
|
+
updateMessageContent(llmBubble, llmText, true);
|
|
686
|
+
}
|
|
687
|
+
} catch (e) {
|
|
688
|
+
// Fallback to regex parsing if JSON parsing fails
|
|
689
|
+
const match = jsonStr.match(/"content"\s*:\s*"((?:\\.|[^"\\])*)"/);
|
|
690
|
+
if (match && match[1]) {
|
|
691
|
+
llmText += unescapeJsonString(match[1]);
|
|
692
|
+
updateMessageContent(llmBubble, llmText, true);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
}
|
|
409
696
|
}
|
|
410
697
|
}
|
|
411
698
|
messages.push({ role: 'assistant', content: llmText });
|
|
412
699
|
} catch (e) {
|
|
413
|
-
|
|
700
|
+
llmBubble.textContent = '[Error: ' + e.message + ']';
|
|
414
701
|
}
|
|
415
702
|
sendBtn.disabled = false;
|
|
416
703
|
}
|
lemonade/version.py
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
__version__ = "8.0.
|
|
1
|
+
__version__ = "8.0.5"
|