import { marked } from 'marked'; import katex from 'katex'; const renderLatexInHtml = (html: string): string => { // 匹配块级公式($$...$$) html = html.replace(/\$\$(.*?)\$\$/gs, (_match, formula) => { return katex.renderToString(formula.trim(), { displayMode: true, throwOnError: false, strict: false, trust: true, }); }); // 匹配行内公式($...$) html = html.replace(/\$(.*?)\$/g, (_match, formula) => { return katex.renderToString(formula.trim(), { displayMode: false, throwOnError: false, strict: false, }); }); return html; }; export const renderMarkdown = (text: string, icons?: { copy: string; download: string; preview: string; copySuccess: string }): string => { if (!text) return ''; const processed = text.replace(//gi, ' \n'); let html = marked.parse(processed) as string; html = renderLatexInHtml(html); return icons ? wrapTables(html, icons) : html; }; export const wrapTables = (html: string, icons: { copy: string; download: string; preview: string; copySuccess: string }): string => { return html.replace(/([\s\S]*?)<\/table>/g, (match) => { const encodedTable = encodeURIComponent(match); return `
${match}
`; }); }; export const tableHtmlToMarkdown = (tableHtml: string): string => { const parser = new DOMParser(); const doc = parser.parseFromString(tableHtml, 'text/html'); const table = doc.querySelector('table'); if (!table) return tableHtml; const rows: string[][] = []; table.querySelectorAll('tr').forEach((tr) => { const cells: string[] = []; tr.querySelectorAll('th, td').forEach((cell) => { cells.push(cell.textContent?.trim() || ''); }); rows.push(cells); }); if (rows.length === 0) return tableHtml; const colCount = Math.max(...rows.map((r) => r.length)); const normalized = rows.map((r) => { while (r.length < colCount) r.push(''); return r; }); const lines: string[] = []; lines.push('| ' + normalized[0].join(' | ') + ' |'); lines.push('| ' + normalized[0].map(() => '---').join(' | ') + ' |'); for (let i = 1; i < normalized.length; i++) { lines.push('| ' + normalized[i].join(' | ') + ' |'); } return lines.join('\n'); }; export const tableHtmlToCsv = (tableHtml: string): string => { const parser = new DOMParser(); const doc = parser.parseFromString(tableHtml, 'text/html'); const table = doc.querySelector('table'); if (!table) return ''; const rows: string[] = []; table.querySelectorAll('tr').forEach((tr) => { const cells: string[] = []; tr.querySelectorAll('th, td').forEach((cell) => { const text = (cell.textContent?.trim() || '').replace(/"/g, '""'); cells.push(`"${text}"`); }); rows.push(cells.join(',')); }); return '' + rows.join('\n'); }; export const formatFileSize = (bytes: number): string => { if (bytes < 1024) return bytes + ' B'; if (bytes < 1024 * 1024) return (bytes / 1024).toFixed(2) + ' KB'; return (bytes / (1024 * 1024)).toFixed(2) + ' MB'; }; export const isImageFile = (filename: string): boolean => /\.(jpg|jpeg|png|gif|bmp|webp|svg|ico|tiff|tif)$/i.test(filename); export const isPdfFile = (filename: string): boolean => /\.pdf$/i.test(filename); export const isVideoFile = (filename: string): boolean => /\.(mp4|webm|ogg|mov|avi|mkv|flv|wmv|m4v)$/i.test(filename); export const isAudioFile = (filename: string): boolean => /\.(mp3|wav|ogg|aac|flac|wma|m4a|opus)$/i.test(filename); export const isTextFile = (filename: string): boolean => /\.(txt|md|json|csv|xml|yaml|yml|log|ini|toml|cfg|conf|env|gitignore|dockerignore|editorconfig|prettierrc|eslintrc|babelrc|properties|sh|bat|ps1|cmd|bash|zsh|fish|sql|graphql|proto|js|ts|jsx|tsx|vue|css|scss|sass|less|html|htm|svelte|py|java|kt|kts|c|cpp|h|hpp|cc|cxx|cs|go|rs|rb|php|swift|m|mm|r|R|lua|pl|pm|hs|ex|exs|erl|clj|scala|dart|zig|nim|v|sol|tf|hcl|gradle|cmake|makefile|mk)$/i.test( filename ); export const getFileExtension = (filename: string): string => { const match = filename.match(/\.([^.]+)$/); return match ? `.${match[1]}`.toUpperCase() : ''; };