Compare commits

...

120 Commits

Author SHA1 Message Date
josc146
87e1daa733 release v1.5.9 2023-12-08 19:22:01 +08:00
josc146
f5900179e0 model tags classifier 2023-12-08 18:17:53 +08:00
josc146
51e162970e always reset to activePreset 2023-12-08 17:10:23 +08:00
josc146
0b339ad0f6 improve ConfigSelector performance of Configs page 2023-12-08 16:36:15 +08:00
josc146
60693d6a29 improve presets interaction 2023-12-08 15:36:53 +08:00
josc146
eea53a6e9e add available tag for model downloaded configs 2023-12-08 15:34:45 +08:00
josc146
8a19181a38 chore 2023-12-08 15:30:46 +08:00
josc146
94d835c7ae better customCuda condition 2023-12-08 15:30:05 +08:00
josc146
d9e25ad69f better state cache 2023-12-08 15:28:33 +08:00
josc146
75244fbd8b disable hashed assets 2023-12-08 11:22:31 +08:00
josc146
5ce84edc3d add web-rwkv-converter (Safetensors Convert no longer depends on Python) 2023-12-07 23:26:39 +08:00
josc146
1c683087f4 update ci webgpu components 2023-12-07 23:04:56 +08:00
josc146
85a3b39cbc fix webWails undefined functions 2023-12-06 23:19:56 +08:00
josc146
cc6c24f0c3 add python-3.10.11-embed-amd64.zip cnMirror and chore 2023-12-06 23:19:22 +08:00
josc146
c733b6419c for devices that gpu is not supported, use cpu to merge lora 2023-12-06 23:17:13 +08:00
josc146
c853c5b60b chore 2023-12-06 23:09:39 +08:00
josc146
053a08f5b7 update convert_safetensors.py 2023-12-06 23:08:40 +08:00
josc146
f7227cd1c1 update ci webgpu components 2023-12-06 23:08:20 +08:00
josc146
861e245062 RWKV_RESCALE_LAYER 999 for music model 2023-12-04 17:51:21 +08:00
josc146
8f0fc7db56 update README_ZH.md 2023-11-30 22:07:16 +08:00
josc146
3dd06fa70e update README_ZH.md 2023-11-30 21:49:31 +08:00
josc146
86a855e7bc fix damaged logo 2023-11-30 21:48:14 +08:00
github-actions[bot]
b3110d4ad8 release v1.5.8 2023-11-30 05:04:31 +00:00
josc146
602004ad34 release v1.5.8 2023-11-30 13:04:02 +08:00
josc146
a8b4f0bb7e lora finetune version check 2023-11-30 13:01:38 +08:00
josc146
24cc8be085 add high loss warning 2023-11-30 12:40:16 +08:00
josc146
a96d7aef8d display mainInstrument of track 2023-11-30 12:36:03 +08:00
josc146
cbe299583b improve details of MIDI Input 2023-11-30 11:57:52 +08:00
josc146
68c70a362b darkmode of midi tracks 2023-11-30 11:56:45 +08:00
josc146
a78c346371 fix NoteOff ElapsedTime of MIDI Tracks 2023-11-30 11:55:10 +08:00
github-actions[bot]
102763b94d release v1.5.7 2023-11-29 15:01:26 +00:00
josc146
ad65765ba8 release v1.5.7 2023-11-29 22:59:47 +08:00
josc146
d04fd7cb87 fix lib 2023-11-29 22:59:42 +08:00
github-actions[bot]
b398cbb591 release v1.5.6 2023-11-29 13:22:21 +00:00
josc146
19b97e985c release v1.5.6 2023-11-29 21:21:50 +08:00
josc146
93bf74a320 fix NoteOff 2023-11-29 21:21:42 +08:00
josc146
7daae23bbb update defaultConfigs 2023-11-29 21:21:29 +08:00
josc146
0d0a3f15cc chore 2023-11-29 21:21:14 +08:00
github-actions[bot]
04fbb38861 release v1.5.5 2023-11-29 11:32:40 +00:00
josc146
d666c6032b release v1.5.5 2023-11-29 19:31:56 +08:00
josc146
93e8660d69 add instruments i18n 2023-11-29 19:31:52 +08:00
josc146
e687cf02bb try to use local soundfont by default 2023-11-29 19:17:19 +08:00
josc146
e858f1477a update locales 2023-11-29 19:10:01 +08:00
josc146
a2062ae9cc feat: save MIDI tracks to generation area; playing tracks and audio preview are still under development 2023-11-29 19:04:41 +08:00
josc146
34112c79c7 fix autoPlayed midi cannot be stopped 2023-11-29 15:28:43 +08:00
josc146
b625b8a6d1 MIDI Recording and details improvement 2023-11-29 14:05:58 +08:00
josc146
14a13d5768 basic MIDI Input Audio Tracks 2023-11-28 15:34:06 +08:00
josc146
7ce464ecda improve details 2023-11-26 22:54:59 +08:00
github-actions[bot]
2c1f89383f release v1.5.4 2023-11-24 11:22:42 +00:00
josc146
e666c50f77 release v1.5.4 2023-11-24 19:22:07 +08:00
josc146
1b441752b0 chore 2023-11-24 19:21:58 +08:00
josc146
e01897b24d improve launch flow of webgpu mode 2023-11-24 19:21:14 +08:00
josc146
6146d910b4 improve launch flow of webgpu mode 2023-11-24 18:36:44 +08:00
josc146
0063c171f3 upgrade to rwkv 0.8.22 (rwkv6 support) 2023-11-24 17:55:16 +08:00
josc146
bea3c29c1c update defaultConfigs 2023-11-24 17:13:22 +08:00
josc146
5f543c2545 update manifest 2023-11-24 16:35:21 +08:00
josc146
177b2c54d9 allow reading attachments even if the model is offline 2023-11-24 16:25:21 +08:00
josc146
645e8e2f44 chore 2023-11-24 15:58:53 +08:00
josc146
f2d0dda2ff allow safetensors converter on macOS 2023-11-21 22:32:25 +08:00
josc146
3a449e7b46 fix fs watcher of macOS 2023-11-21 22:30:42 +08:00
github-actions[bot]
18d2ecb7a7 release v1.5.3 2023-11-20 16:22:32 +00:00
josc146
bb3a93b419 release v1.5.3 2023-11-21 00:21:09 +08:00
josc146
1334f0e5ba chore 2023-11-21 00:20:54 +08:00
josc146
8781416cfb add hf-mirror for cn users 2023-11-21 00:04:23 +08:00
josc146
a9819139b8 add sidePanel for Chat page 2023-11-20 23:47:39 +08:00
josc146
66e43c9d9b display lastModelName at the top (WorkHeader) 2023-11-20 23:27:44 +08:00
josc146
41e5bd5eb8 change ValuedSlider's step to 100 2023-11-20 23:25:39 +08:00
josc146
48fef0235b add webgpu nf4 2023-11-20 21:10:10 +08:00
josc146
d435436525 improve finetune error 2023-11-20 20:39:00 +08:00
josc146
cd7a9896dc improve styles 2023-11-20 20:16:55 +08:00
josc146
bbcc6b07b6 improve precision description 2023-11-20 20:13:30 +08:00
josc146
646bcd81c0 fix webgpu permission for macos 2023-11-20 20:12:20 +08:00
josc146
dbf0dccc9d add tokenizer(/switch-model) to /docs 2023-11-20 20:11:45 +08:00
josc146
437de2be20 improve lazy loading ui 2023-11-18 13:59:37 +08:00
josc146
f739c61197 fix a finetune bug 2023-11-17 22:37:21 +08:00
josc146
01d3c89ea4 add rwkv API URL Option; update OpenAI models Option 2023-11-17 22:16:49 +08:00
josc146
d18218f21a use local API when it's working, even if a custom API URL is provided 2023-11-17 21:53:29 +08:00
josc146
c8470e77fd fix state_cache of deploy mode 2023-11-17 21:32:11 +08:00
josc146
9ede7d7c6d strict default_stop 2023-11-17 21:18:52 +08:00
josc146
a59c4436c8 macos: change default webgpu backend to aarch64-apple-darwin 2023-11-17 21:16:08 +08:00
josc146
068be2bfc4 update setup comments 2023-11-17 20:47:33 +08:00
josc146
94a5dc4fb7 update setup.sh comments 2023-11-14 17:38:24 +08:00
github-actions[bot]
9f288de951 release v1.5.2 2023-11-09 14:11:40 +00:00
josc146
3d5c3dcd31 release v1.5.2 2023-11-09 22:11:05 +08:00
josc146
0a4876a564 improve user guide 2023-11-09 22:07:01 +08:00
josc146
4f0558ae34 add client upgrade progress 2023-11-09 21:38:02 +08:00
josc146
f03c9cf25f improve mobile view 2023-11-09 12:21:01 +08:00
josc146
07797537d1 add RWKV-Runner WebUI to Server-Deploy-Examples 2023-11-09 00:21:02 +08:00
github-actions[bot]
0c3a50cb07 release v1.5.1 2023-11-08 15:41:53 +00:00
josc146
c7dcff52a1 release v1.5.1 2023-11-08 23:41:17 +08:00
josc146
c6ef32958e when client webUI enabled, set server into deployment mode 2023-11-08 23:31:13 +08:00
josc146
7235e1067b add deployment mode. If /switch-model with deploy: true, will disable /switch-model, /exit and other dangerous APIs (state cache APIs, part of midi APIs) 2023-11-08 23:29:42 +08:00
josc146
0594290b92 disable WebUI Option of WebGPU Mode (webgpu not supported yet) 2023-11-08 23:05:59 +08:00
josc146
d249a4c29a print error.txt 2023-11-08 22:57:38 +08:00
josc146
02ba37fab4 improve api url getter 2023-11-08 22:25:41 +08:00
josc146
b5a6f8a425 set deepspeed to 0.11.2 to avoid finetune error 2023-11-08 22:20:11 +08:00
josc146
1ad86d737c chore 2023-11-08 22:18:49 +08:00
josc146
cfa3669f6f fix /docs default api params (Pydantic v2) 2023-11-07 22:53:11 +08:00
josc146
26d4c9f0ed chore 2023-11-07 22:28:13 +08:00
josc146
3ddcf9f62e add webui entry 2023-11-07 22:24:06 +08:00
josc146
e734fce64f create webui assets 2023-11-07 22:23:26 +08:00
josc146
150beb578c chore 2023-11-07 22:23:00 +08:00
josc146
db6fbe8366 add python webui server 2023-11-07 22:22:29 +08:00
josc146
46f52923c3 improve webui 2023-11-07 22:21:41 +08:00
josc146
893be5cf43 webui build 2023-11-07 19:27:21 +08:00
github-actions[bot]
384e4ce4d0 release v1.5.0 2023-11-05 13:10:50 +00:00
josc146
b8712e0b89 release v1.5.0 2023-11-05 21:10:21 +08:00
josc146
37dda4333d chat attachment is now related to single message 2023-11-05 21:05:06 +08:00
josc146
64826b9af7 fix log encoding error 2023-11-05 21:00:31 +08:00
josc146
47b0c35441 update ngrok_connect 2023-11-04 20:22:28 +08:00
josc146
1dcda47013 improve startup process 2023-11-04 20:21:55 +08:00
josc146
1f81a1e5a8 upgrade to rwkv 0.8.20 2023-11-03 23:27:14 +08:00
josc146
35e92d2aef chore 2023-11-03 23:22:52 +08:00
josc146
0d99e5549e port occupied detection 2023-11-03 21:18:42 +08:00
josc146
fed1594ddc fix stop button status of Chat page 2023-10-30 21:09:23 +08:00
josc146
14b90bb36b improve dml mode performance (20% faster, https://github.com/BlinkDL/ChatRWKV/pull/181) 2023-10-30 20:24:57 +08:00
josc146
f86b7f1f08 python38 compatibility 2023-10-29 14:11:11 +08:00
josc146
54355d5a7a improve the compatibility between frontend presets and chatgpt api 2023-10-28 23:06:19 +08:00
josc146
ff7306349a improve memory usage of state cache 2023-10-28 23:04:49 +08:00
github-actions[bot]
77df56cddc release v1.4.9 2023-10-27 06:04:00 +00:00
98 changed files with 5855 additions and 2423 deletions

View File

@@ -48,16 +48,13 @@ jobs:
id: cp310 id: cp310
with: with:
python-version: '3.10' python-version: '3.10'
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: wasm32-unknown-unknown
- uses: crazy-max/ghaction-chocolatey@v2 - uses: crazy-max/ghaction-chocolatey@v2
with: with:
args: install upx args: install upx
- run: | - run: |
Start-BitsTransfer https://github.com/josStorer/LibreHardwareMonitor.Console/releases/download/v0.1.0/LibreHardwareMonitor.Console.zip ./LibreHardwareMonitor.Console.zip Start-BitsTransfer https://github.com/josStorer/ai00_rwkv_server/releases/latest/download/webgpu_server_windows_x86_64.exe ./backend-rust/webgpu_server.exe
Start-BitsTransfer https://github.com/josStorer/web-rwkv-converter/releases/latest/download/web-rwkv-converter_windows_x86_64.exe ./backend-rust/web-rwkv-converter.exe
Start-BitsTransfer https://github.com/josStorer/LibreHardwareMonitor.Console/releases/latest/download/LibreHardwareMonitor.Console.zip ./LibreHardwareMonitor.Console.zip
Expand-Archive ./LibreHardwareMonitor.Console.zip -DestinationPath ./components/LibreHardwareMonitor.Console Expand-Archive ./LibreHardwareMonitor.Console.zip -DestinationPath ./components/LibreHardwareMonitor.Console
Start-BitsTransfer https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip ./python-3.10.11-embed-amd64.zip Start-BitsTransfer https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip ./python-3.10.11-embed-amd64.zip
Expand-Archive ./python-3.10.11-embed-amd64.zip -DestinationPath ./py310 Expand-Archive ./python-3.10.11-embed-amd64.zip -DestinationPath ./py310
@@ -67,11 +64,6 @@ jobs:
Copy-Item -Path "${{ steps.cp310.outputs.python-path }}/../include" -Destination "py310/include" -Recurse Copy-Item -Path "${{ steps.cp310.outputs.python-path }}/../include" -Destination "py310/include" -Recurse
Copy-Item -Path "${{ steps.cp310.outputs.python-path }}/../libs" -Destination "py310/libs" -Recurse Copy-Item -Path "${{ steps.cp310.outputs.python-path }}/../libs" -Destination "py310/libs" -Recurse
./py310/python -m pip install cyac==1.9 ./py310/python -m pip install cyac==1.9
git clone https://github.com/josStorer/ai00_rwkv_server --depth=1
cd ai00_rwkv_server
cargo build --release
mv ./target/release/ai00_server.exe ../backend-rust/webgpu_server.exe
cd ..
go install github.com/wailsapp/wails/v2/cmd/wails@latest go install github.com/wailsapp/wails/v2/cmd/wails@latest
(Get-Content -Path ./backend-golang/app.go) -replace "//go:custom_build windows ", "" | Set-Content -Path ./backend-golang/app.go (Get-Content -Path ./backend-golang/app.go) -replace "//go:custom_build windows ", "" | Set-Content -Path ./backend-golang/app.go
make make
@@ -89,26 +81,16 @@ jobs:
- uses: actions/setup-go@v4 - uses: actions/setup-go@v4
with: with:
go-version: '1.20.5' go-version: '1.20.5'
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: wasm32-unknown-unknown
- run: | - run: |
wget https://github.com/josStorer/ai00_rwkv_server/releases/latest/download/webgpu_server_linux_x86_64 -O ./backend-rust/webgpu_server
wget https://github.com/josStorer/web-rwkv-converter/releases/latest/download/web-rwkv-converter_linux_x86_64 -O ./backend-rust/web-rwkv-converter
sudo apt-get update sudo apt-get update
sudo apt-get install upx sudo apt-get install upx
sudo apt-get install build-essential libgtk-3-dev libwebkit2gtk-4.0-dev sudo apt-get install build-essential libgtk-3-dev libwebkit2gtk-4.0-dev libasound2-dev
git clone https://github.com/josStorer/ai00_rwkv_server --depth=1
cd ai00_rwkv_server
sudo apt-get install libudev-dev
sudo apt-get install libasound2-dev
rustup target add x86_64-unknown-linux-gnu
cargo build --release --target x86_64-unknown-linux-gnu
mv ./target/x86_64-unknown-linux-gnu/release/ai00_server ../backend-rust/webgpu_server
cd ..
go install github.com/wailsapp/wails/v2/cmd/wails@latest go install github.com/wailsapp/wails/v2/cmd/wails@latest
rm ./backend-python/rwkv_pip/wkv_cuda.pyd rm ./backend-python/rwkv_pip/wkv_cuda.pyd
rm ./backend-python/rwkv_pip/rwkv5.pyd rm ./backend-python/rwkv_pip/rwkv5.pyd
rm ./backend-python/rwkv_pip/rwkv6.pyd
rm ./backend-python/rwkv_pip/beta/wkv_cuda.pyd rm ./backend-python/rwkv_pip/beta/wkv_cuda.pyd
rm ./backend-python/get-pip.py rm ./backend-python/get-pip.py
make make
@@ -126,20 +108,13 @@ jobs:
- uses: actions/setup-go@v4 - uses: actions/setup-go@v4
with: with:
go-version: '1.20.5' go-version: '1.20.5'
- uses: actions-rs/toolchain@v1
with:
toolchain: stable
override: true
target: wasm32-unknown-unknown
- run: | - run: |
git clone https://github.com/josStorer/ai00_rwkv_server --depth=1 wget https://github.com/josStorer/ai00_rwkv_server/releases/latest/download/webgpu_server_darwin_aarch64 -O ./backend-rust/webgpu_server
cd ai00_rwkv_server wget https://github.com/josStorer/web-rwkv-converter/releases/latest/download/web-rwkv-converter_darwin_aarch64 -O ./backend-rust/web-rwkv-converter
cargo build --release
mv ./target/release/ai00_server ../backend-rust/webgpu_server
cd ..
go install github.com/wailsapp/wails/v2/cmd/wails@latest go install github.com/wailsapp/wails/v2/cmd/wails@latest
rm ./backend-python/rwkv_pip/wkv_cuda.pyd rm ./backend-python/rwkv_pip/wkv_cuda.pyd
rm ./backend-python/rwkv_pip/rwkv5.pyd rm ./backend-python/rwkv_pip/rwkv5.pyd
rm ./backend-python/rwkv_pip/rwkv6.pyd
rm ./backend-python/rwkv_pip/beta/wkv_cuda.pyd rm ./backend-python/rwkv_pip/beta/wkv_cuda.pyd
rm ./backend-python/get-pip.py rm ./backend-python/get-pip.py
make make

2
.gitignore vendored
View File

@@ -18,7 +18,7 @@ __pycache__
/cmd-helper.bat /cmd-helper.bat
/install-py-dep.bat /install-py-dep.bat
/backend-python/wkv_cuda /backend-python/wkv_cuda
/backend-python/rwkv5 /backend-python/rwkv*
*.exe *.exe
*.old *.old
.DS_Store .DS_Store

View File

@@ -1,45 +1,22 @@
## Changes ## Changes
### Features - add web-rwkv-converter (Safetensors Convert no longer depends on Python) (WebGPU Server 0.3.3)
- model tags classifier
- allow conversation with some document (.pdf, .txt) (Experimental) - improve presets interaction
- add `/file-to-text` api - better state cache
- allow avatarImg to be local absolute path - better customCuda condition
- base64 preset support - add python-3.10.11-embed-amd64.zip cnMirror
- always reset to activePreset
### Upgrades - for devices that gpu is not supported, use cpu to merge lora
- RWKV_RESCALE_LAYER 999 for music model
- upgrade to rwkv 0.8.16 (DirectML support; rwkv 5.2 no longer needs to ensure custom cuda kernel enabled) - disable hashed assets
- upgrade to webgpu 0.2.2 (WebGPU Mode is now recommended for AMD and Intel - fix webWails undefined functions
Users) (https://github.com/josStorer/ai00_rwkv_server) - fix damaged logo
- upgrade python packages
### Improvements
- improve cuda kernel compatibility (compute compatibility 5.3, Jetson Nano, Nvidia 10 Series+)
- RWKVType now no longer relies on the file name (use emb)
- improve message interruption and retry for Chat page
- update sample.jsonl of LoRA finetune
- update api stop strategy for better custom user_name and assistant_name support
- edited chat message now is marked as Normal
- change default World series prefix to User/Assistant
### Chores
- update manifest.json (RWKV-5)
- update readme and client text description
- add pip --no-warn-script-location
- mark rwkv raven series as old model
- chore - chore
### Fixes
- fix linux kernel (partial revert 68228a45)
- fix the `make` command on Linux and macOS, no longer need manual operations on the wsl.go file. (#158, #173, #207)
## Install ## Install
- Windows: https://github.com/josStorer/RWKV-Runner/blob/master/build/windows/Readme_Install.txt - Windows: https://github.com/josStorer/RWKV-Runner/blob/master/build/windows/Readme_Install.txt
- MacOS: https://github.com/josStorer/RWKV-Runner/blob/master/build/darwin/Readme_Install.txt - MacOS: https://github.com/josStorer/RWKV-Runner/blob/master/build/darwin/Readme_Install.txt
- Linux: https://github.com/josStorer/RWKV-Runner/blob/master/build/linux/Readme_Install.txt - Linux: https://github.com/josStorer/RWKV-Runner/blob/master/build/linux/Readme_Install.txt
- Server-Deploy-Examples: https://github.com/josStorer/RWKV-Runner/tree/master/deploy-examples - Server-Deploy-Examples: https://github.com/josStorer/RWKV-Runner/tree/master/deploy-examples

View File

@@ -8,16 +8,26 @@ endif
build-windows: build-windows:
@echo ---- build for windows @echo ---- build for windows
wails build -upx -ldflags "-s -w" -platform windows/amd64 wails build -upx -ldflags '-s -w -extldflags "-static"' -platform windows/amd64
build-macos: build-macos:
@echo ---- build for macos @echo ---- build for macos
wails build -ldflags "-s -w" -platform darwin/universal wails build -ldflags '-s -w' -platform darwin/universal
build-linux: 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

View File

@@ -20,7 +20,7 @@ API兼容的接口这意味着一切ChatGPT客户端都是RWKV客户端。
[![MacOS][MacOS-image]][MacOS-url] [![MacOS][MacOS-image]][MacOS-url]
[![Linux][Linux-image]][Linux-url] [![Linux][Linux-image]][Linux-url]
[视频演示](https://www.bilibili.com/video/BV1hM4y1v76R) | [疑难解答](https://www.bilibili.com/read/cv23921171) | [预览](#Preview) | [下载][download-url] | [懒人包](https://pan.baidu.com/s/1zdzZ_a0uM3gDqi6pXIZVAA?pwd=1111) | [服务器部署示例](https://github.com/josStorer/RWKV-Runner/tree/master/deploy-examples) [视频演示](https://www.bilibili.com/video/BV1hM4y1v76R) | [疑难解答](https://www.bilibili.com/read/cv23921171) | [预览](#Preview) | [下载][download-url] | [懒人包](https://pan.baidu.com/s/1zdzZ_a0uM3gDqi6pXIZVAA?pwd=1111) | [简明服务部署示例](#Simple-Deploy-Example) | [服务器部署示例](https://github.com/josStorer/RWKV-Runner/tree/master/deploy-examples) | [MIDI硬件输入](#MIDI-Input)
[license-image]: http://img.shields.io/badge/license-MIT-blue.svg [license-image]: http://img.shields.io/badge/license-MIT-blue.svg
@@ -57,19 +57,45 @@ API兼容的接口这意味着一切ChatGPT客户端都是RWKV客户端。
## 功能 ## 功能
- RWKV模型管理一键启动 - RWKV模型管理一键启动
- 与OpenAI API完全兼容一切ChatGPT客户端都是RWKV客户端。启动模型后打开 http://127.0.0.1:8000/docs 查看详细内容 - 前后端分离如果你不想使用客户端也允许单独部署前端服务或后端推理服务或具有WebUI的后端推理服务。
[简明服务部署示例](#Simple-Deploy-Example) | [服务器部署示例](https://github.com/josStorer/RWKV-Runner/tree/master/deploy-examples)
- 与OpenAI API兼容一切ChatGPT客户端都是RWKV客户端。启动模型后打开 http://127.0.0.1:8000/docs 查看API文档
- 全自动依赖安装,你只需要一个轻巧的可执行程序 - 全自动依赖安装,你只需要一个轻巧的可执行程序
- 预设了2G至32G显存配置,几乎在各种电脑上工作良好 - 预设多级显存配置,几乎在各种电脑上工作良好。通过配置页面切换到WebGPU策略还可以在AMDIntel等显卡上运行
- 自带用户友好的聊天续写交互页面 - 自带用户友好的聊天续写,作曲交互页面。支持聊天预设附件上传MIDI硬件输入及音轨编辑。
- 易于理解和操作的参数配置 [预览](#Preview) | [MIDI硬件输入](#MIDI-Input)
- 内置WebUI选项一键启动Web服务共享硬件资源
- 易于理解和操作的参数配置,及各类操作引导提示
- 内置模型转换工具 - 内置模型转换工具
- 内置下载管理和远程模型检视 - 内置下载管理和远程模型检视
- 内置一键LoRA微调 - 内置一键LoRA微调
- 也可用作 OpenAI ChatGPT 和 GPT Playground 客户端 - 也可用作 OpenAI ChatGPT 和 GPT Playground 客户端 (在设置内填写API URL和API Key)
- 多语言本地化 - 多语言本地化
- 主题切换 - 主题切换
- 自动更新 - 自动更新
## Simple Deploy Example
```bash
git clone https://github.com/josStorer/RWKV-Runner
# 然后
cd RWKV-Runner
python ./backend-python/main.py #后端推理服务已启动, 调用/switch-model载入模型, 参考API文档: http://127.0.0.1:8000/docs
# 或者
cd RWKV-Runner/frontend
npm ci
npm run build #编译前端
cd ..
python ./backend-python/webui_server.py #单独启动前端服务
# 或者
python ./backend-python/main.py --webui #同时启动前后端服务
# 帮助参数
python ./backend-python/main.py -h
```
## API并发压力测试 ## API并发压力测试
```bash ```bash
@@ -130,6 +156,40 @@ for i in np.argsort(embeddings_cos_sim)[::-1]:
print(f"{embeddings_cos_sim[i]:.10f} - {values[i]}") print(f"{embeddings_cos_sim[i]:.10f} - {values[i]}")
``` ```
## MIDI Input
小贴士: 你可以下载 https://github.com/josStorer/sgm_plus, 并解压到程序的`assets/sound-font`目录, 以使用离线音源. 注意,
如果你正在从源码编译程序, 请不要将其放置在源码目录中
### USB MIDI 连接
- USB MIDI设备是即插即用的, 你能够在作曲页面选择你的输入设备
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/a448c34a-56d8-46eb-8dc2-dd11e8e0c4ce)
### Mac MIDI 蓝牙连接
- 对于想要使用蓝牙输入的Mac用户,
请安装[Bluetooth MIDI Connect](https://apps.apple.com/us/app/bluetooth-midi-connect/id1108321791), 启动后点击托盘连接,
之后你可以在作曲页面选择你的输入设备
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/c079a109-1e3d-45c1-bbf5-eed85da1550e)
### Windows MIDI 蓝牙连接
- Windows似乎只为UWP实现了蓝牙MIDI支持, 因此需要多个步骤进行连接, 我们需要创建一个本地的虚拟MIDI设备, 然后启动一个UWP应用,
通过此UWP应用将蓝牙MIDI输入重定向到虚拟MIDI设备, 然后本软件监听虚拟MIDI设备的输入
- 因此, 首先你需要下载[loopMIDI](https://www.tobias-erichsen.de/wp-content/uploads/2020/01/loopMIDISetup_1_0_16_27.zip),
用于创建虚拟MIDI设备, 点击左下角的加号创建设备
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/b75998ff-115c-4ddd-b97c-deeb5c106255)
- 然后, 你需要下载[Bluetooth LE Explorer](https://apps.microsoft.com/detail/9N0ZTKF1QD98), 以发现并连接蓝牙MIDI设备,
点击Start搜索设备, 然后点击Pair绑定MIDI设备
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/c142c3ea-a973-4531-9807-4c385d640a2b)
- 最后, 你需要安装[MIDIberry](https://apps.microsoft.com/detail/9N39720H2M05), 这个UWP应用能将MIDI蓝牙输入重定向到虚拟MIDI设备,
启动后, 在输入栏, 双击你实际的蓝牙MIDI设备名称, 在输出栏, 双击我们先前创建的虚拟MIDI设备名称
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/5ad6a1d9-4f68-4d95-ae17-4296107d1669)
- 现在, 你可以在作曲页面选择虚拟MIDI设备作为输入. Bluetooth LE Explorer不再需要运行, loopMIDI窗口也可以退出, 它会自动在后台运行,
仅保持MIDIberry打开即可
- ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/6460c355-884e-4b28-a2eb-8ab7a2e3a01a)
## 相关仓库: ## 相关仓库:
- RWKV-4-World: https://huggingface.co/BlinkDL/rwkv-4-world/tree/main - RWKV-4-World: https://huggingface.co/BlinkDL/rwkv-4-world/tree/main
@@ -143,27 +203,34 @@ for i in np.argsort(embeddings_cos_sim)[::-1]:
### 主页 ### 主页
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/ff2b1eef-dd3b-4cbf-98fb-b5a1ecee43e1) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/cd82674e-3ee3-4175-bd9c-a11d45437327)
### 聊天 ### 聊天
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/9570e73b-dca2-4316-9e92-09961f3c48c4) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/9570e73b-dca2-4316-9e92-09961f3c48c4)
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/54bb0e2b-cdc4-4ea0-8d16-9beaf57c232c)
### 续写 ### 续写
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/69f9ba7a-2fe8-4a5e-94cb-aa655aa409e2) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/69f9ba7a-2fe8-4a5e-94cb-aa655aa409e2)
### 作曲 ### 作曲
小贴士: 你可以下载 https://github.com/josStorer/sgm_plus, 并解压到程序的`assets/sound-font`目录, 以使用离线音源. 注意,
如果你正在从源码编译程序, 请不要将其放置在源码目录中
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/95b34893-80c2-4706-87f9-bc141032ed4b) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/95b34893-80c2-4706-87f9-bc141032ed4b)
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/3cb31ca8-d708-42f1-8768-1605fb0b2174)
### 配置 ### 配置
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/59460f69-b172-4c7a-86cb-573262543076) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/0f4d4f21-8abe-4f4d-8c4f-cd7d5607f20e)
### 模型管理 ### 模型管理
![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/551121ee-1bfe-421b-a9d1-24125126ab4b) ![image](https://github.com/josStorer/RWKV-Runner/assets/13366013/6bbbecec-d000-457e-993d-27ff53881d09)
### 下载管理 ### 下载管理

View File

@@ -4,12 +4,14 @@ import (
"bufio" "bufio"
"context" "context"
"errors" "errors"
"io"
"net/http" "net/http"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"syscall" "syscall"
"time"
"github.com/fsnotify/fsnotify" "github.com/fsnotify/fsnotify"
"github.com/minio/selfupdate" "github.com/minio/selfupdate"
@@ -43,7 +45,8 @@ func (a *App) OnStartup(ctx context.Context) {
a.cmdPrefix = "cd " + a.exDir + " && " a.cmdPrefix = "cd " + a.exDir + " && "
} }
os.Chmod("./backend-rust/webgpu_server", 0777) os.Chmod(a.exDir+"backend-rust/webgpu_server", 0777)
os.Chmod(a.exDir+"backend-rust/web-rwkv-converter", 0777)
os.Mkdir(a.exDir+"models", os.ModePerm) os.Mkdir(a.exDir+"models", os.ModePerm)
os.Mkdir(a.exDir+"lora-models", os.ModePerm) os.Mkdir(a.exDir+"lora-models", os.ModePerm)
os.Mkdir(a.exDir+"finetune/json2binidx_tool/data", os.ModePerm) os.Mkdir(a.exDir+"finetune/json2binidx_tool/data", os.ModePerm)
@@ -53,6 +56,7 @@ func (a *App) OnStartup(ctx context.Context) {
} }
a.downloadLoop() a.downloadLoop()
a.midiLoop()
a.watchFs() a.watchFs()
a.monitorHardware() a.monitorHardware()
} }
@@ -67,8 +71,8 @@ func (a *App) OnBeforeClose(ctx context.Context) bool {
func (a *App) watchFs() { func (a *App) watchFs() {
watcher, err := fsnotify.NewWatcher() watcher, err := fsnotify.NewWatcher()
if err == nil { if err == nil {
watcher.Add("./lora-models") watcher.Add(a.exDir + "./lora-models")
watcher.Add("./models") watcher.Add(a.exDir + "./models")
go func() { go func() {
for { for {
select { select {
@@ -118,13 +122,50 @@ func (a *App) monitorHardware() {
monitor.Start() monitor.Start()
} }
type ProgressReader struct {
reader io.Reader
total int64
err error
}
func (pr *ProgressReader) Read(p []byte) (n int, err error) {
n, err = pr.reader.Read(p)
pr.err = err
pr.total += int64(n)
return
}
func (a *App) UpdateApp(url string) (broken bool, err error) { func (a *App) UpdateApp(url string) (broken bool, err error) {
resp, err := http.Get(url) resp, err := http.Get(url)
if err != nil { if err != nil {
return false, err return false, err
} }
defer resp.Body.Close() defer resp.Body.Close()
err = selfupdate.Apply(resp.Body, selfupdate.Options{}) pr := &ProgressReader{reader: resp.Body}
ticker := time.NewTicker(250 * time.Millisecond)
defer ticker.Stop()
go func() {
for {
<-ticker.C
wruntime.EventsEmit(a.ctx, "updateApp", &DownloadStatus{
Name: filepath.Base(url),
Path: "",
Url: url,
Transferred: pr.total,
Size: resp.ContentLength,
Speed: 0,
Progress: 100 * (float64(pr.total) / float64(resp.ContentLength)),
Downloading: pr.err == nil && pr.total < resp.ContentLength,
Done: pr.total == resp.ContentLength,
})
if pr.err != nil || pr.total == resp.ContentLength {
break
}
}
}()
err = selfupdate.Apply(pr, selfupdate.Options{})
if err != nil { if err != nil {
if rerr := selfupdate.RollbackError(err); rerr != nil { if rerr := selfupdate.RollbackError(err); rerr != nil {
return true, rerr return true, rerr

View File

@@ -33,9 +33,9 @@ type DownloadStatus struct {
var downloadList []*DownloadStatus var downloadList []*DownloadStatus
func existsInDownloadList(url string) bool { func existsInDownloadList(path string, url string) bool {
for _, ds := range downloadList { for _, ds := range downloadList {
if ds.Url == url { if ds.Path == path || ds.Url == url {
return true return true
} }
} }
@@ -88,7 +88,7 @@ func (a *App) ContinueDownload(url string) {
} }
func (a *App) AddToDownloadList(path string, url string) { func (a *App) AddToDownloadList(path string, url string) {
if !existsInDownloadList(url) { if !existsInDownloadList(a.exDir+path, url) {
downloadList = append(downloadList, &DownloadStatus{ downloadList = append(downloadList, &DownloadStatus{
resp: nil, resp: nil,
Name: filepath.Base(path), Name: filepath.Base(path),

170
backend-golang/midi.go Normal file
View File

@@ -0,0 +1,170 @@
package backend_golang
import (
"errors"
"fmt"
"time"
"github.com/mattrtaylor/go-rtmidi"
"github.com/wailsapp/wails/v2/pkg/runtime"
)
type Port struct {
Name string `json:"name"`
}
type MIDIMessage struct {
MessageType string `json:"messageType"`
Channel int `json:"channel"`
Note int `json:"note"`
Velocity int `json:"velocity"`
Control int `json:"control"`
Value int `json:"value"`
}
var ports []Port
var input rtmidi.MIDIIn
var out rtmidi.MIDIOut
var activeIndex int = -1
var lastNoteTime time.Time
func (a *App) midiLoop() {
var err error
input, err = rtmidi.NewMIDIInDefault()
if err != nil {
runtime.EventsEmit(a.ctx, "midiError", err.Error())
return
}
out, err = rtmidi.NewMIDIOutDefault()
if err != nil {
runtime.EventsEmit(a.ctx, "midiError", err.Error())
}
err = out.OpenPort(0, "")
if err != nil {
runtime.EventsEmit(a.ctx, "midiError", err.Error())
}
ticker := time.NewTicker(500 * time.Millisecond)
go func() {
for {
<-ticker.C
count, err := input.PortCount()
if err != nil {
continue
}
ports = make([]Port, count)
for i := 0; i < count; i++ {
name, err := input.PortName(i)
if err == nil {
ports[i].Name = name
}
}
runtime.EventsEmit(a.ctx, "midiPorts", &ports)
}
}()
}
func (a *App) OpenMidiPort(index int) error {
if input == nil {
return errors.New("failed to initialize MIDI input")
}
if activeIndex == index {
return nil
}
input.Destroy()
var err error
input, err = rtmidi.NewMIDIInDefault()
if err != nil {
return err
}
err = input.SetCallback(func(msg rtmidi.MIDIIn, bytes []byte, t float64) {
// https://www.midi.org/specifications-old/item/table-1-summary-of-midi-message
// https://www.rfc-editor.org/rfc/rfc6295.html
//
// msgType channel
// 1001 0000
//
msgType := bytes[0] >> 4
channel := bytes[0] & 0x0f
switch msgType {
case 0x8:
elapsed := time.Since(lastNoteTime)
lastNoteTime = time.Now()
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
MessageType: "ElapsedTime",
Value: int(elapsed.Milliseconds()),
})
note := bytes[1]
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
MessageType: "NoteOff",
Channel: int(channel),
Note: int(note),
})
case 0x9:
elapsed := time.Since(lastNoteTime)
lastNoteTime = time.Now()
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
MessageType: "ElapsedTime",
Value: int(elapsed.Milliseconds()),
})
note := bytes[1]
velocity := bytes[2]
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
MessageType: "NoteOn",
Channel: int(channel),
Note: int(note),
Velocity: int(velocity),
})
case 0xb:
// control 12 => K1 knob, control 13 => K2 knob
control := bytes[1]
value := bytes[2]
runtime.EventsEmit(a.ctx, "midiMessage", &MIDIMessage{
MessageType: "ControlChange",
Channel: int(channel),
Control: int(control),
Value: int(value),
})
default:
fmt.Printf("Unknown midi message: %v\n", bytes)
}
})
if err != nil {
return err
}
err = input.OpenPort(index, "")
if err != nil {
return err
}
activeIndex = index
lastNoteTime = time.Now()
return nil
}
func (a *App) CloseMidiPort() error {
if input == nil {
return errors.New("failed to initialize MIDI input")
}
if activeIndex == -1 {
return nil
}
activeIndex = -1
input.Destroy()
var err error
input, err = rtmidi.NewMIDIInDefault()
if err != nil {
return err
}
return nil
}
func (a *App) PlayNote(msg MIDIMessage) error {
if out == nil {
return errors.New("failed to initialize MIDI output")
}
channelByte := byte(msg.Channel)
if msg.MessageType == "NoteOn" {
out.SendMessage([]byte{0x90 | channelByte, byte(msg.Note), byte(msg.Velocity)})
} else if msg.MessageType == "NoteOff" {
out.SendMessage([]byte{0x80 | channelByte, byte(msg.Note), byte(msg.Velocity)})
}
return nil
}

View File

@@ -10,7 +10,7 @@ import (
"strings" "strings"
) )
func (a *App) StartServer(python string, port int, host string, rwkvBeta bool) (string, error) { func (a *App) StartServer(python string, port int, host string, webui bool, rwkvBeta bool) (string, error) {
var err error var err error
if python == "" { if python == "" {
python, err = GetPython() python, err = GetPython()
@@ -19,6 +19,9 @@ func (a *App) StartServer(python string, port int, host string, rwkvBeta bool) (
return "", err return "", err
} }
args := []string{python, "./backend-python/main.py"} args := []string{python, "./backend-python/main.py"}
if webui {
args = append(args, "--webui")
}
if rwkvBeta { if rwkvBeta {
args = append(args, "--rwkv-beta") args = append(args, "--rwkv-beta")
} }
@@ -43,15 +46,10 @@ func (a *App) ConvertModel(python string, modelPath string, strategy string, out
return Cmd(python, "./backend-python/convert_model.py", "--in", modelPath, "--out", outPath, "--strategy", strategy) return Cmd(python, "./backend-python/convert_model.py", "--in", modelPath, "--out", outPath, "--strategy", strategy)
} }
func (a *App) ConvertSafetensors(python string, modelPath string, outPath string) (string, error) { func (a *App) ConvertSafetensors(modelPath string, outPath string) (string, error) {
var err error args := []string{"./backend-rust/web-rwkv-converter"}
if python == "" { args = append(args, "--input", modelPath, "--output", outPath)
python, err = GetPython() return Cmd(args...)
}
if err != nil {
return "", err
}
return Cmd(python, "./backend-python/convert_safetensors.py", "--input", modelPath, "--output", outPath)
} }
func (a *App) ConvertData(python string, input string, outputPrefix string, vocab string) (string, error) { func (a *App) ConvertData(python string, input string, outputPrefix string, vocab string) (string, error) {

View File

@@ -5,12 +5,15 @@ import (
"bufio" "bufio"
"embed" "embed"
"errors" "errors"
"fmt"
"io" "io"
"io/fs" "io/fs"
"net"
"os" "os"
"os/exec" "os/exec"
"path/filepath" "path/filepath"
"runtime" "runtime"
"strconv"
"strings" "strings"
) )
@@ -205,3 +208,12 @@ func Unzip(source, destination string) error {
} }
return nil return nil
} }
func (a *App) IsPortAvailable(port int) bool {
l, err := net.Listen("tcp", fmt.Sprintf("127.0.0.1:%s", strconv.Itoa(port)))
if err != nil {
return false
}
defer l.Close()
return true
}

View File

@@ -231,5 +231,6 @@ try:
convert_and_save_and_exit=args.out, convert_and_save_and_exit=args.out,
) )
except Exception as e: except Exception as e:
print(e)
with open("error.txt", "w") as f: with open("error.txt", "w") as f:
f.write(str(e)) f.write(str(e))

View File

@@ -25,7 +25,7 @@ def rename_key(rename, name):
return name return name
def convert_file(pt_filename: str, sf_filename: str, transpose_names=[], rename={}): def convert_file(pt_filename: str, sf_filename: str, rename={}, transpose_names=[]):
loaded = torch.load(pt_filename, map_location="cpu") loaded = torch.load(pt_filename, map_location="cpu")
if "state_dict" in loaded: if "state_dict" in loaded:
loaded = loaded["state_dict"] loaded = loaded["state_dict"]
@@ -34,12 +34,14 @@ def convert_file(pt_filename: str, sf_filename: str, transpose_names=[], rename=
# for k, v in loaded.items(): # for k, v in loaded.items():
# print(f'{k}\t{v.shape}\t{v.dtype}') # print(f'{k}\t{v.shape}\t{v.dtype}')
loaded = {rename_key(rename, k).lower(): v.contiguous() for k, v in loaded.items()}
# For tensors to be contiguous # For tensors to be contiguous
for k, v in loaded.items(): for k, v in loaded.items():
for transpose_name in transpose_names: for transpose_name in transpose_names:
if transpose_name in k: if transpose_name in k:
loaded[k] = v.transpose(0, 1) loaded[k] = v.transpose(0, 1)
loaded = {rename_key(rename, k).lower(): v.contiguous() for k, v in loaded.items()}
loaded = {k: v.clone().half().contiguous() for k, v in loaded.items()}
for k, v in loaded.items(): for k, v in loaded.items():
print(f"{k}\t{v.shape}\t{v.dtype}") print(f"{k}\t{v.shape}\t{v.dtype}")
@@ -60,10 +62,21 @@ if __name__ == "__main__":
convert_file( convert_file(
args.input, args.input,
args.output, args.output,
["lora_A"], rename={
{"time_faaaa": "time_first", "lora_A": "lora.0", "lora_B": "lora.1"}, "time_faaaa": "time_first",
"time_maa": "time_mix",
"lora_A": "lora.0",
"lora_B": "lora.1",
},
transpose_names=[
"time_mix_w1",
"time_mix_w2",
"time_decay_w1",
"time_decay_w2",
],
) )
print(f"Saved to {args.output}") print(f"Saved to {args.output}")
except Exception as e: except Exception as e:
print(e)
with open("error.txt", "w") as f: with open("error.txt", "w") as f:
f.write(str(e)) f.write(str(e))

View File

@@ -4,6 +4,7 @@ Args = "args"
Model = "model" Model = "model"
Model_Status = "model_status" Model_Status = "model_status"
Model_Config = "model_config" Model_Config = "model_config"
Deploy_Mode = "deploy_mode"
class ModelStatus(Enum): class ModelStatus(Enum):
@@ -16,6 +17,7 @@ def init():
global GLOBALS global GLOBALS
GLOBALS = {} GLOBALS = {}
set(Model_Status, ModelStatus.Offline) set(Model_Status, ModelStatus.Offline)
set(Deploy_Mode, False)
def set(key, value): def set(key, value):

View File

@@ -2,17 +2,53 @@ import time
start_time = time.time() start_time = time.time()
import setuptools # avoid warnings import argparse
from typing import Union, Sequence
def get_args(args: Union[Sequence[str], None] = None):
parser = argparse.ArgumentParser()
group = parser.add_argument_group(title="server arguments")
group.add_argument(
"--port",
type=int,
default=8000,
help="port to run the server on (default: 8000)",
)
group.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="host to run the server on (default: 127.0.0.1)",
)
group = parser.add_argument_group(title="mode arguments")
group.add_argument(
"--webui",
action="store_true",
help="whether to enable WebUI (default: False)",
)
group.add_argument(
"--rwkv-beta",
action="store_true",
help="whether to use rwkv-beta (default: False)",
)
args = parser.parse_args(args)
return args
if __name__ == "__main__":
args = get_args()
import os import os
import sys import sys
import argparse
from typing import Sequence
from contextlib import asynccontextmanager
sys.path.append(os.path.dirname(os.path.realpath(__file__))) sys.path.append(os.path.dirname(os.path.realpath(__file__)))
import psutil import psutil
from fastapi import Depends, FastAPI from contextlib import asynccontextmanager
from fastapi import Depends, FastAPI, status
from fastapi.middleware.cors import CORSMiddleware from fastapi.middleware.cors import CORSMiddleware
import uvicorn import uvicorn
@@ -48,6 +84,35 @@ app.include_router(misc.router)
app.include_router(state_cache.router) app.include_router(state_cache.router)
@app.post("/exit", tags=["Root"])
def exit():
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
parent_pid = os.getpid()
parent = psutil.Process(parent_pid)
for child in parent.children(recursive=True):
child.kill()
parent.kill()
try:
if (
"RWKV_RUNNER_PARAMS" in os.environ
and "--webui" in os.environ["RWKV_RUNNER_PARAMS"].split(" ")
) or args.webui:
from webui_server import webui_server
app.mount("/", webui_server)
except NameError:
pass
@app.get("/", tags=["Root"])
def read_root():
return {"Hello": "World!"}
def init(): def init():
global_var.init() global_var.init()
cmd_params = os.environ["RWKV_RUNNER_PARAMS"] cmd_params = os.environ["RWKV_RUNNER_PARAMS"]
@@ -63,48 +128,7 @@ def init():
ngrok_connect() ngrok_connect()
@app.get("/", tags=["Root"])
def read_root():
return {"Hello": "World!"}
@app.post("/exit", tags=["Root"])
def exit():
parent_pid = os.getpid()
parent = psutil.Process(parent_pid)
for child in parent.children(recursive=True):
child.kill()
parent.kill()
def get_args(args: Union[Sequence[str], None] = None):
parser = argparse.ArgumentParser()
group = parser.add_argument_group(title="server arguments")
group.add_argument(
"--port",
type=int,
default=8000,
help="port to run the server on (default: 8000)",
)
group.add_argument(
"--host",
type=str,
default="127.0.0.1",
help="host to run the server on (default: 127.0.0.1)",
)
group = parser.add_argument_group(title="mode arguments")
group.add_argument(
"--rwkv-beta",
action="store_true",
help="whether to use rwkv-beta (default: False)",
)
args = parser.parse_args(args)
return args
if __name__ == "__main__": if __name__ == "__main__":
args = get_args()
os.environ["RWKV_RUNNER_PARAMS"] = " ".join(sys.argv[1:]) os.environ["RWKV_RUNNER_PARAMS"] = " ".join(sys.argv[1:])
print("--- %s seconds ---" % (time.time() - start_time)) print("--- %s seconds ---" % (time.time() - start_time))
uvicorn.run("main:app", port=args.port, host=args.host, workers=1) uvicorn.run("main:app", port=args.port, host=args.host, workers=1)

Binary file not shown.

View File

@@ -35,6 +35,11 @@ default_stop = [
"\n\nQ", "\n\nQ",
"\n\nHuman", "\n\nHuman",
"\n\nBob", "\n\nBob",
"\n\nAssistant",
"\n\nAnswer",
"\n\nA",
"\n\nBot",
"\n\nAlice",
] ]
@@ -53,8 +58,8 @@ class ChatCompletionBody(ModelConfigBody):
True, description="Whether to insert default system prompt at the beginning" True, description="Whether to insert default system prompt at the beginning"
) )
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"messages": [ "messages": [
{"role": Role.User.value, "content": "hello", "raw": False} {"role": Role.User.value, "content": "hello", "raw": False}
@@ -72,6 +77,7 @@ class ChatCompletionBody(ModelConfigBody):
"frequency_penalty": 0.4, "frequency_penalty": 0.4,
} }
} }
}
class CompletionBody(ModelConfigBody): class CompletionBody(ModelConfigBody):
@@ -80,8 +86,8 @@ class CompletionBody(ModelConfigBody):
stream: bool = False stream: bool = False
stop: Union[str, List[str], None] = None stop: Union[str, List[str], None] = None
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"prompt": "The following is an epic science fiction masterpiece that is immortalized, " "prompt": "The following is an epic science fiction masterpiece that is immortalized, "
+ "with delicate descriptions and grand depictions of interstellar civilization wars.\nChapter 1.\n", + "with delicate descriptions and grand depictions of interstellar civilization wars.\nChapter 1.\n",
@@ -95,6 +101,7 @@ class CompletionBody(ModelConfigBody):
"frequency_penalty": 0.4, "frequency_penalty": 0.4,
} }
} }
}
completion_lock = Lock() completion_lock = Lock()
@@ -376,8 +383,8 @@ class EmbeddingsBody(BaseModel):
encoding_format: str = None encoding_format: str = None
fast_mode: bool = False fast_mode: bool = False
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"input": "a big apple", "input": "a big apple",
"model": "rwkv", "model": "rwkv",
@@ -385,6 +392,7 @@ class EmbeddingsBody(BaseModel):
"fast_mode": False, "fast_mode": False,
} }
} }
}
def embedding_base64(embedding: List[float]) -> str: def embedding_base64(embedding: List[float]) -> str:

