Files
image-gen/app.py
2026-04-15 18:48:33 +08:00

288 lines
10 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import os
import base64
import io
from dotenv import load_dotenv
import requests
import gradio as gr
from PIL import Image
load_dotenv()
API_URL = os.getenv("IMAGE_API_URL", "https://llm.dev.maimaiag.com/v1/images/generations")
API_KEY = os.getenv("IMAGE_API_KEY", "")
def _call_remote_api(prompt: str, size: str, seed: int, num_inference_steps: int) -> Image.Image:
headers = {
"Content-Type": "application/json",
}
if API_KEY:
headers["Authorization"] = f"Bearer {API_KEY}"
payload = {
"model": "openai/z-image-turbo",
"prompt": prompt,
"size": size,
"seed": seed,
"num_inference_steps": num_inference_steps,
}
response = requests.post(API_URL, headers=headers, json=payload, timeout=300)
response.raise_for_status()
data = response.json()
b64_image = data["data"][0]["b64_json"]
image_bytes = base64.b64decode(b64_image)
return Image.open(io.BytesIO(image_bytes))
def generate_image(prompt, height, width, num_inference_steps, seed, randomize_seed, progress=gr.Progress(track_tqdm=True)):
"""Generate an image from the given prompt via remote API."""
if randomize_seed:
import random
seed = random.randint(0, 2**32 - 1)
size = f"{int(width)}x{int(height)}"
image = _call_remote_api(
prompt=prompt,
size=size,
seed=int(seed),
num_inference_steps=int(num_inference_steps),
)
return image, seed
# 示例提示词
examples = [
["一张黑白照片约1950年代捕捉了一场欢乐的跨代家庭感恩节或节日晚餐。中心人物是一位身穿白衬衫、系深色领带的微笑男子他正在餐桌主位上兴致勃勃地切割一只大烤火鸡。他被一大群各年龄段的家庭成员包围所有人都迫不及待地伸出盘子来接食物。许多不同年龄的孩子围在周围眼睛睁得大大的充满期待有的站着有的坐着。还有几位女性和青少年有的在帮忙上菜有的抱着婴儿。整个场景充满了自然的互动、笑声和热闹温馨的氛围。餐桌上摆满了传统的节日菜肴。背景是一面简单的、可能贴着壁纸的墙壁反映了真实家庭住宅的质朴感。"],
["一幅全景场景,背景从石器时代过渡到未来(洞穴→金字塔→城堡→工厂→摩天大楼→漂浮城市),主体是前景中同一张面孔/同一个人,从左到右佩戴着符合时代特征的头盔:骨/兽皮头饰、青铜古代头盔、中世纪板甲头盔、一战钢盔、现代太空头盔,以及未来能量/全息头盔。"],
["一张现代会议室里的商务团队照片。在会议桌的首席位置,一位自信的老板站着,充满热情地介绍一个雄心勃勃的新产品创意。围坐在桌旁的雇员们反应各异,有的好奇、有的挑眉、有的若有所思,有的在记笔记,有的在提问。他们身后的大窗外可以看到摩天大楼和城市灯火。氛围专业但充满紧张和引人入胜的气息。"],
["一幅文艺复兴风格的绘画,描绘了一座现代城市景观。"],
["节日期间繁忙的城市街道,彩旗飘扬,人群熙攘,街头艺人在表演。"],
["科幻电影的电影海报,宇航员站在火星上凝望地球,戏剧性的灯光,黑暗的氛围,顶部有大号金属质感字体标题\"THE VOYAGE\""],
["兴奋的年轻内容创作者指着漂浮的机器人全息图充满活力的紫色和蓝色霓虹背景高对比度YouTube缩略图风格粗体黄色文字\"AI UPDATE\""],
["现代工作空间,木质桌面上放着笔记本电脑和咖啡,晨光透过窗户洒入,温馨的氛围,照片级真实感,高质量,便利贴上写着\"Productivity\""],
]
# 自定义主题,深色科技感 (Gradio 6)
custom_theme = gr.themes.Soft(
primary_hue="cyan",
secondary_hue="violet",
neutral_hue="zinc",
font=gr.themes.GoogleFont("Inter"),
text_size="lg",
spacing_size="md",
radius_size="lg"
).set(
button_primary_background_fill="*primary_500",
button_primary_background_fill_hover="*primary_400",
button_primary_text_color="*neutral_950",
block_title_text_weight="600",
block_background_fill="*neutral_900",
body_background_fill="*neutral_950",
body_text_color="*neutral_200",
block_label_text_color="*neutral_300",
input_background_fill="*neutral_800",
border_color_primary="*neutral_700",
)
# 构建 Gradio 界面
with gr.Blocks(fill_height=True) as demo:
# 页眉
gr.Markdown(
"""
# Z-Image-Turbo
极速 AI 图像生成 • 仅需 8 步即可生成精美图像
""",
elem_classes="header-text"
)
with gr.Row(equal_height=False):
# 左栏 - 输入控制
with gr.Column(scale=1, min_width=320):
prompt = gr.Textbox(
label="提示词",
placeholder="描述你想要生成的图像...",
lines=5,
max_lines=10,
autofocus=True,
)
with gr.Accordion("高级设置", open=False):
with gr.Row():
height = gr.Slider(
minimum=512,
maximum=2048,
value=1024,
step=64,
label="高度",
info="图像高度(像素)"
)
width = gr.Slider(
minimum=512,
maximum=2048,
value=1024,
step=64,
label="宽度",
info="图像宽度(像素)"
)
num_inference_steps = gr.Slider(
minimum=1,
maximum=20,
value=9,
step=1,
label="推理步数",
info="9 步 = 8 次 DiT 前向传播(推荐)"
)
with gr.Row():
randomize_seed = gr.Checkbox(
label="随机种子",
value=True,
)
seed = gr.Number(
label="种子",
value=42,
precision=0,
visible=False,
)
def toggle_seed(randomize):
return gr.Number(visible=not randomize)
randomize_seed.change(
toggle_seed,
inputs=[randomize_seed],
outputs=[seed]
)
generate_btn = gr.Button(
"生成图像",
variant="primary",
size="lg",
scale=1
)
# 示例提示词
gr.Examples(
examples=examples,
inputs=[prompt],
label="试试这些提示词",
examples_per_page=5,
)
# 右栏 - 输出
with gr.Column(scale=1, min_width=320):
output_image = gr.Image(
label="生成的图像",
type="pil",
format="png",
show_label=False,
height=600,
buttons=["download", "share"],
)
used_seed = gr.Number(
label="使用的种子",
interactive=False,
container=True,
)
# 连接生成按钮
generate_btn.click(
fn=generate_image,
inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
outputs=[output_image, used_seed],
)
# 也支持在提示词框中按回车键生成
prompt.submit(
fn=generate_image,
inputs=[prompt, height, width, num_inference_steps, seed, randomize_seed],
outputs=[output_image, used_seed],
)
if __name__ == "__main__":
demo.launch(
server_name="0.0.0.0",
server_port=7860,
theme=custom_theme,
css="""
.header-text h1 {
font-size: 2.5rem !important;
font-weight: 700 !important;
margin-bottom: 0.5rem !important;
background: linear-gradient(135deg, #22d3ee 0%, #a78bfa 100%);
-webkit-background-clip: text;
-webkit-text-fill-color: transparent;
background-clip: text;
letter-spacing: -0.02em;
}
.header-text p {
font-size: 1.1rem !important;
color: #94a3b8 !important;
margin-top: 0 !important;
}
.footer-text {
padding: 1rem 0;
}
.footer-text a {
color: #22d3ee !important;
text-decoration: none !important;
font-weight: 500;
}
.footer-text a:hover {
text-decoration: underline !important;
}
/* Mobile optimizations */
@media (max-width: 768px) {
.header-text h1 {
font-size: 1.8rem !important;
}
.header-text p {
font-size: 1rem !important;
}
}
/* Smooth transitions */
button, .gr-button {
transition: all 0.2s ease !important;
}
button:hover, .gr-button:hover {
transform: translateY(-1px);
box-shadow: 0 0 20px rgba(34, 211, 238, 0.35) !important;
}
/* Better spacing */
.gradio-container {
max-width: 1400px !important;
margin: 0 auto !important;
}
/* Glassmorphism panels */
.gradio-container .gr-block,
.gradio-container .gr-form,
.gradio-container .gr-box {
backdrop-filter: blur(8px) !important;
border: 1px solid rgba(148, 163, 184, 0.12) !important;
}
/* Input focus glow */
input:focus, textarea:focus {
box-shadow: 0 0 0 2px rgba(34, 211, 238, 0.25) !important;
}
""",
footer_links=[
"api",
"gradio"
],
mcp_server=True
)