ALwrity + Wordpress + Wix + GSC integration

This commit is contained in:
ajaysi
2025-10-08 10:13:14 +05:30
parent 14dfb2e5c0
commit 3bab3450dc
147 changed files with 19815 additions and 17053 deletions

View File

@@ -0,0 +1,350 @@
import React, { useState } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Button,
Grid,
Chip,
Dialog,
DialogTitle,
DialogContent,
DialogActions,
List,
ListItem,
ListItemIcon,
ListItemText,
Divider,
Alert,
LinearProgress
} from '@mui/material';
import {
AutoAwesome as AutoAwesomeIcon,
ContentPaste as ContentIcon,
Psychology as PsychologyIcon,
TrendingUp as TrendingIcon,
Security as SecurityIcon,
Speed as SpeedIcon,
CheckCircle as CheckIcon,
Schedule as ScheduleIcon,
Rocket as RocketIcon,
DataUsage as DataIcon,
Tune as TuneIcon,
SmartToy as SmartToyIcon
} from '@mui/icons-material';
interface ComingSoonSectionProps {
contentCalendar?: any[];
}
export const ComingSoonSection: React.FC<ComingSoonSectionProps> = ({
contentCalendar = []
}) => {
const [openModal, setOpenModal] = useState(false);
const [selectedFeature, setSelectedFeature] = useState<string | null>(null);
const features = [
{
id: 'test-persona',
title: 'Test Your Persona',
description: 'Generate content with different personas to see the difference',
icon: <PsychologyIcon />,
status: 'Coming Soon',
color: '#3b82f6',
details: [
'Compare content generated with and without your persona',
'Test Core, Blog, and LinkedIn personas side-by-side',
'Choose from your content calendar topics',
'Provide feedback to improve your persona',
'AI model settings automatically optimized per persona'
]
},
{
id: 'deep-crawl',
title: 'Deep Website Analysis',
description: 'Crawl 10+ pages for comprehensive persona generation',
icon: <DataIcon />,
status: 'In Development',
color: '#10b981',
details: [
'Analyze multiple blog posts and pages',
'Extract comprehensive writing patterns',
'Understand content themes and topics',
'Generate more accurate personas',
'Better brand voice detection'
]
},
{
id: 'fine-tuning',
title: 'Personal AI Fine-Tuning',
description: 'Train a custom AI model specifically for your brand',
icon: <SmartToyIcon />,
status: 'Planned',
color: '#8b5cf6',
details: [
'Fine-tune Google Gemma model with your data',
'Create your personal AI marketing team',
'Learn from your website, social media, and analytics',
'Generate content that sounds authentically like you',
'Private model - your data stays secure'
]
}
];
const handleFeatureClick = (featureId: string) => {
setSelectedFeature(featureId);
setOpenModal(true);
};
const selectedFeatureData = features.find(f => f.id === selectedFeature);
return (
<>
<Box sx={{ mt: 4, mb: 2 }}>
<Typography variant="h4" sx={{ fontWeight: 700, color: '#1e293b', mb: 1.5 }}>
🚀 Coming Soon
</Typography>
<Typography variant="body1" sx={{ color: '#64748b', mb: 4, fontSize: '1.1rem' }}>
Exciting features in development to make your AI writing even more powerful
</Typography>
<Grid container spacing={2}>
{features.map((feature) => (
<Grid item xs={12} md={4} key={feature.id}>
<Card
sx={{
height: '100%',
cursor: 'pointer',
transition: 'all 0.3s ease',
border: '2px solid #e2e8f0',
backgroundColor: '#ffffff',
'&:hover': {
transform: 'translateY(-4px)',
boxShadow: '0 12px 30px rgba(0, 0, 0, 0.15)',
borderColor: feature.color,
'& .feature-icon': {
transform: 'scale(1.1)',
backgroundColor: `${feature.color}20`
}
}
}}
onClick={() => handleFeatureClick(feature.id)}
>
<CardContent sx={{ p: 3 }}>
<Box sx={{ display: 'flex', alignItems: 'center', mb: 2 }}>
<Box
className="feature-icon"
sx={{
p: 2,
borderRadius: 3,
backgroundColor: `${feature.color}15`,
color: feature.color,
mr: 2,
transition: 'all 0.3s ease',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
{feature.icon}
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 700, color: '#1e293b', mb: 1 }}>
{feature.title}
</Typography>
<Chip
label={feature.status}
size="small"
sx={{
backgroundColor: `${feature.color}20`,
color: feature.color,
fontWeight: 600,
fontSize: '0.75rem',
height: 24,
'& .MuiChip-label': {
px: 1.5
}
}}
/>
</Box>
</Box>
<Typography variant="body1" sx={{ color: '#64748b', mb: 3, lineHeight: 1.6 }}>
{feature.description}
</Typography>
<Button
variant="outlined"
size="medium"
sx={{
borderColor: feature.color,
color: feature.color,
fontWeight: 600,
px: 3,
py: 1,
borderRadius: 2,
textTransform: 'none',
'&:hover': {
backgroundColor: `${feature.color}15`,
borderColor: feature.color,
transform: 'translateY(-1px)'
}
}}
>
Learn More
</Button>
</CardContent>
</Card>
</Grid>
))}
</Grid>
<Alert
severity="info"
sx={{
mt: 4,
backgroundColor: '#f0f9ff',
border: '2px solid #0ea5e9',
borderRadius: 3,
'& .MuiAlert-icon': {
color: '#0ea5e9',
fontSize: '1.5rem'
}
}}
>
<Typography variant="body1" sx={{ color: '#0c4a6e', fontWeight: 500 }}>
<strong>What's Next:</strong> These features will be available in upcoming releases.
Your current persona is already powerful and ready to use!
</Typography>
</Alert>
</Box>
{/* Feature Details Modal */}
<Dialog
open={openModal}
onClose={() => setOpenModal(false)}
maxWidth="md"
fullWidth
>
<DialogTitle sx={{ pb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3 }}>
{selectedFeatureData && (
<>
<Box
sx={{
p: 2,
borderRadius: 3,
backgroundColor: `${selectedFeatureData.color}20`,
color: selectedFeatureData.color,
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
{selectedFeatureData.icon}
</Box>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, color: '#1e293b', mb: 1 }}>
{selectedFeatureData.title}
</Typography>
<Chip
label={selectedFeatureData.status}
size="medium"
sx={{
backgroundColor: `${selectedFeatureData.color}20`,
color: selectedFeatureData.color,
fontWeight: 600,
fontSize: '0.875rem'
}}
/>
</Box>
</>
)}
</Box>
</DialogTitle>
<DialogContent>
{selectedFeatureData && (
<>
<Typography variant="body1" sx={{ color: '#64748b', mb: 4, fontSize: '1.1rem', lineHeight: 1.6 }}>
{selectedFeatureData.description}
</Typography>
<Typography variant="h6" sx={{ fontWeight: 700, mb: 3, color: '#1e293b' }}>
Key Features:
</Typography>
<List sx={{ pl: 0 }}>
{selectedFeatureData.details.map((detail, index) => (
<ListItem key={index} sx={{ pl: 0, py: 1 }}>
<ListItemIcon sx={{ minWidth: 32 }}>
<CheckIcon sx={{ color: selectedFeatureData.color, fontSize: 20 }} />
</ListItemIcon>
<ListItemText
primary={detail}
primaryTypographyProps={{
variant: 'body1',
color: '#374151',
fontWeight: 500
}}
/>
</ListItem>
))}
</List>
{selectedFeatureData.id === 'test-persona' && (
<Box sx={{ mt: 3, p: 2, backgroundColor: '#f8fafc', borderRadius: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: '#1e293b' }}>
How It Works:
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Select a topic from your content calendar, then generate content using different personas
to see how your AI adapts its writing style. Compare the results and provide feedback
to continuously improve your persona.
</Typography>
</Box>
)}
{selectedFeatureData.id === 'fine-tuning' && (
<Box sx={{ mt: 3, p: 2, backgroundColor: '#f0f9ff', borderRadius: 2 }}>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1, color: '#1e293b' }}>
Privacy & Security:
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Your data is used exclusively to train your private AI model. It's never shared
or used for any other purpose. You own your AI, and it works only for you.
</Typography>
</Box>
)}
</>
)}
</DialogContent>
<DialogActions sx={{ p: 3, pt: 1 }}>
<Button
onClick={() => setOpenModal(false)}
variant="outlined"
>
Close
</Button>
<Button
onClick={() => setOpenModal(false)}
variant="contained"
sx={{
backgroundColor: selectedFeatureData?.color || '#3b82f6',
'&:hover': {
backgroundColor: selectedFeatureData?.color || '#3b82f6',
opacity: 0.9
}
}}
>
Notify Me When Ready
</Button>
</DialogActions>
</Dialog>
</>
);
};
export default ComingSoonSection;

View File

