add latex support and markdown highlight async build

This commit is contained in:
Zhang Minghan 2023-09-12 17:03:51 +08:00
parent 4a5b021727
commit b6c18f28ab
17 changed files with 411 additions and 151 deletions

View File

@ -13,6 +13,7 @@
<meta name="baidu-site-verification" content="codeva-TJkbi40ZBi" />
<link href="https://fonts.googlefonts.cn/css?family=Andika" rel="stylesheet">
<link href="https://cdn.zmh-program.site/fonts/jetbrains-mono.css" rel="stylesheet">
<link href="https://cdn.jsdelivr.net/npm/katex@0.16.0/dist/katex.min.css" rel="stylesheet">
</head>
<body>
<div id="root"></div>

View File

@ -38,12 +38,13 @@
"react-redux": "^8.1.2",
"react-router-dom": "^6.15.0",
"react-syntax-highlighter": "^15.5.0",
"rehype-katex": "^6.0.3",
"remark-math": "^5.1.1",
"sort-by": "^1.2.0",
"tailwind-merge": "^1.14.0",
"tailwindcss-animate": "^1.0.7"
},
"devDependencies": {
"@rollup/plugin-terser": "^0.4.3",
"@types/node": "^20.5.9",
"@types/react": "^18.2.15",
"@types/react-dom": "^18.2.7",

218
app/pnpm-lock.yaml generated
View File

@ -86,6 +86,12 @@ dependencies:
react-syntax-highlighter:
specifier: ^15.5.0
version: 15.5.0(react@18.2.0)
rehype-katex:
specifier: ^6.0.3
version: 6.0.3
remark-math:
specifier: ^5.1.1
version: 5.1.1
sort-by:
specifier: ^1.2.0
version: 1.2.0
@ -97,9 +103,6 @@ dependencies:
version: 1.0.7(tailwindcss@3.3.3)
devDependencies:
'@rollup/plugin-terser':
specifier: ^0.4.3
version: 0.4.3(rollup@2.79.1)
'@types/node':
specifier: ^20.5.9
version: 20.5.9
@ -2535,21 +2538,6 @@ packages:
rollup: 2.79.1
dev: true
/@rollup/plugin-terser@0.4.3(rollup@2.79.1):
resolution: {integrity: sha512-EF0oejTMtkyhrkwCdg0HJ0IpkcaVg1MMSf2olHb2Jp+1mnLM04OhjpJWGma4HobiDTF0WCyViWuvadyE9ch2XA==}
engines: {node: '>=14.0.0'}
peerDependencies:
rollup: ^2.x || ^3.x
peerDependenciesMeta:
rollup:
optional: true
dependencies:
rollup: 2.79.1
serialize-javascript: 6.0.1
smob: 1.4.0
terser: 5.19.4
dev: true
/@rollup/pluginutils@3.1.0(rollup@2.79.1):
resolution: {integrity: sha512-GksZ6pr6TpIjHm8h9lSQ8pi8BE9VeubNT0OMJ3B5uZJ8pz73NPiqOtCog/x2/QzM1ENChPKxMDhiQuRHsqc+lg==}
engines: {node: '>= 8.0.0'}
@ -2742,6 +2730,14 @@ packages:
resolution: {integrity: sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==}
dev: true
/@types/katex@0.14.0:
resolution: {integrity: sha512-+2FW2CcT0K3P+JMR8YG846bmDwplKUTsWgT2ENwdQ1UdVfRk3GQrh6Mi4sTopy30gI8Uau5CEqHTDZ6YvWIUPA==}
dev: false
/@types/katex@0.16.2:
resolution: {integrity: sha512-dHsSjSlU/EWEEbeNADr3FtZZOAXPkFPUO457QCnoNqcZQXNqNEu/svQd0Nritvd3wNff4vvC/f4e6xgX3Llt8A==}
dev: false
/@types/mdast@3.0.12:
resolution: {integrity: sha512-DT+iNIRNX884cx0/Q1ja7NyUPpZuv0KPyL5rGNxm1WC1OtHstl7n4Jb7nk+xacNShQMbczJjt8uFzznpp6kYBg==}
dependencies:
@ -3458,7 +3454,6 @@ packages:
/commander@8.3.0:
resolution: {integrity: sha512-OkTL9umf+He2DZkUq8f8J9of7yL6RJKI24dVITBmNfZBmri9zYZQrKkuXiKhyfPSu8tUhnVBB1iKXevvnlR4Ww==}
engines: {node: '>= 12'}
dev: true
/common-tags@1.8.2:
resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==}
@ -3684,6 +3679,11 @@ packages:
resolution: {integrity: sha512-p92if5Nz619I0w+akJrLZH0MX0Pb5DX39XOwQTtXSdQQOaYH03S1uIQp4mhOZtAXrxq4ViO67YTiLBo2638o9A==}
dev: true
/entities@4.5.0:
resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==}
engines: {node: '>=0.12'}
dev: false
/errno@0.1.8:
resolution: {integrity: sha512-dJ6oBr5SQ1VSd9qkk7ByRgb/1SH4JZjCHSW/mr63/QcXO9zLVxvJ6Oy13nio03rxpSnVDDjFor75SjVeZWPW/A==}
hasBin: true
@ -4257,10 +4257,70 @@ packages:
dependencies:
function-bind: 1.1.1
/hast-util-from-dom@4.2.0:
resolution: {integrity: sha512-t1RJW/OpJbCAJQeKi3Qrj1cAOLA0+av/iPFori112+0X7R3wng+jxLA+kXec8K4szqPRGI8vPxbbpEYvvpwaeQ==}
dependencies:
hastscript: 7.2.0
web-namespaces: 2.0.1
dev: false
/hast-util-from-html-isomorphic@1.0.0:
resolution: {integrity: sha512-Yu480AKeOEN/+l5LA674a+7BmIvtDj24GvOt7MtQWuhzUwlaaRWdEPXAh3Qm5vhuthpAipFb2vTetKXWOjmTvw==}
dependencies:
'@types/hast': 2.3.5
hast-util-from-dom: 4.2.0
hast-util-from-html: 1.0.2
unist-util-remove-position: 4.0.2
dev: false
/hast-util-from-html@1.0.2:
resolution: {integrity: sha512-LhrTA2gfCbLOGJq2u/asp4kwuG0y6NhWTXiPKP+n0qNukKy7hc10whqqCFfyvIA1Q5U5d0sp9HhNim9gglEH4A==}
dependencies:
'@types/hast': 2.3.5
hast-util-from-parse5: 7.1.2
parse5: 7.1.2
vfile: 5.3.7
vfile-message: 3.1.4
dev: false
/hast-util-from-parse5@7.1.2:
resolution: {integrity: sha512-Nz7FfPBuljzsN3tCQ4kCBKqdNhQE2l0Tn+X1ubgKBPRoiDIu1mL08Cfw4k7q71+Duyaw7DXDN+VTAp4Vh3oCOw==}
dependencies:
'@types/hast': 2.3.5
'@types/unist': 2.0.8
hastscript: 7.2.0
property-information: 6.3.0
vfile: 5.3.7
vfile-location: 4.1.0
web-namespaces: 2.0.1
dev: false
/hast-util-is-element@2.1.3:
resolution: {integrity: sha512-O1bKah6mhgEq2WtVMk+Ta5K7pPMqsBBlmzysLdcwKVrqzZQ0CHqUPiIVspNhAG1rvxpvJjtGee17XfauZYKqVA==}
dependencies:
'@types/hast': 2.3.5
'@types/unist': 2.0.8
dev: false
/hast-util-parse-selector@2.2.5:
resolution: {integrity: sha512-7j6mrk/qqkSehsM92wQjdIgWM2/BW61u/53G6xmC8i1OmEdKLHbk419QKQUjz6LglWsfqoiHmyMRkP1BGjecNQ==}
dev: false
/hast-util-parse-selector@3.1.1:
resolution: {integrity: sha512-jdlwBjEexy1oGz0aJ2f4GKMaVKkA9jwjr4MjAAI22E5fM/TXVZHuS5OpONtdeIkRKqAaryQ2E9xNQxijoThSZA==}
dependencies:
'@types/hast': 2.3.5
dev: false
/hast-util-to-text@3.1.2:
resolution: {integrity: sha512-tcllLfp23dJJ+ju5wCCZHVpzsQQ43+moJbqVX3jNWPB7z/KFC4FyZD6R7y94cHL6MQ33YtMZL8Z0aIXXI4XFTw==}
dependencies:
'@types/hast': 2.3.5
'@types/unist': 2.0.8
hast-util-is-element: 2.1.3
unist-util-find-after: 4.0.1
dev: false
/hast-util-whitespace@2.0.1:
resolution: {integrity: sha512-nAxA0v8+vXSBDt3AnRUNjyRIQ0rD+ntpbAp4LnPkumc5M9yUbSMa4XDU9Q6etY4f1Wp4bNgvc1yjiZtsTTrSng==}
dev: false
@ -4275,6 +4335,16 @@ packages:
space-separated-tokens: 1.1.5
dev: false
/hastscript@7.2.0:
resolution: {integrity: sha512-TtYPq24IldU8iKoJQqvZOuhi5CyCQRAbvDOX0x1eW6rsHSxa/1i2CCiptNTotGHJ3VoHRGmqiv6/D3q113ikkw==}
dependencies:
'@types/hast': 2.3.5
comma-separated-tokens: 2.0.3
hast-util-parse-selector: 3.1.1
property-information: 6.3.0
space-separated-tokens: 2.0.2
dev: false
/he@1.2.0:
resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==}
hasBin: true
@ -4665,6 +4735,13 @@ packages:
engines: {node: '>=0.10.0'}
dev: true
/katex@0.16.8:
resolution: {integrity: sha512-ftuDnJbcbOckGY11OO+zg3OofESlbR5DRl2cmN8HeWeeFIV7wTXvAOx8kEjZjobhA+9wh2fbKeO6cdcA9Mnovg==}
hasBin: true
dependencies:
commander: 8.3.0
dev: false
/keyv@4.5.3:
resolution: {integrity: sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==}
dependencies:
@ -4767,6 +4844,10 @@ packages:
resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==}
dev: true
/longest-streak@3.1.0:
resolution: {integrity: sha512-9Ri+o0JYgehTaVBBDoMqIl8GXtbWg711O3srftcHhZ0dqnETqLaoIK0x17fUw9rFSlK/0NlsKe0Ahhyl5pXE2g==}
dev: false
/loose-envify@1.4.0:
resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==}
hasBin: true
@ -4858,6 +4939,21 @@ packages:
- supports-color
dev: false
/mdast-util-math@2.0.2:
resolution: {integrity: sha512-8gmkKVp9v6+Tgjtq6SYx9kGPpTf6FVYRa53/DLh479aldR9AyP48qeVOgNZ5X7QUK7nOy4yw7vg6mbiGcs9jWQ==}
dependencies:
'@types/mdast': 3.0.12
longest-streak: 3.1.0
mdast-util-to-markdown: 1.5.0
dev: false
/mdast-util-phrasing@3.0.1:
resolution: {integrity: sha512-WmI1gTXUBJo4/ZmSk79Wcb2HcjPJBzM1nlI/OUWA8yk2X9ik3ffNbBGsU+09BFmXaL1IBb9fiuvq6/KMiNycSg==}
dependencies:
'@types/mdast': 3.0.12
unist-util-is: 5.2.1
dev: false
/mdast-util-to-hast@12.3.0:
resolution: {integrity: sha512-pits93r8PhnIoU4Vy9bjW39M2jJ6/tdHyja9rrot9uujkN7UTU9SDnE6WNJz/IGyQk3XHX6yNNtrBH6cQzm8Hw==}
dependencies:
@ -4871,6 +4967,19 @@ packages:
unist-util-visit: 4.1.2
dev: false
/mdast-util-to-markdown@1.5.0:
resolution: {integrity: sha512-bbv7TPv/WC49thZPg3jXuqzuvI45IL2EVAr/KxF0BSdHsU0ceFHOmwQn6evxAh1GaoK/6GQ1wp4R4oW2+LFL/A==}
dependencies:
'@types/mdast': 3.0.12
'@types/unist': 2.0.8
longest-streak: 3.1.0
mdast-util-phrasing: 3.0.1
mdast-util-to-string: 3.2.0
micromark-util-decode-string: 1.1.0
unist-util-visit: 4.1.2
zwitch: 2.0.4
dev: false
/mdast-util-to-string@3.2.0:
resolution: {integrity: sha512-V4Zn/ncyN1QNSqSBxTrMOLpjr+IKdHl2v3KVLoWmDPscP4r9GcCi71gjgvUV1SFSKh92AjAG4peFuBl2/YgCJg==}
dependencies:
@ -4906,6 +5015,18 @@ packages:
uvu: 0.5.6
dev: false
/micromark-extension-math@2.1.2:
resolution: {integrity: sha512-es0CcOV89VNS9wFmyn+wyFTKweXGW4CEvdaAca6SWRWPyYCbBisnjaHLjWO4Nszuiud84jCpkHsqAJoa768Pvg==}
dependencies:
'@types/katex': 0.16.2
katex: 0.16.8
micromark-factory-space: 1.1.0
micromark-util-character: 1.2.0
micromark-util-symbol: 1.1.0
micromark-util-types: 1.1.0
uvu: 0.5.6
dev: false
/micromark-factory-destination@1.1.0:
resolution: {integrity: sha512-XaNDROBgx9SgSChd69pjiGKbV+nfHGDPVYFs5dOoDd7ZnMAE+Cuu91BCpsY8RT2NP9vo/B8pds2VQNCLiu0zhg==}
dependencies:
@ -5269,6 +5390,12 @@ packages:
engines: {node: '>= 0.10'}
dev: true
/parse5@7.1.2:
resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==}
dependencies:
entities: 4.5.0
dev: false
/pascal-case@3.1.2:
resolution: {integrity: sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==}
dependencies:
@ -5735,11 +5862,31 @@ packages:
jsesc: 0.5.0
dev: true
/rehype-katex@6.0.3:
resolution: {integrity: sha512-ByZlRwRUcWegNbF70CVRm2h/7xy7jQ3R9LaY4VVSvjnoVWwWVhNL60DiZsBpC5tSzYQOCvDbzncIpIjPZWodZA==}
dependencies:
'@types/hast': 2.3.5
'@types/katex': 0.14.0
hast-util-from-html-isomorphic: 1.0.0
hast-util-to-text: 3.1.2
katex: 0.16.8
unist-util-visit: 4.1.2
dev: false
/relateurl@0.2.7:
resolution: {integrity: sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==}
engines: {node: '>= 0.10'}
dev: true
/remark-math@5.1.1:
resolution: {integrity: sha512-cE5T2R/xLVtfFI4cCePtiRn+e6jKMtFDR3P8V3qpv8wpKjwvHoBA4eJzvX+nVrnlNy0911bdGmuspCSwetfYHw==}
dependencies:
'@types/mdast': 3.0.12
mdast-util-math: 2.0.2
micromark-extension-math: 2.1.2
unified: 10.1.2
dev: false
/remark-parse@10.0.2:
resolution: {integrity: sha512-3ydxgHa/ZQzG8LvC7jTXccARYDcRld3VfcgIIFs7bI6vbRSxJJmzgLEIIoYKyrfhaY+ujuWaf/PJiMZXoiCXgw==}
dependencies:
@ -5943,10 +6090,6 @@ packages:
engines: {node: '>=8'}
dev: true
/smob@1.4.0:
resolution: {integrity: sha512-MqR3fVulhjWuRNSMydnTlweu38UhQ0HXM4buStD/S3mc/BzX3CuM9OmhyQpmtYCvoYdl5ris6TI0ZqH355Ymqg==}
dev: true
/sort-by@1.2.0:
resolution: {integrity: sha512-aRyW65r3xMnf4nxJRluCg0H/woJpksU1dQxRtXYzau30sNBOmf5HACpDd9MZDhKh7ALQ5FgSOfMPwZEtUmMqcg==}
dependencies:
@ -6361,6 +6504,13 @@ packages:
crypto-random-string: 2.0.0
dev: true
/unist-util-find-after@4.0.1:
resolution: {integrity: sha512-QO/PuPMm2ERxC6vFXEPtmAutOopy5PknD+Oq64gGwxKtk4xwo9Z97t9Av1obPmGU0IyTa6EKYUfTrK2QJS3Ozw==}
dependencies:
'@types/unist': 2.0.8
unist-util-is: 5.2.1
dev: false
/unist-util-generated@2.0.1:
resolution: {integrity: sha512-qF72kLmPxAw0oN2fwpWIqbXAVyEqUzDHMsbtPvOudIlUzXYFIeQIuxXQCRCFh22B7cixvU0MG7m3MW8FTq/S+A==}
dev: false
@ -6377,6 +6527,13 @@ packages:
'@types/unist': 2.0.8
dev: false
/unist-util-remove-position@4.0.2:
resolution: {integrity: sha512-TkBb0HABNmxzAcfLf4qsIbFbaPDvMO6wa3b3j4VcEzFVaw1LBKwnW4/sRJ/atSLSzoIg41JWEdnE7N6DIhGDGQ==}
dependencies:
'@types/unist': 2.0.8
unist-util-visit: 4.1.2
dev: false
/unist-util-stringify-position@3.0.3:
resolution: {integrity: sha512-k5GzIBZ/QatR8N5X2y+drfpWG8IDBzdnVj6OInRNWm1oXrzydiaAT2OQiA8DPRRZyAKb9b6I2a6PxYklZD0gKg==}
dependencies:
@ -6478,6 +6635,13 @@ packages:
sade: 1.8.1
dev: false
/vfile-location@4.1.0:
resolution: {integrity: sha512-YF23YMyASIIJXpktBa4vIGLJ5Gs88UB/XePgqPmTa7cDA+JeO3yclbpheQYCHjVHBn/yePzrXuygIL+xbvRYHw==}
dependencies:
'@types/unist': 2.0.8
vfile: 5.3.7
dev: false
/vfile-message@3.1.4:
resolution: {integrity: sha512-fa0Z6P8HUrQN4BZaX05SIVXic+7kE3b05PWAtPuYP9QLHsLKYR7/AlLW3NtOrpXRLeawpDLMsVkmk5DG0NXgWw==}
dependencies:
@ -6582,6 +6746,10 @@ packages:
graceful-fs: 4.2.11
dev: true
/web-namespaces@2.0.1:
resolution: {integrity: sha512-bKr1DkiNa2krS7qxNtdrtHAmzuYGFQLiQ13TsorsdT6ULTkPLKuu5+GsFpDlg6JFjUTwX2DyhMPG2be8uPrqsQ==}
dev: false
/webidl-conversions@4.0.2:
resolution: {integrity: sha512-YQ+BmxuTgd6UXZW3+ICGfyqRyHXVlD5GtQr5+qjiNW7bF0cqrzX500HVXPBOvgXb5YnzDd+h0zqyv61KUD7+Sg==}
dev: true
@ -6838,3 +7006,7 @@ packages:
resolution: {integrity: sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==}
engines: {node: '>=10'}
dev: true
/zwitch@2.0.4:
resolution: {integrity: sha512-bXE4cR/kVZhKZX/RjPEflHaKVhUVl85noU3v6b8apfQEc1x4A+zBxjZ4lN8LqGd6WZ3dl98pY4o717VFmoPp+A==}
dev: false

View File

@ -5,7 +5,7 @@ import { Button } from "./components/ui/button.tsx";
import router from "./router.ts";
import I18nProvider from "./components/I18nProvider.tsx";
import ProjectLink from "./components/ProjectLink.tsx";
import {BadgeCent, Boxes, CalendarPlus, Cloud, Menu} from "lucide-react";
import { BadgeCent, Boxes, CalendarPlus, Cloud, Menu } from "lucide-react";
import { Provider, useDispatch, useSelector } from "react-redux";
import { toggleMenu } from "./store/menu.ts";
import store from "./store/index.ts";

View File

@ -13,6 +13,7 @@
.scroll-action {
position: absolute;
z-index: 64;
bottom: 112px;
right: 36px;
opacity: 0;
@ -31,6 +32,15 @@
display: flex;
gap: 6px;
flex-direction: column;
max-width: calc(100vw - 64px);
pre {
scrollbar-width: thin;
&::-webkit-scrollbar {
height: 6px;
}
}
&:last-child {
animation: FlexInAnimationFromBottom 0.2s cubic-bezier(0.175, 0.885, 0.32, 1.275) 0s 1 normal forwards running;

View File

@ -26,6 +26,10 @@
height: 40px;
cursor: pointer;
}
button {
white-space: nowrap;
}
}
.avatar {

View File

@ -1,6 +1,8 @@
import { Light as SyntaxHighlighter } from "react-syntax-highlighter";
import { LightAsync as SyntaxHighlighter } from "react-syntax-highlighter";
import { atomOneDark as style } from "react-syntax-highlighter/dist/esm/styles/hljs";
import ReactMarkdown from "react-markdown";
import remarkMath from "remark-math";
import rehypeKatex from "rehype-katex";
import "../assets/markdown/all.less";
type MarkdownProps = {
@ -11,6 +13,8 @@ type MarkdownProps = {
function Markdown({ children, className }: MarkdownProps) {
return (
<ReactMarkdown
remarkPlugins={[remarkMath]}
rehypePlugins={[rehypeKatex]}
className={`markdown-body ${className}`}
children={children}
components={{

View File

@ -41,7 +41,9 @@ function MessageSegment({ message }: MessageProps) {
<TooltipTrigger asChild>
<div className={`message-quota`}>
<Cloud className={`h-4 w-4 icon`} />
<span className={`quota`}>{(message.quota < 0 ? 0 : message.quota).toFixed(2)}</span>
<span className={`quota`}>
{(message.quota < 0 ? 0 : message.quota).toFixed(2)}
</span>
</div>
</TooltipTrigger>
<TooltipContent className={`icon-tooltip`}>

View File

@ -1,14 +1,14 @@
import * as React from "react"
import * as SelectPrimitive from "@radix-ui/react-select"
import { Check, ChevronDown } from "lucide-react"
import * as React from "react";
import * as SelectPrimitive from "@radix-ui/react-select";
import { Check, ChevronDown } from "lucide-react";
import { cn } from "./lib/utils"
import { cn } from "./lib/utils";
const Select = SelectPrimitive.Root
const Select = SelectPrimitive.Root;
const SelectGroup = SelectPrimitive.Group
const SelectGroup = SelectPrimitive.Group;
const SelectValue = SelectPrimitive.Value
const SelectValue = SelectPrimitive.Value;
const SelectTrigger = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Trigger>,
@ -18,7 +18,7 @@ const SelectTrigger = React.forwardRef<
ref={ref}
className={cn(
"flex h-10 w-full items-center justify-between rounded-md border border-input bg-background px-3 py-2 text-sm ring-offset-background placeholder:text-muted-foreground focus:outline-none focus:ring-2 focus:ring-ring focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50",
className
className,
)}
{...props}
>
@ -27,8 +27,8 @@ const SelectTrigger = React.forwardRef<
<ChevronDown className="h-4 w-4 opacity-50" />
</SelectPrimitive.Icon>
</SelectPrimitive.Trigger>
))
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
));
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName;
const SelectContent = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Content>,
@ -41,7 +41,7 @@ const SelectContent = React.forwardRef<
"relative z-50 min-w-[8rem] overflow-hidden rounded-md border bg-popover text-popover-foreground shadow-md data-[state=open]:animate-in data-[state=closed]:animate-out data-[state=closed]:fade-out-0 data-[state=open]:fade-in-0 data-[state=closed]:zoom-out-95 data-[state=open]:zoom-in-95 data-[side=bottom]:slide-in-from-top-2 data-[side=left]:slide-in-from-right-2 data-[side=right]:slide-in-from-left-2 data-[side=top]:slide-in-from-bottom-2",
position === "popper" &&
"data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1",
className
className,
)}
position={position}
{...props}
@ -50,15 +50,15 @@ const SelectContent = React.forwardRef<
className={cn(
"p-1",
position === "popper" &&
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]"
"h-[var(--radix-select-trigger-height)] w-full min-w-[var(--radix-select-trigger-width)]",
)}
>
{children}
</SelectPrimitive.Viewport>
</SelectPrimitive.Content>
</SelectPrimitive.Portal>
))
SelectContent.displayName = SelectPrimitive.Content.displayName
));
SelectContent.displayName = SelectPrimitive.Content.displayName;
const SelectLabel = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Label>,
@ -69,8 +69,8 @@ const SelectLabel = React.forwardRef<
className={cn("py-1.5 pl-8 pr-2 text-sm font-semibold", className)}
{...props}
/>
))
SelectLabel.displayName = SelectPrimitive.Label.displayName
));
SelectLabel.displayName = SelectPrimitive.Label.displayName;
const SelectItem = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Item>,
@ -80,7 +80,7 @@ const SelectItem = React.forwardRef<
ref={ref}
className={cn(
"relative flex w-full cursor-default select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none focus:bg-accent focus:text-accent-foreground data-[disabled]:pointer-events-none data-[disabled]:opacity-50",
className
className,
)}
{...props}
>
@ -92,8 +92,8 @@ const SelectItem = React.forwardRef<
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
</SelectPrimitive.Item>
))
SelectItem.displayName = SelectPrimitive.Item.displayName
));
SelectItem.displayName = SelectPrimitive.Item.displayName;
const SelectSeparator = React.forwardRef<
React.ElementRef<typeof SelectPrimitive.Separator>,
@ -104,8 +104,8 @@ const SelectSeparator = React.forwardRef<
className={cn("-mx-1 my-1 h-px bg-muted", className)}
{...props}
/>
))
SelectSeparator.displayName = SelectPrimitive.Separator.displayName
));
SelectSeparator.displayName = SelectPrimitive.Separator.displayName;
export {
Select,
@ -116,4 +116,4 @@ export {
SelectLabel,
SelectItem,
SelectSeparator,
}
};

