Changeset 0.4.1 (#33)

This commit is contained in:
2026-02-18 18:58:47 +00:00
parent c98eb80950
commit e2f538d787
6 changed files with 795 additions and 36 deletions

View File

@@ -205,7 +205,7 @@ async function loadProviderList() {
<div class="provider-row">
<div class="provider-info">
<span class="provider-name">${escapeHtmlUI(c.name)}</span>
<span class="provider-meta">${c.provider} · ${c.model_default || 'no default'}</span>
<span class="provider-meta">${escapeHtmlUI(c.provider)} · ${escapeHtmlUI(c.model_default || 'no default')}</span>
</div>
<div class="provider-actions">
<span class="admin-badge admin-badge-active">${c.has_key ? '🔑' : '⚠️'}</span>
@@ -214,7 +214,7 @@ async function loadProviderList() {
</div>
`).join('');
} catch (e) {
container.innerHTML = '<div class="admin-error">Failed to load providers: ' + e.message + '</div>';
container.innerHTML = '<div class="admin-error">Failed to load providers: ' + escapeHtmlUI(e.message) + '</div>';
}
}
@@ -361,53 +361,55 @@ function createMessageHTML(message, index) {
function formatMessage(content) {
let formatted = content;
// Extract and format thinking blocks FIRST (before escaping)
const thinkingBlocks = [];
// Extract thinking blocks FIRST (before escaping)
if (State.settings.showThinking) {
formatted = formatted.replace(/<thinking>([\s\S]*?)<\/thinking>/gi, (match, thinkingContent) => {
const escapedContent = escapeHtml(thinkingContent.trim());
const blockId = 'thinking-' + Math.random().toString(36).substr(2, 9);
return `__THINKING_BLOCK_${blockId}__${escapedContent}__END_THINKING_BLOCK__`;
// Store raw content; we'll escape it when reinserting
thinkingBlocks.push({ id: blockId, content: thinkingContent.trim() });
return `__THINKPLACEHOLDER_${blockId}__`;
});
} else {
// Remove thinking blocks if setting is off
formatted = formatted.replace(/<thinking>[\s\S]*?<\/thinking>/gi, '');
}
// Now escape HTML for the rest
// Escape HTML for the entire string (placeholders are plain text, safe to escape)
formatted = escapeHtml(formatted);
// Restore thinking blocks with proper HTML
formatted = formatted.replace(/__THINKING_BLOCK_([\w-]+)__([\s\S]*?)__END_THINKING_BLOCK__/g, (match, blockId, content) => {
return `
<div class="thinking-block" id="${blockId}">
<div class="thinking-header" onclick="toggleThinkingBlock('${blockId}')">
// Restore thinking blocks with properly-escaped content (single escape only)
for (const block of thinkingBlocks) {
const escapedContent = escapeHtml(block.content).replace(/\n/g, '<br>');
formatted = formatted.replace(`__THINKPLACEHOLDER_${block.id}__`, `
<div class="thinking-block" id="${block.id}">
<div class="thinking-header" onclick="toggleThinkingBlock('${block.id}')">
<span class="thinking-toggle">▼</span>
<span class="thinking-label">💭 Thinking</span>
</div>
<div class="thinking-content">${content.replace(/\n/g, '<br>')}</div>
<div class="thinking-content">${escapedContent}</div>
</div>
`;
});
`);
}
// Code blocks with copy button
formatted = formatted.replace(/```(\w*)\n?([\s\S]*?)```/g, (match, lang, code) => {
const codeId = 'code-' + Math.random().toString(36).substr(2, 9);
return `<pre><code id="${codeId}" class="language-${lang}">${code.trim()}</code><button class="copy-code-btn" onclick="copyCode('${codeId}')">Copy</button></pre>`;
});
// Inline code
formatted = formatted.replace(/`([^`]+)`/g, '<code>$1</code>');
// Bold
formatted = formatted.replace(/\*\*([^*]+)\*\*/g, '<strong>$1</strong>');
// Italic
// Italic
formatted = formatted.replace(/\*([^*]+)\*/g, '<em>$1</em>');
// Line breaks (but not inside pre/code)
formatted = formatted.replace(/\n/g, '<br>');
return formatted;
}