shiertier commited on
Commit
ba95800
·
verified ·
1 Parent(s): 369c816

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +214 -97
app.py CHANGED
@@ -9,9 +9,9 @@ from typing import Any
9
  # 常量定义
10
  HTTP_STATUS_CENSORED = 451
11
  HTTP_STATUS_OK = 200
12
- MAX_SEED = 2147483647 # (2**31 - 1)
13
  MAX_IMAGE_SIZE = 2048
14
- MIN_IMAGE_SIZE = 256 # Smallest dimension for SDXL like models often 512, but API might support smaller. Adjusted to API's limits.
15
 
16
  # 调试模式
17
  DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
@@ -23,6 +23,7 @@ MODEL_CONFIGS = {
23
  "ep6": "0622.pth",
24
  }
25
 
 
26
  def validate_dimensions(width: int, height: int) -> tuple[int, int]:
27
  """验证并调整图片尺寸"""
28
  width = max(MIN_IMAGE_SIZE, min(int(width), MAX_IMAGE_SIZE))
@@ -31,79 +32,113 @@ def validate_dimensions(width: int, height: int) -> tuple[int, int]:
31
  height = (height // 32) * 32
32
  return width, height
33
 
 
34
  @dataclass
35
  class LuminaConfig:
36
  """Lumina模型配置"""
 
37
  model_name: str | None = None
38
  cfg: float | None = None
39
  step: int | None = None
40
 
 
41
  @dataclass
42
  class ImageGenerationConfig:
43
  """图像生成配置"""
 
44
  prompts: list[dict[str, Any]] = field(default_factory=list)
45
  width: int = 1024
46
  height: int = 1024
47
  seed: int | None = None
48
- use_polish: bool = False # This wasn't exposed in UI, assuming false
49
  is_lumina: bool = True
50
  lumina_config: LuminaConfig = field(default_factory=LuminaConfig)
51
 
 
52
  class ImageClient:
53
  """图像生成客户端"""
 
54
  def __init__(self) -> None:
55
  self.x_token = os.environ.get("API_TOKEN", "")
56
  if not self.x_token:
57
  raise ValueError("环境变量中未设置API_TOKEN")
58
 
59
  self.lumina_api_url = "https://ops.api.talesofai.cn/v3/make_image"
60
- self.lumina_task_status_url = "https://ops.api.talesofai.cn/v1/artifact/task/{task_uuid}"
 
 
61
  self.max_polling_attempts = 100
62
  self.polling_interval = 3.0
63
  self.default_headers = {
64
  "Content-Type": "application/json",
65
- "x-platform": "nieta-app/web", # Or a generic identifier if preferred
66
  "X-Token": self.x_token,
67
  }
68
 
69
- def _prepare_prompt_data(self, prompt: str, negative_prompt: str = "") -> list[dict[str, Any]]:
 
 
70
  prompts_data = [{"type": "freetext", "value": prompt, "weight": 1.0}]
71
  if negative_prompt:
72
- prompts_data.append({"type": "freetext", "value": negative_prompt, "weight": -1.0})
73
- prompts_data.append({
74
- "type": "elementum", "value": "b5edccfe-46a2-4a14-a8ff-f4d430343805",
75
- "uuid": "b5edccfe-46a2-4a14-a8ff-f4d430343805", "weight": 1.0, "name": "lumina1",
76
- "img_url": "https://oss.talesofai.cn/picture_s/1y7f53e6itfn_0.jpeg",
77
- "domain": "", "parent": "", "label": None, "sort_index": 0, "status": "IN_USE",
78
- "polymorphi_values": {}, "sub_type": None,
79
- })
 
 
 
 
 
 
 
 
 
 
 
 
80
  return prompts_data
81
 
82
  def _build_payload(self, config: ImageGenerationConfig) -> dict[str, Any]:
83
  payload = {
84
- "storyId": "", "jobType": "universal", "width": config.width, "height": config.height,
85
- "rawPrompt": config.prompts, "seed": config.seed, "meta": {"entrance": "PICTURE,PURE"},
86
- "context_model_series": None, "negative_freetext": "", # Negative handled in rawPrompt
 
 
 
 
 
 
87
  "advanced_translator": config.use_polish,
88
  }
89
  if config.is_lumina:
90
  client_args = {}
91
- if config.lumina_config.model_name: client_args["ckpt_name"] = config.lumina_config.model_name
92
- if config.lumina_config.cfg is not None: client_args["cfg"] = str(config.lumina_config.cfg)
93
- if config.lumina_config.step is not None: client_args["steps"] = str(config.lumina_config.step)
94
- if client_args: payload["client_args"] = client_args
 
 
 
 
95
  return payload
96
 
97
  async def _poll_task_status(self, task_uuid: str) -> dict[str, Any]:
98
  status_url = self.lumina_task_status_url.format(task_uuid=task_uuid)
99
- async with httpx.AsyncClient(timeout=30.0) as client: # Timeout for individual poll request
 
 
100
  for attempt in range(self.max_polling_attempts):
101
  response = await client.get(status_url, headers=self.default_headers)
102
 
103
  if response.status_code != HTTP_STATUS_OK:
104
  return {
105
  "success": False,
106
- "error": f"获取任务状态失败: {response.status_code} - {response.text}"
107
  }
108
 
109
  # 解析JSON响应
@@ -112,7 +147,7 @@ class ImageClient:
112
  except Exception as e:
113
  return {
114
  "success": False,
115
- "error": f"任务状态响应解析失败: {response.text[:500]}"
116
  }
117
 
118
  task_status = result.get("task_status")
@@ -122,37 +157,35 @@ class ImageClient:
122
  if artifacts and len(artifacts) > 0:
123
  image_url = artifacts[0].get("url")
124
  if image_url:
125
- return {
126
- "success": True,
127
- "image_url": image_url
128
- }
129
- return {
130
- "success": False,
131
- "error": "无法从结果中提取图像URL"
132
- }
133
  elif task_status == "FAILURE":
134
  return {
135
  "success": False,
136
- "error": result.get("error", "任务执行失败")
137
  }
138
  elif task_status == "ILLEGAL_IMAGE":
139
- return {
140
- "success": False,
141
- "error": "图片不合规"
142
- }
143
  elif task_status == "TIMEOUT":
144
- return {
145
- "success": False,
146
- "error": "任务超时"
147
- }
148
 
149
  await asyncio.sleep(self.polling_interval)
150
  return {
151
  "success": False,
152
- "error": "⏳ 生图任务超时(5分钟),服务器可能正在处理大量请求,请稍后重试"
153
  }
154
 
155
- async def generate_image(self, prompt: str, negative_prompt: str, seed: int, width: int, height: int, cfg: float, steps: int, model_name: str = "ep3") -> tuple[str | None, str | None]:
 
 
 
 
 
 
 
 
 
 
156
  """生成图片"""
157
  try:
158
  # 获取模型路径
@@ -165,11 +198,7 @@ class ImageClient:
165
  height=height,
166
  seed=seed,
167
  is_lumina=True,
168
- lumina_config=LuminaConfig(
169
- model_name=model_path,
170
- cfg=cfg,
171
- step=steps
172
- )
173
  )
