@yrpri/api 9.0.90 → 9.0.92

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.
Files changed (28) hide show
  1. package/active-citizen/engine/allOurIdeas/iconGenerator.js +29 -42
  2. package/active-citizen/llms/baseChatBot.js +9 -4
  3. package/active-citizen/llms/collectionImageGenerator.js +67 -31
  4. package/active-citizen/llms/imageGeneration/collectionImageGenerator.js +103 -0
  5. package/active-citizen/llms/imageGeneration/dalleImageGenerator.js +83 -0
  6. package/active-citizen/llms/imageGeneration/fluxImageGenerator.js +49 -0
  7. package/active-citizen/llms/imageGeneration/iImageGenerator.js +1 -0
  8. package/active-citizen/llms/imageGeneration/imageProcessorService.js +64 -0
  9. package/active-citizen/llms/imageGeneration/imagenImageGenerator.js +107 -0
  10. package/active-citizen/llms/imageGeneration/s3Service.js +110 -0
  11. package/active-citizen/models/ac_translation_cache.cjs +2 -0
  12. package/active-citizen/workers/generativeAi.js +1 -1
  13. package/agents/assistants/baseAssistant.js +38 -25
  14. package/agents/assistants/baseAssistantWithVoice.js +33 -6
  15. package/agents/assistants/voiceAssistant.js +31 -3
  16. package/agents/controllers/agentSubscriptionController.js +21 -16
  17. package/agents/controllers/assistantsController.js +29 -10
  18. package/agents/managers/newAiModelSetup.js +3 -0
  19. package/agents/managers/subscriptionManager.js +2 -2
  20. package/app.js +4 -130
  21. package/controllers/images.cjs +64 -10
  22. package/models/image.cjs +1 -1
  23. package/models/index.cjs +20 -12
  24. package/models/video.cjs +1 -1
  25. package/package.json +29 -28
  26. package/utils/manifest_generator.cjs +0 -1
  27. package/utils/sitemap_generator.cjs +6 -0
  28. package/webSockets.js +346 -0
@@ -1,51 +1,38 @@
1
- import fs from "fs";
2
1
  import path from "path";
2
+ import fs from "fs";
3
3
  import { v4 as uuidv4 } from "uuid";
4
+ import axios from "axios";
4
5
  import models from "../../../models/index.cjs";
5
- import { CollectionImageGenerator } from "../../llms/collectionImageGenerator.js";
6
+ import { CollectionImageGenerator } from "../../llms/imageGeneration/collectionImageGenerator.js";
6
7
  const dbModels = models;
7
8
  const Image = dbModels.Image;
