ChronicleNext / api.py
topguy's picture
fix: Overhaul dropdown logic for Cloud deployment contexts
33b76fc
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
@app.get("/api/config")
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()
}
@app.get("/api/example/{filename}")
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")
@app.post("/api/generate_prompt")
def generate_prompt(req: PromptRequest):
args = req.to_args()
prompt = core_generate_prompt(*args)
return {"prompt": prompt}
@app.post("/api/regenerate_features")
def regenerate_features(req: RegenerationRequest):
args = req.to_args()
new_values = core_handle_regeneration(*args)
return {"new_values": new_values}
@app.post("/api/generate_name")
def proxy_generate_name(req: NamingRequest):
name = generate_fantasy_name(req.race)
return {"name": name}
@app.post("/api/refine_prompt")
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}
@app.post("/api/generate_image")
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.")
@app.get("/api/config/oauth")
def get_oauth_config():
return {
"oauth_client_id": os.environ.get("OAUTH_CLIENT_ID")
}
@app.post("/api/save_character")
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)