# @cymmetrik-dev/3d-transfer-sdk

TypeScript SDK for Mobius 3D Transfer API - Convert images to 3D models using AI-powered style transfer.

## Features

- Full TypeScript support with auto-generated types
- Support for multiple frameworks (Vite, Next.js, Expo, Node.js)
- Environment variable based configuration
- Axios-based HTTP client
- **React ModelViewer component** for displaying 3D GLB models
- **Default style filtering** for single-style deployments
- **Additional instruction** for custom style guidance (max 2000 chars)
- **Add-to-cart redirect** for seamless shopping cart integration (supports guest users)
- **Split 2D/3D workflow** - Control when 3D generation starts with `auto_3d` parameter

## Installation

```bash
npm install @cymmetrik-dev/3d-transfer-sdk
# or
yarn add @cymmetrik-dev/3d-transfer-sdk
# or
pnpm add @cymmetrik-dev/3d-transfer-sdk
```

## Environment Variables

Configure the SDK using environment variables based on your framework:

| Variable | Framework | Description |
|----------|-----------|-------------|
| `VITE_TRANSFER_3D_API_URL` | Vite | API base URL |
| `VITE_TRANSFER_3D_API_TOKEN` | Vite | API authentication token |
| `VITE_TRANSFER_3D_DEFAULT_STYLE` | Vite | Default style (optional) |
| `VITE_DEFAULT_PRODUCT_SLUG` | Vite | Default product slug (optional) |
| `NEXT_PUBLIC_TRANSFER_3D_API_URL` | Next.js | API base URL |
| `NEXT_PUBLIC_TRANSFER_3D_API_TOKEN` | Next.js | API authentication token |
| `NEXT_PUBLIC_TRANSFER_3D_DEFAULT_STYLE` | Next.js | Default style (optional) |
| `NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG` | Next.js | Default product slug (optional) |
| `EXPO_PUBLIC_TRANSFER_3D_API_URL` | Expo | API base URL |
| `EXPO_PUBLIC_TRANSFER_3D_API_TOKEN` | Expo | API authentication token |
| `EXPO_PUBLIC_TRANSFER_3D_DEFAULT_STYLE` | Expo | Default style (optional) |
| `EXPO_PUBLIC_DEFAULT_PRODUCT_SLUG` | Expo | Default product slug (optional) |
| `TRANSFER_3D_API_URL` | Node.js | API base URL |
| `TRANSFER_3D_API_TOKEN` | Node.js | API authentication token |
| `TRANSFER_3D_DEFAULT_STYLE` | Node.js | Default style (optional) |
| `DEFAULT_PRODUCT_SLUG` | Node.js | Default product slug (optional) |

### Example .env file

```bash
# For Vite projects
VITE_TRANSFER_3D_API_URL=https://api.example.com/api
VITE_TRANSFER_3D_API_TOKEN=your-api-token-here
VITE_TRANSFER_3D_DEFAULT_STYLE=        # Optional: filter to single style
VITE_DEFAULT_PRODUCT_SLUG=             # Optional: default product for accessory info

# For Next.js projects
NEXT_PUBLIC_TRANSFER_3D_API_URL=https://api.example.com/api
NEXT_PUBLIC_TRANSFER_3D_API_TOKEN=your-api-token-here
NEXT_PUBLIC_TRANSFER_3D_DEFAULT_STYLE= # Optional: filter to single style
NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG=      # Optional: default product for accessory info

# For Node.js/backend projects
TRANSFER_3D_API_URL=https://api.example.com/api
TRANSFER_3D_API_TOKEN=your-api-token-here
TRANSFER_3D_DEFAULT_STYLE=             # Optional: filter to single style
DEFAULT_PRODUCT_SLUG=                  # Optional: default product for accessory info
```

## Quick Start

### Initialize SDK (Recommended)

Use `initTransfer3D()` to configure the SDK once at app startup. This sets the base URL and token for all subsequent API calls.

```typescript
import { initTransfer3D } from '@cymmetrik-dev/3d-transfer-sdk';

// Option 1: Auto-detect from environment variables
initTransfer3D();

// Option 2: Explicit configuration
initTransfer3D({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});
```

In a **Vite/React** project, create a setup file:

