// 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();
});
});