ALwrity Backend and Frontend - Stability and Error Handling Improvements
This commit is contained in:
@@ -1,108 +0,0 @@
|
||||
# Stability AI Configuration Example
|
||||
# Copy this file to .env and fill in your actual values
|
||||
|
||||
# Required: Your Stability AI API Key
|
||||
# Get your API key from: https://platform.stability.ai/account/keys
|
||||
STABILITY_API_KEY=your_stability_api_key_here
|
||||
|
||||
# Optional: Stability AI API Base URL (default: https://api.stability.ai)
|
||||
STABILITY_BASE_URL=https://api.stability.ai
|
||||
|
||||
# Optional: Request timeout in seconds (default: 300)
|
||||
STABILITY_TIMEOUT=300
|
||||
|
||||
# Optional: Maximum retries for failed requests (default: 3)
|
||||
STABILITY_MAX_RETRIES=3
|
||||
|
||||
# Optional: Maximum file size for uploads in bytes (default: 10MB)
|
||||
STABILITY_MAX_FILE_SIZE=10485760
|
||||
|
||||
# Optional: Enable debug mode for detailed logging (default: false)
|
||||
STABILITY_DEBUG=false
|
||||
|
||||
# Optional: Enable caching for responses (default: true)
|
||||
STABILITY_ENABLE_CACHE=true
|
||||
|
||||
# Optional: Cache duration in seconds (default: 3600)
|
||||
STABILITY_CACHE_DURATION=3600
|
||||
|
||||
# Optional: Enable rate limiting (default: true)
|
||||
STABILITY_ENABLE_RATE_LIMIT=true
|
||||
|
||||
# Optional: Rate limit - requests per window (default: 150)
|
||||
STABILITY_RATE_LIMIT_REQUESTS=150
|
||||
|
||||
# Optional: Rate limit window in seconds (default: 10)
|
||||
STABILITY_RATE_LIMIT_WINDOW=10
|
||||
|
||||
# Optional: Enable content moderation (default: true)
|
||||
STABILITY_ENABLE_MODERATION=true
|
||||
|
||||
# Optional: Enable request logging (default: true)
|
||||
STABILITY_ENABLE_LOGGING=true
|
||||
|
||||
# Optional: Maximum log entries to keep in memory (default: 1000)
|
||||
STABILITY_MAX_LOG_ENTRIES=1000
|
||||
|
||||
# Optional: Enable experimental features (default: false)
|
||||
STABILITY_ENABLE_EXPERIMENTAL=false
|
||||
|
||||
# Optional: Default output format for images (default: png)
|
||||
STABILITY_DEFAULT_IMAGE_FORMAT=png
|
||||
|
||||
# Optional: Default output format for audio (default: mp3)
|
||||
STABILITY_DEFAULT_AUDIO_FORMAT=mp3
|
||||
|
||||
# Optional: Enable webhook support (default: false)
|
||||
STABILITY_ENABLE_WEBHOOKS=false
|
||||
|
||||
# Optional: Webhook URL for generation completion notifications
|
||||
STABILITY_WEBHOOK_URL=
|
||||
|
||||
# Optional: Webhook secret for signature validation
|
||||
STABILITY_WEBHOOK_SECRET=
|
||||
|
||||
# Optional: Enable batch processing (default: true)
|
||||
STABILITY_ENABLE_BATCH=true
|
||||
|
||||
# Optional: Maximum batch size (default: 10)
|
||||
STABILITY_MAX_BATCH_SIZE=10
|
||||
|
||||
# Optional: Enable quality analysis features (default: true)
|
||||
STABILITY_ENABLE_QUALITY_ANALYSIS=true
|
||||
|
||||
# Optional: Enable prompt optimization features (default: true)
|
||||
STABILITY_ENABLE_PROMPT_OPTIMIZATION=true
|
||||
|
||||
# Optional: Default creativity level for upscaling (default: 0.35)
|
||||
STABILITY_DEFAULT_CREATIVITY=0.35
|
||||
|
||||
# Optional: Default control strength for control operations (default: 0.7)
|
||||
STABILITY_DEFAULT_CONTROL_STRENGTH=0.7
|
||||
|
||||
# Optional: Default style fidelity for style operations (default: 0.5)
|
||||
STABILITY_DEFAULT_STYLE_FIDELITY=0.5
|
||||
|
||||
# Optional: Enable automatic image format optimization (default: true)
|
||||
STABILITY_AUTO_OPTIMIZE_FORMAT=true
|
||||
|
||||
# Optional: Enable automatic parameter optimization (default: true)
|
||||
STABILITY_AUTO_OPTIMIZE_PARAMS=true
|
||||
|
||||
# Optional: Default model for generate operations (default: core)
|
||||
STABILITY_DEFAULT_GENERATE_MODEL=core
|
||||
|
||||
# Optional: Default model for upscale operations (default: fast)
|
||||
STABILITY_DEFAULT_UPSCALE_MODEL=fast
|
||||
|
||||
# Optional: Enable cost tracking and warnings (default: true)
|
||||
STABILITY_ENABLE_COST_TRACKING=true
|
||||
|
||||
# Optional: Credit warning threshold (default: 10)
|
||||
STABILITY_CREDIT_WARNING_THRESHOLD=10
|
||||
|
||||
# Optional: Enable performance monitoring (default: true)
|
||||
STABILITY_ENABLE_MONITORING=true
|
||||
|
||||
# Optional: Performance monitoring interval in seconds (default: 60)
|
||||
STABILITY_MONITORING_INTERVAL=60
|
||||
@@ -16,72 +16,98 @@ class DatabaseSetup:
|
||||
|
||||
def setup_essential_tables(self) -> bool:
|
||||
"""Set up essential database tables."""
|
||||
print("📊 Setting up essential database tables...")
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if verbose:
|
||||
print("📊 Setting up essential database tables...")
|
||||
|
||||
try:
|
||||
from services.database import init_database, engine
|
||||
|
||||
# Initialize database connection
|
||||
init_database()
|
||||
print(" ✅ Database connection initialized")
|
||||
if verbose:
|
||||
print(" ✅ Database connection initialized")
|
||||
|
||||
# Create essential tables
|
||||
self._create_monitoring_tables()
|
||||
self._create_subscription_tables()
|
||||
self._create_persona_tables()
|
||||
|
||||
print("✅ Essential database tables created")
|
||||
if verbose:
|
||||
print("✅ Essential database tables created")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Warning: Database setup failed: {e}")
|
||||
if self.production_mode:
|
||||
print(" Continuing in production mode...")
|
||||
return True
|
||||
else:
|
||||
print(" This may affect functionality")
|
||||
return True # Don't fail startup for database issues
|
||||
if verbose:
|
||||
print(f"⚠️ Warning: Database setup failed: {e}")
|
||||
if self.production_mode:
|
||||
print(" Continuing in production mode...")
|
||||
else:
|
||||
print(" This may affect functionality")
|
||||
return True # Don't fail startup for database issues
|
||||
|
||||
def _create_monitoring_tables(self) -> bool:
|
||||
"""Create API monitoring tables."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
from models.api_monitoring import Base as MonitoringBase
|
||||
MonitoringBase.metadata.create_all(bind=engine)
|
||||
print(" ✅ Monitoring tables created")
|
||||
if verbose:
|
||||
print(" ✅ Monitoring tables created")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Monitoring tables failed: {e}")
|
||||
if verbose:
|
||||
print(f" ⚠️ Monitoring tables failed: {e}")
|
||||
return True # Non-critical
|
||||
|
||||
def _create_subscription_tables(self) -> bool:
|
||||
"""Create subscription and billing tables."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
from models.subscription_models import Base as SubscriptionBase
|
||||
SubscriptionBase.metadata.create_all(bind=engine)
|
||||
print(" ✅ Subscription tables created")
|
||||
if verbose:
|
||||
print(" ✅ Subscription tables created")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Subscription tables failed: {e}")
|
||||
if verbose:
|
||||
print(f" ⚠️ Subscription tables failed: {e}")
|
||||
return True # Non-critical
|
||||
|
||||
def _create_persona_tables(self) -> bool:
|
||||
"""Create persona analysis tables."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
from models.persona_models import Base as PersonaBase
|
||||
PersonaBase.metadata.create_all(bind=engine)
|
||||
print(" ✅ Persona tables created")
|
||||
if verbose:
|
||||
print(" ✅ Persona tables created")
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Persona tables failed: {e}")
|
||||
if verbose:
|
||||
print(f" ⚠️ Persona tables failed: {e}")
|
||||
return True # Non-critical
|
||||
|
||||
def verify_tables(self) -> bool:
|
||||
"""Verify that essential tables exist."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if self.production_mode:
|
||||
print("⚠️ Skipping table verification in production mode")
|
||||
if verbose:
|
||||
print("⚠️ Skipping table verification in production mode")
|
||||
return True
|
||||
|
||||
print("🔍 Verifying database tables...")
|
||||
if verbose:
|
||||
print("🔍 Verifying database tables...")
|
||||
|
||||
try:
|
||||
from services.database import engine
|
||||
@@ -97,11 +123,13 @@ class DatabaseSetup:
|
||||
]
|
||||
|
||||
existing_tables = [table for table in essential_tables if table in tables]
|
||||
print(f" ✅ Found tables: {existing_tables}")
|
||||
if verbose:
|
||||
print(f" ✅ Found tables: {existing_tables}")
|
||||
|
||||
if len(existing_tables) < len(essential_tables):
|
||||
missing = [table for table in essential_tables if table not in existing_tables]
|
||||
print(f" ⚠️ Missing tables: {missing}")
|
||||
if verbose:
|
||||
print(f" ⚠️ Missing tables: {missing}")
|
||||
|
||||
return True
|
||||
|
||||
@@ -124,11 +152,11 @@ class DatabaseSetup:
|
||||
# Set up billing tables
|
||||
self._setup_billing_tables()
|
||||
|
||||
print("✅ Advanced database features configured")
|
||||
logger.debug("✅ Advanced database features configured")
|
||||
return True
|
||||
|
||||
except Exception as e:
|
||||
print(f"⚠️ Advanced table setup failed: {e}")
|
||||
logger.warning(f"Advanced table setup failed: {e}")
|
||||
return True # Non-critical
|
||||
|
||||
def _setup_monitoring_tables(self) -> bool:
|
||||
@@ -157,16 +185,16 @@ class DatabaseSetup:
|
||||
|
||||
# Check if tables already exist
|
||||
if check_existing_tables(engine):
|
||||
print(" ✅ Billing tables already exist")
|
||||
logger.debug("✅ Billing tables already exist")
|
||||
return True
|
||||
|
||||
if create_billing_tables():
|
||||
print(" ✅ Billing tables created")
|
||||
logger.debug("✅ Billing tables created")
|
||||
return True
|
||||
else:
|
||||
print(" ⚠️ Billing setup failed")
|
||||
logger.warning("Billing setup failed")
|
||||
return True # Non-critical
|
||||
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Billing setup failed: {e}")
|
||||
logger.warning(f"Billing setup failed: {e}")
|
||||
return True # Non-critical
|
||||
|
||||
@@ -51,40 +51,54 @@ class DependencyManager:
|
||||
|
||||
def check_critical_dependencies(self) -> Tuple[bool, List[str]]:
|
||||
"""Check if critical dependencies are available."""
|
||||
print("🔍 Checking critical dependencies...")
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if verbose:
|
||||
print("🔍 Checking critical dependencies...")
|
||||
|
||||
missing_packages = []
|
||||
|
||||
for package in self.critical_packages:
|
||||
try:
|
||||
__import__(package.replace('-', '_'))
|
||||
print(f" ✅ {package}")
|
||||
if verbose:
|
||||
print(f" ✅ {package}")
|
||||
except ImportError:
|
||||
print(f" ❌ {package} - MISSING")
|
||||
if verbose:
|
||||
print(f" ❌ {package} - MISSING")
|
||||
missing_packages.append(package)
|
||||
|
||||
if missing_packages:
|
||||
print(f"❌ Missing critical packages: {', '.join(missing_packages)}")
|
||||
if verbose:
|
||||
print(f"❌ Missing critical packages: {', '.join(missing_packages)}")
|
||||
return False, missing_packages
|
||||
|
||||
print("✅ All critical dependencies available!")
|
||||
if verbose:
|
||||
print("✅ All critical dependencies available!")
|
||||
return True, []
|
||||
|
||||
def check_optional_dependencies(self) -> Tuple[bool, List[str]]:
|
||||
"""Check if optional dependencies are available."""
|
||||
print("🔍 Checking optional dependencies...")
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if verbose:
|
||||
print("🔍 Checking optional dependencies...")
|
||||
|
||||
missing_packages = []
|
||||
|
||||
for package in self.optional_packages:
|
||||
try:
|
||||
__import__(package.replace('-', '_'))
|
||||
print(f" ✅ {package}")
|
||||
if verbose:
|
||||
print(f" ✅ {package}")
|
||||
except ImportError:
|
||||
print(f" ⚠️ {package} - MISSING (optional)")
|
||||
if verbose:
|
||||
print(f" ⚠️ {package} - MISSING (optional)")
|
||||
missing_packages.append(package)
|
||||
|
||||
if missing_packages:
|
||||
if missing_packages and verbose:
|
||||
print(f"⚠️ Missing optional packages: {', '.join(missing_packages)}")
|
||||
print(" Some features may not be available")
|
||||
|
||||
|
||||
@@ -28,21 +28,29 @@ class EnvironmentSetup:
|
||||
|
||||
def setup_directories(self) -> bool:
|
||||
"""Create necessary directories for ALwrity."""
|
||||
print("📁 Setting up directories...")
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if verbose:
|
||||
print("📁 Setting up directories...")
|
||||
|
||||
if not self.required_directories:
|
||||
print(" ⚠️ Skipping directory creation in production mode")
|
||||
if verbose:
|
||||
print(" ⚠️ Skipping directory creation in production mode")
|
||||
return True
|
||||
|
||||
for directory in self.required_directories:
|
||||
try:
|
||||
Path(directory).mkdir(parents=True, exist_ok=True)
|
||||
print(f" ✅ Created: {directory}")
|
||||
if verbose:
|
||||
print(f" ✅ Created: {directory}")
|
||||
except Exception as e:
|
||||
print(f" ❌ Failed to create {directory}: {e}")
|
||||
if verbose:
|
||||
print(f" ❌ Failed to create {directory}: {e}")
|
||||
return False
|
||||
|
||||
print("✅ All directories created successfully")
|
||||
if verbose:
|
||||
print("✅ All directories created successfully")
|
||||
return True
|
||||
|
||||
def setup_environment_variables(self) -> bool:
|
||||
|
||||
@@ -18,22 +18,31 @@ class RouterManager:
|
||||
|
||||
def include_router_safely(self, router, router_name: str = None) -> bool:
|
||||
"""Include a router safely with error handling."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
self.app.include_router(router)
|
||||
router_name = router_name or getattr(router, 'prefix', 'unknown')
|
||||
self.included_routers.append(router_name)
|
||||
logger.info(f"✅ Router included successfully: {router_name}")
|
||||
if verbose:
|
||||
logger.info(f"✅ Router included successfully: {router_name}")
|
||||
return True
|
||||
except Exception as e:
|
||||
router_name = router_name or 'unknown'
|
||||
self.failed_routers.append({"name": router_name, "error": str(e)})
|
||||
logger.warning(f"❌ Router inclusion failed: {router_name} - {e}")
|
||||
if verbose:
|
||||
logger.warning(f"❌ Router inclusion failed: {router_name} - {e}")
|
||||
return False
|
||||
|
||||
def include_core_routers(self) -> bool:
|
||||
"""Include core application routers."""
|
||||
import os
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
try:
|
||||
logger.info("Including core routers...")
|
||||
if verbose:
|
||||
logger.info("Including core routers...")
|
||||
|
||||
# Component logic router
|
||||
from api.component_logic import router as component_logic_router
|
||||
|
||||
@@ -70,129 +70,103 @@ class OnboardingSummaryService:
|
||||
try:
|
||||
db = next(get_db())
|
||||
api_keys = self.db_service.get_api_keys(self.user_id, db)
|
||||
logger.info(f"Retrieved {len(api_keys)} API keys from database for user {self.user_id}")
|
||||
return api_keys
|
||||
db.close()
|
||||
|
||||
if not api_keys:
|
||||
return {
|
||||
"openai": {"configured": False, "value": None},
|
||||
"anthropic": {"configured": False, "value": None},
|
||||
"google": {"configured": False, "value": None}
|
||||
}
|
||||
|
||||
return {
|
||||
"openai": {
|
||||
"configured": bool(api_keys.get('openai_api_key')),
|
||||
"value": api_keys.get('openai_api_key')[:8] + "..." if api_keys.get('openai_api_key') else None
|
||||
},
|
||||
"anthropic": {
|
||||
"configured": bool(api_keys.get('anthropic_api_key')),
|
||||
"value": api_keys.get('anthropic_api_key')[:8] + "..." if api_keys.get('anthropic_api_key') else None
|
||||
},
|
||||
"google": {
|
||||
"configured": bool(api_keys.get('google_api_key')),
|
||||
"value": api_keys.get('google_api_key')[:8] + "..." if api_keys.get('google_api_key') else None
|
||||
}
|
||||
}
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting API keys from database: {e}")
|
||||
return {}
|
||||
logger.error(f"Error getting API keys: {str(e)}")
|
||||
return {
|
||||
"openai": {"configured": False, "value": None},
|
||||
"anthropic": {"configured": False, "value": None},
|
||||
"google": {"configured": False, "value": None}
|
||||
}
|
||||
|
||||
def _get_website_analysis(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get website analysis data from database (Step 2)."""
|
||||
"""Get website analysis data from database."""
|
||||
try:
|
||||
db = next(get_db())
|
||||
website_data = self.db_service.get_website_analysis(self.user_id, db)
|
||||
if website_data:
|
||||
logger.info(f"Retrieved website analysis from database for user {self.user_id}")
|
||||
else:
|
||||
logger.warning(f"No website analysis found in database for user {self.user_id}")
|
||||
db.close()
|
||||
return website_data
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting website analysis from database: {e}")
|
||||
logger.error(f"Error getting website analysis: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_research_preferences(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get research preferences data from database (Step 3)."""
|
||||
"""Get research preferences from database."""
|
||||
try:
|
||||
db = next(get_db())
|
||||
research_data = self.db_service.get_research_preferences(self.user_id, db)
|
||||
if research_data:
|
||||
logger.info(f"Retrieved research preferences from database for user {self.user_id}")
|
||||
else:
|
||||
logger.warning(f"No research preferences found in database for user {self.user_id}")
|
||||
return research_data
|
||||
preferences = self.db_service.get_research_preferences(self.user_id, db)
|
||||
db.close()
|
||||
return preferences
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting research preferences from database: {e}")
|
||||
logger.error(f"Error getting research preferences: {str(e)}")
|
||||
return None
|
||||
|
||||
def _get_personalization_settings(self, research_preferences: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Get personalization settings from Step 4 (Persona) database."""
|
||||
try:
|
||||
# Try to get from Step 4 (Persona) in database
|
||||
db = next(get_db())
|
||||
persona_data = self.db_service.get_persona_data(self.user_id, db)
|
||||
|
||||
if persona_data:
|
||||
logger.info(f"Retrieved persona data from database for user {self.user_id}")
|
||||
# Extract personalization settings from persona data
|
||||
if 'corePersona' in persona_data:
|
||||
core_persona = persona_data.get('corePersona', {})
|
||||
return {
|
||||
'writing_style': core_persona.get('linguistic_fingerprint', {}).get('tone', 'Professional'),
|
||||
'tone': core_persona.get('tonal_range', {}).get('primary_tone', 'Formal'),
|
||||
'brand_voice': core_persona.get('identity', {}).get('voice', 'Trustworthy and Expert')
|
||||
}
|
||||
|
||||
# Fallback to research preferences if persona data not available
|
||||
if research_preferences:
|
||||
logger.info(f"Using research preferences as fallback for personalization")
|
||||
return {
|
||||
'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')
|
||||
}
|
||||
|
||||
return None
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting personalization settings from database: {e}")
|
||||
return None
|
||||
def _get_personalization_settings(self, research_preferences: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Get personalization settings based on research preferences."""
|
||||
if not research_preferences:
|
||||
return {
|
||||
"writing_style": "professional",
|
||||
"target_audience": "general",
|
||||
"content_focus": "informative"
|
||||
}
|
||||
|
||||
return {
|
||||
"writing_style": research_preferences.get('writing_style', 'professional'),
|
||||
"target_audience": research_preferences.get('target_audience', 'general'),
|
||||
"content_focus": research_preferences.get('content_focus', 'informative')
|
||||
}
|
||||
|
||||
def _check_persona_readiness(self, website_analysis: Optional[Dict[str, Any]]) -> Optional[Dict[str, Any]]:
|
||||
"""Check if persona can be generated."""
|
||||
try:
|
||||
persona_service = PersonaAnalysisService()
|
||||
|
||||
# Check if persona can be generated
|
||||
onboarding_data = persona_service._collect_onboarding_data(self.user_id)
|
||||
if onboarding_data:
|
||||
data_sufficiency = persona_service._calculate_data_sufficiency(onboarding_data)
|
||||
return {
|
||||
"ready": data_sufficiency >= 50.0,
|
||||
"data_sufficiency": data_sufficiency,
|
||||
"can_generate": website_analysis is not None
|
||||
}
|
||||
return {"ready": False, "data_sufficiency": 0.0, "can_generate": False}
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not check persona readiness: {str(e)}")
|
||||
return {"ready": False, "error": str(e)}
|
||||
def _check_persona_readiness(self, website_analysis: Optional[Dict[str, Any]]) -> Dict[str, Any]:
|
||||
"""Check if persona generation is ready based on available data."""
|
||||
if not website_analysis:
|
||||
return {
|
||||
"ready": False,
|
||||
"reason": "Website analysis not completed",
|
||||
"missing_data": ["website_url", "style_analysis"]
|
||||
}
|
||||
|
||||
required_fields = ['website_url', 'writing_style', 'target_audience']
|
||||
missing_fields = [field for field in required_fields if not website_analysis.get(field)]
|
||||
|
||||
return {
|
||||
"ready": len(missing_fields) == 0,
|
||||
"reason": "All required data available" if len(missing_fields) == 0 else f"Missing: {', '.join(missing_fields)}",
|
||||
"missing_data": missing_fields
|
||||
}
|
||||
|
||||
def _determine_capabilities(self, api_keys: Dict[str, Any], website_analysis: Optional[Dict[str, Any]],
|
||||
research_preferences: Optional[Dict[str, Any]],
|
||||
personalization_settings: Optional[Dict[str, Any]],
|
||||
persona_readiness: Optional[Dict[str, Any]]) -> Dict[str, bool]:
|
||||
"""Determine user capabilities based on onboarding data."""
|
||||
return {
|
||||
"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
|
||||
personalization_settings: Dict[str, Any],
|
||||
persona_readiness: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Determine available capabilities based on configured data."""
|
||||
capabilities = {
|
||||
"ai_content_generation": any(key.get("configured") for key in api_keys.values()),
|
||||
"website_analysis": website_analysis is not None,
|
||||
"research_capabilities": research_preferences is not None,
|
||||
"persona_generation": persona_readiness.get("ready", False),
|
||||
"content_optimization": website_analysis is not None and research_preferences is not None
|
||||
}
|
||||
|
||||
async def get_website_analysis_data(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get website analysis data for FinalStep."""
|
||||
try:
|
||||
analysis = self._get_website_analysis()
|
||||
|
||||
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
|
||||
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(self) -> Optional[Dict[str, Any]]:
|
||||
"""Get research preferences data for FinalStep."""
|
||||
try:
|
||||
return self._get_research_preferences()
|
||||
except Exception as e:
|
||||
logger.error(f"Error getting research preferences data: {str(e)}")
|
||||
raise HTTPException(status_code=500, detail="Internal server error")
|
||||
|
||||
return capabilities
|
||||
@@ -19,6 +19,10 @@ from alwrity_utils import HealthChecker, RateLimiter, FrontendServing, RouterMan
|
||||
# Load environment variables
|
||||
load_dotenv()
|
||||
|
||||
# Set up clean logging for end users
|
||||
from logging_config import setup_clean_logging
|
||||
setup_clean_logging()
|
||||
|
||||
# Import middleware
|
||||
from middleware.auth_middleware import get_current_user
|
||||
|
||||
|
||||
@@ -1,27 +1,29 @@
|
||||
# Clerk Authentication
|
||||
CLERK_SECRET_KEY=your_clerk_secret_key_here
|
||||
CLERK_PUBLISHABLE_KEY=your_clerk_publishable_key_here
|
||||
# ALwrity Backend Configuration
|
||||
|
||||
# Google Search Console
|
||||
GSC_REDIRECT_URI=your-domain-name/gsc/callback
|
||||
# API Keys (Configure these in the onboarding process)
|
||||
# OPENAI_API_KEY=your_openai_api_key_here
|
||||
GEMINI_API_KEY=your_gemini_api_key_here
|
||||
# ANTHROPIC_API_KEY=your_anthropic_api_key_here
|
||||
# MISTRAL_API_KEY=your_mistral_api_key_here
|
||||
|
||||
# Wix Integration (Headless OAuth - Client ID only, no Client Secret required)
|
||||
WIX_CLIENT_ID=
|
||||
WIX_REDIRECT_URI=your-domain-name/wix/callback
|
||||
# Research API Keys (Optional)
|
||||
# TAVILY_API_KEY=your_tavily_api_key_here
|
||||
# SERPER_API_KEY=your_serper_api_key_here
|
||||
EXA_API_KEY=your_exa_api_key_here
|
||||
|
||||
# WordPress.com OAuth2 Integration
|
||||
# IMPORTANT: You need to register a WordPress.com application to get valid credentials
|
||||
# 1. Go to https://developer.wordpress.com/apps/
|
||||
# 2. Create a new application
|
||||
# 3. Set the redirect URI to: https://your-domain.com/wp/callback
|
||||
# 4. Copy the Client ID and Client Secret below
|
||||
# For development, these are placeholder values that may not work
|
||||
WORDPRESS_CLIENT_ID=your_wordpress_com_client_id_here
|
||||
WORDPRESS_CLIENT_SECRET=your_wordpress_com_client_secret_here
|
||||
WORDPRESS_REDIRECT_URI=
|
||||
# Authentication
|
||||
# CLERK_SECRET_KEY=your_clerk_secret_key_here
|
||||
|
||||
# Development Settings
|
||||
DISABLE_AUTH=false
|
||||
# OAuth Redirect URIs
|
||||
GSC_REDIRECT_URI=https://your-frontend.vercel.app/gsc/callback
|
||||
WORDPRESS_REDIRECT_URI=https://your-frontend.vercel.app/wp/callback
|
||||
WIX_REDIRECT_URI=https://your-frontend.vercel.app/wix/callback
|
||||
|
||||
# local development
|
||||
DEPLOY_ENV=local
|
||||
|
||||
# Server Configuration
|
||||
HOST=0.0.0.0
|
||||
PORT=8000
|
||||
DEBUG=true
|
||||
|
||||
# Logging
|
||||
LOG_LEVEL=INFO
|
||||
|
||||
110
backend/logging_config.py
Normal file
110
backend/logging_config.py
Normal file
@@ -0,0 +1,110 @@
|
||||
"""
|
||||
Logging configuration for ALwrity backend.
|
||||
Provides clean logging setup for end users vs developers.
|
||||
"""
|
||||
|
||||
import logging
|
||||
import os
|
||||
import sys
|
||||
from loguru import logger
|
||||
|
||||
|
||||
def setup_clean_logging():
|
||||
"""Set up clean logging for end users."""
|
||||
verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if not verbose_mode:
|
||||
# Suppress verbose logging for end users - be more aggressive
|
||||
logging.getLogger('sqlalchemy.engine').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('sqlalchemy.pool').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('sqlalchemy.dialects').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('sqlalchemy.orm').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('sqlalchemy').setLevel(logging.CRITICAL)
|
||||
logging.getLogger('sqlalchemy.engine.Engine').setLevel(logging.CRITICAL)
|
||||
|
||||
# Suppress service initialization logs
|
||||
logging.getLogger('services').setLevel(logging.WARNING)
|
||||
logging.getLogger('api').setLevel(logging.WARNING)
|
||||
logging.getLogger('models').setLevel(logging.WARNING)
|
||||
|
||||
# Suppress specific noisy loggers
|
||||
noisy_loggers = [
|
||||
'linkedin_persona_service',
|
||||
'facebook_persona_service',
|
||||
'core_persona_service',
|
||||
'persona_analysis_service',
|
||||
'ai_service_manager',
|
||||
'ai_engine_service',
|
||||
'website_analyzer',
|
||||
'competitor_analyzer',
|
||||
'keyword_researcher',
|
||||
'content_gap_analyzer',
|
||||
'onboarding_data_service',
|
||||
'comprehensive_user_data',
|
||||
'strategy_data',
|
||||
'gap_analysis_data',
|
||||
'phase1_steps',
|
||||
'daily_schedule_generator',
|
||||
'gsc_service',
|
||||
'wordpress_oauth',
|
||||
'data_filter',
|
||||
'source_mapper',
|
||||
'grounding_engine',
|
||||
'blog_content_seo_analyzer',
|
||||
'linkedin_service',
|
||||
'citation_manager',
|
||||
'content_analyzer',
|
||||
'linkedin_prompt_generator',
|
||||
'linkedin_image_storage',
|
||||
'hallucination_detector',
|
||||
'writing_assistant',
|
||||
'onboarding_data_service',
|
||||
'enhanced_linguistic_analyzer',
|
||||
'persona_quality_improver',
|
||||
'logging_middleware',
|
||||
'exa_service',
|
||||
'step3_research_service',
|
||||
'sitemap_service',
|
||||
'linkedin_image_generator',
|
||||
'linkedin_prompt_generator',
|
||||
'linkedin_image_storage',
|
||||
'router_manager',
|
||||
'frontend_serving',
|
||||
'database',
|
||||
'user_business_info',
|
||||
'auth_middleware',
|
||||
'pricing_service',
|
||||
'create_billing_tables'
|
||||
]
|
||||
|
||||
for logger_name in noisy_loggers:
|
||||
logging.getLogger(logger_name).setLevel(logging.WARNING)
|
||||
|
||||
# Configure loguru to be less verbose (only show warnings and errors)
|
||||
logger.remove() # Remove default handler
|
||||
|
||||
def warning_only_filter(record):
|
||||
return record["level"].name in ["WARNING", "ERROR", "CRITICAL"]
|
||||
|
||||
logger.add(
|
||||
sys.stdout.write,
|
||||
level="WARNING",
|
||||
format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n",
|
||||
filter=warning_only_filter
|
||||
)
|
||||
else:
|
||||
# In verbose mode, show all log levels with detailed formatting
|
||||
logger.remove() # Remove default handler
|
||||
logger.add(
|
||||
sys.stdout.write,
|
||||
level="DEBUG",
|
||||
format="{time:HH:mm:ss} | {level: <8} | {name}:{function}:{line} - {message}\n"
|
||||
)
|
||||
|
||||
return verbose_mode
|
||||
|
||||
|
||||
def get_uvicorn_log_level():
|
||||
"""Get appropriate uvicorn log level based on verbose mode."""
|
||||
verbose_mode = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
return "debug" if verbose_mode else "warning"
|
||||
@@ -6,7 +6,7 @@ from datetime import datetime
|
||||
|
||||
Base = declarative_base()
|
||||
|
||||
logger.info("🔄 Loading UserBusinessInfo model...")
|
||||
logger.debug("🔄 Loading UserBusinessInfo model...")
|
||||
|
||||
class UserBusinessInfo(Base):
|
||||
__tablename__ = 'user_business_info'
|
||||
@@ -35,4 +35,4 @@ class UserBusinessInfo(Base):
|
||||
"updated_at": self.updated_at.isoformat() if self.updated_at else None,
|
||||
}
|
||||
|
||||
logger.info("✅ UserBusinessInfo model loaded successfully!")
|
||||
logger.debug("✅ UserBusinessInfo model loaded successfully!")
|
||||
|
||||
@@ -26,12 +26,12 @@ def create_billing_tables():
|
||||
|
||||
try:
|
||||
# Create engine
|
||||
engine = create_engine(DATABASE_URL, echo=True)
|
||||
engine = create_engine(DATABASE_URL, echo=False)
|
||||
|
||||
# Create all tables
|
||||
logger.info("Creating billing and subscription system tables...")
|
||||
logger.debug("Creating billing and subscription system tables...")
|
||||
SubscriptionBase.metadata.create_all(bind=engine)
|
||||
logger.info("✅ Billing and subscription tables created successfully")
|
||||
logger.debug("✅ Billing and subscription tables created successfully")
|
||||
|
||||
# Create session for data initialization
|
||||
SessionLocal = sessionmaker(autocommit=False, autoflush=False, bind=engine)
|
||||
@@ -41,13 +41,13 @@ def create_billing_tables():
|
||||
# Initialize pricing and plans
|
||||
pricing_service = PricingService(db)
|
||||
|
||||
logger.info("Initializing default API pricing...")
|
||||
logger.debug("Initializing default API pricing...")
|
||||
pricing_service.initialize_default_pricing()
|
||||
logger.info("✅ Default API pricing initialized")
|
||||
logger.debug("✅ Default API pricing initialized")
|
||||
|
||||
logger.info("Initializing default subscription plans...")
|
||||
logger.debug("Initializing default subscription plans...")
|
||||
pricing_service.initialize_default_plans()
|
||||
logger.info("✅ Default subscription plans initialized")
|
||||
logger.debug("✅ Default subscription plans initialized")
|
||||
|
||||
except Exception as e:
|
||||
logger.error(f"Error initializing default data: {e}")
|
||||
@@ -57,7 +57,7 @@ def create_billing_tables():
|
||||
finally:
|
||||
db.close()
|
||||
|
||||
logger.info("🎉 Billing system setup completed successfully!")
|
||||
logger.info("✅ Billing system setup completed successfully!")
|
||||
|
||||
# Display summary
|
||||
display_setup_summary(engine)
|
||||
@@ -94,7 +94,7 @@ def display_setup_summary(engine):
|
||||
|
||||
logger.info(f"\n📊 Created Tables ({len(tables)}):")
|
||||
for table in tables:
|
||||
logger.info(f" • {table[0]}")
|
||||
logger.debug(f" • {table[0]}")
|
||||
|
||||
# Check subscription plans
|
||||
try:
|
||||
@@ -114,7 +114,7 @@ def display_setup_summary(engine):
|
||||
|
||||
for plan in plans:
|
||||
name, tier, monthly, yearly = plan
|
||||
logger.info(f" • {name} ({tier}): ${monthly}/month, ${yearly}/year")
|
||||
logger.debug(f" • {name} ({tier}): ${monthly}/month, ${yearly}/year")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not check subscription plans: {e}")
|
||||
|
||||
@@ -124,22 +124,22 @@ def display_setup_summary(engine):
|
||||
result = conn.execute(pricing_query)
|
||||
pricing_count = result.fetchone()[0]
|
||||
logger.info(f"\n💰 API Pricing Entries: {pricing_count}")
|
||||
|
||||
|
||||
if pricing_count > 0:
|
||||
pricing_detail_query = text("""
|
||||
SELECT provider, model_name, cost_per_input_token, cost_per_output_token
|
||||
FROM api_provider_pricing
|
||||
SELECT provider, model_name, cost_per_input_token, cost_per_output_token
|
||||
FROM api_provider_pricing
|
||||
WHERE cost_per_input_token > 0 OR cost_per_output_token > 0
|
||||
ORDER BY provider, model_name
|
||||
LIMIT 10
|
||||
""")
|
||||
result = conn.execute(pricing_detail_query)
|
||||
pricing_entries = result.fetchall()
|
||||
|
||||
|
||||
logger.info("\n LLM Pricing (per token) - Top 10:")
|
||||
for entry in pricing_entries:
|
||||
provider, model, input_cost, output_cost = entry
|
||||
logger.info(f" • {provider}/{model}: ${input_cost:.8f} in, ${output_cost:.8f} out")
|
||||
logger.debug(f" • {provider}/{model}: ${input_cost:.8f} in, ${output_cost:.8f} out")
|
||||
except Exception as e:
|
||||
logger.warning(f"Could not check API pricing: {e}")
|
||||
|
||||
@@ -183,7 +183,7 @@ def check_existing_tables(engine):
|
||||
|
||||
if existing_tables:
|
||||
logger.warning(f"Found existing billing tables: {[t[0] for t in existing_tables]}")
|
||||
logger.info("Tables already exist. Skipping creation to preserve data.")
|
||||
logger.debug("Tables already exist. Skipping creation to preserve data.")
|
||||
return False
|
||||
|
||||
return True
|
||||
@@ -193,7 +193,7 @@ def check_existing_tables(engine):
|
||||
return True # Proceed anyway
|
||||
|
||||
if __name__ == "__main__":
|
||||
logger.info("🚀 Starting billing system database migration...")
|
||||
logger.debug("🚀 Starting billing system database migration...")
|
||||
|
||||
try:
|
||||
# Create engine to check existing tables
|
||||
@@ -201,7 +201,7 @@ if __name__ == "__main__":
|
||||
|
||||
# Check existing tables
|
||||
if not check_existing_tables(engine):
|
||||
logger.info("✅ Billing tables already exist, skipping creation")
|
||||
logger.debug("✅ Billing tables already exist, skipping creation")
|
||||
sys.exit(0)
|
||||
|
||||
# Create tables and initialize data
|
||||
@@ -210,7 +210,7 @@ if __name__ == "__main__":
|
||||
logger.info("✅ Billing system migration completed successfully!")
|
||||
|
||||
except KeyboardInterrupt:
|
||||
logger.info("Migration cancelled by user")
|
||||
logger.warning("Migration cancelled by user")
|
||||
sys.exit(0)
|
||||
except Exception as e:
|
||||
logger.error(f"❌ Migration failed: {e}")
|
||||
|
||||
@@ -47,15 +47,26 @@ class AIServiceMetrics:
|
||||
class AIServiceManager:
|
||||
"""Centralized AI service management for content planning system."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(AIServiceManager, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize AI service manager."""
|
||||
self.logger = logger
|
||||
self.metrics: List[AIServiceMetrics] = []
|
||||
self.prompts = self._load_centralized_prompts()
|
||||
self.schemas = self._load_centralized_schemas()
|
||||
self.config = self._load_ai_configuration()
|
||||
|
||||
logger.info("AIServiceManager initialized")
|
||||
"""Initialize AI service manager (only once)."""
|
||||
if not self._initialized:
|
||||
self.logger = logger
|
||||
self.metrics: List[AIServiceMetrics] = []
|
||||
self.prompts = self._load_centralized_prompts()
|
||||
self.schemas = self._load_centralized_schemas()
|
||||
self.config = self._load_ai_configuration()
|
||||
|
||||
logger.debug("AIServiceManager initialized")
|
||||
self._initialized = True
|
||||
|
||||
def _load_ai_configuration(self) -> Dict[str, Any]:
|
||||
"""Load AI configuration settings."""
|
||||
|
||||
@@ -24,10 +24,21 @@ from services.database import get_db_session
|
||||
class AIEngineService:
|
||||
"""AI engine for content planning insights and analysis."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(AIEngineService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the AI engine service."""
|
||||
self.ai_service_manager = AIServiceManager()
|
||||
logger.info("AIEngineService initialized")
|
||||
"""Initialize the AI engine service (only once)."""
|
||||
if not self._initialized:
|
||||
self.ai_service_manager = AIServiceManager()
|
||||
logger.debug("AIEngineService initialized")
|
||||
self._initialized = True
|
||||
|
||||
async def analyze_content_gaps(self, analysis_summary: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -18,13 +18,24 @@ from services.persona.facebook.facebook_persona_service import FacebookPersonaSe
|
||||
class CorePersonaService:
|
||||
"""Core service for generating writing personas using Gemini AI."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(CorePersonaService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the core persona service."""
|
||||
self.data_collector = OnboardingDataCollector()
|
||||
self.prompt_builder = PersonaPromptBuilder()
|
||||
self.linkedin_service = LinkedInPersonaService()
|
||||
self.facebook_service = FacebookPersonaService()
|
||||
logger.info("CorePersonaService initialized")
|
||||
"""Initialize the core persona service (only once)."""
|
||||
if not self._initialized:
|
||||
self.data_collector = OnboardingDataCollector()
|
||||
self.prompt_builder = PersonaPromptBuilder()
|
||||
self.linkedin_service = LinkedInPersonaService()
|
||||
self.facebook_service = FacebookPersonaService()
|
||||
logger.debug("CorePersonaService initialized")
|
||||
self._initialized = True
|
||||
|
||||
def generate_core_persona(self, onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Generate core writing persona using Gemini structured response."""
|
||||
|
||||
@@ -26,7 +26,7 @@ class EnhancedLinguisticAnalyzer:
|
||||
import spacy
|
||||
self.nlp = spacy.load("en_core_web_sm")
|
||||
self.spacy_available = True
|
||||
logger.info("SUCCESS: spaCy model loaded successfully - Enhanced linguistic analysis available")
|
||||
logger.debug("SUCCESS: spaCy model loaded successfully - Enhanced linguistic analysis available")
|
||||
except ImportError as e:
|
||||
logger.error(f"ERROR: spaCy is REQUIRED for persona generation. Install with: pip install spacy && python -m spacy download en_core_web_sm")
|
||||
raise ImportError("spaCy is required for enhanced persona generation. Install with: pip install spacy && python -m spacy download en_core_web_sm") from e
|
||||
|
||||
@@ -20,14 +20,25 @@ from services.llm_providers.gemini_provider import gemini_structured_json_respon
|
||||
class FacebookPersonaService:
|
||||
"""Facebook-specific persona generation and optimization service."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(FacebookPersonaService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the Facebook persona service."""
|
||||
self.schemas = FacebookPersonaSchema
|
||||
self.constraints = FacebookPersonaConstraints()
|
||||
self.validation = FacebookPersonaValidation()
|
||||
self.optimization = FacebookPersonaOptimization()
|
||||
self.prompts = FacebookPersonaPrompts()
|
||||
logger.info("FacebookPersonaService initialized")
|
||||
"""Initialize the Facebook persona service (only once)."""
|
||||
if not self._initialized:
|
||||
self.schemas = FacebookPersonaSchema
|
||||
self.constraints = FacebookPersonaConstraints()
|
||||
self.validation = FacebookPersonaValidation()
|
||||
self.optimization = FacebookPersonaOptimization()
|
||||
self.prompts = FacebookPersonaPrompts()
|
||||
logger.debug("FacebookPersonaService initialized")
|
||||
self._initialized = True
|
||||
|
||||
def generate_facebook_persona(self, core_persona: Dict[str, Any], onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -14,11 +14,22 @@ from .linkedin_persona_schemas import LinkedInPersonaSchemas
|
||||
class LinkedInPersonaService:
|
||||
"""Service for generating LinkedIn-specific persona adaptations."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(LinkedInPersonaService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the LinkedIn persona service."""
|
||||
self.prompts = LinkedInPersonaPrompts()
|
||||
self.schemas = LinkedInPersonaSchemas()
|
||||
logger.info("LinkedInPersonaService initialized")
|
||||
"""Initialize the LinkedIn persona service (only once)."""
|
||||
if not self._initialized:
|
||||
self.prompts = LinkedInPersonaPrompts()
|
||||
self.schemas = LinkedInPersonaSchemas()
|
||||
logger.debug("LinkedInPersonaService initialized")
|
||||
self._initialized = True
|
||||
|
||||
def generate_linkedin_persona(self, core_persona: Dict[str, Any], onboarding_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -24,7 +24,7 @@ class PersonaQualityImprover:
|
||||
def __init__(self):
|
||||
"""Initialize the quality improver."""
|
||||
self.linguistic_analyzer = EnhancedLinguisticAnalyzer()
|
||||
logger.info("PersonaQualityImprover initialized")
|
||||
logger.debug("PersonaQualityImprover initialized")
|
||||
|
||||
def assess_persona_quality_comprehensive(
|
||||
self,
|
||||
|
||||
@@ -19,13 +19,24 @@ from services.persona.facebook.facebook_persona_service import FacebookPersonaSe
|
||||
class PersonaAnalysisService:
|
||||
"""Service for analyzing onboarding data and generating writing personas using Gemini AI."""
|
||||
|
||||
_instance = None
|
||||
_initialized = False
|
||||
|
||||
def __new__(cls):
|
||||
"""Implement singleton pattern to prevent multiple initializations."""
|
||||
if cls._instance is None:
|
||||
cls._instance = super(PersonaAnalysisService, cls).__new__(cls)
|
||||
return cls._instance
|
||||
|
||||
def __init__(self):
|
||||
"""Initialize the persona analysis service."""
|
||||
self.core_persona_service = CorePersonaService()
|
||||
self.data_collector = OnboardingDataCollector()
|
||||
self.linkedin_service = LinkedInPersonaService()
|
||||
self.facebook_service = FacebookPersonaService()
|
||||
logger.info("PersonaAnalysisService initialized")
|
||||
"""Initialize the persona analysis service (only once)."""
|
||||
if not self._initialized:
|
||||
self.core_persona_service = CorePersonaService()
|
||||
self.data_collector = OnboardingDataCollector()
|
||||
self.linkedin_service = LinkedInPersonaService()
|
||||
self.facebook_service = FacebookPersonaService()
|
||||
logger.debug("PersonaAnalysisService initialized")
|
||||
self._initialized = True
|
||||
|
||||
def generate_persona_from_onboarding(self, user_id: int, onboarding_session_id: int = None) -> Dict[str, Any]:
|
||||
"""
|
||||
|
||||
@@ -215,7 +215,7 @@ class PricingService:
|
||||
self.db.add(pricing)
|
||||
|
||||
self.db.commit()
|
||||
logger.info("Default API pricing initialized")
|
||||
logger.debug("Default API pricing initialized")
|
||||
|
||||
def initialize_default_plans(self):
|
||||
"""Initialize default subscription plans."""
|
||||
@@ -318,7 +318,7 @@ class PricingService:
|
||||
self.db.add(plan)
|
||||
|
||||
self.db.commit()
|
||||
logger.info("Default subscription plans initialized")
|
||||
logger.debug("Default subscription plans initialized")
|
||||
|
||||
def calculate_api_cost(self, provider: APIProvider, model_name: str,
|
||||
tokens_input: int = 0, tokens_output: int = 0,
|
||||
|
||||
@@ -17,28 +17,37 @@ def bootstrap_linguistic_models():
|
||||
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
|
||||
"""
|
||||
import subprocess
|
||||
import os
|
||||
|
||||
print("🔍 Bootstrapping linguistic models...")
|
||||
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
||||
|
||||
if verbose:
|
||||
print("🔍 Bootstrapping linguistic models...")
|
||||
|
||||
# Check and download spaCy model
|
||||
try:
|
||||
import spacy
|
||||
try:
|
||||
nlp = spacy.load("en_core_web_sm")
|
||||
print(" ✅ spaCy model 'en_core_web_sm' available")
|
||||
if verbose:
|
||||
print(" ✅ spaCy model 'en_core_web_sm' available")
|
||||
except OSError:
|
||||
print(" ⚠️ spaCy model 'en_core_web_sm' not found, downloading...")
|
||||
if verbose:
|
||||
print(" ⚠️ spaCy model 'en_core_web_sm' not found, downloading...")
|
||||
try:
|
||||
subprocess.check_call([
|
||||
sys.executable, "-m", "spacy", "download", "en_core_web_sm"
|
||||
])
|
||||
print(" ✅ spaCy model downloaded successfully")
|
||||
if verbose:
|
||||
print(" ✅ spaCy model downloaded successfully")
|
||||
except subprocess.CalledProcessError as e:
|
||||
print(f" ❌ Failed to download spaCy model: {e}")
|
||||
print(" Please run: python -m spacy download en_core_web_sm")
|
||||
if verbose:
|
||||
print(f" ❌ Failed to download spaCy model: {e}")
|
||||
print(" Please run: python -m spacy download en_core_web_sm")
|
||||
return False
|
||||
except ImportError:
|
||||
print(" ⚠️ spaCy not installed - skipping")
|
||||
if verbose:
|
||||
print(" ⚠️ spaCy not installed - skipping")
|
||||
|
||||
# Check and download NLTK data
|
||||
try:
|
||||
@@ -52,25 +61,32 @@ def bootstrap_linguistic_models():
|
||||
for data_package, path in essential_data:
|
||||
try:
|
||||
nltk.data.find(path)
|
||||
print(f" ✅ NLTK {data_package} available")
|
||||
if verbose:
|
||||
print(f" ✅ NLTK {data_package} available")
|
||||
except LookupError:
|
||||
print(f" ⚠️ NLTK {data_package} not found, downloading...")
|
||||
if verbose:
|
||||
print(f" ⚠️ NLTK {data_package} not found, downloading...")
|
||||
try:
|
||||
nltk.download(data_package, quiet=True)
|
||||
print(f" ✅ NLTK {data_package} downloaded")
|
||||
if verbose:
|
||||
print(f" ✅ NLTK {data_package} downloaded")
|
||||
except Exception as e:
|
||||
print(f" ⚠️ Failed to download {data_package}: {e}")
|
||||
if verbose:
|
||||
print(f" ⚠️ Failed to download {data_package}: {e}")
|
||||
# Try fallback
|
||||
if data_package == 'punkt_tab':
|
||||
try:
|
||||
nltk.download('punkt', quiet=True)
|
||||
print(f" ✅ NLTK punkt (fallback) downloaded")
|
||||
if verbose:
|
||||
print(f" ✅ NLTK punkt (fallback) downloaded")
|
||||
except:
|
||||
pass
|
||||
except ImportError:
|
||||
print(" ⚠️ NLTK not installed - skipping")
|
||||
if verbose:
|
||||
print(" ⚠️ NLTK not installed - skipping")
|
||||
|
||||
print("✅ Linguistic model bootstrap complete")
|
||||
if verbose:
|
||||
print("✅ Linguistic model bootstrap complete")
|
||||
return True
|
||||
|
||||
|
||||
@@ -127,11 +143,10 @@ def start_backend(enable_reload=False, production_mode=False):
|
||||
import uvicorn
|
||||
|
||||
# Explicitly initialize database before starting server
|
||||
print("[DB] Initializing database...")
|
||||
init_database()
|
||||
print("[OK] Database initialized successfully")
|
||||
|
||||
print("\n🌐 Backend is starting...")
|
||||
print("\n🌐 ALwrity Backend Server")
|
||||
print("=" * 50)
|
||||
print(" 📖 API Documentation: http://localhost:8000/api/docs")
|
||||
print(" 🔍 Health Check: http://localhost:8000/health")
|
||||
print(" 📊 ReDoc: http://localhost:8000/api/redoc")
|
||||
@@ -142,12 +157,13 @@ def start_backend(enable_reload=False, production_mode=False):
|
||||
print(" 📊 Usage Tracking: http://localhost:8000/api/subscription/usage/demo")
|
||||
|
||||
print("\n[STOP] Press Ctrl+C to stop the server")
|
||||
print("=" * 60)
|
||||
print("\n💡 Usage:")
|
||||
print(" Production mode: python start_alwrity_backend.py --production")
|
||||
print(" Development mode: python start_alwrity_backend.py --dev")
|
||||
print(" With auto-reload: python start_alwrity_backend.py --reload")
|
||||
print("=" * 60)
|
||||
print("=" * 50)
|
||||
|
||||
# Set up clean logging for end users
|
||||
from logging_config import setup_clean_logging, get_uvicorn_log_level
|
||||
|
||||
verbose_mode = setup_clean_logging()
|
||||
uvicorn_log_level = get_uvicorn_log_level()
|
||||
|
||||
uvicorn.run(
|
||||
"app:app",
|
||||
@@ -186,7 +202,7 @@ def start_backend(enable_reload=False, production_mode=False):
|
||||
"api/**/*.py",
|
||||
"services/**/*.py"
|
||||
],
|
||||
log_level="info"
|
||||
log_level=uvicorn_log_level
|
||||
)
|
||||
|
||||
except KeyboardInterrupt:
|
||||
@@ -205,16 +221,23 @@ def main():
|
||||
parser.add_argument("--reload", action="store_true", help="Enable auto-reload for development")
|
||||
parser.add_argument("--dev", action="store_true", help="Enable development mode (auto-reload)")
|
||||
parser.add_argument("--production", action="store_true", help="Enable production mode (optimized for deployment)")
|
||||
parser.add_argument("--verbose", action="store_true", help="Enable verbose logging for debugging")
|
||||
args = parser.parse_args()
|
||||
|
||||
# Determine mode
|
||||
production_mode = args.production
|
||||
enable_reload = (args.reload or args.dev) and not production_mode
|
||||
verbose_mode = args.verbose
|
||||
|
||||
print("ALwrity Backend Server")
|
||||
# Set global verbose flag for utilities
|
||||
os.environ["ALWRITY_VERBOSE"] = "true" if verbose_mode else "false"
|
||||
|
||||
print("🚀 ALwrity Backend Server")
|
||||
print("=" * 40)
|
||||
print(f"Mode: {'PRODUCTION' if production_mode else 'DEVELOPMENT'}")
|
||||
print(f"Auto-reload: {'ENABLED' if enable_reload else 'DISABLED'}")
|
||||
if verbose_mode:
|
||||
print("Verbose logging: ENABLED")
|
||||
print("=" * 40)
|
||||
|
||||
# Check if we're in the right directory
|
||||
@@ -230,39 +253,59 @@ def main():
|
||||
database_setup = DatabaseSetup(production_mode=production_mode)
|
||||
production_optimizer = ProductionOptimizer()
|
||||
|
||||
# Setup progress tracking
|
||||
setup_steps = [
|
||||
"Checking dependencies",
|
||||
"Setting up environment",
|
||||
"Configuring database",
|
||||
"Starting server"
|
||||
]
|
||||
|
||||
print("🔧 Initializing ALwrity...")
|
||||
|
||||
# Apply production optimizations if needed
|
||||
if production_mode:
|
||||
if not production_optimizer.apply_production_optimizations():
|
||||
print("[ERROR] Production optimization failed")
|
||||
print("❌ Production optimization failed")
|
||||
return False
|
||||
|
||||
# Check and install dependencies
|
||||
# Step 1: Dependencies
|
||||
print(f" 📦 {setup_steps[0]}...", end=" ", flush=True)
|
||||
critical_ok, missing_critical = dependency_manager.check_critical_dependencies()
|
||||
if not critical_ok:
|
||||
print("[ERROR] Critical dependencies missing, installing...")
|
||||
print("installing...", end=" ", flush=True)
|
||||
if not dependency_manager.install_requirements():
|
||||
print("[ERROR] Failed to install dependencies")
|
||||
print("❌ Failed")
|
||||
return False
|
||||
print("✅ Done")
|
||||
else:
|
||||
print("✅ Done")
|
||||
|
||||
# Check optional dependencies (non-critical)
|
||||
dependency_manager.check_optional_dependencies()
|
||||
# Check optional dependencies (non-critical) - only in verbose mode
|
||||
if verbose_mode:
|
||||
dependency_manager.check_optional_dependencies()
|
||||
|
||||
# Setup environment
|
||||
# Step 2: Environment
|
||||
print(f" 🔧 {setup_steps[1]}...", end=" ", flush=True)
|
||||
if not environment_setup.setup_directories():
|
||||
print("[ERROR] Directory setup failed")
|
||||
print("❌ Directory setup failed")
|
||||
return False
|
||||
|
||||
if not environment_setup.setup_environment_variables():
|
||||
print("[ERROR] Environment variable setup failed")
|
||||
print("❌ Environment setup failed")
|
||||
return False
|
||||
|
||||
# Create .env file only in development
|
||||
if not production_mode:
|
||||
environment_setup.create_env_file()
|
||||
print("✅ Done")
|
||||
|
||||
# Setup database
|
||||
# Step 3: Database
|
||||
print(f" 📊 {setup_steps[2]}...", end=" ", flush=True)
|
||||
if not database_setup.setup_essential_tables():
|
||||
print("[WARNING] Database setup had issues, continuing...")
|
||||
print("⚠️ Issues detected, continuing...")
|
||||
else:
|
||||
print("✅ Done")
|
||||
|
||||
# Setup advanced features in development, verify in all modes
|
||||
if not production_mode:
|
||||
@@ -274,7 +317,8 @@ def main():
|
||||
# Note: Linguistic models (spaCy/NLTK) are bootstrapped before imports
|
||||
# See bootstrap_linguistic_models() at the top of this file
|
||||
|
||||
# Start backend
|
||||
# Step 4: Start backend
|
||||
print(f" 🚀 {setup_steps[3]}...")
|
||||
return start_backend(enable_reload=enable_reload, production_mode=production_mode)
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user