AIClient-2-API/static/app/config-manager.js
leonai bd8f03b68e feat(config): 添加凭证切换最大重试次数配置项
1. 新增 CREDENTIAL_SWITCH_MAX_RETRIES 配置项,默认值为 5
2. 在 config-manager.js 中添加配置初始化
3. 在 config-api.js 中支持配置的读取和更新
4. 在 common.js 中使用该配置控制凭证切换重试次数
5. 在前端配置页面添加对应的输入控件
2026-01-13 19:00:00 +08:00

275 lines
13 KiB
JavaScript

// 配置管理模块
import { showToast, formatUptime } from './utils.js';
import { handleProviderChange, handleGeminiCredsTypeChange, handleKiroCredsTypeChange } from './event-handlers.js';
import { loadProviders } from './provider-manager.js';
import { t } from './i18n.js';
/**
* 加载配置
*/
async function loadConfiguration() {
try {
const data = await window.apiClient.get('/config');
// 基础配置
const apiKeyEl = document.getElementById('apiKey');
const hostEl = document.getElementById('host');
const portEl = document.getElementById('port');
const modelProviderEl = document.getElementById('modelProvider');
const systemPromptEl = document.getElementById('systemPrompt');
if (apiKeyEl) apiKeyEl.value = data.REQUIRED_API_KEY || '';
if (hostEl) hostEl.value = data.HOST || '127.0.0.1';
if (portEl) portEl.value = data.SERVER_PORT || 3000;
if (modelProviderEl) {
// 处理多选 MODEL_PROVIDER (标签按钮)
const providers = Array.isArray(data.DEFAULT_MODEL_PROVIDERS)
? data.DEFAULT_MODEL_PROVIDERS
: (typeof data.MODEL_PROVIDER === 'string' ? data.MODEL_PROVIDER.split(',') : []);
const tags = modelProviderEl.querySelectorAll('.provider-tag');
tags.forEach(tag => {
const value = tag.getAttribute('data-value');
if (providers.includes(value)) {
tag.classList.add('selected');
} else {
tag.classList.remove('selected');
}
});
// 如果没有任何选中的,默认选中第一个(保持兼容性)
const anySelected = Array.from(tags).some(tag => tag.classList.contains('selected'));
if (!anySelected && tags.length > 0) {
tags[0].classList.add('selected');
}
// 为标签按钮添加点击事件监听
tags.forEach(tag => {
// 移除旧的监听器(通过克隆节点)
const newTag = tag.cloneNode(true);
tag.parentNode.replaceChild(newTag, tag);
newTag.addEventListener('click', (e) => {
e.preventDefault();
const isSelected = newTag.classList.contains('selected');
const selectedCount = modelProviderEl.querySelectorAll('.provider-tag.selected').length;
// 如果当前是选中状态且只剩一个选中的,不允许取消
if (isSelected && selectedCount === 1) {
showToast(t('common.warning'), t('config.modelProviderRequired'), 'warning');
return;
}
// 切换选中状态
newTag.classList.toggle('selected');
});
});
}
if (systemPromptEl) systemPromptEl.value = data.systemPrompt || '';
// 高级配置参数
const systemPromptFilePathEl = document.getElementById('systemPromptFilePath');
const systemPromptModeEl = document.getElementById('systemPromptMode');
const promptLogBaseNameEl = document.getElementById('promptLogBaseName');
const promptLogModeEl = document.getElementById('promptLogMode');
const requestMaxRetriesEl = document.getElementById('requestMaxRetries');
const requestBaseDelayEl = document.getElementById('requestBaseDelay');
const cronNearMinutesEl = document.getElementById('cronNearMinutes');
const cronRefreshTokenEl = document.getElementById('cronRefreshToken');
const providerPoolsFilePathEl = document.getElementById('providerPoolsFilePath');
const maxErrorCountEl = document.getElementById('maxErrorCount');
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 (systemPromptModeEl) systemPromptModeEl.value = data.SYSTEM_PROMPT_MODE || 'append';
if (promptLogBaseNameEl) promptLogBaseNameEl.value = data.PROMPT_LOG_BASE_NAME || 'prompt_log';
if (promptLogModeEl) promptLogModeEl.value = data.PROMPT_LOG_MODE || 'none';
if (requestMaxRetriesEl) requestMaxRetriesEl.value = data.REQUEST_MAX_RETRIES || 3;
if (requestBaseDelayEl) requestBaseDelayEl.value = data.REQUEST_BASE_DELAY || 1000;
// 坏凭证切换最大重试次数
const credentialSwitchMaxRetriesEl = document.getElementById('credentialSwitchMaxRetries');
if (credentialSwitchMaxRetriesEl) credentialSwitchMaxRetriesEl.value = data.CREDENTIAL_SWITCH_MAX_RETRIES || 5;
if (cronNearMinutesEl) cronNearMinutesEl.value = data.CRON_NEAR_MINUTES || 1;
if (cronRefreshTokenEl) cronRefreshTokenEl.checked = data.CRON_REFRESH_TOKEN || false;
if (providerPoolsFilePathEl) providerPoolsFilePathEl.value = data.PROVIDER_POOLS_FILE_PATH;
if (maxErrorCountEl) maxErrorCountEl.value = data.MAX_ERROR_COUNT || 3;
// 加载 Fallback 链配置
if (providerFallbackChainEl) {
if (data.providerFallbackChain && typeof data.providerFallbackChain === 'object') {
providerFallbackChainEl.value = JSON.stringify(data.providerFallbackChain, null, 2);
} else {
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');
if (proxyUrlEl) proxyUrlEl.value = data.PROXY_URL || '';
// 加载启用代理的提供商 (标签按钮)
const proxyProvidersEl = document.getElementById('proxyProviders');
if (proxyProvidersEl) {
const enabledProviders = data.PROXY_ENABLED_PROVIDERS || [];
const proxyTags = proxyProvidersEl.querySelectorAll('.provider-tag');
proxyTags.forEach(tag => {
const value = tag.getAttribute('data-value');
if (enabledProviders.includes(value)) {
tag.classList.add('selected');
} else {
tag.classList.remove('selected');
}
});
// 为代理提供商标签按钮添加点击事件监听
proxyTags.forEach(tag => {
// 移除旧的监听器(通过克隆节点)
const newTag = tag.cloneNode(true);
tag.parentNode.replaceChild(newTag, tag);
newTag.addEventListener('click', (e) => {
e.preventDefault();
// 代理提供商可以全部取消选择,所以不需要检查最少选择数量
newTag.classList.toggle('selected');
});
});
}
} catch (error) {
console.error('Failed to load configuration:', error);
}
}
/**
* 保存配置
*/
async function saveConfiguration() {
const modelProviderEl = document.getElementById('modelProvider');
let selectedProviders = [];
if (modelProviderEl) {
// 从标签按钮中获取选中的提供商
selectedProviders = Array.from(modelProviderEl.querySelectorAll('.provider-tag.selected'))
.map(tag => tag.getAttribute('data-value'));
}
// 校验:必须至少勾选一个
if (selectedProviders.length === 0) {
showToast(t('common.error'), t('config.modelProviderRequired'), 'error');
return;
}
const config = {
REQUIRED_API_KEY: document.getElementById('apiKey')?.value || '',
HOST: document.getElementById('host')?.value || '127.0.0.1',
SERVER_PORT: parseInt(document.getElementById('port')?.value || 3000),
MODEL_PROVIDER: selectedProviders.length > 0 ? selectedProviders.join(',') : 'gemini-cli-oauth',
systemPrompt: document.getElementById('systemPrompt')?.value || '',
};
// 获取后台登录密码(如果有输入)
const adminPassword = document.getElementById('adminPassword')?.value || '';
// 保存高级配置参数
config.SYSTEM_PROMPT_FILE_PATH = document.getElementById('systemPromptFilePath')?.value || 'configs/input_system_prompt.txt';
config.SYSTEM_PROMPT_MODE = document.getElementById('systemPromptMode')?.value || 'append';
config.PROMPT_LOG_BASE_NAME = document.getElementById('promptLogBaseName')?.value || '';
config.PROMPT_LOG_MODE = document.getElementById('promptLogMode')?.value || '';
config.REQUEST_MAX_RETRIES = parseInt(document.getElementById('requestMaxRetries')?.value || 3);
config.REQUEST_BASE_DELAY = parseInt(document.getElementById('requestBaseDelay')?.value || 1000);
config.CREDENTIAL_SWITCH_MAX_RETRIES = parseInt(document.getElementById('credentialSwitchMaxRetries')?.value || 5);
config.CRON_NEAR_MINUTES = parseInt(document.getElementById('cronNearMinutes')?.value || 1);
config.CRON_REFRESH_TOKEN = document.getElementById('cronRefreshToken')?.checked || false;
config.PROVIDER_POOLS_FILE_PATH = document.getElementById('providerPoolsFilePath')?.value || '';
config.MAX_ERROR_COUNT = parseInt(document.getElementById('maxErrorCount')?.value || 3);
// 保存 Fallback 链配置
const fallbackChainValue = document.getElementById('providerFallbackChain')?.value?.trim() || '';
if (fallbackChainValue) {
try {
config.providerFallbackChain = JSON.parse(fallbackChainValue);
} catch (e) {
showToast(t('common.error'), t('config.advanced.fallbackChainInvalid') || 'Fallback 链配置格式无效,请输入有效的 JSON', 'error');
return;
}
} else {
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;
// 获取启用代理的提供商列表 (从标签按钮)
const proxyProvidersEl = document.getElementById('proxyProviders');
if (proxyProvidersEl) {
config.PROXY_ENABLED_PROVIDERS = Array.from(proxyProvidersEl.querySelectorAll('.provider-tag.selected'))
.map(tag => tag.getAttribute('data-value'));
} else {
config.PROXY_ENABLED_PROVIDERS = [];
}
try {
await window.apiClient.post('/config', config);
// 如果输入了新密码,单独保存密码
if (adminPassword) {
try {
await window.apiClient.post('/admin-password', { password: adminPassword });
// 清空密码输入框
const adminPasswordEl = document.getElementById('adminPassword');
if (adminPasswordEl) adminPasswordEl.value = '';
showToast(t('common.success'), t('common.passwordUpdated'), 'success');
} catch (pwdError) {
console.error('Failed to save admin password:', pwdError);
showToast(t('common.error'), t('common.error') + ': ' + pwdError.message, 'error');
}
}
await window.apiClient.post('/reload-config');
showToast(t('common.success'), t('common.configSaved'), 'success');
// 检查当前是否在提供商池管理页面,如果是则刷新数据
const providersSection = document.getElementById('providers');
if (providersSection && providersSection.classList.contains('active')) {
// 当前在提供商池页面,刷新数据
await loadProviders();
showToast(t('common.success'), t('common.providerPoolRefreshed'), 'success');
}
} catch (error) {
console.error('Failed to save configuration:', error);
showToast(t('common.error'), t('common.error') + ': ' + error.message, 'error');
}
}
export {
loadConfiguration,
saveConfiguration
};