fix: corrige geracao de imagens do Gemini usando compatibilidade OpenAI no Google AI Studio (generativelanguage) e adicionando novos modelos no frontend
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m39s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m39s
Details
This commit is contained in:
parent
3a953a7e41
commit
85b43e6608
|
|
@ -40,7 +40,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
openrouterModel: data.aiConfig?.openrouterModel || 'google/gemini-2.5-flash',
|
openrouterModel: data.aiConfig?.openrouterModel || 'google/gemini-2.5-flash',
|
||||||
activeImageProvider: data.aiConfig?.activeImageProvider || 'gemini',
|
activeImageProvider: data.aiConfig?.activeImageProvider || 'gemini',
|
||||||
openaiImageModel: data.aiConfig?.openaiImageModel || 'dall-e-3',
|
openaiImageModel: data.aiConfig?.openaiImageModel || 'dall-e-3',
|
||||||
geminiImageModel: data.aiConfig?.geminiImageModel || 'imagen-3.0-generate-002'
|
geminiImageModel: data.aiConfig?.geminiImageModel || 'gemini-2.5-flash-image'
|
||||||
});
|
});
|
||||||
|
|
||||||
const [dynamicModels, setDynamicModels] = useState<{
|
const [dynamicModels, setDynamicModels] = useState<{
|
||||||
|
|
@ -93,7 +93,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
openrouterModel: data.aiConfig?.openrouterModel || 'google/gemini-2.5-flash',
|
openrouterModel: data.aiConfig?.openrouterModel || 'google/gemini-2.5-flash',
|
||||||
activeImageProvider: data.aiConfig?.activeImageProvider || 'gemini',
|
activeImageProvider: data.aiConfig?.activeImageProvider || 'gemini',
|
||||||
openaiImageModel: data.aiConfig?.openaiImageModel || 'dall-e-3',
|
openaiImageModel: data.aiConfig?.openaiImageModel || 'dall-e-3',
|
||||||
geminiImageModel: data.aiConfig?.geminiImageModel || 'imagen-3.0-generate-002'
|
geminiImageModel: data.aiConfig?.geminiImageModel || 'gemini-2.5-flash-image'
|
||||||
});
|
});
|
||||||
setShowAIConfigModal(true);
|
setShowAIConfigModal(true);
|
||||||
};
|
};
|
||||||
|
|
@ -1467,7 +1467,9 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
onChange={e => setAiForm({ ...aiForm, geminiImageModel: e.target.value })}
|
onChange={e => setAiForm({ ...aiForm, geminiImageModel: e.target.value })}
|
||||||
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-bold text-slate-700 text-sm"
|
className="w-full px-4 py-3 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-bold text-slate-700 text-sm"
|
||||||
>
|
>
|
||||||
<option value="imagen-3.0-generate-002">Imagen 3.0 Generate 002 (Padrão)</option>
|
<option value="gemini-2.5-flash-image">Gemini 2.5 Flash Image (Recomendado)</option>
|
||||||
|
<option value="gemini-3.1-flash-image">Gemini 3.1 Flash Image (Mais Recente)</option>
|
||||||
|
<option value="imagen-3.0-generate-002">Imagen 3.0 (Legado)</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
)}
|
)}
|
||||||
|
|
|
||||||
|
|
@ -1661,43 +1661,62 @@ 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 3
|
// Usar Google Gemini Imagen via OpenAI Compatibility Layer
|
||||||
const geminiImageModel = aiConfig.geminiImageModel || 'imagen-3.0-generate-002';
|
const geminiImageModel = aiConfig.geminiImageModel || 'gemini-2.5-flash-image';
|
||||||
const imagenUrl = `https://generativelanguage.googleapis.com/v1beta/models/${geminiImageModel}:predict?key=${apiKey}`;
|
const imagenUrl = `https://generativelanguage.googleapis.com/v1beta/openai/images/generations?key=${apiKey}`;
|
||||||
const payload = {
|
const payload = {
|
||||||
instances: [
|
prompt: `Ilustração educativa flat vector sobre: ${prompt}`,
|
||||||
{
|
n: 1,
|
||||||
prompt: `Ilustração educativa flat vector sobre: ${prompt}`
|
size: '1024x1024',
|
||||||
}
|
model: geminiImageModel
|
||||||
],
|
|
||||||
parameters: {
|
|
||||||
sampleCount: 1,
|
|
||||||
aspectRatio: "1:1",
|
|
||||||
outputMimeType: "image/jpeg"
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const response = await fetch(imagenUrl, {
|
let response = await fetch(imagenUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify(payload)
|
body: JSON.stringify(payload)
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Se der 404, tentar com o modelo gemini-2.5-flash-image de fallback
|
||||||
|
if (!response.ok && response.status === 404 && geminiImageModel !== 'gemini-2.5-flash-image') {
|
||||||
|
console.warn(`[Gemini Imagen] Modelo ${geminiImageModel} retornou 404, tentando gemini-2.5-flash-image de fallback...`);
|
||||||
|
payload.model = 'gemini-2.5-flash-image';
|
||||||
|
response = await fetch(imagenUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Se der 404 novamente, tentar com gemini-3.1-flash-image
|
||||||
|
if (!response.ok && response.status === 404 && payload.model !== 'gemini-3.1-flash-image') {
|
||||||
|
console.warn(`[Gemini Imagen] Tentando gemini-3.1-flash-image de fallback...`);
|
||||||
|
payload.model = 'gemini-3.1-flash-image';
|
||||||
|
response = await fetch(imagenUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fallback final para imagen-3.0-generate-002 se as flash image falharem
|
||||||
|
if (!response.ok && response.status === 404 && payload.model !== 'imagen-3.0-generate-002') {
|
||||||
|
console.warn(`[Gemini Imagen] Tentando imagen-3.0-generate-002 de fallback final...`);
|
||||||
|
payload.model = 'imagen-3.0-generate-002';
|
||||||
|
response = await fetch(imagenUrl, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
if (!response.ok) {
|
if (!response.ok) {
|
||||||
const errText = await response.text();
|
const errText = await response.text();
|
||||||
throw new Error(`Gemini Imagen retornou erro: ${errText}`);
|
throw new Error(`Gemini Imagen retornou erro: ${response.status} - ${errText}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
const resJson = await response.json();
|
const resJson = await response.json();
|
||||||
const base64ImageBytes = resJson.predictions?.[0]?.bytesBase64Encoded;
|
imageUrl = resJson.data?.[0]?.url;
|
||||||
if (!base64ImageBytes) {
|
|
||||||
throw new Error('Retorno vazio da API Imagen do Gemini');
|
|
||||||
}
|
|
||||||
|
|
||||||
const imageBuffer = Buffer.from(base64ImageBytes, 'base64');
|
|
||||||
const { uploadExamImage } = await import('./services/storage.js');
|
|
||||||
const savedUrl = await uploadExamImage(imageBuffer, 'image/jpeg');
|
|
||||||
return res.json({ url: savedUrl });
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imageUrl) {
|
if (!imageUrl) {
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue