feat: persiste chaves de IA no banco de dados e adiciona suporte ao Claude
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m3s
Details
Build and Deploy (Gitea) / build-and-deploy (push) Successful in 2m3s
Details
This commit is contained in:
parent
a6dbd80362
commit
6816c945cc
|
|
@ -21,13 +21,11 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
const { showAlert, showConfirm } = useDialog();
|
const { showAlert, showConfirm } = useDialog();
|
||||||
|
|
||||||
const [showAIConfigModal, setShowAIConfigModal] = useState(false);
|
const [showAIConfigModal, setShowAIConfigModal] = useState(false);
|
||||||
const [aiConfig, setAIConfig] = useState<{ provider: 'gemini' | 'openai'; apiKey: string }>(() => {
|
const [aiForm, setAiForm] = useState({
|
||||||
try {
|
activeProvider: data.aiConfig?.activeProvider || 'gemini',
|
||||||
const saved = localStorage.getItem('edumanager_ai_config');
|
geminiKey: data.aiConfig?.geminiKey ? '********' : '',
|
||||||
return saved ? JSON.parse(saved) : { provider: 'gemini', apiKey: '' };
|
openaiKey: data.aiConfig?.openaiKey ? '********' : '',
|
||||||
} catch {
|
claudeKey: data.aiConfig?.claudeKey ? '********' : ''
|
||||||
return { provider: 'gemini', apiKey: '' };
|
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
|
||||||
const [isGeneratingAI, setIsGeneratingAI] = useState(false);
|
const [isGeneratingAI, setIsGeneratingAI] = useState(false);
|
||||||
|
|
@ -35,12 +33,32 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
const [aiQuantity, setAiQuantity] = useState(5);
|
const [aiQuantity, setAiQuantity] = useState(5);
|
||||||
const [aiDifficulty, setAiDifficulty] = useState('medium');
|
const [aiDifficulty, setAiDifficulty] = useState('medium');
|
||||||
|
|
||||||
const handleSaveAIConfig = (provider: 'gemini' | 'openai', apiKey: string) => {
|
const handleOpenAIConfig = () => {
|
||||||
const newConfig = { provider, apiKey };
|
setAiForm({
|
||||||
setAIConfig(newConfig);
|
activeProvider: data.aiConfig?.activeProvider || 'gemini',
|
||||||
localStorage.setItem('edumanager_ai_config', JSON.stringify(newConfig));
|
geminiKey: data.aiConfig?.geminiKey ? '********' : '',
|
||||||
|
openaiKey: data.aiConfig?.openaiKey ? '********' : '',
|
||||||
|
claudeKey: data.aiConfig?.claudeKey ? '********' : ''
|
||||||
|
});
|
||||||
|
setShowAIConfigModal(true);
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleSaveAIConfig = async () => {
|
||||||
|
const newConfig = {
|
||||||
|
activeProvider: aiForm.activeProvider,
|
||||||
|
geminiKey: aiForm.geminiKey === '********' ? (data.aiConfig?.geminiKey || '') : aiForm.geminiKey,
|
||||||
|
openaiKey: aiForm.openaiKey === '********' ? (data.aiConfig?.openaiKey || '') : aiForm.openaiKey,
|
||||||
|
claudeKey: aiForm.claudeKey === '********' ? (data.aiConfig?.claudeKey || '') : aiForm.claudeKey
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateData({ aiConfig: newConfig });
|
||||||
setShowAIConfigModal(false);
|
setShowAIConfigModal(false);
|
||||||
showAlert('Sucesso', 'Configurações de IA salvas com sucesso!', 'success');
|
showAlert('Sucesso', 'Configurações de IA salvas com sucesso no banco de dados!', 'success');
|
||||||
|
} catch (e) {
|
||||||
|
console.error(e);
|
||||||
|
showAlert('Erro', 'Falha ao salvar configurações de IA.', 'error');
|
||||||
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
const [dbClasses, setDbClasses] = useState<any[]>(data?.classes || []);
|
||||||
|
|
@ -250,10 +268,18 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleGenerateQuestionsAI = async (quantityOverride?: number) => {
|
const handleGenerateQuestionsAI = async (quantityOverride?: number) => {
|
||||||
if (!aiConfig.apiKey) {
|
const activeProvider = data.aiConfig?.activeProvider || 'gemini';
|
||||||
showAlert('IA não configurada', 'Por favor, configure sua API Key clicando no botão "Configurar IA" no cabeçalho da página.', 'warning');
|
const hasKey = !!(
|
||||||
|
(activeProvider === 'gemini' && data.aiConfig?.geminiKey) ||
|
||||||
|
(activeProvider === 'openai' && data.aiConfig?.openaiKey) ||
|
||||||
|
(activeProvider === 'claude' && data.aiConfig?.claudeKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasKey) {
|
||||||
|
showAlert('IA não configurada', 'Por favor, configure a chave de API do seu provedor ativo clicando no botão "Configurar IA" no cabeçalho.', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!aiTopic.trim()) {
|
if (!aiTopic.trim()) {
|
||||||
showAlert('Atenção', 'Informe o tema ou assunto das questões a serem geradas.', 'warning');
|
showAlert('Atenção', 'Informe o tema ou assunto das questões a serem geradas.', 'warning');
|
||||||
return;
|
return;
|
||||||
|
|
@ -267,8 +293,6 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
apiKey: aiConfig.apiKey,
|
|
||||||
provider: aiConfig.provider,
|
|
||||||
topic: aiTopic,
|
topic: aiTopic,
|
||||||
quantity: qty,
|
quantity: qty,
|
||||||
difficulty: aiDifficulty
|
difficulty: aiDifficulty
|
||||||
|
|
@ -310,10 +334,18 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
const handleRegenerateQuestionAI = async (qIndex: number) => {
|
const handleRegenerateQuestionAI = async (qIndex: number) => {
|
||||||
if (!aiConfig.apiKey) {
|
const activeProvider = data.aiConfig?.activeProvider || 'gemini';
|
||||||
showAlert('IA não configurada', 'Por favor, configure sua API Key clicando no botão "Configurar IA" no cabeçalho da página.', 'warning');
|
const hasKey = !!(
|
||||||
|
(activeProvider === 'gemini' && data.aiConfig?.geminiKey) ||
|
||||||
|
(activeProvider === 'openai' && data.aiConfig?.openaiKey) ||
|
||||||
|
(activeProvider === 'claude' && data.aiConfig?.claudeKey)
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!hasKey) {
|
||||||
|
showAlert('IA não configurada', 'Por favor, configure a chave de API do seu provedor ativo clicando no botão "Configurar IA" no cabeçalho.', 'warning');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const topic = aiTopic.trim() || editingExam?.title || 'Conhecimentos Gerais';
|
const topic = aiTopic.trim() || editingExam?.title || 'Conhecimentos Gerais';
|
||||||
|
|
||||||
setIsGeneratingAI(true);
|
setIsGeneratingAI(true);
|
||||||
|
|
@ -322,8 +354,6 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
apiKey: aiConfig.apiKey,
|
|
||||||
provider: aiConfig.provider,
|
|
||||||
topic: topic,
|
topic: topic,
|
||||||
quantity: 1,
|
quantity: 1,
|
||||||
difficulty: aiDifficulty
|
difficulty: aiDifficulty
|
||||||
|
|
@ -519,6 +549,12 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
};
|
};
|
||||||
|
|
||||||
if (currentView === 'builder' && editingExam) {
|
if (currentView === 'builder' && editingExam) {
|
||||||
|
const activeProvider = data.aiConfig?.activeProvider || 'gemini';
|
||||||
|
const hasAPIKey = !!(
|
||||||
|
(activeProvider === 'gemini' && data.aiConfig?.geminiKey) ||
|
||||||
|
(activeProvider === 'openai' && data.aiConfig?.openaiKey) ||
|
||||||
|
(activeProvider === 'claude' && data.aiConfig?.claudeKey)
|
||||||
|
);
|
||||||
return (
|
return (
|
||||||
<div className="p-8 max-w-4xl mx-auto animate-in fade-in duration-500 pb-32">
|
<div className="p-8 max-w-4xl mx-auto animate-in fade-in duration-500 pb-32">
|
||||||
<div className="flex items-center gap-4 mb-8">
|
<div className="flex items-center gap-4 mb-8">
|
||||||
|
|
@ -643,7 +679,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
</h3>
|
</h3>
|
||||||
<p className="text-slate-500 text-sm mt-1 font-medium">Use nossa inteligência artificial para criar questões completas instantaneamente.</p>
|
<p className="text-slate-500 text-sm mt-1 font-medium">Use nossa inteligência artificial para criar questões completas instantaneamente.</p>
|
||||||
</div>
|
</div>
|
||||||
{!aiConfig.apiKey && (
|
{!hasAPIKey && (
|
||||||
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-amber-50 text-amber-700 rounded-lg text-xs font-bold border border-amber-200">
|
<span className="inline-flex items-center gap-1.5 px-3 py-1.5 bg-amber-50 text-amber-700 rounded-lg text-xs font-bold border border-amber-200">
|
||||||
<AlertTriangle size={14} /> IA não configurada
|
<AlertTriangle size={14} /> IA não configurada
|
||||||
</span>
|
</span>
|
||||||
|
|
@ -688,10 +724,10 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex flex-col sm:flex-row gap-4 mt-6 pt-6 border-t border-slate-100 justify-end">
|
<div className="flex flex-col sm:flex-row gap-4 mt-6 pt-6 border-t border-slate-100 justify-end">
|
||||||
{!aiConfig.apiKey ? (
|
{!hasAPIKey ? (
|
||||||
<button
|
<button
|
||||||
type="button"
|
type="button"
|
||||||
onClick={() => setShowAIConfigModal(true)}
|
onClick={handleOpenAIConfig}
|
||||||
className="flex items-center justify-center gap-2 px-6 py-3 bg-indigo-50 text-indigo-700 rounded-xl font-bold hover:bg-indigo-100 transition-all text-sm"
|
className="flex items-center justify-center gap-2 px-6 py-3 bg-indigo-50 text-indigo-700 rounded-xl font-bold hover:bg-indigo-100 transition-all text-sm"
|
||||||
>
|
>
|
||||||
<Sparkles size={18} />
|
<Sparkles size={18} />
|
||||||
|
|
@ -924,7 +960,7 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<button
|
<button
|
||||||
onClick={() => setShowAIConfigModal(true)}
|
onClick={handleOpenAIConfig}
|
||||||
className="flex items-center justify-center gap-2 px-4 py-3 bg-slate-100 text-slate-700 rounded-xl font-bold hover:bg-slate-200 transition-all border border-slate-200"
|
className="flex items-center justify-center gap-2 px-4 py-3 bg-slate-100 text-slate-700 rounded-xl font-bold hover:bg-slate-200 transition-all border border-slate-200"
|
||||||
title="Configurar Chaves de IA"
|
title="Configurar Chaves de IA"
|
||||||
>
|
>
|
||||||
|
|
@ -1154,41 +1190,63 @@ const Exams: React.FC<ExamsProps> = ({ data, updateData }) => {
|
||||||
|
|
||||||
<div className="space-y-5">
|
<div className="space-y-5">
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-slate-700 mb-2">Provedor de IA</label>
|
<label className="block text-sm font-bold text-slate-700 mb-2">Provedor de IA Ativo</label>
|
||||||
<select
|
<select
|
||||||
value={aiConfig.provider}
|
value={aiForm.activeProvider}
|
||||||
onChange={e => setAIConfig({ ...aiConfig, provider: e.target.value as any })}
|
onChange={e => setAiForm({ ...aiForm, activeProvider: e.target.value as any })}
|
||||||
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"
|
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="gemini">Google Gemini (Recomendado)</option>
|
<option value="gemini">Google Gemini</option>
|
||||||
<option value="openai">OpenAI (ChatGPT)</option>
|
<option value="openai">OpenAI (ChatGPT)</option>
|
||||||
|
<option value="claude">Anthropic Claude</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div>
|
<div>
|
||||||
<label className="block text-sm font-bold text-slate-700 mb-2">Chave de API (API Key)</label>
|
<label className="block text-sm font-bold text-slate-700 mb-2">Chave Google Gemini</label>
|
||||||
<input
|
<input
|
||||||
type="password"
|
type="password"
|
||||||
value={aiConfig.apiKey}
|
value={aiForm.geminiKey}
|
||||||
onChange={e => setAIConfig({ ...aiConfig, apiKey: e.target.value })}
|
onChange={e => setAiForm({ ...aiForm, geminiKey: e.target.value })}
|
||||||
placeholder={aiConfig.provider === 'gemini' ? 'AIzaSy...' : 'sk-...'}
|
placeholder="Cole a chave do Gemini (AIzaSy...)"
|
||||||
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-medium text-slate-800"
|
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-medium text-slate-800 text-sm"
|
||||||
/>
|
/>
|
||||||
<p className="text-[11px] text-slate-500 mt-2 font-medium">
|
|
||||||
Sua chave é armazenada de forma segura apenas localmente no seu navegador.
|
|
||||||
</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div className="flex gap-3 pt-4">
|
<div>
|
||||||
|
<label className="block text-sm font-bold text-slate-700 mb-2">Chave OpenAI GPT</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={aiForm.openaiKey}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, openaiKey: e.target.value })}
|
||||||
|
placeholder="Cole a chave da OpenAI (sk-...)"
|
||||||
|
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-medium text-slate-800 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<label className="block text-sm font-bold text-slate-700 mb-2">Chave Anthropic Claude</label>
|
||||||
|
<input
|
||||||
|
type="password"
|
||||||
|
value={aiForm.claudeKey}
|
||||||
|
onChange={e => setAiForm({ ...aiForm, claudeKey: e.target.value })}
|
||||||
|
placeholder="Cole a chave do Claude (sk-ant-...)"
|
||||||
|
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-medium text-slate-800 text-sm"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div className="flex gap-3 pt-4 border-t border-slate-100">
|
||||||
<button
|
<button
|
||||||
|
type="button"
|
||||||
onClick={() => setShowAIConfigModal(false)}
|
onClick={() => setShowAIConfigModal(false)}
|
||||||
className="flex-1 px-6 py-3 bg-slate-100 text-slate-600 rounded-xl font-bold hover:bg-slate-200 transition-all"
|
className="flex-1 px-6 py-3 bg-slate-100 text-slate-600 rounded-xl font-bold hover:bg-slate-200 transition-all text-sm"
|
||||||
>
|
>
|
||||||
Cancelar
|
Cancelar
|
||||||
</button>
|
</button>
|
||||||
<button
|
<button
|
||||||
onClick={() => handleSaveAIConfig(aiConfig.provider, aiConfig.apiKey)}
|
type="button"
|
||||||
className="flex-1 px-6 py-3 bg-indigo-600 text-white rounded-xl font-bold hover:bg-indigo-700 transition-all shadow-lg shadow-indigo-100"
|
onClick={handleSaveAIConfig}
|
||||||
|
className="flex-1 px-6 py-3 bg-indigo-600 text-white rounded-xl font-bold hover:bg-indigo-700 transition-all shadow-lg shadow-indigo-100 text-sm"
|
||||||
>
|
>
|
||||||
Salvar
|
Salvar
|
||||||
</button>
|
</button>
|
||||||
|
|
|
||||||
|
|
@ -1261,11 +1261,8 @@ app.post('/api/provas/:id/questoes', async (req, res) => {
|
||||||
});
|
});
|
||||||
|
|
||||||
app.post('/api/ai/generate-questions', async (req, res) => {
|
app.post('/api/ai/generate-questions', async (req, res) => {
|
||||||
const { apiKey, provider, topic, quantity, difficulty } = req.body;
|
const { topic, quantity, difficulty } = req.body;
|
||||||
|
|
||||||
if (!apiKey) {
|
|
||||||
return res.status(400).json({ error: 'API Key não informada' });
|
|
||||||
}
|
|
||||||
if (!topic) {
|
if (!topic) {
|
||||||
return res.status(400).json({ error: 'Tema não informado' });
|
return res.status(400).json({ error: 'Tema não informado' });
|
||||||
}
|
}
|
||||||
|
|
@ -1273,6 +1270,19 @@ app.post('/api/ai/generate-questions', async (req, res) => {
|
||||||
const limitQty = Math.min(Math.max(Number(quantity) || 1, 1), 20);
|
const limitQty = Math.min(Math.max(Number(quantity) || 1, 1), 20);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
const appData = await getSchoolData();
|
||||||
|
const aiConfig = appData.aiConfig || {};
|
||||||
|
const provider = aiConfig.activeProvider || 'gemini';
|
||||||
|
|
||||||
|
let apiKey = '';
|
||||||
|
if (provider === 'gemini') apiKey = aiConfig.geminiKey;
|
||||||
|
else if (provider === 'openai') apiKey = aiConfig.openaiKey;
|
||||||
|
else if (provider === 'claude') apiKey = aiConfig.claudeKey;
|
||||||
|
|
||||||
|
if (!apiKey) {
|
||||||
|
return res.status(400).json({ error: `Chave de API não configurada para o provedor ${provider.toUpperCase()} no painel da escola.` });
|
||||||
|
}
|
||||||
|
|
||||||
if (provider === 'openai') {
|
if (provider === 'openai') {
|
||||||
const payload = {
|
const payload = {
|
||||||
model: 'gpt-4o-mini',
|
model: 'gpt-4o-mini',
|
||||||
|
|
@ -1329,6 +1339,64 @@ Exemplo de formato esperado:
|
||||||
const questions = parsed.questions || parsed || [];
|
const questions = parsed.questions || parsed || [];
|
||||||
return res.json({ questions });
|
return res.json({ questions });
|
||||||
|
|
||||||
|
} else if (provider === 'claude') {
|
||||||
|
const payload = {
|
||||||
|
model: 'claude-3-5-sonnet-20241022',
|
||||||
|
max_tokens: 4096,
|
||||||
|
system: `Você é um gerador de questões escolares de múltipla escolha para provas de alunos.
|
||||||
|
Retorne APENAS um array de objetos JSON estruturado (sem blocos de código markdown como \`\`\`json ou texto explicativo extra).
|
||||||
|
Cada questão do array deve conter obrigatoriamente:
|
||||||
|
- "text": o enunciado da questão.
|
||||||
|
- "options": um array com exatamente 4 opções de resposta.
|
||||||
|
- "correctOptionIndex": o índice da opção correta (de 0 a 3).
|
||||||
|
|
||||||
|
Exemplo de formato esperado:
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"text": "Qual é a principal função do processador?",
|
||||||
|
"options": ["Processar dados", "Armazenar arquivos", "Exibir imagens", "Conectar à internet"],
|
||||||
|
"correctOptionIndex": 0
|
||||||
|
}
|
||||||
|
]`,
|
||||||
|
messages: [
|
||||||
|
{
|
||||||
|
role: 'user',
|
||||||
|
content: `Gere exatamente ${limitQty} questões escolares de múltipla escolha sobre o tema "${topic}" com nível de dificuldade "${difficulty}" em Português do Brasil.`
|
||||||
|
}
|
||||||
|
]
|
||||||
|
};
|
||||||
|
|
||||||
|
const response = await fetch('https://api.anthropic.com/v1/messages', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'x-api-key': apiKey,
|
||||||
|
'anthropic-version': '2023-06-01'
|
||||||
|
},
|
||||||
|
body: JSON.stringify(payload)
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!response.ok) {
|
||||||
|
const errorText = await response.text();
|
||||||
|
throw new Error(`Claude API retornou erro: ${response.status} - ${errorText}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
const resJson = await response.json();
|
||||||
|
let contentText = resJson.content?.[0]?.text || '';
|
||||||
|
contentText = contentText.trim();
|
||||||
|
if (contentText.startsWith('```json')) {
|
||||||
|
contentText = contentText.substring(7);
|
||||||
|
} else if (contentText.startsWith('```')) {
|
||||||
|
contentText = contentText.substring(3);
|
||||||
|
}
|
||||||
|
if (contentText.endsWith('```')) {
|
||||||
|
contentText = contentText.substring(0, contentText.length - 3);
|
||||||
|
}
|
||||||
|
contentText = contentText.trim();
|
||||||
|
|
||||||
|
const questions = JSON.parse(contentText);
|
||||||
|
return res.json({ questions });
|
||||||
|
|
||||||
} else {
|
} else {
|
||||||
// Provedor padrão: Google Gemini
|
// Provedor padrão: Google Gemini
|
||||||
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`;
|
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash:generateContent?key=${apiKey}`;
|
||||||
|
|
|
||||||
|
|
@ -297,6 +297,13 @@ export interface PreMatriculaInscricao {
|
||||||
createdAt: string;
|
createdAt: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AIConfig {
|
||||||
|
activeProvider: 'gemini' | 'openai' | 'claude';
|
||||||
|
geminiKey?: string;
|
||||||
|
openaiKey?: string;
|
||||||
|
claudeKey?: string;
|
||||||
|
}
|
||||||
|
|
||||||
export interface SchoolData {
|
export interface SchoolData {
|
||||||
users: User[];
|
users: User[];
|
||||||
courses: Course[];
|
courses: Course[];
|
||||||
|
|
@ -327,6 +334,7 @@ export interface SchoolData {
|
||||||
instanceName: string;
|
instanceName: string;
|
||||||
apiKey: string;
|
apiKey: string;
|
||||||
};
|
};
|
||||||
|
aiConfig?: AIConfig;
|
||||||
messageTemplates?: {
|
messageTemplates?: {
|
||||||
boletoGerado: string;
|
boletoGerado: string;
|
||||||
pagamentoConfirmado: string;
|
pagamentoConfirmado: string;
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue