Files
dsProject/dsLightRag/static/Midjourney/mj.js
2025-08-25 19:18:40 +08:00

506 lines
22 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// 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 = `<i class="fa fa-exclamation-circle"></i> 图像生成超时(已等待${elapsedTime}秒),您可以稍后在历史记录中查看,或重新生成。`;
document.getElementById('progress-text').textContent = '生成超时';
// 恢复生成按钮
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
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 = `<i class="fa fa-exclamation-circle"></i> 生成失败: ${data.error}`;
statusMessage.style.color = '#ef4444';
// 恢复生成按钮
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
return;
}
// 更新状态信息
statusMessage.innerHTML = `<i class="fa fa-spinner fa-spin"></i> 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 = '<i class="fa fa-check-circle"></i> 图像生成完成!';
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 = `<i class="fa fa-exclamation-circle"></i> 生成失败: ${data.error || '未知错误'}`;
statusMessage.style.color = '#ef4444';
// 恢复生成按钮
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
}
// 继续轮询
else {
timerId = setTimeout(checkStatus, queryInterval);
}
})
.catch(error => {
console.error('检查任务状态失败:', error);
// 在错误情况下仍继续轮询直到达到最大次数
if (queryCount < maxQueries) {
queryCount++;
timerId = setTimeout(checkStatus, queryInterval);
} else {
document.getElementById('status-display').innerHTML = `<i class="fa fa-exclamation-circle"></i> 连接服务器失败,请重试`;
document.getElementById('status-display').style.color = '#ef4444';
// 恢复生成按钮
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
}
});
}
// 立即开始第一次检查
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 = '<i class="fa fa-magic mr-2"></i>生成图像';
}
// 添加 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();
});
});