From c02ce51d868b093e0903d973816aff40db97592b Mon Sep 17 00:00:00 2001 From: josc146 Date: Sun, 21 May 2023 12:54:37 +0800 Subject: [PATCH] improve details --- frontend/src/_locales/zh-hans/main.json | 3 +- frontend/src/components/RunButton.tsx | 271 ++++++++++---------- frontend/src/pages/Configs.tsx | 318 ++++++++++++------------ 3 files changed, 306 insertions(+), 286 deletions(-) diff --git a/frontend/src/_locales/zh-hans/main.json b/frontend/src/_locales/zh-hans/main.json index 04c75f8..f23d3bc 100644 --- a/frontend/src/_locales/zh-hans/main.json +++ b/frontend/src/_locales/zh-hans/main.json @@ -95,5 +95,6 @@ "Python dependencies are incomplete, would you like to install them?": "Python依赖缺失, 是否安装?", "Install": "安装", "This is the latest version": "已是最新版", - "Use Tsinghua Pip Mirrors": "使用清华大学Pip镜像源" + "Use Tsinghua Pip Mirrors": "使用清华大学Pip镜像源", + "Model Config Exception": "模型配置异常" } \ No newline at end of file diff --git a/frontend/src/components/RunButton.tsx b/frontend/src/components/RunButton.tsx index 88ebd75..8c3d6f3 100644 --- a/frontend/src/components/RunButton.tsx +++ b/frontend/src/components/RunButton.tsx @@ -1,16 +1,17 @@ -import React, {FC, MouseEventHandler, ReactElement} from 'react'; -import commonStore, {ModelStatus} from '../stores/commonStore'; -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 React, { FC, MouseEventHandler, ReactElement } from 'react'; +import commonStore, { ModelStatus } from '../stores/commonStore'; +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, saveCache, 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 { getStrategy, saveCache, 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/runtime'; const mainButtonText = { [ModelStatus.Offline]: 'Run', @@ -20,141 +21,157 @@ const mainButtonText = { }; const iconModeButtonIcon: { [modelStatus: number]: ReactElement } = { - [ModelStatus.Offline]: , - [ModelStatus.Starting]: , - [ModelStatus.Loading]: , - [ModelStatus.Working]: + [ModelStatus.Offline]: , + [ModelStatus.Starting]: , + [ModelStatus.Loading]: , + [ModelStatus.Working]: }; export const RunButton: FC<{ onClickRun?: MouseEventHandler, iconMode?: boolean }> = observer(({ - onClickRun, - iconMode - }) => { - const {t} = useTranslation(); - const navigate = useNavigate(); + onClickRun, + iconMode + }) => { + const { t } = useTranslation(); + const navigate = useNavigate(); - const onClickMainButton = async () => { - const modelConfig = commonStore.getCurrentModelConfig(); - const modelName = modelConfig.modelParameters.modelName; - const modelPath = `./${manifest.localModelDir}/${modelName}`; + const onClickMainButton = async () => { + if (commonStore.modelStatus === ModelStatus.Offline) { + commonStore.setModelStatus(ModelStatus.Starting); - 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(commonStore.settings.cnMirror); - }); + const modelConfig = commonStore.getCurrentModelConfig(); + let modelName = '' + let modelPath = '' + if (modelConfig && modelConfig.modelParameters) { + modelName = modelConfig.modelParameters.modelName; + modelPath = `./${manifest.localModelDir}/${modelName}`; } else { - toast(depErrorMsg, {type: 'error'}); + toast(t('Model Config Exception'), { type: 'error' }); + commonStore.setModelStatus(ModelStatus.Offline); + return; } - }); - 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; - if (downloadUrl) { - toastWithButton(`${t('Downloading')} ${modelName}`, t('Check'), () => { - navigate({pathname: '/downloads'}); - }, - {autoClose: 3000}); - AddToDownloadList(modelPath, downloadUrl); - } else { - toast(t('Can not find download url'), {type: 'error'}); - } - }); - - return; - } - - const port = modelConfig.apiParameters.apiPort; - - if (commonStore.modelStatus === ModelStatus.Offline) { - commonStore.setModelStatus(ModelStatus.Starting); - await exit(1000).catch(() => { - }); - StartServer(port); - - let timeoutCount = 6; - let loading = false; - const intervalId = setInterval(() => { - readRoot() - .then(r => { - if (r.ok && !loading) { - clearInterval(intervalId); - commonStore.setModelStatus(ModelStatus.Loading); - loading = true; - toast(t('Loading Model'), {type: 'info'}); - updateConfig({ - max_tokens: modelConfig.apiParameters.maxResponseToken, - temperature: modelConfig.apiParameters.temperature, - top_p: modelConfig.apiParameters.topP, - presence_penalty: modelConfig.apiParameters.presencePenalty, - frequency_penalty: modelConfig.apiParameters.frequencyPenalty + if (!commonStore.depComplete) { + let depErrorMsg = ''; + await DepCheck().catch((e) => { + depErrorMsg = e.message || e; + WindowShow(); + 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'); }); - switchModel({ - model: `${manifest.localModelDir}/${modelConfig.modelParameters.modelName}`, - strategy: getStrategy(modelConfig) - }).then((r) => { - if (r.ok) { - commonStore.setModelStatus(ModelStatus.Working); - toast(t('Startup Completed'), {type: 'success'}); - } else if (r.status === 304) { - toast(t('Loading Model'), {type: 'info'}); - } else { - commonStore.setModelStatus(ModelStatus.Offline); - toast(t('Failed to switch model'), {type: 'error'}); - } - }).catch(() => { - commonStore.setModelStatus(ModelStatus.Offline); - toast(t('Failed to switch model'), {type: 'error'}); + } else if (depErrorMsg.includes('DepCheck Error')) { + toastWithButton(t('Python dependencies are incomplete, would you like to install them?'), t('Install'), () => { + InstallPyDep(commonStore.settings.cnMirror); + setTimeout(WindowShow, 1000) }); + } else { + toast(depErrorMsg, { type: 'error' }); } - }).catch(() => { - if (timeoutCount <= 0) { - clearInterval(intervalId); + }); + if (depErrorMsg) { commonStore.setModelStatus(ModelStatus.Offline); + 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; + if (downloadUrl) { + toastWithButton(`${t('Downloading')} ${modelName}`, t('Check'), () => { + navigate({ pathname: '/downloads' }); + }, + { autoClose: 3000 }); + AddToDownloadList(modelPath, downloadUrl); + } else { + toast(t('Can not find download url'), { type: 'error' }); + } + }); + + commonStore.setModelStatus(ModelStatus.Offline); + return; + } + + const port = modelConfig.apiParameters.apiPort; + + await exit(1000).catch(() => { }); + StartServer(port); + setTimeout(WindowShow, 1000) - timeoutCount--; - }, 1000); - } else { - commonStore.setModelStatus(ModelStatus.Offline); - exit(); - } - }; + let timeoutCount = 6; + let loading = false; + const intervalId = setInterval(() => { + readRoot() + .then(r => { + if (r.ok && !loading) { + clearInterval(intervalId); + commonStore.setModelStatus(ModelStatus.Loading); + loading = true; + toast(t('Loading Model'), { type: 'info' }); + updateConfig({ + max_tokens: modelConfig.apiParameters.maxResponseToken, + temperature: modelConfig.apiParameters.temperature, + top_p: modelConfig.apiParameters.topP, + presence_penalty: modelConfig.apiParameters.presencePenalty, + frequency_penalty: modelConfig.apiParameters.frequencyPenalty + }); + switchModel({ + model: `${manifest.localModelDir}/${modelConfig.modelParameters.modelName}`, + strategy: getStrategy(modelConfig) + }).then((r) => { + if (r.ok) { + commonStore.setModelStatus(ModelStatus.Working); + toastWithButton(t('Startup Completed'), t('Chat'), () => { + navigate({ pathname: '/chat' }); + }, { type: 'success', autoClose: 3000 }); + } else if (r.status === 304) { + toast(t('Loading Model'), { type: 'info' }); + } else { + commonStore.setModelStatus(ModelStatus.Offline); + toast(t('Failed to switch model'), { type: 'error' }); + } + }).catch(() => { + commonStore.setModelStatus(ModelStatus.Offline); + toast(t('Failed to switch model'), { type: 'error' }); + }); + } + }).catch(() => { + if (timeoutCount <= 0) { + clearInterval(intervalId); + commonStore.setModelStatus(ModelStatus.Offline); + } + }); - const onClick = async (e: any) => { - if (commonStore.modelStatus === ModelStatus.Offline) - await onClickRun?.(e); - await onClickMainButton(); - }; + timeoutCount--; + }, 1000); + } else { + commonStore.setModelStatus(ModelStatus.Offline); + exit(); + } + }; - return (iconMode ? + const onClick = async (e: any) => { + if (commonStore.modelStatus === ModelStatus.Offline) + await onClickRun?.(e); + await onClickMainButton(); + }; + + return (iconMode ? + icon={iconModeButtonIcon[commonStore.modelStatus]} + desc={t(mainButtonText[commonStore.modelStatus])} + size="small" onClick={onClick} /> : - ); -}); + ); + }); diff --git a/frontend/src/pages/Configs.tsx b/frontend/src/pages/Configs.tsx index 7d2cd8d..82f092c 100644 --- a/frontend/src/pages/Configs.tsx +++ b/frontend/src/pages/Configs.tsx @@ -1,22 +1,23 @@ -import {Dropdown, Input, Label, Option, Select, Switch} from '@fluentui/react-components'; -import {AddCircle20Regular, DataUsageSettings20Regular, Delete20Regular, Save20Regular} from '@fluentui/react-icons'; -import React, {FC} from 'react'; -import {Section} from '../components/Section'; -import {Labeled} from '../components/Labeled'; -import {ToolTipButton} from '../components/ToolTipButton'; +import { Dropdown, Input, Label, Option, Select, Switch } from '@fluentui/react-components'; +import { AddCircle20Regular, DataUsageSettings20Regular, Delete20Regular, Save20Regular } from '@fluentui/react-icons'; +import React, { FC } from 'react'; +import { Section } from '../components/Section'; +import { Labeled } from '../components/Labeled'; +import { ToolTipButton } from '../components/ToolTipButton'; import commonStore from '../stores/commonStore'; -import {observer} from 'mobx-react-lite'; -import {toast} from 'react-toastify'; -import {ValuedSlider} from '../components/ValuedSlider'; -import {NumberInput} from '../components/NumberInput'; -import {Page} from '../components/Page'; -import {useNavigate} from 'react-router'; -import {RunButton} from '../components/RunButton'; -import {updateConfig} from '../apis'; -import {ConvertModel, FileExists} from '../../wailsjs/go/backend_golang/App'; +import { observer } from 'mobx-react-lite'; +import { toast } from 'react-toastify'; +import { ValuedSlider } from '../components/ValuedSlider'; +import { NumberInput } from '../components/NumberInput'; +import { Page } from '../components/Page'; +import { useNavigate } from 'react-router'; +import { RunButton } from '../components/RunButton'; +import { updateConfig } from '../apis'; +import { ConvertModel, FileExists } from '../../wailsjs/go/backend_golang/App'; import manifest from '../../../manifest.json'; -import {getStrategy, refreshLocalModels} from '../utils'; -import {useTranslation} from 'react-i18next'; +import { getStrategy, refreshLocalModels } from '../utils'; +import { useTranslation } from 'react-i18next'; +import { WindowShow } from '../../wailsjs/runtime/runtime'; export type ApiParameters = { apiPort: number @@ -70,7 +71,7 @@ export const defaultModelConfigs: ModelConfig[] = [ ]; export const Configs: FC = observer(() => { - const {t} = useTranslation(); + const { t } = useTranslation(); const [selectedIndex, setSelectedIndex] = React.useState(commonStore.currentModelConfigIndex); const [selectedConfig, setSelectedConfig] = React.useState(commonStore.modelConfigs[selectedIndex]); const navigate = useNavigate(); @@ -85,7 +86,7 @@ export const Configs: FC = observer(() => { }; const setSelectedConfigName = (newName: string) => { - setSelectedConfig({...selectedConfig, name: newName}); + setSelectedConfig({ ...selectedConfig, name: newName }); }; const setSelectedConfigApiParams = (newParams: Partial) => { @@ -115,39 +116,39 @@ export const Configs: FC = observer(() => { presence_penalty: selectedConfig.apiParameters.presencePenalty, frequency_penalty: selectedConfig.apiParameters.frequencyPenalty }); - toast(t('Config Saved'), {autoClose: 300, type: 'success'}); + toast(t('Config Saved'), { autoClose: 300, type: 'success' }); }; return (
- { - if (data.optionValue) { - updateSelectedIndex(Number(data.optionValue)); - } - }}> + { + if (data.optionValue) { + updateSelectedIndex(Number(data.optionValue)); + } + }}> {commonStore.modelConfigs.map((config, index) => )} - } onClick={() => { + } onClick={() => { commonStore.createModelConfig(); updateSelectedIndex(commonStore.modelConfigs.length - 1); - }}/> - } onClick={() => { + }} /> + } onClick={() => { commonStore.deleteModelConfig(selectedIndex); updateSelectedIndex(Math.min(selectedIndex, commonStore.modelConfigs.length - 1)); - }}/> - } onClick={onClickSave}/> + }} /> + } onClick={onClickSave} />
{ setSelectedConfigName(data.value); - }}/> + }} />
{ content={
{ - setSelectedConfigApiParams({ - apiPort: data.value - }); - }}/> - }/> + desc={t('Open the following URL with your browser to view the API documentation') + `: http://127.0.0.1:${port}/docs. ` + + 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}` + '\'.'} + content={ + { + setSelectedConfigApiParams({ + apiPort: data.value + }); + }} /> + } /> { - setSelectedConfigApiParams({ - maxResponseToken: data.value - }); - }}/> - }/> + 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={ + { + setSelectedConfigApiParams({ + maxResponseToken: data.value + }); + }} /> + } /> { - setSelectedConfigApiParams({ - temperature: data.value - }); - }}/> - }/> + desc={t('Sampling temperature, the higher the stronger the randomness and creativity, while the lower, the more focused and deterministic it will be.')} + content={ + { + setSelectedConfigApiParams({ + temperature: data.value + }); + }} /> + } /> { - setSelectedConfigApiParams({ - topP: data.value - }); - }}/> - }/> + desc={t('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={ + { + setSelectedConfigApiParams({ + topP: data.value + }); + }} /> + } /> { - setSelectedConfigApiParams({ - presencePenalty: data.value - }); - }}/> - }/> + 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={ + { + setSelectedConfigApiParams({ + presencePenalty: data.value + }); + }} /> + } /> { - setSelectedConfigApiParams({ - frequencyPenalty: data.value - }); - }}/> - }/> + 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={ + { + setSelectedConfigApiParams({ + frequencyPenalty: data.value + }); + }} /> + } />
} /> @@ -230,99 +231,100 @@ export const Configs: FC = observer(() => {
- { + setSelectedConfigModelParams({ + modelName: data.value + }); + }}> {commonStore.modelSourceList.map((modelItem, index) => modelItem.isLocal && )} - } onClick={() => { - navigate({pathname: '/models'}); - }}/> + } onClick={() => { + navigate({ pathname: '/models' }); + }} />
- }/> + } /> { const modelPath = `${manifest.localModelDir}/${selectedConfig.modelParameters.modelName}`; if (await FileExists(modelPath)) { const strategy = getStrategy(selectedConfig); const newModelPath = modelPath + '-' + strategy.replace(/[> *+]/g, '-'); - toast(t('Start Converting'), {autoClose: 1000, type: 'info'}); + toast(t('Start Converting'), { autoClose: 1000, type: 'info' }); ConvertModel(modelPath, strategy, newModelPath).then(() => { - toast(`${t('Convert Success')} - ${newModelPath}`, {type: 'success'}); - refreshLocalModels({models: commonStore.modelSourceList}, false); + toast(`${t('Convert Success')} - ${newModelPath}`, { type: 'success' }); + refreshLocalModels({ models: commonStore.modelSourceList }, false); }).catch(e => { - toast(`${t('Convert Failed')} - ${e.message || e}`, {type: 'error'}); + toast(`${t('Convert Failed')} - ${e.message || e}`, { type: 'error' }); }); + setTimeout(WindowShow, 1000) } else { - toast(`${t('Model Not Found')} - ${modelPath}`, {type: 'error'}); + toast(`${t('Model Not Found')} - ${modelPath}`, { type: 'error' }); } - }}/> + }} /> { - if (data.optionText) { - setSelectedConfigModelParams({ - device: data.optionText as Device - }); - } - }}> + { + if (data.optionText) { + setSelectedConfigModelParams({ + device: data.optionText as Device + }); + } + }}> - }/> + } /> { - if (data.optionText) { - setSelectedConfigModelParams({ - precision: data.optionText as Precision - }); - } - }}> - - - - - }/> + desc={t('int8 uses less VRAM, and is faster, but has slightly lower quality. fp16 has higher quality, and fp32 has the best quality.')} + content={ + { + if (data.optionText) { + setSelectedConfigModelParams({ + precision: data.optionText as Precision + }); + } + }}> + + + + + } /> { - setSelectedConfigModelParams({ - storedLayers: data.value - }); - }}/> - }/> + desc={t('Number of the neural network layers loaded into VRAM, the more you load, the faster the speed, but it consumes more VRAM.')} + content={ + { + setSelectedConfigModelParams({ + storedLayers: data.value + }); + }} /> + } /> { - setSelectedConfigModelParams({ - enableHighPrecisionForLastLayer: data.checked - }); - }}/> - }/> + desc={t('Whether to use CPU to calculate the last output layer of the neural network with FP32 precision to obtain better quality.')} + content={ + { + setSelectedConfigModelParams({ + enableHighPrecisionForLastLayer: data.checked + }); + }} /> + } />
} />
- +
- }/> + } /> ); });