// 模态框管理模块 import { showToast, getFieldLabel, getProviderTypeFields } from './utils.js'; import { handleProviderPasswordToggle } from './event-handlers.js'; import { t } from './i18n.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 `OpenAI Codex 需要通过 OAuth 授权获取访问令牌,无法手动填写凭据。请点击下方按钮进行授权。
${t('modal.provider.noProviderType')}
`; } 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(t('common.success'), t('modal.provider.add.success'), '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(t('common.error'), t('modal.provider.add.failed') + ': ' + 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 ? t('modal.provider.enableConfirm') : t('modal.provider.disableConfirm'); if (!confirm(confirmMessage)) { return; } try { await window.apiClient.post(`/providers/${encodeURIComponent(providerType)}/${uuid}/${action}`, { action }); await window.apiClient.post('/reload-config'); showToast(t('common.success'), t('common.success'), 'success'); // 重新获取该提供商类型的最新配置 await refreshProviderConfig(providerType); } catch (error) { console.error('Failed to toggle provider status:', error); showToast(t('common.error'), t('common.error') + ': ' + error.message, 'error'); } } /** * 重置所有提供商的健康状态 * @param {string} providerType - 提供商类型 */ async function resetAllProvidersHealth(providerType) { if (!confirm(t('modal.provider.resetHealthConfirm', {type: providerType}))) { return; } try { showToast(t('common.info'), t('modal.provider.resetHealth') + '...', 'info'); const response = await window.apiClient.post( `/providers/${encodeURIComponent(providerType)}/reset-health`, {} ); if (response.success) { showToast(t('common.success'), t('modal.provider.resetHealth.success', { count: response.resetCount }), 'success'); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast(t('common.error'), t('modal.provider.resetHealth.failed'), 'error'); } } catch (error) { console.error('重置健康状态失败:', error); showToast(t('common.error'), t('modal.provider.resetHealth.failed') + ': ' + error.message, 'error'); } } /** * 执行健康检测 * @param {string} providerType - 提供商类型 */ async function performHealthCheck(providerType) { if (!confirm(t('modal.provider.healthCheckConfirm', {type: providerType}))) { return; } try { showToast(t('common.info'), t('modal.provider.healthCheck') + '...', '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 = `${t('modal.provider.healthCheck.complete', { success: successCount })}`; if (failCount > 0) message += t('modal.provider.healthCheck.abnormal', { fail: failCount }); if (skippedCount > 0) message += t('modal.provider.healthCheck.skipped', { skipped: skippedCount }); showToast(t('common.info'), message, failCount > 0 ? 'warning' : 'success'); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast(t('common.error'), t('modal.provider.healthCheck') + ' ' + t('common.error'), 'error'); } } catch (error) { console.error('健康检测失败:', error); showToast(t('common.error'), t('modal.provider.healthCheck') + ' ' + t('common.error') + ': ' + error.message, 'error'); } } /** * 刷新提供商UUID * @param {string} uuid - 提供商UUID * @param {Event} event - 事件对象 */ async function refreshProviderUuid(uuid, event) { event.stopPropagation(); if (!confirm(t('modal.provider.refreshUuidConfirm', { oldUuid: uuid }))) { return; } const providerDetail = event.target.closest('.provider-item-detail'); const providerType = providerDetail.closest('.provider-modal').getAttribute('data-provider-type'); try { const response = await window.apiClient.post( `/providers/${encodeURIComponent(providerType)}/${uuid}/refresh-uuid`, {} ); if (response.success) { showToast(t('common.success'), t('modal.provider.refreshUuid.success', { oldUuid: response.oldUuid, newUuid: response.newUuid }), 'success'); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast(t('common.error'), t('modal.provider.refreshUuid.failed'), 'error'); } } catch (error) { console.error('刷新uuid失败:', error); showToast(t('common.error'), t('modal.provider.refreshUuid.failed') + ': ' + error.message, 'error'); } } /** * 删除所有不健康的提供商节点 * @param {string} providerType - 提供商类型 */ async function deleteUnhealthyProviders(providerType) { // 先获取不健康节点数量 const unhealthyCount = currentProviders.filter(p => !p.isHealthy).length; if (unhealthyCount === 0) { showToast(t('common.info'), t('modal.provider.deleteUnhealthy.noUnhealthy'), 'info'); return; } if (!confirm(t('modal.provider.deleteUnhealthyConfirm', { type: providerType, count: unhealthyCount }))) { return; } try { showToast(t('common.info'), t('modal.provider.deleteUnhealthy.deleting'), 'info'); const response = await window.apiClient.delete( `/providers/${encodeURIComponent(providerType)}/delete-unhealthy` ); if (response.success) { showToast( t('common.success'), t('modal.provider.deleteUnhealthy.success', { count: response.deletedCount }), 'success' ); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast(t('common.error'), t('modal.provider.deleteUnhealthy.failed'), 'error'); } } catch (error) { console.error('删除不健康节点失败:', error); showToast(t('common.error'), t('modal.provider.deleteUnhealthy.failed') + ': ' + error.message, 'error'); } } /** * 批量刷新不健康节点的UUID * @param {string} providerType - 提供商类型 */ async function refreshUnhealthyUuids(providerType) { // 先获取不健康节点数量 const unhealthyCount = currentProviders.filter(p => !p.isHealthy).length; if (unhealthyCount === 0) { showToast(t('common.info'), t('modal.provider.refreshUnhealthyUuids.noUnhealthy'), 'info'); return; } if (!confirm(t('modal.provider.refreshUnhealthyUuidsConfirm', { type: providerType, count: unhealthyCount }))) { return; } try { showToast(t('common.info'), t('modal.provider.refreshUnhealthyUuids.refreshing'), 'info'); const response = await window.apiClient.post( `/providers/${encodeURIComponent(providerType)}/refresh-unhealthy-uuids` ); if (response.success) { showToast( t('common.success'), t('modal.provider.refreshUnhealthyUuids.success', { count: response.refreshedCount }), 'success' ); // 重新加载配置 await window.apiClient.post('/reload-config'); // 刷新提供商配置显示 await refreshProviderConfig(providerType); } else { showToast(t('common.error'), t('modal.provider.refreshUnhealthyUuids.failed'), 'error'); } } catch (error) { console.error('刷新不健康节点UUID失败:', error); showToast(t('common.error'), t('modal.provider.refreshUnhealthyUuids.failed') + ': ' + 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 = `