Files
FabledCurator/frontend/src/components/settings/DangerZoneCard.vue
T
bvandeusen 7c19ad91ed
CI / lint (push) Successful in 2s
CI / frontend-build (push) Successful in 18s
CI / backend-lint-and-test (push) Successful in 37s
CI / integration (push) Successful in 3m28s
feat: cap-aware autoscaler + token-gated whole-instance tag reset (operator feedback)
Autoscaler (agent 2026-07-02.5): the buffer-occupancy signal alone would peg
downloaders at DL_MAX while the bandwidth CAP — not concurrency — is the real
constraint (8 streams sharing 8 MB/s move no more data than 4). Growth is now
gated on the pipe having headroom (net < 85% of cap) and a pipe pinned at the
cap (>= 95%) sheds streams down to 3; dead band prevents flapping. The UI hint
says 'holding at the bandwidth cap' and /status reports bw_capped, so the
behavior is legible without tests that need the ML stack.

Reset content tagging: stays a FULL-instance reset (operator's call), but now
lives in a fenced 'Danger zone' section on Cleanup and the apply is gated by a
preview-derived confirm token (mirrors the Tier-C bulk-delete pattern — stale
counts are rejected server-side). Copy no longer claims suggestions repopulate:
it says plainly the heads' training examples are deleted and re-tagging starts
fresh. Moved out of TagMaintenanceCard into DangerZoneCard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01CDgx8bQS5YrGRK76v8HUnM
2026-07-02 16:14:48 -04:00

123 lines
4.1 KiB
Vue

<template>
<MaintenanceTile
icon="mdi-nuke"
title="Reset content tagging (whole instance)"
blurb="Delete ALL general/character tags and their applications — a start-over. Requires a confirmation code."
destructive
>
<p class="text-body-2 mb-2">
Deletes every <code>general</code> and <code>character</code> tag and
removes them from every image <strong>including the examples the
tagging heads learned from</strong>. Suggestions will <strong>not</strong>
repopulate on their own: you re-tag from scratch, and the heads retrain
from your new tags as they accumulate. Fandoms and series (with their
page order) are kept.
</p>
<v-alert type="error" variant="tonal" density="compact" class="mb-3">
Irreversible no undo except restoring a DB backup
(Settings Maintenance Backup). Back one up first.
</v-alert>
<v-btn
color="accent" variant="flat" rounded="pill"
prepend-icon="mdi-magnify"
:loading="loadingPreview"
class="mb-3"
@click="onPreview"
>Preview content-tag reset</v-btn>
<div v-if="preview">
<p class="text-body-2 mb-2">
<strong>{{ preview.count }}</strong> content tag(s)
<span v-for="(n, k) in preview.by_kind" :key="k" class="fc-muted">
({{ k }}: {{ n }})&nbsp;
</span>
across <strong>{{ preview.applications }}</strong> image
application(s).
</p>
<SampleNameGrid
v-if="preview.sample_names?.length"
:names="preview.sample_names" class="mb-3"
/>
<template v-if="preview.count">
<p class="text-body-2 mb-2">
To arm the reset, type the confirmation code
<code class="fc-code">{{ preview.confirm }}</code> below.
</p>
<div class="d-flex align-center mb-1" style="gap: 12px">
<v-text-field
v-model="typed" density="compact" hide-details variant="outlined"
label="Confirmation code" style="max-width: 200px"
autocomplete="off" spellcheck="false"
/>
<v-btn
color="error" variant="flat" rounded="pill"
prepend-icon="mdi-delete-alert"
:disabled="typed !== preview.confirm"
:loading="committing"
@click="onCommit"
>Delete {{ preview.count }} tag(s) +
{{ preview.applications }} application(s)</v-btn>
</div>
<p class="fc-muted text-caption mb-0">
The code is derived from the counts above if tagging changes
between preview and apply, the server rejects the stale code.
</p>
</template>
</div>
</MaintenanceTile>
</template>
<script setup>
import { ref } from 'vue'
import { toast } from '../../utils/toast.js'
import MaintenanceTile from '../common/MaintenanceTile.vue'
import SampleNameGrid from '../common/SampleNameGrid.vue'
import { useAdminStore } from '../../stores/admin.js'
const store = useAdminStore()
const preview = ref(null)
const loadingPreview = ref(false)
const committing = ref(false)
const typed = ref('')
async function onPreview() {
loadingPreview.value = true
typed.value = ''
try {
preview.value = await store.resetContentTagging({ dryRun: true })
} catch (e) {
toast({ text: `Preview failed: ${e.message}`, type: 'error' })
} finally {
loadingPreview.value = false
}
}
async function onCommit() {
committing.value = true
try {
const res = await store.resetContentTagging({
dryRun: false, confirm: typed.value,
})
toast({ text: `Deleted ${res.deleted} content tag(s) — re-tagging starts fresh`, type: 'success' })
preview.value = null
typed.value = ''
} catch (e) {
toast({ text: `Reset rejected: ${e.message}`, type: 'error' })
} finally {
committing.value = false
}
}
</script>
<style scoped>
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-code {
background: rgb(var(--v-theme-surface-light));
border-radius: 4px; padding: 2px 8px;
font-family: 'JetBrains Mono', monospace; font-weight: 700;
letter-spacing: 0.06em;
}
</style>