Files
ALwrity/frontend/src/components/BlogWriter/DiffPreviewModal/DiffPreviewModal.tsx
ajaysi 923fa671fe feat: ContentGuardianAgent, onboarding UX, Team Activity action wiring, docs, agent help modal
ContentGuardianAgent consolidation:
- Merge 3 duplicate classes into single source in specialized/content_guardian.py
- Watchdog audit_committee() with heuristic scoring, coverage gaps, overlaps, alerts
- Remove misleading rejection_rate() helper; use acceptance_rate directly
- Integrate audit + alerts + trend signals into today_workflow_service.py

Team Activity page:
- QualityAuditPanel: health ring, per-agent critiques, coverage gaps, overlaps
- TrendSignalsPanel: opportunity cards with urgency/impact/coverage bars
- AlertBanner: persistent dismiss via POST /alerts/{id}/mark-read
- AgentHelpModal: dialog showing all 8 agents with descriptions, tools, schedule
- QualityAuditPanel action buttons: Fill gap -> /content-planning, Resolve overlap, View CTA on alerts/issues
- TrendSignalsPanel action buttons: Create content from this trend -> /blog-writer with trend context state

Onboarding system:
- Step 4 validation: no auto-pass via basic_ready; requires persona data or explicit progression
- Step 5 validation: logs warning on auto-pass without integration data
- OnboardingCompletionService: single DB session, transactional task creation, upsert pattern
- Business-without-website: nullable website_url on SIFIndexingTask and MarketTrendsTask
- DeepCompetitorAnalysisExecutor: 5-min timeout, 10-competitor cap, asyncio.wait_for
- Persona generation: async with 30s timeout, falls back to scheduler
- OnboardingProgressService.reset_onboarding(): resets session + pauses all DB tasks
- OnboardingControlService.reset_onboarding(): also cancels APScheduler jobs
- FinalStep TaskSchedulingPanel: shows scheduled/failed tasks after completion, 8s auto-redirect
- onboarding_completed agent activity event logged to feed

Documentation:
- docs-site/features/onboarding/: overview, steps, scheduler-tasks, technical-reference (4 pages)
- docs-site/mkdocs.yml: added Onboarding System nav section
- docs-site/features/sif-agents/: overview, agent-directory, committee-system, content-guardian (4 pages)
- docs-site/features/team-activity/: overview, quality-audit, trend-signals, alert-system (4 pages)
- docs-site/features/todays-workflow/: updated overview, technical-architecture, workflow-guide, api-reference
2026-06-01 12:24:31 +05:30

165 lines
5.4 KiB
TypeScript