```typescript
// src/lib/transfer3d.ts
import { initTransfer3D } from '@cymmetrik-dev/3d-transfer-sdk';

initTransfer3D({
  baseURL: import.meta.env.VITE_TRANSFER_3D_API_URL,
  token: import.meta.env.VITE_TRANSFER_3D_API_TOKEN,
});
```

Then import it early in your app:

```typescript
// src/main.tsx
import './lib/transfer3d';  // Initialize SDK
import { createRoot } from 'react-dom/client';
import App from './App';

createRoot(document.getElementById('root')!).render(<App />);
```

### Alternative: Create Custom Client

If you need multiple clients (e.g., different API endpoints), use `createTransfer3DClient()`:

```typescript
import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Create a custom client instance
const client = createTransfer3DClient({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});

// Pass client to each API call
const styles = await getTransfer3dStyles({ client });
```

### Complete Workflow Example

```typescript
import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

async function convertImageTo3D(imageFile: File) {
  // 1. Initialize client
  const client = createTransfer3DClient({
    token: 'your-api-token',
  });

  // 2. Get available styles
  const stylesResponse = await getTransfer3dStyles({ client });
  console.log('Available styles:', stylesResponse.data);
  // Output: [{ mode_key: 'smart', mode_name: 'Q版公仔', ... }]

  // 3. Upload image and start conversion
  const conversionResponse = await initiateTransfer3dConversion({
    client,
    body: {
      image: imageFile,
      style: 'smart', // Use a style from step 2
      additional_instruction: '增加金色高光效果', // Optional: custom guidance
    },
  });

  const requestId = conversionResponse.data.data.request_id;
  console.log('Conversion started:', requestId);

  // 4. Poll for status until completed
  let status = 'PENDING';
  let result = null;

  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((resolve) => setTimeout(resolve, 2000)); // Wait 2 seconds

    const statusResponse = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    const data = statusResponse.data.data;
    status = data.status;
    console.log('Status:', status, 'Phase:', data.phase, 'Progress:', data.progress);

    // 2D preview is available when phase changes to 3d_generation
    if (data.result?.preview_2d_url) {
      console.log('2D Preview ready:', data.result.preview_2d_url);
    }

    if (status === 'COMPLETED') {
      result = data.result;
    }
  }

  if (result) {
    console.log('3D Model URL:', result.model_3d_url);
    console.log('GLB File URL:', result.glb_url);
    console.log('2D Preview URL:', result.preview_2d_url);
  }

  return result;
}
```

### Split 2D/3D Workflow (Manual 3D Trigger)

When you want to let users preview the 2D styled image before proceeding to 3D generation:

```typescript
import {
  createTransfer3DClient,
  initiateTransfer3dConversion,
  triggerTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';

async function convertWithManual3DTrigger(imageFile: File) {
  const client = createTransfer3DClient();

  // 1. Start 2D-only conversion
  const { data } = await initiateTransfer3dConversion({
    client,
    body: {
      image: imageFile,
      style: 'smart',
      auto_3d: false,  // Don't auto-proceed to 3D
    },
  });

  const requestId = data.data.request_id;

  // 2. Poll until 2D completes
  let status = 'PENDING';
  let preview2dUrl = null;

  while (status !== '2D_COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));

    const { data: statusData } = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    status = statusData.data.status;

    if (statusData.data.result?.preview_2d_url) {
      preview2dUrl = statusData.data.result.preview_2d_url;
    }
  }

  console.log('2D Preview ready:', preview2dUrl);
  // At this point, show preview_2d_url to user and wait for confirmation

  // 3. When user confirms, trigger 3D generation
  await triggerTransfer3dConversion({
    client,
    body: { request_id: requestId },
  });

  // 4. Poll until 3D completes
  status = 'IN_PROGRESS';
  let glbUrl = null;

  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));

    const { data: statusData } = await getTransfer3dStatus({
      client,
      path: { requestId },
    });

    status = statusData.data.status;

    if (status === 'COMPLETED') {
      glbUrl = statusData.data.result?.glb_url;
    }
  }

  return { preview2dUrl, glbUrl };
}
```

## React Components

### ModelViewer

The SDK includes a React component for displaying 3D GLB/GLTF models using Google's [model-viewer](https://modelviewer.dev/) web component.

```tsx
import { ModelViewer } from '@cymmetrik-dev/3d-transfer-sdk/react';

function My3DViewer() {
  return (
    <ModelViewer
      src="https://example.com/model.glb"
      alt="3D Model"
      cameraControls
      autoRotate
      style={{ width: '100%', height: '400px' }}
    />
  );
}
```

#### ModelViewer Props

| Prop | Type | Default | Description |
|------|------|---------|-------------|
| `src` | `string` | *required* | URL to the GLB/GLTF model file |
| `alt` | `string` | `'3D Model'` | Alt text for accessibility |
| `cameraControls` | `boolean` | `true` | Enable camera controls (pan, zoom, rotate) |
| `autoRotate` | `boolean` | `true` | Enable auto rotation |
| `shadowIntensity` | `number` | `1` | Shadow intensity (0-1) |
| `transparentBackground` | `boolean` | `false` | Enable transparent background (removes default gradient) |
| `poster` | `string` | - | Poster image URL to show while loading |
| `className` | `string` | - | CSS class name |
| `style` | `CSSProperties` | - | Inline styles |
| `loading` | `'auto' \| 'lazy' \| 'eager'` | `'auto'` | Loading strategy |
| `reveal` | `'auto' \| 'manual'` | `'auto'` | Reveal strategy |
| `onLoad` | `() => void` | - | Callback when model is loaded |
| `onError` | `(error: Error) => void` | - | Callback when error occurs |

#### Transparent Background Example

To display the 3D model with a transparent background (useful for overlays or custom backgrounds):

```tsx
<ModelViewer
  src="https://example.com/model.glb"
  transparentBackground
  cameraControls
  autoRotate
  style={{ width: '200px', height: '200px' }}
/>
```

#### Complete React Example with ModelViewer

```tsx
import { useState, useEffect } from 'react';
import {
  createTransfer3DClient,
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
} from '@cymmetrik-dev/3d-transfer-sdk';
import { ModelViewer } from '@cymmetrik-dev/3d-transfer-sdk/react';

export function ImageTo3DConverter() {
  const [status, setStatus] = useState<string>('idle');
  const [progress, setProgress] = useState(0);
  const [preview2dUrl, setPreview2dUrl] = useState<string | null>(null);
  const [glbUrl, setGlbUrl] = useState<string | null>(null);
  const [webViewUrl, setWebViewUrl] = useState<string | null>(null);

  const handleConvert = async (file: File, style: string) => {
    const client = createTransfer3DClient();

    setStatus('uploading');
    const { data } = await initiateTransfer3dConversion({
      client,
      body: { image: file, style },
    });

    const requestId = data.data.request_id;
    setStatus('processing');

    // Poll for completion
    const pollStatus = async () => {
      const { data: statusData } = await getTransfer3dStatus({
        client,
        path: { requestId },
      });

      const result = statusData.data;
      setProgress(result.progress || 0);

      // Web view URL for viewing on main site
      if (result.web_view_url) {
        setWebViewUrl(result.web_view_url);
      }

      // 2D preview becomes available during 3D generation phase
      if (result.result?.preview_2d_url) {
        setPreview2dUrl(result.result.preview_2d_url);
      }

      if (result.status === 'COMPLETED') {
        setGlbUrl(result.result.glb_url);
        setStatus('completed');
      } else if (result.status === 'FAILED') {
        setStatus('failed');
      } else {
        setTimeout(pollStatus, 2000);
      }
    };

    pollStatus();
  };

  return (
    <div>
      <p>Status: {status} ({progress}%)</p>

      {/* Link to view progress on main site */}
      {webViewUrl && (
        <a href={webViewUrl} target="_blank" rel="noopener noreferrer">
          View on main site
        </a>
      )}

      {/* 2D styled preview */}
      {preview2dUrl && (
        <div>
          <h3>2D Style Preview</h3>
          <img src={preview2dUrl} alt="2D Preview" />
        </div>
      )}

      {/* 3D Model Viewer */}
      {glbUrl && (
        <div>
          <h3>3D Model</h3>
          <ModelViewer
            src={glbUrl}
            alt="Generated 3D Model"
            cameraControls
            autoRotate
            style={{ width: '100%', height: '400px' }}
          />
        </div>
      )}
    </div>
  );
}
```

## API Reference

### `initTransfer3D(config?)`

Initialize the default SDK client. Call this once at app startup to configure the base URL and authentication token for all API calls.

