feat: add oauth for siliconflow (#976)
* wip: silicon oauth * feat: Add custom protocol handler for SiliconFlow OAuth login * feat: Improve SiliconFlow OAuth flow with dynamic key update * feat: Enhance OAuth and Provider Settings UI * feat: Refactor SiliconFlow OAuth and update localization strings * chore: Update provider localization and system provider configuration * feat: Add OAuth support for AIHubMix provider
This commit is contained in:
parent
333547df3d
commit
53f46218d3
@ -50,7 +50,7 @@ export default defineConfig({
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
optimizeDeps: {
|
optimizeDeps: {
|
||||||
exclude: ['chunk-RK3FTE5R.js']
|
exclude: ['chunk-PZ64DZKH.js']
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@ -19,6 +19,25 @@ if (!app.requestSingleInstanceLock()) {
|
|||||||
app.whenReady().then(async () => {
|
app.whenReady().then(async () => {
|
||||||
await updateUserDataPath()
|
await updateUserDataPath()
|
||||||
|
|
||||||
|
// Register custom protocol
|
||||||
|
if (!app.isDefaultProtocolClient('cherrystudio')) {
|
||||||
|
app.setAsDefaultProtocolClient('cherrystudio')
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle protocol open
|
||||||
|
app.on('open-url', (event, url) => {
|
||||||
|
event.preventDefault()
|
||||||
|
const parsedUrl = new URL(url)
|
||||||
|
if (parsedUrl.pathname === 'siliconflow.oauth.login') {
|
||||||
|
const code = parsedUrl.searchParams.get('code')
|
||||||
|
if (code) {
|
||||||
|
// Handle the OAuth code here
|
||||||
|
console.log('OAuth code received:', code)
|
||||||
|
// You can send this code to your renderer process via IPC if needed
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
// Set app user model id for windows
|
// Set app user model id for windows
|
||||||
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
electronApp.setAppUserModelId(import.meta.env.VITE_MAIN_BUNDLE_ID || 'com.kangfenmao.CherryStudio')
|
||||||
|
|
||||||
|
|||||||
@ -163,6 +163,19 @@ export class WindowService {
|
|||||||
mainWindow.webContents.setWindowOpenHandler((details) => {
|
mainWindow.webContents.setWindowOpenHandler((details) => {
|
||||||
const { url } = details
|
const { url } = details
|
||||||
|
|
||||||
|
const oauthProviderUrls = ['https://account.siliconflow.cn']
|
||||||
|
|
||||||
|
if (oauthProviderUrls.some((url) => url.startsWith(url))) {
|
||||||
|
return {
|
||||||
|
action: 'allow',
|
||||||
|
overrideBrowserWindowOptions: {
|
||||||
|
webPreferences: {
|
||||||
|
partition: 'persist:webview'
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (url.includes('http://file/')) {
|
if (url.includes('http://file/')) {
|
||||||
const fileName = url.replace('http://file/', '')
|
const fileName = url.replace('http://file/', '')
|
||||||
const storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
const storageDir = path.join(app.getPath('userData'), 'Data', 'Files')
|
||||||
|
|||||||
40
src/renderer/src/components/OAuth/OAuthButton.tsx
Normal file
40
src/renderer/src/components/OAuth/OAuthButton.tsx
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import { useProvider } from '@renderer/hooks/useProvider'
|
||||||
|
import { Provider } from '@renderer/types'
|
||||||
|
import { oauthWithAihubmix, oauthWithSiliconFlow } from '@renderer/utils/oauth'
|
||||||
|
import { Button, ButtonProps } from 'antd'
|
||||||
|
import { FC } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
|
||||||
|
interface Props extends ButtonProps {
|
||||||
|
provider: Provider
|
||||||
|
}
|
||||||
|
|
||||||
|
const OAuthButton: FC<Props> = (props) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const { provider, updateProvider } = useProvider(props.provider.id)
|
||||||
|
|
||||||
|
const onAuth = () => {
|
||||||
|
const onSuccess = (key: string) => {
|
||||||
|
if (key.trim()) {
|
||||||
|
updateProvider({ ...provider, apiKey: key })
|
||||||
|
window.message.success(t('auth.get_key_success'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'silicon') {
|
||||||
|
oauthWithSiliconFlow(onSuccess)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'aihubmix') {
|
||||||
|
oauthWithAihubmix(onSuccess)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Button onClick={onAuth} {...props}>
|
||||||
|
{t('auth.get_key')}
|
||||||
|
</Button>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default OAuthButton
|
||||||
@ -8,3 +8,5 @@ export const platform = window.electron?.process?.platform
|
|||||||
export const isMac = platform === 'darwin'
|
export const isMac = platform === 'darwin'
|
||||||
export const isWindows = platform === 'win32' || platform === 'win64'
|
export const isWindows = platform === 'win32' || platform === 'win64'
|
||||||
export const isLinux = platform === 'linux'
|
export const isLinux = platform === 'linux'
|
||||||
|
|
||||||
|
export const SILICON_CLIENT_ID = 'SFaJLLq0y6CAMoyDm81aMu'
|
||||||
|
|||||||
@ -15,9 +15,17 @@ const resources = {
|
|||||||
'ru-RU': ruRU
|
'ru-RU': ruRU
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getLanguage = () => {
|
||||||
|
return localStorage.getItem('language') || navigator.language || 'en-US'
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getLanguageCode = () => {
|
||||||
|
return getLanguage().split('-')[0]
|
||||||
|
}
|
||||||
|
|
||||||
i18n.use(initReactI18next).init({
|
i18n.use(initReactI18next).init({
|
||||||
resources,
|
resources,
|
||||||
lng: localStorage.getItem('language') || navigator.language || 'en-US',
|
lng: getLanguage(),
|
||||||
fallbackLng: 'en-US',
|
fallbackLng: 'en-US',
|
||||||
interpolation: {
|
interpolation: {
|
||||||
escapeValue: false
|
escapeValue: false
|
||||||
|
|||||||
@ -509,7 +509,6 @@
|
|||||||
"provider.check": "Check",
|
"provider.check": "Check",
|
||||||
"provider.docs_check": "Check",
|
"provider.docs_check": "Check",
|
||||||
"provider.docs_more_details": "for more details",
|
"provider.docs_more_details": "for more details",
|
||||||
"provider.get_api_key": "Get API Key",
|
|
||||||
"provider.search_placeholder": "Search model id or name",
|
"provider.search_placeholder": "Search model id or name",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
@ -688,6 +687,12 @@
|
|||||||
"esc_back": "back",
|
"esc_back": "back",
|
||||||
"copy_last_message": "Press C to copy"
|
"copy_last_message": "Press C to copy"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"oauth_button": "Auth with {{provider}}",
|
||||||
|
"get_key": "Get",
|
||||||
|
"get_key_success": "API key automatically obtained successfully",
|
||||||
|
"login": "Login"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -672,6 +672,12 @@
|
|||||||
"esc_back": "戻る",
|
"esc_back": "戻る",
|
||||||
"copy_last_message": "C キーを押してコピー"
|
"copy_last_message": "C キーを押してコピー"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"oauth_button": "{{provider}}で認証",
|
||||||
|
"get_key": "取得",
|
||||||
|
"get_key_success": "APIキーの自動取得に成功しました",
|
||||||
|
"login": "認証"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -506,7 +506,6 @@
|
|||||||
"provider.check": "Проверить",
|
"provider.check": "Проверить",
|
||||||
"provider.docs_check": "Проверить",
|
"provider.docs_check": "Проверить",
|
||||||
"provider.docs_more_details": "для получения дополнительной информации",
|
"provider.docs_more_details": "для получения дополнительной информации",
|
||||||
"provider.get_api_key": "Получить ключ API",
|
|
||||||
"provider.search_placeholder": "Поиск по ID или имени модели",
|
"provider.search_placeholder": "Поиск по ID или имени модели",
|
||||||
"proxy": {
|
"proxy": {
|
||||||
"mode": {
|
"mode": {
|
||||||
@ -685,6 +684,12 @@
|
|||||||
"esc_back": "возвращения",
|
"esc_back": "возвращения",
|
||||||
"copy_last_message": "Нажмите C для копирования"
|
"copy_last_message": "Нажмите C для копирования"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"oauth_button": "Авторизоваться с {{provider}}",
|
||||||
|
"get_key": "Получить",
|
||||||
|
"get_key_success": "Автоматический получение ключа API успешно",
|
||||||
|
"login": "Войти"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -489,7 +489,7 @@
|
|||||||
"delete.title": "删除提供商",
|
"delete.title": "删除提供商",
|
||||||
"docs_check": "查看",
|
"docs_check": "查看",
|
||||||
"docs_more_details": "获取更多详情",
|
"docs_more_details": "获取更多详情",
|
||||||
"get_api_key": "点击这里获取密钥",
|
"get_api_key": "获取密钥",
|
||||||
"no_models": "请先添加模型再检查 API 连接",
|
"no_models": "请先添加模型再检查 API 连接",
|
||||||
"not_checked": "未检查",
|
"not_checked": "未检查",
|
||||||
"remove_duplicate_keys": "移除重复密钥",
|
"remove_duplicate_keys": "移除重复密钥",
|
||||||
@ -674,6 +674,12 @@
|
|||||||
"esc_back": "返回",
|
"esc_back": "返回",
|
||||||
"copy_last_message": "按 C 键复制"
|
"copy_last_message": "按 C 键复制"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"oauth_button": "使用{{provider}}登录",
|
||||||
|
"get_key": "获取",
|
||||||
|
"get_key_success": "自动获取密钥成功",
|
||||||
|
"login": "登录"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -488,7 +488,7 @@
|
|||||||
"delete.title": "刪除提供者",
|
"delete.title": "刪除提供者",
|
||||||
"docs_check": "檢查",
|
"docs_check": "檢查",
|
||||||
"docs_more_details": "查看更多細節",
|
"docs_more_details": "查看更多細節",
|
||||||
"get_api_key": "獲取 API 密鑰",
|
"get_api_key": "獲取密鑰",
|
||||||
"no_models": "請先添加模型再檢查 API 連接",
|
"no_models": "請先添加模型再檢查 API 連接",
|
||||||
"not_checked": "未檢查",
|
"not_checked": "未檢查",
|
||||||
"remove_duplicate_keys": "移除重複密鑰",
|
"remove_duplicate_keys": "移除重複密鑰",
|
||||||
@ -673,6 +673,12 @@
|
|||||||
"esc_back": "返回",
|
"esc_back": "返回",
|
||||||
"copy_last_message": "按 C 鍵複製"
|
"copy_last_message": "按 C 鍵複製"
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"auth": {
|
||||||
|
"oauth_button": "使用{{provider}}登入",
|
||||||
|
"get_key": "獲取",
|
||||||
|
"get_key_success": "自動獲取密鑰成功",
|
||||||
|
"login": "登入"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,6 +8,7 @@ import {
|
|||||||
SettingOutlined
|
SettingOutlined
|
||||||
} from '@ant-design/icons'
|
} from '@ant-design/icons'
|
||||||
import ModelTags from '@renderer/components/ModelTags'
|
import ModelTags from '@renderer/components/ModelTags'
|
||||||
|
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
|
||||||
import { EMBEDDING_REGEX, getModelLogo, VISION_REGEX } from '@renderer/config/models'
|
import { EMBEDDING_REGEX, getModelLogo, VISION_REGEX } from '@renderer/config/models'
|
||||||
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
||||||
import { useTheme } from '@renderer/context/ThemeProvider'
|
import { useTheme } from '@renderer/context/ThemeProvider'
|
||||||
@ -16,6 +17,7 @@ import { useProvider } from '@renderer/hooks/useProvider'
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import { isOpenAIProvider } from '@renderer/providers/ProviderFactory'
|
import { isOpenAIProvider } from '@renderer/providers/ProviderFactory'
|
||||||
import { checkApi } from '@renderer/services/ApiService'
|
import { checkApi } from '@renderer/services/ApiService'
|
||||||
|
import { isProviderSupportAuth } from '@renderer/services/ProviderService'
|
||||||
import { useAppDispatch } from '@renderer/store'
|
import { useAppDispatch } from '@renderer/store'
|
||||||
import { setModel } from '@renderer/store/assistants'
|
import { setModel } from '@renderer/store/assistants'
|
||||||
import { Model, ModelType, Provider } from '@renderer/types'
|
import { Model, ModelType, Provider } from '@renderer/types'
|
||||||
@ -61,17 +63,18 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
const { defaultModel, setDefaultModel } = useDefaultModel()
|
const { defaultModel, setDefaultModel } = useDefaultModel()
|
||||||
|
|
||||||
const modelGroups = groupBy(models, 'group')
|
const modelGroups = groupBy(models, 'group')
|
||||||
|
const isAzureOpenAI = provider.id === 'azure-openai' || provider.type === 'azure-openai'
|
||||||
|
|
||||||
useEffect(() => {
|
const providerConfig = PROVIDER_CONFIG[provider.id]
|
||||||
setApiKey(provider.apiKey)
|
const officialWebsite = providerConfig?.websites?.official
|
||||||
setApiHost(provider.apiHost)
|
const apiKeyWebsite = providerConfig?.websites?.apiKey
|
||||||
}, [provider])
|
const docsWebsite = providerConfig?.websites?.docs
|
||||||
|
const modelsWebsite = providerConfig?.websites?.models
|
||||||
|
const configedApiHost = providerConfig?.api?.url
|
||||||
|
|
||||||
const onUpdateApiKey = () => {
|
const onUpdateApiKey = () => {
|
||||||
if (apiKey.trim()) {
|
if (apiKey !== provider.apiKey) {
|
||||||
updateProvider({ ...provider, apiKey })
|
updateProvider({ ...provider, apiKey })
|
||||||
} else {
|
|
||||||
setApiKey(provider.apiKey)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -138,13 +141,6 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const providerConfig = PROVIDER_CONFIG[provider.id]
|
|
||||||
const officialWebsite = providerConfig?.websites?.official
|
|
||||||
const apiKeyWebsite = providerConfig?.websites?.apiKey
|
|
||||||
const docsWebsite = providerConfig?.websites?.docs
|
|
||||||
const modelsWebsite = providerConfig?.websites?.models
|
|
||||||
const configedApiHost = providerConfig?.api?.url
|
|
||||||
|
|
||||||
const onReset = () => {
|
const onReset = () => {
|
||||||
setApiHost(configedApiHost)
|
setApiHost(configedApiHost)
|
||||||
updateProvider({ ...provider, apiHost: configedApiHost })
|
updateProvider({ ...provider, apiHost: configedApiHost })
|
||||||
@ -201,16 +197,28 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
return value.replaceAll(',', ',').replaceAll(' ', ',').replaceAll(' ', '').replaceAll('\n', ',')
|
return value.replaceAll(',', ',').replaceAll(' ', ',').replaceAll(' ', '').replaceAll('\n', ',')
|
||||||
}
|
}
|
||||||
|
|
||||||
const isAzureOpenAI = provider.id === 'azure-openai' || provider.type === 'azure-openai'
|
useEffect(() => {
|
||||||
|
setApiKey(provider.apiKey)
|
||||||
|
setApiHost(provider.apiHost)
|
||||||
|
}, [provider.apiKey, provider.apiHost])
|
||||||
|
|
||||||
|
// Save apiKey to provider when unmount
|
||||||
|
useEffect(() => {
|
||||||
|
return () => {
|
||||||
|
if (apiKey.trim() && apiKey !== provider.apiKey) {
|
||||||
|
updateProvider({ ...provider, apiKey })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [apiKey, provider, updateProvider])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<SettingContainer theme={theme}>
|
<SettingContainer theme={theme}>
|
||||||
<SettingTitle>
|
<SettingTitle>
|
||||||
<Flex align="center">
|
<Flex align="center" gap={8}>
|
||||||
<ProviderName>{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}</ProviderName>
|
<ProviderName>{provider.isSystem ? t(`provider.${provider.id}`) : provider.name}</ProviderName>
|
||||||
{officialWebsite! && (
|
{officialWebsite! && (
|
||||||
<Link target="_blank" href={providerConfig.websites.official}>
|
<Link target="_blank" href={providerConfig.websites.official}>
|
||||||
<ExportOutlined style={{ marginLeft: '8px', color: 'var(--color-text)', fontSize: '12px' }} />
|
<ExportOutlined style={{ color: 'var(--color-text)', fontSize: '12px' }} />
|
||||||
</Link>
|
</Link>
|
||||||
)}
|
)}
|
||||||
</Flex>
|
</Flex>
|
||||||
@ -232,6 +240,7 @@ const ProviderSetting: FC<Props> = ({ provider: _provider }) => {
|
|||||||
type="password"
|
type="password"
|
||||||
autoFocus={provider.enabled && apiKey === ''}
|
autoFocus={provider.enabled && apiKey === ''}
|
||||||
/>
|
/>
|
||||||
|
{isProviderSupportAuth(provider) && <OAuthButton provider={provider} />}
|
||||||
<Button
|
<Button
|
||||||
type={apiValid ? 'primary' : 'default'}
|
type={apiValid ? 'primary' : 'default'}
|
||||||
ghost={apiValid}
|
ghost={apiValid}
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
import i18n from '@renderer/i18n'
|
import i18n from '@renderer/i18n'
|
||||||
import store from '@renderer/store'
|
import store from '@renderer/store'
|
||||||
|
import { Provider } from '@renderer/types'
|
||||||
|
|
||||||
export function getProviderName(id: string) {
|
export function getProviderName(id: string) {
|
||||||
const provider = store.getState().llm.providers.find((p) => p.id === id)
|
const provider = store.getState().llm.providers.find((p) => p.id === id)
|
||||||
@ -13,3 +14,8 @@ export function getProviderName(id: string) {
|
|||||||
|
|
||||||
return provider?.name
|
return provider?.name
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function isProviderSupportAuth(provider: Provider) {
|
||||||
|
const supportProviders = ['silicon']
|
||||||
|
return supportProviders.includes(provider.id)
|
||||||
|
}
|
||||||
|
|||||||
@ -43,6 +43,16 @@ const initialState: LlmState = {
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
id: 'deepseek',
|
||||||
|
name: 'deepseek',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://api.deepseek.com',
|
||||||
|
models: SYSTEM_MODELS.deepseek,
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
|
},
|
||||||
{
|
{
|
||||||
id: 'ollama',
|
id: 'ollama',
|
||||||
name: 'Ollama',
|
name: 'Ollama',
|
||||||
@ -94,16 +104,6 @@ const initialState: LlmState = {
|
|||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
},
|
},
|
||||||
{
|
|
||||||
id: 'deepseek',
|
|
||||||
name: 'deepseek',
|
|
||||||
type: 'openai',
|
|
||||||
apiKey: '',
|
|
||||||
apiHost: 'https://api.deepseek.com',
|
|
||||||
models: SYSTEM_MODELS.deepseek,
|
|
||||||
isSystem: true,
|
|
||||||
enabled: false
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
id: 'ocoolai',
|
id: 'ocoolai',
|
||||||
name: 'ocoolAI',
|
name: 'ocoolAI',
|
||||||
|
|||||||
@ -1,12 +1,48 @@
|
|||||||
|
import { SILICON_CLIENT_ID } from '@renderer/config/constant'
|
||||||
|
import { getLanguageCode } from '@renderer/i18n'
|
||||||
export const oauthWithSiliconFlow = async (setKey) => {
|
export const oauthWithSiliconFlow = async (setKey) => {
|
||||||
const clientId = 'SFrugiu0ezVmREv8BAU6GV'
|
const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}`
|
||||||
const ACCOUNT_ENDPOINT = 'https://account.siliconflow.cn'
|
|
||||||
const authUrl = `${ACCOUNT_ENDPOINT}/oauth?client_id=${clientId}`
|
const popup = window.open(
|
||||||
const popup = window.open(authUrl, 'oauthPopup', 'width=600,height=600')
|
authUrl,
|
||||||
window.addEventListener('message', (event) => {
|
'oauth',
|
||||||
|
'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) {
|
if (event.data.length > 0 && event.data[0]['secretKey'] !== undefined) {
|
||||||
setKey(event.data[0]['secretKey'])
|
setKey(event.data[0]['secretKey'])
|
||||||
popup?.close()
|
popup?.close()
|
||||||
|
window.removeEventListener('message', messageHandler)
|
||||||
}
|
}
|
||||||
})
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('message', messageHandler)
|
||||||
|
window.addEventListener('message', messageHandler)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const oauthWithAihubmix = async (setKey) => {
|
||||||
|
const authUrl = `https://aihubmix.com/login?cherry_studio_oauth=true&lang=${getLanguageCode()}&aff=SJyh`
|
||||||
|
|
||||||
|
const popup = window.open(
|
||||||
|
authUrl,
|
||||||
|
'oauth',
|
||||||
|
'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes'
|
||||||
|
)
|
||||||
|
|
||||||
|
const messageHandler = (event) => {
|
||||||
|
const data = event.data
|
||||||
|
|
||||||
|
if (data && data.key === 'cherry_studio_oauth_callback') {
|
||||||
|
const apiKeys = data?.data?.apiKeys
|
||||||
|
if (apiKeys && apiKeys.length > 0) {
|
||||||
|
setKey(apiKeys[0].value)
|
||||||
|
popup?.close()
|
||||||
|
window.removeEventListener('message', messageHandler)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
window.removeEventListener('message', messageHandler)
|
||||||
|
window.addEventListener('message', messageHandler)
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user