import streamlit as st
import torch
from PIL import Image
import time
import base64
import io
from model_loader import get_model_loader
st.set_page_config(
page_title="灵感画廊 · Atelier of Light and Shadow",
page_icon="🎨",
layout="wide",
initial_sidebar_state="expanded"
)
st.markdown("""
<style>
/* 主色调:宣纸米白与深灰 */
.main { background-color: #faf8f5; }
.stApp { background-color: #faf8f5; }
/* 标题字体:衬线体,文艺感 */
h1, h2, h3 { font-family: 'Noto Serif SC', serif; color: #2c3e50; font-weight: 400; }
/* 输入框样式 */
.stTextArea textarea { font-family: 'Noto Serif SC', serif; font-size: 16px; border: 1px solid #ddd; border-radius: 8px; background-color: #fffefc; }
/* 按钮样式 */
.stButton button { font-family: 'Noto Serif SC', serif; background-color: #8b7355; color: white; border: none; padding: 12px 28px; border-radius: 25px; font-size: 18px; transition: all 0.3s; width: 100%; }
.stButton button:hover { background-color: #6f5a41; transform: translateY(-2px); box-shadow: 0 5px 15px rgba(139, 115, 85, 0.3); }
/* 侧边栏 */
.css-1d391kg { background-color: #f5f1eb; }
</style>
""", unsafe_allow_html=True)
st.markdown('<link href="https://fonts.googleapis.com/css2?family=Noto+Serif+SC:wght@400;700&display=swap" rel="stylesheet">', unsafe_allow_html=True)
with st.sidebar:
st.markdown("## 🖼️ 画布规制")
preset = st.selectbox(
"意境预设",
["无", "影院余晖 (Cinematic Sunset)", "浮世幻象 (Ukiyo Fantasy)", "纪实瞬间 (Documentary Moment)", "水墨诗意 (Ink Wash)", "赛博霓虹 (Cyber Neon)"],
help="选择一种基础美学风格,它将融入你的梦境描述中。"
)
ratio = st.selectbox(
"画幅比例",
["方幅 (1:1)", "横卷 (16:9)", "立轴 (9:16)", "宽幅 (4:3)", "长幅 (3:4)"],
index=0
)
ratio_map = {
"方幅 (1:1)": (1024, 1024),
"横卷 (16:9)": (1152, 648),
"立轴 (9:16)": (648, 1152),
"宽幅 (4:3)": (1024, 768),
"长幅 (3:4)": (768, 1024),
}
width, height = ratio_map[ratio]
guidance_scale = st.slider(
"灵感契合度",
min_value=5.0,
max_value=15.0,
value=7.5,
step=0.5,
help="数值越高,生成画作越遵循你的描述,但可能降低创造性;数值越低则反之。"
)
num_inference_steps = st.slider(
"凝光步数",
min_value=20,
max_value=50,
value=30,
step=5,
help="步数越多,细节越丰富,但生成时间越长。"
)
seed = st.number_input(
"随机种子 (留空则随机)",
min_value=0,
max_value=2**32 - 1,
value=None,
placeholder="输入一个数字以复现相同画作",
help="相同的种子和输入将产生相同的输出。"
)
st.markdown("---")
st.markdown("### 🕯️ 技术注记")
st.caption(f"当前画幅:{width} x {height}")
st.caption("内核:Stable Diffusion XL 1.0")
st.caption("采样:DPM++ 2M Karras")
st.markdown("# 🎨 灵感画廊 · Atelier of Light and Shadow")
st.markdown("> **见微知著,凝光成影。将梦境的碎片,凝结为永恒的视觉诗篇。**")
if 'generated_images' not in st.session_state:
st.session_state.generated_images = []
col_input, col_gallery = st.columns([1, 1])
with col_input:
st.markdown("### 📜 捕捉梦境")
prompt = st.text_area(
"**梦境描述**",
height=150,
placeholder="在此轻声描述你心中的画面...\n例如:『晨雾笼罩的竹林深处,一位身着素衣的琴师抚琴,几缕阳光穿透竹叶,尘埃在光柱中舞动。』",
help="用描述性、感受性的语言勾勒你想要的画面。"
)
negative_prompt = st.text_area(
"**尘杂规避**",
height=100,
placeholder="写下你希望画面避免的元素...\n例如:『模糊,扭曲,畸形的手,多余的手指,丑陋,画质差,水印,文字。』",
help="列出不希望出现在画作中的元素,以获得更纯净的结果。"
)
preset_map = {
"无": "",
"影院余晖 (Cinematic Sunset)": "cinematic lighting, dramatic sunset, golden hour, volumetric rays, film grain, anamorphic lens flare, ",
"浮世幻象 (Ukiyo Fantasy)": "ukiyo-e style, woodblock print, flat colors, elegant lines, traditional japanese art, ",
"纪实瞬间 (Documentary Moment)": "documentary photography, 35mm film, grainy, candid, natural lighting, street photography, authentic, ",
"水墨诗意 (Ink Wash)": "chinese ink painting, watercolor wash, brush strokes, minimalist, monochromatic, serene, ",
"赛博霓虹 (Cyber Neon)": "cyberpunk, neon lights, rainy night, tokyo street, futuristic, synthwave, vibrant colors, "
}
enhanced_prompt = preset_map[preset] + prompt if preset != "无" else prompt
generate_button = st.button("🚀 **挥笔成画**", use_container_width=True, type="primary")
with col_gallery:
st.markdown("### 🖼️ 光影浮现")
image_placeholder = st.empty()
status_placeholder = st.empty()
if st.session_state.generated_images:
st.markdown("#### 📜 创作履迹")
cols_history = st.columns(min(3, len(st.session_state.generated_images)))
for idx, (img, desc) in enumerate(st.session_state.generated_images[-3:]):
with cols_history[idx]:
st.image(img, width=150, caption=desc[:30] + "..." if len(desc) > 30 else desc)
if generate_button and prompt:
with status_placeholder:
with st.spinner("🕯️ 光影正在凝结,请静候片刻..."):
try:
@st.cache_resource
def load_model():
loader = get_model_loader()
return loader.load_pipeline()
pipe = load_model()
generator = None
if seed is not None:
generator = torch.Generator(device="cuda" if torch.cuda.is_available() else "cpu").manual_seed(int(seed))
start_time = time.time()
image = pipe(
prompt=enhanced_prompt,
negative_prompt=negative_prompt,
width=width,
height=height,
guidance_scale=guidance_scale,
num_inference_steps=num_inference_steps,
generator=generator
).images[0]
gen_time = time.time() - start_time
image_placeholder.image(image, use_column_width=True, caption=f"『{prompt[:50]}...』")
status_placeholder.success(f"✨ 画作凝结完成!耗时 {gen_time:.1f} 秒")
st.session_state.generated_images.append((image, prompt))
buf = io.BytesIO()
image.save(buf, format="PNG")
byte_im = buf.getvalue()
st.download_button(
label="💾 珍藏此作",
data=byte_im,
file_name=f"inspiration_{int(time.time())}.png",
mime="image/png",
use_container_width=True
)
except Exception as e:
status_placeholder.error(f"❌ 光影消散,创作中断:{str(e)}")
elif generate_button and not prompt:
st.warning("请先输入梦境描述。")
st.markdown("---")
st.markdown("""
<div style="font-family: 'Noto Serif SC', serif;">
<p>灵感之外,皆为光影。</p>
<p><em>由匠心凝炼而成</em></p>
</div>
""", unsafe_allow_html=True)