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
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m8s
Details
This commit is contained in:
parent
643d3f9d33
commit
727942b92c
|
|
@ -1,4 +1,4 @@
|
||||||
import React, { useState, useRef } from 'react';
|
import React, { useState, useRef, useEffect } from 'react';
|
||||||
import { SchoolData, Exam, Question } from '../types';
|
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 { 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';
|
import { uploadExamImage } from '../services/supabase';
|
||||||
|
|
@ -87,7 +87,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
setShowAIConfigModal(true);
|
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 }));
|
setLoadingModels(prev => ({ ...prev, [provider]: true }));
|
||||||
try {
|
try {
|
||||||
let key = '';
|
let key = '';
|
||||||
|
|
@ -110,18 +110,38 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
const { models } = await response.json();
|
const { models } = await response.json();
|
||||||
if (models && models.length > 0) {
|
if (models && models.length > 0) {
|
||||||
setDynamicModels(prev => ({ ...prev, [provider]: models }));
|
setDynamicModels(prev => ({ ...prev, [provider]: models }));
|
||||||
showAlert('Sucesso', `Lista de modelos para ${provider.toUpperCase()} atualizada!`, 'success');
|
if (!silent) {
|
||||||
|
showAlert('Sucesso', `Lista de modelos para ${provider.toUpperCase()} atualizada!`, 'success');
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
throw new Error('Nenhum modelo compatível retornado.');
|
throw new Error('Nenhum modelo compatível retornado.');
|
||||||
}
|
}
|
||||||
} catch (e: any) {
|
} catch (e: any) {
|
||||||
console.error(e);
|
console.error(e);
|
||||||
showAlert('Erro ao listar modelos', e.message || 'Falha ao buscar modelos, verifique sua chave de API.', 'error');
|
if (!silent) {
|
||||||
|
showAlert('Erro ao listar modelos', e.message || 'Falha ao buscar modelos, verifique sua chave de API.', 'error');
|
||||||
|
}
|
||||||
} finally {
|
} finally {
|
||||||
setLoadingModels(prev => ({ ...prev, [provider]: false }));
|
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 handleSaveAIConfig = async () => {
|
||||||
const newConfig = {
|
const newConfig = {
|
||||||
activeProvider: aiForm.activeProvider,
|
activeProvider: aiForm.activeProvider,
|
||||||
|
|
@ -1411,197 +1431,205 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="border-t border-slate-100 pt-4">
|
{aiForm.activeProvider === 'gemini' && (
|
||||||
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Google Gemini</h4>
|
<div className="border-t border-slate-100 pt-4">
|
||||||
<div className="space-y-3">
|
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Google Gemini</h4>
|
||||||
<div>
|
<div className="space-y-3">
|
||||||
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
<div>
|
||||||
<input
|
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
||||||
type="password"
|
<input
|
||||||
value={aiForm.geminiKey}
|
type="password"
|
||||||
onChange={e => setAiForm({ ...aiForm, geminiKey: e.target.value })}
|
value={aiForm.geminiKey}
|
||||||
placeholder="Cole a chave (AIzaSy...)"
|
onChange={e => setAiForm({ ...aiForm, geminiKey: e.target.value })}
|
||||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
placeholder="Cole a chave (AIzaSy...)"
|
||||||
/>
|
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div>
|
||||||
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
<div className="flex items-center justify-between mb-1">
|
||||||
<button
|
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
||||||
type="button"
|
<button
|
||||||
disabled={loadingModels['gemini']}
|
type="button"
|
||||||
onClick={() => handleFetchModels('gemini')}
|
disabled={loadingModels['gemini']}
|
||||||
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
onClick={() => handleFetchModels('gemini')}
|
||||||
>
|
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
||||||
{loadingModels['gemini'] ? (
|
>
|
||||||
<>
|
{loadingModels['gemini'] ? (
|
||||||
<RefreshCw className="animate-spin" size={10} />
|
<>
|
||||||
<span>Carregando...</span>
|
<RefreshCw className="animate-spin" size={10} />
|
||||||
</>
|
<span>Carregando...</span>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<Sparkles size={10} />
|
<>
|
||||||
<span>Carregar Modelos</span>
|
<Sparkles size={10} />
|
||||||
</>
|
<span>Carregar Modelos</span>
|
||||||
)}
|
</>
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={aiForm.geminiModel}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, geminiModel: e.target.value })}
|
||||||
|
className="w-full px-4 py-2.5 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-xs"
|
||||||
|
>
|
||||||
|
{dynamicModels.gemini.map(m => (
|
||||||
|
<option key={m.id} value={m.id}>{m.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<select
|
|
||||||
value={aiForm.geminiModel}
|
|
||||||
onChange={e => setAiForm({ ...aiForm, geminiModel: e.target.value })}
|
|
||||||
className="w-full px-4 py-2.5 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-xs"
|
|
||||||
>
|
|
||||||
{dynamicModels.gemini.map(m => (
|
|
||||||
<option key={m.id} value={m.id}>{m.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="border-t border-slate-100 pt-4">
|
{aiForm.activeProvider === 'openai' && (
|
||||||
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenAI (ChatGPT)</h4>
|
<div className="border-t border-slate-100 pt-4">
|
||||||
<div className="space-y-3">
|
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenAI (ChatGPT)</h4>
|
||||||
<div>
|
<div className="space-y-3">
|
||||||
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
<div>
|
||||||
<input
|
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
||||||
type="password"
|
<input
|
||||||
value={aiForm.openaiKey}
|
type="password"
|
||||||
onChange={e => setAiForm({ ...aiForm, openaiKey: e.target.value })}
|
value={aiForm.openaiKey}
|
||||||
placeholder="Cole a chave (sk-...)"
|
onChange={e => setAiForm({ ...aiForm, openaiKey: e.target.value })}
|
||||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
placeholder="Cole a chave (sk-...)"
|
||||||
/>
|
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div>
|
||||||
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
<div className="flex items-center justify-between mb-1">
|
||||||
<button
|
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
||||||
type="button"
|
<button
|
||||||
disabled={loadingModels['openai']}
|
type="button"
|
||||||
onClick={() => handleFetchModels('openai')}
|
disabled={loadingModels['openai']}
|
||||||
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
onClick={() => handleFetchModels('openai')}
|
||||||
>
|
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
||||||
{loadingModels['openai'] ? (
|
>
|
||||||
<>
|
{loadingModels['openai'] ? (
|
||||||
<RefreshCw className="animate-spin" size={10} />
|
<>
|
||||||
<span>Carregando...</span>
|
<RefreshCw className="animate-spin" size={10} />
|
||||||
</>
|
<span>Carregando...</span>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<Sparkles size={10} />
|
<>
|
||||||
<span>Carregar Modelos</span>
|
<Sparkles size={10} />
|
||||||
</>
|
<span>Carregar Modelos</span>
|
||||||
)}
|
</>
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={aiForm.openaiModel}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, openaiModel: e.target.value })}
|
||||||
|
className="w-full px-4 py-2.5 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-xs"
|
||||||
|
>
|
||||||
|
{dynamicModels.openai.map(m => (
|
||||||
|
<option key={m.id} value={m.id}>{m.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<select
|
|
||||||
value={aiForm.openaiModel}
|
|
||||||
onChange={e => setAiForm({ ...aiForm, openaiModel: e.target.value })}
|
|
||||||
className="w-full px-4 py-2.5 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-xs"
|
|
||||||
>
|
|
||||||
{dynamicModels.openai.map(m => (
|
|
||||||
<option key={m.id} value={m.id}>{m.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="border-t border-slate-100 pt-4">
|
{aiForm.activeProvider === 'claude' && (
|
||||||
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Anthropic Claude</h4>
|
<div className="border-t border-slate-100 pt-4">
|
||||||
<div className="space-y-3">
|
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">Anthropic Claude</h4>
|
||||||
<div>
|
<div className="space-y-3">
|
||||||
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
<div>
|
||||||
<input
|
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
||||||
type="password"
|
<input
|
||||||
value={aiForm.claudeKey}
|
type="password"
|
||||||
onChange={e => setAiForm({ ...aiForm, claudeKey: e.target.value })}
|
value={aiForm.claudeKey}
|
||||||
placeholder="Cole a chave (sk-ant-...)"
|
onChange={e => setAiForm({ ...aiForm, claudeKey: e.target.value })}
|
||||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
placeholder="Cole a chave (sk-ant-...)"
|
||||||
/>
|
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div>
|
||||||
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
<div className="flex items-center justify-between mb-1">
|
||||||
<button
|
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
||||||
type="button"
|
<button
|
||||||
disabled={loadingModels['claude']}
|
type="button"
|
||||||
onClick={() => handleFetchModels('claude')}
|
disabled={loadingModels['claude']}
|
||||||
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
onClick={() => handleFetchModels('claude')}
|
||||||
>
|
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
||||||
{loadingModels['claude'] ? (
|
>
|
||||||
<>
|
{loadingModels['claude'] ? (
|
||||||
<RefreshCw className="animate-spin" size={10} />
|
<>
|
||||||
<span>Carregando...</span>
|
<RefreshCw className="animate-spin" size={10} />
|
||||||
</>
|
<span>Carregando...</span>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<Sparkles size={10} />
|
<>
|
||||||
<span>Carregar Modelos</span>
|
<Sparkles size={10} />
|
||||||
</>
|
<span>Carregar Modelos</span>
|
||||||
)}
|
</>
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={aiForm.claudeModel}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, claudeModel: e.target.value })}
|
||||||
|
className="w-full px-4 py-2.5 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-xs"
|
||||||
|
>
|
||||||
|
{dynamicModels.claude.map(m => (
|
||||||
|
<option key={m.id} value={m.id}>{m.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<select
|
|
||||||
value={aiForm.claudeModel}
|
|
||||||
onChange={e => setAiForm({ ...aiForm, claudeModel: e.target.value })}
|
|
||||||
className="w-full px-4 py-2.5 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-xs"
|
|
||||||
>
|
|
||||||
{dynamicModels.claude.map(m => (
|
|
||||||
<option key={m.id} value={m.id}>{m.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
|
|
||||||
<div className="border-t border-slate-100 pt-4">
|
{aiForm.activeProvider === 'openrouter' && (
|
||||||
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenRouter</h4>
|
<div className="border-t border-slate-100 pt-4">
|
||||||
<div className="space-y-3">
|
<h4 className="text-xs font-black text-slate-400 uppercase tracking-widest mb-3">OpenRouter</h4>
|
||||||
<div>
|
<div className="space-y-3">
|
||||||
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
<div>
|
||||||
<input
|
<label className="block text-[11px] font-bold text-slate-500 mb-1">Chave de API</label>
|
||||||
type="password"
|
<input
|
||||||
value={aiForm.openrouterKey}
|
type="password"
|
||||||
onChange={e => setAiForm({ ...aiForm, openrouterKey: e.target.value })}
|
value={aiForm.openrouterKey}
|
||||||
placeholder="Cole a chave (sk-or-...)"
|
onChange={e => setAiForm({ ...aiForm, openrouterKey: e.target.value })}
|
||||||
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
placeholder="Cole a chave (sk-or-...)"
|
||||||
/>
|
className="w-full px-4 py-2.5 bg-slate-50 border border-slate-200 rounded-xl focus:ring-2 focus:ring-indigo-500 focus:outline-none transition-all font-medium text-slate-800 text-xs"
|
||||||
</div>
|
/>
|
||||||
<div>
|
</div>
|
||||||
<div className="flex items-center justify-between mb-1">
|
<div>
|
||||||
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
<div className="flex items-center justify-between mb-1">
|
||||||
<button
|
<label className="block text-[11px] font-bold text-slate-500">Modelo</label>
|
||||||
type="button"
|
<button
|
||||||
disabled={loadingModels['openrouter']}
|
type="button"
|
||||||
onClick={() => handleFetchModels('openrouter')}
|
disabled={loadingModels['openrouter']}
|
||||||
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
onClick={() => handleFetchModels('openrouter')}
|
||||||
>
|
className="text-[10px] font-black text-indigo-600 hover:text-indigo-800 flex items-center gap-1 transition-colors"
|
||||||
{loadingModels['openrouter'] ? (
|
>
|
||||||
<>
|
{loadingModels['openrouter'] ? (
|
||||||
<RefreshCw className="animate-spin" size={10} />
|
<>
|
||||||
<span>Carregando...</span>
|
<RefreshCw className="animate-spin" size={10} />
|
||||||
</>
|
<span>Carregando...</span>
|
||||||
) : (
|
</>
|
||||||
<>
|
) : (
|
||||||
<Sparkles size={10} />
|
<>
|
||||||
<span>Carregar Modelos</span>
|
<Sparkles size={10} />
|
||||||
</>
|
<span>Carregar Modelos</span>
|
||||||
)}
|
</>
|
||||||
</button>
|
)}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<select
|
||||||
|
value={aiForm.openrouterModel}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, openrouterModel: e.target.value })}
|
||||||
|
className="w-full px-4 py-2.5 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-xs"
|
||||||
|
>
|
||||||
|
{dynamicModels.openrouter.map(m => (
|
||||||
|
<option key={m.id} value={m.id}>{m.name}</option>
|
||||||
|
))}
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
<select
|
|
||||||
value={aiForm.openrouterModel}
|
|
||||||
onChange={e => setAiForm({ ...aiForm, openrouterModel: e.target.value })}
|
|
||||||
className="w-full px-4 py-2.5 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-xs"
|
|
||||||
>
|
|
||||||
{dynamicModels.openrouter.map(m => (
|
|
||||||
<option key={m.id} value={m.id}>{m.name}</option>
|
|
||||||
))}
|
|
||||||
</select>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
)}
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3 pt-4 border-t border-slate-100 mt-5">
|
<div className="flex gap-3 pt-4 border-t border-slate-100 mt-5">
|
||||||
|
|
|
||||||
|
|
@ -1753,12 +1753,20 @@ app.post('/api/ai/list-models', async (req, res) => {
|
||||||
throw new Error(`Erro Gemini: ${response.status} - ${errText}`);
|
throw new Error(`Erro Gemini: ${response.status} - ${errText}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const models = (json.models || [])
|
let models = (json.models || [])
|
||||||
.filter(m => m.supportedMethods && m.supportedMethods.includes('generateContent'))
|
.filter(m => m.supportedGenerationMethods && m.supportedGenerationMethods.includes('generateContent'))
|
||||||
.map(m => ({
|
.map(m => ({
|
||||||
id: m.name.replace('models/', ''),
|
id: m.name.replace('models/', ''),
|
||||||
name: m.displayName || m.name
|
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 });
|
return res.json({ models });
|
||||||
|
|
||||||
} else if (provider === 'openai') {
|
} 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}`);
|
throw new Error(`Erro OpenAI: ${response.status} - ${errText}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
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'))
|
.filter(m => m.id.includes('gpt') || m.id.includes('o1') || m.id.includes('o3'))
|
||||||
.map(m => ({
|
.map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
name: 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 });
|
return res.json({ models });
|
||||||
|
|
||||||
} else if (provider === 'claude') {
|
} else if (provider === 'claude') {
|
||||||
|
|
@ -1797,10 +1812,16 @@ app.post('/api/ai/list-models', async (req, res) => {
|
||||||
return res.json({ models: defaultModels });
|
return res.json({ models: defaultModels });
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const models = (json.data || []).map(m => ({
|
let models = (json.data || []).map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
name: m.display_name || 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 });
|
return res.json({ models });
|
||||||
|
|
||||||
} else if (provider === 'openrouter') {
|
} 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}`);
|
throw new Error(`Erro OpenRouter: ${response.status} - ${errText}`);
|
||||||
}
|
}
|
||||||
const json = await response.json();
|
const json = await response.json();
|
||||||
const models = (json.data || []).map(m => ({
|
let models = (json.data || []).map(m => ({
|
||||||
id: m.id,
|
id: m.id,
|
||||||
name: `${m.name || 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 });
|
return res.json({ models });
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue