| Current Path : /var/www/pythonian/qwen/static/ |
| Current File : /var/www/pythonian/qwen/static/image-generator.html |
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>AI Image Generator</title>
<script src="https://cdn.tailwindcss.com"></script>
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css">
<style>
.switch {
position: relative;
display: inline-block;
width: 50px;
height: 24px;
}
.switch input {
opacity: 0;
width: 0;
height: 0;
}
.slider {
position: absolute;
cursor: pointer;
top: 0;
left: 0;
right: 0;
bottom: 0;
background-color: #ccc;
transition: 0.4s;
border-radius: 24px;
}
.slider:before {
position: absolute;
content: "";
height: 18px;
width: 18px;
left: 3px;
bottom: 3px;
background-color: white;
transition: 0.4s;
border-radius: 50%;
}
input:checked + .slider {
background-color: #3b82f6;
}
input:checked + .slider:before {
transform: translateX(26px);
}
.spinner {
border: 6px solid #f3f3f3;
border-top: 6px solid #3b82f6;
border-radius: 50%;
width: 60px;
height: 60px;
animation: spin 1s linear infinite;
margin: 0 auto 1rem;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body class="bg-gray-50 min-h-screen">
<div x-data="imageGenerator()" class="max-w-6xl mx-auto p-4 sm:p-6 lg:p-8">
<!-- Header -->
<div class="text-center mb-8">
<h1 class="text-4xl font-bold text-gray-800 mb-2">
<i class="fas fa-palette mr-3 text-blue-500"></i>
AI Image Generator
</h1>
<p class="text-gray-600 text-lg">Create stunning images with AI-powered generation</p>
</div>
<!-- Generator Form -->
<div class="bg-white rounded-xl shadow-lg p-6 mb-8">
<!-- Prompt Input -->
<div class="mb-6">
<label for="prompt" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-pen mr-2 text-blue-500"></i>
Prompt <span class="text-red-500">*</span>
</label>
<textarea
id="prompt"
x-model="prompt"
placeholder="Describe the image you want to create..."
rows="4"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"
></textarea>
<p class="mt-1 text-xs text-gray-500">Be specific and descriptive for better results</p>
</div>
<!-- Negative Prompt -->
<div class="mb-6">
<label for="negativePrompt" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-ban mr-2 text-red-500"></i>
Negative Prompt
</label>
<textarea
id="negativePrompt"
x-model="negativePrompt"
placeholder="Elements to avoid in the image..."
rows="2"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"
></textarea>
</div>
<!-- Options Grid -->
<div class="grid grid-cols-1 md:grid-cols-3 gap-6 mb-6">
<!-- Model Selection -->
<div>
<label for="model" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-brain mr-2 text-purple-500"></i>
Model
</label>
<select
id="model"
x-model="model"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"
>
<option value="qwen-image-plus">Qwen Image Plus (Default)</option>
<option value="qwen-image">Qwen Image</option>
<option value="qwen-image-max">Qwen Image Max</option>
</select>
</div>
<!-- Number of Images -->
<div>
<label for="n" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-images mr-2 text-green-500"></i>
Number of Images
</label>
<input
type="number"
id="n"
x-model.number="n"
min="1"
max="4"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"
/>
</div>
<!-- Size Selection -->
<div>
<label for="size" class="block text-sm font-semibold text-gray-700 mb-2">
<i class="fas fa-expand mr-2 text-orange-500"></i>
Size
</label>
<select
id="size"
x-model="size"
class="w-full px-4 py-3 border-2 border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-500 focus:border-transparent transition"
>
<option value="1664*928">1664×928 (Default)</option>
<option value="1024*1024">1024×1024 (Square)</option>
<option value="1280*720">1280×720 (HD)</option>
<option value="1920*1080">1920×1080 (Full HD)</option>
</select>
</div>
</div>
<!-- Toggle Options -->
<div class="grid grid-cols-1 md:grid-cols-2 gap-4 mb-6 p-4 bg-gray-50 rounded-lg">
<div class="flex items-center">
<label class="switch mr-3">
<input type="checkbox" x-model="promptExtend" />
<span class="slider"></span>
</label>
<div>
<span class="font-semibold text-gray-700">Prompt Extend</span>
<p class="text-xs text-gray-500">Automatically enhance your prompt</p>
</div>
</div>
<div class="flex items-center">
<label class="switch mr-3">
<input type="checkbox" x-model="watermark" />
<span class="slider"></span>
</label>
<div>
<span class="font-semibold text-gray-700">Add Watermark</span>
<p class="text-xs text-gray-500">Add watermark to generated images</p>
</div>
</div>
</div>
<!-- Action Buttons -->
<div class="flex flex-col sm:flex-row gap-4 justify-center">
<button
@click="generateImage"
:disabled="isGenerating || !prompt"
class="px-8 py-4 bg-gradient-to-r from-blue-500 to-purple-600 text-white font-bold rounded-lg hover:from-blue-600 hover:to-purple-700 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 disabled:opacity-50 disabled:cursor-not-allowed transition transform hover:-translate-y-1"
>
<template x-if="isGenerating">
<span class="flex items-center justify-center">
<i class="fas fa-spinner fa-spin mr-2"></i>
Generating...
</span>
</template>
<template x-if="!isGenerating">
<span class="flex items-center justify-center">
<i class="fas fa-magic mr-2"></i>
Generate Image
</span>
</template>
</button>
<button
@click="resetForm"
class="px-8 py-4 bg-red-500 text-white font-bold rounded-lg hover:bg-red-600 focus:outline-none focus:ring-2 focus:ring-red-500 focus:ring-offset-2 transition transform hover:-translate-y-1"
>
<i class="fas fa-redo mr-2"></i>
Reset
</button>
</div>
</div>
<!-- Error Message -->
<template x-if="errorMessage">
<div class="bg-red-50 border-l-4 border-red-500 p-4 mb-6 rounded">
<div class="flex items-center">
<i class="fas fa-exclamation-triangle text-red-500 mr-3 text-xl"></i>
<p class="text-red-700 font-medium" x-text="errorMessage"></p>
</div>
</div>
</template>
<!-- Loading Overlay -->
<template x-if="isGenerating">
<div class="fixed inset-0 bg-white bg-opacity-95 z-50 flex items-center justify-center">
<div class="text-center">
<div class="spinner"></div>
<p class="text-2xl font-bold text-gray-800">Creating your masterpiece...</p>
<p class="text-gray-600 mt-2">This may take a moment</p>
</div>
</div>
</template>
<!-- Results Section -->
<template x-if="generatedImages.length > 0">
<div class="mt-8">
<h2 class="text-2xl font-bold text-gray-800 mb-6 text-center">
<i class="fas fa-images mr-2 text-blue-500"></i>
Generated Images
</h2>
<div class="grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-6">
<template x-for="(image, index) in generatedImages" :key="index">
<div class="bg-white rounded-xl shadow-lg overflow-hidden hover:shadow-xl transition">
<img
:src="image.url"
:alt="`Generated image ${index + 1}`"
class="w-full h-auto"
@error="handleImageError"
/>
<div class="p-4 bg-gray-50">
<div class="flex gap-2 justify-center">
<button
@click="downloadImage(image.url)"
class="flex-1 px-4 py-2 bg-green-500 text-white font-semibold rounded-lg hover:bg-green-600 focus:outline-none focus:ring-2 focus:ring-green-500 focus:ring-offset-2 transition"
>
<i class="fas fa-download mr-2"></i>
Download
</button>
<button
@click="copyImageUrl(image.url)"
class="flex-1 px-4 py-2 bg-blue-500 text-white font-semibold rounded-lg hover:bg-blue-600 focus:outline-none focus:ring-2 focus:ring-blue-500 focus:ring-offset-2 transition"
>
<i class="fas fa-copy mr-2"></i>
Copy URL
</button>
</div>
</div>
</div>
</template>
</div>
</div>
</template>
</div>
<script>
document.addEventListener('alpine:init', () => {
Alpine.data('imageGenerator', () => ({
// Form data
prompt: '',
negativePrompt: '',
n: 1,
size: '1664*928',
promptExtend: true,
watermark: false,
model: 'qwen-image-plus',
// State
isGenerating: false,
errorMessage: '',
generatedImages: [],
requestId: '',
// Generate image
async generateImage() {
if (!this.prompt.trim()) {
this.errorMessage = 'Please enter a prompt to generate an image.';
return;
}
this.isGenerating = true;
this.errorMessage = '';
this.generatedImages = [];
try {
const response = await fetch('/generate-image', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify({
prompt: this.prompt,
negative_prompt: this.negativePrompt,
n: this.n,
size: this.size,
prompt_extend: this.promptExtend,
watermark: this.watermark,
model: this.model
})
});
const data = await response.json();
if (response.ok) {
this.generatedImages = data.image_urls.map(url => ({
url: url,
prompt: data.prompt
}));
this.requestId = data.request_id;
this.errorMessage = '';
} else {
this.errorMessage = data.error || 'Image generation failed';
}
} catch (error) {
this.errorMessage = `Error: ${error.message}`;
console.error('Generation error:', error);
} finally {
this.isGenerating = false;
}
},
// Reset form
resetForm() {
this.prompt = '';
this.negativePrompt = '';
this.n = 1;
this.size = '1664*928';
this.promptExtend = true;
this.watermark = false;
this.model = 'qwen-image-plus';
this.generatedImages = [];
this.errorMessage = '';
},
// Download image
async downloadImage(url) {
try {
const response = await fetch(url);
const blob = await response.blob();
const downloadUrl = window.URL.createObjectURL(blob);
const link = document.createElement('a');
link.href = downloadUrl;
link.download = `generated-image-${Date.now()}.png`;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
window.URL.revokeObjectURL(downloadUrl);
} catch (error) {
console.error('Download error:', error);
alert('Failed to download image');
}
},
// Copy image URL
copyImageUrl(url) {
const fullUrl = window.location.origin + url;
navigator.clipboard.writeText(fullUrl)
.then(() => {
alert('Image URL copied to clipboard!');
})
.catch(() => {
alert('Failed to copy URL');
});
},
// Handle image load error
handleImageError(event) {
event.target.src = 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cmVjdCB3aWR0aD0iNDAwIiBoZWlnaHQ9IjMwMCIgZmlsbD0iI2YwZjBmMCIvPjx0ZXh0IHg9IjUwJSIgeT0iNTAlIiBmb250LWZhbWlseT0iQXJpYWwiIGZvbnQtc2l6ZT0iMjQiIHRleHQtYW5jaG9yPSJtaWRkbGUiIGZpbGw9IiM5OTk5OTkiPkltYWdlIExvYWRlZCBGYWlsZWQ8L3RleHQ+PC9zdmc+';
}
}));
});
</script>
</body>
</html>