'use strict'; (function () { const views = { crawl: document.getElementById('viewCrawl'), gen: document.getElementById('viewGen'), tasks: document.getElementById('viewTasks'), history: document.getElementById('viewHistory'), settings: document.getElementById('viewSettings'), }; const navItems = document.querySelectorAll('.nav-item[data-view]'); let settingsLoaded = false; let logTimer = null; let lastLogSignature = ''; function showSettingsStatus(message, type = 'info') { const status = document.getElementById('settingsStatus'); status.textContent = message; status.className = `badge ${type === 'success' ? 'badge-ok' : type === 'error' ? 'badge-fail' : 'badge-info'}`; } async function loadModels(selectedModel) { const select = document.getElementById('settingsModel'); const response = await fetch('/api/models'); if (!response.ok) throw new Error('模型列表加载失败'); const data = await response.json(); select.innerHTML = ''; for (const model of data.models || []) { const option = document.createElement('option'); option.value = typeof model === 'string' ? model : model.id; option.textContent = typeof model === 'string' ? model : (model.label || model.name || model.id); option.selected = option.value === selectedModel; select.appendChild(option); } } async function loadSettings() { showSettingsStatus('加载中…'); const response = await fetch('/api/settings'); if (!response.ok) throw new Error('设置加载失败'); const settings = await response.json(); document.getElementById('settingsApiKey').value = settings.apiKey || ''; document.getElementById('settingsApiBase').value = settings.apiBase || ''; document.getElementById('settingsRootDir').value = settings.rootDir || ''; document.getElementById('pageSizeInput').value = settings.pageSize || 50; document.getElementById('delayInput').value = settings.delayMs ?? 300; const cookieInput = document.getElementById('settingsCookie'); cookieInput.value = ''; cookieInput.placeholder = settings.cookieConfigured ? '已配置,输入新 Cookie 可更换' : '登录 melody.shop.ele.me 后从 DevTools 复制'; await loadModels(settings.model); showSettingsStatus('已同步'); } async function saveSettings() { const button = document.getElementById('btnSaveSettings'); button.disabled = true; showSettingsStatus('保存中…'); try { const response = await fetch('/api/settings', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ apiKey: document.getElementById('settingsApiKey').value.trim(), model: document.getElementById('settingsModel').value, rootDir: document.getElementById('settingsRootDir').value.trim(), pageSize: Number(document.getElementById('pageSizeInput').value), delayMs: Number(document.getElementById('delayInput').value), ...(document.getElementById('settingsCookie').value.trim() ? { cookie: document.getElementById('settingsCookie').value.trim() } : {}), }), }); const data = await response.json(); if (!response.ok) throw new Error(data.error || `HTTP ${response.status}`); await loadSettings(); showSettingsStatus('已保存', 'success'); } catch (error) { showSettingsStatus(error.message, 'error'); } finally { button.disabled = false; } } async function initSettings() { if (settingsLoaded) return; document.getElementById('btnSaveSettings').addEventListener('click', saveSettings); document.getElementById('btnClearLog').addEventListener('click', () => { clearLogs().catch(() => {}); }); try { await loadSettings(); settingsLoaded = true; } catch (error) { showSettingsStatus(error.message, 'error'); } } function escapeLogText(value) { const div = document.createElement('div'); div.textContent = String(value ?? ''); return div.innerHTML; } function renderLogs(logs) { const panel = document.getElementById('logPanel'); if (!panel) return; const sourceMap = { task: '任务', system: '系统' }; const levelMap = { info: 'INFO', success: 'OK', warn: 'WARN', error: 'FAIL' }; panel.innerHTML = logs.map((log) => { const level = levelMap[log.level] || 'INFO'; const levelClass = log.level === 'success' ? 'ok' : log.level || 'info'; return `
${escapeLogText(log.time)} ${sourceMap[log.source] || log.source} ${level} ${escapeLogText(log.message)}
`; }).join(''); } async function refreshLogs() { const response = await fetch('/api/logs'); if (!response.ok) return; const data = await response.json(); const logs = (data.logs || []).sort((a, b) => b.seq - a.seq); const signature = logs.map((log) => `${log.seq}:${log.level}:${log.message}`).join('|'); if (signature === lastLogSignature) return; lastLogSignature = signature; renderLogs(logs); } function startLogPolling() { if (logTimer) return; refreshLogs(); logTimer = setInterval(refreshLogs, 1500); } async function clearLogs() { await fetch('/api/logs/clear', { method: 'POST' }); lastLogSignature = ''; await refreshLogs(); } function switchView(name) { for (const [key, el] of Object.entries(views)) { el.classList.toggle('active', key === name); } navItems.forEach((n) => n.classList.toggle('active', n.dataset.view === name)); if (name === 'crawl' && window.shanCrawlInit) window.shanCrawlInit(); if (name === 'gen' && window.shanGenInit) window.shanGenInit(); if (name === 'tasks' && window.shanTasksInit) window.shanTasksInit(); if (name === 'history' && window.shanHistoryInit) window.shanHistoryInit(); if (name === 'settings') initSettings(); } navItems.forEach((n) => n.addEventListener('click', () => switchView(n.dataset.view))); switchView('crawl'); startLogPolling(); const splash = document.getElementById('splash'); if (splash) { setTimeout(() => splash.classList.add('hide'), 1800); setTimeout(() => splash.remove(), 2400); } })();