ajustes tela agente
This commit is contained in:
parent
fe1d816022
commit
03c35facd7
8
package-lock.json
generated
8
package-lock.json
generated
@ -10,7 +10,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"apexcharts": "^3.54.1",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"npm-check-updates": "^17.1.11",
|
||||
@ -837,9 +837,9 @@
|
||||
"integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
|
||||
},
|
||||
"node_modules/axios": {
|
||||
"version": "1.7.9",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.7.9.tgz",
|
||||
"integrity": "sha512-LhLcE7Hbiryz8oMDdDptSrWowmB4Bl6RCt6sIJKpRB4XtVf0iEgewX3au/pJqm+Py1kCASkb/FFKjxQaLtxJvw==",
|
||||
"version": "1.8.4",
|
||||
"resolved": "https://registry.npmjs.org/axios/-/axios-1.8.4.tgz",
|
||||
"integrity": "sha512-eBSYY4Y68NNlHbHBMdeDmKNtDgXWhQsJcGqzO3iLUM0GraQFSS9cVgPX5I9b3lbdFKyYoAEGAZF1DwhTaljNAw==",
|
||||
"dependencies": {
|
||||
"follow-redirects": "^1.15.6",
|
||||
"form-data": "^4.0.0",
|
||||
|
||||
@ -11,7 +11,7 @@
|
||||
"dependencies": {
|
||||
"@mdi/font": "^7.4.47",
|
||||
"apexcharts": "^3.54.1",
|
||||
"axios": "^1.7.9",
|
||||
"axios": "^1.8.4",
|
||||
"date-fns": "^4.1.0",
|
||||
"jwt-decode": "^4.0.0",
|
||||
"npm-check-updates": "^17.1.11",
|
||||
|
||||
@ -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>
|
||||
@ -34,8 +34,8 @@ const menuItems = [
|
||||
{ title: 'Admin', path: '/dashboard/profile', icon: 'mdi-account', requiredPermission: 'read' },
|
||||
{ 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: 'Agentes', path: '/dashboard/agentlist', icon: 'mdi mdi-account-group', 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' },
|
||||
]
|
||||
|
||||
|
||||
@ -1,264 +1,165 @@
|
||||
<template>
|
||||
<v-container fluid>
|
||||
<v-row justify="space-between">
|
||||
<!-- Card de Treinamento -->
|
||||
<v-col cols="12" md="6">
|
||||
<v-card class="mx-auto card" width="100%">
|
||||
<v-toolbar class="header">
|
||||
<v-toolbar-title class="text">Train Lucy</v-toolbar-title>
|
||||
<v-toolbar-title class="text">Criar Novo Agente</v-toolbar-title>
|
||||
</v-toolbar>
|
||||
<v-tabs v-model="tab" color="primary" grow>
|
||||
<v-tab value="1"><v-icon>mdi-file-pdf-box</v-icon></v-tab>
|
||||
<v-tab value="2"><v-icon>mdi-text-box</v-icon></v-tab>
|
||||
<v-tab value="3"><v-icon>mdi-web</v-icon></v-tab>
|
||||
</v-tabs>
|
||||
|
||||
<v-window v-model="tab">
|
||||
<!-- Aba 1: PDF ou Imagens -->
|
||||
<v-window-item value="1">
|
||||
<v-card-text>
|
||||
<div
|
||||
class="upload-box"
|
||||
@dragover.prevent="onDragOver"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
@drop.prevent="onDrop($event, ['pdf', 'png', 'jpg', 'jpeg'])"
|
||||
:class="{ 'dragging': isDragging }">
|
||||
<v-icon size="x-large" color="primary">mdi-cloud-upload</v-icon>
|
||||
<p>Arraste arquivos PDF ou imagens</p>
|
||||
</div>
|
||||
<v-file-input
|
||||
v-model="files"
|
||||
accept=".pdf,.png,.jpg,.jpeg"
|
||||
label="Enviar PDF ou Imagens"
|
||||
multiple
|
||||
<v-form @submit.prevent="createAgent">
|
||||
<v-text-field
|
||||
v-model="agentName"
|
||||
label="Nome do Agente*"
|
||||
placeholder="Ex: Assistente de Vendas"
|
||||
variant="outlined"
|
||||
@update:model-value="handleFileUpload($event, ['pdf', 'png', 'jpg', 'jpeg'])"
|
||||
></v-file-input>
|
||||
</v-card-text>
|
||||
</v-window-item>
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Nome é obrigatório']"
|
||||
></v-text-field>
|
||||
|
||||
<!-- Aba 2: Arquivos de Texto -->
|
||||
<v-window-item value="2">
|
||||
<v-card-text>
|
||||
<div
|
||||
class="upload-box"
|
||||
@dragover.prevent="onDragOver"
|
||||
@dragleave.prevent="onDragLeave"
|
||||
@drop.prevent="onDrop($event, ['txt'])"
|
||||
:class="{ 'dragging': isDragging }">
|
||||
<v-icon size="x-large" color="primary">mdi-cloud-upload</v-icon>
|
||||
<p>Arraste arquivos de Texto ou clique para enviar</p>
|
||||
</div>
|
||||
<v-file-input
|
||||
v-model="files"
|
||||
accept=".txt"
|
||||
label="Enviar Arquivos de Texto"
|
||||
multiple
|
||||
<v-text-field
|
||||
v-model="agentDescription"
|
||||
label="Descrição*"
|
||||
placeholder="Ex: Especialista em atendimento ao cliente"
|
||||
variant="outlined"
|
||||
@update:model-value="handleFileUpload($event, ['txt'])"
|
||||
></v-file-input>
|
||||
</v-card-text>
|
||||
</v-window-item>
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Descrição é obrigatória']"
|
||||
></v-text-field>
|
||||
|
||||
<v-select
|
||||
v-model="agentFunction"
|
||||
label="Função do Agente*"
|
||||
:items="functionOptions"
|
||||
variant="outlined"
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Função é obrigatória']"
|
||||
></v-select>
|
||||
|
||||
<v-select
|
||||
v-model="agentPersonality"
|
||||
label="Personalidade*"
|
||||
:items="personalityOptions"
|
||||
variant="outlined"
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Personalidade é obrigatória']"
|
||||
></v-select>
|
||||
|
||||
<v-select
|
||||
v-model="openAiModel"
|
||||
label="Modelo OpenAI*"
|
||||
:items="modelOptions"
|
||||
variant="outlined"
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Modelo é obrigatório']"
|
||||
></v-select>
|
||||
|
||||
<v-select
|
||||
v-model="toolType"
|
||||
label="Tipo de Agente*"
|
||||
:items="toolOptions"
|
||||
variant="outlined"
|
||||
required
|
||||
class="mb-4"
|
||||
:rules="[v => !!v || 'Tipo é obrigatório']"
|
||||
></v-select>
|
||||
|
||||
<!-- Aba 3: URLs -->
|
||||
<v-window-item value="3">
|
||||
<v-card-text>
|
||||
<v-btn
|
||||
color="primary"
|
||||
block
|
||||
@click="showUrlDialog = true"
|
||||
type="submit"
|
||||
:loading="isLoading"
|
||||
>
|
||||
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
|
||||
color="#4cc9f0"
|
||||
block
|
||||
@click="trainModel"
|
||||
:disabled="uploadedFiles.length === 0"
|
||||
>
|
||||
Treinar Lucy
|
||||
</v-btn>
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- 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
|
||||
Criar Agente
|
||||
</v-btn>
|
||||
</v-form>
|
||||
</v-card-text>
|
||||
</v-card>
|
||||
</v-col>
|
||||
</v-row>
|
||||
</v-container>
|
||||
</template>
|
||||
|
||||
<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 files = ref([])
|
||||
const uploadedFiles = ref([])
|
||||
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 router = useRouter();
|
||||
const authStore = useAuthStore();
|
||||
const apiUrl = import.meta.env.VITE_API_AGENT;
|
||||
|
||||
const formatFileSize = (size) => {
|
||||
if (size === 'N/A') return size
|
||||
const units = ['B', 'KB', 'MB', 'GB']
|
||||
let formattedSize = size
|
||||
let unitIndex = 0
|
||||
// Variáveis do formulário
|
||||
const agentName = ref('');
|
||||
const agentDescription = ref('');
|
||||
const agentFunction = ref('assistente geral');
|
||||
const agentPersonality = ref('neutro');
|
||||
const openAiModel = ref('gpt-4');
|
||||
const toolType = ref('chat');
|
||||
const isLoading = ref(false);
|
||||
|
||||
while (formattedSize >= 1024 && unitIndex < units.length - 1) {
|
||||
formattedSize /= 1024
|
||||
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]}`
|
||||
}
|
||||
isLoading.value = true;
|
||||
try {
|
||||
const payload = {
|
||||
user_id: authStore.user?.id, // Remove o fallback '1' (obrigatório ter usuário logado)
|
||||
name: agentName.value,
|
||||
description: agentDescription.value,
|
||||
personality: agentPersonality.value,
|
||||
function: agentFunction.value,
|
||||
model: openAiModel.value,
|
||||
tool_type: toolType.value,
|
||||
};
|
||||
|
||||
const handleFileUpload = (newFiles, allowedExtensions) => {
|
||||
if (!newFiles) return
|
||||
|
||||
const validFiles = Array.from(newFiles).filter(file => {
|
||||
const ext = file.name.split('.').pop().toLowerCase()
|
||||
return allowedExtensions.includes(ext)
|
||||
})
|
||||
|
||||
uploadedFiles.value.push(...validFiles)
|
||||
files.value = []
|
||||
}
|
||||
|
||||
const handleUrlUpload = () => {
|
||||
if (url.value.trim()) {
|
||||
uploadedFiles.value.push({
|
||||
name: url.value,
|
||||
type: 'url',
|
||||
size: 'N/A'
|
||||
})
|
||||
url.value = ''
|
||||
showUrlDialog.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 = ''
|
||||
const response = await axios.post(`${apiUrl}/new-api/create-agent`, payload);
|
||||
window.dispatchEvent(new CustomEvent('agent-created'));
|
||||
router.push({ name: 'agentList' });
|
||||
} catch (error) {
|
||||
console.error('Erro ao criar agente:', error);
|
||||
if (error.response?.status === 400) {
|
||||
alert('Usuário não autenticado. Faça login novamente.');
|
||||
router.push({ name: 'login' }); // Redireciona para login se o user_id for inválido
|
||||
}
|
||||
} finally {
|
||||
isLoading.value = false;
|
||||
}
|
||||
};
|
||||
</script>
|
||||
|
||||
<style scoped>
|
||||
@ -267,39 +168,16 @@ const sendMessage = () => {
|
||||
}
|
||||
|
||||
.card {
|
||||
color: #6c00b5;
|
||||
color: #333;
|
||||
border-radius: 25px;
|
||||
background: linear-gradient(145deg, #eeeded, #ffffff);
|
||||
box-shadow: 20px 20px 60px #d9d9d9,
|
||||
-20px -20px 60px #ffffff;
|
||||
}
|
||||
|
||||
.upload-box {
|
||||
margin: 20px 0;
|
||||
border: 2px dashed #ccc;
|
||||
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 {
|
||||
color: #6c00b5;
|
||||
font-weight: 600;
|
||||
color: #333;
|
||||
}
|
||||
</style>
|
||||
@ -60,7 +60,7 @@ const emit = defineEmits(['update:isModalOpen', 'save']);
|
||||
const userStore = useUserStore();
|
||||
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({
|
||||
id: props.modalUser?.id || null,
|
||||
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
|
||||
});
|
||||
|
||||
// Função para formatar a data no formato esperado pela API
|
||||
function formatBirthDate(dateString) {
|
||||
const date = parseISO(dateString);
|
||||
return format(date, "yyyy-MM-dd");
|
||||
// Sincroniza `isOpen` com `props.isModalOpen`
|
||||
watch(() => props.isModalOpen, (newValue) => {
|
||||
isOpen.value = newValue;
|
||||
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 { username, email, password, birth_date, phone } = localUser.value;
|
||||
@ -92,24 +124,6 @@ const isFormValid = computed(() => {
|
||||
(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 = () => {
|
||||
isOpen.value = false;
|
||||
};
|
||||
|
||||
@ -8,16 +8,14 @@ import Settings from '../views/Settings.vue'
|
||||
import Users from '../views/Users.vue'
|
||||
import UserEditPage from '../views/UserEditPage.vue'
|
||||
import Train from '../views/Train.vue'
|
||||
import DashboardView from '../views/DashboardView.vue'
|
||||
import Chat from '../views/Chat.vue'
|
||||
import Home from '../views/Home.vue'
|
||||
|
||||
import AgentList from '../views/AgentList.vue'
|
||||
import AgentChat from '../views/AgentChat.vue'
|
||||
|
||||
const routes = [
|
||||
{ path: '/', redirect: '/login' },
|
||||
{ path: '/login', component: Login },
|
||||
|
||||
|
||||
{
|
||||
path: '/dashboard',
|
||||
component: Dashboard,
|
||||
@ -27,19 +25,34 @@ const routes = [
|
||||
{ path: 'profile', name: 'profile', component: Profile },
|
||||
{ path: 'settings', name: 'settings', component: Settings },
|
||||
{ path: 'users', name: 'users', component: Users },
|
||||
{ path: 'agentlist', name: 'agentlist', component: AgentList },
|
||||
{ path: 'train', name: 'train', component: Train },
|
||||
{ path: 'dashboardview', name: 'dashboard-view', component: DashboardView},
|
||||
{ path: 'chat', name: 'chat', component: Chat },
|
||||
|
||||
|
||||
{
|
||||
path: 'users/edit/:id', // Removido `/` do início para alinhar ao filho de dashboard
|
||||
path: 'users/edit/:id',
|
||||
name: 'editUser',
|
||||
component: UserEditPage, // Agora refere diretamente ao componente
|
||||
component: UserEditPage,
|
||||
},
|
||||
]
|
||||
}
|
||||
]
|
||||
{
|
||||
path: '/agents',
|
||||
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({
|
||||
history: createWebHistory(),
|
||||
|
||||
43
src/services/apiService.js
Normal file
43
src/services/apiService.js
Normal 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
40
src/stores/agentStore.js
Normal 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,
|
||||
};
|
||||
});
|
||||
@ -56,6 +56,9 @@ export const useAuthStore = defineStore("auth", {
|
||||
|
||||
);
|
||||
|
||||
this.user = response.data.user; // Deve incluir o id
|
||||
this.token = response.data.token;
|
||||
|
||||
|
||||
this.setAuthData(response.data);
|
||||
|
||||
|
||||
@ -99,18 +99,24 @@ export const useUserStore = defineStore('users', {
|
||||
try {
|
||||
this.loading = true;
|
||||
const authStore = useAuthStore();
|
||||
const token = authStore.token; // Obtém o token de autenticação
|
||||
const token = authStore.token;
|
||||
|
||||
// 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
|
||||
};
|
||||
|
||||
// Formata os dados conforme o esperado pela API
|
||||
const formattedData = {
|
||||
birth_date: new Date(userData.birth_date).toISOString(), // Corrigido para toISOString
|
||||
birth_date: formatDateForMySQL(userData.birth_date),
|
||||
email: userData.email,
|
||||
parent_id: userData.parent_id,
|
||||
password: userData.password,
|
||||
phone: userData.phone,
|
||||
profile_image: userData.profile_image,
|
||||
username: userData.username,
|
||||
deleted: false // Adiciona o campo deleted
|
||||
deleted: false
|
||||
};
|
||||
|
||||
console.log("📤 Enviando dados para API:", formattedData);
|
||||
@ -134,22 +140,25 @@ export const useUserStore = defineStore('users', {
|
||||
}
|
||||
},
|
||||
|
||||
// Na função updateUser
|
||||
async updateUser(userId, userData) {
|
||||
try {
|
||||
this.loading = true;
|
||||
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
|
||||
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,
|
||||
parent_id: userData.parent_id,
|
||||
password: userData.password,
|
||||
// Somente inclui password se foi fornecido
|
||||
...(userData.password ? { password: userData.password } : {}),
|
||||
phone: userData.phone,
|
||||
profile_image: userData.profile_image,
|
||||
username: userData.username,
|
||||
deleted: userData.deleted // Mantém o campo deleted
|
||||
deleted: userData.deleted || false
|
||||
};
|
||||
|
||||
console.log("📤 Enviando dados para API:", formattedData);
|
||||
|
||||
143
src/views/AgentChat.vue
Normal file
143
src/views/AgentChat.vue
Normal 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
372
src/views/AgentList.vue
Normal 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>
|
||||
@ -20,7 +20,7 @@
|
||||
|
||||
<!-- Cards de informações -->
|
||||
<v-row>
|
||||
<!-- Total de Clientes -->
|
||||
<!-- Total de Usuários -->
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="cards">
|
||||
<v-card-title>
|
||||
@ -37,7 +37,7 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Total de Produtos -->
|
||||
<!-- Total de Agentes -->
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="cards">
|
||||
<v-card-title>
|
||||
@ -71,7 +71,7 @@
|
||||
</v-card>
|
||||
</v-col>
|
||||
|
||||
<!-- Total de Vendas -->
|
||||
<!-- Total de Falhas -->
|
||||
<v-col cols="12" md="3">
|
||||
<v-card class="cards">
|
||||
<v-card-title>
|
||||
@ -148,21 +148,19 @@
|
||||
</template>
|
||||
|
||||
<script setup>
|
||||
import { ref } from 'vue'
|
||||
import VueApexCharts from 'vue3-apexcharts'
|
||||
import { ref, onMounted } from 'vue';
|
||||
import { useUserStore } from '../stores/users';
|
||||
import { useAgentStore } from '../stores/agentStore';
|
||||
import VueApexCharts from 'vue3-apexcharts';
|
||||
|
||||
const totalUsuarios = ref(30)
|
||||
const totalAgentes = ref(5)
|
||||
const totalFalhas = ref(5)
|
||||
const temperaturaAgente = ref(10)
|
||||
// Referências reativas para os valores
|
||||
const totalUsuarios = ref(0);
|
||||
const totalAgentes = ref(0);
|
||||
const totalFalhas = ref(0);
|
||||
const temperaturaAgente = ref(0);
|
||||
|
||||
// Dados de vendas por categoria de produto
|
||||
const series = ref([
|
||||
{
|
||||
name: 'Vendas',
|
||||
data: [120, 200, 150, 180, 220], // Exemplo de dados de vendas por categoria
|
||||
}
|
||||
])
|
||||
const series = ref([]);
|
||||
|
||||
// Configurações do gráfico
|
||||
const chartOptions = ref({
|
||||
@ -181,7 +179,7 @@ const chartOptions = ref({
|
||||
enabled: false,
|
||||
},
|
||||
xaxis: {
|
||||
categories: ['Eletrônicos', 'Roupas', 'Alimentos', 'Móveis', 'Brinquedos'], // Categorias de produtos
|
||||
categories: [], // Categorias de produtos
|
||||
},
|
||||
yaxis: {
|
||||
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>
|
||||
|
||||
@ -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>
|
||||
@ -152,6 +152,7 @@ export default {
|
||||
const itemToDelete = ref(null);
|
||||
const page = ref(1);
|
||||
const itemsPerPage = ref(10);
|
||||
const selectedUser = ref(null);
|
||||
|
||||
// Loading states
|
||||
const loading = ref({
|
||||
@ -316,10 +317,33 @@ export default {
|
||||
};
|
||||
|
||||
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
|
||||
openDialog('user', 'edit', item);
|
||||
// Criar uma cópia do item para evitar problemas de mutação
|
||||
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) => {
|
||||
@ -439,6 +463,7 @@ export default {
|
||||
page,
|
||||
itemsPerPage,
|
||||
itemToDelete,
|
||||
selectedUser,
|
||||
|
||||
// Computed
|
||||
filteredUsers,
|
||||
@ -450,7 +475,8 @@ export default {
|
||||
deleteItem,
|
||||
openEditPage,
|
||||
filterUsers,
|
||||
exportToCSV
|
||||
exportToCSV,
|
||||
handleUserUpdated
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@ -10,6 +10,11 @@ export default defineConfig({
|
||||
target: 'http://127.0.0.1:5000',
|
||||
changeOrigin: true,
|
||||
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/, '')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
Reference in New Issue
Block a user