Feature: Inventando Histórias profissional com 3-stage pipeline narrativa, vinculacao a turmas/alunos, ilustracoes por cena, leitor interativo, TTS narrado e exportacao PDF/TXT

This commit is contained in:
2026-08-31 11:25:33 +00:00
parent 3931af93db
commit 837762c1af
4 changed files with 1193 additions and 175 deletions
+530 -68
View File
@@ -7694,8 +7694,67 @@ window.toggleCustomAudio = (btn) => {
// ============================================================
// INVENTANDO HISTORIAS
// INVENTANDO HISTÓRIAS (ESTÚDIO PROFISSIONAL)
// ============================================================
const HISTORIA_TEMPLATES = {
arca: {
titulo: 'A Arca da Imaginação',
objeto: 'Arca Mágica de Madeira',
tema: 'As crianças encontram uma arca misteriosa no parquinho que se abre apenas quando todos dão as mãos e compartilham uma ideia criativa.',
estilo: 'Lúdico, Afetivo e Dialogado',
faixa: 'Crianças pequenas (4 anos a 5 anos e 11 meses)',
visual: 'Cartoon colorido e acolhedor'
},
horta: {
titulo: 'O Mistério da Horta Encantada',
objeto: 'Semente Brilhante de Girassol',
tema: 'A turma planta uma sementinha brilhante na horta da escola e descobre que ela cresce quando ouve canções de carinho e risadas.',
estilo: 'Contação Clássica com Suspense Leve',
faixa: 'Crianças pequenas (4 anos a 5 anos e 11 meses)',
visual: 'Aquarela suave infantil'
},
dinossauro: {
titulo: 'O Pequeno Dinossauro Amigo',
objeto: 'Pegada Colorida de Brinquedo',
tema: 'Um pequeno dinossauro verde e tímido aparece na hora do lanche procurando amigos para brincar de roda e desenhar.',
estilo: 'Lúdico, Afetivo e Dialogado',
faixa: 'Crianças bem pequenas (1 ano e 7 meses a 3 anos e 11 meses)',
visual: 'Cartoon colorido e acolhedor'
},
chuva: {
titulo: 'O Dia em que a Chuva Dançou',
objeto: 'Galochas Amarelas Saltitantes',
tema: 'Em um dia chuvoso, as gotas batem na janela fazendo música e convidam as crianças para uma dança aconchegante dentro da sala.',
estilo: 'Roda de Conversa Interativa',
faixa: 'Crianças pequenas (4 anos a 5 anos e 11 meses)',
visual: 'Aquarela suave infantil'
},
bolhas: {
titulo: 'O Reino Mágico das Bolhas de Sabão',
objeto: 'Varinha de Fazer Bolhas Gigantes',
tema: 'Cada bolha de sabão que as crianças assopram no pátio leva um desejo de abraço e alegria até as nuvens coloridas.',
estilo: 'Lúdico, Afetivo e Dialogado',
faixa: 'Bebês (0 a 1 ano e 6 meses)',
visual: 'Giz de cera e textura lúdica'
},
brinquedos: {
titulo: 'A Reunião Secreta dos Brinquedos',
objeto: 'Ursinho de Pano Guardião',
tema: 'Na hora do descanso, os brinquedos se organizam com muito capricho para esperar o acordar dos alunos com uma surpresa de blocos montados.',
estilo: 'Aventura e Descoberta na Escola',
faixa: 'Crianças pequenas (4 anos a 5 anos e 11 meses)',
visual: 'Livro infantil clássico e detalhado'
},
emocoes: {
titulo: 'O Monstrinho das Cores e dos Sentimentos',
objeto: 'Pote Brilhante das Emoções',
tema: 'Um monstrinho amigo muda de cor quando sente alegria (amarelo), calma (azul) ou saudade (rosa), ensinando a turma a expressar o que sente.',
estilo: 'Roda de Conversa Interativa',
faixa: 'Crianças pequenas (4 anos a 5 anos e 11 meses)',
visual: 'Cartoon colorido e acolhedor'
}
};
const barBtnHistoria = document.getElementById('barBtnHistoria');
const historiaModal = document.getElementById('historiaModal');
const btnCloseHistoriaModal = document.getElementById('btnCloseHistoriaModal');
@@ -7703,35 +7762,118 @@ window.toggleCustomAudio = (btn) => {
const historiaHistoryModal = document.getElementById('historiaHistoryModal');
const btnCloseHistoriaHistory = document.getElementById('btnCloseHistoriaHistory');
const historiaTemplateSelect = document.getElementById('historiaTemplateSelect');
const historiaTurmaSelect = document.getElementById('historiaTurmaSelect');
const historiaPersonagemInput = document.getElementById('historiaPersonagemInput');
const historiaObjetoMagicoInput = document.getElementById('historiaObjetoMagicoInput');
const historiaFaixaEtaria = document.getElementById('historiaFaixaEtaria');
const historiaParagrafos = document.getElementById('historiaParagrafos');
const historiaPersonagens = document.getElementById('historiaPersonagens');
const historiaEstilo = document.getElementById('historiaEstilo');
const historiaEstiloVisual = document.getElementById('historiaEstiloVisual');
const historiaTemaInput = document.getElementById('historiaTemaInput');
const btnGenerateHistoria = document.getElementById('btnGenerateHistoria');
const historiaGenerateLoader = document.getElementById('historiaGenerateLoader');
const historiaResultArea = document.getElementById('historiaResultArea');
const historiaTextOutput = document.getElementById('historiaTextOutput');
const btnCopyHistoria = document.getElementById('btnCopyHistoria');
const btnDownloadHistoriaPdf = document.getElementById('btnDownloadHistoriaPdf');
let currentHistoria = '';
// Elementos do Leitor e Visualizador
const historiaPlaceholderText = document.getElementById('historiaPlaceholderText');
const historiaActiveBox = document.getElementById('historiaActiveBox');
const historiaActiveTitle = document.getElementById('historiaActiveTitle');
const historiaActiveSubtitle = document.getElementById('historiaActiveSubtitle');
const historiaBnccBadge = document.getElementById('historiaBnccBadge');
if (barBtnHistoria) {
barBtnHistoria.addEventListener('click', () => {
historiaModal.style.display = 'flex';
historiaTemaInput.value = '';
historiaResultArea.style.display = 'none';
historiaGenerateLoader.style.display = 'none';
btnGenerateHistoria.disabled = false;
currentHistoria = '';
// Player de Áudio
const historiaAudioPlayerBox = document.getElementById('historiaAudioPlayerBox');
const historiaAudioPlayer = document.getElementById('historiaAudioPlayer');
const btnDownloadHistoriaAudio = document.getElementById('btnDownloadHistoriaAudio');
const historiaWaveBars = document.querySelectorAll('.wave-bar-historia');
// Abas
const historiaContentTabs = document.getElementById('historiaContentTabs');
const btnTabHistoriaCenas = document.getElementById('btnTabHistoriaCenas');
const btnTabHistoriaTexto = document.getElementById('btnTabHistoriaTexto');
const btnTabHistoriaAtividades = document.getElementById('btnTabHistoriaAtividades');
const historiaCenasView = document.getElementById('historiaCenasView');
const historiaTextoView = document.getElementById('historiaTextoView');
const historiaAtividadesView = document.getElementById('historiaAtividadesView');
// Cenas Interativas
const btnPrevCena = document.getElementById('btnPrevCena');
const btnNextCena = document.getElementById('btnNextCena');
const historiaCenaIndicator = document.getElementById('historiaCenaIndicator');
const historiaCenaTitle = document.getElementById('historiaCenaTitle');
const historiaCenaText = document.getElementById('historiaCenaText');
const historiaCenaImageContainer = document.getElementById('historiaCenaImageContainer');
const historiaCenaImg = document.getElementById('historiaCenaImg');
const btnGenerateSceneImage = document.getElementById('btnGenerateSceneImage');
// BNCC e Perguntas
const historiaBnccDesc = document.getElementById('historiaBnccDesc');
const historiaPerguntasList = document.getElementById('historiaPerguntasList');
// Ações
const btnCopyActiveHistoria = document.getElementById('btnCopyActiveHistoria');
const btnDownloadActiveHistoriaTxt = document.getElementById('btnDownloadActiveHistoriaTxt');
const btnSendHistoriaToMusic = document.getElementById('btnSendHistoriaToMusic');
const btnExportActiveHistoriaPdf = document.getElementById('btnExportActiveHistoriaPdf');
let currentHistoriaData = null;
let currentSceneIndex = 0;
// Carregar turmas no select de histórias
async function loadTurmasForHistoria() {
if (!historiaTurmaSelect) return;
try {
const res = await fetch('/api/turmas');
if (res.ok) {
const turmas = await res.json();
historiaTurmaSelect.innerHTML = '<option value="">-- Selecionar Turma --</option>';
turmas.forEach(t => {
const opt = document.createElement('option');
opt.value = t.id;
opt.textContent = `${t.nome} (${t.ano_letivo || '2026'})`;
historiaTurmaSelect.appendChild(opt);
});
}
} catch (e) {
console.warn('Erro ao carregar turmas para histórias:', e);
}
}
// Mudança de template
if (historiaTemplateSelect) {
historiaTemplateSelect.addEventListener('change', () => {
const key = historiaTemplateSelect.value;
if (key && HISTORIA_TEMPLATES[key]) {
const tpl = HISTORIA_TEMPLATES[key];
historiaTemaInput.value = tpl.tema;
if (historiaObjetoMagicoInput) historiaObjetoMagicoInput.value = tpl.objeto;
if (historiaEstilo) historiaEstilo.value = tpl.estilo;
if (historiaFaixaEtaria) historiaFaixaEtaria.value = tpl.faixa;
if (historiaEstiloVisual) historiaEstiloVisual.value = tpl.visual;
}
});
}
if (btnCloseHistoriaModal) historiaModal.addEventListener('click', (e) => { if(e.target === historiaModal) historiaModal.style.display = 'none'; });
if (btnCloseHistoriaModal) btnCloseHistoriaModal.addEventListener('click', () => historiaModal.style.display = 'none');
// Abertura do Modal
if (barBtnHistoria) {
barBtnHistoria.addEventListener('click', () => {
historiaModal.style.display = 'flex';
loadTurmasForHistoria();
if (!currentHistoriaData) {
if (historiaPlaceholderText) historiaPlaceholderText.style.display = 'block';
if (historiaActiveBox) historiaActiveBox.style.display = 'none';
if (historiaAudioPlayerBox) historiaAudioPlayerBox.style.display = 'none';
if (historiaContentTabs) historiaContentTabs.style.display = 'none';
}
});
}
if (btnCloseHistoriaModal) {
historiaModal.addEventListener('click', (e) => { if (e.target === historiaModal) historiaModal.style.display = 'none'; });
btnCloseHistoriaModal.addEventListener('click', () => historiaModal.style.display = 'none');
}
// Histórico
if (btnOpenHistoriaHistory) {
btnOpenHistoriaHistory.addEventListener('click', () => {
historiaHistoryModal.style.display = 'flex';
@@ -7739,93 +7881,358 @@ window.toggleCustomAudio = (btn) => {
});
}
if (btnCloseHistoriaHistory) btnCloseHistoriaHistory.addEventListener('click', () => historiaHistoryModal.style.display = 'none');
historiaHistoryModal.addEventListener('click', (e) => { if(e.target === historiaHistoryModal) historiaHistoryModal.style.display = 'none'; });
historiaHistoryModal.addEventListener('click', (e) => { if (e.target === historiaHistoryModal) historiaHistoryModal.style.display = 'none'; });
// Alternância de Abas
function switchHistoriaTab(tab) {
[btnTabHistoriaCenas, btnTabHistoriaTexto, btnTabHistoriaAtividades].forEach(b => {
if (b) {
b.classList.remove('active');
b.style.background = 'transparent';
b.style.color = 'var(--text-secondary)';
}
});
if (historiaCenasView) historiaCenasView.style.display = 'none';
if (historiaTextoView) historiaTextoView.style.display = 'none';
if (historiaAtividadesView) historiaAtividadesView.style.display = 'none';
if (tab === 'cenas') {
if (btnTabHistoriaCenas) {
btnTabHistoriaCenas.classList.add('active');
btnTabHistoriaCenas.style.background = 'rgba(16,185,129,0.15)';
btnTabHistoriaCenas.style.color = '#34d399';
}
if (historiaCenasView) historiaCenasView.style.display = 'flex';
} else if (tab === 'texto') {
if (btnTabHistoriaTexto) {
btnTabHistoriaTexto.classList.add('active');
btnTabHistoriaTexto.style.background = 'rgba(16,185,129,0.15)';
btnTabHistoriaTexto.style.color = '#34d399';
}
if (historiaTextoView) historiaTextoView.style.display = 'block';
} else if (tab === 'atividades') {
if (btnTabHistoriaAtividades) {
btnTabHistoriaAtividades.classList.add('active');
btnTabHistoriaAtividades.style.background = 'rgba(16,185,129,0.15)';
btnTabHistoriaAtividades.style.color = '#34d399';
}
if (historiaAtividadesView) historiaAtividadesView.style.display = 'flex';
}
}
if (btnTabHistoriaCenas) btnTabHistoriaCenas.addEventListener('click', () => switchHistoriaTab('cenas'));
if (btnTabHistoriaTexto) btnTabHistoriaTexto.addEventListener('click', () => switchHistoriaTab('texto'));
if (btnTabHistoriaAtividades) btnTabHistoriaAtividades.addEventListener('click', () => switchHistoriaTab('atividades'));
// Renderizar Cena Atual
function renderCurrentScene() {
if (!currentHistoriaData || !currentHistoriaData.cenas || currentHistoriaData.cenas.length === 0) return;
const total = currentHistoriaData.cenas.length;
if (currentSceneIndex < 0) currentSceneIndex = 0;
if (currentSceneIndex >= total) currentSceneIndex = total - 1;
const cena = currentHistoriaData.cenas[currentSceneIndex];
if (historiaCenaIndicator) historiaCenaIndicator.textContent = `Cena ${currentSceneIndex + 1} de ${total}`;
if (historiaCenaTitle) historiaCenaTitle.textContent = cena.titulo || `Ato ${currentSceneIndex + 1}`;
if (historiaCenaText) historiaCenaText.textContent = cena.texto || '';
// Imagem da cena
if (cena.imageUrl) {
if (historiaCenaImg) historiaCenaImg.src = cena.imageUrl;
if (historiaCenaImageContainer) historiaCenaImageContainer.style.display = 'block';
if (btnGenerateSceneImage) btnGenerateSceneImage.innerHTML = '🔄 Regenerar Ilustração';
} else {
if (historiaCenaImageContainer) historiaCenaImageContainer.style.display = 'none';
if (btnGenerateSceneImage) btnGenerateSceneImage.innerHTML = '🎨 Gerar Ilustração';
}
if (btnPrevCena) btnPrevCena.disabled = currentSceneIndex === 0;
if (btnNextCena) btnNextCena.disabled = currentSceneIndex === total - 1;
}
if (btnPrevCena) {
btnPrevCena.addEventListener('click', () => {
if (currentSceneIndex > 0) {
currentSceneIndex--;
renderCurrentScene();
}
});
}
if (btnNextCena) {
btnNextCena.addEventListener('click', () => {
if (currentHistoriaData && currentHistoriaData.cenas && currentSceneIndex < currentHistoriaData.cenas.length - 1) {
currentSceneIndex++;
renderCurrentScene();
}
});
}
// Gerar Ilustração da Cena Atual
if (btnGenerateSceneImage) {
btnGenerateSceneImage.addEventListener('click', async () => {
if (!currentHistoriaData || !currentHistoriaData.cenas) return;
const cena = currentHistoriaData.cenas[currentSceneIndex];
if (!cena) return;
const originalBtnHtml = btnGenerateSceneImage.innerHTML;
btnGenerateSceneImage.disabled = true;
btnGenerateSceneImage.innerHTML = '⏳ Ilustrando...';
try {
const res = await fetch('/api/historias/scene-image', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
prompt: cena.promptImagem || cena.texto || 'Cute children book illustration',
estiloVisual: currentHistoriaData.estiloVisual || 'Cartoon colorido',
cenaNumero: currentSceneIndex + 1
})
});
const data = await res.json();
if (res.ok && data.imageUrl) {
cena.imageUrl = data.imageUrl;
renderCurrentScene();
showToast('Ilustração da cena criada com sucesso!', 'success');
} else {
showToast(data.error || 'Erro ao gerar ilustração.', 'error');
}
} catch (err) {
showToast('Erro ao conectar com gerador de ilustração.', 'error');
} finally {
btnGenerateSceneImage.disabled = false;
btnGenerateSceneImage.innerHTML = originalBtnHtml;
}
});
}
// Gerar História Completa
if (btnGenerateHistoria) {
btnGenerateHistoria.addEventListener('click', async () => {
const tema = historiaTemaInput.value.trim();
if (!tema) {
showToast('Por favor, informe o tema da história.', 'error');
showToast('Por favor, informe o tema ou enredo da história.', 'error');
return;
}
btnGenerateHistoria.disabled = true;
historiaResultArea.style.display = 'none';
historiaGenerateLoader.style.display = 'flex';
if (historiaGenerateLoader) historiaGenerateLoader.style.display = 'flex';
if (historiaPlaceholderText) historiaPlaceholderText.style.display = 'none';
try {
const response = await fetch('/api/historias/generate', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
faixaEtaria: historiaFaixaEtaria.value,
paragrafos: historiaParagrafos.value,
personagens: historiaPersonagens.value,
estilo: historiaEstilo.value,
tema: tema
tema: tema,
faixaEtaria: historiaFaixaEtaria ? historiaFaixaEtaria.value : 'Crianças pequenas',
paragrafos: historiaParagrafos ? historiaParagrafos.value : '3',
estilo: historiaEstilo ? historiaEstilo.value : 'Lúdico',
estiloVisual: historiaEstiloVisual ? historiaEstiloVisual.value : 'Cartoon colorido',
personagemPrincipal: historiaPersonagemInput ? historiaPersonagemInput.value.trim() : '',
objetoMagico: historiaObjetoMagicoInput ? historiaObjetoMagicoInput.value.trim() : '',
turmaId: historiaTurmaSelect ? historiaTurmaSelect.value : null
})
});
const data = await response.json();
if (response.ok) {
currentHistoria = data.historia;
historiaTextOutput.innerHTML = window.marked ? marked.parse(currentHistoria) : currentHistoria;
historiaResultArea.style.display = 'flex';
if (response.ok && data.success) {
currentHistoriaData = data;
currentSceneIndex = 0;
// Header
if (historiaActiveTitle) historiaActiveTitle.textContent = data.titulo || 'História Mágica';
if (historiaActiveSubtitle) {
historiaActiveSubtitle.textContent = `Personagem: ${data.personagemPrincipal || 'Turma'}${data.cenas ? data.cenas.length : 3} Cenas • ${data.duracaoLeitura || '3 min'}`;
}
if (historiaBnccBadge) {
historiaBnccBadge.textContent = `BNCC: ${data.bnccCampos || 'EI02EF04 / EI03EF05'}`;
historiaBnccBadge.style.display = 'inline-block';
}
// Player de Áudio
if (data.audioUrl) {
if (historiaAudioPlayer) historiaAudioPlayer.src = data.audioUrl;
if (btnDownloadHistoriaAudio) btnDownloadHistoriaAudio.href = data.audioUrl;
if (historiaAudioPlayerBox) historiaAudioPlayerBox.style.display = 'flex';
if (historiaAudioPlayer) {
historiaAudioPlayer.onplay = () => historiaWaveBars.forEach(b => b.classList.add('wave-active'));
historiaAudioPlayer.onpause = () => historiaWaveBars.forEach(b => b.classList.remove('wave-active'));
historiaAudioPlayer.onended = () => historiaWaveBars.forEach(b => b.classList.remove('wave-active'));
}
} else {
if (historiaAudioPlayerBox) historiaAudioPlayerBox.style.display = 'none';
}
// Abas & Visualização
if (historiaContentTabs) historiaContentTabs.style.display = 'flex';
if (historiaActiveBox) historiaActiveBox.style.display = 'flex';
// Texto completo
if (historiaTextoView) historiaTextoView.textContent = data.historiaCompleta || '';
// Atividades e BNCC
if (historiaBnccDesc) historiaBnccDesc.textContent = data.bnccCampos || 'EI02EF04 / EI03EF05 • Escuta, Fala, Pensamento e Imaginação';
if (historiaPerguntasList) {
historiaPerguntasList.innerHTML = '';
(data.perguntasCompreensao || []).forEach(p => {
const pEl = document.createElement('div');
pEl.style.cssText = 'padding: 4px 0; border-bottom: 1px dashed rgba(255,255,255,0.05);';
pEl.textContent = p;
historiaPerguntasList.appendChild(pEl);
});
}
renderCurrentScene();
switchHistoriaTab('cenas');
showToast('História inventada com sucesso!', 'success');
} else {
showToast(data.error || 'Erro ao inventar história.', 'error');
showToast(data.error || 'Erro ao gerar história.', 'error');
}
} catch (err) {
showToast('Erro de conexão.', 'error');
showToast('Erro de conexão ao gerar história.', 'error');
} finally {
historiaGenerateLoader.style.display = 'none';
if (historiaGenerateLoader) historiaGenerateLoader.style.display = 'none';
btnGenerateHistoria.disabled = false;
}
});
}
if (btnCopyHistoria) {
btnCopyHistoria.addEventListener('click', () => {
if (currentHistoria) {
navigator.clipboard.writeText(currentHistoria);
// Copiar História
if (btnCopyActiveHistoria) {
btnCopyActiveHistoria.addEventListener('click', () => {
if (currentHistoriaData && currentHistoriaData.historiaCompleta) {
navigator.clipboard.writeText(currentHistoriaData.historiaCompleta);
showToast('História copiada para a área de transferência!', 'success');
}
});
}
if (btnDownloadHistoriaPdf) {
btnDownloadHistoriaPdf.addEventListener('click', () => {
if (!currentHistoria) return;
const printWindow = window.open('', '_blank');
printWindow.document.write(`
<html>
// Baixar TXT
if (btnDownloadActiveHistoriaTxt) {
btnDownloadActiveHistoriaTxt.addEventListener('click', () => {
if (!currentHistoriaData) return;
const content = `=========================================
📖 ${currentHistoriaData.titulo || 'HISTÓRIA PEDAGÓGICA'}
=========================================
Personagem Principal: ${currentHistoriaData.personagemPrincipal || 'Turma'}
Objeto Mágico: ${currentHistoriaData.objetoMagico || 'Especial'}
BNCC: ${currentHistoriaData.bnccCampos || 'EI02EF04'}
Duração Estimada: ${currentHistoriaData.duracaoLeitura || '3 min'}
--- HISTÓRIA COMPLETA ---
${currentHistoriaData.historiaCompleta || ''}
--- PERGUNTAS PARA RODA DE CONVERSA ---
${(currentHistoriaData.perguntasCompreensao || []).join('\n')}
Gerado por PedagogIA Assistente da Professora Camila`;
const blob = new Blob([content], { type: 'text/plain;charset=utf-8' });
const url = URL.createObjectURL(blob);
const a = document.createElement('a');
a.href = url;
a.download = `${(currentHistoriaData.titulo || 'historia').toLowerCase().replace(/\s+/g, '_')}.txt`;
a.click();
URL.revokeObjectURL(url);
});
}
// Musicar História
if (btnSendHistoriaToMusic) {
btnSendHistoriaToMusic.addEventListener('click', () => {
if (!currentHistoriaData) return;
const barBtnMusica = document.getElementById('barBtnMusica');
const musicaTemaInput = document.getElementById('musicaTemaInput');
historiaModal.style.display = 'none';
if (barBtnMusica) barBtnMusica.click();
if (musicaTemaInput) {
musicaTemaInput.value = `Canção sobre a história "${currentHistoriaData.titulo}": ${currentHistoriaData.objetoMagico || ''}, amizade e imaginação`;
}
showToast('História transferida para o Musicando Ideias!', 'info');
});
}
// Exportar PDF Ilustrado
if (btnExportActiveHistoriaPdf) {
btnExportActiveHistoriaPdf.addEventListener('click', () => {
if (!currentHistoriaData) return;
const printWin = window.open('', '_blank');
if (!printWin) {
showToast('Permita pop-ups para gerar o PDF.', 'warning');
return;
}
let cenasHtml = '';
(currentHistoriaData.cenas || []).forEach((c, idx) => {
cenasHtml += `
<div style="margin-bottom: 20px; padding: 15px; border-left: 4px solid #10b981; background: #f8fafc; border-radius: 0 8px 8px 0;">
<h3 style="margin-top: 0; color: #059669; font-size: 1.1rem;">${c.titulo || `Ato ${idx + 1}`}</h3>
${c.imageUrl ? `<div style="text-align: center; margin: 10px 0;"><img src="${c.imageUrl}" style="max-height: 220px; border-radius: 8px;" /></div>` : ''}
<p style="font-size: 1rem; line-height: 1.7; color: #334155; margin: 0;">${c.texto || ''}</p>
</div>
`;
});
let perguntasHtml = '';
(currentHistoriaData.perguntasCompreensao || []).forEach(p => {
perguntasHtml += `<li style="margin-bottom: 6px;">${p}</li>`;
});
printWin.document.write(`
<!DOCTYPE html>
<html lang="pt-BR">
<head>
<title>História - Assistente PedaGog</title>
<meta charset="UTF-8">
<title>${currentHistoriaData.titulo || 'História Pedagógica'} - 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; } }
body { font-family: 'Outfit', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif; padding: 35px; color: #1e293b; line-height: 1.6; max-width: 800px; margin: 0 auto; }
h1 { color: #059669; margin: 0 0 6px 0; font-size: 1.8rem; }
.header-box { border-bottom: 2px solid #e2e8f0; padding-bottom: 14px; margin-bottom: 24px; }
.badge { display: inline-block; background: #d1fae5; color: #065f46; font-size: 0.8rem; padding: 4px 10px; border-radius: 6px; font-weight: bold; margin-right: 6px; }
.btn-print { background: #10b981; color: white; border: none; padding: 10px 20px; border-radius: 8px; font-weight: bold; cursor: pointer; float: right; font-size: 0.95rem; }
@media print { .btn-print { display: none; } body { padding: 0; } }
</style>
</head>
<body>
<div style="max-width: 800px; margin: 0 auto;">
<div style="display: flex; justify-content: space-between; align-items: center; border-bottom: 2px solid #e2e8f0; padding-bottom: 10px; margin-bottom: 20px;">
<h1 style="margin: 0;"> História Pedagógica</h1>
<button onclick="window.print()" style="background: #10b981; color: white; border: none; padding: 8px 16px; border-radius: 6px; cursor: pointer; font-weight: bold;">Imprimir / PDF</button>
<button class="btn-print" onclick="window.print()">🖨 Imprimir / Salvar PDF</button>
<div class="header-box">
<h1>📖 ${currentHistoriaData.titulo || 'História Pedagógica'}</h1>
<div style="margin-top: 8px;">
<span class="badge">Personagem: ${currentHistoriaData.personagemPrincipal || 'Turma'}</span>
<span class="badge">Objeto: ${currentHistoriaData.objetoMagico || 'Especial'}</span>
<span class="badge">BNCC: ${currentHistoriaData.bnccCampos || 'EI02EF04'}</span>
</div>
<div>${window.marked ? marked.parse(currentHistoria) : currentHistoria}</div>
</div>
<h2 style="color: #0f172a; font-size: 1.2rem; border-bottom: 1px solid #cbd5e1; padding-bottom: 6px;">🎬 Cenas da História</h2>
${cenasHtml}
<div style="margin-top: 30px; padding: 18px; background: #f1f5f9; border-radius: 8px;">
<h3 style="margin-top: 0; color: #0f172a; font-size: 1.05rem;">🧩 Roda de Conversa & Atividades</h3>
<ul style="margin: 0; padding-left: 20px; color: #475569; font-size: 0.95rem;">
${perguntasHtml}
</ul>
</div>
<div style="margin-top: 40px; text-align: center; font-size: 0.8rem; color: #94a3b8; border-top: 1px solid #e2e8f0; padding-top: 12px;">
Pedagog Estúdio de Literatura Infantil da Professora Camila Martella Gasparini Reifonas
</div>
</body>
</html>
`);
printWindow.document.close();
printWin.document.close();
});
}
// Carregar Histórico
async function loadHistoriaHistory() {
const listContainer = document.getElementById('historiaHistoryList');
if (!listContainer) return;
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando...</p>';
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Carregando histórias...</p>';
try {
const response = await fetch('/api/historias/list');
const data = await response.json();
@@ -7835,19 +8242,24 @@ window.toggleCustomAudio = (btn) => {
const date = new Date(item.created_at).toLocaleString('pt-BR');
const div = document.createElement('div');
div.className = 'history-item-card';
div.style.cssText = 'background: rgba(255,255,255,0.02); padding: 14px; border-radius: 8px; border: 1px solid var(--border-light); display: flex; flex-direction: column; gap: 8px; cursor: pointer; position: relative;';
div.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: 6px; cursor: pointer; position: relative;';
div.innerHTML = `
<div style="display: flex; justify-content: space-between; align-items: flex-start; padding-right: 30px;">
<strong style="color: var(--brand-green); font-size: 0.95rem;">${item.faixa_etaria} (${item.estilo})</strong>
<span style="font-size: 0.75rem; color: var(--text-secondary);">${date}</span>
<strong style="color: #34d399; font-size: 0.95rem;">${item.titulo || item.tema || 'História'}</strong>
<span style="font-size: 0.72rem; color: var(--text-secondary);">${date}</span>
</div>
<p style="margin: 0; font-size: 0.82rem; color: var(--text-primary); opacity: 0.9;">Tema: ${item.tema}</p>
<div style="display: flex; gap: 8px; font-size: 0.72rem; color: var(--text-secondary);">
<span>👶 ${item.faixa_etaria}</span>
${item.personagem_principal ? `<span>• 👤 ${item.personagem_principal}</span>` : ''}
${item.audio_url ? `<span>• 🎙️ Áudio MP3</span>` : ''}
</div>
<p style="margin: 0; font-size: 0.85rem; color: var(--text-primary); opacity: 0.9;">Tema: ${item.tema}</p>
<button class="btn-delete-historia" data-id="${item.id}" style="position: absolute; top: 12px; right: 12px; background: transparent; border: none; color: #ef4444; cursor: pointer; font-size: 1.1rem; padding: 4px; border-radius: 4px;" title="Excluir">🗑</button>
`;
div.addEventListener('click', async (e) => {
if (e.target.closest('.btn-delete-historia')) {
e.stopPropagation();
if (confirm('Tem certeza que deseja excluir?')) {
if (confirm('Tem certeza que deseja excluir esta história?')) {
try {
const delRes = await fetch(`/api/historias/${item.id}`, { method: 'DELETE' });
if (delRes.ok) { showToast('História excluída.', 'success'); loadHistoriaHistory(); }
@@ -7859,19 +8271,69 @@ window.toggleCustomAudio = (btn) => {
const res = await fetch(`/api/historias/${item.id}`);
const detail = await res.json();
if (res.ok) {
currentHistoria = detail.historia;
historiaTextOutput.innerHTML = window.marked ? marked.parse(currentHistoria) : currentHistoria;
historiaResultArea.style.display = 'flex';
currentHistoriaData = {
id: detail.id,
titulo: detail.titulo || `História: ${detail.tema}`,
personagemPrincipal: detail.personagem_principal || '',
objetoMagico: detail.objeto_magico || '',
historiaCompleta: detail.historia || '',
cenas: typeof detail.cenas === 'string' ? JSON.parse(detail.cenas) : (detail.cenas || []),
perguntasCompreensao: typeof detail.perguntas_compreensao === 'string' ? JSON.parse(detail.perguntas_compreensao) : (detail.perguntas_compreensao || []),
bnccCampos: detail.bncc || 'EI02EF04 / EI03EF05',
duracaoLeitura: detail.duracao_leitura || '3 min',
complexidade: detail.complexidade || 'Lúdica',
audioUrl: detail.audio_url || null,
estiloVisual: detail.estilo_visual || 'Cartoon colorido'
};
currentSceneIndex = 0;
if (historiaActiveTitle) historiaActiveTitle.textContent = currentHistoriaData.titulo;
if (historiaActiveSubtitle) {
historiaActiveSubtitle.textContent = `Personagem: ${currentHistoriaData.personagemPrincipal || 'Turma'}${currentHistoriaData.cenas ? currentHistoriaData.cenas.length : 3} Cenas`;
}
if (historiaBnccBadge) {
historiaBnccBadge.textContent = `BNCC: ${currentHistoriaData.bnccCampos}`;
historiaBnccBadge.style.display = 'inline-block';
}
if (currentHistoriaData.audioUrl) {
if (historiaAudioPlayer) historiaAudioPlayer.src = currentHistoriaData.audioUrl;
if (btnDownloadHistoriaAudio) btnDownloadHistoriaAudio.href = currentHistoriaData.audioUrl;
if (historiaAudioPlayerBox) historiaAudioPlayerBox.style.display = 'flex';
} else {
if (historiaAudioPlayerBox) historiaAudioPlayerBox.style.display = 'none';
}
if (historiaContentTabs) historiaContentTabs.style.display = 'flex';
if (historiaActiveBox) historiaActiveBox.style.display = 'flex';
if (historiaPlaceholderText) historiaPlaceholderText.style.display = 'none';
if (historiaTextoView) historiaTextoView.textContent = currentHistoriaData.historiaCompleta;
if (historiaBnccDesc) historiaBnccDesc.textContent = currentHistoriaData.bnccCampos;
if (historiaPerguntasList) {
historiaPerguntasList.innerHTML = '';
(currentHistoriaData.perguntasCompreensao || []).forEach(p => {
const pEl = document.createElement('div');
pEl.style.cssText = 'padding: 4px 0; border-bottom: 1px dashed rgba(255,255,255,0.05);';
pEl.textContent = p;
historiaPerguntasList.appendChild(pEl);
});
}
renderCurrentScene();
switchHistoriaTab('cenas');
historiaHistoryModal.style.display = 'none';
}
} catch (err) { showToast('Erro ao carregar.', 'error'); }
} catch (err) { showToast('Erro ao carregar história.', 'error'); }
});
listContainer.appendChild(div);
});
} else {
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Nenhum histórico.</p>';
listContainer.innerHTML = '<p style="text-align:center; color:var(--text-secondary);">Nenhuma história salva ainda.</p>';
}
} catch (err) { listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar.</p>'; }
} catch (err) {
listContainer.innerHTML = '<p style="text-align:center; color:#ef4444;">Erro ao carregar histórico de histórias.</p>';
}
}
if (document.readyState === 'loading') {