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.

@@ -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> // Tab switching logic
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 = index;
356
+ defaultIndex = filteredModels.length - 1;
195
357
  }
358
+
196
359
  select.appendChild(opt);
197
360
  });
198
- select.selectedIndex = defaultIndex;
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
- // Add reasoning label if reasoning is true
222
- if (modelData.reasoning === true) {
223
- const reasoningLabel = document.createElement('span');
224
- reasoningLabel.className = 'model-label reasoning';
225
- reasoningLabel.textContent = 'reasoning';
226
- container.appendChild(reasoningLabel);
227
- }
228
-
229
- // Add other labels if they exist
230
- if (modelData.labels && Array.isArray(modelData.labels)) {
231
- modelData.labels.forEach(label => {
232
- const labelSpan = document.createElement('span');
233
- const labelLower = label.toLowerCase();
234
- const labelClass = (labelLower === 'vision') ? 'vision' : 'other';
235
- labelSpan.className = `model-label ${labelClass}`;
236
- labelSpan.textContent = label;
237
- container.appendChild(labelSpan);
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
- bubble.innerHTML = text;
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
- llmDiv.textContent = '';
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
- // Try to extract the content from the OpenAI chunk
405
- const match = chunk.match(/"content"\s*:\s*"([^"]*)"/);
406
- if (match && match[1]) {
407
- llmText += match[1];
408
- llmDiv.textContent = llmText;
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
- llmDiv.textContent = '[Error: ' + e.message + ']';
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.3"
1
+ __version__ = "8.0.5"