webui build
This commit is contained in:
parent
384e4ce4d0
commit
893be5cf43
10
Makefile
10
Makefile
@ -18,6 +18,16 @@ build-linux:
|
||||
@echo ---- build for linux
|
||||
wails build -upx -ldflags "-s -w" -platform linux/amd64
|
||||
|
||||
build-web:
|
||||
@echo ---- build for web
|
||||
cd frontend && npm run build
|
||||
|
||||
dev:
|
||||
wails dev
|
||||
|
||||
dev-web:
|
||||
cd frontend && npm run dev
|
||||
|
||||
preview:
|
||||
cd frontend && npm run preview
|
||||
|
||||
|
@ -1,9 +1,10 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="UTF-8"/>
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport"/>
|
||||
<title>RWKV-Runner</title>
|
||||
<meta charset="UTF-8" />
|
||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||
<title>RWKV-Runner</title>
|
||||
<link href="./src/assets/images/logo.png" rel="icon" type="image/x-icon">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root"></div>
|
||||
|
174
frontend/package-lock.json
generated
174
frontend/package-lock.json
generated
@ -15,6 +15,7 @@
|
||||
"@primer/octicons-react": "^19.1.0",
|
||||
"chart.js": "^4.3.0",
|
||||
"classnames": "^2.3.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"html-midi-player": "^1.5.0",
|
||||
"i18next": "^22.4.15",
|
||||
@ -37,6 +38,7 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-beautiful-dnd": "^13.1.4",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
@ -74,12 +76,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/code-frame": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
||||
"version": "7.22.13",
|
||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/highlight": "^7.22.5"
|
||||
"@babel/highlight": "^7.22.13",
|
||||
"chalk": "^2.4.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -125,12 +128,12 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/generator": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
|
||||
"integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/types": "^7.23.0",
|
||||
"@jridgewell/gen-mapping": "^0.3.2",
|
||||
"@jridgewell/trace-mapping": "^0.3.17",
|
||||
"jsesc": "^2.5.1"
|
||||
@ -159,22 +162,22 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-environment-visitor": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-function-name": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/template": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/template": "^7.22.15",
|
||||
"@babel/types": "^7.23.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -245,9 +248,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-split-export-declaration": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz",
|
||||
"integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==",
|
||||
"version": "7.22.6",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/types": "^7.22.5"
|
||||
@ -266,9 +269,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/helper-validator-identifier": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||
"dev": true,
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
@ -298,13 +301,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/highlight": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
||||
"version": "7.22.20",
|
||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"chalk": "^2.0.0",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"chalk": "^2.4.2",
|
||||
"js-tokens": "^4.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -312,9 +315,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/parser": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
|
||||
"integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||
"dev": true,
|
||||
"bin": {
|
||||
"parser": "bin/babel-parser.js"
|
||||
@ -365,33 +368,33 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/template": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
|
||||
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
|
||||
"version": "7.22.15",
|
||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/parser": "^7.22.5",
|
||||
"@babel/types": "^7.22.5"
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/parser": "^7.22.15",
|
||||
"@babel/types": "^7.22.15"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.9.0"
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/traverse": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz",
|
||||
"integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==",
|
||||
"version": "7.23.2",
|
||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/code-frame": "^7.22.5",
|
||||
"@babel/generator": "^7.22.5",
|
||||
"@babel/helper-environment-visitor": "^7.22.5",
|
||||
"@babel/helper-function-name": "^7.22.5",
|
||||
"@babel/code-frame": "^7.22.13",
|
||||
"@babel/generator": "^7.23.0",
|
||||
"@babel/helper-environment-visitor": "^7.22.20",
|
||||
"@babel/helper-function-name": "^7.23.0",
|
||||
"@babel/helper-hoist-variables": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.5",
|
||||
"@babel/parser": "^7.22.5",
|
||||
"@babel/types": "^7.22.5",
|
||||
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||
"@babel/parser": "^7.23.0",
|
||||
"@babel/types": "^7.23.0",
|
||||
"debug": "^4.1.0",
|
||||
"globals": "^11.1.0"
|
||||
},
|
||||
@ -400,13 +403,13 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@babel/types": {
|
||||
"version": "7.22.5",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
|
||||
"integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
|
||||
"version": "7.23.0",
|
||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||
"dev": true,
|
||||
"dependencies": {
|
||||
"@babel/helper-string-parser": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.5",
|
||||
"@babel/helper-validator-identifier": "^7.22.20",
|
||||
"to-fast-properties": "^2.0.0"
|
||||
},
|
||||
"engines": {
|
||||
@ -2279,6 +2282,12 @@
|
||||
"@types/ms": "*"
|
||||
}
|
||||
},
|
||||
"node_modules/@types/file-saver": {
|
||||
"version": "2.0.7",
|
||||
"resolved": "https://registry.npmjs.org/@types/file-saver/-/file-saver-2.0.7.tgz",
|
||||
"integrity": "sha512-dNKVfHd/jk0SkR/exKGj2ggkB45MAkzvWCaqLUUgkyjITkGNzH8H+yUwr+BLJUBjZOe9w8X3wgmXhZDRg1ED6A==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/@types/hast": {
|
||||
"version": "2.3.4",
|
||||
"resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz",
|
||||
@ -2288,9 +2297,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/hoist-non-react-statics": {
|
||||
"version": "3.3.1",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
||||
"version": "3.3.5",
|
||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||
"dependencies": {
|
||||
"@types/react": "*",
|
||||
"hoist-non-react-statics": "^3.3.0"
|
||||
@ -2371,9 +2380,9 @@
|
||||
}
|
||||
},
|
||||
"node_modules/@types/react-redux": {
|
||||
"version": "7.1.25",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
|
||||
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
|
||||
"version": "7.1.29",
|
||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.29.tgz",
|
||||
"integrity": "sha512-orHCOWqBBQ1LP1uD6JVdXL+ZRTEWhGGne+VOPcXef03rC+QYdzktLhxR3ozymPDyZK0CNCUuQs9tyQhfg1ku+w==",
|
||||
"dependencies": {
|
||||
"@types/hoist-non-react-statics": "^3.3.0",
|
||||
"@types/react": "*",
|
||||
@ -2649,10 +2658,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/caniuse-lite": {
|
||||
"version": "1.0.30001482",
|
||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz",
|
||||
"integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==",
|
||||
"dev": true
|
||||
"version": "1.0.30001561",
|
||||
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz",
|
||||
"integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/browserslist"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/caniuse-lite"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ccount": {
|
||||
"version": "2.0.1",
|
||||
@ -3232,6 +3255,11 @@
|
||||
"resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz",
|
||||
"integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw=="
|
||||
},
|
||||
"node_modules/file-saver": {
|
||||
"version": "2.0.5",
|
||||
"resolved": "https://registry.npmjs.org/file-saver/-/file-saver-2.0.5.tgz",
|
||||
"integrity": "sha512-P9bmyZ3h/PRG+Nzga+rbdI4OEpNDzAVyy74uVO9ATgzLK6VtAsYybF/+TOCvrc0MO793d6+42lLyZTw7/ArVzA=="
|
||||
},
|
||||
"node_modules/fill-range": {
|
||||
"version": "7.0.1",
|
||||
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
|
||||
@ -4591,10 +4619,24 @@
|
||||
}
|
||||
},
|
||||
"node_modules/postcss": {
|
||||
"version": "8.4.23",
|
||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.23.tgz",
|
||||
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
|
||||
"version": "8.4.31",
|
||||
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||
"dev": true,
|
||||
"funding": [
|
||||
{
|
||||
"type": "opencollective",
|
||||
"url": "https://opencollective.com/postcss/"
|
||||
},
|
||||
{
|
||||
"type": "tidelift",
|
||||
"url": "https://tidelift.com/funding/github/npm/postcss"
|
||||
},
|
||||
{
|
||||
"type": "github",
|
||||
"url": "https://github.com/sponsors/ai"
|
||||
}
|
||||
],
|
||||
"dependencies": {
|
||||
"nanoid": "^3.3.6",
|
||||
"picocolors": "^1.0.0",
|
||||
@ -4696,9 +4738,9 @@
|
||||
"integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg=="
|
||||
},
|
||||
"node_modules/protobufjs": {
|
||||
"version": "6.11.3",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
|
||||
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
|
||||
"version": "6.11.4",
|
||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
|
||||
"integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
|
||||
"hasInstallScript": true,
|
||||
"dependencies": {
|
||||
"@protobufjs/aspromise": "^1.1.2",
|
||||
|
@ -16,6 +16,7 @@
|
||||
"@primer/octicons-react": "^19.1.0",
|
||||
"chart.js": "^4.3.0",
|
||||
"classnames": "^2.3.2",
|
||||
"file-saver": "^2.0.5",
|
||||
"github-markdown-css": "^5.2.0",
|
||||
"html-midi-player": "^1.5.0",
|
||||
"i18next": "^22.4.15",
|
||||
@ -38,6 +39,7 @@
|
||||
"uuid": "^9.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/file-saver": "^2.0.7",
|
||||
"@types/react": "^18.2.6",
|
||||
"@types/react-beautiful-dnd": "^13.1.4",
|
||||
"@types/react-dom": "^18.2.4",
|
||||
|
@ -26,18 +26,22 @@
|
||||
import { FluentProvider, Tab, TabList, webDarkTheme, webLightTheme } from '@fluentui/react-components';
|
||||
import { FC, useEffect, useState } from 'react';
|
||||
import { Route, Routes, useLocation, useNavigate } from 'react-router';
|
||||
import { pages } from './pages';
|
||||
import { pages as clientPages } from './pages';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
import commonStore from './stores/commonStore';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { CustomToastContainer } from './components/CustomToastContainer';
|
||||
import { LazyImportComponent } from './components/LazyImportComponent';
|
||||
|
||||
const App: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const location = useLocation();
|
||||
const mq = useMediaQuery('(min-width: 640px)');
|
||||
const pages = commonStore.platform === 'web' ? clientPages.filter(page =>
|
||||
!['/configs', '/models', '/downloads', '/train', '/about'].some(path => page.path === path)
|
||||
) : clientPages;
|
||||
|
||||
const [path, setPath] = useState<string>(pages[0].path);
|
||||
|
||||
@ -82,7 +86,7 @@ const App: FC = observer(() => {
|
||||
<div className="h-full w-full p-2 box-border overflow-y-hidden">
|
||||
<Routes>
|
||||
{pages.map(({ path, element }, index) => (
|
||||
<Route key={`${path}-${index}`} path={path} element={element} />
|
||||
<Route key={`${path}-${index}`} path={path} element={<LazyImportComponent lazyChildren={element} />} />
|
||||
))}
|
||||
</Routes>
|
||||
</div>
|
||||
|
@ -262,5 +262,7 @@
|
||||
"is as follows. When replying to me, consider the file content and respond accordingly:": "の内容は以下の通りです。私に返信する際は、ファイルの内容を考慮して適切に返信してください:",
|
||||
"What's the file name": "ファイル名は何ですか",
|
||||
"The file name is: ": "ファイル名は次のとおりです: ",
|
||||
"Port is occupied. Change it in Configs page or close the program that occupies the port.": "ポートが占有されています。設定ページで変更するか、ポートを占有しているプログラムを終了してください。"
|
||||
"Port is occupied. Change it in Configs page or close the program that occupies the port.": "ポートが占有されています。設定ページで変更するか、ポートを占有しているプログラムを終了してください。",
|
||||
"Loading...": "読み込み中...",
|
||||
"Hello, what can I do for you?": "こんにちは、何かお手伝いできますか?"
|
||||
}
|
@ -262,5 +262,7 @@
|
||||
"is as follows. When replying to me, consider the file content and respond accordingly:": "内容如下。回复时考虑文件内容并做出相应回复:",
|
||||
"What's the file name": "文件名是什么",
|
||||
"The file name is: ": "文件名是:",
|
||||
"Port is occupied. Change it in Configs page or close the program that occupies the port.": "端口被占用。请在配置页面更改端口,或关闭占用端口的程序"
|
||||
"Port is occupied. Change it in Configs page or close the program that occupies the port.": "端口被占用。请在配置页面更改端口,或关闭占用端口的程序",
|
||||
"Loading...": "加载中...",
|
||||
"Hello, what can I do for you?": "你好,有什么要我帮忙的吗?"
|
||||
}
|
@ -1,4 +1,4 @@
|
||||
import { FC, ReactElement } from 'react';
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@ -11,7 +11,9 @@ import {
|
||||
} from '@fluentui/react-components';
|
||||
import { ToolTipButton } from './ToolTipButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import MarkdownRender from './MarkdownRender';
|
||||
import { LazyImportComponent } from './LazyImportComponent';
|
||||
|
||||
const MarkdownRender = React.lazy(() => import('./MarkdownRender'));
|
||||
|
||||
export const DialogButton: FC<{
|
||||
text?: string | null
|
||||
@ -45,7 +47,9 @@ export const DialogButton: FC<{
|
||||
<DialogContent>
|
||||
{
|
||||
markdown ?
|
||||
<MarkdownRender>{contentText}</MarkdownRender> :
|
||||
<LazyImportComponent lazyChildren={MarkdownRender}>
|
||||
{contentText}
|
||||
</LazyImportComponent> :
|
||||
contentText
|
||||
}
|
||||
</DialogContent>
|
||||
|
20
frontend/src/components/LazyImportComponent.tsx
Normal file
20
frontend/src/components/LazyImportComponent.tsx
Normal file
@ -0,0 +1,20 @@
|
||||
import { FC, LazyExoticComponent, ReactNode, Suspense } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
interface LazyImportComponentProps {
|
||||
lazyChildren: LazyExoticComponent<FC<any>>;
|
||||
lazyProps?: any;
|
||||
children?: ReactNode;
|
||||
}
|
||||
|
||||
export const LazyImportComponent: FC<LazyImportComponentProps> = (props) => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
<Suspense fallback={<div>{t('Loading...')}</div>}>
|
||||
<props.lazyChildren {...props.lazyProps}>
|
||||
{props.children}
|
||||
</props.lazyChildren>
|
||||
</Suspense>
|
||||
);
|
||||
};
|
@ -21,7 +21,7 @@ const Hyperlink: FC<any> = ({ href, children }) => {
|
||||
);
|
||||
};
|
||||
|
||||
export const MarkdownRender: FC<ReactMarkdownOptions> = (props) => {
|
||||
const MarkdownRender: FC<ReactMarkdownOptions> = (props) => {
|
||||
return (
|
||||
<div dir="auto" className="markdown-body">
|
||||
<ReactMarkdown
|
||||
|
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
|
||||
import { ToolTipButton } from './ToolTipButton';
|
||||
import { Play16Regular, Stop16Regular } from '@fluentui/react-icons';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { WindowShow } from '../../wailsjs/runtime/runtime';
|
||||
import { WindowShow } from '../../wailsjs/runtime';
|
||||
|
||||
const mainButtonText = {
|
||||
[ModelStatus.Offline]: 'Run',
|
||||
|
@ -25,7 +25,8 @@ export const WorkHeader: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
|
||||
|
||||
return (
|
||||
return commonStore.platform === 'web' ?
|
||||
<div /> :
|
||||
<div className="flex flex-col gap-1">
|
||||
<div className="flex justify-between items-center">
|
||||
<div className="flex items-center gap-2">
|
||||
@ -42,5 +43,5 @@ export const WorkHeader: FC = observer(() => {
|
||||
</Text>
|
||||
<Divider style={{ flexGrow: 0 }} />
|
||||
</div>
|
||||
);
|
||||
;
|
||||
});
|
@ -1,3 +1,4 @@
|
||||
import './webWails';
|
||||
import React from 'react';
|
||||
import { createRoot } from 'react-dom/client';
|
||||
import './style.scss';
|
||||
@ -6,7 +7,6 @@ import App from './App';
|
||||
import { HashRouter } from 'react-router-dom';
|
||||
import { startup } from './startup';
|
||||
import './_locales/i18n-react';
|
||||
import 'html-midi-player';
|
||||
import { WindowShow } from '../wailsjs/runtime';
|
||||
|
||||
startup().then(() => {
|
||||
|
@ -5,9 +5,7 @@ import MarkdownRender from '../components/MarkdownRender';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import commonStore from '../stores/commonStore';
|
||||
|
||||
export type AboutContent = { [lang: string]: string }
|
||||
|
||||
export const About: FC = observer(() => {
|
||||
const About: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const lang: string = commonStore.settings.language;
|
||||
|
||||
@ -21,3 +19,5 @@ export const About: FC = observer(() => {
|
||||
} />
|
||||
);
|
||||
});
|
||||
|
||||
export default About;
|
||||
|
@ -28,46 +28,16 @@ import { OpenFileFolder, OpenOpenFileDialog, OpenSaveFileDialog } from '../../wa
|
||||
import { absPathAsset, bytesToReadable, toastWithButton } from '../utils';
|
||||
import { PresetsButton } from './PresetsManager/PresetsButton';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
import { botName, ConversationMessage, MessageType, userName, welcomeUuid } from '../types/chat';
|
||||
|
||||
export const userName = 'M E';
|
||||
export const botName = 'A I';
|
||||
let chatSseControllers: {
|
||||
[id: string]: AbortController
|
||||
} = {};
|
||||
|
||||
export const welcomeUuid = 'welcome';
|
||||
|
||||
export enum MessageType {
|
||||
Normal,
|
||||
Error
|
||||
}
|
||||
|
||||
export type Side = 'left' | 'right'
|
||||
|
||||
export type Color = 'neutral' | 'brand' | 'colorful'
|
||||
|
||||
export type MessageItem = {
|
||||
sender: string,
|
||||
type: MessageType,
|
||||
color: Color,
|
||||
avatarImg?: string,
|
||||
time: string,
|
||||
content: string,
|
||||
side: Side,
|
||||
done: boolean
|
||||
}
|
||||
|
||||
export type Conversation = {
|
||||
[uuid: string]: MessageItem
|
||||
}
|
||||
|
||||
export type Role = 'assistant' | 'user' | 'system';
|
||||
|
||||
export type ConversationMessage = {
|
||||
role: Role;
|
||||
content: string;
|
||||
}
|
||||
|
||||
let chatSseControllers: { [id: string]: AbortController } = {};
|
||||
|
||||
const MoreUtilsButton: FC<{ uuid: string, setEditing: (editing: boolean) => void }> = observer(({
|
||||
const MoreUtilsButton: FC<{
|
||||
uuid: string,
|
||||
setEditing: (editing: boolean) => void
|
||||
}> = observer(({
|
||||
uuid,
|
||||
setEditing
|
||||
}) => {
|
||||
@ -98,7 +68,8 @@ const MoreUtilsButton: FC<{ uuid: string, setEditing: (editing: boolean) => void
|
||||
});
|
||||
|
||||
const ChatMessageItem: FC<{
|
||||
uuid: string, onSubmit: (message: string | null, answerId: string | null,
|
||||
uuid: string,
|
||||
onSubmit: (message: string | null, answerId: string | null,
|
||||
startUuid: string | null, endUuid: string | null, includeEndUuid: boolean) => void
|
||||
}> = observer(({ uuid, onSubmit }) => {
|
||||
const { t } = useTranslation();
|
||||
@ -243,7 +214,7 @@ const ChatPanel: FC = observer(() => {
|
||||
color: 'colorful',
|
||||
avatarImg: logo,
|
||||
time: new Date().toISOString(),
|
||||
content: t('Hello! I\'m RWKV, an open-source and commercially usable large language model.'),
|
||||
content: commonStore.platform === 'web' ? t('Hello, what can I do for you?') : t('Hello! I\'m RWKV, an open-source and commercially usable large language model.'),
|
||||
side: 'left',
|
||||
done: true
|
||||
}
|
||||
@ -260,7 +231,7 @@ const ChatPanel: FC = observer(() => {
|
||||
e.stopPropagation();
|
||||
if (e.type === 'click' || (e.keyCode === 13 && !e.shiftKey)) {
|
||||
e.preventDefault();
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl && commonStore.platform !== 'web') {
|
||||
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
@ -464,7 +435,7 @@ const ChatPanel: FC = observer(() => {
|
||||
: <Attach16Regular />}
|
||||
size="small" shape="circular" appearance="secondary"
|
||||
onClick={() => {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl && commonStore.platform !== 'web') {
|
||||
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||
return;
|
||||
}
|
||||
@ -478,43 +449,62 @@ const ChatPanel: FC = observer(() => {
|
||||
|
||||
commonStore.setAttachmentUploading(true);
|
||||
|
||||
// Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file.
|
||||
// const blob = new Blob([atob(info.content as unknown as string)]); // await fetch(`data:application/octet-stream;base64,${info.content}`).then(r => r.blob());
|
||||
const blob = await fetch(absPathAsset(filePath)).then(r => r.blob());
|
||||
const attachmentName = filePath.split(/[\\/]/).pop();
|
||||
const urlPath = `/file-to-text?file_name=${attachmentName}`;
|
||||
const bodyForm = new FormData();
|
||||
bodyForm.append('file_data', blob, attachmentName);
|
||||
fetch(commonStore.settings.apiUrl ?
|
||||
commonStore.settings.apiUrl + urlPath :
|
||||
`http://127.0.0.1:${port}${urlPath}`, {
|
||||
method: 'POST',
|
||||
body: bodyForm
|
||||
}).then(async r => {
|
||||
if (r.status === 200) {
|
||||
const pages = (await r.json()).pages as any[];
|
||||
let attachmentContent: string;
|
||||
if (pages.length === 1)
|
||||
attachmentContent = pages[0].page_content;
|
||||
else
|
||||
attachmentContent = pages.map((p, i) => `Page ${i + 1}:\n${p.page_content}`).join('\n\n');
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent
|
||||
});
|
||||
} else {
|
||||
toast(r.statusText + '\n' + (await r.text()), {
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
commonStore.setAttachmentUploading(false);
|
||||
}
|
||||
).catch(e => {
|
||||
let blob: Blob;
|
||||
let attachmentName: string | undefined;
|
||||
let attachmentContent: string | undefined;
|
||||
if (commonStore.platform === 'web') {
|
||||
const webReturn = filePath as any;
|
||||
blob = webReturn.blob;
|
||||
attachmentName = blob.name;
|
||||
attachmentContent = webReturn.content;
|
||||
} else {
|
||||
// Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file.
|
||||
// const blob = new Blob([atob(info.content as unknown as string)]); // await fetch(`data:application/octet-stream;base64,${info.content}`).then(r => r.blob());
|
||||
blob = await fetch(absPathAsset(filePath)).then(r => r.blob());
|
||||
attachmentName = filePath.split(/[\\/]/).pop();
|
||||
}
|
||||
if (attachmentContent) {
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent
|
||||
});
|
||||
commonStore.setAttachmentUploading(false);
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
});
|
||||
} else {
|
||||
const urlPath = `/file-to-text?file_name=${attachmentName}`;
|
||||
const bodyForm = new FormData();
|
||||
bodyForm.append('file_data', blob, attachmentName);
|
||||
fetch(commonStore.settings.apiUrl ?
|
||||
commonStore.settings.apiUrl + urlPath :
|
||||
`http://127.0.0.1:${port}${urlPath}`, {
|
||||
method: 'POST',
|
||||
body: bodyForm
|
||||
}).then(async r => {
|
||||
if (r.status === 200) {
|
||||
const pages = (await r.json()).pages as any[];
|
||||
if (pages.length === 1)
|
||||
attachmentContent = pages[0].page_content;
|
||||
else
|
||||
attachmentContent = pages.map((p, i) => `Page ${i + 1}:\n${p.page_content}`).join('\n\n');
|
||||
commonStore.setCurrentTempAttachment(
|
||||
{
|
||||
name: attachmentName!,
|
||||
size: blob.size,
|
||||
content: attachmentContent!
|
||||
});
|
||||
} else {
|
||||
toast(r.statusText + '\n' + (await r.text()), {
|
||||
type: 'error'
|
||||
});
|
||||
}
|
||||
commonStore.setAttachmentUploading(false);
|
||||
}
|
||||
).catch(e => {
|
||||
commonStore.setAttachmentUploading(false);
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
});
|
||||
}
|
||||
}).catch(e => {
|
||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||
});
|
||||
@ -586,7 +576,7 @@ const ChatPanel: FC = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const Chat: FC = observer(() => {
|
||||
const Chat: FC = observer(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||
<WorkHeader />
|
||||
@ -594,3 +584,5 @@ export const Chat: FC = observer(() => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Chat;
|
||||
|
@ -5,7 +5,6 @@ import { Button, Dropdown, Input, Option, Textarea } from '@fluentui/react-compo
|
||||
import { Labeled } from '../components/Labeled';
|
||||
import { ValuedSlider } from '../components/ValuedSlider';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ApiParameters } from './Configs';
|
||||
import commonStore, { ModelStatus } from '../stores/commonStore';
|
||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||
import { toast } from 'react-toastify';
|
||||
@ -14,18 +13,7 @@ import { PresetsButton } from './PresetsManager/PresetsButton';
|
||||
import { ToolTipButton } from '../components/ToolTipButton';
|
||||
import { ArrowSync20Regular } from '@fluentui/react-icons';
|
||||
import { defaultPresets } from './defaultConfigs';
|
||||
|
||||
export type CompletionParams = Omit<ApiParameters, 'apiPort'> & {
|
||||
stop: string,
|
||||
injectStart: string,
|
||||
injectEnd: string
|
||||
};
|
||||
|
||||
export type CompletionPreset = {
|
||||
name: string,
|
||||
prompt: string,
|
||||
params: CompletionParams
|
||||
}
|
||||
import { CompletionParams, CompletionPreset } from '../types/completion';
|
||||
|
||||
let completionSseController: AbortController | null = null;
|
||||
|
||||
@ -80,7 +68,7 @@ const CompletionPanel: FC = observer(() => {
|
||||
const onSubmit = (prompt: string) => {
|
||||
commonStore.setCompletionSubmittedPrompt(prompt);
|
||||
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl && commonStore.platform !== 'web') {
|
||||
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||
commonStore.setCompletionGenerating(false);
|
||||
return;
|
||||
@ -269,7 +257,7 @@ const CompletionPanel: FC = observer(() => {
|
||||
} />
|
||||
</div>
|
||||
<div className="grow" />
|
||||
<div className="flex justify-between gap-2">
|
||||
<div className="hidden justify-between gap-2 sm:flex">
|
||||
<Button className="grow" onClick={() => {
|
||||
const newPrompt = prompt.replace(/\n+\ /g, '\n').split('\n').map((line) => line.trim()).join('\n');
|
||||
setPrompt(newPrompt);
|
||||
@ -303,7 +291,7 @@ const CompletionPanel: FC = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const Completion: FC = observer(() => {
|
||||
const Completion: FC = observer(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||
<WorkHeader />
|
||||
@ -311,3 +299,5 @@ export const Completion: FC = observer(() => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Completion;
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'html-midi-player';
|
||||
import React, { FC, useEffect, useRef } from 'react';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { WorkHeader } from '../components/WorkHeader';
|
||||
@ -17,17 +18,7 @@ import { NoteSequence } from '@magenta/music/esm/protobuf.js';
|
||||
import { defaultCompositionPrompt } from './defaultConfigs';
|
||||
import { FileExists, OpenFileFolder, OpenSaveFileDialogBytes } from '../../wailsjs/go/backend_golang/App';
|
||||
import { toastWithButton } from '../utils';
|
||||
|
||||
export type CompositionParams = {
|
||||
prompt: string,
|
||||
maxResponseToken: number,
|
||||
temperature: number,
|
||||
topP: number,
|
||||
autoPlay: boolean,
|
||||
useLocalSoundFont: boolean,
|
||||
midi: ArrayBuffer | null,
|
||||
ns: NoteSequence | null
|
||||
}
|
||||
import { CompositionParams } from '../types/composition';
|
||||
|
||||
let compositionSseController: AbortController | null = null;
|
||||
|
||||
@ -137,7 +128,7 @@ const CompositionPanel: FC = observer(() => {
|
||||
const onSubmit = (prompt: string) => {
|
||||
commonStore.setCompositionSubmittedPrompt(prompt);
|
||||
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
|
||||
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl && commonStore.platform !== 'web') {
|
||||
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||
commonStore.setCompositionGenerating(false);
|
||||
return;
|
||||
@ -335,7 +326,7 @@ const CompositionPanel: FC = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
export const Composition: FC = observer(() => {
|
||||
const Composition: FC = observer(() => {
|
||||
return (
|
||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||
<WorkHeader />
|
||||
@ -343,3 +334,5 @@ export const Composition: FC = observer(() => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default Composition;
|
||||
|
@ -29,45 +29,14 @@ import { updateConfig } from '../apis';
|
||||
import { ConvertModel, ConvertSafetensors, FileExists, GetPyError } from '../../wailsjs/go/backend_golang/App';
|
||||
import { checkDependencies, getStrategy } from '../utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { WindowShow } from '../../wailsjs/runtime/runtime';
|
||||
import { WindowShow } from '../../wailsjs/runtime';
|
||||
import strategyImg from '../assets/images/strategy.jpg';
|
||||
import strategyZhImg from '../assets/images/strategy_zh.jpg';
|
||||
import { ResetConfigsButton } from '../components/ResetConfigsButton';
|
||||
import { useMediaQuery } from 'usehooks-ts';
|
||||
import { ApiParameters, Device, ModelParameters, Precision } from '../types/configs';
|
||||
|
||||
export type ApiParameters = {
|
||||
apiPort: number
|
||||
maxResponseToken: number;
|
||||
temperature: number;
|
||||
topP: number;
|
||||
presencePenalty: number;
|
||||
frequencyPenalty: number;
|
||||
}
|
||||
|
||||
export type Device = 'CPU' | 'CUDA' | 'CUDA-Beta' | 'WebGPU' | 'MPS' | 'Custom';
|
||||
export type Precision = 'fp16' | 'int8' | 'fp32';
|
||||
|
||||
export type ModelParameters = {
|
||||
// different models can not have the same name
|
||||
modelName: string;
|
||||
device: Device;
|
||||
precision: Precision;
|
||||
storedLayers: number;
|
||||
maxStoredLayers: number;
|
||||
useCustomCuda?: boolean;
|
||||
customStrategy?: string;
|
||||
useCustomTokenizer?: boolean;
|
||||
customTokenizer?: string;
|
||||
}
|
||||
|
||||
export type ModelConfig = {
|
||||
// different configs can have the same name
|
||||
name: string;
|
||||
apiParameters: ApiParameters
|
||||
modelParameters: ModelParameters
|
||||
}
|
||||
|
||||
export const Configs: FC = observer(() => {
|
||||
const Configs: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const [selectedIndex, setSelectedIndex] = React.useState(commonStore.currentModelConfigIndex);
|
||||
const [selectedConfig, setSelectedConfig] = React.useState(commonStore.modelConfigs[selectedIndex]);
|
||||
@ -478,3 +447,5 @@ export const Configs: FC = observer(() => {
|
||||
} />
|
||||
);
|
||||
});
|
||||
|
||||
export default Configs;
|
||||
|
@ -9,19 +9,7 @@ import { ToolTipButton } from '../components/ToolTipButton';
|
||||
import { Folder20Regular, Pause20Regular, Play20Regular } from '@fluentui/react-icons';
|
||||
import { AddToDownloadList, OpenFileFolder, PauseDownload } from '../../wailsjs/go/backend_golang/App';
|
||||
|
||||
export type DownloadStatus = {
|
||||
name: string;
|
||||
path: string;
|
||||
url: string;
|
||||
transferred: number;
|
||||
size: number;
|
||||
speed: number;
|
||||
progress: number;
|
||||
downloading: boolean;
|
||||
done: boolean;
|
||||
}
|
||||
|
||||
export const Downloads: FC = observer(() => {
|
||||
const Downloads: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const finishedModelsLen = commonStore.downloadList.filter((status) => status.done && status.name.endsWith('.pth')).length;
|
||||
useEffect(() => {
|
||||
@ -91,3 +79,5 @@ export const Downloads: FC = observer(() => {
|
||||
} />
|
||||
);
|
||||
});
|
||||
|
||||
export default Downloads;
|
||||
|
@ -1,11 +1,13 @@
|
||||
import { CompoundButton, Link, Text } from '@fluentui/react-components';
|
||||
import React, { FC, ReactElement } from 'react';
|
||||
import React, { FC } from 'react';
|
||||
import banner from '../assets/images/banner.jpg';
|
||||
import {
|
||||
Chat20Regular,
|
||||
ClipboardEdit20Regular,
|
||||
DataUsageSettings20Regular,
|
||||
DocumentSettings20Regular
|
||||
DocumentSettings20Regular,
|
||||
MusicNote220Regular,
|
||||
Settings20Regular
|
||||
} from '@fluentui/react-icons';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
@ -14,21 +16,13 @@ import manifest from '../../../manifest.json';
|
||||
import { BrowserOpenURL } from '../../wailsjs/runtime';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ConfigSelector } from '../components/ConfigSelector';
|
||||
import MarkdownRender from '../components/MarkdownRender';
|
||||
import commonStore from '../stores/commonStore';
|
||||
import { Completion } from './Completion';
|
||||
import { ResetConfigsButton } from '../components/ResetConfigsButton';
|
||||
import { AdvancedGeneralSettings } from './Settings';
|
||||
import { NavCard } from '../types/home';
|
||||
import { LazyImportComponent } from '../components/LazyImportComponent';
|
||||
|
||||
export type IntroductionContent = { [lang: string]: string }
|
||||
|
||||
type NavCard = {
|
||||
label: string;
|
||||
desc: string;
|
||||
path: string;
|
||||
icon: ReactElement;
|
||||
};
|
||||
|
||||
const navCards: NavCard[] = [
|
||||
const clientNavCards: NavCard[] = [
|
||||
{
|
||||
label: 'Chat',
|
||||
desc: 'Go to chat page',
|
||||
@ -55,7 +49,36 @@ const navCards: NavCard[] = [
|
||||
}
|
||||
];
|
||||
|
||||
export const Home: FC = observer(() => {
|
||||
const webNavCards: NavCard[] = [
|
||||
{
|
||||
label: 'Chat',
|
||||
desc: 'Go to chat page',
|
||||
path: '/chat',
|
||||
icon: <Chat20Regular />
|
||||
},
|
||||
{
|
||||
label: 'Completion',
|
||||
desc: 'Writer, Translator, Role-playing',
|
||||
path: '/completion',
|
||||
icon: <ClipboardEdit20Regular />
|
||||
},
|
||||
{
|
||||
label: 'Composition',
|
||||
desc: '',
|
||||
path: '/composition',
|
||||
icon: <MusicNote220Regular />
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
desc: '',
|
||||
path: '/settings',
|
||||
icon: <Settings20Regular />
|
||||
}
|
||||
];
|
||||
|
||||
const MarkdownRender = React.lazy(() => import('../components/MarkdownRender'));
|
||||
|
||||
const Home: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const navigate = useNavigate();
|
||||
const lang: string = commonStore.settings.language;
|
||||
@ -64,39 +87,64 @@ export const Home: FC = observer(() => {
|
||||
navigate({ pathname: path });
|
||||
};
|
||||
|
||||
return (
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<img className="rounded-xl select-none hidden sm:block"
|
||||
style={{ maxHeight: '40%', margin: '0 auto' }} src={banner} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text size={600} weight="medium">{t('Introduction')}</Text>
|
||||
<div className="h-40 overflow-y-auto overflow-x-hidden p-1">
|
||||
<MarkdownRender>
|
||||
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']}
|
||||
</MarkdownRender>
|
||||
return commonStore.platform === 'web' ?
|
||||
(
|
||||
<div className="flex flex-col gap-2 h-full">
|
||||
<img className="rounded-xl select-none object-cover grow"
|
||||
style={{ maxHeight: '40%' }} src={banner} />
|
||||
<div className="grow"></div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
|
||||
{webNavCards.map(({ label, path, icon, desc }, index) => (
|
||||
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
|
||||
size="large" onClick={() => onClickNavCard(path)}>
|
||||
{t(label)}
|
||||
</CompoundButton>
|
||||
))}
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
|
||||
{navCards.map(({ label, path, icon, desc }, index) => (
|
||||
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
|
||||
size="large" onClick={() => onClickNavCard(path)}>
|
||||
{t(label)}
|
||||
</CompoundButton>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row-reverse sm:fixed bottom-2 right-2">
|
||||
<div className="flex gap-3">
|
||||
<ResetConfigsButton />
|
||||
<ConfigSelector />
|
||||
<RunButton />
|
||||
<div className="flex flex-col gap-2">
|
||||
<AdvancedGeneralSettings />
|
||||
<div className="flex gap-4 items-end">
|
||||
{t('Version')}: {manifest.version}
|
||||
<Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-end">
|
||||
{t('Version')}: {manifest.version}
|
||||
<Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link>
|
||||
</div>
|
||||
)
|
||||
: (
|
||||
<div className="flex flex-col justify-between h-full">
|
||||
<img className="rounded-xl select-none object-cover hidden sm:block"
|
||||
style={{ maxHeight: '40%' }} src={banner} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<Text size={600} weight="medium">{t('Introduction')}</Text>
|
||||
<div className="h-40 overflow-y-auto overflow-x-hidden p-1">
|
||||
<LazyImportComponent lazyChildren={MarkdownRender}>
|
||||
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']}
|
||||
</LazyImportComponent>
|
||||
</div>
|
||||
</div>
|
||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
|
||||
{clientNavCards.map(({ label, path, icon, desc }, index) => (
|
||||
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
|
||||
size="large" onClick={() => onClickNavCard(path)}>
|
||||
{t(label)}
|
||||
</CompoundButton>
|
||||
))}
|
||||
</div>
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-row-reverse sm:fixed bottom-2 right-2">
|
||||
<div className="flex gap-3">
|
||||
<ResetConfigsButton />
|
||||
<ConfigSelector />
|
||||
<RunButton />
|
||||
</div>
|
||||
</div>
|
||||
<div className="flex gap-4 items-end">
|
||||
{t('Version')}: {manifest.version}
|
||||
<Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
);
|
||||
});
|
||||
|
||||
export default Home;
|
||||
|
@ -22,21 +22,7 @@ import { Page } from '../components/Page';
|
||||
import { bytesToGb, refreshModels, saveConfigs, toastWithButton } from '../utils';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { useNavigate } from 'react-router';
|
||||
|
||||
export type ModelSourceItem = {
|
||||
name: string;
|
||||
size: number;
|
||||
lastUpdated: string;
|
||||
desc?: { [lang: string]: string | undefined; };
|
||||
SHA256?: string;
|
||||
url?: string;
|
||||
downloadUrl?: string;
|
||||
isComplete?: boolean;
|
||||
isLocal?: boolean;
|
||||
localSize?: number;
|
||||
lastUpdatedMs?: number;
|
||||
hide?: boolean;
|
||||
};
|
||||
import { ModelSourceItem } from '../types/models';
|
||||
|
||||
const columns: TableColumnDefinition<ModelSourceItem>[] = [
|
||||
createTableColumn<ModelSourceItem>({
|
||||
@ -165,7 +151,7 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
|
||||
})
|
||||
];
|
||||
|
||||
export const Models: FC = observer(() => {
|
||||
const Models: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return (
|
||||
@ -220,3 +206,5 @@ export const Models: FC = observer(() => {
|
||||
} />
|
||||
);
|
||||
});
|
||||
|
||||
export default Models;
|
||||
|
@ -1,14 +1,14 @@
|
||||
import React, { FC, useState } from 'react';
|
||||
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||
import commonStore from '../../stores/commonStore';
|
||||
import { Preset } from './PresetsButton';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { Button, Card, Dropdown, Option, Textarea } from '@fluentui/react-components';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { ToolTipButton } from '../../components/ToolTipButton';
|
||||
import { Delete20Regular, ReOrderDotsVertical20Regular } from '@fluentui/react-icons';
|
||||
import { ConversationMessage, Role } from '../Chat';
|
||||
import { Preset } from '../../types/presets';
|
||||
import { ConversationMessage, Role } from '../../types/chat';
|
||||
|
||||
type Item = {
|
||||
id: string;
|
||||
@ -31,7 +31,7 @@ const reorder = (list: Item[], startIndex: number, endIndex: number) => {
|
||||
return result;
|
||||
};
|
||||
|
||||
export const MessagesEditor: FC = observer(() => {
|
||||
const MessagesEditor: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
const editingPreset = commonStore.editingPreset!;
|
||||
@ -152,3 +152,5 @@ export const MessagesEditor: FC = observer(() => {
|
||||
</div>
|
||||
);
|
||||
});
|
||||
|
||||
export default MessagesEditor;
|
||||
|
@ -1,6 +1,6 @@
|
||||
// TODO refactor
|
||||
|
||||
import React, { FC, PropsWithChildren, ReactElement, useState } from 'react';
|
||||
import React, { FC, lazy, PropsWithChildren, ReactElement, useState } from 'react';
|
||||
import {
|
||||
Button,
|
||||
Dialog,
|
||||
@ -25,44 +25,21 @@ import {
|
||||
} from '@fluentui/react-icons';
|
||||
import { ToolTipButton } from '../../components/ToolTipButton';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { botName, Conversation, ConversationMessage, MessageType, userName } from '../Chat';
|
||||
import { SelectTabEventHandler } from '@fluentui/react-tabs';
|
||||
import { Labeled } from '../../components/Labeled';
|
||||
import commonStore from '../../stores/commonStore';
|
||||
import logo from '../../assets/images/logo.png';
|
||||
import { observer } from 'mobx-react-lite';
|
||||
import { MessagesEditor } from './MessagesEditor';
|
||||
import { ClipboardGetText, ClipboardSetText } from '../../../wailsjs/runtime';
|
||||
import { toast } from 'react-toastify';
|
||||
import { CustomToastContainer } from '../../components/CustomToastContainer';
|
||||
import { v4 as uuid } from 'uuid';
|
||||
import { absPathAsset } from '../../utils';
|
||||
import { Preset, PresetsNavigationItem } from '../../types/presets';
|
||||
import { botName, Conversation, MessageType, userName } from '../../types/chat';
|
||||
import { LazyImportComponent } from '../../components/LazyImportComponent';
|
||||
|
||||
export type PresetType = 'chat' | 'completion' | 'chatInCompletion'
|
||||
|
||||
export type Preset = {
|
||||
name: string,
|
||||
tag: string,
|
||||
// if name and sourceUrl are same, it will be overridden when importing
|
||||
sourceUrl: string,
|
||||
desc: string,
|
||||
avatarImg: string,
|
||||
type: PresetType,
|
||||
// chat
|
||||
welcomeMessage: string,
|
||||
messages: ConversationMessage[],
|
||||
displayPresetMessages: boolean,
|
||||
// completion
|
||||
prompt: string,
|
||||
stop: string,
|
||||
injectStart: string,
|
||||
injectEnd: string,
|
||||
presystem?: boolean,
|
||||
userName?: string,
|
||||
assistantName?: string
|
||||
}
|
||||
|
||||
export const defaultPreset: Preset = {
|
||||
const defaultPreset: Preset = {
|
||||
name: 'RWKV',
|
||||
tag: 'default',
|
||||
sourceUrl: '',
|
||||
@ -78,6 +55,8 @@ export const defaultPreset: Preset = {
|
||||
injectEnd: ''
|
||||
};
|
||||
|
||||
const MessagesEditor = lazy(() => import('./MessagesEditor'));
|
||||
|
||||
const setActivePreset = (preset: Preset) => {
|
||||
commonStore.setActivePreset(preset);
|
||||
//TODO if (preset.displayPresetMessages) {
|
||||
@ -101,7 +80,7 @@ const setActivePreset = (preset: Preset) => {
|
||||
//}
|
||||
};
|
||||
|
||||
export const PresetCardFrame: FC<PropsWithChildren & { onClick?: () => void }> = (props) => {
|
||||
const PresetCardFrame: FC<PropsWithChildren & { onClick?: () => void }> = (props) => {
|
||||
return <Button
|
||||
className="flex flex-col gap-1 w-32 h-56 break-all"
|
||||
style={{ minWidth: 0, borderRadius: '0.75rem', justifyContent: 'unset' }}
|
||||
@ -111,7 +90,7 @@ export const PresetCardFrame: FC<PropsWithChildren & { onClick?: () => void }> =
|
||||
</Button>;
|
||||
};
|
||||
|
||||
export const PresetCard: FC<{
|
||||
const PresetCard: FC<{
|
||||
avatarImg: string,
|
||||
name: string,
|
||||
desc: string,
|
||||
@ -147,7 +126,7 @@ export const PresetCard: FC<{
|
||||
</PresetCardFrame>;
|
||||
});
|
||||
|
||||
export const ChatPresetEditor: FC<{
|
||||
const ChatPresetEditor: FC<{
|
||||
triggerButton: ReactElement,
|
||||
presetIndex: number
|
||||
}> = observer(({ triggerButton, presetIndex }) => {
|
||||
@ -291,7 +270,7 @@ export const ChatPresetEditor: FC<{
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<MessagesEditor />
|
||||
<LazyImportComponent lazyChildren={MessagesEditor} />
|
||||
</div> :
|
||||
<div className="flex flex-col gap-1 p-2 overflow-x-hidden overflow-y-auto">
|
||||
<Labeled flex breakline label={`${t('Description')} (${t('Preview Only')})`}
|
||||
@ -356,7 +335,7 @@ export const ChatPresetEditor: FC<{
|
||||
</Dialog>;
|
||||
});
|
||||
|
||||
export const ChatPresets: FC = observer(() => {
|
||||
const ChatPresets: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
return <div className="flex flex-wrap gap-2">
|
||||
@ -392,11 +371,6 @@ export const ChatPresets: FC = observer(() => {
|
||||
</div>;
|
||||
});
|
||||
|
||||
type PresetsNavigationItem = {
|
||||
icon: ReactElement;
|
||||
element: ReactElement;
|
||||
};
|
||||
|
||||
const pages: { [label: string]: PresetsNavigationItem } = {
|
||||
Chat: {
|
||||
icon: <Chat20Regular />,
|
||||
@ -412,7 +386,7 @@ const pages: { [label: string]: PresetsNavigationItem } = {
|
||||
}
|
||||
};
|
||||
|
||||
export const PresetsManager: FC<{ initTab: string }> = ({ initTab }) => {
|
||||
const PresetsManager: FC<{ initTab: string }> = ({ initTab }) => {
|
||||
const { t } = useTranslation();
|
||||
const [tab, setTab] = useState(initTab);
|
||||
|
||||
|
@ -16,32 +16,170 @@ import { observer } from 'mobx-react-lite';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { checkUpdate, toastWithButton } from '../utils';
|
||||
import { RestartApp } from '../../wailsjs/go/backend_golang/App';
|
||||
import { Language, Languages } from '../types/settings';
|
||||
|
||||
export const Languages = {
|
||||
dev: 'English', // i18n default
|
||||
zh: '简体中文',
|
||||
ja: '日本語'
|
||||
};
|
||||
export const GeneralSettings: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
export type Language = keyof typeof Languages;
|
||||
return <div className="flex flex-col gap-2">
|
||||
<Labeled label={t('Language')} flex spaceBetween content={
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value={Languages[commonStore.settings.language]}
|
||||
selectedOptions={[commonStore.settings.language]}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
const lang = data.optionValue as Language;
|
||||
commonStore.setSettings({
|
||||
language: lang
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
Object.entries(Languages).map(([langKey, desc]) =>
|
||||
<Option key={langKey} value={langKey}>{desc}</Option>)
|
||||
}
|
||||
</Dropdown>
|
||||
} />
|
||||
{
|
||||
commonStore.platform === 'windows' &&
|
||||
<Labeled label={t('DPI Scaling')} flex spaceBetween content={
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value={commonStore.settings.dpiScaling + '%'}
|
||||
selectedOptions={[commonStore.settings.dpiScaling.toString()]}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
dpiScaling: Number(data.optionValue)
|
||||
});
|
||||
toastWithButton(t('Restart the app to apply DPI Scaling.'), t('Restart'), () => {
|
||||
RestartApp();
|
||||
}, {
|
||||
autoClose: 5000
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
Array.from({ length: 7 }, (_, i) => (i + 2) * 25).map((v, i) =>
|
||||
<Option key={i} value={v.toString()}>{v + '%'}</Option>)
|
||||
}
|
||||
</Dropdown>
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Dark Mode')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.darkMode}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
darkMode: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
</div>;
|
||||
});
|
||||
|
||||
export type SettingsType = {
|
||||
language: Language
|
||||
darkMode: boolean
|
||||
autoUpdatesCheck: boolean
|
||||
giteeUpdatesSource: boolean
|
||||
cnMirror: boolean
|
||||
host: string
|
||||
dpiScaling: number
|
||||
customModelsPath: string
|
||||
customPythonPath: string
|
||||
apiUrl: string
|
||||
apiKey: string
|
||||
apiChatModelName: string
|
||||
apiCompletionModelName: string
|
||||
}
|
||||
export const AdvancedGeneralSettings: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
|
||||
export const Settings: FC = observer(() => {
|
||||
return <div className="flex flex-col gap-2">
|
||||
<Labeled label={'API URL'}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" value={commonStore.settings.apiUrl}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiUrl: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: '33px' }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
commonStore.setSettings({
|
||||
apiUrl: data.optionValue
|
||||
});
|
||||
if (data.optionText === 'OpenAI') {
|
||||
if (commonStore.settings.apiChatModelName === 'rwkv')
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: 'gpt-3.5-turbo'
|
||||
});
|
||||
if (commonStore.settings.apiCompletionModelName === 'rwkv')
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: 'text-davinci-003'
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<Option value="">{t('Localhost')!}</Option>
|
||||
<Option value="https://api.openai.com">OpenAI</Option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
<Labeled label={'API Key'}
|
||||
content={
|
||||
<Input type="password" className="grow" placeholder="sk-" value={commonStore.settings.apiKey}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiKey: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Labeled label={t('API Chat Model Name')}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
|
||||
value={commonStore.settings.apiChatModelName}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: '33px' }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: data.optionValue
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
['rwkv', 'gpt-4', 'gpt-4-0613', 'gpt-4-32k', 'gpt-4-32k-0613', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613']
|
||||
.map((v, i) =>
|
||||
<Option key={i} value={v}>{v}</Option>
|
||||
)
|
||||
}
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
<Labeled label={t('API Completion Model Name')}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
|
||||
value={commonStore.settings.apiCompletionModelName}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: '33px' }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: data.optionValue
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
['rwkv', 'text-davinci-003', 'text-davinci-002', 'text-curie-001', 'text-babbage-001', 'text-ada-001']
|
||||
.map((v, i) =>
|
||||
<Option key={i} value={v}>{v}</Option>
|
||||
)
|
||||
}
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
</div>;
|
||||
});
|
||||
|
||||
const Settings: FC = observer(() => {
|
||||
const { t } = useTranslation();
|
||||
const advancedHeaderRef = useRef<HTMLDivElement>(null);
|
||||
|
||||
@ -53,227 +191,101 @@ export const Settings: FC = observer(() => {
|
||||
return (
|
||||
<Page title={t('Settings')} content={
|
||||
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1">
|
||||
<Labeled label={t('Language')} flex spaceBetween content={
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value={Languages[commonStore.settings.language]}
|
||||
selectedOptions={[commonStore.settings.language]}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
const lang = data.optionValue as Language;
|
||||
commonStore.setSettings({
|
||||
language: lang
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
Object.entries(Languages).map(([langKey, desc]) =>
|
||||
<Option key={langKey} value={langKey}>{desc}</Option>)
|
||||
}
|
||||
</Dropdown>
|
||||
} />
|
||||
{
|
||||
commonStore.platform === 'windows' &&
|
||||
<Labeled label={t('DPI Scaling')} flex spaceBetween content={
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value={commonStore.settings.dpiScaling + '%'}
|
||||
selectedOptions={[commonStore.settings.dpiScaling.toString()]}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
dpiScaling: Number(data.optionValue)
|
||||
});
|
||||
toastWithButton(t('Restart the app to apply DPI Scaling.'), t('Restart'), () => {
|
||||
RestartApp();
|
||||
}, {
|
||||
autoClose: 5000
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
Array.from({ length: 7 }, (_, i) => (i + 2) * 25).map((v, i) =>
|
||||
<Option key={i} value={v.toString()}>{v + '%'}</Option>)
|
||||
}
|
||||
</Dropdown>
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Dark Mode')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.darkMode}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
darkMode: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.autoUpdatesCheck}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
autoUpdatesCheck: data.checked
|
||||
});
|
||||
if (data.checked)
|
||||
checkUpdate(true);
|
||||
}} />
|
||||
} />
|
||||
{
|
||||
commonStore.settings.language === 'zh' &&
|
||||
<Labeled label={t('Use Gitee Updates Source')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.giteeUpdatesSource}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
giteeUpdatesSource: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
{
|
||||
commonStore.settings.language === 'zh' && commonStore.platform !== 'linux' &&
|
||||
<Labeled label={t('Use Tsinghua Pip Mirrors')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.cnMirror}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
cnMirror: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Allow external access to the API (service must be restarted)')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.host !== '127.0.0.1'}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
host: data.checked ? '0.0.0.0' : '127.0.0.1'
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Accordion collapsible openItems={!commonStore.advancedCollapsed && 'advanced'} onToggle={(e, data) => {
|
||||
if (data.value === 'advanced')
|
||||
commonStore.setAdvancedCollapsed(!commonStore.advancedCollapsed);
|
||||
}}>
|
||||
<AccordionItem value="advanced">
|
||||
<AccordionHeader ref={advancedHeaderRef} size="large">{t('Advanced')}</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className="flex flex-col gap-2 overflow-hidden">
|
||||
{commonStore.platform !== 'darwin' &&
|
||||
<Labeled label={t('Custom Models Path')}
|
||||
content={
|
||||
<Input className="grow" placeholder="./models" value={commonStore.settings.customModelsPath}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
customModelsPath: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
|
||||
content={
|
||||
<Input className="grow" placeholder="./py310/python" value={commonStore.settings.customPythonPath}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setDepComplete(false);
|
||||
commonStore.setSettings({
|
||||
customPythonPath: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Labeled label={'API URL'}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" value={commonStore.settings.apiUrl}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiUrl: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
commonStore.setSettings({
|
||||
apiUrl: data.optionValue
|
||||
});
|
||||
if (data.optionText === 'OpenAI') {
|
||||
if (commonStore.settings.apiChatModelName === 'rwkv')
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: 'gpt-3.5-turbo'
|
||||
});
|
||||
if (commonStore.settings.apiCompletionModelName === 'rwkv')
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: 'text-davinci-003'
|
||||
});
|
||||
}
|
||||
}}>
|
||||
<Option value="">{t('Localhost')!}</Option>
|
||||
<Option value="https://api.openai.com">OpenAI</Option>
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
<Labeled label={'API Key'}
|
||||
content={
|
||||
<Input className="grow" placeholder="sk-" value={commonStore.settings.apiKey}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiKey: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Labeled label={t('API Chat Model Name')}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
|
||||
value={commonStore.settings.apiChatModelName}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
apiChatModelName: data.optionValue
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
['rwkv', 'gpt-4', 'gpt-4-0613', 'gpt-4-32k', 'gpt-4-32k-0613', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613']
|
||||
.map((v, i) =>
|
||||
<Option key={i} value={v}>{v}</Option>
|
||||
)
|
||||
}
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
<Labeled label={t('API Completion Model Name')}
|
||||
content={
|
||||
<div className="flex gap-2">
|
||||
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
|
||||
value={commonStore.settings.apiCompletionModelName}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: data.value
|
||||
});
|
||||
}} />
|
||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||
value="..." selectedOptions={[]} expandIcon={null}
|
||||
onOptionSelect={(_, data) => {
|
||||
if (data.optionValue) {
|
||||
commonStore.setSettings({
|
||||
apiCompletionModelName: data.optionValue
|
||||
});
|
||||
}
|
||||
}}>
|
||||
{
|
||||
['rwkv', 'text-davinci-003', 'text-davinci-002', 'text-curie-001', 'text-babbage-001', 'text-ada-001']
|
||||
.map((v, i) =>
|
||||
<Option key={i} value={v}>{v}</Option>
|
||||
)
|
||||
}
|
||||
</Dropdown>
|
||||
</div>
|
||||
} />
|
||||
commonStore.platform === 'web' ?
|
||||
(
|
||||
<div className="flex flex-col gap-2">
|
||||
<GeneralSettings />
|
||||
<AdvancedGeneralSettings />
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
)
|
||||
:
|
||||
(
|
||||
<div className="flex flex-col gap-2">
|
||||
<GeneralSettings />
|
||||
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.autoUpdatesCheck}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
autoUpdatesCheck: data.checked
|
||||
});
|
||||
if (data.checked)
|
||||
checkUpdate(true);
|
||||
}} />
|
||||
} />
|
||||
{
|
||||
commonStore.settings.language === 'zh' &&
|
||||
<Labeled label={t('Use Gitee Updates Source')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.giteeUpdatesSource}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
giteeUpdatesSource: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
{
|
||||
commonStore.settings.language === 'zh' && commonStore.platform !== 'linux' &&
|
||||
<Labeled label={t('Use Tsinghua Pip Mirrors')} flex spaceBetween content={
|
||||
<Switch checked={commonStore.settings.cnMirror}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
cnMirror: data.checked
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Allow external access to the API (service must be restarted)')} flex spaceBetween
|
||||
content={
|
||||
<Switch checked={commonStore.settings.host !== '127.0.0.1'}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
host: data.checked ? '0.0.0.0' : '127.0.0.1'
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<Accordion collapsible openItems={!commonStore.advancedCollapsed && 'advanced'} onToggle={(e, data) => {
|
||||
if (data.value === 'advanced')
|
||||
commonStore.setAdvancedCollapsed(!commonStore.advancedCollapsed);
|
||||
}}>
|
||||
<AccordionItem value="advanced">
|
||||
<AccordionHeader ref={advancedHeaderRef} size="large">{t('Advanced')}</AccordionHeader>
|
||||
<AccordionPanel>
|
||||
<div className="flex flex-col gap-2 overflow-hidden">
|
||||
{commonStore.platform !== 'darwin' &&
|
||||
<Labeled label={t('Custom Models Path')}
|
||||
content={
|
||||
<Input className="grow" placeholder="./models"
|
||||
value={commonStore.settings.customModelsPath}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setSettings({
|
||||
customModelsPath: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
}
|
||||
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
|
||||
content={
|
||||
<Input className="grow" placeholder="./py310/python"
|
||||
value={commonStore.settings.customPythonPath}
|
||||
onChange={(e, data) => {
|
||||
commonStore.setDepComplete(false);
|
||||
commonStore.setSettings({
|
||||
customPythonPath: data.value
|
||||
});
|
||||
}} />
|
||||
} />
|
||||
<AdvancedGeneralSettings />
|
||||
</div>
|
||||
</AccordionPanel>
|
||||
</AccordionItem>
|
||||
</Accordion>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
</div>
|
||||
} />
|
||||
);
|
||||
});
|
||||
|
||||
export default Settings;
|
||||
|
@ -1,4 +1,4 @@
|
||||
import React, { FC, ReactElement, useEffect, useRef, useState } from 'react';
|
||||
import React, { FC, useEffect, useRef, useState } from 'react';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import { Button, Dropdown, Input, Option, Select, Switch, Tab, TabList } from '@fluentui/react-components';
|
||||
import {
|
||||
@ -24,7 +24,6 @@ import { Labeled } from '../components/Labeled';
|
||||
import { ToolTipButton } from '../components/ToolTipButton';
|
||||
import { DataUsageSettings20Regular, Folder20Regular } from '@fluentui/react-icons';
|
||||
import { useNavigate } from 'react-router';
|
||||
import { Precision } from './Configs';
|
||||
import {
|
||||
CategoryScale,
|
||||
Chart as ChartJS,
|
||||
@ -40,6 +39,12 @@ import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
|
||||
import { WindowShow } from '../../wailsjs/runtime';
|
||||
import { t } from 'i18next';
|
||||
import { DialogButton } from '../components/DialogButton';
|
||||
import {
|
||||
DataProcessParameters,
|
||||
LoraFinetuneParameters,
|
||||
LoraFinetunePrecision,
|
||||
TrainNavigationItem
|
||||
} from '../types/train';
|
||||
|
||||
ChartJS.register(
|
||||
CategoryScale,
|
||||
@ -86,39 +91,6 @@ const addLossDataToChart = (epoch: number, loss: number) => {
|
||||
commonStore.setChartData(commonStore.chartData);
|
||||
};
|
||||
|
||||
export type DataProcessParameters = {
|
||||
dataPath: string;
|
||||
vocabPath: string;
|
||||
}
|
||||
|
||||
export type LoraFinetunePrecision = 'bf16' | 'fp16' | 'tf32';
|
||||
|
||||
export type LoraFinetuneParameters = {
|
||||
baseModel: string;
|
||||
ctxLen: number;
|
||||
epochSteps: number;
|
||||
epochCount: number;
|
||||
epochBegin: number;
|
||||
epochSave: number;
|
||||
microBsz: number;
|
||||
accumGradBatches: number;
|
||||
preFfn: boolean;
|
||||
headQk: boolean;
|
||||
lrInit: string;
|
||||
lrFinal: string;
|
||||
warmupSteps: number;
|
||||
beta1: number;
|
||||
beta2: number;
|
||||
adamEps: string;
|
||||
devices: number;
|
||||
precision: LoraFinetunePrecision;
|
||||
gradCp: boolean;
|
||||
loraR: number;
|
||||
loraAlpha: number;
|
||||
loraDropout: number;
|
||||
loraLoad: string
|
||||
}
|
||||
|
||||
const loraFinetuneParametersOptions: Array<[key: keyof LoraFinetuneParameters, type: string, name: string]> = [
|
||||
['devices', 'number', 'Devices'],
|
||||
['precision', 'LoraFinetunePrecision', 'Precision'],
|
||||
@ -568,10 +540,6 @@ const LoraFinetune: FC = observer(() => {
|
||||
);
|
||||
});
|
||||
|
||||
type TrainNavigationItem = {
|
||||
element: ReactElement;
|
||||
};
|
||||
|
||||
const pages: { [label: string]: TrainNavigationItem } = {
|
||||
'LoRA Finetune': {
|
||||
element: <LoraFinetune />
|
||||
@ -582,7 +550,7 @@ const pages: { [label: string]: TrainNavigationItem } = {
|
||||
};
|
||||
|
||||
|
||||
export const Train: FC = () => {
|
||||
const Train: FC = () => {
|
||||
const { t } = useTranslation();
|
||||
const [tab, setTab] = useState('LoRA Finetune');
|
||||
|
||||
@ -607,3 +575,5 @@ export const Train: FC = () => {
|
||||
</div>
|
||||
</div>;
|
||||
};
|
||||
|
||||
export default Train;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import { ModelConfig } from './Configs';
|
||||
import { CompletionPreset } from './Completion';
|
||||
import { CompletionPreset } from '../types/completion';
|
||||
import { ModelConfig } from '../types/configs';
|
||||
|
||||
export const defaultCompositionPrompt = '<pad>';
|
||||
|
||||
|
@ -1,5 +1,4 @@
|
||||
import { ReactElement } from 'react';
|
||||
import { Configs } from './Configs';
|
||||
import { FC, lazy, LazyExoticComponent, ReactElement } from 'react';
|
||||
import {
|
||||
ArrowDownload20Regular,
|
||||
Chat20Regular,
|
||||
@ -12,21 +11,12 @@ import {
|
||||
Settings20Regular,
|
||||
Storage20Regular
|
||||
} from '@fluentui/react-icons';
|
||||
import { Home } from './Home';
|
||||
import { Chat } from './Chat';
|
||||
import { Models } from './Models';
|
||||
import { Train } from './Train';
|
||||
import { Settings } from './Settings';
|
||||
import { About } from './About';
|
||||
import { Downloads } from './Downloads';
|
||||
import { Completion } from './Completion';
|
||||
import { Composition } from './Composition';
|
||||
|
||||
type NavigationItem = {
|
||||
label: string;
|
||||
path: string;
|
||||
icon: ReactElement;
|
||||
element: ReactElement;
|
||||
element: LazyExoticComponent<FC>;
|
||||
top: boolean;
|
||||
};
|
||||
|
||||
@ -35,70 +25,70 @@ export const pages: NavigationItem[] = [
|
||||
label: 'Home',
|
||||
path: '/',
|
||||
icon: <Home20Regular />,
|
||||
element: <Home />,
|
||||
element: lazy(() => import('./Home')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Chat',
|
||||
path: '/chat',
|
||||
icon: <Chat20Regular />,
|
||||
element: <Chat />,
|
||||
element: lazy(() => import('./Chat')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Completion',
|
||||
path: '/completion',
|
||||
icon: <ClipboardEdit20Regular />,
|
||||
element: <Completion />,
|
||||
element: lazy(() => import('./Completion')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Composition',
|
||||
path: '/composition',
|
||||
icon: <MusicNote220Regular />,
|
||||
element: <Composition />,
|
||||
element: lazy(() => import('./Composition')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Configs',
|
||||
path: '/configs',
|
||||
icon: <DocumentSettings20Regular />,
|
||||
element: <Configs />,
|
||||
element: lazy(() => import('./Configs')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Models',
|
||||
path: '/models',
|
||||
icon: <DataUsageSettings20Regular />,
|
||||
element: <Models />,
|
||||
element: lazy(() => import('./Models')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Downloads',
|
||||
path: '/downloads',
|
||||
icon: <ArrowDownload20Regular />,
|
||||
element: <Downloads />,
|
||||
element: lazy(() => import('./Downloads')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Train',
|
||||
path: '/train',
|
||||
icon: <Storage20Regular />,
|
||||
element: <Train />,
|
||||
element: lazy(() => import('./Train')),
|
||||
top: true
|
||||
},
|
||||
{
|
||||
label: 'Settings',
|
||||
path: '/settings',
|
||||
icon: <Settings20Regular />,
|
||||
element: <Settings />,
|
||||
element: lazy(() => import('./Settings')),
|
||||
top: false
|
||||
},
|
||||
{
|
||||
label: 'About',
|
||||
path: '/about',
|
||||
icon: <Info20Regular />,
|
||||
element: <About />,
|
||||
element: lazy(() => import('./About')),
|
||||
top: false
|
||||
}
|
||||
];
|
||||
|
@ -5,39 +5,42 @@ import { getStatus } from './apis';
|
||||
import { EventsOn, WindowSetTitle } from '../wailsjs/runtime';
|
||||
import manifest from '../../manifest.json';
|
||||
import { defaultModelConfigs, defaultModelConfigsMac } from './pages/defaultConfigs';
|
||||
import { Preset } from './pages/PresetsManager/PresetsButton';
|
||||
import { wslHandler } from './pages/Train';
|
||||
import { t } from 'i18next';
|
||||
import { Preset } from './types/presets';
|
||||
|
||||
export async function startup() {
|
||||
downloadProgramFiles();
|
||||
EventsOn('downloadList', (data) => {
|
||||
if (data)
|
||||
commonStore.setDownloadList(data);
|
||||
});
|
||||
EventsOn('wsl', wslHandler);
|
||||
EventsOn('wslerr', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
initLocalModelsNotify();
|
||||
initLoraModels();
|
||||
|
||||
initPresets();
|
||||
|
||||
initHardwareMonitor();
|
||||
|
||||
await GetPlatform().then(p => commonStore.setPlatform(p as Platform));
|
||||
|
||||
if (commonStore.platform !== 'web') {
|
||||
downloadProgramFiles();
|
||||
EventsOn('downloadList', (data) => {
|
||||
if (data)
|
||||
commonStore.setDownloadList(data);
|
||||
});
|
||||
EventsOn('wsl', (await import('./pages/Train')).wslHandler);
|
||||
EventsOn('wslerr', (e) => {
|
||||
console.log(e);
|
||||
});
|
||||
initLocalModelsNotify();
|
||||
initLoraModels();
|
||||
initHardwareMonitor();
|
||||
}
|
||||
|
||||
await initConfig();
|
||||
|
||||
initCache(true).then(initRemoteText); // depends on config customModelsPath
|
||||
if (commonStore.platform !== 'web') {
|
||||
initCache(true).then(initRemoteText); // depends on config customModelsPath
|
||||
|
||||
if (commonStore.settings.autoUpdatesCheck) // depends on config settings
|
||||
checkUpdate();
|
||||
if (commonStore.settings.autoUpdatesCheck) // depends on config settings
|
||||
checkUpdate();
|
||||
|
||||
getStatus(1000).then(status => { // depends on config api port
|
||||
if (status)
|
||||
commonStore.setStatus(status);
|
||||
});
|
||||
getStatus(1000).then(status => { // depends on config api port
|
||||
if (status)
|
||||
commonStore.setStatus(status);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
async function initRemoteText() {
|
||||
@ -88,7 +91,8 @@ async function initCache(initUnfinishedModels: boolean) {
|
||||
|
||||
async function initPresets() {
|
||||
await ReadJson('presets.json').then((presets: Preset[]) => {
|
||||
commonStore.setPresets(presets, false);
|
||||
if (Array.isArray(presets))
|
||||
commonStore.setPresets(presets, false);
|
||||
}).catch(() => {
|
||||
});
|
||||
}
|
||||
|
@ -2,21 +2,21 @@ import { makeAutoObservable } from 'mobx';
|
||||
import { getUserLanguage, isSystemLightMode, saveCache, saveConfigs, savePresets } from '../utils';
|
||||
import { WindowSetDarkTheme, WindowSetLightTheme } from '../../wailsjs/runtime';
|
||||
import manifest from '../../../manifest.json';
|
||||
import { ModelConfig } from '../pages/Configs';
|
||||
import { Conversation } from '../pages/Chat';
|
||||
import { ModelSourceItem } from '../pages/Models';
|
||||
import { DownloadStatus } from '../pages/Downloads';
|
||||
import { SettingsType } from '../pages/Settings';
|
||||
import { IntroductionContent } from '../pages/Home';
|
||||
import { AboutContent } from '../pages/About';
|
||||
import i18n from 'i18next';
|
||||
import { CompletionPreset } from '../pages/Completion';
|
||||
import { defaultCompositionPrompt, defaultModelConfigs, defaultModelConfigsMac } from '../pages/defaultConfigs';
|
||||
import commonStore from './commonStore';
|
||||
import { Preset } from '../pages/PresetsManager/PresetsButton';
|
||||
import { DataProcessParameters, LoraFinetuneParameters } from '../pages/Train';
|
||||
import { ChartData } from 'chart.js';
|
||||
import { CompositionParams } from '../pages/Composition';
|
||||
import { Preset } from '../types/presets';
|
||||
import { AboutContent } from '../types/about';
|
||||
import { Conversation } from '../types/chat';
|
||||
import { CompletionPreset } from '../types/completion';
|
||||
import { CompositionParams } from '../types/composition';
|
||||
import { ModelConfig } from '../types/configs';
|
||||
import { DownloadStatus } from '../types/downloads';
|
||||
import { IntroductionContent } from '../types/home';
|
||||
import { ModelSourceItem } from '../types/models';
|
||||
import { SettingsType } from '../types/settings';
|
||||
import { DataProcessParameters, LoraFinetuneParameters } from '../types/train';
|
||||
|
||||
export enum ModelStatus {
|
||||
Offline,
|
||||
@ -37,7 +37,7 @@ export type Attachment = {
|
||||
content: string;
|
||||
}
|
||||
|
||||
export type Platform = 'windows' | 'darwin' | 'linux';
|
||||
export type Platform = 'windows' | 'darwin' | 'linux' | 'web';
|
||||
|
||||
class CommonStore {
|
||||
// global
|
||||
@ -135,7 +135,7 @@ class CommonStore {
|
||||
customModelsPath: './models',
|
||||
customPythonPath: '',
|
||||
apiUrl: '',
|
||||
apiKey: 'sk-',
|
||||
apiKey: '',
|
||||
apiChatModelName: 'rwkv',
|
||||
apiCompletionModelName: 'rwkv'
|
||||
};
|
||||
|
1
frontend/src/types/about.ts
Normal file
1
frontend/src/types/about.ts
Normal file
@ -0,0 +1 @@
|
||||
export type AboutContent = { [lang: string]: string }
|
29
frontend/src/types/chat.ts
Normal file
29
frontend/src/types/chat.ts
Normal file
@ -0,0 +1,29 @@
|
||||
export const userName = 'M E';
|
||||
export const botName = 'A I';
|
||||
export const welcomeUuid = 'welcome';
|
||||
|
||||
export enum MessageType {
|
||||
Normal,
|
||||
Error
|
||||
}
|
||||
|
||||
export type Side = 'left' | 'right'
|
||||
export type Color = 'neutral' | 'brand' | 'colorful'
|
||||
export type MessageItem = {
|
||||
sender: string,
|
||||
type: MessageType,
|
||||
color: Color,
|
||||
avatarImg?: string,
|
||||
time: string,
|
||||
content: string,
|
||||
side: Side,
|
||||
done: boolean
|
||||
}
|
||||
export type Conversation = {
|
||||
[uuid: string]: MessageItem
|
||||
}
|
||||
export type Role = 'assistant' | 'user' | 'system';
|
||||
export type ConversationMessage = {
|
||||
role: Role;
|
||||
content: string;
|
||||
}
|
12
frontend/src/types/completion.ts
Normal file
12
frontend/src/types/completion.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { ApiParameters } from './configs';
|
||||
|
||||
export type CompletionParams = Omit<ApiParameters, 'apiPort'> & {
|
||||
stop: string,
|
||||
injectStart: string,
|
||||
injectEnd: string
|
||||
};
|
||||
export type CompletionPreset = {
|
||||
name: string,
|
||||
prompt: string,
|
||||
params: CompletionParams
|
||||
}
|
12
frontend/src/types/composition.ts
Normal file
12
frontend/src/types/composition.ts
Normal file
@ -0,0 +1,12 @@
|
||||
import { NoteSequence } from '@magenta/music/esm/protobuf';
|
||||
|
||||
export type CompositionParams = {
|
||||
prompt: string,
|
||||
maxResponseToken: number,
|
||||
temperature: number,
|
||||
topP: number,
|
||||
autoPlay: boolean,
|
||||
useLocalSoundFont: boolean,
|
||||
midi: ArrayBuffer | null,
|
||||
ns: NoteSequence | null
|
||||
}
|
28
frontend/src/types/configs.ts
Normal file
28
frontend/src/types/configs.ts
Normal file
@ -0,0 +1,28 @@
|
||||
export type ApiParameters = {
|
||||
apiPort: number
|
||||
maxResponseToken: number;
|
||||
temperature: number;
|
||||
topP: number;
|
||||
presencePenalty: number;
|
||||
frequencyPenalty: number;
|
||||
}
|
||||
export type Device = 'CPU' | 'CUDA' | 'CUDA-Beta' | 'WebGPU' | 'MPS' | 'Custom';
|
||||
export type Precision = 'fp16' | 'int8' | 'fp32';
|
||||
export type ModelParameters = {
|
||||
// different models can not have the same name
|
||||
modelName: string;
|
||||
device: Device;
|
||||
precision: Precision;
|
||||
storedLayers: number;
|
||||
maxStoredLayers: number;
|
||||
useCustomCuda?: boolean;
|
||||
customStrategy?: string;
|
||||
useCustomTokenizer?: boolean;
|
||||
customTokenizer?: string;
|
||||
}
|
||||
export type ModelConfig = {
|
||||
// different configs can have the same name
|
||||
name: string;
|
||||
apiParameters: ApiParameters
|
||||
modelParameters: ModelParameters
|
||||
}
|
11
frontend/src/types/downloads.ts
Normal file
11
frontend/src/types/downloads.ts
Normal file
@ -0,0 +1,11 @@
|
||||
export type DownloadStatus = {
|
||||
name: string;
|
||||
path: string;
|
||||
url: string;
|
||||
transferred: number;
|
||||
size: number;
|
||||
speed: number;
|
||||
progress: number;
|
||||
downloading: boolean;
|
||||
done: boolean;
|
||||
}
|
11
frontend/src/types/home.ts
Normal file
11
frontend/src/types/home.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export type IntroductionContent = {
|
||||
[lang: string]: string
|
||||
}
|
||||
export type NavCard = {
|
||||
label: string;
|
||||
desc: string;
|
||||
path: string;
|
||||
icon: ReactElement;
|
||||
};
|
14
frontend/src/types/models.ts
Normal file
14
frontend/src/types/models.ts
Normal file
@ -0,0 +1,14 @@
|
||||
export type ModelSourceItem = {
|
||||
name: string;
|
||||
size: number;
|
||||
lastUpdated: string;
|
||||
desc?: { [lang: string]: string | undefined; };
|
||||
SHA256?: string;
|
||||
url?: string;
|
||||
downloadUrl?: string;
|
||||
isComplete?: boolean;
|
||||
isLocal?: boolean;
|
||||
localSize?: number;
|
||||
lastUpdatedMs?: number;
|
||||
hide?: boolean;
|
||||
};
|
30
frontend/src/types/presets.ts
Normal file
30
frontend/src/types/presets.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
import { ConversationMessage } from './chat';
|
||||
|
||||
export type PresetType = 'chat' | 'completion' | 'chatInCompletion'
|
||||
export type Preset = {
|
||||
name: string,
|
||||
tag: string,
|
||||
// if name and sourceUrl are same, it will be overridden when importing
|
||||
sourceUrl: string,
|
||||
desc: string,
|
||||
avatarImg: string,
|
||||
type: PresetType,
|
||||
// chat
|
||||
welcomeMessage: string,
|
||||
messages: ConversationMessage[],
|
||||
displayPresetMessages: boolean,
|
||||
// completion
|
||||
prompt: string,
|
||||
stop: string,
|
||||
injectStart: string,
|
||||
injectEnd: string,
|
||||
presystem?: boolean,
|
||||
userName?: string,
|
||||
assistantName?: string
|
||||
}
|
||||
export type PresetsNavigationItem = {
|
||||
icon: ReactElement;
|
||||
element: ReactElement;
|
||||
};
|
21
frontend/src/types/settings.ts
Normal file
21
frontend/src/types/settings.ts
Normal file
@ -0,0 +1,21 @@
|
||||
export const Languages = {
|
||||
dev: 'English', // i18n default
|
||||
zh: '简体中文',
|
||||
ja: '日本語'
|
||||
};
|
||||
export type Language = keyof typeof Languages;
|
||||
export type SettingsType = {
|
||||
language: Language
|
||||
darkMode: boolean
|
||||
autoUpdatesCheck: boolean
|
||||
giteeUpdatesSource: boolean
|
||||
cnMirror: boolean
|
||||
host: string
|
||||
dpiScaling: number
|
||||
customModelsPath: string
|
||||
customPythonPath: string
|
||||
apiUrl: string
|
||||
apiKey: string
|
||||
apiChatModelName: string
|
||||
apiCompletionModelName: string
|
||||
}
|
35
frontend/src/types/train.ts
Normal file
35
frontend/src/types/train.ts
Normal file
@ -0,0 +1,35 @@
|
||||
import { ReactElement } from 'react';
|
||||
|
||||
export type DataProcessParameters = {
|
||||
dataPath: string;
|
||||
vocabPath: string;
|
||||
}
|
||||
export type LoraFinetunePrecision = 'bf16' | 'fp16' | 'tf32';
|
||||
export type LoraFinetuneParameters = {
|
||||
baseModel: string;
|
||||
ctxLen: number;
|
||||
epochSteps: number;
|
||||
epochCount: number;
|
||||
epochBegin: number;
|
||||
epochSave: number;
|
||||
microBsz: number;
|
||||
accumGradBatches: number;
|
||||
preFfn: boolean;
|
||||
headQk: boolean;
|
||||
lrInit: string;
|
||||
lrFinal: string;
|
||||
warmupSteps: number;
|
||||
beta1: number;
|
||||
beta2: number;
|
||||
adamEps: string;
|
||||
devices: number;
|
||||
precision: LoraFinetunePrecision;
|
||||
gradCp: boolean;
|
||||
loraR: number;
|
||||
loraAlpha: number;
|
||||
loraDropout: number;
|
||||
loraLoad: string
|
||||
}
|
||||
export type TrainNavigationItem = {
|
||||
element: ReactElement;
|
||||
};
|
@ -15,13 +15,13 @@ import { toast } from 'react-toastify';
|
||||
import { t } from 'i18next';
|
||||
import { ToastOptions } from 'react-toastify/dist/types';
|
||||
import { Button } from '@fluentui/react-components';
|
||||
import { Language, Languages, SettingsType } from '../pages/Settings';
|
||||
import { ModelSourceItem } from '../pages/Models';
|
||||
import { ModelConfig, ModelParameters } from '../pages/Configs';
|
||||
import { DownloadStatus } from '../pages/Downloads';
|
||||
import { DataProcessParameters, LoraFinetuneParameters } from '../pages/Train';
|
||||
import { BrowserOpenURL, WindowShow } from '../../wailsjs/runtime';
|
||||
import { NavigateFunction } from 'react-router';
|
||||
import { ModelConfig, ModelParameters } from '../types/configs';
|
||||
import { DownloadStatus } from '../types/downloads';
|
||||
import { ModelSourceItem } from '../types/models';
|
||||
import { Language, Languages, SettingsType } from '../types/settings';
|
||||
import { DataProcessParameters, LoraFinetuneParameters } from '../types/train';
|
||||
|
||||
export type Cache = {
|
||||
version: string
|
||||
@ -290,6 +290,8 @@ export function bytesToReadable(size: number) {
|
||||
}
|
||||
|
||||
export function absPathAsset(path: string) {
|
||||
if (commonStore.platform === 'web')
|
||||
return path;
|
||||
if ((path.length > 0 && path[0] === '/') ||
|
||||
(path.length > 1 && path[1] === ':')) {
|
||||
return '=>' + path;
|
||||
|
157
frontend/src/webWails.js
Normal file
157
frontend/src/webWails.js
Normal file
@ -0,0 +1,157 @@
|
||||
function defineRuntime(name, func) {
|
||||
window.runtime[name] = func
|
||||
}
|
||||
|
||||
function defineApp(name, func) {
|
||||
window.go['backend_golang']['App'][name] = func
|
||||
}
|
||||
|
||||
if (!window.runtime) {
|
||||
window.runtime = {}
|
||||
document.title += ' WebUI'
|
||||
|
||||
// not implemented
|
||||
defineRuntime('EventsOnMultiple', () => {
|
||||
})
|
||||
defineRuntime('WindowSetLightTheme', () => {
|
||||
})
|
||||
defineRuntime('WindowSetDarkTheme', () => {
|
||||
})
|
||||
defineRuntime('WindowShow', () => {
|
||||
})
|
||||
defineRuntime('WindowHide', () => {
|
||||
})
|
||||
|
||||
// implemented
|
||||
defineRuntime('ClipboardGetText', async () => {
|
||||
return await navigator.clipboard.readText()
|
||||
})
|
||||
defineRuntime('ClipboardSetText', async (text) => {
|
||||
await navigator.clipboard.writeText(text)
|
||||
return true
|
||||
})
|
||||
defineRuntime('WindowSetTitle', (title) => {
|
||||
document.title = title
|
||||
})
|
||||
defineRuntime('BrowserOpenURL', (url) => {
|
||||
window.open(url, '_blank', 'noopener, noreferrer')
|
||||
})
|
||||
}
|
||||
|
||||
if (!window.go) {
|
||||
window.go = {}
|
||||
window.go['backend_golang'] = {}
|
||||
window.go['backend_golang']['App'] = {}
|
||||
|
||||
// not implemented
|
||||
defineApp('AddToDownloadList', async () => {
|
||||
})
|
||||
defineApp('ContinueDownload', async () => {
|
||||
})
|
||||
defineApp('ConvertData', async () => {
|
||||
})
|
||||
defineApp('ConvertModel', async () => {
|
||||
})
|
||||
defineApp('ConvertSafetensors', async () => {
|
||||
})
|
||||
defineApp('CopyFile', async () => {
|
||||
})
|
||||
defineApp('DeleteFile', async () => {
|
||||
})
|
||||
defineApp('DepCheck', async () => {
|
||||
})
|
||||
defineApp('DownloadFile', async () => {
|
||||
})
|
||||
defineApp('GetPyError', async () => {
|
||||
})
|
||||
defineApp('InstallPyDep', async () => {
|
||||
})
|
||||
defineApp('IsPortAvailable', async () => {
|
||||
})
|
||||
defineApp('MergeLora', async () => {
|
||||
})
|
||||
defineApp('OpenFileFolder', async () => {
|
||||
})
|
||||
defineApp('PauseDownload', async () => {
|
||||
})
|
||||
defineApp('ReadFileInfo', async () => {
|
||||
})
|
||||
defineApp('RestartApp', async () => {
|
||||
})
|
||||
defineApp('StartServer', async () => {
|
||||
})
|
||||
defineApp('StartWebGPUServer', async () => {
|
||||
})
|
||||
defineApp('UpdateApp', async () => {
|
||||
})
|
||||
defineApp('WslCommand', async () => {
|
||||
})
|
||||
defineApp('WslEnable', async () => {
|
||||
})
|
||||
defineApp('WslInstallUbuntu', async () => {
|
||||
})
|
||||
defineApp('WslIsEnabled', async () => {
|
||||
})
|
||||
defineApp('WslStart', async () => {
|
||||
})
|
||||
defineApp('WslStop', async () => {
|
||||
})
|
||||
|
||||
// implemented
|
||||
defineApp('FileExists', async () => {
|
||||
return false
|
||||
})
|
||||
defineApp('GetPlatform', async () => {
|
||||
return 'web'
|
||||
})
|
||||
defineApp('ListDirFiles', async () => {
|
||||
return []
|
||||
})
|
||||
defineApp('OpenOpenFileDialog', async (filterPattern) => {
|
||||
return new Promise((resolve, reject) => {
|
||||
const input = document.createElement('input')
|
||||
input.type = 'file'
|
||||
input.accept = filterPattern
|
||||
.replaceAll('*.txt', 'text/plain')
|
||||
.replaceAll('*.', 'application/')
|
||||
.replaceAll(';', ',')
|
||||
|
||||
input.onchange = e => {
|
||||
const file = e.target?.files[0]
|
||||
if (file.type === 'text/plain') {
|
||||
const reader = new FileReader()
|
||||
reader.readAsText(file, 'UTF-8')
|
||||
|
||||
reader.onload = readerEvent => {
|
||||
const content = readerEvent.target?.result
|
||||
resolve({
|
||||
blob: file,
|
||||
content: content
|
||||
})
|
||||
}
|
||||
} else {
|
||||
resolve({
|
||||
blob: file
|
||||
})
|
||||
}
|
||||
}
|
||||
input.click()
|
||||
})
|
||||
})
|
||||
defineApp('OpenSaveFileDialog', async (filterPattern, defaultFileName, savedContent) => {
|
||||
const saver = await import('file-saver')
|
||||
saver.saveAs(new Blob([savedContent], { type: 'text/plain;charset=utf-8' }), defaultFileName)
|
||||
return ''
|
||||
})
|
||||
defineApp('OpenSaveFileDialogBytes', async (filterPattern, defaultFileName, savedContent) => {
|
||||
const saver = await import('file-saver')
|
||||
saver.saveAs(new Blob([new Uint8Array(savedContent)], { type: 'octet/stream' }), defaultFileName)
|
||||
return ''
|
||||
})
|
||||
defineApp('ReadJson', async (fileName) => {
|
||||
return JSON.parse(localStorage.getItem(fileName))
|
||||
})
|
||||
defineApp('SaveJson', async (fileName, data) => {
|
||||
localStorage.setItem(fileName, JSON.stringify(data))
|
||||
})
|
||||
}
|
@ -1,6 +1,37 @@
|
||||
import {defineConfig} from 'vite';
|
||||
// @ts-ignore
|
||||
import { dependencies } from './package.json';
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react';
|
||||
import {visualizer} from 'rollup-plugin-visualizer';
|
||||
import { visualizer } from 'rollup-plugin-visualizer';
|
||||
|
||||
// dependencies that exist anywhere
|
||||
const vendor = [
|
||||
'react', 'react-dom', 'react-router', 'react-router-dom',
|
||||
'@fluentui/react-icons',
|
||||
'mobx', 'mobx-react-lite',
|
||||
'i18next', 'react-i18next',
|
||||
'usehooks-ts', 'react-toastify',
|
||||
'classnames'
|
||||
];
|
||||
|
||||
const embedded = [
|
||||
// split @fluentui/react-components by components
|
||||
'@fluentui/react-components',
|
||||
|
||||
// dependencies that exist in single component
|
||||
'react-beautiful-dnd',
|
||||
'@magenta/music', 'html-midi-player',
|
||||
'react-markdown', 'rehype-highlight', 'rehype-raw', 'remark-breaks', 'remark-gfm'
|
||||
];
|
||||
|
||||
function renderChunks(deps: Record<string, string>) {
|
||||
let chunks = {};
|
||||
Object.keys(deps).forEach((key) => {
|
||||
if ([...vendor, ...embedded].includes(key)) return;
|
||||
chunks[key] = [key];
|
||||
});
|
||||
return chunks;
|
||||
}
|
||||
|
||||
// https://vitejs.dev/config/
|
||||
export default defineConfig({
|
||||
@ -9,5 +40,16 @@ export default defineConfig({
|
||||
template: 'treemap',
|
||||
gzipSize: true,
|
||||
brotliSize: true
|
||||
})]
|
||||
})],
|
||||
build: {
|
||||
chunkSizeWarningLimit: 3000,
|
||||
rollupOptions: {
|
||||
output: {
|
||||
manualChunks: {
|
||||
vendor,
|
||||
...renderChunks(dependencies)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
Loading…
Reference in New Issue
Block a user