// 模态框管理模块 import { showToast, getFieldLabel, getProviderTypeFields } from './utils.js'; import { handleProviderPasswordToggle } from './event-handlers.js'; /** * 显示提供商管理模态框 * @param {Object} data - 提供商数据 */ function showProviderManagerModal(data) { const { providerType, providers, totalCount, healthyCount } = data; // 移除已存在的模态框 const existingModal = document.querySelector('.provider-modal'); if (existingModal) { // 清理事件监听器 if (existingModal.cleanup) { existingModal.cleanup(); } existingModal.remove(); } // 创建模态框 const modal = document.createElement('div'); modal.className = 'provider-modal'; modal.setAttribute('data-provider-type', providerType); modal.innerHTML = `
`; // 添加到页面 document.body.appendChild(modal); // 添加模态框事件监听 addModalEventListeners(modal); } /** * 为模态框添加事件监听器 * @param {HTMLElement} modal - 模态框元素 */ function addModalEventListeners(modal) { // ESC键关闭模态框 const handleEscKey = (event) => { if (event.key === 'Escape') { modal.remove(); document.removeEventListener('keydown', handleEscKey); } }; // 点击背景关闭模态框 const handleBackgroundClick = (event) => { if (event.target === modal) { modal.remove(); document.removeEventListener('keydown', handleEscKey); } }; // 防止模态框内容区域点击时关闭模态框 const modalContent = modal.querySelector('.provider-modal-content'); const handleContentClick = (event) => { event.stopPropagation(); }; // 密码切换按钮事件处理 const handlePasswordToggleClick = (event) => { const button = event.target.closest('.password-toggle'); if (button) { event.preventDefault(); event.stopPropagation(); handleProviderPasswordToggle(button); } }; // 上传按钮事件处理 const handleUploadButtonClick = (event) => { const button = event.target.closest('.upload-btn'); if (button) { event.preventDefault(); event.stopPropagation(); const targetInputId = button.getAttribute('data-target'); const providerType = modal.getAttribute('data-provider-type'); if (targetInputId && window.fileUploadHandler) { window.fileUploadHandler.handleFileUpload(button, targetInputId, providerType); } } }; // 添加事件监听器 document.addEventListener('keydown', handleEscKey); modal.addEventListener('click', handleBackgroundClick); if (modalContent) { modalContent.addEventListener('click', handleContentClick); modalContent.addEventListener('click', handlePasswordToggleClick); modalContent.addEventListener('click', handleUploadButtonClick); } // 清理函数,在模态框关闭时调用 modal.cleanup = () => { document.removeEventListener('keydown', handleEscKey); modal.removeEventListener('click', handleBackgroundClick); if (modalContent) { modalContent.removeEventListener('click', handleContentClick); modalContent.removeEventListener('click', handlePasswordToggleClick); modalContent.removeEventListener('click', handleUploadButtonClick); } }; } /** * 关闭模态框并清理事件监听器 * @param {HTMLElement} button - 关闭按钮 */ function closeProviderModal(button) { const modal = button.closest('.provider-modal'); if (modal) { if (modal.cleanup) { modal.cleanup(); } modal.remove(); } } /** * 渲染提供商列表 * @param {Array} providers - 提供商数组 * @returns {string} HTML字符串 */ function renderProviderList(providers) { return providers.map(provider => { const isHealthy = provider.isHealthy; const isDisabled = provider.isDisabled || false; const lastUsed = provider.lastUsed ? new Date(provider.lastUsed).toLocaleString() : '从未使用'; const healthClass = isHealthy ? 'healthy' : 'unhealthy'; const disabledClass = isDisabled ? 'disabled' : ''; const healthIcon = isHealthy ? 'fas fa-check-circle text-success' : 'fas fa-exclamation-triangle text-warning'; const healthText = isHealthy ? '正常' : '异常'; const disabledText = isDisabled ? '已禁用' : '已启用'; const disabledIcon = isDisabled ? 'fas fa-ban text-muted' : 'fas fa-play text-success'; const toggleButtonText = isDisabled ? '启用' : '禁用'; const toggleButtonIcon = isDisabled ? 'fas fa-play' : 'fas fa-ban'; const toggleButtonClass = isDisabled ? 'btn-success' : 'btn-warning'; return `不支持的提供商类型
'; } 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 checkModelName = document.getElementById('newCheckModelName')?.value; const checkHealth = document.getElementById('newCheckHealth')?.value === 'true'; const providerConfig = { checkModelName: checkModelName || '', // 允许为空 checkHealth }; // 根据提供商类型收集配置 switch (providerType) { case 'openai-custom': providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || ''; providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || ''; break; case 'openaiResponses-custom': providerConfig.OPENAI_API_KEY = document.getElementById('newOpenaiApiKey')?.value || ''; providerConfig.OPENAI_BASE_URL = document.getElementById('newOpenaiBaseUrl')?.value || ''; break; case 'claude-custom': providerConfig.CLAUDE_API_KEY = document.getElementById('newClaudeApiKey')?.value || ''; providerConfig.CLAUDE_BASE_URL = document.getElementById('newClaudeBaseUrl')?.value || ''; break; case 'gemini-cli-oauth': providerConfig.PROJECT_ID = document.getElementById('newProjectId')?.value || ''; providerConfig.GEMINI_OAUTH_CREDS_FILE_PATH = document.getElementById('newGeminiOauthCredsFilePath')?.value || ''; break; case 'claude-kiro-oauth': providerConfig.KIRO_OAUTH_CREDS_FILE_PATH = document.getElementById('newKiroOauthCredsFilePath')?.value || ''; break; case 'openai-qwen-oauth': providerConfig.QWEN_OAUTH_CREDS_FILE_PATH = document.getElementById('newQwenOauthCredsFilePath')?.value || ''; break; } 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'); } } // 导出所有函数,并挂载到window对象供HTML调用 export { showProviderManagerModal, closeProviderModal, toggleProviderDetails, editProvider, cancelEdit, saveProvider, deleteProvider, refreshProviderConfig, showAddProviderForm, addProvider, toggleProviderStatus }; // 将函数挂载到window对象 window.closeProviderModal = closeProviderModal; window.toggleProviderDetails = toggleProviderDetails; window.editProvider = editProvider; window.cancelEdit = cancelEdit; window.saveProvider = saveProvider; window.deleteProvider = deleteProvider; window.showAddProviderForm = showAddProviderForm; window.addProvider = addProvider; window.toggleProviderStatus = toggleProviderStatus;