174
  # 发送生成请求
175
  async with httpx.AsyncClient(timeout=300.0) as client:
@@ -179,9 +208,7 @@ class ImageClient:
179
  print(f"DEBUG: 请求载荷: {payload}")
180
 
181
  response = await client.post(
182
- self.lumina_api_url,
183
- json=payload,
184
- headers=self.default_headers
185
  )
186
 
187
  if DEBUG_MODE:
@@ -196,7 +223,10 @@ class ImageClient:
196
  return None, "⏳ 服务器正忙,同时生成的图片数量已达上限,请稍后重试"
197
 
198
  if response.status_code != HTTP_STATUS_OK:
199
- return None, f"API请求失败: {response.status_code} - {response.text}"
 
 
 
200
 
201
  # API直接返回UUID字符串(根据model_studio的实现)
202
  content = response.text.strip()
@@ -217,6 +247,7 @@ class ImageClient:
217
  except Exception as e:
218
  return None, f"生成图片时发生错误: {str(e)}"
219
 
 
220
  # 创建图片生成客户端实例
221
  try:
222
  image_client = ImageClient()
@@ -234,17 +265,37 @@ example_titles = [
234
  "黄昏天台,白裙女子独望天空,惆怅静谧,风中飘扬",
235
  ]
236
  full_prompts = {
237
- example_titles[0]: "looking_up, rain, heavy_rain, emotionless, high_angle, perspective, face_focus, from_above, from_side, grey_background, empty_eyes, defeated_look, limited_palette, partially_colored, monochrome_background, wet_hair, wet_clothes, messy_hair A figure stands motionless in torrential rain, their face captured from a high-angle perspective with vacant eyes staring upward into the downpour. Sodden strands of hair cling to pale skin, soaked clothing blending into the monochrome grey void surrounding them. The limited color palette isolates faint blue tones in their parted lips against the washed-out background, their expression of utter defeat frozen as raindrops streak vertically through the frame. Sharp focus on their emotionless features contrasts with the blurred chaos of falling water, the composition's oppressive atmosphere amplified by the overhead view's psychological distance.",
238
- example_titles[1]: "Elegant anime-style illustration of a glamorous woman standing on a dimly lit staircase, bathed in soft, ethereal light. Medium full-body shot. She has short, wavy black hair adorned with ornate golden hairpieces and a delicate feather. Her makeup is refined, with deep red lipstick and sharp eyeliner accentuating her poised expression. She wears a dark green high-collared cheongsam embroidered with a vividly colored crane and landscape motif, blending traditional Chinese elements with a touch of Art Deco. Her black lace gloves, beaded pearl chains draped across her body, and intricate earrings enhance her luxurious, vintage aura. A fur stole rests over her arms, adding texture and sophistication. The setting is subtly blurred, with falling sparkles and filtered light through windowpanes creating a dreamy, nostalgic atmosphere. The composition evokes 1930s Shanghai elegance, rendered in a painterly digital style with high fashion sensibility and dramatic lighting contrast.",
239
- example_titles[2]: "薄明のサイバーパンク都市を背景にした、ハイブリッドな獣人少女のアニメイラスト。構図はバストアップ〜ウエストまでの斜めアングルで、キャラクターが観覧者にやや振り返るような姿勢をとっている。彼女の顔は精巧な狼のマズルと人間的な表情が融合しており、瞳はグリッチ風の蛍光ブルーに輝くオッドアイ。毛皮は柔らかな銀灰色で、頬や耳の先端には淡い紫のグラデーションが入り、毛並みは丁寧に一本ずつ描写されている。衣装は近未来的なデザインのボディスーツで、胸元には蛍光回路のような文様が走り、左肩には機械義肢風のメタルアーマーが装着されている。耳にはピアスとデータ受信装置が取り付けられており、首には布製のスカーフと識別タグが結ばれている。背景にはネオンに照らされた廃墟ビルとホログラム広告が揺らめき、空にはサイバー・オーロラが流れている。カラーリングはダークトーンに蛍光アクセントが効いたネオ東京風配色(黒、紫、青、サイバーグリーン)。アートスタイルはセルルックアニメ+ハードエッジグラデーション、ディテール豊かで表情と毛の質感に特に重点を置く。キャラクターの雰囲気はクールで孤独だが、どこか繊細な感情を秘めた印象。",
240
- example_titles[3]: "銀白色の長い髪を持つ若い女性が黒いコートを羽織り、都会の街中に佇む、メランコリックな雰囲気のアニメ風イラスト。背景にはグレースケールで描かれた��顔のない人々の群衆がぼかされて描かれ、彼女だけがシャープな焦点で際立っている。彼女は長いダークカラーのオーバーコートを着用し、物憂げで遠くを見つめるような表情を浮かべている。シーンは現代の都市の街角で設定されており、背景には建築的な要素が描かれている。配色はモノクロームで、黒・白・灰色を基調としたフィルム・ノワール風の雰囲気を醸し出している。アートスタイルはリアルな都市描写とアニメ的キャラクターデザインを融合させており、主人公の細部まで描き込まれた描写と、幽霊のように描かれた背景の人々とのコントラストによって、強い孤独感と疎外感を表現している。どんよりとした曇り空のライティングと都市背景が、重苦しい空気感を一層引き立てている。キャラクターデザインは、暗い服装に映える銀髪により、どこか神秘的または超自然的な要素を感じさせる。プロフェッショナルなデジタルイラストで、映画のような質感と強い感情的トーンを備え、現代社会における孤独や断絶のテーマを描いている。雰囲気としては、心理的または超自然的な物語の一場面を想起させる構成となっている。",
241
- example_titles[4]: "戏剧感十足的数字插画,描绘一位黑社会风格的长发美男子大佬坐姿翘着二郎腿,从大仰视视角俯视观者,气场强烈。他身穿剪裁锋利的深色西装,带有低调的细条纹图案,外套敞开,内衬深色马甲与整洁的衬衫,显得既优雅又危险。他的长发黑亮顺滑,自肩膀垂落,几缕发丝在环境光中微微反光。他一只手漫不经心地夹着一根燃着的雪茄,袅袅烟雾在昏暗中盘旋上升。他俯视下方,眉头微蹙,唇角紧绷,脸上带有一丝明显的不悦,神情冷峻。仰视视角放大了他身上的压迫感与支配力,整个人散发出沉稳却不可侵犯的气势。另一只手或搭在椅扶或搁在膝上,动作自信从容。背景为昏暗豪华的私人会客室或办公室,可见金属饰条、皮革沙发或昏黄灯光等元素。画面风格精致、电影感强烈,通过明暗对比、表情细节与强光源制造紧张与权威氛围。",
242
- example_titles[5]: "惆怅氛围的数字插画,描绘一位身穿白裙的女子独自站在天台上,在黄昏时分凝望天空。她的长发与白裙的裙摆在风中轻轻飘扬,画面充满静谧与动感的交织。她仰望着染上橙粉色霞光的天空,脸上露出若有所思、带着忧郁的神情,仿佛沉浸在回忆或思念之中。夕阳的余晖洒落在她身上与天台上,为画面增添一层柔和的金色光晕。她的站姿略带沉重,肩膀微微下垂,双手自然垂落或轻轻拽住裙摆,展现出内敛的情绪张力。天台背景简洁,设有低矮护栏、通风设备等结构,远处是模糊的城市天际线,进一步强化孤独氛围。风吹起她的发丝与裙摆,为静止的场景注入一丝淡淡的动态诗意。画风柔和写意,色彩渐变细腻,注重光影、空气感与人物情绪的融合,营造出宁静而感伤的黄昏场景。"
 
 
 
 
 
 
 
 
 
 
 
 
243
  }
