chore & auto dep

This commit is contained in:
josc146 2023-05-20 23:34:33 +08:00
parent 9076ff3fd7
commit b8f7582513
15 changed files with 291 additions and 53 deletions

3
.gitignore vendored
View File

@ -10,3 +10,6 @@ __pycache__
/cache.json
/frontend/stats.html
/frontend/package.json.md5
/backend-python/get-pip.py
/py310
*.zip

View File

@ -5,9 +5,10 @@ import (
"net/http"
"os"
"os/exec"
"runtime"
"github.com/minio/selfupdate"
"github.com/wailsapp/wails/v2/pkg/runtime"
wruntime "github.com/wailsapp/wails/v2/pkg/runtime"
)
// App struct
@ -46,6 +47,10 @@ func (a *App) UpdateApp(url string) (broken bool, err error) {
return false, err
}
exec.Command(name, os.Args[1:]...).Start()
runtime.Quit(a.ctx)
wruntime.Quit(a.ctx)
return false, nil
}
func (a *App) GetPlatform() string {
return runtime.GOOS
}

View File

@ -1,28 +1,52 @@
package backend_golang
import (
"errors"
"os/exec"
"path/filepath"
"strconv"
)
func cmd(args ...string) (string, error) {
cmdHelper, err := filepath.Abs("./cmd-helper")
if err != nil {
return "", err
}
cmd := exec.Command(cmdHelper, args...)
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
return string(out), nil
}
func (a *App) StartServer(port int) (string, error) {
return cmd("python", "./backend-python/main.py", strconv.Itoa(port))
python, err := GetPython()
if err != nil {
return "", err
}
return Cmd(python, "./backend-python/main.py", strconv.Itoa(port))
}
func (a *App) ConvertModel(modelPath string, strategy string, outPath string) (string, error) {
return cmd("python", "./backend-python/convert_model.py", "--in", modelPath, "--out", outPath, "--strategy", strategy)
python, err := GetPython()
if err != nil {
return "", err
}
return Cmd(python, "./backend-python/convert_model.py", "--in", modelPath, "--out", outPath, "--strategy", strategy)
}
func (a *App) DepCheck() error {
python, err := GetPython()
if err != nil {
return err
}
out, err := exec.Command(python, "./backend-python/dep_check.py").CombinedOutput()
if err != nil {
return errors.New("DepCheck Error: " + string(out))
}
return nil
}
func (a *App) InstallPyDep() (string, error) {
python, err := GetPython()
if err != nil {
return "", err
}
_, err = Cmd(python, "./backend-python/get-pip.py")
if err != nil {
return "", err
}
ChangeFileLine("./py310/python310._pth", 3, "Lib\\site-packages")
_, err = Cmd(python, "-m", "pip", "install", "torch", "torchvision", "torchaudio", "--index-url", "https://download.pytorch.org/whl/cu117")
if err != nil {
return "", err
}
return Cmd(python, "-m", "pip", "install", "-r", "./backend-python/requirements_versions.txt")
}

130
backend-golang/utils.go Normal file
View File

@ -0,0 +1,130 @@
package backend_golang
import (
"archive/zip"
"bufio"
"errors"
"io"
"os"
"os/exec"
"path/filepath"
"runtime"
"strings"
)
func Cmd(args ...string) (string, error) {
cmdHelper, err := filepath.Abs("./cmd-helper")
if err != nil {
return "", err
}
cmd := exec.Command(cmdHelper, args...)
out, err := cmd.CombinedOutput()
if err != nil {
return "", err
}
return string(out), nil
}
func GetPython() (string, error) {
switch platform := runtime.GOOS; platform {
case "windows":
_, err := os.Stat("py310/python.exe")
if err != nil {
_, err := os.Stat("python-3.10.11-embed-amd64.zip")
if err != nil {
return "", errors.New("python zip not found")
} else {
err := Unzip("python-3.10.11-embed-amd64.zip", "py310")
if err != nil {
return "", errors.New("failed to unzip python")
} else {
return "py310/python.exe", nil
}
}
} else {
return "py310/python.exe", nil
}
case "darwin":
return "python3", nil
case "linux":
return "python3", nil
}
return "", errors.New("unsupported OS")
}
func ChangeFileLine(filePath string, lineNumber int, newText string) error {
file, err := os.OpenFile(filePath, os.O_RDWR, 0644)
if err != nil {
return err
}
defer file.Close()
scanner := bufio.NewScanner(file)
content := make([]string, 0)
for scanner.Scan() {
content = append(content, scanner.Text())
}
content[lineNumber-1] = newText
newContent := strings.Join(content, "\n")
err = file.Truncate(0)
if err != nil {
return err
}
_, err = file.Seek(0, io.SeekStart)
if err != nil {
return err
}
_, err = file.WriteString(newContent)
if err != nil {
return err
}
return nil
}
// https://gist.github.com/paulerickson/6d8650947ee4e3f3dbcc28fde10eaae7
func Unzip(source, destination string) error {
archive, err := zip.OpenReader(source)
if err != nil {
return err
}
defer archive.Close()
for _, file := range archive.Reader.File {
reader, err := file.Open()
if err != nil {
return err
}
defer reader.Close()
path := filepath.Join(destination, file.Name)
// Remove file if it already exists; no problem if it doesn't; other cases can error out below
_ = os.Remove(path)
// Create a directory at path, including parents
err = os.MkdirAll(path, os.ModePerm)
if err != nil {
return err
}
// If file is _supposed_ to be a directory, we're done
if file.FileInfo().IsDir() {
continue
}
// otherwise, remove that directory (_not_ including parents)
err = os.Remove(path)
if err != nil {
return err
}
// and create the actual file. This ensures that the parent directories exist!
// An archive may have a single file with a nested path, rather than a file for each parent dir
writer, err := os.OpenFile(path, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, file.Mode())
if err != nil {
return err
}
defer writer.Close()
_, err = io.Copy(writer, reader)
if err != nil {
return err
}
}
return nil
}

