import { h } from 'preact'; import { useState } from 'preact/hooks'; interface Props { onClose: () => void; onCreated: () => void; } interface NodeParam { label: string; key: string; type: 'number' | 'text'; default: string; } const NODE_TYPES: Array<{ type: string; label: string; params: NodeParam[] }> = [ { type: 'gain', label: 'Gain', params: [{ label: 'Factor', key: 'factor', type: 'number', default: '1' }] }, { type: 'offset', label: 'Offset', params: [{ label: 'Offset', key: 'offset', type: 'number', default: '0' }] }, { type: 'moving_avg', label: 'Moving Average', params: [{ label: 'Window (n)', key: 'n', type: 'number', default: '10' }] }, { type: 'rms', label: 'RMS', params: [{ label: 'Window (n)', key: 'n', type: 'number', default: '10' }] }, { type: 'lowpass', label: 'Lowpass Filter', params: [{ label: 'Cutoff Hz', key: 'cutoff', type: 'number', default: '1' }, { label: 'Sample Hz', key: 'sample_rate', type: 'number', default: '10' }] }, { type: 'highpass', label: 'Highpass Filter', params: [{ label: 'Cutoff Hz', key: 'cutoff', type: 'number', default: '1' }, { label: 'Sample Hz', key: 'sample_rate', type: 'number', default: '10' }] }, { type: 'derivative', label: 'Derivative', params: [] }, { type: 'integral', label: 'Integral', params: [] }, { type: 'clamp', label: 'Clamp', params: [{ label: 'Min', key: 'min', type: 'number', default: '0' }, { label: 'Max', key: 'max', type: 'number', default: '100' }] }, { type: 'formula', label: 'Formula', params: [{ label: 'Expression (use "x")', key: 'expr', type: 'text', default: 'x * 1.0' }] }, ]; export default function SyntheticWizard({ onClose, onCreated }: Props) { const [name, setName] = useState(''); const [inputDs, setInputDs] = useState('stub'); const [inputSig, setInputSig] = useState('sine_1hz'); const [nodeType, setNodeType] = useState('gain'); const [params, setParams] = useState>({}); const [unit, setUnit] = useState(''); const [desc, setDesc] = useState(''); const [dispLow, setDispLow] = useState('0'); const [dispHigh, setDispHigh] = useState('100'); const [saving, setSaving] = useState(false); const [error, setError] = useState(''); const nodeDef = NODE_TYPES.find(n => n.type === nodeType)!; function getParam(key: string, def: string): string { return params[key] ?? def; } function setParam(key: string, val: string) { setParams(p => ({ ...p, [key]: val })); } async function handleCreate() { if (!name.trim()) { setError('Name is required'); return; } if (!inputSig.trim()) { setError('Input signal is required'); return; } const nodeParams: Record = {}; for (const p of nodeDef.params) { const raw = getParam(p.key, p.default); nodeParams[p.key] = p.type === 'number' ? parseFloat(raw) : raw; } const def = { name: name.trim(), ds: inputDs, signal: inputSig.trim(), pipeline: [{ type: nodeType, params: nodeParams }], meta: { unit: unit || undefined, description: desc || undefined, displayLow: parseFloat(dispLow) || 0, displayHigh: parseFloat(dispHigh) || 100, }, }; setSaving(true); setError(''); try { const res = await fetch('/api/v1/synthetic', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(def), }); if (!res.ok) { const j = await res.json().catch(() => ({ error: res.statusText })); throw new Error(j.error ?? res.statusText); } onCreated(); onClose(); } catch (err) { setError(err instanceof Error ? err.message : String(err)); } finally { setSaving(false); } } return (
e.stopPropagation()}>
New Synthetic Signal
{error &&

{error}

}
Signal identity
setName((e.target as HTMLInputElement).value)} />
Input signal
setInputDs((e.target as HTMLInputElement).value)} />
setInputSig((e.target as HTMLInputElement).value)} />
Processing
{nodeDef.params.map(p => (
setParam(p.key, (e.target as HTMLInputElement).value)} />
))}
Metadata (optional)
setUnit((e.target as HTMLInputElement).value)} />
setDispLow((e.target as HTMLInputElement).value)} />
setDispHigh((e.target as HTMLInputElement).value)} />
setDesc((e.target as HTMLInputElement).value)} />
); }