Aowrow commited on
Commit
d4a836b
·
1 Parent(s): e24037b

init commit

Browse files
Files changed (3) hide show
  1. README.md +0 -13
  2. app.py +612 -0
  3. requirements.txt +3 -0
README.md DELETED
@@ -1,13 +0,0 @@
1
- ---
2
- title: Lumina-Nieta T2I Playground
3
- emoji: 👁
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: gradio
7
- sdk_version: 5.34.0
8
- app_file: app.py
9
- pinned: false
10
- license: apache-2.0
11
- ---
12
-
13
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
app.py ADDED
@@ -0,0 +1,612 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import os
3
+ import random
4
+ import httpx
5
+ import asyncio
6
+ from dataclasses import dataclass, field
7
+ from typing import Any
8
+ from dotenv import load_dotenv
9
+
10
+ # 加载.env文件
11
+ load_dotenv()
12
+
13
+ # 常量定义
14
+ HTTP_STATUS_CENSORED = 451
15
+ HTTP_STATUS_OK = 200
16
+ MAX_SEED = 2147483647
17
+ MAX_IMAGE_SIZE = 2048
18
+ MIN_IMAGE_SIZE = 256
19
+
20
+ # 调试模式
21
+ DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
22
+
23
+ # 模型配置映射
24
+ MODEL_CONFIGS = {
25
+ "base": "results_cosine_2e-4_bs64_infallssssuum/checkpoint-e4_s82000/consolidated.00-of-01.pth",
26
+ "aesthetics_fixed": "results_cosine_2e-4_bs64_infallssssuumnnnnaaa/checkpoint-e54_s43058/consolidated.00-of-01.pth",
27
+ "ep3": "results_cosine_2e-4_bs64_infallssssuumnnnn/checkpoint-e3_s63858/consolidated.00-of-01.pth",
28
+ "ep3latest": "autodl-fs/lumina_hf/results_cosine_2e-4_bs64_infallssssuumnnnn/checkpoint-e3_s83000/consolidated.00-of-01.pth"
29
+ }
30
+
31
+ def validate_dimensions(width: int, height: int) -> tuple[int, int]:
32
+ """验证并调整图片尺寸"""
33
+ # 确保尺寸是32的倍数
34
+ width = max(MIN_IMAGE_SIZE, min(width, MAX_IMAGE_SIZE))
35
+ height = max(MIN_IMAGE_SIZE, min(height, MAX_IMAGE_SIZE))
36
+ width = (width // 32) * 32
37
+ height = (height // 32) * 32
38
+ return width, height
39
+
40
+ @dataclass
41
+ class LuminaConfig:
42
+ """Lumina模型配置"""
43
+ model_name: str | None = None
44
+ cfg: float | None = None
45
+ step: int | None = None
46
+
47
+ @dataclass
48
+ class ImageGenerationConfig:
49
+ """图像生成配置"""
50
+ prompts: list[dict[str, Any]] = field(default_factory=list)
51
+ width: int = 1024
52
+ height: int = 1024
53
+ seed: int | None = None
54
+ use_polish: bool = False
55
+ is_lumina: bool = True
56
+ lumina_config: LuminaConfig = field(default_factory=LuminaConfig)
57
+
58
+ class ImageClient:
59
+ """图像生成客户端"""
60
+ def __init__(self) -> None:
61
+ # 使用环境变量中的API_TOKEN
62
+ self.x_token = os.environ.get("API_TOKEN", "")
63
+ if not self.x_token:
64
+ raise ValueError("环境变量中未设置API_TOKEN")
65
+
66
+ # API端点
67
+ self.lumina_api_url = "https://ops.api.talesofai.cn/v3/make_image"
68
+ self.lumina_task_status_url = "https://ops.api.talesofai.cn/v1/artifact/task/{task_uuid}"
69
+
70
+ # 轮询配置(增加超时时间以应对服务器负载)
71
+ self.max_polling_attempts = 100 # 增加到100次
72
+ self.polling_interval = 3.0 # 保持3秒间隔
73
+ # 总超时时间:100 × 3.0 = 300秒 = 5分钟
74
+
75
+ # 默认请求头
76
+ self.default_headers = {
77
+ "Content-Type": "application/json",
78
+ "x-platform": "nieta-app/web",
79
+ "X-Token": self.x_token,
80
+ }
81
+
82
+ def _prepare_prompt_data(self, prompt: str, negative_prompt: str = "") -> list[dict[str, Any]]:
83
+ """准备提示词数据"""
84
+ prompts = [
85
+ {
86
+ "type": "freetext",
87
+ "value": prompt,
88
+ "weight": 1.0
89
+ }
90
+ ]
91
+
92
+ if negative_prompt:
93
+ prompts.append({
94
+ "type": "freetext",
95
+ "value": negative_prompt,
96
+ "weight": -1.0
97
+ })
98
+
99
+ # 添加Lumina元素
100
+ prompts.append({
101
+ "type": "elementum",
102
+ "value": "b5edccfe-46a2-4a14-a8ff-f4d430343805",
103
+ "uuid": "b5edccfe-46a2-4a14-a8ff-f4d430343805",
104
+ "weight": 1.0,
105
+ "name": "lumina1",
106
+ "img_url": "https://oss.talesofai.cn/picture_s/1y7f53e6itfn_0.jpeg",
107
+ "domain": "",
108
+ "parent": "",
109
+ "label": None,
110
+ "sort_index": 0,
111
+ "status": "IN_USE",
112
+ "polymorphi_values": {},
113
+ "sub_type": None,
114
+ })
115
+
116
+ return prompts
117
+
118
+ def _build_payload(self, config: ImageGenerationConfig) -> dict[str, Any]:
119
+ """构建API请求载荷"""
120
+ payload = {
121
+ "storyId": "",
122
+ "jobType": "universal",
123
+ "width": config.width,
124
+ "height": config.height,
125
+ "rawPrompt": config.prompts,
126
+ "seed": config.seed,
127
+ "meta": {"entrance": "PICTURE,PURE"},
128
+ "context_model_series": None,
129
+ "negative_freetext": "",
130
+ "advanced_translator": config.use_polish,
131
+ }
132
+
133
+ if config.is_lumina:
134
+ client_args = {}
135
+ if config.lumina_config.model_name:
136
+ client_args["ckpt_name"] = config.lumina_config.model_name
137
+ if config.lumina_config.cfg is not None:
138
+ client_args["cfg"] = str(config.lumina_config.cfg)
139
+ if config.lumina_config.step is not None:
140
+ client_args["steps"] = str(config.lumina_config.step)
141
+
142
+ if client_args:
143
+ payload["client_args"] = client_args
144
+
145
+ return payload
146
+
147
+ async def _poll_task_status(self, task_uuid: str) -> dict[str, Any]:
148
+ """轮询任务状态"""
149
+ async with httpx.AsyncClient(timeout=30.0) as client:
150
+ for _ in range(self.max_polling_attempts):
151
+ response = await client.get(
152
+ self.lumina_task_status_url.format(task_uuid=task_uuid),
153
+ headers=self.default_headers
154
+ )
155
+
156
+ if response.status_code != HTTP_STATUS_OK:
157
+ return {
158
+ "success": False,
159
+ "error": f"获取任务状态失败: {response.status_code} - {response.text}"
160
+ }
161
+
162
+ # 解析JSON响应
163
+ try:
164
+ result = response.json()
165
+ except Exception as e:
166
+ return {
167
+ "success": False,
168
+ "error": f"任务状态响应解析失败: {response.text[:500]}"
169
+ }
170
+
171
+ # 使用正确的字段名(根据model_studio的实现)
172
+ task_status = result.get("task_status")
173
+
174
+ if task_status == "SUCCESS":
175
+ # 从artifacts数组中获取图片URL
176
+ artifacts = result.get("artifacts", [])
177
+ if artifacts and len(artifacts) > 0:
178
+ image_url = artifacts[0].get("url")
179
+ if image_url:
180
+ return {
181
+ "success": True,
182
+ "image_url": image_url
183
+ }
184
+ return {
185
+ "success": False,
186
+ "error": "无法从结果中提取图像URL"
187
+ }
188
+ elif task_status == "FAILURE":
189
+ return {
190
+ "success": False,
191
+ "error": result.get("error", "任务执行失败")
192
+ }
193
+ elif task_status == "ILLEGAL_IMAGE":
194
+ return {
195
+ "success": False,
196
+ "error": "图片不合规"
197
+ }
198
+ elif task_status == "TIMEOUT":
199
+ return {
200
+ "success": False,
201
+ "error": "任务超时"
202
+ }
203
+
204
+ await asyncio.sleep(self.polling_interval)
205
+
206
+ return {
207
+ "success": False,
208
+ "error": "⏳ 生图任务超时(5分钟),服务器可能正在处理大量请求,请稍后重试"
209
+ }
210
+
211
+ async def generate_image(self, prompt: str, negative_prompt: str, seed: int, width: int, height: int, cfg: float, steps: int, model_name: str = "base") -> tuple[str | None, str | None]:
212
+ """生成图片"""
213
+ try:
214
+ # 获取模型路径
215
+ model_path = MODEL_CONFIGS.get(model_name, MODEL_CONFIGS["base"])
216
+
217
+ # 准备配置
218
+ config = ImageGenerationConfig(
219
+ prompts=self._prepare_prompt_data(prompt, negative_prompt),
220
+ width=width,
221
+ height=height,
222
+ seed=seed,
223
+ is_lumina=True,
224
+ lumina_config=LuminaConfig(
225
+ model_name=model_path,
226
+ cfg=cfg,
227
+ step=steps
228
+ )
229
+ )
230
+
231
+ # 发送生成请求
232
+ async with httpx.AsyncClient(timeout=300.0) as client:
233
+ payload = self._build_payload(config)
234
+ if DEBUG_MODE:
235
+ print(f"DEBUG: 发送API请求到 {self.lumina_api_url}")
236
+ print(f"DEBUG: 请求载荷: {payload}")
237
+
238
+ response = await client.post(
239
+ self.lumina_api_url,
240
+ json=payload,
241
+ headers=self.default_headers
242
+ )
243
+
244
+ if DEBUG_MODE:
245
+ print(f"DEBUG: API响应状态码: {response.status_code}")
246
+ print(f"DEBUG: API响应内容: {response.text[:1000]}")
247
+
248
+ if response.status_code == HTTP_STATUS_CENSORED:
249
+ return None, "内容不合规"
250
+
251
+ # 处理并发限制错误
252
+ if response.status_code == 433:
253
+ return None, "⏳ 服务器正忙,同时生成的图片数量已达上限,请稍后重试"
254
+
255
+ if response.status_code != HTTP_STATUS_OK:
256
+ return None, f"API请求失败: {response.status_code} - {response.text}"
257
+
258
+ # API直接返回UUID字符串(根据model_studio的实现)
259
+ content = response.text.strip()
260
+ task_uuid = content.replace('"', "")
261
+
262
+ if DEBUG_MODE:
263
+ print(f"DEBUG: API返回UUID: {task_uuid}")
264
+
265
+ if not task_uuid:
266
+ return None, f"未获取到任务ID,API响应: {response.text}"
267
+
268
+ # 轮询任务状态
269
+ result = await self._poll_task_status(task_uuid)
270
+ if result["success"]:
271
+ return result["image_url"], None
272
+ else:
273
+ return None, result["error"]
274
+
275
+ except Exception as e:
276
+ return None, f"生成图片时发生错误: {str(e)}"
277
+
278
+ # 创建图片生成客户端实例
279
+ image_client = ImageClient()
280
+
281
+ # 示例提示词
282
+ examples = [
283
+ "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k",
284
+ "An astronaut riding a green horse",
285
+ "A delicious ceviche cheesecake slice",
286
+ ]
287
+
288
+ css = """
289
+ .main-container {
290
+ max-width: 1400px !important;
291
+ margin: 0 auto !important;
292
+ padding: 20px !important;
293
+ }
294
+
295
+ .left-panel {
296
+ background: linear-gradient(145deg, #f8f9fa, #e9ecef) !important;
297
+ border-radius: 16px !important;
298
+ padding: 24px !important;
299
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1) !important;
300
+ border: 1px solid rgba(255,255,255,0.2) !important;
301
+ }
302
+
303
+ .right-panel {
304
+ background: linear-gradient(145deg, #ffffff, #f8f9fa) !important;
305
+ border-radius: 16px !important;
306
+ padding: 24px !important;
307
+ box-shadow: 0 8px 32px rgba(0,0,0,0.1) !important;
308
+ border: 1px solid rgba(255,255,255,0.2) !important;
309
+ display: flex !important;
310
+ flex-direction: column !important;
311
+ align-items: center !important;
312
+ justify-content: flex-start !important;
313
+ min-height: 600px !important;
314
+ }
315
+
316
+ #main-prompt textarea {
317
+ min-height: 180px !important;
318
+ font-size: 15px !important;
319
+ line-height: 1.6 !important;
320
+ padding: 16px !important;
321
+ border-radius: 12px !important;
322
+ border: 2px solid #e9ecef !important;
323
+ transition: all 0.3s ease !important;
324
+ background: white !important;
325
+ }
326
+
327
+ #main-prompt textarea:focus {
328
+ border-color: #4f46e5 !important;
329
+ box-shadow: 0 0 0 3px rgba(79, 70, 229, 0.1) !important;
330
+ }
331
+
332
+ .run-button {
333
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
334
+ border: none !important;
335
+ border-radius: 12px !important;
336
+ padding: 12px 32px !important;
337
+ font-weight: 600 !important;
338
+ font-size: 16px !important;
339
+ color: white !important;
340
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
341
+ transition: all 0.3s ease !important;
342
+ }
343
+
344
+ .run-button:hover {
345
+ transform: translateY(-2px) !important;
346
+ box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
347
+ }
348
+
349
+ .settings-section {
350
+ background: white !important;
351
+ border-radius: 12px !important;
352
+ padding: 16px !important;
353
+ margin-top: 12px !important;
354
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05) !important;
355
+ border: 1px solid #e9ecef !important;
356
+ }
357
+
358
+ .settings-title {
359
+ font-size: 18px !important;
360
+ font-weight: 600 !important;
361
+ color: #374151 !important;
362
+ margin-bottom: 12px !important;
363
+ padding-bottom: 6px !important;
364
+ border-bottom: 2px solid #e9ecef !important;
365
+ }
366
+
367
+ .right-panel .settings-title {
368
+ margin-bottom: 8px !important;
369
+ padding-bottom: 4px !important;
370
+ font-size: 16px !important;
371
+ }
372
+
373
+ .right-panel .block {
374
+ min-height: unset !important;
375
+ height: auto !important;
376
+ flex: none !important;
377
+ }
378
+
379
+ .right-panel .html-container {
380
+ padding: 0 !important;
381
+ margin: 0 !important;
382
+ }
383
+
384
+ .slider-container .wrap {
385
+ background: #f8f9fa !important;
386
+ border-radius: 8px !important;
387
+ padding: 6px !important;
388
+ margin: 2px 0 !important;
389
+ }
390
+
391
+ .settings-section .block {
392
+ margin: 4px 0 !important;
393
+ }
394
+
395
+ .settings-section .row {
396
+ margin: 6px 0 !important;
397
+ }
398
+
399
+ .settings-section .form {
400
+ gap: 4px !important;
401
+ }
402
+
403
+ .settings-section .html-container {
404
+ padding: 0 !important;
405
+ margin: 8px 0 4px 0 !important;
406
+ }
407
+
408
+ .result-image {
409
+ border-radius: 16px !important;
410
+ box-shadow: 0 8px 32px rgba(0,0,0,0.15) !important;
411
+ max-width: 100% !important;
412
+ height: auto !important;
413
+ min-height: 400px !important;
414
+ width: 100% !important;
415
+ }
416
+
417
+ .result-image img {
418
+ border-radius: 16px !important;
419
+ object-fit: contain !important;
420
+ max-width: 100% !important;
421
+ max-height: 600px !important;
422
+ width: auto !important;
423
+ height: auto !important;
424
+ }
425
+
426
+ .examples-container {
427
+ margin-top: 20px !important;
428
+ }
429
+
430
+ .title-header {
431
+ text-align: center !important;
432
+ margin-bottom: 30px !important;
433
+ padding: 20px !important;
434
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
435
+ color: white !important;
436
+ border-radius: 16px !important;
437
+ box-shadow: 0 4px 15px rgba(102, 126, 234, 0.3) !important;
438
+ }
439
+
440
+ .title-header h1 {
441
+ font-size: 28px !important;
442
+ font-weight: 700 !important;
443
+ margin: 0 !important;
444
+ text-shadow: 0 2px 4px rgba(0,0,0,0.1) !important;
445
+ }
446
+ """
447
+
448
+ async def infer(
449
+ prompt,
450
+ seed,
451
+ randomize_seed,
452
+ width,
453
+ height,
454
+ cfg,
455
+ steps,
456
+ model_name,
457
+ progress=gr.Progress(track_tqdm=True),
458
+ ):
459
+ if not prompt.strip():
460
+ raise gr.Error("提示词���能为空")
461
+
462
+ if randomize_seed:
463
+ seed = random.randint(0, MAX_SEED)
464
+
465
+ # 验证并调整尺寸
466
+ width, height = validate_dimensions(width, height)
467
+
468
+ # 验证其他参数
469
+ if not 1.0 <= cfg <= 20.0:
470
+ raise gr.Error("CFG Scale 必须在 1.0 到 20.0 之间")
471
+ if not 1 <= steps <= 50:
472
+ raise gr.Error("Steps 必须在 1 到 50 之间")
473
+
474
+ image_url, error = await image_client.generate_image(
475
+ prompt=prompt,
476
+ negative_prompt="",
477
+ seed=seed,
478
+ width=width,
479
+ height=height,
480
+ cfg=cfg,
481
+ steps=steps,
482
+ model_name=model_name
483
+ )
484
+
485
+ if error:
486
+ raise gr.Error(error)
487
+
488
+ return image_url, seed
489
+
490
+ with gr.Blocks(css=css) as demo:
491
+ with gr.Column(elem_classes=["main-container"]):
492
+ # 标题区域
493
+ with gr.Row(elem_classes=["title-header"]):
494
+ gr.HTML("<h1>🎨 Lumina Text-to-Image Playground</h1>")
495
+
496
+ # 主体左右布局
497
+ with gr.Row(equal_height=True):
498
+ # 左侧参数设置面板
499
+ with gr.Column(scale=1, elem_classes=["left-panel"]):
500
+ gr.HTML("<div class='settings-title'>✨ Generation Settings</div>")
501
+
502
+ # 主要提示词输入
503
+ prompt = gr.Text(
504
+ label="Prompt",
505
+ show_label=True,
506
+ max_lines=10,
507
+ lines=8,
508
+ placeholder="Describe what you want to generate in detail...",
509
+ elem_id="main-prompt",
510
+ )
511
+
512
+ run_button = gr.Button("🚀 Generate Image", elem_classes=["run-button"], variant="primary")
513
+
514
+ # 参数设置区域
515
+ with gr.Column(elem_classes=["settings-section"]):
516
+ gr.HTML("<div class='settings-title'>🎛️ Parameters</div>")
517
+
518
+ # 种子设置
519
+ with gr.Row():
520
+ seed = gr.Slider(
521
+ label="Seed",
522
+ minimum=0,
523
+ maximum=MAX_SEED,
524
+ step=1,
525
+ value=0,
526
+ elem_classes=["slider-container"]
527
+ )
528
+ randomize_seed = gr.Checkbox(label="Random Seed", value=True)
529
+
530
+ # 尺寸设置
531
+ gr.HTML("<div style='margin: 16px 0 8px 0; font-weight: 600; color: #6B7280;'>📐 Image Dimensions</div>")
532
+ with gr.Row():
533
+ width = gr.Slider(
534
+ label="Width",
535
+ minimum=256,
536
+ maximum=MAX_IMAGE_SIZE,
537
+ step=32,
538
+ value=1024,
539
+ elem_classes=["slider-container"]
540
+ )
541
+ height = gr.Slider(
542
+ label="Height",
543
+ minimum=256,
544
+ maximum=MAX_IMAGE_SIZE,
545
+ step=32,
546
+ value=1024,
547
+ elem_classes=["slider-container"]
548
+ )
549
+
550
+ # 生成参数
551
+ gr.HTML("<div style='margin: 16px 0 8px 0; font-weight: 600; color: #6B7280;'>⚙️ Generation Parameters</div>")
552
+ with gr.Row():
553
+ cfg = gr.Slider(
554
+ label="CFG Scale",
555
+ minimum=1.0,
556
+ maximum=20.0,
557
+ step=0.1,
558
+ value=5.5,
559
+ elem_classes=["slider-container"]
560
+ )
561
+ steps = gr.Slider(
562
+ label="Steps",
563
+ minimum=1,
564
+ maximum=50,
565
+ step=1,
566
+ value=30,
567
+ elem_classes=["slider-container"]
568
+ )
569
+
570
+ # 模型选择
571
+ model_name = gr.Dropdown(
572
+ label="🤖 Model Selection",
573
+ choices=list(MODEL_CONFIGS.keys()),
574
+ value="base"
575
+ )
576
+
577
+ # 示例提示词
578
+ with gr.Column(elem_classes=["examples-container"]):
579
+ gr.HTML("<div class='settings-title'>💡 Example Prompts</div>")
580
+ gr.Examples(examples=examples, inputs=[prompt])
581
+
582
+ # 右侧图像显示面板
583
+ with gr.Column(scale=1, elem_classes=["right-panel"]):
584
+ gr.HTML("<div class='settings-title'>🖼️ Generated Image</div>")
585
+ result = gr.Image(
586
+ label="Result",
587
+ show_label=False,
588
+ elem_classes=["result-image"],
589
+ height=600,
590
+ container=True,
591
+ show_download_button=True,
592
+ show_share_button=False
593
+ )
594
+
595
+ gr.on(
596
+ triggers=[run_button.click, prompt.submit],
597
+ fn=infer,
598
+ inputs=[
599
+ prompt,
600
+ seed,
601
+ randomize_seed,
602
+ width,
603
+ height,
604
+ cfg,
605
+ steps,
606
+ model_name,
607
+ ],
608
+ outputs=[result, seed],
609
+ )
610
+
611
+ if __name__ == "__main__":
612
+ demo.launch()
requirements.txt ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ gradio>=4.0.0
2
+ httpx>=0.24.0
3
+ python-dotenv>=1.0.0