View File

@ -1,7 +1,9 @@
import os
import psutil
import sys
sys.path.append(os.path.dirname(os.path.realpath(__file__)))
import psutil
from fastapi import FastAPI
from fastapi.middleware.cors import CORSMiddleware
import uvicorn

View File

@ -90,5 +90,8 @@
"Continue": "继续",
"Check": "查看",
"Model file not found": "模型文件不存在",
"Can not find download url": "找不到下载地址"
"Can not find download url": "找不到下载地址",
"Python target not found, would you like to download it?": "没有找到目标Python, 是否下载?",
"Python dependencies are incomplete, would you like to install them?": "Python依赖缺失, 是否安装?",
"Install": "安装"
}

View File

@ -1,12 +1,12 @@
import React, {FC, MouseEventHandler, ReactElement} from 'react';
import commonStore, {ModelStatus} from '../stores/commonStore';
import {AddToDownloadList, FileExists, StartServer} from '../../wailsjs/go/backend_golang/App';
import {AddToDownloadList, DepCheck, FileExists, InstallPyDep, StartServer} from '../../wailsjs/go/backend_golang/App';
import {Button} from '@fluentui/react-components';
import {observer} from 'mobx-react-lite';
import {exit, readRoot, switchModel, updateConfig} from '../apis';
import {toast} from 'react-toastify';
import manifest from '../../../manifest.json';
import {getStrategy, toastWithButton} from '../utils';
import {getStrategy, saveCache, toastWithButton} from '../utils';
import {useTranslation} from 'react-i18next';
import {ToolTipButton} from './ToolTipButton';
import {Play16Regular, Stop16Regular} from '@fluentui/react-icons';
@ -39,6 +39,32 @@ export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean
const modelName = modelConfig.modelParameters.modelName;
const modelPath = `./${manifest.localModelDir}/${modelName}`;
if (!commonStore.depComplete) {
let depErrorMsg = '';
await DepCheck().catch((e) => {
depErrorMsg = e.message || e;
if (depErrorMsg === 'python zip not found') {
toastWithButton(t('Python target not found, would you like to download it?'), t('Download'), () => {
toastWithButton(`${t('Downloading')} Python`, t('Check'), () => {
navigate({pathname: '/downloads'});
}, {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');
});
} else if (depErrorMsg.includes('DepCheck Error')) {
toastWithButton(t('Python dependencies are incomplete, would you like to install them?'), t('Install'), () => {
InstallPyDep();
});
} else {
toast(depErrorMsg, {type: 'error'});
}
});
if (depErrorMsg) {
return;
}
commonStore.setDepComplete(true);
saveCache();
}
if (!await FileExists(modelPath)) {
toastWithButton(t('Model file not found'), t('Download'), () => {
const downloadUrl = commonStore.modelSourceList.find(item => item.name === modelName)?.downloadUrl;

View File

@ -256,7 +256,7 @@ export const Configs: FC = observer(() => {
toast(`${t('Convert Success')} - ${newModelPath}`, {type: 'success'});
refreshLocalModels({models: commonStore.modelSourceList}, false);
}).catch(e => {
toast(`${t('Convert Failed')} - ${e}`, {type: 'error'});
toast(`${t('Convert Failed')} - ${e.message || e}`, {type: 'error'});
});
} else {
toast(`${t('Model Not Found')} - ${modelPath}`, {type: 'error'});

View File

@ -4,7 +4,7 @@ import {Page} from '../components/Page';
import {observer} from 'mobx-react-lite';
import commonStore from '../stores/commonStore';
import {Divider, Field, ProgressBar} from '@fluentui/react-components';
import {bytesToGb, bytesToMb, refreshLocalModels} from '../utils';
import {bytesToGb, bytesToKb, bytesToMb, refreshLocalModels} from '../utils';
import {ToolTipButton} from '../components/ToolTipButton';
import {Folder20Regular, Pause20Regular, Play20Regular} from '@fluentui/react-icons';
import {ContinueDownload, OpenFileFolder, PauseDownload} from '../../wailsjs/go/backend_golang/App';
@ -23,21 +23,31 @@ export type DownloadStatus = {
export const Downloads: FC = observer(() => {
const {t} = useTranslation();
const finishedDownloads = commonStore.downloadList.filter((status) => status.done).length;
const finishedModelsLen = commonStore.downloadList.filter((status) => status.done && status.name.endsWith('.pth')).length;
useEffect(() => {
if (finishedDownloads > 0)
if (finishedModelsLen > 0)
refreshLocalModels({models: commonStore.modelSourceList}, false);
console.log('finishedDownloads:', finishedDownloads);
}, [finishedDownloads]);
console.log('finishedModelsLen:', finishedModelsLen);
}, [finishedModelsLen]);
return (
<Page title={t('Downloads')} content={
<div className="flex flex-col gap-2 overflow-y-auto overflow-x-hidden p-1">
{commonStore.downloadList.map((status, index) => (
<div className="flex flex-col gap-1" key={index}>
{commonStore.downloadList.slice().reverse().map((status, index) => {
const downloadProgress = `${status.progress.toFixed(2)}%`;
const downloadSpeed = `${status.downloading ? bytesToMb(status.speed) : '0'}MB/s`;
let downloadDetails: string;
if (status.size < 1024 * 1024)
downloadDetails = `${bytesToKb(status.transferred) + 'KB'}/${bytesToKb(status.size) + 'KB'}`;
else if (status.size < 1024 * 1024 * 1024)
downloadDetails = `${bytesToMb(status.transferred) + 'MB'}/${bytesToMb(status.size) + 'MB'}`;
else
downloadDetails = `${bytesToGb(status.transferred) + 'GB'}/${bytesToGb(status.size) + 'GB'}`;
return <div className="flex flex-col gap-1" key={index}>
<Field
label={`${status.downloading ? (t('Downloading') + ': ') : ''}${status.name}`}
validationMessage={`${status.progress.toFixed(2)}% - ${bytesToGb(status.transferred) + 'GB'}/${bytesToGb(status.size) + 'GB'} - ${status.downloading ? bytesToMb(status.speed) : 0}MB/s - ${status.url}`}
validationMessage={`${downloadProgress} - ${downloadDetails} - ${downloadSpeed} - ${status.url}`}
validationState={status.done ? 'success' : 'none'}
>
<div className="flex items-center gap-2">
@ -57,8 +67,8 @@ export const Downloads: FC = observer(() => {
</div>
</Field>
<Divider style={{flexGrow: 0}}/>
</div>
))
</div>;
})
}
</div>
}/>

View File

@ -1,29 +1,28 @@
import commonStore from './stores/commonStore';
import {ReadJson} from '../wailsjs/go/backend_golang/App';
import {Cache, checkUpdate, downloadProgramFiles, LocalConfig, refreshModels} from './utils';
import {Cache, checkUpdate, downloadProgramFiles, LocalConfig, refreshModels, saveCache} from './utils';
import {getStatus} from './apis';
import {EventsOn} from '../wailsjs/runtime';
import {defaultModelConfigs} from './pages/Configs';
export async function startup() {
downloadProgramFiles();
initRemoteText();
initCache();
await initConfig();
if (commonStore.settings.autoUpdatesCheck)
checkUpdate();
getStatus(500).then(status => {
if (status)
commonStore.setModelStatus(status);
});
EventsOn('downloadList', (data) => {
if (data)
commonStore.setDownloadList(data);
});
initCache().then(initRemoteText);
await initConfig();
if (commonStore.settings.autoUpdatesCheck) // depends on config settings
checkUpdate();
getStatus(500).then(status => { // depends on config api port
if (status)
commonStore.setModelStatus(status);
});
}
async function initRemoteText() {
@ -33,7 +32,7 @@ async function initRemoteText() {
commonStore.setIntroduction(data.introduction);
if (data.about)
commonStore.setAbout(data.about);
});
}).then(saveCache);
}
async function initConfig() {
@ -61,6 +60,8 @@ async function initCache() {
commonStore.setIntroduction(cacheData.introduction);
if (cacheData.about)
commonStore.setAbout(cacheData.about);
if (cacheData.depComplete)
commonStore.setDepComplete(cacheData.depComplete);
}).catch(() => {
});
await refreshModels(false);

View File

@ -24,6 +24,7 @@ class CommonStore {
// global
modelStatus: ModelStatus = ModelStatus.Offline;
depComplete: boolean = false;
// home
introduction: IntroductionContent = manifest.introduction;
@ -130,6 +131,10 @@ class CommonStore {
this.about = value;
};
setDepComplete = (value: boolean) => {
this.depComplete = value;
};
setDownloadList = (value: DownloadStatus[]) => {
this.downloadList = value;
};

View File

@ -1,4 +1,5 @@
import {
AddToDownloadList,
DeleteFile,
DownloadFile,
FileExists,
@ -23,6 +24,7 @@ export type Cache = {
models: ModelSourceItem[]
introduction: IntroductionContent,
about: AboutContent
depComplete: boolean
}
export type LocalConfig = {
@ -153,7 +155,8 @@ export const saveCache = async () => {
const data: Cache = {
models: commonStore.modelSourceList,
introduction: commonStore.introduction,
about: commonStore.about
about: commonStore.about,
depComplete: commonStore.depComplete
};
return SaveJson('cache.json', data);
};
@ -175,7 +178,7 @@ export function downloadProgramFiles() {
manifest.programFiles.forEach(({url, path}) => {
FileExists(path).then(exists => {
if (!exists)
DownloadFile(path, url);
AddToDownloadList(path, url);
});
});
}
@ -188,7 +191,7 @@ export function forceDownloadProgramFiles() {
export function deletePythonProgramFiles() {
manifest.programFiles.forEach(({path}) => {
if (path.endsWith('.py'))
if (path.endsWith('.py') && !path.includes('get-pip.py'))
DeleteFile(path);
});
}
@ -201,6 +204,10 @@ export function bytesToMb(size: number) {
return (size / 1024 / 1024).toFixed(2);
}
export function bytesToKb(size: number) {
return (size / 1024).toFixed(2);
}
export async function checkUpdate() {
let updateUrl = '';
await fetch('https://api.github.com/repos/josstorer/RWKV-Runner/releases/latest').then((r) => {
@ -214,7 +221,7 @@ export async function checkUpdate() {
deletePythonProgramFiles();
setTimeout(() => {
UpdateApp(updateUrl).catch((e) => {
toast(t('Update Error, Please restart this program') + ' - ' + e.message, {
toast(t('Update Error, Please restart this program') + ' - ' + e.message || e, {
type: 'error',
position: 'bottom-left',
autoClose: false
@ -235,7 +242,7 @@ export async function checkUpdate() {
}
}
).catch((e) => {
toast(t('Updates Check Error') + ' - ' + e.message, {type: 'error', position: 'bottom-left'});
toast(t('Updates Check Error') + ' - ' + e.message || e, {type: 'error', position: 'bottom-left'});
});
return updateUrl;
}

View File

@ -10,10 +10,16 @@ export function ConvertModel(arg1:string,arg2:string,arg3:string):Promise<string
export function DeleteFile(arg1:string):Promise<void>;
export function DepCheck():Promise<void>;
export function DownloadFile(arg1:string,arg2:string):Promise<void>;
export function FileExists(arg1:string):Promise<boolean>;
export function GetPlatform():Promise<string>;
export function InstallPyDep():Promise<string>;
export function ListDirFiles(arg1:string):Promise<Array<backend_golang.FileInfo>>;
export function OpenFileFolder(arg1:string):Promise<void>;

View File

@ -18,6 +18,10 @@ export function DeleteFile(arg1) {
return window['go']['backend_golang']['App']['DeleteFile'](arg1);
}
export function DepCheck() {
return window['go']['backend_golang']['App']['DepCheck']();
}
export function DownloadFile(arg1, arg2) {
return window['go']['backend_golang']['App']['DownloadFile'](arg1, arg2);
}
@ -26,6 +30,14 @@ export function FileExists(arg1) {
return window['go']['backend_golang']['App']['FileExists'](arg1);
}
export function GetPlatform() {
return window['go']['backend_golang']['App']['GetPlatform']();
}
export function InstallPyDep() {
return window['go']['backend_golang']['App']['InstallPyDep']();
}
export function ListDirFiles(arg1) {
return window['go']['backend_golang']['App']['ListDirFiles'](arg1);
}

View File

@ -61,6 +61,10 @@
{
"url": "https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner/backend-python/20B_tokenizer.json",
"path": "backend-python/20B_tokenizer.json"
},
{
"url": "https://raw.githubusercontent.com/pypa/get-pip/main/public/get-pip.py",
"path": "backend-python/get-pip.py"
}
],
"models": [