From 4c66b205bb7ae525f33dc356d33ba753b18c3260 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Thu, 26 Jun 2025 15:43:45 +0800 Subject: [PATCH] feat: implement early access feature toggle and update related configurations (#7304) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: implement early access feature toggle and update related configurations - Replace FeedUrl with EnableEarlyAccess in IpcChannel and ConfigManager - Update AppUpdater to handle early access updates from GitHub - Modify settings and localization files to reflect early access functionality - Ensure proper integration in the renderer and preload layers * fix: enhance error handling in AppUpdater for GitHub release fetching - Wrap the fetch call in a try-catch block to handle potential errors when retrieving the latest non-draft version from GitHub. - Log an error message if the fetch fails and return a default feed URL. * refactor: remove early access feature handling from AppUpdater - Eliminate the early access feature toggle logic from the AppUpdater class. - Adjust the feed URL setting to ensure it retrieves the latest non-draft version from GitHub when applicable. - Clean up unnecessary user-agent header in the fetch request. * feat(AppUpdater): enhance update feed URL logic and disable differential downloads - Introduced a new private method to streamline feed URL setting based on early access and IP country. - Disabled differential downloads for compatibility with GitHub and GitCode. - Cleaned up the checkForUpdates method for better readability and maintainability. * refactor(AppUpdater): simplify early access feed URL logic - Consolidated the feed URL setting logic in setEnableEarlyAccess to a single line for improved readability. - Removed redundant conditional checks while maintaining functionality for early access updates. * refactor(AppUpdater): update feed URL structure and remove early access setting - Modified the return structure of the latest release URL to include the channel type. - Removed the early access setting from the IPC handler, streamlining the update process. - Ensured the autoUpdater channel is set based on the latest release information. * feat(UpgradeChannel): add upgrade channel management and IPC integration - Introduced a new UpgradeChannel enum to manage different upgrade paths (latest, rc, beta). - Updated IpcChannel to include App_SetUpgradeChannel for setting the upgrade channel. - Enhanced ConfigManager to store and retrieve the selected upgrade channel. - Modified AppUpdater to fetch pre-release versions based on the selected upgrade channel. - Updated settings UI to allow users to select their preferred upgrade channel with tooltips for guidance. - Localized new strings for upgrade channel options in multiple languages. * refactor(AboutSettings): update version type detection and localize upgrade channel tooltips - Changed version type detection to use the UpgradeChannel enum for better clarity. - Localized success messages for switching upgrade channels to enhance user experience. * chore: update version to 1.4.4-beta.1 and refactor upgrade channel handling in AboutSettings - Updated package version to 1.4.4-beta.1. - Renamed version type detection function to getVersionChannel for clarity. - Refactored available version options to getAvailableTestChannels for better organization. - Added logic to clear update info when switching upgrade channels and when toggling early access settings. * chore: update version to 1.4.4 in package.json * fix lint error * feat(AppUpdater): enhance upgrade channel management and localization - Added cancellation functionality for ongoing downloads in AppUpdater. - Introduced a new upgrade channel option for the latest stable version. - Updated IPC handlers to cancel downloads when changing early access settings or upgrade channels. - Localized new strings for the latest version option in multiple languages. - Refactored AboutSettings to include the latest version in the upgrade channel selection. * refactor(AboutSettings): remove version channel detection logic - Eliminated the getVersionChannel function to simplify version handling. - Updated AboutSettings to streamline upgrade channel management. * feat(AboutSettings): set default upgrade channel to latest - Updated the AboutSettings component to set the default value of the upgrade channel to the latest option, enhancing user experience in channel selection. * refactor(AboutSettings): simplify upgrade channel change handling - Removed individual success messages for different upgrade channels in the handleUpgradeChannelChange function, streamlining the code and improving maintainability. * refactor: file actions into FileAction service (#7413) * refactor: file actions into FileAction service Moved file sorting, deletion, and renaming logic from FilesPage to a new FileAction service for better modularity and reuse. Updated FileList and FilesPage to use the new service functions, and improved the delete button UI in FileList. * fix: add tag collapse state management for assistants (#7436) Add tag collapse state management for assistants Introduces a collapsedTags state to manage the collapsed/expanded state of tag groups in the assistants list. Updates useTags and AssistantsTab to use this state, and adds actions to toggle and initialize tag collapse in the Redux store. * fix(model): doubao thinking param (#7499) * feat: Implement occupied directories handling during data copy (#7485) * feat: Implement occupied directories handling during data copy - Added `occupiedDirs` constant to manage directories that should not be copied. - Enhanced the `copyOccupiedDirsInMainProcess` function to copy occupied directories to a new app data path in the main process. - Updated IPC and preload APIs to support passing occupied directories during the copy operation. - Modified the DataSettings component to utilize the new copy functionality with occupied directories. * fix: Improve occupied directories handling during data copy - Updated the filter logic in the `registerIpc` function to resolve directory paths correctly. - Modified the `DataSettings` component to pass the correct occupied directories format during the copy operation. * feat: add appcode (#7507) Co-authored-by: zhaochenxue * fix: non streamoutput sometimes (#7512) * feat(migrate): add default settings for assistants during migration - Introduced a new migration step to assign default settings for assistants that lack configuration. - Default settings include temperature, context count, and other parameters to ensure consistent behavior across the application. * chore(store): increment version number to 115 for persisted reducer * Revert "feat: Update API Key Management Interface (#3444)" This reverts commit 31b3ce1049a607d2448213e5bc1c1fc0f28231d2. * feat: 一些UI上的优化和重构 (#7479) - 调整AntdProvider中主题配置,包括颜色、尺寸 - 重构聊天气泡模式的样式 - 重构多选模式的样式 - 添加Selector组件取代ant Select组件 - 重构消息搜索弹窗界面 - 重构知识库搜索弹窗界面 - 优化其他弹框UI * fix: bailian reranker (#7518) * feat: implement Python MCP server using existing Pyodide infrastructure (#7506) * refactor: rename isWindows to isWin for consistency across main/renderer (#7530) refactor: rename isWindows to isWin for consistency across components * refactor: data migration modal logic in DataSettings (#7503) * refactor: data migration modal logic in DataSettings Moved showProgressModal and startMigration functions inside the useEffect hook and added t as a dependency. This improves encapsulation and ensures translation updates are handled correctly. * remove trailing whitespace in DataSettings.tsx Cleaned up a line by removing unnecessary trailing whitespace in the DataSettings component. * fix: clear search cache on resending (#7510) * fix: Resolve vllm bad request caused by always sending dimensions in embedding requests (#7525) fix(知识库): 将dimensions字段改为可选并修复相关逻辑 * feat: Support custom registry address when configuring mcp for npm & fix lint error (#7531) * feat: Support custom registry address when configuring mcp for npm * fix: lint * refactor(GeminiAPIClient): separate model and user message handling to adapt vertex (#7511) - Introduced a new modelParts array to manage model-related messages separately from user messages. - Updated the logic to push model messages to currentReqMessages only if they exist, improving clarity and structure. - Adjusted the return order of messages in buildSdkMessages to ensure history is appended correctly. - Enhanced McpToolChunkMiddleware to reset tool processing state output when output is present. * feat: enhance WindowFooter with show/hide functionality for UI elements - Added state management to control visibility of UI elements in the WindowFooter. - Implemented a timer to automatically hide elements after a period of inactivity. - Updated hotkey handlers to reset the visibility timer on user interaction. - Modified styled component to reflect the new visibility logic. * fix(SelectionAssistant): opacity slider too slow when sliding in settings page (#7537) feat: enhance opacity control in Selection Assistant Settings - Added state management for opacity value in SelectionAssistantSettings component. - Updated Slider component to use the new opacity state instead of the previous actionWindowOpacity variable. - Ensured onChangeComplete updates the actionWindowOpacity accordingly. * feat(AihubmixAPIClient): add getBaseURL method to handle client base URL retrieval * fix(migrate): restore upgradeChannel setting in migration logic - Reintroduced the upgradeChannel setting to the state during the migration process, ensuring it defaults to LATEST when applicable. - Adjusted the migration logic to maintain consistency in settings management. --------- Co-authored-by: 自由的世界人 <3196812536@qq.com> Co-authored-by: one Co-authored-by: chenxue Co-authored-by: zhaochenxue Co-authored-by: SuYao Co-authored-by: kangfenmao Co-authored-by: Teo Co-authored-by: Chen Tao <70054568+eeee0717@users.noreply.github.com> Co-authored-by: LiuVaayne <10231735+vaayne@users.noreply.github.com> Co-authored-by: fullex <106392080+0xfullex@users.noreply.github.com> Co-authored-by: Wang Jiyuan <59059173+EurFelux@users.noreply.github.com> Co-authored-by: 陈天寒 Co-authored-by: fullex <0xfullex@gmail.com> --- packages/shared/IpcChannel.ts | 3 +- packages/shared/config/constant.ts | 9 +- src/main/ipc.ts | 12 ++- src/main/services/AppUpdater.ts | 94 ++++++++++++++++--- src/main/services/ConfigManager.ts | 21 +++-- src/preload/index.ts | 5 +- src/renderer/src/hooks/useSettings.ts | 10 +- src/renderer/src/i18n/locales/en-us.json | 9 +- src/renderer/src/i18n/locales/ja-jp.json | 9 +- src/renderer/src/i18n/locales/ru-ru.json | 9 +- src/renderer/src/i18n/locales/zh-cn.json | 9 +- src/renderer/src/i18n/locales/zh-tw.json | 9 +- .../src/pages/settings/AboutSettings.tsx | 82 +++++++++++++++- src/renderer/src/store/migrate.ts | 4 + src/renderer/src/store/settings.ts | 7 ++ 15 files changed, 254 insertions(+), 38 deletions(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index b77fd5d430..8da9a67429 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -15,7 +15,8 @@ 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_SetEnableEarlyAccess = 'app:set-enable-early-access', + App_SetUpgradeChannel = 'app:set-upgrade-channel', App_HandleZoomFactor = 'app:handle-zoom-factor', App_Select = 'app:select', App_HasWritePermission = 'app:has-write-permission', diff --git a/packages/shared/config/constant.ts b/packages/shared/config/constant.ts index 0b568461ea..975767fefa 100644 --- a/packages/shared/config/constant.ts +++ b/packages/shared/config/constant.ts @@ -406,8 +406,15 @@ 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' + GITHUB_LATEST = 'https://github.com/CherryHQ/cherry-studio/releases/latest/download' } + +export enum UpgradeChannel { + LATEST = 'latest', // 最新稳定版本 + RC = 'rc', // 公测版本 + BETA = 'beta' // 预览版本 +} + export const defaultTimeout = 5 * 1000 * 60 export const occupiedDirs = ['logs', 'Network', 'Partitions/webview/Network'] diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 8d8690a54e..b2003f8db8 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -5,7 +5,7 @@ import path from 'node:path' import { isMac, isWin } from '@main/constant' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { handleZoomFactor } from '@main/utils/zoom' -import { FeedUrl } from '@shared/config/constant' +import { UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { Shortcut, ThemeMode } from '@types' import { BrowserWindow, dialog, ipcMain, session, shell } from 'electron' @@ -141,8 +141,14 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { configManager.setAutoUpdate(isActive) }) - ipcMain.handle(IpcChannel.App_SetFeedUrl, (_, feedUrl: FeedUrl) => { - appUpdater.setFeedUrl(feedUrl) + ipcMain.handle(IpcChannel.App_SetEnableEarlyAccess, async (_, isActive: boolean) => { + appUpdater.cancelDownload() + configManager.setEnableEarlyAccess(isActive) + }) + + ipcMain.handle(IpcChannel.App_SetUpgradeChannel, async (_, channel: UpgradeChannel) => { + appUpdater.cancelDownload() + configManager.setUpgradeChannel(channel) }) ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => { diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 4c71a05556..e26a779d59 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -1,8 +1,8 @@ import { isWin } from '@main/constant' import { locales } from '@main/utils/locales' -import { FeedUrl } from '@shared/config/constant' +import { FeedUrl, UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' -import { UpdateInfo } from 'builder-util-runtime' +import { CancellationToken, UpdateInfo } from 'builder-util-runtime' import { app, BrowserWindow, dialog } from 'electron' import logger from 'electron-log' import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater } from 'electron-updater' @@ -14,6 +14,7 @@ import { configManager } from './ConfigManager' export default class AppUpdater { autoUpdater: _AppUpdater = autoUpdater private releaseInfo: UpdateInfo | undefined + private cancellationToken: CancellationToken = new CancellationToken() constructor(mainWindow: BrowserWindow) { logger.transports.file.level = 'info' @@ -22,9 +23,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) => { // 简单记录错误信息和时间戳 logger.error('更新异常', { @@ -64,6 +63,33 @@ export default class AppUpdater { this.autoUpdater = autoUpdater } + private async _getPreReleaseVersionFromGithub(channel: UpgradeChannel) { + try { + const responses = await fetch('https://api.github.com/repos/CherryHQ/cherry-studio/releases?per_page=8', { + headers: { + Accept: 'application/vnd.github+json', + 'X-GitHub-Api-Version': '2022-11-28', + 'Accept-Language': 'en-US,en;q=0.9' + } + }) + const data = (await responses.json()) as GithubReleaseInfo[] + logger.debug('github release data', data) + const release: GithubReleaseInfo | undefined = data.find((item: GithubReleaseInfo) => { + return item.prerelease && item.tag_name.includes(`-${channel}.`) + }) + + if (!release) { + return null + } + + logger.info('release info', release.tag_name) + return `https://github.com/CherryHQ/cherry-studio/releases/download/${release.tag_name}` + } catch (error) { + logger.error('Failed to get latest not draft version from github:', error) + return null + } + } + private async _getIpCountry() { try { // add timeout using AbortController @@ -93,9 +119,44 @@ export default class AppUpdater { autoUpdater.autoInstallOnAppQuit = isActive } - public setFeedUrl(feedUrl: FeedUrl) { - autoUpdater.setFeedURL(feedUrl) - configManager.setFeedUrl(feedUrl) + private async _setFeedUrl() { + // disable downgrade and differential download + // github and gitcode don't support multiple range download + this.autoUpdater.allowDowngrade = false + this.autoUpdater.disableDifferentialDownload = true + + if (configManager.getEnableEarlyAccess()) { + const channel = configManager.getUpgradeChannel() + if (channel === UpgradeChannel.LATEST) { + this.autoUpdater.setFeedURL(FeedUrl.GITHUB_LATEST) + this.autoUpdater.channel = UpgradeChannel.LATEST + return true + } + + const preReleaseUrl = await this._getPreReleaseVersionFromGithub(channel) + if (preReleaseUrl) { + this.autoUpdater.setFeedURL(preReleaseUrl) + this.autoUpdater.channel = channel + return true + } + return false + } + + // no early access, use latest version + this.autoUpdater.channel = 'latest' + this.autoUpdater.setFeedURL(FeedUrl.PRODUCTION) + + const ipCountry = await this._getIpCountry() + logger.info('ipCountry', ipCountry) + if (ipCountry.toLowerCase() !== 'cn') { + this.autoUpdater.setFeedURL(FeedUrl.GITHUB_LATEST) + } + return true + } + + public cancelDownload() { + this.cancellationToken.cancel() + this.cancellationToken = new CancellationToken() } public async checkForUpdates() { @@ -106,10 +167,12 @@ export default class AppUpdater { } } - const ipCountry = await this._getIpCountry() - logger.info('ipCountry', ipCountry) - if (ipCountry !== 'CN') { - this.autoUpdater.setFeedURL(FeedUrl.EARLY_ACCESS) + const isSetFeedUrl = await this._setFeedUrl() + if (!isSetFeedUrl) { + return { + currentVersion: app.getVersion(), + updateInfo: null + } } try { @@ -117,7 +180,8 @@ export default class AppUpdater { if (update?.isUpdateAvailable && !this.autoUpdater.autoDownload) { // 如果 autoDownload 为 false,则需要再调用下面的函数触发下 // do not use await, because it will block the return of this function - this.autoUpdater.downloadUpdate() + logger.info('downloadUpdate manual by check for updates', this.cancellationToken) + this.autoUpdater.downloadUpdate(this.cancellationToken) } return { @@ -178,7 +242,11 @@ export default class AppUpdater { return releaseNotes.map((note) => note.note).join('\n') } } - +interface GithubReleaseInfo { + draft: boolean + prerelease: boolean + tag_name: string +} interface ReleaseNoteInfo { readonly version: string readonly note: string | null diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index 573674bd70..6d33b6e3dd 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -1,4 +1,4 @@ -import { defaultLanguage, FeedUrl, ZOOM_SHORTCUTS } from '@shared/config/constant' +import { defaultLanguage, UpgradeChannel, ZOOM_SHORTCUTS } from '@shared/config/constant' import { LanguageVarious, Shortcut, ThemeMode } from '@types' import { app } from 'electron' import Store from 'electron-store' @@ -16,7 +16,8 @@ export enum ConfigKeys { ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant', EnableQuickAssistant = 'enableQuickAssistant', AutoUpdate = 'autoUpdate', - FeedUrl = 'feedUrl', + EnableEarlyAccess = 'enableEarlyAccess', + UpgradeChannel = 'upgradeChannel', EnableDataCollection = 'enableDataCollection', SelectionAssistantEnabled = 'selectionAssistantEnabled', SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode', @@ -142,12 +143,20 @@ export class ConfigManager { this.set(ConfigKeys.AutoUpdate, value) } - getFeedUrl(): string { - return this.get(ConfigKeys.FeedUrl, FeedUrl.PRODUCTION) + getEnableEarlyAccess(): boolean { + return this.get(ConfigKeys.EnableEarlyAccess, false) } - setFeedUrl(value: FeedUrl) { - this.set(ConfigKeys.FeedUrl, value) + setEnableEarlyAccess(value: boolean) { + this.set(ConfigKeys.EnableEarlyAccess, value) + } + + getUpgradeChannel(): UpgradeChannel { + return this.get(ConfigKeys.UpgradeChannel, UpgradeChannel.LATEST) + } + + setUpgradeChannel(value: UpgradeChannel) { + this.set(ConfigKeys.UpgradeChannel, value) } getEnableDataCollection(): boolean { diff --git a/src/preload/index.ts b/src/preload/index.ts index 3e9ae532b2..ed2a2042e0 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -1,6 +1,6 @@ import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import { electronAPI } from '@electron-toolkit/preload' -import { FeedUrl } from '@shared/config/constant' +import { UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, ThemeMode, WebDavConfig } from '@types' import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' @@ -23,7 +23,8 @@ 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), + setEnableEarlyAccess: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetEnableEarlyAccess, isActive), + setUpgradeChannel: (channel: UpgradeChannel) => ipcRenderer.invoke(IpcChannel.App_SetUpgradeChannel, channel), 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 10f9ee9001..72560706e6 100644 --- a/src/renderer/src/hooks/useSettings.ts +++ b/src/renderer/src/hooks/useSettings.ts @@ -17,10 +17,11 @@ import { setTopicPosition, setTray as _setTray, setTrayOnClose, + setUpgradeChannel as _setUpgradeChannel, setWindowStyle } from '@renderer/store/settings' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types' -import { FeedUrl } from '@shared/config/constant' +import { UpgradeChannel } from '@shared/config/constant' export function useSettings() { const settings = useAppSelector((state) => state.settings) @@ -62,7 +63,12 @@ export function useSettings() { setEarlyAccess(isEarlyAccess: boolean) { dispatch(_setEarlyAccess(isEarlyAccess)) - window.api.setFeedUrl(isEarlyAccess ? FeedUrl.EARLY_ACCESS : FeedUrl.PRODUCTION) + window.api.setEnableEarlyAccess(isEarlyAccess) + }, + + setUpgradeChannel(channel: UpgradeChannel) { + dispatch(_setUpgradeChannel(channel)) + window.api.setUpgradeChannel(channel) }, setTheme(theme: ThemeMode) { diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 303899f2e8..b58056ae92 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -1385,7 +1385,14 @@ "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.early_access.tooltip": "Updating to test versions cannot be downgraded, there is a risk of data loss, please backup your data in advance", + "general.early_access.beta_version": "Beta Version", + "general.early_access.rc_version": "RC Version", + "general.early_access.latest_version": "Latest Version", + "general.early_access.latest_version_tooltip": "github latest version, latest stable version", + "general.early_access.version_options": "Version Options", + "general.early_access.rc_version_tooltip": "More stable, please backup your data", + "general.early_access.beta_version_tooltip": "Latest features but unstable, use with caution", "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 bd20c61ead..64f6a98475 100644 --- a/src/renderer/src/i18n/locales/ja-jp.json +++ b/src/renderer/src/i18n/locales/ja-jp.json @@ -1830,7 +1830,14 @@ }, "general.auto_check_update.title": "自動更新", "general.early_access.title": "早期アクセス", - "general.early_access.tooltip": "有効にすると、GitHub の最新バージョンを使用します。ダウンロード速度が遅く、不安定な場合があります。データを事前にバックアップしてください。", + "general.early_access.tooltip": "更新すると、データが失われる可能性があります。データを事前にバックアップしてください。", + "general.early_access.beta_version": "ベータ版", + "general.early_access.rc_version": "RC版", + "general.early_access.latest_version": "最新版", + "general.early_access.latest_version_tooltip": "github latest バージョン, 最新安定版", + "general.early_access.version_options": "バージョンオプション", + "general.early_access.rc_version_tooltip": "より安定しています。データを事前にバックアップしてください。", + "general.early_access.beta_version_tooltip": "最新の機能ですが、不安定な場合があります。使用には注意してください。", "quickPhrase": { "title": "クイックフレーズ", "add": "フレーズを追加", diff --git a/src/renderer/src/i18n/locales/ru-ru.json b/src/renderer/src/i18n/locales/ru-ru.json index fcd5487b92..e027e7270d 100644 --- a/src/renderer/src/i18n/locales/ru-ru.json +++ b/src/renderer/src/i18n/locales/ru-ru.json @@ -1830,7 +1830,14 @@ }, "general.auto_check_update.title": "Автоматическое обновление", "general.early_access.title": "Ранний доступ", - "general.early_access.tooltip": "Включить для использования последней версии из GitHub, что может быть медленнее и нестабильно. Пожалуйста, сделайте резервную копию данных заранее.", + "general.early_access.tooltip": "Обновление до тестовых версий не может быть откачено, существует риск потери данных, пожалуйста, сделайте резервную копию данных заранее", + "general.early_access.beta_version": "Бета версия", + "general.early_access.rc_version": "RC версия", + "general.early_access.latest_version": "Стабильная версия", + "general.early_access.latest_version_tooltip": "github latest версия, стабильная версия", + "general.early_access.version_options": "Варианты версии", + "general.early_access.rc_version_tooltip": "Более стабильно, пожалуйста, сделайте резервную копию данных заранее", + "general.early_access.beta_version_tooltip": "Самые последние функции, но нестабильно, используйте с осторожностью", "quickPhrase": { "title": "Быстрые фразы", "add": "Добавить фразу", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 9bc4201f71..894d53c2be 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -1385,7 +1385,14 @@ "general.image_upload": "图片上传", "general.auto_check_update.title": "自动更新", "general.early_access.title": "抢先体验", - "general.early_access.tooltip": "开启后,将使用 GitHub 的最新版本,下载速度可能较慢,请务必提前备份数据", + "general.early_access.tooltip": "更新到测试版本不能降级,有数据丢失风险,请务必提前备份数据", + "general.early_access.beta_version": "预览版本", + "general.early_access.rc_version": "公测版本", + "general.early_access.latest_version": "稳定版本", + "general.early_access.version_options": "版本选择", + "general.early_access.rc_version_tooltip": "相对稳定,请备份数据", + "general.early_access.beta_version_tooltip": "功能最新但不稳定,谨慎使用", + "general.early_access.latest_version_tooltip": "github latest 版本, 最新稳定版本", "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 e99c57e170..65a3a3f58c 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -1833,7 +1833,14 @@ }, "general.auto_check_update.title": "自動更新", "general.early_access.title": "搶先體驗", - "general.early_access.tooltip": "開啟後,將使用 GitHub 的最新版本,下載速度可能較慢,請務必提前備份數據", + "general.early_access.tooltip": "更新到測試版本不能降級,有數據丟失風險,請務必提前備份數據", + "general.early_access.beta_version": "預覽版本", + "general.early_access.rc_version": "公測版本", + "general.early_access.latest_version": "穩定版本", + "general.early_access.latest_version_tooltip": "github latest 版本, 最新穩定版本", + "general.early_access.version_options": "版本選項", + "general.early_access.rc_version_tooltip": "相對穩定,請務必提前備份數據", + "general.early_access.beta_version_tooltip": "功能最新但不穩定,謹慎使用", "quickPhrase": { "title": "快捷短語", "add": "新增短語", diff --git a/src/renderer/src/pages/settings/AboutSettings.tsx b/src/renderer/src/pages/settings/AboutSettings.tsx index 82f11ec7d4..9bf65e4489 100644 --- a/src/renderer/src/pages/settings/AboutSettings.tsx +++ b/src/renderer/src/pages/settings/AboutSettings.tsx @@ -10,7 +10,8 @@ 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, Tooltip } from 'antd' +import { UpgradeChannel } from '@shared/config/constant' +import { Avatar, Button, Progress, Radio, 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 +26,8 @@ const AboutSettings: FC = () => { const [version, setVersion] = useState('') const [isPortable, setIsPortable] = useState(false) const { t } = useTranslation() - const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess } = useSettings() + const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess, upgradeChannel, setUpgradeChannel } = + useSettings() const { theme } = useTheme() const dispatch = useAppDispatch() const { update } = useRuntime() @@ -95,15 +97,65 @@ const AboutSettings: FC = () => { const hasNewVersion = update?.info?.version && version ? compareVersions(update.info.version, version) > 0 : false + const handleUpgradeChannelChange = async (value: UpgradeChannel) => { + setUpgradeChannel(value) + // Clear update info when switching upgrade channel + dispatch( + setUpdateState({ + available: false, + info: null, + downloaded: false, + checking: false, + downloading: false, + downloadProgress: 0 + }) + ) + } + + // Get available test version options based on current version + const getAvailableTestChannels = () => { + return [ + { + tooltip: t('settings.general.early_access.latest_version_tooltip'), + label: t('settings.general.early_access.latest_version'), + value: UpgradeChannel.LATEST + }, + { + tooltip: t('settings.general.early_access.rc_version_tooltip'), + label: t('settings.general.early_access.rc_version'), + value: UpgradeChannel.RC + }, + { + tooltip: t('settings.general.early_access.beta_version_tooltip'), + label: t('settings.general.early_access.beta_version'), + value: UpgradeChannel.BETA + } + ] + } + + const handlerSetEarlyAccess = (value: boolean) => { + setEarlyAccess(value) + dispatch( + setUpdateState({ + available: false, + info: null, + downloaded: false, + checking: false, + downloading: false, + downloadProgress: 0 + }) + ) + if (value === false) setUpgradeChannel(UpgradeChannel.LATEST) + } + useEffect(() => { runAsyncFunction(async () => { const appInfo = await window.api.getAppInfo() setVersion(appInfo.version) setIsPortable(appInfo.isPortable) }) - setEarlyAccess(earlyAccess) setAutoCheckUpdate(autoCheckUpdate) - }, [autoCheckUpdate, earlyAccess, setAutoCheckUpdate, setEarlyAccess]) + }, [autoCheckUpdate, setAutoCheckUpdate, setEarlyAccess]) return ( @@ -167,9 +219,29 @@ const AboutSettings: FC = () => { {t('settings.general.early_access.title')} - setEarlyAccess(v)} /> + handlerSetEarlyAccess(v)} /> + {earlyAccess && getAvailableTestChannels().length > 0 && ( + <> + + + {t('settings.general.early_access.version_options')} + handleUpgradeChannelChange(e.target.value)}> + {getAvailableTestChannels().map((option) => ( + + {option.label} + + ))} + + + + )} )} diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index df89be66b8..0e9385de0c 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -7,6 +7,7 @@ import db from '@renderer/databases' import i18n from '@renderer/i18n' import { Assistant, Provider, WebSearchProvider } from '@renderer/types' import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils' +import { UpgradeChannel } from '@shared/config/constant' import { isEmpty } from 'lodash' import { createMigrate } from 'redux-persist' @@ -1627,6 +1628,9 @@ const migrateConfig = { } } }) + if (state.settings) { + state.settings.upgradeChannel = UpgradeChannel.LATEST + } return state } catch (error) { return state diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index d4014adf25..ee991556f7 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -11,6 +11,7 @@ import { ThemeMode, TranslateLanguageVarious } from '@renderer/types' +import { UpgradeChannel } from '@shared/config/constant' import { WebDAVSyncState } from './backup' @@ -68,6 +69,7 @@ export interface SettingsState { clickAssistantToShowTopic: boolean autoCheckUpdate: boolean earlyAccess: boolean + upgradeChannel: UpgradeChannel renderInputMessageAsMarkdown: boolean // 代码执行 codeExecution: { @@ -221,6 +223,7 @@ export const initialState: SettingsState = { clickAssistantToShowTopic: true, autoCheckUpdate: true, earlyAccess: false, + upgradeChannel: UpgradeChannel.LATEST, renderInputMessageAsMarkdown: false, codeExecution: { enabled: false, @@ -429,6 +432,9 @@ const settingsSlice = createSlice({ setEarlyAccess: (state, action: PayloadAction) => { state.earlyAccess = action.payload }, + setUpgradeChannel: (state, action: PayloadAction) => { + state.upgradeChannel = action.payload + }, setRenderInputMessageAsMarkdown: (state, action: PayloadAction) => { state.renderInputMessageAsMarkdown = action.payload }, @@ -725,6 +731,7 @@ export const { setPasteLongTextAsFile, setAutoCheckUpdate, setEarlyAccess, + setUpgradeChannel, setRenderInputMessageAsMarkdown, setClickAssistantToShowTopic, setSkipBackupFile,