Recovered critical missing components: PerformanceMonitor, MarketSignalDetector, and SemanticDashboard

This commit is contained in:
ajaysi
2026-02-08 14:06:09 +05:30
parent e404a86502
commit 43e66835ac
3 changed files with 1837 additions and 165 deletions

View File

@@ -0,0 +1,470 @@
import React, { useEffect, useState } from 'react';
import {
Box,
Card,
CardContent,
Typography,
Grid,
LinearProgress,
Chip,
IconButton,
Tooltip,
Alert,
Skeleton,
Button
} from '@mui/material';
import {
TrendingUp as TrendingUpIcon,
TrendingDown as TrendingDownIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
Refresh as RefreshIcon,
Info as InfoIcon,
Speed as SpeedIcon,
Assessment as AssessmentIcon,
Group as GroupIcon,
Lightbulb as LightbulbIcon
} from '@mui/icons-material';
import { motion, AnimatePresence } from 'framer-motion';
import { GlassCard, ShimmerHeader } from '../../shared/styled';
import { useSemanticDashboardStore } from '../../../stores/semanticDashboardStore';
import { SemanticHealthMetric, CompetitorSemanticSnapshot, ContentSemanticInsight } from '../../../api/semanticDashboard';
interface SemanticDashboardProps {
className?: string;
}
const SemanticDashboard: React.FC<SemanticDashboardProps> = ({ className }) => {
const {
semanticHealth,
competitorSnapshots,
contentInsights,
loading,
error,
lastUpdated,
monitoringStatus,
fetchSemanticHealth,
fetchCompetitorSnapshots,
fetchContentInsights,
fetchAllSemanticData,
refreshSemanticAnalysis,
getHealthStatusColor,
getInsightTypeColor,
getConfidenceColor
} = useSemanticDashboardStore();
const [refreshing, setRefreshing] = useState(false);
// Fetch semantic data on component mount
useEffect(() => {
fetchAllSemanticData();
}, []);
// Auto-refresh every 24 hours (86400000ms)
useEffect(() => {
const interval = setInterval(() => {
if (monitoringStatus === 'active') {
fetchAllSemanticData();
}
}, 86400000); // 24 hours
return () => clearInterval(interval);
}, [monitoringStatus, fetchAllSemanticData]);
const handleRefresh = async () => {
setRefreshing(true);
try {
await refreshSemanticAnalysis();
} catch (error) {
console.error('Failed to refresh semantic analysis:', error);
} finally {
setRefreshing(false);
}
};
const getStatusIcon = (status: string) => {
switch (status) {
case 'healthy':
return <CheckCircleIcon sx={{ color: '#4CAF50' }} />;
case 'warning':
return <WarningIcon sx={{ color: '#FF9800' }} />;
case 'critical':
return <WarningIcon sx={{ color: '#F44336' }} />;
default:
return <InfoIcon sx={{ color: '#9E9E9E' }} />;
}
};
const getTrendIcon = (trend: string) => {
switch (trend) {
case 'up':
return <TrendingUpIcon sx={{ color: '#4CAF50' }} />;
case 'down':
return <TrendingDownIcon sx={{ color: '#F44336' }} />;
default:
return <AssessmentIcon sx={{ color: '#9E9E9E' }} />;
}
};
const formatLastUpdated = (timestamp: string | null) => {
if (!timestamp) return 'Never';
const date = new Date(timestamp);
const now = new Date();
const diffMs = now.getTime() - date.getTime();
const diffHours = Math.floor(diffMs / (1000 * 60 * 60));
if (diffHours < 1) return 'Just now';
if (diffHours < 24) return `${diffHours}h ago`;
return `${Math.floor(diffHours / 24)}d ago`;
};
if (error) {
return (
<GlassCard className={className}>
<CardContent>
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
<Button
variant="outlined"
onClick={fetchAllSemanticData}
disabled={loading}
startIcon={<RefreshIcon />}
>
Retry
</Button>
</CardContent>
</GlassCard>
);
}
return (
<Box className={className}>
{/* Header */}
<Box display="flex" alignItems="center" justifyContent="space-between" mb={3}>
<Box display="flex" alignItems="center" gap={1}>
<AssessmentIcon sx={{ color: '#64B5F6', fontSize: 28 }} />
<Typography variant="h5" component="h2" sx={{ color: 'white', fontWeight: 600 }}>
Semantic Intelligence
</Typography>
<Chip
label={monitoringStatus === 'active' ? 'Active' : 'Inactive'}
color={monitoringStatus === 'active' ? 'success' : 'default'}
size="small"
sx={{ ml: 1 }}
/>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.7)' }}>
Updated: {formatLastUpdated(lastUpdated)}
</Typography>
<Tooltip title="Refresh semantic analysis">
<IconButton
onClick={handleRefresh}
disabled={loading || refreshing}
sx={{ color: 'rgba(255,255,255,0.7)' }}
>
<RefreshIcon />
</IconButton>
</Tooltip>
</Box>
</Box>
{/* Semantic Health Card */}
<GlassCard sx={{ mb: 3 }}>
<ShimmerHeader />
<CardContent>
<Box display="flex" alignItems="center" mb={2}>
<SpeedIcon sx={{ color: '#64B5F6', mr: 1 }} />
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
Semantic Health
</Typography>
</Box>
{loading && !semanticHealth ? (
<Box>
<Skeleton variant="rectangular" height={60} sx={{ mb: 2, borderRadius: 2 }} />
<Skeleton variant="text" width="80%" />
<Skeleton variant="text" width="60%" />
</Box>
) : semanticHealth ? (
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5 }}
>
<Box display="flex" alignItems="center" mb={2}>
{getStatusIcon(semanticHealth.status)}
<Box ml={2} flex={1}>
<Typography variant="h6" sx={{ color: 'white' }}>
{semanticHealth.metric_name.replace(/_/g, ' ').toUpperCase()}
</Typography>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
{semanticHealth.description}
</Typography>
</Box>
<Box textAlign="right">
<Typography variant="h4" sx={{ color: 'white', fontWeight: 700 }}>
{Math.round(semanticHealth.value * 100)}%
</Typography>
<LinearProgress
variant="determinate"
value={semanticHealth.value * 100}
sx={{
width: 100,
height: 6,
borderRadius: 3,
backgroundColor: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
backgroundColor: getHealthStatusColor(semanticHealth.status),
borderRadius: 3
}
}}
/>
</Box>
</Box>
{semanticHealth.recommendations.length > 0 && (
<Box>
<Typography variant="subtitle2" sx={{ color: 'rgba(255,255,255,0.8)', mb: 1 }}>
Recommendations:
</Typography>
{semanticHealth.recommendations.map((rec, index) => (
<Typography key={index} variant="body2" sx={{ color: 'rgba(255,255,255,0.7)', mb: 0.5 }}>
{rec}
</Typography>
))}
</Box>
)}
</motion.div>
) : (
<Typography sx={{ color: 'rgba(255,255,255,0.7)' }}>
No semantic health data available
</Typography>
)}
</CardContent>
</GlassCard>
{/* Competitor Semantic Analysis */}
<GlassCard sx={{ mb: 3 }}>
<ShimmerHeader />
<CardContent>
<Box display="flex" alignItems="center" mb={2}>
<GroupIcon sx={{ color: '#81C784', mr: 1 }} />
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
Competitor Semantic Positioning
</Typography>
</Box>
{loading && competitorSnapshots.length === 0 ? (
<Grid container spacing={2}>
{[1, 2, 3].map((i) => (
<Grid item xs={12} md={6} key={i}>
<Skeleton variant="rectangular" height={120} sx={{ borderRadius: 2 }} />
</Grid>
))}
</Grid>
) : competitorSnapshots.length > 0 ? (
<Grid container spacing={2}>
<AnimatePresence>
{competitorSnapshots.map((competitor, index) => (
<Grid item xs={12} md={6} key={competitor.competitor_id}>
<motion.div
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Card
sx={{
background: 'rgba(255,255,255,0.05)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 2
}}
>
<CardContent>
<Typography variant="h6" sx={{ color: 'white', mb: 1 }}>
{competitor.competitor_name}
</Typography>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={1}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
Semantic Overlap
</Typography>
<Typography variant="h6" sx={{ color: 'white' }}>
{Math.round(competitor.semantic_overlap * 100)}%
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center" mb={1}>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
Authority Score
</Typography>
<Typography variant="h6" sx={{ color: 'white' }}>
{Math.round(competitor.authority_score * 100)}
</Typography>
</Box>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.7)' }}>
Content Volume
</Typography>
<Typography variant="body2" sx={{ color: 'white' }}>
{competitor.content_volume} pages
</Typography>
</Box>
{competitor.trending_topics.length > 0 && (
<Box mt={2}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.7)', mb: 1, display: 'block' }}>
Trending Topics:
</Typography>
<Box display="flex" flexWrap="wrap" gap={0.5}>
{competitor.trending_topics.slice(0, 3).map((topic, idx) => (
<Chip
key={idx}
label={topic}
size="small"
sx={{
backgroundColor: 'rgba(255,255,255,0.1)',
color: 'white',
fontSize: '0.7rem'
}}
/>
))}
</Box>
</Box>
)}
</CardContent>
</Card>
</motion.div>
</Grid>
))}
</AnimatePresence>
</Grid>
) : (
<Typography sx={{ color: 'rgba(255,255,255,0.7)' }}>
No competitor semantic data available
</Typography>
)}
</CardContent>
</GlassCard>
{/* Content Insights */}
<GlassCard>
<ShimmerHeader />
<CardContent>
<Box display="flex" alignItems="center" mb={2}>
<LightbulbIcon sx={{ color: '#FFD54F', mr: 1 }} />
<Typography variant="h6" sx={{ color: 'white', fontWeight: 600 }}>
Content Intelligence Insights
</Typography>
</Box>
{loading && contentInsights.length === 0 ? (
<Box>
{[1, 2, 3].map((i) => (
<Box key={i} mb={2}>
<Skeleton variant="rectangular" height={80} sx={{ borderRadius: 2, mb: 1 }} />
</Box>
))}
</Box>
) : contentInsights.length > 0 ? (
<AnimatePresence>
{contentInsights.map((insight, index) => (
<motion.div
key={insight.insight_id}
initial={{ opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={{ duration: 0.5, delay: index * 0.1 }}
>
<Card
sx={{
background: 'rgba(255,255,255,0.05)',
backdropFilter: 'blur(10px)',
border: '1px solid rgba(255,255,255,0.1)',
borderRadius: 2,
mb: 2
}}
>
<CardContent>
<Box display="flex" alignItems="center" justifyContent="space-between" mb={1}>
<Box display="flex" alignItems="center" gap={1}>
<Chip
label={insight.insight_type.toUpperCase()}
size="small"
sx={{
backgroundColor: getInsightTypeColor(insight.insight_type),
color: 'white',
fontWeight: 600,
fontSize: '0.7rem'
}}
/>
<Typography variant="subtitle2" sx={{ color: 'white', fontWeight: 600 }}>
{insight.title}
</Typography>
</Box>
<Box display="flex" alignItems="center" gap={1}>
<Tooltip title={`Confidence: ${Math.round(insight.confidence_score * 100)}%`}>
<Box>
<LinearProgress
variant="determinate"
value={insight.confidence_score * 100}
sx={{
width: 60,
height: 4,
borderRadius: 2,
backgroundColor: 'rgba(255,255,255,0.2)',
'& .MuiLinearProgress-bar': {
backgroundColor: getConfidenceColor(insight.confidence_score),
borderRadius: 2
}
}}
/>
</Box>
</Tooltip>
</Box>
</Box>
<Typography variant="body2" sx={{ color: 'rgba(255,255,255,0.8)', mb: 1 }}>
{insight.description}
</Typography>
<Box display="flex" justifyContent="space-between" alignItems="center">
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
Impact Score: {Math.round(insight.impact_score * 100)}/100
</Typography>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.6)' }}>
Expires: {new Date(insight.expires_at).toLocaleDateString()}
</Typography>
</Box>
{insight.suggested_actions.length > 0 && (
<Box mt={1}>
<Typography variant="caption" sx={{ color: 'rgba(255,255,255,0.7)', mb: 0.5, display: 'block' }}>
Suggested Actions:
</Typography>
{insight.suggested_actions.map((action, idx) => (
<Typography key={idx} variant="caption" sx={{ color: 'rgba(255,255,255,0.6)', display: 'block' }}>
{action}
</Typography>
))}
</Box>
)}
</CardContent>
</Card>
</motion.div>
))}
</AnimatePresence>
) : (
<Typography sx={{ color: 'rgba(255,255,255,0.7)' }}>
No content insights available
</Typography>
)}
</CardContent>
</GlassCard>
</Box>
);
};
export default SemanticDashboard;