8
9
  export class AoiIconGenerator extends CollectionImageGenerator {
9
10
  async createCollectionImage(workPackage) {
10
- return new Promise(async (resolve, reject) => {
11
- let newImageUrl;
12
- const imageFilePath = path.join("/tmp", `${uuidv4()}.png`);
13
- const s3ImagePath = `ypGenAi/${workPackage.collectionType}/${workPackage.collectionId}/${uuidv4()}.png`;
14
- try {
15
- const imageUrl = await this.getImageUrlFromDalle(workPackage.prompt, workPackage.imageType);
16
- if (imageUrl) {
17
- await this.downloadImage(imageUrl, imageFilePath);
18
- console.debug(fs.existsSync(imageFilePath)
19
- ? "File downloaded successfully."
20
- : "File download failed.");
21
- const resizedImageFilePath = await this.resizeImage(imageFilePath, 400, 400);
22
- await this.uploadImageToS3(process.env.S3_BUCKET, resizedImageFilePath, s3ImagePath);
23
- if (process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN) {
24
- newImageUrl = `https://${process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN}/${s3ImagePath}`;
25
- }
26
- else {
27
- newImageUrl = `https://${process.env
28
- .S3_BUCKET}.s3.amazonaws.com/${s3ImagePath}`;
29
- }
30
- const formats = JSON.stringify([newImageUrl]);
31
- const image = await Image.build({
32
- user_id: workPackage.userId,
33
- s3_bucket_name: process.env.S3_BUCKET,
34
- original_filename: "n/a",
35
- formats,
36
- user_agent: "AI worker",
37
- ip_address: "127.0.0.1",
38
- });
39
- await image.save();
40
- resolve({ imageId: image.id, imageUrl: newImageUrl });
41
- }
42
- else {
43
- reject("Error getting image URL from prompt.");
44
- }
45
- }
46
- catch (error) {
47
- reject(error);
48
- }
49
- });
11
+ // 1. Generate the image and record using the base implementation.
12
+ const { imageId, imageUrl } = await super.createCollectionImage(workPackage);
13
+ // 2. Now process the image for the icon:
14
+ // Download the generated image to a temporary location.
15
+ const tempIconPath = path.join("/tmp", `${uuidv4()}-icon.png`);
16
+ await this.imageProcessorService.downloadImage(imageUrl, tempIconPath, axios);
17
+ if (!fs.existsSync(tempIconPath)) {
18
+ throw new Error("Failed to download the generated image for icon processing.");
19
+ }
20
+ // Resize the downloaded image to 400x400 pixels.
21
+ const resizedIconPath = await this.imageProcessorService.resizeImage(tempIconPath, 400, 400);
22
+ // Define a new S3 path for the icon image.
23
+ const iconS3ImagePath = `ypGenAi/${workPackage.collectionType}/${workPackage.collectionId}/${uuidv4()}-icon.png`;
24
+ // Upload the resized icon to S3.
25
+ await this.s3Service.uploadImageToS3(process.env.S3_BUCKET, resizedIconPath, iconS3ImagePath);
26
+ // Construct a public URL for the icon image.
27
+ const newIconUrl = process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN
28
+ ? `https://${process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN}/${iconS3ImagePath}`
29
+ : `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${iconS3ImagePath}`;
30
+ // Optionally, update the DB record with the new icon URL.
31
+ const imageRecord = await Image.findOne({ where: { id: imageId } });
32
+ if (imageRecord) {
33
+ imageRecord.formats = JSON.stringify([newIconUrl]);
34
+ await imageRecord.save();
35
+ }
36
+ return { imageId, imageUrl: newIconUrl };
50
37
  }
51
38
  }
