'commit'
This commit is contained in:
BIN
dsLightRag/Midjourney/__pycache__/Txt2Img.cpython-310.pyc
Normal file
BIN
dsLightRag/Midjourney/__pycache__/Txt2Img.cpython-310.pyc
Normal file
Binary file not shown.
@@ -11,6 +11,7 @@ from pydantic import BaseModel
|
||||
from Midjourney.Txt2Img import Txt2Img
|
||||
from Config import Config
|
||||
import asyncio
|
||||
import threading
|
||||
from fastapi.responses import StreamingResponse
|
||||
|
||||
# 创建路由路由器
|
||||
@@ -71,6 +72,72 @@ async def submit_imagine(request: ImagineRequest, background_tasks: BackgroundTa
|
||||
|
||||
# 存储Midjourney任务ID
|
||||
TASK_STATUS[task_id]["midjourney_task_id"] = midjourney_task_id
|
||||
|
||||
# 添加后台任务轮询状态
|
||||
def poll_task_status_background(task_id, midjourney_task_id):
|
||||
max_retries = 1000
|
||||
retry_count = 0
|
||||
retry_interval = 5 # 5秒
|
||||
|
||||
while retry_count < max_retries:
|
||||
try:
|
||||
# 查询任务状态
|
||||
result = Txt2Img.query_task_status(midjourney_task_id)
|
||||
|
||||
# 更新任务状态
|
||||
if result.get("status") == "SUCCESS":
|
||||
TASK_STATUS[task_id] = {
|
||||
"status": "completed",
|
||||
"image_url": result.get("imageUrl"),
|
||||
"progress": 100,
|
||||
"error": None,
|
||||
"midjourney_task_id": midjourney_task_id
|
||||
}
|
||||
logger.info(f"任务 {task_id} 完成,图片URL: {result.get('imageUrl')}")
|
||||
break
|
||||
elif result.get("status") == "FAILED":
|
||||
TASK_STATUS[task_id] = {
|
||||
"status": "failed",
|
||||
"image_url": None,
|
||||
"progress": 0,
|
||||
"error": result.get("errorMsg", "未知错误"),
|
||||
"midjourney_task_id": midjourney_task_id
|
||||
}
|
||||
logger.error(f"任务 {task_id} 失败: {result.get('errorMsg', '未知错误')}")
|
||||
break
|
||||
else:
|
||||
# 更新进度
|
||||
progress = result.get("progress", 0)
|
||||
TASK_STATUS[task_id]["progress"] = progress
|
||||
TASK_STATUS[task_id]["status"] = "processing"
|
||||
logger.info(f"任务 {task_id} 处理中,进度: {progress}%")
|
||||
|
||||
# 增加重试计数
|
||||
retry_count += 1
|
||||
|
||||
# 等待重试间隔
|
||||
time.sleep(retry_interval)
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"轮询任务 {task_id} 状态失败: {str(e)}")
|
||||
TASK_STATUS[task_id]["error"] = str(e)
|
||||
time.sleep(retry_interval)
|
||||
|
||||
if retry_count >= max_retries:
|
||||
logger.error(f"任务 {task_id} 超时")
|
||||
TASK_STATUS[task_id] = {
|
||||
"status": "failed",
|
||||
"image_url": None,
|
||||
"progress": 0,
|
||||
"error": "任务处理超时",
|
||||
"midjourney_task_id": midjourney_task_id
|
||||
}
|
||||
|
||||
# 使用线程运行后台任务
|
||||
thread = threading.Thread(target=poll_task_status_background, args=(task_id, midjourney_task_id))
|
||||
thread.daemon = True
|
||||
thread.start()
|
||||
|
||||
return {"task_id": task_id}
|
||||
except Exception as e:
|
||||
logger.error(f"提交文生图请求失败: {str(e)}")
|
||||
|
Binary file not shown.
BIN
dsLightRag/Routes/__pycache__/MjRoute.cpython-310.pyc
Normal file
BIN
dsLightRag/Routes/__pycache__/MjRoute.cpython-310.pyc
Normal file
Binary file not shown.
Binary file not shown.
Binary file not shown.
@@ -359,367 +359,430 @@
|
||||
</div>
|
||||
</footer>
|
||||
|
||||
<!-- JavaScript -->
|
||||
<script>
|
||||
// DOM 元素
|
||||
const themeToggle = document.getElementById('themeToggle');
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
const textToImageTab = document.getElementById('textToImageTab');
|
||||
const imageToImageTab = document.getElementById('imageToImageTab');
|
||||
const textToImagePanel = document.getElementById('textToImagePanel');
|
||||
const imageToImagePanel = document.getElementById('imageToImagePanel');
|
||||
const imageUploadArea = document.getElementById('imageUploadArea');
|
||||
const imageUpload = document.getElementById('imageUpload');
|
||||
const browseImage = document.getElementById('browseImage');
|
||||
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');
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
const statusSection = document.getElementById('statusSection');
|
||||
const progressBar = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
const resultImage = document.getElementById('resultImage');
|
||||
const downloadBtn = document.getElementById('downloadBtn');
|
||||
const regenerateBtn = document.getElementById('regenerateBtn');
|
||||
const variationBtn = document.getElementById('variationBtn');
|
||||
const upscaleBtn = document.getElementById('upscaleBtn');
|
||||
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
||||
const historyGrid = document.getElementById('historyGrid');
|
||||
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');
|
||||
} else {
|
||||
document.documentElement.classList.remove('dark');
|
||||
}
|
||||
|
||||
// 主题切换
|
||||
themeToggle.addEventListener('click', () => {
|
||||
if (document.documentElement.classList.contains('dark')) {
|
||||
document.documentElement.classList.remove('dark');
|
||||
localStorage.setItem('theme', 'light');
|
||||
} else {
|
||||
// 检查本地存储的主题偏好
|
||||
if (localStorage.getItem('theme') === 'dark' || (!localStorage.getItem('theme') && window.matchMedia('(prefers-color-scheme: dark)').matches)) {
|
||||
document.documentElement.classList.add('dark');
|
||||
localStorage.setItem('theme', 'dark');
|
||||
}
|
||||
});
|
||||
|
||||
// 移动端菜单
|
||||
menuToggle.addEventListener('click', () => {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
// 移动端菜单切换
|
||||
const menuToggle = document.getElementById('menuToggle');
|
||||
const mobileMenu = document.getElementById('mobileMenu');
|
||||
menuToggle.addEventListener('click', function() {
|
||||
mobileMenu.classList.toggle('hidden');
|
||||
});
|
||||
|
||||
// 选项卡切换
|
||||
textToImageTab.addEventListener('click', () => {
|
||||
textToImageTab.classList.add('text-primary', 'border-primary');
|
||||
textToImageTab.classList.remove('text-gray-500', 'dark:text-gray-400', 'border-transparent');
|
||||
imageToImageTab.classList.remove('text-primary', 'border-primary');
|
||||
imageToImageTab.classList.add('text-gray-500', 'dark:text-gray-400', 'border-transparent');
|
||||
textToImagePanel.classList.remove('hidden');
|
||||
imageToImagePanel.classList.add('hidden');
|
||||
});
|
||||
// 选项卡切换
|
||||
const textToImageTab = document.getElementById('textToImageTab');
|
||||
const imageToImageTab = document.getElementById('imageToImageTab');
|
||||
const textToImagePanel = document.getElementById('textToImagePanel');
|
||||
const imageToImagePanel = document.getElementById('imageToImagePanel');
|
||||
|
||||
imageToImageTab.addEventListener('click', () => {
|
||||
imageToImageTab.classList.add('text-primary', 'border-primary');
|
||||
imageToImageTab.classList.remove('text-gray-500', 'dark:text-gray-400', 'border-transparent');
|
||||
textToImageTab.classList.remove('text-primary', 'border-primary');
|
||||
textToImageTab.classList.add('text-gray-500', 'dark:text-gray-400', 'border-transparent');
|
||||
textToImagePanel.classList.add('hidden');
|
||||
imageToImagePanel.classList.remove('hidden');
|
||||
});
|
||||
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');
|
||||
});
|
||||
|
||||
// 图片上传
|
||||
browseImage.addEventListener('click', () => {
|
||||
imageUpload.click();
|
||||
});
|
||||
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');
|
||||
});
|
||||
|
||||
imageUploadArea.addEventListener('click', (e) => {
|
||||
if (e.target === imageUploadArea || e.target === browseImage || e.target.parentNode === browseImage) {
|
||||
// 图生图相关功能
|
||||
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]);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
imageUpload.addEventListener('change', (e) => {
|
||||
const file = e.target.files[0];
|
||||
if (file) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
previewImage.src = event.target.result;
|
||||
imagePreview.classList.remove('hidden');
|
||||
imageUploadArea.classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
removeImage.addEventListener('click', function() {
|
||||
imagePreview.classList.add('hidden');
|
||||
imageUpload.value = '';
|
||||
});
|
||||
|
||||
removeImage.addEventListener('click', () => {
|
||||
previewImage.src = '';
|
||||
imagePreview.classList.add('hidden');
|
||||
imageUploadArea.classList.remove('hidden');
|
||||
imageUpload.value = '';
|
||||
});
|
||||
imageStrength.addEventListener('input', function() {
|
||||
strengthValue.textContent = this.value;
|
||||
});
|
||||
|
||||
// 拖放上传
|
||||
imageUploadArea.addEventListener('dragover', (e) => {
|
||||
e.preventDefault();
|
||||
imageUploadArea.classList.add('border-primary', 'bg-primary/5');
|
||||
});
|
||||
// 生成按钮点击事件
|
||||
const generateBtn = document.getElementById('generateBtn');
|
||||
generateBtn.addEventListener('click', generateImage);
|
||||
|
||||
imageUploadArea.addEventListener('dragleave', () => {
|
||||
imageUploadArea.classList.remove('border-primary', 'bg-primary/5');
|
||||
});
|
||||
// 重新生成按钮点击事件
|
||||
const regenerateBtn = document.getElementById('regenerateBtn');
|
||||
regenerateBtn.addEventListener('click', function() {
|
||||
// 隐藏结果区域
|
||||
document.getElementById('resultSection').classList.add('hidden');
|
||||
// 显示状态区域
|
||||
document.getElementById('statusSection').classList.remove('hidden');
|
||||
// 重新生成
|
||||
generateImage();
|
||||
});
|
||||
|
||||
imageUploadArea.addEventListener('drop', (e) => {
|
||||
e.preventDefault();
|
||||
imageUploadArea.classList.remove('border-primary', 'bg-primary/5');
|
||||
const file = e.dataTransfer.files[0];
|
||||
if (file && file.type.startsWith('image/')) {
|
||||
const reader = new FileReader();
|
||||
reader.onload = (event) => {
|
||||
previewImage.src = event.target.result;
|
||||
imagePreview.classList.remove('hidden');
|
||||
imageUploadArea.classList.add('hidden');
|
||||
};
|
||||
reader.readAsDataURL(file);
|
||||
}
|
||||
});
|
||||
// 放大按钮点击事件
|
||||
const upscaleBtn = document.getElementById('upscaleBtn');
|
||||
upscaleBtn.addEventListener('click', function() {
|
||||
alert('图像放大功能即将实现');
|
||||
});
|
||||
|
||||
// 强度滑块
|
||||
imageStrength.addEventListener('input', () => {
|
||||
strengthValue.textContent = imageStrength.value;
|
||||
});
|
||||
// 变体按钮点击事件
|
||||
const variationBtn = document.getElementById('variationBtn');
|
||||
variationBtn.addEventListener('click', function() {
|
||||
alert('生成变体功能即将实现');
|
||||
});
|
||||
|
||||
// 生成图像
|
||||
generateBtn.addEventListener('click', async () => {
|
||||
// 验证输入
|
||||
let prompt, style, aspectRatio, quality;
|
||||
let base64Array = null;
|
||||
// 下载按钮点击事件
|
||||
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);
|
||||
});
|
||||
|
||||
if (textToImagePanel.classList.contains('hidden')) {
|
||||
// 图生图
|
||||
prompt = document.getElementById('imagePrompt').value.trim();
|
||||
style = document.getElementById('imageStyle').value;
|
||||
aspectRatio = document.getElementById('imageAspectRatio').value;
|
||||
// 清除历史记录按钮点击事件
|
||||
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
|
||||
clearHistoryBtn.addEventListener('click', function() {
|
||||
if (confirm('确定要清除所有历史记录吗?')) {
|
||||
localStorage.removeItem('mjHistory');
|
||||
updateHistoryGrid();
|
||||
}
|
||||
});
|
||||
|
||||
if (!prompt) {
|
||||
alert('请输入提示词');
|
||||
// 生成图像函数
|
||||
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;
|
||||
}
|
||||
|
||||
if (imageUploadArea.classList.contains('hidden')) {
|
||||
// 有上传图片
|
||||
const canvas = document.createElement('canvas');
|
||||
const ctx = canvas.getContext('2d');
|
||||
const img = new Image();
|
||||
img.src = previewImage.src;
|
||||
// 提交生成请求
|
||||
submitGenerateRequest(prompt, base64Array);
|
||||
}
|
||||
|
||||
await new Promise((resolve) => {
|
||||
img.onload = resolve;
|
||||
// 提交生成请求函数
|
||||
async function submitGenerateRequest(prompt, base64Array) {
|
||||
try {
|
||||
// 禁用生成按钮
|
||||
generateBtn.disabled = true;
|
||||
generateBtn.innerHTML = '<i class="fa fa-spinner fa-spin mr-2"></i>正在提交...';
|
||||
|
||||
// 准备请求数据
|
||||
const requestData = {
|
||||
prompt: prompt,
|
||||
base64_array: base64Array
|
||||
};
|
||||
|
||||
// 发送请求
|
||||
const response = await fetch('/api/mj/imagine', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(requestData)
|
||||
});
|
||||
|
||||
canvas.width = img.width;
|
||||
canvas.height = img.height;
|
||||
ctx.drawImage(img, 0, 0);
|
||||
const base64 = canvas.toDataURL('image/jpeg', 0.8);
|
||||
base64Array = [base64.split(',')[1]]; // 只取base64部分
|
||||
} else {
|
||||
alert('请上传参考图片');
|
||||
return;
|
||||
}
|
||||
} else {
|
||||
// 文生图
|
||||
prompt = document.getElementById('prompt').value.trim();
|
||||
style = document.getElementById('style').value;
|
||||
aspectRatio = document.getElementById('aspectRatio').value;
|
||||
quality = document.getElementById('quality').value;
|
||||
|
||||
if (!prompt) {
|
||||
alert('请输入提示词');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 显示状态区域
|
||||
statusSection.classList.remove('hidden');
|
||||
resultSection.classList.add('hidden');
|
||||
progressBar.style.width = '0%';
|
||||
progressText.textContent = '0%';
|
||||
|
||||
try {
|
||||
// 提交生成请求
|
||||
const response = await fetch('/api/mj/imagine', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
prompt: prompt,
|
||||
base64_array: base64Array,
|
||||
notify_hook: null
|
||||
})
|
||||
});
|
||||
|
||||
if (!response.ok) {
|
||||
throw new Error('提交请求失败');
|
||||
}
|
||||
|
||||
const data = await response.json();
|
||||
const taskId = data.task_id;
|
||||
|
||||
// 轮询任务状态
|
||||
let progress = 0;
|
||||
const intervalId = setInterval(async () => {
|
||||
try {
|
||||
const statusResponse = await fetch(`/api/mj/task_status?task_id=${taskId}`);
|
||||
const statusData = await statusResponse.json();
|
||||
|
||||
// 更新进度
|
||||
if (statusData.progress !== undefined) {
|
||||
progress = statusData.progress;
|
||||
progressBar.style.width = `${progress}%`;
|
||||
progressText.textContent = `${progress}%`;
|
||||
}
|
||||
|
||||
// 检查是否完成
|
||||
if (statusData.status === 'completed') {
|
||||
clearInterval(intervalId);
|
||||
progressBar.style.width = '100%';
|
||||
progressText.textContent = '100%';
|
||||
|
||||
// 延迟显示结果,让用户看到进度完成
|
||||
setTimeout(() => {
|
||||
statusSection.classList.add('hidden');
|
||||
resultSection.classList.remove('hidden');
|
||||
resultImage.src = statusData.image_url || 'https://picsum.photos/seed/result/800/600';
|
||||
|
||||
// 保存到历史记录
|
||||
saveToHistory({
|
||||
id: taskId,
|
||||
imageUrl: statusData.image_url || 'https://picsum.photos/seed/result/800/600',
|
||||
prompt: prompt,
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
updateHistoryDisplay();
|
||||
}, 1000);
|
||||
} else if (statusData.status === 'failed') {
|
||||
clearInterval(intervalId);
|
||||
alert(`生成失败: ${statusData.error || '未知错误'}`);
|
||||
statusSection.classList.add('hidden');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('轮询任务状态失败:', error);
|
||||
if (!response.ok) {
|
||||
throw new Error(`提交请求失败: ${response.statusText}`);
|
||||
}
|
||||
}, 3000); // 每3秒轮询一次
|
||||
} catch (error) {
|
||||
console.error('生成图像失败:', error);
|
||||
alert(`生成图像失败: ${error.message}`);
|
||||
statusSection.classList.add('hidden');
|
||||
}
|
||||
});
|
||||
|
||||
// 下载图片
|
||||
downloadBtn.addEventListener('click', () => {
|
||||
const link = document.createElement('a');
|
||||
link.href = resultImage.src;
|
||||
link.download = `midjourney_${new Date().getTime()}.png`;
|
||||
document.body.appendChild(link);
|
||||
link.click();
|
||||
document.body.removeChild(link);
|
||||
});
|
||||
const data = await response.json();
|
||||
const taskId = data.task_id;
|
||||
|
||||
// 重新生成
|
||||
regenerateBtn.addEventListener('click', () => {
|
||||
generateBtn.click();
|
||||
});
|
||||
// 显示状态区域
|
||||
document.getElementById('statusSection').classList.remove('hidden');
|
||||
|
||||
// 生成变体
|
||||
variationBtn.addEventListener('click', () => {
|
||||
alert('变体功能将在下一版本推出');
|
||||
});
|
||||
|
||||
// 放大图像
|
||||
upscaleBtn.addEventListener('click', () => {
|
||||
alert('放大功能将在下一版本推出');
|
||||
});
|
||||
|
||||
// 历史记录
|
||||
function saveToHistory(item) {
|
||||
let history = JSON.parse(localStorage.getItem('midjourneyHistory') || '[]');
|
||||
history.unshift(item); // 添加到开头
|
||||
// 限制最多100条记录
|
||||
if (history.length > 100) {
|
||||
history = history.slice(0, 100);
|
||||
}
|
||||
localStorage.setItem('midjourneyHistory', JSON.stringify(history));
|
||||
}
|
||||
|
||||
function updateHistoryDisplay() {
|
||||
const history = JSON.parse(localStorage.getItem('midjourneyHistory') || '[]');
|
||||
if (history.length === 0) {
|
||||
historyGrid.innerHTML = `
|
||||
<div class="col-span-full text-center py-12 text-gray-500 dark:text-gray-400">
|
||||
<i class="fa fa-history text-4xl mb-4"></i>
|
||||
<p>暂无历史记录</p>
|
||||
<p class="text-sm mt-2">开始生成图像以保存到历史记录</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
// 开始轮询任务状态
|
||||
pollTaskStatus(taskId, prompt);
|
||||
} catch (error) {
|
||||
console.error('生成图像失败:', error);
|
||||
alert(`生成图像失败: ${error.message}`);
|
||||
// 恢复生成按钮
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
|
||||
}
|
||||
}
|
||||
|
||||
historyGrid.innerHTML = '';
|
||||
history.forEach(item => {
|
||||
const date = new Date(item.timestamp);
|
||||
const formattedDate = `${date.getFullYear()}-${(date.getMonth() + 1).toString().padStart(2, '0')}-${date.getDate().toString().padStart(2, '0')} ${date.getHours().toString().padStart(2, '0')}:${date.getMinutes().toString().padStart(2, '0')}`;
|
||||
// 轮询任务状态函数
|
||||
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
|
||||
|
||||
const historyItem = document.createElement('div');
|
||||
historyItem.className = 'overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group';
|
||||
historyItem.innerHTML = `
|
||||
<div class="relative">
|
||||
<img src="${item.imageUrl}" alt="生成的图像" class="w-full h-48 object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/70 via-transparent to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-3">
|
||||
<button class="view-history-item p-2 bg-white/90 hover:bg-white rounded-full shadow-md transition-all-300" data-id="${item.id}">
|
||||
function checkTaskStatus() {
|
||||
if (queryCount >= maxQueries) {
|
||||
const elapsedTime = Math.round((new Date().getTime() - startTime) / 1000);
|
||||
document.getElementById('statusMessage').innerHTML = `<i class="fa fa-exclamation-circle"></i> 图像生成超时(已等待${elapsedTime}秒),您可以稍后在历史记录中查看,或重新生成。`;
|
||||
document.getElementById('progressText').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 => response.json())
|
||||
.then(data => {
|
||||
queryCount++;
|
||||
// 更新状态信息
|
||||
const statusMessage = document.getElementById('statusMessage');
|
||||
const progressFill = document.getElementById('progressBar');
|
||||
const progressText = document.getElementById('progressText');
|
||||
|
||||
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 正在创作中...`;
|
||||
|
||||
// 更新进度条
|
||||
let progress = data.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%
|
||||
progress = 100;
|
||||
progressFill.style.width = `${progress}%`;
|
||||
progressText.textContent = `${Math.round(progress)}%`;
|
||||
|
||||
// 显示结果
|
||||
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(checkTaskStatus, queryInterval);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
console.error('检查任务状态失败:', error);
|
||||
// 只有在任务未完成且未超时时才继续查询
|
||||
if (queryCount < maxQueries) {
|
||||
timerId = setTimeout(checkTaskStatus, queryInterval);
|
||||
} else {
|
||||
// 恢复生成按钮
|
||||
generateBtn.disabled = false;
|
||||
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// 立即检查一次
|
||||
checkTaskStatus();
|
||||
}
|
||||
|
||||
// 显示结果函数
|
||||
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>生成图像';
|
||||
|
||||
// 保存到历史记录
|
||||
saveToHistory(taskId, prompt, imageUrl);
|
||||
|
||||
// 更新历史记录网格
|
||||
updateHistoryGrid();
|
||||
}
|
||||
|
||||
// 保存到历史记录函数
|
||||
function saveToHistory(taskId, prompt, imageUrl) {
|
||||
let history = JSON.parse(localStorage.getItem('mjHistory') || '[]');
|
||||
history.unshift({
|
||||
id: taskId,
|
||||
prompt: prompt,
|
||||
imageUrl: imageUrl,
|
||||
timestamp: new Date().getTime()
|
||||
});
|
||||
// 只保留最近20条记录
|
||||
if (history.length > 20) {
|
||||
history = history.slice(0, 20);
|
||||
}
|
||||
localStorage.setItem('mjHistory', JSON.stringify(history));
|
||||
}
|
||||
|
||||
// 更新历史记录网格函数
|
||||
function updateHistoryGrid() {
|
||||
const historyGrid = document.getElementById('historyGrid');
|
||||
const history = JSON.parse(localStorage.getItem('mjHistory') || '[]');
|
||||
|
||||
if (history.length === 0) {
|
||||
historyGrid.innerHTML = `
|
||||
<div class="col-span-full text-center py-12 text-gray-500 dark:text-gray-400">
|
||||
<i class="fa fa-history text-4xl mb-4"></i>
|
||||
<p>暂无历史记录</p>
|
||||
<p class="text-sm mt-2">开始生成图像以保存到历史记录</p>
|
||||
</div>
|
||||
`;
|
||||
return;
|
||||
}
|
||||
|
||||
historyGrid.innerHTML = '';
|
||||
history.forEach(item => {
|
||||
const date = new Date(item.timestamp);
|
||||
const formattedDate = `${date.getFullYear()}-${String(date.getMonth() + 1).padStart(2, '0')}-${String(date.getDate()).padStart(2, '0')} ${String(date.getHours()).padStart(2, '0')}:${String(date.getMinutes()).padStart(2, '0')}`;
|
||||
|
||||
const historyItem = document.createElement('div');
|
||||
historyItem.className = 'overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group bg-white dark:bg-gray-800';
|
||||
historyItem.innerHTML = `
|
||||
<div class="relative h-48 overflow-hidden">
|
||||
<img src="${item.imageUrl}" alt="生成的图像" class="w-full h-full object-cover transition-transform duration-500 group-hover:scale-110">
|
||||
<div class="absolute inset-0 bg-gradient-to-t from-black/60 to-transparent opacity-0 group-hover:opacity-100 transition-opacity duration-300 flex items-end p-4">
|
||||
<div class="text-white text-sm truncate w-full">${item.prompt}</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-4 flex justify-between items-center">
|
||||
<span class="text-xs text-gray-500 dark:text-gray-400">${formattedDate}</span>
|
||||
<button class="view-history-item text-primary hover:text-primary/80 transition-colors" data-image-url="${item.imageUrl}">
|
||||
<i class="fa fa-eye"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="p-3 bg-white dark:bg-gray-800">
|
||||
<p class="text-sm font-medium truncate mb-1">${item.prompt}</p>
|
||||
<p class="text-xs text-gray-500 dark:text-gray-400">${formattedDate}</p>
|
||||
</div>
|
||||
`;
|
||||
historyGrid.appendChild(historyItem);
|
||||
});
|
||||
|
||||
// 添加查看历史项的事件监听
|
||||
document.querySelectorAll('.view-history-item').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const id = e.currentTarget.getAttribute('data-id');
|
||||
const item = history.find(i => i.id === id);
|
||||
if (item) {
|
||||
resultImage.src = item.imageUrl;
|
||||
statusSection.classList.add('hidden');
|
||||
resultSection.classList.remove('hidden');
|
||||
// 滚动到结果区域
|
||||
resultSection.scrollIntoView({ behavior: 'smooth' });
|
||||
}
|
||||
`;
|
||||
historyGrid.appendChild(historyItem);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// 清除历史记录
|
||||
clearHistoryBtn.addEventListener('click', () => {
|
||||
if (confirm('确定要清除所有历史记录吗?此操作不可恢复。')) {
|
||||
localStorage.removeItem('midjourneyHistory');
|
||||
updateHistoryDisplay();
|
||||
// 添加历史记录项点击事件
|
||||
document.querySelectorAll('.view-history-item').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const imageUrl = this.getAttribute('data-image-url');
|
||||
// 显示结果区域
|
||||
const resultSection = document.getElementById('resultSection');
|
||||
resultSection.classList.remove('hidden');
|
||||
// 设置结果图像
|
||||
const resultImage = document.getElementById('resultImage');
|
||||
resultImage.src = imageUrl;
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// 初始化历史记录显示
|
||||
updateHistoryDisplay();
|
||||
// 初始化历史记录网格
|
||||
updateHistoryGrid();
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
Reference in New Issue
Block a user