Added onboarding progress tracking & landing page
This commit is contained in:
@@ -14,10 +14,12 @@ import time
|
||||
from services.api_key_manager import (
|
||||
OnboardingProgress,
|
||||
get_onboarding_progress,
|
||||
get_onboarding_progress_for_user,
|
||||
StepStatus,
|
||||
StepData,
|
||||
APIKeyManager
|
||||
)
|
||||
from middleware.auth_middleware import get_current_user
|
||||
from services.validation import check_all_api_keys
|
||||
|
||||
# Pydantic models for API requests/responses
|
||||
@@ -76,220 +78,172 @@ def health_check():
|
||||
"""Health check endpoint."""
|
||||
return {"status": "healthy", "timestamp": datetime.now().isoformat()}
|
||||
|
||||
# Onboarding status endpoints
|
||||
async def get_onboarding_status():
|
||||
"""Get the current onboarding status."""
|
||||
# Batch initialization endpoint - combines multiple calls into one
|
||||
async def initialize_onboarding(current_user: Dict[str, Any] = Depends(get_current_user)):
|
||||
"""
|
||||
Single endpoint for onboarding initialization - reduces round trips.
|
||||
|
||||
Combines:
|
||||
- User information
|
||||
- Onboarding status
|
||||
- Progress details
|
||||
- Step data
|
||||
|
||||
This eliminates 3-4 separate API calls on initial load.
|
||||
"""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
user_id = str(current_user.get('id'))
|
||||
progress = get_onboarding_progress_for_user(user_id)
|
||||
|
||||
# Safety check: if all steps are completed, ensure is_completed is True
|
||||
all_steps_completed = all(s.status in [StepStatus.COMPLETED, StepStatus.SKIPPED] for s in progress.steps)
|
||||
if all_steps_completed and not progress.is_completed:
|
||||
logger.info(f"[get_onboarding_status] All steps completed but is_completed was False, fixing...")
|
||||
progress.is_completed = True
|
||||
progress.completed_at = datetime.now().isoformat()
|
||||
progress.current_step = len(progress.steps) # Ensure current_step is valid
|
||||
progress.save_progress()
|
||||
# Build comprehensive step data
|
||||
steps_data = []
|
||||
for step in progress.steps:
|
||||
steps_data.append({
|
||||
"step_number": step.step_number,
|
||||
"title": step.title,
|
||||
"description": step.description,
|
||||
"status": step.status.value,
|
||||
"completed_at": step.completed_at,
|
||||
"has_data": step.data is not None and len(step.data) > 0 if step.data else False
|
||||
})
|
||||
|
||||
logger.info(f"[get_onboarding_status] Current step: {progress.current_step}")
|
||||
logger.info(f"[get_onboarding_status] Is completed: {progress.is_completed}")
|
||||
logger.info(f"[get_onboarding_status] Steps status: {[f'{s.step_number}:{s.status.value}' for s in progress.steps]}")
|
||||
# Get next incomplete step
|
||||
next_step = progress.get_next_incomplete_step()
|
||||
|
||||
return OnboardingStatusResponse(
|
||||
is_completed=progress.is_completed,
|
||||
current_step=progress.current_step,
|
||||
completion_percentage=progress.get_completion_percentage(),
|
||||
next_step=progress.get_next_incomplete_step(),
|
||||
started_at=progress.started_at,
|
||||
completed_at=progress.completed_at,
|
||||
can_proceed_to_final=progress.can_complete_onboarding()
|
||||
response_data = {
|
||||
"user": {
|
||||
"id": user_id,
|
||||
"email": current_user.get('email'),
|
||||
"first_name": current_user.get('first_name'),
|
||||
"last_name": current_user.get('last_name'),
|
||||
"clerk_user_id": user_id # Clerk user ID is the session
|
||||
},
|
||||
"onboarding": {
|
||||
"is_completed": progress.is_completed,
|
||||
"current_step": progress.current_step,
|
||||
"completion_percentage": progress.get_completion_percentage(),
|
||||
"next_step": next_step,
|
||||
"started_at": progress.started_at,
|
||||
"last_updated": progress.last_updated,
|
||||
"completed_at": progress.completed_at,
|
||||
"can_proceed_to_final": progress.can_complete_onboarding(),
|
||||
"steps": steps_data
|
||||
},
|
||||
"session": {
|
||||
"session_id": user_id, # Clerk user ID is the session identifier
|
||||
"initialized_at": datetime.now().isoformat()
|
||||
}
|
||||
}
|
||||
|
||||
logger.info(f"Batch init successful for user {user_id}: step {progress.current_step}/{len(progress.steps)}")
|
||||
return response_data
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error in initialize_onboarding: {str(e)}", exc_info=True)
|
||||
raise HTTPException(
|
||||
status_code=500,
|
||||
detail=f"Failed to initialize onboarding: {str(e)}"
|
||||
)
|
||||
|
||||
# Onboarding status endpoints
|
||||
async def get_onboarding_status(current_user: Dict[str, Any]):
|
||||
"""Get the current onboarding status (per user)."""
|
||||
try:
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
step_service = StepManagementService()
|
||||
return await step_service.get_onboarding_status(current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding status: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_onboarding_progress_full():
|
||||
async def get_onboarding_progress_full(current_user: Dict[str, Any]):
|
||||
"""Get the full onboarding progress data."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
# Convert StepData objects to Pydantic models
|
||||
step_models = []
|
||||
for step in progress.steps:
|
||||
step_models.append(StepDataModel(
|
||||
step_number=step.step_number,
|
||||
title=step.title,
|
||||
description=step.description,
|
||||
status=step.status.value,
|
||||
completed_at=step.completed_at,
|
||||
data=step.data,
|
||||
validation_errors=step.validation_errors or []
|
||||
))
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
return OnboardingProgressModel(
|
||||
steps=step_models,
|
||||
current_step=progress.current_step,
|
||||
started_at=progress.started_at,
|
||||
last_updated=progress.last_updated,
|
||||
is_completed=progress.is_completed,
|
||||
completed_at=progress.completed_at
|
||||
)
|
||||
step_service = StepManagementService()
|
||||
return await step_service.get_onboarding_progress_full(current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding progress: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_step_data(step_number: int):
|
||||
async def get_step_data(step_number: int, current_user: Dict[str, Any]):
|
||||
"""Get data for a specific step."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
step = progress.get_step_data(step_number)
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
if not step:
|
||||
raise HTTPException(status_code=404, detail=f"Step {step_number} not found")
|
||||
|
||||
return StepDataModel(
|
||||
step_number=step.step_number,
|
||||
title=step.title,
|
||||
description=step.description,
|
||||
status=step.status.value,
|
||||
completed_at=step.completed_at,
|
||||
data=step.data,
|
||||
validation_errors=step.validation_errors or []
|
||||
)
|
||||
except HTTPException:
|
||||
raise
|
||||
step_service = StepManagementService()
|
||||
return await step_service.get_step_data(step_number, current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting step data: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def complete_step(step_number: int, request: StepCompletionRequest):
|
||||
async def complete_step(step_number: int, request: StepCompletionRequest, current_user: Dict[str, Any]):
|
||||
"""Mark a step as completed."""
|
||||
try:
|
||||
logger.info(f"[complete_step] Completing step {step_number}")
|
||||
progress = get_onboarding_progress()
|
||||
step = progress.get_step_data(step_number)
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
if not step:
|
||||
logger.error(f"[complete_step] Step {step_number} not found")
|
||||
raise HTTPException(status_code=404, detail=f"Step {step_number} not found")
|
||||
|
||||
# Mark step as completed
|
||||
progress.mark_step_completed(step_number, request.data)
|
||||
logger.info(f"[complete_step] Step {step_number} completed successfully")
|
||||
|
||||
return {
|
||||
"message": f"Step {step_number} completed successfully",
|
||||
"step_number": step_number,
|
||||
"data": request.data
|
||||
}
|
||||
step_service = StepManagementService()
|
||||
return await step_service.complete_step(step_number, request.data, current_user)
|
||||
except HTTPException:
|
||||
# Propagate known HTTP errors (e.g., 400 validation failures) without converting to 500
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.error(f"Error completing step: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def skip_step(step_number: int):
|
||||
async def skip_step(step_number: int, current_user: Dict[str, Any]):
|
||||
"""Skip a step (for optional steps)."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
step = progress.get_step_data(step_number)
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
if not step:
|
||||
raise HTTPException(status_code=404, detail=f"Step {step_number} not found")
|
||||
|
||||
# Mark step as skipped
|
||||
progress.mark_step_skipped(step_number)
|
||||
|
||||
return {
|
||||
"message": f"Step {step_number} skipped successfully",
|
||||
"step_number": step_number
|
||||
}
|
||||
except HTTPException:
|
||||
raise
|
||||
step_service = StepManagementService()
|
||||
return await step_service.skip_step(step_number, current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error skipping step: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def validate_step_access(step_number: int):
|
||||
async def validate_step_access(step_number: int, current_user: Dict[str, Any]):
|
||||
"""Validate if user can access a specific step."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
from api.onboarding_utils.step_management_service import StepManagementService
|
||||
|
||||
if not progress.can_proceed_to_step(step_number):
|
||||
return StepValidationResponse(
|
||||
can_proceed=False,
|
||||
validation_errors=[f"Cannot proceed to step {step_number}. Complete previous steps first."],
|
||||
step_status="locked"
|
||||
)
|
||||
|
||||
return StepValidationResponse(
|
||||
can_proceed=True,
|
||||
validation_errors=[],
|
||||
step_status="available"
|
||||
)
|
||||
step_service = StepManagementService()
|
||||
return await step_service.validate_step_access(step_number, current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating step access: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
# Simple cache for API keys
|
||||
_api_keys_cache = None
|
||||
_cache_timestamp = 0
|
||||
CACHE_DURATION = 30 # Cache for 30 seconds
|
||||
|
||||
async def get_api_keys():
|
||||
"""Get all configured API keys (masked)."""
|
||||
global _api_keys_cache, _cache_timestamp
|
||||
|
||||
current_time = time.time()
|
||||
|
||||
# Return cached result if still valid
|
||||
if _api_keys_cache and (current_time - _cache_timestamp) < CACHE_DURATION:
|
||||
logger.debug("Returning cached API keys")
|
||||
return _api_keys_cache
|
||||
|
||||
try:
|
||||
api_manager = APIKeyManager()
|
||||
api_manager.load_api_keys() # Load keys from environment
|
||||
api_keys = api_manager.api_keys # Get the loaded keys
|
||||
from api.onboarding_utils.api_key_management_service import APIKeyManagementService
|
||||
|
||||
# Mask the API keys for security
|
||||
masked_keys = {}
|
||||
for provider, key in api_keys.items():
|
||||
if key:
|
||||
masked_keys[provider] = "*" * (len(key) - 4) + key[-4:] if len(key) > 4 else "*" * len(key)
|
||||
else:
|
||||
masked_keys[provider] = None
|
||||
|
||||
result = {
|
||||
"api_keys": masked_keys,
|
||||
"total_providers": len(api_keys),
|
||||
"configured_providers": [k for k, v in api_keys.items() if v]
|
||||
}
|
||||
|
||||
# Cache the result
|
||||
_api_keys_cache = result
|
||||
_cache_timestamp = current_time
|
||||
|
||||
return result
|
||||
api_service = APIKeyManagementService()
|
||||
return await api_service.get_api_keys()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting API keys: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_api_keys_for_onboarding():
|
||||
"""Get all configured API keys for onboarding (unmasked)."""
|
||||
try:
|
||||
from api.onboarding_utils.api_key_management_service import APIKeyManagementService
|
||||
|
||||
api_service = APIKeyManagementService()
|
||||
return await api_service.get_api_keys_for_onboarding()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting API keys for onboarding: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def save_api_key(request: APIKeyRequest):
|
||||
"""Save an API key for a provider."""
|
||||
try:
|
||||
api_manager = APIKeyManager()
|
||||
success = api_manager.save_api_key(request.provider, request.api_key)
|
||||
from api.onboarding_utils.api_key_management_service import APIKeyManagementService
|
||||
|
||||
if success:
|
||||
return {
|
||||
"message": f"API key for {request.provider} saved successfully",
|
||||
"provider": request.provider,
|
||||
"status": "saved"
|
||||
}
|
||||
else:
|
||||
raise HTTPException(status_code=400, detail=f"Failed to save API key for {request.provider}")
|
||||
except HTTPException:
|
||||
raise
|
||||
api_service = APIKeyManagementService()
|
||||
return await api_service.save_api_key(request.provider, request.api_key, request.description)
|
||||
except Exception as e:
|
||||
logger.error(f"Error saving API key: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -297,87 +251,32 @@ async def save_api_key(request: APIKeyRequest):
|
||||
async def validate_api_keys():
|
||||
"""Validate all configured API keys."""
|
||||
try:
|
||||
api_manager = APIKeyManager()
|
||||
validation_results = check_all_api_keys(api_manager)
|
||||
from api.onboarding_utils.api_key_management_service import APIKeyManagementService
|
||||
|
||||
return {
|
||||
"validation_results": validation_results.get('results', {}),
|
||||
"all_valid": validation_results.get('all_valid', False),
|
||||
"total_providers": len(validation_results.get('results', {}))
|
||||
}
|
||||
api_service = APIKeyManagementService()
|
||||
return await api_service.validate_api_keys()
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating API keys: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def start_onboarding():
|
||||
async def start_onboarding(current_user: Dict[str, Any]):
|
||||
"""Start a new onboarding session."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
progress.reset_progress()
|
||||
from api.onboarding_utils.onboarding_control_service import OnboardingControlService
|
||||
|
||||
return {
|
||||
"message": "Onboarding started successfully",
|
||||
"current_step": progress.current_step,
|
||||
"started_at": progress.started_at
|
||||
}
|
||||
control_service = OnboardingControlService()
|
||||
return await control_service.start_onboarding(current_user)
|
||||
except Exception as e:
|
||||
logger.error(f"Error starting onboarding: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def complete_onboarding():
|
||||
async def complete_onboarding(current_user: Dict[str, Any]):
|
||||
"""Complete the onboarding process."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
from api.onboarding_utils.onboarding_completion_service import OnboardingCompletionService
|
||||
|
||||
# Check which required steps are missing
|
||||
required_steps = [1, 2, 3, 6] # Steps 1, 2, 3, and 6 are required
|
||||
missing_steps = []
|
||||
|
||||
for step_num in required_steps:
|
||||
step = progress.get_step_data(step_num)
|
||||
if step and step.status not in [StepStatus.COMPLETED, StepStatus.SKIPPED]:
|
||||
missing_steps.append(step.title)
|
||||
|
||||
if missing_steps:
|
||||
missing_steps_str = ", ".join(missing_steps)
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail=f"Cannot complete onboarding. The following steps must be completed first: {missing_steps_str}"
|
||||
)
|
||||
|
||||
# Additional validation: Check if API keys are configured
|
||||
api_manager = get_api_key_manager()
|
||||
api_keys = api_manager.get_all_keys()
|
||||
if not api_keys:
|
||||
raise HTTPException(
|
||||
status_code=400,
|
||||
detail="Cannot complete onboarding. At least one AI provider API key must be configured."
|
||||
)
|
||||
|
||||
# Generate writing persona from onboarding data
|
||||
try:
|
||||
from services.persona_analysis_service import PersonaAnalysisService
|
||||
persona_service = PersonaAnalysisService()
|
||||
|
||||
# Use user_id = 1 for now (assuming single user system)
|
||||
user_id = 1
|
||||
persona_result = persona_service.generate_persona_from_onboarding(user_id)
|
||||
|
||||
if "error" not in persona_result:
|
||||
logger.info(f"✅ Writing persona generated during onboarding completion: {persona_result.get('persona_id')}")
|
||||
else:
|
||||
logger.warning(f"⚠️ Persona generation failed during onboarding: {persona_result['error']}")
|
||||
except Exception as e:
|
||||
logger.warning(f"⚠️ Non-critical error generating persona during onboarding: {str(e)}")
|
||||
|
||||
progress.complete_onboarding()
|
||||
|
||||
return {
|
||||
"message": "Onboarding completed successfully",
|
||||
"completed_at": progress.completed_at,
|
||||
"completion_percentage": 100.0,
|
||||
"persona_generated": "error" not in persona_result if 'persona_result' in locals() else False
|
||||
}
|
||||
completion_service = OnboardingCompletionService()
|
||||
return await completion_service.complete_onboarding(current_user)
|
||||
except HTTPException:
|
||||
raise
|
||||
except Exception as e:
|
||||
@@ -387,14 +286,10 @@ async def complete_onboarding():
|
||||
async def reset_onboarding():
|
||||
"""Reset the onboarding progress."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
progress.reset_progress()
|
||||
from api.onboarding_utils.onboarding_control_service import OnboardingControlService
|
||||
|
||||
return {
|
||||
"message": "Onboarding progress reset successfully",
|
||||
"current_step": progress.current_step,
|
||||
"started_at": progress.started_at
|
||||
}
|
||||
control_service = OnboardingControlService()
|
||||
return await control_service.reset_onboarding()
|
||||
except Exception as e:
|
||||
logger.error(f"Error resetting onboarding: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -402,124 +297,56 @@ async def reset_onboarding():
|
||||
async def get_resume_info():
|
||||
"""Get information for resuming onboarding."""
|
||||
try:
|
||||
progress = get_onboarding_progress()
|
||||
from api.onboarding_utils.onboarding_control_service import OnboardingControlService
|
||||
|
||||
if progress.is_completed:
|
||||
return {
|
||||
"can_resume": False,
|
||||
"message": "Onboarding is already completed",
|
||||
"completion_percentage": 100.0
|
||||
}
|
||||
|
||||
resume_step = progress.get_resume_step()
|
||||
|
||||
return {
|
||||
"can_resume": True,
|
||||
"resume_step": resume_step,
|
||||
"current_step": progress.current_step,
|
||||
"completion_percentage": progress.get_completion_percentage(),
|
||||
"started_at": progress.started_at,
|
||||
"last_updated": progress.last_updated
|
||||
}
|
||||
control_service = OnboardingControlService()
|
||||
return await control_service.get_resume_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting resume info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
def get_onboarding_config():
|
||||
"""Get onboarding configuration and requirements."""
|
||||
return {
|
||||
"total_steps": 6,
|
||||
"steps": [
|
||||
{
|
||||
"number": 1,
|
||||
"title": "AI LLM Providers",
|
||||
"description": "Configure AI language model providers",
|
||||
"required": True,
|
||||
"providers": ["openai", "gemini", "anthropic"]
|
||||
},
|
||||
{
|
||||
"number": 2,
|
||||
"title": "Website Analysis",
|
||||
"description": "Set up website analysis and crawling",
|
||||
"required": True
|
||||
},
|
||||
{
|
||||
"number": 3,
|
||||
"title": "AI Research",
|
||||
"description": "Configure AI research capabilities",
|
||||
"required": True
|
||||
},
|
||||
{
|
||||
"number": 4,
|
||||
"title": "Personalization",
|
||||
"description": "Set up personalization features",
|
||||
"required": False
|
||||
},
|
||||
{
|
||||
"number": 5,
|
||||
"title": "Integrations",
|
||||
"description": "Configure ALwrity integrations",
|
||||
"required": False
|
||||
},
|
||||
{
|
||||
"number": 6,
|
||||
"title": "Complete Setup",
|
||||
"description": "Finalize and complete onboarding",
|
||||
"required": True
|
||||
}
|
||||
],
|
||||
"requirements": {
|
||||
"min_api_keys": 1,
|
||||
"required_providers": ["openai"],
|
||||
"optional_providers": ["gemini", "anthropic"]
|
||||
}
|
||||
}
|
||||
try:
|
||||
from api.onboarding_utils.onboarding_config_service import OnboardingConfigService
|
||||
|
||||
config_service = OnboardingConfigService()
|
||||
return config_service.get_onboarding_config()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding config: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
# Add new endpoints for enhanced functionality
|
||||
|
||||
async def get_provider_setup_info(provider: str):
|
||||
"""Get setup information for a specific provider."""
|
||||
try:
|
||||
providers_info = get_all_providers_info()
|
||||
if provider in providers_info:
|
||||
return providers_info[provider]
|
||||
else:
|
||||
raise HTTPException(status_code=404, detail=f"Provider {provider} not found")
|
||||
from api.onboarding_utils.onboarding_config_service import OnboardingConfigService
|
||||
|
||||
config_service = OnboardingConfigService()
|
||||
return await config_service.get_provider_setup_info(provider)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting provider setup info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_all_providers_info():
|
||||
"""Get setup information for all providers."""
|
||||
return {
|
||||
"openai": {
|
||||
"name": "OpenAI",
|
||||
"description": "GPT-4 and GPT-3.5 models for content generation",
|
||||
"setup_url": "https://platform.openai.com/api-keys",
|
||||
"required_fields": ["api_key"],
|
||||
"optional_fields": ["organization_id"]
|
||||
},
|
||||
"gemini": {
|
||||
"name": "Google Gemini",
|
||||
"description": "Google's advanced AI models for content creation",
|
||||
"setup_url": "https://makersuite.google.com/app/apikey",
|
||||
"required_fields": ["api_key"],
|
||||
"optional_fields": []
|
||||
},
|
||||
"anthropic": {
|
||||
"name": "Anthropic",
|
||||
"description": "Claude models for sophisticated content generation",
|
||||
"setup_url": "https://console.anthropic.com/",
|
||||
"required_fields": ["api_key"],
|
||||
"optional_fields": []
|
||||
}
|
||||
}
|
||||
try:
|
||||
from api.onboarding_utils.onboarding_config_service import OnboardingConfigService
|
||||
|
||||
config_service = OnboardingConfigService()
|
||||
return config_service.get_all_providers_info()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting all providers info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def validate_provider_key(provider: str, request: APIKeyRequest):
|
||||
"""Validate a specific provider's API key."""
|
||||
try:
|
||||
result = await validate_api_key(provider, request.api_key)
|
||||
return result
|
||||
from api.onboarding_utils.onboarding_config_service import OnboardingConfigService
|
||||
|
||||
config_service = OnboardingConfigService()
|
||||
return await config_service.validate_provider_key(provider, request.api_key)
|
||||
except Exception as e:
|
||||
logger.error(f"Error validating provider key: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -527,122 +354,50 @@ async def validate_provider_key(provider: str, request: APIKeyRequest):
|
||||
async def get_enhanced_validation_status():
|
||||
"""Get enhanced validation status for all configured services."""
|
||||
try:
|
||||
return await check_all_api_keys(get_api_key_manager())
|
||||
from api.onboarding_utils.onboarding_config_service import OnboardingConfigService
|
||||
|
||||
config_service = OnboardingConfigService()
|
||||
return await config_service.get_enhanced_validation_status()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting enhanced validation status: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
# New endpoints for FinalStep data loading
|
||||
async def get_onboarding_summary():
|
||||
"""Get comprehensive onboarding summary for FinalStep."""
|
||||
async def get_onboarding_summary(current_user: Dict[str, Any]):
|
||||
"""Get comprehensive onboarding summary for FinalStep with user isolation."""
|
||||
try:
|
||||
from services.database import get_db
|
||||
from services.website_analysis_service import WebsiteAnalysisService
|
||||
from services.research_preferences_service import ResearchPreferencesService
|
||||
from services.persona_analysis_service import PersonaAnalysisService
|
||||
from api.onboarding_utils.onboarding_summary_service import OnboardingSummaryService
|
||||
|
||||
# Get current session (assuming session ID 1 for now)
|
||||
session_id = 1
|
||||
user_id = 1 # Assuming single user system for now
|
||||
|
||||
# Get API keys
|
||||
api_manager = get_api_key_manager()
|
||||
api_keys = api_manager.get_all_keys()
|
||||
|
||||
# Get website analysis data
|
||||
db = next(get_db())
|
||||
website_service = WebsiteAnalysisService(db)
|
||||
website_analysis = website_service.get_analysis_by_session(session_id)
|
||||
|
||||
# Get research preferences
|
||||
research_service = ResearchPreferencesService(db)
|
||||
research_preferences = research_service.get_research_preferences(session_id)
|
||||
|
||||
# Get personalization settings (from research preferences)
|
||||
personalization_settings = None
|
||||
if research_preferences:
|
||||
personalization_settings = {
|
||||
'writing_style': research_preferences.get('writing_style', {}).get('tone', 'Professional'),
|
||||
'tone': research_preferences.get('writing_style', {}).get('voice', 'Formal'),
|
||||
'brand_voice': research_preferences.get('writing_style', {}).get('complexity', 'Trustworthy and Expert')
|
||||
}
|
||||
|
||||
# Check persona generation readiness
|
||||
persona_service = PersonaAnalysisService()
|
||||
persona_readiness = None
|
||||
try:
|
||||
# Check if persona can be generated
|
||||
onboarding_data = persona_service._collect_onboarding_data(user_id)
|
||||
if onboarding_data:
|
||||
data_sufficiency = persona_service._calculate_data_sufficiency(onboarding_data)
|
||||
persona_readiness = {
|
||||
"ready": data_sufficiency >= 50.0,
|
||||
"data_sufficiency": data_sufficiency,
|
||||
"can_generate": website_analysis is not None
|
||||
}
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not check persona readiness: {str(e)}")
|
||||
persona_readiness = {"ready": False, "error": str(e)}
|
||||
|
||||
return {
|
||||
"api_keys": api_keys,
|
||||
"website_url": website_analysis.get('website_url') if website_analysis else None,
|
||||
"style_analysis": website_analysis.get('style_analysis') if website_analysis else None,
|
||||
"research_preferences": research_preferences,
|
||||
"personalization_settings": personalization_settings,
|
||||
"persona_readiness": persona_readiness,
|
||||
"integrations": {}, # TODO: Implement integrations data
|
||||
"capabilities": {
|
||||
"ai_content": len(api_keys) > 0,
|
||||
"style_analysis": website_analysis is not None,
|
||||
"research_tools": research_preferences is not None,
|
||||
"personalization": personalization_settings is not None,
|
||||
"persona_generation": persona_readiness.get("ready", False) if persona_readiness else False,
|
||||
"integrations": False # TODO: Implement
|
||||
}
|
||||
}
|
||||
user_id = str(current_user.get('id'))
|
||||
summary_service = OnboardingSummaryService(user_id)
|
||||
logger.info(f"Getting onboarding summary for user {user_id}")
|
||||
return await summary_service.get_onboarding_summary()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting onboarding summary: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_website_analysis_data():
|
||||
"""Get website analysis data for FinalStep."""
|
||||
async def get_website_analysis_data(current_user: Dict[str, Any]):
|
||||
"""Get website analysis data for FinalStep with user isolation."""
|
||||
try:
|
||||
from services.database import get_db
|
||||
from services.website_analysis_service import WebsiteAnalysisService
|
||||
from api.onboarding_utils.onboarding_summary_service import OnboardingSummaryService
|
||||
|
||||
session_id = 1
|
||||
db = next(get_db())
|
||||
website_service = WebsiteAnalysisService(db)
|
||||
analysis = website_service.get_analysis_by_session(session_id)
|
||||
|
||||
if analysis:
|
||||
return {
|
||||
"website_url": analysis.get('website_url'),
|
||||
"style_analysis": analysis.get('style_analysis'),
|
||||
"style_patterns": analysis.get('style_patterns'),
|
||||
"style_guidelines": analysis.get('style_guidelines'),
|
||||
"status": analysis.get('status'),
|
||||
"completed_at": analysis.get('created_at')
|
||||
}
|
||||
else:
|
||||
return None
|
||||
user_id = str(current_user.get('id'))
|
||||
summary_service = OnboardingSummaryService(user_id)
|
||||
logger.info(f"Getting website analysis data for user {user_id}")
|
||||
return await summary_service.get_website_analysis_data()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting website analysis data: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
async def get_research_preferences_data():
|
||||
"""Get research preferences data for FinalStep."""
|
||||
async def get_research_preferences_data(current_user: Dict[str, Any]):
|
||||
"""Get research preferences data for FinalStep with user isolation."""
|
||||
try:
|
||||
from services.database import get_db
|
||||
from services.research_preferences_service import ResearchPreferencesService
|
||||
from api.onboarding_utils.onboarding_summary_service import OnboardingSummaryService
|
||||
|
||||
session_id = 1
|
||||
db = next(get_db())
|
||||
research_service = ResearchPreferencesService(db)
|
||||
preferences = research_service.get_research_preferences(session_id)
|
||||
|
||||
return preferences
|
||||
user_id = str(current_user.get('id'))
|
||||
summary_service = OnboardingSummaryService(user_id)
|
||||
logger.info(f"Getting research preferences data for user {user_id}")
|
||||
return await summary_service.get_research_preferences_data()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting research preferences data: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -652,8 +407,10 @@ async def get_research_preferences_data():
|
||||
async def check_persona_generation_readiness(user_id: int = 1):
|
||||
"""Check if user has sufficient data for persona generation."""
|
||||
try:
|
||||
from api.persona import validate_persona_generation_readiness
|
||||
return await validate_persona_generation_readiness(user_id)
|
||||
from api.onboarding_utils.persona_management_service import PersonaManagementService
|
||||
|
||||
persona_service = PersonaManagementService()
|
||||
return await persona_service.check_persona_generation_readiness(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error checking persona readiness: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -661,8 +418,10 @@ async def check_persona_generation_readiness(user_id: int = 1):
|
||||
async def generate_persona_preview(user_id: int = 1):
|
||||
"""Generate a preview of the writing persona without saving."""
|
||||
try:
|
||||
from api.persona import generate_persona_preview
|
||||
return await generate_persona_preview(user_id)
|
||||
from api.onboarding_utils.persona_management_service import PersonaManagementService
|
||||
|
||||
persona_service = PersonaManagementService()
|
||||
return await persona_service.generate_persona_preview(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating persona preview: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -670,9 +429,10 @@ async def generate_persona_preview(user_id: int = 1):
|
||||
async def generate_writing_persona(user_id: int = 1):
|
||||
"""Generate and save a writing persona from onboarding data."""
|
||||
try:
|
||||
from api.persona import generate_persona, PersonaGenerationRequest
|
||||
request = PersonaGenerationRequest(force_regenerate=False)
|
||||
return await generate_persona(user_id, request)
|
||||
from api.onboarding_utils.persona_management_service import PersonaManagementService
|
||||
|
||||
persona_service = PersonaManagementService()
|
||||
return await persona_service.generate_writing_persona(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error generating writing persona: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -680,8 +440,10 @@ async def generate_writing_persona(user_id: int = 1):
|
||||
async def get_user_writing_personas(user_id: int = 1):
|
||||
"""Get all writing personas for the user."""
|
||||
try:
|
||||
from api.persona import get_user_personas
|
||||
return await get_user_personas(user_id)
|
||||
from api.onboarding_utils.persona_management_service import PersonaManagementService
|
||||
|
||||
persona_service = PersonaManagementService()
|
||||
return await persona_service.get_user_writing_personas(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting user personas: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
@@ -690,13 +452,10 @@ async def get_user_writing_personas(user_id: int = 1):
|
||||
async def save_business_info(business_info: 'BusinessInfoRequest'):
|
||||
"""Save business information for users without websites."""
|
||||
try:
|
||||
from models.business_info_request import BusinessInfoRequest
|
||||
from services.business_info_service import business_info_service
|
||||
from api.onboarding_utils.business_info_service import BusinessInfoService
|
||||
|
||||
logger.info(f"🔄 Saving business info for user_id: {business_info.user_id}")
|
||||
result = business_info_service.save_business_info(business_info)
|
||||
logger.success(f"✅ Business info saved successfully for user_id: {business_info.user_id}")
|
||||
return result
|
||||
business_service = BusinessInfoService()
|
||||
return await business_service.save_business_info(business_info)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error saving business info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to save business info: {str(e)}")
|
||||
@@ -704,18 +463,10 @@ async def save_business_info(business_info: 'BusinessInfoRequest'):
|
||||
async def get_business_info(business_info_id: int):
|
||||
"""Get business information by ID."""
|
||||
try:
|
||||
from services.business_info_service import business_info_service
|
||||
from api.onboarding_utils.business_info_service import BusinessInfoService
|
||||
|
||||
logger.info(f"🔄 Getting business info for ID: {business_info_id}")
|
||||
result = business_info_service.get_business_info(business_info_id)
|
||||
if result:
|
||||
logger.success(f"✅ Business info retrieved for ID: {business_info_id}")
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"⚠️ No business info found for ID: {business_info_id}")
|
||||
raise HTTPException(status_code=404, detail="Business info not found")
|
||||
except HTTPException:
|
||||
raise
|
||||
business_service = BusinessInfoService()
|
||||
return await business_service.get_business_info(business_info_id)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting business info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get business info: {str(e)}")
|
||||
@@ -723,18 +474,10 @@ async def get_business_info(business_info_id: int):
|
||||
async def get_business_info_by_user(user_id: int):
|
||||
"""Get business information by user ID."""
|
||||
try:
|
||||
from services.business_info_service import business_info_service
|
||||
from api.onboarding_utils.business_info_service import BusinessInfoService
|
||||
|
||||
logger.info(f"🔄 Getting business info for user ID: {user_id}")
|
||||
result = business_info_service.get_business_info_by_user(user_id)
|
||||
if result:
|
||||
logger.success(f"✅ Business info retrieved for user ID: {user_id}")
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"⚠️ No business info found for user ID: {user_id}")
|
||||
raise HTTPException(status_code=404, detail="Business info not found")
|
||||
except HTTPException:
|
||||
raise
|
||||
business_service = BusinessInfoService()
|
||||
return await business_service.get_business_info_by_user(user_id)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error getting business info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to get business info: {str(e)}")
|
||||
@@ -742,19 +485,10 @@ async def get_business_info_by_user(user_id: int):
|
||||
async def update_business_info(business_info_id: int, business_info: 'BusinessInfoRequest'):
|
||||
"""Update business information."""
|
||||
try:
|
||||
from models.business_info_request import BusinessInfoRequest
|
||||
from services.business_info_service import business_info_service
|
||||
from api.onboarding_utils.business_info_service import BusinessInfoService
|
||||
|
||||
logger.info(f"🔄 Updating business info for ID: {business_info_id}")
|
||||
result = business_info_service.update_business_info(business_info_id, business_info)
|
||||
if result:
|
||||
logger.success(f"✅ Business info updated for ID: {business_info_id}")
|
||||
return result
|
||||
else:
|
||||
logger.warning(f"⚠️ No business info found to update for ID: {business_info_id}")
|
||||
raise HTTPException(status_code=404, detail="Business info not found")
|
||||
except HTTPException:
|
||||
raise
|
||||
business_service = BusinessInfoService()
|
||||
return await business_service.update_business_info(business_info_id, business_info)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Error updating business info: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail=f"Failed to update business info: {str(e)}")
|
||||
|
||||
Reference in New Issue
Block a user