244
 
 
245
  async def infer(
246
- prompt_text, seed_val, randomize_seed_val, width_val, height_val,
247
- cfg_val, steps_val, model_name_val, progress=gr.Progress(track_tqdm=True)
 
 
 
 
 
 
 
248
  ):
249
  print(prompt_text)
250
  if image_client is None:
@@ -273,7 +324,7 @@ async def infer(
273
  height=height_val,
274
  cfg=float(cfg_val),
275
  steps=int(steps_val),
276
- model_name=model_name_val
277
  )
278
 
279
  if error:
@@ -283,14 +334,18 @@ async def infer(
283
 
284
 
285
  # Links for HTML header
286
- DISCORD_LINK = os.environ.get("DISCORD_LINK", "https://discord.com/invite/AtRtbe9W8w") # Example
287
- APP_INDEX_LINK = os.environ.get("APP_INDEX_LINK", "https://neta.art/") # Example
288
- APP_INDEX_ICON = "https://cdn-avatars.huggingface.co/v1/production/uploads/62be651a1e22ec8427aa7096/XQEUF5niIZXQbiOOxn8rQ.jpeg" # Using HF logo
 
 
289
 
290
 
291
  with gr.Blocks(theme=gr.themes.Soft(), title="Lumina Image Playground") as demo:
292
  gr.Markdown("<h1>🎨 Lumina Text-to-Image Playground | Lumina 文本生图工具</h1>")
293
- gr.Markdown("Describe your vision and let the AI bring it to life! Uses an external API for image generation. | 描述您的创意愿景,让AI为您创造精美图像!使用外部API进行图像生成。")
 
 
294
 
295
  gr.HTML(f"""
296
  <div style="display: flex; justify-content: flex-start; align-items: center; gap: 15px; margin-bottom: 20px; padding: 10px;">
@@ -306,67 +361,129 @@ with gr.Blocks(theme=gr.themes.Soft(), title="Lumina Image Playground") as demo:
306
  """)
307
 
308
  with gr.Row(variant="panel"):
309
- with gr.Column(scale=2): # Controls Panel
310
  gr.Markdown("## ⚙️ Generation Controls | 生成控制")
311
  prompt = gr.Textbox(
312
- label="Prompt | 提示词", lines=5,
 
313
  placeholder="e.g., A majestic dragon soaring through a cyberpunk city skyline, neon lights reflecting off its scales, intricate details. | 例如:一条威武的巨龙翱翔在赛博朋克城市天际线,霓虹灯映照在它的鳞片上,细节精美。",
314
- info="Describe the image you want to create. | 描述您想要创建的图像。"
315
  )
316
 
317
  with gr.Accordion("🔧 Advanced Settings | 高级设置", open=True):
318
  model_name = gr.Dropdown(
319
- label="Model Version | 模型版本", choices=list(MODEL_CONFIGS.keys()), value="ep3",
320
- info="Select the generation model. | 选择生成模型。"
 
 
321
  )
322
  with gr.Row():
323
- cfg = gr.Slider(label="CFG Scale | CFG缩放", minimum=1.0, maximum=20.0, step=0.1, value=5.5, info="Guidance strength. Higher values adhere more to prompt. | 引导强度,更高的值更贴近提示词。")
324
- steps = gr.Slider(label="Sampling Steps | 采样步数", minimum=1, maximum=50, step=1, value=30, info="Number of steps. More steps can improve quality but take longer. | 步数,更多步数可提高质量但耗时更长。")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
325
 
326
  with gr.Row():
327
- width = gr.Slider(label="Width | 宽度", minimum=MIN_IMAGE_SIZE, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
328
- height = gr.Slider(label="Height | 高度", minimum=MIN_IMAGE_SIZE, maximum=MAX_IMAGE_SIZE, step=32, value=1024)
 
 
 
 
 
 
 
 
 
 
 
 
329
 
330
  with gr.Row():
331
- seed = gr.Slider(label="Seed | 种子", minimum=0, maximum=MAX_SEED, step=1, value=random.randint(0, MAX_SEED))
332
- randomize_seed = gr.Checkbox(label="Randomize Seed | 随机种子", value=True, info="Use a new random seed for each generation if checked. | 勾选后每次生成使用新的随机种子。")
333
-
334
- run_button = gr.Button("🚀 Generate Image | 生成图像", variant="primary", scale=0)
 
 
 
 
 
 
 
 
 
 
 
 
335
 
336
  with gr.Group():
337
- gr.Markdown("### ✨ Example Prompts | 示例提示词")
338
- for i, title in enumerate(example_titles):
339
- btn = gr.Button(title)
340
- btn.click(lambda t=title: full_prompts[t], outputs=[prompt])
341
 
342
-
343
- with gr.Column(scale=3): # Output Panel
344
  gr.Markdown("## 🖼️ Generated Image | 生成图像")
345
  result_image = gr.Image(
346
- label="Output Image | 输出图像", show_label=False, type="filepath",
347
- height=600, # Max display height
348
- show_download_button=True, interactive=False,
349
- elem_id="result_image_display" # for potential CSS targeting if needed
 
 
 
 
 
 
 
 
350
  )
351
- generated_seed_info = gr.Textbox(label="Seed Used | 使用的种子", interactive=False, placeholder="The seed for the generated image will appear here. | 生成图像所使用的种子值将显示在此处。")
352
 
353
  # Event Handlers
354
  inputs_list = [prompt, seed, randomize_seed, width, height, cfg, steps, model_name]
355
  outputs_list = [result_image, generated_seed_info]
356
 
357
- run_button.click(fn=infer, inputs=inputs_list, outputs=outputs_list, api_name="generate_image")
358
- prompt.submit(fn=infer, inputs=inputs_list, outputs=outputs_list, api_name="generate_image_submit")
 
 
 
 
 
 
 
359
 
360
 
361
  if __name__ == "__main__":
362
  if DEBUG_MODE:
363
  print("DEBUG_MODE is enabled.")
364
  if not os.environ.get("API_TOKEN"):
365
- print("**************************************************************************************")
 
 
366
  print("WARNING: API_TOKEN environment variable is not set locally.")
367
- print("The application will run, but image generation will fail until API_TOKEN is provided.")
 
 
368
  print("You can set it by running: export API_TOKEN='your_actual_token_here'")
369
- print("Or if using a .env file, ensure it's loaded or API_TOKEN is set in your run config.")
370
- print("**************************************************************************************")
371
-
372
- demo.launch(debug=DEBUG_MODE, show_error=True)
 
 
 
 
 
9
  # 常量定义
10
  HTTP_STATUS_CENSORED = 451
11
  HTTP_STATUS_OK = 200
12
+ MAX_SEED = 2147483647 # (2**31 - 1)
13
  MAX_IMAGE_SIZE = 2048
14
+ MIN_IMAGE_SIZE = 256 # Smallest dimension for SDXL like models often 512, but API might support smaller. Adjusted to API's limits.
15
 
16
  # 调试模式
17
  DEBUG_MODE = os.environ.get("DEBUG_MODE", "false").lower() == "true"
 
23
  "ep6": "0622.pth",
24
  }
25
 
26
+
27
  def validate_dimensions(width: int, height: int) -> tuple[int, int]:
28
  """验证并调整图片尺寸"""
29
  width = max(MIN_IMAGE_SIZE, min(int(width), MAX_IMAGE_SIZE))
 
32
  height = (height // 32) * 32
33
  return width, height
34
 
35
+
36
  @dataclass
37
  class LuminaConfig:
38
  """Lumina模型配置"""
39
+
40
  model_name: str | None = None
41
  cfg: float | None = None
42
  step: int | None = None
43
 
44
+
45
  @dataclass
46
  class ImageGenerationConfig:
47
  """图像生成配置"""
48
+
49
  prompts: list[dict[str, Any]] = field(default_factory=list)
50
  width: int = 1024
51
  height: int = 1024
52
  seed: int | None = None
53
+ use_polish: bool = False # This wasn't exposed in UI, assuming false
54
  is_lumina: bool = True
55
  lumina_config: LuminaConfig = field(default_factory=LuminaConfig)
56
 
57
+
58
  class ImageClient:
59
  """图像生成客户端"""
60
+
61
  def __init__(self) -> None:
62
  self.x_token = os.environ.get("API_TOKEN", "")
63
  if not self.x_token:
64
  raise ValueError("环境变量中未设置API_TOKEN")
65
 
66
  self.lumina_api_url = "https://ops.api.talesofai.cn/v3/make_image"
67
+ self.lumina_task_status_url = (
68
+ "https://ops.api.talesofai.cn/v1/artifact/task/{task_uuid}"
69
+ )
70
  self.max_polling_attempts = 100
71
  self.polling_interval = 3.0
72
  self.default_headers = {
73
  "Content-Type": "application/json",
74
+ "x-platform": "nieta-app/web", # Or a generic identifier if preferred
75
  "X-Token": self.x_token,
76
  }
77
 
78
+ def _prepare_prompt_data(
79
+ self, prompt: str, negative_prompt: str = ""
80
+ ) -> list[dict[str, Any]]:
81
  prompts_data = [{"type": "freetext", "value": prompt, "weight": 1.0}]
82
  if negative_prompt:
83
+ prompts_data.append(
84
+ {"type": "freetext", "value": negative_prompt, "weight": -1.0}
85
+ )
86
+ prompts_data.append(
87
+ {
88
+ "type": "elementum",
89
+ "value": "b5edccfe-46a2-4a14-a8ff-f4d430343805",
90
+ "uuid": "b5edccfe-46a2-4a14-a8ff-f4d430343805",
91
+ "weight": 1.0,
92
+ "name": "lumina1",
93
+ "img_url": "https://oss.talesofai.cn/picture_s/1y7f53e6itfn_0.jpeg",
94
+ "domain": "",
95
+ "parent": "",
96
+ "label": None,
97
+ "sort_index": 0,
98
+ "status": "IN_USE",
99
+ "polymorphi_values": {},
100
+ "sub_type": None,
101
+ }
102
+ )
103
  return prompts_data
104
 
105
  def _build_payload(self, config: ImageGenerationConfig) -> dict[str, Any]:
106
  payload = {
107
+ "storyId": "",
108
+ "jobType": "universal",
109
+ "width": config.width,
110
+ "height": config.height,
111
+ "rawPrompt": config.prompts,
112
+ "seed": config.seed,
113
+ "meta": {"entrance": "PICTURE,PURE"},
114
+ "context_model_series": None,
115
+ "negative_freetext": "", # Negative handled in rawPrompt
116
  "advanced_translator": config.use_polish,
117
  }
118
  if config.is_lumina:
119
  client_args = {}
120
+ if config.lumina_config.model_name:
121
+ client_args["ckpt_name"] = config.lumina_config.model_name
122
+ if config.lumina_config.cfg is not None:
123
+ client_args["cfg"] = str(config.lumina_config.cfg)
124
+ if config.lumina_config.step is not None:
125
+ client_args["steps"] = str(config.lumina_config.step)
126
+ if client_args:
127
+ payload["client_args"] = client_args
128
  return payload
129
 
130
  async def _poll_task_status(self, task_uuid: str) -> dict[str, Any]:
131
  status_url = self.lumina_task_status_url.format(task_uuid=task_uuid)
132
+ async with httpx.AsyncClient(
133
+ timeout=30.0
134
+ ) as client: # Timeout for individual poll request
135
  for attempt in range(self.max_polling_attempts):
136
  response = await client.get(status_url, headers=self.default_headers)
137
 
138
  if response.status_code != HTTP_STATUS_OK:
139
  return {
140
  "success": False,
141
+ "error": f"获取任务状态失败: {response.status_code} - {response.text}",
142
  }
143
 
144
  # 解析JSON响应
 
147
  except Exception as e:
148
  return {
149
  "success": False,
150
+ "error": f"任务状态响应解析失败: {response.text[:500]}",
151
  }
152
 
153
  task_status = result.get("task_status")
 
157
  if artifacts and len(artifacts) > 0:
158
  image_url = artifacts[0].get("url")
159
  if image_url:
160
+ return {"success": True, "image_url": image_url}
161
+ return {"success": False, "error": "无法从结果中提取图像URL"}
 
 
 
 
 
 
162
  elif task_status == "FAILURE":
163
  return {
164
  "success": False,
165
+ "error": result.get("error", "任务执行失败"),
166
  }
167
  elif task_status == "ILLEGAL_IMAGE":
168
+ return {"success": False, "error": "图片不合规"}
 
 
 
169
  elif task_status == "TIMEOUT":
170
+ return {"success": False, "error": "任务超时"}
 
 
 
171
 
172
  await asyncio.sleep(self.polling_interval)
173
  return {
174
  "success": False,
175
+ "error": "⏳ 生图任务超时(5分钟),服务器可能正在处理大量请求,请稍后重试",
176
  }
177
 
178
+ async def generate_image(
179
+ self,
180
+ prompt: str,
181
+ negative_prompt: str,
182
+ seed: int,
183
+ width: int,
184
+ height: int,
185
+ cfg: float,
186
+ steps: int,
187
+ model_name: str = "ep3",
188
+ ) -> tuple[str | None, str | None]:
189
  """生成图片"""
190
  try:
191
  # 获取模型路径
 
198
  height=height,
199
  seed=seed,
200
  is_lumina=True,
201
+ lumina_config=LuminaConfig(model_name=model_path, cfg=cfg, step=steps),
 
 
 
 
202
  )
203
  # 发送生成请求
204
  async with httpx.AsyncClient(timeout=300.0) as client:
 
208
  print(f"DEBUG: 请求载荷: {payload}")
209
 
210
  response = await client.post(
211
+ self.lumina_api_url, json=payload, headers=self.default_headers
 
 
212
  )
213
 
214
  if DEBUG_MODE:
 
223
  return None, "⏳ 服务器正忙,同时生成的图片数量已达上限,请稍后重试"
224
 
225
  if response.status_code != HTTP_STATUS_OK:
226
+ return (
227
+ None,
228
+ f"API请求失败: {response.status_code} - {response.text}",
229
+ )
230
 
231
  # API直接返回UUID字符串(根据model_studio的实现)
232
  content = response.text.strip()
 
247
  except Exception as e:
248
  return None, f"生成图片时发生错误: {str(e)}"
249
 
250
+
251
  # 创建图片生成客户端实例
252
  try:
253
  image_client = ImageClient()
 
265
  "黄昏天台,白裙女子独望天空,惆怅静谧,风中飘扬",
266
  ]