**Parameters:**
- `config.baseURL` (optional): API base URL. Falls back to environment variables.
- `config.token` (optional): Bearer authentication token. Falls back to environment variables.

**Example:**
```typescript
import { initTransfer3D, getTransfer3dStyles } from '@cymmetrik-dev/3d-transfer-sdk';

// Initialize with explicit config
initTransfer3D({
  baseURL: 'https://api.example.com/api',
  token: 'your-api-token',
});

// Or auto-detect from environment variables
initTransfer3D();

// Now all API calls use this configuration (no need to pass client)
const styles = await getTransfer3dStyles();
```

---

### `createTransfer3DClient(config?)`

Create a separate API client instance. Use this when you need multiple clients or want to override the default configuration for specific calls.

**Parameters:**
- `config.baseURL` (optional): API base URL
- `config.token` (optional): Bearer authentication token
- `config.timeout` (optional): Request timeout in ms (default: 30000)

**Returns:** Configured axios client

---

### `getTransfer3dStyles(options)`

Get available 3D transfer styles.

**Parameters:**
- `query.default_style` (optional): string - Filter to return only this style

**Example:**
```typescript
// Get all styles
const allStyles = await getTransfer3dStyles({ client });

// Get single style (useful for single-style deployments)
const singleStyle = await getTransfer3dStyles({
  client,
  query: { default_style: 'smart' },
});
```

**Returns:**
```typescript
{
  success: true,
  data: Array<{
    mode_key: string;      // Style identifier (e.g., 'smart')
    mode_name: string;     // Display name (e.g., 'Q版公仔')
    mode_description: string;
    preview_image_url: string | null;
  }>
}
```

> **Note:** If `default_style` is specified but not found, API returns all styles as fallback.

---

### `initiateTransfer3dConversion(options)`

Upload an image and start the 3D conversion process.

**Parameters:**
- `body.image`: File - Image to convert (max 10MB, supported: jpg, png, webp)
- `body.style`: string - Style key from `getTransfer3dStyles()`
- `body.additional_instruction` (optional): string - Custom style guidance (max 2000 chars)
- `body.auto_3d` (optional): boolean - Auto-proceed to 3D after 2D completes (default: `true`)

**Example:**
```typescript
// Basic conversion (auto 2D → 3D)
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
  },
});

// With additional instruction for fine-tuning
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
    additional_instruction: '增加金色高光效果，使用暖色調',
  },
});

// 2D-only mode: Stop after 2D style transfer
const result = await initiateTransfer3dConversion({
  client,
  body: {
    image: imageFile,
    style: 'smart',
    auto_3d: false,  // Will stop at 2D_COMPLETED status
  },
});
```

**Additional Instruction Use Cases:**
- Fine-tune style output (e.g., "更卡通化", "增加質感")
- Specify colors or materials (e.g., "使用暖色調", "金屬質感")
- Adjust lighting effects (e.g., "增加高光", "柔和陰影")

> **Note:** `additional_instruction` is **appended** to the style's base instructions, not replaced.

**Returns:**
```typescript
{
  success: true,
  data: {
    request_id: string;       // UUID for status polling
    chat_thread_id: number;   // Internal ChatThread ID (for add-to-cart)
    status: 'PENDING';
    message: string;
  }
}
```

---

### `triggerTransfer3dConversion(options)`

Trigger 3D generation for a request that completed 2D styling. Use this when you initiated a conversion with `auto_3d: false`.

**Parameters:**
- `body.request_id`: string - The request ID from the original `/convert` call

**Example:**
```typescript
// After status shows '2D_COMPLETED', trigger 3D generation
const result = await triggerTransfer3dConversion({
  client,
  body: {
    request_id: 'uuid-xxx',
  },
});
```

**Returns:**
```typescript
{
  success: true,
  data: {
    request_id: string;
    chat_thread_id: number;   // Internal ChatThread ID (for add-to-cart)
    status: 'IN_PROGRESS';
    message: '3D conversion initiated';
  }
}
```

**Error Responses:**
- `400` - 2D style transfer not yet completed
- `400` - 3D conversion already in progress or completed
- `404` - Request not found

---

### `getTransfer3dStatus(options)`

Get the status of a conversion request.

**Parameters:**
- `path.requestId`: string - Request ID from conversion response

**Returns:**
```typescript
{
  success: true,
  data: {
    request_id: string;
    status: 'PENDING' | 'IN_PROGRESS' | '2D_COMPLETED' | 'COMPLETED' | 'FAILED';
    phase: '2d_style_transfer' | '3d_generation_pending' | '3d_generation';
    progress: number;  // 0-100 (2D_COMPLETED = 50%)
    web_view_url: string;  // URL to view progress on main site
    auto_3d?: boolean;  // Present for API requests, indicates if 3D will auto-start
    result?: {
      preview_2d_url: string;  // Available when 2D transfer completes
      model_3d_url: string;    // Available when fully completed
      glb_url: string;         // GLB file URL (same as model_3d_url)
    };
    error?: string;  // Present if status is FAILED
  }
}
```

> **Note:** When `auto_3d: false`, the status will be `2D_COMPLETED` (not `COMPLETED`) when 2D styling finishes. Call `triggerTransfer3dConversion()` to start 3D generation.

---

### `getTransfer3DProduct(options)`

Get product information by slug, including name, description, price, and images.

**Parameters:**
- `path.slug`: string - The product slug

**Example:**
```typescript
import {
  createTransfer3DClient,
  getTransfer3DProduct,
  getDefaultProductSlug,
} from '@cymmetrik-dev/3d-transfer-sdk';

const client = createTransfer3DClient();

// Get product by slug
const { data } = await getTransfer3DProduct({
  client,
  path: { slug: 'my-product-slug' },
});

console.log('Product:', data.data.name);
console.log('Price:', data.data.price?.formatted);
console.log('Main Image:', data.data.main_image_url);

// Or use default product slug from environment
const defaultSlug = getDefaultProductSlug();
if (defaultSlug) {
  const { data } = await getTransfer3DProduct({
    client,
    path: { slug: defaultSlug },
  });
}
```

**Returns:**
```typescript
{
  success: true,
  data: {
    slug: string;
    name: string;
    description: string;
    price: {
      value: number;      // Price in smallest currency unit
      formatted: string;  // e.g., "NT$1,990"
      currency: string;   // e.g., "TWD"
    } | null;
    main_image_url: string | null;
    images: Array<{
      url: string;
      alt: string;
    }>;
    variants: Array<{
      id: number;           // Variant ID (use for add-to-cart)
      sku: string;          // SKU code
      options: Array<{      // Variant options (e.g., color, size)
        name: string;       // Option name (e.g., "顏色")
        value: string;      // Option value (e.g., "紅色")
      }>;
      price: {
        value: number;
        formatted: string;
        currency: string;
      };
    }>;
  }
}
```

**Example with Variants:**
```typescript
const { data } = await getTransfer3DProduct({
  client,
  path: { slug: 'my-product' },
});

// Display product with variant selector
console.log('Product:', data.data.name);

data.data.variants.forEach((variant) => {
  const optionText = variant.options
    .map(o => `${o.name}: ${o.value}`)
    .join(', ');
  console.log(`- ${variant.sku} (${optionText}): ${variant.price.formatted}`);
});

// Add specific variant to cart
const selectedVariant = data.data.variants[0];
const cartUrl = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: selectedVariant.id }],
});
```

---

### `getDefaultProductSlug()`

Get the default product slug from environment variables.

**Returns:** `string | undefined`

**Example:**
```typescript
import { getDefaultProductSlug } from '@cymmetrik-dev/3d-transfer-sdk';

const slug = getDefaultProductSlug();
// Returns value from VITE_DEFAULT_PRODUCT_SLUG, NEXT_PUBLIC_DEFAULT_PRODUCT_SLUG, etc.
```

---

### `getDefaultStyle()`

Get the default style from environment variables.

**Returns:** `string | undefined`

**Example:**
```typescript
import { getDefaultStyle } from '@cymmetrik-dev/3d-transfer-sdk';

const style = getDefaultStyle();
// Returns value from VITE_TRANSFER_3D_DEFAULT_STYLE, etc.
```

---

## Add to Cart Redirect

The SDK provides helper functions to build URLs that add products to the shopping cart and redirect users. This feature:
- Works for both logged-in users and guest users
- Supports adding multiple products at once
- Allows custom redirect URLs after adding to cart
- No authentication token required (public endpoint)

