@reactor-models/lingbot
v0.2.40
Published
Strongly-typed SDK for the Lingbot model on Reactor
Maintainers
Readme
@reactor-models/lingbot
Typed JavaScript + React SDK for the Lingbot model on Reactor. Version v0.2.40.
Get started
Scaffold a starter app for Lingbot with create-reactor-app:
npx create-reactor-app my-app --model=lingbotpnpm dlx create-reactor-app my-app --model=lingbotInstall
npm install @reactor-models/lingbotpnpm add @reactor-models/lingbotThe package exports a plain-JavaScript client and a set of React bindings. Import whichever you need from @reactor-models/lingbot:
import { LingbotModel } from "@reactor-models/lingbot";import { LingbotProvider, useLingbot } from "@reactor-models/lingbot";React 18 or later is required when using the provider and hooks. The token-loading examples below use React 19's use(); on React 18, fetch the JWT in a useEffect and pass it to the provider once it resolves.
Authenticate
Reactor uses short-lived JWTs for session auth. You hold your API key on your server, mint a token on demand, and the client never sees the raw key. Tokens are valid for 6 hours — if one leaks, it expires on its own.
Mint a JWT with POST https://api.reactor.inc/tokens and the Reactor-API-Key header; the response JSON is { "jwt": "..." }.
JavaScript (Next.js route handler)
// app/api/reactor/token/route.ts
import { NextResponse } from "next/server";
export async function POST() {
const res = await fetch("https://api.reactor.inc/tokens", {
method: "POST",
headers: { "Reactor-API-Key": process.env.REACTOR_API_KEY! },
});
const { jwt } = await res.json();
return NextResponse.json({ jwt });
}React (provider)
Call the /api/reactor/token route above from a client component and pass the result to the provider:
"use client";
import { use } from "react";
import { LingbotProvider } from "@reactor-models/lingbot";
import { ReactorView } from "@reactor-team/js-sdk";
async function getToken() {
const r = await fetch("/api/reactor/token", { method: "POST" });
const { jwt } = await r.json();
return jwt;
}
const tokenPromise = getToken();
export default function App() {
const token = use(tokenPromise);
return (
<LingbotProvider jwtToken={token} connectOptions={{ autoConnect: true }}>
<ReactorView className="w-full aspect-video" />
</LingbotProvider>
);
}Connect
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);React
The provider takes the JWT as a prop; fetch it from the same /api/reactor/token route the Authenticate example mints:
"use client";
import { use } from "react";
import { LingbotProvider, useLingbot } from "@reactor-models/lingbot";
async function getToken() {
const r = await fetch("/api/reactor/token", { method: "POST" });
const { jwt } = await r.json();
return jwt;
}
const tokenPromise = getToken();
function Controller() {
const { status } = useLingbot();
return <span>Status: {status}</span>;
}
export default function App() {
const token = use(tokenPromise);
return (
<LingbotProvider jwtToken={token}>
<Controller />
</LingbotProvider>
);
}Events
Client-to-model commands. The typed surface is LingbotModel (one method per event) in plain JS, and useLingbot() in React — every field name below matches the parameter name the method accepts.
pause
Pause generation after the current chunk finishes. Frames stop streaming on main_video until resume is called. Requires generation to be active. Emits generation_paused and state on success, or command_error if not generating or already paused.
Emits: generation_paused, state, command_error
No parameters.
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.pause();React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { pause } = useLingbot();
return <button onClick={() => pause()}>pause</button>;
}reset
Abort the current run, clear the active prompt and reference image, and return to the waiting state. Valid at any time. After reset, call set_prompt and set_image again before start to begin a new session. Emits generation_reset and state.
Emits: generation_reset, state
No parameters.
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.reset();React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { reset } = useLingbot();
return <button onClick={() => reset()}>reset</button>;
}start
Begin generating video on main_video. Requires both a prompt (via set_prompt) and a reference image (via set_image). Emits generation_started and state on success, or command_error if a precondition is missing. Has no effect while already generating.
Emits: generation_started, state, command_error
No parameters.
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.start();React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { start } = useLingbot();
return <button onClick={() => start()}>start</button>;
}resume
Resume generation from a previous pause. Requires the session to be paused. Emits generation_resumed and state on success, or command_error if not paused.
Emits: generation_resumed, state, command_error
No parameters.
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.resume();React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { resume } = useLingbot();
return <button onClick={() => resume()}>resume</button>;
}setSeed
Set seed
| Parameter | Type | Required | Description |
|---|---|---|---|
| seed | number | | Seed for the random generator used to sample the initial noise. Must be a non-negative integer; the model never draws its own random seed — pick one explicitly (or keep the default) for reproducible runs. Read once when start fires; later changes take effect only after reset followed by a new start. (min 0, default 42) |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setSeed({ seed: 42 });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setSeed } = useLingbot();
return <button onClick={() => setSeed({ seed: 42 })}>setSeed</button>;
}setImage
Provide a reference image that anchors generation (image-to-video). Call before start; the image is required for generation to begin. Changes during generation have no effect until reset is issued and start is called again. Emits image_accepted, conditions_ready, and state on success, or command_error if the file is missing, not an image, or cannot be decoded.
Emits: image_accepted, conditions_ready, state, command_error
| Parameter | Type | Required | Description |
|---|---|---|---|
| image | FileRef | | Reference to a file uploaded via the Reactor presigned-URL protocol. (default null) |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
const fileRef = await lingbot.uploadFile(blob);
await lingbot.setImage({ image: fileRef });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setImage, uploadFile } = useLingbot();
async function handlePick(file: File) {
const ref = await uploadFile(file);
await setImage({ image: ref });
}
return <input type="file" onChange={(e) => handlePick(e.target.files![0])} />;
}setPrompt
Set the scene prompt. Valid at any time — call before start to arm generation, or hot-swap during generation to steer the next chunk. Emits prompt_accepted, conditions_ready, and state on success.
Emits: prompt_accepted, conditions_ready, state
| Parameter | Type | Required | Description |
|---|---|---|---|
| prompt | string | | Natural-language description of the scene to generate. Replaces the previously active prompt. Applied on the next chunk when generating; otherwise takes effect when start fires. (default "") |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setPrompt({ prompt: "A sunset over the ocean" });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setPrompt } = useLingbot();
return <button onClick={() => setPrompt({ prompt: "A sunset over the ocean" })}>setPrompt</button>;
}setMovement
Set movement
| Parameter | Type | Required | Description |
|---|---|---|---|
| movement | "idle" \| "forward" \| "back" \| "strafe_left" \| "strafe_right" | | Character movement in the generated scene. idle holds the character stationary; forward / back translate along the look axis; strafe_left / strafe_right translate sideways. Can be changed at any time; the new value applies to the next chunk. (default "idle") |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setMovement({ movement: "idle" });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setMovement } = useLingbot();
return <button onClick={() => setMovement({ movement: "idle" })}>setMovement</button>;
}setLookVertical
Set look_vertical
| Parameter | Type | Required | Description |
|---|---|---|---|
| look_vertical | "idle" \| "up" \| "down" | | Vertical (pitch) camera rotation. idle holds pitch steady; up / down rotate the camera at the rate given by rotation_speed_deg. Can be changed at any time; the new value applies to the next chunk. (default "idle") |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setLookVertical({ look_vertical: "idle" });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setLookVertical } = useLingbot();
return <button onClick={() => setLookVertical({ look_vertical: "idle" })}>setLookVertical</button>;
}setLookHorizontal
Set look_horizontal
| Parameter | Type | Required | Description |
|---|---|---|---|
| look_horizontal | "idle" \| "left" \| "right" | | Horizontal (yaw) camera rotation. idle holds yaw steady; left / right rotate the camera at the rate given by rotation_speed_deg. Can be changed at any time; the new value applies to the next chunk. (default "idle") |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setLookHorizontal({ look_horizontal: "idle" });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setLookHorizontal } = useLingbot();
return <button onClick={() => setLookHorizontal({ look_horizontal: "idle" })}>setLookHorizontal</button>;
}setRotationSpeedDeg
Set rotation_speed_deg
| Parameter | Type | Required | Description |
|---|---|---|---|
| rotation_speed_deg | number | | Camera rotation speed in degrees per latent frame, applied when look_horizontal or look_vertical is not idle. Range 0.0 – 30.0. Ignored when both look axes are idle. Can be changed at any time; the new value applies to the next chunk. (min 0, max 30, default 5) |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
await lingbot.connect(jwt);
await lingbot.setRotationSpeedDeg({ rotation_speed_deg: 5 });React
"use client";
import { useLingbot } from "@reactor-models/lingbot";
function Example() {
const { setRotationSpeedDeg } = useLingbot();
return <button onClick={() => setRotationSpeedDeg({ rotation_speed_deg: 5 })}>setRotationSpeedDeg</button>;
}Messages
Model-to-client messages. Register a typed listener with on… on LingbotModel, or a useLingbot… hook in React, to receive only the messages you care about.
state
Snapshot of the session's observable state.
Emitted on connect, after every command that mutates session state (set_prompt, set_image, start, pause, resume, reset, and the auto-generated set_<field> setters), and after each chunk_complete. Clients can treat this as the single source of truth for driving UI, without having to track every individual command and message themselves.
Listener: onState · React hook: useLingbotState
| Field | Type | Description |
|---|---|---|
| seed | number | Current value of the seed input field. The seed that was actually used by the running generation was captured when start fired — later changes to seed only take effect after reset and a new start. |
| paused | boolean | True while generation is paused via pause. |
| running | boolean | True while the chunk loop is actively producing frames — equivalent to started and not paused. False both before start and while paused; read started to disambiguate. |
| started | boolean | True once start has been accepted. Remains true while paused; reset to false by reset or after generation_complete when the session is not auto-restarting. |
| movement | string | Current value of the movement input field. |
| has_image | boolean | True once a reference image has been set for the session. |
| has_prompt | boolean | True once a prompt has been set for the session. |
| current_chunk | number | Zero-based index of the last completed chunk. 0 before the first chunk has completed, and resets to 0 on reset. |
| look_vertical | string | Current value of the look_vertical input field. |
| current_action | string | Composite action string derived from movement, look_horizontal, and look_vertical — a +-joined combination of w/s/a/d and left/right/up/down, or still when idle. |
| current_prompt | unknown | The prompt currently driving generation, or null if no prompt has been set for the session. |
| look_horizontal | string | Current value of the look_horizontal input field. |
| rotation_speed_deg | number | Current value of the rotation_speed_deg input field (0.0 – 30.0). |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onState((msg) => {
console.log(
"state",
msg.seed,
msg.paused,
msg.running,
msg.started,
msg.movement,
msg.has_image,
msg.has_prompt,
msg.current_chunk,
msg.look_vertical,
msg.current_action,
msg.current_prompt,
msg.look_horizontal,
msg.rotation_speed_deg,
);
});
await lingbot.connect(jwt);React
import { useLingbotState } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotState((msg) => {
console.log(
"state",
msg.seed,
msg.paused,
msg.running,
msg.started,
msg.movement,
msg.has_image,
msg.has_prompt,
msg.current_chunk,
msg.look_vertical,
msg.current_action,
msg.current_prompt,
msg.look_horizontal,
msg.rotation_speed_deg,
);
});command_error
Emitted when a command is rejected because preconditions are not met or its arguments could not be processed.
Listener: onCommandError · React hook: useLingbotCommandError
| Field | Type | Description |
|---|---|---|
| reason | string | Human-readable explanation of why the command was rejected. |
| command | string | Name of the command that was rejected. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onCommandError((msg) => {
console.log("command_error", msg.reason, msg.command);
});
await lingbot.connect(jwt);React
import { useLingbotCommandError } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotCommandError((msg) => {
console.log("command_error", msg.reason, msg.command);
});chunk_complete
Emitted once per completed chunk of main_video.
Listener: onChunkComplete · React hook: useLingbotChunkComplete
| Field | Type | Description |
|---|---|---|
| chunk_index | number | Zero-based index of the chunk that just completed. |
| active_action | string | The composite action string used to drive this chunk — a +-joined combination of movement (w/s/a/d) and look directions (left/right/up/down), or still when the character is idle with no camera rotation. |
| active_prompt | string | The prompt that was active while this chunk was generated. |
| frames_emitted | number | Number of pixel frames emitted by this chunk. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onChunkComplete((msg) => {
console.log(
"chunk_complete",
msg.chunk_index,
msg.active_action,
msg.active_prompt,
msg.frames_emitted,
);
});
await lingbot.connect(jwt);React
import { useLingbotChunkComplete } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotChunkComplete((msg) => {
console.log(
"chunk_complete",
msg.chunk_index,
msg.active_action,
msg.active_prompt,
msg.frames_emitted,
);
});image_accepted
Emitted after set_image successfully decodes the uploaded file.
Listener: onImageAccepted · React hook: useLingbotImageAccepted
| Field | Type | Description |
|---|---|---|
| width | number | Width in pixels of the decoded reference image. |
| height | number | Height in pixels of the decoded reference image. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onImageAccepted((msg) => {
console.log("image_accepted", msg.width, msg.height);
});
await lingbot.connect(jwt);React
import { useLingbotImageAccepted } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotImageAccepted((msg) => {
console.log("image_accepted", msg.width, msg.height);
});prompt_accepted
Emitted after set_prompt is accepted.
Listener: onPromptAccepted · React hook: useLingbotPromptAccepted
| Field | Type | Description |
|---|---|---|
| prompt | string | The prompt text that was accepted. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onPromptAccepted((msg) => {
console.log("prompt_accepted", msg.prompt);
});
await lingbot.connect(jwt);React
import { useLingbotPromptAccepted } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotPromptAccepted((msg) => {
console.log("prompt_accepted", msg.prompt);
});conditions_ready
Emitted after set_prompt or set_image so the client can tell at a glance whether start will succeed.
Listener: onConditionsReady · React hook: useLingbotConditionsReady
| Field | Type | Description |
|---|---|---|
| has_image | boolean | True once a reference image has been set for the session. |
| has_prompt | boolean | True once a prompt has been set for the session. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onConditionsReady((msg) => {
console.log("conditions_ready", msg.has_image, msg.has_prompt);
});
await lingbot.connect(jwt);React
import { useLingbotConditionsReady } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotConditionsReady((msg) => {
console.log("conditions_ready", msg.has_image, msg.has_prompt);
});generation_reset
Emitted after reset clears session state and returns to the waiting state.
Listener: onGenerationReset · React hook: useLingbotGenerationReset
| Field | Type | Description |
|---|---|---|
| reason | string | Short human-readable reason the reset was issued. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onGenerationReset((msg) => {
console.log("generation_reset", msg.reason);
});
await lingbot.connect(jwt);React
import { useLingbotGenerationReset } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotGenerationReset((msg) => {
console.log("generation_reset", msg.reason);
});generation_paused
Emitted in response to pause, once the current chunk finishes.
Listener: onGenerationPaused · React hook: useLingbotGenerationPaused
| Field | Type | Description |
|---|---|---|
| chunk_index | number | Index of the last completed chunk before pausing. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onGenerationPaused((msg) => {
console.log("generation_paused", msg.chunk_index);
});
await lingbot.connect(jwt);React
import { useLingbotGenerationPaused } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotGenerationPaused((msg) => {
console.log("generation_paused", msg.chunk_index);
});generation_resumed
Emitted in response to resume when leaving the paused state.
Listener: onGenerationResumed · React hook: useLingbotGenerationResumed
| Field | Type | Description |
|---|---|---|
| chunk_index | number | Index of the last completed chunk before resuming. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onGenerationResumed((msg) => {
console.log("generation_resumed", msg.chunk_index);
});
await lingbot.connect(jwt);React
import { useLingbotGenerationResumed } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotGenerationResumed((msg) => {
console.log("generation_resumed", msg.chunk_index);
});generation_started
Emitted once when start succeeds and frames begin streaming.
Listener: onGenerationStarted · React hook: useLingbotGenerationStarted
| Field | Type | Description |
|---|---|---|
| prompt | string | The prompt active at the start of generation. |
| chunk_num | number | Total number of chunks the run will produce before generation_complete fires. |
| frame_num | number | Total number of pixel frames the run will emit on main_video before generation_complete. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onGenerationStarted((msg) => {
console.log(
"generation_started",
msg.prompt,
msg.chunk_num,
msg.frame_num,
);
});
await lingbot.connect(jwt);React
import { useLingbotGenerationStarted } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotGenerationStarted((msg) => {
console.log(
"generation_started",
msg.prompt,
msg.chunk_num,
msg.frame_num,
);
});generation_complete
Emitted when all chunk_num chunks of a run have streamed. If the session is still started, a new run kicks off immediately with the same prompt and image; call reset to stop.
Listener: onGenerationComplete · React hook: useLingbotGenerationComplete
| Field | Type | Description |
|---|---|---|
| total_chunks | number | Total number of chunks produced by the run. |
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onGenerationComplete((msg) => {
console.log("generation_complete", msg.total_chunks);
});
await lingbot.connect(jwt);React
import { useLingbotGenerationComplete } from "@reactor-models/lingbot";
// Inside a React component wrapped by <LingbotProvider>:
useLingbotGenerationComplete((msg) => {
console.log("generation_complete", msg.total_chunks);
});Tracks
Named media channels between your app and the Lingbot model. Use the typed helpers below — LingbotModel.publish<Track> / on<Track> in plain JS, and useLingbotTrack or the per-track <Lingbot<Track>View> components in React — so track names are checked at compile time.
main_video
A video channel you subscribe to — the model publishes this for your app to render.
JavaScript
import { LingbotModel } from "@reactor-models/lingbot";
const lingbot = new LingbotModel();
lingbot.onMainVideo((track, stream) => {
// attach to a <video> element, pipe to a canvas, etc.
videoEl.srcObject = stream;
});
await lingbot.connect(jwt);React
"use client";
import { LingbotMainVideoView } from "@reactor-models/lingbot";
// Inside a component wrapped by <LingbotProvider>:
export function Example() {
return <LingbotMainVideoView className="w-full aspect-video" />;
}