🔧 feat: Sistema de identidade dinâmica do agente - modal de configurações, avatares customizáveis, nome dinâmico sincronizado com Hermes/Telegram

This commit is contained in:
2026-05-26 19:40:28 +00:00
parent c78c4020cf
commit 6c35f7ed1b
5 changed files with 514 additions and 7 deletions
+95 -1
View File
@@ -25,6 +25,92 @@ const requireAuth = (req, res, next) => {
}
};
const fs = require('fs');
const CONFIG_FILE_PATH = path.join(__dirname, 'agent_config.json');
function readAgentConfig() {
try {
if (fs.existsSync(CONFIG_FILE_PATH)) {
return JSON.parse(fs.readFileSync(CONFIG_FILE_PATH, 'utf8'));
}
} catch (e) {
console.error('Erro ao ler agent_config.json:', e);
}
return {
agentName: "Kemily",
kemilyAvatarUrl: "assets/kemily.png",
camilaAvatarUrl: "assets/camila_prof.png"
};
}
function writeAgentConfig(config) {
try {
fs.writeFileSync(CONFIG_FILE_PATH, JSON.stringify(config, null, 2), 'utf8');
// Copiar também para o BotVPS para sincronização com o Hermes
const botConfigDir = path.join(__dirname, '..', 'BotVPS', 'data');
if (fs.existsSync(botConfigDir)) {
fs.writeFileSync(path.join(botConfigDir, 'agent_config.json'), JSON.stringify(config, null, 2), 'utf8');
}
} catch (e) {
console.error('Erro ao salvar agent_config.json:', e);
}
}
// Rota GET para obter configurações do agente
app.get('/api/agent-config', requireAuth, (req, res) => {
const config = readAgentConfig();
res.json(config);
});
// Rota POST para atualizar configurações do agente
app.post('/api/agent-config', requireAuth, (req, res) => {
const { agentName, kemilyAvatarUrl, camilaAvatarUrl } = req.body;
if (!agentName) {
return res.status(400).json({ error: 'Nome do agente é obrigatório' });
}
const config = {
agentName: agentName.trim(),
kemilyAvatarUrl: kemilyAvatarUrl || "assets/kemily.png",
camilaAvatarUrl: camilaAvatarUrl || "assets/camila_prof.png"
};
writeAgentConfig(config);
res.json({ success: true, config });
});
// Rota POST para upload de imagem em base64
app.post('/api/upload-avatar', requireAuth, (req, res) => {
const { avatarType, base64Data } = req.body;
if (!avatarType || !base64Data) {
return res.status(400).json({ error: 'Dados inválidos' });
}
const matches = base64Data.match(/^data:([A-Za-z-+\/]+);base64,(.+)$/);
if (!matches || matches.length !== 3) {
return res.status(400).json({ error: 'Formato de imagem inválido' });
}
const imageBuffer = Buffer.from(matches[2], 'base64');
const fileExt = matches[1].split('/')[1] || 'png';
const fileName = `custom_${avatarType}_avatar_${Date.now()}.${fileExt}`;
// Garantir que a pasta public/assets existe
const assetsDir = path.join(__dirname, 'public', 'assets');
if (!fs.existsSync(assetsDir)) {
fs.mkdirSync(assetsDir, { recursive: true });
}
const filePath = path.join(assetsDir, fileName);
fs.writeFile(filePath, imageBuffer, (err) => {
if (err) {
console.error('Erro ao salvar avatar:', err);
return res.status(500).json({ error: 'Erro ao salvar arquivo' });
}
return res.json({ success: true, url: `assets/${fileName}` });
});
});
// Rota de login (página)
app.get('/login', (req, res) => {
const session = req.signedCookies.camila_session || req.cookies.camila_session;
@@ -737,7 +823,15 @@ app.post('/api/chat', requireAuth, async (req, res) => {
// ==========================================================================
// FLUXO PADRÃO DE TEXTO (OpenRouter com SSE streaming original, com fallback Groq)
// ==========================================================================
const enrichedMessages = [KEMILY_SYSTEM_PROMPT, ...messages];
const agentConfig = readAgentConfig();
const agentName = agentConfig.agentName || "Kemily";
const dynamicSystemPrompt = {
role: "system",
content: KEMILY_SYSTEM_PROMPT.content.replace(/Você é a Kemily/g, `Você é a ${agentName}`)
};
const enrichedMessages = [dynamicSystemPrompt, ...messages];
try {
let chatResponse;