This commit is contained in:
2025-08-25 14:30:29 +08:00
parent 71a67388fe
commit 0987c97df4

725
dsLightRag/static/mj.html Normal file
View File

@@ -0,0 +1,725 @@
<!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.jsdelivr.net/npm/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>
<textarea id="prompt" rows="4" class="w-full 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>
<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>
<textarea id="imagePrompt" rows="2" class="w-full 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>
</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>
<!-- 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');
// 初始化主题
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 {
document.documentElement.classList.add('dark');
localStorage.setItem('theme', 'dark');
}
});
// 移动端菜单
menuToggle.addEventListener('click', () => {
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');
});
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');
});
// 图片上传
browseImage.addEventListener('click', () => {
imageUpload.click();
});
imageUploadArea.addEventListener('click', (e) => {
if (e.target === imageUploadArea || e.target === browseImage || e.target.parentNode === browseImage) {
imageUpload.click();
}
});
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', () => {
previewImage.src = '';
imagePreview.classList.add('hidden');
imageUploadArea.classList.remove('hidden');
imageUpload.value = '';
});
// 拖放上传
imageUploadArea.addEventListener('dragover', (e) => {
e.preventDefault();
imageUploadArea.classList.add('border-primary', 'bg-primary/5');
});
imageUploadArea.addEventListener('dragleave', () => {
imageUploadArea.classList.remove('border-primary', 'bg-primary/5');
});
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);
}
});
// 强度滑块
imageStrength.addEventListener('input', () => {
strengthValue.textContent = imageStrength.value;
});
// 生成图像
generateBtn.addEventListener('click', async () => {
// 验证输入
let prompt, style, aspectRatio, quality;
let base64Array = null;
if (textToImagePanel.classList.contains('hidden')) {
// 图生图
prompt = document.getElementById('imagePrompt').value.trim();
style = document.getElementById('imageStyle').value;
aspectRatio = document.getElementById('imageAspectRatio').value;
if (!prompt) {
alert('请输入提示词');
return;
}
if (imageUploadArea.classList.contains('hidden')) {
// 有上传图片
const canvas = document.createElement('canvas');
const ctx = canvas.getContext('2d');
const img = new Image();
img.src = previewImage.src;
await new Promise((resolve) => {
img.onload = resolve;
});
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);
}
}, 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);
});
// 重新生成
regenerateBtn.addEventListener('click', () => {
generateBtn.click();
});
// 生成变体
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;
}
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')}`;
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}">
<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' });
}
});
});
}
// 清除历史记录
clearHistoryBtn.addEventListener('click', () => {
if (confirm('确定要清除所有历史记录吗?此操作不可恢复。')) {
localStorage.removeItem('midjourneyHistory');
updateHistoryDisplay();
}
});
// 初始化历史记录显示
updateHistoryDisplay();
</script>
</body>
</html>