rai-bench / src /components /BenchmarkExplainer.tsx
rohanjaggi
fix: mobile responsiveness
fa3f2ca
Raw
History Blame Contribute Delete
12.2 kB
'use client'
import React from 'react'
type Mode = 'models' | 'guardrails'
interface ToggleOption {
key: string
label: string
subtitle: string
}
interface Props {
mode: Mode
onModeChange: (mode: Mode) => void
options: ToggleOption[]
}
const MODE_COLORS: Record<Mode, { color: string; dim: string }> = {
models: { color: '#BA2FA2', dim: 'rgba(186,47,162,0.07)' },
guardrails: { color: '#00C0F3', dim: 'rgba(0,192,243,0.07)' },
}
const TAB_ICONS: Record<Mode, (p: { color: string }) => React.ReactNode> = {
models: ({ color }) => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden>
<rect x="7" y="7" width="10" height="10" rx="2" stroke={color} strokeWidth="1.5"/>
<line x1="10" y1="7" x2="10" y2="4" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="14" y1="7" x2="14" y2="4" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="10" y1="17" x2="10" y2="20" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="14" y1="17" x2="14" y2="20" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="7" y1="10" x2="4" y2="10" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="7" y1="14" x2="4" y2="14" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="17" y1="10" x2="20" y2="10" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<line x1="17" y1="14" x2="20" y2="14" stroke={color} strokeWidth="1.5" strokeLinecap="round"/>
<rect x="10" y="10" width="4" height="4" rx="1" fill={color} opacity="0.4"/>
</svg>
),
guardrails: ({ color }) => (
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" aria-hidden>
<path d="M3 4h18l-7 7.5V19l-4 2V11.5L3 4z" stroke={color} strokeWidth="1.5" strokeLinejoin="round" strokeLinecap="round"/>
<line x1="7" y1="8.5" x2="17" y2="8.5" stroke={color} strokeWidth="1.2" strokeLinecap="round" opacity="0.45"/>
</svg>
),
}
const CRITERIA = [
{
num: '01',
color: '#00C0F3',
colorDim: 'rgba(0,192,243,0.10)',
title: 'Localised Undesired Content',
abbr: 'Refusal Rate',
tagline: 'Can the model refuse contextually harmful prompts?',
guardrailDetail: 'Scored on: Recall, Precision, F1 · Sub-tests: General, Physics, Career, JD',
modes: ['models', 'guardrails'] as Mode[],
Icon: ({ color }: { color: string }) => (
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden>
<path d="M14 2L4 6v8c0 5.5 4 10.4 10 12 6-1.6 10-6.5 10-12V6L14 2z"
stroke={color} strokeWidth="1.4" strokeLinejoin="round" fill="none" opacity="0.9" />
<line x1="9" y1="14" x2="13" y2="18" stroke={color} strokeWidth="1.6" strokeLinecap="round" />
<line x1="13" y1="18" x2="20" y2="10" stroke={color} strokeWidth="1.6" strokeLinecap="round" />
</svg>
),
},
{
num: '02',
color: '#6366F1',
colorDim: 'rgba(99,102,241,0.08)',
title: 'RAG Knowledge Robustness',
abbr: 'Robustness',
tagline: "Does it know what it doesn't know?",
guardrailDetail: null,
modes: ['models'] as Mode[],
Icon: ({ color }: { color: string }) => (
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden>
<circle cx="14" cy="14" r="3" stroke={color} strokeWidth="1.4" fill="none" />
<circle cx="6" cy="7" r="2" stroke={color} strokeWidth="1.2" fill="none" />
<circle cx="22" cy="7" r="2" stroke={color} strokeWidth="1.2" fill="none" />
<circle cx="22" cy="21" r="2" stroke={color} strokeWidth="1.2" fill="none" />
<line x1="14" y1="11" x2="8" y2="9" stroke={color} strokeWidth="1.2" strokeLinecap="round" />
<line x1="14" y1="11" x2="20" y2="9" stroke={color} strokeWidth="1.2" strokeLinecap="round" />
<line x1="14" y1="17" x2="20" y2="19" stroke={color} strokeWidth="1.2" strokeLinecap="round" strokeDasharray="2.5 2" opacity="0.5" />
<circle cx="6" cy="22" r="2" stroke={color} strokeWidth="1.2" fill="none" opacity="0.3" />
<line x1="14" y1="17" x2="7" y2="21" stroke={color} strokeWidth="1.2" strokeLinecap="round" strokeDasharray="2.5 2" opacity="0.25" />
</svg>
),
},
{
num: '03',
color: '#BA2FA2',
colorDim: 'rgba(186,47,162,0.10)',
title: 'Demographic Fairness',
abbr: 'Fairness Disparity',
tagline: 'Does it produce equitable outputs for all users?',
guardrailDetail: null,
modes: ['models'] as Mode[],
Icon: ({ color }: { color: string }) => (
<svg width="22" height="22" viewBox="0 0 28 28" fill="none" aria-hidden>
<line x1="14" y1="4" x2="14" y2="24" stroke={color} strokeWidth="1.3" strokeLinecap="round" opacity="0.6" />
<line x1="5" y1="11" x2="23" y2="11" stroke={color} strokeWidth="1.5" strokeLinecap="round" />
<path d="M5 11 Q5 18 9 18 Q13 18 13 11" stroke={color} strokeWidth="1.2" fill="none" />
<path d="M15 11 Q15 18 19 18 Q23 18 23 11" stroke={color} strokeWidth="1.2" fill="none" />
<line x1="10" y1="21" x2="18" y2="21" stroke={color} strokeWidth="1.2" strokeLinecap="round" opacity="0.5" />
<line x1="11" y1="23" x2="17" y2="23" stroke={color} strokeWidth="1.2" strokeLinecap="round" opacity="0.3" />
</svg>
),
},
]
export default function BenchmarkExplainer({ mode, onModeChange, options }: Props) {
const visibleCriteria = CRITERIA.filter(c => c.modes.includes(mode))
return (
<section className="explainer-section" id="criteria">
<div className="section-wrap" style={{ paddingBottom: '1.5rem' }}>
{/* Mode switcher — full-width toggle */}
<div className="mode-toggle-grid" style={{
display: 'grid', gridTemplateColumns: '1fr 1fr',
padding: 5,
background: 'var(--bg-0)',
border: '1.5px solid var(--border-1)',
borderRadius: 14,
marginBottom: '1.75rem',
gap: 5,
animation: 'fadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.1s both',
}}>
{options.map(o => {
const isActive = mode === o.key
const { color, dim } = MODE_COLORS[o.key as Mode]
const TabIcon = TAB_ICONS[o.key as Mode]
return (
<button
key={o.key}
className="mode-toggle-btn"
onClick={() => onModeChange(o.key as Mode)}
style={{
position: 'relative',
padding: '1.25rem 1.75rem',
border: 'none',
borderRadius: 10,
background: isActive ? 'var(--bg-1)' : 'transparent',
boxShadow: isActive ? '0 1px 4px rgba(0,0,0,0.08), 0 0 0 1px var(--border-1)' : 'none',
cursor: 'pointer',
textAlign: 'left',
transition: 'background 0.25s, box-shadow 0.25s',
display: 'block',
}}
>
{/* Top accent bar */}
<div style={{
position: 'absolute', top: 0, left: 12, right: 12, height: 3,
background: color, opacity: isActive ? 1 : 0,
borderRadius: '0 0 2px 2px',
transition: 'opacity 0.25s',
}} />
{/* Label row */}
<div style={{ display: 'flex', alignItems: 'center', gap: 12, marginBottom: 8 }}>
<div style={{
width: 36, height: 36, borderRadius: 8, flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: isActive ? dim : 'var(--bg-0)',
border: `1.5px solid ${isActive ? color : 'var(--border-1)'}`,
transition: 'all 0.2s',
}}>
<TabIcon color={isActive ? color : 'var(--border-2)'} />
</div>
<span style={{
fontSize: 18, fontWeight: 900, letterSpacing: '-0.01em',
color: isActive ? color : 'var(--text-2)',
fontFamily: 'var(--font-lato), sans-serif',
transition: 'color 0.2s',
}}>{o.label}</span>
</div>
{/* Subtitle */}
<p className="mode-toggle-subtitle" style={{
fontSize: 13, color: isActive ? 'var(--text-2)' : 'var(--text-3)',
margin: 0, lineHeight: 1.6, paddingLeft: 48,
transition: 'color 0.2s',
}}>{o.subtitle}</p>
</button>
)
})}
</div>
{/* Criteria rows — keyed by mode to trigger transition */}
<div key={mode} style={{ animation: 'fadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.2s both' }}>
<div style={{ display: 'flex', flexDirection: 'column', gap: 0, border: '1.5px solid var(--border-1)', borderRadius: 10, overflow: 'hidden', background: 'var(--bg-1)' }}>
{visibleCriteria.map((c, i) => (
<div
key={c.num}
style={{
display: 'flex',
alignItems: 'center',
gap: '1rem',
padding: '1.1rem 1.5rem',
borderBottom: i < visibleCriteria.length - 1 ? '1px solid var(--border-0)' : 'none',
}}
>
{/* Numbered icon badge */}
<div style={{
width: 40, height: 40, borderRadius: 8, flexShrink: 0,
display: 'flex', alignItems: 'center', justifyContent: 'center',
background: c.colorDim, position: 'relative',
}}>
<c.Icon color={c.color} />
<div style={{
position: 'absolute', top: -5, right: -5,
fontSize: 9, fontWeight: 900, fontFamily: 'inherit',
color: '#fff', background: c.color,
minWidth: 16, height: 16, borderRadius: 4,
display: 'flex', alignItems: 'center', justifyContent: 'center',
padding: '0 3px', lineHeight: 1,
}}>{c.num}</div>
</div>
{/* Content */}
<div style={{ flex: 1, minWidth: 0 }}>
<div style={{ display: 'flex', alignItems: 'center', gap: '0.75rem', flexWrap: 'wrap' }}>
<span style={{ fontSize: 15, fontWeight: 900, color: 'var(--text-0)' }}>{c.title}</span>
<span style={{
fontSize: 10, fontWeight: 700, letterSpacing: '0.06em', textTransform: 'uppercase',
color: c.color, background: c.colorDim, border: `1px solid ${c.color}`,
padding: '2px 7px', borderRadius: 4,
}}>{c.abbr}</span>
</div>
<p style={{ fontSize: 13, color: 'var(--text-2)', marginTop: 3, fontStyle: 'italic' }}>
&ldquo;{c.tagline}&rdquo;
</p>
{mode === 'guardrails' && c.guardrailDetail && (
<p style={{ fontSize: 11, color: 'var(--text-3)', marginTop: 4, fontStyle: 'normal' }}>
{c.guardrailDetail}
</p>
)}
</div>
</div>
))}
</div>
{/* CTA */}
<div className="explainer-cta" style={{ animation: 'fadeUp 0.5s cubic-bezier(0.16, 1, 0.3, 1) 0.3s both' }}>
<span className="explainer-cta-text">Want to understand what the scores mean?</span>
<a href="#about" className="explainer-cta-link">
Our methodology
<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<line x1="12" y1="5" x2="12" y2="19" />
<polyline points="19 12 12 19 5 12" />
</svg>
</a>
</div>
</div>
</div>
</section>
)
}