Alwrity version 0.5.4

This commit is contained in:
ajaysi
2025-08-12 22:35:21 +05:30
parent 39b96c44da
commit 66ece49705
44 changed files with 9577 additions and 2112 deletions

View File

@@ -1,232 +0,0 @@
import re
import os
import PyPDF2
import openai
import streamlit as st
import tempfile
from loguru import logger
from lib.ai_writers.ai_news_article_writer import ai_news_generation
from lib.ai_writers.ai_finance_report_generator.ai_financial_dashboard import get_dashboard
from lib.ai_writers.ai_facebook_writer.facebook_ai_writer import facebook_main_menu
from lib.ai_writers.linkedin_writer.linkedin_ai_writer import linkedin_main_menu
from lib.ai_writers.twitter_writers.twitter_dashboard import run_dashboard
from lib.ai_writers.insta_ai_writer import insta_writer
from lib.ai_writers.youtube_writers.youtube_ai_writer import youtube_main_menu
from lib.ai_writers.ai_essay_writer import ai_essay_generator
from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image
#from lib.content_planning_calender.content_planning_agents_alwrity_crew import ai_agents_content_planner
from lib.gpt_providers.text_generation.main_text_generation import llm_text_gen
def ai_agents_team():
# Define options for AI Content Teams
st.title("🐲 Your AI Agents Teams")
st.markdown("""Alwrity offers AI agents team for content creators to easily modify them for their needs.
Abstracting tech & plumbing, easily define role, goal, task. Use different AI agents framework.""")
options = [
"AI Planning Team",
"AI Content Creation Team"
]
# Radio button for choosing an AI Content Team
selected_team = st.radio("**Choose AI Agents Team:**", options)
if selected_team == "AI Planning Team":
st.title("AI Agents for Content Ideation")
plan_keywords = st.text_input(
"Enter Keywords to get 2 months content calendar:",
placeholder="Enter keywords to generate AI content calendar:",
help="Enter at least two words for better results."
)
if st.button("Get calendar"):
if plan_keywords and len(plan_keywords.split()) >= 2:
with st.spinner("Get Content Plan..."):
try:
#plan_content = ai_agents_content_planner(plan_keywords)
st.success(f"Coming soon: Content plan for: {plan_keywords}")
#st.markdown(plan_content)
except Exception as err:
st.error(f"Failed to generate content plan: {err}")
else:
st.error("🚫 Single keywords are just too vague. Try again.")
elif selected_team == "AI Content Creation Team":
content_agents()
def content_agents():
st.markdown("AI Agents Team for Content Writing")
content_keywords = st.text_input(
"Enter Main Domain Keywords of your business:",
placeholder="Better keywords, Better content. Get keywords from Google search",
help="These keywords define your main business sector, blogging niche, Industry, domain etc"
)
if st.button("Start Writing"):
if content_keywords and len(content_keywords.split()) >= 2:
with st.spinner("Generating Content..."):
try:
#calendar_content = ai_agents_writers(content_keywords)
st.success(f"🚫 Not implemented yet: {content_keywords}")
#st.markdown(calendar_content)
except Exception as err:
st.error(f"🚫 Failed to generate content with AI Agents: {err}")
else:
st.error("🚫 Single keywords are just too vague. Try again.")
def essay_writer():
st.title("AI Essay Writer 📝")
st.write("Select your essay type, education level, and desired length, then let AI generate an essay for you. ✨")
# Define essay types and education levels
essay_types = [
"📖 Argumentative - Forming an opinion via research. Building an evidence-based argument.",
"📚 Expository - Knowledge of a topic. Communicating information clearly.",
"✒️ Narrative - Creative language use. Presenting a compelling narrative.",
"🎨 Descriptive - Creative language use. Describing sensory details."
]
education_levels = [
"🏫 Primary School",
"🏫 High School",
"🎓 College",
"🎓 Graduate School"
]
# Define the options for number of pages
num_pages_options = [
"📄 Short Form (1-2 pages)",
"📄📄 Medium Form (3-5 pages)",
"📄📄📄 Long Form (6+ pages)"
]
# Create columns for input fields
col1, col2 = st.columns(2)
with col1:
# Ask the user for the title of the essay
essay_title = st.text_input("📝 Essay Title", placeholder="Enter the title of your essay", help="Provide a clear and concise title for your essay.")
# Ask the user for type of essay
selected_essay_type = st.selectbox("📚 Type of Essay", options=essay_types, help="Choose the type of essay you want to write.")
with col2:
# Ask the user for level of education
selected_education_level = st.selectbox("🎓 Level of Education", options=education_levels, help="Choose your level of education.")
# Ask the user for number of pages
selected_num_pages = st.selectbox("📄 Number of Pages", options=num_pages_options, help="Select the length of your essay.")
if st.button("🚀 Generate Essay"):
if essay_title:
st.success("Generating your essay... ✨")
ai_essay_generator(essay_title, selected_essay_type, selected_education_level, selected_num_pages)
else:
st.error("Please enter a valid title for your essay. 🚫")
def ai_news_writer():
""" AI News Writer """
st.markdown("<h1>📰 AI News Writer 🗞️ </h1>", unsafe_allow_html=True)
# Input for news keywords
news_keywords = st.text_input(
"**🔑 Enter Keywords from News Headlines:**",
placeholder="Describe the News article in 3-5 words. Enter main keywords describing the News Event:",
help="Enter at least two words for better results."
)
if news_keywords and len(news_keywords.split()) < 2:
st.error("🚫 News keywords should be at least two words long. Least, you can do..")
# Selectbox for country and language
countries = [
("es", "Spain"),
("vn", "Vietnam"),
("pk", "Pakistan"),
("in", "India"),
("de", "Germany"),
("cn", "China")
]
languages = [
("en", "English"),
("es", "Spanish"),
("vi", "Vietnamese"),
("ar", "Arabic"),
("hi", "Hindi"),
("de", "German"),
("zh-cn", "Chinese")
]
col1, col2 = st.columns(2)
with col1:
news_country = st.selectbox("**🌍 Select Origin Country of News Event:**",
countries, format_func=lambda x: x[1], help="Which country did the NEWS originate from ?")
with col2:
news_language = st.selectbox("**🗣️ Select News Article Language to Search For:**",
languages, format_func=lambda x: x[1], help="Language to output News Article in ?")
if st.button("📰 Generate News Report"):
if news_keywords and len(news_keywords.split()) >= 2:
with st.spinner("Generating News Report... ⏳"):
try:
news_report = ai_news_generation(news_keywords, news_country, news_language)
st.success(f"Successfully generated news report on: {news_keywords} 🎉")
st.markdown(news_report)
except Exception as err:
st.error(f"Failed to generate news report: {err}")
else:
st.error("Please enter valid keywords for the news report. 🚫")
def ai_finance_ta_writer():
st.markdown("<div class='sub-header'>AI Financial Technical Analysis Writer</div>", unsafe_allow_html=True)
ticker_symbol = st.text_input(
"Enter Ticker Symbol for TA:",
placeholder="Enter a valid Ticker Symbol (Examples: IBM, BABA, HDFCBANK.NS, TATAMOTORS.NS etc)",
help="Be sure of the ticker symbol. Double-check it! Examples: IBM, BABA, HDFCBANK.NS, TATAMOTORS.NS"
)
if st.button("Generate TA Report"):
if ticker_symbol:
with st.spinner("Generating TA Report..."):
try:
# Get dashboard instance and generate technical analysis
dashboard = get_dashboard()
ta_report = dashboard.generate_technical_analysis(ticker_symbol)
st.success(f"Successfully generated TA report for: {ticker_symbol}")
st.markdown(ta_report)
except Exception as err:
st.error(f"🚫 Check ticker symbol: Failed to write Financial Technical Analysis. Error: {err}")
else:
st.error("🚫 Provide a valid Ticker Symbol. Don't waste my time.")
def ai_social_writer():
# Define social media platforms as radio buttons
social_media_options = [
("facebook", "Facebook"),
("linkedin", "LinkedIn"),
("twitter", "Twitter"),
("instagram", "Instagram"),
("youtube", "YouTube")
]
# Selectbox for choosing a platform
selected_platform = st.radio("Choose a Social Media Platform:", social_media_options, format_func=lambda x: x[1])
if "facebook" in selected_platform:
facebook_main_menu()
elif "linkedin" in selected_platform:
linkedin_main_menu()
elif "twitter" in selected_platform:
run_dashboard()
elif "instagram" in selected_platform:
insta_writer()
elif "youtube" in selected_platform:
youtube_main_menu()

View File