@@ -0,0 +1,264 @@
import React from 'react';
import {
Box,
Card,
CardContent,
Typography,
LinearProgress,
CircularProgress,
Grid
} from '@mui/material';
import {
CheckCircle as CheckCircleIcon,
AutoAwesome as AutoAwesomeIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import { Fade } from '@mui/material';
export interface GenerationStep {
id: string;
name: string;
description: string;
icon: React.ReactNode;
completed: boolean;
progress: number;
}
export interface ProgressMessage {
timestamp: string;
message: string;
progress?: number;
}
export interface PersonaGenerationProgressProps {
isGenerating: boolean;
progress: number;
currentStep: string;
generationSteps: GenerationStep[];
progressMessages: ProgressMessage[];
}
export const PersonaGenerationProgress: React.FC<PersonaGenerationProgressProps> = ({
isGenerating,
progress,
currentStep,
generationSteps,
progressMessages
}) => {
const activeStep = generationSteps.find(step => step.id === currentStep);
return (
<>
{/* Generation Progress Card */}
{isGenerating && (
<Fade in={true}>
<Card
sx={{
mb: 4,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05)',
borderRadius: 3,
overflow: 'hidden'
}}
>
<CardContent sx={{ p: 4 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, mb: 2 }}>
<Box
sx={{
p: 1.5,
borderRadius: 2,
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<AutoAwesomeIcon sx={{ fontSize: 20 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 0.5 }}>
{activeStep?.name}
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
{activeStep?.description}
</Typography>
</Box>
</Box>
<Box sx={{ mb: 3 }}>
<LinearProgress
variant="determinate"
value={progress}
sx={{
height: 8,
borderRadius: 4,
backgroundColor: '#e2e8f0',
'& .MuiLinearProgress-bar': {
borderRadius: 4,
background: 'linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%)'
}
}}
/>
<Typography variant="body2" sx={{ mt: 1, fontWeight: 500, color: '#475569' }}>
{progress}% Complete
</Typography>
</Box>
{/* Real-time progress messages */}
{progressMessages.length > 0 && (
<Box sx={{ mt: 2 }}>
<Typography variant="body2" sx={{ fontWeight: 500, color: '#334155', mb: 2 }}>
Recent Updates:
</Typography>
<Box sx={{ maxHeight: 120, overflow: 'auto', pl: 1 }}>
{progressMessages.slice(-3).map((msg, index) => (
<Box key={index} sx={{ display: 'flex', alignItems: 'center', gap: 1.5, mb: 1.5 }}>
<Box
sx={{
width: 6,
height: 6,
borderRadius: '50%',
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
flexShrink: 0
}}
/>
<Typography variant="body2" sx={{ color: '#475569', fontSize: '0.875rem' }}>
{msg.message}
</Typography>
</Box>
))}
</Box>
</Box>
)}
</CardContent>
</Card>
</Fade>
)}
{/* Generation Steps Grid */}
<AnimatePresence>
{isGenerating && (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
exit={{ opacity: 0, y: -20 }}
>
<Grid container spacing={3} sx={{ mb: 4 }}>
{generationSteps.map((step, index) => (
<Grid item xs={12} sm={6} md={3} key={step.id}>
<Card
sx={{
height: '100%',
background: step.completed
? 'linear-gradient(135deg, #10b981 0%, #059669 100%)'
: step.id === currentStep
? 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)'
: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
color: step.completed || step.id === currentStep ? 'white' : '#1e293b',
transition: 'all 0.3s ease',
border: '1px solid',
borderColor: step.completed || step.id === currentStep ? 'transparent' : '#e2e8f0',
boxShadow: step.completed || step.id === currentStep
? '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)'
: '0 4px 6px -1px rgba(0, 0, 0, 0.05), 0 2px 4px -1px rgba(0, 0, 0, 0.05)',
borderRadius: 3,
cursor: 'default',
'&:hover': {
transform: 'translateY(-2px)',
boxShadow: step.completed || step.id === currentStep
? '0 20px 25px -5px rgba(0, 0, 0, 0.15), 0 10px 10px -5px rgba(0, 0, 0, 0.05)'
: '0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -2px rgba(0, 0, 0, 0.05)',
}
}}
>
<CardContent sx={{ textAlign: 'center', p: 3 }}>
<Box sx={{ mb: 2 }}>
{step.completed ? (
<Box
sx={{
width: 48,
height: 48,
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mx: 'auto',
backdropFilter: 'blur(10px)'
}}
>
<CheckCircleIcon sx={{ fontSize: 24, color: 'white' }} />
</Box>
) : step.id === currentStep ? (
<Box
sx={{
width: 48,
height: 48,
borderRadius: '50%',
background: 'rgba(255, 255, 255, 0.2)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mx: 'auto',
backdropFilter: 'blur(10px)',
position: 'relative'
}}
>
<CircularProgress
size={24}
sx={{
color: 'white',
'& .MuiCircularProgress-circle': {
strokeLinecap: 'round',
}
}}
/>
</Box>
) : (
<Box
sx={{
width: 48,
height: 48,
borderRadius: '50%',
background: 'linear-gradient(135deg, #e2e8f0 0%, #cbd5e1 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mx: 'auto'
}}
>
<Box sx={{ color: '#64748b' }}>
{step.icon}
</Box>
</Box>
)}
</Box>
<Typography variant="subtitle2" sx={{ fontWeight: 600, mb: 1 }}>
{step.name}
</Typography>
<Typography variant="caption" sx={{
opacity: step.completed || step.id === currentStep ? 0.9 : 0.7,
lineHeight: 1.4,
display: '-webkit-box',
WebkitLineClamp: 2,
WebkitBoxOrient: 'vertical',
overflow: 'hidden'
}}>
{step.description}
</Typography>
</CardContent>
</Card>
</Grid>
))}
</Grid>
</motion.div>
)}
</AnimatePresence>
</>
);
};
export default PersonaGenerationProgress;

View File

@@ -0,0 +1,143 @@
import React from 'react';
import {
Box,
Button,
Typography,
Alert,
Card,
CardContent,
LinearProgress,
Fade
} from '@mui/material';
import { Psychology as PsychologyIcon } from '@mui/icons-material';
import { PersonaGenerationProgress } from './PersonaGenerationProgress';
import { type GenerationStep } from './PersonaGenerationProgress';
interface PersonaLoadingStateProps {
showPreview: boolean;
isGenerating: boolean;
corePersona: any;
progress: number;
generationStep: string;
generationSteps: GenerationStep[];
progressMessages: any[];
error: string | null;
pollingError: string | null;
success: string | null;
handleRegenerate: () => void;
generatePersonas: () => void;
setShowPreview: (show: boolean) => void;
setSuccess: (message: string | null) => void;
}
export const PersonaLoadingState: React.FC<PersonaLoadingStateProps> = ({
showPreview,
isGenerating,
corePersona,
progress,
generationStep,
generationSteps,
progressMessages,
error,
pollingError,
success,
handleRegenerate,
generatePersonas,
setShowPreview,
setSuccess
}) => {
return (
<>
{/* Safeguard: show loading instead of blank while initial checks run */}
{!showPreview && !isGenerating && !corePersona && (
<Fade in={true}>
<Card sx={{
mb: 4,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
borderRadius: 3
}}>
<CardContent sx={{ p: 4, textAlign: 'center' }}>
<Box sx={{ mb: 3 }}>
<Box
sx={{
width: 64,
height: 64,
borderRadius: '50%',
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
display: 'flex',
alignItems: 'center',
justifyContent: 'center',
mx: 'auto',
mb: 2
}}
>
<PsychologyIcon sx={{ fontSize: 32, color: 'white' }} />
</Box>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 1 }}>
Preparing Persona Workspace
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Checking cache and initializing generation...
</Typography>
</Box>
<LinearProgress
sx={{
height: 6,
borderRadius: 3,
backgroundColor: '#e2e8f0',
'& .MuiLinearProgress-bar': {
borderRadius: 3,
background: 'linear-gradient(90deg, #3b82f6 0%, #1d4ed8 100%)'
}
}}
/>
</CardContent>
</Card>
</Fade>
)}
{/* Generation Progress */}
<PersonaGenerationProgress
isGenerating={isGenerating}
progress={progress}
currentStep={generationStep}
generationSteps={generationSteps}
progressMessages={progressMessages}
/>
{/* Error Display */}
{(error || pollingError) && (
<Alert severity="error" sx={{ mb: 3 }}>
{error || pollingError}
<Button
size="small"
onClick={handleRegenerate}
sx={{ ml: 2 }}
>
Try Again
</Button>
</Alert>
)}
{/* Generate New Button (when cached data is loaded) */}
{showPreview && success && success.includes('cached') && (
<Alert severity="info" sx={{ mb: 3 }}>
{success}
<Button
size="small"
onClick={() => {
setShowPreview(false);
setSuccess(null);
generatePersonas();
}}
sx={{ ml: 2 }}
>
Generate New
</Button>
</Alert>
)}
</>
);
};

View File

@@ -0,0 +1,369 @@
import React from 'react';
import {
Box,
Button,
Typography,
Alert,
Chip,
Divider,
Accordion,
AccordionSummary,
AccordionDetails,
Fade
} from '@mui/material';
import {
ExpandMore as ExpandMoreIcon,
Refresh as RefreshIcon,
Psychology as PsychologyIcon,
AutoAwesome as AutoAwesomeIcon,
Assessment as AssessmentIcon,
LinkedIn as LinkedInIcon,
Facebook as FacebookIcon,
Twitter as TwitterIcon,
Article as ArticleIcon,
Instagram as InstagramIcon
} from '@mui/icons-material';
import { CorePersonaDisplay } from './sections/CorePersonaDisplay';
import { PlatformPersonaDisplay } from './sections/PlatformPersonaDisplay';
import { QualityMetricsDisplay } from './QualityMetricsDisplay';
interface PersonaPreviewSectionProps {
showPreview: boolean;
corePersona: any;
platformPersonas: Record<string, any>;
qualityMetrics: any;
selectedPlatforms: string[];
expandedAccordion: string | false;
setExpandedAccordion: (accordion: string | false) => void;
setCorePersona: (persona: any) => void;
setPlatformPersonas: (personas: Record<string, any>) => void;
handleRegenerate: () => void;
}
const availablePlatforms = [
{ id: 'linkedin', name: 'LinkedIn', icon: <LinkedInIcon />, color: '#0077B5' },
{ id: 'facebook', name: 'Facebook', icon: <FacebookIcon />, color: '#1877F2' },
{ id: 'twitter', name: 'Twitter', icon: <TwitterIcon />, color: '#1DA1F2' },
{ id: 'blog', name: 'Blog', icon: <ArticleIcon />, color: '#FF6B35' },
{ id: 'instagram', name: 'Instagram', icon: <InstagramIcon />, color: '#E4405F' }
];
export const PersonaPreviewSection: React.FC<PersonaPreviewSectionProps> = ({
showPreview,
corePersona,
platformPersonas,
qualityMetrics,
selectedPlatforms,
expandedAccordion,
setExpandedAccordion,
setCorePersona,
setPlatformPersonas,
handleRegenerate
}) => {
if (!showPreview || !corePersona) {
return null;
}
return (
<Fade in={true}>
<Box>
<Box sx={{
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
mb: 4,
p: 3,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05)'
}}>
<Box>
<Typography variant="h5" sx={{ fontWeight: 700, color: '#1e293b', mb: 0.5 }}>
Your AI Writing Persona
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Comprehensive analysis of your unique writing style and brand voice
</Typography>
</Box>
<Button
variant="outlined"
startIcon={<RefreshIcon />}
onClick={handleRegenerate}
size="small"
sx={{
borderColor: '#e2e8f0',
color: '#475569',
'&:hover': {
borderColor: '#3b82f6',
backgroundColor: '#f8fafc'
}
}}
>
Regenerate
</Button>
</Box>
{/* Core Persona */}
<Accordion
expanded={expandedAccordion === 'core'}
onChange={() => setExpandedAccordion(expandedAccordion === 'core' ? false : 'core')}
sx={{
mb: 3,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
'&:before': {
display: 'none'
},
'&.Mui-expanded': {
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
}
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ color: '#64748b' }} />}
sx={{
px: 4,
py: 3,
'&:hover': {
backgroundColor: '#f8fafc'
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, width: '100%' }}>
<Box
sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<PsychologyIcon sx={{ fontSize: 24 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 0.5 }}>
Core Writing Style
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Your unique voice and writing characteristics
</Typography>
</Box>
{qualityMetrics && (
<Chip
label={`${qualityMetrics.overall_score}% Quality`}
sx={{
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
color: 'white',
fontWeight: 600,
'& .MuiChip-label': {
px: 2
}
}}
size="small"
/>
)}
</Box>
</AccordionSummary>
<AccordionDetails sx={{ px: 4, pb: 4 }}>
<CorePersonaDisplay
persona={corePersona}
onChange={(updatedPersona) => {
setCorePersona(updatedPersona);
// TODO: Add debounced auto-save
}}
/>
</AccordionDetails>
</Accordion>
{/* Platform Adaptations */}
<Accordion
expanded={expandedAccordion === 'platforms'}
onChange={() => setExpandedAccordion(expandedAccordion === 'platforms' ? false : 'platforms')}
sx={{
mb: 3,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
'&:before': {
display: 'none'
},
'&.Mui-expanded': {
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
}
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ color: '#64748b' }} />}
sx={{
px: 4,
py: 3,
'&:hover': {
backgroundColor: '#f8fafc'
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, width: '100%' }}>
<Box
sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<AutoAwesomeIcon sx={{ fontSize: 24 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 0.5 }}>
Platform Adaptations
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Optimized for different content platforms
</Typography>
</Box>
<Chip
label={`${selectedPlatforms.length} Platforms`}
sx={{
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
color: 'white',
fontWeight: 600,
'& .MuiChip-label': {
px: 2
}
}}
size="small"
/>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ px: 4, pb: 4 }}>
<Box>
{selectedPlatforms.map((platformId, index) => {
const platformInfo = availablePlatforms.find(p => p.id === platformId);
return (
<Box key={platformId} sx={{ mb: index < selectedPlatforms.length - 1 ? 4 : 0 }}>
<Divider sx={{ mb: 3 }}>
<Chip
icon={platformInfo?.icon}
label={platformInfo?.name || platformId}
sx={{
background: 'linear-gradient(135deg, #8b5cf6 0%, #7c3aed 100%)',
color: 'white',
fontWeight: 600
}}
/>
</Divider>
<PlatformPersonaDisplay
platformPersona={platformPersonas[platformId] || {}}
platformName={platformId}
onChange={(updatedPersona) => {
setPlatformPersonas({
...platformPersonas,
[platformId]: updatedPersona
});
// TODO: Add debounced auto-save
}}
/>
</Box>
);
})}
{selectedPlatforms.length === 0 && (
<Alert severity="info" sx={{
background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)',
border: '1px solid #0ea5e9',
color: '#0c4a6e'
}}>
No platforms selected. Please select at least one platform to see optimized personas.
</Alert>
)}
</Box>
</AccordionDetails>
</Accordion>
{/* Quality Metrics */}
{qualityMetrics && (
<Accordion
expanded={expandedAccordion === 'quality'}
onChange={() => setExpandedAccordion(expandedAccordion === 'quality' ? false : 'quality')}
sx={{
mb: 4,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
boxShadow: '0 4px 6px -1px rgba(0, 0, 0, 0.05)',
'&:before': {
display: 'none'
},
'&.Mui-expanded': {
boxShadow: '0 10px 15px -3px rgba(0, 0, 0, 0.1)'
}
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon sx={{ color: '#64748b' }} />}
sx={{
px: 4,
py: 3,
'&:hover': {
backgroundColor: '#f8fafc'
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 3, width: '100%' }}>
<Box
sx={{
p: 2,
borderRadius: 2,
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
color: 'white',
display: 'flex',
alignItems: 'center',
justifyContent: 'center'
}}
>
<AssessmentIcon sx={{ fontSize: 24 }} />
</Box>
<Box sx={{ flex: 1 }}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 0.5 }}>
Quality Assessment
</Typography>
<Typography variant="body2" sx={{ color: '#64748b' }}>
Performance metrics and recommendations
</Typography>
</Box>
<Chip
label={`${qualityMetrics.overall_score}% Quality`}
sx={{
background: qualityMetrics.overall_score >= 85
? 'linear-gradient(135deg, #10b981 0%, #059669 100%)'
: qualityMetrics.overall_score >= 70
? 'linear-gradient(135deg, #f59e0b 0%, #d97706 100%)'
: 'linear-gradient(135deg, #ef4444 0%, #dc2626 100%)',
color: 'white',
fontWeight: 600,
'& .MuiChip-label': {
px: 2
}
}}
size="small"
/>
</Box>
</AccordionSummary>
<AccordionDetails sx={{ px: 4, pb: 4 }}>
<QualityMetricsDisplay metrics={qualityMetrics} />
</AccordionDetails>
</Accordion>
)}
</Box>
</Fade>
);
};

View File

