| import { BrainCircuit, ShieldAlert, ShieldCheck, Scale, Sparkles } from 'lucide-react'; |
| import styles from './AssessmentPanels.module.css'; |
|
|
| function SectionCard({ title, icon: Icon, tone = 'neutral', subtitle, children }) { |
| return ( |
| <section className={`${styles.card} ${styles[`tone_${tone}`]}`}> |
| <div className={styles.header}> |
| <div className={styles.titleWrap}> |
| <div className={styles.iconWrap}><Icon size={16} /></div> |
| <div> |
| <h3 className={styles.title}>{title}</h3> |
| {subtitle ? <p className={styles.subtitle}>{subtitle}</p> : null} |
| </div> |
| </div> |
| </div> |
| <div className={styles.body}>{children}</div> |
| </section> |
| ); |
| } |
|
|
| function Row({ label, value, mono = false, strong = false }) { |
| return ( |
| <div className={styles.row}> |
| <span className={styles.label}>{label}</span> |
| <span className={`${styles.value} ${mono ? styles.mono : ''} ${strong ? styles.strong : ''}`}>{value}</span> |
| </div> |
| ); |
| } |
|
|
| export default function AssessmentPanels({ result }) { |
| const ethical = result.ethical; |
| const neurosymbolic = result.neurosymbolic; |
| if (!ethical && !neurosymbolic) return null; |
|
|
| const ethicalTone = ethical ? (ethical.is_ethical ? 'positive' : 'critical') : 'neutral'; |
| const nsTone = neurosymbolic?.decision === 'SYMBOLIC_SUPPORTS_AI' ? 'critical' : 'neutral'; |
|
|
| return ( |
| <div className={styles.grid}> |
| {neurosymbolic && ( |
| <SectionCard |
| title="Neurosymbolic Analysis" |
| icon={BrainCircuit} |
| tone={nsTone} |
| subtitle="Symbolic rules layered over the model output for transparent forensic reasoning" |
| > |
| <Row label="Decision" value={neurosymbolic.decision?.replaceAll('_', ' ')} strong /> |
| <Row label="Symbolic Score" value={`${((neurosymbolic.symbolic_score || 0) * 100).toFixed(1)}%`} /> |
| {neurosymbolic.features && ( |
| <> |
| <Row label="Residual Std" value={(neurosymbolic.features.residual_std || 0).toFixed(4)} mono /> |
| <Row label="HF Ratio" value={(neurosymbolic.features.hf_ratio || 0).toFixed(4)} mono /> |
| <Row label="LBP Entropy" value={(neurosymbolic.features.lbp_entropy || 0).toFixed(4)} mono /> |
| </> |
| )} |
| <div className={styles.ruleBlock}> |
| <div className={styles.ruleTitle}><Sparkles size={14} />Triggered Rules</div> |
| {neurosymbolic.fired_rules?.length ? neurosymbolic.fired_rules.map((rule) => ( |
| <div key={rule.rule} className={styles.ruleItem}> |
| <div className={styles.ruleHead}> |
| <span className={styles.ruleCode}>{rule.rule}</span> |
| <span className={styles.ruleWeight}>+{Number(rule.weight || 0).toFixed(2)}</span> |
| </div> |
| <p className={styles.ruleReason}>{rule.reason}</p> |
| </div> |
| )) : <p className={styles.empty}>No symbolic rules fired.</p>} |
| </div> |
| </SectionCard> |
| )} |
|
|
| {ethical && ( |
| <SectionCard |
| title="Ethical Assessment" |
| icon={ethical.is_ethical ? ShieldCheck : ShieldAlert} |
| tone={ethicalTone} |
| subtitle="Risk-oriented interpretation layer for sensitive or harmful synthetic content" |
| > |
| <Row label="Status" value={ethical.status || 'UNKNOWN'} strong /> |
| <Row label="Risk Score" value={`${((ethical.riskScore || 0) * 100).toFixed(1)}%`} /> |
| <Row label="Simple Verdict" value={ethical.simpleStatus || 'Unavailable'} /> |
| <div className={styles.flagBlock}> |
| <div className={styles.ruleTitle}><Scale size={14} />Flags</div> |
| <div className={styles.flagList}> |
| {ethical.flags?.length ? ethical.flags.map((flag) => ( |
| <span key={flag} className={styles.flag}>{flag.replaceAll('_', ' ')}</span> |
| )) : <p className={styles.empty}>No ethical flags raised.</p>} |
| </div> |
| </div> |
| </SectionCard> |
| )} |
| </div> |
| ); |
| } |
|
|