Spaces:
Running
Running
| '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' }}> | |
| “{c.tagline}” | |
| </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> | |
| ) | |
| } | |