Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions examples/bun-cli/README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Bun CLI Example

A simple CLI for text-to-image generation using the Decart SDK.
A simple CLI for image editing using the Decart SDK.

## Setup

Expand All @@ -27,13 +27,13 @@ A simple CLI for text-to-image generation using the Decart SDK.
## Usage

### Dev-time
```
./cli.ts text-to-image "A cyberpunk cityscape at night"
```bash
./cli.ts image-edit "A cyberpunk cityscape at night" ./input.png
```

### Compiled
```bash
decart text-to-image "A cyberpunk cityscape at night"
decart image-edit "A cyberpunk cityscape at night" ./input.png
```

The generated image will be saved to `output.png`.
The edited image will be saved to `output.png`.
12 changes: 7 additions & 5 deletions examples/bun-cli/cli.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
#!/usr/bin/env bun
import { createDecartClient, models } from "@decartai/sdk";

const [command, prompt] = process.argv.slice(2);
const [command, prompt, inputPath] = process.argv.slice(2);

if (command !== "text-to-image" || !prompt) {
console.error("Usage: decart text-to-image <prompt>");
if (command !== "image-edit" || !prompt || !inputPath) {
console.error("Usage: decart image-edit <prompt> <input-image-path>");
process.exit(1);
}

const client = createDecartClient();
const inputImage = Bun.file(inputPath);

console.log("Generating image...");
console.log("Editing image...");
const image = await client.process({
model: models.image("lucy-pro-t2i"),
model: models.image("lucy-pro-i2i"),
prompt,
data: inputImage,
});

await Bun.write("output.png", image);
Expand Down
32 changes: 11 additions & 21 deletions examples/express-api/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,52 +27,42 @@ pnpm dev

## Endpoints

### Image Generation
These editing endpoints expect `imageDataUrl` / `videoDataUrl` fields containing base64 `data:` URLs. This keeps the example self-contained and avoids server-side fetching of arbitrary remote URLs.

### Image Editing

```bash
# Text-to-image
curl -X POST http://localhost:3000/api/image/generate \
curl -X POST http://localhost:3000/api/image/edit \
-H "Content-Type: application/json" \
-d '{"prompt": "A beautiful sunset over mountains"}' \
-d '{"prompt": "Oil painting style", "imageDataUrl": "data:image/png;base64,<base64-image>"}' \
--output image.png

# Image-to-image
curl -X POST http://localhost:3000/api/image/transform \
-H "Content-Type: application/json" \
-d '{"prompt": "Oil painting style", "imageUrl": "https://example.com/image.jpg"}' \
--output transformed.png
```

### Video Generation
### Video Editing

```bash
# Submit video job
curl -X POST http://localhost:3000/api/video/generate \
-H "Content-Type: application/json" \
-d '{"prompt": "A cat playing piano"}'
-d '{"prompt": "Make it look like a watercolor painting", "videoDataUrl": "data:video/mp4;base64,<base64-video>"}'
# Returns: {"jobId": "abc123", "status": "pending"}

# Check status
curl http://localhost:3000/api/video/status/abc123
# Returns: {"job_id": "abc123", "status": "processing"}

# Get result (when completed)
curl http://localhost:3000/api/video/result/abc123 --output video.mp4

