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:
@@ -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) {
|
||||
|
||||
Reference in New Issue
Block a user