### `buildAddToCartUrl(baseURL, options)`

Build a URL that adds products to cart and redirects.

**Parameters:**
- `baseURL`: string - The shop's base URL (without `/api`)
- `options.products`: CartProduct[] - Products to add
- `options.redirectUrl`: string (optional) - Redirect URL after adding (default: `/cart`)

**CartProduct interface:**
```typescript
interface CartProduct {
  model_type:
    | 'Lunar\\Models\\ProductVariant'
    | 'Projects\\Frontier\\Models\\FrontierOrder'
    | 'Projects\\Frontier\\Models\\ChatThread';  // Auto-creates FrontierOrder
  model_id: number;
  quantity?: number;  // default: 1
}
```

**Example:**
```typescript
import { buildAddToCartUrl } from '@cymmetrik-dev/3d-transfer-sdk';

// Add single product
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: 123 }],
});
// Result: https://shop.example.com/api/3d-transfer/add-to-cart?products=[{"model_type":"product_variant","model_id":123}]

// Add multiple products with quantities
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [
    { model_type: 'product_variant', model_id: 123, quantity: 2 },
    { model_type: 'product_variant', model_id: 456, quantity: 1 },
  ],
});

// With custom redirect URL
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'product_variant', model_id: 123 }],
  redirectUrl: '/checkout',
});

// Add 3D model by ChatThread ID (auto-creates FrontierOrder if needed)
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'Projects\\Frontier\\Models\\ChatThread', model_id: chatThreadId }],
});

// Or add existing FrontierOrder directly
const url = buildAddToCartUrl('https://shop.example.com', {
  products: [{ model_type: 'Projects\\Frontier\\Models\\FrontierOrder', model_id: 789 }],
});
```

---

### `buildAddToCartUrlFromEnv(options)`

Build add-to-cart URL using the API URL from environment variables.

**Parameters:**
- `options.products`: CartProduct[] - Products to add
- `options.redirectUrl`: string (optional) - Redirect URL after adding

**Returns:** `string | null` - URL or null if API URL not configured

**Example:**
```typescript
import { buildAddToCartUrlFromEnv } from '@cymmetrik-dev/3d-transfer-sdk';

const url = buildAddToCartUrlFromEnv({
  products: [{ model_type: 'product_variant', model_id: 123 }],
});

if (url) {
  window.location.href = url;
}
```

---

### Add to Cart - React Example

```tsx
import { buildAddToCartUrl, getTransfer3DProduct } from '@cymmetrik-dev/3d-transfer-sdk';

function BuyButton({ productId }: { productId: number }) {
  const handleBuy = () => {
    const url = buildAddToCartUrl('https://shop.example.com', {
      products: [{ model_type: 'product_variant', model_id: productId }],
      redirectUrl: '/checkout',
    });
    window.location.href = url;
  };

  return (
    <button onClick={handleBuy}>
      加入購物車
    </button>
  );
}

// Or use as a link
function BuyLink({ productId }: { productId: number }) {
  const url = buildAddToCartUrl('https://shop.example.com', {
    products: [{ model_type: 'product_variant', model_id: productId }],
  });

  return (
    <a href={url}>
      加入購物車
    </a>
  );
}
```

---

### Add to Cart - HTML Example

```html
<!-- Direct link to add product to cart -->
<a href="https://shop.example.com/api/3d-transfer/add-to-cart?products=[{&quot;model_type&quot;:&quot;product_variant&quot;,&quot;model_id&quot;:123}]">
  加入購物車
</a>

<!-- With redirect to checkout -->
<a href="https://shop.example.com/api/3d-transfer/add-to-cart?products=[{&quot;model_type&quot;:&quot;product_variant&quot;,&quot;model_id&quot;:123}]&redirect_url=/checkout">
  立即購買
</a>
```

---

### Complete Workflow: 3D Model to Cart

