fix: altera geracao de imagem do gemini para generateContent nativo com responseModalities
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m5s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m5s
Details
This commit is contained in:
parent
85b43e6608
commit
eb70433ae1
|
|
@ -1661,62 +1661,102 @@ app.post('/api/ai/generate-image', async (req, res) => {
|
||||||
imageUrl = resJson.data?.[0]?.url;
|
imageUrl = resJson.data?.[0]?.url;
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Usar Google Gemini Imagen via OpenAI Compatibility Layer
|
// Usar Google Gemini Imagen via API Nativa generateContent com responseModalities
|
||||||
const geminiImageModel = aiConfig.geminiImageModel || 'gemini-2.5-flash-image';
|
const geminiImageModel = aiConfig.geminiImageModel || 'gemini-2.5-flash-image';
|
||||||
const imagenUrl = `https://generativelanguage.googleapis.com/v1beta/openai/images/generations?key=${apiKey}`;
|
|
||||||
const payload = {
|
const tentarGerarGemini = async (modelName) => {
|
||||||
prompt: `Ilustração educativa flat vector sobre: ${prompt}`,
|
const url = `https://generativelanguage.googleapis.com/v1beta/models/${modelName}:generateContent?key=${apiKey}`;
|
||||||
n: 1,
|
const payload = {
|
||||||
size: '1024x1024',
|
contents: [
|
||||||
model: geminiImageModel
|
{
|
||||||
|
parts: [
|
||||||
|
{
|
||||||
|
text: `Gere uma ilustração educativa, estilo flat vector minimalista, limpa e com fundo neutro sobre o tema: ${prompt}`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
generationConfig: {
|
||||||
|
responseModalities: ["TEXT", "IMAGE"]
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch(url, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errText = await response.text();
|
||||||
|
throw new Error(`Erro HTTP ${response.status}: ${errText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resJson = await response.json();
|
||||||
|
|
||||||
|
let base64 = null;
|
||||||
|
if (resJson.candidates?.[0]?.content?.parts) {
|
||||||
|
for (const part of resJson.candidates[0].content.parts) {
|
||||||
|
if (part.inlineData && part.inlineData.data) {
|
||||||
|
base64 = part.inlineData.data;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return base64;
|
||||||
};
|
};
|
||||||
|
|
||||||
let response = await fetch(imagenUrl, {
|
let base64ImageBytes = null;
|
||||||
method: 'POST',
|
let modelUsed = geminiImageModel;
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
|
|
||||||
// Se der 404, tentar com o modelo gemini-2.5-flash-image de fallback
|
try {
|
||||||
if (!response.ok && response.status === 404 && geminiImageModel !== 'gemini-2.5-flash-image') {
|
console.log(`[Gemini Imagen] Tentando gerar com o modelo selecionado: ${modelUsed}`);
|
||||||
console.warn(`[Gemini Imagen] Modelo ${geminiImageModel} retornou 404, tentando gemini-2.5-flash-image de fallback...`);
|
base64ImageBytes = await tentarGerarGemini(modelUsed);
|
||||||
payload.model = 'gemini-2.5-flash-image';
|
} catch (err) {
|
||||||
response = await fetch(imagenUrl, {
|
console.warn(`[Gemini Imagen] Falha no modelo principal ${modelUsed}:`, err.message);
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify(payload)
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Se der 404 novamente, tentar com gemini-3.1-flash-image
|
// Fallback 1: gemini-2.5-flash-image
|
||||||
if (!response.ok && response.status === 404 && payload.model !== 'gemini-3.1-flash-image') {
|
if (!base64ImageBytes && modelUsed !== 'gemini-2.5-flash-image') {
|
||||||
console.warn(`[Gemini Imagen] Tentando gemini-3.1-flash-image de fallback...`);
|
try {
|
||||||
payload.model = 'gemini-3.1-flash-image';
|
modelUsed = 'gemini-2.5-flash-image';
|
||||||
response = await fetch(imagenUrl, {
|
console.log(`[Gemini Imagen] Tentando fallback para: ${modelUsed}`);
|
||||||
method: 'POST',
|
base64ImageBytes = await tentarGerarGemini(modelUsed);
|
||||||
headers: { 'Content-Type': 'application/json' },
|
} catch (err) {
|
||||||
body: JSON.stringify(payload)
|
console.warn(`[Gemini Imagen] Falha no fallback ${modelUsed}:`, err.message);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Fallback final para imagen-3.0-generate-002 se as flash image falharem
|
// Fallback 2: gemini-3.1-flash-image
|
||||||
if (!response.ok && response.status === 404 && payload.model !== 'imagen-3.0-generate-002') {
|
if (!base64ImageBytes && modelUsed !== 'gemini-3.1-flash-image') {
|
||||||
console.warn(`[Gemini Imagen] Tentando imagen-3.0-generate-002 de fallback final...`);
|
try {
|
||||||
payload.model = 'imagen-3.0-generate-002';
|
modelUsed = 'gemini-3.1-flash-image';
|
||||||
response = await fetch(imagenUrl, {
|
console.log(`[Gemini Imagen] Tentando fallback para: ${modelUsed}`);
|
||||||
method: 'POST',
|
base64ImageBytes = await tentarGerarGemini(modelUsed);
|
||||||
headers: { 'Content-Type': 'application/json' },
|
} catch (err) {
|
||||||
body: JSON.stringify(payload)
|
console.warn(`[Gemini Imagen] Falha no fallback ${modelUsed}:`, err.message);
|
||||||
});
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
// Fallback 3: gemini-2.0-flash-exp (experimental)
|
||||||
const errText = await response.text();
|
if (!base64ImageBytes && modelUsed !== 'gemini-2.0-flash-exp') {
|
||||||
throw new Error(`Gemini Imagen retornou erro: ${response.status} - ${errText}`);
|
try {
|
||||||
|
modelUsed = 'gemini-2.0-flash-exp';
|
||||||
|
console.log(`[Gemini Imagen] Tentando fallback para: ${modelUsed}`);
|
||||||
|
base64ImageBytes = await tentarGerarGemini(modelUsed);
|
||||||
|
} catch (err) {
|
||||||
|
console.warn(`[Gemini Imagen] Falha no fallback ${modelUsed}:`, err.message);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
const resJson = await response.json();
|
if (!base64ImageBytes) {
|
||||||
imageUrl = resJson.data?.[0]?.url;
|
throw new Error('Não foi possível gerar a imagem em nenhum dos modelos Gemini disponíveis. Verifique sua chave de API ou se a cota foi atingida.');
|
||||||
|
}
|
||||||
|
|
||||||
|
const imageBuffer = Buffer.from(base64ImageBytes, 'base64');
|
||||||
|
const { uploadExamImage } = await import('./services/storage.js');
|
||||||
|
const savedUrl = await uploadExamImage(imageBuffer, 'image/png');
|
||||||
|
return res.json({ url: savedUrl });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue