MySafeCode commited on
Commit
f333122
·
verified ·
1 Parent(s): 6565ae4

Create dapp.py

Browse files
Files changed (1) hide show
  1. dapp.py +960 -0
dapp.py ADDED
@@ -0,0 +1,960 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import json
3
+ import requests
4
+ import gradio as gr
5
+ from datetime import datetime
6
+ import time
7
+
8
+ # API Configuration
9
+ API_KEY = os.getenv('StableCogKey')
10
+ API_HOST = 'https://api.stablecog.com'
11
+
12
+ headers = {
13
+ 'Authorization': f'Bearer {API_KEY}',
14
+ 'Content-Type': 'application/json'
15
+ }
16
+
17
+ # Global state for outputs pagination
18
+ current_outputs = []
19
+ current_page = 0
20
+ page_size = 10
21
+
22
+ # ========== MODEL AND SCHEDULER MANAGEMENT ==========
23
+ def get_model_list():
24
+ """Get list of models for dropdown"""
25
+ try:
26
+ url = f'{API_HOST}/v1/image/generation/models'
27
+ response = requests.get(url, headers=headers, timeout=10)
28
+ if response.status_code == 200:
29
+ models = response.json().get('models', [])
30
+ # Sort: default first, then by name
31
+ models.sort(key=lambda x: (not x.get('is_default', False), x.get('name', '')))
32
+ return [(f"{m['name']}", m['id']) for m in models]
33
+ except Exception as e:
34
+ print(f"Error fetching models: {e}")
35
+ # Fallback to a working model from your successful response
36
+ return [
37
+ ("SDXL 1.0", "0a99668b-45bd-4f7e-aa9c-f9aaa41ef13b"),
38
+ ("SDXL Lightning", "22b0857d-7edc-4d00-9cd9-45aa509db093")
39
+ ]
40
+
41
+ def get_scheduler_list():
42
+ """Get list of schedulers"""
43
+ # From your successful response, use the actual scheduler IDs
44
+ return [
45
+ ("Euler", "af2679a4-dbbb-4950-8c06-c3bb15416ef6"),
46
+ ("Euler A", "6fb13a76-990d-49df-a2ab-7d9d22c33e3d"),
47
+ ("DDIM", "c5a0bad3-bd9d-4c5c-96e3-9d8e8c0c7a6b"),
48
+ ("DPMSolver++", "e9c3a7b3-2b5c-4a5d-8e2d-7b1c9a3d4e5f"),
49
+ ("DPM++ 2M Karras", "f1c3a7b3-2b5c-4a5d-8e2d-7b1c9a3d4e5f")
50
+ ]
51
+
52
+ # Initialize model and scheduler lists
53
+ MODELS = get_model_list()
54
+ SCHEDULERS = get_scheduler_list()
55
+
56
+ # Create mapping dictionaries
57
+ MODEL_MAP = {name: id for name, id in MODELS}
58
+ SCHEDULER_MAP = {name: id for name, id in SCHEDULERS}
59
+
60
+ # Default values
61
+ DEFAULT_MODEL = MODELS[0][0] if MODELS else "SDXL 1.0"
62
+ DEFAULT_SCHEDULER = SCHEDULERS[0][0] if SCHEDULERS else "Euler"
63
+
64
+ # ========== CUSTOM DARK THEME CSS ==========
65
+ CUSTOM_CSS = """
66
+ /* Dark theme background */
67
+ .gradio-container {
68
+ background: linear-gradient(135deg, #0a0a2a 0%, #1a1a3a 100%) !important;
69
+ }
70
+
71
+ /* Tab styling */
72
+ .tabs {
73
+ background: rgba(0, 0, 0, 0.3) !important;
74
+ border-radius: 10px !important;
75
+ padding: 5px !important;
76
+ margin-bottom: 20px !important;
77
+ }
78
+
79
+ .tab-nav {
80
+ background: rgba(255, 255, 255, 0.1) !important;
81
+ border-radius: 8px !important;
82
+ }
83
+
84
+ .tab-button {
85
+ background: transparent !important;
86
+ color: #aaa !important;
87
+ border: none !important;
88
+ }
89
+
90
+ .tab-button.selected {
91
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
92
+ color: white !important;
93
+ font-weight: bold !important;
94
+ }
95
+
96
+ /* Button styling */
97
+ button {
98
+ border-radius: 8px !important;
99
+ border: none !important;
100
+ }
101
+
102
+ button.primary {
103
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
104
+ color: white !important;
105
+ font-weight: bold !important;
106
+ }
107
+
108
+ button.secondary {
109
+ background: rgba(255, 255, 255, 0.1) !important;
110
+ color: white !important;
111
+ border: 1px solid rgba(255, 255, 255, 0.2) !important;
112
+ }
113
+
114
+ button:hover {
115
+ transform: translateY(-2px) !important;
116
+ box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3) !important;
117
+ }
118
+
119
+ /* Input styling */
120
+ input, textarea, .gradio-dropdown {
121
+ background: rgba(255, 255, 255, 0.05) !important;
122
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
123
+ color: white !important;
124
+ border-radius: 8px !important;
125
+ }
126
+
127
+ input:focus, textarea:focus {
128
+ border-color: #667eea !important;
129
+ box-shadow: 0 0 0 2px rgba(102, 126, 234, 0.2) !important;
130
+ }
131
+
132
+ /* Label styling */
133
+ label {
134
+ color: #ddd !important;
135
+ font-weight: 500 !important;
136
+ }
137
+
138
+ /* Textbox and code styling */
139
+ .gradio-textbox, .gradio-code {
140
+ background: rgba(0, 0, 0, 0.3) !important;
141
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
142
+ color: #eee !important;
143
+ }
144
+
145
+ /* Slider styling */
146
+ .gr-slider > .range {
147
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
148
+ }
149
+
150
+ .gr-slider > .range-container {
151
+ background: rgba(255, 255, 255, 0.1) !important;
152
+ }
153
+
154
+ /* Group styling */
155
+ .gr-group {
156
+ background: rgba(255, 255, 255, 0.05) !important;
157
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
158
+ border-radius: 12px !important;
159
+ padding: 20px !important;
160
+ margin-bottom: 20px !important;
161
+ }
162
+
163
+ /* Markdown styling */
164
+ .gr-markdown h1, .gr-markdown h2, .gr-markdown h3 {
165
+ color: white !important;
166
+ }
167
+
168
+ .gr-markdown p {
169
+ color: #bbb !important;
170
+ }
171
+
172
+ /* Accordion styling */
173
+ .gr-accordion {
174
+ background: rgba(255, 255, 255, 0.05) !important;
175
+ border: 1px solid rgba(255, 255, 255, 0.1) !important;
176
+ border-radius: 8px !important;
177
+ }
178
+
179
+ /* Custom scrollbar */
180
+ ::-webkit-scrollbar {
181
+ width: 8px;
182
+ height: 8px;
183
+ }
184
+
185
+ ::-webkit-scrollbar-track {
186
+ background: rgba(255, 255, 255, 0.05);
187
+ border-radius: 4px;
188
+ }
189
+
190
+ ::-webkit-scrollbar-thumb {
191
+ background: rgba(102, 126, 234, 0.5);
192
+ border-radius: 4px;
193
+ }
194
+
195
+ ::-webkit-scrollbar-thumb:hover {
196
+ background: rgba(102, 126, 234, 0.8);
197
+ }
198
+ """
199
+
200
+ # ========== MODELS TAB ==========
201
+ def get_models():
202
+ """Fetch and display available models"""
203
+ try:
204
+ url = f'{API_HOST}/v1/image/generation/models'
205
+ response = requests.get(url, headers=headers, timeout=10)
206
+
207
+ if response.status_code == 200:
208
+ data = response.json()
209
+ models = data.get('models', [])
210
+
211
+ # Format display
212
+ display_text = f"📊 Found {len(models)} models\n"
213
+ display_text += f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
214
+
215
+ for i, model in enumerate(models, 1):
216
+ name = model.get('name', 'Unknown')
217
+ model_type = model.get('type', 'unknown')
218
+ description = model.get('description', 'No description')
219
+
220
+ display_text += f"{i}. 🔹 **{name}**\n"
221
+ display_text += f" 📝 {description}\n"
222
+ display_text += f" 🏷️ Type: {model_type}\n"
223
+ display_text += f" 🌍 Public: {'✅' if model.get('is_public') else '❌'}\n"
224
+ display_text += f" ⭐ Default: {'✅' if model.get('is_default') else '❌'}\n"
225
+ display_text += f" 👥 Community: {'✅' if model.get('is_community') else '❌'}\n"
226
+ display_text += "─" * 40 + "\n"
227
+
228
+ return display_text, str(data)
229
+
230
+ else:
231
+ return f"❌ Error {response.status_code}", f"Error: {response.text}"
232
+
233
+ except Exception as e:
234
+ return f"❌ Error: {str(e)}", "No data"
235
+
236
+ # ========== GENERATE TAB ==========
237
+ def generate_image(prompt, negative_prompt, model_name, width, height,
238
+ num_outputs, guidance_scale, inference_steps,
239
+ scheduler_name, seed, init_image_url, prompt_strength):
240
+ """Generate images using StableCog API"""
241
+ try:
242
+ url = f'{API_HOST}/v1/image/generation/create'
243
+
244
+ # Get actual IDs from maps
245
+ model_id = MODEL_MAP.get(model_name, MODELS[0][1] if MODELS else "0a99668b-45bd-4f7e-aa9c-f9aaa41ef13b")
246
+ scheduler_id = SCHEDULER_MAP.get(scheduler_name, "af2679a4-dbbb-4950-8c06-c3bb15416ef6")
247
+
248
+ # Prepare request data
249
+ data = {
250
+ "prompt": prompt,
251
+ "model_id": model_id,
252
+ "width": int(width),
253
+ "height": int(height),
254
+ "num_outputs": int(num_outputs),
255
+ "guidance_scale": float(guidance_scale),
256
+ "inference_steps": int(inference_steps),
257
+ "scheduler_id": scheduler_id,
258
+ }
259
+
260
+ # Add optional fields if provided
261
+ if negative_prompt and negative_prompt.strip():
262
+ data["negative_prompt"] = negative_prompt.strip()
263
+
264
+ if seed and seed.strip():
265
+ try:
266
+ data["seed"] = int(seed.strip())
267
+ except:
268
+ # Generate random seed if invalid
269
+ import random
270
+ data["seed"] = random.randint(1, 1000000000)
271
+
272
+ if init_image_url and init_image_url.strip():
273
+ data["init_image_url"] = init_image_url.strip()
274
+ if prompt_strength is not None:
275
+ data["prompt_strength"] = float(prompt_strength)
276
+
277
+ # Show loading state
278
+ start_time = time.time()
279
+
280
+ # Make API request
281
+ response = requests.post(
282
+ url,
283
+ json=data,
284
+ headers=headers,
285
+ timeout=60 # Longer timeout for generation
286
+ )
287
+
288
+ generation_time = time.time() - start_time
289
+
290
+ if response.status_code == 200:
291
+ result = response.json()
292
+ outputs = result.get('outputs', [])
293
+ remaining_credits = result.get('remaining_credits', 0)
294
+ settings = result.get('settings', {})
295
+
296
+ # Format response
297
+ display_text = f"✅ Generation successful!\n"
298
+ display_text += f"🪙 Remaining credits: {remaining_credits}\n"
299
+ display_text += f"🖼️ Generated {len(outputs)} image(s)\n"
300
+ display_text += f"⏱️ Generation time: {generation_time:.1f}s\n"
301
+ display_text += f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
302
+
303
+ # Get image URLs for gallery
304
+ image_urls = []
305
+ for i, output in enumerate(outputs, 1):
306
+ output_id = output.get('id', 'N/A')
307
+ image_url = output.get('url', '')
308
+
309
+ display_text += f"{i}. 🔹 Output ID: {output_id}\n"
310
+ display_text += f" 📷 URL: {image_url}\n"
311
+
312
+ if image_url:
313
+ image_urls.append(image_url)
314
+
315
+ # Create gallery HTML
316
+ gallery_html = ""
317
+ if image_urls:
318
+ gallery_html = create_gallery_html(image_urls, "🎨 Generated Images")
319
+
320
+ # Show settings used
321
+ display_text += "\n⚙️ Settings used:\n"
322
+ for key, value in settings.items():
323
+ display_text += f" {key}: {value}\n"
324
+
325
+ return display_text, gallery_html, str(result)
326
+
327
+ else:
328
+ error_msg = f"❌ Generation failed: {response.status_code}"
329
+ try:
330
+ error_detail = response.json()
331
+ error_msg += f"\nDetails: {error_detail.get('error', str(error_detail))}"
332
+ except:
333
+ error_msg += f"\nResponse: {response.text[:200]}..."
334
+ return error_msg, "", error_msg
335
+
336
+ except Exception as e:
337
+ error_msg = f"❌ Error: {str(e)}"
338
+ return error_msg, "", error_msg
339
+
340
+ # ========== OUTPUTS TAB ==========
341
+ def create_gallery_html(image_urls, title="Gallery"):
342
+ """Create HTML gallery with lightbox"""
343
+ if not image_urls:
344
+ return """
345
+ <div style='
346
+ text-align: center;
347
+ padding: 60px;
348
+ color: #888;
349
+ background: rgba(255,255,255,0.05);
350
+ border-radius: 12px;
351
+ border: 2px dashed rgba(255,255,255,0.1);
352
+ margin: 20px 0;
353
+ '>
354
+ <div style='font-size: 48px; margin-bottom: 20px;'>🎨</div>
355
+ <div style='font-size: 18px; font-weight: bold;'>No images to display</div>
356
+ <div style='margin-top: 10px; opacity: 0.7;'>Generate some images first!</div>
357
+ </div>
358
+ """
359
+
360
+ html = f"""
361
+ <style>
362
+ .image-gallery {{
363
+ display: grid;
364
+ grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
365
+ gap: 15px;
366
+ margin-bottom: 20px;
367
+ }}
368
+ .image-card {{
369
+ border-radius: 10px;
370
+ overflow: hidden;
371
+ background: rgba(255,255,255,0.1);
372
+ padding: 10px;
373
+ cursor: pointer;
374
+ transition: transform 0.2s, box-shadow 0.2s;
375
+ border: 1px solid rgba(255,255,255,0.1);
376
+ }}
377
+ .image-card:hover {{
378
+ transform: scale(1.02);
379
+ background: rgba(255,255,255,0.15);
380
+ box-shadow: 0 5px 15px rgba(0,0,0,0.3);
381
+ border-color: #667eea;
382
+ }}
383
+ .image-card img {{
384
+ width: 100%;
385
+ height: 200px;
386
+ object-fit: cover;
387
+ border-radius: 8px;
388
+ }}
389
+ .image-meta {{
390
+ margin-top: 8px;
391
+ font-size: 12px;
392
+ line-height: 1.4;
393
+ color: rgba(255,255,255,0.9);
394
+ }}
395
+ .lightbox {{
396
+ display: none;
397
+ position: fixed;
398
+ z-index: 9999;
399
+ left: 0;
400
+ top: 0;
401
+ width: 100%;
402
+ height: 100%;
403
+ background: rgba(0,0,0,0.95);
404
+ justify-content: center;
405
+ align-items: center;
406
+ animation: fadeIn 0.3s;
407
+ }}
408
+ @keyframes fadeIn {{
409
+ from {{ opacity: 0; }}
410
+ to {{ opacity: 1; }}
411
+ }}
412
+ .lightbox img {{
413
+ max-width: 95%;
414
+ max-height: 95%;
415
+ border-radius: 10px;
416
+ box-shadow: 0 20px 60px rgba(0,0,0,0.5);
417
+ animation: zoomIn 0.3s;
418
+ }}
419
+ @keyframes zoomIn {{
420
+ from {{ transform: scale(0.9); opacity: 0; }}
421
+ to {{ transform: scale(1); opacity: 1; }}
422
+ }}
423
+ .lightbox.active {{
424
+ display: flex;
425
+ }}
426
+ .close-btn {{
427
+ position: absolute;
428
+ top: 20px;
429
+ right: 30px;
430
+ color: white;
431
+ font-size: 40px;
432
+ font-weight: bold;
433
+ cursor: pointer;
434
+ z-index: 10000;
435
+ opacity: 0.8;
436
+ transition: opacity 0.2s;
437
+ background: rgba(0,0,0,0.5);
438
+ width: 50px;
439
+ height: 50px;
440
+ border-radius: 50%;
441
+ display: flex;
442
+ align-items: center;
443
+ justify-content: center;
444
+ }}
445
+ .close-btn:hover {{
446
+ opacity: 1;
447
+ background: rgba(0,0,0,0.7);
448
+ }}
449
+ .pagination {{
450
+ display: flex;
451
+ justify-content: center;
452
+ gap: 10px;
453
+ margin-top: 20px;
454
+ }}
455
+ .page-btn {{
456
+ padding: 8px 16px;
457
+ background: rgba(255,255,255,0.1);
458
+ border: 1px solid rgba(255,255,255,0.2);
459
+ border-radius: 8px;
460
+ color: white;
461
+ cursor: pointer;
462
+ transition: background 0.2s;
463
+ }}
464
+ .page-btn:hover {{
465
+ background: rgba(255,255,255,0.2);
466
+ }}
467
+ .page-btn.disabled {{
468
+ opacity: 0.5;
469
+ cursor: not-allowed;
470
+ }}
471
+ .page-info {{
472
+ display: flex;
473
+ align-items: center;
474
+ padding: 8px 16px;
475
+ color: white;
476
+ font-weight: bold;
477
+ background: rgba(255,255,255,0.05);
478
+ border-radius: 8px;
479
+ }}
480
+ .gallery-title {{
481
+ color: white;
482
+ font-size: 24px;
483
+ margin-bottom: 20px;
484
+ padding-bottom: 10px;
485
+ border-bottom: 2px solid rgba(102, 126, 234, 0.5);
486
+ }}
487
+ </style>
488
+
489
+ <div class="lightbox" id="lightbox" onclick="closeLightbox()">
490
+ <span class="close-btn" onclick="closeLightbox()">&times;</span>
491
+ <img id="lightbox-img" onclick="event.stopPropagation()">
492
+ </div>
493
+
494
+ <script>
495
+ function openLightbox(imgSrc) {{
496
+ document.getElementById('lightbox-img').src = imgSrc;
497
+ document.getElementById('lightbox').classList.add('active');
498
+ document.body.style.overflow = 'hidden';
499
+ }}
500
+ function closeLightbox() {{
501
+ document.getElementById('lightbox').classList.remove('active');
502
+ document.body.style.overflow = 'auto';
503
+ }}
504
+ // Close lightbox on ESC key
505
+ document.addEventListener('keydown', function(e) {{
506
+ if (e.key === 'Escape') closeLightbox();
507
+ }});
508
+ </script>
509
+
510
+ <div class="gallery-title">{title}</div>
511
+ <div class="image-gallery">
512
+ """
513
+
514
+ for i, image_url in enumerate(image_urls):
515
+ html += f"""
516
+ <div class="image-card" onclick="openLightbox('{image_url}')">
517
+ <img src="{image_url}" alt="Image {i+1}" onerror="this.src='https://via.placeholder.com/200x200/333/666?text=Image+Error'">
518
+ <div class="image-meta">
519
+ <div>🖼️ Image #{i+1}</div>
520
+ <div>📏 Click to enlarge</div>
521
+ </div>
522
+ </div>
523
+ """
524
+
525
+ html += "</div>"
526
+ return html
527
+
528
+ def fetch_outputs():
529
+ """Fetch outputs from API"""
530
+ global current_outputs
531
+ try:
532
+ url = f'{API_HOST}/v1/image/generation/outputs'
533
+ response = requests.get(url, headers=headers, timeout=10)
534
+
535
+ if response.status_code == 200:
536
+ data = response.json()
537
+ current_outputs = data.get('outputs', [])
538
+ return data
539
+ else:
540
+ return None
541
+ except:
542
+ return None
543
+
544
+ def update_outputs_display():
545
+ """Update outputs display with current page"""
546
+ global current_outputs, current_page, page_size
547
+
548
+ if not current_outputs:
549
+ return "📭 No outputs found. Generate some images first!", """
550
+ <div style='
551
+ text-align: center;
552
+ padding: 60px;
553
+ color: #888;
554
+ background: rgba(255,255,255,0.05);
555
+ border-radius: 12px;
556
+ border: 2px dashed rgba(255,255,255,0.1);
557
+ margin: 20px 0;
558
+ '>
559
+ <div style='font-size: 48px; margin-bottom: 20px;'>🎨</div>
560
+ <div style='font-size: 18px; font-weight: bold;'>No images found yet</div>
561
+ <div style='margin-top: 10px; opacity: 0.7;'>Try generating some images in the Generate tab!</div>
562
+ </div>
563
+ """, "[]"
564
+
565
+ total = len(current_outputs)
566
+ total_pages = (total + page_size - 1) // page_size # Ceiling division
567
+
568
+ # Calculate page bounds
569
+ start_idx = current_page * page_size
570
+ end_idx = min(start_idx + page_size, total)
571
+ page_outputs = current_outputs[start_idx:end_idx]
572
+
573
+ # Format display
574
+ display_text = f"📊 Total outputs: {total}\n"
575
+ display_text += f"📄 Page {current_page + 1} of {total_pages}\n"
576
+ display_text += f"🖼️ Showing {start_idx + 1}-{end_idx} of {total}\n"
577
+ display_text += f"⏰ {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}\n\n"
578
+
579
+ # Get image URLs for gallery
580
+ image_urls = []
581
+ for idx, output in enumerate(page_outputs, start=start_idx + 1):
582
+ output_id = output.get('id', 'N/A')
583
+ created_at = output.get('created_at', 'N/A')
584
+ model_name = output.get('model_name', 'Unknown')
585
+
586
+ # Gallery status
587
+ gallery_status = output.get('gallery_status', 'not_submitted')
588
+ gallery_emoji = {
589
+ 'not_submitted': '🔒',
590
+ 'submitted': '📤',
591
+ 'approved': '✅',
592
+ 'rejected': '❌'
593
+ }.get(gallery_status, '❓')
594
+
595
+ # Favorites
596
+ is_favorited = output.get('is_favorited', False)
597
+ favorite_emoji = '❤️' if is_favorited else '🤍'
598
+
599
+ # Format timestamp
600
+ if created_at != 'N/A':
601
+ try:
602
+ dt = datetime.fromisoformat(created_at.replace('Z', '+00:00'))
603
+ created_date = dt.strftime('%Y-%m-%d')
604
+ created_time = dt.strftime('%H:%M')
605
+ display_date = f"{created_date} {created_time}"
606
+ except:
607
+ display_date = created_at
608
+ else:
609
+ display_date = 'Unknown'
610
+
611
+ # Get image URL
612
+ image_url = output.get('image_url')
613
+ if not image_url:
614
+ image_urls_list = output.get('image_urls', [])
615
+ if image_urls_list and isinstance(image_urls_list, list) and len(image_urls_list) > 0:
616
+ image_url = image_urls_list[0]
617
+
618
+ if image_url:
619
+ image_urls.append(image_url)
620
+ display_text += f"{idx}. 🔹 **Output {output_id[:8]}...**\n"
621
+ display_text += f" 🕒 {display_date}\n"
622
+ display_text += f" 🤖 {model_name}\n"
623
+ display_text += f" 🖼️ {gallery_emoji} {gallery_status}\n"
624
+ display_text += f" {favorite_emoji} Favorite\n"
625
+
626
+ # Show generation details if available
627
+ generation = output.get('generation', {})
628
+ if generation:
629
+ prompt = generation.get('prompt', 'No prompt')
630
+ if len(prompt) > 50:
631
+ prompt = prompt[:50] + '...'
632
+ display_text += f" 📝 Prompt: {prompt}\n"
633
+ else:
634
+ display_text += f"{idx}. ⚠️ No image data\n"
635
+
636
+ display_text += "─" * 40 + "\n"
637
+
638
+ # Create gallery HTML
639
+ gallery_html = create_gallery_html(image_urls, f"🎨 Your Generated Images (Page {current_page + 1}/{total_pages})")
640
+
641
+ # Add pagination controls
642
+ gallery_html += f"""
643
+ <div class="pagination">
644
+ <button class="page-btn {'disabled' if current_page == 0 else ''}"
645
+ onclick="{'return false;' if current_page == 0 else f'window.paginationButtonClick({current_page - 1})'}">
646
+ ◀ Previous
647
+ </button>
648
+ <div class="page-info">
649
+ Page {current_page + 1} of {total_pages}
650
+ </div>
651
+ <button class="page-btn {'disabled' if current_page >= total_pages - 1 else ''}"
652
+ onclick="{'return false;' if current_page >= total_pages - 1 else f'window.paginationButtonClick({current_page + 1})'}">
653
+ Next ▶
654
+ </button>
655
+ </div>
656
+
657
+ <script>
658
+ window.paginationButtonClick = function(page) {{
659
+ const event = new CustomEvent('gradio_pagination', {{ detail: {{ page: page }} }});
660
+ document.dispatchEvent(event);
661
+ }}
662
+ </script>
663
+ """
664
+
665
+ return display_text, gallery_html, str(current_outputs)
666
+
667
+ def load_outputs():
668
+ """Load outputs from API and display first page"""
669
+ global current_page
670
+ current_page = 0
671
+ data = fetch_outputs()
672
+ if data:
673
+ return update_outputs_display()
674
+ else:
675
+ return "❌ Failed to load outputs", """
676
+ <div style='
677
+ color: #ff6b6b;
678
+ padding: 30px;
679
+ background: rgba(255,107,107,0.1);
680
+ border-radius: 12px;
681
+ border: 1px solid rgba(255,107,107,0.3);
682
+ text-align: center;
683
+ '>
684
+ <div style='font-size: 24px; margin-bottom: 10px;'>⚠️</div>
685
+ <div style='font-size: 16px; font-weight: bold;'>Failed to load outputs</div>
686
+ <div style='margin-top: 10px; opacity: 0.8;'>Check your API key and connection</div>
687
+ </div>
688
+ """, "[]"
689
+
690
+ def next_page():
691
+ """Go to next page"""
692
+ global current_page
693
+ if current_outputs:
694
+ total_pages = (len(current_outputs) + page_size - 1) // page_size
695
+ if current_page < total_pages - 1:
696
+ current_page += 1
697
+ return update_outputs_display()
698
+
699
+ def prev_page():
700
+ """Go to previous page"""
701
+ global current_page
702
+ if current_page > 0:
703
+ current_page -= 1
704
+ return update_outputs_display()
705
+
706
+ # ========== CREATE INTERFACE ==========
707
+ with gr.Blocks(
708
+ title="StableCog Image Generator",
709
+ theme=gr.themes.Soft(primary_hue="purple", secondary_hue="blue"),
710
+ css=CUSTOM_CSS
711
+ ) as demo:
712
+ gr.Markdown("""
713
+ # 🎨 StableCog Image Generator
714
+ ### Create, browse, and manage your AI-generated images
715
+ """)
716
+
717
+ with gr.Tabs():
718
+ # ========== GENERATE TAB ==========
719
+ with gr.Tab("✨ Generate", id="generate"):
720
+ with gr.Row():
721
+ with gr.Column(scale=1):
722
+ with gr.Group():
723
+ gr.Markdown("### 🎯 Prompt Settings")
724
+ prompt = gr.Textbox(
725
+ label="Prompt",
726
+ placeholder="A beautiful sunset over mountains, digital art...",
727
+ lines=3,
728
+ value="A majestic dragon flying over a fantasy castle, digital art, epic lighting"
729
+ )
730
+ negative_prompt = gr.Textbox(
731
+ label="Negative Prompt (Optional)",
732
+ placeholder="blurry, low quality, distorted...",
733
+ lines=2,
734
+ value="blurry, distorted, low quality, ugly"
735
+ )
736
+ init_image_url = gr.Textbox(
737
+ label="Init Image URL (Optional - for img2img)",
738
+ placeholder="https://example.com/image.jpg",
739
+ lines=1
740
+ )
741
+ prompt_strength = gr.Slider(
742
+ label="Prompt Strength (for img2img)",
743
+ minimum=0.0,
744
+ maximum=1.0,
745
+ value=0.8,
746
+ step=0.1,
747
+ visible=False
748
+ )
749
+
750
+ # Show prompt strength only when init image is provided
751
+ init_image_url.change(
752
+ lambda x: gr.update(visible=bool(x and x.strip())),
753
+ inputs=[init_image_url],
754
+ outputs=[prompt_strength]
755
+ )
756
+
757
+ with gr.Group():
758
+ gr.Markdown("### ⚙️ Generation Settings")
759
+
760
+ with gr.Row():
761
+ model_dropdown = gr.Dropdown(
762
+ label="Model",
763
+ choices=[m[0] for m in MODELS],
764
+ value=DEFAULT_MODEL,
765
+ info="Select which AI model to use"
766
+ )
767
+
768
+ with gr.Row():
769
+ width = gr.Slider(
770
+ label="Width",
771
+ minimum=256,
772
+ maximum=1024,
773
+ value=768,
774
+ step=8,
775
+ info="Image width (pixels)"
776
+ )
777
+ height = gr.Slider(
778
+ label="Height",
779
+ minimum=256,
780
+ maximum=1024,
781
+ value=768,
782
+ step=8,
783
+ info="Image height (pixels)"
784
+ )
785
+
786
+ num_outputs = gr.Slider(
787
+ label="Number of Images",
788
+ minimum=1,
789
+ maximum=4,
790
+ value=1,
791
+ step=1,
792
+ info="How many images to generate (uses more credits)"
793
+ )
794
+
795
+ guidance_scale = gr.Slider(
796
+ label="Guidance Scale",
797
+ minimum=1.0,
798
+ maximum=20.0,
799
+ value=7.0,
800
+ step=0.5,
801
+ info="How closely to follow the prompt"
802
+ )
803
+
804
+ inference_steps = gr.Slider(
805
+ label="Inference Steps",
806
+ minimum=10,
807
+ maximum=50,
808
+ value=30,
809
+ step=1,
810
+ info="More steps = more detail but slower"
811
+ )
812
+
813
+ with gr.Row():
814
+ scheduler_dropdown = gr.Dropdown(
815
+ label="Scheduler",
816
+ choices=[s[0] for s in SCHEDULERS],
817
+ value=DEFAULT_SCHEDULER,
818
+ info="Diffusion sampling method"
819
+ )
820
+
821
+ seed = gr.Textbox(
822
+ label="Seed (Optional)",
823
+ placeholder="Leave empty for random",
824
+ lines=1,
825
+ info="Same seed + same settings = same image"
826
+ )
827
+
828
+ with gr.Column(scale=1):
829
+ generate_btn = gr.Button(
830
+ "🚀 Generate Image",
831
+ variant="primary",
832
+ size="lg",
833
+ scale=1
834
+ )
835
+
836
+ with gr.Group():
837
+ gr.Markdown("### 📊 Generation Results")
838
+ generate_output = gr.Textbox(
839
+ label="Status & Details",
840
+ lines=15,
841
+ interactive=False,
842
+ value="Ready to generate! Enter a prompt and click the button above."
843
+ )
844
+
845
+ with gr.Group():
846
+ gr.Markdown("### 🖼️ Generated Images")
847
+ generate_gallery = gr.HTML(
848
+ label="",
849
+ value="""
850
+ <div style='
851
+ text-align: center;
852
+ padding: 40px;
853
+ color: #888;
854
+ background: rgba(255,255,255,0.05);
855
+ border-radius: 12px;
856
+ '>
857
+ <div style='font-size: 36px; margin-bottom: 10px;'>🖼️</div>
858
+ <div>Images will appear here after generation</div>
859
+ </div>
860
+ """
861
+ )
862
+
863
+ with gr.Group():
864
+ with gr.Accordion("📋 Raw API Response", open=False):
865
+ generate_raw = gr.Code(
866
+ label="",
867
+ language="json",
868
+ lines=10,
869
+ interactive=False,
870
+ value="// API response will appear here"
871
+ )
872
+
873
+ # Connect generate button
874
+ generate_btn.click(
875
+ generate_image,
876
+ inputs=[
877
+ prompt, negative_prompt, model_dropdown, width, height,
878
+ num_outputs, guidance_scale, inference_steps,
879
+ scheduler_dropdown, seed, init_image_url, prompt_strength
880
+ ],
881
+ outputs=[generate_output, generate_gallery, generate_raw]
882
+ ).then(
883
+ lambda: time.sleep(1),
884
+ outputs=[]
885
+ ).then(
886
+ load_outputs,
887
+ outputs=[gr.Outputs(outputs_display, outputs_gallery, outputs_raw) if 'outputs_display' in locals() else None]
888
+ )
889
+
890
+ # ========== OUTPUTS TAB ==========
891
+ with gr.Tab("🖼️ My Outputs", id="outputs"):
892
+ with gr.Row():
893
+ with gr.Column(scale=1):
894
+ outputs_display = gr.Textbox(
895
+ label="Output Details",
896
+ lines=25,
897
+ value="Click 'Load My Outputs' to see your generated images."
898
+ )
899
+ with gr.Column(scale=2):
900
+ outputs_gallery = gr.HTML(
901
+ label="Image Gallery",
902
+ value="""
903
+ <div style='
904
+ text-align: center;
905
+ padding: 60px;
906
+ color: #888;
907
+ background: rgba(255,255,255,0.05);
908
+ border-radius: 12px;
909
+ border: 2px dashed rgba(255,255,255,0.1);
910
+ '>
911
+ <div style='font-size: 48px; margin-bottom: 20px;'>🎨</div>
912
+ <div style='font-size: 18px; font-weight: bold;'>Your images will appear here</div>
913
+ <div style='margin-top: 10px; opacity: 0.7;'>Click 'Load My Outputs' to see your generated images</div>
914
+ </div>
915
+ """
916
+ )
917
+
918
+ with gr.Row():
919
+ with gr.Accordion("📋 Raw JSON Data", open=False):
920
+ outputs_raw = gr.Code(
921
+ label="",
922
+ language="json",
923
+ lines=10,
924
+ interactive=False,
925
+ value="// Your outputs data will appear here"
926
+ )
927
+
928
+ with gr.Row():
929
+ load_outputs_btn = gr.Button("🔄 Load My Outputs", variant="primary")
930
+ prev_page_btn = gr.Button("◀ Previous Page", variant="secondary")
931
+ next_page_btn = gr.Button("Next Page ▶", variant="secondary")
932
+
933
+ # Add pagination JavaScript
934
+ js = """
935
+ <script>
936
+ window.paginationButtonClick = function(page) {
937
+ const event = new CustomEvent('gradio_pagination', { detail: { page: page } });
938
+ document.dispatchEvent(event);
939
+ }
940
+ </script>
941
+ """
942
+ gr.HTML(js)
943
+
944
+ # Connect buttons
945
+ load_outputs_btn.click(
946
+ load_outputs,
947
+ outputs=[outputs_display, outputs_gallery, outputs_raw]
948
+ )
949
+
950
+ prev_page_btn.click(
951
+ prev_page,
952
+ outputs=[outputs_display, outputs_gallery, outputs_raw]
953
+ )
954
+
955
+ next_page_btn.click(
956
+ next_page,
957
+ outputs=[outputs_display, outputs_gallery, outputs_raw]
958
+ )
959
+
960
+ # ========== MODELS TAB