@yrpri/api 9.0.231 → 9.0.233

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 (69) hide show
  1. package/agents/managers/subscriptionManager.js +2 -2
  2. package/controllers/allOurIdeas.js +1 -1
  3. package/controllers/communities.cjs +282 -91
  4. package/controllers/domains.cjs +54 -8
  5. package/controllers/groups.cjs +51 -6
  6. package/controllers/points.cjs +4 -9
  7. package/controllers/posts.cjs +7 -9
  8. package/controllers/ratings.cjs +3 -6
  9. package/models/point.cjs +31 -3
  10. package/models/post.cjs +2 -3
  11. package/package.json +32 -67
  12. package/services/engine/allOurIdeas/aiHelper.d.ts +7 -4
  13. package/services/engine/allOurIdeas/aiHelper.js +34 -19
  14. package/services/engine/allOurIdeas/explainAnswersAssistant.d.ts +1 -1
  15. package/services/engine/allOurIdeas/explainAnswersAssistant.js +3 -9
  16. package/services/engine/moderation/fraud/CreateFraudAuditReport.cjs +35 -11
  17. package/services/engine/moderation/fraud/CreateFraudAuditReport.d.cts +21 -0
  18. package/services/engine/moderation/fraud/FraudBase.cjs +38 -18
  19. package/services/engine/moderation/fraud/FraudBase.d.cts +2 -0
  20. package/services/engine/moderation/fraud/FraudDeleteBase.cjs +48 -29
  21. package/services/engine/moderation/fraud/FraudDeleteBase.d.cts +8 -6
  22. package/services/engine/moderation/fraud/FraudDeleteEndorsements.cjs +5 -4
  23. package/services/engine/moderation/fraud/FraudDeleteEndorsements.d.cts +2 -2
  24. package/services/engine/moderation/fraud/FraudDeletePointQualities.cjs +3 -2
  25. package/services/engine/moderation/fraud/FraudDeletePointQualities.d.cts +1 -1
  26. package/services/engine/moderation/fraud/FraudDeletePoints.cjs +3 -2
  27. package/services/engine/moderation/fraud/FraudDeletePoints.d.cts +1 -1
  28. package/services/engine/moderation/fraud/FraudDeletePosts.cjs +3 -2
  29. package/services/engine/moderation/fraud/FraudDeleteRatings.cjs +61 -4
  30. package/services/engine/moderation/fraud/FraudGetBase.cjs +44 -20
  31. package/services/engine/moderation/fraud/FraudGetBase.d.cts +5 -0
  32. package/services/engine/moderation/fraud/FraudGetEndorsements.cjs +4 -13
  33. package/services/engine/moderation/fraud/FraudGetEndorsements.d.cts +1 -1
  34. package/services/engine/moderation/fraud/FraudGetPointQualities.cjs +3 -0
  35. package/services/engine/moderation/fraud/FraudGetPointQualities.d.cts +1 -1
  36. package/services/engine/moderation/fraud/FraudGetPoints.cjs +3 -0
  37. package/services/engine/moderation/fraud/FraudGetPoints.d.cts +1 -1
  38. package/services/engine/moderation/fraud/FraudGetPosts.cjs +17 -16
  39. package/services/engine/moderation/fraud/FraudGetPosts.d.cts +3 -3
  40. package/services/engine/moderation/fraud/FraudGetRatings.cjs +62 -30
  41. package/services/engine/moderation/fraud/FraudGetRatings.d.cts +4 -1
  42. package/services/engine/moderation/fraud/FraudRequestValidation.cjs +143 -0
  43. package/services/engine/moderation/fraud/FraudRequestValidation.d.cts +21 -0
  44. package/services/engine/moderation/fraud/FraudScannerNotifier.cjs +59 -35
  45. package/services/engine/moderation/fraud/FraudScannerNotifier.d.cts +20 -1
  46. package/services/llms/baseChatBot.d.ts +2 -0
  47. package/services/llms/baseChatBot.js +25 -9
  48. package/services/llms/imageGeneration/chatGptImageGenerator.d.ts +2 -2
  49. package/services/llms/imageGeneration/chatGptImageGenerator.js +13 -10
  50. package/services/llms/imageGeneration/collectionImageGenerator.js +31 -13
  51. package/services/llms/imageGeneration/dalleImageGenerator.d.ts +2 -2
  52. package/services/llms/imageGeneration/dalleImageGenerator.js +28 -16
  53. package/services/llms/imageGeneration/fluxImageGenerator.d.ts +2 -2
  54. package/services/llms/imageGeneration/fluxImageGenerator.js +9 -3
  55. package/services/llms/imageGeneration/iImageGenerator.d.ts +8 -1
  56. package/services/llms/imageGeneration/imageModelConfig.cjs +319 -0
  57. package/services/llms/imageGeneration/imageModelConfig.d.cts +79 -0
  58. package/services/llms/imageGeneration/imagenImageGenerator.d.ts +2 -3
  59. package/services/llms/imageGeneration/imagenImageGenerator.js +10 -10
  60. package/tests/fraudManagement.test.cjs +470 -0
  61. package/tests/fraudManagement.test.d.cts +1 -0
  62. package/tests/imageModelConfig.test.cjs +144 -0
  63. package/tests/imageModelConfig.test.d.cts +1 -0
  64. package/utils/ai_image_generation_guard.cjs +268 -0
  65. package/utils/ai_image_generation_guard.d.cts +34 -0
  66. package/utils/fingerprint_data.cjs +32 -0
  67. package/utils/fingerprint_data.d.cts +6 -0
  68. package/utils/recount_utils.cjs +53 -37
  69. package/utils/recount_utils.d.cts +7 -7
