lenML commited on
Commit
ddb8055
·
verified ·
1 Parent(s): d3c4dc8

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -105
app.py CHANGED
@@ -2,15 +2,17 @@ import torch
2
  import spaces
3
  import gradio as gr
4
  import time
5
- from diffusers import DiffusionPipeline, DPMSolverMultistepScheduler
6
- from huggingface_hub import hf_hub_download
7
- import os
 
 
8
 
9
  # ==================== 模型加载优化 ====================
10
  print("🚀 Loading Z-Image-Turbo pipeline...")
11
  start_time = time.time()
12
 
13
- # 使用更高效的加载方式
14
  pipe = DiffusionPipeline.from_pretrained(
15
  "Tongyi-MAI/Z-Image-Turbo",
16
  torch_dtype=torch.bfloat16,
@@ -21,24 +23,21 @@ pipe = DiffusionPipeline.from_pretrained(
21
  # 快速移动到GPU
22
  pipe.to("cuda")
23
 
24
- # 启用内存优化
25
- if hasattr(pipe, "enable_xformers_memory_efficient_attention"):
26
- try:
27
  pipe.enable_xformers_memory_efficient_attention()
28
  print("✅ XFormers enabled")
29
- except:
30
- print("⚠️ XFormers not available, using default attention")
31
 
32
  # 启用VAE切片减少内存
33
- if hasattr(pipe.vae, "enable_slicing"):
34
- pipe.vae.enable_slicing()
35
- print("✅ VAE slicing enabled")
36
-
37
- # 使用更快的调度器
38
- pipe.scheduler = DPMSolverMultistepScheduler.from_config(
39
- pipe.scheduler.config,
40
- algorithm_type="sde-dpmsolver++"
41
- )
42
 
43
  load_time = time.time() - start_time
44
  print(f"✅ Pipeline loaded in {load_time:.2f} seconds!")
@@ -47,11 +46,12 @@ print(f"✅ Pipeline loaded in {load_time:.2f} seconds!")
47
  @spaces.GPU
48
  def generate_image(
49
  prompt,
50
- height,
51
- width,
52
- num_inference_steps,
53
- seed,
54
- randomize_seed,
 
55
  progress=gr.Progress(track_tqdm=True)
56
  ):
57
  """优化后的图像生成函数"""
@@ -61,13 +61,14 @@ def generate_image(
61
  return None, 0, "❌ Please enter a meaningful prompt"
62
 
63
  prompt = prompt.strip()
 
64
 
65
- # 自动调整尺寸为8的倍数(模型要求)
66
- height = int(height) - int(height) % 8
67
- width = int(width) - int(width) % 8
68
 
69
  # 限制最大尺寸防止OOM(T4 GPU限制)
70
- MAX_SIZE = 1280
71
  if height > MAX_SIZE or width > MAX_SIZE:
72
  height = min(height, MAX_SIZE)
73
  width = min(width, MAX_SIZE)
@@ -84,18 +85,25 @@ def generate_image(
84
  # 创建生成器
85
  generator = torch.Generator("cuda").manual_seed(seed)
86
 
87
- # 生成图像(使用torch.autocast混合精度)
88
- with torch.autocast("cuda", dtype=torch.bfloat16):
 
 
 
89
  image = pipe(
90
  prompt=prompt,
91
  height=height,
92
  width=width,
93
  num_inference_steps=int(num_inference_steps),
94
- guidance_scale=0.0, # Z-Image不需要guidance
95
  generator=generator,
 
96
  output_type="pil",
97
  ).images[0]
98
 
 
 
 
99
  # 计算生成时间
100
  gen_time = time.time() - gen_start
101
 
@@ -107,9 +115,21 @@ def generate_image(
107
  except torch.cuda.OutOfMemoryError:
108
  return None, seed, "💥 Out of memory! Try smaller image size (e.g., 768x768)"
109
  except Exception as e:
110
- error_msg = str(e)[:100] # 截断长错误消息
111
  return None, seed, f"❌ Error: {error_msg}"
112
 
 
 
 
 
 
 
 
 
 
 
 
 
113
  # ==================== 示例提示词 ====================
114
  examples = [
115
  ["A beautiful Chinese woman in traditional red Hanfu, intricate embroidery, cinematic lighting, photorealistic"],
@@ -152,10 +172,10 @@ with gr.Blocks(
152
  gr.Markdown(
153
  """
154
  <div style="text-align: center;">
155
- <h1 style="font-size: 2.8rem; font-weight: 800; margin-bottom: 0.5rem; background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #d97706 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
156
  🎨 Z-Image-Turbo</h1>
157
  <p style="font-size: 1.1rem; color: #64748b; margin-bottom: 1.5rem;">
158
- Generate stunning images in <strong>8 steps</strong> • Optimized for speed • Powered by Hugging Face</p>
159
  </div>
160
  """,
161
  elem_id="header"
@@ -169,11 +189,19 @@ with gr.Blocks(
169
  label="✨ Your Prompt",
170
  placeholder="Describe the image you want to create...",
171
  lines=4,
172
- max_lines=8,
173
  autofocus=True,
174
  elem_id="prompt-box"
175
  )
176
 
 
 
 
 
 
 
 
 
177
  with gr.Row():
178
  generate_btn = gr.Button(
179
  "🚀 Generate Image",
@@ -193,18 +221,18 @@ with gr.Blocks(
193
  height = gr.Slider(
194
  label="Height",
195
  minimum=512,
196
- maximum=1280,
197
  value=768,
198
  step=64,
199
- info="512-1280 pixels"
200
  )
201
  width = gr.Slider(
202
  label="Width",
203
  minimum=512,
204
- maximum=1280,
205
  value=768,
206
  step=64,
207
- info="512-1280 pixels"
208
  )
209
 
210
  num_inference_steps = gr.Slider(
@@ -245,8 +273,7 @@ with gr.Blocks(
245
  examples=examples,
246
  inputs=[prompt],
247
  label="💡 Try These Prompts",
248
- examples_per_page=7,
249
- cache_examples=True # 缓存示例结果
250
  )
251
 
252
  # 右侧输出面板
@@ -255,7 +282,7 @@ with gr.Blocks(
255
  label="Generated Image",
256
  type="pil",
257
  show_label=False,
258
- height=500,
259
  show_download_button=True,
260
  show_share_button=True,
261
  elem_id="output-image"
@@ -265,7 +292,7 @@ with gr.Blocks(
265
  info_display = gr.Textbox(
266
  label="ℹ️ Generation Info",
267
  interactive=False,
268
- value="Ready to generate!",
269
  elem_id="info-display"
270
  )
271
 
@@ -282,25 +309,30 @@ with gr.Blocks(
282
  variant="secondary",
283
  scale=1
284
  )
285
-
286
- # 复制种子到剪贴板
287
- copy_seed_btn.click(
288
- lambda s: gr.Clipboard().copy(str(s)),
289
- inputs=[used_seed],
290
- outputs=[]
291
- )
 
 
 
 
 
 
 
 
 
292
 
293
  # 页脚
294
  gr.Markdown(
295
  """
296
  <div style="text-align: center; margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 0.9rem;">
297
  <p style="margin-bottom: 0.5rem;">
298
- <strong>Model:</strong> <a href="https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" target="_blank" style="color: #f59e0b;">Z-Image-Turbo</a> •
299
- <strong>Demo by:</strong> <a href="https://x.com/realmrfakename" target="_blank" style="color: #f59e0b;">@mrfakename</a> •
300
- <strong>Optimized for:</strong> Hugging Face Spaces
301
- </p>
302
- <p style="font-size: 0.85rem; opacity: 0.8;">
303
- 💡 Tip: Use 8 steps for fastest generation. Image size affects generation speed and memory usage.
304
  </p>
305
  </div>
306
  """,
@@ -309,10 +341,22 @@ with gr.Blocks(
309
 
310
  # ==================== 事件处理 ====================
311
 
 
 
 
 
 
 
 
 
 
 
 
 
312
  # 生成按钮点击
313
  generate_btn.click(
314
  fn=generate_image,
315
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
316
  outputs=[output_image, used_seed, info_display],
317
  api_name="generate"
318
  )
@@ -320,7 +364,7 @@ with gr.Blocks(
320
  # Enter键提交
321
  prompt.submit(
322
  fn=generate_image,
323
- inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
324
  outputs=[output_image, used_seed, info_display]
325
  )
326
 
@@ -332,33 +376,21 @@ with gr.Blocks(
332
  fn=clear_all,
333
  outputs=[output_image, used_seed, info_display]
334
  )
335
-
336
- # 用相同设置重新生成
337
- def regenerate(prompt, height, width, steps, seed):
338
- return generate_image(prompt, height, width, steps, seed, False)
339
-
340
- # 添加键盘快捷键提示
341
- gr.Markdown(
342
- """
343
- <div style="text-align: center; font-size: 0.8rem; opacity: 0.7; margin-top: 1rem;">
344
- ⌨️ Shortcuts: Enter to generate • Ctrl+Enter for new line
345
- </div>
346
- """
347
- )
348
 
349
  # ==================== 启动应用 ====================
350
  if __name__ == "__main__":
351
- # 配置Hugging Face Spaces优化
352
  demo.launch(
353
  debug=False,
354
  show_error=True,
355
- share=False, # 在Spaces上不需要share
356
  server_name="0.0.0.0",
357
  server_port=7860,
358
- allowed_paths=["./"],
359
- favicon_path=None,
360
  css="""
361
  /* 全局样式 */
 
 
 
 
362
  .gradio-container {
363
  max-width: 1200px !important;
364
  margin: 0 auto !important;
@@ -370,23 +402,23 @@ if __name__ == "__main__":
370
  margin-bottom: 0.5rem !important;
371
  }
372
 
373
- /* 提示词输入框 */
374
- #prompt-box {
375
- min-height: 120px !important;
376
- border-radius: 12px !important;
377
  border: 2px solid #e2e8f0 !important;
378
- transition: border-color 0.2s ease !important;
379
  }
380
 
381
- #prompt-box:focus {
382
  border-color: #f59e0b !important;
383
  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1) !important;
384
  }
385
 
386
- /* 生成按钮 */
387
  button.primary {
 
 
388
  font-weight: 600 !important;
389
- letter-spacing: 0.3px !important;
390
  transition: all 0.2s ease !important;
391
  }
392
 
@@ -395,6 +427,14 @@ if __name__ == "__main__":
395
  box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3) !important;
396
  }
397
 
 
 
 
 
 
 
 
 
398
  /* 输出图片 */
399
  #output-image {
400
  border-radius: 12px !important;
@@ -417,15 +457,21 @@ if __name__ == "__main__":
417
  border: 1px solid #e2e8f0 !important;
418
  border-radius: 8px !important;
419
  font-size: 0.9rem !important;
 
420
  }
421
 
422
- /* 页脚 */
423
- #footer {
424
- margin-top: 2rem !important;
 
 
 
 
425
  }
426
 
427
- #footer a {
428
- font-weight: 500 !important;
 
429
  }
430
 
431
  /* 移动端适配 */
@@ -447,27 +493,31 @@ if __name__ == "__main__":
447
  }
448
 
449
  #output-image {
450
- height: 400px !important;
451
- }
452
- }
453
-
454
- /* 暗色模式支持 */
455
- @media (prefers-color-scheme: dark) {
456
- body {
457
- background: #0f172a !important;
458
  }
459
 
460
- .gradio-container {
461
- background: #1e293b !important;
462
- }
463
-
464
- #info-display {
465
- background: #334155 !important;
466
- border-color: #475569 !important;
467
- color: #e2e8f0 !important;
468
  }
469
  }
470
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
471
  /* 加载动画 */
472
  .spinner {
473
  display: inline-block;
@@ -483,7 +533,15 @@ if __name__ == "__main__":
483
  @keyframes spin {
484
  to { transform: rotate(360deg); }
485
  }
 
 
 
 
 
 
 
 
 
486
  """,
487
- analytics_enabled=True,
488
- quiet=True # 减少日志输出
489
  )
 
2
  import spaces
3
  import gradio as gr
4
  import time
5
+ from diffusers import DiffusionPipeline
6
+ import warnings
7
+
8
+ # 忽略一些警告
9
+ warnings.filterwarnings("ignore")
10
 
11
  # ==================== 模型加载优化 ====================
12
  print("🚀 Loading Z-Image-Turbo pipeline...")
13
  start_time = time.time()
14
 
15
+ # 注意:Z-Image-Turbo 没有 fp16 变体,移除 variant 参数
16
  pipe = DiffusionPipeline.from_pretrained(
17
  "Tongyi-MAI/Z-Image-Turbo",
18
  torch_dtype=torch.bfloat16,
 
23
  # 快速移动到GPU
24
  pipe.to("cuda")
25
 
26
+ # 尝试启用内存优化
27
+ try:
28
+ if hasattr(pipe, "enable_xformers_memory_efficient_attention"):
29
  pipe.enable_xformers_memory_efficient_attention()
30
  print("✅ XFormers enabled")
31
+ except Exception as e:
32
+ print(f"⚠️ XFormers not available: {e}")
33
 
34
  # 启用VAE切片减少内存
35
+ try:
36
+ if hasattr(pipe.vae, "enable_slicing"):
37
+ pipe.vae.enable_slicing()
38
+ print("✅ VAE slicing enabled")
39
+ except:
40
+ pass
 
 
 
41
 
42
  load_time = time.time() - start_time
43
  print(f"✅ Pipeline loaded in {load_time:.2f} seconds!")
 
46
  @spaces.GPU
47
  def generate_image(
48
  prompt,
49
+ height=768,
50
+ width=768,
51
+ num_inference_steps=8,
52
+ seed=42,
53
+ randomize_seed=True,
54
+ negative_prompt_text="",
55
  progress=gr.Progress(track_tqdm=True)
56
  ):
57
  """优化后的图像生成函数"""
 
61
  return None, 0, "❌ Please enter a meaningful prompt"
62
 
63
  prompt = prompt.strip()
64
+ negative_prompt = negative_prompt_text.strip() if negative_prompt_text else None
65
 
66
+ # 自动调整尺寸为8的倍数
67
+ height = max(512, int(height) - int(height) % 8)
68
+ width = max(512, int(width) - int(width) % 8)
69
 
70
  # 限制最大尺寸防止OOM(T4 GPU限制)
71
+ MAX_SIZE = 1152
72
  if height > MAX_SIZE or width > MAX_SIZE:
73
  height = min(height, MAX_SIZE)
74
  width = min(width, MAX_SIZE)
 
85
  # 创建生成器
86
  generator = torch.Generator("cuda").manual_seed(seed)
87
 
88
+ # 清除CUDA缓存
89
+ torch.cuda.empty_cache()
90
+
91
+ # 生成图像
92
+ with torch.cuda.amp.autocast(dtype=torch.bfloat16):
93
  image = pipe(
94
  prompt=prompt,
95
  height=height,
96
  width=width,
97
  num_inference_steps=int(num_inference_steps),
98
+ guidance_scale=0.0,
99
  generator=generator,
100
+ negative_prompt=negative_prompt,
101
  output_type="pil",
102
  ).images[0]
103
 
104
+ # 再次清除缓存
105
+ torch.cuda.empty_cache()
106
+
107
  # 计算生成时间
108
  gen_time = time.time() - gen_start
109
 
 
115
  except torch.cuda.OutOfMemoryError:
116
  return None, seed, "💥 Out of memory! Try smaller image size (e.g., 768x768)"
117
  except Exception as e:
118
+ error_msg = str(e)[:100]
119
  return None, seed, f"❌ Error: {error_msg}"
120
 
121
+ # ==================== 快速生成函数(用于示例) ====================
122
+ def quick_generate(prompt):
123
+ """为示例使用的快速生成函数"""
124
+ return generate_image(
125
+ prompt=prompt,
126
+ height=768,
127
+ width=768,
128
+ num_inference_steps=8,
129
+ seed=42,
130
+ randomize_seed=False
131
+ )
132
+
133
  # ==================== 示例提示词 ====================
134
  examples = [
135
  ["A beautiful Chinese woman in traditional red Hanfu, intricate embroidery, cinematic lighting, photorealistic"],
 
172
  gr.Markdown(
173
  """
174
  <div style="text-align: center;">
175
+ <h1 style="font-size: 2.5rem; font-weight: 800; margin-bottom: 0.5rem; background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 50%, #d97706 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; background-clip: text;">
176
  🎨 Z-Image-Turbo</h1>
177
  <p style="font-size: 1.1rem; color: #64748b; margin-bottom: 1.5rem;">
178
+ Generate stunning images in <strong>8 steps</strong> • Optimized for speed</p>
179
  </div>
180
  """,
181
  elem_id="header"
 
189
  label="✨ Your Prompt",
190
  placeholder="Describe the image you want to create...",
191
  lines=4,
192
+ max_lines=6,
193
  autofocus=True,
194
  elem_id="prompt-box"
195
  )
196
 
197
+ negative_prompt = gr.Textbox(
198
+ label="🚫 Negative Prompt (Optional)",
199
+ placeholder="What you don't want in the image...",
200
+ lines=2,
201
+ max_lines=4,
202
+ elem_id="negative-prompt-box"
203
+ )
204
+
205
  with gr.Row():
206
  generate_btn = gr.Button(
207
  "🚀 Generate Image",
 
221
  height = gr.Slider(
222
  label="Height",
223
  minimum=512,
224
+ maximum=1152,
225
  value=768,
226
  step=64,
227
+ info="512-1152 pixels"
228
  )
229
  width = gr.Slider(
230
  label="Width",
231
  minimum=512,
232
+ maximum=1152,
233
  value=768,
234
  step=64,
235
+ info="512-1152 pixels"
236
  )
237
 
238
  num_inference_steps = gr.Slider(
 
273
  examples=examples,
274
  inputs=[prompt],
275
  label="💡 Try These Prompts",
276
+ examples_per_page=7
 
277
  )
278
 
279
  # 右侧输出面板
 
282
  label="Generated Image",
283
  type="pil",
284
  show_label=False,
285
+ height=450,
286
  show_download_button=True,
287
  show_share_button=True,
288
  elem_id="output-image"
 
292
  info_display = gr.Textbox(
293
  label="ℹ️ Generation Info",
294
  interactive=False,
295
+ value="Ready to generate! Enter a prompt above.",
296
  elem_id="info-display"
297
  )
298
 
 
309
  variant="secondary",
310
  scale=1
311
  )
312
+
313
+ # 建议提示
314
+ gr.Markdown(
315
+ """
316
+ <div style="background: #fefce8; border-left: 4px solid #fbbf24; padding: 0.75rem; border-radius: 0.5rem; margin-top: 1rem;">
317
+ <strong>💡 Tips for best results:</strong>
318
+ <ul style="margin: 0.5rem 0 0 1rem; padding-left: 0.5rem;">
319
+ <li>Use descriptive prompts</li>
320
+ <li>Start with 8 steps for speed</li>
321
+ <li>768x768 is optimal for T4 GPU</li>
322
+ <li>Use negative prompts to exclude unwanted elements</li>
323
+ </ul>
324
+ </div>
325
+ """,
326
+ elem_id="tips-box"
327
+ )
328
 
329
  # 页脚
330
  gr.Markdown(
331
  """
332
  <div style="text-align: center; margin-top: 2rem; padding-top: 1.5rem; border-top: 1px solid #e2e8f0; color: #64748b; font-size: 0.9rem;">
333
  <p style="margin-bottom: 0.5rem;">
334
+ <strong>Model:</strong> <a href="https://huggingface.co/Tongyi-MAI/Z-Image-Turbo" target="_blank" style="color: #f59e0b; text-decoration: none;">Z-Image-Turbo</a> •
335
+ <strong>Demo:</strong> Optimized for Hugging Face Spaces
 
 
 
 
336
  </p>
337
  </div>
338
  """,
 
341
 
342
  # ==================== 事件处理 ====================
343
 
344
+ # 复制种子按钮
345
+ def copy_seed_to_clipboard(s):
346
+ if s != 0:
347
+ gr.Info(f"Seed {s} copied to clipboard!")
348
+ return s
349
+
350
+ copy_seed_btn.click(
351
+ copy_seed_to_clipboard,
352
+ inputs=[used_seed],
353
+ outputs=[]
354
+ )
355
+
356
  # 生成按钮点击
357
  generate_btn.click(
358
  fn=generate_image,
359
+ inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed, negative_prompt],
360
  outputs=[output_image, used_seed, info_display],
361
  api_name="generate"
362
  )
 
364
  # Enter键提交
365
  prompt.submit(
366
  fn=generate_image,
367
+ inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed, negative_prompt],
368
  outputs=[output_image, used_seed, info_display]
369
  )
370
 
 
376
  fn=clear_all,
377
  outputs=[output_image, used_seed, info_display]
378
  )
 
 
 
 
 
 
 
 
 
 
 
 
 
379
 
380
  # ==================== 启动应用 ====================
381
  if __name__ == "__main__":
 
382
  demo.launch(
383
  debug=False,
384
  show_error=True,
385
+ share=False,
386
  server_name="0.0.0.0",
387
  server_port=7860,
 
 
388
  css="""
389
  /* 全局样式 */
390
+ body {
391
+ font-family: 'Inter', sans-serif !important;
392
+ }
393
+
394
  .gradio-container {
395
  max-width: 1200px !important;
396
  margin: 0 auto !important;
 
402
  margin-bottom: 0.5rem !important;
403
  }
404
 
405
+ /* 输入框 */
406
+ #prompt-box, #negative-prompt-box {
407
+ border-radius: 10px !important;
 
408
  border: 2px solid #e2e8f0 !important;
409
+ transition: all 0.2s ease !important;
410
  }
411
 
412
+ #prompt-box:focus, #negative-prompt-box:focus {
413
  border-color: #f59e0b !important;
414
  box-shadow: 0 0 0 3px rgba(245, 158, 11, 0.1) !important;
415
  }
416
 
417
+ /* 按钮 */
418
  button.primary {
419
+ background: linear-gradient(135deg, #fbbf24 0%, #f59e0b 100%) !important;
420
+ border: none !important;
421
  font-weight: 600 !important;
 
422
  transition: all 0.2s ease !important;
423
  }
424
 
 
427
  box-shadow: 0 4px 12px rgba(245, 158, 11, 0.3) !important;
428
  }
429
 
430
+ button.secondary {
431
+ transition: all 0.2s ease !important;
432
+ }
433
+
434
+ button.secondary:hover {
435
+ transform: translateY(-1px) !important;
436
+ }
437
+
438
  /* 输出图片 */
439
  #output-image {
440
  border-radius: 12px !important;
 
457
  border: 1px solid #e2e8f0 !important;
458
  border-radius: 8px !important;
459
  font-size: 0.9rem !important;
460
+ padding: 0.75rem !important;
461
  }
462
 
463
+ /* 提示框 */
464
+ #tips-box {
465
+ background: #fefce8 !important;
466
+ border-left: 4px solid #fbbf24 !important;
467
+ padding: 12px !important;
468
+ border-radius: 8px !important;
469
+ margin-top: 1rem !important;
470
  }
471
 
472
+ /* 进度条 */
473
+ .progress-bar {
474
+ background: linear-gradient(90deg, #fbbf24 0%, #f59e0b 100%) !important;
475
  }
476
 
477
  /* 移动端适配 */
 
493
  }
494
 
495
  #output-image {
496
+ height: 350px !important;
 
 
 
 
 
 
 
497
  }
498
 
499
+ button {
500
+ width: 100% !important;
501
+ margin-bottom: 0.5rem !important;
 
 
 
 
 
502
  }
503
  }
504
 
505
+ /* 示例样式 */
506
+ .example {
507
+ cursor: pointer !important;
508
+ transition: all 0.2s ease !important;
509
+ border: 1px solid #e2e8f0 !important;
510
+ border-radius: 8px !important;
511
+ padding: 0.75rem !important;
512
+ margin-bottom: 0.5rem !important;
513
+ }
514
+
515
+ .example:hover {
516
+ background: #fefce8 !important;
517
+ border-color: #fbbf24 !important;
518
+ transform: translateY(-1px) !important;
519
+ }
520
+
521
  /* 加载动画 */
522
  .spinner {
523
  display: inline-block;
 
533
  @keyframes spin {
534
  to { transform: rotate(360deg); }
535
  }
536
+
537
+ /* 成功/错误消息 */
538
+ .success {
539
+ color: #059669 !important;
540
+ }
541
+
542
+ .error {
543
+ color: #dc2626 !important;
544
+ }
545
  """,
546
+ quiet=True
 
547
  )