vision / public /index.html
shashwatIDR's picture
Update public/index.html
03c93e3 verified
<!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>