ALwrity onboarding fixes

This commit is contained in:
ajaysi
2025-10-04 13:24:41 +05:30
parent 510b79bbf8
commit 14dfb2e5c0
14 changed files with 1182 additions and 446 deletions

View File

@@ -5,7 +5,7 @@
"title": "AI LLM Providers",
"description": "Configure AI language model providers",
"status": "completed",
"completed_at": "2025-09-30T11:54:21.688932",
"completed_at": "2025-10-03T17:29:12.878656",
"data": {
"api_keys": {
"gemini": "AIzaSyB6QrCiOBAzh8xLdmSumec2ysdHeyqyxgw",
@@ -19,9 +19,175 @@
"step_number": 2,
"title": "Website Analysis",
"description": "Set up website analysis and crawling",
"status": "pending",
"completed_at": null,
"data": null,
"status": "completed",
"completed_at": "2025-10-03T17:42:17.953305",
"data": {
"website": "https://alwrity.com",
"domainName": "Alwrity.com",
"analysis": {
"writing_style": {
"tone": "Informative, enthusiastic",
"voice": "Helpful, direct",
"complexity": "Moderate",
"engagement_level": "High",
"brand_personality": "Innovative, friendly",
"formality_level": "Semi-formal",
"emotional_appeal": "Benefit-driven"
},
"content_characteristics": {
"sentence_structure": "Varied",
"vocabulary_level": "Accessible",
"paragraph_organization": "Clear, concise",
"content_flow": "Logical, user-centric",
"readability_score": "Good",
"content_density": "Moderate",
"visual_elements_usage": "High"
},
"target_audience": {
"demographics": [
"Marketers",
"Bloggers",
"Content creators"
],
"expertise_level": "Beginner to intermediate",
"industry_focus": "General, tech",
"geographic_focus": "",
"psychographic_profile": [
"Tech-savvy",
"Value-conscious",
"Efficiency-seeking"
],
"pain_points": [
"Time-consuming content creation",
"Lack of SEO knowledge",
"Writer's block"
],
"motivations": [
"Efficiency",
"Increased website traffic",
"Content quality"
]
},
"content_type": {
"primary_type": "AI writing tool promotion",
"secondary_types": [
"Blog posts",
"Marketing materials"
],
"purpose": "Promote AI tools, generate leads",
"call_to_action": "Try Now!",
"conversion_focus": "Tool usage",
"educational_value": "Moderate"
},
"recommended_settings": {
"writing_tone": "Enthusiastic, informative",
"target_audience": "Content creators, marketers",
"content_type": "AI tool promotion, blog",
"creativity_level": "High",
"geographic_location": "",
"industry_context": "AI, Content Creation",
"brand_alignment": "Strong"
},
"guidelines": {
"tone_recommendations": [
"Informative & Enthusiastic: Maintain a helpful and engaging tone. Example: 'Let's dive into how AI can revolutionize your content creation!'"
],
"structure_guidelines": [
"Clear & Concise: Use headings, subheadings, and bullet points for easy readability. Example: Break down complex topics into digestible sections.",
"Logical Flow: Organize content with a user-centric approach, starting with the problem and offering solutions. Example: Start with the pain points of content creation and then introduce your AI tools."
],
"vocabulary_suggestions": [
"Accessible Language: Avoid jargon; use clear and concise language. Example: Instead of 'leverage AI,' use 'use AI.'"
],
"engagement_tips": [
"Visuals: Incorporate images, screenshots, and videos to enhance understanding. Example: Include screenshots of your AI tools in action.",
"Benefit-Driven: Focus on the benefits for the user. Example: 'Save time and create high-quality content with our AI.'"
],
"audience_considerations": [
"Targeted Content: Address the needs of marketers, bloggers, and content creators. Example: Provide specific examples relevant to their workflows.",
"Address Pain Points: Acknowledge and solve common content creation challenges. Example: Offer solutions to writer's block and SEO optimization."
],
"brand_alignment": [
"Helpful & Innovative Voice: Maintain a helpful, innovative, and friendly brand voice. Example: Offer free resources and tutorials.",
"Open Source Focus: Highlight the open-source nature of the tools. Example: Mention the benefits of open-source for users.",
"Value Proposition: Clearly communicate the value of the tools. Example: 'Create fact-based, multilingual content efficiently.'"
],
"seo_optimization": [
"Keyword Research: Identify relevant keywords and incorporate them naturally. Example: Use keywords like 'AI content creation,' 'SEO optimization,' and 'free AI tools.'",
"Optimize Headings & Meta Descriptions: Use keywords in headings and create compelling meta descriptions. Example: Write a meta description that includes a clear call to action and keyword."
],
"conversion_optimization": [
"Clear CTAs: Include clear calls to action. Example: 'Try our free AI tool today!'",
"Focus on Benefits: Emphasize the value proposition. Example: 'Sign up to get instant access to AI-powered content creation.'"
]
},
"best_practices": [
"Provide In-Depth Tutorials: Offer detailed guides and tutorials to showcase the tools' capabilities.",
"Showcase Diverse Use Cases: Demonstrate how the tools can be applied in various scenarios.",
"Build Community: Encourage user interaction and feedback.",
"Integrate with Platforms: Explore integrations with popular content platforms."
],
"avoid_elements": [
"Overly Technical Jargon: Steer clear of overly complex technical terms that may alienate the audience.",
"Misleading Claims: Avoid making exaggerated claims about AI capabilities.",
"Negative Brand Association: Do not use language that portrays the brand as anything other than helpful and accessible."
],
"content_strategy": "Focus on creating informative, user-centric content that highlights the benefits of open-source AI tools for content creation, addressing the pain points of the target audience while providing practical solutions and SEO optimization.",
"ai_generation_tips": [
"Fact-Checking: Always verify the information generated by AI tools.",
"Human Oversight: Review and refine AI-generated content for accuracy, clarity, and brand voice.",
"Experimentation: Test different prompts and inputs to optimize output."
],
"competitive_advantages": [
"Fact-Based Content: Emphasize the ability to generate fact-based content.",
"Open Source: Highlight the benefits of open-source for users (e.g., transparency, community support, customization).",
"Multilingual Support: Promote the multilingual capabilities of the tools."
],
"content_calendar_suggestions": [
"Tutorials: Create step-by-step guides on using the AI tools for different content types.",
"Use Case Studies: Showcase successful implementations of the tools.",
"SEO Optimization Guides: Provide tips and best practices for improving search engine rankings.",
"Industry News & Trends: Share insights on the latest developments in AI and content creation."
],
"style_patterns": {
"patterns": {
"sentence_length": "short to medium",
"vocabulary_patterns": [
"keywords related to AI and content generation",
"action-oriented verbs"
],
"rhetorical_devices": [
"repetition",
"call to action"
],
"paragraph_structure": "varied, often short and focused",
"transition_phrases": [
"Click to",
"and"
]
},
"style_consistency": "consistent in tone and purpose",
"unique_elements": [
"focus on AI-powered content creation tools",
"integration with platforms like WordPress"
],
"meta": {
"schema_version": "1.1",
"confidence": 0.8,
"notes": "The content is promotional and tool-focused.",
"uncertainty": {
"fields": []
}
}
},
"style_consistency": "consistent in tone and purpose",
"unique_elements": [
"focus on AI-powered content creation tools",
"integration with platforms like WordPress"
]
},
"useAnalysisForGenAI": true
},
"validation_errors": []
},
{
@@ -61,9 +227,9 @@
"validation_errors": []
}
],
"current_step": 2,
"current_step": 3,
"started_at": "2025-09-29T17:22:14.375002",
"last_updated": "2025-09-30T11:54:21.688938",
"last_updated": "2025-10-03T17:42:17.953324",
"is_completed": false,
"completed_at": null
}

