Happy Horse 1.0 is now on fal

decart/lucy2-vton/realtime

Realtime Try On experience with Decart Lucy 2.1 VTON
Inference
Commercial use
Partner

Input

Click to enable webcam

Result

Idle

Waiting for your input...

What would you like to do next?

Your request will cost $0.02 per second.

Logs

🎥 Using Lucy 2.1 Virtual Try On

Lucy 2.1 Virtual Try On is a real-time virtual try-on model — it transforms your live webcam feed using a text prompt and/or reference garment image over a WebRTC connection.

🚀 Connect & Start a Session

javascript
import { fal } from "@fal-ai/client";

let pc = null;

fal.config({
  credentials: "YOUR_FAL_KEY"
});

const connection = fal.realtime.connect("decart/lucy2-vton/realtime", {
  connectionKey: `session-${Date.now()}`,
  throttleInterval: 0,
  onResult: handleResult,
  onError: (err) => console.error(err),
});

// Send initial prompt (and optional reference image)
// `stream`      → your MediaStream (e.g. from getUserMedia)
// `outputVideo` → your <video> element for the transformed output
connection.send({
  prompt: "Substitute the current top with a bright red hoodie with an oversized casual fit",
  reference_image_url: "https://example.com/outfit.png", // optional
});

📡 Handle Server Messages (`onResult`)

javascript
async function handleResult(result) {
  switch (result.type) {
    case "iceservers":
    case "iceServers": {
      const servers = (result.iceservers || result.iceServers || result.ice_servers)
        .map((s) => ({ urls: s.urls, username: s.username, credential: s.credential }));

      pc = new RTCPeerConnection({ iceServers: servers });

      stream.getTracks().forEach((track) => pc.addTrack(track, stream));

      pc.ontrack = (e) => { outputVideo.srcObject = e.streams[0]; };

      pc.onicecandidate = (e) => {
        if (e.candidate) {
          connection.send({
            type: "icecandidate",
            candidate: {
              candidate: e.candidate.candidate,
              sdpMid: e.candidate.sdpMid,
              sdpMLineIndex: e.candidate.sdpMLineIndex,
            },
          });
        }
      };

      const offer = await pc.createOffer();
      await pc.setLocalDescription(offer);
      connection.send({ type: "offer", sdp: offer.sdp });
      break;
    }
    case "answer":
      await pc.setRemoteDescription({ type: "answer", sdp: result.sdp });
      break;
    case "icecandidate":
      await pc.addIceCandidate(new RTCIceCandidate(result.candidate));
      break;
    case "ice-restart":
      if (result.turn_config && pc) {
        pc.setConfiguration({
          iceServers: [
            { urls: "stun:stun.l.google.com:19302" },
            {
              urls: result.turn_config.server_url,
              username: result.turn_config.username,
              credential: result.turn_config.credential,
            },
          ],
        });
        const offer = await pc.createOffer({ iceRestart: true });
        await pc.setLocalDescription(offer);
        connection.send({ type: "offer", sdp: offer.sdp });
      }
      break;
    case "prompt_ack":
      if (!result.success) console.error("Prompt failed:", result.error);
      break;
    case "set_image_ack":
      if (!result.success) console.error("Image failed:", result.error);
      break;
    case "generation_started":
      console.log("Model is producing frames");
      break;
    case "error":
      console.error("Server error:", result.error);
      break;
  }
}

🔄 Update Prompt Mid-Session

javascript
connection.send({
  prompt: "Add a navy baseball cap with a blue logo to the person's head",
  enhance_prompt: true,
});

🖼️ Update Reference Image Mid-Session

javascript
connection.send({
  reference_image_url: "https://example.com/new-outfit.png",
  prompt: "Add a navy baseball cap with a blue logo to the person's head",
  enhance_prompt: true,
});

🔌 Disconnect

javascript
pc.close();
connection.close();

📚 Documentation

For more details, visit the official docs: