fix(replay): serve admitted AI overlays without revalidation
This commit is contained in:
@@ -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,
|
||||
|
||||
Reference in New Issue
Block a user