genmix 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,246 @@
1
+ # 🎨 GenMix
2
+
3
+ AI-powered image generator using Google Gemini API. Supports image generation from text prompts and image modification with reference images.
4
+
5
+ ## Features ✨
6
+
7
+ - 🖼️ **Image generation** from text descriptions
8
+ - 🎨 **Image modification** using reference images
9
+ - 🔄 **Style transfer** - Apply artistic styles to your images
10
+ - 📐 **Quality control** - Generate in 1K, 2K or 4K
11
+ - 🎯 **Multiple formats** - Supports local paths, URLs and Base64
12
+ - 💾 **Auto-save** with unique hash per prompt
13
+
14
+ ## Installation
15
+
16
+ ```bash
17
+ npm install genmix
18
+ ```
19
+
20
+ ## Setup
21
+
22
+ Create a `.env` file in your project root:
23
+
24
+ ```env
25
+ GEMINI_API_KEY=your_api_key_here
26
+ ```
27
+
28
+ ## Basic Usage
29
+
30
+ ### Simple Image Generation
31
+
32
+ ```javascript
33
+ const { GeminiGenerator } = require('genmix');
34
+
35
+ const generator = new GeminiGenerator();
36
+
37
+ // Generate an image
38
+ const result = await generator.generate(
39
+ 'A futuristic city with flying cars, cyberpunk style',
40
+ {
41
+ numberOfImages: 1,
42
+ quality: '2K',
43
+ aspectRatio: '16:9'
44
+ }
45
+ );
46
+
47
+ // Save images
48
+ const savedPaths = generator.save('./output');
49
+ console.log('Images saved:', savedPaths);
50
+ ```
51
+
52
+ ### Image Modification with Reference Images
53
+
54
+ ```javascript
55
+ // Modify an existing image
56
+ const result = await generator.generate(
57
+ 'Transform this image to have sunset lighting with warm orange tones',
58
+ {
59
+ referenceImage: './my-image.png', // Local path
60
+ quality: '2K',
61
+ numberOfImages: 1
62
+ }
63
+ );
64
+
65
+ const savedPaths = generator.save('./output');
66
+ ```
67
+
68
+ ### Using URLs as Reference
69
+
70
+ ```javascript
71
+ const result = await generator.generate(
72
+ 'Convert this photo into a watercolor painting',
73
+ {
74
+ referenceImage: 'https://example.com/image.jpg', // URL
75
+ quality: '1K'
76
+ }
77
+ );
78
+
79
+ generator.save('./output');
80
+ ```
81
+
82
+ ## Configuration Options
83
+
84
+ ### Constructor
85
+
86
+ ```javascript
87
+ new GeminiGenerator({
88
+ apiKey: string, // Your Google API key (required)
89
+ modelId: string // Model to use (optional, default: 'gemini-3-pro-image-preview')
90
+ })
91
+ ```
92
+
93
+ ### generate() Method
94
+
95
+ ```javascript
96
+ await generator.generate(prompt, options)
97
+ ```
98
+
99
+ **Parameters:**
100
+
101
+ | Option | Type | Description | Default |
102
+ | ------------------------ | ------------- | ------------------------------------------- | ------- |
103
+ | `prompt` | string | Description of what you want to generate | - |
104
+ | `options.referenceImage` | string/Buffer | Reference image (path, URL, Base64, Buffer) | - |
105
+ | `options.numberOfImages` | number | Number of images to generate | 1 |
106
+ | `options.quality` | string | Quality: '1K', '2K', '4K' | - |
107
+ | `options.aspectRatio` | string | Aspect ratio: '1:1', '16:9', '4:3', etc. | - |
108
+
109
+ ## Advanced Examples
110
+
111
+ ### Style Transfer
112
+
113
+ ```javascript
114
+ const result = await generator.generate(
115
+ 'Transform this photo into a Van Gogh style painting with visible brush strokes',
116
+ {
117
+ referenceImage: './photo.jpg',
118
+ quality: '4K'
119
+ }
120
+ );
121
+ ```
122
+
123
+ ### Lighting Modification
124
+
125
+ ```javascript
126
+ const result = await generator.generate(
127
+ 'Change the lighting to dramatic studio lighting with strong shadows',
128
+ {
129
+ referenceImage: './portrait.png',
130
+ quality: '2K'
131
+ }
132
+ );
133
+ ```
134
+
135
+ ### Multiple Variations
136
+
137
+ ```javascript
138
+ const result = await generator.generate(
139
+ 'Add dramatic clouds and enhance colors',
140
+ {
141
+ referenceImage: './landscape.jpg',
142
+ numberOfImages: 3,
143
+ quality: '1K'
144
+ }
145
+ );
146
+
147
+ // Generates 3 variations of the same modification
148
+ ```
149
+
150
+ ### Using Buffers
151
+
152
+ ```javascript
153
+ const fs = require('fs');
154
+ const imageBuffer = fs.readFileSync('./image.png');
155
+
156
+ const result = await generator.generate(
157
+ 'Make this image look cinematic',
158
+ {
159
+ referenceImage: imageBuffer
160
+ }
161
+ );
162
+ ```
163
+
164
+ ## Reference Image Formats
165
+
166
+ GenMix accepts reference images in multiple formats:
167
+
168
+ 1. **Local file path**: `'./image.png'`
169
+ 2. **URL**: `'https://example.com/image.jpg'`
170
+ 3. **Data URI**: `'data:image/png;base64,iVBORw0KG...'`
171
+ 4. **Buffer**: `Buffer.from(...)`
172
+
173
+ Supported image formats: PNG, JPEG, GIF, WEBP
174
+
175
+ ## Error Handling
176
+
177
+ ```javascript
178
+ try {
179
+ const result = await generator.generate(prompt, options);
180
+
181
+ if (result.images && result.images.length > 0) {
182
+ const paths = generator.save('./output');
183
+ console.log('Success!', paths);
184
+ } else {
185
+ console.log('No images generated');
186
+ }
187
+ } catch (error) {
188
+ console.error('Error:', error.message);
189
+
190
+ // Common errors:
191
+ // - 'API Key is required'
192
+ // - 'Failed to read reference image file'
193
+ // - 'Failed to download reference image from URL'
194
+ // - 'Gemini API Error: ...'
195
+ }
196
+ ```
197
+
198
+ ## Project Structure
199
+
200
+ ```
201
+ genmix/
202
+ └── generators/
203
+ │ ├── BaseGenerator.js # Base class with utilities
204
+ │ └── GeminiGenerator.js # Gemini API implementation
205
+ ├── demo/
206
+ │ ├── example.js # Basic examples
207
+ │ └── example-translation.js # Translate image
208
+ ├── index.js # Entry point
209
+ └── README.md
210
+ ```
211
+
212
+ ## Best Practices
213
+
214
+ 1. **Clear Prompts**: Be specific about what you want
215
+ ```javascript
216
+ // ✅ Good
217
+ 'Add dramatic sunset lighting with orange and pink tones in the sky'
218
+
219
+ // ❌ Vague
220
+ 'Make it better'
221
+ ```
222
+
223
+ 2. **Appropriate Quality**:
224
+ - `1K`: Quick tests
225
+ - `2K`: General use
226
+ - `4K`: High quality (slower)
227
+
228
+ 3. **Image Size**: Reference images between 512x512 and 2048x2048 work best
229
+
230
+ 4. **Result Caching**: Images are automatically saved with unique hash based on the prompt
231
+
232
+ ## Additional Resources
233
+
234
+ - [Code Examples](./demo/)
235
+ - [Google Gemini API Documentation](https://ai.google.dev/)
236
+
237
+ ## License
238
+
239
+ MIT
240
+
241
+ ## Contributing
242
+
243
+ Contributions are welcome! Please open an issue or pull request.
244
+
245
+
246
+
package/demo/.env ADDED
@@ -0,0 +1 @@
1
+ GEMINI_API_KEY="AIzaSyDMy_iCPVzMHF3sSrbCYr2K9PO9lHkCPsg"
Binary file
@@ -0,0 +1,71 @@
1
+ process.loadEnvFile(); // Native Node.js .env loading (Node 20.12+)
2
+ const { GeminiGenerator } = require('../index');
3
+ const path = require('path');
4
+ const fs = require('fs');
5
+
6
+ async function main() {
7
+ try {
8
+ const generator = new GeminiGenerator();
9
+
10
+ // === Option 1: Use a local image ===
11
+ const localImagePath = path.join(__dirname, 'camera_4126.jpg');
12
+
13
+ if (fs.existsSync(localImagePath)) {
14
+ console.log('📸 Processing local image...\n');
15
+
16
+ const result1 = await generator.generate(
17
+ 'Translate this image to Portuguese',
18
+ {
19
+ referenceImage: localImagePath,
20
+ quality: '2K',
21
+ numberOfImages: 1
22
+ }
23
+ );
24
+
25
+ if (result1.images && result1.images.length > 0) {
26
+ const saved = generator.save(__dirname);
27
+ console.log('✅ Modified image saved at:', saved[0], '\n');
28
+ } else if (result1.text) {
29
+ console.log('📝 Result:', result1.text, '\n');
30
+ }
31
+ }
32
+
33
+
34
+ // === Option 3: Multiple variations of the same image ===
35
+ if (fs.existsSync(localImagePath)) {
36
+ console.log('🎨 Generating multiple variations...\n');
37
+
38
+ const result3 = await generator.generate(
39
+ 'Add a vintage film look with grain and vignette effect',
40
+ {
41
+ referenceImage: localImagePath,
42
+ quality: '1K',
43
+ numberOfImages: 3
44
+ }
45
+ );
46
+
47
+ if (result3.images && result3.images.length > 0) {
48
+ const saved = generator.save(__dirname);
49
+ console.log(`✅ ${saved.length} variations saved:`);
50
+ saved.forEach(p => console.log(` - ${p}`));
51
+ console.log();
52
+ }
53
+ }
54
+
55
+ console.log('🎉 All examples completed successfully!\n');
56
+
57
+ } catch (error) {
58
+ console.error('❌ Error:', error.message);
59
+
60
+ if (error.message.includes('API Key')) {
61
+ console.error('\n💡 Tip: Make sure you have GEMINI_API_KEY in your .env file');
62
+ }
63
+
64
+ if (error.message.includes('reference image')) {
65
+ console.error('\n💡 Tip: Verify that the image path is correct');
66
+ }
67
+ }
68
+ }
69
+
70
+ main();
71
+
@@ -0,0 +1,51 @@
1
+ process.loadEnvFile(); // Native Node.js .env loading (Node 20.12+)
2
+ const { GeminiGenerator } = require('../index');
3
+
4
+
5
+ async function exampleBasicGeneration() {
6
+ console.log('\n=== Example 1: Basic Image Generation ===\n');
7
+
8
+ const generator = new GeminiGenerator();
9
+
10
+ const prompt = 'A futuristic city with flying cars, cyberpunk style';
11
+
12
+ console.log('Generating image...');
13
+ const result = await generator.generate(prompt, {
14
+ numberOfImages: 2,
15
+ quality: '1K', // Options: 1K, 2K, 4K
16
+ aspectRatio: '1:1'
17
+ });
18
+
19
+ console.log('Generation complete!');
20
+
21
+ if (result.text) {
22
+ console.log('Text response:', result.text);
23
+ }
24
+
25
+ if (result.images && result.images.length > 0) {
26
+ console.log(`Found ${result.images.length} images.`);
27
+
28
+ const savedPaths = generator.save(__dirname);
29
+ savedPaths.forEach(p => console.log(`Saved image to ${p}`));
30
+ } else {
31
+ console.log('No images generated.');
32
+ }
33
+ }
34
+
35
+
36
+
37
+ async function main() {
38
+ try {
39
+ // Example 1: Basic generation
40
+ await exampleBasicGeneration();
41
+
42
+ console.log('\n=== All examples completed! ===\n');
43
+ } catch (error) {
44
+ console.error('Error:', error.message);
45
+ if (error.stack) {
46
+ console.error(error.stack);
47
+ }
48
+ }
49
+ }
50
+
51
+ main();
@@ -0,0 +1,58 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const hashFactory = require('hash-factory');
4
+
5
+ class BaseGenerator {
6
+ constructor(config = {}) {
7
+ this.config = config;
8
+ }
9
+
10
+ /**
11
+ * Generates a hash from the text prompt.
12
+ * @param {string} prompt
13
+ * @param {Object} options
14
+ * @returns {string}
15
+ */
16
+ generateHash(prompt, options = { length: 10 }) {
17
+ return hashFactory(prompt, options);
18
+ }
19
+
20
+ /**
21
+ * Saves generated images to the specified directory using hashed filenames.
22
+ * @param {string[]} images - Array of base64 image strings.
23
+ * @param {string} directory - Target directory path.
24
+ * @param {string} prompt - The prompt used for generation (to create hash).
25
+ * @param {string} [extension='png'] - File extension.
26
+ * @returns {string[]} Array of saved file paths.
27
+ */
28
+ saveImages(images, directory, prompt, extension = 'png') {
29
+ if (!images || !Array.isArray(images) || images.length === 0) {
30
+ return [];
31
+ }
32
+
33
+ // Ensure directory exists
34
+ if (!fs.existsSync(directory)) {
35
+ fs.mkdirSync(directory, { recursive: true });
36
+ }
37
+
38
+ const savedPaths = [];
39
+ const hash = this.generateHash(prompt);
40
+
41
+ images.forEach((imgData, index) => {
42
+ const base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
43
+ const buffer = Buffer.from(base64Data, 'base64');
44
+
45
+ // Using format: hash_index.ext
46
+ const fileName = `${hash}_${index + 1}.${extension}`;
47
+ const outputPath = path.join(directory, fileName);
48
+
49
+ fs.writeFileSync(outputPath, buffer);
50
+ savedPaths.push(outputPath);
51
+ });
52
+
53
+ return savedPaths;
54
+ }
55
+ }
56
+
57
+ module.exports = BaseGenerator;
58
+
@@ -0,0 +1,63 @@
1
+ const fs = require('fs');
2
+ const path = require('path');
3
+ const hashFactory = require('hash-factory');
4
+
5
+ class BaseGenerator {
6
+ constructor(config = {}) {
7
+ this.config = config;
8
+ this.lastGeneration = null;
9
+ }
10
+
11
+ /**
12
+ * Generates a hash from the text prompt.
13
+ * @param {string} prompt
14
+ * @param {Object} options
15
+ * @returns {string}
16
+ */
17
+ generateHash(prompt, options = { alpha: true, words: true, now: true }) {
18
+ const hasher = hashFactory(options);
19
+ return hasher(prompt);
20
+ }
21
+
22
+ /**
23
+ * Saves generated images to the specified directory using hashed filenames.
24
+ * @param {string} directory - Target directory path.
25
+ * @returns {string[]} Array of saved file paths.
26
+ */
27
+ save(directory) {
28
+ const extension = 'png';
29
+ const targetImages = this.lastGeneration?.images;
30
+ const targetPrompt = this.lastGeneration?.prompt;
31
+
32
+ if (!targetImages || !Array.isArray(targetImages) || targetImages.length === 0) {
33
+ console.warn('No images to save.');
34
+ return [];
35
+ }
36
+
37
+ if (!targetPrompt) {
38
+ console.warn('No prompt available for hash generation.');
39
+ }
40
+
41
+ // Ensure directory exists
42
+ if (!fs.existsSync(directory)) {
43
+ fs.mkdirSync(directory, { recursive: true });
44
+ }
45
+
46
+ const savedPaths = [];
47
+ targetImages.forEach((imgData, index) => {
48
+ const base64Data = imgData.replace(/^data:image\/\w+;base64,/, "");
49
+ const buffer = Buffer.from(base64Data, 'base64');
50
+
51
+ const hash = this.generateHash(targetPrompt + '_' + index);
52
+ const fileName = `${hash}.${extension}`;
53
+ const outputPath = path.join(directory, fileName);
54
+
55
+ fs.writeFileSync(outputPath, buffer);
56
+ savedPaths.push(outputPath);
57
+ });
58
+
59
+ return savedPaths;
60
+ }
61
+ }
62
+
63
+ module.exports = BaseGenerator;
@@ -0,0 +1,264 @@
1
+ const axios = require('axios');
2
+ const BaseGenerator = require('./BaseGenerator');
3
+ const fs = require('fs');
4
+ const path = require('path');
5
+
6
+ class GeminiGenerator extends BaseGenerator {
7
+ /**
8
+ * @param {Object} [config]
9
+ * @param {string} [config.apiKey]
10
+ * @param {string} [config.modelId]
11
+ */
12
+ constructor(config = {}) {
13
+ super(config);
14
+ this.apiKey = config.apiKey || process.env.GEMINI_API_KEY;
15
+
16
+ if (!this.apiKey) {
17
+ throw new Error('API Key is required. Provide it in the constructor or set GEMINI_API_KEY environment variable.');
18
+ }
19
+ // Use the specific model for image generation or default to pro-vision if needed
20
+ // However, for standard image generation via prompt, specific models like 'gemini-1.5-pro' or 'imagen-3.0-generate-001' are often used.
21
+ // Keeping the user's modelId preference.
22
+ this.modelId = config.modelId || 'gemini-3-pro-image-preview';
23
+ // NOTE: For simple generation, we might not need streamGenerateContent unless we want text streaming.
24
+ // But let's stick to what worked for text, and adjust the body.
25
+ this.apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${this.modelId}:streamGenerateContent`;
26
+ }
27
+
28
+ /**
29
+ * @param {string} prompt
30
+ * @param {Object} [options]
31
+ * @param {string|Buffer} [options.referenceImage] - Path to image file, base64 data URI, or Buffer
32
+ * @param {string} [options.numberOfImages] - Number of images to generate
33
+ * @param {string} [options.quality] - Image quality: '1K', '2K', '4K'
34
+ * @param {string} [options.aspectRatio] - Aspect ratio like '1:1', '16:9', etc.
35
+ * @returns {Promise<{images: string[], text: string, raw: any}>}
36
+ */
37
+ async generate(prompt, options = {}) {
38
+ // If the user asks for multiple images, we might need to make parallel requests
39
+ // if the API doesn't support candidateCount > 1 for images.
40
+ // Based on search results, candidateCount > 1 can cause 400 errors.
41
+ const numberOfImages = options.numberOfImages || 1;
42
+
43
+ let result;
44
+ if (numberOfImages > 1) {
45
+ result = await this.generateMultiple(prompt, numberOfImages, options);
46
+ } else {
47
+ result = await this._generateSingleRequest(prompt, options);
48
+ }
49
+
50
+ // Store result in state for saveImages()
51
+ this.lastGeneration = {
52
+ prompt: prompt,
53
+ images: result.images,
54
+ text: result.text,
55
+ raw: result.raw
56
+ };
57
+
58
+ return result;
59
+ }
60
+
61
+ /**
62
+ * Processes a reference image and returns base64 data and mime type
63
+ * @param {string|Buffer} imageInput - File path, base64 data URI, or Buffer
64
+ * @returns {Promise<{data: string, mimeType: string}>}
65
+ * @private
66
+ */
67
+ async _processReferenceImage(imageInput) {
68
+ let base64Data;
69
+ let mimeType = 'image/png'; // default
70
+
71
+ if (Buffer.isBuffer(imageInput)) {
72
+ // If it's already a buffer
73
+ base64Data = imageInput.toString('base64');
74
+ } else if (typeof imageInput === 'string') {
75
+ if (imageInput.startsWith('data:image/')) {
76
+ // It's a data URI
77
+ const match = imageInput.match(/^data:(image\/[^;]+);base64,(.+)$/);
78
+ if (match) {
79
+ mimeType = match[1];
80
+ base64Data = match[2];
81
+ } else {
82
+ throw new Error('Invalid data URI format for reference image');
83
+ }
84
+ } else if (imageInput.startsWith('http://') || imageInput.startsWith('https://')) {
85
+ // It's a URL - download it
86
+ try {
87
+ const response = await axios.get(imageInput, { responseType: 'arraybuffer' });
88
+ base64Data = Buffer.from(response.data).toString('base64');
89
+ mimeType = response.headers['content-type'] || 'image/png';
90
+ } catch (error) {
91
+ throw new Error(`Failed to download reference image from URL: ${error.message}`);
92
+ }
93
+ } else {
94
+ // Assume it's a file path
95
+ try {
96
+ const fileBuffer = fs.readFileSync(imageInput);
97
+ base64Data = fileBuffer.toString('base64');
98
+
99
+ // Determine mime type from extension
100
+ const ext = path.extname(imageInput).toLowerCase();
101
+ const mimeTypes = {
102
+ '.png': 'image/png',
103
+ '.jpg': 'image/jpeg',
104
+ '.jpeg': 'image/jpeg',
105
+ '.gif': 'image/gif',
106
+ '.webp': 'image/webp'
107
+ };
108
+ mimeType = mimeTypes[ext] || 'image/png';
109
+ } catch (error) {
110
+ throw new Error(`Failed to read reference image file: ${error.message}`);
111
+ }
112
+ }
113
+ } else {
114
+ throw new Error('Reference image must be a file path, URL, data URI, or Buffer');
115
+ }
116
+
117
+ return { data: base64Data, mimeType };
118
+ }
119
+
120
+ async generateMultiple(prompt, count, options) {
121
+ const promises = [];
122
+ for (let i = 0; i < count; i++) {
123
+ promises.push(this._generateSingleRequest(prompt, options));
124
+ }
125
+
126
+ try {
127
+ const results = await Promise.all(promises);
128
+
129
+ const merged = {
130
+ images: [],
131
+ text: '',
132
+ raw: results.map(r => r.raw),
133
+ };
134
+
135
+ for (const res of results) {
136
+ merged.images.push(...res.images);
137
+ if (res.text && !merged.text.includes(res.text)) {
138
+ merged.text += res.text + '\n';
139
+ }
140
+ }
141
+ return merged;
142
+ } catch (error) {
143
+ throw error;
144
+ }
145
+ }
146
+
147
+ async _generateSingleRequest(prompt, options) {
148
+ const url = `${this.apiUrl}?key=${this.apiKey}`;
149
+
150
+ // Build the parts array
151
+ const parts = [];
152
+
153
+ // Add reference image if provided
154
+ if (options.referenceImage) {
155
+ const imageData = await this._processReferenceImage(options.referenceImage);
156
+ parts.push({
157
+ inlineData: {
158
+ mimeType: imageData.mimeType,
159
+ data: imageData.data
160
+ }
161
+ });
162
+ }
163
+
164
+ // Add text prompt
165
+ parts.push({
166
+ text: prompt,
167
+ });
168
+
169
+ // Simplified data structure to minimize conflicts
170
+ const data = {
171
+ contents: [
172
+ {
173
+ role: 'user', // Role is good practice
174
+ parts: parts,
175
+ },
176
+ ],
177
+ // Only include generationConfig if there are specific options to set
178
+ generationConfig: {
179
+ // 'responseModalities' is often implied or specific to the model.
180
+ // Removing explicit 'responseModalities' might help if the model detects intent from prompt.
181
+ // But let's keep basic structure.
182
+ // candidateCount MUST be 1 for many image models per request.
183
+ candidateCount: 1,
184
+ },
185
+ };
186
+
187
+ // Only add imageConfig if it's actually supported/needed.
188
+ // Some models might reject 'imageConfig' inside 'generationConfig' if they expect text.
189
+ // However, if we are targeting an image model, we need to be careful.
190
+ // Let's try sending a cleaner request first.
191
+
192
+ // If specific image options are passed, add them carefully
193
+ if (options.imageSize || options.aspectRatio || options.quality) {
194
+ // Note: Not all models support 'imageConfig' this way.
195
+ // But if the user is using a model that supports it...
196
+ data.generationConfig.imageConfig = {};
197
+
198
+ // User mentioned quality is 1k, 2k, 4k. Mapping quality or imageSize to this.
199
+ const size = options.quality || options.imageSize;
200
+ if (size) {
201
+ data.generationConfig.imageConfig.image_size = size; // Expects '1K', '2K', '4K'
202
+ }
203
+
204
+ if (options.aspectRatio) {
205
+ data.generationConfig.imageConfig.aspect_ratio = options.aspectRatio;
206
+ }
207
+ }
208
+
209
+ // Remove 'tools' unless explicitly requested or needed for search grounding.
210
+ // 'googleSearch' tool might conflict with pure image generation intent on some endpoints.
211
+ // data.tools = ... (removed)
212
+
213
+ try {
214
+ const response = await axios.post(url, data, {
215
+ headers: {
216
+ 'Content-Type': 'application/json',
217
+ },
218
+ });
219
+
220
+ return this.processResponse(response.data);
221
+ } catch (error) {
222
+ const errorMessage = error.response?.data?.error?.message || error.message;
223
+ // Log detailed error for debugging
224
+ if (error.response?.data) {
225
+ console.error("API Error Details:", JSON.stringify(error.response.data, null, 2));
226
+ }
227
+ throw new Error(`Gemini API Error: ${errorMessage}`);
228
+ }
229
+ }
230
+
231
+ processResponse(data) {
232
+ // Handle stream response which might be an array of chunks
233
+ const chunks = Array.isArray(data) ? data : [data];
234
+ const images = [];
235
+ let fullText = '';
236
+
237
+ for (const chunk of chunks) {
238
+ if (chunk.candidates) {
239
+ for (const candidate of chunk.candidates) {
240
+ if (candidate.content && candidate.content.parts) {
241
+ for (const part of candidate.content.parts) {
242
+ if (part.text) {
243
+ fullText += part.text;
244
+ }
245
+ if (part.inlineData && part.inlineData.mimeType.startsWith('image/')) {
246
+ // Base64 image data
247
+ images.push(`data:${part.inlineData.mimeType};base64,${part.inlineData.data}`);
248
+ }
249
+ // Handle executable code or other parts if necessary
250
+ }
251
+ }
252
+ }
253
+ }
254
+ }
255
+
256
+ return {
257
+ images,
258
+ text: fullText,
259
+ raw: data,
260
+ };
261
+ }
262
+ }
263
+
264
+ module.exports = GeminiGenerator;
package/index.js ADDED
@@ -0,0 +1,6 @@
1
+ const GeminiGenerator = require('./generators/GeminiGenerator');
2
+
3
+ module.exports = {
4
+ GeminiGenerator,
5
+ };
6
+
package/package.json ADDED
@@ -0,0 +1,16 @@
1
+ {
2
+ "name": "genmix",
3
+ "version": "1.0.0",
4
+ "description": "",
5
+ "license": "ISC",
6
+ "author": "",
7
+ "type": "commonjs",
8
+ "main": "index.js",
9
+ "scripts": {
10
+ "test": "echo \"Error: no test specified\" && exit 1"
11
+ },
12
+ "dependencies": {
13
+ "axios": "^1.13.2",
14
+ "hash-factory": "^1.1.2"
15
+ }
16
+ }