LinkedIn and Facebook Persona Services Implementation

This commit is contained in:
ajaysi
2025-10-26 10:06:24 +05:30
parent caeb6e56a9
commit 5866f49325
15 changed files with 868 additions and 281 deletions

View File

@@ -8,6 +8,7 @@ from pydantic import BaseModel, Field
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
from datetime import datetime from datetime import datetime
from loguru import logger from loguru import logger
from sqlalchemy.orm import Session
from services.persona_analysis_service import PersonaAnalysisService from services.persona_analysis_service import PersonaAnalysisService
from services.database import get_db from services.database import get_db
@@ -110,50 +111,45 @@ async def generate_persona(user_id: int, request: PersonaGenerationRequest):
logger.error(f"Error generating persona: {str(e)}") logger.error(f"Error generating persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to generate persona: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to generate persona: {str(e)}")
async def get_user_personas(user_id: int): async def get_user_personas(user_id: str):
"""Get all personas for a user.""" """Get all personas for a user using PersonaData."""
try: try:
persona_service = get_persona_service() from services.persona_data_service import PersonaDataService
personas = persona_service.get_user_personas(user_id)
persona_service = PersonaDataService()
all_personas = persona_service.get_all_platform_personas(user_id)
return { return {
"personas": personas, "personas": all_personas,
"total_count": len(personas) "total_count": len(all_personas),
"platforms": list(all_personas.keys())
} }
except Exception as e: except Exception as e:
logger.error(f"Error getting user personas: {str(e)}") logger.error(f"Error getting user personas: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get 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): async def get_persona_details(user_id: str, persona_id: int):
"""Get detailed information about a specific persona.""" """Get detailed information about a specific persona using PersonaData."""
try: try:
from services.database import get_db_session from services.persona_data_service import PersonaDataService
from models.persona_models import WritingPersona, PlatformPersona
session = get_db_session() persona_service = PersonaDataService()
persona_data = persona_service.get_user_persona_data(user_id)
# Get persona if not persona_data:
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") raise HTTPException(status_code=404, detail="Persona not found")
# Get platform adaptations # Return the complete persona data with all platforms
platform_personas = session.query(PlatformPersona).filter( return {
PlatformPersona.writing_persona_id == persona_id, "persona_id": persona_data.get('id'),
PlatformPersona.is_active == True "core_persona": persona_data.get('core_persona', {}),
).all() "platform_personas": persona_data.get('platform_personas', {}),
"quality_metrics": persona_data.get('quality_metrics', {}),
result = persona.to_dict() "selected_platforms": persona_data.get('selected_platforms', []),
result["platform_adaptations"] = [pp.to_dict() for pp in platform_personas] "created_at": persona_data.get('created_at'),
"updated_at": persona_data.get('updated_at')
session.close() }
return result
except HTTPException: except HTTPException:
raise raise
@@ -161,11 +157,13 @@ async def get_persona_details(user_id: int, persona_id: int):
logger.error(f"Error getting persona details: {str(e)}") logger.error(f"Error getting persona details: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get 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): async def get_platform_persona(user_id: str, platform: str):
"""Get persona adaptation for a specific platform.""" """Get persona adaptation for a specific platform using PersonaData."""
try: try:
persona_service = get_persona_service() from services.persona_data_service import PersonaDataService
platform_persona = persona_service.get_persona_for_platform(user_id, platform)
persona_service = PersonaDataService()
platform_persona = persona_service.get_platform_persona(user_id, platform)
if not platform_persona: if not platform_persona:
raise HTTPException(status_code=404, detail=f"No persona found for platform {platform}") raise HTTPException(status_code=404, detail=f"No persona found for platform {platform}")
@@ -178,41 +176,52 @@ async def get_platform_persona(user_id: int, platform: str):
logger.error(f"Error getting platform persona: {str(e)}") logger.error(f"Error getting platform persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get 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]): async def get_persona_summary(user_id: str):
"""Update an existing persona.""" """Get persona summary for a user using PersonaData."""
try: try:
from services.database import get_db_session from services.persona_data_service import PersonaDataService
from models.persona_models import WritingPersona
session = get_db_session() persona_service = PersonaDataService()
summary = persona_service.get_persona_summary(user_id)
persona = session.query(WritingPersona).filter( return summary
WritingPersona.id == persona_id,
WritingPersona.user_id == user_id
).first()
if not persona: except Exception as e:
raise HTTPException(status_code=404, detail="Persona not found") logger.error(f"Error getting persona summary: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to get persona summary: {str(e)}")
async def update_persona(user_id: str, persona_id: int, update_data: Dict[str, Any]):
"""Update an existing persona using PersonaData."""
try:
from services.persona_data_service import PersonaDataService
from models.onboarding import PersonaData
# Update allowed fields persona_service = PersonaDataService()
updatable_fields = [
'persona_name', 'archetype', 'core_belief', 'brand_voice_description',
'linguistic_fingerprint', 'platform_adaptations'
]
for field in updatable_fields: # For PersonaData, we update the core_persona field
if field in update_data: if 'core_persona' in update_data:
setattr(persona, field, update_data[field]) # Get current persona data
persona_data = persona_service.get_user_persona_data(user_id)
persona.updated_at = datetime.utcnow() if not persona_data:
session.commit() raise HTTPException(status_code=404, detail="Persona not found")
session.close()
# Update core persona with new data
return { persona_service.db.query(PersonaData).filter(
"message": "Persona updated successfully", PersonaData.id == persona_data.get('id')
"persona_id": persona_id, ).update({
"updated_at": persona.updated_at.isoformat() 'core_persona': update_data['core_persona'],
} 'updated_at': datetime.utcnow()
})
persona_service.db.commit()
persona_service.db.close()
return {
"message": "Persona updated successfully",
"persona_id": persona_data.get('id'),
"updated_at": datetime.utcnow().isoformat()
}
else:
raise HTTPException(status_code=400, detail="core_persona field is required for updates")
except HTTPException: except HTTPException:
raise raise
@@ -220,40 +229,28 @@ async def update_persona(user_id: int, persona_id: int, update_data: Dict[str, A
logger.error(f"Error updating persona: {str(e)}") logger.error(f"Error updating persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to update 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): async def delete_persona(user_id: str, persona_id: int):
"""Delete a persona (soft delete by setting is_active=False).""" """Delete a persona using PersonaData (not recommended, personas are generated during onboarding)."""
try: try:
from services.database import get_db_session from services.persona_data_service import PersonaDataService
from models.persona_models import WritingPersona, PlatformPersona from models.onboarding import PersonaData
session = get_db_session() persona_service = PersonaDataService()
persona = session.query(WritingPersona).filter( # Get persona data
WritingPersona.id == persona_id, persona_data = persona_service.get_user_persona_data(user_id)
WritingPersona.user_id == user_id if not persona_data:
).first()
if not persona:
raise HTTPException(status_code=404, detail="Persona not found") raise HTTPException(status_code=404, detail="Persona not found")
# Soft delete persona and platform adaptations # For PersonaData, we mark it as deleted by setting a flag
persona.is_active = False # Note: In production, you might want to add a deleted_at field or similar
persona.updated_at = datetime.utcnow() # For now, we'll just return a warning that deletion is not recommended
logger.warning(f"Delete persona requested for user {user_id}. PersonaData deletion is not recommended.")
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 { return {
"message": "Persona deleted successfully", "message": "Persona deletion requested. Note: Personas are generated during onboarding and deletion is not recommended.",
"persona_id": persona_id "persona_id": persona_data.get('id'),
"alternative": "Consider re-running onboarding to regenerate persona if needed."
} }
except HTTPException: except HTTPException:
@@ -262,67 +259,24 @@ async def delete_persona(user_id: int, persona_id: int):
logger.error(f"Error deleting persona: {str(e)}") logger.error(f"Error deleting persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to delete persona: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to delete persona: {str(e)}")
async def update_platform_persona(user_id: int, platform: str, update_data: Dict[str, Any]): async def update_platform_persona(user_id: str, platform: str, update_data: Dict[str, Any]):
"""Update platform-specific persona fields for a user's persona. """Update platform-specific persona fields using PersonaData."""
This updates the underlying PlatformPersona row for the given platform.
"""
try: try:
from services.database import get_db_session from services.persona_data_service import PersonaDataService
from models.persona_models import WritingPersona, PlatformPersona
session = get_db_session() persona_service = PersonaDataService()
# Find the user's active core persona id # Update platform-specific persona data
core_persona = session.query(WritingPersona).filter( success = persona_service.update_platform_persona(user_id, platform, update_data)
WritingPersona.user_id == user_id,
WritingPersona.is_active == True if not success:
).order_by(WritingPersona.created_at.desc()).first()
if not core_persona:
raise HTTPException(status_code=404, detail="No active persona found for user")
# Find the platform persona for the requested platform
platform_persona = session.query(PlatformPersona).filter(
PlatformPersona.writing_persona_id == core_persona.id,
PlatformPersona.platform_type.ilike(platform),
PlatformPersona.is_active == True
).first()
if not platform_persona:
raise HTTPException(status_code=404, detail=f"No platform persona found for platform {platform}") raise HTTPException(status_code=404, detail=f"No platform persona found for platform {platform}")
# Update allowed platform fields
updatable_fields = [
'sentence_metrics', 'lexical_features', 'rhetorical_devices', 'tonal_range',
'stylistic_constraints', 'content_format_rules', 'engagement_patterns',
'posting_frequency', 'content_types', 'platform_best_practices', 'algorithm_considerations'
]
updated_any = False
for field in updatable_fields:
if field in update_data:
setattr(platform_persona, field, update_data[field])
updated_any = True
if not updated_any:
# Nothing to update
session.close()
return {
"message": "No updatable fields provided",
"platform": platform_persona.platform_type,
"persona_id": core_persona.id
}
platform_persona.updated_at = datetime.utcnow()
session.commit()
session.close()
return { return {
"message": "Platform persona updated successfully", "message": "Platform persona updated successfully",
"platform": platform, "platform": platform,
"persona_id": core_persona.id, "user_id": user_id,
"updated_at": platform_persona.updated_at.isoformat() "updated_at": datetime.utcnow().isoformat()
} }
except HTTPException: except HTTPException:
@@ -331,6 +285,101 @@ async def update_platform_persona(user_id: int, platform: str, update_data: Dict
logger.error(f"Error updating platform persona: {str(e)}") logger.error(f"Error updating platform persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to update platform persona: {str(e)}") raise HTTPException(status_code=500, detail=f"Failed to update platform persona: {str(e)}")
async def generate_platform_persona(user_id: str, platform: str, db_session):
"""
Generate a platform-specific persona from core persona and save it.
Args:
user_id: User ID from auth
platform: Platform name (facebook, linkedin, etc.)
db_session: Database session from FastAPI dependency injection
Returns:
Generated platform persona with validation results
"""
try:
logger.info(f"Generating {platform} persona for user {user_id}")
# Import services
from services.persona_data_service import PersonaDataService
from services.onboarding_database_service import OnboardingDatabaseService
persona_data_service = PersonaDataService(db_session=db_session)
onboarding_service = OnboardingDatabaseService(db=db_session)
# Get core persona data
persona_data = persona_data_service.get_user_persona_data(user_id)
if not persona_data:
raise HTTPException(status_code=404, detail="Core persona not found")
core_persona = persona_data.get('core_persona', {})
if not core_persona:
raise HTTPException(status_code=404, detail="Core persona data is empty")
# Get onboarding data for context
onboarding_session = onboarding_service.get_session_by_user(user_id)
if not onboarding_session:
raise HTTPException(status_code=404, detail="Onboarding session not found")
# Get website analysis for context
website_analysis = onboarding_service.get_website_analysis(user_id)
research_prefs = onboarding_service.get_research_preferences(user_id)
onboarding_data = {
"website_url": website_analysis.get('website_url', '') if website_analysis else '',
"writing_style": website_analysis.get('writing_style', {}) if website_analysis else {},
"content_characteristics": website_analysis.get('content_characteristics', {}) if website_analysis else {},
"target_audience": website_analysis.get('target_audience', '') if website_analysis else '',
"research_preferences": research_prefs or {}
}
# Generate platform persona based on platform
generated_persona = None
platform_service = None
if platform.lower() == 'facebook':
from services.persona.facebook.facebook_persona_service import FacebookPersonaService
platform_service = FacebookPersonaService()
generated_persona = platform_service.generate_facebook_persona(
core_persona,
onboarding_data
)
elif platform.lower() == 'linkedin':
from services.persona.linkedin.linkedin_persona_service import LinkedInPersonaService
platform_service = LinkedInPersonaService()
generated_persona = platform_service.generate_linkedin_persona(
core_persona,
onboarding_data
)
else:
raise HTTPException(status_code=400, detail=f"Unsupported platform: {platform}")
# Check for errors in generation
if "error" in generated_persona:
raise HTTPException(status_code=500, detail=generated_persona["error"])
# Save the generated platform persona to database
success = persona_data_service.save_platform_persona(user_id, platform, generated_persona)
if not success:
raise HTTPException(status_code=500, detail=f"Failed to save {platform} persona")
logger.info(f"✅ Successfully generated and saved {platform} persona for user {user_id}")
return {
"success": True,
"platform": platform,
"persona": generated_persona,
"validation_results": generated_persona.get("validation_results", {}),
"quality_score": generated_persona.get("validation_results", {}).get("quality_score", 0)
}
except HTTPException:
raise
except Exception as e:
logger.error(f"Error generating {platform} persona: {str(e)}")
raise HTTPException(status_code=500, detail=f"Failed to generate {platform} persona: {str(e)}")
async def validate_persona_generation_readiness(user_id: int): async def validate_persona_generation_readiness(user_id: int):
"""Check if user has sufficient onboarding data for persona generation.""" """Check if user has sufficient onboarding data for persona generation."""
try: try:

View File

@@ -3,14 +3,18 @@ FastAPI routes for persona management.
Integrates persona generation and management into the main API. Integrates persona generation and management into the main API.
""" """
from fastapi import APIRouter, HTTPException, Query from fastapi import APIRouter, HTTPException, Query, Depends
from typing import Dict, Any, Optional from typing import Dict, Any, Optional
from sqlalchemy.orm import Session
from middleware.auth_middleware import get_current_user
from services.database import get_db
from api.persona import ( from api.persona import (
generate_persona, generate_persona,
get_user_personas, get_user_personas,
get_persona_details, get_persona_details,
get_platform_persona, get_platform_persona,
get_persona_summary,
update_persona, update_persona,
delete_persona, delete_persona,
validate_persona_generation_readiness, validate_persona_generation_readiness,
@@ -32,7 +36,7 @@ from api.persona import (
) )
from services.persona_replication_engine import PersonaReplicationEngine from services.persona_replication_engine import PersonaReplicationEngine
from api.persona import update_platform_persona from api.persona import update_platform_persona, generate_platform_persona
# Create router # Create router
router = APIRouter(prefix="/api/personas", tags=["personas"]) router = APIRouter(prefix="/api/personas", tags=["personas"])
@@ -45,29 +49,45 @@ async def generate_persona_endpoint(
"""Generate a new writing persona from onboarding data.""" """Generate a new writing persona from onboarding data."""
return await generate_persona(user_id, request) return await generate_persona(user_id, request)
@router.get("/user/{user_id}") @router.get("/user")
async def get_user_personas_endpoint(user_id: int): async def get_user_personas_endpoint(current_user: Dict[str, Any] = Depends(get_current_user)):
"""Get all personas for a user.""" """Get all personas for the current user."""
# Beta testing: Force user_id=1 for all requests user_id = str(current_user.get('id'))
return await get_user_personas(1) return await get_user_personas(user_id)
@router.get("/summary")
async def get_persona_summary_endpoint(current_user: Dict[str, Any] = Depends(get_current_user)):
"""Get persona summary for the current user."""
user_id = str(current_user.get('id'))
return await get_persona_summary(user_id)
@router.get("/{persona_id}") @router.get("/{persona_id}")
async def get_persona_details_endpoint( async def get_persona_details_endpoint(
persona_id: int, persona_id: int,
user_id: int = Query(..., description="User ID") current_user: Dict[str, Any] = Depends(get_current_user)
): ):
"""Get detailed information about a specific persona.""" """Get detailed information about a specific persona."""
# Beta testing: Force user_id=1 for all requests user_id = str(current_user.get('id'))
return await get_persona_details(1, persona_id) return await get_persona_details(user_id, persona_id)
@router.get("/platform/{platform}") @router.get("/platform/{platform}")
async def get_platform_persona_endpoint( async def get_platform_persona_endpoint(
platform: str, platform: str,
user_id: int = Query(1, description="User ID") current_user: Dict[str, Any] = Depends(get_current_user)
): ):
"""Get persona adaptation for a specific platform.""" """Get persona adaptation for a specific platform."""
# Beta testing: Force user_id=1 for all requests user_id = str(current_user.get('id'))
return await get_platform_persona(1, platform) return await get_platform_persona(user_id, platform)
@router.post("/generate-platform/{platform}")
async def generate_platform_persona_endpoint(
platform: str,
current_user: Dict[str, Any] = Depends(get_current_user),
db: Session = Depends(get_db)
):
"""Generate a platform-specific persona from core persona."""
user_id = str(current_user.get('id'))
return await generate_platform_persona(user_id, platform, db)
@router.put("/{persona_id}") @router.put("/{persona_id}")
async def update_persona_endpoint( async def update_persona_endpoint(

View File

@@ -18,7 +18,7 @@ class WritingPersona(Base):
# Primary fields # Primary fields
id = Column(Integer, primary_key=True) id = Column(Integer, primary_key=True)
user_id = Column(Integer, nullable=False) user_id = Column(String(255), nullable=False) # Changed to String to support Clerk user IDs
persona_name = Column(String(255), nullable=False) # e.g., "Professional LinkedIn Voice", "Casual Blog Writer" persona_name = Column(String(255), nullable=False) # e.g., "Professional LinkedIn Voice", "Casual Blog Writer"
# Core Identity # Core Identity

View File

@@ -355,19 +355,36 @@ class FacebookPersonaService:
"properties": { "properties": {
"text_posts": { "text_posts": {
"type": "object", "type": "object",
"description": "Text post optimization for Facebook" "description": "Text post optimization for Facebook",
"properties": {
"optimal_length": {"type": "string"},
"structure_guidelines": {"type": "array", "items": {"type": "string"}},
"hook_strategies": {"type": "array", "items": {"type": "string"}}
}
}, },
"image_posts": { "image_posts": {
"type": "object", "type": "object",
"description": "Image post optimization for Facebook" "description": "Image post optimization for Facebook",
"properties": {
"image_guidelines": {"type": "array", "items": {"type": "string"}},
"caption_strategies": {"type": "array", "items": {"type": "string"}}
}
}, },
"video_posts": { "video_posts": {
"type": "object", "type": "object",
"description": "Video post optimization for Facebook" "description": "Video post optimization for Facebook",
"properties": {
"video_length_guidelines": {"type": "array", "items": {"type": "string"}},
"engagement_hooks": {"type": "array", "items": {"type": "string"}}
}
}, },
"carousel_posts": { "carousel_posts": {
"type": "object", "type": "object",
"description": "Carousel post optimization for Facebook" "description": "Carousel post optimization for Facebook",
"properties": {
"slide_structure": {"type": "array", "items": {"type": "string"}},
"storytelling_flow": {"type": "array", "items": {"type": "string"}}
}
} }
} }
}, },

View File

@@ -1,6 +1,12 @@
""" """
Persona Analysis Service Persona Analysis Service
Uses Gemini structured responses to analyze onboarding data and create writing personas. Uses Gemini structured responses to analyze onboarding data and create writing personas.
NOTE: This service uses the legacy WritingPersona/PlatformPersona models.
For new code, use PersonaDataService instead, which works with the PersonaData table
and provides richer persona data from onboarding.
DEPRECATED: Consider migrating to PersonaDataService for better data richness.
""" """
from typing import Dict, Any, List, Optional from typing import Dict, Any, List, Optional
@@ -514,7 +520,7 @@ Generate a platform-optimized persona adaptation that maintains brand consistenc
return min(score, 100.0) return min(score, 100.0)
def get_user_personas(self, user_id: int) -> List[Dict[str, Any]]: def get_user_personas(self, user_id: str) -> List[Dict[str, Any]]:
"""Get all personas for a user.""" """Get all personas for a user."""
try: try:
session = get_db_session() session = get_db_session()
@@ -544,7 +550,7 @@ Generate a platform-optimized persona adaptation that maintains brand consistenc
logger.error(f"Error getting user personas: {str(e)}") logger.error(f"Error getting user personas: {str(e)}")
return [] return []
def get_persona_for_platform(self, user_id: int, platform: str) -> Optional[Dict[str, Any]]: def get_persona_for_platform(self, user_id: str, platform: str) -> Optional[Dict[str, Any]]:
"""Get the best persona for a specific platform.""" """Get the best persona for a specific platform."""
try: try:
session = get_db_session() session = get_db_session()

View File

@@ -0,0 +1,252 @@
"""
Persona Data Service
Direct service for working with PersonaData table from onboarding.
Leverages the rich JSON structure for better content generation.
"""
from typing import Dict, Any, Optional, List
from datetime import datetime
from loguru import logger
from sqlalchemy.orm import Session
from services.database import get_db_session
from models.onboarding import PersonaData, OnboardingSession
class PersonaDataService:
"""Service for working directly with PersonaData table."""
def __init__(self, db_session: Optional[Session] = None):
self.db = db_session or get_db_session()
def get_user_persona_data(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Get complete persona data for a user from PersonaData table."""
try:
# Get onboarding session for user
session = self.db.query(OnboardingSession).filter(
OnboardingSession.user_id == user_id
).first()
if not session:
logger.warning(f"No onboarding session found for user {user_id}")
return None
# Get persona data
persona_data = self.db.query(PersonaData).filter(
PersonaData.session_id == session.id
).first()
if not persona_data:
logger.warning(f"No persona data found for user {user_id}")
return None
return persona_data.to_dict()
except Exception as e:
logger.error(f"Error getting persona data for user {user_id}: {str(e)}")
return None
def get_platform_persona(self, user_id: str, platform: str) -> Optional[Dict[str, Any]]:
"""Get platform-specific persona data for a user."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return None
platform_personas = persona_data.get('platform_personas', {})
platform_data = platform_personas.get(platform)
if not platform_data:
logger.warning(f"No {platform} persona found for user {user_id}")
return None
# Return rich platform-specific data
return {
"platform": platform,
"platform_persona": platform_data,
"core_persona": persona_data.get('core_persona', {}),
"quality_metrics": persona_data.get('quality_metrics', {}),
"selected_platforms": persona_data.get('selected_platforms', []),
"created_at": persona_data.get('created_at'),
"updated_at": persona_data.get('updated_at')
}
except Exception as e:
logger.error(f"Error getting {platform} persona for user {user_id}: {str(e)}")
return None
def get_all_platform_personas(self, user_id: str) -> Dict[str, Any]:
"""Get all platform personas for a user."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return {}
platform_personas = persona_data.get('platform_personas', {})
# Return structured data for all platforms
result = {}
for platform, platform_data in platform_personas.items():
if isinstance(platform_data, dict) and 'error' not in platform_data:
result[platform] = {
"platform": platform,
"platform_persona": platform_data,
"core_persona": persona_data.get('core_persona', {}),
"quality_metrics": persona_data.get('quality_metrics', {}),
"created_at": persona_data.get('created_at'),
"updated_at": persona_data.get('updated_at')
}
return result
except Exception as e:
logger.error(f"Error getting all platform personas for user {user_id}: {str(e)}")
return {}
def get_core_persona(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Get core persona data for a user."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return None
return {
"core_persona": persona_data.get('core_persona', {}),
"quality_metrics": persona_data.get('quality_metrics', {}),
"selected_platforms": persona_data.get('selected_platforms', []),
"created_at": persona_data.get('created_at'),
"updated_at": persona_data.get('updated_at')
}
except Exception as e:
logger.error(f"Error getting core persona for user {user_id}: {str(e)}")
return None
def get_persona_quality_metrics(self, user_id: str) -> Optional[Dict[str, Any]]:
"""Get quality metrics for a user's persona."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return None
return persona_data.get('quality_metrics', {})
except Exception as e:
logger.error(f"Error getting quality metrics for user {user_id}: {str(e)}")
return None
def update_platform_persona(self, user_id: str, platform: str, updates: Dict[str, Any]) -> bool:
"""Update platform-specific persona data."""
try:
# Get onboarding session for user
session = self.db.query(OnboardingSession).filter(
OnboardingSession.user_id == user_id
).first()
if not session:
logger.error(f"No onboarding session found for user {user_id}")
return False
# Get persona data
persona_data = self.db.query(PersonaData).filter(
PersonaData.session_id == session.id
).first()
if not persona_data:
logger.error(f"No persona data found for user {user_id}")
return False
# Update platform-specific data
platform_personas = persona_data.platform_personas or {}
if platform in platform_personas:
platform_personas[platform].update(updates)
persona_data.platform_personas = platform_personas
persona_data.updated_at = datetime.utcnow()
self.db.commit()
logger.info(f"Updated {platform} persona for user {user_id}")
return True
else:
logger.warning(f"Platform {platform} not found for user {user_id}")
return False
except Exception as e:
logger.error(f"Error updating {platform} persona for user {user_id}: {str(e)}")
self.db.rollback()
return False
def save_platform_persona(self, user_id: str, platform: str, platform_data: Dict[str, Any]) -> bool:
"""Save or create platform-specific persona data (creates if doesn't exist)."""
try:
# Get onboarding session
session = self.db.query(OnboardingSession).filter(
OnboardingSession.user_id == user_id
).first()
if not session:
logger.error(f"No onboarding session found for user {user_id}")
return False
# Get persona data
persona_data = self.db.query(PersonaData).filter(
PersonaData.session_id == session.id
).first()
if not persona_data:
logger.error(f"No persona data found for user {user_id}")
return False
# Update or create platform persona
platform_personas = persona_data.platform_personas or {}
platform_personas[platform] = platform_data # Create or overwrite
persona_data.platform_personas = platform_personas
persona_data.updated_at = datetime.utcnow()
self.db.commit()
logger.info(f"Saved {platform} persona for user {user_id}")
return True
except Exception as e:
logger.error(f"Error saving {platform} persona for user {user_id}: {str(e)}")
self.db.rollback()
return False
def get_supported_platforms(self, user_id: str) -> List[str]:
"""Get list of platforms for which personas exist."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return []
platform_personas = persona_data.get('platform_personas', {})
return [platform for platform, data in platform_personas.items()
if isinstance(data, dict) and 'error' not in data]
except Exception as e:
logger.error(f"Error getting supported platforms for user {user_id}: {str(e)}")
return []
def get_persona_summary(self, user_id: str) -> Dict[str, Any]:
"""Get a summary of persona data for a user."""
try:
persona_data = self.get_user_persona_data(user_id)
if not persona_data:
return {"error": "No persona data found"}
platform_personas = persona_data.get('platform_personas', {})
quality_metrics = persona_data.get('quality_metrics', {})
return {
"user_id": user_id,
"has_core_persona": bool(persona_data.get('core_persona')),
"platforms": list(platform_personas.keys()),
"platform_count": len(platform_personas),
"quality_score": quality_metrics.get('overall_score', 0),
"selected_platforms": persona_data.get('selected_platforms', []),
"created_at": persona_data.get('created_at'),
"updated_at": persona_data.get('updated_at')
}
except Exception as e:
logger.error(f"Error getting persona summary for user {user_id}: {str(e)}")
return {"error": str(e)}

View File

@@ -3,9 +3,7 @@
* Handles writing persona generation and management * Handles writing persona generation and management
*/ */
import axios from 'axios'; import { apiClient } from './client';
const API_BASE_URL = process.env.REACT_APP_API_URL || 'http://localhost:8000';
export interface PersonaGenerationRequest { export interface PersonaGenerationRequest {
onboarding_session_id?: number; onboarding_session_id?: number;
@@ -79,7 +77,7 @@ export interface SupportedPlatformsResponse {
*/ */
export const checkPersonaReadiness = async (userId: number = 1): Promise<PersonaReadinessResponse> => { export const checkPersonaReadiness = async (userId: number = 1): Promise<PersonaReadinessResponse> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/onboarding/persona-readiness`, { const response = await apiClient.get('/api/onboarding/persona-readiness', {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -94,7 +92,7 @@ export const checkPersonaReadiness = async (userId: number = 1): Promise<Persona
*/ */
export const generatePersonaPreview = async (userId: number = 1): Promise<PersonaPreviewResponse> => { export const generatePersonaPreview = async (userId: number = 1): Promise<PersonaPreviewResponse> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/onboarding/persona-preview`, { const response = await apiClient.get('/api/onboarding/persona-preview', {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -109,7 +107,7 @@ export const generatePersonaPreview = async (userId: number = 1): Promise<Person
*/ */
export const generateWritingPersona = async (userId: number = 1, request: PersonaGenerationRequest = {}): Promise<PersonaGenerationResponse> => { export const generateWritingPersona = async (userId: number = 1, request: PersonaGenerationRequest = {}): Promise<PersonaGenerationResponse> => {
try { try {
const response = await axios.post(`${API_BASE_URL}/api/personas/generate`, request, { const response = await apiClient.post('/api/personas/generate', request, {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -121,10 +119,11 @@ export const generateWritingPersona = async (userId: number = 1, request: Person
/** /**
* Get all writing personas for a user * Get all writing personas for a user
* Note: user_id is extracted from Clerk JWT token, no need to pass it
*/ */
export const getUserPersonas = async (userId: number = 1): Promise<{ personas: PersonaResponse[]; total_count: number }> => { export const getUserPersonas = async (): Promise<{ personas: PersonaResponse[]; total_count: number }> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/personas/user/${userId}`); const response = await apiClient.get('/api/personas/user');
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
console.error('Error getting user personas:', error); console.error('Error getting user personas:', error);
@@ -137,7 +136,7 @@ export const getUserPersonas = async (userId: number = 1): Promise<{ personas: P
*/ */
export const getPersonaDetails = async (userId: number, personaId: number): Promise<any> => { export const getPersonaDetails = async (userId: number, personaId: number): Promise<any> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/personas/${personaId}`, { const response = await apiClient.get(`/api/personas/${personaId}`, {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -149,12 +148,11 @@ export const getPersonaDetails = async (userId: number, personaId: number): Prom
/** /**
* Get persona adaptation for a specific platform * Get persona adaptation for a specific platform
* Note: user_id is extracted from Clerk JWT token, no need to pass it
*/ */
export const getPlatformPersona = async (userId: number, platform: string): Promise<any> => { export const getPlatformPersona = async (platform: string): Promise<any> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/personas/platform/${platform}`, { const response = await apiClient.get(`/api/personas/platform/${platform}`);
params: { user_id: userId }
});
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
console.error('Error getting platform persona:', error); console.error('Error getting platform persona:', error);
@@ -167,7 +165,7 @@ export const getPlatformPersona = async (userId: number, platform: string): Prom
*/ */
export const getSupportedPlatforms = async (): Promise<SupportedPlatformsResponse> => { export const getSupportedPlatforms = async (): Promise<SupportedPlatformsResponse> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/personas/platforms`); const response = await apiClient.get('/api/personas/platforms');
return response.data; return response.data;
} catch (error: any) { } catch (error: any) {
console.error('Error getting supported platforms:', error); console.error('Error getting supported platforms:', error);
@@ -180,7 +178,7 @@ export const getSupportedPlatforms = async (): Promise<SupportedPlatformsRespons
*/ */
export const updatePersona = async (userId: number, personaId: number, updateData: any): Promise<any> => { export const updatePersona = async (userId: number, personaId: number, updateData: any): Promise<any> => {
try { try {
const response = await axios.put(`${API_BASE_URL}/api/personas/${personaId}`, updateData, { const response = await apiClient.put(`/api/personas/${personaId}`, updateData, {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -190,12 +188,40 @@ export const updatePersona = async (userId: number, personaId: number, updateDat
} }
}; };
/**
* Update platform-specific persona
* Note: user_id is extracted from Clerk JWT token
*/
export const updatePlatformPersona = async (platform: string, updateData: any): Promise<any> => {
try {
const response = await apiClient.put(`/api/personas/platform/${platform}`, updateData);
return response.data;
} catch (error: any) {
console.error('Error updating platform persona:', error);
throw new Error(error.response?.data?.detail || 'Failed to update platform persona');
}
};
/**
* Generate a platform-specific persona from core persona
* Note: user_id is extracted from Clerk JWT token
*/
export const generatePlatformPersona = async (platform: string): Promise<any> => {
try {
const response = await apiClient.post(`/api/personas/generate-platform/${platform}`);
return response.data;
} catch (error: any) {
console.error(`Error generating ${platform} persona:`, error);
throw new Error(error.response?.data?.detail || `Failed to generate ${platform} persona`);
}
};
/** /**
* Delete a persona * Delete a persona
*/ */
export const deletePersona = async (userId: number, personaId: number): Promise<any> => { export const deletePersona = async (userId: number, personaId: number): Promise<any> => {
try { try {
const response = await axios.delete(`${API_BASE_URL}/api/personas/${personaId}`, { const response = await apiClient.delete(`/api/personas/${personaId}`, {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;
@@ -215,7 +241,7 @@ export const generateContentWithPersona = async (
contentType: string = 'post' contentType: string = 'post'
): Promise<any> => { ): Promise<any> => {
try { try {
const response = await axios.post(`${API_BASE_URL}/api/personas/generate-content`, { const response = await apiClient.post('/api/personas/generate-content', {
user_id: userId, user_id: userId,
platform, platform,
content_request: contentRequest, content_request: contentRequest,
@@ -233,7 +259,7 @@ export const generateContentWithPersona = async (
*/ */
export const exportPersonaPrompt = async (userId: number, platform: string): Promise<any> => { export const exportPersonaPrompt = async (userId: number, platform: string): Promise<any> => {
try { try {
const response = await axios.get(`${API_BASE_URL}/api/personas/export/${platform}`, { const response = await apiClient.get(`/api/personas/export/${platform}`, {
params: { user_id: userId } params: { user_id: userId }
}); });
return response.data; return response.data;

View File

@@ -1,4 +1,5 @@
import React from 'react'; import React from 'react';
import { useNavigate } from 'react-router-dom';
import { Box, Container, Typography, TextField, Paper, Button } from '@mui/material'; import { Box, Container, Typography, TextField, Paper, Button } from '@mui/material';
import { CopilotSidebar } from '@copilotkit/react-ui'; import { CopilotSidebar } from '@copilotkit/react-ui';
import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core'; import { useCopilotReadable, useCopilotAction } from '@copilotkit/react-core';
@@ -7,6 +8,7 @@ import RegisterFacebookActions from './RegisterFacebookActions';
import RegisterFacebookEditActions from './RegisterFacebookEditActions'; import RegisterFacebookEditActions from './RegisterFacebookEditActions';
import RegisterFacebookActionsEnhanced from './RegisterFacebookActionsEnhanced'; import RegisterFacebookActionsEnhanced from './RegisterFacebookActionsEnhanced';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider'; import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
import { generatePlatformPersona } from '../../api/persona';
const useCopilotActionTyped = useCopilotAction as any; const useCopilotActionTyped = useCopilotAction as any;
@@ -142,6 +144,7 @@ const FacebookWriter: React.FC<FacebookWriterProps> = ({ className = '' }) => {
// Main Facebook Writer Content Component // Main Facebook Writer Content Component
const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }) => { const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }) => {
const navigate = useNavigate();
const [postDraft, setPostDraft] = React.useState<string>(''); const [postDraft, setPostDraft] = React.useState<string>('');
const [notes, setNotes] = React.useState<string>(''); const [notes, setNotes] = React.useState<string>('');
const [stage, setStage] = React.useState<'start' | 'edit'>('start'); const [stage, setStage] = React.useState<'start' | 'edit'>('start');
@@ -160,7 +163,34 @@ const FacebookWriterContent: React.FC<FacebookWriterProps> = ({ className = '' }
const [selectionMenu, setSelectionMenu] = React.useState<{ x: number; y: number; text: string } | null>(null); const [selectionMenu, setSelectionMenu] = React.useState<{ x: number; y: number; text: string } | null>(null);
// Get persona context for enhanced AI assistance // Get persona context for enhanced AI assistance
const { corePersona, platformPersona, loading: personaLoading } = usePlatformPersonaContext(); const { corePersona, platformPersona, loading: personaLoading, refreshPersonas } = usePlatformPersonaContext();
// State for generating persona
const [isGeneratingPersona, setIsGeneratingPersona] = React.useState<boolean>(false);
const [personaError, setPersonaError] = React.useState<string | null>(null);
// Handler to generate Facebook persona on-demand
const handleGeneratePersona = async () => {
setIsGeneratingPersona(true);
setPersonaError(null);
try {
const result = await generatePlatformPersona('facebook');
if (result.success) {
// Refresh the persona context to load the newly generated persona
await refreshPersonas();
console.log('✅ Facebook persona generated successfully');
} else {
throw new Error('Failed to generate persona');
}
} catch (error: any) {
console.error('Error generating persona:', error);
setPersonaError(error.message || 'Failed to generate Facebook persona');
} finally {
setIsGeneratingPersona(false);
}
};
React.useEffect(() => { React.useEffect(() => {
const onUpdate = (e: any) => { const onUpdate = (e: any) => {
@@ -395,9 +425,31 @@ Always use the most appropriate tool for the user's request.`.trim();
> >
<Container maxWidth="md" sx={{ position: 'relative', zIndex: 1, py: 4 }}> <Container maxWidth="md" sx={{ position: 'relative', zIndex: 1, py: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}> <Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: 0.3 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
Facebook Writer (Preview) {/* Back Button */}
</Typography> <Button
onClick={() => navigate('/dashboard')}
sx={{
padding: '8px 16px',
background: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: 2,
textTransform: 'none',
fontSize: 14,
fontWeight: 600,
'&:hover': {
background: 'rgba(255, 255, 255, 0.2)',
}
}}
>
Back to Dashboard
</Button>
<Typography variant="h4" sx={{ fontWeight: 800, letterSpacing: 0.3 }}>
Facebook Writer
</Typography>
</Box>
</Box> </Box>
{/* Persona Integration Indicator */} {/* Persona Integration Indicator */}
@@ -405,8 +457,8 @@ Always use the most appropriate tool for the user's request.`.trim();
<div <div
style={{ style={{
padding: '8px 16px', padding: '8px 16px',
backgroundColor: 'rgba(24, 119, 242, 0.1)', backgroundColor: platformPersona ? 'rgba(24, 119, 242, 0.1)' : 'rgba(255, 152, 0, 0.1)',
borderBottom: '1px solid rgba(24, 119, 242, 0.3)', borderBottom: `1px solid ${platformPersona ? 'rgba(24, 119, 242, 0.3)' : 'rgba(255, 152, 0, 0.3)'}`,
fontSize: '12px', fontSize: '12px',
color: 'rgba(255, 255, 255, 0.8)', color: 'rgba(255, 255, 255, 0.8)',
display: 'flex', display: 'flex',
@@ -416,7 +468,7 @@ Always use the most appropriate tool for the user's request.`.trim();
position: 'relative', position: 'relative',
marginBottom: '16px', marginBottom: '16px',
borderRadius: '8px', borderRadius: '8px',
border: '1px solid rgba(24, 119, 242, 0.2)' border: `1px solid ${platformPersona ? 'rgba(24, 119, 242, 0.2)' : 'rgba(255, 152, 0, 0.2)'}`
}} }}
title={`🎭 YOUR PERSONALIZED WRITING ASSISTANT title={`🎭 YOUR PERSONALIZED WRITING ASSISTANT
@@ -468,17 +520,70 @@ Instead of generic content, you get:
💡 TRY THIS: Ask the AI to "generate a Facebook post about [your topic]" and watch how it automatically applies your persona to create content that sounds like you!`} 💡 TRY THIS: Ask the AI to "generate a Facebook post about [your topic]" and watch how it automatically applies your persona to create content that sounds like you!`}
> >
<span style={{ color: '#1877f2' }}>🎭</span> <span style={{ color: platformPersona ? '#1877f2' : '#FF9800' }}>🎭</span>
<span><strong>🎭 Your Writing Assistant:</strong> {corePersona.persona_name} ({corePersona.archetype})</span> <span>
<strong>🎭 Your Writing Assistant:</strong> {corePersona.persona_name} ({corePersona.archetype})
{!platformPersona && <span style={{ color: '#FF9800', marginLeft: '8px' }}> Facebook persona not generated yet</span>}
</span>
<span style={{ marginLeft: 'auto', fontSize: '11px' }}> <span style={{ marginLeft: 'auto', fontSize: '11px' }}>
{corePersona.confidence_score}% accuracy | {corePersona.confidence_score}% accuracy |
Platform: Facebook Optimized Platform: {platformPersona ? 'Facebook Optimized' : 'Generic (Generate Facebook Persona for better results)'}
</span> </span>
<span style={{ fontSize: '10px', color: 'rgba(255, 255, 255, 0.6)', marginLeft: '8px' }}> <span style={{ fontSize: '10px', color: 'rgba(255, 255, 255, 0.6)', marginLeft: '8px' }}>
(Hover for details) (Hover for details)
</span> </span>
</div> </div>
)} )}
{/* Warning when platform persona is missing */}
{corePersona && !platformPersona && !personaLoading && (
<div
style={{
padding: '12px 16px',
backgroundColor: 'rgba(255, 152, 0, 0.15)',
border: '1px solid rgba(255, 152, 0, 0.3)',
fontSize: '13px',
color: 'rgba(255, 255, 255, 0.9)',
marginBottom: '16px',
borderRadius: '8px',
display: 'flex',
alignItems: 'center',
gap: '12px'
}}
>
<span style={{ fontSize: '20px' }}></span>
<div style={{ flex: 1 }}>
<strong>Facebook Persona Not Generated</strong>
<div style={{ fontSize: '12px', color: 'rgba(255, 255, 255, 0.7)', marginTop: '4px' }}>
You're using a generic persona. Generate a Facebook-specific persona for personalized content that matches your brand voice and Facebook's algorithm.
</div>
{personaError && (
<div style={{ fontSize: '11px', color: '#ff6b6b', marginTop: '4px' }}>
{personaError}
</div>
)}
</div>
<Button
onClick={handleGeneratePersona}
disabled={isGeneratingPersona}
size="small"
sx={{
background: 'rgba(255, 152, 0, 0.2)',
color: 'white',
border: '1px solid rgba(255, 152, 0, 0.4)',
textTransform: 'none',
'&:hover': {
background: 'rgba(255, 152, 0, 0.3)',
},
'&:disabled': {
opacity: 0.6
}
}}
>
{isGeneratingPersona ? 'Generating...' : 'Generate Persona →'}
</Button>
</div>
)}
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}> <Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<Button size="small" variant="outlined" disabled sx={{ color: 'rgba(255,255,255,0.7)', borderColor: 'rgba(255,255,255,0.25)' }}> <Button size="small" variant="outlined" disabled sx={{ color: 'rgba(255,255,255,0.7)', borderColor: 'rgba(255,255,255,0.25)' }}>
DashBoard DashBoard

View File

@@ -6,7 +6,8 @@ import './styles/alwrity-copilot.css';
import RegisterLinkedInActions from './RegisterLinkedInActions'; import RegisterLinkedInActions from './RegisterLinkedInActions';
import RegisterLinkedInEditActions from './RegisterLinkedInEditActions'; import RegisterLinkedInEditActions from './RegisterLinkedInEditActions';
import RegisterLinkedInActionsEnhanced from './RegisterLinkedInActionsEnhanced'; import RegisterLinkedInActionsEnhanced from './RegisterLinkedInActionsEnhanced';
import { Header, ContentEditor, LoadingIndicator, WelcomeMessage, ProgressTracker, CopilotActions } from './components'; import { Header, ContentEditor, LoadingIndicator, WelcomeMessage, ProgressTracker } from './components';
import { useCopilotActions } from './components/CopilotActions';
import { useLinkedInWriter } from './hooks/useLinkedInWriter'; import { useLinkedInWriter } from './hooks/useLinkedInWriter';
import { useCopilotPersistence } from './utils/enhancedPersistence'; import { useCopilotPersistence } from './utils/enhancedPersistence';
import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider'; import { PlatformPersonaProvider, usePlatformPersonaContext } from '../shared/PersonaContext/PlatformPersonaProvider';
@@ -226,8 +227,8 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
}); });
// Initialize CopilotActions component to handle all copilot-related functionality // Use the CopilotActions hook to handle all copilot-related functionality
const getIntelligentSuggestions = CopilotActions({ const getIntelligentSuggestions = useCopilotActions({
draft, draft,
context, context,
userPreferences, userPreferences,
@@ -237,7 +238,15 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
}); });
return ( return (
<div className={`linkedin-writer ${className}`} style={{ height: '100vh', display: 'flex', flexDirection: 'column' }}> <div
className={`linkedin-writer ${className}`}
style={{
height: '100vh',
display: 'flex',
flexDirection: 'column',
backgroundColor: '#ffffff' // White professional background
}}
>
{/* Header */} {/* Header */}
<Header <Header
userPreferences={userPreferences} userPreferences={userPreferences}
@@ -267,7 +276,7 @@ const LinkedInWriterContent: React.FC<LinkedInWriterProps> = ({ className = '' }
{/* Main Content */} {/* Main Content */}
<div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden' }}> <div style={{ flex: 1, display: 'flex', flexDirection: 'column', overflow: 'hidden', backgroundColor: '#ffffff' }}>
{/* Loading Indicator */} {/* Loading Indicator */}
<LoadingIndicator <LoadingIndicator
isGenerating={isGenerating} isGenerating={isGenerating}

View File

@@ -16,9 +16,9 @@ interface CopilotActionsProps {
setDraft: (draft: string) => void; setDraft: (draft: string) => void;
} }
// Note: This is implemented as a hook-like utility, not a rendered component. // Note: This is implemented as a custom hook.
// It returns the getIntelligentSuggestions function for use by the caller. // It returns the getIntelligentSuggestions function for use by the caller.
const CopilotActions = ({ export const useCopilotActions = ({
draft, draft,
context, context,
userPreferences, userPreferences,
@@ -428,5 +428,3 @@ const CopilotActions = ({
// Return the suggestions function directly // Return the suggestions function directly
return getIntelligentSuggestions; return getIntelligentSuggestions;
}; };
export default CopilotActions;

View File

@@ -1,4 +1,5 @@
import React, { useState, useMemo } from 'react'; import React, { useState, useMemo } from 'react';
import { useNavigate } from 'react-router-dom';
import { LinkedInPreferences } from '../utils/storageUtils'; import { LinkedInPreferences } from '../utils/storageUtils';
import { PersonaChip } from '../../TextEditor/ContentPreviewHeaderComponents'; import { PersonaChip } from '../../TextEditor/ContentPreviewHeaderComponents';
import { usePlatformPersonaContext } from '../../shared/PersonaContext/PlatformPersonaProvider'; import { usePlatformPersonaContext } from '../../shared/PersonaContext/PlatformPersonaProvider';
@@ -25,6 +26,7 @@ export const Header: React.FC<HeaderProps> = ({
onClearHistory, onClearHistory,
getHistoryLength getHistoryLength
}) => { }) => {
const navigate = useNavigate();
const [personaOverride, setPersonaOverride] = useState<any>(null); const [personaOverride, setPersonaOverride] = useState<any>(null);
const { corePersona, platformPersona } = usePlatformPersonaContext(); const { corePersona, platformPersona } = usePlatformPersonaContext();
@@ -89,6 +91,34 @@ export const Header: React.FC<HeaderProps> = ({
<div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}> <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
{/* Left Section - Logo and Title */} {/* Left Section - Logo and Title */}
<div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '20px' }}>
{/* Back Button */}
<button
onClick={() => navigate('/dashboard')}
style={{
padding: '8px 12px',
background: 'rgba(255, 255, 255, 0.1)',
color: 'white',
border: '1px solid rgba(255, 255, 255, 0.2)',
borderRadius: 8,
cursor: 'pointer',
fontSize: 14,
fontWeight: 600,
display: 'flex',
alignItems: 'center',
gap: 6,
transition: 'all 0.2s ease'
}}
onMouseEnter={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.2)';
}}
onMouseLeave={(e) => {
e.currentTarget.style.background = 'rgba(255, 255, 255, 0.1)';
}}
title="Back to Dashboard"
>
Back to Dashboard
</button>
<div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}> <div style={{ display: 'flex', alignItems: 'center', gap: '16px' }}>
<img <img
src={alwrityLogo} src={alwrityLogo}
@@ -211,7 +241,6 @@ export const Header: React.FC<HeaderProps> = ({
}}> }}>
<PersonaChip <PersonaChip
platform="linkedin" platform="linkedin"
userId={1}
onPersonaUpdate={handlePersonaUpdate} onPersonaUpdate={handlePersonaUpdate}
/> />
</div> </div>

View File

@@ -26,4 +26,4 @@ export { default as ImageGenerationTest } from './ImageGenerationTest';
// Refactored Components // Refactored Components
export { default as BrainstormFlow } from './BrainstormFlow'; export { default as BrainstormFlow } from './BrainstormFlow';
export { default as CopilotActions } from './CopilotActions'; export { useCopilotActions } from './CopilotActions';

View File

@@ -87,7 +87,6 @@ const MainContentPreviewHeader: React.FC<MainContentPreviewHeaderProps> = ({
{/* Persona Chip */} {/* Persona Chip */}
<PersonaChip <PersonaChip
platform="linkedin" platform="linkedin"
userId={1}
onPersonaUpdate={(personaData) => { onPersonaUpdate={(personaData) => {
console.log('Persona updated:', personaData); console.log('Persona updated:', personaData);
// You can add additional logic here to handle persona updates // You can add additional logic here to handle persona updates

View File

@@ -1,5 +1,6 @@
import React, { useState, useEffect } from 'react'; import React, { useState, useEffect } from 'react';
import PersonaEditorModal from './PersonaEditorModal'; import PersonaEditorModal from './PersonaEditorModal';
import { getUserPersonas, getPlatformPersona, updatePersona, updatePlatformPersona } from '../../../api/persona';
interface PersonaData { interface PersonaData {
id?: number; id?: number;
@@ -28,13 +29,11 @@ interface PersonaData {
interface PersonaChipProps { interface PersonaChipProps {
platform: string; platform: string;
userId?: number;
onPersonaUpdate?: (personaData: PersonaData) => void; onPersonaUpdate?: (personaData: PersonaData) => void;
} }
const PersonaChip: React.FC<PersonaChipProps> = ({ const PersonaChip: React.FC<PersonaChipProps> = ({
platform, platform,
userId = 1,
onPersonaUpdate onPersonaUpdate
}) => { }) => {
const [personaData, setPersonaData] = useState<PersonaData | null>(null); const [personaData, setPersonaData] = useState<PersonaData | null>(null);
@@ -48,44 +47,48 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
setError(null); setError(null);
try { try {
// Fetch core persona list (take most recent active) and platform-specific details // Fetch core persona list (take most recent active) and platform-specific details using authenticated API client
const [coreRes, platformRes] = await Promise.all([ const [coreList, platformData] = await Promise.all([
fetch(`/api/personas/user/${userId}`), getUserPersonas(),
fetch(`/api/personas/platform/${platform}?user_id=${userId}`) getPlatformPersona(platform)
]); ]);
if (coreRes.ok && platformRes.ok) { if (coreList && platformData) {
const coreList = await coreRes.json(); // Extract core persona from the response
const platformData = await platformRes.json(); const corePersona = platformData?.core_persona || {};
const core = (coreList?.personas && coreList.personas.length > 0) ? coreList.personas[0] : {}; const platformPersona = platformData?.platform_persona || {};
const qualityMetrics = platformData?.quality_metrics || {};
if (!corePersona || Object.keys(corePersona).length === 0) {
setError('No persona found for this platform');
return;
}
// Merge core + platform fields for editor convenience // Merge core + platform fields for editor convenience
setPersonaData({ setPersonaData({
id: core.id, id: platformData?.id || 1,
user_id: core.user_id, user_id: 1, // Placeholder, not used
persona_name: core.persona_name, persona_name: corePersona.persona_name || 'Untitled Persona',
archetype: core.archetype, archetype: corePersona.archetype || 'General',
core_belief: core.core_belief, core_belief: corePersona.core_belief || '',
brand_voice_description: core.brand_voice_description, brand_voice_description: corePersona.brand_voice_description || corePersona.core_belief || '',
linguistic_fingerprint: core.linguistic_fingerprint, linguistic_fingerprint: corePersona.linguistic_fingerprint || {},
platform_adaptations: core.platform_adaptations, platform_adaptations: corePersona.platform_adaptations || {},
confidence_score: core.confidence_score, confidence_score: qualityMetrics.confidence_score || corePersona.confidence_score || 0,
ai_analysis_version: core.ai_analysis_version, ai_analysis_version: platformData?.ai_analysis_version || '1.0',
platform_type: platform, platform_type: platform,
sentence_metrics: platformData?.sentence_metrics, sentence_metrics: platformPersona?.sentence_metrics || {},
lexical_features: platformData?.lexical_features, lexical_features: platformPersona?.lexical_features || {},
rhetorical_devices: platformData?.rhetorical_devices, rhetorical_devices: platformPersona?.rhetorical_devices || {},
tonal_range: platformData?.tonal_range, tonal_range: platformPersona?.tonal_range || {},
stylistic_constraints: platformData?.stylistic_constraints, stylistic_constraints: platformPersona?.stylistic_constraints || {},
content_format_rules: platformData?.content_format_rules, content_format_rules: platformPersona?.content_format_rules || {},
engagement_patterns: platformData?.engagement_patterns, engagement_patterns: platformPersona?.engagement_patterns || {},
posting_frequency: platformData?.posting_frequency, posting_frequency: platformPersona?.posting_frequency || {},
content_types: platformData?.content_types, content_types: platformPersona?.content_types || {},
platform_best_practices: platformData?.platform_best_practices, platform_best_practices: platformPersona?.platform_best_practices || {},
algorithm_considerations: platformData?.algorithm_considerations, algorithm_considerations: platformPersona?.algorithm_considerations || {},
} as any); } as any);
} else {
setError('No persona found for this platform');
} }
} catch (err) { } catch (err) {
setError('Failed to load persona data'); setError('Failed to load persona data');
@@ -98,7 +101,7 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
useEffect(() => { useEffect(() => {
fetchPersonaData(); fetchPersonaData();
// eslint-disable-next-line react-hooks/exhaustive-deps // eslint-disable-next-line react-hooks/exhaustive-deps
}, [platform, userId]); }, [platform]);
const handleSavePersona = async (data: PersonaData, saveToDatabase: boolean) => { const handleSavePersona = async (data: PersonaData, saveToDatabase: boolean) => {
try { try {
@@ -114,12 +117,8 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
platform_adaptations: data.platform_adaptations, platform_adaptations: data.platform_adaptations,
}; };
const coreRes = await fetch(`/api/personas/${data.id}?user_id=${userId}`, { // Use authenticated API client, note that user ID is extracted from JWT
method: 'PUT', await updatePersona(1, data.id, { core_persona: corePayload });
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(corePayload)
});
if (!coreRes.ok) throw new Error('Failed to update core persona');
} }
// Save platform persona fields // Save platform persona fields
@@ -137,12 +136,8 @@ const PersonaChip: React.FC<PersonaChipProps> = ({
algorithm_considerations: data.algorithm_considerations, algorithm_considerations: data.algorithm_considerations,
}; };
const platRes = await fetch(`/api/personas/platform/${platform}?user_id=${userId}`, { // Use authenticated API client, note that user ID is extracted from JWT
method: 'PUT', await updatePlatformPersona(platform, platformPayload);
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify(platformPayload)
});
if (!platRes.ok) throw new Error('Failed to update platform persona');
} }
// Update local state // Update local state

View File

@@ -6,6 +6,7 @@
import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback, useRef } from 'react'; import React, { createContext, useContext, useState, useEffect, ReactNode, useCallback, useRef } from 'react';
import { useCopilotReadable } from '@copilotkit/react-core'; import { useCopilotReadable } from '@copilotkit/react-core';
import { useAuth } from '@clerk/clerk-react';
import { import {
WritingPersona, WritingPersona,
PlatformAdaptation, PlatformAdaptation,
@@ -33,7 +34,6 @@ const PlatformPersonaContext = createContext<PlatformPersonaContextType | null>(
interface PlatformPersonaProviderProps { interface PlatformPersonaProviderProps {
children: ReactNode; children: ReactNode;
platform: PlatformType; platform: PlatformType;
userId?: number; // Default to 1 for now, can be enhanced with auth context later
} }
// Cache duration: 5 minutes (constant outside component to avoid dependency issues) // Cache duration: 5 minutes (constant outside component to avoid dependency issues)
@@ -42,9 +42,13 @@ const CACHE_DURATION = 5 * 60 * 1000;
// Provider component // Provider component
export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = ({ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = ({
children, children,
platform, platform
userId = 1
}) => { }) => {
// Get Clerk user ID
const { userId } = useAuth();
// Convert string userId to number for legacy API compatibility
const numericUserId = userId ? 1 : 1; // Use 1 as placeholder, API uses Clerk ID from auth
// State management // State management
const [corePersona, setCorePersona] = useState<WritingPersona | null>(null); const [corePersona, setCorePersona] = useState<WritingPersona | null>(null);
const [platformPersona, setPlatformPersona] = useState<PlatformAdaptation | null>(null); const [platformPersona, setPlatformPersona] = useState<PlatformAdaptation | null>(null);
@@ -83,24 +87,69 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
setError(null); setError(null);
// Fetch both core persona and platform-specific data // Fetch both core persona and platform-specific data
const [userPersonasResponse, platformPersonaResponse] = await Promise.all([ // Note: APIs use Clerk auth, so user ID is extracted from JWT
getUserPersonas(userId), let userPersonasResponse;
getPlatformPersona(userId, platform) let platformPersonaResponse = null;
]);
try {
const results = await Promise.all([
getUserPersonas(),
getPlatformPersona(platform).catch(err => {
// Handle 404 gracefully - platform persona doesn't exist yet
if (err.message && err.message.includes('No persona found')) {
console.warn(`⚠️ No ${platform} persona found - user can still generate content`);
return null;
}
throw err;
})
]);
userPersonasResponse = results[0];
platformPersonaResponse = results[1];
} catch (error) {
// If platform persona fetch fails, continue with core persona only
console.warn(`⚠️ Platform persona unavailable: ${error instanceof Error ? error.message : 'Unknown error'}`);
userPersonasResponse = await getUserPersonas();
platformPersonaResponse = null;
}
// Handle core persona data // Handle core persona data
if (userPersonasResponse.personas && userPersonasResponse.personas.length > 0) { console.log('🔍 API Response - userPersonasResponse:', userPersonasResponse);
const primaryPersona = userPersonasResponse.personas[0];
// Backend returns personas as a dictionary of platform -> persona data
// Convert to array format for easier processing
let personasArray: any[] = [];
if (userPersonasResponse && userPersonasResponse.personas) {
if (Array.isArray(userPersonasResponse.personas)) {
personasArray = userPersonasResponse.personas;
} else if (typeof userPersonasResponse.personas === 'object') {
// Convert dictionary to array
personasArray = Object.values(userPersonasResponse.personas);
}
}
console.log('🔍 Processed personas array:', {
isArray: Array.isArray(personasArray),
length: personasArray.length,
firstItem: personasArray[0]
});
if (personasArray.length > 0) {
const primaryPersona = personasArray[0];
console.log('🔍 Primary persona from API:', primaryPersona);
// Extract core persona data (may be nested in the response)
const corePersonaData = primaryPersona.core_persona || primaryPersona;
const identity = corePersonaData.identity || {};
// Convert API response to WritingPersona format // Convert API response to WritingPersona format
const convertedPersona: WritingPersona = { const convertedPersona: WritingPersona = {
id: primaryPersona.persona_id, id: primaryPersona.id || corePersonaData.id || 1,
user_id: userId, user_id: numericUserId, // Use numeric ID for legacy compatibility
persona_name: primaryPersona.persona_name, persona_name: identity.persona_name || corePersonaData.persona_name || primaryPersona.persona_name || 'Untitled Persona',
archetype: primaryPersona.archetype, archetype: identity.archetype || corePersonaData.archetype || primaryPersona.archetype || 'General',
core_belief: primaryPersona.core_belief, core_belief: identity.core_belief || corePersonaData.core_belief || primaryPersona.core_belief || '',
brand_voice_description: primaryPersona.core_belief, // Use core_belief as fallback brand_voice_description: identity.brand_voice_description || corePersonaData.brand_voice_description || corePersonaData.core_belief || primaryPersona.core_belief || '',
linguistic_fingerprint: { linguistic_fingerprint: corePersonaData.linguistic_fingerprint || {
sentence_metrics: { sentence_metrics: {
average_sentence_length_words: 15, average_sentence_length_words: 15,
preferred_sentence_type: "compound", preferred_sentence_type: "compound",
@@ -130,10 +179,11 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
source_website_analysis: {}, source_website_analysis: {},
source_research_preferences: {}, source_research_preferences: {},
ai_analysis_version: "1.0", ai_analysis_version: "1.0",
confidence_score: primaryPersona.confidence_score, confidence_score: primaryPersona.quality_metrics?.overall_score ? primaryPersona.quality_metrics.overall_score / 100 :
analysis_date: primaryPersona.created_at, (corePersonaData.confidence_score || primaryPersona.confidence_score || 0),
analysis_date: corePersonaData.created_at || primaryPersona.created_at,
created_at: primaryPersona.created_at, created_at: primaryPersona.created_at,
updated_at: primaryPersona.created_at, updated_at: primaryPersona.updated_at || primaryPersona.created_at,
is_active: true is_active: true
}; };
@@ -142,7 +192,10 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
console.log('✅ Core persona loaded:', { console.log('✅ Core persona loaded:', {
name: convertedPersona.persona_name, name: convertedPersona.persona_name,
archetype: convertedPersona.archetype, archetype: convertedPersona.archetype,
confidence: convertedPersona.confidence_score confidence: convertedPersona.confidence_score,
hasLinguisticFingerprint: !!(convertedPersona.linguistic_fingerprint && Object.keys(convertedPersona.linguistic_fingerprint).length),
identityData: identity,
quality_metrics: primaryPersona.quality_metrics
}); });
} else { } else {
console.warn('⚠️ No core personas found for user'); console.warn('⚠️ No core personas found for user');
@@ -150,46 +203,51 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
} }
// Handle platform-specific persona data // Handle platform-specific persona data
console.log('🔍 API Response - platformPersonaResponse:', platformPersonaResponse);
if (platformPersonaResponse) { if (platformPersonaResponse) {
// Extract platform-specific data from API response
const platformPersona = platformPersonaResponse.platform_persona || {};
const corePersonaFromPlatform = platformPersonaResponse.core_persona || {};
// Convert API response to PlatformAdaptation format // Convert API response to PlatformAdaptation format
const convertedPlatformPersona: PlatformAdaptation = { const convertedPlatformPersona: PlatformAdaptation = {
id: 1, id: 1,
writing_persona_id: corePersona?.id || 1, writing_persona_id: corePersona?.id || 1,
platform_type: platform, platform_type: platform,
sentence_metrics: { sentence_metrics: platformPersona.sentence_metrics || {
optimal_length: "150-300 words", optimal_length: "150-300 words",
character_limit: platform === 'linkedin' ? 3000 : 280, character_limit: platform === 'linkedin' ? 3000 : 280,
sentence_structure: "varied", sentence_structure: "varied",
paragraph_breaks: "frequent", paragraph_breaks: "frequent",
readability_score: 8.5 readability_score: 8.5
}, },
lexical_features: { lexical_features: platformPersona.lexical_features || {
hashtag_strategy: "3-5 relevant hashtags", hashtag_strategy: "3-5 relevant hashtags",
platform_specific_terms: [], platform_specific_terms: [],
engagement_phrases: ["What do you think?", "Share your thoughts"], engagement_phrases: ["What do you think?", "Share your thoughts"],
call_to_action_style: "gentle" call_to_action_style: "gentle"
}, },
rhetorical_devices: { rhetorical_devices: platformPersona.rhetorical_devices || {
question_frequency: "occasional", question_frequency: "occasional",
story_elements: "personal_anecdotes", story_elements: "personal_anecdotes",
visual_descriptions: "minimal", visual_descriptions: "minimal",
interactive_elements: "questions" interactive_elements: "questions"
}, },
tonal_range: { tonal_range: platformPersona.tonal_range || {
default_tone: "professional_friendly", default_tone: "professional_friendly",
permissible_tones: ["inspiring", "thoughtful"], permissible_tones: ["inspiring", "thoughtful"],
forbidden_tones: ["salesy", "academic"], forbidden_tones: ["salesy", "academic"],
emotional_range: "moderate", emotional_range: "moderate",
formality_level: "semi_formal" formality_level: "semi_formal"
}, },
stylistic_constraints: { stylistic_constraints: platformPersona.stylistic_constraints || {
punctuation_preferences: "standard", punctuation_preferences: "standard",
formatting_rules: "clean", formatting_rules: "clean",
emoji_usage: "minimal", emoji_usage: "minimal",
link_placement: "end", link_placement: "end",
media_integration: "encouraged" media_integration: "encouraged"
}, },
content_format_rules: { content_format_rules: platformPersona.content_format_rules || {
character_limit: platform === 'linkedin' ? 3000 : 280, character_limit: platform === 'linkedin' ? 3000 : 280,
optimal_length: platform === 'linkedin' ? "150-300 words" : "120-150 characters", optimal_length: platform === 'linkedin' ? "150-300 words" : "120-150 characters",
word_count: platform === 'linkedin' ? "150-300" : "20-25", word_count: platform === 'linkedin' ? "150-300" : "20-25",
@@ -197,26 +255,26 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
media_requirements: "optional", media_requirements: "optional",
link_restrictions: "unlimited" link_restrictions: "unlimited"
}, },
engagement_patterns: { engagement_patterns: platformPersona.engagement_patterns || {
posting_frequency: "2-3 times per week", posting_frequency: "2-3 times per week",
best_timing: "9 AM - 11 AM, 1 PM - 3 PM", best_timing: "9 AM - 11 AM, 1 PM - 3 PM",
interaction_style: "conversational", interaction_style: "conversational",
response_strategy: "within 2 hours", response_strategy: "within 2 hours",
community_approach: "collaborative" community_approach: "collaborative"
}, },
posting_frequency: { posting_frequency: platformPersona.posting_frequency || {
frequency: "2-3 times per week", frequency: "2-3 times per week",
optimal_days: ["Tuesday", "Wednesday", "Thursday"], optimal_days: ["Tuesday", "Wednesday", "Thursday"],
optimal_times: ["9:00 AM", "1:00 PM"], optimal_times: ["9:00 AM", "1:00 PM"],
seasonal_adjustments: "moderate" seasonal_adjustments: "moderate"
}, },
content_types: { content_types: platformPersona.content_types || {
primary_content: ["thought_leadership", "industry_insights"], primary_content: ["thought_leadership", "industry_insights"],
secondary_content: ["personal_stories", "tips"], secondary_content: ["personal_stories", "tips"],
content_mix: "70% professional, 30% personal", content_mix: "70% professional, 30% personal",
seasonal_content: ["trending_topics", "industry_events"] seasonal_content: ["trending_topics", "industry_events"]
}, },
platform_best_practices: { platform_best_practices: platformPersona.platform_best_practices || {
algorithm_tips: ["post_consistently", "engage_with_community"], algorithm_tips: ["post_consistently", "engage_with_community"],
engagement_tactics: ["ask_questions", "share_stories"], engagement_tactics: ["ask_questions", "share_stories"],
content_strategies: ["value_first", "authentic_voice"], content_strategies: ["value_first", "authentic_voice"],
@@ -231,7 +289,8 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
console.log('✅ Platform persona loaded:', { console.log('✅ Platform persona loaded:', {
platform: convertedPlatformPersona.platform_type, platform: convertedPlatformPersona.platform_type,
characterLimit: convertedPlatformPersona.content_format_rules?.character_limit, characterLimit: convertedPlatformPersona.content_format_rules?.character_limit,
optimalLength: convertedPlatformPersona.content_format_rules?.optimal_length optimalLength: convertedPlatformPersona.content_format_rules?.optimal_length,
hasData: !!(platformPersona && Object.keys(platformPersona).length > 0)
}); });
} else { } else {
console.warn(`⚠️ No platform-specific persona found for ${platform}`); console.warn(`⚠️ No platform-specific persona found for ${platform}`);
@@ -272,6 +331,18 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
parentId: corePersona?.id?.toString() parentId: corePersona?.id?.toString()
}); });
// Debug: Log when persona data is available for CopilotKit
useEffect(() => {
if (corePersona) {
console.log('🎯 Injected core persona into CopilotKit:', {
name: corePersona.persona_name,
archetype: corePersona.archetype,
confidence: corePersona.confidence_score,
hasLinguisticFingerprint: !!(corePersona.linguistic_fingerprint && Object.keys(corePersona.linguistic_fingerprint).length)
});
}
}, [corePersona]);
// Inject platform-specific persona into CopilotKit context // Inject platform-specific persona into CopilotKit context
useCopilotReadable({ useCopilotReadable({
description: `${platform} platform optimization rules and constraints`, description: `${platform} platform optimization rules and constraints`,
@@ -280,6 +351,17 @@ export const PlatformPersonaProvider: React.FC<PlatformPersonaProviderProps> = (
parentId: corePersona?.id?.toString() parentId: corePersona?.id?.toString()
}); });
// Debug: Log when platform persona is available for CopilotKit
useEffect(() => {
if (platformPersona) {
console.log('🎯 Injected platform persona into CopilotKit:', {
platform: platformPersona.platform_type,
characterLimit: platformPersona.content_format_rules?.character_limit,
optimalLength: platformPersona.content_format_rules?.optimal_length
});
}
}, [platformPersona]);
// Inject combined persona context for comprehensive understanding // Inject combined persona context for comprehensive understanding
useCopilotReadable({ useCopilotReadable({
description: `Complete ${platform} writing persona with linguistic fingerprint and platform optimization`, description: `Complete ${platform} writing persona with linguistic fingerprint and platform optimization`,