Roshan1162003 commited on
Commit
9f67cf9
·
1 Parent(s): a9d185a

Add Flask image generation app

Browse files
Files changed (3) hide show
  1. app.py +101 -145
  2. requirements.txt +7 -6
  3. templates/index.html +601 -0
app.py CHANGED
@@ -1,154 +1,110 @@
1
- import gradio as gr
2
- import numpy as np
3
- import random
4
-
5
- # import spaces #[uncomment to use ZeroGPU]
6
- from diffusers import DiffusionPipeline
7
  import torch
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8
 
9
- device = "cuda" if torch.cuda.is_available() else "cpu"
10
- model_repo_id = "stabilityai/sdxl-turbo" # Replace to the model you would like to use
11
-
12
  if torch.cuda.is_available():
13
- torch_dtype = torch.float16
 
 
 
 
 
14
  else:
15
- torch_dtype = torch.float32
16
-
17
- pipe = DiffusionPipeline.from_pretrained(model_repo_id, torch_dtype=torch_dtype)
18
- pipe = pipe.to(device)
19
-
20
- MAX_SEED = np.iinfo(np.int32).max
21
- MAX_IMAGE_SIZE = 1024
22
-
23
-
24
- # @spaces.GPU #[uncomment to use ZeroGPU]
25
- def infer(
26
- prompt,
27
- negative_prompt,
28
- seed,
29
- randomize_seed,
30
- width,
31
- height,
32
- guidance_scale,
33
- num_inference_steps,
34
- progress=gr.Progress(track_tqdm=True),
35
- ):
36
- if randomize_seed:
37
- seed = random.randint(0, MAX_SEED)
38
-
39
- generator = torch.Generator().manual_seed(seed)
40
-
41
- image = pipe(
42
- prompt=prompt,
43
- negative_prompt=negative_prompt,
44
- guidance_scale=guidance_scale,
45
- num_inference_steps=num_inference_steps,
46
- width=width,
47
- height=height,
48
- generator=generator,
49
- ).images[0]
50
-
51
- return image, seed
52
-
53
-
54
- examples = [
55
- "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
56
- "An astronaut riding a green horse",
57
- "A delicious ceviche cheesecake slice",
58
- ]
59
 
60
- css = """
61
- #col-container {
62
- margin: 0 auto;
63
- max-width: 640px;
 
64
  }
65
- """
66
-
67
- with gr.Blocks(css=css) as demo:
68
- with gr.Column(elem_id="col-container"):
69
- gr.Markdown(" # Text-to-Image Gradio Template")
70
-
71
- with gr.Row():
72
- prompt = gr.Text(
73
- label="Prompt",
74
- show_label=False,
75
- max_lines=1,
76
- placeholder="Enter your prompt",
77
- container=False,
78
- )
79
-
80
- run_button = gr.Button("Run", scale=0, variant="primary")
81
-
82
- result = gr.Image(label="Result", show_label=False)
83
-
84
- with gr.Accordion("Advanced Settings", open=False):
85
- negative_prompt = gr.Text(
86
- label="Negative prompt",
87
- max_lines=1,
88
- placeholder="Enter a negative prompt",
89
- visible=False,
90
- )
91
-
92
- seed = gr.Slider(
93
- label="Seed",
94
- minimum=0,
95
- maximum=MAX_SEED,
96
- step=1,
97
- value=0,
98
- )
99
 
