mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-24 10:40:07 +08:00
feat: add apiIdentifier setting for custom providers
This commit is contained in:
parent
4571d01a92
commit
812028823a
@ -4469,6 +4469,16 @@
|
||||
"api_host_no_valid": "API address is invalid",
|
||||
"api_host_preview": "Preview: {{url}}",
|
||||
"api_host_tooltip": "Override only when your provider requires a custom OpenAI-compatible endpoint.",
|
||||
"api_identifier": {
|
||||
"error": {
|
||||
"duplicate": "Identifier is already used by another provider",
|
||||
"invalid": "Only letters, numbers, '-' and '_' are allowed, and ':' is not allowed"
|
||||
},
|
||||
"label": "API Identifier",
|
||||
"placeholder": "Example: my-provider",
|
||||
"preview": "Example: {{model}}",
|
||||
"tip": "Used as the model prefix in API relay. Leave empty to use the internal UUID."
|
||||
},
|
||||
"api_key": {
|
||||
"label": "API Key",
|
||||
"tip": "Use commas to separate multiple keys"
|
||||
|
||||
@ -4469,6 +4469,16 @@
|
||||
"api_host_no_valid": "API 地址不合法",
|
||||
"api_host_preview": "预览:{{url}}",
|
||||
"api_host_tooltip": "仅在服务商需要自定义的 OpenAI 兼容地址时覆盖。",
|
||||
"api_identifier": {
|
||||
"error": {
|
||||
"duplicate": "该标识符已被其他提供商使用",
|
||||
"invalid": "仅支持字母、数字、“-”、“_”,且不能包含“:”"
|
||||
},
|
||||
"label": "API 标识符",
|
||||
"placeholder": "例如:my-provider",
|
||||
"preview": "示例:{{model}}",
|
||||
"tip": "用于 API Relay 的模型前缀。留空则使用内部 UUID。"
|
||||
},
|
||||
"api_key": {
|
||||
"label": "API 密钥",
|
||||
"tip": "多个密钥使用逗号分隔"
|
||||
|
||||
@ -4469,6 +4469,16 @@
|
||||
"api_host_no_valid": "API 位址不合法",
|
||||
"api_host_preview": "預覽:{{url}}",
|
||||
"api_host_tooltip": "僅在供應商需要自訂的 OpenAI 相容端點時才覆蓋。",
|
||||
"api_identifier": {
|
||||
"error": {
|
||||
"duplicate": "此識別碼已被其他供應商使用",
|
||||
"invalid": "僅支援字母、數字、“-”、“_”,且不能包含“:”"
|
||||
},
|
||||
"label": "API 識別碼",
|
||||
"placeholder": "例如:my-provider",
|
||||
"preview": "範例:{{model}}",
|
||||
"tip": "用於 API Relay 的模型前綴。留空則使用內部 UUID。"
|
||||
},
|
||||
"api_key": {
|
||||
"label": "API 金鑰",
|
||||
"tip": "多個金鑰使用逗號分隔"
|
||||
|
||||
@ -92,6 +92,8 @@ const isAnthropicCompatibleProviderId = (id: string): id is AnthropicCompatibleP
|
||||
|
||||
type HostField = 'apiHost' | 'anthropicApiHost'
|
||||
|
||||
const API_IDENTIFIER_PATTERN = /^[a-zA-Z0-9][a-zA-Z0-9_-]{0,31}$/
|
||||
|
||||
const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
const { provider, updateProvider, models } = useProvider(providerId)
|
||||
const allProviders = useAllProviders()
|
||||
@ -122,6 +124,7 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
const fancyProviderName = getFancyProviderName(provider)
|
||||
|
||||
const [localApiKey, setLocalApiKey] = useState(provider.apiKey)
|
||||
const [apiIdentifier, setApiIdentifier] = useState(provider.apiIdentifier ?? '')
|
||||
const [apiKeyConnectivity, setApiKeyConnectivity] = useState<ApiKeyConnectivity>({
|
||||
status: HealthStatus.NOT_CHECKED,
|
||||
checking: false
|
||||
@ -147,6 +150,10 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
setApiKeyConnectivity({ status: HealthStatus.NOT_CHECKED })
|
||||
}, [provider.apiKey])
|
||||
|
||||
useEffect(() => {
|
||||
setApiIdentifier(provider.apiIdentifier ?? '')
|
||||
}, [provider.apiIdentifier])
|
||||
|
||||
// 同步 localApiKey 到 provider.apiKey(防抖)
|
||||
useEffect(() => {
|
||||
if (localApiKey !== provider.apiKey) {
|
||||
@ -385,6 +392,41 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
|
||||
const isAnthropicOAuth = () => provider.id === 'anthropic' && provider.authType === 'oauth'
|
||||
|
||||
const onUpdateApiIdentifier = useCallback(() => {
|
||||
const normalizedIdentifier = apiIdentifier.trim()
|
||||
|
||||
if (!normalizedIdentifier) {
|
||||
updateProvider({ apiIdentifier: undefined })
|
||||
setApiIdentifier('')
|
||||
return
|
||||
}
|
||||
|
||||
if (!API_IDENTIFIER_PATTERN.test(normalizedIdentifier) || normalizedIdentifier.includes(':')) {
|
||||
window.toast.error(t('settings.provider.api_identifier.error.invalid'))
|
||||
setApiIdentifier(provider.apiIdentifier ?? '')
|
||||
return
|
||||
}
|
||||
|
||||
const conflictProvider = allProviders.find((p) => {
|
||||
if (p.id === provider.id) {
|
||||
return false
|
||||
}
|
||||
if (p.id === normalizedIdentifier) {
|
||||
return true
|
||||
}
|
||||
return p.apiIdentifier?.trim() === normalizedIdentifier
|
||||
})
|
||||
|
||||
if (conflictProvider) {
|
||||
window.toast.error(t('settings.provider.api_identifier.error.duplicate'))
|
||||
setApiIdentifier(provider.apiIdentifier ?? '')
|
||||
return
|
||||
}
|
||||
|
||||
updateProvider({ apiIdentifier: normalizedIdentifier })
|
||||
setApiIdentifier(normalizedIdentifier)
|
||||
}, [allProviders, apiIdentifier, provider.apiIdentifier, provider.id, t, updateProvider])
|
||||
|
||||
return (
|
||||
<SettingContainer theme={theme} style={{ background: 'var(--color-background)' }}>
|
||||
<SettingTitle>
|
||||
@ -418,6 +460,34 @@ const ProviderSetting: FC<Props> = ({ providerId }) => {
|
||||
/>
|
||||
</SettingTitle>
|
||||
<Divider style={{ width: '100%', margin: '10px 0' }} />
|
||||
{!isSystemProvider(provider) && (
|
||||
<>
|
||||
<SettingSubtitle style={{ marginTop: 5, display: 'flex', alignItems: 'center', gap: 6 }}>
|
||||
{t('settings.provider.api_identifier.label')}
|
||||
<HelpTooltip title={t('settings.provider.api_identifier.tip')}></HelpTooltip>
|
||||
</SettingSubtitle>
|
||||
<Input
|
||||
value={apiIdentifier}
|
||||
placeholder={t('settings.provider.api_identifier.placeholder')}
|
||||
onChange={(e) => setApiIdentifier(e.target.value)}
|
||||
onBlur={onUpdateApiIdentifier}
|
||||
onKeyDown={(e) => {
|
||||
if (e.key === 'Enter' && !e.nativeEvent.isComposing) {
|
||||
onUpdateApiIdentifier()
|
||||
}
|
||||
}}
|
||||
spellCheck={false}
|
||||
maxLength={32}
|
||||
/>
|
||||
<SettingHelpTextRow style={{ justifyContent: 'space-between' }}>
|
||||
<SettingHelpText>
|
||||
{t('settings.provider.api_identifier.preview', {
|
||||
model: `${apiIdentifier.trim() || provider.id}:glm-4.6`
|
||||
})}
|
||||
</SettingHelpText>
|
||||
</SettingHelpTextRow>
|
||||
</>
|
||||
)}
|
||||
{isProviderSupportAuth(provider) && <ProviderOAuth providerId={provider.id} />}
|
||||
{provider.id === 'openai' && <OpenAIAlert />}
|
||||
{provider.id === 'ovms' && <OVMSSettings />}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user