View File

@ -1,6 +1,6 @@
import axios from "axios";
export const version: string = "2.3.0";
export const version: string = "2.4.0";
export const deploy: boolean = true;
export let rest_api: string = "http://localhost:8094";
export let ws_api: string = "ws://localhost:8094";

View File

@ -15,12 +15,12 @@ type SubscriptionResponse = {
status: boolean;
is_subscribed: boolean;
expired: number;
}
};
type BuySubscriptionResponse = {
status: boolean;
error: string;
}
};
export async function buyQuota(quota: number): Promise<QuotaResponse> {
try {

View File

@ -108,27 +108,29 @@ const resources = {
"pro-dalle": "50 quotas per day",
"pro-service": "Priority Service Support",
"pro-thread": "Concurrency Increase",
"current": "Current Plan",
"upgrade": "Upgrade",
"renew": "Renew",
current: "Current Plan",
upgrade: "Upgrade",
renew: "Renew",
"cannot-select": "Cannot Select",
"select-time": "Select Subscription Time",
"price": "Price {{price}} CNY",
"expired": "Your Pro subscription will expire in {{expired}} days",
price: "Price {{price}} CNY",
expired: "Your Pro subscription will expire in {{expired}} days",
time: {
1: "1 Month",
3: "3 Months",
6: "6 Months",
12: "1 Year",
},
"success": "Subscribe success",
"success-prompt": "You have successfully subscribed to {{month}} months of Pro.",
"failed": "Subscribe failed",
"failed-prompt": "Failed to subscribe, please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
success: "Subscribe success",
"success-prompt":
"You have successfully subscribed to {{month}} months of Pro.",
failed: "Subscribe failed",
"failed-prompt":
"Failed to subscribe, please make sure you have enough balance, you will soon jump to deeptrain wallet to pay balance.",
},
"cancel": "Cancel",
"confirm": "Confirm",
"percent": "{{cent}}0%",
cancel: "Cancel",
confirm: "Confirm",
percent: "{{cent}}0%",
},
},
cn: {
@ -227,27 +229,28 @@ const resources = {
"pro-dalle": "每日 50 次绘图",
"pro-service": "优先服务支持",
"pro-thread": "并发数提升",
"current": "当前计划",
"upgrade": "升级",
"renew": "续费",
current: "当前计划",
upgrade: "升级",
renew: "续费",
"cannot-select": "无法选择",
"select-time": "选择订阅时间",
"price": "价格 {{price}} 元",
"expired": "您的专业版订阅还有 {{expired}} 天到期",
price: "价格 {{price}} 元",
expired: "您的专业版订阅还有 {{expired}} 天到期",
time: {
1: "1个月",
3: "3个月",
6: "半年",
12: "1年",
},
"success": "订阅成功",
success: "订阅成功",
"success-prompt": "您已成功订阅 {{month}} 月专业版。",
"failed": "订阅失败",
"failed-prompt": "订阅失败,请确保您有足够的余额,您即将跳转到 deeptrain 钱包支付余额。",
failed: "订阅失败",
"failed-prompt":
"订阅失败,请确保您有足够的余额,您即将跳转到 deeptrain 钱包支付余额。",
},
"cancel": "取消",
"confirm": "确认",
"percent": "{{cent}}折",
cancel: "取消",
confirm: "确认",
percent: "{{cent}}折",
},
},
};

