Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
54 commits
Select commit Hold shift + click to select a range
61d5961
Leonardo AI components
sergio-eliot-rodriguez Sep 13, 2025
1f0e5b4
added unzoom image action
sergio-eliot-rodriguez Sep 13, 2025
43c2645
fixing link errors
sergio-eliot-rodriguez Sep 13, 2025
185b2cd
more lint fixes
sergio-eliot-rodriguez Sep 13, 2025
b52e6af
Merging pull request #18359
jocarino Sep 14, 2025
768d13e
Merging pull request #18361
michelle0927 Sep 14, 2025
cab79a5
Google Sheets - update row refresh fields (#18369)
andrewjschuang Sep 15, 2025
ab578e6
Pipedrive - fix app name (#18370)
andrewjschuang Sep 15, 2025
47cc79a
Pipedrive - pipelineId integer (#18372)
andrewjschuang Sep 15, 2025
39af3b6
Adding app scaffolding for lightspeed_ecom_c_series
danhsiung Sep 15, 2025
53176bc
Adding app scaffolding for financial_data
danhsiung Sep 15, 2025
48338b7
Adding app scaffolding for microsoft_authenticator
danhsiung Sep 16, 2025
cdbc17b
Merging pull request #18345
michelle0927 Sep 16, 2025
4ea8c2e
Merging pull request #18368
michelle0927 Sep 16, 2025
0c0e183
Coinbase Developer Platform - New Wallet Event (#18342)
michelle0927 Sep 16, 2025
8268c0e
Hubspot - update search-crm (#18360)
michelle0927 Sep 16, 2025
d5c554d
Merging pull request #18347
michelle0927 Sep 17, 2025
07aa960
Adding app scaffolding for rundeck
danhsiung Sep 17, 2025
cef1e1b
Merging pull request #18378
luancazarine Sep 17, 2025
3cb8df5
Merging pull request #18382
michelle0927 Sep 17, 2025
6f23e27
Merging pull request #18323
lcaresia Sep 17, 2025
e35feaf
Merging pull request #18377
lcaresia Sep 17, 2025
874a7cf
Merging pull request #18376
lcaresia Sep 17, 2025
f381c67
Adding app scaffolding for etrusted
danhsiung Sep 17, 2025
d11624e
Adding app scaffolding for intelliflo_office
danhsiung Sep 17, 2025
dc3964a
Adding app scaffolding for thoughtspot
danhsiung Sep 17, 2025
bbde963
Adding app scaffolding for kordiam
danhsiung Sep 17, 2025
fc90f7b
Adding app scaffolding for ticketsauce
danhsiung Sep 17, 2025
961dd55
trustpilot fixes (#18152)
Afstkla Sep 17, 2025
e838ebe
Adding app scaffolding for peekalink
danhsiung Sep 17, 2025
85bd98a
18314 twilio (#18350)
luancazarine Sep 17, 2025
2dfb941
Updating LinkedIn API version (#18399)
GTFalcao Sep 17, 2025
ffa140c
Merging pull request #18394
michelle0927 Sep 18, 2025
1884cf0
Databricks API - Jobs action components (#18371)
jcortes Sep 18, 2025
4e0aa1d
Notion property building improvements (#18381)
michelle0927 Sep 18, 2025
5a236a9
Google Business - add debug log (#18407)
andrewjschuang Sep 18, 2025
52c9a94
Notion API Key - update @pipedream/notion version (#18409)
michelle0927 Sep 18, 2025
5e65398
Adding app scaffolding for reduct_video
danhsiung Sep 18, 2025
7073397
Adding app scaffolding for shopware
danhsiung Sep 19, 2025
61539b7
Adding app scaffolding for instamojo
danhsiung Sep 19, 2025
2863b95
Hubspot - bug fix to sources w/ property changes (#18379)
michelle0927 Sep 19, 2025
d15d706
Google sheets type fix (#18411)
GTFalcao Sep 20, 2025
1721bbf
Merging pull request #18393
michelle0927 Sep 21, 2025
0185460
Merging pull request #18408
michelle0927 Sep 21, 2025
72c48a3
Merging pull request #18419
michelle0927 Sep 21, 2025
8b4f66c
Changes per PR Review
sergio-eliot-rodriguez Sep 21, 2025
3260986
Removes leonardo_ai_actions.mdc not indented for merging
sergio-eliot-rodriguez Sep 21, 2025
7a6774b
synced lockfile after install
sergio-eliot-rodriguez Sep 21, 2025
d2d0b88
fully lock form-data for leonardo_ai
sergio-eliot-rodriguez Sep 22, 2025
c04c193
lockfile conflict seems fixed
sergio-eliot-rodriguez Sep 22, 2025
b5f7dce
conflict solving
sergio-eliot-rodriguez Sep 22, 2025
1d35b09
lint fixes
sergio-eliot-rodriguez Sep 22, 2025
18e7669
Chipped down Readme, implemented async options in gen motion
sergio-eliot-rodriguez Sep 23, 2025
6765ddc
Merge remote-tracking branch 'upstream/master' into leonardo_components
sergio-eliot-rodriguez Sep 23, 2025
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
14 changes: 14 additions & 0 deletions components/leonardo_ai/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
# Leonardo AI

[Leonardo AI](https://leonardo.ai) is an AI-powered image generation platform that allows you to create stunning images, videos, and 3D models using advanced machine learning models.

## API Reference

For detailed information about Leonardo AI's API, visit the [official documentation](https://docs.leonardo.ai/reference).

## Support

For support with this component or Leonardo AI's API, please refer to:
- [Leonardo AI Documentation](https://docs.leonardo.ai)
- [Leonardo AI Community](https://community.leonardo.ai)
- [Pipedream Support](https://pipedream.com/support)
111 changes: 111 additions & 0 deletions components/leonardo_ai/actions/generate-image/generate-image.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,111 @@
import app from "../../leonardo_ai.app.mjs";

export default {
key: "leonardo_ai-generate-image",
name: "Generate Image",
description: "Generates new images using Leonardo AI's image generation API. [See the documentation](https://docs.leonardo.ai/reference/creategeneration)",
version: "0.0.1",
type: "action",
props: {
app,
prompt: {
type: "string",
label: "Prompt",
description: "The text prompt describing the image you want to generate.",
},
modelId: {
type: "string",
label: "Model",
description: "The model to use for generation. Leave empty to use the default model.",
async options() {
const models = await this.app.getPlatformModels();
return models.map((model) => ({
label: model.name || model.id,
value: model.id,
}));
},
optional: true,
},
width: {
type: "integer",
label: "Width",
description: "Width of the generated image in pixels.",
default: 1024,
min: 32,
max: 1536,
},
height: {
type: "integer",
label: "Height",
description: "Height of the generated image in pixels.",
default: 768,
min: 32,
max: 1536,
},
numImages: {
type: "integer",
label: "Number of Images",
description: "Number of images to generate (1-8). If either width or height is over 768, must be between 1 and 4.",
default: 1,
min: 1,
max: 8,
},
Comment on lines +45 to +52
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

Validate size vs. image count before calling the API

Enforce “if width or height > 768, numImages must be 1–4” to prevent API errors.

   async run({ $ }) {
     const {
       prompt,
       modelId,
       width,
       height,
       numImages,
       guidanceScale,
       numInferenceSteps,
       seed,
     } = this;

+    if ((width > 768 || height > 768) && numImages > 4) {
+      throw new Error("When Width or Height exceeds 768, Number of Images must be between 1 and 4.");
+    }

Also applies to: 78-88

🤖 Prompt for AI Agents
In components/leonardo_ai/actions/generate-image/generate-image.mjs around lines
45-52 (and also apply to 78-88), the current schema allows numImages up to 8
regardless of width/height; add validation before calling the API that checks if
either width or height is greater than 768, and if so enforce numImages is
between 1 and 4 (otherwise allow 1–8). Implement this by reading the width and
height inputs, and if either >768 and numImages is outside 1–4, throw/return a
validation error (or clamp/adjust per project convention) with a clear message;
apply the same validation logic in the other block at lines 78-88.

guidanceScale: {
type: "integer",
label: "Guidance Scale",
description: "How closely the model should follow the prompt. Must be between 1 and 20. Higher values = more adherence to prompt.",
default: 7,
min: 1,
max: 20,
optional: true,
},
numInferenceSteps: {
type: "integer",
label: "Inference Steps",
description: "Number of denoising steps. More steps = higher quality but slower generation.",
default: 15,
min: 10,
max: 60,
optional: true,
},
seed: {
type: "integer",
label: "Seed",
description: "Random seed for reproducible generation. Leave empty for random generation.",
optional: true,
},
},
async run({ $ }) {
const {
prompt,
modelId,
width,
height,
numImages,
guidanceScale,
numInferenceSteps,
seed,
} = this;

const data = {
prompt,
width,
height,
num_images: numImages,
modelId,
guidance_scale: guidanceScale,
num_inference_steps: numInferenceSteps,
seed,
};

const response = await this.app._makeRequest({
$,
method: "POST",
path: "/generations",
data,
});

$.export("$summary", `Successfully generated ${numImages} image(s)`);
return response;
},
};
117 changes: 117 additions & 0 deletions components/leonardo_ai/actions/generate-motion/generate-motion.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import app from "../../leonardo_ai.app.mjs";

export default {
key: "leonardo_ai-generate-motion",
name: "Generate Motion",
description: "Generates a motion (video) from the provided image using Leonardo AI's SVD Motion Generation API. [See the documentation](https://docs.leonardo.ai/reference/createsvdmotiongeneration)",
version: "0.0.1",
type: "action",
props: {
app,
imageId: {
type: "string",
label: "Image ID",
description: "The ID of the image to generate motion from. You can either select from previously generated images or manually enter the ID of an uploaded image.",
async options({ prevContext }) {
// Get user info to retrieve userId
const userInfo = await this.app.getUserInfo({
$: this,
});
// Extract userId from the response structure
const userId = userInfo.user_details?.[0]?.user?.id || userInfo.id;
Copy link
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue

🧩 Analysis chain

Potential issue with userId extraction fallback

The userId extraction uses userInfo.user_details?.[0]?.user?.id || userInfo.id as fallback. This might work, but without knowing the exact API response structure, the fallback to userInfo.id could be incorrect if the user info response doesn't have a top-level id field.

Let me verify the actual response structure from the /me endpoint:


🌐 Web query:

Leonardo AI API /me endpoint response structure getUserInfo

💡 Result:

Example response (GET /api/rest/v1/me — getUserInfo) — sample JSON structure:

{
"user_details": [
{
"user": {
"id": "string",
"username": "string"
},
"tokenRenewalDate": "string",
"paidTokens": 0,
"subscriptionTokens": 0,
"subscriptionGptTokens": 0,
"subscriptionModelTokens": 0,
"apiConcurrencySlots": 0,
"apiPaidTokens": 0,
"apiSubscriptionTokens": 0,
"apiPlanTokenRenewalDate": "string"
}
]
}

Notes: this matches the documented sample for the /me (Get user information) endpoint. For full, authoritative docs and any updates see the Leonardo API documentation. [1][2]

Sources:
[1] API listing / Get user information (example response).
[2] Leonardo.ai API documentation overview.


Fix userId extraction — don't fallback to a non-existent top-level id

The /me response places the id at user_details[0].user.id (no top-level id), so userInfo.id will be undefined; update the code to rely on userInfo.user_details?.[0]?.user?.id and add explicit handling/error if missing — components/leonardo_ai/actions/generate-motion/generate-motion.mjs:21

🤖 Prompt for AI Agents
In components/leonardo_ai/actions/generate-motion/generate-motion.mjs around
line 21, the code falls back to a non-existent top-level userInfo.id; change the
extraction to rely solely on userInfo.user_details?.[0]?.user?.id and add
explicit handling when that value is missing (throw or return a clear error/log
and avoid proceeding), so the function either uses the nested id or fails fast
with a descriptive message when user id is not present.


// Get generations with pagination
const offset = prevContext?.offset || 0;
const limit = 20;

const generations = await this.app.getGenerationsByUserId({
$: this,
userId,
offset,
limit,
});

// Extract image IDs from generated_images array
const options = [];
if (generations.generations) {
for (const generation of generations.generations) {
if (generation.generated_images) {
for (const image of generation.generated_images) {
options.push({
label: `Image ${image.id} (Generation ${generation.id})`,
value: image.id,
});
}
}
}
}

// Check if there are more pages
const hasMore = generations.generations && generations.generations.length === limit;
const nextOffset = hasMore
? offset + limit
: null;

return {
options,
context: nextOffset
? {
offset: nextOffset,
}
: {},
};
},
},
motionStrength: {
type: "integer",
label: "Motion Strength",
description: "The motion strength for the video generation.",
optional: true,
},
isPublic: {
type: "boolean",
label: "Is Public",
description: "Whether the generation is public or not.",
optional: true,
},
isInitImage: {
type: "boolean",
label: "Is Init Image",
description: "Whether the image being used is an init image uploaded by the user.",
optional: true,
},
isVariation: {
type: "boolean",
label: "Is Variation",
description: "Whether the image being used is a variation image.",
optional: true,
},
},
async run({ $ }) {
const {
imageId,
motionStrength,
isPublic,
isInitImage,
isVariation,
} = this;

const data = {
imageId,
motionStrength,
isPublic,
isInitImage,
isVariation,
};

const response = await this.app._makeRequest({
$,
method: "POST",
path: "/generations-motion-svd",
data,
});

$.export("$summary", `Successfully generated motion from image ID: ${imageId}`);
return response;
},
};
44 changes: 44 additions & 0 deletions components/leonardo_ai/actions/unzoom-image/unzoom-image.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import app from "../../leonardo_ai.app.mjs";

export default {
key: "leonardo_ai-unzoom-image",
name: "Unzoom Image",
description: "Creates an unzoom variation for a generated or variation image using Leonardo AI's unzoom API. [See the documentation](https://docs.leonardo.ai/reference/createvariationunzoom)",
version: "0.0.1",
type: "action",
props: {
app,
imageId: {
type: "string",
label: "Image ID",
description: "The ID of the image to create an unzoom variation for. This should be a previously generated or variation image ID.",
},
isVariation: {
type: "boolean",
label: "Is Variation",
description: "Whether the image is a variation image.",
default: false,
},
},
async run({ $ }) {
const {
imageId,
isVariation,
} = this;

const data = {
id: imageId,
isVariation,
};

const response = await this.app._makeRequest({
$,
method: "POST",
path: "/variations/unzoom",
data,
});

$.export("$summary", `Successfully created unzoom variation for image ID: ${imageId}`);
return response;
},
};
96 changes: 96 additions & 0 deletions components/leonardo_ai/actions/upload-image/upload-image.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
import app from "../../leonardo_ai.app.mjs";
import FormData from "form-data";
import { getFileStreamAndMetadata } from "@pipedream/platform";

export default {
key: "leonardo_ai-upload-image",
name: "Upload Image",
description: "Uploads a new image to Leonardo AI for use in generations and variations. [See the documentation](https://docs.leonardo.ai/docs/how-to-upload-an-image-using-a-presigned-url)",
version: "0.0.1",
type: "action",
props: {
app,
extension: {
type: "string",
label: "File Extension",
description: "The file extension of the image to upload.",
options: [
{
label: "PNG",
value: "png",
},
{
label: "JPG",
value: "jpg",
},
{
label: "JPEG",
value: "jpeg",
},
{
label: "WebP",
value: "webp",
},
],
},
filePath: {
type: "string",
label: "File Path or URL",
description: "The file to upload. Provide either a file URL or a path to a file in the `/tmp` directory (for example, `/tmp/myImage.png`)",
},
syncDir: {
type: "dir",
accessMode: "read",
sync: true,
optional: true,
},
},
async run({ $ }) {
const {
extension,
filePath,
} = this;
// Get file stream from URL or /tmp based path
const {
stream,
metadata,
} = await getFileStreamAndMetadata(filePath);

// Step 1: Get the presigned URL, upload fields appended to formData
const uploadResponse = await this.app.getUploadInitImage({
$,
extension,
});

const { uploadInitImage } = uploadResponse;
const fields = JSON.parse(uploadInitImage.fields);
const formData = new FormData();

//Important: Order of fields is sanctioned by Leonardo AI API. Fields go first, then the file
for (const [
label,
value,
] of Object.entries(fields)) {
formData.append(label, value.toString());
}
formData.append("file", stream, {
contentType: metadata.contentType,
knownLength: metadata.size,
filename: metadata.name,
});
const uploadUrl = uploadInitImage.url;

// Step 2: Upload the file to the presigned URL
const uploadResult = await this.app.uploadFileToPresignedUrl({
$,
url: uploadUrl,
formData,
});

$.export("$summary", `Successfully uploaded image: ${metadata.name || filePath}`);
return {
uploadInitImage,
uploadResult,
};
},
};
Loading
Loading