Corrigindo output truncado do Garatujas e adicionando rotas de Historias/Musicas
This commit is contained in:
+370
@@ -2287,6 +2287,8 @@ const initApp = () => {
|
||||
}
|
||||
if (btn.id === 'barBtnEstudio') return; // Manipulado separadamente
|
||||
if (btn.id === 'barBtnComics') return; // Manipulado separadamente
|
||||
if (btn.id === 'barBtnMusica') return;
|
||||
if (btn.id === 'barBtnHistoria') return;
|
||||
|
||||
const mode = btn.dataset.mode;
|
||||
const text = userInput.value.trim();
|
||||
@@ -5729,6 +5731,374 @@ window.toggleCustomAudio = (btn) => {
|
||||
timeSpan.textContent = '0:00';
|
||||
};
|
||||
};
|
||||
// ============================================================
|
||||
// MUSICANDO IDEIAS
|
||||
// ============================================================
|
||||
const barBtnMusica = document.getElementById('barBtnMusica');
|
||||
const musicaModal = document.getElementById('musicaModal');
|
||||
const btnCloseMusicaModal = document.getElementById('btnCloseMusicaModal');
|
||||
const btnOpenMusicaHistory = document.getElementById('btnOpenMusicaHistory');
|
||||
const musicaHistoryModal = document.getElementById('musicaHistoryModal');
|
||||
const btnCloseMusicaHistory = document.getElementById('btnCloseMusicaHistory');
|
||||
|
||||
const musicaFaixaEtaria = document.getElementById('musicaFaixaEtaria');
|
||||
const musicaRitmo = document.getElementById('musicaRitmo');
|
||||
const musicaTemaInput = document.getElementById('musicaTemaInput');
|
||||
const btnGenerateMusica = document.getElementById('btnGenerateMusica');
|
||||
|
||||
const musicaGenerateLoader = document.getElementById('musicaGenerateLoader');
|
||||
const musicaResultArea = document.getElementById('musicaResultArea');
|
||||
const musicaTextOutput = document.getElementById('musicaTextOutput');
|
||||
const btnCopyMusica = document.getElementById('btnCopyMusica');
|
||||
const btnDownloadMusicaPdf = document.getElementById('btnDownloadMusicaPdf');
|
||||
|
||||
let currentMusicaEstrofes = 3;
|
||||
let currentMusica = '';
|
||||
|
||||
document.querySelectorAll('.btn-musica-estrofes').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
document.querySelectorAll('.btn-musica-estrofes').forEach(b => b.classList.remove('active'));
|
||||
e.target.classList.add('active');
|
||||
currentMusicaEstrofes = parseInt(e.target.dataset.estrofes);
|
||||
});
|
||||
});
|
||||
|
||||
if (barBtnMusica) {
|
||||
barBtnMusica.addEventListener('click', () => {
|
||||
musicaModal.style.display = 'flex';
|
||||
musicaTemaInput.value = '';
|
||||
musicaResultArea.style.display = 'none';
|
||||
musicaGenerateLoader.style.display = 'none';
|
||||
btnGenerateMusica.disabled = false;
|
||||
currentMusica = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCloseMusicaModal) musicaModal.addEventListener('click', (e) => { if(e.target === musicaModal) musicaModal.style.display = 'none'; });
|
||||
if (btnCloseMusicaModal) btnCloseMusicaModal.addEventListener('click', () => musicaModal.style.display = 'none');
|
||||
|
||||
if (btnOpenMusicaHistory) {
|
||||
btnOpenMusicaHistory.addEventListener('click', () => {
|
||||
musicaHistoryModal.style.display = 'flex';
|
||||
loadMusicaHistory();
|
||||
});
|
||||
}
|
||||
if (btnCloseMusicaHistory) btnCloseMusicaHistory.addEventListener('click', () => musicaHistoryModal.style.display = 'none');
|
||||
musicaHistoryModal.addEventListener('click', (e) => { if(e.target === musicaHistoryModal) musicaHistoryModal.style.display = 'none'; });
|
||||
|
||||
if (btnGenerateMusica) {
|
||||
btnGenerateMusica.addEventListener('click', async () => {
|
||||
const tema = musicaTemaInput.value.trim();
|
||||
if (!tema) {
|
||||
showToast('Por favor, informe o tema da música.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btnGenerateMusica.disabled = true;
|
||||
musicaResultArea.style.display = 'none';
|
||||
musicaGenerateLoader.style.display = 'flex';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/musicas/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
faixaEtaria: musicaFaixaEtaria.value,
|
||||
estrofes: currentMusicaEstrofes,
|
||||
ritmo: musicaRitmo.value,
|
||||
tema: tema
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentMusica = data.musica;
|
||||
musicaTextOutput.innerHTML = window.marked ? marked.parse(currentMusica) : currentMusica;
|
||||
musicaResultArea.style.display = 'flex';
|
||||
} else {
|
||||
showToast(data.error || 'Erro ao gerar música.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('Erro de conexão.', 'error');
|
||||
} finally {
|
||||
musicaGenerateLoader.style.display = 'none';
|
||||
btnGenerateMusica.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCopyMusica) {
|
||||
btnCopyMusica.addEventListener('click', () => {
|
||||
if (currentMusica) {
|
||||
navigator.clipboard.writeText(currentMusica);
|
||||
showToast('Música copiada para a área de transferência!', 'success');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnDownloadMusicaPdf) {
|
||||
btnDownloadMusicaPdf.addEventListener('click', () => {
|
||||
if (!currentMusica) return;
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>Música - Assistente PedaGog</title>
|
||||
<style>
|
||||
body { font-family: 'Outfit', 'Inter', sans-serif; padding: 40px; color: #333; line-height: 1.6; }
|
||||
h1, h2, h3 { color: #10b981; margin-top: 24px; margin-bottom: 12px; }
|
||||
@media print { body { padding: 0; } button { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 800px; margin: 0 auto;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<h1 style="margin: 0;">🎵 Música Pedagógica</h1>
|
||||
<button onclick="window.print()" style="background: #10b981; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold;">Imprimir / PDF</button>
|
||||
</div>
|
||||
<div>${window.marked ? marked.parse(currentMusica) : currentMusica}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
});
|
||||
}
|
||||
|
||||
async function loadMusicaHistory() {
|
||||
const listContainer = document.getElementById('musicaHistoryList');
|
||||
if (!listContainer) return;
|
||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando...</p>';
|
||||
try {
|
||||
const response = await fetch('/api/musicas/list');
|
||||
const data = await response.json();
|
||||
if (data && data.length > 0) {
|
||||
listContainer.innerHTML = '';
|
||||
data.forEach(item => {
|
||||
const date = new Date(item.created_at).toLocaleString('pt-BR');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'history-item-card';
|
||||
div.style.cssText = 'background: rgba(255,255,255,0.02); padding: 14px; border-radius: 8px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 8px; cursor: pointer; position: relative;';
|
||||
div.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding-right: 30px;">
|
||||
<strong style="color: var(--brand-green); font-size: 0.95rem;">${item.faixa_etaria} - ${item.ritmo}</strong>
|
||||
<span style="font-size: 0.75rem; color: var(--text-secondary);">${date}</span>
|
||||
</div>
|
||||
<p style="margin: 0; font-size: 0.85rem; color: var(--text-primary); opacity: 0.9;">Tema: ${item.tema}</p>
|
||||
<button class="btn-delete-musica" data-id="${item.id}" style="position: absolute; top: 12px; right: 12px; background: transparent; border: none; color: #ef4444; cursor: pointer; font-size: 1.1rem; padding: 4px; border-radius: 4px;" title="Excluir">🗑️</button>
|
||||
`;
|
||||
div.addEventListener('click', async (e) => {
|
||||
if (e.target.closest('.btn-delete-musica')) {
|
||||
e.stopPropagation();
|
||||
if (confirm('Tem certeza que deseja excluir?')) {
|
||||
try {
|
||||
const delRes = await fetch(`/api/musicas/${item.id}`, { method: 'DELETE' });
|
||||
if (delRes.ok) { showToast('Música excluída.', 'success'); loadMusicaHistory(); }
|
||||
} catch (err) { showToast('Erro ao excluir.', 'error'); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`/api/musicas/${item.id}`);
|
||||
const detail = await res.json();
|
||||
if (res.ok) {
|
||||
currentMusica = detail.musica;
|
||||
musicaTextOutput.innerHTML = window.marked ? marked.parse(currentMusica) : currentMusica;
|
||||
musicaResultArea.style.display = 'flex';
|
||||
musicaHistoryModal.style.display = 'none';
|
||||
}
|
||||
} catch (err) { showToast('Erro ao carregar.', 'error'); }
|
||||
});
|
||||
listContainer.appendChild(div);
|
||||
});
|
||||
} else {
|
||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Nenhum histórico.</p>';
|
||||
}
|
||||
} catch (err) { listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar.</p>'; }
|
||||
}
|
||||
|
||||
|
||||
// ============================================================
|
||||
// INVENTANDO HISTORIAS
|
||||
// ============================================================
|
||||
const barBtnHistoria = document.getElementById('barBtnHistoria');
|
||||
const historiaModal = document.getElementById('historiaModal');
|
||||
const btnCloseHistoriaModal = document.getElementById('btnCloseHistoriaModal');
|
||||
const btnOpenHistoriaHistory = document.getElementById('btnOpenHistoriaHistory');
|
||||
const historiaHistoryModal = document.getElementById('historiaHistoryModal');
|
||||
const btnCloseHistoriaHistory = document.getElementById('btnCloseHistoriaHistory');
|
||||
|
||||
const historiaFaixaEtaria = document.getElementById('historiaFaixaEtaria');
|
||||
const historiaParagrafos = document.getElementById('historiaParagrafos');
|
||||
const historiaPersonagens = document.getElementById('historiaPersonagens');
|
||||
const historiaEstilo = document.getElementById('historiaEstilo');
|
||||
const historiaTemaInput = document.getElementById('historiaTemaInput');
|
||||
const btnGenerateHistoria = document.getElementById('btnGenerateHistoria');
|
||||
|
||||
const historiaGenerateLoader = document.getElementById('historiaGenerateLoader');
|
||||
const historiaResultArea = document.getElementById('historiaResultArea');
|
||||
const historiaTextOutput = document.getElementById('historiaTextOutput');
|
||||
const btnCopyHistoria = document.getElementById('btnCopyHistoria');
|
||||
const btnDownloadHistoriaPdf = document.getElementById('btnDownloadHistoriaPdf');
|
||||
|
||||
let currentHistoria = '';
|
||||
|
||||
if (barBtnHistoria) {
|
||||
barBtnHistoria.addEventListener('click', () => {
|
||||
historiaModal.style.display = 'flex';
|
||||
historiaTemaInput.value = '';
|
||||
historiaResultArea.style.display = 'none';
|
||||
historiaGenerateLoader.style.display = 'none';
|
||||
btnGenerateHistoria.disabled = false;
|
||||
currentHistoria = '';
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCloseHistoriaModal) historiaModal.addEventListener('click', (e) => { if(e.target === historiaModal) historiaModal.style.display = 'none'; });
|
||||
if (btnCloseHistoriaModal) btnCloseHistoriaModal.addEventListener('click', () => historiaModal.style.display = 'none');
|
||||
|
||||
if (btnOpenHistoriaHistory) {
|
||||
btnOpenHistoriaHistory.addEventListener('click', () => {
|
||||
historiaHistoryModal.style.display = 'flex';
|
||||
loadHistoriaHistory();
|
||||
});
|
||||
}
|
||||
if (btnCloseHistoriaHistory) btnCloseHistoriaHistory.addEventListener('click', () => historiaHistoryModal.style.display = 'none');
|
||||
historiaHistoryModal.addEventListener('click', (e) => { if(e.target === historiaHistoryModal) historiaHistoryModal.style.display = 'none'; });
|
||||
|
||||
if (btnGenerateHistoria) {
|
||||
btnGenerateHistoria.addEventListener('click', async () => {
|
||||
const tema = historiaTemaInput.value.trim();
|
||||
if (!tema) {
|
||||
showToast('Por favor, informe o tema da história.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btnGenerateHistoria.disabled = true;
|
||||
historiaResultArea.style.display = 'none';
|
||||
historiaGenerateLoader.style.display = 'flex';
|
||||
|
||||
try {
|
||||
const response = await fetch('/api/historias/generate', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
faixaEtaria: historiaFaixaEtaria.value,
|
||||
paragrafos: historiaParagrafos.value,
|
||||
personagens: historiaPersonagens.value,
|
||||
estilo: historiaEstilo.value,
|
||||
tema: tema
|
||||
})
|
||||
});
|
||||
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
currentHistoria = data.historia;
|
||||
historiaTextOutput.innerHTML = window.marked ? marked.parse(currentHistoria) : currentHistoria;
|
||||
historiaResultArea.style.display = 'flex';
|
||||
} else {
|
||||
showToast(data.error || 'Erro ao inventar história.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('Erro de conexão.', 'error');
|
||||
} finally {
|
||||
historiaGenerateLoader.style.display = 'none';
|
||||
btnGenerateHistoria.disabled = false;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCopyHistoria) {
|
||||
btnCopyHistoria.addEventListener('click', () => {
|
||||
if (currentHistoria) {
|
||||
navigator.clipboard.writeText(currentHistoria);
|
||||
showToast('História copiada para a área de transferência!', 'success');
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
if (btnDownloadHistoriaPdf) {
|
||||
btnDownloadHistoriaPdf.addEventListener('click', () => {
|
||||
if (!currentHistoria) return;
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>História - Assistente PedaGog</title>
|
||||
<style>
|
||||
body { font-family: 'Outfit', 'Inter', sans-serif; padding: 40px; color: #333; line-height: 1.6; }
|
||||
h1, h2, h3 { color: #10b981; margin-top: 24px; margin-bottom: 12px; }
|
||||
@media print { body { padding: 0; } button { display: none; } }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div style="max-width: 800px; margin: 0 auto;">
|
||||
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; margin-bottom: 20px;">
|
||||
<h1 style="margin: 0;">✍️ História Pedagógica</h1>
|
||||
<button onclick="window.print()" style="background: #10b981; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold;">Imprimir / PDF</button>
|
||||
</div>
|
||||
<div>${window.marked ? marked.parse(currentHistoria) : currentHistoria}</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
});
|
||||
}
|
||||
|
||||
async function loadHistoriaHistory() {
|
||||
const listContainer = document.getElementById('historiaHistoryList');
|
||||
if (!listContainer) return;
|
||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando...</p>';
|
||||
try {
|
||||
const response = await fetch('/api/historias/list');
|
||||
const data = await response.json();
|
||||
if (data && data.length > 0) {
|
||||
listContainer.innerHTML = '';
|
||||
data.forEach(item => {
|
||||
const date = new Date(item.created_at).toLocaleString('pt-BR');
|
||||
const div = document.createElement('div');
|
||||
div.className = 'history-item-card';
|
||||
div.style.cssText = 'background: rgba(255,255,255,0.02); padding: 14px; border-radius: 8px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 8px; cursor: pointer; position: relative;';
|
||||
div.innerHTML = `
|
||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding-right: 30px;">
|
||||
<strong style="color: var(--brand-green); font-size: 0.95rem;">${item.faixa_etaria} (${item.estilo})</strong>
|
||||
<span style="font-size: 0.75rem; color: var(--text-secondary);">${date}</span>
|
||||
</div>
|
||||
<p style="margin: 0; font-size: 0.85rem; color: var(--text-primary); opacity: 0.9;">Tema: ${item.tema}</p>
|
||||
<button class="btn-delete-historia" data-id="${item.id}" style="position: absolute; top: 12px; right: 12px; background: transparent; border: none; color: #ef4444; cursor: pointer; font-size: 1.1rem; padding: 4px; border-radius: 4px;" title="Excluir">🗑️</button>
|
||||
`;
|
||||
div.addEventListener('click', async (e) => {
|
||||
if (e.target.closest('.btn-delete-historia')) {
|
||||
e.stopPropagation();
|
||||
if (confirm('Tem certeza que deseja excluir?')) {
|
||||
try {
|
||||
const delRes = await fetch(`/api/historias/${item.id}`, { method: 'DELETE' });
|
||||
if (delRes.ok) { showToast('História excluída.', 'success'); loadHistoriaHistory(); }
|
||||
} catch (err) { showToast('Erro ao excluir.', 'error'); }
|
||||
}
|
||||
return;
|
||||
}
|
||||
try {
|
||||
const res = await fetch(`/api/historias/${item.id}`);
|
||||
const detail = await res.json();
|
||||
if (res.ok) {
|
||||
currentHistoria = detail.historia;
|
||||
historiaTextOutput.innerHTML = window.marked ? marked.parse(currentHistoria) : currentHistoria;
|
||||
historiaResultArea.style.display = 'flex';
|
||||
historiaHistoryModal.style.display = 'none';
|
||||
}
|
||||
} catch (err) { showToast('Erro ao carregar.', 'error'); }
|
||||
});
|
||||
listContainer.appendChild(div);
|
||||
});
|
||||
} else {
|
||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Nenhum histórico.</p>';
|
||||
}
|
||||
} catch (err) { listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar.</p>'; }
|
||||
}
|
||||
|
||||
if (document.readyState === 'loading') {
|
||||
document.addEventListener('DOMContentLoaded', initApp);
|
||||
|
||||
+170
-2
@@ -238,7 +238,7 @@
|
||||
<button type="button" class="btn-prompt-mode mode-bncc" data-prompt="Crie um planejamento pedagógico de artes alinhado à BNCC para o campo 'Traços, sons, cores e formas'." style="border-color: rgba(234, 179, 8, 0.4); color: #fef08a; background: rgba(234, 179, 8, 0.05);" title="Atividade BNCC">
|
||||
<span class="mode-icon">🎨</span> Atividade BNCC
|
||||
</button>
|
||||
<button type="button" class="btn-prompt-mode mode-music" data-mode="AUDIO" title="Compor e gerar uma música pedagógica">
|
||||
<button type="button" class="btn-prompt-mode mode-music" id="barBtnMusica" title="Compor e gerar uma música pedagógica">
|
||||
<span class="mode-icon">🎵</span> Musicando Ideias
|
||||
</button>
|
||||
<button type="button" class="btn-prompt-mode mode-video" data-mode="VIDEO" title="Criar um roteiro ou animação de vídeo">
|
||||
@@ -250,7 +250,7 @@
|
||||
<button type="button" class="btn-prompt-mode mode-poetry" id="barBtnPoesia" title="Escrever uma poesia infantil pedagógica">
|
||||
<span class="mode-icon">📜</span> Estúdio de Poesia
|
||||
</button>
|
||||
<button type="button" class="btn-prompt-mode mode-text" data-mode="TEXT" title="Escrever histórias, relatórios ou conversar">
|
||||
<button type="button" class="btn-prompt-mode mode-text" id="barBtnHistoria" title="Escrever histórias, relatórios ou conversar">
|
||||
<span class="mode-icon">✍️</span> Inventando Historias
|
||||
</button>
|
||||
</div>
|
||||
@@ -737,6 +737,174 @@
|
||||
</div>
|
||||
|
||||
<!-- Modal: Estúdio de Poesia -->
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: MUSICANDO IDEIAS -->
|
||||
<!-- ========================================== -->
|
||||
<div id="musicaModal" class="settings-modal" style="display: none;">
|
||||
<div class="settings-modal-content" style="max-width: 600px; 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);">
|
||||
<h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">🎵 Musicando Ideias PedaGog</h3>
|
||||
<button id="btnCloseMusicaModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px 20px; display: flex; justify-content: flex-end; border-bottom: 1px solid var(--border-light);">
|
||||
<button id="btnOpenMusicaHistory" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-primary); padding: 6px 12px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 6px; font-size: 0.85rem;">🕒 Ver Histórico</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;">
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">1. Faixa Etária (Idade da Criança)</label>
|
||||
<select id="musicaFaixaEtaria" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="Bebês (0 a 1 ano e 6 meses)">Bebês (0 a 1 ano e 6 meses)</option>
|
||||
<option value="Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)">Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)</option>
|
||||
<option value="Crianças pequenas (4 anos a 5 anos e 11 meses)" selected>Crianças pequenas (4 anos a 5 anos e 11 meses)</option>
|
||||
<option value="Ensino Fundamental I (6 a 10 anos)">Ensino Fundamental I (6 a 10 anos)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">2. Ritmo Musical</label>
|
||||
<select id="musicaRitmo" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="Cantiga de Roda">Cantiga de Roda</option>
|
||||
<option value="Pop Infantil">Pop Infantil</option>
|
||||
<option value="Samba/Pagode">Samba/Pagode</option>
|
||||
<option value="Forró">Forró</option>
|
||||
<option value="Rock">Rock</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">3. Quantidade de Estrofes</label>
|
||||
<div class="music-option-grid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 4px;">
|
||||
<button type="button" class="btn-musica-estrofes mode-card" data-estrofes="2" style="padding: 10px; text-align: center;">2</button>
|
||||
<button type="button" class="btn-musica-estrofes mode-card active" data-estrofes="3" style="padding: 10px; text-align: center;">3</button>
|
||||
<button type="button" class="btn-musica-estrofes mode-card" data-estrofes="4" style="padding: 10px; text-align: center;">4</button>
|
||||
<button type="button" class="btn-musica-estrofes mode-card" data-estrofes="5" style="padding: 10px; text-align: center;">5</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">4. Tema da Música</label>
|
||||
<input type="text" id="musicaTemaInput" placeholder="Ex: amizade na escola, lavar as mãos, alfabeto..." class="obs-input" style="width: 100%; margin-top: 4px; padding: 10px;">
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnGenerateMusica" class="btn-login" style="margin-top: 10px; padding: 14px; font-size: 1rem;">🎵 Compor Música</button>
|
||||
|
||||
<div id="musicaGenerateLoader" style="display: none; align-items: center; justify-content: center; gap: 10px; margin-top: 15px;">
|
||||
<div class="spinner"></div>
|
||||
<span style="color: var(--text-secondary); font-size: 0.9rem;">Compondo a letra...</span>
|
||||
</div>
|
||||
|
||||
<div id="musicaResultArea" style="display: none; flex-direction: column; gap: 12px; margin-top: 20px;">
|
||||
<div style="background: rgba(16, 185, 129, 0.05); border: 1px solid rgba(16, 185, 129, 0.2); padding: 15px; border-radius: 8px;">
|
||||
<h4 style="color: var(--brand-green); margin-top: 0; margin-bottom: 10px;">Letra Gerada:</h4>
|
||||
<div id="musicaTextOutput" style="font-size: 0.95rem; line-height: 1.6; white-space: pre-wrap; color: var(--text-primary);"></div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<button id="btnCopyMusica" style="background: var(--bg-secondary); border: 1px solid var(--border-light); color: var(--text-primary); padding: 8px 16px; border-radius: 6px; cursor: pointer;">📋 Copiar</button>
|
||||
<button id="btnDownloadMusicaPdf" style="background: var(--brand-green); border: none; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold;">📄 Salvar PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="musicaHistoryModal" class="settings-modal" style="display: none; z-index: 10001;">
|
||||
<div class="settings-modal-content" style="max-width: 500px; 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 de Músicas</h3>
|
||||
<button id="btnCloseMusicaHistory" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
<div id="musicaHistoryList" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: INVENTANDO HISTORIAS -->
|
||||
<!-- ========================================== -->
|
||||
<div id="historiaModal" class="settings-modal" style="display: none;">
|
||||
<div class="settings-modal-content" style="max-width: 600px; 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);">
|
||||
<h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">✍️ Inventando Histórias PedaGog</h3>
|
||||
<button id="btnCloseHistoriaModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
|
||||
<div style="padding: 12px 20px; display: flex; justify-content: flex-end; border-bottom: 1px solid var(--border-light);">
|
||||
<button id="btnOpenHistoriaHistory" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-primary); padding: 6px 12px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 6px; font-size: 0.85rem;">🕒 Ver Histórico</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;">
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">1. Faixa Etária</label>
|
||||
<select id="historiaFaixaEtaria" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="Bebês (0 a 1 ano e 6 meses)">Bebês (0 a 1 ano e 6 meses)</option>
|
||||
<option value="Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)">Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)</option>
|
||||
<option value="Crianças pequenas (4 anos a 5 anos e 11 meses)" selected>Crianças pequenas (4 anos a 5 anos e 11 meses)</option>
|
||||
<option value="Ensino Fundamental I (6 a 10 anos)">Ensino Fundamental I (6 a 10 anos)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px; align-items: flex-start;">
|
||||
<div class="settings-group" style="flex: 1;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Tamanho (Parágrafos)</label>
|
||||
<select id="historiaParagrafos" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="2">2 Parágrafos</option>
|
||||
<option value="3" selected>3 Parágrafos</option>
|
||||
<option value="5">5 Parágrafos</option>
|
||||
<option value="7">7 Parágrafos</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-group" style="flex: 1;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Personagens</label>
|
||||
<input type="number" id="historiaPersonagens" value="2" min="1" max="10" class="obs-input" style="width: 100%; margin-top: 4px; padding: 10px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">3. Estilo da Narrativa</label>
|
||||
<select id="historiaEstilo" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="Narração Direta">Narração Direta (Apenas Narrador)</option>
|
||||
<option value="Dialogada">Dialogada (Com fala dos personagens)</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">4. Tema da História</label>
|
||||
<input type="text" id="historiaTemaInput" placeholder="Ex: Uma fada que não sabia voar, a importância de dividir..." class="obs-input" style="width: 100%; margin-top: 4px; padding: 10px;">
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnGenerateHistoria" class="btn-login" style="margin-top: 10px; padding: 14px; font-size: 1rem;">✍️ Inventar História</button>
|
||||
|
||||
<div id="historiaGenerateLoader" style="display: none; align-items: center; justify-content: center; gap: 10px; margin-top: 15px;">
|
||||
<div class="spinner"></div>
|
||||
<span style="color: var(--text-secondary); font-size: 0.9rem;">Inventando a história...</span>
|
||||
</div>
|
||||
|
||||
<div id="historiaResultArea" style="display: none; flex-direction: column; gap: 12px; margin-top: 20px;">
|
||||
<div style="background: rgba(16, 185, 129, 0.05); border: 1px solid rgba(16, 185, 129, 0.2); padding: 15px; border-radius: 8px;">
|
||||
<h4 style="color: var(--brand-green); margin-top: 0; margin-bottom: 10px;">História Gerada:</h4>
|
||||
<div id="historiaTextOutput" style="font-size: 0.95rem; line-height: 1.6; white-space: pre-wrap; color: var(--text-primary);"></div>
|
||||
</div>
|
||||
<div style="display: flex; gap: 10px; justify-content: flex-end;">
|
||||
<button id="btnCopyHistoria" style="background: var(--bg-secondary); border: 1px solid var(--border-light); color: var(--text-primary); padding: 8px 16px; border-radius: 6px; cursor: pointer;">📋 Copiar</button>
|
||||
<button id="btnDownloadHistoriaPdf" style="background: var(--brand-green); border: none; color: white; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold;">📄 Salvar PDF</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="historiaHistoryModal" class="settings-modal" style="display: none; z-index: 10001;">
|
||||
<div class="settings-modal-content" style="max-width: 500px; 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 de Histórias</h3>
|
||||
<button id="btnCloseHistoriaHistory" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
<div id="historiaHistoryList" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
<div id="poesiaModal" class="settings-modal" style="display: none;">
|
||||
<div class="settings-modal-content" style="max-width: 600px; 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);">
|
||||
|
||||
@@ -906,7 +906,7 @@ Retorne a resposta estruturada em Markdown, usando títulos claros:
|
||||
],
|
||||
generationConfig: {
|
||||
temperature: 0.4,
|
||||
maxOutputTokens: 2048
|
||||
maxOutputTokens: 8192
|
||||
}
|
||||
})
|
||||
});
|
||||
@@ -1277,9 +1277,173 @@ app.delete('/api/poesias/:id', requireAuth, async (req, res) => {
|
||||
} catch (err) {
|
||||
console.error('Erro ao deletar poesia:', err);
|
||||
res.status(500).json({ error: 'Erro ao deletar poesia.' });
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// ROTAS DO MUSICANDO IDEIAS
|
||||
// ============================================================
|
||||
app.post('/api/musicas/generate', requireAuth, async (req, res) => {
|
||||
const { faixaEtaria, estrofes, ritmo, tema } = req.body;
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
if (!faixaEtaria || !estrofes || !ritmo || !tema) {
|
||||
return res.status(400).json({ error: 'Faixa etária, estrofes, ritmo e tema são obrigatórios.' });
|
||||
}
|
||||
|
||||
try {
|
||||
let idadeInstrucao = '';
|
||||
if (faixaEtaria.includes('Bebês') || faixaEtaria.includes('bem pequenas')) {
|
||||
idadeInstrucao = '- NÍVEL FÁCIL: Use palavras EXTREMAMENTE simples e curtas. Foco em onomatopeias e repetições.';
|
||||
} else {
|
||||
idadeInstrucao = '- NÍVEL MÉDIO/AVANÇADO: Vocabulário lúdico, com rimas criativas.';
|
||||
}
|
||||
|
||||
const systemInstruction = `Você é um compositor infantil especializado em criar músicas pedagógicas animadas no Brasil.
|
||||
REGRAS:
|
||||
- Faixa etária: ${faixaEtaria}
|
||||
${idadeInstrucao}
|
||||
- Estrofes: ${estrofes}
|
||||
- Ritmo musical base: ${ritmo}
|
||||
- Tema: "${tema}"
|
||||
- A música deve ter musicalidade e métrica próprias do ritmo escolhido (${ritmo}).
|
||||
- Responda APENAS a letra da música em Markdown.`;
|
||||
|
||||
let musicaText = '';
|
||||
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 }] }] })
|
||||
});
|
||||
const data = await response.json();
|
||||
musicaText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
}
|
||||
|
||||
// Limpeza
|
||||
musicaText = musicaText.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
||||
|
||||
if (!musicaText) throw new Error('Falha ao gerar música');
|
||||
|
||||
const dbRes = await dbPool.query(
|
||||
`INSERT INTO escola.musicas (usuario_id, faixa_etaria, estrofes, ritmo, tema, musica) VALUES ($1, $2, $3, $4, $5, $6) RETURNING id`,
|
||||
[usuarioId, faixaEtaria, estrofes, ritmo, tema, musicaText]
|
||||
);
|
||||
|
||||
res.json({ id: dbRes.rows[0].id, musica: musicaText });
|
||||
} catch (err) {
|
||||
console.error('Erro Música:', err);
|
||||
res.status(500).json({ error: 'Erro ao gerar música.' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/musicas/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, ritmo, tema, created_at FROM escola.musicas 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.' }); }
|
||||
});
|
||||
|
||||
app.get('/api/musicas/:id', requireAuth, async (req, res) => {
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(`SELECT * FROM escola.musicas 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/musicas/:id', requireAuth, async (req, res) => {
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(`DELETE FROM escola.musicas 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.' }); }
|
||||
});
|
||||
|
||||
|
||||
// ============================================================
|
||||
// ROTAS DO INVENTANDO HISTÓRIAS
|
||||
// ============================================================
|
||||
app.post('/api/historias/generate', requireAuth, async (req, res) => {
|
||||
const { faixaEtaria, paragrafos, personagens, estilo, tema } = req.body;
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
if (!faixaEtaria || !paragrafos || !personagens || !estilo || !tema) {
|
||||
return res.status(400).json({ error: 'Todos os campos são obrigatórios.' });
|
||||
}
|
||||
|
||||
try {
|
||||
let idadeInstrucao = '';
|
||||
if (faixaEtaria.includes('Bebês') || faixaEtaria.includes('bem pequenas')) {
|
||||
idadeInstrucao = '- NÍVEL FÁCIL: Enredo muito simples, frases curtas, foco em ações diretas.';
|
||||
} else {
|
||||
idadeInstrucao = '- NÍVEL MÉDIO/AVANÇADO: Enredo com lição de moral, imaginação fluida e bom vocabulário.';
|
||||
}
|
||||
|
||||
const systemInstruction = `Você é um contador de histórias pedagógico focado na educação infantil brasileira.
|
||||
REGRAS:
|
||||
- Faixa etária: ${faixaEtaria}
|
||||
${idadeInstrucao}
|
||||
- Tamanho: Exatamente ${paragrafos} parágrafos.
|
||||
- Quantidade de personagens: ${personagens}
|
||||
- Estilo narrativo: ${estilo} (se Dialogada, inclua falas claras entre os personagens; se Narração Direta, apenas o narrador conta tudo).
|
||||
- Tema: "${tema}"
|
||||
- Responda APENAS a história em Markdown.`;
|
||||
|
||||
let historiaText = '';
|
||||
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 }] }] })
|
||||
});
|
||||
const data = await response.json();
|
||||
historiaText = data.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||
}
|
||||
|
||||
historiaText = historiaText.replace(/<think>[\s\S]*?<\/think>/gi, '').trim();
|
||||
|
||||
if (!historiaText) throw new Error('Falha ao inventar história');
|
||||
|
||||
const dbRes = await dbPool.query(
|
||||
`INSERT INTO escola.historias (usuario_id, faixa_etaria, paragrafos, personagens, estilo, tema, historia) VALUES ($1, $2, $3, $4, $5, $6, $7) RETURNING id`,
|
||||
[usuarioId, faixaEtaria, paragrafos, personagens, estilo, tema, historiaText]
|
||||
);
|
||||
|
||||
res.json({ id: dbRes.rows[0].id, historia: historiaText });
|
||||
} catch (err) {
|
||||
console.error('Erro História:', err);
|
||||
res.status(500).json({ error: 'Erro ao inventar história.' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/historias/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, estilo, tema, created_at FROM escola.historias 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.' }); }
|
||||
});
|
||||
|
||||
app.get('/api/historias/:id', requireAuth, async (req, res) => {
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(`SELECT * FROM escola.historias 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/historias/:id', requireAuth, async (req, res) => {
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(`DELETE FROM escola.historias 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
|
||||
app.get('/api/agent-config', requireAuth, (req, res) => {
|
||||
const config = readAgentConfig();
|
||||
|
||||
Reference in New Issue
Block a user