-
-
-

-
-
+
+
+
+
+
+
Controla como a assistente se comporta nas respostas — mais técnica ou mais detalhista
+
+
+
+
+
+
+
+
+
+
+
+
Informações que a assistente aprendeu com você ao longo das conversas. Você pode revisar e remover o que não for mais válido.
+
+
+
+
+
+
+
+
+
diff --git a/public/style.css b/public/style.css
index b096dd5..af71940 100644
--- a/public/style.css
+++ b/public/style.css
@@ -2377,6 +2377,162 @@ code {
opacity: 0.9;
}
+/* Modal de Configurações - Tamanho grande para abas */
+.settings-modal-large .settings-modal-content {
+ max-width: 680px;
+ max-height: 85vh;
+}
+
+/* Tabs de navegação */
+.settings-tabs {
+ display: flex;
+ gap: 4px;
+ padding: 12px 20px 0;
+ border-bottom: 1px solid var(--border-light, #262626);
+}
+
+.settings-tab {
+ padding: 10px 16px;
+ border: none;
+ background: transparent;
+ color: var(--text-secondary, #a3a3a3);
+ font-size: 0.9rem;
+ font-weight: 500;
+ cursor: pointer;
+ border-radius: 8px 8px 0 0;
+ transition: all 0.2s;
+}
+
+.settings-tab:hover {
+ color: var(--text-primary, #ffffff);
+ background: rgba(255,255,255,0.05);
+}
+
+.settings-tab.active {
+ color: var(--brand-green, #10a37f);
+ background: rgba(16, 163, 127, 0.1);
+ border-bottom: 2px solid var(--brand-green, #10a37f);
+}
+
+/* Tab content */
+.settings-tab-content {
+ display: none;
+ padding: 20px 0;
+}
+
+.settings-tab-content.active {
+ display: block;
+}
+
+/* Opções de temperatura */
+.temperature-options {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.temperature-option {
+ display: flex;
+ align-items: flex-start;
+ gap: 12px;
+ padding: 14px 16px;
+ border: 1px solid var(--border-light, #262626);
+ border-radius: 10px;
+ cursor: pointer;
+ transition: all 0.2s;
+ background: var(--bg-secondary, #1a1a1a);
+}
+
+.temperature-option:hover {
+ border-color: var(--brand-green, #10a37f);
+ background: rgba(16, 163, 127, 0.05);
+}
+
+.temperature-option.selected {
+ border-color: var(--brand-green, #10a37f);
+ background: rgba(16, 163, 127, 0.1);
+}
+
+.temperature-option input[type="radio"] {
+ margin-top: 4px;
+ accent-color: var(--brand-green, #10a37f);
+}
+
+.temperature-option-content {
+ display: flex;
+ flex-direction: column;
+ gap: 2px;
+}
+
+.temperature-option-content strong {
+ color: var(--text-primary, #ffffff);
+ font-size: 0.95rem;
+}
+
+.temperature-option-content span {
+ color: var(--text-secondary, #a3a3a3);
+ font-size: 0.8rem;
+}
+
+/* Lista de conhecimento */
+.knowledge-list {
+ display: flex;
+ flex-direction: column;
+ gap: 10px;
+}
+
+.knowledge-item {
+ display: flex;
+ align-items: flex-start;
+ gap: 10px;
+ padding: 12px 14px;
+ background: var(--bg-secondary, #1a1a1a);
+ border: 1px solid var(--border-light, #262626);
+ border-radius: 10px;
+}
+
+.knowledge-item-content {
+ flex: 1;
+ display: flex;
+ flex-direction: column;
+ gap: 4px;
+}
+
+.knowledge-source {
+ font-size: 0.7rem;
+ text-transform: uppercase;
+ color: var(--brand-green, #10a37f);
+ font-weight: 600;
+ letter-spacing: 0.5px;
+}
+
+.knowledge-item-content p {
+ margin: 0;
+ color: var(--text-primary, #ffffff);
+ font-size: 0.9rem;
+ line-height: 1.4;
+}
+
+.knowledge-item-content small {
+ color: var(--text-muted, #666);
+ font-size: 0.75rem;
+}
+
+.knowledge-delete-btn {
+ background: transparent;
+ border: none;
+ color: var(--text-muted, #666);
+ font-size: 1.2rem;
+ cursor: pointer;
+ padding: 0 4px;
+ line-height: 1;
+ transition: color 0.2s;
+}
+
+.knowledge-delete-btn:hover {
+ color: #ef4444;
+}
+
@keyframes fadeIn {
from { opacity: 0; }
to { opacity: 1; }
diff --git a/server.js b/server.js
index f675314..c6e7781 100644
--- a/server.js
+++ b/server.js
@@ -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) {