@@ -12,6 +12,9 @@ export class YpBaseChatBot {
12
12
  get redisKey() {
13
13
  return `${YpBaseChatBot.redisMemoryKeyPrefix}-${this.memoryId}`;
14
14
  }
15
+ destroy() {
16
+ this.wsClientSocket = undefined;
17
+ }
15
18
  static loadMemoryFromRedis(memoryId) {
16
19
  return new Promise(async (resolve, reject) => {
17
20
  try {
@@ -70,12 +73,14 @@ export class YpBaseChatBot {
70
73
  });
71
74
  this.wsClientId = wsClientId;
72
75
  this.wsClientSocket = wsClients.get(this.wsClientId);
76
+ this.wsClients = wsClients;
77
+ console.log(`WebSockets: BaseChatBot constructor for ${this.wsClientId}`);
78
+ if (!this.wsClientSocket) {
79
+ console.error(`WebSockets: WS Client ${this.wsClientId} not found in streamWebSocketResponses`);
80
+ }
73
81
  this.openaiClient = new OpenAI({
74
82
  apiKey: process.env.OPENAI_API_KEY,
75
83
  });
76
- if (!this.wsClientSocket) {
77
- console.error(`WS Client ${this.wsClientId} not found in streamWebSocketResponses`);
78
- }
79
84
  this.memoryId = memoryId;
80
85
  this.setupMemory(memoryId);
81
86
  }
@@ -163,7 +168,7 @@ export class YpBaseChatBot {
163
168
  }
164
169
  sendToClient(sender, message, type = "stream", uniqueToken = undefined, hiddenContextMessage = false) {
165
170
  try {
166
- if (DEBUG) {
171
+ if (process.env.WS_DEBUG) {
167
172
  console.log(`sendToClient: ${JSON.stringify({ sender, type, message, hiddenContextMessage }, null, 2)}`);
168
173
  }
169
174
  this.wsClientSocket.send(JSON.stringify({
@@ -1,4 +1,4 @@
1
- import { OpenAI } from "openai";
1
+ import { AzureOpenAI, OpenAI } from "openai";
2
2
  import axios from "axios";
3
3
  import AWS from "aws-sdk";
4
4
  import fs from "fs";
@@ -7,7 +7,6 @@ import { v4 as uuidv4 } from "uuid";
7
7
  import models from "../../models/index.cjs";
8
8
  import sharp from "sharp";
9
9
  import Replicate from "replicate";
10
- import { OpenAIClient, AzureKeyCredential, } from "@azure/openai";
11
10
  const dbModels = models;
12
11
  const Image = dbModels.Image;
13
12
  const AcBackgroundJob = dbModels.AcBackgroundJob;
@@ -16,11 +15,47 @@ const disableFlux = false;
16
15
  export class CollectionImageGenerator {
17
16
  async resizeImage(imagePath, width, height) {
18
17
  const resizedImageFilePath = path.join("/tmp", `${uuidv4()}.png`);
19
- await sharp(imagePath)
20
- .resize({ width, height })
21
- .toFile(resizedImageFilePath);
22
- fs.unlinkSync(imagePath);
23
- return resizedImageFilePath;
18
+ try {
19
+ // 1) Initialize Sharp instance
20
+ const image = sharp(imagePath).rotate(); // rotate fixes orientation from EXIF
21
+ // 2) Read metadata to validate format
22
+ const metadata = await image.metadata();
23
+ const validFormats = [
24
+ "jpeg",
25
+ "png",
26
+ "webp",
27
+ "gif",
28
+ "tiff",
29
+ "avif",
30
+ "svg"
31
+ ];
32
+ if (!metadata.format || !validFormats.includes(metadata.format)) {
33
+ throw new Error(`Unsupported format: ${metadata.format} (expected one of ${validFormats.join(", ")})`);
34
+ }
35
+ // 3) Resize + convert
36
+ await image
37
+ .resize({
38
+ width,
39
+ height,
40
+ fit: "inside",
41
+ withoutEnlargement: true,
42
+ })
43
+ .toFormat("png", {
44
+ quality: 90,
45
+ progressive: true,
46
+ })
47
+ .toFile(resizedImageFilePath);
48
+ // 4) Remove the original file after successful resize
49
+ fs.unlinkSync(imagePath);
50
+ return resizedImageFilePath;
51
+ }
52
+ catch (err) {
53
+ console.error("Error resizing image:", err);
54
+ if (fs.existsSync(resizedImageFilePath)) {
55
+ fs.unlinkSync(resizedImageFilePath);
56
+ }
57
+ throw err;
58
+ }
24
59
  }
25
60
  async downloadImage(imageUrl, imageFilePath) {
26
61
  const response = await axios({
@@ -31,12 +66,11 @@ export class CollectionImageGenerator {
31
66
  const writer = fs.createWriteStream(imageFilePath);
32
67
  response.data.pipe(writer);
33
68
  return new Promise((resolve, reject) => {
34
- writer.on("finish", resolve);
69
+ writer.on("finish", () => resolve());
35
70
  writer.on("error", reject);
36
71
  });
37
72
  }
38
73
  async deleteS3Url(imageUrl) {
39
- // Parse the S3 bucket and key from the URL
40
74
  const { bucket, key } = this.parseImageUrl(imageUrl);
41
75
  if (!bucket || !key) {
42
76
  throw new Error("Could not parse bucket or key from URL");
@@ -45,9 +79,9 @@ export class CollectionImageGenerator {
45
79
  const params = {
46
80
  Bucket: bucket,
47
81
  Key: key,
48
- ACL: "private", // Changing the ACL to private
82
+ ACL: "private",
49
83
  };
50
- console.log(`=========================____________________>>>>>>>>>>>>>>>>> Disabling/Deleting Key from S3: ${JSON.stringify(params)}`);
84
+ console.log(`Disabling/Deleting Key from S3: ${JSON.stringify(params)}`);
51
85
  return new Promise((resolve, reject) => {
52
86
  s3.putObjectAcl(params, (err, data) => {
53
87
  if (err) {
@@ -55,14 +89,12 @@ export class CollectionImageGenerator {
55
89
  reject(err);
56
90
  }
57
91
  else {
58
- console.log(`============= Deleted image from S3: ${imageUrl}`, data);
92
+ console.log(`Deleted image from S3: ${imageUrl}`, data);
59
93
  if (process.env.CLOUDFLARE_API_KEY &&
60
94
  process.env.CLOUDFLARE_ZONE_ID) {
61
95
  console.log("Purging Cloudflare cache for image:", imageUrl);
62
96
  axios
63
- .post(`https://api.cloudflare.com/client/v4/zones/${process.env.CLOUDFLARE_ZONE_ID}/purge_cache`, {
64
- files: [imageUrl],
65
- }, {
97
+ .post(`https://api.cloudflare.com/client/v4/zones/${process.env.CLOUDFLARE_ZONE_ID}/purge_cache`, { files: [imageUrl] }, {
66
98
  headers: {
67
99
  Authorization: `Bearer ${process.env.CLOUDFLARE_API_KEY}`,
68
100
  "Content-Type": "application/json",
@@ -79,11 +111,9 @@ export class CollectionImageGenerator {
79
111
  console.error("Headers:", error.response.headers);
80
112
  }
81
113
  else if (error.request) {
82
- // The request was made but no response was received
83
114
  console.error("No response received:", error.request);
84
115
  }
85
116
  else {
86
- // Something happened in setting up the request that triggered an Error
87
117
  console.error("Error setting up request:", error.message);
88
118
  }
89
119
  resolve(data);
@@ -100,14 +130,12 @@ export class CollectionImageGenerator {
100
130
  let bucket, key;
101
131
  if (process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN &&
102
132
  imageUrl.includes(process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN)) {
103
- // Parse URL for Cloudflare proxied images
104
- const path = new URL(imageUrl).pathname;
105
- const [, ...pathParts] = path.split("/");
133
+ const urlPath = new URL(imageUrl).pathname;
134
+ const [, ...pathParts] = urlPath.split("/");
106
135
  bucket = process.env.S3_BUCKET;
107
136
  key = pathParts.join("/");
108
137
  }
109
138
  else {
110
- // Parse URL for direct S3 images
111
139
  const match = imageUrl.match(/https:\/\/(.+?)\.s3\.amazonaws\.com\/(.+)/);
112
140
  if (match) {
113
141
  bucket = match[1];
@@ -129,7 +157,7 @@ export class CollectionImageGenerator {
129
157
  Bucket: bucket,
130
158
  Key: key,
131
159
  Body: fileContent,
132
- ACL: "public-read", // Makes sure the uploaded files are publicly accessible
160
+ ACL: "public-read",
133
161
  ContentType: "image/png",
134
162
  ContentDisposition: "inline",
135
163
  };
@@ -138,8 +166,7 @@ export class CollectionImageGenerator {
138
166
  if (err) {
139
167
  reject(err);
140
168
  }
141
- fs.unlinkSync(filePath); // Deleting file from local storage
142
- //console.log(`Upload response: ${JSON.stringify(data)}`);
169
+ fs.unlinkSync(filePath);
143
170
  resolve(data);
144
171
  });
145
172
  });
@@ -194,7 +221,12 @@ export class CollectionImageGenerator {
194
221
  const azureOpenAiApiKey = process.env["AZURE_OPENAI_API_KEY"];
195
222
  let client;
196
223
  if (azureOpenAiApiKey && azureOpenaAiBase) {
197
- client = new OpenAIClient(azureOpenaAiBase, new AzureKeyCredential(azureOpenAiApiKey));
224
+ client = new AzureOpenAI({
225
+ apiKey: azureOpenAiApiKey,
226
+ endpoint: azureOpenaAiBase,
227
+ deployment: process.env.AZURE_OPENAI_API_DALLE_DEPLOYMENT_NAME,
228
+ apiVersion: "2024-10-21",
229
+ });
198
230
  }
199
231
  else {
200
232
  client = new OpenAI({
@@ -202,7 +234,7 @@ export class CollectionImageGenerator {
202
234
  });
203
235
  }
204
236
  let retryCount = 0;
205
- let retrying = true; // Initialize as true
237
+ let retrying = true;
206
238
  let result;
207
239
  let imageOptions;
208
240
  if (type === "logo") {
@@ -229,19 +261,24 @@ export class CollectionImageGenerator {
229
261
  while (retrying && retryCount < maxRetryCount) {
230
262
  try {
231
263
  if (azureOpenAiApiKey && azureOpenaAiBase) {
232
- result = await client.getImages(process.env.AZURE_OPENAI_API_DALLE_DEPLOYMENT_NAME, prompt, imageOptions);
264
+ result = await client.images.generate({
265
+ prompt,
266
+ n: imageOptions.n,
267
+ size: imageOptions.size,
268
+ quality: imageOptions.quality,
269
+ });
233
270
  }
234
271
  else {
235
272
  result = await client.images.generate({
236
273
  model: "dall-e-3",
237
274
  prompt,
238
275
  n: imageOptions.n,
239
- quality: imageOptions.quality,
240
276
  size: imageOptions.size,
277
+ quality: imageOptions.quality,
241
278
  });
242
279
  }
243
280
  if (result) {
244
- retrying = false; // Only change retrying to false if there is a result.
281
+ retrying = false;
245
282
  }
246
283
  else {
247
284
  console.debug(`Result: NONE`);
@@ -294,8 +331,7 @@ export class CollectionImageGenerator {
294
331
  newImageUrl = `https://${process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN}/${s3ImagePath}`;
295
332
  }
296
333
  else {
297
- newImageUrl = `https://${process.env
298
- .S3_BUCKET}.s3.amazonaws.com/${s3ImagePath}`;
334
+ newImageUrl = `https://${process.env.S3_BUCKET}.s3.amazonaws.com/${s3ImagePath}`;
299
335
  }
300
336
  const formats = JSON.stringify([newImageUrl]);
301
337
  const image = await Image.build({
@@ -0,0 +1,103 @@
1
+ import path from "path";
2
+ import fs from "fs";
3
+ import { v4 as uuidv4 } from "uuid";
4
+ import axios from "axios";
5
+ import { FluxImageGenerator } from "./fluxImageGenerator.js";
6
+ import { DalleImageGenerator } from "./dalleImageGenerator.js";
7
+ import { ImageProcessorService } from "./imageProcessorService.js";
8
+ import { S3Service } from "./s3Service.js";
9
+ // Suppose these come from your codebase
10
+ import models from "../../../models/index.cjs";
11
+ import { ImagenImageGenerator } from "./imagenImageGenerator.js";
12
+ // For reference, in your code:
13
+ const dbModels = models;
14
+ const Image = dbModels.Image;
15
+ const AcBackgroundJob = dbModels.AcBackgroundJob;
16
+ const disableFlux = false;
17
+ const useImagen = true;
18
+ export class CollectionImageGenerator {
19
+ constructor() {
20
+ this.s3Service = new S3Service(process.env.CLOUDFLARE_API_KEY, process.env.CLOUDFLARE_ZONE_ID);
21
+ this.imageProcessorService = new ImageProcessorService();
22
+ // Initialize generators
23
+ if (!disableFlux &&
24
+ process.env.REPLICATE_API_TOKEN &&
25
+ process.env.FLUX_PRO_MODEL_NAME) {
26
+ this.fluxImageGenerator = new FluxImageGenerator(process.env.REPLICATE_API_TOKEN, process.env.FLUX_PRO_MODEL_NAME);
27
+ }
28
+ this.dalleImageGenerator = new DalleImageGenerator(process.env.AZURE_OPENAI_API_BASE, process.env.AZURE_OPENAI_API_KEY, process.env.AZURE_OPENAI_API_DALLE_DEPLOYMENT_NAME, process.env.OPENAI_API_KEY);
29
+ if (useImagen && process.env.GOOGLE_CLOUD_PROJECT_ID) {
30
+ this.imagenImageGenerator = new ImagenImageGenerator(this.s3Service);
31
+ }
32
+ }
33
+ /**
34
+ * Orchestrates image generation (via Flux or DALL·E), downloads that image,
35
+ * uploads it to S3, and saves a record in the DB.
36
+ */
37
+ async createCollectionImage(workPackage) {
38
+ return new Promise(async (resolve, reject) => {
39
+ let newImageUrl;
40
+ const imageFilePath = path.join("/tmp", `${uuidv4()}.png`);
41
+ const s3ImagePath = `ypGenAi/${workPackage.collectionType}/${workPackage.collectionId}/${uuidv4()}.png`;
42
+ try {
43
+ let imageGenerator;
44
+ // Decide which generator to use
45
+ if (this.imagenImageGenerator) {
46
+ imageGenerator = this.imagenImageGenerator;
47
+ console.info("Using ImagenImageGenerator");
48
+ }
49
+ else if (this.fluxImageGenerator) {
50
+ imageGenerator = this.fluxImageGenerator;
51
+ console.info("Using FluxImageGenerator");
52
+ }
53
+ else {
54
+ imageGenerator = this.dalleImageGenerator;
55
+ console.info("Using DalleImageGenerator");
56
+ }
57
+ // 1) Generate image
58
+ const imageUrl = await imageGenerator.generateImageUrl(workPackage.prompt, workPackage.imageType);
59
+ if (!imageUrl) {
60
+ return reject("Error getting image URL from prompt.");
61
+ }
62
+ if (useImagen && this.imagenImageGenerator) {
63
+ newImageUrl = imageUrl;
64
+ }
65
+ else {
66
+ // 2) Download image to temporary location
67
+ await this.imageProcessorService.downloadImage(imageUrl, imageFilePath, axios);
68
+ console.debug(fs.existsSync(imageFilePath)
69
+ ? "File downloaded successfully."
70
+ : "File download failed.");
71
+ // (Optional) If you want to resize the image before upload:
72
+ // const resizedPath = await this.imageProcessorService.resizeImage(imageFilePath, 1024, 1024);
73
+ // Upload the `resizedPath` instead of `imageFilePath`
74
+ // 3) Upload image to S3
75
+ await this.s3Service.uploadImageToS3(process.env.S3_BUCKET, imageFilePath, s3ImagePath);
76
+ // 4) Construct a public URL (optionally going through Cloudflare)
77
+ if (process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN) {
78
+ newImageUrl = `https://${process.env.CLOUDFLARE_IMAGE_PROXY_DOMAIN}/${s3ImagePath}`;
79
+ }
80
+ else {
81
+ newImageUrl = `https://${process.env
82
+ .S3_BUCKET}.s3.amazonaws.com/${s3ImagePath}`;
83
+ }
84
+ }
85
+ // 5) Save record in DB
86
+ const formats = JSON.stringify([newImageUrl]);
87
+ const imageRecord = await Image.build({
88
+ user_id: workPackage.userId,
89
+ s3_bucket_name: process.env.S3_BUCKET,
90
+ original_filename: "n/a",
91
+ formats,
92
+ user_agent: "AI worker",
93
+ ip_address: "127.0.0.1",
94
+ });
95
+ await imageRecord.save();
96
+ resolve({ imageId: imageRecord.id, imageUrl: newImageUrl });
97
+ }
98
+ catch (error) {
99
+ reject(error);
100
+ }
101
+ });
102
+ }
103
+ }
@@ -0,0 +1,83 @@
1
+ import { AzureOpenAI, OpenAI } from "openai";
2
+ export class DalleImageGenerator {
3
+ constructor(azureOpenaAiBase, azureOpenAiApiKey, azureDalleDeployment, openAiKey) {
4
+ this.maxRetryCount = 3;
5
+ this.azureOpenaAiBase = azureOpenaAiBase;
6
+ this.azureOpenAiApiKey = azureOpenAiApiKey;
7
+ this.azureDalleDeployment = azureDalleDeployment;
8
+ this.openAiKey = openAiKey;
9
+ }
10
+ async generateImageUrl(prompt, type = "logo") {
11
+ let client;
12
+ let result;
13
+ let retryCount = 0;
14
+ let retrying = true;
15
+ // Decide which client to instantiate (Azure vs. standard OpenAI)
16
+ if (this.azureOpenaAiBase && this.azureOpenAiApiKey && this.azureDalleDeployment) {
17
+ client = new AzureOpenAI({
18
+ apiKey: this.azureOpenAiApiKey,
19
+ endpoint: this.azureOpenaAiBase,
20
+ deployment: this.azureDalleDeployment,
21
+ apiVersion: "2024-10-21",
22
+ });
23
+ }
24
+ else {
25
+ // fallback to standard OpenAI
26
+ client = new OpenAI({
27
+ apiKey: this.openAiKey,
28
+ });
29
+ }
30
+ // Decide on image dimensions
31
+ let size = "1792x1024";
32
+ if (type === "logo") {
33
+ size = "1792x1024";
34
+ }
35
+ else if (type === "icon") {
36
+ size = "1024x1024";
37
+ }
38
+ while (retrying && retryCount < this.maxRetryCount) {
39
+ try {
40
+ // If using Azure OpenAI
41
+ if (this.azureOpenaAiBase && this.azureOpenAiApiKey && this.azureDalleDeployment) {
42
+ result = await client.images.generate({
43
+ prompt,
44
+ n: 1,
45
+ size,
46
+ quality: "hd",
47
+ });
48
+ }
49
+ else {
50
+ // Standard OpenAI
51
+ result = await client.images.generate({
52
+ model: "dall-e-3",
53
+ prompt,
54
+ n: 1,
55
+ size,
56
+ quality: "hd",
57
+ });
58
+ }
59
+ if (result) {
60
+ retrying = false;
61
+ }
62
+ else {
63
+ console.debug("Result: NONE");
64
+ }
65
+ }
66
+ catch (error) {
67
+ console.warn("Error generating image with DALL·E, retrying...");
68
+ console.warn(error.stack);
69
+ retryCount++;
70
+ const sleepingFor = 5000 + retryCount * 10000;
71
+ console.debug(`Sleeping for ${sleepingFor} milliseconds`);
72
+ await new Promise((resolve) => setTimeout(resolve, sleepingFor));
73
+ }
74
+ }
75
+ if (result && result.data && result.data[0].url) {
76
+ return result.data[0].url;
77
+ }
78
+ else {
79
+ console.error(`Error generating image after ${retryCount} retries`);
80
+ return undefined;
81
+ }
82
+ }
83
+ }
@@ -0,0 +1,49 @@
1
+ import Replicate from "replicate";
2
+ export class FluxImageGenerator {
3
+ constructor(replicateApiKey, fluxProModelName) {
4
+ this.replicateApiKey = replicateApiKey;
5
+ this.fluxProModelName = fluxProModelName;
6
+ this.maxRetryCount = 3;
7
+ this.replicate = new Replicate({ auth: replicateApiKey });
8
+ }
9
+ async generateImageUrl(prompt, type = "logo") {
10
+ let retryCount = 0;
11
+ let retrying = true;
12
+ let result;
13
+ // Configure the input to replicate’s model
14
+ const input = { prompt };
15
+ // Assign aspect ratio depending on type
16
+ if (type === "logo") {
17
+ input.aspect_ratio = "16:9";
18
+ }
19
+ else if (type === "icon") {
20
+ input.aspect_ratio = "1:1";
21
+ }
22
+ else {
23
+ input.aspect_ratio = "16:9";
24
+ }
25
+ while (retrying && retryCount < this.maxRetryCount) {
26
+ try {
27
+ result = await this.replicate.run(this.fluxProModelName, {
28
+ input,
29
+ });
30
+ if (result) {
31
+ retrying = false;
32
+ return result; // typically a single URL
33
+ }
34
+ }
35
+ catch (error) {
36
+ console.warn("Error generating image with Flux, retrying...");
37
+ console.warn(error.stack);
38
+ retryCount++;
39
+ const sleepingFor = 5000 + retryCount * 10000;
40
+ console.debug(`Sleeping for ${sleepingFor} milliseconds`);
41
+ await new Promise((resolve) => setTimeout(resolve, sleepingFor));
42
+ }
43
+ }
44
+ if (!result) {
45
+ console.error(`Error generating image after ${retryCount} retries`);
46
+ }
47
+ return undefined;
48
+ }
49
+ }
@@ -0,0 +1,64 @@
1
+ import fs from "fs";
2
+ import path from "path";
3
+ import sharp from "sharp";
4
+ import { v4 as uuidv4 } from "uuid";
5
+ export class ImageProcessorService {
6
+ constructor() {
7
+ this.validFormats = ["jpeg", "png", "webp", "gif", "tiff", "avif", "svg"];
8
+ }
9
+ /**
10
+ * Downloads an image from a given URL into the specified filepath.
11
+ */
12
+ async downloadImage(imageUrl, imageFilePath, axiosInstance) {
13
+ const response = await axiosInstance({
14
+ method: "GET",
15
+ url: imageUrl,
16
+ responseType: "stream",
17
+ });
18
+ const writer = fs.createWriteStream(imageFilePath);
19
+ response.data.pipe(writer);
20
+ return new Promise((resolve, reject) => {
21
+ writer.on("finish", () => resolve());
22
+ writer.on("error", reject);
23
+ });
24
+ }
25
+ /**
26
+ * Resizes an image to given dimensions (width x height).
27
+ * If the image is smaller, it won't be enlarged.
28
+ */
29
+ async resizeImage(imagePath, width, height) {
30
+ const resizedImageFilePath = path.join("/tmp", `${uuidv4()}.png`);
31
+ try {
32
+ // 1) Initialize Sharp instance
33
+ const image = sharp(imagePath).rotate(); // rotate fixes orientation from EXIF
34
+ // 2) Read metadata to validate format
35
+ const metadata = await image.metadata();
36
+ if (!metadata.format || !this.validFormats.includes(metadata.format)) {
37
+ throw new Error(`Unsupported format: ${metadata.format} (expected one of ${this.validFormats.join(", ")})`);
38
+ }
39
+ // 3) Resize + convert
40
+ await image
41
+ .resize({
42
+ width,
43
+ height,
44
+ fit: "inside",
45
+ withoutEnlargement: true,
46
+ })
47
+ .toFormat("png", {
48
+ quality: 90,
49
+ progressive: true,
50
+ })
51
+ .toFile(resizedImageFilePath);
52
+ // 4) Remove the original file
53
+ fs.unlinkSync(imagePath);
54
+ return resizedImageFilePath;
55
+ }
56
+ catch (err) {
57
+ console.error("Error resizing image:", err);
58
+ if (fs.existsSync(resizedImageFilePath)) {
59
+ fs.unlinkSync(resizedImageFilePath);
60
+ }
61
+ throw err;
62
+ }
63
+ }
64
+ }