From 57e2165fb8aebd6d7e3e0de395eae6eb0d6f3843 Mon Sep 17 00:00:00 2001 From: hex2077 Date: Thu, 5 Feb 2026 17:52:22 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E6=94=AF=E6=8C=81=E6=89=B9=E9=87=8F?= =?UTF-8?q?=E5=85=B3=E8=81=94=E9=85=8D=E7=BD=AE=E6=96=87=E4=BB=B6=E5=B9=B6?= =?UTF-8?q?=E4=BC=98=E5=8C=96=E6=A8=A1=E5=9E=8B=E5=88=97=E8=A1=A8=E7=AE=A1?= =?UTF-8?q?=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改批量关联功能,从逐个请求改为批量处理,提高效率 - 扩展快速链接API,支持同时处理多个配置文件 - 优化iFlow模型获取逻辑,支持手动添加多个模型 - 更新提供商模型列表,添加新模型并调整顺序 --- src/providers/openai/iflow-core.js | 35 +++--- src/providers/provider-models.js | 7 +- src/ui-modules/provider-api.js | 184 ++++++++++++++++------------ static/app/upload-config-manager.js | 41 +++---- 4 files changed, 155 insertions(+), 112 deletions(-) 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(); } }