fix(replay): serve admitted AI overlays without revalidation
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
export type PerceptionPreparationPhase =
|
||||
| "idle"
|
||||
| "cache-lookup"
|
||||
| "queued"
|
||||
| "artifact-validation"
|
||||
| "rendering"
|
||||
| "cache-write"
|
||||
| "ready"
|
||||
| "unavailable"
|
||||
| "error";
|
||||
|
||||
export interface PerceptionPreparationStatus {
|
||||
state: "idle" | "preparing" | "ready" | "unavailable" | "error";
|
||||
phase: PerceptionPreparationPhase;
|
||||
elapsedSeconds: number;
|
||||
byteLength: number | null;
|
||||
}
|
||||
|
||||
const RECORDED_PERCEPTION_PATH =
|
||||
/^\/api\/v1\/observation-sessions\/[A-Za-z0-9][A-Za-z0-9._:-]{0,127}\/perception\.rrd$/;
|
||||
const SAFE_RECORDING_ID = /^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/;
|
||||
|
||||
export async function fetchPerceptionPreparationStatus(
|
||||
endpointUrl: string,
|
||||
recordingId: string,
|
||||
{
|
||||
origin,
|
||||
signal,
|
||||
fetcher = globalThis.fetch,
|
||||
}: {
|
||||
origin: string;
|
||||
signal?: AbortSignal;
|
||||
fetcher?: typeof globalThis.fetch;
|
||||
},
|
||||
): Promise<PerceptionPreparationStatus> {
|
||||
const base = new URL(origin);
|
||||
const endpoint = new URL(endpointUrl, base.origin);
|
||||
if (
|
||||
endpoint.origin !== base.origin ||
|
||||
endpoint.search ||
|
||||
endpoint.hash ||
|
||||
!RECORDED_PERCEPTION_PATH.test(endpoint.pathname) ||
|
||||
!SAFE_RECORDING_ID.test(recordingId)
|
||||
) {
|
||||
throw new Error("Unsafe perception preparation status request");
|
||||
}
|
||||
endpoint.pathname = endpoint.pathname.replace(/\/perception\.rrd$/, "/perception/status");
|
||||
endpoint.searchParams.set("recording_id", recordingId);
|
||||
const response = await fetcher(endpoint.href, {
|
||||
method: "GET",
|
||||
cache: "no-store",
|
||||
credentials: "same-origin",
|
||||
headers: { Accept: "application/json" },
|
||||
signal,
|
||||
});
|
||||
if (!response.ok) {
|
||||
throw new Error("Perception preparation status is unavailable");
|
||||
}
|
||||
const value: unknown = await response.json();
|
||||
if (!isPerceptionPreparationStatus(value)) {
|
||||
throw new Error("Invalid perception preparation status");
|
||||
}
|
||||
return {
|
||||
state: value.state,
|
||||
phase: value.phase,
|
||||
elapsedSeconds: value.elapsed_seconds,
|
||||
byteLength: value.byte_length,
|
||||
};
|
||||
}
|
||||
|
||||
export function perceptionPreparationMessage(
|
||||
phase: PerceptionPreparationPhase,
|
||||
): string | null {
|
||||
switch (phase) {
|
||||
case "cache-lookup":
|
||||
return "Проверяем готовый AI-слой.";
|
||||
case "queued":
|
||||
return "AI-слой ожидает последовательной подготовки.";
|
||||
case "artifact-validation":
|
||||
return "Проверяем опубликованный AI-результат.";
|
||||
case "rendering":
|
||||
return "Формируем AI-слой.";
|
||||
case "cache-write":
|
||||
return "Сохраняем AI-слой.";
|
||||
case "ready":
|
||||
return "AI-слой готов. Загружаем.";
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
function isPerceptionPreparationStatus(
|
||||
value: unknown,
|
||||
): value is {
|
||||
state: PerceptionPreparationStatus["state"];
|
||||
phase: PerceptionPreparationPhase;
|
||||
elapsed_seconds: number;
|
||||
byte_length: number | null;
|
||||
} {
|
||||
if (typeof value !== "object" || value === null) return false;
|
||||
const candidate = value as Record<string, unknown>;
|
||||
return (
|
||||
["idle", "preparing", "ready", "unavailable", "error"].includes(
|
||||
String(candidate.state),
|
||||
) &&
|
||||
[
|
||||
"idle",
|
||||
"cache-lookup",
|
||||
"queued",
|
||||
"artifact-validation",
|
||||
"rendering",
|
||||
"cache-write",
|
||||
"ready",
|
||||
"unavailable",
|
||||
"error",
|
||||
].includes(String(candidate.phase)) &&
|
||||
typeof candidate.elapsed_seconds === "number" &&
|
||||
Number.isFinite(candidate.elapsed_seconds) &&
|
||||
candidate.elapsed_seconds >= 0 &&
|
||||
(candidate.byte_length === null ||
|
||||
(typeof candidate.byte_length === "number" &&
|
||||
Number.isSafeInteger(candidate.byte_length) &&
|
||||
candidate.byte_length >= 0))
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user