ai-chatbot/lib/worldlabs/splat-utils.ts
Your Name dd2b06f10f
Some checks failed
Lint / build (20) (push) Has been cancelled
Playwright Tests / test (push) Has been cancelled
fix: harden worldlabs asset loading
2026-05-25 17:27:25 +08:00

246 lines
6.6 KiB
TypeScript

function normalizeSplatLabel(label: string) {
return String(label || "").trim().toLowerCase();
}
function parseResolutionScore(label: string) {
const normalized = normalizeSplatLabel(label);
if (!normalized) return 0;
if (
normalized.includes("full") ||
normalized.includes("origin") ||
normalized.includes("source") ||
normalized.includes("max")
) {
return Number.MAX_SAFE_INTEGER;
}
const match = normalized.match(/(\d+(?:\.\d+)?)\s*([km]?)/);
if (!match) return 0;
const value = Number(match[1]);
if (!Number.isFinite(value)) return 0;
const unit = match[2];
if (unit === "m") return Math.round(value * 1_000_000);
if (unit === "k") return Math.round(value * 1_000);
return Math.round(value);
}
function compareSplatEntries(
a: [string, string],
b: [string, string]
) {
const scoreDelta = parseResolutionScore(b[0]) - parseResolutionScore(a[0]);
if (scoreDelta !== 0) return scoreDelta;
return a[0].localeCompare(b[0], "en");
}
export function getSortedWorldLabsSplatEntries(
spzUrls?: Record<string, string> | null
) {
return Object.entries(spzUrls || {})
.filter(
(entry): entry is [string, string] =>
Boolean(entry[0]) && typeof entry[1] === "string" && entry[1].trim().length > 0
)
.sort(compareSplatEntries);
}
export function pickBestWorldLabsSplatEntry(
spzUrls?: Record<string, string> | null
) {
return getSortedWorldLabsSplatEntries(spzUrls)[0] || null;
}
export function pickBestWorldLabsSplatUrl(
spzUrls?: Record<string, string> | null
) {
return pickBestWorldLabsSplatEntry(spzUrls)?.[1] || null;
}
// World Labs exports multiple SPZ tiers (for example 100k / 500k / full).
// For embedded previews we bias hard toward the lightweight tier so users see
// the world quickly, then can switch to imagery / mesh for detail if needed.
const INLINE_WORLDLABS_SPLAT_TARGET_SCORE = 100_000;
function pickClosestResolutionEntry(
entries: Array<[string, string]>,
targetScore: number
) {
if (!entries.length) return null;
const scoredEntries = entries
.map((entry) => ({
entry,
score: parseResolutionScore(entry[0]),
}))
.filter(
(item) =>
Number.isFinite(item.score) &&
item.score > 0 &&
item.score !== Number.MAX_SAFE_INTEGER
);
if (!scoredEntries.length) {
return entries[0] || null;
}
return scoredEntries
.slice()
.sort((left, right) => {
const distanceDelta =
Math.abs(left.score - targetScore) - Math.abs(right.score - targetScore);
if (distanceDelta !== 0) return distanceDelta;
return left.score - right.score;
})[0]?.entry || null;
}
function getResolutionDistance(entry: [string, string], targetScore: number) {
const score = parseResolutionScore(entry[0]);
if (
!Number.isFinite(score) ||
score <= 0 ||
score === Number.MAX_SAFE_INTEGER
) {
return Number.MAX_SAFE_INTEGER;
}
return Math.abs(score - targetScore);
}
function compareInlineSplatEntries(
left: [string, string],
right: [string, string]
) {
const leftDistance = getResolutionDistance(
left,
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
const rightDistance = getResolutionDistance(
right,
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
if (leftDistance !== rightDistance) return leftDistance - rightDistance;
const leftScore = parseResolutionScore(left[0]);
const rightScore = parseResolutionScore(right[0]);
if (leftScore !== rightScore) return leftScore - rightScore;
return left[0].localeCompare(right[0], "en");
}
const KNOWN_ARCHIVED_ASSET_HOSTS = [
"blob.vercel-storage.com",
"s3.1080p.hk",
"img.yydd.ai",
];
function isKnownArchivedAssetHost(hostname: string) {
return KNOWN_ARCHIVED_ASSET_HOSTS.some(
(host) => hostname === host || hostname.endsWith(`.${host}`)
);
}
export function isLikelyWorldLabsTemporaryAssetUrl(value?: string | null) {
const raw = String(value || "").trim();
if (!raw) return false;
if (raw.startsWith("/") || raw.startsWith("blob:") || raw.startsWith("data:")) {
return false;
}
try {
const parsed = new URL(raw);
const hostname = parsed.hostname.toLowerCase();
const pathname = parsed.pathname.toLowerCase();
if (isKnownArchivedAssetHost(hostname)) {
return false;
}
if (hostname.includes("worldlabs") || hostname.includes("marble")) {
return true;
}
const likelyWorldAsset =
pathname.includes("worldlabs") ||
pathname.includes("marble") ||
pathname.includes("spz") ||
pathname.includes("splat") ||
pathname.includes("mesh") ||
pathname.endsWith(".glb") ||
pathname.endsWith(".gltf");
return (
likelyWorldAsset &&
(hostname.includes("googleapis.com") ||
hostname.includes("googleusercontent.com") ||
hostname.includes("cloudfront.net"))
);
} catch {
return false;
}
}
export function isArchivedWorldLabsAssetUrl(value?: string | null) {
const raw = String(value || "").trim();
return Boolean(raw) && !isLikelyWorldLabsTemporaryAssetUrl(raw);
}
export function getArchivedWorldLabsSplatEntries(
spzUrls?: Record<string, string> | null
) {
return getSortedWorldLabsSplatEntries(spzUrls).filter(([, url]) =>
isArchivedWorldLabsAssetUrl(url)
);
}
export function pickBestArchivedWorldLabsSplatUrl(
spzUrls?: Record<string, string> | null
) {
return getArchivedWorldLabsSplatEntries(spzUrls)[0]?.[1] || null;
}
export function pickPreferredInlineWorldLabsSplatEntry(
spzUrls?: Record<string, string> | null
) {
const archivedEntries = getArchivedWorldLabsSplatEntries(spzUrls);
if (archivedEntries.length > 0) {
return pickClosestResolutionEntry(
archivedEntries,
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
}
return pickClosestResolutionEntry(
getSortedWorldLabsSplatEntries(spzUrls),
INLINE_WORLDLABS_SPLAT_TARGET_SCORE
);
}
export function pickPreferredInlineWorldLabsSplatUrl(
spzUrls?: Record<string, string> | null
) {
return pickPreferredInlineWorldLabsSplatEntry(spzUrls)?.[1] || null;
}
export function getPreferredInlineWorldLabsSplatUrls(
spzUrls?: Record<string, string> | null
) {
const archivedEntries = getArchivedWorldLabsSplatEntries(spzUrls);
const sourceEntries =
archivedEntries.length > 0
? archivedEntries
: getSortedWorldLabsSplatEntries(spzUrls);
const orderedEntries = sourceEntries.slice().sort(compareInlineSplatEntries);
const seen = new Set<string>();
return orderedEntries
.map(([, url]) => url)
.filter((url) => {
const normalized = String(url || "").trim();
if (!normalized || seen.has(normalized)) return false;
seen.add(normalized);
return true;
});
}