- Redesign Settings: Testi, Immagini, Video, Voiceover — sezioni separate - Ogni sezione ha dropdown provider + API key + campo opzionale modello - Opzione "Personalizzato" con campo Base URL libero per qualsiasi servizio - LLM: aggiunto OpenRouter + provider custom OpenAI-compatible - Backend: OpenAICompatibleProvider unifica OpenAI/OpenRouter/custom - Router content: passa llm_base_url a get_llm_provider Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
227 lines
7.5 KiB
Python
227 lines
7.5 KiB
Python
"""Content generation router.
|
|
|
|
Handles post generation via LLM, image generation, and CRUD operations on posts.
|
|
"""
|
|
|
|
from datetime import datetime
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.orm import Session
|
|
|
|
from ..auth import get_current_user
|
|
from ..database import get_db
|
|
from ..models import AffiliateLink, Character, Post, SystemSetting
|
|
from ..schemas import (
|
|
GenerateContentRequest,
|
|
GenerateImageRequest,
|
|
PostResponse,
|
|
PostUpdate,
|
|
)
|
|
from ..services.content import generate_hashtags, generate_post_text, inject_affiliate_links
|
|
from ..services.images import get_image_provider
|
|
from ..services.llm import get_llm_provider
|
|
|
|
router = APIRouter(
|
|
prefix="/api/content",
|
|
tags=["content"],
|
|
dependencies=[Depends(get_current_user)],
|
|
)
|
|
|
|
|
|
def _get_setting(db: Session, key: str) -> str | None:
|
|
"""Retrieve a system setting value by key."""
|
|
setting = db.query(SystemSetting).filter(SystemSetting.key == key).first()
|
|
if setting is None:
|
|
return None
|
|
return setting.value
|
|
|
|
|
|
@router.post("/generate", response_model=PostResponse)
|
|
def generate_content(request: GenerateContentRequest, db: Session = Depends(get_db)):
|
|
"""Generate content for a character using LLM."""
|
|
# Validate character exists
|
|
character = db.query(Character).filter(Character.id == request.character_id).first()
|
|
if not character:
|
|
raise HTTPException(status_code=404, detail="Character not found")
|
|
|
|
# Get LLM settings
|
|
provider_name = request.provider or _get_setting(db, "llm_provider")
|
|
api_key = _get_setting(db, "llm_api_key")
|
|
model = request.model or _get_setting(db, "llm_model")
|
|
|
|
if not provider_name:
|
|
raise HTTPException(status_code=400, detail="LLM provider not configured. Set 'llm_provider' in settings.")
|
|
if not api_key:
|
|
raise HTTPException(status_code=400, detail="LLM API key not configured. Set 'llm_api_key' in settings.")
|
|
|
|
# Build character dict for content service
|
|
char_dict = {
|
|
"name": character.name,
|
|
"niche": character.niche,
|
|
"topics": character.topics or [],
|
|
"tone": character.tone or "professional",
|
|
}
|
|
|
|
# Create LLM provider and generate text
|
|
base_url = _get_setting(db, "llm_base_url")
|
|
llm = get_llm_provider(provider_name, api_key, model, base_url=base_url)
|
|
text = generate_post_text(
|
|
character=char_dict,
|
|
llm_provider=llm,
|
|
platform=request.platform,
|
|
topic_hint=request.topic_hint,
|
|
)
|
|
|
|
# Generate hashtags
|
|
hashtags = generate_hashtags(text, llm, request.platform)
|
|
|
|
# Handle affiliate links
|
|
affiliate_links_used: list[dict] = []
|
|
if request.include_affiliates:
|
|
links = (
|
|
db.query(AffiliateLink)
|
|
.filter(
|
|
AffiliateLink.is_active == True,
|
|
(AffiliateLink.character_id == character.id) | (AffiliateLink.character_id == None),
|
|
)
|
|
.all()
|
|
)
|
|
if links:
|
|
link_dicts = [
|
|
{
|
|
"url": link.url,
|
|
"label": link.name,
|
|
"keywords": link.topics or [],
|
|
}
|
|
for link in links
|
|
]
|
|
text, affiliate_links_used = inject_affiliate_links(
|
|
text, link_dicts, character.topics or []
|
|
)
|
|
|
|
# Create post record
|
|
post = Post(
|
|
character_id=character.id,
|
|
content_type=request.content_type,
|
|
text_content=text,
|
|
hashtags=hashtags,
|
|
affiliate_links_used=affiliate_links_used,
|
|
llm_provider=provider_name,
|
|
llm_model=model,
|
|
platform_hint=request.platform,
|
|
status="draft",
|
|
)
|
|
db.add(post)
|
|
db.commit()
|
|
db.refresh(post)
|
|
return post
|
|
|
|
|
|
@router.post("/generate-image", response_model=PostResponse)
|
|
def generate_image(request: GenerateImageRequest, db: Session = Depends(get_db)):
|
|
"""Generate an image for a character and attach to a post."""
|
|
# Validate character exists
|
|
character = db.query(Character).filter(Character.id == request.character_id).first()
|
|
if not character:
|
|
raise HTTPException(status_code=404, detail="Character not found")
|
|
|
|
# Get image settings
|
|
provider_name = request.provider or _get_setting(db, "image_provider")
|
|
api_key = _get_setting(db, "image_api_key")
|
|
|
|
if not provider_name:
|
|
raise HTTPException(status_code=400, detail="Image provider not configured. Set 'image_provider' in settings.")
|
|
if not api_key:
|
|
raise HTTPException(status_code=400, detail="Image API key not configured. Set 'image_api_key' in settings.")
|
|
|
|
# Build prompt from character if not provided
|
|
prompt = request.prompt
|
|
if not prompt:
|
|
style_hint = request.style_hint or ""
|
|
visual_style = character.visual_style or {}
|
|
style_desc = visual_style.get("description", "")
|
|
prompt = (
|
|
f"Create a social media image for {character.name}, "
|
|
f"a content creator in the {character.niche} niche. "
|
|
f"Style: {style_desc} {style_hint}".strip()
|
|
)
|
|
|
|
# Generate image
|
|
image_provider = get_image_provider(provider_name, api_key)
|
|
image_url = image_provider.generate(prompt, size=request.size)
|
|
|
|
# Create a new post with the image
|
|
post = Post(
|
|
character_id=character.id,
|
|
content_type="image",
|
|
image_url=image_url,
|
|
platform_hint="instagram",
|
|
status="draft",
|
|
)
|
|
db.add(post)
|
|
db.commit()
|
|
db.refresh(post)
|
|
return post
|
|
|
|
|
|
@router.get("/posts", response_model=list[PostResponse])
|
|
def list_posts(
|
|
character_id: int | None = Query(None),
|
|
status: str | None = Query(None),
|
|
db: Session = Depends(get_db),
|
|
):
|
|
"""List all posts with optional filters."""
|
|
query = db.query(Post)
|
|
if character_id is not None:
|
|
query = query.filter(Post.character_id == character_id)
|
|
if status is not None:
|
|
query = query.filter(Post.status == status)
|
|
return query.order_by(Post.created_at.desc()).all()
|
|
|
|
|
|
@router.get("/posts/{post_id}", response_model=PostResponse)
|
|
def get_post(post_id: int, db: Session = Depends(get_db)):
|
|
"""Get a single post by ID."""
|
|
post = db.query(Post).filter(Post.id == post_id).first()
|
|
if not post:
|
|
raise HTTPException(status_code=404, detail="Post not found")
|
|
return post
|
|
|
|
|
|
@router.put("/posts/{post_id}", response_model=PostResponse)
|
|
def update_post(post_id: int, data: PostUpdate, db: Session = Depends(get_db)):
|
|
"""Update a post."""
|
|
post = db.query(Post).filter(Post.id == post_id).first()
|
|
if not post:
|
|
raise HTTPException(status_code=404, detail="Post not found")
|
|
update_data = data.model_dump(exclude_unset=True)
|
|
for key, value in update_data.items():
|
|
setattr(post, key, value)
|
|
post.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(post)
|
|
return post
|
|
|
|
|
|
@router.delete("/posts/{post_id}", status_code=204)
|
|
def delete_post(post_id: int, db: Session = Depends(get_db)):
|
|
"""Delete a post."""
|
|
post = db.query(Post).filter(Post.id == post_id).first()
|
|
if not post:
|
|
raise HTTPException(status_code=404, detail="Post not found")
|
|
db.delete(post)
|
|
db.commit()
|
|
|
|
|
|
@router.post("/posts/{post_id}/approve", response_model=PostResponse)
|
|
def approve_post(post_id: int, db: Session = Depends(get_db)):
|
|
"""Approve a post (set status to 'approved')."""
|
|
post = db.query(Post).filter(Post.id == post_id).first()
|
|
if not post:
|
|
raise HTTPException(status_code=404, detail="Post not found")
|
|
post.status = "approved"
|
|
post.updated_at = datetime.utcnow()
|
|
db.commit()
|
|
db.refresh(post)
|
|
return post
|