267
  full_prompts = {
268
+ example_titles[
269
+ 0
270
+ ]: "looking_up, rain, heavy_rain, emotionless, high_angle, perspective, face_focus, from_above, from_side, grey_background, empty_eyes, defeated_look, limited_palette, partially_colored, monochrome_background, wet_hair, wet_clothes, messy_hair A figure stands motionless in torrential rain, their face captured from a high-angle perspective with vacant eyes staring upward into the downpour. Sodden strands of hair cling to pale skin, soaked clothing blending into the monochrome grey void surrounding them. The limited color palette isolates faint blue tones in their parted lips against the washed-out background, their expression of utter defeat frozen as raindrops streak vertically through the frame. Sharp focus on their emotionless features contrasts with the blurred chaos of falling water, the composition's oppressive atmosphere amplified by the overhead view's psychological distance.",
271
+ example_titles[
272
+ 1
273
+ ]: "Elegant anime-style illustration of a glamorous woman standing on a dimly lit staircase, bathed in soft, ethereal light. Medium full-body shot. She has short, wavy black hair adorned with ornate golden hairpieces and a delicate feather. Her makeup is refined, with deep red lipstick and sharp eyeliner accentuating her poised expression. She wears a dark green high-collared cheongsam embroidered with a vividly colored crane and landscape motif, blending traditional Chinese elements with a touch of Art Deco. Her black lace gloves, beaded pearl chains draped across her body, and intricate earrings enhance her luxurious, vintage aura. A fur stole rests over her arms, adding texture and sophistication. The setting is subtly blurred, with falling sparkles and filtered light through windowpanes creating a dreamy, nostalgic atmosphere. The composition evokes 1930s Shanghai elegance, rendered in a painterly digital style with high fashion sensibility and dramatic lighting contrast.",
274
+ example_titles[
275
+ 2
276
+ ]: "薄明のサイバーパンク都市を背景にした、ハイブリッドな獣人少女のアニメイラスト。構図はバストアップ〜ウエストまでの斜めアングルで、キャラクターが観覧者にやや振り返るような姿勢をとっている。彼女の顔は精巧な狼のマズルと人間的な表情が融合しており、瞳はグリッチ風の蛍光ブルーに輝くオッドアイ。毛皮は柔らかな銀灰色で、頬や耳の先端には淡い紫のグラデーションが入り、毛並みは丁寧に一本ずつ描写されている。衣装は近未来的なデザインのボディスーツで、胸元には蛍光回路のような文様が走り、左肩には機械義肢風のメタルアーマーが装着されている。耳にはピアスとデータ受信装置が取り付けられており、首には布製のスカーフと識別タグが結ばれている。背景にはネオンに照らされた廃墟ビルとホログラム広告が揺らめき、空にはサイバー・オーロラが流れている。カラーリングはダークトーンに蛍光アクセントが効いたネオ東京風配色(黒、紫、青、サイバーグリーン)。アートスタイルはセルルックアニメ+ハードエッジグラデーション、ディテール豊かで表情と毛の質感に特に重点を置く。キャラクターの雰囲気はクールで孤独だが、どこか繊細な感情を秘めた印象。",
277
+ example_titles[
278
+ 3
279
+ ]: "銀白色の長い髪を持つ若い女性が黒いコートを羽織り、都会の街中に佇む、メランコリックな雰囲気のアニメ風イラスト。背景にはグレースケールで描かれた、顔のない人々の群衆がぼかされて描かれ、彼女だけがシャープな焦点で際立っている。彼女は長いダークカラーのオーバーコートを着用し、物憂げで遠くを見つめるような表情を浮かべている。シーンは現代の都市の街角で設定されており、背景には建築的な要素が描かれている。配色はモノクロームで、黒・白・灰色を基調としたフィルム・ノワール風の雰囲気を醸し出している。アートスタイルはリアルな都市描写とアニメ的キャラクターデザインを融合させており、主人公の細部まで描き込まれた描写と、幽霊のように描かれた背景の人々とのコントラストによって、強い孤独感と疎外感を表現している。どんよりとした曇り空のライティングと都市背景が、重苦しい空気感を一層引き立てている。キャラクターデザインは、暗い服装に映える銀髪により、どこか神秘的または超自然的な要素を感じさせる。プロフェッショナルなデジタルイラストで、映画のような質感と強い感情的トーンを備え、現代社会における孤独や断絶のテーマを描いている。雰囲気としては、心理的または超自然的な物語の一場面を想起させる構成となっている。",
280
+ example_titles[
281
+ 4
282
+ ]: "戏剧感十足的数字插画,描绘一位黑社会风格的长发美男子大佬坐姿翘着二郎腿,从大仰视视角俯视观者,气场强烈。他身穿剪裁锋利的深色西装,带有低调的细条纹图案,外套敞开,内衬深色马甲与整洁的衬衫,显得既优雅又危险。他的长发黑亮顺滑,自肩膀垂落,几缕发丝在环境光中微微反光。他一只手漫不经心地夹着一根燃着的雪茄,袅袅烟雾在昏暗中盘旋上升。他俯视下方,眉头微蹙,唇角紧绷,脸上带有一丝明显的不悦,神情冷峻。仰视视角放大了他身上的压迫感与支配力,整个人散发出沉稳却不可侵犯的气势。另一只手或搭在椅扶或搁在膝上,动作自信从容。背景为昏暗豪华的私人会客室或办公室,可见金属饰条、皮革沙发或昏黄灯光等元素。画面风格精致、电影感强烈,通过明暗对比、表情细节与强光源制造紧张与权威氛围。",
283
+ example_titles[
284
+ 5
285
+ ]: "惆怅氛围的数字插画,描绘一位身穿白裙的女子独自站在天台上,在黄昏时分凝望天空。她的长发与白裙的裙摆在风中轻轻飘扬,画面充满静谧与动感的交织。她仰望着染上橙粉色霞光的天空,脸上露出若有所思、带着忧郁的神情,仿佛沉浸在回忆或思念之中。夕阳的余晖洒落在她身上与天台上,为画面增添一层柔和的金色光晕。她的站姿略带沉重,肩膀微微下垂,双手自然垂落或轻轻拽住裙摆,展现出内敛的情绪张力。天台背景简洁,设有低矮护栏、通风设备等结构,远处是模糊的城市天际线,进一步强化孤独氛围。风吹起她的发丝与裙摆,为静止的场景注入一丝淡淡的动态诗意。画风柔和写意,色彩渐变细腻,注重光影、空气感与人物情绪的融合,营造出宁静而感伤的黄昏场景。",
286
  }
287
 
288
+
289
  async def infer(
290
+ prompt_text,
291
+ seed_val,
292
+ randomize_seed_val,
293
+ width_val,
294
+ height_val,
295
+ cfg_val,
296
+ steps_val,
297
+ model_name_val,
298
+ progress=gr.Progress(track_tqdm=True),
299
  ):
300
  print(prompt_text)
301
  if image_client is None:
 
324
  height=height_val,
325
  cfg=float(cfg_val),
326
  steps=int(steps_val),
327
+ model_name=model_name_val,
328
  )
329
 
330
  if error:
 
334
 
335
 
336
  # Links for HTML header
337
+ DISCORD_LINK = os.environ.get(
338
+ "DISCORD_LINK", "https://discord.com/invite/AtRtbe9W8w"
339
+ ) # Example
340
+ APP_INDEX_LINK = os.environ.get("APP_INDEX_LINK", "https://neta.art/") # Example
341
+ APP_INDEX_ICON = "https://cdn-avatars.huggingface.co/v1/production/uploads/62be651a1e22ec8427aa7096/XQEUF5niIZXQbiOOxn8rQ.jpeg" # Using HF logo
342
 
343
 
344
  with gr.Blocks(theme=gr.themes.Soft(), title="Lumina Image Playground") as demo:
