mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-19 14:41:24 +08:00
feat: add CherryIN API host selection settings (#11797)
feat(provider): add CherryIN settings and migration support - Introduced CherryINSettings component for configuring CherryIN API hosts. - Updated ProviderSetting to conditionally render CherryINSettings based on provider ID and user language. - Enhanced providerToAiSdkConfig to include CherryIN API host URLs. - Incremented store version to 183 and added migration logic to set default CherryIN API hosts.
This commit is contained in:
parent
96085707ce
commit
76524d68c6
@ -9,6 +9,7 @@ import {
|
|||||||
} from '@renderer/hooks/useAwsBedrock'
|
} from '@renderer/hooks/useAwsBedrock'
|
||||||
import { createVertexProvider, isVertexAIConfigured } from '@renderer/hooks/useVertexAI'
|
import { createVertexProvider, isVertexAIConfigured } from '@renderer/hooks/useVertexAI'
|
||||||
import { getProviderByModel } from '@renderer/services/AssistantService'
|
import { getProviderByModel } from '@renderer/services/AssistantService'
|
||||||
|
import { getProviderById } from '@renderer/services/ProviderService'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
import { isSystemProvider, type Model, type Provider, SystemProviderIds } from '@renderer/types'
|
import { isSystemProvider, type Model, type Provider, SystemProviderIds } from '@renderer/types'
|
||||||
import type { OpenAICompletionsStreamOptions } from '@renderer/types/aiCoreTypes'
|
import type { OpenAICompletionsStreamOptions } from '@renderer/types/aiCoreTypes'
|
||||||
@ -250,6 +251,12 @@ export function providerToAiSdkConfig(actualProvider: Provider, model: Model): A
|
|||||||
if (model.endpoint_type) {
|
if (model.endpoint_type) {
|
||||||
extraOptions.endpointType = model.endpoint_type
|
extraOptions.endpointType = model.endpoint_type
|
||||||
}
|
}
|
||||||
|
// CherryIN API Host
|
||||||
|
const cherryinProvider = getProviderById(SystemProviderIds.cherryin)
|
||||||
|
if (cherryinProvider) {
|
||||||
|
extraOptions.anthropicBaseURL = cherryinProvider.anthropicApiHost
|
||||||
|
extraOptions.geminiBaseURL = cherryinProvider.apiHost + '/gemini/v1beta'
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (hasProviderConfig(aiSdkProviderId) && aiSdkProviderId !== 'openai-compatible') {
|
if (hasProviderConfig(aiSdkProviderId) && aiSdkProviderId !== 'openai-compatible') {
|
||||||
|
|||||||
@ -0,0 +1,74 @@
|
|||||||
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import { Select } from 'antd'
|
||||||
|
import type { FC } from 'react'
|
||||||
|
import { useCallback, useMemo } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface CherryINSettingsProps {
|
||||||
|
providerId: string
|
||||||
|
apiHost: string
|
||||||
|
setApiHost: (host: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const API_HOST_OPTIONS = [
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.cc',
|
||||||
|
labelKey: '加速域名',
|
||||||
|
description: 'open.cherryin.cc'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.net',
|
||||||
|
labelKey: '国际域名',
|
||||||
|
description: 'open.cherryin.net'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
value: 'https://open.cherryin.ai',
|
||||||
|
labelKey: '备用域名',
|
||||||
|
description: 'open.cherryin.ai'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
const CherryINSettings: FC<CherryINSettingsProps> = ({ providerId, apiHost, setApiHost }) => {
|
||||||
|
const { updateProvider } = useProvider(providerId)
|
||||||
|
const { t } = useTranslation()
|
||||||
|
|
||||||
|
const getCurrentHost = useMemo(() => {
|
||||||
|
const matchedOption = API_HOST_OPTIONS.find((option) => apiHost?.includes(option.value.replace('https://', '')))
|
||||||
|
return matchedOption?.value ?? API_HOST_OPTIONS[0].value
|
||||||
|
}, [apiHost])
|
||||||
|
|
||||||
|
const handleHostChange = useCallback(
|
||||||
|
(value: string) => {
|
||||||
|
setApiHost(value)
|
||||||
|
updateProvider({ apiHost: value, anthropicApiHost: value })
|
||||||
|
},
|
||||||
|
[setApiHost, updateProvider]
|
||||||
|
)
|
||||||
|
|
||||||
|
const options = useMemo(
|
||||||
|
() =>
|
||||||
|
API_HOST_OPTIONS.map((option) => ({
|
||||||
|
value: option.value,
|
||||||
|
label: (
|
||||||
|
<div className="flex flex-col gap-0.5">
|
||||||
|
<span>{t(option.labelKey)}</span>
|
||||||
|
<span className="text-[var(--color-text-3)] text-xs">{t(option.description)}</span>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
})),
|
||||||
|
[t]
|
||||||
|
)
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
value={getCurrentHost}
|
||||||
|
onChange={handleHostChange}
|
||||||
|
options={options}
|
||||||
|
style={{ width: '100%', marginTop: 5 }}
|
||||||
|
optionLabelProp="label"
|
||||||
|
labelRender={(option) => option.value}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default CherryINSettings
|
||||||
@ -10,7 +10,6 @@ import { PROVIDER_URLS } from '@renderer/config/providers'
|
|||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
import { useAllProviders, useProvider, useProviders } from '@renderer/hooks/useProvider'
|
||||||
import { useTimer } from '@renderer/hooks/useTimer'
|
import { useTimer } from '@renderer/hooks/useTimer'
|
||||||
import i18n from '@renderer/i18n'
|
|
||||||
import AnthropicSettings from '@renderer/pages/settings/ProviderSettings/AnthropicSettings'
|
import AnthropicSettings from '@renderer/pages/settings/ProviderSettings/AnthropicSettings'
|
||||||
import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList'
|
import { ModelList } from '@renderer/pages/settings/ProviderSettings/ModelList'
|
||||||
import { checkApi } from '@renderer/services/ApiService'
|
import { checkApi } from '@renderer/services/ApiService'
|
||||||
@ -53,6 +52,7 @@ import {
|
|||||||
} from '..'
|
} from '..'
|
||||||
import ApiOptionsSettingsPopup from './ApiOptionsSettings/ApiOptionsSettingsPopup'
|
import ApiOptionsSettingsPopup from './ApiOptionsSettings/ApiOptionsSettingsPopup'
|
||||||
import AwsBedrockSettings from './AwsBedrockSettings'
|
import AwsBedrockSettings from './AwsBedrockSettings'
|
||||||
|
import CherryINSettings from './CherryINSettings'
|
||||||
import CustomHeaderPopup from './CustomHeaderPopup'
|
import CustomHeaderPopup from './CustomHeaderPopup'
|
||||||
import DMXAPISettings from './DMXAPISettings'
|
import DMXAPISettings from './DMXAPISettings'
|
||||||
import GithubCopilotSettings from './GithubCopilotSettings'
|
import GithubCopilotSettings from './GithubCopilotSettings'
|
||||||
@ -99,13 +99,15 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
const [anthropicApiHost, setAnthropicHost] = useState<string | undefined>(provider.anthropicApiHost)
|
const [anthropicApiHost, setAnthropicHost] = useState<string | undefined>(provider.anthropicApiHost)
|
||||||
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
const [apiVersion, setApiVersion] = useState(provider.apiVersion)
|
||||||
const [activeHostField, setActiveHostField] = useState<HostField>('apiHost')
|
const [activeHostField, setActiveHostField] = useState<HostField>('apiHost')
|
||||||
const { t } = useTranslation()
|
const { t, i18n } = useTranslation()
|
||||||
const { theme } = useTheme()
|
const { theme } = useTheme()
|
||||||
const { setTimeoutTimer } = useTimer()
|
const { setTimeoutTimer } = useTimer()
|
||||||
const dispatch = useAppDispatch()
|
const dispatch = useAppDispatch()
|
||||||
|
|
||||||
const isAzureOpenAI = isAzureOpenAIProvider(provider)
|
const isAzureOpenAI = isAzureOpenAIProvider(provider)
|
||||||
const isDmxapi = provider.id === 'dmxapi'
|
const isDmxapi = provider.id === 'dmxapi'
|
||||||
|
const isCherryIN = provider.id === 'cherryin'
|
||||||
|
const isChineseUser = i18n.language.startsWith('zh')
|
||||||
const noAPIInputProviders = ['aws-bedrock'] as const satisfies SystemProviderId[]
|
const noAPIInputProviders = ['aws-bedrock'] as const satisfies SystemProviderId[]
|
||||||
const hideApiInput = noAPIInputProviders.some((id) => id === provider.id)
|
const hideApiInput = noAPIInputProviders.some((id) => id === provider.id)
|
||||||
const noAPIKeyInputProviders = ['copilot', 'vertexai'] as const satisfies SystemProviderId[]
|
const noAPIKeyInputProviders = ['copilot', 'vertexai'] as const satisfies SystemProviderId[]
|
||||||
@ -338,13 +340,16 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
}, [provider.anthropicApiHost])
|
}, [provider.anthropicApiHost])
|
||||||
|
|
||||||
const canConfigureAnthropicHost = useMemo(() => {
|
const canConfigureAnthropicHost = useMemo(() => {
|
||||||
|
if (isCherryIN) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
if (isNewApiProvider(provider)) {
|
if (isNewApiProvider(provider)) {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
provider.type !== 'anthropic' && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id)
|
provider.type !== 'anthropic' && isSystemProviderId(provider.id) && isAnthropicCompatibleProviderId(provider.id)
|
||||||
)
|
)
|
||||||
}, [provider])
|
}, [isCherryIN, provider])
|
||||||
|
|
||||||
const anthropicHostPreview = useMemo(() => {
|
const anthropicHostPreview = useMemo(() => {
|
||||||
const rawHost = anthropicApiHost ?? provider.anthropicApiHost
|
const rawHost = anthropicApiHost ?? provider.anthropicApiHost
|
||||||
@ -513,6 +518,9 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
</SettingSubtitle>
|
</SettingSubtitle>
|
||||||
{activeHostField === 'apiHost' && (
|
{activeHostField === 'apiHost' && (
|
||||||
<>
|
<>
|
||||||
|
{isCherryIN && isChineseUser ? (
|
||||||
|
<CherryINSettings providerId={provider.id} apiHost={apiHost} setApiHost={setApiHost} />
|
||||||
|
) : (
|
||||||
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
<Space.Compact style={{ width: '100%', marginTop: 5 }}>
|
||||||
<Input
|
<Input
|
||||||
value={apiHost}
|
value={apiHost}
|
||||||
@ -526,6 +534,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
|||||||
</Button>
|
</Button>
|
||||||
)}
|
)}
|
||||||
</Space.Compact>
|
</Space.Compact>
|
||||||
|
)}
|
||||||
{isVertexProvider(provider) && (
|
{isVertexProvider(provider) && (
|
||||||
<SettingHelpTextRow>
|
<SettingHelpTextRow>
|
||||||
<SettingHelpText>{t('settings.provider.vertex_ai.api_host_help')}</SettingHelpText>
|
<SettingHelpText>{t('settings.provider.vertex_ai.api_host_help')}</SettingHelpText>
|
||||||
|
|||||||
@ -67,7 +67,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 182,
|
version: 183,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
blacklist: ['runtime', 'messages', 'messageBlocks', 'tabs', 'toolPermissions'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2976,6 +2976,22 @@ const migrateConfig = {
|
|||||||
logger.error('migrate 182 error', error as Error)
|
logger.error('migrate 182 error', error as Error)
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'183': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
state.llm.providers.forEach((provider) => {
|
||||||
|
if (provider.id === SystemProviderIds.cherryin) {
|
||||||
|
provider.apiHost = 'https://open.cherryin.cc'
|
||||||
|
provider.anthropicApiHost = 'https://open.cherryin.cc'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
state.llm.providers = moveProvider(state.llm.providers, SystemProviderIds.poe, 10)
|
||||||
|
logger.info('migrate 183 success')
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
logger.error('migrate 183 error', error as Error)
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user