@@ -0,0 +1,165 @@
import React from 'react';
import {
Box,
Grid,
Typography,
Stack
} from '@mui/material';
interface QualityMetrics {
overall_score: number;
style_consistency?: number;
brand_alignment?: number;
platform_optimization?: number;
engagement_potential?: number;
core_completeness?: number;
platform_consistency?: number;
linguistic_quality?: number;
recommendations: string[];
}
interface QualityMetricsDisplayProps {
metrics: QualityMetrics;
}
export const QualityMetricsDisplay: React.FC<QualityMetricsDisplayProps> = ({ metrics }) => {
// Determine which metric set is being used (old vs new)
const isNewMetrics = metrics.core_completeness !== undefined;
const metricItems = isNewMetrics ? [
{ label: 'Overall Quality', value: metrics.overall_score },
{ label: 'Core Completeness', value: metrics.core_completeness || 0 },
{ label: 'Platform Consistency', value: metrics.platform_consistency || 0 },
{ label: 'Platform Optimization', value: metrics.platform_optimization || 0 },
{ label: 'Linguistic Quality', value: metrics.linguistic_quality || 0 }
] : [
{ label: 'Overall Quality', value: metrics.overall_score },
{ label: 'Style Consistency', value: metrics.style_consistency || 0 },
{ label: 'Brand Alignment', value: metrics.brand_alignment || 0 },
{ label: 'Platform Optimization', value: metrics.platform_optimization || 0 },
{ label: 'Engagement Potential', value: metrics.engagement_potential || 0 }
];
return (
<Grid container spacing={4}>
<Grid item xs={12} md={6}>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
height: '100%'
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Performance Scores
</Typography>
<Stack spacing={3}>
{metricItems.map((metric, index) => (
<Box key={index}>
<Box sx={{ display: 'flex', justifyContent: 'space-between', mb: 1.5 }}>
<Typography variant="body2" sx={{ fontWeight: 500, color: '#334155' }}>
{metric.label}
</Typography>
<Typography variant="body2" sx={{
fontWeight: 700,
color: metric.value >= 85 ? '#059669' : metric.value >= 70 ? '#d97706' : '#dc2626'
}}>
{metric.value}%
</Typography>
</Box>
<Box sx={{
width: '100%',
height: 10,
backgroundColor: '#e2e8f0',
borderRadius: 5,
overflow: 'hidden',
position: 'relative'
}}>
<Box
sx={{
width: `${metric.value}%`,
height: '100%',
background: metric.value >= 85
? 'linear-gradient(90deg, #10b981 0%, #059669 100%)'
: metric.value >= 70
? 'linear-gradient(90deg, #f59e0b 0%, #d97706 100%)'
: 'linear-gradient(90deg, #ef4444 0%, #dc2626 100%)',
borderRadius: 5,
transition: 'width 1s ease-in-out'
}}
/>
</Box>
</Box>
))}
</Stack>
</Box>
</Grid>
<Grid item xs={12} md={6}>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
height: '100%'
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Recommendations
</Typography>
<Stack spacing={2}>
{metrics.recommendations && metrics.recommendations.length > 0 ? (
metrics.recommendations.map((recommendation, index) => (
<Box key={index} sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 2,
p: 2,
background: 'linear-gradient(135deg, #ffffff 0%, #f8fafc 100%)',
border: '1px solid #e2e8f0',
borderRadius: 2,
boxShadow: '0 1px 3px 0 rgba(0, 0, 0, 0.05)'
}}>
<Box sx={{
width: 8,
height: 8,
borderRadius: '50%',
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
mt: 0.5,
flexShrink: 0
}} />
<Typography variant="body2" sx={{ color: '#334155', lineHeight: 1.6 }}>
{recommendation}
</Typography>
</Box>
))
) : (
<Box sx={{
display: 'flex',
alignItems: 'flex-start',
gap: 2,
p: 2,
background: 'linear-gradient(135deg, #ecfdf5 0%, #d1fae5 100%)',
border: '1px solid #10b981',
borderRadius: 2
}}>
<Box sx={{
width: 8,
height: 8,
borderRadius: '50%',
background: 'linear-gradient(135deg, #10b981 0%, #059669 100%)',
mt: 0.5,
flexShrink: 0
}} />
<Typography variant="body2" sx={{ color: '#065f46', lineHeight: 1.6 }}>
Your personas demonstrate excellent quality across all assessment criteria!
</Typography>
</Box>
)}
</Stack>
</Box>
</Grid>
</Grid>
);
};
export default QualityMetricsDisplay;

View File

@@ -0,0 +1,250 @@
import React, { useState } from 'react';
import {
Box,
Chip,
TextField,
IconButton,
Typography,
Tooltip,
Paper,
Stack
} from '@mui/material';
import {
Add as AddIcon,
Delete as DeleteIcon,
Info as InfoIcon
} from '@mui/icons-material';
interface EditableChipArrayProps {
label: string;
values: string[];
onChange: (newValues: string[]) => void;
placeholder?: string;
maxItems?: number;
color?: 'default' | 'primary' | 'secondary' | 'error' | 'info' | 'success' | 'warning';
helperText?: string;
allowDuplicates?: boolean;
tooltipInfo?: {
title: string;
description: string;
howWeCalculated: string;
whyItMatters: string;
example?: string;
};
}
/**
* Editable array of chips (tags) component
* Allows adding, removing, and managing string arrays
*/
export const EditableChipArray: React.FC<EditableChipArrayProps> = ({
label,
values = [],
onChange,
placeholder = 'Type and press Enter to add...',
maxItems,
color = 'primary',
helperText,
allowDuplicates = false,
tooltipInfo
}) => {
const [inputValue, setInputValue] = useState('');
const [error, setError] = useState('');
const renderTooltipContent = () => {
if (!tooltipInfo) return '';
return (
<Box sx={{ maxWidth: 400, p: 1 }}>
<Typography variant="subtitle2" fontWeight="bold" gutterBottom>
{tooltipInfo.title}
</Typography>
<Typography variant="body2" paragraph>
{tooltipInfo.description}
</Typography>
<Typography variant="caption" display="block" sx={{ fontWeight: 600, mt: 1 }}>
🔍 How we calculated this:
</Typography>
<Typography variant="caption" display="block" paragraph>
{tooltipInfo.howWeCalculated}
</Typography>
<Typography variant="caption" display="block" sx={{ fontWeight: 600 }}>
💡 Why it matters:
</Typography>
<Typography variant="caption" display="block" paragraph>
{tooltipInfo.whyItMatters}
</Typography>
{tooltipInfo.example && (
<>
<Typography variant="caption" display="block" sx={{ fontWeight: 600 }}>
📝 Example:
</Typography>
<Typography variant="caption" display="block" sx={{ fontStyle: 'italic' }}>
{tooltipInfo.example}
</Typography>
</>
)}
</Box>
);
};
const handleAdd = () => {
const trimmedValue = inputValue.trim();
if (!trimmedValue) {
setError('Value cannot be empty');
return;
}
if (!allowDuplicates && values.includes(trimmedValue)) {
setError('This value already exists');
return;
}
if (maxItems && values.length >= maxItems) {
setError(`Maximum ${maxItems} items allowed`);
return;
}
onChange([...values, trimmedValue]);
setInputValue('');
setError('');
};
const handleRemove = (indexToRemove: number) => {
onChange(values.filter((_, index) => index !== indexToRemove));
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter') {
e.preventDefault();
handleAdd();
} else if (e.key === 'Escape') {
setInputValue('');
setError('');
}
};
const canAdd = !maxItems || values.length < maxItems;
return (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 1 }}>
<Typography variant="caption" color="text.secondary">
{label}
</Typography>
{tooltipInfo && (
<Tooltip title={renderTooltipContent()} arrow placement="right" enterDelay={200}>
<InfoIcon sx={{ fontSize: 14, color: 'info.main', cursor: 'help' }} />
</Tooltip>
)}
{maxItems && (
<Typography
component="span"
variant="caption"
color="text.disabled"
sx={{ ml: 'auto' }}
>
({values.length}/{maxItems})
</Typography>
)}
</Box>
{/* Input field for adding new items */}
{canAdd && (
<Box sx={{ display: 'flex', gap: 1, mb: 1.5 }}>
<TextField
value={inputValue}
onChange={(e) => {
setInputValue(e.target.value);
setError('');
}}
onKeyDown={handleKeyDown}
placeholder={placeholder}
size="small"
fullWidth
error={!!error}
helperText={error || helperText}
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: 'background.paper'
}
}}
/>
<Tooltip title="Add item (Enter)">
<span>
<IconButton
onClick={handleAdd}
color="primary"
disabled={!inputValue.trim()}
size="small"
sx={{
height: 40,
width: 40
}}
>
<AddIcon />
</IconButton>
</span>
</Tooltip>
</Box>
)}
{/* Display chips */}
{values.length > 0 ? (
<Paper
variant="outlined"
sx={{
p: 1.5,
backgroundColor: 'background.default',
minHeight: 60
}}
>
<Stack direction="row" spacing={0.5} flexWrap="wrap" useFlexGap>
{values.map((value, index) => (
<Chip
key={`${value}-${index}`}
label={value}
color={color}
size="small"
onDelete={() => handleRemove(index)}
deleteIcon={
<Tooltip title="Remove">
<DeleteIcon />
</Tooltip>
}
sx={{
mb: 0.5,
'& .MuiChip-deleteIcon': {
fontSize: '16px'
}
}}
/>
))}
</Stack>
</Paper>
) : (
<Paper
variant="outlined"
sx={{
p: 2,
backgroundColor: 'background.default',
textAlign: 'center'
}}
>
<Typography variant="body2" color="text.disabled">
No items added yet. {canAdd ? 'Add some above!' : ''}
</Typography>
</Paper>
)}
{!canAdd && (
<Typography variant="caption" color="warning.main" sx={{ mt: 0.5, display: 'block' }}>
Maximum items reached. Remove some to add more.
</Typography>
)}
</Box>
);
};
export default EditableChipArray;

View File