```tsx
import {
  createTransfer3DClient,
  initiateTransfer3dConversion,
  getTransfer3dStatus,
  buildAddToCartUrl,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Full workflow: Upload image → Convert → Add to cart
async function convertAndAddToCart(imageFile: File) {
  const client = createTransfer3DClient();

  // 1. Start conversion and get chat_thread_id
  const { data: conversionData } = await initiateTransfer3dConversion({
    client,
    body: { image: imageFile, style: 'smart' },
  });

  const { request_id: requestId, chat_thread_id: chatThreadId } = conversionData.data;
  console.log('ChatThread ID:', chatThreadId);  // Use this for add-to-cart

  // 2. Poll until completed
  let status = 'PENDING';
  while (status !== 'COMPLETED' && status !== 'FAILED') {
    await new Promise((r) => setTimeout(r, 2000));
    const { data } = await getTransfer3dStatus({ client, path: { requestId } });
    status = data.data.status;
  }

  // 3. Add to cart using ChatThread ID (auto-creates FrontierOrder)
  if (status === 'COMPLETED') {
    const cartUrl = buildAddToCartUrl('https://shop.example.com', {
      products: [{
        model_type: 'Projects\\Frontier\\Models\\ChatThread',
        model_id: chatThreadId,
      }],
    });
    window.location.href = cartUrl;
  }
}
```

## Conversion Phases

| Phase | Progress | Description |
|-------|----------|-------------|
| `2d_style_transfer` | 0-50% | Image is being styled with AI |
| `3d_generation_pending` | 50% | 2D complete, waiting for 3D to start |
| `3d_generation` | 50-100% | 3D model is being generated |

**Note:** The `preview_2d_url` becomes available when the phase changes to `3d_generation_pending` or `3d_generation`, allowing you to show the styled image while the 3D model is still being generated.

## Status Values

| Status | Description |
|--------|-------------|
| `PENDING` | Conversion request queued |
| `IN_PROGRESS` | Currently processing (2D or 3D) |
| `2D_COMPLETED` | 2D styling finished, waiting for 3D trigger (only when `auto_3d: false`) |
| `COMPLETED` | Full conversion finished (2D + 3D) |
| `FAILED` | An error occurred |

> **Tip:** For `auto_3d: false` requests, poll until `status === '2D_COMPLETED'`, show the `preview_2d_url`, then call `triggerTransfer3dConversion()` when ready.

## TypeScript Types

```typescript
import type {
  // Request/Response types (auto-generated)
  GetTransfer3dStylesResponse,
  InitiateTransfer3dConversionData,
  InitiateTransfer3dConversionResponse,
  TriggerTransfer3dConversionData,
  TriggerTransfer3dConversionResponse,
  GetTransfer3dStatusData,
  GetTransfer3dStatusResponse,
  GetTransfer3DProductData,
  GetTransfer3DProductResponse,
  // Add to cart types
  CartProduct,
  AddToCartOptions,
} from '@cymmetrik-dev/3d-transfer-sdk';

// Helper functions
import {
  getDefaultProductSlug,
  getDefaultStyle,
  buildAddToCartUrl,
  buildAddToCartUrlFromEnv,
} from '@cymmetrik-dev/3d-transfer-sdk';

// API functions
import {
  getTransfer3dStyles,
  initiateTransfer3dConversion,
  triggerTransfer3dConversion,
  getTransfer3dStatus,
  getTransfer3DProduct,
} from '@cymmetrik-dev/3d-transfer-sdk';

// React component types
import type { ModelViewerProps } from '@cymmetrik-dev/3d-transfer-sdk/react';
```

## Error Handling

```typescript
import { AxiosError } from 'axios';

try {
  const response = await initiateTransfer3dConversion({
    client,
    body: { image: file, style: 'smart' },
  });
} catch (error) {
  if (error instanceof AxiosError) {
    if (error.response?.status === 401) {
      console.error('Authentication failed. Check your API token.');
    } else if (error.response?.status === 422) {
      console.error('Validation error:', error.response.data.errors);
    } else if (error.response?.status === 429) {
      console.error('Rate limit exceeded. Please try again later.');
    }
  }
  throw error;
}
```

## Demo Application

A complete React demo application is available in the [examples/react-demo](https://github.com/cympotek/mobius-frontier/tree/main/builds/3d-transfer-sdk/examples/react-demo) directory.

To run the demo:

```bash
cd examples/react-demo
pnpm install
cp .env.example .env  # Configure your API URL and token
pnpm dev
```

## Support

- [GitHub Issues](https://github.com/cympotek/mobius-frontier/issues)

---

*This SDK is auto-generated from the Mobius Frontier OpenAPI specification using [@hey-api/openapi-ts](https://heyapi.vercel.app/)*
