feat: enhance image generation handling and normalize response structure in AI service
This commit is contained in:
@@ -46,8 +46,7 @@ export const command: CommandMessage = {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
//@ts-ignore
|
(message.channel as any)?.sendTyping?.().catch(() => {});
|
||||||
await message.channel.sendTyping().catch(() => {});
|
|
||||||
const result = await aiService.generateImage(prompt, { size });
|
const result = await aiService.generateImage(prompt, { size });
|
||||||
|
|
||||||
await message.reply({
|
await message.reply({
|
||||||
@@ -55,7 +54,7 @@ export const command: CommandMessage = {
|
|||||||
files: [{ attachment: result.data, name: result.fileName }]
|
files: [{ attachment: result.data, name: result.fileName }]
|
||||||
});
|
});
|
||||||
} catch (error: any) {
|
} catch (error: any) {
|
||||||
logger.error('Error generando imagen:', error);
|
logger.error(error, 'Error generando imagen');
|
||||||
await message.reply({ content: `❌ Error generando imagen: ${error?.message || 'Error desconocido'}` });
|
await message.reply({ content: `❌ Error generando imagen: ${error?.message || 'Error desconocido'}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1378,59 +1378,64 @@ Responde de forma directa y útil:`;
|
|||||||
throw new Error('El SDK moderno (@google/genai) no está inicializado');
|
throw new Error('El SDK moderno (@google/genai) no está inicializado');
|
||||||
}
|
}
|
||||||
|
|
||||||
// Mapear tamaño a hints simples (si el modelo los admite)
|
|
||||||
const sizeHint = options?.size ?? 'square';
|
|
||||||
const mimeType = options?.mimeType ?? 'image/png';
|
const mimeType = options?.mimeType ?? 'image/png';
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Llamada flexible usando any para compatibilidad de tipos entre versiones
|
const res: any = await (this.genAIv2 as any).models.generateContent({
|
||||||
const res: any = await (this.genAIv2 as any).models.generateImages({
|
|
||||||
model: 'gemini-2.5-flash-image',
|
model: 'gemini-2.5-flash-image',
|
||||||
// La API moderna acepta `contents` o `prompt` según versión; usamos ambos por compatibilidad
|
|
||||||
contents: prompt,
|
contents: prompt,
|
||||||
prompt,
|
prompt,
|
||||||
config: {
|
config: {
|
||||||
// Algunos despliegues soportan indicar formato de salida
|
|
||||||
responseMimeType: mimeType,
|
responseMimeType: mimeType,
|
||||||
// Hints de tamaño en metadatos si aplica
|
responseModalities: ['IMAGE'],
|
||||||
// Nota: si no es soportado, el backend lo ignorará sin fallar
|
|
||||||
aspectRatio: sizeHint === 'portrait' ? '9:16' : sizeHint === 'landscape' ? '16:9' : '1:1',
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Intentar obtener el primer resultado de imagen en varias formas comunes
|
// Normalize response shape
|
||||||
let imageDataBase64: string | undefined;
|
const response = res?.response ?? res;
|
||||||
let resultMime: string = mimeType;
|
const candidates = response?.candidates ?? [];
|
||||||
let fileName = `gen_${Date.now()}.${mimeType.includes('png') ? 'png' : mimeType.includes('jpeg') ? 'jpg' : 'img'}`;
|
const parts = candidates[0]?.content?.parts ?? [];
|
||||||
|
let base64: string | undefined;
|
||||||
|
let outMime: string | undefined;
|
||||||
|
|
||||||
if (res?.image?.data) {
|
const imgPart = parts.find((p: any) => p?.inlineData?.data || p?.imageData?.data || p?.media?.data);
|
||||||
imageDataBase64 = res.image.data;
|
if (imgPart?.inlineData?.data) {
|
||||||
resultMime = res.image.mimeType ?? resultMime;
|
base64 = imgPart.inlineData.data;
|
||||||
} else if (Array.isArray(res?.images) && res.images.length > 0) {
|
outMime = imgPart.inlineData.mimeType;
|
||||||
// Some SDKs return { images: [{ data, mimeType }] }
|
} else if (imgPart?.imageData?.data) {
|
||||||
imageDataBase64 = res.images[0].data ?? res.images[0].inlineData?.data;
|
base64 = imgPart.imageData.data;
|
||||||
resultMime = res.images[0].mimeType ?? res.images[0].inlineData?.mimeType ?? resultMime;
|
outMime = imgPart.imageData.mimeType;
|
||||||
} else if (res?.response?.candidates?.[0]?.content?.parts) {
|
} else if (imgPart?.media?.data) {
|
||||||
// Fallback: imagen como inlineData en parts
|
base64 = imgPart.media.data;
|
||||||
const parts = res.response.candidates[0].content.parts;
|
outMime = imgPart.media.mimeType;
|
||||||
const imgPart = parts.find((p: any) => p.inlineData && p.inlineData.data);
|
}
|
||||||
if (imgPart) {
|
|
||||||
imageDataBase64 = imgPart.inlineData.data;
|
if (!base64) {
|
||||||
resultMime = imgPart.inlineData.mimeType ?? resultMime;
|
// Try other top-level shapes
|
||||||
|
if (res?.image?.data) {
|
||||||
|
base64 = res.image.data;
|
||||||
|
outMime = res.image.mimeType;
|
||||||
|
} else if (Array.isArray(res?.images) && res.images.length > 0) {
|
||||||
|
const first = res.images[0];
|
||||||
|
base64 = first.data || first.b64Data || first.inlineData?.data;
|
||||||
|
outMime = first.mimeType || first.inlineData?.mimeType;
|
||||||
|
} else if (response?.media?.data) {
|
||||||
|
base64 = response.media.data;
|
||||||
|
outMime = response.media.mimeType;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imageDataBase64) {
|
if (!base64) {
|
||||||
throw new Error('No se recibió imagen del modelo');
|
throw new Error('No se recibió imagen del modelo');
|
||||||
}
|
}
|
||||||
|
|
||||||
const buffer = Buffer.from(imageDataBase64, 'base64');
|
const finalMime = outMime || mimeType;
|
||||||
// Ajustar nombre de archivo segun mimetype real
|
let fileName = `gen_${Date.now()}.img`;
|
||||||
if (resultMime.includes('png')) fileName = fileName.replace(/\.[^.]+$/, '.png');
|
if (finalMime.includes('png')) fileName = fileName.replace(/\.img$/, '.png');
|
||||||
else if (resultMime.includes('jpeg') || resultMime.includes('jpg')) fileName = fileName.replace(/\.[^.]+$/, '.jpg');
|
else if (finalMime.includes('jpeg') || finalMime.includes('jpg')) fileName = fileName.replace(/\.img$/, '.jpg');
|
||||||
else if (resultMime.includes('webp')) fileName = fileName.replace(/\.[^.]+$/, '.webp');
|
else if (finalMime.includes('webp')) fileName = fileName.replace(/\.img$/, '.webp');
|
||||||
|
|
||||||
return { data: buffer, mimeType: resultMime, fileName };
|
return { data: Buffer.from(base64, 'base64'), mimeType: finalMime, fileName };
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
const msg = this.parseAPIError(e);
|
const msg = this.parseAPIError(e);
|
||||||
throw new Error(msg);
|
throw new Error(msg);
|
||||||
|
|||||||
Reference in New Issue
Block a user