100
- randomize_seed = gr.Checkbox(label="Randomize seed", value=True)
101
-
102
- with gr.Row():
103
- width = gr.Slider(
104
- label="Width",
105
- minimum=256,
106
- maximum=MAX_IMAGE_SIZE,
107
- step=32,
108
- value=1024, # Replace with defaults that work for your model
109
- )
110
-
111
- height = gr.Slider(
112
- label="Height",
113
- minimum=256,
114
- maximum=MAX_IMAGE_SIZE,
115
- step=32,
116
- value=1024, # Replace with defaults that work for your model
117
- )
118
-
119
- with gr.Row():
120
- guidance_scale = gr.Slider(
121
- label="Guidance scale",
122
- minimum=0.0,
123
- maximum=10.0,
124
- step=0.1,
125
- value=0.0, # Replace with defaults that work for your model
126
- )
127
-
128
- num_inference_steps = gr.Slider(
129
- label="Number of inference steps",
130
- minimum=1,
131
- maximum=50,
132
- step=1,
133
- value=2, # Replace with defaults that work for your model
134
- )
135
-
136
- gr.Examples(examples=examples, inputs=[prompt])
137
- gr.on(
138
- triggers=[run_button.click, prompt.submit],
139
- fn=infer,
140
- inputs=[
141
- prompt,
142
- negative_prompt,
143
- seed,
144
- randomize_seed,
145
- width,
146
- height,
147
- guidance_scale,
148
- num_inference_steps,
149
- ],
150
- outputs=[result, seed],
151
- )
 
 
 
 
 
 
 
 
 
152
 
153
  if __name__ == "__main__":
154
- demo.launch()
 
1
+ from flask import Flask, render_template, request, jsonify
2
+ import os
 
 
 
 
3
  import torch
4
+ from diffusers import StableDiffusionPipeline
5
+ from PIL import Image
6
+ import re
7
+ import time
8
+
9
+ app = Flask(__name__)
10
+
11
+ # Define paths
12
+ MODEL_PATH = "Roshan1162003/fine_tuned_model" # Replace with your HF model repository ID
13
+ STATIC_IMAGES_PATH = os.path.join("static", "images")
14
+ os.makedirs(STATIC_IMAGES_PATH, exist_ok=True)
15
+
16
+ # Restricted terms for prompt filtering
17
+ RESTRICTED_TERMS = [
18
+ "crime", "abuse", "violence", "illegal", "explicit", "nsfw",
19
+ "offensive", "hate", "nude", "porn", "gore", "drug"
20
+ ]
21
 
22
+ # Load the fine-tuned model
23
+ pipe = None
 
24
  if torch.cuda.is_available():
25
+ pipe = StableDiffusionPipeline.from_pretrained(
26
+ MODEL_PATH,
27
+ torch_dtype=torch.float16,
28
+ use_safetensors=True,
29
+ use_auth_token=os.getenv("HF_TOKEN") # Use HF_TOKEN from environment
30
+ ).to("cuda")
31
  else:
32
+ pipe = StableDiffusionPipeline.from_pretrained(
33
+ MODEL_PATH,
34
+ torch_dtype=torch.float32,
35
+ use_safetensors=True,
36
+ use_auth_token=os.getenv("HF_TOKEN")
37
+ )
38
+ print("Model loaded successfully")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
39
 
