diff --git a/endpoints/lollms_apps.py b/endpoints/lollms_apps.py index d77fa0ef..defbe35a 100644 --- a/endpoints/lollms_apps.py +++ b/endpoints/lollms_apps.py @@ -260,6 +260,25 @@ async def lollms_js(): data = file.read() return data +@router.get("/lollms_markdown_renderer", response_class=PlainTextResponse) +async def lollms_markdown_renderer(): + # Define the path to the JSON file using pathlib + file_path = Path(__file__).parent / "lollms_markdown_renderer.js" + + # Read the JSON file + with file_path.open('r') as file: + data = file.read() + return data + +@router.get("/lollms_markdown_renderer_css") +async def lollms_markdown_renderer_css(): + # Define the path to the CSS file using pathlib + file_path = Path(__file__).parent / "lollms_markdown_renderer.css" + + # Use FileResponse to serve the CSS file + return FileResponse(file_path, media_type="text/css") + + @router.get("/template") async def lollms_js(): return { diff --git a/endpoints/lollms_client_js.js b/endpoints/lollms_client_js.js index 9547554c..43745bb6 100644 --- a/endpoints/lollms_client_js.js +++ b/endpoints/lollms_client_js.js @@ -105,7 +105,6 @@ class LollmsClient { * @returns {Array} A list of tokens representing the tokenized prompt. */ const output = await axios.post("/lollms_tokenize", {"prompt": prompt}); - console.log(output.data.named_tokens) return output.data.named_tokens } async detokenize(tokensList) { @@ -657,9 +656,76 @@ extractCodeBlocks(text) { return codeBlocks; } +/** + * Updates the given code based on the provided query string. + * The query string can contain two types of modifications: + * 1. FULL_REWRITE: Completely replaces the original code with the new code. + * 2. REPLACE: Replaces specific code snippets within the original code. + * + * @param {string} originalCode - The original code to be updated. + * @param {string} queryString - The string containing the update instructions. + * @returns {object} - An object with the following properties: + * - updatedCode: The updated code. + * - modifications: An array of objects representing the changes made, each with properties 'oldCode' and 'newCode'. + * - hasQuery: A boolean indicating whether the queryString contained any valid queries. + */ +updateCode(originalCode, queryString) { + const queries = queryString.split('# REPLACE\n'); + let updatedCode = originalCode; + const modifications = []; + // Check if there's a FULL_REWRITE first + const fullRewriteStart = queryString.indexOf('# FULL_REWRITE'); + if (fullRewriteStart !== -1) { + const newCode = queryString.slice(fullRewriteStart + 14).trim(); + updatedCode = newCode; + modifications.push({ + oldCode: originalCode, + newCode + }); + return { + updatedCode, + modifications, + hasQuery: true + }; + } + + if (queries.length === 1 && queries[0].trim() === '') { + console.log("No queries detected"); + return { + updatedCode, + modifications: [], + hasQuery: false + }; + } + + for (const query of queries) { + if (query.trim() === '') continue; + + const originalCodeStart = query.indexOf('# ORIGINAL\n') + 11; + const originalCodeEnd = query.indexOf('\n# SET\n'); + const oldCode = query.slice(originalCodeStart, originalCodeEnd); + + const newCodeStart = query.indexOf('# SET\n') + 6; + const newCode = query.slice(newCodeStart); + + const modification = { + oldCode: oldCode.trim(), + newCode: newCode.trim() + }; + modifications.push(modification); + + updatedCode = updatedCode.replace(oldCode, newCode.trim()); + } + + console.log("New code", updatedCode); + return { + updatedCode, + modifications, + hasQuery: true + }; +} } - class LOLLMSRAGClient { constructor(baseURL, apiKey) { diff --git a/endpoints/lollms_markdown_renderer.css b/endpoints/lollms_markdown_renderer.css new file mode 100644 index 00000000..40074d7d --- /dev/null +++ b/endpoints/lollms_markdown_renderer.css @@ -0,0 +1,236 @@ +.fade-in { + opacity: 0; + transition: opacity 0.5s ease-in-out; +} +.fade-in.show { + opacity: 1; +} +.spin { + animation: spin 1s linear infinite; +} +@keyframes spin { + 0% { transform: rotate(0deg); } + 100% { transform: rotate(360deg); } +} +/* Dark mode styles */ +.dark { + color-scheme: dark; +} +.dark body { + background-color: #1a202c; + color: #e2e8f0; +} +.dark .bg-white { + background-color: #2d3748; +} +.dark .text-gray-900 { + color: #e2e8f0; +} +.dark .hover\:bg-gray-200:hover { + background-color: #4a5568; +} +.dark .border { + border-color: #4a5568; +} +.dark input, .dark select { + background-color: #2d3748; + color: #e2e8f0; +} +.dark button { + background-color: #4a5568; + color: #e2e8f0; +} +.dark button:hover { + background-color: #718096; +} +body { + padding-bottom: 60px; +} +main { + min-height: calc(100vh - 60px); +} +#help-section { + max-width: 800px; + margin: 0 auto; +} +#help-section h2 { + text-align: center; + margin-bottom: 1.5rem; +} +#help-section h3 { + color: #553c9a; +} +#help-section .dark h3 { + color: #d6bcfa; +} +#help-section p, #help-section li { + line-height: 1.6; +} +#help-section .bg-purple-100 { + background-color: rgba(237, 233, 254, 0.5); +} +#help-section .dark .bg-purple-900 { + background-color: rgba(76, 29, 149, 0.5); +} +/* Updated styles for dark mode */ +.dark #help-section { + color: #e2e8f0; +} +.dark #help-section h2 { + color: #a78bfa; +} +.dark #help-section h3 { + color: #d6bcfa; +} +.dark #help-section .bg-white { + background-color: #1a202c; +} +.dark #help-section .text-gray-600, +.dark #help-section .text-gray-700 { + color: #cbd5e0; +} +.dark #help-section .text-purple-700, +.dark #help-section .text-purple-800 { + color: #e9d8fd; +} +.dark #help-section button { + background-color: #8b5cf6; +} +.dark #help-section button:hover { + background-color: #7c3aed; +} +/* Styles for rendered Markdown */ +.markdown-content h1 { + font-size: 2em; + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.markdown-content h2 { + font-size: 1.5em; + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.markdown-content h3 { + font-size: 1.17em; + margin-top: 0.5em; + margin-bottom: 0.5em; +} +.markdown-content p { + margin-bottom: 1em; +} +.markdown-content ul, .markdown-content ol { + margin-left: 1.5em; + margin-bottom: 1em; +} +.markdown-content code { + background-color: #f0f0f0; + padding: 0.2em 0.4em; + border-radius: 3px; + font-family: monospace; +} +.markdown-content pre { + background-color: #f0f0f0; + padding: 1em; + border-radius: 5px; + overflow-x: auto; +} +.dark .markdown-content code { + background-color: #2d3748; +} +.dark .markdown-content pre { + background-color: #2d3748; +} +.ascii-art { + font-family: monospace; + white-space: pre; + background-color: #f0f0f0; + padding: 10px; + border-radius: 5px; +} +.code-block-wrapper { + background-color: #f5f5f5; + border-radius: 4px; + margin: 20px 0; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.code-block-header { +padding: 10px; +background-color: #e0e0e0; +border-top-left-radius: 4px; +border-top-right-radius: 4px; +display: flex; +justify-content: space-between; +align-items: center; +} + +.language-label { +font-weight: bold; +color: #333; +} + +.copy-button { +background-color: #4CAF50; +border: none; +color: white; +padding: 5px 10px; +text-align: center; +text-decoration: none; +font-size: 14px; +cursor: pointer; +border-radius: 3px; +transition: background-color 0.3s ease; +display: flex; +flex-direction: row; +align-items: center; +} + +.copy-button:hover { +background-color: #45a049; +} + +.copy-button svg { +margin-right: 5px; +width: 16px; +height: 16px; +} + +.copy-button.copied { +background-color: #2196F3; +} + +pre.line-numbers { +margin: 0; +padding: 10px 0; +font-family: 'Courier New', Courier, monospace; +font-size: 14px; +line-height: 1.5; +overflow-x: auto; +} + +code { +display: block; +padding: 0 10px; +} + +.code-line { +display: block; +white-space: pre; +} + +.line-number { +display: inline-block; +width: 30px; +text-align: right; +color: #999; +padding-right: 10px; +user-select: none; +} + +.line-content { +display: inline; +} + +.code-line:hover { +background-color: rgba(0, 0, 0, 0.05); +} \ No newline at end of file diff --git a/endpoints/lollms_markdown_renderer.js b/endpoints/lollms_markdown_renderer.js new file mode 100644 index 00000000..11abe45d --- /dev/null +++ b/endpoints/lollms_markdown_renderer.js @@ -0,0 +1,400 @@ +// Requires importing: +// +// +// + +// +// + +// +// + + +// +// +// + + +// +// + +// +// + +// +// +// +// +// +// +// +// +// When served with lollms, just use +// Don't forget to get the css too + +// Make sure there is a global variable called mr that instanciate MarkdownRenderer +// mr = new MarkdownRenderer() + + +class MarkdownRenderer { + async renderMermaidDiagrams(text) { + const mermaidCodeRegex = /```mermaid\n([\s\S]*?)```/g; + const matches = text.match(mermaidCodeRegex); + + if (!matches) return text; // Return original text if no Mermaid code found + + for (const match of matches) { + const mermaidCode = match.replace(/```mermaid\n/, '').replace(/```$/, ''); + const uniqueId = 'mermaid-' + Math.random().toString(36).substr(2, 9); + + try { + const result = await mermaid.render(uniqueId, mermaidCode); + const htmlCode = ` +
+
+ ${result.svg} +
+
+ + + + +
+
+ `; + text = text.replace(match, htmlCode); + } catch (error) { + console.error('Mermaid rendering failed:', error); + text = text.replace(match, `
Failed to render diagram
`); + } + } + + return text; + } + + async renderCodeBlocks(text) { + if (typeof Prism === 'undefined') { + throw new Error('Prism is not loaded. Please include Prism.js in your project.'); + } + + const codeBlockRegex = /```(\w+)?\n([\s\S]*?)```/g; + + const renderedText = await text.replace(codeBlockRegex, (match, language, code) => { + language = language || 'plaintext'; + + if (!Prism.languages[language]) { + console.warn(`Language '${language}' is not supported by Prism. Falling back to plaintext.`); + language = 'plaintext'; + } + + const highlightedCode = Prism.highlight(code.trim(), Prism.languages[language], language); + + const lines = highlightedCode.split(/\r?\n/); + const numberedLines = lines.map((line, index) => + `${index + 1}${line}` + ).join('\n'); + + return `
+
+
${language}
+ +
+
${numberedLines}
+
`; + }); + + return renderedText; + } + + handleInlineCode(text) { + return text.replace(/`([^`]+)`/g, function(match, code) { + return `${code}`; + }); + } + + handleMathEquations(text) { + return text.replace(/\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^\n]+?)\$/g, function(match, p1, p2, p3) { + const equation = p1 || p2 || p3; + return '' + equation + ''; + }); + } + + async handleTables(text) { + let alignments = []; + let tableRows = []; + let isInTable = false; + let hasHeader = false; + + // Process the text line by line + text = text.split('\n').map(line => { + // Check if the line is a table row + if (line.trim().startsWith('|') && line.trim().endsWith('|')) { + isInTable = true; + const tableRow = line.trim().slice(1, -1); // Remove leading and trailing | + const cells = tableRow.split('|').map(cell => cell.trim()); + + if (cells.every(cell => cell.match(/^:?-+:?$/))) { + // This is the header separator row + alignments = cells.map(cell => { + if (cell.startsWith(':') && cell.endsWith(':')) return 'center'; + if (cell.endsWith(':')) return 'right'; + return 'left'; + }); + hasHeader = true; + return ''; // Remove separator row + } + + const cellType = !hasHeader ? 'th' : 'td'; + const renderedCells = cells.map((cell, cellIndex) => + `<${cellType} class="border px-4 py-2" style="text-align: ${alignments[cellIndex] || 'left'};">${cell}` + ).join(''); + + tableRows.push(`${renderedCells}`); + return ''; // Remove the original Markdown line + } else if (isInTable) { + // We've reached the end of the table + isInTable = false; + hasHeader = false; + const tableContent = tableRows.join(''); + tableRows = []; // Reset for next table + return `${tableContent}
`; + } + return line; // Return non-table lines unchanged + }).join('\n'); + + // Handle case where table is at the end of the text + if (isInTable) { + const tableContent = tableRows.join(''); + text += `${tableContent}
`; + } + return text + } + + handleHeaders(text) { + return text.replace(/^(#{1,6})\s+(.*?)$/gm, function(match, hashes, content) { + const level = hashes.length; + return '' + content + ''; + }); + } + + handleBoldText(text) { + return text.replace(/\*\*(.*?)\*\*/g, '$1'); + } + + handleItalicText(text) { + return text.replace(/\*(.*?)\*/g, '$1'); + } + + handleLinks(text) { + return text.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, '$1'); + } + + handleUnorderedLists(text) { + return text.replace(/^\s*[-*+]\s+(.*?)$/gm, '
  • $1
  • ') + .replace(/(
  • .*<\/li>)/s, ''); + } + + handleOrderedLists(text) { + return text.replace(/^\s*(\d+)\.\s+(.*?)$/gm, '
  • $2
  • ') + .replace(/(
  • .*<\/li>)/s, '
      $1
    '); + } + + handleBlockquotes(text) { + return text.replace(/^>\s+(.*?)$/gm, '
    $1
    '); + } + + handleHorizontalRules(text) { + return text.replace(/^(-{3,}|_{3,}|\*{3,})$/gm, '
    '); + } + + handleParagraphs(text) { + //return text.replace(/^(?!<[uo]l|$1

    '); + // No need to handle paragraphs separately, they will be handled as the remaining content + return text; + } + + async renderMarkdown(text) { + // Handle Mermaid graphs first + text = await this.renderMermaidDiagrams(text); + + // Handle code blocks with syntax highlighting and copy button + text = await this.renderCodeBlocks(text); + + // Handle inline code + text = this.handleInlineCode(text); + + // Handle LaTeX-style math equations + text = this.handleMathEquations(text); + + // Handle tables + text = await this.handleTables(text); + + // Handle headers + text = this.handleHeaders(text); + + // Handle bold text + text = this.handleBoldText(text); + + // Handle italic text + text = this.handleItalicText(text); + + // Handle links + text = this.handleLinks(text); + + // Handle unordered lists + text = this.handleUnorderedLists(text); + + // Handle ordered lists + text = this.handleOrderedLists(text); + + // Handle blockquotes + text = this.handleBlockquotes(text); + + // Handle horizontal rules + text = this.handleHorizontalRules(text); + + // Handle paragraphs + text = this.handleParagraphs(text); + + return text; + } + + initMermaid() { + if (typeof mermaid !== 'undefined') { + mermaid.initialize({ startOnLoad: false }); + } else { + console.error('Mermaid library is not loaded'); + } + } + + initPrism() { + if (typeof Prism !== 'undefined') { + // No further initialization needed + } else { + console.error('Prism library is not loaded'); + } + } + + // Helper functions for Mermaid and code block handling + saveMermaidAsPNG(id) { + const svg = document.querySelector(`#${id} svg`); + const svgData = new XMLSerializer().serializeToString(svg); + + // Create a canvas with a higher resolution + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + // Set a scale factor for higher resolution (e.g., 2 for double resolution) + const scaleFactor = 2; + + const img = new Image(); + img.onload = function() { + canvas.width = img.width * scaleFactor; + canvas.height = img.height * scaleFactor; + + // Scale the context to draw the image at a higher resolution + ctx.scale(scaleFactor, scaleFactor); + ctx.drawImage(img, 0, 0); + + const pngFile = canvas.toDataURL("image/png"); + const downloadLink = document.createElement("a"); + downloadLink.download = "mermaid_diagram.png"; + downloadLink.href = pngFile; + downloadLink.click(); + }; + img.src = "data:image/svg+xml;base64," + btoa(unescape(encodeURIComponent(svgData))); + } + + saveMermaidAsSVG(id) { + const svg = document.querySelector(`#${id} svg`); + const svgData = new XMLSerializer().serializeToString(svg); + const svgBlob = new Blob([svgData], {type: "image/svg+xml;charset=utf-8"}); + const svgUrl = URL.createObjectURL(svgBlob); + const downloadLink = document.createElement("a"); + downloadLink.href = svgUrl; + downloadLink.download = "mermaid_diagram.svg"; + document.body.appendChild(downloadLink); + downloadLink.click(); + document.body.removeChild(downloadLink); + } + + zoomMermaid(id, factor) { + const diagram = document.getElementById(id); + const currentScale = diagram.style.transform ? parseFloat(diagram.style.transform.replace('scale(', '').replace(')', '')) : 1; + const newScale = currentScale * factor; + diagram.style.transform = `scale(${newScale})`; + } + + copyCode(button) { + const codeBlock = button.closest('.code-block-wrapper').querySelector('code'); + const codeLines = codeBlock.querySelectorAll('.code-line'); + let codeText = ''; + + codeLines.forEach((line) => { + const lineNumber = line.querySelector('.line-number').textContent; + const lineContent = line.querySelector('.line-content').textContent; + codeText += `${lineNumber} ${lineContent}\n`; + }); + + navigator.clipboard.writeText(codeText.trim()).then(() => { + button.classList.add('copied'); + button.querySelector('svg').style.display = 'none'; + button.innerHTML = 'Copied!'; + setTimeout(() => { + button.classList.remove('copied'); + button.innerHTML = ` + + + + + Copy + `; + }, 2000); + }).catch(err => { + console.error('Failed to copy text: ', err); + }); + } + + async highlightCode(code, language) { + // Make sure the language is supported by your highlighting library + const supportedLanguage = Prism.languages[language] ? language : 'plaintext'; + return Prism.highlight(code, Prism.languages[supportedLanguage], supportedLanguage); + } + + // Helper function to escape HTML special characters + escapeHtml(unsafe) { + if (typeof unsafe !== 'string') { + console.log("Found unsafe string:", text) + return ''; + } + return unsafe + .replace(/&/g, "&") + .replace(//g, ">") + .replace(/"/g, """) + .replace(/'/g, "'"); + } +} \ No newline at end of file diff --git a/lollms_core b/lollms_core index 5d805b13..fa70a957 160000 --- a/lollms_core +++ b/lollms_core @@ -1 +1 @@ -Subproject commit 5d805b131b379f0e551023be0ed472f2c23a0245 +Subproject commit fa70a95766bbda5860af375b0d2c046414066f4a diff --git a/web/src/views/PlayGroundView.vue b/web/src/views/PlayGroundView.vue index 3e954904..c5ce3a55 100644 --- a/web/src/views/PlayGroundView.vue +++ b/web/src/views/PlayGroundView.vue @@ -6,7 +6,6 @@ - + - -
    +
    -
    @@ -117,7 +126,6 @@ title="Add bash block" @click.stop="addBlock('bash')">
    -
    @@ -133,7 +141,7 @@ > - Cursor position {{ cursorPosition }} +Cursor position {{ cursorPosition }}
    - +
    - - +
    + Settings +
    +
    +

    Settings

    + -
    +

    Temperature

    Current value: {{ temperature }} @@ -228,7 +239,7 @@ Current value: {{ seed }}
    - +
    diff --git a/zoos/personalities_zoo b/zoos/personalities_zoo index ea3581ed..67539d31 160000 --- a/zoos/personalities_zoo +++ b/zoos/personalities_zoo @@ -1 +1 @@ -Subproject commit ea3581eda1c421c761505c6190c8c3041295acd6 +Subproject commit 67539d31d70b5dff9dd578310c6429538cd2651b