Download src/lib/ai/math.ts from asnannp/DocDoeAI: direct link, hf CLI and curl.
- Browser
- Download file 6.73 kB
-
https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/ai/math.ts
- Command line
-
hf download hf://spaces/asnannp/DocDoeAI/src/lib/ai/math.ts
-
curl -L -o math.ts https://huggingface.co/spaces/asnannp/DocDoeAI/resolve/main/src/lib/ai/math.ts
6.73 kB
| /** | |
| * Math / formula normalisation for student-facing AI answers. | |
| * | |
| * Pipeline (applied after sanitizeAIResponse): | |
| * 1. Convert LaTeX delimiters \( … \) → $ … $ (inline math) | |
| * 2. Convert LaTeX delimiters \[ … \] → $$ … $$ (display math) | |
| * 3. Collapse excess whitespace produced by stripping reasoning blocks. | |
| * | |
| * Formulas inside the resulting $ … $ blocks are left untouched — KaTeX | |
| * handles them at render time via remark-math + rehype-katex. | |
| * | |
| * chemicalText() is exported separately as a fallback plain-text formatter | |
| * for contexts where ReactMarkdown / KaTeX is not available (e.g. tooltips, | |
| * clipboard copy, alt-text). It converts common TeX subscripts/arrows to | |
| * Unicode equivalents. | |
| */ | |
| // --------------------------------------------------------------------------- | |
| // 1. LaTeX delimiter normalisation → remark-math compatible $ / $$ delimiters | |
| // --------------------------------------------------------------------------- | |
| /** | |
| * Convert \( … \) and \[ … \] delimiters to $ … $ and $$ … $$. | |
| * Also cleans up a handful of stray TeX artefacts that appear outside math | |
| * when models hallucinate raw LaTeX into prose. | |
| */ | |
| export function normalizeMathMarkdown(text: string): string { | |
| if (!text) return ""; | |
| let t = text; | |
| // 1a. Display math \[ … \] → $$ … $$ | |
| // Use non-greedy multiline match so one call handles multiple blocks. | |
| t = t.replace(/\\\[\s*([\s\S]*?)\s*\\\]/g, (_m, inner: string) => `$$${inner.trim()}$$`); | |
| // 1b. Inline math \( … \) → $ … $ | |
| t = t.replace(/\\\(\s*([\s\S]*?)\s*\\\)/g, (_m, inner: string) => `$${inner.trim()}$`); | |
| // 1c. Strip stray \text{} wrappers that ended up outside any math block | |
| // (models sometimes write \text{something} directly in prose). | |
| // Inside $…$ they are valid and must be left alone — handled by KaTeX. | |
| // This regex only removes them when NOT preceded by $ on the same line. | |
| t = t.replace(/\\text\{([^}]*)\}/g, (match, inner: string, offset: number) => { | |
| // Check if inside a $…$ block by counting $ signs before this position | |
| const before = t.slice(0, offset); | |
| const dollarsBefore = (before.match(/\$/g) ?? []).length; | |
| // If odd number of $ before → inside inline math → leave it (return original) | |
| if (dollarsBefore % 2 === 1) return match; | |
| return inner; // outside math — just keep the inner text | |
| }); | |
| // 1d. Replace \rightarrow and \to outside math blocks with → | |
| t = replaceOutsideMath(t, /\\(?:rightarrow|to)\b/g, "→"); | |
| // 1e. Replace \Rightarrow outside math blocks with ⟹ | |
| t = replaceOutsideMath(t, /\\Rightarrow\b/g, "⟹"); | |
| // 1f. Replace \cdot outside math with · | |
| t = replaceOutsideMath(t, /\\cdot\b/g, "·"); | |
| // 1g. Replace \times outside math with × | |
| t = replaceOutsideMath(t, /\\times\b/g, "×"); | |
| // 1h. Collapse 3+ blank lines (side-effect of removing reasoning blocks) | |
| t = t.replace(/\n{3,}/g, "\n\n").trim(); | |
| return t; | |
| } | |
| /** | |
| * Apply a regex replacement only to text segments that are outside $ math $ delimiters. | |
| * Walks the string, alternating between "outside math" and "inside math" segments. | |
| */ | |
| function replaceOutsideMath(text: string, pattern: RegExp, replacement: string): string { | |
| // Split on $ boundaries while preserving the delimiters | |
| const segments = text.split(/(\$\$[\s\S]*?\$\$|\$[^$\n]*?\$)/); | |
| return segments | |
| .map((seg, i) => { | |
| // Even indices → outside math; odd indices → inside a $…$ or $$…$$ block | |
| if (i % 2 === 0) return seg.replace(pattern, replacement); | |
| return seg; | |
| }) | |
| .join(""); | |
| } | |
| // --------------------------------------------------------------------------- | |
| // 2. Plain-text chemical / math formatter (fallback / clipboard) | |
| // --------------------------------------------------------------------------- | |
| const SUB_MAP: Record<string, string> = { | |
| "0": "₀", "1": "₁", "2": "₂", "3": "₃", "4": "₄", | |
| "5": "₅", "6": "₆", "7": "₇", "8": "₈", "9": "₉", | |
| }; | |
| const SUP_MAP: Record<string, string> = { | |
| "0": "⁰", "1": "¹", "2": "²", "3": "³", "4": "⁴", | |
| "5": "⁵", "6": "⁶", "7": "⁷", "8": "⁸", "9": "⁹", | |
| "+": "⁺", "-": "⁻", "n": "ⁿ", | |
| }; | |
| function toSubscript(digits: string): string { | |
| return [...digits].map((d) => SUB_MAP[d] ?? d).join(""); | |
| } | |
| function toSuperscript(digits: string): string { | |
| return [...digits].map((d) => SUP_MAP[d] ?? d).join(""); | |
| } | |
| /** | |
| * Convert chemical/math TeX into readable Unicode plain-text. | |
| * Designed for clipboard copy, tooltips, and accessibility alt-text. | |
| * | |
| * Examples: | |
| * C_6H_{12}O_6 → C₆H₁₂O₆ | |
| * CO_2 → CO₂ | |
| * H_2O → H₂O | |
| * x^2 → x² | |
| * \rightarrow → → | |
| * \text{foo} → foo | |
| * \( E = mc^2 \) → E = mc² | |
| */ | |
| export function chemicalText(raw: string): string { | |
| if (!raw) return ""; | |
| let t = raw; | |
| // Strip LaTeX display/inline delimiters | |
| t = t.replace(/\\\[|\\\]/g, "").replace(/\\\(|\\\)/g, ""); | |
| // \text{…} → inner text | |
| t = t.replace(/\\text\{([^}]*)\}/g, "$1"); | |
| // Arrows and operators — keep trailing space (→ B, × 3 etc.) | |
| t = t.replace(/\\(?:rightarrow|to)\b/g, "→"); | |
| t = t.replace(/\\Rightarrow\b/g, "⟹"); | |
| t = t.replace(/\\(?:leftarrow)\b/g, "←"); | |
| t = t.replace(/\\cdot\b/g, "·"); | |
| t = t.replace(/\\times\b/g, "×"); | |
| t = t.replace(/\\pm\b/g, "±"); | |
| t = t.replace(/\\infty\b/g, "∞"); | |
| // Uppercase Greek used as thermodynamic prefix symbols (ΔH, ΔG, ΔS, ΣF …) | |
| // These appear as "\Delta H" in LaTeX — the space is a LaTeX artifact, not prose space. | |
| // Consume optional trailing space so output is "ΔH" not "Δ H". | |
| t = t.replace(/\\Delta\b\s*/g, "Δ"); | |
| t = t.replace(/\\Gamma\b\s*/g, "Γ"); | |
| t = t.replace(/\\Sigma\b\s*/g, "Σ"); | |
| // Lowercase Greek — keep trailing space (λ = 500, not λ=500) | |
| t = t.replace(/\\alpha\b/g, "α"); | |
| t = t.replace(/\\beta\b/g, "β"); | |
| t = t.replace(/\\gamma\b/g, "γ"); | |
| t = t.replace(/\\lambda\b/g, "λ"); | |
| t = t.replace(/\\mu\b/g, "μ"); | |
| t = t.replace(/\\pi\b/g, "π"); | |
| t = t.replace(/\\theta\b/g, "θ"); | |
| t = t.replace(/\\omega\b/g, "ω"); | |
| t = t.replace(/\\sigma\b/g, "σ"); | |
| // Subscript: _{12} or _2 | |
| t = t.replace(/\_\{([0-9+\-n]+)\}/g, (_m, d: string) => toSubscript(d)); | |
| t = t.replace(/\_([0-9])/g, (_m, d: string) => toSubscript(d)); | |
| // Superscript: ^{2} or ^2 | |
| t = t.replace(/\^\{([0-9+\-n]+)\}/g, (_m, d: string) => toSuperscript(d)); | |
| t = t.replace(/\^([0-9+\-n])/g, (_m, d: string) => toSuperscript(d)); | |
| // Strip remaining bare backslash-commands we didn't handle | |
| t = t.replace(/\\[a-zA-Z]+\b/g, ""); | |
| // Strip leftover braces | |
| t = t.replace(/[{}]/g, ""); | |
| return t.trim(); | |
| } | |