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
76 lines
2.8 KiB
Vue
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>
|