diff --git a/src/providers/openai/iflow-core.js b/src/providers/openai/iflow-core.js index 31d5d89..696ce83 100644 --- a/src/providers/openai/iflow-core.js +++ b/src/providers/openai/iflow-core.js @@ -1054,34 +1054,41 @@ export class IFlowApiService { await this.initialize(); } + // 需要手动添加的模型列表 + const manualModels = ['glm-4.7', 'kimi-k2.5', 'minimax-m2.1']; + try { const response = await this.axiosInstance.get('/models', { headers: this._getHeaders(false) }); - // 检查返回数据中是否包含 glm-4.7,如果没有则添加 + // 检查返回数据中是否包含手动添加的模型,如果没有则添加 const modelsData = response.data; if (modelsData && modelsData.data && Array.isArray(modelsData.data)) { - const hasGlm47 = modelsData.data.some(model => model.id === 'glm-4.7'); - if (!hasGlm47) { - // 添加 glm-4.7 模型到返回列表 - modelsData.data.push({ - id: 'glm-4.7', - object: 'model', - created: Math.floor(Date.now() / 1000), - owned_by: 'iflow' - }); - logger.info('[iFlow] Added glm-4.7 to models list'); + for (const modelId of manualModels) { + const hasModel = modelsData.data.some(model => model.id === modelId); + if (!hasModel) { + // 添加模型到返回列表 + modelsData.data.push({ + id: modelId, + object: 'model', + created: Math.floor(Date.now() / 1000), + owned_by: 'iflow' + }); + logger.info(`[iFlow] Added ${modelId} to models list`); + } } } return modelsData; } catch (error) { logger.warn('[iFlow] Failed to fetch models from API, using default list:', error.message); - // 返回默认模型列表,确保包含 glm-4.7 + // 返回默认模型列表,确保包含手动添加的模型 const defaultModels = [...IFLOW_MODELS]; - if (!defaultModels.includes('glm-4.7')) { - defaultModels.push('glm-4.7'); + for (const modelId of manualModels) { + if (!defaultModels.includes(modelId)) { + defaultModels.push(modelId); + } } return { object: 'list', diff --git a/src/providers/provider-models.js b/src/providers/provider-models.js index 5a35d33..0f38aec 100644 --- a/src/providers/provider-models.js +++ b/src/providers/provider-models.js @@ -56,11 +56,14 @@ export const PROVIDER_MODELS = { 'kimi-k2', // GLM 模型 'glm-4.6', - 'glm-4.7', // DeepSeek 模型 'deepseek-v3.2', 'deepseek-r1', - 'deepseek-v3' + 'deepseek-v3', + // 手动定义 + 'glm-4.7', + 'kimi-k2.5', + 'minimax-m2.1', ], 'openai-codex-oauth': [ 'gpt-5', diff --git a/src/ui-modules/provider-api.js b/src/ui-modules/provider-api.js index aa3952d..813f542 100644 --- a/src/ui-modules/provider-api.js +++ b/src/ui-modules/provider-api.js @@ -810,34 +810,22 @@ export async function handleHealthCheck(req, res, currentConfig, providerPoolMan /** * 快速链接配置文件到对应的提供商 + * 支持单个文件路径或文件路径数组 */ export async function handleQuickLinkProvider(req, res, currentConfig, providerPoolManager) { try { const body = await getRequestBody(req); - const { filePath } = body; + const { filePath, filePaths } = body; - if (!filePath) { + // 支持单个文件路径或文件路径数组 + const pathsToLink = filePaths || (filePath ? [filePath] : []); + + if (!pathsToLink || pathsToLink.length === 0) { res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: { message: 'filePath is required' } })); + res.end(JSON.stringify({ error: { message: 'filePath or filePaths is required' } })); return true; } - const normalizedPath = filePath.replace(/\\/g, '/').toLowerCase(); - - // 根据文件路径自动识别提供商类型 - const providerMapping = detectProviderFromPath(normalizedPath); - - if (!providerMapping) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ - error: { - message: 'Unable to identify provider type for config file, please ensure file is in configs/kiro/, configs/gemini/, configs/qwen/ or configs/antigravity/ directory' - } - })); - return true; - } - - const { providerType, credPathKey, defaultCheckModel, displayName } = providerMapping; const poolsFilePath = currentConfig.PROVIDER_POOLS_FILE_PATH || 'configs/provider_pools.json'; // Load existing pools @@ -851,70 +839,116 @@ export async function handleQuickLinkProvider(req, res, currentConfig, providerP } } - // Ensure provider type array exists - if (!providerPools[providerType]) { - providerPools[providerType] = []; + const results = []; + const linkedProviders = []; + + // 处理每个文件路径 + for (const currentFilePath of pathsToLink) { + const normalizedPath = currentFilePath.replace(/\\/g, '/').toLowerCase(); + + // 根据文件路径自动识别提供商类型 + const providerMapping = detectProviderFromPath(normalizedPath); + + if (!providerMapping) { + results.push({ + filePath: currentFilePath, + success: false, + error: 'Unable to identify provider type for config file' + }); + continue; + } + + const { providerType, credPathKey, defaultCheckModel, displayName } = providerMapping; + + // Ensure provider type array exists + if (!providerPools[providerType]) { + providerPools[providerType] = []; + } + + // Check if already linked - 使用标准化路径进行比较 + const normalizedForComparison = currentFilePath.replace(/\\/g, '/'); + const isAlreadyLinked = providerPools[providerType].some(p => { + const existingPath = p[credPathKey]; + if (!existingPath) return false; + const normalizedExistingPath = existingPath.replace(/\\/g, '/'); + return normalizedExistingPath === normalizedForComparison || + normalizedExistingPath === './' + normalizedForComparison || + './' + normalizedExistingPath === normalizedForComparison; + }); + + if (isAlreadyLinked) { + results.push({ + filePath: currentFilePath, + success: false, + error: 'This config file is already linked', + providerType: providerType + }); + continue; + } + + // Create new provider config based on provider type + const newProvider = createProviderConfig({ + credPathKey, + credPath: formatSystemPath(currentFilePath), + defaultCheckModel, + needsProjectId: providerMapping.needsProjectId + }); + + providerPools[providerType].push(newProvider); + linkedProviders.push({ providerType, provider: newProvider }); + + results.push({ + filePath: currentFilePath, + success: true, + providerType: providerType, + displayName: displayName, + provider: newProvider + }); + + logger.info(`[UI API] Quick linked config: ${currentFilePath} -> ${providerType}`); } - // Check if already linked - 使用标准化路径进行比较 - const normalizedForComparison = filePath.replace(/\\/g, '/'); - const isAlreadyLinked = providerPools[providerType].some(p => { - const existingPath = p[credPathKey]; - if (!existingPath) return false; - const normalizedExistingPath = existingPath.replace(/\\/g, '/'); - return normalizedExistingPath === normalizedForComparison || - normalizedExistingPath === './' + normalizedForComparison || - './' + normalizedExistingPath === normalizedForComparison; - }); + // Save to file only if there were successful links + const successCount = results.filter(r => r.success).length; + if (successCount > 0) { + writeFileSync(poolsFilePath, JSON.stringify(providerPools, null, 2), 'utf-8'); - if (isAlreadyLinked) { - res.writeHead(400, { 'Content-Type': 'application/json' }); - res.end(JSON.stringify({ error: { message: 'This config file is already linked' } })); - return true; + // Update provider pool manager if available + if (providerPoolManager) { + providerPoolManager.providerPools = providerPools; + providerPoolManager.initializeProviderStatus(); + } + + // Broadcast update events + broadcastEvent('config_update', { + action: 'quick_link_batch', + filePath: poolsFilePath, + results: results, + timestamp: new Date().toISOString() + }); + + for (const { providerType, provider } of linkedProviders) { + broadcastEvent('provider_update', { + action: 'add', + providerType, + providerConfig: provider, + timestamp: new Date().toISOString() + }); + } } - // Create new provider config based on provider type - const newProvider = createProviderConfig({ - credPathKey, - credPath: formatSystemPath(filePath), - defaultCheckModel, - needsProjectId: providerMapping.needsProjectId - }); - - providerPools[providerType].push(newProvider); - - // Save to file - writeFileSync(poolsFilePath, JSON.stringify(providerPools, null, 2), 'utf-8'); - logger.info(`[UI API] Quick linked config: ${filePath} -> ${providerType}`); - - // Update provider pool manager if available - if (providerPoolManager) { - providerPoolManager.providerPools = providerPools; - providerPoolManager.initializeProviderStatus(); - } - - // Broadcast update event - broadcastEvent('config_update', { - action: 'quick_link', - filePath: poolsFilePath, - providerType, - newProvider, - timestamp: new Date().toISOString() - }); - - broadcastEvent('provider_update', { - action: 'add', - providerType, - providerConfig: newProvider, - timestamp: new Date().toISOString() - }); + const failCount = results.filter(r => !r.success).length; + const message = successCount > 0 + ? `Successfully linked ${successCount} config file(s)${failCount > 0 ? `, ${failCount} failed` : ''}` + : `Failed to link all ${failCount} config file(s)`; res.writeHead(200, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ - success: true, - message: `Config successfully linked to ${displayName}`, - provider: newProvider, - providerType: providerType + success: successCount > 0, + message: message, + successCount: successCount, + failCount: failCount, + results: results })); return true; } catch (error) { diff --git a/static/app/upload-config-manager.js b/static/app/upload-config-manager.js index 92c2a79..7c5af53 100644 --- a/static/app/upload-config-manager.js +++ b/static/app/upload-config-manager.js @@ -941,28 +941,27 @@ async function batchLinkProviderConfigs() { showToast(t('common.info'), t('upload.batchLink.processing', { count: unlinkedConfigs.length }), 'info'); - let successCount = 0; - let failCount = 0; - - for (const config of unlinkedConfigs) { - try { - await window.apiClient.post('/quick-link-provider', { - filePath: config.path - }); - successCount++; - } catch (error) { - console.error(`关联失败: ${config.path}`, error); - failCount++; + try { + // 一次性传递所有文件路径进行批量关联 + const filePaths = unlinkedConfigs.map(config => config.path); + const result = await window.apiClient.post('/quick-link-provider', { + filePaths: filePaths + }); + + // 刷新配置列表 + await loadConfigList(); + + if (result.failCount === 0) { + showToast(t('common.success'), t('upload.batchLink.success', { count: result.successCount }), 'success'); + } else { + showToast(t('common.warning'), t('upload.batchLink.partial', { success: result.successCount, fail: result.failCount }), 'warning'); } - } - - // 刷新配置列表 - await loadConfigList(); - - if (failCount === 0) { - showToast(t('common.success'), t('upload.batchLink.success', { count: successCount }), 'success'); - } else { - showToast(t('common.warning'), t('upload.batchLink.partial', { success: successCount, fail: failCount }), 'warning'); + } catch (error) { + console.error('批量关联失败:', error); + showToast(t('common.error'), t('upload.batchLink.failed') + ': ' + error.message, 'error'); + + // 即使失败也刷新列表,可能部分成功 + await loadConfigList(); } }