alt-images-ai 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md ADDED
@@ -0,0 +1,186 @@
1
+ # alt-images-ai
2
+
3
+ [![npm version](https://img.shields.io/npm/v/alt-images-ai)](https://www.npmjs.com/package/alt-images-ai)
4
+ [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://opensource.org/licenses/MIT)
5
+ [![Node.js](https://img.shields.io/badge/node-%3E%3D18-brightgreen)](https://nodejs.org)
6
+
7
+ Generate accessible alt text for images using AI. Supports **OpenAI** (GPT-4o) and **Google Gemini**.
8
+
9
+ Zero runtime dependencies — uses only Node.js built-in modules. Full TypeScript support.
10
+
11
+ ## Features
12
+
13
+ - **Multiple AI providers** — OpenAI and Google Gemini out of the box
14
+ - **Flexible image input** — URLs, local files, base64 data URIs, or Buffers
15
+ - **HTML processing** — Automatically scan and add `alt` attributes to `<img>` tags
16
+ - **Batch processing** — Generate alt text for multiple images in parallel
17
+ - **Multi-language** — Generate descriptions in any language
18
+ - **Configurable** — Custom prompts, max length, model selection
19
+ - **TypeScript** — Full type definitions included
20
+ - **Lightweight** — Zero runtime dependencies
21
+
22
+ ## Installation
23
+
24
+ ```bash
25
+ npm install alt-images-ai
26
+ ```
27
+
28
+ ## Quick Start
29
+
30
+ ```typescript
31
+ import { AltImageClient } from "alt-images-ai";
32
+
33
+ const client = new AltImageClient({
34
+ provider: "openai", // or "gemini"
35
+ apiKey: "your-api-key",
36
+ });
37
+
38
+ const alt = await client.generateAlt("https://example.com/photo.jpg");
39
+ console.log(alt);
40
+ // "Golden retriever running across a sunlit meadow"
41
+ ```
42
+
43
+ ## Usage
44
+
45
+ ### Create a Client
46
+
47
+ ```typescript
48
+ import { AltImageClient } from "alt-images-ai";
49
+
50
+ const client = new AltImageClient({
51
+ provider: "openai", // "openai" | "gemini"
52
+ apiKey: "sk-...",
53
+ language: "es", // optional — default: "en"
54
+ maxLength: 125, // optional — max characters for alt text
55
+ model: "gpt-4o", // optional — override default model
56
+ customPrompt: "...", // optional — fully custom AI prompt
57
+ });
58
+ ```
59
+
60
+ ### Generate Alt Text for a Single Image
61
+
62
+ ```typescript
63
+ // From a URL
64
+ const alt = await client.generateAlt("https://example.com/photo.jpg");
65
+
66
+ // From a local file
67
+ const alt = await client.generateAlt("./images/hero.png");
68
+
69
+ // From a Buffer
70
+ import fs from "fs";
71
+ const buffer = fs.readFileSync("photo.jpg");
72
+ const alt = await client.generateAlt(buffer);
73
+
74
+ // With per-call overrides
75
+ const alt = await client.generateAlt("photo.jpg", {
76
+ language: "fr",
77
+ maxLength: 100,
78
+ });
79
+ ```
80
+
81
+ ### Batch Processing
82
+
83
+ Process multiple images in parallel. Failed images are collected in the `errors` array without stopping the rest.
84
+
85
+ ```typescript
86
+ const { results, errors } = await client.generateAltBatch([
87
+ "https://example.com/img1.jpg",
88
+ "https://example.com/img2.jpg",
89
+ "./local-image.png",
90
+ ]);
91
+
92
+ for (const { src, alt } of results) {
93
+ console.log(`${src} → ${alt}`);
94
+ }
95
+
96
+ for (const { src, error } of errors) {
97
+ console.error(`${src} failed: ${error.message}`);
98
+ }
99
+ ```
100
+
101
+ ### Process HTML
102
+
103
+ Scan an HTML string, find `<img>` tags missing alt text, and automatically generate it:
104
+
105
+ ```typescript
106
+ const html = `
107
+ <div>
108
+ <img src="hero.jpg">
109
+ <img src="team.jpg" alt="">
110
+ <img src="logo.png" alt="Company logo">
111
+ </div>
112
+ `;
113
+
114
+ const result = await client.processHTML(html);
115
+ // hero.jpg → gets AI-generated alt
116
+ // team.jpg → gets AI-generated alt (empty alt counts as missing)
117
+ // logo.png → left unchanged (already has alt text)
118
+ ```
119
+
120
+ #### HTML Processing Options
121
+
122
+ ```typescript
123
+ const result = await client.processHTML(html, {
124
+ onlyMissing: true, // only process images without alt (default: true)
125
+ overrideExisting: false, // replace existing alt values (default: false)
126
+ language: "es", // override language for this call
127
+ });
128
+ ```
129
+
130
+ ### One-liner
131
+
132
+ Generate alt text without creating a client first:
133
+
134
+ ```typescript
135
+ import { generateAlt } from "alt-images-ai";
136
+
137
+ const alt = await generateAlt("photo.jpg", {
138
+ provider: "openai",
139
+ apiKey: "sk-...",
140
+ });
141
+ ```
142
+
143
+ ## Providers
144
+
145
+ | Provider | Default Model | Documentation |
146
+ | -------- | ---------------- | -------------------------------------------------------------- |
147
+ | `openai` | `gpt-4o-mini` | [OpenAI Vision](https://platform.openai.com/docs/guides/vision) |
148
+ | `gemini` | `gemini-2.0-flash` | [Google Gemini](https://ai.google.dev/docs) |
149
+
150
+ ## API Reference
151
+
152
+ ### `new AltImageClient(options)`
153
+
154
+ | Option | Type | Required | Default | Description |
155
+ | -------------- | ------------------------ | -------- | ---------------- | ---------------------------------- |
156
+ | `provider` | `"openai" \| "gemini"` | Yes | — | AI provider to use |
157
+ | `apiKey` | `string` | Yes | — | API key for the provider |
158
+ | `model` | `string` | No | Provider default | Override the default model |
159
+ | `language` | `string` | No | `"en"` | Language for generated alt text |
160
+ | `maxLength` | `number` | No | — | Maximum character length |
161
+ | `customPrompt` | `string` | No | — | Fully custom prompt for the AI |
162
+
163
+ ### `client.generateAlt(image, options?): Promise<string>`
164
+
165
+ Generate alt text for a single image. Accepts a URL, file path, data URI, or Buffer.
166
+
167
+ ### `client.generateAltBatch(images, options?): Promise<BatchResult>`
168
+
169
+ Generate alt text for multiple images in parallel. Returns `{ results, errors }`.
170
+
171
+ ### `client.processHTML(html, options?): Promise<string>`
172
+
173
+ Parse HTML, find `<img>` tags, and add AI-generated alt attributes. Returns the modified HTML string.
174
+
175
+ ### `generateAlt(image, options): Promise<string>`
176
+
177
+ Standalone function that creates a client internally — useful for one-off calls.
178
+
179
+ ## Requirements
180
+
181
+ - Node.js >= 18
182
+ - An API key from [OpenAI](https://platform.openai.com/api-keys) or [Google AI Studio](https://aistudio.google.com/apikey)
183
+
184
+ ## License
185
+
186
+ MIT
@@ -0,0 +1,33 @@
1
+ import type { AltImageClientOptions, BatchResult, GenerateAltOptions, ProcessHTMLOptions } from "./types";
2
+ export declare class AltImageClient {
3
+ private provider;
4
+ private language;
5
+ private maxLength;
6
+ private customPrompt;
7
+ constructor(options: AltImageClientOptions);
8
+ /**
9
+ * Generate alt text for a single image.
10
+ *
11
+ * @param image - URL, file path, data URI, or Buffer
12
+ * @param options - Override default language, maxLength, or prompt
13
+ * @returns The generated alt text string
14
+ */
15
+ generateAlt(image: string | Buffer, options?: GenerateAltOptions): Promise<string>;
16
+ /**
17
+ * Generate alt text for multiple images in parallel.
18
+ *
19
+ * @param images - Array of URLs, file paths, data URIs, or Buffers
20
+ * @param options - Override default language, maxLength, or prompt
21
+ * @returns Object with results array and errors array
22
+ */
23
+ generateAltBatch(images: (string | Buffer)[], options?: GenerateAltOptions): Promise<BatchResult>;
24
+ /**
25
+ * Process an HTML string: find <img> tags and generate alt text for them.
26
+ *
27
+ * @param html - HTML string to process
28
+ * @param options - Configure which images to process and generation options
29
+ * @returns The HTML string with alt attributes added/updated
30
+ */
31
+ processHTML(html: string, options?: ProcessHTMLOptions): Promise<string>;
32
+ private buildPrompt;
33
+ }
package/dist/client.js ADDED
@@ -0,0 +1,117 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.AltImageClient = void 0;
4
+ const openai_1 = require("./providers/openai");
5
+ const gemini_1 = require("./providers/gemini");
6
+ const image_1 = require("./utils/image");
7
+ const html_1 = require("./utils/html");
8
+ class AltImageClient {
9
+ constructor(options) {
10
+ if (!options.apiKey) {
11
+ throw new Error("apiKey is required");
12
+ }
13
+ this.language = options.language ?? "en";
14
+ this.maxLength = options.maxLength;
15
+ this.customPrompt = options.customPrompt;
16
+ switch (options.provider) {
17
+ case "openai":
18
+ this.provider = new openai_1.OpenAIProvider(options.apiKey, options.model);
19
+ break;
20
+ case "gemini":
21
+ this.provider = new gemini_1.GeminiProvider(options.apiKey, options.model);
22
+ break;
23
+ default:
24
+ throw new Error(`Unsupported provider: ${options.provider}. Use "openai" or "gemini".`);
25
+ }
26
+ }
27
+ /**
28
+ * Generate alt text for a single image.
29
+ *
30
+ * @param image - URL, file path, data URI, or Buffer
31
+ * @param options - Override default language, maxLength, or prompt
32
+ * @returns The generated alt text string
33
+ */
34
+ async generateAlt(image, options) {
35
+ const { base64, mimeType } = await (0, image_1.resolveImage)(image);
36
+ const prompt = this.buildPrompt(options);
37
+ let alt = await this.provider.generateAlt(base64, mimeType, prompt);
38
+ const maxLen = options?.maxLength ?? this.maxLength;
39
+ if (maxLen && alt.length > maxLen) {
40
+ alt = alt.substring(0, maxLen - 3).trimEnd() + "...";
41
+ }
42
+ return alt;
43
+ }
44
+ /**
45
+ * Generate alt text for multiple images in parallel.
46
+ *
47
+ * @param images - Array of URLs, file paths, data URIs, or Buffers
48
+ * @param options - Override default language, maxLength, or prompt
49
+ * @returns Object with results array and errors array
50
+ */
51
+ async generateAltBatch(images, options) {
52
+ const results = [];
53
+ const errors = [];
54
+ const promises = images.map(async (image) => {
55
+ const src = Buffer.isBuffer(image) ? "[Buffer]" : image;
56
+ try {
57
+ const alt = await this.generateAlt(image, options);
58
+ results.push({ alt, src });
59
+ }
60
+ catch (err) {
61
+ errors.push({
62
+ src,
63
+ error: err instanceof Error ? err : new Error(String(err)),
64
+ });
65
+ }
66
+ });
67
+ await Promise.all(promises);
68
+ return { results, errors };
69
+ }
70
+ /**
71
+ * Process an HTML string: find <img> tags and generate alt text for them.
72
+ *
73
+ * @param html - HTML string to process
74
+ * @param options - Configure which images to process and generation options
75
+ * @returns The HTML string with alt attributes added/updated
76
+ */
77
+ async processHTML(html, options) {
78
+ const onlyMissing = options?.onlyMissing ?? true;
79
+ const overrideExisting = options?.overrideExisting ?? false;
80
+ const imgTags = (0, html_1.extractImgTags)(html);
81
+ let result = html;
82
+ for (const tag of imgTags) {
83
+ const shouldProcess = !tag.hasAlt || (overrideExisting && !onlyMissing) || (tag.hasAlt && tag.alt === "");
84
+ if (!shouldProcess && onlyMissing)
85
+ continue;
86
+ if (tag.hasAlt && !overrideExisting && tag.alt !== "")
87
+ continue;
88
+ try {
89
+ const alt = await this.generateAlt(tag.src, options);
90
+ result = (0, html_1.setImgAlt)(result, tag, alt);
91
+ }
92
+ catch {
93
+ // Skip images that fail — keep existing HTML intact
94
+ }
95
+ }
96
+ return result;
97
+ }
98
+ buildPrompt(options) {
99
+ const custom = options?.customPrompt ?? this.customPrompt;
100
+ if (custom)
101
+ return custom;
102
+ const lang = options?.language ?? this.language;
103
+ const maxLen = options?.maxLength ?? this.maxLength;
104
+ let prompt = "Generate a concise, descriptive alt text for this image. " +
105
+ "The alt text should be useful for screen readers and accessibility. " +
106
+ "Describe the key visual content without starting with 'Image of' or 'Picture of'. " +
107
+ "Return ONLY the alt text, no quotes, no explanation.";
108
+ if (lang !== "en") {
109
+ prompt += ` Write the alt text in ${lang}.`;
110
+ }
111
+ if (maxLen) {
112
+ prompt += ` Keep it under ${maxLen} characters.`;
113
+ }
114
+ return prompt;
115
+ }
116
+ }
117
+ exports.AltImageClient = AltImageClient;
@@ -0,0 +1,13 @@
1
+ export { AltImageClient } from "./client";
2
+ export type { Provider, ImageInput, AltImageClientOptions, GenerateAltOptions, ProcessHTMLOptions, AltResult, BatchResult, BatchError, AIProvider, } from "./types";
3
+ /**
4
+ * Convenience function: create a client and generate alt text in one call.
5
+ */
6
+ export declare function generateAlt(image: string | Buffer, options: {
7
+ provider: "openai" | "gemini";
8
+ apiKey: string;
9
+ model?: string;
10
+ language?: string;
11
+ maxLength?: number;
12
+ customPrompt?: string;
13
+ }): Promise<string>;
package/dist/index.js ADDED
@@ -0,0 +1,47 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.AltImageClient = void 0;
37
+ exports.generateAlt = generateAlt;
38
+ var client_1 = require("./client");
39
+ Object.defineProperty(exports, "AltImageClient", { enumerable: true, get: function () { return client_1.AltImageClient; } });
40
+ /**
41
+ * Convenience function: create a client and generate alt text in one call.
42
+ */
43
+ async function generateAlt(image, options) {
44
+ const { AltImageClient: Client } = await Promise.resolve().then(() => __importStar(require("./client")));
45
+ const client = new Client(options);
46
+ return client.generateAlt(image, options);
47
+ }
@@ -0,0 +1,8 @@
1
+ import type { AIProvider } from "../types";
2
+ export declare class GeminiProvider implements AIProvider {
3
+ private apiKey;
4
+ private model;
5
+ constructor(apiKey: string, model?: string);
6
+ generateAlt(imageBase64: string, mimeType: string, prompt: string): Promise<string>;
7
+ private request;
8
+ }
@@ -0,0 +1,95 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.GeminiProvider = void 0;
37
+ const https = __importStar(require("https"));
38
+ class GeminiProvider {
39
+ constructor(apiKey, model) {
40
+ this.apiKey = apiKey;
41
+ this.model = model ?? "gemini-2.0-flash";
42
+ }
43
+ async generateAlt(imageBase64, mimeType, prompt) {
44
+ const body = JSON.stringify({
45
+ contents: [
46
+ {
47
+ parts: [
48
+ { text: prompt },
49
+ {
50
+ inline_data: {
51
+ mime_type: mimeType,
52
+ data: imageBase64,
53
+ },
54
+ },
55
+ ],
56
+ },
57
+ ],
58
+ generationConfig: {
59
+ maxOutputTokens: 300,
60
+ },
61
+ });
62
+ const response = await this.request(body);
63
+ const data = JSON.parse(response);
64
+ if (data.error) {
65
+ throw new Error(`Gemini API error: ${data.error.message}`);
66
+ }
67
+ const text = data.candidates?.[0]?.content?.parts?.[0]?.text;
68
+ if (!text) {
69
+ throw new Error("Gemini returned an empty response");
70
+ }
71
+ return text.trim();
72
+ }
73
+ request(body) {
74
+ return new Promise((resolve, reject) => {
75
+ const req = https.request({
76
+ hostname: "generativelanguage.googleapis.com",
77
+ path: `/v1beta/models/${this.model}:generateContent?key=${this.apiKey}`,
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ },
82
+ }, (res) => {
83
+ let data = "";
84
+ res.on("data", (chunk) => {
85
+ data += chunk.toString();
86
+ });
87
+ res.on("end", () => resolve(data));
88
+ });
89
+ req.on("error", reject);
90
+ req.write(body);
91
+ req.end();
92
+ });
93
+ }
94
+ }
95
+ exports.GeminiProvider = GeminiProvider;
@@ -0,0 +1,2 @@
1
+ export { OpenAIProvider } from "./openai";
2
+ export { GeminiProvider } from "./gemini";
@@ -0,0 +1,7 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.GeminiProvider = exports.OpenAIProvider = void 0;
4
+ var openai_1 = require("./openai");
5
+ Object.defineProperty(exports, "OpenAIProvider", { enumerable: true, get: function () { return openai_1.OpenAIProvider; } });
6
+ var gemini_1 = require("./gemini");
7
+ Object.defineProperty(exports, "GeminiProvider", { enumerable: true, get: function () { return gemini_1.GeminiProvider; } });
@@ -0,0 +1,8 @@
1
+ import type { AIProvider } from "../types";
2
+ export declare class OpenAIProvider implements AIProvider {
3
+ private apiKey;
4
+ private model;
5
+ constructor(apiKey: string, model?: string);
6
+ generateAlt(imageBase64: string, mimeType: string, prompt: string): Promise<string>;
7
+ private request;
8
+ }
@@ -0,0 +1,96 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.OpenAIProvider = void 0;
37
+ const https = __importStar(require("https"));
38
+ class OpenAIProvider {
39
+ constructor(apiKey, model) {
40
+ this.apiKey = apiKey;
41
+ this.model = model ?? "gpt-4o-mini";
42
+ }
43
+ async generateAlt(imageBase64, mimeType, prompt) {
44
+ const body = JSON.stringify({
45
+ model: this.model,
46
+ messages: [
47
+ {
48
+ role: "user",
49
+ content: [
50
+ { type: "text", text: prompt },
51
+ {
52
+ type: "image_url",
53
+ image_url: {
54
+ url: `data:${mimeType};base64,${imageBase64}`,
55
+ },
56
+ },
57
+ ],
58
+ },
59
+ ],
60
+ max_tokens: 300,
61
+ });
62
+ const response = await this.request(body);
63
+ const data = JSON.parse(response);
64
+ if (data.error) {
65
+ throw new Error(`OpenAI API error: ${data.error.message}`);
66
+ }
67
+ const text = data.choices?.[0]?.message?.content;
68
+ if (!text) {
69
+ throw new Error("OpenAI returned an empty response");
70
+ }
71
+ return text.trim();
72
+ }
73
+ request(body) {
74
+ return new Promise((resolve, reject) => {
75
+ const req = https.request({
76
+ hostname: "api.openai.com",
77
+ path: "/v1/chat/completions",
78
+ method: "POST",
79
+ headers: {
80
+ "Content-Type": "application/json",
81
+ Authorization: `Bearer ${this.apiKey}`,
82
+ },
83
+ }, (res) => {
84
+ let data = "";
85
+ res.on("data", (chunk) => {
86
+ data += chunk.toString();
87
+ });
88
+ res.on("end", () => resolve(data));
89
+ });
90
+ req.on("error", reject);
91
+ req.write(body);
92
+ req.end();
93
+ });
94
+ }
95
+ }
96
+ exports.OpenAIProvider = OpenAIProvider;
@@ -0,0 +1,61 @@
1
+ /** Supported AI providers */
2
+ export type Provider = "openai" | "gemini";
3
+ /** Image input: URL, local file path, or base64-encoded string */
4
+ export type ImageInput = string | Buffer;
5
+ /** Configuration for creating an AltImageClient */
6
+ export interface AltImageClientOptions {
7
+ /** AI provider to use */
8
+ provider: Provider;
9
+ /** API key for the chosen provider */
10
+ apiKey: string;
11
+ /** Model to use (defaults to provider's recommended vision model) */
12
+ model?: string;
13
+ /** Language for the generated alt text (e.g. "en", "es", "fr") */
14
+ language?: string;
15
+ /** Maximum character length for the alt text */
16
+ maxLength?: number;
17
+ /** Custom prompt to guide the AI generation */
18
+ customPrompt?: string;
19
+ }
20
+ /** Options for a single generateAlt call (overrides client defaults) */
21
+ export interface GenerateAltOptions {
22
+ /** Language for the generated alt text */
23
+ language?: string;
24
+ /** Maximum character length for the alt text */
25
+ maxLength?: number;
26
+ /** Custom prompt to guide the AI generation */
27
+ customPrompt?: string;
28
+ }
29
+ /** Options for HTML processing */
30
+ export interface ProcessHTMLOptions extends GenerateAltOptions {
31
+ /** Only process <img> tags that are missing alt attributes (default: true) */
32
+ onlyMissing?: boolean;
33
+ /** Override existing alt attributes (default: false) */
34
+ overrideExisting?: boolean;
35
+ }
36
+ /** Result of a single alt text generation */
37
+ export interface AltResult {
38
+ /** The generated alt text */
39
+ alt: string;
40
+ /** The image source that was processed */
41
+ src: string;
42
+ }
43
+ /** Result of a batch operation */
44
+ export interface BatchResult {
45
+ /** Successfully generated results */
46
+ results: AltResult[];
47
+ /** Errors encountered during processing */
48
+ errors: BatchError[];
49
+ }
50
+ /** Error from a batch operation */
51
+ export interface BatchError {
52
+ /** The image source that failed */
53
+ src: string;
54
+ /** The error that occurred */
55
+ error: Error;
56
+ }
57
+ /** Internal interface for AI provider implementations */
58
+ export interface AIProvider {
59
+ /** Generate alt text for an image */
60
+ generateAlt(imageBase64: string, mimeType: string, prompt: string): Promise<string>;
61
+ }
package/dist/types.js ADDED
@@ -0,0 +1,2 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
@@ -0,0 +1,18 @@
1
+ export interface ImgTag {
2
+ /** Full match of the <img> tag */
3
+ fullMatch: string;
4
+ /** Value of the src attribute */
5
+ src: string;
6
+ /** Value of the alt attribute (undefined if missing) */
7
+ alt: string | undefined;
8
+ /** Whether the tag has an alt attribute at all */
9
+ hasAlt: boolean;
10
+ }
11
+ /**
12
+ * Extract all <img> tags from an HTML string.
13
+ */
14
+ export declare function extractImgTags(html: string): ImgTag[];
15
+ /**
16
+ * Replace an <img> tag's alt attribute in HTML, or add one if missing.
17
+ */
18
+ export declare function setImgAlt(html: string, imgTag: ImgTag, altText: string): string;
@@ -0,0 +1,53 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.extractImgTags = extractImgTags;
4
+ exports.setImgAlt = setImgAlt;
5
+ const IMG_REGEX = /<img\s[^>]*?>/gi;
6
+ const SRC_REGEX = /src\s*=\s*(?:"([^"]*)"|'([^']*)'|(\S+))/i;
7
+ const ALT_REGEX = /alt\s*=\s*(?:"([^"]*)"|'([^']*)')/i;
8
+ /**
9
+ * Extract all <img> tags from an HTML string.
10
+ */
11
+ function extractImgTags(html) {
12
+ const tags = [];
13
+ let match;
14
+ while ((match = IMG_REGEX.exec(html)) !== null) {
15
+ const fullMatch = match[0];
16
+ const srcMatch = SRC_REGEX.exec(fullMatch);
17
+ const altMatch = ALT_REGEX.exec(fullMatch);
18
+ const src = srcMatch
19
+ ? srcMatch[1] ?? srcMatch[2] ?? srcMatch[3] ?? ""
20
+ : "";
21
+ const hasAlt = altMatch !== null;
22
+ const alt = hasAlt
23
+ ? altMatch[1] ?? altMatch[2] ?? ""
24
+ : undefined;
25
+ if (src) {
26
+ tags.push({ fullMatch, src, alt, hasAlt });
27
+ }
28
+ }
29
+ return tags;
30
+ }
31
+ /**
32
+ * Replace an <img> tag's alt attribute in HTML, or add one if missing.
33
+ */
34
+ function setImgAlt(html, imgTag, altText) {
35
+ const escaped = escapeAttrValue(altText);
36
+ let newTag;
37
+ if (imgTag.hasAlt) {
38
+ // Replace existing alt attribute
39
+ newTag = imgTag.fullMatch.replace(ALT_REGEX, `alt="${escaped}"`);
40
+ }
41
+ else {
42
+ // Add alt attribute after <img
43
+ newTag = imgTag.fullMatch.replace(/^<img/i, `<img alt="${escaped}"`);
44
+ }
45
+ return html.replace(imgTag.fullMatch, newTag);
46
+ }
47
+ function escapeAttrValue(value) {
48
+ return value
49
+ .replace(/&/g, "&amp;")
50
+ .replace(/"/g, "&quot;")
51
+ .replace(/</g, "&lt;")
52
+ .replace(/>/g, "&gt;");
53
+ }
@@ -0,0 +1,8 @@
1
+ export interface ImageData {
2
+ base64: string;
3
+ mimeType: string;
4
+ }
5
+ /**
6
+ * Resolves an image input (URL, file path, base64, or Buffer) to base64 + mimeType.
7
+ */
8
+ export declare function resolveImage(input: string | Buffer): Promise<ImageData>;
@@ -0,0 +1,137 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ Object.defineProperty(exports, "__esModule", { value: true });
36
+ exports.resolveImage = resolveImage;
37
+ const fs = __importStar(require("fs"));
38
+ const https = __importStar(require("https"));
39
+ const http = __importStar(require("http"));
40
+ const path = __importStar(require("path"));
41
+ const MIME_TYPES = {
42
+ ".jpg": "image/jpeg",
43
+ ".jpeg": "image/jpeg",
44
+ ".png": "image/png",
45
+ ".gif": "image/gif",
46
+ ".webp": "image/webp",
47
+ ".bmp": "image/bmp",
48
+ ".svg": "image/svg+xml",
49
+ };
50
+ /**
51
+ * Resolves an image input (URL, file path, base64, or Buffer) to base64 + mimeType.
52
+ */
53
+ async function resolveImage(input) {
54
+ if (Buffer.isBuffer(input)) {
55
+ return {
56
+ base64: input.toString("base64"),
57
+ mimeType: detectMimeFromBuffer(input),
58
+ };
59
+ }
60
+ // Base64 data URI
61
+ if (input.startsWith("data:")) {
62
+ const match = input.match(/^data:(image\/[^;]+);base64,(.+)$/);
63
+ if (!match) {
64
+ throw new Error("Invalid data URI format");
65
+ }
66
+ return { base64: match[2], mimeType: match[1] };
67
+ }
68
+ // URL
69
+ if (input.startsWith("http://") || input.startsWith("https://")) {
70
+ return downloadImage(input);
71
+ }
72
+ // File path
73
+ return readImageFile(input);
74
+ }
75
+ function readImageFile(filePath) {
76
+ if (!fs.existsSync(filePath)) {
77
+ throw new Error(`File not found: ${filePath}`);
78
+ }
79
+ const buffer = fs.readFileSync(filePath);
80
+ const ext = path.extname(filePath).toLowerCase();
81
+ const mimeType = MIME_TYPES[ext] ?? detectMimeFromBuffer(buffer);
82
+ return {
83
+ base64: buffer.toString("base64"),
84
+ mimeType,
85
+ };
86
+ }
87
+ function downloadImage(url) {
88
+ const client = url.startsWith("https://") ? https : http;
89
+ return new Promise((resolve, reject) => {
90
+ client.get(url, (res) => {
91
+ if (res.statusCode &&
92
+ res.statusCode >= 300 &&
93
+ res.statusCode < 400 &&
94
+ res.headers.location) {
95
+ downloadImage(res.headers.location).then(resolve, reject);
96
+ return;
97
+ }
98
+ if (res.statusCode && res.statusCode >= 400) {
99
+ reject(new Error(`Failed to download image: HTTP ${res.statusCode}`));
100
+ return;
101
+ }
102
+ const chunks = [];
103
+ res.on("data", (chunk) => chunks.push(chunk));
104
+ res.on("end", () => {
105
+ const buffer = Buffer.concat(chunks);
106
+ const contentType = res.headers["content-type"] ?? "";
107
+ const mimeType = contentType.startsWith("image/")
108
+ ? contentType.split(";")[0]
109
+ : detectMimeFromBuffer(buffer);
110
+ resolve({
111
+ base64: buffer.toString("base64"),
112
+ mimeType,
113
+ });
114
+ });
115
+ res.on("error", reject);
116
+ }).on("error", reject);
117
+ });
118
+ }
119
+ function detectMimeFromBuffer(buffer) {
120
+ if (buffer[0] === 0xff && buffer[1] === 0xd8)
121
+ return "image/jpeg";
122
+ if (buffer[0] === 0x89 &&
123
+ buffer[1] === 0x50 &&
124
+ buffer[2] === 0x4e &&
125
+ buffer[3] === 0x47)
126
+ return "image/png";
127
+ if (buffer[0] === 0x47 &&
128
+ buffer[1] === 0x49 &&
129
+ buffer[2] === 0x46)
130
+ return "image/gif";
131
+ if (buffer[0] === 0x52 &&
132
+ buffer[1] === 0x49 &&
133
+ buffer[2] === 0x46 &&
134
+ buffer[3] === 0x46)
135
+ return "image/webp";
136
+ return "image/jpeg"; // fallback
137
+ }
@@ -0,0 +1,4 @@
1
+ export { resolveImage } from "./image";
2
+ export type { ImageData } from "./image";
3
+ export { extractImgTags, setImgAlt } from "./html";
4
+ export type { ImgTag } from "./html";
@@ -0,0 +1,8 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.setImgAlt = exports.extractImgTags = exports.resolveImage = void 0;
4
+ var image_1 = require("./image");
5
+ Object.defineProperty(exports, "resolveImage", { enumerable: true, get: function () { return image_1.resolveImage; } });
6
+ var html_1 = require("./html");
7
+ Object.defineProperty(exports, "extractImgTags", { enumerable: true, get: function () { return html_1.extractImgTags; } });
8
+ Object.defineProperty(exports, "setImgAlt", { enumerable: true, get: function () { return html_1.setImgAlt; } });
package/package.json ADDED
@@ -0,0 +1,42 @@
1
+ {
2
+ "name": "alt-images-ai",
3
+ "version": "1.0.0",
4
+ "description": "Generate accessible alt text for images using AI (OpenAI, Google Gemini). Process single images, batch operations, or full HTML documents.",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "files": [
8
+ "dist"
9
+ ],
10
+ "scripts": {
11
+ "build": "tsc",
12
+ "prepublishOnly": "npm run build"
13
+ },
14
+ "keywords": [
15
+ "alt-text",
16
+ "accessibility",
17
+ "a11y",
18
+ "images",
19
+ "ai",
20
+ "openai",
21
+ "gemini",
22
+ "gpt-4-vision",
23
+ "image-description",
24
+ "wcag",
25
+ "aria",
26
+ "html",
27
+ "seo"
28
+ ],
29
+ "author": "kreent",
30
+ "license": "MIT",
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/kreent/alt-images-ai"
34
+ },
35
+ "engines": {
36
+ "node": ">=18.0.0"
37
+ },
38
+ "devDependencies": {
39
+ "@types/node": "^25.2.2",
40
+ "typescript": "^5.4.0"
41
+ }
42
+ }