@@ -0,0 +1,272 @@
import React, { useState, useEffect } from 'react';
import {
Box,
TextField,
Typography,
IconButton,
Tooltip,
Fade
} from '@mui/material';
import {
Edit as EditIcon,
Check as CheckIcon,
Close as CloseIcon,
Info as InfoIcon
} from '@mui/icons-material';
interface EditableTextFieldProps {
label: string;
value: string;
onChange: (newValue: string) => void;
multiline?: boolean;
helperText?: string;
fullWidth?: boolean;
placeholder?: string;
required?: boolean;
maxLength?: number;
type?: 'text' | 'number';
tooltipInfo?: {
title: string;
description: string;
howWeCalculated: string;
whyItMatters: string;
example?: string;
};
}
/**
* Editable text field component with inline editing
* Shows text display by default, switches to edit mode on click
*/
export const EditableTextField: React.FC<EditableTextFieldProps> = ({
label,
value,
onChange,
multiline = false,
helperText,
fullWidth = true,
placeholder = 'Click to edit...',
required = false,
maxLength,
type = 'text',
tooltipInfo
}) => {
const [isEditing, setIsEditing] = useState(false);
const [localValue, setLocalValue] = useState(value);
const [isHovered, setIsHovered] = useState(false);
// Update local value when prop changes
useEffect(() => {
setLocalValue(value);
}, [value]);
const handleSave = () => {
if (required && !localValue.trim()) {
return; // Don't save if required field is empty
}
onChange(localValue);
setIsEditing(false);
};
const handleCancel = () => {
setLocalValue(value); // Reset to original value
setIsEditing(false);
};
const handleKeyDown = (e: React.KeyboardEvent) => {
if (e.key === 'Enter' && !multiline) {
e.preventDefault();
handleSave();
} else if (e.key === 'Escape') {
handleCancel();
}
};
const renderTooltipContent = () => {
if (!tooltipInfo) return '';
return (
<Box sx={{ maxWidth: 400, p: 1 }}>
<Typography variant="subtitle2" fontWeight="bold" gutterBottom>
{tooltipInfo.title}
</Typography>
<Typography variant="body2" paragraph>
{tooltipInfo.description}
</Typography>
<Typography variant="caption" display="block" sx={{ fontWeight: 600, mt: 1 }}>
🔍 How we calculated this:
</Typography>
<Typography variant="caption" display="block" paragraph>
{tooltipInfo.howWeCalculated}
</Typography>
<Typography variant="caption" display="block" sx={{ fontWeight: 600 }}>
💡 Why it matters:
</Typography>
<Typography variant="caption" display="block" paragraph>
{tooltipInfo.whyItMatters}
</Typography>
{tooltipInfo.example && (
<>
<Typography variant="caption" display="block" sx={{ fontWeight: 600 }}>
📝 Example:
</Typography>
<Typography variant="caption" display="block" sx={{ fontStyle: 'italic' }}>
{tooltipInfo.example}
</Typography>
</>
)}
</Box>
);
};
if (isEditing) {
return (
<Box sx={{ mb: 2 }}>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
<Typography variant="body2" sx={{ fontWeight: 600, color: '#1e293b', fontSize: '0.875rem' }}>
{label} {required && <span style={{ color: '#ef4444' }}>*</span>}
</Typography>
{tooltipInfo && (
<Tooltip title={renderTooltipContent()} arrow placement="right" enterDelay={200}>
<InfoIcon sx={{ fontSize: 14, color: 'info.main', cursor: 'help' }} />
</Tooltip>
)}
</Box>
<Box sx={{ display: 'flex', alignItems: 'flex-start', gap: 1 }}>
<TextField
value={localValue}
onChange={(e) => setLocalValue(e.target.value)}
onKeyDown={handleKeyDown}
multiline={multiline}
rows={multiline ? 3 : 1}
fullWidth={fullWidth}
placeholder={placeholder}
autoFocus
size="small"
helperText={helperText}
error={required && !localValue.trim()}
inputProps={{
maxLength: maxLength
}}
type={type}
sx={{
'& .MuiOutlinedInput-root': {
backgroundColor: '#ffffff',
fontSize: '0.875rem',
'&:hover': {
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#3b82f6',
},
},
'&.Mui-focused': {
'& .MuiOutlinedInput-notchedOutline': {
borderColor: '#3b82f6',
borderWidth: 2,
},
},
},
}}
/>
<Box sx={{ display: 'flex', gap: 0.5, pt: 0.5 }}>
<Tooltip title="Save (Enter)">
<IconButton
size="small"
color="primary"
onClick={handleSave}
disabled={required && !localValue.trim()}
>
<CheckIcon fontSize="small" />
</IconButton>
</Tooltip>
<Tooltip title="Cancel (Esc)">
<IconButton size="small" color="default" onClick={handleCancel}>
<CloseIcon fontSize="small" />
</IconButton>
</Tooltip>
</Box>
</Box>
</Box>
);
}
return (
<Box
sx={{
mb: 2,
position: 'relative',
width: '100%',
maxWidth: '100%'
}}
onMouseEnter={() => setIsHovered(true)}
onMouseLeave={() => setIsHovered(false)}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 0.5, mb: 0.5 }}>
<Typography variant="body2" sx={{ fontWeight: 600, color: '#1e293b', fontSize: '0.875rem' }}>
{label}
</Typography>
{tooltipInfo && (
<Tooltip title={renderTooltipContent()} arrow placement="right" enterDelay={200}>
<InfoIcon sx={{ fontSize: 14, color: 'info.main', cursor: 'help' }} />
</Tooltip>
)}
</Box>
<Box
onClick={() => setIsEditing(true)}
sx={{
p: 1.5,
borderRadius: 1,
border: '1px solid #e2e8f0',
backgroundColor: value ? '#f8fafc' : '#ffffff',
cursor: 'pointer',
transition: 'all 0.2s',
minHeight: multiline ? '60px' : '36px',
display: 'flex',
alignItems: multiline ? 'flex-start' : 'center',
position: 'relative',
width: '100%',
maxWidth: '100%',
'&:hover': {
borderColor: '#3b82f6',
backgroundColor: '#f1f5f9'
}
}}
>
<Typography
variant="body2"
sx={{
flex: 1,
color: value ? '#1e293b' : '#94a3b8',
whiteSpace: multiline ? 'pre-wrap' : 'nowrap',
overflow: 'hidden',
textOverflow: 'ellipsis',
fontSize: '0.875rem',
lineHeight: multiline ? 1.4 : 1
}}
>
{value || placeholder}
</Typography>
<Fade in={isHovered}>
<IconButton
size="small"
sx={{
position: 'absolute',
right: 4,
top: '50%',
transform: 'translateY(-50%)',
opacity: 0.7
}}
>
<EditIcon fontSize="small" />
</IconButton>
</Fade>
</Box>
{helperText && (
<Typography variant="caption" sx={{ color: '#64748b', mt: 0.5, display: 'block', fontSize: '0.75rem' }}>
{helperText}
</Typography>
)}
</Box>
);
};
export default EditableTextField;

View File

@@ -0,0 +1,135 @@
import React from 'react';
import {
Accordion,
AccordionSummary,
AccordionDetails,
Typography,
Box,
Avatar,
Chip
} from '@mui/material';
import {
ExpandMore as ExpandMoreIcon
} from '@mui/icons-material';
interface SectionAccordionProps {
title: string;
icon?: React.ReactNode;
children: React.ReactNode;
defaultExpanded?: boolean;
badge?: string | number;
subtitle?: string;
color?: string;
expanded?: boolean;
onChange?: (event: React.SyntheticEvent, isExpanded: boolean) => void;
}
/**
* Reusable accordion component for organizing persona sections
* Provides consistent styling and behavior across all sections
*/
export const SectionAccordion: React.FC<SectionAccordionProps> = ({
title,
icon,
children,
defaultExpanded = false,
badge,
subtitle,
color = 'primary.main',
expanded,
onChange
}) => {
return (
<Accordion
defaultExpanded={defaultExpanded}
expanded={expanded}
onChange={onChange}
sx={{
mb: 1.5,
borderRadius: 2,
background: '#ffffff',
border: '1px solid #e2e8f0',
width: '100%',
maxWidth: '100%',
'&:before': {
display: 'none' // Remove default MUI divider
},
boxShadow: '0 1px 3px rgba(0, 0, 0, 0.1)',
'&.Mui-expanded': {
boxShadow: '0 4px 6px rgba(0, 0, 0, 0.1)'
}
}}
>
<AccordionSummary
expandIcon={<ExpandMoreIcon />}
sx={{
px: 2,
py: 1.5,
'&.Mui-expanded': {
minHeight: 56
},
'& .MuiAccordionSummary-content': {
my: 0,
'&.Mui-expanded': {
my: 0
}
}
}}
>
<Box sx={{ display: 'flex', alignItems: 'center', gap: 2, width: '100%' }}>
{/* Icon */}
{icon && (
<Avatar
sx={{
bgcolor: color,
width: 32,
height: 32
}}
>
{icon}
</Avatar>
)}
{/* Title and subtitle */}
<Box sx={{ flex: 1 }}>
<Typography variant="h6" fontWeight="600" sx={{ fontSize: '1rem', color: '#1e293b' }}>
{title}
</Typography>
{subtitle && (
<Typography variant="body2" sx={{ color: '#64748b', fontSize: '0.875rem' }}>
{subtitle}
</Typography>
)}
</Box>
{/* Badge */}
{badge !== undefined && (
<Chip
label={badge}
size="small"
color="primary"
sx={{
fontWeight: 600,
minWidth: 60
}}
/>
)}
</Box>
</AccordionSummary>
<AccordionDetails
sx={{
pt: 1,
pb: 2,
px: 2,
backgroundColor: '#ffffff'
}}
>
{children}
</AccordionDetails>
</Accordion>
);
};
export default SectionAccordion;

View File

@@ -0,0 +1,9 @@
/**
* Persona Step Components Index
* Export all reusable components for persona display
*/
export { EditableTextField } from './EditableTextField';
export { EditableChipArray } from './EditableChipArray';
export { SectionAccordion } from './SectionAccordion';

View File

@@ -0,0 +1,163 @@
import { useCallback } from 'react';
import { apiClient } from '../../../api/client';
import {
generateWritingPersonas,
assessPersonaQuality,
prepareOnboardingData,
validatePersonaRequest,
PersonaGenerationRequest
} from '../../../api/personaApi';
interface PersonaGenerationProps {
onboardingData: any;
selectedPlatforms: string[];
setCorePersona: (persona: any) => void;
setPlatformPersonas: (personas: Record<string, any>) => void;
setQualityMetrics: (metrics: any) => void;
setShowPreview: (show: boolean) => void;
setGenerationStep: (step: string) => void;
setProgress: (progress: number) => void;
setIsGenerating: (generating: boolean) => void;
setError: (error: string | null) => void;
savePersonaDataToCache: (data: any) => void;
startPolling: (taskId: string) => void;
}
export const usePersonaGeneration = ({
onboardingData,
selectedPlatforms,
setCorePersona,
setPlatformPersonas,
setQualityMetrics,
setShowPreview,
setGenerationStep,
setProgress,
setIsGenerating,
setError,
savePersonaDataToCache,
startPolling
}: PersonaGenerationProps) => {
const generatePersonas = useCallback(async () => {
setIsGenerating(true);
setError(null);
setProgress(0);
setShowPreview(false);
// Clear session cache flag since we're generating fresh
sessionStorage.removeItem('persona_server_cache_checked');
try {
// Start async persona generation
const request: PersonaGenerationRequest = {
onboarding_data: prepareOnboardingData(onboardingData),
selected_platforms: selectedPlatforms,
user_preferences: null
};
console.log('Starting async persona generation...');
const response = await apiClient.post('/api/onboarding/step4/generate-personas-async', request);
if (response.data.task_id) {
console.log('Persona generation task response:', response.data);
// Check if the task is already completed (cache hit)
if (response.data.status === 'completed') {
console.log('Task already completed (cache hit), fetching result immediately');
// Fetch the completed task result
const taskResponse = await apiClient.get(`/api/onboarding/step4/persona-task/${response.data.task_id}`);
if (taskResponse.data && taskResponse.data.result) {
const result = taskResponse.data.result;
setCorePersona(result.core_persona);
setPlatformPersonas(result.platform_personas);
setQualityMetrics(result.quality_metrics);
setShowPreview(true);
setGenerationStep('preview');
setProgress(100);
savePersonaDataToCache(result);
setIsGenerating(false);
return;
}
}
// Start polling for the task
console.log('Starting polling for task:', response.data.task_id);
startPolling(response.data.task_id);
} else {
throw new Error('Failed to start persona generation task');
}
} catch (err) {
console.error('Failed to start persona generation:', err);
setError(err instanceof Error ? err.message : 'Failed to start persona generation');
setIsGenerating(false);
}
}, [onboardingData, selectedPlatforms, startPolling, setIsGenerating, setError, setProgress, setShowPreview, setCorePersona, setPlatformPersonas, setQualityMetrics, setGenerationStep, savePersonaDataToCache]);
const generateCorePersona = async (data: any) => {
const request: PersonaGenerationRequest = {
onboarding_data: prepareOnboardingData(data),
selected_platforms: selectedPlatforms,
user_preferences: null
};
// Validate request
const validationErrors = validatePersonaRequest(request);
if (validationErrors.length > 0) {
throw new Error(`Validation failed: ${validationErrors.join(', ')}`);
}
const response = await generateWritingPersonas(request);
if (!response.success) {
throw new Error(response.error || 'Failed to generate core persona');
}
return response.core_persona;
};
const generatePlatformPersonas = async (corePersona: any, platforms: string[]) => {
const request: PersonaGenerationRequest = {
onboarding_data: prepareOnboardingData(onboardingData),
selected_platforms: platforms,
user_preferences: null
};
const response = await generateWritingPersonas(request);
if (!response.success) {
throw new Error(response.error || 'Failed to generate platform personas');
}
return response.platform_personas || {};
};
const assessPersonaQualityInternal = async (corePersona: any, platformPersonas: any) => {
const response = await assessPersonaQuality({
core_persona: corePersona,
platform_personas: platformPersonas,
user_feedback: null
});
if (!response.success) {
throw new Error(response.error || 'Failed to assess persona quality');
}
return response.quality_metrics;
};
const getStepFromMessage = (message: string): string => {
if (message.includes('Initializing')) return 'analyzing';
if (message.includes('core persona')) return 'generating';
if (message.includes('platform')) return 'adapting';
if (message.includes('quality')) return 'assessing';
if (message.includes('completed')) return 'preview';
return 'generating';
};
return {
generatePersonas,
generateCorePersona,
generatePlatformPersonas,
assessPersonaQualityInternal,
getStepFromMessage
};
};

View File

