A stumble should not colour the desk red for the rest of the service
Build 121. Three things a single 16 s stall did that it should not have. captionOnlyFinals is a running total for the whole service, and the verdict tested it for more than zero — so one stale sentence pinned the light red until the next restart. MEASURED: a campus Mac stalled once at 14:25, recovered immediately to a 421 ms lag with an empty queue, and still showed red half an hour later, with the operator quite reasonably reporting it as behind. The totals still travel for the record; the verdict now asks when it last happened, inside a two-minute window. A light that cannot go back to green teaches the desk to ignore it. That sentence also had its translations skipped. Dropping them is how the app catches up — the listener has moved on and the time is better spent on what is behind it. But that reasoning needs a backlog, and the queue was EMPTY: the room lost a line of the sermon to a catch-up that had nothing to catch up on. It now skips only when something is actually waiting, and says plainly when it chooses to translate a late sentence anyway. The stall itself was not the app. Two Screen Sharing sessions were taking as much of the machine as the app was, with WindowServer on top — watching a campus Mac during a service measurably degrades it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
39dea80433
commit
a06cf35cbb
+29
-4
@@ -190,7 +190,9 @@ const state = {
|
|||||||
|
|
||||||
/* health counters, reported to the operator page */
|
/* health counters, reported to the operator page */
|
||||||
degradedFinals: 0, // finals that shipped with fewer languages (stale)
|
degradedFinals: 0, // finals that shipped with fewer languages (stale)
|
||||||
captionOnlyFinals: 0 // finals that shipped with no translations at all
|
captionOnlyFinals: 0, // finals that shipped with no translations at all
|
||||||
|
captionOnlyAt: 0, // when the last one happened
|
||||||
|
degradedAt: 0
|
||||||
};
|
};
|
||||||
|
|
||||||
// ────────────────────────────────────────────────────────── logging
|
// ────────────────────────────────────────────────────────── logging
|
||||||
@@ -1891,15 +1893,33 @@ async function pump() {
|
|||||||
const sentences = collapseLoops(splitSentences(text));
|
const sentences = collapseLoops(splitSentences(text));
|
||||||
|
|
||||||
let targets = orderedTargets();
|
let targets = orderedTargets();
|
||||||
if (ageSec > 12) {
|
/*
|
||||||
|
* SKIPPING A TRANSLATION ONLY BUYS SOMETHING IF THERE IS A QUEUE TO DRAIN.
|
||||||
|
*
|
||||||
|
* Dropping the translations of stale audio is how the app catches up: the
|
||||||
|
* listener has moved on, so the work is wasted and the time is better spent on
|
||||||
|
* the sentences behind it. That reasoning needs a backlog. With nothing waiting
|
||||||
|
* behind it, skipping costs the room a sentence and saves nothing at all.
|
||||||
|
*
|
||||||
|
* MEASURED: a campus Mac took one 16 s stall — two Screen Sharing sessions
|
||||||
|
* competing for the machine — and skipped that sentence's translations with an
|
||||||
|
* EMPTY queue. The room lost a line of the sermon to a catch-up that had
|
||||||
|
* nothing to catch up on.
|
||||||
|
*/
|
||||||
|
if (ageSec > 12 && queue.length > 0) {
|
||||||
targets = [];
|
targets = [];
|
||||||
state.captionOnlyFinals++;
|
state.captionOnlyFinals++;
|
||||||
log('audio ' + ageSec.toFixed(0) + 's stale — caption only, translations skipped', 'err');
|
state.captionOnlyAt = Date.now();
|
||||||
|
log('audio ' + ageSec.toFixed(0) + 's stale with ' + queue.length + ' waiting — ' +
|
||||||
|
'caption only, translations skipped', 'err');
|
||||||
|
} else if (ageSec > 12) {
|
||||||
|
/* Late, but nothing is behind it: say so, and translate it anyway. */
|
||||||
|
log('audio ' + ageSec.toFixed(0) + 's late, but nothing is queued — translating it anyway', 'warn');
|
||||||
} else if (ageSec > 6) {
|
} else if (ageSec > 6) {
|
||||||
const listening = new Set((state.byLanguage || []).map((r) => r.lang));
|
const listening = new Set((state.byLanguage || []).map((r) => r.lang));
|
||||||
const before = targets.length;
|
const before = targets.length;
|
||||||
targets = targets.filter((t) => listening.has(t));
|
targets = targets.filter((t) => listening.has(t));
|
||||||
if (targets.length < before) state.degradedFinals++;
|
if (targets.length < before) { state.degradedFinals++; state.degradedAt = Date.now(); }
|
||||||
}
|
}
|
||||||
|
|
||||||
/* The segment's own loudness, measured once and shared by every sentence
|
/* The segment's own loudness, measured once and shared by every sentence
|
||||||
@@ -2758,6 +2778,7 @@ function goLive(on) {
|
|||||||
state.live = on;
|
state.live = on;
|
||||||
if (on) {
|
if (on) {
|
||||||
state.degradedFinals = 0; state.captionOnlyFinals = 0; state.fragmentFinals = 0;
|
state.degradedFinals = 0; state.captionOnlyFinals = 0; state.fragmentFinals = 0;
|
||||||
|
state.captionOnlyAt = 0; state.degradedAt = 0;
|
||||||
/* The watchdog measures from the start of the service, not from the last
|
/* The watchdog measures from the start of the service, not from the last
|
||||||
sentence of the previous one. */
|
sentence of the previous one. */
|
||||||
state.lastFinalAt = Date.now();
|
state.lastFinalAt = Date.now();
|
||||||
@@ -2832,6 +2853,10 @@ function reportStatus() {
|
|||||||
queue: queue.length,
|
queue: queue.length,
|
||||||
degradedFinals: state.degradedFinals,
|
degradedFinals: state.degradedFinals,
|
||||||
captionOnlyFinals: state.captionOnlyFinals,
|
captionOnlyFinals: state.captionOnlyFinals,
|
||||||
|
/* WHEN, not just how many — the verdict needs to know whether this is
|
||||||
|
happening now or happened once an hour ago. */
|
||||||
|
captionOnlyAt: state.captionOnlyAt || 0,
|
||||||
|
degradedAt: state.degradedAt || 0,
|
||||||
fragmentFinals: state.fragmentFinals || 0,
|
fragmentFinals: state.fragmentFinals || 0,
|
||||||
cloud: state.cloudStats || null
|
cloud: state.cloudStats || null
|
||||||
}
|
}
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
"name": "church-translate-engine",
|
"name": "church-translate-engine",
|
||||||
"productName": "Church Translate",
|
"productName": "Church Translate",
|
||||||
"version": "3.23.2",
|
"version": "3.23.2",
|
||||||
"buildNumber": 120,
|
"buildNumber": 121,
|
||||||
"private": true,
|
"private": true,
|
||||||
"description": "Live sermon translation — self-contained: captures audio, transcribes, translates, and serves the congregation page itself",
|
"description": "Live sermon translation — self-contained: captures audio, transcribes, translates, and serves the congregation page itself",
|
||||||
"main": "main.js",
|
"main": "main.js",
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
"asar": false,
|
"asar": false,
|
||||||
"buildVersion": "120",
|
"buildVersion": "121",
|
||||||
"win": {
|
"win": {
|
||||||
"target": [
|
"target": [
|
||||||
{
|
{
|
||||||
|
|||||||
+24
-2
@@ -510,6 +510,11 @@ const ENGINE_STALE_MS = 12000;
|
|||||||
* A campus reported "constantly behind, in the red" and the cause took an
|
* A campus reported "constantly behind, in the red" and the cause took an
|
||||||
* afternoon to find; it was eight languages on hardware tuned against one.
|
* afternoon to find; it was eight languages on hardware tuned against one.
|
||||||
*/
|
*/
|
||||||
|
/* How long a stumble keeps colouring the verdict. Long enough that a real
|
||||||
|
problem stays visible while someone walks to the desk, short enough that a
|
||||||
|
one-off does not outlive the sentence after it. */
|
||||||
|
const TROUBLE_WINDOW_MS = 120000;
|
||||||
|
|
||||||
function loadNote() {
|
function loadNote() {
|
||||||
const n = activeLanguages().length;
|
const n = activeLanguages().length;
|
||||||
if (n < 2) return '';
|
if (n < 2) return '';
|
||||||
@@ -523,10 +528,27 @@ function healthVerdict(connected, stale) {
|
|||||||
if (stale) return { level: 'red', detail: 'The translation app has stopped responding.' };
|
if (stale) return { level: 'red', detail: 'The translation app has stopped responding.' };
|
||||||
if (!state.live) return { level: 'green', detail: 'Ready.' };
|
if (!state.live) return { level: 'green', detail: 'Ready.' };
|
||||||
if (!es.modelsReady) return { level: 'amber', detail: 'Models are still loading.' };
|
if (!es.modelsReady) return { level: 'amber', detail: 'Models are still loading.' };
|
||||||
if ((es.lagMs || 0) > 12000 || (es.captionOnlyFinals || 0) > 0)
|
/*
|
||||||
|
* RECENTLY, not ever.
|
||||||
|
*
|
||||||
|
* captionOnlyFinals is a running total for the whole service, and testing it
|
||||||
|
* for "more than zero" meant a single stale sentence pinned the desk's light
|
||||||
|
* red until the next restart. MEASURED: a campus Mac took one 16 s stall at
|
||||||
|
* 14:25 — caused by two Screen Sharing sessions competing for the machine —
|
||||||
|
* recovered immediately to a 421 ms lag with an empty queue, and still showed
|
||||||
|
* red half an hour later. An operator who looks up and sees red wants to know
|
||||||
|
* what is wrong NOW; a light that cannot go back to green teaches them to
|
||||||
|
* ignore it.
|
||||||
|
*
|
||||||
|
* So the totals still travel, for the record and for the operator page, but
|
||||||
|
* the verdict asks when it last happened.
|
||||||
|
*/
|
||||||
|
const recentCaptionOnly = es.captionOnlyAt && (Date.now() - es.captionOnlyAt) < TROUBLE_WINDOW_MS;
|
||||||
|
if ((es.lagMs || 0) > 12000 || recentCaptionOnly)
|
||||||
return { level: 'red',
|
return { level: 'red',
|
||||||
detail: 'Falling badly behind — some sentences are captions only.' + loadNote() };
|
detail: 'Falling badly behind — some sentences are captions only.' + loadNote() };
|
||||||
if ((es.lagMs || 0) > 6000 || (es.queue || 0) >= 2 || (es.degradedFinals || 0) > 0)
|
const recentDegraded = es.degradedAt && (Date.now() - es.degradedAt) < TROUBLE_WINDOW_MS;
|
||||||
|
if ((es.lagMs || 0) > 6000 || (es.queue || 0) >= 2 || recentDegraded)
|
||||||
return { level: 'amber',
|
return { level: 'amber',
|
||||||
detail: 'Running behind — idle languages may be trimmed to catch up.' + loadNote() };
|
detail: 'Running behind — idle languages may be trimmed to catch up.' + loadNote() };
|
||||||
if (es.cloud && es.cloud.parked)
|
if (es.cloud && es.cloud.parked)
|
||||||
|
|||||||
Reference in New Issue
Block a user