🔧 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:
@@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"agentName": "Kemily",
|
||||||
|
"kemilyAvatarUrl": "assets/kemily.png",
|
||||||
|
"camilaAvatarUrl": "assets/camila_prof.png"
|
||||||
|
}
|
||||||
+184
-3
@@ -94,6 +94,187 @@ const initApp = () => {
|
|||||||
const searchClearAll = document.getElementById('searchClearAll');
|
const searchClearAll = document.getElementById('searchClearAll');
|
||||||
const sidebarTags = document.getElementById('sidebarTags');
|
const sidebarTags = document.getElementById('sidebarTags');
|
||||||
|
|
||||||
|
// Configuração de Identidade do Agente (Kemily/Camila)
|
||||||
|
const btnSettings = document.getElementById('btnSettings');
|
||||||
|
const settingsModal = document.getElementById('settingsModal');
|
||||||
|
const closeSettingsModal = document.getElementById('closeSettingsModal');
|
||||||
|
const btnSaveSettings = document.getElementById('btnSaveSettings');
|
||||||
|
const settingAgentName = document.getElementById('settingAgentName');
|
||||||
|
const previewKemilyAvatar = document.getElementById('previewKemilyAvatar');
|
||||||
|
const uploadKemilyAvatar = document.getElementById('uploadKemilyAvatar');
|
||||||
|
const previewCamilaAvatar = document.getElementById('previewCamilaAvatar');
|
||||||
|
const uploadCamilaAvatar = document.getElementById('uploadCamilaAvatar');
|
||||||
|
const welcomeTitle = document.getElementById('welcomeTitle');
|
||||||
|
const welcomeAvatarImg = document.getElementById('welcomeAvatarImg');
|
||||||
|
const sidebarAvatarImg = document.getElementById('sidebarAvatarImg');
|
||||||
|
|
||||||
|
window.agentConfig = {
|
||||||
|
agentName: "Kemily",
|
||||||
|
kemilyAvatarUrl: "assets/kemily.png",
|
||||||
|
camilaAvatarUrl: "assets/camila_prof.png"
|
||||||
|
};
|
||||||
|
|
||||||
|
// Carrega as configurações do servidor
|
||||||
|
const loadAgentConfig = async () => {
|
||||||
|
try {
|
||||||
|
const res = await fetch('/api/agent-config');
|
||||||
|
if (res.ok) {
|
||||||
|
window.agentConfig = await res.json();
|
||||||
|
updateAgentIdentityUI();
|
||||||
|
}
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Erro ao carregar configurações do agente:', e);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const updateAgentIdentityUI = () => {
|
||||||
|
if (welcomeTitle) {
|
||||||
|
welcomeTitle.innerText = `Olá! Sou sua assistente virtual e me chamo ${window.agentConfig.agentName}. Como posso ajudar?`;
|
||||||
|
}
|
||||||
|
if (welcomeAvatarImg) {
|
||||||
|
welcomeAvatarImg.src = window.agentConfig.kemilyAvatarUrl;
|
||||||
|
}
|
||||||
|
if (sidebarAvatarImg) {
|
||||||
|
sidebarAvatarImg.src = window.agentConfig.camilaAvatarUrl;
|
||||||
|
}
|
||||||
|
if (settingAgentName) {
|
||||||
|
settingAgentName.value = window.agentConfig.agentName;
|
||||||
|
}
|
||||||
|
if (previewKemilyAvatar) {
|
||||||
|
previewKemilyAvatar.src = window.agentConfig.kemilyAvatarUrl;
|
||||||
|
}
|
||||||
|
if (previewCamilaAvatar) {
|
||||||
|
previewCamilaAvatar.src = window.agentConfig.camilaAvatarUrl;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Variáveis temporárias para avatares (em base64 ou URLs novas)
|
||||||
|
let newKemilyAvatarData = null;
|
||||||
|
let newCamilaAvatarData = null;
|
||||||
|
|
||||||
|
// Eventos do Modal
|
||||||
|
if (btnSettings) {
|
||||||
|
btnSettings.addEventListener('click', () => {
|
||||||
|
newKemilyAvatarData = null;
|
||||||
|
newCamilaAvatarData = null;
|
||||||
|
if (settingAgentName) settingAgentName.value = window.agentConfig.agentName;
|
||||||
|
if (previewKemilyAvatar) previewKemilyAvatar.src = window.agentConfig.kemilyAvatarUrl;
|
||||||
|
if (previewCamilaAvatar) previewCamilaAvatar.src = window.agentConfig.camilaAvatarUrl;
|
||||||
|
settingsModal.style.display = 'flex';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (closeSettingsModal) {
|
||||||
|
closeSettingsModal.addEventListener('click', () => {
|
||||||
|
settingsModal.style.display = 'none';
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fechar clicando fora do modal-content
|
||||||
|
if (settingsModal) {
|
||||||
|
settingsModal.addEventListener('click', (e) => {
|
||||||
|
if (e.target === settingsModal) {
|
||||||
|
settingsModal.style.display = 'none';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Previews de Imagem
|
||||||
|
const handleAvatarFileSelect = (input, previewImg, callback) => {
|
||||||
|
const file = input.files[0];
|
||||||
|
if (file) {
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = (e) => {
|
||||||
|
previewImg.src = e.target.result;
|
||||||
|
callback(e.target.result);
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (uploadKemilyAvatar) {
|
||||||
|
uploadKemilyAvatar.addEventListener('change', () => {
|
||||||
|
handleAvatarFileSelect(uploadKemilyAvatar, previewKemilyAvatar, (base64) => {
|
||||||
|
newKemilyAvatarData = base64;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (uploadCamilaAvatar) {
|
||||||
|
uploadCamilaAvatar.addEventListener('change', () => {
|
||||||
|
handleAvatarFileSelect(uploadCamilaAvatar, previewCamilaAvatar, (base64) => {
|
||||||
|
newCamilaAvatarData = base64;
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Salvar Configurações
|
||||||
|
if (btnSaveSettings) {
|
||||||
|
btnSaveSettings.addEventListener('click', async () => {
|
||||||
|
btnSaveSettings.disabled = true;
|
||||||
|
btnSaveSettings.innerText = 'Salvando...';
|
||||||
|
|
||||||
|
try {
|
||||||
|
let kemilyUrl = window.agentConfig.kemilyAvatarUrl;
|
||||||
|
let camilaUrl = window.agentConfig.camilaAvatarUrl;
|
||||||
|
|
||||||
|
// Se houver novas imagens, faz upload
|
||||||
|
if (newKemilyAvatarData) {
|
||||||
|
const res = await fetch('/api/upload-avatar', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ avatarType: 'kemily', base64Data: newKemilyAvatarData })
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
kemilyUrl = data.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newCamilaAvatarData) {
|
||||||
|
const res = await fetch('/api/upload-avatar', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({ avatarType: 'camila', base64Data: newCamilaAvatarData })
|
||||||
|
});
|
||||||
|
if (res.ok) {
|
||||||
|
const data = await res.json();
|
||||||
|
camilaUrl = data.url;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Envia as configurações gerais
|
||||||
|
const resConfig = await fetch('/api/agent-config', {
|
||||||
|
method: 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body: JSON.stringify({
|
||||||
|
agentName: settingAgentName.value,
|
||||||
|
kemilyAvatarUrl: kemilyUrl,
|
||||||
|
camilaAvatarUrl: camilaUrl
|
||||||
|
})
|
||||||
|
});
|
||||||
|
|
||||||
|
if (resConfig.ok) {
|
||||||
|
const result = await resConfig.json();
|
||||||
|
window.agentConfig = result.config;
|
||||||
|
updateAgentIdentityUI();
|
||||||
|
settingsModal.style.display = 'none';
|
||||||
|
} else {
|
||||||
|
alert('Erro ao salvar as configurações.');
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
console.error(err);
|
||||||
|
alert('Ocorreu um erro ao salvar.');
|
||||||
|
} finally {
|
||||||
|
btnSaveSettings.disabled = false;
|
||||||
|
btnSaveSettings.innerText = 'Salvar Configurações';
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Executa o carregamento inicial das configurações
|
||||||
|
loadAgentConfig();
|
||||||
|
|
||||||
// Variáveis de Estado
|
// Variáveis de Estado
|
||||||
let chats = JSON.parse(localStorage.getItem('camila_chats')) || [];
|
let chats = JSON.parse(localStorage.getItem('camila_chats')) || [];
|
||||||
// Migração: adicionar campos tags e pinned a chats antigos
|
// Migração: adicionar campos tags e pinned a chats antigos
|
||||||
@@ -918,8 +1099,8 @@ const initApp = () => {
|
|||||||
row.className = `message-row ${role}`;
|
row.className = `message-row ${role}`;
|
||||||
|
|
||||||
const avatarHtml = isUser
|
const avatarHtml = isUser
|
||||||
? `<div class="avatar user-avatar">U</div>`
|
? `<div class="avatar user-avatar user-avatar-img-wrapper"><img src="${window.agentConfig?.camilaAvatarUrl || 'assets/camila_prof.png'}" alt="Camila" class="user-avatar-img"></div>`
|
||||||
: `<div class="avatar bot-avatar bot-avatar-img-wrapper"><img src="assets/kemily.png" alt="Kemily AI" class="bot-avatar-img"></div>`;
|
: `<div class="avatar bot-avatar bot-avatar-img-wrapper"><img src="${window.agentConfig?.kemilyAvatarUrl || 'assets/kemily.png'}" alt="${window.agentConfig?.agentName || 'Kemily'}" class="bot-avatar-img"></div>`;
|
||||||
|
|
||||||
let contentHtml = '';
|
let contentHtml = '';
|
||||||
if (isUser) {
|
if (isUser) {
|
||||||
@@ -1270,7 +1451,7 @@ const initApp = () => {
|
|||||||
|
|
||||||
botRow.innerHTML = `
|
botRow.innerHTML = `
|
||||||
<div class="message-icon">
|
<div class="message-icon">
|
||||||
<div class="avatar bot-avatar bot-avatar-img-wrapper"><img src="assets/kemily.png" alt="Kemily AI" class="bot-avatar-img"></div>
|
<div class="avatar bot-avatar bot-avatar-img-wrapper"><img src="${window.agentConfig?.kemilyAvatarUrl || 'assets/kemily.png'}" alt="${window.agentConfig?.agentName || 'Kemily'}" class="bot-avatar-img"></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="message-content-wrapper">
|
<div class="message-content-wrapper">
|
||||||
<div class="message-content">
|
<div class="message-content">
|
||||||
|
|||||||
+45
-3
@@ -82,12 +82,17 @@
|
|||||||
<div class="sidebar-footer">
|
<div class="sidebar-footer">
|
||||||
<div class="user-profile">
|
<div class="user-profile">
|
||||||
<div class="avatar user-avatar sidebar-avatar-wrapper">
|
<div class="avatar user-avatar sidebar-avatar-wrapper">
|
||||||
<img src="assets/camila_prof.png" alt="Camila Reifonas" class="sidebar-avatar-img">
|
<img src="assets/camila_prof.png" alt="Camila Reifonas" class="sidebar-avatar-img" id="sidebarAvatarImg">
|
||||||
</div>
|
</div>
|
||||||
<div class="user-info">
|
<div class="user-info">
|
||||||
<span class="user-name">Camila Reifonas</span>
|
<span class="user-name">Camila Reifonas</span>
|
||||||
<span class="user-status">Online</span>
|
<span class="user-status">Online</span>
|
||||||
</div>
|
</div>
|
||||||
|
<button class="btn-settings" id="btnSettings" title="Configurações do Agente" style="background: none; border: none; color: var(--text-secondary); cursor: pointer; padding: 8px; border-radius: 8px; display: flex; align-items: center; justify-content: center; transition: color var(--transition-fast), background-color var(--transition-fast); margin-right: 4px;">
|
||||||
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor" style="width: 20px; height: 20px;">
|
||||||
|
<path stroke-linecap="round" stroke-linejoin="round" d="M9.594 3.94c.09-.542.56-.94 1.11-.94h2.593c.55 0 1.02.398 1.11.94l.213 1.281c.063.374.313.686.645.87.074.04.147.083.22.127.324.196.72.257 1.075.124l1.217-.456a1.125 1.125 0 0 1 1.37.49l1.296 2.247a1.125 1.125 0 0 1-.26 1.43l-1.003.828c-.293.241-.438.613-.43.992a7.723 7.723 0 0 1 0 .255c-.008.378.137.75.43.991l1.004.827c.424.35.534.954.26 1.43l-1.298 2.247a1.125 1.125 0 0 1-1.369.491l-1.217-.456c-.355-.133-.75-.072-1.076.124a6.57 6.57 0 0 1-.22.128c-.331.183-.581.495-.644.869l-.213 1.28c-.09.543-.56.941-1.11.941h-2.594c-.55 0-1.02-.398-1.11-.94l-.213-1.281c-.062-.374-.312-.686-.644-.87a6.52 6.52 0 0 1-.22-.127c-.325-.196-.72-.257-1.076-.124l-1.217.456a1.125 1.125 0 0 1-1.369-.49l-1.297-2.247a1.125 1.125 0 0 1 .26-1.43l1.004-.827c.292-.24.437-.613.43-.992a6.932 6.932 0 0 1 0-.255c.007-.378-.138-.75-.43-.991l-1.004-.827a1.125 1.125 0 0 1-.26-1.43l1.297-2.247a1.125 1.125 0 0 1 1.37-.491l1.216.456c.356.133.751.072 1.076-.124.072-.044.146-.087.22-.128.332-.183.582-.495.645-.869L9.594 3.94ZM12 15a3 3 0 1 0 0-6 3 3 0 0 0 0 6Z" />
|
||||||
|
</svg>
|
||||||
|
</button>
|
||||||
<button class="btn-logout" id="btnLogout" title="Sair">
|
<button class="btn-logout" id="btnLogout" title="Sair">
|
||||||
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
<svg xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" stroke-width="2" stroke="currentColor">
|
||||||
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15" />
|
<path stroke-linecap="round" stroke-linejoin="round" d="M8.25 9V5.25A2.25 2.25 0 0 1 10.5 3h6a2.25 2.25 0 0 1 2.25 2.25v13.5A2.25 2.25 0 0 1 16.5 21h-6a2.25 2.25 0 0 1-2.25-2.25V15m-3 0-3-3m0 0 3-3m-3 3H15" />
|
||||||
@@ -143,10 +148,10 @@
|
|||||||
<div class="welcome-container" id="welcomeContainer">
|
<div class="welcome-container" id="welcomeContainer">
|
||||||
<div class="welcome-logo">
|
<div class="welcome-logo">
|
||||||
<div class="logo-outer welcome-avatar-wrapper">
|
<div class="logo-outer welcome-avatar-wrapper">
|
||||||
<img src="assets/kemily.png" alt="Kemily AI" class="welcome-avatar-img">
|
<img src="assets/kemily.png" alt="Kemily AI" class="welcome-avatar-img" id="welcomeAvatarImg">
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<h2>Olá! Sou sua assistente virtual e me chamo Kemily. Como posso ajudar?</h2>
|
<h2 id="welcomeTitle">Olá! Sou sua assistente virtual e me chamo Kemily. Como posso ajudar?</h2>
|
||||||
|
|
||||||
<div class="welcome-grid">
|
<div class="welcome-grid">
|
||||||
<div class="suggestion-card" data-prompt="Escreva um relatório de observação semanal de um aluno de educação infantil focado em socialização e interação.">
|
<div class="suggestion-card" data-prompt="Escreva um relatório de observação semanal de um aluno de educação infantil focado em socialização e interação.">
|
||||||
@@ -242,6 +247,43 @@
|
|||||||
</main>
|
</main>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
<!-- Modal de Configurações do Agente -->
|
||||||
|
<div id="settingsModal" class="settings-modal" style="display: none;">
|
||||||
|
<div class="settings-modal-content">
|
||||||
|
<div class="settings-modal-header">
|
||||||
|
<h3>Configurações da Assistente</h3>
|
||||||
|
<button id="closeSettingsModal" class="close-settings-btn">×</button>
|
||||||
|
</div>
|
||||||
|
<div class="settings-modal-body">
|
||||||
|
<div class="settings-group">
|
||||||
|
<label for="settingAgentName">Nome da Assistente</label>
|
||||||
|
<input type="text" id="settingAgentName" placeholder="Ex: Kemily">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Avatar da Assistente</label>
|
||||||
|
<div class="avatar-preview-container">
|
||||||
|
<img id="previewKemilyAvatar" src="assets/kemily.png" alt="Kemily Preview" class="preview-avatar-img">
|
||||||
|
<input type="file" id="uploadKemilyAvatar" accept="image/*" style="display: none;">
|
||||||
|
<button type="button" class="btn-change-avatar" onclick="document.getElementById('uploadKemilyAvatar').click()">Escolher Imagem</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="settings-group">
|
||||||
|
<label>Avatar da Camila (Usuária)</label>
|
||||||
|
<div class="avatar-preview-container">
|
||||||
|
<img id="previewCamilaAvatar" src="assets/camila_prof.png" alt="Camila Preview" class="preview-avatar-img">
|
||||||
|
<input type="file" id="uploadCamilaAvatar" accept="image/*" style="display: none;">
|
||||||
|
<button type="button" class="btn-change-avatar" onclick="document.getElementById('uploadCamilaAvatar').click()">Escolher Imagem</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class="settings-modal-footer">
|
||||||
|
<button id="btnSaveSettings" class="btn-save-settings">Salvar Configurações</button>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
<!-- Bibliotecas externas para renderizar markdown e blocos de código -->
|
<!-- Bibliotecas externas para renderizar markdown e blocos de código -->
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/marked/12.0.1/marked.min.js"></script>
|
||||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/highlight.js/11.9.0/highlight.min.js"></script>
|
||||||
|
|||||||
@@ -2195,3 +2195,188 @@ code {
|
|||||||
background-color: rgba(16, 163, 127, 0.06);
|
background-color: rgba(16, 163, 127, 0.06);
|
||||||
color: rgb(167, 243, 208);
|
color: rgb(167, 243, 208);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Hover no botao de configuracoes da sidebar */
|
||||||
|
.btn-settings:hover {
|
||||||
|
color: var(--text-primary) !important;
|
||||||
|
background-color: var(--bg-secondary) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Avatar do Usuario no Chat */
|
||||||
|
.user-avatar-img-wrapper {
|
||||||
|
width: 32px;
|
||||||
|
height: 32px;
|
||||||
|
border-radius: 50%;
|
||||||
|
overflow: hidden;
|
||||||
|
border: 1.5px solid rgba(255, 255, 255, 0.2);
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.user-avatar-img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Modal de Configurações */
|
||||||
|
.settings-modal {
|
||||||
|
position: fixed;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
background-color: rgba(0, 0, 0, 0.6);
|
||||||
|
backdrop-filter: blur(8px);
|
||||||
|
-webkit-backdrop-filter: blur(8px);
|
||||||
|
display: flex;
|
||||||
|
justify-content: center;
|
||||||
|
align-items: center;
|
||||||
|
z-index: 1000;
|
||||||
|
animation: fadeIn 0.2s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-content {
|
||||||
|
background-color: var(--bg-primary, #121212);
|
||||||
|
border: 1px solid var(--border-light, #262626);
|
||||||
|
border-radius: 16px;
|
||||||
|
width: 450px;
|
||||||
|
max-width: 90%;
|
||||||
|
box-shadow: 0 8px 32px rgba(0, 0, 0, 0.4);
|
||||||
|
overflow: hidden;
|
||||||
|
animation: scaleIn 0.2s cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-header {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-bottom: 1px solid var(--border-light, #262626);
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
align-items: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-header h3 {
|
||||||
|
margin: 0;
|
||||||
|
font-size: 1.15rem;
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-settings-btn {
|
||||||
|
background: none;
|
||||||
|
border: none;
|
||||||
|
color: var(--text-secondary, #a3a3a3);
|
||||||
|
font-size: 1.5rem;
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 4px 8px;
|
||||||
|
border-radius: 4px;
|
||||||
|
line-height: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.close-settings-btn:hover {
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-body {
|
||||||
|
padding: 20px;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group label {
|
||||||
|
font-size: 0.85rem;
|
||||||
|
color: var(--text-secondary, #a3a3a3);
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group input[type="text"] {
|
||||||
|
background-color: var(--bg-secondary, #1a1a1a);
|
||||||
|
border: 1px solid var(--border-light, #262626);
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 10px 12px;
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
font-size: 0.95rem;
|
||||||
|
outline: none;
|
||||||
|
transition: border-color var(--transition-fast);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-group input[type="text"]:focus {
|
||||||
|
border-color: var(--brand-green, #0ea5e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.avatar-preview-container {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
gap: 16px;
|
||||||
|
background-color: var(--bg-secondary, #1a1a1a);
|
||||||
|
border: 1px solid var(--border-light, #262626);
|
||||||
|
padding: 12px;
|
||||||
|
border-radius: 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.preview-avatar-img {
|
||||||
|
width: 50px;
|
||||||
|
height: 50px;
|
||||||
|
border-radius: 50%;
|
||||||
|
object-fit: cover;
|
||||||
|
border: 2px solid var(--brand-green, #0ea5e9);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-change-avatar {
|
||||||
|
background-color: rgba(255, 255, 255, 0.05);
|
||||||
|
border: 1px solid var(--border-light, #262626);
|
||||||
|
color: var(--text-primary, #ffffff);
|
||||||
|
padding: 8px 14px;
|
||||||
|
border-radius: 6px;
|
||||||
|
font-size: 0.85rem;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: background-color 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-change-avatar:hover {
|
||||||
|
background-color: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.settings-modal-footer {
|
||||||
|
padding: 16px 20px;
|
||||||
|
border-top: 1px solid var(--border-light, #262626);
|
||||||
|
display: flex;
|
||||||
|
justify-content: flex-end;
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-settings {
|
||||||
|
background: linear-gradient(135deg, var(--brand-green) 0%, #0a5f49 100%);
|
||||||
|
border: none;
|
||||||
|
color: #ffffff;
|
||||||
|
padding: 10px 18px;
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 0.9rem;
|
||||||
|
font-weight: 500;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: opacity 0.2s;
|
||||||
|
box-shadow: 0 0 12px var(--brand-glow);
|
||||||
|
}
|
||||||
|
|
||||||
|
.btn-save-settings:hover {
|
||||||
|
opacity: 0.9;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes fadeIn {
|
||||||
|
from { opacity: 0; }
|
||||||
|
to { opacity: 1; }
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes scaleIn {
|
||||||
|
from { transform: scale(0.95); opacity: 0; }
|
||||||
|
to { transform: scale(1); opacity: 1; }
|
||||||
|
}
|
||||||
|
|||||||
@@ -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)
|
// Rota de login (página)
|
||||||
app.get('/login', (req, res) => {
|
app.get('/login', (req, res) => {
|
||||||
const session = req.signedCookies.camila_session || req.cookies.camila_session;
|
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)
|
// 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 {
|
try {
|
||||||
let chatResponse;
|
let chatResponse;
|
||||||
|
|||||||
Reference in New Issue
Block a user