|
|
"""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)} |
|
|
|