345
  gr.Markdown("<h1>🎨 Lumina Text-to-Image Playground | Lumina 文本生图工具</h1>")
346
+ gr.Markdown(
347
+ "Describe your vision and let the AI bring it to life! Uses an external API for image generation. | 描述您的创意愿景,让AI为您创造精美图像!使用外部API进行图像生成。"
348
+ )
349
 
350
  gr.HTML(f"""
351
  <div style="display: flex; justify-content: flex-start; align-items: center; gap: 15px; margin-bottom: 20px; padding: 10px;">
 
361
  """)
362
 
363
  with gr.Row(variant="panel"):
364
+ with gr.Column(scale=2): # Controls Panel
365
  gr.Markdown("## ⚙️ Generation Controls | 生成控制")
366
  prompt = gr.Textbox(
367
+ label="Prompt | 提示词",
368
+ lines=5,
369
  placeholder="e.g., A majestic dragon soaring through a cyberpunk city skyline, neon lights reflecting off its scales, intricate details. | 例如:一条威武的巨龙翱翔在赛博朋克城市天际线,霓虹灯映照在它的鳞片上,细节精美。",
370
+ info="Describe the image you want to create. | 描述您想要创建的图像。",
371
  )
372
 
373
  with gr.Accordion("🔧 Advanced Settings | 高级设置", open=True):
