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
|
@echo ---- build for linux
|
||||||
wails build -upx -ldflags "-s -w" -platform linux/amd64
|
wails build -upx -ldflags "-s -w" -platform linux/amd64
|
||||||
|
|
||||||
|
build-web:
|
||||||
|
@echo ---- build for web
|
||||||
|
cd frontend && npm run build
|
||||||
|
|
||||||
dev:
|
dev:
|
||||||
wails dev
|
wails dev
|
||||||
|
|
||||||
|
dev-web:
|
||||||
|
cd frontend && npm run dev
|
||||||
|
|
||||||
|
preview:
|
||||||
|
cd frontend && npm run preview
|
||||||
|
|
||||||
|
@ -4,6 +4,7 @@
|
|||||||
<meta charset="UTF-8" />
|
<meta charset="UTF-8" />
|
||||||
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
<meta content="width=device-width, initial-scale=1.0" name="viewport" />
|
||||||
<title>RWKV-Runner</title>
|
<title>RWKV-Runner</title>
|
||||||
|
<link href="./src/assets/images/logo.png" rel="icon" type="image/x-icon">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id="root"></div>
|
<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",
|
"@primer/octicons-react": "^19.1.0",
|
||||||
"chart.js": "^4.3.0",
|
"chart.js": "^4.3.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"github-markdown-css": "^5.2.0",
|
"github-markdown-css": "^5.2.0",
|
||||||
"html-midi-player": "^1.5.0",
|
"html-midi-player": "^1.5.0",
|
||||||
"i18next": "^22.4.15",
|
"i18next": "^22.4.15",
|
||||||
@ -37,6 +38,7 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/react": "^18.2.6",
|
"@types/react": "^18.2.6",
|
||||||
"@types/react-beautiful-dnd": "^13.1.4",
|
"@types/react-beautiful-dnd": "^13.1.4",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
@ -74,12 +76,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/code-frame": {
|
"node_modules/@babel/code-frame": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.13",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.13.tgz",
|
||||||
"integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==",
|
"integrity": "sha512-XktuhWlJ5g+3TJXc5upd9Ks1HutSArik6jf2eAjYFyIOf4ej3RN+184cZbzDvbPnuTJIUhPKKJE3cIsYTiAT3w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/highlight": "^7.22.5"
|
"@babel/highlight": "^7.22.13",
|
||||||
|
"chalk": "^2.4.2"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -125,12 +128,12 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/generator": {
|
"node_modules/@babel/generator": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.23.0.tgz",
|
||||||
"integrity": "sha512-+lcUbnTRhd0jOewtFSedLyiPsD5tswKkbgcezOqqWFUVNEwoUTlpPOBmvhG7OXWLR4jMdv0czPGH5XbflnD1EA==",
|
"integrity": "sha512-lN85QRR+5IbYrMWM6Y4pE/noaQtg4pNiqeNGX60eqOfo6gtEj6uw/JagelB8vVztSd7R6M5n1+PQkDbHbBRU4g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.22.5",
|
"@babel/types": "^7.23.0",
|
||||||
"@jridgewell/gen-mapping": "^0.3.2",
|
"@jridgewell/gen-mapping": "^0.3.2",
|
||||||
"@jridgewell/trace-mapping": "^0.3.17",
|
"@jridgewell/trace-mapping": "^0.3.17",
|
||||||
"jsesc": "^2.5.1"
|
"jsesc": "^2.5.1"
|
||||||
@ -159,22 +162,22 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-environment-visitor": {
|
"node_modules/@babel/helper-environment-visitor": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.20",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.22.20.tgz",
|
||||||
"integrity": "sha512-XGmhECfVA/5sAt+H+xpSg0mfrHq6FzNr9Oxh7PSEBBRUb/mL7Kz3NICXb194rCqAEdxkhPT1a88teizAFyvk8Q==",
|
"integrity": "sha512-zfedSIzFhat/gFhWfHtgWvlec0nqB9YEIVrpuwjruLlXfUSnA8cJB0miHKwqDnQ7d32aKo2xt88/xZptwxbfhA==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-function-name": {
|
"node_modules/@babel/helper-function-name": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.23.0.tgz",
|
||||||
"integrity": "sha512-wtHSq6jMRE3uF2otvfuD3DIvVhOsSNshQl0Qrd7qC9oQJzHvOL4qQXlQn2916+CXGywIjpGuIkoyZRRxHPiNQQ==",
|
"integrity": "sha512-OErEqsrxjZTJciZ4Oo+eoZqeW9UIiOcuYKRJA4ZAgV9myA+pOXhhmpfNCKjEH/auVfEYVFJ6y1Tc4r0eIApqiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/template": "^7.22.5",
|
"@babel/template": "^7.22.15",
|
||||||
"@babel/types": "^7.22.5"
|
"@babel/types": "^7.23.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -245,9 +248,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-split-export-declaration": {
|
"node_modules/@babel/helper-split-export-declaration": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.6",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.22.6.tgz",
|
||||||
"integrity": "sha512-thqK5QFghPKWLhAV321lxF95yCg2K3Ob5yw+M3VHWfdia0IkPXUtoLH8x/6Fh486QUvzhb8YOWHChTVen2/PoQ==",
|
"integrity": "sha512-AsUnxuLhRYsisFiaJwvp1QF+I3KjD5FOxut14q/GzovUe6orHLesW2C7d754kRm53h5gqrz6sFl6sxc4BVtE/g==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/types": "^7.22.5"
|
"@babel/types": "^7.22.5"
|
||||||
@ -266,9 +269,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/helper-validator-identifier": {
|
"node_modules/@babel/helper-validator-identifier": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.20",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
|
||||||
"integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==",
|
"integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
@ -298,13 +301,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/highlight": {
|
"node_modules/@babel/highlight": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.20",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.20.tgz",
|
||||||
"integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==",
|
"integrity": "sha512-dkdMCN3py0+ksCgYmGG8jKeGA/8Tk+gJwSYYlFGxG5lmhfKNoAy004YpLxpS1W2J8m/EK2Ew+yOs9pVRwO89mg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-validator-identifier": "^7.22.5",
|
"@babel/helper-validator-identifier": "^7.22.20",
|
||||||
"chalk": "^2.0.0",
|
"chalk": "^2.4.2",
|
||||||
"js-tokens": "^4.0.0"
|
"js-tokens": "^4.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -312,9 +315,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/parser": {
|
"node_modules/@babel/parser": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.23.0.tgz",
|
||||||
"integrity": "sha512-DFZMC9LJUG9PLOclRC32G63UXwzqS2koQC8dkx+PLdmt1xSePYpbT/NbsrJy8Q/muXz7o/h/d4A7Fuyixm559Q==",
|
"integrity": "sha512-vvPKKdMemU85V9WE/l5wZEmImpCtLqbnTvqDS2U1fJ96KrxoW7KrXhNsNCblQlg8Ck4b85yxdTyelsMUgFUXiw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"bin": {
|
"bin": {
|
||||||
"parser": "bin/babel-parser.js"
|
"parser": "bin/babel-parser.js"
|
||||||
@ -365,33 +368,33 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/template": {
|
"node_modules/@babel/template": {
|
||||||
"version": "7.22.5",
|
"version": "7.22.15",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/template/-/template-7.22.15.tgz",
|
||||||
"integrity": "sha512-X7yV7eiwAxdj9k94NEylvbVHLiVG1nvzCV2EAowhxLTwODV1jl9UzZ48leOC0sH7OnuHrIkllaBgneUykIcZaw==",
|
"integrity": "sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.22.5",
|
"@babel/code-frame": "^7.22.13",
|
||||||
"@babel/parser": "^7.22.5",
|
"@babel/parser": "^7.22.15",
|
||||||
"@babel/types": "^7.22.5"
|
"@babel/types": "^7.22.15"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=6.9.0"
|
"node": ">=6.9.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/traverse": {
|
"node_modules/@babel/traverse": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.2",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.23.2.tgz",
|
||||||
"integrity": "sha512-7DuIjPgERaNo6r+PZwItpjCZEa5vyw4eJGufeLxrPdBXBoLcCJCIasvK6pK/9DVNrLZTLFhUGqaC6X/PA007TQ==",
|
"integrity": "sha512-azpe59SQ48qG6nu2CzcMLbxUudtN+dOM9kDbUqGq3HXUJRlo7i8fvPoxQUzYgLZ4cMVmuZgm8vvBpNeRhd6XSw==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/code-frame": "^7.22.5",
|
"@babel/code-frame": "^7.22.13",
|
||||||
"@babel/generator": "^7.22.5",
|
"@babel/generator": "^7.23.0",
|
||||||
"@babel/helper-environment-visitor": "^7.22.5",
|
"@babel/helper-environment-visitor": "^7.22.20",
|
||||||
"@babel/helper-function-name": "^7.22.5",
|
"@babel/helper-function-name": "^7.23.0",
|
||||||
"@babel/helper-hoist-variables": "^7.22.5",
|
"@babel/helper-hoist-variables": "^7.22.5",
|
||||||
"@babel/helper-split-export-declaration": "^7.22.5",
|
"@babel/helper-split-export-declaration": "^7.22.6",
|
||||||
"@babel/parser": "^7.22.5",
|
"@babel/parser": "^7.23.0",
|
||||||
"@babel/types": "^7.22.5",
|
"@babel/types": "^7.23.0",
|
||||||
"debug": "^4.1.0",
|
"debug": "^4.1.0",
|
||||||
"globals": "^11.1.0"
|
"globals": "^11.1.0"
|
||||||
},
|
},
|
||||||
@ -400,13 +403,13 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@babel/types": {
|
"node_modules/@babel/types": {
|
||||||
"version": "7.22.5",
|
"version": "7.23.0",
|
||||||
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.22.5.tgz",
|
"resolved": "https://registry.npmjs.org/@babel/types/-/types-7.23.0.tgz",
|
||||||
"integrity": "sha512-zo3MIHGOkPOfoRXitsgHLjEXmlDaD/5KU1Uzuc9GNiZPhSqVxVRtxuPaSBZDsYZ9qV88AjtMtWW7ww98loJ9KA==",
|
"integrity": "sha512-0oIyUfKoI3mSqMvsxBdclDwxXKXAUA8v/apZbc+iSyARYou1o8ZGDxbUYyLFoW2arqS2jDGqJuZvv1d/io1axg==",
|
||||||
"dev": true,
|
"dev": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@babel/helper-string-parser": "^7.22.5",
|
"@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"
|
"to-fast-properties": "^2.0.0"
|
||||||
},
|
},
|
||||||
"engines": {
|
"engines": {
|
||||||
@ -2279,6 +2282,12 @@
|
|||||||
"@types/ms": "*"
|
"@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": {
|
"node_modules/@types/hast": {
|
||||||
"version": "2.3.4",
|
"version": "2.3.4",
|
||||||
"resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz",
|
"resolved": "https://registry.npmmirror.com/@types/hast/-/hast-2.3.4.tgz",
|
||||||
@ -2288,9 +2297,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/hoist-non-react-statics": {
|
"node_modules/@types/hoist-non-react-statics": {
|
||||||
"version": "3.3.1",
|
"version": "3.3.5",
|
||||||
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/@types/hoist-non-react-statics/-/hoist-non-react-statics-3.3.5.tgz",
|
||||||
"integrity": "sha512-iMIqiko6ooLrTh1joXodJK5X9xeEALT1kM5G3ZLhD3hszxBdIEd5C75U834D9mLcINgD4OyZf5uQXjkuYydWvA==",
|
"integrity": "sha512-SbcrWzkKBw2cdwRTwQAswfpB9g9LJWfjtUeW/jvNwbhC8cpmmNYVePa+ncbUe0rGTQ7G3Ff6mYUN2VMfLVr+Sg==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
"hoist-non-react-statics": "^3.3.0"
|
"hoist-non-react-statics": "^3.3.0"
|
||||||
@ -2371,9 +2380,9 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/@types/react-redux": {
|
"node_modules/@types/react-redux": {
|
||||||
"version": "7.1.25",
|
"version": "7.1.29",
|
||||||
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.25.tgz",
|
"resolved": "https://registry.npmjs.org/@types/react-redux/-/react-redux-7.1.29.tgz",
|
||||||
"integrity": "sha512-bAGh4e+w5D8dajd6InASVIyCo4pZLJ66oLb80F9OBLO1gKESbZcRCJpTT6uLXX+HAB57zw1WTdwJdAsewuTweg==",
|
"integrity": "sha512-orHCOWqBBQ1LP1uD6JVdXL+ZRTEWhGGne+VOPcXef03rC+QYdzktLhxR3ozymPDyZK0CNCUuQs9tyQhfg1ku+w==",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@types/hoist-non-react-statics": "^3.3.0",
|
"@types/hoist-non-react-statics": "^3.3.0",
|
||||||
"@types/react": "*",
|
"@types/react": "*",
|
||||||
@ -2649,10 +2658,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/caniuse-lite": {
|
"node_modules/caniuse-lite": {
|
||||||
"version": "1.0.30001482",
|
"version": "1.0.30001561",
|
||||||
"resolved": "https://registry.npmmirror.com/caniuse-lite/-/caniuse-lite-1.0.30001482.tgz",
|
"resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001561.tgz",
|
||||||
"integrity": "sha512-F1ZInsg53cegyjroxLNW9DmrEQ1SuGRTO1QlpA0o2/6OpQ0gFeDRoq1yFmnr8Sakn9qwwt9DmbxHB6w167OSuQ==",
|
"integrity": "sha512-NTt0DNoKe958Q0BE0j0c1V9jbUzhBxHIEJy7asmGrpE0yG63KTV7PLHPnK2E1O9RsQrQ081I3NLuXGS6zht3cw==",
|
||||||
"dev": true
|
"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": {
|
"node_modules/ccount": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
@ -3232,6 +3255,11 @@
|
|||||||
"resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz",
|
"resolved": "https://registry.npmjs.org/fft.js/-/fft.js-4.0.4.tgz",
|
||||||
"integrity": "sha512-f9c00hphOgeQTlDyavwTtu6RiK8AIFjD6+jvXkNkpeQ7rirK3uFWVpalkoS4LAwbdX7mfZ8aoBfFVQX1Re/8aw=="
|
"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": {
|
"node_modules/fill-range": {
|
||||||
"version": "7.0.1",
|
"version": "7.0.1",
|
||||||
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
|
"resolved": "https://registry.npmmirror.com/fill-range/-/fill-range-7.0.1.tgz",
|
||||||
@ -4591,10 +4619,24 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"node_modules/postcss": {
|
"node_modules/postcss": {
|
||||||
"version": "8.4.23",
|
"version": "8.4.31",
|
||||||
"resolved": "https://registry.npmmirror.com/postcss/-/postcss-8.4.23.tgz",
|
"resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.31.tgz",
|
||||||
"integrity": "sha512-bQ3qMcpF6A/YjR55xtoTr0jGOlnPOKAIMdOWiv0EIT6HVPEaJiJB4NLljSbiHoC2RX7DN5Uvjtpbg1NPdwv1oA==",
|
"integrity": "sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ==",
|
||||||
"dev": true,
|
"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": {
|
"dependencies": {
|
||||||
"nanoid": "^3.3.6",
|
"nanoid": "^3.3.6",
|
||||||
"picocolors": "^1.0.0",
|
"picocolors": "^1.0.0",
|
||||||
@ -4696,9 +4738,9 @@
|
|||||||
"integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg=="
|
"integrity": "sha512-kma4U7AFCTwpqq5twzC1YVIDXSqg6qQK6JN0smOw8fgRy1OkMi0CYSzFmsy6dnqSenamAtj0CyXMUJ1Mf6oROg=="
|
||||||
},
|
},
|
||||||
"node_modules/protobufjs": {
|
"node_modules/protobufjs": {
|
||||||
"version": "6.11.3",
|
"version": "6.11.4",
|
||||||
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.3.tgz",
|
"resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-6.11.4.tgz",
|
||||||
"integrity": "sha512-xL96WDdCZYdU7Slin569tFX712BxsxslWwAfAhCYjQKGTq7dAU91Lomy6nLLhh/dyGhk/YH4TwTSRxTzhuHyZg==",
|
"integrity": "sha512-5kQWPaJHi1WoCpjTGszzQ32PG2F4+wRY6BmAT4Vfw56Q2FZ4YZzK20xUYQH4YkfehY1e6QSICrJquM6xXZNcrw==",
|
||||||
"hasInstallScript": true,
|
"hasInstallScript": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@protobufjs/aspromise": "^1.1.2",
|
"@protobufjs/aspromise": "^1.1.2",
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
"@primer/octicons-react": "^19.1.0",
|
"@primer/octicons-react": "^19.1.0",
|
||||||
"chart.js": "^4.3.0",
|
"chart.js": "^4.3.0",
|
||||||
"classnames": "^2.3.2",
|
"classnames": "^2.3.2",
|
||||||
|
"file-saver": "^2.0.5",
|
||||||
"github-markdown-css": "^5.2.0",
|
"github-markdown-css": "^5.2.0",
|
||||||
"html-midi-player": "^1.5.0",
|
"html-midi-player": "^1.5.0",
|
||||||
"i18next": "^22.4.15",
|
"i18next": "^22.4.15",
|
||||||
@ -38,6 +39,7 @@
|
|||||||
"uuid": "^9.0.0"
|
"uuid": "^9.0.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
|
"@types/file-saver": "^2.0.7",
|
||||||
"@types/react": "^18.2.6",
|
"@types/react": "^18.2.6",
|
||||||
"@types/react-beautiful-dnd": "^13.1.4",
|
"@types/react-beautiful-dnd": "^13.1.4",
|
||||||
"@types/react-dom": "^18.2.4",
|
"@types/react-dom": "^18.2.4",
|
||||||
|
@ -26,18 +26,22 @@
|
|||||||
import { FluentProvider, Tab, TabList, webDarkTheme, webLightTheme } from '@fluentui/react-components';
|
import { FluentProvider, Tab, TabList, webDarkTheme, webLightTheme } from '@fluentui/react-components';
|
||||||
import { FC, useEffect, useState } from 'react';
|
import { FC, useEffect, useState } from 'react';
|
||||||
import { Route, Routes, useLocation, useNavigate } from 'react-router';
|
import { Route, Routes, useLocation, useNavigate } from 'react-router';
|
||||||
import { pages } from './pages';
|
import { pages as clientPages } from './pages';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
import commonStore from './stores/commonStore';
|
import commonStore from './stores/commonStore';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { CustomToastContainer } from './components/CustomToastContainer';
|
import { CustomToastContainer } from './components/CustomToastContainer';
|
||||||
|
import { LazyImportComponent } from './components/LazyImportComponent';
|
||||||
|
|
||||||
const App: FC = observer(() => {
|
const App: FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const location = useLocation();
|
const location = useLocation();
|
||||||
const mq = useMediaQuery('(min-width: 640px)');
|
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);
|
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">
|
<div className="h-full w-full p-2 box-border overflow-y-hidden">
|
||||||
<Routes>
|
<Routes>
|
||||||
{pages.map(({ path, element }, index) => (
|
{pages.map(({ path, element }, index) => (
|
||||||
<Route key={`${path}-${index}`} path={path} element={element} />
|
<Route key={`${path}-${index}`} path={path} element={<LazyImportComponent lazyChildren={element} />} />
|
||||||
))}
|
))}
|
||||||
</Routes>
|
</Routes>
|
||||||
</div>
|
</div>
|
||||||
|
@ -262,5 +262,7 @@
|
|||||||
"is as follows. When replying to me, consider the file content and respond accordingly:": "の内容は以下の通りです。私に返信する際は、ファイルの内容を考慮して適切に返信してください:",
|
"is as follows. When replying to me, consider the file content and respond accordingly:": "の内容は以下の通りです。私に返信する際は、ファイルの内容を考慮して適切に返信してください:",
|
||||||
"What's the file name": "ファイル名は何ですか",
|
"What's the file name": "ファイル名は何ですか",
|
||||||
"The file name is: ": "ファイル名は次のとおりです: ",
|
"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:": "内容如下。回复时考虑文件内容并做出相应回复:",
|
"is as follows. When replying to me, consider the file content and respond accordingly:": "内容如下。回复时考虑文件内容并做出相应回复:",
|
||||||
"What's the file name": "文件名是什么",
|
"What's the file name": "文件名是什么",
|
||||||
"The file name is: ": "文件名是:",
|
"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 {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -11,7 +11,9 @@ import {
|
|||||||
} from '@fluentui/react-components';
|
} from '@fluentui/react-components';
|
||||||
import { ToolTipButton } from './ToolTipButton';
|
import { ToolTipButton } from './ToolTipButton';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import MarkdownRender from './MarkdownRender';
|
import { LazyImportComponent } from './LazyImportComponent';
|
||||||
|
|
||||||
|
const MarkdownRender = React.lazy(() => import('./MarkdownRender'));
|
||||||
|
|
||||||
export const DialogButton: FC<{
|
export const DialogButton: FC<{
|
||||||
text?: string | null
|
text?: string | null
|
||||||
@ -45,7 +47,9 @@ export const DialogButton: FC<{
|
|||||||
<DialogContent>
|
<DialogContent>
|
||||||
{
|
{
|
||||||
markdown ?
|
markdown ?
|
||||||
<MarkdownRender>{contentText}</MarkdownRender> :
|
<LazyImportComponent lazyChildren={MarkdownRender}>
|
||||||
|
{contentText}
|
||||||
|
</LazyImportComponent> :
|
||||||
contentText
|
contentText
|
||||||
}
|
}
|
||||||
</DialogContent>
|
</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 (
|
return (
|
||||||
<div dir="auto" className="markdown-body">
|
<div dir="auto" className="markdown-body">
|
||||||
<ReactMarkdown
|
<ReactMarkdown
|
||||||
|
@ -16,7 +16,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
import { ToolTipButton } from './ToolTipButton';
|
import { ToolTipButton } from './ToolTipButton';
|
||||||
import { Play16Regular, Stop16Regular } from '@fluentui/react-icons';
|
import { Play16Regular, Stop16Regular } from '@fluentui/react-icons';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { WindowShow } from '../../wailsjs/runtime/runtime';
|
import { WindowShow } from '../../wailsjs/runtime';
|
||||||
|
|
||||||
const mainButtonText = {
|
const mainButtonText = {
|
||||||
[ModelStatus.Offline]: 'Run',
|
[ModelStatus.Offline]: 'Run',
|
||||||
|
@ -25,7 +25,8 @@ export const WorkHeader: FC = observer(() => {
|
|||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
|
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
|
||||||
|
|
||||||
return (
|
return commonStore.platform === 'web' ?
|
||||||
|
<div /> :
|
||||||
<div className="flex flex-col gap-1">
|
<div className="flex flex-col gap-1">
|
||||||
<div className="flex justify-between items-center">
|
<div className="flex justify-between items-center">
|
||||||
<div className="flex items-center gap-2">
|
<div className="flex items-center gap-2">
|
||||||
@ -42,5 +43,5 @@ export const WorkHeader: FC = observer(() => {
|
|||||||
</Text>
|
</Text>
|
||||||
<Divider style={{ flexGrow: 0 }} />
|
<Divider style={{ flexGrow: 0 }} />
|
||||||
</div>
|
</div>
|
||||||
);
|
;
|
||||||
});
|
});
|
@ -1,3 +1,4 @@
|
|||||||
|
import './webWails';
|
||||||
import React from 'react';
|
import React from 'react';
|
||||||
import { createRoot } from 'react-dom/client';
|
import { createRoot } from 'react-dom/client';
|
||||||
import './style.scss';
|
import './style.scss';
|
||||||
@ -6,7 +7,6 @@ import App from './App';
|
|||||||
import { HashRouter } from 'react-router-dom';
|
import { HashRouter } from 'react-router-dom';
|
||||||
import { startup } from './startup';
|
import { startup } from './startup';
|
||||||
import './_locales/i18n-react';
|
import './_locales/i18n-react';
|
||||||
import 'html-midi-player';
|
|
||||||
import { WindowShow } from '../wailsjs/runtime';
|
import { WindowShow } from '../wailsjs/runtime';
|
||||||
|
|
||||||
startup().then(() => {
|
startup().then(() => {
|
||||||
|
@ -5,9 +5,7 @@ import MarkdownRender from '../components/MarkdownRender';
|
|||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import commonStore from '../stores/commonStore';
|
import commonStore from '../stores/commonStore';
|
||||||
|
|
||||||
export type AboutContent = { [lang: string]: string }
|
const About: FC = observer(() => {
|
||||||
|
|
||||||
export const About: FC = observer(() => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const lang: string = commonStore.settings.language;
|
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 { absPathAsset, bytesToReadable, toastWithButton } from '../utils';
|
||||||
import { PresetsButton } from './PresetsManager/PresetsButton';
|
import { PresetsButton } from './PresetsManager/PresetsButton';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import { botName, ConversationMessage, MessageType, userName, welcomeUuid } from '../types/chat';
|
||||||
|
|
||||||
export const userName = 'M E';
|
let chatSseControllers: {
|
||||||
export const botName = 'A I';
|
[id: string]: AbortController
|
||||||
|
} = {};
|
||||||
|
|
||||||
export const welcomeUuid = 'welcome';
|
const MoreUtilsButton: FC<{
|
||||||
|
uuid: string,
|
||||||
export enum MessageType {
|
setEditing: (editing: boolean) => void
|
||||||
Normal,
|
}> = observer(({
|
||||||
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(({
|
|
||||||
uuid,
|
uuid,
|
||||||
setEditing
|
setEditing
|
||||||
}) => {
|
}) => {
|
||||||
@ -98,7 +68,8 @@ const MoreUtilsButton: FC<{ uuid: string, setEditing: (editing: boolean) => void
|
|||||||
});
|
});
|
||||||
|
|
||||||
const ChatMessageItem: FC<{
|
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
|
startUuid: string | null, endUuid: string | null, includeEndUuid: boolean) => void
|
||||||
}> = observer(({ uuid, onSubmit }) => {
|
}> = observer(({ uuid, onSubmit }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@ -243,7 +214,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
color: 'colorful',
|
color: 'colorful',
|
||||||
avatarImg: logo,
|
avatarImg: logo,
|
||||||
time: new Date().toISOString(),
|
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',
|
side: 'left',
|
||||||
done: true
|
done: true
|
||||||
}
|
}
|
||||||
@ -260,7 +231,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (e.type === 'click' || (e.keyCode === 13 && !e.shiftKey)) {
|
if (e.type === 'click' || (e.keyCode === 13 && !e.shiftKey)) {
|
||||||
e.preventDefault();
|
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' });
|
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -464,7 +435,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
: <Attach16Regular />}
|
: <Attach16Regular />}
|
||||||
size="small" shape="circular" appearance="secondary"
|
size="small" shape="circular" appearance="secondary"
|
||||||
onClick={() => {
|
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' });
|
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -478,10 +449,29 @@ const ChatPanel: FC = observer(() => {
|
|||||||
|
|
||||||
commonStore.setAttachmentUploading(true);
|
commonStore.setAttachmentUploading(true);
|
||||||
|
|
||||||
|
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.
|
// 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 = 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());
|
blob = await fetch(absPathAsset(filePath)).then(r => r.blob());
|
||||||
const attachmentName = filePath.split(/[\\/]/).pop();
|
attachmentName = filePath.split(/[\\/]/).pop();
|
||||||
|
}
|
||||||
|
if (attachmentContent) {
|
||||||
|
commonStore.setCurrentTempAttachment(
|
||||||
|
{
|
||||||
|
name: attachmentName!,
|
||||||
|
size: blob.size,
|
||||||
|
content: attachmentContent
|
||||||
|
});
|
||||||
|
commonStore.setAttachmentUploading(false);
|
||||||
|
} else {
|
||||||
const urlPath = `/file-to-text?file_name=${attachmentName}`;
|
const urlPath = `/file-to-text?file_name=${attachmentName}`;
|
||||||
const bodyForm = new FormData();
|
const bodyForm = new FormData();
|
||||||
bodyForm.append('file_data', blob, attachmentName);
|
bodyForm.append('file_data', blob, attachmentName);
|
||||||
@ -493,7 +483,6 @@ const ChatPanel: FC = observer(() => {
|
|||||||
}).then(async r => {
|
}).then(async r => {
|
||||||
if (r.status === 200) {
|
if (r.status === 200) {
|
||||||
const pages = (await r.json()).pages as any[];
|
const pages = (await r.json()).pages as any[];
|
||||||
let attachmentContent: string;
|
|
||||||
if (pages.length === 1)
|
if (pages.length === 1)
|
||||||
attachmentContent = pages[0].page_content;
|
attachmentContent = pages[0].page_content;
|
||||||
else
|
else
|
||||||
@ -502,7 +491,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
{
|
{
|
||||||
name: attachmentName!,
|
name: attachmentName!,
|
||||||
size: blob.size,
|
size: blob.size,
|
||||||
content: attachmentContent
|
content: attachmentContent!
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
toast(r.statusText + '\n' + (await r.text()), {
|
toast(r.statusText + '\n' + (await r.text()), {
|
||||||
@ -515,6 +504,7 @@ const ChatPanel: FC = observer(() => {
|
|||||||
commonStore.setAttachmentUploading(false);
|
commonStore.setAttachmentUploading(false);
|
||||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
||||||
});
|
});
|
||||||
|
}
|
||||||
}).catch(e => {
|
}).catch(e => {
|
||||||
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
|
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 (
|
return (
|
||||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||||
<WorkHeader />
|
<WorkHeader />
|
||||||
@ -594,3 +584,5 @@ export const Chat: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Chat;
|
||||||
|
@ -5,7 +5,6 @@ import { Button, Dropdown, Input, Option, Textarea } from '@fluentui/react-compo
|
|||||||
import { Labeled } from '../components/Labeled';
|
import { Labeled } from '../components/Labeled';
|
||||||
import { ValuedSlider } from '../components/ValuedSlider';
|
import { ValuedSlider } from '../components/ValuedSlider';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ApiParameters } from './Configs';
|
|
||||||
import commonStore, { ModelStatus } from '../stores/commonStore';
|
import commonStore, { ModelStatus } from '../stores/commonStore';
|
||||||
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
import { fetchEventSource } from '@microsoft/fetch-event-source';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
@ -14,18 +13,7 @@ import { PresetsButton } from './PresetsManager/PresetsButton';
|
|||||||
import { ToolTipButton } from '../components/ToolTipButton';
|
import { ToolTipButton } from '../components/ToolTipButton';
|
||||||
import { ArrowSync20Regular } from '@fluentui/react-icons';
|
import { ArrowSync20Regular } from '@fluentui/react-icons';
|
||||||
import { defaultPresets } from './defaultConfigs';
|
import { defaultPresets } from './defaultConfigs';
|
||||||
|
import { CompletionParams, CompletionPreset } from '../types/completion';
|
||||||
export type CompletionParams = Omit<ApiParameters, 'apiPort'> & {
|
|
||||||
stop: string,
|
|
||||||
injectStart: string,
|
|
||||||
injectEnd: string
|
|
||||||
};
|
|
||||||
|
|
||||||
export type CompletionPreset = {
|
|
||||||
name: string,
|
|
||||||
prompt: string,
|
|
||||||
params: CompletionParams
|
|
||||||
}
|
|
||||||
|
|
||||||
let completionSseController: AbortController | null = null;
|
let completionSseController: AbortController | null = null;
|
||||||
|
|
||||||
@ -80,7 +68,7 @@ const CompletionPanel: FC = observer(() => {
|
|||||||
const onSubmit = (prompt: string) => {
|
const onSubmit = (prompt: string) => {
|
||||||
commonStore.setCompletionSubmittedPrompt(prompt);
|
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' });
|
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||||
commonStore.setCompletionGenerating(false);
|
commonStore.setCompletionGenerating(false);
|
||||||
return;
|
return;
|
||||||
@ -269,7 +257,7 @@ const CompletionPanel: FC = observer(() => {
|
|||||||
} />
|
} />
|
||||||
</div>
|
</div>
|
||||||
<div className="grow" />
|
<div className="grow" />
|
||||||
<div className="flex justify-between gap-2">
|
<div className="hidden justify-between gap-2 sm:flex">
|
||||||
<Button className="grow" onClick={() => {
|
<Button className="grow" onClick={() => {
|
||||||
const newPrompt = prompt.replace(/\n+\ /g, '\n').split('\n').map((line) => line.trim()).join('\n');
|
const newPrompt = prompt.replace(/\n+\ /g, '\n').split('\n').map((line) => line.trim()).join('\n');
|
||||||
setPrompt(newPrompt);
|
setPrompt(newPrompt);
|
||||||
@ -303,7 +291,7 @@ const CompletionPanel: FC = observer(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Completion: FC = observer(() => {
|
const Completion: FC = observer(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||||
<WorkHeader />
|
<WorkHeader />
|
||||||
@ -311,3 +299,5 @@ export const Completion: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Completion;
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
import 'html-midi-player';
|
||||||
import React, { FC, useEffect, useRef } from 'react';
|
import React, { FC, useEffect, useRef } from 'react';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { WorkHeader } from '../components/WorkHeader';
|
import { WorkHeader } from '../components/WorkHeader';
|
||||||
@ -17,17 +18,7 @@ import { NoteSequence } from '@magenta/music/esm/protobuf.js';
|
|||||||
import { defaultCompositionPrompt } from './defaultConfigs';
|
import { defaultCompositionPrompt } from './defaultConfigs';
|
||||||
import { FileExists, OpenFileFolder, OpenSaveFileDialogBytes } from '../../wailsjs/go/backend_golang/App';
|
import { FileExists, OpenFileFolder, OpenSaveFileDialogBytes } from '../../wailsjs/go/backend_golang/App';
|
||||||
import { toastWithButton } from '../utils';
|
import { toastWithButton } from '../utils';
|
||||||
|
import { CompositionParams } from '../types/composition';
|
||||||
export type CompositionParams = {
|
|
||||||
prompt: string,
|
|
||||||
maxResponseToken: number,
|
|
||||||
temperature: number,
|
|
||||||
topP: number,
|
|
||||||
autoPlay: boolean,
|
|
||||||
useLocalSoundFont: boolean,
|
|
||||||
midi: ArrayBuffer | null,
|
|
||||||
ns: NoteSequence | null
|
|
||||||
}
|
|
||||||
|
|
||||||
let compositionSseController: AbortController | null = null;
|
let compositionSseController: AbortController | null = null;
|
||||||
|
|
||||||
@ -137,7 +128,7 @@ const CompositionPanel: FC = observer(() => {
|
|||||||
const onSubmit = (prompt: string) => {
|
const onSubmit = (prompt: string) => {
|
||||||
commonStore.setCompositionSubmittedPrompt(prompt);
|
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' });
|
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
|
||||||
commonStore.setCompositionGenerating(false);
|
commonStore.setCompositionGenerating(false);
|
||||||
return;
|
return;
|
||||||
@ -335,7 +326,7 @@ const CompositionPanel: FC = observer(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
export const Composition: FC = observer(() => {
|
const Composition: FC = observer(() => {
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
<div className="flex flex-col gap-1 p-2 h-full overflow-hidden">
|
||||||
<WorkHeader />
|
<WorkHeader />
|
||||||
@ -343,3 +334,5 @@ export const Composition: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Composition;
|
||||||
|
@ -29,45 +29,14 @@ import { updateConfig } from '../apis';
|
|||||||
import { ConvertModel, ConvertSafetensors, FileExists, GetPyError } from '../../wailsjs/go/backend_golang/App';
|
import { ConvertModel, ConvertSafetensors, FileExists, GetPyError } from '../../wailsjs/go/backend_golang/App';
|
||||||
import { checkDependencies, getStrategy } from '../utils';
|
import { checkDependencies, getStrategy } from '../utils';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { WindowShow } from '../../wailsjs/runtime/runtime';
|
import { WindowShow } from '../../wailsjs/runtime';
|
||||||
import strategyImg from '../assets/images/strategy.jpg';
|
import strategyImg from '../assets/images/strategy.jpg';
|
||||||
import strategyZhImg from '../assets/images/strategy_zh.jpg';
|
import strategyZhImg from '../assets/images/strategy_zh.jpg';
|
||||||
import { ResetConfigsButton } from '../components/ResetConfigsButton';
|
import { ResetConfigsButton } from '../components/ResetConfigsButton';
|
||||||
import { useMediaQuery } from 'usehooks-ts';
|
import { useMediaQuery } from 'usehooks-ts';
|
||||||
|
import { ApiParameters, Device, ModelParameters, Precision } from '../types/configs';
|
||||||
|
|
||||||
export type ApiParameters = {
|
const Configs: FC = observer(() => {
|
||||||
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 { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [selectedIndex, setSelectedIndex] = React.useState(commonStore.currentModelConfigIndex);
|
const [selectedIndex, setSelectedIndex] = React.useState(commonStore.currentModelConfigIndex);
|
||||||
const [selectedConfig, setSelectedConfig] = React.useState(commonStore.modelConfigs[selectedIndex]);
|
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 { Folder20Regular, Pause20Regular, Play20Regular } from '@fluentui/react-icons';
|
||||||
import { AddToDownloadList, OpenFileFolder, PauseDownload } from '../../wailsjs/go/backend_golang/App';
|
import { AddToDownloadList, OpenFileFolder, PauseDownload } from '../../wailsjs/go/backend_golang/App';
|
||||||
|
|
||||||
export type DownloadStatus = {
|
const Downloads: FC = observer(() => {
|
||||||
name: string;
|
|
||||||
path: string;
|
|
||||||
url: string;
|
|
||||||
transferred: number;
|
|
||||||
size: number;
|
|
||||||
speed: number;
|
|
||||||
progress: number;
|
|
||||||
downloading: boolean;
|
|
||||||
done: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Downloads: FC = observer(() => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const finishedModelsLen = commonStore.downloadList.filter((status) => status.done && status.name.endsWith('.pth')).length;
|
const finishedModelsLen = commonStore.downloadList.filter((status) => status.done && status.name.endsWith('.pth')).length;
|
||||||
useEffect(() => {
|
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 { 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 banner from '../assets/images/banner.jpg';
|
||||||
import {
|
import {
|
||||||
Chat20Regular,
|
Chat20Regular,
|
||||||
ClipboardEdit20Regular,
|
ClipboardEdit20Regular,
|
||||||
DataUsageSettings20Regular,
|
DataUsageSettings20Regular,
|
||||||
DocumentSettings20Regular
|
DocumentSettings20Regular,
|
||||||
|
MusicNote220Regular,
|
||||||
|
Settings20Regular
|
||||||
} from '@fluentui/react-icons';
|
} from '@fluentui/react-icons';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
@ -14,21 +16,13 @@ import manifest from '../../../manifest.json';
|
|||||||
import { BrowserOpenURL } from '../../wailsjs/runtime';
|
import { BrowserOpenURL } from '../../wailsjs/runtime';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ConfigSelector } from '../components/ConfigSelector';
|
import { ConfigSelector } from '../components/ConfigSelector';
|
||||||
import MarkdownRender from '../components/MarkdownRender';
|
|
||||||
import commonStore from '../stores/commonStore';
|
import commonStore from '../stores/commonStore';
|
||||||
import { Completion } from './Completion';
|
|
||||||
import { ResetConfigsButton } from '../components/ResetConfigsButton';
|
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 }
|
const clientNavCards: NavCard[] = [
|
||||||
|
|
||||||
type NavCard = {
|
|
||||||
label: string;
|
|
||||||
desc: string;
|
|
||||||
path: string;
|
|
||||||
icon: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
const navCards: NavCard[] = [
|
|
||||||
{
|
{
|
||||||
label: 'Chat',
|
label: 'Chat',
|
||||||
desc: 'Go to chat page',
|
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 { t } = useTranslation();
|
||||||
const navigate = useNavigate();
|
const navigate = useNavigate();
|
||||||
const lang: string = commonStore.settings.language;
|
const lang: string = commonStore.settings.language;
|
||||||
@ -64,20 +87,43 @@ export const Home: FC = observer(() => {
|
|||||||
navigate({ pathname: path });
|
navigate({ pathname: path });
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
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 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>
|
||||||
|
)
|
||||||
|
: (
|
||||||
<div className="flex flex-col justify-between h-full">
|
<div className="flex flex-col justify-between h-full">
|
||||||
<img className="rounded-xl select-none hidden sm:block"
|
<img className="rounded-xl select-none object-cover hidden sm:block"
|
||||||
style={{ maxHeight: '40%', margin: '0 auto' }} src={banner} />
|
style={{ maxHeight: '40%' }} src={banner} />
|
||||||
<div className="flex flex-col gap-2">
|
<div className="flex flex-col gap-2">
|
||||||
<Text size={600} weight="medium">{t('Introduction')}</Text>
|
<Text size={600} weight="medium">{t('Introduction')}</Text>
|
||||||
<div className="h-40 overflow-y-auto overflow-x-hidden p-1">
|
<div className="h-40 overflow-y-auto overflow-x-hidden p-1">
|
||||||
<MarkdownRender>
|
<LazyImportComponent lazyChildren={MarkdownRender}>
|
||||||
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']}
|
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']}
|
||||||
</MarkdownRender>
|
</LazyImportComponent>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
|
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
|
||||||
{navCards.map(({ label, path, icon, desc }, index) => (
|
{clientNavCards.map(({ label, path, icon, desc }, index) => (
|
||||||
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
|
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
|
||||||
size="large" onClick={() => onClickNavCard(path)}>
|
size="large" onClick={() => onClickNavCard(path)}>
|
||||||
{t(label)}
|
{t(label)}
|
||||||
@ -100,3 +146,5 @@ export const Home: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Home;
|
||||||
|
@ -22,21 +22,7 @@ import { Page } from '../components/Page';
|
|||||||
import { bytesToGb, refreshModels, saveConfigs, toastWithButton } from '../utils';
|
import { bytesToGb, refreshModels, saveConfigs, toastWithButton } from '../utils';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
|
import { ModelSourceItem } from '../types/models';
|
||||||
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;
|
|
||||||
};
|
|
||||||
|
|
||||||
const columns: TableColumnDefinition<ModelSourceItem>[] = [
|
const columns: TableColumnDefinition<ModelSourceItem>[] = [
|
||||||
createTableColumn<ModelSourceItem>({
|
createTableColumn<ModelSourceItem>({
|
||||||
@ -165,7 +151,7 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
|
|||||||
})
|
})
|
||||||
];
|
];
|
||||||
|
|
||||||
export const Models: FC = observer(() => {
|
const Models: FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -220,3 +206,5 @@ export const Models: FC = observer(() => {
|
|||||||
} />
|
} />
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default Models;
|
||||||
|
@ -1,14 +1,14 @@
|
|||||||
import React, { FC, useState } from 'react';
|
import React, { FC, useState } from 'react';
|
||||||
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
|
import { DragDropContext, Draggable, Droppable, DropResult } from 'react-beautiful-dnd';
|
||||||
import commonStore from '../../stores/commonStore';
|
import commonStore from '../../stores/commonStore';
|
||||||
import { Preset } from './PresetsButton';
|
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { Button, Card, Dropdown, Option, Textarea } from '@fluentui/react-components';
|
import { Button, Card, Dropdown, Option, Textarea } from '@fluentui/react-components';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { ToolTipButton } from '../../components/ToolTipButton';
|
import { ToolTipButton } from '../../components/ToolTipButton';
|
||||||
import { Delete20Regular, ReOrderDotsVertical20Regular } from '@fluentui/react-icons';
|
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 = {
|
type Item = {
|
||||||
id: string;
|
id: string;
|
||||||
@ -31,7 +31,7 @@ const reorder = (list: Item[], startIndex: number, endIndex: number) => {
|
|||||||
return result;
|
return result;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const MessagesEditor: FC = observer(() => {
|
const MessagesEditor: FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
const editingPreset = commonStore.editingPreset!;
|
const editingPreset = commonStore.editingPreset!;
|
||||||
@ -152,3 +152,5 @@ export const MessagesEditor: FC = observer(() => {
|
|||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
export default MessagesEditor;
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
// TODO refactor
|
// TODO refactor
|
||||||
|
|
||||||
import React, { FC, PropsWithChildren, ReactElement, useState } from 'react';
|
import React, { FC, lazy, PropsWithChildren, ReactElement, useState } from 'react';
|
||||||
import {
|
import {
|
||||||
Button,
|
Button,
|
||||||
Dialog,
|
Dialog,
|
||||||
@ -25,44 +25,21 @@ import {
|
|||||||
} from '@fluentui/react-icons';
|
} from '@fluentui/react-icons';
|
||||||
import { ToolTipButton } from '../../components/ToolTipButton';
|
import { ToolTipButton } from '../../components/ToolTipButton';
|
||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { botName, Conversation, ConversationMessage, MessageType, userName } from '../Chat';
|
|
||||||
import { SelectTabEventHandler } from '@fluentui/react-tabs';
|
import { SelectTabEventHandler } from '@fluentui/react-tabs';
|
||||||
import { Labeled } from '../../components/Labeled';
|
import { Labeled } from '../../components/Labeled';
|
||||||
import commonStore from '../../stores/commonStore';
|
import commonStore from '../../stores/commonStore';
|
||||||
import logo from '../../assets/images/logo.png';
|
import logo from '../../assets/images/logo.png';
|
||||||
import { observer } from 'mobx-react-lite';
|
import { observer } from 'mobx-react-lite';
|
||||||
import { MessagesEditor } from './MessagesEditor';
|
|
||||||
import { ClipboardGetText, ClipboardSetText } from '../../../wailsjs/runtime';
|
import { ClipboardGetText, ClipboardSetText } from '../../../wailsjs/runtime';
|
||||||
import { toast } from 'react-toastify';
|
import { toast } from 'react-toastify';
|
||||||
import { CustomToastContainer } from '../../components/CustomToastContainer';
|
import { CustomToastContainer } from '../../components/CustomToastContainer';
|
||||||
import { v4 as uuid } from 'uuid';
|
import { v4 as uuid } from 'uuid';
|
||||||
import { absPathAsset } from '../../utils';
|
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'
|
const defaultPreset: Preset = {
|
||||||
|
|
||||||
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 = {
|
|
||||||
name: 'RWKV',
|
name: 'RWKV',
|
||||||
tag: 'default',
|
tag: 'default',
|
||||||
sourceUrl: '',
|
sourceUrl: '',
|
||||||
@ -78,6 +55,8 @@ export const defaultPreset: Preset = {
|
|||||||
injectEnd: ''
|
injectEnd: ''
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const MessagesEditor = lazy(() => import('./MessagesEditor'));
|
||||||
|
|
||||||
const setActivePreset = (preset: Preset) => {
|
const setActivePreset = (preset: Preset) => {
|
||||||
commonStore.setActivePreset(preset);
|
commonStore.setActivePreset(preset);
|
||||||
//TODO if (preset.displayPresetMessages) {
|
//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
|
return <Button
|
||||||
className="flex flex-col gap-1 w-32 h-56 break-all"
|
className="flex flex-col gap-1 w-32 h-56 break-all"
|
||||||
style={{ minWidth: 0, borderRadius: '0.75rem', justifyContent: 'unset' }}
|
style={{ minWidth: 0, borderRadius: '0.75rem', justifyContent: 'unset' }}
|
||||||
@ -111,7 +90,7 @@ export const PresetCardFrame: FC<PropsWithChildren & { onClick?: () => void }> =
|
|||||||
</Button>;
|
</Button>;
|
||||||
};
|
};
|
||||||
|
|
||||||
export const PresetCard: FC<{
|
const PresetCard: FC<{
|
||||||
avatarImg: string,
|
avatarImg: string,
|
||||||
name: string,
|
name: string,
|
||||||
desc: string,
|
desc: string,
|
||||||
@ -147,7 +126,7 @@ export const PresetCard: FC<{
|
|||||||
</PresetCardFrame>;
|
</PresetCardFrame>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ChatPresetEditor: FC<{
|
const ChatPresetEditor: FC<{
|
||||||
triggerButton: ReactElement,
|
triggerButton: ReactElement,
|
||||||
presetIndex: number
|
presetIndex: number
|
||||||
}> = observer(({ triggerButton, presetIndex }) => {
|
}> = observer(({ triggerButton, presetIndex }) => {
|
||||||
@ -291,7 +270,7 @@ export const ChatPresetEditor: FC<{
|
|||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
} />
|
} />
|
||||||
<MessagesEditor />
|
<LazyImportComponent lazyChildren={MessagesEditor} />
|
||||||
</div> :
|
</div> :
|
||||||
<div className="flex flex-col gap-1 p-2 overflow-x-hidden overflow-y-auto">
|
<div className="flex flex-col gap-1 p-2 overflow-x-hidden overflow-y-auto">
|
||||||
<Labeled flex breakline label={`${t('Description')} (${t('Preview Only')})`}
|
<Labeled flex breakline label={`${t('Description')} (${t('Preview Only')})`}
|
||||||
@ -356,7 +335,7 @@ export const ChatPresetEditor: FC<{
|
|||||||
</Dialog>;
|
</Dialog>;
|
||||||
});
|
});
|
||||||
|
|
||||||
export const ChatPresets: FC = observer(() => {
|
const ChatPresets: FC = observer(() => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
|
|
||||||
return <div className="flex flex-wrap gap-2">
|
return <div className="flex flex-wrap gap-2">
|
||||||
@ -392,11 +371,6 @@ export const ChatPresets: FC = observer(() => {
|
|||||||
</div>;
|
</div>;
|
||||||
});
|
});
|
||||||
|
|
||||||
type PresetsNavigationItem = {
|
|
||||||
icon: ReactElement;
|
|
||||||
element: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pages: { [label: string]: PresetsNavigationItem } = {
|
const pages: { [label: string]: PresetsNavigationItem } = {
|
||||||
Chat: {
|
Chat: {
|
||||||
icon: <Chat20Regular />,
|
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 { t } = useTranslation();
|
||||||
const [tab, setTab] = useState(initTab);
|
const [tab, setTab] = useState(initTab);
|
||||||
|
|
||||||
|
@ -16,43 +16,12 @@ import { observer } from 'mobx-react-lite';
|
|||||||
import { useTranslation } from 'react-i18next';
|
import { useTranslation } from 'react-i18next';
|
||||||
import { checkUpdate, toastWithButton } from '../utils';
|
import { checkUpdate, toastWithButton } from '../utils';
|
||||||
import { RestartApp } from '../../wailsjs/go/backend_golang/App';
|
import { RestartApp } from '../../wailsjs/go/backend_golang/App';
|
||||||
|
import { Language, Languages } from '../types/settings';
|
||||||
|
|
||||||
export const Languages = {
|
export const GeneralSettings: FC = observer(() => {
|
||||||
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
|
|
||||||
}
|
|
||||||
|
|
||||||
export const Settings: FC = observer(() => {
|
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const advancedHeaderRef = useRef<HTMLDivElement>(null);
|
|
||||||
|
|
||||||
useEffect(() => {
|
return <div className="flex flex-col gap-2">
|
||||||
if (advancedHeaderRef.current)
|
|
||||||
(advancedHeaderRef.current.firstElementChild as HTMLElement).style.padding = '0';
|
|
||||||
}, []);
|
|
||||||
|
|
||||||
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={
|
<Labeled label={t('Language')} flex spaceBetween content={
|
||||||
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
|
||||||
value={Languages[commonStore.settings.language]}
|
value={Languages[commonStore.settings.language]}
|
||||||
@ -104,6 +73,136 @@ export const Settings: FC = observer(() => {
|
|||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
} />
|
} />
|
||||||
|
</div>;
|
||||||
|
});
|
||||||
|
|
||||||
|
export const AdvancedGeneralSettings: FC = observer(() => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (advancedHeaderRef.current)
|
||||||
|
(advancedHeaderRef.current.firstElementChild as HTMLElement).style.padding = '0';
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Page title={t('Settings')} content={
|
||||||
|
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1">
|
||||||
|
{
|
||||||
|
commonStore.platform === 'web' ?
|
||||||
|
(
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<GeneralSettings />
|
||||||
|
<AdvancedGeneralSettings />
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
:
|
||||||
|
(
|
||||||
|
<div className="flex flex-col gap-2">
|
||||||
|
<GeneralSettings />
|
||||||
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
|
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
|
||||||
<Switch checked={commonStore.settings.autoUpdatesCheck}
|
<Switch checked={commonStore.settings.autoUpdatesCheck}
|
||||||
onChange={(e, data) => {
|
onChange={(e, data) => {
|
||||||
@ -136,7 +235,8 @@ export const Settings: FC = observer(() => {
|
|||||||
}} />
|
}} />
|
||||||
} />
|
} />
|
||||||
}
|
}
|
||||||
<Labeled label={t('Allow external access to the API (service must be restarted)')} flex spaceBetween content={
|
<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'}
|
<Switch checked={commonStore.settings.host !== '127.0.0.1'}
|
||||||
onChange={(e, data) => {
|
onChange={(e, data) => {
|
||||||
commonStore.setSettings({
|
commonStore.setSettings({
|
||||||
@ -155,7 +255,8 @@ export const Settings: FC = observer(() => {
|
|||||||
{commonStore.platform !== 'darwin' &&
|
{commonStore.platform !== 'darwin' &&
|
||||||
<Labeled label={t('Custom Models Path')}
|
<Labeled label={t('Custom Models Path')}
|
||||||
content={
|
content={
|
||||||
<Input className="grow" placeholder="./models" value={commonStore.settings.customModelsPath}
|
<Input className="grow" placeholder="./models"
|
||||||
|
value={commonStore.settings.customModelsPath}
|
||||||
onChange={(e, data) => {
|
onChange={(e, data) => {
|
||||||
commonStore.setSettings({
|
commonStore.setSettings({
|
||||||
customModelsPath: data.value
|
customModelsPath: data.value
|
||||||
@ -165,7 +266,8 @@ export const Settings: FC = observer(() => {
|
|||||||
}
|
}
|
||||||
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
|
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
|
||||||
content={
|
content={
|
||||||
<Input className="grow" placeholder="./py310/python" value={commonStore.settings.customPythonPath}
|
<Input className="grow" placeholder="./py310/python"
|
||||||
|
value={commonStore.settings.customPythonPath}
|
||||||
onChange={(e, data) => {
|
onChange={(e, data) => {
|
||||||
commonStore.setDepComplete(false);
|
commonStore.setDepComplete(false);
|
||||||
commonStore.setSettings({
|
commonStore.setSettings({
|
||||||
@ -173,107 +275,17 @@ export const Settings: FC = observer(() => {
|
|||||||
});
|
});
|
||||||
}} />
|
}} />
|
||||||
} />
|
} />
|
||||||
<Labeled label={'API URL'}
|
<AdvancedGeneralSettings />
|
||||||
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>
|
|
||||||
} />
|
|
||||||
</div>
|
</div>
|
||||||
</AccordionPanel>
|
</AccordionPanel>
|
||||||
</AccordionItem>
|
</AccordionItem>
|
||||||
</Accordion>
|
</Accordion>
|
||||||
</div>
|
</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 { useTranslation } from 'react-i18next';
|
||||||
import { Button, Dropdown, Input, Option, Select, Switch, Tab, TabList } from '@fluentui/react-components';
|
import { Button, Dropdown, Input, Option, Select, Switch, Tab, TabList } from '@fluentui/react-components';
|
||||||
import {
|
import {
|
||||||
@ -24,7 +24,6 @@ import { Labeled } from '../components/Labeled';
|
|||||||
import { ToolTipButton } from '../components/ToolTipButton';
|
import { ToolTipButton } from '../components/ToolTipButton';
|
||||||
import { DataUsageSettings20Regular, Folder20Regular } from '@fluentui/react-icons';
|
import { DataUsageSettings20Regular, Folder20Regular } from '@fluentui/react-icons';
|
||||||
import { useNavigate } from 'react-router';
|
import { useNavigate } from 'react-router';
|
||||||
import { Precision } from './Configs';
|
|
||||||
import {
|
import {
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
Chart as ChartJS,
|
Chart as ChartJS,
|
||||||
@ -40,6 +39,12 @@ import { ChartJSOrUndefined } from 'react-chartjs-2/dist/types';
|
|||||||
import { WindowShow } from '../../wailsjs/runtime';
|
import { WindowShow } from '../../wailsjs/runtime';
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
import { DialogButton } from '../components/DialogButton';
|
import { DialogButton } from '../components/DialogButton';
|
||||||
|
import {
|
||||||
|
DataProcessParameters,
|
||||||
|
LoraFinetuneParameters,
|
||||||
|
LoraFinetunePrecision,
|
||||||
|
TrainNavigationItem
|
||||||
|
} from '../types/train';
|
||||||
|
|
||||||
ChartJS.register(
|
ChartJS.register(
|
||||||
CategoryScale,
|
CategoryScale,
|
||||||
@ -86,39 +91,6 @@ const addLossDataToChart = (epoch: number, loss: number) => {
|
|||||||
commonStore.setChartData(commonStore.chartData);
|
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]> = [
|
const loraFinetuneParametersOptions: Array<[key: keyof LoraFinetuneParameters, type: string, name: string]> = [
|
||||||
['devices', 'number', 'Devices'],
|
['devices', 'number', 'Devices'],
|
||||||
['precision', 'LoraFinetunePrecision', 'Precision'],
|
['precision', 'LoraFinetunePrecision', 'Precision'],
|
||||||
@ -568,10 +540,6 @@ const LoraFinetune: FC = observer(() => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
type TrainNavigationItem = {
|
|
||||||
element: ReactElement;
|
|
||||||
};
|
|
||||||
|
|
||||||
const pages: { [label: string]: TrainNavigationItem } = {
|
const pages: { [label: string]: TrainNavigationItem } = {
|
||||||
'LoRA Finetune': {
|
'LoRA Finetune': {
|
||||||
element: <LoraFinetune />
|
element: <LoraFinetune />
|
||||||
@ -582,7 +550,7 @@ const pages: { [label: string]: TrainNavigationItem } = {
|
|||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
export const Train: FC = () => {
|
const Train: FC = () => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [tab, setTab] = useState('LoRA Finetune');
|
const [tab, setTab] = useState('LoRA Finetune');
|
||||||
|
|
||||||
@ -607,3 +575,5 @@ export const Train: FC = () => {
|
|||||||
</div>
|
</div>
|
||||||
</div>;
|
</div>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export default Train;
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
import { ModelConfig } from './Configs';
|
import { CompletionPreset } from '../types/completion';
|
||||||
import { CompletionPreset } from './Completion';
|
import { ModelConfig } from '../types/configs';
|
||||||
|
|
||||||
export const defaultCompositionPrompt = '<pad>';
|
export const defaultCompositionPrompt = '<pad>';
|
||||||
|
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ReactElement } from 'react';
|
import { FC, lazy, LazyExoticComponent, ReactElement } from 'react';
|
||||||
import { Configs } from './Configs';
|
|
||||||
import {
|
import {
|
||||||
ArrowDownload20Regular,
|
ArrowDownload20Regular,
|
||||||
Chat20Regular,
|
Chat20Regular,
|
||||||
@ -12,21 +11,12 @@ import {
|
|||||||
Settings20Regular,
|
Settings20Regular,
|
||||||
Storage20Regular
|
Storage20Regular
|
||||||
} from '@fluentui/react-icons';
|
} 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 = {
|
type NavigationItem = {
|
||||||
label: string;
|
label: string;
|
||||||
path: string;
|
path: string;
|
||||||
icon: ReactElement;
|
icon: ReactElement;
|
||||||
element: ReactElement;
|
element: LazyExoticComponent<FC>;
|
||||||
top: boolean;
|
top: boolean;
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -35,70 +25,70 @@ export const pages: NavigationItem[] = [
|
|||||||
label: 'Home',
|
label: 'Home',
|
||||||
path: '/',
|
path: '/',
|
||||||
icon: <Home20Regular />,
|
icon: <Home20Regular />,
|
||||||
element: <Home />,
|
element: lazy(() => import('./Home')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Chat',
|
label: 'Chat',
|
||||||
path: '/chat',
|
path: '/chat',
|
||||||
icon: <Chat20Regular />,
|
icon: <Chat20Regular />,
|
||||||
element: <Chat />,
|
element: lazy(() => import('./Chat')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Completion',
|
label: 'Completion',
|
||||||
path: '/completion',
|
path: '/completion',
|
||||||
icon: <ClipboardEdit20Regular />,
|
icon: <ClipboardEdit20Regular />,
|
||||||
element: <Completion />,
|
element: lazy(() => import('./Completion')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Composition',
|
label: 'Composition',
|
||||||
path: '/composition',
|
path: '/composition',
|
||||||
icon: <MusicNote220Regular />,
|
icon: <MusicNote220Regular />,
|
||||||
element: <Composition />,
|
element: lazy(() => import('./Composition')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Configs',
|
label: 'Configs',
|
||||||
path: '/configs',
|
path: '/configs',
|
||||||
icon: <DocumentSettings20Regular />,
|
icon: <DocumentSettings20Regular />,
|
||||||
element: <Configs />,
|
element: lazy(() => import('./Configs')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Models',
|
label: 'Models',
|
||||||
path: '/models',
|
path: '/models',
|
||||||
icon: <DataUsageSettings20Regular />,
|
icon: <DataUsageSettings20Regular />,
|
||||||
element: <Models />,
|
element: lazy(() => import('./Models')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Downloads',
|
label: 'Downloads',
|
||||||
path: '/downloads',
|
path: '/downloads',
|
||||||
icon: <ArrowDownload20Regular />,
|
icon: <ArrowDownload20Regular />,
|
||||||
element: <Downloads />,
|
element: lazy(() => import('./Downloads')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Train',
|
label: 'Train',
|
||||||
path: '/train',
|
path: '/train',
|
||||||
icon: <Storage20Regular />,
|
icon: <Storage20Regular />,
|
||||||
element: <Train />,
|
element: lazy(() => import('./Train')),
|
||||||
top: true
|
top: true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'Settings',
|
label: 'Settings',
|
||||||
path: '/settings',
|
path: '/settings',
|
||||||
icon: <Settings20Regular />,
|
icon: <Settings20Regular />,
|
||||||
element: <Settings />,
|
element: lazy(() => import('./Settings')),
|
||||||
top: false
|
top: false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
label: 'About',
|
label: 'About',
|
||||||
path: '/about',
|
path: '/about',
|
||||||
icon: <Info20Regular />,
|
icon: <Info20Regular />,
|
||||||
element: <About />,
|
element: lazy(() => import('./About')),
|
||||||
top: false
|
top: false
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
@ -5,30 +5,32 @@ import { getStatus } from './apis';
|
|||||||
import { EventsOn, WindowSetTitle } from '../wailsjs/runtime';
|
import { EventsOn, WindowSetTitle } from '../wailsjs/runtime';
|
||||||
import manifest from '../../manifest.json';
|
import manifest from '../../manifest.json';
|
||||||
import { defaultModelConfigs, defaultModelConfigsMac } from './pages/defaultConfigs';
|
import { defaultModelConfigs, defaultModelConfigsMac } from './pages/defaultConfigs';
|
||||||
import { Preset } from './pages/PresetsManager/PresetsButton';
|
|
||||||
import { wslHandler } from './pages/Train';
|
|
||||||
import { t } from 'i18next';
|
import { t } from 'i18next';
|
||||||
|
import { Preset } from './types/presets';
|
||||||
|
|
||||||
export async function startup() {
|
export async function startup() {
|
||||||
|
initPresets();
|
||||||
|
|
||||||
|
await GetPlatform().then(p => commonStore.setPlatform(p as Platform));
|
||||||
|
|
||||||
|
if (commonStore.platform !== 'web') {
|
||||||
downloadProgramFiles();
|
downloadProgramFiles();
|
||||||
EventsOn('downloadList', (data) => {
|
EventsOn('downloadList', (data) => {
|
||||||
if (data)
|
if (data)
|
||||||
commonStore.setDownloadList(data);
|
commonStore.setDownloadList(data);
|
||||||
});
|
});
|
||||||
EventsOn('wsl', wslHandler);
|
EventsOn('wsl', (await import('./pages/Train')).wslHandler);
|
||||||
EventsOn('wslerr', (e) => {
|
EventsOn('wslerr', (e) => {
|
||||||
console.log(e);
|
console.log(e);
|
||||||
});
|
});
|
||||||
initLocalModelsNotify();
|
initLocalModelsNotify();
|
||||||
initLoraModels();
|
initLoraModels();
|
||||||
|
|
||||||
initPresets();
|
|
||||||
|
|
||||||
initHardwareMonitor();
|
initHardwareMonitor();
|
||||||
|
}
|
||||||
|
|
||||||
await GetPlatform().then(p => commonStore.setPlatform(p as Platform));
|
|
||||||
await initConfig();
|
await initConfig();
|
||||||
|
|
||||||
|
if (commonStore.platform !== 'web') {
|
||||||
initCache(true).then(initRemoteText); // depends on config customModelsPath
|
initCache(true).then(initRemoteText); // depends on config customModelsPath
|
||||||
|
|
||||||
if (commonStore.settings.autoUpdatesCheck) // depends on config settings
|
if (commonStore.settings.autoUpdatesCheck) // depends on config settings
|
||||||
@ -39,6 +41,7 @@ export async function startup() {
|
|||||||
commonStore.setStatus(status);
|
commonStore.setStatus(status);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
async function initRemoteText() {
|
async function initRemoteText() {
|
||||||
await fetch('https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner@master/manifest.json', { cache: 'no-cache' })
|
await fetch('https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner@master/manifest.json', { cache: 'no-cache' })
|
||||||
@ -88,6 +91,7 @@ async function initCache(initUnfinishedModels: boolean) {
|
|||||||
|
|
||||||
async function initPresets() {
|
async function initPresets() {
|
||||||
await ReadJson('presets.json').then((presets: Preset[]) => {
|
await ReadJson('presets.json').then((presets: Preset[]) => {
|
||||||
|
if (Array.isArray(presets))
|
||||||
commonStore.setPresets(presets, false);
|
commonStore.setPresets(presets, false);
|
||||||
}).catch(() => {
|
}).catch(() => {
|
||||||
});
|
});
|
||||||
|
@ -2,21 +2,21 @@ import { makeAutoObservable } from 'mobx';
|
|||||||
import { getUserLanguage, isSystemLightMode, saveCache, saveConfigs, savePresets } from '../utils';
|
import { getUserLanguage, isSystemLightMode, saveCache, saveConfigs, savePresets } from '../utils';
|
||||||
import { WindowSetDarkTheme, WindowSetLightTheme } from '../../wailsjs/runtime';
|
import { WindowSetDarkTheme, WindowSetLightTheme } from '../../wailsjs/runtime';
|
||||||
import manifest from '../../../manifest.json';
|
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 i18n from 'i18next';
|
||||||
import { CompletionPreset } from '../pages/Completion';
|
|
||||||
import { defaultCompositionPrompt, defaultModelConfigs, defaultModelConfigsMac } from '../pages/defaultConfigs';
|
import { defaultCompositionPrompt, defaultModelConfigs, defaultModelConfigsMac } from '../pages/defaultConfigs';
|
||||||
import commonStore from './commonStore';
|
import commonStore from './commonStore';
|
||||||
import { Preset } from '../pages/PresetsManager/PresetsButton';
|
|
||||||
import { DataProcessParameters, LoraFinetuneParameters } from '../pages/Train';
|
|
||||||
import { ChartData } from 'chart.js';
|
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 {
|
export enum ModelStatus {
|
||||||
Offline,
|
Offline,
|
||||||
@ -37,7 +37,7 @@ export type Attachment = {
|
|||||||
content: string;
|
content: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export type Platform = 'windows' | 'darwin' | 'linux';
|
export type Platform = 'windows' | 'darwin' | 'linux' | 'web';
|
||||||
|
|
||||||
class CommonStore {
|
class CommonStore {
|
||||||
// global
|
// global
|
||||||
@ -135,7 +135,7 @@ class CommonStore {
|
|||||||
customModelsPath: './models',
|
customModelsPath: './models',
|
||||||
customPythonPath: '',
|
customPythonPath: '',
|
||||||
apiUrl: '',
|
apiUrl: '',
|
||||||
apiKey: 'sk-',
|
apiKey: '',
|
||||||
apiChatModelName: 'rwkv',
|
apiChatModelName: 'rwkv',
|
||||||
apiCompletionModelName: '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 { t } from 'i18next';
|
||||||
import { ToastOptions } from 'react-toastify/dist/types';
|
import { ToastOptions } from 'react-toastify/dist/types';
|
||||||
import { Button } from '@fluentui/react-components';
|
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 { BrowserOpenURL, WindowShow } from '../../wailsjs/runtime';
|
||||||
import { NavigateFunction } from 'react-router';
|
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 = {
|
export type Cache = {
|
||||||
version: string
|
version: string
|
||||||
@ -290,6 +290,8 @@ export function bytesToReadable(size: number) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function absPathAsset(path: string) {
|
export function absPathAsset(path: string) {
|
||||||
|
if (commonStore.platform === 'web')
|
||||||
|
return path;
|
||||||
if ((path.length > 0 && path[0] === '/') ||
|
if ((path.length > 0 && path[0] === '/') ||
|
||||||
(path.length > 1 && path[1] === ':')) {
|
(path.length > 1 && path[1] === ':')) {
|
||||||
return '=>' + path;
|
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,7 +1,38 @@
|
|||||||
|
// @ts-ignore
|
||||||
|
import { dependencies } from './package.json';
|
||||||
import { defineConfig } from 'vite';
|
import { defineConfig } from 'vite';
|
||||||
import react from '@vitejs/plugin-react';
|
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/
|
// https://vitejs.dev/config/
|
||||||
export default defineConfig({
|
export default defineConfig({
|
||||||
plugins: [react(),
|
plugins: [react(),
|
||||||
@ -9,5 +40,16 @@ export default defineConfig({
|
|||||||
template: 'treemap',
|
template: 'treemap',
|
||||||
gzipSize: true,
|
gzipSize: true,
|
||||||
brotliSize: true
|
brotliSize: true
|
||||||
})]
|
})],
|
||||||
|
build: {
|
||||||
|
chunkSizeWarningLimit: 3000,
|
||||||
|
rollupOptions: {
|
||||||
|
output: {
|
||||||
|
manualChunks: {
|
||||||
|
vendor,
|
||||||
|
...renderChunks(dependencies)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user