ALwrity version 0.5.5

This commit is contained in:
ajaysi
2025-08-20 20:22:56 +05:30
parent 74e22b421a
commit 3f2f4d7b8c
43 changed files with 2938 additions and 690 deletions

View File

@@ -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:

View File

@@ -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()})

View File

@@ -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."""

View File

@@ -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

View File

@@ -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"
]
}

View File

@@ -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>

View File

@@ -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

View File

@@ -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
}}

View File

@@ -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) => (

View File

@@ -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'}`);

View File

@@ -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;

View File

@@ -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>
);
};

View File

@@ -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');
}

View File

@@ -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

View File

@@ -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);

View File

@@ -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
};

View File

@@ -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

View File

@@ -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);

View File

@@ -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',

View File

@@ -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>

View File

@@ -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',

View File

@@ -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',

View File

@@ -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

View File

@@ -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 = () => {

View File

@@ -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',

View File

@@ -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);

View File

@@ -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;

View File

@@ -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':

View File

@@ -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>

View File

@@ -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',

View File

@@ -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;

View File

@@ -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;

View File

@@ -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';
};

View File

@@ -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);
};

View File

@@ -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> = ({

View File

@@ -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>
)}

View File

@@ -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 ||

View File

@@ -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]);

View File

@@ -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`, {

View File

@@ -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: [] });
}
},

View File

@@ -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
};
}
}));

View File

@@ -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 });
},