374
  model_name = gr.Dropdown(
375
+ label="Model Version | 模型版本",
376
+ choices=list(MODEL_CONFIGS.keys()),
377
+ value="ep6",
378
+ info="Select the generation model. | 选择生成模型。",
379
  )
380
  with gr.Row():
381
+ cfg = gr.Slider(
382
+ label="CFG Scale | CFG缩放",
383
+ minimum=1.0,
384
+ maximum=20.0,
385
+ step=0.1,
386
+ value=5.5,
387
+ info="Guidance strength. Higher values adhere more to prompt. | 引导强度,更高的值更贴近提示词。",
388
+ )
389
+ steps = gr.Slider(
390
+ label="Sampling Steps | 采样步数",
391
+ minimum=1,
392
+ maximum=50,
393
+ step=1,
394
+ value=30,
395
+ info="Number of steps. More steps can improve quality but take longer. | 步数,更多步数可提高质量但耗时更长。",
396
+ )
397
 
398
  with gr.Row():
399
+ width = gr.Slider(
400
+ label="Width | 宽度",
401
+ minimum=MIN_IMAGE_SIZE,
402
+ maximum=MAX_IMAGE_SIZE,
403
+ step=32,
404
+ value=1024,
405
+ )
406
+ height = gr.Slider(
407
+ label="Height | 高度",
408
+ minimum=MIN_IMAGE_SIZE,
409
+ maximum=MAX_IMAGE_SIZE,
410
+ step=32,
411
+ value=1024,
412
+ )
413
 
