Feat: Criação do histórico, tabela no banco e geração nativa do Planejamento Mind Lab

This commit is contained in:
2026-06-05 18:51:05 +00:00
parent d74bb5e287
commit 5ea9c24ea0
4 changed files with 272 additions and 20 deletions
+146 -18
View File
@@ -6219,7 +6219,7 @@ document.addEventListener('DOMContentLoaded', () => {
} }
if (btnGenerateMindLab) { if (btnGenerateMindLab) {
btnGenerateMindLab.addEventListener('click', () => { btnGenerateMindLab.addEventListener('click', async () => {
const activeFaixa = document.querySelector('.btn-mindlab-faixa.active'); const activeFaixa = document.querySelector('.btn-mindlab-faixa.active');
const activeJogoBtn = document.querySelector('.btn-mindlab-jogo.active'); const activeJogoBtn = document.querySelector('.btn-mindlab-jogo.active');
const activeMetodo = document.querySelector('.btn-mindlab-metodo.active'); const activeMetodo = document.querySelector('.btn-mindlab-metodo.active');
@@ -6236,25 +6236,153 @@ document.addEventListener('DOMContentLoaded', () => {
const duracaoStr = document.getElementById('mindLabDuracaoSelect').value; const duracaoStr = document.getElementById('mindLabDuracaoSelect').value;
const temaLivre = document.getElementById('mindLabTemaLivreInput').value.trim(); const temaLivre = document.getElementById('mindLabTemaLivreInput').value.trim();
const promptText = `Atue como um especialista na metodologia educacional Mente Inovadora (Mind Lab). Crie um planejamento de aula super detalhado e engajador com os seguintes parâmetros: const btnG = document.getElementById('btnGenerateMindLab');
- *Faixa Etária:* ${faixaStr} const loader = document.getElementById('mindLabGenerateLoader');
- *Jogo de Raciocínio:* ${jogoStr} const resultArea = document.getElementById('mindLabResultArea');
- *Método Metacognitivo Foco:* ${metodoStr} const output = document.getElementById('mindLabOutput');
- *Foco de Transferência (Socioemocional):* ${focoStr}
- *Duração:* ${duracaoStr}${temaLivre ? '\n- *Tema Contextual:* ' + temaLivre : ''} btnG.style.display = 'none';
loader.style.display = 'flex';
resultArea.style.display = 'none';
Por favor, estruture a aula com: try {
1. *Abertura (Contextualização):* Como introduzir o método e o tema. const res = await fetch('/api/mindlab/generate', {
2. *Jogo (Aplicação):* Orientações para a mediação durante a partida. method: 'POST',
3. *Reflexão (Mediação Transcendental):* Perguntas provocativas para fazer as crianças refletirem. headers: { 'Content-Type': 'application/json', 'Authorization': `Bearer ${localStorage.getItem('token')}` },
4. *Transferência:* Como conectar o que aprenderam no jogo e o método escolhido (ex: Semáforo) com situações reais da vida deles.`; body: JSON.stringify({ faixa_etaria: faixaStr, jogo: jogoStr, metodo: metodoStr, foco: focoStr, duracao: duracaoStr, tema: temaLivre })
});
mindLabModal.style.display = 'none'; const data = await res.json();
if (typeof handleSendMessage === 'function') { if (!res.ok) throw new Error(data.error || 'Erro na requisição');
handleSendMessage(promptText);
} else { output.innerHTML = typeof marked !== 'undefined' ? marked.parse(data.planejamento) : data.planejamento;
console.error("handleSendMessage not found"); output.dataset.raw = data.planejamento;
resultArea.style.display = 'block';
btnG.style.display = 'flex';
btnG.innerHTML = '🧠 Criar Novo Planejamento';
} catch (err) {
alert('Erro ao gerar: ' + err.message);
btnG.style.display = 'flex';
} finally {
loader.style.display = 'none';
} }
}); });
} }
const btnCopyMindLab = document.getElementById('btnCopyMindLab');
if (btnCopyMindLab) {
btnCopyMindLab.addEventListener('click', () => {
const output = document.getElementById('mindLabOutput');
if (output && output.dataset.raw) {
navigator.clipboard.writeText(output.dataset.raw);
btnCopyMindLab.innerHTML = '<span class="material-icons" style="font-size:14px; vertical-align:middle;">check</span> Copiado!';
setTimeout(() => {
btnCopyMindLab.innerHTML = '<span class="material-icons" style="font-size:14px; vertical-align:middle;">content_copy</span> Copiar';
}, 2000);
}
});
}
const btnPdfMindLab = document.getElementById('btnPdfMindLab');
if (btnPdfMindLab) {
btnPdfMindLab.addEventListener('click', () => {
const output = document.getElementById('mindLabOutput');
if (output && output.dataset.raw && window.jspdf) {
const { jsPDF } = window.jspdf;
const doc = new jsPDF();
doc.setFontSize(12);
doc.setFont("helvetica", "normal");
const lines = doc.splitTextToSize(output.dataset.raw.replace(/[#*]/g, ''), 180);
doc.text(lines, 15, 20);
doc.save('Planejamento_Mind_Lab.pdf');
} else {
alert('Carregando biblioteca PDF ou nenhum conteúdo para salvar.');
}
});
}
const btnOpenMindLabHistory = document.getElementById('btnOpenMindLabHistory');
const mindLabHistoryModal = document.getElementById('mindLabHistoryModal');
const btnCloseMindLabHistory = document.getElementById('btnCloseMindLabHistory');
const mindLabHistoryList = document.getElementById('mindLabHistoryList');
if (btnOpenMindLabHistory) {
btnOpenMindLabHistory.addEventListener('click', async () => {
mindLabHistoryModal.style.display = 'flex';
mindLabHistoryList.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 20px;">Carregando histórico...</div>';
try {
const res = await fetch('/api/mindlab/list', {
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
});
const data = await res.json();
mindLabHistoryList.innerHTML = '';
if (data.length === 0) {
mindLabHistoryList.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 20px;">Nenhum planejamento salvo ainda.</div>';
return;
}
data.forEach(item => {
const div = document.createElement('div');
div.style.background = 'var(--bg-tertiary)';
div.style.padding = '12px 15px';
div.style.borderRadius = '8px';
div.style.display = 'flex';
div.style.justifyContent = 'space-between';
div.style.alignItems = 'center';
div.innerHTML = `
<div>
<div style="font-weight: 600; font-size: 0.95rem; color: var(--text-primary);">🎲 ${item.jogo} - ${item.faixa_etaria}</div>
<div style="font-size: 0.8rem; color: var(--text-secondary); margin-top: 4px;">🎯 ${item.foco}</div>
<div style="font-size: 0.75rem; color: var(--text-secondary); margin-top: 4px;">${new Date(item.created_at).toLocaleString()}</div>
</div>
<div style="display: flex; gap: 8px;">
<button class="btn-ver-mindlab btn-secondary" data-id="${item.id}" style="padding: 6px 12px; font-size: 0.85rem; border-radius: 6px;">Ver</button>
<button class="btn-del-mindlab btn-secondary" data-id="${item.id}" style="padding: 6px 12px; font-size: 0.85rem; border-radius: 6px; color: #ef4444; border-color: rgba(239, 68, 68, 0.3);">🗑️</button>
</div>
`;
mindLabHistoryList.appendChild(div);
});
document.querySelectorAll('.btn-ver-mindlab').forEach(btn => {
btn.addEventListener('click', async (e) => {
const id = e.target.dataset.id;
try {
const res = await fetch(`/api/mindlab/${id}`, {
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
});
const itemData = await res.json();
document.getElementById('mindLabOutput').innerHTML = typeof marked !== 'undefined' ? marked.parse(itemData.planejamento) : itemData.planejamento;
document.getElementById('mindLabOutput').dataset.raw = itemData.planejamento;
document.getElementById('mindLabResultArea').style.display = 'block';
mindLabHistoryModal.style.display = 'none';
document.getElementById('btnGenerateMindLab').style.display = 'flex';
} catch (err) { alert('Erro ao carregar planejamento.'); }
});
});
document.querySelectorAll('.btn-del-mindlab').forEach(btn => {
btn.addEventListener('click', async (e) => {
if(!confirm('Tem certeza que deseja apagar este planejamento?')) return;
const id = e.target.dataset.id;
try {
await fetch(`/api/mindlab/${id}`, {
method: 'DELETE',
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
});
e.target.closest('div[style*="var(--bg-tertiary)"]').remove();
} catch (err) { alert('Erro ao deletar.'); }
});
});
} catch (err) {
mindLabHistoryList.innerHTML = '<div style="color: #ef4444; padding: 20px;">Erro ao carregar histórico.</div>';
}
});
}
if (btnCloseMindLabHistory) {
btnCloseMindLabHistory.addEventListener('click', () => {
mindLabHistoryModal.style.display = 'none';
});
}
}); });
+32 -1
View File
@@ -910,7 +910,12 @@
<div class="settings-modal-content" style="max-width: 700px; width: 95%; max-height: 90vh; display: flex; flex-direction: column;"> <div class="settings-modal-content" style="max-width: 700px; width: 95%; max-height: 90vh; display: flex; flex-direction: column;">
<div class="settings-modal-header" style="background: var(--bg-secondary); border-bottom: 1px solid var(--border-light);"> <div class="settings-modal-header" style="background: var(--bg-secondary); border-bottom: 1px solid var(--border-light);">
<h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">🧠 Planejador de Aulas Mind Lab</h3> <h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">🧠 Planejador de Aulas Mind Lab</h3>
<button id="btnCloseMindLabModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">&times;</button> <div style="display: flex; gap: 10px; align-items: center;">
<button id="btnOpenMindLabHistory" class="btn-secondary" style="padding: 6px 12px; font-size: 0.85rem; border-radius: 6px; display: flex; align-items: center; gap: 6px; background: rgba(139, 92, 246, 0.1); color: #a78bfa; border: 1px solid rgba(139, 92, 246, 0.2);">
<span class="material-icons" style="font-size: 14px;">history</span> Histórico
</button>
<button id="btnCloseMindLabModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">&times;</button>
</div>
</div> </div>
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;"> <div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;">
@@ -982,6 +987,32 @@
<div class="spinner"></div> <div class="spinner"></div>
<span style="color: var(--text-secondary); font-size: 0.9rem;">Elaborando metodologia e transferência...</span> <span style="color: var(--text-secondary); font-size: 0.9rem;">Elaborando metodologia e transferência...</span>
</div> </div>
<div id="mindLabResultArea" style="display: none; margin-top: 15px; border-top: 1px solid var(--border-light); padding-top: 15px;">
<div style="display: flex; justify-content: space-between; align-items: center; margin-bottom: 10px;">
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Planejamento Gerado:</label>
<div style="display: flex; gap: 8px;">
<button id="btnCopyMindLab" class="btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;"><span class="material-icons" style="font-size:14px; vertical-align:middle;">content_copy</span> Copiar</button>
<button id="btnPdfMindLab" class="btn-secondary" style="padding: 6px 12px; font-size: 0.85rem;"><span class="material-icons" style="font-size:14px; vertical-align:middle;">picture_as_pdf</span> PDF</button>
</div>
</div>
<div id="mindLabOutput" style="background: var(--bg-tertiary); padding: 15px; border-radius: 8px; white-space: pre-wrap; font-size: 0.95rem; line-height: 1.5; color: var(--text-primary); max-height: 300px; overflow-y: auto;"></div>
</div>
</div>
</div>
</div>
<!-- Modal: Histórico Mind Lab -->
<div id="mindLabHistoryModal" class="settings-modal" style="display: none; z-index: 20000;">
<div class="settings-modal-content" style="max-width: 600px; width: 95%; max-height: 80vh; display: flex; flex-direction: column;">
<div class="settings-modal-header" style="background: var(--bg-secondary); border-bottom: 1px solid var(--border-light);">
<h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">🕒 Histórico Mind Lab</h3>
<button id="btnCloseMindLabHistory" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">&times;</button>
</div>
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; flex: 1;">
<div id="mindLabHistoryList" style="display: flex; flex-direction: column; gap: 10px;">
<!-- Preenchido dinamicamente -->
</div>
</div> </div>
</div> </div>
</div> </div>
+1 -1
View File
@@ -1,4 +1,4 @@
const CACHE_NAME = 'camila-ai-v4'; const CACHE_NAME = 'camila-ai-v5';
const urlsToCache = [ const urlsToCache = [
'/', '/',
'/index.html', '/index.html',
+93
View File
@@ -1453,6 +1453,86 @@ app.delete('/api/historias/:id', requireAuth, async (req, res) => {
} catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); } } catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); }
}); });
// ============================================================
// ROTAS DO MIND LAB
// ============================================================
app.post('/api/mindlab/generate', requireAuth, async (req, res) => {
const { faixa_etaria, jogo, metodo, foco, duracao, tema } = req.body;
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
if (!faixa_etaria || !jogo || !metodo || !foco) {
return res.status(400).json({ error: 'Os campos obrigatórios do planejamento não foram preenchidos.' });
}
try {
const systemInstruction = `Você é um especialista na metodologia educacional Mente Inovadora (Mind Lab).
Crie um planejamento de aula detalhado em Markdown com os seguintes parâmetros:
- Faixa Etária: ${faixa_etaria}
- Jogo de Raciocínio: ${jogo}
- Método Metacognitivo Foco: ${metodo}
- Foco de Transferência (Socioemocional): ${foco}
- Duração: ${duracao}
- Tema Contextual: ${tema || 'Nenhum especifico'}
Por favor, estruture a aula obrigatoriamente com:
1. **Abertura (Contextualização):** Como introduzir o método e o tema.
2. **O Jogo (Aplicação):** Orientações para a mediação durante a partida.
3. **Reflexão (Mediação Transcendental):** Perguntas provocativas para fazer as crianças refletirem.
4. **Transferência:** Como conectar o que aprenderam no jogo e o método escolhido com situações reais da vida deles.`;
let planejamentoText = '';
if (process.env.GOOGLE_AI_API_KEY) {
const geminiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${process.env.GOOGLE_AI_MODEL || 'gemini-2.5-flash'}:generateContent?key=${process.env.GOOGLE_AI_API_KEY}`;
const response = await fetch(geminiUrl, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ contents: [{ parts: [{ text: systemInstruction }] }] })
});
if (!response.ok) throw new Error('Erro na API Gemini');
const data = await response.json();
planejamentoText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
}
planejamentoText = planejamentoText.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
if (!planejamentoText) throw new Error('Falha ao gerar o planejamento.');
const dbRes = await dbPool.query(
`INSERT INTO escola.mindlab (usuario_id, faixa_etaria, jogo, metodo, foco, tema, planejamento) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
[usuarioId, faixa_etaria, jogo, metodo, foco, tema, planejamentoText]
);
res.json({ id: dbRes.rows[0].id, planejamento: planejamentoText });
} catch (err) {
console.error('Erro Mind Lab:', err);
res.status(500).json({ error: 'Erro ao gerar planejamento Mind Lab: ' + err.message });
}
});
app.get('/api/mindlab/list', requireAuth, async (req, res) => {
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
try {
const dbRes = await dbPool.query(`SELECT id, faixa_etaria, jogo, metodo, foco, created_at FROM escola.mindlab WHERE usuario_id = $1 ORDER BY created_at DESC`, [usuarioId]);
res.json(dbRes.rows);
} catch (err) { res.status(500).json({ error: 'Erro ao listar histórico do Mind Lab.' }); }
});
app.get('/api/mindlab/:id', requireAuth, async (req, res) => {
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
try {
const dbRes = await dbPool.query(`SELECT * FROM escola.mindlab WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
if (dbRes.rows.length === 0) return res.status(404).json({ error: 'Não encontrada.' });
res.json(dbRes.rows[0]);
} catch (err) { res.status(500).json({ error: 'Erro ao buscar.' }); }
});
app.delete('/api/mindlab/:id', requireAuth, async (req, res) => {
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
try {
const dbRes = await dbPool.query(`DELETE FROM escola.mindlab WHERE id = $1 AND usuario_id = $2`, [req.params.id, usuarioId]);
res.json({ success: true });
} catch (err) { res.status(500).json({ error: 'Erro ao deletar.' }); }
});
// Rota GET para obter configurações do agente // Rota GET para obter configurações do agente
app.get('/api/agent-config', requireAuth, (req, res) => { app.get('/api/agent-config', requireAuth, (req, res) => {
@@ -1677,6 +1757,19 @@ async function initDatabase() {
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW() created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
); );
-- Planejamento Mind Lab
CREATE TABLE IF NOT EXISTS escola.mindlab (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
faixa_etaria VARCHAR(100) NOT NULL,
jogo VARCHAR(100) NOT NULL,
metodo VARCHAR(100) NOT NULL,
foco VARCHAR(100) NOT NULL,
tema TEXT,
planejamento TEXT NOT NULL,
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
);
-- Histórias (Inventando Histórias) -- Histórias (Inventando Histórias)
CREATE TABLE IF NOT EXISTS escola.historias ( CREATE TABLE IF NOT EXISTS escola.historias (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), id UUID PRIMARY KEY DEFAULT gen_random_uuid(),