ALwrity version 0.5.5

This commit is contained in:
ajaysi
2025-08-13 17:38:54 +05:30
parent 66ece49705
commit 2b8c66c4d0
23 changed files with 3080 additions and 976 deletions

View File

@@ -1,404 +1,95 @@
import React, { useState, useEffect } from 'react';
import React, { useState } from 'react';
import {
Box,
Grid,
Paper,
Tabs,
Tab,
Typography,
Button,
TextField,
Card,
CardContent,
Chip,
Divider,
Alert,
CircularProgress,
List,
ListItem,
ListItemText,
ListItemIcon
Alert
} from '@mui/material';
import {
Analytics as AnalyticsIcon,
TrendingUp as TrendingIcon,
Search as SearchIcon,
Add as AddIcon,
Warning as WarningIcon,
CheckCircle as CheckCircleIcon,
TrendingUp as TrendingUpIcon,
Assessment as AssessmentIcon
Assessment as AssessmentIcon,
BarChart as BarChartIcon,
PieChart as PieChartIcon
} from '@mui/icons-material';
import { useContentPlanningStore } from '../../../stores/contentPlanningStore';
import { contentPlanningApi } from '../../../services/contentPlanningApi';
import RefineAnalysisTab from './RefineAnalysisTab';
import ContentOptimizerTab from './ContentOptimizerTab';
import TrendingTopicsTab from './TrendingTopicsTab';
import KeywordResearchTab from './KeywordResearchTab';
import PerformanceAnalyticsTab from './PerformanceAnalyticsTab';
import ContentPillarsTab from './ContentPillarsTab';
interface TabPanelProps {
children?: React.ReactNode;
index: number;
value: number;
}
function TabPanel(props: TabPanelProps) {
const { children, value, index, ...other } = props;
return (
<div
role="tabpanel"
hidden={value !== index}
id={`gap-analysis-tabpanel-${index}`}
aria-labelledby={`gap-analysis-tab-${index}`}
{...other}
>
{value === index && <Box>{children}</Box>}
</div>
);
}
const GapAnalysisTab: React.FC = () => {
const {
gapAnalyses,
loading,
error,
loadGapAnalyses,
analyzeContentGaps,
updateGapAnalyses
} = useContentPlanningStore();
const [analysisForm, setAnalysisForm] = useState({
website_url: '',
competitors: [] as string[],
keywords: [] as string[]
});
const [newCompetitor, setNewCompetitor] = useState('');
const [newKeyword, setNewKeyword] = useState('');
const [dataLoading, setDataLoading] = useState(false);
const [tabValue, setTabValue] = useState(0);
useEffect(() => {
loadGapAnalysisData();
}, []);
const loadGapAnalysisData = async () => {
try {
setDataLoading(true);
const response = await contentPlanningApi.getGapAnalysesSafe();
console.log('Gap Analysis Response:', response);
// Transform the backend response to match frontend expectations
if (response && response.gap_analyses) {
const transformedAnalyses = response.gap_analyses.map((analysis: any, index: number) => ({
id: analysis.id || `analysis_${index}`,
website_url: analysis.website_url || 'example.com',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
gaps: analysis.gaps || [],
recommendations: analysis.recommendations || [],
created_at: analysis.created_at || new Date().toISOString()
}));
console.log('Transformed Analyses:', transformedAnalyses);
// Update the store with transformed data
updateGapAnalyses(transformedAnalyses);
} else {
console.log('No gap analyses found in response');
updateGapAnalyses([]);
}
} catch (error) {
console.error('Error loading gap analysis data:', error);
updateGapAnalyses([]);
} finally {
setDataLoading(false);
}
const handleTabChange = (event: React.SyntheticEvent, newValue: number) => {
setTabValue(newValue);
};
const handleAddCompetitor = () => {
if (newCompetitor.trim() && !analysisForm.competitors.includes(newCompetitor.trim())) {
setAnalysisForm(prev => ({
...prev,
competitors: [...prev.competitors, newCompetitor.trim()]
}));
setNewCompetitor('');
}
};
const handleRemoveCompetitor = (competitorToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
competitors: prev.competitors.filter(comp => comp !== competitorToRemove)
}));
};
const handleAddKeyword = () => {
if (newKeyword.trim() && !analysisForm.keywords.includes(newKeyword.trim())) {
setAnalysisForm(prev => ({
...prev,
keywords: [...prev.keywords, newKeyword.trim()]
}));
setNewKeyword('');
}
};
const handleRemoveKeyword = (keywordToRemove: string) => {
setAnalysisForm(prev => ({
...prev,
keywords: prev.keywords.filter(keyword => keyword !== keywordToRemove)
}));
};
const handleRunAnalysis = async () => {
if (!analysisForm.website_url) {
return;
}
try {
setDataLoading(true);
await analyzeContentGaps({
website_url: analysisForm.website_url,
competitors: analysisForm.competitors,
keywords: analysisForm.keywords
});
// Reload data after analysis
await loadGapAnalyses();
// Reset form
setAnalysisForm({
website_url: '',
competitors: [],
keywords: []
});
} catch (error) {
console.error('Error running gap analysis:', error);
} finally {
setDataLoading(false);
}
};
// Ensure gapAnalyses is always an array and transform the data structure
const safeGapAnalyses = Array.isArray(gapAnalyses) ? gapAnalyses : [];
// Transform backend data structure to frontend expected structure
const transformedGapAnalyses = safeGapAnalyses.map((analysis, index) => {
// Handle the actual backend structure: { recommendations: [...] }
const recommendations = analysis.recommendations || [];
return {
id: analysis.id || `analysis-${index}`,
website_url: analysis.website_url || 'Unknown Website',
competitors: analysis.competitors || [],
keywords: analysis.keywords || [],
recommendations: recommendations,
created_at: analysis.created_at || new Date().toISOString(),
// Extract gaps from recommendations if available
gaps: recommendations.length > 0 ?
recommendations.filter((rec: any) => rec.type === 'gap').map((rec: any) => rec.title || rec.description || 'Content gap identified') :
[]
};
});
return (
<Box sx={{ p: 3 }}>
<Typography variant="h4" gutterBottom>
Content Gap Analysis
Gap Analysis & Optimization
</Typography>
{error && (
<Alert severity="error" sx={{ mb: 2 }}>
{error}
</Alert>
)}
<Box sx={{ borderBottom: 1, borderColor: 'divider', mb: 3 }}>
<Tabs value={tabValue} onChange={handleTabChange} aria-label="gap analysis tabs">
<Tab label="Content Optimizer" icon={<AnalyticsIcon />} iconPosition="start" />
<Tab label="Trending Topics" icon={<TrendingIcon />} iconPosition="start" />
<Tab label="Keyword Research" icon={<SearchIcon />} iconPosition="start" />
<Tab label="Performance Analytics" icon={<BarChartIcon />} iconPosition="start" />
<Tab label="Content Pillars" icon={<PieChartIcon />} iconPosition="start" />
<Tab label="Refine Analysis" icon={<AssessmentIcon />} iconPosition="start" />
</Tabs>
</Box>
<Grid container spacing={3}>
{/* Analysis Setup */}
<Grid item xs={12} md={4}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<SearchIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Analysis Setup
</Typography>
<Divider sx={{ mb: 2 }} />
<TextField
fullWidth
label="Website URL"
value={analysisForm.website_url}
onChange={(e) => setAnalysisForm(prev => ({ ...prev, website_url: e.target.value }))}
placeholder="https://example.com"
sx={{ mb: 2 }}
/>
<Typography variant="subtitle2" gutterBottom>
Competitors
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Competitor"
value={newCompetitor}
onChange={(e) => setNewCompetitor(e.target.value)}
placeholder="competitor.com"
onKeyPress={(e) => e.key === 'Enter' && handleAddCompetitor()}
/>
<Button
variant="outlined"
onClick={handleAddCompetitor}
disabled={!newCompetitor.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.competitors.map((competitor, index) => (
<Chip
key={index}
label={competitor}
onDelete={() => handleRemoveCompetitor(competitor)}
color="primary"
variant="outlined"
/>
))}
</Box>
<Typography variant="subtitle2" gutterBottom>
Keywords
</Typography>
<Box sx={{ display: 'flex', gap: 1, mb: 2 }}>
<TextField
fullWidth
label="Add Keyword"
value={newKeyword}
onChange={(e) => setNewKeyword(e.target.value)}
placeholder="target keyword"
onKeyPress={(e) => e.key === 'Enter' && handleAddKeyword()}
/>
<Button
variant="outlined"
onClick={handleAddKeyword}
disabled={!newKeyword.trim()}
>
<AddIcon />
</Button>
</Box>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1, mb: 3 }}>
{analysisForm.keywords.map((keyword, index) => (
<Chip
key={index}
label={keyword}
onDelete={() => handleRemoveKeyword(keyword)}
color="secondary"
variant="outlined"
/>
))}
</Box>
<Button
variant="contained"
fullWidth
onClick={handleRunAnalysis}
disabled={loading || dataLoading || !analysisForm.website_url}
startIcon={<AssessmentIcon />}
>
{loading || dataLoading ? 'Running Analysis...' : 'Run Gap Analysis'}
</Button>
</Paper>
</Grid>
<TabPanel value={tabValue} index={0}>
<ContentOptimizerTab />
</TabPanel>
{/* Content Gaps */}
<Grid item xs={12} md={8}>
<Paper sx={{ p: 3, mb: 3 }}>
<Typography variant="h6" gutterBottom>
<WarningIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Content Gaps
</Typography>
<Divider sx={{ mb: 2 }} />
{dataLoading ? (
<Box sx={{ display: 'flex', justifyContent: 'center', p: 3 }}>
<CircularProgress />
</Box>
) : transformedGapAnalyses.length === 0 ? (
<Typography variant="body2" color="text.secondary" sx={{ textAlign: 'center', py: 2 }}>
No previous analyses found. Run your first analysis to see results here.
</Typography>
) : (
<Grid container spacing={2}>
{transformedGapAnalyses.map((analysis) => (
<Grid item xs={12} md={6} lg={4} key={analysis.id}>
<Card>
<CardContent>
<Typography variant="h6" component="div">
{analysis.website_url}
</Typography>
<Typography variant="body2" color="text.secondary" sx={{ mb: 2 }}>
{new Date(analysis.created_at).toLocaleDateString()}
</Typography>
<Box sx={{ display: 'flex', flexWrap: 'wrap', gap: 1 }}>
<Chip
label={`${analysis.competitors?.length || 0} competitors`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.keywords?.length || 0} keywords`}
size="small"
variant="outlined"
/>
<Chip
label={`${analysis.gaps?.length || 0} gaps found`}
size="small"
color="warning"
/>
<Chip
label={`${analysis.recommendations?.length || 0} recommendations`}
size="small"
color="success"
/>
</Box>
</CardContent>
</Card>
</Grid>
))}
</Grid>
)}
</Paper>
<TabPanel value={tabValue} index={1}>
<TrendingTopicsTab />
</TabPanel>
{/* Detailed Analysis Results */}
{transformedGapAnalyses.length > 0 && (
<Paper sx={{ p: 3 }}>
<Typography variant="h6" gutterBottom>
<TrendingUpIcon sx={{ mr: 1, verticalAlign: 'middle' }} />
Detailed Analysis Results
</Typography>
<Divider sx={{ mb: 2 }} />
{transformedGapAnalyses.map((analysis, index) => (
<Box key={index} sx={{ mb: 3 }}>
<Typography variant="subtitle1" gutterBottom>
Analysis for {analysis.website_url}
</Typography>
{analysis.gaps && analysis.gaps.length > 0 && (
<Box sx={{ mb: 2 }}>
<Typography variant="subtitle2" gutterBottom>
Identified Content Gaps:
</Typography>
<List dense>
{analysis.gaps.map((gap, gapIndex) => (
<ListItem key={gapIndex}>
<ListItemIcon>
<WarningIcon color="warning" />
</ListItemIcon>
<ListItemText primary={gap} />
</ListItem>
))}
</List>
</Box>
)}
{analysis.recommendations && analysis.recommendations.length > 0 && (
<Box>
<Typography variant="subtitle2" gutterBottom>
Recommendations:
</Typography>
<List dense>
{analysis.recommendations.map((rec, recIndex) => (
<ListItem key={recIndex}>
<ListItemIcon>
<CheckCircleIcon color="success" />
</ListItemIcon>
<ListItemText
primary={rec.title || rec.description || 'Recommendation'}
secondary={rec.description}
/>
</ListItem>
))}
</List>
</Box>
)}
</Box>
))}
</Paper>
)}
</Grid>
</Grid>
<TabPanel value={tabValue} index={2}>
<KeywordResearchTab />
</TabPanel>
<TabPanel value={tabValue} index={3}>
<PerformanceAnalyticsTab />
</TabPanel>
<TabPanel value={tabValue} index={4}>
<ContentPillarsTab />
</TabPanel>
<TabPanel value={tabValue} index={5}>
<RefineAnalysisTab />
</TabPanel>
</Box>
);
};