/** * Shared building blocks for the Safety & Compliance pages: published-run * selection (safety analyses require published megaeval runs) and a compact * markdown renderer for evidence packages. */ import { useEffect, useMemo, useState, type ReactNode } from 'react'; import Box from '@mui/material/Box'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import type { EvaluationRunInfo } from '../../types/megaeval'; import { getRuns } from '../../services/megaeval'; import { tokens } from '../../theme'; // ---------------------------------------------------------------- runs hook export function usePublishedRuns() { const [runs, setRuns] = useState(null); const [error, setError] = useState(null); useEffect(() => { let cancelled = false; getRuns() .then((r) => { if (cancelled) return; const published = r.runs .filter((x) => x.status === 'published') .sort((a, b) => (b.created_at ?? '').localeCompare(a.created_at ?? '')); setRuns(published); }) .catch((e) => { if (!cancelled) setError(e instanceof Error ? e.message : String(e)); }); return () => { cancelled = true; }; }, []); return { runs, error }; } export function RunSelect({ label, value, onChange, runs, exclude, }: { label: string; value: string | null; onChange: (id: string) => void; runs: EvaluationRunInfo[] | null; exclude?: string | null; }) { const options = useMemo(() => (runs ?? []).filter((r) => r.run_id !== exclude), [runs, exclude]); return ( onChange(e.target.value)} sx={{ minWidth: 260 }} > {options.map((r) => ( {r.run_id.slice(0, 14)} {r.model_version} ))} ); } // ---------------------------------------------------------------- markdown-lite /** Minimal markdown renderer (headings, bold, code, lists, tables, hr) — * enough for the generated Safety Evidence Package; no external deps. */ export function MarkdownLite({ text }: { text: string }) { const blocks = useMemo(() => parseBlocks(text), [text]); return *:first-of-type': { mt: 0 } }}>{blocks}; } function inline(text: string): ReactNode[] { // **bold** and `code` const parts: ReactNode[] = []; const re = /(\*\*[^*]+\*\*|`[^`]+`)/g; let last = 0; let m: RegExpExecArray | null; let key = 0; while ((m = re.exec(text)) !== null) { if (m.index > last) parts.push(text.slice(last, m.index)); const tok = m[0]; if (tok.startsWith('**')) { parts.push({tok.slice(2, -2)}); } else { parts.push( {tok.slice(1, -1)} ); } last = m.index + tok.length; } if (last < text.length) parts.push(text.slice(last)); return parts; } function parseBlocks(text: string): ReactNode[] { const lines = text.split('\n'); const out: ReactNode[] = []; let i = 0; let key = 0; while (i < lines.length) { const line = lines[i]; if (/^\s*$/.test(line)) { i += 1; continue; } const h = line.match(/^(#{1,4})\s+(.*)$/); if (h) { const level = h[1].length; out.push( = 3 ? tokens.color.info : undefined }} > {inline(h[2])} ); i += 1; continue; } if (/^\s*(---|\*\*\*)\s*$/.test(line)) { out.push(); i += 1; continue; } if (/^\s*[-*]\s+/.test(line)) { const items: string[] = []; while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) { items.push(lines[i].replace(/^\s*[-*]\s+/, '')); i += 1; } out.push( {items.map((it, j) => ( {inline(it)} ))} ); continue; } if (/^\s*\|.*\|\s*$/.test(line)) { const rows: string[][] = []; while (i < lines.length && /^\s*\|.*\|\s*$/.test(lines[i])) { const cells = lines[i].trim().slice(1, -1).split('|').map((c) => c.trim()); if (!cells.every((c) => /^:?-+:?$/.test(c))) rows.push(cells); i += 1; } if (rows.length) { const [head, ...body] = rows; out.push( {head.map((c, j) => {inline(c)})} {body.map((r, ri) => ( {r.map((c, j) => {inline(c)})} ))} ); } continue; } // paragraph: gather consecutive plain lines const para: string[] = [line]; i += 1; while (i < lines.length && !/^\s*$/.test(lines[i]) && !/^(#{1,4})\s|^\s*[-*]\s|^\s*\|/.test(lines[i])) { para.push(lines[i]); i += 1; } out.push( {inline(para.join(' '))} ); } return out; } /** Trigger a client-side JSON download. */ export function downloadJson(name: string, data: unknown) { const blob = new Blob([JSON.stringify(data, null, 2)], { type: 'application/json' }); const url = URL.createObjectURL(blob); const a = document.createElement('a'); a.href = url; a.download = name; a.click(); URL.revokeObjectURL(url); }