mirror of
https://github.com/CherryHQ/cherry-studio.git
synced 2026-01-06 21:35:52 +08:00
feat: 同步百炼服务器功能 (#9205)
* 同步百炼服务器功能 * cr修改 --------- Co-authored-by: yunze <yunze.wyz@alibaba-inc.com>
This commit is contained in:
parent
47f49532c6
commit
4f2b1e23a9
@ -7,6 +7,7 @@ import { useTranslation } from 'react-i18next'
|
|||||||
import styled from 'styled-components'
|
import styled from 'styled-components'
|
||||||
|
|
||||||
import { getAI302Token, saveAI302Token, syncAi302Servers } from './providers/302ai'
|
import { getAI302Token, saveAI302Token, syncAi302Servers } from './providers/302ai'
|
||||||
|
import { getBailianToken, saveBailianToken, syncBailianServers } from './providers/bailian'
|
||||||
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './providers/lanyun'
|
import { getTokenLanYunToken, LANYUN_KEY_HOST, saveTokenLanYunToken, syncTokenLanYunServers } from './providers/lanyun'
|
||||||
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './providers/modelscope'
|
import { getModelScopeToken, MODELSCOPE_HOST, saveModelScopeToken, syncModelScopeServers } from './providers/modelscope'
|
||||||
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux'
|
import { getTokenFluxToken, saveTokenFluxToken, syncTokenFluxServers, TOKENFLUX_HOST } from './providers/tokenflux'
|
||||||
@ -69,6 +70,17 @@ const providers: ProviderConfig[] = [
|
|||||||
getToken: getAI302Token,
|
getToken: getAI302Token,
|
||||||
saveToken: saveAI302Token,
|
saveToken: saveAI302Token,
|
||||||
syncServers: syncAi302Servers
|
syncServers: syncAi302Servers
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key: 'bailian',
|
||||||
|
name: '阿里云百炼',
|
||||||
|
description: '百炼平台服务',
|
||||||
|
discoverUrl: `https://bailian.console.aliyun.com/?tab=mcp#/mcp-market`,
|
||||||
|
apiKeyUrl: `https://bailian.console.aliyun.com/?tab=app#/api-key`,
|
||||||
|
tokenFieldName: 'bailianToken',
|
||||||
|
getToken: getBailianToken,
|
||||||
|
saveToken: saveBailianToken,
|
||||||
|
syncServers: syncBailianServers
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
208
src/renderer/src/pages/settings/MCPSettings/providers/bailian.ts
Normal file
208
src/renderer/src/pages/settings/MCPSettings/providers/bailian.ts
Normal file
@ -0,0 +1,208 @@
|
|||||||
|
import { loggerService } from '@logger'
|
||||||
|
import { nanoid } from '@reduxjs/toolkit'
|
||||||
|
import type { MCPServer } from '@renderer/types'
|
||||||
|
import i18next from 'i18next'
|
||||||
|
|
||||||
|
const logger = loggerService.withContext('BailianSyncUtils')
|
||||||
|
|
||||||
|
// 常量定义
|
||||||
|
export const BAILIAN_HOST = 'https://dashscope.aliyuncs.com'
|
||||||
|
const TOKEN_STORAGE_KEY = 'bailian_token'
|
||||||
|
|
||||||
|
// Token 工具函数
|
||||||
|
export const saveBailianToken = (token: string): void => {
|
||||||
|
localStorage.setItem(TOKEN_STORAGE_KEY, token)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const getBailianToken = (): string | null => {
|
||||||
|
const token = localStorage.getItem(TOKEN_STORAGE_KEY)
|
||||||
|
return token
|
||||||
|
}
|
||||||
|
|
||||||
|
export const clearBailianToken = (): void => {
|
||||||
|
localStorage.removeItem(TOKEN_STORAGE_KEY)
|
||||||
|
}
|
||||||
|
|
||||||
|
export const hasBailianToken = (): boolean => {
|
||||||
|
const hasToken = !!getBailianToken()
|
||||||
|
return hasToken
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 类型定义 ==========
|
||||||
|
export interface BailianServer {
|
||||||
|
id: string
|
||||||
|
name: string
|
||||||
|
description?: string
|
||||||
|
operationalUrl?: string
|
||||||
|
tags?: string[]
|
||||||
|
logoUrl?: string
|
||||||
|
providerUrl?: string
|
||||||
|
provider?: string
|
||||||
|
type?: 'streamableHttp' | 'sse'
|
||||||
|
active: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
interface McpServerCherryDetailResponse {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
requestId: string
|
||||||
|
total: number
|
||||||
|
data: BailianServer[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface BailianSyncResult {
|
||||||
|
success: boolean
|
||||||
|
message: string
|
||||||
|
addedServers: MCPServer[]
|
||||||
|
updatedServers: MCPServer[]
|
||||||
|
errorDetails?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 拉取所有 MCP 服务 ==========
|
||||||
|
const PAGE_SIZE = 20
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 拉取全部 MCP 服务器列表,分页封装
|
||||||
|
* 抛出明确错误字符串,供 syncBailianServers 捕捉
|
||||||
|
*/
|
||||||
|
async function fetchAllMcpServers(token: string): Promise<BailianServer[]> {
|
||||||
|
const allServers: BailianServer[] = []
|
||||||
|
let pageNum = 1
|
||||||
|
let total = 0
|
||||||
|
let length = 0
|
||||||
|
|
||||||
|
do {
|
||||||
|
const url = `${BAILIAN_HOST}/api/v1/mcps/user/list?pageNo=${pageNum}&pageSize=${PAGE_SIZE}`
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'GET',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// ----- 错误处理(不再封装 Result,直接 throw,外层处理) -----
|
||||||
|
if (response.status === 401 || response.status === 403) {
|
||||||
|
throw new Error('unauthorized')
|
||||||
|
}
|
||||||
|
if (response.status === 500) {
|
||||||
|
throw new Error('server_error')
|
||||||
|
}
|
||||||
|
if (!response.ok) {
|
||||||
|
throw new Error(`Status: ${response.status}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
const result: McpServerCherryDetailResponse = await response.json()
|
||||||
|
|
||||||
|
if (!result.success) {
|
||||||
|
throw new Error(result.message || 'Fetch failed')
|
||||||
|
}
|
||||||
|
|
||||||
|
allServers.push(...(result.data || []))
|
||||||
|
length = result.data.length
|
||||||
|
total = result.total || 0
|
||||||
|
pageNum++
|
||||||
|
} while ((pageNum - 1) * PAGE_SIZE < total && length > 0)
|
||||||
|
|
||||||
|
return allServers
|
||||||
|
}
|
||||||
|
|
||||||
|
// ========== 主同步函数 ==========
|
||||||
|
export const syncBailianServers = async (token: string, existingServers: MCPServer[]): Promise<BailianSyncResult> => {
|
||||||
|
const t = i18next.t
|
||||||
|
|
||||||
|
try {
|
||||||
|
const servers = await fetchAllMcpServers(token)
|
||||||
|
|
||||||
|
const addedServers: MCPServer[] = []
|
||||||
|
const updatedServers: MCPServer[] = []
|
||||||
|
|
||||||
|
for (const server of servers) {
|
||||||
|
try {
|
||||||
|
if (!server.operationalUrl) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
const id = `@bailian/${server.id}`
|
||||||
|
const existingServer = existingServers.find((s) => s.id === id)
|
||||||
|
|
||||||
|
const mcpServer: MCPServer = {
|
||||||
|
id,
|
||||||
|
name: server.name || `Bailian Server ${nanoid()}`,
|
||||||
|
description: server.description || '',
|
||||||
|
type: server.type,
|
||||||
|
baseUrl: server.operationalUrl,
|
||||||
|
command: '',
|
||||||
|
args: [],
|
||||||
|
env: {},
|
||||||
|
isActive: server.active,
|
||||||
|
provider: server.provider,
|
||||||
|
providerUrl: server.providerUrl,
|
||||||
|
logoUrl: server.logoUrl || '',
|
||||||
|
tags: server.tags || [],
|
||||||
|
headers: {
|
||||||
|
Authorization: `Bearer ${token}`
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (existingServer) {
|
||||||
|
updatedServers.push(mcpServer)
|
||||||
|
} else {
|
||||||
|
addedServers.push(mcpServer)
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
logger.error(`Error processing Bailian server ${server.id}:`, err as Error)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const totalServers = addedServers.length + updatedServers.length
|
||||||
|
|
||||||
|
return {
|
||||||
|
success: true,
|
||||||
|
message: t('settings.mcp.sync.success', { count: totalServers }),
|
||||||
|
addedServers,
|
||||||
|
updatedServers
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let message = ''
|
||||||
|
let errorDetails: string | undefined = undefined
|
||||||
|
|
||||||
|
if (error instanceof Error && error.message === 'unauthorized') {
|
||||||
|
clearBailianToken()
|
||||||
|
message = t('settings.mcp.sync.unauthorized', 'Sync Unauthorized')
|
||||||
|
logger.error('Unauthorized access during sync')
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: []
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error instanceof Error && error.message === 'server_error') {
|
||||||
|
message = t('settings.mcp.sync.error')
|
||||||
|
errorDetails = 'Status: 500'
|
||||||
|
logger.error('Server error during sync')
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: [],
|
||||||
|
errorDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 其他情况
|
||||||
|
logger.error('Bailian sync error:', error as Error)
|
||||||
|
message = t('settings.mcp.sync.error')
|
||||||
|
errorDetails = String(error)
|
||||||
|
return {
|
||||||
|
success: false,
|
||||||
|
message,
|
||||||
|
addedServers: [],
|
||||||
|
updatedServers: [],
|
||||||
|
errorDetails
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in New Issue
Block a user