🚀 Auto-deploy: Camila atualizado em 06/06/2026 21:07:19
This commit is contained in:
+405
@@ -6785,5 +6785,410 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
btnCloseMindLabHistory.addEventListener('click', () => {
|
||||
mindLabHistoryModal.style.display = 'none';
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// CRIAR CARTAZES
|
||||
// ============================================================
|
||||
const barBtnCartazes = document.getElementById('barBtnCartazes');
|
||||
const cartazesModal = document.getElementById('cartazesModal');
|
||||
const btnCloseCartazesModal = document.getElementById('btnCloseCartazesModal');
|
||||
|
||||
const tabCartazesCriar = document.getElementById('tabCartazesCriar');
|
||||
const tabCartazesHistorico = document.getElementById('tabCartazesHistorico');
|
||||
const panelCartazesCriar = document.getElementById('panelCartazesCriar');
|
||||
const panelCartazesHistorico = document.getElementById('panelCartazesHistorico');
|
||||
|
||||
const cartazTituloInput = document.getElementById('cartazTituloInput');
|
||||
const cartazTemaInput = document.getElementById('cartazTemaInput');
|
||||
const cartazLayoutSelect = document.getElementById('cartazLayoutSelect');
|
||||
const cartazBgColor = document.getElementById('cartazBgColor');
|
||||
const cartazTextColor = document.getElementById('cartazTextColor');
|
||||
const cartazFontSelect = document.getElementById('cartazFontSelect');
|
||||
const cartazBorderSelect = document.getElementById('cartazBorderSelect');
|
||||
const cartazPromptImagem = document.getElementById('cartazPromptImagem');
|
||||
const btnGenerateCartaz = document.getElementById('btnGenerateCartaz');
|
||||
const cartazGenerateLoader = document.getElementById('cartazGenerateLoader');
|
||||
|
||||
const btnEditCartazToggle = document.getElementById('btnEditCartazToggle');
|
||||
const btnPrintCartaz = document.getElementById('btnPrintCartaz');
|
||||
const cartazEditArea = document.getElementById('cartazEditArea');
|
||||
const cartazHtmlEditor = document.getElementById('cartazHtmlEditor');
|
||||
const btnUpdateCartazPreview = document.getElementById('btnUpdateCartazPreview');
|
||||
|
||||
const cartazPrintableArea = document.getElementById('cartazPrintableArea');
|
||||
const cartazPreviewTitle = document.getElementById('cartazPreviewTitle');
|
||||
const cartazPreviewContent = document.getElementById('cartazPreviewContent');
|
||||
const cartazPreviewImageContainer = document.getElementById('cartazPreviewImageContainer');
|
||||
const cartazPreviewImage = document.getElementById('cartazPreviewImage');
|
||||
|
||||
const cartazesHistoryContainer = document.getElementById('cartazesHistoryContainer');
|
||||
const cartazesNoData = document.getElementById('cartazesNoData');
|
||||
|
||||
let activeCartazData = null;
|
||||
|
||||
// Toggle Modal
|
||||
if (barBtnCartazes) {
|
||||
barBtnCartazes.addEventListener('click', () => {
|
||||
cartazesModal.style.display = 'flex';
|
||||
switchCartazTab('criar');
|
||||
});
|
||||
}
|
||||
|
||||
if (btnCloseCartazesModal) {
|
||||
btnCloseCartazesModal.addEventListener('click', () => {
|
||||
cartazesModal.style.display = 'none';
|
||||
});
|
||||
}
|
||||
|
||||
cartazesModal.addEventListener('click', (e) => {
|
||||
if (e.target === cartazesModal) cartazesModal.style.display = 'none';
|
||||
});
|
||||
|
||||
// Switch Tab
|
||||
function switchCartazTab(tab) {
|
||||
if (tab === 'criar') {
|
||||
tabCartazesCriar.classList.add('active');
|
||||
tabCartazesHistorico.classList.remove('active');
|
||||
panelCartazesCriar.style.display = 'flex';
|
||||
panelCartazesHistorico.style.display = 'none';
|
||||
|
||||
tabCartazesCriar.style.color = 'var(--text-primary)';
|
||||
tabCartazesHistorico.style.color = 'var(--text-secondary)';
|
||||
} else {
|
||||
tabCartazesCriar.classList.remove('active');
|
||||
tabCartazesHistorico.classList.add('active');
|
||||
panelCartazesCriar.style.display = 'none';
|
||||
panelCartazesHistorico.style.display = 'flex';
|
||||
|
||||
tabCartazesCriar.style.color = 'var(--text-secondary)';
|
||||
tabCartazesHistorico.style.color = 'var(--text-primary)';
|
||||
loadCartazesHistory();
|
||||
}
|
||||
}
|
||||
|
||||
if (tabCartazesCriar) tabCartazesCriar.addEventListener('click', () => switchCartazTab('criar'));
|
||||
if (tabCartazesHistorico) tabCartazesHistorico.addEventListener('click', () => switchCartazTab('historico'));
|
||||
|
||||
// Live Style Updates
|
||||
function updateLivePreview() {
|
||||
const bg = cartazBgColor.value;
|
||||
const text = cartazTextColor.value;
|
||||
const font = cartazFontSelect.value;
|
||||
const border = cartazBorderSelect.value;
|
||||
const title = cartazTituloInput.value.trim() || 'Título do Cartaz';
|
||||
|
||||
cartazPrintableArea.style.backgroundColor = bg;
|
||||
cartazPrintableArea.style.color = text;
|
||||
cartazPrintableArea.style.fontFamily = font;
|
||||
cartazPreviewTitle.textContent = title;
|
||||
cartazPreviewTitle.style.borderColor = text;
|
||||
|
||||
if (border === 'none') {
|
||||
cartazPrintableArea.style.border = 'none';
|
||||
} else {
|
||||
cartazPrintableArea.style.border = `${border} ${text}`;
|
||||
}
|
||||
}
|
||||
|
||||
[cartazBgColor, cartazTextColor, cartazFontSelect, cartazBorderSelect, cartazTituloInput].forEach(elem => {
|
||||
if (elem) elem.addEventListener('input', updateLivePreview);
|
||||
});
|
||||
|
||||
// Preset Palettes
|
||||
document.querySelectorAll('.btn-palette-preset').forEach(btn => {
|
||||
btn.addEventListener('click', (e) => {
|
||||
const bg = e.target.dataset.bg;
|
||||
const text = e.target.dataset.text;
|
||||
cartazBgColor.value = bg;
|
||||
cartazTextColor.value = text;
|
||||
updateLivePreview();
|
||||
});
|
||||
});
|
||||
|
||||
// Toggle HTML Editor
|
||||
if (btnEditCartazToggle) {
|
||||
btnEditCartazToggle.addEventListener('click', () => {
|
||||
if (cartazEditArea.style.display === 'none') {
|
||||
cartazEditArea.style.display = 'flex';
|
||||
btnEditCartazToggle.textContent = '👁️ Ocultar Editor';
|
||||
} else {
|
||||
cartazEditArea.style.display = 'none';
|
||||
btnEditCartazToggle.textContent = '✏️ Editar Conteúdo';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Update from Editor
|
||||
if (btnUpdateCartazPreview) {
|
||||
btnUpdateCartazPreview.addEventListener('click', () => {
|
||||
cartazPreviewContent.innerHTML = cartazHtmlEditor.value;
|
||||
});
|
||||
}
|
||||
|
||||
// Generate Poster
|
||||
if (btnGenerateCartaz) {
|
||||
btnGenerateCartaz.addEventListener('click', async () => {
|
||||
const titulo = cartazTituloInput.value.trim();
|
||||
const tema = cartazTemaInput.value.trim();
|
||||
const layout = cartazLayoutSelect.value;
|
||||
const promptImagem = cartazPromptImagem.value.trim();
|
||||
|
||||
if (!titulo || !tema) {
|
||||
showToast('Por favor, defina o Título e o Tema do Cartaz.', 'error');
|
||||
return;
|
||||
}
|
||||
|
||||
btnGenerateCartaz.disabled = true;
|
||||
cartazGenerateLoader.style.display = 'flex';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/cartazes/generate', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${localStorage.getItem('token')}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
titulo,
|
||||
tema,
|
||||
corFundo: cartazBgColor.value,
|
||||
corTexto: cartazTextColor.value,
|
||||
layout,
|
||||
promptImagem
|
||||
})
|
||||
});
|
||||
|
||||
const data = await res.json();
|
||||
if (res.ok) {
|
||||
activeCartazData = data;
|
||||
|
||||
// Preencher Preview
|
||||
cartazPreviewContent.innerHTML = data.conteudo;
|
||||
cartazHtmlEditor.value = data.conteudo;
|
||||
|
||||
if (data.imagemUrl) {
|
||||
cartazPreviewImage.src = data.imagemUrl;
|
||||
cartazPreviewImageContainer.style.display = 'block';
|
||||
} else {
|
||||
cartazPreviewImageContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
updateLivePreview();
|
||||
showToast('Cartaz criado e salvo com sucesso!', 'success');
|
||||
} else {
|
||||
showToast(data.error || 'Erro ao gerar cartaz.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
showToast('Erro ao se conectar com o servidor.', 'error');
|
||||
} finally {
|
||||
btnGenerateCartaz.disabled = false;
|
||||
cartazGenerateLoader.style.display = 'none';
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Print Poster
|
||||
if (btnPrintCartaz) {
|
||||
btnPrintCartaz.addEventListener('click', () => {
|
||||
const title = cartazTituloInput.value.trim() || 'Cartaz Pedagógico';
|
||||
const bg = cartazBgColor.value;
|
||||
const text = cartazTextColor.value;
|
||||
const font = cartazFontSelect.value;
|
||||
const border = cartazBorderSelect.value;
|
||||
const imgContainerHtml = cartazPreviewImageContainer.style.display === 'block'
|
||||
? `<div style="width: 100%; margin-bottom: 20px; border-radius: 8px; overflow: hidden; max-height: 350px; border: 2px solid rgba(0,0,0,0.1);"><img src="${cartazPreviewImage.src}" style="width: 100%; height: 100%; object-fit: cover;"></div>`
|
||||
: '';
|
||||
|
||||
const printWindow = window.open('', '_blank');
|
||||
printWindow.document.write(`
|
||||
<html>
|
||||
<head>
|
||||
<title>${title} - 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=Caveat:wght@400;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<style>
|
||||
body {
|
||||
font-family: ${font};
|
||||
margin: 0;
|
||||
padding: 40px;
|
||||
background-color: #f7fafc;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
min-height: 100vh;
|
||||
}
|
||||
.cartaz-frame {
|
||||
background-color: ${bg};
|
||||
color: ${text};
|
||||
padding: 50px;
|
||||
border-radius: 16px;
|
||||
width: 100%;
|
||||
max-width: 800px;
|
||||
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.05);
|
||||
box-sizing: border-box;
|
||||
border: ${border === 'none' ? 'none' : `${border} ${text}`};
|
||||
}
|
||||
h1 {
|
||||
text-align: center;
|
||||
margin-top: 0;
|
||||
font-size: 3rem;
|
||||
border-bottom: 3px solid ${text};
|
||||
padding-bottom: 16px;
|
||||
margin-bottom: 30px;
|
||||
}
|
||||
.content-area {
|
||||
font-size: 1.4rem;
|
||||
line-height: 1.8;
|
||||
}
|
||||
ul {
|
||||
margin-left: 20px;
|
||||
}
|
||||
li {
|
||||
margin-bottom: 12px;
|
||||
}
|
||||
@media print {
|
||||
body { background: transparent; padding: 0; }
|
||||
.cartaz-frame { box-shadow: none; border-radius: 0; max-width: 100%; width: 100%; height: 100%; padding: 30px; }
|
||||
button { display: none; }
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="cartaz-frame">
|
||||
<h1>${title}</h1>
|
||||
${imgContainerHtml}
|
||||
<div class="content-area">
|
||||
${cartazPreviewContent.innerHTML}
|
||||
</div>
|
||||
</div>
|
||||
<script>
|
||||
window.onload = function() {
|
||||
window.print();
|
||||
}
|
||||
<\/script>
|
||||
</body>
|
||||
</html>
|
||||
`);
|
||||
printWindow.document.close();
|
||||
});
|
||||
}
|
||||
|
||||
// Load History list
|
||||
async function loadCartazesHistory() {
|
||||
if (!cartazesHistoryContainer) return;
|
||||
cartazesHistoryContainer.innerHTML = '<div style="text-align: center; color: var(--text-secondary); padding: 20px;">Carregando histórico...</div>';
|
||||
|
||||
try {
|
||||
const res = await fetch('/api/cartazes/list', {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
});
|
||||
const data = await res.json();
|
||||
cartazesHistoryContainer.innerHTML = '';
|
||||
|
||||
if (!data || data.length === 0) {
|
||||
cartazesNoData.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
cartazesNoData.style.display = 'none';
|
||||
|
||||
data.forEach(item => {
|
||||
const card = document.createElement('div');
|
||||
card.style.background = 'var(--bg-tertiary)';
|
||||
card.style.border = '1px solid var(--border-light)';
|
||||
card.style.borderRadius = '10px';
|
||||
card.style.padding = '16px';
|
||||
card.style.display = 'flex';
|
||||
card.style.flexDirection = 'column';
|
||||
card.style.justifyContent = 'space-between';
|
||||
card.style.gap = '12px';
|
||||
card.style.boxShadow = '0 4px 6px rgba(0,0,0,0.1)';
|
||||
|
||||
const thumbnail = item.imagemUrl
|
||||
? `<div style="height: 120px; overflow:hidden; border-radius: 6px; border: 1px solid var(--border-light); margin-bottom: 6px;">
|
||||
<img src="${item.imagemUrl}" style="width:100%; height:100%; object-fit:cover;">
|
||||
</div>`
|
||||
: `<div style="height: 120px; border-radius: 6px; background: ${item.corFundo || '#fff9eb'}; color: ${item.corTexto || '#2d3748'}; display:flex; align-items:center; justify-content:center; border: 1px solid var(--border-light); margin-bottom: 6px; font-weight: bold; font-size: 0.8rem; overflow: hidden; padding: 10px; text-align:center;">
|
||||
${item.titulo}
|
||||
</div>`;
|
||||
|
||||
card.innerHTML = `
|
||||
<div>
|
||||
${thumbnail}
|
||||
<h5 style="margin: 0; font-size: 1rem; color: var(--text-primary); font-family: 'Outfit', sans-serif;">${item.titulo}</h5>
|
||||
<p style="margin: 4px 0 0 0; font-size: 0.8rem; color: var(--text-secondary);">Foco: ${item.tema}</p>
|
||||
<span style="font-size: 0.75rem; color: var(--text-muted); display: block; margin-top: 6px;">Layout: ${item.layout.toUpperCase()}</span>
|
||||
</div>
|
||||
<div style="display: flex; gap: 8px; justify-content: flex-end; border-top: 1px solid var(--border-light); padding-top: 10px;">
|
||||
<button class="btn-ver-cartaz btn-secondary" data-id="${item.id}" style="padding: 6px 12px; font-size: 0.8rem; border-radius: 6px; cursor: pointer; background: rgba(59, 130, 246, 0.1); border-color: rgba(59, 130, 246, 0.3); color: #60a5fa;">Ver / Editar</button>
|
||||
<button class="btn-del-cartaz btn-secondary" data-id="${item.id}" style="padding: 6px 12px; font-size: 0.8rem; border-radius: 6px; cursor: pointer; color: #ef4444; border-color: rgba(239, 68, 68, 0.3);">🗑️</button>
|
||||
</div>
|
||||
`;
|
||||
cartazesHistoryContainer.appendChild(card);
|
||||
});
|
||||
|
||||
// Bind Ver/Editar Buttons
|
||||
document.querySelectorAll('.btn-ver-cartaz').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
const id = e.target.dataset.id;
|
||||
try {
|
||||
const r = await fetch(`/api/cartazes/${id}`, {
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
});
|
||||
const poster = await r.json();
|
||||
|
||||
// Set Inputs
|
||||
cartazTituloInput.value = poster.titulo;
|
||||
cartazTemaInput.value = poster.tema;
|
||||
cartazLayoutSelect.value = poster.layout;
|
||||
cartazBgColor.value = poster.corFundo;
|
||||
cartazTextColor.value = poster.corTexto;
|
||||
cartazHtmlEditor.value = poster.conteudo;
|
||||
cartazPreviewContent.innerHTML = poster.conteudo;
|
||||
|
||||
if (poster.imagemUrl) {
|
||||
cartazPreviewImage.src = poster.imagemUrl;
|
||||
cartazPreviewImageContainer.style.display = 'block';
|
||||
} else {
|
||||
cartazPreviewImageContainer.style.display = 'none';
|
||||
}
|
||||
|
||||
updateLivePreview();
|
||||
switchCartazTab('criar');
|
||||
} catch (err) {
|
||||
showToast('Erro ao carregar cartaz.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Bind Delete Buttons
|
||||
document.querySelectorAll('.btn-del-cartaz').forEach(btn => {
|
||||
btn.addEventListener('click', async (e) => {
|
||||
if (!confirm('Tem certeza que deseja apagar este cartaz?')) return;
|
||||
const id = e.target.dataset.id;
|
||||
try {
|
||||
const r = await fetch(`/api/cartazes/${id}`, {
|
||||
method: 'DELETE',
|
||||
headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
|
||||
});
|
||||
if (r.ok) {
|
||||
showToast('Cartaz deletado com sucesso.', 'success');
|
||||
loadCartazesHistory();
|
||||
} else {
|
||||
showToast('Erro ao deletar cartaz.', 'error');
|
||||
}
|
||||
} catch (err) {
|
||||
showToast('Erro de conexão ao deletar cartaz.', 'error');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error(err);
|
||||
cartazesHistoryContainer.innerHTML = '<div style="color: #ef4444; padding: 20px;">Erro ao carregar histórico de cartazes.</div>';
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
+160
-1
@@ -16,7 +16,7 @@
|
||||
<!-- Google Fonts Outfit & Inter -->
|
||||
<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=Inter:wght@300;400;500;600&family=Outfit:wght@400;600;700&display=swap" rel="stylesheet">
|
||||
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600&family=Outfit:wght@400;600;700&family=Fredoka:wght@400;600;700&family=Caveat:wght@400;700&family=Playfair+Display:ital,wght@0,400;0,700;1,400&display=swap" rel="stylesheet">
|
||||
<!-- Highlight.js para destaque de sintaxe em códigos -->
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/styles/github-dark.min.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
@@ -278,6 +278,9 @@
|
||||
<button type="button" class="btn-prompt-mode mode-text" id="barBtnHistoria" title="Escrever histórias, relatórios ou conversar">
|
||||
<span class="mode-icon">✍️</span> Inventando Historias
|
||||
</button>
|
||||
<button type="button" class="btn-prompt-mode mode-comics" id="barBtnCartazes" style="border-color: rgba(16, 185, 129, 0.4); color: #34d399; background: rgba(16, 185, 129, 0.05);" title="Criar e armazenar cartazes para sala de aula">
|
||||
<span class="mode-icon">🖼️</span> Criar Cartazes
|
||||
</button>
|
||||
</div>
|
||||
<form id="chatForm">
|
||||
<div class="input-box">
|
||||
@@ -1683,6 +1686,162 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- ========================================== -->
|
||||
<!-- MODAL: CRIAR CARTAZES -->
|
||||
<!-- ========================================== -->
|
||||
<div id="cartazesModal" class="settings-modal" style="display: none;">
|
||||
<div class="settings-modal-content" style="max-width: 1100px; width: 95%; max-height: 95vh; 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 Cartazes PedaGog</h3>
|
||||
<div style="display: flex; gap: 10px;">
|
||||
<button id="btnCloseCartazesModal" class="btn-close-modal" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; font-size: 1.5rem;">×</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Abas do Modal -->
|
||||
<div style="padding: 12px 20px; display: flex; gap: 15px; border-bottom: 1px solid var(--border-light); background: var(--bg-primary);">
|
||||
<button id="tabCartazesCriar" class="tab-btn active" style="background: transparent; border: none; color: var(--text-primary); font-weight: 600; padding: 6px 12px; border-radius: 6px; cursor: pointer;">✨ Criar Cartaz</button>
|
||||
<button id="tabCartazesHistorico" class="tab-btn" style="background: transparent; border: none; color: var(--text-secondary); font-weight: 600; padding: 6px 12px; border-radius: 6px; cursor: pointer;">🕒 Seus Cartazes Salvos</button>
|
||||
</div>
|
||||
|
||||
<div class="settings-modal-body" style="padding: 20px; overflow-y: auto; flex: 1; display: flex; flex-direction: column;">
|
||||
|
||||
<!-- Conteúdo 1: Criador -->
|
||||
<div id="panelCartazesCriar" style="display: flex; flex-direction: row; gap: 24px; height: 100%; flex-wrap: wrap;">
|
||||
|
||||
<!-- Lado Esquerdo: Configurações -->
|
||||
<div style="flex: 1; min-width: 320px; display: flex; flex-direction: column; gap: 16px;">
|
||||
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 8px;">1. Definições do Cartaz</h4>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 600; text-transform: uppercase;">Título do Cartaz</label>
|
||||
<input type="text" id="cartazTituloInput" placeholder="Ex: Regras da Nossa Sala" class="obs-input" style="width: 100%; margin-top: 4px; padding: 10px;">
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 600; text-transform: uppercase;">Tema / Objetivo Pedagógico</label>
|
||||
<input type="text" id="cartazTemaInput" placeholder="Ex: Combinados positivos de convivência" class="obs-input" style="width: 100%; margin-top: 4px; padding: 10px;">
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 600; text-transform: uppercase;">Layout Visual</label>
|
||||
<select id="cartazLayoutSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="regras">📜 Combinados / Regras (Cards Individuais)</option>
|
||||
<option value="mural">📌 Mural de Avisos (Quadro Informativo)</option>
|
||||
<option value="calendario">📅 Calendário e Clima (Rotina Semanal)</option>
|
||||
<option value="livre">🎨 Livre / Cartaz Temático Customizado</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<h4 style="margin: 0; color: var(--text-primary); border-bottom: 1px dashed var(--border-light); padding-bottom: 8px; margin-top: 8px;">2. Estilo e Cores</h4>
|
||||
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Cor de Fundo</label>
|
||||
<input type="color" id="cartazBgColor" value="#fff9eb" style="width: 100%; height: 38px; padding: 2px; border: 1px solid var(--border-light); border-radius: 6px; cursor: pointer; margin-top: 4px;">
|
||||
</div>
|
||||
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Cor do Texto</label>
|
||||
<input type="color" id="cartazTextColor" value="#2d3748" style="width: 100%; height: 38px; padding: 2px; border: 1px solid var(--border-light); border-radius: 6px; cursor: pointer; margin-top: 4px;">
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 600; text-transform: uppercase;">Paletas Rápidas</label>
|
||||
<div style="display: flex; gap: 8px; margin-top: 6px; flex-wrap: wrap;">
|
||||
<button type="button" class="btn-palette-preset" data-bg="#fff9eb" data-text="#2d3748" style="background: #fff9eb; color: #2d3748; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.7rem; cursor: pointer; font-weight: bold;">Tons Pastéis</button>
|
||||
<button type="button" class="btn-palette-preset" data-bg="#e6fffa" data-text="#004d40" style="background: #e6fffa; color: #004d40; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.7rem; cursor: pointer; font-weight: bold;">Jardim Fresco</button>
|
||||
<button type="button" class="btn-palette-preset" data-bg="#ebf8ff" data-text="#2b6cb0" style="background: #ebf8ff; color: #2b6cb0; padding: 6px; border: 1px solid #ddd; border-radius: 4px; font-size: 0.7rem; cursor: pointer; font-weight: bold;">Céu Azul</button>
|
||||
<button type="button" class="btn-palette-preset" data-bg="#1a202c" data-text="#f7fafc" style="background: #1a202c; color: #f7fafc; padding: 6px; border: 1px solid #444; border-radius: 4px; font-size: 0.7rem; cursor: pointer; font-weight: bold;">Lousa Escura</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display: flex; gap: 12px; flex-wrap: wrap;">
|
||||
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Fonte do Cartaz</label>
|
||||
<select id="cartazFontSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="'Fredoka', sans-serif" selected>🎈 Fredoka (Fofa/Infantil)</option>
|
||||
<option value="'Caveat', cursive">✍️ Caveat (Manuscrita)</option>
|
||||
<option value="'Playfair Display', serif">📖 Playfair (Clássica)</option>
|
||||
<option value="'Outfit', sans-serif">⚡ Outfit (Moderna)</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="settings-group" style="flex: 1; min-width: 140px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.75rem; font-weight: 600; text-transform: uppercase;">Estilo de Borda</label>
|
||||
<select id="cartazBorderSelect" class="obs-select" style="width: 100%; margin-top: 4px;">
|
||||
<option value="none">Nenhuma</option>
|
||||
<option value="8px dashed">Pontilhada Colorida</option>
|
||||
<option value="8px double">Linha Dupla Clássica</option>
|
||||
<option value="12px solid">Borda Larga Colorida</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="settings-group">
|
||||
<label style="color: var(--text-secondary); font-size: 0.8rem; font-weight: 600; text-transform: uppercase;">Ilustração por IA (Opcional)</label>
|
||||
<textarea id="cartazPromptImagem" placeholder="Ex: Desenho fofo de brinquedos sorridentes na caixa de brinquedos, aquarela, cores suaves..." class="obs-input" style="width: 100%; min-height: 60px; padding: 8px; margin-top: 4px; font-size: 0.85rem; resize: vertical;"></textarea>
|
||||
</div>
|
||||
|
||||
<button type="button" id="btnGenerateCartaz" style="width: 100%; padding: 12px; background: #10b981; color: white; 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(16, 185, 129, 0.3); transition: all 0.2s; margin-top: 8px;">
|
||||
<span>🪄 Criar Cartaz Pedagógico</span>
|
||||
</button>
|
||||
|
||||
<div id="cartazGenerateLoader" 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>
|
||||
<span id="cartazLoaderText">Montando e diagramando cartaz...</span>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Lado Direito: Preview & Editor de Código -->
|
||||
<div 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: 8px;">
|
||||
<h4 style="margin: 0; color: var(--text-primary);">👁️ Visualização do Cartaz</h4>
|
||||
<div style="display: flex; gap: 8px;">
|
||||
<button id="btnEditCartazToggle" class="btn-save-settings" style="background: transparent; border: 1px solid var(--border-light); color: var(--text-secondary); padding: 5px 10px; font-size: 0.75rem;">✏️ Editar Conteúdo</button>
|
||||
<button id="btnPrintCartaz" class="btn-save-settings" style="background: #3b82f6; border: none; color: white; padding: 5px 12px; font-size: 0.75rem; font-weight: bold; border-radius: 6px;">🖨️ Imprimir Cartaz</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Área de Edição do Conteúdo (Inicialmente Oculta) -->
|
||||
<div id="cartazEditArea" style="display: none; flex-direction: column; gap: 8px;">
|
||||
<label style="color: var(--text-secondary); font-size: 0.78rem; font-weight: 600; text-transform: uppercase;">Editor HTML do Cartaz</label>
|
||||
<textarea id="cartazHtmlEditor" class="obs-input" style="width: 100%; min-height: 120px; font-family: monospace; font-size: 0.85rem; padding: 10px;"></textarea>
|
||||
<button type="button" id="btnUpdateCartazPreview" style="padding: 6px 12px; align-self: flex-end; background: var(--border-light); border: 1px solid var(--border-light); border-radius: 6px; cursor: pointer; color: var(--text-primary); font-size: 0.8rem;">Atualizar Preview</button>
|
||||
</div>
|
||||
|
||||
<!-- Caixa de Preview Imprimível -->
|
||||
<div style="background: #2d3748; padding: 12px; border-radius: 12px; border: 1px solid var(--border-light); overflow: auto; display: flex; justify-content: center; align-items: center; flex: 1; min-height: 400px;">
|
||||
<div id="cartazPrintableArea" style="background: #fff9eb; color: #2d3748; padding: 30px; border-radius: 12px; width: 100%; max-width: 500px; box-sizing: border-box; box-shadow: 0 10px 25px rgba(0,0,0,0.3); transition: all 0.3s; min-height: 600px; display: flex; flex-direction: column; font-family: 'Fredoka', sans-serif;">
|
||||
<h1 id="cartazPreviewTitle" style="text-align: center; margin-top: 0; font-size: 2.2rem; border-bottom: 2px solid; padding-bottom: 12px; margin-bottom: 20px;">Título do Cartaz</h1>
|
||||
|
||||
<!-- Área da Imagem IA no Cartaz -->
|
||||
<div id="cartazPreviewImageContainer" style="display: none; width: 100%; margin-bottom: 20px; border-radius: 8px; overflow: hidden; max-height: 250px; border: 2px solid rgba(0,0,0,0.1);">
|
||||
<img id="cartazPreviewImage" src="" style="width: 100%; height: 100%; object-fit: cover;">
|
||||
</div>
|
||||
|
||||
<!-- Conteúdo de Texto do Cartaz -->
|
||||
<div id="cartazPreviewContent" style="font-size: 1.1rem; line-height: 1.6; flex: 1;">
|
||||
<p style="text-align: center; color: #718096; font-style: italic;">Clique em "Criar Cartaz Pedagógico" para que a assistente monte e estruture o cartaz ideal para você.</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Conteúdo 2: Histórico -->
|
||||
<div id="panelCartazesHistorico" style="display: none; flex-direction: column; gap: 16px;">
|
||||
<div id="cartazesHistoryContainer" style="display: grid; grid-template-columns: repeat(auto-fill, minmax(280px, 1fr)); gap: 16px;">
|
||||
<!-- Preenchido via JS -->
|
||||
</div>
|
||||
<div id="cartazesNoData" style="text-align: center; color: var(--text-muted); font-style: italic; padding: 40px 0;">
|
||||
Nenhum cartaz gerado ainda. Crie um acima!
|
||||
</div>
|
||||
</div>
|
||||
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bibliotecas externas para renderizar markdown e blocos de código -->
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||
|
||||
@@ -1335,6 +1335,207 @@ app.delete('/api/poesias/:id', requireAuth, async (req, res) => {
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// ROTAS DO CRIAR CARTAZES
|
||||
// ============================================================
|
||||
app.post('/api/cartazes/generate', requireAuth, async (req, res) => {
|
||||
const { titulo, tema, corFundo, corTexto, layout, promptImagem } = req.body;
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
|
||||
if (!titulo || !tema || !layout) {
|
||||
return res.status(400).json({ error: 'Título, tema e layout são obrigatórios.' });
|
||||
}
|
||||
|
||||
try {
|
||||
const agentConfig = readAgentConfig();
|
||||
const agentName = agentConfig.agentName || "Kemily";
|
||||
|
||||
// 1. Gerar Conteúdo do Cartaz
|
||||
const posterPrompt = `Você é a ${agentName}, assistente pedagógica da professora Camila Martella Gasparini Reifonas.
|
||||
A professora Camila quer criar um Cartaz para sala de aula da Educação Infantil.
|
||||
|
||||
Detalhes do Cartaz:
|
||||
- Título: "${titulo}"
|
||||
- Tema / Foco Pedagógico: "${tema}"
|
||||
- Layout Selecionado: "${layout}" (Opções: regras, mural, calendario, livre)
|
||||
|
||||
Por favor, gere o CONTEÚDO textual estruturado do cartaz.
|
||||
REGRAS:
|
||||
- Deve ser acolhedor, lúdico e muito bem estruturado.
|
||||
- Use emojis apropriados para crianças.
|
||||
- Se for layout "regras", crie uma lista de combinados da sala (max 5) de forma positiva (ex: "Cuidar dos nossos brinquedos" ao invés de "Não quebrar os brinquedos").
|
||||
- Se for "calendario", estruture áreas para o dia da semana, ajudante do dia e tempo (ensolarado, chuvoso).
|
||||
- Se for "mural", crie seções de recados e novidades da semana.
|
||||
- Se for "livre", crie um texto poético ou explicativo baseado no tema.
|
||||
- Mantenha os textos curtos, visuais e fáceis de ler para crianças ou pais.
|
||||
- Retorne apenas o conteúdo em formato HTML simples (use tags como <h1>, <p>, <ul>, <li>, <strong>, <span> e estilos inline simples se necessário) para renderizarmos no preview do cartaz. Não adicione tags <html> ou <body>, apenas o bloco de conteúdo interno.`;
|
||||
|
||||
let conteudoHtml = '';
|
||||
|
||||
// Tentar MiniMax M3
|
||||
try {
|
||||
console.log('[Cartazes] Gerando conteúdo com MiniMax...');
|
||||
const result = await callMinimax({
|
||||
messages: [{ role: 'user', content: posterPrompt }],
|
||||
temperature: 0.7,
|
||||
max_tokens: 1500
|
||||
});
|
||||
conteudoHtml = result.text;
|
||||
} catch (err) {
|
||||
console.error('[Cartazes] Erro MiniMax:', err.message);
|
||||
}
|
||||
|
||||
// Fallback OpenRouter
|
||||
if (!conteudoHtml && process.env.OPENROUTER_API_KEY) {
|
||||
try {
|
||||
console.log('[Cartazes] 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: posterPrompt }],
|
||||
temperature: 0.7,
|
||||
max_tokens: 1500
|
||||
})
|
||||
});
|
||||
if (r.ok) {
|
||||
const data = await r.json();
|
||||
conteudoHtml = data.choices?.[0]?.message?.content || '';
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('[Cartazes] Erro OpenRouter:', err.message);
|
||||
}
|
||||
}
|
||||
|
||||
if (!conteudoHtml) {
|
||||
conteudoHtml = `<h2>${titulo}</h2><p>Tema: ${tema}</p><p>Não foi possível gerar com IA. Edite o conteúdo livremente.</p>`;
|
||||
}
|
||||
|
||||
// Clean HTML from backticks
|
||||
conteudoHtml = conteudoHtml.replace(/```html|```/g, '').trim();
|
||||
|
||||
// 2. Gerar Imagem Ilustrativa se solicitado
|
||||
let generatedImageUrl = null;
|
||||
if (promptImagem && promptImagem.trim().length > 3) {
|
||||
try {
|
||||
console.log('[Cartazes] Gerando ilustração para o cartaz...');
|
||||
const minmBase = (process.env.MINIMAX_API_BASE || 'https://api.minimax.io/v1').replace(/\/v1\/?$/, '');
|
||||
const genResp = await fetch(`${minmBase}/v1/image_generation`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Authorization': `Bearer ${process.env.MINIMAX_API_KEY}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify({
|
||||
model: 'image-01',
|
||||
prompt: `${promptImagem}. Estilo ilustração de livro infantil de alta qualidade, 3D Pixar, cores suaves, traço limpo, sem texto.`,
|
||||
n: 1
|
||||
})
|
||||
});
|
||||
|
||||
if (genResp.ok) {
|
||||
const genData = await genResp.json();
|
||||
const rawImgUrl = genData.data?.image_urls?.[0];
|
||||
if (rawImgUrl) {
|
||||
const mediaDir = path.join(__dirname, 'public', 'generated-media');
|
||||
if (!fs.existsSync(mediaDir)) fs.mkdirSync(mediaDir, { recursive: true });
|
||||
|
||||
const imgFetch = await fetch(rawImgUrl);
|
||||
if (imgFetch.ok) {
|
||||
const imgBuffer = Buffer.from(await imgFetch.arrayBuffer());
|
||||
const fileName = `cartaz_${Date.now()}_${Math.floor(Math.random() * 1000)}.jpg`;
|
||||
fs.writeFileSync(path.join(mediaDir, fileName), imgBuffer);
|
||||
generatedImageUrl = `/generated-media/${fileName}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (imgErr) {
|
||||
console.error('[Cartazes] Erro na geração de imagem:', imgErr);
|
||||
}
|
||||
}
|
||||
|
||||
// 3. Salvar no Banco
|
||||
const dbRes = await dbPool.query(
|
||||
`INSERT INTO escola.cartazes (usuario_id, titulo, tema, cor_fundo, cor_texto, layout, conteudo, imagem_url)
|
||||
VALUES ($1, $2, $3, $4, $5, $6, $7, $8) RETURNING id, created_at`,
|
||||
[usuarioId, titulo, tema, corFundo || '#ffffff', corTexto || '#1e293b', layout, conteudoHtml, generatedImageUrl]
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
id: dbRes.rows[0].id,
|
||||
titulo,
|
||||
tema,
|
||||
corFundo,
|
||||
corTexto,
|
||||
layout,
|
||||
conteudo: conteudoHtml,
|
||||
imagemUrl: generatedImageUrl,
|
||||
createdAt: dbRes.rows[0].created_at
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
console.error('Erro ao criar cartaz:', err);
|
||||
res.status(500).json({ error: 'Erro ao criar cartaz: ' + err.message });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/cartazes/list', requireAuth, async (req, res) => {
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(
|
||||
`SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, imagem_url as "imagemUrl", created_at as "createdAt"
|
||||
FROM escola.cartazes WHERE usuario_id = $1 ORDER BY created_at DESC`,
|
||||
[usuarioId]
|
||||
);
|
||||
res.json(dbRes.rows);
|
||||
} catch (err) {
|
||||
console.error('Erro ao listar cartazes:', err);
|
||||
res.status(500).json({ error: 'Erro ao listar cartazes.' });
|
||||
}
|
||||
});
|
||||
|
||||
app.get('/api/cartazes/:id', requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(
|
||||
`SELECT id, titulo, tema, cor_fundo as "corFundo", cor_texto as "corTexto", layout, conteudo, imagem_url as "imagemUrl", created_at as "createdAt"
|
||||
FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`,
|
||||
[id, usuarioId]
|
||||
);
|
||||
if (dbRes.rows.length === 0) {
|
||||
return res.status(404).json({ error: 'Cartaz não encontrado.' });
|
||||
}
|
||||
res.json(dbRes.rows[0]);
|
||||
} catch (err) {
|
||||
console.error('Erro ao buscar cartaz:', err);
|
||||
res.status(500).json({ error: 'Erro ao buscar cartaz.' });
|
||||
}
|
||||
});
|
||||
|
||||
app.delete('/api/cartazes/:id', requireAuth, async (req, res) => {
|
||||
const { id } = req.params;
|
||||
const usuarioId = req.user?.id || '00000000-0000-0000-0000-000000000000';
|
||||
try {
|
||||
const dbRes = await dbPool.query(
|
||||
`DELETE FROM escola.cartazes WHERE id = $1 AND usuario_id = $2`,
|
||||
[id, usuarioId]
|
||||
);
|
||||
if (dbRes.rowCount === 0) {
|
||||
return res.status(404).json({ error: 'Cartaz não encontrado ou não pertence ao usuário.' });
|
||||
}
|
||||
res.json({ success: true, message: 'Cartaz excluído com sucesso.' });
|
||||
} catch (err) {
|
||||
console.error('Erro ao deletar cartaz:', err);
|
||||
res.status(500).json({ error: 'Erro ao deletar cartaz.' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// ROTAS DO MUSICANDO IDEIAS
|
||||
// ============================================================
|
||||
@@ -1848,6 +2049,20 @@ async function initDatabase() {
|
||||
historia TEXT NOT NULL,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
|
||||
-- Cartazes (Criar Cartazes)
|
||||
CREATE TABLE IF NOT EXISTS escola.cartazes (
|
||||
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
|
||||
usuario_id UUID NOT NULL DEFAULT '00000000-0000-0000-0000-000000000000',
|
||||
titulo VARCHAR(255) NOT NULL,
|
||||
tema VARCHAR(255) NOT NULL,
|
||||
cor_fundo VARCHAR(50) NOT NULL,
|
||||
cor_texto VARCHAR(50) NOT NULL,
|
||||
layout VARCHAR(50) NOT NULL, -- 'mural', 'regras', 'calendario', 'livre'
|
||||
conteudo TEXT NOT NULL,
|
||||
imagem_url TEXT,
|
||||
created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW()
|
||||
);
|
||||
`);
|
||||
|
||||
// Garantir que a coluna character_description_english exista na tabela projetos_comics
|
||||
|
||||
Reference in New Issue
Block a user