Files
Consultoría AS 964e38564a feat(phase-3): Complete AI content generation system
- Add /api/generate endpoints for AI content generation
- Integrate DeepSeek AI into dashboard compose page
- Add content templates for tips, products, services, engagement
- Implement batch generation for weekly/monthly calendars
- Add cost estimation endpoint

New endpoints:
- POST /api/generate/tip - Generate tech tips
- POST /api/generate/product - Generate product posts
- POST /api/generate/service - Generate service posts
- POST /api/generate/thread - Generate educational threads
- POST /api/generate/response - Generate response suggestions
- POST /api/generate/adapt - Adapt content to platform
- POST /api/generate/improve - Improve existing content
- POST /api/generate/batch - Batch generate for calendar
- GET /api/generate/batch/estimate - Estimate batch costs
- GET /api/generate/status - Check AI connection

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-28 01:49:49 +00:00

552 lines
16 KiB
Python

"""
API endpoints para generación de contenido con IA.
"""
from typing import Optional, List
from fastapi import APIRouter, HTTPException
from pydantic import BaseModel
from app.services.content_generator import content_generator
from app.core.config import settings
router = APIRouter()
# ============================================================
# Schemas
# ============================================================
class GenerateTipRequest(BaseModel):
"""Solicitud para generar tip tech."""
category: str = "general"
platform: str = "x"
template: Optional[str] = None
class GenerateProductPostRequest(BaseModel):
"""Solicitud para generar post de producto."""
product: dict # name, description, price, category, specs, highlights
platform: str = "x"
class GenerateServicePostRequest(BaseModel):
"""Solicitud para generar post de servicio."""
service: dict # name, description, category, target_sectors, benefits, call_to_action
platform: str = "x"
class GenerateThreadRequest(BaseModel):
"""Solicitud para generar hilo educativo."""
topic: str
num_posts: int = 5
class GenerateResponseRequest(BaseModel):
"""Solicitud para generar sugerencias de respuesta."""
interaction_content: str
interaction_type: str = "comment" # comment, mention, dm
context: Optional[str] = None
class AdaptContentRequest(BaseModel):
"""Solicitud para adaptar contenido a plataforma."""
content: str
target_platform: str
class ImproveContentRequest(BaseModel):
"""Solicitud para mejorar contenido existente."""
content: str
platform: str = "x"
style: str = "engaging" # engaging, professional, casual, educational
class GenerateResponse(BaseModel):
"""Respuesta de generación."""
success: bool
content: Optional[str] = None
contents: Optional[List[str]] = None
error: Optional[str] = None
tokens_used: Optional[int] = None
# ============================================================
# Helper
# ============================================================
def check_api_configured():
"""Verificar que la API de DeepSeek esté configurada."""
if not settings.DEEPSEEK_API_KEY:
raise HTTPException(
status_code=503,
detail="DeepSeek API no configurada. Agrega DEEPSEEK_API_KEY en .env"
)
# ============================================================
# Endpoints
# ============================================================
@router.post("/tip", response_model=GenerateResponse)
async def generate_tip(request: GenerateTipRequest):
"""
Generar un tip de tecnología.
Categorías sugeridas:
- productividad, seguridad, ia, programacion, hardware,
- redes, cloud, automatizacion, impresion3d, general
"""
check_api_configured()
try:
content = await content_generator.generate_tip_tech(
category=request.category,
platform=request.platform,
template=request.template
)
return GenerateResponse(
success=True,
content=content
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/product", response_model=GenerateResponse)
async def generate_product_post(request: GenerateProductPostRequest):
"""
Generar post promocional de producto.
Campos del producto:
- name (requerido): Nombre del producto
- description: Descripción
- price (requerido): Precio en MXN
- category: Categoría (computadoras, impresoras3d, accesorios)
- specs: Dict con especificaciones técnicas
- highlights: Lista de puntos destacados
"""
check_api_configured()
# Validar campos mínimos
if "name" not in request.product or "price" not in request.product:
raise HTTPException(
status_code=400,
detail="El producto debe tener 'name' y 'price'"
)
try:
content = await content_generator.generate_product_post(
product=request.product,
platform=request.platform
)
return GenerateResponse(
success=True,
content=content
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/service", response_model=GenerateResponse)
async def generate_service_post(request: GenerateServicePostRequest):
"""
Generar post promocional de servicio.
Campos del servicio:
- name (requerido): Nombre del servicio
- description: Descripción
- category: Categoría (consultoria, desarrollo, soporte, capacitacion)
- target_sectors: Lista de sectores objetivo
- benefits: Lista de beneficios
- call_to_action: CTA personalizado
"""
check_api_configured()
if "name" not in request.service:
raise HTTPException(
status_code=400,
detail="El servicio debe tener 'name'"
)
try:
content = await content_generator.generate_service_post(
service=request.service,
platform=request.platform
)
return GenerateResponse(
success=True,
content=content
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/thread", response_model=GenerateResponse)
async def generate_thread(request: GenerateThreadRequest):
"""
Generar hilo educativo.
- topic: Tema del hilo (ej: "5 tips para proteger tu PC")
- num_posts: Cantidad de posts en el hilo (3-10)
"""
check_api_configured()
if request.num_posts < 3 or request.num_posts > 10:
raise HTTPException(
status_code=400,
detail="num_posts debe estar entre 3 y 10"
)
try:
posts = await content_generator.generate_thread(
topic=request.topic,
num_posts=request.num_posts
)
return GenerateResponse(
success=True,
contents=posts
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/response", response_model=GenerateResponse)
async def generate_response_suggestions(request: GenerateResponseRequest):
"""
Generar sugerencias de respuesta para una interacción.
Devuelve 3 opciones de respuesta diferentes.
"""
check_api_configured()
try:
suggestions = await content_generator.generate_response_suggestion(
interaction_content=request.interaction_content,
interaction_type=request.interaction_type,
context=request.context
)
return GenerateResponse(
success=True,
contents=suggestions
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/adapt", response_model=GenerateResponse)
async def adapt_content(request: AdaptContentRequest):
"""
Adaptar contenido existente a una plataforma específica.
Útil para tomar contenido de una plataforma y adaptarlo
a los límites y estilo de otra.
"""
check_api_configured()
valid_platforms = ["x", "threads", "instagram", "facebook"]
if request.target_platform.lower() not in valid_platforms:
raise HTTPException(
status_code=400,
detail=f"Plataforma no válida. Opciones: {valid_platforms}"
)
try:
content = await content_generator.adapt_content_for_platform(
content=request.content,
target_platform=request.target_platform.lower()
)
return GenerateResponse(
success=True,
content=content
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.post("/improve", response_model=GenerateResponse)
async def improve_content(request: ImproveContentRequest):
"""
Mejorar contenido existente.
Estilos disponibles:
- engaging: Más atractivo y con gancho
- professional: Más formal y corporativo
- casual: Más cercano y amigable
- educational: Más informativo y didáctico
"""
check_api_configured()
try:
# Usar el generador para mejorar
from openai import OpenAI
client = OpenAI(
api_key=settings.DEEPSEEK_API_KEY,
base_url=settings.DEEPSEEK_BASE_URL
)
style_prompts = {
"engaging": "Hazlo más atractivo, con un gancho inicial que capture atención",
"professional": "Hazlo más profesional y corporativo, manteniendo credibilidad",
"casual": "Hazlo más cercano y amigable, como si hablaras con un amigo",
"educational": "Hazlo más informativo y didáctico, que enseñe algo útil"
}
char_limits = {
"x": 280,
"threads": 500,
"instagram": 2200,
"facebook": 1000
}
prompt = f"""Mejora este contenido para {request.platform}:
CONTENIDO ORIGINAL:
{request.content}
ESTILO DESEADO: {style_prompts.get(request.style, style_prompts['engaging'])}
LÍMITE DE CARACTERES: {char_limits.get(request.platform, 500)}
REQUISITOS:
- Mantén el mensaje principal
- Mejora la redacción y el impacto
- Incluye emojis relevantes
- Termina con hashtags apropiados
Responde SOLO con el contenido mejorado."""
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": content_generator._get_system_prompt()},
{"role": "user", "content": prompt}
],
max_tokens=400,
temperature=0.7
)
improved = response.choices[0].message.content.strip()
return GenerateResponse(
success=True,
content=improved,
tokens_used=response.usage.total_tokens
)
except Exception as e:
return GenerateResponse(
success=False,
error=str(e)
)
@router.get("/categories")
async def get_tip_categories():
"""Obtener categorías disponibles para tips."""
return {
"categories": [
{"id": "productividad", "name": "Productividad", "description": "Tips para ser más eficiente"},
{"id": "seguridad", "name": "Seguridad", "description": "Ciberseguridad y protección"},
{"id": "ia", "name": "Inteligencia Artificial", "description": "IA y machine learning"},
{"id": "programacion", "name": "Programación", "description": "Desarrollo de software"},
{"id": "hardware", "name": "Hardware", "description": "Equipos y componentes"},
{"id": "redes", "name": "Redes", "description": "Networking y conectividad"},
{"id": "cloud", "name": "Cloud", "description": "Servicios en la nube"},
{"id": "automatizacion", "name": "Automatización", "description": "Automatizar procesos"},
{"id": "impresion3d", "name": "Impresión 3D", "description": "Impresoras y diseño 3D"},
{"id": "general", "name": "General", "description": "Tips variados de tecnología"},
]
}
class BatchGenerateRequest(BaseModel):
"""Solicitud de generación por lotes."""
platforms: List[str]
days: int = 7 # 7 para semana, 30 para mes
tip_categories: Optional[List[str]] = None
@router.post("/batch")
async def generate_batch(request: BatchGenerateRequest):
"""
Generar contenido por lotes para el calendario.
- **platforms**: Lista de plataformas ["x", "threads", "instagram"]
- **days**: Cantidad de días (7 para semana, 30 para mes)
- **tip_categories**: Categorías de tips a usar
"""
check_api_configured()
if request.days < 1 or request.days > 30:
raise HTTPException(
status_code=400,
detail="days debe estar entre 1 y 30"
)
from app.services.batch_generator import batch_generator
try:
result = await batch_generator._generate_batch(
platforms=request.platforms,
start_date=None, # Usa mañana por defecto
days=request.days,
tip_categories=request.tip_categories
)
return {
"success": result.success,
"total_requested": result.total_requested,
"total_generated": result.total_generated,
"posts": [
{
"content": p.content,
"content_type": p.content_type.value,
"platform": p.platform,
"scheduled_at": p.scheduled_at.isoformat(),
"metadata": p.metadata
}
for p in result.posts
],
"errors": result.errors
}
except Exception as e:
raise HTTPException(
status_code=500,
detail=str(e)
)
@router.get("/batch/estimate")
async def estimate_batch_cost(
platforms: str, # comma-separated
days: int = 7
):
"""
Estimar costo de generación por lotes.
Calcula tokens y costo estimado sin generar contenido.
"""
from app.services.batch_generator import batch_generator
platform_list = [p.strip() for p in platforms.split(",")]
estimate = batch_generator.calculate_costs(platform_list, days)
return estimate
@router.post("/tips/batch")
async def generate_tips_batch(
count: int = 10,
platform: str = "x",
categories: Optional[str] = None # comma-separated
):
"""
Generar múltiples tips de una vez.
- **count**: Cantidad de tips (1-20)
- **platform**: Plataforma destino
- **categories**: Categorías separadas por coma
"""
check_api_configured()
if count < 1 or count > 20:
raise HTTPException(
status_code=400,
detail="count debe estar entre 1 y 20"
)
from app.services.batch_generator import batch_generator
category_list = None
if categories:
category_list = [c.strip() for c in categories.split(",")]
try:
tips = await batch_generator.generate_tips_batch(
count=count,
platform=platform,
categories=category_list
)
return {
"success": True,
"count": len(tips),
"tips": tips
}
except Exception as e:
return {
"success": False,
"error": str(e),
"tips": []
}
@router.get("/status")
async def get_ai_status():
"""Verificar estado de la API de IA."""
configured = bool(settings.DEEPSEEK_API_KEY)
result = {
"configured": configured,
"provider": "DeepSeek",
"model": "deepseek-chat",
"base_url": settings.DEEPSEEK_BASE_URL
}
if configured:
try:
# Test rápido
from openai import OpenAI
client = OpenAI(
api_key=settings.DEEPSEEK_API_KEY,
base_url=settings.DEEPSEEK_BASE_URL
)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[{"role": "user", "content": "test"}],
max_tokens=5
)
result["status"] = "connected"
result["test"] = "OK"
except Exception as e:
result["status"] = "error"
result["error"] = str(e)
else:
result["status"] = "not_configured"
return result