diff --git a/src/lib/components/chat/MessageInput.svelte b/src/lib/components/chat/MessageInput.svelte index f384c5b8f..31cf94e8b 100644 --- a/src/lib/components/chat/MessageInput.svelte +++ b/src/lib/components/chat/MessageInput.svelte @@ -754,6 +754,7 @@ bind:this={chatInputElement} bind:value={prompt} id="chat-input" + preserveBreaks={true} messageInput={true} shiftEnter={!($settings?.ctrlEnterToSend ?? false) && (!$mobile || diff --git a/src/lib/components/chat/MessageInput/Commands/Prompts.svelte b/src/lib/components/chat/MessageInput/Commands/Prompts.svelte index c6d8bcb82..9d49cee25 100644 --- a/src/lib/components/chat/MessageInput/Commands/Prompts.svelte +++ b/src/lib/components/chat/MessageInput/Commands/Prompts.svelte @@ -22,9 +22,18 @@ let selectedPromptIdx = 0; let filteredPrompts = []; - $: filteredPrompts = $prompts - .filter((p) => p.command.toLowerCase().includes(command.toLowerCase())) - .sort((a, b) => a.title.localeCompare(b.title)); + $: { + if (command && command.length > 0) { + const commandName = command.substring(1).toLowerCase(); + const cleanedCommandName = commandName.replace(/<\/?p>/gi, '').trim(); + + filteredPrompts = $prompts + .filter((p) => p.command.toLowerCase().includes(cleanedCommandName)) + .sort((a, b) => a.title.localeCompare(b.title)); + } else { + filteredPrompts = []; + } + } $: if (command) { selectedPromptIdx = 0; @@ -120,23 +129,42 @@ text = text.replaceAll('{{CURRENT_WEEKDAY}}', weekday); } - const lines = prompt.split('\n'); - const lastLine = lines.pop(); - - const lastLineWords = lastLine.split(' '); - const lastWord = lastLineWords.pop(); - if ($settings?.richTextInput ?? true) { - lastLineWords.push( - `${text.replace(//g, '>').replaceAll('\n', '
')}` - ); + const allPromptLines = prompt.split('\n'); + const lastLineWithTrigger = allPromptLines.pop() || ''; // Line where command was typed + const wordsInLastLine = lastLineWithTrigger.split(' '); + wordsInLastLine.pop(); // Remove the command trigger itself (e.g., /mycommand) + + let fullPromptPrefix = ''; + if (allPromptLines.length > 0) { + fullPromptPrefix = allPromptLines.join('\n'); + } + if (wordsInLastLine.length > 0) { + if (fullPromptPrefix.length > 0) { + fullPromptPrefix += '\n'; + } + fullPromptPrefix += wordsInLastLine.join(' '); + } + fullPromptPrefix = fullPromptPrefix.trimEnd(); + + if (text && text.trim().length > 0) { // If 'text' (the command's content) is not empty + if (fullPromptPrefix.length > 0) { + prompt = fullPromptPrefix + '\n\n' + text; + } else { + prompt = text; + } + } else { + prompt = fullPromptPrefix; + } - lines.push(lastLineWords.join(' ')); - prompt = lines.join('
'); } else { - lastLineWords.push(text); - lines.push(lastLineWords.join(' ')); - prompt = lines.join('\n'); + const currentInputLines = prompt.split('\n'); + const lastCurrentInputLine = currentInputLines.pop() || ''; + const lastCurrentInputLineWords = lastCurrentInputLine.split(' '); + lastCurrentInputLineWords.pop(); + lastCurrentInputLineWords.push(command.content); + currentInputLines.push(lastCurrentInputLineWords.join(' ')); + prompt = currentInputLines.join('\n'); } const chatInputContainerElement = document.getElementById('chat-input-container'); diff --git a/src/lib/components/common/RichTextInput.svelte b/src/lib/components/common/RichTextInput.svelte index eb7013e5e..b543241cf 100644 --- a/src/lib/components/common/RichTextInput.svelte +++ b/src/lib/components/common/RichTextInput.svelte @@ -163,25 +163,34 @@ } if (!raw) { - async function tryParse(value, attempts = 3, interval = 100) { + async function tryParse(value, attempts = 3, interval = 100, useMarkdownBreaks = false) { try { + // Normalize all types of newlines to \n + const normalizedTextValue = (value || '').replace(/\r\n|\r/g, '\n'); // Try parsing the value - return marked.parse(value.replaceAll(`\n
`, `
`), { - breaks: false + return marked.parse(normalizedTextValue, { + breaks: useMarkdownBreaks, + gfm: true // Ensure GFM is active for paragraph handling }); } catch (error) { // If no attempts remain, fallback to plain text if (attempts <= 1) { - return value; + return (value || '').replace(/\r\n|\r/g, '\n'); } // Wait for the interval, then retry await new Promise((resolve) => setTimeout(resolve, interval)); - return tryParse(value, attempts - 1, interval); // Recursive call + return tryParse(value, attempts - 1, interval, useMarkdownBreaks); } } // Usage example - content = await tryParse(value); + content = await tryParse(value, 3, 100, preserveBreaks); + + if (preserveBreaks && content) { + // Ensure truly empty paragraphs generated by marked get an   + // so they are visually represented and can be saved back correctly. + content = content.replace(/

(?: |)?<\/p>/gi, '

'); + } } } else { if (html && !content) { @@ -244,9 +253,7 @@ if (!raw) { let newValue = turndownService .turndown( - editor - .getHTML() - .replace(/

<\/p>/g, '
') + html .replace(/ {2,}/g, (m) => m.replace(/ /g, '\u00a0')) ) .replace(/\u00a0/g, ' '); @@ -438,14 +445,23 @@ ) .replace(/\u00a0/g, ' ') ) { - preserveBreaks - ? editor.commands.setContent(value) - : editor.commands.setContent( - marked.parse(value.replaceAll(`\n
`, `
`), { - breaks: false - }) - ); // Update editor content - + if (preserveBreaks) { + const normalizedValue = (value || '').replace(/\r\n|\r/g, '\n'); + // Parse Markdown with breaks:true to convert \n to
for Tiptap + let htmlToSet = marked.parse(normalizedValue, { breaks: true, gfm: true }); + if (htmlToSet) { + // htmlToSet = htmlToSet.replace(/

<\/p>/g, '

 

'); // Old way + htmlToSet = htmlToSet.replace(/

(?: |)?<\/p>/gi, '

'); + } + editor.commands.setContent( + marked.parse(normalizedValue, { breaks: true, gfm: true }) + ); + } else { + const normalizedValue = (value || '').replace(/\r\n|\r/g, '\n'); + editor.commands.setContent( + marked.parse(normalizedValue, { breaks: false, gfm: true }) + ); + } selectTemplate(); } } diff --git a/src/lib/components/workspace/Prompts/PromptEditor.svelte b/src/lib/components/workspace/Prompts/PromptEditor.svelte index 62d75fa65..34b66f33b 100644 --- a/src/lib/components/workspace/Prompts/PromptEditor.svelte +++ b/src/lib/components/workspace/Prompts/PromptEditor.svelte @@ -156,6 +156,7 @@ bind:value={content} rows={6} required + preserveBreaks={true} />