# Or use the sync endpoint (waits for completion)
curl -X POST http://localhost:3000/api/video/generate-sync \
-H "Content-Type: application/json" \
-d '{"prompt": "A timelapse of clouds moving"}' \
-d '{"prompt": "Transform into anime style", "videoDataUrl": "data:video/mp4;base64,<base64-video>"}' \
--output video.mp4
```

## API Reference

| Endpoint | Method | Description |
|----------|--------|-------------|
| `/api/image/generate` | POST | Generate image from text |
| `/api/image/transform` | POST | Transform image with prompt |
| `/api/video/generate` | POST | Submit video generation job |
| `/api/image/edit` | POST | Edit image from base64 data URL + prompt |
| `/api/video/generate` | POST | Submit video editing job |
| `/api/video/status/:id` | GET | Check video job status |
| `/api/video/result/:id` | GET | Get completed video |
| `/api/video/generate-sync` | POST | Generate video (waits for completion) |
| `/api/video/generate-sync` | POST | Edit video (waits for completion) |
68 changes: 40 additions & 28 deletions examples/express-api/src/server.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,33 +9,37 @@ const client = createDecartClient({
apiKey: process.env.DECART_API_KEY!,
});

// Generate image from text (sync - returns immediately)
app.post("/api/image/generate", async (req, res) => {
try {
const { prompt } = req.body;
function parseBase64DataUrl(dataUrl: unknown, mediaType: "image" | "video"): Blob {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Express JSON body limit blocks base64 data URLs

Medium Severity

express.json() defaults to a 100KB body size limit. The endpoints now expect imageDataUrl and videoDataUrl fields containing full base64-encoded files in the JSON body, which will easily exceed this limit. Previously, payloads were small (just prompts and URL strings), so the default was fine. Now any real image or video will trigger a 413 Payload Too Large error, making all editing endpoints non-functional.

Fix in Cursor Fix in Web

if (typeof dataUrl !== "string") {
throw new Error(`${mediaType}DataUrl must be a base64 data URL string`);
}

const blob = await client.process({
model: models.image("lucy-pro-t2i"),
prompt,
});
const match = /^data:([^;]+);base64,(.+)$/.exec(dataUrl);
if (!match) {
throw new Error(`${mediaType}DataUrl must be a valid base64 data URL`);
}

const buffer = Buffer.from(await blob.arrayBuffer());
res.setHeader("Content-Type", "image/png");
res.send(buffer);
} catch (error) {
res.status(500).json({ error: String(error) });
const [, mimeType, base64] = match;
if (!mimeType.startsWith(`${mediaType}/`)) {
throw new Error(`${mediaType}DataUrl must contain a ${mediaType} MIME type`);
}
});

// Transform image (sync - returns immediately)
app.post("/api/image/transform", async (req, res) => {
return new Blob([Buffer.from(base64, "base64")], { type: mimeType });
}

// Edit image (sync - returns immediately)
app.post("/api/image/edit", async (req, res) => {
try {
const { prompt, imageUrl } = req.body;
const { prompt, imageDataUrl } = req.body;

if (!prompt || !imageDataUrl) {
return res.status(400).json({ error: "prompt and imageDataUrl are required" });
}

const blob = await client.process({
model: models.image("lucy-pro-i2i"),
prompt,
data: imageUrl,
data: parseBase64DataUrl(imageDataUrl, "image"),
});

const buffer = Buffer.from(await blob.arrayBuffer());
Expand All @@ -46,14 +50,19 @@ app.post("/api/image/transform", async (req, res) => {
}
});

// Submit video generation job (async - returns job ID)
// Submit video editing job (async - returns job ID)
app.post("/api/video/generate", async (req, res) => {
try {
const { prompt } = req.body;
const { prompt, videoDataUrl } = req.body;

if (!prompt || !videoDataUrl) {
return res.status(400).json({ error: "prompt and videoDataUrl are required" });
}

const job = await client.queue.submit({
model: models.video("lucy-pro-t2v"),
model: models.video("lucy-pro-v2v"),
prompt,
data: parseBase64DataUrl(videoDataUrl, "video"),
});

res.json({ jobId: job.job_id, status: job.status });
Expand Down Expand Up @@ -87,12 +96,16 @@ app.get("/api/video/result/:jobId", async (req, res) => {
// Generate video with automatic polling (convenience endpoint)
app.post("/api/video/generate-sync", async (req, res) => {
try {
const { prompt, videoUrl } = req.body;
const { prompt, videoDataUrl } = req.body;

if (!prompt || !videoDataUrl) {
return res.status(400).json({ error: "prompt and videoDataUrl are required" });
}

const result = await client.queue.submitAndPoll({
model: videoUrl ? models.video("lucy-pro-v2v") : models.video("lucy-pro-t2v"),
model: models.video("lucy-pro-v2v"),
prompt,
...(videoUrl && { data: videoUrl }),
data: parseBase64DataUrl(videoDataUrl, "video"),
});

if (result.status === "completed") {
Expand All @@ -112,10 +125,9 @@ app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
console.log("");
console.log("Available endpoints:");
console.log(" POST /api/image/generate - Generate image from text");
console.log(" POST /api/image/transform - Transform image");
console.log(" POST /api/video/generate - Submit video job");
console.log(" POST /api/image/edit - Edit image from base64 data URL + prompt");
console.log(" POST /api/video/generate - Submit video edit job from base64 data URL + prompt");
console.log(" GET /api/video/status/:id - Check job status");
console.log(" GET /api/video/result/:id - Get video result");
console.log(" POST /api/video/generate-sync - Generate video (wait for result)");
console.log(" POST /api/video/generate-sync - Edit video (wait for result)");
});
13 changes: 7 additions & 6 deletions examples/express-proxy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,18 +60,19 @@ app.use(route, handler({
The frontend:
1. Loads the Decart SDK from CDN
2. Creates a client pointing to the proxy endpoint
3. Uses the SDK normally - no API key needed!
3. Uploads a source image from the browser and edits it with a prompt

```javascript
import { createDecartClient, models } from '@decartai/sdk';

const client = createDecartClient({
proxy: '/api/decart', // Points to your Express proxy
proxy: '/api/decart',
});

const blob = await client.process({
model: models.image('lucy-pro-t2i'),
prompt: 'A beautiful sunset',
model: models.image('lucy-pro-i2i'),
prompt: 'Turn this into a watercolor painting',
data: sourceFile,
});
```

Expand All @@ -86,8 +87,8 @@ const blob = await client.process({

1. Start the server: `pnpm dev`
2. Open `http://localhost:3000` in your browser
3. Enter a prompt and click "Generate Image"
4. The image will be generated through the proxy
3. Enter a prompt, choose a source image, and click "Edit Image"
4. The edited image will be generated through the proxy

## Security Notes

Expand Down
Loading
Loading