# FireRed LoRA — AI Image Editing API

Send a photo + text prompt, get back an edited image in **3.5 seconds**.

Built on [FireRed-Edit-1.1](https://huggingface.co/FireRedTeam/FireRed-Image-Edit-1.1) + [Lightning LoRA](https://huggingface.co/FireRedTeam/FireRed-Image-Edit-LoRA-Zoo) for 3x faster inference. Deployed on H100 GPUs via Baseten.

---

## Samples

All edits below were generated from the **same source image**, using only a text prompt. 6 steps, cfg=1.0, ~3.5s each.

<p align="center">
<img src="samples/original.jpg" width="200"><br>
<em>Source image</em>
</p>

### Accessories & Clothing

<table>
<tr>
<td align="center" width="33%">
<img src="samples/sample_01_cowboy.png" width="250"><br>
<em>"Put a tiny cowboy hat on the cat"</em>
</td>
<td align="center" width="33%">
<img src="samples/sample_02_tuxedo.png" width="250"><br>
<em>"Make the cat wear a tiny formal black tuxedo with a bow tie"</em>
</td>
<td align="center" width="33%">
<img src="samples/sample_03_sunglasses.png" width="250"><br>
<em>"Add cool aviator sunglasses to the cat"</em>
</td>
</tr>
<tr>
<td align="center">
<img src="samples/sample_06_superhero.png" width="250"><br>
<em>"Add a red superhero cape to the cat"</em>
</td>
<td align="center">
<img src="samples/sample_07_crown.png" width="250"><br>
<em>"Put a royal golden crown on the cat's head"</em>
</td>
<td align="center">
<img src="samples/sample_09_painting.png" width="250"><br>
<em>"Turn this into an oil painting in renaissance style"</em>
</td>
</tr>
</table>

### Background & Scene

<table>
<tr>
<td align="center" width="33%">
<img src="samples/sample_04_space.png" width="250"><br>
<em>"Change the background to outer space with stars and galaxies"</em>
</td>
<td align="center" width="33%">
<img src="samples/sample_05_livingroom.png" width="250"><br>
<em>"Put the cat in a cozy living room with a warm fireplace"</em>
</td>
<td align="center" width="33%">
<img src="samples/sample_08_snow.png" width="250"><br>
<em>"Change the background to a snowy winter wonderland"</em>
</td>
</tr>
</table>

### Quality Scores (Gemini 3.1 Pro Eval)

| Metric | Score |
|--------|-------|
| Overall Quality | **9.33 / 10** |
| Edit Accuracy | 9.78 / 10 |
| Identity Preservation | **10.0 / 10** |
| Visual Quality | 9.44 / 10 |

---

## API

**Base URL:** `https://firered-lora-api.vercel.app`

All `/api/*` endpoints require `X-API-Key` header.

### `POST /api/edit` — Edit an image

```bash
curl -X POST https://firered-lora-api.vercel.app/api/edit \
  -H "Content-Type: application/json" \
  -H "X-API-Key: YOUR_API_KEY" \
  -d '{
    "image": "<base64-encoded-image>",
    "prompt": "Add sunglasses"
  }'
```

**Request:**

| Field | Type | Required | Default | Description |
|-------|------|----------|---------|-------------|
| `image` | string | Yes | — | Base64-encoded JPEG or PNG |
| `prompt` | string | Yes | — | What to edit, in plain English |
| `steps` | int | No | 6 | Inference steps (4-20) |
| `cfg` | float | No | 1.0 | Guidance scale. Keep at 1.0. |
| `seed` | int | No | 42 | For reproducibility |
| `client_id` | string | No | — | Your app/user identifier |

**Response:**

```json
{
  "job_id": "fec1bc51-00df-4352-9031-fbc368f498d1",
  "status": "completed",
  "actual_time_s": 3.48,
  "output_b64": "iVBORw0KGgo...",
  "output_image_url": "",
  "config": { "steps": 6, "cfg": 1.0, "seed": 42 },
  "input_size": "600x600",
  "output_size": "1024x1024"
}
```

### `GET /api/status/{job_id}` — Job details + output image

### `GET /api/history?page=1&limit=20&status=completed` — Paginated job list

### `GET /api/queue` — Live stats + GPU status

```json
{
  "completed": 12, "processing": 0, "queued": 0, "failed": 0,
  "avg_time_s": 3.51, "throughput_per_min": 17.1,
  "gpu": { "status": "ACTIVE", "active_replicas": 1, "instance_type": "H100" }
}
```

### `POST /api/gpu/scale?replicas=N` — Scale GPUs up/down

### `POST /api/job/{id}/cancel` | `POST /api/job/{id}/retry`

Full API docs with all fields: [`api/api_docs.json`](api/api_docs.json)

---

## Code Examples

### Python
```python
import requests, base64

with open("photo.jpg", "rb") as f:
    img_b64 = base64.b64encode(f.read()).decode()

resp = requests.post(
    "https://firered-lora-api.vercel.app/api/edit",
    headers={"X-API-Key": "YOUR_API_KEY"},
    json={"image": img_b64, "prompt": "Add sunglasses"},
    timeout=60,
)
result = resp.json()

with open("edited.png", "wb") as f:
    f.write(base64.b64decode(result["output_b64"]))
```

### JavaScript
```javascript
async function editImage(imageBase64, prompt) {
  const resp = await fetch("https://firered-lora-api.vercel.app/api/edit", {
    method: "POST",
    headers: {
      "Content-Type": "application/json",
      "X-API-Key": "YOUR_API_KEY",
    },
    body: JSON.stringify({ image: imageBase64, prompt }),
  });
  const { output_b64 } = await resp.json();
  return `data:image/png;base64,${output_b64}`;
}
```

### React Native
```javascript
async function editPhoto(uri, prompt) {
  const response = await fetch(uri);
  const blob = await response.blob();
  const reader = new FileReader();
  const base64 = await new Promise(resolve => {
    reader.onload = () => resolve(reader.result.split(",")[1]);
    reader.readAsDataURL(blob);
  });

  const resp = await fetch("https://firered-lora-api.vercel.app/api/edit", {
    method: "POST",
    headers: { "Content-Type": "application/json", "X-API-Key": "YOUR_API_KEY" },
    body: JSON.stringify({ image: base64, prompt, client_id: "mobile-app" }),
  });
  const { output_b64 } = await resp.json();
  return `data:image/png;base64,${output_b64}`;
}
```

---

## Architecture

```
Client App ──► Vercel (FastAPI + auth) ──► Baseten H100 GPU
                     │                          │
                     ▼                          │
               Supabase (job tracking) ◄────────┘
```

| Component | Role |
|-----------|------|
| **Baseten** | GPU inference — H100, auto-scaling 0 to N replicas |
| **Vercel** | API gateway — auth, routing, dashboard |
| **Supabase** | Job queue — status tracking, history, analytics |

## Model

| | |
|---|---|
| Base | [FireRed-Edit-1.1](https://huggingface.co/FireRedTeam/FireRed-Image-Edit-1.1) |
| LoRA | [Lightning 8-step](https://huggingface.co/FireRedTeam/FireRed-Image-Edit-LoRA-Zoo) |
| Pipeline | `QwenImageEditPlusPipeline` (diffusers) |
| Config | 6 steps, cfg=1.0 |
| Inference | 3.2–3.5s on H100 |
| VRAM | ~55 GB bf16, ~31 GB INT8 |
| Output | 1024x1024 |

## Scaling

| GPUs | Edits/min | Edits/hr | Cost/hr |
|------|-----------|----------|---------|
| 1 | 17 | 1,028 | ~$2.50 |
| 2 | 34 | 2,057 | ~$5.00 |
| 4 | 68 | 4,114 | ~$10.00 |
| 8 | 137 | 8,228 | ~$20.00 |

GPU scales to zero when idle (no cost). Cold start ~2 min.

## Admin Dashboard

Password-protected at API root. Real-time stats, GPU status, full job history with images, auto-refresh.

## Structure

```
firered-lora/
├── model/
│   ├── __init__.py
│   └── model.py            # Baseten Truss: load() + predict()
├── config.yaml              # H100, bf16, concurrency=1
├── api/
│   ├── server.py            # FastAPI (auth + dashboard + endpoints)
│   ├── api/index.py         # Vercel entrypoint
│   ├── vercel.json
│   ├── requirements.txt
│   ├── supabase_schema.sql  # Postgres job queue schema
│   └── api_docs.json        # Complete API reference (JSON)
└── samples/                 # Demo edits
```

## Deploy

```bash
# 1. GPU (Baseten)
pip install truss
truss login --api-key $BASETEN_KEY
cd model/ && truss push --promote

# 2. Database (Supabase)
# Run api/supabase_schema.sql in SQL editor

# 3. API (Vercel)
cd api/ && vercel --prod
```
