Skip to content

Commit 5a605a0

Browse files
luancazarinemichelle0927coderabbitai[bot]
authored
New Components - alttextify (#17029)
* alttextify init * [Components] alttextify #16964 Sources - New Image Processed Actions - Submit Image - Submit Image From URL - Get AltText By Asset ID - Get AltText By Job ID - Delete Image * pnpm update * some adjusts * Update components/alttextify/actions/submit-image/submit-image.mjs Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * some adjusts --------- Co-authored-by: michelle0927 <[email protected]> Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com>
1 parent 34a9c2b commit 5a605a0

File tree

9 files changed

+370
-7
lines changed

9 files changed

+370
-7
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
import alttextify from "../../alttextify.app.mjs";
2+
3+
export default {
4+
key: "alttextify-delete-image",
5+
name: "Delete Image Alt Text",
6+
description: "Delete the generated alt text for a specific image using the asset ID. [See the documentation](https://apidoc.alttextify.net/#api-Image-DeleteImage)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
alttextify,
11+
assetId: {
12+
propDefinition: [
13+
alttextify,
14+
"assetId",
15+
],
16+
description: "The ID of the asset for retrieving or deleting alt text.",
17+
},
18+
},
19+
async run({ $ }) {
20+
const response = await this.alttextify.deleteAltTextByAssetId({
21+
$,
22+
assetId: this.assetId,
23+
});
24+
25+
$.export("$summary", `Successfully deleted alt text for asset ID: ${this.assetId}`);
26+
return response;
27+
},
28+
};
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
import alttextify from "../../alttextify.app.mjs";
2+
3+
export default {
4+
key: "alttextify-get-alttext-by-asset-id",
5+
name: "Retrieve Alt Text by Asset ID",
6+
description: "Retrieve alt text for a previously submitted image using the asset ID. [See the documentation](https://apidoc.alttextify.net/#api-Image-GetImageByAssetID)",
7+
version: "0.0.1",
8+
type: "action",
9+
props: {
10+
alttextify,
11+
assetId: {
12+
propDefinition: [
13+
alttextify,
14+
"assetId",
15+
],
16+
},
17+
},
18+
async run({ $ }) {
19+
const response = await this.alttextify.retrieveAltTextByAssetId({
20+
$,
21+
assetId: this.assetId,
22+
});
23+
$.export("$summary", `Successfully retrieved alt text by Asset ID: ${this.assetId}`);
24+
25+
return response;
26+
},
27+
};
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
import { getFileStreamAndMetadata } from "@pipedream/platform";
2+
import alttextify from "../../alttextify.app.mjs";
3+
4+
export default {
5+
key: "alttextify-submit-image",
6+
name: "Submit Image to Alttextify",
7+
description: "Upload or submit an image to Alttextify for alt text generation. [See the documentation](https://apidoc.alttextify.net/#api-Image-UploadRawImage)",
8+
version: "0.0.1",
9+
type: "action",
10+
props: {
11+
alttextify,
12+
alert: {
13+
type: "alert",
14+
alertType: "info",
15+
content: "Supported formats: JPEG, PNG, GIF, WEBP, BMP\nMaximum file size: 16 MB\nMinimum dimensions: 50 x 50 (smaller images may not be able to generate alt text).",
16+
},
17+
async: {
18+
type: "boolean",
19+
label: "Async",
20+
description: "Whether to add the image in the background or immediately (synchronously). If async is set to true, the API response will always be successful with an empty response body.",
21+
default: false,
22+
},
23+
image: {
24+
type: "string",
25+
label: "Image",
26+
description: "The URL of the file or path to the file saved to the `/tmp` directory. [See the documentation on working with files](https://pipedream.com/docs/code/nodejs/working-with-files/#writing-a-file-to-tmp)",
27+
},
28+
lang: {
29+
type: "string",
30+
label: "Language",
31+
description: "The language for the alt text. Supported language codes are accepted. If not provided, the account's default language is used.",
32+
default: "en",
33+
},
34+
maxChars: {
35+
type: "integer",
36+
label: "Max Characters",
37+
description: "Maximum length of the generated alt text.",
38+
},
39+
assetId: {
40+
type: "string",
41+
label: "Asset ID",
42+
description: "The unique identifier for the asset.",
43+
optional: true,
44+
},
45+
keywords: {
46+
type: "string[]",
47+
label: "Keywords",
48+
description: "List of keywords/phrases for SEO-optimized alt text. Only one or two will be used per alt text, but all are considered. Keywords must be in English, even for alt text in other languages.",
49+
optional: true,
50+
},
51+
ecommerceRunOCR: {
52+
type: "boolean",
53+
label: "Ecommerce Run OCR",
54+
description: "Flag to indicate if OCR should be run on the product.",
55+
},
56+
ecommerceProductName: {
57+
type: "string",
58+
label: "Ecommerce Product Name",
59+
description: "The name of the product in the image.",
60+
optional: true,
61+
},
62+
ecommerceProductBrand: {
63+
type: "string",
64+
label: "Ecommerce Product Brand",
65+
description: "The brand of the product in the image.",
66+
optional: true,
67+
},
68+
ecommerceProductColor: {
69+
type: "string",
70+
label: "Ecommerce Product Color",
71+
description: "The color of the product in the image.",
72+
optional: true,
73+
},
74+
ecommerceProductSize: {
75+
type: "string",
76+
label: "Ecommerce Product Size",
77+
description: "The size of the product in the image.",
78+
optional: true,
79+
},
80+
},
81+
methods: {
82+
async streamToBase64(stream) {
83+
return new Promise((resolve, reject) => {
84+
const chunks = [];
85+
86+
stream.on("data", (chunk) => {
87+
chunks.push(chunk);
88+
});
89+
90+
stream.on("end", () => {
91+
const buffer = Buffer.concat(chunks);
92+
resolve(buffer.toString("base64"));
93+
});
94+
95+
stream.on("error", (err) => {
96+
reject(err);
97+
});
98+
});
99+
},
100+
},
101+
async run({ $ }) {
102+
const {
103+
stream, metadata,
104+
} = await getFileStreamAndMetadata(this.image);
105+
const base64String = await this.streamToBase64(stream);
106+
107+
const response = await this.alttextify.uploadImage({
108+
$,
109+
data: {
110+
async: this.async,
111+
image: `data:${metadata.contentType};base64,${base64String}`,
112+
lang: this.lang,
113+
maxChars: this.maxChars,
114+
assetId: this.assetId,
115+
keywords: this.keywords,
116+
ecommerce: {
117+
run_ocr: this.ecommerceRunOCR,
118+
product: {
119+
name: this.ecommerceProductName,
120+
brand: this.ecommerceProductBrand,
121+
color: this.ecommerceProductColor,
122+
size: this.ecommerceProductSize,
123+
},
124+
},
125+
},
126+
127+
});
128+
129+
$.export("$summary", `Successfully submitted image to Alttextify for alt text generation with Asset ID: ${response.asset_id}`);
130+
return response;
131+
},
132+
};
Lines changed: 93 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,100 @@
1+
import { axios } from "@pipedream/platform";
2+
13
export default {
24
type: "app",
35
app: "alttextify",
4-
propDefinitions: {},
6+
propDefinitions: {
7+
assetId: {
8+
type: "string",
9+
label: "Asset ID",
10+
description: "The ID of the asset for retrieving alt text.",
11+
async options({ page }) {
12+
const data = await this.listAltTexts({
13+
params: {
14+
page: page + 1,
15+
},
16+
});
17+
18+
return data.map(({
19+
asset_id: value, alt_text: label,
20+
}) => ({
21+
label,
22+
value,
23+
}));
24+
},
25+
},
26+
},
527
methods: {
6-
// this.$auth contains connected account data
7-
authKeys() {
8-
console.log(Object.keys(this.$auth));
28+
_baseUrl() {
29+
return "https://api.alttextify.net/api/v1";
30+
},
31+
_headers() {
32+
return {
33+
"x-api-key": `${this.$auth.api_key}`,
34+
};
35+
},
36+
_makeRequest({
37+
$ = this, path, ...opts
38+
}) {
39+
return axios($, {
40+
url: this._baseUrl() + path,
41+
headers: this._headers(),
42+
...opts,
43+
});
44+
},
45+
uploadImage(opts = {}) {
46+
return this._makeRequest({
47+
method: "POST",
48+
path: "/image/raw",
49+
...opts,
50+
});
51+
},
52+
deleteAltTextByAssetId({
53+
assetId, ...opts
54+
}) {
55+
return this._makeRequest({
56+
method: "DELETE",
57+
path: `/image/${assetId}`,
58+
...opts,
59+
});
60+
},
61+
retrieveAltTextByAssetId({
62+
assetId, ...opts
63+
}) {
64+
return this._makeRequest({
65+
path: `/image/${assetId}`,
66+
...opts,
67+
});
68+
},
69+
listAltTexts({ ...opts }) {
70+
return this._makeRequest({
71+
path: "/image",
72+
...opts,
73+
});
74+
},
75+
async *paginate({
76+
fn, params = {}, maxResults = null, ...opts
77+
}) {
78+
let hasMore = false;
79+
let count = 0;
80+
let page = 0;
81+
82+
do {
83+
params.page = ++page;
84+
const data = await fn({
85+
params,
86+
...opts,
87+
});
88+
for (const d of data) {
89+
yield d;
90+
91+
if (maxResults && ++count === maxResults) {
92+
return count;
93+
}
94+
}
95+
96+
hasMore = data.length;
97+
} while (hasMore);
998
},
1099
},
11100
};
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export const checkTmp = (filename) => {
2+
if (!filename.startsWith("/tmp")) {
3+
return `/tmp/${filename}`;
4+
}
5+
return filename;
6+
};

components/alttextify/package.json

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@pipedream/alttextify",
3-
"version": "0.0.1",
3+
"version": "0.1.0",
44
"description": "Pipedream AltTextify Components",
55
"main": "alttextify.app.mjs",
66
"keywords": [
@@ -11,5 +11,8 @@
1111
"author": "Pipedream <[email protected]> (https://pipedream.com/)",
1212
"publishConfig": {
1313
"access": "public"
14+
},
15+
"dependencies": {
16+
"@pipedream/platform": "^3.0.3"
1417
}
15-
}
18+
}
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
import { DEFAULT_POLLING_SOURCE_TIMER_INTERVAL } from "@pipedream/platform";
2+
import alttextify from "../../alttextify.app.mjs";
3+
import sampleEmit from "./test-event.mjs";
4+
5+
export default {
6+
key: "alttextify-new-alttext-generated",
7+
name: "New Alt Text Generated",
8+
description: "Emit new event when new alt text is generated for an image. [See the documentation](https://apidoc.alttextify.net/#api-Image-GetImages)",
9+
version: "0.0.1",
10+
type: "source",
11+
dedupe: "unique",
12+
props: {
13+
alttextify,
14+
db: "$.service.db",
15+
timer: {
16+
type: "$.interface.timer",
17+
default: {
18+
intervalSeconds: DEFAULT_POLLING_SOURCE_TIMER_INTERVAL,
19+
},
20+
},
21+
},
22+
methods: {
23+
_getLastDate() {
24+
return this.db.get("lastDate") || 0;
25+
},
26+
_setLastDate(lastDate) {
27+
this.db.set("lastDate", lastDate);
28+
},
29+
async emitEvent(maxResults = false) {
30+
const lastDate = this._getLastDate();
31+
32+
const response = this.alttextify.paginate({
33+
fn: this.alttextify.listAltTexts,
34+
});
35+
36+
let responseArray = [];
37+
for await (const item of response) {
38+
if (Date.parse(item.created_at) <= lastDate) break;
39+
responseArray.push(item);
40+
}
41+
42+
if (responseArray.length) {
43+
if (maxResults && (responseArray.length > maxResults)) {
44+
responseArray.length = maxResults;
45+
}
46+
this._setLastDate(Date.parse(responseArray[0].created_at));
47+
}
48+
49+
for (const item of responseArray.reverse()) {
50+
this.$emit(item, {
51+
id: item.asset_id,
52+
summary: `New alt text generated for asset ${item.asset_id}`,
53+
ts: Date.parse(item.created_at),
54+
});
55+
}
56+
},
57+
},
58+
hooks: {
59+
async deploy() {
60+
await this.emitEvent(25);
61+
},
62+
},
63+
async run() {
64+
await this.emitEvent();
65+
},
66+
sampleEmit,
67+
};
Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
export default {
2+
"asset_id": "pQ0-oLcLT",
3+
"image_source": "06e1acf1-2312-450c-87f1-d4a1ee0547be.webp",
4+
"alt_text": ["A sprawling metropolis transforms into a kaleidoscope of vibrant lights at dusk, as skyscrapers stand sentinel amidst a symphony of urban activity."],
5+
"tag": null,
6+
"created_at": "2025-03-21T08:39:29.815Z"
7+
}

0 commit comments

Comments
 (0)