Better markdown support
This commit is contained in:
+85
-10
@@ -408,6 +408,22 @@ INTERACTIVE_CSS = '''
|
|||||||
padding-left: 0.5rem;
|
padding-left: 0.5rem;
|
||||||
color: var(--color-base-font, #cdd6f4);
|
color: var(--color-base-font, #cdd6f4);
|
||||||
}
|
}
|
||||||
|
.sxng-md-content {
|
||||||
|
line-height: 1.6;
|
||||||
|
}
|
||||||
|
.sxng-md-content ul, .sxng-md-content ol {
|
||||||
|
margin: 0.5rem 0;
|
||||||
|
padding-left: 1.5rem;
|
||||||
|
}
|
||||||
|
.sxng-md-content li {
|
||||||
|
margin: 0.2rem 0;
|
||||||
|
}
|
||||||
|
.sxng-md-content p {
|
||||||
|
margin: 0.4rem 0;
|
||||||
|
}
|
||||||
|
.sxng-md-content code {
|
||||||
|
font-family: monospace;
|
||||||
|
}
|
||||||
'''
|
'''
|
||||||
|
|
||||||
INTERACTIVE_HTML = '''
|
INTERACTIVE_HTML = '''
|
||||||
@@ -430,6 +446,62 @@ INTERACTIVE_HTML = '''
|
|||||||
'''
|
'''
|
||||||
|
|
||||||
CITATION_HELPER_JS = r'''
|
CITATION_HELPER_JS = r'''
|
||||||
|
function parseMarkdown(text) {
|
||||||
|
text = text.replace(/\*\*(.*?)\*\*/g, '<strong>$1</strong>');
|
||||||
|
text = text.replace(/__(.*?)__/g, '<strong>$1</strong>');
|
||||||
|
text = text.replace(/(?<!\*)\*(?!\*)(.*?)(?<!\*)\*(?!\*)/g, '<em>$1</em>');
|
||||||
|
text = text.replace(/(?<!_)_(?!_)(.*?)(?<!_)_(?!_)/g, '<em>$1</em>');
|
||||||
|
text = text.replace(/`([^`]+)`/g, '<code style="background:var(--color-sidebar-bg,#2a2a2e);padding:1px 5px;border-radius:3px;font-family:monospace;font-size:0.9em;">$1</code>');
|
||||||
|
text = text.replace(/((?:^|\n)[*\-+] .+)+/g, (match) => {
|
||||||
|
const items = match.trim().split('\n').map(line => {
|
||||||
|
const content = line.replace(/^[*\-+] /, '').trim();
|
||||||
|
return `<li>${content}</li>`;
|
||||||
|
}).join('');
|
||||||
|
return `<ul style="margin:0.5rem 0;padding-left:1.5rem;">${items}</ul>`;
|
||||||
|
});
|
||||||
|
text = text.replace(/((?:^|\n)\d+\. .+)+/g, (match) => {
|
||||||
|
const items = match.trim().split('\n').map(line => {
|
||||||
|
const content = line.replace(/^\d+\. /, '').trim();
|
||||||
|
return `<li>${content}</li>`;
|
||||||
|
}).join('');
|
||||||
|
return `<ol style="margin:0.5rem 0;padding-left:1.5rem;">${items}</ol>`;
|
||||||
|
});
|
||||||
|
text = text.replace(/^### (.+)$/gm, '<h4 style="margin:0.5rem 0 0.25rem;font-size:0.95em;font-weight:700;">$1</h4>');
|
||||||
|
text = text.replace(/^## (.+)$/gm, '<h3 style="margin:0.5rem 0 0.25rem;font-size:1em;font-weight:700;">$1</h3>');
|
||||||
|
text = text.replace(/^# (.+)$/gm, '<h3 style="margin:0.5rem 0 0.25rem;font-size:1.05em;font-weight:700;">$1</h3>');
|
||||||
|
text = text.replace(/^---+$/gm, '<hr style="border:none;border-top:1px solid var(--color-sidebar-bg,#424247);margin:0.5rem 0;">');
|
||||||
|
text = text.replace(/\n\n/g, '</p><p style="margin:0.4rem 0;">');
|
||||||
|
text = text.replace(/\n(?!<)/g, '<br>');
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
function linkCitationsInElement(el, urls) {
|
||||||
|
const walker = document.createTreeWalker(
|
||||||
|
el, NodeFilter.SHOW_TEXT, null
|
||||||
|
);
|
||||||
|
const textNodes = [];
|
||||||
|
let node;
|
||||||
|
while (node = walker.nextNode()) {
|
||||||
|
textNodes.push(node);
|
||||||
|
}
|
||||||
|
textNodes.forEach(textNode => {
|
||||||
|
const text = textNode.textContent;
|
||||||
|
if (!/\[\d/.test(text)) return;
|
||||||
|
const span = document.createElement('span');
|
||||||
|
span.innerHTML = text.replace(/\[(\d{1,2}(?:,\s*\d{1,2})*)\]/g, (match, nums) => {
|
||||||
|
return nums.split(/\s*,\s*/).map(n => {
|
||||||
|
const idx = parseInt(n.trim());
|
||||||
|
const url = urls[idx - 1];
|
||||||
|
if (url) {
|
||||||
|
return `<a href="${url}" target="_blank" style="text-decoration:none;color:var(--color-result-link);font-weight:bold;">[${n.trim()}]</a>`;
|
||||||
|
}
|
||||||
|
return match;
|
||||||
|
}).join('');
|
||||||
|
});
|
||||||
|
textNode.parentNode.replaceChild(span, textNode);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
function renderCitations(text, urls) {
|
function renderCitations(text, urls) {
|
||||||
const fragment = document.createDocumentFragment();
|
const fragment = document.createDocumentFragment();
|
||||||
const re = /\[(\d{1,2}(?:\s*,\s*\d{1,2})*)\]/g;
|
const re = /\[(\d{1,2}(?:\s*,\s*\d{1,2})*)\]/g;
|
||||||
@@ -1039,17 +1111,19 @@ FRONTEND_JS_TEMPLATE = r"""
|
|||||||
|
|
||||||
if (cursor) cursor.remove();
|
if (cursor) cursor.remove();
|
||||||
|
|
||||||
let last = data.lastChild;
|
// Replace streamed text nodes with markdown-rendered content
|
||||||
while (last) {
|
Array.from(data.childNodes).forEach(node => {
|
||||||
if (last.textContent && last.textContent.trim().length === 0) {
|
if (node.nodeType !== 1 || !node.classList.contains('sxng-prior-history')) {
|
||||||
const prev = last.previousSibling;
|
node.remove();
|
||||||
last.remove();
|
|
||||||
last = prev;
|
|
||||||
} else {
|
|
||||||
if (last.textContent) last.textContent = last.textContent.trimEnd();
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
const rendered = parseMarkdown(fullText.trim());
|
||||||
|
const mdDiv = document.createElement('div');
|
||||||
|
mdDiv.className = 'sxng-md-content';
|
||||||
|
mdDiv.innerHTML = rendered;
|
||||||
|
linkCitationsInElement(mdDiv, urls);
|
||||||
|
data.appendChild(mdDiv);
|
||||||
|
|
||||||
renderCitationFooter(fullText, urls, data);
|
renderCitationFooter(fullText, urls, data);
|
||||||
|
|
||||||
@@ -1546,6 +1620,7 @@ class SXNGPlugin(Plugin):
|
|||||||
"Answer the question directly using the provided context.",
|
"Answer the question directly using the provided context.",
|
||||||
"MUST CITE SOURCES by tailing a sentence with [n] or [n,n] etc. If citing general knowledge, use [*].",
|
"MUST CITE SOURCES by tailing a sentence with [n] or [n,n] etc. If citing general knowledge, use [*].",
|
||||||
"Never explain your process. The user expects a direct response.",
|
"Never explain your process. The user expects a direct response.",
|
||||||
|
"Use markdown formatting where it improves clarity: **bold** for key terms, bullet lists for enumerations, numbered lists for steps. Keep formatting minimal and purposeful.",
|
||||||
intent_cfg['format'],
|
intent_cfg['format'],
|
||||||
"If sources and general knowledge are insufficient, respond with 'Insufficient information to answer.'"
|
"If sources and general knowledge are insufficient, respond with 'Insufficient information to answer.'"
|
||||||
]
|
]
|
||||||
|
|||||||
Reference in New Issue
Block a user