Merge pull request #158 from ZqinKing/main
feat(fallback): 新增跨协议模型 Fallback 映射功能
This commit is contained in:
commit
982babb5aa
8 changed files with 163 additions and 28 deletions
|
|
@ -19,9 +19,31 @@
|
||||||
"claude-kiro-oauth": ["claude-custom"],
|
"claude-kiro-oauth": ["claude-custom"],
|
||||||
"claude-custom": ["claude-kiro-oauth"]
|
"claude-custom": ["claude-kiro-oauth"]
|
||||||
},
|
},
|
||||||
|
"modelFallbackMapping": {
|
||||||
|
"gemini-claude-opus-4-5-thinking": {
|
||||||
|
"targetProviderType": "claude-kiro-oauth",
|
||||||
|
"targetModel": "claude-opus-4-5"
|
||||||
|
},
|
||||||
|
"gemini-claude-sonnet-4-5-thinking": {
|
||||||
|
"targetProviderType": "claude-kiro-oauth",
|
||||||
|
"targetModel": "claude-sonnet-4-5"
|
||||||
|
},
|
||||||
|
"gemini-claude-sonnet-4-5": {
|
||||||
|
"targetProviderType": "claude-kiro-oauth",
|
||||||
|
"targetModel": "claude-sonnet-4-5"
|
||||||
|
},
|
||||||
|
"claude-opus-4-5": {
|
||||||
|
"targetProviderType": "gemini-antigravity",
|
||||||
|
"targetModel": "gemini-claude-opus-4-5-thinking"
|
||||||
|
},
|
||||||
|
"claude-sonnet-4-5": {
|
||||||
|
"targetProviderType": "gemini-antigravity",
|
||||||
|
"targetModel": "gemini-claude-sonnet-4-5"
|
||||||
|
}
|
||||||
|
},
|
||||||
"PROXY_URL": "http://127.0.0.1:1089",
|
"PROXY_URL": "http://127.0.0.1:1089",
|
||||||
"PROXY_ENABLED_PROVIDERS": [
|
"PROXY_ENABLED_PROVIDERS": [
|
||||||
"gemini-cli-oauth",
|
"gemini-cli-oauth",
|
||||||
"gemini-antigravity"
|
"gemini-antigravity"
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -393,7 +393,7 @@ export async function handleContentGenerationRequest(req, res, service, endpoint
|
||||||
}
|
}
|
||||||
|
|
||||||
// 2. Extract model and determine if the request is for streaming.
|
// 2. Extract model and determine if the request is for streaming.
|
||||||
const { model, isStream } = _extractModelAndStreamInfo(req, originalRequestBody, fromProvider);
|
let { model, isStream } = _extractModelAndStreamInfo(req, originalRequestBody, fromProvider);
|
||||||
|
|
||||||
if (!model) {
|
if (!model) {
|
||||||
throw new Error("Could not determine the model from the request.");
|
throw new Error("Could not determine the model from the request.");
|
||||||
|
|
@ -410,6 +410,12 @@ export async function handleContentGenerationRequest(req, res, service, endpoint
|
||||||
toProvider = result.actualProviderType;
|
toProvider = result.actualProviderType;
|
||||||
actualUuid = result.uuid || pooluuid;
|
actualUuid = result.uuid || pooluuid;
|
||||||
|
|
||||||
|
// 如果发生了模型级别的 fallback,需要更新请求使用的模型
|
||||||
|
if (result.actualModel && result.actualModel !== model) {
|
||||||
|
console.log(`[Content Generation] Model Fallback: ${model} -> ${result.actualModel}`);
|
||||||
|
model = result.actualModel;
|
||||||
|
}
|
||||||
|
|
||||||
if (result.isFallback) {
|
if (result.isFallback) {
|
||||||
console.log(`[Content Generation] Fallback activated: ${CONFIG.MODEL_PROVIDER} -> ${toProvider} (uuid: ${actualUuid})`);
|
console.log(`[Content Generation] Fallback activated: ${CONFIG.MODEL_PROVIDER} -> ${toProvider} (uuid: ${actualUuid})`);
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -802,4 +808,4 @@ function createStreamErrorResponse(error, fromProvider) {
|
||||||
};
|
};
|
||||||
return `data: ${JSON.stringify(defaultError)}\n\n`;
|
return `data: ${JSON.stringify(defaultError)}\n\n`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,9 @@ export class ProviderPoolManager {
|
||||||
// Fallback 链配置
|
// Fallback 链配置
|
||||||
this.fallbackChain = options.globalConfig?.providerFallbackChain || {};
|
this.fallbackChain = options.globalConfig?.providerFallbackChain || {};
|
||||||
|
|
||||||
|
// Model Fallback 映射配置
|
||||||
|
this.modelFallbackMapping = options.globalConfig?.modelFallbackMapping || {};
|
||||||
|
|
||||||
this.initializeProviderStatus();
|
this.initializeProviderStatus();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -189,27 +192,19 @@ export class ProviderPoolManager {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ==========================
|
||||||
|
// 优先级 1: Provider Fallback Chain (同协议/兼容协议的回退)
|
||||||
|
// ==========================
|
||||||
|
|
||||||
// 记录尝试过的类型,避免循环
|
// 记录尝试过的类型,避免循环
|
||||||
const triedTypes = new Set();
|
const triedTypes = new Set();
|
||||||
const typesToTry = [providerType];
|
const typesToTry = [providerType];
|
||||||
|
|
||||||
// 添加 fallback 类型到尝试列表
|
const fallbackTypes = this.fallbackChain[providerType] || [];
|
||||||
const fallbackTypes = this.fallbackChain[providerType];
|
|
||||||
if (!fallbackTypes || fallbackTypes.length === 0) {
|
|
||||||
this._log('info', `No fallback types configured for ${providerType}`);
|
|
||||||
const selectedConfig = this.selectProvider(providerType, requestedModel, options);
|
|
||||||
if (selectedConfig) {
|
|
||||||
return {
|
|
||||||
config: selectedConfig,
|
|
||||||
actualProviderType: providerType,
|
|
||||||
isFallback: false
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (Array.isArray(fallbackTypes)) {
|
if (Array.isArray(fallbackTypes)) {
|
||||||
typesToTry.push(...fallbackTypes);
|
typesToTry.push(...fallbackTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
for (const currentType of typesToTry) {
|
for (const currentType of typesToTry) {
|
||||||
// 避免重复尝试
|
// 避免重复尝试
|
||||||
if (triedTypes.has(currentType)) {
|
if (triedTypes.has(currentType)) {
|
||||||
|
|
@ -219,7 +214,7 @@ export class ProviderPoolManager {
|
||||||
|
|
||||||
// 检查该类型是否有配置的池
|
// 检查该类型是否有配置的池
|
||||||
if (!this.providerStatus[currentType] || this.providerStatus[currentType].length === 0) {
|
if (!this.providerStatus[currentType] || this.providerStatus[currentType].length === 0) {
|
||||||
this._log('info', `No provider pool configured for type: ${currentType}`);
|
this._log('debug', `No provider pool configured for type: ${currentType}`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -230,14 +225,14 @@ export class ProviderPoolManager {
|
||||||
const fallbackProtocol = getProtocolPrefix(currentType);
|
const fallbackProtocol = getProtocolPrefix(currentType);
|
||||||
|
|
||||||
if (primaryProtocol !== fallbackProtocol) {
|
if (primaryProtocol !== fallbackProtocol) {
|
||||||
this._log('info', `Skipping fallback type ${currentType}: protocol mismatch (${primaryProtocol} vs ${fallbackProtocol})`);
|
this._log('debug', `Skipping fallback type ${currentType}: protocol mismatch (${primaryProtocol} vs ${fallbackProtocol})`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 检查 fallback 类型是否支持请求的模型
|
// 检查 fallback 类型是否支持请求的模型
|
||||||
const supportedModels = getProviderModels(currentType);
|
const supportedModels = getProviderModels(currentType);
|
||||||
if (supportedModels.length > 0 && !supportedModels.includes(requestedModel)) {
|
if (supportedModels.length > 0 && !supportedModels.includes(requestedModel)) {
|
||||||
this._log('info', `Skipping fallback type ${currentType}: model ${requestedModel} not supported`);
|
this._log('debug', `Skipping fallback type ${currentType}: model ${requestedModel} not supported`);
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -247,7 +242,7 @@ export class ProviderPoolManager {
|
||||||
|
|
||||||
if (selectedConfig) {
|
if (selectedConfig) {
|
||||||
if (currentType !== providerType) {
|
if (currentType !== providerType) {
|
||||||
this._log('info', `Fallback activated: ${providerType} -> ${currentType} (uuid: ${selectedConfig.uuid})`);
|
this._log('info', `Fallback activated (Chain): ${providerType} -> ${currentType} (uuid: ${selectedConfig.uuid})`);
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
config: selectedConfig,
|
config: selectedConfig,
|
||||||
|
|
@ -257,7 +252,72 @@ export class ProviderPoolManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
this._log('warn', `None available provider found for ${providerType} or any of its fallback types: ${fallbackTypes?.join(', ') || 'none configured'}`);
|
// ==========================
|
||||||
|
// 优先级 2: Model Fallback Mapping (跨协议/特定模型的回退)
|
||||||
|
// ==========================
|
||||||
|
|
||||||
|
if (requestedModel && this.modelFallbackMapping && this.modelFallbackMapping[requestedModel]) {
|
||||||
|
const mapping = this.modelFallbackMapping[requestedModel];
|
||||||
|
const targetProviderType = mapping.targetProviderType;
|
||||||
|
const targetModel = mapping.targetModel;
|
||||||
|
|
||||||
|
if (targetProviderType && targetModel) {
|
||||||
|
this._log('info', `Trying Model Fallback Mapping for ${requestedModel}: -> ${targetProviderType} (${targetModel})`);
|
||||||
|
|
||||||
|
// 递归调用 selectProviderWithFallback,但这次针对目标提供商类型
|
||||||
|
// 注意:这里我们直接尝试从目标提供商池中选择,因为如果再次递归可能会导致死循环或逻辑复杂化
|
||||||
|
// 简单起见,我们直接尝试选择目标提供商
|
||||||
|
|
||||||
|
// 检查目标类型是否有配置的池
|
||||||
|
if (this.providerStatus[targetProviderType] && this.providerStatus[targetProviderType].length > 0) {
|
||||||
|
// 尝试从目标类型选择提供商(使用转换后的模型名)
|
||||||
|
const selectedConfig = this.selectProvider(targetProviderType, targetModel, options);
|
||||||
|
|
||||||
|
if (selectedConfig) {
|
||||||
|
this._log('info', `Fallback activated (Model Mapping): ${providerType} (${requestedModel}) -> ${targetProviderType} (${targetModel}) (uuid: ${selectedConfig.uuid})`);
|
||||||
|
return {
|
||||||
|
config: selectedConfig,
|
||||||
|
actualProviderType: targetProviderType,
|
||||||
|
isFallback: true,
|
||||||
|
actualModel: targetModel // 返回实际使用的模型名,供上层进行请求转换
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
// 如果目标类型的主池也不可用,尝试目标类型的 fallback chain
|
||||||
|
// 例如 claude-kiro-oauth (mapped) -> claude-custom (chain)
|
||||||
|
// 这需要我们小心处理,避免无限递归。
|
||||||
|
// 我们可以手动检查目标类型的 fallback chain
|
||||||
|
|
||||||
|
const targetFallbackTypes = this.fallbackChain[targetProviderType] || [];
|
||||||
|
for (const fallbackType of targetFallbackTypes) {
|
||||||
|
// 检查协议兼容性 (目标类型 vs 它的 fallback)
|
||||||
|
const targetProtocol = getProtocolPrefix(targetProviderType);
|
||||||
|
const fallbackProtocol = getProtocolPrefix(fallbackType);
|
||||||
|
|
||||||
|
if (targetProtocol !== fallbackProtocol) continue;
|
||||||
|
|
||||||
|
// 检查模型支持
|
||||||
|
const supportedModels = getProviderModels(fallbackType);
|
||||||
|
if (supportedModels.length > 0 && !supportedModels.includes(targetModel)) continue;
|
||||||
|
|
||||||
|
const fallbackSelectedConfig = this.selectProvider(fallbackType, targetModel, options);
|
||||||
|
if (fallbackSelectedConfig) {
|
||||||
|
this._log('info', `Fallback activated (Model Mapping -> Chain): ${providerType} (${requestedModel}) -> ${targetProviderType} -> ${fallbackType} (${targetModel}) (uuid: ${fallbackSelectedConfig.uuid})`);
|
||||||
|
return {
|
||||||
|
config: fallbackSelectedConfig,
|
||||||
|
actualProviderType: fallbackType,
|
||||||
|
isFallback: true,
|
||||||
|
actualModel: targetModel
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this._log('warn', `Model Fallback target provider ${targetProviderType} not configured or empty.`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
this._log('warn', `None available provider found for ${providerType} (Model: ${requestedModel}) after checking fallback chain and model mapping.`);
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -716,4 +776,4 @@ export class ProviderPoolManager {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -274,6 +274,7 @@ export async function getApiServiceWithFallback(config, requestedModel = null, o
|
||||||
let actualProviderType = config.MODEL_PROVIDER;
|
let actualProviderType = config.MODEL_PROVIDER;
|
||||||
let isFallback = false;
|
let isFallback = false;
|
||||||
let selectedUuid = null;
|
let selectedUuid = null;
|
||||||
|
let actualModel = null;
|
||||||
|
|
||||||
if (providerPoolManager && config.providerPools && config.providerPools[config.MODEL_PROVIDER]) {
|
if (providerPoolManager && config.providerPools && config.providerPools[config.MODEL_PROVIDER]) {
|
||||||
const selectedResult = providerPoolManager.selectProviderWithFallback(
|
const selectedResult = providerPoolManager.selectProviderWithFallback(
|
||||||
|
|
@ -283,7 +284,7 @@ export async function getApiServiceWithFallback(config, requestedModel = null, o
|
||||||
);
|
);
|
||||||
|
|
||||||
if (selectedResult) {
|
if (selectedResult) {
|
||||||
const { config: selectedProviderConfig, actualProviderType: selectedType, isFallback: fallbackUsed } = selectedResult;
|
const { config: selectedProviderConfig, actualProviderType: selectedType, isFallback: fallbackUsed, actualModel: fallbackModel } = selectedResult;
|
||||||
|
|
||||||
// 合并选中的提供者配置到当前请求的 config 中
|
// 合并选中的提供者配置到当前请求的 config 中
|
||||||
serviceConfig = deepmerge(config, selectedProviderConfig);
|
serviceConfig = deepmerge(config, selectedProviderConfig);
|
||||||
|
|
@ -292,6 +293,7 @@ export async function getApiServiceWithFallback(config, requestedModel = null, o
|
||||||
actualProviderType = selectedType;
|
actualProviderType = selectedType;
|
||||||
isFallback = fallbackUsed;
|
isFallback = fallbackUsed;
|
||||||
selectedUuid = selectedProviderConfig.uuid;
|
selectedUuid = selectedProviderConfig.uuid;
|
||||||
|
actualModel = fallbackModel;
|
||||||
|
|
||||||
// 如果发生了 fallback,需要更新 MODEL_PROVIDER
|
// 如果发生了 fallback,需要更新 MODEL_PROVIDER
|
||||||
if (isFallback) {
|
if (isFallback) {
|
||||||
|
|
@ -311,7 +313,8 @@ export async function getApiServiceWithFallback(config, requestedModel = null, o
|
||||||
serviceConfig,
|
serviceConfig,
|
||||||
actualProviderType,
|
actualProviderType,
|
||||||
isFallback,
|
isFallback,
|
||||||
uuid: selectedUuid
|
uuid: selectedUuid,
|
||||||
|
actualModel
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -701,6 +701,7 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
|
||||||
if (newConfig.PROVIDER_POOLS_FILE_PATH !== undefined) currentConfig.PROVIDER_POOLS_FILE_PATH = newConfig.PROVIDER_POOLS_FILE_PATH;
|
if (newConfig.PROVIDER_POOLS_FILE_PATH !== undefined) currentConfig.PROVIDER_POOLS_FILE_PATH = newConfig.PROVIDER_POOLS_FILE_PATH;
|
||||||
if (newConfig.MAX_ERROR_COUNT !== undefined) currentConfig.MAX_ERROR_COUNT = newConfig.MAX_ERROR_COUNT;
|
if (newConfig.MAX_ERROR_COUNT !== undefined) currentConfig.MAX_ERROR_COUNT = newConfig.MAX_ERROR_COUNT;
|
||||||
if (newConfig.providerFallbackChain !== undefined) currentConfig.providerFallbackChain = newConfig.providerFallbackChain;
|
if (newConfig.providerFallbackChain !== undefined) currentConfig.providerFallbackChain = newConfig.providerFallbackChain;
|
||||||
|
if (newConfig.modelFallbackMapping !== undefined) currentConfig.modelFallbackMapping = newConfig.modelFallbackMapping;
|
||||||
|
|
||||||
// Proxy settings
|
// Proxy settings
|
||||||
if (newConfig.PROXY_URL !== undefined) currentConfig.PROXY_URL = newConfig.PROXY_URL;
|
if (newConfig.PROXY_URL !== undefined) currentConfig.PROXY_URL = newConfig.PROXY_URL;
|
||||||
|
|
@ -748,6 +749,7 @@ export async function handleUIApiRequests(method, pathParam, req, res, currentCo
|
||||||
PROVIDER_POOLS_FILE_PATH: currentConfig.PROVIDER_POOLS_FILE_PATH,
|
PROVIDER_POOLS_FILE_PATH: currentConfig.PROVIDER_POOLS_FILE_PATH,
|
||||||
MAX_ERROR_COUNT: currentConfig.MAX_ERROR_COUNT,
|
MAX_ERROR_COUNT: currentConfig.MAX_ERROR_COUNT,
|
||||||
providerFallbackChain: currentConfig.providerFallbackChain,
|
providerFallbackChain: currentConfig.providerFallbackChain,
|
||||||
|
modelFallbackMapping: currentConfig.modelFallbackMapping,
|
||||||
PROXY_URL: currentConfig.PROXY_URL,
|
PROXY_URL: currentConfig.PROXY_URL,
|
||||||
PROXY_ENABLED_PROVIDERS: currentConfig.PROXY_ENABLED_PROVIDERS
|
PROXY_ENABLED_PROVIDERS: currentConfig.PROXY_ENABLED_PROVIDERS
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -70,6 +70,7 @@ async function loadConfiguration() {
|
||||||
const providerPoolsFilePathEl = document.getElementById('providerPoolsFilePath');
|
const providerPoolsFilePathEl = document.getElementById('providerPoolsFilePath');
|
||||||
const maxErrorCountEl = document.getElementById('maxErrorCount');
|
const maxErrorCountEl = document.getElementById('maxErrorCount');
|
||||||
const providerFallbackChainEl = document.getElementById('providerFallbackChain');
|
const providerFallbackChainEl = document.getElementById('providerFallbackChain');
|
||||||
|
const modelFallbackMappingEl = document.getElementById('modelFallbackMapping');
|
||||||
|
|
||||||
if (systemPromptFilePathEl) systemPromptFilePathEl.value = data.SYSTEM_PROMPT_FILE_PATH || 'configs/input_system_prompt.txt';
|
if (systemPromptFilePathEl) systemPromptFilePathEl.value = data.SYSTEM_PROMPT_FILE_PATH || 'configs/input_system_prompt.txt';
|
||||||
if (systemPromptModeEl) systemPromptModeEl.value = data.SYSTEM_PROMPT_MODE || 'append';
|
if (systemPromptModeEl) systemPromptModeEl.value = data.SYSTEM_PROMPT_MODE || 'append';
|
||||||
|
|
@ -90,6 +91,15 @@ async function loadConfiguration() {
|
||||||
providerFallbackChainEl.value = '';
|
providerFallbackChainEl.value = '';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 加载 Model Fallback 映射配置
|
||||||
|
if (modelFallbackMappingEl) {
|
||||||
|
if (data.modelFallbackMapping && typeof data.modelFallbackMapping === 'object') {
|
||||||
|
modelFallbackMappingEl.value = JSON.stringify(data.modelFallbackMapping, null, 2);
|
||||||
|
} else {
|
||||||
|
modelFallbackMappingEl.value = '';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// 加载代理配置
|
// 加载代理配置
|
||||||
const proxyUrlEl = document.getElementById('proxyUrl');
|
const proxyUrlEl = document.getElementById('proxyUrl');
|
||||||
|
|
@ -160,6 +170,19 @@ async function saveConfiguration() {
|
||||||
} else {
|
} else {
|
||||||
config.providerFallbackChain = {};
|
config.providerFallbackChain = {};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 保存 Model Fallback 映射配置
|
||||||
|
const modelFallbackMappingValue = document.getElementById('modelFallbackMapping')?.value?.trim() || '';
|
||||||
|
if (modelFallbackMappingValue) {
|
||||||
|
try {
|
||||||
|
config.modelFallbackMapping = JSON.parse(modelFallbackMappingValue);
|
||||||
|
} catch (e) {
|
||||||
|
showToast(t('common.error'), t('config.advanced.modelFallbackMappingInvalid') || 'Model Fallback 映射配置格式无效,请输入有效的 JSON', 'error');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
config.modelFallbackMapping = {};
|
||||||
|
}
|
||||||
|
|
||||||
// 保存代理配置
|
// 保存代理配置
|
||||||
config.PROXY_URL = document.getElementById('proxyUrl')?.value?.trim() || null;
|
config.PROXY_URL = document.getElementById('proxyUrl')?.value?.trim() || null;
|
||||||
|
|
@ -204,4 +227,4 @@ async function saveConfiguration() {
|
||||||
export {
|
export {
|
||||||
loadConfiguration,
|
loadConfiguration,
|
||||||
saveConfiguration
|
saveConfiguration
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -206,6 +206,10 @@ const translations = {
|
||||||
'config.advanced.fallbackChainPlaceholder': '例如:\n{\n "gemini-cli-oauth": ["gemini-antigravity"],\n "gemini-antigravity": ["gemini-cli-oauth"],\n "claude-kiro-oauth": ["claude-custom"]\n}',
|
'config.advanced.fallbackChainPlaceholder': '例如:\n{\n "gemini-cli-oauth": ["gemini-antigravity"],\n "gemini-antigravity": ["gemini-cli-oauth"],\n "claude-kiro-oauth": ["claude-custom"]\n}',
|
||||||
'config.advanced.fallbackChainNote': '当某一 Provider Type 所有账号都不健康时,自动切换到配置的 Fallback 类型。JSON 格式,键为主类型,值为 Fallback 类型数组(按优先级排序)',
|
'config.advanced.fallbackChainNote': '当某一 Provider Type 所有账号都不健康时,自动切换到配置的 Fallback 类型。JSON 格式,键为主类型,值为 Fallback 类型数组(按优先级排序)',
|
||||||
'config.advanced.fallbackChainInvalid': 'Fallback 链配置格式无效,请输入有效的 JSON',
|
'config.advanced.fallbackChainInvalid': 'Fallback 链配置格式无效,请输入有效的 JSON',
|
||||||
|
'config.advanced.modelFallbackMapping': '跨协议模型 Fallback 映射',
|
||||||
|
'config.advanced.modelFallbackMappingPlaceholder': '例如:\n{\n "gemini-claude-opus-4-5-thinking": {\n "targetProviderType": "claude-kiro-oauth",\n "targetModel": "claude-opus-4-5"\n }\n}',
|
||||||
|
'config.advanced.modelFallbackMappingNote': '当主 Provider 不可用时,根据模型名映射到其他协议的 Provider 和模型。优先级低于上方的 Fallback 链配置。JSON 格式。',
|
||||||
|
'config.advanced.modelFallbackMappingInvalid': 'Model Fallback 映射配置格式无效,请输入有效的 JSON',
|
||||||
'config.advanced.systemPrompt': '系统提示',
|
'config.advanced.systemPrompt': '系统提示',
|
||||||
'config.advanced.systemPromptPlaceholder': '输入系统提示...',
|
'config.advanced.systemPromptPlaceholder': '输入系统提示...',
|
||||||
'config.advanced.adminPassword': '后台登录密码',
|
'config.advanced.adminPassword': '后台登录密码',
|
||||||
|
|
@ -621,6 +625,10 @@ const translations = {
|
||||||
'config.advanced.fallbackChainPlaceholder': 'Example:\n{\n "gemini-cli-oauth": ["gemini-antigravity"],\n "gemini-antigravity": ["gemini-cli-oauth"],\n "claude-kiro-oauth": ["claude-custom"]\n}',
|
'config.advanced.fallbackChainPlaceholder': 'Example:\n{\n "gemini-cli-oauth": ["gemini-antigravity"],\n "gemini-antigravity": ["gemini-cli-oauth"],\n "claude-kiro-oauth": ["claude-custom"]\n}',
|
||||||
'config.advanced.fallbackChainNote': 'When all accounts of a Provider Type are unhealthy, automatically switch to configured Fallback types. JSON format, key is primary type, value is Fallback type array (sorted by priority)',
|
'config.advanced.fallbackChainNote': 'When all accounts of a Provider Type are unhealthy, automatically switch to configured Fallback types. JSON format, key is primary type, value is Fallback type array (sorted by priority)',
|
||||||
'config.advanced.fallbackChainInvalid': 'Invalid Fallback chain config format, please enter valid JSON',
|
'config.advanced.fallbackChainInvalid': 'Invalid Fallback chain config format, please enter valid JSON',
|
||||||
|
'config.advanced.modelFallbackMapping': 'Cross-Protocol Model Fallback Mapping',
|
||||||
|
'config.advanced.modelFallbackMappingPlaceholder': 'Example:\n{\n "gemini-claude-opus-4-5-thinking": {\n "targetProviderType": "claude-kiro-oauth",\n "targetModel": "claude-opus-4-5"\n }\n}',
|
||||||
|
'config.advanced.modelFallbackMappingNote': 'When the primary Provider is unavailable, map to other protocol Providers and models by model name. Priority is lower than the Fallback Chain Config above. JSON format.',
|
||||||
|
'config.advanced.modelFallbackMappingInvalid': 'Invalid Model Fallback mapping config format, please enter valid JSON',
|
||||||
'config.advanced.systemPrompt': 'System Prompt',
|
'config.advanced.systemPrompt': 'System Prompt',
|
||||||
'config.advanced.systemPromptPlaceholder': 'Enter system prompt...',
|
'config.advanced.systemPromptPlaceholder': 'Enter system prompt...',
|
||||||
'config.advanced.adminPassword': 'Admin Password',
|
'config.advanced.adminPassword': 'Admin Password',
|
||||||
|
|
@ -1047,4 +1055,4 @@ export default {
|
||||||
setLanguage,
|
setLanguage,
|
||||||
getCurrentLanguage,
|
getCurrentLanguage,
|
||||||
initI18n
|
initI18n
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -701,6 +701,18 @@
|
||||||
<small class="form-text" data-i18n="config.advanced.fallbackChainNote">当某一 Provider Type 所有账号都不健康时,自动切换到配置的 Fallback 类型。JSON 格式,键为主类型,值为 Fallback 类型数组(按优先级排序)</small>
|
<small class="form-text" data-i18n="config.advanced.fallbackChainNote">当某一 Provider Type 所有账号都不健康时,自动切换到配置的 Fallback 类型。JSON 格式,键为主类型,值为 Fallback 类型数组(按优先级排序)</small>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div class="form-group pool-section">
|
||||||
|
<label for="modelFallbackMapping" data-i18n="config.advanced.modelFallbackMapping">跨协议模型 Fallback 映射</label>
|
||||||
|
<textarea id="modelFallbackMapping" class="form-control" rows="6" data-i18n-placeholder="config.advanced.modelFallbackMappingPlaceholder" placeholder='例如:
|
||||||
|
{
|
||||||
|
"gemini-claude-opus-4-5-thinking": {
|
||||||
|
"targetProviderType": "claude-kiro-oauth",
|
||||||
|
"targetModel": "claude-opus-4-5"
|
||||||
|
}
|
||||||
|
}'></textarea>
|
||||||
|
<small class="form-text" data-i18n="config.advanced.modelFallbackMappingNote">当主 Provider 不可用时,根据模型名映射到其他协议的 Provider 和模型。JSON 格式。</small>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- 系统提示配置移到最下面 -->
|
<!-- 系统提示配置移到最下面 -->
|
||||||
<div class="form-group system-prompt-section">
|
<div class="form-group system-prompt-section">
|
||||||
<label for="systemPrompt" data-i18n="config.advanced.systemPrompt">系统提示</label>
|
<label for="systemPrompt" data-i18n="config.advanced.systemPrompt">系统提示</label>
|
||||||
|
|
@ -972,4 +984,3 @@
|
||||||
<script type="module" src="app/app.js"></script>
|
<script type="module" src="app/app.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue