char-fork/llama.cpp
git clone https://git.t4t.associates/char-fork/llama.cpp
92d1bb0c9
char/aliceai-t5-moe
1/* eslint-disable no-irregular-whitespace */ 2import { maskInlineLaTeX , preprocessLaTeX } from '$lib/utils/latex-protection' ; 3import { describe , expect , it , test } from 'vitest' ; 4 5describe ( 'maskInlineLaTeX' , () => { 6it ( 'should protect LaTeX $x + y$ but not money $3.99' , () => { 7const latexExpressions :string [] = []; 8const input = 'I have $10, $3.99 and $x + y$ and $100x$. The amount is $2,000.' ; 9const output = maskInlineLaTeX ( input , latexExpressions ); 10 11expect ( output ). toBe ( 'I have $10, $3.99 and <<LATEX_0>> and <<LATEX_1>>. The amount is $2,000.' ); 12expect ( latexExpressions ). toEqual ([ '$x + y$' , '$100x$' ]); 13}); 14 15it ( 'should ignore money like $5 and $12.99' , () => { 16const latexExpressions :string [] = []; 17const input = 'Prices are $12.99 and $5. Tax?' ; 18const output = maskInlineLaTeX ( input , latexExpressions ); 19 20expect ( output ). toBe ( 'Prices are $12.99 and $5. Tax?' ); 21expect ( latexExpressions ). toEqual ([]); 22}); 23 24it ( 'should protect inline math $a^2 + b^2$ even after text' , () => { 25const latexExpressions :string [] = []; 26const input = 'Pythagorean: $a^2 + b^2 = c^2$.' ; 27const output = maskInlineLaTeX ( input , latexExpressions ); 28 29expect ( output ). toBe ( 'Pythagorean: <<LATEX_0>>.' ); 30expect ( latexExpressions ). toEqual ([ '$a^2 + b^2 = c^2$' ]); 31}); 32 33it ( 'should not protect math that has letter after closing $ (e.g. units)' , () => { 34const latexExpressions :string [] = []; 35const input = 'The cost is $99 and change.' ; 36const output = maskInlineLaTeX ( input , latexExpressions ); 37 38expect ( output ). toBe ( 'The cost is $99 and change.' ); 39expect ( latexExpressions ). toEqual ([]); 40}); 41 42it ( 'should allow $x$ followed by punctuation' , () => { 43const latexExpressions :string [] = []; 44const input = 'We know $x$, right?' ; 45const output = maskInlineLaTeX ( input , latexExpressions ); 46 47expect ( output ). toBe ( 'We know <<LATEX_0>>, right?' ); 48expect ( latexExpressions ). toEqual ([ '$x$' ]); 49}); 50 51it ( 'should work across multiple lines' , () => { 52const latexExpressions :string [] = []; 53const input = `Emma buys cupcakes for $3 each.\nHow much is $x + y$?` ; 54const output = maskInlineLaTeX ( input , latexExpressions ); 55 56expect ( output ). toBe ( `Emma buys cupcakes for $3 each.\nHow much is <<LATEX_0>>?` ); 57expect ( latexExpressions ). toEqual ([ '$x + y$' ]); 58}); 59 60it ( 'should not protect $100 but protect $matrix$' , () => { 61const latexExpressions :string [] = []; 62const input = '$100 and $\\mathrm{GL}_2(\\mathbb{F}_7)$ are different.' ; 63const output = maskInlineLaTeX ( input , latexExpressions ); 64 65expect ( output ). toBe ( '$100 and <<LATEX_0>> are different.' ); 66expect ( latexExpressions ). toEqual ([ '$\\mathrm{GL}_2(\\mathbb{F}_7)$' ]); 67}); 68 69it ( 'should skip if $ is followed by digit and alphanumeric after close (money)' , () => { 70const latexExpressions :string [] = []; 71const input = 'I paid $5 quickly.' ; 72const output = maskInlineLaTeX ( input , latexExpressions ); 73 74expect ( output ). toBe ( 'I paid $5 quickly.' ); 75expect ( latexExpressions ). toEqual ([]); 76}); 77 78it ( 'should protect LaTeX even with special chars inside' , () => { 79const latexExpressions :string [] = []; 80const input = 'Consider $\\alpha_1 + \\beta_2$ now.' ; 81const output = maskInlineLaTeX ( input , latexExpressions ); 82 83expect ( output ). toBe ( 'Consider <<LATEX_0>> now.' ); 84expect ( latexExpressions ). toEqual ([ '$\\alpha_1 + \\beta_2$' ]); 85}); 86 87it ( 'short text' , () => { 88const latexExpressions :string [] = [ '$0$' ]; 89const input = '$a$\n$a$ and $b$' ; 90const output = maskInlineLaTeX ( input , latexExpressions ); 91 92expect ( output ). toBe ( '<<LATEX_1>>\n<<LATEX_2>> and <<LATEX_3>>' ); 93expect ( latexExpressions ). toEqual ([ '$0$' , '$a$' , '$a$' , '$b$' ]); 94}); 95 96it ( 'empty text' , () => { 97const latexExpressions :string [] = []; 98const input = '$\n$$\n' ; 99const output = maskInlineLaTeX ( input , latexExpressions ); 100 101expect ( output ). toBe ( '$\n$$\n' ); 102expect ( latexExpressions ). toEqual ([]); 103}); 104 105it ( 'LaTeX-spacer preceded by backslash' , () => { 106const latexExpressions :string [] = []; 107const input = `\\[ 108\\boxed{ 109\\begin{aligned} 110N_{\\text{att}}^{\\text{(MHA)}} &= 111h \\bigl[\\, d_{\\text{model}}\\;d_{k} + d_{\\text{model}}\\;d_{v}\\, \\bigr] && (\\text{Q,K,V の重み})\\\\ 112&\\quad+ h(d_{k}+d_{k}+d_{v}) && (\\text{バイアス Q,K,V)}\\\\[4pt] 113&\\quad+ (h d_{v})\\, d_{\\text{model}} && (\\text{出力射影 }W^{O})\\\\ 114&\\quad+ d_{\\text{model}} && (\\text{バイアス }b^{O}) 115\\end{aligned}} 116\\]` ; 117const output = maskInlineLaTeX ( input , latexExpressions ); 118 119expect ( output ). toBe ( input ); 120expect ( latexExpressions ). toEqual ([]); 121}); 122}); 123 124describe ( 'preprocessLaTeX' , () => { 125test ( 'converts inline \\( ... \\) to $...$' , () => { 126const input = 127'\\( \\mathrm{GL}_2(\\mathbb{F}_7) \\): Group of invertible matrices with entries in \\(\\mathbb{F}_7\\).' ; 128const output = preprocessLaTeX ( input ); 129 130expect ( output ). toBe ( 131'$ \\mathrm{GL}_2(\\mathbb{F}_7) $: Group of invertible matrices with entries in $\\mathbb{F}_7$.' 132); 133}); 134 135test ( "don't inline \\\\( ... \\) to $...$" , () => { 136const input = 137'Chapter 20 of The TeXbook, in source "Definitions\\\\(also called Macros)", containst the formula \\((x_1,\\ldots,x_n)\\).' ; 138const output = preprocessLaTeX ( input ); 139 140expect ( output ). toBe ( 141'Chapter 20 of The TeXbook, in source "Definitions\\\\(also called Macros)", containst the formula $(x_1,\\ldots,x_n)$.' 142); 143}); 144 145test ( 'preserves display math \\[ ... \\] and protects adjacent text' , () => { 146const input = `Some kernel of \\(\\mathrm{SL}_2(\\mathbb{F}_7)\\): 147\\[ 148\\left\\{ \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, \\begin{pmatrix} -1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\right\\} = \\{\\pm I\\} 149\\]` ; 150const output = preprocessLaTeX ( input ); 151 152expect ( output ). toBe ( `Some kernel of $\\mathrm{SL}_2(\\mathbb{F}_7)$: 153$$ 154\\left\\{ \\begin{pmatrix} 1 & 0 \\\\ 0 & 1 \\end{pmatrix}, \\begin{pmatrix} -1 & 0 \\\\ 0 & -1 \\end{pmatrix} \\right\\} = \\{\\pm I\\} 155$$` ); 156}); 157 158test ( 'handles standalone display math equation' , () => { 159const input = `Algebra: 160\\[ 161x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a} 162\\]` ; 163const output = preprocessLaTeX ( input ); 164 165expect ( output ). toBe ( `Algebra: 166$$ 167x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a} 168$$` ); 169}); 170 171test ( 'does not interpret currency values as LaTeX' , () => { 172const input = 'I have $10, $3.99 and $x + y$ and $100x$. The amount is $2,000.' ; 173const output = preprocessLaTeX ( input ); 174 175expect ( output ). toBe ( 'I have \\$10, \\$3.99 and $x + y$ and $100x$. The amount is \\$2,000.' ); 176}); 177 178test ( 'ignores dollar signs followed by digits (money), but keeps valid math $x + y$' , () => { 179const input = 'I have $10, $3.99 and $x + y$ and $100x$. The amount is $2,000.' ; 180const output = preprocessLaTeX ( input ); 181 182expect ( output ). toBe ( 'I have \\$10, \\$3.99 and $x + y$ and $100x$. The amount is \\$2,000.' ); 183}); 184 185test ( 'handles real-world word problems with amounts and no math delimiters' , () => { 186const input = 187'Emma buys 2 cupcakes for $3 each and 1 cookie for $1.50. How much money does she spend in total?' ; 188const output = preprocessLaTeX ( input ); 189 190expect ( output ). toBe ( 191'Emma buys 2 cupcakes for \\$3 each and 1 cookie for \\$1.50. How much money does she spend in total?' 192); 193}); 194 195test ( 'handles decimal amounts in word problem correctly' , () => { 196const input = 197'Maria has $20. She buys a notebook for $4.75 and a pack of pencils for $3.25. How much change does she receive?' ; 198const output = preprocessLaTeX ( input ); 199 200expect ( output ). toBe ( 201'Maria has \\$20. She buys a notebook for \\$4.75 and a pack of pencils for \\$3.25. How much change does she receive?' 202); 203}); 204 205test ( 'preserves display math with surrounding non-ASCII text' , () => { 206const input = `1 kg の質量は 207\\[ 208E = (1\\ \\text{kg}) \\times (3.0 \\times 10^8\\ \\text{m/s})^2 \\approx 9.0 \\times 10^{16}\\ \\text{J} 209\\] 210というエネルギーに相当します。これは約 21 百万トンの TNT が爆発したときのエネルギーに匹敵します。` ; 211const output = preprocessLaTeX ( input ); 212 213expect ( output ). toBe ( 214`1 kg の質量は 215$$ 216E = (1\\ \\text{kg}) \\times (3.0 \\times 10^8\\ \\text{m/s})^2 \\approx 9.0 \\times 10^{16}\\ \\text{J} 217$$ 218というエネルギーに相当します。これは約 21 百万トンの TNT が爆発したときのエネルギーに匹敵します。` 219); 220}); 221 222test ( 'LaTeX-spacer preceded by backslash' , () => { 223const input = `\\[ 224\\boxed{ 225\\begin{aligned} 226N_{\\text{att}}^{\\text{(MHA)}} &= 227h \\bigl[\\, d_{\\text{model}}\\;d_{k} + d_{\\text{model}}\\;d_{v}\\, \\bigr] && (\\text{Q,K,V の重み})\\\\ 228&\\quad+ h(d_{k}+d_{k}+d_{v}) && (\\text{バイアス Q,K,V)}\\\\[4pt] 229&\\quad+ (h d_{v})\\, d_{\\text{model}} && (\\text{出力射影 }W^{O})\\\\ 230&\\quad+ d_{\\text{model}} && (\\text{バイアス }b^{O}) 231\\end{aligned}} 232\\]` ; 233const output = preprocessLaTeX ( input ); 234 235expect ( output ). toBe ( 236`$$ 237\\boxed{ 238\\begin{aligned} 239N_{\\text{att}}^{\\text{(MHA)}} &= 240h \\bigl[\\, d_{\\text{model}}\\;d_{k} + d_{\\text{model}}\\;d_{v}\\, \\bigr] && (\\text{Q,K,V の重み})\\\\ 241&\\quad+ h(d_{k}+d_{k}+d_{v}) && (\\text{バイアス Q,K,V)}\\\\[4pt] 242&\\quad+ (h d_{v})\\, d_{\\text{model}} && (\\text{出力射影 }W^{O})\\\\ 243&\\quad+ d_{\\text{model}} && (\\text{バイアス }b^{O}) 244\\end{aligned}} 245$$` 246); 247}); 248 249test ( 'converts \\[ ... \\] even when preceded by text without space' , () => { 250const input = 'Some line ...\nAlgebra: \\[x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a}\\]' ; 251const output = preprocessLaTeX ( input ); 252 253expect ( output ). toBe ( 254'Some line ...\nAlgebra: \n$$x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a}$$\n' 255); 256}); 257 258test ( 'converts \\[ ... \\] in table-cells' , () => { 259const input = `| ID | Expression |\n| #1 | \\[ 260x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a} 261\\] |` ; 262const output = preprocessLaTeX ( input ); 263 264expect ( output ). toBe ( 265'| ID | Expression |\n| #1 | $x = \\frac{-b \\pm \\sqrt{\\,b^{2}-4ac\\,}}{2a}$ |' 266); 267}); 268 269test ( 'escapes isolated $ before digits ($5 → \\$5), but not valid math' , () => { 270const input = 'This costs $5 and this is math $x^2$. $100 is money.' ; 271const output = preprocessLaTeX ( input ); 272 273expect ( output ). toBe ( 'This costs \\$5 and this is math $x^2$. \\$100 is money.' ); 274// Note: Since $x^2$ is detected as valid LaTeX, it's preserved. 275// $5 becomes \$5 only *after* real math is masked — but here it's correct because the masking logic avoids treating $5 as math. 276}); 277 278test ( 'display with LaTeX-line-breaks' , () => { 279const input = String . raw `- Algebraic topology, Homotopy Groups of $\mathbb{S}^3$: 280$$\pi_n(\mathbb{S}^3) = \begin{cases} 281\mathbb{Z} & n = 3 \\ 2820 & n > 3, n \neq 4 \\ 283\mathbb{Z}_2 & n = 4 \\ 284\end{cases}$$` ; 285const output = preprocessLaTeX ( input ); 286 287// If the formula contains '\\' the $$-delimiters should be in their own line. 288expect ( output ). toBe ( `- Algebraic topology, Homotopy Groups of $\\mathbb{S}^3$: 289$$\n\\pi_n(\\mathbb{S}^3) = \\begin{cases} 290\\mathbb{Z} & n = 3 \\\\ 2910 & n > 3, n \\neq 4 \\\\ 292\\mathbb{Z}_2 & n = 4 \\\\ 293\\end{cases}\n$$` ); 294}); 295 296test ( 'handles mhchem notation safely if present' , () => { 297const input = 'Chemical reaction: \\( \\ce{H2O} \\) and $\\ce{CO2}$' ; 298const output = preprocessLaTeX ( input ); 299 300expect ( output ). toBe ( 'Chemical reaction: $ \\ce{H2O} $ and $\\ce{CO2}$' ); 301}); 302 303test ( 'preserves code blocks' , () => { 304const input = 'Inline code: `sum $total` and block:\n```\ndollar $amount\n```\nEnd.' ; 305const output = preprocessLaTeX ( input ); 306 307expect ( output ). toBe ( input ); // Code blocks prevent misinterpretation 308}); 309 310test ( 'preserves backslash parentheses in code blocks (GitHub issue)' , () => { 311const input = '```python\nfoo = "\\(bar\\)"\n```' ; 312const output = preprocessLaTeX ( input ); 313 314expect ( output ). toBe ( input ); // Code blocks should not have LaTeX conversion applied 315}); 316 317test ( 'preserves backslash brackets in code blocks' , () => { 318const input = '```python\nfoo = "\\[bar\\]"\n```' ; 319const output = preprocessLaTeX ( input ); 320 321expect ( output ). toBe ( input ); // Code blocks should not have LaTeX conversion applied 322}); 323 324test ( 'preserves backslash parentheses in inline code' , () => { 325const input = 'Use `foo = "\\(bar\\)"` in your code.' ; 326const output = preprocessLaTeX ( input ); 327 328expect ( output ). toBe ( input ); 329}); 330 331test ( 'escape backslash in mchem ce' , () => { 332const input = 'mchem ce:\n$\\ce{2H2(g) + O2(g) -> 2H2O(l)}$' ; 333const output = preprocessLaTeX ( input ); 334 335// mhchem-escape would insert a backslash here. 336expect ( output ). toBe ( 'mchem ce:\n$\\ce{2H2(g) + O2(g) -> 2H2O(l)}$' ); 337}); 338 339test ( 'escape backslash in mchem pu' , () => { 340const input = 'mchem pu:\n$\\pu{-572 kJ mol^{-1}}$' ; 341const output = preprocessLaTeX ( input ); 342 343// mhchem-escape would insert a backslash here. 344expect ( output ). toBe ( 'mchem pu:\n$\\pu{-572 kJ mol^{-1}}$' ); 345}); 346 347test ( 'LaTeX in blockquotes with display math' , () => { 348const input = 349'> **Definition (limit):** \n> \\[\n> \\lim_{x\\to a} f(x) = L\n> \\]\n> means that as \\(x\\) gets close to \\(a\\).' ; 350const output = preprocessLaTeX ( input ); 351 352// Blockquote markers should be preserved, LaTeX should be converted 353expect ( output ). toContain ( '> **Definition (limit):**' ); 354expect ( output ). toContain ( '$$' ); 355expect ( output ). toContain ( '$x$' ); 356expect ( output ). not . toContain ( '\\[' ); 357expect ( output ). not . toContain ( '\\]' ); 358expect ( output ). not . toContain ( '\\(' ); 359expect ( output ). not . toContain ( '\\)' ); 360}); 361 362test ( 'LaTeX in blockquotes with inline math' , () => { 363const input = 364"> The derivative \\(f'(x)\\) at point \\(x=a\\) measures slope.\n> Formula: \\(f'(a)=\\lim_{h\\to 0}\\frac{f(a+h)-f(a)}{h}\\)" ; 365const output = preprocessLaTeX ( input ); 366 367// Blockquote markers should be preserved, inline LaTeX converted to $...$ 368expect ( output ). toContain ( "> The derivative $f'(x)$ at point $x=a$ measures slope." ); 369expect ( output ). toContain ( "> Formula: $f'(a)=\\lim_{h\\to 0}\\frac{f(a+h)-f(a)}{h}$" ); 370}); 371 372test ( 'Mixed content with blockquotes and regular text' , () => { 373const input = 374'Regular text with \\(x^2\\).\n\n> Quote with \\(y^2\\).\n\nMore text with \\(z^2\\).' ; 375const output = preprocessLaTeX ( input ); 376 377// All LaTeX should be converted, blockquote markers preserved 378expect ( output ). toBe ( 'Regular text with $x^2$.\n\n> Quote with $y^2$.\n\nMore text with $z^2$.' ); 379}); 380}); 381 382// The fast path skips the whole protect/restore pipeline when the content has 383// neither `$` nor a backslash. These pin the assumption it relies on: such 384// content is returned byte-for-byte unchanged, and anything carrying a trigger 385// character still goes through the full pipeline. 386describe ( 'preprocessLaTeX fast path' , () => { 387const untouched = [ 388[ 'plain prose' , 'The quick brown fox jumps over the lazy dog.' ], 389[ 'headings and lists' , '# Title\n\n- one\n- two\n\n## Sub\n\n1. first\n2. second' ], 390[ 'fenced code without escapes' , '```ts\nconst x = 1;\n```' ], 391[ 'inline code and emphasis' , 'Use `npm run build` with **bold** and _italic_.' ], 392[ 'blockquotes' , '> quoted line\n> another line' ], 393[ 'tables' , '| a | b |\n| --- | --- |\n| 1 | 2 |' ], 394[ 'brackets and parens without escapes' , 'array[0] and call(arg) and [link](/url)' ], 395[ 'multi-paragraph prose' , 'First para.\n\nSecond para.\n\nThird para.' ] 396]; 397 398for ( const [ label , input ] of untouched ) { 399test ( `returns ${ label } unchanged` , () => { 400expect ( preprocessLaTeX ( input )). toBe ( input ); 401}); 402} 403 404test ( 'still processes content once a trigger character is present' , () => { 405expect ( preprocessLaTeX ( 'inline \\(x^2\\) here' )). toBe ( 'inline $x^2$ here' ); 406expect ( preprocessLaTeX ( 'Price: $5' )). toBe ( 'Price: \\$5' ); 407}); 408 409test ( 'fast path result matches the full pipeline for trigger-free content' , () => { 410// Appending a trigger char forces the slow path; stripping it back off 411// must agree with what the fast path returned for the same prefix. 412const body = 'Plain paragraph with no math.\n\nAnother paragraph here.' ; 413const viaFastPath = preprocessLaTeX ( body ); 414const viaFullPipeline = preprocessLaTeX ( ` ${ body } \n\n\\(z\\)` ); 415 416expect ( viaFullPipeline . startsWith ( viaFastPath )). toBe ( true ); 417}); 418});