Added new example tasks (FFmpeg / Sharp / Vercel AI SDK) (#1312)
* Added FFmpeg / sharp / vercel and updated mint.json * Amends including s3 -> r2
This commit is contained in:
@@ -0,0 +1,329 @@
|
||||
---
|
||||
title: "Video processing with FFmpeg"
|
||||
sidebarTitle: "FFmpeg video processing"
|
||||
description: "These examples show you how to process videos in various ways using FFmpeg with Trigger.dev."
|
||||
---
|
||||
|
||||
## Adding the FFmpeg build extension
|
||||
|
||||
To use these example tasks, you'll first need to add our FFmpeg extension to your project configuration like this:
|
||||
|
||||
```ts trigger.config.ts
|
||||
import { ffmpeg } from "@trigger.dev/build/extensions/core";
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export default defineConfig({
|
||||
project: "<project ref>",
|
||||
// Your other config settings...
|
||||
build: {
|
||||
extensions: [ffmpeg()],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
[Build extensions](../guides/build-extensions) allow you to hook into the build system and
|
||||
customize the build process or the resulting bundle and container image (in the case of
|
||||
deploying). You can use pre-built extensions or create your own.
|
||||
</Note>
|
||||
|
||||
You'll also need to add `@trigger.dev/build` to your `package.json` file under `devDependencies` if you don't already have it there.
|
||||
|
||||
## Compress a video using FFmpeg
|
||||
|
||||
This task demonstrates how to use FFmpeg to compress a video, reducing its file size while maintaining reasonable quality, and upload the compressed video to R2 storage.
|
||||
|
||||
### Key Features:
|
||||
|
||||
- Fetches a video from a given URL
|
||||
- Compresses the video using FFmpeg with various compression settings
|
||||
- Uploads the compressed video to R2 storage
|
||||
|
||||
### Task code
|
||||
|
||||
```ts trigger/ffmpeg-compress-video.ts
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import fs from "fs/promises";
|
||||
import fetch from "node-fetch";
|
||||
import { Readable } from "node:stream";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
// Initialize S3 client
|
||||
const s3Client = new S3Client({
|
||||
// How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const ffmpegCompressVideo = task({
|
||||
id: "ffmpeg-compress-video",
|
||||
run: async (payload: { videoUrl: string }) => {
|
||||
const { videoUrl } = payload;
|
||||
|
||||
// Generate temporary file names
|
||||
const tempDirectory = os.tmpdir();
|
||||
const outputPath = path.join(tempDirectory, `output_${Date.now()}.mp4`);
|
||||
|
||||
// Fetch the video
|
||||
const response = await fetch(videoUrl);
|
||||
|
||||
// Compress the video
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!response.body) {
|
||||
return reject(new Error("Failed to fetch video"));
|
||||
}
|
||||
|
||||
ffmpeg(Readable.from(response.body))
|
||||
.outputOptions([
|
||||
"-c:v libx264", // Use H.264 codec
|
||||
"-crf 28", // Higher CRF for more compression (28 is near the upper limit for acceptable quality)
|
||||
"-preset veryslow", // Slowest preset for best compression
|
||||
"-vf scale=iw/2:ih/2", // Reduce resolution to 320p width (height auto-calculated)
|
||||
"-c:a aac", // Use AAC for audio
|
||||
"-b:a 64k", // Reduce audio bitrate to 64k
|
||||
"-ac 1", // Convert to mono audio
|
||||
])
|
||||
.output(outputPath)
|
||||
.on("end", resolve)
|
||||
.on("error", reject)
|
||||
.run();
|
||||
});
|
||||
|
||||
// Read the compressed video
|
||||
const compressedVideo = await fs.readFile(outputPath);
|
||||
|
||||
const compressedSize = compressedVideo.length;
|
||||
|
||||
// Log compression results
|
||||
logger.log(`Compressed video size: ${compressedSize} bytes`);
|
||||
logger.log(`Compressed video saved at: ${outputPath}`);
|
||||
|
||||
// Upload the compressed video to S3, replacing slashes with underscores
|
||||
const r2Key = `processed-videos/${path.basename(outputPath)}`;
|
||||
|
||||
const uploadParams = {
|
||||
Bucket: process.env.R2_BUCKET,
|
||||
Key: r2Key,
|
||||
Body: compressedVideo,
|
||||
};
|
||||
|
||||
// Upload the video to R2 and get the URL
|
||||
await s3Client.send(new PutObjectCommand(uploadParams));
|
||||
const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
|
||||
logger.log("Compressed video uploaded to R2", { url: r2Url });
|
||||
|
||||
// Delete the temporary compressed video file
|
||||
await fs.unlink(outputPath);
|
||||
|
||||
// Return the compressed video file path, compressed size, and S3 URL
|
||||
return {
|
||||
compressedVideoPath: outputPath,
|
||||
compressedSize,
|
||||
r2Url,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Extract audio from a video using FFmpeg
|
||||
|
||||
This task demonstrates how to use FFmpeg to extract audio from a video, convert it to WAV format, and upload it to R2 storage.
|
||||
|
||||
### Key Features:
|
||||
|
||||
- Fetches a video from a given URL
|
||||
- Extracts the audio from the video using FFmpeg
|
||||
- Converts the extracted audio to WAV format
|
||||
- Uploads the extracted audio to R2 storage
|
||||
|
||||
### Task code
|
||||
|
||||
<Warning>
|
||||
When testing, make sure to provide a video URL that contains audio. If the video does not have
|
||||
audio, the task will fail.
|
||||
</Warning>
|
||||
|
||||
```ts trigger/ffmpeg-extract-audio.ts
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import fs from "fs/promises";
|
||||
import fetch from "node-fetch";
|
||||
import { Readable } from "node:stream";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
// Initialize S3 client
|
||||
const s3Client = new S3Client({
|
||||
// How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const ffmpegExtractAudio = task({
|
||||
id: "ffmpeg-extract-audio",
|
||||
run: async (payload: { videoUrl: string }) => {
|
||||
const { videoUrl } = payload;
|
||||
|
||||
// Generate temporary and output file names
|
||||
const tempDirectory = os.tmpdir();
|
||||
const outputPath = path.join(tempDirectory, `output_${Date.now()}.wav`);
|
||||
|
||||
// Fetch the video
|
||||
const response = await fetch(videoUrl);
|
||||
|
||||
// Convert the video to WAV
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!response.body) {
|
||||
return reject(new Error("Failed to fetch video"));
|
||||
}
|
||||
ffmpeg(Readable.from(response.body))
|
||||
.toFormat("wav")
|
||||
.save(outputPath)
|
||||
.on("end", () => {
|
||||
logger.log(`WAV file saved to ${outputPath}`);
|
||||
resolve(outputPath);
|
||||
})
|
||||
.on("error", (err) => {
|
||||
reject(err);
|
||||
});
|
||||
});
|
||||
|
||||
// Read the WAV file
|
||||
const wavBuffer = await fs.readFile(outputPath);
|
||||
|
||||
// Log the output file path
|
||||
logger.log(`Converted video saved at: ${outputPath}`);
|
||||
|
||||
// Upload the compressed video to S3, replacing slashes with underscores
|
||||
const r2Key = `processed-audio/${path.basename(outputPath)}`;
|
||||
|
||||
const uploadParams = {
|
||||
Bucket: process.env.R2_BUCKET,
|
||||
Key: r2Key,
|
||||
Body: wavBuffer,
|
||||
};
|
||||
|
||||
// Upload the audio to R2 and get the URL
|
||||
await s3Client.send(new PutObjectCommand(uploadParams));
|
||||
const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
|
||||
logger.log("Extracted audio uploaded to R2", { url: r2Url });
|
||||
|
||||
// Delete the temporary file
|
||||
await fs.unlink(outputPath);
|
||||
|
||||
// Return the WAV buffer and file path
|
||||
return {
|
||||
wavBuffer,
|
||||
wavFilePath: outputPath,
|
||||
r2Url,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
## Generate a thumbnail from a video using FFmpeg
|
||||
|
||||
This task demonstrates how to use FFmpeg to generate a thumbnail from a video at a specific time and upload the generated thumbnail to R2 storage.
|
||||
|
||||
### Key Features:
|
||||
|
||||
- Fetches a video from a given URL
|
||||
- Generates a thumbnail from the video at the 5-second mark
|
||||
- Uploads the generated thumbnail to R2 storage
|
||||
|
||||
### Task code
|
||||
|
||||
```ts trigger/ffmpeg-generate-thumbnail.ts
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import ffmpeg from "fluent-ffmpeg";
|
||||
import fs from "fs/promises";
|
||||
import fetch from "node-fetch";
|
||||
import { Readable } from "node:stream";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
|
||||
// Initialize S3 client
|
||||
const s3Client = new S3Client({
|
||||
// How to authenticate to R2: https://developers.cloudflare.com/r2/api/s3/tokens/
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const ffmpegGenerateThumbnail = task({
|
||||
id: "ffmpeg-generate-thumbnail",
|
||||
run: async (payload: { videoUrl: string }) => {
|
||||
const { videoUrl } = payload;
|
||||
|
||||
// Generate output file name
|
||||
const tempDirectory = os.tmpdir();
|
||||
const outputPath = path.join(tempDirectory, `thumbnail_${Date.now()}.jpg`);
|
||||
|
||||
// Fetch the video
|
||||
const response = await fetch(videoUrl);
|
||||
|
||||
// Generate the thumbnail
|
||||
await new Promise((resolve, reject) => {
|
||||
if (!response.body) {
|
||||
return reject(new Error("Failed to fetch video"));
|
||||
}
|
||||
ffmpeg(Readable.from(response.body))
|
||||
.screenshots({
|
||||
count: 1,
|
||||
folder: "/tmp",
|
||||
filename: path.basename(outputPath),
|
||||
size: "320x240",
|
||||
timemarks: ["5"], // 5 seconds
|
||||
})
|
||||
.on("end", resolve)
|
||||
.on("error", reject);
|
||||
});
|
||||
|
||||
// Read the generated thumbnail
|
||||
const thumbnail = await fs.readFile(outputPath);
|
||||
|
||||
// Upload the compressed video to S3, replacing slashes with underscores
|
||||
const r2Key = `thumbnails/${path.basename(outputPath)}`;
|
||||
|
||||
const uploadParams = {
|
||||
Bucket: process.env.R2_BUCKET,
|
||||
Key: r2Key,
|
||||
Body: thumbnail,
|
||||
};
|
||||
|
||||
// Upload the thumbnail to R2 and get the URL
|
||||
await s3Client.send(new PutObjectCommand(uploadParams));
|
||||
const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
|
||||
logger.log("Thumbnail uploaded to R2", { url: r2Url });
|
||||
|
||||
// Delete the temporary file
|
||||
await fs.unlink(outputPath);
|
||||
|
||||
// Log thumbnail generation results
|
||||
logger.log(`Thumbnail uploaded to S3: ${r2Url}`);
|
||||
|
||||
// Return the thumbnail buffer, file path, sizes, and S3 URL
|
||||
return {
|
||||
thumbnailBuffer: thumbnail,
|
||||
thumbnailPath: outputPath,
|
||||
r2Url,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,121 @@
|
||||
---
|
||||
title: "Process images using Sharp"
|
||||
sidebarTitle: "Sharp image processing"
|
||||
description: "This example demonstrates how to process images using the Sharp library with Trigger.dev."
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
This task optimizes and watermarks an image using the Sharp library, and then uploads the processed image to R2 storage.
|
||||
|
||||
## Adding build configurations
|
||||
|
||||
To use this example, you'll first need to add these build settings to your `trigger.config.ts` file:
|
||||
|
||||
```ts trigger.config.ts
|
||||
import { defineConfig } from "@trigger.dev/sdk/v3";
|
||||
|
||||
export default defineConfig({
|
||||
project: "<project ref>",
|
||||
// Your other config settings...
|
||||
build: {
|
||||
// This is required to use the Sharp library
|
||||
external: ["sharp"],
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
<Note>
|
||||
Any packages that install or build a native binary should be added to external, as native binaries
|
||||
cannot be bundled.
|
||||
</Note>
|
||||
|
||||
## Key features
|
||||
|
||||
- Resizes and rotates an image
|
||||
- Adds a watermark to the image
|
||||
- Uploads the processed image to R2 storage
|
||||
|
||||
## Task code
|
||||
|
||||
```ts trigger/sharp-image-processing.ts
|
||||
import { PutObjectCommand, S3Client } from "@aws-sdk/client-s3";
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import fs from "fs/promises";
|
||||
import fetch from "node-fetch";
|
||||
import os from "os";
|
||||
import path from "path";
|
||||
import sharp from "sharp";
|
||||
|
||||
// Initialize R2 client
|
||||
const r2Client = new S3Client({
|
||||
region: "auto",
|
||||
endpoint: process.env.R2_ENDPOINT,
|
||||
credentials: {
|
||||
accessKeyId: process.env.R2_ACCESS_KEY_ID ?? "",
|
||||
secretAccessKey: process.env.R2_SECRET_ACCESS_KEY ?? "",
|
||||
},
|
||||
});
|
||||
|
||||
export const sharpProcessImage = task({
|
||||
id: "sharp-process-image",
|
||||
run: async (payload: { imageUrl: string; watermarkUrl: string }) => {
|
||||
const { imageUrl, watermarkUrl } = payload;
|
||||
|
||||
// Generate temporary and output file names
|
||||
const tempDirectory = os.tmpdir();
|
||||
const outputPath = path.join(tempDirectory, `output_${Date.now()}.jpg`);
|
||||
|
||||
// Fetch the image and watermark
|
||||
const [imageResponse, watermarkResponse] = await Promise.all([
|
||||
fetch(imageUrl),
|
||||
fetch(watermarkUrl),
|
||||
]);
|
||||
const imageBuffer = await imageResponse.arrayBuffer();
|
||||
const watermarkBuffer = await watermarkResponse.arrayBuffer();
|
||||
|
||||
// Optimize the image using Sharp
|
||||
await sharp(Buffer.from(imageBuffer))
|
||||
.rotate(90) // Rotate the image by 90 degrees
|
||||
.resize(800, 600) // Resize the image to 800x600
|
||||
.composite([
|
||||
{
|
||||
input: Buffer.from(watermarkBuffer),
|
||||
gravity: "southeast", // Position the watermark in the bottom-right corner
|
||||
},
|
||||
])
|
||||
.toFormat("jpeg")
|
||||
.toFile(outputPath);
|
||||
|
||||
// Log the output file path
|
||||
logger.log(`Optimized image saved at: ${outputPath}`);
|
||||
|
||||
// Read the optimized image file
|
||||
const optimizedImageBuffer = await fs.readFile(outputPath);
|
||||
|
||||
// Upload the optimized image to R2, replacing slashes with underscores
|
||||
const r2Key = `processed-images/${path.basename(outputPath)}`;
|
||||
|
||||
const uploadParams = {
|
||||
Bucket: process.env.R2_BUCKET,
|
||||
Key: r2Key,
|
||||
Body: optimizedImageBuffer,
|
||||
};
|
||||
|
||||
// Upload the image to R2 and get the URL
|
||||
await r2Client.send(new PutObjectCommand(uploadParams));
|
||||
const r2Url = `https://${process.env.R2_ACCOUNT_ID}.r2.cloudflarestorage.com/${process.env.R2_BUCKET}/${r2Key}`;
|
||||
logger.log("Optimized image uploaded to R2", { url: r2Url });
|
||||
|
||||
// Delete the temporary file
|
||||
await fs.unlink(outputPath);
|
||||
|
||||
// Return the optimized image buffer, file path, and R2 URL
|
||||
return {
|
||||
optimizedImageBuffer,
|
||||
optimizedImagePath: outputPath,
|
||||
r2Url,
|
||||
};
|
||||
},
|
||||
});
|
||||
```
|
||||
@@ -0,0 +1,43 @@
|
||||
---
|
||||
title: "Using the Vercel AI SDK"
|
||||
sidebarTitle: "Vercel AI SDK"
|
||||
description: "This example demonstrates how to use the Vercel AI SDK with Trigger.dev."
|
||||
---
|
||||
|
||||
## Overview
|
||||
|
||||
The [Vercel AI SDK](https://www.npmjs.com/package/ai) is a simple way to use AI models from many different providers, including OpenAI, Microsoft Azure, Google Generative AI, Anthropic, Amazon Bedrock, Groq, Perplexity and [more](https://sdk.vercel.ai/providers/ai-sdk-providers).
|
||||
|
||||
It provides a consistent interface to interact with the different AI models, so you can easily switch between them without needing to change your code.
|
||||
|
||||
## Generate text using OpenAI
|
||||
|
||||
This task shows how to use the Vercel AI SDK to generate text from a prompt with OpenAI.
|
||||
|
||||
### Task code
|
||||
|
||||
```ts trigger/vercel-ai-sdk-openai.ts
|
||||
import { logger, task } from "@trigger.dev/sdk/v3";
|
||||
import { generateText } from "ai";
|
||||
// Install the package of the AI model you want to use, in this case OpenAI
|
||||
import { openai } from "@ai-sdk/openai"; // Ensure OPENAI_API_KEY environment variable is set
|
||||
|
||||
export const openaiTask = task({
|
||||
id: "openai-text-generate",
|
||||
|
||||
run: async (payload: { prompt: string }) => {
|
||||
const chatCompletion = await generateText({
|
||||
model: openai("gpt-4-turbo"),
|
||||
// Add a system message which will be included with the prompt
|
||||
system: "You are a friendly assistant!",
|
||||
// The prompt passed in from the payload
|
||||
prompt: payload.prompt,
|
||||
});
|
||||
|
||||
// Log the generated text
|
||||
logger.log("chatCompletion text:" + chatCompletion.text);
|
||||
|
||||
return chatCompletion;
|
||||
},
|
||||
});
|
||||
```
|
||||
+17
-50
@@ -1,10 +1,7 @@
|
||||
{
|
||||
"$schema": "https://mintlify.com/schema.json",
|
||||
"name": "Trigger.dev",
|
||||
"openapi": [
|
||||
"/openapi.yml",
|
||||
"/v3-openapi.yaml"
|
||||
],
|
||||
"openapi": ["/openapi.yml", "/v3-openapi.yaml"],
|
||||
"api": {
|
||||
"playground": {
|
||||
"mode": "simple"
|
||||
@@ -103,23 +100,14 @@
|
||||
"navigation": [
|
||||
{
|
||||
"group": "Getting Started",
|
||||
"pages": [
|
||||
"introduction",
|
||||
"quick-start",
|
||||
"how-it-works",
|
||||
"upgrading-beta",
|
||||
"limits"
|
||||
]
|
||||
"pages": ["introduction", "quick-start", "how-it-works", "upgrading-beta", "limits"]
|
||||
},
|
||||
{
|
||||
"group": "Fundamentals",
|
||||
"pages": [
|
||||
{
|
||||
"group": "Tasks",
|
||||
"pages": [
|
||||
"tasks/overview",
|
||||
"tasks/scheduled"
|
||||
]
|
||||
"pages": ["tasks/overview", "tasks/scheduled"]
|
||||
},
|
||||
"triggering",
|
||||
"apikeys",
|
||||
@@ -128,10 +116,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Development",
|
||||
"pages": [
|
||||
"cli-dev",
|
||||
"run-tests"
|
||||
]
|
||||
"pages": ["cli-dev", "run-tests"]
|
||||
},
|
||||
{
|
||||
"group": "Deployment",
|
||||
@@ -141,9 +126,7 @@
|
||||
"github-actions",
|
||||
{
|
||||
"group": "Deployment integrations",
|
||||
"pages": [
|
||||
"vercel-integration"
|
||||
]
|
||||
"pages": ["vercel-integration"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -155,13 +138,7 @@
|
||||
"errors-retrying",
|
||||
{
|
||||
"group": "Wait",
|
||||
"pages": [
|
||||
"wait",
|
||||
"wait-for",
|
||||
"wait-until",
|
||||
"wait-for-event",
|
||||
"wait-for-request"
|
||||
]
|
||||
"pages": ["wait", "wait-for", "wait-until", "wait-for-event", "wait-for-request"]
|
||||
},
|
||||
"queue-concurrency",
|
||||
"versioning",
|
||||
@@ -179,10 +156,7 @@
|
||||
"management/overview",
|
||||
{
|
||||
"group": "Tasks API",
|
||||
"pages": [
|
||||
"management/tasks/trigger",
|
||||
"management/tasks/batch-trigger"
|
||||
]
|
||||
"pages": ["management/tasks/trigger", "management/tasks/batch-trigger"]
|
||||
},
|
||||
{
|
||||
"group": "Runs API",
|
||||
@@ -220,9 +194,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Projects API",
|
||||
"pages": [
|
||||
"management/projects/runs"
|
||||
]
|
||||
"pages": ["management/projects/runs"]
|
||||
}
|
||||
]
|
||||
},
|
||||
@@ -268,11 +240,7 @@
|
||||
},
|
||||
{
|
||||
"group": "Help",
|
||||
"pages": [
|
||||
"community",
|
||||
"help-slack",
|
||||
"help-email"
|
||||
]
|
||||
"pages": ["community", "help-slack", "help-email"]
|
||||
},
|
||||
{
|
||||
"group": "Frameworks",
|
||||
@@ -294,23 +262,22 @@
|
||||
},
|
||||
{
|
||||
"group": "Dashboard",
|
||||
"pages": [
|
||||
"guides/dashboard/creating-a-project"
|
||||
]
|
||||
"pages": ["guides/dashboard/creating-a-project"]
|
||||
},
|
||||
{
|
||||
"group": "Migrations",
|
||||
"pages": [
|
||||
"guides/use-cases/upgrading-from-v2"
|
||||
]
|
||||
"pages": ["guides/use-cases/upgrading-from-v2"]
|
||||
},
|
||||
{
|
||||
"group": "Examples",
|
||||
"pages": [
|
||||
"examples/generate-image-with-dall-e3",
|
||||
"examples/dall-e3-generate-image",
|
||||
"examples/ffmpeg-video-processing",
|
||||
"examples/open-ai-with-retrying",
|
||||
"examples/sharp-image-processing",
|
||||
"examples/react-pdf",
|
||||
"examples/resend-email-sequence"
|
||||
"examples/resend-email-sequence",
|
||||
"examples/vercel-ai-sdk"
|
||||
]
|
||||
}
|
||||
],
|
||||
@@ -319,4 +286,4 @@
|
||||
"github": "https://github.com/triggerdotdev",
|
||||
"linkedin": "https://www.linkedin.com/company/triggerdotdev"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user