import React from 'react';
import {
Dialog, DialogTitle, DialogContent, DialogActions,
Button, Typography, Box, Chip, IconButton, Divider
} from '@mui/material';
import { Close as CloseIcon, Check as CheckIcon } from '@mui/icons-material';
import type { DiffPreviewData, DiffSegment } from '../../../utils/getSectionDiffs';
interface DiffPreviewModalProps {
isOpen: boolean;
diffData: DiffPreviewData | null;
onAccept: () => void;
onReject: () => void;
loading?: boolean;
}
function renderDiffSegments(segments: DiffSegment[]): React.ReactNode {
return segments.map((seg, i) => {
if (seg.added) {
return (
<Box
component="span"
key={i}
sx={{
bgcolor: '#dcfce7',
color: '#166534',
px: 0.5,
borderRadius: '3px',
fontWeight: 600,
}}
>
{seg.value}
</Box>
);
}
if (seg.removed) {
return (
<Box
component="span"
key={i}
sx={{
bgcolor: '#fee2e2',
color: '#991b1b',
px: 0.5,
borderRadius: '3px',
textDecoration: 'line-through',
textDecorationColor: '#dc2626',
}}
>
{seg.value}
</Box>
);
}
return <span key={i}>{seg.value}</span>;
});
}
export const DiffPreviewModal: React.FC<DiffPreviewModalProps> = ({
isOpen,
diffData,
onAccept,
onReject,
loading = false,
}) => {
if (!diffData) return null;
const hasAnyChange = diffData.introductionChanged || diffData.sectionDiffs.some(s => s.changed);
return (
<Dialog open={isOpen} maxWidth="lg" fullWidth fullScreen>
<Box
sx={{
position: 'sticky',
top: 0,
zIndex: 10,
bgcolor: 'white',
borderBottom: '2px solid #e2e8f0',
}}
>
<DialogTitle sx={{ pb: 1, display: 'flex', alignItems: 'center', gap: 1.5 }}>
<Typography variant="h6" sx={{ fontWeight: 700, flexGrow: 1 }}>
SEO Recommendations Review Changes
</Typography>
<IconButton onClick={onReject} size="small"><CloseIcon /></IconButton>
</DialogTitle>
<Box sx={{ px: 3, pb: 1.5, display: 'flex', alignItems: 'center', gap: 2 }}>
<Chip
icon={<CheckIcon />}
label={`${diffData.sectionDiffs.filter(s => s.changed).length} section(s) changed`}
color="warning"
size="small"
variant="outlined"
/>
<Box sx={{ display: 'flex', gap: 1, alignItems: 'center', ml: 'auto' }}>
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center', fontSize: '0.75rem', color: '#166534' }}>
<Box sx={{ width: 14, height: 14, bgcolor: '#dcfce7', border: '1px solid #86efac', borderRadius: '2px' }} />
<span>Added</span>
</Box>
<Box sx={{ display: 'flex', gap: 0.5, alignItems: 'center', fontSize: '0.75rem', color: '#991b1b' }}>
<Box sx={{ width: 14, height: 14, bgcolor: '#fee2e2', border: '1px solid #fca5a5', borderRadius: '2px' }} />
<span>Removed</span>
</Box>
</Box>
</Box>
</Box>
<DialogContent sx={{ py: 3, bgcolor: '#f8fafc' }}>
{!hasAnyChange && (
<Typography sx={{ textAlign: 'center', py: 4, color: '#64748b' }}>
No changes detected between original and recommended content.
</Typography>
)}
{diffData.introductionChanged && (
<Box sx={{ mb: 4 }}>
<Typography sx={{ fontWeight: 700, fontSize: '0.85rem', textTransform: 'uppercase', letterSpacing: '0.05em', color: '#475569', mb: 1 }}>
Introduction
</Typography>
<Box sx={{ bgcolor: 'white', border: '1px solid #e2e8f0', borderRadius: 2, p: 2.5, fontFamily: 'Georgia, serif', fontSize: '1rem', lineHeight: 1.8, color: '#1e293b' }}>
{renderDiffSegments(diffData.introductionDiff!)}
</Box>
</Box>
)}
{diffData.sectionDiffs.map((section, idx) => {
if (!section.changed) return null;
return (
<Box key={section.heading || idx} sx={{ mb: 3 }}>
<Typography sx={{ fontWeight: 700, fontSize: '0.85rem', textTransform: 'uppercase', letterSpacing: '0.05em', color: '#475569', mb: 0.5 }}>
{section.heading}
</Typography>
<Box sx={{ bgcolor: 'white', border: '1px solid #e2e8f0', borderRadius: 2, p: 2.5, fontFamily: 'Georgia, serif', fontSize: '1rem', lineHeight: 1.8, color: '#1e293b' }}>
{renderDiffSegments(section.segments)}
</Box>
</Box>
);
})}
</DialogContent>
<DialogActions sx={{ px: 3, py: 2, borderTop: '1px solid #e2e8f0', bgcolor: 'white' }}>
<Button
onClick={onReject}
disabled={loading}
variant="outlined"
color="error"
sx={{ textTransform: 'none', fontWeight: 600 }}
>
Reject Changes
</Button>
<Button
onClick={onAccept}
disabled={loading}
variant="contained"
color="primary"
sx={{ textTransform: 'none', fontWeight: 600 }}
>
Accept Changes
</Button>
</DialogActions>
</Dialog>
);
};
export default DiffPreviewModal;