fix(replay): serve admitted AI overlays without revalidation

This commit is contained in:
DCCONSTRUCTIONS
2026-07-29 23:13:42 +03:00
parent 8e4891d608
commit cc6838ed24
10 changed files with 990 additions and 173 deletions
@@ -10,6 +10,10 @@ import {
} from "../core/observation/liveReceiverWatchdog";
import { postLiveViewerDiagnostic } from "../core/observation/liveViewerDiagnostics";
import type { LiveViewerFailureStage } from "../core/observation/liveViewerDiagnostics";
import {
fetchPerceptionPreparationStatus,
perceptionPreparationMessage,
} from "../core/observation/perceptionPreparation";
import type { RecordedAdmissionPhase } from "../core/observation/recordedSessionAdmission";
export type RerunViewportStatus = "idle" | "loading" | "ready" | "error";
@@ -1575,6 +1579,9 @@ export function RerunViewport({
}
const abort = new AbortController();
let lastReportedPercent = -1;
let requestSettled = false;
let transferStarted = false;
let statusTimer: number | null = null;
onPerceptionLoadChange?.({
phase: "loading",
receivedBytes: 0,
@@ -1582,10 +1589,51 @@ export function RerunViewport({
progress: null,
message: "Сервер готовит AI-слои.",
});
const pollPreparationStatus = () => {
void fetchPerceptionPreparationStatus(
recordedPerceptionUrl,
identity.recordingId,
{
origin: window.location.origin,
signal: abort.signal,
},
).then((status) => {
if (
abort.signal.aborted ||
requestSettled ||
transferStarted ||
perceptionChannelRef.current !== active
) {
return;
}
const message = perceptionPreparationMessage(status.phase);
if (message) {
onPerceptionLoadChange?.({
phase: "loading",
receivedBytes: 0,
totalBytes: status.byteLength,
progress: null,
message,
});
}
}).catch(() => {
// The primary RRD request remains authoritative when status polling is unavailable.
}).finally(() => {
if (!abort.signal.aborted && !requestSettled && !transferStarted) {
statusTimer = window.setTimeout(pollPreparationStatus, 750);
}
});
};
pollPreparationStatus();
void fetchRecordedPerceptionRrd(recordedPerceptionUrl, identity, {
origin: window.location.origin,
signal: abort.signal,
onProgress: (receivedBytes, totalBytes) => {
transferStarted = true;
if (statusTimer !== null) {
window.clearTimeout(statusTimer);
statusTimer = null;
}
const progress = totalBytes > 0 ? receivedBytes / totalBytes : null;
const percent = progress === null ? -1 : Math.floor(progress * 100);
if (percent === lastReportedPercent && receivedBytes !== totalBytes) return;
@@ -1640,8 +1688,18 @@ export function RerunViewport({
: "AI-слои недоступны.",
});
// The base recording remains available when no admitted perception layer exists.
}).finally(() => {
requestSettled = true;
if (statusTimer !== null) {
window.clearTimeout(statusTimer);
statusTimer = null;
}
});
return () => abort.abort();
return () => {
requestSettled = true;
if (statusTimer !== null) window.clearTimeout(statusTimer);
abort.abort();
};
}, [
onPerceptionLoadChange,
perceptionChannelRevision,