mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-02 18:39:06 +08:00
PPIO OAuth Login (#7717)
* feat: integrate PPIO OAuth login support Add OAuth authentication support for PPIO provider with complete integration: - Add PPIO OAuth configuration and client ID - Implement oauthWithPPIO authentication flow - Add PPIO to OAuth and charge-supported providers list - Include PPIO logo and UI components for OAuth settings - Support charge and billing URL redirects for PPIO 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: fix url * fix: fix redirect url * feat: add PPIO OAuth login * fix: migrate * fix: migrate * fix: ppio migrate --------- Co-authored-by: Claude <noreply@anthropic.com>
This commit is contained in:
parent
d5e8ffc00f
commit
561c563bd7
@ -1,5 +1,5 @@
|
||||
import { Provider } from '@renderer/types'
|
||||
import { oauthWithAihubmix, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth'
|
||||
import { oauthWithAihubmix, oauthWithPPIO, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth'
|
||||
import { Button, ButtonProps } from 'antd'
|
||||
import { FC } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
@ -28,6 +28,10 @@ const OAuthButton: FC<Props> = ({ provider, onSuccess, ...buttonProps }) => {
|
||||
oauthWithAihubmix(handleSuccess)
|
||||
}
|
||||
|
||||
if (provider.id === 'ppio') {
|
||||
oauthWithPPIO(handleSuccess)
|
||||
}
|
||||
|
||||
if (provider.id === 'tokenflux') {
|
||||
oauthWithTokenFlux()
|
||||
}
|
||||
|
||||
@ -11,6 +11,8 @@ export const isWin = platform === 'win32' || platform === 'win64'
|
||||
export const isLinux = platform === 'linux'
|
||||
|
||||
export const SILICON_CLIENT_ID = 'SFaJLLq0y6CAMoyDm81aMu'
|
||||
export const PPIO_CLIENT_ID = '37d0828c96b34936a600b62c'
|
||||
export const PPIO_APP_SECRET = import.meta.env.RENDERER_VITE_PPIO_APP_SECRET || ''
|
||||
export const TOKENFLUX_HOST = 'https://tokenflux.ai'
|
||||
|
||||
// Messages loading configuration
|
||||
|
||||
@ -844,18 +844,6 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
||||
provider: 'ppio',
|
||||
name: 'Qwen3 Reranker 8B',
|
||||
group: 'qwen'
|
||||
},
|
||||
{
|
||||
id: 'thudm/glm-z1-32b-0414',
|
||||
provider: 'ppio',
|
||||
name: 'GLM-Z1 32B',
|
||||
group: 'thudm'
|
||||
},
|
||||
{
|
||||
id: 'thudm/glm-z1-9b-0414',
|
||||
provider: 'ppio',
|
||||
name: 'GLM-Z1 9B',
|
||||
group: 'thudm'
|
||||
}
|
||||
],
|
||||
alayanew: [],
|
||||
|
||||
@ -163,8 +163,9 @@ export const PROVIDER_CONFIG = {
|
||||
url: 'https://api.ppinfra.com/v3/openai'
|
||||
},
|
||||
websites: {
|
||||
official: 'https://ppio.cn/user/register?invited_by=JYT9GD&utm_source=github_cherry-studio',
|
||||
apiKey: 'https://ppio.cn/user/register?invited_by=JYT9GD&utm_source=github_cherry-studio',
|
||||
official: 'https://ppio.cn/user/register?invited_by=JYT9GD&utm_source=github_cherry-studio&redirect=/',
|
||||
apiKey:
|
||||
'https://ppio.cn/user/register?invited_by=JYT9GD&utm_source=github_cherry-studio&redirect=/settings/key-management',
|
||||
docs: 'https://docs.cherry-ai.com/pre-basic/providers/ppio?invited_by=JYT9GD&utm_source=github_cherry-studio',
|
||||
models: 'https://ppio.cn/model-api/product/llm-api?invited_by=JYT9GD&utm_source=github_cherry-studio'
|
||||
}
|
||||
|
||||
@ -1,4 +1,5 @@
|
||||
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.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'
|
||||
import { HStack } from '@renderer/components/Layout'
|
||||
@ -21,14 +22,18 @@ interface Props {
|
||||
const PROVIDER_LOGO_MAP = {
|
||||
silicon: SiliconFlowProviderLogo,
|
||||
aihubmix: AiHubMixProviderLogo,
|
||||
ppio: PPIOProviderLogo,
|
||||
tokenflux: TokenFluxProviderLogo
|
||||
}
|
||||
|
||||
const ProviderOAuth: FC<Props> = ({ provider, setApiKey }) => {
|
||||
const { t } = useTranslation()
|
||||
|
||||
const providerWebsite =
|
||||
let providerWebsite =
|
||||
PROVIDER_CONFIG[provider.id]?.api?.url.replace('https://', '').replace('api.', '') || provider.name
|
||||
if (provider.id === 'ppio') {
|
||||
providerWebsite = 'ppio.cn'
|
||||
}
|
||||
|
||||
return (
|
||||
<Container>
|
||||
|
||||
@ -16,11 +16,11 @@ export function getProviderName(id: string) {
|
||||
}
|
||||
|
||||
export function isProviderSupportAuth(provider: Provider) {
|
||||
const supportProviders = ['silicon', 'aihubmix', 'tokenflux']
|
||||
const supportProviders = ['silicon', 'aihubmix', 'ppio', 'tokenflux']
|
||||
return supportProviders.includes(provider.id)
|
||||
}
|
||||
|
||||
export function isProviderSupportCharge(provider: Provider) {
|
||||
const supportProviders = ['silicon', 'aihubmix']
|
||||
const supportProviders = ['silicon', 'aihubmix', 'ppio']
|
||||
return supportProviders.includes(provider.id)
|
||||
}
|
||||
|
||||
@ -50,7 +50,7 @@ const persistedReducer = persistReducer(
|
||||
{
|
||||
key: 'cherry-studio',
|
||||
storage,
|
||||
version: 117,
|
||||
version: 118,
|
||||
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
||||
migrate
|
||||
},
|
||||
|
||||
@ -1661,12 +1661,30 @@ const migrateConfig = {
|
||||
return state
|
||||
}
|
||||
},
|
||||
'117': (state: RootState) => {
|
||||
'118': (state: RootState) => {
|
||||
try {
|
||||
updateProvider(state, 'ppio', {
|
||||
models: SYSTEM_MODELS.ppio,
|
||||
apiHost: 'https://api.ppinfra.com/v3/openai/'
|
||||
})
|
||||
const ppioProvider = state.llm.providers.find((provider) => provider.id === 'ppio')
|
||||
const modelsToRemove = [
|
||||
'qwen/qwen-2.5-72b-instruct',
|
||||
'qwen/qwen2.5-32b-instruct',
|
||||
'meta-llama/llama-3.1-70b-instruct',
|
||||
'meta-llama/llama-3.1-8b-instruct',
|
||||
'01-ai/yi-1.5-34b-chat',
|
||||
'01-ai/yi-1.5-9b-chat',
|
||||
'thudm/glm-z1-32b-0414',
|
||||
'thudm/glm-z1-9b-0414'
|
||||
]
|
||||
if (ppioProvider) {
|
||||
updateProvider(state, 'ppio', {
|
||||
models: [
|
||||
...ppioProvider.models.filter((model) => !modelsToRemove.includes(model.id)),
|
||||
...SYSTEM_MODELS.ppio.filter(
|
||||
(systemModel) => !ppioProvider.models.some((existingModel) => existingModel.id === systemModel.id)
|
||||
)
|
||||
],
|
||||
apiHost: 'https://api.ppinfra.com/v3/openai/'
|
||||
})
|
||||
}
|
||||
return state
|
||||
} catch (error) {
|
||||
return state
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { SILICON_CLIENT_ID, TOKENFLUX_HOST } from '@renderer/config/constant'
|
||||
import { PPIO_APP_SECRET, PPIO_CLIENT_ID, SILICON_CLIENT_ID, TOKENFLUX_HOST } from '@renderer/config/constant'
|
||||
import i18n, { getLanguageCode } from '@renderer/i18n'
|
||||
|
||||
export const oauthWithSiliconFlow = async (setKey) => {
|
||||
@ -58,6 +58,81 @@ export const oauthWithAihubmix = async (setKey) => {
|
||||
window.addEventListener('message', messageHandler)
|
||||
}
|
||||
|
||||
export const oauthWithPPIO = async (setKey) => {
|
||||
const redirectUri = 'cherrystudio://'
|
||||
const authUrl = `https://ppio.cn/oauth/authorize?client_id=${PPIO_CLIENT_ID}&scope=api%20openid&response_type=code&redirect_uri=${encodeURIComponent(redirectUri)}`
|
||||
|
||||
window.open(
|
||||
authUrl,
|
||||
'oauth',
|
||||
'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes'
|
||||
)
|
||||
|
||||
if (!setKey) {
|
||||
console.log('[PPIO OAuth] No setKey callback provided, returning early')
|
||||
return
|
||||
}
|
||||
|
||||
console.log('[PPIO OAuth] Setting up protocol listener')
|
||||
|
||||
return new Promise<string>((resolve, reject) => {
|
||||
const removeListener = window.api.protocol.onReceiveData(async (data) => {
|
||||
try {
|
||||
const url = new URL(data.url)
|
||||
const params = new URLSearchParams(url.search)
|
||||
const code = params.get('code')
|
||||
|
||||
if (!code) {
|
||||
reject(new Error('No authorization code received'))
|
||||
return
|
||||
}
|
||||
|
||||
if (!PPIO_APP_SECRET) {
|
||||
reject(
|
||||
new Error('PPIO_APP_SECRET not configured. Please set RENDERER_VITE_PPIO_APP_SECRET environment variable.')
|
||||
)
|
||||
return
|
||||
}
|
||||
const formData = new URLSearchParams({
|
||||
client_id: PPIO_CLIENT_ID,
|
||||
client_secret: PPIO_APP_SECRET,
|
||||
code: code,
|
||||
grant_type: 'authorization_code',
|
||||
redirect_uri: redirectUri
|
||||
})
|
||||
const tokenResponse = await fetch('https://ppio.cn/oauth/token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded'
|
||||
},
|
||||
body: formData.toString()
|
||||
})
|
||||
|
||||
if (!tokenResponse.ok) {
|
||||
const errorText = await tokenResponse.text()
|
||||
console.error('[PPIO OAuth] Token exchange failed:', tokenResponse.status, errorText)
|
||||
throw new Error(`Failed to exchange code for token: ${tokenResponse.status} ${errorText}`)
|
||||
}
|
||||
|
||||
const tokenData = await tokenResponse.json()
|
||||
const accessToken = tokenData.access_token
|
||||
|
||||
if (accessToken) {
|
||||
setKey(accessToken)
|
||||
resolve(accessToken)
|
||||
} else {
|
||||
reject(new Error('No access token received'))
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('[PPIO OAuth] Error processing callback:', error)
|
||||
reject(error)
|
||||
} finally {
|
||||
removeListener()
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
export const oauthWithTokenFlux = async () => {
|
||||
const callbackUrl = `${TOKENFLUX_HOST}/auth/callback?redirect_to=/dashboard/api-keys`
|
||||
const resp = await fetch(`${TOKENFLUX_HOST}/api/auth/auth-url?type=login&callback=${callbackUrl}`, {})
|
||||
@ -90,6 +165,11 @@ export const providerCharge = async (provider: string) => {
|
||||
url: `https://tokenflux.ai/dashboard/billing`,
|
||||
width: 900,
|
||||
height: 700
|
||||
},
|
||||
ppio: {
|
||||
url: 'https://ppio.cn/billing?utm_source=github_cherry-studio',
|
||||
width: 900,
|
||||
height: 700
|
||||
}
|
||||
}
|
||||
|
||||
@ -118,6 +198,11 @@ export const providerBills = async (provider: string) => {
|
||||
url: `https://tokenflux.ai/dashboard/billing`,
|
||||
width: 900,
|
||||
height: 700
|
||||
},
|
||||
ppio: {
|
||||
url: 'https://ppio.cn/billing/billing-details?utm_source=github_cherry-studio',
|
||||
width: 900,
|
||||
height: 700
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user