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 显示逻辑 const keyBtn = document.getElementById('modeKeyBtn'); const modeButtonsContainer = keyBtn ? keyBtn.parentElement : null; if (keyBtn && modeButtonsContainer) { if (d.hide_custom_key) { // 如果后端说要隐藏(用过积分生成),则隐藏整个按钮组 modeButtonsContainer.classList.add('hidden'); // 修改标题为“账户状态” const authTitle = document.querySelector('#authSection h3'); if (authTitle) authTitle.innerText = '账户状态'; } else { // 否则显示按钮组,让用户可以选择 modeButtonsContainer.classList.remove('hidden'); // 如果有 Key,默认帮忙切过去,方便老用户 if (d.api_key) { switchMode('key'); const keyInput = document.getElementById('apiKey'); if (keyInput && !keyInput.value) keyInput.value = d.api_key; return; } } } // 如果用户已经有绑定的 Key,且当前没手动输入,则默认切到 Key 模式 // 强制使用积分模式 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' let uploadedFiles = []; // 存储当前待上传的参考图 let isSetterActive = false; // 是否激活了拍摄角度设置器模式 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&filter_type=image`); const d = await r.json(); const list = document.getElementById('historyList'); if (d.history && d.history.length > 0) { const html = d.history.map(item => `
${item.created_at} ${item.model}
${item.urls.map(u => `
`).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 numSelect = document.getElementById('numSelect'); if (numSelect) numSelect.onchange = () => updateCostPreview(); // 历史记录控制 const historyDrawer = document.getElementById('historyDrawer'); const showHistoryBtn = document.getElementById('showHistoryBtn'); const closeHistoryBtn = document.getElementById('closeHistoryBtn'); const historyList = document.getElementById('historyList'); // 3D 构图辅助控制 const openVisualizerBtn = document.getElementById('openVisualizerBtn'); const closeVisualizerBtn = document.getElementById('closeVisualizerBtn'); if (openVisualizerBtn) openVisualizerBtn.onclick = openVisualizerModal; if (closeVisualizerBtn) closeVisualizerBtn.onclick = closeVisualizerModal; const openSavePromptBtn = document.getElementById('openSavePromptBtn'); if (openSavePromptBtn) openSavePromptBtn.onclick = openSavePromptModal; 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 { await refreshPromptsList(); updateCostPreview(); // 初始化时显示默认模型的积分 } catch (e) { console.error(e); } // 初始化拖拽排序 const prev = document.getElementById('imagePreview'); if (prev) { new Sortable(prev, { animation: 150, ghostClass: 'opacity-50', onEnd: function (evt) { // 拖拽结束,重新对 uploadedFiles 排序 const oldIndex = evt.oldIndex; const newIndex = evt.newIndex; const movedFile = uploadedFiles.splice(oldIndex, 1)[0]; uploadedFiles.splice(newIndex, 0, movedFile); // 重新渲染以更新“图1/图2/图3”标签 renderImagePreviews(); } }); } } 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 => { // 处理 data-id const dataIdAttr = i.id ? ` data-id="${i.id}"` : ''; const disabledAttr = i.disabled ? ' disabled' : ''; return ``; }).join(''); } } // 更新计费预览显示 function updateCostPreview() { const modelSelect = document.getElementById('modelSelect'); const costPreview = document.getElementById('costPreview'); const numSelect = document.getElementById('numSelect'); const isPremium = document.getElementById('isPremium')?.checked || false; const selectedOption = modelSelect.options[modelSelect.selectedIndex]; if (currentMode === 'trial' && selectedOption) { let baseCost = parseInt(selectedOption.getAttribute('data-cost') || 0); let num = parseInt(numSelect?.value || 1); let totalCost = baseCost * num; if (isPremium) totalCost *= 2; // 优质模式 2 倍积分 costPreview.innerText = `本次生成将消耗 ${totalCost} 积分`; costPreview.classList.remove('hidden'); } else { costPreview.classList.add('hidden'); } } // 渲染参考图预览 function renderImagePreviews() { const prev = document.getElementById('imagePreview'); if (!prev) return; prev.innerHTML = ''; uploadedFiles.forEach((file, index) => { // 同步创建容器,确保编号顺序永远正确 const d = document.createElement('div'); d.className = 'relative w-20 h-20 rounded-2xl overflow-hidden flex-shrink-0 border-2 border-white shadow-md group cursor-move'; d.setAttribute('data-index', index); d.innerHTML = `
图${index + 1}
`; prev.appendChild(d); // 异步加载图片内容 const reader = new FileReader(); reader.onload = (ev) => { d.innerHTML = `
图${index + 1}
`; lucide.createIcons(); }; reader.readAsDataURL(file); }); lucide.createIcons(); } function removeUploadedFile(index) { uploadedFiles.splice(index, 1); renderImagePreviews(); } // 统一处理新上传/粘贴/拖入的文件 function handleNewFiles(files) { const newFiles = Array.from(files).filter(f => f.type.startsWith('image/')); if (newFiles.length === 0) return; // 如果处于设置器模式,严格限制为 1 张 if (isSetterActive) { if (newFiles.length > 0) { uploadedFiles = [newFiles[0]]; showToast('设置器模式已开启,仅保留第一张参考图', 'info'); } } else { if (uploadedFiles.length + newFiles.length > 3) { showToast('最多只能上传 3 张参考图', 'warning'); const remaining = 3 - uploadedFiles.length; if (remaining > 0) { uploadedFiles = uploadedFiles.concat(newFiles.slice(0, remaining)); } } else { uploadedFiles = uploadedFiles.concat(newFiles); } } renderImagePreviews(); } document.getElementById('fileInput').onchange = (e) => { handleNewFiles(e.target.files); e.target.value = ''; // 重置以允许重复选择同一文件 }; // 拖拽上传逻辑 const dropZone = document.getElementById('dropZone'); if (dropZone) { ['dragenter', 'dragover', 'dragleave', 'drop'].forEach(name => { dropZone.addEventListener(name, (e) => { e.preventDefault(); e.stopPropagation(); }, false); }); ['dragenter', 'dragover'].forEach(name => { dropZone.addEventListener(name, () => dropZone.classList.add('border-indigo-500', 'bg-indigo-50/50'), false); }); ['dragleave', 'drop'].forEach(name => { dropZone.addEventListener(name, () => dropZone.classList.remove('border-indigo-500', 'bg-indigo-50/50'), false); }); dropZone.addEventListener('drop', (e) => { handleNewFiles(e.dataTransfer.files); }, false); } // 粘贴上传逻辑 document.addEventListener('paste', (e) => { const items = (e.clipboardData || e.originalEvent.clipboardData).items; const files = []; for (let item of items) { if (item.type.indexOf('image') !== -1) { files.push(item.getAsFile()); } } if (files.length > 0) { handleNewFiles(files); } }); 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'); const deleteContainer = document.getElementById('deletePromptContainer'); const selectedOption = e.target.options[e.target.selectedIndex]; // 判断是否是用户的收藏项 (通过 data-id 属性) const promptId = selectedOption.getAttribute('data-id'); if (promptId) { deleteContainer.classList.remove('hidden'); document.getElementById('deletePromptBtn').onclick = () => openDeleteConfirmModal(promptId); } else { deleteContainer.classList.add('hidden'); } if (e.target.value !== 'manual') { area.value = e.target.value; area.classList.add('hidden'); } else { area.value = ''; area.classList.remove('hidden'); } }; document.getElementById('submitBtn').onclick = async () => { const btn = document.getElementById('submitBtn'); const prompt = document.getElementById('manualPrompt').value; 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 (!prompt && uploadedFiles.length === 0) { return showToast('请至少输入提示词或上传参考图', 'warning'); } // UI 锁定 btn.disabled = true; const btnText = btn.querySelector('span'); btnText.innerText = uploadedFiles.length > 0 ? "正在同步参考图..." : "正在开启 AI 引擎..."; 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 { let image_data = []; // 1. 将图片转换为 Base64 if (uploadedFiles.length > 0) { btnText.innerText = "正在准备图片数据..."; const readFileAsBase64 = (file) => new Promise((resolve, reject) => { const reader = new FileReader(); reader.onload = () => resolve(reader.result); reader.onerror = reject; reader.readAsDataURL(file); }); image_data = await Promise.all(uploadedFiles.map(f => readFileAsBase64(f))); } // 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-[200px] flex items-center justify-center rounded-[2.5rem] border border-slate-100 shadow-sm'; slot.innerHTML = `
正在排队中...
`; grid.appendChild(slot); try { // 1. 发起生成请求,获取任务 ID 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_data // 发送 Base64 数组 }) }); const res = await r.json(); if (res.error) throw new Error(res.error); // 如果直接返回了 data (比如聊天模型),直接显示 if (res.data) { displayResult(slot, res.data[0]); return; } // 2. 轮询任务状态 const taskId = res.task_id; let pollCount = 0; const maxPolls = 500; // 最多轮询约 16 分钟 (2s * 500 = 1000s) while (pollCount < maxPolls) { await new Promise(resolve => setTimeout(resolve, 2000)); pollCount++; const statusR = await fetch(`/api/task_status/${taskId}`); const statusRes = await statusR.json(); if (statusRes.status === 'complete') { const imgUrl = statusRes.urls[0]; currentGeneratedUrls.push(imgUrl); displayResult(slot, { url: imgUrl }); finishedCount++; btnText.innerText = `AI 构思中 (${finishedCount}/${num})...`; if (currentMode === 'trial') checkAuth(); return; // 任务正常结束 } else if (statusRes.status === 'error') { throw new Error(statusRes.message || "生成失败"); } else { // 继续轮询状态显示 slot.innerHTML = `
AI 正在努力创作中 (${pollCount * 2}s)...
`; } } throw new Error("生成超时,请稍后在历史记录中查看"); } 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}
`; } } }; 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'); } }; // 提取结果展示逻辑 const displayResult = (slot, data) => { if (data.type === 'text') { slot.className = 'image-frame relative bg-white border border-slate-100 p-8 rounded-[2.5rem] shadow-xl overflow-y-auto max-h-[600px]'; slot.innerHTML = `
${data.content.replace(/\n/g, '
')}
`; } else { const imgUrl = data.url; slot.className = 'image-frame group relative animate-in zoom-in-95 duration-700 flex flex-col items-center justify-center overflow-hidden bg-white shadow-2xl transition-all hover:shadow-indigo-100/50'; slot.innerHTML = `
`; } lucide.createIcons(); }; 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'); } }); // 拍摄角度设置器弹窗控制 function openVisualizerModal() { const modal = document.getElementById('visualizerModal'); if (!modal) return; // 检查是否已上传参考图 if (uploadedFiles.length === 0) { return showToast('请先上传一张参考图作为基准', 'warning'); } modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('opacity-100'); modal.querySelector('div').classList.remove('scale-95'); // 将主页面的图片同步到设置器 iframe const reader = new FileReader(); reader.onload = (e) => { const iframe = document.getElementById('visualizerFrame'); iframe.contentWindow.postMessage({ type: 'sync_image', dataUrl: e.target.result }, '*'); }; reader.readAsDataURL(uploadedFiles[0]); }, 10); } function closeVisualizerModal() { const modal = document.getElementById('visualizerModal'); if (!modal) return; modal.classList.remove('opacity-100'); modal.querySelector('div').classList.add('scale-95'); setTimeout(() => { modal.classList.add('hidden'); }, 300); } // 监听来自设置器的消息 window.addEventListener('message', (e) => { if (e.data.type === 'apply_prompt') { isSetterActive = true; const area = document.getElementById('manualPrompt'); const promptTpl = document.getElementById('promptTpl'); const modelSelect = document.getElementById('modelSelect'); const sizeGroup = document.getElementById('sizeGroup'); // 1. 强制切换并锁定版本 2.0 (nano-banana-2) if (modelSelect) { modelSelect.value = 'nano-banana-2'; modelSelect.disabled = true; // 锁定选择 sizeGroup.classList.remove('hidden'); updateCostPreview(); } // 2. 隐藏模板选择器 if (promptTpl) { promptTpl.classList.add('hidden'); promptTpl.value = 'manual'; } // 3. 强制图片数量为 1 if (uploadedFiles.length > 1) { uploadedFiles = uploadedFiles.slice(0, 1); renderImagePreviews(); } // 4. 替换提示词 if (area) { area.value = e.data.prompt; area.classList.remove('hidden'); showToast('已同步拍摄角度并切换至 2.0 引擎', 'success'); } closeVisualizerModal(); } }); // --- 积分与钱包中心逻辑 --- let pointsChart = null; async function openPointsModal() { const modal = document.getElementById('pointsModal'); if (!modal) return; modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('opacity-100'); modal.querySelector('div').classList.remove('scale-95'); }, 10); // 加载数据 loadPointStats(); loadPointDetails(); // 更新当前余额 const r = await fetch('/api/auth/me'); const d = await r.json(); if (d.logged_in) { document.getElementById('modalPointsDisplay').innerText = d.points; } } function closePointsModal() { const modal = document.getElementById('pointsModal'); modal.classList.remove('opacity-100'); modal.querySelector('div').classList.add('scale-95'); setTimeout(() => modal.classList.add('hidden'), 300); } async function loadPointStats() { const r = await fetch('/api/stats/points?days=7'); const d = await r.json(); const canvas = document.getElementById('pointsChart'); if (!canvas) return; const ctx = canvas.getContext('2d'); if (pointsChart) pointsChart.destroy(); if (typeof Chart === 'undefined') { console.error('Chart.js not loaded'); return; } pointsChart = new Chart(ctx, { type: 'line', data: { labels: d.labels, datasets: [ { label: '消耗积分', data: d.deductions, borderColor: '#6366f1', backgroundColor: 'rgba(99, 102, 241, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointRadius: 4, pointBackgroundColor: '#6366f1' }, { label: '充值积分', data: d.incomes, borderColor: '#10b981', backgroundColor: 'rgba(16, 185, 129, 0.1)', borderWidth: 3, fill: true, tension: 0.4, pointRadius: 4, pointBackgroundColor: '#10b981' } ] }, options: { responsive: true, maintainAspectRatio: false, plugins: { legend: { display: false } }, scales: { y: { beginAtZero: true, grid: { color: 'rgba(241, 245, 249, 1)' }, ticks: { font: { weight: 'bold' } } }, x: { grid: { display: false }, ticks: { font: { weight: 'bold' } } } } } }); } async function loadPointDetails() { const body = document.getElementById('pointDetailsBody'); if (!body) return; body.innerHTML = '正在加载明细...'; try { const r = await fetch('/api/stats/details?page=1'); const d = await r.json(); if (d.items.length === 0) { body.innerHTML = '暂无积分变动记录'; return; } body.innerHTML = d.items.map(item => `
${item.desc}
${item.model} ${item.change} ${item.time} `).join(''); } catch (e) { body.innerHTML = '加载失败'; } } // --- 自定义提示词逻辑 --- async function loadUserPrompts() { try { const r = await fetch('/api/prompts'); const d = await r.json(); return d.error ? [] : d; } catch (e) { return []; } } async function refreshPromptsList() { try { const r = await fetch('/api/config'); const d = await r.json(); fillSelect('modelSelect', d.models); fillSelect('ratioSelect', d.ratios); fillSelect('sizeSelect', d.sizes); const userPrompts = await loadUserPrompts(); const mergedPrompts = [ { label: '✨ 自定义创作', value: 'manual' }, ...(userPrompts.length > 0 ? [{ label: '--- 我的收藏 ---', value: 'manual', disabled: true }] : []), ...userPrompts.map(p => ({ id: p.id, label: '⭐ ' + p.label, value: p.value })), { label: '--- 系统预设 ---', value: 'manual', disabled: true }, ...d.prompts ]; fillSelect('promptTpl', mergedPrompts); } catch (e) { console.error(e); } } function openSavePromptModal() { // Check login if (document.getElementById('loginEntryBtn').offsetParent !== null) { showToast('请先登录', 'warning'); return; } const modal = document.getElementById('savePromptModal'); const input = document.getElementById('promptTitleInput'); const prompt = document.getElementById('manualPrompt').value; if (!prompt || !prompt.trim()) { showToast('请先输入一些提示词内容', 'warning'); return; } if (modal) { modal.classList.remove('hidden'); input.value = ''; setTimeout(() => { modal.classList.add('opacity-100'); modal.querySelector('div').classList.remove('scale-95'); input.focus(); }, 10); } } function closeSavePromptModal() { const modal = document.getElementById('savePromptModal'); if (modal) { modal.classList.remove('opacity-100'); modal.querySelector('div').classList.add('scale-95'); setTimeout(() => modal.classList.add('hidden'), 300); } } async function confirmSavePrompt() { const title = document.getElementById('promptTitleInput').value.trim(); const prompt = document.getElementById('manualPrompt').value.trim(); if (!title) { showToast('请输入标题', 'warning'); return; } try { const r = await fetch('/api/prompts', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ title, prompt }) }); const d = await r.json(); if (r.ok) { showToast('收藏成功', 'success'); closeSavePromptModal(); refreshPromptsList(); } else { showToast(d.error || '保存失败', 'error'); } } catch (e) { showToast('保存异常', 'error'); } } async function deleteUserPrompt(id) { // Legacy function replaced by openDeleteConfirmModal // Kept for reference or simple fallback if needed, but logic moved to executeDeletePrompt } function openDeleteConfirmModal(id) { const modal = document.getElementById('deleteConfirmModal'); const confirmBtn = document.getElementById('confirmDeleteBtn'); if (modal && confirmBtn) { // Store ID on the button confirmBtn.setAttribute('data-delete-id', id); confirmBtn.onclick = () => executeDeletePrompt(id); // Bind click modal.classList.remove('hidden'); setTimeout(() => { modal.classList.add('opacity-100'); modal.querySelector('div').classList.remove('scale-95'); }, 10); } } function closeDeleteConfirmModal() { const modal = document.getElementById('deleteConfirmModal'); if (modal) { modal.classList.remove('opacity-100'); modal.querySelector('div').classList.add('scale-95'); setTimeout(() => modal.classList.add('hidden'), 300); } } async function executeDeletePrompt(id) { const confirmBtn = document.getElementById('confirmDeleteBtn'); const originalText = confirmBtn.innerHTML; confirmBtn.disabled = true; confirmBtn.innerHTML = ''; lucide.createIcons(); try { const r = await fetch(`/api/prompts/${id}`, { method: 'DELETE' }); if (r.ok) { showToast('已删除', 'success'); // Reset selection document.getElementById('promptTpl').value = 'manual'; document.getElementById('manualPrompt').value = ''; document.getElementById('manualPrompt').classList.remove('hidden'); document.getElementById('deletePromptContainer').classList.add('hidden'); closeDeleteConfirmModal(); refreshPromptsList(); } else { showToast('删除失败', 'error'); } } catch (e) { showToast('删除异常', 'error'); } finally { confirmBtn.disabled = false; confirmBtn.innerHTML = originalText; } }