@@ -0,0 +1,125 @@
import { useCallback } from 'react';
interface PersonaInitializationProps {
stepData?: {
corePersona?: any;
platformPersonas?: Record<string, any>;
qualityMetrics?: any;
selectedPlatforms?: string[];
};
updateHeaderContent: (content: { title: string; description: string }) => void;
setCorePersona: (persona: any) => void;
setPlatformPersonas: (personas: Record<string, any>) => void;
setQualityMetrics: (metrics: any) => void;
setSelectedPlatforms: (platforms: string[]) => void;
setShowPreview: (show: boolean) => void;
setGenerationStep: (step: string) => void;
setProgress: (progress: number) => void;
setHasCheckedCache: (checked: boolean) => void;
setSuccess: (message: string | null) => void;
loadCachedPersonaData: () => boolean;
loadServerCachedPersonaData: () => Promise<boolean>;
generatePersonas: () => Promise<void>;
}
export const usePersonaInitialization = ({
stepData,
updateHeaderContent,
setCorePersona,
setPlatformPersonas,
setQualityMetrics,
setSelectedPlatforms,
setShowPreview,
setGenerationStep,
setProgress,
setHasCheckedCache,
setSuccess,
loadCachedPersonaData,
loadServerCachedPersonaData,
generatePersonas
}: PersonaInitializationProps) => {
const initialize = useCallback(async () => {
console.log('PersonaStep: Initialization started');
// Update header immediately
updateHeaderContent({
title: 'AI Writing Persona Generation',
description: 'ALwrity is analyzing your content and creating a sophisticated AI writing persona that captures your unique style, brand voice, and content preferences across all platforms.'
});
// Check if we already have persona data from stepData (when navigating back)
if (stepData?.corePersona) {
console.log('PersonaStep: Loading persona data from stepData (navigation back)');
setCorePersona(stepData.corePersona);
setPlatformPersonas(stepData.platformPersonas || {});
setQualityMetrics(stepData.qualityMetrics || null);
if (stepData.selectedPlatforms) {
setSelectedPlatforms(stepData.selectedPlatforms);
}
setShowPreview(true);
setGenerationStep('preview');
setProgress(100);
setHasCheckedCache(true);
return;
}
// Check session flag to avoid redundant server cache checks
const serverCacheChecked = sessionStorage.getItem('persona_server_cache_checked');
// Try to load from server cache first (skip if already checked this session and was 404)
let foundCache = false;
if (!serverCacheChecked || serverCacheChecked !== '404') {
try {
console.log('PersonaStep: Checking server cache');
foundCache = await loadServerCachedPersonaData();
if (foundCache) {
console.log('PersonaStep: Server cache found, using it');
sessionStorage.setItem('persona_server_cache_checked', 'found');
setHasCheckedCache(true);
return;
} else {
// Mark that we checked and got 404
sessionStorage.setItem('persona_server_cache_checked', '404');
}
} catch (error: any) {
console.warn('PersonaStep: Error loading server cache, trying local cache:', error);
sessionStorage.setItem('persona_server_cache_checked', '404');
}
} else {
console.log('PersonaStep: Skipping server cache check (already checked this session, was 404)');
}
// Try local cache
console.log('PersonaStep: Checking local cache');
foundCache = loadCachedPersonaData();
if (foundCache) {
console.log('PersonaStep: Local cache found, using it');
setHasCheckedCache(true);
return;
}
// No cache found, start generation
console.log('PersonaStep: No cache found, starting generation');
await generatePersonas();
setHasCheckedCache(true);
}, [
stepData,
updateHeaderContent,
setCorePersona,
setPlatformPersonas,
setQualityMetrics,
setSelectedPlatforms,
setShowPreview,
setGenerationStep,
setProgress,
setHasCheckedCache,
loadCachedPersonaData,
loadServerCachedPersonaData,
generatePersonas
]);
return {
initialize
};
};

View File

@@ -0,0 +1,506 @@
import React from 'react';
import { Box, Grid, Typography } from '@mui/material';
import {
Psychology as PsychologyIcon,
RecordVoiceOver as VoiceIcon,
Tune as TuneIcon,
FormatPaint as FormatIcon,
Assessment as AssessmentIcon
} from '@mui/icons-material';
import { SectionAccordion } from '../components/SectionAccordion';
import { EditableTextField } from '../components/EditableTextField';
import { EditableChipArray } from '../components/EditableChipArray';
import { corePersonaTooltips } from '../utils/personaTooltips';
interface CorePersonaDisplayProps {
persona: any;
onChange: (updatedPersona: any) => void;
}
/**
* Comprehensive display for Core Persona data
* Shows all backend-generated fields in organized, editable sections
*/
export const CorePersonaDisplay: React.FC<CorePersonaDisplayProps> = ({
persona,
onChange
}) => {
// Helper function to update nested fields
const updateField = (path: string[], value: any) => {
const updatedPersona = { ...persona };
let current = updatedPersona;
for (let i = 0; i < path.length - 1; i++) {
if (!current[path[i]]) {
current[path[i]] = {};
}
current = current[path[i]];
}
current[path[path.length - 1]] = value;
onChange(updatedPersona);
};
// Safe getter for nested properties
const getNestedValue = (obj: any, path: string[], defaultValue: any = '') => {
return path.reduce((current, key) => current?.[key], obj) ?? defaultValue;
};
return (
<Box>
{/* 1. Identity & Brand Voice Section */}
<SectionAccordion
title="Identity & Brand Voice"
subtitle="Core personality and brand characteristics"
icon={<PsychologyIcon />}
defaultExpanded={true}
color="primary.main"
>
<Box sx={{
p: 2,
background: '#ffffff',
border: '1px solid #e2e8f0',
borderRadius: 2,
mb: 2,
width: '100%',
overflow: 'visible'
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 2 }}>
Core Identity
</Typography>
<Grid container spacing={2} sx={{ width: '100%' }}>
<Grid item xs={12} sm={6} sx={{ width: '100%' }}>
<EditableTextField
label="Persona Name"
value={getNestedValue(persona, ['identity', 'persona_name'])}
onChange={(val) => updateField(['identity', 'persona_name'], val)}
placeholder="e.g., The Thought Leader"
helperText="A descriptive name for this writing persona"
tooltipInfo={corePersonaTooltips.personaName}
/>
</Grid>
<Grid item xs={12} sm={6} sx={{ width: '100%' }}>
<EditableTextField
label="Archetype"
value={getNestedValue(persona, ['identity', 'archetype'])}
onChange={(val) => updateField(['identity', 'archetype'], val)}
placeholder="e.g., Expert Educator, Innovator, Storyteller"
helperText="The primary archetype this persona embodies"
tooltipInfo={corePersonaTooltips.archetype}
/>
</Grid>
<Grid item xs={12} sx={{ width: '100%' }}>
<EditableTextField
label="Core Belief"
value={getNestedValue(persona, ['identity', 'core_belief'])}
onChange={(val) => updateField(['identity', 'core_belief'], val)}
multiline
placeholder="What is the fundamental belief driving this persona?"
helperText="The underlying philosophy or conviction"
tooltipInfo={corePersonaTooltips.coreBelief}
/>
</Grid>
<Grid item xs={12}>
<EditableTextField
label="Brand Voice Description"
value={getNestedValue(persona, ['identity', 'brand_voice_description'])}
onChange={(val) => updateField(['identity', 'brand_voice_description'], val)}
multiline
placeholder="Describe the overall brand voice..."
helperText="A comprehensive description of the brand voice and tone"
tooltipInfo={corePersonaTooltips.brandVoice}
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 2. Linguistic Fingerprint Section */}
<SectionAccordion
title="Linguistic Fingerprint"
subtitle="Detailed writing style characteristics"
icon={<VoiceIcon />}
color="secondary.main"
>
{/* Sentence Metrics */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Sentence Metrics
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Average Sentence Length (words)"
value={getNestedValue(persona, ['linguistic_fingerprint', 'sentence_metrics', 'average_sentence_length_words'], '')}
onChange={(val) => updateField(['linguistic_fingerprint', 'sentence_metrics', 'average_sentence_length_words'], Number(val))}
type="number"
placeholder="e.g., 18"
helperText="Typical sentence length in words"
tooltipInfo={corePersonaTooltips.avgSentenceLength}
/>
<EditableTextField
label="Preferred Sentence Type"
value={getNestedValue(persona, ['linguistic_fingerprint', 'sentence_metrics', 'preferred_sentence_type'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'sentence_metrics', 'preferred_sentence_type'], val)}
placeholder="e.g., Compound, Complex, Simple"
helperText="Most commonly used sentence structure"
tooltipInfo={corePersonaTooltips.sentenceType}
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Active to Passive Ratio"
value={getNestedValue(persona, ['linguistic_fingerprint', 'sentence_metrics', 'active_to_passive_ratio'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'sentence_metrics', 'active_to_passive_ratio'], val)}
placeholder="e.g., 80:20, Mostly active"
helperText="Balance of active vs passive voice"
tooltipInfo={corePersonaTooltips.activePassiveRatio}
/>
<EditableTextField
label="Complexity Level"
value={getNestedValue(persona, ['linguistic_fingerprint', 'sentence_metrics', 'complexity_level'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'sentence_metrics', 'complexity_level'], val)}
placeholder="e.g., Moderate, Complex, Simple"
helperText="Overall sentence complexity"
tooltipInfo={corePersonaTooltips.complexityLevel}
/>
</Grid>
</Grid>
</Box>
{/* Lexical Features */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Lexical Features
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Go-To Words"
values={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'go_to_words'], [])}
onChange={(vals) => updateField(['linguistic_fingerprint', 'lexical_features', 'go_to_words'], vals)}
placeholder="Add frequently used words..."
color="primary"
helperText="Words frequently used in this writing style"
tooltipInfo={corePersonaTooltips.goToWords}
/>
<EditableChipArray
label="Go-To Phrases"
values={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'go_to_phrases'], [])}
onChange={(vals) => updateField(['linguistic_fingerprint', 'lexical_features', 'go_to_phrases'], vals)}
placeholder="Add signature phrases..."
color="secondary"
helperText="Signature phrases or expressions"
tooltipInfo={corePersonaTooltips.goToPhrases}
/>
<EditableChipArray
label="Avoid Words"
values={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'avoid_words'], [])}
onChange={(vals) => updateField(['linguistic_fingerprint', 'lexical_features', 'avoid_words'], vals)}
placeholder="Add words to avoid..."
color="error"
helperText="Words that should be avoided"
tooltipInfo={corePersonaTooltips.avoidWords}
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Contractions Usage"
value={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'contractions'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'lexical_features', 'contractions'], val)}
placeholder="e.g., Frequent, Occasional, Rare"
helperText="How often contractions are used"
tooltipInfo={corePersonaTooltips.contractions}
/>
<EditableTextField
label="Filler Words"
value={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'filler_words'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'lexical_features', 'filler_words'], val)}
placeholder="e.g., Minimal, Moderate"
helperText="Usage of filler words (um, uh, like, etc.)"
tooltipInfo={corePersonaTooltips.contractions}
/>
<EditableTextField
label="Vocabulary Level"
value={getNestedValue(persona, ['linguistic_fingerprint', 'lexical_features', 'vocabulary_level'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'lexical_features', 'vocabulary_level'], val)}
placeholder="e.g., Advanced, Intermediate, Accessible"
helperText="Overall sophistication of vocabulary"
tooltipInfo={corePersonaTooltips.vocabularyLevel}
/>
</Grid>
</Grid>
</Box>
{/* Rhetorical Devices */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Rhetorical Devices
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Metaphors"
value={getNestedValue(persona, ['linguistic_fingerprint', 'rhetorical_devices', 'metaphors'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'rhetorical_devices', 'metaphors'], val)}
multiline
placeholder="Describe metaphor usage..."
helperText="How metaphors are used in writing"
tooltipInfo={corePersonaTooltips.metaphors}
/>
<EditableTextField
label="Analogies"
value={getNestedValue(persona, ['linguistic_fingerprint', 'rhetorical_devices', 'analogies'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'rhetorical_devices', 'analogies'], val)}
multiline
placeholder="Describe analogy usage..."
helperText="How analogies are used to explain concepts"
tooltipInfo={corePersonaTooltips.analogies}
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Rhetorical Questions"
value={getNestedValue(persona, ['linguistic_fingerprint', 'rhetorical_devices', 'rhetorical_questions'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'rhetorical_devices', 'rhetorical_questions'], val)}
multiline
placeholder="Describe usage of rhetorical questions..."
helperText="How rhetorical questions are employed"
tooltipInfo={corePersonaTooltips.rhetoricalQuestions}
/>
<EditableTextField
label="Storytelling Style"
value={getNestedValue(persona, ['linguistic_fingerprint', 'rhetorical_devices', 'storytelling_style'])}
onChange={(val) => updateField(['linguistic_fingerprint', 'rhetorical_devices', 'storytelling_style'], val)}
multiline
placeholder="Describe storytelling approach..."
helperText="Narrative and storytelling techniques used"
tooltipInfo={corePersonaTooltips.storytelling}
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 3. Tonal Range Section */}
<SectionAccordion
title="Tonal Range"
subtitle="Voice tone and emotional characteristics"
icon={<TuneIcon />}
color="info.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Voice Characteristics
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Default Tone"
value={getNestedValue(persona, ['tonal_range', 'default_tone'])}
onChange={(val) => updateField(['tonal_range', 'default_tone'], val)}
placeholder="e.g., Professional yet approachable"
helperText="The primary tone used in most content"
tooltipInfo={corePersonaTooltips.defaultTone}
/>
<EditableTextField
label="Emotional Range"
value={getNestedValue(persona, ['tonal_range', 'emotional_range'])}
onChange={(val) => updateField(['tonal_range', 'emotional_range'], val)}
multiline
placeholder="Describe the emotional spectrum..."
helperText="Range of emotions expressed in writing"
tooltipInfo={corePersonaTooltips.emotionalRange}
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Permissible Tones"
values={getNestedValue(persona, ['tonal_range', 'permissible_tones'], [])}
onChange={(vals) => updateField(['tonal_range', 'permissible_tones'], vals)}
placeholder="Add acceptable tones..."
color="success"
helperText="Tones that fit this persona"
tooltipInfo={corePersonaTooltips.permissibleTones}
/>
<EditableChipArray
label="Forbidden Tones"
values={getNestedValue(persona, ['tonal_range', 'forbidden_tones'], [])}
onChange={(vals) => updateField(['tonal_range', 'forbidden_tones'], vals)}
placeholder="Add tones to avoid..."
color="error"
helperText="Tones that should be avoided"
tooltipInfo={corePersonaTooltips.forbiddenTones}
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 4. Stylistic Constraints Section */}
<SectionAccordion
title="Stylistic Constraints"
subtitle="Formatting and punctuation preferences"
icon={<FormatIcon />}
color="warning.main"
>
{/* Punctuation */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Punctuation Preferences
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<EditableTextField
label="Ellipses Usage"
value={getNestedValue(persona, ['stylistic_constraints', 'punctuation', 'ellipses'])}
onChange={(val) => updateField(['stylistic_constraints', 'punctuation', 'ellipses'], val)}
placeholder="e.g., Rarely, Never, Occasionally"
helperText="How ellipses (...) are used"
tooltipInfo={corePersonaTooltips.ellipses}
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Em-Dash Usage"
value={getNestedValue(persona, ['stylistic_constraints', 'punctuation', 'em_dash'])}
onChange={(val) => updateField(['stylistic_constraints', 'punctuation', 'em_dash'], val)}
placeholder="e.g., Frequent, Sparingly"
helperText="How em-dashes (—) are used"
tooltipInfo={corePersonaTooltips.emDash}
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Exclamation Points"
value={getNestedValue(persona, ['stylistic_constraints', 'punctuation', 'exclamation_points'])}
onChange={(val) => updateField(['stylistic_constraints', 'punctuation', 'exclamation_points'], val)}
placeholder="e.g., Minimal, Never, For emphasis"
helperText="How exclamation points are used"
tooltipInfo={corePersonaTooltips.exclamations}
/>
</Grid>
</Grid>
</Box>
{/* Formatting */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Formatting Preferences
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<EditableTextField
label="Paragraph Style"
value={getNestedValue(persona, ['stylistic_constraints', 'formatting', 'paragraphs'])}
onChange={(val) => updateField(['stylistic_constraints', 'formatting', 'paragraphs'], val)}
multiline
placeholder="Describe paragraph preferences..."
helperText="Paragraph length and structure"
tooltipInfo={corePersonaTooltips.paragraphs}
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Lists Preference"
value={getNestedValue(persona, ['stylistic_constraints', 'formatting', 'lists'])}
onChange={(val) => updateField(['stylistic_constraints', 'formatting', 'lists'], val)}
multiline
placeholder="Describe list usage..."
helperText="How and when to use lists"
tooltipInfo={corePersonaTooltips.lists}
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Markdown Usage"
value={getNestedValue(persona, ['stylistic_constraints', 'formatting', 'markdown'])}
onChange={(val) => updateField(['stylistic_constraints', 'formatting', 'markdown'], val)}
multiline
placeholder="Describe markdown preferences..."
helperText="Markdown formatting guidelines"
tooltipInfo={corePersonaTooltips.markdown}
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 5. Persona Generation Summary */}
<SectionAccordion
title="Persona Generation Summary"
subtitle="How your persona was created"
icon={<AssessmentIcon />}
color="success.main"
>
<Box sx={{
p: 4,
background: 'linear-gradient(135deg, #f0f9ff 0%, #e0f2fe 100%)',
border: '1px solid #0ea5e9',
borderRadius: 3,
borderLeft: '4px solid #0ea5e9'
}}>
<Typography variant="h6" gutterBottom sx={{ color: '#0c4a6e', fontWeight: 600, mb: 3 }}>
Your AI Writing Persona
</Typography>
<Typography variant="body2" paragraph sx={{ lineHeight: 1.8, color: '#0c4a6e' }}>
This persona was generated by analyzing comprehensive data from your website,
competitor research, sitemap analysis, and business context. Our AI examined
your writing style patterns, tone consistency, sentence structure, vocabulary
choices, and brand voice to create an authentic digital replica of your
communication style.
</Typography>
<Typography variant="body2" paragraph sx={{ lineHeight: 1.8, color: '#0c4a6e' }}>
The persona includes linguistic fingerprints (sentence metrics, lexical features,
rhetorical devices), tonal guidelines, and stylistic constraints that ensure
content generated across different platforms maintains your unique voice while
optimizing for each platform's best practices.
</Typography>
<Typography variant="body2" sx={{ lineHeight: 1.8, fontStyle: 'italic', color: '#0c4a6e' }}>
You can edit any field above to refine your persona. All changes are saved
automatically and will be used to generate content that truly sounds like you.
</Typography>
</Box>
</SectionAccordion>
</Box>
);
};
export default CorePersonaDisplay;