@@ -0,0 +1,143 @@
1
+ "use strict";
2
+ const MAX_FRAUD_IDS_TO_DELETE = 1000;
3
+ const validActionTypes = new Set(["get-items", "delete-one-item", "delete-items"]);
4
+ const validCollectionTypes = new Set([
5
+ "endorsements",
6
+ "ratings",
7
+ "pointQualities",
8
+ "points",
9
+ "posts",
10
+ ]);
11
+ const commonMethods = new Set([
12
+ "byIpFingerprint",
13
+ "byMissingBrowserFingerprint",
14
+ "byIpAddress",
15
+ ]);
16
+ const postMethods = new Set([
17
+ ...commonMethods,
18
+ "byIpFingerprintPostId",
19
+ "byIpUserAgentPostId",
20
+ ]);
21
+ const pointMethods = new Set([
22
+ ...commonMethods,
23
+ "byIpFingerprintPointId",
24
+ "byIpUserAgentPointId",
25
+ ]);
26
+ const normalizeIdValue = (value) => {
27
+ if (typeof value === "number") {
28
+ return Number.isSafeInteger(value) && value > 0 ? value : null;
29
+ }
30
+ else if (typeof value === "string") {
31
+ const trimmedValue = value.trim();
32
+ if (!/^[1-9]\d*$/.test(trimmedValue)) {
33
+ return null;
34
+ }
35
+ const parsedValue = Number(trimmedValue);
36
+ return Number.isSafeInteger(parsedValue) ? parsedValue : null;
37
+ }
38
+ else {
39
+ return null;
40
+ }
41
+ };
42
+ const validateIdsToDelete = (idsToDelete) => {
43
+ if (idsToDelete === undefined || idsToDelete === null) {
44
+ return { idsToDelete: [] };
45
+ }
46
+ if (!Array.isArray(idsToDelete)) {
47
+ return {
48
+ error: "invalid_ids_to_delete",
49
+ idsToDelete: [],
50
+ };
51
+ }
52
+ if (idsToDelete.length > MAX_FRAUD_IDS_TO_DELETE) {
53
+ return {
54
+ error: "too_many_ids_to_delete",
55
+ idsToDelete: [],
56
+ };
57
+ }
58
+ const seenIds = new Set();
59
+ const normalizedIds = [];
60
+ for (let i = 0; i < idsToDelete.length; i++) {
61
+ const normalizedId = normalizeIdValue(idsToDelete[i]);
62
+ if (!normalizedId) {
63
+ return {
64
+ error: "invalid_ids_to_delete",
65
+ idsToDelete: [],
66
+ };
67
+ }
68
+ if (!seenIds.has(normalizedId)) {
69
+ seenIds.add(normalizedId);
70
+ normalizedIds.push(normalizedId);
71
+ }
72
+ }
73
+ return { idsToDelete: normalizedIds };
74
+ };
75
+ const normalizeIdsToDelete = (idsToDelete) => {
76
+ return validateIdsToDelete(idsToDelete).idsToDelete;
77
+ };
78
+ const getValidMethodsForCollectionType = (collectionType) => {
79
+ if (collectionType === "pointQualities") {
80
+ return pointMethods;
81
+ }
82
+ else if (collectionType === "posts") {
83
+ return commonMethods;
84
+ }
85
+ else {
86
+ return postMethods;
87
+ }
88
+ };
89
+ const validateFraudActionRequest = ({ type, selectedMethod, collectionType, idsToDelete, }) => {
90
+ if (!validActionTypes.has(type)) {
91
+ return {
92
+ error: "invalid_fraud_action_type",
93
+ idsToDelete: [],
94
+ };
95
+ }
96
+ if (!validCollectionTypes.has(collectionType)) {
97
+ return {
98
+ error: "invalid_fraud_collection_type",
99
+ idsToDelete: [],
100
+ };
101
+ }
102
+ const validMethods = getValidMethodsForCollectionType(collectionType);
103
+ if (!validMethods.has(selectedMethod)) {
104
+ return {
105
+ error: "invalid_fraud_detection_method",
106
+ idsToDelete: [],
107
+ };
108
+ }
109
+ const idsValidation = validateIdsToDelete(idsToDelete);
110
+ const normalizedIdsToDelete = idsValidation.idsToDelete;
111
+ if (idsValidation.error) {
112
+ return idsValidation;
113
+ }
114
+ if ((type === "delete-one-item" || type === "delete-items") &&
115
+ normalizedIdsToDelete.length === 0) {
116
+ return {
117
+ error: "delete_requires_ids",
118
+ idsToDelete: normalizedIdsToDelete,
119
+ };
120
+ }
121
+ if (type === "delete-one-item" && normalizedIdsToDelete.length !== 1) {
122
+ return {
123
+ error: "single_delete_requires_one_id",
124
+ idsToDelete: normalizedIdsToDelete,
125
+ };
126
+ }
127
+ if (selectedMethod === "byMissingBrowserFingerprint" &&
128
+ (type === "delete-items" || normalizedIdsToDelete.length > 1)) {
129
+ return {
130
+ error: "bulk_delete_missing_fingerprint_disabled",
131
+ idsToDelete: normalizedIdsToDelete,
132
+ };
133
+ }
134
+ return {
135
+ idsToDelete: normalizedIdsToDelete,
136
+ };
137
+ };
138
+ module.exports = {
139
+ MAX_FRAUD_IDS_TO_DELETE,
140
+ normalizeIdsToDelete,
141
+ validateFraudActionRequest,
142
+ validateIdsToDelete,
143
+ };
@@ -0,0 +1,21 @@
1
+ export const MAX_FRAUD_IDS_TO_DELETE: 1000;
2
+ export function normalizeIdsToDelete(idsToDelete: any): number[] | never[];
3
+ export function validateFraudActionRequest({ type, selectedMethod, collectionType, idsToDelete, }: {
4
+ type: any;
5
+ selectedMethod: any;
6
+ collectionType: any;
7
+ idsToDelete: any;
8
+ }): {
9
+ error: string;
10
+ idsToDelete: number[] | never[];
11
+ } | {
12
+ idsToDelete: number[] | never[];
13
+ error?: undefined;
14
+ };
15
+ export function validateIdsToDelete(idsToDelete: any): {
16
+ error: string;
17
+ idsToDelete: never[];
18
+ } | {
19
+ idsToDelete: number[];
20
+ error?: undefined;
21
+ };
@@ -4,9 +4,11 @@ const models = require("../../../../models/index.cjs");
4
4
  const i18n = require('../../../utils/i18n.cjs');
