🚀 Auto-deploy: Camila atualizado em 06/06/2026 21:07:19

This commit is contained in:
2026-06-06 21:07:19 +00:00
parent 8ce48af127
commit 852713db00
3 changed files with 780 additions and 1 deletions
+215
View File
@@ -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