LinkedIn and Facebook Persona Services Implementation
This commit is contained in:
@@ -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:
|
||||||
|
|||||||
@@ -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(
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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"}}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
252
backend/services/persona_data_service.py
Normal file
252
backend/services/persona_data_service.py
Normal 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)}
|
||||||
@@ -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;
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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}
|
||||||
|
|||||||
@@ -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;
|
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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';
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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`,
|
||||||
|
|||||||
Reference in New Issue
Block a user