masterofaudio2077's picture
Add CFG rescale control (default 0.5)
db103d0
Raw
History Blame Contribute Delete
5.45 kB
import base64
import io
import json
from pathlib import Path
from urllib.parse import quote
from fastapi import FastAPI
from fastapi.responses import FileResponse, StreamingResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel
from pipeline import (
generate, NUM_STEPS, MAX_STEPS, GUIDANCE_SCALE, GUIDANCE_RESCALE,
DEFAULT_SPACING,
)
SPACINGS = ("trailing", "quadratic")
BASE = Path(__file__).parent
SPRITE_DIR = BASE / "sprites"
STATIC_DIR = BASE / "static"
app = FastAPI(title="Senior LDM")
class CachedStaticFiles(StaticFiles):
"""StaticFiles with long-lived cache headers. The sprite animation swaps the
<img> src ~10Γ—/sec; without caching the browser re-downloads every frame
forever. Sprites/gallery are immutable assets, so cache them aggressively."""
async def get_response(self, path, scope):
response = await super().get_response(path, scope)
response.headers["Cache-Control"] = "public, max-age=31536000, immutable"
return response
app.mount("/sprites", CachedStaticFiles(directory=str(SPRITE_DIR)), name="sprites")
app.mount("/static", CachedStaticFiles(directory=str(STATIC_DIR)), name="static")
# ── sprite state machine β†’ frame files ──────────────────────────────────────
SPRITE_MAP = {
"idle": ["Screenshot 2026-05-31 020023.png", "Screenshot 2026-05-31 020056.png"],
"run": ["Screenshot 2026-05-31 020138.png", "Screenshot 2026-05-31 020141.png",
"Screenshot 2026-05-31 020145.png", "Screenshot 2026-05-31 020148.png"],
"turn": ["Screenshot 2026-05-31 020210.png", "Screenshot 2026-05-31 020215.png"],
"done": ["Screenshot 2026-05-31 020023.png"],
}
# ── prompt ideas β€” art / LAION-aesthetics styles, no humans ─────────────────
PROMPT_IDEAS = [
"a serene mountain lake at sunset, oil painting",
"venice at night, shimmering canals, painterly",
"a classical still life of fruit and wine",
"a fantasy castle on a cliff, hd wallpaper",
"a brown mountain under a starry night sky",
"a forest at sunset with dramatic clouds",
"a winter cabin at the water's edge, wildlife art",
"a lighthouse on a rocky coast during a storm",
"a field of lavender under a stormy sky",
"an autumn forest path of golden leaves",
"a watercolor painting of a snowy village",
"a futuristic city skyline glowing at night",
"a tranquil japanese garden with a koi pond",
"an abstract swirling galaxy of vibrant color",
"a cozy cabin interior with warm firelight",
"a snowy forest path in soft morning light",
]
# ── gallery showcase β€” example outputs served from static/gallery ───────────
def _gallery():
gdir = STATIC_DIR / "gallery"
items = []
for f in sorted(gdir.glob("*.png")):
# filename β†’ readable label: drop the source/index prefix, de-underscore.
parts = f.stem.split("_")
label = " ".join(parts[2:] if len(parts) > 2 else parts)
items.append({"url": f"/static/gallery/{quote(f.name)}", "label": label})
return items
GALLERY = _gallery()
class GenRequest(BaseModel):
prompt: str
seed: int = 42
steps: int = NUM_STEPS
guidance: float = GUIDANCE_SCALE
guidance_rescale: float = GUIDANCE_RESCALE
spacing: str = DEFAULT_SPACING
@app.get("/")
def index():
return FileResponse(str(STATIC_DIR / "index.html"))
@app.get("/api/config")
def config():
return {
"sprites": {state: [f"/sprites/{quote(f)}" for f in files]
for state, files in SPRITE_MAP.items()},
"prompts": PROMPT_IDEAS,
"gallery": GALLERY,
"num_steps": NUM_STEPS,
"max_steps": MAX_STEPS,
"guidance": GUIDANCE_SCALE,
"guidance_rescale": GUIDANCE_RESCALE,
"spacing": DEFAULT_SPACING,
"spacings": list(SPACINGS),
}
@app.post("/api/generate")
def api_generate(req: GenRequest):
prompt = req.prompt.strip()
steps = max(1, min(MAX_STEPS, req.steps))
guidance = max(0.0, min(20.0, req.guidance))
g_rescale = max(0.0, min(1.0, req.guidance_rescale))
spacing = req.spacing if req.spacing in SPACINGS else DEFAULT_SPACING
def stream():
if not prompt:
yield json.dumps({"type": "error", "message": "empty prompt"}) + "\n"
return
for step, total, elapsed, image in generate(
prompt, seed=req.seed, steps=steps, guidance=guidance,
spacing=spacing, guidance_rescale=g_rescale,
):
if image is None:
yield json.dumps({
"type": "step", "step": step, "total": total,
"elapsed": round(elapsed, 2),
}) + "\n"
else:
buf = io.BytesIO()
image.save(buf, format="PNG")
b64 = base64.b64encode(buf.getvalue()).decode()
yield json.dumps({
"type": "done", "step": step, "total": total,
"elapsed": round(elapsed, 2),
"image": "data:image/png;base64," + b64,
}) + "\n"
return StreamingResponse(stream(), media_type="application/x-ndjson")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=7860)