View File

@@ -8,6 +8,7 @@ from sqlalchemy.orm import Session
from loguru import logger
from typing import Dict, Any
from datetime import datetime
import hashlib
from models.component_logic import (
UserInfoRequest, UserInfoResponse,
@@ -45,6 +46,23 @@ research_utilities = ResearchUtilities()
# Create router
router = APIRouter(prefix="/api/onboarding", tags=["component_logic"])
# Utility function for consistent user ID to integer conversion
def clerk_user_id_to_int(user_id: str) -> int:
"""
Convert Clerk user ID to consistent integer for database session_id.
Uses SHA256 hashing for deterministic, consistent results across all requests.
Args:
user_id: Clerk user ID (e.g., 'user_2qA6V8bFFnhPRGp8JYxP4YTJtHl')
Returns:
int: Deterministic integer derived from user ID
"""
# Use SHA256 for consistent hashing (unlike Python's hash() which varies per process)
user_id_hash = hashlib.sha256(user_id.encode()).hexdigest()
# Take first 8 characters of hex and convert to int, mod to fit in INT range
return int(user_id_hash[:8], 16) % 2147483647
# AI Research Endpoints
@router.post("/ai-research/validate-user", response_model=UserInfoResponse)
@@ -99,11 +117,8 @@ async def configure_research_preferences(
preferences_service = ResearchPreferencesService(db)
# Use authenticated Clerk user ID for proper user isolation
# Convert user_id to int if service expects it, or update service to accept string
try:
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
except:
user_id_int = hash(user_id) % 2147483647
# Use consistent SHA256-based conversion
user_id_int = clerk_user_id_to_int(user_id)
# Save preferences with user ID (not session_id)
preferences_id = preferences_service.save_preferences_with_style_data(user_id_int, preferences)
@@ -504,10 +519,8 @@ async def complete_style_detection(
analysis_service = WebsiteAnalysisService(db_session)
# Use authenticated Clerk user ID for proper user isolation
try:
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
except:
user_id_int = hash(user_id) % 2147483647
# Use consistent SHA256-based conversion
user_id_int = clerk_user_id_to_int(user_id)
# Check for existing analysis if URL is provided
existing_analysis = None
@@ -536,11 +549,44 @@ async def complete_style_detection(
timestamp=datetime.now().isoformat()
)
# Step 2: Analyze style
style_analysis = style_logic.analyze_content_style(crawl_result['content'])
# Step 2-4: Parallelize AI API calls for performance (3 calls → 1 parallel batch)
import asyncio
from functools import partial
# Prepare parallel tasks
logger.info("[complete_style_detection] Starting parallel AI analysis...")
async def run_style_analysis():
"""Run style analysis in executor"""
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, partial(style_logic.analyze_content_style, crawl_result['content']))
async def run_patterns_analysis():
"""Run patterns analysis in executor (if requested)"""
if not request.include_patterns:
return None
loop = asyncio.get_event_loop()
return await loop.run_in_executor(None, partial(style_logic.analyze_style_patterns, crawl_result['content']))
# Execute style and patterns analysis in parallel
style_analysis, patterns_result = await asyncio.gather(
run_style_analysis(),
run_patterns_analysis(),
return_exceptions=True
)
# Check if style_analysis failed
if isinstance(style_analysis, Exception):
error_msg = str(style_analysis)
logger.error(f"Style analysis failed with exception: {error_msg}")
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
return StyleDetectionResponse(
success=False,
error=f"Style analysis failed: {error_msg}",
timestamp=datetime.now().isoformat()
)
if not style_analysis or not style_analysis.get('success'):
# Check if it's an API key issue
error_msg = style_analysis.get('error', 'Unknown error') if style_analysis else 'Analysis failed'
if 'API key' in error_msg or 'configure' in error_msg:
return StyleDetectionResponse(
@@ -549,7 +595,6 @@ async def complete_style_detection(
timestamp=datetime.now().isoformat()
)
else:
# Save error analysis
analysis_service.save_error_analysis(user_id_int, request.url or "text_sample", error_msg)
return StyleDetectionResponse(
success=False,
@@ -557,17 +602,20 @@ async def complete_style_detection(
timestamp=datetime.now().isoformat()
)
# Step 3: Analyze patterns (optional)
# Process patterns result
style_patterns = None
if request.include_patterns:
patterns_result = style_logic.analyze_style_patterns(crawl_result['content'])
if patterns_result and patterns_result.get('success'):
if request.include_patterns and patterns_result and not isinstance(patterns_result, Exception):
if patterns_result.get('success'):
style_patterns = patterns_result.get('patterns')
# Step 4: Generate guidelines (optional)
# Step 4: Generate guidelines (depends on style_analysis, must run after)
style_guidelines = None
if request.include_guidelines:
guidelines_result = style_logic.generate_style_guidelines(style_analysis.get('analysis', {}))
loop = asyncio.get_event_loop()
guidelines_result = await loop.run_in_executor(
None,
partial(style_logic.generate_style_guidelines, style_analysis.get('analysis', {}))
)
if guidelines_result and guidelines_result.get('success'):
style_guidelines = guidelines_result.get('guidelines')
@@ -628,10 +676,8 @@ async def check_existing_analysis(
analysis_service = WebsiteAnalysisService(db_session)
# Use authenticated Clerk user ID for proper user isolation
try:
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
except:
user_id_int = hash(user_id) % 2147483647
# Use consistent SHA256-based conversion
user_id_int = clerk_user_id_to_int(user_id)
# Check for existing analysis for THIS USER ONLY
existing_analysis = analysis_service.check_existing_analysis(user_id_int, website_url)
@@ -684,10 +730,8 @@ async def get_session_analyses(current_user: Dict[str, Any] = Depends(get_curren
analysis_service = WebsiteAnalysisService(db_session)
# Use authenticated Clerk user ID for proper user isolation
try:
user_id_int = int(user_id.replace('user_', '').replace('-', '')[:8], 16) % 2147483647
except:
user_id_int = hash(user_id) % 2147483647
# Use consistent SHA256-based conversion
user_id_int = clerk_user_id_to_int(user_id)
# Get analyses for THIS USER ONLY (not all users!)
analyses = analysis_service.get_session_analyses(user_id_int)

View File

@@ -117,26 +117,24 @@ class ClerkAuthMiddleware:
# Use cached PyJWKClient to avoid repeated JWKS fetches
if jwks_url not in self._jwks_client_cache:
logger.info(f"Creating new PyJWKClient for {jwks_url} with caching enabled")
# Create client with caching: cache_keys=True, max_cached_keys=16, cache_jwk_set_timeout=3600 (1 hour)
# Create client with caching enabled (cache_keys=True keeps keys in memory)
self._jwks_client_cache[jwks_url] = PyJWKClient(
jwks_url,
cache_keys=True,
max_cached_keys=16,
cache_jwk_set_timeout=3600, # Cache JWKS for 1 hour
timeout=10 # 10 second timeout for JWKS fetch
max_cached_keys=16
)
jwks_client = self._jwks_client_cache[jwks_url]
signing_key = jwks_client.get_signing_key_from_jwt(token)
# Verify and decode the token with clock skew tolerance
# Add 60 seconds leeway to handle clock skew between client/server
# Add 300 seconds (5 minutes) leeway to handle clock skew and token refresh delays
decoded_token = jwt.decode(
token,
signing_key.key,
algorithms=["RS256"],
options={"verify_signature": True, "verify_exp": True},
leeway=60 # Allow 60 seconds clock skew
leeway=300 # Allow 5 minutes leeway for token refresh during navigation
)
# Extract user information
@@ -171,7 +169,7 @@ class ClerkAuthMiddleware:
decoded_token = jwt.decode(
token,
options={"verify_signature": False},
leeway=60 # Allow 60 seconds clock skew
leeway=300 # Allow 5 minutes leeway for token refresh
)
# Extract user information from the token

View File

@@ -41,11 +41,17 @@ class WebsiteAnalysisService:
if existing_analysis:
# Update existing analysis
existing_analysis.writing_style = analysis_data.get('style_analysis', {}).get('writing_style')
existing_analysis.content_characteristics = analysis_data.get('style_analysis', {}).get('content_characteristics')
existing_analysis.target_audience = analysis_data.get('style_analysis', {}).get('target_audience')
existing_analysis.content_type = analysis_data.get('style_analysis', {}).get('content_type')
existing_analysis.recommended_settings = analysis_data.get('style_analysis', {}).get('recommended_settings')
style_analysis = analysis_data.get('style_analysis', {})
existing_analysis.writing_style = style_analysis.get('writing_style')
existing_analysis.content_characteristics = style_analysis.get('content_characteristics')
existing_analysis.target_audience = style_analysis.get('target_audience')
existing_analysis.content_type = style_analysis.get('content_type')
existing_analysis.recommended_settings = style_analysis.get('recommended_settings')
# Store brand_analysis and content_strategy_insights if model supports it
if hasattr(existing_analysis, 'brand_analysis'):
existing_analysis.brand_analysis = style_analysis.get('brand_analysis')
if hasattr(existing_analysis, 'content_strategy_insights'):
existing_analysis.content_strategy_insights = style_analysis.get('content_strategy_insights')
existing_analysis.crawl_result = analysis_data.get('crawl_result')
existing_analysis.style_patterns = analysis_data.get('style_patterns')
existing_analysis.style_guidelines = analysis_data.get('style_guidelines')
@@ -59,20 +65,28 @@ class WebsiteAnalysisService:
return existing_analysis.id
else:
# Create new analysis
analysis = WebsiteAnalysis(
session_id=session_id,
website_url=website_url,
writing_style=analysis_data.get('style_analysis', {}).get('writing_style'),
content_characteristics=analysis_data.get('style_analysis', {}).get('content_characteristics'),
target_audience=analysis_data.get('style_analysis', {}).get('target_audience'),
content_type=analysis_data.get('style_analysis', {}).get('content_type'),
recommended_settings=analysis_data.get('style_analysis', {}).get('recommended_settings'),
crawl_result=analysis_data.get('crawl_result'),
style_patterns=analysis_data.get('style_patterns'),
style_guidelines=analysis_data.get('style_guidelines'),
status='completed',
warning_message=analysis_data.get('warning')
)
style_analysis = analysis_data.get('style_analysis', {})
analysis_args = {
'session_id': session_id,
'website_url': website_url,
'writing_style': style_analysis.get('writing_style'),
'content_characteristics': style_analysis.get('content_characteristics'),
'target_audience': style_analysis.get('target_audience'),
'content_type': style_analysis.get('content_type'),
'recommended_settings': style_analysis.get('recommended_settings'),
'crawl_result': analysis_data.get('crawl_result'),
'style_patterns': analysis_data.get('style_patterns'),
'style_guidelines': analysis_data.get('style_guidelines'),
'status': 'completed',
'warning_message': analysis_data.get('warning')
}
# Add brand_analysis and content_strategy_insights if model supports it
if hasattr(WebsiteAnalysis, 'brand_analysis'):
analysis_args['brand_analysis'] = style_analysis.get('brand_analysis')
if hasattr(WebsiteAnalysis, 'content_strategy_insights'):
analysis_args['content_strategy_insights'] = style_analysis.get('content_strategy_insights')
analysis = WebsiteAnalysis(**analysis_args)
self.db.add(analysis)
self.db.commit()

View File

@@ -82,13 +82,64 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
const [showHighlightsModal, setShowHighlightsModal] = useState(false);
const [selectedCompetitorHighlights, setSelectedCompetitorHighlights] = useState<string[]>([]);
const [selectedCompetitorTitle, setSelectedCompetitorTitle] = useState<string>('');
const [usingCachedData, setUsingCachedData] = useState(false);
// Check for cached competitor analysis data
const loadCachedAnalysis = useCallback(() => {
try {
const cachedData = localStorage.getItem('competitor_analysis_data');
const cachedUrl = localStorage.getItem('competitor_analysis_url');
const cacheTimestamp = localStorage.getItem('competitor_analysis_timestamp');
// Get current URL for comparison
const finalUserUrl = userUrl || localStorage.getItem('website_url') || '';
if (cachedData && cachedUrl === finalUserUrl && cacheTimestamp) {
const cacheAge = Date.now() - parseInt(cacheTimestamp);
const cacheValidDuration = 24 * 60 * 60 * 1000; // 24 hours
// Check if cache is still valid (less than 24 hours old)
if (cacheAge < cacheValidDuration) {
const parsedData = JSON.parse(cachedData);
console.log('CompetitorAnalysisStep: Loading cached competitor analysis:', {
url: cachedUrl,
cacheAge: Math.round(cacheAge / (60 * 1000)),
competitors: parsedData.competitors?.length || 0
});
setCompetitors(parsedData.competitors || []);
setSocialMediaAccounts(parsedData.social_media_accounts || {});
setSocialMediaCitations(parsedData.social_media_citations || []);
setResearchSummary(parsedData.research_summary || null);
setUsingCachedData(true);
return true; // Successfully loaded from cache
} else {
console.log('CompetitorAnalysisStep: Cache expired, will run fresh analysis');
}
}
return false; // No valid cache found
} catch (err) {
console.error('Error loading cached analysis:', err);
return false;
}
}, [userUrl]);
const startCompetitorDiscovery = useCallback(async (force = false) => {
// Check cache first unless forced
if (!force && loadCachedAnalysis()) {
console.log('CompetitorAnalysisStep: Using cached competitor analysis');
return;
}
const startCompetitorDiscovery = useCallback(async () => {
setIsAnalyzing(true);
setShowProgressModal(true);
setError(null);
setAnalysisProgress(0);
setAnalysisStep('Initializing competitor discovery...');
setUsingCachedData(false);
try {
setAnalysisStep('Validating session...');
@@ -139,10 +190,28 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
setAnalysisProgress(100);
await new Promise(resolve => setTimeout(resolve, 500));
setCompetitors(result.competitors || []);
setSocialMediaAccounts(result.social_media_accounts || {});
setSocialMediaCitations(result.social_media_citations || []);
setResearchSummary(result.research_summary || null);
const analysisData = {
competitors: result.competitors || [],
social_media_accounts: result.social_media_accounts || {},
social_media_citations: result.social_media_citations || [],
research_summary: result.research_summary || null
};
setCompetitors(analysisData.competitors);
setSocialMediaAccounts(analysisData.social_media_accounts);
setSocialMediaCitations(analysisData.social_media_citations);
setResearchSummary(analysisData.research_summary);
// Cache the analysis results
try {
localStorage.setItem('competitor_analysis_data', JSON.stringify(analysisData));
localStorage.setItem('competitor_analysis_url', finalUserUrl);
localStorage.setItem('competitor_analysis_timestamp', Date.now().toString());
console.log('CompetitorAnalysisStep: Cached competitor analysis for future use');
} catch (cacheErr) {
console.warn('Failed to cache competitor analysis:', cacheErr);
}
setShowProgressModal(false);
setIsAnalyzing(false);
} else {
@@ -154,11 +223,23 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
setIsAnalyzing(false);
setShowProgressModal(false);
}
}, [userUrl, industryContext]); // sessionId removed from dependencies
}, [userUrl, industryContext, loadCachedAnalysis]); // sessionId removed from dependencies
// Initialize: Check cache first, then run analysis if needed
useEffect(() => {
startCompetitorDiscovery();
}, [startCompetitorDiscovery]);
const initialize = async () => {
// Try to load from cache first
const cacheLoaded = loadCachedAnalysis();
// If no cache found, run fresh analysis
if (!cacheLoaded) {
await startCompetitorDiscovery(false);
}
};
initialize();
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []); // Run only once on mount
const handleContinue = () => {
const researchData = {
@@ -180,20 +261,58 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
return (
<Box sx={classes.container}>
<Box sx={classes.header}>
<Typography variant="h4" sx={{ fontWeight: 600, mb: 2 }}>
<Typography
variant="h4"
sx={{
fontWeight: 600,
mb: 2,
color: '#1a202c !important' // Force dark text for readability
}}
>
Research Your Competition
</Typography>
<Typography variant="h6" sx={{ color: 'text.secondary', fontWeight: 400 }}>
<Typography
variant="h6"
sx={{
color: '#4a5568 !important', // Force dark secondary text
fontWeight: 400
}}
>
Discover your competitors and analyze their strategies to gain competitive advantage
</Typography>
</Box>
{usingCachedData && !error && (
<Alert
severity="info"
sx={{
mb: 3,
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
border: '1px solid #81d4fa',
color: '#01579b',
'& .MuiAlert-icon': {
color: '#0277bd'
}
}}
>
Loaded previously analyzed competitor data.
<Button
startIcon={<RefreshIcon />}
onClick={() => startCompetitorDiscovery(true)}
sx={{ ml: 2 }}
size="small"
>
Run Fresh Analysis
</Button>
</Alert>
)}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
<Button
startIcon={<RefreshIcon />}
onClick={startCompetitorDiscovery}
onClick={() => startCompetitorDiscovery(true)}
sx={{ ml: 2 }}
>
Retry
@@ -204,23 +323,36 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
{!isAnalyzing && !error && (competitors.length > 0 || researchSummary) && (
<Box>
{researchSummary && (
<Paper sx={{ p: 3, mb: 4, backgroundColor: 'primary.50', border: '1px solid', borderColor: 'primary.200' }}>
<Paper sx={{
p: 3,
mb: 4,
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)', // Warm sky-blue gradient
border: '1px solid #81d4fa',
borderRadius: 2,
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)'
}}>
<Typography variant="h6" gutterBottom fontWeight={600} color="primary">
<AssessmentIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Research Summary
</Typography>
<Grid container spacing={3} mt={1}>
<Grid item xs={12} md={3}>
<Grid item xs={12} sm={6} md={3}>
<Typography variant="h4" color="primary" fontWeight={700}>
{researchSummary.total_competitors}
</Typography>
<Typography variant="body2" color="text.secondary">
<Typography
variant="body2"
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
>
Competitors Found
</Typography>
</Grid>
<Grid item xs={12} md={9}>
<Typography variant="body1" color="text.secondary">
<Grid item xs={12} sm={6} md={9}>
<Typography
variant="body1"
sx={{ color: '#2d3748 !important' }} // Force dark text for readability
>
{researchSummary.market_insights}
</Typography>
</Grid>
@@ -231,8 +363,14 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
{/* Social Media Accounts Section */}
{Object.keys(socialMediaAccounts).length > 0 && (
<>
<Typography variant="h6" gutterBottom fontWeight={600} mb={3}>
<ShareIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
<Typography
variant="h6"
gutterBottom
fontWeight={600}
mb={3}
sx={{ color: '#1a202c !important' }} // Force dark text
>
<ShareIcon sx={{ mr: 1, verticalAlign: 'middle', color: '#667eea !important' }} />
Social Media Presence
</Typography>
@@ -250,8 +388,18 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
};
return (
<Grid item xs={12} sm={6} md={4} key={platform}>
<Card sx={{ height: '100%' }}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={platform}>
<Card sx={{
height: '100%',
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
border: '1px solid #81d4fa',
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 8px 20px rgba(3, 169, 244, 0.25)'
}
}}>
<CardContent>
<Box display="flex" alignItems="center" gap={2}>
<Avatar sx={{ width: 40, height: 40, bgcolor: 'primary.main' }}>
@@ -282,25 +430,52 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
</>
)}
<Typography variant="h6" gutterBottom fontWeight={600} mb={3}>
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
<Typography
variant="h6"
gutterBottom
fontWeight={600}
mb={3}
sx={{ color: '#1a202c !important' }} // Force dark text
>
<BusinessIcon sx={{ mr: 1, verticalAlign: 'middle', color: '#667eea !important' }} />
Discovered Competitors ({competitors.length})
</Typography>
<Grid container spacing={3}>
{competitors.map((competitor, index) => (
<Grid item xs={12} md={6} lg={4} key={index}>
<Card sx={{ height: '100%', display: 'flex', flexDirection: 'column' }}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2} key={index}>
<Card sx={{
height: '100%',
display: 'flex',
flexDirection: 'column',
background: 'linear-gradient(135deg, #e0f2fe 0%, #b3e5fc 100%)',
border: '1px solid #81d4fa',
boxShadow: '0 4px 12px rgba(3, 169, 244, 0.15)',
transition: 'all 0.3s ease',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 8px 20px rgba(3, 169, 244, 0.25)'
}
}}>
<CardContent sx={{ flexGrow: 1 }}>
<Box display="flex" alignItems="flex-start" gap={2} mb={2}>
<Avatar sx={{ width: 40, height: 40 }}>
<BusinessIcon />
</Avatar>
<Box flex={1}>
<Typography variant="h6" fontWeight={600} gutterBottom>
<Typography
variant="h6"
fontWeight={600}
gutterBottom
sx={{ color: '#1a202c !important' }} // Force dark text for readability
>
{competitor.title}
</Typography>
<Typography variant="body2" color="text.secondary" gutterBottom>
<Typography
variant="body2"
gutterBottom
sx={{ color: '#4a5568 !important' }} // Force dark text for readability
>
{competitor.domain}
</Typography>
<Chip
@@ -311,7 +486,11 @@ const CompetitorAnalysisStep: React.FC<CompetitorAnalysisStepProps> = ({
</Box>
</Box>
<Typography variant="body2" color="text.secondary" mb={2}>
<Typography
variant="body2"
mb={2}
sx={{ color: '#2d3748 !important' }} // Force dark text for readability
>
{competitor.summary.length > 150
? `${competitor.summary.substring(0, 150)}...`
: competitor.summary

View File

@@ -8,27 +8,17 @@ import {
Alert,
CircularProgress,
Card,
CardContent,
Grid,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContentText,
Fade,
LinearProgress,
Stepper,
Step,
StepLabel,
Checkbox,
FormControlLabel
DialogContentText
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
History as HistoryIcon,
Business as BusinessIcon,
Star as StarIcon,
Verified as VerifiedIcon
Business as BusinessIcon
} from '@mui/icons-material';
// Extracted components
@@ -37,7 +27,6 @@ import { AnalysisResultsDisplay, AnalysisProgressDisplay } from './WebsiteStep/c
// Extracted utilities
import {
fixUrlFormat,
extractDomainName,
checkExistingAnalysis,
loadExistingAnalysis,
performAnalysis,
@@ -336,18 +325,17 @@ const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderConte
return;
}
const stepData = {
website: fixedUrl,
domainName: result.domainName || domainName,
analysis: result.analysis,
useAnalysisForGenAI,
};
// Set the loaded analysis data for display
setDomainName(result.domainName || domainName);
setAnalysis(result.analysis);
setSuccess('Previous analysis loaded successfully!');
// Store in localStorage for Step 3 (Competitor Analysis)
localStorage.setItem('website_url', fixedUrl);
localStorage.setItem('website_analysis_data', JSON.stringify(result.analysis));
onContinue(stepData);
// DO NOT call onContinue() here - let user review the analysis first
// User will click "Continue" button when ready to proceed
};
const handleNewAnalysis = async () => {

View File

@@ -14,22 +14,15 @@ import {
Checkbox,
FormControlLabel,
Alert,
Paper,
Tooltip
Paper
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
Verified as VerifiedIcon,
Psychology as PsychologyIcon,
Analytics as AnalyticsIcon,
TrendingUp as TrendingUpIcon,
Language as LanguageIcon,
Palette as PaletteIcon,
Speed as SpeedIcon,
Group as GroupIcon,
Business as BusinessIcon,
Lightbulb as LightbulbIcon,
Warning as WarningIcon
Lightbulb as LightbulbIcon
} from '@mui/icons-material';
// Import rendering utilities
@@ -45,7 +38,12 @@ import {
} from '../utils/renderUtils';
// Import extracted components
import { EnhancedGuidelinesSection, KeyInsightsGrid } from './index';
import {
EnhancedGuidelinesSection,
KeyInsightsGrid,
ContentCharacteristicsSection,
TargetAudienceAnalysisSection
} from './index';
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
interface StyleAnalysis {
@@ -169,10 +167,24 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
<Box sx={styles.analysisHeader}>
<VerifiedIcon sx={styles.analysisHeaderIcon} />
<Box>
<Typography variant="h4" sx={styles.analysisHeaderTitle} gutterBottom>
<Typography
variant="h4"
sx={{
...styles.analysisHeaderTitle,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
{domainName} Style Analysis
</Typography>
<Typography variant="body1" sx={styles.analysisHeaderSubtitle}>
<Typography
variant="body1"
sx={{
...styles.analysisHeaderSubtitle,
color: '#4a5568 !important' // Force dark secondary text
}}
>
Comprehensive content analysis and personalized recommendations
</Typography>
</Box>
@@ -186,253 +198,32 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
/>
{/* Content Characteristics Section */}
{analysis.content_characteristics && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<AnalyticsIcon color="info" />
Content Characteristics
</Typography>
<Grid container spacing={2}>
{analysis.content_characteristics.vocabulary_level && (
<Grid item xs={12} md={6}>
<Tooltip title="The complexity and sophistication of words used in the content. Higher levels use more advanced vocabulary while accessible levels use simpler, everyday words." arrow>
<Box>
{renderKeyInsight(
'Vocabulary Level',
analysis.content_characteristics.vocabulary_level,
<LanguageIcon />,
'info'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.readability_score && (
<Grid item xs={12} md={6}>
<Tooltip title="How easy it is for readers to understand the content. Higher scores mean the content is easier to read and comprehend." arrow>
<Box>
{renderKeyInsight(
'Readability Score',
analysis.content_characteristics.readability_score,
<SpeedIcon />,
'success'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.content_density && (
<Grid item xs={12} md={6}>
<Tooltip title="How much information is packed into each section. Moderate density balances information with readability." arrow>
<Box>
{renderKeyInsight(
'Content Density',
analysis.content_characteristics.content_density,
<PaletteIcon />,
'warning'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.sentence_structure && (
<Grid item xs={12} md={6}>
<Tooltip title="The variety and complexity of sentence patterns used. Varied structures keep readers engaged." arrow>
<Box>
{renderKeyInsight(
'Sentence Structure',
analysis.content_characteristics.sentence_structure,
<AnalyticsIcon />,
'secondary'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.paragraph_organization && (
<Grid item xs={12} md={6}>
<Tooltip title="How paragraphs are structured and organized. Clear organization helps readers follow the content easily." arrow>
<Box>
{renderKeyInsight(
'Paragraph Organization',
analysis.content_characteristics.paragraph_organization,
<AnalyticsIcon />,
'primary'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.content_flow && (
<Grid item xs={12} md={6}>
<Tooltip title="How smoothly the content moves from one idea to the next. Good flow keeps readers engaged throughout." arrow>
<Box>
{renderKeyInsight(
'Content Flow',
analysis.content_characteristics.content_flow,
<TrendingUpIcon />,
'success'
)}
</Box>
</Tooltip>
</Grid>
)}
{analysis.content_characteristics.visual_elements_usage && (
<Grid item xs={12} md={6}>
<Tooltip title="How often images, charts, and other visual elements are used to support the text content." arrow>
<Box>
{renderKeyInsight(
'Visual Elements Usage',
analysis.content_characteristics.visual_elements_usage,
<PaletteIcon />,
'warning'
)}
</Box>
</Tooltip>
</Grid>
)}
</Grid>
</Box>
)}
<ContentCharacteristicsSection
contentCharacteristics={analysis.content_characteristics as any}
/>
{/* Detailed Target Audience Section */}
{analysis.target_audience && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<GroupIcon color="info" />
Target Audience Analysis
</Typography>
<Grid container spacing={2}>
{analysis.target_audience.demographics && analysis.target_audience.demographics.length > 0 && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Demographics',
analysis.target_audience.demographics,
<GroupIcon />,
'info'
)}
</Grid>
)}
{analysis.target_audience.industry_focus && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Industry Focus',
analysis.target_audience.industry_focus,
<BusinessIcon />,
'primary'
)}
</Grid>
)}
{analysis.target_audience.geographic_focus && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Geographic Focus',
analysis.target_audience.geographic_focus,
<AnalyticsIcon />,
'secondary'
)}
</Grid>
)}
{analysis.target_audience.psychographic_profile && (
<Grid item xs={12} md={6}>
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconSuccess}>
<PsychologyIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Psychographic Profile
</Typography>
<Box component="ul" sx={styles.analysisList}>
{Array.isArray(analysis.target_audience.psychographic_profile)
? analysis.target_audience.psychographic_profile.map((item: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{item}
</Typography>
))
: (
<Typography component="li" variant="body2" sx={styles.analysisListItem}>
{analysis.target_audience.psychographic_profile}
</Typography>
)}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
{analysis.target_audience.pain_points && analysis.target_audience.pain_points.length > 0 && (
<Grid item xs={12} md={6}>
<Paper elevation={2} sx={styles.analysisAccentPaperError}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconError}>
<WarningIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Pain Points
</Typography>
<Box component="ul" sx={styles.analysisList}>
{analysis.target_audience.pain_points.map((painPoint: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{painPoint}
</Typography>
))}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
{analysis.target_audience.motivations && analysis.target_audience.motivations.length > 0 && (
<Grid item xs={12} md={6}>
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconSuccess}>
<TrendingUpIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Motivations
</Typography>
<Box component="ul" sx={styles.analysisList}>
{analysis.target_audience.motivations.map((motivation: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{motivation}
</Typography>
))}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
</Grid>
</Box>
)}
{/* Target Audience Analysis Section */}
<TargetAudienceAnalysisSection
targetAudience={analysis.target_audience as any}
/>
{/* Content Type Details Section */}
{analysis.content_type && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<BusinessIcon color="primary" />
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
>
<BusinessIcon sx={{ color: '#667eea !important' }} />
Content Type Analysis
</Typography>
<Grid container spacing={2}>
{analysis.content_type.purpose && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Content Purpose',
analysis.content_type.purpose,
@@ -443,7 +234,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.content_type.call_to_action && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Call to Action Style',
analysis.content_type.call_to_action,
@@ -454,7 +245,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.content_type.conversion_focus && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Conversion Focus',
analysis.content_type.conversion_focus,
@@ -465,7 +256,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.content_type.educational_value && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Educational Value',
analysis.content_type.educational_value,
@@ -507,7 +298,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Content Strategy */}
{analysis.content_strategy && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AutoAwesomeIcon color="primary" />
Content Strategy
</Typography>
@@ -522,14 +321,22 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Recommended Settings for AI Generation */}
{analysis.recommended_settings && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AutoAwesomeIcon color="primary" />
Recommended AI Generation Settings
</Typography>
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
<Grid container spacing={2}>
{analysis.recommended_settings.writing_tone && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Writing Tone:
</Typography>
@@ -540,7 +347,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.target_audience && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Target Audience:
</Typography>
@@ -551,7 +358,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.content_type && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Content Type:
</Typography>
@@ -562,7 +369,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.creativity_level && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Creativity Level:
</Typography>
@@ -573,7 +380,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.industry_context && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Industry Context:
</Typography>
@@ -584,7 +391,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.geographic_location && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Geographic Focus:
</Typography>
@@ -595,7 +402,7 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
)}
{analysis.recommended_settings.brand_alignment && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Typography variant="subtitle2" sx={styles.analysisSubheader}>
Brand Alignment:
</Typography>
@@ -628,7 +435,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Style Consistency Section */}
{analysis.style_consistency && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AnalyticsIcon color="info" />
Style Consistency
</Typography>
@@ -643,7 +458,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Unique Elements Section */}
{analysis.unique_elements && analysis.unique_elements.length > 0 && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AutoAwesomeIcon color="primary" />
Unique Style Elements
</Typography>
@@ -670,13 +493,13 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Best Practices & Avoid Elements */}
<Grid container spacing={2} sx={styles.analysisSection}>
{analysis.best_practices && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderBestPracticesSection(analysis.best_practices)}
</Grid>
)}
{analysis.avoid_elements && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderAvoidElementsSection(analysis.avoid_elements)}
</Grid>
)}
@@ -685,7 +508,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Competitive Advantages */}
{analysis.competitive_advantages && analysis.competitive_advantages.length > 0 && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<TrendingUpIcon color="success" />
Competitive Advantages
</Typography>
@@ -704,7 +535,15 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
{/* Content Calendar Suggestions */}
{analysis.content_calendar_suggestions && analysis.content_calendar_suggestions.length > 0 && (
<Box sx={styles.analysisSection}>
<Typography variant="h5" sx={styles.analysisSectionHeader} gutterBottom>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AnalyticsIcon color="primary" />
Content Calendar Suggestions
</Typography>
@@ -720,6 +559,56 @@ const AnalysisResultsDisplay: React.FC<AnalysisResultsDisplayProps> = ({
</Box>
)}
{/* Content Strategy */}
{analysis.content_strategy && (
<Box sx={styles.analysisSection}>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<BusinessIcon color="primary" />
Content Strategy Overview
</Typography>
<Paper elevation={3} sx={styles.analysisGradientPaperPrimary}>
<Typography variant="body1" sx={styles.analysisParagraph}>
{analysis.content_strategy}
</Typography>
</Paper>
</Box>
)}
{/* AI Generation Tips */}
{analysis.ai_generation_tips && analysis.ai_generation_tips.length > 0 && (
<Box sx={styles.analysisSection}>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
gutterBottom
>
<AutoAwesomeIcon color="secondary" />
AI Content Generation Tips
</Typography>
<Paper elevation={3} sx={styles.analysisGradientPaperAccent}>
<Box component="ul" sx={styles.analysisList}>
{analysis.ai_generation_tips.map((tip: string, index: number) => (
<Typography component="li" variant="body1" key={index} sx={styles.analysisListItem}>
{tip}
</Typography>
))}
</Box>
</Paper>
</Box>
)}
{/* GenAI Integration Checkbox */}
<Box sx={styles.analysisCheckboxContainer}>
<FormControlLabel

View File

@@ -0,0 +1,171 @@
/**
* Content Characteristics Section Component
* Displays content characteristics analysis in a reusable format
*/
import React from 'react';
import {
Box,
Typography,
Grid,
Tooltip
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
Language as LanguageIcon,
Speed as SpeedIcon,
Palette as PaletteIcon,
TrendingUp as TrendingUpIcon
} from '@mui/icons-material';
import { renderKeyInsight } from '../utils/renderUtils';
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
interface ContentCharacteristics {
sentence_structure?: string;
vocabulary_level?: string;
paragraph_organization?: string;
content_flow?: string;
readability_score?: string;
content_density?: string;
visual_elements_usage?: string;
}
interface ContentCharacteristicsSectionProps {
contentCharacteristics?: ContentCharacteristics;
}
const ContentCharacteristicsSection: React.FC<ContentCharacteristicsSectionProps> = ({
contentCharacteristics
}) => {
const styles = useOnboardingStyles();
if (!contentCharacteristics) {
return null;
}
return (
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
>
<AnalyticsIcon sx={{ color: '#667eea !important' }} />
Content Characteristics
</Typography>
<Grid container spacing={2}>
{contentCharacteristics.vocabulary_level && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The complexity and sophistication of words used in the content. Higher levels use more advanced vocabulary while accessible levels use simpler, everyday words." arrow>
<Box>
{renderKeyInsight(
'Vocabulary Level',
contentCharacteristics.vocabulary_level,
<LanguageIcon />,
'info'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.readability_score && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How easy it is for readers to understand the content. Higher scores mean the content is easier to read and comprehend." arrow>
<Box>
{renderKeyInsight(
'Readability Score',
contentCharacteristics.readability_score,
<SpeedIcon />,
'success'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.content_density && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How much information is packed into each section. Moderate density balances information with readability." arrow>
<Box>
{renderKeyInsight(
'Content Density',
contentCharacteristics.content_density,
<PaletteIcon />,
'warning'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.sentence_structure && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The variety and complexity of sentence patterns used. Varied structures keep readers engaged." arrow>
<Box>
{renderKeyInsight(
'Sentence Structure',
contentCharacteristics.sentence_structure,
<AnalyticsIcon />,
'secondary'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.paragraph_organization && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How paragraphs are structured and organized. Clear organization helps readers follow the content easily." arrow>
<Box>
{renderKeyInsight(
'Paragraph Organization',
contentCharacteristics.paragraph_organization,
<AnalyticsIcon />,
'primary'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.content_flow && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How smoothly the content moves from one idea to the next. Good flow keeps readers engaged throughout." arrow>
<Box>
{renderKeyInsight(
'Content Flow',
contentCharacteristics.content_flow,
<TrendingUpIcon />,
'success'
)}
</Box>
</Tooltip>
</Grid>
)}
{contentCharacteristics.visual_elements_usage && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How often images, charts, and other visual elements are used to support the text content." arrow>
<Box>
{renderKeyInsight(
'Visual Elements Usage',
contentCharacteristics.visual_elements_usage,
<PaletteIcon />,
'warning'
)}
</Box>
</Tooltip>
</Grid>
)}
</Grid>
</Box>
);
};
export default ContentCharacteristicsSection;

View File

@@ -56,7 +56,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
return (
<Grid container spacing={2} sx={{ mb: 2.5 }}>
{writing_style?.tone && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The emotional quality and attitude of the writing - how it makes readers feel and the mood it creates." arrow>
<Box>
{renderKeyInsight(
@@ -71,7 +71,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.complexity && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How sophisticated or simple the content is. Moderate complexity balances depth with accessibility." arrow>
<Box>
{renderKeyInsight(
@@ -86,7 +86,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.voice && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The unique personality and style of the writing - what makes it distinctive and recognizable." arrow>
<Box>
{renderKeyInsight(
@@ -101,7 +101,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.engagement_level && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How well the content captures and maintains reader attention throughout the piece." arrow>
<Box>
{renderKeyInsight(
@@ -116,7 +116,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.brand_personality && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The human characteristics and traits associated with the brand, like friendly, professional, or innovative." arrow>
<Box>
{renderKeyInsight(
@@ -131,7 +131,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.formality_level && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How formal or casual the writing style is. Semi-formal strikes a balance between professional and approachable." arrow>
<Box>
{renderKeyInsight(
@@ -146,7 +146,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{writing_style?.emotional_appeal && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="How the content connects with readers' emotions - what feelings it aims to evoke." arrow>
<Box>
{renderKeyInsight(
@@ -161,7 +161,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{target_audience?.expertise_level && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The skill level and experience of the intended readers - from beginners to experts in the subject matter." arrow>
<Box>
{renderKeyInsight(
@@ -176,7 +176,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{target_audience?.geographic_focus && target_audience.geographic_focus.trim() !== '' && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The geographical regions or areas the content is primarily intended for - local, national, or global reach." arrow>
<Box>
{renderKeyInsight(
@@ -191,7 +191,7 @@ const KeyInsightsGrid: React.FC<KeyInsightsGridProps> = ({
)}
{content_type?.primary_type && (
<Grid item xs={12} md={6}>
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Tooltip title="The main category or format of content being created - blog posts, tutorials, product descriptions, etc." arrow>
<Box>
{renderKeyInsight(

View File

@@ -0,0 +1,178 @@
/**
* Target Audience Analysis Section Component
* Displays target audience analysis in a reusable format
*/
import React from 'react';
import {
Box,
Typography,
Grid,
Paper
} from '@mui/material';
import {
Group as GroupIcon,
Business as BusinessIcon,
Analytics as AnalyticsIcon,
Psychology as PsychologyIcon,
Warning as WarningIcon,
TrendingUp as TrendingUpIcon
} from '@mui/icons-material';
import { renderKeyInsight } from '../utils/renderUtils';
import { useOnboardingStyles } from '../../common/useOnboardingStyles';
interface TargetAudience {
demographics?: string[];
expertise_level?: string;
industry_focus?: string;
geographic_focus?: string;
psychographic_profile?: string;
pain_points?: string[];
motivations?: string[];
}
interface TargetAudienceAnalysisSectionProps {
targetAudience?: TargetAudience;
}
const TargetAudienceAnalysisSection: React.FC<TargetAudienceAnalysisSectionProps> = ({
targetAudience
}) => {
const styles = useOnboardingStyles();
if (!targetAudience) {
return null;
}
return (
<Box sx={{ ...styles.analysisSection, mt: 4 }}>
<Typography
variant="h5"
sx={{
...styles.analysisSectionHeader,
color: '#1a202c !important', // Force dark text
fontWeight: '700 !important'
}}
>
<GroupIcon sx={{ color: '#667eea !important' }} />
Target Audience Analysis
</Typography>
<Grid container spacing={2}>
{targetAudience.demographics && targetAudience.demographics.length > 0 && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Demographics',
targetAudience.demographics,
<GroupIcon />,
'info'
)}
</Grid>
)}
{targetAudience.industry_focus && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Industry Focus',
targetAudience.industry_focus,
<BusinessIcon />,
'primary'
)}
</Grid>
)}
{targetAudience.geographic_focus && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
{renderKeyInsight(
'Geographic Focus',
targetAudience.geographic_focus,
<AnalyticsIcon />,
'secondary'
)}
</Grid>
)}
{targetAudience.psychographic_profile && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconSuccess}>
<PsychologyIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Psychographic Profile
</Typography>
<Box component="ul" sx={styles.analysisList}>
{Array.isArray(targetAudience.psychographic_profile)
? targetAudience.psychographic_profile.map((item: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{item}
</Typography>
))
: (
<Typography component="li" variant="body2" sx={styles.analysisListItem}>
{targetAudience.psychographic_profile}
</Typography>
)}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
{targetAudience.pain_points && targetAudience.pain_points.length > 0 && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Paper elevation={2} sx={styles.analysisAccentPaperError}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconError}>
<WarningIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Pain Points
</Typography>
<Box component="ul" sx={styles.analysisList}>
{targetAudience.pain_points.map((painPoint: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{painPoint}
</Typography>
))}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
{targetAudience.motivations && targetAudience.motivations.length > 0 && (
<Grid item xs={12} sm={6} md={4} lg={3} xl={2}>
<Paper elevation={2} sx={styles.analysisAccentPaperSuccess}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={styles.analysisAccentIconSuccess}>
<TrendingUpIcon />
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
Motivations
</Typography>
<Box component="ul" sx={styles.analysisList}>
{targetAudience.motivations.map((motivation: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={styles.analysisListItem}>
{motivation}
</Typography>
))}
</Box>
</Box>
</Box>
</Paper>
</Grid>
)}
</Grid>
</Box>
);
};
export default TargetAudienceAnalysisSection;

View File

@@ -7,3 +7,5 @@ export { default as AnalysisResultsDisplay } from './AnalysisResultsDisplay';
export { default as AnalysisProgressDisplay } from './AnalysisProgressDisplay';
export { default as EnhancedGuidelinesSection } from './EnhancedGuidelinesSection';
export { default as KeyInsightsGrid } from './KeyInsightsGrid';
export { default as ContentCharacteristicsSection } from './ContentCharacteristicsSection';
export { default as TargetAudienceAnalysisSection } from './TargetAudienceAnalysisSection';

View File

@@ -20,6 +20,7 @@ import {
Zoom,
Divider
} from '@mui/material';
import { useTheme, alpha } from '@mui/material/styles';
import {
ExpandMore as ExpandMoreIcon,
CheckCircle as CheckIcon,
@@ -39,49 +40,120 @@ import {
} from '@mui/icons-material';
/**
* Renders a key insight card with icon and value
* Key Insight Card Component
*/
interface KeyInsightProps {
title: string;
value: string | string[];
icon: React.ReactNode;
color?: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info';
}
const KeyInsightCard: React.FC<KeyInsightProps> = ({
title,
value,
icon,
color = 'primary'
}) => {
const theme = useTheme();
const isDark = theme.palette.mode === 'dark';
// Helper function to safely get palette colors
const getPaletteColor = (colorKey: string) => {
const palette = theme.palette as any;
return palette[colorKey] || palette.primary;
};
const paletteColor = getPaletteColor(color);
return (
<Paper
elevation={0}
sx={{
p: 2.5,
mb: 0,
borderRadius: 2.5,
background: isDark
? `linear-gradient(135deg, ${alpha(paletteColor.main, 0.08)} 0%, ${alpha(paletteColor.main, 0.04)} 100%)`
: `linear-gradient(135deg, ${alpha(paletteColor.main, 0.06)} 0%, ${alpha(paletteColor.light, 0.08)} 100%)`,
border: `2px solid`,
borderColor: isDark
? alpha(paletteColor.main, 0.2)
: alpha(paletteColor.main, 0.15),
borderLeftWidth: '5px',
transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)',
'&:hover': {
background: isDark
? `linear-gradient(135deg, ${alpha(paletteColor.main, 0.12)} 0%, ${alpha(paletteColor.main, 0.08)} 100%)`
: `linear-gradient(135deg, ${alpha(paletteColor.main, 0.10)} 0%, ${alpha(paletteColor.light, 0.12)} 100%)`,
borderColor: alpha(paletteColor.main, 0.4),
transform: 'translateY(-4px)',
boxShadow: isDark
? `0 12px 40px ${alpha(paletteColor.main, 0.2)}`
: `0 12px 40px ${alpha(paletteColor.main, 0.15)}`
}
}}
>
<Box display="flex" alignItems="center" gap={2}>
<Box
sx={{
color: `${color}.main`,
fontSize: '1.75rem',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
width: 48,
height: 48,
borderRadius: 2,
background: isDark
? alpha(paletteColor.main, 0.15)
: alpha(paletteColor.main, 0.1),
}}
>
{icon}
</Box>
<Box flex={1}>
<Typography
variant="caption"
sx={{
fontWeight: 700,
fontSize: '0.75rem',
letterSpacing: '0.5px',
textTransform: 'uppercase',
color: isDark ? '#ffffff !important' : '#000000 !important', // Maximum contrast
mb: 0.5,
display: 'block'
}}
>
{title}
</Typography>
<Typography
variant="body1"
sx={{
fontWeight: 600,
fontSize: '1.05rem',
color: isDark ? '#ffffff !important' : '#000000 !important', // Maximum contrast
lineHeight: 1.4
}}
>
{Array.isArray(value) ? value.join(', ') : value}
</Typography>
</Box>
</Box>
</Paper>
);
};
/**
* Backward-compatible wrapper function for renderKeyInsight
* @deprecated Use KeyInsightCard component directly instead
*/
export const renderKeyInsight = (
title: string,
value: string | string[],
icon: React.ReactNode,
color: string = 'primary'
) => (
<Paper
elevation={3}
sx={{
p: 2,
mb: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.08) 0%, rgba(255, 255, 255, 0.12) 100%)',
border: `1px solid rgba(255, 255, 255, 0.15)`,
borderLeft: `4px solid`,
borderLeftColor: `${color}.main`,
backdropFilter: 'blur(10px)',
transition: 'all 0.2s ease-in-out',
'&:hover': {
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.12) 0%, rgba(255, 255, 255, 0.16) 100%)',
border: `1px solid rgba(255, 255, 255, 0.25)`,
transform: 'translateY(-2px)',
boxShadow: '0 8px 25px rgba(0, 0, 0, 0.3)'
}
}}
>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={{ color: `${color}.main`, fontSize: '1.2rem' }}>
{icon}
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="text.secondary" gutterBottom sx={{ fontWeight: 600, fontSize: '0.85rem' }}>
{title}
</Typography>
<Typography variant="body1" fontWeight={600} color="text.primary" sx={{ fontSize: '0.95rem' }}>
{Array.isArray(value) ? value.join(', ') : value}
</Typography>
</Box>
</Box>
</Paper>
);
title: string,
value: string | string[],
icon: React.ReactNode,
color: 'primary' | 'secondary' | 'success' | 'error' | 'warning' | 'info' = 'primary'
) => <KeyInsightCard title={title} value={value} icon={icon} color={color} />;
/**
* Renders a guidelines card with title, items, and icon

View File

@@ -117,13 +117,28 @@ export const loadExistingAnalysis = async (analysisId: number, website: string):
// Extract domain name for personalization
const extractedDomain = extractDomainName(website);
// Combine all analysis data into a comprehensive object
// Database structure: flat fields at top level
// Need to combine them into the format expected by UI
const comprehensiveAnalysis = {
...result.analysis.style_analysis,
guidelines: result.analysis.style_guidelines,
// Top-level style analysis fields from database
writing_style: result.analysis.writing_style,
content_characteristics: result.analysis.content_characteristics,
target_audience: result.analysis.target_audience,
content_type: result.analysis.content_type,
brand_analysis: result.analysis.brand_analysis,
content_strategy_insights: result.analysis.content_strategy_insights,
recommended_settings: result.analysis.recommended_settings,
// Extract guidelines from style_guidelines object
guidelines: result.analysis.style_guidelines?.guidelines,
best_practices: result.analysis.style_guidelines?.best_practices,
avoid_elements: result.analysis.style_guidelines?.avoid_elements,
content_strategy: result.analysis.style_guidelines?.content_strategy,
ai_generation_tips: result.analysis.style_guidelines?.ai_generation_tips,
competitive_advantages: result.analysis.style_guidelines?.competitive_advantages,
content_calendar_suggestions: result.analysis.style_guidelines?.content_calendar_suggestions,
// Style patterns
style_patterns: result.analysis.style_patterns,
style_consistency: result.analysis.style_patterns?.style_consistency,
unique_elements: result.analysis.style_patterns?.unique_elements
@@ -193,10 +208,13 @@ export const performAnalysis = async (
// Combine all analysis data into a comprehensive object
const comprehensiveAnalysis = {
...result.style_analysis,
guidelines: result.style_guidelines,
guidelines: result.style_guidelines?.guidelines,
best_practices: result.style_guidelines?.best_practices,
avoid_elements: result.style_guidelines?.avoid_elements,
content_strategy: result.style_guidelines?.content_strategy,
ai_generation_tips: result.style_guidelines?.ai_generation_tips,
competitive_advantages: result.style_guidelines?.competitive_advantages,
content_calendar_suggestions: result.style_guidelines?.content_calendar_suggestions,
style_patterns: result.style_patterns,
style_consistency: result.style_patterns?.style_consistency,
unique_elements: result.style_patterns?.unique_elements

View File

@@ -6,8 +6,9 @@ export const useOnboardingStyles = () => {
const styles = {
// Layout styles
container: {
maxWidth: 800,
mx: 'auto',
maxWidth: '100%', // Use full width for maximum data display
mx: 0, // Remove auto margins to use full width
px: { xs: 1, md: 2 }, // Minimal padding to maximize content area
},
// Header styles
@@ -241,17 +242,25 @@ export const useOnboardingStyles = () => {
analysisContainer: {
display: 'flex',
flexDirection: 'column',
gap: 2,
gap: 3,
width: '100%',
maxWidth: '100%', // Use full width for maximum data display
mx: 0, // Remove auto margins to use full width
px: { xs: 1, md: 2 }, // Minimal padding to maximize content area
},
analysisHeaderCard: {
mb: 2,
background: 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)',
borderRadius: 2,
boxShadow: '0 8px 32px rgba(0, 0, 0, 0.4)',
border: `1px solid rgba(255, 255, 255, 0.1)`,
backdropFilter: 'blur(20px)',
mb: 3,
background: theme.palette.mode === 'dark'
? 'linear-gradient(135deg, rgba(255, 255, 255, 0.05) 0%, rgba(255, 255, 255, 0.08) 100%)'
: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
borderRadius: 3,
boxShadow: theme.palette.mode === 'dark'
? '0 8px 32px rgba(0, 0, 0, 0.4)'
: '0 4px 20px rgba(0, 0, 0, 0.08)',
border: theme.palette.mode === 'dark'
? `1px solid rgba(255, 255, 255, 0.1)`
: `1px solid ${alpha(theme.palette.primary.main, 0.1)}`,
overflow: 'hidden',
},
@@ -274,12 +283,12 @@ export const useOnboardingStyles = () => {
analysisHeaderTitle: {
fontWeight: 700,
letterSpacing: '-0.025em',
color: theme.palette.text.primary,
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
fontSize: '1.5rem',
},
analysisHeaderSubtitle: {
color: theme.palette.text.secondary,
color: theme.palette.mode === 'dark' ? '#e2e8f0' : '#4a5568', // High contrast secondary text
fontSize: '0.95rem',
lineHeight: 1.5,
mt: 0.5,
@@ -292,11 +301,19 @@ export const useOnboardingStyles = () => {
analysisSectionHeader: {
display: 'flex',
alignItems: 'center',
gap: 1,
fontWeight: 600,
color: theme.palette.text.primary,
fontSize: '1.1rem',
mb: 1.5,
gap: 1.5,
fontWeight: 700,
color: theme.palette.mode === 'dark' ? '#ffffff' : '#1a202c', // High contrast dark text
fontSize: '1.25rem',
mb: 2,
pb: 1.5,
borderBottom: `3px solid ${theme.palette.mode === 'dark'
? alpha(theme.palette.primary.main, 0.4)
: alpha(theme.palette.primary.main, 0.25)}`, // More prominent border
'& .MuiSvgIcon-root': {
fontSize: '1.5rem',
color: theme.palette.mode === 'dark' ? theme.palette.primary.light : theme.palette.primary.main,
}
},
analysisSubheader: {