Prune hosted run controls

This commit is contained in:
Meta Vens 2026-04-29 17:58:25 +01:00
commit f3dc8e0d52
5 changed files with 58 additions and 78 deletions

View file

@ -210,7 +210,6 @@ const deviceField = byId<HTMLElement>("deviceField");
const deviceInput = byId<HTMLSelectElement>("deviceInput");
const stripBackgroundField = byId<HTMLElement>("stripBackgroundField");
const stripBackgroundInput = byId<HTMLInputElement>("stripBackgroundInput");
const skipRerankInput = byId<HTMLInputElement>("skipRerankInput");
const paintInputs = {
r: byId<HTMLInputElement>("paintR"),
@ -1493,7 +1492,7 @@ function buildFormData(): FormData {
["seed", String(parseOptionalInteger(seedInput) ?? 7)],
["device", runtimeConfig?.hostedDemo ? "cpu" : deviceInput.value],
["strip_background", runtimeConfig?.hostedDemo ? "false" : stripBackgroundInput.checked ? "true" : "false"],
["skip_candidate_rerank", skipRerankInput.checked ? "true" : "false"],
["skip_candidate_rerank", "false"],
];
for (const [key, value] of values) {
if (value !== null) {

View file

@ -170,7 +170,7 @@ class HostedDemoConfig:
defaults = {
"max_upload_bytes": 2 * 1_048_576 if hosted_demo else 16 * 1_048_576,
"max_input_dimension": 2048 if hosted_demo else 4096,
"max_output_dimension": 256 if hosted_demo else 1024,
"max_output_dimension": 512 if hosted_demo else 1024,
"default_steps": 32 if hosted_demo else 48,
"max_steps": 48 if hosted_demo else 256,
"queue_capacity": 10 if hosted_demo else 32,
@ -515,7 +515,7 @@ def _normalize_job_options(
normalized_strip_background = False if config.hosted_demo else bool(strip_background)
inference_mode = "autocorr" if fixed_dims is None else "fixed"
max_inferred_target_size = config.max_output_dimension if fixed_dims is None else None
normalized_skip_candidate_rerank = bool(skip_candidate_rerank)
normalized_skip_candidate_rerank = False
return {
"target_size": normalized_target_size,
"target_width": normalized_target_width,

View file

@ -49,7 +49,6 @@ const deviceField = byId("deviceField");
const deviceInput = byId("deviceInput");
const stripBackgroundField = byId("stripBackgroundField");
const stripBackgroundInput = byId("stripBackgroundInput");
const skipRerankInput = byId("skipRerankInput");
const paintInputs = {
r: byId("paintR"),
g: byId("paintG"),
@ -65,8 +64,8 @@ const eventTypes = [
"stage_started",
"source_loaded",
"preprocess_completed",
"lattice_inference_started",
"lattice_inference_progress",
"lattice_search_started",
"lattice_search_progress",
"inference_candidates_ready",
"candidate_rerank_started",
"candidate_rerank_candidate_started",
@ -92,7 +91,7 @@ const state = {
sourceImage: null,
preprocessedImage: null,
latticeImage: null,
signalImage: null,
guidanceImage: null,
inference: null,
inferenceMode: null,
latticeSearch: null,
@ -311,7 +310,7 @@ function applyRuntimeConfig(config) {
}
else {
inputCopy.textContent = "Start with the ugly input.";
runControlsCopy.textContent = "Pin what you know. Infer what you do not.";
runControlsCopy.textContent = "Pin what you know. Search what you do not.";
}
}
function resetRunArtifacts(options = {}) {
@ -322,7 +321,7 @@ function resetRunArtifacts(options = {}) {
}
state.preprocessedImage = null;
state.latticeImage = null;
state.signalImage = null;
state.guidanceImage = null;
state.inference = null;
state.inferenceMode = null;
state.latticeSearch = null;
@ -495,6 +494,9 @@ function renderInference() {
<strong>Chosen Grid</strong>
<span>${state.inference.target_width} x ${state.inference.target_height}</span>
</div>
<div class="info-card summary-card">
<strong>Phase</strong>
</div>
<div class="info-card summary-card">
<strong>Confidence</strong>
<span>${formatNumber(state.inference.confidence, 3)}</span>
@ -544,12 +546,7 @@ function renderStatusMetrics() {
}
else if (state.stageKey === "analysis" || state.stageKey === "selection") {
if (state.inference) {
const modeLabel = state.inferenceMode === "fixed"
? "pinned"
: state.inferenceMode === "autocorr"
? "autocorr direct"
: state.inferenceMode ?? "search";
items.push({ label: "Mode", value: modeLabel });
items.push({ label: "Mode", value: state.inferenceMode === "fixed" ? "pinned" : state.inferenceMode ?? "search" });
items.push({ label: "Grid", value: `${state.inference.target_width} x ${state.inference.target_height}` });
items.push({ label: "Confidence", value: formatNumber(state.inference.confidence, 3) });
}
@ -584,11 +581,11 @@ function renderStatusMetrics() {
}
else if (state.stageKey === "solver") {
const solverTerms = [
{ label: "Signal", value: formatNumber(frame?.terms.local_signal ?? frame?.solverMetrics.local_signal ?? null, 4) },
{ label: "Grid align", value: formatNumber(frame?.terms.grid_alignment ?? frame?.solverMetrics.grid_alignment ?? null, 4) },
{ label: "Smoothness", value: formatNumber(frame?.terms.smoothness ?? frame?.solverMetrics.smoothness ?? null, 4) },
{ label: "Collapse", value: formatNumber(frame?.terms.collapse ?? frame?.solverMetrics.collapse ?? null, 4) },
{ label: "Magnitude", value: formatNumber(frame?.terms.magnitude ?? frame?.solverMetrics.magnitude ?? null, 4) },
{ label: "Coherence", value: formatNumber(frame?.terms.local_coherence ?? null, 4) },
{ label: "Edge", value: formatNumber(frame?.terms.local_edge ?? null, 4) },
{ label: "Smoothness", value: formatNumber(frame?.terms.smoothness ?? null, 4) },
{ label: "Collapse", value: formatNumber(frame?.terms.collapse ?? null, 4) },
{ label: "Magnitude", value: formatNumber(frame?.terms.magnitude ?? null, 4) },
];
items.push({
label: "Grid",
@ -623,23 +620,15 @@ function resolveViewerPanels() {
leftAsset = state.latticeImage;
leftLabel = "Lattice Prep";
}
if (state.signalImage) {
leftAsset = state.signalImage;
leftLabel = "Signal";
if (state.guidanceImage) {
leftAsset = state.guidanceImage;
leftLabel = "Guidance";
}
if (frame) {
if (state.stageKey === "solver" && state.signalImage) {
leftAsset = state.signalImage;
rightAsset = frame.samplingOverlayImage;
leftLabel = "Signal";
rightLabel = "Sampling Overlay";
}
else {
leftAsset = frame.samplingOverlayImage;
rightAsset = frame.outputImage;
leftLabel = "Sampling Overlay";
rightLabel = "Current Output";
}
leftAsset = frame.samplingOverlayImage;
rightAsset = frame.outputImage;
leftLabel = "Sampling Overlay";
rightLabel = "Current Output";
}
else if (state.cleanupImage) {
if (state.heatmapImage) {
@ -794,14 +783,12 @@ function renderLossChart() {
const domainMax = getLossDomainMax();
const samples = buildLossSamples();
const observedMaxLoss = samples.length === 0 ? null : Math.max(...samples.map((sample) => sample.loss));
const observedMinLoss = samples.length === 0 ? null : Math.min(...samples.map((sample) => sample.loss));
if (observedMaxLoss !== null && observedMinLoss !== null) {
const observedAbsLoss = Math.max(Math.abs(observedMaxLoss), Math.abs(observedMinLoss));
state.lossAxisMax = Math.max(state.lossAxisMax ?? 0, observedAbsLoss);
if (observedMaxLoss !== null) {
state.lossAxisMax = Math.max(state.lossAxisMax ?? 0, observedMaxLoss);
}
const axisAbsLoss = Math.max(0.001, state.lossAxisMax ?? Math.max(Math.abs(observedMaxLoss ?? 1), Math.abs(observedMinLoss ?? 0)));
const maxLoss = axisAbsLoss * 1.05;
const minLoss = -axisAbsLoss * 1.05;
const axisMaxLoss = Math.max(0.001, state.lossAxisMax ?? observedMaxLoss ?? 1);
const maxLoss = axisMaxLoss * 1.05;
const minLoss = 0;
const chartLeft = 88;
const chartRight = width - 18;
const chartTop = 16;
@ -1240,7 +1227,7 @@ function buildFormData() {
["seed", String(parseOptionalInteger(seedInput) ?? 7)],
["device", runtimeConfig?.hostedDemo ? "cpu" : deviceInput.value],
["strip_background", runtimeConfig?.hostedDemo ? "false" : stripBackgroundInput.checked ? "true" : "false"],
["skip_candidate_rerank", skipRerankInput.checked ? "true" : "false"],
["skip_candidate_rerank", "false"],
];
for (const [key, value] of values) {
if (value !== null) {
@ -1302,7 +1289,7 @@ async function handleEvent(eventName, payload) {
state.preprocessedImage = payload.sourceImage;
addLog("Preprocess", "Stripped edge-connected background noise.");
break;
case "lattice_inference_started":
case "lattice_search_started":
state.latticeSearch = {
candidateCount: Number(payload.candidateCount ?? 0),
completedCandidates: 0,
@ -1311,10 +1298,10 @@ async function handleEvent(eventName, payload) {
currentTargetHeight: null,
bestScore: null,
};
setStage("inference", "Autocorr lattice", `Testing ${state.latticeSearch.candidateCount} autocorr candidate sizes from canonical cell centers.`);
addLog("Inference", `Started autocorr lattice inference across ${String(payload.candidateCount)} size candidates.`);
setStage("inference", "Lattice search", `Testing ${state.latticeSearch.candidateCount} candidate sizes from canonical cell centers.`);
addLog("Search", `Started lattice search across ${String(payload.candidateCount)} size candidates.`);
break;
case "lattice_inference_progress":
case "lattice_search_progress":
if (!state.latticeSearch) {
state.latticeSearch = {
candidateCount: Number(payload.totalCandidates ?? 0),
@ -1331,7 +1318,7 @@ async function handleEvent(eventName, payload) {
state.latticeSearch.currentTargetHeight = Number(payload.targetHeight ?? 0);
state.latticeSearch.bestScore =
payload.bestScore === null || payload.bestScore === undefined ? state.latticeSearch.bestScore : Number(payload.bestScore);
setStage("inference", "Autocorr lattice", `Size ${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}: scored ${state.latticeSearch.currentTargetWidth} x ${state.latticeSearch.currentTargetHeight} from canonical cell centers.`);
setStage("inference", "Lattice search", `Size ${state.latticeSearch.completedCandidates} / ${state.latticeSearch.candidateCount}: scored ${state.latticeSearch.currentTargetWidth} x ${state.latticeSearch.currentTargetHeight} from canonical cell centers.`);
break;
case "inference_candidates_ready":
state.inference = payload.inference;
@ -1398,11 +1385,11 @@ async function handleEvent(eventName, payload) {
addLog("Selection", "Committed to a lattice size.");
break;
case "analysis_completed":
addLog("Analysis", "Rendered the diagnostic edge preview.");
addLog("Scout", "Built the edge map that tells the solver where the floorboards creak.");
break;
case "phase_field_prepared":
state.latticeImage = payload.latticeImage;
state.signalImage = payload.signalImage;
state.guidanceImage = payload.guidanceImage;
state.phaseFieldPrep = {
targetWidth: Number(payload.targetWidth ?? 0),
targetHeight: Number(payload.targetHeight ?? 0),
@ -1435,8 +1422,8 @@ async function handleEvent(eventName, payload) {
case "cleanup_completed":
state.cleanupImage = payload.cleanedImage;
state.heatmapImage = payload.heatmapImage;
setStage("cleanup", "Cleanup", "Snapping alpha to the final discrete mask.");
addLog("Cleanup", "Applied alpha snapping.");
setStage("cleanup", "Cleanup", "Sweeping isolated artifacts and smoothing the last stubborn junk.");
addLog("Cleanup", "Ran the local cleanup sweep.");
break;
case "palette_completed":
state.finalOutputImage = payload.outputImage;

View file

@ -175,7 +175,7 @@
</div>
</div>
<form id="controlsForm" class="controls-grid">
<label id="deviceField" class="field">
<label id="deviceField" class="field" hidden>
<span class="field-heading">
<span class="field-label">Device</span>
<span
@ -263,7 +263,7 @@
</span>
<input id="seedInput" class="field-control" name="seed" type="number" min="0" value="7" />
</label>
<label id="stripBackgroundField" class="toggle-row ui-surface ui-frame">
<label id="stripBackgroundField" class="toggle-row ui-surface ui-frame" hidden>
<input id="stripBackgroundInput" name="stripBackground" type="checkbox" />
<span class="toggle-copy">
<span>Strip checkerboard-ish junk</span>
@ -277,20 +277,6 @@
>
</span>
</label>
<label class="toggle-row ui-surface ui-frame">
<input id="skipRerankInput" name="skipRerank" type="checkbox" />
<span class="toggle-copy">
<span>Skip candidate rerank</span>
<span
class="help-tip"
tabindex="0"
role="note"
title="Skip the preview rerank pass that rescues shaky size picks when confidence is low. Faster, but more likely to keep a bad lattice guess."
aria-label="Skip the preview rerank pass that rescues shaky size picks when confidence is low. Faster, but more likely to keep a bad lattice guess."
>?</span
>
</span>
</label>
</form>
<button id="runButton" class="button" type="button">Run The Machine</button>
</section>

View file

@ -386,7 +386,7 @@ def test_gui_static_assets_disable_browser_caching() -> None:
app = create_app()
html_status, html_headers, html_body = asyncio.run(_get_response(app, "/app/"))
js_status, js_headers, _js_body = asyncio.run(_get_response(app, "/app/app.js"))
js_status, js_headers, js_body = asyncio.run(_get_response(app, "/app/app.js"))
assert html_status == 200
assert js_status == 200
@ -395,8 +395,15 @@ def test_gui_static_assets_disable_browser_caching() -> None:
assert html_headers["pragma"] == "no-cache"
assert js_headers["pragma"] == "no-cache"
html_text = html_body.decode("utf-8")
js_text = js_body.decode("utf-8")
assert "./styles.css?v=" in html_text
assert "./app.js?v=" in html_text
assert 'id="deviceField" class="field" hidden' in html_text
assert 'id="stripBackgroundField" class="toggle-row ui-surface ui-frame" hidden' in html_text
assert "skipRerankInput" not in html_text
assert "Skip candidate rerank" not in html_text
assert '["skip_candidate_rerank", "false"]' in js_text
assert "skipRerankInput" not in js_text
def test_gui_hosted_root_serves_landing_page(monkeypatch, tmp_path: Path) -> None:
@ -711,7 +718,8 @@ def test_gui_hosted_config_endpoint_exposes_demo_limits(monkeypatch, tmp_path: P
payload = _response_json(_route_endpoint(app, "/api/config", "GET")())
assert payload["hostedDemo"] is True
assert payload["limits"]["maxOutputDimension"] == 256
assert payload["limits"]["maxUploadBytes"] == 2 * 1_048_576
assert payload["limits"]["maxOutputDimension"] == 512
assert payload["limits"]["defaultSteps"] == 32
assert payload["ui"]["showDeviceControl"] is False
assert payload["ui"]["showStripBackgroundControl"] is False
@ -943,15 +951,15 @@ def test_gui_heimdall_patreon_auth_start_sends_membership_policy(monkeypatch, tm
}
def test_hosted_job_options_use_autocorr_and_allow_rerank() -> None:
def test_hosted_job_options_use_autocorr_and_force_rerank() -> None:
from repixelizer.gui import HostedDemoConfig, _normalize_job_options
config = HostedDemoConfig(
hosted_demo=True,
show_queue_panel=True,
max_upload_bytes=1_048_576,
max_upload_bytes=2 * 1_048_576,
max_input_dimension=2048,
max_output_dimension=256,
max_output_dimension=512,
default_steps=32,
max_steps=48,
queue_capacity=10,
@ -972,12 +980,12 @@ def test_hosted_job_options_use_autocorr_and_allow_rerank() -> None:
seed=7,
device="auto",
strip_background=False,
skip_candidate_rerank=False,
skip_candidate_rerank=True,
)
assert options["target_size"] is None
assert options["lattice_inference_mode"] == "autocorr"
assert options["max_inferred_target_size"] == 256
assert options["max_inferred_target_size"] == 512
assert options["skip_candidate_rerank"] is False
@ -1153,7 +1161,7 @@ def test_gui_hosted_jobs_dispatch_direct_autocorr_pipeline(monkeypatch, tmp_path
assert created.status_code == 200
assert calls
assert calls[0]["lattice_inference_mode"] == "autocorr"
assert calls[0]["max_inferred_target_size"] == 256
assert calls[0]["max_inferred_target_size"] == 512
assert calls[0]["enable_candidate_rerank"] is True
assert calls[0]["target_size"] is None