ajustes tela agente

This commit is contained in:
Lila.rodri 2025-03-31 09:26:22 -03:00
parent fe1d816022
commit 03c35facd7
17 changed files with 943 additions and 415 deletions

8
package-lock.json generated
View File

@ -10,7 +10,7 @@
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"apexcharts": "^3.54.1", "apexcharts": "^3.54.1",
"axios": "^1.7.9", "axios": "^1.8.4",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"npm-check-updates": "^17.1.11", "npm-check-updates": "^17.1.11",
@ -837,9 +837,9 @@
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
}, },
"node_modules/axios": { "node_modules/axios": {
"version": "1.7.9", "version": "1.8.4",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz", "resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==", "integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
"dependencies": { "dependencies": {
"follow-redirects": "^1.15.6", "follow-redirects": "^1.15.6",
"form-data": "^4.0.0", "form-data": "^4.0.0",

View File

@ -11,7 +11,7 @@
"dependencies": { "dependencies": {
"@mdi/font": "^7.4.47", "@mdi/font": "^7.4.47",
"apexcharts": "^3.54.1", "apexcharts": "^3.54.1",
"axios": "^1.7.9", "axios": "^1.8.4",
"date-fns": "^4.1.0", "date-fns": "^4.1.0",
"jwt-decode": "^4.0.0", "jwt-decode": "^4.0.0",
"npm-check-updates": "^17.1.11", "npm-check-updates": "^17.1.11",

View File

@ -1,33 +0,0 @@
<template>
<v-card
class="pa-4"
:class="{ 'striped-bg': item.striped }"
:color="item.color"
outlined
>
<v-card-text class="text-center text-white text-h6 card">
{{ item.name }}
</v-card-text>
</v-card>
</template>
<script setup>
defineProps({
item: Object,
});
</script>
<style scoped>
.card {
color: #6c00b5!important;
}
.striped-bg {
border-radius: 50px;
background: linear-gradient(145deg, #eeeded, #ffffff);
box-shadow: 20px 20px 60px #d9d9d9,
-20px -20px 60px #ffffff;
}
</style>

View File

@ -34,8 +34,8 @@ const menuItems = [
{ title: 'Admin', path: '/dashboard/profile', icon: 'mdi-account', requiredPermission: 'read' }, { title: 'Admin', path: '/dashboard/profile', icon: 'mdi-account', requiredPermission: 'read' },
{ title: 'Configurações', path: '/dashboard/settings', icon: 'mdi-cog', requiredPermission: 'write' }, { title: 'Configurações', path: '/dashboard/settings', icon: 'mdi-cog', requiredPermission: 'write' },
{ title: 'Usuários', path: '/dashboard/users', icon: 'mdi-account-multiple-outline', requiredPermission: 'write' }, { title: 'Usuários', path: '/dashboard/users', icon: 'mdi-account-multiple-outline', requiredPermission: 'write' },
{ title: 'Agentes', path: '/dashboard/agentlist', icon: 'mdi mdi-account-group', requiredPermission: 'write' },
{ title: 'Train', path: '/dashboard/train', icon: 'mdi mdi-thought-bubble', requiredPermission: 'write' }, { title: 'Train', path: '/dashboard/train', icon: 'mdi mdi-thought-bubble', requiredPermission: 'write' },
{ title: 'Cards', path: '/dashboard/dashboardview', icon: 'mdi mdi-cards', requiredPermission: 'read' },
{ title: 'Chat', path: '/dashboard/chat', icon: 'mdi mdi-face-agent', requiredPermission: 'read' }, { title: 'Chat', path: '/dashboard/chat', icon: 'mdi mdi-face-agent', requiredPermission: 'read' },
] ]

View File

@ -1,264 +1,165 @@
<template> <template>
<v-container fluid> <v-container fluid>
<v-row justify="space-between"> <v-card class="mx-auto card" width="100%">
<!-- Card de Treinamento --> <v-toolbar class="header">
<v-col cols="12" md="6"> <v-toolbar-title class="text">Criar Novo Agente</v-toolbar-title>
<v-card class="mx-auto card" width="100%"> </v-toolbar>
<v-toolbar class="header">
<v-toolbar-title class="text">Train Lucy</v-toolbar-title> <v-card-text>
</v-toolbar> <v-form @submit.prevent="createAgent">
<v-tabs v-model="tab" color="primary" grow> <v-text-field
<v-tab value="1"><v-icon>mdi-file-pdf-box</v-icon></v-tab> v-model="agentName"
<v-tab value="2"><v-icon>mdi-text-box</v-icon></v-tab> label="Nome do Agente*"
<v-tab value="3"><v-icon>mdi-web</v-icon></v-tab> placeholder="Ex: Assistente de Vendas"
</v-tabs> variant="outlined"
required
<v-window v-model="tab"> class="mb-4"
<!-- Aba 1: PDF ou Imagens --> :rules="[v => !!v || 'Nome é obrigatório']"
<v-window-item value="1"> ></v-text-field>
<v-card-text>
<div <v-text-field
class="upload-box" v-model="agentDescription"
@dragover.prevent="onDragOver" label="Descrição*"
@dragleave.prevent="onDragLeave" placeholder="Ex: Especialista em atendimento ao cliente"
@drop.prevent="onDrop($event, ['pdf', 'png', 'jpg', 'jpeg'])" variant="outlined"
:class="{ 'dragging': isDragging }"> required
<v-icon size="x-large" color="primary">mdi-cloud-upload</v-icon> class="mb-4"
<p>Arraste arquivos PDF ou imagens</p> :rules="[v => !!v || 'Descrição é obrigatória']"
</div> ></v-text-field>
<v-file-input
v-model="files" <v-select
accept=".pdf,.png,.jpg,.jpeg" v-model="agentFunction"
label="Enviar PDF ou Imagens" label="Função do Agente*"
multiple :items="functionOptions"
variant="outlined" variant="outlined"
@update:model-value="handleFileUpload($event, ['pdf', 'png', 'jpg', 'jpeg'])" required
></v-file-input> class="mb-4"
</v-card-text> :rules="[v => !!v || 'Função é obrigatória']"
</v-window-item> ></v-select>
<!-- Aba 2: Arquivos de Texto --> <v-select
<v-window-item value="2"> v-model="agentPersonality"
<v-card-text> label="Personalidade*"
<div :items="personalityOptions"
class="upload-box" variant="outlined"
@dragover.prevent="onDragOver" required
@dragleave.prevent="onDragLeave" class="mb-4"
@drop.prevent="onDrop($event, ['txt'])" :rules="[v => !!v || 'Personalidade é obrigatória']"
:class="{ 'dragging': isDragging }"> ></v-select>
<v-icon size="x-large" color="primary">mdi-cloud-upload</v-icon>
<p>Arraste arquivos de Texto ou clique para enviar</p> <v-select
</div> v-model="openAiModel"
<v-file-input label="Modelo OpenAI*"
v-model="files" :items="modelOptions"
accept=".txt" variant="outlined"
label="Enviar Arquivos de Texto" required
multiple class="mb-4"
variant="outlined" :rules="[v => !!v || 'Modelo é obrigatório']"
@update:model-value="handleFileUpload($event, ['txt'])" ></v-select>
></v-file-input>
</v-card-text> <v-select
</v-window-item> v-model="toolType"
label="Tipo de Agente*"
<!-- Aba 3: URLs --> :items="toolOptions"
<v-window-item value="3"> variant="outlined"
<v-card-text> required
<v-btn class="mb-4"
color="primary" :rules="[v => !!v || 'Tipo é obrigatório']"
block ></v-select>
@click="showUrlDialog = true"
>
Adicionar URL
</v-btn>
<v-dialog v-model="showUrlDialog" max-width="500">
<v-card>
<v-card-title>Digite a URL</v-card-title>
<v-card-text>
<v-text-field
v-model="url"
label="URL"
placeholder="https://exemplo.com/arquivo"
variant="outlined"
density="compact"
></v-text-field>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="primary" @click="handleUrlUpload">Enviar</v-btn>
<v-btn color="secondary" @click="showUrlDialog = false">Cancelar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-card-text>
</v-window-item>
</v-window>
<!-- Lista de arquivos -->
<v-divider v-if="uploadedFiles.length > 0"></v-divider>
<v-list v-if="uploadedFiles.length > 0">
<v-list-subheader>Arquivos carregados</v-list-subheader>
<v-list-item
v-for="(file, index) in uploadedFiles"
:key="index"
:title="file.name"
:subtitle="formatFileSize(file.size)"
>
<template v-slot:append>
<v-btn
icon="mdi-delete"
color="error"
variant="text"
@click="removeFile(index)"
></v-btn>
</template>
</v-list-item>
</v-list>
<!-- Botão de Treinar -->
<v-divider v-if="uploadedFiles.length > 0"></v-divider>
<v-btn <v-btn
color="#4cc9f0" color="primary"
block block
@click="trainModel" type="submit"
:disabled="uploadedFiles.length === 0" :loading="isLoading"
> >
Treinar Lucy Criar Agente
</v-btn> </v-btn>
</v-card> </v-form>
</v-col> </v-card-text>
</v-card>
<!-- Card de Interação com Lucy -->
<v-col cols="12" md="6">
<v-card class="mx-auto card" width="100%">
<v-toolbar class="header">
<v-toolbar-title class="text">Converse com Lucy</v-toolbar-title>
</v-toolbar>
<v-card-text v-if="!modelTrained && !isTraining">
<p>Treine Lucy antes de iniciar a conversa.</p>
</v-card-text>
<v-card-text v-else-if="isTraining">
<p>Lucy está sendo treinada... Aguarde!</p>
</v-card-text>
<v-card-text v-else>
<v-list>
<v-list-subheader>Conversa com Lucy</v-list-subheader>
<v-list-item v-for="(message, index) in chatHistory" :key="index">
<v-list-item-content>
<v-list-item-title :class="{ 'user-message': message.sender === 'user' }">
{{ message.sender }}: {{ message.text }}
</v-list-item-title>
</v-list-item-content>
</v-list-item>
</v-list>
<v-text-field
v-model="userMessage"
label="Digite sua mensagem"
placeholder="Pergunte algo à Lucy"
variant="outlined"
density="compact"
@keyup.enter="sendMessage"
:disabled="!modelTrained"
></v-text-field>
<v-btn
color="primary"
block
@click="sendMessage"
:disabled="!modelTrained"
>
Enviar
</v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-container> </v-container>
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref } from 'vue';
import axios from 'axios';
import { useRouter } from 'vue-router';
import { useAuthStore } from '../stores/auth'; // Importe sua store de autenticação
const tab = ref('1') const router = useRouter();
const files = ref([]) const authStore = useAuthStore();
const uploadedFiles = ref([]) const apiUrl = import.meta.env.VITE_API_AGENT;
const isDragging = ref(false)
const showUrlDialog = ref(false)
const url = ref('')
const modelTrained = ref(false)
const isTraining = ref(false) // Estado para verificar se Lucy está sendo treinada
const chatHistory = ref([])
const userMessage = ref('')
const formatFileSize = (size) => { // Variáveis do formulário
if (size === 'N/A') return size const agentName = ref('');
const units = ['B', 'KB', 'MB', 'GB'] const agentDescription = ref('');
let formattedSize = size const agentFunction = ref('assistente geral');
let unitIndex = 0 const agentPersonality = ref('neutro');
const openAiModel = ref('gpt-4');
while (formattedSize >= 1024 && unitIndex < units.length - 1) { const toolType = ref('chat');
formattedSize /= 1024 const isLoading = ref(false);
unitIndex++
// Opções para selects
const functionOptions = [
'assistente geral',
'suporte técnico',
'vendas',
'atendimento ao cliente',
'recursos humanos',
'financeiro'
];
const personalityOptions = [
'neutro',
'formal',
'amigável',
'técnico',
'entusiasta',
'profissional'
];
const modelOptions = [
'gpt-4',
'gpt-3.5-turbo'
];
const toolOptions = [
'chat',
'db'
];
const createAgent = async () => {
if (!agentName.value || !agentDescription.value || !toolType.value) {
alert('Por favor, preencha todos os campos obrigatórios');
return;
} }
return `${Math.round(formattedSize * 100) / 100} ${units[unitIndex]}`
}
const handleFileUpload = (newFiles, allowedExtensions) => { isLoading.value = true;
if (!newFiles) return try {
const payload = {
const validFiles = Array.from(newFiles).filter(file => { user_id: authStore.user?.id, // Remove o fallback '1' (obrigatório ter usuário logado)
const ext = file.name.split('.').pop().toLowerCase() name: agentName.value,
return allowedExtensions.includes(ext) description: agentDescription.value,
}) personality: agentPersonality.value,
function: agentFunction.value,
uploadedFiles.value.push(...validFiles) model: openAiModel.value,
files.value = [] tool_type: toolType.value,
} };
const handleUrlUpload = () => { const response = await axios.post(`${apiUrl}/new-api/create-agent`, payload);
if (url.value.trim()) { window.dispatchEvent(new CustomEvent('agent-created'));
uploadedFiles.value.push({ router.push({ name: 'agentList' });
name: url.value, } catch (error) {
type: 'url', console.error('Erro ao criar agente:', error);
size: 'N/A' if (error.response?.status === 400) {
}) alert('Usuário não autenticado. Faça login novamente.');
url.value = '' router.push({ name: 'login' }); // Redireciona para login se o user_id for inválido
showUrlDialog.value = false }
} finally {
isLoading.value = false;
} }
} };
const removeFile = (index) => {
uploadedFiles.value.splice(index, 1)
}
const onDrop = (event, allowedExtensions) => {
isDragging.value = false
const droppedFiles = event.dataTransfer.files
if (droppedFiles.length) {
handleFileUpload(droppedFiles, allowedExtensions)
}
}
const onDragOver = () => isDragging.value = true
const onDragLeave = () => isDragging.value = false
const trainModel = () => {
isTraining.value = true
setTimeout(() => {
modelTrained.value = true
isTraining.value = false
console.log('Lucy foi treinada com os arquivos:', uploadedFiles.value)
}, 3000) // Simula o tempo de treinamento
}
const sendMessage = () => {
if (userMessage.value.trim()) {
chatHistory.value.push({ sender: 'user', text: userMessage.value })
chatHistory.value.push({ sender: 'Lucy', text: `Você disse: "${userMessage.value}"` })
userMessage.value = ''
}
}
</script> </script>
<style scoped> <style scoped>
@ -267,39 +168,16 @@ const sendMessage = () => {
} }
.card { .card {
color: #6c00b5; color: #333;
border-radius: 25px; border-radius: 25px;
background: linear-gradient(145deg, #eeeded, #ffffff); background: linear-gradient(145deg, #eeeded, #ffffff);
box-shadow: 20px 20px 60px #d9d9d9, box-shadow: 20px 20px 60px #d9d9d9,
-20px -20px 60px #ffffff; -20px -20px 60px #ffffff;
}
.upload-box {
margin: 20px 0;
border: 2px dashed #ccc;
padding: 20px; padding: 20px;
border-radius: 8px;
text-align: center;
transition: all 0.3s;
cursor: pointer;
}
.upload-box.dragging {
background-color: #e3f2fd;
border-color: #64b5f6;
}
.upload-box p {
margin-top: 8px;
font-size: 14px;
color: #777;
}
.user-message {
font-weight: bold;
} }
.text { .text {
color: #6c00b5; font-weight: 600;
color: #333;
} }
</style> </style>

View File

@ -60,7 +60,7 @@ const emit = defineEmits(['update:isModalOpen', 'save']);
const userStore = useUserStore(); const userStore = useUserStore();
const authStore = useAuthStore(); // Obtenha o authStore para verificar o papel do usuário const authStore = useAuthStore(); // Obtenha o authStore para verificar o papel do usuário
const isOpen = ref(props.isModalOpen); const isOpen = ref(false); // Estado local para o modal
const localUser = ref({ const localUser = ref({
id: props.modalUser?.id || null, id: props.modalUser?.id || null,
username: props.modalUser?.username || '', username: props.modalUser?.username || '',
@ -72,11 +72,43 @@ const localUser = ref({
parent_id: props.isEditMode ? props.modalUser?.parent_id : 1 // Padrão 1 para novos usuários parent_id: props.isEditMode ? props.modalUser?.parent_id : 1 // Padrão 1 para novos usuários
}); });
// Função para formatar a data no formato esperado pela API // Sincroniza `isOpen` com `props.isModalOpen`
function formatBirthDate(dateString) { watch(() => props.isModalOpen, (newValue) => {
const date = parseISO(dateString); isOpen.value = newValue;
return format(date, "yyyy-MM-dd"); if (newValue && props.isEditMode && props.modalUser) {
} loadUserData();
}
});
// Atualiza `props.isModalOpen` quando o modal é fechado
watch(isOpen, (newValue) => {
if (!newValue) {
emit('update:isModalOpen', false);
}
});
// Carrega os dados do usuário ao abrir o modal no modo de edição
const loadUserData = async () => {
try {
const fullUserData = await userStore.fetchUserById(props.modalUser.id);
if (fullUserData) {
localUser.value = {
id: fullUserData.user.id || null,
username: fullUserData.user.username || '',
email: fullUserData.user.email || '',
password: '', // Não carregamos a senha por segurança
birth_date: fullUserData.user.birth_date
? new Date(fullUserData.user.birth_date).toISOString().split('T')[0]
: '',
phone: fullUserData.user.phone || '',
profile_image: fullUserData.user.profile_image || '',
parent_id: fullUserData.user.parent_id || 1
};
}
} catch (error) {
console.error('Erro ao carregar dados do usuário:', error);
}
};
const isFormValid = computed(() => { const isFormValid = computed(() => {
const { username, email, password, birth_date, phone } = localUser.value; const { username, email, password, birth_date, phone } = localUser.value;
@ -92,24 +124,6 @@ const isFormValid = computed(() => {
(props.isEditMode || password); (props.isEditMode || password);
}); });
watch(() => props.isModalOpen, (newValue) => {
isOpen.value = newValue;
localUser.value = {
id: props.modalUser?.id || null,
username: props.modalUser?.username || '',
email: props.modalUser?.email || '',
password: '',
birth_date: props.modalUser?.birth_date || '',
phone: props.modalUser?.phone || '',
profile_image: props.modalUser?.profile_image || '',
parent_id: props.isEditMode ? props.modalUser?.parent_id : 1
};
});
watch(isOpen, (newValue) => {
emit('update:isModalOpen', newValue);
});
const closeModal = () => { const closeModal = () => {
isOpen.value = false; isOpen.value = false;
}; };

View File

@ -8,38 +8,51 @@ import Settings from '../views/Settings.vue'
import Users from '../views/Users.vue' import Users from '../views/Users.vue'
import UserEditPage from '../views/UserEditPage.vue' import UserEditPage from '../views/UserEditPage.vue'
import Train from '../views/Train.vue' import Train from '../views/Train.vue'
import DashboardView from '../views/DashboardView.vue'
import Chat from '../views/Chat.vue' import Chat from '../views/Chat.vue'
import Home from '../views/Home.vue' import Home from '../views/Home.vue'
import AgentList from '../views/AgentList.vue'
import AgentChat from '../views/AgentChat.vue'
const routes = [ const routes = [
{ path: '/', redirect: '/login' }, { path: '/', redirect: '/login' },
{ path: '/login', component: Login }, { path: '/login', component: Login },
{
path: '/dashboard',
{ component: Dashboard,
path: '/dashboard', children: [
component: Dashboard, { path: 'home', name: 'home', component: Home },
children: [ { path: '', name: 'dashboard-home', component: DashboardHome },
{ path: 'home', name:'home', component: Home}, { path: 'profile', name: 'profile', component: Profile },
{ path: '', name: 'dashboard-home', component: DashboardHome }, { path: 'settings', name: 'settings', component: Settings },
{ path: 'profile', name: 'profile', component: Profile }, { path: 'users', name: 'users', component: Users },
{ path: 'settings', name: 'settings', component: Settings }, { path: 'agentlist', name: 'agentlist', component: AgentList },
{ path: 'users', name: 'users', component: Users }, { path: 'train', name: 'train', component: Train },
{ path: 'train', name: 'train', component: Train}, { path: 'chat', name: 'chat', component: Chat },
{ path: 'dashboardview', name: 'dashboard-view', component: DashboardView}, {
{ path: 'chat', name: 'chat', component: Chat }, path: 'users/edit/:id',
name: 'editUser',
component: UserEditPage,
{ },
path: 'users/edit/:id', // Removido `/` do início para alinhar ao filho de dashboard {
name: 'editUser', path: '/agents',
component: UserEditPage, // Agora refere diretamente ao componente name: 'agentList', // Este é o nome que deve ser usado no router.push
}, component: AgentList
] },
} {
] path: '/agents/create',
name: 'createAgent',
component: Train
},
{
path: 'agents/:id',
name: 'agentChat',
component: AgentChat,
props: true, // Permite passar o parâmetro `id` como prop para o componente
},
],
},
{ path: '/:pathMatch(.*)*', redirect: '/login' }, // Fallback route
];
const router = createRouter({ const router = createRouter({
history: createWebHistory(), history: createWebHistory(),

View File

@ -0,0 +1,43 @@
import axios from 'axios';
// Configurar a URL base da API a partir do .env
const api = axios.create({
baseURL: process.env.VUE_APP_API_URL,
});
// Função para criar um usuário
export const createUser = (userData) => {
return api.post('/create-user', userData);
};
// Função para criar um agente
export const createAgent = (agentData) => {
return api.post('/create-agent', agentData);
};
// Função para listar agentes de um usuário
export const listAgents = (userId) => {
return api.get('/list-agents', { params: { user_id: userId } });
};
// Função para treinar um agente
export const trainAgent = (trainingData) => {
return api.post('/train-agent', trainingData);
};
// Função para interagir com um agente
export const interactAgent = (interactionData) => {
return api.post('/interact-agent', interactionData);
};
// Função para atualizar um agente
export const updateAgent = (agentId, agentData) => {
return api.put(`/update-agent/${agentId}`, agentData);
};
// Função para excluir um agente
export const deleteAgent = (agentId) => {
return api.delete(`/delete-agent/${agentId}`);
};
export default api;

40
src/stores/agentStore.js Normal file
View File

@ -0,0 +1,40 @@
import { defineStore } from 'pinia';
import { ref } from 'vue';
export const useAgentStore = defineStore('agent', () => {
const agents = ref([]); // Lista de agentes criados
// Adiciona um novo agente
const addAgent = (agent) => {
const newAgent = {
...agent,
id: Date.now().toString(), // ID único
chatHistory: [], // Histórico de conversas
createdAt: new Date().toISOString(), // Data de criação
};
agents.value.push(newAgent);
return newAgent;
};
// Atualiza um agente existente
const updateAgent = (updatedAgent) => {
const index = agents.value.findIndex((agent) => agent.id === updatedAgent.id);
if (index !== -1) {
agents.value[index] = { ...agents.value[index], ...updatedAgent };
} else {
throw new Error(`Agente com ID ${updatedAgent.id} não encontrado.`);
}
};
// Busca um agente pelo ID
const getAgentById = (id) => {
return agents.value.find((agent) => agent.id === id);
};
return {
agents,
addAgent,
updateAgent, // Certifique-se de que está aqui
getAgentById,
};
});

View File

@ -55,6 +55,9 @@ export const useAuthStore = defineStore("auth", {
} }
); );
this.user = response.data.user; // Deve incluir o id
this.token = response.data.token;
this.setAuthData(response.data); this.setAuthData(response.data);

View File

@ -99,31 +99,37 @@ export const useUserStore = defineStore('users', {
try { try {
this.loading = true; this.loading = true;
const authStore = useAuthStore(); const authStore = useAuthStore();
const token = authStore.token; // Obtém o token de autenticação const token = authStore.token;
// Formata os dados conforme o esperado pela API // Função para formatar a data no formato YYYY-MM-DD
const formatDateForMySQL = (dateString) => {
if (!dateString) return null;
const date = new Date(dateString);
return date.toISOString().split('T')[0]; // Retorna apenas a parte da data
};
const formattedData = { const formattedData = {
birth_date: new Date(userData.birth_date).toISOString(), // Corrigido para toISOString birth_date: formatDateForMySQL(userData.birth_date),
email: userData.email, email: userData.email,
parent_id: userData.parent_id, parent_id: userData.parent_id,
password: userData.password, password: userData.password,
phone: userData.phone, phone: userData.phone,
profile_image: userData.profile_image, profile_image: userData.profile_image,
username: userData.username, username: userData.username,
deleted: false // Adiciona o campo deleted deleted: false
}; };
console.log("📤 Enviando dados para API:", formattedData); console.log("📤 Enviando dados para API:", formattedData);
const response = await api.post('/users/', formattedData, { const response = await api.post('/users/', formattedData, {
headers: { headers: {
'Content-Type': 'application/json', 'Content-Type': 'application/json',
'Authorization': `Bearer ${token}` 'Authorization': `Bearer ${token}`
}, },
}); });
console.log("✅ Resposta da API:", response.data); console.log("✅ Resposta da API:", response.data);
await this.fetchUsersHierarchy(); await this.fetchUsersHierarchy();
return response.data; return response.data;
} catch (error) { } catch (error) {
@ -134,22 +140,25 @@ export const useUserStore = defineStore('users', {
} }
}, },
// Na função updateUser
async updateUser(userId, userData) { async updateUser(userId, userData) {
try { try {
this.loading = true; this.loading = true;
const authStore = useAuthStore(); const authStore = useAuthStore();
const token = authStore.token; // Obtém o token de autenticação const token = authStore.token;
// Formata os dados conforme o esperado pela API // Formata os dados conforme o esperado pela API
const formattedData = { const formattedData = {
birth_date: new Date(userData.birth_date).toISOString(), // Corrigido para toISOString // Verifica se birth_date existe e é válido antes de converter
birth_date: userData.birth_date ? new Date(userData.birth_date).toISOString() : null,
email: userData.email, email: userData.email,
parent_id: userData.parent_id, parent_id: userData.parent_id,
password: userData.password, // Somente inclui password se foi fornecido
...(userData.password ? { password: userData.password } : {}),
phone: userData.phone, phone: userData.phone,
profile_image: userData.profile_image, profile_image: userData.profile_image,
username: userData.username, username: userData.username,
deleted: userData.deleted // Mantém o campo deleted deleted: userData.deleted || false
}; };
console.log("📤 Enviando dados para API:", formattedData); console.log("📤 Enviando dados para API:", formattedData);

143
src/views/AgentChat.vue Normal file
View File

@ -0,0 +1,143 @@
<template>
<v-container>
<v-btn to="agentlist" color="primary" class="mb-4">
Voltar para a Lista
</v-btn>
<v-card v-if="agent">
<v-toolbar color="primary">
<v-toolbar-title>Conversando com {{ agent.name }}</v-toolbar-title>
<v-toolbar-subtitle>{{ agent.service }}</v-toolbar-subtitle>
</v-toolbar>
<v-card-text>
<div class="chat-container">
<v-list class="chat-messages">
<v-list-item
v-for="(message, index) in agent.chatHistory"
:key="index"
>
<v-list-item-title
:class="{
'user-message': message.sender === 'user',
'agent-message': message.sender === 'agent',
}"
>
<strong>{{ message.sender }}:</strong>
<span v-html="formatMessageText(message.text)"></span>
</v-list-item-title>
</v-list-item>
</v-list>
</div>
<v-text-field
v-model="userMessage"
label="Digite sua mensagem"
placeholder="Pergunte algo ao agente"
variant="outlined"
density="compact"
@keyup.enter="sendMessage"
></v-text-field>
<v-btn
color="primary"
block
@click="sendMessage"
:disabled="!userMessage.trim()"
>
Enviar
</v-btn>
</v-card-text>
</v-card>
<v-alert v-else type="error" color="error">
Agente não encontrado. Verifique o ID fornecido.
</v-alert>
</v-container>
</template>
<script setup>
import { ref, computed, watch } from 'vue';
import { useRoute } from 'vue-router';
import { useAgentStore } from '../stores/agentStore';
const route = useRoute();
const agentStore = useAgentStore();
const userMessage = ref('');
// Busca o agente pelo ID da rota
const agent = computed(() => {
return agentStore.getAgentById(route.params.id);
});
// Formata o texto das mensagens
const formatMessageText = (text) => {
return text.replace(/\n/g, '<br>');
};
// Envia uma mensagem para o agente
const sendMessage = () => {
if (userMessage.value.trim()) {
const message = {
sender: 'user',
text: userMessage.value.trim(),
timestamp: new Date().toISOString(),
};
agent.value.chatHistory.push(message);
// Simula uma resposta do agente
setTimeout(() => {
const response = {
sender: 'agent',
text: `Você disse: "${message.text}". Esta é uma resposta simulada.`,
timestamp: new Date().toISOString(),
};
agent.value.chatHistory.push(response);
}, 1000);
userMessage.value = '';
}
};
// Rolagem automática para a última mensagem
watch(
() => agent.value.chatHistory.length,
() => {
setTimeout(() => {
const container = document.querySelector('.chat-container');
if (container) {
container.scrollTop = container.scrollHeight;
}
}, 50);
},
{ deep: true }
);
</script>
<style scoped>
.chat-container {
height: 400px;
overflow-y: auto;
border: 1px solid #e0e0e0;
border-radius: 8px;
padding: 16px;
margin-bottom: 16px;
background-color: #f5f5f5;
}
.user-message {
text-align: right;
background-color: #e3f2fd;
border-radius: 8px;
padding: 8px;
margin: 4px 0;
}
.agent-message {
text-align: left;
background-color: #f3e5f5;
border-radius: 8px;
padding: 8px;
margin: 4px 0;
}
</style>

372
src/views/AgentList.vue Normal file
View File

@ -0,0 +1,372 @@
<template>
<v-container>
<v-row>
<v-col cols="12" class="d-flex justify-space-between align-center">
<h1>Meus Agentes</h1>
<v-btn
color="primary"
:to="{ name: 'createAgent' }"
prepend-icon="mdi-plus"
>
Novo Agente
</v-btn>
</v-col>
<v-col cols="12">
<v-row>
<v-col
v-for="agent in agents"
:key="agent.id"
cols="12"
sm="6"
md="4"
>
<v-card
class="agent-card"
outlined
rounded="lg"
>
<v-card-title>{{ agent.name }}</v-card-title>
<v-card-subtitle>{{ agent.description }}</v-card-subtitle>
<v-card-text>
<div class="d-flex flex-wrap gap-2 mt-2">
<v-chip>{{ agent.function }}</v-chip>
<v-chip>{{ agent.personality }}</v-chip>
<v-chip>{{ agent.model }}</v-chip>
</div>
</v-card-text>
<v-card-actions class="justify-space-between">
<v-btn
color="primary"
variant="text"
density="comfortable"
:to="{ name: 'agentChat', params: { id: agent.id } }"
>
Conversar
</v-btn>
<div class="d-flex">
<!-- Botão de Treino adicionado -->
<v-btn
icon
color="green-darken-1"
variant="text"
size="small"
@click="showTrainingDialog(agent)"
>
<v-icon>mdi-brain</v-icon>
</v-btn>
<v-btn
icon
color="blue-darken-1"
variant="text"
size="small"
@click="showEditDialog(agent)"
>
<v-icon>mdi-pencil</v-icon>
</v-btn>
<v-btn
icon
color="red-darken-1"
variant="text"
size="small"
@click="showDeleteDialog(agent)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</div>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-col>
</v-row>
<!-- Diálogo de confirmação para excluir -->
<v-dialog v-model="deleteDialog" max-width="400">
<v-card>
<v-card-title class="text-h5">Excluir Agente</v-card-title>
<v-card-text>
Tem certeza que deseja excluir o agente "{{ selectedAgent?.name }}"? Esta ação não poderá ser desfeita.
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey-darken-1" variant="text" @click="deleteDialog = false">Cancelar</v-btn>
<v-btn color="red-darken-1" variant="text" @click="deleteAgent">Excluir</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Modal de Edição -->
<v-dialog v-model="editDialog" max-width="600">
<v-card>
<v-card-title class="text-h5">Editar Agente</v-card-title>
<v-card-text>
<v-form ref="editForm" v-model="isFormValid">
<v-text-field
v-model="editedAgent.name"
label="Nome"
required
></v-text-field>
<v-textarea
v-model="editedAgent.description"
label="Descrição"
required
></v-textarea>
<v-text-field
v-model="editedAgent.function"
label="Função"
required
></v-text-field>
<v-text-field
v-model="editedAgent.personality"
label="Personalidade"
required
></v-text-field>
<v-text-field
v-model="editedAgent.model"
label="Modelo"
required
></v-text-field>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey-darken-1" variant="text" @click="editDialog = false">Cancelar</v-btn>
<v-btn color="blue-darken-1" variant="text" :disabled="!isFormValid" @click="updateAgent">Salvar</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
<!-- Modal de Treino do Agente -->
<v-dialog v-model="trainingDialog" max-width="600">
<v-card>
<v-card-title class="text-h5">Treinar Agente: {{ selectedAgent?.name }}</v-card-title>
<v-card-text>
<v-form ref="trainingForm" v-model="isTrainingFormValid">
<v-textarea
v-model="trainingData.trainingText"
label="Texto de Treinamento"
hint="Insira informações ou contexto para treinar o agente"
persistent-hint
required
rows="4"
></v-textarea>
<v-select
v-model="trainingData.trainingType"
:items="trainingTypes"
label="Tipo de Treinamento"
required
></v-select>
</v-form>
</v-card-text>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn color="grey-darken-1" variant="text" @click="trainingDialog = false">Cancelar</v-btn>
<v-btn
color="green-darken-1"
variant="text"
:disabled="!isTrainingFormValid"
@click="trainAgent"
>
Treinar
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</v-container>
</template>
<script setup>
import { ref, onMounted, onBeforeUnmount } from 'vue';
import { useRouter } from 'vue-router';
import axios from 'axios';
import { useAuthStore } from '../stores/auth';
const router = useRouter();
const authStore = useAuthStore();
const apiUrl = import.meta.env.VITE_API_AGENT;
const userId = ref(authStore.user?.id) || '1';
const agents = ref([]);
const deleteDialog = ref(false);
const editDialog = ref(false);
const trainingDialog = ref(false);
const selectedAgent = ref(null);
const editedAgent = ref({});
const isFormValid = ref(false);
// Novas variáveis para treinamento
const trainingData = ref({
trainingText: '',
trainingType: null
});
const isTrainingFormValid = ref(false);
const trainingTypes = [
'Conhecimento Geral',
'Contexto Específico',
'Personalidade',
'Estilo de Comunicação'
];
const fetchAgents = async () => {
try {
if (!authStore.user?.id) {
throw new Error('Usuário não autenticado');
}
const response = await axios.get(`${apiUrl}/new-api/list-agents`, {
params: { user_id: authStore.user.id },
headers: {
Authorization: `Bearer ${authStore.token}`,
},
});
agents.value = response.data.agents || [];
} catch (error) {
console.error('Erro ao buscar agentes:', error);
if (error.response?.status === 401) {
router.push({ name: 'login' });
}
}
};
const handleAgentCreated = () => {
fetchAgents(true);
};
onMounted(async () => {
await fetchAgents();
window.addEventListener('agent-created', handleAgentCreated);
});
onBeforeUnmount(() => {
window.removeEventListener('agent-created', handleAgentCreated);
});
const editAgent = (id) => {
router.push({ name: 'editAgent', params: { id } });
};
const showDeleteDialog = (agent) => {
selectedAgent.value = agent;
deleteDialog.value = true;
};
const showEditDialog = (agent) => {
editedAgent.value = { ...agent };
editDialog.value = true;
};
// Nova função para mostrar o modal de treinamento
const showTrainingDialog = (agent) => {
selectedAgent.value = agent;
trainingData.value = {
trainingText: '',
trainingType: null
};
trainingDialog.value = true;
};
const updateAgent = async () => {
try {
await axios.put(`${apiUrl}/new-api/update-agent/${editedAgent.value.id}`, editedAgent.value, {
headers: {
Authorization: `Bearer ${authStore.token}`,
},
});
await fetchAgents();
editDialog.value = false;
} catch (error) {
console.error('Erro ao atualizar agente:', error);
alert('Erro ao atualizar o agente. Por favor, tente novamente.');
}
};
const deleteAgent = async () => {
try {
await axios.delete(`${apiUrl}/new-api/delete-agent/${selectedAgent.value.id}`);
await fetchAgents();
deleteDialog.value = false;
} catch (error) {
console.error('Erro ao excluir agente:', error);
}
};
// Nova função para treinar o agente
const trainAgent = async () => {
try {
// Verifica se há dados válidos
if (!trainingData.value.trainingText || !trainingData.value.trainingType) {
alert('Por favor, preencha todos os campos de treinamento');
return;
}
const response = await axios.post(
`${apiUrl}/new-api/train-agent/${selectedAgent.value.id}`,
{
training_text: trainingData.value.trainingText,
training_type: trainingData.value.trainingType
},
{
headers: {
'Authorization': `Bearer ${authStore.token}`,
'Content-Type': 'application/json'
},
}
);
// Tratamento de resposta
if (response.data.success) {
alert('Agente treinado com sucesso!');
trainingDialog.value = false;
} else {
alert(response.data.message || 'Erro no treinamento');
}
} catch (error) {
console.error('Erro ao treinar agente:', error);
if (error.response) {
// Erro de resposta do servidor
alert(error.response.data.message || 'Erro ao treinar o agente');
} else if (error.request) {
// Erro de requisição
alert('Sem resposta do servidor');
} else {
// Erro na configuração
alert('Erro ao configurar treinamento');
}
}
};
</script>
<style scoped>
.agent-card {
cursor: pointer;
transition: all 0.3s;
border: 1px solid #4da3ff;
background-color: #f0f7ff;
height: 100%;
}
.agent-card:nth-child(odd) {
border-color: #ffd280;
background-color: #fffaf0;
}
.v-card-title {
font-size: 1.2rem;
font-weight: 500;
}
.agent-card:hover {
transform: translateY(-3px);
box-shadow: 0 4px 8px rgba(0, 0, 0, 0.1);
}
.gap-2 {
gap: 8px;
}
</style>

View File

@ -20,7 +20,7 @@
<!-- Cards de informações --> <!-- Cards de informações -->
<v-row> <v-row>
<!-- Total de Clientes --> <!-- Total de Usuários -->
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<v-card class="cards"> <v-card class="cards">
<v-card-title> <v-card-title>
@ -37,7 +37,7 @@
</v-card> </v-card>
</v-col> </v-col>
<!-- Total de Produtos --> <!-- Total de Agentes -->
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<v-card class="cards"> <v-card class="cards">
<v-card-title> <v-card-title>
@ -71,7 +71,7 @@
</v-card> </v-card>
</v-col> </v-col>
<!-- Total de Vendas --> <!-- Total de Falhas -->
<v-col cols="12" md="3"> <v-col cols="12" md="3">
<v-card class="cards"> <v-card class="cards">
<v-card-title> <v-card-title>
@ -148,21 +148,19 @@
</template> </template>
<script setup> <script setup>
import { ref } from 'vue' import { ref, onMounted } from 'vue';
import VueApexCharts from 'vue3-apexcharts' import { useUserStore } from '../stores/users';
import { useAgentStore } from '../stores/agentStore';
import VueApexCharts from 'vue3-apexcharts';
const totalUsuarios = ref(30) // Referências reativas para os valores
const totalAgentes = ref(5) const totalUsuarios = ref(0);
const totalFalhas = ref(5) const totalAgentes = ref(0);
const temperaturaAgente = ref(10) const totalFalhas = ref(0);
const temperaturaAgente = ref(0);
// Dados de vendas por categoria de produto // Dados de vendas por categoria de produto
const series = ref([ const series = ref([]);
{
name: 'Vendas',
data: [120, 200, 150, 180, 220], // Exemplo de dados de vendas por categoria
}
])
// Configurações do gráfico // Configurações do gráfico
const chartOptions = ref({ const chartOptions = ref({
@ -181,7 +179,7 @@ const chartOptions = ref({
enabled: false, enabled: false,
}, },
xaxis: { xaxis: {
categories: ['Eletrônicos', 'Roupas', 'Alimentos', 'Móveis', 'Brinquedos'], // Categorias de produtos categories: [], // Categorias de produtos
}, },
yaxis: { yaxis: {
title: { title: {
@ -207,9 +205,42 @@ const chartOptions = ref({
}, },
}); });
// Aplicando estilos ao contêiner do gráfico // Stores
const userStore = useUserStore();
const agentStore = useAgentStore();
// Função para carregar os dados
const loadData = async () => {
try {
// Atualizar total de usuários
const users = await userStore.catchUsers();
totalUsuarios.value = users.length;
// Atualizar total de agentes
const agents = agentStore.agents.filter(agent => !agent.isDeleted);
totalAgentes.value = agents.length;
// Atualizar temperatura dos agentes (exemplo: média de temperaturas)
temperaturaAgente.value = agents.reduce((sum, agent) => sum + (agent.temperature || 0), 0) / agents.length;
// Atualizar total de falhas (exemplo: agentes com status de falha)
totalFalhas.value = agents.filter(agent => agent.status === 'falha').length;
// Atualizar dados do gráfico
series.value = [
{
name: 'Vendas',
data: [120, 200, 150, 180, 220], // Substituir pelos dados reais
},
];
chartOptions.value.xaxis.categories = ['Eletrônicos', 'Roupas', 'Alimentos', 'Móveis', 'Brinquedos']; // Substituir pelas categorias reais
} catch (error) {
console.error('Erro ao carregar os dados:', error);
}
};
// Carregar os dados ao montar o componente
onMounted(loadData);
</script> </script>
<script> <script>

View File

@ -1,16 +0,0 @@
<template>
<v-container>
<v-row>
<v-col v-for="(item, index) in dashboardStore.items" :key="index" cols="12" md="4">
<CardItem :item="item" />
</v-col>
</v-row>
</v-container>
</template>
<script setup>
import { useDashboardStore } from '../stores/useDashboardStore';
import CardItem from '../components/CardItem.vue'
const dashboardStore = useDashboardStore();
</script>

View File

@ -152,6 +152,7 @@ export default {
const itemToDelete = ref(null); const itemToDelete = ref(null);
const page = ref(1); const page = ref(1);
const itemsPerPage = ref(10); const itemsPerPage = ref(10);
const selectedUser = ref(null);
// Loading states // Loading states
const loading = ref({ const loading = ref({
@ -316,10 +317,33 @@ export default {
}; };
const openEditPage = (type, item) => { const openEditPage = (type, item) => {
console.log('Usuário que será editado:', item); // Verifica se os dados estão corretos console.log('Usuário que será editado:', item); // Log para depuração
itemToEdit.value = { type, item }; // Atualiza os dados // Criar uma cópia do item para evitar problemas de mutação
openDialog('user', 'edit', item); const userData = { ...item };
// Garantir que todos os campos necessários estejam presentes
forms.value[type] = {
id: userData.id,
username: userData.username || '',
email: userData.email || '',
birth_date: userData.birth_date || '',
phone: userData.phone || '',
profile_image: userData.profile_image || ''
};
// Definir modo de edição e abrir o diálogo
isEditing.value = true;
dialogs.value[type] = true;
selectedUser.value = null; // Remover a seleção do usuário
};
const handleUserUpdated = (updatedUser) => {
const index = users.value.findIndex(user => user.id === updatedUser.id);
if (index !== -1) {
users.value[index] = updatedUser;
}
showNotification('Usuário atualizado com sucesso!');
}; };
const confirmDelete = (type, item) => { const confirmDelete = (type, item) => {
@ -439,6 +463,7 @@ export default {
page, page,
itemsPerPage, itemsPerPage,
itemToDelete, itemToDelete,
selectedUser,
// Computed // Computed
filteredUsers, filteredUsers,
@ -450,7 +475,8 @@ export default {
deleteItem, deleteItem,
openEditPage, openEditPage,
filterUsers, filterUsers,
exportToCSV exportToCSV,
handleUserUpdated
}; };
} }
}; };

View File

@ -10,6 +10,11 @@ export default defineConfig({
target: 'http://127.0.0.1:5000', target: 'http://127.0.0.1:5000',
changeOrigin: true, changeOrigin: true,
rewrite: (path) => path.replace(/^\/api/, '') rewrite: (path) => path.replace(/^\/api/, '')
},
'/new-api': { // Adicionando a nova API
target: 'http://127.0.0.1:5001',
changeOrigin: true,
rewrite: (path) => path.replace(/^\/new-api/, '')
} }
} }
} }