diff --git a/backend-golang/app.go b/backend-golang/app.go index 855f0dc..deffeab 100644 --- a/backend-golang/app.go +++ b/backend-golang/app.go @@ -2,6 +2,9 @@ package backend_golang import ( "context" + "net/http" + + "github.com/minio/selfupdate" ) // App struct @@ -19,3 +22,19 @@ func NewApp() *App { func (a *App) OnStartup(ctx context.Context) { a.ctx = ctx } + +func (a *App) UpdateApp(url string) (broken bool, err error) { + resp, err := http.Get(url) + if err != nil { + return false, err + } + defer resp.Body.Close() + err = selfupdate.Apply(resp.Body, selfupdate.Options{}) + if err != nil { + if rerr := selfupdate.RollbackError(err); rerr != nil { + return true, rerr + } + return false, err + } + return false, nil +} diff --git a/backend-golang/rwkv.go b/backend-golang/rwkv.go index 5ed0504..5b19014 100644 --- a/backend-golang/rwkv.go +++ b/backend-golang/rwkv.go @@ -6,12 +6,12 @@ import ( "strconv" ) -func (a *App) StartServer(port int) (string, error) { +func cmd(args ...string) (string, error) { cmdHelper, err := filepath.Abs("./cmd-helper") if err != nil { return "", err } - cmd := exec.Command(cmdHelper, "python", "./backend-python/main.py", strconv.Itoa(port)) + cmd := exec.Command(cmdHelper, args...) out, err := cmd.CombinedOutput() if err != nil { return "", err @@ -19,15 +19,10 @@ func (a *App) StartServer(port int) (string, error) { return string(out), nil } -func (a *App) ConvertModel(modelPath string, strategy string, outPath string) (string, error) { - cmdHelper, err := filepath.Abs("./cmd-helper") - if err != nil { - return "", err - } - cmd := exec.Command(cmdHelper, "python", "./backend-python/convert_model.py", "--in", modelPath, "--out", outPath, "--strategy", strategy) - 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)) +} + +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) } diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index cd14978..3d8a603 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -23,14 +23,16 @@ // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. -import {FluentProvider, Tab, TabList, webDarkTheme} from '@fluentui/react-components'; +import {FluentProvider, Tab, TabList, webDarkTheme, webLightTheme} from '@fluentui/react-components'; import {FC, useEffect, useState} from 'react'; import {Route, Routes, useLocation, useNavigate} from 'react-router'; import {pages} from './pages'; import {useMediaQuery} from 'usehooks-ts'; import {ToastContainer} from 'react-toastify'; +import commonStore from './stores/commonStore'; +import {observer} from 'mobx-react-lite'; -const App: FC = () => { +const App: FC = observer(() => { const navigate = useNavigate(); const location = useLocation(); const mq = useMediaQuery('(min-width: 640px)'); @@ -43,7 +45,7 @@ const App: FC = () => { useEffect(() => setPath(location.pathname), [location]); return ( - +
{ rtl={false} pauseOnFocusLoss={false} draggable={false} - theme={'dark'} + theme={commonStore.settings.darkMode ? 'dark' : 'light'} /> ); -}; +}); export default App; diff --git a/frontend/src/components/Labeled.tsx b/frontend/src/components/Labeled.tsx index 69cc976..6bb164e 100644 --- a/frontend/src/components/Labeled.tsx +++ b/frontend/src/components/Labeled.tsx @@ -1,9 +1,21 @@ import {FC, ReactElement} from 'react'; import {Label, Tooltip} from '@fluentui/react-components'; -export const Labeled: FC<{ label: string; desc?: string, content: ReactElement }> = ({label, desc, content}) => { +export const Labeled: FC<{ + label: string; desc?: string, content: ReactElement, flex?: boolean, spaceBetween?: boolean +}> = ({ + label, + desc, + content, + flex, + spaceBetween + }) => { return ( -
+
{desc ? diff --git a/frontend/src/components/Page.tsx b/frontend/src/components/Page.tsx index 2e47b8e..5f55c92 100644 --- a/frontend/src/components/Page.tsx +++ b/frontend/src/components/Page.tsx @@ -1,7 +1,7 @@ import React, {FC, ReactElement} from 'react'; import {Divider, Text} from '@fluentui/react-components'; -export const Page: FC<{ title: string; content: ReactElement }> = ({title, content = true}) => { +export const Page: FC<{ title: string; content: ReactElement }> = ({title, content}) => { return (
{title} diff --git a/frontend/src/pages/Chat.tsx b/frontend/src/pages/Chat.tsx index fad2bfc..4fec0c7 100644 --- a/frontend/src/pages/Chat.tsx +++ b/frontend/src/pages/Chat.tsx @@ -1,10 +1,13 @@ import React, {FC} from 'react'; -import {Text} from '@fluentui/react-components'; +import {Page} from '../components/Page'; +import {PresenceBadge} from '@fluentui/react-components'; export const Chat: FC = () => { return ( -
- In Development -
+ + +
+ }/> ); }; diff --git a/frontend/src/pages/Models.tsx b/frontend/src/pages/Models.tsx index 9ff9d05..1c0cc5d 100644 --- a/frontend/src/pages/Models.tsx +++ b/frontend/src/pages/Models.tsx @@ -21,7 +21,7 @@ import {DownloadFile, OpenFileFolder} from '../../wailsjs/go/backend_golang/App' import manifest from '../../../manifest.json'; import {toast} from 'react-toastify'; import {Page} from '../components/Page'; -import {refreshModels} from '../utils'; +import {refreshModels, saveConfigs} from '../utils'; const columns: TableColumnDefinition[] = [ createTableColumn({ @@ -134,6 +134,7 @@ export const Models: FC = observer(() => { Model Source Manifest List } onClick={() => { refreshModels(false); + saveConfigs(); }}/>
diff --git a/frontend/src/pages/Settings.tsx b/frontend/src/pages/Settings.tsx index e3e4709..b33489e 100644 --- a/frontend/src/pages/Settings.tsx +++ b/frontend/src/pages/Settings.tsx @@ -1,10 +1,40 @@ import React, {FC} from 'react'; -import {Text} from '@fluentui/react-components'; +import {Page} from '../components/Page'; +import {Dropdown, Option, Switch} from '@fluentui/react-components'; +import {Labeled} from '../components/Labeled'; +import commonStore from '../stores/commonStore'; +import {observer} from 'mobx-react-lite'; -export const Settings: FC = () => { +export const Settings: FC = observer(() => { return ( -
- In Development -
+ + { + if (data.optionText) { + } + }}> + + + + }/> + { + commonStore.setSettings({ + darkMode: data.checked + }); + }}/> + }/> + { + }}/> + }/> +
+ }/> ); -}; +}); diff --git a/frontend/src/startup.ts b/frontend/src/startup.ts index 171740b..9614cf4 100644 --- a/frontend/src/startup.ts +++ b/frontend/src/startup.ts @@ -11,6 +11,10 @@ async function initConfig() { await ReadJson('config.json').then((configData: LocalConfig) => { if (configData.modelSourceManifestList) commonStore.setModelSourceManifestList(configData.modelSourceManifestList); + + if (configData.settings) + commonStore.setSettings(configData.settings, false); + if (configData.modelConfigs && Array.isArray(configData.modelConfigs)) commonStore.setModelConfigs(configData.modelConfigs, false); else throw new Error('Invalid config.json'); diff --git a/frontend/src/stores/commonStore.ts b/frontend/src/stores/commonStore.ts index 9695744..11e3e95 100644 --- a/frontend/src/stores/commonStore.ts +++ b/frontend/src/stores/commonStore.ts @@ -1,5 +1,6 @@ import {makeAutoObservable} from 'mobx'; -import {saveConfigs} from '../utils'; +import {getNavigatorLanguage, isSystemLightMode, saveConfigs, Settings} from '../utils'; +import {WindowSetDarkTheme, WindowSetLightTheme} from '../../wailsjs/runtime'; export enum ModelStatus { Offline, @@ -82,6 +83,11 @@ class CommonStore { modelConfigs: ModelConfig[] = []; modelSourceManifestList: string = 'https://cdn.jsdelivr.net/gh/josstorer/RWKV-Runner/manifest.json;'; modelSourceList: ModelSourceItem[] = []; + settings: Settings = { + language: getNavigatorLanguage(), + darkMode: !isSystemLightMode(), + autoUpdatesCheck: true + }; getCurrentModelConfig = () => { return this.modelConfigs[this.currentModelConfigIndex]; @@ -139,6 +145,18 @@ class CommonStore { setModelSourceList = (value: ModelSourceItem[]) => { this.modelSourceList = value; }; + + setSettings = (value: Partial, saveConfig: boolean = true) => { + this.settings = {...this.settings, ...value}; + + if (this.settings.darkMode) + WindowSetDarkTheme(); + else + WindowSetLightTheme(); + + if (saveConfig) + saveConfigs(); + }; } export default new CommonStore(); \ No newline at end of file diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts index 52d5ad3..74d5eb1 100644 --- a/frontend/src/utils/index.ts +++ b/frontend/src/utils/index.ts @@ -6,10 +6,17 @@ export type Cache = { models: ModelSourceItem[] } +export type Settings = { + language: string, + darkMode: boolean + autoUpdatesCheck: boolean +} + export type LocalConfig = { modelSourceManifestList: string currentModelConfigIndex: number modelConfigs: ModelConfig[] + settings: Settings } export async function refreshBuiltInModels(readCache: boolean = false) { @@ -122,7 +129,8 @@ export const saveConfigs = async () => { const data: LocalConfig = { modelSourceManifestList: commonStore.modelSourceManifestList, currentModelConfigIndex: commonStore.currentModelConfigIndex, - modelConfigs: commonStore.modelConfigs + modelConfigs: commonStore.modelConfigs, + settings: commonStore.settings }; return SaveJson('config.json', data); }; @@ -132,4 +140,14 @@ export const saveCache = async () => { models: commonStore.modelSourceList }; return SaveJson('cache.json', data); -}; \ No newline at end of file +}; + +export function getNavigatorLanguage() { + // const l = navigator.language.toLowerCase(); + // if (['zh-hk', 'zh-mo', 'zh-tw', 'zh-cht', 'zh-hant'].includes(l)) return 'zhHant' + return navigator.language.substring(0, 2); +} + +export function isSystemLightMode() { + return window.matchMedia && window.matchMedia('(prefers-color-scheme: light)').matches; +} \ No newline at end of file diff --git a/frontend/wailsjs/go/backend_golang/App.d.ts b/frontend/wailsjs/go/backend_golang/App.d.ts index 3f3c108..c322908 100644 --- a/frontend/wailsjs/go/backend_golang/App.d.ts +++ b/frontend/wailsjs/go/backend_golang/App.d.ts @@ -19,3 +19,5 @@ export function ReadJson(arg1:string):Promise; export function SaveJson(arg1:string,arg2:any):Promise; export function StartServer(arg1:number):Promise; + +export function UpdateApp(arg1:string):Promise; diff --git a/frontend/wailsjs/go/backend_golang/App.js b/frontend/wailsjs/go/backend_golang/App.js index cb3b2d4..4c45d61 100644 --- a/frontend/wailsjs/go/backend_golang/App.js +++ b/frontend/wailsjs/go/backend_golang/App.js @@ -37,3 +37,7 @@ export function SaveJson(arg1, arg2) { export function StartServer(arg1) { return window['go']['backend_golang']['App']['StartServer'](arg1); } + +export function UpdateApp(arg1) { + return window['go']['backend_golang']['App']['UpdateApp'](arg1); +} diff --git a/go.mod b/go.mod index 7bdce5c..958411f 100644 --- a/go.mod +++ b/go.mod @@ -4,10 +4,12 @@ go 1.20 require ( github.com/cavaliergopher/grab/v3 v3.0.1 + github.com/minio/selfupdate v0.6.0 github.com/wailsapp/wails/v2 v2.5.1 ) require ( + aead.dev/minisign v0.2.0 // indirect github.com/bep/debounce v1.2.1 // indirect github.com/go-ole/go-ole v1.2.6 // indirect github.com/google/uuid v1.3.0 // indirect diff --git a/go.sum b/go.sum index 2f27fd6..ab625bc 100644 --- a/go.sum +++ b/go.sum @@ -1,3 +1,5 @@ +aead.dev/minisign v0.2.0 h1:kAWrq/hBRu4AARY6AlciO83xhNnW9UaC8YipS2uhLPk= +aead.dev/minisign v0.2.0/go.mod h1:zdq6LdSd9TbuSxchxwhpA9zEb9YXcVGoE8JakuiGaIQ= github.com/bep/debounce v1.2.1 h1:v67fRdBA9UQu2NhLFXrSg0Brw7CexQekrBwDMM8bzeY= github.com/bep/debounce v1.2.1/go.mod h1:H8yggRPQKLUhUoqrJC1bO2xNya7vanpDl7xR3ISbCJ0= github.com/cavaliergopher/grab/v3 v3.0.1 h1:4z7TkBfmPjmLAAmkkAZNX/6QJ1nNFdv3SdIHXju0Fr4= @@ -33,6 +35,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.18 h1:DOKFKCQ7FNG2L1rbrmstDN4QVRdS89Nkh85u68Uwp98= github.com/mattn/go-isatty v0.0.18/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= +github.com/minio/selfupdate v0.6.0 h1:i76PgT0K5xO9+hjzKcacQtO7+MjJ4JKA8Ak8XQ9DDwU= +github.com/minio/selfupdate v0.6.0/go.mod h1:bO02GTIPCMQFTEvE5h4DjYB58bCoZ35XLeBf0buTDdM= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8 h1:KoWmjvw+nsYOo29YJK9vDA65RGE3NrOnUtO7a+RF9HU= github.com/pkg/browser v0.0.0-20210911075715-681adbf594b8/go.mod h1:HKlIX3XHQyzLZPlr7++PzdhaXEj94dEiJgZDTsxEqUI= github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= @@ -58,17 +62,26 @@ github.com/wailsapp/mimetype v1.4.1 h1:pQN9ycO7uo4vsUUuPeHEYoUkLVkaRntMnHJxVwYhw github.com/wailsapp/mimetype v1.4.1/go.mod h1:9aV5k31bBOv5z6u+QP8TltzvNGJPmNJD4XlAL3U+j3o= github.com/wailsapp/wails/v2 v2.5.1 h1:mfG+2kWqQXYOwdgI43HEILjOZDXbk5woPYI3jP2b+js= github.com/wailsapp/wails/v2 v2.5.1/go.mod h1:jbOZbcr/zm79PxXxAjP8UoVlDd9wLW3uDs+isIthDfs= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20210220033148-5ea612d1eb83/go.mod h1:jdWPYTVW3xRLrWPugEBEK3UY2ZEsg3UU495nc5E+M+I= +golang.org/x/crypto v0.0.0-20211209193657-4570a0811e8b/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.9.0 h1:LF6fAI+IutBocDJ2OT0Q1g8plpYljMZ4+lty+dsqw3g= golang.org/x/crypto v0.9.0/go.mod h1:yrmDGqONDYtNj3tH8X9dzUun2m2lzPa9ngI6/RUPGR0= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc h1:mCRnTeVUjcrhlRmO0VK8a6k6Rrf6TF9htwo2pJVSjIU= golang.org/x/exp v0.0.0-20230515195305-f3d0a9c9a5cc/go.mod h1:V1LtkGg67GoY2N1AnLN78QLrzxkLyJw7RJb1gzOOz9w= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= golang.org/x/net v0.0.0-20210505024714-0287a6fb4125/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= +golang.org/x/net v0.0.0-20211112202133-69e39bad7dc2/go.mod h1:9nx3DQGgdP8bBQD5qxJ1jj9UTztislL4KSBs9R2vV5Y= golang.org/x/net v0.10.0 h1:X2//UzNDwYmtCLn7To6G58Wr6f5ahEAQgKNzv9Y951M= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20191026070338-33540a1f6037/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20200810151505-1b9f1253b3ed/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201119102817-f84b799fce68/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210228012217-479acdf4ea46/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210423082822-04245dca01da/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210615035016-665e8c7367d1/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210616045830-e2b7044e8c71/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20210927094055-39ccf1dd6fa6/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= @@ -77,7 +90,9 @@ golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBc golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0 h1:EBmGv8NaZBZTWvrbjNoL6HVt+IVy3QDQpJs7VRIw3tU= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/term v0.0.0-20201117132131-f5c789dd3221/go.mod h1:Nr5EML6q2oocZ2LXRh80K7BxOlk5/8JxuGnuhpl+muw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.6/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= golang.org/x/text v0.9.0 h1:2sjJmO8cDvYveuX97RDLsxlyUxLl+GHoLxBiRdHllBE= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8=