View File

@ -20,7 +20,8 @@ import {
Cloud,
ExternalLink,
HardDriveDownload,
HardDriveUpload, Info,
HardDriveUpload,
Info,
Plus,
} from "lucide-react";
import { Input } from "../components/ui/input.tsx";

View File

@ -1,15 +1,24 @@
import {
dialogSelector,
expiredSelector,
isSubscribedSelector, refreshSubscription,
isSubscribedSelector,
refreshSubscription,
refreshSubscriptionTask,
setDialog
setDialog,
} from "../store/subscription.ts";
import {Dialog, DialogContent, DialogDescription, DialogFooter, DialogHeader, DialogTitle, DialogTrigger} from "../components/ui/dialog.tsx";
import {useDispatch, useSelector} from "react-redux";
import {useTranslation} from "react-i18next";
import {useToast} from "../components/ui/use-toast.ts";
import React, {useEffect} from "react";
import {
Dialog,
DialogContent,
DialogDescription,
DialogFooter,
DialogHeader,
DialogTitle,
DialogTrigger,
} from "../components/ui/dialog.tsx";
import { useDispatch, useSelector } from "react-redux";
import { useTranslation } from "react-i18next";
import { useToast } from "../components/ui/use-toast.ts";
import React, { useEffect } from "react";
import "../assets/subscription.less";
import {
Calendar,
@ -19,13 +28,21 @@ import {
ImagePlus,
LifeBuoy,
MessageSquare,
MessagesSquare, Plus,
ServerCrash, Webhook
MessagesSquare,
Plus,
ServerCrash,
Webhook,
} from "lucide-react";
import {Button} from "../components/ui/button.tsx";
import {Select, SelectContent, SelectItem, SelectTrigger, SelectValue} from "../components/ui/select.tsx";
import {Badge} from "../components/ui/badge.tsx";
import {buySubscription} from "../conversation/addition.ts";
import { Button } from "../components/ui/button.tsx";
import {
Select,
SelectContent,
SelectItem,
SelectTrigger,
SelectValue,
} from "../components/ui/select.tsx";
import { Badge } from "../components/ui/badge.tsx";
import { buySubscription } from "../conversation/addition.ts";
function calc_prize(month: number): number {
if (month >= 12) {
@ -36,29 +53,31 @@ function calc_prize(month: number): number {
type UpgradeProps = {
children: React.ReactNode;
}
};
async function callBuyAction(t: any, toast: any, month: number): Promise<boolean> {
const res = await buySubscription(month);
if (res.status) {
toast({
title: t("sub.success"),
description: t("sub.success-prompt", {
month,
}),
});
} else {
toast({
title: t("sub.failed"),
description: t("sub.failed-prompt"),
});
setTimeout(() => {
window.open(
"https://deeptrain.lightxi.com/home/wallet",
);
}, 2000);
}
return res.status;
async function callBuyAction(
t: any,
toast: any,
month: number,
): Promise<boolean> {
const res = await buySubscription(month);
if (res.status) {
toast({
title: t("sub.success"),
description: t("sub.success-prompt", {
month,
}),
});
} else {
toast({
title: t("sub.failed"),
description: t("sub.failed-prompt"),
});
setTimeout(() => {
window.open("https://deeptrain.lightxi.com/home/wallet");
}, 2000);
}
return res.status;
}
function Upgrade({ children }: UpgradeProps) {
const { t } = useTranslation();
@ -72,12 +91,10 @@ function Upgrade({ children }: UpgradeProps) {
<DialogTrigger asChild>{children}</DialogTrigger>
<DialogContent className={`flex-dialog`}>
<DialogHeader>
<DialogTitle>{ t('sub.select-time') }</DialogTitle>
<DialogTitle>{t("sub.select-time")}</DialogTitle>
</DialogHeader>
<div className="upgrade-wrapper">
<Select onValueChange={
(value: string) => setMonth(parseInt(value))
}>
<Select onValueChange={(value: string) => setMonth(parseInt(value))}>
<SelectTrigger className="w-[200px]">
<SelectValue placeholder={t(`sub.time.${month}`)} />
</SelectTrigger>
@ -87,32 +104,36 @@ function Upgrade({ children }: UpgradeProps) {
<SelectItem value={"6"}>{t(`sub.time.6`)}</SelectItem>
<SelectItem value={"12"}>
{t(`sub.time.12`)}
<Badge className={`ml-2 cent`}>{t(`percent`, { cent: 9 })}</Badge>
<Badge className={`ml-2 cent`}>
{t(`percent`, { cent: 9 })}
</Badge>
</SelectItem>
</SelectContent>
</Select>
<p className={`price`}>{ t('sub.price', { price: calc_prize(month) }) }</p>
<p className={`price`}>
{t("sub.price", { price: calc_prize(month) })}
</p>
</div>
<DialogFooter>
<Button variant={`outline`} onClick={
() => setOpen(false)
}>{ t('cancel') }</Button>
<Button onClick={
async () => {
<Button variant={`outline`} onClick={() => setOpen(false)}>
{t("cancel")}
</Button>
<Button
onClick={async () => {
const res = await callBuyAction(t, toast, month);
if (res) {
setOpen(false);
await refreshSubscription(dispatch);
}
}
}>
}}
>
<Plus className={`h-4 w-4 mr-1`} />
{ t('confirm') }
{t("confirm")}
</Button>
</DialogFooter>
</DialogContent>
</Dialog>
)
);
}
function Subscription() {
@ -135,24 +156,37 @@ function Subscription() {
<DialogTitle>{t("sub.dialog-title")}</DialogTitle>
<DialogDescription asChild>
<div className={`sub-wrapper`}>
{
subscription && (
<div className={`date`}>
<Calendar className={`h-4 w-4 mr-1`} />
{t("sub.expired", { expired })}
</div>
)
}
{subscription && (
<div className={`date`}>
<Calendar className={`h-4 w-4 mr-1`} />
{t("sub.expired", { expired })}
</div>
)}
<div className={`plan-wrapper`}>
<div className={`plan`}>
<div className={`title`}>{t("sub.free")}</div>
<div className={`price`}>{t("sub.free-price")}</div>
<div className={`desc`}>
<div><MessageSquare className={`h-4 w-4 mr-1`} />{t("sub.free-gpt3")}</div>
<div><Image className={`h-4 w-4 mr-1`} />{t("sub.free-dalle")}</div>
<div><Globe className={`h-4 w-4 mr-1`} />{t("sub.free-web")}</div>
<div><MessagesSquare className={`h-4 w-4 mr-1`} />{t("sub.free-conversation")}</div>
<div><Webhook className={`h-4 w-4 mr-1`} />{t("sub.free-api")}</div>
<div>
<MessageSquare className={`h-4 w-4 mr-1`} />
{t("sub.free-gpt3")}
</div>
<div>
<Image className={`h-4 w-4 mr-1`} />
{t("sub.free-dalle")}
</div>
<div>
<Globe className={`h-4 w-4 mr-1`} />
{t("sub.free-web")}
</div>
<div>
<MessagesSquare className={`h-4 w-4 mr-1`} />
{t("sub.free-conversation")}
</div>
<div>
<Webhook className={`h-4 w-4 mr-1`} />
{t("sub.free-api")}
</div>
</div>
<Button className={`action`} variant={`outline`} disabled>
{subscription ? t("sub.cannot-select") : t("sub.current")}
@ -162,10 +196,22 @@ function Subscription() {
<div className={`title`}>{t("sub.pro")}</div>
<div className={`price`}>{t("sub.pro-price")}</div>
<div className={`desc`}>
<div><Compass className={`h-4 w-4 mr-1`} />{t("sub.pro-gpt4")}</div>
<div><ImagePlus className={`h-4 w-4 mr-1`} />{t("sub.pro-dalle")}</div>
<div><LifeBuoy className={`h-4 w-4 mr-1`} />{t("sub.pro-service")}</div>
<div><ServerCrash className={`h-4 w-4 mr-1`} />{t("sub.pro-thread")}</div>
<div>
<Compass className={`h-4 w-4 mr-1`} />
{t("sub.pro-gpt4")}
</div>
<div>
<ImagePlus className={`h-4 w-4 mr-1`} />
{t("sub.pro-dalle")}
</div>
<div>
<LifeBuoy className={`h-4 w-4 mr-1`} />
{t("sub.pro-service")}
</div>
<div>
<ServerCrash className={`h-4 w-4 mr-1`} />
{t("sub.pro-thread")}
</div>
</div>
<Upgrade>
<Button className={`action`} variant={`default`}>
@ -179,7 +225,7 @@ function Subscription() {
</DialogHeader>
</DialogContent>
</Dialog>
)
);
}
export default Subscription;

View File

@ -32,8 +32,8 @@ const chatSlice = createSlice({
},
addHistory: (state, action) => {
const name = action.payload.message as string;
const id = state.history.length ?
Math.max(...state.history.map((item) => item.id)) + 1
const id = state.history.length
? Math.max(...state.history.map((item) => item.id)) + 1
: 1;
state.history = insertStart(state.history, { id, name, message: [] });

View File

@ -1,5 +1,5 @@
import { createSlice } from "@reduxjs/toolkit";
import {getSubscription} from "../conversation/addition.ts";
import { getSubscription } from "../conversation/addition.ts";
export const subscriptionSlice = createSlice({
name: "subscription",
@ -37,13 +37,19 @@ export const {
} = subscriptionSlice.actions;
export default subscriptionSlice.reducer;
export const dialogSelector = (state: any): boolean => state.subscription.dialog;
export const isSubscribedSelector = (state: any): boolean => state.subscription.is_subscribed;
export const expiredSelector = (state: any): number => state.subscription.expired;
export const dialogSelector = (state: any): boolean =>
state.subscription.dialog;
export const isSubscribedSelector = (state: any): boolean =>
state.subscription.is_subscribed;
export const expiredSelector = (state: any): number =>
state.subscription.expired;
export const refreshSubscription = async (dispatch: any) => {
const current = new Date().getTime(); //@ts-ignore
if (window.hasOwnProperty("subscription") && current - window.subscription < 2500)
if (
window.hasOwnProperty("subscription") && //@ts-ignore
current - window.subscription < 2500
)
return; //@ts-ignore
window.subscription = current;

View File

@ -67,6 +67,16 @@ export default defineConfig({
}
}
},
{
urlPattern: new RegExp('https://cdn.jsdelivr.net/(.*)'),
handler: 'CacheFirst',
options: {
cacheName: 'cdn-jsdelivr',
expiration: {
maxEntries: 3600,
}
}
},
{
urlPattern: /\.(?:png|gif|jpg|jpeg|svg|webp)$/,
handler: 'CacheFirst',