borderless / assets /markdown.js
spagestic's picture
Updated the chat UI and backend so MiniCPM thinking renders
c3f3998
Raw
History Blame Contribute Delete
2.64 kB
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 = "</" + "think" + ">";
export function splitThinkingContent(fullText, options = {}) {
const metaThinking = String(options.thinking || "").trim();
let text = String(fullText || "")
.replace(/<\|redacted_im_end\|>/g, "")
.replace(/<think>/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(),
};
}