diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 04e7287f..918f2380 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -27,7 +27,7 @@ const backupManager = new BackupManager() const exportService = new ExportService(fileManager) export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { - const { autoUpdater } = new AppUpdater(mainWindow) + const appUpdater = new AppUpdater(mainWindow) ipcMain.handle('app:info', () => ({ version: app.getVersion(), @@ -48,6 +48,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ipcMain.handle('app:reload', () => mainWindow.reload()) ipcMain.handle('open:website', (_, url: string) => shell.openExternal(url)) + // Update + ipcMain.handle('app:show-update-dialog', () => appUpdater.showUpdateDialog(mainWindow)) + // language ipcMain.handle('app:set-language', (_, language) => { configManager.setLanguage(language) @@ -99,9 +102,9 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { // check for update ipcMain.handle('app:check-for-update', async () => { - const update = await autoUpdater.checkForUpdates() + const update = await appUpdater.autoUpdater.checkForUpdates() return { - currentVersion: autoUpdater.currentVersion, + currentVersion: appUpdater.autoUpdater.currentVersion, updateInfo: update?.updateInfo } }) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 34475289..f6e1c4e4 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,11 +1,13 @@ +import { UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' -import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater' +import { AppUpdater as _AppUpdater, autoUpdater } from 'electron-updater' import icon from '../../../build/icon.png?asset' export default class AppUpdater { autoUpdater: _AppUpdater = autoUpdater + private releaseInfo: UpdateInfo | undefined constructor(mainWindow: BrowserWindow) { logger.transports.file.level = 'info' @@ -37,34 +39,40 @@ export default class AppUpdater { // 当需要更新的内容下载完成后 autoUpdater.on('update-downloaded', (releaseInfo: UpdateInfo) => { - mainWindow.webContents.send('update-downloaded') - - logger.info('下载完成,询问用户是否更新', releaseInfo) - - dialog - .showMessageBox({ - type: 'info', - title: '安装更新', - icon, - message: `新版本 ${releaseInfo.version} 已准备就绪`, - detail: this.formatReleaseNotes(releaseInfo.releaseNotes), - buttons: ['稍后安装', '立即安装'], - defaultId: 1, - cancelId: 0 - }) - .then(({ response }) => { - if (response === 1) { - app.isQuitting = true - setImmediate(() => autoUpdater.quitAndInstall()) - } else { - mainWindow.webContents.send('update-downloaded-cancelled') - } - }) + mainWindow.webContents.send('update-downloaded', releaseInfo) + this.releaseInfo = releaseInfo + logger.info('下载完成', releaseInfo) }) this.autoUpdater = autoUpdater } + public async showUpdateDialog(mainWindow: BrowserWindow) { + if (!this.releaseInfo) { + return + } + + dialog + .showMessageBox({ + type: 'info', + title: '安装更新', + icon, + message: `新版本 ${this.releaseInfo.version} 已准备就绪`, + detail: this.formatReleaseNotes(this.releaseInfo.releaseNotes), + buttons: ['稍后安装', '立即安装'], + defaultId: 1, + cancelId: 0 + }) + .then(({ response }) => { + if (response === 1) { + app.isQuitting = true + setImmediate(() => autoUpdater.quitAndInstall()) + } else { + mainWindow.webContents.send('update-downloaded-cancelled') + } + }) + } + private formatReleaseNotes(releaseNotes: string | ReleaseNoteInfo[] | null | undefined): string { if (!releaseNotes) { return '暂无更新说明' diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 7ae73dfc..bb0624bf 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -15,6 +15,7 @@ declare global { api: { getAppInfo: () => Promise checkForUpdate: () => Promise<{ currentVersion: string; updateInfo: UpdateInfo | null }> + showUpdateDialog: () => Promise openWebsite: (url: string) => void setProxy: (proxy: string | undefined) => void setLanguage: (theme: LanguageVarious) => void diff --git a/src/preload/index.ts b/src/preload/index.ts index 079dd041..ca93f359 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -8,6 +8,7 @@ const api = { reload: () => ipcRenderer.invoke('app:reload'), setProxy: (proxy: string) => ipcRenderer.invoke('app:proxy', proxy), checkForUpdate: () => ipcRenderer.invoke('app:check-for-update'), + showUpdateDialog: () => ipcRenderer.invoke('app:show-update-dialog'), setLanguage: (lang: string) => ipcRenderer.invoke('app:set-language', lang), setTray: (isActive: boolean) => ipcRenderer.invoke('app:set-tray', isActive), restartTray: () => ipcRenderer.invoke('app:restart-tray'), diff --git a/src/renderer/src/hooks/useUpdateHandler.ts b/src/renderer/src/hooks/useUpdateHandler.ts index bc47001a..a8fe07f8 100644 --- a/src/renderer/src/hooks/useUpdateHandler.ts +++ b/src/renderer/src/hooks/useUpdateHandler.ts @@ -1,6 +1,6 @@ import { useAppDispatch } from '@renderer/store' import { setUpdateState } from '@renderer/store/runtime' -import type { ProgressInfo, UpdateInfo } from 'electron-updater' +import type { ProgressInfo, UpdateInfo } from 'builder-util-runtime' import { useEffect } from 'react' import { useTranslation } from 'react-i18next' @@ -46,8 +46,14 @@ export default function useUpdateHandler() { }) ) }), - ipcRenderer.on('update-downloaded', () => { - dispatch(setUpdateState({ downloading: false })) + ipcRenderer.on('update-downloaded', (_, releaseInfo: UpdateInfo) => { + dispatch( + setUpdateState({ + downloading: false, + info: releaseInfo, + downloaded: true + }) + ) }), ipcRenderer.on('update-error', (_, error) => { dispatch( diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 0fb0b24c..7a7a8fbb 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -68,7 +68,8 @@ "collapse": "Collapse", "manage": "Manage", "select_model": "Select Model", - "show.all": "Show All" + "show.all": "Show All", + "update_available": "Update Available" }, "chat": { "add.assistant.title": "Add Assistant", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index a7f6e126..68bc75d5 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -68,7 +68,8 @@ "collapse": "折りたたむ", "manage": "管理", "select_model": "モデルを選択", - "show.all": "すべて表示" + "show.all": "すべて表示", + "update_available": "更新可能" }, "chat": { "add.assistant.title": "アシスタントを追加", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 2eec2498..bb6a43f1 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -68,7 +68,8 @@ "collapse": "Свернуть", "manage": "Редактировать", "select_model": "Выбрать модель", - "show.all": "Показать все" + "show.all": "Показать все", + "update_available": "Доступно обновление" }, "chat": { "add.assistant.title": "Добавить ассистента", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 933e6c2d..6f0c1577 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -68,7 +68,8 @@ "collapse": "收起", "manage": "管理", "select_model": "选择模型", - "show.all": "显示全部" + "show.all": "显示全部", + "update_available": "有可用更新" }, "chat": { "add.assistant.title": "添加助手", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 146f8080..0faa45ba 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -68,7 +68,8 @@ "collapse": "收起", "manage": "管理", "select_model": "選擇模型", - "show.all": "顯示全部" + "show.all": "顯示全部", + "update_available": "有可用更新" }, "chat": { "add.assistant.title": "添加助手", diff --git a/src/renderer/src/pages/home/Navbar.tsx b/src/renderer/src/pages/home/Navbar.tsx index 5a935d2f..34895581 100644 --- a/src/renderer/src/pages/home/Navbar.tsx +++ b/src/renderer/src/pages/home/Navbar.tsx @@ -18,6 +18,7 @@ import { FC } from 'react' import styled from 'styled-components' import SelectModelButton from './components/SelectModelButton' +import UpdateAppButton from './components/UpdateAppButton' interface Props { activeAssistant: Assistant @@ -83,6 +84,7 @@ const HeaderNavbar: FC = ({ activeAssistant }) => { + SearchPopup.show()}> diff --git a/src/renderer/src/pages/home/components/UpdateAppButton.tsx b/src/renderer/src/pages/home/components/UpdateAppButton.tsx new file mode 100644 index 00000000..6ac6c028 --- /dev/null +++ b/src/renderer/src/pages/home/components/UpdateAppButton.tsx @@ -0,0 +1,45 @@ +import { SyncOutlined } from '@ant-design/icons' +import { useRuntime } from '@renderer/hooks/useRuntime' +import { Button } from 'antd' +import { FC } from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +const UpdateAppButton: FC = () => { + const { update } = useRuntime() + const { t } = useTranslation() + + if (!update) { + return null + } + + if (!update.downloaded) { + return null + } + + return ( + + window.api.showUpdateDialog()} + icon={} + color="orange" + variant="outlined" + size="small"> + {t('button.update_available')} + + + ) +} + +const Container = styled.div`` + +const UpdateButton = styled(Button)` + border-radius: 24px; + font-size: 12px; + @media (max-width: 1000px) { + display: none; + } +` + +export default UpdateAppButton diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index ada2d000..11618d78 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -36,6 +36,11 @@ const AboutSettings: FC = () => { return } + if (update.downloaded) { + window.api.showUpdateDialog() + return + } + dispatch(setUpdateState({ checking: true })) try { diff --git a/src/renderer/src/store/runtime.ts b/src/renderer/src/store/runtime.ts index 0f1359c2..e7962ec3 100644 --- a/src/renderer/src/store/runtime.ts +++ b/src/renderer/src/store/runtime.ts @@ -1,11 +1,12 @@ import { createSlice, PayloadAction } from '@reduxjs/toolkit' import { AppLogo, UserAvatar } from '@renderer/config/env' -import type { UpdateInfo } from 'electron-updater' +import type { UpdateInfo } from 'builder-util-runtime' export interface UpdateState { info: UpdateInfo | null checking: boolean downloading: boolean + downloaded: boolean downloadProgress: number available: boolean } @@ -43,6 +44,7 @@ const initialState: RuntimeState = { info: null, checking: false, downloading: false, + downloaded: false, downloadProgress: 0, available: false },