5
5
  const deepEqual = require('deep-equal');
6
6
  const log = require("../../../../utils/logger.cjs");
7
- const FraudGetEndorsements = require("./FraudGetEndorsements");
8
- const FraudGetPointQualities = require("./FraudGetPointQualities");
9
- const FraudGetRatings = require("./FraudGetRatings");
7
+ const FraudGetEndorsements = require("./FraudGetEndorsements.cjs");
8
+ const FraudGetPointQualities = require("./FraudGetPointQualities.cjs");
9
+ const FraudGetRatings = require("./FraudGetRatings.cjs");
10
+ const FraudGetPoints = require("./FraudGetPoints.cjs");
11
+ const FraudGetPosts = require("./FraudGetPosts.cjs");
10
12
  const queue = require("../../../workers/queue.cjs");
11
13
  const Backend = require("i18next-fs-backend");
12
14
  const path = require("path");
@@ -15,8 +17,17 @@ class FraudScannerNotifier {
15
17
  constructor() {
16
18
  this.currentCommunity = null;
17
19
  this.uniqueCollectionItemsIds = {};
18
- this.collectionsToScan = ['endorsements', 'ratings', 'pointQualities'];
19
- this.scannerModels = [FraudGetEndorsements, FraudGetRatings, FraudGetPointQualities];
20
+ this.collectionsToScan = ['endorsements', 'ratings', 'pointQualities', 'points', 'posts'];
21
+ this.scannerModels = [
22
+ FraudGetEndorsements,
23
+ FraudGetRatings,
24
+ FraudGetPointQualities,
25
+ FraudGetPoints,
26
+ FraudGetPosts
27
+ ];
28
+ }
29
+ resetCounts() {
30
+ this.uniqueCollectionItemsIds = {};
20
31
  }
21
32
  getCommunityURL() {
22
33
  if (this.currentCommunity && this.currentCommunity.Domain && this.currentCommunity.Domain.domain_name) {
@@ -134,7 +145,7 @@ class FraudScannerNotifier {
134
145
  }
135
146
  getContainerOldCount(collectionType) {
136
147
  let foundCollection;
137
- if (this.currentCommunity.data.lastFraudScanResults) {
148
+ if (this.currentCommunity.data && this.currentCommunity.data.lastFraudScanResults) {
138
149
  for (let i = 0; i < this.currentCommunity.data.lastFraudScanResults.length; i++) {
139
150
  if (this.currentCommunity.data.lastFraudScanResults[i].collectionType &&
140
151
  this.currentCommunity.data.lastFraudScanResults[i].collectionType === collectionType) {
@@ -187,7 +198,7 @@ class FraudScannerNotifier {
187
198
  if (this.collectionsToScan[c] === "pointQualities") {
188
199
  methodsToScan = methodsToScan.concat(['byIpFingerprintPointId', 'byIpUserAgentPointId']);
189
200
  }
190
- else {
201
+ else if (this.collectionsToScan[c] !== "posts") {
191
202
  methodsToScan = methodsToScan.concat(['byIpFingerprintPostId', 'byIpUserAgentPostId']);
192
203
  }
193
204
  for (let m = 0; m < methodsToScan.length; m++) {
@@ -230,6 +241,7 @@ class FraudScannerNotifier {
230
241
  for (let i = 0; i < communities.length; i++) {
231
242
  log.info("Processing community: " + communities[i].name);
232
243
  this.currentCommunity = communities[i];
244
+ this.resetCounts();
233
245
  try {
234
246
  await this.scan();
235
247
  await this.notify();
@@ -249,32 +261,44 @@ class FraudScannerNotifier {
249
261
  });
250
262
  }
251
263
  }
252
- i18n
253
- .use(Backend)
254
- .init({
255
- preload: ['en', 'fr', 'sk', 'bg', 'cs', 'it', 'da', 'kl', 'es', 'sv', 'sq', 'uz', 'uk', 'ca', 'hr', 'ro', 'ru',
256
- 'ro_md', 'pt_br', 'hu', 'tr', 'is', 'nl', 'no', 'pl', 'zh_tw', 'ky'],
257
- fallbackLng: 'en',
258
- // this is the defaults
259
- backend: {
260
- // path where resources get loaded from
261
- loadPath: localesPath + '/{{lng}}/translation.json',
262
- // path to post missing resources
263
- addPath: localesPath + '/{{lng}}/translation.missing.json',
264
- // jsonIndent to use when storing json files
265
- jsonIndent: 2
266
- }
267
- }, function (err, t) {
268
- (async () => {
269
- try {
270
- const scanner = new FraudScannerNotifier();
271
- await scanner.scanAndNotify();
272
- log.info("Fraud Scanning Complete");
273
- process.exit();
274
- }
275
- catch (error) {
276
- log.error(error);
277
- process.exit();
264
+ const runFraudScannerNotifier = () => {
265
+ i18n
266
+ .use(Backend)
267
+ .init({
268
+ preload: ['en', 'fr', 'sk', 'bg', 'cs', 'it', 'da', 'kl', 'es', 'sv', 'sq', 'uz', 'uk', 'ca', 'hr', 'ro', 'ru',
269
+ 'ro_md', 'pt_br', 'hu', 'tr', 'is', 'nl', 'no', 'pl', 'zh_tw', 'ky'],
270
+ fallbackLng: 'en',
271
+ // this is the defaults
272
+ backend: {
273
+ // path where resources get loaded from
274
+ loadPath: localesPath + '/{{lng}}/translation.json',
275
+ // path to post missing resources
276
+ addPath: localesPath + '/{{lng}}/translation.missing.json',
277
+ // jsonIndent to use when storing json files
278
+ jsonIndent: 2
278
279
  }
279
- })();
280
- });
280
+ }, function (err, t) {
281
+ (async () => {
282
+ try {
283
+ if (err) {
284
+ throw err;
285
+ }
286
+ const scanner = new FraudScannerNotifier();
287
+ await scanner.scanAndNotify();
288
+ log.info("Fraud Scanning Complete");
289
+ process.exit(0);
290
+ }
291
+ catch (error) {
292
+ log.error(error);
293
+ process.exit(1);
294
+ }
295
+ })();
296
+ });
297
+ };
298
+ if (require.main === module) {
299
+ runFraudScannerNotifier();
300
+ }
301
+ module.exports = {
302
+ FraudScannerNotifier,
303
+ runFraudScannerNotifier
304
+ };
@@ -1 +1,20 @@
1
- export {};
1
+ export class FraudScannerNotifier {
2
+ currentCommunity: (import("sequelize").Model<YpCommunityData, Partial<YpCommunityData>> & YpCommunityData) | null;
3
+ uniqueCollectionItemsIds: {};
4
+ collectionsToScan: string[];
5
+ scannerModels: (typeof FraudGetPoints)[];
6
+ resetCounts(): void;
7
+ getCommunityURL(): string;
8
+ setupCounts(items: any, collectionType: any): void;
9
+ capitalizeFirstLetter(string: any): any;
10
+ formatNumber(value: any): any;
11
+ getNumberSign(number: any): "" | "+";
12
+ sendNotificationEmails(fraudAuditResults: any): Promise<void>;
13
+ getContainerOldCount(collectionType: any): any;
14
+ getWithDifference(results: any): any;
15
+ notify(): Promise<void>;
16
+ scan(): Promise<void>;
17
+ scanAndNotify(): Promise<any>;
18
+ }
19
+ export function runFraudScannerNotifier(): void;
20
+ import FraudGetPoints = require("./FraudGetPoints.cjs");
@@ -26,6 +26,8 @@ export declare class YpBaseChatBot {
26
26
  sendAgentUpdate(message: string): void;
27
27
  sendToClient(sender: YpSenderType, message: string, type?: YpAssistantMessageType, uniqueToken?: string | undefined, hiddenContextMessage?: boolean): void;
28
28
  streamWebSocketResponses(stream: AsyncIterable<OpenAI.Chat.Completions.ChatCompletionChunk>): Promise<void>;
29
+ usesMaxCompletionTokens(modelName: string): boolean;
30
+ getStreamingChatCompletionParams(messages: OpenAI.Chat.Completions.ChatCompletionMessageParam[]): OpenAI.Chat.Completions.ChatCompletionCreateParamsStreaming;
29
31
  saveMemoryIfNeeded(): Promise<void>;
30
32
  setChatLog(chatLog: YpSimpleChatLog[]): Promise<void>;
31
33
  conversation(chatLog: YpSimpleChatLog[]): Promise<void>;
@@ -41,6 +41,7 @@ export class YpBaseChatBot {
41
41
  this.wsClientId = wsClientId;
42
42
  this.wsClientSocket = wsClients.get(this.wsClientId);
43
43
  this.wsClients = wsClients;
44
+ this.memory = { chatLog: [] };
44
45
  log.info(`WebSockets: BaseChatBot constructor for ${this.wsClientId}`);
45
46
  if (!this.wsClientSocket) {
46
47
  log.error(`WebSockets: WS Client ${this.wsClientId} not found in streamWebSocketResponses`);
@@ -126,8 +127,11 @@ export class YpBaseChatBot {
126
127
  try {
127
128
  let botMessage = "";
128
129
  for await (const part of stream) {
129
- this.sendToClient("assistant", part.choices[0].delta.content);
130
- botMessage += part.choices[0].delta.content;
130
+ const content = part.choices[0].delta.content;
131
+ if (content) {
132
+ this.sendToClient("assistant", content);
133
+ botMessage += content;
134
+ }
131
135
  if (part.choices[0].finish_reason == "stop") {
132
136
  this.memory.chatLog.push({
133
137
  sender: "assistant",
@@ -149,6 +153,24 @@ export class YpBaseChatBot {
149
153
  resolve();
150
154
  });
151
155
  }
156
+ usesMaxCompletionTokens(modelName) {
157
+ return modelName.startsWith("gpt-5");
158
+ }
159
+ getStreamingChatCompletionParams(messages) {
160
+ const requestParams = {
161
+ model: this.llmModel,
162
+ messages,
163
+ stream: true,
164
+ };
165
+ if (this.usesMaxCompletionTokens(this.llmModel)) {
166
+ requestParams.max_completion_tokens = this.maxTokens;
167
+ }
168
+ else {
169
+ requestParams.max_tokens = this.maxTokens;
170
+ requestParams.temperature = this.temperature;
171
+ }
172
+ return requestParams;
173
+ }
152
174
  async saveMemoryIfNeeded() {
153
175
  if (this.persistMemory) {
154
176
  await this.saveMemory();
@@ -171,13 +193,7 @@ export class YpBaseChatBot {
171
193
  content: this.renderSystemPrompt(),
172
194
  };
173
195
  messages.unshift(systemMessage);
174
- const stream = await this.openaiClient.chat.completions.create({
175
- model: this.llmModel,
176
- messages,
177
- max_tokens: this.maxTokens,
178
- temperature: this.temperature,
179
- stream: true,
180
- });
196
+ const stream = await this.openaiClient.chat.completions.create(this.getStreamingChatCompletionParams(messages));
181
197
  this.streamWebSocketResponses(stream);
182
198
  }
183
199
  }
@@ -1,4 +1,4 @@
1
- import { IImageGenerator, YpAiGenerateImageTypes } from "./iImageGenerator.js";
1
+ import { IImageGenerator, YpAiGenerateImageTypes, YpImageGenerationOptions } from "./iImageGenerator.js";
2
2
  export declare class ChatGptImageGenerator implements IImageGenerator {
3
3
  private readonly maxRetryCount;
4
4
  private readonly openAiKey?;
@@ -8,6 +8,6 @@ export declare class ChatGptImageGenerator implements IImageGenerator {
8
8
  * The returned link remains live for ~60 minutes – be sure to download
9
9
  * or cache it right away in the calling service.
10
10
  */
11
- generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes): Promise<string | undefined>;
11
+ generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes, options?: YpImageGenerationOptions): Promise<string | undefined>;
12
12
  }
13
13
  export default ChatGptImageGenerator;
@@ -1,6 +1,8 @@
1
1
  // chatGptImageGenerator.ts
2
2
  import { OpenAI } from "openai";
3
3
  import log from "../../../utils/loggerTs.js";
4
+ import imageModelConfig from "./imageModelConfig.cjs";
5
+ const { defaultOpenAiImageModel, getDefaultImageQualityForOptions, getDefaultImageSizeForOptions, } = imageModelConfig;
4
6
  export class ChatGptImageGenerator {
5
7
  constructor(openAiKey) {
6
8
  this.maxRetryCount = 3;
@@ -12,14 +14,15 @@ export class ChatGptImageGenerator {
12
14
  * The returned link remains live for ~60 minutes – be sure to download
13
15
  * or cache it right away in the calling service.
14
16
  */
15
- async generateImageUrl(prompt, type = "logo") {
17
+ async generateImageUrl(prompt, type = "logo", options) {
16
18
  const client = new OpenAI({ apiKey: this.openAiKey });
17
- // Pick a sensible canvas size from the image type
18
- let size = "1536x1024";
19
- if (type === "icon")
20
- size = "1024x1024";
21
- else if (type === "other")
22
- size = "1536x1024";
19
+ const model = options?.imageModel || defaultOpenAiImageModel;
20
+ const size = options?.imageSize ||
21
+ getDefaultImageSizeForOptions("openai", model, type) ||
22
+ "1536x1024";
23
+ const quality = options?.imageQuality ||
24
+ getDefaultImageQualityForOptions("openai", model) ||
25
+ "medium";
23
26
  const finalPrompt = `
24
27
  ${prompt}
25
28
 
@@ -30,11 +33,11 @@ export class ChatGptImageGenerator {
30
33
  while (retryCount < this.maxRetryCount) {
31
34
  try {
32
35
  const res = await client.images.generate({
33
- model: "gpt-image-1",
36
+ model,
34
37
  prompt: finalPrompt,
35
- quality: "medium",
38
+ quality: quality,
36
39
  n: 1,
37
- size,
40
+ size: size,
38
41
  });
39
42
  log.info("res", JSON.stringify(res, null, 2));
40
43
  const b64Json = res?.data?.[0]?.b64_json;
@@ -11,12 +11,13 @@ import log from "../../../utils/loggerTs.js";
11
11
  import models from "../../../models/index.cjs";
12
12
  import { ImagenImageGenerator } from "./imagenImageGenerator.js";
13
13
  import { ChatGptImageGenerator } from "./chatGptImageGenerator.js";
14
+ import imageModelConfig from "./imageModelConfig.cjs";
14
15
  // For reference, in your code:
15
16
  const dbModels = models;
16
17
  const Image = dbModels.Image;
17
18
  const AcBackgroundJob = dbModels.AcBackgroundJob;
18
19
  const disableFlux = false;
19
- const useImagen = false;
20
+ const { isOpenAiDalleImageModel, isOpenAiGptImageModel, normalizeImageGenerationOptions, } = imageModelConfig;
20
21
  export class CollectionImageGenerator {
21
22
  constructor() {
22
23
  this.s3Service = new S3Service(process.env.CLOUDFLARE_API_KEY, process.env.CLOUDFLARE_ZONE_ID);
@@ -29,7 +30,7 @@ export class CollectionImageGenerator {
29
30
  }
30
31
  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);
31
32
  this.chatGptImageGenerator = new ChatGptImageGenerator(process.env.OPENAI_API_KEY);
32
- if (useImagen && process.env.GOOGLE_CLOUD_PROJECT_ID) {
33
+ if (process.env.GOOGLE_CLOUD_PROJECT_ID) {
33
34
  this.imagenImageGenerator = new ImagenImageGenerator(this.s3Service);
34
35
  }
35
36
  }
@@ -44,29 +45,46 @@ export class CollectionImageGenerator {
44
45
  const s3ImagePath = `ypGenAi/${workPackage.collectionType}/${workPackage.collectionId}/${uuidv4()}.png`;
45
46
  try {
46
47
  let imageGenerator;
47
- // Decide which generator to use
48
- if (this.imagenImageGenerator) {
48
+ const rawImageOptions = normalizeImageGenerationOptions(workPackage.imageProvider, workPackage.imageModel, workPackage.imageSize, workPackage.imageQuality);
49
+ if (rawImageOptions.error) {
50
+ return reject(rawImageOptions.error);
51
+ }
52
+ const imageOptions = rawImageOptions;
53
+ if (imageOptions.imageProvider === "imagen") {
54
+ if (!this.imagenImageGenerator) {
55
+ return reject("Imagen image generator is not configured.");
56
+ }
49
57
  imageGenerator = this.imagenImageGenerator;
50
- log.info("Using ImagenImageGenerator");
58
+ log.info(`Using ImagenImageGenerator: ${imageOptions.imageModel}`);
51
59
  }
52
- else if (this.fluxImageGenerator) {
60
+ else if (imageOptions.imageProvider === "flux") {
61
+ if (!this.fluxImageGenerator) {
62
+ return reject("Flux image generator is not configured.");
63
+ }
53
64
  imageGenerator = this.fluxImageGenerator;
54
- log.info("Using FluxImageGenerator");
65
+ log.info(`Using FluxImageGenerator: ${imageOptions.imageModel}`);
66
+ }
67
+ else if (imageOptions.imageProvider === "azureOpenai") {
68
+ imageGenerator = this.dalleImageGenerator;
69
+ log.info(`Using Azure OpenAI image generator: ${imageOptions.imageModel}`);
70
+ }
71
+ else if (isOpenAiDalleImageModel(imageOptions.imageModel)) {
72
+ imageGenerator = this.dalleImageGenerator;
73
+ log.info(`Using DalleImageGenerator: ${imageOptions.imageModel}`);
55
74
  }
56
- else if (process.env.USE_CHATGPT_IMAGE_GENERATOR) {
75
+ else if (isOpenAiGptImageModel(imageOptions.imageModel)) {
57
76
  imageGenerator = this.chatGptImageGenerator;
58
- log.info("Using ChatGptImageGenerator");
77
+ log.info(`Using ChatGptImageGenerator: ${imageOptions.imageModel}`);
59
78
  }
60
79
  else {
61
- imageGenerator = this.dalleImageGenerator;
62
- log.info("Using DalleImageGenerator");
80
+ return reject(`Unsupported OpenAI image model: ${imageOptions.imageModel}`);
63
81
  }
64
82
  // 1) Generate image
65
- const imageUrl = await imageGenerator.generateImageUrl(workPackage.prompt, workPackage.imageType);
83
+ const imageUrl = await imageGenerator.generateImageUrl(workPackage.prompt, workPackage.imageType, imageOptions);
66
84
  if (!imageUrl) {
67
85
  return reject("Error getting image URL from prompt.");
68
86
  }
69
- if (useImagen && this.imagenImageGenerator) {
87
+ if (imageOptions.imageProvider === "imagen") {
70
88
  newImageUrl = imageUrl;
71
89
  }
72
90
  else {
@@ -1,4 +1,4 @@
1
- import { IImageGenerator, YpAiGenerateImageTypes } from "./iImageGenerator.js";
1
+ import { IImageGenerator, YpAiGenerateImageTypes, YpImageGenerationOptions } from "./iImageGenerator.js";
2
2
  export declare class DalleImageGenerator implements IImageGenerator {
3
3
  private maxRetryCount;
4
4
  private azureOpenaAiBase?;
@@ -6,5 +6,5 @@ export declare class DalleImageGenerator implements IImageGenerator {
6
6
  private azureDalleDeployment?;
7
7
  private openAiKey?;
8
8
  constructor(azureOpenaAiBase: string | undefined, azureOpenAiApiKey: string | undefined, azureDalleDeployment: string | undefined, openAiKey: string | undefined);
9
- generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes): Promise<string | undefined>;
9
+ generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes, options?: YpImageGenerationOptions): Promise<string | undefined>;
10
10
  }
@@ -1,5 +1,7 @@
1
1
  import { AzureOpenAI, OpenAI } from "openai";
2
2
  import log from "../../../utils/loggerTs.js";
3
+ import imageModelConfig from "./imageModelConfig.cjs";
4
+ const { getDefaultImageQualityForOptions, getDefaultImageSizeForOptions, } = imageModelConfig;
3
5
  export class DalleImageGenerator {
4
6
  constructor(azureOpenaAiBase, azureOpenAiApiKey, azureDalleDeployment, openAiKey) {
5
7
  this.maxRetryCount = 3;
@@ -8,17 +10,30 @@ export class DalleImageGenerator {
8
10
  this.azureDalleDeployment = azureDalleDeployment;
9
11
  this.openAiKey = openAiKey;
10
12
  }
11
- async generateImageUrl(prompt, type = "logo") {
13
+ async generateImageUrl(prompt, type = "logo", options) {
12
14
  let client;
13
15
  let result;
14
16
  let retryCount = 0;
15
17
  let retrying = true;
18
+ const hasAzureOpenAiConfig = Boolean(this.azureOpenaAiBase &&
19
+ this.azureOpenAiApiKey &&
20
+ this.azureDalleDeployment);
21
+ const useAzureOpenAi = options?.imageProvider === "azureOpenai" ||
22
+ (!options?.imageProvider && hasAzureOpenAiConfig);
23
+ const requestedModel = options?.imageModel ||
24
+ (useAzureOpenAi ? this.azureDalleDeployment : undefined) ||
25
+ "dall-e-3";
26
+ const imageProvider = useAzureOpenAi ? "azureOpenai" : "openai";
16
27
  // Decide which client to instantiate (Azure vs. standard OpenAI)
17
- if (this.azureOpenaAiBase && this.azureOpenAiApiKey && this.azureDalleDeployment) {
28
+ if (useAzureOpenAi) {
29
+ if (!this.azureOpenaAiBase || !this.azureOpenAiApiKey) {
30
+ log.error("Azure OpenAI image generator is not configured.");
31
+ return undefined;
32
+ }
18
33
  client = new AzureOpenAI({
19
34
  apiKey: this.azureOpenAiApiKey,
20
35
  endpoint: this.azureOpenaAiBase,
21
- deployment: this.azureDalleDeployment,
36
+ deployment: requestedModel,
22
37
  apiVersion: "2024-10-21",
23
38
  });
24
39
  }
@@ -28,33 +43,30 @@ export class DalleImageGenerator {
28
43
  apiKey: this.openAiKey,
29
44
  });
30
45
  }
31
- // Decide on image dimensions
32
- let size = "1792x1024";
33
- if (type === "logo") {
34
- size = "1792x1024";
35
- }
36
- else if (type === "icon") {
37
- size = "1024x1024";
38
- }
39
- const modelQuality = "standard";
46
+ const size = options?.imageSize ||
47
+ getDefaultImageSizeForOptions(imageProvider, requestedModel, type) ||
48
+ "1792x1024";
49
+ const modelQuality = options?.imageQuality ||
50
+ getDefaultImageQualityForOptions(imageProvider, requestedModel) ||
51
+ "standard";
40
52
  while (retrying && retryCount < this.maxRetryCount) {
41
53
  try {
42
54
  // If using Azure OpenAI
43
- if (this.azureOpenaAiBase && this.azureOpenAiApiKey && this.azureDalleDeployment) {
55
+ if (useAzureOpenAi) {
44
56
  result = await client.images.generate({
45
57
  prompt,
46
58
  n: 1,
47
- size,
59
+ size: size,
48
60
  quality: modelQuality,
49
61
  });
50
62
  }
51
63
  else {
52
64
  // Standard OpenAI
53
65
  result = await client.images.generate({
54
- model: "dall-e-3",
66
+ model: requestedModel,
55
67
  prompt,
56
68
  n: 1,
57
- size,
69
+ size: size,
58
70
  quality: modelQuality,
59
71
  });
60
72
  }
@@ -1,9 +1,9 @@
1
- import { IImageGenerator, YpAiGenerateImageTypes } from "./iImageGenerator.js";
1
+ import { IImageGenerator, YpAiGenerateImageTypes, YpImageGenerationOptions } from "./iImageGenerator.js";
2
2
  export declare class FluxImageGenerator implements IImageGenerator {
3
3
  private replicateApiKey;
4
4
  private fluxProModelName;
5
5
  private replicate;
6
6
  private maxRetryCount;
7
7
  constructor(replicateApiKey: string, fluxProModelName: string);
8
- generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes): Promise<string | undefined>;
8
+ generateImageUrl(prompt: string, type?: YpAiGenerateImageTypes, options?: YpImageGenerationOptions): Promise<string | undefined>;
9
9
  }