From 1b231aed1eef80e15c7289c6998b47a23699bbe9 Mon Sep 17 00:00:00 2001 From: icarus Date: Wed, 6 Aug 2025 17:45:53 +0800 Subject: [PATCH] =?UTF-8?q?feat(provider):=20=E6=94=AF=E6=8C=81=E5=9C=A8?= =?UTF-8?q?=E6=8F=90=E4=BE=9B=E5=95=86=E8=AE=BE=E7=BD=AE=E4=B8=AD=E9=85=8D?= =?UTF-8?q?=E7=BD=AE=20service=5Ftier=20=E5=8F=82=E6=95=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 service_tier 配置从全局设置迁移到提供商设置中,并添加相关 UI 和逻辑支持 --- .../src/aiCore/clients/BaseApiClient.ts | 26 ++++--- src/renderer/src/config/providers.ts | 46 +++++++++---- src/renderer/src/i18n/locales/zh-cn.json | 5 ++ .../src/pages/home/Tabs/SettingsTab.tsx | 15 +--- .../Tabs/components/OpenAISettingsGroup.tsx | 68 +++++++++++-------- .../ProviderSettings/ApiOptionsSettings.tsx | 9 +++ src/renderer/src/store/settings.ts | 5 +- 7 files changed, 109 insertions(+), 65 deletions(-) diff --git a/src/renderer/src/aiCore/clients/BaseApiClient.ts b/src/renderer/src/aiCore/clients/BaseApiClient.ts index a88cb2a628..e7dea5c46a 100644 --- a/src/renderer/src/aiCore/clients/BaseApiClient.ts +++ b/src/renderer/src/aiCore/clients/BaseApiClient.ts @@ -6,10 +6,9 @@ import { isSupportFlexServiceTierModel } from '@renderer/config/models' import { REFERENCE_PROMPT } from '@renderer/config/prompts' +import { isSupportServiceTierProviders } from '@renderer/config/providers' import { getLMStudioKeepAliveTime } from '@renderer/hooks/useLMStudio' -import { getStoreSetting } from '@renderer/hooks/useSettings' import { getAssistantSettings } from '@renderer/services/AssistantService' -import { SettingsState } from '@renderer/store/settings' import { Assistant, FileTypes, @@ -21,6 +20,7 @@ import { MemoryItem, Model, OpenAIServiceTier, + OpenAIServiceTiers, Provider, ToolCallResponse, WebSearchProviderResponse, @@ -201,22 +201,30 @@ export abstract class BaseApiClient< return assistantSettings?.enableTopP ? assistantSettings?.topP : undefined } + // NOTE: 这个也许可以迁移到OpenAIBaseClient protected getServiceTier(model: Model) { - if (!isOpenAIModel(model) || model.provider === 'github' || model.provider === 'copilot') { + if ( + !isSupportServiceTierProviders(this.provider) || + !isOpenAIModel(model) || + model.provider === 'github' || + model.provider === 'copilot' + ) { return undefined } - const openAI = getStoreSetting('openAI') as SettingsState['openAI'] - let serviceTier = 'auto' as OpenAIServiceTier + let serviceTier: OpenAIServiceTier = OpenAIServiceTiers.AUTO + const serviceTierSetting = this.provider.serviceTier - if (openAI && openAI?.serviceTier === 'flex') { + if (serviceTierSetting === OpenAIServiceTiers.FLEX) { if (isSupportFlexServiceTierModel(model)) { - serviceTier = 'flex' + serviceTier = OpenAIServiceTiers.FLEX } else { - serviceTier = 'auto' + serviceTier = OpenAIServiceTiers.AUTO } + } else if (serviceTierSetting) { + serviceTier = serviceTierSetting } else { - serviceTier = openAI.serviceTier + // undefined 时使用默认值 auto } return serviceTier diff --git a/src/renderer/src/config/providers.ts b/src/renderer/src/config/providers.ts index bd04073bf4..522141aad2 100644 --- a/src/renderer/src/config/providers.ts +++ b/src/renderer/src/config/providers.ts @@ -258,7 +258,6 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = models: SYSTEM_MODELS.openai, isSystem: true, enabled: false, - isSupportServiceTier: true, serviceTier: OpenAIServiceTiers.AUTO }, 'azure-openai': { @@ -415,9 +414,7 @@ export const SYSTEM_PROVIDERS_CONFIG: Record = apiHost: 'https://api.groq.com/openai', models: SYSTEM_MODELS.groq, isSystem: true, - enabled: false, - isSupportServiceTier: true, - serviceTier: OpenAIServiceTiers.DEFAULT + enabled: false }, together: { id: 'together', @@ -1259,38 +1256,63 @@ export const PROVIDER_CONFIG = { } } -const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = ['deepseek', 'baichuan', 'minimax', 'xirang'] +const NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS = [ + 'deepseek', + 'baichuan', + 'minimax', + 'xirang' +] as const satisfies SystemProviderId[] /** * 判断提供商是否支持 message 的 content 为数组类型。 Only for OpenAI Chat Completions API. */ export const isSupportArrayContentProvider = (provider: Provider) => { - return provider.isNotSupportArrayContent !== true || !NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS.includes(provider.id) + return ( + provider.isNotSupportArrayContent !== true || + !NOT_SUPPORT_ARRAY_CONTENT_PROVIDERS.some((pid) => pid === provider.id) + ) } -const NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS = ['poe'] +const NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS = ['poe'] as const satisfies SystemProviderId[] /** * 判断提供商是否支持 developer 作为 message role。 Only for OpenAI API. */ export const isSupportDeveloperRoleProvider = (provider: Provider) => { - return provider.isNotSupportDeveloperRole !== true || !NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS.includes(provider.id) + return ( + provider.isNotSupportDeveloperRole !== true || + !NOT_SUPPORT_DEVELOPER_ROLE_PROVIDERS.some((pid) => pid === provider.id) + ) } -const NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS = ['mistral'] +const NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS = ['mistral'] as const satisfies SystemProviderId[] /** * 判断提供商是否支持 stream_options 参数。Only for OpenAI API. */ export const isSupportStreamOptionsProvider = (provider: Provider) => { - return provider.isNotSupportStreamOptions !== true || !NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS.includes(provider.id) + return ( + provider.isNotSupportStreamOptions !== true || + !NOT_SUPPORT_STREAM_OPTIONS_PROVIDERS.some((pid) => pid === provider.id) + ) } -const SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER = ['dashscope', 'modelscope'] +const SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER = ['dashscope', 'modelscope'] as const satisfies SystemProviderId[] /** * 判断提供商是否支持使用enable_thinking参数来控制Qwen3系列模型的思考。 Only for OpenAI Chat Completions API. */ export const isSupportQwen3EnableThinkingProvider = (provider: Provider) => { - return SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER.includes(provider.id) + return SUPPORT_QWEN3_ENABLE_THINKING_PROVIDER.some((pid) => pid === provider.id) +} + +const NOT_SUPPORT_SERVICE_TIER_PROVIDERS = ['github', 'copilot'] as const satisfies SystemProviderId[] + +/** + * 判断提供商是否支持 service_tier 设置。 Only for OpenAI API. + */ +export const isSupportServiceTierProviders = (provider: Provider) => { + return ( + provider.isNotSupportServiceTier !== true || !NOT_SUPPORT_SERVICE_TIER_PROVIDERS.some((pid) => pid === provider.id) + ) } diff --git a/src/renderer/src/i18n/locales/zh-cn.json b/src/renderer/src/i18n/locales/zh-cn.json index 78bd4143ca..b9e59b2fb9 100644 --- a/src/renderer/src/i18n/locales/zh-cn.json +++ b/src/renderer/src/i18n/locales/zh-cn.json @@ -3054,6 +3054,7 @@ "auto": "自动", "default": "默认", "flex": "灵活", + "priority": "优先", "tip": "指定用于处理请求的延迟层级", "title": "服务层级" }, @@ -3107,6 +3108,10 @@ "label": "支持 Developer Message" }, "label": "API 设置", + "service_tier": { + "help": "该提供商是否支持配置 service_tier 参数。开启后,可在对话页面的服务层级设置中调整该参数。(仅限OpenAI模型)", + "label": "支持 service_tier" + }, "stream_options": { "help": "该提供商是否支持 stream_options 参数", "label": "支持 stream_options" diff --git a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx index a01763ede3..f4e31d9cd0 100644 --- a/src/renderer/src/pages/home/Tabs/SettingsTab.tsx +++ b/src/renderer/src/pages/home/Tabs/SettingsTab.tsx @@ -3,11 +3,7 @@ import { HStack } from '@renderer/components/Layout' import Scrollbar from '@renderer/components/Scrollbar' import Selector from '@renderer/components/Selector' import { DEFAULT_CONTEXTCOUNT, DEFAULT_MAX_TOKENS, DEFAULT_TEMPERATURE } from '@renderer/config/constant' -import { - isOpenAIModel, - isSupportedReasoningEffortOpenAIModel, - isSupportFlexServiceTierModel -} from '@renderer/config/models' +import { isOpenAIModel } from '@renderer/config/models' import { translateLanguageOptions } from '@renderer/config/translate' import { useCodeStyle } from '@renderer/context/CodeStyleProvider' import { useTheme } from '@renderer/context/ThemeProvider' @@ -168,11 +164,6 @@ const SettingsTab: FC = (props) => { const model = assistant.model || getDefaultModel() const isOpenAI = isOpenAIModel(model) - const isOpenAIReasoning = - isSupportedReasoningEffortOpenAIModel(model) && - !model.id.includes('o1-pro') && - (provider.type === 'openai-response' || provider.id === 'aihubmix') - const isOpenAIFlexServiceTier = isSupportFlexServiceTierModel(model) return ( @@ -300,8 +291,8 @@ const SettingsTab: FC = (props) => { {isOpenAI && ( diff --git a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx index 6e2f407b56..624d8baea8 100644 --- a/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx +++ b/src/renderer/src/pages/home/Tabs/components/OpenAISettingsGroup.tsx @@ -1,9 +1,11 @@ import Selector from '@renderer/components/Selector' +import { isSupportedReasoningEffortOpenAIModel, isSupportFlexServiceTierModel } from '@renderer/config/models' +import { useProvider } from '@renderer/hooks/useProvider' import { SettingDivider, SettingRow } from '@renderer/pages/settings' import { CollapsibleSettingGroup } from '@renderer/pages/settings/SettingGroup' import { RootState, useAppDispatch } from '@renderer/store' -import { setOpenAIServiceTier, setOpenAISummaryText } from '@renderer/store/settings' -import { OpenAIServiceTier, OpenAISummaryText } from '@renderer/types' +import { setOpenAISummaryText } from '@renderer/store/settings' +import { Model, OpenAIServiceTier, OpenAISummaryText } from '@renderer/types' import { Tooltip } from 'antd' import { CircleHelp } from 'lucide-react' import { FC, useCallback, useEffect, useMemo } from 'react' @@ -11,8 +13,8 @@ import { useTranslation } from 'react-i18next' import { useSelector } from 'react-redux' interface Props { - isOpenAIReasoning: boolean - isSupportedFlexServiceTier: boolean + model: Model + providerId: string SettingGroup: FC<{ children: React.ReactNode }> SettingRowTitleSmall: FC<{ children: React.ReactNode }> } @@ -24,17 +26,20 @@ const FALL_BACK_SERVICE_TIER: Record = { priority: 'priority' } -const OpenAISettingsGroup: FC = ({ - isOpenAIReasoning, - isSupportedFlexServiceTier, - SettingGroup, - SettingRowTitleSmall -}) => { +const OpenAISettingsGroup: FC = ({ model, providerId, SettingGroup, SettingRowTitleSmall }) => { const { t } = useTranslation() + const { provider, updateProvider } = useProvider(providerId) const summaryText = useSelector((state: RootState) => state.settings.openAI.summaryText) - const serviceTierMode = useSelector((state: RootState) => state.settings.openAI.serviceTier) + const serviceTierMode = provider.serviceTier const dispatch = useAppDispatch() + const isOpenAIReasoning = + isSupportedReasoningEffortOpenAIModel(model) && + !model.id.includes('o1-pro') && + (provider.type === 'openai-response' || provider.id === 'aihubmix') + const isSupportServiceTier = !provider.isNotSupportServiceTier + const isSupportedFlexServiceTier = isSupportFlexServiceTierModel(model) + const setSummaryText = useCallback( (value: OpenAISummaryText) => { dispatch(setOpenAISummaryText(value)) @@ -44,9 +49,9 @@ const OpenAISettingsGroup: FC = ({ const setServiceTierMode = useCallback( (value: OpenAIServiceTier) => { - dispatch(setOpenAIServiceTier(value)) + updateProvider({ serviceTier: value }) }, - [dispatch] + [updateProvider] ) const summaryTextOptions = [ @@ -97,24 +102,31 @@ const OpenAISettingsGroup: FC = ({ } }, [serviceTierMode, serviceTierOptions, setServiceTierMode]) + if (!isOpenAIReasoning && !isSupportServiceTier) { + return null + } + return ( - - - {t('settings.openai.service_tier.title')}{' '} - - - - - { - setServiceTierMode(value as OpenAIServiceTier) - }} - options={serviceTierOptions} - /> - + {isSupportServiceTier && ( + + + {t('settings.openai.service_tier.title')}{' '} + + + + + { + setServiceTierMode(value as OpenAIServiceTier) + }} + options={serviceTierOptions} + placeholder={t('settings.openai.service_tier.auto')} + /> + + )} {isOpenAIReasoning && ( <> diff --git a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings.tsx b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings.tsx index fa438bab24..4993bd85e9 100644 --- a/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings.tsx +++ b/src/renderer/src/pages/settings/ProviderSettings/ApiOptionsSettings.tsx @@ -59,6 +59,15 @@ const ApiOptionsSettings = ({ providerId }: Props) => { updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked }) }, checked: !provider.isNotSupportArrayContent + }, + { + key: 'openai_service_tier', + label: t('settings.provider.api.options.service_tier.label'), + tip: t('settings.provider.api.options.service_tier.help'), + onChange: (checked: boolean) => { + updateProviderTransition({ ...provider, isNotSupportArrayContent: !checked }) + }, + checked: !provider.isNotSupportServiceTier } ], [t, provider, updateProviderTransition] diff --git a/src/renderer/src/store/settings.ts b/src/renderer/src/store/settings.ts index be1d950b2a..8ada4a1ad6 100644 --- a/src/renderer/src/store/settings.ts +++ b/src/renderer/src/store/settings.ts @@ -186,6 +186,7 @@ export interface SettingsState { // OpenAI openAI: { summaryText: OpenAISummaryText + /** @deprecated 现在该设置迁移到Provider对象中 */ serviceTier: OpenAIServiceTier } // Notification @@ -759,9 +760,6 @@ const settingsSlice = createSlice({ setOpenAISummaryText: (state, action: PayloadAction) => { state.openAI.summaryText = action.payload }, - setOpenAIServiceTier: (state, action: PayloadAction) => { - state.openAI.serviceTier = action.payload - }, setNotificationSettings: (state, action: PayloadAction) => { state.notification = action.payload }, @@ -925,7 +923,6 @@ export const { setEnableBackspaceDeleteModel, setDisableHardwareAcceleration, setOpenAISummaryText, - setOpenAIServiceTier, setNotificationSettings, // Local backup settings setLocalBackupDir,