import type { ReactNode } from 'react'; import { ExternalLinkIcon, SparklesIcon, XIcon } from 'lucide-react'; import { classNames } from '../../lib/utils'; import { scoreTone, type AnalysisResult } from '../analysis/analyzer'; import { resultTone, todoSummary } from './result-tone'; export const TONE_CLASSES = { great: 'text-emerald-600 dark:text-emerald-400', good: 'text-amber-600 dark:text-amber-400', bad: 'text-red-600 dark:text-red-400', }; const TONE_STROKE = { great: 'stroke-emerald-500', good: 'stroke-amber-500', bad: 'stroke-red-500', }; const TONE_FILL = { great: '#059669', good: '#d97706', bad: '#dc2626', }; export const TONE_LABELS = { great: 'Great', good: 'Good', bad: 'Needs work', }; /** * The content score at the top of the panel, kept short so the focus * keyword and the snippet come right after it: the ring and the number, * how many tests passed, "Fix with AI" (the fix in place when the post can * take one, the Copilot otherwise) and one line to the tests that fail, * which the analysis lists with a fix beside each. Without a focus keyword * there is no score: the tests that need none still run and count. */ export const ScoreBadge = ({ score, passed, total, failing = [], onShowAll, onFix, fixUrl, children, }: { /** Null without a focus keyword: the score measures the page against it. */ score: number | null; passed: number; total: number; failing?: AnalysisResult[]; onShowAll?: () => void; /** Opens the fix in place; without it, the button opens the Copilot. */ onFix?: () => void; /** The Copilot with the post and its failing tests as the first turn. */ fixUrl?: string; /** More ways to act on the post, beside the Fix with AI button. */ children?: ReactNode; }): JSX.Element => { const tone = score === null ? null : scoreTone(score); const radius = 17; const circumference = 2 * Math.PI * radius; const fixable = (onFix || fixUrl) && failing.length > 0; return (
{tone && score !== null && ( )} {score ?? '–'} {tone ? ( {TONE_LABELS[tone]}{' '} · {score}/100 ) : ( No score yet{' '} · add a focus keyword )} {passed} of {total} tests passed
{(fixable || children) && (
{onFix && failing.length > 0 && ( )} {!onFix && fixUrl && failing.length > 0 && ( Fix in Copilot{' '} )} {children} {onFix && fixUrl && failing.length > 0 && ( Copilot )}
)} {failing.length > 0 && onShowAll && ( )}
); }; /** The score in the editor's toolbar button, as a coloured pill. */ export const ScoreIcon = ({ score }: { score: number | null }): JSX.Element => { const fill = score === null ? '#6b7280' : TONE_FILL[scoreTone(score)]; return ( ); };