// 事件监听器模块 import { elements, autoScroll, setAutoScroll, clearLogs } from './constants.js'; import { showToast } from './utils.js'; import { fileUploadHandler } from './file-upload.js'; import { t } from './i18n.js'; import { checkUpdate, performUpdate } from './provider-manager.js'; /** * 初始化所有事件监听器 */ function initEventListeners() { // 重启按钮 if (elements.restartBtn) { elements.restartBtn.addEventListener('click', handleRestart); } // 清空日志 if (elements.clearLogsBtn) { elements.clearLogsBtn.addEventListener('click', async () => { // 显示确认对话框,明确提示会清空本地日志文件 const confirmed = confirm(t('logs.clear.confirm.msg')); if (!confirmed) { return; } try { const token = window.authManager.getToken(); if (!token) { showToast(t('common.error'), '请先登录', 'error'); return; } // 调用后端 API 清空日志文件 const response = await fetch(`${window.location.origin}/api/system/clear-log`, { method: 'POST', headers: { 'Authorization': `Bearer ${token}`, 'Content-Type': 'application/json' } }); if (response.status === 401) { showToast(t('common.error'), '认证失败,请重新登录', 'error'); window.authManager.clearToken(); window.location.href = '/login.html'; return; } const result = await response.json(); if (result.success) { // 清空前端日志显示 clearLogs(); if (elements.logsContainer) { elements.logsContainer.innerHTML = ''; } // 显示成功提示,明确说明已清空本地日志文件 showToast( t('logs.clear.success.title'), t('logs.clear.success.msg'), 'success', 5000 // 显示 5 秒 ); } else { showToast(t('common.error'), t('logs.clear.failed'), 'error'); } } catch (error) { console.error('清空日志失败:', error); showToast(t('common.error'), t('logs.clear.failed') + ': ' + error.message, 'error'); } }); } // 下载日志 if (elements.downloadLogsBtn) { elements.downloadLogsBtn.addEventListener('click', async () => { try { const token = window.authManager.getToken(); if (!token) { showToast(t('common.error'), '请先登录', 'error'); return; } // 使用带认证的方式下载文件 const url = `${window.location.origin}/api/system/download-log`; const response = await fetch(url, { method: 'GET', headers: { 'Authorization': `Bearer ${token}` } }); if (response.status === 401) { showToast(t('common.error'), '认证失败,请重新登录', 'error'); window.authManager.clearToken(); window.location.href = '/login.html'; return; } if (!response.ok) { const errorData = await response.json(); showToast(t('common.error'), errorData.error?.message || '下载失败', 'error'); return; } // 获取文件名 const contentDisposition = response.headers.get('Content-Disposition'); let filename = 'app.log'; if (contentDisposition) { const matches = /filename="?([^"]+)"?/.exec(contentDisposition); if (matches && matches[1]) { filename = matches[1]; } } // 下载文件 const blob = await response.blob(); const downloadUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = downloadUrl; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(downloadUrl); showToast(t('common.success'), '日志下载成功', 'success'); } catch (error) { console.error('下载日志失败:', error); showToast(t('common.error'), '下载失败: ' + error.message, 'error'); } }); } // 自动滚动切换 if (elements.toggleAutoScrollBtn) { elements.toggleAutoScrollBtn.addEventListener('click', () => { const newAutoScroll = !autoScroll; setAutoScroll(newAutoScroll); elements.toggleAutoScrollBtn.dataset.enabled = newAutoScroll; const statusText = newAutoScroll ? t('logs.autoScroll.on') : t('logs.autoScroll.off'); elements.toggleAutoScrollBtn.innerHTML = ` ${statusText} `; }); } // 保存配置 if (elements.saveConfigBtn) { elements.saveConfigBtn.addEventListener('click', saveConfiguration); } // 重置配置 if (elements.resetConfigBtn) { elements.resetConfigBtn.addEventListener('click', loadInitialData); } // 模型提供商切换 if (elements.modelProvider) { elements.modelProvider.addEventListener('change', handleProviderChange); } // Gemini凭据类型切换 document.querySelectorAll('input[name="geminiCredsType"]').forEach(radio => { radio.addEventListener('change', handleGeminiCredsTypeChange); }); // Kiro凭据类型切换 document.querySelectorAll('input[name="kiroCredsType"]').forEach(radio => { radio.addEventListener('change', handleKiroCredsTypeChange); }); // 密码显示/隐藏切换 document.querySelectorAll('.password-toggle').forEach(button => { button.addEventListener('click', handlePasswordToggle); }); // 生成凭据按钮监听 document.querySelectorAll('.generate-creds-btn').forEach(button => { button.addEventListener('click', handleGenerateCreds); }); // 提供商池配置监听 // const providerPoolsInput = document.getElementById('providerPoolsFilePath'); // if (providerPoolsInput) { // providerPoolsInput.addEventListener('input', handleProviderPoolsConfigChange); // } // 检查更新按钮 const checkUpdateBtn = document.getElementById('checkUpdateBtn'); if (checkUpdateBtn) { checkUpdateBtn.addEventListener('click', () => checkUpdate(false)); } // 执行更新按钮 const performUpdateBtn = document.getElementById('performUpdateBtn'); if (performUpdateBtn) { performUpdateBtn.addEventListener('click', performUpdate); } // 日志容器滚动 if (elements.logsContainer) { elements.logsContainer.addEventListener('scroll', () => { if (autoScroll) { const isAtBottom = elements.logsContainer.scrollTop + elements.logsContainer.clientHeight >= elements.logsContainer.scrollHeight - 5; if (!isAtBottom) { setAutoScroll(false); elements.toggleAutoScrollBtn.dataset.enabled = false; elements.toggleAutoScrollBtn.innerHTML = ` ${t('logs.autoScroll.off')} `; } } }); } } /** * 提供商配置切换处理 */ function handleProviderChange() { const selectedProvider = elements.modelProvider?.value; if (!selectedProvider) return; const allProviderConfigs = document.querySelectorAll('.provider-config'); // 隐藏所有提供商配置 allProviderConfigs.forEach(config => { config.style.display = 'none'; }); // 显示当前选中的提供商配置 const targetConfig = document.querySelector(`[data-provider="${selectedProvider}"]`); if (targetConfig) { targetConfig.style.display = 'block'; } } /** * Gemini凭据类型切换 * @param {Event} event - 事件对象 */ function handleGeminiCredsTypeChange(event) { const selectedType = event.target.value; const base64Group = document.getElementById('geminiCredsBase64Group'); const fileGroup = document.getElementById('geminiCredsFileGroup'); if (selectedType === 'base64') { if (base64Group) base64Group.style.display = 'block'; if (fileGroup) fileGroup.style.display = 'none'; } else { if (base64Group) base64Group.style.display = 'none'; if (fileGroup) fileGroup.style.display = 'block'; } } /** * Kiro凭据类型切换 * @param {Event} event - 事件对象 */ function handleKiroCredsTypeChange(event) { const selectedType = event.target.value; const base64Group = document.getElementById('kiroCredsBase64Group'); const fileGroup = document.getElementById('kiroCredsFileGroup'); if (selectedType === 'base64') { if (base64Group) base64Group.style.display = 'block'; if (fileGroup) fileGroup.style.display = 'none'; } else { if (base64Group) base64Group.style.display = 'none'; if (fileGroup) fileGroup.style.display = 'block'; } } /** * 密码显示/隐藏切换处理 * @param {Event} event - 事件对象 */ function handlePasswordToggle(event) { const button = event.target.closest('.password-toggle'); if (!button) return; const targetId = button.getAttribute('data-target'); const input = document.getElementById(targetId); const icon = button.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 {Event} event - 事件对象 */ async function handleGenerateCreds(event) { const button = event.target.closest('.generate-creds-btn'); if (!button) return; const providerType = button.getAttribute('data-provider'); const targetInputId = button.getAttribute('data-target'); try { // 如果是 Kiro OAuth,先显示认证方式选择对话框 if (providerType === 'claude-kiro-oauth') { const modal = document.createElement('div'); modal.className = 'modal-overlay'; modal.style.display = 'flex'; modal.innerHTML = `
`; document.body.appendChild(modal); const closeModal = () => modal.remove(); modal.querySelector('.modal-close').onclick = closeModal; modal.querySelector('.modal-cancel').onclick = closeModal; modal.querySelectorAll('.auth-method-btn').forEach(btn => { btn.onclick = async () => { const method = btn.dataset.method; closeModal(); await proceedWithAuth(providerType, targetInputId, { method }); }; }); return; } await proceedWithAuth(providerType, targetInputId, {}); } catch (error) { console.error('生成凭据失败:', error); showToast(t('common.error'), t('modal.provider.auth.failed') + `: ${error.message}`, 'error'); } } /** * 实际执行授权逻辑 */ async function proceedWithAuth(providerType, targetInputId, extraOptions = {}) { if (window.executeGenerateAuthUrl) { await window.executeGenerateAuthUrl(providerType, { targetInputId, ...extraOptions }); } else { console.error('executeGenerateAuthUrl not found'); } } /** * 提供商池配置变化处理 * @param {Event} event - 事件对象 */ function handleProviderPoolsConfigChange(event) { const filePath = event.target.value.trim(); const providersMenuItem = document.querySelector('.nav-item[data-section="providers"]'); if (filePath) { // 显示提供商池菜单 if (providersMenuItem) providersMenuItem.style.display = 'flex'; } else { // 隐藏提供商池菜单 if (providersMenuItem) providersMenuItem.style.display = 'none'; // 如果当前在提供商池页面,切换到仪表盘 if (providersMenuItem && providersMenuItem.classList.contains('active')) { const dashboardItem = document.querySelector('.nav-item[data-section="dashboard"]'); const dashboardSection = document.getElementById('dashboard'); // 更新导航状态 document.querySelectorAll('.nav-item').forEach(nav => nav.classList.remove('active')); document.querySelectorAll('.section').forEach(section => section.classList.remove('active')); if (dashboardItem) dashboardItem.classList.add('active'); if (dashboardSection) dashboardSection.classList.add('active'); } } } /** * 密码显示/隐藏切换处理(用于模态框中的密码输入框) * @param {HTMLElement} button - 按钮元素 */ function handleProviderPasswordToggle(button) { const targetKey = button.getAttribute('data-target'); const input = button.parentNode.querySelector(`input[data-config-key="${targetKey}"]`); const icon = button.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'; } } // 数据加载函数(需要从主模块导入) let loadInitialData; let saveConfiguration; let reloadConfig; // 当前服务模式(由 provider-manager.js 设置) let currentServiceMode = 'worker'; /** * 设置当前服务模式 * @param {string} mode - 服务模式 ('worker' 或 'standalone') */ export function setServiceMode(mode) { currentServiceMode = mode; } /** * 获取当前服务模式 * @returns {string} 当前服务模式 */ export function getServiceMode() { return currentServiceMode; } // 重启/重载服务处理函数 async function handleRestart() { try { // 根据服务模式执行不同操作 if (currentServiceMode === 'standalone') { // 独立模式:执行重载配置 await handleReloadConfig(); } else { // 子进程模式:执行重启服务 await handleRestartService(); } } catch (error) { console.error('Operation failed:', error); const errorKey = currentServiceMode === 'standalone' ? 'header.reload.failed' : 'header.restart.failed'; showToast(t('common.error'), t(errorKey) + ': ' + error.message, 'error'); } } /** * 重载配置(独立模式) */ async function handleReloadConfig() { // 确认重载操作 if (!confirm(t('header.reload.confirm'))) { return; } showToast(t('common.info'), t('header.reload.requesting'), 'info'); // 先刷新基础数据 if (loadInitialData) { loadInitialData(); } // 如果reloadConfig函数可用,则也刷新配置 if (reloadConfig) { await reloadConfig(); } } /** * 重启服务(子进程模式) */ async function handleRestartService() { // 确认重启操作 if (!confirm(t('header.restart.confirm'))) { return; } showToast(t('common.info'), t('header.restart.requesting'), 'info'); const result = await window.apiClient.post('/restart-service'); if (result.success) { showToast(t('common.success'), result.message || t('header.restart.success'), 'success'); // 如果是 worker 模式,服务会自动重启,等待几秒后刷新页面 if (result.mode === 'worker') { setTimeout(() => { showToast(t('common.info'), t('header.restart.reconnecting'), 'info'); // 等待服务重启后刷新页面 setTimeout(() => { window.location.reload(); }, 3000); }, 2000); } } else { // 显示错误信息 const errorMsg = result.message || result.error?.message || t('header.restart.failed'); showToast(t('common.error'), errorMsg, 'error'); // 如果是独立模式,显示提示 if (result.mode === 'standalone') { showToast(t('common.info'), result.hint, 'warning'); } } } export function setDataLoaders(dataLoader, configSaver) { loadInitialData = dataLoader; saveConfiguration = configSaver; } export function setReloadConfig(configReloader) { reloadConfig = configReloader; } export { initEventListeners, handleProviderChange, handleGeminiCredsTypeChange, handleKiroCredsTypeChange, handlePasswordToggle, handleProviderPoolsConfigChange, handleProviderPasswordToggle };