414
  with gr.Row():
415
+ seed = gr.Slider(
416
+ label="Seed | 种子",
417
+ minimum=0,
418
+ maximum=MAX_SEED,
419
+ step=1,
420
+ value=random.randint(0, MAX_SEED),
421
+ )
422
+ randomize_seed = gr.Checkbox(
423
+ label="Randomize Seed | 随机种子",
424
+ value=True,
425
+ info="Use a new random seed for each generation if checked. | 勾选后每次生成使用新的随机种子。",
426
+ )
427
+
428
+ run_button = gr.Button(
429
+ "🚀 Generate Image | 生成图像", variant="primary", scale=0
430
+ )
431
 
432
  with gr.Group():
433
+ gr.Markdown("### ✨ Example Prompts | 示例提示词")
434
+ for i, title in enumerate(example_titles):
435
+ btn = gr.Button(title)
436
+ btn.click(lambda t=title: full_prompts[t], outputs=[prompt])
437
 
438
+ with gr.Column(scale=3): # Output Panel
 
439
  gr.Markdown("## 🖼️ Generated Image | 生成图像")
440
  result_image = gr.Image(
441
+ label="Output Image | 输出图像",
442
+ show_label=False,
443
+ type="filepath",
444
+ height=600, # Max display height
445
+ show_download_button=True,
446
+ interactive=False,
447
+ elem_id="result_image_display", # for potential CSS targeting if needed
448
+ )
449
+ generated_seed_info = gr.Textbox(
450
+ label="Seed Used | 使用的种子",
451
+ interactive=False,
452
+ placeholder="The seed for the generated image will appear here. | 生成图像所使用的种子值将显示在此处。",
453
  )
 
454
 
455
  # Event Handlers
456
  inputs_list = [prompt, seed, randomize_seed, width, height, cfg, steps, model_name]
457
  outputs_list = [result_image, generated_seed_info]
458
 
459
+ run_button.click(
460
+ fn=infer, inputs=inputs_list, outputs=outputs_list, api_name="generate_image"
461
+ )
462
+ prompt.submit(
463
+ fn=infer,
464
+ inputs=inputs_list,
465
+ outputs=outputs_list,
466
+ api_name="generate_image_submit",
467
+ )
468
 
469
 
470
  if __name__ == "__main__":
471
  if DEBUG_MODE:
472
  print("DEBUG_MODE is enabled.")
473
  if not os.environ.get("API_TOKEN"):
474
+ print(
475
+ "**************************************************************************************"
476
+ )
477
  print("WARNING: API_TOKEN environment variable is not set locally.")
478
+ print(
479
+ "The application will run, but image generation will fail until API_TOKEN is provided."
480
+ )
481
  print("You can set it by running: export API_TOKEN='your_actual_token_here'")
482
+ print(
483
+ "Or if using a .env file, ensure it's loaded or API_TOKEN is set in your run config."
484
+ )
485
+ print(
486
+ "**************************************************************************************"
487
+ )
488
+
489
+ demo.launch(debug=DEBUG_MODE, show_error=True)