export function extractTextSnippet(source: string, maxChars = 220): string { const strippedHtml = source .replace(/[\s\S]*?<\/script>/gi, ' ') .replace(/[\s\S]*?<\/style>/gi, ' ') .replace(/<[^>]+>/g, ' '); const normalizedMarkdown = strippedHtml .replace(/```[\s\S]*?```/g, ' ') .replace(/`[^`]+`/g, ' ') .replace(/!\[[^\]]*?\]\([^)]+\)/g, ' ') .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') .replace(/[#>*_~]/g, ' '); const normalized = normalizedMarkdown .replace(/\r\n/g, '\n') .replace(/\n{3,}/g, '\n\n') .replace(/[ \t]{2,}/g, ' ') .trim(); const paragraphs = normalized .split(/\n\s*\n/) .map((p) => p.trim()) .filter(Boolean); const first = paragraphs[0] ?? normalized; if (first.length <= maxChars) return first; return `${first.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`; } export function extractRawTextSnippet(source: string, maxChars = 1600): string { const strippedHtml = source .replace(/[\s\S]*?<\/script>/gi, '') .replace(/[\s\S]*?<\/style>/gi, ''); const normalized = strippedHtml.replace(/\r\n/g, '\n').trim(); if (normalized.length <= maxChars) return normalized; return `${normalized.slice(0, Math.max(0, maxChars - 1)).trimEnd()}…`; }