feat(translation): 8h sweep + drain button + detection probe (#1376)
Throughput: translate_posts now runs every 8h (was daily) as the steady-state cadence for newly-imported posts, and the Settings "Translate now" button runs it in drain mode (run-until-done, no reset) so one press clears the whole untranslated backlog instead of a single 300-post chunk. The interrupt/backoff re-enqueue now preserves the drain flag so a bulk drain resumes cleanly after an Interpreter restart. Misdetection groundwork: surface the detector's confidence from the Interpreter client (it was in the detectedLanguage payload but discarded) and add a read-only "Test translation" box — POST /settings/translation/ probe + TranslationCard UI — that shows detected language + confidence + engine + result for pasted text, without saving. Lets the operator see why a short/abbreviation-heavy English title gets mis-detected so the detection guard (min-length + confidence floor) can be tuned from real numbers. The guard itself follows once the mis-detected cases are probed. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01CgZP9v2otxVJymiYsnVuMy
This commit is contained in:
@@ -78,6 +78,61 @@
|
||||
artist, use the Re-translate button on that artist's page.
|
||||
</p>
|
||||
|
||||
<v-divider class="my-3" />
|
||||
|
||||
<!-- Diagnostic probe (task #1376): paste text → what Interpreter DETECTS
|
||||
(language + confidence) and returns, without saving. Used to see why a
|
||||
short/abbreviation-heavy English title gets mis-detected, and to pick a
|
||||
detection-confidence / min-length guard from real numbers. -->
|
||||
<div class="mb-1">
|
||||
<span class="fc-section-h">Test translation</span>
|
||||
<p class="fc-muted text-caption mt-1 mb-2">
|
||||
Paste a title or snippet to see what Interpreter detects and returns —
|
||||
handy for checking why a short string is mis-detected. Nothing is saved.
|
||||
</p>
|
||||
</div>
|
||||
<v-textarea
|
||||
v-model="probeText" label="Text to test" rows="2" auto-grow
|
||||
density="compact" hide-details class="mb-2" :disabled="!baseUrl"
|
||||
/>
|
||||
<div class="d-flex align-center flex-wrap mb-2" style="gap: 8px;">
|
||||
<v-btn
|
||||
size="small" variant="tonal" rounded="pill" prepend-icon="mdi-magnify"
|
||||
:loading="probing" :disabled="!baseUrl || !probeText.trim()"
|
||||
@click="onProbe"
|
||||
>Test translation</v-btn>
|
||||
</div>
|
||||
<v-sheet
|
||||
v-if="probeResult" rounded class="pa-3 mb-1 text-body-2"
|
||||
color="rgba(var(--v-theme-accent), 0.06)"
|
||||
>
|
||||
<div class="d-flex flex-wrap" style="gap: 4px 16px;">
|
||||
<span>
|
||||
<span class="fc-muted">Detected:</span>
|
||||
<strong>{{ probeResult.detected_lang || '—' }}</strong>
|
||||
</span>
|
||||
<span v-if="probeResult.detected_confidence != null">
|
||||
<span class="fc-muted">Confidence:</span>
|
||||
<strong>{{ fmtConf(probeResult.detected_confidence) }}</strong>
|
||||
</span>
|
||||
<span>
|
||||
<span class="fc-muted">Engine:</span>
|
||||
{{ probeResult.engine || '—' }}<template
|
||||
v-if="probeResult.engine_version"
|
||||
> ({{ probeResult.engine_version }})</template>
|
||||
</span>
|
||||
<span><span class="fc-muted">Target:</span> {{ probeResult.target }}</span>
|
||||
</div>
|
||||
<v-divider class="my-2" />
|
||||
<div class="fc-muted mb-1">
|
||||
Result<template v-if="probeResult.detected_lang === probeResult.target">
|
||||
— already {{ probeResult.target }}, so the sweep leaves it as-is</template>:
|
||||
</div>
|
||||
<p class="mb-0" style="white-space: pre-wrap;">{{
|
||||
probeResult.translated || '(no change)'
|
||||
}}</p>
|
||||
</v-sheet>
|
||||
|
||||
<v-alert
|
||||
v-if="err" type="error" variant="tonal" density="compact"
|
||||
class="mt-3" closable @click:close="err = null"
|
||||
@@ -129,6 +184,17 @@ const testResult = ref(null) // null = not tested this session; true/false = l
|
||||
const status = ref(null)
|
||||
const err = ref(null)
|
||||
|
||||
// #1376 "Test translation" probe: paste text → detected lang/confidence + result.
|
||||
const probeText = ref('')
|
||||
const probing = ref(false)
|
||||
const probeResult = ref(null)
|
||||
|
||||
// Confidence scale is Interpreter-defined (0–100 or 0–1) — show it as-is, just
|
||||
// trimmed to 2 decimals, so the operator reads the true number.
|
||||
function fmtConf (c) {
|
||||
return c == null ? '' : Math.round(c * 100) / 100
|
||||
}
|
||||
|
||||
const statusColor = computed(() => {
|
||||
if (!enabled.value || !baseUrl.value) return 'grey'
|
||||
return status.value?.healthy ? 'success' : 'error'
|
||||
@@ -170,6 +236,21 @@ async function onTest() {
|
||||
}
|
||||
}
|
||||
|
||||
async function onProbe () {
|
||||
probing.value = true
|
||||
err.value = null
|
||||
probeResult.value = null
|
||||
try {
|
||||
probeResult.value = await api.post('/api/settings/translation/probe', {
|
||||
body: { text: probeText.value.trim() },
|
||||
})
|
||||
} catch (e) {
|
||||
err.value = e.message
|
||||
} finally {
|
||||
probing.value = false
|
||||
}
|
||||
}
|
||||
|
||||
onMounted(async () => {
|
||||
try {
|
||||
await store.loadSettings()
|
||||
|
||||
Reference in New Issue
Block a user