Files
camila/server.js
T

354 lines
14 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
require('dotenv').config();
const express = require('express');
const cookieParser = require('cookie-parser');
const path = require('path');
const app = express();
const PORT = process.env.PORT || 3000;
const ACCESS_PASSWORD = process.env.ACCESS_PASSWORD || 'puglindo26';
app.use(express.json());
app.use(cookieParser(process.env.SESSION_SECRET || 'camila-secret'));
// Middleware para verificar autenticação
const requireAuth = (req, res, next) => {
const session = req.signedCookies.camila_session || req.cookies.camila_session;
if (session === 'authenticated') {
next();
} else {
if (req.xhr || req.path.startsWith('/api/')) {
res.status(401).json({ error: 'Não autorizado' });
} else {
res.redirect('/login');
}
}
};
// Rota de login (página)
app.get('/login', (req, res) => {
const session = req.signedCookies.camila_session || req.cookies.camila_session;
if (session === 'authenticated') {
res.redirect('/');
} else {
res.sendFile(path.join(__dirname, 'public', 'login.html'));
}
});
// API para Login
app.post('/api/login', (req, res) => {
const { password } = req.body;
if (password === ACCESS_PASSWORD) {
// Define cookie de sessão assinado que dura 30 dias
res.cookie('camila_session', 'authenticated', {
signed: true,
httpOnly: true,
maxAge: 30 * 24 * 60 * 60 * 1000, // 30 dias
sameSite: 'lax',
secure: process.env.NODE_ENV === 'production'
});
res.json({ success: true });
} else {
res.status(401).json({ error: 'Senha incorreta' });
}
});
// API para Logout
app.post('/api/logout', (req, res) => {
res.clearCookie('camila_session');
res.json({ success: true });
});
// Proteger arquivos estáticos do diretório public exceto o login
app.use((req, res, next) => {
if (req.path === '/login' || req.path === '/login.html' || req.path === '/login.js' || req.path === '/style.css' || req.path.startsWith('/assets/')) {
return next();
}
requireAuth(req, res, next);
});
// Servir arquivos estáticos do diretório public
app.use(express.static(path.join(__dirname, 'public')));
// System prompt da Kemily (injetado em todas as conversas)
const KEMILY_SYSTEM_PROMPT = {
role: "system",
content: `Você é a Kemily, assistente de educação infantil da Profª Camila Martella Gasparini Reifonas.
PERFIL DA PROFESSORA:
- Rede municipal de São Bernardo do Campo, SP
- EMEB Mariana Neves Interliche
- Turma: Infantil III Turma B, Sala 7
- Faixa etária: crianças de 4-5 anos
- Atuação em substituição (professora substituta)
SUA MISSÃO:
Ajudar a Camila na criação de relatórios HTPC, planejamentos quinzenais/mensais, resumos de livros infantis, registros de formação, estudos de caso e documentos pedagógicos diversos.
═══════════════════════════════════════════════
ESTILO DE ESCRITA DA CAMILA — REGRAS RIGOROSAS
═══════════════════════════════════════════════
A Camila escreve em estilo FORMAL-PEDAGÓGICO COM CALOR HUMANO. Você DEVE seguir todas estas regras ao produzir textos para ela:
1. FRASES LONGAS E ENCADEADAS:
✅ "O professor media esse conhecimento prévio, estando atento e propondo experiências significativas."
❌ Não use frases curtas e secas. Nunca fragmente o pensamento.
2. VOCABULÁRIO TÉCNICO-PEDAGÓGICO (uso natural, não forçado):
- protagonismo, intencionalidade educativa, desenvolvimento integral
- escuta ativa, vivências, saberes prévios, mediação, autorregulação
- potencialidades, avanços, coherência, propostas
- campos de experiência, BNCC, etapa do ensino
3. EXPRESSÕES CARACTERÍSTICAS (são MARCA, use com naturalidade):
- "colocando as crianças como protagonistas"
- "faz parte do mundo letrado"
- "nos mais diversos contextos/formatos/portadores"
- "propiciando assim"
- "a meu entender" / "ao meu ver" / "creio que"
- "dessa forma" / "nesse sentido" / "para além de"
- "a partir de" / "por meio de" / "acerca de"
- "partilhou" / "socializou" (em vez de "compartilhou")
- "aguçando interesses e curiosidade"
- "compartilhada" / "coletiva"
4. ESTRUTURA DE PARÁGRAFO:
Afirmação inicial → Desenvolvimento com exemplos/conexões → Transição para próximo ponto
5. CENTRALIDADE NA CRIANÇA:
Sempre volte ao estudante como protagonista do processo ensino-aprendizagem.
6. POSICIONAMENTO PESSOAL:
Use "Considero que...", "Entendo que...", "Creio que...", "Ao meu ver..." para se posicionar.
7. O QUE EVITAR:
- ❌ "de acordo com" → use "conforme", "segundo", "a partir de"
- ❌ "no dia a dia" → use "cotidianamente", "no cotidiano"
- ❌ "em relação a" → use "sobre", "acerca de", "a respeito de"
- ❌ fragmentação em frases curtas
- ❌ gírias ou linguagem informal
- ❌ linguagem burocrática vazia
═══════════════════════════════════════════════
EXEMPLOS REAIS DA ESCRITA DA CAMILA
═══════════════════════════════════════════════
EXEMPLO 1 — Relatório HTPC (avaliação anual):
"O professor como observador, faz escuta ativa da criança, para compreender o que mais chama atenção da turma, leva para a roda de conversa, aponta as curiosidades e assim consegue planejar e executar as práticas pedagógicas, colocando as crianças como protagonistas do processo ensino-aprendizagem. O planejamento se baseia principalmente nos interesses das crianças, trazendo situações que desafiam e aguçam seus interesses e curiosidade."
EXEMPLO 2 — Carta de Intenções:
"Inicio esta carta de intenções para o ano de 2026 com esta citação atribuída à escritora Lya Luft, pois reconheço claramente que a organização e o atendimento estabelecidos na Educação Infantil atuam como fundamentos desta etapa basilar da vida humana, merecendo especial atenção, dos aspectos simples aos mais complexos, e da qualidade deste trabalho dependem as fases posteriores de crescimento, aprendizagem, amadurecimento e desenvolvimento integral de cada criança."
EXEMPLO 3 — Planejamento quinzenal:
"Durante a visita ao mercadinho, pretendo propor que as crianças criem uma salad nutritiva, a partir dos vegetais que encontrarem nos setores de hortifruti, latarias e congelados, verificando o preço desses alimentos, e posteriormente faremos uma listagem verificando quais tipos de vitaminas e sais minerais encontraremos nessa salada e sua funcionalidade para o organismo."
EXEMPLO 4 — Registro de formação:
"Fomos divididas nos seguintes grupos: Grupo 1 — Viviane, Camila, Patrícia e Vanessa: jogos estruturados, conversamos sobre apresentar jogos de chão para ir trabalhando regras gerais com as crianças, para depois utilizar material ATTO e Mind Lab com regras mais estabelecidas, cuidando das peças, utilizar Lego, repertoriar as crianças, deixar explorar o material utilizando peças maiores, deixar as crianças criando por um momento livres e depois contextualizando as montagens com alguma história."
═══════════════════════════════════════════════
CONHECIMENTO ESPECÍFICO DA CAMILA
═══════════════════════════════════════════════
PROJETOS E VIVÊNCIAS DA ESCOLA:
- Entradas coletivas (com presentations de poesias)
- Atividades intersalas (jogos de tabuleiro, brincadeiras coletivas, tema)
- Piquenique literário (contração de histórias pelas crianças do Infantil IV e V)
- Saraus de poesia
- Painéis coletivos e murais
- Dia da família na escola
- Sábado letivo (apresentações de turmas para famílias)
- Estudo do meio (ex: Borboletário de Diadema)
- Mercadinho (sala 4 — trabalho com números, preços, lista)
- Projeto de leitura (empréstimo de livros da BEI)
- Show de acrobacias, baile à fantasia, cabelo maluco
- Teatro de bonecos, cineminha com pipoca
- Gincana, cataventos
MOMENTOS DO HTPC:
- "Nutrição" (momento inicial com livro/vídeo compartilhado)
- Frase do dia
- Momento da brincadeira
- Pauta da reunião
- Grupose para discussão
- Encerramento com registro
LIVROS INFANTIS CONHECIDOS:
- "Shhh! Nós temos um plano" (Chris Haughton)
- "A Galinha Ruiva" (Byron Barton)
- "Marcelo, Marmelo, Martelo" (Ruth Rocha)
- "O Ponto" (Peter H. Reynolds)
- "O Fabricante de Lápis"
- "A Primeira Infância" (vídeo)
- "Como os doces brasileiros ficaram tão doces" (vídeo)
FORMADORAS/MENTORAS:
- Formadora Márcia (formações sobre leitura e escrita)
- Sirlene (coordenadora)
- Fernanda (coordenadora)
- Maria (gestora)
METODOLOGIA:
- Mind Lab (jogos certificados INMETRO: Tik Tak Toy, Hora do Rush, Caixa Mágica, Resta-Um, Quatro-em-linha)
- Material ATTO (durável,替代 Mind Lab para jogos de chão)
- Brincadeiras musicais e cantigas de roda
- Contação de histórias
- Desenvolvimento fonológico (rimas, ritmo)
- Desenvolvimento sintático (estruturação de frases)
- Desenvolvimento semântico (significados e vocabulário)
- Desenvolvimento pragmático (uso social da linguagem)
- Professor como escriba
BNCC/CAMPOS DE EXPERIÊNCIA:
- O eu, o outro e o nós
- Corpo, gestos e movimentos
- Traços, sons, cores e formas
- Escuta, fala, pensamento e imaginação
- Espaços, tempos, quantidades, relações e transformações
ESCRITA DA CRIANÇA:
- Hipótese de escrita silábico-alfabética
- Hypothesis de escrita alfabética
═══════════════════════════════════════════════
REGRAS DE ATUAÇÃO
═══════════════════════════════════════════════
1. Quando a Camila pedir um documento (relatório, planejamento, etc):
- Seguir ESTRITAMENTE o estilo de escrita dela
- Usar frases longas e encadeadas
- Incluir vocabulário técnico-pedagógico natural
- Posicionar-se pessoalmente quando apropriado
- Manter centralidade na criança
2. Quando pedir resumo de livro infantil:
- Identificar temas, personagens, lições
- Sugerir atividades práticas de sala
- Conectar com BNCC e campos de experiência
3. Quando pedir pesquisa pedagógica:
- Fundamentar com referências reais
- Dar exemplos práticos conectando com a realidade da turma
4. Para relatórios HTPC, usar estrutura:
- Planejamento e execução das práticas pedagógicas
- Coerência entre propostas e desenvolvimento
- Diversidade de experiências e intencionalidade educativa
- Avaliação e acompanhamento das aprendizagens
5. Nunca inventar nomes de crianças, atividades ou datas. Use apenas o que conhece dos documentos.
6. Referências: usar "conforme a BNCC", "segundo a Proposta Curricular de SBC", "a partir do documento orientador".
Responda SEMPRE em português do Brasil.`
};
// Rota de Chat com OpenRouter (Streaming nativo)
app.post('/api/chat', requireAuth, async (req, res) => {
const { messages } = req.body;
if (!messages || !Array.isArray(messages)) {
return res.status(400).json({ error: 'Mensagens inválidas' });
}
// Injetar system prompt da Kemily no início da conversa
const enrichedMessages = [KEMILY_SYSTEM_PROMPT, ...messages];
try {
const openrouterUrl = 'https://openrouter.ai/api/v1/chat/completions';
const response = await fetch(openrouterUrl, {
method: 'POST',
headers: {
'Authorization': `Bearer ${process.env.OPENROUTER_API_KEY}`,
'HTTP-Referer': 'https://camila.reifonas.cloud',
'X-Title': 'Camila ChatGPT replica',
'Content-Type': 'application/json'
},
body: JSON.stringify({
model: process.env.OPENROUTER_MODEL,
messages: enrichedMessages,
stream: true
})
});
if (!response.ok) {
const errorText = await response.text();
console.error('Erro na API do OpenRouter:', errorText);
return res.status(response.status).json({ error: 'Erro ao processar requisição na API do OpenRouter' });
}
// Configurar headers para Server-Sent Events (SSE)
res.setHeader('Content-Type', 'text/event-stream');
res.setHeader('Cache-Control', 'no-cache');
res.setHeader('Connection', 'keep-alive');
const reader = response.body.getReader();
const decoder = new TextDecoder('utf-8');
let buffer = '';
while (true) {
const { done, value } = await reader.read();
if (done) break;
buffer += decoder.decode(value, { stream: true });
const lines = buffer.split('\n');
// Manter a última linha inacabada no buffer
buffer = lines.pop();
for (const line of lines) {
const cleanLine = line.trim();
if (cleanLine.startsWith('data: ')) {
const dataContent = cleanLine.slice(6);
if (dataContent === '[DONE]') {
res.write('data: [DONE]\n\n');
continue;
}
try {
const parsed = JSON.parse(dataContent);
const content = parsed.choices?.[0]?.delta?.content || '';
if (content) {
res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
} catch (e) {
// Ignorar linhas incompletas ou JSON inválido
}
}
}
}
// Enviar qualquer conteúdo restante no buffer
if (buffer) {
const cleanLine = buffer.trim();
if (cleanLine.startsWith('data: ')) {
const dataContent = cleanLine.slice(6);
if (dataContent !== '[DONE]') {
try {
const parsed = JSON.parse(dataContent);
const content = parsed.choices?.[0]?.delta?.content || '';
if (content) {
res.write(`data: ${JSON.stringify({ content })}\n\n`);
}
} catch (e) {}
}
}
}
res.write('data: [DONE]\n\n');
res.end();
} catch (error) {
console.error('Erro na rota de chat:', error);
res.status(500).json({ error: 'Erro interno no servidor ao chamar a API de IA' });
}
});
// Rota coringa para redirecionar para index.html
app.get('*', requireAuth, (req, res) => {
res.sendFile(path.join(__dirname, 'public', 'index.html'));
});
app.listen(PORT, '0.0.0.0', () => {
console.log(`Servidor rodando na porta ${PORT}`);
});