import { marked } from 'marked'; import DOMPurify from 'dompurify'; import { MAX_MSG_CHARS, MAX_MSG_WORDS } from './constants'; import { cleanUrl } from './utils'; import markedLinkifyIt from 'marked-linkify-it'; import markedKatex from 'marked-katex-extension'; import markedExtendedTables from './markedExtendedTables'; // Always configure marked with necessary extensions marked.use({ async: false, gfm: true, pedantic: false, renderer: { link: ({ href, title, text, }: { href: string | null; title?: string | null; text: string; }) => { const cleanHref = href ? cleanUrl(href) : null; if (cleanHref === null) { return text; } href = cleanHref; let out = '' + text + ''; return out; }, }, }); marked.use(markedLinkifyIt()); marked.use(markedExtendedTables()); export const needsTruncation = (message: string) => { return ( message.length > MAX_MSG_CHARS || message.split(' ').length > MAX_MSG_WORDS ); }; export const truncateMessage = (message: string) => { let truncatedMessage = message; if (message.length > MAX_MSG_CHARS) { truncatedMessage = `${message.slice(0, MAX_MSG_CHARS)}\n...`; } if (truncatedMessage.split(' ').length > MAX_MSG_WORDS) { truncatedMessage = truncatedMessage .split(' ') .slice(0, MAX_MSG_WORDS) .join(' '); } return truncatedMessage; }; export const renderMsg = ( text: string, useMathFormatting = false, reasoningText = 'Reasoning...', showReasoning: boolean ): { text: string; } => { try { // Preprocessing del testo per gestire i delimitatori LaTeX let preprocessedText = text .trim() .replaceAll( /\[([^\]]+)\]\(([^\)]+)\)/g, '$1' ) .replaceAll( /(.*?)<\/think>/gs, showReasoning ? `${reasoningText}$1` : '' ) // Remove document_attachment tags from text - they will be handled as media .replaceAll( /([\s\S]*?)<\/document_attachment>/g, '' ) .replaceAll( /]*data-mimetype\s*=\s*["\']([^"']+)["\'][^>]*>([\s\S]*?)(?:<\/output>|$)/gi, '' ) .replaceAll(/```markdown([^```]+)```/g, '$1') .replaceAll('($', '( $') .replaceAll(':$', ': $') .replaceAll('\frac', '\\frac') .replaceAll('\beta', '\\beta') .replaceAll('cdot', '\\cdot'); // Correzione dei delimitatori LaTeX inconsistenti if (useMathFormatting) { // Abilita il supporto per KaTeX marked.use(markedKatex({})); // Normalizza tutti i delimitatori LaTeX per equazioni su linea separata // Da \\[ ... \\] o \\[ ... ] a $$ ... $$ preprocessedText = preprocessedText.replace( /\\+\[(.*?)\\*\]/gs, (_, content) => { return `$$${content}$$`; } ); // Gestione dei delimitatori [ ... ] che dovrebbero essere equazioni preprocessedText = preprocessedText.replace( /\[([^[\]]+?)\]/g, (match, content) => { // Verifica se sembra una formula matematica if ( /[\\+a-z0-9_{}^=\-\+\*\/]+/i.test(content) && !match.startsWith('[http') && !match.includes('](') ) { return `$$${content}$$`; } return match; // Mantieni invariati i link e altre strutture } ); } // Extract output tags before markdown processing to preserve their content as raw text const outputTags: string[] = []; let outputPlaceholder = 0; // Replace output tags with placeholders to prevent markdown parsing inside them preprocessedText = preprocessedText.replace( /(]*>)([\s\S]*?)(<\/output>)/g, (_, openTag, content, closeTag) => { const placeholder = ``; outputTags.push(`${openTag}${content}${closeTag}`); return placeholder; } ); // Ensure proper separation after placeholders preprocessedText = preprocessedText.replace( /()\s*([^\s<\n-])/g, '$1\n\n$2' ); // Ora procedi con il parsing markdown let parsedText = marked.parse(preprocessedText).toString().trim(); // Restore output tags from placeholders (after markdown processing) outputTags.forEach((tag, index) => { parsedText = parsedText.replace( ``, tag ); }); // Sanitize HTML parsedText = DOMPurify.sanitize(parsedText, { ADD_ATTR: ['target'], }); // Clean up final text const finalText = parsedText .replace(/()+/g, '') .replace(/<\/p>/g, '') .replace(/<\/p>/g, ''); return { text: finalText }; } catch (e) { console.error('Error rendering message:', e); return { text }; } }; export const sanitizeMsg = (msg: string) => DOMPurify.sanitize(msg, { ADD_ATTR: ['target'] });
<\/p>/g, '') .replace(/
<\/p>/g, ''); return { text: finalText }; } catch (e) { console.error('Error rendering message:', e); return { text }; } }; export const sanitizeMsg = (msg: string) => DOMPurify.sanitize(msg, { ADD_ATTR: ['target'] });