feat(ui): edit objective/constraint source in panel + UNSET wiring
This commit is contained in:
@@ -820,6 +820,34 @@ interface ObjectiveNodeConfigProps {
|
||||
}
|
||||
|
||||
function ObjectiveNodeConfig({ node, onChange }: ObjectiveNodeConfigProps) {
|
||||
const spec = useSpec();
|
||||
const extractors = spec?.extractors || [];
|
||||
|
||||
const currentExtractorId = node.source?.extractor_id || '__UNSET__';
|
||||
const currentOutputName = node.source?.output_name || '__UNSET__';
|
||||
|
||||
const selectedExtractor = extractors.find((e) => e.id === currentExtractorId);
|
||||
const outputOptions = selectedExtractor?.outputs?.map((o) => o.name) || [];
|
||||
|
||||
const handleExtractorChange = (extractorId: string) => {
|
||||
// Reset output_name to a sensible default when extractor changes
|
||||
const ext = extractors.find((e) => e.id === extractorId);
|
||||
const outs = ext?.outputs?.map((o) => o.name) || [];
|
||||
const preferred = outs.includes('value') ? 'value' : outs[0] || '__UNSET__';
|
||||
|
||||
onChange('source', {
|
||||
extractor_id: extractorId,
|
||||
output_name: preferred,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOutputChange = (outputName: string) => {
|
||||
onChange('source', {
|
||||
extractor_id: currentExtractorId,
|
||||
output_name: outputName,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@@ -831,6 +859,45 @@ function ObjectiveNodeConfig({ node, onChange }: ObjectiveNodeConfigProps) {
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Source Extractor</label>
|
||||
<select
|
||||
value={currentExtractorId}
|
||||
onChange={(e) => handleExtractorChange(e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="__UNSET__">(not connected)</option>
|
||||
{extractors.map((ext) => (
|
||||
<option key={ext.id} value={ext.id}>
|
||||
{ext.id} — {ext.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Source Output</label>
|
||||
<select
|
||||
value={currentOutputName}
|
||||
onChange={(e) => handleOutputChange(e.target.value)}
|
||||
className={selectClass}
|
||||
disabled={currentExtractorId === '__UNSET__'}
|
||||
>
|
||||
{currentExtractorId === '__UNSET__' ? (
|
||||
<option value="__UNSET__">(select an extractor)</option>
|
||||
) : (
|
||||
outputOptions.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
<p className="text-xs text-dark-500 mt-1">
|
||||
This drives execution. Canvas wires are just a visual check.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Direction</label>
|
||||
@@ -877,6 +944,33 @@ interface ConstraintNodeConfigProps {
|
||||
}
|
||||
|
||||
function ConstraintNodeConfig({ node, onChange }: ConstraintNodeConfigProps) {
|
||||
const spec = useSpec();
|
||||
const extractors = spec?.extractors || [];
|
||||
|
||||
const currentExtractorId = node.source?.extractor_id || '__UNSET__';
|
||||
const currentOutputName = node.source?.output_name || '__UNSET__';
|
||||
|
||||
const selectedExtractor = extractors.find((e) => e.id === currentExtractorId);
|
||||
const outputOptions = selectedExtractor?.outputs?.map((o) => o.name) || [];
|
||||
|
||||
const handleExtractorChange = (extractorId: string) => {
|
||||
const ext = extractors.find((e) => e.id === extractorId);
|
||||
const outs = ext?.outputs?.map((o) => o.name) || [];
|
||||
const preferred = outs.includes('value') ? 'value' : outs[0] || '__UNSET__';
|
||||
|
||||
onChange('source', {
|
||||
extractor_id: extractorId,
|
||||
output_name: preferred,
|
||||
});
|
||||
};
|
||||
|
||||
const handleOutputChange = (outputName: string) => {
|
||||
onChange('source', {
|
||||
extractor_id: currentExtractorId,
|
||||
output_name: outputName,
|
||||
});
|
||||
};
|
||||
|
||||
return (
|
||||
<>
|
||||
<div>
|
||||
@@ -888,6 +982,45 @@ function ConstraintNodeConfig({ node, onChange }: ConstraintNodeConfigProps) {
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Source Extractor</label>
|
||||
<select
|
||||
value={currentExtractorId}
|
||||
onChange={(e) => handleExtractorChange(e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="__UNSET__">(not connected)</option>
|
||||
{extractors.map((ext) => (
|
||||
<option key={ext.id} value={ext.id}>
|
||||
{ext.id} — {ext.name}
|
||||
</option>
|
||||
))}
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Source Output</label>
|
||||
<select
|
||||
value={currentOutputName}
|
||||
onChange={(e) => handleOutputChange(e.target.value)}
|
||||
className={selectClass}
|
||||
disabled={currentExtractorId === '__UNSET__'}
|
||||
>
|
||||
{currentExtractorId === '__UNSET__' ? (
|
||||
<option value="__UNSET__">(select an extractor)</option>
|
||||
) : (
|
||||
outputOptions.map((name) => (
|
||||
<option key={name} value={name}>
|
||||
{name}
|
||||
</option>
|
||||
))
|
||||
)}
|
||||
</select>
|
||||
<p className="text-xs text-dark-500 mt-1">
|
||||
This drives execution. Canvas wires are just a visual check.
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<div className="grid grid-cols-2 gap-2">
|
||||
<div>
|
||||
@@ -897,24 +1030,37 @@ function ConstraintNodeConfig({ node, onChange }: ConstraintNodeConfigProps) {
|
||||
onChange={(e) => onChange('type', e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="less_than">< Less than</option>
|
||||
<option value="less_equal"><= Less or equal</option>
|
||||
<option value="greater_than">> Greater than</option>
|
||||
<option value="greater_equal">>= Greater or equal</option>
|
||||
<option value="equal">= Equal</option>
|
||||
<option value="hard">Hard</option>
|
||||
<option value="soft">Soft</option>
|
||||
</select>
|
||||
<p className="text-xs text-dark-500 mt-1">Spec type (hard/soft). Operator is set below.</p>
|
||||
</div>
|
||||
<div>
|
||||
<label className={labelClass}>Threshold</label>
|
||||
<input
|
||||
type="number"
|
||||
value={node.threshold}
|
||||
onChange={(e) => onChange('threshold', parseFloat(e.target.value))}
|
||||
className={inputClass}
|
||||
/>
|
||||
<label className={labelClass}>Operator</label>
|
||||
<select
|
||||
value={node.operator}
|
||||
onChange={(e) => onChange('operator', e.target.value)}
|
||||
className={selectClass}
|
||||
>
|
||||
<option value="<="><=</option>
|
||||
<option value="<"><</option>
|
||||
<option value=">=">>=</option>
|
||||
<option value=">">></option>
|
||||
<option value="==">==</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div>
|
||||
<label className={labelClass}>Threshold</label>
|
||||
<input
|
||||
type="number"
|
||||
value={node.threshold}
|
||||
onChange={(e) => onChange('threshold', parseFloat(e.target.value))}
|
||||
className={inputClass}
|
||||
/>
|
||||
</div>
|
||||
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user