hallmark / app.py
arvindcr4's picture
Hallmark: provenance-first AI media on Genblaze + Backblaze B2
855b5c0
Raw
History Blame Contribute Delete
2.79 kB
"""Hallmark — HTTP surface.
Three things a user can do:
POST /api/mint generate an image and hallmark it into B2
POST /api/verify upload any file, ask where it came from
GET /api/library browse what this instance has minted
"""
from __future__ import annotations
import os
from pathlib import Path
from fastapi import FastAPI, File, HTTPException, UploadFile
from fastapi.responses import FileResponse, JSONResponse
from fastapi.staticfiles import StaticFiles
from pydantic import BaseModel, Field
from hallmark import __version__, studio
app = FastAPI(title="Hallmark", version=__version__)
STATIC = Path(__file__).parent / "static"
MAX_UPLOAD_BYTES = 25 * 1024 * 1024
class MintRequest(BaseModel):
prompt: str = Field(min_length=3, max_length=1200)
model: str = "flux"
width: int = Field(default=1024, ge=256, le=1536)
height: int = Field(default=1024, ge=256, le=1536)
seed: int | None = None
@app.get("/api/health")
def health() -> dict[str, object]:
return {
"ok": True,
"version": __version__,
"b2_configured": studio.b2_configured(),
"bucket": studio.BUCKET,
"region": studio.REGION,
}
@app.post("/api/mint")
def mint(req: MintRequest) -> JSONResponse:
try:
result = studio.mint(
req.prompt, model=req.model, width=req.width, height=req.height, seed=req.seed
)
except studio.NotConfigured as exc:
raise HTTPException(status_code=503, detail=str(exc)) from exc
except Exception as exc:
raise HTTPException(status_code=502, detail=f"generation failed: {exc}") from exc
return JSONResponse(result.as_dict())
@app.post("/api/verify")
async def verify(file: UploadFile = File(...)) -> JSONResponse:
data = await file.read(MAX_UPLOAD_BYTES + 1)
if len(data) > MAX_UPLOAD_BYTES:
raise HTTPException(status_code=413, detail="file larger than 25 MB")
if not data:
raise HTTPException(status_code=400, detail="empty upload")
try:
report = studio.verify_bytes(data, filename=file.filename or "upload")
except studio.NotConfigured as exc:
raise HTTPException(status_code=503, detail=str(exc)) from exc
return JSONResponse(report)
@app.get("/api/library")
def library(limit: int = 60) -> JSONResponse:
try:
return JSONResponse({"items": studio.library(limit=limit)})
except studio.NotConfigured as exc:
raise HTTPException(status_code=503, detail=str(exc)) from exc
@app.get("/")
def index() -> FileResponse:
return FileResponse(STATIC / "index.html")
app.mount("/static", StaticFiles(directory=STATIC), name="static")
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=int(os.environ.get("PORT", 7860)))