Skip to content
GitLab
Projects
Groups
Snippets
/
Help
Help
Support
Community forum
Keyboard shortcuts
?
Submit feedback
Sign in
Toggle navigation
Menu
Open sidebar
math_tutor_dev
public_math_tutor
Commits
a94d3cb3
Commit
a94d3cb3
authored
Jun 26, 2026
by
Kantz
Browse files
conversion from mathjax to mathlive
parent
4e0eb686
Changes
15
Hide whitespace changes
Inline
Side-by-side
math-tutor/frontend/index.html
View file @
a94d3cb3
...
@@ -5,22 +5,6 @@
...
@@ -5,22 +5,6 @@
<link
rel=
"icon"
type=
"image/png"
href=
"/SuMINT-Logo.png"
/>
<link
rel=
"icon"
type=
"image/png"
href=
"/SuMINT-Logo.png"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<meta
name=
"viewport"
content=
"width=device-width, initial-scale=1.0"
/>
<title>
Math Tutor
</title>
<title>
Math Tutor
</title>
<script>
window
.
MathJax
=
{
tex
:
{
inlineMath
:
[[
"
$
"
,
"
$
"
],
[
"
\\
(
"
,
"
\\
)
"
]],
displayMath
:
[[
"
$$
"
,
"
$$
"
],
[
"
\\
[
"
,
"
\\
]
"
]],
},
options
:
{
skipHtmlTags
:
[
"
script
"
,
"
noscript
"
,
"
style
"
,
"
textarea
"
,
"
pre
"
,
"
code
"
],
},
};
</script>
<script
id=
"mathjax-script"
async
src=
"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
></script>
</head>
</head>
<body>
<body>
<div
id=
"root"
></div>
<div
id=
"root"
></div>
...
...
math-tutor/frontend/package.json
View file @
a94d3cb3
...
@@ -12,6 +12,7 @@
...
@@ -12,6 +12,7 @@
},
},
"dependencies"
:
{
"dependencies"
:
{
"iconoir-react"
:
"^7.11.0"
,
"iconoir-react"
:
"^7.11.0"
,
"mathlive"
:
"^0.110.0"
,
"react"
:
"^19.2.0"
,
"react"
:
"^19.2.0"
,
"react-dom"
:
"^19.2.0"
,
"react-dom"
:
"^19.2.0"
,
"react-markdown"
:
"^10.1.0"
,
"react-markdown"
:
"^10.1.0"
,
...
...
math-tutor/frontend/pnpm-lock.yaml
View file @
a94d3cb3
...
@@ -11,6 +11,9 @@ importers:
...
@@ -11,6 +11,9 @@ importers:
iconoir-react
:
iconoir-react
:
specifier
:
^7.11.0
specifier
:
^7.11.0
version
:
7.11.0(react@19.2.3)
version
:
7.11.0(react@19.2.3)
mathlive
:
specifier
:
^0.110.0
version
:
0.110.0
react
:
react
:
specifier
:
^19.2.0
specifier
:
^19.2.0
version
:
19.2.3
version
:
19.2.3
...
@@ -63,6 +66,9 @@ importers:
...
@@ -63,6 +66,9 @@ importers:
packages
:
packages
:
'
@arnog/colors@0.5.0'
:
resolution
:
{
integrity
:
sha512-NB7yqrO7qvInsqJdqz6C6mDU+2oiKmbBbk3czMS0E7KkGxLRy538tEHCsB5TKqmaxxXpi6/U120TnGl8NsDePg==
}
'
@babel/code-frame@7.28.6'
:
'
@babel/code-frame@7.28.6'
:
resolution
:
{
integrity
:
sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==
}
resolution
:
{
integrity
:
sha512-JYgintcMjRiCvS8mMECzaEn+m3PfoQiyqukOMCCVQtoJGYJw8j/8LBJEiqkHLkfwCcs74E3pbAUFNg7d9VNJ+Q==
}
engines
:
{
node
:
'
>=6.9.0'
}
engines
:
{
node
:
'
>=6.9.0'
}
...
@@ -146,6 +152,10 @@ packages:
...
@@ -146,6 +152,10 @@ packages:
resolution
:
{
integrity
:
sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==
}
resolution
:
{
integrity
:
sha512-0ZrskXVEHSWIqZM/sQZ4EV3jZJXRkio/WCxaqKZP1g//CEWEPSfeZFcms4XeKBCHU0ZKnIkdJeU/kF+eRp5lBg==
}
engines
:
{
node
:
'
>=6.9.0'
}
engines
:
{
node
:
'
>=6.9.0'
}
'
@cortex-js/compute-engine@0.58.0'
:
resolution
:
{
integrity
:
sha512-N0+vXjVZfKwJS7ZIGvq41mojI55m5TrdDXveAXzNjzjZ9iNiNrdEzXL2EmrcP+9GCWCwTonrEeZWAIG78f3INg==
}
engines
:
{
node
:
'
>=21.7.3'
,
npm
:
'
>=10.5.0'
}
'
@emnapi/core@1.8.1'
:
'
@emnapi/core@1.8.1'
:
resolution
:
{
integrity
:
sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==
}
resolution
:
{
integrity
:
sha512-AvT9QFpxK0Zd8J0jopedNm+w/2fIzvtPKPjqyw9jwvBaReTTqPBk9Hixaz7KbjimP+QNz605/XnjFcDAL2pqBg==
}
...
@@ -270,24 +280,28 @@ packages:
...
@@ -270,24 +280,28 @@ packages:
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
cpu
:
[
arm64
]
cpu
:
[
arm64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rolldown/binding-linux-arm64-musl@1.0.0-beta.50'
:
'
@rolldown/binding-linux-arm64-musl@1.0.0-beta.50'
:
resolution
:
{
integrity
:
sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==
}
resolution
:
{
integrity
:
sha512-L0zRdH2oDPkmB+wvuTl+dJbXCsx62SkqcEqdM+79LOcB+PxbAxxjzHU14BuZIQdXcAVDzfpMfaHWzZuwhhBTcw==
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
cpu
:
[
arm64
]
cpu
:
[
arm64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rolldown/binding-linux-x64-gnu@1.0.0-beta.50'
:
'
@rolldown/binding-linux-x64-gnu@1.0.0-beta.50'
:
resolution
:
{
integrity
:
sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==
}
resolution
:
{
integrity
:
sha512-gyoI8o/TGpQd3OzkJnh1M2kxy1Bisg8qJ5Gci0sXm9yLFzEXIFdtc4EAzepxGvrT2ri99ar5rdsmNG0zP0SbIg==
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
cpu
:
[
x64
]
cpu
:
[
x64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
glibc
]
'
@rolldown/binding-linux-x64-musl@1.0.0-beta.50'
:
'
@rolldown/binding-linux-x64-musl@1.0.0-beta.50'
:
resolution
:
{
integrity
:
sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==
}
resolution
:
{
integrity
:
sha512-zti8A7M+xFDpKlghpcCAzyOi+e5nfUl3QhU023ce5NCgUxRG5zGP2GR9LTydQ1rnIPwZUVBWd4o7NjZDaQxaXA==
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
engines
:
{
node
:
^20.19.0 || >=22.12.0
}
cpu
:
[
x64
]
cpu
:
[
x64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
musl
]
'
@rolldown/binding-openharmony-arm64@1.0.0-beta.50'
:
'
@rolldown/binding-openharmony-arm64@1.0.0-beta.50'
:
resolution
:
{
integrity
:
sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==
}
resolution
:
{
integrity
:
sha512-eZUssog7qljrrRU9Mi0eqYEPm3Ch0UwB+qlWPMKSUXHNqhm3TvDZarJQdTevGEfu3EHAXJvBIe0YFYr0TPVaMA==
}
...
@@ -522,6 +536,10 @@ packages:
...
@@ -522,6 +536,10 @@ packages:
comma-separated-tokens@2.0.3
:
comma-separated-tokens@2.0.3
:
resolution
:
{
integrity
:
sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
}
resolution
:
{
integrity
:
sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==
}
complex-esm@2.1.1-esm1
:
resolution
:
{
integrity
:
sha512-IShBEWHILB9s7MnfyevqNGxV0A1cfcSnewL/4uPFiSxkcQL4Mm3FxJ0pXMtCXuWLjYz3lRRyk6OfkeDZcjD6nw==
}
engines
:
{
node
:
'
>=16.14.2'
,
npm
:
'
>=8.5.0'
}
concat-map@0.0.1
:
concat-map@0.0.1
:
resolution
:
{
integrity
:
sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
}
resolution
:
{
integrity
:
sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==
}
...
@@ -548,6 +566,9 @@ packages:
...
@@ -548,6 +566,9 @@ packages:
supports-color
:
supports-color
:
optional
:
true
optional
:
true
decimal.js@10.6.0
:
resolution
:
{
integrity
:
sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==
}
decode-named-character-reference@1.3.0
:
decode-named-character-reference@1.3.0
:
resolution
:
{
integrity
:
sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==
}
resolution
:
{
integrity
:
sha512-GtpQYB283KrPp6nRw50q3U9/VfOutZOe103qlN7BPP6Ad27xYnOIWv4lPzo8HCAL+mMZofJ9KEy30fq6MfaK6Q==
}
...
@@ -827,24 +848,28 @@ packages:
...
@@ -827,24 +848,28 @@ packages:
engines
:
{
node
:
'
>=
12.0.0'
}
engines
:
{
node
:
'
>=
12.0.0'
}
cpu
:
[
arm64
]
cpu
:
[
arm64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
glibc
]
lightningcss-linux-arm64-musl@1.30.2
:
lightningcss-linux-arm64-musl@1.30.2
:
resolution
:
{
integrity
:
sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==
}
resolution
:
{
integrity
:
sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==
}
engines
:
{
node
:
'
>=
12.0.0'
}
engines
:
{
node
:
'
>=
12.0.0'
}
cpu
:
[
arm64
]
cpu
:
[
arm64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
musl
]
lightningcss-linux-x64-gnu@1.30.2
:
lightningcss-linux-x64-gnu@1.30.2
:
resolution
:
{
integrity
:
sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==
}
resolution
:
{
integrity
:
sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==
}
engines
:
{
node
:
'
>=
12.0.0'
}
engines
:
{
node
:
'
>=
12.0.0'
}
cpu
:
[
x64
]
cpu
:
[
x64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
glibc
]
lightningcss-linux-x64-musl@1.30.2
:
lightningcss-linux-x64-musl@1.30.2
:
resolution
:
{
integrity
:
sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==
}
resolution
:
{
integrity
:
sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==
}
engines
:
{
node
:
'
>=
12.0.0'
}
engines
:
{
node
:
'
>=
12.0.0'
}
cpu
:
[
x64
]
cpu
:
[
x64
]
os
:
[
linux
]
os
:
[
linux
]
libc
:
[
musl
]
lightningcss-win32-arm64-msvc@1.30.2
:
lightningcss-win32-arm64-msvc@1.30.2
:
resolution
:
{
integrity
:
sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==
}
resolution
:
{
integrity
:
sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==
}
...
@@ -875,6 +900,9 @@ packages:
...
@@ -875,6 +900,9 @@ packages:
lru-cache@5.1.1
:
lru-cache@5.1.1
:
resolution
:
{
integrity
:
sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
}
resolution
:
{
integrity
:
sha512-KpNARQA3Iwv+jTA0utUVVbrh+Jlrr1Fv0e56GGzAFOXN7dk/FviaDW8LHmK52DlcH4WP2n6gI8vN1aesBFgo9w==
}
mathlive@0.110.0
:
resolution
:
{
integrity
:
sha512-UOpJsQ6h1eeN0xZULGTl1MwUB/lZLCDuzKeHvKEh7Zra8U/rtgDNrssj5PZdJtBTIV48AfEhtv6rv47AeMOxJA==
}
mdast-util-from-markdown@2.0.2
:
mdast-util-from-markdown@2.0.2
:
resolution
:
{
integrity
:
sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
}
resolution
:
{
integrity
:
sha512-uZhTV/8NBuw0WHkPTrCqDOl0zVe1BIng5ZtHoDk49ME1qqcjYmmLmOf0gELgcRMxN4w2iuIeVso5/6QymSrgmA==
}
...
@@ -1271,6 +1299,8 @@ packages:
...
@@ -1271,6 +1299,8 @@ packages:
snapshots
:
snapshots
:
'
@arnog/colors@0.5.0'
:
{}
'
@babel/code-frame@7.28.6'
:
'
@babel/code-frame@7.28.6'
:
dependencies
:
dependencies
:
'
@babel/helper-validator-identifier'
:
7.28.5
'
@babel/helper-validator-identifier'
:
7.28.5
...
@@ -1383,6 +1413,12 @@ snapshots:
...
@@ -1383,6 +1413,12 @@ snapshots:
'
@babel/helper-string-parser'
:
7.27.1
'
@babel/helper-string-parser'
:
7.27.1
'@babel/helper-validator-identifier'
:
7.28.5
'@babel/helper-validator-identifier'
:
7.28.5
'
@cortex-js/compute-engine@0.58.0'
:
dependencies
:
'
@arnog/colors'
:
0.5.0
complex-esm
:
2.1.1-esm1
decimal.js
:
10.6.0
'
@emnapi/core@1.8.1'
:
'
@emnapi/core@1.8.1'
:
dependencies
:
dependencies
:
'
@emnapi/wasi-threads'
:
1.1.0
'
@emnapi/wasi-threads'
:
1.1.0
...
@@ -1772,6 +1808,8 @@ snapshots:
...
@@ -1772,6 +1808,8 @@ snapshots:
comma-separated-tokens@2.0.3
:
{}
comma-separated-tokens@2.0.3
:
{}
complex-esm@2.1.1-esm1
:
{}
concat-map@0.0.1
:
{}
concat-map@0.0.1
:
{}
convert-source-map@2.0.0
:
{}
convert-source-map@2.0.0
:
{}
...
@@ -1790,6 +1828,8 @@ snapshots:
...
@@ -1790,6 +1828,8 @@ snapshots:
dependencies
:
dependencies
:
ms
:
2.1.3
ms
:
2.1.3
decimal.js@10.6.0
:
{}
decode-named-character-reference@1.3.0
:
decode-named-character-reference@1.3.0
:
dependencies
:
dependencies
:
character-entities
:
2.0.2
character-entities
:
2.0.2
...
@@ -2092,6 +2132,10 @@ snapshots:
...
@@ -2092,6 +2132,10 @@ snapshots:
dependencies
:
dependencies
:
yallist
:
3.1.1
yallist
:
3.1.1
mathlive@0.110.0
:
dependencies
:
'
@cortex-js/compute-engine'
:
0.58.0
mdast-util-from-markdown@2.0.2
:
mdast-util-from-markdown@2.0.2
:
dependencies
:
dependencies
:
'
@types/mdast'
:
4.0.4
'
@types/mdast'
:
4.0.4
...
...
math-tutor/frontend/src/components/Chat/MessageBubble.tsx
View file @
a94d3cb3
...
@@ -2,7 +2,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
...
@@ -2,7 +2,8 @@ import { useEffect, useMemo, useRef, useState } from "react";
import
ReactMarkdown
,
{
defaultUrlTransform
}
from
"
react-markdown
"
;
import
ReactMarkdown
,
{
defaultUrlTransform
}
from
"
react-markdown
"
;
import
type
{
RetrievedDoc
}
from
"
../Retrieval/DocPanel
"
;
import
type
{
RetrievedDoc
}
from
"
../Retrieval/DocPanel
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
escapeAsterisksInsideMath
}
from
"
../../utils/mathMarkdown
"
;
import
{
escapeMarkdownInMath
}
from
"
../../utils/mathText
"
;
import
{
renderMath
}
from
"
../../utils/mathRender
"
;
type
MessageBubbleProps
=
{
type
MessageBubbleProps
=
{
role
:
"
user
"
|
"
assistant
"
;
role
:
"
user
"
|
"
assistant
"
;
...
@@ -12,14 +13,6 @@ type MessageBubbleProps = {
...
@@ -12,14 +13,6 @@ type MessageBubbleProps = {
docSlugIndex
?:
Record
<
string
,
RetrievedDoc
>
;
docSlugIndex
?:
Record
<
string
,
RetrievedDoc
>
;
};
};
declare
global
{
interface
Window
{
MathJax
?:
{
typesetPromise
?:
(
elements
?:
Element
[])
=>
Promise
<
void
>
;
};
}
}
export
default
function
MessageBubble
({
export
default
function
MessageBubble
({
role
,
role
,
text
,
text
,
...
@@ -36,15 +29,15 @@ export default function MessageBubble({
...
@@ -36,15 +29,15 @@ export default function MessageBubble({
if
(
role
!==
"
assistant
"
||
parts
.
length
<
2
)
{
if
(
role
!==
"
assistant
"
||
parts
.
length
<
2
)
{
return
{
return
{
thinkingText
:
""
,
thinkingText
:
""
,
answerText
:
escape
AsterisksInside
Math
(
text
),
answerText
:
escape
MarkdownIn
Math
(
text
),
};
};
}
}
const
rawThinking
=
parts
.
slice
(
0
,
-
1
).
join
(
"
</think>
"
).
trim
();
const
rawThinking
=
parts
.
slice
(
0
,
-
1
).
join
(
"
</think>
"
).
trim
();
const
rawAnswer
=
parts
[
parts
.
length
-
1
].
trim
();
const
rawAnswer
=
parts
[
parts
.
length
-
1
].
trim
();
return
{
return
{
thinkingText
:
escape
AsterisksInside
Math
(
rawThinking
),
thinkingText
:
escape
MarkdownIn
Math
(
rawThinking
),
answerText
:
escape
AsterisksInside
Math
(
rawAnswer
),
answerText
:
escape
MarkdownIn
Math
(
rawAnswer
),
};
};
},
[
role
,
text
]);
},
[
role
,
text
]);
...
@@ -129,22 +122,7 @@ export default function MessageBubble({
...
@@ -129,22 +122,7 @@ export default function MessageBubble({
return
;
return
;
}
}
const
typeset
=
()
=>
{
renderMath
(
bubbleRef
.
current
);
if
(
window
.
MathJax
?.
typesetPromise
&&
bubbleRef
.
current
)
{
window
.
MathJax
.
typesetPromise
([
bubbleRef
.
current
]).
catch
(()
=>
undefined
);
}
};
if
(
window
.
MathJax
?.
typesetPromise
)
{
typeset
();
return
;
}
const
script
=
document
.
getElementById
(
"
mathjax-script
"
);
if
(
script
)
{
script
.
addEventListener
(
"
load
"
,
typeset
);
return
()
=>
script
.
removeEventListener
(
"
load
"
,
typeset
);
}
},
[
answerText
,
isRawView
,
isThinkingExpanded
,
thinkingText
]);
},
[
answerText
,
isRawView
,
isThinkingExpanded
,
thinkingText
]);
return
(
return
(
...
...
math-tutor/frontend/src/components/Chat/MessageInput.tsx
View file @
a94d3cb3
import
{
useEffect
,
useRef
}
from
"
react
"
;
import
"
mathlive
"
;
import
{
useEffect
,
useRef
,
useState
}
from
"
react
"
;
import
{
EditPencil
,
Send
,
Upload
}
from
"
iconoir-react
"
;
import
{
EditPencil
,
Send
,
Upload
}
from
"
iconoir-react
"
;
import
type
{
MathfieldElement
}
from
"
mathlive
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
type
MessageInputProps
=
{
type
MessageInputProps
=
{
...
@@ -28,9 +30,12 @@ export default function MessageInput({
...
@@ -28,9 +30,12 @@ export default function MessageInput({
const
canSend
=
value
.
trim
().
length
>
0
;
const
canSend
=
value
.
trim
().
length
>
0
;
const
uploadInputRef
=
useRef
<
HTMLInputElement
|
null
>
(
null
);
const
uploadInputRef
=
useRef
<
HTMLInputElement
|
null
>
(
null
);
const
textareaRef
=
useRef
<
HTMLTextAreaElement
|
null
>
(
null
);
const
textareaRef
=
useRef
<
HTMLTextAreaElement
|
null
>
(
null
);
const
mathfieldRef
=
useRef
<
MathfieldElement
|
null
>
(
null
);
const
wasSendingRef
=
useRef
(
isSending
);
const
wasSendingRef
=
useRef
(
isSending
);
const
shouldRestoreFocusRef
=
useRef
(
false
);
const
shouldRestoreFocusRef
=
useRef
(
false
);
const
sendButtonStartedFromTextareaRef
=
useRef
(
false
);
const
sendButtonStartedFromTextareaRef
=
useRef
(
false
);
const
[
isMathEditorOpen
,
setIsMathEditorOpen
]
=
useState
(
false
);
const
[
mathValue
,
setMathValue
]
=
useState
(
""
);
useEffect
(()
=>
{
useEffect
(()
=>
{
if
(
wasSendingRef
.
current
&&
!
isSending
)
{
if
(
wasSendingRef
.
current
&&
!
isSending
)
{
...
@@ -43,6 +48,23 @@ export default function MessageInput({
...
@@ -43,6 +48,23 @@ export default function MessageInput({
wasSendingRef
.
current
=
isSending
;
wasSendingRef
.
current
=
isSending
;
},
[
isSending
]);
},
[
isSending
]);
useEffect
(()
=>
{
const
mathfield
=
mathfieldRef
.
current
;
if
(
!
mathfield
)
{
return
;
}
mathfield
.
smartFence
=
true
;
mathfield
.
mathVirtualKeyboardPolicy
=
"
manual
"
;
const
handleInput
=
()
=>
setMathValue
(
mathfield
.
value
);
mathfield
.
addEventListener
(
"
input
"
,
handleInput
);
return
()
=>
{
mathfield
.
removeEventListener
(
"
input
"
,
handleInput
);
};
},
[
isMathEditorOpen
]);
const
handleUploadClick
=
()
=>
{
const
handleUploadClick
=
()
=>
{
uploadInputRef
.
current
?.
click
();
uploadInputRef
.
current
?.
click
();
};
};
...
@@ -63,6 +85,38 @@ export default function MessageInput({
...
@@ -63,6 +85,38 @@ export default function MessageInput({
onSend
();
onSend
();
};
};
const
insertAtCursor
=
(
snippet
:
string
)
=>
{
const
textarea
=
textareaRef
.
current
;
if
(
!
textarea
)
{
onChange
(
value
?
`
${
value
}
${
snippet
}
`
:
snippet
);
return
;
}
const
start
=
textarea
.
selectionStart
;
const
end
=
textarea
.
selectionEnd
;
const
nextValue
=
value
.
slice
(
0
,
start
)
+
snippet
+
value
.
slice
(
end
);
onChange
(
nextValue
);
requestAnimationFrame
(()
=>
{
const
nextCursor
=
start
+
snippet
.
length
;
textarea
.
focus
({
preventScroll
:
true
});
textarea
.
setSelectionRange
(
nextCursor
,
nextCursor
);
});
};
const
handleInsertMath
=
()
=>
{
const
latex
=
mathValue
.
trim
();
if
(
!
latex
)
{
return
;
}
insertAtCursor
(
`$
${
latex
}
$`
);
setMathValue
(
""
);
if
(
mathfieldRef
.
current
)
{
mathfieldRef
.
current
.
value
=
""
;
}
};
return
(
return
(
<
div
className
=
"composer"
>
<
div
className
=
"composer"
>
<
div
className
=
"composer-row"
>
<
div
className
=
"composer-row"
>
...
@@ -93,6 +147,16 @@ export default function MessageInput({
...
@@ -93,6 +147,16 @@ export default function MessageInput({
className
=
"canvas-upload-input"
className
=
"canvas-upload-input"
onChange
=
{
handleUploadChange
}
onChange
=
{
handleUploadChange
}
/>
/>
<
button
className
=
"btn"
type
=
"button"
onClick
=
{
()
=>
setIsMathEditorOpen
((
prev
)
=>
!
prev
)
}
disabled
=
{
isSending
}
aria-pressed
=
{
isMathEditorOpen
}
title
=
"Formula editor"
>
f (x)
</
button
>
{
onTip
?
(
{
onTip
?
(
<
button
<
button
className
=
"btn"
className
=
"btn"
...
@@ -176,6 +240,27 @@ export default function MessageInput({
...
@@ -176,6 +240,27 @@ export default function MessageInput({
}
}
}
}
}
}
/>
/>
{
isMathEditorOpen
?
(
<
div
className
=
"composer-math"
>
<
math
-
field
ref
=
{
mathfieldRef
}
className
=
"composer-mathfield"
onKeyDown
=
{
(
event
)
=>
{
if
(
isSending
)
{
event
.
preventDefault
();
return
;
}
if
(
event
.
key
===
"
Enter
"
&&
!
event
.
shiftKey
)
{
event
.
preventDefault
();
handleInsertMath
();
}
}
}
>
{
mathValue
}
</
math
-
field
>
</
div
>
)
:
null
}
</
div
>
</
div
>
);
);
}
}
math-tutor/frontend/src/components/Retrieval/DocCard.tsx
View file @
a94d3cb3
import
{
useEffect
,
useMemo
,
useRef
}
from
"
react
"
;
import
{
useEffect
,
useMemo
,
useRef
}
from
"
react
"
;
import
ReactMarkdown
from
"
react-markdown
"
;
import
ReactMarkdown
from
"
react-markdown
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
escapeAsterisksInsideMath
}
from
"
../../utils/mathMarkdown
"
;
import
{
escapeMarkdownInMath
}
from
"
../../utils/mathText
"
;
import
{
renderMath
}
from
"
../../utils/mathRender
"
;
declare
global
{
interface
Window
{
MathJax
?:
{
typesetPromise
?:
(
elements
?:
Element
[])
=>
Promise
<
void
>
;
};
}
}
type
DocCardProps
=
{
type
DocCardProps
=
{
title
:
string
;
title
:
string
;
...
@@ -32,27 +25,12 @@ export default function DocCard({
...
@@ -32,27 +25,12 @@ export default function DocCard({
}:
DocCardProps
)
{
}:
DocCardProps
)
{
const
contentRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
contentRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
renderedSnippet
=
useMemo
(
const
renderedSnippet
=
useMemo
(
()
=>
escape
AsterisksInside
Math
(
snippetMarkdown
),
()
=>
escape
MarkdownIn
Math
(
snippetMarkdown
),
[
snippetMarkdown
]
[
snippetMarkdown
]
);
);
useEffect
(()
=>
{
useEffect
(()
=>
{
const
typeset
=
()
=>
{
renderMath
(
contentRef
.
current
);
if
(
window
.
MathJax
?.
typesetPromise
&&
contentRef
.
current
)
{
window
.
MathJax
.
typesetPromise
([
contentRef
.
current
]).
catch
(()
=>
undefined
);
}
};
if
(
window
.
MathJax
?.
typesetPromise
)
{
typeset
();
return
;
}
const
script
=
document
.
getElementById
(
"
mathjax-script
"
);
if
(
script
)
{
script
.
addEventListener
(
"
load
"
,
typeset
);
return
()
=>
script
.
removeEventListener
(
"
load
"
,
typeset
);
}
},
[
snippetMarkdown
]);
},
[
snippetMarkdown
]);
return
(
return
(
...
...
math-tutor/frontend/src/components/Task/SocraticPanel.tsx
View file @
a94d3cb3
import
{
useEffect
,
useMemo
,
useRef
}
from
"
react
"
;
import
{
useEffect
,
useMemo
,
useRef
}
from
"
react
"
;
import
ReactMarkdown
from
"
react-markdown
"
;
import
ReactMarkdown
from
"
react-markdown
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
escapeAsterisksInsideMath
}
from
"
../../utils/mathMarkdown
"
;
import
{
escapeMarkdownInMath
}
from
"
../../utils/mathText
"
;
import
{
renderMath
}
from
"
../../utils/mathRender
"
;
type
SocraticPanelProps
=
{
type
SocraticPanelProps
=
{
selectedTopicLabel
?:
string
;
selectedTopicLabel
?:
string
;
...
@@ -17,7 +18,7 @@ export default function SocraticPanel({
...
@@ -17,7 +18,7 @@ export default function SocraticPanel({
const
contentRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
contentRef
=
useRef
<
HTMLDivElement
|
null
>
(
null
);
const
summaryMarkdown
=
selectedTopicSummary
?.
trim
()
||
""
;
const
summaryMarkdown
=
selectedTopicSummary
?.
trim
()
||
""
;
const
renderedSummary
=
useMemo
(
const
renderedSummary
=
useMemo
(
()
=>
escape
AsterisksInside
Math
(
summaryMarkdown
),
()
=>
escape
MarkdownIn
Math
(
summaryMarkdown
),
[
summaryMarkdown
]
[
summaryMarkdown
]
);
);
...
@@ -25,11 +26,7 @@ export default function SocraticPanel({
...
@@ -25,11 +26,7 @@ export default function SocraticPanel({
if
(
!
contentRef
.
current
)
{
if
(
!
contentRef
.
current
)
{
return
;
return
;
}
}
const
mathjax
=
window
.
MathJax
;
renderMath
(
contentRef
.
current
);
if
(
!
mathjax
?.
typesetPromise
)
{
return
;
}
mathjax
.
typesetPromise
([
contentRef
.
current
]).
catch
(()
=>
undefined
);
},
[
selectedTopicSummary
]);
},
[
selectedTopicSummary
]);
return
(
return
(
...
...
math-tutor/frontend/src/components/Task/TaskPanel.tsx
View file @
a94d3cb3
import
{
useEffect
,
useRef
}
from
"
react
"
;
import
{
useEffect
,
useRef
}
from
"
react
"
;
import
type
{
TaskImage
}
from
"
../../api/taskApi
"
;
import
type
{
TaskImage
}
from
"
../../api/taskApi
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
t
}
from
"
../../i18n
"
;
import
{
renderMath
}
from
"
../../utils/mathRender
"
;
import
TaskContent
from
"
./TaskContent
"
;
import
TaskContent
from
"
./TaskContent
"
;
type
SelectOption
=
{
type
SelectOption
=
{
...
@@ -51,11 +52,7 @@ export default function TaskPanel({
...
@@ -51,11 +52,7 @@ export default function TaskPanel({
if
(
!
selectedTaskText
||
!
taskDisplayRef
.
current
)
{
if
(
!
selectedTaskText
||
!
taskDisplayRef
.
current
)
{
return
;
return
;
}
}
const
mathjax
=
window
.
MathJax
;
renderMath
(
taskDisplayRef
.
current
);
if
(
!
mathjax
?.
typesetPromise
)
{
return
;
}
mathjax
.
typesetPromise
([
taskDisplayRef
.
current
]).
catch
(()
=>
undefined
);
},
[
selectedTaskText
]);
},
[
selectedTaskText
]);
return
(
return
(
...
...
math-tutor/frontend/src/pages/ChatPage.tsx
View file @
a94d3cb3
...
@@ -1257,19 +1257,17 @@ export default function ChatPage() {
...
@@ -1257,19 +1257,17 @@ export default function ChatPage() {
document.getElementById("meta").textContent = path;
document.getElementById("meta").textContent = path;
document.getElementById("content").innerHTML = marked.parse(markdown);
document.getElementById("content").innerHTML = marked.parse(markdown);
</script>
</script>
<script>
<script type="module">
window.MathJax = {
import { renderMathInElement } from "https://esm.run/mathlive";
tex: {
renderMathInElement(document.getElementById("content"), {
inlineMath: [["$", "$"], ["\\\\(", "\\\\)"]],
TeX: {
displayMath: [["$$", "$$"], ["\\\\[", "\\\\]"]],
delimiters: {
}
inline: [["$", "$"], ["\\\\(", "\\\\)"]],
};
display: [["$$", "$$"], ["\\\\[", "\\\\]"]],
},
},
});
</script>
</script>
<script
id="mathjax-script"
async
src="https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-chtml.js"
></script>
</body>
</body>
</html>`
;
</html>`
;
...
...
math-tutor/frontend/src/pages/SelectionPage.tsx
View file @
a94d3cb3
...
@@ -9,6 +9,7 @@ import {
...
@@ -9,6 +9,7 @@ import {
getSelectionRouteForOrchestrator
,
getSelectionRouteForOrchestrator
,
type
OrchestratorName
,
type
OrchestratorName
,
}
from
"
../utils/orchestrator
"
;
}
from
"
../utils/orchestrator
"
;
import
{
renderMath
}
from
"
../utils/mathRender
"
;
import
"
../styles/theme.css
"
;
import
"
../styles/theme.css
"
;
import
sumintLogo
from
"
../../SuMINT-Logo.png
"
;
import
sumintLogo
from
"
../../SuMINT-Logo.png
"
;
...
@@ -91,11 +92,7 @@ export default function SelectionPage({ mode }: SelectionPageProps) {
...
@@ -91,11 +92,7 @@ export default function SelectionPage({ mode }: SelectionPageProps) {
if
(
!
isTaskMode
||
!
selectedTask
?.
fullText
||
!
taskDisplayRef
.
current
)
{
if
(
!
isTaskMode
||
!
selectedTask
?.
fullText
||
!
taskDisplayRef
.
current
)
{
return
;
return
;
}
}
const
mathjax
=
window
.
MathJax
;
renderMath
(
taskDisplayRef
.
current
);
if
(
!
mathjax
?.
typesetPromise
)
{
return
;
}
mathjax
.
typesetPromise
([
taskDisplayRef
.
current
]).
catch
(()
=>
undefined
);
},
[
isTaskMode
,
selectedTask
?.
fullText
]);
},
[
isTaskMode
,
selectedTask
?.
fullText
]);
const
handleStart
=
async
()
=>
{
const
handleStart
=
async
()
=>
{
...
...
math-tutor/frontend/src/styles/theme.css
View file @
a94d3cb3
...
@@ -585,6 +585,30 @@ body {
...
@@ -585,6 +585,30 @@ body {
cursor
:
not-allowed
;
cursor
:
not-allowed
;
}
}
.composer-math
{
display
:
flex
;
flex-direction
:
column
;
gap
:
8px
;
padding
:
10px
;
border
:
1px
solid
#d8d1c4
;
border-radius
:
12px
;
background
:
#fffaf0
;
}
.composer-mathfield
{
width
:
100%
;
min-height
:
54px
;
border
:
1px
solid
#d8d1c4
;
border-radius
:
10px
;
background
:
#fff
;
padding
:
10px
12px
;
--hue
:
32
;
}
.composer-mathfield
::part
(
virtual-keyboard-toggle
)
{
display
:
none
;
}
.btn
{
.btn
{
border
:
1px
solid
#b9b2a4
;
border
:
1px
solid
#b9b2a4
;
background
:
#ffffff
;
background
:
#ffffff
;
...
...
math-tutor/frontend/src/types/math.d.ts
0 → 100644
View file @
a94d3cb3
import
type
{
DetailedHTMLProps
,
HTMLAttributes
}
from
"
react
"
;
import
type
{
MathfieldElement
}
from
"
mathlive
"
;
export
{};
declare
module
"
react
"
{
namespace
JSX
{
interface
IntrinsicElements
{
"
math-field
"
:
DetailedHTMLProps
<
HTMLAttributes
<
MathfieldElement
>
,
MathfieldElement
>
;
}
}
}
math-tutor/frontend/src/utils/mathMarkdown.ts
deleted
100644 → 0
View file @
4e0eb686
const
MATH_SEGMENT_PATTERN
=
/
(\$\$[\s\S]
*
?\$\$
|
\\\[[\s\S]
*
?\\\]
|
\\\([\s\S]
*
?\\\)
|
\$(?:\\
.|
[^
$
\\\n])
+
\$)
/g
;
// wandlet * in \* um sodass * in der MArkdown darstellung nich zum zeichen
// für kursiv oder fett gedruckt wird.
const
escapeAsterisks
=
(
value
:
string
)
=>
value
.
replace
(
/
\*
/g
,
"
\\
*
"
);
// Wandelt einfache Bruchschreibweisen in LaTeX-Brüche um.
// Beispiele:
// - (x+1)/(x-1) -> \frac{x+1}{x-1}
// - a/b -> \frac{a}{b}
//
// Diese Ersetzung läuft nur innerhalb erkannter Math-Segmente, damit
// normale Markdown-Links, URLs oder Fließtext unangetastet bleiben.
const
convertSimpleFractions
=
(
value
:
string
)
=>
{
let
output
=
value
;
// Vorrang für explizit geklammerte Ausdrücke:
// (x+1)/(x-1) => \frac{x+1}{x-1}
output
=
output
.
replace
(
/
\(([^
()
]
+
)\)\s
*
\/\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
numerator
:
string
,
denominator
:
string
)
=>
`\\frac{
${
numerator
}
}{
${
denominator
}
}`
);
// Einseitig geklammerte Ausdrücke:
// (a+b)/c => \frac{a+b}{c}
// a/(b+c) => \frac{a}{b+c}
// Diese Regel kommt vor der ganz einfachen a/b-Regel, damit die Klammern
// erhalten bleiben und nicht versehentlich nur Teilstücke ersetzt werden.
output
=
output
.
replace
(
/
\(([^
()
]
+
)\)\s
*
\/\s
*
([
A-Za-z0-9
]
+
)(?![\w
{
])
/g
,
(
_match
,
numerator
:
string
,
denominator
:
string
)
=>
`\\frac{
${
numerator
}
}{
${
denominator
}
}`
);
output
=
output
.
replace
(
/
([
A-Za-z0-9
]
+
)\s
*
\/\s
*
\(([^
()
]
+
)\)(?![\w
{
])
/g
,
(
_match
,
numerator
:
string
,
denominator
:
string
)
=>
`\\frac{
${
numerator
}
}{
${
denominator
}
}`
);
// Danach sehr einfache Kurzschreibweise:
// a/b => \frac{a}{b}
// Hinweis: Das ist bewusst konservativ gehalten, damit keine komplexen
// Ausdrücke oder schon vorhandene LaTeX-Befehle umgeschrieben werden.
output
=
output
.
replace
(
/
(
^|
[^\\\w
}
])([
A-Za-z0-9
]
+
)\s
*
\/\s
*
([
A-Za-z0-9
]
+
)(?![\w
{
])
/g
,
(
_match
,
prefix
:
string
,
numerator
:
string
,
denominator
:
string
)
=>
`
${
prefix
}
\\frac{
${
numerator
}
}{
${
denominator
}
}`
);
return
output
;
};
// Formatiert Potenzen so, dass zusammengesetzte Exponenten in Klammern
// vollständig in LaTeX-Klammern gesetzt werden.
// Beispiel:
// - x^(a+b) -> x^{a+b}
const
normalizePowers
=
(
value
:
string
)
=>
value
.
replace
(
/
([
A-Za-z0-9
\\]
+
)\s
*
\^\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
base
:
string
,
exponent
:
string
)
=>
`
${
base
}
^{
${
exponent
}
}`
);
// Wandelt sqrt(...) in LaTeX-Wurzelnotation um.
// Beispiel:
// - sqrt(x) -> \sqrt{x}
const
normalizeSquareRoots
=
(
value
:
string
)
=>
value
.
replace
(
/
\b
sqrt
\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
inner
:
string
)
=>
`\\sqrt{
${
inner
}
}`
);
// Wandelt exp(x) in e^x um.
// Beispiel:
// - exp(x) -> e^x
const
normalizeExp
=
(
value
:
string
)
=>
value
.
replace
(
/
\b
exp
\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
inner
:
string
)
=>
`e^
${
inner
}
`
);
// Wandelt log(x) in eine lesbare Logarithmus-Notation um.
// Beispiel:
// - log(x) -> \log x
const
normalizeLog
=
(
value
:
string
)
=>
value
.
replace
(
/
\b
log
\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
inner
:
string
)
=>
`\\log
${
inner
}
`
);
// Wandelt pi in das griechische LaTeX-Symbol um.
// Beispiel:
// - pi -> \pi
const
normalizePi
=
(
value
:
string
)
=>
value
.
replace
(
/
\b
pi
\b
/g
,
"
\\
pi
"
);
// Wandelt sin/cos/tan-Aufrufe in LaTeX-Makros um.
// Beispiele:
// - sin(a) -> \sin(a)
// - cos(a) -> \cos(a)
// - tan(a) -> \tan(a)
const
normalizeTrigFunctions
=
(
value
:
string
)
=>
value
.
replace
(
/
\b(
sin|cos|tan
)\s
*
\(([^
()
]
+
)\)
/g
,
(
_match
,
fn
:
string
,
inner
:
string
)
=>
`\
\$
{fn}(
${
inner
}
)`
);
// Wandelt inf/minf in Infinities um.
// Beispiele:
// - inf -> \infty
// - minf -> - \infty
const
normalizeInfinityTokens
=
(
value
:
string
)
=>
value
.
replace
(
/
\b
minf
\b
/g
,
"
-
\\
infty
"
)
.
replace
(
/
\b
inf
\b
/g
,
"
\\
infty
"
);
// Macht bereits geschriebene Mengenklammern in Math-Segmenten sichtbar.
// Beispiele:
// - \{1, 2, 3\} -> \\{1, 2, 3\\}
// - \{x \in \mathbb{R} \mid x > 0\} -> \\{x \in \mathbb{R} \mid x > 0\\}
//
// Wichtig:
// - Rohe {a,b,c} werden absichtlich NICHT umgeschrieben, damit z. B. x_{a,b}
// nicht fälschlich als Menge interpretiert wird.
// - Nur bereits escapte Klammern werden angehoben, damit MathJax sie sichtbar
// rendert statt sie als Gruppierungszeichen zu schlucken.
const
normalizeSetBraces
=
(
value
:
string
)
=>
value
.
replace
(
/
\\\{
/g
,
"
\\\\
{
"
)
.
replace
(
/
\\\}
/g
,
"
\\\\
}
"
);
export
const
escapeAsterisksInsideMath
=
(
input
:
string
):
string
=>
{
return
input
.
replace
(
MATH_SEGMENT_PATTERN
,
(
segment
)
=>
{
if
(
segment
.
startsWith
(
"
$$
"
)
&&
segment
.
endsWith
(
"
$$
"
))
{
const
inner
=
segment
.
slice
(
2
,
-
2
);
return
`$$
${
normalizeSetBraces
(
normalizeInfinityTokens
(
normalizeTrigFunctions
(
normalizePi
(
normalizeLog
(
normalizeExp
(
normalizeSquareRoots
(
normalizePowers
(
convertSimpleFractions
(
escapeAsterisks
(
inner
))))))))))}
$$`
;
}
if
(
segment
.
startsWith
(
"
\\
[
"
)
&&
segment
.
endsWith
(
"
\\
]
"
))
{
const
inner
=
segment
.
slice
(
2
,
-
2
);
return
`\\[
${
normalizeSetBraces
(
normalizeInfinityTokens
(
normalizeTrigFunctions
(
normalizePi
(
normalizeLog
(
normalizeExp
(
normalizeSquareRoots
(
normalizePowers
(
convertSimpleFractions
(
escapeAsterisks
(
inner
))))))))))}
\\]`
;
}
if
(
segment
.
startsWith
(
"
\\
(
"
)
&&
segment
.
endsWith
(
"
\\
)
"
))
{
const
inner
=
segment
.
slice
(
2
,
-
2
);
return
`\\(
${
normalizeSetBraces
(
normalizeInfinityTokens
(
normalizeTrigFunctions
(
normalizePi
(
normalizeLog
(
normalizeExp
(
normalizeSquareRoots
(
normalizePowers
(
convertSimpleFractions
(
escapeAsterisks
(
inner
))))))))))}
\\)`
;
}
if
(
segment
.
startsWith
(
"
$
"
)
&&
segment
.
endsWith
(
"
$
"
))
{
const
inner
=
segment
.
slice
(
1
,
-
1
);
return
`$
${
normalizeSetBraces
(
normalizeInfinityTokens
(
normalizeTrigFunctions
(
normalizePi
(
normalizeLog
(
normalizeExp
(
normalizeSquareRoots
(
normalizePowers
(
convertSimpleFractions
(
escapeAsterisks
(
inner
))))))))))}
$`
;
}
return
segment
;
});
};
math-tutor/frontend/src/utils/mathRender.ts
0 → 100644
View file @
a94d3cb3
import
{
renderMathInElement
,
type
StaticRenderOptions
}
from
"
mathlive
"
;
const
mathRenderOptions
:
StaticRenderOptions
=
{
TeX
:
{
delimiters
:
{
inline
:
[
[
"
$
"
,
"
$
"
],
[
"
\\
(
"
,
"
\\
)
"
],
],
display
:
[
[
"
$$
"
,
"
$$
"
],
[
"
\\
[
"
,
"
\\
]
"
],
],
},
},
};
export
const
renderMath
=
(
element
:
HTMLElement
|
null
)
=>
{
if
(
!
element
)
{
return
;
}
renderMathInElement
(
element
,
mathRenderOptions
);
};
math-tutor/frontend/src/utils/mathText.ts
0 → 100644
View file @
a94d3cb3
const
MATH_SEGMENT_PATTERN
=
/
(\$\$[\s\S]
*
?\$\$
|
\\\[[\s\S]
*
?\\\]
|
\\\([\s\S]
*
?\\\)
|
\$(?:\\
.|
[^
$
\\\n])
+
\$)
/g
;
const
escapeAsterisks
=
(
value
:
string
)
=>
value
.
replace
(
/
\*
/g
,
"
\\
*
"
);
export
const
escapeMarkdownInMath
=
(
input
:
string
):
string
=>
input
.
replace
(
MATH_SEGMENT_PATTERN
,
(
segment
)
=>
{
if
(
segment
.
startsWith
(
"
$$
"
)
&&
segment
.
endsWith
(
"
$$
"
))
{
return
`$$
${
escapeAsterisks
(
segment
.
slice
(
2
,
-
2
))}
$$`
;
}
if
(
segment
.
startsWith
(
"
\\
[
"
)
&&
segment
.
endsWith
(
"
\\
]
"
))
{
return
`\\[
${
escapeAsterisks
(
segment
.
slice
(
2
,
-
2
))}
\\]`
;
}
if
(
segment
.
startsWith
(
"
\\
(
"
)
&&
segment
.
endsWith
(
"
\\
)
"
))
{
return
`\\(
${
escapeAsterisks
(
segment
.
slice
(
2
,
-
2
))}
\\)`
;
}
if
(
segment
.
startsWith
(
"
$
"
)
&&
segment
.
endsWith
(
"
$
"
))
{
return
`$
${
escapeAsterisks
(
segment
.
slice
(
1
,
-
1
))}
$`
;
}
return
segment
;
});
Kantz
@jonathan.kantz
mentioned in commit
590d71a3
·
Jul 07, 2026
mentioned in commit
590d71a3
mentioned in commit 590d71a36f2823b0703bf58290b82bb0c9c35345
Toggle commit list
Write
Preview
Supports
Markdown
0%
Try again
or
attach a new file
.
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment