-
- mdi-progress-wrench
- Auto-tag work-in-progress
-
-
+
Auto-tag wip and editor screenshot process art
once a head has learned them (≥ {{ minPositives }} examples) and clears
@@ -217,16 +200,14 @@
manual tags, never its own guesses — so it can't run away. Every tag reversible.
-
-
@@ -272,6 +253,9 @@ import { toast } from '../../utils/toast.js'
import { computed, onMounted, onUnmounted, ref } from 'vue'
import MaintenanceTile from '../common/MaintenanceTile.vue'
+import SettingNumberField from '../common/SettingNumberField.vue'
+import SettingToggleRow from '../common/SettingToggleRow.vue'
+import { useSettingSave } from '../../composables/useSettingSave.js'
import { useHeadsStore } from '../../stores/heads.js'
import { useMLStore } from '../../stores/ml.js'
@@ -285,7 +269,9 @@ let pollTimer = null
const autoEnabled = ref(false)
const autoPrecisionInput = ref(0.97)
const autoMinPosInput = ref(30)
-const settingBusy = ref(false)
+// Shared settings-save flow (busy + toast + revert); `settingBusy` gates the
+// toggles/fields, `save` returns ok/false for the optimistic-switch revert.
+const { busy: settingBusy, save } = useSettingSave(mlSettings.patchSettings)
const autoBusy = ref(false)
const autoStatus = ref(null)
const metricsData = ref(null)
@@ -395,81 +381,39 @@ function startAutoPoll() {
function stopAutoPoll() { if (autoTimer) { clearInterval(autoTimer); autoTimer = null } }
async function onToggleAuto(val) {
- settingBusy.value = true
- try {
- await mlSettings.patchSettings({ head_auto_apply_enabled: !!val })
- toast({ text: val ? 'Auto-apply on' : 'Auto-apply off', type: 'success' })
- } catch (e) {
- autoEnabled.value = !val // revert the switch
- toast({ text: `Could not update: ${e.message}`, type: 'error' })
- } finally {
- settingBusy.value = false
- }
+ const ok = await save({ head_auto_apply_enabled: !!val },
+ { successMessage: val ? 'Auto-apply on' : 'Auto-apply off', errorPrefix: 'Could not update' })
+ if (!ok) autoEnabled.value = !val // revert the switch
}
async function onSaveSettings() {
- settingBusy.value = true
- try {
- await mlSettings.patchSettings({
- head_auto_apply_precision: Number(autoPrecisionInput.value),
- head_auto_apply_min_positives: Number(autoMinPosInput.value),
- })
- } catch (e) {
- toast({ text: `Could not save: ${e.message}`, type: 'error' })
- } finally {
- settingBusy.value = false
- }
+ await save({
+ head_auto_apply_precision: Number(autoPrecisionInput.value),
+ head_auto_apply_min_positives: Number(autoMinPosInput.value),
+ })
}
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
- }
+ const ok = await save({ presentation_auto_apply_enabled: !!val },
+ { successMessage: val ? 'Chrome auto-hide on' : 'Chrome auto-hide off', errorPrefix: 'Could not update' })
+ if (!ok) presentationEnabled.value = !val // revert the switch
}
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
- }
+ await save({
+ presentation_auto_apply_threshold: Number(presentationThresholdInput.value),
+ presentation_conflict_threshold: Number(presentationConflictInput.value),
+ })
}
async function onToggleProcess(val) {
- settingBusy.value = true
- try {
- await mlSettings.patchSettings({ process_auto_apply_enabled: !!val })
- toast({ text: val ? 'WIP auto-tag on' : 'WIP auto-tag off', type: 'success' })
- } catch (e) {
- processEnabled.value = !val // revert the switch
- toast({ text: `Could not update: ${e.message}`, type: 'error' })
- } finally {
- settingBusy.value = false
- }
+ const ok = await save({ process_auto_apply_enabled: !!val },
+ { successMessage: val ? 'WIP auto-tag on' : 'WIP auto-tag off', errorPrefix: 'Could not update' })
+ if (!ok) processEnabled.value = !val // revert the switch
}
async function onSaveProcess() {
- settingBusy.value = true
- try {
- await mlSettings.patchSettings({
- process_auto_apply_threshold: Number(processThresholdInput.value),
- process_conflict_threshold: Number(processConflictInput.value),
- })
- } catch (e) {
- toast({ text: `Could not save: ${e.message}`, type: 'error' })
- } finally {
- settingBusy.value = false
- }
+ await save({
+ process_auto_apply_threshold: Number(processThresholdInput.value),
+ process_conflict_threshold: Number(processConflictInput.value),
+ })
}
function onPreview() { startSweep(true) }
function onApplyNow() { startSweep(false) }
diff --git a/frontend/src/components/settings/MLBackfillCard.vue b/frontend/src/components/settings/MLBackfillCard.vue
index aca226a..dafae6f 100644
--- a/frontend/src/components/settings/MLBackfillCard.vue
+++ b/frontend/src/components/settings/MLBackfillCard.vue
@@ -39,13 +39,14 @@
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)
-const saving = ref(false)
onMounted(async () => {
try {
await store.loadSettings()
@@ -55,21 +56,12 @@ onMounted(async () => {
} catch { /* non-fatal */ }
})
async function onToggle() {
- saving.value = true
- try {
- await store.patchSettings({ cpu_embed_enabled: enabled.value })
- toast({
- text: enabled.value
- ? 'CPU embedding on — imports queue embeds for the ml-worker'
- : 'CPU embedding off — the GPU embed backfill owns whole-image embeds',
- type: 'success',
- })
- } catch (e) {
- toast({ text: `Could not save: ${e.message}`, type: 'error' })
- enabled.value = !enabled.value
- } finally {
- saving.value = false
- }
+ 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
diff --git a/frontend/src/components/settings/MaintenancePanel.vue b/frontend/src/components/settings/MaintenancePanel.vue
index b379015..372f920 100644
--- a/frontend/src/components/settings/MaintenancePanel.vue
+++ b/frontend/src/components/settings/MaintenancePanel.vue
@@ -24,6 +24,7 @@
the CPU fallback.
+
@@ -36,7 +37,6 @@
Suggestion thresholds, trained heads and tag aliases.
-
@@ -77,7 +77,7 @@ import ArchiveReextractCard from './ArchiveReextractCard.vue'
import MissingFileRepairCard from './MissingFileRepairCard.vue'
import GpuTriageCard from './GpuTriageCard.vue'
import DbMaintenanceCard from './DbMaintenanceCard.vue'
-import MLThresholdSliders from './MLThresholdSliders.vue'
+import VideoEmbeddingCard from './VideoEmbeddingCard.vue'
import CropProposersCard from './CropProposersCard.vue'
import HeadsCard from './HeadsCard.vue'
import GpuAgentCard from './GpuAgentCard.vue'
diff --git a/frontend/src/components/settings/MLThresholdSliders.vue b/frontend/src/components/settings/VideoEmbeddingCard.vue
similarity index 53%
rename from frontend/src/components/settings/MLThresholdSliders.vue
rename to frontend/src/components/settings/VideoEmbeddingCard.vue
index 49c4a59..dcd34a9 100644
--- a/frontend/src/components/settings/MLThresholdSliders.vue
+++ b/frontend/src/components/settings/VideoEmbeddingCard.vue
@@ -12,17 +12,17 @@
-
-
@@ -32,21 +32,23 @@
diff --git a/frontend/src/composables/useSettingSave.js b/frontend/src/composables/useSettingSave.js
new file mode 100644
index 0000000..dced75d
--- /dev/null
+++ b/frontend/src/composables/useSettingSave.js
@@ -0,0 +1,33 @@
+import { ref } from 'vue'
+import { toast } from '../utils/toast.js'
+
+// The shared "persist a settings patch" flow for the ML settings cards. Flips a
+// busy flag, calls the store's patch (which rethrows on failure), toasts
+// success/error, and returns true/false so a toggle handler can revert its
+// optimistic switch on failure. Centralises the try/catch/toast the cards each
+// hand-rolled (HeadsCard x6, CropProposersCard, MLBackfillCard) — and where the
+// threshold-clamp drifted; the clamp now lives in
.
+//
+// Pass the store's patch fn, e.g. useSettingSave(ml.patchSettings).
+export function useSettingSave(patchFn) {
+ const busy = ref(false)
+
+ // opts.successMessage — toast on success (toggles announce their new state;
+ // silent field-saves omit it). opts.errorPrefix — the failure toast prefix
+ // ("Could not save" default; toggles used "Could not update").
+ async function save(patch, { successMessage = '', errorPrefix = 'Could not save' } = {}) {
+ busy.value = true
+ try {
+ await patchFn(patch)
+ if (successMessage) toast({ text: successMessage, type: 'success' })
+ return true
+ } catch (e) {
+ toast({ text: `${errorPrefix}: ${e.message}`, type: 'error' })
+ return false
+ } finally {
+ busy.value = false
+ }
+ }
+
+ return { busy, save }
+}