feat: implement early access feature toggle and update related configurations (#7304)

* 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 <zhaochenxue@bixin.cn>

* 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 31b3ce1049.

* 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 <wangan.cs@gmail.com>
Co-authored-by: chenxue <DDU1222@users.noreply.github.com>
Co-authored-by: zhaochenxue <zhaochenxue@bixin.cn>
Co-authored-by: SuYao <sy20010504@gmail.com>
Co-authored-by: kangfenmao <kangfenmao@qq.com>
Co-authored-by: Teo <cheesen.xu@gmail.com>
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: 陈天寒 <silenceboychen@gmail.com>
Co-authored-by: fullex <0xfullex@gmail.com>
This commit is contained in:
beyondkmp 2025-06-26 15:43:45 +08:00 committed by GitHub
parent 6342998c9f
commit 4c66b205bb
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 254 additions and 38 deletions

View File

@ -15,7 +15,8 @@ export enum IpcChannel {
App_SetTrayOnClose = 'app:set-tray-on-close', App_SetTrayOnClose = 'app:set-tray-on-close',
App_SetTheme = 'app:set-theme', App_SetTheme = 'app:set-theme',
App_SetAutoUpdate = 'app:set-auto-update', 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_HandleZoomFactor = 'app:handle-zoom-factor',
App_Select = 'app:select', App_Select = 'app:select',
App_HasWritePermission = 'app:has-write-permission', App_HasWritePermission = 'app:has-write-permission',

View File

@ -406,8 +406,15 @@ export const defaultLanguage = 'en-US'
export enum FeedUrl { export enum FeedUrl {
PRODUCTION = 'https://releases.cherry-ai.com', 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 defaultTimeout = 5 * 1000 * 60
export const occupiedDirs = ['logs', 'Network', 'Partitions/webview/Network'] export const occupiedDirs = ['logs', 'Network', 'Partitions/webview/Network']

View File

@ -5,7 +5,7 @@ import path from 'node:path'
import { isMac, isWin } from '@main/constant' import { isMac, isWin } from '@main/constant'
import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process'
import { handleZoomFactor } from '@main/utils/zoom' import { handleZoomFactor } from '@main/utils/zoom'
import { FeedUrl } from '@shared/config/constant' import { UpgradeChannel } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { Shortcut, ThemeMode } from '@types' import { Shortcut, ThemeMode } from '@types'
import { BrowserWindow, dialog, ipcMain, session, shell } from 'electron' import { BrowserWindow, dialog, ipcMain, session, shell } from 'electron'
@ -141,8 +141,14 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) {
configManager.setAutoUpdate(isActive) configManager.setAutoUpdate(isActive)
}) })
ipcMain.handle(IpcChannel.App_SetFeedUrl, (_, feedUrl: FeedUrl) => { ipcMain.handle(IpcChannel.App_SetEnableEarlyAccess, async (_, isActive: boolean) => {
appUpdater.setFeedUrl(feedUrl) 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) => { ipcMain.handle(IpcChannel.Config_Set, (_, key: string, value: any, isNotify: boolean = false) => {

View File

@ -1,8 +1,8 @@
import { isWin } from '@main/constant' import { isWin } from '@main/constant'
import { locales } from '@main/utils/locales' 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 { IpcChannel } from '@shared/IpcChannel'
import { UpdateInfo } from 'builder-util-runtime' import { CancellationToken, UpdateInfo } from 'builder-util-runtime'
import { app, BrowserWindow, dialog } from 'electron' import { app, BrowserWindow, dialog } from 'electron'
import logger from 'electron-log' import logger from 'electron-log'
import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater } from 'electron-updater' import { AppUpdater as _AppUpdater, autoUpdater, NsisUpdater } from 'electron-updater'
@ -14,6 +14,7 @@ import { configManager } from './ConfigManager'
export default class AppUpdater { export default class AppUpdater {
autoUpdater: _AppUpdater = autoUpdater autoUpdater: _AppUpdater = autoUpdater
private releaseInfo: UpdateInfo | undefined private releaseInfo: UpdateInfo | undefined
private cancellationToken: CancellationToken = new CancellationToken()
constructor(mainWindow: BrowserWindow) { constructor(mainWindow: BrowserWindow) {
logger.transports.file.level = 'info' logger.transports.file.level = 'info'
@ -22,9 +23,7 @@ export default class AppUpdater {
autoUpdater.forceDevUpdateConfig = !app.isPackaged autoUpdater.forceDevUpdateConfig = !app.isPackaged
autoUpdater.autoDownload = configManager.getAutoUpdate() autoUpdater.autoDownload = configManager.getAutoUpdate()
autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate() autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate()
autoUpdater.setFeedURL(configManager.getFeedUrl())
// 检测下载错误
autoUpdater.on('error', (error) => { autoUpdater.on('error', (error) => {
// 简单记录错误信息和时间戳 // 简单记录错误信息和时间戳
logger.error('更新异常', { logger.error('更新异常', {
@ -64,6 +63,33 @@ export default class AppUpdater {
this.autoUpdater = autoUpdater 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() { private async _getIpCountry() {
try { try {
// add timeout using AbortController // add timeout using AbortController
@ -93,9 +119,44 @@ export default class AppUpdater {
autoUpdater.autoInstallOnAppQuit = isActive autoUpdater.autoInstallOnAppQuit = isActive
} }
public setFeedUrl(feedUrl: FeedUrl) { private async _setFeedUrl() {
autoUpdater.setFeedURL(feedUrl) // disable downgrade and differential download
configManager.setFeedUrl(feedUrl) // 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() { public async checkForUpdates() {
@ -106,10 +167,12 @@ export default class AppUpdater {
} }
} }
const ipCountry = await this._getIpCountry() const isSetFeedUrl = await this._setFeedUrl()
logger.info('ipCountry', ipCountry) if (!isSetFeedUrl) {
if (ipCountry !== 'CN') { return {
this.autoUpdater.setFeedURL(FeedUrl.EARLY_ACCESS) currentVersion: app.getVersion(),
updateInfo: null
}
} }
try { try {
@ -117,7 +180,8 @@ export default class AppUpdater {
if (update?.isUpdateAvailable && !this.autoUpdater.autoDownload) { if (update?.isUpdateAvailable && !this.autoUpdater.autoDownload) {
// 如果 autoDownload 为 false则需要再调用下面的函数触发下 // 如果 autoDownload 为 false则需要再调用下面的函数触发下
// do not use await, because it will block the return of this function // 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 { return {
@ -178,7 +242,11 @@ export default class AppUpdater {
return releaseNotes.map((note) => note.note).join('\n') return releaseNotes.map((note) => note.note).join('\n')
} }
} }
interface GithubReleaseInfo {
draft: boolean
prerelease: boolean
tag_name: string
}
interface ReleaseNoteInfo { interface ReleaseNoteInfo {
readonly version: string readonly version: string
readonly note: string | null readonly note: string | null

View File

@ -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 { LanguageVarious, Shortcut, ThemeMode } from '@types'
import { app } from 'electron' import { app } from 'electron'
import Store from 'electron-store' import Store from 'electron-store'
@ -16,7 +16,8 @@ export enum ConfigKeys {
ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant', ClickTrayToShowQuickAssistant = 'clickTrayToShowQuickAssistant',
EnableQuickAssistant = 'enableQuickAssistant', EnableQuickAssistant = 'enableQuickAssistant',
AutoUpdate = 'autoUpdate', AutoUpdate = 'autoUpdate',
FeedUrl = 'feedUrl', EnableEarlyAccess = 'enableEarlyAccess',
UpgradeChannel = 'upgradeChannel',
EnableDataCollection = 'enableDataCollection', EnableDataCollection = 'enableDataCollection',
SelectionAssistantEnabled = 'selectionAssistantEnabled', SelectionAssistantEnabled = 'selectionAssistantEnabled',
SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode', SelectionAssistantTriggerMode = 'selectionAssistantTriggerMode',
@ -142,12 +143,20 @@ export class ConfigManager {
this.set(ConfigKeys.AutoUpdate, value) this.set(ConfigKeys.AutoUpdate, value)
} }
getFeedUrl(): string { getEnableEarlyAccess(): boolean {
return this.get<string>(ConfigKeys.FeedUrl, FeedUrl.PRODUCTION) return this.get<boolean>(ConfigKeys.EnableEarlyAccess, false)
} }
setFeedUrl(value: FeedUrl) { setEnableEarlyAccess(value: boolean) {
this.set(ConfigKeys.FeedUrl, value) this.set(ConfigKeys.EnableEarlyAccess, value)
}
getUpgradeChannel(): UpgradeChannel {
return this.get<UpgradeChannel>(ConfigKeys.UpgradeChannel, UpgradeChannel.LATEST)
}
setUpgradeChannel(value: UpgradeChannel) {
this.set(ConfigKeys.UpgradeChannel, value)
} }
getEnableDataCollection(): boolean { getEnableDataCollection(): boolean {

View File

@ -1,6 +1,6 @@
import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces' import type { ExtractChunkData } from '@cherrystudio/embedjs-interfaces'
import { electronAPI } from '@electron-toolkit/preload' import { electronAPI } from '@electron-toolkit/preload'
import { FeedUrl } from '@shared/config/constant' import { UpgradeChannel } from '@shared/config/constant'
import { IpcChannel } from '@shared/IpcChannel' import { IpcChannel } from '@shared/IpcChannel'
import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, ThemeMode, WebDavConfig } from '@types' import { FileType, KnowledgeBaseParams, KnowledgeItem, MCPServer, Shortcut, ThemeMode, WebDavConfig } from '@types'
import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron' import { contextBridge, ipcRenderer, OpenDialogOptions, shell, webUtils } from 'electron'
@ -23,7 +23,8 @@ const api = {
setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive), setLaunchToTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetLaunchToTray, isActive),
setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive), setTray: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTray, isActive),
setTrayOnClose: (isActive: boolean) => ipcRenderer.invoke(IpcChannel.App_SetTrayOnClose, 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), setTheme: (theme: ThemeMode) => ipcRenderer.invoke(IpcChannel.App_SetTheme, theme),
handleZoomFactor: (delta: number, reset: boolean = false) => handleZoomFactor: (delta: number, reset: boolean = false) =>
ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset), ipcRenderer.invoke(IpcChannel.App_HandleZoomFactor, delta, reset),

View File

@ -17,10 +17,11 @@ import {
setTopicPosition, setTopicPosition,
setTray as _setTray, setTray as _setTray,
setTrayOnClose, setTrayOnClose,
setUpgradeChannel as _setUpgradeChannel,
setWindowStyle setWindowStyle
} from '@renderer/store/settings' } from '@renderer/store/settings'
import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types' import { SidebarIcon, ThemeMode, TranslateLanguageVarious } from '@renderer/types'
import { FeedUrl } from '@shared/config/constant' import { UpgradeChannel } from '@shared/config/constant'
export function useSettings() { export function useSettings() {
const settings = useAppSelector((state) => state.settings) const settings = useAppSelector((state) => state.settings)
@ -62,7 +63,12 @@ export function useSettings() {
setEarlyAccess(isEarlyAccess: boolean) { setEarlyAccess(isEarlyAccess: boolean) {
dispatch(_setEarlyAccess(isEarlyAccess)) 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) { setTheme(theme: ThemeMode) {

View File

@ -1385,7 +1385,14 @@
"general.image_upload": "Image Upload", "general.image_upload": "Image Upload",
"general.auto_check_update.title": "Auto Update", "general.auto_check_update.title": "Auto Update",
"general.early_access.title": "Early Access", "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.button": "Reset",
"general.reset.title": "Data Reset", "general.reset.title": "Data Reset",
"general.restore.button": "Restore", "general.restore.button": "Restore",

View File

@ -1830,7 +1830,14 @@
}, },
"general.auto_check_update.title": "自動更新", "general.auto_check_update.title": "自動更新",
"general.early_access.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": { "quickPhrase": {
"title": "クイックフレーズ", "title": "クイックフレーズ",
"add": "フレーズを追加", "add": "フレーズを追加",

View File

@ -1830,7 +1830,14 @@
}, },
"general.auto_check_update.title": "Автоматическое обновление", "general.auto_check_update.title": "Автоматическое обновление",
"general.early_access.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": { "quickPhrase": {
"title": "Быстрые фразы", "title": "Быстрые фразы",
"add": "Добавить фразу", "add": "Добавить фразу",

View File

@ -1385,7 +1385,14 @@
"general.image_upload": "图片上传", "general.image_upload": "图片上传",
"general.auto_check_update.title": "自动更新", "general.auto_check_update.title": "自动更新",
"general.early_access.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.button": "重置",
"general.reset.title": "重置数据", "general.reset.title": "重置数据",
"general.restore.button": "恢复", "general.restore.button": "恢复",

View File

@ -1833,7 +1833,14 @@
}, },
"general.auto_check_update.title": "自動更新", "general.auto_check_update.title": "自動更新",
"general.early_access.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": { "quickPhrase": {
"title": "快捷短語", "title": "快捷短語",
"add": "新增短語", "add": "新增短語",

View File

@ -10,7 +10,8 @@ import { useAppDispatch } from '@renderer/store'
import { setUpdateState } from '@renderer/store/runtime' import { setUpdateState } from '@renderer/store/runtime'
import { ThemeMode } from '@renderer/types' import { ThemeMode } from '@renderer/types'
import { compareVersions, runAsyncFunction } from '@renderer/utils' 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 { debounce } from 'lodash'
import { Bug, FileCheck, Github, Globe, Mail, Rss } from 'lucide-react' import { Bug, FileCheck, Github, Globe, Mail, Rss } from 'lucide-react'
import { FC, useEffect, useState } from 'react' import { FC, useEffect, useState } from 'react'
@ -25,7 +26,8 @@ const AboutSettings: FC = () => {
const [version, setVersion] = useState('') const [version, setVersion] = useState('')
const [isPortable, setIsPortable] = useState(false) const [isPortable, setIsPortable] = useState(false)
const { t } = useTranslation() const { t } = useTranslation()
const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess } = useSettings() const { autoCheckUpdate, setAutoCheckUpdate, earlyAccess, setEarlyAccess, upgradeChannel, setUpgradeChannel } =
useSettings()
const { theme } = useTheme() const { theme } = useTheme()
const dispatch = useAppDispatch() const dispatch = useAppDispatch()
const { update } = useRuntime() const { update } = useRuntime()
@ -95,15 +97,65 @@ const AboutSettings: FC = () => {
const hasNewVersion = update?.info?.version && version ? compareVersions(update.info.version, version) > 0 : false 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(() => { useEffect(() => {
runAsyncFunction(async () => { runAsyncFunction(async () => {
const appInfo = await window.api.getAppInfo() const appInfo = await window.api.getAppInfo()
setVersion(appInfo.version) setVersion(appInfo.version)
setIsPortable(appInfo.isPortable) setIsPortable(appInfo.isPortable)
}) })
setEarlyAccess(earlyAccess)
setAutoCheckUpdate(autoCheckUpdate) setAutoCheckUpdate(autoCheckUpdate)
}, [autoCheckUpdate, earlyAccess, setAutoCheckUpdate, setEarlyAccess]) }, [autoCheckUpdate, setAutoCheckUpdate, setEarlyAccess])
return ( return (
<SettingContainer theme={theme}> <SettingContainer theme={theme}>
@ -167,9 +219,29 @@ const AboutSettings: FC = () => {
<SettingRow> <SettingRow>
<SettingRowTitle>{t('settings.general.early_access.title')}</SettingRowTitle> <SettingRowTitle>{t('settings.general.early_access.title')}</SettingRowTitle>
<Tooltip title={t('settings.general.early_access.tooltip')} trigger={['hover', 'focus']}> <Tooltip title={t('settings.general.early_access.tooltip')} trigger={['hover', 'focus']}>
<Switch value={earlyAccess} onChange={(v) => setEarlyAccess(v)} /> <Switch value={earlyAccess} onChange={(v) => handlerSetEarlyAccess(v)} />
</Tooltip> </Tooltip>
</SettingRow> </SettingRow>
{earlyAccess && getAvailableTestChannels().length > 0 && (
<>
<SettingDivider />
<SettingRow>
<SettingRowTitle>{t('settings.general.early_access.version_options')}</SettingRowTitle>
<Radio.Group
size="small"
buttonStyle="solid"
defaultValue={UpgradeChannel.LATEST}
value={upgradeChannel}
onChange={(e) => handleUpgradeChannelChange(e.target.value)}>
{getAvailableTestChannels().map((option) => (
<Tooltip key={option.value} title={option.tooltip}>
<Radio.Button value={option.value}>{option.label}</Radio.Button>
</Tooltip>
))}
</Radio.Group>
</SettingRow>
</>
)}
</> </>
)} )}
</SettingGroup> </SettingGroup>

View File

@ -7,6 +7,7 @@ import db from '@renderer/databases'
import i18n from '@renderer/i18n' import i18n from '@renderer/i18n'
import { Assistant, Provider, WebSearchProvider } from '@renderer/types' import { Assistant, Provider, WebSearchProvider } from '@renderer/types'
import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils' import { getDefaultGroupName, getLeadingEmoji, runAsyncFunction, uuid } from '@renderer/utils'
import { UpgradeChannel } from '@shared/config/constant'
import { isEmpty } from 'lodash' import { isEmpty } from 'lodash'
import { createMigrate } from 'redux-persist' import { createMigrate } from 'redux-persist'
@ -1627,6 +1628,9 @@ const migrateConfig = {
} }
} }
}) })
if (state.settings) {
state.settings.upgradeChannel = UpgradeChannel.LATEST
}
return state return state
} catch (error) { } catch (error) {
return state return state

View File

@ -11,6 +11,7 @@ import {
ThemeMode, ThemeMode,
TranslateLanguageVarious TranslateLanguageVarious
} from '@renderer/types' } from '@renderer/types'
import { UpgradeChannel } from '@shared/config/constant'
import { WebDAVSyncState } from './backup' import { WebDAVSyncState } from './backup'
@ -68,6 +69,7 @@ export interface SettingsState {
clickAssistantToShowTopic: boolean clickAssistantToShowTopic: boolean
autoCheckUpdate: boolean autoCheckUpdate: boolean
earlyAccess: boolean earlyAccess: boolean
upgradeChannel: UpgradeChannel
renderInputMessageAsMarkdown: boolean renderInputMessageAsMarkdown: boolean
// 代码执行 // 代码执行
codeExecution: { codeExecution: {
@ -221,6 +223,7 @@ export const initialState: SettingsState = {
clickAssistantToShowTopic: true, clickAssistantToShowTopic: true,
autoCheckUpdate: true, autoCheckUpdate: true,
earlyAccess: false, earlyAccess: false,
upgradeChannel: UpgradeChannel.LATEST,
renderInputMessageAsMarkdown: false, renderInputMessageAsMarkdown: false,
codeExecution: { codeExecution: {
enabled: false, enabled: false,
@ -429,6 +432,9 @@ const settingsSlice = createSlice({
setEarlyAccess: (state, action: PayloadAction<boolean>) => { setEarlyAccess: (state, action: PayloadAction<boolean>) => {
state.earlyAccess = action.payload state.earlyAccess = action.payload
}, },
setUpgradeChannel: (state, action: PayloadAction<UpgradeChannel>) => {
state.upgradeChannel = action.payload
},
setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => { setRenderInputMessageAsMarkdown: (state, action: PayloadAction<boolean>) => {
state.renderInputMessageAsMarkdown = action.payload state.renderInputMessageAsMarkdown = action.payload
}, },
@ -725,6 +731,7 @@ export const {
setPasteLongTextAsFile, setPasteLongTextAsFile,
setAutoCheckUpdate, setAutoCheckUpdate,
setEarlyAccess, setEarlyAccess,
setUpgradeChannel,
setRenderInputMessageAsMarkdown, setRenderInputMessageAsMarkdown,
setClickAssistantToShowTopic, setClickAssistantToShowTopic,
setSkipBackupFile, setSkipBackupFile,