Added an enhanced markdown renderer as a service for apps

This commit is contained in:
Saifeddine ALOUI 2024-08-08 21:17:24 +02:00
parent 578377f4e7
commit de48601007
7 changed files with 751 additions and 19 deletions

View File

@ -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 {

View File

@ -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) {

View File

@ -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);
}

View File

@ -0,0 +1,400 @@
// Requires importing:
// <!-- For code highlighting -->
// <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/styles/default.min.css">
// <script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.5.1/highlight.min.js"></script>
// <!-- For LaTeX math rendering -->
// <script src="https://cdnjs.cloudflare.com/ajax/libs/mathjax/3.2.0/es5/tex-mml-chtml.js"></script>
// <!-- For Mermaid graph rendering -->
// <script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
// <script src="https://cdn.tailwindcss.com"></script>
// <script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
// <script src="https://cdn.jsdelivr.net/npm/animejs@3.2.1/lib/anime.min.js"></script>
// <!-- Prism CSS -->
// <link href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/themes/prism.min.css" rel="stylesheet" />
// <!-- Prism JS -->
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/prism.min.js"></script>
// <!-- If you want additional languages, include them like this -->
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-python.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-javascript.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-markup.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-c.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-cpp.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-java.min.js"></script>
// <script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.24.1/components/prism-latex.min.js"></script>
// When served with lollms, just use <script src="/lollms_markdown_renderer"></script>
// Don't forget to get the css too <link rel="stylesheet" href="/lollms_markdown_renderer.css">
// 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 = `
<div class="relative flex justify-center items-center mt-4 mb-4">
<div class="mermaid-diagram" id="${uniqueId}" style="transform-origin: center; transition: transform 0.3s;">
${result.svg}
</div>
<div class="absolute top-0 left-0 flex gap-1 p-1">
<button onclick="zoomMermaid('${uniqueId}', 1.1)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 100 100">
<circle cx="40" cy="40" r="25" stroke="black" stroke-width="5" fill="none" />
<line x1="60" y1="60" x2="80" y2="80" stroke="black" stroke-width="5" />
<line x1="50" y1="40" x2="30" y2="40" stroke="black" stroke-width="3" />
<line x1="40" y1="30" x2="40" y2="50" stroke="black" stroke-width="3" />
</svg>
</button>
<button onclick="zoomMermaid('${uniqueId}', 0.9)" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center">
<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 100 100">
<circle cx="40" cy="40" r="25" stroke="black" stroke-width="5" fill="none" />
<line x1="60" y1="60" x2="80" y2="80" stroke="black" stroke-width="5" />
<line x1="50" y1="40" x2="30" y2="40" stroke="black" stroke-width="3" />
</svg>
</button>
<button onclick="saveMermaidAsPNG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center">
PNG
</button>
<button onclick="saveMermaidAsSVG('${uniqueId}')" class="bg-gray-200 hover:bg-gray-300 text-gray-700 font-bold p-1 rounded inline-flex items-center">
SVG
</button>
</div>
</div>
`;
text = text.replace(match, htmlCode);
} catch (error) {
console.error('Mermaid rendering failed:', error);
text = text.replace(match, `<div class="mermaid-error">Failed to render diagram</div>`);
}
}
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) =>
`<span class="code-line"><span class="line-number">${index + 1}</span><span class="line-content">${line}</span></span>`
).join('\n');
return `<div class="code-block-wrapper">
<div class="code-block-header">
<div class="language-label">${language}</div>
<button class="copy-button" onclick="mr.copyCode(this)">
<svg xmlns="http://www.w3.org/2000/svg" width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
Copy
</button>
</div>
<pre class="line-numbers"><code class="language-${language}">${numberedLines}</code></pre>
</div>`;
});
return renderedText;
}
handleInlineCode(text) {
return text.replace(/`([^`]+)`/g, function(match, code) {
return `<b>${code}</b>`;
});
}
handleMathEquations(text) {
return text.replace(/\\\[([\s\S]*?)\\\]|\$\$([\s\S]*?)\$\$|\$([^\n]+?)\$/g, function(match, p1, p2, p3) {
const equation = p1 || p2 || p3;
return '<span class="math">' + equation + '</span>';
});
}
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}</${cellType}>`
).join('');
tableRows.push(`<tr>${renderedCells}</tr>`);
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 `<table class="table-auto w-full border-collapse border border-gray-300">${tableContent}</table>`;
}
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 += `<table class="table-auto w-full border-collapse border border-gray-300">${tableContent}</table>`;
}
return text
}
handleHeaders(text) {
return text.replace(/^(#{1,6})\s+(.*?)$/gm, function(match, hashes, content) {
const level = hashes.length;
return '<h' + level + '>' + content + '</h' + level + '>';
});
}
handleBoldText(text) {
return text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
}
handleItalicText(text) {
return text.replace(/\*(.*?)\*/g, '<em>$1</em>');
}
handleLinks(text) {
return text.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, '<a href="$2">$1</a>');
}
handleUnorderedLists(text) {
return text.replace(/^\s*[-*+]\s+(.*?)$/gm, '<li>$1</li>')
.replace(/(<li>.*<\/li>)/s, '<ul>$1</ul>');
}
handleOrderedLists(text) {
return text.replace(/^\s*(\d+)\.\s+(.*?)$/gm, '<li>$2</li>')
.replace(/(<li>.*<\/li>)/s, '<ol>$1</ol>');
}
handleBlockquotes(text) {
return text.replace(/^>\s+(.*?)$/gm, '<blockquote>$1</blockquote>');
}
handleHorizontalRules(text) {
return text.replace(/^(-{3,}|_{3,}|\*{3,})$/gm, '<hr>');
}
handleParagraphs(text) {
//return text.replace(/^(?!<[uo]l|<blockquote|<h\d|<pre|<hr|<table|<li|<button)(.+)$/gm, '<p class="mb-4">$1</p>');
// 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 = `
<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round">
<rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect>
<path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path>
</svg>
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, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#039;");
}
}

