import React, { useState } from 'react'; import { useTranslation } from 'react-i18next'; import styled from '@emotion/styled'; import { Cpu, ShieldCheck, ShieldAlert, AlertOctagon, AlertTriangle, AlertCircle, FileText, Image as ImageIcon, Sparkles, ChevronDown, Plus, } from 'lucide-react'; import type { LucideIcon } from 'lucide-react'; import { typography } from '../../../theme'; import { localized } from '../../../utils/i18n-fields'; import { enumLabel } from '../../../utils/i18n-enums'; import type { AiTamperedResult, AiIndicatorDetected } from '../../../types/analysis-session'; import { HeadlineCard } from './HeadlineCard'; import { FindingCard, type FindingAccent } from './FindingCard'; import { SectionDivider } from './SectionDivider'; import { StatBlock, ChipsRow, Chip, CollapseBtn, CollapseBtnLeft, CollapseBtnChevron, } from '../styles'; const probAccent = (p: number): string => p >= 80 ? '#ef4444' : p >= 60 ? '#f97316' : p >= 40 ? '#eab308' : '#22c55e'; const probIcon = (p: number): LucideIcon => { if (p >= 80) return AlertOctagon; if (p >= 60) return AlertTriangle; if (p >= 40) return Cpu; return ShieldCheck; }; const indicatorAccent = (confidence: number): FindingAccent => { if (confidence >= 80) return 'critical'; if (confidence >= 60) return 'warning'; if (confidence >= 40) return 'info'; return 'neutral'; }; const verdictLabelKey = (verdict: string): string => { switch (verdict) { case 'LIKELY_AI': return 'aiTamper.verdicts.likelyAi'; case 'POSSIBLY_AI': return 'aiTamper.verdicts.possiblyAi'; case 'UNLIKELY_AI': return 'aiTamper.verdicts.unlikelyAi'; case 'LIKELY_HUMAN': return 'aiTamper.verdicts.likelyHuman'; case 'HUMAN': return 'aiTamper.verdicts.humanWritten'; default: return ''; } }; const CATEGORY_LABEL_KEYS: Record = { T1: 'aiTamper.categories.writingStyle', T2: 'aiTamper.categories.contentPatterns', T3: 'aiTamper.categories.structuralAnalysis', T4: 'aiTamper.categories.statisticalMarkers', T5: 'aiTamper.categories.explicitSignals', }; const DEFAULT_VISIBLE_INDICATORS = 5; /** Display-friendly shape that flattens AiTamperedResult variations across modes. */ export interface AiDisplayResult { verdict: string; ai_probability: number; disclosure_detected?: boolean; disclosure_explicit?: boolean; disclosure_text?: string | null; /** Quick mode text indicators */ indicators_found?: string[]; /** Deep mode text + audio/video indicators */ indicators_detected?: AiIndicatorDetected[]; categories_affected?: string[]; coupling_context?: AiTamperedResult['coupling_context']; image_indicators?: string[]; image_evidence?: string; model_used?: string; transcript?: string; } /** Build AiDisplayResult from a raw AiTamperedResult + optional input meta. */ export function toAiDisplayResult( r: AiTamperedResult, ctx: { inputType?: string; inputText?: string | null } = {}, ): AiDisplayResult { const out: AiDisplayResult = { verdict: r.verdict, ai_probability: r.ai_probability, disclosure_detected: r.disclosure_detected, disclosure_explicit: r.disclosure_explicit, disclosure_text: r.disclosure_text, }; if (r.indicators_detected && r.indicators_detected.length > 0) { out.indicators_detected = r.indicators_detected; out.categories_affected = r.categories_affected; out.coupling_context = r.coupling_context; } if (r.image_analysis?.indicators && r.image_analysis.indicators.length > 0) { out.image_indicators = r.image_analysis.indicators; out.image_evidence = r.image_analysis.evidence; out.model_used = r.image_analysis.model_used; } if (ctx.inputText && (ctx.inputType === 'audio' || ctx.inputType === 'video')) { out.transcript = ctx.inputText; } return out; } interface Props { result: AiDisplayResult; isRo: boolean; /** When set, a "try deep analysis" CTA is shown inside the clean-content card. Used by the live standalone where the user can re-run in deep mode. History view should omit this. */ onTryDeepScan?: () => void; } export const AiResults: React.FC = ({ result, isRo, onTryDeepScan }) => { const { t } = useTranslation(); const [indicatorsExpand, setIndicatorsExpand] = useState(false); const probability = result.ai_probability || 0; const verdict = result.verdict || ''; const accent = probAccent(probability); const Icon = probIcon(probability); const verdictText = verdictLabelKey(verdict) ? t(verdictLabelKey(verdict)) : (verdict || '').replace(/_/g, ' '); const tldr = (() => { const indCount = result.indicators_detected?.length || result.indicators_found?.length || result.image_indicators?.length || 0; const disclosureNote = result.disclosure_detected === true ? (isRo ? ' cu declarație AI' : ' with AI disclosure') : result.disclosure_detected === false ? (isRo ? ' fără declarație AI' : ' without AI disclosure') : ''; if (isRo) { return `Probabilitate ${probability}% de generare AI${disclosureNote}${indCount > 0 ? ` · ${indCount} ${indCount === 1 ? 'indicator detectat' : 'indicatori detectați'}` : ''}.`; } return `${probability}% likelihood of AI generation${disclosureNote}${indCount > 0 ? ` · ${indCount} ${indCount === 1 ? 'indicator detected' : 'indicators detected'}` : ''}.`; })(); const totalIndicators = result.indicators_detected?.length ?? 0; const visibleCount = indicatorsExpand ? totalIndicators : Math.min(DEFAULT_VISIBLE_INDICATORS, totalIndicators); const visibleIndicators = result.indicators_detected?.slice(0, visibleCount) ?? []; const remainingIndicators = totalIndicators - visibleCount; return ( <> {result.coupling_context?.for_verdict?.confidence_level && ( <> {isRo ? 'încredere' : 'confidence'} {enumLabel(result.coupling_context.for_verdict.confidence_level).toLowerCase()} {result.disclosure_detected !== undefined && ·} )} {result.disclosure_detected === true && ( {isRo ? 'cu declarație AI' : 'AI disclosure'} )} {result.disclosure_detected === false && ( {isRo ? 'fără declarație AI' : 'no AI disclosure'} )} } tldr={tldr} /> {(result.coupling_context?.for_verdict || (result.categories_affected && result.categories_affected.length > 0) || result.model_used) && ( {result.coupling_context?.for_verdict?.undisclosed_ai && ( {t('aiTamper.undisclosedAi')} )} {result.coupling_context?.for_verdict?.needs_manual_review && ( {t('aiTamper.needsManualReview')} )} {result.categories_affected?.map(cat => ( {cat} · {CATEGORY_LABEL_KEYS[cat] ? t(CATEGORY_LABEL_KEYS[cat]) : cat} ))} {result.model_used && ( {result.model_used} )} )} {result.disclosure_detected !== undefined && ( )} {result.image_evidence && ( )} {result.transcript && ( 320 ? result.transcript.slice(0, 320) + '…' : result.transcript} /> )} {result.indicators_found && result.indicators_found.length > 0 && ( <> {result.indicators_found.map((ind, i) => ( ))} )} {result.image_indicators && result.image_indicators.length > 0 && ( <> {result.image_indicators.map((ind, i) => ( ))} )} {totalIndicators > 0 && ( <> {visibleIndicators.map(ind => { const catLabel = CATEGORY_LABEL_KEYS[ind.category] ? t(CATEGORY_LABEL_KEYS[ind.category]) : ind.category; const eyebrowParts = [ ind.category, catLabel, `${isRo ? 'încredere' : 'confidence'} ${ind.confidence}%`, ]; return ( ); })} {remainingIndicators > 0 && ( setIndicatorsExpand(true)}> {isRo ? `Vezi celelalți ${remainingIndicators} ${remainingIndicators === 1 ? 'indicator' : 'indicatori'}` : `Show ${remainingIndicators} more ${remainingIndicators === 1 ? 'indicator' : 'indicators'}`} )} {indicatorsExpand && totalIndicators > DEFAULT_VISIBLE_INDICATORS && ( setIndicatorsExpand(false)}> {isRo ? 'Ascunde' : 'Hide'} )} )} {(result.indicators_found?.length === 0 && !result.indicators_detected && !result.image_indicators?.length) && ( {t('aiTamper.tryDeepAnalysis')} ) : undefined } /> )} ); }; const DeepScanHint = styled.button` padding: 0; border: none; background: none; font-family: ${typography.fontFamily.primary}; font-size: 12px; font-weight: ${typography.fontWeight.semibold}; color: var(--accent-text); cursor: pointer; text-decoration: underline; text-underline-offset: 2px; &:hover { color: var(--accent-hover); } `;