| """Angle × Concept matrix endpoints.""" |
|
|
| from fastapi import APIRouter, HTTPException, Depends |
|
|
| from api.schemas import ( |
| MatrixGenerateRequest, |
| MatrixGenerateResponse, |
| MatrixBatchRequest, |
| TestingMatrixResponse, |
| RefineCustomRequest, |
| RefineCustomResponse, |
| ) |
| from services.generator import ad_generator |
| from services.matrix import matrix_service |
| from services.auth_dependency import get_current_user |
|
|
| router = APIRouter(tags=["matrix"]) |
|
|
|
|
| @router.post("/matrix/generate", response_model=MatrixGenerateResponse) |
| async def generate_with_matrix( |
| request: MatrixGenerateRequest, |
| username: str = Depends(get_current_user), |
| ): |
| """ |
| Generate ad using the Angle × Concept matrix approach. |
| Requires authentication. Supports custom angle/concept when key is 'custom'. |
| """ |
| try: |
| return await ad_generator.generate_ad_with_matrix( |
| niche=request.niche, |
| angle_key=request.angle_key, |
| concept_key=request.concept_key, |
| custom_angle=request.custom_angle, |
| custom_concept=request.custom_concept, |
| num_images=request.num_images, |
| image_model=request.image_model, |
| username=username, |
| core_motivator=request.core_motivator, |
| target_audience=request.target_audience, |
| offer=request.offer, |
| ) |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.post("/matrix/testing", response_model=TestingMatrixResponse) |
| async def generate_testing_matrix(request: MatrixBatchRequest): |
| """ |
| Generate a testing matrix (combinations without images). |
| Strategies: balanced, top_performers, diverse. |
| """ |
| try: |
| combinations = matrix_service.generate_testing_matrix( |
| niche=request.niche, |
| angle_count=request.angle_count, |
| concept_count=request.concept_count, |
| strategy=request.strategy, |
| ) |
| summary = matrix_service.get_matrix_summary(combinations) |
| return { |
| "niche": request.niche, |
| "strategy": request.strategy, |
| "summary": summary, |
| "combinations": combinations, |
| } |
| except Exception as e: |
| raise HTTPException(status_code=500, detail=str(e)) |
|
|
|
|
| @router.get("/matrix/angles") |
| async def list_angles(): |
| """List all available angles (100 total, 10 categories).""" |
| from data.angles import ANGLES, get_all_angles |
|
|
| categories = {} |
| for cat_key, cat_data in ANGLES.items(): |
| categories[cat_key.value] = { |
| "name": cat_data["name"], |
| "angle_count": len(cat_data["angles"]), |
| "angles": [ |
| {"key": a["key"], "name": a["name"], "trigger": a["trigger"], "example": a["example"]} |
| for a in cat_data["angles"] |
| ], |
| } |
| return {"total_angles": len(get_all_angles()), "categories": categories} |
|
|
|
|
| @router.get("/matrix/concepts") |
| async def list_concepts(): |
| """List all available concepts (100 total, 10 categories).""" |
| from data.concepts import CONCEPTS, get_all_concepts |
|
|
| categories = {} |
| for cat_key, cat_data in CONCEPTS.items(): |
| categories[cat_key.value] = { |
| "name": cat_data["name"], |
| "concept_count": len(cat_data["concepts"]), |
| "concepts": [ |
| {"key": c["key"], "name": c["name"], "structure": c["structure"], "visual": c["visual"]} |
| for c in cat_data["concepts"] |
| ], |
| } |
| return {"total_concepts": len(get_all_concepts()), "categories": categories} |
|
|
|
|
| @router.get("/matrix/angle/{angle_key}") |
| async def get_angle(angle_key: str): |
| """Get details for a specific angle by key.""" |
| from data.angles import get_angle_by_key |
|
|
| angle = get_angle_by_key(angle_key) |
| if not angle: |
| raise HTTPException(status_code=404, detail=f"Angle '{angle_key}' not found") |
| return angle |
|
|
|
|
| @router.get("/matrix/concept/{concept_key}") |
| async def get_concept(concept_key: str): |
| """Get details for a specific concept by key.""" |
| from data.concepts import get_concept_by_key |
|
|
| concept = get_concept_by_key(concept_key) |
| if not concept: |
| raise HTTPException(status_code=404, detail=f"Concept '{concept_key}' not found") |
| return concept |
|
|
|
|
| @router.get("/matrix/compatible/{angle_key}") |
| async def get_compatible_concepts(angle_key: str): |
| """Get concepts compatible with a specific angle.""" |
| from data.angles import get_angle_by_key |
| from data.concepts import get_compatible_concepts as get_compatible |
|
|
| angle = get_angle_by_key(angle_key) |
| if not angle: |
| raise HTTPException(status_code=404, detail=f"Angle '{angle_key}' not found") |
| compatible = get_compatible(angle.get("trigger", "")) |
| return { |
| "angle": {"key": angle["key"], "name": angle["name"], "trigger": angle["trigger"]}, |
| "compatible_concepts": [ |
| {"key": c["key"], "name": c["name"], "structure": c["structure"]} |
| for c in compatible |
| ], |
| } |
|
|
|
|
| @router.post("/matrix/refine-custom", response_model=RefineCustomResponse) |
| async def refine_custom_angle_or_concept(request: RefineCustomRequest): |
| """Refine a custom angle or concept text using AI.""" |
| try: |
| result = await ad_generator.refine_custom_angle_or_concept( |
| text=request.text, |
| type=request.type, |
| niche=request.niche, |
| goal=request.goal, |
| ) |
| return {"status": "success", "type": request.type, "refined": result} |
| except Exception as e: |
| return {"status": "error", "type": request.type, "refined": None, "error": str(e)} |
|
|