Implement persona generation system with platform-specific adaptations

Co-authored-by: ajay.calsoft <ajay.calsoft@gmail.com>
This commit is contained in:
Cursor Agent
2025-08-31 08:26:51 +00:00
parent 1e0a13e204
commit 7dbebd45eb
19 changed files with 4417 additions and 2 deletions

385
backend/api/persona.py Normal file
View File

@@ -0,0 +1,385 @@
"""
Persona API endpoints for ALwrity.
Handles writing persona generation, management, and platform-specific adaptations.
"""
from fastapi import HTTPException, Depends
from pydantic import BaseModel, Field
from typing import Dict, Any, List, Optional
from datetime import datetime
from loguru import logger
from services.persona_analysis_service import PersonaAnalysisService
from services.database import get_db
class PersonaGenerationRequest(BaseModel):
"""Request model for persona generation."""
onboarding_session_id: Optional[int] = Field(None, description="Specific onboarding session ID to use")
force_regenerate: bool = Field(False, description="Force regeneration even if persona exists")
class PersonaResponse(BaseModel):
"""Response model for persona data."""
persona_id: int
persona_name: str
archetype: str
core_belief: str
confidence_score: float
platforms: List[str]
created_at: str
class PlatformPersonaResponse(BaseModel):
"""Response model for platform-specific persona."""
platform_type: str
sentence_metrics: Dict[str, Any]
lexical_features: Dict[str, Any]
content_format_rules: Dict[str, Any]
engagement_patterns: Dict[str, Any]
platform_best_practices: Dict[str, Any]
class PersonaGenerationResponse(BaseModel):
"""Response model for persona generation result."""
success: bool
persona_id: Optional[int] = None
message: str
confidence_score: Optional[float] = None
data_sufficiency: Optional[float] = None
platforms_generated: List[str] = []
# Dependency to get persona service
def get_persona_service() -> PersonaAnalysisService:
"""Get the persona analysis service instance."""
return PersonaAnalysisService()
async def generate_persona(user_id: int, request: PersonaGenerationRequest):
"""Generate a new writing persona from onboarding data."""
try:
logger.info(f"Generating persona for user {user_id}")
persona_service = get_persona_service()
# Check if persona already exists and force_regenerate is False
if not request.force_regenerate:
existing_personas = persona_service.get_user_personas(user_id)
if existing_personas:
return PersonaGenerationResponse(
success=False,
message="Persona already exists. Use force_regenerate=true to create a new one.",
persona_id=existing_personas[0]["id"]
)
# Generate new persona
result = persona_service.generate_persona_from_onboarding(
user_id=user_id,
onboarding_session_id=request.onboarding_session_id
)
if "error" in result:
return PersonaGenerationResponse(
success=False,
message=result["error"]
)
return PersonaGenerationResponse(
success=True,
persona_id=result["persona_id"],
message="Persona generated successfully",
confidence_score=result["analysis_metadata"]["confidence_score"],
data_sufficiency=result["analysis_metadata"].get("data_sufficiency", 0.0),
platforms_generated=list(result["platform_personas"].keys())
)
except Exception as e:
logger.error(f"Error generating persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to generate persona: {str(e)}")
async def get_user_personas(user_id: int):
"""Get all personas for a user."""
try:
persona_service = get_persona_service()
personas = persona_service.get_user_personas(user_id)
return {
"personas": personas,
"total_count": len(personas)
}
except Exception as e:
logger.error(f"Error getting user personas: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get personas: {str(e)}")
async def get_persona_details(user_id: int, persona_id: int):
"""Get detailed information about a specific persona."""
try:
from services.database import get_db_session
from models.persona_models import WritingPersona, PlatformPersona
session = get_db_session()
# Get persona
persona = session.query(WritingPersona).filter(
WritingPersona.id == persona_id,
WritingPersona.user_id == user_id,
WritingPersona.is_active == True
).first()
if not persona:
raise HTTPException(status_code=404, detail="Persona not found")
# Get platform adaptations
platform_personas = session.query(PlatformPersona).filter(
PlatformPersona.writing_persona_id == persona_id,
PlatformPersona.is_active == True
).all()
result = persona.to_dict()
result["platform_adaptations"] = [pp.to_dict() for pp in platform_personas]
session.close()
return result
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting persona details: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get persona details: {str(e)}")
async def get_platform_persona(user_id: int, platform: str):
"""Get persona adaptation for a specific platform."""
try:
persona_service = get_persona_service()
platform_persona = persona_service.get_persona_for_platform(user_id, platform)
if not platform_persona:
raise HTTPException(status_code=404, detail=f"No persona found for platform {platform}")
return platform_persona
except HTTPException:
raise
except Exception as e:
logger.error(f"Error getting platform persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get platform persona: {str(e)}")
async def update_persona(user_id: int, persona_id: int, update_data: Dict[str, Any]):
"""Update an existing persona."""
try:
from services.database import get_db_session
from models.persona_models import WritingPersona
session = get_db_session()
persona = session.query(WritingPersona).filter(
WritingPersona.id == persona_id,
WritingPersona.user_id == user_id
).first()
if not persona:
raise HTTPException(status_code=404, detail="Persona not found")
# Update allowed fields
updatable_fields = [
'persona_name', 'archetype', 'core_belief', 'brand_voice_description',
'linguistic_fingerprint', 'platform_adaptations'
]
for field in updatable_fields:
if field in update_data:
setattr(persona, field, update_data[field])
persona.updated_at = datetime.utcnow()
session.commit()
session.close()
return {
"message": "Persona updated successfully",
"persona_id": persona_id,
"updated_at": persona.updated_at.isoformat()
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error updating persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to update persona: {str(e)}")
async def delete_persona(user_id: int, persona_id: int):
"""Delete a persona (soft delete by setting is_active=False)."""
try:
from services.database import get_db_session
from models.persona_models import WritingPersona, PlatformPersona
session = get_db_session()
persona = session.query(WritingPersona).filter(
WritingPersona.id == persona_id,
WritingPersona.user_id == user_id
).first()
if not persona:
raise HTTPException(status_code=404, detail="Persona not found")
# Soft delete persona and platform adaptations
persona.is_active = False
persona.updated_at = datetime.utcnow()
platform_personas = session.query(PlatformPersona).filter(
PlatformPersona.writing_persona_id == persona_id
).all()
for pp in platform_personas:
pp.is_active = False
pp.updated_at = datetime.utcnow()
session.commit()
session.close()
return {
"message": "Persona deleted successfully",
"persona_id": persona_id
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error deleting persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to delete persona: {str(e)}")
async def validate_persona_generation_readiness(user_id: int):
"""Check if user has sufficient onboarding data for persona generation."""
try:
persona_service = get_persona_service()
# Get onboarding data
onboarding_data = persona_service._collect_onboarding_data(user_id)
if not onboarding_data:
return {
"ready": False,
"message": "No onboarding data found. Please complete onboarding first.",
"missing_steps": ["All onboarding steps"],
"data_sufficiency": 0.0
}
data_sufficiency = persona_service._calculate_data_sufficiency(onboarding_data)
missing_steps = []
if not onboarding_data.get("website_analysis"):
missing_steps.append("Website Analysis (Step 2)")
if not onboarding_data.get("research_preferences"):
missing_steps.append("Research Preferences (Step 3)")
ready = data_sufficiency >= 50.0 # Require at least 50% data sufficiency
return {
"ready": ready,
"message": "Ready for persona generation" if ready else "Insufficient data for reliable persona generation",
"missing_steps": missing_steps,
"data_sufficiency": data_sufficiency,
"recommendations": [
"Complete website analysis for better style detection",
"Provide research preferences for content type optimization"
] if not ready else []
}
except Exception as e:
logger.error(f"Error validating persona generation readiness: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to validate readiness: {str(e)}")
async def generate_persona_preview(user_id: int):
"""Generate a preview of what the persona would look like without saving."""
try:
persona_service = get_persona_service()
# Get onboarding data
onboarding_data = persona_service._collect_onboarding_data(user_id)
if not onboarding_data:
raise HTTPException(status_code=400, detail="No onboarding data available")
# Generate core persona (without saving)
core_persona = persona_service._generate_core_persona(onboarding_data)
if "error" in core_persona:
raise HTTPException(status_code=400, detail=core_persona["error"])
# Generate sample platform adaptation (just one for preview)
sample_platform = "linkedin"
platform_preview = persona_service._generate_single_platform_persona(
core_persona, sample_platform, onboarding_data
)
return {
"preview": {
"identity": core_persona.get("identity", {}),
"linguistic_fingerprint": core_persona.get("linguistic_fingerprint", {}),
"tonal_range": core_persona.get("tonal_range", {}),
"sample_platform": {
"platform": sample_platform,
"adaptation": platform_preview
}
},
"confidence_score": core_persona.get("confidence_score", 0.0),
"data_sufficiency": persona_service._calculate_data_sufficiency(onboarding_data)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error generating persona preview: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to generate preview: {str(e)}")
async def get_supported_platforms():
"""Get list of supported platforms for persona generation."""
return {
"platforms": [
{
"id": "twitter",
"name": "Twitter/X",
"description": "Microblogging platform optimized for short, engaging content",
"character_limit": 280,
"optimal_length": "120-150 characters"
},
{
"id": "linkedin",
"name": "LinkedIn",
"description": "Professional networking platform for thought leadership content",
"character_limit": 3000,
"optimal_length": "150-300 words"
},
{
"id": "instagram",
"name": "Instagram",
"description": "Visual-first platform with engaging captions",
"character_limit": 2200,
"optimal_length": "125-150 words"
},
{
"id": "facebook",
"name": "Facebook",
"description": "Social networking platform for community engagement",
"character_limit": 63206,
"optimal_length": "40-80 words"
},
{
"id": "blog",
"name": "Blog Posts",
"description": "Long-form content optimized for SEO and engagement",
"word_count": "800-2000 words",
"seo_optimized": True
},
{
"id": "medium",
"name": "Medium",
"description": "Publishing platform for storytelling and thought leadership",
"word_count": "1000-3000 words",
"storytelling_focus": True
},
{
"id": "substack",
"name": "Substack",
"description": "Newsletter platform for building subscriber relationships",
"format": "email newsletter",
"subscription_focus": True
}
]
}