View File

@@ -0,0 +1,48 @@
# CorePersonaDisplay Tooltip Mappings
## Fields that need tooltipInfo added:
### Linguistic Fingerprint - Sentence Metrics
- avgSentenceLength
- sentenceType
- activePassiveRatio
- complexityLevel
### Linguistic Fingerprint - Lexical Features
- goToWords
- goToPhrases
- avoidWords
- contractions
- vocabularyLevel
### Linguistic Fingerprint - Rhetorical Devices
- metaphors
- analogies
- rhetoricalQuestions
- storytelling
### Tonal Range
- defaultTone
- permissibleTones
- forbiddenTones
- emotionalRange
### Stylistic Constraints - Punctuation
- ellipses
- emDash
- exclamations
### Stylistic Constraints - Formatting
- paragraphs
- lists
- markdown
### Confidence & Analysis
- confidenceScore
- analysisNotes
## Quick Reference for Adding:
```typescript
tooltipInfo={corePersonaTooltips.fieldName}
```

View File

@@ -0,0 +1,621 @@
import React from 'react';
import { Box, Grid, Typography, Chip } from '@mui/material';
import {
ContentPaste as ContentIcon,
TrendingUp as TrendingIcon,
Psychology as StrategyIcon,
EmojiEvents as FeaturesIcon,
Speed as AlgorithmIcon,
Business as ProfessionalIcon,
CheckCircle as BestPracticeIcon
} from '@mui/icons-material';
import { SectionAccordion } from '../components/SectionAccordion';
import { EditableTextField } from '../components/EditableTextField';
import { EditableChipArray } from '../components/EditableChipArray';
import { platformPersonaTooltips } from '../utils/personaTooltips';
interface PlatformPersonaDisplayProps {
platformPersona: any;
platformName: string;
onChange: (updatedPersona: any) => void;
}
/**
* Comprehensive display for Platform-Specific Persona data
* Shows all platform-optimized fields (LinkedIn example shown)
*/
export const PlatformPersonaDisplay: React.FC<PlatformPersonaDisplayProps> = ({
platformPersona,
platformName,
onChange
}) => {
// Helper function to update nested fields
const updateField = (path: string[], value: any) => {
const updatedPersona = { ...platformPersona };
let current = updatedPersona;
for (let i = 0; i < path.length - 1; i++) {
if (!current[path[i]]) {
current[path[i]] = {};
}
current = current[path[i]];
}
current[path[path.length - 1]] = value;
onChange(updatedPersona);
};
// Safe getter for nested properties
const getNestedValue = (obj: any, path: string[], defaultValue: any = '') => {
return path.reduce((current, key) => current?.[key], obj) ?? defaultValue;
};
const isLinkedIn = platformName.toLowerCase() === 'linkedin';
return (
<Box>
{/* Platform Overview */}
<Box sx={{
p: 3,
mb: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
textAlign: 'center'
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 1 }}>
{platformName.charAt(0).toUpperCase() + platformName.slice(1)} Persona
</Typography>
<Chip
label={getNestedValue(platformPersona, ['platform_type'], platformName)}
sx={{
background: 'linear-gradient(135deg, #3b82f6 0%, #1d4ed8 100%)',
color: 'white',
fontWeight: 600
}}
size="small"
/>
</Box>
{/* 1. Content Format Rules Section */}
<SectionAccordion
title="Content Format Rules"
subtitle="Platform-specific formatting guidelines"
icon={<ContentIcon />}
defaultExpanded={true}
color="primary.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Content Guidelines
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Character Limit"
value={getNestedValue(platformPersona, ['content_format_rules', 'character_limit'], '')}
onChange={(val) => updateField(['content_format_rules', 'character_limit'], Number(val))}
type="number"
placeholder="e.g., 3000"
helperText="Maximum characters allowed per post"
tooltipInfo={platformPersonaTooltips.characterLimit}
/>
<EditableTextField
label="Paragraph Structure"
value={getNestedValue(platformPersona, ['content_format_rules', 'paragraph_structure'])}
onChange={(val) => updateField(['content_format_rules', 'paragraph_structure'], val)}
multiline
placeholder="Describe ideal paragraph structure..."
helperText="How to structure paragraphs for this platform"
tooltipInfo={platformPersonaTooltips.paragraphStructure}
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Call-to-Action Style"
value={getNestedValue(platformPersona, ['content_format_rules', 'call_to_action_style'])}
onChange={(val) => updateField(['content_format_rules', 'call_to_action_style'], val)}
multiline
placeholder="Describe CTA approach..."
helperText="How to craft effective CTAs"
tooltipInfo={platformPersonaTooltips.ctaStyle}
/>
<EditableTextField
label="Link Placement"
value={getNestedValue(platformPersona, ['content_format_rules', 'link_placement'])}
onChange={(val) => updateField(['content_format_rules', 'link_placement'], val)}
multiline
placeholder="Where and how to place links..."
helperText="Best practices for link positioning"
tooltipInfo={platformPersonaTooltips.linkPlacement}
/>
</Grid>
</Grid>
</Box>
{/* Sentence Metrics */}
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Sentence Metrics
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={4}>
<EditableTextField
label="Max Sentence Length"
value={getNestedValue(platformPersona, ['sentence_metrics', 'max_sentence_length'], '')}
onChange={(val) => updateField(['sentence_metrics', 'max_sentence_length'], Number(val))}
type="number"
placeholder="e.g., 25"
helperText="Maximum words per sentence"
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Optimal Sentence Length"
value={getNestedValue(platformPersona, ['sentence_metrics', 'optimal_sentence_length'], '')}
onChange={(val) => updateField(['sentence_metrics', 'optimal_sentence_length'], Number(val))}
type="number"
placeholder="e.g., 15"
helperText="Ideal words per sentence"
/>
</Grid>
<Grid item xs={12} md={4}>
<EditableTextField
label="Sentence Variety"
value={getNestedValue(platformPersona, ['sentence_metrics', 'sentence_variety'])}
onChange={(val) => updateField(['sentence_metrics', 'sentence_variety'], val)}
placeholder="e.g., High, Moderate, Low"
helperText="Variety in sentence structure"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 2. Engagement Strategy Section */}
<SectionAccordion
title="Engagement Strategy"
subtitle="Posting and community interaction tactics"
icon={<TrendingIcon />}
color="secondary.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Community Engagement
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Posting Frequency"
value={getNestedValue(platformPersona, ['engagement_patterns', 'posting_frequency'])}
onChange={(val) => updateField(['engagement_patterns', 'posting_frequency'], val)}
placeholder="e.g., 3-5 times per week"
helperText="Recommended posting frequency"
tooltipInfo={platformPersonaTooltips.postingFrequency}
/>
<EditableTextField
label="Community Interaction"
value={getNestedValue(platformPersona, ['engagement_patterns', 'community_interaction'])}
onChange={(val) => updateField(['engagement_patterns', 'community_interaction'], val)}
multiline
placeholder="Describe community engagement approach..."
helperText="How to interact with community"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Optimal Posting Times"
values={getNestedValue(platformPersona, ['engagement_patterns', 'optimal_posting_times'], [])}
onChange={(vals) => updateField(['engagement_patterns', 'optimal_posting_times'], vals)}
placeholder="Add posting times..."
color="primary"
helperText="Best times to post for engagement"
tooltipInfo={platformPersonaTooltips.optimalTimes}
/>
<EditableChipArray
label="Engagement Tactics"
values={getNestedValue(platformPersona, ['engagement_patterns', 'engagement_tactics'], [])}
onChange={(vals) => updateField(['engagement_patterns', 'engagement_tactics'], vals)}
placeholder="Add engagement tactics..."
color="secondary"
helperText="Specific tactics to boost engagement"
tooltipInfo={platformPersonaTooltips.engagementTactics}
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 3. Lexical Adaptations Section */}
<SectionAccordion
title="Lexical Adaptations"
subtitle="Platform-specific language and expressions"
icon={<StrategyIcon />}
color="info.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Language & Expression
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Platform-Specific Words"
values={getNestedValue(platformPersona, ['lexical_adaptations', 'platform_specific_words'], [])}
onChange={(vals) => updateField(['lexical_adaptations', 'platform_specific_words'], vals)}
placeholder="Add platform-specific terms..."
color="primary"
helperText="Words and terms unique to this platform"
/>
<EditableTextField
label="Hashtag Strategy"
value={getNestedValue(platformPersona, ['lexical_adaptations', 'hashtag_strategy'])}
onChange={(val) => updateField(['lexical_adaptations', 'hashtag_strategy'], val)}
multiline
placeholder="Describe hashtag approach..."
helperText="How to use hashtags effectively"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Emoji Usage"
value={getNestedValue(platformPersona, ['lexical_adaptations', 'emoji_usage'])}
onChange={(val) => updateField(['lexical_adaptations', 'emoji_usage'], val)}
multiline
placeholder="Describe emoji usage..."
helperText="When and how to use emojis"
/>
<EditableTextField
label="Mention Strategy"
value={getNestedValue(platformPersona, ['lexical_adaptations', 'mention_strategy'])}
onChange={(val) => updateField(['lexical_adaptations', 'mention_strategy'], val)}
multiline
placeholder="Describe mention approach..."
helperText="How to mention others effectively"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* LinkedIn-specific sections */}
{isLinkedIn && (
<>
{/* 4. LinkedIn Features Section */}
<SectionAccordion
title="LinkedIn Features Optimization"
subtitle="Leverage LinkedIn-specific features"
icon={<FeaturesIcon />}
color="warning.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Platform Features
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Articles Strategy"
value={getNestedValue(platformPersona, ['linkedin_features', 'articles_strategy'])}
onChange={(val) => updateField(['linkedin_features', 'articles_strategy'], val)}
multiline
placeholder="How to use LinkedIn Articles..."
helperText="Strategy for long-form articles"
/>
<EditableTextField
label="Polls Optimization"
value={getNestedValue(platformPersona, ['linkedin_features', 'polls_optimization'])}
onChange={(val) => updateField(['linkedin_features', 'polls_optimization'], val)}
multiline
placeholder="How to create engaging polls..."
helperText="Best practices for LinkedIn polls"
/>
<EditableTextField
label="Events Networking"
value={getNestedValue(platformPersona, ['linkedin_features', 'events_networking'])}
onChange={(val) => updateField(['linkedin_features', 'events_networking'], val)}
multiline
placeholder="How to leverage LinkedIn Events..."
helperText="Strategy for events and networking"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Carousels Education"
value={getNestedValue(platformPersona, ['linkedin_features', 'carousels_education'])}
onChange={(val) => updateField(['linkedin_features', 'carousels_education'], val)}
multiline
placeholder="How to create carousel posts..."
helperText="Strategy for educational carousels"
/>
<EditableTextField
label="Live Discussions"
value={getNestedValue(platformPersona, ['linkedin_features', 'live_discussions'])}
onChange={(val) => updateField(['linkedin_features', 'live_discussions'], val)}
multiline
placeholder="How to host LinkedIn Live..."
helperText="Approach to live streaming"
/>
<EditableTextField
label="Native Video"
value={getNestedValue(platformPersona, ['linkedin_features', 'native_video'])}
onChange={(val) => updateField(['linkedin_features', 'native_video'], val)}
multiline
placeholder="Video content strategy..."
helperText="Best practices for native video"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 5. Algorithm Optimization Section */}
<SectionAccordion
title="Algorithm Optimization"
subtitle="Maximize reach and engagement"
icon={<AlgorithmIcon />}
color="error.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Algorithm Strategies
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Engagement Patterns"
values={getNestedValue(platformPersona, ['algorithm_optimization', 'engagement_patterns'], [])}
onChange={(vals) => updateField(['algorithm_optimization', 'engagement_patterns'], vals)}
placeholder="Add engagement patterns..."
color="primary"
helperText="Patterns that boost algorithmic reach"
/>
<EditableChipArray
label="Content Timing"
values={getNestedValue(platformPersona, ['algorithm_optimization', 'content_timing'], [])}
onChange={(vals) => updateField(['algorithm_optimization', 'content_timing'], vals)}
placeholder="Add timing strategies..."
color="secondary"
helperText="Timing strategies for maximum reach"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Professional Value Metrics"
values={getNestedValue(platformPersona, ['algorithm_optimization', 'professional_value_metrics'], [])}
onChange={(vals) => updateField(['algorithm_optimization', 'professional_value_metrics'], vals)}
placeholder="Add value metrics..."
color="info"
helperText="Metrics the algorithm values"
/>
<EditableChipArray
label="Network Interaction Strategies"
values={getNestedValue(platformPersona, ['algorithm_optimization', 'network_interaction_strategies'], [])}
onChange={(vals) => updateField(['algorithm_optimization', 'network_interaction_strategies'], vals)}
placeholder="Add interaction strategies..."
color="success"
helperText="How to interact with network"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 6. Professional Networking Section */}
<SectionAccordion
title="Professional Networking"
subtitle="Build thought leadership and authority"
icon={<ProfessionalIcon />}
color="success.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Leadership & Authority
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Thought Leadership Positioning"
value={getNestedValue(platformPersona, ['professional_networking', 'thought_leadership_positioning'])}
onChange={(val) => updateField(['professional_networking', 'thought_leadership_positioning'], val)}
multiline
placeholder="How to position as thought leader..."
helperText="Strategy for thought leadership"
/>
<EditableTextField
label="Industry Authority Building"
value={getNestedValue(platformPersona, ['professional_networking', 'industry_authority_building'])}
onChange={(val) => updateField(['professional_networking', 'industry_authority_building'], val)}
multiline
placeholder="How to build industry authority..."
helperText="Approach to establishing authority"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableChipArray
label="Professional Relationship Strategies"
values={getNestedValue(platformPersona, ['professional_networking', 'professional_relationship_strategies'], [])}
onChange={(vals) => updateField(['professional_networking', 'professional_relationship_strategies'], vals)}
placeholder="Add relationship strategies..."
color="primary"
helperText="Strategies for building relationships"
/>
<EditableTextField
label="Career Advancement Focus"
value={getNestedValue(platformPersona, ['professional_networking', 'career_advancement_focus'])}
onChange={(val) => updateField(['professional_networking', 'career_advancement_focus'], val)}
multiline
placeholder="Career advancement approach..."
helperText="How to focus on career growth"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
{/* 7. Professional Context Optimization */}
<SectionAccordion
title="Professional Context Optimization"
subtitle="Industry and audience-specific adaptations"
icon={<ProfessionalIcon />}
color="primary.dark"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3,
mb: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Context & Positioning
</Typography>
<Grid container spacing={3}>
<Grid item xs={12} md={6}>
<EditableTextField
label="Industry-Specific Positioning"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'industry_specific_positioning'])}
onChange={(val) => updateField(['professional_context_optimization', 'industry_specific_positioning'], val)}
multiline
placeholder="Industry-specific approach..."
helperText="How to position within your industry"
/>
<EditableTextField
label="Expertise Level Adaptation"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'expertise_level_adaptation'])}
onChange={(val) => updateField(['professional_context_optimization', 'expertise_level_adaptation'], val)}
multiline
placeholder="Expertise positioning..."
helperText="How to communicate expertise level"
/>
<EditableTextField
label="Company Size Considerations"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'company_size_considerations'])}
onChange={(val) => updateField(['professional_context_optimization', 'company_size_considerations'], val)}
multiline
placeholder="Company size strategy..."
helperText="Adaptations based on company size"
/>
<EditableTextField
label="Business Model Alignment"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'business_model_alignment'])}
onChange={(val) => updateField(['professional_context_optimization', 'business_model_alignment'], val)}
multiline
placeholder="Business model approach..."
helperText="How to align with business model"
/>
</Grid>
<Grid item xs={12} md={6}>
<EditableTextField
label="Professional Role Authority"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'professional_role_authority'])}
onChange={(val) => updateField(['professional_context_optimization', 'professional_role_authority'], val)}
multiline
placeholder="Role authority strategy..."
helperText="How to leverage professional role"
/>
<EditableChipArray
label="Demographic Targeting"
values={getNestedValue(platformPersona, ['professional_context_optimization', 'demographic_targeting'], [])}
onChange={(vals) => updateField(['professional_context_optimization', 'demographic_targeting'], vals)}
placeholder="Add target demographics..."
color="info"
helperText="Target audience demographics"
/>
<EditableTextField
label="Psychographic Engagement"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'psychographic_engagement'])}
onChange={(val) => updateField(['professional_context_optimization', 'psychographic_engagement'], val)}
multiline
placeholder="Psychographic approach..."
helperText="Engagement based on psychographics"
/>
<EditableTextField
label="Conversion Optimization"
value={getNestedValue(platformPersona, ['professional_context_optimization', 'conversion_optimization'])}
onChange={(val) => updateField(['professional_context_optimization', 'conversion_optimization'], val)}
multiline
placeholder="Conversion strategy..."
helperText="How to optimize for conversions"
/>
</Grid>
</Grid>
</Box>
</SectionAccordion>
</>
)}
{/* 8. Best Practices Section (for all platforms) */}
<SectionAccordion
title="Platform Best Practices"
subtitle="Recommended practices and tips"
icon={<BestPracticeIcon />}
color="success.main"
>
<Box sx={{
p: 3,
background: 'linear-gradient(135deg, #f8fafc 0%, #f1f5f9 100%)',
border: '1px solid #e2e8f0',
borderRadius: 3
}}>
<Typography variant="h6" sx={{ fontWeight: 600, color: '#1e293b', mb: 3 }}>
Best Practices
</Typography>
<EditableChipArray
label="Platform Best Practices"
values={getNestedValue(platformPersona, ['platform_best_practices'], [])}
onChange={(vals) => updateField(['platform_best_practices'], vals)}
placeholder="Add best practices..."
color="success"
helperText="Platform-specific recommendations and tips"
/>
</Box>
</SectionAccordion>
</Box>
);
};
export default PlatformPersonaDisplay;

