🚀 Auto-deploy: Camila atualizado em 25/05/2026 19:09:25
This commit is contained in:
@@ -0,0 +1,176 @@
|
||||
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 === '/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')));
|
||||
|
||||
// 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' });
|
||||
}
|
||||
|
||||
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: messages,
|
||||
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}`);
|
||||
});
|
||||
Reference in New Issue
Block a user