let markdownTools = null; export async function ensureMarkdownTools() { if (markdownTools) { return markdownTools; } const [{ marked }, { default: DOMPurify }] = await Promise.all([ import("https://cdn.jsdelivr.net/npm/marked@15.0.7/lib/marked.esm.js"), import("https://cdn.jsdelivr.net/npm/dompurify@3.2.4/dist/purify.es.min.js"), ]); marked.setOptions({ breaks: true, gfm: true, headerIds: false, mangle: false, }); markdownTools = { marked, DOMPurify }; return markdownTools; } export async function renderMarkdownHtml(content) { const text = String(content || "").trim(); if (!text) { return ""; } const { marked, DOMPurify } = await ensureMarkdownTools(); const html = marked.parse(text); const sanitized = DOMPurify.sanitize(html, { USE_PROFILES: { html: true }, ADD_ATTR: ["target", "rel"], }); return sanitized; } export async function renderMarkdownInto(element, content) { element.innerHTML = await renderMarkdownHtml(content); for (const link of element.querySelectorAll("a")) { link.target = "_blank"; link.rel = "noopener noreferrer"; } } export function plainTextSummary(content, limit = 280) { const text = String(content || "") .replace(/[#>*`_\-[\]()]/g, " ") .replace(/\s+/g, " ") .trim(); if (text.length <= limit) { return text; } return `${text.slice(0, limit - 1)}…`; } const THINK_OPEN = "<" + "think" + ">"; const THINK_CLOSE = ""; export function splitThinkingContent(fullText, options = {}) { const metaThinking = String(options.thinking || "").trim(); let text = String(fullText || "") .replace(/<\|redacted_im_end\|>/g, "") .replace(//g, THINK_OPEN) .replace(/<\/redacted_thinking>/g, THINK_CLOSE); let thinking = metaThinking; let answer = text.trim(); const openIdx = text.indexOf(THINK_OPEN); const closeIdx = text.indexOf(THINK_CLOSE); if (openIdx !== -1 && closeIdx !== -1 && closeIdx > openIdx) { const extracted = text.slice(openIdx + THINK_OPEN.length, closeIdx).trim(); thinking = thinking ? `${thinking}\n\n${extracted}` : extracted; answer = `${text.slice(0, openIdx)}${text.slice(closeIdx + THINK_CLOSE.length)}`.trim(); } else if (openIdx !== -1 && closeIdx === -1) { const extracted = text.slice(openIdx + THINK_OPEN.length).trim(); thinking = thinking ? `${thinking}\n\n${extracted}` : extracted; answer = text.slice(0, openIdx).trim(); } return { thinking: thinking.trim(), answer: answer.trim(), }; }