View File

@@ -15,20 +15,29 @@ class SwitchModelBody(BaseModel):
strategy: str strategy: str
tokenizer: Union[str, None] = None tokenizer: Union[str, None] = None
customCuda: bool = False customCuda: bool = False
deploy: bool = Field(
False,
description="Deploy mode. If success, will disable /switch-model, /exit and other dangerous APIs (state cache APIs, part of midi APIs)",
)
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"model": "models/RWKV-4-World-3B-v1-20230619-ctx4096.pth", "model": "models/RWKV-4-World-3B-v1-20230619-ctx4096.pth",
"strategy": "cuda fp16", "strategy": "cuda fp16",
"tokenizer": None, "tokenizer": "",
"customCuda": False, "customCuda": False,
"deploy": False,
} }
} }
}
@router.post("/switch-model", tags=["Configs"]) @router.post("/switch-model", tags=["Configs"])
def switch_model(body: SwitchModelBody, response: Response, request: Request): def switch_model(body: SwitchModelBody, response: Response, request: Request):
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(Status.HTTP_403_FORBIDDEN)
if global_var.get(global_var.Model_Status) is global_var.ModelStatus.Loading: if global_var.get(global_var.Model_Status) is global_var.ModelStatus.Loading:
response.status_code = Status.HTTP_304_NOT_MODIFIED response.status_code = Status.HTTP_304_NOT_MODIFIED
return return
@@ -40,13 +49,26 @@ def switch_model(body: SwitchModelBody, response: Response, request: Request):
if body.model == "": if body.model == "":
return "success" return "success"
if "->" in body.strategy: STRATEGY_REGEX = r"^(?:(?:^|->) *(?:cuda(?::[\d]+)?|cpu|mps|dml) (?:fp(?:16|32)|bf16)(?:i8|i4|i3)?(?: \*[\d]+\+?)? *)+$"
state_cache.disable_state_cache() if not re.match(STRATEGY_REGEX, body.strategy):
else: raise HTTPException(
try: Status.HTTP_400_BAD_REQUEST,
state_cache.enable_state_cache() "Invalid strategy. Please read https://pypi.org/project/rwkv/",
except HTTPException: )
pass devices = set(
[
x.strip().split(" ")[0].replace("cuda:0", "cuda")
for x in body.strategy.split("->")
]
)
print(f"Devices: {devices}")
# if len(devices) > 1:
# state_cache.disable_state_cache()
# else:
try:
state_cache.enable_state_cache()
except HTTPException:
pass
os.environ["RWKV_CUDA_ON"] = "1" if body.customCuda else "0" os.environ["RWKV_CUDA_ON"] = "1" if body.customCuda else "0"
@@ -64,6 +86,8 @@ def switch_model(body: SwitchModelBody, response: Response, request: Request):
Status.HTTP_500_INTERNAL_SERVER_ERROR, f"failed to load: {e}" Status.HTTP_500_INTERNAL_SERVER_ERROR, f"failed to load: {e}"
) )
if body.deploy:
global_var.set(global_var.Deploy_Mode, True)
if global_var.get(global_var.Model_Config) is None: if global_var.get(global_var.Model_Config) is None:
global_var.set( global_var.set(
global_var.Model_Config, get_rwkv_config(global_var.get(global_var.Model)) global_var.Model_Config, get_rwkv_config(global_var.get(global_var.Model))

View File

@@ -1,4 +1,5 @@
import io import io
import global_var
from fastapi import APIRouter, HTTPException, status from fastapi import APIRouter, HTTPException, status
from starlette.responses import StreamingResponse from starlette.responses import StreamingResponse
from pydantic import BaseModel from pydantic import BaseModel
@@ -11,12 +12,13 @@ router = APIRouter()
class TextToMidiBody(BaseModel): class TextToMidiBody(BaseModel):
text: str text: str
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"text": "p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0", "text": "p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0",
} }
} }
}
@router.post("/text-to-midi", tags=["MIDI"]) @router.post("/text-to-midi", tags=["MIDI"])
@@ -35,17 +37,21 @@ class TxtToMidiBody(BaseModel):
txt_path: str txt_path: str
midi_path: str midi_path: str
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"txt_path": "midi/sample.txt", "txt_path": "midi/sample.txt",
"midi_path": "midi/sample.mid", "midi_path": "midi/sample.mid",
} }
} }
}
@router.post("/txt-to-midi", tags=["MIDI"]) @router.post("/txt-to-midi", tags=["MIDI"])
def txt_to_midi(body: TxtToMidiBody): def txt_to_midi(body: TxtToMidiBody):
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
if not body.midi_path.startswith("midi/"): if not body.midi_path.startswith("midi/"):
raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path") raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path")
@@ -65,14 +71,15 @@ class MidiToWavBody(BaseModel):
wav_path: str wav_path: str
sound_font_path: str = "assets/default_sound_font.sf2" sound_font_path: str = "assets/default_sound_font.sf2"
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"midi_path": "midi/sample.mid", "midi_path": "midi/sample.mid",
"wav_path": "midi/sample.wav", "wav_path": "midi/sample.wav",
"sound_font_path": "assets/default_sound_font.sf2", "sound_font_path": "assets/default_sound_font.sf2",
} }
} }
}
@router.post("/midi-to-wav", tags=["MIDI"]) @router.post("/midi-to-wav", tags=["MIDI"])
@@ -81,6 +88,9 @@ def midi_to_wav(body: MidiToWavBody):
Install fluidsynth first, see more: https://github.com/FluidSynth/fluidsynth/wiki/Download#distributions Install fluidsynth first, see more: https://github.com/FluidSynth/fluidsynth/wiki/Download#distributions
""" """
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
if not body.wav_path.startswith("midi/"): if not body.wav_path.startswith("midi/"):
raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path") raise HTTPException(status.HTTP_400_BAD_REQUEST, "bad output path")
@@ -95,14 +105,15 @@ class TextToWavBody(BaseModel):
wav_name: str wav_name: str
sound_font_path: str = "assets/default_sound_font.sf2" sound_font_path: str = "assets/default_sound_font.sf2"
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"text": "p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0", "text": "p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:2e:a p:3b:a p:45:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2e:0 p:3b:0 p:45:0 b:26:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:26:a g:3e:a g:3e:a g:42:a g:42:a g:45:a g:45:a pi:3e:a pi:42:a pi:45:a t14 p:2a:0 p:3b:0 p:45:0 b:26:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:2d:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:2d:0 g:3e:0 g:3e:0 g:42:0 g:42:0 g:45:0 g:45:0 pi:3e:0 pi:42:0 pi:45:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2a:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a g:39:a g:39:a g:3d:a g:3d:a g:40:a g:40:a pi:39:a pi:3d:a pi:40:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 t2 p:26:a p:2e:a p:31:a p:39:a p:3b:a p:45:a b:21:a t14 p:26:0 p:2e:0 p:31:0 p:39:0 p:3b:0 p:45:0 b:21:0 g:39:0 g:39:0 g:3d:0 g:3d:0 g:40:0 g:40:0 pi:39:0 pi:3d:0 pi:40:0 t2 p:24:a p:2a:a p:31:a p:39:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:24:0 p:2a:0 p:31:0 p:39:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:2e:a p:3b:a p:45:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2e:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:26:a p:2a:a p:3b:a p:45:a t14 p:26:0 p:2a:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a b:1f:a g:3b:a g:3b:a g:3e:a g:3e:a g:43:a g:43:a pi:3b:a pi:3e:a pi:43:a t14 p:2a:0 p:3b:0 p:45:0 b:1f:0 t2 p:24:a p:2a:a p:3b:a p:45:a b:1f:a t14 p:24:0 p:2a:0 p:3b:0 p:45:0 b:1f:0 g:3b:0 g:3b:0 g:3e:0 g:3e:0 g:43:0 g:43:0 pi:3b:0 pi:3e:0 pi:43:0 t2 p:24:a p:2e:a p:3b:a p:45:a b:26:a g:39:a g:39:a g:3e:a g:3e:a g:42:a g:42:a pi:39:a pi:3e:a pi:42:a t14 p:24:0 p:2e:0 p:3b:0 p:45:0 t2 p:2a:a p:3b:a p:45:a t14 p:2a:0 p:3b:0",
"wav_name": "sample", "wav_name": "sample",
"sound_font_path": "assets/default_sound_font.sf2", "sound_font_path": "assets/default_sound_font.sf2",
} }
} }
}
@router.post("/text-to-wav", tags=["MIDI"]) @router.post("/text-to-wav", tags=["MIDI"])
@@ -111,6 +122,9 @@ def text_to_wav(body: TextToWavBody):
Install fluidsynth first, see more: https://github.com/FluidSynth/fluidsynth/wiki/Download#distributions Install fluidsynth first, see more: https://github.com/FluidSynth/fluidsynth/wiki/Download#distributions
""" """
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
text = body.text.strip() text = body.text.strip()
if not text.startswith("<start>"): if not text.startswith("<start>"):
text = "<start> " + text text = "<start> " + text

View File

@@ -4,12 +4,13 @@ from fastapi import APIRouter, HTTPException, Request, Response, status
from pydantic import BaseModel from pydantic import BaseModel
import gc import gc
import copy import copy
import global_var
router = APIRouter() router = APIRouter()
trie = None trie = None
dtrie: Dict = {} dtrie: Dict = {}
max_trie_len = 3000 max_trie_len = 300
loop_start_id = 1 # to prevent preloaded prompts from being deleted loop_start_id = 1 # to prevent preloaded prompts from being deleted
loop_del_trie_id = loop_start_id loop_del_trie_id = loop_start_id
@@ -36,16 +37,24 @@ def init():
def disable_state_cache(): def disable_state_cache():
global trie, dtrie global trie, dtrie
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
trie = None trie = None
dtrie = {} dtrie = {}
gc.collect() gc.collect()
print("state cache disabled")
return "success" return "success"
@router.post("/enable-state-cache", tags=["State Cache"]) @router.post("/enable-state-cache", tags=["State Cache"])
def enable_state_cache(): def enable_state_cache():
global trie, dtrie global trie, dtrie
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
try: try:
import cyac import cyac
@@ -53,8 +62,10 @@ def enable_state_cache():
dtrie = {} dtrie = {}
gc.collect() gc.collect()
print("state cache enabled")
return "success" return "success"
except ModuleNotFoundError: except ModuleNotFoundError:
print("state cache disabled")
raise HTTPException(status.HTTP_400_BAD_REQUEST, "cyac not found") raise HTTPException(status.HTTP_400_BAD_REQUEST, "cyac not found")
@@ -65,9 +76,13 @@ class AddStateBody(BaseModel):
logits: Any logits: Any
@router.post("/add-state", tags=["State Cache"]) # @router.post("/add-state", tags=["State Cache"])
def add_state(body: AddStateBody): def add_state(body: AddStateBody):
global trie, dtrie, loop_del_trie_id global trie, dtrie, loop_del_trie_id
# if global_var.get(global_var.Deploy_Mode) is True:
# raise HTTPException(status.HTTP_403_FORBIDDEN)
if trie is None: if trie is None:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded") raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded")
@@ -75,14 +90,12 @@ def add_state(body: AddStateBody):
try: try:
id: int = trie.insert(body.prompt) id: int = trie.insert(body.prompt)
device: torch.device = body.state[0].device devices: List[torch.device] = [tensor.device for tensor in body.state]
dtrie[id] = { dtrie[id] = {
"tokens": copy.deepcopy(body.tokens), "tokens": copy.deepcopy(body.tokens),
"state": [tensor.cpu() for tensor in body.state] "state": [tensor.cpu() for tensor in body.state],
if device != torch.device("cpu")
else copy.deepcopy(body.state),
"logits": copy.deepcopy(body.logits), "logits": copy.deepcopy(body.logits),
"device": device, "devices": devices,
} }
if len(trie) >= max_trie_len: if len(trie) >= max_trie_len:
@@ -108,6 +121,10 @@ def add_state(body: AddStateBody):
@router.post("/reset-state", tags=["State Cache"]) @router.post("/reset-state", tags=["State Cache"])
def reset_state(): def reset_state():
global trie, dtrie global trie, dtrie
if global_var.get(global_var.Deploy_Mode) is True:
raise HTTPException(status.HTTP_403_FORBIDDEN)
if trie is None: if trie is None:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded") raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded")
@@ -141,9 +158,13 @@ def __get_a_dtrie_buff_size(dtrie_v):
return 54 * len(dtrie_v["tokens"]) + 491520 + 262144 + 28 # TODO return 54 * len(dtrie_v["tokens"]) + 491520 + 262144 + 28 # TODO
@router.post("/longest-prefix-state", tags=["State Cache"]) # @router.post("/longest-prefix-state", tags=["State Cache"])
def longest_prefix_state(body: LongestPrefixStateBody, request: Request): def longest_prefix_state(body: LongestPrefixStateBody, request: Request):
global trie global trie
# if global_var.get(global_var.Deploy_Mode) is True:
# raise HTTPException(status.HTTP_403_FORBIDDEN)
if trie is None: if trie is None:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded") raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded")
@@ -157,32 +178,27 @@ def longest_prefix_state(body: LongestPrefixStateBody, request: Request):
pass pass
if id != -1: if id != -1:
v = dtrie[id] v = dtrie[id]
device: torch.device = v["device"] devices: List[torch.device] = v["devices"]
prompt: str = trie[id] prompt: str = trie[id]
quick_log(request, body, "Hit:\n" + prompt) quick_log(request, body, "Hit:\n" + prompt)
return { return {
"prompt": prompt, "prompt": prompt,
"tokens": v["tokens"], "tokens": v["tokens"],
"state": [tensor.to(device) for tensor in v["state"]] "state": [tensor.to(devices[i]) for i, tensor in enumerate(v["state"])],
if device != torch.device("cpu")
else v["state"],
"logits": v["logits"], "logits": v["logits"],
"device": device.type,
} }
else: else:
return { return {"prompt": "", "tokens": [], "state": None, "logits": None}
"prompt": "",
"tokens": [],
"state": None,
"logits": None,
"device": None,
}
@router.post("/save-state", tags=["State Cache"]) # @router.post("/save-state", tags=["State Cache"])
def save_state(): def save_state():
global trie global trie
# if global_var.get(global_var.Deploy_Mode) is True:
# raise HTTPException(status.HTTP_403_FORBIDDEN)
if trie is None: if trie is None:
raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded") raise HTTPException(status.HTTP_400_BAD_REQUEST, "trie not loaded")

View File

@@ -3,6 +3,8 @@
#include <cuda_fp16.h> #include <cuda_fp16.h>
#include <cuda_runtime.h> #include <cuda_runtime.h>
#include <torch/extension.h> #include <torch/extension.h>
#include <c10/cuda/CUDAGuard.h>
#include <ATen/cuda/CUDAContext.h>
#define CUBLAS_CHECK(condition) \ #define CUBLAS_CHECK(condition) \
for (cublasStatus_t _cublas_check_status = (condition); \ for (cublasStatus_t _cublas_check_status = (condition); \
@@ -18,26 +20,13 @@
"CUDA error " + std::string(cudaGetErrorString(_cuda_check_status)) + \ "CUDA error " + std::string(cudaGetErrorString(_cuda_check_status)) + \
" at " + std::to_string(__LINE__)); " at " + std::to_string(__LINE__));
cublasHandle_t get_cublas_handle() {
static cublasHandle_t cublas_handle = []() {
cublasHandle_t handle = nullptr;
CUBLAS_CHECK(cublasCreate(&handle));
#if CUDA_VERSION < 11000
CUBLAS_CHECK(cublasSetMathMode(handle, CUBLAS_TENSOR_OP_MATH));
#else
CUBLAS_CHECK(cublasSetMathMode(handle, CUBLAS_DEFAULT_MATH));
#endif // CUDA_VERSION < 11000
return handle;
}();
return cublas_handle;
}
/* /*
NOTE: blas gemm is column-major by default, but we need row-major output. NOTE: blas gemm is column-major by default, but we need row-major output.
The data of row-major, transposed matrix is exactly the same as the The data of row-major, transposed matrix is exactly the same as the
column-major, non-transposed matrix, and C = A * B ---> C^T = B^T * A^T column-major, non-transposed matrix, and C = A * B ---> C^T = B^T * A^T
*/ */
void gemm_fp16_cublas(torch::Tensor a, torch::Tensor b, torch::Tensor c) { void gemm_fp16_cublas(torch::Tensor a, torch::Tensor b, torch::Tensor c) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(a));
const auto cuda_data_type = CUDA_R_16F; const auto cuda_data_type = CUDA_R_16F;
const auto cuda_c_data_type = const auto cuda_c_data_type =
c.dtype() == torch::kFloat32 ? CUDA_R_32F : CUDA_R_16F; c.dtype() == torch::kFloat32 ? CUDA_R_32F : CUDA_R_16F;
@@ -55,7 +44,7 @@ void gemm_fp16_cublas(torch::Tensor a, torch::Tensor b, torch::Tensor c) {
const int cublas_lda = m; const int cublas_lda = m;
const int cublas_ldb = k; const int cublas_ldb = k;
const int cublas_ldc = m; const int cublas_ldc = m;
cublasHandle_t cublas_handle = get_cublas_handle(); cublasHandle_t cublas_handle = at::cuda::getCurrentCUDABlasHandle();
#if CUDA_VERSION >= 11000 #if CUDA_VERSION >= 11000
cublasGemmAlgo_t algo = CUBLAS_GEMM_DEFAULT; cublasGemmAlgo_t algo = CUBLAS_GEMM_DEFAULT;

View File

@@ -1,5 +1,6 @@
#include <torch/extension.h> #include <torch/extension.h>
#include "ATen/ATen.h" #include "ATen/ATen.h"
#include <c10/cuda/CUDAGuard.h>
typedef at::BFloat16 bf16; typedef at::BFloat16 bf16;
typedef at::Half fp16; typedef at::Half fp16;
typedef float fp32; typedef float fp32;
@@ -9,12 +10,15 @@ void cuda_forward_fp16(int B, int T, int C, int H, float *state, fp16 *r, fp16 *
void cuda_forward_fp32(int B, int T, int C, int H, float *state, fp32 *r, fp32 *k, fp32 *v, float *w, fp32 *u, fp32 *y); void cuda_forward_fp32(int B, int T, int C, int H, float *state, fp32 *r, fp32 *k, fp32 *v, float *w, fp32 *u, fp32 *y);
void forward_bf16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) { void forward_bf16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_bf16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<bf16>(), k.data_ptr<bf16>(), v.data_ptr<bf16>(), w.data_ptr<float>(), u.data_ptr<bf16>(), y.data_ptr<bf16>()); cuda_forward_bf16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<bf16>(), k.data_ptr<bf16>(), v.data_ptr<bf16>(), w.data_ptr<float>(), u.data_ptr<bf16>(), y.data_ptr<bf16>());
} }
void forward_fp16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) { void forward_fp16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_fp16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp16>(), k.data_ptr<fp16>(), v.data_ptr<fp16>(), w.data_ptr<float>(), u.data_ptr<fp16>(), y.data_ptr<fp16>()); cuda_forward_fp16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp16>(), k.data_ptr<fp16>(), v.data_ptr<fp16>(), w.data_ptr<float>(), u.data_ptr<fp16>(), y.data_ptr<fp16>());
} }
void forward_fp32(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) { void forward_fp32(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_fp32(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp32>(), k.data_ptr<fp32>(), v.data_ptr<fp32>(), w.data_ptr<float>(), u.data_ptr<fp32>(), y.data_ptr<fp32>()); cuda_forward_fp32(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp32>(), k.data_ptr<fp32>(), v.data_ptr<fp32>(), w.data_ptr<float>(), u.data_ptr<fp32>(), y.data_ptr<fp32>());
} }

87
backend-python/rwkv_pip/cuda/rwkv6.cu vendored Normal file
View File

@@ -0,0 +1,87 @@
#include <stdio.h>
#include <assert.h>
#include "ATen/ATen.h"
typedef at::BFloat16 bf16;
typedef at::Half fp16;
typedef float fp32;
template <typename F>
__global__ void kernel_forward(const int B, const int T, const int C, const int H, float *__restrict__ _state,
const F *__restrict__ const _r, const F *__restrict__ const _k, const F *__restrict__ const _v, const float *__restrict__ _w, const F *__restrict__ _u,
F *__restrict__ const _y)
{
const int b = blockIdx.x / H;
const int h = blockIdx.x % H;
const int i = threadIdx.x;
_u += h*_N_;
_state += h*_N_*_N_ + i*_N_; // wrong if B > 1 !!!
__shared__ float r[_N_], k[_N_], u[_N_], w[_N_];
float state[_N_];
#pragma unroll
for (int j = 0; j < _N_; j++)
state[j] = _state[j];
__syncthreads();
u[i] = float(_u[i]);
__syncthreads();
for (int t = b*T*C + h*_N_ + i; t < (b+1)*T*C + h*_N_ + i; t += C)
{
__syncthreads();
w[i] = _w[t];
r[i] = float(_r[t]);
k[i] = float(_k[t]);
__syncthreads();
const float v = float(_v[t]);
float y = 0;
#pragma unroll
for (int j = 0; j < _N_; j+=4)
{
const float4& r_ = (float4&)(r[j]);
const float4& k_ = (float4&)(k[j]);
const float4& w_ = (float4&)(w[j]);
const float4& u_ = (float4&)(u[j]);
float4& s = (float4&)(state[j]);
float4 x;
x.x = k_.x * v;
x.y = k_.y * v;
x.z = k_.z * v;
x.w = k_.w * v;
y += r_.x * (u_.x * x.x + s.x);
y += r_.y * (u_.y * x.y + s.y);
y += r_.z * (u_.z * x.z + s.z);
y += r_.w * (u_.w * x.w + s.w);
s.x = s.x * w_.x + x.x;
s.y = s.y * w_.y + x.y;
s.z = s.z * w_.z + x.z;
s.w = s.w * w_.w + x.w;
}
_y[t] = F(y);
}
#pragma unroll
for (int j = 0; j < _N_; j++)
_state[j] = state[j];
}
void cuda_forward_bf16(int B, int T, int C, int H, float *state, bf16 *r, bf16 *k, bf16 *v, float *w, bf16 *u, bf16 *y)
{
assert(H*_N_ == C);
kernel_forward<<<dim3(B * H), dim3(_N_)>>>(B, T, C, H, state, r, k, v, w, u, y);
}
void cuda_forward_fp16(int B, int T, int C, int H, float *state, fp16 *r, fp16 *k, fp16 *v, float *w, fp16 *u, fp16 *y)
{
assert(H*_N_ == C);
kernel_forward<<<dim3(B * H), dim3(_N_)>>>(B, T, C, H, state, r, k, v, w, u, y);
}
void cuda_forward_fp32(int B, int T, int C, int H, float *state, fp32 *r, fp32 *k, fp32 *v, float *w, fp32 *u, fp32 *y)
{
assert(H*_N_ == C);
kernel_forward<<<dim3(B * H), dim3(_N_)>>>(B, T, C, H, state, r, k, v, w, u, y);
}

View File

@@ -0,0 +1,34 @@
#include <torch/extension.h>
#include "ATen/ATen.h"
#include <c10/cuda/CUDAGuard.h>
typedef at::BFloat16 bf16;
typedef at::Half fp16;
typedef float fp32;
void cuda_forward_bf16(int B, int T, int C, int H, float *state, bf16 *r, bf16 *k, bf16 *v, float *w, bf16 *u, bf16 *y);
void cuda_forward_fp16(int B, int T, int C, int H, float *state, fp16 *r, fp16 *k, fp16 *v, float *w, fp16 *u, fp16 *y);
void cuda_forward_fp32(int B, int T, int C, int H, float *state, fp32 *r, fp32 *k, fp32 *v, float *w, fp32 *u, fp32 *y);
void forward_bf16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_bf16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<bf16>(), k.data_ptr<bf16>(), v.data_ptr<bf16>(), w.data_ptr<float>(), u.data_ptr<bf16>(), y.data_ptr<bf16>());
}
void forward_fp16(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_fp16(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp16>(), k.data_ptr<fp16>(), v.data_ptr<fp16>(), w.data_ptr<float>(), u.data_ptr<fp16>(), y.data_ptr<fp16>());
}
void forward_fp32(int64_t B, int64_t T, int64_t C, int64_t H, torch::Tensor &state, torch::Tensor &r, torch::Tensor &k, torch::Tensor &v, torch::Tensor &w, torch::Tensor &u, torch::Tensor &y) {
const at::cuda::OptionalCUDAGuard device_guard(device_of(state));
cuda_forward_fp32(B, T, C, H, state.data_ptr<float>(), r.data_ptr<fp32>(), k.data_ptr<fp32>(), v.data_ptr<fp32>(), w.data_ptr<float>(), u.data_ptr<fp32>(), y.data_ptr<fp32>());
}
PYBIND11_MODULE(TORCH_EXTENSION_NAME, m) {
m.def("forward_bf16", &forward_bf16, "rwkv6 forward_bf16");
m.def("forward_fp16", &forward_fp16, "rwkv6 forward_fp16");
m.def("forward_fp32", &forward_fp32, "rwkv6 forward_fp32");
}
TORCH_LIBRARY(rwkv6, m) {
m.def("forward_bf16", forward_bf16);
m.def("forward_fp16", forward_fp16);
m.def("forward_fp32", forward_fp32);
}

File diff suppressed because it is too large Load Diff

Binary file not shown.

BIN
backend-python/rwkv_pip/rwkv6.pyd vendored Normal file

Binary file not shown.

View File

@@ -81,8 +81,9 @@ class PIPELINE:
def sample_logits(self, logits, temperature=1.0, top_p=0.85, top_k=0): def sample_logits(self, logits, temperature=1.0, top_p=0.85, top_k=0):
probs = F.softmax(logits.float(), dim=-1) probs = F.softmax(logits.float(), dim=-1)
top_k = int(top_k) top_k = int(top_k)
if probs.device == torch.device("cpu"): # 'privateuseone' is the type of custom devices like `torch_directml.device()`
probs = probs.numpy() if probs.device.type in ["cpu", "privateuseone"]:
probs = probs.cpu().numpy()
sorted_ids = np.argsort(probs) sorted_ids = np.argsort(probs)
sorted_probs = probs[sorted_ids][::-1] sorted_probs = probs[sorted_ids][::-1]
cumulative_probs = np.cumsum(sorted_probs) cumulative_probs = np.cumsum(sorted_probs)

Binary file not shown.

View File

