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": {
|
"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",
|
||||||
|
|||||||
@ -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",
|
||||||
|
|||||||
@ -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: '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' },
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|||||||
@ -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>
|
||||||
@ -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;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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(),
|
||||||
|
|||||||
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,
|
||||||
|
};
|
||||||
|
});
|
||||||
@ -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);
|
||||||
|
|||||||
@ -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
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 -->
|
<!-- 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>
|
||||||
|
|||||||
@ -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 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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|||||||
@ -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/, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user