// 模态框管理模块 import { showToast, getFieldLabel, getProviderTypeFields } from './utils.js'; import { handleProviderPasswordToggle } from './event-handlers.js'; // 分页配置 const PROVIDERS_PER_PAGE = 5; let currentPage = 1; let currentProviders = []; let currentProviderType = ''; let cachedModels = []; // 缓存模型列表 /** * 显示提供商管理模态框 * @param {Object} data - 提供商数据 */ function showProviderManagerModal(data) { const { providerType, providers, totalCount, healthyCount } = data; // 保存当前数据用于分页 currentProviders = providers; currentProviderType = providerType; currentPage = 1; cachedModels = []; // 移除已存在的模态框 const existingModal = document.querySelector('.provider-modal'); if (existingModal) { // 清理事件监听器 if (existingModal.cleanup) { existingModal.cleanup(); } existingModal.remove(); } const totalPages = Math.ceil(providers.length / PROVIDERS_PER_PAGE); // 创建模态框 const modal = document.createElement('div'); modal.className = 'provider-modal'; modal.setAttribute('data-provider-type', providerType); modal.innerHTML = `
`; // 添加到页面 document.body.appendChild(modal); // 添加模态框事件监听 addModalEventListeners(modal); // 先获取该提供商类型的模型列表(只调用一次API) const pageProviders = providers.slice(0, PROVIDERS_PER_PAGE); loadModelsForProviderType(providerType, pageProviders); } /** * 渲染分页控件 * @param {number} currentPage - 当前页码 * @param {number} totalPages - 总页数 * @param {number} totalItems - 总条目数 * @param {string} position - 位置标识 (top/bottom) * @returns {string} HTML字符串 */ function renderPagination(page, totalPages, totalItems, position = 'top') { const startItem = (page - 1) * PROVIDERS_PER_PAGE + 1; const endItem = Math.min(page * PROVIDERS_PER_PAGE, totalItems); // 生成页码按钮 let pageButtons = ''; const maxVisiblePages = 5; let startPage = Math.max(1, page - Math.floor(maxVisiblePages / 2)); let endPage = Math.min(totalPages, startPage + maxVisiblePages - 1); if (endPage - startPage < maxVisiblePages - 1) { startPage = Math.max(1, endPage - maxVisiblePages + 1); } if (startPage > 1) { pageButtons += ``; if (startPage > 2) { pageButtons += `...`; } } for (let i = startPage; i <= endPage; i++) { pageButtons += ``; } if (endPage < totalPages) { if (endPage < totalPages - 1) { pageButtons += `...`; } pageButtons += ``; } return `clientId 和 clientSecret 字段' : ''}
clientId 和 clientSecret 字段' : ''}
不支持的提供商类型
'; } configFields.innerHTML = fields; } /** * 为添加新提供商表单中的密码切换按钮绑定事件监听器 * @param {HTMLElement} form - 表单元素 */ function bindAddFormPasswordToggleListeners(form) { const passwordToggles = form.querySelectorAll('.password-toggle'); passwordToggles.forEach(button => { button.addEventListener('click', function() { const targetId = this.getAttribute('data-target'); const input = document.getElementById(targetId); const icon = this.querySelector('i'); if (!input || !icon) return; if (input.type === 'password') { input.type = 'text'; icon.className = 'fas fa-eye-slash'; } else { input.type = 'password'; icon.className = 'fas fa-eye'; } }); }); } /** * 添加新提供商 * @param {string} providerType - 提供商类型 */ async function addProvider(providerType) { const customName = document.getElementById('newCustomName')?.value; const checkModelName = document.getElementById('newCheckModelName')?.value; const checkHealth = document.getElementById('newCheckHealth')?.value === 'true'; const providerConfig = { customName: customName || '', // 允许为空 checkModelName: checkModelName || '', // 允许为空 checkHealth }; // 根据提供商类型动态收集配置字段(自动匹配 utils.js 中的定义) const allFields = getProviderTypeFields(providerType); allFields.forEach(field => { const element = document.getElementById(`new${field.id}`); if (element) { providerConfig[field.id] = element.value || ''; } }); try { await window.apiClient.post('/providers', { providerType, providerConfig }); await window.apiClient.post('/reload-config'); showToast('提供商配置添加成功', 'success'); // 移除添加表单 const form = document.querySelector('.add-provider-form'); if (form) { form.remove(); } // 重新获取最新配置数据 await refreshProviderConfig(providerType); } catch (error) { console.error('Failed to add provider:', error); showToast('添加失败: ' + error.message, 'error'); } } /** * 切换提供商禁用/启用状态 * @param {string} uuid - 提供商UUID * @param {Event} event - 事件对象 */ async function toggleProviderStatus(uuid, event) { event.stopPropagation(); const providerDetail = event.target.closest('.provider-item-detail'); const providerType = providerDetail.closest('.provider-modal').getAttribute('data-provider-type'); const currentProvider = providerDetail.closest('.provider-modal').querySelector(`[data-uuid="${uuid}"]`); // 获取当前提供商信息 const isCurrentlyDisabled = currentProvider.classList.contains('disabled'); const action = isCurrentlyDisabled ? 'enable' : 'disable'; const confirmMessage = isCurrentlyDisabled ? `确定要启用这个提供商配置吗?` : `确定要禁用这个提供商配置吗?禁用后该提供商将不会被选中使用。`; if (!confirm(confirmMessage)) { return; } try { await window.apiClient.post(`/providers/${encodeURIComponent(providerType)}/${uuid}/${action}`, { action }); await window.apiClient.post('/reload-config'); showToast(`提供商${isCurrentlyDisabled ? '启用' : '禁用'}成功`, 'success'); // 重新获取该提供商类型的最新配置 await refreshProviderConfig(providerType); } catch (error) { console.error('Failed to toggle provider status:', error); showToast(`操作失败: ${error.message}`, 'error'); } } /** * 重置所有提供商的健康状态 * @param {string} providerType - 提供商类型 */ async function resetAllProvidersHealth(providerType) { if (!confirm(`确定要将 ${providerType} 的所有节点重置为健康状态吗?\n\n这将清除所有节点的错误计数和错误时间。`)) { return; } try { showToast('正在重置健康状态...', 'info'); const response = await window.apiClient.post( `/providers/${encodeURIComponent(providerType)}/reset-health`, {} ); if (response.success) { showToast(`成功重置 ${response.resetCount} 个节点的健康状态`, 'success'); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast('重置健康状态失败', 'error'); } } catch (error) { console.error('重置健康状态失败:', error); showToast(`重置健康状态失败: ${error.message}`, 'error'); } } /** * 执行健康检测 * @param {string} providerType - 提供商类型 */ async function performHealthCheck(providerType) { if (!confirm(`确定要对 ${providerType} 的所有节点执行健康检测吗?\n\n这将向每个节点发送测试请求来验证其可用性。`)) { return; } try { showToast('正在执行健康检测,请稍候...', 'info'); const response = await window.apiClient.post( `/providers/${encodeURIComponent(providerType)}/health-check`, {} ); if (response.success) { const { successCount, failCount, totalCount, results } = response; // 统计跳过的数量(checkHealth 未启用的) const skippedCount = results ? results.filter(r => r.success === null).length : 0; let message = `健康检测完成: ${successCount} 健康`; if (failCount > 0) message += `, ${failCount} 异常`; if (skippedCount > 0) message += `, ${skippedCount} 跳过(未启用)`; showToast(message, failCount > 0 ? 'warning' : 'success'); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast('健康检测失败', 'error'); } } catch (error) { console.error('健康检测失败:', error); showToast(`健康检测失败: ${error.message}`, 'error'); } } /** * 渲染不支持的模型选择器(不调用API,直接使用传入的模型列表) * @param {string} uuid - 提供商UUID * @param {Array} models - 模型列表 * @param {Array} notSupportedModels - 当前不支持的模型列表 */ function renderNotSupportedModelsSelector(uuid, models, notSupportedModels = []) { const container = document.querySelector(`.not-supported-models-container[data-uuid="${uuid}"]`); if (!container) return; if (models.length === 0) { container.innerHTML = '