Spaces:
Sleeping
Sleeping
| import os | |
| from fastapi import FastAPI, UploadFile, File, Form, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from fastapi.responses import FileResponse | |
| from pydantic import BaseModel | |
| from typing import List, Optional, Any | |
| from modules.config import FEATURE_SEQUENCE, SECTIONS | |
| from modules.core_logic import ( | |
| generate_prompt as core_generate_prompt, | |
| handle_regeneration as core_handle_regeneration, | |
| save_character as core_save_character, | |
| load_character as core_load_character | |
| ) | |
| from modules.integrations import ( | |
| refine_master, | |
| generate_name_master, | |
| generate_image_master, | |
| get_ollama_models, | |
| check_comfy_availability | |
| ) | |
| from modules.name_generator import generate_fantasy_name | |
| app = FastAPI(title="Chronicle Portrait Studio API") | |
| # Setup CORS | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], # Since frontend might run on different port | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| class PromptRequest(BaseModel): | |
| character_name: str | |
| features: List[str] | |
| randomization: List[bool] | |
| extra_info: List[str] | |
| def to_args(self): | |
| return [self.character_name] + self.features + self.randomization + self.extra_info | |
| class NamingRequest(BaseModel): | |
| race: str | |
| class RegenerationRequest(BaseModel): | |
| current_values: List[str] | |
| checkboxes: List[bool] | |
| def to_args(self): | |
| return self.current_values + self.checkboxes | |
| class RefinementRequest(BaseModel): | |
| prompt: str | |
| backend: str | |
| ollama_model: Optional[str] = None | |
| hf_text_model: Optional[str] = None | |
| hf_text_provider: Optional[str] = None | |
| oauth_token: Optional[str] = None | |
| character_name: Optional[str] = None | |
| class ImageGenerationRequest(BaseModel): | |
| refined_prompt: str | |
| technical_prompt: str | |
| aspect_ratio: str | |
| backend: str | |
| hf_image_model: Optional[str] = None | |
| hf_image_provider: Optional[str] = None | |
| oauth_token: Optional[str] = None | |
| character_name: Optional[str] = None | |
| def get_config(): | |
| """Returns static config info needed by frontend to render dropdowns, etc.""" | |
| from modules.core_logic import features_data, get_example_list | |
| from modules.integrations import gemini_active, hf_active | |
| from modules.config import HF_TEXT_MODELS, HF_IMAGE_MODELS | |
| return { | |
| "features_data": features_data, | |
| "feature_sequence": FEATURE_SEQUENCE, | |
| "sections": SECTIONS, | |
| "ollama_models": get_ollama_models(), | |
| "comfy_active": check_comfy_availability(), | |
| "gemini_active": gemini_active, | |
| "hf_active": hf_active, | |
| "is_hf_space": bool(os.environ.get("SPACE_ID")), | |
| "hf_text_models": HF_TEXT_MODELS, | |
| "hf_image_models": HF_IMAGE_MODELS, | |
| "examples": get_example_list() | |
| } | |
| def get_example(filename: str): | |
| from modules.config import EXAMPLES_DIR | |
| import os | |
| path = os.path.join(EXAMPLES_DIR, filename) | |
| if os.path.exists(path): | |
| return FileResponse(path, media_type="application/json") | |
| raise HTTPException(status_code=404, detail="Example not found") | |
| def generate_prompt(req: PromptRequest): | |
| args = req.to_args() | |
| prompt = core_generate_prompt(*args) | |
| return {"prompt": prompt} | |
| def regenerate_features(req: RegenerationRequest): | |
| args = req.to_args() | |
| new_values = core_handle_regeneration(*args) | |
| return {"new_values": new_values} | |
| def proxy_generate_name(req: NamingRequest): | |
| name = generate_fantasy_name(req.race) | |
| return {"name": name} | |
| def refine_prompt(req: RefinementRequest): | |
| result, error_msg = refine_master( | |
| req.prompt, | |
| req.backend, | |
| req.ollama_model, | |
| req.hf_text_model, | |
| req.hf_text_provider, | |
| req.oauth_token, | |
| req.character_name | |
| ) | |
| # result can be a gr.update() if it fails, and error_msg will have content | |
| if error_msg: | |
| raise HTTPException(status_code=400, detail=error_msg) | |
| return {"refined_prompt": result} | |
| def generate_image(req: ImageGenerationRequest): | |
| print(f"DEBUG: Received Image Request: {req.dict()}") | |
| img, img_path, status_msg = generate_image_master( | |
| req.refined_prompt, | |
| req.technical_prompt, | |
| req.aspect_ratio, | |
| req.backend, | |
| req.hf_image_model, | |
| req.hf_image_provider, | |
| req.oauth_token, | |
| req.character_name | |
| ) | |
| if img_path and os.path.exists(img_path): | |
| return FileResponse(img_path, media_type="image/png", headers={"X-Status-Msg": status_msg}) | |
| else: | |
| raise HTTPException(status_code=500, detail=status_msg or "Failed to generate image.") | |
| def get_oauth_config(): | |
| return { | |
| "oauth_client_id": os.environ.get("OAUTH_CLIENT_ID") | |
| } | |
| def save_character(req: PromptRequest): | |
| args = req.to_args() | |
| file_path = core_save_character(*args) | |
| if file_path and os.path.exists(file_path): | |
| return FileResponse(file_path, media_type="application/json", filename=os.path.basename(file_path)) | |
| raise HTTPException(status_code=500, detail="Failed to save character state.") | |
| from fastapi.staticfiles import StaticFiles | |
| frontend_path = os.path.join(os.path.dirname(__file__), "frontend", "out") | |
| if os.path.exists(frontend_path): | |
| app.mount("/", StaticFiles(directory=frontend_path, html=True), name="frontend") | |
| else: | |
| print(f"WARNING: Static frontend directory not found at {frontend_path}. Please build the Next.js frontend.") | |
| if __name__ == "__main__": | |
| import uvicorn | |
| uvicorn.run("api:app", host="0.0.0.0", port=8000, reload=True) | |