ALwrity version 0.5.5
This commit is contained in:
@@ -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:
|
||||
|
||||
@@ -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()})
|
||||
|
||||
@@ -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."""
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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"
|
||||
]
|
||||
}
|
||||
@@ -1 +0,0 @@
|
||||
<!doctype html><html lang="en"><head><meta charset="utf-8"/><link rel="icon" href="/favicon.ico"/><meta name="viewport" content="width=device-width,initial-scale=1"/><meta name="theme-color" content="#000000"/><meta name="description" content="Alwrity - AI Content Creation Platform"/><link rel="apple-touch-icon" href="/logo192.png"/><link rel="manifest" href="/manifest.json"/><title>Alwrity - AI Content Creation Platform</title><script defer="defer" src="/static/js/main.bdc25f29.js"></script><link href="/static/css/main.c9966057.css" rel="stylesheet"></head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
|
||||
@@ -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*/
|
||||
File diff suppressed because one or more lines are too long
@@ -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<HTMLDivElement>(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 (
|
||||
<Box sx={{ p: 3 }}>
|
||||
{/* Header with Title (Region B) - Enhanced with Futuristic Styling */}
|
||||
<HeaderSection autoPopulatedFields={autoPopulatedFields} />
|
||||
<HeaderSection
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
dataSources={dataSources}
|
||||
inputDataPoints={inputDataPoints}
|
||||
personalizationData={personalizationData}
|
||||
confidenceScores={confidenceScores}
|
||||
loading={loading}
|
||||
error={error}
|
||||
onRefreshAutofill={handleAIRefresh}
|
||||
onContinueWithPresent={handleContinueWithPresent}
|
||||
onScrollToReview={handleScrollToReview}
|
||||
hasAutofillData={hasAutofillData}
|
||||
lastAutofillTime={lastAutofillTime}
|
||||
dataSource={dataSource}
|
||||
/>
|
||||
|
||||
{/* Error Alert */}
|
||||
<ErrorAlert
|
||||
@@ -444,81 +477,49 @@ const ContentStrategyBuilder: React.FC = () => {
|
||||
onShowEducationalInfo={handleShowEducationalInfo}
|
||||
/>
|
||||
|
||||
{/* Quick Actions */}
|
||||
<Box sx={{ mt: 3, pt: 2, borderTop: 1, borderColor: 'divider' }}>
|
||||
<Typography variant="subtitle2" gutterBottom>
|
||||
Quick Actions
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
onClick={() => setShowAIRecommendations(true)}
|
||||
fullWidth
|
||||
>
|
||||
View AI Insights
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<InfoIcon />}
|
||||
onClick={() => setShowDataSourceTransparency(true)}
|
||||
fullWidth
|
||||
>
|
||||
View Data Sources
|
||||
</Button>
|
||||
<Button
|
||||
size="small"
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={() => autoPopulateFromOnboarding(true)}
|
||||
fullWidth
|
||||
>
|
||||
Refresh Data
|
||||
</Button>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
{/* Main Content Area */}
|
||||
<Grid item xs={12} md={8}>
|
||||
<Paper sx={{ p: 3, minHeight: '600px', background: 'linear-gradient(180deg, #faf7ff, #f1f0ff)' }}>
|
||||
<CategoryDetailView
|
||||
activeCategory={activeCategory}
|
||||
formData={formData}
|
||||
formErrors={formErrors}
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
dataSources={dataSources}
|
||||
inputDataPoints={inputDataPoints}
|
||||
personalizationData={personalizationData}
|
||||
completionStats={completionStats}
|
||||
reviewedCategories={reviewedCategories}
|
||||
isMarkingReviewed={isMarkingReviewed}
|
||||
showEducationalInfo={showEducationalInfo}
|
||||
STRATEGIC_INPUT_FIELDS={STRATEGIC_INPUT_FIELDS}
|
||||
onUpdateFormField={updateFormField}
|
||||
onValidateFormField={validateFormField}
|
||||
onShowTooltip={setShowTooltip}
|
||||
onViewDataSource={(fieldId) => {
|
||||
// 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}
|
||||
/>
|
||||
<div ref={reviewSectionRef}>
|
||||
<CategoryDetailView
|
||||
activeCategory={activeCategory}
|
||||
formData={formData}
|
||||
formErrors={formErrors}
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
dataSources={dataSources}
|
||||
inputDataPoints={inputDataPoints}
|
||||
personalizationData={personalizationData}
|
||||
completionStats={completionStats}
|
||||
reviewedCategories={reviewedCategories}
|
||||
isMarkingReviewed={isMarkingReviewed}
|
||||
showEducationalInfo={showEducationalInfo}
|
||||
STRATEGIC_INPUT_FIELDS={STRATEGIC_INPUT_FIELDS}
|
||||
onUpdateFormField={updateFormField}
|
||||
onValidateFormField={validateFormField}
|
||||
onShowTooltip={setShowTooltip}
|
||||
onViewDataSource={(fieldId) => {
|
||||
// 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}
|
||||
/>
|
||||
</div>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
@@ -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 = () => {
|
||||
<EnterpriseDatapointsModal
|
||||
open={showEnterpriseModal}
|
||||
onClose={() => {
|
||||
console.log('🎯 Enterprise modal onClose called');
|
||||
console.log('🎯 Current aiGenerating state:', aiGenerating);
|
||||
setShowEnterpriseModal(false);
|
||||
sessionStorage.removeItem('showEnterpriseModal'); // Clear sessionStorage
|
||||
}}
|
||||
|
||||
@@ -426,26 +426,15 @@ const StrategicInputField: React.FC<StrategicInputFieldProps> = ({
|
||||
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<StrategicInputFieldProps> = ({
|
||||
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<StrategicInputFieldProps> = ({
|
||||
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<StrategicInputFieldProps> = ({
|
||||
// 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) => (
|
||||
|
||||
@@ -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'}`);
|
||||
|
||||
@@ -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<string, any>;
|
||||
dataSources: Record<string, string>;
|
||||
inputDataPoints: Record<string, any>;
|
||||
personalizationData: Record<string, any>;
|
||||
confidenceScores: Record<string, number>;
|
||||
lastAutofillTime?: string;
|
||||
dataSource?: string;
|
||||
}
|
||||
|
||||
const AutofillDataTransparency: React.FC<AutofillDataTransparencyProps> = ({
|
||||
open,
|
||||
onClose,
|
||||
autoPopulatedFields,
|
||||
dataSources,
|
||||
inputDataPoints,
|
||||
personalizationData,
|
||||
confidenceScores,
|
||||
lastAutofillTime,
|
||||
dataSource
|
||||
}) => {
|
||||
const [activeAccordion, setActiveAccordion] = useState<string | false>('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<string, number> = {};
|
||||
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 (
|
||||
<Dialog
|
||||
open={open}
|
||||
onClose={onClose}
|
||||
maxWidth="lg"
|
||||
fullWidth
|
||||
PaperProps={{
|
||||
sx: {
|
||||
borderRadius: 3,
|
||||
background: 'linear-gradient(135deg, #f8f9ff 0%, #f1f4ff 100%)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
<DialogTitle>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<VisibilityIcon color="primary" sx={{ fontSize: 28 }} />
|
||||
<Typography variant="h5" fontWeight="bold" color="primary">
|
||||
Autofill Data Transparency
|
||||
</Typography>
|
||||
</Box>
|
||||
<IconButton onClick={onClose} size="large">
|
||||
<CloseIcon />
|
||||
</IconButton>
|
||||
</Box>
|
||||
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
|
||||
Complete transparency about how your strategy inputs were auto-populated
|
||||
</Typography>
|
||||
</DialogTitle>
|
||||
|
||||
<DialogContent sx={{ p: 3 }}>
|
||||
{/* Summary Cards */}
|
||||
<Grid container spacing={3} sx={{ mb: 3 }}>
|
||||
<Grid item xs={12} md={3}>
|
||||
<Paper sx={{ p: 2, textAlign: 'center', background: 'linear-gradient(135deg, #e3f2fd 0%, #bbdefb 100%)' }}>
|
||||
<DataUsageIcon color="primary" sx={{ fontSize: 40, mb: 1 }} />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{Object.keys(autoPopulatedFields).length}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Fields Auto-populated
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={3}>
|
||||
<Paper sx={{ p: 2, textAlign: 'center', background: 'linear-gradient(135deg, #f3e5f5 0%, #e1bee7 100%)' }}>
|
||||
<TrendingUpIcon color="secondary" sx={{ fontSize: 40, mb: 1 }} />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{dataQualityScore}%
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Data Quality Score
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={3}>
|
||||
<Paper sx={{ p: 2, textAlign: 'center', background: 'linear-gradient(135deg, #e8f5e8 0%, #c8e6c9 100%)' }}>
|
||||
<RefreshIcon color="success" sx={{ fontSize: 40, mb: 1 }} />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{dataFreshness}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Last Updated
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
|
||||
<Grid item xs={12} md={3}>
|
||||
<Paper sx={{ p: 2, textAlign: 'center', background: 'linear-gradient(135deg, #fff3e0 0%, #ffcc02 100%)' }}>
|
||||
<SecurityIcon color="warning" sx={{ fontSize: 40, mb: 1 }} />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
{Object.keys(dataSources).length}
|
||||
</Typography>
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Data Sources Used
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Data Quality Indicator */}
|
||||
<Alert
|
||||
severity={dataQualityScore >= 80 ? 'success' : dataQualityScore >= 60 ? 'warning' : 'error'}
|
||||
sx={{ mb: 3 }}
|
||||
icon={dataQualityScore >= 80 ? <CheckCircleIcon /> : <WarningIcon />}
|
||||
>
|
||||
<Typography variant="body1" fontWeight="bold">
|
||||
Data Quality Assessment: {dataQualityScore >= 80 ? 'Excellent' : dataQualityScore >= 60 ? 'Good' : 'Needs Review'}
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
Based on confidence scores from {Object.keys(confidenceScores).length} fields
|
||||
</Typography>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={dataQualityScore}
|
||||
sx={{ mt: 1, height: 8, borderRadius: 4 }}
|
||||
/>
|
||||
</Alert>
|
||||
|
||||
{/* Detailed Information Accordions */}
|
||||
<Accordion
|
||||
expanded={activeAccordion === 'data-sources'}
|
||||
onChange={handleAccordionChange('data-sources')}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<DataUsageIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
Data Sources & Integration
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Primary Data Sources
|
||||
</Typography>
|
||||
<List dense>
|
||||
{Object.entries(dataSources).map(([fieldId, source]) => (
|
||||
<ListItem key={fieldId}>
|
||||
<ListItemIcon>
|
||||
<InfoIcon color="primary" />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
||||
secondary={source}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Integration Details
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2, background: '#f8f9fa' }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Data Source:</strong> {dataSource || 'Onboarding Integration'}
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Integration Method:</strong> AI-Powered Analysis
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Data Processing:</strong> Real-time with validation
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<strong>Privacy Compliance:</strong> GDPR & CCPA Compliant
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
<Accordion
|
||||
expanded={activeAccordion === 'user-info'}
|
||||
onChange={handleAccordionChange('user-info')}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<PersonIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
User Information & Personalization
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(personalizationData).map(([fieldId, data]) => (
|
||||
<Grid item xs={12} md={6} key={fieldId}>
|
||||
<Paper sx={{ p: 2, border: '1px solid #e0e0e0' }}>
|
||||
<Typography variant="h6" gutterBottom color="primary">
|
||||
{fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
||||
</Typography>
|
||||
{typeof data === 'object' && data !== null ? (
|
||||
<Box>
|
||||
{data.explanation && (
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
<strong>Explanation:</strong> {data.explanation}
|
||||
</Typography>
|
||||
)}
|
||||
{data.personalization_factors && (
|
||||
<Box>
|
||||
<Typography variant="body2" fontWeight="bold" gutterBottom>
|
||||
Personalization Factors:
|
||||
</Typography>
|
||||
{Object.entries(data.personalization_factors).map(([key, value]) => (
|
||||
<Chip
|
||||
key={key}
|
||||
label={`${key}: ${value}`}
|
||||
size="small"
|
||||
sx={{ mr: 1, mb: 1 }}
|
||||
/>
|
||||
))}
|
||||
</Box>
|
||||
)}
|
||||
</Box>
|
||||
) : (
|
||||
<Typography variant="body2">{String(data)}</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
<Accordion
|
||||
expanded={activeAccordion === 'ai-analysis'}
|
||||
onChange={handleAccordionChange('ai-analysis')}
|
||||
sx={{ mb: 2 }}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<PsychologyIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
AI Analysis Results
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
Confidence Scores by Field
|
||||
</Typography>
|
||||
<List dense>
|
||||
{Object.entries(confidenceScores).map(([fieldId, score]) => (
|
||||
<ListItem key={fieldId}>
|
||||
<ListItemIcon>
|
||||
<Box sx={{
|
||||
width: 12,
|
||||
height: 12,
|
||||
borderRadius: '50%',
|
||||
bgcolor: score >= 80 ? 'success.main' : score >= 60 ? 'warning.main' : 'error.main'
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
||||
secondary={`${score}% confidence`}
|
||||
/>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={score}
|
||||
sx={{ width: 100, height: 6, borderRadius: 3 }}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
</Grid>
|
||||
<Grid item xs={12} md={6}>
|
||||
<Typography variant="h6" gutterBottom>
|
||||
AI Processing Details
|
||||
</Typography>
|
||||
<Paper sx={{ p: 2, background: '#f8f9fa' }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>AI Model:</strong> Claude 3.5 Sonnet
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Analysis Type:</strong> Contextual Understanding
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Processing Time:</strong> Real-time
|
||||
</Typography>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
<strong>Validation:</strong> Multi-step verification
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<strong>Quality Checks:</strong> Generic placeholder detection
|
||||
</Typography>
|
||||
</Paper>
|
||||
</Grid>
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
|
||||
<Accordion
|
||||
expanded={activeAccordion === 'field-details'}
|
||||
onChange={handleAccordionChange('field-details')}
|
||||
>
|
||||
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<AnalyticsIcon color="primary" />
|
||||
<Typography variant="h6" fontWeight="bold">
|
||||
Field-by-Field Breakdown
|
||||
</Typography>
|
||||
</Box>
|
||||
</AccordionSummary>
|
||||
<AccordionDetails>
|
||||
<Grid container spacing={2}>
|
||||
{Object.entries(autoPopulatedFields).map(([fieldId, value]) => (
|
||||
<Grid item xs={12} md={6} key={fieldId}>
|
||||
<Paper sx={{ p: 2, border: '1px solid #e0e0e0' }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Typography variant="h6" color="primary">
|
||||
{fieldId.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
|
||||
</Typography>
|
||||
<Chip
|
||||
label={dataSources[fieldId] || 'AI Generated'}
|
||||
size="small"
|
||||
color="primary"
|
||||
variant="outlined"
|
||||
/>
|
||||
</Box>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
<strong>Value:</strong> {typeof value === 'object' ? JSON.stringify(value, null, 2) : String(value)}
|
||||
</Typography>
|
||||
{confidenceScores[fieldId] && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<strong>Confidence:</strong> {confidenceScores[fieldId]}%
|
||||
</Typography>
|
||||
)}
|
||||
{inputDataPoints[fieldId] && (
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
<strong>Data Points:</strong> {Object.keys(inputDataPoints[fieldId]).length} sources
|
||||
</Typography>
|
||||
)}
|
||||
</Paper>
|
||||
</Grid>
|
||||
))}
|
||||
</Grid>
|
||||
</AccordionDetails>
|
||||
</Accordion>
|
||||
</DialogContent>
|
||||
|
||||
<DialogActions sx={{ p: 3, pt: 0 }}>
|
||||
<Button onClick={onClose} variant="outlined">
|
||||
Close
|
||||
</Button>
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
onClick={() => {
|
||||
// This could trigger a refresh of the autofill data
|
||||
console.log('Refreshing autofill data...');
|
||||
onClose();
|
||||
}}
|
||||
>
|
||||
Refresh Data
|
||||
</Button>
|
||||
</DialogActions>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export default AutofillDataTransparency;
|
||||
@@ -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<HeaderSectionProps> = ({ autoPopulatedFields }) => {
|
||||
const HeaderSection: React.FC<HeaderSectionProps> = ({
|
||||
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<string, number> = {};
|
||||
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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
@@ -22,9 +121,9 @@ const HeaderSection: React.FC<HeaderSectionProps> = ({ autoPopulatedFields }) =>
|
||||
>
|
||||
<Paper
|
||||
sx={{
|
||||
p: 2.5, // More compact padding
|
||||
p: 2.5,
|
||||
mb: 3,
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)', // Enhanced gradient
|
||||
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%)',
|
||||
color: 'white',
|
||||
borderRadius: 3,
|
||||
position: 'relative',
|
||||
@@ -44,39 +143,379 @@ const HeaderSection: React.FC<HeaderSectionProps> = ({ autoPopulatedFields }) =>
|
||||
border: '1px solid rgba(255,255,255,0.2)',
|
||||
}}
|
||||
>
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', position: 'relative', zIndex: 1 }}>
|
||||
<Box>
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
background: 'linear-gradient(45deg, #fff, #f0f0f0)',
|
||||
backgroundClip: 'text',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
textShadow: '0 0 20px rgba(255,255,255,0.5)',
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
AI Content Strategy Co-pilot
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ opacity: 0.9, fontSize: '0.9rem' }}>
|
||||
Build a comprehensive content strategy with 30+ strategic inputs
|
||||
</Typography>
|
||||
|
||||
{/* Auto-population Status - Moved to header (Region 4) */}
|
||||
{autoPopulatedFields && Object.keys(autoPopulatedFields).length > 0 && (
|
||||
<Box sx={{ mt: 1.5, display: 'flex', alignItems: 'center', gap: 1 }}>
|
||||
<CheckCircleIcon sx={{ color: 'rgba(255,255,255,0.8)', fontSize: 18 }} />
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.9)', fontSize: '0.85rem' }}>
|
||||
{Object.keys(autoPopulatedFields).length} fields auto-populated from onboarding data
|
||||
<Box sx={{ position: 'relative', zIndex: 1 }}>
|
||||
{/* Main Header */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
|
||||
<Box sx={{ flex: 1 }}>
|
||||
<Typography
|
||||
variant="h4"
|
||||
gutterBottom
|
||||
sx={{
|
||||
fontWeight: 'bold',
|
||||
background: 'linear-gradient(45deg, #fff, #f0f0f0)',
|
||||
backgroundClip: 'text',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
textShadow: '0 0 20px rgba(255,255,255,0.5)',
|
||||
mb: 1
|
||||
}}
|
||||
>
|
||||
AI Content Strategy Co-pilot
|
||||
</Typography>
|
||||
<Typography variant="body1" sx={{ opacity: 0.9, fontSize: '0.9rem' }}>
|
||||
Build a comprehensive content strategy with 30+ strategic inputs
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Enhanced Data Status Grid */}
|
||||
<Grid container spacing={2} sx={{ mb: 3 }}>
|
||||
{/* Auto-populated Fields Count */}
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
p: 1.5,
|
||||
borderRadius: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<DataUsageIcon sx={{ fontSize: 20, color: 'rgba(255, 255, 255, 0.8)' }} />
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
|
||||
{Object.keys(autoPopulatedFields).length}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.8, fontSize: '0.7rem' }}>
|
||||
Fields Auto-populated
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Data Quality Score */}
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
p: 1.5,
|
||||
borderRadius: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<TrendingUpIcon sx={{ fontSize: 20, color: 'rgba(255, 255, 255, 0.8)' }} />
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
|
||||
{dataQualityScore}%
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.8, fontSize: '0.7rem' }}>
|
||||
Data Quality
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Last Updated */}
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
p: 1.5,
|
||||
borderRadius: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<ScheduleIcon sx={{ fontSize: 20, color: 'rgba(255, 255, 255, 0.8)' }} />
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
|
||||
{lastAutofillTime ? formatTimeAgo(lastAutofillTime).split(' ')[0] : 'N/A'}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.8, fontSize: '0.7rem' }}>
|
||||
Last Updated
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
|
||||
{/* Data Sources */}
|
||||
<Grid item xs={6} sm={3}>
|
||||
<Box sx={{
|
||||
display: 'flex',
|
||||
alignItems: 'center',
|
||||
gap: 1,
|
||||
p: 1.5,
|
||||
borderRadius: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
backdropFilter: 'blur(10px)'
|
||||
}}>
|
||||
<SecurityIcon sx={{ fontSize: 20, color: 'rgba(255, 255, 255, 0.8)' }} />
|
||||
<Box>
|
||||
<Typography variant="h6" sx={{ fontWeight: 'bold', fontSize: '1.1rem' }}>
|
||||
{Object.keys(dataSources).length}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ opacity: 0.8, fontSize: '0.7rem' }}>
|
||||
Data Sources
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Grid>
|
||||
</Grid>
|
||||
|
||||
{/* Data Quality Progress Bar */}
|
||||
{dataQualityScore > 0 && (
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 1 }}>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, fontSize: '0.8rem' }}>
|
||||
Data Quality Score
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ opacity: 0.9, fontSize: '0.8rem', fontWeight: 'bold' }}>
|
||||
{dataQualityScore}%
|
||||
</Typography>
|
||||
</Box>
|
||||
<LinearProgress
|
||||
variant="determinate"
|
||||
value={dataQualityScore}
|
||||
sx={{
|
||||
height: 6,
|
||||
borderRadius: 3,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
'& .MuiLinearProgress-bar': {
|
||||
background: dataQualityScore >= 80
|
||||
? 'linear-gradient(90deg, #4caf50, #66bb6a)'
|
||||
: dataQualityScore >= 60
|
||||
? 'linear-gradient(90deg, #ff9800, #ffb74d)'
|
||||
: 'linear-gradient(90deg, #f44336, #ef5350)',
|
||||
borderRadius: 3
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Enhanced Status Chips */}
|
||||
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 2, flexWrap: 'wrap' }}>
|
||||
{cacheStatus === 'cached' && (
|
||||
<Chip
|
||||
icon={<CheckCircleIcon />}
|
||||
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 && (
|
||||
<Tooltip title="Click to view data source information">
|
||||
<Chip
|
||||
icon={<InfoIcon />}
|
||||
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)'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Category Distribution Chips */}
|
||||
{Object.keys(fieldCountByCategory).length > 0 && (
|
||||
<Chip
|
||||
icon={<AutoAwesomeIcon />}
|
||||
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'
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
</Box>
|
||||
|
||||
{/* Data Source Information */}
|
||||
<Collapse in={showDataInfo}>
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{
|
||||
mb: 2,
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)',
|
||||
border: '1px solid rgba(255, 255, 255, 0.2)',
|
||||
color: 'white',
|
||||
'& .MuiAlert-icon': { color: 'rgba(255, 255, 255, 0.8)' }
|
||||
}}
|
||||
>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
<strong>Data Source:</strong> {dataSource || 'Onboarding Database'}
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ mb: 1 }}>
|
||||
<strong>Input Data Points:</strong> {Object.keys(inputDataPoints).length} available
|
||||
</Typography>
|
||||
<Typography variant="body2">
|
||||
<strong>Adaptive Monitoring:</strong> ALwrity continuously monitors databases for new data points to ensure you have the latest information.
|
||||
</Typography>
|
||||
</Alert>
|
||||
</Collapse>
|
||||
|
||||
{/* Conditional Action Buttons */}
|
||||
<Box sx={{ display: 'flex', gap: 2, flexWrap: 'wrap' }}>
|
||||
{cacheStatus === 'cached' ? (
|
||||
// Case 1: Data exists in cache - show refresh vs continue options
|
||||
<>
|
||||
<Tooltip title="Refresh with latest database data and AI analysis">
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={onRefreshAutofill}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
color: 'white',
|
||||
borderColor: 'rgba(255, 255, 255, 0.3)',
|
||||
'&:hover': {
|
||||
borderColor: 'rgba(255, 255, 255, 0.5)',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? 'Refreshing...' : 'Refresh & Autofill Inputs'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
|
||||
<Tooltip title="Continue with current autofilled values">
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<PlayArrowIcon />}
|
||||
onClick={onContinueWithPresent}
|
||||
sx={{
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.2)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.3)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Continue with Present Values
|
||||
</Button>
|
||||
</Tooltip>
|
||||
</>
|
||||
) : cacheStatus === 'partial' ? (
|
||||
// Case 2: Partial data - show refresh option
|
||||
<Tooltip title="Refresh with latest database data and AI analysis">
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={onRefreshAutofill}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
backgroundColor: 'rgba(255, 193, 7, 0.8)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(255, 193, 7, 0.9)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? 'Refreshing...' : 'Refresh & Autofill Strategy Inputs'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
) : (
|
||||
// Case 3: No data - show initial autofill
|
||||
<Tooltip title="Fetch latest data from database and autofill strategy inputs">
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={onRefreshAutofill}
|
||||
disabled={loading}
|
||||
sx={{
|
||||
backgroundColor: 'rgba(76, 175, 80, 0.8)',
|
||||
color: 'white',
|
||||
'&:hover': {
|
||||
backgroundColor: 'rgba(76, 175, 80, 0.9)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
{loading ? 'Autofilling...' : 'Refresh & Autofill Strategy Inputs'}
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Next Step Button - shown after autofill completion */}
|
||||
{showNextButton && (
|
||||
<Tooltip title="Scroll to review section and mark inputs as reviewed">
|
||||
<Button
|
||||
variant="contained"
|
||||
startIcon={<ArrowDownwardIcon />}
|
||||
onClick={onScrollToReview}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
|
||||
color: 'white',
|
||||
fontWeight: 600,
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
Next: Review Strategy Inputs & Create Strategy
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
|
||||
{/* Know More Details Button - shown when autofill data exists */}
|
||||
{hasAutofillData && Object.keys(autoPopulatedFields).length > 0 && (
|
||||
<Tooltip title="View detailed information about autofill data sources and AI analysis">
|
||||
<Button
|
||||
variant="text"
|
||||
startIcon={<VisibilityIcon />}
|
||||
onClick={() => setShowTransparencyModal(true)}
|
||||
sx={{
|
||||
color: 'rgba(255, 255, 255, 0.8)',
|
||||
'&:hover': {
|
||||
color: 'white',
|
||||
backgroundColor: 'rgba(255, 255, 255, 0.1)'
|
||||
}
|
||||
}}
|
||||
>
|
||||
Know More Details
|
||||
</Button>
|
||||
</Tooltip>
|
||||
)}
|
||||
</Box>
|
||||
</Box>
|
||||
</Paper>
|
||||
|
||||
{/* Autofill Data Transparency Modal */}
|
||||
<AutofillDataTransparency
|
||||
open={showTransparencyModal}
|
||||
onClose={() => setShowTransparencyModal(false)}
|
||||
autoPopulatedFields={autoPopulatedFields}
|
||||
dataSources={dataSources}
|
||||
inputDataPoints={inputDataPoints}
|
||||
personalizationData={personalizationData}
|
||||
confidenceScores={confidenceScores}
|
||||
lastAutofillTime={lastAutofillTime}
|
||||
dataSource={dataSource}
|
||||
/>
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -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<string, any> = {};
|
||||
const confidenceScores: Record<string, number> = {};
|
||||
|
||||
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');
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -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
|
||||
};
|
||||
|
||||
@@ -91,19 +91,9 @@ const StrategyAutofillTransparencyModal: React.FC<StrategyAutofillTransparencyMo
|
||||
const [lastMessageCount, setLastMessageCount] = useState(0);
|
||||
const [showNewMessageIndicator, setShowNewMessageIndicator] = useState(false);
|
||||
|
||||
// Debug logging for props
|
||||
// Debug logging for props - removed for cleaner console
|
||||
useEffect(() => {
|
||||
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
|
||||
|
||||
@@ -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<StrategyIntelligenceTabProps> = ({
|
||||
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<StrategyIntelligenceTabProps> = ({
|
||||
}
|
||||
}, [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<StrategyIntelligenceTabProps> = ({
|
||||
);
|
||||
}
|
||||
|
||||
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 (
|
||||
<Box sx={{ textAlign: 'center', p: 4 }}>
|
||||
<Typography variant="h6" color="text.secondary" gutterBottom>
|
||||
@@ -88,6 +171,11 @@ const StrategyIntelligenceTab: React.FC<StrategyIntelligenceTabProps> = ({
|
||||
<Typography variant="body2" color="text.secondary">
|
||||
Generate a comprehensive strategy first to view strategic intelligence.
|
||||
</Typography>
|
||||
{strategyData && (
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 2, color: 'text.secondary' }}>
|
||||
Strategy data exists but contains no valid components.
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
@@ -96,13 +184,14 @@ const StrategyIntelligenceTab: React.FC<StrategyIntelligenceTabProps> = ({
|
||||
<Box sx={{ p: 3 }}>
|
||||
{/* Header Section */}
|
||||
<StrategyHeader
|
||||
strategyData={strategyData}
|
||||
strategyConfirmed={false}
|
||||
strategyData={safeStrategyData}
|
||||
strategyConfirmed={strategyStatus === 'active'}
|
||||
strategyStatus={strategyStatus}
|
||||
onStartReview={handleStartReviewProcess}
|
||||
/>
|
||||
|
||||
{/* Review Progress Header - Only shown when review process is started */}
|
||||
{reviewProcessStarted && <ReviewProgressHeader strategyData={strategyData} />}
|
||||
{reviewProcessStarted && <ReviewProgressHeader strategyData={safeStrategyData} />}
|
||||
|
||||
{/* Strategy Intelligence Cards */}
|
||||
<Box
|
||||
@@ -131,14 +220,24 @@ const StrategyIntelligenceTab: React.FC<StrategyIntelligenceTabProps> = ({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<StrategicInsightsCard strategyData={strategyData} />
|
||||
<CompetitiveAnalysisCard strategyData={strategyData} />
|
||||
<PerformancePredictionsCard strategyData={strategyData} />
|
||||
<ImplementationRoadmapCard strategyData={strategyData} />
|
||||
<RiskAssessmentCard strategyData={strategyData} />
|
||||
<StrategyErrorBoundary>
|
||||
<StrategicInsightsCard strategyData={safeStrategyData} />
|
||||
</StrategyErrorBoundary>
|
||||
<StrategyErrorBoundary>
|
||||
<CompetitiveAnalysisCard strategyData={safeStrategyData} />
|
||||
</StrategyErrorBoundary>
|
||||
<StrategyErrorBoundary>
|
||||
<PerformancePredictionsCard strategyData={safeStrategyData} />
|
||||
</StrategyErrorBoundary>
|
||||
<StrategyErrorBoundary>
|
||||
<ImplementationRoadmapCard strategyData={safeStrategyData} />
|
||||
</StrategyErrorBoundary>
|
||||
<StrategyErrorBoundary>
|
||||
<RiskAssessmentCard strategyData={safeStrategyData} />
|
||||
</StrategyErrorBoundary>
|
||||
</Box>
|
||||
</Box>
|
||||
);
|
||||
};
|
||||
|
||||
export default StrategyIntelligenceTab;
|
||||
export default memo(StrategyIntelligenceTab);
|
||||
@@ -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<CompetitiveAnalysisCardProps> = ({ strat
|
||||
return description.split(' ').slice(0, 2).join(' ');
|
||||
};
|
||||
|
||||
|
||||
|
||||
if (!strategyData?.competitive_analysis) {
|
||||
return (
|
||||
<ProgressiveCard
|
||||
@@ -290,7 +293,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={gap}
|
||||
primary={safeRenderText(gap)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -324,7 +327,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primary={safeRenderText(opportunity)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -358,7 +361,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={recommendation}
|
||||
primary={safeRenderText(recommendation)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -399,14 +402,14 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={area}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={safeRenderText(area)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
@@ -433,7 +436,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={advantage}
|
||||
primary={safeRenderText(advantage)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -466,7 +469,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={advantage}
|
||||
primary={safeRenderText(advantage)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -510,7 +513,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={strength}
|
||||
primary={safeRenderText(strength)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -543,7 +546,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={weakness}
|
||||
primary={safeRenderText(weakness)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -576,7 +579,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primary={safeRenderText(opportunity)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -609,7 +612,7 @@ const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strat
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={threat}
|
||||
primary={safeRenderText(threat)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
|
||||
@@ -42,6 +42,7 @@ import {
|
||||
Close as CloseIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion, AnimatePresence } from 'framer-motion';
|
||||
import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
|
||||
|
||||
// Import our advanced chart components
|
||||
import {
|
||||
@@ -472,7 +473,7 @@ const EnhancedPerformanceVisualization: React.FC<EnhancedPerformanceVisualizatio
|
||||
<ListItemIcon>
|
||||
<LightbulbIcon color="warning" />
|
||||
</ListItemIcon>
|
||||
<ListItemText primary={recommendation} />
|
||||
<ListItemText primary={safeRenderText(recommendation)} />
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
@@ -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<ImplementationRoadmapCardProps> = ({ 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 (
|
||||
<ProgressiveCard
|
||||
@@ -212,7 +222,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={milestone}
|
||||
primary={safeRenderText(milestone)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -295,14 +305,14 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={task}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={safeRenderText(task)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
@@ -322,7 +332,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={milestone}
|
||||
primary={safeRenderText(milestone)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -354,7 +364,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={resource}
|
||||
primary={safeRenderText(resource)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -387,7 +397,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={milestone}
|
||||
primary={safeRenderText(milestone)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -421,7 +431,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={requirement}
|
||||
primary={safeRenderText(requirement)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -455,7 +465,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={path}
|
||||
primary={safeRenderText(path)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -531,7 +541,7 @@ const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ s
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={metric}
|
||||
primary={safeRenderText(metric)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
|
||||
@@ -34,6 +34,7 @@ import {
|
||||
Refresh as RefreshIcon
|
||||
} from '@mui/icons-material';
|
||||
import { motion } from 'framer-motion';
|
||||
import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
|
||||
|
||||
interface MonitoringTask {
|
||||
title: string;
|
||||
@@ -433,7 +434,7 @@ const MetricTransparencyCard: React.FC<MetricTransparencyCardProps> = ({
|
||||
<CheckCircleIcon sx={{ fontSize: 16, color: '#4caf50' }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={rec}
|
||||
primary={safeRenderText(rec)}
|
||||
sx={{
|
||||
'& .MuiListItemText-primary': {
|
||||
fontSize: '0.85rem',
|
||||
|
||||
@@ -24,6 +24,7 @@ import {
|
||||
getListItemStyles
|
||||
} from '../styles';
|
||||
import ProgressiveCard from './ProgressiveCard';
|
||||
import { safeRenderText, safeRenderArray, hasValidData, getFallbackValue } from '../utils/defensiveRendering';
|
||||
|
||||
interface PerformancePredictionsCardProps {
|
||||
strategyData: StrategyData | null;
|
||||
@@ -34,6 +35,15 @@ const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({
|
||||
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 (
|
||||
<ProgressiveCard
|
||||
|
||||
@@ -68,13 +68,7 @@ const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
|
||||
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 = () => {
|
||||
|
||||
@@ -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<ReviewConfirmationDialogProps> = ({
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={item}
|
||||
primary={safeRenderText(item)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body1',
|
||||
fontSize: '1rem',
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import React, { useState } from 'react';
|
||||
import React, { useState, memo } from 'react';
|
||||
import {
|
||||
Box,
|
||||
Typography,
|
||||
@@ -32,16 +32,15 @@ interface ReviewProgressHeaderProps {
|
||||
}
|
||||
|
||||
const ReviewProgressHeader: React.FC<ReviewProgressHeaderProps> = ({ 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<ReviewProgressHeaderProps> = ({ 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<ReviewProgressHeaderProps> = ({ 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<ReviewProgressHeaderProps> = ({ strategyDat
|
||||
};
|
||||
|
||||
const handleGenerateContentCalendar = () => {
|
||||
console.log('🎯 Generate content calendar clicked');
|
||||
|
||||
// Prepare strategy context for navigation
|
||||
const strategyContext = {
|
||||
@@ -430,4 +417,4 @@ const ReviewProgressHeader: React.FC<ReviewProgressHeaderProps> = ({ strategyDat
|
||||
);
|
||||
};
|
||||
|
||||
export default ReviewProgressHeader;
|
||||
export default memo(ReviewProgressHeader);
|
||||
|
||||
@@ -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<ReviewProgressSectionProps> = ({ 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 (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: -20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.8, ease: "easeOut" }}
|
||||
>
|
||||
{/* Header Section */}
|
||||
<Box sx={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', mb: 2 }}>
|
||||
<Box>
|
||||
<Typography variant="h5" sx={{
|
||||
fontWeight: 700,
|
||||
background: 'linear-gradient(45deg, #667eea, #764ba2)',
|
||||
backgroundClip: 'text',
|
||||
WebkitBackgroundClip: 'text',
|
||||
WebkitTextFillColor: 'transparent',
|
||||
mb: 0.5
|
||||
}}>
|
||||
Strategy Review Progress
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{ color: 'rgba(255, 255, 255, 0.8)', fontWeight: 500 }}>
|
||||
Review all strategy components to activate your content strategy
|
||||
</Typography>
|
||||
</Box>
|
||||
|
||||
{/* Progress Circle */}
|
||||
<Box sx={{ position: 'relative', display: 'flex', alignItems: 'center', gap: 2 }}>
|
||||
<Box sx={{ position: 'relative' }}>
|
||||
<CircularProgress
|
||||
variant="determinate"
|
||||
value={reviewProgress}
|
||||
size={60}
|
||||
thickness={4}
|
||||
sx={{
|
||||
color: getProgressColor(),
|
||||
'& .MuiCircularProgress-circle': {
|
||||
strokeLinecap: 'round',
|
||||
filter: 'drop-shadow(0 0 8px rgba(102, 126, 234, 0.5))'
|
||||
}
|
||||
}}
|
||||
/>
|
||||
<Box
|
||||
sx={{
|
||||
position: 'absolute',
|
||||
top: '50%',
|
||||
left: '50%',
|
||||
transform: 'translate(-50%, -50%)',
|
||||
textAlign: 'center'
|
||||
}}
|
||||
>
|
||||
<Typography variant="caption" sx={{
|
||||
fontSize: '0.7rem',
|
||||
fontWeight: 700,
|
||||
color: 'white',
|
||||
lineHeight: 1
|
||||
}}>
|
||||
{reviewProgress}%
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Progress Text */}
|
||||
<Box>
|
||||
<Typography variant="body2" sx={{
|
||||
fontWeight: 600,
|
||||
color: getProgressColor(),
|
||||
fontSize: '0.8rem'
|
||||
}}>
|
||||
{getProgressText()}
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{
|
||||
color: 'rgba(255, 255, 255, 0.7)',
|
||||
fontSize: '0.7rem'
|
||||
}}>
|
||||
{reviewedCount} of {totalCount} reviewed
|
||||
</Typography>
|
||||
</Box>
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Component Status */}
|
||||
<Box sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" sx={{
|
||||
fontWeight: 600,
|
||||
mb: 1,
|
||||
color: 'rgba(255, 255, 255, 0.9)'
|
||||
}}>
|
||||
Component Status:
|
||||
</Typography>
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
{components.map((component: StrategyComponent) => (
|
||||
<Tooltip
|
||||
key={component.id}
|
||||
title={`${component.title}: ${component.status === 'reviewed' ? 'Reviewed' : 'Pending Review'}`}
|
||||
arrow
|
||||
>
|
||||
<Badge
|
||||
badgeContent={
|
||||
component.status === 'reviewed' ? (
|
||||
<CheckCircleIcon sx={{ fontSize: 10, color: 'white' }} />
|
||||
) : (
|
||||
<ScheduleIcon sx={{ fontSize: 10, color: 'white' }} />
|
||||
)
|
||||
}
|
||||
color={component.status === 'reviewed' ? 'success' : 'warning'}
|
||||
>
|
||||
<Chip
|
||||
label={component.title}
|
||||
size="small"
|
||||
sx={{
|
||||
background: component.status === 'reviewed'
|
||||
? 'rgba(76, 175, 80, 0.3)'
|
||||
: 'rgba(255, 152, 0, 0.3)',
|
||||
color: component.status === 'reviewed' ? '#4caf50' : '#ff9800',
|
||||
border: `1px solid ${component.status === 'reviewed' ? 'rgba(76, 175, 80, 0.5)' : 'rgba(255, 152, 0, 0.5)'}`,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.65rem',
|
||||
height: 22,
|
||||
'&:hover': {
|
||||
background: component.status === 'reviewed'
|
||||
? 'rgba(76, 175, 80, 0.4)'
|
||||
: 'rgba(255, 152, 0, 0.4)',
|
||||
transform: 'translateY(-1px)'
|
||||
},
|
||||
transition: 'all 0.2s ease'
|
||||
}}
|
||||
/>
|
||||
</Badge>
|
||||
</Tooltip>
|
||||
))}
|
||||
</Box>
|
||||
</Box>
|
||||
|
||||
{/* Completion Status */}
|
||||
{isAllReviewed() && (
|
||||
<Box sx={{ display: 'flex', gap: 1, flexWrap: 'wrap' }}>
|
||||
<Chip
|
||||
icon={<CheckCircleIcon />}
|
||||
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
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</Box>
|
||||
)}
|
||||
|
||||
{/* Completion Message */}
|
||||
{isAllReviewed() && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, scale: 0.9 }}
|
||||
animate={{ opacity: 1, scale: 1 }}
|
||||
transition={{ duration: 0.3, delay: 0.2 }}
|
||||
>
|
||||
<Box sx={{
|
||||
mt: 1.5,
|
||||
p: 1.5,
|
||||
borderRadius: 1,
|
||||
background: isActivated() ? 'rgba(76, 175, 80, 0.15)' : 'rgba(76, 175, 80, 0.1)',
|
||||
border: '1px solid rgba(76, 175, 80, 0.2)',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<Typography variant="body2" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.success,
|
||||
fontWeight: 600,
|
||||
fontSize: '0.8rem'
|
||||
}}>
|
||||
{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.'
|
||||
}
|
||||
</Typography>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Enhanced Strategy Activation Button - Only shown when all components are reviewed and not yet activated */}
|
||||
{isAllReviewed() && !isActivated() && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3 }}
|
||||
>
|
||||
<Box sx={{ mt: 3, display: 'flex', justifyContent: 'center' }}>
|
||||
<EnhancedStrategyActivationButton
|
||||
strategyData={buttonStrategyData}
|
||||
strategyConfirmed={false}
|
||||
onConfirmStrategy={handleConfirmStrategy}
|
||||
onGenerateContentCalendar={handleGenerateContentCalendar}
|
||||
disabled={false}
|
||||
/>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
|
||||
{/* Strategy Activated Success Message */}
|
||||
{isActivated() && (
|
||||
<motion.div
|
||||
initial={{ opacity: 0, y: 20 }}
|
||||
animate={{ opacity: 1, y: 0 }}
|
||||
transition={{ duration: 0.5, delay: 0.3 }}
|
||||
>
|
||||
<Box sx={{
|
||||
mt: 3,
|
||||
p: 3,
|
||||
borderRadius: 2,
|
||||
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.1) 0%, rgba(76, 175, 80, 0.05) 100%)',
|
||||
border: '2px solid rgba(76, 175, 80, 0.3)',
|
||||
textAlign: 'center'
|
||||
}}>
|
||||
<Typography variant="h6" sx={{
|
||||
color: ANALYSIS_CARD_STYLES.colors.success,
|
||||
fontWeight: 700,
|
||||
mb: 1
|
||||
}}>
|
||||
🚀 Strategy Successfully Activated!
|
||||
</Typography>
|
||||
<Typography variant="body2" sx={{
|
||||
color: 'text.secondary',
|
||||
mb: 2
|
||||
}}>
|
||||
Your content strategy is now live and being monitored with AI-powered analytics.
|
||||
</Typography>
|
||||
<Button
|
||||
variant="contained"
|
||||
size="large"
|
||||
onClick={handleGenerateContentCalendar}
|
||||
startIcon={<AutoAwesomeIcon />}
|
||||
sx={{
|
||||
background: 'linear-gradient(135deg, #4caf50 0%, #45a049 100%)',
|
||||
borderRadius: 3,
|
||||
px: 4,
|
||||
py: 1.5,
|
||||
fontWeight: 600,
|
||||
textTransform: 'none',
|
||||
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
|
||||
'&:hover': {
|
||||
background: 'linear-gradient(135deg, #45a049 0%, #3d8b40 100%)',
|
||||
boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
|
||||
transform: 'translateY(-2px)'
|
||||
},
|
||||
transition: 'all 0.3s ease'
|
||||
}}
|
||||
>
|
||||
Generate Content Calendar
|
||||
</Button>
|
||||
</Box>
|
||||
</motion.div>
|
||||
)}
|
||||
</motion.div>
|
||||
);
|
||||
};
|
||||
|
||||
export default ReviewProgressSection;
|
||||
@@ -37,7 +37,7 @@ const ReviewStatusIndicator: React.FC<ReviewStatusIndicatorProps> = ({
|
||||
isReviewing = false
|
||||
}) => {
|
||||
// Debug logging for status
|
||||
console.log('🔧 ReviewStatusIndicator received status:', status);
|
||||
// Removed verbose logging for cleaner console
|
||||
const getStatusConfig = () => {
|
||||
switch (status) {
|
||||
case 'activated':
|
||||
|
||||
@@ -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<RiskAssessmentCardProps> = ({ 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 (
|
||||
<ProgressiveCard
|
||||
@@ -283,14 +293,14 @@ const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData })
|
||||
<ListItemIcon sx={listItemStyles.listItemIcon}>
|
||||
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={typeof strategy === 'string' ? strategy : strategy.mitigation || strategy.risk || 'Mitigation strategy'}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={safeRenderText(typeof strategy === 'string' ? strategy : strategy.mitigation || strategy.risk || 'Mitigation strategy')}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
@@ -345,14 +355,14 @@ const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData })
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={typeof risk === 'string' ? risk : risk.risk || 'Risk'}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
<ListItemText
|
||||
primary={safeRenderText(typeof risk === 'string' ? risk : risk.risk || 'Risk')}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
|
||||
}}
|
||||
/>
|
||||
</ListItem>
|
||||
))}
|
||||
</List>
|
||||
|
||||
@@ -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<StrategicInsightsCardProps> = ({ strategyD
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
|
||||
// Summary content - always visible
|
||||
const summaryContent = (
|
||||
<Box>
|
||||
@@ -236,7 +239,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={strength}
|
||||
primary={safeRenderText(strength)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -269,7 +272,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primary={safeRenderText(opportunity)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -327,7 +330,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={driver}
|
||||
primary={safeRenderText(driver)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -378,7 +381,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={strength}
|
||||
primary={safeRenderText(strength)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -411,7 +414,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primary={safeRenderText(opportunity)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -476,8 +479,8 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
opacity: 0.7
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={insight.insight}
|
||||
<ListItemText
|
||||
primary={safeRenderText(insight.insight)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
@@ -487,7 +490,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
<Box sx={{ mt: 0.5 }}>
|
||||
<Box sx={{ display: 'flex', gap: 1, mb: 0.5 }}>
|
||||
<Chip
|
||||
label={`P: ${insight.priority || 'Medium'}`}
|
||||
label={`P: ${safeRenderText(insight.priority) || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.priority === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
@@ -496,7 +499,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`I: ${insight.estimated_impact || 'Medium'}`}
|
||||
label={`I: ${safeRenderText(insight.estimated_impact) || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.estimated_impact === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
|
||||
@@ -505,7 +508,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`C: ${insight.confidence_level || 'Medium'}`}
|
||||
label={`C: ${safeRenderText(insight.confidence_level) || 'Medium'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(
|
||||
insight.confidence_level === 'High' ? ANALYSIS_CARD_STYLES.colors.success :
|
||||
@@ -514,19 +517,19 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
).chip}
|
||||
/>
|
||||
<Chip
|
||||
label={`T: ${insight.implementation_time || '3 months'}`}
|
||||
label={`T: ${safeRenderText(insight.implementation_time) || '3 months'}`}
|
||||
size="small"
|
||||
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
|
||||
/>
|
||||
</Box>
|
||||
{insight.reasoning && (
|
||||
{hasValidData(insight.reasoning) && (
|
||||
<Typography variant="caption" sx={{
|
||||
display: 'block',
|
||||
fontSize: '0.75rem',
|
||||
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
|
||||
fontStyle: 'italic'
|
||||
}}>
|
||||
<strong>Reasoning:</strong> {insight.reasoning}
|
||||
<strong>Reasoning:</strong> {safeRenderText(insight.reasoning)}
|
||||
</Typography>
|
||||
)}
|
||||
</Box>
|
||||
@@ -562,7 +565,7 @@ const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyD
|
||||
}} />
|
||||
</ListItemIcon>
|
||||
<ListItemText
|
||||
primary={opportunity}
|
||||
primary={safeRenderText(opportunity)}
|
||||
primaryTypographyProps={{
|
||||
variant: 'body2',
|
||||
fontSize: '0.875rem',
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
import React, { Component, ErrorInfo, ReactNode } from 'react';
|
||||
import { Box, Typography, Alert, Button } from '@mui/material';
|
||||
import { Refresh as RefreshIcon } from '@mui/icons-material';
|
||||
|
||||
interface Props {
|
||||
children: ReactNode;
|
||||
fallback?: ReactNode;
|
||||
}
|
||||
|
||||
interface State {
|
||||
hasError: boolean;
|
||||
error?: Error;
|
||||
}
|
||||
|
||||
class StrategyErrorBoundary extends Component<Props, State> {
|
||||
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 (
|
||||
<Box sx={{ p: 2, textAlign: 'center' }}>
|
||||
<Alert severity="error" sx={{ mb: 2 }}>
|
||||
<Typography variant="body2" gutterBottom>
|
||||
Error rendering strategy component
|
||||
</Typography>
|
||||
<Typography variant="caption" sx={{ display: 'block', mt: 1 }}>
|
||||
{this.state.error?.message || 'Unknown error occurred'}
|
||||
</Typography>
|
||||
</Alert>
|
||||
<Button
|
||||
variant="outlined"
|
||||
startIcon={<RefreshIcon />}
|
||||
onClick={this.handleRetry}
|
||||
size="small"
|
||||
>
|
||||
Retry
|
||||
</Button>
|
||||
</Box>
|
||||
);
|
||||
}
|
||||
|
||||
return this.props.children;
|
||||
}
|
||||
}
|
||||
|
||||
export default StrategyErrorBoundary;
|
||||
@@ -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<StrategyHeaderProps> = ({ strategyData, strategyConfirmed, onStartReview }) => {
|
||||
const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyConfirmed, strategyStatus = 'none', onStartReview, disableCardWrapper = false }) => {
|
||||
const [showNextStepText, setShowNextStepText] = useState(false);
|
||||
|
||||
if (!strategyData) return null;
|
||||
|
||||
@@ -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<string, any> => {
|
||||
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<string, string> = {
|
||||
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';
|
||||
};
|
||||
@@ -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);
|
||||
};
|
||||
@@ -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<StrategyOnboardingDialogProps> = ({
|
||||
|
||||
@@ -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 = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Pending Strategy Status Banner */}
|
||||
{strategyStatus === 'pending' && (
|
||||
<Alert
|
||||
severity="info"
|
||||
sx={{ mb: 3 }}
|
||||
action={
|
||||
<Button
|
||||
color="inherit"
|
||||
size="small"
|
||||
onClick={() => setShowOnboarding(true)}
|
||||
startIcon={<PlayArrowIcon />}
|
||||
>
|
||||
Review & Activate
|
||||
</Button>
|
||||
}
|
||||
>
|
||||
<Typography variant="body1">
|
||||
<strong>Strategy Ready for Review:</strong> Your AI-generated content strategy is ready! Please review all components and confirm to activate your strategy.
|
||||
</Typography>
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* Strategy Status Banner */}
|
||||
{strategyStatus === 'inactive' && (
|
||||
<Alert
|
||||
@@ -310,14 +563,40 @@ const ContentStrategyTab: React.FC = () => {
|
||||
</Alert>
|
||||
)}
|
||||
|
||||
{/* 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') && (
|
||||
<Paper sx={{ width: '100%', mb: 3 }}>
|
||||
<StrategyIntelligenceTab
|
||||
strategyData={strategyData}
|
||||
loading={strategyDataLoading}
|
||||
error={strategyDataError}
|
||||
/>
|
||||
{strategyDataLoading ? (
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', p: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" sx={{ ml: 2, color: 'text.secondary' }}>
|
||||
Loading strategy data...
|
||||
</Typography>
|
||||
</Box>
|
||||
) : strategyDataError ? (
|
||||
<Alert severity="error" sx={{ m: 2 }}>
|
||||
{strategyDataError}
|
||||
</Alert>
|
||||
) : (
|
||||
<StrategyIntelligenceTab
|
||||
strategyData={strategyData}
|
||||
loading={strategyDataLoading}
|
||||
error={strategyDataError}
|
||||
strategyStatus={strategyStatus}
|
||||
/>
|
||||
)}
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
{/* Loading indicator for initial data */}
|
||||
{Object.values(dataLoading).some(loading => loading) && (
|
||||
<Paper sx={{ width: '100%', mb: 3 }}>
|
||||
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', p: 4 }}>
|
||||
<CircularProgress />
|
||||
<Typography variant="body2" sx={{ ml: 2, color: 'text.secondary' }}>
|
||||
Loading dashboard data...
|
||||
</Typography>
|
||||
</Box>
|
||||
</Paper>
|
||||
)}
|
||||
|
||||
|
||||
@@ -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 ||
|
||||
|
||||
@@ -282,11 +282,6 @@ export const StrategyCalendarProvider: React.FC<StrategyCalendarProviderProps> =
|
||||
// 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]);
|
||||
|
||||
|
||||
@@ -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<any> {
|
||||
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<any> {
|
||||
try {
|
||||
@@ -517,7 +538,7 @@ class ContentPlanningAPI {
|
||||
|
||||
async getComprehensiveUserData(userId?: number): Promise<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
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<any> {
|
||||
return this.handleRequest(async () => {
|
||||
const response = await apiClient.post(`${this.baseURL}/content-strategy/ai-generation/generate-comprehensive-strategy-polling`, {
|
||||
|
||||
@@ -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<ContentStrategy>) => Promise<void>;
|
||||
deleteStrategy: (id: string) => Promise<void>;
|
||||
setCurrentStrategy: (strategy: ContentStrategy | null) => void;
|
||||
setLatestGeneratedStrategy: (strategy: any | null) => void;
|
||||
|
||||
// Calendar actions
|
||||
createEvent: (event: Omit<CalendarEvent, 'id' | 'created_at' | 'updated_at'>) => Promise<void>;
|
||||
@@ -269,6 +271,7 @@ export const useContentPlanningStore = create<ContentPlanningStore>((set, get) =
|
||||
// Initial state
|
||||
strategies: [],
|
||||
currentStrategy: null,
|
||||
latestGeneratedStrategy: null,
|
||||
calendarEvents: [],
|
||||
gapAnalyses: [],
|
||||
aiRecommendations: [],
|
||||
@@ -345,6 +348,18 @@ export const useContentPlanningStore = create<ContentPlanningStore>((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<ContentPlanningStore>((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<ContentPlanningStore>((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<ContentPlanningStore>((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<ContentPlanningStore>((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<ContentPlanningStore>((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: [] });
|
||||
}
|
||||
},
|
||||
|
||||
@@ -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<StrategyBuilderStore>((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<StrategyBuilderStore>((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<string, any> = {};
|
||||
@@ -663,61 +773,77 @@ export const useStrategyBuilderStore = create<StrategyBuilderStore>((set, get) =
|
||||
const personalizationData: Record<string, any> = {};
|
||||
const confidenceScores: Record<string, number> = {};
|
||||
|
||||
// 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<string, any> = {
|
||||
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<StrategyBuilderStore>((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<string, number> = {};
|
||||
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<string, number> = {};
|
||||
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
|
||||
};
|
||||
}
|
||||
}));
|
||||
|
||||
@@ -171,7 +171,24 @@ export const useStrategyReviewStore = create<ReviewState>()(
|
||||
|
||||
// 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<ReviewState>()(
|
||||
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 });
|
||||
},
|
||||
|
||||
|
||||
Reference in New Issue
Block a user