@@ -10,7 +10,7 @@ logger = logging.getLogger()
logger.setLevel(logging.INFO) logger.setLevel(logging.INFO)
formatter = logging.Formatter("%(asctime)s - %(levelname)s\n%(message)s") formatter = logging.Formatter("%(asctime)s - %(levelname)s\n%(message)s")
fh = logging.handlers.RotatingFileHandler( fh = logging.handlers.RotatingFileHandler(
"api.log", mode="a", maxBytes=3 * 1024 * 1024, backupCount=3 "api.log", mode="a", maxBytes=3 * 1024 * 1024, backupCount=3, encoding="utf-8"
) )
fh.setFormatter(formatter) fh.setFormatter(formatter)
logger.addHandler(fh) logger.addHandler(fh)

View File

@@ -1,11 +1,13 @@
import os import os
import sys import global_var
def ngrok_connect(): def ngrok_connect():
from pyngrok import ngrok, conf from pyngrok import ngrok, conf
conf.set_default(conf.PyngrokConfig(ngrok_path="./ngrok")) conf.set_default(
conf.PyngrokConfig(ngrok_path="./ngrok.exe" if os.name == "nt" else "./ngrok")
)
ngrok.set_auth_token(os.environ["ngrok_token"]) ngrok.set_auth_token(os.environ["ngrok_token"])
http_tunnel = ngrok.connect(8000 if len(sys.argv) == 1 else int(sys.argv[1])) http_tunnel = ngrok.connect(global_var.get(global_var.Args).port)
print(http_tunnel.public_url) print(f"ngrok url: {http_tunnel.public_url}")

View File

@@ -511,6 +511,9 @@ def get_tokenizer(tokenizer_len: int):
def RWKV(model: str, strategy: str, tokenizer: Union[str, None]) -> AbstractRWKV: def RWKV(model: str, strategy: str, tokenizer: Union[str, None]) -> AbstractRWKV:
rwkv_beta = global_var.get(global_var.Args).rwkv_beta rwkv_beta = global_var.get(global_var.Args).rwkv_beta
if "midi" in model.lower() or "abc" in model.lower():
os.environ["RWKV_RESCALE_LAYER"] = "999"
# dynamic import to make RWKV_CUDA_ON work # dynamic import to make RWKV_CUDA_ON work
if rwkv_beta: if rwkv_beta:
from rwkv_pip.beta.model import ( from rwkv_pip.beta.model import (
@@ -551,8 +554,8 @@ class ModelConfigBody(BaseModel):
presence_penalty: float = Field(default=None, ge=-2, le=2) presence_penalty: float = Field(default=None, ge=-2, le=2)
frequency_penalty: float = Field(default=None, ge=-2, le=2) frequency_penalty: float = Field(default=None, ge=-2, le=2)
class Config: model_config = {
json_schema_extra = { "json_schema_extra": {
"example": { "example": {
"max_tokens": 1000, "max_tokens": 1000,
"temperature": 1.2, "temperature": 1.2,
@@ -561,6 +564,7 @@ class ModelConfigBody(BaseModel):
"frequency_penalty": 0.4, "frequency_penalty": 0.4,
} }
} }
}
def set_rwkv_config(model: AbstractRWKV, body: ModelConfigBody): def set_rwkv_config(model: AbstractRWKV, body: ModelConfigBody):

View File

@@ -0,0 +1,14 @@
from fastapi import FastAPI
from fastapi.middleware.gzip import GZipMiddleware
from fastapi.staticfiles import StaticFiles
import uvicorn
webui_server = FastAPI()
webui_server.add_middleware(GZipMiddleware, minimum_size=1000)
webui_server.mount(
"/", StaticFiles(directory="frontend/dist", html=True), name="static"
)
if __name__ == "__main__":
uvicorn.run("webui_server:webui_server")

View File

@@ -9,7 +9,7 @@ cd RWKV-Next-Web
git clone https://github.com/josStorer/RWKV-Runner --depth=1 git clone https://github.com/josStorer/RWKV-Runner --depth=1
python3 -m pip install torch torchvision torchaudio python3 -m pip install torch torchvision torchaudio
python3 -m pip install -r RWKV-Runner/backend-python/requirements.txt python3 -m pip install -r RWKV-Runner/backend-python/requirements.txt
python3 ./RWKV-Runner/backend-python/main.py > log.txt & python3 ./RWKV-Runner/backend-python/main.py > log.txt & # this is only an example, you should use screen or other tools to run it in background
if [ ! -d RWKV-Runner/models ]; then if [ ! -d RWKV-Runner/models ]; then
mkdir RWKV-Runner/models mkdir RWKV-Runner/models
@@ -22,6 +22,6 @@ yarn install
yarn build yarn build
export PROXY_URL="" export PROXY_URL=""
export BASE_URL=http://127.0.0.1:8000 export BASE_URL=http://127.0.0.1:8000
yarn start & yarn start & # this is only an example, you should use screen or other tools to run it in background
curl http://127.0.0.1:8000/switch-model -X POST -H "Content-Type: application/json" -d '{"model":"./RWKV-Runner/models/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth","strategy":"cpu fp32"}' curl http://127.0.0.1:8000/switch-model -X POST -H "Content-Type: application/json" -d '{"model":"./RWKV-Runner/models/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth","strategy":"cpu fp32"}'

View File

@@ -0,0 +1,19 @@
: install git python3.10 npm by yourself
: change model and strategy according to your hardware
git clone https://github.com/josStorer/RWKV-Runner --depth=1
python -m pip install torch==1.13.1 torchvision==0.14.1 torchaudio==0.13.1 --index-url https://download.pytorch.org/whl/cu117
python -m pip install -r RWKV-Runner/backend-python/requirements.txt
cd RWKV-Runner/frontend
call npm ci
call npm run build
cd ..
: optional: set ngrok_token=YOUR_NGROK_TOKEN
start python ./backend-python/main.py --webui
start "C:\Program Files (x86)\Microsoft\Edge\Application\msedge.exe" "http://127.0.0.1:8000"
powershell -Command "(Test-Path ./models) -or (mkdir models)"
powershell -Command "Import-Module BitsTransfer"
powershell -Command "(Test-Path ./models/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth) -or (Start-BitsTransfer https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth ./models/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth)"
powershell -Command "Invoke-WebRequest http://127.0.0.1:8000/switch-model -Method POST -ContentType 'application/json' -Body '{\"model\":\"./models/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth\",\"strategy\":\"cuda fp32 *20+\",\"deploy\":\"true\"}'"

View File

@@ -0,0 +1,22 @@
# install git python3.10 npm by yourself
# change model and strategy according to your hardware
sudo apt install python3-dev
git clone https://github.com/josStorer/RWKV-Runner --depth=1
python3 -m pip install torch torchvision torchaudio
python3 -m pip install -r RWKV-Runner/backend-python/requirements.txt
cd RWKV-Runner/frontend
npm ci
npm run build
cd ..
# optional: export ngrok_token=YOUR_NGROK_TOKEN
python3 ./backend-python/main.py --webui > log.txt & # this is only an example, you should use screen or other tools to run it in background
if [ ! -d models ]; then
mkdir models
fi
wget -N https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth -P models/
curl http://127.0.0.1:8000/switch-model -X POST -H "Content-Type: application/json" -d '{"model":"./models/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth","strategy":"cpu fp32","deploy":"true"}'

View File

@@ -23,6 +23,7 @@ def file_cleaner(file):
return cleaner return cleaner
expected_max_version = float(sys.argv[2]) if len(sys.argv) > 2 else 100
model_file = open(sys.argv[1], "rb") model_file = open(sys.argv[1], "rb")
cleaner = file_cleaner(model_file) cleaner = file_cleaner(model_file)
cleaner_thread = threading.Thread(target=cleaner, daemon=True) cleaner_thread = threading.Thread(target=cleaner, daemon=True)
@@ -34,8 +35,23 @@ gc.collect()
n_embd = w["emb.weight"].shape[1] n_embd = w["emb.weight"].shape[1]
n_layer = 0 n_layer = 0
keys = list(w.keys()) keys = list(w.keys())
version = 4
for x in keys: for x in keys:
layer_id = int(x.split(".")[1]) if ("blocks." in x) else 0 layer_id = int(x.split(".")[1]) if ("blocks." in x) else 0
n_layer = max(n_layer, layer_id + 1) n_layer = max(n_layer, layer_id + 1)
print(f"--n_layer {n_layer} --n_embd {n_embd}", end="") if "ln_x" in x:
version = max(5, version)
if "gate.weight" in x:
version = max(5.1, version)
if int(version) == 5 and "att.time_decay" in x:
if len(w[x].shape) > 1:
if w[x].shape[1] > 1:
version = max(5.2, version)
if "time_maa" in x:
version = max(6, version)
if version <= expected_max_version:
print(f"--n_layer {n_layer} --n_embd {n_embd}", end="")
else:
raise Exception(f"RWKV{version} is not supported")

View File

@@ -47,8 +47,12 @@ else
fi fi
echo "loading $loadModel" echo "loading $loadModel"
modelInfo=$(python3 ./finetune/get_layer_and_embd.py $loadModel) modelInfo=$(python3 ./finetune/get_layer_and_embd.py $loadModel 4)
echo $modelInfo echo $modelInfo
if [[ $modelInfo =~ "--n_layer" ]]; then
python3 ./finetune/lora/train.py $modelInfo $@ --proj_dir lora-models --data_type binidx --lora \ python3 ./finetune/lora/train.py $modelInfo $@ --proj_dir lora-models --data_type binidx --lora \
--lora_parts=att,ffn,time,ln --strategy deepspeed_stage_2 --accelerator gpu --lora_parts=att,ffn,time,ln --strategy deepspeed_stage_2 --accelerator gpu
else
echo "modelInfo is invalid"
exit 1
fi

View File

@@ -246,5 +246,6 @@ if __name__ == "__main__":
try: try:
main() main()
except Exception as e: except Exception as e:
print(e)
with open("error.txt", "w") as f: with open("error.txt", "w") as f:
f.write(str(e)) f.write(str(e))

View File

@@ -64,5 +64,6 @@ try:
torch.save(output_w, output) torch.save(output_w, output)
except Exception as e: except Exception as e:
print(e)
with open("error.txt", "w") as f: with open("error.txt", "w") as f:
f.write(str(e)) f.write(str(e))

View File

@@ -264,7 +264,7 @@ if __name__ == "__main__":
# #
# Data = {args.data_file} ({args.data_type}), ProjDir = {args.proj_dir} # Data = {args.data_file} ({args.data_type}), ProjDir = {args.proj_dir}
# #
# Epoch = {args.epoch_begin} to {args.epoch_begin + args.epoch_count - 1} (will continue afterwards), save every {args.epoch_save} epoch # Epoch = {args.epoch_begin} to {args.epoch_begin + args.epoch_count - 1}, save every {args.epoch_save} epoch
# #
# Each "epoch" = {args.epoch_steps} steps, {samples_per_epoch} samples, {tokens_per_epoch} tokens # Each "epoch" = {args.epoch_steps} steps, {samples_per_epoch} samples, {tokens_per_epoch} tokens
# #

View File

@@ -1,3 +1,3 @@
torch==1.13.1 torch==1.13.1
pytorch_lightning==1.9.5 pytorch_lightning==1.9.5
deepspeed deepspeed==0.11.2

View File

@@ -1,9 +1,10 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<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>

File diff suppressed because it is too large Load Diff

View File

@@ -16,15 +16,17 @@
"@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",
"github-markdown-css": "^5.2.0", "file-saver": "^2.0.5",
"html-midi-player": "^1.5.0", "html-midi-player": "^1.5.0",
"i18next": "^22.4.15", "i18next": "^22.4.15",
"mobx": "^6.9.0", "mobx": "^6.9.0",
"mobx-react-lite": "^3.4.3", "mobx-react-lite": "^3.4.3",
"pdfjs-dist": "^4.0.189",
"react": "^18.2.0", "react": "^18.2.0",
"react-beautiful-dnd": "^13.1.1", "react-beautiful-dnd": "^13.1.1",
"react-chartjs-2": "^5.2.0", "react-chartjs-2": "^5.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-draggable": "^4.4.6",
"react-i18next": "^12.2.2", "react-i18next": "^12.2.2",
"react-markdown": "^8.0.7", "react-markdown": "^8.0.7",
"react-router": "^6.11.1", "react-router": "^6.11.1",
@@ -38,6 +40,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",
@@ -49,6 +52,7 @@
"sass": "^1.62.1", "sass": "^1.62.1",
"tailwindcss": "^3.3.2", "tailwindcss": "^3.3.2",
"typescript": "^5.0.4", "typescript": "^5.0.4",
"vite": "^4.3.6" "vite": "^4.3.6",
"vite-plugin-top-level-await": "^1.3.1"
} }
} }

View File

@@ -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);
@@ -47,10 +51,10 @@ const App: FC = observer(() => {
useEffect(() => setPath(location.pathname), [location]); useEffect(() => setPath(location.pathname), [location]);
return ( return (
<FluentProvider className="h-screen" <FluentProvider
theme={commonStore.settings.darkMode ? webDarkTheme : webLightTheme} theme={commonStore.settings.darkMode ? webDarkTheme : webLightTheme}
data-theme={commonStore.settings.darkMode ? 'dark' : 'light'}> data-theme={commonStore.settings.darkMode ? 'dark' : 'light'}>
<div className="flex h-full"> <div className="flex h-screen">
<div className="flex flex-col w-16 sm:w-48 p-2 justify-between"> <div className="flex flex-col w-16 sm:w-48 p-2 justify-between">
<TabList <TabList
size="large" size="large"
@@ -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>

View File

@@ -13,7 +13,7 @@
"Working": "動作中", "Working": "動作中",
"Stop": "停止", "Stop": "停止",
"Enable High Precision For Last Layer": "最後の層で高精度を有効にする", "Enable High Precision For Last Layer": "最後の層で高精度を有効にする",
"Stored Layers": "メモリ層読み込み", "Stored Layers": "保存されるレイヤー",
"Precision": "精度", "Precision": "精度",
"Device": "デバイス", "Device": "デバイス",
"Convert model with these configs. Using a converted model will greatly improve the loading speed, but model parameters of the converted model cannot be modified.": "これらの設定でモデルを変換します。変換されたモデルを使用すると、読み込み速度が大幅に向上しますが、変換したモデルのパラメータを変更することはできません。", "Convert model with these configs. Using a converted model will greatly improve the loading speed, but model parameters of the converted model cannot be modified.": "これらの設定でモデルを変換します。変換されたモデルを使用すると、読み込み速度が大幅に向上しますが、変換したモデルのパラメータを変更することはできません。",
@@ -82,7 +82,7 @@
"Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.": "モデルに鎮静剤を与えるようなもの。上位nの確率質量の結果を考えてみてください。0.1は上位10を考えており、質が高いが保守的で、1は全ての結果を考慮しており、質は低いが多様性があります。", "Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.": "モデルに鎮静剤を与えるようなもの。上位nの確率質量の結果を考えてみてください。0.1は上位10を考えており、質が高いが保守的で、1は全ての結果を考慮しており、質は低いが多様性があります。",
"Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.": "ポジティヴ値は、新しいトークンが今までのテキストに出現していたかどうかに基づいてこれらをペナルティとし、新しいトピックについて話す可能性を増加させます。", "Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.": "ポジティヴ値は、新しいトークンが今までのテキストに出現していたかどうかに基づいてこれらをペナルティとし、新しいトピックについて話す可能性を増加させます。",
"Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.": "ポジティブ値は、新しいトークンが既存のテキストでどれだけ頻繁に使われているかに基づいてペナルティを与え、モデルが同じ行を完全に繰り返す可能性を減らします。", "Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.": "ポジティブ値は、新しいトークンが既存のテキストでどれだけ頻繁に使われているかに基づいてペナルティを与え、モデルが同じ行を完全に繰り返す可能性を減らします。",
"int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality, and fp32 has the best quality.": "int8はVRAMの使用量が少ないですが、質が若干低いです。fp16は高品質、fp32は最高品質です。", "int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality.": "int8はVRAMの使用量が少ないですが、質が若干低いです。fp16は高品質。",
"Number of the neural network layers loaded into VRAM, the more you load, the faster the speed, but it consumes more VRAM. (If your VRAM is not enough, it will fail to load)": "VRAMにロードされるニューラルネットワークの層の数。ロードする量が多いほど速度は速くなりますが、VRAMを多く消費します。(VRAMが不足している場合、ロードに失敗します)", "Number of the neural network layers loaded into VRAM, the more you load, the faster the speed, but it consumes more VRAM. (If your VRAM is not enough, it will fail to load)": "VRAMにロードされるニューラルネットワークの層の数。ロードする量が多いほど速度は速くなりますが、VRAMを多く消費します。(VRAMが不足している場合、ロードに失敗します)",
"Whether to use CPU to calculate the last output layer of the neural network with FP32 precision to obtain better quality.": "ネットワークの最終出力層をFP32精度で計算するためにCPUを使用するかどうか。", "Whether to use CPU to calculate the last output layer of the neural network with FP32 precision to obtain better quality.": "ネットワークの最終出力層をFP32精度で計算するためにCPUを使用するかどうか。",
"Downloads": "ダウンロード", "Downloads": "ダウンロード",
@@ -229,7 +229,7 @@
"Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "メモリが不足しています、仮想メモリ (WSL Swap) を増やすか小さなベースモデルを使用してみてください。", "Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "メモリが不足しています、仮想メモリ (WSL Swap) を増やすか小さなベースモデルを使用してみてください。",
"VRAM is not enough": "ビデオRAMが不足しています", "VRAM is not enough": "ビデオRAMが不足しています",
"Training data is not enough, reduce context length or add more data for training": "トレーニングデータが不足しています、コンテキストの長さを減らすか、トレーニング用のデータをさらに追加してください", "Training data is not enough, reduce context length or add more data for training": "トレーニングデータが不足しています、コンテキストの長さを減らすか、トレーニング用のデータをさらに追加してください",
"You are using WSL 1 for training, please upgrade to WSL 2. e.g. Run \"wsl --set-version Ubuntu-22.04 2\"": "トレーニングにWSL 1を使用していますWSL 2にアップグレードしてください。例:\"wsl --set-version Ubuntu-22.04 2\"を実行する", "Can not find an Nvidia GPU. Perhaps the gpu driver of windows is too old, or you are using WSL 1 for training, please upgrade to WSL 2. e.g. Run \"wsl --set-version Ubuntu-22.04 2\"": "Nvidia GPUが見つかりません。WindowsのGPUドライバが古すぎるか、トレーニングにWSL 1を使用している可能性がありますWSL 2にアップグレードしてください。例\"wsl --set-version Ubuntu-22.04 2\"を実行してください",
"Matched CUDA is not installed": "対応するCUDAがインストールされていません", "Matched CUDA is not installed": "対応するCUDAがインストールされていません",
"Failed to convert data": "データの変換に失敗しました", "Failed to convert data": "データの変換に失敗しました",
"Failed to merge model": "モデルのマージに失敗しました", "Failed to merge model": "モデルのマージに失敗しました",
@@ -250,7 +250,7 @@
"VRAM": "VRAM", "VRAM": "VRAM",
"GPU Usage": "GPU使用率", "GPU Usage": "GPU使用率",
"Use Custom Tokenizer": "カスタムトークナイザーを使用する", "Use Custom Tokenizer": "カスタムトークナイザーを使用する",
"Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json)": "トークナイザーパス (例: backend-python/rwkv_pip/20B_tokenizer.json)", "Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json or rwkv_vocab_v20230424.txt)": "トークナイザーパス (例: backend-python/rwkv_pip/20B_tokenizer.json または rwkv_vocab_v20230424.txt)",
"User Name": "ユーザー名", "User Name": "ユーザー名",
"Assistant Name": "アシスタント名", "Assistant Name": "アシスタント名",
"Insert default system prompt at the beginning": "最初にデフォルトのシステムプロンプトを挿入", "Insert default system prompt at the beginning": "最初にデフォルトのシステムプロンプトを挿入",
@@ -261,5 +261,55 @@
"The content of file": "ファイル", "The content of file": "ファイル",
"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.": "ポートが占有されています。設定ページで変更するか、ポートを占有しているプログラムを終了してください。",
"Loading...": "読み込み中...",
"Hello, what can I do for you?": "こんにちは、何かお手伝いできますか?",
"Enable WebUI": "WebUIを有効化",
"Server is working on deployment mode, please close the terminal window manually": "サーバーはデプロイモードで動作しています、ターミナルウィンドウを手動で閉じてください",
"Server is working on deployment mode, please exit the program manually to stop the server": "サーバーはデプロイモードで動作しています、サーバーを停止するにはプログラムを手動で終了してください",
"You can increase the number of stored layers in Configs page to improve performance": "パフォーマンスを向上させるために、保存されるレイヤーの数を設定ページで増やすことができます",
"Failed to load model, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "モデルの読み込みに失敗しました、仮想メモリ (WSL Swap) を増やすか小さなベースモデルを使用してみてください。",
"Save Conversation": "会話を保存",
"Use Hugging Face Mirror": "Hugging Faceミラーを使用",
"File is empty": "ファイルが空です",
"Open MIDI Input Audio Tracks": "MIDI入力オーディオトラックを開く",
"Track": "トラック",
"Play All": "すべて再生",
"Clear All": "すべてクリア",
"Scale View": "スケールビュー",
"Record": "録音",
"Play": "再生",
"New Track": "新規トラック",
"Select a track to preview the content": "トラックを選択して内容をプレビュー",
"Save to generation area": "生成エリアに保存",
"Piano": "ピアノ",
"Percussion": "パーカッション",
"Drum": "ドラム",
"Tuba": "チューバ",
"Marimba": "マリンバ",
"Bass": "ベース",
"Guitar": "ギター",
"Violin": "バイオリン",
"Trumpet": "トランペット",
"Sax": "サックス",
"Flute": "フルート",
"Lead": "リード",
"Pad": "パッド",
"MIDI Input": "MIDI入力",
"Select the MIDI input device to be used.": "使用するMIDI入力デバイスを選択します。",
"Start Time": "開始時間",
"Content Duration": "内容の長さ",
"Please select a MIDI device first": "まずMIDIデバイスを選択してください",
"Piano is the main instrument": "ピアノはメインの楽器です",
"Loss is too high, please check the training data, and ensure your gpu driver is up to date.": "Lossが大きすぎます、トレーニングデータを確認し、GPUドライバが最新であることを確認してください。",
"This version of RWKV is not supported yet.": "このバージョンのRWKVはまだサポートされていません。",
"Main": "メイン",
"Finetuned": "微調整",
"Global": "グローバル",
"Local": "ローカル",
"CN": "中国語",
"JP": "日本語",
"Music": "音楽",
"Other": "その他"
} }

View File

@@ -82,7 +82,7 @@
"Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.": "就像给模型喂镇静剂. 考虑前 n% 概率质量的结果, 0.1 考虑前 10%, 质量更高, 但更保守, 1 考虑所有质量结果, 质量降低, 但更多样", "Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.": "就像给模型喂镇静剂. 考虑前 n% 概率质量的结果, 0.1 考虑前 10%, 质量更高, 但更保守, 1 考虑所有质量结果, 质量降低, 但更多样",
"Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.": "存在惩罚. 正值根据新token在至今的文本中是否出现过, 来对其进行惩罚, 从而增加了模型涉及新话题的可能性", "Positive values penalize new tokens based on whether they appear in the text so far, increasing the model's likelihood to talk about new topics.": "存在惩罚. 正值根据新token在至今的文本中是否出现过, 来对其进行惩罚, 从而增加了模型涉及新话题的可能性",
"Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.": "频率惩罚. 正值根据新token在至今的文本中出现的频率/次数, 来对其进行惩罚, 从而减少模型原封不动地重复相同句子的可能性", "Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model's likelihood to repeat the same line verbatim.": "频率惩罚. 正值根据新token在至今的文本中出现的频率/次数, 来对其进行惩罚, 从而减少模型原封不动地重复相同句子的可能性",
"int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality, and fp32 has the best quality.": "int8占用显存更低, 但质量略微下降. fp16质量更好, fp32质量最好", "int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality.": "int8占用显存更低, 但质量略微下降. fp16质量更好",
"Number of the neural network layers loaded into VRAM, the more you load, the faster the speed, but it consumes more VRAM. (If your VRAM is not enough, it will fail to load)": "载入显存的神经网络层数, 载入越多, 速度越快, 但显存消耗越大 (如果你的显存不够, 会载入失败)", "Number of the neural network layers loaded into VRAM, the more you load, the faster the speed, but it consumes more VRAM. (If your VRAM is not enough, it will fail to load)": "载入显存的神经网络层数, 载入越多, 速度越快, 但显存消耗越大 (如果你的显存不够, 会载入失败)",
"Whether to use CPU to calculate the last output layer of the neural network with FP32 precision to obtain better quality.": "是否使用cpu以fp32精度计算神经网络的最后一层输出层, 以获得更好的质量", "Whether to use CPU to calculate the last output layer of the neural network with FP32 precision to obtain better quality.": "是否使用cpu以fp32精度计算神经网络的最后一层输出层, 以获得更好的质量",
"Downloads": "下载", "Downloads": "下载",
@@ -229,7 +229,7 @@
"Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "内存不足,尝试增加虚拟内存(WSL Swap),或使用一个更小规模的基底模型", "Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "内存不足,尝试增加虚拟内存(WSL Swap),或使用一个更小规模的基底模型",
"VRAM is not enough": "显存不足", "VRAM is not enough": "显存不足",
"Training data is not enough, reduce context length or add more data for training": "训练数据不足,请减小上下文长度或增加训练数据", "Training data is not enough, reduce context length or add more data for training": "训练数据不足,请减小上下文长度或增加训练数据",
"You are using WSL 1 for training, please upgrade to WSL 2. e.g. Run \"wsl --set-version Ubuntu-22.04 2\"": "你正在使用WSL 1进行训练请升级到WSL 2。例如行\"wsl --set-version Ubuntu-22.04 2\"", "Can not find an Nvidia GPU. Perhaps the gpu driver of windows is too old, or you are using WSL 1 for training, please upgrade to WSL 2. e.g. Run \"wsl --set-version Ubuntu-22.04 2\"": "没有找到Nvidia显卡。可能是因为你的windows显卡驱动太旧或者你正在使用WSL 1进行训练请升级到WSL 2。例如行\"wsl --set-version Ubuntu-22.04 2\"",
"Matched CUDA is not installed": "未安装匹配的CUDA", "Matched CUDA is not installed": "未安装匹配的CUDA",
"Failed to convert data": "数据转换失败", "Failed to convert data": "数据转换失败",
"Failed to merge model": "合并模型失败", "Failed to merge model": "合并模型失败",
@@ -250,7 +250,7 @@
"VRAM": "显存", "VRAM": "显存",
"GPU Usage": "GPU占用", "GPU Usage": "GPU占用",
"Use Custom Tokenizer": "使用自定义Tokenizer", "Use Custom Tokenizer": "使用自定义Tokenizer",
"Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json)": "Tokenizer路径 (例如: backend-python/rwkv_pip/20B_tokenizer.json)", "Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json or rwkv_vocab_v20230424.txt)": "Tokenizer路径 (例如: backend-python/rwkv_pip/20B_tokenizer.json 或 rwkv_vocab_v20230424.txt)",
"User Name": "用户名称", "User Name": "用户名称",
"Assistant Name": "AI名称", "Assistant Name": "AI名称",
"Insert default system prompt at the beginning": "在开头自动插入默认系统提示", "Insert default system prompt at the beginning": "在开头自动插入默认系统提示",
@@ -261,5 +261,55 @@
"The content of file": "文件", "The content of file": "文件",
"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.": "端口被占用。请在配置页面更改端口,或关闭占用端口的程序",
"Loading...": "加载中...",
"Hello, what can I do for you?": "你好,有什么要我帮忙的吗?",
"Enable WebUI": "启用WebUI",
"Server is working on deployment mode, please close the terminal window manually": "服务器正在部署模式下运行,请手动关闭终端窗口",
"Server is working on deployment mode, please exit the program manually to stop the server": "服务器正在部署模式下运行,请手动退出程序以停止服务器",
"You can increase the number of stored layers in Configs page to improve performance": "你可以在配置页面增加载入显存层数以提升性能",
"Failed to load model, try to increase the virtual memory (Swap of WSL) or use a smaller base model.": "模型载入失败,尝试增加虚拟内存(WSL Swap),或使用一个更小规模的基底模型",
"Save Conversation": "保存对话",
"Use Hugging Face Mirror": "使用Hugging Face镜像源",
"File is empty": "文件为空",
"Open MIDI Input Audio Tracks": "打开MIDI输入音轨",
"Track": "音轨",
"Play All": "播放全部",
"Clear All": "清空",
"Scale View": "缩放视图",
"Record": "录制",
"Play": "播放",
"New Track": "新建音轨",
"Select a track to preview the content": "选择一个音轨以预览内容",
"Save to generation area": "保存到生成区",
"Piano": "钢琴",
"Percussion": "打击乐",
"Drum": "鼓",
"Tuba": "大号",
"Marimba": "马林巴",
"Bass": "贝斯",
"Guitar": "吉他",
"Violin": "小提琴",
"Trumpet": "小号",
"Sax": "萨克斯",
"Flute": "长笛",
"Lead": "主音",
"Pad": "和音",
"MIDI Input": "MIDI输入",
"Select the MIDI input device to be used.": "选择要使用的MIDI输入设备",
"Start Time": "开始时间",
"Content Duration": "内容时长",
"Please select a MIDI device first": "请先选择一个MIDI设备",
"Piano is the main instrument": "钢琴为主",
"Loss is too high, please check the training data, and ensure your gpu driver is up to date.": "Loss过高请检查训练数据并确保你的显卡驱动是最新的",
"This version of RWKV is not supported yet.": "暂不支持此版本的RWKV",
"Main": "主干",
"Finetuned": "微调",
"Global": "全球",
"Local": "本地",
"CN": "中文",
"JP": "日文",
"Music": "音乐",
"Other": "其他"
} }

View File

@@ -1,10 +1,10 @@
import { FC } from 'react'; import { FC } from 'react';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { Dropdown, Option } from '@fluentui/react-components'; import { Dropdown, Option, PresenceBadge } from '@fluentui/react-components';
import commonStore from '../stores/commonStore'; import commonStore from '../stores/commonStore';
export const ConfigSelector: FC<{ size?: 'small' | 'medium' | 'large' }> = observer(({ size }) => { export const ConfigSelector: FC<{ size?: 'small' | 'medium' | 'large' }> = observer(({ size }) => {
return <Dropdown size={size} style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }} return <Dropdown size={size} style={{ minWidth: 0 }} listbox={{ style: { minWidth: 'fit-content' } }}
value={commonStore.getCurrentModelConfig().name} value={commonStore.getCurrentModelConfig().name}
selectedOptions={[commonStore.currentModelConfigIndex.toString()]} selectedOptions={[commonStore.currentModelConfigIndex.toString()]}
onOptionSelect={(_, data) => { onOptionSelect={(_, data) => {
@@ -12,7 +12,13 @@ export const ConfigSelector: FC<{ size?: 'small' | 'medium' | 'large' }> = obser
commonStore.setCurrentConfigIndex(Number(data.optionValue)); commonStore.setCurrentConfigIndex(Number(data.optionValue));
}}> }}>
{commonStore.modelConfigs.map((config, index) => {commonStore.modelConfigs.map((config, index) =>
<Option key={index} value={index.toString()}>{config.name}</Option> <Option key={index} value={index.toString()} text={config.name}>
<div className="flex justify-between grow">
{config.name}
{commonStore.modelSourceList.find(item => item.name === config.modelParameters.modelName)?.isComplete
&& <PresenceBadge status="available" />}
</div>
</Option>
)} )}
</Dropdown>; </Dropdown>;
}); });

View File

@@ -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>

View File

@@ -0,0 +1,24 @@
import { FC, LazyExoticComponent, ReactNode, Suspense } from 'react';
import { useTranslation } from 'react-i18next';
import { Spinner } from '@fluentui/react-components';
interface LazyImportComponentProps {
lazyChildren: LazyExoticComponent<FC<any>>;
lazyProps?: any;
children?: ReactNode;
}
export const LazyImportComponent: FC<LazyImportComponentProps> = (props) => {
const { t } = useTranslation();
return (
<Suspense fallback={
<div className="flex justify-center items-center h-full w-full">
<Spinner size="huge" label={t('Loading...')} />
</div>}>
<props.lazyChildren {...props.lazyProps}>
{props.children}
</props.lazyChildren>
</Suspense>
);
};

View File

@@ -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

View File

@@ -40,6 +40,8 @@ export const ReadButton: FC<{
voice = voices.find((v) => v.name.toLowerCase().includes('microsoft aria')); voice = voices.find((v) => v.name.toLowerCase().includes('microsoft aria'));
else if (lang === 'zh') else if (lang === 'zh')
voice = voices.find((v) => v.name.toLowerCase().includes('xiaoyi')); voice = voices.find((v) => v.name.toLowerCase().includes('xiaoyi'));
else if (lang === 'ja')
voice = voices.find((v) => v.name.toLowerCase().includes('nanami'));
if (!voice) voice = voices.find((v) => v.lang.substring(0, 2) === lang); if (!voice) voice = voices.find((v) => v.lang.substring(0, 2) === lang);
if (!voice) voice = voices.find((v) => v.lang === navigator.language); if (!voice) voice = voices.find((v) => v.lang === navigator.language);

View File

@@ -1,16 +1,23 @@
import React, { FC, MouseEventHandler, ReactElement } from 'react'; import React, { FC, MouseEventHandler, ReactElement } from 'react';
import commonStore, { ModelStatus } from '../stores/commonStore'; import commonStore, { ModelStatus } from '../stores/commonStore';
import { AddToDownloadList, FileExists, StartServer, StartWebGPUServer } from '../../wailsjs/go/backend_golang/App'; import {
AddToDownloadList,
FileExists,
IsPortAvailable,
StartServer,
StartWebGPUServer
} from '../../wailsjs/go/backend_golang/App';
import { Button } from '@fluentui/react-components'; import { Button } from '@fluentui/react-components';
import { observer } from 'mobx-react-lite'; import { observer } from 'mobx-react-lite';
import { exit, getStatus, readRoot, switchModel, updateConfig } from '../apis'; import { exit, getStatus, readRoot, switchModel, updateConfig } from '../apis';
import { toast } from 'react-toastify'; import { toast } from 'react-toastify';
import { checkDependencies, getStrategy, toastWithButton } from '../utils'; import { checkDependencies, getHfDownloadUrl, getStrategy, toastWithButton } from '../utils';
import { useTranslation } from 'react-i18next'; 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';
import { convertToSt } from '../utils/convert-to-st';
const mainButtonText = { const mainButtonText = {
[ModelStatus.Offline]: 'Run', [ModelStatus.Offline]: 'Run',
@@ -51,13 +58,40 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
return; return;
} }
const currentModelSource = commonStore.modelSourceList.find(item => item.name === modelName);
const showDownloadPrompt = (promptInfo: string, downloadName: string) => {
toastWithButton(promptInfo, t('Download'), () => {
const downloadUrl = currentModelSource?.downloadUrl;
if (downloadUrl) {
toastWithButton(`${t('Downloading')} ${downloadName}`, t('Check'), () => {
navigate({ pathname: '/downloads' });
},
{ autoClose: 3000 });
AddToDownloadList(modelPath, getHfDownloadUrl(downloadUrl));
} else {
toast(t('Can not find download url'), { type: 'error' });
}
});
};
if (webgpu) { if (webgpu) {
if (!['.st', '.safetensors'].some(ext => modelPath.endsWith(ext))) { if (!['.st', '.safetensors'].some(ext => modelPath.endsWith(ext))) {
const stModelPath = modelPath.replace(/\.pth$/, '.st'); const stModelPath = modelPath.replace(/\.pth$/, '.st');
if (await FileExists(stModelPath)) { if (await FileExists(stModelPath)) {
modelPath = stModelPath; modelPath = stModelPath;
} else if (!await FileExists(modelPath)) {
showDownloadPrompt(t('Model file not found'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline });
return;
} else if (!currentModelSource?.isComplete) {
showDownloadPrompt(t('Model file download is not complete'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline });
return;
} else { } else {
toast(t('Please convert model to safe tensors format first'), { type: 'error' }); toastWithButton(t('Please convert model to safe tensors format first'), t('Convert'), () => {
convertToSt(modelConfig);
});
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
return; return;
} }
@@ -78,28 +112,13 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
return; return;
} }
const currentModelSource = commonStore.modelSourceList.find(item => item.name === modelName);
const showDownloadPrompt = (promptInfo: string, downloadName: string) => {
toastWithButton(promptInfo, t('Download'), () => {
const downloadUrl = currentModelSource?.downloadUrl;
if (downloadUrl) {
toastWithButton(`${t('Downloading')} ${downloadName}`, t('Check'), () => {
navigate({ pathname: '/downloads' });
},
{ autoClose: 3000 });
AddToDownloadList(modelPath, downloadUrl);
} else {
toast(t('Can not find download url'), { type: 'error' });
}
});
};
if (!await FileExists(modelPath)) { if (!await FileExists(modelPath)) {
showDownloadPrompt(t('Model file not found'), modelName); showDownloadPrompt(t('Model file not found'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
return; return;
} else if (!currentModelSource?.isComplete) { } else // If the user selects the .pth model with WebGPU mode, modelPath will be set to the .st model.
// However, if the .pth model is deleted, modelPath will exist and isComplete will be false.
if (!currentModelSource?.isComplete && modelPath.endsWith('.pth')) {
showDownloadPrompt(t('Model file download is not complete'), modelName); showDownloadPrompt(t('Model file download is not complete'), modelName);
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
return; return;
@@ -107,8 +126,15 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
const port = modelConfig.apiParameters.apiPort; const port = modelConfig.apiParameters.apiPort;
await exit(1000).catch(() => { if (!await IsPortAvailable(port)) {
}); await exit(1000).catch(() => {
});
if (!await IsPortAvailable(port)) {
toast(t('Port is occupied. Change it in Configs page or close the program that occupies the port.'), { type: 'error' });
commonStore.setStatus({ status: ModelStatus.Offline });
return;
}
}
const startServer = webgpu ? const startServer = webgpu ?
(_: string, port: number, host: string) => StartWebGPUServer(port, host) (_: string, port: number, host: string) => StartWebGPUServer(port, host)
@@ -116,7 +142,7 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
const isUsingCudaBeta = modelConfig.modelParameters.device === 'CUDA-Beta'; const isUsingCudaBeta = modelConfig.modelParameters.device === 'CUDA-Beta';
startServer(commonStore.settings.customPythonPath, port, commonStore.settings.host !== '127.0.0.1' ? '0.0.0.0' : '127.0.0.1', startServer(commonStore.settings.customPythonPath, port, commonStore.settings.host !== '127.0.0.1' ? '0.0.0.0' : '127.0.0.1',
isUsingCudaBeta !!modelConfig.enableWebUI, isUsingCudaBeta
).catch((e) => { ).catch((e) => {
const errMsg = e.message || e; const errMsg = e.message || e;
if (errMsg.includes('path contains space')) if (errMsg.includes('path contains space'))
@@ -125,6 +151,8 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
toast(t('Error') + ' - ' + errMsg, { type: 'error' }); toast(t('Error') + ' - ' + errMsg, { type: 'error' });
}); });
setTimeout(WindowShow, 1000); setTimeout(WindowShow, 1000);
setTimeout(WindowShow, 2000);
setTimeout(WindowShow, 3000);
let timeoutCount = 6; let timeoutCount = 6;
let loading = false; let loading = false;
@@ -141,7 +169,7 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
}); });
} }
commonStore.setStatus({ status: ModelStatus.Loading }); commonStore.setStatus({ status: ModelStatus.Loading });
toast(t('Loading Model'), { type: 'info' }); const loadingId = toast(t('Loading Model'), { type: 'info', autoClose: false });
if (!webgpu) { if (!webgpu) {
updateConfig({ updateConfig({
max_tokens: modelConfig.apiParameters.maxResponseToken, max_tokens: modelConfig.apiParameters.maxResponseToken,
@@ -155,7 +183,8 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
const strategy = getStrategy(modelConfig); const strategy = getStrategy(modelConfig);
let customCudaFile = ''; let customCudaFile = '';
if ((modelConfig.modelParameters.device.includes('CUDA') || modelConfig.modelParameters.device === 'Custom') if ((modelConfig.modelParameters.device.includes('CUDA') || modelConfig.modelParameters.device === 'Custom')
&& modelConfig.modelParameters.useCustomCuda && !strategy.includes('fp32')) { && modelConfig.modelParameters.useCustomCuda
&& !strategy.split('->').some(s => ['cuda', 'fp32'].every(v => s.includes(v)))) {
if (commonStore.platform === 'windows') { if (commonStore.platform === 'windows') {
// this part is currently unused because there's no longer a need to use different kernels for different GPUs, but it might still be needed in the future // this part is currently unused because there's no longer a need to use different kernels for different GPUs, but it might still be needed in the future
// //
@@ -186,7 +215,8 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
model: modelPath, model: modelPath,
strategy: strategy, strategy: strategy,
tokenizer: modelConfig.modelParameters.useCustomTokenizer ? modelConfig.modelParameters.customTokenizer : undefined, tokenizer: modelConfig.modelParameters.useCustomTokenizer ? modelConfig.modelParameters.customTokenizer : undefined,
customCuda: customCudaFile !== '' customCuda: customCudaFile !== '',
deploy: modelConfig.enableWebUI
}).then(async (r) => { }).then(async (r) => {
if (r.ok) { if (r.ok) {
commonStore.setStatus({ status: ModelStatus.Working }); commonStore.setStatus({ status: ModelStatus.Working });
@@ -199,6 +229,12 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
const buttonFn = () => { const buttonFn = () => {
navigate({ pathname: '/' + buttonName.toLowerCase() }); navigate({ pathname: '/' + buttonName.toLowerCase() });
}; };
if ((modelConfig.modelParameters.device === 'CUDA' || modelConfig.modelParameters.device === 'CUDA-Beta') &&
modelConfig.modelParameters.storedLayers < modelConfig.modelParameters.maxStoredLayers &&
commonStore.monitorData && commonStore.monitorData.totalVram !== 0 &&
(commonStore.monitorData.usedVram / commonStore.monitorData.totalVram) < 0.9)
toast(t('You can increase the number of stored layers in Configs page to improve performance'), { type: 'info' });
toastWithButton(t('Startup Completed'), t(buttonName), buttonFn, { type: 'success', autoClose: 3000 }); toastWithButton(t('Startup Completed'), t(buttonName), buttonFn, { type: 'success', autoClose: 3000 });
} else if (r.status === 304) { } else if (r.status === 304) {
toast(t('Loading Model'), { type: 'info' }); toast(t('Loading Model'), { type: 'info' });
@@ -211,7 +247,7 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
'invalid header or archive is corrupted': 'The model file is corrupted, please download again.', 'invalid header or archive is corrupted': 'The model file is corrupted, please download again.',
'no NVIDIA driver': 'Found no NVIDIA driver, please install the latest driver.', 'no NVIDIA driver': 'Found no NVIDIA driver, please install the latest driver.',
'CUDA out of memory': 'VRAM is not enough, please reduce stored layers or use a lower precision in Configs page.', 'CUDA out of memory': 'VRAM is not enough, please reduce stored layers or use a lower precision in Configs page.',
'Ninja is required to load C++ extensions': 'Failed to enable custom CUDA kernel, ninja is required to load C++ extensions. You may be using the CPU version of PyTorch, please reinstall PyTorch with CUDA. Or if you are using a custom Python interpreter, you must compile the CUDA kernel by yourself or disable Custom CUDA kernel acceleration.', 'Ninja is required to load C++ extensions': 'Failed to enable custom CUDA kernel, ninja is required to load C++ extensions. You may be using the CPU version of PyTorch, please reinstall PyTorch with CUDA. Or if you are using a custom Python interpreter, you must compile the CUDA kernel by yourself or disable Custom CUDA kernel acceleration.'
}; };
const matchedError = Object.entries(errorsMap).find(([key, _]) => error.includes(key)); const matchedError = Object.entries(errorsMap).find(([key, _]) => error.includes(key));
const message = matchedError ? t(matchedError[1]) : error; const message = matchedError ? t(matchedError[1]) : error;
@@ -220,6 +256,8 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
}).catch((e) => { }).catch((e) => {
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
toast(t('Failed to switch model') + ' - ' + (e.message || e), { type: 'error' }); toast(t('Failed to switch model') + ' - ' + (e.message || e), { type: 'error' });
}).finally(() => {
toast.dismiss(loadingId);
}); });
} }
}).catch(() => { }).catch(() => {
@@ -233,7 +271,13 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
}, 1000); }, 1000);
} else { } else {
commonStore.setStatus({ status: ModelStatus.Offline }); commonStore.setStatus({ status: ModelStatus.Offline });
exit(); exit().then(r => {
if (r.status === 403)
if (commonStore.platform !== 'linux')
toast(t('Server is working on deployment mode, please close the terminal window manually'), { type: 'info' });
else
toast(t('Server is working on deployment mode, please exit the program manually to stop the server'), { type: 'info' });
});
} }
}; };

