Files
ALwrity/frontend/src/components/OnboardingWizard/WebsiteStep.tsx
2025-08-06 12:48:02 +05:30

1363 lines
49 KiB
TypeScript

import React, { useState, useEffect } from 'react';
import {
Box,
Button,
TextField,
Typography,
Alert,
CircularProgress,
Card,
CardContent,
Grid,
Accordion,
AccordionSummary,
AccordionDetails,
LinearProgress,
Stepper,
Step,
StepLabel,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
DialogContentText,
Chip,
Divider,
Checkbox,
FormControlLabel,
Paper,
Fade,
Slide,
Zoom,
Tooltip,
IconButton
} from '@mui/material';
import {
ExpandMore as ExpandMoreIcon,
CheckCircle as CheckIcon,
Info as InfoIcon,
Language as LanguageIcon,
Web as WebIcon,
Analytics as AnalyticsIcon,
Psychology as PsychologyIcon,
TrendingUp as TrendingUpIcon,
History as HistoryIcon,
Star as StarIcon,
Warning as WarningIcon,
Lightbulb as LightbulbIcon,
Palette as PaletteIcon,
Speed as SpeedIcon,
Group as GroupIcon,
Business as BusinessIcon,
LocationOn as LocationIcon,
AutoAwesome as AutoAwesomeIcon,
Verified as VerifiedIcon,
Close as CloseIcon
} from '@mui/icons-material';
interface WebsiteStepProps {
onContinue: () => void;
updateHeaderContent: (content: { title: string; description: string }) => void;
}
interface StyleAnalysis {
writing_style?: {
tone: string;
voice: string;
complexity: string;
engagement_level: string;
brand_personality?: string;
formality_level?: string;
emotional_appeal?: string;
};
content_characteristics?: {
sentence_structure: string;
vocabulary_level: string;
paragraph_organization: string;
content_flow: string;
readability_score?: string;
content_density?: string;
visual_elements_usage?: string;
};
target_audience?: {
demographics: string[];
expertise_level: string;
industry_focus: string;
geographic_focus: string;
psychographic_profile?: string;
pain_points?: string[];
motivations?: string[];
};
content_type?: {
primary_type: string;
secondary_types: string[];
purpose: string;
call_to_action: string;
conversion_focus?: string;
educational_value?: string;
};
brand_analysis?: {
brand_voice: string;
brand_values: string[];
brand_positioning: string;
competitive_differentiation: string;
trust_signals: string[];
authority_indicators: string[];
};
content_strategy_insights?: {
strengths: string[];
weaknesses: string[];
opportunities: string[];
threats: string[];
recommended_improvements: string[];
content_gaps: string[];
};
recommended_settings?: {
writing_tone: string;
target_audience: string;
content_type: string;
creativity_level: string;
geographic_location: string;
industry_context?: string;
brand_alignment?: string;
};
// New comprehensive analysis fields
guidelines?: {
tone_recommendations: string[];
structure_guidelines: string[];
vocabulary_suggestions: string[];
engagement_tips: string[];
audience_considerations: string[];
brand_alignment?: string[];
seo_optimization?: string[];
conversion_optimization?: string[];
};
best_practices?: string[];
avoid_elements?: string[];
content_strategy?: string;
ai_generation_tips?: string[];
competitive_advantages?: string[];
content_calendar_suggestions?: string[];
style_patterns?: {
sentence_length: string;
vocabulary_patterns: string[];
rhetorical_devices: string[];
paragraph_structure: string;
transition_phrases: string[];
};
style_consistency?: string;
unique_elements?: string[];
}
interface AnalysisProgress {
step: number;
message: string;
completed: boolean;
}
interface ExistingAnalysis {
exists: boolean;
analysis_date?: string;
analysis_id?: number;
summary?: {
writing_style?: any;
target_audience?: any;
content_type?: any;
};
error?: string;
}
const WebsiteStep: React.FC<WebsiteStepProps> = ({ onContinue, updateHeaderContent }) => {
const [website, setWebsite] = useState('');
const [error, setError] = useState<string | null>(null);
const [loading, setLoading] = useState(false);
const [success, setSuccess] = useState<string | null>(null);
const [analysis, setAnalysis] = useState<StyleAnalysis | null>(null);
const [existingAnalysis, setExistingAnalysis] = useState<ExistingAnalysis | null>(null);
const [showConfirmationDialog, setShowConfirmationDialog] = useState(false);
const [useAnalysisForGenAI, setUseAnalysisForGenAI] = useState(true);
const [domainName, setDomainName] = useState<string>('');
const [hasCheckedExisting, setHasCheckedExisting] = useState(false);
const [progress, setProgress] = useState<AnalysisProgress[]>([
{ step: 1, message: 'Validating website URL', completed: false },
{ step: 2, message: 'Crawling website content', completed: false },
{ step: 3, message: 'Extracting content structure', completed: false },
{ step: 4, message: 'Analyzing writing style', completed: false },
{ step: 5, message: 'Identifying content characteristics', completed: false },
{ step: 6, message: 'Determining target audience', completed: false },
{ step: 7, message: 'Generating recommendations', completed: false }
]);
useEffect(() => {
// Update header content when component mounts
updateHeaderContent({
title: 'Analyze Your Website',
description: 'Let Alwrity analyze your website to understand your brand voice, writing style, and content characteristics. This helps us generate content that matches your existing tone and resonates with your audience.'
});
}, [updateHeaderContent]);
useEffect(() => {
// Prefill from last session analysis on mount
const fetchLastAnalysis = async () => {
try {
const res = await fetch('/api/style-detection/session-analyses');
const data = await res.json();
if (data.success && Array.isArray(data.analyses) && data.analyses.length > 0) {
// Pick the most recent analysis (assuming sorted by date desc, else sort here)
const last = data.analyses[0];
if (last && last.website_url) {
setWebsite(last.website_url);
}
if (last && last.style_analysis) {
setAnalysis(last.style_analysis);
}
}
} catch (err) {
console.error('WebsiteStep: Error pre-filling from last analysis', err);
}
};
fetchLastAnalysis();
}, []);
// Reset existing analysis check when URL changes significantly
useEffect(() => {
if (website.trim()) {
setHasCheckedExisting(false);
setExistingAnalysis(null);
setShowConfirmationDialog(false);
}
}, [website]);
// Check for existing analysis when URL changes
useEffect(() => {
if (website.trim() && !hasCheckedExisting) {
const checkExisting = async () => {
const fixedUrl = fixUrlFormat(website);
if (fixedUrl) {
console.log('WebsiteStep: Checking for existing analysis for URL:', fixedUrl);
const hasExisting = await checkExistingAnalysis(fixedUrl);
if (hasExisting) {
console.log('WebsiteStep: Found existing analysis, showing confirmation dialog');
setShowConfirmationDialog(true);
}
setHasCheckedExisting(true);
}
};
// Debounce the check to avoid too many API calls
const timeoutId = setTimeout(checkExisting, 1000);
return () => clearTimeout(timeoutId);
}
}, [website, hasCheckedExisting]);
const checkExistingAnalysis = async (url: string) => {
try {
console.log('WebsiteStep: Checking existing analysis for URL:', url);
const response = await fetch(`/api/onboarding/style-detection/check-existing/${encodeURIComponent(url)}`);
const result = await response.json();
if (result.exists) {
console.log('WebsiteStep: Existing analysis found:', result);
setExistingAnalysis(result);
return true;
} else {
console.log('WebsiteStep: No existing analysis found');
setExistingAnalysis(null);
return false;
}
} catch (error) {
console.error('WebsiteStep: Error checking existing analysis:', error);
setExistingAnalysis(null);
return false;
}
};
const loadExistingAnalysis = async (analysisId: number) => {
try {
const response = await fetch(`/api/onboarding/style-detection/analysis/${analysisId}`);
const result = await response.json();
if (result.success && result.analysis) {
// Extract domain name for personalization
const extractedDomain = extractDomainName(website);
setDomainName(extractedDomain);
// Combine all analysis data into a comprehensive object
const comprehensiveAnalysis = {
...result.analysis.style_analysis,
guidelines: result.analysis.style_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,
style_patterns: result.analysis.style_patterns,
style_consistency: result.analysis.style_patterns?.style_consistency,
unique_elements: result.analysis.style_patterns?.unique_elements
};
setAnalysis(comprehensiveAnalysis);
setSuccess('Loaded previous analysis successfully!');
return true;
}
return false;
} catch (error) {
console.error('Error loading existing analysis:', error);
return false;
}
};
const handleAnalyze = async () => {
setError(null);
setSuccess(null);
setLoading(true);
setAnalysis(null);
// Reset progress
setProgress(prev => prev.map(p => ({ ...p, completed: false })));
try {
// Validate and fix URL format
const fixedUrl = fixUrlFormat(website);
if (!fixedUrl) {
setError('Please enter a valid website URL (starting with http:// or https://)');
setLoading(false);
return;
}
// Check for existing analysis
const hasExisting = await checkExistingAnalysis(fixedUrl);
if (hasExisting && existingAnalysis) {
setShowConfirmationDialog(true);
setLoading(false);
return;
}
// Proceed with new analysis
await performAnalysis(fixedUrl);
} catch (err) {
console.error('Analysis error:', err);
setError('Failed to analyze website. Please check your internet connection and try again.');
} finally {
setLoading(false);
}
};
const performAnalysis = async (fixedUrl: string) => {
// Simulate progress updates
const updateProgress = (step: number, message: string) => {
setProgress(prev => prev.map(p =>
p.step === step ? { ...p, message, completed: true } : p
));
};
updateProgress(1, 'Website URL validated');
const requestData = {
url: fixedUrl,
include_patterns: true,
include_guidelines: true
};
updateProgress(2, 'Starting content crawl...');
const response = await fetch('/api/onboarding/style-detection/complete', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify(requestData),
});
updateProgress(3, 'Content extracted successfully');
updateProgress(4, 'Style analysis in progress...');
updateProgress(5, 'Content characteristics analyzed');
updateProgress(6, 'Target audience identified');
updateProgress(7, 'Recommendations generated');
const result = await response.json();
if (result.success) {
// Extract domain name for personalization
const extractedDomain = extractDomainName(fixedUrl);
setDomainName(extractedDomain);
// Combine all analysis data into a comprehensive object
const comprehensiveAnalysis = {
...result.style_analysis,
guidelines: result.style_guidelines,
best_practices: result.style_guidelines?.best_practices,
avoid_elements: result.style_guidelines?.avoid_elements,
content_strategy: result.style_guidelines?.content_strategy,
style_patterns: result.style_patterns,
style_consistency: result.style_patterns?.style_consistency,
unique_elements: result.style_patterns?.unique_elements
};
setAnalysis(comprehensiveAnalysis);
// Check if there's a warning about fallback data
if (result.warning) {
setSuccess(`Website style analysis completed successfully! Note: ${result.warning}`);
} else {
setSuccess('Website style analysis completed successfully!');
}
} else {
// Handle specific error cases
let errorMessage = result.error || 'Analysis failed';
if (errorMessage.includes('API key') || errorMessage.includes('configure')) {
errorMessage = 'API keys not configured. Please complete step 1 of onboarding to configure your AI provider API keys.';
} else if (errorMessage.includes('library not available')) {
errorMessage = 'AI provider library not available. Please ensure your AI provider is properly configured in step 1.';
} else if (errorMessage.includes('crawl') || errorMessage.includes('website')) {
errorMessage = 'Unable to access the website. Please check the URL and ensure the website is publicly accessible.';
}
setError(errorMessage);
}
};
const handleLoadExisting = async () => {
if (existingAnalysis?.analysis_id) {
setLoading(true);
const success = await loadExistingAnalysis(existingAnalysis.analysis_id);
if (!success) {
setError('Failed to load existing analysis. Please try a new analysis.');
}
setLoading(false);
}
setShowConfirmationDialog(false);
};
const handleNewAnalysis = async () => {
setShowConfirmationDialog(false);
setExistingAnalysis(null);
if (website) {
const fixedUrl = fixUrlFormat(website);
if (fixedUrl) {
setLoading(true);
await performAnalysis(fixedUrl);
setLoading(false);
}
}
};
const fixUrlFormat = (url: string): string | null => {
if (!url) return null;
// Remove leading/trailing whitespace
let fixedUrl = url.trim();
// Check if URL already has a protocol but is missing slashes
if (fixedUrl.startsWith('https:/') && !fixedUrl.startsWith('https://')) {
fixedUrl = fixedUrl.replace('https:/', 'https://');
} else if (fixedUrl.startsWith('http:/') && !fixedUrl.startsWith('http://')) {
fixedUrl = fixedUrl.replace('http:/', 'http://');
}
// Add protocol if missing
if (!fixedUrl.startsWith('http://') && !fixedUrl.startsWith('https://')) {
fixedUrl = 'https://' + fixedUrl;
}
// Fix missing slash after protocol
if (fixedUrl.includes('://') && !fixedUrl.split('://')[1].startsWith('/')) {
fixedUrl = fixedUrl.replace('://', ':///');
}
// Ensure only two slashes after protocol
if (fixedUrl.includes(':///')) {
fixedUrl = fixedUrl.replace(':///', '://');
}
// Basic URL validation
try {
new URL(fixedUrl);
return fixedUrl;
} catch {
return null;
}
};
const handleContinue = () => {
setError(null);
const fixedUrl = fixUrlFormat(website);
if (!fixedUrl) {
setError('Please enter a valid website URL (starting with http:// or https://)');
return;
}
onContinue();
};
const renderAnalysisSection = (title: string, data: any, icon: React.ReactNode, description?: string) => (
<Accordion key={title} sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
{icon}
<Typography variant="h6">{title}</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
{description && (
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
{description}
</Typography>
)}
<Grid container spacing={2}>
{Object.entries(data).map(([key, value]) => (
<Grid item xs={12} md={6} key={key}>
<Typography variant="subtitle2" color="primary" gutterBottom>
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:
</Typography>
<Typography variant="body2">
{Array.isArray(value) ? value.join(', ') : String(value)}
</Typography>
</Grid>
))}
</Grid>
</AccordionDetails>
</Accordion>
);
const renderGuidelinesSection = (guidelines: any) => (
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
<PsychologyIcon color="primary" />
<Typography variant="h6">Content Guidelines</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Personalized recommendations for improving your content creation based on your writing style analysis.
</Typography>
{guidelines.tone_recommendations && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle1" color="primary" gutterBottom>
Tone Recommendations
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{guidelines.tone_recommendations.map((rec: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{rec}
</Typography>
))}
</Box>
</Box>
)}
{guidelines.structure_guidelines && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle1" color="primary" gutterBottom>
Structure Guidelines
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{guidelines.structure_guidelines.map((guideline: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{guideline}
</Typography>
))}
</Box>
</Box>
)}
{guidelines.vocabulary_suggestions && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle1" color="primary" gutterBottom>
Vocabulary Suggestions
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{guidelines.vocabulary_suggestions.map((suggestion: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{suggestion}
</Typography>
))}
</Box>
</Box>
)}
{guidelines.engagement_tips && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle1" color="primary" gutterBottom>
Engagement Tips
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{guidelines.engagement_tips.map((tip: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{tip}
</Typography>
))}
</Box>
</Box>
)}
{guidelines.audience_considerations && (
<Box sx={{ mb: 3 }}>
<Typography variant="subtitle1" color="primary" gutterBottom>
Audience Considerations
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{guidelines.audience_considerations.map((consideration: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{consideration}
</Typography>
))}
</Box>
</Box>
)}
</AccordionDetails>
</Accordion>
);
const renderBestPracticesSection = (bestPractices: string[]) => (
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
<CheckIcon color="success" />
<Typography variant="h6">Best Practices</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Recommended practices to enhance your content quality and effectiveness.
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{bestPractices.map((practice: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{practice}
</Typography>
))}
</Box>
</AccordionDetails>
</Accordion>
);
const renderAvoidElementsSection = (avoidElements: string[]) => (
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
<InfoIcon color="warning" />
<Typography variant="h6">Elements to Avoid</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Elements that may detract from your content's effectiveness based on your writing style.
</Typography>
<Box component="ul" sx={{ pl: 2 }}>
{avoidElements.map((element: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{element}
</Typography>
))}
</Box>
</AccordionDetails>
</Accordion>
);
const renderContentStrategySection = (contentStrategy: string) => (
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
<TrendingUpIcon color="info" />
<Typography variant="h6">Content Strategy</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Overall content strategy recommendation based on your writing style analysis.
</Typography>
<Typography variant="body1" sx={{ lineHeight: 1.6 }}>
{contentStrategy}
</Typography>
</AccordionDetails>
</Accordion>
);
const renderStylePatternsSection = (patterns: any) => (
<Accordion sx={{ mb: 2 }}>
<AccordionSummary expandIcon={<ExpandMoreIcon />}>
<Box display="flex" alignItems="center" gap={1}>
<AnalyticsIcon color="secondary" />
<Typography variant="h6">Style Patterns</Typography>
</Box>
</AccordionSummary>
<AccordionDetails>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
Recurring patterns and characteristics identified in your writing style.
</Typography>
<Grid container spacing={2}>
{Object.entries(patterns).map(([key, value]) => (
<Grid item xs={12} md={6} key={key}>
<Typography variant="subtitle2" color="primary" gutterBottom>
{key.replace(/_/g, ' ').replace(/\b\w/g, l => l.toUpperCase())}:
</Typography>
<Typography variant="body2">
{Array.isArray(value) ? value.join(', ') : String(value)}
</Typography>
</Grid>
))}
</Grid>
</AccordionDetails>
</Accordion>
);
const getProgressPercentage = () => {
const completedSteps = progress.filter(p => p.completed).length;
return (completedSteps / progress.length) * 100;
};
const extractDomainName = (url: string): string => {
try {
const domain = new URL(url).hostname.replace('www.', '');
return domain.charAt(0).toUpperCase() + domain.slice(1);
} catch {
return 'Your Website';
}
};
const renderKeyInsight = (title: string, value: string | string[], icon: React.ReactNode, color: string = 'primary') => (
<Fade in timeout={800}>
<Paper elevation={2} sx={{ p: 2, mb: 2, borderLeft: `4px solid ${color}.main` }}>
<Box display="flex" alignItems="center" gap={2}>
<Box sx={{ color: `${color}.main` }}>
{icon}
</Box>
<Box flex={1}>
<Typography variant="subtitle2" color="textSecondary" gutterBottom>
{title}
</Typography>
<Typography variant="body1" fontWeight={500}>
{Array.isArray(value) ? value.join(', ') : value}
</Typography>
</Box>
</Box>
</Paper>
</Fade>
);
const renderGuidelinesCard = (title: string, items: string[], icon: React.ReactNode, color: string = 'primary') => (
<Zoom in timeout={600}>
<Card sx={{ mb: 2, border: `1px solid ${color}.light` }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<Box sx={{ color: `${color}.main` }}>
{icon}
</Box>
<Typography variant="h6" fontWeight={600}>
{title}
</Typography>
</Box>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{items.map((item, index) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1, lineHeight: 1.6 }}>
{item}
</Typography>
))}
</Box>
</CardContent>
</Card>
</Zoom>
);
const renderProUpgradeAlert = () => (
<Slide direction="up" in timeout={1000}>
<Alert
severity="info"
sx={{
mb: 3,
background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)',
color: 'white',
'& .MuiAlert-icon': { color: 'white' }
}}
action={
<Button color="inherit" size="small" variant="outlined" sx={{ color: 'white', borderColor: 'white' }}>
Learn More
</Button>
}
>
<Typography variant="subtitle2" gutterBottom>
<StarIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Limited Analysis Scope
</Typography>
<Typography variant="body2">
This analysis is based on your homepage only. <strong>ALwrity Pro</strong> can index your entire website and social media content for comprehensive personalized content generation.
</Typography>
</Alert>
</Slide>
);
const renderBrandAnalysisSection = (brandAnalysis: any) => (
<Zoom in timeout={700}>
<Card sx={{ mb: 2, border: '2px solid info.light', background: 'info.50' }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<BusinessIcon color="info" />
<Typography variant="h6" fontWeight={600} color="info.main">
Brand Analysis
</Typography>
</Box>
<Grid container spacing={2}>
{brandAnalysis.brand_voice && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" color="primary" gutterBottom>
Brand Voice:
</Typography>
<Typography variant="body2" sx={{ mb: 2 }}>
{brandAnalysis.brand_voice}
</Typography>
</Grid>
)}
{brandAnalysis.brand_positioning && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" color="primary" gutterBottom>
Brand Positioning:
</Typography>
<Typography variant="body2" sx={{ mb: 2 }}>
{brandAnalysis.brand_positioning}
</Typography>
</Grid>
)}
{brandAnalysis.brand_values && brandAnalysis.brand_values.length > 0 && (
<Grid item xs={12}>
<Typography variant="subtitle2" color="primary" gutterBottom>
Brand Values:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{brandAnalysis.brand_values.map((value: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{value}
</Typography>
))}
</Box>
</Grid>
)}
</Grid>
</CardContent>
</Card>
</Zoom>
);
const renderContentStrategyInsightsSection = (insights: any) => (
<Zoom in timeout={800}>
<Card sx={{ mb: 2, border: '2px solid secondary.light', background: 'secondary.50' }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<AnalyticsIcon color="secondary" />
<Typography variant="h6" fontWeight={600} color="secondary.main">
Content Strategy Insights
</Typography>
</Box>
<Grid container spacing={3}>
{insights.strengths && insights.strengths.length > 0 && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" color="success.main" gutterBottom>
✅ Strengths:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{insights.strengths.map((strength: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{strength}
</Typography>
))}
</Box>
</Grid>
)}
{insights.opportunities && insights.opportunities.length > 0 && (
<Grid item xs={12} md={6}>
<Typography variant="subtitle2" color="info.main" gutterBottom>
🎯 Opportunities:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{insights.opportunities.map((opportunity: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{opportunity}
</Typography>
))}
</Box>
</Grid>
)}
{insights.recommended_improvements && insights.recommended_improvements.length > 0 && (
<Grid item xs={12}>
<Typography variant="subtitle2" color="primary" gutterBottom>
🔧 Recommended Improvements:
</Typography>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{insights.recommended_improvements.map((improvement: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1 }}>
{improvement}
</Typography>
))}
</Box>
</Grid>
)}
</Grid>
</CardContent>
</Card>
</Zoom>
);
const renderAIGenerationTipsSection = (tips: string[]) => (
<Zoom in timeout={900}>
<Card sx={{ mb: 2, border: '2px solid primary.light', background: 'primary.50' }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<AutoAwesomeIcon color="primary" />
<Typography variant="h6" fontWeight={600} color="primary.main">
AI Content Generation Tips
</Typography>
</Box>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{tips.map((tip: string, index: number) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1, lineHeight: 1.6 }}>
{tip}
</Typography>
))}
</Box>
</CardContent>
</Card>
</Zoom>
);
return (
<Box sx={{ maxWidth: 900, mx: 'auto', p: 3 }}>
{/* Enhanced Explanatory Text */}
<Box sx={{ mb: 4, textAlign: 'center' }}>
<Typography variant="h6" color="text.secondary" sx={{
mb: 3,
lineHeight: 1.6,
maxWidth: 800,
mx: 'auto',
fontWeight: 500,
opacity: 0.8
}}>
Provide your website URL to enable comprehensive content analysis and style detection.
We'll analyze your content to understand your writing style, target audience, and provide personalized recommendations for better content creation.
</Typography>
</Box>
{/* API Key Configuration Notice */}
<Alert severity="info" sx={{ mb: 3 }}>
<Typography variant="body2">
<strong>Note:</strong> To perform accurate style analysis, you need to configure AI provider API keys in step 1.
If you haven't completed step 1 yet, please go back and configure your API keys for the best experience.
</Typography>
</Alert>
<Card sx={{ mb: 3, p: 3 }}>
<Grid container spacing={2} alignItems="center">
<Grid item xs={12} md={8}>
<TextField
label="Website URL"
value={website}
onChange={e => setWebsite(e.target.value)}
fullWidth
placeholder="https://yourwebsite.com"
disabled={loading}
/>
</Grid>
<Grid item xs={12} md={4}>
<Button
variant="contained"
color="primary"
onClick={handleAnalyze}
disabled={!website || loading}
fullWidth
startIcon={loading ? <CircularProgress size={20} /> : <AnalyticsIcon />}
>
{loading ? 'Analyzing...' : 'Analyze Content Style'}
</Button>
</Grid>
</Grid>
</Card>
{loading && (
<Card sx={{ mb: 3, p: 3 }}>
<Typography variant="h6" gutterBottom>
<AnalyticsIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Analysis Progress
</Typography>
<LinearProgress
variant="determinate"
value={getProgressPercentage()}
sx={{ mb: 2 }}
/>
<Typography variant="body2" color="textSecondary" sx={{ mb: 2 }}>
{Math.round(getProgressPercentage())}% Complete
</Typography>
<Stepper orientation="vertical" activeStep={progress.filter(p => p.completed).length}>
{progress.map((step) => (
<Step key={step.step} completed={step.completed}>
<StepLabel>
<Typography variant="body2">
{step.message}
</Typography>
</StepLabel>
</Step>
))}
</Stepper>
</Card>
)}
{error && (
<Alert severity="error" sx={{ mb: 3 }}>
{error}
</Alert>
)}
{success && (
<Alert severity="success" sx={{ mb: 3 }}>
{success}
</Alert>
)}
{analysis && (
<Fade in timeout={800}>
<Box>
{/* Pro Upgrade Alert */}
{renderProUpgradeAlert()}
{/* Main Analysis Results */}
<Card sx={{ mb: 3, background: 'linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%)' }}>
<CardContent sx={{ p: 4 }}>
<Box display="flex" alignItems="center" gap={2} mb={3}>
<VerifiedIcon sx={{ color: 'success.main', fontSize: 32 }} />
<Box>
<Typography variant="h4" fontWeight={700} gutterBottom>
{domainName} Style Analysis
</Typography>
<Typography variant="body1" color="textSecondary">
Comprehensive content analysis and personalized recommendations
</Typography>
</Box>
</Box>
{/* Key Insights Grid */}
<Grid container spacing={3} mb={4}>
{analysis.writing_style?.tone && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Writing Tone',
analysis.writing_style.tone,
<PaletteIcon />,
'primary'
)}
</Grid>
)}
{analysis.writing_style?.complexity && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Content Complexity',
analysis.writing_style.complexity,
<SpeedIcon />,
'secondary'
)}
</Grid>
)}
{analysis.target_audience?.expertise_level && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Target Audience',
analysis.target_audience.expertise_level,
<GroupIcon />,
'info'
)}
</Grid>
)}
{analysis.content_type?.primary_type && (
<Grid item xs={12} md={6}>
{renderKeyInsight(
'Content Type',
analysis.content_type.primary_type,
<BusinessIcon />,
'warning'
)}
</Grid>
)}
</Grid>
<Divider sx={{ my: 3 }} />
{/* Content Strategy */}
{analysis.content_strategy && (
<Box mb={4}>
<Typography variant="h5" fontWeight={600} gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<AutoAwesomeIcon color="primary" />
Content Strategy
</Typography>
<Paper elevation={3} sx={{ p: 3, background: 'linear-gradient(135deg, #667eea 0%, #764ba2 100%)', color: 'white' }}>
<Typography variant="body1" sx={{ lineHeight: 1.8, fontSize: '1.1rem' }}>
{analysis.content_strategy}
</Typography>
</Paper>
</Box>
)}
{/* Brand Analysis */}
{analysis.brand_analysis && renderBrandAnalysisSection(analysis.brand_analysis)}
{/* Content Strategy Insights */}
{analysis.content_strategy_insights && renderContentStrategyInsightsSection(analysis.content_strategy_insights)}
{/* AI Generation Tips */}
{analysis.ai_generation_tips && renderAIGenerationTipsSection(analysis.ai_generation_tips)}
{/* Enhanced Guidelines Section */}
{analysis.guidelines && (
<Box mb={4}>
<Typography variant="h5" fontWeight={600} gutterBottom sx={{ display: 'flex', alignItems: 'center', gap: 1 }}>
<LightbulbIcon color="primary" />
Enhanced Content Guidelines for {domainName}
</Typography>
<Grid container spacing={3}>
{analysis.guidelines.tone_recommendations && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Tone Recommendations',
analysis.guidelines.tone_recommendations,
<PsychologyIcon />,
'primary'
)}
</Grid>
)}
{analysis.guidelines.structure_guidelines && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Structure Guidelines',
analysis.guidelines.structure_guidelines,
<AnalyticsIcon />,
'secondary'
)}
</Grid>
)}
{analysis.guidelines.engagement_tips && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Engagement Tips',
analysis.guidelines.engagement_tips,
<TrendingUpIcon />,
'success'
)}
</Grid>
)}
{analysis.guidelines.vocabulary_suggestions && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Vocabulary Suggestions',
analysis.guidelines.vocabulary_suggestions,
<LanguageIcon />,
'info'
)}
</Grid>
)}
{analysis.guidelines.brand_alignment && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Brand Alignment',
analysis.guidelines.brand_alignment,
<BusinessIcon />,
'warning'
)}
</Grid>
)}
{analysis.guidelines.seo_optimization && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'SEO Optimization',
analysis.guidelines.seo_optimization,
<WebIcon />,
'primary'
)}
</Grid>
)}
{analysis.guidelines.conversion_optimization && (
<Grid item xs={12} md={6}>
{renderGuidelinesCard(
'Conversion Optimization',
analysis.guidelines.conversion_optimization,
<TrendingUpIcon />,
'success'
)}
</Grid>
)}
</Grid>
</Box>
)}
{/* Best Practices & Avoid Elements */}
<Grid container spacing={3} mb={4}>
{analysis.best_practices && (
<Grid item xs={12} md={6}>
<Zoom in timeout={800}>
<Card sx={{ border: '2px solid success.light', background: 'success.50' }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<CheckIcon color="success" />
<Typography variant="h6" fontWeight={600} color="success.main">
Best Practices
</Typography>
</Box>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{analysis.best_practices.map((practice, index) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1, lineHeight: 1.6 }}>
{practice}
</Typography>
))}
</Box>
</CardContent>
</Card>
</Zoom>
</Grid>
)}
{analysis.avoid_elements && (
<Grid item xs={12} md={6}>
<Zoom in timeout={1000}>
<Card sx={{ border: '2px solid warning.light', background: 'warning.50' }}>
<CardContent>
<Box display="flex" alignItems="center" gap={1} mb={2}>
<WarningIcon color="warning" />
<Typography variant="h6" fontWeight={600} color="warning.main">
Elements to Avoid
</Typography>
</Box>
<Box component="ul" sx={{ pl: 2, m: 0 }}>
{analysis.avoid_elements.map((element, index) => (
<Typography component="li" variant="body2" key={index} sx={{ mb: 1, lineHeight: 1.6 }}>
{element}
</Typography>
))}
</Box>
</CardContent>
</Card>
</Zoom>
</Grid>
)}
</Grid>
{/* GenAI Integration Checkbox */}
<Box sx={{
p: 3,
bgcolor: 'primary.50',
borderRadius: 2,
border: '2px solid primary.light',
mb: 3
}}>
<FormControlLabel
control={
<Checkbox
checked={useAnalysisForGenAI}
onChange={(e) => setUseAnalysisForGenAI(e.target.checked)}
color="primary"
size="large"
/>
}
label={
<Box>
<Typography variant="h6" fontWeight={600} gutterBottom>
Use Analysis for AI Content Generation
</Typography>
<Typography variant="body2" color="textSecondary">
Apply this style analysis to personalize AI-generated content, ensuring it matches {domainName}'s voice and tone.
</Typography>
</Box>
}
/>
</Box>
{/* Success Message */}
<Alert severity="success" sx={{ mb: 0 }}>
<Typography variant="body1" fontWeight={500}>
Analysis complete! Your content style has been analyzed and personalized recommendations are ready.
</Typography>
</Alert>
</CardContent>
</Card>
</Box>
</Fade>
)}
{/* Confirmation Dialog for Existing Analysis */}
<Dialog
open={showConfirmationDialog}
onClose={() => setShowConfirmationDialog(false)}
maxWidth="sm"
fullWidth
>
<DialogTitle>
<Box display="flex" alignItems="center" gap={1}>
<HistoryIcon color="primary" />
Previous Analysis Found
</Box>
</DialogTitle>
<DialogContent>
<DialogContentText>
We found a previous analysis for this website from{' '}
{existingAnalysis?.analysis_date ?
new Date(existingAnalysis.analysis_date).toLocaleDateString() :
'a previous session'
}.
</DialogContentText>
<DialogContentText sx={{ mt: 2 }}>
Would you like to load the previous analysis or perform a new one?
</DialogContentText>
{existingAnalysis?.summary && (
<Box sx={{ mt: 2, p: 2, bgcolor: 'grey.50', borderRadius: 1 }}>
<Typography variant="subtitle2" gutterBottom>
Previous Analysis Summary:
</Typography>
{existingAnalysis.summary.writing_style?.tone && (
<Typography variant="body2" color="textSecondary">
Tone: {existingAnalysis.summary.writing_style.tone}
</Typography>
)}
{existingAnalysis.summary.target_audience?.expertise_level && (
<Typography variant="body2" color="textSecondary">
Target Audience: {existingAnalysis.summary.target_audience.expertise_level}
</Typography>
)}
{existingAnalysis.summary.content_type?.primary_type && (
<Typography variant="body2" color="textSecondary">
Content Type: {existingAnalysis.summary.content_type.primary_type}
</Typography>
)}
</Box>
)}
</DialogContent>
<DialogActions>
<Button onClick={() => setShowConfirmationDialog(false)}>
Cancel
</Button>
<Button onClick={handleLoadExisting} variant="outlined" startIcon={<HistoryIcon />}>
Load Previous
</Button>
<Button onClick={handleNewAnalysis} variant="contained" startIcon={<AnalyticsIcon />}>
New Analysis
</Button>
</DialogActions>
</Dialog>
</Box>
);
};
export default WebsiteStep;