@ -1 +1 @@
Subproject commit 5d805b131b379f0e551023be0ed472f2c23a0245
Subproject commit fa70a95766bbda5860af375b0d2c046414066f4a

View File

@ -6,7 +6,6 @@
<button v-show="!generating" id="generate-button" title="Generate from current cursor position" @click="generate" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="pen-tool"></i></button>
<button v-show="!generating" id="generate-next-button" title="Generate from next place holder" @click="generate_in_placeholder" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="archive"></i></button>
<button v-show="!generating" id="tokenize" title="Tokenize text" @click="tokenize_text" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><img width="25" height="25" :src="tokenize_icon"></button>
<span class="w-80"></span>
<button v-show="generating" id="stop-button" @click="stopGeneration" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="x"></i></button>
<button
@ -36,9 +35,21 @@
>
<img v-if="!pending" :src="is_deaf_transcribing?deaf_on:deaf_off" height="25">
<img v-if="pending" :src="loading_icon" height="25">
</button>
</button>
<button
.slider-value {
display: inline-block;
margin-left: 10px;
color: #6b7280;
font-size: 14px;
}
.small-button {
padding: 0.5rem 0.75rem;
font-size: 0.875rem;
}
</style>
<button
type="button"
title="Start recording audio"
@click="startRecording"
@ -47,7 +58,6 @@
>
<img v-if="!pending" :src="is_recording?rec_on:rec_off" height="25">
<img v-if="pending" :src="loading_icon" height="25">
</button>
<button v-if="!isSynthesizingVoice"
title="generate audio from the text"
@ -67,7 +77,7 @@
<button v-show="!generating" id="export-button" @click="exportText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="upload"></i></button>
<button v-show="!generating" id="import-button" @click="importText" class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer"><i data-feather="download"></i></button>
<div class="flex gap-3 flex-1 items-center flex-grow justify-end">
<div class="flex gap-3 flex-1 items-center flex-grow justify-end">
<button
class="border-2 text-blue-600 dark:text-white border-blue-300 p-2 rounded shadow-lg hover:border-gray-600 dark:link-item-dark cursor-pointer"
@click="tab_id='source'" :class="{'bg-blue-200 dark:bg-blue-500':tab_id=='source'}">
@ -80,7 +90,6 @@
</button>
</div>
<input type="file" id="import-input" class="hidden">
</div>
<div class="flex-grow m-2 p-2 border border-blue-300 rounded-md border-2 border-blue-300 m-2 p-4" :class="{ 'border-red-500': generating }">
<div v-if="tab_id === 'source'">
@ -117,7 +126,6 @@
title="Add bash block" @click.stop="addBlock('bash')">
<img :src="bash_block" width="25" height="25">
</div>
<div class="text-lg hover:text-secondary duration-75 active:scale-90 p-2 cursor-pointer hover:border-2"
title="Copy message to clipboard" @click.stop="copyContentToClipboard()">
<i data-feather="copy"></i>
@ -133,7 +141,7 @@
>
</textarea>
<span>Cursor position {{ cursorPosition }}</span>
<span>Cursor position {{ cursorPosition }}</span>
</div>
<audio controls v-if="audio_url!=null" :key="audio_url">
<source :src="audio_url" type="audio/wav" ref="audio_player">
@ -141,15 +149,19 @@
</audio>
<tokens-hilighter :namedTokens="namedTokens">
</tokens-hilighter>
</tokens-hilighter>
<div v-if="tab_id === 'render'">
<MarkdownRenderer ref="mdRender" :client_id="this.$store.state.client_id" :message_id="0" :discussion_id="0" :markdown-text="text" class="mt-4 p-2 rounded shadow-lg dark:bg-bg-dark">
</MarkdownRenderer>
</div>
</div>
</div>
<Card title="settings" class="slider-container ml-0 mr-0" :isHorizontal="false" :disableHoverAnimation="true" :disableFocus="true">
<Card title="Model" class="slider-container ml-0 mr-0" :is_subcard="true" :isHorizontal="false" :disableHoverAnimation="true" :disableFocus="true">
<div class="settings-button" @click="showSettings = !showSettings">
<i data-feather="settings"></i> Settings
</div>
<div v-if="showSettings" class="settings bg-white dark:bg-gray-800 rounded-lg shadow-md p-6">
<h2 class="text-2xl font-bold text-gray-900 dark:text-white mb-4">Settings</h2>
<Card title="Model" class="slider-container ml-0 mr-0" :isHorizontal="false" :disableHoverAnimation="true" :disableFocus="true">
<select v-model="this.$store.state.selectedModel" @change="setModel" class="bg-white dark:bg-black m-0 border-2 rounded-md shadow-sm w-full">
<option v-for="model in models" :key="model" :value="model">
{{ model }}
@ -171,7 +183,7 @@
</div>
</div>
</Card>
</Card>
<Card title="Presets" class="slider-container ml-0 mr-0" :is_subcard="true" :isHorizontal="false" :disableHoverAnimation="true" :disableFocus="true">
<select v-model="selectedPreset" class="bg-white dark:bg-black mb-2 border-2 rounded-md shadow-sm w-full">
<option v-for="preset in presets" :key="preset" :value="preset">
@ -183,11 +195,10 @@
<button class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer" @click="addPreset" title="Add this text as a preset"><i data-feather="plus"></i></button>
<button class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer" @click="removePreset" title="Remove preset"><i data-feather="x"></i></button>
<button class="w-6 ml-2 hover:text-secondary duration-75 active:scale-90 cursor-pointer" @click="reloadPresets" title="Reload presets list"><i data-feather="refresh-ccw"></i></button>
</Card>
<Card title="Generation params" class="slider-container ml-0 mr-0" :is_subcard="true" :isHorizontal="false" :disableHoverAnimation="true" :disableFocus="true">
<div class="slider-container ml-2 mr-2">
<div class="slider-container ml-2 mr-2">
<h3 class="text-gray-600">Temperature</h3>
<input type="range" v-model="temperature" min="0" max="5" step="0.1" class="w-full">
<span class="slider-value text-gray-500">Current value: {{ temperature }}</span>
@ -228,7 +239,7 @@
<span class="slider-value text-gray-500">Current value: {{ seed }}</span>
</div>
</Card>
</Card>
</div>
</div>
</div>
<Toast ref="toast"/>

@ -1 +1 @@
Subproject commit ea3581eda1c421c761505c6190c8c3041295acd6
Subproject commit 67539d31d70b5dff9dd578310c6429538cd2651b