Files
dsProject/dsLightRag/static/Midjourney/mj.html
2025-08-25 15:36:15 +08:00

964 lines
48 KiB
HTML
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.

<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Midjourney AI 绘图工具</title>
<!-- 引入 Tailwind CSS -->
<script src="https://cdn.tailwindcss.com"></script>
<!-- 引入 Font Awesome -->
<link href="https://cdn.bootcdn.net/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css" rel="stylesheet">
<!-- Tailwind 配置 -->
<script>
tailwind.config = {
theme: {
extend: {
colors: {
primary: '#6366f1',
secondary: '#8b5cf6',
dark: '#1e293b',
light: '#f8fafc',
accent: '#ec4899'
},
fontFamily: {
sans: ['Inter', 'system-ui', 'sans-serif'],
},
animation: {
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
}
},
}
}
</script>
<style type="text/tailwindcss">
@layer utilities {
.content-auto {
content-visibility: auto;
}
.glass-effect {
backdrop-filter: blur(10px);
background-color: rgba(255, 255, 255, 0.7);
}
.glass-effect-dark {
backdrop-filter: blur(10px);
background-color: rgba(15, 23, 42, 0.7);
}
.text-shadow {
text-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
}
.transition-all-300 {
transition: all 300ms ease-in-out;
}
}
</style>
</head>
<body class="min-h-screen bg-gradient-to-br from-light to-gray-100 dark:from-dark dark:to-gray-900 text-gray-800 dark:text-gray-100 transition-colors duration-300">
<!-- 导航栏 -->
<header class="sticky top-0 z-50 glass-effect dark:glass-effect-dark border-b border-gray-200 dark:border-gray-700 shadow-sm">
<div class="container mx-auto px-4 py-3 flex justify-between items-center">
<div class="flex items-center space-x-2">
<i class="fa fa-paint-brush text-primary text-2xl"></i>
<h1 class="text-xl font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Midjourney AI</h1>
</div>
<nav class="hidden md:flex items-center space-x-6">
<a href="#generator"
class="font-medium hover:text-primary dark:hover:text-primary transition-colors">生成器</a>
<a href="#history"
class="font-medium hover:text-primary dark:hover:text-primary transition-colors">历史记录</a>
<a href="#gallery" class="font-medium hover:text-primary dark:hover:text-primary transition-colors">画廊</a>
</nav>
<div class="flex items-center space-x-3">
<button id="themeToggle"
class="p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors">
<i class="fa fa-moon-o dark:hidden"></i>
<i class="fa fa-sun-o hidden dark:inline-block"></i>
</button>
<button class="md:hidden p-2 rounded-full hover:bg-gray-200 dark:hover:bg-gray-700 transition-colors"
id="menuToggle">
<i class="fa fa-bars"></i>
</button>
</div>
</div>
<!-- 移动端菜单 -->
<div id="mobileMenu"
class="hidden md:hidden glass-effect dark:glass-effect-dark border-t border-gray-200 dark:border-gray-700">
<div class="container mx-auto px-4 py-2 flex flex-col space-y-3">
<a href="#generator" class="font-medium hover:text-primary dark:hover:text-primary transition-colors py-2">生成器</a>
<a href="#history" class="font-medium hover:text-primary dark:hover:text-primary transition-colors py-2">历史记录</a>
<a href="#gallery"
class="font-medium hover:text-primary dark:hover:text-primary transition-colors py-2">画廊</a>
</div>
</div>
</header>
<main class="container mx-auto px-4 py-8">
<!-- 英雄区 -->
<section class="text-center mb-12">
<h2 class="text-[clamp(2rem,5vw,3.5rem)] font-bold mb-4 text-shadow">
<span class="bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">释放您的创意</span>
</h2>
<p class="text-lg md:text-xl text-gray-600 dark:text-gray-300 max-w-3xl mx-auto mb-8">
使用 Midjourney AI 生成令人惊叹的图像,只需输入文字描述或上传参考图片
</p>
</section>
<!-- 生成器区域 -->
<section id="generator"
class="mb-16 bg-white dark:bg-gray-800 rounded-2xl shadow-xl overflow-hidden transform transition-all duration-500 hover:shadow-2xl">
<!-- 选项卡 -->
<div class="flex border-b border-gray-200 dark:border-gray-700">
<button id="textToImageTab"
class="flex-1 py-4 px-6 text-center font-medium text-primary border-b-2 border-primary dark:text-primary dark:border-primary transition-all-300">
<i class="fa fa-file-text-o mr-2"></i>文生图
</button>
<button id="imageToImageTab"
class="flex-1 py-4 px-6 text-center font-medium text-gray-500 dark:text-gray-400 border-b-2 border-transparent hover:text-primary dark:hover:text-primary transition-all-300">
<i class="fa fa-picture-o mr-2"></i>图生图
</button>
</div>
<!-- 文生图面板 -->
<div id="textToImagePanel" class="p-6 md:p-8 space-y-6">
<div class="space-y-4">
<label for="prompt" class="block text-sm font-medium text-gray-700 dark:text-gray-300">提示词 <span
class="text-red-500">*</span></label>
<div class="flex space-x-2">
<textarea id="prompt" rows="4"
class="flex-1 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-primary resize-none transition-all-300"
placeholder="描述您想要生成的图像..."></textarea>
<button id="randomPromptBtn"
class="p-3 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-all-300"
title="随机示例">
<i class="fa fa-random text-primary mr-1"></i>随机
</button>
</div>
<div class="text-xs text-gray-500 dark:text-gray-400">
提示越详细,生成的图像越符合您的预期。尝试指定风格、细节、光照等。
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="space-y-2">
<label for="style" class="block text-sm font-medium text-gray-700 dark:text-gray-300">风格</label>
<select id="style"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition-all-300">
<option value="default">默认</option>
<option value="realistic">写实</option>
<option value="cartoon">卡通</option>
<option value="anime">动漫</option>
<option value="watercolor">水彩</option>
<option value="oil">油画</option>
</select>
</div>
<div class="space-y-2">
<label for="aspectRatio"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">比例</label>
<select id="aspectRatio"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition-all-300">
<option value="1:1">1:1 (正方形)</option>
<option value="16:9">16:9 (宽屏)</option>
<option value="9:16">9:16 (竖屏)</option>
<option value="4:3">4:3 (标准)</option>
<option value="3:2">3:2 (照片)</option>
</select>
</div>
<div class="space-y-2">
<label for="quality" class="block text-sm font-medium text-gray-700 dark:text-gray-300">质量</label>
<select id="quality"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition-all-300">
<option value="standard">标准</option>
<option value="high">高清</option>
<option value="ultra">超高清</option>
</select>
</div>
</div>
</div>
<!-- 图生图面板 -->
<div id="imageToImagePanel" class="p-6 md:p-8 space-y-6 hidden">
<div class="space-y-4">
<label for="imagePrompt" class="block text-sm font-medium text-gray-700 dark:text-gray-300">提示词 <span
class="text-red-500">*</span></label>
<div class="flex space-x-2">
<textarea id="imagePrompt" rows="2"
class="flex-1 px-4 py-3 rounded-lg border border-gray-300 dark:border-gray-600 bg-gray-50 dark:bg-gray-900 focus:outline-none focus:ring-2 focus:ring-primary resize-none transition-all-300"
placeholder="描述您想要生成的图像..."></textarea>
<button id="randomImagePromptBtn"
class="p-3 bg-gray-100 dark:bg-gray-700 hover:bg-gray-200 dark:hover:bg-gray-600 rounded-lg transition-all-300"
title="随机示例">
<i class="fa fa-random text-primary mr-1"></i>随机
</button>
</div>
</div>
<div class="space-y-4">
<label class="block text-sm font-medium text-gray-700 dark:text-gray-300">上传参考图片 <span
class="text-red-500">*</span></label>
<div id="imageUploadArea"
class="border-2 border-dashed border-gray-300 dark:border-gray-600 rounded-lg p-8 text-center hover:border-primary dark:hover:border-primary transition-all-300 cursor-pointer bg-gray-50 dark:bg-gray-900">
<input type="file" id="imageUpload" accept="image/*" class="hidden">
<i class="fa fa-cloud-upload text-4xl text-gray-400 mb-3"></i>
<p class="text-gray-500 dark:text-gray-400 mb-2">拖放图片到此处,或</p>
<button id="browseImage"
class="px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded-lg transition-all-300">
浏览文件
</button>
</div>
<div id="imagePreview" class="hidden space-y-2">
<p class="text-sm font-medium text-gray-700 dark:text-gray-300">预览:</p>
<img id="previewImage" src="" alt="预览图片"
class="max-h-64 object-contain rounded-lg border border-gray-200 dark:border-gray-700 shadow-sm">
<button id="removeImage"
class="text-red-500 hover:text-red-600 text-sm flex items-center transition-colors">
<i class="fa fa-times-circle mr-1"></i> 移除图片
</button>
</div>
</div>
<div class="grid grid-cols-1 md:grid-cols-3 gap-6">
<div class="space-y-2">
<label for="imageStyle"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">风格</label>
<select id="imageStyle"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition-all-300">
<option value="default">默认</option>
<option value="realistic">写实</option>
<option value="cartoon">卡通</option>
<option value="anime">动漫</option>
<option value="watercolor">水彩</option>
<option value="oil">油画</option>
</select>
</div>
<div class="space-y-2">
<label for="imageAspectRatio"
class="block text-sm font-medium text-gray-700 dark:text-gray-300">比例</label>
<select id="imageAspectRatio"
class="w-full px-4 py-2 rounded-lg border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-800 focus:outline-none focus:ring-2 focus:ring-primary transition-all-300">
<option value="1:1">1:1 (正方形)</option>
<option value="16:9">16:9 (宽屏)</option>
<option value="9:16">9:16 (竖屏)</option>
<option value="4:3">4:3 (标准)</option>
<option value="3:2">3:2 (照片)</option>
</select>
</div>
<div class="space-y-2">
<label for="imageStrength" class="block text-sm font-medium text-gray-700 dark:text-gray-300">参考强度
(0-100)</label>
<input type="range" id="imageStrength" min="0" max="100" value="70"
class="w-full h-2 bg-gray-200 rounded-lg appearance-none cursor-pointer dark:bg-gray-700 accent-primary">
<div class="flex justify-between text-xs text-gray-500 dark:text-gray-400">
<span>更创意</span>
<span id="strengthValue">70</span>
<span>更相似</span>
</div>
</div>
</div>
</div>
<!-- 生成按钮 -->
<div class="p-6 md:p-8 bg-gray-50 dark:bg-gray-900 flex flex-col md:flex-row justify-between items-center space-y-4 md:space-y-0">
<div class="text-sm text-gray-500 dark:text-gray-400 flex items-center">
<i class="fa fa-lightbulb-o text-yellow-500 mr-2"></i>
<span>提示:生成高质量图像可能需要几秒钟时间</span>
</div>
<button id="generateBtn"
class="px-8 py-3 bg-primary hover:bg-primary/90 text-white rounded-lg shadow-lg hover:shadow-xl transform hover:-translate-y-1 transition-all-300 font-medium flex items-center">
<i class="fa fa-magic mr-2"></i>生成图像
</button>
</div>
</section>
<!-- 生成状态区域 -->
<section id="statusSection" class="mb-16 hidden">
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg p-6 md:p-8">
<div class="flex flex-col items-center text-center space-y-6">
<div id="thinkingIndicator" class="flex flex-col items-center space-y-4">
<div class="w-16 h-16 rounded-full bg-primary/10 flex items-center justify-center animate-pulse-slow">
<i class="fa fa-cog fa-spin text-2xl text-primary"></i>
</div>
<h3 class="text-xl font-semibold">AI 正在创作中...</h3>
<p class="text-gray-600 dark:text-gray-300 max-w-md">
请稍候,我们的 AI 正在努力为您生成精美的图像
</p>
</div>
<div id="progressBarContainer"
class="w-full max-w-2xl bg-gray-200 dark:bg-gray-700 rounded-full h-3 overflow-hidden">
<div id="progressBar"
class="bg-gradient-to-r from-primary to-secondary h-full w-0 transition-all duration-300"></div>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400">
<span id="progressText">0%</span> 完成
</div>
</div>
</div>
</section>
<!-- 结果展示区域 -->
<section id="resultSection" class="mb-16 hidden">
<div class="bg-white dark:bg-gray-800 rounded-2xl shadow-lg overflow-hidden">
<div class="p-6 md:p-8 border-b border-gray-200 dark:border-gray-700">
<h3 class="text-xl font-semibold mb-2">生成结果</h3>
<p class="text-gray-600 dark:text-gray-300">您的图像已成功生成</p>
</div>
<div class="p-6 md:p-8 flex flex-col items-center">
<div id="resultImageContainer" class="relative mb-8 max-w-3xl mx-auto">
<img id="resultImage" src="" alt="生成的图像"
class="max-w-full h-auto rounded-lg border border-gray-200 dark:border-gray-700 shadow-lg transform transition-all duration-500 hover:shadow-xl">
<div class="absolute top-4 right-4 flex space-x-2">
<button id="downloadBtn"
class="p-2 bg-white dark:bg-gray-800 rounded-full shadow-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-all-300"
title="下载图片">
<i class="fa fa-download"></i>
</button>
<button id="regenerateBtn"
class="p-2 bg-white dark:bg-gray-800 rounded-full shadow-md hover:bg-gray-100 dark:hover:bg-gray-700 transition-all-300"
title="重新生成">
<i class="fa fa-refresh"></i>
</button>
</div>
</div>
<div class="flex flex-wrap justify-center gap-4 w-full max-w-2xl">
<button id="variationBtn"
class="flex-1 px-4 py-2 border border-primary text-primary hover:bg-primary/10 rounded-lg transition-all-300 font-medium flex items-center justify-center">
<i class="fa fa-random mr-2"></i>生成变体
</button>
<button id="upscaleBtn"
class="flex-1 px-4 py-2 bg-primary hover:bg-primary/90 text-white rounded-lg transition-all-300 font-medium flex items-center justify-center">
<i class="fa fa-search-plus mr-2"></i>放大图像
</button>
</div>
</div>
</div>
</section>
<!-- 历史记录区域 -->
<section id="history" class="mb-16">
<div class="flex justify-between items-center mb-6">
<h3 class="text-2xl font-bold">历史记录</h3>
<button id="clearHistoryBtn"
class="text-sm text-gray-500 dark:text-gray-400 hover:text-red-500 transition-colors">
清除全部
</button>
</div>
<div id="historyGrid" class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<!-- 历史记录项会通过 JavaScript 动态添加 -->
<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>
</div>
</section>
<!-- 画廊区域 -->
<section id="gallery" class="mb-16">
<h3 class="text-2xl font-bold mb-6">精选画廊</h3>
<div class="grid grid-cols-1 sm:grid-cols-2 md:grid-cols-3 lg:grid-cols-4 gap-6">
<div class="overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group">
<img src="https://picsum.photos/seed/art1/600/400" alt="艺术作品"
class="w-full h-60 object-cover transition-transform duration-500 group-hover:scale-110">
<div class="p-4 bg-white dark:bg-gray-800">
<p class="font-medium truncate">梦幻城堡,超现实主义风格</p>
</div>
</div>
<div class="overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group">
<img src="https://picsum.photos/seed/art2/600/400" alt="艺术作品"
class="w-full h-60 object-cover transition-transform duration-500 group-hover:scale-110">
<div class="p-4 bg-white dark:bg-gray-800">
<p class="font-medium truncate">未来城市,赛博朋克风格</p>
</div>
</div>
<div class="overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group">
<img src="https://picsum.photos/seed/art3/600/400" alt="艺术作品"
class="w-full h-60 object-cover transition-transform duration-500 group-hover:scale-110">
<div class="p-4 bg-white dark:bg-gray-800">
<p class="font-medium truncate">奇幻森林,童话风格</p>
</div>
</div>
<div class="overflow-hidden rounded-xl shadow-md hover:shadow-xl transition-all-300 group">
<img src="https://picsum.photos/seed/art4/600/400" alt="艺术作品"
class="w-full h-60 object-cover transition-transform duration-500 group-hover:scale-110">
<div class="p-4 bg-white dark:bg-gray-800">
<p class="font-medium truncate">抽象艺术,色彩斑斓</p>
</div>
</div>
</div>
</section>
</main>
<footer class="bg-white dark:bg-gray-800 border-t border-gray-200 dark:border-gray-700 py-8">
<div class="container mx-auto px-4">
<div class="flex flex-col md:flex-row justify-between items-center">
<div class="flex items-center space-x-2 mb-4 md:mb-0">
<i class="fa fa-paint-brush text-primary text-xl"></i>
<h2 class="text-lg font-bold bg-gradient-to-r from-primary to-secondary bg-clip-text text-transparent">
Midjourney AI</h2>
</div>
<div class="text-sm text-gray-500 dark:text-gray-400 mb-4 md:mb-0">
© 2023 Midjourney AI. 保留所有权利。
</div>
<div class="flex space-x-4">
<a href="#"
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
<i class="fa fa-github text-xl"></i>
</a>
<a href="#"
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
<i class="fa fa-twitter text-xl"></i>
</a>
<a href="#"
class="text-gray-500 dark:text-gray-400 hover:text-primary dark:hover:text-primary transition-colors">
<i class="fa fa-instagram text-xl"></i>
</a>
</div>
</div>
</div>
</footer>
<script>
// ==============================================
// 全局函数 - 必须放在所有事件监听和调用之前
// ==============================================
function checkTaskStatus(taskId) {
if (!taskId) {
console.error('任务ID不存在');
return;
}
fetch(`/api/mj/task_status?task_id=${encodeURIComponent(taskId)}`)
.then(response => {
if (!response.ok) throw new Error('网络响应不正常');
return response.json();
})
.then(data => {
const progressBar = document.getElementById('progress-bar');
const progressText = document.getElementById('progress-text');
const statusDisplay = document.getElementById('status-display');
if (progressBar && progressText) {
const progress = data.progress || 0;
progressBar.style.width = `${progress}%`;
progressText.textContent = `${progress}%`;
}
if (statusDisplay) {
statusDisplay.textContent = `状态: ${data.status || '未知'}`;
}
if (data.status === 'completed') {
stopPolling();
displayResult(data.result);
addToHistory(data);
} else if (data.status === 'failed') {
stopPolling();
showError(data.error || '生成失败,请重试');
}
})
.catch(error => {
console.error('获取任务状态时出错:', error);
stopPolling();
showError('获取状态失败: ' + error.message);
});
}
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;
});
// 生成按钮点击事件
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);
});
// 清除历史记录按钮点击事件
const clearHistoryBtn = document.getElementById('clearHistoryBtn');
clearHistoryBtn.addEventListener('click', function () {
if (confirm('确定要清除所有历史记录吗?')) {
localStorage.removeItem('mjHistory');
updateHistoryGrid();
}
});
// 生成图像函数
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;
}
// 提交生成请求
submitGenerateRequest(prompt, base64Array);
}
// 提交生成请求函数
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)
});
if (!response.ok) {
throw new Error(`提交请求失败: ${response.statusText}`);
}
const data = await response.json();
const taskId = data.task_id;
// 显示状态区域
document.getElementById('statusSection').classList.remove('hidden');
// 开始轮询任务状态
pollTaskStatus(taskId, prompt);
} catch (error) {
console.error('生成图像失败:', error);
alert(`生成图像失败: ${error.message}`);
// 恢复生成按钮
generateBtn.disabled = false;
generateBtn.innerHTML = '<i class="fa fa-magic mr-2"></i>生成图像';
}
}
// 轮询任务状态函数
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
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>
`;
historyGrid.appendChild(historyItem);
});
// 添加历史记录项点击事件
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;
});
});
// 初始化历史记录网格
updateHistoryGrid();
}
</script>
<!-- 添加随机示例功能的JavaScript -->
<script>
// 文生图示例提示词库
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);
});
}
});
</script>
</body>
</html>