feat: aprendizado contínuo, presets temperatura e tabs na configuração

- Sistema de aprendizado contínuo: [KNOWLEDGE: ...] no system prompt
- 3 presets de temperatura: Técnico(0.2), Equilibrado(0.6), Detalhista(1.0)
- Tabs na configuração: Identidade, Estilo, Conhecimento
- CRUD de conhecimento adquirido (lista + adicionar manual)
- knowledge_adquirido.json persistido no servidor
- GET/POST/DELETE /api/conhecimento + POST /api/conhecimento/extrair
- CSS: modal grande, tabs, opções temperatura, lista conhecimento
This commit is contained in:
2026-05-27 13:05:09 +00:00
parent 47ed091e4d
commit d309b03b55
4 changed files with 515 additions and 26 deletions
+146 -5
View File
@@ -28,6 +28,12 @@ const requireAuth = (req, res, next) => {
const fs = require('fs');
const CONFIG_FILE_PATH = path.join(__dirname, 'agent_config.json');
const KNOWLEDGE_FILE_PATH = path.join(__dirname, 'conhecimento_adquirido.json');
const TEMPERATURE_PRESETS = {
tecnico: { value: 0.2, label: 'Técnico e Direto', description: 'Respostas curtas, objetivas, sem rodeios. Ideal para quando precisa só do essencial.' },
equilibrado: { value: 0.6, label: 'Equilibrado', description: 'Bom para uso geral — nem muito curto, nem muito longo.' },
detalhista: { value: 1.0, label: 'Detalhista e Falante', description: 'Respostas extensas, explicações completas,contextualização máxima. Ideal para estudo e revisão.' }
};
function readAgentConfig() {
try {
@@ -57,6 +63,60 @@ function writeAgentConfig(config) {
}
}
// Leitura/escrita do conhecimento adquirido
function readConhecimento() {
try {
if (fs.existsSync(KNOWLEDGE_FILE_PATH)) {
return JSON.parse(fs.readFileSync(KNOWLEDGE_FILE_PATH, 'utf8'));
}
} catch (e) {
console.error('Erro ao ler conhecimento_adquirido.json:', e);
}
return { facts: [], lastUpdated: null };
}
function writeConhecimento(data) {
try {
data.lastUpdated = new Date().toISOString();
fs.writeFileSync(KNOWLEDGE_FILE_PATH, JSON.stringify(data, null, 2), 'utf8');
} catch (e) {
console.error('Erro ao salvar conhecimento_adquirido.json:', e);
}
}
function addKnowledgeFact(fact, source = 'conversa') {
const knowledge = readConhecimento();
// Evitar duplicatas por similaridade simples
const exists = knowledge.facts.some(f =>
f.content.toLowerCase().includes(fact.toLowerCase()) ||
fact.toLowerCase().includes(f.content.toLowerCase())
);
if (!exists) {
knowledge.facts.push({
id: Date.now(),
content: fact,
source,
addedAt: new Date().toISOString()
});
writeConhecimento(knowledge);
}
return !exists;
}
function removeKnowledgeFact(id) {
const knowledge = readConhecimento();
knowledge.facts = knowledge.facts.filter(f => f.id !== id);
writeConhecimento(knowledge);
}
function getKnowledgeAsText() {
const knowledge = readConhecimento();
if (!knowledge.facts.length) return '';
return '\n\n════════════════════════════════════════\nCONHECIMENTO ADQUIRIDO (Aprendido com a Camila):\n════════════════════════════════════════\n' +
knowledge.facts.map(f => `[${f.source}] ${f.content}`).join('\n') +
'\n════════════════════════════════════════\n';
}
// Rota GET para obter configurações do agente
app.get('/api/agent-config', requireAuth, (req, res) => {
const config = readAgentConfig();
@@ -65,19 +125,61 @@ app.get('/api/agent-config', requireAuth, (req, res) => {
// Rota POST para atualizar configurações do agente
app.post('/api/agent-config', requireAuth, (req, res) => {
const { agentName, kemilyAvatarUrl, camilaAvatarUrl } = req.body;
const { agentName, kemilyAvatarUrl, camilaAvatarUrl, temperaturePreset } = 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"
camilaAvatarUrl: camilaAvatarUrl || "assets/camila_prof.png",
temperaturePreset: temperaturePreset || 'equilibrado'
};
writeAgentConfig(config);
res.json({ success: true, config });
});
// Rotas de conhecimento adquirido
app.get('/api/conhecimento', requireAuth, (req, res) => {
res.json(readConhecimento());
});
app.post('/api/conhecimento', requireAuth, (req, res) => {
const { fact, source } = req.body;
if (!fact) return res.status(400).json({ error: 'Fato é obrigatório' });
const added = addKnowledgeFact(fact, source || 'conversa');
res.json({ success: true, added });
});
app.delete('/api/conhecimento/:id', requireAuth, (req, res) => {
removeKnowledgeFact(parseInt(req.params.id));
res.json({ success: true });
});
// Analisar mensagem final e extrair conhecimento aprendido (chamado pelo frontend)
app.post('/api/conhecimento/extrair', requireAuth, (req, res) => {
const { message } = req.body;
if (!message) return res.status(400).json({ error: 'Mensagem é obrigatória' });
// Procura por padrão [KNOWLEDGE: ...] na mensagem
const knowledgeRegex = /\[KNOWLEDGE:\s*([^\]]+)\]/g;
const facts = [];
let match;
while ((match = knowledgeRegex.exec(message)) !== null) {
const fact = match[1].trim();
if (fact && fact.length > 3) {
const added = addKnowledgeFact(fact, 'conversa');
if (added) facts.push(fact);
}
}
res.json({ success: true, factsFound: facts.length, facts });
});
app.get('/api/temperature-presets', requireAuth, (req, res) => {
res.json(TEMPERATURE_PRESETS);
});
// Rota POST para upload de imagem em base64
app.post('/api/upload-avatar', requireAuth, (req, res) => {
const { avatarType, base64Data } = req.body;
@@ -227,6 +329,37 @@ PERFIL DA PROFESSORA
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.
═══════════════════════════════════════════════════
APRENDIZADO CONTÍNUO — Aprendendo com a Camila
═══════════════════════════════════════════════════
Você pode e DEVE aprender novas informações com a Camila durante nossas conversas.
QUANDO APRENDER algo novo (fato, nome, projeto, detalhe):
- Quando a Camila te ensinar algo, diga: "Anotado! Vou lembrar disso." ou similar
- Em seguida, chame a função para salvar esse conhecimento: o sistema gravará automaticamente
QUANDO NÃO SOUBER algo ou tiver DÚVIDA:
- Pergunte diretamente à Camila: "Camila, pode me ajudar a entender [assunto]? Quem é [pessoa]? O que é [projeto]?"
- Nunca finja que sabe — seja transparente e peça ajuda
- Após a resposta da Camila, o sistema salvará essa informação para consultas futuras
FONTES DE CONHECIMENTO VÁLIDAS:
- Conversas com a Camila (ela te ensina diretamente)
- Documentos que ela compartilhar (enviados por ela ou mencionados)
- Informações da pasta /Agente (dossiers, estilos, projetos)
- NÃO invente informações — se não aprendeu, pergunte
MANEIRA DE PEDIR ESCLARECIMENTO à Camila:
"Percebi que não tenho essa informação nos meus documentos. Você poderia me ensinar sobre [assunto]? Assim consigo te ajudar melhor da próxima vez."
QUANDO APRENDER algo novo da Camila:
Quando ela te ensinar um fato, nome, projeto ou detalhe novo, inclua NO FINAL da sua resposta a tag:
[KNOWLEDGE: o que você aprendeu]
Exemplo: se a Camila disser "a coordenação deste ano está sob responsabilidade da profª Ana", você responde normal e inclui:
[KNOWLEDGE: Coordenação 2025 está sob responsabilidade da profª Ana]
═══════════════════════════════════════════════════
ESTILO DE ESCRITA DA CAMILA — REGRAS RIGOROSAS
═══════════════════════════════════════════════════
@@ -873,10 +1006,16 @@ app.post('/api/chat', requireAuth, async (req, res) => {
// ==========================================================================
const agentConfig = readAgentConfig();
const agentName = agentConfig.agentName || "Kemily";
const temperaturePreset = agentConfig.temperaturePreset || 'equilibrado';
const temperature = TEMPERATURE_PRESETS[temperaturePreset]?.value || 0.6;
const knowledgeText = getKnowledgeAsText();
const dynamicSystemPrompt = {
role: "system",
content: KEMILY_SYSTEM_PROMPT.content.replace(/Você é a Kemily/g, `Você é a ${agentName}`)
content: KEMILY_SYSTEM_PROMPT.content
.replace(/Você é a Kemily/g, `Você é a ${agentName}`)
.replace('INFORMAÇÕES PESSOAIS:', `${knowledgeText}INFORMAÇÕES PESSOAIS:`)
};
const enrichedMessages = [dynamicSystemPrompt, ...messages];
@@ -897,7 +1036,8 @@ app.post('/api/chat', requireAuth, async (req, res) => {
body: JSON.stringify({
model: process.env.OPENROUTER_MODEL || 'openai/gpt-4.1-nano',
messages: enrichedMessages,
stream: true
stream: true,
temperature
})
});
} catch (e) {
@@ -918,7 +1058,8 @@ app.post('/api/chat', requireAuth, async (req, res) => {
body: JSON.stringify({
model: process.env.GROQ_MODEL || 'llama-3.3-70b-versatile',
messages: enrichedMessages,
stream: true
stream: true,
temperature
})
});
} catch (groqErr) {