@interware/content-generator 0.7.0 → 0.7.1
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/dist/server/index.js
CHANGED
|
@@ -266,14 +266,11 @@ const safetySettings = (safety) => HARM_CATEGORIES.map((category) => ({
|
|
|
266
266
|
category,
|
|
267
267
|
threshold: safety ? genai.HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE : genai.HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
268
268
|
}));
|
|
269
|
-
const
|
|
270
|
-
none: "ALLOW_NONE",
|
|
271
|
-
adults: genai.PersonGeneration.ALLOW_ADULT
|
|
272
|
-
};
|
|
269
|
+
const NO_PEOPLE = "ALLOW_NONE";
|
|
273
270
|
const imageConfig = ({ aspectRatio, imageSize, allowPeople }, backend = "gemini-api") => ({
|
|
274
271
|
...aspectRatio ? { aspectRatio } : {},
|
|
275
272
|
...imageSize ? { imageSize } : {},
|
|
276
|
-
...backend === "vertex"
|
|
273
|
+
...backend === "vertex" && !allowPeople ? { personGeneration: NO_PEOPLE } : {}
|
|
277
274
|
});
|
|
278
275
|
const promptPart = (text2, allowPeople, backend = "gemini-api") => ({
|
|
279
276
|
text: backend === "gemini-api" && !allowPeople ? `${text2} ${PEOPLE_CLAUSE}` : text2
|
|
@@ -339,6 +336,10 @@ const EXPAND_INSTRUCTION = (concept) => [
|
|
|
339
336
|
`Concepto: ${concept}`
|
|
340
337
|
].join("\n");
|
|
341
338
|
class BlockedError extends Error {
|
|
339
|
+
constructor(message, detail) {
|
|
340
|
+
super(message);
|
|
341
|
+
this.detail = detail;
|
|
342
|
+
}
|
|
342
343
|
}
|
|
343
344
|
const sourceOf = (chunk) => {
|
|
344
345
|
if (chunk.web?.uri) return { title: chunk.web.title || new URL(chunk.web.uri).hostname, uri: chunk.web.uri };
|
|
@@ -360,12 +361,21 @@ const BLOCK_REASONS = {
|
|
|
360
361
|
PROHIBITED_CONTENT: "El modelo bloqueó la solicitud por contenido no permitido.",
|
|
361
362
|
IMAGE_SAFETY: "El modelo bloqueó la imagen resultante por seguridad."
|
|
362
363
|
};
|
|
364
|
+
const blockMessage = (reason, googleMessage) => {
|
|
365
|
+
const code = googleMessage.match(/Support code:\s*(\d+)/i)?.[1];
|
|
366
|
+
const suffix = code ? ` (Código de Google: ${code})` : "";
|
|
367
|
+
if (/PersonGeneration/i.test(googleMessage)) {
|
|
368
|
+
return /input image/i.test(googleMessage) ? `Google rechazó la imagen de referencia por la configuración de «Personas». Prueba con otra imagen o cambia «Personas».${suffix}` : `Google filtró la imagen generada por la configuración de «Personas». Cambia el prompt o «Personas».${suffix}`;
|
|
369
|
+
}
|
|
370
|
+
return `${BLOCK_REASONS[reason] ?? "El modelo no devolvió una imagen. Intenta con otro prompt."}${suffix}`;
|
|
371
|
+
};
|
|
363
372
|
const imageFrom = (res, model) => {
|
|
364
373
|
const parts = res.candidates?.[0]?.content?.parts ?? [];
|
|
365
374
|
const part = parts.find((p) => p.inlineData?.data && (p.inlineData.mimeType ?? "").startsWith("image/"));
|
|
366
375
|
if (part?.inlineData?.data) return { image: part.inlineData.data, mimeType: part.inlineData.mimeType ?? "image/png", model };
|
|
367
|
-
const reason = res.promptFeedback?.blockReason ?? res.candidates?.[0]?.finishReason;
|
|
368
|
-
|
|
376
|
+
const reason = String(res.promptFeedback?.blockReason ?? res.candidates?.[0]?.finishReason ?? "");
|
|
377
|
+
const googleMessage = [res.candidates?.[0]?.finishMessage, res.promptFeedback?.blockReasonMessage].filter(Boolean).join(" ");
|
|
378
|
+
throw new BlockedError(blockMessage(reason, googleMessage), [reason, googleMessage].filter(Boolean).join(": ") || void 0);
|
|
369
379
|
};
|
|
370
380
|
const inline = async (file, read) => inlinePart(await read(file.filepath), file.mimetype ?? "image/png");
|
|
371
381
|
const userTurn = (parts) => [{ role: "user", parts }];
|
|
@@ -486,7 +496,10 @@ const imageStudio = ({ strapi }) => {
|
|
|
486
496
|
ctx.body = await work();
|
|
487
497
|
} catch (err) {
|
|
488
498
|
if (err instanceof InputError) return fail$3(ctx, 400, "BadRequestError", err.message);
|
|
489
|
-
if (err instanceof BlockedError)
|
|
499
|
+
if (err instanceof BlockedError) {
|
|
500
|
+
strapi.log.warn(`[image-studio] bloqueo: ${err.detail ?? err.message}`);
|
|
501
|
+
return fail$3(ctx, 422, "UnprocessableEntityError", err.message);
|
|
502
|
+
}
|
|
490
503
|
strapi.log.error(`[image-studio] ${err instanceof Error ? err.message : String(err)}`);
|
|
491
504
|
return fail$3(ctx, 502, "BadGatewayError", "El servicio de imágenes no respondió. Intenta de nuevo en un momento.");
|
|
492
505
|
}
|
package/dist/server/index.mjs
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import * as fs from "fs";
|
|
2
2
|
import { promises } from "fs";
|
|
3
|
-
import { HarmCategory,
|
|
3
|
+
import { HarmCategory, Modality, HarmBlockThreshold, GoogleGenAI, VideoGenerationReferenceType } from "@google/genai";
|
|
4
4
|
const PLUGIN_ID = "strapi-content-generator-gemini";
|
|
5
5
|
const FOLDER_UID = "plugin::upload.folder";
|
|
6
6
|
const hasValidPath = (path) => /^(\/\d+)+$/.test(path);
|
|
@@ -248,14 +248,11 @@ const safetySettings = (safety) => HARM_CATEGORIES.map((category) => ({
|
|
|
248
248
|
category,
|
|
249
249
|
threshold: safety ? HarmBlockThreshold.BLOCK_MEDIUM_AND_ABOVE : HarmBlockThreshold.BLOCK_ONLY_HIGH
|
|
250
250
|
}));
|
|
251
|
-
const
|
|
252
|
-
none: "ALLOW_NONE",
|
|
253
|
-
adults: PersonGeneration.ALLOW_ADULT
|
|
254
|
-
};
|
|
251
|
+
const NO_PEOPLE = "ALLOW_NONE";
|
|
255
252
|
const imageConfig = ({ aspectRatio, imageSize, allowPeople }, backend = "gemini-api") => ({
|
|
256
253
|
...aspectRatio ? { aspectRatio } : {},
|
|
257
254
|
...imageSize ? { imageSize } : {},
|
|
258
|
-
...backend === "vertex"
|
|
255
|
+
...backend === "vertex" && !allowPeople ? { personGeneration: NO_PEOPLE } : {}
|
|
259
256
|
});
|
|
260
257
|
const promptPart = (text2, allowPeople, backend = "gemini-api") => ({
|
|
261
258
|
text: backend === "gemini-api" && !allowPeople ? `${text2} ${PEOPLE_CLAUSE}` : text2
|
|
@@ -321,6 +318,10 @@ const EXPAND_INSTRUCTION = (concept) => [
|
|
|
321
318
|
`Concepto: ${concept}`
|
|
322
319
|
].join("\n");
|
|
323
320
|
class BlockedError extends Error {
|
|
321
|
+
constructor(message, detail) {
|
|
322
|
+
super(message);
|
|
323
|
+
this.detail = detail;
|
|
324
|
+
}
|
|
324
325
|
}
|
|
325
326
|
const sourceOf = (chunk) => {
|
|
326
327
|
if (chunk.web?.uri) return { title: chunk.web.title || new URL(chunk.web.uri).hostname, uri: chunk.web.uri };
|
|
@@ -342,12 +343,21 @@ const BLOCK_REASONS = {
|
|
|
342
343
|
PROHIBITED_CONTENT: "El modelo bloqueó la solicitud por contenido no permitido.",
|
|
343
344
|
IMAGE_SAFETY: "El modelo bloqueó la imagen resultante por seguridad."
|
|
344
345
|
};
|
|
346
|
+
const blockMessage = (reason, googleMessage) => {
|
|
347
|
+
const code = googleMessage.match(/Support code:\s*(\d+)/i)?.[1];
|
|
348
|
+
const suffix = code ? ` (Código de Google: ${code})` : "";
|
|
349
|
+
if (/PersonGeneration/i.test(googleMessage)) {
|
|
350
|
+
return /input image/i.test(googleMessage) ? `Google rechazó la imagen de referencia por la configuración de «Personas». Prueba con otra imagen o cambia «Personas».${suffix}` : `Google filtró la imagen generada por la configuración de «Personas». Cambia el prompt o «Personas».${suffix}`;
|
|
351
|
+
}
|
|
352
|
+
return `${BLOCK_REASONS[reason] ?? "El modelo no devolvió una imagen. Intenta con otro prompt."}${suffix}`;
|
|
353
|
+
};
|
|
345
354
|
const imageFrom = (res, model) => {
|
|
346
355
|
const parts = res.candidates?.[0]?.content?.parts ?? [];
|
|
347
356
|
const part = parts.find((p) => p.inlineData?.data && (p.inlineData.mimeType ?? "").startsWith("image/"));
|
|
348
357
|
if (part?.inlineData?.data) return { image: part.inlineData.data, mimeType: part.inlineData.mimeType ?? "image/png", model };
|
|
349
|
-
const reason = res.promptFeedback?.blockReason ?? res.candidates?.[0]?.finishReason;
|
|
350
|
-
|
|
358
|
+
const reason = String(res.promptFeedback?.blockReason ?? res.candidates?.[0]?.finishReason ?? "");
|
|
359
|
+
const googleMessage = [res.candidates?.[0]?.finishMessage, res.promptFeedback?.blockReasonMessage].filter(Boolean).join(" ");
|
|
360
|
+
throw new BlockedError(blockMessage(reason, googleMessage), [reason, googleMessage].filter(Boolean).join(": ") || void 0);
|
|
351
361
|
};
|
|
352
362
|
const inline = async (file, read) => inlinePart(await read(file.filepath), file.mimetype ?? "image/png");
|
|
353
363
|
const userTurn = (parts) => [{ role: "user", parts }];
|
|
@@ -468,7 +478,10 @@ const imageStudio = ({ strapi }) => {
|
|
|
468
478
|
ctx.body = await work();
|
|
469
479
|
} catch (err) {
|
|
470
480
|
if (err instanceof InputError) return fail$3(ctx, 400, "BadRequestError", err.message);
|
|
471
|
-
if (err instanceof BlockedError)
|
|
481
|
+
if (err instanceof BlockedError) {
|
|
482
|
+
strapi.log.warn(`[image-studio] bloqueo: ${err.detail ?? err.message}`);
|
|
483
|
+
return fail$3(ctx, 422, "UnprocessableEntityError", err.message);
|
|
484
|
+
}
|
|
472
485
|
strapi.log.error(`[image-studio] ${err instanceof Error ? err.message : String(err)}`);
|
|
473
486
|
return fail$3(ctx, 502, "BadGatewayError", "El servicio de imágenes no respondió. Intenta de nuevo en un momento.");
|
|
474
487
|
}
|
|
@@ -4,8 +4,13 @@ import type { Core } from '@strapi/strapi';
|
|
|
4
4
|
import type { GoogleGenAI } from '@google/genai';
|
|
5
5
|
import type { Backend } from '../image-studio/geminiConfig';
|
|
6
6
|
import type { EditInput, GenerateInput } from '../image-studio/validate';
|
|
7
|
-
/**
|
|
7
|
+
/**
|
|
8
|
+
* El modelo no devolvió imagen (bloqueo de seguridad u otra causa): el controlador responde 422 con
|
|
9
|
+
* `message` y registra `detail` (motivo y mensaje de Google, con su código de soporte).
|
|
10
|
+
*/
|
|
8
11
|
export declare class BlockedError extends Error {
|
|
12
|
+
readonly detail?: string;
|
|
13
|
+
constructor(message: string, detail?: string);
|
|
9
14
|
}
|
|
10
15
|
type ModelsApi = Pick<GoogleGenAI['models'], 'generateContent' | 'list'>;
|
|
11
16
|
type ReadFile = (path: string) => Promise<Buffer>;
|
package/package.json
CHANGED