Files
FabledCurator/frontend/src/components/settings/MLBackfillCard.vue
T
bvandeusenandClaude Opus 4.8 ec66ea5f83
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 22s
CI / backend-lint-and-test (push) Successful in 33s
CI / integration (push) Successful in 3m52s
extension / lint (pull_request) Successful in 10s
refactor(ui): settings-card primitives + fix threshold clamp / card misgroup (#161)
Tier-3 frontend DRY for the ML settings cards, plus the F-D2 clamp bug and the
F-D3 card misgrouping.

New primitives (components/common + composables):
- <SettingToggleRow> — the accent-icon + .fc-section-h label + right-aligned
  switch row (HeadsCard x3, CropProposersCard). iconColor prop absorbs the
  on/off dim.
- <SettingNumberField> — compact numeric field that CLAMPS to [min,max] on
  commit. This fixes F-D2: HeadsCard/CropProposersCard previously sent
  Number(raw) straight to the API, so an out-of-range threshold bounced off the
  400 validator (only TranslationCard clamped). density prop for the grid cards.
- useSettingSave(patchFn) — the busy + patch + toast + revert-on-failure flow
  each card hand-rolled (HeadsCard x6 handlers, CropProposersCard, MLBackfillCard,
  VideoEmbeddingCard). Returns ok/false for the optimistic-switch revert.

Adopted in HeadsCard, CropProposersCard, MLBackfillCard (handler only — its
plain labelled switch is a different affordance), VideoEmbeddingCard.

F-D3: MLThresholdSliders.vue actually rendered a "Video embedding" (frame-
sampling) card but sat under "Tagging → Suggestion thresholds". Renamed it
VideoEmbeddingCard.vue and moved it to the "GPU agent & embeddings" section.

Left deliberately (over-DRY guard): TranslationCard uses an inline error ALERT
(not a toast), already clamps its confidence with a NaN fallback, and lives on
the ImportStore — a genuinely different save pattern, so forcing it onto
useSettingSave would change its UX.

Behaviour-preserving refactor; CI has no Vue type-check so this needs a live
UI pass (toggles persist + revert on failure, thresholds clamp on blur, video
card now under Embeddings).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
2026-07-13 22:28:47 -04:00

76 lines
2.8 KiB
Vue

<template>
<MaintenanceTile
icon="mdi-refresh"
title="CPU embedding backfill"
blurb="Whole-image embeddings without a GPU agent — the built-in fallback."
:open="busy"
>
<p class="text-body-2 mb-3">
Computes the whole-image SigLIP embedding for anything missing one
images directly, videos by sampling frames (the same approach as the
GPU agent). Runs on the ml-worker's CPU, so search, similarity and
head suggestions work <strong>without</strong> a GPU agent; new imports
are embedded this way automatically. Detection, cropping and character
(CCIP) embeddings are GPU-agent-only. Safe to re-run. To re-embed under
a NEW model, use the GPU agent's "Re-embed library" instead.
</p>
<v-switch
v-model="enabled" color="accent" hide-details density="compact"
:loading="saving" label="CPU embedding enabled"
class="mb-1" @update:model-value="onToggle"
/>
<p class="fc-muted text-caption mb-3">
Turn OFF if you run the GPU agent and removed the ml-worker container
imports then stop queueing CPU embed work nothing will consume (the
daily GPU embed backfill covers those images instead).
</p>
<v-btn
color="primary" rounded="pill" :loading="busy" :disabled="!enabled"
@click="run"
>
<v-icon start>mdi-refresh</v-icon> Run backfill now
</v-btn>
<span v-if="done" class="ml-3 text-caption">Enqueued.</span>
<QueueStatusBar queue="ml" queue-label="ML" />
</MaintenanceTile>
</template>
<script setup>
import { toast } from '../../utils/toast.js'
import { onMounted, ref } from 'vue'
import { useMLStore } from '../../stores/ml.js'
import { useSettingSave } from '../../composables/useSettingSave.js'
import MaintenanceTile from '../common/MaintenanceTile.vue'
import QueueStatusBar from './QueueStatusBar.vue'
const store = useMLStore()
const { busy: saving, save } = useSettingSave(store.patchSettings)
const busy = ref(false)
const done = ref(false)
const enabled = ref(true)
onMounted(async () => {
try {
await store.loadSettings()
if (store.settings?.cpu_embed_enabled != null) {
enabled.value = store.settings.cpu_embed_enabled
}
} catch { /* non-fatal */ }
})
async function onToggle() {
const ok = await save({ cpu_embed_enabled: enabled.value }, {
successMessage: enabled.value
? 'CPU embedding on — imports queue embeds for the ml-worker'
: 'CPU embedding off — the GPU embed backfill owns whole-image embeds',
})
if (!ok) enabled.value = !enabled.value
}
async function run() {
busy.value = true
try { await store.triggerBackfill(); done.value = true }
catch (e) { toast({ text: e.message, type: 'error' }) }
finally { busy.value = false }
}
</script>
<style scoped>
</style>