From 84257d86c1b6951c8e848dc0f0493e3b8fefecd6 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Tue, 16 Jul 2024 19:57:25 +0800 Subject: [PATCH] feat: check update --- dev-app-update.yml | 9 +- electron-builder.yml | 6 +- src/main/index.ts | 44 +++++---- src/main/updater.ts | 25 ++--- src/preload/index.d.ts | 3 + src/preload/index.ts | 3 +- src/renderer/src/CHANGELOG.en.md | 25 +++-- src/renderer/src/CHANGELOG.zh.md | 25 +++-- src/renderer/src/hooks/useAppInitEffect.ts | 7 ++ src/renderer/src/i18n/index.ts | 14 ++- src/renderer/src/init.ts | 31 ++++--- .../src/pages/settings/AboutSettings.tsx | 91 ++++++++++++++++++- .../pages/settings/components/Changelog.tsx | 2 +- .../components}/changelog.module.scss | 3 +- 14 files changed, 214 insertions(+), 74 deletions(-) rename src/renderer/src/{assets/styles => pages/settings/components}/changelog.module.scss (95%) diff --git a/dev-app-update.yml b/dev-app-update.yml index e5cc9e006f..012d045561 100644 --- a/dev-app-update.yml +++ b/dev-app-update.yml @@ -1,3 +1,6 @@ -provider: generic -url: http://127.0.0.1:8080 -updaterCacheDirName: cherry-studio-updater +# provider: generic +# url: http://127.0.0.1:8080 +# updaterCacheDirName: cherry-studio-updater +provider: github +repo: cherry-studio +owner: kangfenmao diff --git a/electron-builder.yml b/electron-builder.yml index f9b5525174..1add5c73c0 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -56,7 +56,5 @@ electronDownload: afterSign: scripts/notarize.js releaseInfo: releaseNotes: | - - 修复多语言提示错误 - - 修复智谱AI默认模型错误问题 - - 修复 OpenRouter API 检测出错问题 - - 修复模型提供商多语言翻译错误问题 + - 修复更新日志页面不能滚动问题 + - 新增检查更新按钮 diff --git a/src/main/index.ts b/src/main/index.ts index e647e33bcd..1888d7cc4a 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,13 +1,13 @@ import { electronApp, is, optimizer } from '@electron-toolkit/utils' +import * as Sentry from '@sentry/electron/main' import { app, BrowserWindow, ipcMain, Menu, MenuItem, shell } from 'electron' +import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer' import windowStateKeeper from 'electron-window-state' import { join } from 'path' import icon from '../../resources/icon.png?asset' -import installExtension, { REDUX_DEVTOOLS } from 'electron-devtools-installer' import AppUpdater from './updater' -import * as Sentry from '@sentry/electron/main' -function createWindow(): void { +function createWindow() { // Load the previous state with fallback to defaults const mainWindowState = windowStateKeeper({ defaultWidth: 1080, @@ -62,6 +62,8 @@ function createWindow(): void { } else { mainWindow.loadFile(join(__dirname, '../renderer/index.html')) } + + return mainWindow } // This method will be called when Electron has finished @@ -78,26 +80,36 @@ app.whenReady().then(() => { optimizer.watchWindowShortcuts(window) }) - // IPC - ipcMain.handle('get-app-info', () => ({ - version: app.getVersion() - })) - - createWindow() - app.on('activate', function () { // On macOS it's common to re-create a window in the app when the // dock icon is clicked and there are no other windows open. if (BrowserWindow.getAllWindows().length === 0) createWindow() }) - installExtension(REDUX_DEVTOOLS) - .then((name) => console.log(`Added Extension: ${name}`)) - .catch((err) => console.log('An error occurred: ', err)) + const mainWindow = createWindow() - if (app.isPackaged) { - setTimeout(() => new AppUpdater(), 3000) - } + const { autoUpdater } = new AppUpdater(mainWindow) + + // IPC + ipcMain.handle('get-app-info', () => ({ + version: app.getVersion(), + isPackaged: app.isPackaged + })) + + ipcMain.handle('open-website', (_, url: string) => { + shell.openExternal(url) + }) + + // 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法) + ipcMain.handle('check-for-update', async () => { + autoUpdater.logger?.info('触发检查更新') + return { + currentVersion: autoUpdater.currentVersion, + update: await autoUpdater.checkForUpdates() + } + }) + + installExtension(REDUX_DEVTOOLS) }) // Quit when all windows are closed, except on macOS. There, it's common diff --git a/src/main/updater.ts b/src/main/updater.ts index aef817aa81..05b8aca44e 100644 --- a/src/main/updater.ts +++ b/src/main/updater.ts @@ -1,24 +1,20 @@ -import { autoUpdater, UpdateInfo } from 'electron-updater' +import { AppUpdater as _AppUpdater, autoUpdater, UpdateInfo } from 'electron-updater' import logger from 'electron-log' -import { dialog, ipcMain } from 'electron' +import { BrowserWindow, dialog } from 'electron' export default class AppUpdater { - constructor() { + autoUpdater: _AppUpdater = autoUpdater + + constructor(mainWindow: BrowserWindow) { logger.transports.file.level = 'debug' autoUpdater.logger = logger autoUpdater.forceDevUpdateConfig = true autoUpdater.autoDownload = false - autoUpdater.checkForUpdates() - - // 触发检查更新(此方法用于被渲染线程调用,例如页面点击检查更新按钮来调用此方法) - ipcMain.on('check-for-update', () => { - logger.info('触发检查更新') - return autoUpdater.checkForUpdates() - }) // 检测下载错误 autoUpdater.on('error', (error) => { logger.error('更新异常', error) + mainWindow.webContents.send('update-error', error) }) // 检测是否需要更新 @@ -28,6 +24,7 @@ export default class AppUpdater { autoUpdater.on('update-available', (releaseInfo: UpdateInfo) => { autoUpdater.logger?.info('检测到新版本,确认是否下载') + mainWindow.webContents.send('update-available', releaseInfo) const releaseNotes = releaseInfo.releaseNotes let releaseContent = '' if (releaseNotes) { @@ -49,10 +46,12 @@ export default class AppUpdater { title: '应用有新的更新', detail: releaseContent, message: '发现新版本,是否现在更新?', - buttons: ['否', '是'] + buttons: ['下次再说', '更新'] }) .then(({ response }) => { if (response === 1) { + logger.info('用户选择更新,准备下载更新') + mainWindow.webContents.send('download-update') autoUpdater.downloadUpdate() } }) @@ -61,11 +60,13 @@ export default class AppUpdater { // 检测到不需要更新时 autoUpdater.on('update-not-available', () => { logger.info('现在使用的就是最新版本,不用更新') + mainWindow.webContents.send('update-not-available') }) // 更新下载进度 autoUpdater.on('download-progress', (progress) => { logger.info('下载进度', progress) + mainWindow.webContents.send('download-progress', progress) }) // 当需要更新的内容下载完成后 @@ -80,5 +81,7 @@ export default class AppUpdater { setImmediate(() => autoUpdater.quitAndInstall()) }) }) + + this.autoUpdater = autoUpdater } } diff --git a/src/preload/index.d.ts b/src/preload/index.d.ts index 69767f0f36..01c52c8a08 100644 --- a/src/preload/index.d.ts +++ b/src/preload/index.d.ts @@ -6,7 +6,10 @@ declare global { api: { getAppInfo: () => Promise<{ version: string + isPackaged: boolean }> + checkForUpdate: () => void + openWebsite: (url: string) => void } } } diff --git a/src/preload/index.ts b/src/preload/index.ts index e9cc413d90..4523d7fdb0 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -4,7 +4,8 @@ import { electronAPI } from '@electron-toolkit/preload' // Custom APIs for renderer const api = { getAppInfo: () => ipcRenderer.invoke('get-app-info'), - checkForUpdate: () => ipcRenderer.invoke('check-for-update') + checkForUpdate: () => ipcRenderer.invoke('check-for-update'), + openWebsite: (url: string) => ipcRenderer.invoke('open-website', url) } // Use `contextBridge` APIs to expose Electron APIs to diff --git a/src/renderer/src/CHANGELOG.en.md b/src/renderer/src/CHANGELOG.en.md index 9b21cec699..15544adef3 100644 --- a/src/renderer/src/CHANGELOG.en.md +++ b/src/renderer/src/CHANGELOG.en.md @@ -1,20 +1,25 @@ # CHANGES LOG +### v0.2.4 - 2024-07-16 + +- Fixed the issue of the update log page cannot be scrolled +- Added a check for updates button + ### v0.2.3 - 2024-07-16 -1. Fixed multi-language prompt errors -2. Fixed default model error issues with ZHIPU AI -3. Fixed OpenRouter API detection error issues -4. Fixed multi-language translation errors with model providers +- Fixed multi-language prompt errors +- Fixed default model error issues with ZHIPU AI +- Fixed OpenRouter API detection error issues +- Fixed multi-language translation errors with model providers ### v0.2.2 - 2024-07-15 -1. Fix the issue where the default assistant name is empty. -2. Fix the problem with default language detection during the first installation. -3. Adjust the changelog style. +- Fix the issue where the default assistant name is empty. +- Fix the problem with default language detection during the first installation. +- Adjust the changelog style. ### v0.2.1 - 2024-07-15 -1. **Feature**: Add new feature for pausing message sending -2. **Fix**: Resolve incomplete translation issue upon language switch -3. **Build**: Support for macOS Intel architecture +- **Feature**: Add new feature for pausing message sending +- **Fix**: Resolve incomplete translation issue upon language switch +- **Build**: Support for macOS Intel architecture diff --git a/src/renderer/src/CHANGELOG.zh.md b/src/renderer/src/CHANGELOG.zh.md index 0e41b0c324..d37028fd0e 100644 --- a/src/renderer/src/CHANGELOG.zh.md +++ b/src/renderer/src/CHANGELOG.zh.md @@ -1,21 +1,26 @@ # 更新日志 +### v0.2.4 - 2024-07-16 + +- 修复更新日志页面不能滚动问题 +- 新增检查更新按钮 + ### v0.2.3 - 2024-07-16 -1. 修复多语言提示错误 -2. 修复智谱AI默认模型错误问题 -3. 修复 OpenRouter API 检测出错问题 -4. 修复模型提供商多语言翻译错误问题 +- 修复多语言提示错误 +- 修复智谱AI默认模型错误问题 +- 修复 OpenRouter API 检测出错问题 +- 修复模型提供商多语言翻译错误问题 ### v0.2.2 - 2024-07-15 -1. 修复默认助理名称为空的问题 -2. 修复首次安装默认语言检测问题 -3. 更新日志样式微调 +- 修复默认助理名称为空的问题 +- 修复首次安装默认语言检测问题 +- 更新日志样式微调 ### v0.2.1 - 2024-07-15 -1. 【功能】新增消息暂停发送功能 -2. 【修复】修复多语言切换不彻底问题 -3. 【构建】支持 macOS Intel 架构 +- 【功能】新增消息暂停发送功能 +- 【修复】修复多语言切换不彻底问题 +- 【构建】支持 macOS Intel 架构 diff --git a/src/renderer/src/hooks/useAppInitEffect.ts b/src/renderer/src/hooks/useAppInitEffect.ts index 98ad872c57..fd88969108 100644 --- a/src/renderer/src/hooks/useAppInitEffect.ts +++ b/src/renderer/src/hooks/useAppInitEffect.ts @@ -15,4 +15,11 @@ export function useAppInitEffect() { }) i18nInit() }, [dispatch]) + + useEffect(() => { + runAsyncFunction(async () => { + const { isPackaged } = await window.api.getAppInfo() + isPackaged && setTimeout(window.api.checkForUpdate, 3000) + }) + }, []) } diff --git a/src/renderer/src/i18n/index.ts b/src/renderer/src/i18n/index.ts index 765a509a90..0d09a031ce 100644 --- a/src/renderer/src/i18n/index.ts +++ b/src/renderer/src/i18n/index.ts @@ -108,7 +108,12 @@ const resources = { 'models.add.group_name.placeholder': 'Optional e.g. ChatGPT', 'models.empty': 'No models found', 'assistant.title': 'Default Assistant', - 'about.description': 'A powerful AI assistant for producer' + 'about.description': 'A powerful AI assistant for producer', + 'about.updateNotAvailable': 'You are using the latest version', + 'about.checkingUpdate': 'Checking for updates...', + 'about.updateError': 'Update error', + 'about.checkUpdate': 'Check Update', + 'about.downloading': 'Downloading...' } } }, @@ -217,7 +222,12 @@ const resources = { 'models.add.group_name.placeholder': '例如 ChatGPT', 'models.empty': '没有模型', 'assistant.title': '默认助手', - 'about.description': '一个为创造者而生的 AI 助手' + 'about.description': '一个为创造者而生的 AI 助手', + 'about.updateNotAvailable': '你的软件已是最新版本', + 'about.checkingUpdate': '正在检查更新...', + 'about.updateError': '更新出错', + 'about.checkUpdate': '检查更新', + 'about.downloading': '正在下载更新...' } } } diff --git a/src/renderer/src/init.ts b/src/renderer/src/init.ts index 2e350a7410..46d07e8aa6 100644 --- a/src/renderer/src/init.ts +++ b/src/renderer/src/init.ts @@ -2,17 +2,11 @@ import localforage from 'localforage' import KeyvStorage from '@kangfenmao/keyv-storage' import * as Sentry from '@sentry/electron/renderer' -function init() { - localforage.config({ - driver: localforage.INDEXEDDB, - name: 'CherryAI', - version: 1.0, - storeName: 'cherryai', - description: 'Cherry Studio Storage' - }) - - window.keyv = new KeyvStorage() - window.keyv.init() +function initSentry() { + // Disable sentry in development mode + if (process?.env?.NODE_ENV === 'development') { + return + } Sentry.init({ integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()], @@ -29,4 +23,19 @@ function init() { }) } +function init() { + localforage.config({ + driver: localforage.INDEXEDDB, + name: 'CherryAI', + version: 1.0, + storeName: 'cherryai', + description: 'Cherry Studio Storage' + }) + + window.keyv = new KeyvStorage() + window.keyv.init() + + initSentry() +} + init() diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index c88033a93b..65de78d4ca 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -1,14 +1,34 @@ -import { Avatar } from 'antd' +import { Avatar, Button, Progress } from 'antd' import { FC, useEffect, useState } from 'react' import styled from 'styled-components' import Logo from '@renderer/assets/images/logo.png' import { runAsyncFunction } from '@renderer/utils' import { useTranslation } from 'react-i18next' import Changelog from './components/Changelog' +import { debounce } from 'lodash' +import { ProgressInfo } from 'electron-updater' const AboutSettings: FC = () => { const [version, setVersion] = useState('') const { t } = useTranslation() + const [percent, setPercent] = useState(0) + const [checkUpdateLoading, setCheckUpdateLoading] = useState(false) + const [downloading, setDownloading] = useState(false) + + const onCheckUpdate = debounce( + async () => { + if (checkUpdateLoading || downloading) return + setCheckUpdateLoading(true) + await window.api.checkForUpdate() + setCheckUpdateLoading(false) + }, + 2000, + { leading: true, trailing: false } + ) + + const onOpenWebsite = (suffix = '') => { + window.api.openWebsite('https://github.com/kangfenmao/cherry-studio' + suffix) + } useEffect(() => { runAsyncFunction(async () => { @@ -17,20 +37,65 @@ const AboutSettings: FC = () => { }) }, []) + useEffect(() => { + const ipcRenderer = window.electron.ipcRenderer + const removers = [ + ipcRenderer.on('update-not-available', () => { + setCheckUpdateLoading(false) + window.message.success(t('settings.about.updateNotAvailable')) + }), + ipcRenderer.on('update-available', () => { + setCheckUpdateLoading(false) + }), + ipcRenderer.on('download-update', () => { + setCheckUpdateLoading(false) + setDownloading(true) + }), + ipcRenderer.on('download-progress', (_, progress: ProgressInfo) => { + setPercent(progress.percent) + }), + ipcRenderer.on('update-error', (_, error) => { + setCheckUpdateLoading(false) + setDownloading(false) + setPercent(0) + window.modal.info({ + title: t('settings.about.updateError'), + content: error?.message || t('settings.about.updateError'), + icon: null + }) + }) + ] + return () => removers.forEach((remover) => remover()) + }, [t]) + return ( - + onOpenWebsite()}> + {percent > 0 && ( + + )} + + - Cherry Studio <Version>(v{version})</Version> + Cherry Studio <Version onClick={() => onOpenWebsite('/releases')}>(v{version})</Version> {t('settings.about.description')} + + {downloading ? t('settings.about.downloading') : t('settings.about.checkUpdate')} + ) } const Container = styled.div` - padding: 20px; display: flex; flex: 1; flex-direction: column; @@ -38,6 +103,8 @@ const Container = styled.div` justify-content: flex-start; height: calc(100vh - var(--navbar-height)); overflow-y: scroll; + padding: 0; + padding-bottom: 50px; ` const Title = styled.div` @@ -52,6 +119,7 @@ const Version = styled.span` color: var(--color-text-2); margin: 10px 0; text-align: center; + cursor: pointer; ` const Description = styled.div` @@ -60,4 +128,19 @@ const Description = styled.div` text-align: center; ` +const CheckUpdateButton = styled(Button)` + margin-top: 10px; +` + +const AvatarWrapper = styled.div` + position: relative; + cursor: pointer; +` + +const ProgressCircle = styled(Progress)` + position: absolute; + top: 48px; + left: -2px; +` + export default AboutSettings diff --git a/src/renderer/src/pages/settings/components/Changelog.tsx b/src/renderer/src/pages/settings/components/Changelog.tsx index b5f58a2521..a842c9b960 100644 --- a/src/renderer/src/pages/settings/components/Changelog.tsx +++ b/src/renderer/src/pages/settings/components/Changelog.tsx @@ -3,7 +3,7 @@ import changelogZh from '@renderer/CHANGELOG.zh.md?raw' import { FC } from 'react' import Markdown from 'react-markdown' import styled from 'styled-components' -import styles from '@renderer/assets/styles/changelog.module.scss' +import styles from './changelog.module.scss' import i18n from '@renderer/i18n' const Changelog: FC = () => { diff --git a/src/renderer/src/assets/styles/changelog.module.scss b/src/renderer/src/pages/settings/components/changelog.module.scss similarity index 95% rename from src/renderer/src/assets/styles/changelog.module.scss rename to src/renderer/src/pages/settings/components/changelog.module.scss index 3f7a62e4a2..7cda3bd080 100644 --- a/src/renderer/src/assets/styles/changelog.module.scss +++ b/src/renderer/src/pages/settings/components/changelog.module.scss @@ -69,7 +69,8 @@ $code-color: #f0e7db; ul, ol { - padding-left: 30px; + padding-left: 20px; + list-style: disc; } li {