@@ -1,310 +0,0 @@
"""Test configuration settings page for ALwrity."""
import streamlit as st
from loguru import logger
import asyncio
from lib.web_crawlers.async_web_crawler import AsyncWebCrawlerService
from pages.style_utils import (
get_test_config_styles,
get_glass_container,
get_info_section,
get_example_box,
get_analysis_section,
get_style_guide_html
)
import sys
from lib.personalization.style_analyzer import StyleAnalyzer
# Set page config - must be the first Streamlit command
st.set_page_config(
layout="wide",
initial_sidebar_state="collapsed",
menu_items={
'Get Help': None,
'Report a bug': None,
'About': None
}
)
import yaml
from pathlib import Path
import os
from loguru import logger
from lib.utils.read_main_config_params import get_personalization_settings
from lib.web_crawlers.crawl4ai_web_crawler import analyze_style
# Configure logger
logger.remove() # Remove default handler
logger.add(
"logs/test_config_settings.log",
rotation="500 MB",
retention="10 days",
level="DEBUG",
format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}",
backtrace=True,
diagnose=True
)
logger.add(
sys.stdout,
level="INFO",
format="<green>{time:YYYY-MM-DD HH:mm:ss}</green> | <level>{level: <8}</level> | <cyan>{message}</cyan>"
)
# Apply CSS styles
st.markdown(get_test_config_styles(), unsafe_allow_html=True)
def load_website_url():
"""Load website URL from config file."""
try:
logger.debug("Loading website URL from config file")
config_path = Path(os.environ["ALWRITY_CONFIG"])
config = yaml.safe_load(config_path.read_text())
url = config.get('website', {}).get('url', '')
logger.info(f"Loaded website URL: {url}")
return url
except Exception as e:
logger.error(f"Error loading website URL: {str(e)}", exc_info=True)
return ''
def display_style_analysis(analysis_results: dict):
"""Display the style analysis results in a structured format."""
try:
# Writing Style Section
st.markdown("### 🎨 Writing Style Analysis")
writing_style = analysis_results.get("writing_style", {})
writing_style_content = f"""
<ul>
<li><strong>Tone:</strong> {writing_style.get("tone", "N/A")}</li>
<li><strong>Voice:</strong> {writing_style.get("voice", "N/A")}</li>
<li><strong>Complexity:</strong> {writing_style.get("complexity", "N/A")}</li>
<li><strong>Engagement Level:</strong> {writing_style.get("engagement_level", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Writing Style", writing_style_content), unsafe_allow_html=True)
# Content Characteristics Section
content_chars = analysis_results.get("content_characteristics", {})
content_chars_content = f"""
<ul>
<li><strong>Sentence Structure:</strong> {content_chars.get("sentence_structure", "N/A")}</li>
<li><strong>Vocabulary Level:</strong> {content_chars.get("vocabulary_level", "N/A")}</li>
<li><strong>Paragraph Organization:</strong> {content_chars.get("paragraph_organization", "N/A")}</li>
<li><strong>Content Flow:</strong> {content_chars.get("content_flow", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Content Characteristics", content_chars_content), unsafe_allow_html=True)
# Target Audience Section
target_audience = analysis_results.get("target_audience", {})
target_audience_content = f"""
<ul>
<li><strong>Demographics:</strong> {', '.join(target_audience.get("demographics", ["N/A"]))}</li>
<li><strong>Expertise Level:</strong> {target_audience.get("expertise_level", "N/A")}</li>
<li><strong>Industry Focus:</strong> {target_audience.get("industry_focus", "N/A")}</li>
<li><strong>Geographic Focus:</strong> {target_audience.get("geographic_focus", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Target Audience", target_audience_content), unsafe_allow_html=True)
# Content Type Section
content_type = analysis_results.get("content_type", {})
content_type_content = f"""
<ul>
<li><strong>Primary Type:</strong> {content_type.get("primary_type", "N/A")}</li>
<li><strong>Secondary Types:</strong> {', '.join(content_type.get("secondary_types", ["N/A"]))}</li>
<li><strong>Purpose:</strong> {content_type.get("purpose", "N/A")}</li>
<li><strong>Call to Action:</strong> {content_type.get("call_to_action", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Content Type", content_type_content), unsafe_allow_html=True)
# Recommended Settings Section
recommended = analysis_results.get("recommended_settings", {})
recommended_content = f"""
<ul>
<li><strong>Writing Tone:</strong> {recommended.get("writing_tone", "N/A")}</li>
<li><strong>Target Audience:</strong> {recommended.get("target_audience", "N/A")}</li>
<li><strong>Content Type:</strong> {recommended.get("content_type", "N/A")}</li>
<li><strong>Creativity Level:</strong> {recommended.get("creativity_level", "N/A")}</li>
<li><strong>Geographic Location:</strong> {recommended.get("geographic_location", "N/A")}</li>
</ul>
"""
st.markdown(get_analysis_section("Recommended Settings", recommended_content), unsafe_allow_html=True)
except Exception as e:
logger.error(f"Error displaying style analysis: {str(e)}")
st.error(f"Error displaying analysis results: {str(e)}")
def render_test_config_settings():
"""Render the test configuration settings page."""
try:
logger.info("Starting to render test configuration settings")
# Add back button at the top
col1, col2 = st.columns([1, 3])
with col1:
if st.button("← Back to Personalization Setup"):
logger.info("User clicked back to personalization setup")
# Set session state for navigation
st.session_state.current_step = 4
st.session_state.next_step = "personalization_setup"
# Navigate back to the main page where personalization setup is rendered
st.switch_page("alwrity.py")
# Title and description
st.title("🎨 Find Your Style with ALwrity")
st.markdown(get_glass_container(
"<p>Enter a website URL or provide content samples to analyze your writing style and get personalized recommendations.</p>"
), unsafe_allow_html=True)
# Create two columns for the layout
col1, col2 = st.columns([2, 1])
with col1:
# Website URL input
st.markdown("### Website URL")
url = st.text_input(
"Enter your website URL",
placeholder="https://example.com",
help="Provide your website URL to analyze your content style. Leave empty if you want to provide written samples instead."
)
logger.debug(f"Website URL input value: {url}")
# Alternative: Written samples
if not url:
st.markdown("### Written Samples")
st.markdown(get_info_section("""
<p>No website URL? No problem! You can provide written samples of your content instead.</p>
<p>Share your best articles, blog posts, or any content that represents your writing style.</p>
"""), unsafe_allow_html=True)
samples = st.text_area(
"Paste your content samples here",
help="Paste 2-3 samples of your best content. This helps ALwrity understand your writing style."
)
logger.debug(f"Sample text length: {len(samples) if samples else 0}")
st.markdown('</div>', unsafe_allow_html=True)
# ALwrity Style button
st.markdown("<div style='height: 20px'></div>", unsafe_allow_html=True)
if st.button("🎨 ALwrity Style", use_container_width=True):
if url:
with st.status("Starting style analysis...", expanded=True) as status:
try:
logger.info(f"Starting style analysis for URL: {url}")
# Step 1: Initialize crawler
status.update(label="Step 1/4: Initializing web crawler...", state="running")
crawler_service = AsyncWebCrawlerService()
# Step 2: Crawl website
status.update(label="Step 2/4: Crawling website content...", state="running")
loop = asyncio.new_event_loop()
asyncio.set_event_loop(loop)
result = loop.run_until_complete(crawler_service.crawl_website(url))
loop.close()
if result.get('success', False):
content = result.get('content', {})
# Step 3: Initialize style analyzer
status.update(label="Step 3/4: Analyzing content style...", state="running")
style_analyzer = StyleAnalyzer()
# Step 4: Perform style analysis
status.update(label="Step 4/4: Generating style recommendations...", state="running")
style_analysis = style_analyzer.analyze_content_style(content)
if style_analysis.get('error'):
status.update(label="Analysis failed", state="error")
st.error(f"Style analysis failed: {style_analysis['error']}")
else:
status.update(label="Analysis complete!", state="complete")
# Display style analysis results
display_style_analysis(style_analysis)
# Display original content in tabs
tab1, tab2, tab3 = st.tabs(["Content", "Metadata", "Links"])
with tab1:
st.markdown("### Main Content")
st.markdown(content.get('main_content', 'No content found'))
with tab2:
st.markdown("### Metadata")
st.markdown(f"""
**Title:** {content.get('title', 'No title found')}
**Description:** {content.get('description', 'No description found')}
**Meta Tags:**
{content.get('meta_tags', {})}
""")
with tab3:
st.markdown("### Links")
for link in content.get('links', []):
st.markdown(f"- [{link.get('text', '')}]({link.get('href', '')})")
else:
status.update(label="Crawling failed", state="error")
st.error(f"Failed to analyze website: {result.get('error', 'Unknown error')}")
except Exception as e:
logger.error(f"Error during style analysis: {str(e)}")
st.error(f"Analysis failed: {str(e)}")
elif samples:
with st.spinner("Analyzing content samples..."):
try:
# TODO: Implement sample text analysis
st.info("Sample text analysis coming soon!")
except Exception as e:
logger.error(f"Error analyzing samples: {str(e)}")
st.error(f"Analysis failed: {str(e)}")
else:
st.warning("Please provide either a website URL or content samples")
with col2:
st.markdown("""
### How ALwrity Discovers Your Style
**AI-Powered Style Analysis**
ALwrity AI analyzes your existing content to understand your unique writing style and preferences. This helps us generate content that matches your voice perfectly.
**Step 1: Content Analysis**
We'll analyze your website content or written samples to understand:
- Writing tone and voice
- Vocabulary and language style
- Content structure and formatting
- Target audience and engagement style
**Step 2: Style Recommendations**
Based on the analysis, we'll provide:
- Personalized writing guidelines
- Content structure templates
- Tone and voice recommendations
- Audience engagement strategies
**Step 3: Content Generation**
Finally, we'll use these insights to:
- Generate content that matches your style
- Maintain consistency across all content
- Optimize for your target audience
- Ensure brand voice alignment
""")
except Exception as e:
logger.error(f"Error in render_test_config_settings: {str(e)}")
st.error(f"An error occurred: {str(e)}")
if __name__ == "__main__":
logger.info("Starting test config settings page")
render_test_config_settings()
logger.info("Test config settings page rendered successfully")

View File

@@ -1,23 +0,0 @@
import streamlit as st
from streamlit_mic_recorder import speech_to_text
def record_voice(language="en"):
# https://github.com/B4PT0R/streamlit-mic-recorder?tab=readme-ov-file#example
state = st.session_state
if "text_received" not in state:
state.text_received = []
text = speech_to_text(
start_prompt="🎙Press & Speak🔊",
stop_prompt="🔇Stop Recording🚨",
language=language,
use_container_width=True,
just_once=False,
)
if text:
state.text_received.append(text)
result = ""
for text in state.text_received:
result += text
state.text_received = []
return result if result else None

View File

@@ -1,45 +0,0 @@
"""Data models for website analysis results."""
from dataclasses import dataclass
from typing import List, Dict, Optional
from datetime import datetime
@dataclass
class SEORecommendation:
"""A single SEO recommendation."""
priority: str # 'high', 'medium', 'low'
category: str # 'content', 'technical', 'meta', etc.
issue: str
recommendation: str
impact: str
@dataclass
class MetaTagAnalysis:
"""Analysis of meta tags."""
title: Dict[str, str] # {'status': 'good', 'value': 'actual title', 'recommendation': 'suggestion'}
description: Dict[str, str]
keywords: Dict[str, str]
has_robots: bool
has_sitemap: bool
@dataclass
class ContentAnalysis:
"""Analysis of page content."""
word_count: int
headings_structure: Dict[str, int] # {'h1': 1, 'h2': 3, etc}
keyword_density: Dict[str, float]
readability_score: float
content_quality_score: float
@dataclass
class SEOAnalysisResult:
"""Complete SEO analysis result."""
url: str
analyzed_at: datetime
overall_score: float # 0-100
meta_tags: MetaTagAnalysis
content: ContentAnalysis
recommendations: List[SEORecommendation]
errors: List[str]
warnings: List[str]
success: bool

View File

@@ -0,0 +1,8 @@
"""
Content Strategy Educational Content Module
Provides educational content and messages for strategy generation process.
"""
from .educational_content import EducationalContentManager
__all__ = ['EducationalContentManager']

View File

@@ -0,0 +1,319 @@
"""
Educational Content Manager
Manages educational content and messages for strategy generation process.
"""
from typing import Dict, Any, List
from datetime import datetime
class EducationalContentManager:
"""Manages educational content for strategy generation steps."""
@staticmethod
def get_initialization_content() -> Dict[str, Any]:
"""Get educational content for initialization step."""
return {
"title": "🤖 AI-Powered Strategy Generation",
"description": "Initializing AI analysis and preparing educational content...",
"details": [
"🔧 Setting up AI services",
"📊 Loading user context",
"🎯 Preparing strategy framework",
"📚 Generating educational content"
],
"insight": "We're getting everything ready for your personalized AI strategy generation.",
"estimated_time": "2-3 minutes total"
}
@staticmethod
def get_step_content(step: int) -> Dict[str, Any]:
"""Get educational content for a specific step."""
step_content = {
1: EducationalContentManager._get_user_context_content(),
2: EducationalContentManager._get_foundation_content(),
3: EducationalContentManager._get_strategic_insights_content(),
4: EducationalContentManager._get_competitive_analysis_content(),
5: EducationalContentManager._get_performance_predictions_content(),
6: EducationalContentManager._get_implementation_roadmap_content(),
7: EducationalContentManager._get_compilation_content(),
8: EducationalContentManager._get_completion_content()
}
return step_content.get(step, EducationalContentManager._get_default_content())
@staticmethod
def get_step_completion_content(step: int, result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get educational content for step completion."""
completion_content = {
3: EducationalContentManager._get_strategic_insights_completion(result_data),
4: EducationalContentManager._get_competitive_analysis_completion(result_data),
5: EducationalContentManager._get_performance_predictions_completion(result_data),
6: EducationalContentManager._get_implementation_roadmap_completion(result_data)
}
return completion_content.get(step, EducationalContentManager._get_default_completion())
@staticmethod
def _get_user_context_content() -> Dict[str, Any]:
"""Get educational content for user context analysis."""
return {
"title": "🔍 Analyzing Your Data",
"description": "We're gathering all your onboarding information to create a personalized strategy.",
"details": [
"📊 Website analysis data",
"🎯 Research preferences",
"🔑 API configurations",
"📈 Historical performance metrics"
],
"insight": "Your data helps us understand your business context, target audience, and competitive landscape.",
"ai_prompt_preview": "Analyzing user onboarding data to extract business context, audience insights, and competitive positioning..."
}
@staticmethod
def _get_foundation_content() -> Dict[str, Any]:
"""Get educational content for foundation building."""
return {
"title": "🏗️ Building Foundation",
"description": "Creating the core strategy framework based on your business objectives.",
"details": [
"🎯 Business objectives mapping",
"📊 Target metrics definition",
"💰 Budget allocation strategy",
"⏰ Timeline planning"
],
"insight": "A solid foundation ensures your content strategy aligns with business goals and resources.",
"ai_prompt_preview": "Generating strategic foundation: business objectives, target metrics, budget allocation, and timeline planning..."
}
@staticmethod
def _get_strategic_insights_content() -> Dict[str, Any]:
"""Get educational content for strategic insights generation."""
return {
"title": "🧠 Strategic Intelligence Analysis",
"description": "AI is analyzing your market position and identifying strategic opportunities.",
"details": [
"🎯 Market positioning analysis",
"💡 Opportunity identification",
"📈 Growth potential assessment",
"🎪 Competitive advantage mapping"
],
"insight": "Strategic insights help you understand where you stand in the market and how to differentiate.",
"ai_prompt_preview": "Analyzing market position, identifying strategic opportunities, assessing growth potential, and mapping competitive advantages...",
"estimated_time": "15-20 seconds"
}
@staticmethod
def _get_competitive_analysis_content() -> Dict[str, Any]:
"""Get educational content for competitive analysis."""
return {
"title": "🔍 Competitive Intelligence Analysis",
"description": "AI is analyzing your competitors to identify gaps and opportunities.",
"details": [
"🏢 Competitor content strategies",
"📊 Market gap analysis",
"🎯 Differentiation opportunities",
"📈 Industry trend analysis"
],
"insight": "Understanding your competitors helps you find unique angles and underserved market segments.",
"ai_prompt_preview": "Analyzing competitor content strategies, identifying market gaps, finding differentiation opportunities, and assessing industry trends...",
"estimated_time": "20-25 seconds"
}
@staticmethod
def _get_performance_predictions_content() -> Dict[str, Any]:
"""Get educational content for performance predictions."""
return {
"title": "📊 Performance Forecasting",
"description": "AI is predicting content performance and ROI based on industry data.",
"details": [
"📈 Traffic growth projections",
"💰 ROI predictions",
"🎯 Conversion rate estimates",
"📊 Engagement metrics forecasting"
],
"insight": "Performance predictions help you set realistic expectations and optimize resource allocation.",
"ai_prompt_preview": "Analyzing industry benchmarks, predicting traffic growth, estimating ROI, forecasting conversion rates, and projecting engagement metrics...",
"estimated_time": "15-20 seconds"
}
@staticmethod
def _get_implementation_roadmap_content() -> Dict[str, Any]:
"""Get educational content for implementation roadmap."""
return {
"title": "🗺️ Implementation Roadmap",
"description": "AI is creating a detailed implementation plan for your content strategy.",
"details": [
"📋 Task breakdown and timeline",
"👥 Resource allocation planning",
"🎯 Milestone definition",
"📊 Success metric tracking"
],
"insight": "A clear implementation roadmap ensures successful strategy execution and measurable results.",
"ai_prompt_preview": "Creating implementation roadmap: task breakdown, resource allocation, milestone planning, and success metric definition...",
"estimated_time": "15-20 seconds"
}
@staticmethod
def _get_risk_assessment_content() -> Dict[str, Any]:
"""Get educational content for risk assessment."""
return {
"title": "⚠️ Risk Assessment",
"description": "AI is identifying potential risks and mitigation strategies for your content strategy.",
"details": [
"🔍 Risk identification and analysis",
"📊 Risk probability assessment",
"🛡️ Mitigation strategy development",
"📈 Risk monitoring framework"
],
"insight": "Proactive risk assessment helps you prepare for challenges and maintain strategy effectiveness.",
"ai_prompt_preview": "Assessing risks: identifying potential challenges, analyzing probability and impact, developing mitigation strategies, and creating monitoring framework...",
"estimated_time": "10-15 seconds"
}
@staticmethod
def _get_compilation_content() -> Dict[str, Any]:
"""Get educational content for strategy compilation."""
return {
"title": "📋 Strategy Compilation",
"description": "AI is compiling all components into a comprehensive content strategy.",
"details": [
"🔗 Component integration",
"📊 Data synthesis",
"📝 Strategy documentation",
"✅ Quality validation"
],
"insight": "A comprehensive strategy integrates all components into a cohesive, actionable plan.",
"ai_prompt_preview": "Compiling comprehensive strategy: integrating all components, synthesizing data, documenting strategy, and validating quality...",
"estimated_time": "5-10 seconds"
}
@staticmethod
def _get_completion_content() -> Dict[str, Any]:
"""Get educational content for strategy completion."""
return {
"title": "🎉 Strategy Generation Complete!",
"description": "Your comprehensive AI-powered content strategy is ready for review!",
"summary": {
"total_components": 5,
"successful_components": 5,
"estimated_roi": "15-25%",
"implementation_timeline": "12 months",
"risk_level": "Medium"
},
"key_achievements": [
"🧠 Strategic insights generated",
"🔍 Competitive analysis completed",
"📊 Performance predictions calculated",
"🗺️ Implementation roadmap planned",
"⚠️ Risk assessment conducted"
],
"next_steps": [
"Review your comprehensive strategy in the Strategic Intelligence tab",
"Customize specific components as needed",
"Confirm the strategy to proceed",
"Generate content calendar based on confirmed strategy"
],
"ai_insights": "Your strategy leverages advanced AI analysis of your business context, competitive landscape, and industry best practices to create a data-driven content approach.",
"personalization_note": "This strategy is uniquely tailored to your business based on your onboarding data, ensuring relevance and effectiveness.",
"content_calendar_note": "Content calendar will be generated separately after you review and confirm this strategy, ensuring it's based on your final approved strategy."
}
@staticmethod
def _get_default_content() -> Dict[str, Any]:
"""Get default educational content."""
return {
"title": "🔄 Processing",
"description": "AI is working on your strategy...",
"details": [
"⏳ Processing in progress",
"📊 Analyzing data",
"🎯 Generating insights",
"📝 Compiling results"
],
"insight": "The AI is working hard to create your personalized strategy.",
"estimated_time": "A few moments"
}
@staticmethod
def _get_strategic_insights_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get completion content for strategic insights."""
insights_count = len(result_data.get("insights", [])) if result_data else 0
return {
"title": "✅ Strategic Insights Complete",
"description": "Successfully identified key strategic opportunities and market positioning.",
"achievement": f"Generated {insights_count} strategic insights",
"next_step": "Moving to competitive analysis..."
}
@staticmethod
def _get_competitive_analysis_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get completion content for competitive analysis."""
competitors_count = len(result_data.get("competitors", [])) if result_data else 0
return {
"title": "✅ Competitive Analysis Complete",
"description": "Successfully analyzed competitive landscape and identified market opportunities.",
"achievement": f"Analyzed {competitors_count} competitors",
"next_step": "Moving to performance predictions..."
}
@staticmethod
def _get_performance_predictions_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get completion content for performance predictions."""
estimated_roi = result_data.get("estimated_roi", "15-25%") if result_data else "15-25%"
return {
"title": "✅ Performance Predictions Complete",
"description": "Successfully predicted content performance and ROI.",
"achievement": f"Predicted {estimated_roi} ROI",
"next_step": "Moving to implementation roadmap..."
}
@staticmethod
def _get_implementation_roadmap_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get completion content for implementation roadmap."""
timeline = result_data.get("total_duration", "12 months") if result_data else "12 months"
return {
"title": "✅ Implementation Roadmap Complete",
"description": "Successfully created detailed implementation plan.",
"achievement": f"Planned {timeline} implementation timeline",
"next_step": "Moving to compilation..."
}
@staticmethod
def _get_risk_assessment_completion(result_data: Dict[str, Any] = None) -> Dict[str, Any]:
"""Get completion content for risk assessment."""
risk_level = result_data.get("overall_risk_level", "Medium") if result_data else "Medium"
return {
"title": "✅ Risk Assessment Complete",
"description": "Successfully identified risks and mitigation strategies.",
"achievement": f"Assessed {risk_level} risk level",
"next_step": "Finalizing comprehensive strategy..."
}
@staticmethod
def _get_default_completion() -> Dict[str, Any]:
"""Get default completion content."""
return {
"title": "✅ Step Complete",
"description": "Successfully completed this step.",
"achievement": "Step completed successfully",
"next_step": "Moving to next step..."
}
@staticmethod
def update_completion_summary(completion_content: Dict[str, Any], strategy_data: Dict[str, Any]) -> Dict[str, Any]:
"""Update completion content with actual strategy data."""
if "summary" in completion_content:
content_calendar = strategy_data.get("content_calendar", {})
performance_predictions = strategy_data.get("performance_predictions", {})
implementation_roadmap = strategy_data.get("implementation_roadmap", {})
risk_assessment = strategy_data.get("risk_assessment", {})
completion_content["summary"].update({
"total_content_pieces": len(content_calendar.get("content_pieces", [])),
"estimated_roi": performance_predictions.get("estimated_roi", "15-25%"),
"implementation_timeline": implementation_roadmap.get("total_duration", "12 months"),
"risk_level": risk_assessment.get("overall_risk_level", "Medium")
})
return completion_content

View File

@@ -1122,4 +1122,4 @@ async def refresh_autofill(
) )
except Exception as e: except Exception as e:
logger.error(f"❌ Error generating fresh auto-fill payload: {str(e)}") logger.error(f"❌ Error generating fresh auto-fill payload: {str(e)}")
raise ContentPlanningErrorHandler.handle_general_error(e, "refresh_autofill") raise ContentPlanningErrorHandler.handle_general_error(e, "refresh_autofill")

View File

@@ -60,7 +60,7 @@ class AIStrategyGenerator:
strategy_name: Optional custom strategy name strategy_name: Optional custom strategy name
Returns: Returns:
Comprehensive strategy with all components Comprehensive strategy with all components (EXCLUDING content calendar)
Raises: Raises:
RuntimeError: If any AI component fails to generate RuntimeError: If any AI component fails to generate
@@ -77,19 +77,16 @@ class AIStrategyGenerator:
# Step 3: Generate competitive analysis # Step 3: Generate competitive analysis
competitive_analysis = await self._generate_competitive_analysis(base_strategy, context) competitive_analysis = await self._generate_competitive_analysis(base_strategy, context)
# Step 4: Generate content calendar # Step 4: Generate performance predictions
content_calendar = await self._generate_content_calendar(base_strategy, context)
# Step 5: Generate performance predictions
performance_predictions = await self._generate_performance_predictions(base_strategy, context) performance_predictions = await self._generate_performance_predictions(base_strategy, context)
# Step 6: Generate implementation roadmap # Step 5: Generate implementation roadmap
implementation_roadmap = await self._generate_implementation_roadmap(base_strategy, context) implementation_roadmap = await self._generate_implementation_roadmap(base_strategy, context)
# Step 7: Generate risk assessment # Step 6: Generate risk assessment
risk_assessment = await self._generate_risk_assessment(base_strategy, context) risk_assessment = await self._generate_risk_assessment(base_strategy, context)
# Step 8: Compile comprehensive strategy # Step 7: Compile comprehensive strategy (NO CONTENT CALENDAR)
comprehensive_strategy = { comprehensive_strategy = {
"strategy_metadata": { "strategy_metadata": {
"generated_at": datetime.utcnow().isoformat(), "generated_at": datetime.utcnow().isoformat(),
@@ -99,21 +96,21 @@ class AIStrategyGenerator:
"ai_model": "gemini-pro", "ai_model": "gemini-pro",
"personalization_level": "high", "personalization_level": "high",
"ai_generated": True, "ai_generated": True,
"comprehensive": True "comprehensive": True,
"content_calendar_ready": False # Indicates calendar needs to be generated separately
}, },
"base_strategy": base_strategy, "base_strategy": base_strategy,
"strategic_insights": strategic_insights, "strategic_insights": strategic_insights,
"competitive_analysis": competitive_analysis, "competitive_analysis": competitive_analysis,
"content_calendar": content_calendar,
"performance_predictions": performance_predictions, "performance_predictions": performance_predictions,
"implementation_roadmap": implementation_roadmap, "implementation_roadmap": implementation_roadmap,
"risk_assessment": risk_assessment, "risk_assessment": risk_assessment,
"summary": { "summary": {
"total_content_pieces": len(content_calendar.get("content_pieces", [])),
"estimated_roi": performance_predictions.get("estimated_roi", "15-25%"), "estimated_roi": performance_predictions.get("estimated_roi", "15-25%"),
"implementation_timeline": implementation_roadmap.get("total_duration", "12 months"), "implementation_timeline": implementation_roadmap.get("total_duration", "12 months"),
"risk_level": risk_assessment.get("overall_risk_level", "Medium"), "risk_level": risk_assessment.get("overall_risk_level", "Medium"),
"success_probability": performance_predictions.get("success_probability", "85%") "success_probability": performance_predictions.get("success_probability", "85%"),
"next_step": "Review strategy and generate content calendar"
} }
} }
@@ -334,8 +331,55 @@ class AIStrategyGenerator:
} }
}, },
"themes": {"type": "array", "items": {"type": "string"}}, "themes": {"type": "array", "items": {"type": "string"}},
"schedule": {"type": "object"}, "schedule": {
"distribution_strategy": {"type": "object"} "type": "object",
"properties": {
"publishing_frequency": {"type": "string"},
"optimal_times": {"type": "array", "items": {"type": "string"}},
"content_mix": {
"type": "object",
"properties": {
"blog_posts": {"type": "string"},
"social_media": {"type": "string"},
"videos": {"type": "string"},
"infographics": {"type": "string"},
"newsletters": {"type": "string"}
}
},
"seasonal_adjustments": {
"type": "object",
"properties": {
"holiday_content": {"type": "array", "items": {"type": "string"}},
"seasonal_themes": {"type": "array", "items": {"type": "string"}},
"peak_periods": {"type": "array", "items": {"type": "string"}}
}
}
}
},
"distribution_strategy": {
"type": "object",
"properties": {
"primary_platforms": {"type": "array", "items": {"type": "string"}},
"cross_posting_strategy": {"type": "string"},
"platform_specific_content": {
"type": "object",
"properties": {
"linkedin_content": {"type": "array", "items": {"type": "string"}},
"twitter_content": {"type": "array", "items": {"type": "string"}},
"instagram_content": {"type": "array", "items": {"type": "string"}},
"facebook_content": {"type": "array", "items": {"type": "string"}}
}
},
"engagement_timing": {
"type": "object",
"properties": {
"best_times": {"type": "array", "items": {"type": "string"}},
"frequency": {"type": "string"},
"timezone_considerations": {"type": "string"}
}
}
}
}
} }
} }
@@ -483,8 +527,33 @@ class AIStrategyGenerator:
} }
} }
}, },
"timeline": {"type": "object"}, "timeline": {
"resource_allocation": {"type": "object"}, "type": "object",
"properties": {
"start_date": {"type": "string"},
"end_date": {"type": "string"},
"key_milestones": {"type": "array", "items": {"type": "string"}},
"critical_path": {"type": "array", "items": {"type": "string"}}
}
},
"resource_allocation": {
"type": "object",
"properties": {
"team_requirements": {"type": "array", "items": {"type": "string"}},
"budget_allocation": {
"type": "object",
"properties": {
"total_budget": {"type": "string"},
"content_creation": {"type": "string"},
"technology_tools": {"type": "string"},
"marketing_promotion": {"type": "string"},
"external_resources": {"type": "string"}
}
},
"technology_needs": {"type": "array", "items": {"type": "string"}},
"external_resources": {"type": "array", "items": {"type": "string"}}
}
},
"success_metrics": {"type": "array", "items": {"type": "string"}}, "success_metrics": {"type": "array", "items": {"type": "string"}},
"total_duration": {"type": "string"} "total_duration": {"type": "string"}
} }
@@ -552,9 +621,25 @@ class AIStrategyGenerator:
} }
}, },
"overall_risk_level": {"type": "string"}, "overall_risk_level": {"type": "string"},
"risk_categories": {"type": "object"}, "risk_categories": {
"type": "object",
"properties": {
"technical_risks": {"type": "array", "items": {"type": "string"}},
"market_risks": {"type": "array", "items": {"type": "string"}},
"operational_risks": {"type": "array", "items": {"type": "string"}},
"financial_risks": {"type": "array", "items": {"type": "string"}}
}
},
"mitigation_strategies": {"type": "array", "items": {"type": "string"}}, "mitigation_strategies": {"type": "array", "items": {"type": "string"}},
"monitoring_framework": {"type": "object"} "monitoring_framework": {
"type": "object",
"properties": {
"key_indicators": {"type": "array", "items": {"type": "string"}},
"monitoring_frequency": {"type": "string"},
"escalation_procedures": {"type": "array", "items": {"type": "string"}},
"review_schedule": {"type": "string"}
}
}
} }
} }

View File

@@ -506,7 +506,7 @@ class EnhancedStrategyService:
def _merge_strategy_with_onboarding(self, strategy_data: Dict[str, Any], field_transformations: Dict[str, Any]) -> Dict[str, Any]: def _merge_strategy_with_onboarding(self, strategy_data: Dict[str, Any], field_transformations: Dict[str, Any]) -> Dict[str, Any]:
"""Merge strategy data with onboarding data.""" """Merge strategy data with onboarding data."""
merged_data = strategy_data.copy() merged_data = strategy_data.copy()
for field, transformation in field_transformations.items(): for field, transformation in field_transformations.items():
if field not in merged_data or merged_data[field] is None: if field not in merged_data or merged_data[field] is None:

View File

@@ -0,0 +1,343 @@
# SSE Migration Strategy & Implementation Plan
## 🚨 **Current Implementation Problems**
### **Backend Issues**
- **Complex SSE Manager**: The `SSEAIServiceManager` with lambda functions is overly complex
- **Async Generator Problems**: The `sse_yield` function using `__anext__()` is fragile
- **Message Format Inconsistency**: Backend sends different message formats that frontend struggles to parse
- **Tight Coupling**: AI service manager is tightly coupled to SSE implementation
- **Error Propagation**: Errors in one component cascade to others
- **Debugging Difficulty**: Complex async flows make debugging hard
### **Frontend Issues**
- **EventSource Limitations**: No built-in reconnection, poor error handling
- **Message Parsing Complexity**: Too many message types to handle
- **Timeout Handling**: Frontend timeouts don't align with backend processing
- **Connection State Management**: Poor handling of connection states
- **Progress Tracking**: Inconsistent progress calculation and display
### **Architecture Problems**
- **Tight Coupling**: Frontend and backend are tightly coupled to specific message formats
- **No Reusability**: SSE implementation is specific to strategy generation
- **Error Handling**: Inconsistent error handling across components
- **Testing Difficulty**: Complex async flows make testing challenging
## 🎯 **Proposed Solution: Clean SSE with sse-starlette**
### **Phase 1: MVP Polling Solution (1-2 hours)**
**Goal**: Get strategy generation working immediately with simple polling
**Implementation**:
- Replace complex SSE with simple polling mechanism
- Poll strategy status every 10 seconds
- Show progress modal with educational content
- Handle timeouts gracefully
- Remove all SSE-related complexity
**Benefits**:
- ✅ Immediate working solution
- ✅ Simple to implement and debug
- ✅ Reliable and predictable
- ✅ Easy to test
### **Phase 2: Proper SSE Implementation (1-2 days)**
**Goal**: Implement clean, reusable SSE infrastructure
**Implementation**:
- Use `sse-starlette` for backend SSE
- Create reusable SSE client for frontend
- Standardize message format
- Add proper error handling and reconnection
- Make SSE infrastructure reusable for other features
**Benefits**:
- ✅ Real-time updates
- ✅ Better user experience
- ✅ Reusable infrastructure
- ✅ Proper error handling
## 🏗️ **Technical Architecture**
### **Backend: sse-starlette Implementation**
#### **Core SSE Module** (`backend/services/sse/`)
```
backend/services/sse/
├── __init__.py
├── sse_manager.py # Core SSE management
├── message_formatter.py # Standardized message formatting
├── connection_manager.py # Connection lifecycle management
├── error_handler.py # SSE error handling
└── types.py # SSE message types and schemas
```
#### **SSE Manager Features**
- **Connection Management**: Handle multiple SSE connections
- **Message Broadcasting**: Send messages to specific clients
- **Error Handling**: Graceful error handling and recovery
- **Message Formatting**: Consistent message format across all features
- **Connection Monitoring**: Track connection health and status
#### **Message Format Standardization**
```python
# Standard SSE message format
{
"event": "progress|complete|error|educational",
"data": {
"step": 1,
"progress": 10,
"message": "Processing...",
"educational_content": {...},
"timestamp": "2024-01-01T00:00:00Z"
}
}
```
### **Frontend: Reusable SSE Client**
#### **Core SSE Module** (`frontend/src/services/sse/`)
```
frontend/src/services/sse/
├── index.ts
├── SSEConnection.ts # Core SSE connection management
├── SSEEventManager.ts # Event handling and message parsing
├── SSEReconnection.ts # Automatic reconnection logic
├── SSEMessageTypes.ts # TypeScript types for messages
└── SSEUtils.ts # Utility functions
```
#### **SSE Client Features**
- **Automatic Reconnection**: Handle connection drops gracefully
- **Message Parsing**: Parse standardized message format
- **Event Handling**: Handle different event types
- **Error Recovery**: Recover from errors automatically
- **Connection Monitoring**: Monitor connection health
#### **React Hook** (`frontend/src/hooks/useSSE.ts`)
```typescript
const useSSE = (url: string, options?: SSEOptions) => {
// Returns: { data, error, isConnected, reconnect }
}
```
## 📋 **Implementation Phases**
### **Phase 1: MVP Polling (Immediate - 1-2 hours)**
#### **Backend Changes**
1. **Remove SSE complexity** from `ai_generation_endpoints.py`
2. **Simplify AI generation** to return immediately after starting
3. **Add status endpoint** to check generation progress
4. **Remove SSEAIServiceManager** and related complexity
#### **Frontend Changes**
1. **Replace SSE with polling** in `ContentStrategyBuilder.tsx`
2. **Implement simple progress modal** with educational content
3. **Add polling mechanism** (every 10 seconds)
4. **Handle timeouts gracefully** (5-minute timeout)
5. **Remove all SSE-related code**
#### **Files to Modify**
- `backend/api/content_planning/api/content_strategy/endpoints/ai_generation_endpoints.py`
- `frontend/src/components/ContentPlanningDashboard/components/ContentStrategyBuilder.tsx`
- `frontend/src/services/contentPlanningApi.ts`
### **Phase 2: Clean SSE Infrastructure (1-2 days)**
#### **Backend Implementation**
1. **Create SSE infrastructure** (`backend/services/sse/`)
2. **Implement sse-starlette endpoints** for strategy generation
3. **Standardize message format** across all SSE endpoints
4. **Add connection management** and error handling
5. **Create reusable SSE utilities**
#### **Frontend Implementation**
1. **Create SSE client infrastructure** (`frontend/src/services/sse/`)
2. **Implement React hook** for SSE connections
3. **Add automatic reconnection** logic
4. **Standardize message parsing** and event handling
5. **Create reusable SSE components**
#### **New Files to Create**
```
Backend:
- backend/services/sse/__init__.py
- backend/services/sse/sse_manager.py
- backend/services/sse/message_formatter.py
- backend/services/sse/connection_manager.py
- backend/services/sse/error_handler.py
- backend/services/sse/types.py
Frontend:
- frontend/src/services/sse/index.ts
- frontend/src/services/sse/SSEConnection.ts
- frontend/src/services/sse/SSEEventManager.ts
- frontend/src/services/sse/SSEReconnection.ts
- frontend/src/services/sse/SSEMessageTypes.ts
- frontend/src/services/sse/SSEUtils.ts
- frontend/src/hooks/useSSE.ts
```
### **Phase 3: Migration & Testing (1 day)**
#### **Migration Steps**
1. **Migrate strategy generation** to new SSE infrastructure
2. **Test end-to-end functionality** with new SSE
3. **Add comprehensive error handling** and recovery
4. **Implement educational content** streaming
5. **Add monitoring and logging** for SSE connections
#### **Testing Strategy**
1. **Unit tests** for SSE infrastructure
2. **Integration tests** for SSE endpoints
3. **End-to-end tests** for strategy generation
4. **Error scenario testing** (network drops, timeouts)
5. **Performance testing** (multiple concurrent connections)
## 🔧 **Technical Specifications**
### **Backend SSE Manager Interface**
```python
class SSEManager:
async def create_connection(self, client_id: str) -> SSEConnection
async def send_message(self, client_id: str, message: SSEMessage)
async def broadcast_message(self, message: SSEMessage, filter_func=None)
async def close_connection(self, client_id: str)
async def get_connection_status(self, client_id: str) -> ConnectionStatus
```
### **Frontend SSE Client Interface**
```typescript
interface SSEConnection {
connect(): Promise<void>
disconnect(): void
send(message: SSEMessage): void
on(event: string, handler: EventHandler): void
off(event: string, handler: EventHandler): void
isConnected(): boolean
reconnect(): Promise<void>
}
```
### **Message Format Specification**
```typescript
interface SSEMessage {
event: 'progress' | 'complete' | 'error' | 'educational' | 'status'
data: {
step?: number
progress?: number
message?: string
educational_content?: EducationalContent
error?: string
timestamp: string
[key: string]: any
}
}
```
## 🎯 **Success Criteria**
### **Phase 1 Success Criteria**
- ✅ Strategy generation works reliably
- ✅ No more "Request timed out" errors
- ✅ Users can see progress and educational content
- ✅ Simple, debuggable implementation
- ✅ Strategy creation completes successfully
### **Phase 2 Success Criteria**
- ✅ Real-time progress updates via SSE
- ✅ Automatic reconnection on network issues
- ✅ Standardized message format across features
- ✅ Reusable SSE infrastructure
- ✅ Proper error handling and recovery
- ✅ Educational content streaming
### **Phase 3 Success Criteria**
- ✅ All features migrated to new SSE infrastructure
- ✅ Comprehensive testing coverage
- ✅ Performance meets requirements
- ✅ Error scenarios handled gracefully
- ✅ Monitoring and logging in place
## 🚀 **Migration Benefits**
### **Immediate Benefits (Phase 1)**
- **Reliability**: No more timeout errors
- **Simplicity**: Easy to debug and maintain
- **User Experience**: Clear progress feedback
- **Stability**: Predictable behavior
### **Long-term Benefits (Phase 2+)**
- **Reusability**: SSE infrastructure for other features
- **Real-time Updates**: Better user experience
- **Scalability**: Handle multiple concurrent connections
- **Maintainability**: Clean, modular architecture
- **Extensibility**: Easy to add new SSE features
## 📝 **Implementation Notes**
### **Dependencies**
- **Backend**: `sse-starlette` package
- **Frontend**: No additional dependencies (uses native EventSource)
### **Configuration**
- **SSE Timeout**: 5 minutes for long-running operations
- **Reconnection**: Exponential backoff (1s, 2s, 4s, 8s, max 30s)
- **Message Format**: JSON with standardized structure
- **Error Handling**: Graceful degradation with fallback options
### **Monitoring & Logging**
- **Connection Status**: Track active connections
- **Message Flow**: Log message types and frequencies
- **Error Tracking**: Monitor and alert on SSE errors
- **Performance Metrics**: Track response times and throughput
### **Security Considerations**
- **Authentication**: Validate client connections
- **Rate Limiting**: Prevent abuse of SSE endpoints
- **Message Validation**: Validate all incoming messages
- **Connection Limits**: Limit concurrent connections per user
## 🔄 **Rollback Plan**
### **If Phase 1 Fails**
- Revert to current SSE implementation
- Keep polling as fallback option
- Document issues for future reference
### **If Phase 2 Fails**
- Keep Phase 1 polling implementation
- Identify specific issues with sse-starlette
- Consider alternative SSE libraries or WebSocket implementation
### **If Phase 3 Fails**
- Rollback to Phase 2 implementation
- Fix specific issues identified during testing
- Re-run migration with fixes
## 📚 **References & Resources**
### **Documentation**
- [sse-starlette Documentation](https://github.com/sysid/sse-starlette)
- [Server-Sent Events MDN](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events)
- [EventSource API](https://developer.mozilla.org/en-US/docs/Web/API/EventSource)
### **Best Practices**
- [SSE Best Practices](https://html.spec.whatwg.org/multipage/server-sent-events.html)
- [Real-time Web Applications](https://web.dev/real-time-web-applications/)
- [Error Handling in SSE](https://developer.mozilla.org/en-US/docs/Web/API/Server-sent_events/Using_server-sent_events#Error_handling)
### **Examples & Templates**
- [sse-starlette Examples](https://github.com/sysid/sse-starlette/tree/main/examples)
- [React SSE Hook Examples](https://github.com/facebook/react/tree/main/packages/react-dom/src/events)
- [FastAPI SSE Examples](https://fastapi.tiangolo.com/advanced/websockets/)
---
**Next Steps**:
1. Commit current code
2. Refresh session
3. Start Phase 1 implementation (MVP polling)
4. Test strategy generation works
5. Proceed to Phase 2 (clean SSE infrastructure)

View File

@@ -1,13 +1,13 @@
{ {
"files": { "files": {
"main.css": "/static/css/main.c9966057.css", "main.css": "/static/css/main.c9966057.css",
"main.js": "/static/js/main.2ee5cd94.js", "main.js": "/static/js/main.2819e23e.js",
"index.html": "/index.html", "index.html": "/index.html",
"main.c9966057.css.map": "/static/css/main.c9966057.css.map", "main.c9966057.css.map": "/static/css/main.c9966057.css.map",
"main.2ee5cd94.js.map": "/static/js/main.2ee5cd94.js.map" "main.2819e23e.js.map": "/static/js/main.2819e23e.js.map"
}, },
"entrypoints": [ "entrypoints": [
"static/css/main.c9966057.css", "static/css/main.c9966057.css",
"static/js/main.2ee5cd94.js" "static/js/main.2819e23e.js"
] ]
} }

View File

@@ -1 +1 @@
<!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.2ee5cd94.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> <!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.2819e23e.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

@@ -50,7 +50,8 @@ import {
Lightbulb as LightbulbIcon, Lightbulb as LightbulbIcon,
Psychology as PsychologyIcon, Psychology as PsychologyIcon,
Timeline as TimelineIcon, Timeline as TimelineIcon,
FiberManualRecord as FiberManualRecordIcon FiberManualRecord as FiberManualRecordIcon,
Schedule as ScheduleIcon
} from '@mui/icons-material'; } from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion'; import { motion, AnimatePresence } from 'framer-motion';
import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore'; import { useEnhancedStrategyStore, STRATEGIC_INPUT_FIELDS } from '../../../stores/enhancedStrategyStore';
@@ -63,6 +64,7 @@ import DataSourceTransparency from './DataSourceTransparency';
import { useCategoryReview } from './ContentStrategyBuilder/hooks/useCategoryReview'; import { useCategoryReview } from './ContentStrategyBuilder/hooks/useCategoryReview';
import { useProgressTracking } from './ContentStrategyBuilder/hooks/useProgressTracking'; import { useProgressTracking } from './ContentStrategyBuilder/hooks/useProgressTracking';
import { useAutoPopulation } from './ContentStrategyBuilder/hooks/useAutoPopulation'; import { useAutoPopulation } from './ContentStrategyBuilder/hooks/useAutoPopulation';
import { useActionButtonsBusinessLogic } from './ContentStrategyBuilder/components/ActionButtons';
// Import extracted utilities // Import extracted utilities
import { getCategoryIcon, getCategoryColor, getCategoryName, getCategoryStatus } from './ContentStrategyBuilder/utils/categoryHelpers'; import { getCategoryIcon, getCategoryColor, getCategoryName, getCategoryStatus } from './ContentStrategyBuilder/utils/categoryHelpers';
@@ -72,7 +74,12 @@ import { getEducationalContent } from './ContentStrategyBuilder/utils/educationa
import CategoryList from './ContentStrategyBuilder/components/CategoryList'; import CategoryList from './ContentStrategyBuilder/components/CategoryList';
import ProgressTracker from './ContentStrategyBuilder/components/ProgressTracker'; import ProgressTracker from './ContentStrategyBuilder/components/ProgressTracker';
import HeaderSection from './ContentStrategyBuilder/components/HeaderSection'; import HeaderSection from './ContentStrategyBuilder/components/HeaderSection';
import EducationalModal from './ContentStrategyBuilder/components/EducationalModal';
import ActionButtons from './ContentStrategyBuilder/components/ActionButtons';
import StrategyDisplay from './ContentStrategyBuilder/components/StrategyDisplay';
import ErrorAlert from './ContentStrategyBuilder/components/ErrorAlert';
import { contentPlanningApi } from '../../../services/contentPlanningApi'; import { contentPlanningApi } from '../../../services/contentPlanningApi';
import CategoryDetailView from './ContentStrategyBuilder/components/CategoryDetailView';
const ContentStrategyBuilder: React.FC = () => { const ContentStrategyBuilder: React.FC = () => {
const { const {
@@ -155,6 +162,25 @@ const ContentStrategyBuilder: React.FC = () => {
completionStats completionStats
}); });
// Use ActionButtons business logic hook
const { handleCreateStrategy, handleSaveStrategy } = useActionButtonsBusinessLogic({
formData,
error,
currentStrategy,
setAIGenerating,
setError,
setCurrentStrategy,
setSaving,
setGenerationProgress,
setEducationalContent,
setShowEducationalModal,
validateAllFields,
getCompletionStats,
generateAIRecommendations,
createEnhancedStrategy,
contentPlanningApi
});
// Auto-populate from onboarding on first load // Auto-populate from onboarding on first load
useEffect(() => { useEffect(() => {
if (!autoPopulateAttempted) { if (!autoPopulateAttempted) {
@@ -211,294 +237,6 @@ const ContentStrategyBuilder: React.FC = () => {
}; };
}, []); }, []);
const handleCreateStrategy = async () => {
try {
setAIGenerating(true);
setError(null);
console.log('Starting strategy creation...');
console.log('Current formData:', formData);
console.log('FormData ID:', formData.id);
// If we have a saved strategy, use its ID
if (formData.id) {
console.log('Using existing strategy ID:', formData.id);
await generateAIRecommendations(formData.id);
} else {
console.log('No strategy ID found, creating new strategy...');
// If no strategy is saved yet, save it first, then generate AI insights
const isValid = validateAllFields();
console.log('Form validation result:', isValid);
if (isValid) {
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1, // This would come from auth context
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
console.log('Attempting to create strategy with data:', strategyData);
// Use SSE streaming endpoint for strategy generation with educational content
await generateStrategyWithSSE(strategyData);
} else {
setError('Please fill in all required fields before generating AI insights.');
console.error('Form validation failed. Cannot generate AI insights.');
}
}
} catch (err: any) {
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
console.error('Error in handleCreateStrategy:', err);
} finally {
setAIGenerating(false);
}
};
const generateStrategyWithSSE = async (strategyData: any) => {
try {
console.log('🚀 Starting SSE strategy generation...');
// Initialize progress and educational content
setGenerationProgress(0);
setEducationalContent({
title: '🤖 AI-Powered Strategy Generation',
description: 'Initializing AI analysis and preparing educational content...',
details: [
'🔧 Setting up AI services',
'📊 Loading user context',
'🎯 Preparing strategy framework',
'📚 Generating educational content'
],
insight: 'We\'re getting everything ready for your personalized AI strategy generation.',
estimated_time: '2-3 minutes total'
});
// Show educational modal
setShowEducationalModal(true);
// Create basic strategy first
const newStrategy = await createEnhancedStrategy(strategyData);
console.log('Basic strategy created:', newStrategy);
if (newStrategy && newStrategy.id) {
console.log('Starting AI generation for strategy ID:', newStrategy.id);
// Set a timeout for the entire process (5 minutes)
const processTimeout = setTimeout(async () => {
console.error('⏰ Strategy generation timeout after 5 minutes');
// Try to check if the strategy was actually created
try {
const existingStrategy = await contentPlanningApi.getEnhancedStrategy(newStrategy.id.toString());
if (existingStrategy) {
console.log('✅ Strategy was created successfully despite SSE timeout');
setCurrentStrategy(existingStrategy);
setError('Strategy created successfully! The AI generation may still be running in the background. Check the Strategic Intelligence tab for detailed insights.');
} else {
setError('Strategy generation is taking longer than expected. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
}
} catch (checkError) {
console.error('Error checking strategy status:', checkError);
setError('Strategy generation is taking longer than expected. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
}
setShowEducationalModal(false);
}, 5 * 60 * 1000); // 5 minutes
// Add heartbeat monitoring
let lastMessageTime = Date.now();
const heartbeatInterval = setInterval(() => {
const timeSinceLastMessage = Date.now() - lastMessageTime;
if (timeSinceLastMessage > 30000) { // 30 seconds without message
console.warn('⚠️ No SSE messages received for 30 seconds');
setEducationalContent({
title: '🤖 AI-Powered Strategy Generation',
description: 'AI analysis is still running in the background. This may take a few more minutes.',
details: [
'⏳ Processing complex AI analysis',
'📊 Analyzing market data',
'🎯 Generating strategic insights',
'📈 Calculating performance predictions'
],
insight: 'The AI is working on comprehensive analysis. This is normal for complex strategies.',
estimated_time: 'Additional 1-2 minutes'
});
}
}, 10000); // Check every 10 seconds
// Use SSE endpoint for AI generation with educational content
const eventSource = await contentPlanningApi.streamStrategyGeneration(Number(newStrategy.id));
console.log('🔌 SSE EventSource created:', eventSource);
console.log('🔌 SSE readyState:', eventSource.readyState);
// Handle SSE data with proper parsing
eventSource.onmessage = (event) => {
try {
console.log('📨 Raw SSE message:', event.data);
// Update last message time for heartbeat
lastMessageTime = Date.now();
// Parse the SSE data
const data = JSON.parse(event.data);
console.log('📨 Parsed SSE data:', data);
console.log('📨 Message type analysis:', {
hasStep: data.step !== undefined,
hasProgress: data.progress !== undefined,
hasEducationalContent: !!data.educational_content,
hasError: !!data.error,
hasSuccess: !!data.success,
hasType: !!data.type,
step: data.step,
progress: data.progress,
message: data.message
});
// Handle different types of messages
if (data.error) {
console.error('❌ SSE Error:', data.error);
clearTimeout(processTimeout);
clearInterval(heartbeatInterval);
setError(`AI generation failed: ${data.error}`);
setShowEducationalModal(false);
eventSource.close();
return;
}
// Handle step and progress updates (backend sends these)
if (data.step !== undefined) {
console.log('🔢 Updating step:', data.step);
// Calculate progress from step (each step is 10%)
const stepProgress = Math.min(data.step * 10, 100);
console.log('📊 Calculated progress from step:', stepProgress);
setGenerationProgress(stepProgress);
}
// Handle explicit progress updates
if (data.progress !== undefined) {
console.log('📊 Updating progress:', data.progress);
setGenerationProgress(data.progress);
}
// Handle educational content updates
if (data.educational_content) {
console.log('📚 Updating educational content:', data.educational_content);
setEducationalContent(data.educational_content);
}
// Handle completion
if (data.step === 10 && data.success) {
console.log('✅ Strategy generation completed successfully!');
clearTimeout(processTimeout);
clearInterval(heartbeatInterval);
setCurrentStrategy(data.strategy);
setShowEducationalModal(false);
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
eventSource.close();
}
// Handle educational content from AI service manager
if (data.type === 'educational_content' && data.educational_content) {
console.log('📚 AI Service educational content:', data.educational_content);
setEducationalContent(data.educational_content);
}
// Handle success messages for individual steps
if (data.success && data.message) {
console.log('✅ Step completed:', data.message);
// Progress is already updated above, just log the success
}
} catch (parseError) {
console.error('❌ Error parsing SSE message:', parseError);
console.error('Raw message:', event.data);
}
};
// Handle SSE errors
eventSource.onerror = (error) => {
console.error('❌ SSE connection error:', error);
console.error(' ReadyState:', eventSource.readyState);
// Check connection state
switch (eventSource.readyState) {
case EventSource.CONNECTING:
console.log('🔄 SSE connection is connecting...');
break;
case EventSource.OPEN:
console.log('✅ SSE connection is open');
break;
case EventSource.CLOSED:
console.log('🔌 SSE connection is closed');
clearTimeout(processTimeout);
clearInterval(heartbeatInterval);
setError('Connection lost during AI generation. The process may still be running in the background. Please check the Strategic Intelligence tab for results.');
setShowEducationalModal(false);
break;
}
};
// Handle SSE connection open
eventSource.onopen = () => {
console.log('✅ SSE connection opened successfully');
console.log(' ReadyState:', eventSource.readyState);
console.log(' URL:', eventSource.url);
// Update educational content to show connection is established
setEducationalContent({
title: '🔌 Connection Established',
description: 'Successfully connected to AI generation service. Starting analysis...',
details: [
'✅ SSE connection active',
'🤖 AI service ready',
'📊 Data processing initialized',
'🎯 Strategy generation starting'
],
insight: 'The connection is now established and AI analysis is beginning.',
estimated_time: '2-3 minutes total'
});
};
} else {
setError('Failed to create strategy or get strategy ID for AI generation.');
console.error('Failed to create strategy or get strategy ID for AI generation.');
setShowEducationalModal(false);
}
} catch (error: any) {
console.error('Error in SSE strategy generation:', error);
setError(`Error in strategy generation: ${error.message || 'Unknown error'}`);
setShowEducationalModal(false);
}
};
const handleSaveStrategy = async () => {
try {
setSaving(true);
setError(null);
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1,
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
const newStrategy = await createEnhancedStrategy(strategyData);
setCurrentStrategy(newStrategy);
setError('Strategy saved successfully!');
} catch (err: any) {
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
} finally {
setSaving(false);
}
};
const handleReviewCategory = (categoryId: string) => { const handleReviewCategory = (categoryId: string) => {
setActiveCategory(activeCategory === categoryId ? null : categoryId); setActiveCategory(activeCategory === categoryId ? null : categoryId);
}; };
@@ -519,75 +257,19 @@ const ContentStrategyBuilder: React.FC = () => {
<HeaderSection autoPopulatedFields={autoPopulatedFields} /> <HeaderSection autoPopulatedFields={autoPopulatedFields} />
{/* Error Alert */} {/* Error Alert */}
{error && ( <ErrorAlert
<Alert error={error}
severity="error" onRetry={() => autoPopulateFromOnboarding(true)}
sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }} onShowDataSourceTransparency={() => setShowDataSourceTransparency(true)}
action={ />
<Box sx={{ display: 'flex', gap: 1 }}>
<Button size="small" variant="outlined" onClick={() => autoPopulateFromOnboarding(true)} startIcon={<RefreshIcon />}>Retry</Button>
<Button size="small" variant="contained" color="primary" onClick={() => setShowDataSourceTransparency(true)} startIcon={<InfoIcon />}>Why?</Button>
</Box>
}
>
<Box>
<Typography variant="subtitle2">Real data required</Typography>
<Typography variant="body2">{error || 'We could not auto-populate because required onboarding/analysis data is missing. Connect sources or complete onboarding, then retry.'}</Typography>
</Box>
</Alert>
)}
{/* Success Alert */} {/* Strategy Display and Success Alerts */}
{!error && currentStrategy && ( <StrategyDisplay
<Alert severity="success" sx={{ mb: 3 }}> currentStrategy={currentStrategy}
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights. error={error}
</Alert> categoryCompletionMessage={categoryCompletionMessage}
)} onViewStrategicIntelligence={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
/>
{/* Strategy Display */}
{currentStrategy && (
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Created Strategy: {currentStrategy.name}
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Industry: {currentStrategy.industry}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Completion: {currentStrategy.completion_percentage}%
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
ID: {currentStrategy.id}
</Typography>
</Grid>
</Grid>
<Box sx={{ mt: 2 }}>
<Button
variant="outlined"
onClick={() => window.location.href = '/content-planning?tab=strategic-intelligence'}
startIcon={<AssessmentIcon />}
>
View Strategic Intelligence
</Button>
</Box>
</Paper>
)}
{categoryCompletionMessage && (
<Alert
severity="success"
sx={{ mb: 3, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
{categoryCompletionMessage}
</Alert>
)}
<Grid container spacing={3}> <Grid container spacing={3}>
{/* Category Overview Panel */} {/* Category Overview Panel */}
@@ -781,212 +463,41 @@ const ContentStrategyBuilder: React.FC = () => {
{/* Main Content Area */} {/* Main Content Area */}
<Grid item xs={12} md={8}> <Grid item xs={12} md={8}>
<Paper sx={{ p: 3, minHeight: '600px', background: 'linear-gradient(180deg, #faf7ff, #f1f0ff)' }}> <Paper sx={{ p: 3, minHeight: '600px', background: 'linear-gradient(180deg, #faf7ff, #f1f0ff)' }}>
{activeCategory ? ( <CategoryDetailView
<motion.div activeCategory={activeCategory}
initial={{ opacity: 0, y: 20 }} formData={formData}
animate={{ opacity: 1, y: 0 }} formErrors={formErrors}
transition={{ duration: 0.3 }} autoPopulatedFields={autoPopulatedFields}
> dataSources={dataSources}
{/* Category Header */} personalizationData={personalizationData}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}> completionStats={completionStats}
{getCategoryIcon(activeCategory)} reviewedCategories={reviewedCategories}
<Typography variant="h5" sx={{ ml: 1 }}> isMarkingReviewed={isMarkingReviewed}
{activeCategory.split('_').map(word => showEducationalInfo={showEducationalInfo}
word.charAt(0).toUpperCase() + word.slice(1) STRATEGIC_INPUT_FIELDS={STRATEGIC_INPUT_FIELDS}
).join(' ')} onUpdateFormField={updateFormField}
</Typography> onValidateFormField={validateFormField}
<Chip onShowTooltip={setShowTooltip}
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`} onViewDataSource={() => setShowDataSourceTransparency(true)}
color={getCategoryColor(activeCategory) as any} onConfirmCategoryReview={handleConfirmCategoryReviewWrapper}
sx={{ ml: 'auto' }} onSetActiveCategory={setActiveCategory}
/> onSetShowEducationalInfo={setShowEducationalInfo}
</Box> getCategoryIcon={getCategoryIcon}
getCategoryColor={getCategoryColor}
{/* Educational Info Dialog */} getEducationalContent={getEducationalContent}
<Dialog />
open={!!showEducationalInfo}
onClose={() => setShowEducationalInfo(null)}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SchoolIcon />
{showEducationalInfo && getEducationalContent(showEducationalInfo).title}
</Box>
</DialogTitle>
<DialogContent>
<Typography variant="body1" paragraph>
{showEducationalInfo && getEducationalContent(showEducationalInfo).description}
</Typography>
<Typography variant="h6" gutterBottom>
Key Points:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).points.map((point, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<LightbulbIcon color="primary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={point} />
</ListItem>
))}
</List>
<Typography variant="h6" gutterBottom>
Pro Tips:
</Typography>
<List>
{showEducationalInfo && getEducationalContent(showEducationalInfo).tips.map((tip, index) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<PsychologyIcon color="secondary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={tip} />
</ListItem>
))}
</List>
</DialogContent>
<DialogActions>
<Button onClick={() => setShowEducationalInfo(null)}>
Got it!
</Button>
</DialogActions>
</Dialog>
{/* Category Fields */}
<Box sx={{ mt: 1 }}>
<Grid container spacing={2}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field, index) => {
// Determine grid size based on field type for better layout organization
const type = field.type;
const isWideField = type === 'json';
const isMediumField = type === 'multiselect' || type === 'select' || type === 'text';
const isCompactField = type === 'number' || type === 'boolean';
const forceFullWidth = field.id === 'content_budget' || field.id === 'team_size';
const gridMd = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridLg = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridSm = 12;
return (
<Grid item xs={12} sm={gridSm} md={gridMd} lg={gridLg} key={field.id}>
<motion.div initial={{ opacity: 0, y: 10 }} animate={{ opacity: 1, y: 0 }} transition={{ duration: 0.25, delay: index * 0.03 }}>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={autoPopulatedFields[field.id] ? 0.8 : undefined}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
personalizationData={personalizationData[field.id]}
onChange={(value: any) => updateFormField(field.id, value)}
onValidate={() => validateFormField(field.id)}
onShowTooltip={() => setShowTooltip(field.id)}
onViewDataSource={() => setShowDataSourceTransparency(true)}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
</motion.div>
</Grid>
);
})}
</Grid>
</Box>
{/* Category Actions */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
{(() => {
const isReviewed = reviewedCategories.has(activeCategory);
console.log('🔍 Category review status:', {
activeCategory,
isReviewed,
reviewedCategories: Array.from(reviewedCategories)
});
return !isReviewed ? (
<Button
variant="contained"
onClick={() => {
console.log('🔘 Button clicked! activeCategory:', activeCategory);
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
handleConfirmCategoryReviewWrapper();
}}
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
disabled={isMarkingReviewed}
>
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
</Button>
) : (
<Chip
label="Category Reviewed"
color="success"
icon={<CheckCircleIcon />}
sx={{ px: 2, py: 1 }}
/>
);
})()}
<Button
variant="outlined"
onClick={() => setActiveCategory(null)}
>
Back to Overview
</Button>
</Box>
</motion.div>
) : (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<Box sx={{ textAlign: 'center', py: 8 }}>
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Select a Category to Review
</Typography>
<Typography variant="body1" color="text.secondary">
Click on any category from the left panel to review and complete the fields.
</Typography>
</Box>
</motion.div>
)}
</Paper> </Paper>
</Grid> </Grid>
</Grid> </Grid>
{/* Action Buttons */} {/* Action Buttons */}
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}> <ActionButtons
<MuiTooltip aiGenerating={aiGenerating}
title={reviewProgressPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(reviewProgressPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'} saving={saving}
placement="top" reviewProgressPercentage={reviewProgressPercentage}
> onCreateStrategy={handleCreateStrategy}
<span> onSaveStrategy={handleSaveStrategy}
<Button />
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={handleCreateStrategy}
disabled={aiGenerating || reviewProgressPercentage < 20}
>
{aiGenerating ? 'Creating...' : 'Create Strategy'}
</Button>
</span>
</MuiTooltip>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={handleSaveStrategy}
disabled={saving || reviewProgressPercentage < 30}
>
{saving ? 'Saving...' : 'Save Strategy'}
</Button>
</Box>
{/* AI Recommendations Modal */} {/* AI Recommendations Modal */}
<Dialog <Dialog
@@ -1009,179 +520,13 @@ const ContentStrategyBuilder: React.FC = () => {
</DialogContent> </DialogContent>
</Dialog> </Dialog>
{/* Educational Modal for Strategy Generation */} {/* Enhanced Educational Modal for Strategy Generation */}
<Dialog <EducationalModal
open={showEducationalModal} open={showEducationalModal}
onClose={() => setShowEducationalModal(false)} onClose={() => setShowEducationalModal(false)}
maxWidth="lg" educationalContent={educationalContent}
fullWidth generationProgress={generationProgress}
> />
<DialogTitle>
<Box display="flex" alignItems="center" gap={1}>
<SchoolIcon color="primary" />
{educationalContent?.title || 'AI Strategy Generation'}
</Box>
</DialogTitle>
<DialogContent>
{educationalContent ? (
<Box>
{/* Progress Bar */}
<Box sx={{ mb: 3 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={1}>
<Typography variant="body2" color="text.secondary">
Progress: {generationProgress}%
</Typography>
<Typography variant="body2" color="text.secondary">
Step {Math.ceil(generationProgress / 10)} of 10
</Typography>
</Box>
<LinearProgress
variant="determinate"
value={generationProgress}
sx={{ height: 8, borderRadius: 4 }}
/>
</Box>
{/* Educational Content */}
<Typography variant="h6" gutterBottom color="primary">
{educationalContent.title || 'AI Strategy Generation'}
</Typography>
{educationalContent.description && (
<Typography variant="body1" paragraph>
{educationalContent.description}
</Typography>
)}
{educationalContent.details && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
What's happening:
</Typography>
<List dense>
{educationalContent.details.map((detail: string, index: number) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon sx={{ minWidth: 32 }}>
<FiberManualRecordIcon sx={{ fontSize: 8 }} />
</ListItemIcon>
<ListItemText primary={detail} />
</ListItem>
))}
</List>
</Box>
)}
{educationalContent.insight && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="primary" gutterBottom>
💡 Insight:
</Typography>
<Typography variant="body2">
{educationalContent.insight}
</Typography>
</Box>
)}
{educationalContent.ai_prompt_preview && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'blue.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="primary" gutterBottom>
🤖 AI Prompt Preview:
</Typography>
<Typography variant="body2" fontFamily="monospace" fontSize="0.875rem">
{educationalContent.ai_prompt_preview}
</Typography>
</Box>
)}
{educationalContent.estimated_time && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'orange.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="warning.main" gutterBottom>
⏱️ Estimated Time:
</Typography>
<Typography variant="body2">
{educationalContent.estimated_time}
</Typography>
</Box>
)}
{educationalContent.achievement && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'green.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="success.main" gutterBottom>
✅ Achievement:
</Typography>
<Typography variant="body2">
{educationalContent.achievement}
</Typography>
</Box>
)}
{educationalContent.next_step && (
<Box sx={{ mb: 2, p: 2, bgcolor: 'purple.50', borderRadius: 1 }}>
<Typography variant="subtitle2" color="secondary.main" gutterBottom>
🔄 Next Step:
</Typography>
<Typography variant="body2">
{educationalContent.next_step}
</Typography>
</Box>
)}
{/* Summary for completion */}
{educationalContent.summary && (
<Box sx={{ mt: 3, p: 2, bgcolor: 'primary.50', borderRadius: 1 }}>
<Typography variant="h6" gutterBottom color="primary">
🎉 Strategy Generation Summary
</Typography>
<Grid container spacing={2}>
<Grid item xs={6}>
<Typography variant="body2">
<strong>Components:</strong> {educationalContent.summary.successful_components}/{educationalContent.summary.total_components}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2">
<strong>Content Pieces:</strong> {educationalContent.summary.total_content_pieces}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2">
<strong>Estimated ROI:</strong> {educationalContent.summary.estimated_roi}
</Typography>
</Grid>
<Grid item xs={6}>
<Typography variant="body2">
<strong>Timeline:</strong> {educationalContent.summary.implementation_timeline}
</Typography>
</Grid>
</Grid>
</Box>
)}
</Box>
) : (
/* Loading state when educational content is not yet available */
<Box sx={{ textAlign: 'center', py: 4 }}>
<CircularProgress sx={{ mb: 2 }} />
<Typography variant="h6" gutterBottom>
🤖 AI-Powered Strategy Generation
</Typography>
<Typography variant="body1" color="text.secondary" paragraph>
Initializing AI analysis and preparing educational content...
</Typography>
<Typography variant="body2" color="text.secondary">
This may take a few moments as we set up the AI services.
</Typography>
</Box>
)}
</DialogContent>
<DialogActions>
<Button
onClick={() => setShowEducationalModal(false)}
disabled={generationProgress < 100}
>
{generationProgress < 100 ? 'Please wait...' : 'Close'}
</Button>
</DialogActions>
</Dialog>
{/* Data Source Transparency Modal */} {/* Data Source Transparency Modal */}
<Dialog <Dialog

View File

@@ -0,0 +1,227 @@
import React from 'react';
import {
Box,
Button,
Tooltip as MuiTooltip
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
Save as SaveIcon
} from '@mui/icons-material';
import { ActionButtonsProps, ActionButtonsBusinessLogicProps } from '../types/contentStrategy.types';
// Business Logic Hook
export const useActionButtonsBusinessLogic = ({
formData,
error,
currentStrategy,
setAIGenerating,
setError,
setCurrentStrategy,
setSaving,
setGenerationProgress,
setEducationalContent,
setShowEducationalModal,
validateAllFields,
getCompletionStats,
generateAIRecommendations,
createEnhancedStrategy,
contentPlanningApi
}: ActionButtonsBusinessLogicProps) => {
const handleCreateStrategy = async () => {
try {
setAIGenerating(true);
setError(null);
console.log('Starting strategy creation...');
console.log('Current formData:', formData);
console.log('FormData ID:', formData.id);
// If we have a saved strategy, use its ID
if (formData.id) {
console.log('Using existing strategy ID:', formData.id);
await generateAIRecommendations(formData.id);
} else {
console.log('No strategy ID found, creating new strategy...');
// If no strategy is saved yet, save it first, then generate AI insights
const isValid = validateAllFields();
console.log('Form validation result:', isValid);
if (isValid) {
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1, // This would come from auth context
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
console.log('Attempting to create strategy with data:', strategyData);
// Use SSE streaming endpoint for strategy generation with educational content
await generateStrategyWithPolling(strategyData);
} else {
setError('Please fill in all required fields before generating AI insights.');
console.error('Form validation failed. Cannot generate AI insights.');
}
}
} catch (err: any) {
setError(`Error generating AI recommendations: ${err.message || 'Unknown error'}`);
console.error('Error in handleCreateStrategy:', err);
} finally {
setAIGenerating(false);
}
};
const generateStrategyWithPolling = async (strategyData: any) => {
try {
console.log('🚀 Starting polling-based strategy generation...');
// Initialize progress and educational content
setGenerationProgress(0);
setEducationalContent({
title: '🤖 AI-Powered Strategy Generation',
description: 'Initializing AI analysis and preparing educational content...',
details: [
'🔧 Setting up AI services',
'📊 Loading user context',
'🎯 Preparing strategy framework',
'📚 Generating educational content'
],
insight: 'We\'re getting everything ready for your personalized AI strategy generation.',
estimated_time: '2-3 minutes total'
});
// Show educational modal
setShowEducationalModal(true);
// Start polling-based strategy generation directly (no basic strategy creation)
const generationResult = await contentPlanningApi.startStrategyGenerationPolling(1, 'Enhanced Content Strategy');
console.log('Strategy generation started:', generationResult);
if (generationResult && generationResult.task_id) {
const taskId = generationResult.task_id;
console.log('Task ID received:', taskId);
// Start polling for status updates
contentPlanningApi.pollStrategyGeneration(
taskId,
// onProgress callback
(status: any) => {
console.log('📊 Progress update:', status);
// Update progress
if (status.progress !== undefined) {
setGenerationProgress(status.progress);
}
// Update educational content
if (status.educational_content) {
console.log('📚 Updating educational content:', status.educational_content);
setEducationalContent(status.educational_content);
}
// Update message
if (status.message) {
console.log('📝 Status message:', status.message);
}
},
// onComplete callback
(strategy: any) => {
console.log('✅ Strategy generation completed successfully!');
setCurrentStrategy(strategy);
setShowEducationalModal(false);
setError('Strategy created successfully! Check the Strategic Intelligence tab for detailed insights.');
},
// onError callback
(error: string) => {
console.error('❌ Strategy generation failed:', error);
setError(`Strategy generation failed: ${error}`);
setShowEducationalModal(false);
},
5000, // 5 second polling interval for faster updates
72 // 6 minutes max (72 * 5 seconds)
);
} else {
setError('Failed to start strategy generation. No task ID received.');
setShowEducationalModal(false);
}
} catch (error: any) {
console.error('Error in polling-based strategy generation:', error);
setError(`Error in strategy generation: ${error.message || 'Unknown error'}`);
setShowEducationalModal(false);
}
};
const handleSaveStrategy = async () => {
try {
setSaving(true);
setError(null);
const completionStats = getCompletionStats();
const strategyData = {
...formData,
completion_percentage: completionStats.completion_percentage,
user_id: 1,
name: formData.name || 'Enhanced Content Strategy',
industry: formData.industry || 'General'
};
const newStrategy = await createEnhancedStrategy(strategyData);
setCurrentStrategy(newStrategy);
setError('Strategy saved successfully!');
} catch (err: any) {
setError(`Error saving strategy: ${err.message || 'Unknown error'}`);
} finally {
setSaving(false);
}
};
return {
handleCreateStrategy,
handleSaveStrategy
};
};
// UI Component
const ActionButtons: React.FC<ActionButtonsProps> = ({
aiGenerating,
saving,
reviewProgressPercentage,
onCreateStrategy,
onSaveStrategy
}) => {
return (
<Box sx={{ mt: 3, display: 'flex', gap: 2, justifyContent: 'flex-end' }}>
<MuiTooltip
title={reviewProgressPercentage < 20 ? `Complete at least 20% of the form (currently ${Math.round(reviewProgressPercentage)}%)` : 'Create a comprehensive content strategy with AI insights'}
placement="top"
>
<span>
<Button
variant="outlined"
startIcon={<AutoAwesomeIcon />}
onClick={onCreateStrategy}
disabled={aiGenerating || reviewProgressPercentage < 20}
>
{aiGenerating ? 'Creating...' : 'Create Strategy'}
</Button>
</span>
</MuiTooltip>
<Button
variant="contained"
startIcon={<SaveIcon />}
onClick={onSaveStrategy}
disabled={saving || reviewProgressPercentage < 30}
>
{saving ? 'Saving...' : 'Save Strategy'}
</Button>
</Box>
);
};
export default ActionButtons;

View File

@@ -0,0 +1,257 @@
import React from 'react';
import {
Box,
Typography,
Chip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
List,
ListItem,
ListItemIcon,
ListItemText,
Grid,
CircularProgress
} from '@mui/material';
import {
School as SchoolIcon,
Lightbulb as LightbulbIcon,
Psychology as PsychologyIcon,
Timeline as TimelineIcon,
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import StrategicInputField from '../StrategicInputField';
import { CategoryDetailViewProps, EducationalInfoDialogProps } from '../types/contentStrategy.types';
const EducationalInfoDialog: React.FC<EducationalInfoDialogProps> = ({
open,
onClose,
categoryId,
getEducationalContent
}) => {
if (!categoryId) return null;
const educationalContent = getEducationalContent(categoryId);
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="md"
fullWidth
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<SchoolIcon />
{educationalContent?.title}
</Box>
</DialogTitle>
<DialogContent>
<Typography variant="body1" paragraph>
{educationalContent?.description}
</Typography>
<Typography variant="h6" gutterBottom>
Key Points:
</Typography>
<List>
{educationalContent?.points?.map((point: string, index: number) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<LightbulbIcon color="primary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={point} />
</ListItem>
))}
</List>
<Typography variant="h6" gutterBottom>
Pro Tips:
</Typography>
<List>
{educationalContent?.tips?.map((tip: string, index: number) => (
<ListItem key={index} sx={{ py: 0.5 }}>
<ListItemIcon>
<PsychologyIcon color="secondary" fontSize="small" />
</ListItemIcon>
<ListItemText primary={tip} />
</ListItem>
))}
</List>
</DialogContent>
<DialogActions>
<Button onClick={onClose}>
Got it!
</Button>
</DialogActions>
</Dialog>
);
};
const CategoryDetailView: React.FC<CategoryDetailViewProps> = ({
activeCategory,
formData,
formErrors,
autoPopulatedFields,
dataSources,
personalizationData,
completionStats,
reviewedCategories,
isMarkingReviewed,
showEducationalInfo,
STRATEGIC_INPUT_FIELDS,
onUpdateFormField,
onValidateFormField,
onShowTooltip,
onViewDataSource,
onConfirmCategoryReview,
onSetActiveCategory,
onSetShowEducationalInfo,
getCategoryIcon,
getCategoryColor,
getEducationalContent
}) => {
if (!activeCategory) {
return (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: 0.3 }}
>
<Box sx={{ textAlign: 'center', py: 8 }}>
<TimelineIcon sx={{ fontSize: 64, color: 'text.secondary', mb: 2 }} />
<Typography variant="h5" gutterBottom>
Select a Category to Review
</Typography>
<Typography variant="body1" color="text.secondary">
Click on any category from the left panel to review and complete the fields.
</Typography>
</Box>
</motion.div>
);
}
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.3 }}
>
{/* Category Header */}
<Box sx={{ display: 'flex', alignItems: 'center', mb: 3 }}>
{getCategoryIcon(activeCategory)}
<Typography variant="h5" sx={{ ml: 1 }}>
{activeCategory.split('_').map(word =>
word.charAt(0).toUpperCase() + word.slice(1)
).join(' ')}
</Typography>
<Chip
label={`${Math.round(completionStats.category_completion[activeCategory])}% Complete`}
color={getCategoryColor(activeCategory) as any}
sx={{ ml: 'auto' }}
/>
</Box>
{/* Educational Info Dialog */}
<EducationalInfoDialog
open={!!showEducationalInfo}
onClose={() => onSetShowEducationalInfo(null)}
categoryId={showEducationalInfo}
getEducationalContent={getEducationalContent}
/>
{/* Category Fields */}
<Box sx={{ mt: 1 }}>
<Grid container spacing={2}>
{STRATEGIC_INPUT_FIELDS
.filter(field => field.category === activeCategory)
.map((field, index) => {
// Determine grid size based on field type for better layout organization
const type = field.type;
const isWideField = type === 'json';
const isMediumField = type === 'multiselect' || type === 'select' || type === 'text';
const isCompactField = type === 'number' || type === 'boolean';
const forceFullWidth = field.id === 'content_budget' || field.id === 'team_size';
const gridMd = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridLg = forceFullWidth ? 12 : (isWideField ? 12 : isMediumField ? 6 : 4);
const gridSm = 12;
return (
<Grid item xs={12} sm={gridSm} md={gridMd} lg={gridLg} key={field.id}>
<motion.div
initial={{ opacity: 0, y: 10 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.25, delay: index * 0.03 }}
>
<StrategicInputField
fieldId={field.id}
value={formData[field.id]}
error={formErrors[field.id]}
autoPopulated={!!autoPopulatedFields[field.id]}
dataSource={dataSources[field.id]}
confidenceLevel={autoPopulatedFields[field.id] ? 0.8 : undefined}
dataQuality={autoPopulatedFields[field.id] ? 'High Quality' : undefined}
personalizationData={personalizationData[field.id]}
onChange={(value: any) => onUpdateFormField(field.id, value)}
onValidate={() => onValidateFormField(field.id)}
onShowTooltip={() => onShowTooltip(field.id)}
onViewDataSource={onViewDataSource}
accentColorKey={getCategoryColor(activeCategory) as any}
isCompact={isCompactField}
/>
</motion.div>
</Grid>
);
})}
</Grid>
</Box>
{/* Category Actions */}
<Box sx={{ mt: 3, display: 'flex', gap: 2 }}>
{(() => {
const isReviewed = reviewedCategories.has(activeCategory);
console.log('🔍 Category review status:', {
activeCategory,
isReviewed,
reviewedCategories: Array.from(reviewedCategories)
});
return !isReviewed ? (
<Button
variant="contained"
onClick={() => {
console.log('🔘 Button clicked! activeCategory:', activeCategory);
console.log('🔘 reviewedCategories:', Array.from(reviewedCategories));
console.log('🔘 isMarkingReviewed:', isMarkingReviewed);
onConfirmCategoryReview();
}}
startIcon={isMarkingReviewed ? <CircularProgress size={20} /> : <CheckCircleIcon />}
disabled={isMarkingReviewed}
>
{isMarkingReviewed ? 'Marking as Reviewed...' : 'Mark as Reviewed'}
</Button>
) : (
<Chip
label="Category Reviewed"
color="success"
icon={<CheckCircleIcon />}
sx={{ px: 2, py: 1 }}
/>
);
})()}
<Button
variant="outlined"
onClick={() => onSetActiveCategory(null)}
>
Back to Overview
</Button>
</Box>
</motion.div>
);
};
export default CategoryDetailView;

View File

@@ -0,0 +1,554 @@
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Box,
Grid,
List,
ListItem,
ListItemIcon,
ListItemText,
CircularProgress
} from '@mui/material';
import {
School as SchoolIcon,
Lightbulb as LightbulbIcon,
Timeline as TimelineIcon,
AutoAwesome as AutoAwesomeIcon,
CheckCircle as CheckCircleIcon,
Schedule as ScheduleIcon,
TrendingUp as TrendingUpIcon,
FiberManualRecord as FiberManualRecordIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { EducationalModalProps } from '../types/contentStrategy.types';
const EducationalModal: React.FC<EducationalModalProps> = ({
open,
onClose,
educationalContent,
generationProgress
}) => {
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="lg"
fullWidth
PaperProps={{
sx: {
borderRadius: 4,
background: 'linear-gradient(135deg, #ffffff 0%, #f8f9ff 100%)',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.15)',
border: '1px solid rgba(102, 126, 234, 0.1)',
overflow: 'hidden'
}
}}
>
<DialogTitle sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
position: 'relative',
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
pointerEvents: 'none'
}
}}>
<Box display="flex" alignItems="center" gap={2} sx={{ position: 'relative', zIndex: 1 }}>
<Box sx={{
p: 1,
borderRadius: 2,
background: 'rgba(255, 255, 255, 0.2)',
backdropFilter: 'blur(10px)',
boxShadow: '0 4px 12px rgba(0, 0, 0, 0.1)'
}}>
<SchoolIcon sx={{ color: 'white', fontSize: 24 }} />
</Box>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, mb: 0.5 }}>
{educationalContent?.title || 'AI Strategy Generation'}
</Typography>
<Typography variant="body2" sx={{ opacity: 0.9, fontWeight: 500 }}>
Creating your comprehensive content strategy
</Typography>
</Box>
</Box>
</DialogTitle>
<DialogContent sx={{ p: 4 }}>
{educationalContent ? (
<Box>
{/* Enhanced Progress Section */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6 }}
>
<Box sx={{ mb: 4 }}>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={2}>
<Box>
<Typography variant="h6" sx={{ fontWeight: 700, color: '#667eea', mb: 0.5 }}>
Progress: {generationProgress}%
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ fontWeight: 500 }}>
Step {Math.ceil(generationProgress / 10)} of 8 {Math.ceil((100 - generationProgress) / 10)} steps remaining
</Typography>
</Box>
<Box sx={{ textAlign: 'right' }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: '#667eea' }}>
{generationProgress}%
</Typography>
<Typography variant="caption" color="text.secondary">
Complete
</Typography>
</Box>
</Box>
{/* Enhanced Progress Bar with Glitch Effect */}
<Box sx={{ position: 'relative', mb: 1 }}>
<Box sx={{
height: 12,
borderRadius: 6,
background: 'linear-gradient(90deg, #f0f0f0 0%, #e0e0e0 100%)',
overflow: 'hidden',
boxShadow: 'inset 0 2px 4px rgba(0, 0, 0, 0.1)',
position: 'relative'
}}>
<motion.div
initial={{ width: 0 }}
animate={{ width: `${generationProgress}%` }}
transition={{
duration: 0.8,
ease: "easeOut",
delay: 0.2
}}
style={{
height: '100%',
background: 'linear-gradient(90deg, #667eea 0%, #764ba2 50%, #667eea 100%)',
borderRadius: 6,
position: 'relative',
overflow: 'hidden'
}}
>
{/* Glitch Effect Overlay */}
<motion.div
animate={{
x: [0, -2, 2, 0],
opacity: [0.8, 1, 0.8]
}}
transition={{
duration: 0.1,
repeat: Infinity,
repeatType: "reverse"
}}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.3) 50%, transparent 100%)',
borderRadius: 6
}}
/>
{/* Shimmer Effect */}
<motion.div
animate={{
x: ['-100%', '100%']
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "linear"
}}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(90deg, transparent 0%, rgba(255,255,255,0.4) 50%, transparent 100%)',
borderRadius: 6
}}
/>
</motion.div>
</Box>
{/* Progress Markers */}
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
mt: 1,
px: 1
}}>
{[0, 25, 50, 75, 100].map((marker) => (
<Box key={marker} sx={{
width: 8,
height: 8,
borderRadius: '50%',
background: generationProgress >= marker ? '#667eea' : '#e0e0e0',
boxShadow: generationProgress >= marker ? '0 2px 8px rgba(102, 126, 234, 0.3)' : 'none',
transition: 'all 0.3s ease'
}} />
))}
</Box>
</Box>
</Box>
</motion.div>
{/* Enhanced Educational Content */}
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.3 }}
>
<Grid container spacing={3}>
{/* Main Content */}
<Grid item xs={12} lg={8}>
{educationalContent.description && (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{
fontWeight: 600,
color: '#667eea',
display: 'flex',
alignItems: 'center',
gap: 1
}}>
<LightbulbIcon sx={{ fontSize: 20 }} />
What's Happening
</Typography>
<Typography variant="body1" sx={{
lineHeight: 1.7,
color: 'text.primary',
fontWeight: 500
}}>
{educationalContent.description}
</Typography>
</Box>
)}
{educationalContent.details && (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{
fontWeight: 600,
color: '#667eea',
display: 'flex',
alignItems: 'center',
gap: 1
}}>
<TimelineIcon sx={{ fontSize: 20 }} />
Current Activities
</Typography>
<Box sx={{
background: 'rgba(102, 126, 234, 0.05)',
borderRadius: 3,
p: 2,
border: '1px solid rgba(102, 126, 234, 0.1)'
}}>
<List dense sx={{ py: 0 }}>
{educationalContent.details.map((detail: string, index: number) => (
<motion.div
key={index}
initial={{ opacity: 0, x: -20 }}
animate={{ opacity: 1, x: 0 }}
transition={{ duration: 0.5, delay: 0.1 * index }}
>
<ListItem sx={{ py: 1, px: 0 }}>
<ListItemIcon sx={{ minWidth: 36 }}>
<Box sx={{
p: 0.5,
borderRadius: 1,
background: 'rgba(102, 126, 234, 0.1)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}>
<FiberManualRecordIcon sx={{
fontSize: 8,
color: '#667eea'
}} />
</Box>
</ListItemIcon>
<ListItemText
primary={detail}
primaryTypographyProps={{
variant: 'body2',
sx: {
fontWeight: 500,
lineHeight: 1.5,
color: 'text.primary'
}
}}
/>
</ListItem>
</motion.div>
))}
</List>
</Box>
</Box>
)}
{educationalContent.ai_prompt_preview && (
<Box sx={{ mb: 3 }}>
<Typography variant="h6" gutterBottom sx={{
fontWeight: 600,
color: '#667eea',
display: 'flex',
alignItems: 'center',
gap: 1
}}>
<AutoAwesomeIcon sx={{ fontSize: 20 }} />
AI Processing
</Typography>
<Box sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 3,
p: 2.5,
color: 'white',
position: 'relative',
overflow: 'hidden'
}}>
<Box sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
pointerEvents: 'none'
}} />
<Typography variant="body2" sx={{
fontFamily: '"JetBrains Mono", "Fira Code", monospace',
fontSize: '0.875rem',
lineHeight: 1.6,
position: 'relative',
zIndex: 1
}}>
{educationalContent.ai_prompt_preview}
</Typography>
</Box>
</Box>
)}
</Grid>
{/* Sidebar with Insights and Info */}
<Grid item xs={12} lg={4}>
<Box sx={{
background: 'rgba(102, 126, 234, 0.02)',
borderRadius: 3,
p: 3,
border: '1px solid rgba(102, 126, 234, 0.1)',
height: 'fit-content'
}}>
<Typography variant="h6" gutterBottom sx={{
fontWeight: 600,
color: '#667eea',
mb: 2
}}>
Insights & Information
</Typography>
{educationalContent.insight && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{
fontWeight: 600,
color: '#4caf50',
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 1
}}>
<LightbulbIcon sx={{ fontSize: 16 }} />
Key Insight
</Typography>
<Typography variant="body2" sx={{
lineHeight: 1.6,
color: 'text.secondary',
fontWeight: 500
}}>
{educationalContent.insight}
</Typography>
</Box>
)}
{educationalContent.estimated_time && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{
fontWeight: 600,
color: '#ff9800',
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 1
}}>
<ScheduleIcon sx={{ fontSize: 16 }} />
Time Estimate
</Typography>
<Typography variant="body2" sx={{
lineHeight: 1.6,
color: 'text.secondary',
fontWeight: 500
}}>
{educationalContent.estimated_time}
</Typography>
</Box>
)}
{educationalContent.achievement && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{
fontWeight: 600,
color: '#4caf50',
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 1
}}>
<CheckCircleIcon sx={{ fontSize: 16 }} />
Achievement
</Typography>
<Typography variant="body2" sx={{
lineHeight: 1.6,
color: 'text.secondary',
fontWeight: 500
}}>
{educationalContent.achievement}
</Typography>
</Box>
)}
{educationalContent.next_step && (
<Box>
<Typography variant="subtitle2" sx={{
fontWeight: 600,
color: '#9c27b0',
display: 'flex',
alignItems: 'center',
gap: 1,
mb: 1
}}>
<TrendingUpIcon sx={{ fontSize: 16 }} />
Next Step
</Typography>
<Typography variant="body2" sx={{
lineHeight: 1.6,
color: 'text.secondary',
fontWeight: 500
}}>
{educationalContent.next_step}
</Typography>
</Box>
)}
</Box>
</Grid>
</Grid>
{/* Summary for completion */}
{educationalContent.summary && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.6, delay: 0.5 }}
>
<Box sx={{
mt: 4,
p: 3,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 3,
color: 'white',
position: 'relative',
overflow: 'hidden'
}}>
<Box sx={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, rgba(255,255,255,0.1) 0%, rgba(255,255,255,0) 100%)',
pointerEvents: 'none'
}} />
<Typography variant="h6" gutterBottom sx={{
fontWeight: 700,
position: 'relative',
zIndex: 1,
display: 'flex',
alignItems: 'center',
gap: 1
}}>
<AutoAwesomeIcon />
Strategy Generation Summary
</Typography>
<Grid container spacing={2} sx={{ position: 'relative', zIndex: 1 }}>
{Object.entries(educationalContent.summary).map(([key, value]) => (
<Grid item xs={6} md={3} key={key}>
<Box sx={{ textAlign: 'center' }}>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 0.5 }}>
{value as string}
</Typography>
<Typography variant="caption" sx={{ opacity: 0.9, fontWeight: 500 }}>
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}
</Typography>
</Box>
</Grid>
))}
</Grid>
</Box>
</motion.div>
)}
</motion.div>
</Box>
) : (
<Box sx={{ textAlign: 'center', py: 4 }}>
<CircularProgress size={60} sx={{ color: '#667eea', mb: 2 }} />
<Typography variant="h6" sx={{ fontWeight: 600, color: '#667eea' }}>
Initializing Strategy Generation
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mt: 1 }}>
Preparing AI models and analyzing your data...
</Typography>
</Box>
)}
</DialogContent>
<DialogActions sx={{
p: 3,
pt: 0,
justifyContent: 'center'
}}>
<Button
variant="outlined"
onClick={onClose}
sx={{
borderRadius: 2,
px: 4,
py: 1.5,
fontWeight: 600,
borderColor: 'rgba(102, 126, 234, 0.3)',
color: '#667eea',
'&:hover': {
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.05)'
}
}}
>
Close
</Button>
</DialogActions>
</Dialog>
);
};
export default EducationalModal;

View File

@@ -0,0 +1,57 @@
import React from 'react';
import {
Box,
Alert,
Button,
Typography
} from '@mui/material';
import {
Refresh as RefreshIcon,
Info as InfoIcon
} from '@mui/icons-material';
import { ErrorAlertProps } from '../types/contentStrategy.types';
const ErrorAlert: React.FC<ErrorAlertProps> = ({
error,
onRetry,
onShowDataSourceTransparency
}) => {
if (!error) return null;
return (
<Alert
severity="error"
sx={{ mb: 2, display: 'flex', alignItems: 'center', justifyContent: 'space-between' }}
action={
<Box sx={{ display: 'flex', gap: 1 }}>
<Button
size="small"
variant="outlined"
onClick={onRetry}
startIcon={<RefreshIcon />}
>
Retry
</Button>
<Button
size="small"
variant="contained"
color="primary"
onClick={onShowDataSourceTransparency}
startIcon={<InfoIcon />}
>
Why?
</Button>
</Box>
}
>
<Box>
<Typography variant="subtitle2">Real data required</Typography>
<Typography variant="body2">
{error || 'We could not auto-populate because required onboarding/analysis data is missing. Connect sources or complete onboarding, then retry.'}
</Typography>
</Box>
</Alert>
);
};
export default ErrorAlert;

View File

@@ -0,0 +1,79 @@
import React from 'react';
import {
Box,
Paper,
Typography,
Button,
Alert,
Grid
} from '@mui/material';
import {
Assessment as AssessmentIcon
} from '@mui/icons-material';
import { StrategyDisplayProps } from '../types/contentStrategy.types';
const StrategyDisplay: React.FC<StrategyDisplayProps> = ({
currentStrategy,
error,
categoryCompletionMessage,
onViewStrategicIntelligence
}) => {
return (
<>
{/* Success Alert */}
{!error && currentStrategy && (
<Alert severity="success" sx={{ mb: 3 }}>
Strategy "{currentStrategy.name}" created successfully! Check the Strategic Intelligence tab for detailed insights.
</Alert>
)}
{/* Strategy Display */}
{currentStrategy && (
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h5" gutterBottom>
Created Strategy: {currentStrategy.name}
</Typography>
<Grid container spacing={2}>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Industry: {currentStrategy.industry}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
Completion: {currentStrategy.completion_percentage}%
</Typography>
</Grid>
<Grid item xs={12} md={6}>
<Typography variant="subtitle1" color="text.secondary">
Created: {new Date(currentStrategy.created_at).toLocaleDateString()}
</Typography>
<Typography variant="subtitle1" color="text.secondary">
ID: {currentStrategy.id}
</Typography>
</Grid>
</Grid>
<Box sx={{ mt: 2 }}>
<Button
variant="outlined"
onClick={onViewStrategicIntelligence}
startIcon={<AssessmentIcon />}
>
View Strategic Intelligence
</Button>
</Box>
</Paper>
)}
{/* Category Completion Message */}
{categoryCompletionMessage && (
<Alert
severity="success"
sx={{ mb: 3, display: 'flex', alignItems: 'center', justifyContent: 'center' }}
>
{categoryCompletionMessage}
</Alert>
)}
</>
);
};
export default StrategyDisplay;

View File

@@ -0,0 +1,18 @@
// Main Content Strategy Builder
export { default as ContentStrategyBuilder } from '../ContentStrategyBuilder';
// Components
export { default as HeaderSection } from './components/HeaderSection';
export { default as CategoryList } from './components/CategoryList';
export { default as ProgressTracker } from './components/ProgressTracker';
export { default as EducationalModal } from './components/EducationalModal';
export { default as CategoryDetailView } from './components/CategoryDetailView';
export { default as ActionButtons } from './components/ActionButtons';
export { default as StrategyDisplay } from './components/StrategyDisplay';
export { default as ErrorAlert } from './components/ErrorAlert';
// Hooks
export { useActionButtonsBusinessLogic } from './components/ActionButtons';
// Types
export * from './types/contentStrategy.types';

View File

@@ -0,0 +1,124 @@
// Educational Content Types
export interface EducationalContent {
title?: string;
description?: string;
details?: string[];
insight?: string;
estimated_time?: string;
achievement?: string;
next_step?: string;
ai_prompt_preview?: string;
summary?: Record<string, string>;
}
// Educational Modal Props
export interface EducationalModalProps {
open: boolean;
onClose: () => void;
educationalContent: EducationalContent | null;
generationProgress: number;
}
// Category Detail View Types
export interface CategoryDetailViewProps {
activeCategory: string | null;
formData: Record<string, any>;
formErrors: Record<string, any>;
autoPopulatedFields: Record<string, any>;
dataSources: Record<string, any>;
personalizationData: Record<string, any>;
completionStats: any;
reviewedCategories: Set<string>;
isMarkingReviewed: boolean;
showEducationalInfo: string | null;
STRATEGIC_INPUT_FIELDS: any[];
onUpdateFormField: (fieldId: string, value: any) => void;
onValidateFormField: (fieldId: string) => boolean;
onShowTooltip: (fieldId: string) => void;
onViewDataSource: () => void;
onConfirmCategoryReview: () => void;
onSetActiveCategory: (category: string | null) => void;
onSetShowEducationalInfo: (categoryId: string | null) => void;
getCategoryIcon: (category: string) => React.ReactNode;
getCategoryColor: (category: string) => string;
getEducationalContent: (categoryId: string) => any;
}
// Educational Info Dialog Types
export interface EducationalInfoDialogProps {
open: boolean;
onClose: () => void;
categoryId: string | null;
getEducationalContent: (categoryId: string) => any;
}
// Action Buttons Types
export interface ActionButtonsProps {
aiGenerating: boolean;
saving: boolean;
reviewProgressPercentage: number;
onCreateStrategy: () => void;
onSaveStrategy: () => void;
}
// Action Buttons Business Logic Types
export interface ActionButtonsBusinessLogicProps {
formData: Record<string, any>;
error: string | null;
currentStrategy: any;
setAIGenerating: (generating: boolean) => void;
setError: (error: string | null) => void;
setCurrentStrategy: (strategy: any) => void;
setSaving: (saving: boolean) => void;
setGenerationProgress: (progress: number) => void;
setEducationalContent: (content: any) => void;
setShowEducationalModal: (show: boolean) => void;
validateAllFields: () => boolean;
getCompletionStats: () => any;
generateAIRecommendations: (strategyId: string) => Promise<void>;
createEnhancedStrategy: (strategyData: any) => Promise<any>;
contentPlanningApi: any;
}
// Strategy Generation Types
export interface StrategyGenerationStatus {
progress?: number;
message?: string;
educational_content?: EducationalContent;
error?: string;
}
export interface StrategyGenerationCallbacks {
onProgress: (status: StrategyGenerationStatus) => void;
onComplete: (strategy: any) => void;
onError: (error: string) => void;
}
// Category Navigation Types
export interface CategoryNavigationProps {
activeCategory: string | null;
onCategorySelect: (category: string | null) => void;
completionStats: any;
reviewedCategories: Set<string>;
}
// Strategy Display Types
export interface StrategyDisplayProps {
currentStrategy: any;
error: string | null;
categoryCompletionMessage: string | null;
onViewStrategicIntelligence: () => void;
}
// Error Alert Types
export interface ErrorAlertProps {
error: string | null;
onRetry: () => void;
onShowDataSourceTransparency: () => void;
}
// Success Alert Types
export interface SuccessAlertProps {
currentStrategy: any;
categoryCompletionMessage: string | null;
}

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { Box, CircularProgress, Alert, Typography, Grid } from '@mui/material';
import { useStrategyData } from './hooks/useStrategyData';
import { useStrategyActions } from './hooks/useStrategyActions';
import StrategyHeader from './components/StrategyHeader';
import StrategicInsightsCard from './components/StrategicInsightsCard';
import CompetitiveAnalysisCard from './components/CompetitiveAnalysisCard';
import PerformancePredictionsCard from './components/PerformancePredictionsCard';
import ImplementationRoadmapCard from './components/ImplementationRoadmapCard';
import RiskAssessmentCard from './components/RiskAssessmentCard';
import StrategyActions from './components/StrategyActions';
import ConfirmationDialog from './components/ConfirmationDialog';
const StrategyIntelligenceTab: React.FC = () => {
const { strategyData, loading, error, loadStrategyData } = useStrategyData();
const {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar
} = useStrategyActions();
const handleConfirmStrategyClick = () => {
handleConfirmStrategy();
};
const handleConfirmStrategyAction = async () => {
await confirmStrategy(strategyData);
};
const handleGenerateContentCalendarAction = async () => {
try {
await handleGenerateContentCalendar(strategyData);
} catch (error) {
console.error('Error generating content calendar:', error);
}
};
if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 400 }}>
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Alert severity="error" sx={{ m: 2 }}>
{error}
</Alert>
);
}
if (!strategyData) {
return (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
No Strategy Data Available
</Typography>
<Typography variant="body2" color="text.secondary">
Generate a comprehensive strategy first to view strategic intelligence.
</Typography>
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
{/* Header Section */}
<StrategyHeader strategyData={strategyData} strategyConfirmed={strategyConfirmed} />
{/* Strategy Components Grid */}
<Grid container spacing={2}>
<StrategicInsightsCard strategyData={strategyData} />
<CompetitiveAnalysisCard strategyData={strategyData} />
<PerformancePredictionsCard strategyData={strategyData} />
<ImplementationRoadmapCard strategyData={strategyData} />
<RiskAssessmentCard strategyData={strategyData} />
</Grid>
{/* Action Buttons */}
<StrategyActions
strategyData={strategyData}
strategyConfirmed={strategyConfirmed}
onConfirmStrategy={handleConfirmStrategyClick}
onGenerateContentCalendar={handleGenerateContentCalendarAction}
onRefreshData={loadStrategyData}
/>
{/* Confirmation Dialog */}
<ConfirmationDialog
open={showConfirmDialog}
onClose={() => setShowConfirmDialog(false)}
onConfirm={handleConfirmStrategyAction}
/>
</Box>
);
};
export default StrategyIntelligenceTab;

View File

@@ -0,0 +1,435 @@
import React, { useState } from 'react';
import {
Grid,
Typography,
Box,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Accordion,
AccordionSummary,
AccordionDetails,
Popover
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
ExpandMore as ExpandMoreIcon,
Business as BusinessIcon,
Star as StarIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Cancel as CancelIcon,
Lightbulb as LightbulbIcon
} from '@mui/icons-material';
import { StrategyData } from '../types/strategy.types';
import {
ANALYSIS_CARD_STYLES,
getSectionStyles,
getAccordionStyles,
getEnhancedChipStyles,
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
interface CompetitiveAnalysisCardProps {
strategyData: StrategyData | null;
}
const CompetitiveAnalysisCard: React.FC<CompetitiveAnalysisCardProps> = ({ strategyData }) => {
const [chipModal, setChipModal] = useState<{
open: boolean;
anchorEl: HTMLElement | null;
content: string;
type: 'strength' | 'weakness';
}>({
open: false,
anchorEl: null,
content: '',
type: 'strength'
});
// Get style objects
const sectionStyles = getSectionStyles();
const accordionStyles = getAccordionStyles();
const listItemStyles = getListItemStyles();
// Helper function to extract company name from description
const extractCompanyName = (description: string): string => {
if (description.includes('Jasper')) return 'Jasper AI';
if (description.includes('Copy.ai')) return 'Copy.ai';
if (description.includes('Writesonic')) return 'Writesonic';
if (description.includes('Grammarly')) return 'Grammarly';
if (description.includes('Surfer')) return 'Surfer SEO';
if (description.includes('Clearscope')) return 'Clearscope';
if (description.includes('Ahrefs')) return 'Ahrefs';
if (description.includes('SEMrush')) return 'SEMrush';
return description.split(' ').slice(0, 2).join(' ');
};
if (!strategyData?.competitive_analysis) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Competitive Analysis"
subtitle="Market positioning insights"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Competitive analysis data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
}
// Summary content - always visible
const summaryContent = (
<Box>
{/* Competitive Overview */}
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.warning} 0%, ${ANALYSIS_CARD_STYLES.colors.error} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.warning}30`
}}>
{strategyData.competitive_analysis.competitors?.length || 0}
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Key Competitors
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
{strategyData.competitive_analysis.competitors?.length || 0} competitors analyzed
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={`${strategyData.competitive_analysis.market_gaps?.length || 0} Gaps`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
<Chip
label={`${strategyData.competitive_analysis.opportunities?.length || 0} Opportunities`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
</Box>
</Box>
</Box>
{/* Competitors Preview */}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
Top Competitors
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{strategyData.competitive_analysis.competitors?.slice(0, 3).map((competitor: any, index: number) => (
<Chip
key={index}
label={extractCompanyName(competitor.name || competitor.description || 'Unknown')}
size="small"
icon={<BusinessIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
))}
{(strategyData.competitive_analysis.competitors?.length || 0) > 3 && (
<Chip
label={`+${(strategyData.competitive_analysis.competitors?.length || 0) - 3} more`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
/>
)}
</Box>
</Box>
</Box>
);
// Detailed content - shown on expansion
const detailedContent = (
<Box>
{/* Competitors Analysis */}
{strategyData.competitive_analysis.competitors && strategyData.competitive_analysis.competitors.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Competitor Analysis ({strategyData.competitive_analysis.competitors.length})
</Typography>
{strategyData.competitive_analysis.competitors.map((competitor: any, index: number) => (
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
sx={accordionStyles.accordionSummary}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Box sx={{ mr: 1.5 }}>
<BusinessIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontSize: 20 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
{extractCompanyName(competitor.name || competitor.description || 'Unknown Competitor')}
</Typography>
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
{competitor.description || 'Competitor analysis'}
</Typography>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
<Box sx={sectionStyles.sectionContainer}>
{/* Content Strategy */}
{competitor.content_strategy && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
Content Strategy
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{competitor.content_strategy}
</Typography>
</Box>
)}
{/* Strengths */}
{competitor.strengths && competitor.strengths.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
Strengths
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{competitor.strengths.map((strength: string, strengthIndex: number) => (
<Chip
key={strengthIndex}
label={strength}
size="small"
icon={<CheckCircleIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
onClick={(e) => setChipModal({
open: true,
anchorEl: e.currentTarget,
content: strength,
type: 'strength'
})}
/>
))}
</Box>
</Box>
)}
{/* Weaknesses */}
{competitor.weaknesses && competitor.weaknesses.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
Weaknesses
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{competitor.weaknesses.map((weakness: string, weaknessIndex: number) => (
<Chip
key={weaknessIndex}
label={weakness}
size="small"
icon={<CancelIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip}
onClick={(e) => setChipModal({
open: true,
anchorEl: e.currentTarget,
content: weakness,
type: 'weakness'
})}
/>
))}
</Box>
</Box>
)}
</Box>
</AccordionDetails>
</Accordion>
))}
</Box>
)}
{/* Market Gaps */}
{strategyData.competitive_analysis.market_gaps && strategyData.competitive_analysis.market_gaps.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Market Gaps ({strategyData.competitive_analysis.market_gaps.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.competitive_analysis.market_gaps.map((gap: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.info,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={gap}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
{/* Opportunities */}
{strategyData.competitive_analysis.opportunities && strategyData.competitive_analysis.opportunities.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Opportunities ({strategyData.competitive_analysis.opportunities.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.competitive_analysis.opportunities.map((opportunity: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={opportunity}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
{/* Strategic Recommendations */}
{strategyData.competitive_analysis.recommendations && strategyData.competitive_analysis.recommendations.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Strategic Recommendations ({strategyData.competitive_analysis.recommendations.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.competitive_analysis.recommendations.map((recommendation: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.accent,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={recommendation}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
</Box>
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Competitive Analysis"
subtitle="Market positioning insights"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
{/* Chip Modal for detailed view */}
<Popover
open={chipModal.open}
anchorEl={chipModal.anchorEl}
onClose={() => setChipModal({ ...chipModal, open: false })}
anchorOrigin={{
vertical: 'bottom',
horizontal: 'left',
}}
transformOrigin={{
vertical: 'top',
horizontal: 'left',
}}
PaperProps={{
sx: {
background: ANALYSIS_CARD_STYLES.colors.background.dark,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
backdropFilter: 'blur(10px)',
p: 2,
maxWidth: 300
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
{chipModal.type === 'strength' ? (
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16, mr: 1 }} />
) : (
<CancelIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontSize: 16, mr: 1 }} />
)}
<Typography variant="body2" sx={{
color: chipModal.type === 'strength' ? ANALYSIS_CARD_STYLES.colors.success : ANALYSIS_CARD_STYLES.colors.warning,
fontWeight: 600
}}>
{chipModal.type === 'strength' ? 'Strength' : 'Weakness'}
</Typography>
</Box>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{chipModal.content}
</Typography>
</Popover>
</Grid>
);
};
export default CompetitiveAnalysisCard;

View File

@@ -0,0 +1,97 @@
import React from 'react';
import {
Dialog,
DialogTitle,
DialogContent,
DialogActions,
Button,
Typography,
Alert,
Box
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
import { ConfirmationDialogProps } from '../types/strategy.types';
const ConfirmationDialog: React.FC<ConfirmationDialogProps> = ({
open,
onClose,
onConfirm
}) => {
return (
<Dialog
open={open}
onClose={onClose}
maxWidth="sm"
fullWidth
PaperProps={{
sx: {
borderRadius: 3,
boxShadow: '0 16px 48px rgba(0, 0, 0, 0.2)'
}
}}
>
<DialogTitle>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
p: 1,
borderRadius: 2,
background: 'linear-gradient(135deg, #4caf50 0%, #8bc34a 100%)',
mr: 1.5,
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)'
}}>
<CheckCircleIcon sx={{ color: 'white', fontSize: 20 }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 600 }}>
Confirm Strategy
</Typography>
</Box>
</DialogTitle>
<DialogContent>
<Typography variant="body1" sx={{ mb: 2, fontWeight: 500 }}>
Are you sure you want to confirm this strategy? Once confirmed, you'll be able to generate a content calendar based on this strategy.
</Typography>
<Alert severity="info" sx={{
mb: 2,
borderRadius: 2,
border: '1px solid rgba(33, 150, 243, 0.3)'
}}>
<Typography variant="body2" sx={{ fontWeight: 500 }}>
<strong>Next Steps:</strong> After confirmation, you can generate a comprehensive content calendar that follows this strategy.
</Typography>
</Alert>
</DialogContent>
<DialogActions sx={{ p: 3, pt: 0 }}>
<Button
onClick={onClose}
sx={{
borderRadius: 2,
px: 3,
fontWeight: 600
}}
>
Cancel
</Button>
<Button
onClick={onConfirm}
variant="contained"
color="success"
sx={{
borderRadius: 2,
px: 3,
fontWeight: 600,
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)',
'&:hover': {
boxShadow: '0 6px 16px rgba(76, 175, 80, 0.4)'
}
}}
>
Confirm Strategy
</Button>
</DialogActions>
</Dialog>
);
};
export default ConfirmationDialog;

View File

@@ -0,0 +1,453 @@
import React from 'react';
import {
Grid,
Typography,
Box,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Accordion,
AccordionSummary,
AccordionDetails
} from '@mui/material';
import {
Timeline as TimelineIcon,
ExpandMore as ExpandMoreIcon,
Group as GroupIcon,
AttachMoney as MoneyIcon,
CheckCircle as CheckCircleIcon
} from '@mui/icons-material';
import { StrategyData } from '../types/strategy.types';
import {
ANALYSIS_CARD_STYLES,
getSectionStyles,
getAccordionStyles,
getEnhancedChipStyles,
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
interface ImplementationRoadmapCardProps {
strategyData: StrategyData | null;
}
const ImplementationRoadmapCard: React.FC<ImplementationRoadmapCardProps> = ({ strategyData }) => {
// Get style objects
const sectionStyles = getSectionStyles();
const accordionStyles = getAccordionStyles();
const listItemStyles = getListItemStyles();
// Helper function to format budget allocation
const formatBudgetAllocation = (budgetAllocation: any): string => {
if (typeof budgetAllocation === 'string') return budgetAllocation;
if (typeof budgetAllocation === 'object' && budgetAllocation !== null) {
return Object.entries(budgetAllocation)
.map(([key, value]) => `${key}: ${value}`)
.join(', ');
}
return 'Budget allocation not specified';
};
if (!strategyData?.implementation_roadmap) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Project timeline and phases"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Implementation roadmap data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
}
// Summary content - always visible
const summaryContent = (
<Box>
{/* Project Overview */}
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.info} 0%, ${ANALYSIS_CARD_STYLES.colors.primary} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.info}30`
}}>
{strategyData.implementation_roadmap.phases?.length || 0}
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Project Phases
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
{strategyData.implementation_roadmap.total_duration || '6 months'} implementation
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={`${strategyData.implementation_roadmap.phases?.reduce((total: number, phase: any) => total + (phase.tasks?.length || 0), 0) || 0} Tasks`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
<Chip
label={`${strategyData.implementation_roadmap.phases?.reduce((total: number, phase: any) => total + (phase.milestones?.length || 0), 0) || 0} Milestones`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
</Box>
</Box>
</Box>
{/* Phases Preview */}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
Implementation Phases
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{strategyData.implementation_roadmap.phases?.slice(0, 4).map((phase: any, index: number) => (
<Chip
key={index}
label={`Phase ${index + 1}`}
size="small"
icon={<TimelineIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
))}
{(strategyData.implementation_roadmap.phases?.length || 0) > 4 && (
<Chip
label={`+${(strategyData.implementation_roadmap.phases?.length || 0) - 4} more`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
/>
)}
</Box>
</Box>
</Box>
);
// Detailed content - shown on expansion
const detailedContent = (
<Box>
{/* Project Timeline */}
{strategyData.implementation_roadmap.timeline && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Project Timeline
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 1 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Duration: {strategyData.implementation_roadmap.total_duration || '6 months'}
</Typography>
</Box>
{strategyData.implementation_roadmap.timeline.start_date && (
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary, mb: 1 }}>
Start Date: {strategyData.implementation_roadmap.timeline.start_date}
</Typography>
)}
{strategyData.implementation_roadmap.timeline.end_date && (
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary, mb: 1 }}>
End Date: {strategyData.implementation_roadmap.timeline.end_date}
</Typography>
)}
{strategyData.implementation_roadmap.timeline.key_milestones && strategyData.implementation_roadmap.timeline.key_milestones.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600, mb: 1 }}>
Key Milestones:
</Typography>
<List dense>
{strategyData.implementation_roadmap.timeline.key_milestones.map((milestone: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={milestone}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
)}
</Box>
</Box>
)}
{/* Implementation Phases */}
{strategyData.implementation_roadmap.phases && strategyData.implementation_roadmap.phases.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Implementation Phases ({strategyData.implementation_roadmap.phases.length})
</Typography>
{strategyData.implementation_roadmap.phases.map((phase: any, index: number) => (
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
sx={accordionStyles.accordionSummary}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Box sx={{
width: 32,
height: 32,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1rem',
fontWeight: 600
}}>
{index + 1}
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
{phase.name || `Phase ${index + 1}`}
</Typography>
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
{phase.duration || '2 months'} {phase.tasks?.length || 0} tasks {phase.milestones?.length || 0} milestones
</Typography>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
<Box sx={sectionStyles.sectionContainer}>
{/* Phase Description */}
{phase.description && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{phase.description}
</Typography>
</Box>
)}
{/* Tasks */}
{phase.tasks && phase.tasks.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
Tasks ({phase.tasks.length})
</Typography>
<List dense>
{phase.tasks.map((task: string, taskIndex: number) => (
<ListItem key={taskIndex} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.primary,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={task}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
)}
{/* Milestones */}
{phase.milestones && phase.milestones.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
Milestones ({phase.milestones.length})
</Typography>
<List dense>
{phase.milestones.map((milestone: string, milestoneIndex: number) => (
<ListItem key={milestoneIndex} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
</ListItemIcon>
<ListItemText
primary={milestone}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
)}
{/* Resources */}
{phase.resources && phase.resources.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
Resources ({phase.resources.length})
</Typography>
<List dense>
{phase.resources.map((resource: string, resourceIndex: number) => (
<ListItem key={resourceIndex} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.warning,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={resource}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
)}
</Box>
</AccordionDetails>
</Accordion>
))}
</Box>
)}
{/* Resource Allocation */}
{strategyData.implementation_roadmap.resource_allocation && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Resource Allocation
</Typography>
<Box sx={sectionStyles.sectionContainer}>
{/* Team Members */}
{strategyData.implementation_roadmap.resource_allocation.team_members && strategyData.implementation_roadmap.resource_allocation.team_members.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 1 }}>
Team Members ({strategyData.implementation_roadmap.resource_allocation.team_members.length})
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{strategyData.implementation_roadmap.resource_allocation.team_members.map((member: string, index: number) => (
<Chip
key={index}
label={member}
size="small"
icon={<GroupIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
))}
</Box>
</Box>
)}
{/* Budget Allocation */}
{strategyData.implementation_roadmap.resource_allocation.budget_allocation && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
Budget Allocation
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{formatBudgetAllocation(strategyData.implementation_roadmap.resource_allocation.budget_allocation)}
</Typography>
</Box>
)}
</Box>
</Box>
)}
{/* Success Metrics */}
{strategyData.implementation_roadmap.success_metrics && strategyData.implementation_roadmap.success_metrics.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Success Metrics ({strategyData.implementation_roadmap.success_metrics.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.implementation_roadmap.success_metrics.map((metric: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={metric}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
</Box>
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Project timeline and phases"
icon={<TimelineIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
};
export default ImplementationRoadmapCard;

View File

@@ -0,0 +1,539 @@
import React from 'react';
import {
Grid,
Typography,
Box,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Divider
} from '@mui/material';
import {
ShowChart as ShowChartIcon,
TrendingUp as TrendingUpIcon,
Timeline as TimelineIcon,
Assessment as AssessmentIcon
} from '@mui/icons-material';
import { StrategyData } from '../types/strategy.types';
import {
ANALYSIS_CARD_STYLES,
getSectionStyles,
getEnhancedChipStyles,
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
interface PerformancePredictionsCardProps {
strategyData: StrategyData | null;
}
const PerformancePredictionsCard: React.FC<PerformancePredictionsCardProps> = ({ strategyData }) => {
// Get style objects
const sectionStyles = getSectionStyles();
const listItemStyles = getListItemStyles();
if (!strategyData?.performance_predictions) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Performance Predictions"
subtitle="ROI and success metrics"
icon={<ShowChartIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Performance predictions data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
}
// Summary content - always visible
const summaryContent = (
<Box>
{/* ROI Summary */}
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{
display: 'flex',
flexDirection: { xs: 'column', sm: 'row' },
alignItems: { xs: 'flex-start', sm: 'center' },
justifyContent: 'space-between',
mb: 2,
gap: 2,
width: '100%'
}}>
<Box sx={{
display: 'flex',
alignItems: 'center',
flex: 1,
minWidth: 0,
overflow: 'hidden'
}}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.accent} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.success}30`,
flexShrink: 0
}}>
{strategyData.performance_predictions.roi_predictions?.estimated_roi || '25%'}
</Box>
<Box sx={{
minWidth: 0,
flex: 1,
overflow: 'hidden'
}}>
<Typography variant="h6" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.primary,
fontWeight: 600,
fontSize: '1rem',
lineHeight: 1.2,
mb: 0.5,
wordBreak: 'break-word'
}}>
ROI Predictions
</Typography>
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.7rem',
lineHeight: 1.2,
wordBreak: 'break-word'
}}>
Expected return on investment
</Typography>
</Box>
</Box>
<Box sx={{
display: 'flex',
gap: 1,
flexWrap: 'wrap',
justifyContent: { xs: 'flex-start', sm: 'flex-end' },
flexShrink: 0
}}>
<Chip
label={`${(strategyData.performance_predictions as any)?.success_probability || '85%'} Success`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
<Chip
label={`${(strategyData.performance_predictions as any)?.implementation_timeline || '6 months'}`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
</Box>
</Box>
{/* ROI Description - Full width container */}
<Box sx={{ mt: 2, width: '100%' }}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.8rem',
lineHeight: 1.5,
wordBreak: 'break-word',
textAlign: 'left'
}}>
ROI of {strategyData.performance_predictions.roi_predictions?.estimated_roi || '300-350%'} is achievable leveraging the strong cost-per-lead to lifetime-value ratio.
</Typography>
</Box>
</Box>
{/* Key Metrics Preview */}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.primary,
mb: 1.5,
fontWeight: 600,
fontSize: '0.85rem'
}}>
Key Metrics Preview
</Typography>
<Box sx={{
display: 'flex',
flexWrap: 'wrap',
gap: 1,
justifyContent: 'flex-start'
}}>
<Chip
label="Traffic Growth"
size="small"
icon={<TrendingUpIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
<Chip
label="Engagement"
size="small"
icon={<AssessmentIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
/>
<Chip
label="Conversion"
size="small"
icon={<ShowChartIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip}
/>
</Box>
</Box>
</Box>
);
// Detailed content - shown on expansion
const detailedContent = (
<Box>
{/* ROI Summary */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
ROI Summary
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1.5 }}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.85rem',
lineHeight: 1.5,
wordBreak: 'break-word'
}}>
Estimated ROI: {strategyData.performance_predictions.roi_predictions?.estimated_roi || '25%'}
</Typography>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.85rem',
lineHeight: 1.5,
wordBreak: 'break-word'
}}>
Success Probability: {(strategyData.performance_predictions as any)?.success_probability || '85%'}
</Typography>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.85rem',
lineHeight: 1.5,
wordBreak: 'break-word'
}}>
Implementation Timeline: {(strategyData.performance_predictions as any)?.implementation_timeline || '6 months'}
</Typography>
</Box>
</Box>
</Box>
{/* Key Metrics */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Key Metrics
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{
display: 'grid',
gridTemplateColumns: { xs: '1fr', sm: 'repeat(auto-fit, minmax(200px, 1fr))' },
gap: 2
}}>
{/* Traffic Predictions */}
{strategyData.performance_predictions.traffic_predictions && (
<Box sx={{
p: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.success}`,
borderRadius: 2,
background: `rgba(76, 175, 80, 0.1)`,
minHeight: 80,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
overflow: 'hidden'
}}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.success,
fontWeight: 600,
mb: 1,
fontSize: '0.8rem',
lineHeight: 1.2,
wordBreak: 'break-word'
}}>
Traffic Growth
</Typography>
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.7rem',
lineHeight: 1.3,
wordBreak: 'break-word'
}}>
{strategyData.performance_predictions.traffic_predictions.growth_rate || '150% increase'}
</Typography>
</Box>
)}
{/* Engagement Predictions */}
{strategyData.performance_predictions.engagement_predictions && (
<Box sx={{
p: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.secondary}`,
borderRadius: 2,
background: `rgba(118, 75, 162, 0.1)`,
minHeight: 80,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
overflow: 'hidden'
}}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.secondary,
fontWeight: 600,
mb: 1,
fontSize: '0.8rem',
lineHeight: 1.2,
wordBreak: 'break-word'
}}>
Engagement Rate
</Typography>
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.7rem',
lineHeight: 1.3,
wordBreak: 'break-word'
}}>
{strategyData.performance_predictions.engagement_predictions.engagement_rate || '45% improvement'}
</Typography>
</Box>
)}
{/* Conversion Predictions */}
{strategyData.performance_predictions.conversion_predictions && (
<Box sx={{
p: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.accent}`,
borderRadius: 2,
background: `rgba(240, 147, 251, 0.1)`,
minHeight: 80,
display: 'flex',
flexDirection: 'column',
justifyContent: 'center',
overflow: 'hidden'
}}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.accent,
fontWeight: 600,
mb: 1,
fontSize: '0.8rem',
lineHeight: 1.2,
wordBreak: 'break-word'
}}>
Conversion Rate
</Typography>
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.7rem',
lineHeight: 1.3,
wordBreak: 'break-word'
}}>
{strategyData.performance_predictions.conversion_predictions.conversion_rate || '3.2% to 5.8%'}
</Typography>
</Box>
)}
</Box>
</Box>
</Box>
<Divider sx={{ my: 2, opacity: 0.2, borderColor: ANALYSIS_CARD_STYLES.colors.border.secondary }} />
{/* Detailed Predictions */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Detailed Predictions
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{/* Traffic Predictions */}
{strategyData.performance_predictions.traffic_predictions && (
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary="Traffic Growth"
secondary={`${strategyData.performance_predictions.traffic_predictions.growth_rate || '150% increase'} in organic visitors`}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 0.5 }
}}
secondaryTypographyProps={{
variant: 'caption',
fontSize: '0.75rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
}}
/>
</ListItem>
)}
{/* Engagement Predictions */}
{strategyData.performance_predictions.engagement_predictions && (
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.secondary,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary="Engagement Rate"
secondary={`${strategyData.performance_predictions.engagement_predictions.engagement_rate || '45% improvement'} in user interaction`}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.secondary, fontWeight: 600, mb: 0.5 }
}}
secondaryTypographyProps={{
variant: 'caption',
fontSize: '0.75rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
}}
/>
</ListItem>
)}
{/* Conversion Predictions */}
{strategyData.performance_predictions.conversion_predictions && (
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.accent,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary="Conversion Rate"
secondary={`${strategyData.performance_predictions.conversion_predictions.conversion_rate || '3.2% to 5.8%'} improvement`}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.accent, fontWeight: 600, mb: 0.5 }
}}
secondaryTypographyProps={{
variant: 'caption',
fontSize: '0.75rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
}}
/>
</ListItem>
)}
{/* ROI Predictions */}
{strategyData.performance_predictions.roi_predictions && (
<ListItem sx={{ ...listItemStyles.listItem, py: 1.5 }}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary="Revenue Impact"
secondary={`${(strategyData.performance_predictions.roi_predictions as any)?.revenue_impact || '$50K'} additional monthly revenue`}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 0.5 }
}}
secondaryTypographyProps={{
variant: 'caption',
fontSize: '0.75rem',
sx: { color: ANALYSIS_CARD_STYLES.colors.text.secondary, lineHeight: 1.4 }
}}
/>
</ListItem>
)}
</List>
</Box>
</Box>
{/* Timeline Projections */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Timeline Projections
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.8rem',
lineHeight: 1.5
}}>
Month 1-2: Initial setup and foundation building
</Typography>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.8rem',
lineHeight: 1.5
}}>
Month 3-4: Content creation and optimization
</Typography>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.8rem',
lineHeight: 1.5
}}>
Month 5-6: Scaling and performance optimization
</Typography>
<Typography variant="body2" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.8rem',
lineHeight: 1.5
}}>
Ongoing: Continuous monitoring and improvement
</Typography>
</Box>
</Box>
</Box>
</Box>
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Performance Predictions"
subtitle="ROI and success metrics"
icon={<ShowChartIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
};
export default PerformancePredictionsCard;

View File

@@ -0,0 +1,259 @@
import React, { useState, useRef } from 'react';
import {
Card,
CardContent,
Box,
Button,
Typography,
Fade,
useTheme
} from '@mui/material';
import {
ExpandMore as ExpandMoreIcon,
ExpandLess as ExpandLessIcon,
KeyboardArrowDown as ArrowDownIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import {
ANALYSIS_CARD_STYLES,
getAnalysisCardStyles,
getEnhancedChipStyles
} from '../styles';
interface ProgressiveCardProps {
summary: React.ReactNode;
details: React.ReactNode;
trigger?: 'hover' | 'click';
title?: string;
subtitle?: string;
icon?: React.ReactNode;
autoCollapseDelay?: number; // milliseconds
className?: string;
}
const ProgressiveCard: React.FC<ProgressiveCardProps> = ({
summary,
details,
trigger = 'click',
title,
subtitle,
icon,
autoCollapseDelay = 3000, // 3 seconds default
className
}) => {
const [isExpanded, setIsExpanded] = useState(false);
const hoverTimeoutRef = useRef<NodeJS.Timeout | null>(null);
const theme = useTheme();
const cardStyles = getAnalysisCardStyles();
// Handle hover interactions
const handleMouseEnter = () => {
if (trigger === 'hover') {
// Clear any existing timeout
if (hoverTimeoutRef.current) {
clearTimeout(hoverTimeoutRef.current);
hoverTimeoutRef.current = null;
}
setIsExpanded(true);
}
};
const handleMouseLeave = () => {
if (trigger === 'hover') {
// Set timeout to auto-collapse
hoverTimeoutRef.current = setTimeout(() => {
setIsExpanded(false);
hoverTimeoutRef.current = null;
}, autoCollapseDelay);
}
};
// Handle click interactions
const handleToggle = () => {
if (trigger === 'click') {
setIsExpanded(!isExpanded);
}
};
return (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: 0.1 }}
whileHover={{ y: -4 }}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
className={className}
>
<Card sx={{
...cardStyles.card,
'& .shimmer-text': {
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
backgroundClip: 'text',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
animation: 'shimmer 2s ease-in-out infinite'
},
'& .bounce-icon': {
animation: 'bounce 2s infinite'
},
'@keyframes shimmer': {
'0%, 100%': { opacity: 1 },
'50%': { opacity: 0.8 }
},
'@keyframes bounce': {
'0%, 20%, 50%, 80%, 100%': { transform: 'translateY(0)' },
'40%': { transform: 'translateY(-4px)' },
'60%': { transform: 'translateY(-2px)' }
}
}}>
<CardContent sx={cardStyles.cardContent}>
{/* Header Section */}
{title && (
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
{icon && (
<Box sx={{
p: 1,
borderRadius: 2,
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.primary} 0%, ${ANALYSIS_CARD_STYLES.colors.secondary} 100%)`,
mr: 1.5,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.primary}30`
}}>
{icon}
</Box>
)}
<Box>
<Typography variant="h6" className="shimmer-text" sx={{
fontWeight: 600
}}>
{title}
</Typography>
{subtitle && (
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: '0.75rem'
}}>
{subtitle}
</Typography>
)}
</Box>
</Box>
{/* Trigger Button */}
{trigger === 'click' && (
<Button
onClick={handleToggle}
variant="text"
size="small"
sx={{
color: ANALYSIS_CARD_STYLES.colors.primary,
'&:hover': {
background: 'rgba(102, 126, 234, 0.1)'
},
minWidth: 'auto',
px: 1.5,
py: 0.5,
borderRadius: 2,
fontSize: '0.75rem',
fontWeight: 600,
textTransform: 'none'
}}
endIcon={
isExpanded ? (
<ExpandLessIcon sx={{ fontSize: 16 }} />
) : (
<ExpandMoreIcon sx={{ fontSize: 16 }} />
)
}
>
{isExpanded ? 'Show Less' : 'Read More'}
</Button>
)}
</Box>
)}
{/* Summary Section - Always Visible */}
<Box sx={{ mb: trigger === 'click' ? 2 : 0 }}>
{summary}
</Box>
{/* Progressive Details Section */}
<AnimatePresence>
{isExpanded && (
<motion.div
initial={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
animate={{
height: 'auto',
opacity: 1,
overflow: 'visible'
}}
exit={{
height: 0,
opacity: 0,
overflow: 'hidden'
}}
transition={{
duration: 0.4,
ease: [0.4, 0.0, 0.2, 1],
opacity: { duration: 0.3 }
}}
>
<Fade in={isExpanded} timeout={300}>
<Box sx={{
pt: 2,
borderTop: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
opacity: 0.9
}}>
{details}
</Box>
</Fade>
</motion.div>
)}
</AnimatePresence>
{/* Hover Indicator (for hover trigger) */}
{trigger === 'hover' && !isExpanded && (
<motion.div
initial={{ opacity: 0 }}
animate={{ opacity: 0.6 }}
transition={{ duration: 0.3 }}
>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mt: 1,
py: 0.5
}}>
<ArrowDownIcon className="bounce-icon" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
fontSize: 16
}} />
<Typography variant="caption" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
ml: 0.5,
fontSize: '0.7rem'
}}>
Hover to see more
</Typography>
</Box>
</motion.div>
)}
</CardContent>
</Card>
</motion.div>
);
};
export default ProgressiveCard;

View File

@@ -0,0 +1,413 @@
import React from 'react';
import {
Grid,
Typography,
Box,
Chip
} from '@mui/material';
import {
Lightbulb as LightbulbIcon,
TrendingUp as TrendingUpIcon,
Security as SecurityIcon,
Schedule as ScheduleIcon,
Assessment as AssessmentIcon
} from '@mui/icons-material';
import ProgressiveCard from './ProgressiveCard';
import { ANALYSIS_CARD_STYLES, getEnhancedChipStyles } from '../styles';
const ProgressiveDemo: React.FC = () => {
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.primary,
mb: 3,
textAlign: 'center',
fontWeight: 600
}}>
Progressive Disclosure Demo
</Typography>
<Typography variant="body1" sx={{
color: ANALYSIS_CARD_STYLES.colors.text.secondary,
mb: 4,
textAlign: 'center',
maxWidth: 800,
mx: 'auto'
}}>
Experience the new progressive disclosure system. Cards show a summary initially,
with detailed content revealed through user interaction.
</Typography>
<Grid container spacing={3}>
{/* Click Trigger Example */}
<Grid item xs={12} md={6}>
<ProgressiveCard
title="Strategic Insights"
subtitle="Click to expand"
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.info} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600
}}>
85%
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Market Analysis
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Strong positioning identified
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="High Growth"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
<Chip
label="6 months"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
</Box>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip label="Market" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip} />
<Chip label="Consumer" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip} />
<Chip label="Business" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip} />
</Box>
</Box>
}
details={
<Box>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Detailed Strategic Insights
</Typography>
<Box sx={{
p: 2,
background: ANALYSIS_CARD_STYLES.colors.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
backdropFilter: 'blur(10px)'
}}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Market positioning analysis reveals strong competitive advantages
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Consumer behavior patterns indicate high engagement potential
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Business model optimization opportunities identified
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
Revenue growth projections show 25% increase potential
</Typography>
</Box>
</Box>
}
trigger="click"
/>
</Grid>
{/* Hover Trigger Example */}
<Grid item xs={12} md={6}>
<ProgressiveCard
title="Performance Predictions"
subtitle="Hover to expand"
icon={<TrendingUpIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.warning} 0%, ${ANALYSIS_CARD_STYLES.colors.error} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600
}}>
92%
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
ROI Predictions
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
High success probability
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="25% ROI"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
<Chip
label="6 months"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
</Box>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip label="Traffic" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip} />
<Chip label="Engagement" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip} />
<Chip label="Conversion" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.accent).chip} />
</Box>
</Box>
}
details={
<Box>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Detailed Performance Metrics
</Typography>
<Box sx={{
p: 2,
background: ANALYSIS_CARD_STYLES.colors.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
backdropFilter: 'blur(10px)'
}}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Traffic growth: 150% increase in organic visitors
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Engagement rate: 45% improvement in user interaction
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Conversion rate: 3.2% to 5.8% improvement
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
Revenue impact: $50K additional monthly revenue
</Typography>
</Box>
</Box>
}
trigger="hover"
autoCollapseDelay={5000}
/>
</Grid>
{/* Risk Assessment Example */}
<Grid item xs={12} md={6}>
<ProgressiveCard
title="Risk Assessment"
subtitle="Click to expand"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.error} 0%, ${ANALYSIS_CARD_STYLES.colors.warning} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600
}}>
Med
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Risk Level
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Medium risk identified
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="7 Risks"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip}
/>
<Chip
label="Mitigated"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
</Box>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip label="Technical" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip} />
<Chip label="Market" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip} />
<Chip label="Operational" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip} />
</Box>
</Box>
}
details={
<Box>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Risk Categories & Mitigation
</Typography>
<Box sx={{
p: 2,
background: ANALYSIS_CARD_STYLES.colors.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
backdropFilter: 'blur(10px)'
}}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Technical Risks: Infrastructure scalability challenges
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Market Risks: Competitive pressure and market saturation
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Operational Risks: Resource allocation and team scaling
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
Mitigation: Comprehensive monitoring and contingency plans
</Typography>
</Box>
</Box>
}
trigger="click"
/>
</Grid>
{/* Implementation Roadmap Example */}
<Grid item xs={12} md={6}>
<ProgressiveCard
title="Implementation Roadmap"
subtitle="Hover to expand"
icon={<ScheduleIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box>
<Box sx={{
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
mb: 2
}}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.info} 0%, ${ANALYSIS_CARD_STYLES.colors.primary} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600
}}>
6M
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Timeline
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
6-month implementation
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="4 Phases"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
<Chip
label="5 Team"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
</Box>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip label="Phase 1" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip} />
<Chip label="Phase 2" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip} />
<Chip label="Phase 3" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip} />
<Chip label="Phase 4" size="small" sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip} />
</Box>
</Box>
}
details={
<Box>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Implementation Phases
</Typography>
<Box sx={{
p: 2,
background: ANALYSIS_CARD_STYLES.colors.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_CARD_STYLES.colors.border.secondary}`,
backdropFilter: 'blur(10px)'
}}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Phase 1 (Months 1-2): Foundation and setup
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Phase 2 (Months 3-4): Core implementation
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1 }}>
Phase 3 (Months 5-6): Optimization and scaling
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary }}>
Phase 4 (Ongoing): Monitoring and maintenance
</Typography>
</Box>
</Box>
}
trigger="hover"
autoCollapseDelay={4000}
/>
</Grid>
</Grid>
</Box>
);
};
export default ProgressiveDemo;

View File

@@ -0,0 +1,404 @@
import React from 'react';
import {
Grid,
Typography,
Box,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Accordion,
AccordionSummary,
AccordionDetails
} from '@mui/material';
import {
Security as SecurityIcon,
ExpandMore as ExpandMoreIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Error as ErrorIcon,
Info as InfoIcon
} from '@mui/icons-material';
import { StrategyData } from '../types/strategy.types';
import {
ANALYSIS_CARD_STYLES,
getSectionStyles,
getAccordionStyles,
getEnhancedChipStyles,
getListItemStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
interface RiskAssessmentCardProps {
strategyData: StrategyData | null;
}
const RiskAssessmentCard: React.FC<RiskAssessmentCardProps> = ({ strategyData }) => {
// Get style objects
const sectionStyles = getSectionStyles();
const accordionStyles = getAccordionStyles();
const listItemStyles = getListItemStyles();
// Helper function to get risk level color
const getRiskLevelColor = (level: string) => {
const lowerLevel = level.toLowerCase();
if (lowerLevel.includes('high')) return ANALYSIS_CARD_STYLES.colors.error;
if (lowerLevel.includes('medium')) return ANALYSIS_CARD_STYLES.colors.warning;
if (lowerLevel.includes('low')) return ANALYSIS_CARD_STYLES.colors.success;
return ANALYSIS_CARD_STYLES.colors.info;
};
if (!strategyData?.risk_assessment) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Risk Assessment"
subtitle="Risk analysis and mitigation"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Risk assessment data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
}
// Summary content - always visible
const summaryContent = (
<Box>
{/* Risk Overview */}
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')} 0%, ${ANALYSIS_CARD_STYLES.colors.warning} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600,
boxShadow: `0 4px 12px ${getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')}30`
}}>
{strategyData.risk_assessment.risks?.length || 0}
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Risk Level
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
{strategyData.risk_assessment.overall_risk_level || 'Medium'} overall risk
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label={`${strategyData.risk_assessment.risks?.length || 0} Risks`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.error).chip}
/>
<Chip
label={`${strategyData.risk_assessment.mitigation_strategies?.length || 0} Mitigations`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
</Box>
</Box>
</Box>
{/* Risk Categories Preview */}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
Risk Categories
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).slice(0, 4).map((category: string) => (
<Chip
key={category}
label={category.replace(/_/g, ' ')}
size="small"
icon={<WarningIcon />}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.warning).chip}
/>
))}
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).length > 4 && (
<Chip
label={`+${Object.keys(strategyData.risk_assessment.risk_categories).length - 4} more`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
/>
)}
</Box>
</Box>
</Box>
);
// Detailed content - shown on expansion
const detailedContent = (
<Box>
{/* Overall Risk Assessment */}
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Overall Risk Assessment
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Chip
label={strategyData.risk_assessment.overall_risk_level || 'Medium'}
size="medium"
icon={<SecurityIcon />}
sx={getEnhancedChipStyles(getRiskLevelColor(strategyData.risk_assessment.overall_risk_level || 'Medium')).chip}
/>
</Box>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
{strategyData.risk_assessment.risks?.length || 0} identified risks with {strategyData.risk_assessment.mitigation_strategies?.length || 0} mitigation strategies
</Typography>
</Box>
</Box>
{/* Individual Risks */}
{strategyData.risk_assessment.risks && strategyData.risk_assessment.risks.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Individual Risks ({strategyData.risk_assessment.risks.length})
</Typography>
{strategyData.risk_assessment.risks.map((risk: any, index: number) => (
<Accordion key={index} defaultExpanded={false} sx={accordionStyles.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
sx={accordionStyles.accordionSummary}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Box sx={{ mr: 1.5 }}>
<ErrorIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.error, fontSize: 20 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
{typeof risk === 'string' ? risk : risk.risk || 'Risk'}
</Typography>
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
{typeof risk === 'object' && risk.probability ? `${risk.probability} probability, ${risk.impact} impact` : 'Risk assessment'}
</Typography>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
<Box sx={sectionStyles.sectionContainer}>
{typeof risk === 'object' && (
<>
{/* Risk Details */}
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem', mb: 1 }}>
{risk.risk}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 1 }}>
<Chip
label={`Probability: ${risk.probability || 'Unknown'}`}
size="small"
sx={getEnhancedChipStyles(
risk.probability === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
risk.probability === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
ANALYSIS_CARD_STYLES.colors.success
).chip}
/>
<Chip
label={`Impact: ${risk.impact || 'Unknown'}`}
size="small"
sx={getEnhancedChipStyles(
risk.impact === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
risk.impact === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
ANALYSIS_CARD_STYLES.colors.success
).chip}
/>
</Box>
</Box>
{/* Mitigation Strategy */}
{risk.mitigation && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontWeight: 600, mb: 1 }}>
Mitigation Strategy
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{risk.mitigation}
</Typography>
</Box>
)}
{/* Contingency Plan */}
{risk.contingency && (
<Box sx={{ mb: 2 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontWeight: 600, mb: 1 }}>
Contingency Plan
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{risk.contingency}
</Typography>
</Box>
)}
</>
)}
{typeof risk === 'string' && (
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{risk}
</Typography>
)}
</Box>
</AccordionDetails>
</Accordion>
))}
</Box>
)}
{/* Mitigation Strategies */}
{strategyData.risk_assessment.mitigation_strategies && strategyData.risk_assessment.mitigation_strategies.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Mitigation Strategies ({strategyData.risk_assessment.mitigation_strategies.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.risk_assessment.mitigation_strategies.map((strategy: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<CheckCircleIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.success, fontSize: 16 }} />
</ListItemIcon>
<ListItemText
primary={strategy}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
{/* Risk Categories */}
{strategyData.risk_assessment.risk_categories && Object.keys(strategyData.risk_assessment.risk_categories).length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Risk Categories
</Typography>
{Object.entries(strategyData.risk_assessment.risk_categories).map(([category, risks]: [string, any]) => (
<Accordion key={category} defaultExpanded={false} sx={accordionStyles.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
sx={accordionStyles.accordionSummary}
>
<Box sx={{ display: 'flex', alignItems: 'center', width: '100%' }}>
<Box sx={{ mr: 1.5 }}>
<WarningIcon sx={{ color: ANALYSIS_CARD_STYLES.colors.warning, fontSize: 20 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
{category.replace(/_/g, ' ')}
</Typography>
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
{Array.isArray(risks) ? `${risks.length} risks` : 'Risk category'}
</Typography>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
<Box sx={sectionStyles.sectionContainer}>
{Array.isArray(risks) ? (
<List dense>
{risks.map((risk: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.warning,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={risk}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
) : (
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{typeof risks === 'string' ? risks : 'Risk category details'}
</Typography>
)}
</Box>
</AccordionDetails>
</Accordion>
))}
</Box>
)}
{/* Monitoring Framework */}
{strategyData.risk_assessment.monitoring_framework && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Monitoring Framework
</Typography>
<Box sx={sectionStyles.sectionContainer}>
{Object.entries(strategyData.risk_assessment.monitoring_framework).map(([key, value]: [string, any]) => (
<Box key={key} sx={{ mb: 1 }}>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.primary, fontWeight: 600, mb: 0.5 }}>
{key.replace(/_/g, ' ')}
</Typography>
<Typography variant="body2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontSize: '0.875rem' }}>
{typeof value === 'string' ? value : JSON.stringify(value)}
</Typography>
</Box>
))}
</Box>
</Box>
)}
</Box>
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Risk Assessment"
subtitle="Risk analysis and mitigation"
icon={<SecurityIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={3000}
/>
</Grid>
);
};
export default RiskAssessmentCard;

View File

@@ -0,0 +1,330 @@
import React from 'react';
import {
Grid,
Typography,
Box,
Chip,
List,
ListItem,
ListItemText,
ListItemIcon,
Accordion,
AccordionSummary,
AccordionDetails
} from '@mui/material';
import {
Lightbulb as LightbulbIcon,
ExpandMore as ExpandMoreIcon,
TrendingUp as TrendingUpIcon,
Psychology as PsychologyIcon,
Business as BusinessIcon,
Analytics as AnalyticsIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { StrategyData } from '../types/strategy.types';
import {
ANALYSIS_CARD_STYLES,
getSectionStyles,
getAccordionStyles,
getEnhancedChipStyles,
getListItemStyles,
getAnimationStyles
} from '../styles';
import ProgressiveCard from './ProgressiveCard';
interface StrategicInsightsCardProps {
strategyData: StrategyData | null;
}
const StrategicInsightsCard: React.FC<StrategicInsightsCardProps> = ({ strategyData }) => {
// Get style objects
const sectionStyles = getSectionStyles();
const accordionStyles = getAccordionStyles();
const listItemStyles = getListItemStyles();
const animationStyles = getAnimationStyles();
console.log('🔍 StrategicInsightsCard - strategyData:', strategyData);
console.log('🔍 StrategicInsightsCard - strategic_insights:', strategyData?.strategic_insights);
if (!strategyData?.strategic_insights) {
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Strategic Insights"
subtitle="AI-powered market analysis"
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="body1" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Strategic insights data not available
</Typography>
</Box>
}
details={
<Box sx={{ textAlign: 'center', py: 2 }}>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Available data keys: {strategyData ? Object.keys(strategyData).join(', ') : 'No data'}
</Typography>
</Box>
}
trigger="click"
/>
</Grid>
);
}
// Helper function to get insight icon
const getInsightIcon = (type: string) => {
switch (type?.toLowerCase()) {
case 'market': return <TrendingUpIcon />;
case 'consumer': return <PsychologyIcon />;
case 'business': return <BusinessIcon />;
case 'analytics': return <AnalyticsIcon />;
default: return <LightbulbIcon />;
}
};
// Summary content - always visible
const summaryContent = (
<Box>
{/* Market Analysis Summary */}
<Box sx={sectionStyles.sectionContainer}>
<Box sx={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{
width: 40,
height: 40,
borderRadius: '50%',
background: `linear-gradient(135deg, ${ANALYSIS_CARD_STYLES.colors.success} 0%, ${ANALYSIS_CARD_STYLES.colors.info} 100%)`,
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mr: 2,
color: 'white',
fontSize: '1.2rem',
fontWeight: 600,
boxShadow: `0 4px 12px ${ANALYSIS_CARD_STYLES.colors.success}30`
}}>
85%
</Box>
<Box>
<Typography variant="h6" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, fontWeight: 600 }}>
Market Analysis
</Typography>
<Typography variant="caption" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.secondary }}>
Strong market positioning identified
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', gap: 1 }}>
<Chip
label="High Growth"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.success).chip}
/>
<Chip
label="6 months"
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
</Box>
</Box>
</Box>
{/* Key Insights Preview */}
<Box sx={{ mt: 2 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 1, fontWeight: 600 }}>
Key Insights Preview
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
{strategyData.strategic_insights.insights?.slice(0, 3).map((insight: any, index: number) => (
<Chip
key={index}
label={insight.type}
size="small"
icon={getInsightIcon(insight.type)}
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.primary).chip}
/>
))}
{(strategyData.strategic_insights.insights?.length || 0) > 3 && (
<Chip
label={`+${(strategyData.strategic_insights.insights?.length || 0) - 3} more`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.secondary).chip}
/>
)}
</Box>
</Box>
</Box>
);
// Detailed content - shown on expansion
const detailedContent = (
<Box>
{/* Strategic Insights by Type */}
{strategyData.strategic_insights.insights && strategyData.strategic_insights.insights.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Detailed Strategic Insights ({strategyData.strategic_insights.insights.length})
</Typography>
{/* Group insights by type */}
{Object.entries(
strategyData.strategic_insights.insights.reduce((acc: any, insight: any) => {
const type = insight.type || 'General';
if (!acc[type]) acc[type] = [];
acc[type].push(insight);
return acc;
}, {})
).map(([type, insights]: [string, any]) => (
<Accordion key={type} defaultExpanded={false} sx={accordionStyles.accordion}>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={accordionStyles.expandIcon} />}
sx={accordionStyles.accordionSummary}
>
<Box sx={{ display: 'flex', alignItems: 'center' }}>
<Box sx={{ mr: 1.5 }}>
{getInsightIcon(type)}
</Box>
<Box>
<Typography variant="body2" sx={accordionStyles.accordionTitle}>
{type} Insights ({insights.length})
</Typography>
<Typography variant="caption" sx={accordionStyles.accordionSubtitle}>
{insights.length} strategic {insights.length === 1 ? 'insight' : 'insights'}
</Typography>
</Box>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ pt: 0 }}>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{insights.map((insight: any, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.primary,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={insight.insight}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
secondary={
<Box sx={{ mt: 0.5 }}>
<Box sx={{ display: 'flex', gap: 1, mb: 0.5 }}>
<Chip
label={`P: ${insight.priority || 'Medium'}`}
size="small"
sx={getEnhancedChipStyles(
insight.priority === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
insight.priority === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
ANALYSIS_CARD_STYLES.colors.success
).chip}
/>
<Chip
label={`I: ${insight.estimated_impact || 'Medium'}`}
size="small"
sx={getEnhancedChipStyles(
insight.estimated_impact === 'High' ? ANALYSIS_CARD_STYLES.colors.error :
insight.estimated_impact === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
ANALYSIS_CARD_STYLES.colors.success
).chip}
/>
<Chip
label={`C: ${insight.confidence_level || 'Medium'}`}
size="small"
sx={getEnhancedChipStyles(
insight.confidence_level === 'High' ? ANALYSIS_CARD_STYLES.colors.success :
insight.confidence_level === 'Medium' ? ANALYSIS_CARD_STYLES.colors.warning :
ANALYSIS_CARD_STYLES.colors.error
).chip}
/>
<Chip
label={`T: ${insight.implementation_time || '3 months'}`}
size="small"
sx={getEnhancedChipStyles(ANALYSIS_CARD_STYLES.colors.info).chip}
/>
</Box>
{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}
</Typography>
)}
</Box>
}
/>
</ListItem>
))}
</List>
</Box>
</AccordionDetails>
</Accordion>
))}
</Box>
)}
{/* Content Opportunities */}
{strategyData.strategic_insights.content_opportunities && strategyData.strategic_insights.content_opportunities.length > 0 && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle2" sx={{ color: ANALYSIS_CARD_STYLES.colors.text.primary, mb: 2, fontWeight: 600 }}>
Content Opportunities ({strategyData.strategic_insights.content_opportunities.length})
</Typography>
<Box sx={sectionStyles.sectionContainer}>
<List dense>
{strategyData.strategic_insights.content_opportunities.map((opportunity: string, index: number) => (
<ListItem key={index} sx={listItemStyles.listItem}>
<ListItemIcon sx={listItemStyles.listItemIcon}>
<Box sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: ANALYSIS_CARD_STYLES.colors.success,
opacity: 0.7
}} />
</ListItemIcon>
<ListItemText
primary={opportunity}
primaryTypographyProps={{
variant: 'body2',
fontSize: '0.875rem',
sx: { lineHeight: 1.4, color: ANALYSIS_CARD_STYLES.colors.text.primary }
}}
/>
</ListItem>
))}
</List>
</Box>
</Box>
)}
</Box>
);
return (
<Grid item xs={12} lg={6}>
<ProgressiveCard
title="Strategic Insights"
subtitle="AI-powered market analysis"
icon={<LightbulbIcon sx={{ color: 'white', fontSize: 20 }} />}
summary={summaryContent}
details={detailedContent}
trigger="hover"
autoCollapseDelay={2000}
/>
</Grid>
);
};
export default StrategicInsightsCard;

View File

@@ -0,0 +1,162 @@
import React from 'react';
import {
Box,
Button,
Alert
} from '@mui/material';
import {
Check as CheckIcon,
CalendarToday as CalendarIcon,
Refresh as RefreshIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { StrategyData } from '../types/strategy.types';
interface StrategyActionsProps {
strategyData: StrategyData | null;
strategyConfirmed: boolean;
onConfirmStrategy: () => void;
onGenerateContentCalendar: () => void;
onRefreshData: () => void;
}
const StrategyActions: React.FC<StrategyActionsProps> = ({
strategyData,
strategyConfirmed,
onConfirmStrategy,
onGenerateContentCalendar,
onRefreshData
}) => {
return (
<Box sx={{ mt: 4 }}>
{/* Strategy Confirmation Status */}
{strategyConfirmed && (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Alert
severity="success"
sx={{
mb: 3,
borderRadius: 2,
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.2)',
border: '1px solid rgba(76, 175, 80, 0.3)'
}}
action={
<Button
color="inherit"
size="small"
onClick={onGenerateContentCalendar}
startIcon={<CalendarIcon />}
sx={{
fontWeight: 600,
'&:hover': {
backgroundColor: 'rgba(76, 175, 80, 0.1)',
transform: 'translateY(-1px)'
},
transition: 'all 0.3s ease'
}}
>
Generate Content Calendar
</Button>
}
>
Strategy confirmed! You can now generate a content calendar based on this strategy.
</Alert>
</motion.div>
)}
{/* Action Buttons */}
<Box sx={{ display: 'flex', gap: 2, justifyContent: 'center' }}>
{!strategyConfirmed ? (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="contained"
size="large"
onClick={onConfirmStrategy}
startIcon={<CheckIcon />}
sx={{
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
boxShadow: '0 8px 32px rgba(102, 126, 234, 0.3)',
'&:hover': {
background: 'linear-gradient(135deg, #5a6fd8 0%, #6a4190 100%)',
boxShadow: '0 12px 40px rgba(102, 126, 234, 0.4)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
Confirm Strategy
</Button>
</motion.div>
) : (
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="contained"
size="large"
onClick={onGenerateContentCalendar}
startIcon={<CalendarIcon />}
color="success"
sx={{
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
boxShadow: '0 8px 32px rgba(76, 175, 80, 0.3)',
'&:hover': {
boxShadow: '0 12px 40px rgba(76, 175, 80, 0.4)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
Generate Content Calendar
</Button>
</motion.div>
)}
<motion.div
whileHover={{ scale: 1.05 }}
whileTap={{ scale: 0.95 }}
>
<Button
variant="outlined"
size="large"
onClick={onRefreshData}
startIcon={<RefreshIcon />}
sx={{
borderRadius: 3,
px: 4,
py: 1.5,
fontWeight: 600,
borderColor: 'rgba(102, 126, 234, 0.3)',
color: '#667eea',
'&:hover': {
borderColor: '#667eea',
backgroundColor: 'rgba(102, 126, 234, 0.05)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
}}
>
Refresh Data
</Button>
</motion.div>
</Box>
</Box>
);
};
export default StrategyActions;

View File

@@ -0,0 +1,619 @@
import React, { useState } from 'react';
import {
Paper,
Grid,
Typography,
Chip,
Box,
Tooltip,
LinearProgress,
Badge,
Card,
CardContent,
CircularProgress,
Button
} from '@mui/material';
import {
Psychology as PsychologyIcon,
TrendingUp as TrendingUpIcon,
CheckCircle as CheckCircleIcon,
Warning as WarningIcon,
Schedule as ScheduleIcon,
CalendarToday as CalendarTodayIcon,
AutoAwesome as AutoAwesomeIcon,
Timeline as TimelineIcon,
Info as InfoIcon,
Analytics as AnalyticsIcon,
Assessment as AssessmentIcon,
Business as BusinessIcon,
ShowChart as ShowChartIcon,
Security as SecurityIcon,
ArrowForward as ArrowForwardIcon,
Star as StarIcon,
DataUsage as DataUsageIcon,
Input as InputIcon,
Storage as StorageIcon,
Person as PersonIcon
} from '@mui/icons-material';
import { motion } from 'framer-motion';
import { StrategyData } from '../types/strategy.types';
import { getStrategyName, getStrategyGenerationDate } from '../utils/strategyTransformers';
interface StrategyHeaderProps {
strategyData: StrategyData | null;
strategyConfirmed: boolean;
}
const StrategyHeader: React.FC<StrategyHeaderProps> = ({ strategyData, strategyConfirmed }) => {
const [showNextStepText, setShowNextStepText] = useState(false);
if (!strategyData) return null;
// Helper function to get percentage value from string
const getPercentageValue = (value: string | undefined) => {
if (!value) return 0;
const match = value.match(/(\d+)/);
return match ? parseInt(match[1]) : 0;
};
// Helper function to get risk color
const getRiskColor = (riskLevel: string | undefined) => {
switch (riskLevel?.toLowerCase()) {
case 'low': return '#4caf50';
case 'medium': return '#ff9800';
case 'high': return '#f44336';
case 'high-medium': return '#ff5722';
default: return '#ff9800';
}
};
// Helper function to extract company name from strategy name
const getCompanyName = (strategyName: string) => {
// Extract company name from strategy name (e.g., "Enhanced Content Strategy" -> "ALwrity")
// For now, we'll use a default company name
return "ALwrity";
};
// Helper function to get timeline percentage
const getTimelinePercentage = (timeline: string | undefined) => {
if (!timeline) return 0;
const match = timeline.match(/(\d+)/);
return match ? Math.min(parseInt(match[1]) * 10, 100) : 0; // Convert months to percentage
};
const roiValue = getPercentageValue(strategyData.summary?.estimated_roi);
const successValue = getPercentageValue(strategyData.summary?.success_probability);
const timelineValue = getTimelinePercentage(strategyData.summary?.implementation_timeline);
const riskColor = getRiskColor(strategyData.summary?.risk_level);
const companyName = getCompanyName(getStrategyName(strategyData));
// Analysis components data
const analysisComponents = [
{ name: 'Strategic Insights', icon: <AnalyticsIcon />, color: '#667eea' },
{ name: 'Competitive Analysis', icon: <BusinessIcon />, color: '#4caf50' },
{ name: 'Performance Predictions', icon: <ShowChartIcon />, color: '#2196f3' },
{ name: 'Implementation Roadmap', icon: <TimelineIcon />, color: '#ff9800' },
{ name: 'Risk Assessment', icon: <SecurityIcon />, color: '#f44336' }
];
// Mock data sources and user inputs (replace with actual data from strategyData)
const dataSources = [
{ name: 'Industry Analysis', type: 'Market Research', icon: <DataUsageIcon /> },
{ name: 'Competitor Data', type: 'External Sources', icon: <StorageIcon /> },
{ name: 'User Preferences', type: 'Input Survey', icon: <PersonIcon /> },
{ name: 'Content Performance', type: 'Analytics', icon: <InputIcon /> }
];
return (
<motion.div
initial={{ opacity: 0, y: -20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.8, ease: "easeOut" }}
>
<Card
sx={{
mb: 3,
background: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%)',
color: 'white',
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(102, 126, 234, 0.3)',
borderRadius: 3,
position: 'relative',
overflow: 'hidden',
border: '1px solid rgba(102, 126, 234, 0.3)',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%)',
pointerEvents: 'none'
},
'&::after': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'linear-gradient(45deg, transparent 30%, rgba(255,255,255,0.1) 50%, transparent 70%)',
animation: 'shimmer 3s infinite',
pointerEvents: 'none'
},
'@keyframes shimmer': {
'0%': { transform: 'translateX(-100%)' },
'100%': { transform: 'translateX(100%)' }
},
'@keyframes gradient': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' }
}
}}
>
{/* Animated Border Lights */}
<motion.div
animate={{
boxShadow: [
'0 0 20px rgba(102, 126, 234, 0.5)',
'0 0 40px rgba(102, 126, 234, 0.8)',
'0 0 20px rgba(102, 126, 234, 0.5)'
]
}}
transition={{
duration: 2,
repeat: Infinity,
ease: "easeInOut"
}}
style={{
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
borderRadius: '12px',
pointerEvents: 'none'
}}
/>
<CardContent sx={{ position: 'relative', zIndex: 1, p: 2.5 }}>
{/* Header Section - More Compact */}
<Grid container spacing={2} alignItems="center" sx={{ mb: 1.5 }}>
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 0.5 }}>
<motion.div
animate={{ rotate: [0, 10, -10, 0] }}
transition={{ duration: 2, repeat: Infinity, ease: "easeInOut" }}
>
<PsychologyIcon sx={{ fontSize: 28, color: '#667eea', mr: 1.5 }} />
</motion.div>
<Box>
<Typography variant="h5" sx={{
fontWeight: 800,
background: 'linear-gradient(45deg, #667eea, #764ba2, #f093fb)',
backgroundSize: '200% 200%',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
animation: 'gradient 3s ease infinite',
mb: 0.25
}}>
{companyName} Content Strategy
</Typography>
<Typography variant="body2" sx={{
opacity: 0.8,
color: '#e0e0e0',
display: 'flex',
alignItems: 'center',
gap: 1,
fontSize: '0.75rem'
}}>
<ScheduleIcon sx={{ fontSize: 12 }} />
Generated on {getStrategyGenerationDate(strategyData)}
</Typography>
</Box>
</Box>
{/* Strategy Metadata Chips - More Compact */}
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75, mb: 1 }}>
<Tooltip title="AI-Generated Strategy using advanced machine learning" arrow>
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
<Chip
icon={<AutoAwesomeIcon />}
label="AI Generated"
size="small"
sx={{
background: 'rgba(102, 126, 234, 0.2)',
color: '#667eea',
border: '1px solid rgba(102, 126, 234, 0.3)',
fontWeight: 600,
fontSize: '0.65rem',
height: 24
}}
/>
</Badge>
</Tooltip>
<Tooltip title="Comprehensive analysis covering all strategic aspects" arrow>
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
<Chip
icon={<PsychologyIcon />}
label="Comprehensive"
size="small"
sx={{
background: 'rgba(76, 175, 80, 0.2)',
color: '#4caf50',
border: '1px solid rgba(76, 175, 80, 0.3)',
fontWeight: 600,
fontSize: '0.65rem',
height: 24
}}
/>
</Badge>
</Tooltip>
<Tooltip title="Content calendar generation status" arrow>
<Badge badgeContent={<WarningIcon sx={{ fontSize: 10, color: '#ff9800' }} />} color="default">
<Chip
icon={<CalendarTodayIcon />}
label={strategyData.strategy_metadata?.content_calendar_ready ? "Calendar Ready" : "Calendar Pending"}
size="small"
color={strategyData.strategy_metadata?.content_calendar_ready ? "success" : "warning"}
sx={{
background: strategyData.strategy_metadata?.content_calendar_ready
? 'rgba(76, 175, 80, 0.2)'
: 'rgba(255, 152, 0, 0.2)',
border: '1px solid rgba(255, 255, 255, 0.2)',
fontWeight: 600,
fontSize: '0.65rem',
height: 24
}}
/>
</Badge>
</Tooltip>
</Box>
</Grid>
{/* Key Metrics Section with 4 Circular Progress Charts */}
<Grid item xs={12} md={6}>
<Box sx={{ display: 'flex', justifyContent: 'space-around', alignItems: 'center' }}>
{/* ROI Circular Progress */}
<Tooltip title="Estimated Return on Investment for content marketing efforts" arrow>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={roiValue}
size={50}
thickness={3}
sx={{
color: '#4caf50',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" sx={{ color: '#4caf50', fontWeight: 700, fontSize: '0.6rem' }}>
{roiValue}%
</Typography>
</Box>
</Box>
<Typography variant="caption" sx={{ color: '#4caf50', fontWeight: 600, fontSize: '0.65rem' }}>
ROI
</Typography>
</Box>
</Tooltip>
{/* Success Probability Circular Progress */}
<Tooltip title="Probability of achieving the defined content strategy goals" arrow>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={successValue}
size={50}
thickness={3}
sx={{
color: '#2196f3',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" sx={{ color: '#2196f3', fontWeight: 700, fontSize: '0.6rem' }}>
{successValue}%
</Typography>
</Box>
</Box>
<Typography variant="caption" sx={{ color: '#2196f3', fontWeight: 600, fontSize: '0.65rem' }}>
Success
</Typography>
</Box>
</Tooltip>
{/* Risk Level Circular Progress */}
<Tooltip title="Overall risk assessment for the content strategy implementation" arrow>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={strategyData.summary?.risk_level === 'Low' ? 25 :
strategyData.summary?.risk_level === 'Medium' ? 50 :
strategyData.summary?.risk_level === 'High' ? 75 : 60}
size={50}
thickness={3}
sx={{
color: riskColor,
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" sx={{ color: riskColor, fontWeight: 700, fontSize: '0.6rem' }}>
{strategyData.summary?.risk_level === 'Low' ? 'L' :
strategyData.summary?.risk_level === 'Medium' ? 'M' :
strategyData.summary?.risk_level === 'High' ? 'H' : 'HM'}
</Typography>
</Box>
</Box>
<Typography variant="caption" sx={{ color: riskColor, fontWeight: 600, fontSize: '0.65rem' }}>
Risk
</Typography>
</Box>
</Tooltip>
{/* Timeline Circular Progress */}
<Tooltip title="Implementation timeline for the content strategy" arrow>
<Box sx={{ display: 'flex', flexDirection: 'column', alignItems: 'center', gap: 0.5 }}>
<Box sx={{ position: 'relative', display: 'inline-flex' }}>
<CircularProgress
variant="determinate"
value={timelineValue}
size={50}
thickness={3}
sx={{
color: '#667eea',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
<Box
sx={{
top: 0,
left: 0,
bottom: 0,
right: 0,
position: 'absolute',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
}}
>
<Typography variant="caption" sx={{ color: '#667eea', fontWeight: 700, fontSize: '0.6rem' }}>
{strategyData.summary?.implementation_timeline?.match(/\d+/)?.[0] || '6'}m
</Typography>
</Box>
</Box>
<Typography variant="caption" sx={{ color: '#667eea', fontWeight: 600, fontSize: '0.65rem' }}>
Timeline
</Typography>
</Box>
</Tooltip>
</Box>
</Grid>
</Grid>
{/* Strategy Status Section - Expanded to show data sources and analysis components */}
<Grid container spacing={2}>
<Grid item xs={12}>
<Box sx={{
background: 'rgba(255, 255, 255, 0.05)',
p: 2,
borderRadius: 2,
border: '1px solid rgba(255, 255, 255, 0.1)',
backdropFilter: 'blur(10px)'
}}>
<Typography variant="h6" sx={{ fontWeight: 600, mb: 1.5, color: '#667eea', fontSize: '0.9rem' }}>
Strategy Status & Data Sources
</Typography>
<Grid container spacing={2}>
{/* Status Info */}
<Grid item xs={12} md={4}>
<Box sx={{ display: 'flex', flexDirection: 'column', gap: 1 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<TrendingUpIcon sx={{ color: '#4caf50', fontSize: 16 }} />
<Box>
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem' }}>
Status
</Typography>
<Typography variant="body2" sx={{ color: 'white', fontWeight: 600, fontSize: '0.75rem' }}>
{strategyConfirmed ? 'Confirmed' : 'Pending Review'}
</Typography>
</Box>
</Box>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<InfoIcon sx={{ color: '#2196f3', fontSize: 16 }} />
<Box>
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem' }}>
User ID
</Typography>
<Typography variant="body2" sx={{ color: 'white', fontWeight: 600, fontSize: '0.75rem' }}>
{strategyData.strategy_metadata?.user_id}
</Typography>
</Box>
</Box>
</Box>
</Grid>
{/* Data Sources */}
<Grid item xs={12} md={8}>
<Box>
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem', mb: 1, display: 'block' }}>
Data Sources & User Inputs
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
{dataSources.map((source, index) => (
<Chip
key={index}
icon={source.icon}
label={`${source.name} (${source.type})`}
size="small"
sx={{
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.3) 0%, rgba(102, 126, 234, 0.2) 100%)',
color: '#667eea',
border: '1px solid rgba(102, 126, 234, 0.4)',
fontWeight: 600,
fontSize: '0.65rem',
height: 24,
boxShadow: '0 2px 8px rgba(102, 126, 234, 0.2)',
'&:hover': {
background: 'linear-gradient(135deg, rgba(102, 126, 234, 0.4) 0%, rgba(102, 126, 234, 0.3) 100%)',
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)',
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
))}
</Box>
</Box>
</Grid>
</Grid>
{/* Analysis Components Section - Moved inside the card */}
<Box sx={{ mt: 2, pt: 2, borderTop: '1px solid rgba(255, 255, 255, 0.1)' }}>
<Typography variant="caption" sx={{ color: '#e0e0e0', fontWeight: 500, fontSize: '0.7rem', mb: 1, display: 'block' }}>
Analysis Components
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 0.75 }}>
{analysisComponents.map((component, index) => (
<Tooltip key={index} title={`${component.name} analysis completed`} arrow>
<Badge badgeContent={<CheckCircleIcon sx={{ fontSize: 10, color: '#4caf50' }} />} color="default">
<Chip
icon={component.icon}
label={component.name}
size="small"
sx={{
background: `linear-gradient(135deg, ${component.color}30 0%, ${component.color}20 100%)`,
color: component.color,
border: `1px solid ${component.color}50`,
fontWeight: 600,
fontSize: '0.65rem',
height: 24,
boxShadow: `0 2px 8px ${component.color}20`,
'&:hover': {
background: `linear-gradient(135deg, ${component.color}40 0%, ${component.color}30 100%)`,
boxShadow: `0 4px 12px ${component.color}30`,
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
</Badge>
</Tooltip>
))}
<Chip
icon={<StarIcon />}
label="Ready for Review"
size="small"
color="success"
sx={{
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.3) 0%, rgba(76, 175, 80, 0.2) 100%)',
color: '#4caf50',
border: '1px solid rgba(76, 175, 80, 0.4)',
fontWeight: 600,
fontSize: '0.65rem',
height: 24,
boxShadow: '0 2px 8px rgba(76, 175, 80, 0.2)',
'&:hover': {
background: 'linear-gradient(135deg, rgba(76, 175, 80, 0.4) 0%, rgba(76, 175, 80, 0.3) 100%)',
boxShadow: '0 4px 12px rgba(76, 175, 80, 0.3)',
transform: 'translateY(-1px)'
},
transition: 'all 0.2s ease'
}}
/>
</Box>
</Box>
</Box>
</Grid>
</Grid>
{/* Next Steps Button - Area B */}
<Box sx={{ mt: 1.5, display: 'flex', justifyContent: 'center' }}>
<Tooltip
title={strategyData.summary?.next_step || "Review strategy and generate content calendar"}
arrow
open={showNextStepText}
onClose={() => setShowNextStepText(false)}
>
<Button
variant="contained"
onMouseEnter={() => setShowNextStepText(true)}
onMouseLeave={() => setShowNextStepText(false)}
sx={{
background: 'linear-gradient(135deg, #4caf50 0%, #66bb6a 50%, #81c784 100%)',
color: 'white',
fontWeight: 700,
fontSize: '0.8rem',
px: 3,
py: 1,
borderRadius: 3,
boxShadow: '0 4px 15px rgba(76, 175, 80, 0.4)',
border: '2px solid rgba(76, 175, 80, 0.3)',
'&:hover': {
background: 'linear-gradient(135deg, #66bb6a 0%, #81c784 50%, #a5d6a7 100%)',
boxShadow: '0 6px 20px rgba(76, 175, 80, 0.6)',
transform: 'translateY(-2px)'
},
transition: 'all 0.3s ease',
textTransform: 'none'
}}
startIcon={<ArrowForwardIcon />}
>
Next Step
</Button>
</Tooltip>
</Box>
</CardContent>
</Card>
</motion.div>
);
};
export default StrategyHeader;

View File

@@ -0,0 +1,33 @@
import React from 'react';
import { Box, Typography, Paper } from '@mui/material';
import { StrategyData } from '../types/strategy.types';
interface TestComponentProps {
strategyData: StrategyData | null;
}
const TestComponent: React.FC<TestComponentProps> = ({ strategyData }) => {
if (!strategyData) return null;
return (
<Paper sx={{ p: 2, mb: 2, background: 'rgba(76, 175, 80, 0.1)' }}>
<Typography variant="h6" color="success.main" gutterBottom>
Modular Structure Test
</Typography>
<Typography variant="body2">
Strategy Name: {strategyData.strategy_metadata?.strategy_name || strategyData.metadata?.strategy_name}
</Typography>
<Typography variant="body2">
ROI: {strategyData.summary?.estimated_roi}
</Typography>
<Typography variant="body2">
Success Probability: {strategyData.summary?.success_probability}
</Typography>
<Typography variant="body2" color="success.main">
Modular structure is working correctly!
</Typography>
</Paper>
);
};
export default TestComponent;

View File

@@ -0,0 +1,84 @@
import { useState } from 'react';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import { StrategyData } from '../types/strategy.types';
export const useStrategyActions = () => {
const [strategyConfirmed, setStrategyConfirmed] = useState(false);
const [showConfirmDialog, setShowConfirmDialog] = useState(false);
const handleConfirmStrategy = () => {
setShowConfirmDialog(true);
};
const confirmStrategy = async (strategyData: StrategyData | null) => {
try {
setStrategyConfirmed(true);
setShowConfirmDialog(false);
// Save confirmation status to backend
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id;
if (userId) {
try {
// Update the strategy with confirmation status
await contentPlanningApi.updateEnhancedStrategy(
userId.toString(),
{ confirmed: true, confirmed_at: new Date().toISOString() }
);
console.log('Strategy confirmation saved to backend');
} catch (updateError) {
console.warn('Could not save confirmation to backend:', updateError);
// Don't fail the confirmation if backend update fails
}
}
console.log('Strategy confirmed! Ready to generate content calendar.');
} catch (error) {
console.error('Error confirming strategy:', error);
setStrategyConfirmed(false);
}
};
const handleGenerateContentCalendar = async (strategyData: StrategyData | null) => {
try {
const userId = strategyData?.strategy_metadata?.user_id || strategyData?.metadata?.user_id;
if (!userId) {
console.error('No strategy data available for calendar generation');
return;
}
// Generate content calendar based on confirmed strategy
const calendarRequest = {
user_id: userId,
strategy_id: userId, // Using user_id as strategy_id for now
calendar_type: 'comprehensive',
industry: strategyData.base_strategy?.industry || 'technology',
business_size: 'medium', // TODO: Get from strategy data
force_refresh: false
};
console.log('Generating content calendar with request:', calendarRequest);
// Call the calendar generation API
const calendarResponse = await contentPlanningApi.generateCalendar(calendarRequest);
console.log('Content calendar generated successfully:', calendarResponse);
// TODO: Navigate to calendar tab or show success message
// You could also store the calendar data in a global state
} catch (error) {
console.error('Error generating content calendar:', error);
// Show error message to user
throw new Error('Failed to generate content calendar. Please try again.');
}
};
return {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar
};
};

View File

@@ -0,0 +1,178 @@
import { useState, useEffect } from 'react';
import { contentPlanningApi } from '../../../../../services/contentPlanningApi';
import { StrategyData } from '../types/strategy.types';
import {
getUserId,
transformPollingStrategyData,
transformFullStructureData,
transformSwotToComprehensiveStructure,
hasFullStructure
} from '../utils/strategyTransformers';
export const useStrategyData = () => {
const [strategyData, setStrategyData] = useState<StrategyData | null>(null);
const [loading, setLoading] = useState(false);
const [error, setError] = useState<string | null>(null);
const loadStrategyData = async (forceRefresh = false) => {
try {
setLoading(true);
setError(null);
const userId = getUserId();
// First, try to get the latest generated strategy from the polling system
try {
const latestStrategyResponse = await contentPlanningApi.getLatestGeneratedStrategy(userId);
if (latestStrategyResponse?.strategy) {
console.log('✅ Found latest generated strategy from polling system:', latestStrategyResponse.strategy);
const transformedStrategy = transformPollingStrategyData(latestStrategyResponse.strategy);
console.log('🔄 Transformed strategy data:', transformedStrategy);
setStrategyData(transformedStrategy);
setLoading(false);
return;
}
} catch (pollingError) {
console.log('No latest strategy found in polling system, checking database...', pollingError);
}
// If no strategy found in polling system, try to get from database
try {
const strategiesResponse = await contentPlanningApi.getEnhancedStrategies(userId);
// Handle the enhanced strategies response structure
const strategies = strategiesResponse?.data?.strategies || strategiesResponse?.strategies || [];
if (strategies && strategies.length > 0) {
// Get the most recent strategy (assuming it's sorted by creation date)
const latestStrategy = strategies[0];
// Check if this strategy has comprehensive AI-generated data
if (latestStrategy.comprehensive_ai_analysis) {
console.log('✅ Found comprehensive strategy in database:', latestStrategy);
console.log('📊 Comprehensive AI analysis structure:', latestStrategy.comprehensive_ai_analysis);
console.log('🔍 Available fields:', Object.keys(latestStrategy.comprehensive_ai_analysis));
// Check if this is the full 5-component structure or SWOT analysis
if (hasFullStructure(latestStrategy.comprehensive_ai_analysis)) {
// Transform the data to match frontend expectations (full 5-component structure)
const transformedStrategy = transformFullStructureData(latestStrategy);
console.log('🔄 Transformed enhanced strategy data (full structure):', transformedStrategy);
console.log('🎯 Final strategy data structure:', {
hasStrategicInsights: !!transformedStrategy.strategic_insights,
hasCompetitiveAnalysis: !!transformedStrategy.competitive_analysis,
hasPerformancePredictions: !!transformedStrategy.performance_predictions,
hasImplementationRoadmap: !!transformedStrategy.implementation_roadmap,
hasRiskAssessment: !!transformedStrategy.risk_assessment,
hasSummary: !!transformedStrategy.summary
});
setStrategyData(transformedStrategy);
setLoading(false);
return;
} else {
// This is SWOT analysis, create a comprehensive 5-component structure enhanced with SWOT data
console.log('🔄 Creating comprehensive 5-component structure from SWOT analysis');
const transformedStrategy = transformSwotToComprehensiveStructure(latestStrategy);
console.log('🔄 Created comprehensive 5-component structure from SWOT analysis:', transformedStrategy);
console.log('🎯 Final strategy data structure:', {
hasStrategicInsights: !!transformedStrategy.strategic_insights,
hasCompetitiveAnalysis: !!transformedStrategy.competitive_analysis,
hasPerformancePredictions: !!transformedStrategy.performance_predictions,
hasImplementationRoadmap: !!transformedStrategy.implementation_roadmap,
hasRiskAssessment: !!transformedStrategy.risk_assessment,
hasSummary: !!transformedStrategy.summary,
swotEnhanced: true
});
setStrategyData(transformedStrategy);
setLoading(false);
return;
}
} else {
console.log('⚠️ Strategy found but no comprehensive_ai_analysis field:', {
strategyId: latestStrategy.id,
strategyName: latestStrategy.name,
availableFields: Object.keys(latestStrategy)
});
}
}
} catch (dbError) {
console.log('No comprehensive strategies found in database, checking for recent generation...', dbError);
}
// If no comprehensive strategy found in database, check for recent generation tasks
try {
// Try to get the latest strategy generation result
const recentStrategies = await contentPlanningApi.getStrategies(userId);
// Handle the enhanced strategies response structure
const strategies = recentStrategies?.data?.strategies || recentStrategies?.strategies || [];
if (strategies && strategies.length > 0) {
// Find the most recent AI-generated strategy
const aiGeneratedStrategy = strategies.find(
(strategy: any) => strategy.comprehensive_ai_analysis
);
if (aiGeneratedStrategy && aiGeneratedStrategy.comprehensive_ai_analysis) {
console.log('✅ Found AI-generated strategy in recent strategies:', aiGeneratedStrategy);
// Transform the data to match frontend expectations
const transformedStrategy = transformPollingStrategyData(aiGeneratedStrategy.comprehensive_ai_analysis);
console.log('🔄 Transformed recent strategy data:', transformedStrategy);
setStrategyData(transformedStrategy);
setLoading(false);
return;
}
}
} catch (strategyError) {
console.log('No recent strategies found, checking for generation tasks...', strategyError);
}
// If no strategy data is available, show appropriate message
console.log('❌ No comprehensive strategy data found');
setStrategyData(null);
setError('No comprehensive strategy data available. Please generate a strategy first.');
setLoading(false);
} catch (err: any) {
console.error('Error loading strategy data:', err);
setError(err.message || 'Failed to load strategy data');
setStrategyData(null);
setLoading(false);
}
};
// Load data on mount and when component becomes visible
useEffect(() => {
// Always refresh data when component mounts to ensure we get the latest strategy
loadStrategyData(true);
// Also set up a listener for when the tab becomes visible (for better UX)
const handleVisibilityChange = () => {
if (!document.hidden) {
// Tab became visible, refresh data
loadStrategyData(true);
}
};
document.addEventListener('visibilitychange', handleVisibilityChange);
return () => {
document.removeEventListener('visibilitychange', handleVisibilityChange);
};
}, []);
return {
strategyData,
loading,
error,
loadStrategyData
};
};

View File

@@ -0,0 +1,22 @@
// Main Strategy Intelligence Tab
export { default as StrategyIntelligenceTab } from '../StrategyIntelligenceTab';
// Components
export { default as StrategyHeader } from './components/StrategyHeader';
export { default as StrategicInsightsCard } from './components/StrategicInsightsCard';
export { default as CompetitiveAnalysisCard } from './components/CompetitiveAnalysisCard';
export { default as PerformancePredictionsCard } from './components/PerformancePredictionsCard';
export { default as ImplementationRoadmapCard } from './components/ImplementationRoadmapCard';
export { default as RiskAssessmentCard } from './components/RiskAssessmentCard';
export { default as StrategyActions } from './components/StrategyActions';
export { default as ConfirmationDialog } from './components/ConfirmationDialog';
// Hooks
export { useStrategyData } from './hooks/useStrategyData';
export { useStrategyActions } from './hooks/useStrategyActions';
// Types
export * from './types/strategy.types';
// Utils
export * from './utils/strategyTransformers';

View File

@@ -0,0 +1,377 @@
import { Theme } from '@mui/material/styles';
// Color palette for analysis components
export const ANALYSIS_COLORS = {
primary: '#667eea',
secondary: '#764ba2',
accent: '#f093fb',
success: '#4caf50',
warning: '#ff9800',
error: '#f44336',
info: '#2196f3',
background: {
gradient: 'linear-gradient(135deg, #0f0f23 0%, #1a1a2e 25%, #16213e 50%, #0f3460 75%, #533483 100%)',
dark: 'rgba(0, 0, 0, 0.3)',
glass: 'rgba(255, 255, 255, 0.05)'
},
text: {
primary: 'white',
secondary: '#e0e0e0',
muted: '#b0b0b0'
},
border: {
primary: 'rgba(102, 126, 234, 0.3)',
secondary: 'rgba(255, 255, 255, 0.1)'
}
};
// Priority colors for chips
export const PRIORITY_COLORS = {
high: '#f44336',
medium: '#ff9800',
low: '#4caf50',
default: '#667eea'
};
// Impact colors for chips
export const IMPACT_COLORS = {
high: '#f44336',
medium: '#ff9800',
low: '#4caf50',
default: '#667eea'
};
// Main card container styles
export const getAnalysisCardStyles = () => ({
card: {
height: '100%',
borderRadius: 3,
background: ANALYSIS_COLORS.background.gradient,
color: ANALYSIS_COLORS.text.primary,
boxShadow: '0 20px 60px rgba(0, 0, 0, 0.5), 0 0 40px rgba(102, 126, 234, 0.3)',
border: `1px solid ${ANALYSIS_COLORS.border.primary}`,
position: 'relative' as const,
overflow: 'hidden',
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: 'radial-gradient(circle at 20% 80%, rgba(120, 119, 198, 0.3) 0%, transparent 50%), radial-gradient(circle at 80% 20%, rgba(255, 119, 198, 0.3) 0%, transparent 50%)',
pointerEvents: 'none'
},
'&:hover': {
boxShadow: '0 25px 70px rgba(102, 126, 234, 0.4)',
transform: 'translateY(-4px)'
},
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)'
},
cardContent: {
p: 2.5,
position: 'relative' as const,
zIndex: 1
}
});
// Header section styles
export const getHeaderStyles = () => ({
headerContainer: {
display: 'flex',
alignItems: 'center',
mb: 2.5
},
iconContainer: {
p: 1,
borderRadius: 2,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
mr: 1.5,
boxShadow: '0 4px 12px rgba(102, 126, 234, 0.3)'
},
titleContainer: {
flex: 1
},
title: {
fontWeight: 700,
background: 'linear-gradient(45deg, #667eea, #764ba2, #f093fb)',
backgroundSize: '200% 200%',
WebkitBackgroundClip: 'text',
WebkitTextFillColor: 'transparent',
animation: 'gradient 3s ease infinite',
'@keyframes gradient': {
'0%': { backgroundPosition: '0% 50%' },
'50%': { backgroundPosition: '100% 50%' },
'100%': { backgroundPosition: '0% 50%' }
}
}
});
// Market analysis header styles
export const getMarketAnalysisHeaderStyles = () => ({
marketAnalysisContainer: {
display: 'flex',
alignItems: 'center',
gap: 2,
p: 1.5,
background: ANALYSIS_COLORS.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
backdropFilter: 'blur(10px)'
},
circularProgress: {
color: ANALYSIS_COLORS.primary,
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
filter: 'drop-shadow(0 2px 4px rgba(102, 126, 234, 0.3))'
}
},
progressText: {
position: 'absolute',
top: '50%',
left: '50%',
transform: 'translate(-50%, -50%)',
fontWeight: 700,
color: ANALYSIS_COLORS.primary,
fontSize: '0.7rem'
},
positionText: {
fontWeight: 600,
color: ANALYSIS_COLORS.text.primary,
display: 'block',
fontSize: '0.7rem'
},
positionLabel: {
color: ANALYSIS_COLORS.text.secondary,
fontSize: '0.6rem'
},
marketChip: {
background: 'rgba(0, 0, 0, 0.4)',
color: ANALYSIS_COLORS.primary,
fontWeight: 600,
fontSize: '0.6rem',
height: 20,
border: `1px solid rgba(102, 126, 234, 0.4)`,
'& .MuiChip-label': {
px: 1
}
}
});
// Section container styles
export const getSectionStyles = () => ({
sectionContainer: {
mb: 2.5,
p: 2,
background: ANALYSIS_COLORS.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
backdropFilter: 'blur(10px)'
},
sectionTitle: {
fontWeight: 600,
color: ANALYSIS_COLORS.text.primary,
fontSize: '0.85rem'
}
});
// Accordion styles
export const getAccordionStyles = () => ({
accordion: {
mb: 1,
'&:before': { display: 'none' },
background: ANALYSIS_COLORS.background.dark,
borderRadius: 2,
border: `1px solid ${ANALYSIS_COLORS.border.secondary}`,
backdropFilter: 'blur(10px)',
'&.Mui-expanded': {
margin: '8px 0'
}
},
accordionSummary: {
'& .MuiAccordionSummary-content': {
margin: '8px 0'
}
},
accordionTitle: {
fontWeight: 600,
color: ANALYSIS_COLORS.text.primary,
fontSize: '0.8rem'
},
accordionSubtitle: {
color: ANALYSIS_COLORS.text.secondary
},
expandIcon: {
color: ANALYSIS_COLORS.primary
}
});
// Enhanced chip styles
export const getEnhancedChipStyles = (color: string) => ({
chip: {
background: `linear-gradient(135deg, ${color}70 0%, ${color}60 100%)`,
color: color,
fontWeight: 700,
fontSize: '0.75rem',
height: 30,
border: `3px solid ${color}80`,
boxShadow: `0 8px 20px ${color}50, inset 0 3px 0 rgba(255, 255, 255, 0.2), inset 0 -2px 0 rgba(0, 0, 0, 0.4), 0 0 15px ${color}30`,
backdropFilter: 'blur(20px)',
transition: 'all 0.4s cubic-bezier(0.4, 0, 0.2, 1)',
textShadow: '0 2px 4px rgba(0, 0, 0, 0.5)',
position: 'relative' as const,
'&::before': {
content: '""',
position: 'absolute',
top: 0,
left: 0,
right: 0,
bottom: 0,
background: `linear-gradient(135deg, rgba(255, 255, 255, 0.1) 0%, transparent 50%, rgba(0, 0, 0, 0.1) 100%)`,
borderRadius: 'inherit',
pointerEvents: 'none'
},
'&:hover': {
transform: 'translateY(-4px) scale(1.05)',
boxShadow: `0 12px 30px ${color}60, inset 0 3px 0 rgba(255, 255, 255, 0.3), inset 0 -2px 0 rgba(0, 0, 0, 0.5), 0 0 25px ${color}40`,
background: `linear-gradient(135deg, ${color}80 0%, ${color}70 100%)`
},
'& .MuiChip-label': {
px: 2.5,
py: 1,
fontWeight: 700,
letterSpacing: '0.6px',
position: 'relative' as const,
zIndex: 1
},
'& .MuiChip-icon': {
color: color,
fontSize: '1.1rem',
filter: 'drop-shadow(0 2px 4px rgba(0, 0, 0, 0.5))',
position: 'relative' as const,
zIndex: 1
}
}
});
// List item styles
export const getListItemStyles = () => ({
listItem: {
px: 0,
py: 1
},
listItemIcon: {
minWidth: 32
},
bulletPoint: {
width: 8,
height: 8,
borderRadius: '50%',
background: ANALYSIS_COLORS.primary,
opacity: 0.7
},
insightText: {
fontWeight: 500,
mb: 1,
color: ANALYSIS_COLORS.text.primary,
lineHeight: 1.5
},
reasoningText: {
color: ANALYSIS_COLORS.text.muted,
fontStyle: 'italic',
lineHeight: 1.4
}
});
// Fallback/empty state styles
export const getFallbackStyles = () => ({
fallbackContainer: {
textAlign: 'center',
py: 4
},
fallbackText: {
mb: 2,
color: ANALYSIS_COLORS.text.secondary
},
fallbackCaption: {
color: ANALYSIS_COLORS.text.muted
}
});
// Animation styles
export const getAnimationStyles = () => ({
iconAnimation: {
animate: { rotate: [0, 10, -10, 0] },
transition: { duration: 2, repeat: Infinity, ease: "easeInOut" as const }
},
cardAnimation: {
initial: { opacity: 0, y: 20 },
animate: { opacity: 1, y: 0 },
transition: { duration: 0.5 },
whileHover: { y: -4 }
}
});
// Utility functions
export const getPriorityColor = (priority: string): string => {
switch (priority.toLowerCase()) {
case 'high':
return PRIORITY_COLORS.high;
case 'medium':
return PRIORITY_COLORS.medium;
case 'low':
return PRIORITY_COLORS.low;
default:
return PRIORITY_COLORS.default;
}
};
export const getImpactColor = (impact: string): string => {
switch (impact.toLowerCase()) {
case 'high':
return IMPACT_COLORS.high;
case 'medium':
return IMPACT_COLORS.medium;
case 'low':
return IMPACT_COLORS.low;
default:
return IMPACT_COLORS.default;
}
};
// Icon mapping for different insight types
export const getInsightIcon = (type: string) => {
switch (type.toLowerCase()) {
case 'market positioning':
return { icon: 'Business', color: ANALYSIS_COLORS.primary };
case 'content opportunity':
return { icon: 'Lightbulb', color: ANALYSIS_COLORS.success };
case 'growth potential':
return { icon: 'TrendingUp', color: ANALYSIS_COLORS.info };
case 'competitive advantage':
return { icon: 'Star', color: ANALYSIS_COLORS.warning };
case 'strategic recommendation':
return { icon: 'Psychology', color: ANALYSIS_COLORS.error };
default:
return { icon: 'Lightbulb', color: ANALYSIS_COLORS.primary };
}
};
// Complete style object for easy import
export const ANALYSIS_CARD_STYLES = {
colors: ANALYSIS_COLORS,
priorityColors: PRIORITY_COLORS,
impactColors: IMPACT_COLORS,
getAnalysisCardStyles,
getHeaderStyles,
getMarketAnalysisHeaderStyles,
getSectionStyles,
getAccordionStyles,
getEnhancedChipStyles,
getListItemStyles,
getFallbackStyles,
getAnimationStyles,
getPriorityColor,
getImpactColor,
getInsightIcon
};

View File

@@ -0,0 +1 @@
export * from './analysisCardStyles';

View File

@@ -0,0 +1,267 @@
export interface StrategyMetadata {
generated_at?: string;
generation_timestamp?: string;
user_id: number;
strategy_name: string;
generation_version?: string;
ai_model?: string;
personalization_level?: string;
ai_generated: boolean;
comprehensive: boolean;
content_calendar_ready: boolean;
}
export interface StrategicInsights {
market_positioning?: {
positioning_strength: number;
current_position: string;
swot_analysis?: {
strengths: string[];
opportunities: string[];
};
};
content_opportunities?: string[];
growth_potential?: {
market_size: string;
growth_rate: string;
key_drivers?: string[];
competitive_advantages?: string[];
};
swot_summary?: {
overall_score: number;
primary_strengths: string[];
key_opportunities: string[];
};
// Backend insights array structure
insights?: Array<{
type: string;
insight: string;
confidence_level: string;
estimated_impact: string;
implementation_time: string;
priority: string;
reasoning: string;
}>;
}
export interface CompetitiveAnalysis {
competitors?: Array<{
name: string;
market_position: string;
strengths: string[];
weaknesses: string[];
content_strategy?: string; // Added to match backend
competitive_response?: string;
}>;
market_gaps?: string[];
opportunities?: string[]; // Added to match backend
recommendations?: string[]; // Added to match backend
competitive_advantages?: {
primary: string[];
sustainable: string[];
development_areas: string[];
};
swot_competitive_insights?: {
leverage_strengths: string[];
address_weaknesses: string[];
capitalize_opportunities: string[];
mitigate_threats: string[];
};
}
export interface PerformancePredictions {
estimated_roi?: string;
key_metrics?: {
engagement_rate?: string;
conversion_rate?: string;
reach_growth?: string;
brand_awareness?: string;
market_share?: string;
};
timeline_projections?: {
[key: string]: string;
};
success_factors?: {
primary: string[];
secondary: string[];
risk_mitigation: string[];
};
swot_based_predictions?: {
strength_impact: string;
opportunity_impact: string;
weakness_mitigation: string;
threat_management: string;
};
// Nested prediction objects from backend
traffic_predictions?: {
monthly_traffic?: string;
growth_rate?: string;
traffic_growth?: string;
month_1?: string;
};
engagement_predictions?: {
engagement_rate?: string;
brand_awareness?: string;
time_on_page?: string;
month_3?: string;
success_factors?: string[];
};
conversion_predictions?: {
conversion_rate?: string;
lead_generation?: string;
month_6?: string;
risk_mitigation?: string[];
};
roi_predictions?: {
estimated_roi?: string;
market_share?: string;
cost_benefit?: string;
success_factors?: string[];
};
}
export interface ImplementationRoadmap {
total_duration?: string;
phases?: Array<{
phase: string;
duration: string;
tasks: string[]; // Changed from activities to match backend
milestones: string[]; // Changed from deliverables to match backend
resources: string[]; // Added to match backend
swot_focus?: string;
}>;
resource_allocation?: {
team_members?: string[]; // Changed from team_requirements to match backend
team_requirements?: string[]; // Added to match backend data
budget_allocation?: {
total_budget?: string;
content_creation?: string;
technology_tools?: string;
marketing_promotion?: string;
external_resources?: string;
};
swot_priorities?: {
high_priority: string[];
medium_priority: string[];
low_priority: string[];
};
};
success_metrics?: string[]; // Added to match backend
timeline?: {
start_date?: string;
end_date?: string;
key_milestones?: string[];
}; // Added to match backend
swot_integration?: {
strength_leverage: string[];
weakness_improvement: string[];
opportunity_capitalization: string[];
threat_mitigation: string[];
};
}
export interface RiskAssessment {
overall_risk_level?: string;
risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
contingency?: string;
}>;
risk_categories?: {
market_risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
}>;
operational_risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
}>;
competitive_risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
}>;
technical_risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
}>;
financial_risks?: Array<{
risk: string;
probability: string;
impact: string;
mitigation?: string;
}>;
};
monitoring_framework?: {
escalation_procedures?: string[];
key_indicators?: string[];
monitoring_frequency?: string;
review_schedule?: string;
};
swot_risk_mapping?: {
strength_risks: string;
weakness_risks: string;
opportunity_risks: string;
threat_risks: string;
};
risk_mitigation_strategies?: {
based_on_strengths: string;
based_on_opportunities: string;
based_on_weaknesses: string;
based_on_threats: string;
};
mitigation_strategies?: string[];
}
export interface StrategySummary {
estimated_roi: string;
implementation_timeline: string;
risk_level: string;
success_probability: string;
next_step: string;
swot_highlights?: {
key_strengths: string[];
key_opportunities: string[];
primary_risks: string[];
};
}
export interface StrategyData {
strategy_metadata?: StrategyMetadata;
metadata?: StrategyMetadata;
base_strategy?: any;
strategic_insights?: StrategicInsights;
competitive_analysis?: CompetitiveAnalysis;
performance_predictions?: PerformancePredictions;
implementation_roadmap?: ImplementationRoadmap;
risk_assessment?: RiskAssessment;
summary?: StrategySummary;
}
export interface StrategyActionsProps {
strategyData: StrategyData | null;
strategyConfirmed: boolean;
onConfirmStrategy: () => void;
onGenerateContentCalendar: () => void;
onRefreshData: () => void;
}
export interface StrategyCardProps {
strategyData: StrategyData | null;
loading?: boolean;
}
export interface ConfirmationDialogProps {
open: boolean;
onClose: () => void;
onConfirm: () => void;
}

View File

@@ -0,0 +1,473 @@
import { StrategyData } from '../types/strategy.types';
// Helper function to get user ID from context or store
export const getUserId = (): number => {
// TODO: Replace with actual user context/store
// For now, return default user ID
return 1;
};
/**
* Transform polling system strategy data to frontend format
*/
export const transformPollingStrategyData = (strategyData: any): StrategyData => {
return {
...strategyData,
strategy_metadata: strategyData.metadata || strategyData.strategy_metadata,
// Add summary if not present
summary: strategyData.summary || {
estimated_roi: strategyData.performance_predictions?.estimated_roi || "15-25%",
implementation_timeline: strategyData.implementation_roadmap?.total_duration || "12 months",
risk_level: strategyData.risk_assessment?.overall_risk_level || "Medium",
success_probability: strategyData.performance_predictions?.success_probability || "85%",
next_step: "Review strategy and generate content calendar"
}
};
};
/**
* Transform full 5-component structure from database
*/
export const transformFullStructureData = (latestStrategy: any): StrategyData => {
const comprehensiveData = latestStrategy.comprehensive_ai_analysis;
return {
// Map metadata
strategy_metadata: comprehensiveData.metadata || comprehensiveData.strategy_metadata,
metadata: comprehensiveData.metadata || comprehensiveData.strategy_metadata,
// Transform Strategic Insights
strategic_insights: comprehensiveData.strategic_insights ? {
market_positioning: {
positioning_strength: 75, // Default value
current_position: "Emerging",
swot_analysis: {
strengths: [],
opportunities: []
}
},
content_opportunities: comprehensiveData.strategic_insights.insights?.filter((insight: any) =>
insight.type === 'Content Opportunity'
).map((insight: any) => insight.insight) || [],
growth_potential: {
market_size: "Growing",
growth_rate: "High",
key_drivers: comprehensiveData.strategic_insights.insights?.filter((insight: any) =>
insight.type === 'Growth Potential'
).map((insight: any) => insight.insight) || [],
competitive_advantages: []
},
swot_summary: {
overall_score: 75,
primary_strengths: comprehensiveData.strategic_insights.insights?.slice(0, 2) || [],
key_opportunities: comprehensiveData.strategic_insights.insights?.slice(2, 4) || []
},
// Map the insights array directly
insights: comprehensiveData.strategic_insights.insights || []
} : undefined,
// Transform Competitive Analysis
competitive_analysis: comprehensiveData.competitive_analysis ? {
competitors: comprehensiveData.competitive_analysis.competitors || [],
market_gaps: comprehensiveData.competitive_analysis.market_gaps || [],
opportunities: comprehensiveData.competitive_analysis.opportunities || [],
recommendations: comprehensiveData.competitive_analysis.recommendations || [],
competitive_advantages: {
primary: comprehensiveData.competitive_analysis.recommendations?.slice(0, 3) || [],
sustainable: comprehensiveData.competitive_analysis.recommendations?.slice(3, 5) || [],
development_areas: comprehensiveData.competitive_analysis.opportunities || []
},
swot_competitive_insights: {
leverage_strengths: comprehensiveData.competitive_analysis.recommendations?.slice(0, 2) || [],
address_weaknesses: comprehensiveData.competitive_analysis.recommendations?.slice(2, 4) || [],
capitalize_opportunities: comprehensiveData.competitive_analysis.opportunities?.slice(0, 2) || [],
mitigate_threats: comprehensiveData.competitive_analysis.recommendations?.slice(4, 6) || []
}
} : undefined,
// Transform Performance Predictions
performance_predictions: comprehensiveData.performance_predictions ? {
estimated_roi: comprehensiveData.performance_predictions.roi_predictions?.estimated_roi || "15-25%",
key_metrics: {
engagement_rate: comprehensiveData.performance_predictions.engagement_predictions?.engagement_rate || "8-12%",
conversion_rate: comprehensiveData.performance_predictions.conversion_predictions?.conversion_rate || "3-5%",
reach_growth: comprehensiveData.performance_predictions.traffic_predictions?.traffic_growth || "40-60%",
brand_awareness: comprehensiveData.performance_predictions.engagement_predictions?.brand_awareness || "25-35%",
market_share: comprehensiveData.performance_predictions.roi_predictions?.market_share || "5-8%"
},
timeline_projections: {
"month_1": comprehensiveData.performance_predictions.traffic_predictions?.month_1 || "Initial setup and content creation",
"month_3": comprehensiveData.performance_predictions.engagement_predictions?.month_3 || "Content optimization and audience growth",
"month_6": comprehensiveData.performance_predictions.conversion_predictions?.month_6 || "Full strategy implementation and scaling"
},
success_factors: {
primary: comprehensiveData.performance_predictions.roi_predictions?.success_factors?.slice(0, 3) || [],
secondary: comprehensiveData.performance_predictions.engagement_predictions?.success_factors?.slice(0, 2) || [],
risk_mitigation: comprehensiveData.performance_predictions.conversion_predictions?.risk_mitigation?.slice(0, 2) || []
},
swot_based_predictions: {
strength_impact: "High positive impact from identified strengths",
opportunity_impact: "Significant growth potential from market opportunities",
weakness_mitigation: "Addressing weaknesses through strategic content planning",
threat_management: "Proactive threat management through diversified approach"
}
} : undefined,
// Transform Implementation Roadmap
implementation_roadmap: comprehensiveData.implementation_roadmap ? {
total_duration: comprehensiveData.implementation_roadmap.total_duration || "6 months",
phases: comprehensiveData.implementation_roadmap.phases || [],
success_metrics: comprehensiveData.implementation_roadmap.success_metrics || [],
timeline: comprehensiveData.implementation_roadmap.timeline || {
start_date: "2024-09-01",
end_date: "2025-02-28",
key_milestones: []
},
resource_allocation: {
team_members: comprehensiveData.implementation_roadmap.resource_allocation?.team_members ||
comprehensiveData.implementation_roadmap.resource_allocation?.team_requirements ||
["Content Strategist", "SEO Specialist", "Content Writer", "Editor"],
team_requirements: comprehensiveData.implementation_roadmap.resource_allocation?.team_requirements ||
comprehensiveData.implementation_roadmap.resource_allocation?.team_members ||
["Content Strategist", "SEO Specialist", "Content Writer", "Editor"],
budget_allocation: comprehensiveData.implementation_roadmap.resource_allocation?.budget_allocation || {
total_budget: "$60,000",
content_creation: "$30,000",
technology_tools: "$5,000",
marketing_promotion: "$20,000",
external_resources: "$5,000"
},
swot_priorities: {
high_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(0, 3) || [],
medium_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(3, 6) || [],
low_priority: comprehensiveData.implementation_roadmap.success_metrics?.slice(6, 9) || []
}
}
} : undefined,
// Transform Risk Assessment
risk_assessment: comprehensiveData.risk_assessment ? {
overall_risk_level: comprehensiveData.risk_assessment.overall_risk_level || "Medium",
risks: comprehensiveData.risk_assessment.risks || [],
risk_categories: comprehensiveData.risk_assessment.risk_categories || {},
monitoring_framework: comprehensiveData.risk_assessment.monitoring_framework || {
escalation_procedures: [],
key_indicators: [],
monitoring_frequency: "Weekly for key performance metrics, Monthly for strategic content review, Quarterly for comprehensive analysis",
review_schedule: "Monthly performance review meetings to discuss analytics and tactical adjustments. Quarterly strategic comprehensive review"
},
swot_risk_mapping: {
strength_risks: "Leverage strengths to mitigate risks",
weakness_risks: "Address weaknesses through strategic planning",
opportunity_risks: "Capitalize on opportunities while managing risks",
threat_risks: "Proactive threat management and contingency planning"
},
risk_mitigation_strategies: {
based_on_strengths: comprehensiveData.risk_assessment.mitigation_strategies?.[0] || "Leverage identified strengths",
based_on_opportunities: comprehensiveData.risk_assessment.mitigation_strategies?.[1] || "Capitalize on market opportunities",
based_on_weaknesses: comprehensiveData.risk_assessment.mitigation_strategies?.[2] || "Address identified weaknesses",
based_on_threats: comprehensiveData.risk_assessment.mitigation_strategies?.[3] || "Proactive threat management"
},
mitigation_strategies: comprehensiveData.risk_assessment.mitigation_strategies || []
} : undefined,
// Add summary
summary: comprehensiveData.summary || {
estimated_roi: comprehensiveData.performance_predictions?.roi_predictions?.estimated_roi || "15-25%",
implementation_timeline: comprehensiveData.implementation_roadmap?.total_duration || "6 months",
risk_level: comprehensiveData.risk_assessment?.overall_risk_level || "Medium",
success_probability: "85%",
next_step: "Review strategy and generate content calendar"
}
};
};
/**
* Transform SWOT analysis into comprehensive 5-component structure
*/
export const transformSwotToComprehensiveStructure = (latestStrategy: any): StrategyData => {
const swotData = latestStrategy.comprehensive_ai_analysis;
return {
strategy_metadata: {
user_id: latestStrategy.user_id,
strategy_name: latestStrategy.name,
ai_generated: true,
comprehensive: true,
content_calendar_ready: false,
generation_timestamp: latestStrategy.created_at
},
// Enhanced Strategic Insights with SWOT data
strategic_insights: {
market_positioning: {
positioning_strength: swotData.overall_score || 75,
current_position: "Emerging",
swot_analysis: {
strengths: swotData.strengths || [],
opportunities: swotData.opportunities || []
}
},
content_opportunities: [
...(swotData.opportunities || []),
"Leverage identified market gaps",
"Focus on unique value propositions",
"Build thought leadership content"
],
growth_potential: {
market_size: "Growing",
growth_rate: "High",
key_drivers: swotData.opportunities || [],
competitive_advantages: swotData.strengths || []
},
swot_summary: {
overall_score: swotData.overall_score || 75,
primary_strengths: (swotData.strengths || []).slice(0, 3),
key_opportunities: (swotData.opportunities || []).slice(0, 3)
}
},
// Enhanced Competitive Analysis with SWOT data
competitive_analysis: {
competitors: [
{
name: "Direct Competitors",
market_position: "Established",
strengths: swotData.strengths || [],
weaknesses: swotData.weaknesses || [],
competitive_response: "Focus on differentiation"
},
{
name: "Emerging Competitors",
market_position: "Growing",
strengths: [],
weaknesses: swotData.weaknesses || [],
competitive_response: "Establish market leadership"
}
],
market_gaps: [
...(swotData.opportunities || []),
"Content personalization opportunities",
"Niche market segments",
"Innovation in content delivery"
],
competitive_advantages: {
primary: swotData.strengths || [],
sustainable: swotData.opportunities || [],
development_areas: swotData.weaknesses || []
},
swot_competitive_insights: {
leverage_strengths: swotData.strengths || [],
address_weaknesses: swotData.weaknesses || [],
capitalize_opportunities: swotData.opportunities || [],
mitigate_threats: swotData.threats || []
}
},
// Enhanced Performance Predictions with SWOT context
performance_predictions: {
estimated_roi: "20-30%",
key_metrics: {
engagement_rate: "5-8%",
conversion_rate: "2-4%",
reach_growth: "40-60%",
brand_awareness: "25-35%",
market_share: "3-5%"
},
timeline_projections: {
"3_months": "Initial traction and brand awareness leveraging identified strengths",
"6_months": "Established presence and engagement addressing market opportunities",
"12_months": "Market leadership and growth capitalizing on competitive advantages"
},
success_factors: {
primary: swotData.strengths || [],
secondary: swotData.opportunities || [],
risk_mitigation: swotData.threats || []
},
swot_based_predictions: {
strength_impact: "High performance in areas of identified strengths",
opportunity_impact: "Growth potential through market opportunities",
weakness_mitigation: "Improved performance by addressing weaknesses",
threat_management: "Risk-adjusted projections considering market threats"
}
},
// Enhanced Implementation Roadmap with SWOT considerations
implementation_roadmap: {
total_duration: "12 months",
phases: [
{
phase: "Foundation (Months 1-3)",
duration: "3 months",
tasks: [
"Brand positioning leveraging identified strengths",
"Content strategy development addressing market opportunities",
"Weakness assessment and improvement planning"
],
milestones: ["Brand guidelines", "Content calendar", "SWOT action plan"],
resources: ["Content Strategist", "Brand Manager", "SWOT Analyst"],
swot_focus: "Strengths and Opportunities"
},
{
phase: "Growth (Months 4-8)",
duration: "5 months",
tasks: [
"Content execution based on competitive advantages",
"Community building addressing market gaps",
"Threat mitigation strategies implementation"
],
milestones: ["Content library", "Engaged audience", "Risk management framework"],
resources: ["Content Writer", "Community Manager", "Risk Analyst"],
swot_focus: "Opportunities and Threats"
},
{
phase: "Scale (Months 9-12)",
duration: "4 months",
tasks: [
"Market expansion capitalizing on strengths",
"Performance optimization addressing weaknesses",
"Sustainable competitive advantage development"
],
milestones: ["Market leadership", "Optimized strategy", "Long-term competitive position"],
resources: ["Growth Manager", "Performance Analyst", "Strategy Consultant"],
swot_focus: "Strengths and Weaknesses"
}
],
resource_allocation: {
team_members: ["Content Strategist", "SEO Specialist", "Content Writer", "Editor", "Marketing Manager"],
budget_allocation: {
total_budget: "$60,000",
content_creation: "$30,000",
technology_tools: "$5,000",
marketing_promotion: "$20,000",
external_resources: "$5,000"
},
swot_priorities: {
high_priority: swotData.opportunities || [],
medium_priority: swotData.strengths || [],
low_priority: swotData.weaknesses || []
}
},
swot_integration: {
strength_leverage: swotData.strengths || [],
weakness_improvement: swotData.weaknesses || [],
opportunity_capitalization: swotData.opportunities || [],
threat_mitigation: swotData.threats || []
}
},
// Enhanced Risk Assessment with SWOT threats
risk_assessment: {
overall_risk_level: "Medium",
risks: [
...(swotData.threats?.map((threat: string) => ({
risk: threat,
probability: "Medium",
impact: "High",
mitigation: "Strategic planning and monitoring"
})) || []),
{
risk: "Market saturation",
probability: "Medium",
impact: "Medium",
mitigation: "Innovation and differentiation"
}
],
risk_categories: {
market_risks: [
...(swotData.threats?.map((threat: string) => ({
risk: threat,
probability: "Medium",
impact: "High",
mitigation: "Strategic planning and monitoring"
})) || []),
{
risk: "Market saturation",
probability: "Medium",
impact: "Medium",
mitigation: "Innovation and differentiation"
}
],
operational_risks: [
{
risk: "Resource constraints",
probability: "Medium",
impact: "Medium",
mitigation: "Efficient resource allocation"
},
{
risk: "Weakness areas",
probability: "High",
impact: "Medium",
mitigation: "Targeted improvement programs"
}
],
competitive_risks: [
{
risk: "Market competition",
probability: "High",
impact: "Medium",
mitigation: "Leverage competitive advantages"
},
{
risk: "Strength erosion",
probability: "Medium",
impact: "High",
mitigation: "Continuous improvement and innovation"
}
]
},
swot_risk_mapping: {
strength_risks: "Risk of over-reliance on current strengths",
weakness_risks: "Risk of weakness exploitation by competitors",
opportunity_risks: "Risk of missing market opportunities",
threat_risks: "Risk of threat materialization"
},
risk_mitigation_strategies: {
based_on_strengths: "Leverage strengths to mitigate threats",
based_on_opportunities: "Use opportunities to address weaknesses",
based_on_weaknesses: "Develop improvement plans for weak areas",
based_on_threats: "Create contingency plans for identified threats"
},
mitigation_strategies: swotData.mitigation_strategies || []
},
// Enhanced summary with SWOT context
summary: {
estimated_roi: "20-30%",
implementation_timeline: "12 months",
risk_level: "Medium",
success_probability: `${swotData.overall_score || 75}%`,
next_step: "Review strategy and generate content calendar",
swot_highlights: {
key_strengths: (swotData.strengths || []).slice(0, 2),
key_opportunities: (swotData.opportunities || []).slice(0, 2),
primary_risks: (swotData.threats || []).slice(0, 2)
}
}
};
};
/**
* Check if strategy data has full 5-component structure
*/
export const hasFullStructure = (comprehensiveAnalysis: any): boolean => {
return !!(comprehensiveAnalysis.strategic_insights ||
comprehensiveAnalysis.competitive_analysis ||
comprehensiveAnalysis.performance_predictions);
};
/**
* Get strategy name from metadata
*/
export const getStrategyName = (strategyData: StrategyData | null): string => {
return strategyData?.strategy_metadata?.strategy_name ||
strategyData?.metadata?.strategy_name ||
'AI-Generated Strategy';
};
/**
* Get strategy generation date
*/
export const getStrategyGenerationDate = (strategyData: StrategyData | null): string => {
const timestamp = strategyData?.strategy_metadata?.generated_at ||
strategyData?.strategy_metadata?.generation_timestamp ||
strategyData?.metadata?.generated_at ||
strategyData?.metadata?.generation_timestamp || '';
return new Date(timestamp).toLocaleDateString();
};

View File

@@ -0,0 +1,103 @@
import React from 'react';
import { Box, CircularProgress, Alert, Typography, Grid } from '@mui/material';
import { useStrategyData } from './StrategyIntelligence/hooks/useStrategyData';
import { useStrategyActions } from './StrategyIntelligence/hooks/useStrategyActions';
import StrategyHeader from './StrategyIntelligence/components/StrategyHeader';
import StrategicInsightsCard from './StrategyIntelligence/components/StrategicInsightsCard';
import CompetitiveAnalysisCard from './StrategyIntelligence/components/CompetitiveAnalysisCard';
import PerformancePredictionsCard from './StrategyIntelligence/components/PerformancePredictionsCard';
import ImplementationRoadmapCard from './StrategyIntelligence/components/ImplementationRoadmapCard';
import RiskAssessmentCard from './StrategyIntelligence/components/RiskAssessmentCard';
import StrategyActions from './StrategyIntelligence/components/StrategyActions';
import ConfirmationDialog from './StrategyIntelligence/components/ConfirmationDialog';
const StrategyIntelligenceTab: React.FC = () => {
const { strategyData, loading, error, loadStrategyData } = useStrategyData();
const {
strategyConfirmed,
showConfirmDialog,
setShowConfirmDialog,
handleConfirmStrategy,
confirmStrategy,
handleGenerateContentCalendar
} = useStrategyActions();
const handleConfirmStrategyClick = () => {
handleConfirmStrategy();
};
const handleConfirmStrategyAction = async () => {
await confirmStrategy(strategyData);
};
const handleGenerateContentCalendarAction = async () => {
try {
await handleGenerateContentCalendar(strategyData);
} catch (error) {
console.error('Error generating content calendar:', error);
}
};
if (loading) {
return (
<Box sx={{ display: 'flex', justifyContent: 'center', alignItems: 'center', minHeight: 400 }}>
<CircularProgress />
</Box>
);
}
if (error) {
return (
<Alert severity="error" sx={{ m: 2 }}>
{error}
</Alert>
);
}
if (!strategyData) {
return (
<Box sx={{ textAlign: 'center', p: 4 }}>
<Typography variant="h6" color="text.secondary" gutterBottom>
No Strategy Data Available
</Typography>
<Typography variant="body2" color="text.secondary">
Generate a comprehensive strategy first to view strategic intelligence.
</Typography>
</Box>
);
}
return (
<Box sx={{ p: 3 }}>
{/* Header Section */}
<StrategyHeader strategyData={strategyData} strategyConfirmed={strategyConfirmed} />
{/* Strategy Components Grid */}
<Grid container spacing={2}>
<StrategicInsightsCard strategyData={strategyData} />
<CompetitiveAnalysisCard strategyData={strategyData} />
<PerformancePredictionsCard strategyData={strategyData} />
<ImplementationRoadmapCard strategyData={strategyData} />
<RiskAssessmentCard strategyData={strategyData} />
</Grid>
{/* Action Buttons */}
<StrategyActions
strategyData={strategyData}
strategyConfirmed={strategyConfirmed}
onConfirmStrategy={handleConfirmStrategyClick}
onGenerateContentCalendar={handleGenerateContentCalendarAction}
onRefreshData={loadStrategyData}
/>
{/* Confirmation Dialog */}
<ConfirmationDialog
open={showConfirmDialog}
onClose={() => setShowConfirmDialog(false)}
onConfirm={handleConfirmStrategyAction}
/>
</Box>
);
};
export default StrategyIntelligenceTab;

View File

@@ -56,6 +56,7 @@ import {
import { useContentPlanningStore } from '../../../stores/contentPlanningStore'; import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi'; import { contentPlanningApi } from '../../../services/contentPlanningApi';
import ContentStrategyBuilder from '../components/ContentStrategyBuilder'; import ContentStrategyBuilder from '../components/ContentStrategyBuilder';
import StrategyIntelligenceTab from '../components/StrategyIntelligenceTab';
interface TabPanelProps { interface TabPanelProps {
children?: React.ReactNode; children?: React.ReactNode;
@@ -461,110 +462,7 @@ const ContentStrategyTab: React.FC = () => {
{/* Strategic Intelligence Tab */} {/* Strategic Intelligence Tab */}
<TabPanel value={tabValue} index={1}> <TabPanel value={tabValue} index={1}>
{dataLoading.strategicIntelligence ? ( <StrategyIntelligenceTab />
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : strategicIntelligence && strategicIntelligence.market_positioning ? (
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Market Positioning
</Typography>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<CircularProgress
variant="determinate"
value={strategicIntelligence.market_positioning.score || 0}
size={60}
color="primary"
/>
<Typography variant="h4" sx={{ ml: 2 }}>
{strategicIntelligence.market_positioning.score || 0}/100
</Typography>
</Box>
<Typography variant="subtitle2" gutterBottom>
Strengths:
</Typography>
<List dense>
{(strategicIntelligence.market_positioning.strengths || []).map((strength: string, index: number) => (
<ListItem key={index}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText primary={strength} />
</ListItem>
))}
</List>
</CardContent>
</Card>
</Grid>
<Grid item xs={12} md={6}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Competitive Advantages
</Typography>
{(strategicIntelligence.competitive_advantages || []).map((advantage: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{advantage.advantage}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={advantage.impact}
color={advantage.impact === 'High' ? 'success' : 'primary'}
size="small"
/>
<Chip
label={advantage.implementation}
variant="outlined"
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
<Grid item xs={12}>
<Card>
<CardContent>
<Typography variant="h6" gutterBottom>
Strategic Risks
</Typography>
{(strategicIntelligence.strategic_risks || []).map((risk: any, index: number) => (
<Box key={index} sx={{ mb: 2 }}>
<Typography variant="subtitle1">
{risk.risk}
</Typography>
<Box sx={{ display: 'flex', gap: 1, mt: 1 }}>
<Chip
label={`Probability: ${risk.probability}`}
color={risk.probability === 'High' ? 'error' : 'warning'}
size="small"
/>
<Chip
label={`Impact: ${risk.impact}`}
color={risk.impact === 'High' ? 'error' : 'warning'}
size="small"
/>
</Box>
</Box>
))}
</CardContent>
</Card>
</Grid>
</Grid>
) : (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', p: 3 }}>
No strategic intelligence data available
</Typography>
)}
</TabPanel> </TabPanel>
{/* Keyword Research Tab */} {/* Keyword Research Tab */}

View File

@@ -196,7 +196,7 @@ class ContentPlanningAPI {
async getStrategies(userId?: number) { async getStrategies(userId?: number) {
const params = userId ? { user_id: userId } : {}; const params = userId ? { user_id: userId } : {};
const response = await apiClient.get(`${this.baseURL}/strategies/`, { params }); const response = await apiClient.get(`${this.baseURL}/enhanced-strategies`, { params });
return response.data; return response.data;
} }
@@ -582,6 +582,105 @@ class ContentPlanningAPI {
return eventSource; return eventSource;
} }
// New polling-based strategy generation methods
async startStrategyGenerationPolling(userId: number = 1, strategyName?: string, config?: any): Promise<any> {
return this.handleRequest(async () => {
const payload = {
user_id: userId,
strategy_name: strategyName || 'Enhanced Content Strategy',
config: config || {}
};
console.log('🚀 Starting polling-based strategy generation:', payload);
const response = await apiClient.post(
`${this.baseURL}/content-strategy/ai-generation/generate-comprehensive-strategy-polling`,
payload
);
console.log('✅ Strategy generation started:', response.data);
return response.data.data || response.data;
});
}
async getStrategyGenerationStatus(taskId: string): Promise<any> {
return this.handleRequest(async () => {
const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/strategy-generation-status/${taskId}`);
return response.data.data || response.data;
});
}
// Get the latest generated strategy from polling system
async getLatestGeneratedStrategy(userId: number = 1): Promise<any> {
return this.handleRequest(async () => {
const response = await apiClient.get(`${this.baseURL}/content-strategy/ai-generation/latest-strategy`, {
params: { user_id: userId }
});
return response.data.data || response.data;
});
}
// Polling utility method
async pollStrategyGeneration(
taskId: string,
onProgress: (status: any) => void,
onComplete: (strategy: any) => void,
onError: (error: any) => void,
pollInterval: number = 10000, // 10 seconds
maxAttempts: number = 36 // 6 minutes max (36 * 10 seconds)
): Promise<void> {
let attempts = 0;
const poll = async () => {
try {
attempts++;
console.log(`📊 Polling attempt ${attempts}/${maxAttempts} for task: ${taskId}`);
const status = await this.getStrategyGenerationStatus(taskId);
// Call progress callback
onProgress(status);
// Check if completed
if (status.status === 'completed') {
console.log('✅ Strategy generation completed:', status);
onComplete(status.strategy);
return;
}
// Check if failed
if (status.status === 'failed') {
console.error('❌ Strategy generation failed:', status.error);
onError(status.error || 'Strategy generation failed');
return;
}
// Check if max attempts reached
if (attempts >= maxAttempts) {
console.warn('⏰ Max polling attempts reached, checking final status...');
const finalStatus = await this.getStrategyGenerationStatus(taskId);
if (finalStatus.status === 'completed') {
onComplete(finalStatus.strategy);
} else {
onError('Strategy generation timeout. The process may still be running in the background.');
}
return;
}
// Continue polling
setTimeout(poll, pollInterval);
} catch (error) {
console.error('❌ Error polling strategy generation status:', error);
onError(error);
}
};
// Start polling
poll();
}
async updateEnhancedStrategy(id: string, updates: any): Promise<any> { async updateEnhancedStrategy(id: string, updates: any): Promise<any> {
return this.handleRequest(async () => { return this.handleRequest(async () => {
const response = await apiClient.put(`${this.baseURL}/enhanced-strategies/${id}`, updates); const response = await apiClient.put(`${this.baseURL}/enhanced-strategies/${id}`, updates);