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 6178d124..75c2c835 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 @@ -662,6 +662,16 @@ async def get_latest_generated_strategy( logger.info(f"๐Ÿ” Querying database for strategies with user_id: {user_id}") # Query for the most recent strategy with comprehensive AI analysis + # First, let's see all strategies for this user + all_strategies = db.query(EnhancedContentStrategy).filter( + EnhancedContentStrategy.user_id == user_id + ).order_by(desc(EnhancedContentStrategy.created_at)).all() + + logger.info(f"๐Ÿ” Found {len(all_strategies)} total strategies for user {user_id}") + for i, strategy in enumerate(all_strategies): + logger.info(f" Strategy {i+1}: ID={strategy.id}, name={strategy.name}, created_at={strategy.created_at}, has_comprehensive_ai_analysis={strategy.comprehensive_ai_analysis is not None}") + + # Now query for the most recent strategy with comprehensive AI analysis latest_db_strategy = db.query(EnhancedContentStrategy).filter( EnhancedContentStrategy.user_id == user_id, EnhancedContentStrategy.comprehensive_ai_analysis.isnot(None) @@ -682,9 +692,31 @@ async def get_latest_generated_strategy( } ) else: - logger.info(f"โš ๏ธ No strategy found in database for user: {user_id}") - if latest_db_strategy: - logger.info(f"๐Ÿ” Strategy found but no comprehensive_ai_analysis: {latest_db_strategy.id}") + logger.info(f"โš ๏ธ No strategy with comprehensive_ai_analysis found in database for user: {user_id}") + + # Fallback: Try to get the most recent strategy regardless of comprehensive_ai_analysis + fallback_strategy = db.query(EnhancedContentStrategy).filter( + EnhancedContentStrategy.user_id == user_id + ).order_by(desc(EnhancedContentStrategy.created_at)).first() + + if fallback_strategy: + logger.info(f"๐Ÿ” Found fallback strategy: ID={fallback_strategy.id}, name={fallback_strategy.name}") + logger.info(f"๐Ÿ” Fallback strategy has ai_recommendations: {fallback_strategy.ai_recommendations is not None}") + + # Try to use ai_recommendations as the strategy data + if fallback_strategy.ai_recommendations: + logger.info(f"โœ… Using ai_recommendations as strategy data for fallback strategy {fallback_strategy.id}") + return ResponseBuilder.create_success_response( + message="Latest generated strategy retrieved successfully from database (fallback)", + data={ + "user_id": user_id, + "strategy": fallback_strategy.ai_recommendations, + "completed_at": fallback_strategy.created_at.isoformat(), + "strategy_id": fallback_strategy.id + } + ) + else: + logger.info(f"โš ๏ธ Fallback strategy has no ai_recommendations either") else: logger.info(f"๐Ÿ” No strategy record found at all for user: {user_id}") except Exception as db_error: diff --git a/backend/api/content_planning/api/enhanced_strategy_routes.py b/backend/api/content_planning/api/enhanced_strategy_routes.py index 41ad4c41..61e405bf 100644 --- a/backend/api/content_planning/api/enhanced_strategy_routes.py +++ b/backend/api/content_planning/api/enhanced_strategy_routes.py @@ -1141,7 +1141,7 @@ async def stream_autofill_refresh( async def refresh_autofill( user_id: Optional[int] = Query(None, description="User ID to build auto-fill for"), use_ai: bool = Query(True, description="Use AI augmentation during refresh"), - ai_only: bool = Query(False, description="AI-first refresh: return AI overrides when available"), + ai_only: bool = Query(True, description="๐Ÿšจ CRITICAL: Force AI-only generation to ensure real AI values"), db: Session = Depends(get_db) ) -> Dict[str, Any]: """Non-stream endpoint to return a fresh auto-fill payload (no DB writes).""" @@ -1149,7 +1149,8 @@ async def refresh_autofill( actual_user_id = user_id or 1 started = datetime.utcnow() refresh_service = AutoFillRefreshService(db) - payload = await refresh_service.build_fresh_payload_with_transparency(actual_user_id, use_ai=use_ai, ai_only=ai_only) + # ๐Ÿšจ CRITICAL: Force AI-only generation for refresh to ensure real AI values + payload = await refresh_service.build_fresh_payload_with_transparency(actual_user_id, use_ai=True, ai_only=True) total_ms = int((datetime.utcnow() - started).total_seconds() * 1000) meta = payload.get('meta') or {} meta.update({'http_total_ms': total_ms, 'http_started_at': started.isoformat()}) diff --git a/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py b/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py index e415fa7e..69a072ab 100644 --- a/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py +++ b/backend/api/content_planning/services/content_strategy/ai_generation/strategy_generator.py @@ -68,23 +68,36 @@ class AIStrategyGenerator: try: self.logger.info(f"๐Ÿš€ Generating comprehensive AI strategy for user: {user_id}") + # Track which components failed during generation + failed_components = [] + # Step 1: Generate base strategy fields (using existing autofill system) base_strategy = await self._generate_base_strategy_fields(user_id, context) # Step 2: Generate strategic insights and recommendations strategic_insights = await self._generate_strategic_insights(base_strategy, context) + if strategic_insights.get("ai_generation_failed"): + failed_components.append("strategic_insights") # Step 3: Generate competitive analysis competitive_analysis = await self._generate_competitive_analysis(base_strategy, context) + if competitive_analysis.get("ai_generation_failed"): + failed_components.append("competitive_analysis") # Step 4: Generate performance predictions performance_predictions = await self._generate_performance_predictions(base_strategy, context) + if performance_predictions.get("ai_generation_failed"): + failed_components.append("performance_predictions") # Step 5: Generate implementation roadmap implementation_roadmap = await self._generate_implementation_roadmap(base_strategy, context) + if implementation_roadmap.get("ai_generation_failed"): + failed_components.append("implementation_roadmap") # Step 6: Generate risk assessment risk_assessment = await self._generate_risk_assessment(base_strategy, context) + if risk_assessment.get("ai_generation_failed"): + failed_components.append("risk_assessment") # Step 7: Compile comprehensive strategy (NO CONTENT CALENDAR) comprehensive_strategy = { @@ -97,7 +110,9 @@ class AIStrategyGenerator: "personalization_level": "high", "ai_generated": True, "comprehensive": True, - "content_calendar_ready": False # Indicates calendar needs to be generated separately + "content_calendar_ready": False, # Indicates calendar needs to be generated separately + "failed_components": failed_components, + "generation_status": "partial" if failed_components else "complete" }, "base_strategy": base_strategy, "strategic_insights": strategic_insights, @@ -114,7 +129,11 @@ class AIStrategyGenerator: } } - self.logger.info(f"โœ… Comprehensive AI strategy generated successfully for user: {user_id}") + if failed_components: + self.logger.warning(f"โš ๏ธ Strategy generated with partial AI components. Failed: {failed_components}") + self.logger.info(f"โœ… Partial AI strategy generated successfully for user: {user_id}") + else: + self.logger.info(f"โœ… Comprehensive AI strategy generated successfully for user: {user_id}") return comprehensive_strategy except Exception as e: @@ -223,8 +242,15 @@ class AIStrategyGenerator: return transformed_response except Exception as e: - logger.error(f"โŒ Error generating strategic insights: {str(e)}") - raise RuntimeError(f"Failed to generate strategic insights: {str(e)}") + logger.warning(f"โš ๏ธ AI service overload or error during strategic insights: {str(e)}") + logger.info("๐Ÿ”„ Continuing strategy generation without strategic insights...") + + # Return empty strategic insights to allow strategy generation to continue + return { + "insights": [], + "ai_generation_failed": True, + "failure_reason": str(e) + } async def _generate_competitive_analysis(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]: """Generate competitive analysis using AI.""" @@ -300,8 +326,18 @@ class AIStrategyGenerator: return transformed_response except Exception as e: - logger.error(f"โŒ Error generating competitive analysis: {str(e)}") - raise RuntimeError(f"Failed to generate competitive analysis: {str(e)}") + logger.warning(f"โš ๏ธ AI service overload or error during competitive analysis: {str(e)}") + logger.info("๐Ÿ”„ Continuing strategy generation without competitive analysis...") + + # Return empty competitive analysis to allow strategy generation to continue + return { + "competitors": [], + "market_gaps": [], + "opportunities": [], + "recommendations": [], + "ai_generation_failed": True, + "failure_reason": str(e) + } async def _generate_content_calendar(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]: """Generate content calendar using AI.""" @@ -502,8 +538,18 @@ class AIStrategyGenerator: return transformed_response except Exception as e: - logger.error(f"โŒ Error generating performance predictions: {str(e)}") - raise RuntimeError(f"Failed to generate performance predictions: {str(e)}") + logger.warning(f"โš ๏ธ AI service overload or error during performance predictions: {str(e)}") + logger.info("๐Ÿ”„ Continuing strategy generation without performance predictions...") + + # Return empty performance predictions to allow strategy generation to continue + return { + "traffic_predictions": {}, + "engagement_predictions": {}, + "conversion_predictions": {}, + "roi_predictions": {}, + "ai_generation_failed": True, + "failure_reason": str(e) + } async def _generate_implementation_roadmap(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]: """Generate implementation roadmap using AI.""" @@ -600,8 +646,19 @@ class AIStrategyGenerator: return transformed_response except Exception as e: - logger.error(f"โŒ Error generating implementation roadmap: {str(e)}") - raise RuntimeError(f"Failed to generate implementation roadmap: {str(e)}") + logger.warning(f"โš ๏ธ AI service overload or error during implementation roadmap: {str(e)}") + logger.info("๐Ÿ”„ Continuing strategy generation without implementation roadmap...") + + # Return empty implementation roadmap to allow strategy generation to continue + return { + "phases": [], + "timeline": {}, + "resource_allocation": {}, + "success_metrics": [], + "total_duration": "TBD", + "ai_generation_failed": True, + "failure_reason": str(e) + } async def _generate_risk_assessment(self, base_strategy: Dict[str, Any], context: Dict[str, Any], ai_manager: Optional[Any] = None) -> Dict[str, Any]: """Generate risk assessment using AI.""" @@ -738,8 +795,29 @@ class AIStrategyGenerator: return transformed_response except Exception as e: - logger.error(f"โŒ Error generating risk assessment: {str(e)}") - raise RuntimeError(f"Failed to generate risk assessment: {str(e)}") + logger.warning(f"โš ๏ธ AI service overload or error during risk assessment: {str(e)}") + logger.info("๐Ÿ”„ Continuing strategy generation without risk assessment...") + + # Return empty risk assessment to allow strategy generation to continue + return { + "risks": [], + "overall_risk_level": "Medium", + "risk_categories": { + "technical_risks": [], + "market_risks": [], + "operational_risks": [], + "financial_risks": [] + }, + "mitigation_strategies": [], + "monitoring_framework": { + "key_indicators": [], + "monitoring_frequency": "Monthly", + "escalation_procedures": [], + "review_schedule": "Quarterly" + }, + "ai_generation_failed": True, + "failure_reason": str(e) + } def _build_strategic_insights_prompt(self, base_strategy: Dict[str, Any], context: Dict[str, Any]) -> str: """Build prompt for strategic insights generation.""" diff --git a/backend/app.py b/backend/app.py index 79fbc71a..58c5effb 100644 --- a/backend/app.py +++ b/backend/app.py @@ -105,12 +105,17 @@ async def rate_limit_middleware(request: Request, call_next): client_ip = request.client.host if request.client else "unknown" current_time = time.time() - # Exempt streaming endpoints from rate limiting + # Exempt streaming endpoints and frequently called endpoints from rate limiting path = request.url.path if any(streaming_path in path for streaming_path in [ "/stream/strategies", "/stream/strategic-intelligence", - "/stream/keyword-research" + "/stream/keyword-research", + "/latest-strategy", # Exempt latest strategy endpoint from rate limiting + "/ai-analytics", # Exempt AI analytics endpoint from rate limiting + "/gap-analysis", # Exempt gap analysis endpoint from rate limiting + "/calendar-events", # Exempt calendar events endpoint from rate limiting + "/health" # Exempt health check endpoints from rate limiting ]): # Allow streaming endpoints without rate limiting response = await call_next(request) @@ -125,7 +130,12 @@ async def rate_limit_middleware(request: Request, call_next): logger.warning(f"Rate limit exceeded for {client_ip}") return JSONResponse( status_code=429, - content={"detail": "Too many requests", "retry_after": RATE_LIMIT_WINDOW} + content={"detail": "Too many requests", "retry_after": RATE_LIMIT_WINDOW}, + headers={ + "Access-Control-Allow-Origin": "*", + "Access-Control-Allow-Methods": "*", + "Access-Control-Allow-Headers": "*" + } ) # Add current request diff --git a/frontend/build/asset-manifest.json b/frontend/build/asset-manifest.json deleted file mode 100644 index 8412847b..00000000 --- a/frontend/build/asset-manifest.json +++ /dev/null @@ -1,13 +0,0 @@ -{ - "files": { - "main.css": "/static/css/main.c9966057.css", - "main.js": "/static/js/main.bdc25f29.js", - "index.html": "/index.html", - "main.c9966057.css.map": "/static/css/main.c9966057.css.map", - "main.bdc25f29.js.map": "/static/js/main.bdc25f29.js.map" - }, - "entrypoints": [ - "static/css/main.c9966057.css", - "static/js/main.bdc25f29.js" - ] -} \ No newline at end of file diff --git a/frontend/build/index.html b/frontend/build/index.html deleted file mode 100644 index f3930fed..00000000 --- a/frontend/build/index.html +++ /dev/null @@ -1 +0,0 @@ -Alwrity - AI Content Creation Platform
\ No newline at end of file diff --git a/frontend/build/static/css/main.c9966057.css b/frontend/build/static/css/main.c9966057.css deleted file mode 100644 index 766f3e46..00000000 --- a/frontend/build/static/css/main.c9966057.css +++ /dev/null @@ -1,2 +0,0 @@ -@import url(https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap);html{scroll-behavior:smooth}:focus{outline:2px solid #667eea;outline-offset:2px}@keyframes fadeIn{0%{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}@keyframes slideUp{0%{opacity:0;transform:translateY(20px)}to{opacity:1;transform:translateY(0)}}@keyframes pulse{0%,to{opacity:1}50%{opacity:.7}}@keyframes shimmer{0%{background-position:-200px 0}to{background-position:calc(200px + 100%) 0}}@keyframes float{0%,to{transform:translateY(0)}50%{transform:translateY(-10px)}}@keyframes glow{0%,to{box-shadow:0 0 5px #667eea80}50%{box-shadow:0 0 20px #667eeacc}}body{-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen,Ubuntu,Cantarell,sans-serif;font-weight:400;line-height:1.6}button{transition:all .3s cubic-bezier(.4,0,.2,1)}button:active{transform:scale(.98)}.MuiCard-root{transition:all .3s cubic-bezier(.4,0,.2,1)}.MuiCard-root:hover{transform:translateY(-2px)}.glass-effect{-webkit-backdrop-filter:blur(20px);backdrop-filter:blur(20px);background:#ffffff1a;border:1px solid #fff3;border-radius:12px}.glass-effect-hover:hover{background:#ffffff26;border:1px solid #ffffff4d;box-shadow:0 8px 32px #0000001a;transform:translateY(-4px)}.MuiTextField-root .MuiOutlinedInput-root{transition:all .3s ease}.MuiTextField-root .MuiOutlinedInput-root:hover{transform:translateY(-1px)}.MuiLinearProgress-root{border-radius:4px;overflow:hidden}.MuiLinearProgress-bar{transition:transform .3s ease}.MuiChip-root{font-weight:600;transition:all .3s ease}.MuiChip-root:hover{transform:translateY(-1px)}.MuiAlert-root{border-radius:8px;font-weight:500}.MuiTooltip-tooltip{border-radius:6px;font-weight:500}.MuiStepLabel-root{transition:all .3s ease}.MuiStepLabel-root:hover{transform:scale(1.05)}.MuiIconButton-root{transition:all .3s cubic-bezier(.4,0,.2,1)}.MuiIconButton-root:hover{transform:scale(1.1)}.MuiPaper-root{transition:all .3s cubic-bezier(.4,0,.2,1)}a{text-decoration:none;transition:all .3s ease}a:hover{text-decoration:underline}::-webkit-scrollbar{width:8px}::-webkit-scrollbar-track{background:#f1f1f1;border-radius:4px}::-webkit-scrollbar-thumb{background:#c1c1c1;border-radius:4px}::-webkit-scrollbar-thumb:hover{background:#a8a8a8}.loading-shimmer{animation:shimmer 1.5s infinite;background:linear-gradient(90deg,#f0f0f0 25%,#e0e0e0 50%,#f0f0f0 75%);background-size:200px 100%}.focus-ring{position:relative}.focus-ring:after{border:2px solid #0000;border-radius:inherit;bottom:-2px;content:"";left:-2px;position:absolute;right:-2px;top:-2px;transition:border-color .3s ease}.focus-ring:focus:after{border-color:#667eea}.gradient-bg{background:linear-gradient(135deg,#667eea,#764ba2)}.gradient-bg-secondary{background:linear-gradient(135deg,#8b5cf6,#7c3aed)}.gradient-bg-premium{background:linear-gradient(135deg,#667eea,#764ba2 50%,#f093fb)}.shadow-sm{box-shadow:0 1px 2px 0 #0000000d}.shadow-md{box-shadow:0 4px 6px -1px #0000001a,0 2px 4px -1px #0000000f}.shadow-lg{box-shadow:0 10px 15px -3px #0000001a,0 4px 6px -2px #0000000d}.shadow-xl{box-shadow:0 20px 25px -5px #0000001a,0 10px 10px -5px #0000000a}.shadow-2xl{box-shadow:0 25px 50px -12px #00000040}.glass-shadow{box-shadow:0 8px 32px #0000001a}.glass-shadow-hover:hover{box-shadow:0 16px 48px #00000026}.animate-float{animation:float 3s ease-in-out infinite}.animate-glow{animation:glow 2s ease-in-out infinite}.animate-fade-in{animation:fadeIn .6s ease-out}.animate-slide-up{animation:slideUp .6s ease-out}.hover-lift:hover{transform:translateY(-4px)}.hover-lift:hover,.hover-scale:hover{transition:transform .3s cubic-bezier(.4,0,.2,1)}.hover-scale:hover{transform:scale(1.05)}.hover-glow:hover{box-shadow:0 0 20px #667eea4d;transition:box-shadow .3s ease}.text-gradient{-webkit-text-fill-color:#0000;background:linear-gradient(135deg,#667eea,#764ba2);-webkit-background-clip:text;background-clip:text}.text-shadow{text-shadow:0 2px 4px #0000004d}@media (max-width:768px){.mobile-hidden{display:none}.mobile-full{width:100%!important}.mobile-text-center{text-align:center}}@media (max-width:480px){.mobile-padding{padding:16px}}@media print{.no-print{display:none!important}}@media (prefers-color-scheme:dark){.auto-dark{background:#1a1a1a;color:#fff}}@media (prefers-contrast:high){.high-contrast{border:2px solid}}@media (prefers-reduced-motion:reduce){*{animation-duration:.01ms!important;animation-iteration-count:1!important;transition-duration:.01ms!important}} -/*# sourceMappingURL=main.c9966057.css.map*/ \ No newline at end of file diff --git a/frontend/build/static/css/main.c9966057.css.map b/frontend/build/static/css/main.c9966057.css.map deleted file mode 100644 index 3607ff04..00000000 --- a/frontend/build/static/css/main.c9966057.css.map +++ /dev/null @@ -1 +0,0 @@ -{"version":3,"file":"static/css/main.c9966057.css","mappings":"sGAKA,KACE,sBACF,CAGA,OACE,yBAA0B,CAC1B,kBACF,CAGA,kBACE,GACE,SAAU,CACV,0BACF,CACA,GACE,SAAU,CACV,uBACF,CACF,CAEA,mBACE,GACE,SAAU,CACV,0BACF,CACA,GACE,SAAU,CACV,uBACF,CACF,CAEA,iBACE,MACE,SACF,CACA,IACE,UACF,CACF,CAEA,mBACE,GACE,4BACF,CACA,GACE,wCACF,CACF,CAEA,iBACE,MACE,uBACF,CACA,IACE,2BACF,CACF,CAEA,gBACE,MACE,4BACF,CACA,IACE,6BACF,CACF,CAGA,KAEE,kCAAmC,CACnC,iCAAkC,CAFlC,qGAA0H,CAI1H,eAAgB,CADhB,eAEF,CAGA,OACE,0CACF,CAEA,cACE,oBACF,CAGA,cACE,0CACF,CAEA,oBACE,0BACF,CAGA,cAEE,kCAA2B,CAA3B,0BAA2B,CAD3B,oBAAoC,CAEpC,sBAA0C,CAC1C,kBACF,CAEA,0BACE,oBAAqC,CACrC,0BAA0C,CAE1C,+BAAyC,CADzC,0BAEF,CAGA,0CACE,uBACF,CAEA,gDACE,0BACF,CAGA,wBACE,iBAAkB,CAClB,eACF,CAEA,uBACE,6BACF,CAGA,cACE,eAAgB,CAChB,uBACF,CAEA,oBACE,0BACF,CAGA,eACE,iBAAkB,CAClB,eACF,CAGA,oBACE,iBAAkB,CAClB,eACF,CAGA,mBACE,uBACF,CAEA,yBACE,qBACF,CAGA,oBACE,0CACF,CAEA,0BACE,oBACF,CAGA,eACE,0CACF,CAGA,EACE,oBAAqB,CACrB,uBACF,CAEA,QACE,yBACF,CAGA,oBACE,SACF,CAEA,0BACE,kBAAmB,CACnB,iBACF,CAEA,0BACE,kBAAmB,CACnB,iBACF,CAEA,gCACE,kBACF,CAGA,iBAGE,+BAAgC,CAFhC,qEAAyE,CACzE,0BAEF,CAGA,YACE,iBACF,CAEA,kBAOE,sBAA6B,CAC7B,qBAAsB,CAFtB,WAAY,CALZ,UAAW,CAGX,SAAU,CAFV,iBAAkB,CAGlB,UAAW,CAFX,QAAS,CAMT,gCACF,CAEA,wBACE,oBACF,CAGA,aACE,kDACF,CAEA,uBACE,kDACF,CAEA,qBACE,8DACF,CAGA,WACE,gCACF,CAEA,WACE,4DACF,CAEA,WACE,8DACF,CAEA,WACE,gEACF,CAEA,YACE,sCACF,CAGA,cACE,+BACF,CAEA,0BACE,gCACF,CAGA,eACE,uCACF,CAEA,cACE,sCACF,CAEA,iBACE,6BACF,CAEA,kBACE,8BACF,CAGA,kBACE,0BAEF,CAEA,qCAHE,gDAMF,CAHA,mBACE,qBAEF,CAEA,kBACE,6BAA6C,CAC7C,8BACF,CAGA,eAGE,6BAAoC,CAFpC,kDAA6D,CAC7D,4BAA6B,CAE7B,oBACF,CAEA,aACE,+BACF,CAGA,yBACE,eACE,YACF,CAEA,aACE,oBACF,CAEA,oBACE,iBACF,CACF,CAEA,yBACE,gBACE,YACF,CACF,CAGA,aACE,UACE,sBACF,CACF,CAGA,mCACE,WACE,kBAAmB,CACnB,UACF,CACF,CAGA,+BACE,eACE,gBACF,CACF,CAGA,uCACE,EACE,kCAAqC,CACrC,qCAAuC,CACvC,mCACF,CACF","sources":["styles/global.css"],"sourcesContent":["/* Global Styles for Alwrity Onboarding */\r\n\r\n@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');\r\n\r\n/* Smooth scrolling */\r\nhtml {\r\n scroll-behavior: smooth;\r\n}\r\n\r\n/* Better focus styles */\r\n*:focus {\r\n outline: 2px solid #667eea;\r\n outline-offset: 2px;\r\n}\r\n\r\n/* Enhanced custom animations */\r\n@keyframes fadeIn {\r\n from {\r\n opacity: 0;\r\n transform: translateY(10px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n@keyframes slideUp {\r\n from {\r\n opacity: 0;\r\n transform: translateY(20px);\r\n }\r\n to {\r\n opacity: 1;\r\n transform: translateY(0);\r\n }\r\n}\r\n\r\n@keyframes pulse {\r\n 0%, 100% {\r\n opacity: 1;\r\n }\r\n 50% {\r\n opacity: 0.7;\r\n }\r\n}\r\n\r\n@keyframes shimmer {\r\n 0% {\r\n background-position: -200px 0;\r\n }\r\n 100% {\r\n background-position: calc(200px + 100%) 0;\r\n }\r\n}\r\n\r\n@keyframes float {\r\n 0%, 100% {\r\n transform: translateY(0px);\r\n }\r\n 50% {\r\n transform: translateY(-10px);\r\n }\r\n}\r\n\r\n@keyframes glow {\r\n 0%, 100% {\r\n box-shadow: 0 0 5px rgba(102, 126, 234, 0.5);\r\n }\r\n 50% {\r\n box-shadow: 0 0 20px rgba(102, 126, 234, 0.8);\r\n }\r\n}\r\n\r\n/* Enhanced typography */\r\nbody {\r\n font-family: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen', 'Ubuntu', 'Cantarell', sans-serif;\r\n -webkit-font-smoothing: antialiased;\r\n -moz-osx-font-smoothing: grayscale;\r\n line-height: 1.6;\r\n font-weight: 400;\r\n}\r\n\r\n/* Better button interactions */\r\nbutton {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\nbutton:active {\r\n transform: scale(0.98);\r\n}\r\n\r\n/* Enhanced card shadows and effects */\r\n.MuiCard-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.MuiCard-root:hover {\r\n transform: translateY(-2px);\r\n}\r\n\r\n/* Glassmorphism effects */\r\n.glass-effect {\r\n background: rgba(255, 255, 255, 0.1);\r\n backdrop-filter: blur(20px);\r\n border: 1px solid rgba(255, 255, 255, 0.2);\r\n border-radius: 12px;\r\n}\r\n\r\n.glass-effect-hover:hover {\r\n background: rgba(255, 255, 255, 0.15);\r\n border: 1px solid rgba(255, 255, 255, 0.3);\r\n transform: translateY(-4px);\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n/* Better form field styling */\r\n.MuiTextField-root .MuiOutlinedInput-root {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiTextField-root .MuiOutlinedInput-root:hover {\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* Enhanced progress bars */\r\n.MuiLinearProgress-root {\r\n border-radius: 4px;\r\n overflow: hidden;\r\n}\r\n\r\n.MuiLinearProgress-bar {\r\n transition: transform 0.3s ease;\r\n}\r\n\r\n/* Better chip styling */\r\n.MuiChip-root {\r\n font-weight: 600;\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiChip-root:hover {\r\n transform: translateY(-1px);\r\n}\r\n\r\n/* Enhanced alert styling */\r\n.MuiAlert-root {\r\n border-radius: 8px;\r\n font-weight: 500;\r\n}\r\n\r\n/* Better tooltip styling */\r\n.MuiTooltip-tooltip {\r\n border-radius: 6px;\r\n font-weight: 500;\r\n}\r\n\r\n/* Enhanced stepper styling */\r\n.MuiStepLabel-root {\r\n transition: all 0.3s ease;\r\n}\r\n\r\n.MuiStepLabel-root:hover {\r\n transform: scale(1.05);\r\n}\r\n\r\n/* Better icon button styling */\r\n.MuiIconButton-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.MuiIconButton-root:hover {\r\n transform: scale(1.1);\r\n}\r\n\r\n/* Enhanced paper styling */\r\n.MuiPaper-root {\r\n transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n/* Better link styling */\r\na {\r\n text-decoration: none;\r\n transition: all 0.3s ease;\r\n}\r\n\r\na:hover {\r\n text-decoration: underline;\r\n}\r\n\r\n/* Custom scrollbar */\r\n::-webkit-scrollbar {\r\n width: 8px;\r\n}\r\n\r\n::-webkit-scrollbar-track {\r\n background: #f1f1f1;\r\n border-radius: 4px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb {\r\n background: #c1c1c1;\r\n border-radius: 4px;\r\n}\r\n\r\n::-webkit-scrollbar-thumb:hover {\r\n background: #a8a8a8;\r\n}\r\n\r\n/* Loading animation */\r\n.loading-shimmer {\r\n background: linear-gradient(90deg, #f0f0f0 25%, #e0e0e0 50%, #f0f0f0 75%);\r\n background-size: 200px 100%;\r\n animation: shimmer 1.5s infinite;\r\n}\r\n\r\n/* Focus ring for accessibility */\r\n.focus-ring {\r\n position: relative;\r\n}\r\n\r\n.focus-ring::after {\r\n content: '';\r\n position: absolute;\r\n top: -2px;\r\n left: -2px;\r\n right: -2px;\r\n bottom: -2px;\r\n border: 2px solid transparent;\r\n border-radius: inherit;\r\n transition: border-color 0.3s ease;\r\n}\r\n\r\n.focus-ring:focus::after {\r\n border-color: #667eea;\r\n}\r\n\r\n/* Enhanced gradient backgrounds */\r\n.gradient-bg {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n}\r\n\r\n.gradient-bg-secondary {\r\n background: linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%);\r\n}\r\n\r\n.gradient-bg-premium {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);\r\n}\r\n\r\n/* Professional shadows */\r\n.shadow-sm {\r\n box-shadow: 0 1px 2px 0 rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.shadow-md {\r\n box-shadow: 0 4px 6px -1px rgba(0, 0, 0, 0.1), 0 2px 4px -1px rgba(0, 0, 0, 0.06);\r\n}\r\n\r\n.shadow-lg {\r\n box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05);\r\n}\r\n\r\n.shadow-xl {\r\n box-shadow: 0 20px 25px -5px rgba(0, 0, 0, 0.1), 0 10px 10px -5px rgba(0, 0, 0, 0.04);\r\n}\r\n\r\n.shadow-2xl {\r\n box-shadow: 0 25px 50px -12px rgba(0, 0, 0, 0.25);\r\n}\r\n\r\n/* Glassmorphism shadows */\r\n.glass-shadow {\r\n box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1);\r\n}\r\n\r\n.glass-shadow-hover:hover {\r\n box-shadow: 0 16px 48px rgba(0, 0, 0, 0.15);\r\n}\r\n\r\n/* Animation classes */\r\n.animate-float {\r\n animation: float 3s ease-in-out infinite;\r\n}\r\n\r\n.animate-glow {\r\n animation: glow 2s ease-in-out infinite;\r\n}\r\n\r\n.animate-fade-in {\r\n animation: fadeIn 0.6s ease-out;\r\n}\r\n\r\n.animate-slide-up {\r\n animation: slideUp 0.6s ease-out;\r\n}\r\n\r\n/* Hover effects */\r\n.hover-lift:hover {\r\n transform: translateY(-4px);\r\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.hover-scale:hover {\r\n transform: scale(1.05);\r\n transition: transform 0.3s cubic-bezier(0.4, 0, 0.2, 1);\r\n}\r\n\r\n.hover-glow:hover {\r\n box-shadow: 0 0 20px rgba(102, 126, 234, 0.3);\r\n transition: box-shadow 0.3s ease;\r\n}\r\n\r\n/* Text effects */\r\n.text-gradient {\r\n background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);\r\n -webkit-background-clip: text;\r\n -webkit-text-fill-color: transparent;\r\n background-clip: text;\r\n}\r\n\r\n.text-shadow {\r\n text-shadow: 0 2px 4px rgba(0, 0, 0, 0.3);\r\n}\r\n\r\n/* Responsive utilities */\r\n@media (max-width: 768px) {\r\n .mobile-hidden {\r\n display: none;\r\n }\r\n \r\n .mobile-full {\r\n width: 100% !important;\r\n }\r\n \r\n .mobile-text-center {\r\n text-align: center;\r\n }\r\n}\r\n\r\n@media (max-width: 480px) {\r\n .mobile-padding {\r\n padding: 16px;\r\n }\r\n}\r\n\r\n/* Print styles */\r\n@media print {\r\n .no-print {\r\n display: none !important;\r\n }\r\n}\r\n\r\n/* Dark mode support */\r\n@media (prefers-color-scheme: dark) {\r\n .auto-dark {\r\n background: #1a1a1a;\r\n color: #ffffff;\r\n }\r\n}\r\n\r\n/* High contrast mode */\r\n@media (prefers-contrast: high) {\r\n .high-contrast {\r\n border: 2px solid;\r\n }\r\n}\r\n\r\n/* Reduced motion support */\r\n@media (prefers-reduced-motion: reduce) {\r\n * {\r\n animation-duration: 0.01ms !important;\r\n animation-iteration-count: 1 !important;\r\n transition-duration: 0.01ms !important;\r\n }\r\n} "],"names":[],"sourceRoot":""} \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx index 78513221..8d1f5fb7 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx @@ -100,6 +100,7 @@ const ContentStrategyBuilder: React.FC = () => { dataSources, inputDataPoints, personalizationData, + confidenceScores, loading, error, saving, @@ -271,6 +272,48 @@ const ContentStrategyBuilder: React.FC = () => { completionStats }); + // Add ref for scroll to review section + const reviewSectionRef = useRef(null); + + // Handle scroll to review section + const handleScrollToReview = () => { + if (reviewSectionRef.current) { + reviewSectionRef.current.scrollIntoView({ + behavior: 'smooth', + block: 'start' + }); + } + }; + + // Handle continue with present values + const handleContinueWithPresent = () => { + console.log('๐ŸŽฏ Continuing with present autofilled values'); + // This will show the next button and allow user to proceed + }; + + // Determine if we have autofill data + const hasAutofillData = Object.keys(autoPopulatedFields).length > 0; + + // Get last autofill time from session storage or use current time + const lastAutofillTime = sessionStorage.getItem('lastAutofillTime') || new Date().toISOString(); + + // Get data source from store + const dataSource = Object.keys(dataSources).length > 0 ? 'Onboarding Database' : undefined; + + // Log autofill data status for debugging + useEffect(() => { + console.log('๐Ÿ“‹ StrategyBuilder: Autofill data status:', { + hasAutofillData, + autoPopulatedFieldsCount: Object.keys(autoPopulatedFields).length, + dataSourcesCount: Object.keys(dataSources).length, + inputDataPointsCount: Object.keys(inputDataPoints).length, + personalizationDataCount: Object.keys(personalizationData).length, + confidenceScoresCount: Object.keys(confidenceScores).length, + lastAutofillTime, + dataSource + }); + }, [hasAutofillData, autoPopulatedFields, dataSources, inputDataPoints, personalizationData, confidenceScores, lastAutofillTime, dataSource]); + // Enhanced handleCreateStrategy to show enterprise modal @@ -332,39 +375,16 @@ const ContentStrategyBuilder: React.FC = () => { if (Object.keys(completionStats.category_completion).length > 0) { const firstCategory = Object.keys(completionStats.category_completion)[0]; - console.log('๐ŸŽฏ Setting default category:', firstCategory); setActiveCategory(firstCategory); hasSetDefaultCategory.current = true; - console.log('โœ… hasSetDefaultCategory set to true'); } }, [completionStats.category_completion]); // Removed activeCategory dependency - // Debug activeCategory changes - useEffect(() => { - console.log('๐Ÿ”„ activeCategory changed to:', activeCategory); - console.trace('๐Ÿ“ Stack trace for activeCategory change'); - }, [activeCategory]); - - // Monitor modal state for debugging - useEffect(() => { - console.log('๐ŸŽฏ Modal state changed - transparencyModalOpen:', transparencyModalOpen); - }, [transparencyModalOpen]); - // Monitor enterprise modal state for debugging useEffect(() => { - console.log('๐ŸŽฏ Enterprise modal state changed - showEnterpriseModal:', showEnterpriseModal); - // If modal was unexpectedly closed, log it if (!showEnterpriseModal && aiGenerating) { - console.warn('๐ŸŽฏ WARNING: Enterprise modal closed while AI is generating'); - } - - // Only warn about unexpected closure if it's not due to hot reload - if (!showEnterpriseModal && !aiGenerating) { - const savedModalState = sessionStorage.getItem('showEnterpriseModal'); - if (savedModalState !== 'true') { - console.warn('๐ŸŽฏ WARNING: Enterprise modal closed unexpectedly (not due to hot reload)'); - } + console.warn('Enterprise modal closed while AI is generating'); } }, [showEnterpriseModal, aiGenerating]); @@ -382,14 +402,27 @@ const ContentStrategyBuilder: React.FC = () => { // Wrapper for the hook function to maintain the same interface const handleConfirmCategoryReviewWrapper = () => { - console.log('๐Ÿ”ง Wrapper called with activeCategory:', activeCategory); handleConfirmCategoryReview(activeCategory); }; return ( {/* Header with Title (Region B) - Enhanced with Futuristic Styling */} - + {/* Error Alert */} { onShowEducationalInfo={handleShowEducationalInfo} /> - {/* Quick Actions */} - - - Quick Actions - - - - - - - + {/* Main Content Area */} - { - // If a specific field is provided, show field-specific data source info - if (fieldId) { - console.log('๐ŸŽฏ Viewing data source for field:', fieldId); - // For now, just open the general data source transparency modal - // In the future, this could open a field-specific modal - setShowDataSourceTransparency(true); - } else { - setShowDataSourceTransparency(true); - } - }} - onConfirmCategoryReview={handleConfirmCategoryReviewWrapper} - onSetActiveCategory={setActiveCategory} - onSetShowEducationalInfo={setShowEducationalInfo} - getCategoryIcon={getCategoryIcon} - getCategoryColor={getCategoryColor} - getEducationalContent={getEducationalContent} - /> +
+ { + // If a specific field is provided, show field-specific data source info + if (fieldId) { + console.log('๐ŸŽฏ Viewing data source for field:', fieldId); + // For now, just open the general data source transparency modal + // In the future, this could open a field-specific modal + setShowDataSourceTransparency(true); + } else { + setShowDataSourceTransparency(true); + } + }} + onConfirmCategoryReview={handleConfirmCategoryReviewWrapper} + onSetActiveCategory={setActiveCategory} + onSetShowEducationalInfo={setShowEducationalInfo} + getCategoryIcon={getCategoryIcon} + getCategoryColor={getCategoryColor} + getEducationalContent={getEducationalContent} + /> +
@@ -560,11 +561,17 @@ const ContentStrategyBuilder: React.FC = () => { educationalContent={storeEducationalContent} generationProgress={storeGenerationProgress} onReviewStrategy={() => { - console.log('๐ŸŽฏ User clicked "Next: Review Strategy and Create Calendar"'); setShowEducationalModal(false); + + // Set flag to indicate coming from strategy builder + sessionStorage.setItem('fromStrategyBuilder', 'true'); + // Navigate to content planning dashboard with Content Strategy tab active navigate('/content-planning', { - state: { activeTab: 0 } // 0 = Content Strategy tab + state: { + activeTab: 0, // 0 = Content Strategy tab + fromStrategyBuilder: true + } }); }} /> @@ -601,10 +608,6 @@ const ContentStrategyBuilder: React.FC = () => { open={transparencyModalOpen} onClose={() => { setTransparencyModalOpen(false); - // Ensure form data is refreshed after modal closes - console.log('๐ŸŽฏ Modal closed - ensuring form data is updated'); - console.log('๐ŸŽฏ Current autoPopulatedFields:', Object.keys(autoPopulatedFields || {})); - console.log('๐ŸŽฏ Current formData keys:', Object.keys(formData || {})); }} autoPopulatedFields={autoPopulatedFields} dataSources={dataSources} @@ -621,8 +624,6 @@ const ContentStrategyBuilder: React.FC = () => { { - console.log('๐ŸŽฏ Enterprise modal onClose called'); - console.log('๐ŸŽฏ Current aiGenerating state:', aiGenerating); setShowEnterpriseModal(false); sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage }} diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx index c9b4d5b3..13305888 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/StrategicInputField.tsx @@ -426,26 +426,15 @@ const StrategicInputField: React.FC = ({ return option === value; }} value={(() => { - // Debug logging for Autocomplete value parsing - console.log('๐ŸŽฏ Autocomplete value parsing:', { - fieldId, - originalValue: value, - valueType: typeof value, - isArray: Array.isArray(value), - availableOptions: multiSelectConfig.options - }); - let parsedValues: string[] = []; if (Array.isArray(value)) { parsedValues = value; - console.log('๐ŸŽฏ Using array value:', parsedValues); } else if (typeof value === 'object' && value !== null) { // Handle object values (convert to array of keys or values) if (typeof value === 'object' && !Array.isArray(value)) { // Convert object to array of keys or values based on context const objectKeys = Object.keys(value); - const objectValues = Object.values(value); // For traffic_sources, we might want to use the keys or convert percentages to options if (fieldId === 'traffic_sources') { @@ -466,12 +455,9 @@ const StrategicInputField: React.FC = ({ parsedValues = objectKeys .map(key => trafficMapping[key.toLowerCase()]) .filter(Boolean); - - console.log('๐ŸŽฏ Converted object to traffic sources:', parsedValues); } else { // For other fields, use object keys parsedValues = objectKeys; - console.log('๐ŸŽฏ Using object keys:', parsedValues); } } } else if (typeof value === 'string') { @@ -480,10 +466,8 @@ const StrategicInputField: React.FC = ({ const parsed = JSON.parse(value); if (Array.isArray(parsed)) { parsedValues = parsed; - console.log('๐ŸŽฏ Parsed as JSON array:', parsedValues); } } catch (error) { - console.log('๐ŸŽฏ JSON parse failed, trying alternative parsing'); // If not valid JSON, try to extract array-like content if (value.startsWith('[') && value.endsWith(']')) { // Remove outer brackets and try to parse as comma-separated @@ -493,37 +477,48 @@ const StrategicInputField: React.FC = ({ // Remove quotes and trim return item.trim().replace(/^["']|["']$/g, ''); }).filter(item => item); - console.log('๐ŸŽฏ Parsed as array-like string:', parsedValues); } else if (value.includes(',')) { // If not array-like, try simple comma splitting parsedValues = value.split(',').map(item => item.trim()).filter(item => item); - console.log('๐ŸŽฏ Parsed as comma-separated string:', parsedValues); } } } - // Filter values to only include valid options + // Filter values to only include valid options and improve matching const validOptions = multiSelectConfig.options || []; const filteredValues = parsedValues.filter(val => { // Check for exact match if (validOptions.includes(val)) { return true; } - // Check for partial match (case-insensitive) - const partialMatch = validOptions.find(option => - option.toLowerCase().includes(val.toLowerCase()) || - val.toLowerCase().includes(option.toLowerCase()) - ); - if (partialMatch) { - console.log('๐ŸŽฏ Found partial match:', val, '->', partialMatch); - return true; - } - console.log('๐ŸŽฏ No match found for:', val); - return false; + // Check for partial match (case-insensitive) with better logic + const partialMatch = validOptions.find(option => { + const optionLower = option.toLowerCase(); + const valLower = val.toLowerCase(); + + // Check if option contains the value or vice versa + return optionLower.includes(valLower) || valLower.includes(optionLower); + }); + + return !!partialMatch; }); - console.log('๐ŸŽฏ Final filtered values:', filteredValues); - return filteredValues; + // Map partial matches to exact options + const mappedValues = filteredValues.map(val => { + const exactMatch = validOptions.find(option => option === val); + if (exactMatch) return exactMatch; + + // Find the best partial match + const partialMatch = validOptions.find(option => { + const optionLower = option.toLowerCase(); + const valLower = val.toLowerCase(); + return optionLower.includes(valLower) || valLower.includes(optionLower); + }); + + return partialMatch || val; + }); + + return mappedValues; })()} onChange={(_, newValue) => handleChange(newValue)} renderInput={(params) => ( diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx index 48a4aed4..456aa38f 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/ActionButtons.tsx @@ -9,6 +9,7 @@ import { Save as SaveIcon } from '@mui/icons-material'; import { ActionButtonsProps, ActionButtonsBusinessLogicProps } from '../types/contentStrategy.types'; +import { useContentPlanningStore } from '../../../../../stores/contentPlanningStore'; // Business Logic Hook export const useActionButtonsBusinessLogic = ({ @@ -29,11 +30,18 @@ export const useActionButtonsBusinessLogic = ({ contentPlanningApi }: ActionButtonsBusinessLogicProps) => { + // Get the content planning store to cache the latest generated strategy + const { setLatestGeneratedStrategy } = useContentPlanningStore(); + const handleCreateStrategy = async () => { try { setAIGenerating(true); setError(null); + // Clear any previous cached strategy when starting new generation + setLatestGeneratedStrategy(null); + console.log('๐Ÿงน Cleared previous cached strategy for new generation'); + console.log('Starting strategy creation...'); console.log('Current formData:', formData); console.log('FormData ID:', formData.id); @@ -152,6 +160,20 @@ export const useActionButtonsBusinessLogic = ({ (strategy: any) => { console.log('โœ… Strategy generation completed successfully!'); setCurrentStrategy(strategy); + + // Cache the latest generated strategy in the content planning store + console.log('๐Ÿ’พ Attempting to cache strategy:', { + strategyId: strategy?.id || strategy?.strategy_id, + strategyName: strategy?.name || strategy?.strategy_name, + hasStrategicInsights: !!strategy?.strategic_insights, + hasCompetitiveAnalysis: !!strategy?.competitive_analysis, + hasPerformancePredictions: !!strategy?.performance_predictions, + hasImplementationRoadmap: !!strategy?.implementation_roadmap, + hasRiskAssessment: !!strategy?.risk_assessment + }); + setLatestGeneratedStrategy(strategy); + console.log('๐Ÿ’พ Cached latest generated strategy in store'); + // Set progress to 100% when completion is detected setGenerationProgress(100); console.log('๐ŸŽฏ Setting progress to 100% in onComplete callback'); @@ -196,6 +218,11 @@ export const useActionButtonsBusinessLogic = ({ const newStrategy = await createEnhancedStrategy(strategyData); setCurrentStrategy(newStrategy); + + // Update the cache with the saved strategy + setLatestGeneratedStrategy(newStrategy); + console.log('๐Ÿ’พ Updated cache with saved strategy'); + setError('Strategy saved successfully!'); } catch (err: any) { setError(`Error saving strategy: ${err.message || 'Unknown error'}`); diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx new file mode 100644 index 00000000..f278b5ee --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/AutofillDataTransparency.tsx @@ -0,0 +1,457 @@ +import React, { useState } from 'react'; +import { + Dialog, + DialogTitle, + DialogContent, + DialogActions, + Button, + Box, + Typography, + Accordion, + AccordionSummary, + AccordionDetails, + Chip, + Grid, + Paper, + Divider, + List, + ListItem, + ListItemText, + ListItemIcon, + Alert, + LinearProgress, + Tooltip, + IconButton +} from '@mui/material'; +import { + ExpandMore as ExpandMoreIcon, + Info as InfoIcon, + DataUsage as DataUsageIcon, + Psychology as PsychologyIcon, + Person as PersonIcon, + Analytics as AnalyticsIcon, + CheckCircle as CheckCircleIcon, + Warning as WarningIcon, + Close as CloseIcon, + Refresh as RefreshIcon, + TrendingUp as TrendingUpIcon, + Security as SecurityIcon, + Visibility as VisibilityIcon, + AutoAwesome as AutoAwesomeIcon +} from '@mui/icons-material'; +import { motion } from 'framer-motion'; + +interface AutofillDataTransparencyProps { + open: boolean; + onClose: () => void; + autoPopulatedFields: Record; + dataSources: Record; + inputDataPoints: Record; + personalizationData: Record; + confidenceScores: Record; + lastAutofillTime?: string; + dataSource?: string; +} + +const AutofillDataTransparency: React.FC = ({ + open, + onClose, + autoPopulatedFields, + dataSources, + inputDataPoints, + personalizationData, + confidenceScores, + lastAutofillTime, + dataSource +}) => { + const [activeAccordion, setActiveAccordion] = useState('data-sources'); + + const handleAccordionChange = (panel: string) => (event: React.SyntheticEvent, isExpanded: boolean) => { + setActiveAccordion(isExpanded ? panel : false); + }; + + // Calculate data freshness + const getDataFreshness = () => { + if (!lastAutofillTime) return 'Unknown'; + const lastUpdate = new Date(lastAutofillTime); + const now = new Date(); + const diffInHours = Math.floor((now.getTime() - lastUpdate.getTime()) / (1000 * 60 * 60)); + + if (diffInHours < 1) return 'Just now'; + if (diffInHours < 24) return `${diffInHours} hours ago`; + const diffInDays = Math.floor(diffInHours / 24); + return `${diffInDays} days ago`; + }; + + // Get data quality score + const getDataQualityScore = () => { + const scores = Object.values(confidenceScores); + if (scores.length === 0) return 0; + return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); + }; + + // Get field count by category + const getFieldCountByCategory = () => { + const categories: Record = {}; + Object.keys(autoPopulatedFields).forEach(fieldId => { + const category = fieldId.split('_')[0] || 'other'; + categories[category] = (categories[category] || 0) + 1; + }); + return categories; + }; + + const dataQualityScore = getDataQualityScore(); + const dataFreshness = getDataFreshness(); + const fieldCountByCategory = getFieldCountByCategory(); + + return ( + + + + + + + Autofill Data Transparency + + + + + + + + Complete transparency about how your strategy inputs were auto-populated + + + + + {/* Summary Cards */} + + + + + + {Object.keys(autoPopulatedFields).length} + + + Fields Auto-populated + + + + + + + + + {dataQualityScore}% + + + Data Quality Score + + + + + + + + + {dataFreshness} + + + Last Updated + + + + + + + + + {Object.keys(dataSources).length} + + + Data Sources Used + + + + + + {/* Data Quality Indicator */} + = 80 ? 'success' : dataQualityScore >= 60 ? 'warning' : 'error'} + sx={{ mb: 3 }} + icon={dataQualityScore >= 80 ? : } + > + + Data Quality Assessment: {dataQualityScore >= 80 ? 'Excellent' : dataQualityScore >= 60 ? 'Good' : 'Needs Review'} + + + Based on confidence scores from {Object.keys(confidenceScores).length} fields + + + + + {/* Detailed Information Accordions */} + + }> + + + + Data Sources & Integration + + + + + + + + Primary Data Sources + + + {Object.entries(dataSources).map(([fieldId, source]) => ( + + + + + l.toUpperCase())} + secondary={source} + /> + + ))} + + + + + Integration Details + + + + Data Source: {dataSource || 'Onboarding Integration'} + + + Integration Method: AI-Powered Analysis + + + Data Processing: Real-time with validation + + + Privacy Compliance: GDPR & CCPA Compliant + + + + + + + + + }> + + + + User Information & Personalization + + + + + + {Object.entries(personalizationData).map(([fieldId, data]) => ( + + + + {fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + {typeof data === 'object' && data !== null ? ( + + {data.explanation && ( + + Explanation: {data.explanation} + + )} + {data.personalization_factors && ( + + + Personalization Factors: + + {Object.entries(data.personalization_factors).map(([key, value]) => ( + + ))} + + )} + + ) : ( + {String(data)} + )} + + + ))} + + + + + + }> + + + + AI Analysis Results + + + + + + + + Confidence Scores by Field + + + {Object.entries(confidenceScores).map(([fieldId, score]) => ( + + + = 80 ? 'success.main' : score >= 60 ? 'warning.main' : 'error.main' + }} /> + + l.toUpperCase())} + secondary={`${score}% confidence`} + /> + + + ))} + + + + + AI Processing Details + + + + AI Model: Claude 3.5 Sonnet + + + Analysis Type: Contextual Understanding + + + Processing Time: Real-time + + + Validation: Multi-step verification + + + Quality Checks: Generic placeholder detection + + + + + + + + + }> + + + + Field-by-Field Breakdown + + + + + + {Object.entries(autoPopulatedFields).map(([fieldId, value]) => ( + + + + + {fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())} + + + + + Value: {typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)} + + {confidenceScores[fieldId] && ( + + Confidence: {confidenceScores[fieldId]}% + + )} + {inputDataPoints[fieldId] && ( + + Data Points: {Object.keys(inputDataPoints[fieldId]).length} sources + + )} + + + ))} + + + + + + + + + + + ); +}; + +export default AutofillDataTransparency; diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx index 6480197b..c12aefbe 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/components/HeaderSection.tsx @@ -1,19 +1,118 @@ -import React from 'react'; +import React, { useState, useEffect } from 'react'; import { Paper, Box, - Typography + Typography, + Button, + Chip, + Alert, + Collapse, + Tooltip, + Grid, + LinearProgress } from '@mui/material'; import { - CheckCircle as CheckCircleIcon + CheckCircle as CheckCircleIcon, + Refresh as RefreshIcon, + PlayArrow as PlayArrowIcon, + Schedule as ScheduleIcon, + Info as InfoIcon, + ArrowDownward as ArrowDownwardIcon, + Visibility as VisibilityIcon, + DataUsage as DataUsageIcon, + TrendingUp as TrendingUpIcon, + Security as SecurityIcon, + AutoAwesome as AutoAwesomeIcon } from '@mui/icons-material'; import { motion } from 'framer-motion'; +import AutofillDataTransparency from './AutofillDataTransparency'; interface HeaderSectionProps { autoPopulatedFields: any; + dataSources: any; + inputDataPoints: any; + personalizationData: any; + confidenceScores: any; + loading: boolean; + error: string | null; + onRefreshAutofill: () => void; + onContinueWithPresent: () => void; + onScrollToReview: () => void; + hasAutofillData: boolean; + lastAutofillTime?: string; + dataSource?: string; } -const HeaderSection: React.FC = ({ autoPopulatedFields }) => { +const HeaderSection: React.FC = ({ + autoPopulatedFields, + dataSources, + inputDataPoints, + personalizationData, + confidenceScores, + loading, + error, + onRefreshAutofill, + onContinueWithPresent, + onScrollToReview, + hasAutofillData, + lastAutofillTime, + dataSource +}) => { + const [showTransparencyModal, setShowTransparencyModal] = useState(false); + const [showDataInfo, setShowDataInfo] = useState(false); + const [showNextButton, setShowNextButton] = useState(false); + + // Show next button when autofill is complete + useEffect(() => { + if (hasAutofillData && Object.keys(autoPopulatedFields).length > 0) { + setShowNextButton(true); + } + }, [hasAutofillData, autoPopulatedFields]); + + // Determine cache status and show appropriate buttons + const getCacheStatus = () => { + if (hasAutofillData && Object.keys(autoPopulatedFields).length > 0) { + return 'cached'; + } else if (Object.keys(inputDataPoints).length > 0) { + return 'partial'; + } else { + return 'empty'; + } + }; + + const cacheStatus = getCacheStatus(); + + const formatTimeAgo = (timestamp: string) => { + const now = new Date(); + const time = new Date(timestamp); + const diffInMinutes = Math.floor((now.getTime() - time.getTime()) / (1000 * 60)); + + if (diffInMinutes < 1) return 'Just now'; + if (diffInMinutes < 60) return `${diffInMinutes} minutes ago`; + if (diffInMinutes < 1440) return `${Math.floor(diffInMinutes / 60)} hours ago`; + return `${Math.floor(diffInMinutes / 1440)} days ago`; + }; + + // Calculate data quality score + const getDataQualityScore = () => { + const scores = Object.values(confidenceScores).filter((score): score is number => typeof score === 'number'); + if (scores.length === 0) return 0; + return Math.round(scores.reduce((a, b) => a + b, 0) / scores.length); + }; + + // Get field count by category + const getFieldCountByCategory = () => { + const categories: Record = {}; + Object.keys(autoPopulatedFields).forEach(fieldId => { + const category = fieldId.split('_')[0] || 'other'; + categories[category] = (categories[category] || 0) + 1; + }); + return categories; + }; + + const dataQualityScore = getDataQualityScore(); + const fieldCountByCategory = getFieldCountByCategory(); + return ( = ({ autoPopulatedFields }) => > = ({ autoPopulatedFields }) => border: '1px solid rgba(255,255,255,0.2)', }} > - - - - AI Content Strategy Co-pilot - - - Build a comprehensive content strategy with 30+ strategic inputs - - - {/* Auto-population Status - Moved to header (Region 4) */} - {autoPopulatedFields && Object.keys(autoPopulatedFields).length > 0 && ( - - - - {Object.keys(autoPopulatedFields).length} fields auto-populated from onboarding data + + {/* Main Header */} + + + + AI Content Strategy Co-pilot + + + Build a comprehensive content strategy with 30+ strategic inputs + + + + + {/* Enhanced Data Status Grid */} + + {/* Auto-populated Fields Count */} + + + + + + {Object.keys(autoPopulatedFields).length} + + + Fields Auto-populated + + + + + + {/* Data Quality Score */} + + + + + + {dataQualityScore}% + + + Data Quality + + + + + + {/* Last Updated */} + + + + + + {lastAutofillTime ? formatTimeAgo(lastAutofillTime).split(' ')[0] : 'N/A'} + + + Last Updated + + + + + + {/* Data Sources */} + + + + + + {Object.keys(dataSources).length} + + + Data Sources + + + + + + + {/* Data Quality Progress Bar */} + {dataQualityScore > 0 && ( + + + + Data Quality Score + + + {dataQualityScore}% + = 80 + ? 'linear-gradient(90deg, #4caf50, #66bb6a)' + : dataQualityScore >= 60 + ? 'linear-gradient(90deg, #ff9800, #ffb74d)' + : 'linear-gradient(90deg, #f44336, #ef5350)', + borderRadius: 3 + } + }} + /> + + )} + + {/* Enhanced Status Chips */} + + {cacheStatus === 'cached' && ( + } + label={`${Object.keys(autoPopulatedFields).length} fields auto-populated`} + sx={{ + backgroundColor: 'rgba(76, 175, 80, 0.2)', + color: 'white', + border: '1px solid rgba(76, 175, 80, 0.3)', + '& .MuiChip-icon': { color: 'rgba(76, 175, 80, 0.8)' }, + fontWeight: 500, + fontSize: '0.8rem' + }} + /> + )} + + {dataSource && ( + + } + label={`Source: ${dataSource}`} + onClick={() => setShowDataInfo(!showDataInfo)} + sx={{ + backgroundColor: 'rgba(255, 255, 255, 0.1)', + color: 'white', + border: '1px solid rgba(255, 255, 255, 0.2)', + cursor: 'pointer', + fontWeight: 500, + fontSize: '0.8rem', + '&:hover': { + backgroundColor: 'rgba(255, 255, 255, 0.2)' + } + }} + /> + + )} + + {/* Category Distribution Chips */} + {Object.keys(fieldCountByCategory).length > 0 && ( + } + label={`${Object.keys(fieldCountByCategory).length} categories`} + sx={{ + backgroundColor: 'rgba(156, 39, 176, 0.2)', + color: 'white', + border: '1px solid rgba(156, 39, 176, 0.3)', + '& .MuiChip-icon': { color: 'rgba(156, 39, 176, 0.8)' }, + fontWeight: 500, + fontSize: '0.8rem' + }} + /> + )} + + + {/* Data Source Information */} + + + + Data Source: {dataSource || 'Onboarding Database'} + + + Input Data Points: {Object.keys(inputDataPoints).length} available + + + Adaptive Monitoring: ALwrity continuously monitors databases for new data points to ensure you have the latest information. + + + + + {/* Conditional Action Buttons */} + + {cacheStatus === 'cached' ? ( + // Case 1: Data exists in cache - show refresh vs continue options + <> + + + + + + + + + ) : cacheStatus === 'partial' ? ( + // Case 2: Partial data - show refresh option + + + + ) : ( + // Case 3: No data - show initial autofill + + + + )} + + {/* Next Step Button - shown after autofill completion */} + {showNextButton && ( + + + + )} + + {/* Know More Details Button - shown when autofill data exists */} + {hasAutofillData && Object.keys(autoPopulatedFields).length > 0 && ( + + + )} + + {/* Autofill Data Transparency Modal */} + setShowTransparencyModal(false)} + autoPopulatedFields={autoPopulatedFields} + dataSources={dataSources} + inputDataPoints={inputDataPoints} + personalizationData={personalizationData} + confidenceScores={confidenceScores} + lastAutofillTime={lastAutofillTime} + dataSource={dataSource} + /> ); }; diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts index 12ab6021..7ba4da0a 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAIRefresh.ts @@ -35,14 +35,12 @@ export const useAIRefresh = ({ // This approach uses direct HTTP calls with visual feedback // Open transparency modal and initialize transparency state - console.log('๐ŸŽฏ Opening transparency modal...'); setTransparencyModalOpen(true); setIsGenerating(true); setStoreGenerationProgress(0); setCurrentPhase('autofill_initialization'); clearTransparencyMessages(); addTransparencyMessage('Starting strategy inputs generation process...'); - console.log('๐ŸŽฏ Modal state set, transparency initialized'); setAIGenerating(true); setIsRefreshing(true); @@ -68,12 +66,6 @@ export const useAIRefresh = ({ const transparencyInterval = setInterval(() => { if (messageIndex < transparencyMessages.length) { const message = transparencyMessages[messageIndex]; - console.log('๐ŸŽฏ Raw Polling Message:', { - type: message.type, - message: message.message, - progress: message.progress, - timestamp: new Date().toISOString() - }); setCurrentPhase(message.type); addTransparencyMessage(message.message); setStoreGenerationProgress(message.progress); @@ -85,80 +77,20 @@ export const useAIRefresh = ({ }, 2000); // Send a message every 2 seconds for better UX // Call the non-streaming refresh endpoint (Polling-based approach) - console.log('๐ŸŽฏ Calling AI refresh endpoint (Polling-based)...'); const response = await contentPlanningApi.refreshAutofill(1, true, true); - console.log('๐ŸŽฏ Raw Polling Response:', { - success: !!response, - hasData: !!response?.fields, - responseStructure: { - hasFieldsProperty: !!response?.fields, - hasSourcesProperty: !!response?.sources, - hasMetaProperty: !!response?.meta - }, - fieldsCount: Object.keys(response?.fields || {}).length, - sourcesCount: Object.keys(response?.sources || {}).length, - meta: response?.meta || {}, - timestamp: new Date().toISOString() - }); // Clear the transparency interval since we got the response clearInterval(transparencyInterval); // Process the response - // The API method already returns the extracted data, not the full response if (response) { - // Debug the actual response structure - console.log('๐ŸŽฏ Raw response structure:', { - responseType: typeof response, - responseKeys: Object.keys(response), - hasFieldsProperty: response?.hasOwnProperty('fields'), - hasSourcesProperty: response?.hasOwnProperty('sources'), - hasMetaProperty: response?.hasOwnProperty('meta') - }); - - // Debug the actual response data - console.log('๐ŸŽฏ Raw response:', response); - console.log('๐ŸŽฏ Raw response.fields:', response?.fields); - console.log('๐ŸŽฏ Raw response.sources:', response?.sources); - console.log('๐ŸŽฏ Raw response.meta:', response?.meta); - - // The API method already returns the extracted payload from ResponseBuilder - // So response is already the payload with fields, sources, meta, etc. const payload = response; - - // Debug the payload structure - console.log('๐ŸŽฏ Payload structure:', { - payloadType: typeof payload, - payloadKeys: Object.keys(payload), - hasFieldsProperty: payload?.hasOwnProperty('fields'), - hasSourcesProperty: payload?.hasOwnProperty('sources'), - hasMetaProperty: payload?.hasOwnProperty('meta'), - fieldsKeys: payload?.fields ? Object.keys(payload.fields) : 'no fields' - }); - const fields = payload.fields || {}; const sources = payload.sources || {}; const inputDataPoints = payload.input_data_points || {}; const meta = payload.meta || {}; - // Debug the extracted data - console.log('๐ŸŽฏ Extracted fields:', fields); - console.log('๐ŸŽฏ Extracted sources:', sources); - console.log('๐ŸŽฏ Extracted inputDataPoints:', inputDataPoints); - console.log('๐ŸŽฏ Extracted meta:', meta); - console.log('๐ŸŽฏ Fields count:', Object.keys(fields).length); - console.log('๐ŸŽฏ Sources count:', Object.keys(sources).length); - console.log('๐ŸŽฏ InputDataPoints count:', Object.keys(inputDataPoints).length); - - console.log('๐ŸŽฏ AI Refresh Result - Payload:', payload); - console.log('๐ŸŽฏ AI Refresh Result - Fields:', fields); - console.log('๐ŸŽฏ AI Refresh Result - Meta:', meta); - console.log('๐ŸŽฏ Fields structure check:', { - fieldsType: typeof fields, - fieldsKeys: Object.keys(fields), - sampleField: fields[Object.keys(fields)[0]], - hasValueProperty: fields[Object.keys(fields)[0]]?.hasOwnProperty('value') - }); + console.log('๐ŸŽฏ AI Refresh - Generated fields:', Object.keys(fields).length); // ๐Ÿšจ CRITICAL: Check if AI generation failed if (meta.error || !meta.ai_used) { @@ -187,11 +119,11 @@ export const useAIRefresh = ({ return; } - console.log(`โœ… AI generation successful - generated ${fieldsCount} fields, AI overrides: ${meta.ai_overrides_count || 0}`); + console.log(`โœ… AI generation successful - ${fieldsCount} fields generated`); // ๐Ÿšจ CRITICAL: Validate data source (only check for explicit failure states) if (meta.data_source === 'ai_generation_failed' || meta.data_source === 'ai_generation_error') { - console.error('โŒ Invalid data source:', meta.data_source); + console.error('โŒ AI generation failed:', meta.data_source); setError(`AI generation failed: ${meta.error || 'Invalid data source. Please try again.'}`); setTransparencyModalOpen(false); setAIGenerating(false); @@ -202,51 +134,34 @@ export const useAIRefresh = ({ return; } - console.log('โœ… AI generation successful - processing real AI data'); - const fieldValues: Record = {}; const confidenceScores: Record = {}; Object.keys(fields).forEach((fieldId) => { const fieldData = fields[fieldId]; - console.log(`๐ŸŽฏ Processing field ${fieldId}:`, fieldData); if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) { fieldValues[fieldId] = fieldData.value; - console.log(`โœ… Field ${fieldId} value extracted:`, fieldData.value); // Extract confidence score if available if (fieldData.confidence) { confidenceScores[fieldId] = fieldData.confidence; - console.log(`๐ŸŽฏ Field ${fieldId} confidence: ${fieldData.confidence}`); - } - - // Extract personalization data if available - if (fieldData.personalization_data) { - console.log(`๐ŸŽฏ Field ${fieldId} personalization:`, fieldData.personalization_data); } } else { - console.warn(`โš ๏ธ Field ${fieldId} has invalid structure:`, fieldData); + console.warn(`โš ๏ธ Field ${fieldId} has invalid structure`); } }); - console.log('๐ŸŽฏ Processed field values:', Object.keys(fieldValues)); - console.log('๐ŸŽฏ Confidence scores:', confidenceScores); - console.log('๐ŸŽฏ Field values details:', fieldValues); - - // Update the store with the new data + // Update the store with the new data - COMPLETELY REPLACE old data useStrategyBuilderStore.setState((state) => { const newState = { - autoPopulatedFields: { ...state.autoPopulatedFields, ...fieldValues }, - dataSources: { ...state.dataSources, ...sources }, - inputDataPoints: { ...state.inputDataPoints, ...inputDataPoints }, - confidenceScores: { ...state.confidenceScores, ...confidenceScores }, - formData: { ...state.formData, ...fieldValues } + autoPopulatedFields: fieldValues, // ๐Ÿšจ CRITICAL: Replace, don't merge + dataSources: sources, // ๐Ÿšจ CRITICAL: Replace, don't merge + inputDataPoints: inputDataPoints, // ๐Ÿšจ CRITICAL: Replace, don't merge + confidenceScores: confidenceScores, // ๐Ÿšจ CRITICAL: Replace, don't merge + formData: { ...state.formData, ...fieldValues } // Keep existing manual edits }; - console.log('๐ŸŽฏ Updated store state:', newState); - console.log('๐ŸŽฏ Field values being added:', fieldValues); - console.log('๐ŸŽฏ Confidence scores being added:', confidenceScores); - console.log('๐ŸŽฏ Store autoPopulatedFields count:', Object.keys(newState.autoPopulatedFields).length); + console.log('โœ… Store updated with fresh AI data:', Object.keys(fieldValues).length, 'fields'); return newState; }); @@ -263,17 +178,14 @@ export const useAIRefresh = ({ setRefreshProgress(100); }, 100); + // Update session storage with fresh autofill timestamp + sessionStorage.setItem('lastAutofillTime', new Date().toISOString()); + // Reset refresh state setAIGenerating(false); setIsRefreshing(false); setIsGenerating(false); - console.log('๐ŸŽฏ Polling-based AI refresh completed successfully!', { - fieldsGenerated: Object.keys(fieldValues).length, - confidenceScoresCount: Object.keys(confidenceScores).length, - dataSourcesCount: Object.keys(sources).length, - approach: 'Polling (No SSE)', - timestamp: new Date().toISOString() - }); + console.log('โœ… AI refresh completed:', Object.keys(fieldValues).length, 'fields generated'); } else { throw new Error('Invalid response from AI refresh endpoint'); } diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts index d303b14a..6aa94086 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useAutoPopulation.ts @@ -15,10 +15,24 @@ export const useAutoPopulation = ({ // Auto-populate from onboarding on first load useEffect(() => { if (!autoPopulateAttempted && !isAutoPopulating) { + console.log('๐Ÿš€ useAutoPopulation: Triggering initial auto-population'); + console.log('๐Ÿ“Š useAutoPopulation: Current completion stats:', { + totalFields: completionStats?.total_fields || 0, + filledFields: completionStats?.filled_fields || 0, + completionPercentage: completionStats?.completion_percentage || 0 + }); + setIsAutoPopulating(true); autoPopulateFromOnboarding(); setAutoPopulateAttempted(true); setIsAutoPopulating(false); + + console.log('โœ… useAutoPopulation: Auto-population triggered successfully'); + } else { + console.log('โธ๏ธ useAutoPopulation: Auto-population skipped', { + autoPopulateAttempted, + isAutoPopulating + }); } }, [autoPopulateAttempted, isAutoPopulating]); // Removed autoPopulateFromOnboarding from dependencies diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts index 300ed96a..631648db 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useCategoryReview.ts @@ -49,30 +49,18 @@ export const useCategoryReview = ({ completionStats, setError, setActiveCategory // Use the updated reviewedCategories state that includes the current category const updatedReviewedCategories = new Set([...Array.from(reviewedCategories), activeCategory]); - console.log('๐Ÿ” Navigation Debug:', { - activeCategory, - currentIndex, - allCategories, - reviewedCategories: Array.from(reviewedCategories), - updatedReviewedCategories: Array.from(updatedReviewedCategories) - }); - const nextUnreviewedCategory = allCategories.find((categoryId, index) => { if (index <= currentIndex) return false; return !updatedReviewedCategories.has(categoryId); }); - console.log('๐ŸŽฏ Next Category Found:', nextUnreviewedCategory); - if (nextUnreviewedCategory) { // Actually navigate to the next category - console.log('๐Ÿš€ Navigating to:', nextUnreviewedCategory); setActiveCategory(nextUnreviewedCategory); setCategoryCompletionMessage(`๐ŸŽฏ Moving to next category: ${nextUnreviewedCategory.split('_').map(word => word.charAt(0).toUpperCase() + word.slice(1) ).join(' ')}`); } else { - console.log('๐ŸŽ‰ All categories reviewed!'); setCategoryCompletionMessage('๐ŸŽ‰ All categories reviewed and confirmed! You can now create your strategy.'); } }, 1500); diff --git a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts index 12dd4faa..09f260ad 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts +++ b/frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder/hooks/useModalManagement.ts @@ -22,51 +22,42 @@ export const useModalManagement = ({ // Monitor aiGenerating state for debugging useEffect(() => { - console.log('๐ŸŽฏ useModalManagement: aiGenerating state changed:', aiGenerating); + // Removed verbose logging for cleaner console }, [aiGenerating]); // Handle proceed with current strategy (30 fields) const handleProceedWithCurrentStrategy = async () => { - console.log('๐ŸŽฏ User clicked "Proceed with Current Strategy"'); setShowEnterpriseModal(false); sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage // Add a small delay to ensure modal closes properly before showing educational modal setTimeout(async () => { - console.log('๐ŸŽฏ Calling original handleCreateStrategy after enterprise modal closes'); try { // Ensure we're not already generating if (!aiGenerating && originalHandleCreateStrategyRef.current) { - console.log('๐ŸŽฏ Starting strategy generation...'); await originalHandleCreateStrategyRef.current(); - } else { - console.log('๐ŸŽฏ Already generating, skipping duplicate call'); } } catch (error) { - console.error('๐ŸŽฏ Error in handleProceedWithCurrentStrategy:', error); + console.error('Error in handleProceedWithCurrentStrategy:', error); } }, 300); // Increased delay to ensure modal closes completely }; // Handle add enterprise datapoints (coming soon) const handleAddEnterpriseDatapoints = async () => { - console.log('๐ŸŽฏ User clicked "Add Enterprise Datapoints"'); setShowEnterpriseModal(false); sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage // For now, just proceed with current strategy // In Phase 2, this will enable enterprise datapoints setTimeout(async () => { - console.log('๐ŸŽฏ Calling original handleCreateStrategy for enterprise datapoints'); try { // Ensure we're not already generating if (!aiGenerating && originalHandleCreateStrategyRef.current) { await originalHandleCreateStrategyRef.current(); - } else { - console.log('๐ŸŽฏ Already generating, skipping duplicate call'); } } catch (error) { - console.error('๐ŸŽฏ Error in handleAddEnterpriseDatapoints:', error); + console.error('Error in handleAddEnterpriseDatapoints:', error); } }, 200); // Increased delay to ensure modal closes completely }; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx index 8228e56b..a335f71e 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyAutofillTransparencyModal.tsx @@ -91,19 +91,9 @@ const StrategyAutofillTransparencyModal: React.FC { - console.log('๐ŸŽฏ StrategyAutofillTransparencyModal Props:', { - open, - autoPopulatedFields: Object.keys(autoPopulatedFields || {}).length, - dataSources: Object.keys(dataSources || {}).length, - inputDataPoints: Object.keys(inputDataPoints || {}).length, - isGenerating, - generationProgress, - currentPhase, - transparencyMessages: transparencyMessages?.length, - error - }); + // Props monitoring removed for cleaner console }, [open, autoPopulatedFields, dataSources, inputDataPoints, isGenerating, generationProgress, currentPhase, transparencyMessages, error]); // Auto-scroll to bottom when new messages arrive diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx index 96f4631f..2fecc0c3 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/StrategyIntelligenceTab.tsx @@ -1,4 +1,4 @@ -import React, { useEffect } from 'react'; +import React, { useEffect, memo } from 'react'; import { Box, CircularProgress, Alert, Typography } from '@mui/material'; import StrategyHeader from './components/StrategyHeader'; import StrategicInsightsCard from './components/StrategicInsightsCard'; @@ -7,22 +7,33 @@ import PerformancePredictionsCard from './components/PerformancePredictionsCard' import ImplementationRoadmapCard from './components/ImplementationRoadmapCard'; import RiskAssessmentCard from './components/RiskAssessmentCard'; import ReviewProgressHeader from './components/ReviewProgressHeader'; +import StrategyErrorBoundary from './components/StrategyErrorBoundary'; import { StrategyData } from './types/strategy.types'; import { useStrategyReviewStore } from '../../../../stores/strategyReviewStore'; +import { hasValidData } from './utils/defensiveRendering'; +import { createSafeStrategyData, validateStrategyData } from './utils/strategyDataValidator'; interface StrategyIntelligenceTabProps { strategyData?: StrategyData | null; loading?: boolean; error?: string | null; + strategyStatus?: 'active' | 'inactive' | 'pending' | 'none'; } const StrategyIntelligenceTab: React.FC = ({ strategyData, loading = false, - error = null + error = null, + strategyStatus = 'none' }) => { - // Get review process state from store - const { reviewProcessStarted, startReviewProcess, components, initializeComponents } = useStrategyReviewStore(); + // Get review process state from store with selective subscription + const reviewProcessStarted = useStrategyReviewStore(state => state.reviewProcessStarted); + const startReviewProcess = useStrategyReviewStore(state => state.startReviewProcess); + const components = useStrategyReviewStore(state => state.components); + const initializeComponents = useStrategyReviewStore(state => state.initializeComponents); + const isAllReviewed = useStrategyReviewStore(state => state.isAllReviewed); + const isActivated = useStrategyReviewStore(state => state.isActivated); + const resetAllReviews = useStrategyReviewStore(state => state.resetAllReviews); // Initialize components if they don't exist useEffect(() => { @@ -59,7 +70,63 @@ const StrategyIntelligenceTab: React.FC = ({ } }, [components.length, initializeComponents]); + // Create safe strategy data for rendering + const safeStrategyData = createSafeStrategyData(strategyData); + + // Auto-start review process for pending strategies + useEffect(() => { + if (strategyStatus === 'pending' && !reviewProcessStarted && safeStrategyData) { + console.log('๐Ÿ”„ Auto-starting review process for pending strategy'); + console.log('๐Ÿ“‹ Review Process: Starting review workflow for newly generated strategy'); + console.log('๐ŸŽฏ Strategy Review Workflow: User should now see the review interface'); + startReviewProcess(); + } + }, [strategyStatus, safeStrategyData, startReviewProcess, reviewProcessStarted]); + + // Log review process state changes (only when important changes occur, with debounce) + useEffect(() => { + const timeoutId = setTimeout(() => { + if (reviewProcessStarted || strategyStatus === 'pending') { + console.log('๐Ÿ”„ Review Process State:', { + strategyStatus, + reviewProcessStarted, + hasStrategyData: !!safeStrategyData, + componentsCount: components.length, + isAllReviewed: isAllReviewed(), + isActivated: isActivated() + }); + } + }, 150); // 150ms debounce + + return () => clearTimeout(timeoutId); + }, [strategyStatus, reviewProcessStarted, safeStrategyData, components.length, isAllReviewed, isActivated]); + + // Log when review interface becomes visible + useEffect(() => { + if (reviewProcessStarted && safeStrategyData) { + console.log('๐ŸŽฏ RENDERING: Review interface is now visible to user'); + console.log('๐Ÿ“‹ Review Interface: User can now review and confirm the strategy'); + console.log('๐Ÿ“Š Current Strategy Data:', { + hasStrategicInsights: !!safeStrategyData.strategic_insights, + hasCompetitiveAnalysis: !!safeStrategyData.competitive_analysis, + hasPerformancePredictions: !!safeStrategyData.performance_predictions, + hasImplementationRoadmap: !!safeStrategyData.implementation_roadmap, + hasRiskAssessment: !!safeStrategyData.risk_assessment + }); + } + }, [reviewProcessStarted, safeStrategyData]); + + // Log when StrategyHeader is rendered with new data + useEffect(() => { + if (safeStrategyData) { + console.log('๐ŸŽฏ RENDERING: StrategyHeader with status:', strategyStatus, 'and confirmed:', strategyStatus === 'active'); + } + }, [strategyStatus, safeStrategyData]); + + + const handleStartReviewProcess = () => { + console.log('๐Ÿ”„ Manual review process started by user'); startReviewProcess(); }; @@ -79,7 +146,23 @@ const StrategyIntelligenceTab: React.FC = ({ ); } - if (!strategyData) { + // Validate strategy data before rendering + const hasValidStrategyData = (data: StrategyData | null): boolean => { + if (!data) return false; + + // Check if the data has meaningful content + const hasStrategicInsights = hasValidData(data.strategic_insights); + const hasCompetitiveAnalysis = hasValidData(data.competitive_analysis); + const hasPerformancePredictions = hasValidData(data.performance_predictions); + const hasImplementationRoadmap = hasValidData(data.implementation_roadmap); + const hasRiskAssessment = hasValidData(data.risk_assessment); + + // Return true if at least one component has valid data + return hasStrategicInsights || hasCompetitiveAnalysis || hasPerformancePredictions || + hasImplementationRoadmap || hasRiskAssessment; + }; + + if (!safeStrategyData || !hasValidStrategyData(safeStrategyData)) { return ( @@ -88,6 +171,11 @@ const StrategyIntelligenceTab: React.FC = ({ Generate a comprehensive strategy first to view strategic intelligence. + {strategyData && ( + + Strategy data exists but contains no valid components. + + )} ); } @@ -96,13 +184,14 @@ const StrategyIntelligenceTab: React.FC = ({ {/* Header Section */} {/* Review Progress Header - Only shown when review process is started */} - {reviewProcessStarted && } + {reviewProcessStarted && } {/* Strategy Intelligence Cards */} = ({ } }} > - - - - - + + + + + + + + + + + + + + + ); }; -export default StrategyIntelligenceTab; \ No newline at end of file +export default memo(StrategyIntelligenceTab); \ No newline at end of file diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx index 943bc624..acf7c04b 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/CompetitiveAnalysisCard.tsx @@ -32,6 +32,7 @@ import { getListItemStyles } from '../styles'; import ProgressiveCard from './ProgressiveCard'; +import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering'; interface CompetitiveAnalysisCardProps { strategyData: StrategyData | null; @@ -68,6 +69,8 @@ const CompetitiveAnalysisCard: React.FC = ({ strat return description.split(' ').slice(0, 2).join(' '); }; + + if (!strategyData?.competitive_analysis) { return ( = ({ strat }} /> = ({ strat }} /> = ({ strat }} /> = ({ strat opacity: 0.7 }} /> - + ))} @@ -433,7 +436,7 @@ const CompetitiveAnalysisCard: React.FC = ({ strat }} /> = ({ strat }} /> = ({ strat }} /> = ({ strat }} /> = ({ strat }} /> = ({ strat }} /> - + ))} diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx index d47fb4d5..f67ea504 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ImplementationRoadmapCard.tsx @@ -28,6 +28,7 @@ import { getListItemStyles } from '../styles'; import ProgressiveCard from './ProgressiveCard'; +import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering'; interface ImplementationRoadmapCardProps { strategyData: StrategyData | null; @@ -50,6 +51,15 @@ const ImplementationRoadmapCard: React.FC = ({ s return 'Budget allocation not specified'; }; + // Helper function to safely render text content + const safeRenderText = (content: any): string => { + if (typeof content === 'string') return content; + if (typeof content === 'object' && content !== null) { + return JSON.stringify(content); + } + return 'Data not available'; + }; + if (!strategyData?.implementation_roadmap) { return ( = ({ s }} /> = ({ s opacity: 0.7 }} /> - + ))} @@ -322,7 +332,7 @@ const ImplementationRoadmapCard: React.FC = ({ s = ({ s }} /> = ({ s = ({ s }} /> = ({ s }} /> = ({ s }} /> = ({ = ({ const sectionStyles = getSectionStyles(); const listItemStyles = getListItemStyles(); + // Helper function to safely render text content + const safeRenderText = (content: any): string => { + if (typeof content === 'string') return content; + if (typeof content === 'object' && content !== null) { + return JSON.stringify(content); + } + return 'Data not available'; + }; + if (!strategyData?.performance_predictions) { return ( = ({ const componentReviewedAt = component?.reviewedAt; // Debug logging for component status - if (componentId) { - console.log(`๐Ÿ”ง ProgressiveCard [${componentId}]:`, { - componentStatus, - componentReviewedAt, - allComponents: components.map(c => ({ id: c.id, status: c.status })) - }); - } + // Removed verbose logging for cleaner console // Handle hover interactions const handleMouseEnter = () => { diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx index ecf031d4..bc08b427 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewConfirmationDialog.tsx @@ -26,6 +26,7 @@ import { } from '@mui/icons-material'; import { motion } from 'framer-motion'; import { ANALYSIS_CARD_STYLES } from '../styles'; +import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering'; interface ReviewConfirmationDialogProps { open: boolean; @@ -204,7 +205,7 @@ const ReviewConfirmationDialog: React.FC = ({ }} /> = ({ strategyData }) => { - const { - components, - reviewProgress, - isAllReviewed, - isActivated, - resetAllReviews, - getUnreviewedComponents, - getReviewedComponents, - activateStrategy - } = useStrategyReviewStore(); + // Use selective store subscriptions to prevent unnecessary re-renders + const components = useStrategyReviewStore(state => state.components); + const reviewProgress = useStrategyReviewStore(state => state.reviewProgress); + const isAllReviewed = useStrategyReviewStore(state => state.isAllReviewed); + const isActivated = useStrategyReviewStore(state => state.isActivated); + const resetAllReviews = useStrategyReviewStore(state => state.resetAllReviews); + const getUnreviewedComponents = useStrategyReviewStore(state => state.getUnreviewedComponents); + const getReviewedComponents = useStrategyReviewStore(state => state.getReviewedComponents); + const activateStrategy = useStrategyReviewStore(state => state.activateStrategy); // Initialize navigation orchestrator const navigationOrchestrator = useNavigationOrchestrator(); @@ -56,17 +55,7 @@ const ReviewProgressHeader: React.FC = ({ strategyDat const reviewedCount = getReviewedComponents().length; const totalCount = components.length; - // Debug logging - console.log('๐Ÿ” ReviewProgressHeader Debug:', { - components, - reviewProgress, - unreviewedCount, - reviewedCount, - totalCount, - isAllReviewed: isAllReviewed(), - isActivated: isActivated(), - strategyData - }); + // Debug logging - removed for cleaner console const getProgressColor = () => { if (isActivated()) return ANALYSIS_CARD_STYLES.colors.success; @@ -98,7 +87,6 @@ const ReviewProgressHeader: React.FC = ({ strategyDat const handleConfirmStrategy = async () => { // This will be called by the enhanced button when activation is confirmed - console.log('๐ŸŽฏ Strategy activation confirmed'); // Activate the strategy in the store activateStrategy(); @@ -107,7 +95,6 @@ const ReviewProgressHeader: React.FC = ({ strategyDat }; const handleGenerateContentCalendar = () => { - console.log('๐ŸŽฏ Generate content calendar clicked'); // Prepare strategy context for navigation const strategyContext = { @@ -430,4 +417,4 @@ const ReviewProgressHeader: React.FC = ({ strategyDat ); }; -export default ReviewProgressHeader; +export default memo(ReviewProgressHeader); diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx new file mode 100644 index 00000000..bada296f --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewProgressSection.tsx @@ -0,0 +1,387 @@ +import React from 'react'; +import { + Box, + Typography, + Chip, + Tooltip, + CircularProgress, + Badge, + Button +} from '@mui/material'; +import { + CheckCircle as CheckCircleIcon, + Schedule as ScheduleIcon, + AutoAwesome as AutoAwesomeIcon +} from '@mui/icons-material'; +import { motion } from 'framer-motion'; +import { StrategyData } from '../types/strategy.types'; +import { useStrategyReviewStore, StrategyComponent } from '../../../../../stores/strategyReviewStore'; +import { ANALYSIS_CARD_STYLES } from '../styles'; +import EnhancedStrategyActivationButton from './EnhancedStrategyActivationButton'; +import { useNavigationOrchestrator } from '../../../../../services/navigationOrchestrator'; + +interface ReviewProgressSectionProps { + strategyData: StrategyData; +} + +const ReviewProgressSection: React.FC = ({ strategyData }) => { + const { + components, + reviewProgress, + isAllReviewed, + isActivated, + resetAllReviews, + getUnreviewedComponents, + getReviewedComponents, + activateStrategy + } = useStrategyReviewStore(); + + // Initialize navigation orchestrator + const navigationOrchestrator = useNavigationOrchestrator(); + + // Extract domain name from strategy data + const getDomainName = () => { + // Since StrategyMetadata doesn't have domain, we'll use a fallback + return "alwrity.com"; // fallback + }; + + const unreviewedCount = getUnreviewedComponents().length; + const reviewedCount = getReviewedComponents().length; + const totalCount = components.length; + + // Debug logging + console.log('๐Ÿ” ReviewProgressSection Debug:', { + components, + reviewProgress, + unreviewedCount, + reviewedCount, + totalCount, + isAllReviewed: isAllReviewed(), + isActivated: isActivated(), + strategyData + }); + + const getProgressColor = () => { + if (isActivated()) return ANALYSIS_CARD_STYLES.colors.success; + if (reviewProgress === 100) return ANALYSIS_CARD_STYLES.colors.success; + if (reviewProgress >= 60) return ANALYSIS_CARD_STYLES.colors.primary; + if (reviewProgress >= 30) return ANALYSIS_CARD_STYLES.colors.warning; + return ANALYSIS_CARD_STYLES.colors.error; + }; + + const getProgressText = () => { + if (isActivated()) return 'Strategy Active & Monitored!'; + if (reviewProgress === 100) return 'All components reviewed!'; + if (reviewProgress >= 60) return 'Great progress!'; + if (reviewProgress >= 30) return 'Making good progress'; + return 'Getting started'; + }; + + // Prepare strategy data for the enhanced button + const buttonStrategyData = strategyData ? { + id: strategyData.strategy_metadata?.user_id || strategyData.metadata?.user_id || 1, + business_name: strategyData.strategy_metadata?.strategy_name || strategyData.metadata?.strategy_name || "ALwrity", + domain: getDomainName(), + // Add other strategy data as needed + } : { + id: 1, + business_name: "ALwrity", + domain: getDomainName(), + }; + + const handleConfirmStrategy = async () => { + // This will be called by the enhanced button when activation is confirmed + console.log('๐ŸŽฏ Strategy activation confirmed'); + + // Activate the strategy in the store + activateStrategy(); + + // You can add additional logic here if needed + }; + + const handleGenerateContentCalendar = () => { + console.log('๐ŸŽฏ Generate content calendar clicked'); + + // Prepare strategy context for navigation + const strategyContext = { + strategyId: (strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id || '1').toString(), + strategyData: strategyData, + activationStatus: 'active' as const, + activationTimestamp: new Date().toISOString(), + userPreferences: {}, + strategicIntelligence: {} + }; + + // Navigate to calendar wizard using navigation orchestrator + navigationOrchestrator.navigateToCalendarWizard( + strategyContext.strategyId, + strategyContext + ); + }; + + return ( + + {/* Header Section */} + + + + Strategy Review Progress + + + Review all strategy components to activate your content strategy + + + + {/* Progress Circle */} + + + + + + {reviewProgress}% + + + + + {/* Progress Text */} + + + {getProgressText()} + + + {reviewedCount} of {totalCount} reviewed + + + + + + {/* Component Status */} + + + Component Status: + + + {components.map((component: StrategyComponent) => ( + + + ) : ( + + ) + } + color={component.status === 'reviewed' ? 'success' : 'warning'} + > + + + + ))} + + + + {/* Completion Status */} + {isAllReviewed() && ( + + } + label={isActivated() ? "Strategy Active & Monitored" : "Ready for Calendar Creation"} + size="small" + sx={{ + background: ANALYSIS_CARD_STYLES.colors.success, + color: 'white', + fontWeight: 500, + animation: 'pulse 2s infinite', + fontSize: '0.65rem', + height: 22, + '@keyframes pulse': { + '0%, 100%': { opacity: 1 }, + '50%': { opacity: 0.7 } + }, + '& .MuiChip-icon': { + color: 'white', + fontSize: 14 + } + }} + /> + + )} + + {/* Completion Message */} + {isAllReviewed() && ( + + + + {isActivated() + ? '๐ŸŽ‰ Your content strategy is now active and being monitored! AI-powered insights and performance tracking are now live.' + : '๐ŸŽ‰ All strategy components have been reviewed! You can now proceed to create your content calendar.' + } + + + + )} + + {/* Enhanced Strategy Activation Button - Only shown when all components are reviewed and not yet activated */} + {isAllReviewed() && !isActivated() && ( + + + + + + )} + + {/* Strategy Activated Success Message */} + {isActivated() && ( + + + + ๐Ÿš€ Strategy Successfully Activated! + + + Your content strategy is now live and being monitored with AI-powered analytics. + + + + + )} + + ); +}; + +export default ReviewProgressSection; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx index e9ee8607..6e582e4c 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/ReviewStatusIndicator.tsx @@ -37,7 +37,7 @@ const ReviewStatusIndicator: React.FC = ({ isReviewing = false }) => { // Debug logging for status - console.log('๐Ÿ”ง ReviewStatusIndicator received status:', status); + // Removed verbose logging for cleaner console const getStatusConfig = () => { switch (status) { case 'activated': diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx index 1adcd201..348d06a4 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/RiskAssessmentCard.tsx @@ -29,6 +29,7 @@ import { getListItemStyles } from '../styles'; import ProgressiveCard from './ProgressiveCard'; +import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering'; interface RiskAssessmentCardProps { strategyData: StrategyData | null; @@ -51,6 +52,15 @@ const RiskAssessmentCard: React.FC = ({ strategyData }) return ANALYSIS_CARD_STYLES.colors.info; }; + // Helper function to safely render text content + const safeRenderText = (content: any): string => { + if (typeof content === 'string') return content; + if (typeof content === 'object' && content !== null) { + return JSON.stringify(content); + } + return 'Data not available'; + }; + if (!strategyData?.risk_assessment) { return ( = ({ strategyData }) - + ))} @@ -345,14 +355,14 @@ const RiskAssessmentCard: React.FC = ({ strategyData }) opacity: 0.7 }} /> - + ))} diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx index 382a6d3d..68e5b44b 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategicInsightsCard.tsx @@ -29,6 +29,7 @@ import { getListItemStyles } from '../styles'; import ProgressiveCard from './ProgressiveCard'; +import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering'; interface StrategicInsightsCardProps { strategyData: StrategyData | null; @@ -80,6 +81,8 @@ const StrategicInsightsCard: React.FC = ({ strategyD } }; + + // Summary content - always visible const summaryContent = ( @@ -236,7 +239,7 @@ const StrategicInsightsCard: React.FC = ({ strategyD }} /> = ({ strategyD }} /> = ({ strategyD }} /> = ({ strategyD }} /> = ({ strategyD }} /> = ({ strategyD opacity: 0.7 }} /> - = ({ strategyD = ({ strategyD ).chip} /> = ({ strategyD ).chip} /> = ({ strategyD ).chip} /> - {insight.reasoning && ( + {hasValidData(insight.reasoning) && ( - Reasoning: {insight.reasoning} + Reasoning: {safeRenderText(insight.reasoning)} )} @@ -562,7 +565,7 @@ const StrategicInsightsCard: React.FC = ({ strategyD }} /> { + constructor(props: Props) { + super(props); + this.state = { hasError: false }; + } + + static getDerivedStateFromError(error: Error): State { + return { hasError: true, error }; + } + + componentDidCatch(error: Error, errorInfo: ErrorInfo) { + console.error('Strategy component error:', error, errorInfo); + } + + handleRetry = () => { + this.setState({ hasError: false, error: undefined }); + }; + + render() { + if (this.state.hasError) { + if (this.props.fallback) { + return this.props.fallback; + } + + return ( + + + + Error rendering strategy component + + + {this.state.error?.message || 'Unknown error occurred'} + + + + + ); + } + + return this.props.children; + } +} + +export default StrategyErrorBoundary; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx index de458be5..b96ff663 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/components/StrategyHeader.tsx @@ -42,10 +42,12 @@ import { getStrategyName, getStrategyGenerationDate } from '../utils/strategyTra interface StrategyHeaderProps { strategyData: StrategyData | null; strategyConfirmed: boolean; + strategyStatus?: 'active' | 'inactive' | 'pending' | 'none'; onStartReview?: () => void; + disableCardWrapper?: boolean; // New prop to control Card wrapper rendering } -const StrategyHeader: React.FC = ({ strategyData, strategyConfirmed, onStartReview }) => { +const StrategyHeader: React.FC = ({ strategyData, strategyConfirmed, strategyStatus = 'none', onStartReview, disableCardWrapper = false }) => { const [showNextStepText, setShowNextStepText] = useState(false); if (!strategyData) return null; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts new file mode 100644 index 00000000..56952745 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/defensiveRendering.ts @@ -0,0 +1,101 @@ +// Utility functions for defensive rendering in strategy components + +/** + * Safely renders text content, handling objects, arrays, and other non-string types + */ +export const safeRenderText = (content: any): string => { + if (typeof content === 'string') return content; + if (typeof content === 'number') return content.toString(); + if (typeof content === 'boolean') return content.toString(); + if (Array.isArray(content)) return content.join(', '); + if (typeof content === 'object' && content !== null) { + // If it's an empty object, return a fallback message + if (Object.keys(content).length === 0) { + return 'Data not available'; + } + // If it has a meaningful structure, try to extract useful information + if (content.title) return content.title; + if (content.name) return content.name; + if (content.description) return content.description; + if (content.text) return content.text; + // As a last resort, stringify the object + return JSON.stringify(content); + } + return 'Data not available'; +}; + +/** + * Safely renders array content, ensuring it's always an array + */ +export const safeRenderArray = (content: any): any[] => { + if (Array.isArray(content)) return content; + if (typeof content === 'string') return [content]; + if (typeof content === 'object' && content !== null) { + // If it's an empty object, return empty array + if (Object.keys(content).length === 0) { + return []; + } + // Try to extract array-like data + if (content.items && Array.isArray(content.items)) return content.items; + if (content.data && Array.isArray(content.data)) return content.data; + if (content.list && Array.isArray(content.list)) return content.list; + // As a last resort, return the object as a single item + return [content]; + } + return []; +}; + +/** + * Safely renders object content, ensuring it's always a valid object + */ +export const safeRenderObject = (content: any): Record => { + if (typeof content === 'object' && content !== null) { + return content; + } + return {}; +}; + +/** + * Validates if a strategy data field contains meaningful data + */ +export const hasValidData = (content: any): boolean => { + if (content === null || content === undefined) return false; + if (typeof content === 'string') return content.trim().length > 0; + if (Array.isArray(content)) return content.length > 0; + if (typeof content === 'object') { + // Check if it's an empty object + if (Object.keys(content).length === 0) return false; + // Check if it has meaningful properties + return Object.values(content).some(value => hasValidData(value)); + } + return true; +}; + +/** + * Gets a fallback value for empty or invalid data + */ +export const getFallbackValue = (fieldName: string): string => { + const fallbacks: Record = { + insight: 'Insight data not available', + recommendation: 'Recommendation data not available', + prediction: 'Prediction data not available', + analysis: 'Analysis data not available', + strategy: 'Strategy data not available', + risk: 'Risk data not available', + opportunity: 'Opportunity data not available', + strength: 'Strength data not available', + weakness: 'Weakness data not available', + threat: 'Threat data not available', + milestone: 'Milestone data not available', + task: 'Task data not available', + resource: 'Resource data not available', + requirement: 'Requirement data not available', + metric: 'Metric data not available', + gap: 'Gap data not available', + advantage: 'Advantage data not available', + area: 'Area data not available', + path: 'Path data not available' + }; + + return fallbacks[fieldName.toLowerCase()] || 'Data not available'; +}; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts new file mode 100644 index 00000000..cf85f464 --- /dev/null +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyIntelligence/utils/strategyDataValidator.ts @@ -0,0 +1,167 @@ +import { StrategyData } from '../types/strategy.types'; +import { hasValidData } from './defensiveRendering'; + +/** + * Validates strategy data to ensure it's safe for rendering + */ +export const validateStrategyData = (data: StrategyData | null | undefined): { + isValid: boolean; + errors: string[]; + warnings: string[]; +} => { + const errors: string[] = []; + const warnings: string[] = []; + + if (!data) { + return { + isValid: false, + errors: ['Strategy data is null or undefined'], + warnings: [] + }; + } + + // Check for empty objects that could cause rendering issues + const checkForEmptyObjects = (obj: any, path: string): void => { + if (typeof obj === 'object' && obj !== null) { + if (Array.isArray(obj)) { + obj.forEach((item, index) => { + checkForEmptyObjects(item, `${path}[${index}]`); + }); + } else { + const keys = Object.keys(obj); + if (keys.length === 0) { + warnings.push(`Empty object found at ${path}`); + } else { + keys.forEach(key => { + checkForEmptyObjects(obj[key], `${path}.${key}`); + }); + } + } + } + }; + + // Check each major component + const components = [ + { name: 'strategic_insights', data: data.strategic_insights }, + { name: 'competitive_analysis', data: data.competitive_analysis }, + { name: 'performance_predictions', data: data.performance_predictions }, + { name: 'implementation_roadmap', data: data.implementation_roadmap }, + { name: 'risk_assessment', data: data.risk_assessment } + ]; + + components.forEach(({ name, data: componentData }) => { + if (componentData) { + checkForEmptyObjects(componentData, name); + + // Check if the component has meaningful data + if (!hasValidData(componentData)) { + warnings.push(`${name} component exists but contains no meaningful data`); + } + } else { + warnings.push(`${name} component is missing`); + } + }); + + // Check metadata + if (data.strategy_metadata) { + checkForEmptyObjects(data.strategy_metadata, 'strategy_metadata'); + } + + if (data.metadata) { + checkForEmptyObjects(data.metadata, 'metadata'); + } + + // Check summary + if (data.summary) { + checkForEmptyObjects(data.summary, 'summary'); + } + + return { + isValid: errors.length === 0, + errors, + warnings + }; +}; + +/** + * Sanitizes strategy data to prevent rendering errors + */ +export const sanitizeStrategyData = (data: StrategyData | null | undefined): StrategyData | null => { + if (!data) return null; + + const sanitizeValue = (value: any): any => { + if (value === null || value === undefined) { + return null; + } + + if (typeof value === 'object') { + if (Array.isArray(value)) { + return value.map(sanitizeValue).filter(v => v !== null); + } else { + const keys = Object.keys(value); + if (keys.length === 0) { + return null; // Remove empty objects + } + + const sanitized: any = {}; + keys.forEach(key => { + const sanitizedValue = sanitizeValue(value[key]); + if (sanitizedValue !== null) { + sanitized[key] = sanitizedValue; + } + }); + + return Object.keys(sanitized).length > 0 ? sanitized : null; + } + } + + return value; + }; + + // Create a deep copy and sanitize + const sanitized = JSON.parse(JSON.stringify(data)); + + // Sanitize each component + if (sanitized.strategic_insights) { + sanitized.strategic_insights = sanitizeValue(sanitized.strategic_insights); + } + + if (sanitized.competitive_analysis) { + sanitized.competitive_analysis = sanitizeValue(sanitized.competitive_analysis); + } + + if (sanitized.performance_predictions) { + sanitized.performance_predictions = sanitizeValue(sanitized.performance_predictions); + } + + if (sanitized.implementation_roadmap) { + sanitized.implementation_roadmap = sanitizeValue(sanitized.implementation_roadmap); + } + + if (sanitized.risk_assessment) { + sanitized.risk_assessment = sanitizeValue(sanitized.risk_assessment); + } + + return sanitized; +}; + +/** + * Creates a safe strategy data object for rendering + */ +export const createSafeStrategyData = (data: StrategyData | null | undefined): StrategyData | null => { + if (!data) return null; + + // First validate the data + const validation = validateStrategyData(data); + + if (validation.errors.length > 0) { + console.warn('Strategy data validation errors:', validation.errors); + } + + if (validation.warnings.length > 0) { + console.warn('Strategy data validation warnings:', validation.warnings); + } + + // Then sanitize the data + return sanitizeStrategyData(data); +}; diff --git a/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx index c86c347e..f33ed143 100644 --- a/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx +++ b/frontend/src/components/ContentPlanningDashboard/components/StrategyOnboardingDialog.tsx @@ -54,7 +54,7 @@ interface StrategyOnboardingDialogProps { onEditStrategy: () => void; onCreateNewStrategy: () => void; currentStrategy: any; - strategyStatus: 'active' | 'inactive' | 'none'; + strategyStatus: 'active' | 'inactive' | 'pending' | 'none'; } const StrategyOnboardingDialog: React.FC = ({ diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx index 35bd6066..8a84f27a 100644 --- a/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/tabs/ContentStrategyTab.tsx @@ -1,4 +1,4 @@ -import React, { useState, useEffect } from 'react'; +import React, { useState, useEffect, useMemo } from 'react'; import { Box, Paper, @@ -12,6 +12,7 @@ import { AutoAwesome as AutoAwesomeIcon, Edit as EditIcon } from '@mui/icons-material'; +import { useLocation } from 'react-router-dom'; import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; import { contentPlanningApi } from '../../../services/contentPlanningApi'; import StrategyIntelligenceTab from '../components/StrategyIntelligence/StrategyIntelligenceTab'; @@ -19,17 +20,19 @@ import StrategyOnboardingDialog from '../components/StrategyOnboardingDialog'; import { StrategyData } from '../components/StrategyIntelligence/types/strategy.types'; const ContentStrategyTab: React.FC = () => { - const { - strategies, - currentStrategy, - aiInsights, - aiRecommendations, - loading, - error, - loadStrategies, - loadAIInsights, - loadAIRecommendations - } = useContentPlanningStore(); + const location = useLocation(); + // Use selective store subscriptions to prevent unnecessary re-renders + const strategies = useContentPlanningStore(state => state.strategies); + const currentStrategy = useContentPlanningStore(state => state.currentStrategy); + const latestGeneratedStrategy = useContentPlanningStore(state => state.latestGeneratedStrategy); + const aiInsights = useContentPlanningStore(state => state.aiInsights); + const aiRecommendations = useContentPlanningStore(state => state.aiRecommendations); + const loading = useContentPlanningStore(state => state.loading); + const error = useContentPlanningStore(state => state.error); + const loadStrategies = useContentPlanningStore(state => state.loadStrategies); + const loadAIInsights = useContentPlanningStore(state => state.loadAIInsights); + const loadAIRecommendations = useContentPlanningStore(state => state.loadAIRecommendations); + const setLatestGeneratedStrategy = useContentPlanningStore(state => state.setLatestGeneratedStrategy); const [strategyForm, setStrategyForm] = useState({ name: '', @@ -52,23 +55,61 @@ const ContentStrategyTab: React.FC = () => { }); // Strategy status and onboarding - const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'none'>('none'); + const [strategyStatus, setStrategyStatus] = useState<'active' | 'inactive' | 'pending' | 'none'>('none'); const [showOnboarding, setShowOnboarding] = useState(false); const [hasCheckedStrategy, setHasCheckedStrategy] = useState(false); + // Navigation state detection + const [isFromStrategyBuilder, setIsFromStrategyBuilder] = useState(false); + // Load data on component mount useEffect(() => { loadInitialData(); }, []); + // Check if coming from strategy builder + useEffect(() => { + const locationState = location.state as any; + const isFromBuilder = locationState?.fromStrategyBuilder || + locationState?.activeTab === 0 || // Content Strategy tab + sessionStorage.getItem('fromStrategyBuilder') === 'true'; + + console.log('๐Ÿ” ContentStrategyTab: Navigation state check:', { + locationState, + isFromBuilder, + sessionStorage: sessionStorage.getItem('fromStrategyBuilder') + }); + + setIsFromStrategyBuilder(isFromBuilder); + + // Clear the session storage flag after reading it + if (sessionStorage.getItem('fromStrategyBuilder') === 'true') { + sessionStorage.removeItem('fromStrategyBuilder'); + } + + // Clear the cache when navigating away from strategy builder + if (!isFromBuilder && latestGeneratedStrategy) { + console.log('๐Ÿงน Clearing latest generated strategy cache (navigating away from strategy builder)'); + // Note: We don't clear the cache here as it might be needed for the current session + } + }, [location.state]); + + // Track strategy status changes for debugging (with debounce) + useEffect(() => { + const timeoutId = setTimeout(() => { + console.log('๐Ÿ”„ Strategy Status Changed:', { + status: strategyStatus, + hasStrategyData: !!strategyData, + strategyDataKeys: strategyData ? Object.keys(strategyData) : [], + isFromStrategyBuilder + }); + }, 100); // 100ms debounce + + return () => clearTimeout(timeoutId); + }, [strategyStatus, strategyData, isFromStrategyBuilder]); + // Check strategy status when strategies are loaded useEffect(() => { - console.log('๐Ÿ”„ useEffect triggered - strategies changed:', strategies); - console.log('๐Ÿ”„ Strategies type:', typeof strategies); - console.log('๐Ÿ”„ Is Array:', Array.isArray(strategies)); - console.log('๐Ÿ”„ Strategies length:', strategies?.length); - console.log('๐Ÿ”„ Has checked strategy:', hasCheckedStrategy); - // Handle different response formats let strategiesArray: any[] = []; @@ -80,64 +121,237 @@ const ContentStrategyTab: React.FC = () => { strategiesArray = (strategies as any).strategies; } - console.log('๐Ÿ”„ StrategiesArray length:', strategiesArray.length); - if (strategiesArray.length > 0) { - console.log('โœ… Strategies found, checking status...'); checkStrategyStatus(); - loadStrategyData(); + + // Add debounce to prevent rapid successive calls + const timeoutId = setTimeout(() => { + loadStrategyData(); + }, 500); // 500ms debounce + + return () => clearTimeout(timeoutId); } else if (strategiesArray.length === 0 && hasCheckedStrategy) { // Only set to 'none' if we've already checked and confirmed no strategies - console.log('โŒ No strategies found, setting status to none...'); setStrategyStatus('none'); setShowOnboarding(true); } // If strategiesArray.length === 0 and !hasCheckedStrategy, do nothing (wait for data to load) - }, [strategies, loadStrategies]); + }, [strategies, loadStrategies, isFromStrategyBuilder]); const loadStrategyData = async () => { + // Prevent multiple simultaneous requests + if (strategyDataLoading) { + console.log('๐Ÿ”„ Strategy data loading already in progress, skipping...'); + return; + } + try { setStrategyDataLoading(true); setStrategyDataError(null); const userId = 1; // Default user ID - // Try to get the latest generated strategy + // PRIORITY 0: Check cache first for latest generated strategy + console.log('๐Ÿ” PRIORITY 0: Checking cache for latest generated strategy...'); + console.log('๐Ÿ” Cache state:', { + hasCache: !!latestGeneratedStrategy, + cacheData: latestGeneratedStrategy ? { + hasStrategicInsights: !!latestGeneratedStrategy.strategic_insights, + hasCompetitiveAnalysis: !!latestGeneratedStrategy.competitive_analysis, + hasPerformancePredictions: !!latestGeneratedStrategy.performance_predictions, + hasImplementationRoadmap: !!latestGeneratedStrategy.implementation_roadmap, + hasRiskAssessment: !!latestGeneratedStrategy.risk_assessment + } : null + }); + if (latestGeneratedStrategy && latestGeneratedStrategy.strategic_insights) { + console.log('๐Ÿš€ PRIORITY 0: Found latest generated strategy in cache!'); + console.log('๐Ÿ“Š Cached strategy data structure:', { + hasStrategicInsights: !!latestGeneratedStrategy.strategic_insights, + hasCompetitiveAnalysis: !!latestGeneratedStrategy.competitive_analysis, + hasPerformancePredictions: !!latestGeneratedStrategy.performance_predictions, + hasImplementationRoadmap: !!latestGeneratedStrategy.implementation_roadmap, + hasRiskAssessment: !!latestGeneratedStrategy.risk_assessment, + strategyId: latestGeneratedStrategy.id, + strategyName: latestGeneratedStrategy.name + }); + setStrategyData(latestGeneratedStrategy); + + // Set strategy status to pending for newly generated strategy (needs review) + setStrategyStatus('pending'); + setShowOnboarding(false); + console.log('๐Ÿ“‹ Set strategy status to pending for cached strategy (needs review)'); + console.log('๐Ÿ”„ Strategy Review Workflow: New strategy ready for review - User should see review process'); + + return; + } else { + console.log('โŒ PRIORITY 0: No strategy found in cache, proceeding to next priority...'); + } + + // PRIORITY 1: If coming from strategy builder, prioritize the latest generated strategy + if (isFromStrategyBuilder) { + // Try with exponential backoff to handle rate limits + for (let attempt = 1; attempt <= 2; attempt++) { + try { + console.log(`๐Ÿ” PRIORITY 1 (Attempt ${attempt}/2): Trying to get latest generated strategy from polling system...`); + + // Add exponential backoff delay for subsequent attempts + if (attempt > 1) { + const delay = Math.pow(2, attempt) * 1000; // 2s, 4s delays + console.log(`โณ Waiting ${delay}ms before retry...`); + await new Promise(resolve => setTimeout(resolve, delay)); + } + + const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategyWithRetry(userId); + + console.log(`๐Ÿ” Latest strategy response (attempt ${attempt}):`, latestStrategyResponse); + + if (latestStrategyResponse && latestStrategyResponse.strategic_insights) { + console.log('โœ… Found latest generated strategy in polling system'); + console.log('๐Ÿ“Š Latest strategy data structure:', { + hasStrategicInsights: !!latestStrategyResponse.strategic_insights, + hasCompetitiveAnalysis: !!latestStrategyResponse.competitive_analysis, + hasPerformancePredictions: !!latestStrategyResponse.performance_predictions, + hasImplementationRoadmap: !!latestStrategyResponse.implementation_roadmap, + hasRiskAssessment: !!latestStrategyResponse.risk_assessment, + strategyId: latestStrategyResponse.id, + strategyName: latestStrategyResponse.name + }); + setStrategyData(latestStrategyResponse); + + // Cache the strategy since it wasn't cached during generation + console.log('๐Ÿ’พ Caching strategy from polling system since it was not cached during generation'); + setLatestGeneratedStrategy(latestStrategyResponse); + + // Set strategy status to pending for newly generated strategy (needs review) + setStrategyStatus('pending'); + setShowOnboarding(false); + console.log('๐Ÿ“‹ Set strategy status to pending for polling system strategy (needs review)'); + + return; + } else { + console.log(`โš ๏ธ Latest strategy response missing strategic_insights (attempt ${attempt}), trying database...`); + console.log('๐Ÿ” Latest strategy response structure:', latestStrategyResponse); + } + } catch (pollingError: any) { + console.log(`โš ๏ธ Error getting latest strategy from polling system (attempt ${attempt}):`, pollingError); + + // Check if it's a rate limit error + if (pollingError?.response?.status === 429) { + console.log('๐Ÿšซ Rate limit hit, will retry with longer delay...'); + if (attempt === 2) { + console.log('โŒ Rate limit persists, skipping polling system and trying database...'); + break; // Exit the loop and try database instead + } + } else if (attempt === 2) { + console.log('โŒ All attempts failed, continuing to database fallback...'); + } + } + } + } + + // PRIORITY 2: Try to get the latest generated strategy from polling system try { const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategy(userId); - console.log('๐Ÿ” Latest strategy response from API:', latestStrategyResponse); - if (latestStrategyResponse && latestStrategyResponse.strategic_insights) { - console.log('โœ… Found latest generated strategy:', latestStrategyResponse); setStrategyData(latestStrategyResponse); + + // Cache the strategy since it wasn't cached during generation + console.log('๐Ÿ’พ Caching strategy from general polling since it was not cached during generation'); + setLatestGeneratedStrategy(latestStrategyResponse); + + // Set strategy status to pending for newly generated strategy (needs review) + setStrategyStatus('pending'); + setShowOnboarding(false); + console.log('๐Ÿ“‹ Set strategy status to pending for general polling strategy (needs review)'); + return; } } catch (pollingError) { - console.log('No latest strategy found in polling system, checking database...', pollingError); + // Continue to next priority } - // If no strategy found in polling system, try to get from database + // PRIORITY 3: If no strategy found in polling system, try to get from database + console.log('๐ŸŽฏ PRIORITY 3: Checking database for strategies...'); try { const strategiesResponse = await contentPlanningApi.getEnhancedStrategies(userId); const strategies = strategiesResponse?.data?.strategies || strategiesResponse?.strategies || []; if (strategies && strategies.length > 0) { - const latestStrategy = strategies[0]; + console.log('๐Ÿ” Found strategies in database:', strategies.length); + + // Sort strategies by creation date (newest first) to ensure we get the latest + const sortedStrategies = strategies.sort((a: any, b: any) => { + const dateA = new Date(a.created_at || a.createdAt || 0); + const dateB = new Date(b.created_at || b.createdAt || 0); + return dateB.getTime() - dateA.getTime(); // Descending order (newest first) + }); + + console.log('๐Ÿ” Sorted strategies by creation date:', sortedStrategies.map((s: any) => ({ + id: s.id, + name: s.name, + created_at: s.created_at || s.createdAt, + has_comprehensive_analysis: !!s.comprehensive_ai_analysis, + has_ai_recommendations: !!s.ai_recommendations + }))); + + // If coming from strategy builder, prioritize strategies with comprehensive_ai_analysis + if (isFromStrategyBuilder) { + const latestComprehensiveStrategy = sortedStrategies.find((s: any) => s.comprehensive_ai_analysis); + if (latestComprehensiveStrategy) { + console.log('โœ… Found latest comprehensive strategy in database:', latestComprehensiveStrategy.id); + setStrategyData(latestComprehensiveStrategy.comprehensive_ai_analysis); + + // Set strategy status to active for existing database strategy + setStrategyStatus('active'); + setShowOnboarding(false); + console.log('โœ… Set strategy status to active for database comprehensive strategy'); + + return; + } + + // Fallback to ai_recommendations if comprehensive_ai_analysis not available + const latestWithRecommendations = sortedStrategies.find((s: any) => s.ai_recommendations); + if (latestWithRecommendations) { + console.log('โœ… Found latest strategy with ai_recommendations in database:', latestWithRecommendations.id); + setStrategyData(latestWithRecommendations.ai_recommendations); + + // Set strategy status to active for existing database strategy + setStrategyStatus('active'); + setShowOnboarding(false); + console.log('โœ… Set strategy status to active for database recommendations strategy'); + + return; + } + } + + // Get the most recent strategy (first after sorting) + const latestStrategy = sortedStrategies[0]; if (latestStrategy.comprehensive_ai_analysis) { - console.log('โœ… Found comprehensive strategy in database:', latestStrategy); setStrategyData(latestStrategy.comprehensive_ai_analysis); + + // Set strategy status to active for existing database strategy + setStrategyStatus('active'); + setShowOnboarding(false); + console.log('โœ… Set strategy status to active for database latest strategy'); + return; } } - } catch (dbError) { - console.log('No comprehensive strategies found in database:', dbError); + } catch (dbError: any) { + console.error('Database error:', dbError); + + // Check if it's a rate limit error + if (dbError?.response?.status === 429) { + console.log('๐Ÿšซ Database request also hit rate limit, showing error to user...'); + setStrategyDataError('Server is temporarily overloaded. Please try again in a few moments.'); + return; + } } // If no strategy data is available - console.log('โŒ No comprehensive strategy data found'); setStrategyData(null); setStrategyDataError('No comprehensive strategy data available. Please generate a strategy first.'); @@ -150,13 +364,20 @@ const ContentStrategyTab: React.FC = () => { } }; + // Add a timeout to prevent infinite loading + useEffect(() => { + if (strategyDataLoading) { + const timeout = setTimeout(() => { + console.log('โฐ Strategy data loading timeout, resetting state...'); + setStrategyDataLoading(false); + setStrategyDataError('Loading timeout. Please refresh the page.'); + }, 30000); // 30 second timeout + + return () => clearTimeout(timeout); + } + }, [strategyDataLoading]); + const checkStrategyStatus = () => { - console.log('๐Ÿ” Checking strategy status...'); - console.log('๐Ÿ” Strategies from store:', strategies); - console.log('๐Ÿ” Strategies type:', typeof strategies); - console.log('๐Ÿ” Is Array:', Array.isArray(strategies)); - console.log('๐Ÿ” Strategies length:', strategies?.length); - // Handle different response formats let strategiesArray: any[] = []; @@ -168,21 +389,15 @@ const ContentStrategyTab: React.FC = () => { strategiesArray = (strategies as any).strategies; } - console.log('๐Ÿ” StrategiesArray length:', strategiesArray.length); - if (strategiesArray.length > 0) { // Find the most recent strategy const latestStrategy = strategiesArray[0]; // Assuming strategies are sorted by date - console.log('โœ… Found strategies in database:', strategiesArray.length); - console.log('๐Ÿ“Š Latest strategy:', latestStrategy); - // 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 { - console.log('โŒ No strategies found in database'); setStrategyStatus('none'); setShowOnboarding(true); } @@ -193,14 +408,30 @@ const ContentStrategyTab: React.FC = () => { try { setDataLoading({ strategies: true, insights: true, recommendations: true, strategicIntelligence: true }); - // Load strategies + // Load strategies first (most important) + console.log('๐Ÿ”„ Loading strategies...'); await loadStrategies(); - // Load AI insights and recommendations - await Promise.all([ - loadAIInsights(), - loadAIRecommendations() - ]); + // Add delay between requests to avoid rate limits + await new Promise(resolve => setTimeout(resolve, 1000)); + + // Load AI insights and recommendations sequentially instead of in parallel + console.log('๐Ÿ”„ Loading AI insights...'); + try { + await loadAIInsights(); + } catch (error) { + console.warn('โš ๏ธ Failed to load AI insights:', error); + } + + // Add delay between requests + await new Promise(resolve => setTimeout(resolve, 1000)); + + console.log('๐Ÿ”„ Loading AI recommendations...'); + try { + await loadAIRecommendations(); + } catch (error) { + console.warn('โš ๏ธ Failed to load AI recommendations:', error); + } } catch (error) { console.error('Error loading initial data:', error); @@ -245,6 +476,28 @@ const ContentStrategyTab: React.FC = () => { )} + {/* Pending Strategy Status Banner */} + {strategyStatus === 'pending' && ( + setShowOnboarding(true)} + startIcon={} + > + Review & Activate + + } + > + + Strategy Ready for Review: Your AI-generated content strategy is ready! Please review all components and confirm to activate your strategy. + + + )} + {/* Strategy Status Banner */} {strategyStatus === 'inactive' && ( { )} - {/* Strategic Intelligence - Only show if there's an active strategy */} - {strategyStatus === 'active' && ( + {/* Strategic Intelligence - Show for both active and pending strategies */} + {(strategyStatus === 'active' || strategyStatus === 'pending') && ( - + {strategyDataLoading ? ( + + + + Loading strategy data... + + + ) : strategyDataError ? ( + + {strategyDataError} + + ) : ( + + )} + + )} + + {/* Loading indicator for initial data */} + {Object.values(dataLoading).some(loading => loading) && ( + + + + + Loading dashboard data... + + )} diff --git a/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx index 79d66c68..8049c5e7 100644 --- a/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx +++ b/frontend/src/components/ContentPlanningDashboard/tabs/CreateTab.tsx @@ -44,7 +44,7 @@ const CreateTab: React.FC = () => { // Get strategy context from the provider const { state: { strategyContext }, isFromStrategyActivation } = useStrategyCalendarContext(); - console.log('๐Ÿ” CreateTab: Rendering', { userData, strategyContext, locationState: location.state }); + // Removed verbose logging for cleaner console // Memoize the strategy activation status to avoid infinite re-renders const fromStrategyActivation = useMemo(() => { @@ -72,7 +72,7 @@ const CreateTab: React.FC = () => { // Auto-switch to Calendar Wizard tab when coming from strategy activation useEffect(() => { - console.log('๐Ÿ” CreateTab: Checking strategy activation status:', { fromStrategyActivation }); + // Removed verbose logging for cleaner console // Check multiple sources for strategy activation status const isFromStrategy = fromStrategyActivation || diff --git a/frontend/src/contexts/StrategyCalendarContext.tsx b/frontend/src/contexts/StrategyCalendarContext.tsx index 6684e15b..4fdf93b5 100644 --- a/frontend/src/contexts/StrategyCalendarContext.tsx +++ b/frontend/src/contexts/StrategyCalendarContext.tsx @@ -282,11 +282,6 @@ export const StrategyCalendarProvider: React.FC = // Check if we have a preserved strategy context from navigation const result = state.strategyContext?.activationStatus === 'active' && state.strategyContext?.activationTimestamp !== null; - console.log('๐Ÿ” StrategyCalendarContext: isFromStrategyActivation check:', { - activationStatus: state.strategyContext?.activationStatus, - activationTimestamp: state.strategyContext?.activationTimestamp, - result - }); return result; }, [state.strategyContext?.activationStatus, state.strategyContext?.activationTimestamp]); diff --git a/frontend/src/services/contentPlanningApi.ts b/frontend/src/services/contentPlanningApi.ts index 2e4511f2..30064f00 100644 --- a/frontend/src/services/contentPlanningApi.ts +++ b/frontend/src/services/contentPlanningApi.ts @@ -458,6 +458,27 @@ class ContentPlanningAPI { return this.handleRequest(() => this.getAIAnalytics(userId), true); } + // Enhanced version with rate limit handling for AI analytics + async getAIAnalyticsWithRetry(userId?: number, maxRetries: number = 2): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const response = await apiClient.get(`${this.baseURL}/ai-analytics/`, { + params: { user_id: userId || 1 } + }); + return response.data; + } catch (error: any) { + if (error.response?.status === 429 && attempt < maxRetries) { + // Rate limit hit, wait and retry + const delay = Math.pow(2, attempt) * 1000; // Exponential backoff + console.log(`๐Ÿšซ Rate limit hit for AI analytics, waiting ${delay}ms before retry ${attempt + 1}/${maxRetries}`); + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + throw error; // Re-throw if it's not a rate limit or we've exhausted retries + } + } + } + // AI Analytics with force refresh option async getAIAnalyticsWithRefresh(userId?: number, forceRefresh = false): Promise { try { @@ -517,7 +538,7 @@ class ContentPlanningAPI { async getComprehensiveUserData(userId?: number): Promise { return this.handleRequest(async () => { - const response = await apiClient.get(`${this.baseURL}/comprehensive-user-data`, { + const response = await apiClient.get(`${this.baseURL}/calendar-generation/comprehensive-user-data`, { params: { user_id: userId } }); return response.data; @@ -593,30 +614,17 @@ class ContentPlanningAPI { // Non-streaming autofill refresh method async refreshAutofill(userId?: number, useAI: boolean = true, aiOnly: boolean = false): Promise { - const params: any = { use_ai: useAI, ai_only: aiOnly }; + const params: any = { + use_ai: useAI, + ai_only: aiOnly, + _t: Date.now() // ๐Ÿšจ CRITICAL: Cache-busting timestamp to ensure fresh AI generation + }; if (userId) params.user_id = userId; const response = await apiClient.post(`${this.baseURL}/enhanced-strategies/autofill/refresh`, null, { params }); - // Debug the API response - console.log('๐ŸŽฏ API refreshAutofill response:', { - responseType: typeof response, - responseKeys: Object.keys(response), - dataType: typeof response.data, - dataKeys: response.data ? Object.keys(response.data) : 'no data', - hasDataProperty: response.data?.hasOwnProperty('data'), - hasFieldsProperty: response.data?.hasOwnProperty('fields'), - dataDataKeys: response.data?.data ? Object.keys(response.data.data) : 'no data.data' - }); - // The backend returns ResponseBuilder format: { status, message, data, status_code, timestamp } // We need to return the actual payload from response.data.data const result = response.data?.data || response.data; - console.log('๐ŸŽฏ API refreshAutofill returning:', { - resultType: typeof result, - resultKeys: Object.keys(result), - hasFields: result?.hasOwnProperty('fields'), - fieldsCount: result?.fields ? Object.keys(result.fields).length : 0 - }); return result; } @@ -714,21 +722,33 @@ class ContentPlanningAPI { return this.handleRequest(async () => { const params = userId ? { user_id: userId } : {}; const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, { params }); - console.log('๐Ÿ” getLatestGeneratedStrategy response:', response.data); - console.log('๐Ÿ” Response structure:', { - hasData: !!response.data, - dataKeys: Object.keys(response.data || {}), - hasStrategy: !!response.data?.data?.strategy, - strategyKeys: response.data?.data?.strategy ? Object.keys(response.data.data.strategy) : [] - }); // Return the strategy data from the nested response structure const result = response.data?.data?.strategy; - console.log('๐Ÿ” Returning result:', result); - console.log('๐Ÿ” Result keys:', Object.keys(result || {})); return result; }); } + // Enhanced version with rate limit handling + async getLatestGeneratedStrategyWithRetry(userId?: number, maxRetries: number = 2): Promise { + for (let attempt = 1; attempt <= maxRetries; attempt++) { + try { + const params = userId ? { user_id: userId } : {}; + const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, { params }); + const result = response.data?.data?.strategy; + return result; + } catch (error: any) { + if (error.response?.status === 429 && attempt < maxRetries) { + // Rate limit hit, wait and retry + const delay = Math.pow(2, attempt) * 1000; // Exponential backoff + console.log(`๐Ÿšซ Rate limit hit, waiting ${delay}ms before retry ${attempt + 1}/${maxRetries}`); + await new Promise(resolve => setTimeout(resolve, delay)); + continue; + } + throw error; // Re-throw if it's not a rate limit or we've exhausted retries + } + } + } + async startStrategyGenerationPolling(userId: number, strategyName: string): Promise { return this.handleRequest(async () => { const response = await apiClient.post(`${this.baseURL}/content-strategy/ai-generation/generate-comprehensive-strategy-polling`, { diff --git a/frontend/src/stores/contentPlanningStore.ts b/frontend/src/stores/contentPlanningStore.ts index bf803620..a634c3d7 100644 --- a/frontend/src/stores/contentPlanningStore.ts +++ b/frontend/src/stores/contentPlanningStore.ts @@ -151,6 +151,7 @@ interface ContentPlanningStore { // State strategies: ContentStrategy[]; currentStrategy: ContentStrategy | null; + latestGeneratedStrategy: any | null; // Cache for the latest generated strategy calendarEvents: CalendarEvent[]; gapAnalyses: ContentGapAnalysis[]; aiRecommendations: AIRecommendation[]; @@ -182,6 +183,7 @@ interface ContentPlanningStore { updateStrategy: (id: string, updates: Partial) => Promise; deleteStrategy: (id: string) => Promise; setCurrentStrategy: (strategy: ContentStrategy | null) => void; + setLatestGeneratedStrategy: (strategy: any | null) => void; // Calendar actions createEvent: (event: Omit) => Promise; @@ -269,6 +271,7 @@ export const useContentPlanningStore = create((set, get) = // Initial state strategies: [], currentStrategy: null, + latestGeneratedStrategy: null, calendarEvents: [], gapAnalyses: [], aiRecommendations: [], @@ -345,6 +348,18 @@ export const useContentPlanningStore = create((set, get) = }, setCurrentStrategy: (strategy) => set({ currentStrategy: strategy }), + setLatestGeneratedStrategy: (strategy) => { + console.log('๐Ÿ”ง Store: Setting latest generated strategy:', { + strategyId: strategy?.id || strategy?.strategy_id, + strategyName: strategy?.name || strategy?.strategy_name, + hasStrategicInsights: !!strategy?.strategic_insights, + hasCompetitiveAnalysis: !!strategy?.competitive_analysis, + hasPerformancePredictions: !!strategy?.performance_predictions, + hasImplementationRoadmap: !!strategy?.implementation_roadmap, + hasRiskAssessment: !!strategy?.risk_assessment + }); + set({ latestGeneratedStrategy: strategy }); + }, // Calendar actions createEvent: async (event) => { @@ -476,24 +491,17 @@ export const useContentPlanningStore = create((set, get) = loadStrategies: async () => { set({ loading: true, error: null }); try { - console.log('๐Ÿ” Loading strategies from API...'); const strategies = await contentPlanningApi.getStrategiesSafe(); - console.log('๐Ÿ” API response for strategies:', strategies); - console.log('๐Ÿ” Strategies type:', typeof strategies); - console.log('๐Ÿ” Is Array:', Array.isArray(strategies)); if (Array.isArray(strategies)) { - console.log('โœ… Strategies loaded successfully (direct array):', strategies.length); set({ strategies, loading: false }); } else if (strategies && strategies.strategies && Array.isArray(strategies.strategies)) { - console.log('โœ… Strategies found in response.strategies:', strategies.strategies.length); set({ strategies: strategies.strategies, loading: false }); } else { - console.log('โŒ No strategies found in response'); set({ strategies: [], loading: false }); } } catch (error: any) { - console.error('โŒ Error loading strategies:', error); + console.error('Error loading strategies:', error); set({ error: error.message || 'Failed to load strategies', loading: false }); } }, @@ -521,7 +529,7 @@ export const useContentPlanningStore = create((set, get) = loadAIInsights: async () => { set({ loading: true, error: null }); try { - const response = await contentPlanningApi.getAIAnalyticsSafe(); + const response = await contentPlanningApi.getAIAnalyticsWithRetry(); // Validate response structure if (!response || typeof response !== 'object') { @@ -559,6 +567,21 @@ export const useContentPlanningStore = create((set, get) = set({ aiInsights: transformedInsights, loading: false }); } catch (error: any) { console.error('Error loading AI insights:', error); + + // Check if it's a rate limit error + if (error.message?.includes('rate limit') || error.message?.includes('429')) { + console.warn('โš ๏ธ Rate limit hit for AI insights, using empty data'); + set({ aiInsights: [], loading: false }); + return; + } + + // Check if it's a network error + if (error.message?.includes('network') || error.message?.includes('connection')) { + console.warn('โš ๏ธ Network error for AI insights, using empty data'); + set({ aiInsights: [], loading: false }); + return; + } + set({ error: error.message || 'Failed to load AI insights', loading: false, aiInsights: [] }); } }, @@ -566,7 +589,7 @@ export const useContentPlanningStore = create((set, get) = loadAIRecommendations: async () => { set({ loading: true, error: null }); try { - const response = await contentPlanningApi.getAIAnalyticsSafe(); + const response = await contentPlanningApi.getAIAnalyticsWithRetry(); // Validate response structure if (!response || typeof response !== 'object') { @@ -593,6 +616,21 @@ export const useContentPlanningStore = create((set, get) = set({ aiRecommendations: transformedRecommendations, loading: false }); } catch (error: any) { console.error('Error loading AI recommendations:', error); + + // Check if it's a rate limit error + if (error.message?.includes('rate limit') || error.message?.includes('429')) { + console.warn('โš ๏ธ Rate limit hit for AI recommendations, using empty data'); + set({ aiRecommendations: [], loading: false }); + return; + } + + // Check if it's a network error + if (error.message?.includes('network') || error.message?.includes('connection')) { + console.warn('โš ๏ธ Network error for AI recommendations, using empty data'); + set({ aiRecommendations: [], loading: false }); + return; + } + set({ error: error.message || 'Failed to load AI recommendations', loading: false, aiRecommendations: [] }); } }, diff --git a/frontend/src/stores/strategyBuilderStore.ts b/frontend/src/stores/strategyBuilderStore.ts index 7435cce4..143d599e 100644 --- a/frontend/src/stores/strategyBuilderStore.ts +++ b/frontend/src/stores/strategyBuilderStore.ts @@ -4,6 +4,78 @@ import { contentPlanningApi } from '../services/contentPlanningApi'; // Global flag to prevent multiple simultaneous auto-population calls let isAutoPopulating = false; +// Helper function to detect generic placeholder values +const isGenericPlaceholder = (fieldId: string, value: any): boolean => { + if (!value) return false; + + const stringValue = String(value).toLowerCase(); + + // Common generic placeholder patterns + const genericPatterns = [ + 'example.com', + 'your website', + 'your business', + 'your industry', + 'your target audience', + 'your content', + 'your goals', + 'your objectives', + 'your metrics', + 'your budget', + 'your team', + 'your timeline', + 'your competitors', + 'your market', + 'your brand', + 'your voice', + 'your tone', + 'your strategy', + 'your plan', + 'your approach', + 'your focus', + 'your niche', + 'your sector', + 'your domain', + 'your company', + 'your organization', + 'your brand', + 'your product', + 'your service', + 'your offering', + 'your solution', + 'your value proposition', + 'your unique selling point', + 'your competitive advantage', + 'your market position', + 'your customer base', + 'your user base', + 'your audience', + 'your customers', + 'your clients', + 'your users', + 'your visitors', + 'your readers', + 'your followers', + 'your subscribers', + 'your leads', + 'your prospects', + 'your potential customers', + 'your target market', + 'your target segment', + 'your target demographic', + 'your target group', + 'your target population', + 'your target users', + 'your target readers', + 'your target audience', + 'your target customers', + 'your target clients' + ]; + + // Check if the value contains any generic placeholder patterns + return genericPatterns.some(pattern => stringValue.includes(pattern)); +}; + // Enhanced Strategy Types export interface EnhancedStrategy { id: string; @@ -638,7 +710,6 @@ export const useStrategyBuilderStore = create((set, get) = if (forceRefresh) { try { await contentPlanningApi.clearEnhancedCache(1); - console.log('โ™ป๏ธ Cleared enhanced strategy cache for fresh onboarding data'); } catch (e) { console.warn('Cache clear failed (non-blocking):', e); } @@ -646,16 +717,55 @@ export const useStrategyBuilderStore = create((set, get) = // Fetch onboarding data to auto-populate fields const response = await contentPlanningApi.getOnboardingData(); - console.log('๐Ÿ“ก Backend response:', response); + + // Enhanced logging for autofill data + console.log('๐Ÿ“Š Autofill Response Structure:', { + hasResponse: !!response, + responseKeys: response ? Object.keys(response) : [], + fieldsCount: response?.fields ? Object.keys(response.fields).length : 0, + sourcesCount: response?.sources ? Object.keys(response.sources).length : 0, + inputDataPointsCount: response?.input_data_points ? Object.keys(response.input_data_points).length : 0, + hasMeta: !!response?.meta + }); + + // Validate response structure + if (!response) { + throw new Error('Invalid response structure from backend'); + } // Extract field values and sources from the new backend format - const fields = response.data?.fields || {}; - const sources = response.data?.sources || {}; - const inputDataPoints = response.data?.input_data_points || {}; + const fields = response.fields || {}; + const sources = response.sources || {}; + const inputDataPoints = response.input_data_points || {}; - console.log('๐Ÿ“‹ Extracted fields:', fields); - console.log('๐Ÿ”— Data sources:', sources); - console.log('๐Ÿ“ Input data points:', inputDataPoints); + // Log detailed field information + console.log('๐ŸŽฏ Autofill Field Details:', { + totalFields: Object.keys(fields).length, + fieldIds: Object.keys(fields), + sampleFieldData: Object.keys(fields).slice(0, 3).map(id => ({ + id, + hasValue: !!fields[id]?.value, + hasPersonalization: !!fields[id]?.personalization_data, + hasConfidence: !!fields[id]?.confidence_score, + valueType: typeof fields[id]?.value + })) + }); + + // Validate AI generation success + const meta = response.meta || {}; + console.log('๐Ÿค– AI Generation Meta:', { + aiUsed: meta.ai_used, + aiOverridesCount: meta.ai_overrides_count, + error: meta.error, + processingTime: meta.processing_time + }); + + if (meta.ai_used === false || meta.ai_overrides_count === 0) { + console.log('โŒ AI generation failed - no real AI values produced'); + throw new Error(meta.error || 'AI generation failed to produce strategy fields. Please try again.'); + } + + console.log('โœ… AI generation successful:', Object.keys(fields).length, 'fields'); // Transform the fields object to extract values for formData const fieldValues: Record = {}; @@ -663,61 +773,77 @@ export const useStrategyBuilderStore = create((set, get) = const personalizationData: Record = {}; const confidenceScores: Record = {}; - // Check if fields is empty and provide fallback - if (Object.keys(fields).length === 0) { - console.log('โš ๏ธ No fields found in onboarding data, using default values'); + // Check if fields is empty - no fallback to mock data + if (Object.keys(fields).length === 0) { + console.log('โŒ No fields found in onboarding data - AI generation may have failed'); + console.log('๐Ÿšซ No fallback to mock data - user must retry or provide manual input'); + + // Set error state instead of mock data + set({ + loading: false, + error: 'AI generation failed to produce strategy fields. Please try again or provide manual input.', + autoPopulatedFields: {}, + personalizationData: {}, + dataSources: {}, + inputDataPoints: {} + }); + return; + } + + // Process actual fields from backend + let processedFields = 0; + let skippedFields = 0; + let fieldsWithPersonalization = 0; + let fieldsWithConfidence = 0; - // Set default values for strategy builder - const defaultFields: Record = { - industry: 'Technology', - business_objectives: 'Increase brand awareness and drive sales', - target_metrics: { traffic: 10000, conversion_rate: 2.5 }, - content_budget: 5000, - team_size: 3, - content_preferences: ['Blog posts', 'Social media', 'Email marketing'], - preferred_formats: ['Blog posts', 'Whitepapers', 'Videos'], - content_mix: { blog_posts: 40, whitepapers: 20, videos: 15, social_media: 25 } - }; - - Object.keys(defaultFields).forEach(fieldId => { - fieldValues[fieldId] = defaultFields[fieldId]; - autoPopulatedFields[fieldId] = defaultFields[fieldId]; - confidenceScores[fieldId] = 0.7; // Medium confidence for defaults - console.log(`โœ… Set default value for ${fieldId}:`, defaultFields[fieldId]); - }); - } else { - // Process actual fields from backend Object.keys(fields).forEach(fieldId => { const fieldData = fields[fieldId]; - console.log(`๐Ÿ” Processing field ${fieldId}:`, fieldData); if (fieldData && typeof fieldData === 'object' && 'value' in fieldData) { - fieldValues[fieldId] = fieldData.value; - autoPopulatedFields[fieldId] = fieldData.value; + // Validate that the value is not a generic placeholder + const value = fieldData.value; + const isGenericPlaceholderValue = isGenericPlaceholder(fieldId, value); + + if (isGenericPlaceholderValue) { + console.log(`โš ๏ธ Skipping ${fieldId} - contains generic placeholder value: "${value}"`); + skippedFields++; + return; // Skip this field + } + + fieldValues[fieldId] = value; + autoPopulatedFields[fieldId] = value; + processedFields++; // Extract personalization data if available if (fieldData.personalization_data) { personalizationData[fieldId] = fieldData.personalization_data; - console.log(`๐ŸŽฏ Personalization data for ${fieldId}:`, fieldData.personalization_data); + fieldsWithPersonalization++; } // Extract confidence score if available if (fieldData.confidence_score) { confidenceScores[fieldId] = fieldData.confidence_score; - console.log(`๐Ÿ’ฏ Confidence score for ${fieldId}:`, fieldData.confidence_score); + fieldsWithConfidence++; } - - console.log(`โœ… Auto-populated ${fieldId}:`, fieldData.value); } else { - console.log(`โŒ Skipping ${fieldId} - invalid data structure`); + console.log(`โŒ Skipping ${fieldId} - invalid data structure:`, fieldData); + skippedFields++; } }); - } + + // Log field processing summary + console.log('๐Ÿ“ˆ Field Processing Summary:', { + totalFields: Object.keys(fields).length, + processedFields, + skippedFields, + fieldsWithPersonalization, + fieldsWithConfidence, + averageConfidence: fieldsWithConfidence > 0 + ? (Object.values(confidenceScores).filter((score): score is number => typeof score === 'number').reduce((a, b) => a + b, 0) / fieldsWithConfidence).toFixed(1) + : 'N/A' + }); - console.log('๐Ÿ“ Final field values:', fieldValues); - console.log('๐Ÿ”„ Final auto-populated fields:', autoPopulatedFields); - console.log('๐ŸŽฏ Personalization data:', personalizationData); - console.log('๐Ÿ’ฏ Confidence scores:', confidenceScores); + console.log('โœ… Auto-population completed:', Object.keys(fieldValues).length, 'fields'); set((state) => ({ autoPopulatedFields, @@ -728,85 +854,100 @@ export const useStrategyBuilderStore = create((set, get) = formData: { ...state.formData, ...fieldValues } })); - console.log('โœ… Auto-population completed successfully'); - } catch (error: any) { - console.error('โŒ Auto-population error:', error); - const errorMessage = error.message || 'Failed to auto-populate from onboarding'; - set({ - error: errorMessage, - loading: false + // Log final state summary + console.log('๐ŸŽ‰ Auto-population State Summary:', { + autoPopulatedFieldsCount: Object.keys(autoPopulatedFields).length, + dataSourcesCount: Object.keys(sources).length, + inputDataPointsCount: Object.keys(inputDataPoints).length, + personalizationDataCount: Object.keys(personalizationData).length, + confidenceScoresCount: Object.keys(confidenceScores).length, + formDataUpdated: Object.keys(fieldValues).length, + timestamp: new Date().toISOString() }); - // If it's a rate limit error, set a flag to prevent further attempts - if (errorMessage.includes('Too many requests') || errorMessage.includes('No response from server')) { - set({ autoPopulationBlocked: true }); - } - } finally { - set({ loading: false }); - isAutoPopulating = false; // Reset global flag - } - }, - - updateAutoPopulatedField: (fieldId, value, source) => { - set((state) => ({ - autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value }, - dataSources: { ...state.dataSources, [fieldId]: source } - })); - }, - - overrideAutoPopulatedField: (fieldId, value) => { - set((state) => ({ - formData: { ...state.formData, [fieldId]: value }, - autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value } - })); - }, - - // UI Actions - setLoading: (loading) => set({ loading }), - setError: (error) => set({ error }), - setSaving: (saving) => set({ saving }), - - // Completion Tracking - calculateCompletionPercentage: () => { - const formData = get().formData; - const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required); - const filledRequiredFields = requiredFields.filter(field => { - const value = formData[field.id]; - return value && (Array.isArray(value) ? value.length > 0 : true); - }); - - return requiredFields.length > 0 ? (filledRequiredFields.length / requiredFields.length) * 100 : 0; - }, - - getCompletionStats: () => { - const formData = get().formData; - const totalFields = STRATEGIC_INPUT_FIELDS.length; - const filledFields = STRATEGIC_INPUT_FIELDS.filter(field => { - const value = formData[field.id]; - return value && (Array.isArray(value) ? value.length > 0 : true); - }).length; - - const completionPercentage = totalFields > 0 ? (filledFields / totalFields) * 100 : 0; - - // Calculate completion by category - const categoryCompletion: Record = {}; - const categories = Array.from(new Set(STRATEGIC_INPUT_FIELDS.map(field => field.category))); - - categories.forEach(category => { - const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category); - const filledCategoryFields = categoryFields.filter(field => { - const value = formData[field.id]; - return value && (Array.isArray(value) ? value.length > 0 : true); - }).length; - - categoryCompletion[category] = categoryFields.length > 0 ? (filledCategoryFields / categoryFields.length) * 100 : 0; - }); - - return { - total_fields: totalFields, - filled_fields: filledFields, - completion_percentage: completionPercentage, - category_completion: categoryCompletion - }; - } -})); + console.log('โœ… Auto-population completed successfully'); + + // Store the autofill completion time + sessionStorage.setItem('lastAutofillTime', new Date().toISOString()); + + } catch (error: any) { + console.error('โŒ Auto-population error:', error); + const errorMessage = error.message || 'Failed to auto-populate from onboarding'; + set({ + error: errorMessage, + loading: false + }); + + // If it's a rate limit error, set a flag to prevent further attempts + if (errorMessage.includes('Too many requests') || errorMessage.includes('No response from server')) { + set({ autoPopulationBlocked: true }); + } + } finally { + set({ loading: false }); + isAutoPopulating = false; // Reset global flag + } + }, + + updateAutoPopulatedField: (fieldId: string, value: any, source: string) => { + set((state) => ({ + autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value }, + dataSources: { ...state.dataSources, [fieldId]: source } + })); + }, + + overrideAutoPopulatedField: (fieldId: string, value: any) => { + set((state) => ({ + formData: { ...state.formData, [fieldId]: value }, + autoPopulatedFields: { ...state.autoPopulatedFields, [fieldId]: value } + })); + }, + + // UI Actions + setLoading: (loading: boolean) => set({ loading }), + setError: (error: string | null) => set({ error }), + setSaving: (saving: boolean) => set({ saving }), + + // Completion Tracking + calculateCompletionPercentage: () => { + const formData = get().formData; + const requiredFields = STRATEGIC_INPUT_FIELDS.filter(field => field.required); + const filledRequiredFields = requiredFields.filter(field => { + const value = formData[field.id]; + return value && (Array.isArray(value) ? value.length > 0 : true); + }); + + return requiredFields.length > 0 ? (filledRequiredFields.length / requiredFields.length) * 100 : 0; + }, + + getCompletionStats: () => { + const formData = get().formData; + const totalFields = STRATEGIC_INPUT_FIELDS.length; + const filledFields = STRATEGIC_INPUT_FIELDS.filter(field => { + const value = formData[field.id]; + return value && (Array.isArray(value) ? value.length > 0 : true); + }).length; + + const completionPercentage = totalFields > 0 ? (filledFields / totalFields) * 100 : 0; + + // Calculate completion by category + const categoryCompletion: Record = {}; + const categories = Array.from(new Set(STRATEGIC_INPUT_FIELDS.map(field => field.category))); + + categories.forEach(category => { + const categoryFields = STRATEGIC_INPUT_FIELDS.filter(field => field.category === category); + const filledCategoryFields = categoryFields.filter(field => { + const value = formData[field.id]; + return value && (Array.isArray(value) ? value.length > 0 : true); + }).length; + + categoryCompletion[category] = categoryFields.length > 0 ? (filledCategoryFields / categoryFields.length) * 100 : 0; + }); + + return { + total_fields: totalFields, + filled_fields: filledFields, + completion_percentage: completionPercentage, + category_completion: categoryCompletion + }; + } + })); diff --git a/frontend/src/stores/strategyReviewStore.ts b/frontend/src/stores/strategyReviewStore.ts index c80a0dca..5183c3e4 100644 --- a/frontend/src/stores/strategyReviewStore.ts +++ b/frontend/src/stores/strategyReviewStore.ts @@ -171,7 +171,24 @@ export const useStrategyReviewStore = create()( // Start review process startReviewProcess: () => { - set({ reviewProcessStarted: true }); + console.log('๐Ÿ”ง Starting review process - resetting all reviews first'); + // Reset all reviews when starting a new review process + const { components } = get(); + const resetComponents = components.map(comp => ({ + ...comp, + status: 'not_reviewed' as ReviewStatus, + reviewedAt: undefined, + reviewedBy: undefined, + notes: undefined + })); + + set({ + reviewProcessStarted: true, + components: resetComponents, + reviewProgress: 0 + }); + + console.log('๐Ÿ”ง Review process started with reset components'); }, // Update review progress @@ -181,7 +198,6 @@ export const useStrategyReviewStore = create()( const totalCount = components.length; const progress = totalCount > 0 ? (reviewedCount / totalCount) * 100 : 0; - console.log('๐Ÿ”ง Updating progress:', { reviewedCount, totalCount, progress, components: components.map(c => ({ id: c.id, status: c.status })) }); set({ reviewProgress: progress }); },