Source of truth. This page mirrors state-server/docs/JS/model-processing.md. The repo is canonical — update there first, then resync here.
ModelPreviewService (sdk.modelPreview) handles three async pipelines plus the projection-derived 3D model flow:
All methods are available both on sdk.modelPreview.* and as convenience wrappers on sdk.*.
import fs from 'node:fs';
const modelBytes = await fs.promises.readFile('./cube.glb');
const result = await sdk.submitModelPreview('glb', modelBytes);
const result2 = await sdk.submitModelPreview({
format: 'glb',
data: modelBytes,
// data_base64: '...',
});
console.log(result.model_hash, result.status, result.reused);
Accepted formats: 'glb' | 'gltf' | '.glb' | '.gltf'. Accepted data: Uint8Array | ArrayBuffer | Buffer | Blob | base64 | data URI.
API: POST /api/v1/models/previews. Response: model_hash, task_id, status, reused.
const view = await sdk.getModelPreview(result.model_hash);
console.log(view.status, view.metadata, view.previews);
API: GET /api/v1/models/previews/{modelHash}. Response (ModelPreviewView): model_hash, task_id, status, error, metadata, previews[], created_at_unix, updated_at_unix, completed_at_unix.
const result = await sdk.submitModelObject('glb', modelBytes, {
create_as: 'object', // or 'prototype'
title: 'Wooden Chair',
description: 'A simple chair',
user_language: 'en',
estimated_dimensions: [0.5, 1.0, 0.5],
tags: ['furniture', 'chair'],
transformation: [1,0,0,0, 0,1,0,0, 0,0,1,0, 0,0,0,1],
major_preview_index: 0,
prototype_id: 'proto_123',
});
JS alias mapping (both forms work): createAs ↔ create_as, dataBase64 ↔ data_base64, userLanguage ↔ user_language, estimatedDimensions ↔ estimated_dimensions, majorPreviewIndex ↔ major_preview_index, prototypeId ↔ prototype_id, transform ↔ transformation.
API: POST /api/v1/models/objects. Response: model_hash, task_id, status, reused, create_as.
const view = await sdk.getModelObject(result.model_hash);
console.log(view.status, view.created_object_id, view.object, view.created_prototype_id, view.prototype, view.processed_cbor_uri);
API: GET /api/v1/models/objects/{modelHash}. Response (ModelObjectPipelineView): model_hash, status, error, metadata, previews[], processed_cbor_uri, processed_cbor_sha256, created_object_id, object, created_prototype_id, prototype, create_as, overrides, created_at_unix, updated_at_unix, completed_at_unix.
const page = await sdk.listModelObjectTasks({
status: ['queued', 'processing'],
createAs: 'prototype',
limit: 20,
cursor: '0',
});
// page.items, page.next_cursor
API: GET /api/v1/models/objects?status=...&create_as=...&limit=...&cursor=....
The projection workflow generates multi-angle views from a source image, then creates a 3D model.
const avatarBytes = await fs.promises.readFile('./avatar.png');
const job = await sdk.createModelProjectionJob({
prototype_id: 'proto_123',
prompt: 'cartoon like head avatar only',
source_image: avatarBytes,
process: true,
});
// job.job_id, job.sequences (only 'front' generated initially)
API: POST /api/v1/model-projection-jobs. Response (ModelProjectionLatestView): job_id, prototype_id, sequences{}, created_at_unix, updated_at_unix.
const sleep = (ms) => new Promise(r => setTimeout(r, ms));
const isTerminal = (s) => ['completed', 'failed', 'partial'].includes(String(s || ''));
let latest = await sdk.getModelProjectionLatest(job.job_id);
while (!isTerminal(latest.sequences.front?.latest_step?.status)) {
await sleep(3000);
latest = await sdk.getModelProjectionLatest(job.job_id);
}
API: GET /api/v1/model-projection-jobs/{jobId}/sequences/latest.
await sdk.generateMissingModelProjectionSequences(job.job_id);
API: POST /api/v1/model-projection-jobs/{jobId}/generate-missing (empty body). Derives: back, top, bottom, left, right, right_45, left_45 from the front image.
while (true) {
latest = await sdk.getModelProjectionLatest(job.job_id);
const seqs = latest.sequences || {};
const done = Object.values(seqs).every(seq => seq.deleted || isTerminal(seq.latest_step?.status));
if (done) break;
await sleep(3000);
}
const step = await sdk.createModelProjectionStep(job.job_id, 'left', {
source_image: lampBytes,
preprocess_only: true,
process: true,
});
API: POST /api/v1/model-projection-jobs/{jobId}/sequences/{projection}/steps. Response (ModelProjectionStepView): step_id, sequence, step_index, process, preprocess_only, status, error, source_image, preprocessed_image, generated_image, created_at_unix, updated_at_unix.
await sdk.deleteModelProjectionSequence(job.job_id, 'bottom');
await sdk.setModelProjectionLatest(job.job_id, 'front', stepId);
// or: sdk.setModelProjectionLatest(job.job_id, 'front', { step_id: stepId });
const page = await sdk.listModelProjectionSteps(job.job_id, 'right_45', { limit: 20, cursor: '0' });
Generate a 3D model with Tencent Hunyuan 3D 3.1 from selected projection steps:
const frontStepId = latest.sequences.front.latest_step.step_id;
const modelSubmit = await sdk.createModelProjectionModel(job.job_id, {
selected_steps: {
front: frontStepId, // REQUIRED
right_45: latest.sequences.right_45?.latest_step?.step_id, // optional
left: latest.sequences.left?.latest_step?.step_id, // optional
},
enable_pbr: true,
});
API: POST /api/v1/model-projection-jobs/{jobId}/models.
Validation rules:
front is required in selected_steps.front, back, top, bottom, left, right, right_45, left_45.completed or partial.generated_image → preprocessed_image (raw source_image is NOT used).process: false is not enough on its own — create or complete a processed step first.let modelView = await sdk.getModelProjectionModel(job.job_id, modelSubmit.model_id);
while (!isTerminal(modelView.status)) {
await sleep(5000);
modelView = await sdk.getModelProjectionModel(job.job_id, modelSubmit.model_id);
}
console.log(modelView.canonical_model);
console.log(modelView.artifacts);
console.log(modelView.previews);
const models = await sdk.listModelProjectionModels(job.job_id);
// models.items (newest first)
Once a projection model has a canonical_model, start the final processing step (reuses the same object pipeline as POST /api/v1/models/objects):
const processed = await sdk.processModelProjectionModel(job.job_id, modelSubmit.model_id, {
create_as: 'object',
title: 'Sofa from projection',
description: 'Final object created from the canonical projection model',
user_language: 'en',
tags: ['projection', 'sofa'],
prototype_id: 'proto-123',
});
console.log(processed.processing); // { model_hash, status, create_as, ... }
API: POST /api/v1/model-projection-jobs/{jobId}/models/{modelId}/process.
Status semantics: 202 when queued or still running; 200 when reusing an existing completed downstream result.
let processedView = await sdk.getModelProjectionModel(job.job_id, modelSubmit.model_id);
while (!isTerminal(processedView.processing?.status || '')) {
await sleep(5000);
processedView = await sdk.getModelProjectionModel(job.job_id, modelSubmit.model_id);
}
if (processedView.processing?.create_as === 'object') {
console.log(processedView.processing.object);
} else {
console.log(processedView.processing.prototype);
}
GET /api/v1/model-projection-jobs/{jobId}/models/{modelId} returns hydrated processing.object or processing.prototype when the final step has completed. The list endpoint returns the same processing summary without hydrating those entities.
The projection-model → object/prototype handoff is covered by a focused synthetic SDK E2E (path inside the state-server repo): sdk/js/tests/integration/model-projection-process-sdk.e2e.test.js. It creates a synthetic front step, completes the projection-model callback, invokes processModelProjectionModel(...), completes the internal model-object callback, and verifies the created object / prototype state without a real worker.
front, back, top, bottom, left, right, right_45, left_45.
queued → processing → completed | failed | partial.
Not for normal app usage — these endpoints back the signed-callbacks protocol:
sdk.modelPreviewCallback(payload, signature) — POST /api/v1/internal/model-previews/callback.sdk.modelObjectCallback(payload, signature) — POST /api/v1/internal/model-objects/callback.sdk.modelProjectionCallback(payload, signature) — POST /api/v1/internal/model-projection-jobs/callback.sdk.modelProjectionModelCallback(payload, signature) — POST /api/v1/internal/model-projection-jobs/models/callback.Objects, Signed callbacks. state-server/docs/JS/prototypes.md and state-server/docs/JS/types-reference.md in-repo (mirror pending).