- Fix text selection menu not showing: wire contentRef via inputRef on multiline TextField - Fix blog title not truncating: add min-w-0 for flex item overflow - Fix outline generation 500: escape curly braces in f-string prompt template - Fix content generation 'NoneType not callable': replace SessionLocal() with get_session_for_user(), add db param to MediumBlogGenerator, fix signature mismatch in database_task_manager - Fix writing assistant suggest 500: add auth + user_id to API endpoint and service, replace sync requests with httpx.AsyncClient - Fix hallucination detector 404: explicitly include router in main.py and app.py - Fix missing error_data in task failure responses - Hide CopilotKit web inspector button - Remove hardcoded fallback suggestions from SmartTypingAssist - Fix stale closure refs in SmartTypingAssist handleTypingChange - Add two-column editor layout, stats bar, section hover menu - Various subscription, billing, and research module improvements
573 lines
22 KiB
Python
573 lines
22 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
ALwrity Backend Server - Modular Startup Script
|
|
Handles setup, dependency installation, and server startup using modular utilities.
|
|
Run this from the backend directory to set up and start the FastAPI server.
|
|
"""
|
|
|
|
import os
|
|
import sys
|
|
import json
|
|
import argparse
|
|
import platform
|
|
from pathlib import Path
|
|
from dataclasses import dataclass, asdict
|
|
from typing import Optional
|
|
|
|
# Detect platform
|
|
IS_WINDOWS = platform.system() == "Windows"
|
|
IS_LINUX = platform.system() == "Linux"
|
|
|
|
import uvicorn
|
|
|
|
|
|
@dataclass
|
|
class BootstrapResult:
|
|
name: str
|
|
success: bool
|
|
skipped: bool
|
|
reason: Optional[str] = None
|
|
details: Optional[str] = None
|
|
|
|
|
|
LINGUISTIC_REQUIRED_FEATURES = {"content_planning", "strategy_copilot", "facebook", "linkedin", "blog_writer", "persona"}
|
|
|
|
|
|
def get_enabled_features() -> set:
|
|
"""Get enabled features from ALWRITY_ENABLED_FEATURES env var.
|
|
|
|
Values:
|
|
- "all" - enable all features (default)
|
|
- comma-separated: "podcast,blog-writer,youtube"
|
|
- single feature: "podcast"
|
|
"""
|
|
env_value = os.getenv("ALWRITY_ENABLED_FEATURES", "all").strip().lower()
|
|
|
|
if not env_value or env_value == "all":
|
|
return {"all"}
|
|
|
|
return {f.strip() for f in env_value.split(",") if f.strip()}
|
|
|
|
|
|
def should_bootstrap_linguistic_models() -> bool:
|
|
"""Decide whether to bootstrap linguistic models based on enabled features."""
|
|
enabled_features = get_enabled_features()
|
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
|
|
|
if "all" in enabled_features:
|
|
return True
|
|
|
|
# Podcast-only mode doesn't need linguistic models
|
|
if enabled_features == {"podcast"}:
|
|
return False
|
|
|
|
# Map old profile names to features for backwards compatibility
|
|
feature_mapping = {
|
|
"podcast": "podcast",
|
|
"youtube": "youtube",
|
|
"planning": "content-planning",
|
|
"default": "all"
|
|
}
|
|
|
|
# Check if any linguistic-required feature is enabled
|
|
linguistic_features = {"content_planning", "facebook", "linkedin", "blog_writer", "persona"}
|
|
return bool(enabled_features & linguistic_features)
|
|
|
|
|
|
def should_bootstrap_local_llm_models() -> bool:
|
|
"""Decide whether to bootstrap local LLM models based on enabled features.
|
|
|
|
SIF/Story Writer requires local LLM - skip if only podcast is enabled.
|
|
"""
|
|
enabled_features = get_enabled_features()
|
|
|
|
if "all" in enabled_features:
|
|
return True
|
|
|
|
# SIF/Story Writer requires local LLM - only bootstrap if explicitly needed
|
|
# Skip for lean deployments (podcast-only, content-planning only, etc.)
|
|
return False # Default to skip unless "all" is enabled
|
|
|
|
|
|
def bootstrap_linguistic_models() -> BootstrapResult:
|
|
"""
|
|
Bootstrap spaCy and NLTK models BEFORE any imports.
|
|
This prevents import-time failures when EnhancedLinguisticAnalyzer is loaded.
|
|
"""
|
|
import subprocess
|
|
import os
|
|
|
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
|
|
|
if verbose:
|
|
print("[DEBUG] Bootstrapping linguistic models...")
|
|
|
|
# Check and download spaCy model
|
|
try:
|
|
import spacy
|
|
try:
|
|
nlp = spacy.load("en_core_web_sm")
|
|
if verbose:
|
|
print(" [OK] spaCy model 'en_core_web_sm' available")
|
|
except OSError:
|
|
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"
|
|
])
|
|
if verbose:
|
|
print(" [OK] spaCy model downloaded successfully")
|
|
except subprocess.CalledProcessError as e:
|
|
if verbose:
|
|
print(f" [FAIL] Failed to download spaCy model: {e}")
|
|
print(" Please run: python -m spacy download en_core_web_sm")
|
|
return BootstrapResult(name="linguistic_models", success=False, skipped=False, reason="spacy_download_failed")
|
|
except ImportError:
|
|
if verbose:
|
|
print(" ⚠️ spaCy not installed - skipping")
|
|
|
|
# Check and download NLTK data
|
|
try:
|
|
import nltk
|
|
essential_data = [
|
|
('punkt_tab', 'tokenizers/punkt_tab'),
|
|
('stopwords', 'corpora/stopwords'),
|
|
('averaged_perceptron_tagger', 'taggers/averaged_perceptron_tagger')
|
|
]
|
|
|
|
for data_package, path in essential_data:
|
|
try:
|
|
nltk.data.find(path)
|
|
if verbose:
|
|
print(f" [OK] NLTK {data_package} available")
|
|
except LookupError:
|
|
if verbose:
|
|
print(f" ⚠️ NLTK {data_package} not found, downloading...")
|
|
try:
|
|
nltk.download(data_package, quiet=True)
|
|
if verbose:
|
|
print(f" [OK] NLTK {data_package} downloaded")
|
|
except Exception as e:
|
|
if verbose:
|
|
print(f" ⚠️ Failed to download {data_package}: {e}")
|
|
if data_package == 'punkt_tab':
|
|
try:
|
|
nltk.download('punkt', quiet=True)
|
|
if verbose:
|
|
print(f" [OK] NLTK punkt (fallback) downloaded")
|
|
except:
|
|
pass
|
|
except ImportError:
|
|
if verbose:
|
|
print(" ⚠️ NLTK not installed - skipping")
|
|
|
|
if verbose:
|
|
print("[OK] Linguistic model bootstrap complete")
|
|
return BootstrapResult(name="linguistic_models", success=True, skipped=False)
|
|
|
|
|
|
def bootstrap_local_llm_models() -> BootstrapResult:
|
|
"""
|
|
Bootstrap Local LLM models (Qwen) for SIF Agents.
|
|
This ensures the model is cached locally before the server starts,
|
|
preventing large downloads during runtime.
|
|
"""
|
|
import os
|
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
|
|
|
# Model to pre-download
|
|
model_name = "Qwen/Qwen2.5-1.5B-Instruct"
|
|
# Using Qwen2.5-1.5B as it's more efficient for laptop CPU than 4B,
|
|
# but still capable for agent routing/clustering.
|
|
# If user specifically asked for Qwen3-4B, we can use that, but 1.5B is much faster.
|
|
# User said "local qwen model", 4B might be heavy. Let's stick to what was in code: "Qwen/Qwen3-4B-Instruct-2507"
|
|
# Actually, the code had "Qwen/Qwen3-4B-Instruct-2507" which seems like a specific fine-tune or typo.
|
|
# Let's use a standard efficient one: "Qwen/Qwen2.5-3B-Instruct" or "Qwen/Qwen2.5-1.5B-Instruct".
|
|
# Given "optimized for cpu-laptop", 1.5B or 3B is best.
|
|
# Let's use the one referenced in the code if valid, otherwise Qwen2.5-3B.
|
|
# The code had: "Qwen/Qwen3-4B-Instruct-2507". I suspect this is a placeholder or internal model.
|
|
# I will use "Qwen/Qwen2.5-3B-Instruct" as a safe, modern, powerful laptop-friendly default.
|
|
|
|
# Render Free Tier has 512MB RAM. Downloading a 3B model (6GB+) will instantly crash it.
|
|
# We must skip this on Render unless we are on a paid instance with persistent disk and lots of RAM.
|
|
if os.getenv("RENDER") or os.getenv("RAILWAY_ENVIRONMENT"):
|
|
if verbose:
|
|
print(" ⚠️ Cloud environment detected (Render/Railway). Skipping local LLM bootstrap to save RAM/Time.")
|
|
return BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="cloud_environment")
|
|
|
|
target_model = "Qwen/Qwen2.5-3B-Instruct"
|
|
|
|
if verbose:
|
|
print(f"🔍 Checking local LLM model '{target_model}'...")
|
|
|
|
try:
|
|
from huggingface_hub import snapshot_download
|
|
try:
|
|
# This checks cache and downloads if missing
|
|
snapshot_download(repo_id=target_model, repo_type="model")
|
|
if verbose:
|
|
print(f" [OK] Local LLM '{target_model}' available")
|
|
except Exception as e:
|
|
if verbose:
|
|
print(f" ⚠️ Failed to download/check local LLM: {e}")
|
|
print(" SIF agents may try to download it at runtime.")
|
|
return BootstrapResult(name="local_llm_models", success=False, skipped=False, reason=str(e))
|
|
except ImportError:
|
|
if verbose:
|
|
print(" ⚠️ huggingface_hub not installed - skipping LLM bootstrap")
|
|
return BootstrapResult(name="local_llm_models", success=False, skipped=True, reason="huggingface_hub_not_installed")
|
|
|
|
return BootstrapResult(name="local_llm_models", success=True, skipped=False)
|
|
|
|
|
|
# Bootstrap linguistic models BEFORE any imports that might need them
|
|
BOOTSTRAP_RESULTS = []
|
|
|
|
# Load .env file early so ALWRITY_ENABLED_FEATURES is available
|
|
from dotenv import load_dotenv
|
|
from pathlib import Path
|
|
|
|
# Load from backend/.env specifically
|
|
backend_dir = Path(__file__).parent
|
|
load_dotenv(backend_dir / '.env')
|
|
|
|
# Debug: Print what PORT is set to - IMMEDIATELY at startup
|
|
import os
|
|
print(f"[STARTUP] PORT env: {os.getenv('PORT')}", flush=True)
|
|
print(f"[STARTUP] RENDER env: {os.getenv('RENDER')}", flush=True)
|
|
print(f"[STARTUP] ALWRITY_ENABLED_FEATURES: {os.getenv('ALWRITY_ENABLED_FEATURES')}", flush=True)
|
|
print(f"[STARTUP] HOST env: {os.getenv('HOST')}", flush=True)
|
|
|
|
if __name__ == "__main__":
|
|
enabled_features = get_enabled_features()
|
|
features_str = ",".join(sorted(enabled_features))
|
|
os.environ["ALWRITY_ENABLED_FEATURES"] = features_str
|
|
|
|
print(f"\n[OK] Enabled features: {features_str}")
|
|
|
|
if should_bootstrap_linguistic_models():
|
|
result = bootstrap_linguistic_models()
|
|
BOOTSTRAP_RESULTS.append(result)
|
|
else:
|
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
|
if verbose:
|
|
print("[SKIP] Skipping linguistic model bootstrap (profile-gated)")
|
|
BOOTSTRAP_RESULTS.append(BootstrapResult(name="linguistic_models", success=True, skipped=True, reason="profile_gated"))
|
|
|
|
if should_bootstrap_local_llm_models():
|
|
result = bootstrap_local_llm_models()
|
|
BOOTSTRAP_RESULTS.append(result)
|
|
else:
|
|
verbose = os.getenv("ALWRITY_VERBOSE", "false").lower() == "true"
|
|
if verbose:
|
|
print("[SKIP] Skipping local LLM model bootstrap (feature-gated)")
|
|
BOOTSTRAP_RESULTS.append(BootstrapResult(name="local_llm_models", success=True, skipped=True, reason="feature_gated"))
|
|
|
|
summary = {
|
|
"enabled_features": features_str,
|
|
"bootstraps": [asdict(r) for r in BOOTSTRAP_RESULTS]
|
|
}
|
|
os.environ["ALWRITY_BOOTSTRAP_SUMMARY"] = json.dumps(summary)
|
|
|
|
print(f"\n[INFO] Bootstrap Summary:")
|
|
for r in BOOTSTRAP_RESULTS:
|
|
status = "[SKIP] Skipped" if r.skipped else ("[OK] Enabled" if r.success else "[FAIL] Failed")
|
|
print(f" {r.name}: {status}" + (f" ({r.reason})" if r.reason else ""))
|
|
|
|
# NOW import modular utilities (after bootstrap)
|
|
from alwrity_utils import (
|
|
DependencyManager,
|
|
EnvironmentSetup,
|
|
DatabaseSetup,
|
|
ProductionOptimizer
|
|
)
|
|
|
|
|
|
def start_backend(enable_reload=False, production_mode=False):
|
|
"""Start the backend server."""
|
|
print("==> Starting ALwrity Backend...")
|
|
# Check for legacy podcast-only demo mode env vars (backward compat)
|
|
is_legacy_podcast_mode = os.getenv("ALWRITY_PODCAST_ONLY_DEMO_MODE", os.getenv("PODCAST_ONLY_DEMO_MODE", "false")).lower() in {"1", "true", "yes", "on"}
|
|
enabled = get_enabled_features()
|
|
is_feature_limited = "all" not in enabled
|
|
|
|
if is_legacy_podcast_mode or is_feature_limited:
|
|
mode_label = "legacy podcast-only" if is_legacy_podcast_mode else f"feature-limited ({', '.join(sorted(enabled))})"
|
|
print(f"\n{'=' * 60}")
|
|
print(f"==> {mode_label.upper()} MODE ACTIVE")
|
|
print(" Non-matching router groups are intentionally skipped.")
|
|
print("=" * 60)
|
|
|
|
# Set host based on environment and mode
|
|
# Use 127.0.0.1 for local production testing on Windows
|
|
# Use 0.0.0.0 for actual cloud deployments (Render, Railway, etc.)
|
|
# Render provides PORT env var, detect cloud by presence of PORT
|
|
render_port = os.getenv("PORT")
|
|
if render_port:
|
|
# Cloud deployment detected (Render sets PORT env var) - use 0.0.0.0
|
|
os.environ.setdefault("HOST", "0.0.0.0")
|
|
os.environ.setdefault("PORT", render_port)
|
|
else:
|
|
# Local deployment - use 127.0.0.1 for better Windows compatibility
|
|
os.environ.setdefault("HOST", "127.0.0.1")
|
|
|
|
# Render sets PORT automatically. We should respect it if present, otherwise default to 8000.
|
|
# We don't setdefault("PORT", "8000") here because we want to use os.getenv("PORT") directly later
|
|
# to catch if it's missing and THEN default.
|
|
|
|
# Set reload based on argument or environment variable
|
|
if enable_reload and not production_mode:
|
|
os.environ.setdefault("RELOAD", "true")
|
|
print(" [DEV] Development mode: Auto-reload enabled")
|
|
else:
|
|
os.environ.setdefault("RELOAD", "false")
|
|
print(" [PROD] Production mode: Auto-reload disabled")
|
|
|
|
host = os.getenv("HOST", "0.0.0.0")
|
|
port = int(os.getenv("PORT", "8000"))
|
|
reload = os.environ.get("RELOAD", "false").lower() == "true"
|
|
print(f"[DEBUG] Bind prepared - host={host}, port={port}, reload={reload}", flush=True)
|
|
print(f"[DEBUG] ENV check - ALWRITY_ENABLED_FEATURES={os.getenv('ALWRITY_ENABLED_FEATURES')}", flush=True)
|
|
|
|
print(f" ==> Host: {host}", flush=True)
|
|
print(f" ==> Port: {port}", flush=True)
|
|
print(f" [DEV] Reload: {reload}", flush=True)
|
|
print(f"[DEBUG] About to import app module...", flush=True)
|
|
print("[DEBUG] >>> START APP IMPORT <<<", flush=True)
|
|
|
|
try:
|
|
# Import and run the app
|
|
from app import app
|
|
print("[DEBUG] >>> END APP IMPORT <<<", flush=True)
|
|
|
|
import uvicorn
|
|
print(f"[DEBUG] Imported app and uvicorn successfully", flush=True)
|
|
|
|
# Note: Database already initialized by DatabaseSetup in main()
|
|
|
|
print("\n[WORLD] ALwrity Backend Server", flush=True)
|
|
print("=" * 50, flush=True)
|
|
print(f" 📖 API Documentation: http://localhost:{os.getenv('PORT', '8000')}/api/docs", flush=True)
|
|
print(f" 🔍 Health Check: http://localhost:{os.getenv('PORT', '8000')}/health", flush=True)
|
|
print(f" 📊 ReDoc: http://localhost:{os.getenv('PORT', '8000')}/api/redoc", flush=True)
|
|
|
|
if not production_mode:
|
|
print(f" 📈 API Monitoring: http://localhost:{os.getenv('PORT', '8000')}/api/content-planning/monitoring/health", flush=True)
|
|
print(f" 💳 Billing Dashboard: http://localhost:{os.getenv('PORT', '8000')}/api/subscription/plans", flush=True)
|
|
print(f" 📊 Usage Tracking: http://localhost:{os.getenv('PORT', '8000')}/api/subscription/usage/demo", flush=True)
|
|
|
|
print("\n[STOP] Press Ctrl+C to stop the server", flush=True)
|
|
print("=" * 50, flush=True)
|
|
|
|
# Set up clean logging for end users
|
|
from logging_config import setup_clean_logging, get_uvicorn_log_level
|
|
# Video stack preflight (diagnostics + version assert)
|
|
try:
|
|
from services.story_writer.video_preflight import (
|
|
log_video_stack_diagnostics,
|
|
assert_supported_moviepy,
|
|
)
|
|
except Exception:
|
|
# Preflight is optional; continue if module missing
|
|
log_video_stack_diagnostics = None
|
|
assert_supported_moviepy = None
|
|
|
|
verbose_mode = setup_clean_logging()
|
|
uvicorn_log_level = get_uvicorn_log_level()
|
|
|
|
# Log diagnostics and assert versions (fail fast if misconfigured)
|
|
try:
|
|
if log_video_stack_diagnostics:
|
|
log_video_stack_diagnostics()
|
|
if assert_supported_moviepy:
|
|
assert_supported_moviepy()
|
|
except Exception as _video_stack_err:
|
|
print(f"[ERROR] Video stack preflight failed: {_video_stack_err}")
|
|
return False
|
|
|
|
print(f"[DEBUG] Starting uvicorn with host={host} port={port}", flush=True)
|
|
print("[DEBUG] >>> ABOUT TO CALL UVICORN.RUN() <<<", flush=True)
|
|
|
|
# Skip video preflight in feature-limited mode to save memory/time
|
|
is_feature_limited = os.getenv("ALWRITY_ENABLED_FEATURES", "").strip().lower() not in ("", "all")
|
|
print(f"[DEBUG] Feature-limited mode check: {is_feature_limited}", flush=True)
|
|
|
|
if is_feature_limited:
|
|
print("[DEBUG] Feature-limited mode - skipping video preflight", flush=True)
|
|
else:
|
|
# Log diagnostics and assert versions (fail fast if misconfigured)
|
|
try:
|
|
if log_video_stack_diagnostics:
|
|
log_video_stack_diagnostics()
|
|
if assert_supported_moviepy:
|
|
assert_supported_moviepy()
|
|
except Exception as _video_stack_err:
|
|
print(f"[ERROR] Video stack preflight failed: {_video_stack_err}")
|
|
return False
|
|
|
|
uvicorn.run(
|
|
"app:app",
|
|
host=host,
|
|
port=port,
|
|
reload=reload,
|
|
reload_dirs=["."], # Strictly watch backend directory only
|
|
reload_excludes=[
|
|
"workspace/**/*",
|
|
"*.pyc",
|
|
"*.pyo",
|
|
"*.pyd",
|
|
"__pycache__",
|
|
"*.log",
|
|
"*.sqlite",
|
|
"*.db",
|
|
"*.tmp",
|
|
"*.temp",
|
|
"test_*.py",
|
|
"temp_*.py",
|
|
"monitoring_data_service.py",
|
|
"test_monitoring_save.py",
|
|
"*.json",
|
|
"*.yaml",
|
|
"*.yml",
|
|
".env*",
|
|
"logs/**/*",
|
|
"logs",
|
|
"**/*.jsonl",
|
|
"**/*.log",
|
|
"cache/**/*",
|
|
"tmp/**/*",
|
|
"temp/**/*",
|
|
"middleware/*",
|
|
"models/*",
|
|
"scripts/*",
|
|
"alwrity_utils/*"
|
|
],
|
|
log_level=uvicorn_log_level
|
|
)
|
|
print("[DEBUG] uvicorn.run() has finished", flush=True)
|
|
|
|
except KeyboardInterrupt:
|
|
print("\n\n🛑 Backend stopped by user")
|
|
except Exception as e:
|
|
print(f"\n[ERROR] Error starting backend: {e}", flush=True)
|
|
import traceback
|
|
traceback.print_exc()
|
|
return False
|
|
|
|
return True
|
|
|
|
|
|
def main():
|
|
"""Main function to set up and start the backend."""
|
|
# Parse command line arguments
|
|
parser = argparse.ArgumentParser(description="ALwrity Backend Server")
|
|
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
|
|
|
|
# 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
|
|
if not os.path.exists("app.py"):
|
|
print("[ERROR] Error: app.py not found. Please run this script from the backend directory.")
|
|
print(" Current directory:", os.getcwd())
|
|
print(" Expected files:", [f for f in os.listdir('.') if f.endswith('.py')])
|
|
return False
|
|
|
|
# Initialize modular components
|
|
dependency_manager = DependencyManager()
|
|
environment_setup = EnvironmentSetup(production_mode=production_mode)
|
|
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("[FAIL] Production optimization failed")
|
|
return False
|
|
|
|
# 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("installing...", end=" ", flush=True)
|
|
if not dependency_manager.install_requirements():
|
|
print("[FAIL] Failed")
|
|
return False
|
|
print("[OK] Done")
|
|
else:
|
|
print("[OK] Done")
|
|
|
|
# Check optional dependencies (non-critical) - only in verbose mode
|
|
if verbose_mode:
|
|
dependency_manager.check_optional_dependencies()
|
|
|
|
# Step 2: Environment
|
|
print(f" 🔧 {setup_steps[1]}...", end=" ", flush=True)
|
|
if not environment_setup.setup_directories():
|
|
print("[FAIL] Directory setup failed")
|
|
return False
|
|
|
|
if not environment_setup.setup_environment_variables():
|
|
print("[FAIL] Environment setup failed")
|
|
return False
|
|
|
|
# Create .env file only in development
|
|
if not production_mode:
|
|
environment_setup.create_env_file()
|
|
print("[OK] Done")
|
|
|
|
# Step 3: Database
|
|
print(f" 📊 {setup_steps[2]}...", end=" ", flush=True)
|
|
if not database_setup.setup_essential_tables():
|
|
print("⚠️ Issues detected, continuing...")
|
|
else:
|
|
print("[OK] Done")
|
|
|
|
# Setup advanced features in development, verify in all modes
|
|
if not production_mode:
|
|
database_setup.setup_advanced_tables()
|
|
|
|
# Always verify database tables (important for both dev and production)
|
|
database_setup.verify_tables()
|
|
|
|
# Note: Linguistic models (spaCy/NLTK) are bootstrapped before imports
|
|
# See bootstrap_linguistic_models() at the top of this file
|
|
|
|
# Step 4: Start backend
|
|
print(f" 🚀 {setup_steps[3]}...")
|
|
return start_backend(enable_reload=enable_reload, production_mode=production_mode)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
success = main()
|
|
if not success:
|
|
sys.exit(1)
|