Spaces:
Running
Running
File size: 11,496 Bytes
ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f 03c93e3 3f49f6f 03c93e3 ea94f5e 3f49f6f 03c93e3 3f49f6f 03c93e3 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f 03c93e3 3f49f6f 03c93e3 3f49f6f 03c93e3 3f49f6f 03c93e3 3f49f6f ea94f5e 3f49f6f ea94f5e 3f49f6f ea94f5e |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Creator - Next Gen</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
<script>
tailwind.config = {
theme: {
extend: {
colors: {
'neon-blue': '#00d4ff',
'neon-purple': '#8b5cf6',
'neon-pink': '#ec4899',
'dark-bg': '#0a0a0a',
'card-bg': '#1a1a1a',
'border-glow': '#00d4ff40'
},
animation: {
'float': 'float 6s ease-in-out infinite',
'glow': 'glow 2s ease-in-out infinite alternate',
'pulse-slow': 'pulse 3s cubic-bezier(0.4, 0, 0.6, 1) infinite',
}
}
}
}
</script>
<style>
@keyframes float {
0%, 100% { transform: translateY(0px); }
50% { transform: translateY(-20px); }
}
@keyframes glow {
from { box-shadow: 0 0 20px #00d4ff; }
to { box-shadow: 0 0 30px #00d4ff, 0 0 40px #00d4ff; }
}
.gradient-text {
background: linear-gradient(45deg, #00d4ff, #8b5cf6, #ec4899);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
}
.glass-effect {
background: rgba(255, 255, 255, 0.05);
backdrop-filter: blur(10px);
border: 1px solid rgba(255, 255, 255, 0.1);
}
.neon-border {
border: 1px solid #00d4ff;
box-shadow: 0 0 10px #00d4ff40;
}
.neon-border:hover {
box-shadow: 0 0 20px #00d4ff, 0 0 30px #00d4ff40;
}
</style>
</head>
<body class="bg-dark-bg text-white min-h-screen font-sans">
<main class="pt-20">
<section class="min-h-screen flex flex-col items-center justify-center">
<div class="max-w-xl w-full mx-auto px-4 py-12">
<div class="text-center mb-10">
<h2 class="text-5xl font-bold gradient-text mb-4 animate-float">AI Image Generator</h2>
<p class="text-lg text-gray-400 mb-6">Choose a model, enter your prompt, and create stunning images instantly!</p>
</div>
<div class="glass-effect rounded-3xl p-8 neon-border">
<div class="mb-6">
<label class="block mb-2 text-neon-blue font-semibold">Select Model</label>
<select id="modelSelect" class="w-full bg-card-bg border border-white/20 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-neon-blue" onchange="toggleFluxOptions()">
<option value="heartsync">Heartsync (SSE, best for anime)</option>
<option value="flux">FLUX.1-dev (fast, general purpose)</option>
</select>
</div>
<div id="fluxOptions" class="mb-6 hidden">
<label class="block mb-2 text-neon-blue font-semibold">Aspect Ratio (FLUX only)</label>
<select id="fluxAspect" class="w-full bg-card-bg border border-white/20 rounded-xl px-4 py-3 text-white focus:outline-none focus:border-neon-blue" onchange="updateFluxResolution()">
<option value="1:1" selected>1:1 (Square)</option>
<option value="16:9">16:9 (Landscape)</option>
<option value="9:16">9:16 (Portrait)</option>
<option value="4:3">4:3</option>
<option value="3:2">3:2</option>
<option value="2:3">2:3</option>
</select>
</div>
<div class="mb-6">
<label class="block mb-2 text-neon-blue font-semibold">Prompt</label>
<textarea id="promptInput" class="w-full h-32 bg-card-bg border border-white/20 rounded-2xl px-6 py-4 text-white placeholder-gray-500 focus:outline-none focus:border-neon-blue focus:ring-2 focus:ring-neon-blue/20 resize-none" placeholder="Describe your vision... (Resolution options below are for FLUX only)"></textarea>
</div>
<div class="mb-6">
<button id="generateBtn" onclick="generateImage()" class="w-full px-8 py-3 bg-gradient-to-r from-neon-blue to-neon-purple rounded-full font-semibold hover:opacity-90 transition-opacity animate-glow">
<i class="fas fa-magic mr-2"></i>Generate
</button>
</div>
<div id="loadingState" class="hidden text-center py-6">
<div class="inline-flex items-center space-x-4">
<div class="w-8 h-8 border-4 border-neon-blue border-t-transparent rounded-full animate-spin"></div>
<span class="text-neon-blue text-lg">Generating image...</span>
</div>
<div id="queueInfo" class="mt-4 text-gray-400"></div>
</div>
<div id="resultSection" class="hidden mt-8">
<div class="glass-effect rounded-2xl p-6 neon-border">
<img id="generatedImage" class="w-full rounded-xl shadow-2xl" alt="Generated image">
<div class="mt-6 flex justify-center">
<button onclick="downloadImage()" class="px-6 py-3 glass-effect rounded-full hover:neon-border transition-all">
<i class="fas fa-download mr-2"></i>Download
</button>
</div>
</div>
</div>
</div>
</div>
</section>
</main>
<script>
let generatedImageUrl = null;
function showLoading(show, queueText = '') {
document.getElementById('loadingState').classList.toggle('hidden', !show);
document.getElementById('queueInfo').textContent = queueText;
}
function showResult(url) {
generatedImageUrl = url;
document.getElementById('generatedImage').src = url;
document.getElementById('resultSection').classList.remove('hidden');
}
function hideResult() {
document.getElementById('resultSection').classList.add('hidden');
}
function toggleFluxOptions() {
const model = document.getElementById('modelSelect').value;
document.getElementById('fluxOptions').classList.toggle('hidden', model !== 'flux');
}
function updateFluxResolution() {
// This function is for future extensibility if you want to display the actual width/height to the user
}
function getFluxResolution() {
const aspect = document.getElementById('fluxAspect').value;
switch (aspect) {
case '1:1': return { width: 1024, height: 1024 };
case '16:9': return { width: 1280, height: 720 };
case '9:16': return { width: 720, height: 1280 };
case '4:3': return { width: 1024, height: 768 };
case '3:2': return { width: 1200, height: 800 };
case '2:3': return { width: 800, height: 1200 };
default: return { width: 1024, height: 1024 };
}
}
async function generateImage() {
const prompt = document.getElementById('promptInput').value.trim();
const model = document.getElementById('modelSelect').value;
let width, height;
if (!prompt) {
alert('Please enter a prompt');
return;
}
hideResult();
showLoading(true);
if (model === 'heartsync' || model === 'flux') {
// SSE logic for both models
if (model === 'flux') {
const res = getFluxResolution();
width = res.width;
height = res.height;
}
try {
const url = model === 'heartsync' ? '/api/generate' : '/api/generate/flux';
const body = model === 'heartsync' ? { prompt } : { prompt, width, height };
const response = await fetch(url, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(body)
});
const reader = response.body.getReader();
const decoder = new TextDecoder();
while (true) {
const { done, value } = await reader.read();
if (done) break;
const chunk = decoder.decode(value);
const lines = chunk.split('\n');
for (const line of lines) {
if (line.startsWith('data: ')) {
try {
const data = JSON.parse(line.substring(6));
if (data.type === 'estimation') {
showLoading(true, `Queue position: ${data.queueSize}, ETA: ${data.eta}s`);
} else if (data.type === 'processing') {
showLoading(true, 'Processing your image...');
} else if (data.type === 'success') {
showLoading(false);
if (model === 'flux') {
showResult(data.imageUrl);
} else {
showResult(data.originalUrl);
}
return;
} else if (data.type === 'error') {
throw new Error(data.message);
}
} catch (e) { console.error('Error parsing SSE data:', e); }
}
}
}
} catch (error) {
showLoading(false);
alert('Generation failed: ' + error.message);
}
}
}
function downloadImage() {
if (!generatedImageUrl) return;
const link = document.createElement('a');
link.href = generatedImageUrl;
link.download = 'generated-image.jpg';
link.click();
}
window.onload = function() { toggleFluxOptions(); updateFluxResolution(); };
</script>
</body>
</html> |