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,