// Markdown rendering with syntax highlighting import { h } from 'https://esm.sh/preact@10.19.3'; import { marked } from 'https://esm.sh/marked@15.0.7'; import DOMPurify from 'https://esm.sh/dompurify@3.2.4'; import hljs from 'https://esm.sh/highlight.js@11.11.1/lib/core'; import langJavascript from 'https://esm.sh/highlight.js@11.11.1/lib/languages/javascript'; import langTypescript from 'https://esm.sh/highlight.js@11.11.1/lib/languages/typescript'; import langBash from 'https://esm.sh/highlight.js@11.11.1/lib/languages/bash'; import langJson from 'https://esm.sh/highlight.js@11.11.1/lib/languages/json'; import langPython from 'https://esm.sh/highlight.js@11.11.1/lib/languages/python'; import langRust from 'https://esm.sh/highlight.js@11.11.1/lib/languages/rust'; import langCss from 'https://esm.sh/highlight.js@11.11.1/lib/languages/css'; import langXml from 'https://esm.sh/highlight.js@11.11.1/lib/languages/xml'; import langSql from 'https://esm.sh/highlight.js@11.11.1/lib/languages/sql'; import langYaml from 'https://esm.sh/highlight.js@11.11.1/lib/languages/yaml'; import htm from 'https://esm.sh/htm@3.1.1'; const html = htm.bind(h); // Register highlight.js languages hljs.registerLanguage('javascript', langJavascript); hljs.registerLanguage('js', langJavascript); hljs.registerLanguage('typescript', langTypescript); hljs.registerLanguage('ts', langTypescript); hljs.registerLanguage('bash', langBash); hljs.registerLanguage('sh', langBash); hljs.registerLanguage('shell', langBash); hljs.registerLanguage('json', langJson); hljs.registerLanguage('python', langPython); hljs.registerLanguage('py', langPython); hljs.registerLanguage('rust', langRust); hljs.registerLanguage('css', langCss); hljs.registerLanguage('html', langXml); hljs.registerLanguage('xml', langXml); hljs.registerLanguage('sql', langSql); hljs.registerLanguage('yaml', langYaml); hljs.registerLanguage('yml', langYaml); // Configure marked with highlight.js using custom renderer (v15 API) const renderer = { code(token) { const code = token.text; const lang = token.lang || ''; let highlighted; if (lang && hljs.getLanguage(lang)) { highlighted = hljs.highlight(code, { language: lang }).value; } else { highlighted = hljs.highlightAuto(code).value; } return `
${highlighted}`;
}
};
marked.use({ renderer, breaks: false, gfm: true });
// Render markdown content with syntax highlighting
// All HTML is sanitized with DOMPurify before rendering to prevent XSS
export function renderContent(content) {
if (!content) return '';
const rawHtml = marked.parse(content);
const safeHtml = DOMPurify.sanitize(rawHtml);
return html``;
}
// Generate a short summary for a tool call based on name + input
function getToolSummary(name, input) {
if (!input) return name;
switch (name) {
case 'Bash': return input.description || input.command?.slice(0, 60) || 'Bash';
case 'Read': return input.file_path?.split('/').slice(-2).join('/') || 'Read';
case 'Write': return input.file_path?.split('/').slice(-2).join('/') || 'Write';
case 'Edit': return input.file_path?.split('/').slice(-2).join('/') || 'Edit';
case 'Grep': return `/${input.pattern?.slice(0, 40) || ''}/ ${input.glob || ''}`.trim();
case 'Glob': return input.pattern?.slice(0, 50) || 'Glob';
case 'Task': return input.description || 'Task';
default: return name;
}
}
// Render tool call pills (summary mode)
export function renderToolCalls(toolCalls) {
if (!toolCalls || toolCalls.length === 0) return '';
return html`