From ecc183f57de7e993c6cd61b2a5f1b69cc383200e Mon Sep 17 00:00:00 2001 From: perf3ct Date: Mon, 10 Mar 2025 23:09:15 +0000 Subject: [PATCH] almost completely styled codeblocks in response --- src/public/app/widgets/llm/chat_widget.js | 29 +++++++++++++------- src/public/app/widgets/llm_chat_panel.ts | 33 +++++++++++++++++++---- 2 files changed, 47 insertions(+), 15 deletions(-) diff --git a/src/public/app/widgets/llm/chat_widget.js b/src/public/app/widgets/llm/chat_widget.js index 15b4b695d..48cea4376 100644 --- a/src/public/app/widgets/llm/chat_widget.js +++ b/src/public/app/widgets/llm/chat_widget.js @@ -304,21 +304,30 @@ export default class ChatWidget extends TabAwareWidget { formatMessageContent(content) { if (!content) return ''; - // Escape HTML - let formatted = utils.escapeHtml(content); - - // Convert markdown-style code blocks to HTML - formatted = formatted.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (match, language, code) => { - return `
${utils.escapeHtml(code)}
`; + // First extract code blocks to protect them from HTML escaping + const codeBlocks = []; + let processedContent = content.replace(/```(\w+)?\n([\s\S]+?)\n```/g, (match, language, code) => { + const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`; + const languageClass = language ? ` language-${language}` : ''; + codeBlocks.push(`
${utils.escapeHtml(code)}
`); + return placeholder; }); - // Convert inline code - formatted = formatted.replace(/`([^`]+)`/g, '$1'); + // Escape HTML in the remaining content + processedContent = utils.escapeHtml(processedContent); + + // Convert inline code - look for backticks that weren't part of a code block + processedContent = processedContent.replace(/`([^`]+)`/g, '$1'); // Convert line breaks - formatted = formatted.replace(/\n/g, '
'); + processedContent = processedContent.replace(/\n/g, '
'); - return formatted; + // Restore code blocks + codeBlocks.forEach((block, index) => { + processedContent = processedContent.replace(`__CODE_BLOCK_${index}__`, block); + }); + + return processedContent; } scrollToBottom() { diff --git a/src/public/app/widgets/llm_chat_panel.ts b/src/public/app/widgets/llm_chat_panel.ts index b157fb83f..154b8f1e3 100644 --- a/src/public/app/widgets/llm_chat_panel.ts +++ b/src/public/app/widgets/llm_chat_panel.ts @@ -5,6 +5,7 @@ import appContext from "../components/app_context.js"; import utils from "../services/utils.js"; import { t } from "../services/i18n.js"; import libraryLoader from "../services/library_loader.js"; +import { applySyntaxHighlight } from "../services/syntax_highlight.js"; // Import the LLM Chat CSS (async function() { @@ -217,6 +218,8 @@ export default class LlmChatPanel extends BasicWidget { const assistantElement = this.noteContextChatMessages.querySelector('.assistant-message:last-child .message-content'); if (assistantElement) { assistantElement.innerHTML = this.formatMarkdown(assistantResponse); + // Apply syntax highlighting to any code blocks in the updated content + applySyntaxHighlight($(assistantElement as HTMLElement)); } else { this.addMessageToChat('assistant', assistantResponse); } @@ -292,6 +295,9 @@ export default class LlmChatPanel extends BasicWidget { this.noteContextChatMessages.appendChild(messageElement); + // Apply syntax highlighting to any code blocks in the message + applySyntaxHighlight($(contentElement)); + // Scroll to bottom this.chatContainer.scrollTop = this.chatContainer.scrollHeight; } @@ -382,12 +388,29 @@ export default class LlmChatPanel extends BasicWidget { * Format markdown content for display */ private formatMarkdown(content: string): string { - // Simple markdown formatting - could be replaced with a proper markdown library - return content + if (!content) return ''; + + // First, extract code blocks to protect them from other replacements + const codeBlocks: string[] = []; + let processedContent = content.replace(/```(\w+)?\n([\s\S]+?)\n```/gs, (match, language, code) => { + const placeholder = `__CODE_BLOCK_${codeBlocks.length}__`; + const languageClass = language ? ` language-${language}` : ''; + codeBlocks.push(`
${code}
`); + return placeholder; + }); + + // Apply other markdown formatting + processedContent = processedContent .replace(/\*\*(.*?)\*\*/g, '$1') .replace(/\*(.*?)\*/g, '$1') - .replace(/`(.*?)`/g, '$1') - .replace(/\n/g, '
') - .replace(/```(.*?)```/gs, '
$1
'); + .replace(/`([^`]+)`/g, '$1') + .replace(/\n/g, '
'); + + // Restore code blocks + codeBlocks.forEach((block, index) => { + processedContent = processedContent.replace(`__CODE_BLOCK_${index}__`, block); + }); + + return processedContent; } }