40
+ # Aspect ratio to resolution mapping
41
+ ASPECT_RATIOS = {
42
+ "1:1": (512, 512),
43
+ "4:3": (512, 384),
44
+ "16:9": (512, 288)
45
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
46
 
47
+ def is_prompt_safe(prompt):
48
+ """Check if prompt contains restricted terms."""
49
+ prompt_lower = prompt.lower()
50
+ for term in RESTRICTED_TERMS:
51
+ if re.search(r'\b' + re.escape(term) + r'\b', prompt_lower):
52
+ return False
53
+ return True
54
+
55
+ @app.route("/", methods=["GET"])
56
+ def index():
57
+ return render_template("index.html")
58
+
59
+ @app.route("/generate", methods=["POST"])
60
+ def generate():
61
+ try:
62
+ # Get form data
63
+ prompt = request.form.get("prompt", "").strip()
64
+ num_images = int(request.form.get("num_images", 1))
65
+ aspect_ratio = request.form.get("aspect_ratio", "1:1")
66
+ model_name = request.form.get("model", "stable_diffusion")
67
+
68
+ # Validate inputs
69
+ if not prompt:
70
+ return jsonify({"error": "Prompt is required"}), 400
71
+ if model_name != "stable_diffusion":
72
+ return jsonify({"error": "Selected model is locked"}), 400
73
+ if num_images < 1 or num_images > 5:
74
+ return jsonify({"error": "Number of images must be between 1 and 5"}), 400
75
+ if aspect_ratio not in ASPECT_RATIOS:
76
+ return jsonify({"error": "Invalid aspect ratio"}), 400
77
+
78
+ # Check for restricted terms
79
+ if not is_prompt_safe(prompt):
80
+ return jsonify({
81
+ "error": "You are violating the regulation policy terms and conditions due to restricted terms in the prompt."
82
+ }), 400
83
+
84
+ # Get resolution for aspect ratio
85
+ width, height = ASPECT_RATIOS[aspect_ratio]
86
+
87
+ # Generate images
88
+ image_paths = []
89
+ for i in range(num_images):
90
+ image = pipe(
91
+ prompt,
92
+ width=width,
93
+ height=height,
94
+ num_inference_steps=50,
95
+ guidance_scale=7.5,
96
+ seed=42 + i
97
+ ).images[0]
98
+ # Save image
99
+ timestamp = int(time.time() * 1000)
100
+ image_path = os.path.join(STATIC_IMAGES_PATH, f"generated_{timestamp}_{i}.png")
101
+ image.save(image_path)
102
+ image_paths.append(image_path.replace("static/", ""))
103
+
104
+ return jsonify({"images": image_paths})
105
+
106
+ except Exception as e:
107
+ return jsonify({"error": str(e)}), 500
108
 
109
  if __name__ == "__main__":
110
+ app.run(host="0.0.0.0", port=7860, debug=False)
requirements.txt CHANGED
@@ -1,6 +1,7 @@
1
- accelerate
2
- diffusers
3
- invisible_watermark
4
- torch
5
- transformers
6
- xformers
 
 
1
+ flask==3.0.3
2
+ torch==2.3.0
3
+ diffusers==0.34.0
4
+ transformers==4.52.0
5
+ accelerate==0.33.0
6
+ safetensors==0.4.5
7
+ pillow==10.4.0
templates/index.html ADDED
@@ -0,0 +1,601 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AI Image Studio - Professional Text-to-Image Generator</title>
7
+ <link href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.4.0/css/all.min.css" rel="stylesheet">
8
+ <style>
9
+ * {
10
+ margin: 0;
11
+ padding: 0;
12
+ box-sizing: border-box;
13
+ }
14
+
15
+ body {
16
+ font-family: 'Segoe UI', Tango, Geneva, Verdana, sans-serif;
17
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
18
+ min-height: 100vh;
19
+ padding: 20px;
20
+ line-height: 1.6;
21
+ }
22
+
23
+ .main-container {
24
+ max-width: 1200px;
25
+ margin: 0 auto;
26
+ }
27
+
28
+ /* Header */
29
+ .header {
30
+ text-align: center;
31
+ margin-bottom: 40px;
32
+ color: white;
33
+ }
34
+
35
+ .header h1 {
36
+ font-size: 3rem;
37
+ font-weight: 300;
38
+ margin-bottom: 10px;
39
+ letter-spacing: -1px;
40
+ }
41
+
42
+ .header .subtitle {
43
+ font-size: 1.2rem;
44
+ opacity: 0.9;
45
+ font-weight: 300;
46
+ }
47
+
48
+ /* Main Content Grid */
49
+ .content-grid {
50
+ display: grid;
51
+ grid-template-columns: 1fr 1fr;
52
+ gap: 30px;
53
+ align-items: start;
54
+ }
55
+
56
+ /* Control Panel */
57
+ .control-panel {
58
+ background: white;
59
+ border-radius: 16px;
60
+ padding: 32px;
61
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
62
+ backdrop-filter: blur(10px);
63
+ }
64
+
65
+ .panel-title {
66
+ font-size: 1.5rem;
67
+ font-weight: 600;
68
+ color: #2d3748;
69
+ margin-bottom: 24px;
70
+ display: flex;
71
+ align-items: center;
72
+ gap: 12px;
73
+ }
74
+
75
+ .form-group {
76
+ margin-bottom: 24px;
77
+ }
78
+
79
+ .form-label {
80
+ display: block;
81
+ font-size: 0.875rem;
82
+ font-weight: 600;
83
+ color: #4a5568;
84
+ margin-bottom: 8px;
85
+ text-transform: uppercase;
86
+ letter-spacing: 0.5px;
87
+ }
88
+
89
+ .form-input, .form-select, .form-textarea {
90
+ width: 100%;
91
+ padding: 14px 16px;
92
+ border: 2px solid #e2e8f0;
93
+ border-radius: 12px;
94
+ font-size: 1rem;
95
+ transition: all 0.3s ease;
96
+ background: #f8fafc;
97
+ }
98
+
99
+ .form-input:focus, .form-select:focus, .form-textarea:focus {
100
+ outline: none;
101
+ border-color: #667eea;
102
+ background: white;
103
+ box-shadow: 0 0 0 3px rgba(102, 126, 234, 0.1);
104
+ }
105
+
106
+ .form-textarea {
107
+ min-height: 120px;
108
+ resize: vertical;
109
+ font-family: inherit;
110
+ }
111
+
112
+ .form-select {
113
+ cursor: pointer;
114
+ background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 20 20'%3e%3cpath stroke='%236b7280' stroke-linecap='round' stroke-linejoin='round' stroke-width='1.5' d='m6 8 4 4 4-4'/%3e%3c/svg%3e");
115
+ background-position: right 12px center;
116
+ background-repeat: no-repeat;
117
+ background-size: 16px;
118
+ padding-right: 40px;
119
+ appearance: none;
120
+ }
121
+
122
+ .form-row {
123
+ display: grid;
124
+ grid-template-columns: 1fr 1fr;
125
+ gap: 16px;
126
+ }
127
+
128
+ /* Generate Button */
129
+ .generate-btn {
130
+ width: 100%;
131
+ padding: 16px 24px;
132
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
133
+ color: white;
134
+ border: none;
135
+ border-radius: 12px;
136
+ font-size: 1.1rem;
137
+ font-weight: 600;
138
+ cursor: pointer;
139
+ transition: all 0.3s ease;
140
+ display: flex;
141
+ align-items: center;
142
+ justify-content: center;
143
+ gap: 12px;
144
+ margin-top: 8px;
145
+ }
146
+
147
+ .generate-btn:hover {
148
+ transform: translateY(-2px);
149
+ box-shadow: 0 12px 24px rgba(102, 126, 234, 0.3);
150
+ }
151
+
152
+ .generate-btn:active {
153
+ transform: translateY(0);
154
+ }
155
+
156
+ .generate-btn:disabled {
157
+ opacity: 0.6;
158
+ cursor: not-allowed;
159
+ transform: none;
160
+ }
161
+
162
+ /* Results Panel */
163
+ .results-panel {
164
+ background: white;
165
+ border-radius: 16px;
166
+ padding: 32px;
167
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1);
168
+ min-height: 400px;
169
+ display: flex;
170
+ flex-direction: column;
171
+ }
172
+
173
+ .results-header {
174
+ display: flex;
175
+ align-items: center;
176
+ justify-content: space-between;
177
+ margin-bottom: 24px;
178
+ }
179
+
180
+ .results-title {
181
+ font-size: 1.5rem;
182
+ font-weight: 600;
183
+ color: #2d3748;
184
+ display: flex;
185
+ align-items: center;
186
+ gap: 12px;
187
+ }
188
+
189
+ .results-count {
190
+ background: #667eea;
191
+ color: white;
192
+ padding: 4px 12px;
193
+ border-radius: 20px;
194
+ font-size: 0.875rem;
195
+ font-weight: 600;
196
+ }
197
+
198
+ /* Gallery */
199
+ .gallery {
200
+ display: grid;
201
+ grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
202
+ gap: 20px;
203
+ flex: 1;
204
+ }
205
+
206
+ .gallery-item {
207
+ position: relative;
208
+ border-radius: 12px;
209
+ overflow: hidden;
210
+ background: #f8fafc;
211
+ aspect-ratio: 1;
212
+ cursor: pointer;
213
+ transition: all 0.3s ease;
214
+ }
215
+
216
+ .gallery-item:hover {
217
+ transform: translateY(-4px);
218
+ box-shadow: 0 12px 24px rgba(0, 0, 0, 0.15);
219
+ }
220
+
221
+ .gallery-item img {
222
+ width: 100%;
223
+ height: 100%;
224
+ object-fit: cover;
225
+ transition: transform 0.3s ease;
226
+ }
227
+
228
+ .gallery-item:hover img {
229
+ transform: scale(1.05);
230
+ }
231
+
232
+ .gallery-overlay {
233
+ position: absolute;
234
+ top: 0;
235
+ left: 0;
236
+ right: 0;
237
+ bottom: 0;
238
+ background: linear-gradient(to bottom, transparent, rgba(0, 0, 0, 0.7));
239
+ opacity: 0;
240
+ transition: opacity 0.3s ease;
241
+ display: flex;
242
+ align-items: flex-end;
243
+ padding: 16px;
244
+ }
245
+
246
+ .gallery-item:hover .gallery-overlay {
247
+ opacity: 1;
248
+ }
249
+
250
+ .gallery-actions {
251
+ display: flex;
252
+ gap: 8px;
253
+ }
254
+
255
+ .gallery-action {
256
+ background: rgba(255, 255, 255, 0.2);
257
+ backdrop-filter: blur(10px);
258
+ border: none;
259
+ color: white;
260
+ padding: 8px;
261
+ border-radius: 8px;
262
+ cursor: pointer;
263
+ transition: all 0.3s ease;
264
+ }
265
+
266
+ .gallery-action:hover {
267
+ background: rgba(255, 255, 255, 0.3);
268
+ }
269
+
270
+ /* Empty State */
271
+ .empty-state {
272
+ display: flex;
273
+ flex-direction: column;
274
+ align-items: center;
275
+ justify-content: center;
276
+ flex: 1;
277
+ color: #a0aec0;
278
+ text-align: center;
279
+ }
280
+
281
+ .empty-state i {
282
+ font-size: 4rem;
283
+ margin-bottom: 16px;
284
+ opacity: 0.5;
285
+ }
286
+
287
+ .empty-state h3 {
288
+ font-size: 1.25rem;
289
+ margin-bottom: 8px;
290
+ color: #718096;
291
+ }
292
+
293
+ .empty-state p {
294
+ font-size: 0.875rem;
295
+ }
296
+
297
+ /* Loading State */
298
+ .loading {
299
+ display: flex;
300
+ flex-direction: column;
301
+ align-items: center;
302
+ justify-content: center;
303
+ flex: 1;
304
+ color: #667eea;
305
+ }
306
+
307
+ .loading-spinner {
308
+ width: 40px;
309
+ height: 40px;
310
+ border: 3px solid #e2e8f0;
311
+ border-top: 3px solid #667eea;
312
+ border-radius: 50%;
313
+ animation: spin 1s linear infinite;
314
+ margin-bottom: 16px;
315
+ }
316
+
317
+ @keyframes spin {
318
+ 0% { transform: rotate(0deg); }
319
+ 100% { transform: rotate(360deg); }
320
+ }
321
+
322
+ /* Error Message */
323
+ .error-message {
324
+ background: #fed7d7;
325
+ color: #c53030;
326
+ padding: 12px 16px;
327
+ border-radius: 8px;
328
+ margin-bottom: 16px;
329
+ border: 1px solid #feb2b2;
330
+ display: flex;
331
+ align-items: center;
332
+ gap: 8px;
333
+ }
334
+
335
+ /* Responsive Design */
336
+ @media (max-width: 768px) {
337
+ .content-grid {
338
+ grid-template-columns: 1fr;
339
+ gap: 20px;
340
+ }
341
+
342
+ .header h1 {
343
+ font-size: 2rem;
344
+ }
345
+
346
+ .control-panel, .results-panel {
347
+ padding: 24px;
348
+ }
349
+
350
+ .form-row {
351
+ grid-template-columns: 1fr;
352
+ }
353
+
354
+ .gallery {
355
+ grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));
356
+ gap: 16px;
357
+ }
358
+ }
359
+
360
+ /* Additional Professional Touches */
361
+ .badge {
362
+ display: inline-flex;
363
+ align-items: center;
364
+ padding: 4px 8px;
365
+ background: #edf2f7;
366
+ color: #4a5568;
367
+ border-radius: 6px;
368
+ font-size: 0.75rem;
369
+ font-weight: 600;
370
+ text-transform: uppercase;
371
+ letter-spacing: 0.5px;
372
+ }
373
+
374
+ .divider {
375
+ height: 1px;
376
+ background: #e2e8f0;
377
+ margin: 24px 0;
378
+ }
379
+ </style>
380
+ </head>
381
+ <body>
382
+ <div class="main-container">
383
+ <!-- Header -->
384
+ <div class="header">
385
+ <h1>AI Image Studio</h1>
386
+ <p class="subtitle">Transform your ideas into stunning visuals with advanced AI technology</p>
387
+ </div>
388
+
389
+ <!-- Main Content -->
390
+ <div class="content-grid">
391
+ <!-- Control Panel -->
392
+ <div class="control-panel">
393
+ <h2 class="panel-title">
394
+ <i class="fas fa-magic"></i>
395
+ Generate Images
396
+ </h2>
397
+
398
+ <form id="generate-form">
399
+ <div class="form-group">
400
+ <label class="form-label" for="prompt">
401
+ <i class="fas fa-pen"></i> Prompt Description
402
+ </label>
403
+ <textarea
404
+ id="prompt"
405
+ name="prompt"
406
+ class="form-textarea"
407
+ required
408
+ placeholder="Describe the image you want to create... (e.g., A serene landscape with mountains at sunset, digital art style)"
409
+ ></textarea>
410
+ </div>
411
+
412
+ <div class="form-row">
413
+ <div class="form-group">
414
+ <label class="form-label" for="num_images">
415
+ <i class="fas fa-images"></i> Quantity
416
+ </label>
417
+ <select id="num_images" name="num_images" class="form-select">
418
+ <option value="1">1 Image</option>
419
+ <option value="2">2 Images</option>
420
+ <option value="3">3 Images</option>
421
+ <option value="4">4 Images</option>
422
+ <option value="5">5 Images</option>
423
+ </select>
424
+ </div>
425
+
426
+ <div class="form-group">
427
+ <label class="form-label" for="aspect_ratio">
428
+ <i class="fas fa-expand-arrows-alt"></i> Aspect Ratio
429
+ </label>
430
+ <select id="aspect_ratio" name="aspect_ratio" class="form-select">
431
+ <option value="1:1">1:1 Square</option>
432
+ <option value="4:3">4:3 Standard</option>
433
+ <option value="16:9">16:9 Widescreen</option>
434
+ </select>
435
+ </div>
436
+ </div>
437
+
438
+ <div class="form-group">
439
+ <label class="form-label" for="model">
440
+ <i class="fas fa-brain"></i> AI Model
441
+ </label>
442
+ <select id="model" name="model" class="form-select">
443
+ <option value="stable_diffusion">Stable Diffusion</option>
444
+ <option value="locked_model" disabled>Premium Model (Coming Soon)</option>
445
+ </select>
446
+ </div>
447
+
448
+ <div class="divider"></div>
449
+
450
+ <button type="submit" class="generate-btn">
451
+ <i class="fas fa-rocket"></i>
452
+ Generate Images
453
+ </button>
454
+ </form>
455
+
456
+ <div id="error-message"></div>
457
+ </div>
458
+
459
+ <!-- Results Panel -->
460
+ <div class="results-panel">
461
+ <div class="results-header">
462
+ <h2 class="results-title">
463
+ <i class="fas fa-images"></i>
464
+ Generated Images
465
+ </h2>
466
+ <div class="results-count" id="results-count" style="display: none;">0</div>
467
+ </div>
468
+
469
+ <div id="gallery-content">
470
+ <div class="empty-state">
471
+ <i class="fas fa-image"></i>
472
+ <h3>Ready to Create</h3>
473
+ <p>Enter a prompt and click generate to see your AI-created images appear here</p>
474
+ </div>
475
+ </div>
476
+ </div>
477
+ </div>
478
+ </div>
479
+
480
+ <script>
481
+ document.getElementById("generate-form").addEventListener("submit", async (e) => {
482
+ e.preventDefault();
483
+
484
+ const formData = new FormData(e.target);
485
+ const errorDiv = document.getElementById("error-message");
486
+ const galleryContent = document.getElementById("gallery-content");
487
+ const resultsCount = document.getElementById("results-count");
488
+ const generateBtn = document.querySelector(".generate-btn");
489
+
490
+ // Clear previous state
491
+ errorDiv.innerHTML = "";
492
+ resultsCount.style.display = "none";
493
+
494
+ // Show loading state
495
+ generateBtn.disabled = true;
496
+ generateBtn.innerHTML = '<i class="fas fa-spinner fa-spin"></i> Generating...';
497
+
498
+ galleryContent.innerHTML = `
499
+ <div class="loading">
500
+ <div class="loading-spinner"></div>
501
+ <h3>Creating your images...</h3>
502
+ <p>This may take a few moments</p>
503
+ </div>
504
+ `;
505
+
506
+ try {
507
+ const response = await fetch("/generate", {
508
+ method: "POST",
509
+ body: formData
510
+ });
511
+ const data = await response.json();
512
+
513
+ if (data.error) {
514
+ errorDiv.innerHTML = `
515
+ <div class="error-message">
516
+ <i class="fas fa-exclamation-triangle"></i>
517
+ ${data.error}
518
+ </div>
519
+ `;
520
+ galleryContent.innerHTML = `
521
+ <div class="empty-state">
522
+ <i class="fas fa-exclamation-triangle"></i>
523
+ <h3>Generation Failed</h3>
524
+ <p>Please check your prompt and try again</p>
525
+ </div>
526
+ `;
527
+ } else {
528
+ // Show results
529
+ const gallery = document.createElement("div");
530
+ gallery.className = "gallery";
531
+
532
+ data.images.forEach((imgPath, index) => {
533
+ const galleryItem = document.createElement("div");
534
+ galleryItem.className = "gallery-item";
535
+ galleryItem.innerHTML = `
536
+ <img src="/${imgPath}" alt="Generated Image ${index + 1}" loading="lazy">
537
+ <div class="gallery-overlay">
538
+ <div class="gallery-actions">
539
+ <button class="gallery-action" onclick="downloadImage('/${imgPath}')" title="Download">
540
+ <i class="fas fa-download"></i>
541
+ </button>
542
+ </div>
543
+ </div>
544
+ `;
545
+ gallery.appendChild(galleryItem);
546
+ });
547
+
548
+ galleryContent.innerHTML = "";
549
+ galleryContent.appendChild(gallery);
550
+
551
+ // Update results count
552
+ resultsCount.textContent = data.images.length;
553
+ resultsCount.style.display = "block";
554
+ }
555
+ } catch (err) {
556
+ errorDiv.innerHTML = `
557
+ <div class="error-message">
558
+ <i class="fas fa-exclamation-triangle"></i>
559
+ Network error occurred. Please check your connection and try again.
560
+ </div>
561
+ `;
562
+ galleryContent.innerHTML = `
563
+ <div class="empty-state">
564
+ <i class="fas fa-wifi"></i>
565
+ <h3>Connection Error</h3>
566
+ <p>Please check your internet connection and try again</p>
567
+ </div>
568
+ `;
569
+ } finally {
570
+ // Reset button
571
+ generateBtn.disabled = false;
572
+ generateBtn.innerHTML = '<i class="fas fa-rocket"></i> Generate Images';
573
+ }
574
+ });
575
+
576
+ // Download function
577
+ function downloadImage(src) {
578
+ const link = document.createElement('a');
579
+ link.href = src;
580
+ link.download = `ai-generated-image-${Date.now()}.png`;
581
+ document.body.appendChild(link);
582
+ link.click();
583
+ document.body.removeChild(link);
584
+ }
585
+
586
+ // Add some interactive polish
587
+ document.addEventListener('DOMContentLoaded', function() {
588
+ // Add subtle animation to form elements
589
+ const formElements = document.querySelectorAll('.form-input, .form-select, .form-textarea');
590
+ formElements.forEach(element => {
591
+ element.addEventListener('focus', function() {
592
+ this.parentElement.style.transform = 'translateY(-2px)';
593
+ });
594
+ element.addEventListener('blur', function() {
595
+ this.parentElement.style.transform = 'translateY(0)';
596
+ });
597
+ });
598
+ });
599
+ </script>
600
+ </body>
601
+ </html>