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 <beyondkmkp@gmail.com>
This commit is contained in:
beyondkmp 2025-06-06 15:48:54 +08:00 committed by GitHub
parent bc5cc4bf02
commit d47c93b4d8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 69 additions and 5 deletions

View File

@ -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',

View File

@ -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'
}

View File

@ -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)
})

View File

@ -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 {

View File

@ -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<string>(ConfigKeys.FeedUrl, FeedUrl.PRODUCTION)
}
setFeedUrl(value: FeedUrl) {
this.set(ConfigKeys.FeedUrl, value)
}
getEnableDataCollection(): boolean {
return this.get<boolean>(ConfigKeys.EnableDataCollection, true)
}

View File

@ -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),

View File

@ -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))
},

View File

@ -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",

View File

@ -1758,6 +1758,8 @@
"content_limit_tooltip": "検索結果の内容長を制限し、制限を超える内容は切り捨てられます。"
},
"general.auto_check_update.title": "自動更新",
"general.early_access.title": "早期アクセス",
"general.early_access.tooltip": "有効にすると、GitHub の最新バージョンを使用します。ダウンロード速度が遅く、不安定な場合があります。データを事前にバックアップしてください。",
"quickPhrase": {
"title": "クイックフレーズ",
"add": "フレーズを追加",

View File

@ -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": "Добавить фразу",

View File

@ -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": "恢复",

View File

@ -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": "新增短語",

View File

@ -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 = () => {
<SettingRowTitle>{t('settings.general.auto_check_update.title')}</SettingRowTitle>
<Switch value={autoCheckUpdate} onChange={(v) => setAutoCheckUpdate(v)} />
</SettingRow>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.early_access.title')}</SettingRowTitle>
<Tooltip title={t('settings.general.early_access.tooltip')} trigger={['hover', 'focus']}>
<Switch value={earlyAccess} onChange={(v) => setEarlyAccess(v)} />
</Tooltip>
</SettingRow>
</>
)}
</SettingGroup>

View File

@ -1480,6 +1480,7 @@ const migrateConfig = {
state.paintings.tokenFluxPaintings = []
}
state.settings.showTokens = true
state.settings.earlyAccess = false
return state
} catch (error) {
return state

View File

@ -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<boolean>) => {
state.autoCheckUpdate = action.payload
},
setEarlyAccess: (state, action: PayloadAction<boolean>) => {
state.earlyAccess = action.payload
},
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
state.renderInputMessageAsMarkdown = action.payload
},
@ -707,6 +712,7 @@ export const {
setAssistantIconType,
setPasteLongTextAsFile,
setAutoCheckUpdate,
setEarlyAccess,
setRenderInputMessageAsMarkdown,
setClickAssistantToShowTopic,
setSkipBackupFile,