Simplify output editor header
This commit is contained in:
parent
7206b454db
commit
e45b915604
4 changed files with 38 additions and 123 deletions
|
|
@ -140,7 +140,6 @@ type AppState = {
|
|||
cleanupImage: ImageAsset | null;
|
||||
heatmapImage: ImageAsset | null;
|
||||
finalOutputImage: ImageAsset | null;
|
||||
runSummary: JsonRecord | null;
|
||||
runtimeConfig: RuntimeConfig | null;
|
||||
queueSummary: QueueSummary | null;
|
||||
queuePosition: number | null;
|
||||
|
|
@ -201,8 +200,7 @@ const gridToggle = byId<HTMLInputElement>("gridToggle");
|
|||
const editorCanvas = byId<HTMLCanvasElement>("editorCanvas");
|
||||
const editorGridCanvas = byId<HTMLCanvasElement>("editorGridCanvas");
|
||||
const editorSurface = byId<HTMLDivElement>("editorSurface");
|
||||
const editorMeta = byId<HTMLDivElement>("editorMeta");
|
||||
const summaryPanel = byId<HTMLDivElement>("summaryPanel");
|
||||
const editorCanvasSizeValue = byId<HTMLSpanElement>("editorCanvasSizeValue");
|
||||
const resetEditorButton = byId<HTMLButtonElement>("resetEditorButton");
|
||||
const downloadButton = byId<HTMLButtonElement>("downloadButton");
|
||||
|
||||
|
|
@ -277,7 +275,6 @@ const state: AppState = {
|
|||
cleanupImage: null,
|
||||
heatmapImage: null,
|
||||
finalOutputImage: null,
|
||||
runSummary: null,
|
||||
runtimeConfig: null,
|
||||
queueSummary: null,
|
||||
queuePosition: null,
|
||||
|
|
@ -524,7 +521,6 @@ function resetRunArtifacts(options: { preserveSourceImage?: boolean } = {}): voi
|
|||
state.cleanupImage = null;
|
||||
state.heatmapImage = null;
|
||||
state.finalOutputImage = null;
|
||||
state.runSummary = null;
|
||||
state.queuePosition = null;
|
||||
state.queueDepth = 0;
|
||||
state.waitingCount = 0;
|
||||
|
|
@ -670,17 +666,6 @@ async function fileToImageAsset(file: File): Promise<ImageAsset> {
|
|||
};
|
||||
}
|
||||
|
||||
function readNestedNumber(root: JsonRecord | null, path: string[]): number | null {
|
||||
let current: unknown = root;
|
||||
for (const key of path) {
|
||||
if (!current || typeof current !== "object" || !(key in current)) {
|
||||
return null;
|
||||
}
|
||||
current = (current as JsonRecord)[key];
|
||||
}
|
||||
return typeof current === "number" ? current : null;
|
||||
}
|
||||
|
||||
function formatNumber(value: number | null, digits = 3): string {
|
||||
if (value === null || !Number.isFinite(value)) {
|
||||
return "n/a";
|
||||
|
|
@ -858,27 +843,6 @@ function renderStatusMetrics(): void {
|
|||
renderStatusMetricItems(items);
|
||||
}
|
||||
|
||||
function renderSummary(): void {
|
||||
summaryPanel.innerHTML = "";
|
||||
if (!state.runSummary) {
|
||||
summaryPanel.innerHTML = `<p class="muted">Final metrics show up here after the run finishes.</p>`;
|
||||
return;
|
||||
}
|
||||
const entries: Array<[string, number | null]> = [
|
||||
["Structure score", readNestedNumber(state.runSummary, ["source_structure", "score"])],
|
||||
["Edge F1", readNestedNumber(state.runSummary, ["source_structure", "edge_f1"])],
|
||||
["Exact match", readNestedNumber(state.runSummary, ["source_structure", "exact_match_ratio"])],
|
||||
["Final fidelity", readNestedNumber(state.runSummary, ["source_fidelity", "final_output", "score"])],
|
||||
["Mean displacement", readNestedNumber(state.runSummary, ["optimizer_displacement", "final_output", "mean_magnitude_px"])],
|
||||
];
|
||||
for (const [label, value] of entries) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "info-card summary-card";
|
||||
card.innerHTML = `<strong>${label}</strong><span>${formatNumber(value, 3)}</span>`;
|
||||
summaryPanel.appendChild(card);
|
||||
}
|
||||
}
|
||||
|
||||
function resolveViewerPanels(): Record<ViewerPanelKey, ViewerPanelState> {
|
||||
const frame = getSelectedFrame();
|
||||
const debugViewerStage = new Set(["inference", "analysis", "selection", "rerank", "solver", "cleanup"]);
|
||||
|
|
@ -1199,7 +1163,7 @@ function renderEditor(): void {
|
|||
editorCanvas.height = 1;
|
||||
editorGridCanvas.width = 1;
|
||||
editorGridCanvas.height = 1;
|
||||
editorMeta.innerHTML = `<p class="muted">Final output lands here once the solver finishes its little pilgrimage.</p>`;
|
||||
editorCanvasSizeValue.textContent = "n/a";
|
||||
return;
|
||||
}
|
||||
const width = offscreenCanvas.width * state.zoom;
|
||||
|
|
@ -1233,17 +1197,7 @@ function renderEditor(): void {
|
|||
gridContext.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
editorMeta.innerHTML = `
|
||||
<div class="info-card summary-card">
|
||||
<strong>Canvas</strong>
|
||||
<span>${offscreenCanvas.width} x ${offscreenCanvas.height}</span>
|
||||
</div>
|
||||
<div class="info-card summary-card">
|
||||
<strong>State</strong>
|
||||
<span>${state.editorDirty ? "Edited" : "Matches solver output"}</span>
|
||||
</div>
|
||||
`;
|
||||
editorCanvasSizeValue.textContent = `${offscreenCanvas.width} x ${offscreenCanvas.height}`;
|
||||
}
|
||||
|
||||
function setZoom(nextZoom: number, anchor: ViewportAnchor | null = null): void {
|
||||
|
|
@ -1460,9 +1414,6 @@ function applyJobSnapshot(snapshot: Partial<JobStateSnapshot>): void {
|
|||
if (typeof snapshot.status === "string") {
|
||||
setJobState(snapshot.status);
|
||||
}
|
||||
if (snapshot.runSummary && typeof snapshot.runSummary === "object") {
|
||||
state.runSummary = snapshot.runSummary;
|
||||
}
|
||||
}
|
||||
|
||||
async function sendHeartbeat(): Promise<void> {
|
||||
|
|
@ -1571,7 +1522,6 @@ async function renderEverything(): Promise<void> {
|
|||
renderInference();
|
||||
renderStatusMetrics();
|
||||
renderLossChart();
|
||||
renderSummary();
|
||||
await renderViewer();
|
||||
}
|
||||
|
||||
|
|
@ -1813,7 +1763,6 @@ async function handleEvent(eventName: string, payload: JsonRecord): Promise<void
|
|||
break;
|
||||
case "pipeline_completed":
|
||||
state.finalOutputImage = payload.outputImage as ImageAsset;
|
||||
state.runSummary = (payload.runSummary as JsonRecord | null) ?? null;
|
||||
stopHeartbeat();
|
||||
setJobState("completed");
|
||||
setStage("completed", "Run complete", "The output is ready. If the machine still did something stupid, fix it pixel by pixel.");
|
||||
|
|
|
|||
|
|
@ -33,8 +33,7 @@ const gridToggle = byId("gridToggle");
|
|||
const editorCanvas = byId("editorCanvas");
|
||||
const editorGridCanvas = byId("editorGridCanvas");
|
||||
const editorSurface = byId("editorSurface");
|
||||
const editorMeta = byId("editorMeta");
|
||||
const summaryPanel = byId("summaryPanel");
|
||||
const editorCanvasSizeValue = byId("editorCanvasSizeValue");
|
||||
const resetEditorButton = byId("resetEditorButton");
|
||||
const downloadButton = byId("downloadButton");
|
||||
const targetSizeInput = byId("targetSizeInput");
|
||||
|
|
@ -105,7 +104,6 @@ const state = {
|
|||
cleanupImage: null,
|
||||
heatmapImage: null,
|
||||
finalOutputImage: null,
|
||||
runSummary: null,
|
||||
runtimeConfig: null,
|
||||
queueSummary: null,
|
||||
queuePosition: null,
|
||||
|
|
@ -336,7 +334,6 @@ function resetRunArtifacts(options = {}) {
|
|||
state.cleanupImage = null;
|
||||
state.heatmapImage = null;
|
||||
state.finalOutputImage = null;
|
||||
state.runSummary = null;
|
||||
state.queuePosition = null;
|
||||
state.queueDepth = 0;
|
||||
state.waitingCount = 0;
|
||||
|
|
@ -459,16 +456,6 @@ async function fileToImageAsset(file) {
|
|||
height: image.naturalHeight || 1,
|
||||
};
|
||||
}
|
||||
function readNestedNumber(root, path) {
|
||||
let current = root;
|
||||
for (const key of path) {
|
||||
if (!current || typeof current !== "object" || !(key in current)) {
|
||||
return null;
|
||||
}
|
||||
current = current[key];
|
||||
}
|
||||
return typeof current === "number" ? current : null;
|
||||
}
|
||||
function formatNumber(value, digits = 3) {
|
||||
if (value === null || !Number.isFinite(value)) {
|
||||
return "n/a";
|
||||
|
|
@ -635,26 +622,6 @@ function renderStatusMetrics() {
|
|||
}
|
||||
renderStatusMetricItems(items);
|
||||
}
|
||||
function renderSummary() {
|
||||
summaryPanel.innerHTML = "";
|
||||
if (!state.runSummary) {
|
||||
summaryPanel.innerHTML = `<p class="muted">Final metrics show up here after the run finishes.</p>`;
|
||||
return;
|
||||
}
|
||||
const entries = [
|
||||
["Structure score", readNestedNumber(state.runSummary, ["source_structure", "score"])],
|
||||
["Edge F1", readNestedNumber(state.runSummary, ["source_structure", "edge_f1"])],
|
||||
["Exact match", readNestedNumber(state.runSummary, ["source_structure", "exact_match_ratio"])],
|
||||
["Final fidelity", readNestedNumber(state.runSummary, ["source_fidelity", "final_output", "score"])],
|
||||
["Mean displacement", readNestedNumber(state.runSummary, ["optimizer_displacement", "final_output", "mean_magnitude_px"])],
|
||||
];
|
||||
for (const [label, value] of entries) {
|
||||
const card = document.createElement("div");
|
||||
card.className = "info-card summary-card";
|
||||
card.innerHTML = `<strong>${label}</strong><span>${formatNumber(value, 3)}</span>`;
|
||||
summaryPanel.appendChild(card);
|
||||
}
|
||||
}
|
||||
function resolveViewerPanels() {
|
||||
const frame = getSelectedFrame();
|
||||
const debugViewerStage = new Set(["inference", "analysis", "selection", "rerank", "solver", "cleanup"]);
|
||||
|
|
@ -949,7 +916,7 @@ function renderEditor() {
|
|||
editorCanvas.height = 1;
|
||||
editorGridCanvas.width = 1;
|
||||
editorGridCanvas.height = 1;
|
||||
editorMeta.innerHTML = `<p class="muted">Final output lands here once the solver finishes its little pilgrimage.</p>`;
|
||||
editorCanvasSizeValue.textContent = "n/a";
|
||||
return;
|
||||
}
|
||||
const width = offscreenCanvas.width * state.zoom;
|
||||
|
|
@ -982,16 +949,7 @@ function renderEditor() {
|
|||
gridContext.stroke();
|
||||
}
|
||||
}
|
||||
editorMeta.innerHTML = `
|
||||
<div class="info-card summary-card">
|
||||
<strong>Canvas</strong>
|
||||
<span>${offscreenCanvas.width} x ${offscreenCanvas.height}</span>
|
||||
</div>
|
||||
<div class="info-card summary-card">
|
||||
<strong>State</strong>
|
||||
<span>${state.editorDirty ? "Edited" : "Matches solver output"}</span>
|
||||
</div>
|
||||
`;
|
||||
editorCanvasSizeValue.textContent = `${offscreenCanvas.width} x ${offscreenCanvas.height}`;
|
||||
}
|
||||
function setZoom(nextZoom, anchor = null) {
|
||||
const clampedZoom = clampZoom(nextZoom);
|
||||
|
|
@ -1189,9 +1147,6 @@ function applyJobSnapshot(snapshot) {
|
|||
if (typeof snapshot.status === "string") {
|
||||
setJobState(snapshot.status);
|
||||
}
|
||||
if (snapshot.runSummary && typeof snapshot.runSummary === "object") {
|
||||
state.runSummary = snapshot.runSummary;
|
||||
}
|
||||
}
|
||||
async function sendHeartbeat() {
|
||||
if (!state.jobId || !hasActiveJob()) {
|
||||
|
|
@ -1295,7 +1250,6 @@ async function renderEverything() {
|
|||
renderInference();
|
||||
renderStatusMetrics();
|
||||
renderLossChart();
|
||||
renderSummary();
|
||||
await renderViewer();
|
||||
}
|
||||
async function handleEvent(eventName, payload) {
|
||||
|
|
@ -1498,7 +1452,6 @@ async function handleEvent(eventName, payload) {
|
|||
break;
|
||||
case "pipeline_completed":
|
||||
state.finalOutputImage = payload.outputImage;
|
||||
state.runSummary = payload.runSummary ?? null;
|
||||
stopHeartbeat();
|
||||
setJobState("completed");
|
||||
setStage("completed", "Run complete", "The output is ready. If the machine still did something stupid, fix it pixel by pixel.");
|
||||
|
|
|
|||
|
|
@ -97,8 +97,14 @@
|
|||
<p class="copy-tag">Pencil the stragglers. Wheel zooms to cursor, middle mouse pans, Alt steals color.</p>
|
||||
</div>
|
||||
<div class="editor-actions">
|
||||
<button id="resetEditorButton" class="button button-small button-secondary" type="button">Reset Output</button>
|
||||
<button id="downloadButton" class="button button-small" type="button">Export PNG</button>
|
||||
<div id="editorCanvasSizeCard" class="info-card editor-canvas-card">
|
||||
<strong>Canvas</strong>
|
||||
<span id="editorCanvasSizeValue">n/a</span>
|
||||
</div>
|
||||
<div class="editor-button-group">
|
||||
<button id="resetEditorButton" class="button button-small button-secondary" type="button">Reset Output</button>
|
||||
<button id="downloadButton" class="button button-small" type="button">Export PNG</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
|
@ -140,12 +146,6 @@
|
|||
<canvas id="editorCanvas"></canvas>
|
||||
<canvas id="editorGridCanvas"></canvas>
|
||||
</div>
|
||||
<div class="editor-meta">
|
||||
<div id="editorMeta" class="info-stack">
|
||||
<p class="muted">Final output lands here once the solver finishes its little pilgrimage.</p>
|
||||
</div>
|
||||
<div id="summaryPanel" class="summary-panel"></div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
|
|
|
|||
|
|
@ -191,8 +191,6 @@ p {
|
|||
.sidebar,
|
||||
.workspace,
|
||||
.sidebar-section,
|
||||
.editor-meta,
|
||||
.summary-panel,
|
||||
.candidate-list,
|
||||
.event-log,
|
||||
.info-stack {
|
||||
|
|
@ -207,7 +205,6 @@ p {
|
|||
|
||||
.sidebar,
|
||||
.sidebar-section,
|
||||
.summary-panel,
|
||||
.candidate-list,
|
||||
.event-log,
|
||||
.info-stack,
|
||||
|
|
@ -865,6 +862,25 @@ select.field-control optgroup {
|
|||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
margin-left: auto;
|
||||
}
|
||||
|
||||
.editor-button-group {
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 10px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: flex-end;
|
||||
}
|
||||
|
||||
.editor-canvas-card {
|
||||
min-width: 148px;
|
||||
padding: 10px 12px;
|
||||
}
|
||||
|
||||
.editor-canvas-card span {
|
||||
color: var(--ui-text-bright);
|
||||
font-variant-numeric: tabular-nums;
|
||||
}
|
||||
|
||||
.editor-toolbar {
|
||||
|
|
@ -968,10 +984,7 @@ select.field-control optgroup {
|
|||
}
|
||||
|
||||
.editor-stage {
|
||||
display: grid;
|
||||
grid-template-columns: minmax(0, 1fr) 290px;
|
||||
gap: 12px;
|
||||
align-items: start;
|
||||
display: block;
|
||||
}
|
||||
|
||||
.checkerboard {
|
||||
|
|
@ -1011,10 +1024,6 @@ select.field-control optgroup {
|
|||
pointer-events: none;
|
||||
}
|
||||
|
||||
.summary-panel {
|
||||
align-content: start;
|
||||
}
|
||||
|
||||
input[type="range"] {
|
||||
width: 100%;
|
||||
accent-color: var(--ui-accent);
|
||||
|
|
@ -1095,6 +1104,10 @@ select:focus-visible {
|
|||
width: 100%;
|
||||
}
|
||||
|
||||
.editor-button-group {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.status-metrics {
|
||||
grid-template-columns: repeat(var(--status-metric-count), minmax(88px, 1fr));
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue