// DOM加载完成后执行 document.addEventListener('DOMContentLoaded', function () { // 主题切换功能 const themeToggle = document.getElementById('themeToggle'); themeToggle.addEventListener('click', function () { document.documentElement.classList.toggle('dark'); localStorage.setItem('theme', document.documentElement.classList.contains('dark') ? 'dark' : 'light'); }); // 检查本地存储的主题偏好 if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) { document.documentElement.classList.add('dark'); } // 移动端菜单切换 const menuToggle = document.getElementById('menuToggle'); const mobileMenu = document.getElementById('mobileMenu'); menuToggle.addEventListener('click', function () { mobileMenu.classList.toggle('hidden'); }); // 选项卡切换 const textToImageTab = document.getElementById('textToImageTab'); const imageToImageTab = document.getElementById('imageToImageTab'); const textToImagePanel = document.getElementById('textToImagePanel'); const imageToImagePanel = document.getElementById('imageToImagePanel'); textToImageTab.addEventListener('click', function () { textToImageTab.classList.add('text-primary', 'border-primary'); textToImageTab.classList.remove('text-gray-500', 'border-transparent'); imageToImageTab.classList.remove('text-primary', 'border-primary'); imageToImageTab.classList.add('text-gray-500', 'border-transparent'); textToImagePanel.classList.remove('hidden'); imageToImagePanel.classList.add('hidden'); }); imageToImageTab.addEventListener('click', function () { imageToImageTab.classList.add('text-primary', 'border-primary'); imageToImageTab.classList.remove('text-gray-500', 'border-transparent'); textToImageTab.classList.remove('text-primary', 'border-primary'); textToImageTab.classList.add('text-gray-500', 'border-transparent'); imageToImagePanel.classList.remove('hidden'); textToImagePanel.classList.add('hidden'); }); // 图生图相关功能 const browseImage = document.getElementById('browseImage'); const imageUpload = document.getElementById('imageUpload'); const imageUploadArea = document.getElementById('imageUploadArea'); const imagePreview = document.getElementById('imagePreview'); const previewImage = document.getElementById('previewImage'); const removeImage = document.getElementById('removeImage'); const imageStrength = document.getElementById('imageStrength'); const strengthValue = document.getElementById('strengthValue'); browseImage.addEventListener('click', function () { imageUpload.click(); }); imageUploadArea.addEventListener('dragover', function (e) { e.preventDefault(); imageUploadArea.classList.add('border-primary'); }); imageUploadArea.addEventListener('dragleave', function () { imageUploadArea.classList.remove('border-primary'); }); imageUploadArea.addEventListener('drop', function (e) { e.preventDefault(); imageUploadArea.classList.remove('border-primary'); if (e.dataTransfer.files.length) { imageUpload.files = e.dataTransfer.files; handleImageUpload(); } }); imageUpload.addEventListener('change', handleImageUpload); function handleImageUpload() { if (imageUpload.files && imageUpload.files[0]) { const reader = new FileReader(); reader.onload = function (e) { previewImage.src = e.target.result; imagePreview.classList.remove('hidden'); }; reader.readAsDataURL(imageUpload.files[0]); } } removeImage.addEventListener('click', function () { imagePreview.classList.add('hidden'); imageUpload.value = ''; }); imageStrength.addEventListener('input', function () { strengthValue.textContent = this.value; }); // 随机提示词功能 - 移到顶层DOMContentLoaded回调中 const randomPromptBtn = document.getElementById('randomPromptBtn'); const randomImagePromptBtn = document.getElementById('randomImagePromptBtn'); const promptInput = document.getElementById('prompt'); const imagePromptInput = document.getElementById('imagePrompt'); if (randomPromptBtn && promptInput) { randomPromptBtn.addEventListener('click', function () { const randomPrompt = textPromptExamples[Math.floor(Math.random() * textPromptExamples.length)]; promptInput.value = randomPrompt; }); } if (randomImagePromptBtn && imagePromptInput) { randomImagePromptBtn.addEventListener('click', function () { const randomPrompt = imagePromptExamples[Math.floor(Math.random() * imagePromptExamples.length)]; imagePromptInput.value = randomPrompt; }); } // 生成按钮点击事件 const generateBtn = document.getElementById('generateBtn'); generateBtn.addEventListener('click', generateImage); // 重新生成按钮点击事件 const regenerateBtn = document.getElementById('regenerateBtn'); regenerateBtn.addEventListener('click', function () { // 隐藏结果区域 document.getElementById('resultSection').classList.add('hidden'); // 显示状态区域 document.getElementById('statusSection').classList.remove('hidden'); // 重新生成 generateImage(); }); // 放大按钮点击事件 const upscaleBtn = document.getElementById('upscaleBtn'); upscaleBtn.addEventListener('click', function () { alert('图像放大功能即将实现'); }); // 变体按钮点击事件 const variationBtn = document.getElementById('variationBtn'); variationBtn.addEventListener('click', function () { alert('生成变体功能即将实现'); }); // 下载按钮点击事件 const downloadBtn = document.getElementById('downloadBtn'); downloadBtn.addEventListener('click', function () { const resultImage = document.getElementById('resultImage'); const link = document.createElement('a'); link.href = resultImage.src; link.download = 'midjourney-image.png'; document.body.appendChild(link); link.click(); document.body.removeChild(link); }); // 生成图像函数 async function generateImage() { let prompt, base64Array = null; // 检查是文生图还是图生图 if (!textToImagePanel.classList.contains('hidden')) { // 文生图 prompt = document.getElementById('prompt').value.trim(); if (!prompt) { alert('请输入提示词'); return; } } else { // 图生图 prompt = document.getElementById('imagePrompt').value.trim(); if (!prompt) { alert('请输入提示词'); return; } if (!imageUpload.files || !imageUpload.files[0]) { alert('请上传参考图片'); return; } // 转换图片为base64 const reader = new FileReader(); reader.onload = function (e) { base64Array = [e.target.result.split(',')[1]]; submitGenerateRequest(prompt, base64Array); }; reader.readAsDataURL(imageUpload.files[0]); return; } // 获取表单元素并进行空值检查 const promptElement = document.getElementById('prompt'); const aspectRatioElement = document.getElementById('aspectRatio'); const qualityElement = document.getElementById('quality'); const styleElement = document.getElementById('style'); // 检查是否所有必要元素都存在 if (!promptElement || !aspectRatioElement || !qualityElement || !styleElement) { showError('表单元素缺失,请检查页面配置'); return; } // 创建FormData并添加值 const formData = new FormData(); formData.append('prompt', promptElement.value); formData.append('aspect_ratio', aspectRatioElement.value); formData.append('quality', qualityElement.value); formData.append('style', styleElement.value); // 调用提交函数 submitGenerateRequest(formData); } // 提交生成请求函数 async function submitGenerateRequest(formData) { // 验证formData是有效的FormData对象 if (!(formData instanceof FormData)) { showError('无效的表单数据格式'); return; } // 提取表单数据 const prompt = formData.get('prompt'); // 验证必填字段 if (!prompt) { showError('请输入提示词'); return; } // 从formData提取值(确保表单元素有正确的name属性) const promptText = formData.get('prompt')?.trim(); const selectedAspectRatio = formData.get('aspect_ratio'); const selectedQuality = formData.get('quality'); const selectedStyle = formData.get('style'); // 验证必要参数 if (!promptText) { showError('提示词不能为空'); return; } if (!selectedAspectRatio || !selectedQuality || !selectedStyle) { showError('请选择完整的生成参数'); return; } try { const response = await fetch('/api/mj/imagine', { method: 'POST', headers: { 'Content-Type': 'application/json', }, body: JSON.stringify({ prompt: promptText, aspect_ratio: selectedAspectRatio, quality: selectedQuality, style: selectedStyle }) }); if (!response.ok) { const errorData = await response.json().catch(() => null); throw new Error(`生成请求失败: ${errorData?.message || response.statusText}`); } const result = await response.json(); console.log('Backend response:', result); // Add this line if (!result || !result.task_id) { showError('生成失败: 未获取到任务ID'); return null; } // 显示状态区域并开始轮询 document.getElementById('statusSection').classList.remove('hidden'); pollTaskStatus(result.task_id, promptText); return result.task_id; } catch (error) { console.error('提交生成请求失败:', error); showError('提交请求失败: ' + error.message); return null; } } // 轮询任务状态函数 function pollTaskStatus(taskId, prompt) { // 轮询配置 const queryInterval = 3000; // 3秒查询一次 const maxQueries = 120; // 最多查询120次(6分钟) let queryCount = 0; // 当前查询次数 const startTime = new Date().getTime(); // 开始时间 let timerId = null; // 用于存储setTimeout的ID // 定义内部轮询函数 function checkStatus() { // 检查是否超过最大查询次数 if (queryCount >= maxQueries) { const elapsedTime = Math.round((new Date().getTime() - startTime) / 1000); document.getElementById('status-display').innerHTML = ` 图像生成超时(已等待${elapsedTime}秒),您可以稍后在历史记录中查看,或重新生成。`; document.getElementById('progress-text').textContent = '生成超时'; // 恢复生成按钮 generateBtn.disabled = false; generateBtn.innerHTML = '生成图像'; return; } // 发起状态查询请求 fetch(`/api/mj/task_status?task_id=${taskId}`) .then(response => { if (!response.ok) { throw new Error(`HTTP error! status: ${response.status}`); } return response.json(); }) .then(data => { queryCount++; // 获取DOM元素 const statusMessage = document.getElementById('status-display'); const progressFill = document.getElementById('progress-bar'); const progressText = document.getElementById('progress-text'); // 处理错误状态 if (data.error) { statusMessage.innerHTML = ` 生成失败: ${data.error}`; statusMessage.style.color = '#ef4444'; // 恢复生成按钮 generateBtn.disabled = false; generateBtn.innerHTML = '生成图像'; return; } // 更新状态信息 statusMessage.innerHTML = ` AI 正在创作中...`; statusMessage.style.color = ''; // 重置颜色 // 更新进度条 let progress = data.progress || 0; // 正确解析进度值,处理"32%"这样的字符串 if (typeof progress === 'string' && progress.endsWith('%')) { progress = parseFloat(progress); } else if (typeof progress === 'string') { progress = parseFloat(progress) || 0; } // 添加NaN检查 if (isNaN(progress) || progress < 0) progress = 0; if (progress > 100) progress = 100; const elapsedTime = Math.round((new Date().getTime() - startTime) / 1000); progressFill.style.width = `${progress}%`; progressText.textContent = `${Math.round(progress)}%`; statusMessage.innerHTML += ` (已等待${elapsedTime}秒)`; // 任务完成 if (data.status === 'completed') { statusMessage.innerHTML = ' 图像生成完成!'; statusMessage.style.color = '#10b981'; // 确保进度为100% progressFill.style.width = '100%'; progressText.textContent = '100%'; // 显示结果 setTimeout(() => { showResult(data.image_url, taskId, prompt); }, 1000); // 清除定时器 if (timerId) { clearTimeout(timerId); timerId = null; } } // 任务失败 else if (data.status === 'failed') { statusMessage.innerHTML = ` 生成失败: ${data.error || '未知错误'}`; statusMessage.style.color = '#ef4444'; // 恢复生成按钮 generateBtn.disabled = false; generateBtn.innerHTML = '生成图像'; } // 继续轮询 else { timerId = setTimeout(checkStatus, queryInterval); } }) .catch(error => { console.error('检查任务状态失败:', error); // 在错误情况下仍继续轮询直到达到最大次数 if (queryCount < maxQueries) { queryCount++; timerId = setTimeout(checkStatus, queryInterval); } else { document.getElementById('status-display').innerHTML = ` 连接服务器失败,请重试`; document.getElementById('status-display').style.color = '#ef4444'; // 恢复生成按钮 generateBtn.disabled = false; generateBtn.innerHTML = '生成图像'; } }); } // 立即开始第一次检查 checkStatus(); } // 显示结果函数 function showResult(imageUrl, taskId, prompt) { // 隐藏状态区域 document.getElementById('statusSection').classList.add('hidden'); // 显示结果区域 const resultSection = document.getElementById('resultSection'); resultSection.classList.remove('hidden'); // 设置结果图像 const resultImage = document.getElementById('resultImage'); resultImage.src = imageUrl; // 恢复生成按钮 generateBtn.disabled = false; generateBtn.innerHTML = '生成图像'; } // 添加 showError function after checkTaskStatus function showError(message) { const errorElement = document.getElementById('error-message'); if (errorElement) { errorElement.textContent = message; errorElement.classList.remove('hidden'); // Auto-hide after 5 seconds setTimeout(() => errorElement.classList.add('hidden'), 5000); } else { // Fallback if error element not found alert(message); } } // 文生图示例提示词库 const textPromptExamples = [ "一只穿着太空服的柯基犬在火星表面行走,背景是红色星球和远处的地球,科幻风格,高清细节", "中国传统水墨画风格的山间小屋,雾气缭绕,远处有瀑布,意境悠远,留白艺术", "蒸汽朋克风格的城市景观,空中漂浮着飞艇,机械结构细节丰富,黄昏光影,8K分辨率", "可爱的卡通熊猫宇航员在太空中吃竹子,周围有星星和行星,迪士尼风格,明亮色彩", "赛博朋克风格的东京雨夜,霓虹灯闪烁,全息广告,湿润的街道反射, Blade Runner 氛围", "中世纪奇幻城堡坐落在山顶,周围有龙和魔法光芒,日落时分,史诗电影场景", "极简主义风格的室内设计,白色为主色调,原木家具,大窗户,自然光,北欧风格", "未来科技感的跑车设计,流线型车身,透明车顶,悬浮效果,银色金属质感,未来城市背景", "水彩风格的森林动物聚会,兔子、狐狸和松鼠围坐在蘑菇旁,童话风格,柔和色彩", "复古80年代风格的街机游戏厅,霓虹灯光,老式游戏机,像素艺术,怀旧氛围" ]; // 图生图示例提示词库 const imagePromptExamples = [ "将图片转换为梵高风格的油画,星月夜笔触,鲜艳色彩", "变成卡通风格,线条简洁,色彩明亮,适合儿童绘本", "转换为赛博朋克风格,添加霓虹灯效果和未来科技元素", "模拟水墨画效果,黑白灰三色,强调意境和笔触", "变成3D渲染风格,添加立体效果和真实光影", "转换为像素艺术,8-bit风格,适合复古游戏场景", "模拟水彩画效果,柔和的色彩过渡和纹理", "变成低多边形风格,几何图形构成,简约美学", "转换为蒸汽朋克风格,添加齿轮、铜制元素和机械结构", "模拟素描效果,铅笔线条,强调轮廓和阴影" ]; // 页面加载完成后绑定事件 document.addEventListener('DOMContentLoaded', function () { // 文生图随机按钮 const randomPromptBtn = document.getElementById('randomPromptBtn'); const promptInput = document.getElementById('prompt'); // 图生图随机按钮 const randomImagePromptBtn = document.getElementById('randomImagePromptBtn'); const imagePromptInput = document.getElementById('imagePrompt'); // 文生图随机功能 if (randomPromptBtn && promptInput) { randomPromptBtn.addEventListener('click', function () { const randomIndex = Math.floor(Math.random() * textPromptExamples.length); promptInput.value = textPromptExamples[randomIndex]; // 添加输入框焦点效果 promptInput.focus(); setTimeout(() => promptInput.blur(), 100); }); } // 图生图随机功能 if (randomImagePromptBtn && imagePromptInput) { randomImagePromptBtn.addEventListener('click', function () { const randomIndex = Math.floor(Math.random() * imagePromptExamples.length); imagePromptInput.value = imagePromptExamples[randomIndex]; // 添加输入框焦点效果 imagePromptInput.focus(); setTimeout(() => imagePromptInput.blur(), 100); }); } // 初始化历史记录网格 updateHistoryGrid(); }); });