oncodsl / web /app /ParameterFlow.tsx
govindbalki's picture
Upload folder using huggingface_hub
0fff343 verified
Raw
History Blame Contribute Delete
9.07 kB
"use client";
import { useId, useRef, useState } from "react";
import { ParamKey } from "./paramHelpContent";
import { useParamHelp } from "./ParamHelpProvider";
/**
* "How these parameters relate" — a collapsed disclosure with a clean
* HTML/flex pipeline diagram inside. Presentation only.
*
* Centered single-column layout, max-width 460px, width 100% — never
* wider than the panel, never overflowing. Each step is a neutral box;
* between consecutive boxes sits a coloured "pill" naming the
* parameter(s) that govern that transition, with a calm ↓ between
* pill and the next box.
*/
export default function ParameterFlow() {
const [open, setOpen] = useState(false);
const panelId = useId();
return (
<div className="mb-4 -mt-2 text-xs">
<button
type="button"
onClick={() => setOpen((o) => !o)}
aria-expanded={open}
aria-controls={panelId}
className="inline-flex items-center gap-1.5 rounded text-ink hover:text-accent focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-accent"
>
<span
aria-hidden
style={{
display: "inline-block",
transition: "transform 120ms ease",
transform: open ? "rotate(90deg)" : "rotate(0deg)",
}}
>
</span>
<span className="font-medium">How these parameters relate</span>
</button>
{open && (
<div id={panelId} className="mt-3 w-full">
<Pipeline />
<Legend />
</div>
)}
</div>
);
}
// ---------- diagram model ------------------------------------------------
type PillKind = "SPACE" | "EFFORT" | "VALIDATION";
interface Box {
title: string;
sub?: string;
amber?: boolean;
}
/** A pill is a list of inline fragments: either a plain string or a
* {bold} fragment so we can highlight just the parameter name. The
* bold fragments carry an optional ``paramKey`` — when present, that
* fragment is rendered as a button that opens the ParamHelp modal. */
type Frag = string | { bold: string; paramKey?: ParamKey };
interface Pill {
kind: PillKind;
/** Optional small text prefix (e.g. "↻ " for the loop pill). */
prefix?: string;
fragments: Frag[];
}
const PILL_STYLES: Record<PillKind, { bg: string; fg: string }> = {
SPACE: { bg: "#E7EFF1", fg: "#2C5563" },
EFFORT: { bg: "#F6ECE0", fg: "#8A4E20" },
VALIDATION: { bg: "#ECEBE6", fg: "#5A6670" },
};
const BOXES: Box[] = [
{ title: "~20,000 genes", sub: "every gene measured" },
{ title: "Gene pool — N genes", sub: "the shortlist the engine draws from" },
{ title: "One program", sub: "Select → Reduce → Fit" },
{ title: "One generation", sub: "every program scored & ranked" },
{ title: "Winning program" },
{ title: "permutation p", sub: "could this be luck?", amber: true },
];
const PILLS: Pill[] = [
{
kind: "SPACE",
fragments: [
{ bold: "Prefilter top-N", paramKey: "prefilter_n" },
" — keep the N most promising genes (off by default = all ~20,000)",
],
},
{
kind: "SPACE",
fragments: [
{ bold: "Max sets", paramKey: "max_sets" },
" × ",
{ bold: "Genes/set", paramKey: "genes_per_set" },
" — pick 1–2 sets, each ≤ G genes",
],
},
{
kind: "EFFORT",
fragments: [
{ bold: "Population", paramKey: "population" },
" — programs compete each round · ",
{ bold: "λ", paramKey: "lambda" },
" — taxes extra genes",
],
},
{
kind: "EFFORT",
prefix: "↻ ",
fragments: [
"× ",
{ bold: "Generations", paramKey: "generations" },
" — breed the best, repeat · ",
{ bold: "Seed", paramKey: "seed" },
" — fixes the randomness",
],
},
{
kind: "VALIDATION",
fragments: [
{ bold: "Permutations", paramKey: "permutations" },
" — re-run on N shuffled-label sets",
],
},
];
// ---------- render -------------------------------------------------------
function Pipeline() {
return (
<div
style={{
maxWidth: 460,
width: "100%",
marginInline: "auto",
display: "flex",
flexDirection: "column",
alignItems: "stretch",
gap: 8,
}}
>
{BOXES.map((b, i) => (
<div key={`group-${i}`} style={{ display: "contents" }}>
<BoxRow box={b} />
{i < PILLS.length && (
<>
<PillRow pill={PILLS[i]} />
<Arrow />
</>
)}
</div>
))}
</div>
);
}
function BoxRow({ box }: { box: Box }) {
return (
<div
style={{
width: "100%",
background: box.amber ? "#FBF1E6" : "#F5F4F0",
border: `1px solid ${box.amber ? "#BC6B2E" : "#D9D6CE"}`,
borderRadius: 12,
padding: "12px 14px",
textAlign: "center",
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
lineHeight: 1.3,
}}
>
<div
style={{
fontSize: 13,
fontWeight: 600,
color: "#23303A",
}}
>
{box.title}
</div>
{box.sub && (
<div
style={{
marginTop: 2,
fontSize: 11.5,
color: "#6E7F8C",
}}
>
{box.sub}
</div>
)}
</div>
);
}
function PillRow({ pill }: { pill: Pill }) {
const { bg, fg } = PILL_STYLES[pill.kind];
return (
<div
style={{
display: "flex",
justifyContent: "center",
width: "100%",
}}
>
<div
style={{
maxWidth: "92%",
background: bg,
color: fg,
borderRadius: 14,
padding: "6px 12px",
fontSize: 12.5,
lineHeight: 1.35,
textAlign: "center",
whiteSpace: "normal",
overflowWrap: "break-word",
wordBreak: "normal",
fontFamily: '"Helvetica Neue", Helvetica, Arial, sans-serif',
}}
>
{pill.prefix && (
<span style={{ marginRight: 2 }} aria-hidden>
{pill.prefix}
</span>
)}
{pill.fragments.map((f, i) => {
if (typeof f === "string") return <span key={i}>{f}</span>;
if (!f.paramKey) {
return (
<strong key={i} style={{ fontWeight: 600 }}>
{f.bold}
</strong>
);
}
return <ParamPillButton key={i} bold={f.bold} paramKey={f.paramKey} colour={fg} />;
})}
</div>
</div>
);
}
function ParamPillButton({
bold,
paramKey,
colour,
}: {
bold: string;
paramKey: ParamKey;
colour: string;
}) {
const ref = useRef<HTMLButtonElement>(null);
const { open } = useParamHelp();
return (
<button
ref={ref}
type="button"
aria-haspopup="dialog"
aria-label={`About ${bold}`}
onClick={(e) => {
e.stopPropagation();
e.preventDefault();
open(paramKey, ref.current);
}}
style={{
background: "transparent",
border: "none",
padding: 0,
font: "inherit",
color: colour,
fontWeight: 600,
textDecoration: "underline",
textDecorationStyle: "dotted",
textDecorationColor: `${colour}66`,
textUnderlineOffset: 2,
cursor: "pointer",
}}
onMouseOver={(e) => {
(e.currentTarget.style as any).textDecorationStyle = "solid";
}}
onMouseOut={(e) => {
(e.currentTarget.style as any).textDecorationStyle = "dotted";
}}
>
{bold}
</button>
);
}
function Arrow() {
return (
<div
aria-hidden
style={{
textAlign: "center",
color: "#B9B6AE",
fontSize: 16,
lineHeight: 1,
userSelect: "none",
}}
>
</div>
);
}
function Legend() {
return (
<div
style={{
maxWidth: 460,
width: "100%",
marginInline: "auto",
marginTop: 12,
display: "flex",
flexWrap: "wrap",
justifyContent: "center",
gap: "6px 14px",
fontSize: 11,
color: "#6E7F8C",
}}
>
<LegendItem fill="#E7EFF1" ink="#2C5563">
teal = where it searches
</LegendItem>
<LegendItem fill="#F6ECE0" ink="#8A4E20">
amber = how hard it searches
</LegendItem>
<LegendItem fill="#ECEBE6" ink="#5A6670">
grey = validation
</LegendItem>
</div>
);
}
function LegendItem({
fill,
ink,
children,
}: {
fill: string;
ink: string;
children: React.ReactNode;
}) {
return (
<span style={{ display: "inline-flex", alignItems: "center", gap: 6 }}>
<span
aria-hidden
style={{
display: "inline-block",
width: 10,
height: 10,
borderRadius: 999,
background: fill,
border: `1px solid ${ink}33`,
}}
/>
{children}
</span>
);
}