diff --git a/app/api/routes/posts.py b/app/api/routes/posts.py index c9b50fd..6703aa6 100644 --- a/app/api/routes/posts.py +++ b/app/api/routes/posts.py @@ -10,6 +10,7 @@ from pydantic import BaseModel from app.core.database import get_db from app.models.post import Post +from app.services.ai.platform_adapter import platform_adapter router = APIRouter() @@ -122,7 +123,14 @@ async def get_post(post_id: int, db: Session = Depends(get_db)): @router.post("/", response_model=PostResponse) async def create_post(post_data: PostCreate, db: Session = Depends(get_db)): - """Crear un nuevo post.""" + """Crear un nuevo post con auto-adaptación de contenido por plataforma.""" + + # Adaptar contenido para cada plataforma + adapted_content = await platform_adapter.adapt_for_all_platforms_smart( + post_data.content, + post_data.platforms + ) + post = Post( content=post_data.content, content_type=post_data.content_type, @@ -131,7 +139,12 @@ async def create_post(post_data: PostCreate, db: Session = Depends(get_db)): image_url=post_data.image_url, approval_required=post_data.approval_required, hashtags=post_data.hashtags, - status="pending_approval" if post_data.approval_required else "scheduled" + status="pending_approval" if post_data.approval_required else "scheduled", + # Contenido adaptado por plataforma + content_x=adapted_content.get("x"), + content_threads=adapted_content.get("threads"), + content_instagram=adapted_content.get("instagram"), + content_facebook=adapted_content.get("facebook"), ) db.add(post) @@ -143,12 +156,30 @@ async def create_post(post_data: PostCreate, db: Session = Depends(get_db)): @router.put("/{post_id}") async def update_post(post_id: int, post_data: PostUpdate, db: Session = Depends(get_db)): - """Actualizar un post.""" + """Actualizar un post con re-adaptación automática si cambia el contenido.""" post = db.query(Post).filter(Post.id == post_id).first() if not post: raise HTTPException(status_code=404, detail="Post no encontrado") update_data = post_data.dict(exclude_unset=True) + + # Si cambia el contenido principal, re-adaptar para todas las plataformas + if "content" in update_data: + platforms = update_data.get("platforms", post.platforms) + adapted_content = await platform_adapter.adapt_for_all_platforms_smart( + update_data["content"], + platforms + ) + # Solo actualizar campos de plataforma si no vienen explícitos en la request + if "content_x" not in update_data and "x" in platforms: + update_data["content_x"] = adapted_content.get("x") + if "content_threads" not in update_data and "threads" in platforms: + update_data["content_threads"] = adapted_content.get("threads") + if "content_instagram" not in update_data and "instagram" in platforms: + update_data["content_instagram"] = adapted_content.get("instagram") + if "content_facebook" not in update_data and "facebook" in platforms: + update_data["content_facebook"] = adapted_content.get("facebook") + for field, value in update_data.items(): setattr(post, field, value) diff --git a/app/services/ai/platform_adapter.py b/app/services/ai/platform_adapter.py index 8f1b003..8cbee07 100644 --- a/app/services/ai/platform_adapter.py +++ b/app/services/ai/platform_adapter.py @@ -369,6 +369,112 @@ Responde SOLO con el contenido adaptado, sin explicaciones.""" return prompt + # === Adaptación con IA === + + async def adapt_with_ai( + self, + content: str, + platform: str + ) -> AdaptedContent: + """ + Adaptar contenido usando IA si excede el límite. + + Si el contenido cabe en el límite, lo retorna tal cual. + Si excede, usa DeepSeek para condensar manteniendo el mensaje. + + Args: + content: Contenido a adaptar + platform: Plataforma destino + + Returns: + AdaptedContent con el contenido adaptado + """ + limits = self.get_limits(platform) + max_chars = limits.get("max_characters", 2000) + + # Si cabe, solo aplicar formato básico + if len(content) <= max_chars: + return AdaptedContent( + content=content, + platform=platform, + original_content=content, + truncated=False, + hashtags_adjusted=False, + changes_made=[] + ) + + # Excede límite: usar IA para condensar + from openai import OpenAI + from app.core.config import settings + + if not settings.DEEPSEEK_API_KEY: + # Fallback a adaptación por reglas si no hay API + return self.adapt(content, platform) + + client = OpenAI( + api_key=settings.DEEPSEEK_API_KEY, + base_url=settings.DEEPSEEK_BASE_URL or "https://api.deepseek.com" + ) + + # Dejar margen de 10 chars para seguridad + target_chars = max_chars - 10 + + prompt = f"""Condensa este contenido a máximo {target_chars} caracteres para {platform}. + +CONTENIDO ORIGINAL: +{content} + +REGLAS: +- Máximo {target_chars} caracteres (incluyendo hashtags y espacios) +- Mantén la esencia y mensaje principal +- Conserva el call-to-action si existe +- Incluye hashtags relevantes (máximo {limits.get('max_hashtags', 2)}) +- NO uses emojis a menos que estén en el original +- Tono profesional + +Responde SOLO con el contenido adaptado, sin explicaciones.""" + + response = client.chat.completions.create( + model="deepseek-chat", + messages=[{"role": "user", "content": prompt}], + temperature=0.7, + max_tokens=500 + ) + + adapted_content = response.choices[0].message.content.strip() + + return AdaptedContent( + content=adapted_content, + platform=platform, + original_content=content, + truncated=True, + hashtags_adjusted=False, + changes_made=[f"Contenido adaptado con IA: {len(content)} → {len(adapted_content)} caracteres"] + ) + + async def adapt_for_all_platforms_smart( + self, + content: str, + platforms: List[str] + ) -> Dict[str, str]: + """ + Adaptar contenido para múltiples plataformas usando IA cuando sea necesario. + + Args: + content: Contenido base + platforms: Lista de plataformas + + Returns: + Dict de plataforma -> contenido adaptado + """ + results = {} + + for platform in platforms: + adapted = await self.adapt_with_ai(content, platform) + results[platform] = adapted.content + + return results + # Instancia global platform_adapter = PlatformAdapter()