"""Creative upload, analyze, and modify endpoints.""" import os from datetime import datetime from fastapi import APIRouter, HTTPException, Depends, File, UploadFile from api.schemas import ( CreativeAnalyzeRequest, CreativeAnalysisResponse, CreativeModifyRequest, CreativeModifyResponse, FileUploadResponse, ) from services.creative_modifier import creative_modifier_service from services.image import image_service from services.auth_dependency import get_current_user from config import settings router = APIRouter(tags=["creative"]) @router.post("/api/creative/upload", response_model=FileUploadResponse) async def upload_creative( file: UploadFile = File(...), username: str = Depends(get_current_user), ): """ Upload a creative image for analysis and modification. Accepts PNG, JPG, JPEG, WebP. Returns image URL for subsequent steps. """ allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp"] if file.content_type not in allowed_types: raise HTTPException(status_code=400, detail=f"Invalid file type. Allowed: PNG, JPG, JPEG, WebP. Got: {file.content_type}") contents = await file.read() if len(contents) > 10 * 1024 * 1024: raise HTTPException(status_code=400, detail="File too large. Maximum size is 10MB.") try: timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") unique_id = __import__("uuid").uuid4().hex[:8] ext = file.filename.split(".")[-1] if file.filename else "png" filename = f"upload_{username}_{timestamp}_{unique_id}.{ext}" r2_url = None try: from services.r2_storage import get_r2_storage r2_storage = get_r2_storage() if r2_storage: r2_url = r2_storage.upload_image(image_bytes=contents, filename=filename, niche="uploads") except Exception: pass if not r2_url: local_path = os.path.join(settings.output_dir, filename) os.makedirs(os.path.dirname(local_path), exist_ok=True) with open(local_path, "wb") as f: f.write(contents) r2_url = f"/images/{filename}" return {"status": "success", "image_url": r2_url, "filename": filename} except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/api/creative/analyze", response_model=CreativeAnalysisResponse) async def analyze_creative( request: CreativeAnalyzeRequest, username: str = Depends(get_current_user), ): """Analyze a creative image using AI vision (via URL).""" if not request.image_url: raise HTTPException(status_code=400, detail="image_url must be provided") try: image_bytes = await image_service.load_image(image_url=request.image_url) except Exception as e: raise HTTPException(status_code=400, detail=f"Failed to fetch image from URL: {e}") if not image_bytes: raise HTTPException(status_code=400, detail="Failed to load image") try: result = await creative_modifier_service.analyze_creative(image_bytes) if result["status"] != "success": return CreativeAnalysisResponse(status="error", error=result.get("error", "Analysis failed")) return CreativeAnalysisResponse( status="success", analysis=result.get("analysis"), suggested_angles=result.get("suggested_angles"), suggested_concepts=result.get("suggested_concepts"), ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/api/creative/analyze/upload", response_model=CreativeAnalysisResponse) async def analyze_creative_upload( file: UploadFile = File(...), username: str = Depends(get_current_user), ): """Analyze a creative image using AI vision (via file upload).""" allowed_types = ["image/png", "image/jpeg", "image/jpg", "image/webp"] if file.content_type not in allowed_types: raise HTTPException(status_code=400, detail=f"Invalid file type. Allowed: PNG, JPG, JPEG, WebP. Got: {file.content_type}") image_bytes = await file.read() if not image_bytes: raise HTTPException(status_code=400, detail="Failed to load image") try: result = await creative_modifier_service.analyze_creative(image_bytes) if result["status"] != "success": return CreativeAnalysisResponse(status="error", error=result.get("error", "Analysis failed")) return CreativeAnalysisResponse( status="success", analysis=result.get("analysis"), suggested_angles=result.get("suggested_angles"), suggested_concepts=result.get("suggested_concepts"), ) except Exception as e: raise HTTPException(status_code=500, detail=str(e)) @router.post("/api/creative/modify", response_model=CreativeModifyResponse) async def modify_creative( request: CreativeModifyRequest, username: str = Depends(get_current_user), ): """ Modify a creative with angle and/or concept. Modes: 'modify' (image-to-image) or 'inspired' (new generation). """ if not request.angle and not request.concept: raise HTTPException(status_code=400, detail="At least one of 'angle' or 'concept' must be provided") analysis = request.analysis if not analysis: try: image_bytes = await image_service.load_image(image_url=request.image_url) if not image_bytes: raise HTTPException(status_code=400, detail="Failed to load image from URL") analysis_result = await creative_modifier_service.analyze_creative(image_bytes) if analysis_result["status"] != "success": raise HTTPException(status_code=500, detail=analysis_result.get("error", "Analysis failed")) analysis = analysis_result.get("analysis", {}) except HTTPException: raise except Exception as e: raise HTTPException(status_code=500, detail=f"Failed to analyze image: {e}") try: result = await creative_modifier_service.modify_creative( image_url=request.image_url, analysis=analysis, user_angle=request.angle, user_concept=request.concept, mode=request.mode, image_model=request.image_model, user_prompt=request.user_prompt, ) if result["status"] != "success": return CreativeModifyResponse(status="error", error=result.get("error", "Modification failed")) return CreativeModifyResponse(status="success", prompt=result.get("prompt"), image=result.get("image")) except Exception as e: raise HTTPException(status_code=500, detail=str(e))