lucide.createIcons(); async function checkAuth() { const r = await fetch('/api/auth/me'); const d = await r.json(); const profile = document.getElementById('userProfile'); const entry = document.getElementById('loginEntryBtn'); const loginHint = document.getElementById('loginHint'); const submitBtn = document.getElementById('submitBtn'); if(d.logged_in) { if(profile) profile.classList.remove('hidden'); if(entry) entry.classList.add('hidden'); if(loginHint) loginHint.classList.add('hidden'); submitBtn.disabled = false; submitBtn.classList.remove('opacity-50', 'cursor-not-allowed'); const phoneDisp = document.getElementById('userPhoneDisplay'); if(phoneDisp) phoneDisp.innerText = d.phone; // 处理积分显示 const pointsBadge = document.getElementById('pointsBadge'); const pointsDisplay = document.getElementById('pointsDisplay'); if(pointsBadge && pointsDisplay) { pointsBadge.classList.remove('hidden'); pointsDisplay.innerText = d.points; } const headerPoints = document.getElementById('headerPoints'); if(headerPoints) headerPoints.innerText = d.points; // 如果用户已经有绑定的 Key,且当前没手动输入,则默认切到 Key 模式 if(d.api_key) { switchMode('key'); const keyInput = document.getElementById('apiKey'); if(keyInput && !keyInput.value) keyInput.value = d.api_key; } else { switchMode('trial'); } } else { if(profile) profile.classList.add('hidden'); if(entry) entry.classList.remove('hidden'); if(loginHint) loginHint.classList.remove('hidden'); submitBtn.disabled = true; submitBtn.classList.add('opacity-50', 'cursor-not-allowed'); } lucide.createIcons(); } // 移除 redundant logout 监听,因为 base.html 已处理全局登出 // 历史记录分页状态 let currentHistoryPage = 1; let hasMoreHistory = true; let isHistoryLoading = false; // 存储当前生成的所有图片 URL let currentGeneratedUrls = []; let currentMode = 'trial'; // 'trial' 或 'key' function switchMode(mode) { currentMode = mode; const trialBtn = document.getElementById('modeTrialBtn'); const keyBtn = document.getElementById('modeKeyBtn'); const keyInputGroup = document.getElementById('keyInputGroup'); const premiumToggle = document.getElementById('premiumToggle'); if(mode === 'trial') { trialBtn.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2'); trialBtn.classList.remove('border-slate-200', 'text-slate-400'); keyBtn.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2'); keyBtn.classList.add('border-slate-200', 'text-slate-400'); keyInputGroup.classList.add('hidden'); if(premiumToggle) premiumToggle.classList.remove('hidden'); } else { keyBtn.classList.add('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2'); keyBtn.classList.remove('border-slate-200', 'text-slate-400'); trialBtn.classList.remove('border-indigo-500', 'bg-indigo-50', 'text-indigo-600', 'border-2'); trialBtn.classList.add('border-slate-200', 'text-slate-400'); keyInputGroup.classList.remove('hidden'); if(premiumToggle) premiumToggle.classList.add('hidden'); } updateCostPreview(); // 切换模式时同步计费预览 } async function downloadImage(url) { try { const response = await fetch(url); const blob = await response.blob(); const blobUrl = window.URL.createObjectURL(blob); const a = document.createElement('a'); a.href = blobUrl; // 从 URL 提取文件名 const filename = url.split('/').pop().split('?')[0] || 'ai-vision-image.png'; a.download = filename; document.body.appendChild(a); a.click(); document.body.removeChild(a); window.URL.revokeObjectURL(blobUrl); } catch (e) { console.error('下载失败:', e); showToast('下载失败,请尝试右键保存', 'error'); } } async function loadHistory(isLoadMore = false) { if (isHistoryLoading || (!hasMoreHistory && isLoadMore)) return; isHistoryLoading = true; if (!isLoadMore) { currentHistoryPage = 1; document.getElementById('historyList').innerHTML = ''; } const footer = document.getElementById('historyFooter'); footer.classList.remove('hidden'); try { const r = await fetch(`/api/history?page=${currentHistoryPage}&per_page=10`); const d = await r.json(); const list = document.getElementById('historyList'); if (d.history && d.history.length > 0) { const html = d.history.map(item => `
${item.time} ${item.model}

${item.prompt}

${item.urls.map(url => `
`).join('')}
`).join(''); if (isLoadMore) { list.insertAdjacentHTML('beforeend', html); } else { list.innerHTML = html; } hasMoreHistory = d.has_next; currentHistoryPage++; } else if (!isLoadMore) { list.innerHTML = `
暂无生成记录
`; } lucide.createIcons(); } catch (e) { console.error('加载历史失败:', e); if (!isLoadMore) { document.getElementById('historyList').innerHTML = `
加载失败: ${e.message}
`; } } finally { isHistoryLoading = false; footer.classList.add('hidden'); } } async function init() { checkAuth(); // 模式切换监听 const modeTrialBtn = document.getElementById('modeTrialBtn'); const modeKeyBtn = document.getElementById('modeKeyBtn'); const isPremiumCheckbox = document.getElementById('isPremium'); if(modeTrialBtn) modeTrialBtn.onclick = () => switchMode('trial'); if(modeKeyBtn) modeKeyBtn.onclick = () => switchMode('key'); if(isPremiumCheckbox) isPremiumCheckbox.onchange = () => updateCostPreview(); // 历史记录控制 const historyDrawer = document.getElementById('historyDrawer'); const showHistoryBtn = document.getElementById('showHistoryBtn'); const closeHistoryBtn = document.getElementById('closeHistoryBtn'); const historyList = document.getElementById('historyList'); if(showHistoryBtn) { showHistoryBtn.onclick = () => { historyDrawer.classList.remove('translate-x-full'); loadHistory(false); }; } if(closeHistoryBtn) { closeHistoryBtn.onclick = () => { historyDrawer.classList.add('translate-x-full'); }; } // 瀑布流滚动加载 if (historyList) { historyList.onscroll = () => { const threshold = 100; if (historyList.scrollTop + historyList.clientHeight >= historyList.scrollHeight - threshold) { loadHistory(true); } }; } // 全部下载按钮逻辑 const downloadAllBtn = document.getElementById('downloadAllBtn'); if(downloadAllBtn) { downloadAllBtn.onclick = async () => { if(currentGeneratedUrls.length === 0) return; showToast(`正在准备下载 ${currentGeneratedUrls.length} 张作品...`, 'info'); for(const url of currentGeneratedUrls) { await downloadImage(url); // 稍微延迟一下,防止浏览器拦截 await new Promise(r => setTimeout(r, 300)); } }; } // 重新生成按钮逻辑 const regenBtn = document.getElementById('regenBtn'); if(regenBtn) { regenBtn.onclick = () => { const submitBtn = document.getElementById('submitBtn'); if(submitBtn) submitBtn.click(); }; } // 检查是否有来自 URL 的错误提示 const urlParams = new URLSearchParams(window.location.search); if(urlParams.has('error')) { showToast(urlParams.get('error'), 'error'); // 清理 URL 参数以防刷新时重复提示 window.history.replaceState({}, document.title, window.location.pathname); } try { const r = await fetch('/api/config'); const d = await r.json(); fillSelect('modelSelect', d.models); fillSelect('ratioSelect', d.ratios); fillSelect('sizeSelect', d.sizes); fillSelect('promptTpl', [{label:'✨ 自定义创作', value:'manual'}, ...d.prompts]); updateCostPreview(); // 初始化时显示默认模型的积分 } catch(e) { console.error(e); } } function fillSelect(id, list) { const el = document.getElementById(id); if(!el) return; // 如果是模型选择,增加积分显示 if(id === 'modelSelect') { el.innerHTML = list.map(i => ``).join(''); } else { el.innerHTML = list.map(i => ``).join(''); } } // 更新计费预览显示 function updateCostPreview() { const modelSelect = document.getElementById('modelSelect'); const costPreview = document.getElementById('costPreview'); const isPremium = document.getElementById('isPremium')?.checked || false; const selectedOption = modelSelect.options[modelSelect.selectedIndex]; if (currentMode === 'trial' && selectedOption) { let cost = parseInt(selectedOption.getAttribute('data-cost') || 0); if(isPremium) cost *= 2; // 优质模式 2 倍积分 costPreview.innerText = `本次生成将消耗 ${cost} 积分`; costPreview.classList.remove('hidden'); } else { costPreview.classList.add('hidden'); } } document.getElementById('fileInput').onchange = (e) => { const prev = document.getElementById('imagePreview'); prev.innerHTML = ''; Array.from(e.target.files).forEach(file => { const reader = new FileReader(); reader.onload = (ev) => { const d = document.createElement('div'); d.className = 'w-20 h-20 rounded-2xl overflow-hidden flex-shrink-0 border-2 border-white shadow-md'; d.innerHTML = ``; prev.appendChild(d); }; reader.readAsDataURL(file); }); }; document.getElementById('modelSelect').onchange = (e) => { document.getElementById('sizeGroup').classList.toggle('hidden', e.target.value !== 'nano-banana-2'); updateCostPreview(); // 切换模型时更新计费预览 }; document.getElementById('promptTpl').onchange = (e) => { const area = document.getElementById('manualPrompt'); if(e.target.value !== 'manual') { area.value = e.target.value; area.readOnly = true; } else { area.value = ''; area.readOnly = false; } }; document.getElementById('submitBtn').onclick = async () => { const btn = document.getElementById('submitBtn'); const files = document.getElementById('fileInput').files; const apiKey = document.getElementById('apiKey').value; const num = parseInt(document.getElementById('numSelect').value); // 检查登录状态并获取积分 const authCheck = await fetch('/api/auth/me'); const authData = await authCheck.json(); if(!authData.logged_in) { showToast('请先登录后再生成作品', 'warning'); return; } // 根据模式验证 if(currentMode === 'key') { if(!apiKey) return showToast('请输入您的 API 密钥', 'warning'); } else { if(authData.points <= 0) return showToast('可用积分已耗尽,请充值或切换至自定义 Key 模式', 'warning'); } if(files.length === 0) return showToast('请上传照片', 'warning'); // UI 锁定 btn.disabled = true; const btnText = btn.querySelector('span'); btnText.innerText = "正在同步参考图..."; document.getElementById('statusInfo').classList.remove('hidden'); document.getElementById('placeholder').classList.add('hidden'); document.getElementById('finalWrapper').classList.remove('hidden'); const grid = document.getElementById('imageGrid'); grid.innerHTML = ''; // 清空 currentGeneratedUrls = []; // 重置当前生成列表 try { // 1. 先上传参考图获取持久化 URL const uploadData = new FormData(); for(let f of files) uploadData.append('images', f); const upR = await fetch('/api/upload', { method: 'POST', body: uploadData }); const upRes = await upR.json(); if(upRes.error) throw new Error(upRes.error); const image_urls = upRes.urls; // 2. 并行启动多个生成任务 btnText.innerText = `AI 构思中 (0/${num})...`; let finishedCount = 0; const startTask = async (index) => { const slot = document.createElement('div'); slot.className = 'image-frame relative bg-white/50 animate-pulse min-h-[300px] flex items-center justify-center rounded-[2.5rem] border border-slate-100 shadow-sm'; slot.innerHTML = `
正在创作第 ${index + 1} 张...
`; grid.appendChild(slot); try { const r = await fetch('/api/generate', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ mode: currentMode, is_premium: document.getElementById('isPremium')?.checked || false, apiKey: currentMode === 'key' ? apiKey : '', prompt: document.getElementById('manualPrompt').value, model: document.getElementById('modelSelect').value, ratio: document.getElementById('ratioSelect').value, size: document.getElementById('sizeSelect').value, image_urls }) }); const res = await r.json(); if(res.error) throw new Error(res.error); if(res.message) showToast(res.message, 'success'); const imgUrl = res.data[0].url; currentGeneratedUrls.push(imgUrl); slot.className = 'image-frame group relative animate-in zoom-in-95 duration-700'; slot.innerHTML = `
`; lucide.createIcons(); } catch (e) { slot.className = 'image-frame relative bg-red-50/50 flex items-center justify-center rounded-[2.5rem] border border-red-100 p-6'; if(e.message.includes('401') || e.message.includes('请先登录')) { slot.innerHTML = `
登录已过期,请重新登录
`; } else { slot.innerHTML = `
渲染异常: ${e.message}
`; } } finally { finishedCount++; btnText.innerText = `AI 构思中 (${finishedCount}/${num})...`; // 每次生成任务结束后,刷新一次积分显示 if(currentMode === 'trial') checkAuth(); } }; const tasks = Array.from({ length: num }, (_, i) => startTask(i)); await Promise.all(tasks); } catch (e) { showToast('创作引擎中断: ' + e.message, 'error'); document.getElementById('placeholder').classList.remove('hidden'); } finally { btn.disabled = false; btnText.innerText = "立即生成作品"; document.getElementById('statusInfo').classList.add('hidden'); } }; init(); // 修改密码弹窗控制 function openPwdModal() { const modal = document.getElementById('pwdModal'); if(!modal) return; modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('opacity-100'); modal.querySelector('div').classList.remove('scale-95'); }, 10); } function closePwdModal() { const modal = document.getElementById('pwdModal'); if(!modal) return; modal.classList.remove('opacity-100'); modal.querySelector('div').classList.add('scale-95'); setTimeout(() => { modal.classList.add('hidden'); document.getElementById('pwdForm').reset(); }, 300); } document.addEventListener('click', (e) => { if(e.target.closest('#openPwdModalBtn')) { openPwdModal(); } }); document.getElementById('pwdForm')?.addEventListener('submit', async (e) => { e.preventDefault(); const old_password = document.getElementById('oldPwd').value; const new_password = document.getElementById('newPwd').value; try { const r = await fetch('/api/auth/change_password', { method: 'POST', headers: {'Content-Type': 'application/json'}, body: JSON.stringify({old_password, new_password}) }); const d = await r.json(); if(r.ok) { showToast('密码修改成功,请记牢新密码', 'success'); closePwdModal(); } else { showToast(d.error || '修改失败', 'error'); } } catch(err) { showToast('网络连接失败', 'error'); } });