From 1f548d8a7b1067dddaa142609d1ec3cd4d50f9de Mon Sep 17 00:00:00 2001 From: Bryan Van Deusen Date: Mon, 6 Jul 2026 23:16:41 -0400 Subject: [PATCH] feat(ui): presentation-chrome auto-hide Settings controls (#141 step 4) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HeadsCard gains a "Hide presentation chrome" section: on/off switch + "Hide confidence" (presentation_auto_apply_threshold) + "Flag if content ≥" (presentation_conflict_threshold), wired to MLSettings via patchSettings and loaded on mount. Makes the step-4 sweep's thresholds operator-tunable (config-in-UI). wip is called out as never auto-hidden. Co-Authored-By: Claude Opus 4.8 (1M context) Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM --- .../src/components/settings/HeadsCard.vue | 70 +++++++++++++++++++ 1 file changed, 70 insertions(+) diff --git a/frontend/src/components/settings/HeadsCard.vue b/frontend/src/components/settings/HeadsCard.vue index 7117b0d..aad490a 100644 --- a/frontend/src/components/settings/HeadsCard.vue +++ b/frontend/src/components/settings/HeadsCard.vue @@ -159,6 +159,42 @@ + +
+
+ mdi-image-off-outline + Hide presentation chrome + +
+

+ Auto-hide banners and editor screenshots from the gallery once a head has + learned them (≥ {{ minPositives }} examples) and clears + {{ Math.round((presentationThresholdInput || 0) * 100) }}% confidence. + wip is never auto-hidden. If a hidden image also looks like + real content (≥ {{ Math.round((presentationConflictInput || 0) * 100) }}% + on a content tag), it's flagged in the Hidden view instead of buried. + Every auto-hide is reversible. +

+
+ + +
+
+
How auto-apply is landing
@@ -218,6 +254,11 @@ const autoStatus = ref(null) const metricsData = ref(null) let autoTimer = null +// --- Presentation chrome auto-hide state (#141) --- +const presentationEnabled = ref(true) +const presentationThresholdInput = ref(0.90) +const presentationConflictInput = ref(0.50) + const autoRunning = computed(() => autoStatus.value?.running_id != null) const lastSweep = computed(() => (autoStatus.value?.runs || []).find(r => r.status !== 'running') || null) @@ -248,6 +289,9 @@ onMounted(async () => { autoEnabled.value = !!s.head_auto_apply_enabled autoPrecisionInput.value = s.head_auto_apply_precision ?? 0.97 autoMinPosInput.value = s.head_auto_apply_min_positives ?? 30 + presentationEnabled.value = s.presentation_auto_apply_enabled ?? true + presentationThresholdInput.value = s.presentation_auto_apply_threshold ?? 0.90 + presentationConflictInput.value = s.presentation_conflict_threshold ?? 0.50 } catch { /* non-fatal */ } await refresh() if (running.value) startPoll() @@ -332,6 +376,32 @@ async function onSaveSettings() { settingBusy.value = false } } + +async function onTogglePresentation(val) { + settingBusy.value = true + try { + await mlSettings.patchSettings({ presentation_auto_apply_enabled: !!val }) + toast({ text: val ? 'Chrome auto-hide on' : 'Chrome auto-hide off', type: 'success' }) + } catch (e) { + presentationEnabled.value = !val // revert the switch + toast({ text: `Could not update: ${e.message}`, type: 'error' }) + } finally { + settingBusy.value = false + } +} +async function onSavePresentation() { + settingBusy.value = true + try { + await mlSettings.patchSettings({ + presentation_auto_apply_threshold: Number(presentationThresholdInput.value), + presentation_conflict_threshold: Number(presentationConflictInput.value), + }) + } catch (e) { + toast({ text: `Could not save: ${e.message}`, type: 'error' }) + } finally { + settingBusy.value = false + } +} function onPreview() { startSweep(true) } function onApplyNow() { startSweep(false) } async function startSweep(dryRun) {