feat(ui): presentation-chrome auto-hide Settings controls (#141 step 4)
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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
This commit is contained in:
@@ -159,6 +159,42 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Presentation chrome auto-hide (#141) -->
|
||||||
|
<div class="fc-auto mt-6">
|
||||||
|
<div class="d-flex align-center mb-1" style="gap: 10px;">
|
||||||
|
<v-icon size="18" color="accent">mdi-image-off-outline</v-icon>
|
||||||
|
<span class="fc-section-h">Hide presentation chrome</span>
|
||||||
|
<v-switch
|
||||||
|
v-model="presentationEnabled" :loading="settingBusy" hide-details
|
||||||
|
density="compact" color="success" class="ml-auto"
|
||||||
|
@update:model-value="onTogglePresentation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
<p class="fc-muted text-body-2 mb-3">
|
||||||
|
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.
|
||||||
|
<code>wip</code> 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.
|
||||||
|
</p>
|
||||||
|
<div class="d-flex mb-3" style="gap: 12px;">
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="presentationThresholdInput" label="Hide confidence"
|
||||||
|
type="number" min="0.5" max="0.999" step="0.01" density="compact"
|
||||||
|
hide-details style="max-width: 200px;" :disabled="settingBusy"
|
||||||
|
@change="onSavePresentation"
|
||||||
|
/>
|
||||||
|
<v-text-field
|
||||||
|
v-model.number="presentationConflictInput" label="Flag if content ≥"
|
||||||
|
type="number" min="0" max="1" step="0.05" density="compact"
|
||||||
|
hide-details style="max-width: 200px;" :disabled="settingBusy"
|
||||||
|
@change="onSavePresentation"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Performance / tuning -->
|
<!-- Performance / tuning -->
|
||||||
<div v-if="metricsConcepts.length" class="mt-5">
|
<div v-if="metricsConcepts.length" class="mt-5">
|
||||||
<div class="fc-section-h mb-1">How auto-apply is landing</div>
|
<div class="fc-section-h mb-1">How auto-apply is landing</div>
|
||||||
@@ -218,6 +254,11 @@ const autoStatus = ref(null)
|
|||||||
const metricsData = ref(null)
|
const metricsData = ref(null)
|
||||||
let autoTimer = 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 autoRunning = computed(() => autoStatus.value?.running_id != null)
|
||||||
const lastSweep = computed(() =>
|
const lastSweep = computed(() =>
|
||||||
(autoStatus.value?.runs || []).find(r => r.status !== 'running') || null)
|
(autoStatus.value?.runs || []).find(r => r.status !== 'running') || null)
|
||||||
@@ -248,6 +289,9 @@ onMounted(async () => {
|
|||||||
autoEnabled.value = !!s.head_auto_apply_enabled
|
autoEnabled.value = !!s.head_auto_apply_enabled
|
||||||
autoPrecisionInput.value = s.head_auto_apply_precision ?? 0.97
|
autoPrecisionInput.value = s.head_auto_apply_precision ?? 0.97
|
||||||
autoMinPosInput.value = s.head_auto_apply_min_positives ?? 30
|
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 */ }
|
} catch { /* non-fatal */ }
|
||||||
await refresh()
|
await refresh()
|
||||||
if (running.value) startPoll()
|
if (running.value) startPoll()
|
||||||
@@ -332,6 +376,32 @@ async function onSaveSettings() {
|
|||||||
settingBusy.value = false
|
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 onPreview() { startSweep(true) }
|
||||||
function onApplyNow() { startSweep(false) }
|
function onApplyNow() { startSweep(false) }
|
||||||
async function startSweep(dryRun) {
|
async function startSweep(dryRun) {
|
||||||
|
|||||||
Reference in New Issue
Block a user