Xây dựng · Virtual Try-On

Xây dựng tính năng virtual try-on cho ứng dụng của bạn

Tóm tắt trong một câu

Tiếp nhận ảnh sản phẩm ở backend của bạn, POST dữ liệu tới /api/v1/tryon/apparel kèm theo mannequin ID, thực hiện polling với generation ID nhận được từ một background worker (tuyệt đối không dùng luồng HTTP request) cho đến khi status chuyển sang completed, sau đó lưu output_url vào object storage của riêng bạn và kết xuất lên giao diện người dùng.

  • Credits: 5 credits ở 2K · 6 credits ở 4K
  • Thời gian điển hình: 1.5–4 min
  • Xử lý: async, polling

Mọi số liệu trên trang này đều lấy từ tài liệu Photta API · đã kiểm tra

Quy trình chuẩn production: khách hàng tải ảnh sản phẩm lên, backend của bạn đẩy tác vụ Photta try-on vào hàng đợi, một worker thực hiện polling cho đến khi hoàn tất, frontend hiển thị kết quả người mẫu mặc trang phục. Không chặn request, không lộ API key, không cần SDK giả lập.

RESPONSE=$(curl -s -X POST https://api.photta.app/api/v1/tryon/apparel \
  -H "Authorization: Bearer $PHOTTA_API_KEY" \
  -H "Content-Type: application/json" \
  -d '{
  "product_type": "top",
  "product_images": [
    "https://cdn.yourstore.com/products/shirt-front.jpg"
  ],
  "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
  "product_category": "t_shirt",
  "mannequin_id": "mnk_f3a9b1",
  "pose_id": "pose_c2d4e5",
  "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
  "lock_background": true,
  "resolution": "2K",
  "aspect_ratio": "3:4",
  "body_size": "M",
  "expression": "neutral",
  "user_instruction": "Keep the collar pattern crisp and centered."
}')
ID=$(echo "$RESPONSE" | jq -r '.data.id')

Cơ chế hoạt động

Từ bước tải lên đến hình ảnh người mẫu

Sáu bước cụ thể. Dự kiến mất mười đến mười lăm phút tích hợp trong lần đầu tiên.

  1. Bước 1

    Phác thảo luồng dữ liệu

    Thiết bị khách hàng → backend của bạn (tải lên) → Photta API (gửi yêu cầu) → Photta API (polling) → cơ sở dữ liệu của bạn (đồng bộ trạng thái) → thiết bị khách hàng (kết xuất). Hãy phác thảo sơ đồ này ra giấy trước khi viết dòng code đầu tiên — ranh giới giữa backend của bạn và Photta là yếu tố quan trọng nhất.

  2. Bước 2

    Tạo và lưu trữ API key

    Khóa live bắt đầu bằng photta_live_. Hãy lưu nó vào biến môi trường. Tuyệt đối không gửi khóa này xuống trình duyệt. Một khóa duy nhất dùng được cho mọi tính năng của Photta, vì vậy bạn không cần các khóa riêng biệt cho try-on, jewelry hay ghost mannequin.

  3. Bước 3

    Thiết lập endpoint tiếp nhận tải lên

    Tiếp nhận ảnh sản phẩm của khách hàng vào object storage trước. Việc cung cấp cho Photta một URL công khai sẽ đơn giản hơn so với dữ liệu base64, đồng thời giúp bạn lưu lại nhật ký theo dõi cho từng request.

  4. Bước 4

    Gửi tác vụ từ server

    POST tới /api/v1/tryon/apparel kèm theo URL sản phẩm, mannequin_id, resolution và aspect_ratio. API sẽ trả về 202 Accepted + một generation ID. Hãy lưu ID này vào cơ sở dữ liệu của bạn để client có thể polling đến endpoint của riêng bạn, thay vì gọi trực tiếp tới Photta.

  5. Bước 5

    Thực hiện polling từ một background worker

    Không thực hiện polling bên trong HTTP request — tác vụ này mất từ 1.5 đến 4 phút, vượt quá hầu hết các giới hạn timeout của môi trường serverless. Sử dụng Trigger.dev, Inngest, BullMQ, Vercel Cron hoặc hệ thống queue của riêng bạn để lên lịch polling lại mỗi 3 giây một lần cho đến khi tác vụ hoàn tất.

  6. Bước 6

    Kết xuất kết quả

    Khi status chuyển sang completed, hãy sao chép data.output_url vào cơ sở dữ liệu của bạn và phát một realtime event (websocket, SSE hoặc long-poll) tới thiết bị của khách hàng. Giao diện người dùng sẽ thay thế loading skeleton bằng hình ảnh hoàn chỉnh. Hoàn tất.

Ba ví dụ code, end-to-end

Bộ xử lý tải lên, bộ điều phối tác vụ, worker thực hiện polling — toàn bộ các phần cần thiết để bạn triển khai tính năng.

// POST /app/try-on → your backend upload handler
import { put } from "@vercel/blob";

export async function uploadProduct(file: File) {
  const { url } = await put(`products/${crypto.randomUUID()}.jpg`, file, {
    access: "public",
  });
  return url;
}
const res = await fetch("https://api.photta.app/api/v1/tryon/apparel", {
  method: "POST",
  headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}`, "Content-Type": "application/json" },
  body: JSON.stringify({
    "product_type": "top",
    "product_images": [
      "https://cdn.yourstore.com/products/shirt-front.jpg"
    ],
    "product_back_image": "https://cdn.yourstore.com/products/shirt-back.jpg",
    "product_category": "t_shirt",
    "mannequin_id": "mnk_f3a9b1",
    "pose_id": "pose_c2d4e5",
    "background": "https://cdn.yourstore.com/backgrounds/studio-loft.jpg",
    "lock_background": true,
    "resolution": "2K",
    "aspect_ratio": "3:4",
    "body_size": "M",
    "expression": "neutral",
    "user_instruction": "Keep the collar pattern crisp and centered."
  }),
});
const { data } = await res.json(); // data.id, data.status
// Trigger.dev / Inngest / BullMQ task
const BASE = "https://api.photta.app/api/v1";

export const pollTryOnTask = task({
  id: "poll-try-on",
  run: async ({ generationId, attempt = 0 }) => {
    const res = await fetch(`${BASE}/tryon/apparel/${generationId}`, {
      headers: { Authorization: `Bearer ${process.env.PHOTTA_API_KEY}` },
    });
    const { data } = await res.json();
    if (data.status === "completed") return data.output_url;
    if (data.status === "failed") throw new Error(data.error_message);
    if (attempt >= 120) throw new Error("try-on timed out");
    // Re-schedule this same task after 3 seconds.
    return ctx.retry.after(3000, { generationId, attempt: attempt + 1 });
  },
});

Câu hỏi thường gặp từ developer

Những câu hỏi trước khi triển khai

Tôi có thể làm điều này mà không cần backend không?

Không. Khóa API Photta của bạn bắt buộc phải nằm ở phía server. Ngay cả một route handler Next.js gọn nhẹ hay một Cloudflare Worker cũng được tính là một backend cho mục đích này — khóa chỉ cần nằm ở nơi mà user-agent không thể đọc được.

Làm thế nào để tránh việc các HTTP request bị treo?

Tuyệt đối không polling bên trong một request handler. Hãy gửi job đồng bộ, trả về generation ID cho client, và thực hiện polling từ một background worker. Client sẽ poll endpoint trạng thái của BẠN, chứ không phải của Photta — bằng cách đó, rate limit sẽ là bài toán bạn cần quản lý, chứ không đổ lên đầu từng người dùng.

Điều gì xảy ra nếu một job bị lỗi giữa chừng?

Photta tự động hoàn lại credits khi thất bại, và response sẽ kèm theo data.error_code + data.error_message. Hãy hiển thị một thông báo thân thiện cho người dùng và cung cấp nút thử lại để khởi chạy một job mới.

Tôi có thể hiển thị tiến trình thực tế không?

Photta không trả về tiến trình trung gian — trạng thái chỉ có thể là processing, completed hoặc failed. Giải pháp hiệu quả là dùng một thanh tiến trình ước lượng theo thời gian khớp với khoảng 1.5–4 phút, hoặc một skeleton loader đơn giản. Đừng hiển thị phần trăm giả mạo.

URL kết quả có tồn tại vĩnh viễn không?

URL CDN hoạt động ổn định, nhưng sản phẩm của bạn không nên phụ thuộc vào nó để render lâu dài. Hãy tải các byte dữ liệu về một lần và lưu trữ chúng trong bucket S3/GCS/R2 của riêng bạn — khi đó PDP của bạn vẫn hoạt động bình thường ngay cả khi CDN của Photta thay đổi.

Triển khai tính năng virtual try-on chỉ trong một buổi chiều

Mọi mẫu code trên trang này đều khớp với tài liệu chính thức của Photta. Lấy một khóa API và bạn sẽ có một bản prototype hoạt động được trước khi ly cà phê kịp nguội.