feat: implement image size configuration and enhance image response handling in AI service
This commit is contained in:
@@ -1379,53 +1379,65 @@ Responde de forma directa y útil:`;
|
|||||||
}
|
}
|
||||||
|
|
||||||
const mimeType = options?.mimeType ?? 'image/png';
|
const mimeType = options?.mimeType ?? 'image/png';
|
||||||
|
const size = options?.size ?? 'square';
|
||||||
|
const imageSize = size === 'portrait' ? '9:16' : size === 'landscape' ? '16:9' : '1:1';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res: any = await (this.genAIv2 as any).models.generateContent({
|
// Preferir generateImages (SDK moderno) cuando está disponible
|
||||||
|
const res: any = await (this.genAIv2 as any).models.generateImages({
|
||||||
model: 'gemini-2.5-flash-image',
|
model: 'gemini-2.5-flash-image',
|
||||||
contents: prompt,
|
|
||||||
prompt,
|
prompt,
|
||||||
config: {
|
config: {
|
||||||
responseMimeType: mimeType,
|
responseMimeType: mimeType,
|
||||||
responseModalities: ['IMAGE'],
|
imageSize,
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Normalize response shape
|
// Respuesta típica: { images: [{ data, mimeType }] }
|
||||||
const response = res?.response ?? res;
|
|
||||||
const candidates = response?.candidates ?? [];
|
|
||||||
const parts = candidates[0]?.content?.parts ?? [];
|
|
||||||
let base64: string | undefined;
|
let base64: string | undefined;
|
||||||
let outMime: string | undefined;
|
let outMime: string | undefined;
|
||||||
|
|
||||||
const imgPart = parts.find((p: any) => p?.inlineData?.data || p?.imageData?.data || p?.media?.data);
|
if (Array.isArray(res?.images) && res.images.length > 0) {
|
||||||
if (imgPart?.inlineData?.data) {
|
const first = res.images[0];
|
||||||
base64 = imgPart.inlineData.data;
|
base64 = first?.data || first?.b64Data || first?.inlineData?.data;
|
||||||
outMime = imgPart.inlineData.mimeType;
|
outMime = first?.mimeType || first?.inlineData?.mimeType;
|
||||||
} else if (imgPart?.imageData?.data) {
|
|
||||||
base64 = imgPart.imageData.data;
|
|
||||||
outMime = imgPart.imageData.mimeType;
|
|
||||||
} else if (imgPart?.media?.data) {
|
|
||||||
base64 = imgPart.media.data;
|
|
||||||
outMime = imgPart.media.mimeType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Fallbacks para formas alternativas
|
||||||
|
if (!base64 && res?.image?.data) {
|
||||||
|
base64 = res.image.data;
|
||||||
|
outMime = res.image.mimeType;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback a generateContent si generateImages no retorna 'images'
|
||||||
if (!base64) {
|
if (!base64) {
|
||||||
// Try other top-level shapes
|
const alt: any = await (this.genAIv2 as any).models.generateContent({
|
||||||
if (res?.image?.data) {
|
model: 'gemini-2.5-flash-image',
|
||||||
base64 = res.image.data;
|
contents: prompt,
|
||||||
outMime = res.image.mimeType;
|
config: {
|
||||||
} else if (Array.isArray(res?.images) && res.images.length > 0) {
|
responseMimeType: mimeType,
|
||||||
const first = res.images[0];
|
responseModalities: ['IMAGE'],
|
||||||
base64 = first.data || first.b64Data || first.inlineData?.data;
|
imageSize,
|
||||||
outMime = first.mimeType || first.inlineData?.mimeType;
|
}
|
||||||
} else if (response?.media?.data) {
|
});
|
||||||
base64 = response.media.data;
|
const response = alt?.response ?? alt;
|
||||||
outMime = response.media.mimeType;
|
const candidates = response?.candidates ?? [];
|
||||||
|
const parts = candidates[0]?.content?.parts ?? [];
|
||||||
|
const imgPart = parts.find((p: any) => p?.inlineData?.data || p?.imageData?.data || p?.media?.data);
|
||||||
|
if (imgPart?.inlineData?.data) {
|
||||||
|
base64 = imgPart.inlineData.data;
|
||||||
|
outMime = imgPart.inlineData.mimeType;
|
||||||
|
} else if (imgPart?.imageData?.data) {
|
||||||
|
base64 = imgPart.imageData.data;
|
||||||
|
outMime = imgPart.imageData.mimeType;
|
||||||
|
} else if (imgPart?.media?.data) {
|
||||||
|
base64 = imgPart.media.data;
|
||||||
|
outMime = imgPart.media.mimeType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!base64) {
|
if (!base64) {
|
||||||
|
logger.error({ res }, 'Respuesta de imagen sin datos de imagen');
|
||||||
throw new Error('No se recibió imagen del modelo');
|
throw new Error('No se recibió imagen del modelo');
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -1437,8 +1449,13 @@ Responde de forma directa y útil:`;
|
|||||||
|
|
||||||
return { data: Buffer.from(base64, 'base64'), mimeType: finalMime, fileName };
|
return { data: Buffer.from(base64, 'base64'), mimeType: finalMime, fileName };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = this.parseAPIError(e);
|
// Log completo del error original para depuración
|
||||||
throw new Error(msg);
|
logger.error(e as any, 'Fallo en generateImage');
|
||||||
|
const parsed = this.parseAPIError(e);
|
||||||
|
const original = getErrorMessage(e);
|
||||||
|
// Conservar mensaje original si el parser no aporta más contexto
|
||||||
|
const message = parsed === 'Error temporal del servicio de IA. Intenta de nuevo' ? original : parsed;
|
||||||
|
throw new Error(message || parsed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user