beam: add per-model viewer settings
This commit is contained in:
+152
@@ -230,6 +230,77 @@ const resolveUploadSrc = (src) => {
|
||||
return resolved;
|
||||
};
|
||||
|
||||
const normalizeUploadSrcValue = (src) => {
|
||||
const resolved = resolveUploadSrc(src);
|
||||
return resolved ? srcFromUploadPath(resolved) : null;
|
||||
};
|
||||
|
||||
const findManifestForModelSrc = async (src) => {
|
||||
const uploadPath = resolveUploadSrc(src);
|
||||
if (!uploadPath) {
|
||||
return null;
|
||||
}
|
||||
const sourceSrc = srcFromUploadPath(uploadPath);
|
||||
const sourceFormat = CONVERTIBLE_MODEL_FORMATS.get(path.extname(uploadPath).toLowerCase());
|
||||
if (sourceFormat) {
|
||||
return {
|
||||
manifestPath: manifestPathForSource(uploadPath),
|
||||
modelPath: uploadPath,
|
||||
sourceSrc
|
||||
};
|
||||
}
|
||||
|
||||
const directManifestPath = manifestPathForSource(uploadPath);
|
||||
if (existsSync(directManifestPath)) {
|
||||
return {
|
||||
manifestPath: directManifestPath,
|
||||
modelPath: uploadPath,
|
||||
sourceSrc
|
||||
};
|
||||
}
|
||||
|
||||
let entries = [];
|
||||
try {
|
||||
entries = await fs.readdir(path.dirname(uploadPath));
|
||||
} catch (_) {
|
||||
return {
|
||||
manifestPath: directManifestPath,
|
||||
modelPath: uploadPath,
|
||||
sourceSrc
|
||||
};
|
||||
}
|
||||
|
||||
for (const entry of entries) {
|
||||
if (!entry.endsWith(".beam.json")) {
|
||||
continue;
|
||||
}
|
||||
const candidatePath = path.join(path.dirname(uploadPath), entry);
|
||||
const manifest = await readJSONFile(candidatePath);
|
||||
if (!manifest || typeof manifest !== "object") {
|
||||
continue;
|
||||
}
|
||||
const relatedSrcs = [
|
||||
manifest.sourceSrc,
|
||||
manifest.artifactSrc,
|
||||
manifest.fallbackArtifactSrc,
|
||||
manifest.metadataSrc
|
||||
].map(normalizeUploadSrcValue).filter(Boolean);
|
||||
if (relatedSrcs.includes(sourceSrc)) {
|
||||
return {
|
||||
manifestPath: candidatePath,
|
||||
modelPath: uploadPath,
|
||||
sourceSrc: normalizeUploadSrcValue(manifest.sourceSrc) || sourceSrc
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
manifestPath: directManifestPath,
|
||||
modelPath: uploadPath,
|
||||
sourceSrc
|
||||
};
|
||||
};
|
||||
|
||||
const readJSONFile = async (filePath) => {
|
||||
try {
|
||||
const raw = await fs.readFile(filePath, "utf8");
|
||||
@@ -347,6 +418,79 @@ const handleConversionStatus = async (res, searchParams) => {
|
||||
sendJSON(res, 200, manifest);
|
||||
};
|
||||
|
||||
const handleGetModelSettings = async (res, searchParams) => {
|
||||
const manifestRef = await findManifestForModelSrc(searchParams.get("src"));
|
||||
if (!manifestRef) {
|
||||
sendText(res, 400, "Invalid model path");
|
||||
return;
|
||||
}
|
||||
if (!existsSync(manifestRef.modelPath)) {
|
||||
sendText(res, 404, "Model file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
const manifest = await readJSONFile(manifestRef.manifestPath);
|
||||
sendJSON(res, 200, {
|
||||
sourceSrc: manifest?.sourceSrc || manifestRef.sourceSrc,
|
||||
artifactSrc: manifest?.artifactSrc || null,
|
||||
fallbackArtifactSrc: manifest?.fallbackArtifactSrc || null,
|
||||
viewerSettings: manifest?.viewerSettings || null
|
||||
});
|
||||
};
|
||||
|
||||
const handlePutModelSettings = async (req, res, searchParams) => {
|
||||
const manifestRef = await findManifestForModelSrc(searchParams.get("src"));
|
||||
if (!manifestRef) {
|
||||
sendText(res, 400, "Invalid model path");
|
||||
return;
|
||||
}
|
||||
if (!existsSync(manifestRef.modelPath)) {
|
||||
sendText(res, 404, "Model file not found");
|
||||
return;
|
||||
}
|
||||
|
||||
let payload = {};
|
||||
try {
|
||||
payload = await parseJSONBody(req);
|
||||
} catch (err) {
|
||||
sendText(res, 400, err.message || "Invalid JSON");
|
||||
return;
|
||||
}
|
||||
|
||||
const viewerSettings = payload?.viewerSettings;
|
||||
if (!viewerSettings || typeof viewerSettings !== "object" || Array.isArray(viewerSettings)) {
|
||||
sendText(res, 400, "viewerSettings object is required");
|
||||
return;
|
||||
}
|
||||
|
||||
const existing = await readJSONFile(manifestRef.manifestPath) || {};
|
||||
const now = nowIso();
|
||||
const nextManifest = {
|
||||
...existing,
|
||||
sourceSrc: existing.sourceSrc || manifestRef.sourceSrc,
|
||||
status: existing.status || "ready",
|
||||
viewerSettings: {
|
||||
...viewerSettings,
|
||||
updatedAt: viewerSettings.updatedAt || now
|
||||
},
|
||||
viewerSettingsUpdatedAt: now,
|
||||
updatedAt: existing.updatedAt || now
|
||||
};
|
||||
|
||||
try {
|
||||
await writeJSONFile(manifestRef.manifestPath, nextManifest);
|
||||
sendJSON(res, 200, {
|
||||
sourceSrc: nextManifest.sourceSrc,
|
||||
artifactSrc: nextManifest.artifactSrc || null,
|
||||
fallbackArtifactSrc: nextManifest.fallbackArtifactSrc || null,
|
||||
viewerSettings: nextManifest.viewerSettings
|
||||
});
|
||||
} catch (err) {
|
||||
console.error("[server] failed to save model settings", err);
|
||||
sendText(res, 500, "Failed to save model settings");
|
||||
}
|
||||
};
|
||||
|
||||
const buildProjectPayload = (body) => {
|
||||
const now = new Date().toISOString();
|
||||
const id = `proj_${crypto.randomUUID ? crypto.randomUUID() : Math.random().toString(36).slice(2)}`;
|
||||
@@ -634,6 +778,14 @@ const requestHandler = async (req, res) => {
|
||||
return handleConversionStatus(res, url.searchParams);
|
||||
}
|
||||
|
||||
if (req.method === "GET" && url.pathname === "/api/model-settings") {
|
||||
return handleGetModelSettings(res, url.searchParams);
|
||||
}
|
||||
|
||||
if (req.method === "PUT" && url.pathname === "/api/model-settings") {
|
||||
return handlePutModelSettings(req, res, url.searchParams);
|
||||
}
|
||||
|
||||
await serveStatic(req, res, url);
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user