Installation and setup
Create a Next.js app with TypeScript and the App Router, then install the typed SDK:.env.local:
How XMAX X2.1 works
The browser publishes asource video track and receives main_video. Once source frames arrive,
set_prompt enables generation. An empty string is a valid prompt. A null prompt in
state_update means no prompt was set.
Use command replies to show whether an input was accepted. Use state_update and received video to
show what the model does. The typed SDK flattens command replies, so await model.setPrompt(...)
returns a payload with .prompt. Base SDK replies have the envelope { type, data }.
Authentication
Createapp/api/reactor/token/route.ts. The route exchanges the server key for a session-scoped
token and returns its expiry. The token can create sessions only for this model.
Publish and view video
Replaceapp/page.tsx with this client component. It creates both video elements, starts on a user
click, and releases the camera on disconnect.
app/page.tsx
npm run dev, open the local page, and click Connect. Allow camera access. If playback is
blocked, handle the displayed playback error with an explicit play control.
Add references and drag control
For a reference picker, upload the selectedFile, then await setReferenceImage. Keep the
previous selection until the reply confirms acceptance. The new reference applies from the next
block without a stop or restart message. Expect one short pause and a visible transition over the
next few blocks.
setPointers({ frames }). Each sample contains zero to eight { x, y } points
normalized to the output frame. Account for any letterboxing when converting screen coordinates.
Use pointer capture and touch-action: none on the interaction surface. On release, cancellation,
or window blur, send setPointers({ frames: [[], [], [], []] }). The
schema explains how stale windows hold their
newest positions.
Surfacing command_error
Listen for SDK errors for the whole connection lifetime. The example usesmodel.on("error", ...).
The supplied model schema does not declare a typed command_error message or hook.
A typed command can resolve to undefined when it fails. Read getLastError() and keep the prior
UI selection when a reply is absent. reset declares no reply payload, so undefined alone does
not indicate failure for that command. Use SDK errors and the next state_update to confirm its
effect.
Await replies for the command you sent. A general message listener is not a substitute for the
correlated reply. Keep listeners active for later errors, and remove temporary listeners when their
operation ends.