// Pure text helpers for the structured transcript, kept when // structuredRender.ts's imperative DOM builders were deleted (#253): // markdown rendering (sanitized) and the elapsed/context formatters. import { marked, Renderer } from "marked"; import { escapeHtml } from "../../html.js"; /** Small inline renderer retained for compact labels and focused tests. */ export function renderInlineMarkdown(text: string): string { const escaped = escapeHtml(text); return escaped .replace(/`([^`]+)`/g, "$1") .replace(/\*\*([^*]+)\*\*/g, "$1") .replace(/(?$1"); } /** Render assistant output as GFM, then sanitize the complete result before it * enters the DOM. This gives transcripts proper tables, lists, code blocks and * links without trusting model-authored HTML or URL attributes. */ export function renderMarkdown(text: string): string { const renderer = new Renderer(); renderer.html = ({ text: raw }) => escapeHtml(raw); renderer.image = ({ text: alt }) => escapeHtml(alt ? `[image: ${alt}]` : "[image]"); renderer.link = ({ href, title, tokens }) => { const label = renderer.parser.parseInline(tokens); const scheme = /^([a-z][a-z\d+.-]*):/i.exec(href.trim())?.[1]?.toLowerCase(); if (scheme && !["http", "https", "mailto"].includes(scheme)) return label; const safeTitle = title ? ` title="${escapeHtml(title)}"` : ""; return `${label}`; }; return marked.parse(text, { async: false, gfm: true, renderer }); } function compactNumber(value: number): string { if (Math.abs(value) < 1_000) return String(value); if (Math.abs(value) < 1_000_000) return `${(value / 1_000).toFixed(value % 1_000 === 0 ? 0 : 1)}k`; return `${(value / 1_000_000).toFixed(value % 1_000_000 === 0 ? 0 : 1)}m`; } export function formatElapsed(milliseconds: number): string { const seconds = Math.max(0, Math.floor(milliseconds / 1_000)); if (seconds < 60) return `${seconds}s`; const minutes = Math.floor(seconds / 60); const remainder = seconds % 60; return remainder === 0 ? `${minutes}m` : `${minutes}m ${remainder}s`; } export function formatContextTokens(value: number): string { return `${compactNumber(value)} context`; }