import React, { useEffect, useState } from 'react'; import { FiCheckCircle, FiList, FiXCircle } from 'react-icons/fi'; import InfoTooltip from '../agent-analytics/InfoTooltip'; import type { AeoCheck, ArticleAeoChecklistResponse, } from '../../service/visibility/visibility.interface'; import { getArticleAeoChecklist } from '../../service/visibility/visibility.service'; import { scoreTextColor } from './helpers'; interface AeoChecklistPanelProps { clientId: string; token: string; articleId: string; } /** Display label + optional unit for each AEO signal key. */ const CHECK_META: Record = { answer_first: { label: 'Answer-first sections', unit: '%' }, structured_headings: { label: 'Structured headings' }, comparison_table: { label: 'Comparison table' }, lists: { label: 'Bulleted / numbered lists' }, short_paragraphs: { label: 'Short paragraphs' }, faq: { label: 'FAQ block' }, article_schema: { label: 'Article schema (JSON-LD)' }, faq_schema: { label: 'FAQ schema (JSON-LD)' }, title_length: { label: 'Concise title', unit: ' chars' }, meta_description: { label: 'Meta description', unit: ' chars' }, internal_links: { label: 'Internal links' }, external_references: { label: 'Authoritative references' }, freshness: { label: 'Freshness', unit: 'd' }, }; /** One signal row: pass/fail glyph, label, and the measured value. */ function CheckRow({ check }: { check: AeoCheck }): JSX.Element { const meta = CHECK_META[check.key] ?? { label: check.key }; const value = check.value !== null && check.value !== undefined ? `${check.value}${meta.unit ?? ''}` : null; return (
{check.passed ? ( ) : ( )} {meta.label}
{value !== null && {value}}
); } /** Coloured score number, e.g. "SEO 98". */ function ScoreStat({ label, score, tooltip, }: { label: string; score: number; tooltip?: string; }): JSX.Element { return ( {label}{' '} {score} {tooltip && } ); } /** * Article AEO checklist (Beta): the AI-extraction signals an article has vs is * missing, plus the composite AEO / SEO / Quality / Google-compliance scores. * Fetches client-side so it never blocks the article render. */ export function AeoChecklistPanel({ clientId, token, articleId, }: AeoChecklistPanelProps): JSX.Element | null { const [data, setData] = useState(null); const [loading, setLoading] = useState(true); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; setLoading(true); setError(null); getArticleAeoChecklist(clientId, token, articleId) .then(result => { if (!cancelled) setData(result); }) .catch(err => { if (!cancelled) setError( err instanceof Error ? err.message : 'Failed to load AEO checklist.' ); }) .finally(() => { if (!cancelled) setLoading(false); }); return () => { cancelled = true; }; }, [clientId, token, articleId]); // Nothing to show yet: stay silent rather than render an empty card. if (!loading && !error && !data) return null; const passed = data ? data.checks.filter(check => check.passed).length : 0; const total = data ? data.checks.length : 0; return (

AEO checklist

Beta
{data && ( AEO score{' '} {data.aeo_score} /100 )}

How extractable this article is for AI answers. Fix the red items to earn more citations.

{loading && (
)} {error &&

{error}

} {!loading && !error && data && ( <>
{`${passed}/${total}`} {' '} signals passing
{data.checks.map(check => ( ))}
)}
); } export default AeoChecklistPanel;