From d47c93b4d8cc2318c5958b2ac1cabcc329a8ca04 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 8c74ffcaed..9cc3a1d991 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 e1fca4e6d6..cfba46df70 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 2fd60377c8..5339e81691 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 1733bc6068..772c885a01 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 c4e9c65556..573674bd70 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 5e9f3681cc..4ac4b39e9e 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 c058a95237..10f9ee9001 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 2e8d29bda5..c31ae341e6 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 7b1bfce8e4..8467a1c1b2 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 8fd2f91599..2aae5c5baa 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 228e738dc2..04feec6f3b 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 02f02bf421..b205a7d8d0 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 bd7e174f6f..0c292497c4 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 bf19e7b6d5..abec96fa7f 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 2f5e627545..429c48ef92 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,