mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2025-12-22 00:13:09 +08:00
feat: support tokenflux provider (#6358)
* feat: add Cherry Cloud provider with associated assets and localization support * feat: add Cherry Cloud provider support with OAuth integration and IPC handling * fix: add success message for Cherry Cloud API key update * feat: enhance provider navigation with dynamic ID in URL and update selected provider state * feat: implement Cherry Cloud server synchronization with token management and error handling * feat: add CherryCloud provider configuration and token management * feat: integrate TokenFlux provider support and update related configurations fix: update redux-persist version to 104 refactor: remove redundant tokenflux provider model assignment in migration fix: update migration to add TokenFlux provider instead of CherryCloud * feat: enhance TokenFlux server synchronization with API key authentication support * feat: update TokenFlux provider assets and add new models * feat: update migration logic for version 106 to add TokenFlux provider * feat: disable TokenFlux provider by default in INITIAL_PROVIDERS * feat: add TokenFlux billing URLs to providerCharge and providerBills functions
This commit is contained in:
parent
e5bf6916a6
commit
f6d71868cb
@ -173,5 +173,8 @@ export enum IpcChannel {
|
|||||||
StoreSync_Subscribe = 'store-sync:subscribe',
|
StoreSync_Subscribe = 'store-sync:subscribe',
|
||||||
StoreSync_Unsubscribe = 'store-sync:unsubscribe',
|
StoreSync_Unsubscribe = 'store-sync:unsubscribe',
|
||||||
StoreSync_OnUpdate = 'store-sync:on-update',
|
StoreSync_OnUpdate = 'store-sync:on-update',
|
||||||
StoreSync_BroadcastSync = 'store-sync:broadcast-sync'
|
StoreSync_BroadcastSync = 'store-sync:broadcast-sync',
|
||||||
|
|
||||||
|
// Provider
|
||||||
|
Provider_AddKey = 'provider:add-key'
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,6 +6,7 @@ import { promisify } from 'node:util'
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
import Logger from 'electron-log'
|
import Logger from 'electron-log'
|
||||||
|
|
||||||
|
import { handleProvidersProtocolUrl } from './urlschema/handle-providers'
|
||||||
import { handleMcpProtocolUrl } from './urlschema/mcp-install'
|
import { handleMcpProtocolUrl } from './urlschema/mcp-install'
|
||||||
import { windowService } from './WindowService'
|
import { windowService } from './WindowService'
|
||||||
|
|
||||||
@ -34,6 +35,9 @@ export function handleProtocolUrl(url: string) {
|
|||||||
case 'mcp':
|
case 'mcp':
|
||||||
handleMcpProtocolUrl(urlObj)
|
handleMcpProtocolUrl(urlObj)
|
||||||
return
|
return
|
||||||
|
case 'providers':
|
||||||
|
handleProvidersProtocolUrl(urlObj)
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// You can send the data to your renderer process
|
// You can send the data to your renderer process
|
||||||
|
|||||||
37
src/main/services/urlschema/handle-providers.ts
Normal file
37
src/main/services/urlschema/handle-providers.ts
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
|
import Logger from 'electron-log'
|
||||||
|
|
||||||
|
import { windowService } from '../WindowService'
|
||||||
|
|
||||||
|
export function handleProvidersProtocolUrl(url: URL) {
|
||||||
|
const params = new URLSearchParams(url.search)
|
||||||
|
switch (url.pathname) {
|
||||||
|
case '/api-keys': {
|
||||||
|
// jsonConfig example:
|
||||||
|
// {
|
||||||
|
// "id": "tokenflux",
|
||||||
|
// "baseUrl": "https://tokenflux.ai/v1",
|
||||||
|
// "apiKey": "sk-xxxx"
|
||||||
|
// }
|
||||||
|
// cherrystudio://providers/api-keys?data={base64Encode(JSON.stringify(jsonConfig))}
|
||||||
|
const data = params.get('data')
|
||||||
|
if (data) {
|
||||||
|
const stringify = Buffer.from(data, 'base64').toString('utf8')
|
||||||
|
Logger.info('get api keys from urlschema: ', stringify)
|
||||||
|
const jsonConfig = JSON.parse(stringify)
|
||||||
|
Logger.info('get api keys from urlschema: ', jsonConfig)
|
||||||
|
const mainWindow = windowService.getMainWindow()
|
||||||
|
if (mainWindow && !mainWindow.isDestroyed()) {
|
||||||
|
mainWindow.webContents.send(IpcChannel.Provider_AddKey, jsonConfig)
|
||||||
|
mainWindow.webContents.executeJavaScript(`window.navigate('/settings/provider?id=${jsonConfig.id}')`)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Logger.error('No data found in URL')
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
console.error(`Unknown MCP protocol URL: ${url}`)
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
BIN
src/renderer/src/assets/images/models/tokenflux.png
Normal file
BIN
src/renderer/src/assets/images/models/tokenflux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
src/renderer/src/assets/images/models/tokenflux_dark.png
Normal file
BIN
src/renderer/src/assets/images/models/tokenflux_dark.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
BIN
src/renderer/src/assets/images/providers/tokenflux.png
Normal file
BIN
src/renderer/src/assets/images/providers/tokenflux.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 15 KiB |
@ -1,5 +1,5 @@
|
|||||||
import { Provider } from '@renderer/types'
|
import { Provider } from '@renderer/types'
|
||||||
import { oauthWithAihubmix, oauthWithSiliconFlow } from '@renderer/utils/oauth'
|
import { oauthWithAihubmix, oauthWithSiliconFlow, oauthWithTokenFlux } from '@renderer/utils/oauth'
|
||||||
import { Button, ButtonProps } from 'antd'
|
import { Button, ButtonProps } from 'antd'
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@ -27,6 +27,10 @@ const OAuthButton: FC<Props> = ({ provider, onSuccess, ...buttonProps }) => {
|
|||||||
if (provider.id === 'aihubmix') {
|
if (provider.id === 'aihubmix') {
|
||||||
oauthWithAihubmix(handleSuccess)
|
oauthWithAihubmix(handleSuccess)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (provider.id === 'tokenflux') {
|
||||||
|
oauthWithTokenFlux()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@ -10,6 +10,7 @@ export const isWindows = platform === 'win32' || platform === 'win64'
|
|||||||
export const isLinux = platform === 'linux'
|
export const isLinux = platform === 'linux'
|
||||||
|
|
||||||
export const SILICON_CLIENT_ID = 'SFaJLLq0y6CAMoyDm81aMu'
|
export const SILICON_CLIENT_ID = 'SFaJLLq0y6CAMoyDm81aMu'
|
||||||
|
export const TOKENFLUX_HOST = 'https://tokenflux.ai'
|
||||||
|
|
||||||
// Messages loading configuration
|
// Messages loading configuration
|
||||||
export const INITIAL_MESSAGES_COUNT = 20
|
export const INITIAL_MESSAGES_COUNT = 20
|
||||||
|
|||||||
@ -34,8 +34,10 @@ import DianxinModelLogo from '@renderer/assets/images/models/dianxin.png'
|
|||||||
import DianxinModelLogoDark from '@renderer/assets/images/models/dianxin_dark.png'
|
import DianxinModelLogoDark from '@renderer/assets/images/models/dianxin_dark.png'
|
||||||
import DoubaoModelLogo from '@renderer/assets/images/models/doubao.png'
|
import DoubaoModelLogo from '@renderer/assets/images/models/doubao.png'
|
||||||
import DoubaoModelLogoDark from '@renderer/assets/images/models/doubao_dark.png'
|
import DoubaoModelLogoDark from '@renderer/assets/images/models/doubao_dark.png'
|
||||||
import EmbeddingModelLogo from '@renderer/assets/images/models/embedding.png'
|
import {
|
||||||
import EmbeddingModelLogoDark from '@renderer/assets/images/models/embedding.png'
|
default as EmbeddingModelLogo,
|
||||||
|
default as EmbeddingModelLogoDark
|
||||||
|
} from '@renderer/assets/images/models/embedding.png'
|
||||||
import FlashaudioModelLogo from '@renderer/assets/images/models/flashaudio.png'
|
import FlashaudioModelLogo from '@renderer/assets/images/models/flashaudio.png'
|
||||||
import FlashaudioModelLogoDark from '@renderer/assets/images/models/flashaudio_dark.png'
|
import FlashaudioModelLogoDark from '@renderer/assets/images/models/flashaudio_dark.png'
|
||||||
import FluxModelLogo from '@renderer/assets/images/models/flux.png'
|
import FluxModelLogo from '@renderer/assets/images/models/flux.png'
|
||||||
@ -44,14 +46,15 @@ import GeminiModelLogo from '@renderer/assets/images/models/gemini.png'
|
|||||||
import GeminiModelLogoDark from '@renderer/assets/images/models/gemini_dark.png'
|
import GeminiModelLogoDark from '@renderer/assets/images/models/gemini_dark.png'
|
||||||
import GemmaModelLogo from '@renderer/assets/images/models/gemma.png'
|
import GemmaModelLogo from '@renderer/assets/images/models/gemma.png'
|
||||||
import GemmaModelLogoDark from '@renderer/assets/images/models/gemma_dark.png'
|
import GemmaModelLogoDark from '@renderer/assets/images/models/gemma_dark.png'
|
||||||
import GoogleModelLogo from '@renderer/assets/images/models/google.png'
|
import { default as GoogleModelLogo, default as GoogleModelLogoDark } from '@renderer/assets/images/models/google.png'
|
||||||
import GoogleModelLogoDark from '@renderer/assets/images/models/google.png'
|
|
||||||
import ChatGPT35ModelLogo from '@renderer/assets/images/models/gpt_3.5.png'
|
import ChatGPT35ModelLogo from '@renderer/assets/images/models/gpt_3.5.png'
|
||||||
import ChatGPT4ModelLogo from '@renderer/assets/images/models/gpt_4.png'
|
import ChatGPT4ModelLogo from '@renderer/assets/images/models/gpt_4.png'
|
||||||
import ChatGptModelLogoDakr from '@renderer/assets/images/models/gpt_dark.png'
|
import {
|
||||||
import ChatGPT35ModelLogoDark from '@renderer/assets/images/models/gpt_dark.png'
|
default as ChatGPT4ModelLogoDark,
|
||||||
import ChatGPT4ModelLogoDark from '@renderer/assets/images/models/gpt_dark.png'
|
default as ChatGPT35ModelLogoDark,
|
||||||
import ChatGPTo1ModelLogoDark from '@renderer/assets/images/models/gpt_dark.png'
|
default as ChatGptModelLogoDakr,
|
||||||
|
default as ChatGPTo1ModelLogoDark
|
||||||
|
} from '@renderer/assets/images/models/gpt_dark.png'
|
||||||
import ChatGPTo1ModelLogo from '@renderer/assets/images/models/gpt_o1.png'
|
import ChatGPTo1ModelLogo from '@renderer/assets/images/models/gpt_o1.png'
|
||||||
import GrokModelLogo from '@renderer/assets/images/models/grok.png'
|
import GrokModelLogo from '@renderer/assets/images/models/grok.png'
|
||||||
import GrokModelLogoDark from '@renderer/assets/images/models/grok_dark.png'
|
import GrokModelLogoDark from '@renderer/assets/images/models/grok_dark.png'
|
||||||
@ -86,22 +89,28 @@ import MicrosoftModelLogo from '@renderer/assets/images/models/microsoft.png'
|
|||||||
import MicrosoftModelLogoDark from '@renderer/assets/images/models/microsoft_dark.png'
|
import MicrosoftModelLogoDark from '@renderer/assets/images/models/microsoft_dark.png'
|
||||||
import MidjourneyModelLogo from '@renderer/assets/images/models/midjourney.png'
|
import MidjourneyModelLogo from '@renderer/assets/images/models/midjourney.png'
|
||||||
import MidjourneyModelLogoDark from '@renderer/assets/images/models/midjourney_dark.png'
|
import MidjourneyModelLogoDark from '@renderer/assets/images/models/midjourney_dark.png'
|
||||||
import MinicpmModelLogo from '@renderer/assets/images/models/minicpm.webp'
|
import {
|
||||||
import MinicpmModelLogoDark from '@renderer/assets/images/models/minicpm.webp'
|
default as MinicpmModelLogo,
|
||||||
|
default as MinicpmModelLogoDark
|
||||||
|
} from '@renderer/assets/images/models/minicpm.webp'
|
||||||
import MinimaxModelLogo from '@renderer/assets/images/models/minimax.png'
|
import MinimaxModelLogo from '@renderer/assets/images/models/minimax.png'
|
||||||
import MinimaxModelLogoDark from '@renderer/assets/images/models/minimax_dark.png'
|
import MinimaxModelLogoDark from '@renderer/assets/images/models/minimax_dark.png'
|
||||||
import MistralModelLogo from '@renderer/assets/images/models/mixtral.png'
|
import MistralModelLogo from '@renderer/assets/images/models/mixtral.png'
|
||||||
import MistralModelLogoDark from '@renderer/assets/images/models/mixtral_dark.png'
|
import MistralModelLogoDark from '@renderer/assets/images/models/mixtral_dark.png'
|
||||||
import MoonshotModelLogo from '@renderer/assets/images/models/moonshot.png'
|
import MoonshotModelLogo from '@renderer/assets/images/models/moonshot.png'
|
||||||
import MoonshotModelLogoDark from '@renderer/assets/images/models/moonshot_dark.png'
|
import MoonshotModelLogoDark from '@renderer/assets/images/models/moonshot_dark.png'
|
||||||
import NousResearchModelLogo from '@renderer/assets/images/models/nousresearch.png'
|
import {
|
||||||
import NousResearchModelLogoDark from '@renderer/assets/images/models/nousresearch.png'
|
default as NousResearchModelLogo,
|
||||||
|
default as NousResearchModelLogoDark
|
||||||
|
} from '@renderer/assets/images/models/nousresearch.png'
|
||||||
import NvidiaModelLogo from '@renderer/assets/images/models/nvidia.png'
|
import NvidiaModelLogo from '@renderer/assets/images/models/nvidia.png'
|
||||||
import NvidiaModelLogoDark from '@renderer/assets/images/models/nvidia_dark.png'
|
import NvidiaModelLogoDark from '@renderer/assets/images/models/nvidia_dark.png'
|
||||||
import PalmModelLogo from '@renderer/assets/images/models/palm.png'
|
import PalmModelLogo from '@renderer/assets/images/models/palm.png'
|
||||||
import PalmModelLogoDark from '@renderer/assets/images/models/palm_dark.png'
|
import PalmModelLogoDark from '@renderer/assets/images/models/palm_dark.png'
|
||||||
import PerplexityModelLogo from '@renderer/assets/images/models/perplexity.png'
|
import {
|
||||||
import PerplexityModelLogoDark from '@renderer/assets/images/models/perplexity.png'
|
default as PerplexityModelLogo,
|
||||||
|
default as PerplexityModelLogoDark
|
||||||
|
} from '@renderer/assets/images/models/perplexity.png'
|
||||||
import PixtralModelLogo from '@renderer/assets/images/models/pixtral.png'
|
import PixtralModelLogo from '@renderer/assets/images/models/pixtral.png'
|
||||||
import PixtralModelLogoDark from '@renderer/assets/images/models/pixtral_dark.png'
|
import PixtralModelLogoDark from '@renderer/assets/images/models/pixtral_dark.png'
|
||||||
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
|
import QwenModelLogo from '@renderer/assets/images/models/qwen.png'
|
||||||
@ -118,6 +127,8 @@ import SunoModelLogo from '@renderer/assets/images/models/suno.png'
|
|||||||
import SunoModelLogoDark from '@renderer/assets/images/models/suno_dark.png'
|
import SunoModelLogoDark from '@renderer/assets/images/models/suno_dark.png'
|
||||||
import TeleModelLogo from '@renderer/assets/images/models/tele.png'
|
import TeleModelLogo from '@renderer/assets/images/models/tele.png'
|
||||||
import TeleModelLogoDark from '@renderer/assets/images/models/tele_dark.png'
|
import TeleModelLogoDark from '@renderer/assets/images/models/tele_dark.png'
|
||||||
|
import TokenFluxModelLogo from '@renderer/assets/images/models/tokenflux.png'
|
||||||
|
import TokenFluxModelLogoDark from '@renderer/assets/images/models/tokenflux_dark.png'
|
||||||
import UpstageModelLogo from '@renderer/assets/images/models/upstage.png'
|
import UpstageModelLogo from '@renderer/assets/images/models/upstage.png'
|
||||||
import UpstageModelLogoDark from '@renderer/assets/images/models/upstage_dark.png'
|
import UpstageModelLogoDark from '@renderer/assets/images/models/upstage_dark.png'
|
||||||
import ViduModelLogo from '@renderer/assets/images/models/vidu.png'
|
import ViduModelLogo from '@renderer/assets/images/models/vidu.png'
|
||||||
@ -369,7 +380,8 @@ export function getModelLogo(modelId: string) {
|
|||||||
perplexity: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
|
perplexity: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
|
||||||
sonar: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
|
sonar: isLight ? PerplexityModelLogo : PerplexityModelLogoDark,
|
||||||
'bge-': BgeModelLogo,
|
'bge-': BgeModelLogo,
|
||||||
'voyage-': VoyageModelLogo
|
'voyage-': VoyageModelLogo,
|
||||||
|
tokenflux: isLight ? TokenFluxModelLogo : TokenFluxModelLogoDark
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const key in logoMap) {
|
for (const key in logoMap) {
|
||||||
@ -2160,6 +2172,68 @@ export const SYSTEM_MODELS: Record<string, Model[]> = {
|
|||||||
name: 'Qwen2.5 72B Instruct',
|
name: 'Qwen2.5 72B Instruct',
|
||||||
group: 'Qwen'
|
group: 'Qwen'
|
||||||
}
|
}
|
||||||
|
],
|
||||||
|
tokenflux: [
|
||||||
|
{
|
||||||
|
id: 'gpt-4.1',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'GPT-4.1',
|
||||||
|
group: 'GPT-4.1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gpt-4.1-mini',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'GPT-4.1 Mini',
|
||||||
|
group: 'GPT-4.1'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'claude-sonnet-4',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Claude Sonnet 4',
|
||||||
|
group: 'Claude'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'claude-3-7-sonnet',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Claude 3.7 Sonnet',
|
||||||
|
group: 'Claude'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-2.5-pro',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Gemini 2.5 Pro',
|
||||||
|
group: 'Gemini'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'gemini-2.5-flash',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Gemini 2.5 Flash',
|
||||||
|
group: 'Gemini'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deepseek-r1',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'DeepSeek R1',
|
||||||
|
group: 'DeepSeek'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'deepseek-v3',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'DeepSeek V3',
|
||||||
|
group: 'DeepSeek'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwen-max',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Qwen Max',
|
||||||
|
group: 'Qwen'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'qwen-plus',
|
||||||
|
provider: 'tokenflux',
|
||||||
|
name: 'Qwen Plus',
|
||||||
|
group: 'Qwen'
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -38,12 +38,15 @@ import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.p
|
|||||||
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
import StepProviderLogo from '@renderer/assets/images/providers/step.png'
|
||||||
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
import TencentCloudProviderLogo from '@renderer/assets/images/providers/tencent-cloud-ti.png'
|
||||||
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
import TogetherProviderLogo from '@renderer/assets/images/providers/together.png'
|
||||||
|
import TokenFluxProviderLogo from '@renderer/assets/images/providers/tokenflux.png'
|
||||||
import BytedanceProviderLogo from '@renderer/assets/images/providers/volcengine.png'
|
import BytedanceProviderLogo from '@renderer/assets/images/providers/volcengine.png'
|
||||||
import VoyageAIProviderLogo from '@renderer/assets/images/providers/voyageai.png'
|
import VoyageAIProviderLogo from '@renderer/assets/images/providers/voyageai.png'
|
||||||
import XirangProviderLogo from '@renderer/assets/images/providers/xirang.png'
|
import XirangProviderLogo from '@renderer/assets/images/providers/xirang.png'
|
||||||
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
|
import ZeroOneProviderLogo from '@renderer/assets/images/providers/zero-one.png'
|
||||||
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
import ZhipuProviderLogo from '@renderer/assets/images/providers/zhipu.png'
|
||||||
|
|
||||||
|
import { TOKENFLUX_HOST } from './constant'
|
||||||
|
|
||||||
const PROVIDER_LOGO_MAP = {
|
const PROVIDER_LOGO_MAP = {
|
||||||
openai: OpenAiProviderLogo,
|
openai: OpenAiProviderLogo,
|
||||||
silicon: SiliconFlowProviderLogo,
|
silicon: SiliconFlowProviderLogo,
|
||||||
@ -90,7 +93,8 @@ const PROVIDER_LOGO_MAP = {
|
|||||||
gpustack: GPUStackProviderLogo,
|
gpustack: GPUStackProviderLogo,
|
||||||
alayanew: AlayaNewProviderLogo,
|
alayanew: AlayaNewProviderLogo,
|
||||||
voyageai: VoyageAIProviderLogo,
|
voyageai: VoyageAIProviderLogo,
|
||||||
qiniu: QiniuProviderLogo
|
qiniu: QiniuProviderLogo,
|
||||||
|
tokenflux: TokenFluxProviderLogo
|
||||||
} as const
|
} as const
|
||||||
|
|
||||||
export function getProviderLogo(providerId: string) {
|
export function getProviderLogo(providerId: string) {
|
||||||
@ -597,5 +601,16 @@ export const PROVIDER_CONFIG = {
|
|||||||
docs: 'https://developer.qiniu.com/aitokenapi',
|
docs: 'https://developer.qiniu.com/aitokenapi',
|
||||||
models: 'https://developer.qiniu.com/aitokenapi/12883/model-list'
|
models: 'https://developer.qiniu.com/aitokenapi/12883/model-list'
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
tokenflux: {
|
||||||
|
api: {
|
||||||
|
url: TOKENFLUX_HOST
|
||||||
|
},
|
||||||
|
websites: {
|
||||||
|
official: TOKENFLUX_HOST,
|
||||||
|
apiKey: `${TOKENFLUX_HOST}/dashboard/api-keys`,
|
||||||
|
docs: `${TOKENFLUX_HOST}/docs`,
|
||||||
|
models: `${TOKENFLUX_HOST}/models`
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,5 +1,5 @@
|
|||||||
import { createSelector } from '@reduxjs/toolkit'
|
import { createSelector } from '@reduxjs/toolkit'
|
||||||
import { useAppDispatch, useAppSelector } from '@renderer/store'
|
import store, { useAppDispatch, useAppSelector } from '@renderer/store'
|
||||||
import {
|
import {
|
||||||
addModel,
|
addModel,
|
||||||
addProvider,
|
addProvider,
|
||||||
@ -10,6 +10,7 @@ import {
|
|||||||
updateProviders
|
updateProviders
|
||||||
} from '@renderer/store/llm'
|
} from '@renderer/store/llm'
|
||||||
import { Assistant, Model, Provider } from '@renderer/types'
|
import { Assistant, Model, Provider } from '@renderer/types'
|
||||||
|
import { IpcChannel } from '@shared/IpcChannel'
|
||||||
|
|
||||||
import { useDefaultModel } from './useAssistant'
|
import { useDefaultModel } from './useAssistant'
|
||||||
|
|
||||||
@ -63,3 +64,17 @@ export function useProviderByAssistant(assistant: Assistant) {
|
|||||||
const { provider } = useProvider(model.provider)
|
const { provider } = useProvider(model.provider)
|
||||||
return provider
|
return provider
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Listen for server changes from main process
|
||||||
|
window.electron.ipcRenderer.on(IpcChannel.Provider_AddKey, (_, data) => {
|
||||||
|
console.log('Received provider key data:', data)
|
||||||
|
const { id, apiKey } = data
|
||||||
|
// for now only suppor tokenflux, but in the future we can support more
|
||||||
|
if (id === 'tokenflux') {
|
||||||
|
if (apiKey) {
|
||||||
|
store.dispatch(updateProvider({ id, apiKey } as Provider))
|
||||||
|
window.message.success('Provider API key updated')
|
||||||
|
console.log('Provider API key updated:', apiKey)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|||||||
@ -939,7 +939,8 @@
|
|||||||
"zhinao": "360AI",
|
"zhinao": "360AI",
|
||||||
"zhipu": "ZHIPU AI",
|
"zhipu": "ZHIPU AI",
|
||||||
"voyageai": "Voyage AI",
|
"voyageai": "Voyage AI",
|
||||||
"qiniu": "Qiniu"
|
"qiniu": "Qiniu",
|
||||||
|
"tokenflux": "TokenFlux"
|
||||||
},
|
},
|
||||||
"restore": {
|
"restore": {
|
||||||
"confirm": "Are you sure you want to restore data?",
|
"confirm": "Are you sure you want to restore data?",
|
||||||
|
|||||||
@ -940,7 +940,8 @@
|
|||||||
"zhinao": "360智脳",
|
"zhinao": "360智脳",
|
||||||
"zhipu": "智譜AI",
|
"zhipu": "智譜AI",
|
||||||
"voyageai": "Voyage AI",
|
"voyageai": "Voyage AI",
|
||||||
"qiniu": "七牛云"
|
"qiniu": "七牛云",
|
||||||
|
"tokenflux": "TokenFlux"
|
||||||
},
|
},
|
||||||
"restore": {
|
"restore": {
|
||||||
"confirm": "データを復元しますか?",
|
"confirm": "データを復元しますか?",
|
||||||
|
|||||||
@ -940,7 +940,8 @@
|
|||||||
"zhinao": "360AI",
|
"zhinao": "360AI",
|
||||||
"zhipu": "ZHIPU AI",
|
"zhipu": "ZHIPU AI",
|
||||||
"voyageai": "Voyage AI",
|
"voyageai": "Voyage AI",
|
||||||
"qiniu": "Qiniu"
|
"qiniu": "Qiniu",
|
||||||
|
"tokenflux": "TokenFlux"
|
||||||
},
|
},
|
||||||
"restore": {
|
"restore": {
|
||||||
"confirm": "Вы уверены, что хотите восстановить данные?",
|
"confirm": "Вы уверены, что хотите восстановить данные?",
|
||||||
|
|||||||
@ -939,7 +939,8 @@
|
|||||||
"zhinao": "360智脑",
|
"zhinao": "360智脑",
|
||||||
"zhipu": "智谱AI",
|
"zhipu": "智谱AI",
|
||||||
"voyageai": "Voyage AI",
|
"voyageai": "Voyage AI",
|
||||||
"qiniu": "七牛云"
|
"qiniu": "七牛云",
|
||||||
|
"tokenflux": "TokenFlux"
|
||||||
},
|
},
|
||||||
"restore": {
|
"restore": {
|
||||||
"confirm": "确定要恢复数据吗?",
|
"confirm": "确定要恢复数据吗?",
|
||||||
|
|||||||
@ -940,7 +940,8 @@
|
|||||||
"zhinao": "360 智腦",
|
"zhinao": "360 智腦",
|
||||||
"zhipu": "智譜 AI",
|
"zhipu": "智譜 AI",
|
||||||
"voyageai": "Voyage AI",
|
"voyageai": "Voyage AI",
|
||||||
"qiniu": "七牛雲"
|
"qiniu": "七牛雲",
|
||||||
|
"tokenflux": "TokenFlux"
|
||||||
},
|
},
|
||||||
"restore": {
|
"restore": {
|
||||||
"confirm": "確定要復原資料嗎?",
|
"confirm": "確定要復原資料嗎?",
|
||||||
|
|||||||
@ -1,12 +1,13 @@
|
|||||||
import { TopView } from '@renderer/components/TopView'
|
import { TopView } from '@renderer/components/TopView'
|
||||||
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
import { useMCPServers } from '@renderer/hooks/useMCPServers'
|
||||||
import { MCPServer } from '@renderer/types'
|
import type { MCPServer } from '@renderer/types'
|
||||||
import { Button, Form, Input, Modal, Select } from 'antd'
|
import { Button, Form, Input, Modal, Select } from 'antd'
|
||||||
import { useCallback, useEffect, useState } from 'react'
|
import { useCallback, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { getModelScopeToken, saveModelScopeToken, syncModelScopeServers } from './modelscopeSyncUtils'
|
import { getModelScopeToken, saveModelScopeToken, syncModelScopeServers } from './modelscopeSyncUtils'
|
||||||
|
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux'
|
||||||
|
|
||||||
// Provider configuration interface
|
// Provider configuration interface
|
||||||
interface ProviderConfig {
|
interface ProviderConfig {
|
||||||
@ -33,6 +34,17 @@ const providers: ProviderConfig[] = [
|
|||||||
getToken: getModelScopeToken,
|
getToken: getModelScopeToken,
|
||||||
saveToken: saveModelScopeToken,
|
saveToken: saveModelScopeToken,
|
||||||
syncServers: syncModelScopeServers
|
syncServers: syncModelScopeServers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'tokenflux',
|
||||||
|
name: 'TokenFlux',
|
||||||
|
description: 'TokenFlux 平台 MCP 服务',
|
||||||
|
discoverUrl: `${TOKENFLUX_HOST}/mcps`,
|
||||||
|
apiKeyUrl: `${TOKENFLUX_HOST}/dashboard/api-keys`,
|
||||||
|
tokenFieldName: 'tokenfluxToken',
|
||||||
|
getToken: getTokenFluxToken,
|
||||||
|
saveToken: saveTokenFluxToken,
|
||||||
|
syncServers: syncTokenFluxServers
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
@ -83,7 +95,10 @@ const PopupContainer: React.FC<Props> = ({ resolve, existingServers }) => {
|
|||||||
// Save token if present
|
// Save token if present
|
||||||
if (token) {
|
if (token) {
|
||||||
selectedProvider.saveToken(token)
|
selectedProvider.saveToken(token)
|
||||||
setTokens((prev) => ({ ...prev, [selectedProvider.tokenFieldName]: token }))
|
setTokens((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedProvider.tokenFieldName]: token
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Sync servers
|
// Sync servers
|
||||||
@ -196,11 +211,19 @@ const PopupContainer: React.FC<Props> = ({ resolve, existingServers }) => {
|
|||||||
<StepTitle>{t('settings.mcp.sync.setToken', 'Enter Your Token')}</StepTitle>
|
<StepTitle>{t('settings.mcp.sync.setToken', 'Enter Your Token')}</StepTitle>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={selectedProvider.tokenFieldName}
|
name={selectedProvider.tokenFieldName}
|
||||||
rules={[{ required: true, message: t('settings.mcp.sync.tokenRequired', 'API Token is required') }]}>
|
rules={[
|
||||||
|
{
|
||||||
|
required: true,
|
||||||
|
message: t('settings.mcp.sync.tokenRequired', 'API Token is required')
|
||||||
|
}
|
||||||
|
]}>
|
||||||
<Input.Password
|
<Input.Password
|
||||||
placeholder={t('settings.mcp.sync.tokenPlaceholder', 'Enter API token here')}
|
placeholder={t('settings.mcp.sync.tokenPlaceholder', 'Enter API token here')}
|
||||||
onChange={(e) => {
|
onChange={(e) => {
|
||||||
setTokens((prev) => ({ ...prev, [selectedProvider.tokenFieldName]: e.target.value }))
|
setTokens((prev) => ({
|
||||||
|
...prev,
|
||||||
|
[selectedProvider.tokenFieldName]: e.target.value
|
||||||
|
}))
|
||||||
}}
|
}}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@ -0,0 +1,146 @@
|
|||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import type { MCPServer } from '@renderer/types'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
|
||||||
|
// Token storage constants and utilities
|
||||||
|
const TOKEN_STORAGE_KEY = 'tokenflux_token'
|
||||||
|
export const TOKENFLUX_HOST = 'https://tokenflux.ai'
|
||||||
|
|
||||||
|
export const saveTokenFluxToken = (token: string): void => {
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getTokenFluxToken = (): string | null => {
|
||||||
|
return localStorage.getItem(TOKEN_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearTokenFluxToken = (): void => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasTokenFluxToken = (): boolean => {
|
||||||
|
return !!getTokenFluxToken()
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenFluxServerAuthSchemaApiKey {
|
||||||
|
location: string
|
||||||
|
name: string
|
||||||
|
prefix: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenFluxServer {
|
||||||
|
name: string
|
||||||
|
display_name?: string
|
||||||
|
description?: string
|
||||||
|
version: string
|
||||||
|
categories?: string[]
|
||||||
|
logo?: string
|
||||||
|
security_schemes?: Record<string, unknown>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface TokenFluxSyncResult {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
addedServers: MCPServer[]
|
||||||
|
errorDetails?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// Function to fetch and process TokenFlux servers
|
||||||
|
export const syncTokenFluxServers = async (
|
||||||
|
token: string,
|
||||||
|
existingServers: MCPServer[]
|
||||||
|
): Promise<TokenFluxSyncResult> => {
|
||||||
|
const t = i18next.t
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await fetch(`${TOKENFLUX_HOST}/v1/mcps?enabled=true`, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// Handle authentication errors
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
clearTokenFluxToken()
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.unauthorized', 'Sync Unauthorized'),
|
||||||
|
addedServers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle server errors
|
||||||
|
if (response.status === 500 || !response.ok) {
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.error'),
|
||||||
|
addedServers: [],
|
||||||
|
errorDetails: `Status: ${response.status}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process successful response
|
||||||
|
const data = await response.json()
|
||||||
|
const servers: TokenFluxServer[] = data.data || []
|
||||||
|
|
||||||
|
if (servers.length === 0) {
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('settings.mcp.sync.noServersAvailable', 'No MCP servers available'),
|
||||||
|
addedServers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Transform TokenFlux servers to MCP servers format
|
||||||
|
const addedServers: MCPServer[] = []
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
try {
|
||||||
|
// Skip if server already exists
|
||||||
|
if (existingServers.some((s) => s.id === `@tokenflux/${server.name}`)) continue
|
||||||
|
|
||||||
|
const authHeaders = {}
|
||||||
|
if (server.security_schemes && server.security_schemes.api_key) {
|
||||||
|
const keyAuth = server.security_schemes.api_key as TokenFluxServerAuthSchemaApiKey
|
||||||
|
if (keyAuth.location === 'header') {
|
||||||
|
authHeaders[keyAuth.name] = `${keyAuth.prefix || ''} {set your key here}`.trim()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const mcpServer: MCPServer = {
|
||||||
|
id: `@tokenflux/${server.name}`,
|
||||||
|
name: server.display_name || server.name || `TokenFlux Server ${nanoid()}`,
|
||||||
|
description: server.description || '',
|
||||||
|
type: 'streamableHttp',
|
||||||
|
baseUrl: `${TOKENFLUX_HOST}/v1/mcps/${server.name}`,
|
||||||
|
isActive: true,
|
||||||
|
provider: 'TokenFlux',
|
||||||
|
providerUrl: `${TOKENFLUX_HOST}/mcps/${server.name}`,
|
||||||
|
logoUrl: server.logo || '',
|
||||||
|
tags: server.categories || [],
|
||||||
|
headers: authHeaders
|
||||||
|
}
|
||||||
|
|
||||||
|
addedServers.push(mcpServer)
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Error processing TokenFlux server:', err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('settings.mcp.sync.success', { count: addedServers.length }),
|
||||||
|
addedServers
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
console.error('TokenFlux sync error:', error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message: t('settings.mcp.sync.error'),
|
||||||
|
addedServers: [],
|
||||||
|
errorDetails: String(error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -1,5 +1,6 @@
|
|||||||
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp'
|
import AiHubMixProviderLogo from '@renderer/assets/images/providers/aihubmix.webp'
|
||||||
import SiliconFlowProviderLogo from '@renderer/assets/images/providers/silicon.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'
|
import { HStack } from '@renderer/components/Layout'
|
||||||
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
|
import OAuthButton from '@renderer/components/OAuth/OAuthButton'
|
||||||
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
import { PROVIDER_CONFIG } from '@renderer/config/providers'
|
||||||
@ -7,8 +8,7 @@ import { Provider } from '@renderer/types'
|
|||||||
import { providerBills, providerCharge } from '@renderer/utils/oauth'
|
import { providerBills, providerCharge } from '@renderer/utils/oauth'
|
||||||
import { Button } from 'antd'
|
import { Button } from 'antd'
|
||||||
import { isEmpty } from 'lodash'
|
import { isEmpty } from 'lodash'
|
||||||
import { ReceiptText } from 'lucide-react'
|
import { CircleDollarSign, ReceiptText } from 'lucide-react'
|
||||||
import { CircleDollarSign } from 'lucide-react'
|
|
||||||
import { FC } from 'react'
|
import { FC } from 'react'
|
||||||
import { Trans, useTranslation } from 'react-i18next'
|
import { Trans, useTranslation } from 'react-i18next'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
@ -20,7 +20,8 @@ interface Props {
|
|||||||
|
|
||||||
const PROVIDER_LOGO_MAP = {
|
const PROVIDER_LOGO_MAP = {
|
||||||
silicon: SiliconFlowProviderLogo,
|
silicon: SiliconFlowProviderLogo,
|
||||||
aihubmix: AiHubMixProviderLogo
|
aihubmix: AiHubMixProviderLogo,
|
||||||
|
tokenflux: TokenFluxProviderLogo
|
||||||
}
|
}
|
||||||
|
|
||||||
const ProviderOAuth: FC<Props> = ({ provider, setApiKey }) => {
|
const ProviderOAuth: FC<Props> = ({ provider, setApiKey }) => {
|
||||||
|
|||||||
@ -10,6 +10,7 @@ import { Avatar, Button, Dropdown, Input, MenuProps, Tag } from 'antd'
|
|||||||
import { Search, UserPen } from 'lucide-react'
|
import { Search, UserPen } from 'lucide-react'
|
||||||
import { FC, useEffect, useState } from 'react'
|
import { FC, useEffect, useState } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { useSearchParams } from 'react-router-dom'
|
||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import AddProviderPopup from './AddProviderPopup'
|
import AddProviderPopup from './AddProviderPopup'
|
||||||
@ -17,6 +18,7 @@ import ModelNotesPopup from './ModelNotesPopup'
|
|||||||
import ProviderSetting from './ProviderSetting'
|
import ProviderSetting from './ProviderSetting'
|
||||||
|
|
||||||
const ProvidersList: FC = () => {
|
const ProvidersList: FC = () => {
|
||||||
|
const [searchParams] = useSearchParams()
|
||||||
const providers = useAllProviders()
|
const providers = useAllProviders()
|
||||||
const { updateProviders, addProvider, removeProvider, updateProvider } = useProviders()
|
const { updateProviders, addProvider, removeProvider, updateProvider } = useProviders()
|
||||||
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0])
|
const [selectedProvider, setSelectedProvider] = useState<Provider>(providers[0])
|
||||||
@ -46,6 +48,18 @@ const ProvidersList: FC = () => {
|
|||||||
loadAllLogos()
|
loadAllLogos()
|
||||||
}, [providers])
|
}, [providers])
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if (searchParams.get('id')) {
|
||||||
|
const providerId = searchParams.get('id')
|
||||||
|
const provider = providers.find((p) => p.id === providerId)
|
||||||
|
if (provider) {
|
||||||
|
setSelectedProvider(provider)
|
||||||
|
} else {
|
||||||
|
setSelectedProvider(providers[0])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}, [providers, searchParams])
|
||||||
|
|
||||||
const onDragEnd = (result: DropResult) => {
|
const onDragEnd = (result: DropResult) => {
|
||||||
setDragging(false)
|
setDragging(false)
|
||||||
if (result.destination) {
|
if (result.destination) {
|
||||||
|
|||||||
@ -16,7 +16,7 @@ export function getProviderName(id: string) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function isProviderSupportAuth(provider: Provider) {
|
export function isProviderSupportAuth(provider: Provider) {
|
||||||
const supportProviders = ['silicon', 'aihubmix']
|
const supportProviders = ['silicon', 'aihubmix', 'tokenflux']
|
||||||
return supportProviders.includes(provider.id)
|
return supportProviders.includes(provider.id)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -46,7 +46,7 @@ const persistedReducer = persistReducer(
|
|||||||
{
|
{
|
||||||
key: 'cherry-studio',
|
key: 'cherry-studio',
|
||||||
storage,
|
storage,
|
||||||
version: 105,
|
version: 106,
|
||||||
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
blacklist: ['runtime', 'messages', 'messageBlocks'],
|
||||||
migrate
|
migrate
|
||||||
},
|
},
|
||||||
|
|||||||
@ -487,6 +487,16 @@ export const INITIAL_PROVIDERS: Provider[] = [
|
|||||||
models: SYSTEM_MODELS.voyageai,
|
models: SYSTEM_MODELS.voyageai,
|
||||||
isSystem: true,
|
isSystem: true,
|
||||||
enabled: false
|
enabled: false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
id: 'tokenflux',
|
||||||
|
name: 'TokenFlux',
|
||||||
|
type: 'openai',
|
||||||
|
apiKey: '',
|
||||||
|
apiHost: 'https://tokenflux.ai',
|
||||||
|
models: SYSTEM_MODELS.tokenflux,
|
||||||
|
isSystem: true,
|
||||||
|
enabled: false
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -1436,6 +1436,15 @@ const migrateConfig = {
|
|||||||
} catch (error) {
|
} catch (error) {
|
||||||
return state
|
return state
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
'106': (state: RootState) => {
|
||||||
|
try {
|
||||||
|
addProvider(state, 'tokenflux')
|
||||||
|
state.llm.providers = moveProvider(state.llm.providers, 'tokenflux', 15)
|
||||||
|
return state
|
||||||
|
} catch (error) {
|
||||||
|
return state
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
import { SILICON_CLIENT_ID } from '@renderer/config/constant'
|
import { SILICON_CLIENT_ID, TOKENFLUX_HOST } from '@renderer/config/constant'
|
||||||
import { getLanguageCode } from '@renderer/i18n'
|
import i18n, { getLanguageCode } from '@renderer/i18n'
|
||||||
import i18n from '@renderer/i18n'
|
|
||||||
export const oauthWithSiliconFlow = async (setKey) => {
|
export const oauthWithSiliconFlow = async (setKey) => {
|
||||||
const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}`
|
const authUrl = `https://account.siliconflow.cn/oauth?client_id=${SILICON_CLIENT_ID}`
|
||||||
|
|
||||||
@ -58,6 +58,22 @@ export const oauthWithAihubmix = async (setKey) => {
|
|||||||
window.addEventListener('message', messageHandler)
|
window.addEventListener('message', messageHandler)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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}`, {})
|
||||||
|
if (!resp.ok) {
|
||||||
|
window.message.error(i18n.t('oauth.error'))
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const data = await resp.json()
|
||||||
|
const authUrl = data.data.url
|
||||||
|
window.open(
|
||||||
|
authUrl,
|
||||||
|
'oauth',
|
||||||
|
'width=720,height=720,toolbar=no,location=no,status=no,menubar=no,scrollbars=yes,resizable=yes,alwaysOnTop=yes,alwaysRaised=yes'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export const providerCharge = async (provider: string) => {
|
export const providerCharge = async (provider: string) => {
|
||||||
const chargeUrlMap = {
|
const chargeUrlMap = {
|
||||||
silicon: {
|
silicon: {
|
||||||
@ -69,6 +85,11 @@ export const providerCharge = async (provider: string) => {
|
|||||||
url: `https://aihubmix.com/topup?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh`,
|
url: `https://aihubmix.com/topup?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh`,
|
||||||
width: 720,
|
width: 720,
|
||||||
height: 900
|
height: 900
|
||||||
|
},
|
||||||
|
tokenflux: {
|
||||||
|
url: `https://tokenflux.ai/dashboard/billing`,
|
||||||
|
width: 900,
|
||||||
|
height: 700
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +113,11 @@ export const providerBills = async (provider: string) => {
|
|||||||
url: `https://aihubmix.com/statistics?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh`,
|
url: `https://aihubmix.com/statistics?client_id=cherry_studio_oauth&lang=${getLanguageCode()}&aff=SJyh`,
|
||||||
width: 900,
|
width: 900,
|
||||||
height: 700
|
height: 700
|
||||||
|
},
|
||||||
|
tokenflux: {
|
||||||
|
url: `https://tokenflux.ai/dashboard/billing`,
|
||||||
|
width: 900,
|
||||||
|
height: 700
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user