compare view

This commit is contained in:
2026-03-30 13:05:28 -03:00
parent aac27b8504
commit 55e83e4203
23 changed files with 1321 additions and 201 deletions

View File

@@ -10,7 +10,9 @@ import BrandTablePanel from './panels/BrandTablePanel.vue'
import TimelinePanel from './panels/TimelinePanel.vue'
import CostStatsPanel from './panels/CostStatsPanel.vue'
import SourceSelector from './panels/SourceSelector.vue'
import CompareView from './panels/CompareView.vue'
import StageConfig from './components/StageConfig.vue'
import OverlayControls from './components/OverlayControls.vue'
import FrameStrip from './components/FrameStrip.vue'
import { usePipelineStore } from './stores/pipeline'
import { useSSEConnection } from './composables/useSSEConnection'
@@ -41,9 +43,14 @@ const {
// Editor overlays + CV result accumulation
const {
editorOverlays, editorBoxes,
updateDisplayForFrame, onReplayResult, setActiveStage,
updateDisplayForFrame, onReplayResult: _onReplayResult, setActiveStage,
saveOverlaysToCache,
} = useEditorState(currentFrameRef)
function onReplayResult(result: any) {
_onReplayResult(result)
}
// Set active stage when editor opens
watch(() => pipeline.editorStage, (stage) => {
if (stage) setActiveStage(stage)
@@ -56,6 +63,13 @@ function setCheckpointFrame(index: number) {
if (frame) updateDisplayForFrame(frame.seq)
}
// Save overlays to S3 cache then close editor
function closeEditorWithSave() {
saveOverlaysToCache(pipeline.timelineId, pipeline.jobId)
// Small delay to let the save requests fire before navigation
setTimeout(() => pipeline.closeEditor(), 100)
}
// Wire job start to clear log panel
function onJobStarted(newJobId: string, opts?: { pauseAfterStage?: boolean }) {
logPanel.value?.clear()
@@ -74,6 +88,11 @@ function onJobStarted(newJobId: string, opts?: { pauseAfterStage?: boolean }) {
<path d="M2 4h4l2 2h6v8H2V4z"/><path d="M2 4V2h12v2"/>
</svg>
</button>
<button class="header-btn" title="Compare jobs" @click="pipeline.openCompare()">
<svg width="14" height="14" viewBox="0 0 16 16" fill="none" stroke="currentColor" stroke-width="1.5">
<rect x="1" y="3" width="6" height="10" rx="1"/><rect x="9" y="3" width="6" height="10" rx="1"/><path d="M8 5v6"/>
</svg>
</button>
<!-- Transport controls visible when a pipeline is running -->
<div v-if="sseConnected && (status === 'live' || status === 'processing' || paused)" class="transport">
@@ -210,21 +229,8 @@ function onJobStarted(newJobId: string, opts?: { pauseAfterStage?: boolean }) {
/>
<div class="editor-bottom">
<div class="overlay-controls">
<label v-for="(overlay, idx) in editorOverlays" :key="idx" class="overlay-toggle">
<input type="checkbox" v-model="overlay.visible" />
<span class="overlay-label">{{ overlay.label }}</span>
<input
type="range"
min="0" max="1" step="0.05"
:value="overlay.opacity ?? 0.5"
@input="(e: Event) => overlay.opacity = Number((e.target as HTMLInputElement).value)"
class="opacity-slider"
/>
<span class="opacity-value">{{ Math.round((overlay.opacity ?? 0.5) * 100) }}%</span>
</label>
</div>
<button class="editor-close" @click="pipeline.closeEditor()">✕ Close</button>
<OverlayControls :overlays="editorOverlays" />
<button class="editor-close" @click="closeEditorWithSave()">✕ Close</button>
</div>
</div>
@@ -242,6 +248,9 @@ function onJobStarted(newJobId: string, opts?: { pauseAfterStage?: boolean }) {
<!-- === SOURCE SELECTOR MODE === -->
<SourceSelector v-else-if="pipeline.layoutMode === 'source_selector'" @job-started="(id: string, opts: any) => onJobStarted(id, opts)" />
<!-- === COMPARE MODE === -->
<CompareView v-else-if="pipeline.layoutMode === 'compare'" />
</template>
</SplitPane>
@@ -457,41 +466,6 @@ header h1 { font-size: var(--font-size-lg); font-weight: 600; }
font-size: var(--font-size-sm);
}
.overlay-controls {
display: flex;
gap: var(--space-4);
padding: var(--space-2) var(--space-3);
align-items: center;
height: 100%;
}
.overlay-toggle {
display: flex;
align-items: center;
gap: var(--space-2);
font-size: var(--font-size-sm);
color: var(--text-secondary);
cursor: pointer;
}
.overlay-toggle input[type="checkbox"] {
accent-color: #00bcd4;
}
.overlay-label {
min-width: 80px;
}
.opacity-slider {
width: 80px;
height: 3px;
}
.opacity-value {
font-size: 10px;
color: var(--text-dim);
min-width: 30px;
}
/* Source selector */
.source-selector {