// Flow view — input → processing → output for any sub-workflow const { DG: FDG, DG70: FDG70, DG60: FDG60, DG50: FDG50, DG10: FDG10, DG06: FDG06, MINT: FMINT, BGMINT: FBGMINT, BGLIGHT: FBGLIGHT, SPINE: FSPINE } = window; // ─────────────────────── MARKDOWN RENDERER ─────────────────────── // Minimal markdown → React. Supports: # headings, **bold**, *italic*, lists, tables, --- const InlineMD = ({ text }) => { // Render bold/italic/inline-code const parts = []; let rest = text; let key = 0; const regex = /(\*\*[^*]+\*\*|\*[^*]+\*|`[^`]+`)/g; let last = 0; let m; while ((m = regex.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 if (tok.startsWith('*')) parts.push({tok.slice(1,-1)}); else if (tok.startsWith('`')) parts.push({tok.slice(1,-1)}); last = m.index + tok.length; } if (last < text.length) parts.push(text.slice(last)); return <>{parts}; }; const Markdown = ({ source }) => { const lines = source.split('\n'); const blocks = []; let i = 0; let key = 0; while (i < lines.length) { const line = lines[i]; // HR if (/^---+$/.test(line.trim())) { blocks.push(
); i++; continue; } // Heading const h = line.match(/^(#{1,6})\s+(.+)$/); if (h) { const lvl = h[1].length; const size = [22, 18, 15.5, 14, 13, 12.5][lvl-1]; const mt = lvl === 3 ? 18 : lvl === 4 ? 14 : 10; blocks.push(
); i++; continue; } // Table if (line.includes('|') && i+1 < lines.length && /^\s*\|?[\s\-:|]+\|?\s*$/.test(lines[i+1])) { const header = line.split('|').map(s => s.trim()).filter(Boolean); i += 2; const rows = []; while (i < lines.length && lines[i].includes('|')) { rows.push(lines[i].split('|').map(s => s.trim()).filter((_,idx,arr) => !(idx===0 && arr[0]==='') && !(idx===arr.length-1 && arr[arr.length-1]===''))); i++; } blocks.push(
{header.map((h,j) => )} {rows.map((r, ri) => ( {r.map((c, ci) => )} ))}
{h}
); continue; } // Numbered list if (/^\d+\.\s/.test(line)) { const items = []; while (i < lines.length && /^\d+\.\s/.test(lines[i])) { items.push(lines[i].replace(/^\d+\.\s/, '')); i++; } blocks.push(
    {items.map((it, j) =>
  1. )}
); continue; } // Bullet list if (/^[-•]\s/.test(line)) { const items = []; while (i < lines.length && /^[-•]\s/.test(lines[i])) { items.push(lines[i].replace(/^[-•]\s/, '')); i++; } blocks.push( ); continue; } // Blank if (line.trim() === '') { i++; continue; } // Paragraph const paraLines = []; while (i < lines.length && lines[i].trim() !== '' && !/^(#|---|\d+\.\s|[-•]\s)/.test(lines[i]) && !(lines[i].includes('|') && i+1

); } return <>{blocks}; }; // ─────────────────────── PROCESSING ANIMATION ─────────────────────── const ProcessingSteps = ({ steps, speedMs = 1 }) => { const [done, setDone] = React.useState(0); React.useEffect(() => { let cancelled = false; const run = async () => { for (let i = 0; i < steps.length; i++) { await new Promise(r => setTimeout(r, (steps[i].dur || 800) * speedMs)); if (cancelled) return; setDone(i + 1); } }; run(); return () => { cancelled = true; }; }, [steps]); return (
{steps.map((s, i) => { const isDone = i < done; const isActive = i === done; return (
{isDone ? {window.Ic.Check(13)} : isActive ? : }
{s.label} {isDone && ✓ {(s.dur/1000).toFixed(1)}s}
); })}
); }; // ─────────────────────── INPUT PANELS ─────────────────────── // Reusable CTA card for generate buttons — used by all assistants const GenerateCTA = ({ title, description, buttonLabel, onGenerate, meta }) => (
{window.Ic.Sparkles(22)}
{title}
{description}
{meta &&
{meta}
}
); // Scribe: preparation summary card on top, audio recorder full-width, manual notes full-width, CTA at bottom const ScribeInputPanel = ({ input, onGenerate }) => { const [recording, setRecording] = React.useState(false); const [elapsed, setElapsed] = React.useState(0); const [showPrep, setShowPrep] = React.useState(true); React.useEffect(() => { if (!recording) return; const iv = setInterval(() => setElapsed(e => e + 1), 100); return () => clearInterval(iv); }, [recording]); return (
{/* Preparation summary card — links Scribe to Preparation assistant */}
{window.Ic.ClipboardList(20)}
Hysio Preparation · 02.02
KLAAR
Intake-voorbereiding beschikbaar
Werkhypothese: chronische distale patellatendinopathie L. Kern-anamnese ingezet op keten-limiters (enkel-DF, glut med) en VISA-P baseline. 3 DD overwogen, 2 red flags negatief.
{showPrep && (
Focus vandaag: VISA-P hertest, SLS decline squat, ankle DF knee-to-wall, glut med Ekstrand.
Aandachtspunten: progressie naar HSR stage 3 · instep schieten nog 2 weken vermijden · verwachting managen (hersteltijd 3-6 mnd conform richtlijn).
)}
{/* Audio recorder — full width */}
Spraak-naar-Tekst
{Array.from({length:48}).map((_, i) => { const h = recording ? 4 + Math.abs(Math.sin((Date.now()/200 + i*0.4))) * 28 : 4 + (i%3)*2; return
; })}
{recording ? <>
Hysio luistert mee…
{Math.floor(elapsed/600)}:{String(Math.floor(elapsed/10)%60).padStart(2,'0')}
: <>{input.audioLabel}}
{/* Manual notes — full width */}
Handmatige notities (optioneel)