View File

@@ -0,0 +1,8 @@
/**
* Persona Step Sections Index
* Export all persona display sections
*/
export { CorePersonaDisplay } from './CorePersonaDisplay';
export { PlatformPersonaDisplay } from './PlatformPersonaDisplay';

View File

@@ -0,0 +1,343 @@
/**
* Persona Tooltips and Insights
* Comprehensive explanations for every metric and field
*/
export interface TooltipInfo {
title: string;
description: string;
howWeCalculated: string;
whyItMatters: string;
example?: string;
}
/**
* Core Persona Tooltips
*/
export const corePersonaTooltips = {
// Identity Section
personaName: {
title: "Persona Name",
description: "A descriptive name that captures the essence of your writing personality and brand identity.",
howWeCalculated: "Generated by analyzing your writing style patterns, tone consistency, and brand positioning across all analyzed content.",
whyItMatters: "A memorable persona name helps you maintain consistency and makes it easier to switch between different writing contexts.",
example: "E.g., 'The Tech Educator', 'Strategic Storyteller', 'Data-Driven Advisor'"
},
archetype: {
title: "Writing Archetype",
description: "The fundamental character or role your writing embodies - defines how readers perceive you.",
howWeCalculated: "AI analyzed your content themes, communication style, and how you position yourself relative to your audience (teacher, peer, expert, etc.).",
whyItMatters: "Your archetype guides tone, structure, and content approach - ensuring your writing consistently reflects your intended professional image.",
example: "Expert Educator teaches, Innovator challenges conventions, Sage provides wisdom"
},
coreBelief: {
title: "Core Belief",
description: "The fundamental philosophy or conviction that drives your content and messaging.",
howWeCalculated: "Extracted from recurring themes, value statements, and the underlying message across your content. We looked at what you emphasize repeatedly.",
whyItMatters: "Your core belief creates authentic, purpose-driven content that resonates with your audience and builds trust over time.",
example: "'Knowledge should be accessible to everyone' or 'Data-driven decisions lead to success'"
},
brandVoice: {
title: "Brand Voice Description",
description: "A comprehensive characterization of your unique communication style and personality.",
howWeCalculated: "Synthesized from analyzing tone patterns, word choices, sentence structure, and how you engage with different topics across platforms.",
whyItMatters: "Consistent brand voice makes your content instantly recognizable and builds a stronger connection with your audience.",
example: "Professional yet approachable, confident without being arrogant, educational while staying engaging"
},
// Linguistic Fingerprint - Sentence Metrics
avgSentenceLength: {
title: "Average Sentence Length",
description: "The typical number of words per sentence in your writing - affects readability and pacing.",
howWeCalculated: "Analyzed 100+ sentences across your content, calculated mean word count, and identified your natural rhythm.",
whyItMatters: "Shorter sentences (10-15 words) are punchy and clear. Longer sentences (20-30 words) allow for more complex ideas. Your natural length affects engagement.",
example: "15 words = 'Clean and digestible'; 25 words = 'Detailed and thoughtful'"
},
sentenceType: {
title: "Preferred Sentence Type",
description: "The grammatical structure you naturally favor (simple, compound, complex, or compound-complex).",
howWeCalculated: "Parsed sentence structures using NLP to identify patterns in how you combine independent and dependent clauses.",
whyItMatters: "Sentence variety keeps readers engaged. Your preferred type reflects your communication sophistication and should match audience expectations.",
example: "Simple (one idea), Compound (two related ideas), Complex (main + supporting idea)"
},
activePassiveRatio: {
title: "Active to Passive Voice Ratio",
description: "How often you use active voice ('I analyzed data') vs passive voice ('Data was analyzed').",
howWeCalculated: "Used linguistic analysis to identify verb constructions and calculate the percentage of active vs passive sentences.",
whyItMatters: "Active voice (80:20 ratio) is more engaging and direct. Passive voice can add formality or objectivity when needed. Your ratio shows your natural authority level.",
example: "80:20 = Direct and engaging; 50:50 = More formal/academic"
},
complexityLevel: {
title: "Sentence Complexity Level",
description: "Overall sophistication of your sentence structures - simple, moderate, or complex.",
howWeCalculated: "Evaluated using Flesch-Kincaid readability metrics, clause depth, and vocabulary difficulty across your content.",
whyItMatters: "Complexity should match audience education level. Too simple feels condescending; too complex loses readers. We found your natural sweet spot.",
example: "Simple = Grade 8, Moderate = Grade 10-12, Complex = College+"
},
// Linguistic Fingerprint - Lexical Features
goToWords: {
title: "Go-To Words",
description: "Words and terms you use frequently that define your communication style.",
howWeCalculated: "Performed frequency analysis excluding common words, identified terms appearing 3x more than average in your industry.",
whyItMatters: "These signature words make your voice distinctive and memorable. They reveal your focus areas and expertise.",
example: "'innovative', 'strategic', 'actionable', 'framework', 'optimize'"
},
goToPhrases: {
title: "Go-To Phrases",
description: "Signature expressions and turns of phrase that make your writing uniquely yours.",
howWeCalculated: "Used n-gram analysis to find frequently repeated 2-5 word phrases unique to your writing style.",
whyItMatters: "Signature phrases build recognition and trust. They're your verbal brand markers that audiences associate with you.",
example: "'in my experience', 'here's the thing', 'let me show you', 'the reality is'"
},
avoidWords: {
title: "Words to Avoid",
description: "Terms that don't fit your authentic voice or that your analysis rarely uses.",
howWeCalculated: "Identified words common in your industry but conspicuously absent or rare in your content, suggesting conscious avoidance.",
whyItMatters: "Knowing what NOT to say is as important as what to say. These words might feel inauthentic or overused in your industry.",
example: "'basically', 'literally', 'synergy', 'leverage', 'disrupt' (if you avoid business jargon)"
},
contractions: {
title: "Contractions Usage",
description: "How often you use contractions ('don't' vs 'do not') - affects formality level.",
howWeCalculated: "Counted contraction frequency and compared to total verb phrases to determine your natural usage pattern.",
whyItMatters: "Frequent contractions = conversational and approachable. Rare contractions = formal and professional. Your pattern reflects your relationship with readers.",
example: "Frequent = casual/friendly; Occasional = balanced; Rare = formal/academic"
},
vocabularyLevel: {
title: "Vocabulary Level",
description: "The sophistication of words you choose - accessible, intermediate, or advanced.",
howWeCalculated: "Analyzed using Dale-Chall word lists and academic word frequency databases to classify your typical vocabulary tier.",
whyItMatters: "Vocabulary level must match your audience. Too basic = not credible; too advanced = loses readers. We found your effective range.",
example: "Accessible = common words; Intermediate = some technical terms; Advanced = specialized jargon"
},
// Linguistic Fingerprint - Rhetorical Devices
metaphors: {
title: "Metaphor Usage",
description: "How you use metaphorical language to explain concepts ('the market is a battlefield').",
howWeCalculated: "Identified figurative language patterns and categorized types of comparisons you frequently employ.",
whyItMatters: "Effective metaphors make complex ideas accessible and memorable. Your metaphor style reveals how you think and teach.",
example: "Business metaphors, sports analogies, nature comparisons, journey narratives"
},
analogies: {
title: "Analogy Strategy",
description: "How you use analogies to connect new concepts to familiar ones.",
howWeCalculated: "Detected 'like/as/similar to' patterns and analyzed the domains you draw comparisons from.",
whyItMatters: "Good analogies bridge knowledge gaps. Your analogy sources should resonate with your specific audience's experiences.",
example: "Tech explained through cooking, business through sports, strategy through chess"
},
rhetoricalQuestions: {
title: "Rhetorical Questions",
description: "How you use questions to engage readers without expecting literal answers.",
howWeCalculated: "Identified question patterns that appear in non-interrogative contexts and analyzed their positioning (openings, transitions).",
whyItMatters: "Rhetorical questions grab attention, create curiosity, and make readers think. Your usage pattern affects engagement flow.",
example: "'What if I told you...?', 'Sound familiar?', 'Here's the question:'"
},
storytelling: {
title: "Storytelling Style",
description: "How you incorporate narrative elements to make content engaging and relatable.",
howWeCalculated: "Detected narrative structures, personal anecdotes, and story-based explanations in your content.",
whyItMatters: "Stories make content memorable and emotional. Your storytelling approach determines how deeply readers connect with your message.",
example: "Personal anecdotes, case studies, hypothetical scenarios, customer journeys"
},
// Tonal Range
defaultTone: {
title: "Default Tone",
description: "The baseline emotional quality and attitude of your writing.",
howWeCalculated: "Sentiment analysis across 100+ pieces of content to identify your consistent emotional baseline and communication approach.",
whyItMatters: "Your default tone sets expectations. It should align with your brand and make your audience comfortable engaging with your content.",
example: "Professional yet approachable, confident and authoritative, friendly and supportive"
},
permissibleTones: {
title: "Permissible Tones",
description: "Tones you can authentically use while staying true to your brand voice.",
howWeCalculated: "Identified tonal variations that appeared naturally in your content without feeling forced or inconsistent.",
whyItMatters: "Tonal flexibility prevents monotony while maintaining authenticity. These tones expand your range without diluting your brand.",
example: "Inspirational, educational, analytical, conversational, empathetic"
},
forbiddenTones: {
title: "Forbidden Tones",
description: "Tones that feel inauthentic or contradict your established voice and brand.",
howWeCalculated: "Identified tones absent from your content that commonly appear in your industry, suggesting intentional avoidance.",
whyItMatters: "Knowing what to avoid prevents off-brand content. These tones would erode trust and confuse your audience.",
example: "Overly salesy, condescending, apologetic, pessimistic, aggressive"
},
emotionalRange: {
title: "Emotional Range",
description: "The spectrum of emotions you express in your writing, from calm to enthusiastic.",
howWeCalculated: "Analyzed emotional vocabulary, punctuation intensity, and sentiment strength across different content types.",
whyItMatters: "Emotional range creates engaging content that resonates. Too narrow = boring; too wide = inconsistent. Your range fits your brand.",
example: "Calm to moderately enthusiastic, thoughtful to inspired, objective to passionate"
},
// Stylistic Constraints - Punctuation
ellipses: {
title: "Ellipses Usage (...)",
description: "How you use ellipses for pauses, trailing thoughts, or dramatic effect.",
howWeCalculated: "Counted ellipses frequency and analyzed their contextual usage patterns in your writing.",
whyItMatters: "Ellipses create suspense or informality. Overuse can seem unprofessional; strategic use adds personality.",
example: "Rarely = professional; Occasionally = conversational; Frequent = very casual"
},
emDash: {
title: "Em-Dash Usage (—)",
description: "How you use em-dashes for emphasis, interruption, or additional information.",
howWeCalculated: "Analyzed em-dash frequency and function (parenthetical, emphasis, or dramatic pause).",
whyItMatters: "Em-dashes add sophistication and flow. They're more dynamic than commas but less formal than semicolons.",
example: "Frequent = sophisticated writer; Sparingly = traditional; Never = very formal"
},
exclamations: {
title: "Exclamation Points (!)",
description: "How you use exclamation points for emphasis and excitement.",
howWeCalculated: "Counted exclamation frequency and context (announcements, enthusiasm, urgency).",
whyItMatters: "Exclamations convey energy and emotion. Too many seem unprofessional; too few seem cold. Your usage fits your brand.",
example: "Minimal = very professional; Moderate = enthusiastic; Frequent = highly energetic"
},
// Stylistic Constraints - Formatting
paragraphs: {
title: "Paragraph Structure",
description: "Your typical paragraph length and organization style.",
howWeCalculated: "Analyzed average sentences per paragraph, paragraph transitions, and whitespace patterns.",
whyItMatters: "Paragraph length affects readability. Short paragraphs (3-4 sentences) are scannable; longer ones (6-8) are detailed. Your style fits your medium.",
example: "Short paragraphs = blog/social; Medium = articles; Long = academic/formal"
},
lists: {
title: "Lists Preference",
description: "How and when you use bulleted or numbered lists in your content.",
howWeCalculated: "Detected list frequency, type preferences (bullets vs numbers), and usage contexts.",
whyItMatters: "Lists improve scannability and comprehension. Your list style affects how readers process information.",
example: "Frequent bullets = practical/actionable; Numbered = sequential/ranked; Rare = narrative-focused"
},
markdown: {
title: "Markdown/Formatting Usage",
description: "How you use formatting like bold, italics, headers, and other text styling.",
howWeCalculated: "Analyzed formatting markup patterns across different content platforms and types.",
whyItMatters: "Strategic formatting guides attention and improves reading flow. Your style balances visual hierarchy with readability.",
example: "Heavy formatting = attention-guiding; Minimal = clean/traditional; Moderate = balanced"
},
};
/**
* Platform Persona Tooltips (LinkedIn-specific shown, similar for others)
*/
export const platformPersonaTooltips = {
// Content Format Rules
characterLimit: {
title: "Character Limit",
description: "Platform-specific maximum character count per post.",
howWeCalculated: "Based on official platform limits and optimal engagement data from platform research.",
whyItMatters: "Staying within limits ensures content isn't truncated. Knowing optimal ranges (often 50-70% of max) drives better engagement.",
example: "LinkedIn: 3,000 chars max, optimal 1,300-2,000 for highest engagement"
},
paragraphStructure: {
title: "Paragraph Structure",
description: "Platform-optimized paragraph formatting for maximum readability and engagement.",
howWeCalculated: "Analyzed top-performing content on this platform to identify optimal paragraph patterns and whitespace usage.",
whyItMatters: "Each platform has different reading behaviors. Mobile-first platforms need shorter paragraphs; desktop allows longer.",
example: "LinkedIn: 2-3 sentence paragraphs with line breaks for scannability"
},
ctaStyle: {
title: "Call-to-Action Style",
description: "How to craft effective CTAs that drive engagement on this specific platform.",
howWeCalculated: "Analyzed your successful CTAs and platform best practices to determine what drives action from your audience.",
whyItMatters: "Platform-specific CTA styles align with user behavior. LinkedIn users respond to professional invitations; Instagram to emotional appeals.",
example: "LinkedIn: 'What's your experience with this?' drives comments; 'Share your thoughts' drives shares"
},
linkPlacement: {
title: "Link Placement Strategy",
description: "Where and how to place links for optimal visibility and click-through rates.",
howWeCalculated: "Based on platform algorithms, user behavior data, and A/B testing results showing highest link engagement.",
whyItMatters: "Link placement affects both algorithm visibility and user clicks. Wrong placement can reduce reach by 50%+.",
example: "LinkedIn: First comment often better than in post body for algorithm"
},
// Engagement Strategy
postingFrequency: {
title: "Posting Frequency",
description: "Optimal posting cadence for maintaining visibility without overwhelming your audience.",
howWeCalculated: "Analyzed your historical engagement patterns, follower growth, and platform algorithm preferences for your niche.",
whyItMatters: "Posting too much causes unfollows; too little reduces visibility. Your optimal frequency balances growth and sustainability.",
example: "LinkedIn: 3-5x/week for max reach; daily can work for established accounts"
},
optimalTimes: {
title: "Optimal Posting Times",
description: "When your specific audience is most active and engaged on this platform.",
howWeCalculated: "Analyzed your audience timezone data, historical engagement patterns, and industry benchmarks for your sector.",
whyItMatters: "Posting when your audience is active increases initial engagement, which signals algorithms to boost your reach.",
example: "Tue-Thu 8-10am and 12-2pm often best for B2B; adjust for your audience"
},
engagementTactics: {
title: "Engagement Tactics",
description: "Specific strategies to increase likes, comments, shares, and meaningful interactions.",
howWeCalculated: "Identified tactics that correlate with your highest-performing content and match platform algorithm priorities.",
whyItMatters: "Platform algorithms reward engagement. These tactics are proven to work for your content type and audience.",
example: "Ask specific questions, respond within 60 mins, use polls, tag relevant people"
},
// Algorithm Optimization
algorithmInsights: {
title: "Algorithm Optimization",
description: "Platform-specific strategies to maximize content visibility and reach.",
howWeCalculated: "Based on documented platform algorithm factors, reverse-engineering of high-performing content, and your historical data.",
whyItMatters: "Algorithms determine who sees your content. Optimization can increase organic reach by 200-500%.",
example: "LinkedIn values dwell time, meaningful conversations, and professional network engagement"
}
};
/**
* Get tooltip info for a specific field
*/
export const getTooltip = (category: 'core' | 'platform', fieldKey: string): TooltipInfo | null => {
if (category === 'core') {
return corePersonaTooltips[fieldKey as keyof typeof corePersonaTooltips] || null;
} else {
return platformPersonaTooltips[fieldKey as keyof typeof platformPersonaTooltips] || null;
}
};
/**
* Format tooltip content for display
*/
export const formatTooltipContent = (tooltip: TooltipInfo): string => {
return `
📊 ${tooltip.title}
${tooltip.description}
🔍 How we calculated this:
${tooltip.howWeCalculated}
💡 Why it matters:
${tooltip.whyItMatters}
${tooltip.example ? `📝 Example: ${tooltip.example}` : ''}
`.trim();
};