From 4f8507036ac8589635aeac6b87c1b1588df54bea Mon Sep 17 00:00:00 2001 From: QiyuanChen <72334646+qychen2001@users.noreply.github.com> Date: Sun, 21 Sep 2025 21:09:37 +0800 Subject: [PATCH 01/10] feat(image): add Qwen-Image models in the Siliconflow (#10268) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: 添加 Qwen 图像模型到 TEXT_TO_IMAGES_MODELS * Remove Qwen-Image-Edit --- src/renderer/src/pages/paintings/SiliconPage.tsx | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/renderer/src/pages/paintings/SiliconPage.tsx b/src/renderer/src/pages/paintings/SiliconPage.tsx index eebd3ccb95..691aaa0d01 100644 --- a/src/renderer/src/pages/paintings/SiliconPage.tsx +++ b/src/renderer/src/pages/paintings/SiliconPage.tsx @@ -47,6 +47,12 @@ export const TEXT_TO_IMAGES_MODELS = [ provider: 'silicon', name: 'Kolors', group: 'Kwai-Kolors' + }, + { + id: 'Qwen/Qwen-Image', + provider: 'silicon', + name: 'Qwen-Image', + group: 'qwen' } ] From 67a6a6a4451fb3728489863abb059c647c85db5f Mon Sep 17 00:00:00 2001 From: SuYao Date: Mon, 22 Sep 2025 00:11:27 +0800 Subject: [PATCH 02/10] fix: support leadingspace to avoid normal text (#10264) * fix: support leadingspace to avoid normal text * Close QuickPanel when no search results found Add automatic closing of QuickPanel when search yields no results for single-select input triggers, preventing users from getting stuck with empty result lists. * fix: reopen quick panel while editing trigger text * fix: hide quick trigger hints when disabled * Update zh-tw.json --- .../src/components/QuickPanel/view.tsx | 33 ++++-- src/renderer/src/i18n/locales/en-us.json | 3 +- src/renderer/src/i18n/locales/zh-cn.json | 3 +- src/renderer/src/i18n/locales/zh-tw.json | 3 +- .../src/pages/home/Inputbar/Inputbar.tsx | 106 ++++++++++++++---- .../home/Inputbar/MentionModelsButton.tsx | 4 +- 6 files changed, 116 insertions(+), 36 deletions(-) diff --git a/src/renderer/src/components/QuickPanel/view.tsx b/src/renderer/src/components/QuickPanel/view.tsx index 59d72b2de2..52c33607c7 100644 --- a/src/renderer/src/components/QuickPanel/view.tsx +++ b/src/renderer/src/components/QuickPanel/view.tsx @@ -158,15 +158,22 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { const cursorPosition = textArea.selectionStart ?? 0 const textBeforeCursor = textArea.value.slice(0, cursorPosition) - // 查找最后一个 @ 或 / 符号的位置 - const lastAtIndex = textBeforeCursor.lastIndexOf('@') - const lastSlashIndex = textBeforeCursor.lastIndexOf('/') - const lastSymbolIndex = Math.max(lastAtIndex, lastSlashIndex) + // 查找末尾最近的触发符号(@ 或 /),允许位于文本起始或空格后 + const match = textBeforeCursor.match(/(^| )([@/][^\s]*)$/) + if (!match) return - if (lastSymbolIndex === -1) return + const matchIndex = match.index ?? -1 + if (matchIndex === -1) return + + const boundarySegment = match[1] ?? '' + const symbolSegment = match[2] ?? '' + if (!symbolSegment) return + + const boundaryStart = matchIndex + const symbolStart = boundaryStart + boundarySegment.length // 根据 includeSymbol 决定是否删除符号 - const deleteStart = includeSymbol ? lastSymbolIndex : lastSymbolIndex + 1 + const deleteStart = includeSymbol ? boundaryStart : symbolStart + 1 const deleteEnd = cursorPosition if (deleteStart >= deleteEnd) return @@ -203,7 +210,7 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { if (textArea) { setInputText(textArea.value) } - } else if (action && !['outsideclick', 'esc', 'enter_empty'].includes(action)) { + } else if (action && !['outsideclick', 'esc', 'enter_empty', 'no_result'].includes(action)) { clearSearchText(true) } }, @@ -533,6 +540,18 @@ export const QuickPanelView: React.FC = ({ setInputText }) => { const visibleNonPinnedCount = useMemo(() => list.filter((i) => !i.alwaysVisible).length, [list]) const collapsed = hasSearchText && visibleNonPinnedCount === 0 + useEffect(() => { + if (!ctx.isVisible) return + if (!collapsed) return + if (ctx.triggerInfo?.type !== 'input') return + if (ctx.multiple) return + + const trimmedSearch = searchText.replace(/^[/@]/, '').trim() + if (!trimmedSearch) return + + handleClose('no_result') + }, [collapsed, ctx.isVisible, ctx.triggerInfo, ctx.multiple, handleClose, searchText]) + const estimateSize = useCallback(() => ITEM_HEIGHT, []) const rowRenderer = useCallback( diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index 27a3c655df..d576d5544f 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -332,7 +332,8 @@ }, "new_topic": "New Topic {{Command}}", "pause": "Pause", - "placeholder": "Type your message here, press {{key}} to send...", + "placeholder": "Type your message here, press {{key}} to send - @ to Select Model, / to Include Tools", + "placeholder_without_triggers": "Type your message here, press {{key}} to send", "send": "Send", "settings": "Settings", "thinking": { diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index f0c674c3bd..28a53923e5 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -332,7 +332,8 @@ }, "new_topic": "新话题 {{Command}}", "pause": "暂停", - "placeholder": "在这里输入消息,按 {{key}} 发送...", + "placeholder": "在这里输入消息,按 {{key}} 发送 - @ 选择模型, / 选择工具", + "placeholder_without_triggers": "在这里输入消息,按 {{key}} 发送", "send": "发送", "settings": "设置", "thinking": { diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index ab2fc860d9..e191e940c7 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -332,7 +332,8 @@ }, "new_topic": "新話題 {{Command}}", "pause": "暫停", - "placeholder": "在此輸入您的訊息,按 {{key}} 傳送...", + "placeholder": "在此輸入您的訊息,按 {{key}} 傳送 - @ 選擇模型,/ 包含工具", + "placeholder_without_triggers": "在此輸入您的訊息,按 {{key}} 傳送", "send": "傳送", "settings": "設定", "thinking": { diff --git a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx index 5897c4fe45..282f62656d 100644 --- a/src/renderer/src/pages/home/Inputbar/Inputbar.tsx +++ b/src/renderer/src/pages/home/Inputbar/Inputbar.tsx @@ -162,6 +162,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const [tokenCount, setTokenCount] = useState(0) const inputbarToolsRef = useRef(null) + const prevTextRef = useRef(text) // eslint-disable-next-line react-hooks/exhaustive-deps const debouncedEstimate = useCallback( @@ -178,8 +179,21 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = debouncedEstimate(text) }, [text, debouncedEstimate]) + useEffect(() => { + prevTextRef.current = text + }, [text]) + const inputTokenCount = showInputEstimatedTokens ? tokenCount : 0 + const placeholderText = enableQuickPanelTriggers + ? t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) }) + : t('chat.input.placeholder_without_triggers', { + key: getSendMessageShortcutLabel(sendMessageShortcut), + defaultValue: t('chat.input.placeholder', { + key: getSendMessageShortcutLabel(sendMessageShortcut) + }) + }) + const inputEmpty = isEmpty(text.trim()) && files.length === 0 _text = text @@ -441,43 +455,91 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = const newText = e.target.value setText(newText) + const prevText = prevTextRef.current + const isDeletion = newText.length < prevText.length + const textArea = textareaRef.current?.resizableTextArea?.textArea - const cursorPosition = textArea?.selectionStart ?? 0 + const cursorPosition = textArea?.selectionStart ?? newText.length const lastSymbol = newText[cursorPosition - 1] + const previousChar = newText[cursorPosition - 2] + const isCursorAtTextStart = cursorPosition <= 1 + const hasValidTriggerBoundary = previousChar === ' ' || isCursorAtTextStart + + const openRootPanelAt = (position: number) => { + const quickPanelMenu = + inputbarToolsRef.current?.getQuickPanelMenu({ + text: newText, + translate + }) || [] + + quickPanel.open({ + title: t('settings.quickPanel.title'), + list: quickPanelMenu, + symbol: QuickPanelReservedSymbol.Root, + triggerInfo: { + type: 'input', + position, + originalText: newText + } + }) + } + + const openMentionPanelAt = (position: number) => { + inputbarToolsRef.current?.openMentionModelsPanel({ + type: 'input', + position, + originalText: newText + }) + } + + if (enableQuickPanelTriggers && !quickPanel.isVisible) { + const textBeforeCursor = newText.slice(0, cursorPosition) + const lastRootIndex = textBeforeCursor.lastIndexOf(QuickPanelReservedSymbol.Root) + const lastMentionIndex = textBeforeCursor.lastIndexOf(QuickPanelReservedSymbol.MentionModels) + const lastTriggerIndex = Math.max(lastRootIndex, lastMentionIndex) + + if (lastTriggerIndex !== -1 && cursorPosition > lastTriggerIndex) { + const triggerChar = newText[lastTriggerIndex] + const boundaryChar = newText[lastTriggerIndex - 1] ?? '' + const hasBoundary = lastTriggerIndex === 0 || /\s/.test(boundaryChar) + const searchSegment = newText.slice(lastTriggerIndex + 1, cursorPosition) + const hasSearchContent = searchSegment.trim().length > 0 + + if (hasBoundary && (!hasSearchContent || isDeletion)) { + if (triggerChar === QuickPanelReservedSymbol.Root) { + openRootPanelAt(lastTriggerIndex) + } else if (triggerChar === QuickPanelReservedSymbol.MentionModels) { + openMentionPanelAt(lastTriggerIndex) + } + } + } + } // 触发符号为 '/':若当前未打开或符号不同,则切换/打开 - if (enableQuickPanelTriggers && lastSymbol === QuickPanelReservedSymbol.Root) { + if (enableQuickPanelTriggers && lastSymbol === QuickPanelReservedSymbol.Root && hasValidTriggerBoundary) { if (quickPanel.isVisible && quickPanel.symbol !== QuickPanelReservedSymbol.Root) { quickPanel.close('switch-symbol') } if (!quickPanel.isVisible || quickPanel.symbol !== QuickPanelReservedSymbol.Root) { - const quickPanelMenu = - inputbarToolsRef.current?.getQuickPanelMenu({ - text: newText, - translate - }) || [] - - quickPanel.open({ - title: t('settings.quickPanel.title'), - list: quickPanelMenu, - symbol: QuickPanelReservedSymbol.Root - }) + openRootPanelAt(cursorPosition - 1) } } // 触发符号为 '@':若当前未打开或符号不同,则切换/打开 - if (enableQuickPanelTriggers && lastSymbol === QuickPanelReservedSymbol.MentionModels) { + if ( + enableQuickPanelTriggers && + lastSymbol === QuickPanelReservedSymbol.MentionModels && + hasValidTriggerBoundary + ) { if (quickPanel.isVisible && quickPanel.symbol !== QuickPanelReservedSymbol.MentionModels) { quickPanel.close('switch-symbol') } if (!quickPanel.isVisible || quickPanel.symbol !== QuickPanelReservedSymbol.MentionModels) { - inputbarToolsRef.current?.openMentionModelsPanel({ - type: 'input', - position: cursorPosition - 1, - originalText: newText - }) + openMentionPanelAt(cursorPosition - 1) } } + + prevTextRef.current = newText }, [enableQuickPanelTriggers, quickPanel, t, translate] ) @@ -783,11 +845,7 @@ const Inputbar: FC = ({ assistant: _assistant, setActiveTopic, topic }) = value={text} onChange={onChange} onKeyDown={handleKeyDown} - placeholder={ - isTranslating - ? t('chat.input.translating') - : t('chat.input.placeholder', { key: getSendMessageShortcutLabel(sendMessageShortcut) }) - } + placeholder={isTranslating ? t('chat.input.translating') : placeholderText} autoFocus variant="borderless" spellCheck={enableSpellCheck} diff --git a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx index ceaa748bf5..6bb36f988a 100644 --- a/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx +++ b/src/renderer/src/pages/home/Inputbar/MentionModelsButton.tsx @@ -89,7 +89,7 @@ const MentionModelsButton: FC = ({ // 兜底:使用打开时的 position(若存在),按空白边界删除 if (typeof fallbackPosition === 'number' && currentText[fallbackPosition] === '@') { let endPos = fallbackPosition + 1 - while (endPos < currentText.length && currentText[endPos] !== ' ' && currentText[endPos] !== '\n') { + while (endPos < currentText.length && !/\s/.test(currentText[endPos])) { endPos++ } return currentText.slice(0, fallbackPosition) + currentText.slice(endPos) @@ -98,7 +98,7 @@ const MentionModelsButton: FC = ({ } let endPos = start + 1 - while (endPos < currentText.length && currentText[endPos] !== ' ' && currentText[endPos] !== '\n') { + while (endPos < currentText.length && !/\s/.test(currentText[endPos])) { endPos++ } return currentText.slice(0, start) + currentText.slice(endPos) From 87d9c7b4107730d0a03f6c70ae928798c80c4490 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Thu, 11 Sep 2025 21:00:51 +0800 Subject: [PATCH 03/10] feat: add client ID generation and update user agent headers in AppUpdater - Introduced a new method in ConfigManager to generate and retrieve a unique client ID. - Updated AppUpdater to include the client ID in the request headers alongside the user agent. --- src/main/services/AppUpdater.ts | 5 +++-- src/main/services/ConfigManager.ts | 15 ++++++++++++++- 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index bdfb8e3cc8..67e291f9cf 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -2,7 +2,7 @@ import { loggerService } from '@logger' import { isWin } from '@main/constant' import { getIpCountry } from '@main/utils/ipService' import { locales } from '@main/utils/locales' -import { generateUserAgent } from '@main/utils/systemInfo' +import { generateClientTelemetryHeader, generateUserAgent } from '@main/utils/systemInfo' import { FeedUrl, UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { CancellationToken, UpdateInfo } from 'builder-util-runtime' @@ -30,7 +30,8 @@ export default class AppUpdater { autoUpdater.autoInstallOnAppQuit = configManager.getAutoUpdate() autoUpdater.requestHeaders = { ...autoUpdater.requestHeaders, - 'User-Agent': generateUserAgent() + 'User-Agent': generateUserAgent(), + 'X-Client-Id': configManager.getClientId() } autoUpdater.on('error', (error) => { diff --git a/src/main/services/ConfigManager.ts b/src/main/services/ConfigManager.ts index 5f5be2c723..3cab0bf91d 100644 --- a/src/main/services/ConfigManager.ts +++ b/src/main/services/ConfigManager.ts @@ -2,6 +2,7 @@ import { defaultLanguage, UpgradeChannel, ZOOM_SHORTCUTS } from '@shared/config/ import { LanguageVarious, Shortcut, ThemeMode } from '@types' import { app } from 'electron' import Store from 'electron-store' +import { v4 as uuidv4 } from 'uuid' import { locales } from '../utils/locales' @@ -27,7 +28,8 @@ export enum ConfigKeys { SelectionAssistantFilterList = 'selectionAssistantFilterList', DisableHardwareAcceleration = 'disableHardwareAcceleration', Proxy = 'proxy', - EnableDeveloperMode = 'enableDeveloperMode' + EnableDeveloperMode = 'enableDeveloperMode', + ClientId = 'clientId' } export class ConfigManager { @@ -241,6 +243,17 @@ export class ConfigManager { this.set(ConfigKeys.EnableDeveloperMode, value) } + getClientId(): string { + let clientId = this.get(ConfigKeys.ClientId) + + if (!clientId) { + clientId = uuidv4() + this.set(ConfigKeys.ClientId, clientId) + } + + return clientId + } + set(key: string, value: unknown, isNotify: boolean = false) { this.store.set(key, value) isNotify && this.notifySubscribers(key, value) From 25c5d671dcf4f11948ee0322e9df0709453fdec8 Mon Sep 17 00:00:00 2001 From: beyondkmp Date: Mon, 22 Sep 2025 23:04:57 +0800 Subject: [PATCH 04/10] fix(assistant): update translate assistant content handling for QwenMT model (#10306) * fix(assistant): update translate assistant content handling for QwenMT model - Adjusted content assignment in getDefaultTranslateAssistant to use store settings only when the model is not a QwenMT model, ensuring correct translation behavior. * lint err * refactor(assistant): encapsulate content handling logic for translation - Introduced a new function, getTranslateContent, to streamline content assignment in getDefaultTranslateAssistant. - This change improves readability and maintains correct translation behavior for QwenMT models. * format code --- src/main/services/AppUpdater.ts | 2 +- src/renderer/src/services/AssistantService.ts | 16 ++++++++++++---- 2 files changed, 13 insertions(+), 5 deletions(-) diff --git a/src/main/services/AppUpdater.ts b/src/main/services/AppUpdater.ts index 67e291f9cf..3cb1558b0e 100644 --- a/src/main/services/AppUpdater.ts +++ b/src/main/services/AppUpdater.ts @@ -2,7 +2,7 @@ import { loggerService } from '@logger' import { isWin } from '@main/constant' import { getIpCountry } from '@main/utils/ipService' import { locales } from '@main/utils/locales' -import { generateClientTelemetryHeader, generateUserAgent } from '@main/utils/systemInfo' +import { generateUserAgent } from '@main/utils/systemInfo' import { FeedUrl, UpgradeChannel } from '@shared/config/constant' import { IpcChannel } from '@shared/IpcChannel' import { CancellationToken, UpdateInfo } from 'builder-util-runtime' diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index 95dbac85c9..8a86a95937 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -6,6 +6,7 @@ import { MAX_CONTEXT_COUNT, UNLIMITED_CONTEXT_COUNT } from '@renderer/config/constant' +import { isQwenMTModel } from '@renderer/config/models' import { UNKNOWN } from '@renderer/config/translate' import i18n from '@renderer/i18n' import store from '@renderer/store' @@ -69,11 +70,18 @@ export function getDefaultTranslateAssistant(targetLanguage: TranslateLanguage, temperature: 0.7 } - const content = store - .getState() - .settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value) - .replaceAll('{{text}}', text) + const getTranslateContent = (model: Model, text: string, targetLanguage: TranslateLanguage): string => { + if (isQwenMTModel(model)) { + return text // QwenMT models handle raw text directly + } + return store + .getState() + .settings.translateModelPrompt.replaceAll('{{target_language}}', targetLanguage.value) + .replaceAll('{{text}}', text) + } + + const content = getTranslateContent(model, text, targetLanguage) const translateAssistant = { ...assistant, model, From a847b74c3236ca4def5031b749466fdbefd54039 Mon Sep 17 00:00:00 2001 From: jo1yne06 Date: Tue, 23 Sep 2025 19:49:24 +0800 Subject: [PATCH 05/10] feat: add new provider aionly (#10179) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add new provider aionly * fix(store): update migration to properly add 'aionly' provider in v156 Move 'aionly' provider addition from v155 to v156 migration to ensure proper state initialization --------- Co-authored-by: fengjunhao <765838796@qq.com> Co-authored-by: Phantom <59059173+EurFelux@users.noreply.github.com> Co-authored-by: icarus Co-authored-by: 亢奋猫 --- src/main/services/WindowService.ts | 3 +- .../src/assets/images/providers/aiOnly.png | Bin 0 -> 41426 bytes .../src/components/OAuth/OAuthButton.tsx | 5 +++ src/renderer/src/config/models/default.ts | 32 ++++++++++++++++++ src/renderer/src/config/providers.ts | 25 +++++++++++++- src/renderer/src/i18n/label.ts | 3 +- src/renderer/src/i18n/locales/en-us.json | 1 + src/renderer/src/i18n/locales/zh-cn.json | 1 + src/renderer/src/i18n/locales/zh-tw.json | 1 + src/renderer/src/i18n/translate/el-gr.json | 1 + src/renderer/src/i18n/translate/es-es.json | 1 + src/renderer/src/i18n/translate/fr-fr.json | 1 + src/renderer/src/i18n/translate/ja-jp.json | 1 + src/renderer/src/i18n/translate/pt-pt.json | 1 + src/renderer/src/i18n/translate/ru-ru.json | 1 + .../ProviderSettings/ProviderOAuth.tsx | 4 ++- src/renderer/src/services/ProviderService.ts | 2 +- src/renderer/src/store/migrate.ts | 1 + src/renderer/src/types/index.ts | 3 +- src/renderer/src/utils/oauth.ts | 31 +++++++++++++++++ 20 files changed, 112 insertions(+), 6 deletions(-) create mode 100644 src/renderer/src/assets/images/providers/aiOnly.png diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 9b8a176a34..7cf01b9fc7 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -275,7 +275,8 @@ export class WindowService { 'https://aihubmix.com/topup', 'https://aihubmix.com/statistics', 'https://dash.302.ai/sso/login', - 'https://dash.302.ai/charge' + 'https://dash.302.ai/charge', + 'https://www.aiionly.com/login' ] if (oauthProviderUrls.some((link) => url.startsWith(link))) { diff --git a/src/renderer/src/assets/images/providers/aiOnly.png b/src/renderer/src/assets/images/providers/aiOnly.png new file mode 100644 index 0000000000000000000000000000000000000000..a521f3bcb87c1f352868fd3774bf3f16640e902c GIT binary patch literal 41426 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 SsFg2)e`-qFily>*L;gSV8mck? literal 0 HcmV?d00001 diff --git a/src/renderer/src/components/OAuth/OAuthButton.tsx b/src/renderer/src/components/OAuth/OAuthButton.tsx index fec3aae619..3368f60afe 100644 --- a/src/renderer/src/components/OAuth/OAuthButton.tsx +++ b/src/renderer/src/components/OAuth/OAuthButton.tsx @@ -3,6 +3,7 @@ import { Provider } from '@renderer/types' import { oauthWith302AI, oauthWithAihubmix, + oauthWithAiOnly, oauthWithPPIO, oauthWithSiliconFlow, oauthWithTokenFlux @@ -46,6 +47,10 @@ const OAuthButton: FC = ({ provider, onSuccess, ...buttonProps }) => { if (provider.id === '302ai') { oauthWith302AI(handleSuccess) } + + if (provider.id === 'aionly') { + oauthWithAiOnly(handleSuccess) + } } return ( diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index 1b8e9ad1bc..2f13a5e993 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -1785,5 +1785,37 @@ export const SYSTEM_MODELS: Record = provider: 'poe', group: 'poe' } + ], + aionly: [ + { + id: 'claude-opus-4.1', + name: 'claude-opus-4.1', + provider: 'aionly', + group: 'claude' + }, + { + id: 'claude-sonnet4', + name: 'claude-sonnet4', + provider: 'aionly', + group: 'claude' + }, + { + id: 'claude-3.5-sonnet-v2', + name: 'claude-3.5-sonnet-v2', + provider: 'aionly', + group: 'claude' + }, + { + id: 'gpt-4.1', + name: 'gpt-4.1', + provider: 'aionly', + group: 'gpt' + }, + { + id: 'gemini-2.5-flash', + name: 'gemini-2.5-flash', + provider: 'aionly', + group: 'gemini' + } ] } diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index a710605b64..e07414e2c2 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -3,6 +3,7 @@ import HunyuanProviderLogo from '@renderer/assets/images/models/hunyuan.png' import AzureProviderLogo from '@renderer/assets/images/models/microsoft.png' import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' +import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.png' import AlayaNewProviderLogo from '@renderer/assets/images/providers/alayanew.webp' import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png' import AwsProviderLogo from '@renderer/assets/images/providers/aws-bedrock.webp' @@ -600,6 +601,16 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = models: SYSTEM_MODELS['poe'], isSystem: true, enabled: false + }, + aionly: { + id: 'aionly', + name: 'AIOnly', + type: 'openai', + apiKey: '', + apiHost: 'https://api.aiionly.com', + models: SYSTEM_MODELS.aionly, + isSystem: true, + enabled: false } } as const @@ -661,7 +672,8 @@ export const PROVIDER_LOGO_MAP: AtLeast = { vertexai: VertexAIProviderLogo, 'new-api': NewAPIProviderLogo, 'aws-bedrock': AwsProviderLogo, - poe: 'poe' // use svg icon component + poe: 'poe', // use svg icon component + aionly: AiOnlyProviderLogo } as const export function getProviderLogo(providerId: string) { @@ -1255,6 +1267,17 @@ export const PROVIDER_URLS: Record = { docs: 'https://creator.poe.com/docs/external-applications/openai-compatible-api', models: 'https://poe.com/' } + }, + aionly: { + api: { + url: 'https://api.aiionly.com' + }, + websites: { + official: 'https://www.aiionly.com', + apiKey: 'https://www.aiionly.com/keyApi', + docs: 'https://www.aiionly.com/document', + models: 'https://www.aiionly.com' + } } } diff --git a/src/renderer/src/i18n/label.ts b/src/renderer/src/i18n/label.ts index 8d621f6517..a07daa975f 100644 --- a/src/renderer/src/i18n/label.ts +++ b/src/renderer/src/i18n/label.ts @@ -80,7 +80,8 @@ const providerKeyMap = { yi: 'provider.yi', zhinao: 'provider.zhinao', zhipu: 'provider.zhipu', - poe: 'provider.poe' + poe: 'provider.poe', + aionly: 'provider.aionly' } as const /** diff --git a/src/renderer/src/i18n/locales/en-us.json b/src/renderer/src/i18n/locales/en-us.json index d576d5544f..dfd09049ed 100644 --- a/src/renderer/src/i18n/locales/en-us.json +++ b/src/renderer/src/i18n/locales/en-us.json @@ -2018,6 +2018,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 28a53923e5..defbfe9dd6 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2018,6 +2018,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "唯一AI(AiOnly)", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index e191e940c7..b5ac09bb7f 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -2018,6 +2018,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "唯一AI(AiOnly)", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/el-gr.json b/src/renderer/src/i18n/translate/el-gr.json index 8741347fcf..71aca53b82 100644 --- a/src/renderer/src/i18n/translate/el-gr.json +++ b/src/renderer/src/i18n/translate/el-gr.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/es-es.json b/src/renderer/src/i18n/translate/es-es.json index 7bbb64a11c..de3b5cb17f 100644 --- a/src/renderer/src/i18n/translate/es-es.json +++ b/src/renderer/src/i18n/translate/es-es.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Antropológico", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/fr-fr.json b/src/renderer/src/i18n/translate/fr-fr.json index ab74489903..66a3a5b4ae 100644 --- a/src/renderer/src/i18n/translate/fr-fr.json +++ b/src/renderer/src/i18n/translate/fr-fr.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/ja-jp.json b/src/renderer/src/i18n/translate/ja-jp.json index 04bac6fbd0..bee5f54470 100644 --- a/src/renderer/src/i18n/translate/ja-jp.json +++ b/src/renderer/src/i18n/translate/ja-jp.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/pt-pt.json b/src/renderer/src/i18n/translate/pt-pt.json index 4f18f55ef7..5c9bbf566e 100644 --- a/src/renderer/src/i18n/translate/pt-pt.json +++ b/src/renderer/src/i18n/translate/pt-pt.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Antropológico", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/translate/ru-ru.json b/src/renderer/src/i18n/translate/ru-ru.json index 88ce796529..ec50afccd0 100644 --- a/src/renderer/src/i18n/translate/ru-ru.json +++ b/src/renderer/src/i18n/translate/ru-ru.json @@ -2017,6 +2017,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", + "aionly": "AiOnly", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx index 5315110dc2..5fdafa5f23 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx @@ -1,5 +1,6 @@ import AI302ProviderLogo from '@renderer/assets/images/providers/302ai.webp' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' +import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.png' import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png' import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png' import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png' @@ -25,7 +26,8 @@ const PROVIDER_LOGO_MAP = { silicon: SiliconFlowProviderLogo, aihubmix: AiHubMixProviderLogo, ppio: PPIOProviderLogo, - tokenflux: TokenFluxProviderLogo + tokenflux: TokenFluxProviderLogo, + aionly: AiOnlyProviderLogo } const ProviderOAuth: FC = ({ providerId }) => { diff --git a/src/renderer/src/services/ProviderService.ts b/src/renderer/src/services/ProviderService.ts index 7a6409c903..da6df82adc 100644 --- a/src/renderer/src/services/ProviderService.ts +++ b/src/renderer/src/services/ProviderService.ts @@ -33,7 +33,7 @@ export function getProviderByModel(model?: Model) { } export function isProviderSupportAuth(provider: Provider) { - const supportProviders = ['302ai', 'silicon', 'aihubmix', 'ppio', 'tokenflux'] + const supportProviders = ['302ai', 'silicon', 'aihubmix', 'ppio', 'tokenflux', 'aionly'] return supportProviders.includes(provider.id) } diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index 41fa441945..f7fc92ee44 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2479,6 +2479,7 @@ const migrateConfig = { }, '156': (state: RootState) => { try { + addProvider(state, 'aionly') state.llm.providers.forEach((provider) => { if (provider.id === SystemProviderIds.anthropic) { if (provider.apiHost.endsWith('/')) { diff --git a/src/renderer/src/types/index.ts b/src/renderer/src/types/index.ts index cd2562e55a..2d580c8e37 100644 --- a/src/renderer/src/types/index.ts +++ b/src/renderer/src/types/index.ts @@ -321,7 +321,8 @@ export const SystemProviderIds = { gpustack: 'gpustack', voyageai: 'voyageai', 'aws-bedrock': 'aws-bedrock', - poe: 'poe' + poe: 'poe', + aionly: 'aionly' } as const export type SystemProviderId = keyof typeof SystemProviderIds diff --git a/src/renderer/src/utils/oauth.ts b/src/renderer/src/utils/oauth.ts index 9fbb632a07..5d57547f69 100644 --- a/src/renderer/src/utils/oauth.ts +++ b/src/renderer/src/utils/oauth.ts @@ -172,6 +172,27 @@ export const oauthWith302AI = async (setKey) => { window.addEventListener('message', messageHandler) } +export const oauthWithAiOnly = async (setKey) => { + const authUrl = `https://www.aiionly.com/login?inviteCode=1755481173663DrZBBOC0&cherryCode=01` + + const popup = window.open( + authUrl, + 'login', + 'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes' + ) + + const messageHandler = (event) => { + if (event.data.length > 0 && event.data[0]['secretKey'] !== undefined) { + setKey(event.data[0]['secretKey']) + popup?.close() + window.removeEventListener('message', messageHandler) + } + } + + window.removeEventListener('message', messageHandler) + window.addEventListener('message', messageHandler) +} + export const providerCharge = async (provider: string) => { const chargeUrlMap = { silicon: { @@ -198,6 +219,11 @@ export const providerCharge = async (provider: string) => { url: 'https://dash.302.ai/charge', width: 900, height: 700 + }, + aionly: { + url: `https://www.aiionly.com/recharge`, + width: 900, + height: 700 } } @@ -236,6 +262,11 @@ export const providerBills = async (provider: string) => { url: 'https://dash.302.ai/charge', width: 900, height: 700 + }, + aionly: { + url: `https://www.aiionly.com/billManagement`, + width: 900, + height: 700 } } From 183b46be9eae8b4f4573a4ee0e464ad7063c2306 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 22 Sep 2025 20:20:14 +0800 Subject: [PATCH 06/10] feat(ipc): add App_Quit channel and update related handlers - Introduced a new IPC channel for quitting the application. - Updated ipc.ts to handle the App_Quit channel, allowing the app to quit when invoked. - Added corresponding quit method in the preload API for client-side access. - Fixed a minor URL check in WindowService to ensure proper navigation handling. --- packages/shared/IpcChannel.ts | 1 + src/main/ipc.ts | 1 + src/main/services/WindowService.ts | 2 +- src/preload/index.ts | 1 + 4 files changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 1e925984a8..7f766d21e1 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -8,6 +8,7 @@ export enum IpcChannel { App_ShowUpdateDialog = 'app:show-update-dialog', App_CheckForUpdate = 'app:check-for-update', App_Reload = 'app:reload', + App_Quit = 'app:quit', App_Info = 'app:info', App_Proxy = 'app:proxy', App_SetLaunchToTray = 'app:set-launch-to-tray', diff --git a/src/main/ipc.ts b/src/main/ipc.ts index d0ef8ec94a..7ca6853e39 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -126,6 +126,7 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { }) ipcMain.handle(IpcChannel.App_Reload, () => mainWindow.reload()) + ipcMain.handle(IpcChannel.App_Quit, () => app.quit()) ipcMain.handle(IpcChannel.Open_Website, (_, url: string) => shell.openExternal(url)) // Update diff --git a/src/main/services/WindowService.ts b/src/main/services/WindowService.ts index 7cf01b9fc7..66aed098e7 100644 --- a/src/main/services/WindowService.ts +++ b/src/main/services/WindowService.ts @@ -256,7 +256,7 @@ export class WindowService { private setupWebContentsHandlers(mainWindow: BrowserWindow) { mainWindow.webContents.on('will-navigate', (event, url) => { - if (url.includes('localhost:5173')) { + if (url.includes('localhost:517')) { return } diff --git a/src/preload/index.ts b/src/preload/index.ts index af1cac21a1..a77e47e78d 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -47,6 +47,7 @@ const api = { getDiskInfo: (directoryPath: string): Promise<{ free: number; size: number } | null> => ipcRenderer.invoke(IpcChannel.App_GetDiskInfo, directoryPath), reload: () => ipcRenderer.invoke(IpcChannel.App_Reload), + quit: () => ipcRenderer.invoke(IpcChannel.App_Quit), setProxy: (proxy: string | undefined, bypassRules?: string) => ipcRenderer.invoke(IpcChannel.App_Proxy, proxy, bypassRules), checkForUpdate: () => ipcRenderer.invoke(IpcChannel.App_CheckForUpdate), From cc650b58d3181d1c07bd2cf1b1580c7d2e4bca73 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 22 Sep 2025 20:20:23 +0800 Subject: [PATCH 07/10] feat(privacy): add English and Chinese privacy policy pages and popup component - Introduced new HTML files for the privacy policy in English and Chinese. - Implemented a PrivacyPopup component to display the privacy policy within the application. - The popup dynamically loads the appropriate language based on user settings and includes options to accept or decline the policy. --- resources/cherry-studio/privacy-en.html | 252 ++++++++++++++++++ resources/cherry-studio/privacy-zh.html | 230 ++++++++++++++++ .../src/components/Popups/PrivacyPopup.tsx | 137 ++++++++++ 3 files changed, 619 insertions(+) create mode 100644 resources/cherry-studio/privacy-en.html create mode 100644 resources/cherry-studio/privacy-zh.html create mode 100644 src/renderer/src/components/Popups/PrivacyPopup.tsx diff --git a/resources/cherry-studio/privacy-en.html b/resources/cherry-studio/privacy-en.html new file mode 100644 index 0000000000..612f18a70f --- /dev/null +++ b/resources/cherry-studio/privacy-en.html @@ -0,0 +1,252 @@ + + + + + + + Privacy Policy + + + + + +
+

Privacy Policy

+ +

+ Welcome to Cherry Studio (hereinafter referred to as "the Software" or "we"). We highly value your privacy + protection. This Privacy Policy explains how we process and protect your personal information and data. + Please read and understand this policy carefully before using the Software: +

+ +

1. Information We Collect

+

To optimize user experience and improve software quality, we may only collect the following anonymous, + non-personal information:

+
    +
  • Software version information
  • +
  • Activity and usage frequency of software features
  • +
  • Anonymous crash and error log information
  • +
+

The above information is completely anonymous, does not involve any personal identity data, and cannot be + linked to your personal information.

+ +

2. Information We Do Not Collect

+

To maximize the protection of your privacy and security, we explicitly commit that we:

+
    +
  • Will not collect, save, transmit, or process model service API Key information you enter into the + Software
  • +
  • Will not collect, save, transmit, or process any conversation data generated during your use of the + Software, including but not limited to chat content, instruction information, knowledge base + information, vector data, and other custom content
  • +
  • Will not collect, save, transmit, or process any sensitive information that can identify personal + identity
  • +
+ +

3. Data Interaction Description

+

+ The Software uses API Keys from third-party model service providers that you apply for and configure + yourself to complete model calls and conversation functions. The model services you use (such as large + models, API interfaces, etc.) are directly provided by third-party providers of your choice. We do not + intervene, monitor, or interfere with the data transmission process. +

+

+ Data interactions between you and third-party model services are governed by the privacy policies and user + agreements of third-party service providers. We recommend that you fully understand the privacy terms of + relevant service providers before use. +

+ +

4. Local Data Security Protection

+

The Software is a localized application, and all data is stored on your local device by default. We have + taken the following measures to ensure data security:

+
    +
  • Conversation records, configuration information, and other data are only saved on your local device
  • +
  • Data import/export functions are provided to facilitate your independent management and backup of data +
  • +
  • Your local data will not be uploaded to any server or cloud storage
  • +
+ +

5. Third-Party Services

+

+ When using the Software, you may access third-party services (such as AI model APIs, translation services, + etc.). The use of these third-party services is governed by their respective terms of service and privacy + policies. We strongly recommend that you carefully read and understand the relevant terms before use. +

+ +

6. User Rights

+

You have complete control over your data:

+
    +
  • You can view, modify, and delete all locally stored data at any time
  • +
  • You can choose whether to enable specific features or services
  • +
  • You can stop using the Software and delete all related data at any time
  • +
+ +

7. Children's Privacy Protection

+

The Software is not intended for minors under 18 years of age. If you are a minor, please use the Software + under the guidance of a guardian.

+ +

8. Privacy Policy Updates

+

+ We may update this Privacy Policy based on legal requirements or changes in product features. The updated + policy will be published in the Software and you will be notified before it takes effect. If you do not + agree with the updated terms, you can choose to stop using the Software. +

+ +

9. Contact Us

+

If you have any questions, suggestions, or complaints about this Privacy Policy, please contact us through + the following methods:

+ + + +
+ + + \ No newline at end of file diff --git a/resources/cherry-studio/privacy-zh.html b/resources/cherry-studio/privacy-zh.html new file mode 100644 index 0000000000..db4195f68c --- /dev/null +++ b/resources/cherry-studio/privacy-zh.html @@ -0,0 +1,230 @@ + + + + + + + 隐私协议 + + + + + +
+

隐私协议

+ +

+ 欢迎使用 Cherry Studio(以下简称"本软件"或"我们")。我们高度重视您的隐私保护,本隐私协议将说明我们如何处理与保护您的个人信息和数据。请在使用本软件前仔细阅读并理解本协议: +

+ +

一、我们收集的信息范围

+

为了优化用户体验和提升软件质量,我们仅可能会匿名收集以下非个人化信息:

+
    +
  • 软件版本信息;
  • +
  • 软件功能的活跃度、使用频次;
  • +
  • 匿名的崩溃、错误日志信息;
  • +
+

上述信息完全匿名,不会涉及任何个人身份数据,也无法关联到您的个人信息。

+ +

二、我们不会收集的任何信息

+

为了最大限度保护您的隐私安全,我们明确承诺:

+
    +
  • 不会收集、保存、传输或处理您输入到本软件中的模型服务 API Key 信息;
  • +
  • 不会收集、保存、传输或处理您在使用本软件过程中产生的任何对话数据,包括但不限于聊天内容、指令信息、知识库信息、向量数据及其他自定义内容;
  • +
  • 不会收集、保存、传输或处理任何可识别个人身份的敏感信息。
  • +
+ +

三、数据交互说明

+

+ 本软件采用您自行申请并配置的第三方模型服务提供商的 API Key,以完成相关模型的调用与对话功能。您使用的模型服务(例如大模型、API 接口等)由您选择的第三方提供商直接提供,我们不会介入、监控或干扰数据传输过程。 +

+

+ 您与第三方模型服务之间的数据交互受第三方服务提供商的隐私政策和用户协议约束,我们建议您在使用前充分了解相关服务商的隐私条款。 +

+ +

四、本地数据的安全保护

+

本软件为本地化应用程序,所有数据默认存储在您的本地设备上。我们采取了以下措施保障数据安全:

+
    +
  • 对话记录、配置信息等数据仅保存在您的本地设备中;
  • +
  • 提供数据导入/导出功能,方便您自主管理和备份数据;
  • +
  • 不会将您的本地数据上传至任何服务器或云端存储。
  • +
+ +

五、第三方服务

+

+ 在使用本软件过程中,您可能会接入第三方服务(如 AI 模型 API、翻译服务等)。这些第三方服务的使用受其各自的服务条款和隐私政策约束。我们强烈建议您在使用前仔细阅读并理解相关条款。 +

+ +

六、用户权利

+

您对自己的数据拥有完全的控制权:

+
    +
  • 您可以随时查看、修改、删除本地存储的所有数据;
  • +
  • 您可以选择是否启用特定功能或服务;
  • +
  • 您可以随时停止使用本软件并删除所有相关数据。
  • +
+ +

七、儿童隐私保护

+

本软件不面向 18 岁以下的未成年人提供服务。如果您是未成年人,请在监护人的指导下使用本软件。

+ +

八、隐私政策的更新

+

+ 我们可能会根据法律法规要求或产品功能的变化更新本隐私协议。更新后的协议将在软件中发布,并在生效前通知您。如果您不同意更新后的条款,您可以选择停止使用本软件。 +

+ +

九、联系我们

+

如果您对本隐私协议有任何疑问、建议或投诉,请通过以下方式联系我们:

+ + + +
+ + + \ No newline at end of file diff --git a/src/renderer/src/components/Popups/PrivacyPopup.tsx b/src/renderer/src/components/Popups/PrivacyPopup.tsx new file mode 100644 index 0000000000..1ec9639bfc --- /dev/null +++ b/src/renderer/src/components/Popups/PrivacyPopup.tsx @@ -0,0 +1,137 @@ +import { TopView } from '@renderer/components/TopView' +import { useTheme } from '@renderer/context/ThemeProvider' +import { ThemeMode } from '@renderer/types' +import { runAsyncFunction } from '@renderer/utils' +import { Button, Modal } from 'antd' +import { useEffect, useState } from 'react' +import { useTranslation } from 'react-i18next' +import styled from 'styled-components' + +const WebViewContainer = styled.div` + width: 100%; + height: 500px; + overflow: hidden; + + webview { + width: 100%; + height: 100%; + border: none; + background: transparent; + } +` + +interface ShowParams { + title?: string + showDeclineButton?: boolean +} + +interface Props extends ShowParams { + resolve: (data: any) => void +} + +const PopupContainer: React.FC = ({ title, showDeclineButton = true, resolve }) => { + const [open, setOpen] = useState(true) + const [privacyUrl, setPrivacyUrl] = useState('') + const { theme } = useTheme() + const { i18n } = useTranslation() + + const getTitle = () => { + if (title) return title + const isChinese = i18n.language.startsWith('zh') + return isChinese ? '隐私协议' : 'Privacy Policy' + } + + const handleAccept = () => { + setOpen(false) + localStorage.setItem('privacy-popup-accepted', 'true') + resolve({ accepted: true }) + } + + const handleDecline = () => { + setOpen(false) + window.api.quit() + resolve({ accepted: false }) + } + + const onClose = () => { + if (!showDeclineButton) { + handleAccept() + } else { + handleDecline() + } + } + + useEffect(() => { + runAsyncFunction(async () => { + const { appPath } = await window.api.getAppInfo() + const isChinese = i18n.language.startsWith('zh') + const htmlFile = isChinese ? 'privacy-zh.html' : 'privacy-en.html' + const url = `file://${appPath}/resources/cherry-studio/${htmlFile}?theme=${theme === ThemeMode.dark ? 'dark' : 'light'}` + setPrivacyUrl(url) + }) + }, [theme, i18n.language]) + + PrivacyPopup.hide = () => setOpen(false) + + return ( + + {i18n.language.startsWith('zh') ? '拒绝' : 'Decline'} + + ), + + ].filter(Boolean)}> + + {privacyUrl && } + + + ) +} + +const TopViewKey = 'PrivacyPopup' + +export default class PrivacyPopup { + static topviewId = 0 + static hide() { + TopView.hide(TopViewKey) + } + static async show(props?: ShowParams) { + const accepted = localStorage.getItem('privacy-popup-accepted') + + if (accepted) { + return + } + + return new Promise<{ accepted: boolean }>((resolve) => { + TopView.show( + { + resolve(v) + TopView.hide(TopViewKey) + }} + />, + TopViewKey + ) + }) + } +} From 3b34efd33af24e1271f1f198a8fbfec5dcadd684 Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Mon, 22 Sep 2025 20:24:54 +0800 Subject: [PATCH 08/10] feat(settings): update MCP server card layout and styling - Adjusted the width of the CardContainer to dynamically calculate based on viewport width. - Changed the layout of the McpServersList from grid to list, with a vertical orientation and updated styling for list items. --- src/renderer/src/pages/settings/MCPSettings/McpServerCard.tsx | 1 + .../src/pages/settings/MCPSettings/McpServersList.tsx | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/src/renderer/src/pages/settings/MCPSettings/McpServerCard.tsx b/src/renderer/src/pages/settings/MCPSettings/McpServerCard.tsx index 518849d4f0..3d38255bf5 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpServerCard.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpServerCard.tsx @@ -183,6 +183,7 @@ const CardContainer = styled.div<{ $isActive: boolean }>` margin-bottom: 5px; height: 125px; opacity: ${(props) => (props.$isActive ? 1 : 0.6)}; + width: calc(100vw - var(--settings-width) - 40px); &:hover { opacity: 1; diff --git a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx index 67ae6231a8..7e18af264d 100644 --- a/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx +++ b/src/renderer/src/pages/settings/MCPSettings/McpServersList.tsx @@ -249,7 +249,9 @@ const McpServersList: FC = () => { items={filteredMcpServers} itemKey="id" onSortEnd={onSortEnd} - layout="grid" + layout="list" + horizontal={false} + listStyle={{ display: 'flex', flexDirection: 'column' }} gap="12px" restrictions={{ scrollableAncestor: true }} useDragOverlay From 37218eef4f3f886402a96214046e31aebaf294ae Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Tue, 23 Sep 2025 19:18:42 +0800 Subject: [PATCH 09/10] feat: enable cherryin provider --- .github/workflows/nightly-build.yml | 6 +-- .github/workflows/release.yml | 6 +-- .oxlintrc.json | 2 +- eslint.config.mjs | 2 +- packages/shared/IpcChannel.ts | 4 +- src/main/config.ts | 2 +- src/main/integration/cherryai/index.js | 1 + src/main/integration/cherryin/index.js | 1 - src/main/ipc.ts | 6 +-- src/preload/index.ts | 4 +- .../aiCore/legacy/clients/ApiClientFactory.ts | 6 +-- .../index.clientCompatibilityTypes.test.ts | 8 ++- .../CherryAiAPIClient.ts} | 8 +-- .../common/ErrorHandlerMiddleware.ts | 4 +- .../src/aiCore/provider/providerConfig.ts | 4 +- .../src/assets/images/providers/aiOnly.png | Bin 41426 -> 0 bytes .../src/assets/images/providers/aiOnly.webp | Bin 0 -> 10588 bytes .../src/components/FreeTrialModelTag.tsx | 2 +- .../Popups/SelectModelPopup/popup.tsx | 12 +++-- .../src/config/__test__/models.test.ts | 6 +-- src/renderer/src/config/models/default.ts | 19 ++----- src/renderer/src/config/providers.ts | 28 +++++++--- src/renderer/src/hooks/useModel.ts | 5 +- src/renderer/src/hooks/useProvider.ts | 9 ++-- src/renderer/src/hooks/useStore.ts | 7 ++- src/renderer/src/i18n/locales/zh-cn.json | 2 +- src/renderer/src/i18n/locales/zh-tw.json | 2 +- src/renderer/src/pages/code/CodeToolsPage.tsx | 2 +- .../home/components/SelectModelButton.tsx | 4 +- .../ProviderSettings/ModelList/ModelList.tsx | 40 +++++++-------- .../ProviderSettings/ProviderOAuth.tsx | 2 +- .../ProviderSettings/ProviderSetting.tsx | 2 +- src/renderer/src/services/ApiService.ts | 2 +- src/renderer/src/services/AssistantService.ts | 11 ++-- src/renderer/src/services/ModelService.ts | 9 ++-- src/renderer/src/services/ProviderService.ts | 8 +-- src/renderer/src/store/index.ts | 2 +- src/renderer/src/store/migrate.ts | 48 +++++++++++++++++- src/renderer/src/utils/model.ts | 2 +- tsconfig.web.json | 2 +- 40 files changed, 173 insertions(+), 117 deletions(-) create mode 100644 src/main/integration/cherryai/index.js delete mode 100644 src/main/integration/cherryin/index.js rename src/renderer/src/aiCore/legacy/clients/{cherryin/CherryinAPIClient.ts => cherryai/CherryAiAPIClient.ts} (86%) delete mode 100644 src/renderer/src/assets/images/providers/aiOnly.png create mode 100644 src/renderer/src/assets/images/providers/aiOnly.webp diff --git a/.github/workflows/nightly-build.yml b/.github/workflows/nightly-build.yml index ce07892bc4..7f7100dc54 100644 --- a/.github/workflows/nightly-build.yml +++ b/.github/workflows/nightly-build.yml @@ -98,7 +98,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -115,7 +115,7 @@ jobs: APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -127,7 +127,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7428aa031e..c4a772ad6b 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -85,7 +85,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -103,7 +103,7 @@ jobs: APPLE_TEAM_ID: ${{ vars.APPLE_TEAM_ID }} GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} @@ -115,7 +115,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} NODE_OPTIONS: --max-old-space-size=8192 - MAIN_VITE_CHERRYIN_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYIN_CLIENT_SECRET }} + MAIN_VITE_CHERRYAI_CLIENT_SECRET: ${{ secrets.MAIN_VITE_CHERRYAI_CLIENT_SECRET }} MAIN_VITE_MINERU_API_KEY: ${{ vars.MAIN_VITE_MINERU_API_KEY }} RENDERER_VITE_AIHUBMIX_SECRET: ${{ vars.RENDERER_VITE_AIHUBMIX_SECRET }} RENDERER_VITE_PPIO_APP_SECRET: ${{ vars.RENDERER_VITE_PPIO_APP_SECRET }} diff --git a/.oxlintrc.json b/.oxlintrc.json index 6f4accbece..0a6a9764b7 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -15,7 +15,7 @@ ".gitignore", "scripts/cloudflare-worker.js", "src/main/integration/nutstore/sso/lib/**", - "src/main/integration/cherryin/index.js", + "src/main/integration/cherryai/index.js", "src/main/integration/nutstore/sso/lib/**", "src/renderer/src/ui/**", "packages/**/dist", diff --git a/eslint.config.mjs b/eslint.config.mjs index f9e6c07501..be4a95cd60 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -59,7 +59,7 @@ export default defineConfig([ '.gitignore', 'scripts/cloudflare-worker.js', 'src/main/integration/nutstore/sso/lib/**', - 'src/main/integration/cherryin/index.js', + 'src/main/integration/cherryai/index.js', 'src/main/integration/nutstore/sso/lib/**', 'src/renderer/src/ui/**', 'packages/**/dist' diff --git a/packages/shared/IpcChannel.ts b/packages/shared/IpcChannel.ts index 7f766d21e1..0eb0dd2797 100644 --- a/packages/shared/IpcChannel.ts +++ b/packages/shared/IpcChannel.ts @@ -330,6 +330,6 @@ export enum IpcChannel { // OCR OCR_ocr = 'ocr:ocr', - // Cherryin - Cherryin_GetSignature = 'cherryin:get-signature' + // CherryAI + Cherryai_GetSignature = 'cherryai:get-signature' } diff --git a/src/main/config.ts b/src/main/config.ts index 0cffcd1768..b4859bafb0 100644 --- a/src/main/config.ts +++ b/src/main/config.ts @@ -21,4 +21,4 @@ export const titleBarOverlayLight = { symbolColor: '#000' } -global.CHERRYIN_CLIENT_SECRET = import.meta.env.MAIN_VITE_CHERRYIN_CLIENT_SECRET +global.CHERRYAI_CLIENT_SECRET = import.meta.env.MAIN_VITE_CHERRYAI_CLIENT_SECRET diff --git a/src/main/integration/cherryai/index.js b/src/main/integration/cherryai/index.js new file mode 100644 index 0000000000..eccd3b85bf --- /dev/null +++ b/src/main/integration/cherryai/index.js @@ -0,0 +1 @@ +var _0xe15d9a;const crypto=require("\u0063\u0072\u0079\u0070\u0074\u006F");_0xe15d9a=(988194^988194)+(417607^417603);var _0x9b_0x742=(247379^247387)+(371889^371892);const CLIENT_ID="\u0063\u0068\u0065\u0072\u0072\u0079\u002D\u0073\u0074\u0075\u0064\u0069\u006F";_0x9b_0x742=(202849^202856)+(796590^796585);var _0xa971e=(422203^422203)+(167917^167919);const CLIENT_SECRET_SUFFIX="\u0047\u0076\u0049\u0036\u0049\u0035\u005A\u0072\u0045\u0048\u0063\u0047\u004F\u0057\u006A\u004F\u0035\u0041\u004B\u0068\u004A\u004B\u0047\u006D\u006E\u0077\u0077\u0047\u0066\u004D\u0036\u0032\u0058\u004B\u0070\u0057\u0071\u006B\u006A\u0068\u0076\u007A\u0052\u0055\u0032\u004E\u005A\u0049\u0069\u006E\u004D\u0037\u0037\u0061\u0054\u0047\u0049\u0071\u0068\u0071\u0079\u0073\u0030\u0067";_0xa971e=(607707^607705)+(127822^127823);const CLIENT_SECRET=global['\u0043\u0048\u0045\u0052\u0052\u0059\u0041\u0049\u005F\u0043\u004C\u0049\u0045\u004E\u0054\u005F\u0053\u0045\u0043\u0052\u0045\u0054']+"\u002E"+CLIENT_SECRET_SUFFIX;class SignatureClient{constructor(clientId,clientSecret){this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064']=clientId||CLIENT_ID;this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']=clientSecret||CLIENT_SECRET;this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']=this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']['\u0062\u0069\u006E\u0064'](this);}generateSignature(options){const{'\u006D\u0065\u0074\u0068\u006F\u0064':method,'\u0070\u0061\u0074\u0068':path,'\u0071\u0075\u0065\u0072\u0079':query='','\u0062\u006F\u0064\u0079':body=''}=options;var _0x99a7f=(735625^735624)+(520507^520508);const timestamp=Math['\u0066\u006C\u006F\u006F\u0072'](Date['\u006E\u006F\u0077']()/(351300^352172))['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();_0x99a7f=376728^376729;var _0x733a=(876666^876671)+(658949^658944);let bodyString='';_0x733a="kgclcd".split("").reverse().join("");if(body){if(typeof body==="tcejbo".split("").reverse().join("")){bodyString=JSON['\u0073\u0074\u0072\u0069\u006E\u0067\u0069\u0066\u0079'](body);}else{bodyString=body['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();}}var _0xd8edff;const signatureParts=[method['\u0074\u006F\u0055\u0070\u0070\u0065\u0072\u0043\u0061\u0073\u0065'](),path,query,this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],timestamp,bodyString];_0xd8edff=(929945^929951)+(569907^569915);var _0x9g3c3b=(705579^705579)+(981211^981209);const signatureString=signatureParts['\u006A\u006F\u0069\u006E']("\u000A");_0x9g3c3b=527497^527499;var _0x95b35f=(811203^811200)+(628072^628076);const hmac=crypto['\u0063\u0072\u0065\u0061\u0074\u0065\u0048\u006D\u0061\u0063']("\u0073\u0068\u0061\u0032\u0035\u0036",this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']);_0x95b35f=104120^104112;hmac['\u0075\u0070\u0064\u0061\u0074\u0065'](signatureString);var _0xd0f6g;const signature=hmac['\u0064\u0069\u0067\u0065\u0073\u0074']("xeh".split("").reverse().join(""));_0xd0f6g=(615019^615018)+(266997^266992);return{'X-Client-ID':this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],"\u0058\u002D\u0054\u0069\u006D\u0065\u0073\u0074\u0061\u006D\u0070":timestamp,'X-Signature':signature};}}const signatureClient=new SignatureClient();const generateSignature=signatureClient['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065'];module['\u0065\u0078\u0070\u006F\u0072\u0074\u0073']={'\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065\u0043\u006C\u0069\u0065\u006E\u0074':SignatureClient,"generateSignature":generateSignature}; \ No newline at end of file diff --git a/src/main/integration/cherryin/index.js b/src/main/integration/cherryin/index.js deleted file mode 100644 index af185389eb..0000000000 --- a/src/main/integration/cherryin/index.js +++ /dev/null @@ -1 +0,0 @@ -var _0x6gg;const crypto=require("\u0063\u0072\u0079\u0070\u0074\u006F");_0x6gg='\u006D\u006F\u006C\u006A\u0065\u0065';var _0x111cbe;const CLIENT_ID="oiduts-yrrehc".split("").reverse().join("");_0x111cbe=(977158^977167)+(164595^164594);var _0x6d6adc=(756649^756650)+(497587^497587);const CLIENT_SECRET_SUFFIX="\u0047\u0076\u0049\u0036\u0049\u0035\u005A\u0072\u0045\u0048\u0063\u0047\u004F\u0057\u006A\u004F\u0035\u0041\u004B\u0068\u004A\u004B\u0047\u006D\u006E\u0077\u0077\u0047\u0066\u004D\u0036\u0032\u0058\u004B\u0070\u0057\u0071\u006B\u006A\u0068\u0076\u007A\u0052\u0055\u0032\u004E\u005A\u0049\u0069\u006E\u004D\u0037\u0037\u0061\u0054\u0047\u0049\u0071\u0068\u0071\u0079\u0073\u0030\u0067";_0x6d6adc=233169^233176;const CLIENT_SECRET=global['\u0043\u0048\u0045\u0052\u0052\u0059\u0049\u004E\u005F\u0043\u004C\u0049\u0045\u004E\u0054\u005F\u0053\u0045\u0043\u0052\u0045\u0054']+"\u002E"+CLIENT_SECRET_SUFFIX;class SignatureClient{constructor(clientId,clientSecret){this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064']=clientId||CLIENT_ID;this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']=clientSecret||CLIENT_SECRET;this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']=this['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065']['\u0062\u0069\u006E\u0064'](this);}generateSignature(options){const{"method":method,"path":path,"query":query='',"body":body=''}=options;const timestamp=Math['\u0066\u006C\u006F\u006F\u0072'](Date['\u006E\u006F\u0077']()/(110765^111429))['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();var _0xe08cc=(212246^212244)+(773521^773523);let bodyString='';_0xe08cc=(606778^606776)+(962748^962740);if(body){if(typeof body==="\u006F\u0062\u006A\u0065\u0063\u0074"){bodyString=JSON['\u0073\u0074\u0072\u0069\u006E\u0067\u0069\u0066\u0079'](body);}else{bodyString=body['\u0074\u006F\u0053\u0074\u0072\u0069\u006E\u0067']();}}const signatureParts=[method['\u0074\u006F\u0055\u0070\u0070\u0065\u0072\u0043\u0061\u0073\u0065'](),path,query,this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],timestamp,bodyString];var _0x5693g=(936664^936668)+(685268^685277);const signatureString=signatureParts['\u006A\u006F\u0069\u006E']("\u000A");_0x5693g=(266582^266576)+(337322^337315);const hmac=crypto['\u0063\u0072\u0065\u0061\u0074\u0065\u0048\u006D\u0061\u0063']("\u0073\u0068\u0061\u0032\u0035\u0036",this['\u0063\u006C\u0069\u0065\u006E\u0074\u0053\u0065\u0063\u0072\u0065\u0074']);hmac['\u0075\u0070\u0064\u0061\u0074\u0065'](signatureString);var _0x5fba=(354480^354481)+(537437^537434);const signature=hmac['\u0064\u0069\u0067\u0065\u0073\u0074']("\u0068\u0065\u0078");_0x5fba=(249614^249610)+(915906^915914);return{'X-Client-ID':this['\u0063\u006C\u0069\u0065\u006E\u0074\u0049\u0064'],'X-Timestamp':timestamp,'X-Signature':signature};}}const signatureClient=new SignatureClient();const generateSignature=signatureClient['\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065'];module['\u0065\u0078\u0070\u006F\u0072\u0074\u0073']={'\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065\u0043\u006C\u0069\u0065\u006E\u0074':SignatureClient,'\u0067\u0065\u006E\u0065\u0072\u0061\u0074\u0065\u0053\u0069\u0067\u006E\u0061\u0074\u0075\u0072\u0065':generateSignature}; \ No newline at end of file diff --git a/src/main/ipc.ts b/src/main/ipc.ts index 7ca6853e39..0ef9cca2fa 100644 --- a/src/main/ipc.ts +++ b/src/main/ipc.ts @@ -4,7 +4,7 @@ import path from 'node:path' import { loggerService } from '@logger' import { isLinux, isMac, isPortable, isWin } from '@main/constant' -import { generateSignature } from '@main/integration/cherryin' +import { generateSignature } from '@main/integration/cherryai' import anthropicService from '@main/services/AnthropicService' import { getBinaryPath, isBinaryExists, runInstallScript } from '@main/utils/process' import { handleZoomFactor } from '@main/utils/zoom' @@ -841,6 +841,6 @@ export function registerIpc(mainWindow: BrowserWindow, app: Electron.App) { ocrService.ocr(file, provider) ) - // CherryIN - ipcMain.handle(IpcChannel.Cherryin_GetSignature, (_, params) => generateSignature(params)) + // CherryAI + ipcMain.handle(IpcChannel.Cherryai_GetSignature, (_, params) => generateSignature(params)) } diff --git a/src/preload/index.ts b/src/preload/index.ts index a77e47e78d..faa0335aeb 100644 --- a/src/preload/index.ts +++ b/src/preload/index.ts @@ -455,9 +455,9 @@ const api = { ocr: (file: SupportedOcrFile, provider: OcrProvider): Promise => ipcRenderer.invoke(IpcChannel.OCR_ocr, file, provider) }, - cherryin: { + cherryai: { generateSignature: (params: { method: string; path: string; query: string; body: Record }) => - ipcRenderer.invoke(IpcChannel.Cherryin_GetSignature, params) + ipcRenderer.invoke(IpcChannel.Cherryai_GetSignature, params) }, windowControls: { minimize: (): Promise => ipcRenderer.invoke(IpcChannel.Windows_Minimize), diff --git a/src/renderer/src/aiCore/legacy/clients/ApiClientFactory.ts b/src/renderer/src/aiCore/legacy/clients/ApiClientFactory.ts index 31a911533e..b38ab59537 100644 --- a/src/renderer/src/aiCore/legacy/clients/ApiClientFactory.ts +++ b/src/renderer/src/aiCore/legacy/clients/ApiClientFactory.ts @@ -5,7 +5,7 @@ import { AihubmixAPIClient } from './aihubmix/AihubmixAPIClient' import { AnthropicAPIClient } from './anthropic/AnthropicAPIClient' import { AwsBedrockAPIClient } from './aws/AwsBedrockAPIClient' import { BaseApiClient } from './BaseApiClient' -import { CherryinAPIClient } from './cherryin/CherryinAPIClient' +import { CherryAiAPIClient } from './cherryai/CherryAiAPIClient' import { GeminiAPIClient } from './gemini/GeminiAPIClient' import { VertexAPIClient } from './gemini/VertexAPIClient' import { NewAPIClient } from './newapi/NewAPIClient' @@ -34,8 +34,8 @@ export class ApiClientFactory { let instance: BaseApiClient // 首先检查特殊的 Provider ID - if (provider.id === 'cherryin') { - instance = new CherryinAPIClient(provider) as BaseApiClient + if (provider.id === 'cherryai') { + instance = new CherryAiAPIClient(provider) as BaseApiClient return instance } diff --git a/src/renderer/src/aiCore/legacy/clients/__tests__/index.clientCompatibilityTypes.test.ts b/src/renderer/src/aiCore/legacy/clients/__tests__/index.clientCompatibilityTypes.test.ts index 343bc4d544..d70d9c58f9 100644 --- a/src/renderer/src/aiCore/legacy/clients/__tests__/index.clientCompatibilityTypes.test.ts +++ b/src/renderer/src/aiCore/legacy/clients/__tests__/index.clientCompatibilityTypes.test.ts @@ -35,10 +35,16 @@ vi.mock('@renderer/config/models', () => ({ findTokenLimit: vi.fn().mockReturnValue(4096), isFunctionCallingModel: vi.fn().mockReturnValue(false), DEFAULT_MAX_TOKENS: 4096, + qwen38bModel: { + id: 'Qwen/Qwen3-8B', + name: 'Qwen3-8B', + provider: 'cherryai', + group: 'Qwen' + }, glm45FlashModel: { id: 'glm-4.5-flash', name: 'GLM-4.5-Flash', - provider: 'cherryin', + provider: 'cherryai', group: 'GLM-4.5' } })) diff --git a/src/renderer/src/aiCore/legacy/clients/cherryin/CherryinAPIClient.ts b/src/renderer/src/aiCore/legacy/clients/cherryai/CherryAiAPIClient.ts similarity index 86% rename from src/renderer/src/aiCore/legacy/clients/cherryin/CherryinAPIClient.ts rename to src/renderer/src/aiCore/legacy/clients/cherryai/CherryAiAPIClient.ts index bf3ed7d718..8f8969bd59 100644 --- a/src/renderer/src/aiCore/legacy/clients/cherryin/CherryinAPIClient.ts +++ b/src/renderer/src/aiCore/legacy/clients/cherryai/CherryAiAPIClient.ts @@ -4,7 +4,7 @@ import OpenAI from 'openai' import { OpenAIAPIClient } from '../openai/OpenAIApiClient' -export class CherryinAPIClient extends OpenAIAPIClient { +export class CherryAiAPIClient extends OpenAIAPIClient { constructor(provider: Provider) { super(provider) } @@ -17,7 +17,7 @@ export class CherryinAPIClient extends OpenAIAPIClient { options = options || {} options.headers = options.headers || {} - const signature = await window.api.cherryin.generateSignature({ + const signature = await window.api.cherryai.generateSignature({ method: 'POST', path: '/chat/completions', query: '', @@ -34,7 +34,7 @@ export class CherryinAPIClient extends OpenAIAPIClient { } override getClientCompatibilityType(): string[] { - return ['CherryinAPIClient'] + return ['CherryAiAPIClient'] } public async listModels(): Promise { @@ -43,7 +43,7 @@ export class CherryinAPIClient extends OpenAIAPIClient { const created = Date.now() return models.map((id) => ({ id, - owned_by: 'cherryin', + owned_by: 'cherryai', object: 'model' as const, created })) diff --git a/src/renderer/src/aiCore/legacy/middleware/common/ErrorHandlerMiddleware.ts b/src/renderer/src/aiCore/legacy/middleware/common/ErrorHandlerMiddleware.ts index d80c9d2f83..dde98cbd1e 100644 --- a/src/renderer/src/aiCore/legacy/middleware/common/ErrorHandlerMiddleware.ts +++ b/src/renderer/src/aiCore/legacy/middleware/common/ErrorHandlerMiddleware.ts @@ -1,6 +1,6 @@ import { loggerService } from '@logger' import { isZhipuModel } from '@renderer/config/models' -import store from '@renderer/store' +import { getStoreProviders } from '@renderer/hooks/useStore' import { Chunk } from '@renderer/types/chunk' import { CompletionsParams, CompletionsResult } from '../schemas' @@ -87,7 +87,7 @@ function handleError(error: any, params: CompletionsParams): any { * 2. 绘画功能(enableGenerateImage为true)使用通用错误处理 */ function handleZhipuError(error: any): any { - const provider = store.getState().llm.providers.find((p) => p.id === 'zhipu') + const provider = getStoreProviders().find((p) => p.id === 'zhipu') const logger = loggerService.withContext('handleZhipuError') // 定义错误模式映射 diff --git a/src/renderer/src/aiCore/provider/providerConfig.ts b/src/renderer/src/aiCore/provider/providerConfig.ts index 33116272e8..eaaef15211 100644 --- a/src/renderer/src/aiCore/provider/providerConfig.ts +++ b/src/renderer/src/aiCore/provider/providerConfig.ts @@ -250,10 +250,10 @@ export async function prepareSpecialProviderConfig( config.options.apiKey = token break } - case 'cherryin': { + case 'cherryai': { config.options.fetch = async (url, options) => { // 在这里对最终参数进行签名 - const signature = await window.api.cherryin.generateSignature({ + const signature = await window.api.cherryai.generateSignature({ method: 'POST', path: '/chat/completions', query: '', diff --git a/src/renderer/src/assets/images/providers/aiOnly.png b/src/renderer/src/assets/images/providers/aiOnly.png deleted file mode 100644 index a521f3bcb87c1f352868fd3774bf3f16640e902c..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 41426 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 SsFg2)e`-qFily>*L;gSV8mck? diff --git a/src/renderer/src/assets/images/providers/aiOnly.webp b/src/renderer/src/assets/images/providers/aiOnly.webp new file mode 100644 index 0000000000000000000000000000000000000000..f86f74543b3255e5c27eee989286a4545e03eb3a GIT binary patch literal 10588 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 literal 0 HcmV?d00001 diff --git a/src/renderer/src/components/FreeTrialModelTag.tsx b/src/renderer/src/components/FreeTrialModelTag.tsx index 0ce63ade37..b86de99708 100644 --- a/src/renderer/src/components/FreeTrialModelTag.tsx +++ b/src/renderer/src/components/FreeTrialModelTag.tsx @@ -15,7 +15,7 @@ interface Props { } export const FreeTrialModelTag: FC = ({ model, showLabel = true }) => { - if (model.provider !== 'cherryin') { + if (model.provider !== 'cherryai') { return null } diff --git a/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx b/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx index 1cd7926145..45560bcd6c 100644 --- a/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx +++ b/src/renderer/src/components/Popups/SelectModelPopup/popup.tsx @@ -1,5 +1,6 @@ import { PushpinOutlined } from '@ant-design/icons' import { FreeTrialModelTag } from '@renderer/components/FreeTrialModelTag' +import { HStack } from '@renderer/components/Layout' import ModelTagsWithLabel from '@renderer/components/ModelTagsWithLabel' import { TopView } from '@renderer/components/TopView' import { DynamicVirtualList, type DynamicVirtualListRef } from '@renderer/components/VirtualList' @@ -102,16 +103,18 @@ const PopupContainer: React.FC = ({ model, filter: baseFilter, showTagFil (model: Model, provider: Provider, isPinned: boolean): FlatListModel => { const modelId = getModelUniqId(model) const groupName = getFancyProviderName(provider) - const isCherryin = provider.id === 'cherryin' + const isCherryAi = provider.id === 'cherryai' return { key: isPinned ? `${modelId}_pinned` : modelId, type: 'model', name: ( - {model.name} - {isPinned && | {groupName}} - {isCherryin && } + + {model.name} + {isPinned && | {groupName}} + + {isCherryAi && } ), tags: ( @@ -542,6 +545,7 @@ const ModelItemLeft = styled.div` const ModelName = styled.div` display: flex; flex-direction: row; + justify-content: space-between; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; diff --git a/src/renderer/src/config/__test__/models.test.ts b/src/renderer/src/config/__test__/models.test.ts index 547ff42efd..926ac3547d 100644 --- a/src/renderer/src/config/__test__/models.test.ts +++ b/src/renderer/src/config/__test__/models.test.ts @@ -16,7 +16,7 @@ describe('Qwen Model Detection', () => { initialState: {} })) vi.mock('@renderer/services/AssistantService', () => ({ - getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryin' }) + getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryai' }) })) }) test('isQwenReasoningModel', () => { @@ -52,7 +52,7 @@ describe('Vision Model Detection', () => { initialState: {} })) vi.mock('@renderer/services/AssistantService', () => ({ - getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryin' }) + getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryai' }) })) }) test('isVisionModel', () => { @@ -81,7 +81,7 @@ describe('Web Search Model Detection', () => { initialState: {} })) vi.mock('@renderer/services/AssistantService', () => ({ - getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryin' }) + getProviderByModel: vi.fn().mockReturnValue({ id: 'cherryai' }) })) }) test('isWebSearchModel', () => { diff --git a/src/renderer/src/config/models/default.ts b/src/renderer/src/config/models/default.ts index 2f13a5e993..02bf37af9e 100644 --- a/src/renderer/src/config/models/default.ts +++ b/src/renderer/src/config/models/default.ts @@ -3,14 +3,14 @@ import { Model, SystemProviderId } from '@renderer/types' export const glm45FlashModel: Model = { id: 'glm-4.5-flash', name: 'GLM-4.5-Flash', - provider: 'cherryin', + provider: 'cherryai', group: 'GLM-4.5' } export const qwen38bModel: Model = { id: 'Qwen/Qwen3-8B', name: 'Qwen3-8B', - provider: 'cherryin', + provider: 'cherryai', group: 'Qwen' } @@ -25,20 +25,7 @@ export const SYSTEM_MODELS: Record = // Default quick assistant model glm45FlashModel ], - cherryin: [ - { - id: 'glm-4.5-flash', - name: 'GLM-4.5-Flash', - provider: 'cherryin', - group: 'GLM-4.5' - }, - { - id: 'Qwen/Qwen3-8B', - name: 'Qwen3-8B', - provider: 'cherryin', - group: 'Qwen' - } - ], + cherryin: [], vertexai: [], '302ai': [ { diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index e07414e2c2..80ea9bdf7a 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -3,7 +3,7 @@ import HunyuanProviderLogo from '@renderer/assets/images/models/hunyuan.png' import AzureProviderLogo from '@renderer/assets/images/models/microsoft.png' import Ai302ProviderLogo from '@renderer/assets/images/providers/302ai.webp' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' -import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.png' +import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.webp' import AlayaNewProviderLogo from '@renderer/assets/images/providers/alayanew.webp' import AnthropicProviderLogo from '@renderer/assets/images/providers/anthropic.png' import AwsProviderLogo from '@renderer/assets/images/providers/aws-bedrock.webp' @@ -64,7 +64,18 @@ import { } from '@renderer/types' import { TOKENFLUX_HOST } from './constant' -import { SYSTEM_MODELS } from './models' +import { glm45FlashModel, qwen38bModel, SYSTEM_MODELS } from './models' + +export const CHERRYAI_PROVIDER: SystemProvider = { + id: 'cherryai' as SystemProviderId, + name: 'CherryAI', + type: 'openai', + apiKey: '', + apiHost: 'https://api.cherry-ai.com/', + models: [glm45FlashModel, qwen38bModel], + isSystem: true, + enabled: true +} export const SYSTEM_PROVIDERS_CONFIG: Record = { cherryin: { @@ -72,8 +83,8 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = name: 'CherryIN', type: 'openai', apiKey: '', - apiHost: 'https://api.cherry-ai.com/', - models: SYSTEM_MODELS.cherryin, + apiHost: 'https://open.cherryin.ai', + models: [], isSystem: true, enabled: true }, @@ -699,12 +710,13 @@ type ProviderUrls = { export const PROVIDER_URLS: Record = { cherryin: { api: { - url: 'https://api.cherry-ai.com' + url: 'https://open.cherryin.ai' }, websites: { - official: 'https://cherry-ai.com', - docs: 'https://docs.cherry-ai.com', - models: 'https://docs.cherry-ai.com/pre-basic/providers/cherryin' + official: 'https://open.cherryin.ai', + apiKey: 'https://open.cherryin.ai/console/token', + docs: 'https://open.cherryin.ai', + models: 'https://open.cherryin.ai/pricing' } }, ph8: { diff --git a/src/renderer/src/hooks/useModel.ts b/src/renderer/src/hooks/useModel.ts index 27962e7cef..75119d0f89 100644 --- a/src/renderer/src/hooks/useModel.ts +++ b/src/renderer/src/hooks/useModel.ts @@ -1,6 +1,5 @@ -import store from '@renderer/store' - import { useProviders } from './useProvider' +import { getStoreProviders } from './useStore' export function useModel(id?: string, providerId?: string) { const { providers } = useProviders() @@ -15,7 +14,7 @@ export function useModel(id?: string, providerId?: string) { } export function getModel(id?: string, providerId?: string) { - const providers = store.getState().llm.providers + const providers = getStoreProviders() const allModels = providers.map((p) => p.models).flat() return allModels.find((m) => { if (providerId) { diff --git a/src/renderer/src/hooks/useProvider.ts b/src/renderer/src/hooks/useProvider.ts index 9182db3776..96120d4dc8 100644 --- a/src/renderer/src/hooks/useProvider.ts +++ b/src/renderer/src/hooks/useProvider.ts @@ -1,4 +1,5 @@ import { createSelector } from '@reduxjs/toolkit' +import { CHERRYAI_PROVIDER } from '@renderer/config/providers' import { getDefaultProvider } from '@renderer/services/AssistantService' import { useAppDispatch, useAppSelector } from '@renderer/store' import { @@ -16,7 +17,7 @@ import { useDefaultModel } from './useAssistant' const selectEnabledProviders = createSelector( (state) => state.llm.providers, - (providers) => providers.filter((p) => p.enabled) + (providers) => providers.filter((p) => p.enabled).concat(CHERRYAI_PROVIDER) ) export function useProviders() { @@ -24,7 +25,7 @@ export function useProviders() { const dispatch = useAppDispatch() return { - providers: providers || {}, + providers: providers || [], addProvider: (provider: Provider) => dispatch(addProvider(provider)), removeProvider: (provider: Provider) => dispatch(removeProvider(provider)), updateProvider: (updates: Partial & { id: string }) => dispatch(updateProvider(updates)), @@ -45,7 +46,9 @@ export function useAllProviders() { } export function useProvider(id: string) { - const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === id)) || getDefaultProvider() + const provider = + useAppSelector((state) => state.llm.providers.concat([CHERRYAI_PROVIDER]).find((p) => p.id === id)) || + getDefaultProvider() const dispatch = useAppDispatch() return { diff --git a/src/renderer/src/hooks/useStore.ts b/src/renderer/src/hooks/useStore.ts index 1b731e74c7..53e4646450 100644 --- a/src/renderer/src/hooks/useStore.ts +++ b/src/renderer/src/hooks/useStore.ts @@ -1,4 +1,5 @@ -import { useAppDispatch, useAppSelector } from '@renderer/store' +import { CHERRYAI_PROVIDER } from '@renderer/config/providers' +import store, { useAppDispatch, useAppSelector } from '@renderer/store' import { setAssistantsTabSortType, setShowAssistants, @@ -39,3 +40,7 @@ export function useAssistantsTabSortType() { setAssistantsTabSortType: (sortType: AssistantsSortType) => dispatch(setAssistantsTabSortType(sortType)) } } + +export function getStoreProviders() { + return store.getState().llm.providers.concat([CHERRYAI_PROVIDER]) +} diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index defbfe9dd6..dd3fa37be1 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -2018,7 +2018,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", - "aionly": "唯一AI(AiOnly)", + "aionly": "唯一AI (AiOnly)", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/i18n/locales/zh-tw.json b/src/renderer/src/i18n/locales/zh-tw.json index b5ac09bb7f..4e162abda3 100644 --- a/src/renderer/src/i18n/locales/zh-tw.json +++ b/src/renderer/src/i18n/locales/zh-tw.json @@ -2018,7 +2018,7 @@ "provider": { "302ai": "302.AI", "aihubmix": "AiHubMix", - "aionly": "唯一AI(AiOnly)", + "aionly": "唯一AI (AiOnly)", "alayanew": "Alaya NeW", "anthropic": "Anthropic", "aws-bedrock": "AWS Bedrock", diff --git a/src/renderer/src/pages/code/CodeToolsPage.tsx b/src/renderer/src/pages/code/CodeToolsPage.tsx index 14f540f5db..69d9fb728d 100644 --- a/src/renderer/src/pages/code/CodeToolsPage.tsx +++ b/src/renderer/src/pages/code/CodeToolsPage.tsx @@ -70,7 +70,7 @@ const CodeToolsPage: FC = () => { if (isEmbeddingModel(m) || isRerankModel(m) || isTextToImageModel(m)) { return false } - if (m.provider === 'cherryin') { + if (m.provider === 'cherryai') { return false } if (selectedCliTool === codeTools.claudeCode) { diff --git a/src/renderer/src/pages/home/components/SelectModelButton.tsx b/src/renderer/src/pages/home/components/SelectModelButton.tsx index bd6af86f7b..c9fa63d286 100644 --- a/src/renderer/src/pages/home/components/SelectModelButton.tsx +++ b/src/renderer/src/pages/home/components/SelectModelButton.tsx @@ -3,8 +3,8 @@ import SelectModelPopup from '@renderer/components/Popups/SelectModelPopup' import { isLocalAi } from '@renderer/config/env' import { isEmbeddingModel, isRerankModel, isWebSearchModel } from '@renderer/config/models' import { useAssistant } from '@renderer/hooks/useAssistant' +import { useProvider } from '@renderer/hooks/useProvider' import { getProviderName } from '@renderer/services/ProviderService' -import { useAppSelector } from '@renderer/store' import { Assistant, Model } from '@renderer/types' import { Button, Tag } from 'antd' import { ChevronsUpDown } from 'lucide-react' @@ -20,7 +20,7 @@ const SelectModelButton: FC = ({ assistant }) => { const { model, updateAssistant } = useAssistant(assistant.id) const { t } = useTranslation() const timerRef = useRef(undefined) - const provider = useAppSelector((state) => state.llm.providers.find((p) => p.id === model?.provider)) + const provider = useProvider(model?.provider) const modelFilter = (model: Model) => !isEmbeddingModel(model) && !isRerankModel(model) diff --git a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx index d0e1304a2b..58468f09bb 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ModelList/ModelList.tsx @@ -50,7 +50,6 @@ const ModelList: React.FC = ({ providerId }) => { const providerConfig = PROVIDER_URLS[provider.id] const docsWebsite = providerConfig?.websites?.docs const modelsWebsite = providerConfig?.websites?.models - const editable = provider.id !== 'cherryin' const [searchText, _setSearchText] = useState('') const [displayedModelGroups, setDisplayedModelGroups] = useState(() => { @@ -113,17 +112,15 @@ const ModelList: React.FC = ({ providerId }) => { tooltip={t('models.search.tooltip')} /> - {editable && ( - - - - - - )} + + + + ) } diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx index 5fdafa5f23..2d8b38be81 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderOAuth.tsx @@ -1,6 +1,6 @@ import AI302ProviderLogo from '@renderer/assets/images/providers/302ai.webp' import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp' -import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.png' +import AiOnlyProviderLogo from '@renderer/assets/images/providers/aiOnly.webp' import PPIOProviderLogo from '@renderer/assets/images/providers/ppio.png' import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.png' import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png' diff --git a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx index 68bfc67ba2..ea40f6d9ac 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ProviderSetting.tsx @@ -68,7 +68,7 @@ const ProviderSetting: FC = ({ providerId }) => { const isAzureOpenAI = provider.id === 'azure-openai' || provider.type === 'azure-openai' const isDmxapi = provider.id === 'dmxapi' - const hideApiInput = ['vertexai', 'aws-bedrock', 'cherryin'].includes(provider.id) + const hideApiInput = ['vertexai', 'aws-bedrock'].includes(provider.id) const providerConfig = PROVIDER_URLS[provider.id] const officialWebsite = providerConfig?.websites?.official diff --git a/src/renderer/src/services/ApiService.ts b/src/renderer/src/services/ApiService.ts index d954fc1f85..64e2c1ae31 100644 --- a/src/renderer/src/services/ApiService.ts +++ b/src/renderer/src/services/ApiService.ts @@ -337,7 +337,7 @@ export async function fetchGenerate({ export function hasApiKey(provider: Provider) { if (!provider) return false - if (['ollama', 'lmstudio', 'vertexai', 'cherryin'].includes(provider.id)) return true + if (['ollama', 'lmstudio', 'vertexai', 'cherryai'].includes(provider.id)) return true return !isEmpty(provider.apiKey) } diff --git a/src/renderer/src/services/AssistantService.ts b/src/renderer/src/services/AssistantService.ts index 8a86a95937..ea2dd3ef36 100644 --- a/src/renderer/src/services/AssistantService.ts +++ b/src/renderer/src/services/AssistantService.ts @@ -7,7 +7,9 @@ import { UNLIMITED_CONTEXT_COUNT } from '@renderer/config/constant' import { isQwenMTModel } from '@renderer/config/models' +import { CHERRYAI_PROVIDER } from '@renderer/config/providers' import { UNKNOWN } from '@renderer/config/translate' +import { getStoreProviders } from '@renderer/hooks/useStore' import i18n from '@renderer/i18n' import store from '@renderer/store' import { addAssistant } from '@renderer/store/assistants' @@ -126,26 +128,25 @@ export function getTranslateModel() { } export function getAssistantProvider(assistant: Assistant): Provider { - const providers = store.getState().llm.providers + const providers = getStoreProviders() const provider = providers.find((p) => p.id === assistant.model?.provider) return provider || getDefaultProvider() } export function getProviderByModel(model?: Model): Provider { - const providers = store.getState().llm.providers + const providers = getStoreProviders() const provider = providers.find((p) => p.id === model?.provider) if (!provider) { const defaultProvider = providers.find((p) => p.id === getDefaultModel()?.provider) - const cherryinProvider = providers.find((p) => p.id === 'cherryin') - return defaultProvider || cherryinProvider || providers[0] + return defaultProvider || CHERRYAI_PROVIDER || providers[0] } return provider } export function getProviderByModelId(modelId?: string) { - const providers = store.getState().llm.providers + const providers = getStoreProviders() const _modelId = modelId || getDefaultModel().id return providers.find((p) => p.models.find((m) => m.id === _modelId)) as Provider } diff --git a/src/renderer/src/services/ModelService.ts b/src/renderer/src/services/ModelService.ts index 52049f2293..9cb1c5cc4c 100644 --- a/src/renderer/src/services/ModelService.ts +++ b/src/renderer/src/services/ModelService.ts @@ -1,4 +1,4 @@ -import store from '@renderer/store' +import { getStoreProviders } from '@renderer/hooks/useStore' import { Model } from '@renderer/types' import { pick } from 'lodash' @@ -9,9 +9,8 @@ export const getModelUniqId = (m?: Model) => { } export const hasModel = (m?: Model) => { - const allModels = store - .getState() - .llm.providers.filter((p) => p.enabled) + const allModels = getStoreProviders() + .filter((p) => p.enabled) .map((p) => p.models) .flat() @@ -19,7 +18,7 @@ export const hasModel = (m?: Model) => { } export function getModelName(model?: Model) { - const provider = store.getState().llm.providers.find((p) => p.id === model?.provider) + const provider = getStoreProviders().find((p) => p.id === model?.provider) const modelName = model?.name || model?.id || '' if (provider) { diff --git a/src/renderer/src/services/ProviderService.ts b/src/renderer/src/services/ProviderService.ts index da6df82adc..e0c6b38858 100644 --- a/src/renderer/src/services/ProviderService.ts +++ b/src/renderer/src/services/ProviderService.ts @@ -1,4 +1,4 @@ -import store from '@renderer/store' +import { getStoreProviders } from '@renderer/hooks/useStore' import { Model, Provider } from '@renderer/types' import { getFancyProviderName } from '@renderer/utils' @@ -14,9 +14,9 @@ export function getProviderName(model?: Model) { export function getProviderByModel(model?: Model) { const id = model?.provider - const provider = store.getState().llm.providers.find((p) => p.id === id) + const provider = getStoreProviders().find((p) => p.id === id) - if (provider?.id === 'cherryin') { + if (provider?.id === 'cherryai') { const map = { 'glm-4.5-flash': 'zhipu', 'Qwen/Qwen3-8B': 'silicon' @@ -43,5 +43,5 @@ export function isProviderSupportCharge(provider: Provider) { } export function getProviderById(id: string) { - return store.getState().llm.providers.find((p) => p.id === id) + return getStoreProviders().find((p) => p.id === id) } diff --git a/src/renderer/src/store/index.ts b/src/renderer/src/store/index.ts index 3b3cfa3f0c..ba532ecc65 100644 --- a/src/renderer/src/store/index.ts +++ b/src/renderer/src/store/index.ts @@ -67,7 +67,7 @@ const persistedReducer = persistReducer( { key: 'cherry-studio', storage, - version: 156, + version: 157, blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs'], migrate }, diff --git a/src/renderer/src/store/migrate.ts b/src/renderer/src/store/migrate.ts index f7fc92ee44..8063f7ec92 100644 --- a/src/renderer/src/store/migrate.ts +++ b/src/renderer/src/store/migrate.ts @@ -2479,7 +2479,6 @@ const migrateConfig = { }, '156': (state: RootState) => { try { - addProvider(state, 'aionly') state.llm.providers.forEach((provider) => { if (provider.id === SystemProviderIds.anthropic) { if (provider.apiHost.endsWith('/')) { @@ -2492,6 +2491,53 @@ const migrateConfig = { logger.error('migrate 156 error', error as Error) return state } + }, + '157': (state: RootState) => { + try { + addProvider(state, 'aionly') + + const cherryinProvider = state.llm.providers.find((provider) => provider.id === 'cherryin') + + if (cherryinProvider) { + updateProvider(state, 'cherryin', { apiHost: 'https://open.cherryin.ai', models: [] }) + } + + if (state.llm.defaultModel?.provider === 'cherryin') { + state.llm.defaultModel.provider = 'cherryai' + } + + if (state.llm.quickModel?.provider === 'cherryin') { + state.llm.quickModel.provider = 'cherryai' + } + + if (state.llm.translateModel?.provider === 'cherryin') { + state.llm.translateModel.provider = 'cherryai' + } + + state.assistants.assistants.forEach((assistant) => { + if (assistant.model?.provider === 'cherryin') { + assistant.model.provider = 'cherryai' + } + if (assistant.defaultModel?.provider === 'cherryin') { + assistant.defaultModel.provider = 'cherryai' + } + }) + + state.agents.agents.forEach((agent) => { + // @ts-ignore model is not defined in Agent + if (agent.model?.provider === 'cherryin') { + // @ts-ignore model is not defined in Agent + agent.model.provider = 'cherryai' + } + if (agent.defaultModel?.provider === 'cherryin') { + agent.defaultModel.provider = 'cherryai' + } + }) + return state + } catch (error) { + logger.error('migrate 157 error', error as Error) + return state + } } } diff --git a/src/renderer/src/utils/model.ts b/src/renderer/src/utils/model.ts index 9795b72d26..e918c84572 100644 --- a/src/renderer/src/utils/model.ts +++ b/src/renderer/src/utils/model.ts @@ -64,7 +64,7 @@ export const getModelTags = (models: Model[]): Record => { } export function isFreeModel(model: Model) { - if (model.provider === 'cherryin') { + if (model.provider === 'cherryai') { return true } diff --git a/tsconfig.web.json b/tsconfig.web.json index 07c1b41066..1204192253 100644 --- a/tsconfig.web.json +++ b/tsconfig.web.json @@ -8,7 +8,7 @@ "tests/__mocks__/**/*", "packages/mcp-trace/**/*", "packages/aiCore/src/**/*", - "src/main/integration/cherryin/index.js", + "src/main/integration/cherryai/index.js", "packages/extension-table-plus/**/*" ], "compilerOptions": { From 4a4a1686d3d15aaa89db824ea658c38b105767bc Mon Sep 17 00:00:00 2001 From: kangfenmao Date: Tue, 23 Sep 2025 20:46:46 +0800 Subject: [PATCH 10/10] chore: bump version to 1.6.0-rc.4 - Update version in package.json - Update release notes in electron-builder.yml --- electron-builder.yml | 14 +++++--------- package.json | 2 +- 2 files changed, 6 insertions(+), 10 deletions(-) diff --git a/electron-builder.yml b/electron-builder.yml index 8fd4b6a6f6..0fe2f2d3b9 100644 --- a/electron-builder.yml +++ b/electron-builder.yml @@ -126,17 +126,13 @@ artifactBuildCompleted: scripts/artifact-build-completed.js releaseInfo: releaseNotes: | ✨ 新功能: - - 支持在对话中显示 AI 生成的图片 - - 代码编辑工具支持更多终端类型 - - 新增 Azure AI 服务支持 - - 新增通义千问 Plus 模型 + - 新增 CherryIN 服务商 + - 新增 AiOnly AI 服务商 + - 更新 MCP 服务器卡片布局和样式,改为列表视图 🐛 问题修复: - - 修复翻译功能中选中文本未正确使用的问题 - - 修复文件管理中空格键误删文件的问题 - - 修复部分 AI 服务连接不稳定的问题 - - 修复翻译页面长文本显示异常 - - 优化列表显示样式 + - 修复 QwenMT 模型的翻译内容处理逻辑 + - 修复无法将外部笔记添加到知识库的问题 🚀 性能优化: - 提升输入框响应速度 diff --git a/package.json b/package.json index 84426d2e71..7f2e00d4df 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "CherryStudio", - "version": "1.6.0-rc.3", + "version": "1.6.0-rc.4", "private": true, "description": "A powerful AI assistant for producer.", "main": "./out/main/index.js",