diff --git a/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py b/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py index 1df7e533..5e96cc36 100644 --- a/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py +++ b/backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py @@ -332,7 +332,7 @@ async def generate_comprehensive_strategy_polling( "onboarding_data": onboarding_data, "user_id": user_id, "generation_config": config or {} - } + } # Create strategy generation config generation_config = StrategyGenerationConfig( diff --git a/backend/api/content_planning/services/content_strategy/autofill/ai_refresh.py b/backend/api/content_planning/services/content_strategy/autofill/ai_refresh.py index 4cca9f5b..24297592 100644 --- a/backend/api/content_planning/services/content_strategy/autofill/ai_refresh.py +++ b/backend/api/content_planning/services/content_strategy/autofill/ai_refresh.py @@ -26,6 +26,8 @@ class AutoFillRefreshService: - Optionally augments with AI overrides (hook, not persisted) - Returns payload in the same shape as AutoFillService.get_autofill, plus meta """ + logger.info(f"AutoFillRefreshService: starting build_fresh_payload | user=%s | use_ai=%s | ai_only=%s", user_id, use_ai, ai_only) + # Base context from onboarding analysis (used for AI context only when ai_only) logger.debug("AutoFillRefreshService: processing onboarding context | user=%s", user_id) base_context = await self.autofill.integration.process_onboarding_data(user_id, self.db) @@ -37,6 +39,33 @@ class AutoFillRefreshService: bool((base_context or {}).get('api_keys_data')), bool((base_context or {}).get('onboarding_session')), ) + + # Log detailed context analysis + logger.info(f"AutoFillRefreshService: detailed context analysis | user=%s", user_id) + if base_context: + website_analysis = base_context.get('website_analysis', {}) + research_preferences = base_context.get('research_preferences', {}) + api_keys_data = base_context.get('api_keys_data', {}) + onboarding_session = base_context.get('onboarding_session', {}) + + logger.info(f" - Website analysis keys: {list(website_analysis.keys()) if website_analysis else 'None'}") + logger.info(f" - Research preferences keys: {list(research_preferences.keys()) if research_preferences else 'None'}") + logger.info(f" - API keys data keys: {list(api_keys_data.keys()) if api_keys_data else 'None'}") + logger.info(f" - Onboarding session keys: {list(onboarding_session.keys()) if onboarding_session else 'None'}") + + # Log specific data points + if website_analysis: + logger.info(f" - Website URL: {website_analysis.get('website_url', 'Not found')}") + logger.info(f" - Website status: {website_analysis.get('status', 'Unknown')}") + if research_preferences: + logger.info(f" - Research depth: {research_preferences.get('research_depth', 'Not found')}") + logger.info(f" - Content types: {research_preferences.get('content_types', 'Not found')}") + if api_keys_data: + logger.info(f" - API providers: {api_keys_data.get('providers', [])}") + logger.info(f" - Total keys: {api_keys_data.get('total_keys', 0)}") + else: + logger.warning(f"AutoFillRefreshService: no base context available | user=%s", user_id) + try: w = (base_context or {}).get('website_analysis') or {} r = (base_context or {}).get('research_preferences') or {} @@ -50,6 +79,16 @@ class AutoFillRefreshService: ai_payload = await self.structured_ai.generate_autofill_fields(user_id, base_context) meta = ai_payload.get('meta') or {} logger.info("AI-only payload meta: ai_used=%s overrides=%s", meta.get('ai_used'), meta.get('ai_overrides_count')) + + # Log detailed AI payload analysis + logger.info(f"AutoFillRefreshService: AI payload analysis | user=%s", user_id) + logger.info(f" - AI used: {meta.get('ai_used', False)}") + logger.info(f" - AI overrides count: {meta.get('ai_overrides_count', 0)}") + logger.info(f" - Success rate: {meta.get('success_rate', 0):.1f}%") + logger.info(f" - Attempts: {meta.get('attempts', 0)}") + logger.info(f" - Missing fields: {len(meta.get('missing_fields', []))}") + logger.info(f" - Fields generated: {len(ai_payload.get('fields', {}))}") + return ai_payload except Exception as e: logger.error("AI-only structured generation failed | user=%s | err=%s", user_id, repr(e)) @@ -68,6 +107,7 @@ class AutoFillRefreshService: } # Fallback to previous behavior (DB + sparse overrides) + logger.info("AutoFillRefreshService: using fallback behavior (DB + sparse overrides)") payload = await self.autofill.get_autofill(user_id) logger.info("AutoFillRefreshService: Base payload fields: %d", len(payload.get('fields', {}))) diff --git a/backend/api/content_planning/services/content_strategy/autofill/ai_structured_autofill.py b/backend/api/content_planning/services/content_strategy/autofill/ai_structured_autofill.py index 87bebcd3..41f6d207 100644 --- a/backend/api/content_planning/services/content_strategy/autofill/ai_structured_autofill.py +++ b/backend/api/content_planning/services/content_strategy/autofill/ai_structured_autofill.py @@ -496,10 +496,21 @@ Generate the complete JSON with all 30 fields personalized for {website_url}: logger.info("AIStructuredAutofillService: generating %d fields | user=%s", len(CORE_FIELDS), user_id) logger.debug("AIStructuredAutofillService: properties=%d", len(schema.get('properties', {}))) + # Log context summary for debugging + logger.info("AIStructuredAutofillService: context summary | user=%s", user_id) + logger.info(" - Website analysis exists: %s", bool(context_summary.get('user_profile', {}).get('website_url'))) + logger.info(" - Research config: %s", context_summary.get('research_config', {}).get('research_depth', 'None')) + logger.info(" - API capabilities: %s", len(context_summary.get('api_capabilities', {}).get('providers', []))) + logger.info(" - Content analysis: %s", bool(context_summary.get('content_analysis'))) + logger.info(" - Audience insights: %s", bool(context_summary.get('audience_insights'))) + + # Log prompt length for debugging + logger.info("AIStructuredAutofillService: prompt length=%d chars | user=%s", len(prompt), user_id) + last_result = None for attempt in range(self.max_retries + 1): try: - logger.info(f"AI structured call attempt {attempt + 1}/{self.max_retries + 1}") + logger.info(f"AI structured call attempt {attempt + 1}/{self.max_retries + 1} | user=%s", user_id) result = await self.ai.execute_structured_json_call( service_type=AIServiceType.STRATEGIC_INTELLIGENCE, prompt=prompt, @@ -507,8 +518,34 @@ Generate the complete JSON with all 30 fields personalized for {website_url}: ) last_result = result + # Log AI response details + logger.info(f"AI response received | attempt={attempt + 1} | user=%s", user_id) + if isinstance(result, dict): + logger.info(f" - Response keys: {list(result.keys())}") + logger.info(f" - Response type: dict with {len(result)} items") + + # Handle wrapped response from AI service manager + if 'data' in result and 'success' in result: + # This is a wrapped response from AI service manager + if result.get('success'): + # Extract the actual AI response from the 'data' field + ai_response = result.get('data', {}) + logger.info(f" - Extracted AI response from wrapped response") + logger.info(f" - AI response keys: {list(ai_response.keys()) if isinstance(ai_response, dict) else 'N/A'}") + last_result = ai_response + else: + # AI service failed + error_msg = result.get('error', 'Unknown AI service error') + logger.error(f" - AI service failed: {error_msg}") + last_result = {'error': error_msg} + elif 'error' in result: + logger.error(f" - AI returned error: {result['error']}") + else: + logger.warning(f" - Response type: {type(result)}") + # Check if we should retry - if not self._should_retry(result, attempt): + if not self._should_retry(last_result, attempt): + logger.info(f"Retry not needed | attempt={attempt + 1} | user=%s", user_id) break # Add a small delay before retry diff --git a/backend/api/content_planning/services/content_strategy/onboarding/data_integration.py b/backend/api/content_planning/services/content_strategy/onboarding/data_integration.py index 70dabd83..416ef324 100644 --- a/backend/api/content_planning/services/content_strategy/onboarding/data_integration.py +++ b/backend/api/content_planning/services/content_strategy/onboarding/data_integration.py @@ -7,6 +7,7 @@ import logging from typing import Dict, Any, Optional, List from datetime import datetime, timedelta from sqlalchemy.orm import Session +import traceback # Import database models from models.enhanced_strategy_models import ( @@ -39,6 +40,13 @@ class OnboardingDataIntegrationService: api_keys_data = self._get_api_keys_data(user_id, db) onboarding_session = self._get_onboarding_session(user_id, db) + # Log data source status + logger.info(f"Data source status for user {user_id}:") + logger.info(f" - Website analysis: {'✅ Found' if website_analysis else '❌ Missing'}") + logger.info(f" - Research preferences: {'✅ Found' if research_preferences else '❌ Missing'}") + logger.info(f" - API keys data: {'✅ Found' if api_keys_data else '❌ Missing'}") + logger.info(f" - Onboarding session: {'✅ Found' if onboarding_session else '❌ Missing'}") + # Process and integrate data integrated_data = { 'website_analysis': website_analysis, @@ -49,6 +57,14 @@ class OnboardingDataIntegrationService: 'processing_timestamp': datetime.utcnow().isoformat() } + # Log data quality assessment + data_quality = integrated_data['data_quality'] + logger.info(f"Data quality assessment for user {user_id}:") + logger.info(f" - Completeness: {data_quality.get('completeness', 0):.2f}") + logger.info(f" - Freshness: {data_quality.get('freshness', 0):.2f}") + logger.info(f" - Relevance: {data_quality.get('relevance', 0):.2f}") + logger.info(f" - Confidence: {data_quality.get('confidence', 0):.2f}") + # Store integrated data await self._store_integrated_data(user_id, integrated_data, db) @@ -57,6 +73,7 @@ class OnboardingDataIntegrationService: except Exception as e: logger.error(f"Error processing onboarding data for user {user_id}: {str(e)}") + logger.error("Traceback:\n%s", traceback.format_exc()) return self._get_fallback_data() def _get_website_analysis(self, user_id: int, db: Session) -> Dict[str, Any]: diff --git a/backend/services/llm_providers/gemini_provider.py b/backend/services/llm_providers/gemini_provider.py index 387e42cb..817a9fc4 100644 --- a/backend/services/llm_providers/gemini_provider.py +++ b/backend/services/llm_providers/gemini_provider.py @@ -7,7 +7,20 @@ import google.genai as genai from google.genai import types from dotenv import load_dotenv -load_dotenv(Path('../../../.env')) + +# Fix the environment loading path - load from backend directory +current_dir = Path(__file__).parent.parent # services directory +backend_dir = current_dir.parent # backend directory +env_path = backend_dir / '.env' + +if env_path.exists(): + load_dotenv(env_path) + print(f"Loaded .env from: {env_path}") +else: + # Fallback to current directory + load_dotenv() + print(f"No .env found at {env_path}, using current directory") + from loguru import logger logger.remove() logger.add(sys.stdout, @@ -31,14 +44,33 @@ import logging logging.basicConfig(level=logging.INFO, format='[%(asctime)s-%(levelname)s-%(module)s-%(lineno)d]- %(message)s') logger = logging.getLogger(__name__) +def get_gemini_api_key() -> str: + """Get Gemini API key with proper error handling.""" + api_key = os.getenv('GEMINI_API_KEY') + if not api_key: + error_msg = "GEMINI_API_KEY environment variable is not set. Please set it in your .env file." + logger.error(error_msg) + raise ValueError(error_msg) + + # Validate API key format (basic check) + if not api_key.startswith('AIza'): + error_msg = "GEMINI_API_KEY appears to be invalid. It should start with 'AIza'." + logger.error(error_msg) + raise ValueError(error_msg) + + return api_key + @retry(wait=wait_random_exponential(min=1, max=60), stop=stop_after_attempt(6)) def gemini_text_response(prompt, temperature, top_p, n, max_tokens, system_prompt): """ Common functiont to get response from gemini pro Text. """ #FIXME: Include : https://github.com/google-gemini/cookbook/blob/main/quickstarts/rest/System_instructions_REST.ipynb try: - client = genai.Client(api_key=os.getenv('GEMINI_API_KEY')) + api_key = get_gemini_api_key() + client = genai.Client(api_key=api_key) + logger.info("✅ Gemini client initialized successfully") except Exception as err: logger.error(f"Failed to configure Gemini: {err}") + raise logger.info(f"Temp: {temperature}, MaxTokens: {max_tokens}, TopP: {top_p}, N: {n}") # Set up AI model config generation_config = { @@ -121,20 +153,32 @@ async def test_gemini_api_key(api_key: str) -> tuple[bool, str]: tuple[bool, str]: A tuple containing (is_valid, message) """ try: + # Validate API key format first + if not api_key: + return False, "API key is empty" + + if not api_key.startswith('AIza'): + return False, "API key format appears invalid (should start with 'AIza')" + # Configure Gemini with the provided key - genai.configure(api_key=api_key) + client = genai.Client(api_key=api_key) # Try to list models as a simple API test - models = genai.list_models() + models = client.models.list() # Check if Gemini Pro is available - if any(model.name == "gemini-pro" for model in models): + model_names = [model.name for model in models] + logger.info(f"Available models: {model_names}") + + if any("gemini" in model_name.lower() for model_name in model_names): return True, "Gemini API key is valid" else: - return False, "Gemini Pro model not available with this API key" + return False, "No Gemini models available with this API key" except Exception as e: - return False, f"Error testing Gemini API key: {str(e)}" + error_msg = f"Error testing Gemini API key: {str(e)}" + logger.error(error_msg) + return False, error_msg def gemini_pro_text_gen(prompt, temperature=0.7, top_p=0.9, top_k=40, max_tokens=2048): """ @@ -151,18 +195,20 @@ def gemini_pro_text_gen(prompt, temperature=0.7, top_p=0.9, top_k=40, max_tokens str: The generated text completion """ try: - # Configure the model - model = genai.GenerativeModel('gemini-pro') + # Get API key with proper error handling + api_key = get_gemini_api_key() + client = genai.Client(api_key=api_key) - # Generate content - response = model.generate_content( - prompt, - generation_config=genai.types.GenerationConfig( + # Generate content using the new client + response = client.models.generate_content( + model='gemini-2.5-flash', + contents=prompt, + config=types.GenerateContentConfig( + max_output_tokens=max_tokens, temperature=temperature, top_p=top_p, top_k=top_k, - max_output_tokens=max_tokens, - ) + ), ) # Return the generated text @@ -210,7 +256,10 @@ def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9, Generate structured JSON response using Google's Gemini Pro model. """ try: - client = genai.Client(api_key=os.getenv('GEMINI_API_KEY')) + # Get API key with proper error handling + api_key = get_gemini_api_key() + client = genai.Client(api_key=api_key) + logger.info("✅ Gemini client initialized for structured JSON response") # Build config using official SDK schema type try: @@ -329,6 +378,10 @@ def gemini_structured_json_response(prompt, schema, temperature=0.7, top_p=0.9, logger.error(f"Error parsing structured response: {e}") return {"error": f"Failed to parse JSON response: {e}", "raw_response": (response.text or '')} + except ValueError as e: + # API key related errors + logger.error(f"API key error in Gemini Pro structured JSON generation: {e}") + return {"error": str(e)} except Exception as e: logger.error(f"Error in Gemini Pro structured JSON generation: {e}") return {"error": str(e)} diff --git a/backend/test_env_check.py b/backend/test_env_check.py new file mode 100644 index 00000000..502382d6 --- /dev/null +++ b/backend/test_env_check.py @@ -0,0 +1,142 @@ +#!/usr/bin/env python3 +""" +Test script to check environment variables and API key loading. +""" + +import os +import sys +from pathlib import Path + +# Add the backend directory to the Python path +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from dotenv import load_dotenv + +def test_environment_loading(): + """Test environment variable loading.""" + print("🔍 Testing environment variable loading...") + + # Check current working directory + print(f"Current working directory: {os.getcwd()}") + + # Check if .env file exists in various locations + possible_env_paths = [ + Path('.env'), # Current directory + Path('../.env'), # Parent directory + Path('../../.env'), # Grandparent directory + Path('../../../.env'), # Great-grandparent directory + Path('backend/.env'), # Backend directory + ] + + print("\n📁 Checking for .env files:") + for env_path in possible_env_paths: + if env_path.exists(): + print(f"✅ Found .env file: {env_path.absolute()}") + else: + print(f"❌ No .env file: {env_path.absolute()}") + + # Try to load .env from different locations + print("\n🔄 Attempting to load .env files:") + for env_path in possible_env_paths: + if env_path.exists(): + print(f"Loading .env from: {env_path.absolute()}") + load_dotenv(env_path) + break + else: + print("⚠️ No .env file found, trying to load from current directory") + load_dotenv() + + # Check environment variables + print("\n🔑 Checking environment variables:") + env_vars_to_check = [ + 'GEMINI_API_KEY', + 'GOOGLE_API_KEY', + 'OPENAI_API_KEY', + 'DATABASE_URL', + 'SECRET_KEY' + ] + + for var in env_vars_to_check: + value = os.getenv(var) + if value: + # Show first few characters for security + masked_value = value[:8] + "..." if len(value) > 8 else "***" + print(f"✅ {var}: {masked_value}") + else: + print(f"❌ {var}: Not set") + + # Test specific Gemini API key loading + print("\n🤖 Testing Gemini API key loading:") + gemini_key = os.getenv('GEMINI_API_KEY') + if gemini_key: + print(f"✅ GEMINI_API_KEY found: {gemini_key[:8]}...") + + # Test if the key looks valid + if len(gemini_key) > 20: + print("✅ API key length looks valid") + else: + print("⚠️ API key seems too short") + else: + print("❌ GEMINI_API_KEY not found") + + # Check alternative names + alternative_keys = ['GOOGLE_API_KEY', 'GEMINI_KEY', 'GOOGLE_AI_API_KEY'] + for alt_key in alternative_keys: + alt_value = os.getenv(alt_key) + if alt_value: + print(f"⚠️ Found alternative key {alt_key}: {alt_value[:8]}...") + + return gemini_key is not None + +def test_gemini_provider_import(): + """Test importing the Gemini provider.""" + print("\n🧪 Testing Gemini provider import...") + + try: + from services.llm_providers.gemini_provider import gemini_structured_json_response + print("✅ Successfully imported gemini_structured_json_response") + return True + except Exception as e: + print(f"❌ Failed to import Gemini provider: {e}") + return False + +def test_ai_service_manager_import(): + """Test importing the AI service manager.""" + print("\n🧪 Testing AI service manager import...") + + try: + from services.ai_service_manager import AIServiceManager + print("✅ Successfully imported AIServiceManager") + + # Try to create an instance + ai_manager = AIServiceManager() + print("✅ Successfully created AIServiceManager instance") + return True + except Exception as e: + print(f"❌ Failed to import/create AI service manager: {e}") + return False + +if __name__ == "__main__": + print("🚀 Starting environment and API key validation tests") + print("=" * 60) + + # Test environment loading + env_ok = test_environment_loading() + + # Test imports + gemini_import_ok = test_gemini_provider_import() + ai_manager_ok = test_ai_service_manager_import() + + print("\n" + "=" * 60) + print("📊 Test Results Summary:") + print(f"Environment loading: {'✅ PASS' if env_ok else '❌ FAIL'}") + print(f"Gemini provider import: {'✅ PASS' if gemini_import_ok else '❌ FAIL'}") + print(f"AI service manager: {'✅ PASS' if ai_manager_ok else '❌ FAIL'}") + + if not env_ok: + print("\n💡 To fix environment issues:") + print("1. Create a .env file in the backend directory") + print("2. Add your GEMINI_API_KEY to the .env file") + print("3. Example: GEMINI_API_KEY=your_actual_api_key_here") + + print("\n" + "=" * 60) \ No newline at end of file diff --git a/backend/test_onboarding_data.py b/backend/test_onboarding_data.py new file mode 100644 index 00000000..e035fd1c --- /dev/null +++ b/backend/test_onboarding_data.py @@ -0,0 +1,463 @@ +#!/usr/bin/env python3 +""" +Test script to validate onboarding data existence in the database. +This script checks if onboarding data exists for test users and validates the data flow. +""" + +import sys +import os +import asyncio +import logging +from datetime import datetime +from typing import Dict, Any, Optional + +# Add the backend directory to the Python path +sys.path.append(os.path.dirname(os.path.abspath(__file__))) + +from sqlalchemy.orm import Session +from services.database import get_db_session +from models.onboarding import OnboardingSession, WebsiteAnalysis, ResearchPreferences, APIKey +from models.enhanced_strategy_models import OnboardingDataIntegration +from api.content_planning.services.content_strategy.onboarding.data_integration import OnboardingDataIntegrationService +from api.content_planning.services.content_strategy.autofill.ai_structured_autofill import AIStructuredAutofillService +from services.ai_service_manager import AIServiceManager + +# Configure logging +logging.basicConfig( + level=logging.DEBUG, + format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', + handlers=[ + logging.StreamHandler(sys.stdout), + logging.FileHandler('onboarding_test.log') + ] +) +logger = logging.getLogger(__name__) + +class OnboardingDataValidator: + """Validator for onboarding data existence and quality.""" + + def __init__(self): + self.db_session = get_db_session() + self.data_integration_service = OnboardingDataIntegrationService() + self.ai_service = AIStructuredAutofillService() + self.ai_manager = AIServiceManager() + + def test_database_connection(self) -> bool: + """Test database connection.""" + try: + # Simple query to test connection + from sqlalchemy import text + result = self.db_session.execute(text("SELECT 1")) + logger.info("✅ Database connection successful") + return True + except Exception as e: + logger.error(f"❌ Database connection failed: {e}") + return False + + def check_onboarding_sessions(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: + """Check onboarding sessions for given user IDs.""" + if user_ids is None: + user_ids = [1, 2, 3] # Default test user IDs + + results = {} + + for user_id in user_ids: + logger.info(f"🔍 Checking onboarding session for user {user_id}") + + try: + session = self.db_session.query(OnboardingSession).filter( + OnboardingSession.user_id == user_id + ).order_by(OnboardingSession.updated_at.desc()).first() + + if session: + results[user_id] = { + 'session_exists': True, + 'session_id': session.id, + 'status': session.status, + 'progress': session.progress, + 'created_at': session.created_at.isoformat(), + 'updated_at': session.updated_at.isoformat(), + 'data': session.to_dict() if hasattr(session, 'to_dict') else str(session) + } + logger.info(f"✅ Onboarding session found for user {user_id}: {session.status}") + else: + results[user_id] = { + 'session_exists': False, + 'error': 'No onboarding session found' + } + logger.warning(f"❌ No onboarding session found for user {user_id}") + + except Exception as e: + results[user_id] = { + 'session_exists': False, + 'error': str(e) + } + logger.error(f"❌ Error checking onboarding session for user {user_id}: {e}") + + return results + + def check_website_analysis(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: + """Check website analysis data for given user IDs.""" + if user_ids is None: + user_ids = [1, 2, 3] + + results = {} + + for user_id in user_ids: + logger.info(f"🔍 Checking website analysis for user {user_id}") + + try: + # Get onboarding session first + session = self.db_session.query(OnboardingSession).filter( + OnboardingSession.user_id == user_id + ).order_by(OnboardingSession.updated_at.desc()).first() + + if not session: + results[user_id] = { + 'website_analysis_exists': False, + 'error': 'No onboarding session found' + } + continue + + # Get website analysis + website_analysis = self.db_session.query(WebsiteAnalysis).filter( + WebsiteAnalysis.session_id == session.id + ).order_by(WebsiteAnalysis.updated_at.desc()).first() + + if website_analysis: + results[user_id] = { + 'website_analysis_exists': True, + 'analysis_id': website_analysis.id, + 'website_url': website_analysis.website_url, + 'status': website_analysis.status, + 'created_at': website_analysis.created_at.isoformat(), + 'updated_at': website_analysis.updated_at.isoformat(), + 'data_keys': list(website_analysis.to_dict().keys()) if hasattr(website_analysis, 'to_dict') else [] + } + logger.info(f"✅ Website analysis found for user {user_id}: {website_analysis.website_url}") + else: + results[user_id] = { + 'website_analysis_exists': False, + 'error': 'No website analysis found' + } + logger.warning(f"❌ No website analysis found for user {user_id}") + + except Exception as e: + results[user_id] = { + 'website_analysis_exists': False, + 'error': str(e) + } + logger.error(f"❌ Error checking website analysis for user {user_id}: {e}") + + return results + + def check_research_preferences(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: + """Check research preferences data for given user IDs.""" + if user_ids is None: + user_ids = [1, 2, 3] + + results = {} + + for user_id in user_ids: + logger.info(f"🔍 Checking research preferences for user {user_id}") + + try: + # Get onboarding session first + session = self.db_session.query(OnboardingSession).filter( + OnboardingSession.user_id == user_id + ).order_by(OnboardingSession.updated_at.desc()).first() + + if not session: + results[user_id] = { + 'research_preferences_exists': False, + 'error': 'No onboarding session found' + } + continue + + # Get research preferences + research_prefs = self.db_session.query(ResearchPreferences).filter( + ResearchPreferences.session_id == session.id + ).first() + + if research_prefs: + results[user_id] = { + 'research_preferences_exists': True, + 'prefs_id': research_prefs.id, + 'research_depth': research_prefs.research_depth, + 'content_types': research_prefs.content_types, + 'created_at': research_prefs.created_at.isoformat(), + 'updated_at': research_prefs.updated_at.isoformat(), + 'data_keys': list(research_prefs.to_dict().keys()) if hasattr(research_prefs, 'to_dict') else [] + } + logger.info(f"✅ Research preferences found for user {user_id}: {research_prefs.research_depth}") + else: + results[user_id] = { + 'research_preferences_exists': False, + 'error': 'No research preferences found' + } + logger.warning(f"❌ No research preferences found for user {user_id}") + + except Exception as e: + results[user_id] = { + 'research_preferences_exists': False, + 'error': str(e) + } + logger.error(f"❌ Error checking research preferences for user {user_id}: {e}") + + return results + + def check_api_keys(self, user_ids: list = None) -> Dict[int, Dict[str, Any]]: + """Check API keys data for given user IDs.""" + if user_ids is None: + user_ids = [1, 2, 3] + + results = {} + + for user_id in user_ids: + logger.info(f"🔍 Checking API keys for user {user_id}") + + try: + # Get onboarding session first + session = self.db_session.query(OnboardingSession).filter( + OnboardingSession.user_id == user_id + ).order_by(OnboardingSession.updated_at.desc()).first() + + if not session: + results[user_id] = { + 'api_keys_exist': False, + 'error': 'No onboarding session found' + } + continue + + # Get API keys + api_keys = self.db_session.query(APIKey).filter( + APIKey.session_id == session.id + ).all() + + if api_keys: + results[user_id] = { + 'api_keys_exist': True, + 'count': len(api_keys), + 'providers': [key.provider for key in api_keys], + 'created_at': api_keys[0].created_at.isoformat() if api_keys else None, + 'updated_at': api_keys[0].updated_at.isoformat() if api_keys else None + } + logger.info(f"✅ API keys found for user {user_id}: {len(api_keys)} keys") + else: + results[user_id] = { + 'api_keys_exist': False, + 'error': 'No API keys found' + } + logger.warning(f"❌ No API keys found for user {user_id}") + + except Exception as e: + results[user_id] = { + 'api_keys_exist': False, + 'error': str(e) + } + logger.error(f"❌ Error checking API keys for user {user_id}: {e}") + + return results + + async def test_data_integration_service(self, user_id: int = 1) -> Dict[str, Any]: + """Test the data integration service.""" + logger.info(f"🔍 Testing data integration service for user {user_id}") + + try: + # Test the process_onboarding_data method + integrated_data = await self.data_integration_service.process_onboarding_data(user_id, self.db_session) + + if integrated_data: + result = { + 'success': True, + 'has_website_analysis': bool(integrated_data.get('website_analysis')), + 'has_research_preferences': bool(integrated_data.get('research_preferences')), + 'has_api_keys_data': bool(integrated_data.get('api_keys_data')), + 'has_onboarding_session': bool(integrated_data.get('onboarding_session')), + 'data_quality': integrated_data.get('data_quality', {}), + 'processing_timestamp': integrated_data.get('processing_timestamp'), + 'context_keys': list(integrated_data.keys()) + } + + logger.info(f"✅ Data integration successful for user {user_id}") + logger.info(f" Website analysis: {result['has_website_analysis']}") + logger.info(f" Research preferences: {result['has_research_preferences']}") + logger.info(f" API keys: {result['has_api_keys_data']}") + logger.info(f" Onboarding session: {result['has_onboarding_session']}") + + return result + else: + logger.error(f"❌ Data integration returned None for user {user_id}") + return {'success': False, 'error': 'No data returned'} + + except Exception as e: + logger.error(f"❌ Data integration failed for user {user_id}: {e}") + return {'success': False, 'error': str(e)} + + async def test_ai_service_configuration(self) -> Dict[str, Any]: + """Test AI service configuration.""" + logger.info("🔍 Testing AI service configuration") + + try: + # Test basic AI service functionality + test_prompt = "Generate a simple test response" + test_schema = { + "type": "OBJECT", + "properties": { + "test_field": {"type": "STRING", "description": "A test field"} + }, + "required": ["test_field"] + } + + # Test the AI service manager + result = await self.ai_manager.execute_structured_json_call( + service_type="STRATEGIC_INTELLIGENCE", + prompt=test_prompt, + schema=test_schema + ) + + if result and not result.get('error'): + logger.info("✅ AI service configuration successful") + return { + 'success': True, + 'ai_service_working': True, + 'test_response': result + } + else: + logger.error(f"❌ AI service test failed: {result.get('error', 'Unknown error')}") + return { + 'success': False, + 'ai_service_working': False, + 'error': result.get('error', 'Unknown error') + } + + except Exception as e: + logger.error(f"❌ AI service configuration test failed: {e}") + return { + 'success': False, + 'ai_service_working': False, + 'error': str(e) + } + + async def test_ai_structured_autofill(self, user_id: int = 1) -> Dict[str, Any]: + """Test the AI structured autofill service.""" + logger.info(f"🔍 Testing AI structured autofill for user {user_id}") + + try: + # First get the context + integrated_data = await self.data_integration_service.process_onboarding_data(user_id, self.db_session) + + if not integrated_data: + logger.error(f"❌ No integrated data available for user {user_id}") + return {'success': False, 'error': 'No integrated data available'} + + # Test the AI structured autofill + result = await self.ai_service.generate_autofill_fields(user_id, integrated_data) + + if result: + meta = result.get('meta', {}) + fields = result.get('fields', {}) + + test_result = { + 'success': True, + 'ai_used': meta.get('ai_used', False), + 'ai_overrides_count': meta.get('ai_overrides_count', 0), + 'success_rate': meta.get('success_rate', 0), + 'attempts': meta.get('attempts', 0), + 'missing_fields': meta.get('missing_fields', []), + 'fields_generated': len(fields), + 'sample_fields': list(fields.keys())[:5] if fields else [] + } + + logger.info(f"✅ AI structured autofill test completed for user {user_id}") + logger.info(f" AI used: {test_result['ai_used']}") + logger.info(f" Fields generated: {test_result['fields_generated']}") + logger.info(f" Success rate: {test_result['success_rate']:.1f}%") + logger.info(f" Attempts: {test_result['attempts']}") + + return test_result + else: + logger.error(f"❌ AI structured autofill returned None for user {user_id}") + return {'success': False, 'error': 'No result returned'} + + except Exception as e: + logger.error(f"❌ AI structured autofill test failed for user {user_id}: {e}") + return {'success': False, 'error': str(e)} + + def print_summary(self, results: Dict[str, Any]): + """Print a summary of all test results.""" + logger.info("\n" + "="*80) + logger.info("📊 ONBOARDING DATA VALIDATION SUMMARY") + logger.info("="*80) + + for test_name, result in results.items(): + logger.info(f"\n🔍 {test_name.upper()}:") + if isinstance(result, dict): + for key, value in result.items(): + if isinstance(value, dict): + logger.info(f" {key}:") + for sub_key, sub_value in value.items(): + logger.info(f" {sub_key}: {sub_value}") + else: + logger.info(f" {key}: {value}") + else: + logger.info(f" {result}") + + logger.info("\n" + "="*80) + + def cleanup(self): + """Clean up database session.""" + if self.db_session: + self.db_session.close() + +async def main(): + """Main test function.""" + logger.info("🚀 Starting onboarding data validation tests") + + validator = OnboardingDataValidator() + + try: + # Test database connection + db_connected = validator.test_database_connection() + if not db_connected: + logger.error("❌ Cannot proceed without database connection") + return + + # Test user IDs to check + test_user_ids = [1, 2, 3] + + # Run all tests + results = { + 'database_connection': db_connected, + 'onboarding_sessions': validator.check_onboarding_sessions(test_user_ids), + 'website_analysis': validator.check_website_analysis(test_user_ids), + 'research_preferences': validator.check_research_preferences(test_user_ids), + 'api_keys': validator.check_api_keys(test_user_ids), + 'data_integration': await validator.test_data_integration_service(1), + 'ai_service_config': await validator.test_ai_service_configuration(), + 'ai_structured_autofill': await validator.test_ai_structured_autofill(1) + } + + # Print summary + validator.print_summary(results) + + # Determine overall status + overall_success = all([ + results['database_connection'], + any(session.get('session_exists', False) for session in results['onboarding_sessions'].values()), + results['data_integration']['success'], + results['ai_service_config']['success'] + ]) + + if overall_success: + logger.info("✅ All critical tests passed!") + else: + logger.error("❌ Some critical tests failed!") + + except Exception as e: + logger.error(f"❌ Test execution failed: {e}") + finally: + validator.cleanup() + +if __name__ == "__main__": + asyncio.run(main()) \ No newline at end of file diff --git a/docs/content_strategy_alwrityit_implementation_plan.md b/docs/content_strategy_alwrityit_implementation_plan.md new file mode 100644 index 00000000..52700a41 --- /dev/null +++ b/docs/content_strategy_alwrityit_implementation_plan.md @@ -0,0 +1,446 @@ +# ALwrity It - Content Strategy Analysis Customization Feature + +## 🎯 **Feature Overview** + +**ALwrity It** allows users to customize AI-generated analysis components when they don't meet expectations. Users can manually edit data or use AI to regenerate with custom prompts, maintaining context from other analysis components. + +### **Key Benefits:** +- ✅ **User Control**: Full control over AI-generated analysis +- ✅ **Flexibility**: Manual editing or AI-powered regeneration +- ✅ **Context Awareness**: AI considers other analysis components +- ✅ **Structured Output**: Consistent JSON responses via Gemini +- ✅ **Version History**: Track and revert changes +- ✅ **Preview Mode**: Compare original vs modified analysis + +## 🏗️ **Technical Architecture** + +### **File Structure** +``` +frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/ +├── components/ +│ ├── content_strategy_alwrityit/ +│ │ ├── ALwrityItButton.tsx # Main button component +│ │ ├── ALwrityItModal.tsx # Main modal container +│ │ ├── ManualEditForm.tsx # Manual editing form +│ │ ├── AIEditForm.tsx # AI prompt form +│ │ ├── QuickRegenerateForm.tsx # Quick AI regeneration +│ │ ├── AnalysisPreview.tsx # Preview changes +│ │ ├── ModeSelector.tsx # Mode selection interface +│ │ ├── VersionHistory.tsx # Version tracking +│ │ └── TemplateLibrary.tsx # Saved templates +│ └── [existing analysis cards] +├── hooks/ +│ ├── content_strategy_alwrityit/ +│ │ ├── useALwrityIt.ts # Main hook for ALwrity It functionality +│ │ ├── useAnalysisRegeneration.ts # AI regeneration logic +│ │ ├── useManualEditing.ts # Manual editing logic +│ │ └── useVersionHistory.ts # Version management +├── types/ +│ ├── content_strategy_alwrityit/ +│ │ ├── alwrityIt.types.ts # TypeScript types +│ │ ├── analysisSchemas.ts # JSON schemas for each component +│ │ └── promptTemplates.ts # AI prompt templates +├── utils/ +│ ├── content_strategy_alwrityit/ +│ │ ├── analysisTransformers.ts # Data transformation utilities +│ │ ├── promptGenerators.ts # AI prompt generation +│ │ ├── schemaValidators.ts # JSON schema validation +│ │ └── versionManager.ts # Version control utilities +└── providers/ + └── ALwrityItProvider.tsx # Context provider for state management +``` + +### **Backend Structure** +``` +backend/api/content_planning/api/content_strategy/ +├── endpoints/ +│ ├── alwrityit_endpoints.py # ALwrity It API endpoints +│ └── [existing endpoints] +├── services/ +│ ├── alwrityit_service.py # ALwrity It business logic +│ ├── analysis_regeneration_service.py # AI regeneration service +│ └── version_management_service.py # Version control service +└── models/ + ├── alwrityit_models.py # Database models for versions/templates + └── [existing models] +``` + +## 📋 **Implementation Phases** + +### **Phase 1: Core Infrastructure (2-3 days)** + +#### **1.1 Backend API Endpoints** +```python +# backend/api/content_planning/api/content_strategy/endpoints/alwrityit_endpoints.py + +@router.post("/regenerate-analysis-component") +async def regenerate_analysis_component(request: RegenerateAnalysisRequest): + """Regenerate specific analysis component with AI""" + +@router.post("/update-analysis-component-manual") +async def update_analysis_component_manual(request: ManualUpdateRequest): + """Update analysis component with manual edits""" + +@router.get("/analysis-component-schema/{component_type}") +async def get_analysis_component_schema(component_type: str): + """Get JSON schema for specific component type""" + +@router.get("/analysis-versions/{strategy_id}/{component_type}") +async def get_analysis_versions(strategy_id: int, component_type: str): + """Get version history for analysis component""" +``` + +#### **1.2 Frontend Core Components** +```typescript +// ALwrityItButton.tsx +const ALwrityItButton = ({ componentType, currentData, onUpdate }) => { + return ( + setModalOpen(true)} + > + + + ); +}; +``` + +### **Phase 2: Modal & Mode Selection (1-2 days)** + +#### **2.1 Main Modal Component** +```typescript +// ALwrityItModal.tsx +const ALwrityItModal = ({ open, onClose, componentType, currentData, onUpdate }) => { + const [mode, setMode] = useState('manual'); + + return ( + + ALwrity It - {getComponentDisplayName(componentType)} + + + + {mode === 'manual' && ( + + )} + + {mode === 'ai' && ( + + )} + + {mode === 'regenerate' && ( + + )} + + + ); +}; +``` + +#### **2.2 Mode Selector Component** +```typescript +// ModeSelector.tsx +const ModeSelector = ({ mode, onModeChange }) => { + const modes = [ + { + id: 'manual', + title: 'Manual Edit', + description: 'Edit analysis data manually', + icon: , + color: '#4caf50' + }, + { + id: 'ai', + title: 'AI Custom', + description: 'Provide custom prompt for AI regeneration', + icon: , + color: '#667eea' + }, + { + id: 'regenerate', + title: 'Quick Regenerate', + description: 'Regenerate with improved AI analysis', + icon: , + color: '#ff9800' + } + ]; + + return ( + + {modes.map((modeOption) => ( + + onModeChange(modeOption.id)}> + + {modeOption.icon} + {modeOption.title} + {modeOption.description} + + + + ))} + + ); +}; +``` + +### **Phase 3: Manual Editing Interface (1-2 days)** + +#### **3.1 Manual Edit Form** +```typescript +// ManualEditForm.tsx +const ManualEditForm = ({ componentType, currentData, onSave }) => { + const schema = useAnalysisSchema(componentType); + const [formData, setFormData] = useState(currentData); + + return ( + + Manual Edit - {getComponentDisplayName(componentType)} + + {Object.entries(schema.properties).map(([field, fieldSchema]) => ( + setFormData(prev => ({ ...prev, [field]: value }))} + /> + ))} + + + + + + + ); +}; +``` + +### **Phase 4: AI Integration (2-3 days)** + +#### **4.1 AI Edit Form** +```typescript +// AIEditForm.tsx +const AIEditForm = ({ componentType, currentData, onGenerate }) => { + const [prompt, setPrompt] = useState(''); + const [suggestedPrompts, setSuggestedPrompts] = useState([]); + + return ( + + AI Custom Regeneration + + setPrompt(e.target.value)} + placeholder="Describe how you want to improve this analysis..." + /> + + + {suggestedPrompts.map((suggestion, index) => ( + setPrompt(suggestion)} + sx={{ mr: 1, mb: 1 }} + /> + ))} + + + + + ); +}; +``` + +#### **4.2 Backend AI Service** +```python +# backend/services/alwrityit_service.py +class ALwrityItService: + async def regenerate_analysis_component( + self, + component_type: str, + current_data: dict, + user_prompt: str = None, + context_data: dict = None + ) -> dict: + prompt = self._build_regeneration_prompt( + component_type, current_data, user_prompt, context_data + ) + + schema = self._get_component_schema(component_type) + + response = await self.gemini_provider.generate_structured_response( + prompt=prompt, + schema=schema, + context={ + "current_analysis": current_data, + "other_components": context_data, + "user_requirements": user_prompt, + "component_type": component_type + } + ) + + return response +``` + +### **Phase 5: Preview & Version Management (1-2 days)** + +#### **5.1 Analysis Preview Component** +```typescript +// AnalysisPreview.tsx +const AnalysisPreview = ({ original, modified, componentType, onApply, onRevert }) => { + return ( + + Preview Changes + + + + Original Analysis + + + + Modified Analysis + + + + + + + + + + ); +}; +``` + +## 🎨 **UI/UX Design Specifications** + +### **Color Scheme** +```typescript +const ALWRITY_IT_COLORS = { + primary: '#667eea', + secondary: '#764ba2', + success: '#4caf50', + warning: '#ff9800', + error: '#f44336', + background: { + modal: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 100%)', + card: 'rgba(255, 255, 255, 0.05)', + button: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' + } +}; +``` + +## 🔧 **Database Schema** + +### **Version History Table** +```sql +CREATE TABLE analysis_versions ( + id SERIAL PRIMARY KEY, + strategy_id INTEGER NOT NULL, + component_type VARCHAR(50) NOT NULL, + version_data JSONB NOT NULL, + change_type VARCHAR(20) NOT NULL, + user_prompt TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INTEGER, + description TEXT +); +``` + +### **Templates Table** +```sql +CREATE TABLE analysis_templates ( + id SERIAL PRIMARY KEY, + name VARCHAR(100) NOT NULL, + component_type VARCHAR(50) NOT NULL, + template_data JSONB NOT NULL, + description TEXT, + created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP, + created_by INTEGER, + is_public BOOLEAN DEFAULT FALSE +); +``` + +## 🚀 **Implementation Timeline** + +### **Week 1: Core Infrastructure** +- **Day 1-2**: Backend API endpoints and database models +- **Day 3-4**: Frontend component structure and basic modal +- **Day 5**: Integration with existing analysis cards + +### **Week 2: AI Integration** +- **Day 1-2**: Gemini structured response integration +- **Day 3-4**: Prompt engineering and context handling +- **Day 5**: Testing and refinement + +### **Week 3: Manual Editing & Polish** +- **Day 1-2**: Dynamic form generation and validation +- **Day 3-4**: Preview and comparison features +- **Day 5**: Version history and advanced features + +## 🧪 **Testing Strategy** + +### **Unit Tests** +- Component rendering and interactions +- Form validation and data transformation +- AI prompt generation and response parsing + +### **Integration Tests** +- API endpoint functionality +- Database operations +- AI service integration + +### **End-to-End Tests** +- Complete user workflows +- Error handling scenarios +- Performance testing + +## 📊 **Success Metrics** + +### **User Engagement** +- Number of ALwrity It button clicks per analysis +- Most frequently modified components +- User satisfaction with customization options + +### **Technical Performance** +- AI generation response times +- Modal load times +- Error rates and recovery + +## 🔄 **Future Enhancements** + +### **Phase 2 Features** +1. **Collaboration Tools**: Team comments and approvals +2. **Advanced AI**: Multi-step regeneration with user feedback +3. **Integration**: Connect with external data sources +4. **Analytics**: Detailed usage analytics and insights +5. **Templates**: Community template sharing + +--- + +**Next Steps**: +1. Review and approve this implementation plan +2. Set up development environment +3. Begin Phase 1 implementation +4. Create project milestones and tracking +5. Set up testing infrastructure \ No newline at end of file diff --git a/frontend/build/asset-manifest.json b/frontend/build/asset-manifest.json index fe4f6442..071f837a 100644 --- a/frontend/build/asset-manifest.json +++ b/frontend/build/asset-manifest.json @@ -1,13 +1,13 @@ { "files": { "main.css": "/static/css/main.c9966057.css", - "main.js": "/static/js/main.2819e23e.js", + "main.js": "/static/js/main.3e924b71.js", "index.html": "/index.html", "main.c9966057.css.map": "/static/css/main.c9966057.css.map", - "main.2819e23e.js.map": "/static/js/main.2819e23e.js.map" + "main.3e924b71.js.map": "/static/js/main.3e924b71.js.map" }, "entrypoints": [ "static/css/main.c9966057.css", - "static/js/main.2819e23e.js" + "static/js/main.3e924b71.js" ] } \ No newline at end of file diff --git a/frontend/build/index.html b/frontend/build/index.html index 677fc85e..d8f680b8 100644 --- a/frontend/build/index.html +++ b/frontend/build/index.html @@ -1 +1 @@ -Alwrity - AI Content Creation Platform
\ No newline at end of file +Alwrity - AI Content Creation Platform
\ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/ContentPlanningDashboard.tsx b/frontend/src/components/ContentPlanningDashboard/ContentPlanningDashboard.tsx index 9c9c7b79..1a5d83ad 100644 --- a/frontend/src/components/ContentPlanningDashboard/ContentPlanningDashboard.tsx +++ b/frontend/src/components/ContentPlanningDashboard/ContentPlanningDashboard.tsx @@ -19,13 +19,15 @@ import { Analytics as AnalyticsIcon, Search as SearchIcon, Lightbulb as AIInsightsIcon, - Close as CloseIcon + Close as CloseIcon, + Add as CreateIcon } from '@mui/icons-material'; import { motion, AnimatePresence } from 'framer-motion'; import ContentStrategyTab from './tabs/ContentStrategyTab'; import CalendarTab from './tabs/CalendarTab'; import AnalyticsTab from './tabs/AnalyticsTab'; import GapAnalysisTab from './tabs/GapAnalysisTab'; +import CreateTab from './tabs/CreateTab'; import AIInsightsPanel from './components/AIInsightsPanel'; import ServiceStatusPanel from './components/ServiceStatusPanel'; import ProgressIndicator from './components/ProgressIndicator'; @@ -170,7 +172,8 @@ const ContentPlanningDashboard: React.FC = () => { { label: 'CONTENT STRATEGY', icon: , component: }, { label: 'CALENDAR', icon: , component: }, { label: 'ANALYTICS', icon: , component: }, - { label: 'GAP ANALYSIS', icon: , component: } + { label: 'GAP ANALYSIS', icon: , component: }, + { label: 'CREATE', icon: , component: } ]; const totalAIItems = (dashboardData.aiInsights?.length || 0) + (dashboardData.aiRecommendations?.length || 0); diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx index ca82eccc..10b9ed4d 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx @@ -478,7 +478,7 @@ const ContentStrategyBuilder: React.FC = () => { onUpdateFormField={updateFormField} onValidateFormField={validateFormField} onShowTooltip={setShowTooltip} - onViewDataSource={() => setShowDataSourceTransparency(true)} + onViewDataSource={() => setShowDataSourceTransparency(true)} onConfirmCategoryReview={handleConfirmCategoryReviewWrapper} onSetActiveCategory={setActiveCategory} onSetShowEducationalInfo={setShowEducationalInfo} diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx new file mode 100644 index 00000000..c86c347e --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx @@ -0,0 +1,615 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogContent, + DialogTitle, + Box, + Typography, + Button, + Stepper, + Step, + StepLabel, + StepContent, + Card, + CardContent, + Grid, + Chip, + IconButton, + LinearProgress, + Alert, + List, + ListItem, + ListItemIcon, + ListItemText, + Divider +} from '@mui/material'; +import { + Close as CloseIcon, + CheckCircle as CheckCircleIcon, + AutoAwesome as AutoAwesomeIcon, + Psychology as PsychologyIcon, + CalendarToday as CalendarIcon, + Analytics as AnalyticsIcon, + TrendingUp as TrendingUpIcon, + Lightbulb as LightbulbIcon, + School as SchoolIcon, + Rocket as RocketIcon, + Security as SecurityIcon, + Speed as SpeedIcon, + Group as GroupIcon, + Timeline as TimelineIcon, + Assessment as AssessmentIcon, + PlayArrow as PlayArrowIcon, + Pause as PauseIcon, + Refresh as RefreshIcon, + Edit as EditIcon, + Add as AddIcon +} from '@mui/icons-material'; +import { motion, AnimatePresence } from 'framer-motion'; + +interface StrategyOnboardingDialogProps { + open: boolean; + onClose: () => void; + onConfirmStrategy: () => void; + onEditStrategy: () => void; + onCreateNewStrategy: () => void; + currentStrategy: any; + strategyStatus: 'active' | 'inactive' | 'none'; +} + +const StrategyOnboardingDialog: React.FC = ({ + open, + onClose, + onConfirmStrategy, + onEditStrategy, + onCreateNewStrategy, + currentStrategy, + strategyStatus +}) => { + const [activeStep, setActiveStep] = useState(0); + const [loading, setLoading] = useState(false); + + const steps = [ + { + label: 'Welcome to ALwrity', + icon: , + content: ( + + + 🚀 Your AI-Powered Content Strategy Copilot + + + ALwrity democratizes professional content strategy and calendar creation, making it accessible to solopreneurs and small businesses. + + + + + + + + AI-Enhanced Strategy + + + Our AI analyzes your business, competitors, and market trends to create a comprehensive content strategy. + + + + + + + + + + Smart Calendar Creation + + + Automatically generate content calendars with optimal posting times and content mix. + + + + + + + ) + }, + { + label: 'What ALwrity Has Done', + icon: , + content: ( + + + 📊 Comprehensive Research & Analysis + + + + + + + + Market Research + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + Strategic Insights + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + ) + }, + { + label: 'Your Journey with ALwrity', + icon: , + content: ( + + + 🎯 Your 4-Step Success Path + + + + + + + 1 + Review & Confirm Strategy + + + Review the AI-generated content strategy tailored to your business. Make any adjustments and confirm to activate. + + + + + + + + + + 2 + Create Content Calendar + + + ALwrity generates a comprehensive content calendar with optimal posting times and content themes. + + + + + + + + + 3 + Measure Strategy KPIs + + + Track performance metrics and analyze content effectiveness to understand what works best. + + + + + + + + + 4 + Optimize with AI + + + Continuously improve your strategy based on performance data and AI recommendations. + + + + + + + ) + }, + { + label: 'ALwrity as Your Copilot', + icon: , + content: ( + + + 🤖 Your AI Marketing Assistant + + + Once your strategy is active, ALwrity becomes your 24/7 content marketing copilot, handling the heavy lifting while you focus on your business. + + + + + + + + + Automated Execution + + + ALwrity schedules, generates, reviews, and posts content according to your strategy, saving you hours every week. + + + + + + + + + + Performance Tracking + + + Monitor your content performance in real-time with detailed analytics and actionable insights. + + + + + + + + + + Quality Assurance + + + Every piece of content is reviewed for quality, brand consistency, and strategic alignment. + + + + + + + + + Pro Tip: ALwrity learns from your content performance and continuously optimizes your strategy for better results. + + + + ) + }, + { + label: 'Take Action', + icon: , + content: ( + + + 🎯 Ready to Activate Your Strategy? + + + {strategyStatus === 'inactive' && currentStrategy ? ( + + + + Strategy Found: We found an existing strategy that needs to be activated. + + + + + + + Current Strategy: {currentStrategy.name} + + + {currentStrategy.description} + + + + + + + + + + + + + + + + + + + + + ) : strategyStatus === 'none' ? ( + + + + No Strategy Found: Let's create your first content strategy! + + + + + + + + + + + + + ) : ( + + + + Strategy Active: Your content strategy is already active and running! + + + + + + )} + + ) + } + ]; + + const handleNext = () => { + setActiveStep((prevActiveStep) => prevActiveStep + 1); + }; + + const handleBack = () => { + setActiveStep((prevActiveStep) => prevActiveStep - 1); + }; + + const handleReset = () => { + setActiveStep(0); + }; + + return ( + + + + + + ALwrity Content Strategy Onboarding + + + + + + + + + + + {steps.map((step, index) => ( + + ( + = index ? 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)' : 'rgba(255,255,255,0.1)', + display: 'flex', + alignItems: 'center', + justifyContent: 'center', + color: 'white' + }}> + {step.icon} + + )} + > + + {step.label} + + + + + + + {step.content} + + + {index < steps.length - 1 && ( + + + + + )} + + + + + ))} + + + {activeStep === steps.length && ( + + + + + Welcome to ALwrity! + + + You're now ready to start your content marketing journey with AI assistance. + + + + + )} + + + + ); +}; + +export default StrategyOnboardingDialog; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/CalendarTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/CalendarTab.tsx index 24889843..e79128c0 100644 --- a/frontend/src/components/ContentPlanningDashboard/tabs/CalendarTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/tabs/CalendarTab.tsx @@ -8,7 +8,6 @@ import { TextField, Card, CardContent, - CardActions, Chip, IconButton, Dialog, @@ -42,7 +41,6 @@ import { CalendarToday as CalendarIcon, Event as EventIcon, Refresh as RefreshIcon, - AutoAwesome as AIIcon, TrendingUp as TrendingIcon, ContentCopy as RepurposeIcon, Analytics as AnalyticsIcon, @@ -64,7 +62,6 @@ import { } from '@mui/icons-material'; import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; import { contentPlanningApi } from '../../../services/contentPlanningApi'; -import CalendarGenerationWizard from '../components/CalendarGenerationWizard'; interface TabPanelProps { children?: React.ReactNode; @@ -100,10 +97,8 @@ const CalendarTab: React.FC = () => { updateCalendarEvents, // New calendar generation state generatedCalendar, - contentOptimization, performancePrediction, contentRepurposing, - trendingTopics, aiInsights, calendarGenerationError, dataLoading @@ -131,7 +126,7 @@ const CalendarTab: React.FC = () => { aiAnalysisResults: [] }); - const [calendarGenerationMode, setCalendarGenerationMode] = useState<'transparency' | 'wizard'>('transparency'); + const safeCalendarEvents = Array.isArray(calendarEvents) ? calendarEvents : []; useEffect(() => { loadCalendarData(); @@ -214,22 +209,6 @@ const CalendarTab: React.FC = () => { await loadCalendarData(); }; - const handleGenerateAICalendar = async () => { - try { - // This will now use the comprehensive data from the transparency dashboard - const calendarConfig = { - userData, - calendarType: 'monthly', - industry: userData.onboardingData?.industry || 'technology', - businessSize: 'sme' - }; - - await contentPlanningApi.generateComprehensiveCalendar(calendarConfig); - } catch (error) { - console.error('Error generating AI calendar:', error); - } - }; - const handleDataUpdate = (updatedData: any) => { setUserData((prev: any) => ({ ...prev, ...updatedData })); }; @@ -278,9 +257,6 @@ const CalendarTab: React.FC = () => { } }; - // Ensure calendarEvents is always an array - const safeCalendarEvents = Array.isArray(calendarEvents) ? calendarEvents : []; - return ( @@ -321,9 +297,6 @@ const CalendarTab: React.FC = () => { setTabValue(newValue)}> } iconPosition="start" /> - } iconPosition="start" /> - } iconPosition="start" /> - } iconPosition="start" /> @@ -416,102 +389,6 @@ const CalendarTab: React.FC = () => { )} - - {/* Calendar Generation Wizard with Data Transparency */} - - - - - {/* Content Optimizer Tab */} - - - - - - Content Optimization - - - {contentOptimization ? ( - - - Optimization Recommendations - - - {contentOptimization.recommendations?.map((rec: any, index: number) => ( - - - - - - - ))} - - - ) : ( - - - - No optimization data - - - Generate content optimization recommendations - - - )} - - - - - - - {/* Trending Topics Tab */} - - - - - - Trending Topics - - - {trendingTopics ? ( - - - Current Trending Topics - - - {trendingTopics.trending_topics?.map((topic: any, index: number) => ( - - ))} - - - ) : ( - - - - No trending topics - - - Get trending topics for your industry - - - )} - - - - - {/* Event Dialog */} diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/ContentOptimizerTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/ContentOptimizerTab.tsx new file mode 100644 index 00000000..9d454eb8 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/ContentOptimizerTab.tsx @@ -0,0 +1,73 @@ +import React from 'react'; +import { + Box, + Grid, + Paper, + Typography, + List, + ListItem, + ListItemText, + ListItemIcon, + Chip +} from '@mui/material'; +import { + Analytics as AnalyticsIcon, + Lightbulb as LightbulbIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; + +const ContentOptimizerTab: React.FC = () => { + const { contentOptimization } = useContentPlanningStore(); + + return ( + + + Content Optimizer + + + + + + + + Content Optimization + + + {contentOptimization ? ( + + + Optimization Recommendations + + + {contentOptimization.recommendations?.map((rec: any, index: number) => ( + + + + + + + ))} + + + ) : ( + + + + No optimization data + + + Generate content optimization recommendations + + + )} + + + + + ); +}; + +export default ContentOptimizerTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/ContentPillarsTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/ContentPillarsTab.tsx new file mode 100644 index 00000000..87b5f6b2 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/ContentPillarsTab.tsx @@ -0,0 +1,125 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Grid, + Card, + CardContent, + CardActions, + Typography, + Button, + CircularProgress +} from '@mui/material'; +import { + PieChart as PieChartIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; + +const ContentPillarsTab: React.FC = () => { + const { currentStrategy } = useContentPlanningStore(); + const [contentPillars, setContentPillars] = useState([]); + const [dataLoading, setDataLoading] = useState(false); + + useEffect(() => { + loadContentPillars(); + }, [currentStrategy]); + + const loadContentPillars = async () => { + try { + setDataLoading(true); + + // Get content pillars from current strategy + if (currentStrategy && currentStrategy.content_pillars) { + const pillars = currentStrategy.content_pillars.map((pillar: any, index: number) => ({ + name: pillar.name || `Pillar ${index + 1}`, + content_count: pillar.content_count || Math.floor(Math.random() * 20) + 5, + avg_engagement: pillar.avg_engagement || (Math.random() * 30 + 60).toFixed(1), + performance_score: pillar.performance_score || (Math.random() * 20 + 75).toFixed(0) + })); + setContentPillars(pillars); + } else { + // Default pillars if no strategy exists + setContentPillars([ + { name: 'Educational Content', content_count: 15, avg_engagement: 78.5, performance_score: 85 }, + { name: 'Thought Leadership', content_count: 8, avg_engagement: 92.3, performance_score: 91 }, + { name: 'Case Studies', content_count: 12, avg_engagement: 85.7, performance_score: 88 }, + { name: 'Industry Insights', content_count: 10, avg_engagement: 79.2, performance_score: 82 } + ]); + } + } catch (error) { + console.error('Error loading content pillars:', error); + } finally { + setDataLoading(false); + } + }; + + return ( + + + Content Pillars + + + {dataLoading ? ( + + + + ) : contentPillars.length > 0 ? ( + + + + Content Pillars Overview + + + Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics. + + + + {contentPillars.map((pillar, index) => ( + + + + + {pillar.name} + + + + Content Count + + + {pillar.content_count} + + + + + Avg. Engagement + + + {pillar.avg_engagement}% + + + + + Performance Score + + + {pillar.performance_score}/100 + + + + + + + + + + ))} + + ) : ( + + No content pillars data available + + )} + + ); +}; + +export default ContentPillarsTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx index 2a12e703..1c02e6fe 100644 --- a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx @@ -5,12 +5,10 @@ import { Paper, Typography, Button, - TextField, Card, CardContent, CardActions, Chip, - Divider, Alert, List, ListItem, @@ -39,7 +37,6 @@ import { Lightbulb as LightbulbIcon, CheckCircle as CheckCircleIcon, Warning as WarningIcon, - Search as SearchIcon, Analytics as AnalyticsIcon, Timeline as TimelineIcon, Assessment as AssessmentIcon, @@ -48,36 +45,14 @@ import { Add as AddIcon, Edit as EditIcon, Visibility as VisibilityIcon, - BarChart as BarChartIcon, - PieChart as PieChartIcon, ShowChart as ShowChartIcon, - AutoAwesome as AutoAwesomeIcon + AutoAwesome as AutoAwesomeIcon, + PlayArrow as PlayArrowIcon } from '@mui/icons-material'; import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; import { contentPlanningApi } from '../../../services/contentPlanningApi'; -import ContentStrategyBuilder from '../components/ContentStrategyBuilder'; import StrategyIntelligenceTab from '../components/StrategyIntelligenceTab'; - -interface TabPanelProps { - children?: React.ReactNode; - index: number; - value: number; -} - -function TabPanel(props: TabPanelProps) { - const { children, value, index, ...other } = props; - return ( - - ); -} +import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog'; const ContentStrategyTab: React.FC = () => { const { @@ -85,7 +60,6 @@ const ContentStrategyTab: React.FC = () => { currentStrategy, aiInsights, aiRecommendations, - performanceMetrics, loading, error, loadStrategies, @@ -93,7 +67,6 @@ const ContentStrategyTab: React.FC = () => { loadAIRecommendations } = useContentPlanningStore(); - const [tabValue, setTabValue] = useState(0); const [strategyForm, setStrategyForm] = useState({ name: '', description: '', @@ -104,25 +77,53 @@ const ContentStrategyTab: React.FC = () => { // Real data states const [strategicIntelligence, setStrategicIntelligence] = useState(null); - const [keywordResearch, setKeywordResearch] = useState(null); - const [contentPillars, setContentPillars] = useState([]); const [dataLoading, setDataLoading] = useState({ strategies: false, insights: false, recommendations: false, - strategicIntelligence: false, - keywordResearch: false, - pillars: false + strategicIntelligence: false }); + // Strategy status and onboarding + const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'none'>('none'); + const [showOnboarding, setShowOnboarding] = useState(false); + const [hasCheckedStrategy, setHasCheckedStrategy] = useState(false); + // Load data on component mount useEffect(() => { loadInitialData(); }, []); + // Check strategy status when strategies are loaded + useEffect(() => { + if (strategies && strategies.length > 0 && !hasCheckedStrategy) { + checkStrategyStatus(); + } else if ((!strategies || strategies.length === 0) && !hasCheckedStrategy) { + setStrategyStatus('none'); + setHasCheckedStrategy(true); + setShowOnboarding(true); + } + }, [strategies, hasCheckedStrategy]); + + const checkStrategyStatus = () => { + if (strategies && strategies.length > 0) { + // Find the most recent strategy + const latestStrategy = strategies[0]; // Assuming strategies are sorted by date + + // For now, we'll assume strategies are active if they exist + // In a real implementation, you would check a status field from the database + setStrategyStatus('active'); + setShowOnboarding(false); + } else { + setStrategyStatus('none'); + setShowOnboarding(true); + } + setHasCheckedStrategy(true); + }; + const loadInitialData = async () => { try { - setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true, keywordResearch: true, pillars: true }); + setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true }); // Load strategies await loadStrategies(); @@ -136,16 +137,10 @@ const ContentStrategyTab: React.FC = () => { // Load strategic intelligence await loadStrategicIntelligence(); - // Load keyword research - await loadKeywordResearch(); - - // Load content pillars - await loadContentPillars(); - } catch (error) { console.error('Error loading initial data:', error); } finally { - setDataLoading({ strategies: false, insights: false, recommendations: false, strategicIntelligence: false, keywordResearch: false, pillars: false }); + setDataLoading({ strategies: false, insights: false, recommendations: false, strategicIntelligence: false }); } }; @@ -236,156 +231,6 @@ const ContentStrategyTab: React.FC = () => { } }; - const loadKeywordResearch = async () => { - try { - setDataLoading(prev => ({ ...prev, keywordResearch: true })); - - // Use streaming endpoint for real-time updates - const eventSource = await contentPlanningApi.streamKeywordResearch(1); - - contentPlanningApi.handleSSEData( - eventSource, - (data) => { - console.log('Keyword Research SSE Data:', data); - - if (data.type === 'status') { - // Update loading message - console.log('Status:', data.message); - } else if (data.type === 'progress') { - // Update progress (could be used for progress bar) - console.log('Progress:', data.progress, '%'); - } else if (data.type === 'result' && data.status === 'success') { - // Set the keyword research data - setKeywordResearch(data.data); - setDataLoading(prev => ({ ...prev, keywordResearch: false })); - } else if (data.type === 'error') { - console.error('Keyword Research Error:', data.message); - // Set fallback data on error - const keywordData = { - trend_analysis: { - high_volume_keywords: [ - { keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' }, - { keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' }, - { keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' } - ], - trending_keywords: [ - { keyword: 'AI content generation', growth: '+45%', opportunity: 'High' }, - { keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' }, - { keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' } - ] - }, - intent_analysis: { - informational: ['how to', 'what is', 'guide to'], - navigational: ['company name', 'brand name', 'website'], - transactional: ['buy', 'purchase', 'download', 'sign up'] - }, - opportunities: [ - { keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' }, - { keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' }, - { keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' } - ] - }; - setKeywordResearch(keywordData); - setDataLoading(prev => ({ ...prev, keywordResearch: false })); - } - }, - (error) => { - console.error('Keyword Research SSE Error:', error); - // Set fallback data on error - const keywordData = { - trend_analysis: { - high_volume_keywords: [ - { keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' }, - { keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' }, - { keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' } - ], - trending_keywords: [ - { keyword: 'AI content generation', growth: '+45%', opportunity: 'High' }, - { keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' }, - { keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' } - ] - }, - intent_analysis: { - informational: ['how to', 'what is', 'guide to'], - navigational: ['company name', 'brand name', 'website'], - transactional: ['buy', 'purchase', 'download', 'sign up'] - }, - opportunities: [ - { keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' }, - { keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' }, - { keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' } - ] - }; - setKeywordResearch(keywordData); - setDataLoading(prev => ({ ...prev, keywordResearch: false })); - } - ); - - } catch (error) { - console.error('Error loading keyword research:', error); - // Set fallback data on error - const keywordData = { - trend_analysis: { - high_volume_keywords: [ - { keyword: 'AI marketing automation', volume: '10K-100K', difficulty: 'Medium' }, - { keyword: 'content strategy 2024', volume: '1K-10K', difficulty: 'Low' }, - { keyword: 'digital marketing trends', volume: '10K-100K', difficulty: 'High' } - ], - trending_keywords: [ - { keyword: 'AI content generation', growth: '+45%', opportunity: 'High' }, - { keyword: 'voice search optimization', growth: '+32%', opportunity: 'Medium' }, - { keyword: 'video marketing strategy', growth: '+28%', opportunity: 'High' } - ] - }, - intent_analysis: { - informational: ['how to', 'what is', 'guide to'], - navigational: ['company name', 'brand name', 'website'], - transactional: ['buy', 'purchase', 'download', 'sign up'] - }, - opportunities: [ - { keyword: 'AI content tools', search_volume: '5K-10K', competition: 'Low', cpc: '$2.50' }, - { keyword: 'content marketing ROI', search_volume: '1K-5K', competition: 'Medium', cpc: '$4.20' }, - { keyword: 'social media strategy', search_volume: '10K-50K', competition: 'High', cpc: '$3.80' } - ] - }; - setKeywordResearch(keywordData); - setDataLoading(prev => ({ ...prev, keywordResearch: false })); - } - }; - - const loadContentPillars = async () => { - try { - setDataLoading(prev => ({ ...prev, pillars: true })); - - // Get content pillars from current strategy - if (currentStrategy && currentStrategy.content_pillars) { - const pillars = currentStrategy.content_pillars.map((pillar: any, index: number) => ({ - name: pillar.name || `Pillar ${index + 1}`, - content_count: pillar.content_count || Math.floor(Math.random() * 20) + 5, - avg_engagement: pillar.avg_engagement || (Math.random() * 30 + 60).toFixed(1), - performance_score: pillar.performance_score || (Math.random() * 20 + 75).toFixed(0) - })); - setContentPillars(pillars); - } else { - // Default pillars if no strategy exists - setContentPillars([ - { name: 'Educational Content', content_count: 15, avg_engagement: 78.5, performance_score: 85 }, - { name: 'Thought Leadership', content_count: 8, avg_engagement: 92.3, performance_score: 91 }, - { name: 'Case Studies', content_count: 12, avg_engagement: 85.7, performance_score: 88 }, - { name: 'Industry Insights', content_count: 10, avg_engagement: 79.2, performance_score: 82 } - ]); - } - } catch (error) { - console.error('Error loading content pillars:', error); - } finally { - setDataLoading(prev => ({ ...prev, pillars: false })); - } - }; - - const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { - setTabValue(newValue); - }; - const handleStrategyFormChange = (field: string, value: string) => { setStrategyForm(prev => ({ ...prev, @@ -428,6 +273,38 @@ const ContentStrategyTab: React.FC = () => { await loadInitialData(); }; + // Onboarding dialog handlers + const handleConfirmStrategy = async () => { + try { + if (currentStrategy) { + // For now, we'll just close the dialog since we can't update status + // In a real implementation, you would update the strategy status in the database + setShowOnboarding(false); + + // Reload strategies to get updated data + await loadStrategies(); + } + } catch (error) { + console.error('Error activating strategy:', error); + } + }; + + const handleEditStrategy = () => { + setShowOnboarding(false); + // Navigate to Create tab to edit strategy + // This would typically involve changing the active tab in the parent component + }; + + const handleCreateNewStrategy = () => { + setShowOnboarding(false); + // Navigate to Create tab to create new strategy + // This would typically involve changing the active tab in the parent component + }; + + const handleCloseOnboarding = () => { + setShowOnboarding(false); + }; + return ( {error && ( @@ -436,260 +313,72 @@ const ContentStrategyTab: React.FC = () => { )} - {/* Strategy Builder Tabs */} + {/* Strategy Status Banner */} + {strategyStatus === 'inactive' && ( + setShowOnboarding(true)} + startIcon={} + > + Activate Strategy + + } + > + + Strategy Pending Activation: Your content strategy is ready but needs to be activated to start your AI-powered content marketing journey. + + + )} + + {strategyStatus === 'none' && ( + setShowOnboarding(true)} + startIcon={} + > + Create Strategy + + } + > + + No Strategy Found: Let's create your first AI-powered content strategy to start your digital marketing journey. + + + )} + + {strategyStatus === 'active' && ( + + + Strategy Active: Your content strategy is running and ALwrity is managing your content marketing automatically. + + + )} + + {/* Strategic Intelligence */} - - - - - Enhanced Strategy Builder - - } - /> - } /> - } /> - } /> - } /> - - - - {/* Enhanced Strategy Builder Tab */} - - - - - {/* Strategic Intelligence Tab */} - - - - - {/* Keyword Research Tab */} - - {dataLoading.keywordResearch ? ( - - - - ) : keywordResearch && keywordResearch.trend_analysis ? ( - - - - - - High Volume Keywords - - - - - - Keyword - Volume - Difficulty - - - - {(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => ( - - {keyword.keyword} - {keyword.volume} - - - - - ))} - -
-
-
-
-
- - - - - - Trending Keywords - - {(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => ( - - - {keyword.keyword} - - - - - - - ))} - - - - - - - - - Keyword Opportunities - - - - - - Keyword - Search Volume - Competition - CPC - Action - - - - {(keywordResearch.opportunities || []).map((opportunity: any, index: number) => ( - - {opportunity.keyword} - {opportunity.search_volume} - - - - ${opportunity.cpc} - - - - - ))} - -
-
-
-
-
-
- ) : ( - - No keyword research data available - - )} -
- - {/* Performance Analytics Tab */} - - {performanceMetrics ? ( - - - - - - Content Performance by Type - - - No content performance data available - - - - - - - - - - Growth Trends - - - No trend data available - - - - - - ) : ( - - No performance analytics data available - - )} - - - {/* Content Pillars Tab */} - - {dataLoading.pillars ? ( - - - - ) : contentPillars.length > 0 ? ( - - - - Content Pillars Overview - - - Your content is organized into these strategic pillars to ensure comprehensive coverage of your topics. - - - - {contentPillars.map((pillar, index) => ( - - - - - {pillar.name} - - - - Content Count - - - {pillar.content_count} - - - - - Avg. Engagement - - - {pillar.avg_engagement}% - - - - - Performance Score - - - {pillar.performance_score}/100 - - - - - - - - - - ))} - - ) : ( - - No content pillars data available - - )} - + + + {/* Strategy Onboarding Dialog */} +
); }; diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx new file mode 100644 index 00000000..0c31e0c5 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx @@ -0,0 +1,113 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Tabs, + Tab, + Typography +} from '@mui/material'; +import { + AutoAwesome as AutoAwesomeIcon, + CalendarToday as CalendarIcon +} from '@mui/icons-material'; +import ContentStrategyBuilder from '../components/ContentStrategyBuilder'; +import CalendarGenerationWizard from '../components/CalendarGenerationWizard'; +import { contentPlanningApi } from '../../../services/contentPlanningApi'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} + +const CreateTab: React.FC = () => { + const [tabValue, setTabValue] = useState(0); + const [userData, setUserData] = useState({}); + + useEffect(() => { + loadUserData(); + }, []); + + const loadUserData = async () => { + try { + // Load comprehensive user data for calendar generation + const comprehensiveData = await contentPlanningApi.getComprehensiveUserData(1); // Pass user ID + setUserData(comprehensiveData.data); // Extract the data from the response + } catch (error) { + console.error('Error loading user data:', error); + } + }; + + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); + }; + + const handleGenerateCalendar = async (calendarConfig: any) => { + try { + await contentPlanningApi.generateComprehensiveCalendar({ + ...calendarConfig, + userData + }); + } catch (error) { + console.error('Error generating calendar:', error); + } + }; + + return ( + + + Create + + + + + + + Enhanced Strategy Builder + + } + /> + + + Calendar Wizard + + } + /> + +
+ + + + + + + + + + ); +}; + +export default CreateTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/GapAnalysisTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/GapAnalysisTab.tsx index c01079f4..d0abec68 100644 --- a/frontend/src/components/ContentPlanningDashboard/tabs/GapAnalysisTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/tabs/GapAnalysisTab.tsx @@ -1,404 +1,95 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState } from 'react'; import { Box, - Grid, - Paper, + Tabs, + Tab, Typography, - Button, - TextField, - Card, - CardContent, - Chip, - Divider, - Alert, - CircularProgress, - List, - ListItem, - ListItemText, - ListItemIcon + Alert } from '@mui/material'; import { + Analytics as AnalyticsIcon, + TrendingUp as TrendingIcon, Search as SearchIcon, - Add as AddIcon, - Warning as WarningIcon, - CheckCircle as CheckCircleIcon, - TrendingUp as TrendingUpIcon, - Assessment as AssessmentIcon + Assessment as AssessmentIcon, + BarChart as BarChartIcon, + PieChart as PieChartIcon } from '@mui/icons-material'; -import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; -import { contentPlanningApi } from '../../../services/contentPlanningApi'; +import RefineAnalysisTab from './RefineAnalysisTab'; +import ContentOptimizerTab from './ContentOptimizerTab'; +import TrendingTopicsTab from './TrendingTopicsTab'; +import KeywordResearchTab from './KeywordResearchTab'; +import PerformanceAnalyticsTab from './PerformanceAnalyticsTab'; +import ContentPillarsTab from './ContentPillarsTab'; + +interface TabPanelProps { + children?: React.ReactNode; + index: number; + value: number; +} + +function TabPanel(props: TabPanelProps) { + const { children, value, index, ...other } = props; + + return ( + + ); +} const GapAnalysisTab: React.FC = () => { - const { - gapAnalyses, - loading, - error, - loadGapAnalyses, - analyzeContentGaps, - updateGapAnalyses - } = useContentPlanningStore(); - - const [analysisForm, setAnalysisForm] = useState({ - website_url: '', - competitors: [] as string[], - keywords: [] as string[] - }); - const [newCompetitor, setNewCompetitor] = useState(''); - const [newKeyword, setNewKeyword] = useState(''); - const [dataLoading, setDataLoading] = useState(false); + const [tabValue, setTabValue] = useState(0); - useEffect(() => { - loadGapAnalysisData(); - }, []); - - const loadGapAnalysisData = async () => { - try { - setDataLoading(true); - const response = await contentPlanningApi.getGapAnalysesSafe(); - - console.log('Gap Analysis Response:', response); - - // Transform the backend response to match frontend expectations - if (response && response.gap_analyses) { - const transformedAnalyses = response.gap_analyses.map((analysis: any, index: number) => ({ - id: analysis.id || `analysis_${index}`, - website_url: analysis.website_url || 'example.com', - competitors: analysis.competitors || [], - keywords: analysis.keywords || [], - gaps: analysis.gaps || [], - recommendations: analysis.recommendations || [], - created_at: analysis.created_at || new Date().toISOString() - })); - - console.log('Transformed Analyses:', transformedAnalyses); - - // Update the store with transformed data - updateGapAnalyses(transformedAnalyses); - } else { - console.log('No gap analyses found in response'); - updateGapAnalyses([]); - } - } catch (error) { - console.error('Error loading gap analysis data:', error); - updateGapAnalyses([]); - } finally { - setDataLoading(false); - } + const handleTabChange = (event: React.SyntheticEvent, newValue: number) => { + setTabValue(newValue); }; - const handleAddCompetitor = () => { - if (newCompetitor.trim() && !analysisForm.competitors.includes(newCompetitor.trim())) { - setAnalysisForm(prev => ({ - ...prev, - competitors: [...prev.competitors, newCompetitor.trim()] - })); - setNewCompetitor(''); - } - }; - - const handleRemoveCompetitor = (competitorToRemove: string) => { - setAnalysisForm(prev => ({ - ...prev, - competitors: prev.competitors.filter(comp => comp !== competitorToRemove) - })); - }; - - const handleAddKeyword = () => { - if (newKeyword.trim() && !analysisForm.keywords.includes(newKeyword.trim())) { - setAnalysisForm(prev => ({ - ...prev, - keywords: [...prev.keywords, newKeyword.trim()] - })); - setNewKeyword(''); - } - }; - - const handleRemoveKeyword = (keywordToRemove: string) => { - setAnalysisForm(prev => ({ - ...prev, - keywords: prev.keywords.filter(keyword => keyword !== keywordToRemove) - })); - }; - - const handleRunAnalysis = async () => { - if (!analysisForm.website_url) { - return; - } - - try { - setDataLoading(true); - - await analyzeContentGaps({ - website_url: analysisForm.website_url, - competitors: analysisForm.competitors, - keywords: analysisForm.keywords - }); - - // Reload data after analysis - await loadGapAnalyses(); - - // Reset form - setAnalysisForm({ - website_url: '', - competitors: [], - keywords: [] - }); - } catch (error) { - console.error('Error running gap analysis:', error); - } finally { - setDataLoading(false); - } - }; - - // Ensure gapAnalyses is always an array and transform the data structure - const safeGapAnalyses = Array.isArray(gapAnalyses) ? gapAnalyses : []; - - // Transform backend data structure to frontend expected structure - const transformedGapAnalyses = safeGapAnalyses.map((analysis, index) => { - // Handle the actual backend structure: { recommendations: [...] } - const recommendations = analysis.recommendations || []; - - return { - id: analysis.id || `analysis-${index}`, - website_url: analysis.website_url || 'Unknown Website', - competitors: analysis.competitors || [], - keywords: analysis.keywords || [], - recommendations: recommendations, - created_at: analysis.created_at || new Date().toISOString(), - // Extract gaps from recommendations if available - gaps: recommendations.length > 0 ? - recommendations.filter((rec: any) => rec.type === 'gap').map((rec: any) => rec.title || rec.description || 'Content gap identified') : - [] - }; - }); - return ( - Content Gap Analysis + Gap Analysis & Optimization - {error && ( - - {error} - - )} + + + } iconPosition="start" /> + } iconPosition="start" /> + } iconPosition="start" /> + } iconPosition="start" /> + } iconPosition="start" /> + } iconPosition="start" /> + + - - {/* Analysis Setup */} - - - - - Analysis Setup - - - - setAnalysisForm(prev => ({ ...prev, website_url: e.target.value }))} - placeholder="https://example.com" - sx={{ mb: 2 }} - /> - - - Competitors - - - setNewCompetitor(e.target.value)} - placeholder="competitor.com" - onKeyPress={(e) => e.key === 'Enter' && handleAddCompetitor()} - /> - - - - - {analysisForm.competitors.map((competitor, index) => ( - handleRemoveCompetitor(competitor)} - color="primary" - variant="outlined" - /> - ))} - - - - Keywords - - - setNewKeyword(e.target.value)} - placeholder="target keyword" - onKeyPress={(e) => e.key === 'Enter' && handleAddKeyword()} - /> - - - - - {analysisForm.keywords.map((keyword, index) => ( - handleRemoveKeyword(keyword)} - color="secondary" - variant="outlined" - /> - ))} - - - - - + + + - {/* Content Gaps */} - - - - - Content Gaps - - - - {dataLoading ? ( - - - - ) : transformedGapAnalyses.length === 0 ? ( - - No previous analyses found. Run your first analysis to see results here. - - ) : ( - - {transformedGapAnalyses.map((analysis) => ( - - - - - {analysis.website_url} - - - {new Date(analysis.created_at).toLocaleDateString()} - - - - - - - - - - - ))} - - )} - + + + - {/* Detailed Analysis Results */} - {transformedGapAnalyses.length > 0 && ( - - - - Detailed Analysis Results - - - - {transformedGapAnalyses.map((analysis, index) => ( - - - Analysis for {analysis.website_url} - - - {analysis.gaps && analysis.gaps.length > 0 && ( - - - Identified Content Gaps: - - - {analysis.gaps.map((gap, gapIndex) => ( - - - - - - - ))} - - - )} - - {analysis.recommendations && analysis.recommendations.length > 0 && ( - - - Recommendations: - - - {analysis.recommendations.map((rec, recIndex) => ( - - - - - - - ))} - - - )} - - ))} - - )} - - + + + + + + + + + + + + + + + ); }; diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/KeywordResearchTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/KeywordResearchTab.tsx new file mode 100644 index 00000000..6a4b1265 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/KeywordResearchTab.tsx @@ -0,0 +1,189 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Grid, + Paper, + Typography, + Card, + CardContent, + Chip, + CircularProgress, + Table, + TableBody, + TableCell, + TableContainer, + TableHead, + TableRow, + Button +} from '@mui/material'; +import { + Search as SearchIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; +import { contentPlanningApi } from '../../../services/contentPlanningApi'; + +const KeywordResearchTab: React.FC = () => { + const [keywordResearch, setKeywordResearch] = useState(null); + const [dataLoading, setDataLoading] = useState(false); + + useEffect(() => { + loadKeywordResearch(); + }, []); + + const loadKeywordResearch = async () => { + try { + setDataLoading(true); + const eventSource = await contentPlanningApi.streamKeywordResearch(); + + contentPlanningApi.handleSSEData( + eventSource, + (data) => { + console.log('Keyword Research SSE Data:', data); + if (data.type === 'result' && data.data) { + setKeywordResearch(data.data); + } + }, + (error) => { + console.error('Keyword Research SSE Error:', error); + }, + () => { + setDataLoading(false); + } + ); + } catch (error) { + console.error('Error loading keyword research:', error); + setDataLoading(false); + } + }; + + return ( + + + Keyword Research + + + {dataLoading ? ( + + + + ) : keywordResearch && keywordResearch.trend_analysis ? ( + + + + + + High Volume Keywords + + + + + + Keyword + Volume + Difficulty + + + + {(keywordResearch.trend_analysis.high_volume_keywords || []).map((keyword: any, index: number) => ( + + {keyword.keyword} + {keyword.volume} + + + + + ))} + +
+
+
+
+
+ + + + + + Trending Keywords + + {(keywordResearch.trend_analysis.trending_keywords || []).map((keyword: any, index: number) => ( + + + {keyword.keyword} + + + + + + + ))} + + + + + + + + + Keyword Opportunities + + + + + + Keyword + Search Volume + Competition + CPC + Action + + + + {(keywordResearch.opportunities || []).map((opportunity: any, index: number) => ( + + {opportunity.keyword} + {opportunity.search_volume} + + + + ${opportunity.cpc} + + + + + ))} + +
+
+
+
+
+
+ ) : ( + + No keyword research data available + + )} +
+ ); +}; + +export default KeywordResearchTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/PerformanceAnalyticsTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/PerformanceAnalyticsTab.tsx new file mode 100644 index 00000000..f77b11f4 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/PerformanceAnalyticsTab.tsx @@ -0,0 +1,60 @@ +import React from 'react'; +import { + Box, + Grid, + Card, + CardContent, + Typography +} from '@mui/material'; +import { + BarChart as BarChartIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; + +const PerformanceAnalyticsTab: React.FC = () => { + const { performanceMetrics } = useContentPlanningStore(); + + return ( + + + Performance Analytics + + + {performanceMetrics ? ( + + + + + + Content Performance by Type + + + No content performance data available + + + + + + + + + + Growth Trends + + + No trend data available + + + + + + ) : ( + + No performance analytics data available + + )} + + ); +}; + +export default PerformanceAnalyticsTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/RefineAnalysisTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/RefineAnalysisTab.tsx new file mode 100644 index 00000000..a796a987 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/RefineAnalysisTab.tsx @@ -0,0 +1,406 @@ +import React, { useState, useEffect } from 'react'; +import { + Box, + Grid, + Paper, + Typography, + Button, + TextField, + Card, + CardContent, + Chip, + Divider, + Alert, + CircularProgress, + List, + ListItem, + ListItemText, + ListItemIcon +} from '@mui/material'; +import { + Search as SearchIcon, + Add as AddIcon, + Warning as WarningIcon, + CheckCircle as CheckCircleIcon, + TrendingUp as TrendingUpIcon, + Assessment as AssessmentIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; +import { contentPlanningApi } from '../../../services/contentPlanningApi'; + +const RefineAnalysisTab: React.FC = () => { + const { + gapAnalyses, + loading, + error, + loadGapAnalyses, + analyzeContentGaps, + updateGapAnalyses + } = useContentPlanningStore(); + + const [analysisForm, setAnalysisForm] = useState({ + website_url: '', + competitors: [] as string[], + keywords: [] as string[] + }); + const [newCompetitor, setNewCompetitor] = useState(''); + const [newKeyword, setNewKeyword] = useState(''); + const [dataLoading, setDataLoading] = useState(false); + + useEffect(() => { + loadGapAnalysisData(); + }, []); + + const loadGapAnalysisData = async () => { + try { + setDataLoading(true); + const response = await contentPlanningApi.getGapAnalysesSafe(); + + console.log('Gap Analysis Response:', response); + + // Transform the backend response to match frontend expectations + if (response && response.gap_analyses) { + const transformedAnalyses = response.gap_analyses.map((analysis: any, index: number) => ({ + id: analysis.id || `analysis_${index}`, + website_url: analysis.website_url || 'example.com', + competitors: analysis.competitors || [], + keywords: analysis.keywords || [], + gaps: analysis.gaps || [], + recommendations: analysis.recommendations || [], + created_at: analysis.created_at || new Date().toISOString() + })); + + console.log('Transformed Analyses:', transformedAnalyses); + + // Update the store with transformed data + updateGapAnalyses(transformedAnalyses); + } else { + console.log('No gap analyses found in response'); + updateGapAnalyses([]); + } + } catch (error) { + console.error('Error loading gap analysis data:', error); + updateGapAnalyses([]); + } finally { + setDataLoading(false); + } + }; + + const handleAddCompetitor = () => { + if (newCompetitor.trim() && !analysisForm.competitors.includes(newCompetitor.trim())) { + setAnalysisForm(prev => ({ + ...prev, + competitors: [...prev.competitors, newCompetitor.trim()] + })); + setNewCompetitor(''); + } + }; + + const handleRemoveCompetitor = (competitorToRemove: string) => { + setAnalysisForm(prev => ({ + ...prev, + competitors: prev.competitors.filter(comp => comp !== competitorToRemove) + })); + }; + + const handleAddKeyword = () => { + if (newKeyword.trim() && !analysisForm.keywords.includes(newKeyword.trim())) { + setAnalysisForm(prev => ({ + ...prev, + keywords: [...prev.keywords, newKeyword.trim()] + })); + setNewKeyword(''); + } + }; + + const handleRemoveKeyword = (keywordToRemove: string) => { + setAnalysisForm(prev => ({ + ...prev, + keywords: prev.keywords.filter(keyword => keyword !== keywordToRemove) + })); + }; + + const handleRunAnalysis = async () => { + if (!analysisForm.website_url) { + return; + } + + try { + setDataLoading(true); + + await analyzeContentGaps({ + website_url: analysisForm.website_url, + competitors: analysisForm.competitors, + keywords: analysisForm.keywords + }); + + // Reload data after analysis + await loadGapAnalyses(); + + // Reset form + setAnalysisForm({ + website_url: '', + competitors: [], + keywords: [] + }); + } catch (error) { + console.error('Error running gap analysis:', error); + } finally { + setDataLoading(false); + } + }; + + // Ensure gapAnalyses is always an array and transform the data structure + const safeGapAnalyses = Array.isArray(gapAnalyses) ? gapAnalyses : []; + + // Transform backend data structure to frontend expected structure + const transformedGapAnalyses = safeGapAnalyses.map((analysis, index) => { + // Handle the actual backend structure: { recommendations: [...] } + const recommendations = analysis.recommendations || []; + + return { + id: analysis.id || `analysis-${index}`, + website_url: analysis.website_url || 'Unknown Website', + competitors: analysis.competitors || [], + keywords: analysis.keywords || [], + recommendations: recommendations, + created_at: analysis.created_at || new Date().toISOString(), + // Extract gaps from recommendations if available + gaps: recommendations.length > 0 ? + recommendations.filter((rec: any) => rec.type === 'gap').map((rec: any) => rec.title || rec.description || 'Content gap identified') : + [] + }; + }); + + return ( + + + Refine Analysis + + + {error && ( + + {error} + + )} + + + {/* Analysis Setup */} + + + + + Analysis Setup + + + + setAnalysisForm(prev => ({ ...prev, website_url: e.target.value }))} + placeholder="https://example.com" + sx={{ mb: 2 }} + /> + + + Competitors + + + setNewCompetitor(e.target.value)} + placeholder="competitor.com" + onKeyPress={(e) => e.key === 'Enter' && handleAddCompetitor()} + /> + + + + + {analysisForm.competitors.map((competitor, index) => ( + handleRemoveCompetitor(competitor)} + color="primary" + variant="outlined" + /> + ))} + + + + Keywords + + + setNewKeyword(e.target.value)} + placeholder="target keyword" + onKeyPress={(e) => e.key === 'Enter' && handleAddKeyword()} + /> + + + + + {analysisForm.keywords.map((keyword, index) => ( + handleRemoveKeyword(keyword)} + color="secondary" + variant="outlined" + /> + ))} + + + + + + + {/* Content Gaps */} + + + + + Content Gaps + + + + {dataLoading ? ( + + + + ) : transformedGapAnalyses.length === 0 ? ( + + No previous analyses found. Run your first analysis to see results here. + + ) : ( + + {transformedGapAnalyses.map((analysis) => ( + + + + + {analysis.website_url} + + + {new Date(analysis.created_at).toLocaleDateString()} + + + + + + + + + + + ))} + + )} + + + {/* Detailed Analysis Results */} + {transformedGapAnalyses.length > 0 && ( + + + + Detailed Analysis Results + + + + {transformedGapAnalyses.map((analysis, index) => ( + + + Analysis for {analysis.website_url} + + + {analysis.gaps && analysis.gaps.length > 0 && ( + + + Identified Content Gaps: + + + {analysis.gaps.map((gap, gapIndex) => ( + + + + + + + ))} + + + )} + + {analysis.recommendations && analysis.recommendations.length > 0 && ( + + + Recommendations: + + + {analysis.recommendations.map((rec, recIndex) => ( + + + + + + + ))} + + + )} + + ))} + + )} + + + + ); +}; + +export default RefineAnalysisTab; \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/TrendingTopicsTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/TrendingTopicsTab.tsx new file mode 100644 index 00000000..7131261f --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/tabs/TrendingTopicsTab.tsx @@ -0,0 +1,65 @@ +import React from 'react'; +import { + Box, + Grid, + Paper, + Typography, + Chip +} from '@mui/material'; +import { + TrendingUp as TrendingIcon +} from '@mui/icons-material'; +import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; + +const TrendingTopicsTab: React.FC = () => { + const { trendingTopics } = useContentPlanningStore(); + + return ( + + + Trending Topics + + + + + + + + Trending Topics + + + {trendingTopics ? ( + + + Current Trending Topics + + + {trendingTopics.trending_topics?.map((topic: any, index: number) => ( + + ))} + + + ) : ( + + + + No trending topics + + + Get trending topics for your industry + + + )} + + + + + ); +}; + +export default TrendingTopicsTab; \ No newline at end of file