fix: corrige supportedGenerationMethods do Gemini na listagem de modelos, adiciona visual condicional por provedor e auto-loading de modelos
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m8s Details

This commit is contained in:
Sidney 2026-07-17 22:10:45 -03:00
parent 643d3f9d33
commit 727942b92c
2 changed files with 241 additions and 185 deletions

View File

@ -1,4 +1,4 @@
import React, { useState, useRef } from 'react';
import React, { useState, useRef, useEffect } from 'react';
import { SchoolData, Exam, Question } from '../types';
import { FileText, Plus, Search, BookOpen, Upload, Trash2, ArrowLeft, Save, CheckCircle, Image as ImageIcon, X, RefreshCw, Lock, Unlock, AlertTriangle, Copy, Bell, Sparkles, Wand2 } from 'lucide-react';
import { uploadExamImage } from '../services/supabase';
@ -87,7 +87,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
setShowAIConfigModal(true);
};
const handleFetchModels = async (provider: 'gemini' | 'openai' | 'claude' | 'openrouter') => {
const handleFetchModels = async (provider: 'gemini' | 'openai' | 'claude' | 'openrouter', silent = false) => {
setLoadingModels(prev => ({ ...prev, [provider]: true }));
try {
let key = '';
@ -110,18 +110,38 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
const { models } = await response.json();
if (models && models.length > 0) {
setDynamicModels(prev => ({ ...prev, [provider]: models }));
if (!silent) {
showAlert('Sucesso', `Lista de modelos para ${provider.toUpperCase()} atualizada!`, 'success');
}
} else {
throw new Error('Nenhum modelo compatível retornado.');
}
} catch (e: any) {
console.error(e);
if (!silent) {
showAlert('Erro ao listar modelos', e.message || 'Falha ao buscar modelos, verifique sua chave de API.', 'error');
}
} finally {
setLoadingModels(prev => ({ ...prev, [provider]: false }));
}
};
useEffect(() => {
if (showAIConfigModal) {
const provider = aiForm.activeProvider;
let hasKey = false;
if (provider === 'gemini') hasKey = !!aiForm.geminiKey;
else if (provider === 'openai') hasKey = !!aiForm.openaiKey;
else if (provider === 'claude') hasKey = !!aiForm.claudeKey;
else if (provider === 'openrouter') hasKey = !!aiForm.openrouterKey;
// Disparar carregamento automático e silencioso em background
if (hasKey) {
handleFetchModels(provider, true);
}
}
}, [aiForm.activeProvider, showAIConfigModal]);
const handleSaveAIConfig = async () => {
const newConfig = {
activeProvider: aiForm.activeProvider,
@ -1411,6 +1431,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
</div>
</div>
{aiForm.activeProvider === 'gemini' && (
<div className="border-t border-slate-100 pt-4">
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Google Gemini</h4>
<div className="space-y-3">
@ -1458,7 +1479,9 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
</div>
</div>
</div>
)}
{aiForm.activeProvider === 'openai' && (
<div className="border-t border-slate-100 pt-4">
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenAI (ChatGPT)</h4>
<div className="space-y-3">
@ -1506,7 +1529,9 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
</div>
</div>
</div>
)}
{aiForm.activeProvider === 'claude' && (
<div className="border-t border-slate-100 pt-4">
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Anthropic Claude</h4>
<div className="space-y-3">
@ -1554,7 +1579,9 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
</div>
</div>
</div>
)}
{aiForm.activeProvider === 'openrouter' && (
<div className="border-t border-slate-100 pt-4">
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenRouter</h4>
<div className="space-y-3">
@ -1602,6 +1629,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
</div>
</div>
</div>
)}
</div>
<div className="flex gap-3 pt-4 border-t border-slate-100 mt-5">

View File

@ -1753,12 +1753,20 @@ app.post('/api/ai/list-models', async (req, res) => {
throw new Error(`Erro Gemini: ${response.status} - ${errText}`);
}
const json = await response.json();
const models = (json.models || [])
.filter(m => m.supportedMethods && m.supportedMethods.includes('generateContent'))
let models = (json.models || [])
.filter(m => m.supportedGenerationMethods && m.supportedGenerationMethods.includes('generateContent'))
.map(m => ({
id: m.name.replace('models/', ''),
name: m.displayName || m.name
}));
if (models.length === 0) {
models = [
{ id: 'gemini-1.5-flash', name: 'gemini-1.5-flash (Mais Rápido & Gratuito)' },
{ id: 'gemini-1.5-pro', name: 'gemini-1.5-pro (Multimodal)' },
{ id: 'gemini-2.0-flash-exp', name: 'gemini-2.0-flash-exp (Experimental)' },
{ id: 'gemini-2.5-flash', name: 'gemini-2.5-flash (Se ativo na sua conta)' }
];
}
return res.json({ models });
} else if (provider === 'openai') {
@ -1771,12 +1779,19 @@ app.post('/api/ai/list-models', async (req, res) => {
throw new Error(`Erro OpenAI: ${response.status} - ${errText}`);
}
const json = await response.json();
const models = (json.data || [])
let models = (json.data || [])
.filter(m => m.id.includes('gpt') || m.id.includes('o1') || m.id.includes('o3'))
.map(m => ({
id: m.id,
name: m.id
}));
if (models.length === 0) {
models = [
{ id: 'gpt-4o-mini', name: 'gpt-4o-mini (Recomendado)' },
{ id: 'gpt-4o', name: 'gpt-4o (Completo)' },
{ id: 'gpt-3.5-turbo', name: 'gpt-3.5-turbo (Legado)' }
];
}
return res.json({ models });
} else if (provider === 'claude') {
@ -1797,10 +1812,16 @@ app.post('/api/ai/list-models', async (req, res) => {
return res.json({ models: defaultModels });
}
const json = await response.json();
const models = (json.data || []).map(m => ({
let models = (json.data || []).map(m => ({
id: m.id,
name: m.display_name || m.id
}));
if (models.length === 0) {
models = [
{ id: 'claude-3-5-sonnet-20241022', name: 'claude-3-5-sonnet-20241022' },
{ id: 'claude-3-5-haiku-20241022', name: 'claude-3-5-haiku-20241022' }
];
}
return res.json({ models });
} else if (provider === 'openrouter') {
@ -1813,10 +1834,17 @@ app.post('/api/ai/list-models', async (req, res) => {
throw new Error(`Erro OpenRouter: ${response.status} - ${errText}`);
}
const json = await response.json();
const models = (json.data || []).map(m => ({
let models = (json.data || []).map(m => ({
id: m.id,
name: `${m.name || m.id} (${m.id})`
}));
if (models.length === 0) {
models = [
{ id: 'google/gemini-2.5-flash', name: 'Gemini 2.5 Flash' },
{ id: 'openai/gpt-4o-mini', name: 'GPT-4o Mini' },
{ id: 'anthropic/claude-3.5-sonnet', name: 'Claude 3.5 Sonnet' }
];
}
return res.json({ models });
}