knnn11 commited on
Commit
f402022
Β·
verified Β·
1 Parent(s): e00a97b

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +163 -8
app.py CHANGED
@@ -1,17 +1,172 @@
1
- from fastapi import FastAPI
 
 
 
 
 
 
2
  import gradio as gr
 
 
 
 
 
3
 
4
- app = FastAPI()
 
 
 
 
 
 
 
5
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
6
  @app.get("/health")
7
- def health_check():
8
- return {"status": "ok"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
9
 
10
- # Basic Gradio interface
11
- demo = gr.Interface(lambda x: x, "text", "text")
 
 
 
 
 
 
 
 
12
 
13
- app = gr.mount_gradio_app(app, demo, path="/")
 
14
 
 
15
  if __name__ == "__main__":
16
  import uvicorn
17
- uvicorn.run(app, host="0.0.0.0", port=7860)
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import io
3
+ import logging
4
+ import hashlib
5
+ from contextlib import asynccontextmanager
6
+ from typing import Optional
7
+ import aiohttp
8
  import gradio as gr
9
+ from fastapi import FastAPI, HTTPException
10
+ from fastapi.responses import FileResponse, JSONResponse
11
+ from fastapi.middleware.cors import CORSMiddleware
12
+ from pydantic import BaseModel
13
+ from PIL import Image
14
 
15
+ # --- Configuration ---
16
+ class Config:
17
+ OUTPUT_DIR = os.path.join(os.getcwd(), "comic_outputs")
18
+ RUNPOD_ENDPOINT = os.getenv("RUNPOD_ENDPOINT", "http://localhost:8001")
19
+ RUNPOD_API_KEY = os.getenv("RUNPOD_API_KEY", "")
20
+ MAX_RETRIES = 3
21
+ TIMEOUT = 30
22
+ GRADIO_PORT = int(os.getenv("GRADIO_PORT", 7860))
23
 
24
+ # --- Logging Setup ---
25
+ logging.basicConfig(
26
+ level=logging.INFO,
27
+ format="%(asctime)s - %(name)s - %(levelname)s - %(message)s",
28
+ handlers=[logging.StreamHandler()]
29
+ )
30
+ logger = logging.getLogger(__name__)
31
+
32
+ # --- Data Models ---
33
+ class GenerationRequest(BaseModel):
34
+ prompt: str
35
+ steps: Optional[int] = 30
36
+ width: Optional[int] = 768
37
+ height: Optional[int] = 768
38
+ seed: Optional[int] = None
39
+
40
+ # --- Application Setup ---
41
+ @asynccontextmanager
42
+ async def lifespan(app: FastAPI):
43
+ """Modern lifespan handler with resource management"""
44
+ # Startup
45
+ os.makedirs(Config.OUTPUT_DIR, exist_ok=True)
46
+ logger.info("πŸš€ Starting AI Comic Maker API")
47
+ logger.info(f"πŸ“ Output directory: {Config.OUTPUT_DIR}")
48
+
49
+ # Create aiohttp session
50
+ app.state.http_session = aiohttp.ClientSession()
51
+
52
+ yield
53
+
54
+ # Shutdown
55
+ await app.state.http_session.close()
56
+ logger.info("πŸ›‘ Graceful shutdown complete")
57
+
58
+ app = FastAPI(
59
+ title="AI Comic Maker API",
60
+ description="Generate comic art using Stable Diffusion",
61
+ version="1.0.0",
62
+ lifespan=lifespan
63
+ )
64
+
65
+ # CORS Middleware
66
+ app.add_middleware(
67
+ CORSMiddleware,
68
+ allow_origins=["*"],
69
+ allow_methods=["*"],
70
+ allow_headers=["*"],
71
+ )
72
+
73
+ # --- Helper Functions ---
74
+ def sanitize_filename(prompt: str) -> str:
75
+ """Create safe filename from prompt"""
76
+ prompt_hash = hashlib.md5(prompt.encode()).hexdigest()
77
+ return f"comic_{prompt_hash}.png"
78
+
79
+ async def generate_image(payload: dict) -> bytes:
80
+ """Call RunPod API with retry logic"""
81
+ headers = {"Authorization": f"Bearer {Config.RUNPOD_API_KEY}"} if Config.RUNPOD_API_KEY else {}
82
+
83
+ for attempt in range(Config.MAX_RETRIES):
84
+ try:
85
+ async with app.state.http_session.post(
86
+ f"{Config.RUNPOD_ENDPOINT}/generate",
87
+ headers=headers,
88
+ json={"input": payload},
89
+ timeout=Config.TIMEOUT
90
+ ) as response:
91
+ response.raise_for_status()
92
+ return await response.read()
93
+ except Exception as e:
94
+ if attempt == Config.MAX_RETRIES - 1:
95
+ raise
96
+ logger.warning(f"Retry {attempt + 1} for generation request")
97
+ await asyncio.sleep(1)
98
+
99
+ # --- API Endpoints ---
100
  @app.get("/health")
101
+ async def health_check():
102
+ """Liveness probe endpoint"""
103
+ return JSONResponse(content={"status": "healthy"})
104
+
105
+ @app.post("/generate")
106
+ async def generate_comic(request: GenerationRequest):
107
+ """Generate comic image from text prompt"""
108
+ try:
109
+ logger.info(f"🎨 Generating comic: '{request.prompt[:50]}...'")
110
+
111
+ # Call RunPod API
112
+ image_data = await generate_image({
113
+ "prompt": request.prompt,
114
+ "steps": request.steps,
115
+ "width": request.width,
116
+ "height": request.height,
117
+ "seed": request.seed
118
+ })
119
+
120
+ # Save image
121
+ output_filename = sanitize_filename(request.prompt)
122
+ output_path = os.path.join(Config.OUTPUT_DIR, output_filename)
123
+
124
+ with Image.open(io.BytesIO(image_data)) as img:
125
+ img.save(output_path)
126
+
127
+ logger.info(f"βœ… Saved comic to {output_path}")
128
+ return FileResponse(output_path, media_type="image/png")
129
+
130
+ except Exception as e:
131
+ logger.error(f"πŸ”₯ Generation failed: {str(e)}")
132
+ raise HTTPException(status_code=500, detail=str(e))
133
+
134
+ # --- Gradio Interface ---
135
+ def gradio_generate(prompt: str, steps: int = 30):
136
+ """Gradio wrapper for generation"""
137
+ try:
138
+ response = requests.post(
139
+ f"http://localhost:{Config.GRADIO_PORT}/generate",
140
+ json={"prompt": prompt, "steps": steps}
141
+ )
142
+ response.raise_for_status()
143
+ return response.content
144
+ except Exception as e:
145
+ return str(e)
146
 
147
+ gradio_interface = gr.Interface(
148
+ fn=gradio_generate,
149
+ inputs=[
150
+ gr.Textbox(label="Prompt"),
151
+ gr.Slider(10, 100, value=30, label="Steps")
152
+ ],
153
+ outputs=gr.Image(label="Generated Comic"),
154
+ title="AI Comic Maker",
155
+ description="Generate comic art from text prompts"
156
+ )
157
 
158
+ # --- Mount Gradio to FastAPI ---
159
+ app = gr.mount_gradio_app(app, gradio_interface, path="/gradio")
160
 
161
+ # --- Main Entry Point ---
162
  if __name__ == "__main__":
163
  import uvicorn
164
+ import asyncio
165
+
166
+ uvicorn.run(
167
+ app,
168
+ host="0.0.0.0",
169
+ port=Config.GRADIO_PORT,
170
+ log_level="info",
171
+ reload=False
172
+ )