/** Design Lab: challenge brief + pragmatic add-node/connect builder + rationale fields -> graded results. */ import { useMemo, useState } from 'react'; import Alert from '@mui/material/Alert'; import Box from '@mui/material/Box'; import Button from '@mui/material/Button'; import Chip from '@mui/material/Chip'; import IconButton from '@mui/material/IconButton'; import MenuItem from '@mui/material/MenuItem'; import TextField from '@mui/material/TextField'; import Typography from '@mui/material/Typography'; import { Plus, Trash2 } from 'lucide-react'; import { ErrorNote, HBar, LoadingBox, SectionCard } from '../../components/labeleval/shared'; import { PassChip, ScoreChip, useAsync } from '../../components/hillclimb/shared'; import { getDesignChallenges, submitDesign } from '../../services/hillclimb'; import type { DesignComponent, DesignEdge, DesignGrade } from '../../types/hillclimb'; const TYPE_COLORS: Record = { source: '#8d6e63', ingestion: '#4fc3f7', stream: '#29b6f6', batch: '#0288d1', storage: '#9575cd', feature: '#7e57c2', training: '#ffb74d', inference: '#ffa726', eval: '#66bb6a', monitoring: '#26a69a', feedback: '#ec407a', }; export default function DesignLabView() { const catalog = useAsync(getDesignChallenges); const [challengeId, setChallengeId] = useState('petabyte_ingestion'); const [components, setComponents] = useState([]); const [edges, setEdges] = useState([]); const [rationales, setRationales] = useState>({}); const [newType, setNewType] = useState('source'); const [newName, setNewName] = useState(''); const [newNote, setNewNote] = useState(''); const [edgeFrom, setEdgeFrom] = useState(''); const [edgeTo, setEdgeTo] = useState(''); const [grade, setGrade] = useState(null); const [busy, setBusy] = useState(false); const [error, setError] = useState(null); const challenge = useMemo( () => catalog.data?.challenges.find((c) => c.challenge_id === challengeId) ?? null, [catalog.data, challengeId] ); const reset = (cid: string) => { setChallengeId(cid); setComponents([]); setEdges([]); setRationales({}); setGrade(null); }; const addComponent = () => { const id = `c${components.length + 1}`; setComponents([...components, { id, type: newType, name: newName || `${newType}-${id}`, note: newNote }]); setNewName(''); setNewNote(''); }; const removeComponent = (id: string) => { setComponents(components.filter((c) => c.id !== id)); setEdges(edges.filter((e) => e.source !== id && e.target !== id)); }; const addEdge = () => { if (!edgeFrom || !edgeTo || edgeFrom === edgeTo) return; if (edges.some((e) => e.source === edgeFrom && e.target === edgeTo)) return; setEdges([...edges, { source: edgeFrom, target: edgeTo }]); }; const submit = () => { setBusy(true); setError(null); submitDesign(challengeId, components, edges, rationales) .then(setGrade) .catch((e: Error) => setError(e.message)) .finally(() => setBusy(false)); }; if (catalog.loading && !catalog.data) return ; if (catalog.error && !catalog.data) return ; const nameOf = (id: string) => components.find((c) => c.id === id)?.name ?? id; return ( reset(e.target.value)} sx={{ minWidth: 280 }}> {(catalog.data?.challenges ?? []).map((c) => ( {c.title} ))} } help="Submit a typed component graph plus a written rationale per key decision. The rule-based grader checks required stages, orphans, feedback-loop closure, single points of failure, capacity math, and rationale coverage of scalability / reliability / latency / cost / observability / failure handling / tradeoffs." > {challenge ? ( <> {challenge.brief} Required stages:{' '} {challenge.required_stages.map((g) => g.join(' or ')).join(' · ')} {challenge.requires_feedback_loop ? ' · must close a feedback loop' : ''} ) : null} setNewType(e.target.value)} sx={{ width: 130 }}> {(catalog.data?.component_types ?? []).map((t) => ( {t} ))} setNewName(e.target.value)} sx={{ width: 150 }} /> setNewNote(e.target.value)} sx={{ flex: 1, minWidth: 170 }} /> {components.map((c) => ( {c.name} {c.note ? ( {c.note} ) : null} removeComponent(c.id)}> ))} setEdgeFrom(e.target.value)} sx={{ flex: 1 }}> {components.map((c) => ( {c.name} ))} setEdgeTo(e.target.value)} sx={{ flex: 1 }}> {components.map((c) => ( {c.name} ))} {edges.map((e, i) => ( {nameOf(e.source)} → {nameOf(e.target)} setEdges(edges.filter((_x, j) => j !== i))}> ))} {(challenge?.key_decisions ?? []).map((d) => ( setRationales({ ...rationales, [d]: e.target.value })} /> ))} {error ? : null} {grade ? ( Structural checks } sx={{ flex: '1 1 320px' }} > {grade.gaps.map((g, i) => ( • {g} ))} {grade.gaps.length === 0 ? ( No structural gaps found. ) : null} {grade.dimension_grades.map((d) => ( = 3 ? '#66bb6a' : d.score >= 2 ? '#f9a825' : '#ef5350'} valueLabel={`${d.score}/5`} /> ))} {grade.evidence_id ? ( Saved to your evidence library. ) : null} ) : null} ); }