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 (