feat: unkown

This commit is contained in:
2025-12-26 15:53:11 +08:00
parent 1226bbe724
commit cffd536fb8
7 changed files with 244 additions and 187 deletions

300
package-lock.json generated
View File

@@ -14,10 +14,12 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"katex": "^0.16.27", "katex": "^0.16.27",
"lucide-react": "^0.344.0", "lucide-react": "^0.344.0",
"mathml2omml": "^0.5.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"tailwind-merge": "^3.4.0" "tailwind-merge": "^3.4.0"
@@ -66,13 +68,15 @@
} }
}, },
"node_modules/@babel/code-frame": { "node_modules/@babel/code-frame": {
"version": "7.25.7", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.27.1.tgz",
"integrity": "sha512-0xZJFNE5XMpENsgfHYTw8FbX4kv53mFLn2i3XPoq69LyhYSCBJtitaHx9QnsVTrsogI4Z3+HtEfZ2/GFPOtf5g==", "integrity": "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/highlight": "^7.25.7", "@babel/helper-validator-identifier": "^7.27.1",
"picocolors": "^1.0.0" "js-tokens": "^4.0.0",
"picocolors": "^1.1.1"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -203,19 +207,21 @@
} }
}, },
"node_modules/@babel/helper-string-parser": { "node_modules/@babel/helper-string-parser": {
"version": "7.25.7", "version": "7.27.1",
"resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.27.1.tgz",
"integrity": "sha512-CbkjYdsJNHFk8uqpEkpCvRs3YRp9tY6FmFY7wLMSYuGYkrdUi7r2lc4/wqsvlHoMznX3WJ9IP8giGPq68T/Y6g==", "integrity": "sha512-qMlSxKbpRlAridDExk92nSobyDdpPijUq2DW6oDnUqd0iOGxmQjyqhMIihI9+zv4LPyZdRje2cavWPbCbWm3eA==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/helper-validator-identifier": { "node_modules/@babel/helper-validator-identifier": {
"version": "7.25.7", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.28.5.tgz",
"integrity": "sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==", "integrity": "sha512-qSs4ifwzKJSV39ucNjsvc6WVHs6b7S03sOh2OcHF9UHfVPqWWALUsNUVzhSBiItjRZoLHx7nIarVjqKVusUZ1Q==",
"dev": true, "dev": true,
"license": "MIT",
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
@@ -230,40 +236,27 @@
} }
}, },
"node_modules/@babel/helpers": { "node_modules/@babel/helpers": {
"version": "7.25.7", "version": "7.28.4",
"resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.28.4.tgz",
"integrity": "sha512-Sv6pASx7Esm38KQpF/U/OXLwPPrdGHNKoeblRxgZRLXnAtnkEe4ptJPDtAZM7fBLadbc1Q07kQpSiGQ0Jg6tRA==", "integrity": "sha512-HFN59MmQXGHVyYadKLVumYsA9dBFun/ldYxipEjzA4196jpLZd8UjEEBLkbEkvfYreDqJhZxYAWFPtrfhNpj4w==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/template": "^7.25.7", "@babel/template": "^7.27.2",
"@babel/types": "^7.25.7" "@babel/types": "^7.28.4"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/@babel/highlight": {
"version": "7.25.7",
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.25.7.tgz",
"integrity": "sha512-iYyACpW3iW8Fw+ZybQK+drQre+ns/tKpXbNESfrhNnPLIklLbXr7MYJ6gPEd0iETGLOK+SxMjVvKb/ffmk+FEw==",
"dev": true,
"dependencies": {
"@babel/helper-validator-identifier": "^7.25.7",
"chalk": "^2.4.2",
"js-tokens": "^4.0.0",
"picocolors": "^1.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
} }
}, },
"node_modules/@babel/parser": { "node_modules/@babel/parser": {
"version": "7.25.7", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.28.5.tgz",
"integrity": "sha512-aZn7ETtQsjjGG5HruveUK06cU3Hljuhd9Iojm4M8WWv3wLE6OkE5PWbDUkItmMgegmccaITudyuW5RPYrYlgWw==", "integrity": "sha512-KKBU1VGYR7ORr3At5HAtUQ+TV3SzRCXmA/8OdDZiLDBIZxVyzXuztPjfLd3BV1PRAQGCMWWSHYhL0F8d5uHBDQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/types": "^7.25.7" "@babel/types": "^7.28.5"
}, },
"bin": { "bin": {
"parser": "bin/babel-parser.js" "parser": "bin/babel-parser.js"
@@ -303,14 +296,15 @@
} }
}, },
"node_modules/@babel/template": { "node_modules/@babel/template": {
"version": "7.25.7", "version": "7.27.2",
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.2.tgz",
"integrity": "sha512-wRwtAgI3bAS+JGU2upWNL9lSlDcRCqD05BZ1n3X2ONLH1WilFP6O1otQjeMK/1g0pvYcXC7b/qVUB1keofjtZA==", "integrity": "sha512-LPDZ85aEJyYSd18/DkjNh4/y1ntkE5KwUHWTiqgRxruuZL2F1yuHligVHLvcHY2vMHXttKFpJn6LwfI7cw7ODw==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/code-frame": "^7.25.7", "@babel/code-frame": "^7.27.1",
"@babel/parser": "^7.25.7", "@babel/parser": "^7.27.2",
"@babel/types": "^7.25.7" "@babel/types": "^7.27.1"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -344,14 +338,14 @@
} }
}, },
"node_modules/@babel/types": { "node_modules/@babel/types": {
"version": "7.25.7", "version": "7.28.5",
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.25.7.tgz", "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.28.5.tgz",
"integrity": "sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==", "integrity": "sha512-qQ5m48eI/MFLQ5PxQj4PFaprjyCTLI37ElWMmNs0K8Lk3dVeOdNpB3ks8jc7yM5CDmVC73eMVk/trk3fgmrUpA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"@babel/helper-string-parser": "^7.25.7", "@babel/helper-string-parser": "^7.27.1",
"@babel/helper-validator-identifier": "^7.25.7", "@babel/helper-validator-identifier": "^7.28.5"
"to-fast-properties": "^2.0.0"
}, },
"engines": { "engines": {
"node": ">=6.9.0" "node": ">=6.9.0"
@@ -838,17 +832,32 @@
} }
}, },
"node_modules/@eslint/plugin-kit": { "node_modules/@eslint/plugin-kit": {
"version": "0.2.0", "version": "0.2.8",
"resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.0.tgz", "resolved": "https://registry.npmjs.org/@eslint/plugin-kit/-/plugin-kit-0.2.8.tgz",
"integrity": "sha512-vH9PiIMMwvhCx31Af3HiGzsVNULDbyVkHXwlemn/B0TFj/00ho3y55efXrUZTfQipxoHC5u4xq6zblww1zm1Ig==", "integrity": "sha512-ZAoA40rNMPwSm+AeHpCq8STiNAwzWLJuP8Xv4CHIc9wv/PSuExjMrmjfYNj682vW0OOiZ1HKxzvjQr9XZIisQA==",
"dev": true, "dev": true,
"license": "Apache-2.0",
"dependencies": { "dependencies": {
"@eslint/core": "^0.13.0",
"levn": "^0.4.1" "levn": "^0.4.1"
}, },
"engines": { "engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0" "node": "^18.18.0 || ^20.9.0 || >=21.1.0"
} }
}, },
"node_modules/@eslint/plugin-kit/node_modules/@eslint/core": {
"version": "0.13.0",
"resolved": "https://registry.npmjs.org/@eslint/core/-/core-0.13.0.tgz",
"integrity": "sha512-yfkgDw1KR66rkT5A8ci4irzDysN7FRpq3ttJolR88OqQikAWqwA8j5VZyas+vjyBNFIJ7MfybJ9plMILI2UrCw==",
"dev": true,
"license": "Apache-2.0",
"dependencies": {
"@types/json-schema": "^7.0.15"
},
"engines": {
"node": "^18.18.0 || ^20.9.0 || >=21.1.0"
}
},
"node_modules/@humanfs/core": { "node_modules/@humanfs/core": {
"version": "0.19.0", "version": "0.19.0",
"resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz", "resolved": "https://registry.npmjs.org/@humanfs/core/-/core-0.19.0.tgz",
@@ -1611,10 +1620,11 @@
} }
}, },
"node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@@ -1772,18 +1782,6 @@
"url": "https://github.com/chalk/ansi-regex?sponsor=1" "url": "https://github.com/chalk/ansi-regex?sponsor=1"
} }
}, },
"node_modules/ansi-styles": {
"version": "3.2.1",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
"integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
"dev": true,
"dependencies": {
"color-convert": "^1.9.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/any-promise": { "node_modules/any-promise": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz",
@@ -1881,10 +1879,11 @@
} }
}, },
"node_modules/brace-expansion": { "node_modules/brace-expansion": {
"version": "1.1.11", "version": "1.1.12",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0", "balanced-match": "^1.0.0",
"concat-map": "0.0.1" "concat-map": "0.0.1"
@@ -1992,20 +1991,6 @@
"url": "https://github.com/sponsors/wooorm" "url": "https://github.com/sponsors/wooorm"
} }
}, },
"node_modules/chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
"integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
"dev": true,
"dependencies": {
"ansi-styles": "^3.2.1",
"escape-string-regexp": "^1.0.5",
"supports-color": "^5.3.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/character-entities": { "node_modules/character-entities": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz", "resolved": "https://registry.npmjs.org/character-entities/-/character-entities-2.0.2.tgz",
@@ -2091,21 +2076,6 @@
"node": ">=6" "node": ">=6"
} }
}, },
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
"integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
"dev": true,
"dependencies": {
"color-name": "1.1.3"
}
},
"node_modules/color-name": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
"integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
"dev": true
},
"node_modules/comma-separated-tokens": { "node_modules/comma-separated-tokens": {
"version": "2.0.3", "version": "2.0.3",
"resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz", "resolved": "https://registry.npmjs.org/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz",
@@ -2138,10 +2108,11 @@
"dev": true "dev": true
}, },
"node_modules/cross-spawn": { "node_modules/cross-spawn": {
"version": "7.0.3", "version": "7.0.6",
"resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz",
"integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"path-key": "^3.1.0", "path-key": "^3.1.0",
"shebang-command": "^2.0.0", "shebang-command": "^2.0.0",
@@ -2315,12 +2286,15 @@
} }
}, },
"node_modules/escape-string-regexp": { "node_modules/escape-string-regexp": {
"version": "1.0.5", "version": "5.0.0",
"resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-5.0.0.tgz",
"integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "integrity": "sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==",
"dev": true, "license": "MIT",
"engines": { "engines": {
"node": ">=0.8.0" "node": ">=12"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
} }
}, },
"node_modules/eslint": { "node_modules/eslint": {
@@ -2766,10 +2740,11 @@
} }
}, },
"node_modules/glob": { "node_modules/glob": {
"version": "10.4.5", "version": "10.5.0",
"resolved": "https://registry.npmjs.org/glob/-/glob-10.4.5.tgz", "resolved": "https://registry.npmjs.org/glob/-/glob-10.5.0.tgz",
"integrity": "sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg==", "integrity": "sha512-DfXN8DfhJ7NH3Oe7cFmu3NCu1wKbkReJ8TorzSAFbSKrlNaQSKfIzqYqVY8zlbs2NLBbWpRiU52GX2PbaBVNkg==",
"dev": true, "dev": true,
"license": "ISC",
"dependencies": { "dependencies": {
"foreground-child": "^3.1.0", "foreground-child": "^3.1.0",
"jackspeak": "^3.1.2", "jackspeak": "^3.1.2",
@@ -2798,10 +2773,11 @@
} }
}, },
"node_modules/glob/node_modules/brace-expansion": { "node_modules/glob/node_modules/brace-expansion": {
"version": "2.0.1", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.2.tgz",
"integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", "integrity": "sha512-Jt0vHyM+jmUBqojB7E1NIYadt0vI0Qxjxd2TErW94wDz+E2LAm5vKMXXwg6ZZBTHPuUlDgQHKXvjGBdfcF1ZDQ==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"balanced-match": "^1.0.0" "balanced-match": "^1.0.0"
} }
@@ -2839,15 +2815,6 @@
"integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
"dev": true "dev": true
}, },
"node_modules/has-flag": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
"integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/hasown": { "node_modules/hasown": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz",
@@ -3236,10 +3203,11 @@
"integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==" "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
}, },
"node_modules/js-yaml": { "node_modules/js-yaml": {
"version": "4.1.0", "version": "4.1.1",
"resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.1.tgz",
"integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", "integrity": "sha512-qQKT4zQxXl8lLwBtHMWwaTcGfFOZviOJet3Oy/xmGk2gZH677CJM9EvtfdSkgWcATZhj/55JZ0rmy3myCT5lsA==",
"dev": true, "dev": true,
"license": "MIT",
"dependencies": { "dependencies": {
"argparse": "^2.0.1" "argparse": "^2.0.1"
}, },
@@ -3413,6 +3381,28 @@
"react": "^16.5.1 || ^17.0.0 || ^18.0.0" "react": "^16.5.1 || ^17.0.0 || ^18.0.0"
} }
}, },
"node_modules/mathml2omml": {
"version": "0.5.0",
"resolved": "https://registry.npmjs.org/mathml2omml/-/mathml2omml-0.5.0.tgz",
"integrity": "sha512-4eLs37a+TH+CL/M5XZZrlc75SNRJNPiZzIaeSSvH0UFCRaCdSYRHWvrlA7MUbeQks/z4sVhfg+GlcZhVVUHzqg==",
"license": "LGPL-3.0-or-later"
},
"node_modules/mdast-util-find-and-replace": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-find-and-replace/-/mdast-util-find-and-replace-3.0.2.tgz",
"integrity": "sha512-Tmd1Vg/m3Xz43afeNxDIhWRtFZgM2VLyaf4vSTYwudTyeuTneoL3qtWMA5jeLyz/O1vDJmmV4QuScFCA2tBPwg==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"escape-string-regexp": "^5.0.0",
"unist-util-is": "^6.0.0",
"unist-util-visit-parents": "^6.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-from-markdown": { "node_modules/mdast-util-from-markdown": {
"version": "2.0.2", "version": "2.0.2",
"resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz", "resolved": "https://registry.npmjs.org/mdast-util-from-markdown/-/mdast-util-from-markdown-2.0.2.tgz",
@@ -3516,6 +3506,20 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/mdast-util-newline-to-break": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/mdast-util-newline-to-break/-/mdast-util-newline-to-break-2.0.0.tgz",
"integrity": "sha512-MbgeFca0hLYIEx/2zGsszCSEJJ1JSCdiY5xQxRcLDDGa8EPvlLPupJ4DSajbMPAnC0je8jfb9TiUATnxxrHUog==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-find-and-replace": "^3.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/mdast-util-phrasing": { "node_modules/mdast-util-phrasing": {
"version": "4.1.0", "version": "4.1.0",
"resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz", "resolved": "https://registry.npmjs.org/mdast-util-phrasing/-/mdast-util-phrasing-4.1.0.tgz",
@@ -4106,9 +4110,9 @@
} }
}, },
"node_modules/nanoid": { "node_modules/nanoid": {
"version": "3.3.7", "version": "3.3.11",
"resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz",
"integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==", "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==",
"dev": true, "dev": true,
"funding": [ "funding": [
{ {
@@ -4116,6 +4120,7 @@
"url": "https://github.com/sponsors/ai" "url": "https://github.com/sponsors/ai"
} }
], ],
"license": "MIT",
"bin": { "bin": {
"nanoid": "bin/nanoid.cjs" "nanoid": "bin/nanoid.cjs"
}, },
@@ -4647,6 +4652,21 @@
"url": "https://opencollective.com/unified" "url": "https://opencollective.com/unified"
} }
}, },
"node_modules/remark-breaks": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/remark-breaks/-/remark-breaks-4.0.0.tgz",
"integrity": "sha512-IjEjJOkH4FuJvHZVIW0QCDWxcG96kCq7An/KVH2NfJe6rKZU2AsHeB3OEjPNRxi4QC34Xdx7I2KGYn6IpT7gxQ==",
"license": "MIT",
"dependencies": {
"@types/mdast": "^4.0.0",
"mdast-util-newline-to-break": "^2.0.0",
"unified": "^11.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/unified"
}
},
"node_modules/remark-math": { "node_modules/remark-math": {
"version": "6.0.0", "version": "6.0.0",
"resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz", "resolved": "https://registry.npmjs.org/remark-math/-/remark-math-6.0.0.tgz",
@@ -5027,18 +5047,6 @@
"node": ">=16 || 14 >=14.17" "node": ">=16 || 14 >=14.17"
} }
}, },
"node_modules/supports-color": {
"version": "5.5.0",
"resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
"integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
"dev": true,
"dependencies": {
"has-flag": "^3.0.0"
},
"engines": {
"node": ">=4"
}
},
"node_modules/supports-preserve-symlinks-flag": { "node_modules/supports-preserve-symlinks-flag": {
"version": "1.0.0", "version": "1.0.0",
"resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
@@ -5126,15 +5134,6 @@
"node": ">=0.8" "node": ">=0.8"
} }
}, },
"node_modules/to-fast-properties": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
"integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
"dev": true,
"engines": {
"node": ">=4"
}
},
"node_modules/to-regex-range": { "node_modules/to-regex-range": {
"version": "5.0.1", "version": "5.0.1",
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
@@ -5453,10 +5452,11 @@
} }
}, },
"node_modules/vite": { "node_modules/vite": {
"version": "5.4.8", "version": "5.4.21",
"resolved": "https://registry.npmjs.org/vite/-/vite-5.4.8.tgz", "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
"integrity": "sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==", "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
"dev": true, "dev": true,
"license": "MIT",
"peer": true, "peer": true,
"dependencies": { "dependencies": {
"esbuild": "^0.21.3", "esbuild": "^0.21.3",

View File

@@ -19,10 +19,12 @@
"clsx": "^2.1.1", "clsx": "^2.1.1",
"katex": "^0.16.27", "katex": "^0.16.27",
"lucide-react": "^0.344.0", "lucide-react": "^0.344.0",
"mathml2omml": "^0.5.0",
"react": "^18.3.1", "react": "^18.3.1",
"react-dom": "^18.3.1", "react-dom": "^18.3.1",
"react-markdown": "^10.1.0", "react-markdown": "^10.1.0",
"rehype-katex": "^7.0.1", "rehype-katex": "^7.0.1",
"remark-breaks": "^4.0.0",
"remark-math": "^6.0.0", "remark-math": "^6.0.0",
"spark-md5": "^3.0.2", "spark-md5": "^3.0.2",
"tailwind-merge": "^3.4.0" "tailwind-merge": "^3.4.0"

View File

@@ -292,7 +292,7 @@ function App() {
if (pollingIntervals.current[taskNo]) return; if (pollingIntervals.current[taskNo]) return;
let attempts = 0; let attempts = 0;
const maxAttempts = 15; const maxAttempts = 30;
pollingIntervals.current[taskNo] = setInterval(async () => { pollingIntervals.current[taskNo] = setInterval(async () => {
attempts++; attempts++;
@@ -369,7 +369,7 @@ function App() {
alert('Task timeout or network error.'); alert('Task timeout or network error.');
} }
} }
}, 1500); // Poll every 2 seconds }, 2000); // Poll every 2 seconds
}; };
const handleUpload = async (uploadFiles: File[]) => { const handleUpload = async (uploadFiles: File[]) => {

View File

@@ -1,6 +1,7 @@
import { useState } from 'react'; import { useState } from 'react';
import { X, Check, Copy, Download, Code2, Image as ImageIcon, FileText, ChevronRight } from 'lucide-react'; import { X, Check, Copy, Download, Code2, Image as ImageIcon, FileText, ChevronRight } from 'lucide-react';
import { RecognitionResult } from '../types'; import { RecognitionResult } from '../types';
import { convertMathmlToOmml, wrapOmmlForClipboard } from '../lib/ommlConverter';
interface ExportSidebarProps { interface ExportSidebarProps {
isOpen: boolean; isOpen: boolean;
@@ -84,7 +85,20 @@ export default function ExportSidebar({ isOpen, onClose, result }: ExportSidebar
]; ];
const handleAction = async (option: ExportOption) => { const handleAction = async (option: ExportOption) => {
const content = option.getContent(result); let content = option.getContent(result);
// Fallback: If Word MathML is missing, try to convert from MathML
if (option.id === 'mathml_word' && !content && result.mathml_content) {
try {
const omml = await convertMathmlToOmml(result.mathml_content);
if (omml) {
content = wrapOmmlForClipboard(omml);
}
} catch (err) {
console.error('Failed to convert MathML to OMML:', err);
}
}
if (!content) return; if (!content) return;
try { try {

View File

@@ -146,3 +146,5 @@ export default function Navbar() {

View File

@@ -2,6 +2,7 @@ import { useState } from 'react';
import { Download, Code2, Check, Copy } from 'lucide-react'; import { Download, Code2, Check, Copy } from 'lucide-react';
import ReactMarkdown from 'react-markdown'; import ReactMarkdown from 'react-markdown';
import remarkMath from 'remark-math'; import remarkMath from 'remark-math';
import remarkBreaks from 'remark-breaks';
import rehypeKatex from 'rehype-katex'; import rehypeKatex from 'rehype-katex';
import 'katex/dist/katex.min.css'; import 'katex/dist/katex.min.css';
import { RecognitionResult } from '../types'; import { RecognitionResult } from '../types';
@@ -21,6 +22,9 @@ function preprocessLatex(content: string): string {
let processed = content; let processed = content;
// Convert literal \n (escaped newlines from OCR) to actual newlines
// This handles cases where the API returns "\\n" as a string instead of actual newline characters
// Fix mixed display math delimiters: $$\[...\]$$ -> $$...$$ // Fix mixed display math delimiters: $$\[...\]$$ -> $$...$$
// This handles cases where \[ \] are incorrectly nested inside $$ $$ // This handles cases where \[ \] are incorrectly nested inside $$ $$
// Note: In JS replace(), $$ means "insert one $", so we need $$$$ to insert $$ // Note: In JS replace(), $$ means "insert one $", so we need $$$$ to insert $$
@@ -38,46 +42,31 @@ function preprocessLatex(content: string): string {
// Ensure content followed by $$ has a newline: content$$ -> content\n$$ // Ensure content followed by $$ has a newline: content$$ -> content\n$$
processed = processed.replace(/([^\n$])\$\$/g, '$1\n$$$$'); processed = processed.replace(/([^\n$])\$\$/g, '$1\n$$$$');
// Fix: \left \{ -> \left\{ (remove space between \left and delimiter) // // Fix: \left \{ -> \left\{ (remove space between \left and delimiter)
processed = processed.replace(/\\left\s+\\/g, '\\left\\'); processed = processed.replace(/\\left\s+\\/g, '\\left\\');
processed = processed.replace(/\\left\s+\{/g, '\\left\\{'); processed = processed.replace(/\\left\s+\{/g, '\\left\\{');
processed = processed.replace(/\\left\s+\[/g, '\\left['); processed = processed.replace(/\\left\s+\[/g, '\\left[');
processed = processed.replace(/\\left\s+\(/g, '\\left('); processed = processed.replace(/\\left\s+\(/g, '\\left(');
// Fix: \right \} -> \right\} (remove space between \right and delimiter) // // Fix: \right \} -> \right\} (remove space between \right and delimiter)
processed = processed.replace(/\\right\s+\\/g, '\\right\\'); processed = processed.replace(/\\right\s+\\/g, '\\right\\');
processed = processed.replace(/\\right\s+\}/g, '\\right\\}'); processed = processed.replace(/\\right\s+\}/g, '\\right\\}');
processed = processed.replace(/\\right\s+\]/g, '\\right]'); processed = processed.replace(/\\right\s+\]/g, '\\right]');
processed = processed.replace(/\\right\s+\)/g, '\\right)'); processed = processed.replace(/\\right\s+\)/g, '\\right)');
// Fix: \begin{matrix} with mismatched \left/\right -> use \begin{array}
// This is a more complex issue that requires proper \left/\right pairing
// For now, we'll try to convert problematic patterns
// Replace \left( ... \right. text \right) pattern with ( ... \right. text )
// This fixes the common mispairing issue
processed = processed.replace(/\\left\(([^)]*?)\\right\.\s*(\\text\{[^}]*\})\s*\\right\)/g, '($1\\right. $2)');
return processed; return processed;
} }
export default function ResultPanel({ result, fileStatus }: ResultPanelProps) { export default function ResultPanel({ result, fileStatus }: ResultPanelProps) {
const [isExportSidebarOpen, setIsExportSidebarOpen] = useState(false); const [isExportSidebarOpen, setIsExportSidebarOpen] = useState(false);
const [copied, setCopied] = useState(false);
const handleCopy = async () => {
if (result?.markdown_content) {
await navigator.clipboard.writeText(result.markdown_content);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
}
};
if (!result) { if (!result) {
if (fileStatus === 'processing' || fileStatus === 'pending') { if (fileStatus === 'processing' || fileStatus === 'pending') {
return ( return (
<div className="h-full flex flex-col items-center justify-center bg-white text-center p-8"> <div className="h-full flex flex-col items-center justify-center bg-white text-center p-8">
<div className="w-16 h-16 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mb-6"></div> <div className="w-16 h-16 border-4 border-blue-600 border-t-transparent rounded-full animate-spin mb-6"></div>
<h3 className="text-xl font-semibold text-gray-900 mb-2"> <h3 className="text-xl font-semibold text-gray-900 mb-2">
{fileStatus === 'pending' ? 'Waiting in queue...' : 'Analyzing...'} {fileStatus === 'pending' ? 'Waiting in queue...' : 'Analyzing...'}
</h3> </h3>
@@ -125,7 +114,7 @@ export default function ResultPanel({ result, fileStatus }: ResultPanelProps) {
<div className="flex-1 overflow-auto p-8 custom-scrollbar flex justify-center"> <div className="flex-1 overflow-auto p-8 custom-scrollbar flex justify-center">
<div className="prose prose-blue max-w-3xl w-full prose-headings:font-bold prose-h1:text-2xl prose-h2:text-xl prose-p:leading-relaxed prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-100 [&_.katex-display]:text-center"> <div className="prose prose-blue max-w-3xl w-full prose-headings:font-bold prose-h1:text-2xl prose-h2:text-xl prose-p:leading-relaxed prose-pre:bg-gray-50 prose-pre:border prose-pre:border-gray-100 [&_.katex-display]:text-center">
<ReactMarkdown <ReactMarkdown
remarkPlugins={[remarkMath]} remarkPlugins={[remarkMath, remarkBreaks]}
rehypePlugins={[[rehypeKatex, { rehypePlugins={[[rehypeKatex, {
throwOnError: false, throwOnError: false,
errorColor: '#cc0000', errorColor: '#cc0000',

50
src/lib/ommlConverter.ts Normal file
View File

@@ -0,0 +1,50 @@
/**
* MathML to OMML Converter
*
* Uses 'mathml2omml' library to convert MathML to Office Math Markup Language (OMML).
* This is a pure JavaScript implementation and does not require external XSLT files.
*/
import { mml2omml } from 'mathml2omml';
/**
* Converts MathML string to OMML string using mathml2omml library.
* @param mathml The MathML content string
* @returns Promise resolving to OMML string
*/
export async function convertMathmlToOmml(mathml: string): Promise<string> {
try {
// The library is synchronous, but we keep the async signature for compatibility
// and potential future changes (e.g. if we move this to a worker).
const omml = mml2omml(mathml);
return omml;
} catch (error) {
console.error('MathML to OMML conversion failed:', error);
return '';
}
}
/**
* Wraps OMML in Word XML clipboard format if needed.
* This helps Word recognize it when pasting as text.
*/
export function wrapOmmlForClipboard(omml: string): string {
// Replace 2006 namespaces with 2004/2003 namespaces for Word 2003 XML compatibility
// This is necessary because the clipboard format uses the older Word XML structure
const compatibleOmml = omml
.replace(/http:\/\/schemas\.openxmlformats\.org\/officeDocument\/2006\/math/g, 'http://schemas.microsoft.com/office/2004/12/omml')
.replace(/http:\/\/schemas\.openxmlformats\.org\/wordprocessingml\/2006\/main/g, 'http://schemas.microsoft.com/office/word/2003/wordml');
// Simple XML declaration wrapper often helps
return `<?xml version="1.0"?>
<?mso-application progid="Word.Document"?>
<w:wordDocument xmlns:w="http://schemas.microsoft.com/office/word/2003/wordml" xmlns:m="http://schemas.microsoft.com/office/2004/12/omml">
<w:body>
<w:p>
<m:oMathPara>
${compatibleOmml}
</m:oMathPara>
</w:p>
</w:body>
</w:wordDocument>`;
}