Feature: Estúdio de Poesias profissional com dual-stage engine, analise de metricas literarias, guia fonetico, audio TTS narrado, player com equalizer e exportacao PDF/TXT
This commit is contained in:
+522
-124
@@ -6168,6 +6168,8 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
|||||||
// ============================================================
|
// ============================================================
|
||||||
// ESTÚDIO DE POESIA
|
// ESTÚDIO DE POESIA
|
||||||
// ============================================================
|
// ============================================================
|
||||||
|
// ESTÚDIO DE POESIAS (ESTÚDIO PROFISSIONAL)
|
||||||
|
// ============================================================
|
||||||
const poesiaModal = document.getElementById('poesiaModal');
|
const poesiaModal = document.getElementById('poesiaModal');
|
||||||
const btnClosePoesiaModal = document.getElementById('btnClosePoesiaModal');
|
const btnClosePoesiaModal = document.getElementById('btnClosePoesiaModal');
|
||||||
const barBtnPoesia = document.getElementById('barBtnPoesia');
|
const barBtnPoesia = document.getElementById('barBtnPoesia');
|
||||||
@@ -6175,47 +6177,499 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
|||||||
const poesiaHistoryModal = document.getElementById('poesiaHistoryModal');
|
const poesiaHistoryModal = document.getElementById('poesiaHistoryModal');
|
||||||
const btnClosePoesiaHistory = document.getElementById('btnClosePoesiaHistory');
|
const btnClosePoesiaHistory = document.getElementById('btnClosePoesiaHistory');
|
||||||
|
|
||||||
|
const poesiaTemplateSelect = document.getElementById('poesiaTemplateSelect');
|
||||||
|
const poesiaTemaInput = document.getElementById('poesiaTemaInput');
|
||||||
|
const poesiaTurmaSelect = document.getElementById('poesiaTurmaSelect');
|
||||||
|
const poesiaFaixaEtaria = document.getElementById('poesiaFaixaEtaria');
|
||||||
|
const poesiaEstruturaSelect = document.getElementById('poesiaEstruturaSelect');
|
||||||
|
const poesiaEstiloSelect = document.getElementById('poesiaEstiloSelect');
|
||||||
|
const poesiaVozSelect = document.getElementById('poesiaVozSelect');
|
||||||
const btnGeneratePoesia = document.getElementById('btnGeneratePoesia');
|
const btnGeneratePoesia = document.getElementById('btnGeneratePoesia');
|
||||||
const poesiaGenerateLoader = document.getElementById('poesiaGenerateLoader');
|
const poesiaGenerateLoader = document.getElementById('poesiaGenerateLoader');
|
||||||
const poesiaResultArea = document.getElementById('poesiaResultArea');
|
|
||||||
const poesiaTextOutput = document.getElementById('poesiaTextOutput');
|
|
||||||
const btnCopyPoesia = document.getElementById('btnCopyPoesia');
|
|
||||||
const btnDownloadPoesiaPdf = document.getElementById('btnDownloadPoesiaPdf');
|
|
||||||
|
|
||||||
const poesiaTemaInput = document.getElementById('poesiaTemaInput');
|
// Elementos do Painel Direito
|
||||||
const poesiaFaixaEtaria = document.getElementById('poesiaFaixaEtaria');
|
const poesiaBnccBadge = document.getElementById('poesiaBnccBadge');
|
||||||
|
const poesiaMetricsBox = document.getElementById('poesiaMetricsBox');
|
||||||
|
const poesiaEsquemaBadge = document.getElementById('poesiaEsquemaBadge');
|
||||||
|
const poesiaMetricRimaVal = document.getElementById('poesiaMetricRimaVal');
|
||||||
|
const poesiaMetricRimaBar = document.getElementById('poesiaMetricRimaBar');
|
||||||
|
const poesiaMetricRitmoVal = document.getElementById('poesiaMetricRitmoVal');
|
||||||
|
const poesiaMetricRitmoBar = document.getElementById('poesiaMetricRitmoBar');
|
||||||
|
const poesiaMetricAliteracaoVal = document.getElementById('poesiaMetricAliteracaoVal');
|
||||||
|
const poesiaMetricAliteracaoBar = document.getElementById('poesiaMetricAliteracaoBar');
|
||||||
|
const poesiaMetricMusicalidadeVal = document.getElementById('poesiaMetricMusicalidadeVal');
|
||||||
|
const poesiaMetricMusicalidadeBar = document.getElementById('poesiaMetricMusicalidadeBar');
|
||||||
|
const poesiaMetricaDesc = document.getElementById('poesiaMetricaDesc');
|
||||||
|
|
||||||
let currentPoesia = null;
|
const poesiaAudioPlayerBox = document.getElementById('poesiaAudioPlayerBox');
|
||||||
let selectedEstrofes = 3;
|
const poesiaAudioPlayer = document.getElementById('poesiaAudioPlayer');
|
||||||
|
const poesiaPlayerTitle = document.getElementById('poesiaPlayerTitle');
|
||||||
|
const btnDownloadPoesiaAudio = document.getElementById('btnDownloadPoesiaAudio');
|
||||||
|
|
||||||
// Seleção de estrofes
|
const poesiaContentTabs = document.getElementById('poesiaContentTabs');
|
||||||
|
const btnTabPoesiaOriginal = document.getElementById('btnTabPoesiaOriginal');
|
||||||
|
const btnTabPoesiaFonetica = document.getElementById('btnTabPoesiaFonetica');
|
||||||
|
const btnTabPoesiaPedagogica = document.getElementById('btnTabPoesiaPedagogica');
|
||||||
|
|
||||||
|
const poesiaPlaceholderText = document.getElementById('poesiaPlaceholderText');
|
||||||
|
const poesiaActiveBox = document.getElementById('poesiaActiveBox');
|
||||||
|
const poesiaActiveTitle = document.getElementById('poesiaActiveTitle');
|
||||||
|
const poesiaActiveSubtitle = document.getElementById('poesiaActiveSubtitle');
|
||||||
|
const poesiaActiveContent = document.getElementById('poesiaActiveContent');
|
||||||
|
const poesiaPedagogicalTipBox = document.getElementById('poesiaPedagogicalTipBox');
|
||||||
|
const poesiaPedagogicalTipText = document.getElementById('poesiaPedagogicalTipText');
|
||||||
|
|
||||||
|
const btnCopyActivePoesia = document.getElementById('btnCopyActivePoesia');
|
||||||
|
const btnDownloadActivePoesiaTxt = document.getElementById('btnDownloadActivePoesiaTxt');
|
||||||
|
const btnSendPoesiaToMusic = document.getElementById('btnSendPoesiaToMusic');
|
||||||
|
const btnExportActivePoesiaPdf = document.getElementById('btnExportActivePoesiaPdf');
|
||||||
|
|
||||||
|
let selectedPoesiaEstrofes = 3;
|
||||||
|
let activePoesiaTab = 'original';
|
||||||
|
|
||||||
|
// TEMPLATES TEMÁTICOS DO ESTÚDIO DE POESIAS
|
||||||
|
const POESIA_TEMPLATES = {
|
||||||
|
horta: {
|
||||||
|
tema: "Plantar sementinhas na horta da escola, regar a terra com carinho, ver os brotinhos crescerem e colher vegetais coloridos e saudáveis.",
|
||||||
|
faixa: "Crianças pequenas (4 anos a 5 anos e 11 meses)",
|
||||||
|
estrutura: "Quadras (Rimas A-B-A-B)",
|
||||||
|
estilo: "Lúdico-cantado e sonoro",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 3
|
||||||
|
},
|
||||||
|
animais: {
|
||||||
|
tema: "Os bichinhos da fazenda, o canto do galo pela manhã, a vaquinha mimosa, o cachorrinho amigo e as onomatopeias dos sons dos animais.",
|
||||||
|
faixa: "Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)",
|
||||||
|
estrutura: "Quadras (Rimas A-B-A-B)",
|
||||||
|
estilo: "Lúdico-cantado e sonoro",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 3
|
||||||
|
},
|
||||||
|
familia: {
|
||||||
|
tema: "O abraço quentinho da família, o carinho ao chegar em casa, os beijinhos de boa noite e o afeto entre pais, avós e irmãos.",
|
||||||
|
faixa: "Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)",
|
||||||
|
estrutura: "Redondilhas (7 sílabas poéticas)",
|
||||||
|
estilo: "Sensorial, suave e afetivo",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 3
|
||||||
|
},
|
||||||
|
higiene: {
|
||||||
|
tema: "Bolhas de sabão lavando as mãozinhas, a escova de dentes dançando na boca e o cheirinho gostoso de banho tomado.",
|
||||||
|
faixa: "Crianças pequenas (4 anos a 5 anos e 11 meses)",
|
||||||
|
estrutura: "Quadras (Rimas A-B-A-B)",
|
||||||
|
estilo: "Lúdico-cantado e sonoro",
|
||||||
|
voz: "crianca",
|
||||||
|
estrofes: 3
|
||||||
|
},
|
||||||
|
chuva: {
|
||||||
|
tema: "As gotinhas de chuva batendo ploc-ploc no telhado, o cheiro de terra molhada e a dança com guarda-chuva colorido.",
|
||||||
|
faixa: "Crianças pequenas (4 anos a 5 anos e 11 meses)",
|
||||||
|
estrutura: "Redondilhas (7 sílabas poéticas)",
|
||||||
|
estilo: "Sensorial, suave e afetivo",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 3
|
||||||
|
},
|
||||||
|
alfabeto: {
|
||||||
|
tema: "As letrinhas encantadas brincando no papel, as rimas das vogais e a mágica de escrever o primeiro nome.",
|
||||||
|
faixa: "Crianças pequenas (4 anos a 5 anos e 11 meses)",
|
||||||
|
estrutura: "Quadras (Rimas A-B-A-B)",
|
||||||
|
estilo: "Narrativo-infantil com história",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 4
|
||||||
|
},
|
||||||
|
cores: {
|
||||||
|
tema: "O azul do céu infinito, o amarelo do sol brilhante, o verde das folhinhas e a aquarela das cores na pintura com os dedinhos.",
|
||||||
|
faixa: "Crianças pequenas (4 anos a 5 anos e 11 meses)",
|
||||||
|
estrutura: "Haiku Infantil (5-7-5 sílabas)",
|
||||||
|
estilo: "Sensorial, suave e afetivo",
|
||||||
|
voz: "mulher",
|
||||||
|
estrofes: 3
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Estado da poesia ativa
|
||||||
|
let activePoesiaData = {
|
||||||
|
titulo: '',
|
||||||
|
poesia: '',
|
||||||
|
versaoFonetica: '',
|
||||||
|
esquemaRima: '',
|
||||||
|
analiseLiteraria: { rima: 80, ritmo: 80, aliteracao: 60, musicalidade: 85, explicacaoMetrica: '' },
|
||||||
|
bnccCampos: '',
|
||||||
|
dicas: '',
|
||||||
|
audioUrl: '',
|
||||||
|
tema: '',
|
||||||
|
estrutura: '',
|
||||||
|
estilo: '',
|
||||||
|
faixaEtaria: ''
|
||||||
|
};
|
||||||
|
|
||||||
|
// Botões de contagem de estrofes
|
||||||
document.querySelectorAll('.btn-poesia-estrofes').forEach(btn => {
|
document.querySelectorAll('.btn-poesia-estrofes').forEach(btn => {
|
||||||
btn.addEventListener('click', (e) => {
|
btn.addEventListener('click', (e) => {
|
||||||
document.querySelectorAll('.btn-poesia-estrofes').forEach(b => b.classList.remove('active'));
|
document.querySelectorAll('.btn-poesia-estrofes').forEach(b => b.classList.remove('active'));
|
||||||
e.target.classList.add('active');
|
e.target.classList.add('active');
|
||||||
selectedEstrofes = parseInt(e.target.dataset.estrofes);
|
selectedPoesiaEstrofes = parseInt(e.target.dataset.estrofes);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Abrir/Fechar Modal Principal
|
// Carregar turmas no seletor do estúdio de poesia
|
||||||
|
const loadTurmasForPoesia = async () => {
|
||||||
|
if (!poesiaTurmaSelect) return;
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/turmas');
|
||||||
|
if (res.ok) {
|
||||||
|
const turmas = await res.json();
|
||||||
|
poesiaTurmaSelect.innerHTML = '<option value="">-- Selecionar Turma --</option>';
|
||||||
|
turmas.forEach(t => {
|
||||||
|
const opt = document.createElement('option');
|
||||||
|
opt.value = t.id;
|
||||||
|
opt.textContent = `${t.nome} (${t.ano || '2026'})`;
|
||||||
|
poesiaTurmaSelect.appendChild(opt);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.warn('Erro ao carregar turmas para poesia:', err);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Abrir Modal
|
||||||
if (barBtnPoesia) {
|
if (barBtnPoesia) {
|
||||||
barBtnPoesia.addEventListener('click', () => {
|
barBtnPoesia.addEventListener('click', () => {
|
||||||
poesiaModal.style.display = 'flex';
|
poesiaModal.style.display = 'flex';
|
||||||
poesiaTemaInput.value = '';
|
loadTurmasForPoesia();
|
||||||
poesiaResultArea.style.display = 'none';
|
if (!poesiaTemaInput.value) {
|
||||||
poesiaGenerateLoader.style.display = 'none';
|
applyPoesiaTemplate('horta');
|
||||||
btnGeneratePoesia.disabled = false;
|
}
|
||||||
currentPoesia = null;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (btnClosePoesiaModal) {
|
if (btnClosePoesiaModal) {
|
||||||
btnClosePoesiaModal.addEventListener('click', () => {
|
btnClosePoesiaModal.addEventListener('click', () => {
|
||||||
poesiaModal.style.display = 'none';
|
poesiaModal.style.display = 'none';
|
||||||
|
if (poesiaAudioPlayer) poesiaAudioPlayer.pause();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Abrir/Fechar Modal de Histórico
|
poesiaModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === poesiaModal) {
|
||||||
|
poesiaModal.style.display = 'none';
|
||||||
|
if (poesiaAudioPlayer) poesiaAudioPlayer.pause();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Aplicar Template
|
||||||
|
const applyPoesiaTemplate = (key) => {
|
||||||
|
const tmpl = POESIA_TEMPLATES[key];
|
||||||
|
if (!tmpl) return;
|
||||||
|
|
||||||
|
if (poesiaTemaInput) poesiaTemaInput.value = tmpl.tema;
|
||||||
|
if (poesiaFaixaEtaria) poesiaFaixaEtaria.value = tmpl.faixa;
|
||||||
|
if (poesiaEstruturaSelect) poesiaEstruturaSelect.value = tmpl.estrutura;
|
||||||
|
if (poesiaEstiloSelect) poesiaEstiloSelect.value = tmpl.estilo;
|
||||||
|
if (poesiaVozSelect) poesiaVozSelect.value = tmpl.voz;
|
||||||
|
|
||||||
|
// Atualizar botões de estrofes
|
||||||
|
document.querySelectorAll('.btn-poesia-estrofes').forEach(b => {
|
||||||
|
const isMatch = parseInt(b.dataset.estrofes) === tmpl.estrofes;
|
||||||
|
b.classList.toggle('active', isMatch);
|
||||||
|
if (isMatch) selectedPoesiaEstrofes = tmpl.estrofes;
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
if (poesiaTemplateSelect) {
|
||||||
|
poesiaTemplateSelect.addEventListener('change', (e) => {
|
||||||
|
if (e.target.value) {
|
||||||
|
applyPoesiaTemplate(e.target.value);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Alternar Abas no Painel Direito
|
||||||
|
const switchPoesiaTab = (tab) => {
|
||||||
|
activePoesiaTab = tab;
|
||||||
|
[btnTabPoesiaOriginal, btnTabPoesiaFonetica, btnTabPoesiaPedagogica].forEach(b => {
|
||||||
|
if (b) {
|
||||||
|
b.classList.remove('active');
|
||||||
|
b.style.background = 'transparent';
|
||||||
|
b.style.color = 'var(--text-secondary)';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let currentContent = '';
|
||||||
|
let subtitleText = `${activePoesiaData.estrutura || 'Quadras'} • ${activePoesiaData.esquemaRima || 'A-B-A-B'}`;
|
||||||
|
|
||||||
|
if (tab === 'original') {
|
||||||
|
if (btnTabPoesiaOriginal) {
|
||||||
|
btnTabPoesiaOriginal.classList.add('active');
|
||||||
|
btnTabPoesiaOriginal.style.background = 'rgba(234,179,8,0.15)';
|
||||||
|
btnTabPoesiaOriginal.style.color = '#fbbf24';
|
||||||
|
}
|
||||||
|
currentContent = activePoesiaData.poesia;
|
||||||
|
} else if (tab === 'fonetica') {
|
||||||
|
if (btnTabPoesiaFonetica) {
|
||||||
|
btnTabPoesiaFonetica.classList.add('active');
|
||||||
|
btnTabPoesiaFonetica.style.background = 'rgba(234,179,8,0.15)';
|
||||||
|
btnTabPoesiaFonetica.style.color = '#fbbf24';
|
||||||
|
}
|
||||||
|
currentContent = activePoesiaData.versaoFonetica || activePoesiaData.poesia;
|
||||||
|
subtitleText = '🗣️ Guia Fonético com Sílabas Tônicas e Pausas Rítmicas';
|
||||||
|
} else if (tab === 'pedagogica') {
|
||||||
|
if (btnTabPoesiaPedagogica) {
|
||||||
|
btnTabPoesiaPedagogica.classList.add('active');
|
||||||
|
btnTabPoesiaPedagogica.style.background = 'rgba(234,179,8,0.15)';
|
||||||
|
btnTabPoesiaPedagogica.style.color = '#fbbf24';
|
||||||
|
}
|
||||||
|
currentContent = `📚 Campo de Experiência BNCC:\n${activePoesiaData.bnccCampos}\n\n💡 Vivência em Sala de Aula:\n${activePoesiaData.dicas || 'Recite o poema em roda incentivando as crianças a acompanharem o ritmo com palmas.'}`;
|
||||||
|
subtitleText = '💡 Vivência Pedagógica & Alinhamento BNCC';
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poesiaActiveSubtitle) poesiaActiveSubtitle.textContent = subtitleText;
|
||||||
|
if (poesiaActiveContent) {
|
||||||
|
if (tab === 'fonetica') {
|
||||||
|
// Converte **palavra** em negrito no HTML
|
||||||
|
const formatted = currentContent.replace(/\*\*(.*?)\*\*/g, '<strong style="color: #fbbf24; text-decoration: underline;">$1</strong>');
|
||||||
|
poesiaActiveContent.innerHTML = formatted;
|
||||||
|
} else {
|
||||||
|
poesiaActiveContent.textContent = currentContent;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (btnTabPoesiaOriginal) btnTabPoesiaOriginal.addEventListener('click', () => switchPoesiaTab('original'));
|
||||||
|
if (btnTabPoesiaFonetica) btnTabPoesiaFonetica.addEventListener('click', () => switchPoesiaTab('fonetica'));
|
||||||
|
if (btnTabPoesiaPedagogica) btnTabPoesiaPedagogica.addEventListener('click', () => switchPoesiaTab('pedagogica'));
|
||||||
|
|
||||||
|
// Equalizer visualizer quando o áudio recitado toca
|
||||||
|
if (poesiaAudioPlayer) {
|
||||||
|
poesiaAudioPlayer.addEventListener('play', () => {
|
||||||
|
const bars = document.querySelectorAll('#poesiaWaveformVisualizer .wave-bar');
|
||||||
|
bars.forEach(b => b.classList.add('wave-active'));
|
||||||
|
});
|
||||||
|
poesiaAudioPlayer.addEventListener('pause', () => {
|
||||||
|
const bars = document.querySelectorAll('#poesiaWaveformVisualizer .wave-bar');
|
||||||
|
bars.forEach(b => b.classList.remove('wave-active'));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- GERAR POESIA (ANÁLISE LITERÁRIA + ÁUDIO RECITADO) ---
|
||||||
|
if (btnGeneratePoesia) {
|
||||||
|
btnGeneratePoesia.addEventListener('click', async () => {
|
||||||
|
const tema = (poesiaTemaInput?.value || '').trim();
|
||||||
|
if (!tema) {
|
||||||
|
await showCustomAlert('Aviso', 'Por favor, informe a ideia ou tema da poesia.');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
btnGeneratePoesia.disabled = true;
|
||||||
|
if (poesiaGenerateLoader) poesiaGenerateLoader.style.display = 'flex';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/poesias/generate', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
tema,
|
||||||
|
faixaEtaria: poesiaFaixaEtaria?.value,
|
||||||
|
estrofes: selectedPoesiaEstrofes,
|
||||||
|
estrutura: poesiaEstruturaSelect?.value || 'Quadras (Rimas A-B-A-B)',
|
||||||
|
estilo: poesiaEstiloSelect?.value || 'Lúdico-cantado e sonoro',
|
||||||
|
voz: poesiaVozSelect?.value || 'mulher',
|
||||||
|
turmaId: poesiaTurmaSelect?.value || null,
|
||||||
|
gerarAudio: true
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
const data = await res.json();
|
||||||
|
|
||||||
|
if (res.ok && data.success) {
|
||||||
|
activePoesiaData = {
|
||||||
|
titulo: data.titulo || `Poema: ${tema}`,
|
||||||
|
poesia: data.poesia,
|
||||||
|
versaoFonetica: data.versaoFonetica,
|
||||||
|
esquemaRima: data.esquemaRima || 'A-B-A-B',
|
||||||
|
analiseLiteraria: data.analiseLiteraria || { rima: 85, ritmo: 80, aliteracao: 65, musicalidade: 90 },
|
||||||
|
bnccCampos: data.bnccCampos || 'EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação',
|
||||||
|
dicas: data.dicasPedagogicas || '',
|
||||||
|
audioUrl: data.audioUrl,
|
||||||
|
tema: tema,
|
||||||
|
estrutura: data.estrutura || poesiaEstruturaSelect?.value,
|
||||||
|
estilo: data.estilo || poesiaEstiloSelect?.value,
|
||||||
|
faixaEtaria: data.faixaEtaria || poesiaFaixaEtaria?.value
|
||||||
|
};
|
||||||
|
|
||||||
|
// Atualizar Interface
|
||||||
|
if (poesiaPlaceholderText) poesiaPlaceholderText.style.display = 'none';
|
||||||
|
if (poesiaMetricsBox) poesiaMetricsBox.style.display = 'flex';
|
||||||
|
if (poesiaContentTabs) poesiaContentTabs.style.display = 'flex';
|
||||||
|
if (poesiaActiveBox) poesiaActiveBox.style.display = 'flex';
|
||||||
|
|
||||||
|
if (poesiaActiveTitle) poesiaActiveTitle.textContent = activePoesiaData.titulo;
|
||||||
|
if (poesiaBnccBadge) {
|
||||||
|
poesiaBnccBadge.textContent = `BNCC: ${activePoesiaData.bnccCampos.split('•')[0].trim()}`;
|
||||||
|
poesiaBnccBadge.style.display = 'inline-block';
|
||||||
|
}
|
||||||
|
if (poesiaEsquemaBadge) {
|
||||||
|
poesiaEsquemaBadge.textContent = `Esquema: ${activePoesiaData.esquemaRima}`;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Atualizar Barras de Métricas Literárias
|
||||||
|
const metrics = activePoesiaData.analiseLiteraria;
|
||||||
|
if (poesiaMetricRimaVal) poesiaMetricRimaVal.textContent = `${metrics.rima || 85}%`;
|
||||||
|
if (poesiaMetricRimaBar) poesiaMetricRimaBar.style.width = `${metrics.rima || 85}%`;
|
||||||
|
|
||||||
|
if (poesiaMetricRitmoVal) poesiaMetricRitmoVal.textContent = `${metrics.ritmo || 80}%`;
|
||||||
|
if (poesiaMetricRitmoBar) poesiaMetricRitmoBar.style.width = `${metrics.ritmo || 80}%`;
|
||||||
|
|
||||||
|
if (poesiaMetricAliteracaoVal) poesiaMetricAliteracaoVal.textContent = `${metrics.aliteracao || 65}%`;
|
||||||
|
if (poesiaMetricAliteracaoBar) poesiaMetricAliteracaoBar.style.width = `${metrics.aliteracao || 65}%`;
|
||||||
|
|
||||||
|
if (poesiaMetricMusicalidadeVal) poesiaMetricMusicalidadeVal.textContent = `${metrics.musicalidade || 90}%`;
|
||||||
|
if (poesiaMetricMusicalidadeBar) poesiaMetricMusicalidadeBar.style.width = `${metrics.musicalidade || 90}%`;
|
||||||
|
|
||||||
|
if (poesiaMetricaDesc && metrics.explicacaoMetrica) {
|
||||||
|
poesiaMetricaDesc.textContent = metrics.explicacaoMetrica;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (poesiaPedagogicalTipText && activePoesiaData.dicas) {
|
||||||
|
poesiaPedagogicalTipText.textContent = activePoesiaData.dicas;
|
||||||
|
poesiaPedagogicalTipBox.style.display = 'block';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Áudio Recitado
|
||||||
|
if (data.audioUrl) {
|
||||||
|
poesiaAudioPlayer.src = data.audioUrl;
|
||||||
|
if (poesiaPlayerTitle) poesiaPlayerTitle.textContent = activePoesiaData.titulo;
|
||||||
|
if (btnDownloadPoesiaAudio) btnDownloadPoesiaAudio.href = data.audioUrl;
|
||||||
|
if (poesiaAudioPlayerBox) poesiaAudioPlayerBox.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
if (poesiaAudioPlayerBox) poesiaAudioPlayerBox.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPoesiaTab('original');
|
||||||
|
showToast('Poesia pedagógica e métricas geradas com sucesso!', 'success');
|
||||||
|
|
||||||
|
} else {
|
||||||
|
await showCustomAlert('Erro', data.error || 'Erro ao gerar poesia.');
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (err) {
|
||||||
|
console.error('Erro ao compor poesia:', err);
|
||||||
|
await showCustomAlert('Erro', 'Não foi possível conectar ao servidor: ' + err.message);
|
||||||
|
} finally {
|
||||||
|
btnGeneratePoesia.disabled = false;
|
||||||
|
if (poesiaGenerateLoader) poesiaGenerateLoader.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copiar Poema Ativo
|
||||||
|
if (btnCopyActivePoesia) {
|
||||||
|
btnCopyActivePoesia.addEventListener('click', () => {
|
||||||
|
const text = activePoesiaData.poesia || '';
|
||||||
|
if (text) {
|
||||||
|
navigator.clipboard.writeText(`${activePoesiaData.titulo}\n\n${text}`);
|
||||||
|
showToast('Poema copiado para a área de transferência!', 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Baixar Poema TXT
|
||||||
|
if (btnDownloadActivePoesiaTxt) {
|
||||||
|
btnDownloadActivePoesiaTxt.addEventListener('click', () => {
|
||||||
|
const text = activePoesiaData.poesia || '';
|
||||||
|
if (!text) return;
|
||||||
|
const content = `POESIA PEDAGÓGICA - PEDAGOGIA\n========================================\nTítulo: ${activePoesiaData.titulo}\nFaixa Etária: ${activePoesiaData.faixaEtaria}\nEstrutura: ${activePoesiaData.estrutura}\nEsquema de Rima: ${activePoesiaData.esquemaRima}\nBNCC: ${activePoesiaData.bnccCampos}\n========================================\n\n${text}\n\n----------------------------------------\nGuia de Leitura / Fonética:\n${activePoesiaData.versaoFonetica}\n\n----------------------------------------\nVivência Pedagógica:\n${activePoesiaData.dicas}\n`;
|
||||||
|
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
|
||||||
|
const link = document.createElement('a');
|
||||||
|
link.href = URL.createObjectURL(blob);
|
||||||
|
link.download = `poema_${(activePoesiaData.titulo || 'poesia').replace(/[^a-zA-Z0-9_-]/g, '_').toLowerCase()}.txt`;
|
||||||
|
document.body.appendChild(link);
|
||||||
|
link.click();
|
||||||
|
link.remove();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Enviar Poema ao Musicando Ideias
|
||||||
|
if (btnSendPoesiaToMusic) {
|
||||||
|
btnSendPoesiaToMusic.addEventListener('click', () => {
|
||||||
|
poesiaModal.style.display = 'none';
|
||||||
|
const barBtnMusica = document.getElementById('barBtnMusica');
|
||||||
|
const musicaModal = document.getElementById('musicaModal');
|
||||||
|
const musicaTemaInput = document.getElementById('musicaTemaInput');
|
||||||
|
if (musicaModal && musicaTemaInput) {
|
||||||
|
musicaModal.style.display = 'flex';
|
||||||
|
musicaTemaInput.value = `Musicar a poesia "${activePoesiaData.titulo}": ${activePoesiaData.poesia.substring(0, 200)}...`;
|
||||||
|
showToast('Poema transferido para o Estúdio de Música!', 'success');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Exportar PDF Ilustrado Formatado
|
||||||
|
if (btnExportActivePoesiaPdf) {
|
||||||
|
btnExportActivePoesiaPdf.addEventListener('click', () => {
|
||||||
|
const text = activePoesiaData.poesia || '';
|
||||||
|
if (!text) return;
|
||||||
|
|
||||||
|
const printWindow = window.open('', '_blank');
|
||||||
|
printWindow.document.write(`
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<title>${activePoesiaData.titulo} - PedaGog</title>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
|
||||||
|
<link href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;600;700&family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
|
||||||
|
<style>
|
||||||
|
body { font-family: 'Fredoka', 'Outfit', sans-serif; padding: 40px; color: #1e293b; background: #fffdf8; line-height: 1.8; }
|
||||||
|
.header-box { border-bottom: 3px dashed #eab308; padding-bottom: 16px; margin-bottom: 24px; }
|
||||||
|
h1 { color: #ca8a04; margin: 0 0 8px 0; font-size: 2rem; }
|
||||||
|
.meta-tags { display: flex; gap: 12px; font-size: 0.9rem; color: #64748b; font-weight: 600; margin-bottom: 8px; flex-wrap: wrap; }
|
||||||
|
.meta-tag { background: #fef9c3; color: #a16207; padding: 4px 10px; border-radius: 6px; }
|
||||||
|
.poetry-box { font-size: 1.25rem; background: #ffffff; padding: 28px; border-radius: 12px; border: 1px solid #fef08a; white-space: pre-wrap; margin-bottom: 24px; box-shadow: 0 4px 6px rgba(0,0,0,0.02); }
|
||||||
|
.metrics-box { background: #f8fafc; border: 1px solid #e2e8f0; padding: 14px 18px; border-radius: 8px; font-size: 0.85rem; color: #475569; margin-bottom: 20px; }
|
||||||
|
.tip-box { background: #fefce8; border: 1px solid #fde047; color: #854d0e; padding: 14px 18px; border-radius: 8px; font-size: 0.92rem; }
|
||||||
|
@media print { body { padding: 20px; } button { display: none; } }
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div style="max-width: 750px; margin: 0 auto;">
|
||||||
|
<div class="header-box">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: flex-start;">
|
||||||
|
<div>
|
||||||
|
<h1>📜 ${activePoesiaData.titulo}</h1>
|
||||||
|
<div class="meta-tags">
|
||||||
|
<span class="meta-tag">👶 ${activePoesiaData.faixaEtaria}</span>
|
||||||
|
<span class="meta-tag">📐 ${activePoesiaData.estrutura}</span>
|
||||||
|
<span class="meta-tag">📚 ${activePoesiaData.bnccCampos.split('•')[0]}</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<button onclick="window.print()" style="background: #eab308; color: #1e293b; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold; font-family: sans-serif;">🖨️ Imprimir / Salvar PDF</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="poetry-box">${text}</div>
|
||||||
|
|
||||||
|
<div class="metrics-box">
|
||||||
|
<strong>📊 Análise Métrica:</strong> Rima: ${activePoesiaData.analiseLiteraria.rima}% | Ritmo: ${activePoesiaData.analiseLiteraria.ritmo}% | Musicalidade: ${activePoesiaData.analiseLiteraria.musicalidade}% (Esquema: ${activePoesiaData.esquemaRima})
|
||||||
|
</div>
|
||||||
|
|
||||||
|
${activePoesiaData.dicas ? `
|
||||||
|
<div class="tip-box">
|
||||||
|
💡 <strong>Vivência Pedagógica / Roda de Leitura:</strong><br>
|
||||||
|
${activePoesiaData.dicas}
|
||||||
|
</div>` : ''}
|
||||||
|
|
||||||
|
<div style="margin-top: 30px; text-align: center; font-size: 0.8rem; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 10px;">
|
||||||
|
Estúdio de Poesias • PedagogIA • Assistente Pedagógica da Professora Camila
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
|
`);
|
||||||
|
printWindow.document.close();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// --- HISTÓRICO DE POESIAS ---
|
||||||
if (btnOpenPoesiaHistory) {
|
if (btnOpenPoesiaHistory) {
|
||||||
btnOpenPoesiaHistory.addEventListener('click', () => {
|
btnOpenPoesiaHistory.addEventListener('click', () => {
|
||||||
poesiaHistoryModal.style.display = 'flex';
|
poesiaHistoryModal.style.display = 'flex';
|
||||||
@@ -6229,163 +6683,107 @@ Se algum dado não for mencionado, deixe a string vazia ou false para boolean.`
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
window.addEventListener('click', (e) => {
|
poesiaHistoryModal.addEventListener('click', (e) => {
|
||||||
if (e.target === poesiaModal) poesiaModal.style.display = 'none';
|
if (e.target === poesiaHistoryModal) {
|
||||||
if (e.target === poesiaHistoryModal) poesiaHistoryModal.style.display = 'none';
|
poesiaHistoryModal.style.display = 'none';
|
||||||
});
|
|
||||||
|
|
||||||
// Gerar Poesia
|
|
||||||
if (btnGeneratePoesia) {
|
|
||||||
btnGeneratePoesia.addEventListener('click', async () => {
|
|
||||||
const tema = poesiaTemaInput.value.trim();
|
|
||||||
const faixaEtaria = poesiaFaixaEtaria.value;
|
|
||||||
|
|
||||||
if (!tema) {
|
|
||||||
showToast('Por favor, digite o tema da poesia.', 'error');
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
btnGeneratePoesia.disabled = true;
|
|
||||||
poesiaGenerateLoader.style.display = 'flex';
|
|
||||||
poesiaResultArea.style.display = 'none';
|
|
||||||
|
|
||||||
try {
|
|
||||||
const response = await fetch('/api/poesias/generate', {
|
|
||||||
method: 'POST',
|
|
||||||
headers: { 'Content-Type': 'application/json' },
|
|
||||||
body: JSON.stringify({ faixaEtaria, estrofes: selectedEstrofes, tema })
|
|
||||||
});
|
|
||||||
|
|
||||||
const data = await response.json();
|
|
||||||
|
|
||||||
if (response.ok && data.success) {
|
|
||||||
currentPoesia = data.poesia;
|
|
||||||
poesiaTextOutput.innerHTML = window.marked ? marked.parse(currentPoesia) : currentPoesia;
|
|
||||||
poesiaResultArea.style.display = 'flex';
|
|
||||||
showToast('Poesia criada com sucesso!', 'success');
|
|
||||||
} else {
|
|
||||||
showToast(data.error || 'Falha ao gerar poesia.', 'error');
|
|
||||||
}
|
|
||||||
} catch (err) {
|
|
||||||
console.error('Erro na geração da poesia:', err);
|
|
||||||
showToast('Erro de conexão ao gerar poesia.', 'error');
|
|
||||||
} finally {
|
|
||||||
btnGeneratePoesia.disabled = false;
|
|
||||||
poesiaGenerateLoader.style.display = 'none';
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
|
||||||
|
|
||||||
// Copiar e Baixar
|
|
||||||
if (btnCopyPoesia) {
|
|
||||||
btnCopyPoesia.addEventListener('click', () => {
|
|
||||||
if (currentPoesia) {
|
|
||||||
navigator.clipboard.writeText(currentPoesia);
|
|
||||||
showToast('Poesia copiada para a área de transferência!', 'success');
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
if (btnDownloadPoesiaPdf) {
|
|
||||||
btnDownloadPoesiaPdf.addEventListener('click', () => {
|
|
||||||
if (!currentPoesia) return;
|
|
||||||
const printWindow = window.open('', '_blank');
|
|
||||||
printWindow.document.write(`
|
|
||||||
<html>
|
|
||||||
<head>
|
|
||||||
<title>Poesia - 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; border: none; padding: 0;">📜 Poesia 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 / Salvar PDF</button>
|
|
||||||
</div>
|
|
||||||
<div>
|
|
||||||
${window.marked ? marked.parse(currentPoesia) : currentPoesia}
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
`);
|
|
||||||
printWindow.document.close();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Carregar Histórico
|
|
||||||
async function loadPoesiaHistory() {
|
async function loadPoesiaHistory() {
|
||||||
const listContainer = document.getElementById('poesiaHistoryList');
|
const listContainer = document.getElementById('poesiaHistoryList');
|
||||||
if (!listContainer) return;
|
if (!listContainer) return;
|
||||||
|
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando histórico...</p>';
|
||||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando...</p>';
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const response = await fetch('/api/poesias/list');
|
const response = await fetch('/api/poesias/list');
|
||||||
const data = await response.json();
|
const data = await response.json();
|
||||||
|
|
||||||
if (data && data.length > 0) {
|
if (data && data.length > 0) {
|
||||||
listContainer.innerHTML = '';
|
listContainer.innerHTML = '';
|
||||||
data.forEach(item => {
|
data.forEach(item => {
|
||||||
const date = new Date(item.created_at).toLocaleString('pt-BR');
|
const date = new Date(item.created_at).toLocaleString('pt-BR');
|
||||||
const div = document.createElement('div');
|
const div = document.createElement('div');
|
||||||
div.className = 'history-item-card';
|
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.style.cssText = 'background: rgba(255,255,255,0.03); padding: 14px; border-radius: 10px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 8px; cursor: pointer; position: relative; transition: all 0.2s;';
|
||||||
div.innerHTML = `
|
div.innerHTML = `
|
||||||
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding-right: 30px;">
|
<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.estrofes} Estrofes)</strong>
|
<strong style="color: #fbbf24; font-size: 0.95rem;">${item.faixa_etaria} (${item.estrofes} Estrofes)</strong>
|
||||||
<span style="font-size: 0.75rem; color: var(--text-secondary);">${date}</span>
|
<span style="font-size: 0.72rem; color: var(--text-secondary);">${date}</span>
|
||||||
</div>
|
</div>
|
||||||
<p style="margin: 0; font-size: 0.85rem; color: var(--text-primary); opacity: 0.9;">Tema: ${item.tema}</p>
|
<p style="margin: 0; font-size: 0.85rem; color: var(--text-primary); opacity: 0.9;"><strong>Tema:</strong> ${item.tema}</p>
|
||||||
|
${item.audio_url ? `
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px; margin-top: 4px;">
|
||||||
|
<span style="font-size: 0.75rem; color: #10b981; font-weight: 600;">🎙️ Áudio Recitado Disponível</span>
|
||||||
|
</div>
|
||||||
|
` : ''}
|
||||||
<button class="btn-delete-poesia" 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 Poesia">🗑️</button>
|
<button class="btn-delete-poesia" 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 Poesia">🗑️</button>
|
||||||
`;
|
`;
|
||||||
|
|
||||||
div.addEventListener('click', async (e) => {
|
div.addEventListener('click', async (e) => {
|
||||||
if (e.target.closest('.btn-delete-poesia')) {
|
if (e.target.closest('.btn-delete-poesia')) {
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
if (confirm('Tem certeza que deseja excluir esta poesia?')) {
|
if (confirm('Tem certeza que deseja excluir esta poesia do histórico?')) {
|
||||||
try {
|
try {
|
||||||
const delRes = await fetch(`/api/poesias/${item.id}`, { method: 'DELETE' });
|
const delRes = await fetch(`/api/poesias/${item.id}`, { method: 'DELETE' });
|
||||||
if (delRes.ok) {
|
if (delRes.ok) {
|
||||||
showToast('Poesia excluída.', 'success');
|
showToast('Poesia excluída do histórico.', 'success');
|
||||||
loadPoesiaHistory();
|
loadPoesiaHistory();
|
||||||
} else {
|
|
||||||
showToast('Erro ao excluir.', 'error');
|
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast('Erro de conexão.', 'error');
|
showToast('Erro ao excluir poesia.', 'error');
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Carregar poesia clicada
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`/api/poesias/${item.id}`);
|
const res = await fetch(`/api/poesias/${item.id}`);
|
||||||
const detail = await res.json();
|
const detail = await res.json();
|
||||||
if (res.ok) {
|
if (res.ok) {
|
||||||
currentPoesia = detail.poesia;
|
activePoesiaData = {
|
||||||
poesiaTextOutput.innerHTML = window.marked ? marked.parse(currentPoesia) : currentPoesia;
|
titulo: `Poema: ${detail.tema}`,
|
||||||
poesiaResultArea.style.display = 'flex';
|
poesia: detail.poesia,
|
||||||
|
versaoFonetica: detail.versao_fonetica || detail.poesia,
|
||||||
|
esquemaRima: 'A-B-A-B',
|
||||||
|
analiseLiteraria: typeof detail.analise_metrica === 'string' ? JSON.parse(detail.analise_metrica) : (detail.analise_metrica || { rima: 85, ritmo: 80, aliteracao: 65, musicalidade: 90 }),
|
||||||
|
bnccCampos: detail.bncc || 'EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação',
|
||||||
|
dicas: detail.dicas || '',
|
||||||
|
audioUrl: detail.audio_url,
|
||||||
|
tema: detail.tema,
|
||||||
|
estrutura: detail.estrutura || 'Quadras',
|
||||||
|
estilo: detail.estilo || 'Lúdico-cantado',
|
||||||
|
faixaEtaria: detail.faixa_etaria
|
||||||
|
};
|
||||||
|
|
||||||
|
if (poesiaPlaceholderText) poesiaPlaceholderText.style.display = 'none';
|
||||||
|
if (poesiaMetricsBox) poesiaMetricsBox.style.display = 'flex';
|
||||||
|
if (poesiaContentTabs) poesiaContentTabs.style.display = 'flex';
|
||||||
|
if (poesiaActiveBox) poesiaActiveBox.style.display = 'flex';
|
||||||
|
|
||||||
|
if (poesiaActiveTitle) poesiaActiveTitle.textContent = activePoesiaData.titulo;
|
||||||
|
if (detail.audio_url) {
|
||||||
|
poesiaAudioPlayer.src = detail.audio_url;
|
||||||
|
if (btnDownloadPoesiaAudio) btnDownloadPoesiaAudio.href = detail.audio_url;
|
||||||
|
if (poesiaAudioPlayerBox) poesiaAudioPlayerBox.style.display = 'flex';
|
||||||
|
} else {
|
||||||
|
if (poesiaAudioPlayerBox) poesiaAudioPlayerBox.style.display = 'none';
|
||||||
|
}
|
||||||
|
|
||||||
|
switchPoesiaTab('original');
|
||||||
poesiaHistoryModal.style.display = 'none';
|
poesiaHistoryModal.style.display = 'none';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
showToast('Erro ao carregar poesia.', 'error');
|
showToast('Erro ao carregar detalhes da poesia.', 'error');
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
listContainer.appendChild(div);
|
listContainer.appendChild(div);
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Nenhuma poesia salva ainda.</p>';
|
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary); padding: 20px;">Nenhuma poesia gerada até o momento.</p>';
|
||||||
}
|
}
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar histórico.</p>';
|
listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar histórico de poesias.</p>';
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
+259
-51
@@ -1404,84 +1404,292 @@
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- ========================================== -->
|
||||||
|
<!-- MODAL: ESTÚDIO DE POESIAS (PROFISSIONAL) -->
|
||||||
|
<!-- ========================================== -->
|
||||||
<div id="poesiaModal" class="settings-modal" style="display: none;">
|
<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-content" style="max-width: 1050px; width: 95%; max-height: 92vh; 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;">📜 Estúdio de Poesia PedaGog</h3>
|
|
||||||
<div style="display: flex; gap: 10px;">
|
|
||||||
<button id="btnOpenPoesiaHistory" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-primary); padding: 6px 12px; font-size: 0.8rem;">
|
|
||||||
🕒 Histórico
|
|
||||||
</button>
|
|
||||||
<button id="btnClosePoesiaModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 18px; flex: 1;">
|
|
||||||
|
|
||||||
|
<!-- Header -->
|
||||||
|
<div class="settings-modal-header" style="background: var(--bg-secondary); border-bottom: 1px solid var(--border-light); padding: 14px 20px; display: flex; justify-content: space-between; align-items: center; flex-wrap: wrap; gap: 10px;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 10px;">
|
||||||
|
<span style="font-size: 1.4rem;">📜</span>
|
||||||
|
<div>
|
||||||
|
<h3 style="margin: 0; font-family: 'Outfit', sans-serif; font-size: 1.15rem; color: var(--text-primary);">Estúdio de Poesias • PedagogIA</h3>
|
||||||
|
<span style="font-size: 0.75rem; color: var(--text-secondary);">Composição de poesias ritmadas, métrica literária, guia fonético e áudio recitado</span>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 8px; align-items: center;">
|
||||||
|
<button type="button" id="btnOpenPoesiaHistory" style="background: rgba(234, 179, 8, 0.12); border: 1px solid rgba(234, 179, 8, 0.3); color: #fbbf24; padding: 6px 12px; border-radius: 6px; cursor: pointer; display: flex; align-items: center; gap: 6px; font-size: 0.82rem; font-weight: 600;">🕒 Histórico</button>
|
||||||
|
<button type="button" id="btnClosePoesiaModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem; line-height: 1;">×</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Body em 2 Colunas -->
|
||||||
|
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: row; gap: 24px; flex: 1; flex-wrap: wrap;">
|
||||||
|
|
||||||
|
<!-- Lado Esquerdo: Parâmetros & Criação -->
|
||||||
|
<div id="poesiaLeftPanel" style="flex: 1; min-width: 320px; display: flex; flex-direction: column; gap: 14px;">
|
||||||
|
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 6px; font-size: 0.92rem;">✍️ 1. Inspiração & Tema Poético</h4>
|
||||||
|
|
||||||
|
<!-- TEMPLATES RÁPIDOS DE POESIA -->
|
||||||
<div class="settings-group">
|
<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>
|
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Modelos Temáticos (Templates)</label>
|
||||||
<select id="poesiaFaixaEtaria" class="obs-select" style="width: 100%; margin-top: 4px;">
|
<select id="poesiaTemplateSelect" class="obs-select" style="width: 100%; margin-top: 4px; color: #eab308; font-weight: 600;">
|
||||||
<option value="Bebês (0 a 1 ano e 6 meses)">Bebês (0 a 1 ano e 6 meses)</option>
|
<option value="">✨ Escolha um modelo temático...</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="horta">🌱 Minha Horta e a Terra Fértil</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="animais">🐾 Bichinhos da Fazenda e seus Sons</option>
|
||||||
<option value="Ensino Fundamental I (6 a 10 anos)">Ensino Fundamental I (6 a 10 anos)</option>
|
<option value="familia">👨👩👧👦 O Abraço e o Afeto da Família</option>
|
||||||
|
<option value="higiene">🧼 Bolhas de Sabão e Banho Gostoso</option>
|
||||||
|
<option value="chuva">🌧️ Gotinhas de Chuva na Janela</option>
|
||||||
|
<option value="alfabeto">🔤 O Reino das Letras e Palavrinhas</option>
|
||||||
|
<option value="cores">🎨 As Cores Mágicas do Nosso Mundo</option>
|
||||||
</select>
|
</select>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- TEMA / IDEIA CENTRAL -->
|
||||||
<div class="settings-group">
|
<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. Quantidade de Estrofes</label>
|
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Tema ou Ideia Central</label>
|
||||||
<div class="music-option-grid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 10px; margin-top: 4px;">
|
<textarea id="poesiaTemaInput" placeholder="Ex: Plantar sementinhas na horta da escola, regar as folhinhas e ver o tomatinho nascer..." class="obs-input" style="width: 100%; min-height: 60px; padding: 8px; margin-top: 4px; font-size: 0.85rem; resize: vertical;"></textarea>
|
||||||
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="2">2 Estrofes</button>
|
</div>
|
||||||
<button type="button" class="btn-music-option btn-poesia-estrofes active" data-estrofes="3">3 Estrofes</button>
|
|
||||||
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="4">4 Estrofes</button>
|
<!-- TURMA & FAIXA ETÁRIA -->
|
||||||
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="5">5 Estrofes</button>
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||||
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Vincular à Turma</label>
|
||||||
|
<select id="poesiaTurmaSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||||
|
<option value="">-- Selecionar Turma --</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Público / Idade</label>
|
||||||
|
<select id="poesiaFaixaEtaria" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||||
|
<option value="Bebês (0 a 1 ano e 6 meses)">Bebês (0 a 1a6m)</option>
|
||||||
|
<option value="Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)">Bem pequenas (1a7m a 3a11m)</option>
|
||||||
|
<option value="Crianças pequenas (4 anos a 5 anos e 11 meses)" selected>Crianças pequenas (4 a 5a11m)</option>
|
||||||
|
<option value="Ensino Fundamental I (6 a 10 anos)">Ensino Fundamental I</option>
|
||||||
|
</select>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="settings-group">
|
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 6px; margin-top: 4px; font-size: 0.92rem;">📐 2. Estrutura Poética & Estilo Camila</h4>
|
||||||
<label style="color: var(--text-secondary); font-size: 0.82rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">3. Tema da Poesia</label>
|
|
||||||
<textarea id="poesiaTemaInput" placeholder="Ex: Higiene pessoal, amizade na escola, os animais da fazenda..." class="obs-input" style="width: 100%; min-height: 80px; padding: 10px; resize: vertical; margin-top: 4px; line-height: 1.4;"></textarea>
|
<!-- ESTRUTURA & ESTILO -->
|
||||||
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||||
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Estrutura Poética</label>
|
||||||
|
<select id="poesiaEstruturaSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||||
|
<option value="Quadras (Rimas A-B-A-B)" selected>📜 Quadras (Rimas A-B-A-B)</option>
|
||||||
|
<option value="Redondilhas (7 sílabas poéticas)">🎶 Redondilhas (Versos de 7 Sílabas)</option>
|
||||||
|
<option value="Haiku Infantil (5-7-5 sílabas)">🌸 Haiku Infantil (5-7-5 Sílabas)</option>
|
||||||
|
<option value="Poema Livre & Cantado">⚡ Poema Livre & Cantado</option>
|
||||||
|
<option value="Soneto Infantil (2 quartetos + 2 tercetos)">🏛️ Soneto Infantil Clássico</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Estilo Camila</label>
|
||||||
|
<select id="poesiaEstiloSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||||
|
<option value="Lúdico-cantado e sonoro" selected>🌟 Lúdico-cantado e Sonoro</option>
|
||||||
|
<option value="Narrativo-infantil com história">📖 Narrativo-infantil (Contação)</option>
|
||||||
|
<option value="Sensorial, suave e afetivo">💖 Sensorial, Suave & Afetivo</option>
|
||||||
|
<option value="Formal-pedagógico reflexivo">🎓 Formal-pedagógico (Escrita Guiada)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 10px; margin-top: 8px;">
|
<!-- ESTROFES & VOZ NARRADA -->
|
||||||
<button id="btnGeneratePoesia" style="width: 100%; padding: 12px; background: #eab308; color: #1e293b; border: none; border-radius: 10px; font-weight: 700; font-size: 1rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 4px 12px rgba(234, 179, 8, 0.3); transition: all 0.2s;">
|
<div style="display: flex; gap: 10px; flex-wrap: wrap;">
|
||||||
<span>📜 Criar Poesia Personalizada</span>
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Quantidade de Estrofes</label>
|
||||||
|
<div class="music-option-grid" style="display: grid; grid-template-columns: repeat(4, 1fr); gap: 6px; margin-top: 4px;">
|
||||||
|
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="2">2</button>
|
||||||
|
<button type="button" class="btn-music-option btn-poesia-estrofes active" data-estrofes="3">3</button>
|
||||||
|
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="4">4</button>
|
||||||
|
<button type="button" class="btn-music-option btn-poesia-estrofes" data-estrofes="5">5</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||||
|
<label style="color: var(--text-secondary); font-size: 0.72rem; font-weight: 600; text-transform: uppercase;">Voz de Recitação</label>
|
||||||
|
<select id="poesiaVozSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||||
|
<option value="mulher" selected>👩 Professora Camila (Acolhedora)</option>
|
||||||
|
<option value="homem">👨 Professor (Gentil & Firme)</option>
|
||||||
|
<option value="crianca">🧒 Criança (Lúdica & Espontânea)</option>
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- BOTÃO GERAR POESIA -->
|
||||||
|
<button type="button" id="btnGeneratePoesia" style="width: 100%; padding: 12px; background: linear-gradient(135deg, #eab308 0%, #ca8a04 100%); color: #1e293b; border: none; border-radius: 10px; font-weight: 700; font-size: 0.95rem; cursor: pointer; display: flex; align-items: center; justify-content: center; gap: 8px; box-shadow: 0 4px 14px rgba(234, 179, 8, 0.3); transition: all 0.2s; margin-top: 6px;">
|
||||||
|
<span>📜 Compor Poesia & Análise Literária</span>
|
||||||
</button>
|
</button>
|
||||||
<div id="poesiaGenerateLoader" style="display: none; align-items: center; justify-content: center; gap: 10px; color: var(--text-secondary); font-size: 0.9rem; padding: 10px; background: rgba(255, 255, 255, 0.02); border-radius: 8px;">
|
|
||||||
<div class="record-spinner" style="width: 20px; height: 20px; border-width: 2px;"></div>
|
<div id="poesiaGenerateLoader" style="display: none; align-items: center; justify-content: center; gap: 10px; color: var(--text-secondary); font-size: 0.85rem; padding: 10px; background: rgba(255, 255, 255, 0.02); border-radius: 8px;">
|
||||||
<span>A IA está buscando inspiração e compondo sua poesia...</span>
|
<div class="media-spinner" style="width: 18px; height: 18px; border-width: 2px; border-top-color: #eab308;"></div>
|
||||||
|
<span id="poesiaLoaderText">Compondo versos, métrica e áudio recitado...</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Lado Direito: Análise Literária, Áudio & Conteúdo -->
|
||||||
|
<div id="poesiaRightPanel" style="flex: 1.3; min-width: 360px; display: flex; flex-direction: column; gap: 14px;">
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid var(--border-light); padding-bottom: 6px;">
|
||||||
|
<h4 style="margin: 0; color: var(--text-primary); font-size: 0.95rem;">📖 Poema & Análise Literária</h4>
|
||||||
|
<div id="poesiaBnccBadge" style="display: none; font-size: 0.72rem; background: rgba(234, 179, 8, 0.15); color: #fbbf24; border: 1px solid rgba(234, 179, 8, 0.3); padding: 3px 8px; border-radius: 6px; font-weight: 600;">
|
||||||
|
BNCC: EI02EF01
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div id="poesiaResultArea" style="display: none; border-top: 1px solid var(--border-light); padding-top: 16px; flex-direction: column; gap: 14px;">
|
<!-- CARD DE ANÁLISE LITERÁRIA (MÉTRICAS POÉTICAS) -->
|
||||||
<h4 style="margin: 0; color: var(--text-primary); font-size: 1rem; display: flex; align-items: center; gap: 6px;">✨ Sua Poesia Está Pronta!</h4>
|
<div id="poesiaMetricsBox" style="display: none; background: rgba(255, 255, 255, 0.03); border: 1px solid var(--border-light); border-radius: 12px; padding: 12px 16px; flex-direction: column; gap: 10px;">
|
||||||
|
|
||||||
<div style="display: flex; flex-direction: column; gap: 8px;">
|
|
||||||
<div style="display: flex; justify-content: space-between; align-items: center;">
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px;">Poesia Gerada</label>
|
<span style="font-size: 0.75rem; font-weight: 700; color: var(--text-secondary); text-transform: uppercase;">📊 Análise Poética & Musicalidade</span>
|
||||||
<div style="display: flex; gap: 8px;">
|
<span id="poesiaEsquemaBadge" style="font-size: 0.72rem; background: rgba(255,255,255,0.06); padding: 2px 6px; border-radius: 4px; color: var(--text-secondary);">Esquema: A-B-A-B</span>
|
||||||
<button id="btnCopyPoesia" style="background: none; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 5px 10px; border-radius: 6px; font-size: 0.75rem; cursor: pointer; transition: all 0.2s;">📋 Copiar</button>
|
</div>
|
||||||
<button id="btnDownloadPoesiaPdf" style="background: none; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 5px 10px; border-radius: 6px; font-size: 0.75rem; cursor: pointer; transition: all 0.2s;">💾 Baixar PDF</button>
|
|
||||||
|
<div style="display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px;">
|
||||||
|
<!-- Rima -->
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 0.72rem; margin-bottom: 3px;">
|
||||||
|
<span style="color: var(--text-primary);">Rima</span>
|
||||||
|
<span id="poesiaMetricRimaVal" style="color: #38bdf8; font-weight: bold;">85%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: rgba(255,255,255,0.08); height: 6px; border-radius: 3px; overflow: hidden;">
|
||||||
|
<div id="poesiaMetricRimaBar" style="width: 85%; height: 100%; background: #38bdf8; border-radius: 3px; transition: width 0.4s ease;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id="poesiaTextOutput" class="markdown-body" style="background: var(--bg-secondary); padding: 16px; border-radius: 10px; font-size: 1rem; line-height: 1.6; border: 1px solid var(--border-light); color: var(--text-primary); margin: 0; min-height: 150px;"></div>
|
|
||||||
|
<!-- Ritmo -->
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 0.72rem; margin-bottom: 3px;">
|
||||||
|
<span style="color: var(--text-primary);">Ritmo & Métrica</span>
|
||||||
|
<span id="poesiaMetricRitmoVal" style="color: #4ade80; font-weight: bold;">80%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: rgba(255,255,255,0.08); height: 6px; border-radius: 3px; overflow: hidden;">
|
||||||
|
<div id="poesiaMetricRitmoBar" style="width: 80%; height: 100%; background: #4ade80; border-radius: 3px; transition: width 0.4s ease;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Aliteração -->
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 0.72rem; margin-bottom: 3px;">
|
||||||
|
<span style="color: var(--text-primary);">Aliteração & Sons</span>
|
||||||
|
<span id="poesiaMetricAliteracaoVal" style="color: #fb923c; font-weight: bold;">65%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: rgba(255,255,255,0.08); height: 6px; border-radius: 3px; overflow: hidden;">
|
||||||
|
<div id="poesiaMetricAliteracaoBar" style="width: 65%; height: 100%; background: #fb923c; border-radius: 3px; transition: width 0.4s ease;"></div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Musicalidade -->
|
||||||
|
<div>
|
||||||
|
<div style="display: flex; justify-content: space-between; font-size: 0.72rem; margin-bottom: 3px;">
|
||||||
|
<span style="color: var(--text-primary);">Musicalidade</span>
|
||||||
|
<span id="poesiaMetricMusicalidadeVal" style="color: #f472b6; font-weight: bold;">90%</span>
|
||||||
|
</div>
|
||||||
|
<div style="background: rgba(255,255,255,0.08); height: 6px; border-radius: 3px; overflow: hidden;">
|
||||||
|
<div id="poesiaMetricMusicalidadeBar" style="width: 90%; height: 100%; background: #f472b6; border-radius: 3px; transition: width 0.4s ease;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<div id="poesiaMetricaDesc" style="font-size: 0.75rem; color: var(--text-secondary); line-height: 1.4; border-top: 1px dashed var(--border-light); padding-top: 6px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ÁREA DE REPRODUÇÃO DE ÁUDIO RECITADO -->
|
||||||
|
<div id="poesiaAudioPlayerBox" style="display: none; background: linear-gradient(135deg, rgba(234,179,8,0.12), rgba(245,158,11,0.08)); border: 1px solid rgba(234,179,8,0.3); border-radius: 12px; padding: 14px; flex-direction: column; gap: 10px;">
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center;">
|
||||||
|
<div style="display: flex; align-items: center; gap: 8px;">
|
||||||
|
<span style="font-size: 1.2rem;">🎙️</span>
|
||||||
|
<div>
|
||||||
|
<div style="font-size: 0.85rem; font-weight: 700; color: var(--text-primary);" id="poesiaPlayerTitle">Recitação por PedagogIA</div>
|
||||||
|
<div style="font-size: 0.72rem; color: var(--text-secondary);">Leitura Poética Narrada • Formato MP3</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 6px; align-items: center;">
|
||||||
|
<a id="btnDownloadPoesiaAudio" href="#" download="poesia_pedagogica.mp3" style="background: #eab308; color: #1e293b; border: none; padding: 5px 10px; border-radius: 6px; font-size: 0.75rem; font-weight: 700; text-decoration: none; display: flex; align-items: center; gap: 4px;">
|
||||||
|
📥 Baixar Áudio
|
||||||
|
</a>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Waveform / Equalizer Animado -->
|
||||||
|
<div id="poesiaWaveformVisualizer" style="display: flex; align-items: center; justify-content: center; gap: 3px; height: 28px; padding: 4px; background: rgba(0,0,0,0.15); border-radius: 6px;">
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 30%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 60%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 90%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 45%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 75%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 35%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 85%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 50%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 95%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
<div class="wave-bar wave-bar-poesia" style="width: 4px; height: 40%; background: #eab308; border-radius: 2px;"></div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<audio id="poesiaAudioPlayer" controls style="width: 100%; margin-top: 2px;"></audio>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ABAS DE VISUALIZAÇÃO -->
|
||||||
|
<div id="poesiaContentTabs" style="display: none; gap: 6px; border-bottom: 1px solid var(--border-light); padding-bottom: 8px;">
|
||||||
|
<button type="button" id="btnTabPoesiaOriginal" class="tab-btn active" style="font-size: 0.78rem; padding: 6px 12px; background: rgba(234,179,8,0.15); border: none; cursor: pointer; color: #fbbf24; border-radius: 6px; font-weight: bold;">📜 Poema Original</button>
|
||||||
|
<button type="button" id="btnTabPoesiaFonetica" class="tab-btn" style="font-size: 0.78rem; padding: 6px 12px; background: transparent; border: none; cursor: pointer; color: var(--text-secondary); border-radius: 6px;">🗣️ Guia Fonético / Leitura</button>
|
||||||
|
<button type="button" id="btnTabPoesiaPedagogica" class="tab-btn" style="font-size: 0.78rem; padding: 6px 12px; background: transparent; border: none; cursor: pointer; color: var(--text-secondary); border-radius: 6px;">💡 Vivência & BNCC</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- ÁREA DO CONTEÚDO POÉTICO -->
|
||||||
|
<div id="poesiaDisplayArea" style="display: flex; flex-direction: column; gap: 12px; flex: 1;">
|
||||||
|
|
||||||
|
<div id="poesiaPlaceholderText" style="text-align: center; padding: 40px 20px; color: var(--text-secondary); background: rgba(255,255,255,0.02); border: 1px dashed var(--border-light); border-radius: 10px;">
|
||||||
|
<span style="font-size: 2.5rem; display: block; margin-bottom: 10px;">📜</span>
|
||||||
|
<strong style="color: var(--text-primary); font-size: 0.95rem;">Pronto para transformar sua ideia em poesia?</strong>
|
||||||
|
<p style="font-size: 0.82rem; margin-top: 6px; opacity: 0.8;">Escolha um modelo ou digite o tema ao lado e clique em "Compor Poesia" para gerar a análise literária, guia fonético e áudio recitado.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- CONTAINER DO CONTEÚDO ATIVO -->
|
||||||
|
<div id="poesiaActiveBox" style="display: none; background: rgba(255,255,255,0.03); border: 1px solid var(--border-light); border-radius: 12px; padding: 16px; flex-direction: column; gap: 12px;">
|
||||||
|
|
||||||
|
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 1px dashed var(--border-light); padding-bottom: 8px;">
|
||||||
|
<div>
|
||||||
|
<h4 id="poesiaActiveTitle" style="margin: 0; color: #eab308; font-size: 1.05rem; font-family: 'Fredoka', sans-serif;">Título da Poesia</h4>
|
||||||
|
<span id="poesiaActiveSubtitle" style="font-size: 0.72rem; color: var(--text-secondary);">Quadras • Rimas A-B-A-B</span>
|
||||||
|
</div>
|
||||||
|
<div style="display: flex; gap: 6px;">
|
||||||
|
<button type="button" id="btnCopyActivePoesia" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 4px 8px; border-radius: 6px; font-size: 0.72rem; cursor: pointer;" title="Copiar poema">📋 Copiar</button>
|
||||||
|
<button type="button" id="btnDownloadActivePoesiaTxt" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 4px 8px; border-radius: 6px; font-size: 0.72rem; cursor: pointer;" title="Baixar TXT">💾 TXT</button>
|
||||||
|
<button type="button" id="btnSendPoesiaToMusic" style="background: rgba(168,85,247,0.15); border: 1px solid rgba(168,85,247,0.3); color: #c084fc; padding: 4px 8px; border-radius: 6px; font-size: 0.72rem; cursor: pointer; font-weight: 600;" title="Enviar para Musicando Ideias">🎵 Musicar</button>
|
||||||
|
<button type="button" id="btnExportActivePoesiaPdf" style="background: #eab308; color: #1e293b; border: none; padding: 4px 10px; border-radius: 6px; font-size: 0.72rem; font-weight: 700; cursor: pointer;" title="Salvar PDF formatado">📄 PDF</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Texto do Poema ou Guia Fonético -->
|
||||||
|
<div id="poesiaActiveContent" style="font-family: 'Fredoka', 'Outfit', sans-serif; font-size: 1.05rem; line-height: 1.7; color: var(--text-primary); white-space: pre-wrap; background: rgba(0,0,0,0.08); padding: 14px; border-radius: 8px; border: 1px solid rgba(255,255,255,0.05); max-height: 280px; overflow-y: auto;"></div>
|
||||||
|
|
||||||
|
<!-- Dica Pedagógica -->
|
||||||
|
<div id="poesiaPedagogicalTipBox" style="background: rgba(234, 179, 8, 0.08); border: 1px solid rgba(234, 179, 8, 0.2); border-radius: 8px; padding: 10px 14px; font-size: 0.8rem; color: #fde047; line-height: 1.4;">
|
||||||
|
💡 <strong>Vivência & Roda de Conversa:</strong> <span id="poesiaPedagogicalTipText">Estimule as crianças a recitarem as rimas finais com palmas.</span>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
|
</div>
|
||||||
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Modal: Histórico de Poesias -->
|
<!-- Modal: Histórico de Poesias -->
|
||||||
<div id="poesiaHistoryModal" class="settings-modal" style="display: none; z-index: 1002;">
|
<div id="poesiaHistoryModal" class="settings-modal" style="display: none; z-index: 10001;">
|
||||||
<div class="settings-modal-content" style="max-width: 600px; width: 95%; max-height: 80vh; display: flex; flex-direction: column;">
|
<div class="settings-modal-content" style="max-width: 540px; 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);">
|
<div class="settings-modal-header" style="background: var(--bg-secondary); border-bottom: 1px solid var(--border-light); padding: 14px 20px; display: flex; justify-content: space-between; align-items: center;">
|
||||||
<h3 style="display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif;">🕒 Histórico de Poesias</h3>
|
<h3 style="margin: 0; display: flex; align-items: center; gap: 8px; font-family: 'Outfit', sans-serif; font-size: 1.1rem; color: #fbbf24;">🕒 Histórico de Poesias Geradas</h3>
|
||||||
<button id="btnClosePoesiaHistory" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
<button type="button" id="btnClosePoesiaHistory" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem; line-height: 1;">×</button>
|
||||||
</div>
|
|
||||||
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 14px; flex: 1;">
|
|
||||||
<div id="poesiaHistoryList" style="display: flex; flex-direction: column; gap: 10px;">
|
|
||||||
<p style="color: var(--text-secondary); font-size: 0.9rem; text-align: center; margin-top: 20px;">Carregando histórico...</p>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
<div id="poesiaHistoryList" style="padding: 20px; overflow-y: auto; display: flex; flex-direction: column; gap: 12px; flex: 1;"></div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
+12
-1
@@ -4133,12 +4133,23 @@ code {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@media (max-width: 768px) {
|
@media (max-width: 768px) {
|
||||||
#musicaLeftPanel, #musicaRightPanel {
|
#musicaLeftPanel, #musicaRightPanel,
|
||||||
|
#poesiaLeftPanel, #poesiaRightPanel {
|
||||||
min-width: 100% !important;
|
min-width: 100% !important;
|
||||||
width: 100% !important;
|
width: 100% !important;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.wave-bar-poesia.wave-active {
|
||||||
|
animation: wave-pulse-poesia 0.8s infinite ease-in-out alternate;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes wave-pulse-poesia {
|
||||||
|
0% { height: 20%; background-color: #eab308; }
|
||||||
|
50% { height: 95%; background-color: #f59e0b; }
|
||||||
|
100% { height: 40%; background-color: #fbbf24; }
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -1241,85 +1241,285 @@ app.delete('/api/brincar-ativo/:id', requireAuth, async (req, res) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// ============================================================
|
// ============================================================
|
||||||
// ROTAS DO ESTÚDIO DE POESIA
|
// ============================================================
|
||||||
|
// ROTAS DO ESTÚDIO DE POESIAS (ESTÚDIO PROFISSIONAL)
|
||||||
// ============================================================
|
// ============================================================
|
||||||
app.post('/api/poesias/generate', requireAuth, async (req, res) => {
|
app.post('/api/poesias/generate', requireAuth, async (req, res) => {
|
||||||
const { faixaEtaria, estrofes, tema } = req.body;
|
const { faixaEtaria, estrofes, tema, estrutura, estilo, voz, turmaId, gerarAudio } = req.body;
|
||||||
if (!faixaEtaria || !estrofes || !tema) {
|
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||||
return res.status(400).json({ error: 'Faixa etária, quantidade de estrofes e tema são obrigatórios.' });
|
|
||||||
|
if (!tema) {
|
||||||
|
return res.status(400).json({ error: 'O tema da poesia é obrigatório.' });
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
let poesiaText = '';
|
const agentConfig = readAgentConfig();
|
||||||
|
const agentName = agentConfig.agentName || "PedagogIA";
|
||||||
|
|
||||||
let idadeInstrucao = '';
|
const promptPoesia = `Você é a ${agentName}, poetisa, linguista e assistente pedagógica da professora Camila Martella Gasparini Reifonas.
|
||||||
if (faixaEtaria.includes('Bebês') || faixaEtaria.includes('bem pequenas')) {
|
A professora Camila deseja compor uma poesia pedagógica para seus alunos na Educação Infantil.
|
||||||
idadeInstrucao = '- NÍVEL FÁCIL: Use palavras EXTREMAMENTE simples, do cotidiano infantil. As estrofes devem ser MUITO curtas, contendo poucas palavras por verso, pois são crianças de colo ou maternal. Elas precisam conseguir memorizar e repetir facilmente.';
|
|
||||||
} else if (faixaEtaria.includes('pequenas')) {
|
PARÂMETROS DA POESIA:
|
||||||
idadeInstrucao = '- NÍVEL MÉDIO: Use palavras fáceis e lúdicas, focando em sonoridade. As estrofes podem ter tamanho normal de versos infantis (4 a 5 palavras por verso).';
|
- Tema / Ideia Central: "${tema}"
|
||||||
} else {
|
- Faixa Etária: "${faixaEtaria || 'Crianças pequenas (4 a 5 anos)'}"
|
||||||
idadeInstrucao = '- NÍVEL AVANÇADO: Use vocabulário um pouco mais rico, com metáforas leves. Para crianças do Fundamental I, os versos podem ser mais elaborados e trazer reflexões sobre o tema.';
|
- Quantidade de Estrofes: ${estrofes || 3}
|
||||||
|
- Estrutura Poética: "${estrutura || 'Quadras (Rimas A-B-A-B)'}"
|
||||||
|
- Estilo Pedagógico: "${estilo || 'Lúdico-cantado e sonoro'}"
|
||||||
|
|
||||||
|
INSTRUÇÕES DE COMPOSIÇÃO:
|
||||||
|
1. Componha uma poesia envolvente, com ritmo marcante, musicalidade e rimas expressivas adequadas para a idade informada.
|
||||||
|
2. Realize uma análise literária detalhada com estimativas percentuais (0-100%) para:
|
||||||
|
- Rima (precisão das terminações sonoras)
|
||||||
|
- Ritmo (cadência e compasso dos versos)
|
||||||
|
- Aliteração (repetição harmoniosa de sons consonantais)
|
||||||
|
- Musicalidade (sonoridade geral)
|
||||||
|
3. Crie uma versão com guia fonético / marcação de leitura (com acentos tônicos em negrito e marcações de [pausa] para a leitura em voz alta).
|
||||||
|
4. Mapeie o campo de experiência da BNCC e as habilidades trabalhadas (ex: EI02EF01 / EI03EF02 - Escuta, Fala, Pensamento e Imaginação).
|
||||||
|
5. Forneça uma vivência pedagógica prática (dica para a professora Camila conduzir sarau, gestos ou dinâmicas).
|
||||||
|
|
||||||
|
Retorne ESTRITAMENTE um JSON válido (sem texto fora do JSON):
|
||||||
|
{
|
||||||
|
"titulo": "Título Criativo do Poema",
|
||||||
|
"poesia": "Texto completo da poesia com quebras de linha e separação de estrofes.",
|
||||||
|
"esquemaRima": "A-B-A-B",
|
||||||
|
"analiseLiteraria": {
|
||||||
|
"rima": 85,
|
||||||
|
"ritmo": 80,
|
||||||
|
"aliteracao": 65,
|
||||||
|
"musicalidade": 90,
|
||||||
|
"explicacaoMetrica": "Versos regulares com cadência agradável, rimas cruzadas e sonoridade envolvente para contação de histórias."
|
||||||
|
},
|
||||||
|
"versaoFonetica": "Versão da poesia com sílabas tônicas marcadas e [pausa] para orientação de leitura em sala de aula.",
|
||||||
|
"bnccCampos": "EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação",
|
||||||
|
"dicasPedagogicas": "Sugestão de 2 a 3 linhas para a professora aplicar com as crianças em sala."
|
||||||
|
}`;
|
||||||
|
|
||||||
|
let jsonResponseText = '';
|
||||||
|
|
||||||
|
// 1. Tentar MiniMax M3
|
||||||
|
try {
|
||||||
|
console.log('[Poesia] Compondo poesia com MiniMax M3...');
|
||||||
|
const r = await callMinimax({
|
||||||
|
system: "Você é uma poetisa infantil e linguista pedagógica de excelência no Brasil. Responda estritamente em formato JSON válido.",
|
||||||
|
messages: [{ role: 'user', content: promptPoesia }],
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 1800
|
||||||
|
});
|
||||||
|
jsonResponseText = r.text;
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Poesia] Erro MiniMax M3:', err.message);
|
||||||
}
|
}
|
||||||
|
|
||||||
const systemInstruction = `Você é um poeta pedagógico infantil especializado em criar poesias ritmadas, sonoras e adequadas para o desenvolvimento infantil no Brasil.
|
// 2. Fallback Groq
|
||||||
REGRAS RÍGIDAS:
|
if (!jsonResponseText && process.env.GROQ_API_KEY) {
|
||||||
- Escreva a poesia para a faixa etária: ${faixaEtaria}
|
try {
|
||||||
${idadeInstrucao}
|
console.log('[Poesia] Fallback Groq...');
|
||||||
- A poesia DEVE ter EXATAMENTE ${estrofes} estrofes.
|
const r = await fetch('https://api.groq.com/openai/v1/chat/completions', {
|
||||||
- O tema da poesia é: "${tema}"
|
method: 'POST',
|
||||||
- Use rimas claras, ritmo marcante (musicalidade) e seja criativo.
|
headers: {
|
||||||
- Sua resposta deve conter APENAS a poesia formatada em Markdown, sem NENHUM comentário adicional, explicação, ou blocos de pensamento.
|
'Authorization': `Bearer ${process.env.GROQ_API_KEY}`,
|
||||||
- Responda EXCLUSIVAMENTE em Português do Brasil.`;
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: process.env.GROQ_MODEL || 'openai/gpt-oss-120b',
|
||||||
|
messages: [{ role: 'user', content: promptPoesia }],
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 1800,
|
||||||
|
response_format: { type: 'json_object' }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (r.ok) {
|
||||||
|
const data = await r.json();
|
||||||
|
jsonResponseText = data.choices?.[0]?.message?.content || '';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Poesia] Erro Groq:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Utilizando o Gemini diretamente
|
// 3. Fallback OpenRouter
|
||||||
if (process.env.GOOGLE_AI_API_KEY) {
|
if (!jsonResponseText && process.env.OPENROUTER_API_KEY) {
|
||||||
console.log('[Poesia] Usando API direta do Google Gemini...');
|
try {
|
||||||
|
console.log('[Poesia] Fallback OpenRouter...');
|
||||||
|
const r = await fetch('https://openrouter.ai/api/v1/chat/completions', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
|
||||||
|
messages: [{ role: 'user', content: promptPoesia }],
|
||||||
|
temperature: 0.7,
|
||||||
|
max_tokens: 1800
|
||||||
|
})
|
||||||
|
});
|
||||||
|
if (r.ok) {
|
||||||
|
const data = await r.json();
|
||||||
|
jsonResponseText = data.choices?.[0]?.message?.content || '';
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error('[Poesia] Erro OpenRouter:', err.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 4. Fallback Google Gemini
|
||||||
|
if (!jsonResponseText && process.env.GOOGLE_AI_API_KEY) {
|
||||||
|
try {
|
||||||
|
console.log('[Poesia] Fallback Gemini...');
|
||||||
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 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, {
|
const response = await fetch(geminiUrl, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({
|
body: JSON.stringify({
|
||||||
contents: [{ parts: [{ text: systemInstruction }] }],
|
contents: [{ parts: [{ text: promptPoesia }] }],
|
||||||
generationConfig: { temperature: 0.7, maxOutputTokens: 2000 }
|
generationConfig: { temperature: 0.7, maxOutputTokens: 2000 }
|
||||||
})
|
})
|
||||||
});
|
});
|
||||||
|
if (response.ok) {
|
||||||
if (!response.ok) {
|
|
||||||
const errText = await response.text();
|
|
||||||
throw new Error(`Erro na API oficial do Gemini: ${errText}`);
|
|
||||||
}
|
|
||||||
|
|
||||||
const resData = await response.json();
|
const resData = await response.json();
|
||||||
poesiaText = resData.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
jsonResponseText = resData.candidates?.[0]?.content?.parts?.[0]?.text || '';
|
||||||
|
}
|
||||||
// Remover qualquer tag <think> residual (embora a instrução já previna)
|
} catch (err) {
|
||||||
poesiaText = poesiaText.replace(/<think>[\s\S]*?(?:<\/think>|$)/g, '').trim();
|
console.error('[Poesia] Erro Gemini:', err.message);
|
||||||
} else {
|
}
|
||||||
throw new Error('Chave GOOGLE_AI_API_KEY não configurada no servidor.');
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!poesiaText) {
|
// Parse JSON
|
||||||
throw new Error('Nenhuma poesia gerada.');
|
let poesiaData = {};
|
||||||
|
try {
|
||||||
|
const cleanJson = jsonResponseText.replace(/```json|```/g, '').trim();
|
||||||
|
poesiaData = JSON.parse(cleanJson);
|
||||||
|
} catch (parseErr) {
|
||||||
|
console.warn('[Poesia] Erro parse JSON, usando fallback texto:', parseErr.message);
|
||||||
|
poesiaData = {
|
||||||
|
titulo: `Poema: ${tema}`,
|
||||||
|
poesia: jsonResponseText || `Na escola a gente brinca,\nNa escola a gente aprende,\nCom ${tema} todo dia,\nNosso coração se acende!`,
|
||||||
|
esquemaRima: "A-B-C-B",
|
||||||
|
analiseLiteraria: {
|
||||||
|
rima: 80,
|
||||||
|
ritmo: 75,
|
||||||
|
aliteracao: 60,
|
||||||
|
musicalidade: 85,
|
||||||
|
explicacaoMetrica: "Métrica ritmada ideal para contação infantil e memorização em roda."
|
||||||
|
},
|
||||||
|
versaoFonetica: `Na es**co**la a gente **brin**ca, [pausa]\nNa es**co**la a gente a**pren**de, [pausa]\nCom ${tema} todo **di**a, [pausa]\nNosso cora**ção** se a**cen**de!`,
|
||||||
|
bnccCampos: "EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação",
|
||||||
|
dicasPedagogicas: "Faça gestos de mãos no peito e palmas suaves ao recitar as rimas finais."
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
// GERAÇÃO DE ÁUDIO TTS / RECITADO (MiniMax)
|
||||||
|
let audioUrl = null;
|
||||||
|
if (gerarAudio !== false && process.env.MINIMAX_API_KEY) {
|
||||||
|
try {
|
||||||
|
console.log('[Poesia] Gerando áudio recitado com MiniMax...');
|
||||||
|
let voiceDesc = 'sweet gentle native brazilian female teacher voice reciting warm nursery poem, calm clear tempo';
|
||||||
|
if (voz === 'homem') {
|
||||||
|
voiceDesc = 'warm friendly native brazilian male educator voice storytelling poem, soft and clear';
|
||||||
|
} else if (voz === 'crianca') {
|
||||||
|
voiceDesc = 'cute enthusiastic young brazilian child voice reciting poetry, joyful';
|
||||||
|
}
|
||||||
|
|
||||||
|
const audioPrompt = `Spoken poetry recitation, ${voiceDesc}, clear pronunciation, acoustic ambient background`;
|
||||||
|
const lyricsToRecite = (poesiaData.poesia || tema).replace(/[*#]/g, '').trim();
|
||||||
|
|
||||||
|
const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
|
||||||
|
const musicResp = await fetch(`${minmBase}/v1/music_generation`, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
|
||||||
|
'Content-Type': 'application/json'
|
||||||
|
},
|
||||||
|
body: JSON.stringify({
|
||||||
|
model: 'music-2.0',
|
||||||
|
prompt: audioPrompt,
|
||||||
|
lyrics: lyricsToRecite.substring(0, 500),
|
||||||
|
audio_setting: { sample_rate: 32000, bitrate: 128000, format: 'mp3' }
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (musicResp.ok) {
|
||||||
|
const mData = await musicResp.json();
|
||||||
|
const audioHex = mData.data?.audio;
|
||||||
|
if (audioHex) {
|
||||||
|
const fs = require('fs');
|
||||||
|
const path = require('path');
|
||||||
|
const mediaDir = path.join(__dirname, 'public', 'generated-media');
|
||||||
|
if (!fs.existsSync(mediaDir)) {
|
||||||
|
fs.mkdirSync(mediaDir, { recursive: true });
|
||||||
|
}
|
||||||
|
const fileName = `poesia_${Date.now()}.mp3`;
|
||||||
|
const filePath = path.join(mediaDir, fileName);
|
||||||
|
const audioBuffer = Buffer.from(audioHex, 'hex');
|
||||||
|
fs.writeFileSync(filePath, audioBuffer);
|
||||||
|
await backupMediaFile(filePath, audioBuffer);
|
||||||
|
audioUrl = `/generated-media/${fileName}`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (audioErr) {
|
||||||
|
console.error('[Poesia] Erro ao gerar áudio recitado:', audioErr.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salvar no Banco PostgreSQL
|
||||||
|
let savedId = null;
|
||||||
|
try {
|
||||||
const dbRes = await dbPool.query(
|
const dbRes = await dbPool.query(
|
||||||
`INSERT INTO escola.poesias (usuario_id, faixa_etaria, estrofes, tema, poesia)
|
`INSERT INTO escola.poesias (
|
||||||
VALUES ($1, $2, $3, $4, $5) RETURNING id, created_at`,
|
usuario_id, faixa_etaria, estrofes, tema, poesia,
|
||||||
[usuarioId, faixaEtaria, parseInt(estrofes), tema, poesiaText]
|
estrutura, estilo, audio_url, analise_metrica, versao_fonetica, bncc, turma_id, dicas
|
||||||
|
) VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10, $11, $12, $13) RETURNING id`,
|
||||||
|
[
|
||||||
|
usuarioId,
|
||||||
|
faixaEtaria || 'Crianças pequenas',
|
||||||
|
parseInt(estrofes) || 3,
|
||||||
|
tema,
|
||||||
|
poesiaData.poesia || '',
|
||||||
|
estrutura || 'Quadras',
|
||||||
|
estilo || 'Lúdico-cantado',
|
||||||
|
audioUrl,
|
||||||
|
JSON.stringify(poesiaData.analiseLiteraria || {}),
|
||||||
|
poesiaData.versaoFonetica || '',
|
||||||
|
poesiaData.bnccCampos || '',
|
||||||
|
turmaId || null,
|
||||||
|
poesiaData.dicasPedagogicas || ''
|
||||||
|
]
|
||||||
);
|
);
|
||||||
|
savedId = dbRes.rows[0]?.id;
|
||||||
|
} catch (dbErr) {
|
||||||
|
console.warn('[Poesia] Erro ao salvar colunas estendidas, tentando salvar básico:', dbErr.message);
|
||||||
|
try {
|
||||||
|
const dbRes2 = await dbPool.query(
|
||||||
|
`INSERT INTO escola.poesias (usuario_id, faixa_etaria, estrofes, tema, poesia) VALUES ($1, $2, $3, $4, $5) RETURNING id`,
|
||||||
|
[usuarioId, faixaEtaria || 'Crianças', parseInt(estrofes) || 3, tema, poesiaData.poesia || '']
|
||||||
|
);
|
||||||
|
savedId = dbRes2.rows[0]?.id;
|
||||||
|
} catch (err2) {
|
||||||
|
console.error('[Poesia] Erro ao salvar poesia:', err2.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.json({
|
res.json({
|
||||||
success: true,
|
success: true,
|
||||||
id: dbRes.rows[0].id,
|
id: savedId,
|
||||||
poesia: poesiaText,
|
titulo: poesiaData.titulo || `Poema: ${tema}`,
|
||||||
createdAt: dbRes.rows[0].created_at
|
poesia: poesiaData.poesia || '',
|
||||||
|
esquemaRima: poesiaData.esquemaRima || 'A-B-A-B',
|
||||||
|
analiseLiteraria: poesiaData.analiseLiteraria || { rima: 80, ritmo: 80, aliteracao: 60, musicalidade: 85 },
|
||||||
|
versaoFonetica: poesiaData.versaoFonetica || '',
|
||||||
|
bnccCampos: poesiaData.bnccCampos || 'EI02EF01 / EI03EF02 • Escuta, Fala, Pensamento e Imaginação',
|
||||||
|
dicasPedagogicas: poesiaData.dicasPedagogicas || '',
|
||||||
|
audioUrl: audioUrl,
|
||||||
|
tema: tema,
|
||||||
|
faixaEtaria: faixaEtaria,
|
||||||
|
estrutura: estrutura,
|
||||||
|
estilo: estilo
|
||||||
});
|
});
|
||||||
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro na geração de poesia:', err);
|
console.error('Erro geral no Estúdio de Poesia:', err);
|
||||||
res.status(500).json({ error: 'Erro ao gerar poesia: ' + err.message });
|
res.status(500).json({ error: 'Erro ao compor poesia: ' + err.message });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -1327,14 +1527,21 @@ app.get('/api/poesias/list', requireAuth, async (req, res) => {
|
|||||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||||
try {
|
try {
|
||||||
const dbRes = await dbPool.query(
|
const dbRes = await dbPool.query(
|
||||||
`SELECT id, faixa_etaria, estrofes, tema, created_at FROM escola.poesias WHERE usuario_id = $1 ORDER BY created_at DESC`,
|
`SELECT id, faixa_etaria, estrofes, tema, audio_url, created_at FROM escola.poesias WHERE usuario_id = $1 ORDER BY created_at DESC`,
|
||||||
[usuarioId]
|
[usuarioId]
|
||||||
);
|
);
|
||||||
res.json(dbRes.rows);
|
res.json(dbRes.rows);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao listar poesias:', err);
|
try {
|
||||||
|
const dbRes2 = await dbPool.query(
|
||||||
|
`SELECT id, faixa_etaria, estrofes, tema, created_at FROM escola.poesias WHERE usuario_id = $1 ORDER BY created_at DESC`,
|
||||||
|
[usuarioId]
|
||||||
|
);
|
||||||
|
res.json(dbRes2.rows);
|
||||||
|
} catch (err2) {
|
||||||
res.status(500).json({ error: 'Erro ao listar histórico de poesias.' });
|
res.status(500).json({ error: 'Erro ao listar histórico de poesias.' });
|
||||||
}
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
app.get('/api/poesias/:id', requireAuth, async (req, res) => {
|
app.get('/api/poesias/:id', requireAuth, async (req, res) => {
|
||||||
@@ -1350,7 +1557,6 @@ app.get('/api/poesias/:id', requireAuth, async (req, res) => {
|
|||||||
}
|
}
|
||||||
res.json(dbRes.rows[0]);
|
res.json(dbRes.rows[0]);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao buscar poesia:', err);
|
|
||||||
res.status(500).json({ error: 'Erro ao buscar poesia.' });
|
res.status(500).json({ error: 'Erro ao buscar poesia.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -1364,11 +1570,10 @@ app.delete('/api/poesias/:id', requireAuth, async (req, res) => {
|
|||||||
[id, usuarioId]
|
[id, usuarioId]
|
||||||
);
|
);
|
||||||
if (dbRes.rowCount === 0) {
|
if (dbRes.rowCount === 0) {
|
||||||
return res.status(404).json({ error: 'Poesia não encontrada ou não pertence ao usuário.' });
|
return res.status(404).json({ error: 'Poesia não encontrada.' });
|
||||||
}
|
}
|
||||||
res.json({ success: true, message: 'Poesia deletada com sucesso.' });
|
res.json({ success: true, message: 'Poesia deletada com sucesso.' });
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
console.error('Erro ao deletar poesia:', err);
|
|
||||||
res.status(500).json({ error: 'Erro ao deletar poesia.' });
|
res.status(500).json({ error: 'Erro ao deletar poesia.' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -2463,6 +2668,15 @@ async function initDatabase() {
|
|||||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS estrutura VARCHAR(100);
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS estilo VARCHAR(100);
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS audio_url TEXT;
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS analise_metrica JSONB;
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS versao_fonetica TEXT;
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS bncc VARCHAR(100);
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS turma_id UUID;
|
||||||
|
ALTER TABLE escola.poesias ADD COLUMN IF NOT EXISTS dicas TEXT;
|
||||||
|
|
||||||
-- Músicas (Musicando Ideias)
|
-- Músicas (Musicando Ideias)
|
||||||
CREATE TABLE IF NOT EXISTS escola.musicas (
|
CREATE TABLE IF NOT EXISTS escola.musicas (
|
||||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||||
|
|||||||
Reference in New Issue
Block a user