View File

@@ -6,6 +6,7 @@ import { ConfigSelector } from './ConfigSelector';
import { RunButton } from './RunButton'; import { RunButton } from './RunButton';
import { PresenceBadgeStatus } from '@fluentui/react-badge'; import { PresenceBadgeStatus } from '@fluentui/react-badge';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import { useMediaQuery } from 'usehooks-ts';
const statusText = { const statusText = {
[ModelStatus.Offline]: 'Offline', [ModelStatus.Offline]: 'Offline',
@@ -23,15 +24,21 @@ const badgeStatus: { [modelStatus: number]: PresenceBadgeStatus } = {
export const WorkHeader: FC = observer(() => { export const WorkHeader: FC = observer(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const mq = useMediaQuery('(min-width: 640px)');
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">
<PresenceBadge status={badgeStatus[commonStore.status.status]} /> <PresenceBadge status={badgeStatus[commonStore.status.status]} />
<Text size={100}>{t('Model Status') + ': ' + t(statusText[commonStore.status.status])}</Text> <Text size={100}>{t('Model Status') + ': ' + t(statusText[commonStore.status.status])}</Text>
</div> </div>
{commonStore.lastModelName && mq &&
<Text size={100}>
{commonStore.lastModelName}
</Text>}
<div className="flex items-center gap-2"> <div className="flex items-center gap-2">
<ConfigSelector size="small" /> <ConfigSelector size="small" />
<RunButton iconMode /> <RunButton iconMode />
@@ -42,5 +49,5 @@ export const WorkHeader: FC = observer(() => {
</Text> </Text>
<Divider style={{ flexGrow: 0 }} /> <Divider style={{ flexGrow: 0 }} />
</div> </div>
); ;
}); });

View File

@@ -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(() => {

View File

@@ -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;

View File

@@ -0,0 +1,40 @@
import React, { FC, lazy } from 'react';
import { useTranslation } from 'react-i18next';
import { Button, Dialog, DialogBody, DialogContent, DialogSurface, DialogTrigger } from '@fluentui/react-components';
import { CustomToastContainer } from '../../components/CustomToastContainer';
import { LazyImportComponent } from '../../components/LazyImportComponent';
import { flushMidiRecordingContent } from '../../utils';
import commonStore from '../../stores/commonStore';
const AudiotrackEditor = lazy(() => import('./AudiotrackEditor'));
export const AudiotrackButton: FC<{
size?: 'small' | 'medium' | 'large',
shape?: 'rounded' | 'circular' | 'square';
appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent';
setPrompt: (prompt: string) => void;
}> = ({ size, shape, appearance, setPrompt }) => {
const { t } = useTranslation();
return <Dialog onOpenChange={(e, data) => {
if (!data.open) {
flushMidiRecordingContent();
commonStore.setRecordingTrackId('');
commonStore.setPlayingTrackId('');
}
}}>
<DialogTrigger disableButtonEnhancement>
<Button size={size} shape={shape} appearance={appearance}>
{t('Open MIDI Input Audio Tracks')}
</Button>
</DialogTrigger>
<DialogSurface style={{ paddingTop: 0, maxWidth: '90vw', width: 'fit-content' }}>
<DialogBody>
<DialogContent className="overflow-hidden">
<CustomToastContainer />
<LazyImportComponent lazyChildren={AudiotrackEditor} lazyProps={{ setPrompt }} />
</DialogContent>
</DialogBody>
</DialogSurface>
</Dialog>;
};

View File

@@ -0,0 +1,508 @@
import React, { FC, useEffect, useRef, useState } from 'react';
import { observer } from 'mobx-react-lite';
import { useTranslation } from 'react-i18next';
import Draggable from 'react-draggable';
import { ToolTipButton } from '../../components/ToolTipButton';
import { v4 as uuid } from 'uuid';
import {
Add16Regular,
ArrowAutofitWidth20Regular,
Delete16Regular,
MusicNote220Regular,
Pause16Regular,
Play16Filled,
Play16Regular,
Record16Regular,
Stop16Filled
} from '@fluentui/react-icons';
import { Button, Card, DialogTrigger, Slider, Text, Tooltip } from '@fluentui/react-components';
import { useWindowSize } from 'usehooks-ts';
import commonStore from '../../stores/commonStore';
import classnames from 'classnames';
import {
InstrumentTypeNameMap,
InstrumentTypeTokenMap,
MidiMessage,
tracksMinimalTotalTime
} from '../../types/composition';
import { toast } from 'react-toastify';
import { ToastOptions } from 'react-toastify/dist/types';
import { flushMidiRecordingContent, refreshTracksTotalTime } from '../../utils';
import { PlayNote } from '../../../wailsjs/go/backend_golang/App';
import { t } from 'i18next';
const snapValue = 25;
const minimalMoveTime = 8; // 1000/125=8ms wait_events=125
const scaleMin = 0.05;
const scaleMax = 3;
const baseMoveTime = Math.round(minimalMoveTime / scaleMin);
const velocityEvents = 128;
const velocityBins = 12;
const velocityExp = 0.5;
const minimalTrackWidth = 80;
const trackInitOffsetPx = 10;
const pixelFix = 0.5;
const topToArrowIcon = 19;
const arrowIconToTracks = 23;
type TrackProps = {
id: string;
right: number;
scale: number;
isSelected: boolean;
onSelect: (id: string) => void;
};
const displayCurrentInstrumentType = () => {
const displayPanelId = 'instrument_panel_id';
const content: React.ReactNode =
<div className="flex gap-2 items-center">
{InstrumentTypeNameMap.map((name, i) =>
<Text key={name} style={{ whiteSpace: 'nowrap' }}
className={commonStore.instrumentType === i ? 'text-blue-600' : ''}
weight={commonStore.instrumentType === i ? 'bold' : 'regular'}
size={commonStore.instrumentType === i ? 300 : 100}
>{t(name)}</Text>)}
</div>;
const options: ToastOptions = {
type: 'default',
autoClose: 2000,
toastId: displayPanelId,
position: 'top-left',
style: {
width: 'fit-content'
}
};
if (toast.isActive(displayPanelId))
toast.update(displayPanelId, {
render: content,
...options
});
else
toast(content, options);
};
const velocityToBin = (velocity: number) => {
velocity = Math.max(0, Math.min(velocity, velocityEvents - 1));
const binsize = velocityEvents / (velocityBins - 1);
return Math.ceil((velocityEvents * ((Math.pow(velocityExp, (velocity / velocityEvents)) - 1.0) / (velocityExp - 1.0))) / binsize);
};
const midiMessageToToken = (msg: MidiMessage) => {
if (msg.messageType === 'NoteOn' || msg.messageType === 'NoteOff') {
const instrument = InstrumentTypeTokenMap[commonStore.instrumentType];
const note = msg.note.toString(16);
const velocity = velocityToBin(msg.velocity).toString(16);
return `${instrument}:${note}:${velocity} `;
} else if (msg.messageType === 'ElapsedTime') {
let time = Math.round(msg.value / minimalMoveTime);
const num = Math.floor(time / 125); // wait_events=125
time -= num * 125;
let ret = '';
for (let i = 0; i < num; i++) {
ret += 't125 ';
}
if (time > 0)
ret += `t${time} `;
return ret;
} else
return '';
};
let dropRecordingTime = false;
export const midiMessageHandler = async (data: MidiMessage) => {
if (data.messageType === 'ControlChange') {
commonStore.setInstrumentType(Math.round(data.value / 127 * (InstrumentTypeNameMap.length - 1)));
displayCurrentInstrumentType();
return;
}
if (commonStore.recordingTrackId) {
if (dropRecordingTime && data.messageType === 'ElapsedTime') {
dropRecordingTime = false;
return;
}
data = {
...data,
instrument: commonStore.instrumentType
};
commonStore.setRecordingRawContent([...commonStore.recordingRawContent, data]);
commonStore.setRecordingContent(commonStore.recordingContent + midiMessageToToken(data));
//TODO data.channel = data.instrument;
PlayNote(data);
}
};
const Track: React.FC<TrackProps> = observer(({
id,
right,
scale,
isSelected,
onSelect
}) => {
const { t } = useTranslation();
const trackIndex = commonStore.tracks.findIndex(t => t.id === id)!;
const track = commonStore.tracks[trackIndex];
const trackClass = isSelected ? 'bg-blue-600' : (commonStore.settings.darkMode ? 'bg-blue-900' : 'bg-gray-700');
const controlX = useRef(0);
let trackName = t('Track') + ' ' + id;
if (track.mainInstrument)
trackName = t('Track') + ' - ' + t('Piano is the main instrument')!.replace(t('Piano')!, t(track.mainInstrument)) + (track.content && (' - ' + track.content));
else if (track.content)
trackName = t('Track') + ' - ' + track.content;
return (
<Draggable
axis="x"
bounds={{ left: 0, right }}
grid={[snapValue, snapValue]}
position={{
x: (track.offsetTime - commonStore.trackCurrentTime) / (baseMoveTime * scale) * snapValue,
y: 0
}}
onStart={(e, data) => {
controlX.current = data.lastX;
}}
onStop={(e, data) => {
const delta = data.lastX - controlX.current;
let offsetTime = Math.round(Math.round(delta / snapValue * baseMoveTime * scale) / minimalMoveTime) * minimalMoveTime;
offsetTime = Math.min(Math.max(
offsetTime,
-track.offsetTime), commonStore.trackTotalTime - track.offsetTime);
const tracks = commonStore.tracks.slice();
tracks[trackIndex].offsetTime += offsetTime;
commonStore.setTracks(tracks);
refreshTracksTotalTime();
}}
>
<div
className={`p-1 cursor-move rounded whitespace-nowrap overflow-hidden ${trackClass}`}
style={{
width: `${Math.max(minimalTrackWidth,
track.contentTime / (baseMoveTime * scale) * snapValue
)}px`
}}
onClick={() => onSelect(id)}
>
<span className="text-white">{trackName}</span>
</div>
</Draggable>
);
});
const AudiotrackEditor: FC<{ setPrompt: (prompt: string) => void }> = observer(({ setPrompt }) => {
const { t } = useTranslation();
const viewControlsContainerRef = useRef<HTMLDivElement>(null);
const currentTimeControlRef = useRef<HTMLDivElement>(null);
const playStartTimeControlRef = useRef<HTMLDivElement>(null);
const tracksEndLineRef = useRef<HTMLDivElement>(null);
const tracksRef = useRef<HTMLDivElement>(null);
const toolbarRef = useRef<HTMLDivElement>(null);
const toolbarButtonRef = useRef<HTMLDivElement>(null);
const toolbarSliderRef = useRef<HTMLInputElement>(null);
const contentPreviewRef = useRef<HTMLDivElement>(null);
const [refreshRef, setRefreshRef] = useState(false);
const windowSize = useWindowSize();
const scale = (scaleMin + scaleMax) - commonStore.trackScale;
const [selectedTrackId, setSelectedTrackId] = useState<string>('');
const playStartTimeControlX = useRef(0);
const selectedTrack = selectedTrackId ? commonStore.tracks.find(t => t.id === selectedTrackId) : undefined;
useEffect(() => {
if (toolbarSliderRef.current && toolbarSliderRef.current.parentElement)
toolbarSliderRef.current.parentElement.style.removeProperty('--fui-Slider--steps-percent');
}, []);
const scrollContentToBottom = () => {
if (contentPreviewRef.current)
contentPreviewRef.current.scrollTop = contentPreviewRef.current.scrollHeight;
};
useEffect(() => {
scrollContentToBottom();
}, [commonStore.recordingContent]);
useEffect(() => {
setRefreshRef(!refreshRef);
}, [windowSize, commonStore.tracks]);
const viewControlsContainerWidth = (toolbarRef.current && toolbarButtonRef.current && toolbarSliderRef.current) ?
toolbarRef.current.clientWidth - toolbarButtonRef.current.clientWidth - toolbarSliderRef.current.clientWidth - 16 // 16 = ml-2 mr-2
: 0;
const tracksWidth = viewControlsContainerWidth;
const timeOfTracksWidth = Math.floor(tracksWidth / snapValue) // number of moves
* baseMoveTime * scale;
const currentTimeControlWidth = (timeOfTracksWidth < commonStore.trackTotalTime)
? timeOfTracksWidth / commonStore.trackTotalTime * viewControlsContainerWidth
: 0;
const playStartTimeControlPosition = (commonStore.trackPlayStartTime - commonStore.trackCurrentTime) / (baseMoveTime * scale) * snapValue;
const tracksEndPosition = (commonStore.trackTotalTime - commonStore.trackCurrentTime) / (baseMoveTime * scale) * snapValue;
const moveableTracksWidth = (tracksEndLineRef.current && viewControlsContainerRef.current &&
((tracksEndLineRef.current.getBoundingClientRect().left - (viewControlsContainerRef.current.getBoundingClientRect().left + trackInitOffsetPx)) > 0))
? tracksEndLineRef.current.getBoundingClientRect().left - (viewControlsContainerRef.current.getBoundingClientRect().left + trackInitOffsetPx)
: Infinity;
return (
<div className="flex flex-col gap-2 overflow-hidden" style={{ width: '80vw', height: '80vh' }}>
<div className="mx-auto">
<Text size={100}>{`${commonStore.trackPlayStartTime} ms / ${commonStore.trackTotalTime} ms`}</Text>
</div>
<div className="flex pb-2 border-b" ref={toolbarRef}>
<div className="flex gap-2" ref={toolbarButtonRef}>
<ToolTipButton disabled desc={t('Play All') + ' (Unavailable)'} icon={<Play16Regular />} />
<ToolTipButton desc={t('Clear All')} icon={<Delete16Regular />} onClick={() => {
commonStore.setTracks([]);
commonStore.setTrackScale(1);
commonStore.setTrackTotalTime(tracksMinimalTotalTime);
commonStore.setTrackCurrentTime(0);
commonStore.setTrackPlayStartTime(0);
}} />
</div>
<div className="grow">
<div className="flex flex-col ml-2 mr-2" ref={viewControlsContainerRef}>
<div className="relative">
<Tooltip content={`${commonStore.trackTotalTime} ms`} showDelay={0} hideDelay={0}
relationship="description">
<div className="border-l absolute"
ref={tracksEndLineRef}
style={{
height: (tracksRef.current && commonStore.tracks.length > 0)
? tracksRef.current.clientHeight - arrowIconToTracks
: 0,
top: `${topToArrowIcon + arrowIconToTracks}px`,
left: `${tracksEndPosition + trackInitOffsetPx - pixelFix}px`
}} />
</Tooltip>
</div>
<Draggable axis="x" bounds={{
left: 0,
right: viewControlsContainerWidth - currentTimeControlWidth
}}
position={{
x: commonStore.trackCurrentTime / commonStore.trackTotalTime * viewControlsContainerWidth,
y: 0
}}
onDrag={(e, data) => {
setTimeout(() => {
let offset = 0;
if (currentTimeControlRef.current) {
const match = currentTimeControlRef.current.style.transform.match(/translate\((.+)px,/);
if (match)
offset = parseFloat(match[1]);
}
const offsetTime = commonStore.trackTotalTime / viewControlsContainerWidth * offset;
commonStore.setTrackCurrentTime(offsetTime);
}, 1);
}}
>
<div ref={currentTimeControlRef}
className={classnames('h-2 cursor-move rounded', commonStore.settings.darkMode ? 'bg-neutral-600' : 'bg-gray-700')}
style={{ width: currentTimeControlWidth }} />
</Draggable>
<div className={classnames(
'flex',
(playStartTimeControlPosition < 0 || playStartTimeControlPosition > viewControlsContainerWidth)
&& 'hidden'
)}>
<Draggable axis="x" bounds={{
left: 0,
right: (playStartTimeControlRef.current)
? Math.min(viewControlsContainerWidth - playStartTimeControlRef.current.clientWidth, moveableTracksWidth)
: 0
}}
grid={[snapValue, snapValue]}
position={{ x: playStartTimeControlPosition, y: 0 }}
onStart={(e, data) => {
playStartTimeControlX.current = data.lastX;
}}
onStop={(e, data) => {
const delta = data.lastX - playStartTimeControlX.current;
let offsetTime = Math.round(Math.round(delta / snapValue * baseMoveTime * scale) / minimalMoveTime) * minimalMoveTime;
offsetTime = Math.min(Math.max(
offsetTime,
-commonStore.trackPlayStartTime), commonStore.trackTotalTime - commonStore.trackPlayStartTime);
commonStore.setTrackPlayStartTime(commonStore.trackPlayStartTime + offsetTime);
}}
>
<div className="relative cursor-move"
ref={playStartTimeControlRef}>
<ArrowAutofitWidth20Regular />
<div
className={classnames('border-l absolute', commonStore.settings.darkMode ? 'border-white' : 'border-gray-700')}
style={{
height: (tracksRef.current && commonStore.tracks.length > 0)
? tracksRef.current.clientHeight
: 0,
top: '50%',
left: `calc(50% - ${pixelFix}px)`
}} />
</div>
</Draggable>
</div>
</div>
</div>
<Tooltip content={t('Scale View')! + ': ' + commonStore.trackScale} showDelay={0} hideDelay={0}
relationship="description">
<Slider ref={toolbarSliderRef} value={commonStore.trackScale} step={scaleMin} max={scaleMax} min={scaleMin}
onChange={(e, data) => {
commonStore.setTrackScale(data.value);
}}
/>
</Tooltip>
</div>
<div className="flex flex-col overflow-y-auto gap-1" ref={tracksRef}>
{commonStore.tracks.map(track =>
<div key={track.id} className="flex gap-2 pb-1 border-b">
<div className="flex gap-1 border-r h-7">
<ToolTipButton desc={commonStore.recordingTrackId === track.id ? t('Stop') : t('Record')}
icon={commonStore.recordingTrackId === track.id ? <Stop16Filled /> : <Record16Regular />}
size="small" shape="circular" appearance="subtle"
onClick={() => {
flushMidiRecordingContent();
commonStore.setPlayingTrackId('');
if (commonStore.recordingTrackId === track.id) {
commonStore.setRecordingTrackId('');
} else {
if (commonStore.activeMidiDeviceIndex === -1) {
toast(t('Please select a MIDI device first'), { type: 'warning' });
return;
}
dropRecordingTime = true;
setSelectedTrackId(track.id);
commonStore.setRecordingTrackId(track.id);
commonStore.setRecordingContent(track.content);
commonStore.setRecordingRawContent(track.rawContent.slice());
}
}} />
<ToolTipButton disabled
desc={commonStore.playingTrackId === track.id ? t('Stop') : t('Play') + ' (Unavailable)'}
icon={commonStore.playingTrackId === track.id ? <Pause16Regular /> : <Play16Filled />}
size="small" shape="circular" appearance="subtle"
onClick={() => {
flushMidiRecordingContent();
commonStore.setRecordingTrackId('');
if (commonStore.playingTrackId === track.id) {
commonStore.setPlayingTrackId('');
} else {
setSelectedTrackId(track.id);
commonStore.setPlayingTrackId(track.id);
}
}} />
<ToolTipButton desc={t('Delete')} icon={<Delete16Regular />} size="small" shape="circular"
appearance="subtle" onClick={() => {
const tracks = commonStore.tracks.slice().filter(t => t.id !== track.id);
commonStore.setTracks(tracks);
refreshTracksTotalTime();
}} />
</div>
<div className="relative grow overflow-hidden">
<div className="absolute" style={{ left: -0 }}>
<Track
id={track.id}
scale={scale}
right={Math.min(tracksWidth, moveableTracksWidth)}
isSelected={selectedTrackId === track.id}
onSelect={setSelectedTrackId}
/>
</div>
</div>
</div>)}
<div className="flex justify-between items-center">
<Button icon={<Add16Regular />} size="small" shape="circular"
appearance="subtle"
onClick={() => {
commonStore.setTracks([...commonStore.tracks, {
id: uuid(),
mainInstrument: '',
content: '',
rawContent: [],
offsetTime: 0,
contentTime: 0
}]);
}}>
{t('New Track')}
</Button>
<Text size={100}>
{t('Select a track to preview the content')}
</Text>
</div>
</div>
<div className="grow"></div>
{selectedTrack &&
<Card size="small" appearance="outline" style={{ minHeight: '150px', maxHeight: '200px' }}>
<div className="flex flex-col gap-1 overflow-hidden">
<Text size={100}>{`${t('Start Time')}: ${selectedTrack.offsetTime} ms`}</Text>
<Text size={100}>{`${t('Content Duration')}: ${selectedTrack.contentTime} ms`}</Text>
<div className="overflow-y-auto overflow-x-hidden" ref={contentPreviewRef}>
{selectedTrackId === commonStore.recordingTrackId
? commonStore.recordingContent
: selectedTrack.content}
</div>
</div>
</Card>
}
<DialogTrigger disableButtonEnhancement>
<Button icon={<MusicNote220Regular />} style={{ minHeight: '32px' }} onClick={() => {
flushMidiRecordingContent();
commonStore.setRecordingTrackId('');
commonStore.setPlayingTrackId('');
const timestamp = [];
const sortedTracks = commonStore.tracks.slice().sort((a, b) => a.offsetTime - b.offsetTime);
for (const track of sortedTracks) {
timestamp.push(track.offsetTime);
let accContentTime = 0;
for (const msg of track.rawContent) {
if (msg.messageType === 'ElapsedTime') {
accContentTime += msg.value;
timestamp.push(track.offsetTime + accContentTime);
}
}
}
const sortedTimestamp = timestamp.slice().sort((a, b) => a - b);
const globalMessages: MidiMessage[] = sortedTimestamp.reduce((messages, current, i) =>
[...messages, {
messageType: 'ElapsedTime',
value: current - (i === 0 ? 0 : sortedTimestamp[i - 1])
} as MidiMessage]
, [] as MidiMessage[]);
for (const track of sortedTracks) {
let currentTime = track.offsetTime;
let accContentTime = 0;
for (const msg of track.rawContent) {
if (msg.messageType === 'ElapsedTime') {
accContentTime += msg.value;
currentTime = track.offsetTime + accContentTime;
} else if (msg.messageType === 'NoteOn' || msg.messageType === 'NoteOff') {
const insertIndex = sortedTimestamp.findIndex(t => t >= currentTime);
globalMessages.splice(insertIndex + 1, 0, msg);
sortedTimestamp.splice(insertIndex + 1, 0, 0); // placeholder
}
}
}
const result = ('<pad> ' + globalMessages.map(m => midiMessageToToken(m)).join('')).trim();
commonStore.setCompositionSubmittedPrompt(result);
setPrompt(result);
}}>
{t('Save to generation area')}
</Button>
</DialogTrigger>
</div>
);
});
export default AudiotrackEditor;

View File

@@ -16,8 +16,11 @@ import {
Attach16Regular, Attach16Regular,
Delete28Regular, Delete28Regular,
Dismiss16Regular, Dismiss16Regular,
Dismiss24Regular,
RecordStop28Regular, RecordStop28Regular,
Save28Regular SaveRegular,
TextAlignJustify24Regular,
TextAlignJustifyRotate9024Regular
} from '@fluentui/react-icons'; } from '@fluentui/react-icons';
import { CopyButton } from '../components/CopyButton'; import { CopyButton } from '../components/CopyButton';
import { ReadButton } from '../components/ReadButton'; import { ReadButton } from '../components/ReadButton';
@@ -25,49 +28,22 @@ import { toast } from 'react-toastify';
import { WorkHeader } from '../components/WorkHeader'; import { WorkHeader } from '../components/WorkHeader';
import { DialogButton } from '../components/DialogButton'; import { DialogButton } from '../components/DialogButton';
import { OpenFileFolder, OpenOpenFileDialog, OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App'; import { OpenFileFolder, OpenOpenFileDialog, OpenSaveFileDialog } from '../../wailsjs/go/backend_golang/App';
import { absPathAsset, bytesToReadable, toastWithButton } from '../utils'; import { absPathAsset, bytesToReadable, getServerRoot, setActivePreset, toastWithButton } from '../utils';
import { PresetsButton } from './PresetsManager/PresetsButton';
import { useMediaQuery } from 'usehooks-ts'; import { useMediaQuery } from 'usehooks-ts';
import { botName, ConversationMessage, MessageType, userName, welcomeUuid } from '../types/chat';
import { Labeled } from '../components/Labeled';
import { ValuedSlider } from '../components/ValuedSlider';
import { PresetsButton } from './PresetsManager/PresetsButton';
import { webOpenOpenFileDialog } from '../utils/web-file-operations';
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
}) => { }) => {
@@ -91,13 +67,15 @@ const MoreUtilsButton: FC<{ uuid: string, setEditing: (editing: boolean) => void
onClick={() => { onClick={() => {
commonStore.conversationOrder.splice(commonStore.conversationOrder.indexOf(uuid), 1); commonStore.conversationOrder.splice(commonStore.conversationOrder.indexOf(uuid), 1);
delete commonStore.conversation[uuid]; delete commonStore.conversation[uuid];
commonStore.setAttachment(uuid, null);
}} /> }} />
</MenuPopover> </MenuPopover>
</Menu>; </Menu>;
}); });
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();
@@ -157,7 +135,21 @@ const ChatMessageItem: FC<{
)} )}
> >
{!editing ? {!editing ?
<MarkdownRender>{messageItem.content}</MarkdownRender> : <div className="flex flex-col">
<MarkdownRender>{messageItem.content}</MarkdownRender>
{uuid in commonStore.attachments &&
<div className="flex grow">
<div className="grow" />
<ToolTipButton className="whitespace-nowrap"
text={
commonStore.attachments[uuid][0].name.replace(
new RegExp('(^[^\\.]{5})[^\\.]+'), '$1...')
}
desc={`${commonStore.attachments[uuid][0].name} (${bytesToReadable(commonStore.attachments[uuid][0].size)})`}
size="small" shape="circular" appearance="secondary" />
</div>
}
</div> :
<Textarea ref={textareaRef} <Textarea ref={textareaRef}
className="grow" className="grow"
style={{ minWidth: 0 }} style={{ minWidth: 0 }}
@@ -202,11 +194,125 @@ const ChatMessageItem: FC<{
</div>; </div>;
}); });
const SidePanel: FC = observer(() => {
const [t] = useTranslation();
const mq = useMediaQuery('(min-width: 640px)');
const params = commonStore.chatParams;
return <div
className={classnames(
'flex flex-col gap-1 h-full flex-shrink-0 transition-width duration-300 ease-in-out',
commonStore.sidePanelCollapsed ? 'w-0' : (mq ? 'w-64' : 'w-full'),
!commonStore.sidePanelCollapsed && 'ml-1')
}>
<div className="flex m-1">
<div className="grow" />
<PresetsButton tab="Chat" size="medium" shape="circular" appearance="subtle" />
<Button size="medium" shape="circular" appearance="subtle" icon={<Dismiss24Regular />}
onClick={() => commonStore.setSidePanelCollapsed(true)}
/>
</div>
<div className="flex flex-col gap-1 overflow-x-hidden overflow-y-auto p-1">
<Labeled flex breakline label={t('Max Response Token')}
desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')}
content={
<ValuedSlider value={params.maxResponseToken} min={100} max={8100}
step={100}
input
onChange={(e, data) => {
commonStore.setChatParams({
maxResponseToken: data.value
});
}} />
} />
<Labeled flex breakline label={t('Temperature')}
desc={t('Sampling temperature, it\'s like giving alcohol to a model, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.')}
content={
<ValuedSlider value={params.temperature} min={0} max={2} step={0.1}
input
onChange={(e, data) => {
commonStore.setChatParams({
temperature: data.value
});
}} />
} />
<Labeled flex breakline label={t('Top_P')}
desc={t('Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.')}
content={
<ValuedSlider value={params.topP} min={0} max={1} step={0.1} input
onChange={(e, data) => {
commonStore.setChatParams({
topP: data.value
});
}} />
} />
<Labeled flex breakline label={t('Presence Penalty')}
desc={t('Positive values penalize new tokens based on whether they appear in the text so far, increasing the model\'s likelihood to talk about new topics.')}
content={
<ValuedSlider value={params.presencePenalty} min={0} max={2}
step={0.1} input
onChange={(e, data) => {
commonStore.setChatParams({
presencePenalty: data.value
});
}} />
} />
<Labeled flex breakline label={t('Frequency Penalty')}
desc={t('Positive values penalize new tokens based on their existing frequency in the text so far, decreasing the model\'s likelihood to repeat the same line verbatim.')}
content={
<ValuedSlider value={params.frequencyPenalty} min={0} max={2}
step={0.1} input
onChange={(e, data) => {
commonStore.setChatParams({
frequencyPenalty: data.value
});
}} />
} />
</div>
<div className="grow" />
{/*<Button*/}
{/* icon={<FolderOpenVerticalRegular />}*/}
{/* onClick={() => {*/}
{/* }}>*/}
{/* {t('Load Conversation')}*/}
{/*</Button>*/}
<Button
icon={<SaveRegular />}
onClick={() => {
let savedContent: string = '';
const isWorldModel = commonStore.getCurrentModelConfig().modelParameters.modelName.toLowerCase().includes('world');
const user = isWorldModel ? 'User' : 'Bob';
const bot = isWorldModel ? 'Assistant' : 'Alice';
commonStore.conversationOrder.forEach((uuid) => {
if (uuid === welcomeUuid)
return;
const messageItem = commonStore.conversation[uuid];
if (messageItem.type !== MessageType.Error) {
savedContent += `${messageItem.sender === userName ? user : bot}: ${messageItem.content}\n\n`;
}
});
OpenSaveFileDialog('*.txt', 'conversation.txt', savedContent).then((path) => {
if (path)
toastWithButton(t('Conversation Saved'), t('Open'), () => {
OpenFileFolder(path, false);
});
}).catch(e => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
});
}}>
{t('Save Conversation')}
</Button>
</div>;
});
const ChatPanel: FC = observer(() => { const ChatPanel: FC = observer(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const bodyRef = useRef<HTMLDivElement>(null); const bodyRef = useRef<HTMLDivElement>(null);
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const mq = useMediaQuery('(min-width: 640px)'); const mq = useMediaQuery('(min-width: 640px)');
if (commonStore.sidePanelCollapsed === 'auto')
commonStore.setSidePanelCollapsed(!mq);
const currentConfig = commonStore.getCurrentModelConfig(); const currentConfig = commonStore.getCurrentModelConfig();
const apiParams = currentConfig.apiParameters; const apiParams = currentConfig.apiParameters;
const port = apiParams.apiPort; const port = apiParams.apiPort;
@@ -228,7 +334,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
} }
@@ -245,7 +351,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;
} }
@@ -275,6 +381,11 @@ const ChatPanel: FC = observer(() => {
commonStore.setConversation(commonStore.conversation); commonStore.setConversation(commonStore.conversation);
commonStore.conversationOrder.push(newId); commonStore.conversationOrder.push(newId);
commonStore.setConversationOrder(commonStore.conversationOrder); commonStore.setConversationOrder(commonStore.conversationOrder);
if (commonStore.currentTempAttachment) {
commonStore.setAttachment(newId, [commonStore.currentTempAttachment]);
commonStore.setCurrentTempAttachment(null);
}
} }
let startIndex = startUuid ? commonStore.conversationOrder.indexOf(startUuid) : 0; let startIndex = startUuid ? commonStore.conversationOrder.indexOf(startUuid) : 0;
@@ -282,20 +393,21 @@ const ChatPanel: FC = observer(() => {
let targetRange = commonStore.conversationOrder.slice(startIndex, endIndex); let targetRange = commonStore.conversationOrder.slice(startIndex, endIndex);
const messages: ConversationMessage[] = []; const messages: ConversationMessage[] = [];
if (commonStore.attachmentContent) {
messages.push({
role: 'user',
content: t('The content of file') + ` "${commonStore.attachmentName}" `
+ t('is as follows. When replying to me, consider the file content and respond accordingly:')
+ '\n\n' + commonStore.attachmentContent
});
messages.push({ role: 'user', content: t('What\'s the file name') });
messages.push({ role: 'assistant', content: t('The file name is: ') + commonStore.attachmentName });
}
targetRange.forEach((uuid, index) => { targetRange.forEach((uuid, index) => {
if (uuid === welcomeUuid) if (uuid === welcomeUuid)
return; return;
const messageItem = commonStore.conversation[uuid]; const messageItem = commonStore.conversation[uuid];
if (uuid in commonStore.attachments) {
const attachment = commonStore.attachments[uuid][0];
messages.push({
role: 'user',
content: t('The content of file') + ` "${attachment.name}" `
+ t('is as follows. When replying to me, consider the file content and respond accordingly:')
+ '\n\n' + attachment.content
});
messages.push({ role: 'user', content: t('What\'s the file name') });
messages.push({ role: 'assistant', content: t('The file name is: ') + attachment.name });
}
if (messageItem.done && messageItem.type === MessageType.Normal && messageItem.sender === userName) { if (messageItem.done && messageItem.type === MessageType.Normal && messageItem.sender === userName) {
messages.push({ role: 'user', content: messageItem.content }); messages.push({ role: 'user', content: messageItem.content });
} else if (messageItem.done && messageItem.type === MessageType.Normal && messageItem.sender === botName) { } else if (messageItem.done && messageItem.type === MessageType.Normal && messageItem.sender === botName) {
@@ -323,10 +435,8 @@ const ChatPanel: FC = observer(() => {
let answer = ''; let answer = '';
const chatSseController = new AbortController(); const chatSseController = new AbortController();
chatSseControllers[answerId] = chatSseController; chatSseControllers[answerId] = chatSseController;
fetchEventSource( // https://api.openai.com/v1/chat/completions || http://127.0.0.1:${port}/chat/completions fetchEventSource( // https://api.openai.com/v1/chat/completions || http://127.0.0.1:${port}/v1/chat/completions
commonStore.settings.apiUrl ? getServerRoot(port) + '/v1/chat/completions',
commonStore.settings.apiUrl + '/v1/chat/completions' :
`http://127.0.0.1:${port}/chat/completions`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -337,16 +447,20 @@ const ChatPanel: FC = observer(() => {
messages, messages,
stream: true, stream: true,
model: commonStore.settings.apiChatModelName, // 'gpt-3.5-turbo' model: commonStore.settings.apiChatModelName, // 'gpt-3.5-turbo'
temperature: apiParams.temperature, temperature: commonStore.chatParams.temperature,
top_p: apiParams.topP, top_p: commonStore.chatParams.topP,
user_name: commonStore.activePreset?.userName, presence_penalty: commonStore.chatParams.presencePenalty,
assistant_name: commonStore.activePreset?.assistantName, frequency_penalty: commonStore.chatParams.frequencyPenalty,
presystem: commonStore.activePreset?.presystem user_name: commonStore.activePreset?.userName || undefined,
assistant_name: commonStore.activePreset?.assistantName || undefined,
presystem: commonStore.activePreset?.presystem && undefined
}), }),
signal: chatSseController?.signal, signal: chatSseController?.signal,
onmessage(e) { onmessage(e) {
scrollToBottom(); scrollToBottom();
if (e.data.trim() === '[DONE]') { if (e.data.trim() === '[DONE]') {
if (answerId! in chatSseControllers)
delete chatSseControllers[answerId!];
commonStore.conversation[answerId!].done = true; commonStore.conversation[answerId!].done = true;
commonStore.conversation[answerId!].content = commonStore.conversation[answerId!].content.trim(); commonStore.conversation[answerId!].content = commonStore.conversation[answerId!].content.trim();
commonStore.setConversation(commonStore.conversation); commonStore.setConversation(commonStore.conversation);
@@ -360,6 +474,8 @@ const ChatPanel: FC = observer(() => {
console.debug('json error', error); console.debug('json error', error);
return; return;
} }
if (data.model)
commonStore.setLastModelName(data.model);
if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) { if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
answer += data.choices[0]?.delta?.content || ''; answer += data.choices[0]?.delta?.content || '';
commonStore.conversation[answerId!].content = answer; commonStore.conversation[answerId!].content = answer;
@@ -381,6 +497,8 @@ const ChatPanel: FC = observer(() => {
console.log('Connection closed'); console.log('Connection closed');
}, },
onerror(err) { onerror(err) {
if (answerId! in chatSseControllers)
delete chatSseControllers[answerId!];
commonStore.conversation[answerId!].type = MessageType.Error; commonStore.conversation[answerId!].type = MessageType.Error;
commonStore.conversation[answerId!].done = true; commonStore.conversation[answerId!].done = true;
err = err.message || err; err = err.message || err;
@@ -395,172 +513,175 @@ const ChatPanel: FC = observer(() => {
}, []); }, []);
return ( return (
<div className="flex flex-col w-full grow gap-4 pt-4 overflow-hidden"> <div className="flex h-full grow pt-4 overflow-hidden">
<div ref={bodyRef} className="grow overflow-y-scroll overflow-x-hidden pr-2"> <div className="relative flex flex-col w-full grow gap-4 overflow-hidden">
{commonStore.conversationOrder.map(uuid => <Button className="absolute top-1 right-1" size="medium" shape="circular" appearance="subtle"
<ChatMessageItem key={uuid} uuid={uuid} onSubmit={onSubmit} /> style={{ zIndex: 1 }}
)} icon={commonStore.sidePanelCollapsed ? <TextAlignJustify24Regular /> : <TextAlignJustifyRotate9024Regular />}
</div> onClick={() => commonStore.setSidePanelCollapsed(!commonStore.sidePanelCollapsed)} />
<div className={classnames('flex items-end', mq ? 'gap-2' : '')}> <div ref={bodyRef} className="grow overflow-y-scroll overflow-x-hidden pr-2">
<PresetsButton tab="Chat" size={mq ? 'large' : 'small'} shape="circular" appearance="subtle" /> {commonStore.conversationOrder.map(uuid =>
<DialogButton tooltip={t('Clear')} <ChatMessageItem key={uuid} uuid={uuid} onSubmit={onSubmit} />
icon={<Delete28Regular />} )}
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle" title={t('Clear')} </div>
contentText={t('Are you sure you want to clear the conversation? It cannot be undone.')} <div className={classnames('flex items-end', mq ? 'gap-2' : '')}>
onConfirm={() => { <DialogButton tooltip={t('Clear')}
if (generating) { icon={<Delete28Regular />}
for (const id in chatSseControllers) { size={mq ? 'large' : 'small'} shape="circular" appearance="subtle" title={t('Clear')}
chatSseControllers[id].abort(); contentText={t('Are you sure you want to clear the conversation? It cannot be undone.')}
onConfirm={() => {
if (generating) {
for (const id in chatSseControllers) {
chatSseControllers[id].abort();
}
chatSseControllers = {};
} }
chatSseControllers = {}; setActivePreset(commonStore.activePreset);
} }} />
commonStore.setConversation({}); <div className="relative flex grow">
commonStore.setConversationOrder([]); <Textarea
}} /> ref={inputRef}
<div className="relative flex grow"> style={{ minWidth: 0 }}
<Textarea className="grow"
ref={inputRef} resize="vertical"
style={{ minWidth: 0 }} placeholder={t('Type your message here')!}
className="grow" value={commonStore.currentInput}
resize="vertical" onChange={(e) => commonStore.setCurrentInput(e.target.value)}
placeholder={t('Type your message here')!} onKeyDown={handleKeyDownOrClick}
value={commonStore.currentInput} />
onChange={(e) => commonStore.setCurrentInput(e.target.value)} <div className="absolute right-2 bottom-2">
onKeyDown={handleKeyDownOrClick} {!commonStore.currentTempAttachment ?
/> <ToolTipButton
<div className="absolute right-2 bottom-2"> desc={commonStore.attachmentUploading ?
{!commonStore.attachmentContent ? t('Uploading Attachment') :
<ToolTipButton t('Add An Attachment (Accepts pdf, txt)')}
desc={commonStore.attachmentUploading ? icon={commonStore.attachmentUploading ?
t('Uploading Attachment') : <ArrowClockwise16Regular className="animate-spin" />
t('Add An Attachment (Accepts pdf, txt)')} : <Attach16Regular />}
icon={commonStore.attachmentUploading ? size="small" shape="circular" appearance="secondary"
<ArrowClockwise16Regular className="animate-spin" /> onClick={() => {
: <Attach16Regular />} if (commonStore.attachmentUploading)
size="small" shape="circular" appearance="secondary"
onClick={() => {
if (commonStore.status.status === ModelStatus.Offline && !commonStore.settings.apiUrl) {
toast(t('Please click the button in the top right corner to start the model'), { type: 'warning' });
return;
}
if (commonStore.attachmentUploading)
return;
OpenOpenFileDialog('*.txt;*.pdf').then(async filePath => {
if (!filePath)
return; return;
commonStore.setAttachmentUploading(true); const filterPattern = '*.txt;*.pdf';
const setUploading = () => commonStore.setAttachmentUploading(true);
// Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file. // actually, status of web platform is always Offline
// const blob = new Blob([atob(info.content as unknown as string)]); // await fetch(`data:application/octet-stream;base64,${info.content}`).then(r => r.blob()); if (commonStore.platform === 'web' || commonStore.status.status === ModelStatus.Offline || currentConfig.modelParameters.device === 'WebGPU') {
const blob = await fetch(absPathAsset(filePath)).then(r => r.blob()); webOpenOpenFileDialog({ filterPattern, fnStartLoading: setUploading }).then(webReturn => {
const attachmentName = filePath.split(/[\\/]/).pop(); if (webReturn.content)
const urlPath = `/file-to-text?file_name=${attachmentName}`; commonStore.setCurrentTempAttachment(
const bodyForm = new FormData(); {
bodyForm.append('file_data', blob, attachmentName); name: webReturn.blob.name,
fetch(commonStore.settings.apiUrl ? size: webReturn.blob.size,
commonStore.settings.apiUrl + urlPath : content: webReturn.content
`http://127.0.0.1:${port}${urlPath}`, { });
method: 'POST', else
body: bodyForm toast(t('File is empty'), {
}).then(async r => { type: 'info',
if (r.status === 200) { autoClose: 1000
const pages = (await r.json()).pages as any[];
let attachmentContent: string;
if (pages.length === 1)
attachmentContent = pages[0].page_content;
else
attachmentContent = pages.map((p, i) => `Page ${i + 1}:\n${p.page_content}`).join('\n\n');
commonStore.setAttachmentName(attachmentName!);
commonStore.setAttachmentSize(blob.size);
commonStore.setAttachmentContent(attachmentContent);
} else {
toast(r.statusText + '\n' + (await r.text()), {
type: 'error'
}); });
} }).catch(e => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
}).finally(() => {
commonStore.setAttachmentUploading(false); commonStore.setAttachmentUploading(false);
} });
).catch(e => { } else {
commonStore.setAttachmentUploading(false); OpenOpenFileDialog(filterPattern).then(async filePath => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 }); if (!filePath)
}); return;
}).catch(e => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
});
}}
/> :
<div>
<ToolTipButton
text={
commonStore.attachmentName.replace(
new RegExp('(^[^\\.]{5})[^\\.]+'), '$1...')
}
desc={`${commonStore.attachmentName} (${bytesToReadable(commonStore.attachmentSize)})`}
size="small" shape="circular" appearance="secondary" />
<ToolTipButton desc={t('Remove Attachment')}
icon={<Dismiss16Regular />}
size="small" shape="circular" appearance="subtle"
onClick={() => {
commonStore.setAttachmentName('');
commonStore.setAttachmentSize(0);
commonStore.setAttachmentContent('');
}} />
</div>
}
</div>
</div>
<ToolTipButton desc={generating ? t('Stop') : t('Send')}
icon={generating ? <RecordStop28Regular /> : <ArrowCircleUp28Regular />}
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
onClick={(e) => {
if (generating) {
for (const id in chatSseControllers) {
chatSseControllers[id].abort();
commonStore.conversation[id].type = MessageType.Error;
commonStore.conversation[id].done = true;
}
chatSseControllers = {};
commonStore.setConversation(commonStore.conversation);
commonStore.setConversationOrder([...commonStore.conversationOrder]);
} else {
handleKeyDownOrClick(e);
}
}} />
<ToolTipButton desc={t('Save')}
icon={<Save28Regular />}
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
onClick={() => {
let savedContent: string = '';
const isWorldModel = commonStore.getCurrentModelConfig().modelParameters.modelName.toLowerCase().includes('world');
const user = isWorldModel ? 'User' : 'Bob';
const bot = isWorldModel ? 'Assistant' : 'Alice';
commonStore.conversationOrder.forEach((uuid) => {
if (uuid === welcomeUuid)
return;
const messageItem = commonStore.conversation[uuid];
if (messageItem.type !== MessageType.Error) {
savedContent += `${messageItem.sender === userName ? user : bot}: ${messageItem.content}\n\n`;
}
});
OpenSaveFileDialog('*.txt', 'conversation.txt', savedContent).then((path) => { setUploading();
if (path)
toastWithButton(t('Conversation Saved'), t('Open'), () => { // Both are slow. Communication between frontend and backend is slow. Use AssetServer Handler to read the file.
OpenFileFolder(path, false); // 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());
}).catch(e => { const attachmentName = filePath.split(/[\\/]/).pop();
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 }); const urlPath = `/file-to-text?file_name=${attachmentName}`;
}); const bodyForm = new FormData();
}} /> bodyForm.append('file_data', blob, attachmentName);
fetch(getServerRoot(port) + urlPath, {
method: 'POST',
body: bodyForm
}).then(async r => {
if (r.status === 200) {
const pages = (await r.json()).pages as any[];
let attachmentContent: string;
if (pages.length === 1)
attachmentContent = pages[0].page_content;
else
attachmentContent = pages.map((p, i) => `Page ${i + 1}:\n${p.page_content}`).join('\n\n');
if (attachmentContent)
commonStore.setCurrentTempAttachment(
{
name: attachmentName!,
size: blob.size,
content: attachmentContent!
});
else
toast(t('File is empty'), {
type: 'info',
autoClose: 1000
});
} else {
toast(r.statusText + '\n' + (await r.text()), {
type: 'error'
});
}
}
).catch(e => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
}).finally(() => {
commonStore.setAttachmentUploading(false);
});
}).catch(e => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
});
}
}}
/> :
<div>
<ToolTipButton
text={
commonStore.currentTempAttachment.name.replace(
new RegExp('(^[^\\.]{5})[^\\.]+'), '$1...')
}
desc={`${commonStore.currentTempAttachment.name} (${bytesToReadable(commonStore.currentTempAttachment.size)})`}
size="small" shape="circular" appearance="secondary" />
<ToolTipButton desc={t('Remove Attachment')}
icon={<Dismiss16Regular />}
size="small" shape="circular" appearance="subtle"
onClick={() => {
commonStore.setCurrentTempAttachment(null);
}} />
</div>
}
</div>
</div>
<ToolTipButton desc={generating ? t('Stop') : t('Send')}
icon={generating ? <RecordStop28Regular /> : <ArrowCircleUp28Regular />}
size={mq ? 'large' : 'small'} shape="circular" appearance="subtle"
onClick={(e) => {
if (generating) {
for (const id in chatSseControllers) {
chatSseControllers[id].abort();
commonStore.conversation[id].type = MessageType.Error;
commonStore.conversation[id].done = true;
}
chatSseControllers = {};
commonStore.setConversation(commonStore.conversation);
commonStore.setConversationOrder([...commonStore.conversationOrder]);
} else {
handleKeyDownOrClick(e);
}
}} />
</div>
</div> </div>
<SidePanel />
</div> </div>
); );
}); });
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 />
@@ -568,3 +689,5 @@ export const Chat: FC = observer(() => {
</div> </div>
); );
}); });
export default Chat;

View File

@@ -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,8 @@ 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'> & { import { getServerRoot } from '../utils';
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;
@@ -40,8 +29,10 @@ const CompletionPanel: FC = observer(() => {
}; };
useEffect(() => { useEffect(() => {
if (inputRef.current) if (inputRef.current) {
inputRef.current.style.height = '100%'; inputRef.current.style.height = '100%';
inputRef.current.style.maxHeight = '100%';
}
scrollToBottom(); scrollToBottom();
}, []); }, []);
@@ -80,7 +71,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;
@@ -90,10 +81,8 @@ const CompletionPanel: FC = observer(() => {
let answer = ''; let answer = '';
completionSseController = new AbortController(); completionSseController = new AbortController();
fetchEventSource( // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/completions fetchEventSource( // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/v1/completions
commonStore.settings.apiUrl ? getServerRoot(port) + '/v1/completions',
commonStore.settings.apiUrl + '/v1/completions' :
`http://127.0.0.1:${port}/completions`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -125,6 +114,8 @@ const CompletionPanel: FC = observer(() => {
console.debug('json error', error); console.debug('json error', error);
return; return;
} }
if (data.model)
commonStore.setLastModelName(data.model);
if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) { if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
answer += data.choices[0]?.text || data.choices[0]?.delta?.content || ''; answer += data.choices[0]?.text || data.choices[0]?.delta?.content || '';
setPrompt(prompt + answer.replace(/\s+$/, '') + params.injectEnd.replaceAll('\\n', '\n')); setPrompt(prompt + answer.replace(/\s+$/, '') + params.injectEnd.replaceAll('\\n', '\n'));
@@ -186,7 +177,7 @@ const CompletionPanel: FC = observer(() => {
desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')} desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')}
content={ content={
<ValuedSlider value={params.maxResponseToken} min={100} max={8100} <ValuedSlider value={params.maxResponseToken} min={100} max={8100}
step={400} step={100}
input input
onChange={(e, data) => { onChange={(e, data) => {
setParams({ setParams({
@@ -269,7 +260,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 +294,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 +302,5 @@ export const Completion: FC = observer(() => {
</div> </div>
); );
}); });
export default Completion;

View File

@@ -1,7 +1,8 @@
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';
import { Button, Checkbox, Textarea } from '@fluentui/react-components'; import { Button, Checkbox, Dropdown, Option, Textarea } from '@fluentui/react-components';
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';
@@ -15,24 +16,23 @@ import { PlayerElement, VisualizerElement } from 'html-midi-player';
import * as mm from '@magenta/music/esm/core.js'; import * as mm from '@magenta/music/esm/core.js';
import { NoteSequence } from '@magenta/music/esm/protobuf.js'; 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 {
import { toastWithButton } from '../utils'; CloseMidiPort,
FileExists,
export type CompositionParams = { OpenFileFolder,
prompt: string, OpenMidiPort,
maxResponseToken: number, OpenSaveFileDialogBytes
temperature: number, } from '../../wailsjs/go/backend_golang/App';
topP: number, import { getServerRoot, getSoundFont, toastWithButton } from '../utils';
autoPlay: boolean, import { CompositionParams } from '../types/composition';
useLocalSoundFont: boolean, import { useMediaQuery } from 'usehooks-ts';
midi: ArrayBuffer | null, import { AudiotrackButton } from './AudiotrackManager/AudiotrackButton';
ns: NoteSequence | null
}
let compositionSseController: AbortController | null = null; let compositionSseController: AbortController | null = null;
const CompositionPanel: FC = observer(() => { const CompositionPanel: FC = observer(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const mq = useMediaQuery('(min-width: 640px)');
const inputRef = useRef<HTMLTextAreaElement>(null); const inputRef = useRef<HTMLTextAreaElement>(null);
const port = commonStore.getCurrentModelConfig().apiParameters.apiPort; const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
const visualizerRef = useRef<VisualizerElement>(null); const visualizerRef = useRef<VisualizerElement>(null);
@@ -71,26 +71,16 @@ const CompositionPanel: FC = observer(() => {
}; };
const setSoundFont = async () => { const setSoundFont = async () => {
let soundUrl: string;
if (commonStore.compositionParams.useLocalSoundFont)
soundUrl = 'assets/sound-font';
else
soundUrl = !commonStore.settings.giteeUpdatesSource ?
`https://raw.githubusercontent.com/josStorer/sgm_plus/master` :
`https://gitee.com/josc146/sgm_plus/raw/master`;
const fallbackUrl = 'https://cdn.jsdelivr.net/gh/josstorer/sgm_plus';
await fetch(soundUrl + '/soundfont.json').then(r => {
if (!r.ok)
soundUrl = fallbackUrl;
}).catch(() => soundUrl = fallbackUrl);
if (playerRef.current) { if (playerRef.current) {
playerRef.current.soundFont = soundUrl; playerRef.current.soundFont = await getSoundFont();
} }
}; };
useEffect(() => { useEffect(() => {
if (inputRef.current) if (inputRef.current) {
inputRef.current.style.height = '100%'; inputRef.current.style.height = '100%';
inputRef.current.style.maxHeight = '100%';
}
scrollToBottom(); scrollToBottom();
if (playerRef.current && visualizerRef.current) { if (playerRef.current && visualizerRef.current) {
@@ -108,10 +98,15 @@ const CompositionPanel: FC = observer(() => {
} }
}, []); }, []);
useEffect(() => {
if (!(commonStore.activeMidiDeviceIndex in commonStore.midiPorts)) {
commonStore.setActiveMidiDeviceIndex(-1);
CloseMidiPort();
}
}, [commonStore.midiPorts]);
const generateNs = (autoPlay: boolean) => { const generateNs = (autoPlay: boolean) => {
fetch(commonStore.settings.apiUrl ? fetch(getServerRoot(port) + '/text-to-midi', {
commonStore.settings.apiUrl + '/text-to-midi' :
`http://127.0.0.1:${port}/text-to-midi`, {
method: 'POST', method: 'POST',
headers: { headers: {
'Content-Type': 'application/json' 'Content-Type': 'application/json'
@@ -128,7 +123,9 @@ const CompositionPanel: FC = observer(() => {
}); });
updateNs(ns); updateNs(ns);
if (autoPlay) { if (autoPlay) {
playerRef.current?.start(); setTimeout(() => {
playerRef.current?.start();
});
} }
}); });
}); });
@@ -137,7 +134,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;
@@ -145,10 +142,8 @@ const CompositionPanel: FC = observer(() => {
let answer = ''; let answer = '';
compositionSseController = new AbortController(); compositionSseController = new AbortController();
fetchEventSource( // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/completions fetchEventSource( // https://api.openai.com/v1/completions || http://127.0.0.1:${port}/v1/completions
commonStore.settings.apiUrl ? getServerRoot(port) + '/v1/completions',
commonStore.settings.apiUrl + '/v1/completions' :
`http://127.0.0.1:${port}/completions`,
{ {
method: 'POST', method: 'POST',
headers: { headers: {
@@ -178,6 +173,8 @@ const CompositionPanel: FC = observer(() => {
console.debug('json error', error); console.debug('json error', error);
return; return;
} }
if (data.model)
commonStore.setLastModelName(data.model);
if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) { if (data.choices && Array.isArray(data.choices) && data.choices.length > 0) {
answer += data.choices[0]?.text || data.choices[0]?.delta?.content || ''; answer += data.choices[0]?.text || data.choices[0]?.delta?.content || '';
setPrompt(prompt + answer.replace(/\s+$/, '')); setPrompt(prompt + answer.replace(/\s+$/, ''));
@@ -217,62 +214,96 @@ const CompositionPanel: FC = observer(() => {
setPrompt(e.target.value); setPrompt(e.target.value);
}} }}
/> />
<div className="flex flex-col gap-1 max-h-48 sm:max-w-sm sm:max-h-full overflow-x-hidden overflow-y-auto p-1"> <div className="flex flex-col gap-1 max-h-48 sm:max-w-sm sm:max-h-full">
<Labeled flex breakline label={t('Max Response Token')} <div className="flex flex-col gap-1 grow overflow-x-hidden overflow-y-auto p-1">
desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')} <Labeled flex breakline label={t('Max Response Token')}
content={ desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')}
<ValuedSlider value={params.maxResponseToken} min={100} max={4100} content={
step={100} <ValuedSlider value={params.maxResponseToken} min={100} max={4100}
input step={100}
onChange={(e, data) => { input
setParams({ onChange={(e, data) => {
maxResponseToken: data.value setParams({
}); maxResponseToken: data.value
}} /> });
} /> }} />
<Labeled flex breakline label={t('Temperature')} } />
desc={t('Sampling temperature, it\'s like giving alcohol to a model, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.')} <Labeled flex breakline label={t('Temperature')}
content={ desc={t('Sampling temperature, it\'s like giving alcohol to a model, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.')}
<ValuedSlider value={params.temperature} min={0} max={2} step={0.1} content={
input <ValuedSlider value={params.temperature} min={0} max={2} step={0.1}
onChange={(e, data) => { input
setParams({ onChange={(e, data) => {
temperature: data.value setParams({
}); temperature: data.value
}} /> });
} /> }} />
<Labeled flex breakline label={t('Top_P')} } />
desc={t('Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.')} <Labeled flex breakline label={t('Top_P')}
content={ desc={t('Just like feeding sedatives to the model. Consider the results of the top n% probability mass, 0.1 considers the top 10%, with higher quality but more conservative, 1 considers all results, with lower quality but more diverse.')}
<ValuedSlider value={params.topP} min={0} max={1} step={0.1} input content={
onChange={(e, data) => { <ValuedSlider value={params.topP} min={0} max={1} step={0.1} input
setParams({ onChange={(e, data) => {
topP: data.value setParams({
}); topP: data.value
}} /> });
} /> }} />
<div className="grow" /> } />
<Checkbox className="select-none" <div className="grow" />
size="large" label={t('Use Local Sound Font')} checked={params.useLocalSoundFont} <Checkbox className="select-none"
onChange={async (_, data) => { size="large" label={t('Use Local Sound Font')} checked={params.useLocalSoundFont}
if (data.checked) { onChange={async (_, data) => {
if (!await FileExists('assets/sound-font/accordion/instrument.json')) { if (data.checked) {
toast(t('Failed to load local sound font, please check if the files exist - assets/sound-font'), if (!await FileExists('assets/sound-font/accordion/instrument.json')) {
{ type: 'warning' }); toast(t('Failed to load local sound font, please check if the files exist - assets/sound-font'),
return; { type: 'warning' });
return;
}
} }
} setParams({
useLocalSoundFont: data.checked as boolean
});
setSoundFont();
}} />
<Checkbox className="select-none"
size="large" label={t('Auto Play At The End')} checked={params.autoPlay} onChange={(_, data) => {
setParams({ setParams({
useLocalSoundFont: data.checked as boolean autoPlay: data.checked as boolean
}); });
setSoundFont();
}} /> }} />
<Checkbox className="select-none" {commonStore.platform !== 'web' &&
size="large" label={t('Auto Play At The End')} checked={params.autoPlay} onChange={(_, data) => { <Labeled flex breakline label={t('MIDI Input')}
setParams({ desc={t('Select the MIDI input device to be used.')}
autoPlay: data.checked as boolean content={
}); <div className="flex flex-col gap-1">
}} /> <Dropdown style={{ minWidth: 0 }}
value={(commonStore.activeMidiDeviceIndex === -1 || !(commonStore.activeMidiDeviceIndex in commonStore.midiPorts))
? t('None')!
: commonStore.midiPorts[commonStore.activeMidiDeviceIndex].name}
selectedOptions={[commonStore.activeMidiDeviceIndex.toString()]}
onOptionSelect={(_, data) => {
if (data.optionValue) {
const index = Number(data.optionValue);
let action = (index === -1)
? () => CloseMidiPort()
: () => OpenMidiPort(index);
action().then(() => {
commonStore.setActiveMidiDeviceIndex(index);
}).catch((e) => {
toast(t('Error') + ' - ' + (e.message || e), { type: 'error', autoClose: 2500 });
});
}
}}>
<Option value={'-1'}>{t('None')!}</Option>
{commonStore.midiPorts.map((p, i) =>
<Option key={i} value={i.toString()}>{p.name}</Option>)
}
</Dropdown>
<AudiotrackButton setPrompt={setPrompt} />
</div>
} />
}
</div>
<div className="flex justify-between gap-2"> <div className="flex justify-between gap-2">
<ToolTipButton desc={t('Regenerate')} icon={<ArrowSync20Regular />} onClick={() => { <ToolTipButton desc={t('Regenerate')} icon={<ArrowSync20Regular />} onClick={() => {
compositionSseController?.abort(); compositionSseController?.abort();
@@ -311,7 +342,7 @@ const CompositionPanel: FC = observer(() => {
ref={playerRef} ref={playerRef}
style={{ width: '100%' }} style={{ width: '100%' }}
/> />
<Button icon={<Save28Regular />} <Button icon={<Save28Regular />} size={mq ? 'large' : 'medium'} appearance={mq ? 'secondary' : 'subtle'}
onClick={() => { onClick={() => {
if (params.midi) { if (params.midi) {
OpenSaveFileDialogBytes('*.mid', 'music.mid', Array.from(new Uint8Array(params.midi))).then((path) => { OpenSaveFileDialogBytes('*.mid', 'music.mid', Array.from(new Uint8Array(params.midi))).then((path) => {
@@ -327,7 +358,7 @@ const CompositionPanel: FC = observer(() => {
} }
}} }}
> >
{t('Save')} {mq ? t('Save') : ''}
</Button> </Button>
</div> </div>
</div> </div>
@@ -335,7 +366,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 +374,5 @@ export const Composition: FC = observer(() => {
</div> </div>
); );
}); });
export default Composition;

View File

@@ -8,12 +8,13 @@ import {
Input, Input,
Label, Label,
Option, Option,
PresenceBadge,
Select, Select,
Switch, Switch,
Text Text
} from '@fluentui/react-components'; } from '@fluentui/react-components';
import { AddCircle20Regular, DataUsageSettings20Regular, Delete20Regular, Save20Regular } from '@fluentui/react-icons'; import { AddCircle20Regular, DataUsageSettings20Regular, Delete20Regular, Save20Regular } from '@fluentui/react-icons';
import React, { FC, useEffect, useRef } from 'react'; import React, { FC, useCallback, useEffect, useRef } from 'react';
import { Section } from '../components/Section'; import { Section } from '../components/Section';
import { Labeled } from '../components/Labeled'; import { Labeled } from '../components/Labeled';
import { ToolTipButton } from '../components/ToolTipButton'; import { ToolTipButton } from '../components/ToolTipButton';
@@ -26,48 +27,40 @@ import { Page } from '../components/Page';
import { useNavigate } from 'react-router'; import { useNavigate } from 'react-router';
import { RunButton } from '../components/RunButton'; import { RunButton } from '../components/RunButton';
import { updateConfig } from '../apis'; import { updateConfig } from '../apis';
import { ConvertModel, ConvertSafetensors, FileExists, GetPyError } from '../../wailsjs/go/backend_golang/App'; import { ConvertModel, 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';
import { convertToSt } from '../utils/convert-to-st';
export type ApiParameters = { const ConfigSelector: FC<{ selectedIndex: number, updateSelectedIndex: (i: number) => void }> = observer(({ selectedIndex, updateSelectedIndex }) => {
apiPort: number return (
maxResponseToken: number; <Dropdown style={{ minWidth: 0 }} className="grow" value={commonStore.modelConfigs[selectedIndex].name}
temperature: number; selectedOptions={[selectedIndex.toString()]}
topP: number; onOptionSelect={(_, data) => {
presencePenalty: number; if (data.optionValue) {
frequencyPenalty: number; updateSelectedIndex(Number(data.optionValue));
} }
}}>
{commonStore.modelConfigs.map((config, index) => <Option key={index} value={index.toString()}
text={config.name}>
<div className="flex justify-between grow">
{config.name}
{commonStore.modelSourceList.find(item => item.name === config.modelParameters.modelName)?.isComplete
&& <PresenceBadge status="available" />}
</div>
</Option>
)}
</Dropdown>
);
});
export type Device = 'CPU' | 'CUDA' | 'CUDA-Beta' | 'WebGPU' | 'MPS' | 'Custom'; const Configs: FC = observer(() => {
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]);
@@ -82,13 +75,13 @@ export const Configs: FC = observer(() => {
(advancedHeaderRef.current.firstElementChild as HTMLElement).style.padding = '0'; (advancedHeaderRef.current.firstElementChild as HTMLElement).style.padding = '0';
}, []); }, []);
const updateSelectedIndex = (newIndex: number) => { const updateSelectedIndex = useCallback((newIndex: number) => {
setSelectedIndex(newIndex); setSelectedIndex(newIndex);
setSelectedConfig(commonStore.modelConfigs[newIndex]); setSelectedConfig(commonStore.modelConfigs[newIndex]);
// if you don't want to update the config used by the current startup in real time, comment out this line // if you don't want to update the config used by the current startup in real time, comment out this line
commonStore.setCurrentConfigIndex(newIndex); commonStore.setCurrentConfigIndex(newIndex);
}; }, []);
const setSelectedConfigName = (newName: string) => { const setSelectedConfigName = (newName: string) => {
setSelectedConfig({ ...selectedConfig, name: newName }); setSelectedConfig({ ...selectedConfig, name: newName });
@@ -128,17 +121,7 @@ export const Configs: FC = observer(() => {
<Page title={t('Configs')} content={ <Page title={t('Configs')} content={
<div className="flex flex-col gap-2 overflow-hidden"> <div className="flex flex-col gap-2 overflow-hidden">
<div className="flex gap-2 items-center"> <div className="flex gap-2 items-center">
<Dropdown style={{ minWidth: 0 }} className="grow" value={commonStore.modelConfigs[selectedIndex].name} <ConfigSelector selectedIndex={selectedIndex} updateSelectedIndex={updateSelectedIndex} />
selectedOptions={[selectedIndex.toString()]}
onOptionSelect={(_, data) => {
if (data.optionValue) {
updateSelectedIndex(Number(data.optionValue));
}
}}>
{commonStore.modelConfigs.map((config, index) =>
<Option key={index} value={index.toString()}>{config.name}</Option>
)}
</Dropdown>
<ToolTipButton desc={t('New Config')} icon={<AddCircle20Regular />} onClick={() => { <ToolTipButton desc={t('New Config')} icon={<AddCircle20Regular />} onClick={() => {
commonStore.createModelConfig(); commonStore.createModelConfig();
updateSelectedIndex(commonStore.modelConfigs.length - 1); updateSelectedIndex(commonStore.modelConfigs.length - 1);
@@ -181,7 +164,7 @@ export const Configs: FC = observer(() => {
desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')} desc={t('By default, the maximum number of tokens that can be answered in a single response, it can be changed by the user by specifying API parameters.')}
content={ content={
<ValuedSlider value={selectedConfig.apiParameters.maxResponseToken} min={100} max={8100} <ValuedSlider value={selectedConfig.apiParameters.maxResponseToken} min={100} max={8100}
step={400} step={100}
input input
onChange={(e, data) => { onChange={(e, data) => {
setSelectedConfigApiParams({ setSelectedConfigApiParams({
@@ -303,41 +286,7 @@ export const Configs: FC = observer(() => {
}} /> : }} /> :
<ToolTipButton text={t('Convert To Safe Tensors Format')} <ToolTipButton text={t('Convert To Safe Tensors Format')}
desc="" desc=""
onClick={async () => { onClick={() => convertToSt(selectedConfig)} />
if (commonStore.platform === 'darwin') {
toast(t('MacOS is not yet supported for performing this operation, please do it manually.') + ' (backend-python/convert_safetensors.py)', { type: 'info' });
return;
} else if (commonStore.platform === 'linux') {
toast(t('Linux is not yet supported for performing this operation, please do it manually.') + ' (backend-python/convert_safetensors.py)', { type: 'info' });
return;
}
const ok = await checkDependencies(navigate);
if (!ok)
return;
const modelPath = `${commonStore.settings.customModelsPath}/${selectedConfig.modelParameters.modelName}`;
if (await FileExists(modelPath)) {
toast(t('Start Converting'), { autoClose: 1000, type: 'info' });
const newModelPath = modelPath.replace(/\.pth$/, '.st');
ConvertSafetensors(commonStore.settings.customPythonPath, modelPath, newModelPath).then(async () => {
if (!await FileExists(newModelPath)) {
toast(t('Convert Failed') + ' - ' + await GetPyError(), { type: 'error' });
} else {
toast(`${t('Convert Success')} - ${newModelPath}`, { type: 'success' });
}
}).catch(e => {
const errMsg = e.message || e;
if (errMsg.includes('path contains space'))
toast(`${t('Convert Failed')} - ${t('File Path Cannot Contain Space')}`, { type: 'error' });
else
toast(`${t('Convert Failed')} - ${e.message || e}`, { type: 'error' });
});
setTimeout(WindowShow, 1000);
} else {
toast(`${t('Model Not Found')} - ${modelPath}`, { type: 'error' });
}
}} />
} }
<Labeled label={t('Strategy')} content={ <Labeled label={t('Strategy')} content={
<Dropdown style={{ minWidth: 0 }} className="grow" value={t(selectedConfig.modelParameters.device)!} <Dropdown style={{ minWidth: 0 }} className="grow" value={t(selectedConfig.modelParameters.device)!}
@@ -359,7 +308,7 @@ export const Configs: FC = observer(() => {
} /> } />
{ {
selectedConfig.modelParameters.device !== 'Custom' && <Labeled label={t('Precision')} selectedConfig.modelParameters.device !== 'Custom' && <Labeled label={t('Precision')}
desc={t('int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality, and fp32 has the best quality.')} desc={t('int8 uses less VRAM, but has slightly lower quality. fp16 has higher quality.')}
content={ content={
<Dropdown style={{ minWidth: 0 }} className="grow" <Dropdown style={{ minWidth: 0 }} className="grow"
value={selectedConfig.modelParameters.precision} value={selectedConfig.modelParameters.precision}
@@ -371,8 +320,10 @@ export const Configs: FC = observer(() => {
}); });
} }
}}> }}>
<Option>fp16</Option> {selectedConfig.modelParameters.device !== 'CPU' && selectedConfig.modelParameters.device !== 'MPS' &&
<Option>fp16</Option>}
<Option>int8</Option> <Option>int8</Option>
{selectedConfig.modelParameters.device === 'WebGPU' && <Option>nf4</Option>}
{selectedConfig.modelParameters.device !== 'WebGPU' && <Option>fp32</Option>} {selectedConfig.modelParameters.device !== 'WebGPU' && <Option>fp32</Option>}
</Dropdown> </Dropdown>
} /> } />
@@ -454,7 +405,7 @@ export const Configs: FC = observer(() => {
}); });
}} /> }} />
<Input className="grow" <Input className="grow"
placeholder={t('Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json)')!} placeholder={t('Tokenizer Path (e.g. backend-python/rwkv_pip/20B_tokenizer.json or rwkv_vocab_v20230424.txt)')!}
value={selectedConfig.modelParameters.customTokenizer} value={selectedConfig.modelParameters.customTokenizer}
onChange={(e, data) => { onChange={(e, data) => {
setSelectedConfigModelParams({ setSelectedConfigModelParams({
@@ -472,9 +423,23 @@ export const Configs: FC = observer(() => {
/> />
</div> </div>
<div className="flex flex-row-reverse sm:fixed bottom-2 right-2"> <div className="flex flex-row-reverse sm:fixed bottom-2 right-2">
<RunButton onClickRun={onClickSave} /> <div className="flex gap-2">
{selectedConfig.modelParameters.device !== 'WebGPU'
&& <Checkbox className="select-none"
size="large" label={t('Enable WebUI')}
checked={selectedConfig.enableWebUI}
onChange={(_, data) => {
setSelectedConfig({
...selectedConfig,
enableWebUI: data.checked as boolean
});
}} />}
<RunButton onClickRun={onClickSave} />
</div>
</div> </div>
</div> </div>
} /> } />
); );
}); });
export default Configs;

View File

@@ -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;

View File

@@ -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,39 +87,64 @@ export const Home: FC = observer(() => {
navigate({ pathname: path }); navigate({ pathname: path });
}; };
return ( return commonStore.platform === 'web' ?
<div className="flex flex-col justify-between h-full"> (
<img className="rounded-xl select-none hidden sm:block" <div className="flex flex-col gap-2 h-full overflow-x-hidden overflow-y-auto">
style={{ maxHeight: '40%', margin: '0 auto' }} src={banner} /> <img className="rounded-xl select-none object-cover grow"
<div className="flex flex-col gap-2"> style={{ maxHeight: '40%' }} src={banner} />
<Text size={600} weight="medium">{t('Introduction')}</Text> <div className="grow"></div>
<div className="h-40 overflow-y-auto overflow-x-hidden p-1"> <div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
<MarkdownRender> {webNavCards.map(({ label, path, icon, desc }, index) => (
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']} <CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
</MarkdownRender> size="large" onClick={() => onClickNavCard(path)}>
{t(label)}
</CompoundButton>
))}
</div> </div>
</div> <div className="flex flex-col gap-2">
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5"> <AdvancedGeneralSettings />
{navCards.map(({ label, path, icon, desc }, index) => ( <div className="flex gap-4 items-end">
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path} {t('Version')}: {manifest.version}
size="large" onClick={() => onClickNavCard(path)}> <Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link>
{t(label)}
</CompoundButton>
))}
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-row-reverse sm:fixed bottom-2 right-2">
<div className="flex gap-3">
<ResetConfigsButton />
<ConfigSelector />
<RunButton />
</div> </div>
</div> </div>
<div className="flex gap-4 items-end"> </div>
{t('Version')}: {manifest.version} )
<Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link> : (
<div className="flex flex-col justify-between h-full">
<img className="rounded-xl select-none object-cover hidden sm:block"
style={{ maxHeight: '40%' }} src={banner} />
<div className="flex flex-col gap-2">
<Text size={600} weight="medium">{t('Introduction')}</Text>
<div className="h-40 overflow-y-auto overflow-x-hidden p-1">
<LazyImportComponent lazyChildren={MarkdownRender}>
{lang in commonStore.introduction ? commonStore.introduction[lang] : commonStore.introduction['en']}
</LazyImportComponent>
</div>
</div>
<div className="grid grid-cols-2 sm:grid-cols-4 gap-5">
{clientNavCards.map(({ label, path, icon, desc }, index) => (
<CompoundButton icon={icon} secondaryContent={t(desc)} key={`${path}-${index}`} value={path}
size="large" onClick={() => onClickNavCard(path)}>
{t(label)}
</CompoundButton>
))}
</div>
<div className="flex flex-col gap-2">
<div className="flex flex-row-reverse sm:fixed bottom-2 right-2">
<div className="flex gap-3">
<ResetConfigsButton />
<ConfigSelector />
<RunButton />
</div>
</div>
<div className="flex gap-4 items-end">
{t('Version')}: {manifest.version}
<Link onClick={() => BrowserOpenURL('https://github.com/josStorer/RWKV-Runner')}>{t('Help')}</Link>
</div>
</div> </div>
</div> </div>
</div> );
);
}); });
export default Home;

View File

@@ -1,5 +1,7 @@
import React, { FC } from 'react'; import React, { FC, useEffect, useState } from 'react';
import { import {
Button,
Checkbox,
createTableColumn, createTableColumn,
DataGrid, DataGrid,
DataGridBody, DataGridBody,
@@ -19,24 +21,10 @@ import commonStore from '../stores/commonStore';
import { BrowserOpenURL } from '../../wailsjs/runtime'; import { BrowserOpenURL } from '../../wailsjs/runtime';
import { AddToDownloadList, OpenFileFolder } from '../../wailsjs/go/backend_golang/App'; import { AddToDownloadList, OpenFileFolder } from '../../wailsjs/go/backend_golang/App';
import { Page } from '../components/Page'; import { Page } from '../components/Page';
import { bytesToGb, refreshModels, saveConfigs, toastWithButton } from '../utils'; import { bytesToGb, getHfDownloadUrl, 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>({
@@ -153,7 +141,7 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
navigate({ pathname: '/downloads' }); navigate({ pathname: '/downloads' });
}, },
{ autoClose: 3000 }); { autoClose: 3000 });
AddToDownloadList(`${commonStore.settings.customModelsPath}/${item.name}`, item.downloadUrl!); AddToDownloadList(`${commonStore.settings.customModelsPath}/${item.name}`, getHfDownloadUrl(item.downloadUrl!));
}} />} }} />}
{item.url && <ToolTipButton desc={t('Open Url')} icon={<Open20Regular />} onClick={() => { {item.url && <ToolTipButton desc={t('Open Url')} icon={<Open20Regular />} onClick={() => {
BrowserOpenURL(item.url!); BrowserOpenURL(item.url!);
@@ -165,8 +153,24 @@ const columns: TableColumnDefinition<ModelSourceItem>[] = [
}) })
]; ];
export const Models: FC = observer(() => { const Models: FC = observer(() => {
const { t } = useTranslation(); const { t } = useTranslation();
const [tags, setTags] = useState<Array<string>>([]);
const [modelSourceList, setModelSourceList] = useState<ModelSourceItem[]>(commonStore.modelSourceList);
useEffect(() => {
setTags(Array.from(new Set(
[...commonStore.modelSourceList.map(item => item.tags || []).flat()
.filter(i => !i.includes('Other') && !i.includes('Local'))
, 'Other', 'Local'])));
}, [commonStore.modelSourceList]);
useEffect(() => {
if (commonStore.activeModelListTags.length === 0)
setModelSourceList(commonStore.modelSourceList);
else
setModelSourceList(commonStore.modelSourceList.filter(item => commonStore.activeModelListTags.some(tag => item.tags?.includes(tag))));
}, [commonStore.modelSourceList, commonStore.activeModelListTags]);
return ( return (
<Page title={t('Models')} content={ <Page title={t('Models')} content={
@@ -174,10 +178,21 @@ export const Models: FC = observer(() => {
<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">
<Text weight="medium">{t('Model Source Manifest List')}</Text> <Text weight="medium">{t('Model Source Manifest List')}</Text>
<ToolTipButton desc={t('Refresh')} icon={<ArrowClockwise20Regular />} onClick={() => { <div className="flex">
refreshModels(false); {commonStore.settings.language === 'zh' &&
saveConfigs(); <Checkbox className="select-none"
}} /> size="large" label={t('Use Hugging Face Mirror')}
checked={commonStore.settings.useHfMirror}
onChange={(_, data) => {
commonStore.setSettings({
useHfMirror: data.checked as boolean
});
}} />}
<ToolTipButton desc={t('Refresh')} icon={<ArrowClockwise20Regular />} onClick={() => {
refreshModels(false);
saveConfigs();
}} />
</div>
</div> </div>
<Text size={100}> <Text size={100}>
{t('Provide JSON file URLs for the models manifest. Separate URLs with semicolons. The "models" field in JSON files will be parsed into the following table.')} {t('Provide JSON file URLs for the models manifest. Separate URLs with semicolons. The "models" field in JSON files will be parsed into the following table.')}
@@ -186,9 +201,24 @@ export const Models: FC = observer(() => {
value={commonStore.modelSourceManifestList} value={commonStore.modelSourceManifestList}
onChange={(e, data) => commonStore.setModelSourceManifestList(data.value)} /> onChange={(e, data) => commonStore.setModelSourceManifestList(data.value)} />
</div> </div>
<div className="flex gap-2 flex-wrap overflow-y-auto" style={{ minHeight: '88px' }}>
{tags.map(tag =>
<div key={tag} className="mt-auto">
<Button
appearance={commonStore.activeModelListTags.includes(tag) ? 'primary' : 'secondary'} onClick={
() => {
if (commonStore.activeModelListTags.includes(tag))
commonStore.setActiveModelListTags(commonStore.activeModelListTags.filter(t => t !== tag));
else
commonStore.setActiveModelListTags([...commonStore.activeModelListTags, tag]);
}
}>{t(tag)}</Button>
</div>)
}
</div>
<div className="flex grow overflow-hidden"> <div className="flex grow overflow-hidden">
<DataGrid <DataGrid
items={commonStore.modelSourceList} items={modelSourceList}
columns={columns} columns={columns}
sortable={true} sortable={true}
defaultSortState={{ sortColumn: 'actions', sortDirection: 'ascending' }} defaultSortState={{ sortColumn: 'actions', sortDirection: 'ascending' }}
@@ -220,3 +250,5 @@ export const Models: FC = observer(() => {
} /> } />
); );
}); });
export default Models;

View File

@@ -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!;
@@ -108,7 +108,8 @@ export const MessagesEditor: FC = observer(() => {
style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}> style={{ borderTopRightRadius: 0, borderBottomRightRadius: 0 }}>
<ReOrderDotsVertical20Regular /> <ReOrderDotsVertical20Regular />
</Card> </Card>
<Dropdown style={{ minWidth: 0, borderRadius: 0 }} listbox={{ style: { minWidth: 0 } }} <Dropdown style={{ minWidth: 0, borderRadius: 0 }}
listbox={{ style: { minWidth: 'fit-content' } }}
value={t(item.role)!} value={t(item.role)!}
selectedOptions={[item.role]} selectedOptions={[item.role]}
onOptionSelect={(_, data) => { onOptionSelect={(_, data) => {
@@ -152,3 +153,5 @@ export const MessagesEditor: FC = observer(() => {
</div> </div>
); );
}); });
export default MessagesEditor;

View File

@@ -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,19 @@ 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 { absPathAsset, setActivePreset } from '../../utils';
import { absPathAsset } from '../../utils'; import { Preset, PresetsNavigationItem } from '../../types/presets';
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: '',
@@ -75,33 +50,17 @@ export const defaultPreset: Preset = {
prompt: '', prompt: '',
stop: '', stop: '',
injectStart: '', injectStart: '',
injectEnd: '' injectEnd: '',
presystem: true,
userName: '',
assistantName: ''
}; };
const setActivePreset = (preset: Preset) => { const MessagesEditor = lazy(() => import('./MessagesEditor'));
commonStore.setActivePreset(preset);
//TODO if (preset.displayPresetMessages) {
const conversation: Conversation = {};
const conversationOrder: string[] = [];
for (const message of preset.messages) {
const newUuid = uuid();
conversationOrder.push(newUuid);
conversation[newUuid] = {
sender: message.role === 'user' ? userName : botName,
type: MessageType.Normal,
color: message.role === 'user' ? 'brand' : 'colorful',
time: new Date().toISOString(),
content: message.content,
side: message.role === 'user' ? 'right' : 'left',
done: true
};
}
commonStore.setConversation(conversation);
commonStore.setConversationOrder(conversationOrder);
//}
};
export const PresetCardFrame: FC<PropsWithChildren & { onClick?: () => void }> = (props) => { const PresetCardFrame: FC<PropsWithChildren & {
onClick?: React.MouseEventHandler<HTMLButtonElement>
}> = (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 +70,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,
@@ -124,7 +83,10 @@ export const PresetCard: FC<{
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
return <PresetCardFrame onClick={onClick}> return <PresetCardFrame onClick={(e) => {
if (onClick && e.currentTarget.contains(e.target as Node))
onClick();
}}>
<img src={absPathAsset(avatarImg)} className="rounded-xl select-none ml-auto mr-auto h-28" /> <img src={absPathAsset(avatarImg)} className="rounded-xl select-none ml-auto mr-auto h-28" />
<Text size={400}>{name}</Text> <Text size={400}>{name}</Text>
<Text size={200} style={{ <Text size={200} style={{
@@ -137,7 +99,8 @@ export const PresetCard: FC<{
{editable ? {editable ?
<ChatPresetEditor presetIndex={presetIndex} triggerButton={ <ChatPresetEditor presetIndex={presetIndex} triggerButton={
<ToolTipButton size="small" appearance="transparent" desc={t('Edit')} icon={<Edit20Regular />} <ToolTipButton size="small" appearance="transparent" desc={t('Edit')} icon={<Edit20Regular />}
onClick={() => { onClick={(e) => {
e.stopPropagation();
commonStore.setEditingPreset({ ...commonStore.presets[presetIndex] }); commonStore.setEditingPreset({ ...commonStore.presets[presetIndex] });
}} /> }} />
} /> } />
@@ -147,7 +110,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 +254,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 +319,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,12 +355,9 @@ export const ChatPresets: FC = observer(() => {
</div>; </div>;
}); });
type PresetsNavigationItem = { const pages: {
icon: ReactElement; [label: string]: PresetsNavigationItem
element: ReactElement; } = {
};
const pages: { [label: string]: PresetsNavigationItem } = {
Chat: { Chat: {
icon: <Chat20Regular />, icon: <Chat20Regular />,
element: <ChatPresets /> element: <ChatPresets />
@@ -412,7 +372,9 @@ 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);

View File

@@ -16,33 +16,181 @@ 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 const { t } = useTranslation();
zh: '简体中文',
ja: '日本語'
};
export type Language = keyof typeof Languages; return <div className="flex flex-col gap-2">
<Labeled label={t('Language')} flex spaceBetween content={
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 'fit-content' } }}
value={Languages[commonStore.settings.language]}
selectedOptions={[commonStore.settings.language]}
onOptionSelect={(_, data) => {
if (data.optionValue) {
const lang = data.optionValue as Language;
commonStore.setSettings({
language: lang
});
}
}}>
{
Object.entries(Languages).map(([langKey, desc]) =>
<Option key={langKey} value={langKey}>{desc}</Option>)
}
</Dropdown>
} />
{
commonStore.platform === 'windows' &&
<Labeled label={t('DPI Scaling')} flex spaceBetween content={
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 'fit-content' } }}
value={commonStore.settings.dpiScaling + '%'}
selectedOptions={[commonStore.settings.dpiScaling.toString()]}
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
dpiScaling: Number(data.optionValue)
});
toastWithButton(t('Restart the app to apply DPI Scaling.'), t('Restart'), () => {
RestartApp();
}, {
autoClose: 5000
});
}
}}>
{
Array.from({ length: 7 }, (_, i) => (i + 2) * 25).map((v, i) =>
<Option key={i} value={v.toString()}>{v + '%'}</Option>)
}
</Dropdown>
} />
}
<Labeled label={t('Dark Mode')} flex spaceBetween content={
<Switch checked={commonStore.settings.darkMode}
onChange={(e, data) => {
commonStore.setSettings({
darkMode: data.checked
});
}} />
} />
</div>;
});
export type SettingsType = { export const AdvancedGeneralSettings: FC = observer(() => {
language: Language const { t } = useTranslation();
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(() => { return <div className="flex flex-col gap-2">
const { t, i18n } = useTranslation(); <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: 'fit-content' } }}
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: 'gpt-3.5-turbo-instruct'
});
} else if (data.optionText === 'RWKV') {
if (commonStore.settings.apiChatModelName === 'gpt-3.5-turbo')
commonStore.setSettings({
apiChatModelName: 'rwkv'
});
if (commonStore.settings.apiCompletionModelName === 'gpt-3.5-turbo-instruct' || commonStore.settings.apiCompletionModelName === 'text-davinci-003')
commonStore.setSettings({
apiCompletionModelName: 'rwkv'
});
}
}}>
<Option value="">{t('Localhost')!}</Option>
<Option value="https://rwkv.ai-creator.net/chntuned">RWKV</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: 'fit-content' } }}
value="..." selectedOptions={[]} expandIcon={null}
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
apiChatModelName: data.optionValue
});
}
}}>
{
['rwkv', 'gpt-4-1106-preview', 'gpt-4', 'gpt-4-32k', 'gpt-4-0613', 'gpt-4-32k-0613', 'gpt-3.5-turbo-1106', 'gpt-3.5-turbo', 'gpt-3.5-turbo-16k']
.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: 'fit-content', minHeight: 0 } }}
value="..." selectedOptions={[]} expandIcon={null}
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
apiCompletionModelName: data.optionValue
});
}
}}>
{
['rwkv', 'gpt-3.5-turbo-instruct', 'text-davinci-003', 'text-davinci-002', 'code-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); const advancedHeaderRef = useRef<HTMLDivElement>(null);
useEffect(() => { useEffect(() => {
@@ -53,227 +201,101 @@ export const Settings: FC = observer(() => {
return ( return (
<Page title={t('Settings')} content={ <Page title={t('Settings')} content={
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1"> <div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1">
<Labeled label={t('Language')} flex spaceBetween content={
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
value={Languages[commonStore.settings.language]}
selectedOptions={[commonStore.settings.language]}
onOptionSelect={(_, data) => {
if (data.optionValue) {
const lang = data.optionValue as Language;
commonStore.setSettings({
language: lang
});
}
}}>
{
Object.entries(Languages).map(([langKey, desc]) =>
<Option key={langKey} value={langKey}>{desc}</Option>)
}
</Dropdown>
} />
{ {
commonStore.platform === 'windows' && commonStore.platform === 'web' ?
<Labeled label={t('DPI Scaling')} flex spaceBetween content={ (
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }} <div className="flex flex-col gap-2">
value={commonStore.settings.dpiScaling + '%'} <GeneralSettings />
selectedOptions={[commonStore.settings.dpiScaling.toString()]} <AdvancedGeneralSettings />
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
dpiScaling: Number(data.optionValue)
});
toastWithButton(t('Restart the app to apply DPI Scaling.'), t('Restart'), () => {
RestartApp();
}, {
autoClose: 5000
});
}
}}>
{
Array.from({ length: 7 }, (_, i) => (i + 2) * 25).map((v, i) =>
<Option key={i} value={v.toString()}>{v + '%'}</Option>)
}
</Dropdown>
} />
}
<Labeled label={t('Dark Mode')} flex spaceBetween content={
<Switch checked={commonStore.settings.darkMode}
onChange={(e, data) => {
commonStore.setSettings({
darkMode: data.checked
});
}} />
} />
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
<Switch checked={commonStore.settings.autoUpdatesCheck}
onChange={(e, data) => {
commonStore.setSettings({
autoUpdatesCheck: data.checked
});
if (data.checked)
checkUpdate(true);
}} />
} />
{
commonStore.settings.language === 'zh' &&
<Labeled label={t('Use Gitee Updates Source')} flex spaceBetween content={
<Switch checked={commonStore.settings.giteeUpdatesSource}
onChange={(e, data) => {
commonStore.setSettings({
giteeUpdatesSource: data.checked
});
}} />
} />
}
{
commonStore.settings.language === 'zh' && commonStore.platform !== 'linux' &&
<Labeled label={t('Use Tsinghua Pip Mirrors')} flex spaceBetween content={
<Switch checked={commonStore.settings.cnMirror}
onChange={(e, data) => {
commonStore.setSettings({
cnMirror: data.checked
});
}} />
} />
}
<Labeled label={t('Allow external access to the API (service must be restarted)')} flex spaceBetween content={
<Switch checked={commonStore.settings.host !== '127.0.0.1'}
onChange={(e, data) => {
commonStore.setSettings({
host: data.checked ? '0.0.0.0' : '127.0.0.1'
});
}} />
} />
<Accordion collapsible openItems={!commonStore.advancedCollapsed && 'advanced'} onToggle={(e, data) => {
if (data.value === 'advanced')
commonStore.setAdvancedCollapsed(!commonStore.advancedCollapsed);
}}>
<AccordionItem value="advanced">
<AccordionHeader ref={advancedHeaderRef} size="large">{t('Advanced')}</AccordionHeader>
<AccordionPanel>
<div className="flex flex-col gap-2 overflow-hidden">
{commonStore.platform !== 'darwin' &&
<Labeled label={t('Custom Models Path')}
content={
<Input className="grow" placeholder="./models" value={commonStore.settings.customModelsPath}
onChange={(e, data) => {
commonStore.setSettings({
customModelsPath: data.value
});
}} />
} />
}
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
content={
<Input className="grow" placeholder="./py310/python" value={commonStore.settings.customPythonPath}
onChange={(e, data) => {
commonStore.setDepComplete(false);
commonStore.setSettings({
customPythonPath: data.value
});
}} />
} />
<Labeled label={'API URL'}
content={
<div className="flex gap-2">
<Input style={{ minWidth: 0 }} className="grow" value={commonStore.settings.apiUrl}
onChange={(e, data) => {
commonStore.setSettings({
apiUrl: data.value
});
}} />
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
value="..." selectedOptions={[]} expandIcon={null}
onOptionSelect={(_, data) => {
commonStore.setSettings({
apiUrl: data.optionValue
});
if (data.optionText === 'OpenAI') {
if (commonStore.settings.apiChatModelName === 'rwkv')
commonStore.setSettings({
apiChatModelName: 'gpt-3.5-turbo'
});
if (commonStore.settings.apiCompletionModelName === 'rwkv')
commonStore.setSettings({
apiCompletionModelName: 'text-davinci-003'
});
}
}}>
<Option value="">{t('Localhost')!}</Option>
<Option value="https://api.openai.com">OpenAI</Option>
</Dropdown>
</div>
} />
<Labeled label={'API Key'}
content={
<Input className="grow" placeholder="sk-" value={commonStore.settings.apiKey}
onChange={(e, data) => {
commonStore.setSettings({
apiKey: data.value
});
}} />
} />
<Labeled label={t('API Chat Model Name')}
content={
<div className="flex gap-2">
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
value={commonStore.settings.apiChatModelName}
onChange={(e, data) => {
commonStore.setSettings({
apiChatModelName: data.value
});
}} />
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
value="..." selectedOptions={[]} expandIcon={null}
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
apiChatModelName: data.optionValue
});
}
}}>
{
['rwkv', 'gpt-4', 'gpt-4-0613', 'gpt-4-32k', 'gpt-4-32k-0613', 'gpt-3.5-turbo', 'gpt-3.5-turbo-0613', 'gpt-3.5-turbo-16k', 'gpt-3.5-turbo-16k-0613']
.map((v, i) =>
<Option key={i} value={v}>{v}</Option>
)
}
</Dropdown>
</div>
} />
<Labeled label={t('API Completion Model Name')}
content={
<div className="flex gap-2">
<Input style={{ minWidth: 0 }} className="grow" placeholder="rwkv"
value={commonStore.settings.apiCompletionModelName}
onChange={(e, data) => {
commonStore.setSettings({
apiCompletionModelName: data.value
});
}} />
<Dropdown style={{ minWidth: 0 }} listbox={{ style: { minWidth: 0 } }}
value="..." selectedOptions={[]} expandIcon={null}
onOptionSelect={(_, data) => {
if (data.optionValue) {
commonStore.setSettings({
apiCompletionModelName: data.optionValue
});
}
}}>
{
['rwkv', 'text-davinci-003', 'text-davinci-002', 'text-curie-001', 'text-babbage-001', 'text-ada-001']
.map((v, i) =>
<Option key={i} value={v}>{v}</Option>
)
}
</Dropdown>
</div>
} />
</div> </div>
</AccordionPanel> )
</AccordionItem> :
</Accordion> (
<div className="flex flex-col gap-2">
<GeneralSettings />
<Labeled label={t('Automatic Updates Check')} flex spaceBetween content={
<Switch checked={commonStore.settings.autoUpdatesCheck}
onChange={(e, data) => {
commonStore.setSettings({
autoUpdatesCheck: data.checked
});
if (data.checked)
checkUpdate(true);
}} />
} />
{
commonStore.settings.language === 'zh' &&
<Labeled label={t('Use Gitee Updates Source')} flex spaceBetween content={
<Switch checked={commonStore.settings.giteeUpdatesSource}
onChange={(e, data) => {
commonStore.setSettings({
giteeUpdatesSource: data.checked
});
}} />
} />
}
{
commonStore.settings.language === 'zh' && commonStore.platform !== 'linux' &&
<Labeled label={t('Use Tsinghua Pip Mirrors')} flex spaceBetween content={
<Switch checked={commonStore.settings.cnMirror}
onChange={(e, data) => {
commonStore.setSettings({
cnMirror: data.checked
});
}} />
} />
}
<Labeled label={t('Allow external access to the API (service must be restarted)')} flex spaceBetween
content={
<Switch checked={commonStore.settings.host !== '127.0.0.1'}
onChange={(e, data) => {
commonStore.setSettings({
host: data.checked ? '0.0.0.0' : '127.0.0.1'
});
}} />
} />
<Accordion collapsible openItems={!commonStore.advancedCollapsed && 'advanced'} onToggle={(e, data) => {
if (data.value === 'advanced')
commonStore.setAdvancedCollapsed(!commonStore.advancedCollapsed);
}}>
<AccordionItem value="advanced">
<AccordionHeader ref={advancedHeaderRef} size="large">{t('Advanced')}</AccordionHeader>
<AccordionPanel>
<div className="flex flex-col gap-2 overflow-hidden">
{commonStore.platform !== 'darwin' &&
<Labeled label={t('Custom Models Path')}
content={
<Input className="grow" placeholder="./models"
value={commonStore.settings.customModelsPath}
onChange={(e, data) => {
commonStore.setSettings({
customModelsPath: data.value
});
}} />
} />
}
<Labeled label={t('Custom Python Path')} // if set, will not use precompiled cuda kernel
content={
<Input className="grow" placeholder="./py310/python"
value={commonStore.settings.customPythonPath}
onChange={(e, data) => {
commonStore.setDepComplete(false);
commonStore.setSettings({
customPythonPath: data.value
});
}} />
} />
<AdvancedGeneralSettings />
</div>
</AccordionPanel>
</AccordionItem>
</Accordion>
</div>
)
}
</div> </div>
} /> } />
); );
}); });
export default Settings;

View File

@@ -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,
@@ -61,6 +66,11 @@ const parseLossData = (data: string) => {
const loss = parseFloat(lastMatch[8]); const loss = parseFloat(lastMatch[8]);
commonStore.setChartTitle(`Epoch ${epoch}: ${lastMatch[2]} - ${lastMatch[3]}/${lastMatch[4]} - ${lastMatch[5]}/${lastMatch[6]} - ${lastMatch[7]} Loss=${loss}`); commonStore.setChartTitle(`Epoch ${epoch}: ${lastMatch[2]} - ${lastMatch[3]}/${lastMatch[4]} - ${lastMatch[5]}/${lastMatch[6]} - ${lastMatch[7]} Loss=${loss}`);
addLossDataToChart(epoch, loss); addLossDataToChart(epoch, loss);
if (loss > 5)
toast(t('Loss is too high, please check the training data, and ensure your gpu driver is up to date.'), {
type: 'warning',
toastId: 'train_loss_high'
});
return true; return true;
}; };
@@ -86,39 +96,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'],
@@ -157,11 +134,13 @@ const errorsMap = Object.entries({
'python3 ./finetune/lora/train.py': 'Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.', 'python3 ./finetune/lora/train.py': 'Memory is not enough, try to increase the virtual memory (Swap of WSL) or use a smaller base model.',
'cuda out of memory': 'VRAM is not enough', 'cuda out of memory': 'VRAM is not enough',
'valueerror: high <= 0': 'Training data is not enough, reduce context length or add more data for training', 'valueerror: high <= 0': 'Training data is not enough, reduce context length or add more data for training',
'+= \'+ptx\'': 'You are using WSL 1 for training, please upgrade to WSL 2. e.g. Run "wsl --set-version Ubuntu-22.04 2"', '+= \'+ptx\'': 'Can not find an Nvidia GPU. Perhaps the gpu driver of windows is too old, or you are using WSL 1 for training, please upgrade to WSL 2. e.g. Run "wsl --set-version Ubuntu-22.04 2"',
'size mismatch for blocks': 'Size mismatch for blocks. You are attempting to continue training from the LoRA model, but it does not match the base model. Please set LoRA model to None.', 'size mismatch for blocks': 'Size mismatch for blocks. You are attempting to continue training from the LoRA model, but it does not match the base model. Please set LoRA model to None.',
'cuda_home environment variable is not set': 'Matched CUDA is not installed', 'cuda_home environment variable is not set': 'Matched CUDA is not installed',
'unsupported gpu architecture': 'Matched CUDA is not installed', 'unsupported gpu architecture': 'Matched CUDA is not installed',
'error building extension \'fused_adam\'': 'Matched CUDA is not installed' 'error building extension \'fused_adam\'': 'Matched CUDA is not installed',
'rwkv{version} is not supported': 'This version of RWKV is not supported yet.',
'modelinfo is invalid': 'Failed to load model, try to increase the virtual memory (Swap of WSL) or use a smaller base model.'
}); });
export const wslHandler = (data: string) => { export const wslHandler = (data: string) => {
@@ -491,11 +470,12 @@ const LoraFinetune: FC = observer(() => {
return; return;
if (loraParams.loraLoad) { if (loraParams.loraLoad) {
const outputPath = `models/${loraParams.baseModel}-LoRA-${loraParams.loraLoad}`; const outputPath = `models/${loraParams.baseModel}-LoRA-${loraParams.loraLoad}`;
MergeLora(commonStore.settings.customPythonPath, true, loraParams.loraAlpha, MergeLora(commonStore.settings.customPythonPath, !!commonStore.monitorData && commonStore.monitorData.totalVram !== 0, loraParams.loraAlpha,
'models/' + loraParams.baseModel, 'lora-models/' + loraParams.loraLoad, 'models/' + loraParams.baseModel, 'lora-models/' + loraParams.loraLoad,
outputPath).then(async () => { outputPath).then(async () => {
if (!await FileExists(outputPath)) { if (!await FileExists(outputPath)) {
toast(t('Failed to merge model') + ' - ' + await GetPyError(), { type: 'error' }); if (commonStore.platform === 'windows' || commonStore.platform === 'linux')
toast(t('Failed to merge model') + ' - ' + await GetPyError(), { type: 'error' });
} else { } else {
toast(t('Merge model successfully'), { type: 'success' }); toast(t('Merge model successfully'), { type: 'success' });
} }
@@ -568,10 +548,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 +558,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 +583,5 @@ export const Train: FC = () => {
</div> </div>
</div>; </div>;
}; };
export default Train;

File diff suppressed because it is too large Load Diff

View File

@@ -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
} }
]; ];

View File

@@ -1,43 +1,49 @@
import commonStore, { Platform } from './stores/commonStore'; import commonStore, { MonitorData, Platform } from './stores/commonStore';
import { GetPlatform, ListDirFiles, ReadJson } from '../wailsjs/go/backend_golang/App'; import { FileExists, GetPlatform, ListDirFiles, ReadJson } from '../wailsjs/go/backend_golang/App';
import { Cache, checkUpdate, downloadProgramFiles, LocalConfig, refreshLocalModels, refreshModels } from './utils'; import { Cache, checkUpdate, downloadProgramFiles, LocalConfig, refreshLocalModels, refreshModels } from './utils';
import { getStatus } from './apis'; 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';
import { toast } from 'react-toastify';
import { MidiMessage, MidiPort } from './types/composition';
export async function startup() { export async function startup() {
downloadProgramFiles();
EventsOn('downloadList', (data) => {
if (data)
commonStore.setDownloadList(data);
});
EventsOn('wsl', wslHandler);
EventsOn('wslerr', (e) => {
console.log(e);
});
initLocalModelsNotify();
initLoraModels();
initPresets(); initPresets();
initHardwareMonitor();
await GetPlatform().then(p => commonStore.setPlatform(p as Platform)); await GetPlatform().then(p => commonStore.setPlatform(p as Platform));
if (commonStore.platform !== 'web') {
downloadProgramFiles();
EventsOn('downloadList', (data) => {
if (data)
commonStore.setDownloadList(data);
});
EventsOn('wsl', (await import('./pages/Train')).wslHandler);
EventsOn('wslerr', (e) => {
console.log(e);
});
initLocalModelsNotify();
initLoraModels();
initHardwareMonitor();
initMidi();
}
await initConfig(); await initConfig();
initCache(true).then(initRemoteText); // depends on config customModelsPath if (commonStore.platform !== 'web') {
initCache(true).then(initRemoteText); // depends on config customModelsPath
if (commonStore.settings.autoUpdatesCheck) // depends on config settings if (commonStore.settings.autoUpdatesCheck) // depends on config settings
checkUpdate(); checkUpdate();
getStatus(1000).then(status => { // depends on config api port getStatus(1000).then(status => { // depends on config api port
if (status) if (status)
commonStore.setStatus(status); commonStore.setStatus(status);
}); });
}
} }
async function initRemoteText() { async function initRemoteText() {
@@ -88,7 +94,8 @@ 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[]) => {
commonStore.setPresets(presets, false); if (Array.isArray(presets))
commonStore.setPresets(presets, false);
}).catch(() => { }).catch(() => {
}); });
} }
@@ -121,19 +128,31 @@ async function initLocalModelsNotify() {
}); });
} }
type monitorData = {
usedMemory: number;
totalMemory: number;
gpuUsage: number;
gpuPower: number;
usedVram: number;
totalVram: number;
}
async function initHardwareMonitor() { async function initHardwareMonitor() {
EventsOn('monitor', (data: string) => { EventsOn('monitor', (data: string) => {
const results: monitorData = JSON.parse(data); const results: MonitorData = JSON.parse(data);
if (results) if (results) {
commonStore.setMonitorData(results);
WindowSetTitle(`RWKV-Runner (${t('RAM')}: ${results.usedMemory.toFixed(1)}/${results.totalMemory.toFixed(1)} GB, ${t('VRAM')}: ${(results.usedVram / 1024).toFixed(1)}/${(results.totalVram / 1024).toFixed(1)} GB, ${t('GPU Usage')}: ${results.gpuUsage}%)`); WindowSetTitle(`RWKV-Runner (${t('RAM')}: ${results.usedMemory.toFixed(1)}/${results.totalMemory.toFixed(1)} GB, ${t('VRAM')}: ${(results.usedVram / 1024).toFixed(1)}/${(results.totalVram / 1024).toFixed(1)} GB, ${t('GPU Usage')}: ${results.gpuUsage}%)`);
}
}); });
} }
async function initMidi() {
EventsOn('midiError', (data: string) => {
if (commonStore.platform === 'windows')
toast('MIDI Error: ' + data, { type: 'error' });
});
EventsOn('midiPorts', (data: MidiPort[]) => {
commonStore.setMidiPorts(data);
});
EventsOn('midiMessage', async (data: MidiMessage) => {
await (await import('./pages/AudiotrackManager/AudiotrackEditor')).midiMessageHandler(data);
});
if (await FileExists('assets/sound-font/accordion/instrument.json')) {
commonStore.setCompositionParams({
...commonStore.compositionParams,
useLocalSoundFont: true
});
}
}

View File

@@ -2,21 +2,27 @@ 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 { 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 { Attachment, ChatParams, Conversation } from '../types/chat';
import { CompletionPreset } from '../types/completion';
import {
CompositionParams,
InstrumentType,
MidiMessage,
MidiPort,
Track,
tracksMinimalTotalTime
} 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,
@@ -31,9 +37,16 @@ export type Status = {
device_name: string; device_name: string;
} }
export type Platform = 'windows' | 'darwin' | 'linux'; export type MonitorData = {
usedMemory: number;
totalMemory: number;
gpuUsage: number;
gpuPower: number;
usedVram: number;
totalVram: number;
}
const labels = ['January', 'February', 'March', 'April', 'May', 'June', 'July']; export type Platform = 'windows' | 'darwin' | 'linux' | 'web';
class CommonStore { class CommonStore {
// global // global
@@ -42,8 +55,10 @@ class CommonStore {
pid: 0, pid: 0,
device_name: 'CPU' device_name: 'CPU'
}; };
monitorData: MonitorData | null = null;
depComplete: boolean = false; depComplete: boolean = false;
platform: Platform = 'windows'; platform: Platform = 'windows';
lastModelName: string = '';
// presets manager // presets manager
editingPreset: Preset | null = null; editingPreset: Preset | null = null;
presets: Preset[] = []; presets: Preset[] = [];
@@ -55,9 +70,18 @@ class CommonStore {
conversationOrder: string[] = []; conversationOrder: string[] = [];
activePreset: Preset | null = null; activePreset: Preset | null = null;
attachmentUploading: boolean = false; attachmentUploading: boolean = false;
attachmentName: string = ''; attachments: {
attachmentSize: number = 0; [uuid: string]: Attachment[]
attachmentContent: string = ''; } = {};
currentTempAttachment: Attachment | null = null;
chatParams: ChatParams = {
maxResponseToken: 1000,
temperature: 1,
topP: 0.3,
presencePenalty: 0,
frequencyPenalty: 1
};
sidePanelCollapsed: boolean | 'auto' = 'auto';
// completion // completion
completionPreset: CompletionPreset | null = null; completionPreset: CompletionPreset | null = null;
completionGenerating: boolean = false; completionGenerating: boolean = false;
@@ -75,11 +99,26 @@ class CommonStore {
}; };
compositionGenerating: boolean = false; compositionGenerating: boolean = false;
compositionSubmittedPrompt: string = defaultCompositionPrompt; compositionSubmittedPrompt: string = defaultCompositionPrompt;
// composition midi device
midiPorts: MidiPort[] = [];
activeMidiDeviceIndex: number = -1;
instrumentType: InstrumentType = InstrumentType.Piano;
// composition tracks
tracks: Track[] = [];
trackScale: number = 1;
trackTotalTime: number = tracksMinimalTotalTime;
trackCurrentTime: number = 0;
trackPlayStartTime: number = 0;
playingTrackId: string = '';
recordingTrackId: string = '';
recordingContent: string = ''; // used to improve performance of midiMessageHandler, and I'm too lazy to maintain an ID dictionary for this (although that would be better for realtime effects)
recordingRawContent: MidiMessage[] = [];
// configs // configs
currentModelConfigIndex: number = 0; currentModelConfigIndex: number = 0;
modelConfigs: ModelConfig[] = []; modelConfigs: ModelConfig[] = [];
modelParamsCollapsed: boolean = true; modelParamsCollapsed: boolean = true;
// models // models
activeModelListTags: string[] = [];
modelSourceManifestList: string = 'https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner@master/manifest.json;'; modelSourceManifestList: string = 'https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner@master/manifest.json;';
modelSourceList: ModelSourceItem[] = []; modelSourceList: ModelSourceItem[] = [];
// downloads // downloads
@@ -100,7 +139,7 @@ class CommonStore {
epochSteps: 200, epochSteps: 200,
epochCount: 20, epochCount: 20,
epochBegin: 0, epochBegin: 0,
epochSave: 2, epochSave: 1,
microBsz: 1, microBsz: 1,
accumGradBatches: 8, accumGradBatches: 8,
preFfn: false, preFfn: false,
@@ -127,12 +166,13 @@ class CommonStore {
autoUpdatesCheck: true, autoUpdatesCheck: true,
giteeUpdatesSource: getUserLanguage() === 'zh', giteeUpdatesSource: getUserLanguage() === 'zh',
cnMirror: getUserLanguage() === 'zh', cnMirror: getUserLanguage() === 'zh',
useHfMirror: false,
host: '127.0.0.1', host: '127.0.0.1',
dpiScaling: 100, dpiScaling: 100,
customModelsPath: './models', customModelsPath: './models',
customPythonPath: '', customPythonPath: '',
apiUrl: '', apiUrl: '',
apiKey: 'sk-', apiKey: '',
apiChatModelName: 'rwkv', apiChatModelName: 'rwkv',
apiCompletionModelName: 'rwkv' apiCompletionModelName: 'rwkv'
}; };
@@ -172,7 +212,7 @@ class CommonStore {
createModelConfig = (config: ModelConfig = defaultModelConfigs[0], saveConfig: boolean = true) => { createModelConfig = (config: ModelConfig = defaultModelConfigs[0], saveConfig: boolean = true) => {
if (config.name === defaultModelConfigs[0].name) { if (config.name === defaultModelConfigs[0].name) {
// deep copy // deep copy
config = JSON.parse(JSON.stringify(commonStore.platform !== 'darwin' ? defaultModelConfigs[0] : defaultModelConfigsMac[0])); config = JSON.parse(JSON.stringify(this.platform !== 'darwin' ? defaultModelConfigs[0] : defaultModelConfigsMac[0]));
config.name = new Date().toLocaleString(); config.name = new Date().toLocaleString();
} }
this.modelConfigs.push(config); this.modelConfigs.push(config);
@@ -226,6 +266,10 @@ class CommonStore {
this.about = value; this.about = value;
}; };
setLastModelName(value: string) {
this.lastModelName = value;
}
setDepComplete = (value: boolean, inSaveCache: boolean = true) => { setDepComplete = (value: boolean, inSaveCache: boolean = true) => {
this.depComplete = value; this.depComplete = value;
if (inSaveCache) if (inSaveCache)
@@ -252,6 +296,10 @@ class CommonStore {
this.completionGenerating = value; this.completionGenerating = value;
} }
setMonitorData(value: MonitorData) {
this.monitorData = value;
}
setPlatform(value: Platform) { setPlatform(value: Platform) {
this.platform = value; this.platform = value;
} }
@@ -282,7 +330,7 @@ class CommonStore {
savePresets(); savePresets();
} }
setActivePreset(value: Preset) { setActivePreset(value: Preset | null) {
this.activePreset = value; this.activePreset = value;
} }
@@ -334,16 +382,81 @@ class CommonStore {
this.attachmentUploading = value; this.attachmentUploading = value;
} }
setAttachmentName(value: string) { setAttachments(value: {
this.attachmentName = value; [uuid: string]: Attachment[]
}) {
this.attachments = value;
} }
setAttachmentSize(value: number) { setAttachment(uuid: string, value: Attachment[] | null) {
this.attachmentSize = value; if (value === null)
delete this.attachments[uuid];
else
this.attachments[uuid] = value;
} }
setAttachmentContent(value: string) { setCurrentTempAttachment(value: Attachment | null) {
this.attachmentContent = value; this.currentTempAttachment = value;
}
setChatParams(value: Partial<ChatParams>) {
this.chatParams = { ...this.chatParams, ...value };
}
setSidePanelCollapsed(value: boolean | 'auto') {
this.sidePanelCollapsed = value;
}
setTracks(value: Track[]) {
this.tracks = value;
}
setTrackScale(value: number) {
this.trackScale = value;
}
setTrackTotalTime(value: number) {
this.trackTotalTime = value;
}
setTrackCurrentTime(value: number) {
this.trackCurrentTime = value;
}
setTrackPlayStartTime(value: number) {
this.trackPlayStartTime = value;
}
setMidiPorts(value: MidiPort[]) {
this.midiPorts = value;
}
setInstrumentType(value: InstrumentType) {
this.instrumentType = value;
}
setRecordingTrackId(value: string) {
this.recordingTrackId = value;
}
setActiveMidiDeviceIndex(value: number) {
this.activeMidiDeviceIndex = value;
}
setRecordingContent(value: string) {
this.recordingContent = value;
}
setRecordingRawContent(value: MidiMessage[]) {
this.recordingRawContent = value;
}
setPlayingTrackId(value: string) {
this.playingTrackId = value;
}
setActiveModelListTags(value: string[]) {
this.activeModelListTags = value;
} }
} }

View File

@@ -1,12 +1,10 @@
[data-theme='dark'] { [data-theme='dark'] {
@import 'highlight.js/scss/github-dark.scss'; @import 'highlight.js/scss/github-dark.scss';
@import 'github-markdown-css/github-markdown-dark.css';
--color-neutral-muted: rgba(110, 118, 129, 0.4); --color-neutral-muted: rgba(110, 118, 129, 0.4);
} }
[data-theme='light'] { [data-theme='light'] {
@import 'highlight.js/scss/github.scss'; @import 'highlight.js/scss/github.scss';
@import 'github-markdown-css/github-markdown-light.css';
--color-neutral-muted: rgba(150, 160, 170, 0.3); --color-neutral-muted: rgba(150, 160, 170, 0.3);
} }
@@ -16,7 +14,6 @@
body { body {
margin: 0; margin: 0;
overflow: hidden;
width: 100%; width: 100%;
height: 100%; height: 100%;
} }

View File

@@ -0,0 +1 @@
export type AboutContent = { [lang: string]: string }

View File

@@ -0,0 +1,37 @@
import { ApiParameters } from './configs';
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;
}
export type Attachment = {
name: string;
size: number;
content: string;
}
export type ChatParams = Omit<ApiParameters, 'apiPort'>

View 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
}

View File

@@ -0,0 +1,85 @@
import { NoteSequence } from '@magenta/music/esm/protobuf';
export const tracksMinimalTotalTime = 5000;
export type CompositionParams = {
prompt: string,
maxResponseToken: number,
temperature: number,
topP: number,
autoPlay: boolean,
useLocalSoundFont: boolean,
midi: ArrayBuffer | null,
ns: NoteSequence | null
}
export type Track = {
id: string;
mainInstrument: string;
content: string;
rawContent: MidiMessage[];
offsetTime: number;
contentTime: number;
};
export type MidiPort = {
name: string;
}
export type MessageType = 'NoteOff' | 'NoteOn' | 'ElapsedTime' | 'ControlChange';
export type MidiMessage = {
messageType: MessageType;
channel: number;
note: number;
velocity: number;
control: number;
value: number;
instrument: InstrumentType;
}
export enum InstrumentType {
Piano,
Percussion,
Drum,
Tuba,
Marimba,
Bass,
Guitar,
Violin,
Trumpet,
Sax,
Flute,
Lead,
Pad,
}
export const InstrumentTypeNameMap = [
'Piano',
'Percussion',
'Drum',
'Tuba',
'Marimba',
'Bass',
'Guitar',
'Violin',
'Trumpet',
'Sax',
'Flute',
'Lead',
'Pad'
];
export const InstrumentTypeTokenMap = [
'pi',
'p',
'd',
't',
'm',
'b',
'g',
'v',
'tr',
's',
'f',
'l',
'pa'
];

View File

@@ -0,0 +1,29 @@
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' | 'nf4';
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;
enableWebUI?: boolean;
}

View 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;
}

View 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;
};

View File

@@ -0,0 +1,15 @@
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;
tags?: string[];
hide?: boolean;
};

View 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;
};

View File

@@ -0,0 +1,22 @@
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
useHfMirror: boolean
host: string
dpiScaling: number
customModelsPath: string
customPythonPath: string
apiUrl: string
apiKey: string
apiChatModelName: string
apiCompletionModelName: string
}

View 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;
};

View File

@@ -0,0 +1,31 @@
import { toast } from 'react-toastify';
import commonStore from '../stores/commonStore';
import { t } from 'i18next';
import { ConvertSafetensors, FileExists, GetPyError } from '../../wailsjs/go/backend_golang/App';
import { WindowShow } from '../../wailsjs/runtime';
import { ModelConfig } from '../types/configs';
export const convertToSt = async (selectedConfig: ModelConfig) => {
const modelPath = `${commonStore.settings.customModelsPath}/${selectedConfig.modelParameters.modelName}`;
if (await FileExists(modelPath)) {
toast(t('Start Converting'), { autoClose: 2000, type: 'info' });
const newModelPath = modelPath.replace(/\.pth$/, '.st');
ConvertSafetensors(modelPath, newModelPath).then(async () => {
if (!await FileExists(newModelPath)) {
if (commonStore.platform === 'windows' || commonStore.platform === 'linux')
toast(t('Convert Failed') + ' - ' + await GetPyError(), { type: 'error' });
} else {
toast(`${t('Convert Success')} - ${newModelPath}`, { type: 'success' });
}
}).catch(e => {
const errMsg = e.message || e;
if (errMsg.includes('path contains space'))
toast(`${t('Convert Failed')} - ${t('File Path Cannot Contain Space')}`, { type: 'error' });
else
toast(`${t('Convert Failed')} - ${e.message || e}`, { type: 'error' });
});
setTimeout(WindowShow, 1000);
} else {
toast(`${t('Model Not Found')} - ${modelPath}`, { type: 'error' });
}
};

View File

