From 9f3e7fcc428076718eea732c1cf2a9db4aee151c Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Fri, 6 Jun 2025 15:48:54 +0800 Subject: [PATCH] feat: add set feed url functionality for early access (#5723) * feat: add update channel functionality for beta testing - Introduced a new IPC channel for setting the update channel. - Implemented logic in AppUpdater to handle update channel changes. - Updated settings to include a beta testing toggle, allowing users to switch between stable and beta update channels. - Enhanced the settings UI to reflect the new beta testing option. * add i18n * update i18n * update i18n * refactor: rename update channel to feed URL and update related functionality - Changed IPC channel from App_SetUpdateChannel to App_SetFeedUrl. - Updated AppUpdater to set feed URL instead of update channel. - Modified preload and settings to reflect the new feed URL functionality. - Added constants for production and early access feed URLs. * refactor: remove setAutoUpdate method from API - Eliminated the setAutoUpdate method from the API object in preload index, streamlining the IPC communication interface. * refactor: update early access feed URL and improve tooltip descriptions - Changed EARLY_ACCESS_FEED_URL to point to the latest GitHub release. - Simplified the setEarlyAccess function to directly set the feed URL. - Added tooltips for early access settings in multiple languages to inform users about potential instability and the need for data backup. * feat(migrate): add early access setting to state configuration - Introduced a new state setting 'earlyAccess' and initialized it to false in the migration configuration. * fix(i18n): update early access tooltip translations for clarity - Revised the tooltip descriptions for the early access feature in English, Simplified Chinese, and Traditional Chinese to enhance clarity and ensure consistency in messaging regarding potential instability and the importance of data backup. * feat: introduce FeedUrl enum for centralized feed URL management - Added a new enum `FeedUrl` in the constants file to define production and early access feed URLs. - Updated relevant IPC handlers and services to utilize the `FeedUrl` enum for type safety and consistency. - Refactored the configuration manager to include methods for getting and setting the feed URL using the new enum. * feat(settings): initialize early access and auto-update settings in AboutSettings component - Added initialization for early access and auto-check update settings in the AboutSettings component to enhance user configuration options. --------- Co-authored-by: beyondkmp --- packages/shared/IpcChannel.ts | 1 + packages/shared/config/constant.ts | 5 +++++ src/main/ipc.ts | 5 +++++ src/main/services/AppUpdater.ts | 7 +++++++ src/main/services/ConfigManager.ts | 11 ++++++++++- src/preload/index.ts | 2 ++ src/renderer/src/hooks/useSettings.ts | 7 +++++++ src/renderer/src/i18n/locales/en-us.json | 2 ++ src/renderer/src/i18n/locales/ja-jp.json | 2 ++ src/renderer/src/i18n/locales/ru-ru.json | 4 +++- src/renderer/src/i18n/locales/zh-cn.json | 2 ++ src/renderer/src/i18n/locales/zh-tw.json | 4 +++- src/renderer/src/pages/settings/AboutSettings.tsx | 15 +++++++++++++-- src/renderer/src/store/migrate.ts | 1 + src/renderer/src/store/settings.ts | 6 ++++++ 15 files changed, 69 insertions(+), 5 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 8c74ffcae..9cc3a1d99 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -13,6 +13,7 @@ export enum IpcChannel { App_SetTrayOnClose = 'app:set-tray-on-close', App_SetTheme = 'app:set-theme', App_SetAutoUpdate = 'app:set-auto-update', + App_SetFeedUrl = 'app:set-feed-url', App_HandleZoomFactor = 'app:handle-zoom-factor', App_IsBinaryExist = 'app:is-binary-exist', diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index e1fca4e6d..cfba46df7 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -403,3 +403,8 @@ export const KB = 1024 export const MB = 1024 * KB export const GB = 1024 * MB export const defaultLanguage = 'en-US' + +export enum FeedUrl { + PRODUCTION = 'https://releases.cherry-ai.com', + EARLY_ACCESS = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download' +} diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 2fd60377c..5339e8169 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -34,6 +34,7 @@ import { calculateDirectorySize, getResourcePath } from './utils' import { decrypt, encrypt } from './utils/aes' import { getCacheDir, getConfigDir, getFilesDir } from './utils/file' import { compress, decompress } from './utils/zip' +import { FeedUrl } from '@shared/config/constant' const fileManager = new FileStorage() const backupManager = new BackupManager() @@ -112,6 +113,10 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { configManager.setAutoUpdate(isActive) }) + ipcMain.handle(IpcChannel.App_SetFeedUrl, (_, feedUrl: FeedUrl) => { + appUpdater.setFeedUrl(feedUrl) + }) + ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => { configManager.set(key, value, isNotify) }) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 1733bc606..772c885a0 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,6 +1,7 @@ import { isWin } from '@main/constant' import { locales } from '@main/utils/locales' import { IpcChannel } from '@shared/IpcChannel' +import { FeedUrl } from '@shared/config/constant' import { UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' @@ -20,6 +21,7 @@ export default class AppUpdater { autoUpdater.forceDevUpdateConfig = !app.isPackaged autoUpdater.autoDownload = configManager.getAutoUpdate() autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate() + autoUpdater.setFeedURL(configManager.getFeedUrl()) // 检测下载错误 autoUpdater.on('error', (error) => { @@ -62,6 +64,11 @@ export default class AppUpdater { autoUpdater.autoInstallOnAppQuit = isActive } + public setFeedUrl(feedUrl: FeedUrl) { + autoUpdater.setFeedURL(feedUrl) + configManager.setFeedUrl(feedUrl) + } + public async checkForUpdates() { if (isWin && 'PORTABLE_EXECUTABLE_DIR' in process.env) { return { diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index c4e9c6555..573674bd7 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -1,4 +1,4 @@ -import { defaultLanguage, ZOOM_SHORTCUTS } from '@shared/config/constant' +import { defaultLanguage, FeedUrl, ZOOM_SHORTCUTS } from '@shared/config/constant' import { LanguageVarious, Shortcut, ThemeMode } from '@types' import { app } from 'electron' import Store from 'electron-store' @@ -16,6 +16,7 @@ export enum ConfigKeys { ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant', EnableQuickAssistant = 'enableQuickAssistant', AutoUpdate = 'autoUpdate', + FeedUrl = 'feedUrl', EnableDataCollection = 'enableDataCollection', SelectionAssistantEnabled = 'selectionAssistantEnabled', SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode', @@ -141,6 +142,14 @@ export class ConfigManager { this.set(ConfigKeys.AutoUpdate, value) } + getFeedUrl(): string { + return this.get(ConfigKeys.FeedUrl, FeedUrl.PRODUCTION) + } + + setFeedUrl(value: FeedUrl) { + this.set(ConfigKeys.FeedUrl, value) + } + getEnableDataCollection(): boolean { return this.get(ConfigKeys.EnableDataCollection, true) } diff --git a/src/preload/index.ts b/src/preload/index.ts index 5e9f3681c..4ac4b39e9 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -5,6 +5,7 @@ import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, Them import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' import { Notification } from 'src/renderer/src/types/notification' import { CreateDirectoryOptions } from 'webdav' +import { FeedUrl } from '@shared/config/constant' import type { ActionItem } from '../renderer/src/types/selectionTypes' @@ -20,6 +21,7 @@ const api = { setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, isActive), + setFeedUrl: (feedUrl: FeedUrl) => ipcRenderer.invoke(IpcChannel.App_SetFeedUrl, feedUrl), setTheme: (theme: ThemeMode) => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme), handleZoomFactor: (delta: number, reset: boolean = false) => ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), diff --git a/src/renderer/src/hooks/useSettings.ts b/src/renderer/src/hooks/useSettings.ts index c058a9523..10f9ee900 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -4,6 +4,7 @@ import { SendMessageShortcut, setAssistantIconType, setAutoCheckUpdate as _setAutoCheckUpdate, + setEarlyAccess as _setEarlyAccess, setLaunchOnBoot, setLaunchToTray, setPinTopicsToTop, @@ -19,6 +20,7 @@ import { setWindowStyle } from '@renderer/store/settings' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types' +import { FeedUrl } from '@shared/config/constant' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -58,6 +60,11 @@ export function useSettings() { window.api.setAutoUpdate(isAutoUpdate) }, + setEarlyAccess(isEarlyAccess: boolean) { + dispatch(_setEarlyAccess(isEarlyAccess)) + window.api.setFeedUrl(isEarlyAccess ? FeedUrl.EARLY_ACCESS : FeedUrl.PRODUCTION) + }, + setTheme(theme: ThemeMode) { dispatch(setTheme(theme)) }, diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 2e8d29bda..c31ae341e 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1345,6 +1345,8 @@ "general.emoji_picker": "Emoji Picker", "general.image_upload": "Image Upload", "general.auto_check_update.title": "Auto Update", + "general.early_access.title": "Early Access", + "general.early_access.tooltip": "Enable to use the latest version from GitHub, which may be slower. Please backup your data in advance.", "general.reset.button": "Reset", "general.reset.title": "Data Reset", "general.restore.button": "Restore", diff --git a/src/renderer/src/i18n/locales/ja-jp.json b/src/renderer/src/i18n/locales/ja-jp.json index 7b1bfce8e..8467a1c1b 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1758,6 +1758,8 @@ "content_limit_tooltip": "検索結果の内容長を制限し、制限を超える内容は切り捨てられます。" }, "general.auto_check_update.title": "自動更新", + "general.early_access.title": "早期アクセス", + "general.early_access.tooltip": "有効にすると、GitHub の最新バージョンを使用します。ダウンロード速度が遅く、不安定な場合があります。データを事前にバックアップしてください。", "quickPhrase": { "title": "クイックフレーズ", "add": "フレーズを追加", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index 8fd2f9159..2aae5c5ba 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1757,7 +1757,9 @@ "content_limit": "Ограничение длины текста", "content_limit_tooltip": "Ограничьте длину содержимого результатов поиска, контент, превышающий ограничение, будет обрезан." }, - "general.auto_check_update.title": "Включить автообновление", + "general.auto_check_update.title": "Автоматическое обновление", + "general.early_access.title": "Ранний доступ", + "general.early_access.tooltip": "Включить для использования последней версии из GitHub, что может быть медленнее и нестабильно. Пожалуйста, сделайте резервную копию данных заранее.", "quickPhrase": { "title": "Быстрые фразы", "add": "Добавить фразу", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 228e738dc..04feec6f3 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1345,6 +1345,8 @@ "general.emoji_picker": "表情选择器", "general.image_upload": "图片上传", "general.auto_check_update.title": "自动更新", + "general.early_access.title": "抢先体验", + "general.early_access.tooltip": "开启后,将使用 GitHub 的最新版本,下载速度可能较慢,请务必提前备份数据", "general.reset.button": "重置", "general.reset.title": "重置数据", "general.restore.button": "恢复", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index 02f02bf42..b205a7d8d 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1760,7 +1760,9 @@ "content_limit": "內容長度限制", "content_limit_tooltip": "限制搜尋結果的內容長度,超過限制的內容將被截斷" }, - "general.auto_check_update.title": "啟用自動更新", + "general.auto_check_update.title": "自動更新", + "general.early_access.title": "搶先體驗", + "general.early_access.tooltip": "開啟後,將使用 GitHub 的最新版本,下載速度可能較慢,請務必提前備份數據", "quickPhrase": { "title": "快捷短語", "add": "新增短語", diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index bd7e174f6..0c292497c 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -10,7 +10,7 @@ import { useAppDispatch } from '@renderer/store' import { setUpdateState } from '@renderer/store/runtime' import { ThemeMode } from '@renderer/types' import { compareVersions, runAsyncFunction } from '@renderer/utils' -import { Avatar, Button, Progress, Row, Switch, Tag } from 'antd' +import { Avatar, Button, Progress, Row, Switch, Tag, Tooltip } from 'antd' import { debounce } from 'lodash' import { Bug, FileCheck, Github, Globe, Mail, Rss } from 'lucide-react' import { FC, useEffect, useState } from 'react' @@ -25,7 +25,7 @@ const AboutSettings: FC = () => { const [version, setVersion] = useState('') const [isPortable, setIsPortable] = useState(false) const { t } = useTranslation() - const { autoCheckUpdate, setAutoCheckUpdate } = useSettings() + const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess } = useSettings() const { theme } = useTheme() const dispatch = useAppDispatch() const { update } = useRuntime() @@ -101,6 +101,10 @@ const AboutSettings: FC = () => { setVersion(appInfo.version) setIsPortable(appInfo.isPortable) }) + + // 初始化设置 + setEarlyAccess(earlyAccess) + setAutoCheckUpdate(autoCheckUpdate) }, []) return ( @@ -161,6 +165,13 @@ const AboutSettings: FC = () => { {t('settings.general.auto_check_update.title')} setAutoCheckUpdate(v)} /> + + + {t('settings.general.early_access.title')} + + setEarlyAccess(v)} /> + + )} diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index bf19e7b6d..abec96fa7 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -1480,6 +1480,7 @@ const migrateConfig = { state.paintings.tokenFluxPaintings = [] } state.settings.showTokens = true + state.settings.earlyAccess = false return state } catch (error) { return state diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index 2f5e62754..429c48ef9 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -67,6 +67,7 @@ export interface SettingsState { pasteLongTextThreshold: number clickAssistantToShowTopic: boolean autoCheckUpdate: boolean + earlyAccess: boolean renderInputMessageAsMarkdown: boolean // 代码执行 codeExecution: { @@ -216,6 +217,7 @@ export const initialState: SettingsState = { pasteLongTextThreshold: 1500, clickAssistantToShowTopic: true, autoCheckUpdate: true, + earlyAccess: false, renderInputMessageAsMarkdown: false, codeExecution: { enabled: false, @@ -418,6 +420,9 @@ const settingsSlice = createSlice({ setAutoCheckUpdate: (state, action: PayloadAction) => { state.autoCheckUpdate = action.payload }, + setEarlyAccess: (state, action: PayloadAction) => { + state.earlyAccess = action.payload + }, setRenderInputMessageAsMarkdown: (state, action: PayloadAction) => { state.renderInputMessageAsMarkdown = action.payload }, @@ -707,6 +712,7 @@ export const { setAssistantIconType, setPasteLongTextAsFile, setAutoCheckUpdate, + setEarlyAccess, setRenderInputMessageAsMarkdown, setClickAssistantToShowTopic, setSkipBackupFile,