+
- );
-};
+ )
+}
diff --git a/frontend/src/components/ReadButton.tsx b/frontend/src/components/ReadButton.tsx
index 2c019752..0a86b563 100644
--- a/frontend/src/components/ReadButton.tsx
+++ b/frontend/src/components/ReadButton.tsx
@@ -1,70 +1,73 @@
-import { FC, useState } from 'react';
-import { MuteIcon, UnmuteIcon } from '@primer/octicons-react';
-import { useTranslation } from 'react-i18next';
-import { ToolTipButton } from './ToolTipButton';
-import commonStore from '../stores/commonStore';
-import { observer } from 'mobx-react-lite';
+import { FC, useState } from 'react'
+import { MuteIcon, UnmuteIcon } from '@primer/octicons-react'
+import { observer } from 'mobx-react-lite'
+import { useTranslation } from 'react-i18next'
+import commonStore from '../stores/commonStore'
+import { ToolTipButton } from './ToolTipButton'
-const synth = window.speechSynthesis;
+const synth = window.speechSynthesis
export const ReadButton: FC<{
- content: string,
- inSpeaking?: boolean,
- showDelay?: number,
+ content: string
+ inSpeaking?: boolean
+ showDelay?: number
setSpeakingOuter?: (speaking: boolean) => void
-}> = observer(({
- content,
- inSpeaking = false,
- showDelay = 0,
- setSpeakingOuter
-}) => {
- const { t } = useTranslation();
- const [speaking, setSpeaking] = useState(inSpeaking);
- let lang: string = commonStore.settings.language;
- if (lang === 'dev')
- lang = 'en';
+}> = observer(
+ ({ content, inSpeaking = false, showDelay = 0, setSpeakingOuter }) => {
+ const { t } = useTranslation()
+ const [speaking, setSpeaking] = useState(inSpeaking)
+ let lang: string = commonStore.settings.language
+ if (lang === 'dev') lang = 'en'
- const setSpeakingInner = (speaking: boolean) => {
- setSpeakingOuter?.(speaking);
- setSpeaking(speaking);
- };
+ const setSpeakingInner = (speaking: boolean) => {
+ setSpeakingOuter?.(speaking)
+ setSpeaking(speaking)
+ }
- const startSpeak = () => {
- synth.cancel();
+ const startSpeak = () => {
+ synth.cancel()
- const utterance = new SpeechSynthesisUtterance(content);
- const voices = synth.getVoices();
+ const utterance = new SpeechSynthesisUtterance(content)
+ const voices = synth.getVoices()
- let voice;
- if (lang === 'en')
- voice = voices.find((v) => v.name.toLowerCase().includes('microsoft aria'));
- else if (lang === 'zh')
- 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 === navigator.language);
+ let voice
+ if (lang === 'en')
+ voice = voices.find((v) =>
+ v.name.toLowerCase().includes('microsoft aria')
+ )
+ else if (lang === 'zh')
+ 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 === navigator.language)
- Object.assign(utterance, {
- rate: 1,
- volume: 1,
- onend: () => setSpeakingInner(false),
- onerror: () => setSpeakingInner(false),
- voice: voice
- });
+ Object.assign(utterance, {
+ rate: 1,
+ volume: 1,
+ onend: () => setSpeakingInner(false),
+ onerror: () => setSpeakingInner(false),
+ voice: voice,
+ })
- synth.speak(utterance);
- setSpeakingInner(true);
- };
+ synth.speak(utterance)
+ setSpeakingInner(true)
+ }
- const stopSpeak = () => {
- synth.cancel();
- setSpeakingInner(false);
- };
+ const stopSpeak = () => {
+ synth.cancel()
+ setSpeakingInner(false)
+ }
- return (
-
:
}
- onClick={speaking ? stopSpeak : startSpeak} />
- );
-});
+ return (
+
:
}
+ onClick={speaking ? stopSpeak : startSpeak}
+ />
+ )
+ }
+)
diff --git a/frontend/src/components/ResetConfigsButton.tsx b/frontend/src/components/ResetConfigsButton.tsx
index ad9c9918..8068deb1 100644
--- a/frontend/src/components/ResetConfigsButton.tsx
+++ b/frontend/src/components/ResetConfigsButton.tsx
@@ -1,18 +1,35 @@
-import React, { FC } from 'react';
-import { DialogButton } from './DialogButton';
-import { useTranslation } from 'react-i18next';
-import { ArrowReset20Regular } from '@fluentui/react-icons';
-import commonStore from '../stores/commonStore';
+import React, { FC } from 'react'
+import { ArrowReset20Regular } from '@fluentui/react-icons'
+import { useTranslation } from 'react-i18next'
+import {
+ defaultModelConfigs,
+ defaultModelConfigsMac,
+} from '../pages/defaultConfigs'
+import commonStore from '../stores/commonStore'
+import { DialogButton } from './DialogButton'
-import { defaultModelConfigs, defaultModelConfigsMac } from '../pages/defaultConfigs';
-
-export const ResetConfigsButton: FC<{ afterConfirm?: () => void }> = ({ afterConfirm }) => {
- const { t } = useTranslation();
- return
} tooltip={t('Reset All Configs')} title={t('Reset All Configs')}
- content={t('Are you sure you want to reset all configs? This will obtain the latest preset configs, but will override your custom configs and cannot be undone.')}
- onConfirm={() => {
- commonStore.setModelConfigs(commonStore.platform !== 'darwin' ? defaultModelConfigs : defaultModelConfigsMac, false);
- commonStore.setCurrentConfigIndex(0, true);
- afterConfirm?.();
- }} />;
-};
\ No newline at end of file
+export const ResetConfigsButton: FC<{ afterConfirm?: () => void }> = ({
+ afterConfirm,
+}) => {
+ const { t } = useTranslation()
+ return (
+
}
+ tooltip={t('Reset All Configs')}
+ title={t('Reset All Configs')}
+ content={t(
+ 'Are you sure you want to reset all configs? This will obtain the latest preset configs, but will override your custom configs and cannot be undone.'
+ )}
+ onConfirm={() => {
+ commonStore.setModelConfigs(
+ commonStore.platform !== 'darwin'
+ ? defaultModelConfigs
+ : defaultModelConfigsMac,
+ false
+ )
+ commonStore.setCurrentConfigIndex(0, true)
+ afterConfirm?.()
+ }}
+ />
+ )
+}
diff --git a/frontend/src/components/RunButton.tsx b/frontend/src/components/RunButton.tsx
index 8810901c..2b92f26d 100644
--- a/frontend/src/components/RunButton.tsx
+++ b/frontend/src/components/RunButton.tsx
@@ -1,359 +1,482 @@
-import React, { FC, MouseEventHandler, ReactElement } from 'react';
-import commonStore, { ModelStatus } from '../stores/commonStore';
+import React, { FC, MouseEventHandler, ReactElement } from 'react'
+import { Button } from '@fluentui/react-components'
+import { Play16Regular, Stop16Regular } from '@fluentui/react-icons'
+import { observer } from 'mobx-react-lite'
+import { useTranslation } from 'react-i18next'
+import { useNavigate } from 'react-router'
+import { toast } from 'react-toastify'
import {
AddToDownloadList,
FileExists,
IsPortAvailable,
StartServer,
- StartWebGPUServer
-} from '../../wailsjs/go/backend_golang/App';
-import { Button } from '@fluentui/react-components';
-import { observer } from 'mobx-react-lite';
-import { exit, getStatus, readRoot, switchModel, updateConfig } from '../apis';
-import { toast } from 'react-toastify';
-import { checkDependencies, getHfDownloadUrl, getStrategy, toastWithButton } from '../utils';
-import { useTranslation } from 'react-i18next';
-import { ToolTipButton } from './ToolTipButton';
-import { Play16Regular, Stop16Regular } from '@fluentui/react-icons';
-import { useNavigate } from 'react-router';
-import { WindowShow } from '../../wailsjs/runtime';
-import { convertToGGML, convertToSt } from '../utils/convert-model';
-import { Precision } from '../types/configs';
-import { defaultCompositionABCPrompt, defaultCompositionPrompt } from '../pages/defaultConfigs';
+ StartWebGPUServer,
+} from '../../wailsjs/go/backend_golang/App'
+import { WindowShow } from '../../wailsjs/runtime'
+import { exit, getStatus, readRoot, switchModel, updateConfig } from '../apis'
+import {
+ defaultCompositionABCPrompt,
+ defaultCompositionPrompt,
+} from '../pages/defaultConfigs'
+import commonStore, { ModelStatus } from '../stores/commonStore'
+import { Precision } from '../types/configs'
+import {
+ checkDependencies,
+ getHfDownloadUrl,
+ getStrategy,
+ toastWithButton,
+} from '../utils'
+import { convertToGGML, convertToSt } from '../utils/convert-model'
+import { ToolTipButton } from './ToolTipButton'
const mainButtonText = {
[ModelStatus.Offline]: 'Run',
[ModelStatus.Starting]: 'Starting',
[ModelStatus.Loading]: 'Loading',
- [ModelStatus.Working]: 'Stop'
-};
+ [ModelStatus.Working]: 'Stop',
+}
const iconModeButtonIcon: { [modelStatus: number]: ReactElement } = {
[ModelStatus.Offline]:
,
[ModelStatus.Starting]:
,
[ModelStatus.Loading]:
,
- [ModelStatus.Working]:
-};
+ [ModelStatus.Working]:
,
+}
-export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean }>
- = observer(({
- onClickRun,
- iconMode
-}) => {
- const { t } = useTranslation();
- const navigate = useNavigate();
+export const RunButton: FC<{
+ onClickRun?: MouseEventHandler
+ iconMode?: boolean
+}> = observer(({ onClickRun, iconMode }) => {
+ const { t } = useTranslation()
+ const navigate = useNavigate()
const onClickMainButton = async () => {
if (commonStore.status.status === ModelStatus.Offline) {
- commonStore.setStatus({ status: ModelStatus.Starting });
+ commonStore.setStatus({ status: ModelStatus.Starting })
- const modelConfig = commonStore.getCurrentModelConfig();
- const webgpu = modelConfig.modelParameters.device === 'WebGPU';
- const webgpuPython = modelConfig.modelParameters.device === 'WebGPU (Python)';
- const cpp = modelConfig.modelParameters.device === 'CPU (rwkv.cpp)';
- let modelName = '';
- let modelPath = '';
+ const modelConfig = commonStore.getCurrentModelConfig()
+ const webgpu = modelConfig.modelParameters.device === 'WebGPU'
+ const webgpuPython =
+ modelConfig.modelParameters.device === 'WebGPU (Python)'
+ const cpp = modelConfig.modelParameters.device === 'CPU (rwkv.cpp)'
+ let modelName = ''
+ let modelPath = ''
if (modelConfig && modelConfig.modelParameters) {
- modelName = modelConfig.modelParameters.modelName;
- modelPath = `${commonStore.settings.customModelsPath}/${modelName}`;
+ modelName = modelConfig.modelParameters.modelName
+ modelPath = `${commonStore.settings.customModelsPath}/${modelName}`
} else {
- toast(t('Model Config Exception'), { type: 'error' });
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ toast(t('Model Config Exception'), { type: 'error' })
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
- const currentModelSource = commonStore.modelSourceList.find(item => item.name === modelName);
+ const currentModelSource = commonStore.modelSourceList.find(
+ (item) => item.name === modelName
+ )
const showDownloadPrompt = (promptInfo: string, downloadName: string) => {
toastWithButton(promptInfo, t('Download'), () => {
- const downloadUrl = currentModelSource?.downloadUrl;
+ const downloadUrl = currentModelSource?.downloadUrl
if (downloadUrl) {
- toastWithButton(`${t('Downloading')} ${downloadName}`, t('Check'), () => {
- navigate({ pathname: '/downloads' });
+ toastWithButton(
+ `${t('Downloading')} ${downloadName}`,
+ t('Check'),
+ () => {
+ navigate({ pathname: '/downloads' })
},
- { autoClose: 3000 });
- AddToDownloadList(modelPath, getHfDownloadUrl(downloadUrl));
+ { autoClose: 3000 }
+ )
+ AddToDownloadList(modelPath, getHfDownloadUrl(downloadUrl))
} else {
- toast(t('Can not find download url'), { type: 'error' });
+ toast(t('Can not find download url'), { type: 'error' })
}
- });
- };
+ })
+ }
if (webgpu || webgpuPython) {
- if (!['.st', '.safetensors'].some(ext => modelPath.endsWith(ext))) {
- const stModelPath = modelPath.replace(/\.pth$/, '.st');
+ if (!['.st', '.safetensors'].some((ext) => modelPath.endsWith(ext))) {
+ const stModelPath = modelPath.replace(/\.pth$/, '.st')
if (await FileExists(stModelPath)) {
- modelPath = stModelPath;
- } else if (!await FileExists(modelPath)) {
- showDownloadPrompt(t('Model file not found'), modelName);
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ 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;
+ showDownloadPrompt(
+ t('Model file download is not complete'),
+ modelName
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
} else {
- toastWithButton(t('Please convert model to safe tensors format first'), t('Convert'), () => {
- convertToSt(modelConfig, navigate);
- });
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ toastWithButton(
+ t('Please convert model to safe tensors format first'),
+ t('Convert'),
+ () => {
+ convertToSt(modelConfig, navigate)
+ }
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
}
}
if (!webgpu && !webgpuPython) {
- if (['.st', '.safetensors'].some(ext => modelPath.endsWith(ext))) {
- toast(t('Please change Strategy to WebGPU to use safetensors format'), { type: 'error' });
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ if (['.st', '.safetensors'].some((ext) => modelPath.endsWith(ext))) {
+ toast(
+ t('Please change Strategy to WebGPU to use safetensors format'),
+ { type: 'error' }
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
}
if (!webgpu) {
- const ok = await checkDependencies(navigate);
- if (!ok)
- return;
+ const ok = await checkDependencies(navigate)
+ if (!ok) return
}
if (cpp) {
- if (!['.bin'].some(ext => modelPath.endsWith(ext))) {
- const precision: Precision = modelConfig.modelParameters.precision === 'Q5_1' ? 'Q5_1' : 'fp16';
- const ggmlModelPath = modelPath.replace(/\.pth$/, `-${precision}.bin`);
+ if (!['.bin'].some((ext) => modelPath.endsWith(ext))) {
+ const precision: Precision =
+ modelConfig.modelParameters.precision === 'Q5_1' ? 'Q5_1' : 'fp16'
+ const ggmlModelPath = modelPath.replace(/\.pth$/, `-${precision}.bin`)
if (await FileExists(ggmlModelPath)) {
- modelPath = ggmlModelPath;
- } else if (!await FileExists(modelPath)) {
- showDownloadPrompt(t('Model file not found'), modelName);
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ modelPath = ggmlModelPath
+ } 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;
+ showDownloadPrompt(
+ t('Model file download is not complete'),
+ modelName
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
} else {
- toastWithButton(t('Please convert model to GGML format first'), t('Convert'), () => {
- convertToGGML(modelConfig, navigate);
- });
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ toastWithButton(
+ t('Please convert model to GGML format first'),
+ t('Convert'),
+ () => {
+ convertToGGML(modelConfig, navigate)
+ }
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
}
}
if (!cpp) {
- if (['.bin'].some(ext => modelPath.endsWith(ext))) {
- toast(t('Please change Strategy to CPU (rwkv.cpp) to use ggml format'), { type: 'error' });
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ if (['.bin'].some((ext) => modelPath.endsWith(ext))) {
+ toast(
+ t('Please change Strategy to CPU (rwkv.cpp) to use ggml format'),
+ { type: 'error' }
+ )
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
}
- if (!await FileExists(modelPath)) {
- showDownloadPrompt(t('Model file not found'), modelName);
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
- } 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);
- commonStore.setStatus({ status: ModelStatus.Offline });
- return;
+ if (!(await FileExists(modelPath))) {
+ showDownloadPrompt(t('Model file not found'), modelName)
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
+ } // 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.
+ else if (!currentModelSource?.isComplete && modelPath.endsWith('.pth')) {
+ showDownloadPrompt(t('Model file download is not complete'), modelName)
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ return
}
- const port = modelConfig.apiParameters.apiPort;
+ const port = modelConfig.apiParameters.apiPort
- 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;
+ 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 ?
- (_: string, port: number, host: string) => StartWebGPUServer(port, host)
- : StartServer;
- const isUsingCudaBeta = modelConfig.modelParameters.device === 'CUDA-Beta';
+ const startServer = webgpu
+ ? (_: string, port: number, host: string) =>
+ StartWebGPUServer(port, host)
+ : StartServer
+ 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',
- !!modelConfig.enableWebUI, isUsingCudaBeta, cpp, webgpuPython
+ startServer(
+ commonStore.settings.customPythonPath,
+ port,
+ commonStore.settings.host !== '127.0.0.1' ? '0.0.0.0' : '127.0.0.1',
+ !!modelConfig.enableWebUI,
+ isUsingCudaBeta,
+ cpp,
+ webgpuPython
).catch((e) => {
- const errMsg = e.message || e;
+ const errMsg = e.message || e
if (errMsg.includes('path contains space'))
- toast(`${t('Error')} - ${t('File Path Cannot Contain Space')}`, { type: 'error' });
- else
- toast(t('Error') + ' - ' + errMsg, { type: 'error' });
- });
- setTimeout(WindowShow, 1000);
- setTimeout(WindowShow, 2000);
- setTimeout(WindowShow, 3000);
+ toast(`${t('Error')} - ${t('File Path Cannot Contain Space')}`, {
+ type: 'error',
+ })
+ else toast(t('Error') + ' - ' + errMsg, { type: 'error' })
+ })
+ setTimeout(WindowShow, 1000)
+ setTimeout(WindowShow, 2000)
+ setTimeout(WindowShow, 3000)
- let timeoutCount = 6;
- let loading = false;
+ let timeoutCount = 6
+ let loading = false
const intervalId = setInterval(() => {
readRoot()
- .then(async r => {
- if (r.ok && !loading) {
- loading = true;
- clearInterval(intervalId);
- if (!webgpu) {
- await getStatus().then(status => {
- if (status)
- commonStore.setStatus(status);
- });
- }
- commonStore.setStatus({ status: ModelStatus.Loading });
- const loadingId = toast(t('Loading Model'), { type: 'info', autoClose: false });
- if (!webgpu) {
- updateConfig(t, {
- max_tokens: modelConfig.apiParameters.maxResponseToken,
- temperature: modelConfig.apiParameters.temperature,
- top_p: modelConfig.apiParameters.topP,
- presence_penalty: modelConfig.apiParameters.presencePenalty,
- frequency_penalty: modelConfig.apiParameters.frequencyPenalty,
- penalty_decay: modelConfig.apiParameters.penaltyDecay,
- global_penalty: modelConfig.apiParameters.globalPenalty,
- state: modelConfig.apiParameters.stateModel
- }).then(async r => {
- if (r.status !== 200) {
- const error = await r.text();
- if (error.includes('state shape mismatch'))
- toast(t('State model mismatch'), { type: 'error' });
- else if (error.includes('file format of the model or state model not supported'))
- toast(t('File format of the model or state model not supported'), { type: 'error' });
- else
- toast(error, { type: 'error' });
- }
- });
- }
-
- const strategy = getStrategy(modelConfig);
- let customCudaFile = '';
- if ((modelConfig.modelParameters.device.startsWith('CUDA') || modelConfig.modelParameters.device === 'Custom')
- && modelConfig.modelParameters.useCustomCuda
- && !strategy.split('->').some(s => ['cuda', 'fp32'].every(v => s.includes(v)))) {
- 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
- //
- // customCudaFile = getSupportedCustomCudaFile(isUsingCudaBeta);
- // if (customCudaFile) {
- // let kernelTargetPath: string;
- // if (isUsingCudaBeta)
- // kernelTargetPath = './backend-python/rwkv_pip/beta/wkv_cuda.pyd';
- // else
- // kernelTargetPath = './backend-python/rwkv_pip/wkv_cuda.pyd';
- // await CopyFile(customCudaFile, kernelTargetPath).catch(() => {
- // FileExists(kernelTargetPath).then((exist) => {
- // if (!exist) {
- // customCudaFile = '';
- // toast(t('Failed to copy custom cuda file'), { type: 'error' });
- // }
- // });
- // });
- // } else
- // toast(t('Supported custom cuda file not found'), { type: 'warning' });
- customCudaFile = 'any';
- } else {
- customCudaFile = 'any';
+ .then(async (r) => {
+ if (r.ok && !loading) {
+ loading = true
+ clearInterval(intervalId)
+ if (!webgpu) {
+ await getStatus().then((status) => {
+ if (status) commonStore.setStatus(status)
+ })
+ }
+ commonStore.setStatus({ status: ModelStatus.Loading })
+ const loadingId = toast(t('Loading Model'), {
+ type: 'info',
+ autoClose: false,
+ })
+ if (!webgpu) {
+ updateConfig(t, {
+ max_tokens: modelConfig.apiParameters.maxResponseToken,
+ temperature: modelConfig.apiParameters.temperature,
+ top_p: modelConfig.apiParameters.topP,
+ presence_penalty: modelConfig.apiParameters.presencePenalty,
+ frequency_penalty: modelConfig.apiParameters.frequencyPenalty,
+ penalty_decay: modelConfig.apiParameters.penaltyDecay,
+ global_penalty: modelConfig.apiParameters.globalPenalty,
+ state: modelConfig.apiParameters.stateModel,
+ }).then(async (r) => {
+ if (r.status !== 200) {
+ const error = await r.text()
+ if (error.includes('state shape mismatch'))
+ toast(t('State model mismatch'), { type: 'error' })
+ else if (
+ error.includes(
+ 'file format of the model or state model not supported'
+ )
+ )
+ toast(
+ t(
+ 'File format of the model or state model not supported'
+ ),
+ { type: 'error' }
+ )
+ else toast(error, { type: 'error' })
+ }
+ })
}
- }
- switchModel({
- model: modelPath,
- strategy: strategy,
- tokenizer: modelConfig.modelParameters.useCustomTokenizer ? modelConfig.modelParameters.customTokenizer : undefined,
- customCuda: customCudaFile !== '',
- deploy: modelConfig.enableWebUI
- }).then(async (r) => {
- if (r.ok) {
- commonStore.setStatus({ status: ModelStatus.Working });
- let buttonNameMap = {
- 'novel': 'Completion',
- 'abc': 'Composition',
- 'midi': 'Composition'
- };
- let buttonName = 'Chat';
- buttonName = Object.entries(buttonNameMap).find(([key, value]) => modelName.toLowerCase().includes(key))?.[1] || buttonName;
- const buttonFn = () => {
- navigate({ pathname: '/' + buttonName.toLowerCase() });
- };
- if (modelName.toLowerCase().includes('abc') && commonStore.compositionParams.prompt === defaultCompositionPrompt) {
- commonStore.setCompositionParams({
- ...commonStore.compositionParams,
- prompt: defaultCompositionABCPrompt
- });
- commonStore.setCompositionSubmittedPrompt(defaultCompositionABCPrompt);
+ const strategy = getStrategy(modelConfig)
+ let customCudaFile = ''
+ if (
+ (modelConfig.modelParameters.device.startsWith('CUDA') ||
+ modelConfig.modelParameters.device === 'Custom') &&
+ modelConfig.modelParameters.useCustomCuda &&
+ !strategy
+ .split('->')
+ .some((s) => ['cuda', 'fp32'].every((v) => s.includes(v)))
+ ) {
+ 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
+ //
+ // customCudaFile = getSupportedCustomCudaFile(isUsingCudaBeta);
+ // if (customCudaFile) {
+ // let kernelTargetPath: string;
+ // if (isUsingCudaBeta)
+ // kernelTargetPath = './backend-python/rwkv_pip/beta/wkv_cuda.pyd';
+ // else
+ // kernelTargetPath = './backend-python/rwkv_pip/wkv_cuda.pyd';
+ // await CopyFile(customCudaFile, kernelTargetPath).catch(() => {
+ // FileExists(kernelTargetPath).then((exist) => {
+ // if (!exist) {
+ // customCudaFile = '';
+ // toast(t('Failed to copy custom cuda file'), { type: 'error' });
+ // }
+ // });
+ // });
+ // } else
+ // toast(t('Supported custom cuda file not found'), { type: 'warning' });
+ customCudaFile = 'any'
+ } else {
+ customCudaFile = 'any'
}
-
- if (modelConfig.modelParameters.device.startsWith('CUDA') &&
- 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 });
- } else if (r.status === 304) {
- toast(t('Loading Model'), { type: 'info' });
- } else {
- commonStore.setStatus({ status: ModelStatus.Offline });
- const error = await r.text();
- const errorsMap = {
- 'not enough memory': 'Memory is not enough, try to increase the virtual memory or use a smaller model.',
- 'not compiled with CUDA': 'Bad PyTorch version, please reinstall PyTorch with cuda.',
- '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. If you are not using an Nvidia GPU, please switch the \'Strategy\' to WebGPU or CPU in the 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.',
- 're-convert the model': 'Model has been converted and does not match current strategy. If you are using a new strategy, re-convert the model.'
- };
- const matchedError = Object.entries(errorsMap).find(([key, _]) => error.includes(key));
- const message = matchedError ? t(matchedError[1]) : error;
- toast(t('Failed to switch model') + ' - ' + message, { autoClose: 5000, type: 'error' });
}
- }).catch((e) => {
- commonStore.setStatus({ status: ModelStatus.Offline });
- toast(t('Failed to switch model') + ' - ' + (e.message || e), { type: 'error' });
- }).finally(() => {
- toast.dismiss(loadingId);
- });
- }
- }).catch(() => {
- if (timeoutCount <= 0) {
- clearInterval(intervalId);
- commonStore.setStatus({ status: ModelStatus.Offline });
- }
- });
- timeoutCount--;
- }, 1000);
+ switchModel({
+ model: modelPath,
+ strategy: strategy,
+ tokenizer: modelConfig.modelParameters.useCustomTokenizer
+ ? modelConfig.modelParameters.customTokenizer
+ : undefined,
+ customCuda: customCudaFile !== '',
+ deploy: modelConfig.enableWebUI,
+ })
+ .then(async (r) => {
+ if (r.ok) {
+ commonStore.setStatus({ status: ModelStatus.Working })
+ let buttonNameMap = {
+ novel: 'Completion',
+ abc: 'Composition',
+ midi: 'Composition',
+ }
+ let buttonName = 'Chat'
+ buttonName =
+ Object.entries(buttonNameMap).find(([key, value]) =>
+ modelName.toLowerCase().includes(key)
+ )?.[1] || buttonName
+ const buttonFn = () => {
+ navigate({ pathname: '/' + buttonName.toLowerCase() })
+ }
+ if (
+ modelName.toLowerCase().includes('abc') &&
+ commonStore.compositionParams.prompt ===
+ defaultCompositionPrompt
+ ) {
+ commonStore.setCompositionParams({
+ ...commonStore.compositionParams,
+ prompt: defaultCompositionABCPrompt,
+ })
+ commonStore.setCompositionSubmittedPrompt(
+ defaultCompositionABCPrompt
+ )
+ }
+
+ if (
+ modelConfig.modelParameters.device.startsWith('CUDA') &&
+ 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 }
+ )
+ } else if (r.status === 304) {
+ toast(t('Loading Model'), { type: 'info' })
+ } else {
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ const error = await r.text()
+ const errorsMap = {
+ 'not enough memory':
+ 'Memory is not enough, try to increase the virtual memory or use a smaller model.',
+ 'not compiled with CUDA':
+ 'Bad PyTorch version, please reinstall PyTorch with cuda.',
+ '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. If you are not using an Nvidia GPU, please switch the 'Strategy' to WebGPU or CPU in the 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.',
+ 're-convert the model':
+ 'Model has been converted and does not match current strategy. If you are using a new strategy, re-convert the model.',
+ }
+ const matchedError = Object.entries(errorsMap).find(
+ ([key, _]) => error.includes(key)
+ )
+ const message = matchedError ? t(matchedError[1]) : error
+ toast(t('Failed to switch model') + ' - ' + message, {
+ autoClose: 5000,
+ type: 'error',
+ })
+ }
+ })
+ .catch((e) => {
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ toast(
+ t('Failed to switch model') + ' - ' + (e.message || e),
+ { type: 'error' }
+ )
+ })
+ .finally(() => {
+ toast.dismiss(loadingId)
+ })
+ }
+ })
+ .catch(() => {
+ if (timeoutCount <= 0) {
+ clearInterval(intervalId)
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ }
+ })
+
+ timeoutCount--
+ }, 1000)
} else {
- commonStore.setStatus({ status: ModelStatus.Offline });
- exit().then(r => {
+ commonStore.setStatus({ status: ModelStatus.Offline })
+ 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' });
+ 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' });
- });
+ toast(
+ t(
+ 'Server is working on deployment mode, please exit the program manually to stop the server'
+ ),
+ { type: 'info' }
+ )
+ })
}
- };
+ }
const onClick = async (e: any) => {
- if (commonStore.status.status === ModelStatus.Offline)
- await onClickRun?.(e);
- await onClickMainButton();
- };
+ if (commonStore.status.status === ModelStatus.Offline) await onClickRun?.(e)
+ await onClickMainButton()
+ }
- return (iconMode ?
-
- :
-
- {t(mainButtonText[commonStore.status.status])}
-
- );
-});
+ return iconMode ? (
+
+ ) : (
+
+ {t(mainButtonText[commonStore.status.status])}
+
+ )
+})
diff --git a/frontend/src/components/Section.tsx b/frontend/src/components/Section.tsx
index 643c2986..ebb69cb7 100644
--- a/frontend/src/components/Section.tsx
+++ b/frontend/src/components/Section.tsx
@@ -1,21 +1,21 @@
-import { FC, ReactElement } from 'react';
-import { Card, Text } from '@fluentui/react-components';
+import { FC, ReactElement } from 'react'
+import { Card, Text } from '@fluentui/react-components'
export const Section: FC<{
- title: string; desc?: string | null, content: ReactElement, outline?: boolean
-}> =
- ({ title, desc, content, outline = true }) => {
- return (
-
-
-
- {title}
- {desc && {desc} }
-
+ title: string
+ desc?: string | null
+ content: ReactElement
+ outline?: boolean
+}> = ({ title, desc, content, outline = true }) => {
+ return (
+
+
+
+ {title}
+ {desc && {desc} }
-
- {content}
-
-
- );
- };
+
+ {content}
+
+ )
+}
diff --git a/frontend/src/components/ToolTipButton.tsx b/frontend/src/components/ToolTipButton.tsx
index 4c774883..a0ac58cb 100644
--- a/frontend/src/components/ToolTipButton.tsx
+++ b/frontend/src/components/ToolTipButton.tsx
@@ -1,18 +1,23 @@
-import React, { CSSProperties, FC, MouseEventHandler, ReactElement } from 'react';
-import { Button, Tooltip } from '@fluentui/react-components';
+import React, {
+ CSSProperties,
+ FC,
+ MouseEventHandler,
+ ReactElement,
+} from 'react'
+import { Button, Tooltip } from '@fluentui/react-components'
export const ToolTipButton: FC<{
- text?: string | null,
- desc: string,
- icon?: ReactElement,
- className?: string,
- style?: CSSProperties,
- size?: 'small' | 'medium' | 'large',
- shape?: 'rounded' | 'circular' | 'square';
- appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent';
- disabled?: boolean,
+ text?: string | null
+ desc: string
+ icon?: ReactElement
+ className?: string
+ style?: CSSProperties
+ size?: 'small' | 'medium' | 'large'
+ shape?: 'rounded' | 'circular' | 'square'
+ appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent'
+ disabled?: boolean
onClick?: MouseEventHandler
- showDelay?: number,
+ showDelay?: number
}> = ({
text,
desc,
@@ -24,14 +29,40 @@ export const ToolTipButton: FC<{
appearance,
disabled,
onClick,
- showDelay = 0
+ showDelay = 0,
}) => {
- return (desc ?
-
- {text}
- :
-
{text}
- );
-};
+ return desc ? (
+
+
+ {text}
+
+
+ ) : (
+
+ {text}
+
+ )
+}
diff --git a/frontend/src/components/ValuedSlider.tsx b/frontend/src/components/ValuedSlider.tsx
index a60cb2e7..5cc2f948 100644
--- a/frontend/src/components/ValuedSlider.tsx
+++ b/frontend/src/components/ValuedSlider.tsx
@@ -1,35 +1,58 @@
-import React, { FC, useEffect, useRef } from 'react';
-import { Slider, Text } from '@fluentui/react-components';
-import { SliderOnChangeData } from '@fluentui/react-slider';
-import { NumberInput } from './NumberInput';
+import React, { FC, useEffect, useRef } from 'react'
+import { Slider, Text } from '@fluentui/react-components'
+import { SliderOnChangeData } from '@fluentui/react-slider'
+import { NumberInput } from './NumberInput'
export const ValuedSlider: FC<{
- value: number,
- min: number,
- max: number,
- step?: number,
+ value: number
+ min: number
+ max: number
+ step?: number
input?: boolean
- onChange?: (ev: React.ChangeEvent
, data: SliderOnChangeData) => void,
+ onChange?: (
+ ev: React.ChangeEvent,
+ data: SliderOnChangeData
+ ) => void
toFixed?: number
disabled?: boolean
}> = ({ value, min, max, step, input, onChange, toFixed, disabled }) => {
- const sliderRef = useRef(null);
+ const sliderRef = useRef(null)
useEffect(() => {
if (step && sliderRef.current && sliderRef.current.parentElement) {
if ((max - min) / step > 10)
- sliderRef.current.parentElement.style.removeProperty('--fui-Slider--steps-percent');
+ sliderRef.current.parentElement.style.removeProperty(
+ '--fui-Slider--steps-percent'
+ )
}
- }, []);
+ }, [])
return (
-
- {input
- ?
- : {value} }
+
+ {input ? (
+
+ ) : (
+ {value}
+ )}
- );
-};
+ )
+}
diff --git a/frontend/src/components/WorkHeader.tsx b/frontend/src/components/WorkHeader.tsx
index 1f3bc955..52226e8c 100644
--- a/frontend/src/components/WorkHeader.tsx
+++ b/frontend/src/components/WorkHeader.tsx
@@ -1,53 +1,61 @@
-import React, { FC } from 'react';
-import { observer } from 'mobx-react-lite';
-import { Divider, PresenceBadge, Text } from '@fluentui/react-components';
-import commonStore, { ModelStatus } from '../stores/commonStore';
-import { ConfigSelector } from './ConfigSelector';
-import { RunButton } from './RunButton';
-import { PresenceBadgeStatus } from '@fluentui/react-badge';
-import { useTranslation } from 'react-i18next';
-import { useMediaQuery } from 'usehooks-ts';
+import React, { FC } from 'react'
+import { PresenceBadgeStatus } from '@fluentui/react-badge'
+import { Divider, PresenceBadge, Text } from '@fluentui/react-components'
+import { observer } from 'mobx-react-lite'
+import { useTranslation } from 'react-i18next'
+import { useMediaQuery } from 'usehooks-ts'
+import commonStore, { ModelStatus } from '../stores/commonStore'
+import { ConfigSelector } from './ConfigSelector'
+import { RunButton } from './RunButton'
const statusText = {
[ModelStatus.Offline]: 'Offline',
[ModelStatus.Starting]: 'Starting',
[ModelStatus.Loading]: 'Loading',
- [ModelStatus.Working]: 'Working'
-};
+ [ModelStatus.Working]: 'Working',
+}
const badgeStatus: { [modelStatus: number]: PresenceBadgeStatus } = {
[ModelStatus.Offline]: 'unknown',
[ModelStatus.Starting]: 'away',
[ModelStatus.Loading]: 'away',
- [ModelStatus.Working]: 'available'
-};
+ [ModelStatus.Working]: 'available',
+}
export const WorkHeader: FC = observer(() => {
- const { t } = useTranslation();
- const mq = useMediaQuery('(min-width: 640px)');
- const port = commonStore.getCurrentModelConfig().apiParameters.apiPort;
+ const { t } = useTranslation()
+ const mq = useMediaQuery('(min-width: 640px)')
+ const port = commonStore.getCurrentModelConfig().apiParameters.apiPort
- return commonStore.platform === 'web' ?
-
:
+ return commonStore.platform === 'web' ? (
+
+ ) : (
-
+
-
{t('Model Status') + ': ' + t(statusText[commonStore.status.status])}
-
- {commonStore.lastModelName && mq &&
- {commonStore.lastModelName}
- }
+ {t('Model Status') +
+ ': ' +
+ t(statusText[commonStore.status.status])}
+
+
+ {commonStore.lastModelName && mq && (
+
{commonStore.lastModelName}
+ )}
- {t('This tool\'s API is compatible with OpenAI API. It can be used with any ChatGPT tool you like. Go to the settings of some ChatGPT tool, replace the \'https://api.openai.com\' part in the API address with \'') + `http://127.0.0.1:${port}` + '\'.'}
+ {t(
+ "This tool's API is compatible with OpenAI API. It can be used with any ChatGPT tool you like. Go to the settings of some ChatGPT tool, replace the 'https://api.openai.com' part in the API address with '"
+ ) +
+ `http://127.0.0.1:${port}` +
+ "'."}
- ;
-});
\ No newline at end of file
+ )
+})
diff --git a/frontend/src/main.tsx b/frontend/src/main.tsx
index bbf8af84..3db56a94 100644
--- a/frontend/src/main.tsx
+++ b/frontend/src/main.tsx
@@ -1,25 +1,25 @@
-import './webWails';
-import React from 'react';
-import { createRoot } from 'react-dom/client';
-import './style.scss';
-import 'react-toastify/dist/ReactToastify.css';
-import App from './App';
-import { HashRouter } from 'react-router-dom';
-import { startup } from './startup';
-import './_locales/i18n-react';
-import { WindowShow } from '../wailsjs/runtime';
+import './webWails'
+import React from 'react'
+import { createRoot } from 'react-dom/client'
+import './style.scss'
+import 'react-toastify/dist/ReactToastify.css'
+import { HashRouter } from 'react-router-dom'
+import App from './App'
+import { startup } from './startup'
+import './_locales/i18n-react'
+import { WindowShow } from '../wailsjs/runtime'
startup().then(() => {
- const container = document.getElementById('root');
+ const container = document.getElementById('root')
- const root = createRoot(container!);
+ const root = createRoot(container!)
root.render(
- );
+ )
// force display the window
- WindowShow();
-});
+ WindowShow()
+})
diff --git a/frontend/src/pages/About.tsx b/frontend/src/pages/About.tsx
index 608dab8f..3e4814d2 100644
--- a/frontend/src/pages/About.tsx
+++ b/frontend/src/pages/About.tsx
@@ -1,23 +1,28 @@
-import React, { FC } from 'react';
-import { useTranslation } from 'react-i18next';
-import { Page } from '../components/Page';
-import MarkdownRender from '../components/MarkdownRender';
-import { observer } from 'mobx-react-lite';
-import commonStore from '../stores/commonStore';
+import React, { FC } from 'react'
+import { observer } from 'mobx-react-lite'
+import { useTranslation } from 'react-i18next'
+import MarkdownRender from '../components/MarkdownRender'
+import { Page } from '../components/Page'
+import commonStore from '../stores/commonStore'
const About: FC = observer(() => {
- const { t } = useTranslation();
- const lang: string = commonStore.settings.language;
+ const { t } = useTranslation()
+ const lang: string = commonStore.settings.language
return (
-
-
- {lang in commonStore.about ? commonStore.about[lang] : commonStore.about['en']}
-
-
- } />
- );
-});
+
+
+ {lang in commonStore.about
+ ? commonStore.about[lang]
+ : commonStore.about['en']}
+
+
+ }
+ />
+ )
+})
-export default About;
+export default About
diff --git a/frontend/src/pages/AudiotrackManager/AudiotrackButton.tsx b/frontend/src/pages/AudiotrackManager/AudiotrackButton.tsx
index 07a49abb..909f8a7c 100644
--- a/frontend/src/pages/AudiotrackManager/AudiotrackButton.tsx
+++ b/frontend/src/pages/AudiotrackManager/AudiotrackButton.tsx
@@ -1,40 +1,61 @@
-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';
+import React, { FC, lazy } from 'react'
+import {
+ Button,
+ Dialog,
+ DialogBody,
+ DialogContent,
+ DialogSurface,
+ DialogTrigger,
+} from '@fluentui/react-components'
+import { useTranslation } from 'react-i18next'
+import { CustomToastContainer } from '../../components/CustomToastContainer'
+import { LazyImportComponent } from '../../components/LazyImportComponent'
+import commonStore from '../../stores/commonStore'
+import { flushMidiRecordingContent } from '../../utils'
-const AudiotrackEditor = lazy(() => import('./AudiotrackEditor'));
+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?: 'small' | 'medium' | 'large'
+ shape?: 'rounded' | 'circular' | 'square'
+ appearance?: 'secondary' | 'primary' | 'outline' | 'subtle' | 'transparent'
+ setPrompt: (prompt: string) => void
}> = ({ size, shape, appearance, setPrompt }) => {
- const { t } = useTranslation();
+ const { t } = useTranslation()
- return