@@ -15,13 +15,18 @@ 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 { BrowserOpenURL, EventsOff, EventsOn, WindowShow } from '../../wailsjs/runtime';
import { ModelSourceItem } from '../pages/Models';
import { ModelConfig, ModelParameters } from '../pages/Configs';
import { DownloadStatus } from '../pages/Downloads';
import { DataProcessParameters, LoraFinetuneParameters } from '../pages/Train';
import { BrowserOpenURL, WindowShow } from '../../wailsjs/runtime';
import { NavigateFunction } from 'react-router'; import { 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';
import { InstrumentTypeNameMap, tracksMinimalTotalTime } from '../types/composition';
import logo from '../assets/images/logo.png';
import { Preset } from '../types/presets';
import { botName, Conversation, MessageType, userName } from '../types/chat';
import { v4 as uuid } from 'uuid';
export type Cache = { export type Cache = {
version: string version: string
@@ -39,7 +44,9 @@ export type LocalConfig = {
} }
export async function refreshBuiltInModels(readCache: boolean = false) { export async function refreshBuiltInModels(readCache: boolean = false) {
let cache: { models: ModelSourceItem[] } = { models: [] }; let cache: {
models: ModelSourceItem[]
} = { models: [] };
if (readCache) if (readCache)
await ReadJson('cache.json').then((cacheData: Cache) => { await ReadJson('cache.json').then((cacheData: Cache) => {
if (cacheData.models) if (cacheData.models)
@@ -72,7 +79,8 @@ export async function refreshLocalModels(cache: {
size: d.size, size: d.size,
lastUpdated: d.modTime, lastUpdated: d.modTime,
isComplete: true, isComplete: true,
isLocal: true isLocal: true,
tags: ['Local']
}] as ModelSourceItem[]; }] as ModelSourceItem[];
return []; return [];
})); }));
@@ -82,12 +90,15 @@ export async function refreshLocalModels(cache: {
for (let i = 0; i < cache.models.length; i++) { for (let i = 0; i < cache.models.length; i++) {
if (!cache.models[i].lastUpdatedMs) if (!cache.models[i].lastUpdatedMs)
cache.models[i].lastUpdatedMs = Date.parse(cache.models[i].lastUpdated); cache.models[i].lastUpdatedMs = Date.parse(cache.models[i].lastUpdated);
if (!cache.models[i].tags)
cache.models[i].tags = ['Other'];
for (let j = i + 1; j < cache.models.length; j++) { for (let j = i + 1; j < cache.models.length; j++) {
if (!cache.models[j].lastUpdatedMs) if (!cache.models[j].lastUpdatedMs)
cache.models[j].lastUpdatedMs = Date.parse(cache.models[j].lastUpdated); cache.models[j].lastUpdatedMs = Date.parse(cache.models[j].lastUpdated);
if (cache.models[i].name === cache.models[j].name) { if (cache.models[i].name === cache.models[j].name) {
const tags = Array.from(new Set([...cache.models[i].tags as string[], ...cache.models[j].tags as string[]]));
if (cache.models[i].size <= cache.models[j].size) { // j is local file if (cache.models[i].size <= cache.models[j].size) { // j is local file
if (cache.models[i].lastUpdatedMs! < cache.models[j].lastUpdatedMs!) { if (cache.models[i].lastUpdatedMs! < cache.models[j].lastUpdatedMs!) {
cache.models[i] = Object.assign({}, cache.models[i], cache.models[j]); cache.models[i] = Object.assign({}, cache.models[i], cache.models[j]);
@@ -97,6 +108,7 @@ export async function refreshLocalModels(cache: {
} // else is not complete local file } // else is not complete local file
cache.models[i].isLocal = true; cache.models[i].isLocal = true;
cache.models[i].localSize = cache.models[j].size; cache.models[i].localSize = cache.models[j].size;
cache.models[i].tags = tags;
cache.models.splice(j, 1); cache.models.splice(j, 1);
j--; j--;
} }
@@ -117,7 +129,7 @@ function initLastUnfinishedModelDownloads() {
{ {
name: item.name, name: item.name,
path: `${commonStore.settings.customModelsPath}/${item.name}`, path: `${commonStore.settings.customModelsPath}/${item.name}`,
url: item.downloadUrl!, url: getHfDownloadUrl(item.downloadUrl!),
transferred: item.localSize!, transferred: item.localSize!,
size: item.size, size: item.size,
speed: 0, speed: 0,
@@ -131,7 +143,9 @@ function initLastUnfinishedModelDownloads() {
commonStore.setLastUnfinishedModelDownloads(list); commonStore.setLastUnfinishedModelDownloads(list);
} }
export async function refreshRemoteModels(cache: { models: ModelSourceItem[] }) { export async function refreshRemoteModels(cache: {
models: ModelSourceItem[]
}) {
const manifestUrls = commonStore.modelSourceManifestList.split(/[,;\n]/); const manifestUrls = commonStore.modelSourceManifestList.split(/[,;\n]/);
const requests = manifestUrls.filter(url => url.endsWith('.json')).map( const requests = manifestUrls.filter(url => url.endsWith('.json')).map(
url => fetch(url, { cache: 'no-cache' }).then(r => r.json())); url => fetch(url, { cache: 'no-cache' }).then(r => r.json()));
@@ -178,14 +192,14 @@ export const getStrategy = (modelConfig: ModelConfig | undefined = undefined) =>
strategy += params.precision === 'int8' ? 'fp32i8' : 'fp32'; strategy += params.precision === 'int8' ? 'fp32i8' : 'fp32';
break; break;
case 'WebGPU': case 'WebGPU':
strategy += params.precision === 'int8' ? 'fp16i8' : 'fp16'; strategy += params.precision === 'nf4' ? 'fp16i4' : params.precision === 'int8' ? 'fp16i8' : 'fp16';
break; break;
case 'CUDA': case 'CUDA':
case 'CUDA-Beta': case 'CUDA-Beta':
if (avoidOverflow) if (avoidOverflow)
strategy = params.useCustomCuda ? 'cuda fp16 *1 -> ' : 'cuda fp32 *1 -> '; strategy = params.useCustomCuda ? 'cuda fp16 *1 -> ' : 'cuda fp32 *1 -> ';
strategy += 'cuda '; strategy += 'cuda ';
strategy += params.precision === 'fp16' ? 'fp16' : params.precision === 'int8' ? 'fp16i8' : 'fp32'; strategy += params.precision === 'int8' ? 'fp16i8' : params.precision === 'fp32' ? 'fp32' : 'fp16';
if (params.storedLayers < params.maxStoredLayers) if (params.storedLayers < params.maxStoredLayers)
strategy += ` *${params.storedLayers}+`; strategy += ` *${params.storedLayers}+`;
break; break;
@@ -193,7 +207,7 @@ export const getStrategy = (modelConfig: ModelConfig | undefined = undefined) =>
if (avoidOverflow) if (avoidOverflow)
strategy = 'mps fp32 *1 -> '; strategy = 'mps fp32 *1 -> ';
strategy += 'mps '; strategy += 'mps ';
strategy += params.precision === 'fp16' ? 'fp16' : params.precision === 'int8' ? 'fp16i8' : 'fp32'; strategy += params.precision === 'int8' ? 'fp32i8' : 'fp32';
break; break;
case 'Custom': case 'Custom':
strategy = params.customStrategy || ''; strategy = params.customStrategy || '';
@@ -289,7 +303,23 @@ export function bytesToReadable(size: number) {
else return bytesToGb(size) + ' GB'; else return bytesToGb(size) + ' GB';
} }
export function getServerRoot(defaultLocalPort: number) {
const defaultRoot = `http://127.0.0.1:${defaultLocalPort}`;
if (commonStore.status.status !== ModelStatus.Offline)
return defaultRoot;
const customApiUrl = commonStore.settings.apiUrl.trim().replace(/\/$/, '');
if (customApiUrl)
return customApiUrl;
if (commonStore.platform === 'web')
return '';
return defaultRoot;
}
export function absPathAsset(path: string) { export function absPathAsset(path: string) {
if (commonStore.platform === 'web')
return path;
if (path === logo)
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;
@@ -322,27 +352,47 @@ export async function checkUpdate(notifyEvenLatest: boolean = false) {
`https://gitee.com/josc146/RWKV-Runner/releases/download/${versionTag}/${asset.name}`; `https://gitee.com/josc146/RWKV-Runner/releases/download/${versionTag}/${asset.name}`;
toastWithButton(t('New Version Available') + ': ' + versionTag, t('Update'), () => { toastWithButton(t('New Version Available') + ': ' + versionTag, t('Update'), () => {
DeleteFile('cache.json'); DeleteFile('cache.json');
toast(t('Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.'), { const progressId = 'update_app';
type: 'info', const progressEvent = 'updateApp';
position: 'bottom-left', const updateProgress = (ds: DownloadStatus | null) => {
autoClose: false const content =
}); t('Downloading update, please wait. If it is not completed, please manually download the program from GitHub and replace the original program.')
setTimeout(() => { + (ds ? ` (${ds.progress.toFixed(2)}% ${bytesToReadable(ds.transferred)}/${bytesToReadable(ds.size)})` : '');
UpdateApp(updateUrl).then(() => { const options: ToastOptions = {
toast(t('Update completed, please restart the program.'), { type: 'info',
type: 'success', position: 'bottom-left',
position: 'bottom-left', autoClose: false,
autoClose: false toastId: progressId,
} hideProgressBar: false,
); progress: ds ? ds.progress / 100 : 0
}).catch((e) => { };
toast(t('Update Error') + ' - ' + (e.message || e), { if (toast.isActive(progressId))
type: 'error', toast.update(progressId, {
render: content,
...options
});
else
toast(content, options);
};
updateProgress(null);
EventsOn(progressEvent, updateProgress);
UpdateApp(updateUrl).then(() => {
toast(t('Update completed, please restart the program.'), {
type: 'success',
position: 'bottom-left', position: 'bottom-left',
autoClose: false autoClose: false
}); }
);
}).catch((e) => {
toast(t('Update Error') + ' - ' + (e.message || e), {
type: 'error',
position: 'bottom-left',
autoClose: false
}); });
}, 500); }).finally(() => {
toast.dismiss(progressId);
EventsOff(progressEvent);
});
}, { }, {
autoClose: false, autoClose: false,
position: 'bottom-left' position: 'bottom-left'
@@ -383,7 +433,10 @@ export const checkDependencies = async (navigate: NavigateFunction) => {
toastWithButton(`${t('Downloading')} Python`, t('Check'), () => { toastWithButton(`${t('Downloading')} Python`, t('Check'), () => {
navigate({ pathname: '/downloads' }); navigate({ pathname: '/downloads' });
}, { autoClose: 3000 }); }, { autoClose: 3000 });
AddToDownloadList('python-3.10.11-embed-amd64.zip', 'https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip'); AddToDownloadList('python-3.10.11-embed-amd64.zip',
!commonStore.settings.cnMirror
? 'https://www.python.org/ftp/python/3.10.11/python-3.10.11-embed-amd64.zip'
: 'https://mirrors.huaweicloud.com/python/3.10.11/python-3.10.11-embed-amd64.zip');
}); });
} else if (depErrorMsg.includes('DepCheck Error')) { } else if (depErrorMsg.includes('DepCheck Error')) {
if (depErrorMsg.includes('vc_redist') || depErrorMsg.includes('DLL load failed while importing')) { if (depErrorMsg.includes('vc_redist') || depErrorMsg.includes('DLL load failed while importing')) {
@@ -440,6 +493,102 @@ export function toastWithButton(text: string, buttonText: string, onClickButton:
return id; return id;
} }
export function getHfDownloadUrl(url: string) {
if (commonStore.settings.useHfMirror && url.includes('huggingface.co') && url.includes('resolve'))
return url.replace('huggingface.co', 'hf-mirror.com');
return url;
}
export function refreshTracksTotalTime() {
if (commonStore.tracks.length === 0) {
commonStore.setTrackTotalTime(tracksMinimalTotalTime);
commonStore.setTrackCurrentTime(0);
commonStore.setTrackPlayStartTime(0);
return;
}
const endTimes = commonStore.tracks.map(t => t.offsetTime + t.contentTime);
const totalTime = Math.max(...endTimes) + tracksMinimalTotalTime;
if (commonStore.trackPlayStartTime > totalTime)
commonStore.setTrackPlayStartTime(totalTime);
commonStore.setTrackTotalTime(totalTime);
}
export function flushMidiRecordingContent() {
const recordingTrackIndex = commonStore.tracks.findIndex(t => t.id === commonStore.recordingTrackId);
if (recordingTrackIndex >= 0) {
const recordingTrack = commonStore.tracks[recordingTrackIndex];
const tracks = commonStore.tracks.slice();
const contentTime = commonStore.recordingRawContent
.reduce((sum, current) =>
sum + (current.messageType === 'ElapsedTime' ? current.value : 0)
, 0);
const sortedInstrumentFrequency = Object.entries(commonStore.recordingRawContent
.filter(c => c.messageType === 'NoteOn')
.map(c => c.instrument)
.reduce((frequencyCount, current) => (frequencyCount[current] = (frequencyCount[current] || 0) + 1, frequencyCount)
, {} as {
[key: string]: number
}))
.sort((a, b) => b[1] - a[1]);
let mainInstrument: string = '';
if (sortedInstrumentFrequency.length > 0)
mainInstrument = InstrumentTypeNameMap[Number(sortedInstrumentFrequency[0][0])];
tracks[recordingTrackIndex] = {
...recordingTrack,
content: commonStore.recordingContent,
rawContent: commonStore.recordingRawContent,
contentTime: contentTime,
mainInstrument: mainInstrument
};
commonStore.setTracks(tracks);
refreshTracksTotalTime();
}
commonStore.setRecordingContent('');
commonStore.setRecordingRawContent([]);
}
export async function getSoundFont() {
let soundUrl: string;
if (commonStore.compositionParams.useLocalSoundFont)
soundUrl = 'assets/sound-font';
else
soundUrl = !commonStore.settings.giteeUpdatesSource ?
`https://raw.githubusercontent.com/josStorer/sgm_plus/master` :
`https://cdn.jsdelivr.net/gh/josstorer/sgm_plus`;
const fallbackUrl = 'https://cdn.jsdelivr.net/gh/josstorer/sgm_plus';
await fetch(soundUrl + '/soundfont.json').then(r => {
if (!r.ok)
soundUrl = fallbackUrl;
}).catch(() => soundUrl = fallbackUrl);
return soundUrl;
}
export const setActivePreset = (preset: Preset | null) => {
commonStore.setActivePreset(preset);
//TODO if (preset.displayPresetMessages) {
const conversation: Conversation = {};
const conversationOrder: string[] = [];
if (preset)
for (const message of preset.messages) {
const newUuid = uuid();
conversationOrder.push(newUuid);
conversation[newUuid] = {
sender: message.role === 'user' ? userName : botName,
type: MessageType.Normal,
color: message.role === 'user' ? 'brand' : 'colorful',
time: new Date().toISOString(),
content: message.content,
side: message.role === 'user' ? 'right' : 'left',
done: true
};
}
commonStore.setConversation(conversation);
commonStore.setConversationOrder(conversationOrder);
//}
};
export function getSupportedCustomCudaFile(isBeta: boolean) { export function getSupportedCustomCudaFile(isBeta: boolean) {
if ([' 10', ' 16', ' 20', ' 30', 'MX', 'Tesla P', 'Quadro P', 'NVIDIA P', 'TITAN X', 'TITAN RTX', 'RTX A', if ([' 10', ' 16', ' 20', ' 30', 'MX', 'Tesla P', 'Quadro P', 'NVIDIA P', 'TITAN X', 'TITAN RTX', 'RTX A',
'Quadro RTX 4000', 'Quadro RTX 5000', 'Tesla T4', 'NVIDIA A10', 'NVIDIA A40'].some(v => commonStore.status.device_name.includes(v))) 'Quadro RTX 4000', 'Quadro RTX 5000', 'Tesla T4', 'NVIDIA A10', 'NVIDIA A40'].some(v => commonStore.status.device_name.includes(v)))

View File

@@ -0,0 +1,72 @@
import { getDocument, GlobalWorkerOptions, PDFDocumentProxy } from 'pdfjs-dist';
import { TextItem } from 'pdfjs-dist/types/src/display/api';
export function webOpenOpenFileDialog({ filterPattern, fnStartLoading }: { filterPattern: string, fnStartLoading: Function | null }): Promise<{ blob: Blob, content?: string }> {
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 = async e => {
// @ts-ignore
const file: Blob = e.target?.files[0];
if (fnStartLoading && typeof fnStartLoading === 'function')
fnStartLoading();
if (!GlobalWorkerOptions.workerSrc)
// @ts-ignore
GlobalWorkerOptions.workerSrc = await import('pdfjs-dist/build/pdf.worker.min.mjs');
if (file.type === 'text/plain') {
const reader = new FileReader();
reader.readAsText(file, 'UTF-8');
reader.onload = event => {
const content = event.target?.result as string;
resolve({
blob: file,
content: content
});
};
reader.onerror = reject;
} else if (file.type === 'application/pdf') {
const readPDFPage = async (doc: PDFDocumentProxy, pageNo: number) => {
const page = await doc.getPage(pageNo);
const tokenizedText = await page.getTextContent();
return tokenizedText.items.map(token => (token as TextItem).str).join('');
};
let reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = async (event) => {
try {
const doc = await getDocument(event.target?.result!).promise;
const pageTextPromises = [];
for (let pageNo = 1; pageNo <= doc.numPages; pageNo++) {
pageTextPromises.push(readPDFPage(doc, pageNo));
}
const pageTexts = await Promise.all(pageTextPromises);
let content;
if (pageTexts.length === 1)
content = pageTexts[0];
else
content = pageTexts.map((p, i) => `Page ${i + 1}:\n${p}`).join('\n\n');
resolve({
blob: file,
content: content
});
} catch (err) {
reject(err);
}
};
reader.onerror = reject;
} else {
resolve({
blob: file
});
}
};
input.click();
});
}

135
frontend/src/webWails.js Normal file
View File

@@ -0,0 +1,135 @@
import { webOpenOpenFileDialog } from './utils/web-file-operations'
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('CloseMidiPort', 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('OpenMidiPort', async () => {
})
defineApp('PauseDownload', async () => {
})
defineApp('PlayNote', 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', webOpenOpenFileDialog)
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))
})
}

View File

@@ -1,13 +1,66 @@
import {defineConfig} from 'vite'; // @ts-ignore
import { dependencies } from './package.json';
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';
import topLevelAwait from 'vite-plugin-top-level-await';
// 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',
'react-draggable',
'@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(),
visualizer({ visualizer({
template: 'treemap', template: 'treemap',
gzipSize: true, gzipSize: true,
brotliSize: true brotliSize: true
})] }),
topLevelAwait({
promiseExportName: '__tla',
promiseImportName: i => `__tla_${i}`
})
],
build: {
chunkSizeWarningLimit: 3000,
rollupOptions: {
output: {
manualChunks: {
vendor,
...renderChunks(dependencies)
},
entryFileNames: `assets/[name].js`,
chunkFileNames: `assets/[name].js`,
assetFileNames: `assets/[name].[ext]`
}
}
}
}); });

View File

@@ -4,13 +4,15 @@ import {backend_golang} from '../models';
export function AddToDownloadList(arg1:string,arg2:string):Promise<void>; export function AddToDownloadList(arg1:string,arg2:string):Promise<void>;
export function CloseMidiPort():Promise<void>;
export function ContinueDownload(arg1:string):Promise<void>; export function ContinueDownload(arg1:string):Promise<void>;
export function ConvertData(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>; export function ConvertData(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
export function ConvertModel(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>; export function ConvertModel(arg1:string,arg2:string,arg3:string,arg4:string):Promise<string>;
export function ConvertSafetensors(arg1:string,arg2:string,arg3:string):Promise<string>; export function ConvertSafetensors(arg1:string,arg2:string):Promise<string>;
export function CopyFile(arg1:string,arg2:string):Promise<void>; export function CopyFile(arg1:string,arg2:string):Promise<void>;
@@ -28,12 +30,16 @@ export function GetPyError():Promise<string>;
export function InstallPyDep(arg1:string,arg2:boolean):Promise<string>; export function InstallPyDep(arg1:string,arg2:boolean):Promise<string>;
export function IsPortAvailable(arg1:number):Promise<boolean>;
export function ListDirFiles(arg1:string):Promise<Array<backend_golang.FileInfo>>; export function ListDirFiles(arg1:string):Promise<Array<backend_golang.FileInfo>>;
export function MergeLora(arg1:string,arg2:boolean,arg3:number,arg4:string,arg5:string,arg6:string):Promise<string>; export function MergeLora(arg1:string,arg2:boolean,arg3:number,arg4:string,arg5:string,arg6:string):Promise<string>;
export function OpenFileFolder(arg1:string,arg2:boolean):Promise<void>; export function OpenFileFolder(arg1:string,arg2:boolean):Promise<void>;
export function OpenMidiPort(arg1:number):Promise<void>;
export function OpenOpenFileDialog(arg1:string):Promise<string>; export function OpenOpenFileDialog(arg1:string):Promise<string>;
export function OpenSaveFileDialog(arg1:string,arg2:string,arg3:string):Promise<string>; export function OpenSaveFileDialog(arg1:string,arg2:string,arg3:string):Promise<string>;
@@ -42,6 +48,8 @@ export function OpenSaveFileDialogBytes(arg1:string,arg2:string,arg3:Array<numbe
export function PauseDownload(arg1:string):Promise<void>; export function PauseDownload(arg1:string):Promise<void>;
export function PlayNote(arg1:backend_golang.MIDIMessage):Promise<void>;
export function ReadFileInfo(arg1:string):Promise<backend_golang.FileInfo>; export function ReadFileInfo(arg1:string):Promise<backend_golang.FileInfo>;
export function ReadJson(arg1:string):Promise<any>; export function ReadJson(arg1:string):Promise<any>;
@@ -50,7 +58,7 @@ export function RestartApp():Promise<void>;
export function SaveJson(arg1:string,arg2:any):Promise<void>; export function SaveJson(arg1:string,arg2:any):Promise<void>;
export function StartServer(arg1:string,arg2:number,arg3:string,arg4:boolean):Promise<string>; export function StartServer(arg1:string,arg2:number,arg3:string,arg4:boolean,arg5:boolean):Promise<string>;
export function StartWebGPUServer(arg1:number,arg2:string):Promise<string>; export function StartWebGPUServer(arg1:number,arg2:string):Promise<string>;

View File

@@ -6,6 +6,10 @@ export function AddToDownloadList(arg1, arg2) {
return window['go']['backend_golang']['App']['AddToDownloadList'](arg1, arg2); return window['go']['backend_golang']['App']['AddToDownloadList'](arg1, arg2);
} }
export function CloseMidiPort() {
return window['go']['backend_golang']['App']['CloseMidiPort']();
}
export function ContinueDownload(arg1) { export function ContinueDownload(arg1) {
return window['go']['backend_golang']['App']['ContinueDownload'](arg1); return window['go']['backend_golang']['App']['ContinueDownload'](arg1);
} }
@@ -18,8 +22,8 @@ export function ConvertModel(arg1, arg2, arg3, arg4) {
return window['go']['backend_golang']['App']['ConvertModel'](arg1, arg2, arg3, arg4); return window['go']['backend_golang']['App']['ConvertModel'](arg1, arg2, arg3, arg4);
} }
export function ConvertSafetensors(arg1, arg2, arg3) { export function ConvertSafetensors(arg1, arg2) {
return window['go']['backend_golang']['App']['ConvertSafetensors'](arg1, arg2, arg3); return window['go']['backend_golang']['App']['ConvertSafetensors'](arg1, arg2);
} }
export function CopyFile(arg1, arg2) { export function CopyFile(arg1, arg2) {
@@ -54,6 +58,10 @@ export function InstallPyDep(arg1, arg2) {
return window['go']['backend_golang']['App']['InstallPyDep'](arg1, arg2); return window['go']['backend_golang']['App']['InstallPyDep'](arg1, arg2);
} }
export function IsPortAvailable(arg1) {
return window['go']['backend_golang']['App']['IsPortAvailable'](arg1);
}
export function ListDirFiles(arg1) { export function ListDirFiles(arg1) {
return window['go']['backend_golang']['App']['ListDirFiles'](arg1); return window['go']['backend_golang']['App']['ListDirFiles'](arg1);
} }
@@ -66,6 +74,10 @@ export function OpenFileFolder(arg1, arg2) {
return window['go']['backend_golang']['App']['OpenFileFolder'](arg1, arg2); return window['go']['backend_golang']['App']['OpenFileFolder'](arg1, arg2);
} }
export function OpenMidiPort(arg1) {
return window['go']['backend_golang']['App']['OpenMidiPort'](arg1);
}
export function OpenOpenFileDialog(arg1) { export function OpenOpenFileDialog(arg1) {
return window['go']['backend_golang']['App']['OpenOpenFileDialog'](arg1); return window['go']['backend_golang']['App']['OpenOpenFileDialog'](arg1);
} }
@@ -82,6 +94,10 @@ export function PauseDownload(arg1) {
return window['go']['backend_golang']['App']['PauseDownload'](arg1); return window['go']['backend_golang']['App']['PauseDownload'](arg1);
} }
export function PlayNote(arg1) {
return window['go']['backend_golang']['App']['PlayNote'](arg1);
}
export function ReadFileInfo(arg1) { export function ReadFileInfo(arg1) {
return window['go']['backend_golang']['App']['ReadFileInfo'](arg1); return window['go']['backend_golang']['App']['ReadFileInfo'](arg1);
} }
@@ -98,8 +114,8 @@ export function SaveJson(arg1, arg2) {
return window['go']['backend_golang']['App']['SaveJson'](arg1, arg2); return window['go']['backend_golang']['App']['SaveJson'](arg1, arg2);
} }
export function StartServer(arg1, arg2, arg3, arg4) { export function StartServer(arg1, arg2, arg3, arg4, arg5) {
return window['go']['backend_golang']['App']['StartServer'](arg1, arg2, arg3, arg4); return window['go']['backend_golang']['App']['StartServer'](arg1, arg2, arg3, arg4, arg5);
} }
export function StartWebGPUServer(arg1, arg2) { export function StartWebGPUServer(arg1, arg2) {

View File

@@ -18,6 +18,28 @@ export namespace backend_golang {
this.modTime = source["modTime"]; this.modTime = source["modTime"];
} }
} }
export class MIDIMessage {
messageType: string;
channel: number;
note: number;
velocity: number;
control: number;
value: number;
static createFrom(source: any = {}) {
return new MIDIMessage(source);
}
constructor(source: any = {}) {
if ('string' === typeof source) source = JSON.parse(source);
this.messageType = source["messageType"];
this.channel = source["channel"];
this.note = source["note"];
this.velocity = source["velocity"];
this.control = source["control"];
this.value = source["value"];
}
}
} }

1
go.mod
View File

@@ -5,6 +5,7 @@ go 1.20
require ( require (
github.com/cavaliergopher/grab/v3 v3.0.1 github.com/cavaliergopher/grab/v3 v3.0.1
github.com/fsnotify/fsnotify v1.6.0 github.com/fsnotify/fsnotify v1.6.0
github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79
github.com/minio/selfupdate v0.6.0 github.com/minio/selfupdate v0.6.0
github.com/nyaosorg/go-windows-su v0.2.1 github.com/nyaosorg/go-windows-su v0.2.1
github.com/ubuntu/gowsl v0.0.0-20230615094051-94945650cc1e github.com/ubuntu/gowsl v0.0.0-20230615094051-94945650cc1e

2
go.sum
View File

@@ -38,6 +38,8 @@ github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27k
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM= github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA= github.com/mattn/go-isatty v0.0.19 h1:JITubQf0MOLdlGRuRq+jtsDlekdYPia9ZFsB8h/APPA=
github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-isatty v0.0.19/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79 h1:CA1UHN3RuY70DlC0RlvgtB1e8h3kYzmvK7s8CFe+Ohw=
github.com/mattrtaylor/go-rtmidi v0.0.0-20220428034745-af795b1c1a79/go.mod h1:oBuZjmjlKSj9CZKrNhcx/adNhHiiE0hZknECjIP8Z0Q=
github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU=
github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM=
github.com/nyaosorg/go-windows-su v0.2.1 h1:5V0XavLyjOqPUp7psxxCvBISaneU4XmFPSMlejSl5sc= github.com/nyaosorg/go-windows-su v0.2.1 h1:5V0XavLyjOqPUp7psxxCvBISaneU4XmFPSMlejSl5sc=

View File

@@ -67,6 +67,7 @@ var components embed.FS
func main() { func main() {
if buildInfo, ok := debug.ReadBuildInfo(); !ok || strings.Contains(buildInfo.String(), "-ldflags") { if buildInfo, ok := debug.ReadBuildInfo(); !ok || strings.Contains(buildInfo.String(), "-ldflags") {
backend.CopyEmbed(assets)
os.RemoveAll("./py310/Lib/site-packages/cyac-1.7.dist-info") os.RemoveAll("./py310/Lib/site-packages/cyac-1.7.dist-info")
backend.CopyEmbed(cyac) backend.CopyEmbed(cyac)
backend.CopyEmbed(cyacInfo) backend.CopyEmbed(cyacInfo)

View File

@@ -1,5 +1,5 @@
{ {
"version": "1.4.8", "version": "1.5.8",
"introduction": { "introduction": {
"en": "RWKV is an open-source, commercially usable large language model with high flexibility and great potential for development.\n### About This Tool\nThis tool aims to lower the barrier of entry for using large language models, making it accessible to everyone. It provides fully automated dependency and model management. You simply need to click and run, following the instructions, to deploy a local large language model. The tool itself is very compact and only requires a single executable file for one-click deployment.\nAdditionally, this tool offers an interface that is fully compatible with the OpenAI API. This means you can use any ChatGPT client as a client for RWKV, enabling capability expansion beyond just chat functionality.\n### Preset Configuration Rules at the Bottom\nThis tool comes with a series of preset configurations to reduce complexity. The naming rules for each configuration represent the following in order: device - required VRAM/memory - model size - model language.\nFor example, \"GPU-8G-3B-EN\" indicates that this configuration is for a graphics card with 8GB of VRAM, a model size of 3 billion parameters, and it uses an English language model.\nLarger model sizes have higher performance and VRAM requirements. Among configurations with the same model size, those with higher VRAM usage will have faster runtime.\nFor example, if you have 12GB of VRAM but running the \"GPU-12G-7B-EN\" configuration is slow, you can downgrade to \"GPU-8G-3B-EN\" for a significant speed improvement.\n### About RWKV\nRWKV is an RNN with Transformer-level LLM performance, which can also be directly trained like a GPT transformer (parallelizable). And it's 100% attention-free. You only need the hidden state at position t to compute the state at position t+1. You can use the \"GPT\" mode to quickly compute the hidden state for the \"RNN\" mode.<br/>So it's combining the best of RNN and transformer - great performance, fast inference, saves VRAM, fast training, \"infinite\" ctx_len, and free sentence embedding (using the final hidden state).", "en": "RWKV is an open-source, commercially usable large language model with high flexibility and great potential for development.\n### About This Tool\nThis tool aims to lower the barrier of entry for using large language models, making it accessible to everyone. It provides fully automated dependency and model management. You simply need to click and run, following the instructions, to deploy a local large language model. The tool itself is very compact and only requires a single executable file for one-click deployment.\nAdditionally, this tool offers an interface that is fully compatible with the OpenAI API. This means you can use any ChatGPT client as a client for RWKV, enabling capability expansion beyond just chat functionality.\n### Preset Configuration Rules at the Bottom\nThis tool comes with a series of preset configurations to reduce complexity. The naming rules for each configuration represent the following in order: device - required VRAM/memory - model size - model language.\nFor example, \"GPU-8G-3B-EN\" indicates that this configuration is for a graphics card with 8GB of VRAM, a model size of 3 billion parameters, and it uses an English language model.\nLarger model sizes have higher performance and VRAM requirements. Among configurations with the same model size, those with higher VRAM usage will have faster runtime.\nFor example, if you have 12GB of VRAM but running the \"GPU-12G-7B-EN\" configuration is slow, you can downgrade to \"GPU-8G-3B-EN\" for a significant speed improvement.\n### About RWKV\nRWKV is an RNN with Transformer-level LLM performance, which can also be directly trained like a GPT transformer (parallelizable). And it's 100% attention-free. You only need the hidden state at position t to compute the state at position t+1. You can use the \"GPT\" mode to quickly compute the hidden state for the \"RNN\" mode.<br/>So it's combining the best of RNN and transformer - great performance, fast inference, saves VRAM, fast training, \"infinite\" ctx_len, and free sentence embedding (using the final hidden state).",
"zh": "RWKV是一个开源且允许商用的大语言模型灵活性很高且极具发展潜力。\n### 关于本工具\n本工具旨在降低大语言模型的使用门槛做到人人可用本工具提供了全自动化的依赖和模型管理你只需要直接点击运行跟随引导即可完成本地大语言模型的部署工具本身体积极小只需要一个exe即可完成一键部署。\n此外本工具提供了与OpenAI API完全兼容的接口这意味着你可以把任意ChatGPT客户端用作RWKV的客户端实现能力拓展而不局限于聊天。\n### 底部的预设配置规则\n本工具内置了一系列预设配置以降低使用难度每个配置名的规则依次代表着设备-所需显存/内存-模型规模-模型语言。\n例如GPU-8G-3B-CN表示该配置用于显卡需要8G显存模型规模为30亿参数使用的是中文模型。\n模型规模越大性能要求越高显存要求也越高而同样模型规模的配置中显存占用越高的运行速度越快。\n例如当你有12G显存但运行GPU-12G-7B-CN配置速度比较慢可降级成GPU-8G-3B-CN将会大幅提速。\n### 关于RWKV\nRWKV是具有Transformer级别LLM性能的RNN也可以像GPT Transformer一样直接进行训练可并行化。而且它是100% attention-free的。你只需在位置t处获得隐藏状态即可计算位置t + 1处的状态。你可以使用“GPT”模式快速计算用于“RNN”模式的隐藏状态。\n因此它将RNN和Transformer的优点结合起来 - 高性能、快速推理、节省显存、快速训练、“无限”上下文长度以及免费的语句嵌入(使用最终隐藏状态)。" "zh": "RWKV是一个开源且允许商用的大语言模型灵活性很高且极具发展潜力。\n### 关于本工具\n本工具旨在降低大语言模型的使用门槛做到人人可用本工具提供了全自动化的依赖和模型管理你只需要直接点击运行跟随引导即可完成本地大语言模型的部署工具本身体积极小只需要一个exe即可完成一键部署。\n此外本工具提供了与OpenAI API完全兼容的接口这意味着你可以把任意ChatGPT客户端用作RWKV的客户端实现能力拓展而不局限于聊天。\n### 底部的预设配置规则\n本工具内置了一系列预设配置以降低使用难度每个配置名的规则依次代表着设备-所需显存/内存-模型规模-模型语言。\n例如GPU-8G-3B-CN表示该配置用于显卡需要8G显存模型规模为30亿参数使用的是中文模型。\n模型规模越大性能要求越高显存要求也越高而同样模型规模的配置中显存占用越高的运行速度越快。\n例如当你有12G显存但运行GPU-12G-7B-CN配置速度比较慢可降级成GPU-8G-3B-CN将会大幅提速。\n### 关于RWKV\nRWKV是具有Transformer级别LLM性能的RNN也可以像GPT Transformer一样直接进行训练可并行化。而且它是100% attention-free的。你只需在位置t处获得隐藏状态即可计算位置t + 1处的状态。你可以使用“GPT”模式快速计算用于“RNN”模式的隐藏状态。\n因此它将RNN和Transformer的优点结合起来 - 高性能、快速推理、节省显存、快速训练、“无限”上下文长度以及免费的语句嵌入(使用最终隐藏状态)。"
@@ -15,6 +15,42 @@
} }
], ],
"models": [ "models": [
{
"name": "RWKV-5-World-0.1B-v1-20230803-ctx4096.pth",
"desc": {
"en": "RWKV-5 Global Languages 0.1B v1",
"zh": "RWKV-5 全球语言 0.1B v1",
"ja": "RWKV-5 グローバル言語 0.1B v1"
},
"size": 385598386,
"SHA256": "c844a3ee05bcb9065848cb05b10c48a3f381f5ac1953aad89e156ecdf31d7703",
"lastUpdated": "2023-08-03T15:18:46",
"url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-0.1B-v1-20230803-ctx4096.pth?download=true",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-0.1B-v1-20230803-ctx4096.pth?download=true",
"tags": [
"Main",
"RWKV-5",
"Global"
]
},
{
"name": "RWKV-5-World-0.4B-v2-20231113-ctx4096.pth",
"desc": {
"en": "RWKV-5 Global Languages 0.4B v2",
"zh": "RWKV-5 全球语言 0.4B v2",
"ja": "RWKV-5 グローバル言語 0.4B v2"
},
"size": 923523954,
"SHA256": "5a288c54c7f30b0e2d4af23991133fad2af2d5e59ec7ad850ffe78054a5e4f92",
"lastUpdated": "2023-11-14T01:23:49",
"url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-0.4B-v2-20231113-ctx4096.pth?download=true",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-0.4B-v2-20231113-ctx4096.pth?download=true",
"tags": [
"Main",
"RWKV-5",
"Global"
]
},
{ {
"name": "RWKV-5-World-1B5-v2-20231025-ctx4096.pth", "name": "RWKV-5-World-1B5-v2-20231025-ctx4096.pth",
"desc": { "desc": {
@@ -26,7 +62,48 @@
"SHA256": "5a89f56be7f82ab9dd0835af9a6838f788477471616c02f7b041e3aea0c57435", "SHA256": "5a89f56be7f82ab9dd0835af9a6838f788477471616c02f7b041e3aea0c57435",
"lastUpdated": "2023-10-26T05:49:30", "lastUpdated": "2023-10-26T05:49:30",
"url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-1B5-v2-20231025-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-1B5-v2-20231025-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-1B5-v2-20231025-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-1B5-v2-20231025-ctx4096.pth",
"tags": [
"Main",
"RWKV-5",
"Global"
]
},
{
"name": "RWKV-5-World-3B-v2-20231113-ctx4096.pth",
"desc": {
"en": "RWKV-5 Global Languages 3B v2",
"zh": "RWKV-5 全球语言 3B v2",
"ja": "RWKV-5 グローバル言語 3B v2"
},
"size": 6126106674,
"SHA256": "a4bd430343c6fd138b85bbc68bb20262d3a2f053ea57dc4b41078269af68ff9c",
"lastUpdated": "2023-11-14T01:23:49",
"url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-3B-v2-20231113-ctx4096.pth?download=true",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-3B-v2-20231113-ctx4096.pth?download=true",
"tags": [
"Main",
"RWKV-5",
"Global"
]
},
{
"name": "RWKV-5-World-3B-v2-20231118-ctx16k.pth",
"desc": {
"en": "RWKV-5 Global Languages 3B v2 Ctx16k",
"zh": "RWKV-5 全球语言 3B v2 16k上下文",
"ja": "RWKV-5 グローバル言語 3B v2 16kコンテキスト"
},
"size": 6126106467,
"SHA256": "efa5178d1c824b94ef17c6c9a456674e5581a8be832becbda9aba4dc533f88c2",
"lastUpdated": "2023-11-19T04:21:04",
"url": "https://huggingface.co/BlinkDL/rwkv-5-world/blob/main/RWKV-5-World-3B-v2-20231118-ctx16k.pth?download=true",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-world/resolve/main/RWKV-5-World-3B-v2-20231118-ctx16k.pth?download=true",
"tags": [
"Main",
"RWKV-5",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth", "name": "RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth",
@@ -39,7 +116,12 @@
"SHA256": "a3888f9958d378ee6d4976ae1c02edb698f4382e426086febafb4a69417b9080", "SHA256": "a3888f9958d378ee6d4976ae1c02edb698f4382e426086febafb4a69417b9080",
"lastUpdated": "2023-06-17T18:35:26", "lastUpdated": "2023-06-17T18:35:26",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-0.1B-v1-20230617-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "RWKV-4-World-0.1B-v1-20230520-ctx4096.pth", "name": "RWKV-4-World-0.1B-v1-20230520-ctx4096.pth",
@@ -52,7 +134,12 @@
"SHA256": "a10ef99df2a8f8a6801edf4fc92a9c49bedd63dcb900d3e5667a2136b3d671e7", "SHA256": "a10ef99df2a8f8a6801edf4fc92a9c49bedd63dcb900d3e5667a2136b3d671e7",
"lastUpdated": "2023-05-25T09:21:27", "lastUpdated": "2023-05-25T09:21:27",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-0.1B-v1-20230520-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth", "name": "RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth",
@@ -65,7 +152,12 @@
"SHA256": "dbd5302cbee596bbc900f97eb10b2af3001a7f2c7e4d8643bf8683b2cdbdd324", "SHA256": "dbd5302cbee596bbc900f97eb10b2af3001a7f2c7e4d8643bf8683b2cdbdd324",
"lastUpdated": "2023-06-18T10:46:50", "lastUpdated": "2023-06-18T10:46:50",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-0.4B-v1-20230618-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "RWKV-4-World-0.4B-v1-20230529-ctx4096.pth", "name": "RWKV-4-World-0.4B-v1-20230529-ctx4096.pth",
@@ -78,7 +170,12 @@
"SHA256": "4b4a2733cf5e5dc97dd62106f391d99895d16b11c5ccd10c89f28c52067a4919", "SHA256": "4b4a2733cf5e5dc97dd62106f391d99895d16b11c5ccd10c89f28c52067a4919",
"lastUpdated": "2023-05-29T13:25:53", "lastUpdated": "2023-05-29T13:25:53",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-0.4B-v1-20230529-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-0.4B-v1-20230529-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-0.4B-v1-20230529-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-0.4B-v1-20230529-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth", "name": "RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth",
@@ -91,7 +188,12 @@
"SHA256": "9f31f2ed5fe52dcf2d50208eb2efd764b9674dba2adb1baeff61997b4390a26b", "SHA256": "9f31f2ed5fe52dcf2d50208eb2efd764b9674dba2adb1baeff61997b4390a26b",
"lastUpdated": "2023-06-20T06:35:37", "lastUpdated": "2023-06-20T06:35:37",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-1.5B-v1-20230620-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "RWKV-4-World-1.5B-v1-OnlyForTest_57%_trained-20230529-ctx4096.pth", "name": "RWKV-4-World-1.5B-v1-OnlyForTest_57%_trained-20230529-ctx4096.pth",
@@ -130,6 +232,11 @@
"lastUpdated": "2023-06-07T09:33:32", "lastUpdated": "2023-06-07T09:33:32",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-1.5B-v1-20230607-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-1.5B-v1-20230607-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-1.5B-v1-20230607-ctx4096.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-1.5B-v1-20230607-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
],
"hide": true "hide": true
}, },
{ {
@@ -143,7 +250,12 @@
"SHA256": "71f0c3229f9227cbcb8ae5fee6461197129a57e26366c4d23a49058417b046c9", "SHA256": "71f0c3229f9227cbcb8ae5fee6461197129a57e26366c4d23a49058417b046c9",
"lastUpdated": "2023-06-12T06:31:32", "lastUpdated": "2023-06-12T06:31:32",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-1.5B-v1-fixed-20230612-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-3B-v1-OnlyForTest_35%_trained-20230529-ctx4096.pth", "name": "RWKV-4-World-3B-v1-OnlyForTest_35%_trained-20230529-ctx4096.pth",
@@ -208,7 +320,12 @@
"SHA256": "1b227af317fa25b6939ab3c7cd321226ca48b8fe4bbbd2df3db669f1482c54ba", "SHA256": "1b227af317fa25b6939ab3c7cd321226ca48b8fe4bbbd2df3db669f1482c54ba",
"lastUpdated": "2023-06-20T03:00:51", "lastUpdated": "2023-06-20T03:00:51",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-3B-v1-20230619-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-3B-v1-20230619-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-3B-v1-20230619-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-3B-v1-20230619-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth", "name": "RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth",
@@ -221,7 +338,12 @@
"SHA256": "7d3b5a4d0e9780a3e3d9ae7c2defbe8564d240bc9a238db4ba70cfb66dc33888", "SHA256": "7d3b5a4d0e9780a3e3d9ae7c2defbe8564d240bc9a238db4ba70cfb66dc33888",
"lastUpdated": "2023-06-25T14:53:27", "lastUpdated": "2023-06-25T14:53:27",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-3B-v1-20230625-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "RWKV-4-World-7B-v1-OnlyForTest_30%_trained-20230529-ctx4096.pth", "name": "RWKV-4-World-7B-v1-OnlyForTest_30%_trained-20230529-ctx4096.pth",
@@ -312,7 +434,12 @@
"SHA256": "db7b011247a0fe4389e1d76e3d6a904185f85d509c8a44ad18bf401094efc293", "SHA256": "db7b011247a0fe4389e1d76e3d6a904185f85d509c8a44ad18bf401094efc293",
"lastUpdated": "2023-06-26T16:40:04", "lastUpdated": "2023-06-26T16:40:04",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-7B-v1-20230626-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-7B-v1-20230626-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-7B-v1-20230626-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-7B-v1-20230626-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-claude-4-World-7B-20230805-ctx65k.pth", "name": "RWKV-claude-4-World-7B-20230805-ctx65k.pth",
@@ -325,7 +452,12 @@
"SHA256": "8cd25f8a1ab58965993cc47b3b2f99585836eed008a2e44526c258189ea751a6", "SHA256": "8cd25f8a1ab58965993cc47b3b2f99585836eed008a2e44526c258189ea751a6",
"lastUpdated": "2023-08-05T08:52:20", "lastUpdated": "2023-08-05T08:52:20",
"url": "https://huggingface.co/xiaol/RWKV-claude-4-World-7B-65k/blob/main/RWKV-claude-4-World-7B-20230805-ctx65k.pth", "url": "https://huggingface.co/xiaol/RWKV-claude-4-World-7B-65k/blob/main/RWKV-claude-4-World-7B-20230805-ctx65k.pth",
"downloadUrl": "https://huggingface.co/xiaol/RWKV-claude-4-World-7B-65k/resolve/main/RWKV-claude-4-World-7B-20230805-ctx65k.pth" "downloadUrl": "https://huggingface.co/xiaol/RWKV-claude-4-World-7B-65k/resolve/main/RWKV-claude-4-World-7B-20230805-ctx65k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth", "name": "RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth",
@@ -338,7 +470,12 @@
"SHA256": "648a3b21055bdab77021ce278da80fbada8dcaae0b3d41d1eca9aa194c1fd25f", "SHA256": "648a3b21055bdab77021ce278da80fbada8dcaae0b3d41d1eca9aa194c1fd25f",
"lastUpdated": "2023-08-15T07:18:23", "lastUpdated": "2023-08-15T07:18:23",
"url": "https://huggingface.co/xiaol/RWKV-toolformer-translation-japanese-chinese-english-7B-World-128k/blob/main/RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth", "url": "https://huggingface.co/xiaol/RWKV-toolformer-translation-japanese-chinese-english-7B-World-128k/blob/main/RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth",
"downloadUrl": "https://huggingface.co/xiaol/RWKV-toolformer-translation-japanese-chinese-english-7B-World-128k/resolve/main/RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth" "downloadUrl": "https://huggingface.co/xiaol/RWKV-toolformer-translation-japanese-chinese-english-7B-World-128k/resolve/main/RWKV-toolformer-translation-japanese-chinese-english-7B-World-20230815-ctx128k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-code-4-World-7B-20230820-ctx32k.pth", "name": "RWKV-code-4-World-7B-20230820-ctx32k.pth",
@@ -351,7 +488,12 @@
"SHA256": "19666620437ae3a5fb06e16a52729d67e449fca155fab3d5861ffe9ecf247404", "SHA256": "19666620437ae3a5fb06e16a52729d67e449fca155fab3d5861ffe9ecf247404",
"lastUpdated": "2023-08-20T05:00:17", "lastUpdated": "2023-08-20T05:00:17",
"url": "https://huggingface.co/xiaol/RWKV-Code-7B-world-32k/blob/main/RWKV-code-4-World-7B-20230820-ctx32k.pth", "url": "https://huggingface.co/xiaol/RWKV-Code-7B-world-32k/blob/main/RWKV-code-4-World-7B-20230820-ctx32k.pth",
"downloadUrl": "https://huggingface.co/xiaol/RWKV-Code-7B-world-32k/resolve/main/RWKV-code-4-World-7B-20230820-ctx32k.pth" "downloadUrl": "https://huggingface.co/xiaol/RWKV-Code-7B-world-32k/resolve/main/RWKV-code-4-World-7B-20230820-ctx32k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "wizard-rwkv-4-world-ctx32k.pth", "name": "wizard-rwkv-4-world-ctx32k.pth",
@@ -364,7 +506,12 @@
"SHA256": "c5d991f315a1676d4bed93dd91f803b1376096e7a4af5bf72b339d055f53bac7", "SHA256": "c5d991f315a1676d4bed93dd91f803b1376096e7a4af5bf72b339d055f53bac7",
"lastUpdated": "2023-07-29T03:21:47", "lastUpdated": "2023-07-29T03:21:47",
"url": "https://huggingface.co/xiaol/wizard-rwkv-world-7B-ctx32k/blob/main/wizard-rwkv-4-world-ctx32k.pth", "url": "https://huggingface.co/xiaol/wizard-rwkv-world-7B-ctx32k/blob/main/wizard-rwkv-4-world-ctx32k.pth",
"downloadUrl": "https://huggingface.co/xiaol/wizard-rwkv-world-7B-ctx32k/resolve/main/wizard-rwkv-4-world-ctx32k.pth" "downloadUrl": "https://huggingface.co/xiaol/wizard-rwkv-world-7B-ctx32k/resolve/main/wizard-rwkv-4-world-ctx32k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth", "name": "RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth",
@@ -377,7 +524,12 @@
"SHA256": "52d33e8352a40158d21425fee4f68df1515d6324056f788d2c78a366ef578ffa", "SHA256": "52d33e8352a40158d21425fee4f68df1515d6324056f788d2c78a366ef578ffa",
"lastUpdated": "2023-07-09T18:23:33", "lastUpdated": "2023-07-09T18:23:33",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-CHNtuned-7B-v1-20230709-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth", "name": "Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
@@ -390,7 +542,12 @@
"SHA256": "1bd1de8cdbd56b67e1374588fe5d202884049c71278ffcb12f5c4efbdb422ee1", "SHA256": "1bd1de8cdbd56b67e1374588fe5d202884049c71278ffcb12f5c4efbdb422ee1",
"lastUpdated": "2023-07-20T06:11:29", "lastUpdated": "2023-07-20T06:11:29",
"url": "https://huggingface.co/xiaol/readflow-rwkv-4-world-ctx32k/blob/main/Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth", "url": "https://huggingface.co/xiaol/readflow-rwkv-4-world-ctx32k/blob/main/Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
"downloadUrl": "https://huggingface.co/xiaol/readflow-rwkv-4-world-ctx32k/resolve/main/Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth" "downloadUrl": "https://huggingface.co/xiaol/readflow-rwkv-4-world-ctx32k/resolve/main/Readflow-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth", "name": "novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
@@ -403,7 +560,12 @@
"SHA256": "0fe2415ce61af52a8c38c071b475c01b4c9f8a4f2b4aaed6181f0334f3faf7f4", "SHA256": "0fe2415ce61af52a8c38c071b475c01b4c9f8a4f2b4aaed6181f0334f3faf7f4",
"lastUpdated": "2023-07-28T13:30:59", "lastUpdated": "2023-07-28T13:30:59",
"url": "https://huggingface.co/xiaol/ruotangwx-rwkv-7b-novel-32k/blob/main/novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth", "url": "https://huggingface.co/xiaol/ruotangwx-rwkv-7b-novel-32k/blob/main/novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
"downloadUrl": "https://huggingface.co/xiaol/ruotangwx-rwkv-7b-novel-32k/resolve/main/novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth" "downloadUrl": "https://huggingface.co/xiaol/ruotangwx-rwkv-7b-novel-32k/resolve/main/novel-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth", "name": "chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth",
@@ -416,7 +578,12 @@
"SHA256": "aaed29cfd1bddee47c48f564aa800eb001f62fd03290d772647d5678e40d66e8", "SHA256": "aaed29cfd1bddee47c48f564aa800eb001f62fd03290d772647d5678e40d66e8",
"lastUpdated": "2023-07-21T08:59:18", "lastUpdated": "2023-07-21T08:59:18",
"url": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/blob/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth", "url": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/blob/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth",
"downloadUrl": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/resolve/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth" "downloadUrl": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/resolve/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-1000.pth",
"tags": [
"Finetuned",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth", "name": "chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth",
@@ -429,7 +596,12 @@
"SHA256": "b5d347d5dedb4f398ec31489ab87b75b1dee772ae7d0a34c26635cf5d95c8794", "SHA256": "b5d347d5dedb4f398ec31489ab87b75b1dee772ae7d0a34c26635cf5d95c8794",
"lastUpdated": "2023-07-21T07:31:05", "lastUpdated": "2023-07-21T07:31:05",
"url": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/blob/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth", "url": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/blob/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth",
"downloadUrl": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/resolve/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth" "downloadUrl": "https://huggingface.co/xiaol/chatgal-rwkv-7b-world-32k/resolve/main/chatgal-RWKV-4-World-CHNtuned-7B-v1-20230709-ctx32k-500.pth",
"tags": [
"Finetuned",
"RWKV-4",
"CN"
]
}, },
{ {
"name": "RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth", "name": "RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth",
@@ -442,7 +614,12 @@
"SHA256": "3e4c7664ce893ac1f6bb59cd76664fb5c872cb076bb82dbd534db0555b6e9fa5", "SHA256": "3e4c7664ce893ac1f6bb59cd76664fb5c872cb076bb82dbd534db0555b6e9fa5",
"lastUpdated": "2023-07-18T20:01:12", "lastUpdated": "2023-07-18T20:01:12",
"url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-world/blob/main/RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-world/resolve/main/RWKV-4-World-JPNtuned-7B-v1-20230718-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"JP"
]
}, },
{ {
"name": "RWKV-novel-4-World-7B-20230810-ctx128k.pth", "name": "RWKV-novel-4-World-7B-20230810-ctx128k.pth",
@@ -455,7 +632,12 @@
"SHA256": "5e429c49e4cab2f29a93f87a80635422c8710d70e5b1d962c078e47d957389c8", "SHA256": "5e429c49e4cab2f29a93f87a80635422c8710d70e5b1d962c078e47d957389c8",
"lastUpdated": "2023-08-10T06:30:32", "lastUpdated": "2023-08-10T06:30:32",
"url": "https://huggingface.co/xiaol/rwkv-7B-world-novel-128k/blob/main/RWKV-novel-4-World-7B-20230810-ctx128k.pth", "url": "https://huggingface.co/xiaol/rwkv-7B-world-novel-128k/blob/main/RWKV-novel-4-World-7B-20230810-ctx128k.pth",
"downloadUrl": "https://huggingface.co/xiaol/rwkv-7B-world-novel-128k/resolve/main/RWKV-novel-4-World-7B-20230810-ctx128k.pth" "downloadUrl": "https://huggingface.co/xiaol/rwkv-7B-world-novel-128k/resolve/main/RWKV-novel-4-World-7B-20230810-ctx128k.pth",
"tags": [
"Finetuned",
"RWKV-4",
"Global"
]
}, },
{ {
"name": "RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth", "name": "RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth",
@@ -467,7 +649,13 @@
"SHA256": "cd40b661930dea46c0f930c51d99cef6b484fe3d641388981dee5a0c68e2b1c7", "SHA256": "cd40b661930dea46c0f930c51d99cef6b484fe3d641388981dee5a0c68e2b1c7",
"lastUpdated": "2023-04-10T13:55:52", "lastUpdated": "2023-04-10T13:55:52",
"url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-ChnEng-ChnPro-20230410-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"Global"
]
}, },
{ {
"name": "RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth", "name": "RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth",
@@ -479,7 +667,13 @@
"SHA256": "283c6e6fa10c52a93e9a01d9630f288473267ea152a49c6579b5c0427bdc9c61", "SHA256": "283c6e6fa10c52a93e9a01d9630f288473267ea152a49c6579b5c0427bdc9c61",
"lastUpdated": "2023-04-12T13:18:29", "lastUpdated": "2023-04-12T13:18:29",
"url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-3B-v1-ChnEng-20230412-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"Global"
]
}, },
{ {
"name": "RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth", "name": "RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth",
@@ -491,7 +685,13 @@
"SHA256": "bd08c75a296bd193dcfadb993fe06d7f9dd91ca3385231f24c592c89d25cd596", "SHA256": "bd08c75a296bd193dcfadb993fe06d7f9dd91ca3385231f24c592c89d25cd596",
"lastUpdated": "2023-04-26T18:57:01", "lastUpdated": "2023-04-26T18:57:01",
"url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-ChnEng-20230426-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"Global"
]
}, },
{ {
"name": "RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth", "name": "RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth",
@@ -503,7 +703,13 @@
"SHA256": "c41e0af2cbc66e94121377680e8224a1504fac6c9ea620c395f0a79281db26e7", "SHA256": "c41e0af2cbc66e94121377680e8224a1504fac6c9ea620c395f0a79281db26e7",
"lastUpdated": "2023-04-12T13:18:29", "lastUpdated": "2023-04-12T13:18:29",
"url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-3B-v1-Chn-20230412-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"Global"
]
}, },
{ {
"name": "RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth", "name": "RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth",
@@ -515,7 +721,13 @@
"SHA256": "5fced44febdf80d303250eef9c020f087abded43aaecc8caaea8a9e7f1fb771e", "SHA256": "5fced44febdf80d303250eef9c020f087abded43aaecc8caaea8a9e7f1fb771e",
"lastUpdated": "2023-04-26T18:57:01", "lastUpdated": "2023-04-26T18:57:01",
"url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-novel/blob/main/RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-novel/resolve/main/RWKV-4-Novel-7B-v1-Chn-20230426-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"Global"
]
}, },
{ {
"name": "RWKV-4-Raven-1B5-v11-Eng99%-Other1%-20230425-ctx4096.pth", "name": "RWKV-4-Raven-1B5-v11-Eng99%-Other1%-20230425-ctx4096.pth",
@@ -528,6 +740,11 @@
"lastUpdated": "2023-04-26T14:27:55", "lastUpdated": "2023-04-26T14:27:55",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-1B5-v11-Eng99%25-Other1%25-20230425-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-1B5-v11-Eng99%25-Other1%25-20230425-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-1B5-v11-Eng99%25-Other1%25-20230425-ctx4096.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-1B5-v11-Eng99%25-Other1%25-20230425-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
],
"hide": true "hide": true
}, },
{ {
@@ -540,7 +757,12 @@
"SHA256": "6bbbffb3ee2372dfa9ef49c599e9a2bc0a01b94b6a264ba9bf5bd524fc38f723", "SHA256": "6bbbffb3ee2372dfa9ef49c599e9a2bc0a01b94b6a264ba9bf5bd524fc38f723",
"lastUpdated": "2023-05-21T07:08:56", "lastUpdated": "2023-05-21T07:08:56",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-1B5-v12-Eng98%25-Other2%25-20230520-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-1B5-v12-Eng98%25-Other2%25-20230520-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-1B5-v12-Eng98%25-Other2%25-20230520-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-1B5-v12-Eng98%25-Other2%25-20230520-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
]
}, },
{ {
"name": "RWKV-4-Raven-3B-v11-Eng99%-Other1%-20230425-ctx4096.pth", "name": "RWKV-4-Raven-3B-v11-Eng99%-Other1%-20230425-ctx4096.pth",
@@ -553,6 +775,11 @@
"lastUpdated": "2023-04-26T14:27:55", "lastUpdated": "2023-04-26T14:27:55",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v11-Eng99%25-Other1%25-20230425-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v11-Eng99%25-Other1%25-20230425-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v11-Eng99%25-Other1%25-20230425-ctx4096.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v11-Eng99%25-Other1%25-20230425-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
],
"hide": true "hide": true
}, },
{ {
@@ -565,7 +792,12 @@
"SHA256": "1eea1845acfe9729dfdaec66a8d1aeb91a1287d94bebbca5529c13c050540b33", "SHA256": "1eea1845acfe9729dfdaec66a8d1aeb91a1287d94bebbca5529c13c050540b33",
"lastUpdated": "2023-05-21T07:13:25", "lastUpdated": "2023-05-21T07:13:25",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v12-Eng98%25-Other2%25-20230520-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v12-Eng98%25-Other2%25-20230520-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v12-Eng98%25-Other2%25-20230520-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v12-Eng98%25-Other2%25-20230520-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
]
}, },
{ {
"name": "RWKV-4-Raven-3B-v11-Eng49%-Chn49%-Jpn1%-Other1%-20230429-ctx4096.pth", "name": "RWKV-4-Raven-3B-v11-Eng49%-Chn49%-Jpn1%-Other1%-20230429-ctx4096.pth",
@@ -578,6 +810,12 @@
"lastUpdated": "2023-04-29T11:51:51", "lastUpdated": "2023-04-29T11:51:51",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230429-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230429-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230429-ctx4096.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230429-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"CN"
],
"hide": true "hide": true
}, },
{ {
@@ -590,7 +828,13 @@
"SHA256": "c0abb4b745ba3523b9d8b3e1293110867ee55b1ef3dc8c122212f78396755721", "SHA256": "c0abb4b745ba3523b9d8b3e1293110867ee55b1ef3dc8c122212f78396755721",
"lastUpdated": "2023-05-28T11:51:12", "lastUpdated": "2023-05-28T11:51:12",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230527-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-3B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230527-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230527-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-3B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230527-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"CN"
]
}, },
{ {
"name": "RWKV-4-Raven-7B-v11x-Eng99%-Other1%-20230429-ctx8192.pth", "name": "RWKV-4-Raven-7B-v11x-Eng99%-Other1%-20230429-ctx8192.pth",
@@ -603,6 +847,11 @@
"lastUpdated": "2023-04-29T11:44:32", "lastUpdated": "2023-04-29T11:44:32",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v11x-Eng99%25-Other1%25-20230429-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v11x-Eng99%25-Other1%25-20230429-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v11x-Eng99%25-Other1%25-20230429-ctx8192.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v11x-Eng99%25-Other1%25-20230429-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
],
"hide": true "hide": true
}, },
{ {
@@ -615,7 +864,12 @@
"SHA256": "5a725eaeb9e09b724de6c97e6845dd0283097c7920acd05b46852ab7afa9ec32", "SHA256": "5a725eaeb9e09b724de6c97e6845dd0283097c7920acd05b46852ab7afa9ec32",
"lastUpdated": "2023-05-22T10:32:17", "lastUpdated": "2023-05-22T10:32:17",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v12-Eng98%25-Other2%25-20230521-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v12-Eng98%25-Other2%25-20230521-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v12-Eng98%25-Other2%25-20230521-ctx8192.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v12-Eng98%25-Other2%25-20230521-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
]
}, },
{ {
"name": "RWKV-4-Raven-7B-v10x-Eng49%-Chn50%-Other1%-20230423-ctx4096.pth", "name": "RWKV-4-Raven-7B-v10x-Eng49%-Chn50%-Other1%-20230423-ctx4096.pth",
@@ -628,6 +882,12 @@
"lastUpdated": "2023-04-24T07:48:55", "lastUpdated": "2023-04-24T07:48:55",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v10x-Eng49%25-Chn50%25-Other1%25-20230423-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v10x-Eng49%25-Chn50%25-Other1%25-20230423-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v10x-Eng49%25-Chn50%25-Other1%25-20230423-ctx4096.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v10x-Eng49%25-Chn50%25-Other1%25-20230423-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"CN"
],
"hide": true "hide": true
}, },
{ {
@@ -641,6 +901,12 @@
"lastUpdated": "2023-04-30T14:35:59", "lastUpdated": "2023-04-30T14:35:59",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230430-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230430-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230430-ctx8192.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v11-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230430-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"CN"
],
"hide": true "hide": true
}, },
{ {
@@ -653,7 +919,13 @@
"SHA256": "6d4a089ff36d5d9d96b669d425fc5e4e3959cab426535b52e2364df08f58b407", "SHA256": "6d4a089ff36d5d9d96b669d425fc5e4e3959cab426535b52e2364df08f58b407",
"lastUpdated": "2023-05-30T23:16:12", "lastUpdated": "2023-05-30T23:16:12",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230530-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-7B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230530-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230530-ctx8192.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-7B-v12-Eng49%25-Chn49%25-Jpn1%25-Other1%25-20230530-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven",
"CN"
]
}, },
{ {
"name": "RWKV-4-Raven-14B-v11x-Eng99%-Other1%-20230501-ctx8192.pth", "name": "RWKV-4-Raven-14B-v11x-Eng99%-Other1%-20230501-ctx8192.pth",
@@ -666,6 +938,11 @@
"lastUpdated": "2023-05-02T09:43:33", "lastUpdated": "2023-05-02T09:43:33",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-14B-v11x-Eng99%25-Other1%25-20230501-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-14B-v11x-Eng99%25-Other1%25-20230501-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-14B-v11x-Eng99%25-Other1%25-20230501-ctx8192.pth", "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-14B-v11x-Eng99%25-Other1%25-20230501-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
],
"hide": true "hide": true
}, },
{ {
@@ -678,7 +955,12 @@
"SHA256": "1193b5a9ceab572e4dbb9ed1d798eab7bf4793d18904d08bd4bf183579338ae7", "SHA256": "1193b5a9ceab572e4dbb9ed1d798eab7bf4793d18904d08bd4bf183579338ae7",
"lastUpdated": "2023-05-23T11:22:41", "lastUpdated": "2023-05-23T11:22:41",
"url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-14B-v12-Eng98%25-Other2%25-20230523-ctx8192.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-raven/blob/main/RWKV-4-Raven-14B-v12-Eng98%25-Other2%25-20230523-ctx8192.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-14B-v12-Eng98%25-Other2%25-20230523-ctx8192.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-raven/resolve/main/RWKV-4-Raven-14B-v12-Eng98%25-Other2%25-20230523-ctx8192.pth",
"tags": [
"Main",
"RWKV-4",
"Raven"
]
}, },
{ {
"name": "RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth", "name": "RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth",
@@ -691,7 +973,12 @@
"SHA256": "161d27dcf50d0958d230601ba1e0f8e7dd9c236105e92d2b833496412ace430c", "SHA256": "161d27dcf50d0958d230601ba1e0f8e7dd9c236105e92d2b833496412ace430c",
"lastUpdated": "2023-07-15T08:03:36", "lastUpdated": "2023-07-15T08:03:36",
"url": "https://huggingface.co/BlinkDL/rwkv-4-music/blob/main/RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-music/blob/main/RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-music/resolve/main/RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-music/resolve/main/RWKV-4-MIDI-120M-v1-20230714-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Music"
]
}, },
{ {
"name": "RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth", "name": "RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth",
@@ -704,7 +991,12 @@
"SHA256": "62b21841b24af38ef176e9e9d895d9fff730cea8aa0623f53a1784d74ce828d6", "SHA256": "62b21841b24af38ef176e9e9d895d9fff730cea8aa0623f53a1784d74ce828d6",
"lastUpdated": "2023-07-17T15:02:08", "lastUpdated": "2023-07-17T15:02:08",
"url": "https://huggingface.co/BlinkDL/rwkv-4-music/blob/main/RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-4-music/blob/main/RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-music/resolve/main/RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-4-music/resolve/main/RWKV-4-MIDI-560M-v1-20230717-ctx4096.pth",
"tags": [
"Main",
"RWKV-4",
"Music"
]
}, },
{ {
"name": "RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth", "name": "RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth",
@@ -717,7 +1009,12 @@
"SHA256": "c43d4a2ee7a71a331d05d6cd818dd75f7c48c716e4b98c58e4d27231614b0144", "SHA256": "c43d4a2ee7a71a331d05d6cd818dd75f7c48c716e4b98c58e4d27231614b0144",
"lastUpdated": "2023-07-29T02:17:27", "lastUpdated": "2023-07-29T02:17:27",
"url": "https://huggingface.co/BlinkDL/rwkv-5-music/blob/main/RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-5-music/blob/main/RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-music/resolve/main/RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-music/resolve/main/RWKV-5-MIDI-120M-v1-20230728-ctx4096.pth",
"tags": [
"Main",
"RWKV-5",
"Music"
]
}, },
{ {
"name": "RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth", "name": "RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth",
@@ -730,7 +1027,12 @@
"SHA256": "cb4f2fd8956ca8496d6b2e33bff290c2047759b6fe74884903dbf9c73a11cc77", "SHA256": "cb4f2fd8956ca8496d6b2e33bff290c2047759b6fe74884903dbf9c73a11cc77",
"lastUpdated": "2023-09-03T04:48:41", "lastUpdated": "2023-09-03T04:48:41",
"url": "https://huggingface.co/BlinkDL/rwkv-5-music/blob/main/RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth", "url": "https://huggingface.co/BlinkDL/rwkv-5-music/blob/main/RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth",
"downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-music/resolve/main/RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth" "downloadUrl": "https://huggingface.co/BlinkDL/rwkv-5-music/resolve/main/RWKV-5-MIDI-560M-v1-20230902-ctx4096.pth",
"tags": [
"Main",
"RWKV-5",
"Music"
]
} }
] ]
} }