versão 20h07

This commit is contained in:
Thaís Ferreira 2025-01-31 20:08:03 -03:00
parent f3317dc54f
commit 712a87b696
3 changed files with 257 additions and 389 deletions

106
README.md
View File

@ -17,8 +17,8 @@ Pré-requisitos
**JSON Server para a API mockada.**
## Clone o repositório:
- git clone https://github.com/seu-usuario/TARS-LAB.git
- cd olhe-ai
- git clone https://gitlab.com/tars-labs/front-face-recognition.git
- cd front-face-recognition
## Instale as dependências:
Certifique-se de ter o **Node.js** e o **npm** instalados. Em seguida, instale as dependências do projeto.
@ -27,9 +27,6 @@ Certifique-se de ter o **Node.js** e o **npm** instalados. Em seguida, instale a
## Inicie o servidor de desenvolvimento:
- npm run dev
## Inicie o servidor de produção:
- npx run mock
## Acesse a aplicação no navegador:
http://localhost:5173/
@ -46,101 +43,4 @@ Os testes de integração estão localizados na pasta tests/integration. Execute
----
## Estrutura
├── public
│ ├── favicon.ico
│ └── index.html
├── src
│ ├── assets
│ │ ├── styles
│ │ │ ├── base.css
│ │ │ └── main.css
│ │ └── logo.svg
│ ├── components
│ │ ├── common
│ │ │ ├── BaseButton.vue
│ │ │ ├── BaseInput.vue
│ │ │ ├── BaseSelect.vue
│ │ │ ├── BaseTable.vue
│ │ │ └── LoadingSpinner.vue
│ │ ├── dashboard
│ │ │ ├── DashboardModelList.vue
│ │ │ └── ModelCard.vue
│ │ ├── icons
│ │ │ ├── IconCommunity.vue
│ │ │ ├── IconDocumentation.vue
│ │ │ ├── IconEcosystem.vue
│ │ │ ├── IconSupport.vue
│ │ │ └── IconTooling.vue
│ │ ├── reports
│ │ │ ├── DateFilter.vue
│ │ │ └── ReportTable.vue
│ │ ├── testing
│ │ │ ├── ApiTester.vue
│ │ │ └── TestingList.vue
│ │ ├── __tests__
│ │ │ └── HelloWorld.spec.js
│ │ ├── training
│ │ │ ├── CameraSelector.vue
│ │ │ └── TrainingModelList.vue
│ │ ├── users
│ │ │ ├── UserForm.vue
│ │ │ └── UserTable.vue
│ │ ├── AppLayout.vue
│ │ ├── HelloWorld.vue
│ │ └── TheWelcome.vue
│ ├── mock
│ │ ├── data.js
│ │ ├── db.json
│ │ ├── handlers.js
│ │ └── server.js
│ ├── plugins
│ │ └── vuetify.js
│ ├── router
│ │ └── index.js
│ ├── services
│ │ ├── api.js
│ │ ├── authservice.js
│ │ ├── modelservice.js
│ │ ├── reportservice.js
│ │ └── userservice.js
│ ├── store
│ │ ├── modules
│ │ │ ├── auth.js
│ │ │ ├── models.js
│ │ │ ├── reports.js
│ │ │ └── users.js
│ │ └── index.js
│ ├── utils
│ │ ├── constants.js
│ │ ├── helpers.js
│ │ └── validators.js
│ ├── views
│ │ ├── AboutView.vue
│ │ ├── DashboardView.vue
│ │ ├── HomeView.vue
│ │ ├── LoginView.vue
│ │ ├── ReportsView.vue
│ │ ├── TestingView.vue
│ │ ├── TrainingView.vue
│ │ └── UsersView.vue
│ ├── App.vue
│ └── main.js
├── store
│ └── index.js
├── tests
│ └── unit
│ ├── integration
│ │ ├── login.spec.js
│ │ └── users.spec.js
│ ├── auth.spec.js
│ ├── models.spec.js
│ └── users.spec.js
├── db.json
├── index.html
├── jsconfig.json
├── package.json
├── package-lock.json
├── README.md
├── vite.config.js
├── vitest.config.js
└── vue.config.js
...

View File

@ -3,246 +3,193 @@
<v-container>
<v-card>
<v-tabs v-model="activeTab">
<v-tab>Data/Hora</v-tab>
<v-tab>Câmera_id</v-tab>
<v-tab>Cadastro</v-tab>
<v-tab>Câmera ID</v-tab>
<v-tab>Pesquisar</v-tab>
<v-tab>Treino</v-tab>
<v-tab>Lista de Nomes</v-tab>
</v-tabs>
<v-card-text>
<v-tabs-items v-model="activeTab">
<!-- Tab Data/Hora -->
<v-tab-item>
<v-row>
<v-col cols="12" md="6">
<v-menu
v-model="dateMenu"
:close-on-content-click="false"
max-width="290"
>
<template v-slot:activator="{ on }">
<v-text-field
v-model="selectedDate"
label="Data"
readonly
v-on="on"
prepend-icon="mdi-calendar"
></v-text-field>
</template>
<v-date-picker
v-model="selectedDate"
@change="dateMenu = false"
></v-date-picker>
</v-menu>
</v-col>
<v-col cols="12" md="6">
<v-menu
ref="timeMenu"
v-model="timeMenu"
:close-on-content-click="false"
:return-value="selectedTime"
max-width="290"
>
<template v-slot:activator="{ on }">
<v-text-field
v-model="selectedTime"
label="Hora"
readonly
v-on="on"
prepend-icon="mdi-clock"
></v-text-field>
</template>
<v-time-picker
v-model="selectedTime"
full-width
@click:minute="$refs.timeMenu.save(selectedTime)"
></v-time-picker>
</v-menu>
</v-col>
</v-row>
</v-tab-item>
<!-- Cadastro Tab -->
<v-form v-if="activeTab === 0" @submit.prevent="registerCameraModel">
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="cameraModel.name"
label="Nome do Modelo"
required
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="cameraModel.id"
label="ID"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-textarea
v-model="cameraModel.description"
label="Descrição"
rows="3"
></v-textarea>
</v-col>
<v-col cols="12">
<v-text-field
type="datetime-local"
v-model="cameraModel.registrationDate"
label="Data e Hora de Registro"
required
></v-text-field>
</v-col>
<v-col cols="12">
<v-btn type="submit" color="primary">Cadastrar Modelo</v-btn>
</v-col>
</v-row>
</v-form>
<!-- Tab Câmera_id -->
<v-tab-item>
<v-select
v-model="selectedCamera"
:items="cameras"
label="Selecione a câmera"
item-text="name"
item-value="id"
prepend-icon="mdi-camera"
></v-select>
</v-tab-item>
<!-- Pesquisar Tab -->
<v-form v-if="activeTab === 2" @submit.prevent="searchCameraModels">
<v-row>
<v-col cols="12" md="6">
<v-text-field
v-model="searchCriteria.id"
label="ID do Modelo"
></v-text-field>
</v-col>
<v-col cols="12" md="6">
<v-text-field
v-model="searchCriteria.name"
label="Nome do Modelo"
></v-text-field>
</v-col>
<v-col cols="12">
<v-btn type="submit" color="primary">Pesquisar</v-btn>
</v-col>
</v-row>
</v-form>
<!-- Tab Pesquisar -->
<v-tab-item>
<v-text-field
v-model="searchQuery"
label="Pesquisar modelos"
prepend-icon="mdi-magnify"
clearable
></v-text-field>
</v-tab-item>
<!-- Tab Treino -->
<v-tab-item>
<v-row>
<v-col cols="12">
<v-card
class="dropzone"
:class="{ 'dropzone-active': isDragging }"
@dragenter.prevent="isDragging = true"
@dragleave.prevent="isDragging = false"
@dragover.prevent
@drop.prevent="handleDrop"
>
<input
type="file"
ref="fileInput"
style="display: none"
multiple
accept="image/*"
@change="handleFileInput"
>
<div class="text-center pa-8">
<v-icon size="64" color="primary">mdi-cloud-upload</v-icon>
<div class="text-h6 mt-2">
Arraste as imagens aqui ou
<v-btn text color="primary" @click="$refs.fileInput.click()">
clique para selecionar
</v-btn>
</div>
<div class="text-subtitle-1 grey--text">
Suporta múltiplas imagens
</div>
</div>
</v-card>
</v-col>
<v-col cols="12" v-if="uploadedFiles.length > 0">
<v-card>
<v-card-title>Imagens Selecionadas</v-card-title>
<v-card-text>
<v-row>
<v-col
v-for="(file, index) in uploadedFiles"
:key="index"
cols="12"
sm="6"
md="4"
>
<v-card>
<v-img
:src="getFilePreview(file)"
aspect-ratio="1"
contain
></v-img>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
icon
color="error"
@click="removeFile(index)"
>
<v-icon>mdi-delete</v-icon>
</v-btn>
</v-card-actions>
</v-card>
</v-col>
</v-row>
</v-card-text>
</v-card>
</v-col>
<v-col cols="12" v-if="isTraining">
<v-card>
<v-card-text>
<div class="text-h6 mb-2">Treinando Modelo</div>
<v-progress-linear
:value="trainingProgress"
color="primary"
height="25"
<!-- Treino Tab -->
<v-row v-if="activeTab === 3">
<v-col>
<v-card outlined>
<v-card-title>Treinamento de Modelo de Câmera</v-card-title>
<v-card-text>
<v-row>
<v-col cols="12" md="8">
<v-select
:items="untrainedCameras"
v-model="selectedCameraForTraining"
label="Selecionar Câmera"
item-text="name"
return-object
></v-select>
</v-col>
<v-col cols="12" md="4" class="d-flex">
<v-btn
color="primary"
class="mr-2"
@click="startTraining"
:disabled="!selectedCameraForTraining"
>
<template v-slot:default="{ value }">
<strong>{{ Math.ceil(value) }}%</strong>
</template>
</v-progress-linear>
<div class="text-subtitle-1 mt-2">{{ trainingStatus }}</div>
Iniciar Treinamento
</v-btn>
<v-btn
color="success"
@click="integrateModel"
:disabled="!trainingComplete"
>
Integrar
</v-btn>
</v-col>
</v-row>
<v-card v-if="isTraining" class="mt-3">
<v-card-title>Detalhes do Treinamento</v-card-title>
<v-card-text>
<p><strong>Câmera:</strong> {{ selectedCameraForTraining.name }}</p>
<p><strong>ID:</strong> {{ selectedCameraForTraining.id }}</p>
<v-progress-linear
:value="trainingProgress"
color="primary"
height="15"
></v-progress-linear>
<div class="text-center mt-2">
{{ trainingMessage }}
</div>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-tab-item>
</v-card-text>
</v-card>
</v-col>
</v-row>
<!-- Tab Lista de Nomes -->
<v-tab-item>
<v-row>
<v-col cols="12">
<v-text-field
v-model="newName"
label="Adicionar nome"
append-outer-icon="mdi-plus"
@click:append-outer="addName"
@keyup.enter="addName"
></v-text-field>
</v-col>
<v-col cols="12">
<v-chip-group column>
<v-chip
v-for="(name, index) in namesList"
<!-- Lista de Nomes Tab -->
<v-row v-if="activeTab === 4" class="mt-4">
<v-col md="6" sm="12">
<v-card outlined>
<v-card-title class="subtitle-1">
Rostos Identificados
</v-card-title>
<v-card-text>
<v-list>
<v-list-item
v-for="(face, index) in identifiedFaces"
:key="index"
close
@click:close="removeName(index)"
>
{{ name }}
</v-chip>
</v-chip-group>
</v-col>
</v-row>
</v-tab-item>
</v-tabs-items>
<v-list-item-avatar>
<v-icon large>mdi-account-circle</v-icon>
</v-list-item-avatar>
<v-list-item-content>
ID: {{ face.id }} - {{ face.name }}
<v-chip small :color="face.isActive ? 'success' : 'error'">
{{ face.isActive ? 'Ativo' : 'Inativo' }}
</v-chip>
</v-list-item-content>
</v-list-item>
</v-list>
</v-card-text>
</v-card>
</v-col>
<v-col md="6" sm="12">
<v-card outlined>
<v-card-title class="subtitle-1">
Rostos Não Identificados
</v-card-title>
<v-card-text>
<v-list>
<v-list-item
v-for="(face, index) in unidentifiedFaces"
:key="index"
>
<v-list-item-avatar>
<v-icon large>mdi-account-circle</v-icon>
</v-list-item-avatar>
<v-list-item-action>
<v-select
:items="registeredUsers"
label="Selecionar Usuário"
v-model="face.selectedUser"
></v-select>
</v-list-item-action>
</v-list-item>
</v-list>
<v-btn
color="primary"
class="mt-3"
block
@click="confirmAssignments"
>
Confirmar Atribuições
</v-btn>
</v-card-text>
</v-card>
</v-col>
</v-row>
</v-card-text>
<v-divider></v-divider>
<v-card-actions>
<v-spacer></v-spacer>
<v-btn
color="primary"
:disabled="!canStartTraining || isTraining"
:loading="isTraining"
@click="startTraining"
>
Iniciar Treinamento
</v-btn>
<v-btn
text
:disabled="!trainedModel"
@click="integrateModel"
>
Integrar
</v-btn>
</v-card-actions>
</v-card>
<!-- Snackbar para feedback -->
<v-snackbar
v-model="snackbar.show"
:color="snackbar.color"
:timeout="3000"
>
{{ snackbar.text }}
<template v-slot:action="{ attrs }">
<v-btn
text
v-bind="attrs"
@click="snackbar.show = false"
>
Fechar
</v-btn>
</template>
</v-snackbar>
</v-container>
</app-layout>
</template>
@ -251,75 +198,96 @@
export default {
data() {
return {
activeTab: null,
dateMenu: false,
timeMenu: false,
selectedDate: '',
selectedTime: '',
selectedCamera: null,
cameras: [], // A lista de câmeras
searchQuery: '',
uploadedFiles: [],
isDragging: false,
isTraining: false,
trainingProgress: 0,
trainingStatus: '',
namesList: [],
newName: '',
snackbar: {
show: false,
color: 'success',
text: '',
activeTab: 0,
cameraModel: {
name: '',
id: '',
description: '',
registrationDate: null
},
canStartTraining: false,
trainedModel: null,
searchCriteria: {
id: '',
name: ''
},
selectedCameraForTraining: null,
isTraining: false,
trainingComplete: false,
trainingProgress: 0,
trainingMessage: '',
untrainedCameras: [
{ id: 'CAM003', name: 'Câmera Estacionamento', brand: 'Dahua' }
],
identifiedFaces: [
{
id: 'USER001',
name: 'João Silva',
isActive: true
},
{
id: 'USER002',
name: 'Maria Souza',
isActive: true
}
],
unidentifiedFaces: [
{ selectedUser: null },
{ selectedUser: null }
],
registeredUsers: ['João Silva', 'Maria Souza', 'Pedro Santos', 'Ana Oliveira']
};
},
methods: {
handleDrop() {
this.isDragging = false;
registerCameraModel() {
console.log('Registrando modelo de câmera:', this.cameraModel);
this.cameraModel = {
name: '',
id: '',
description: '',
registrationDate: null
};
},
handleFileInput(event) {
const files = event.target.files;
for (let i = 0; i < files.length; i++) {
this.uploadedFiles.push(files[i]);
}
},
getFilePreview(file) {
return URL.createObjectURL(file); // Retorna o preview da imagem
},
removeFile(index) {
this.uploadedFiles.splice(index, 1);
searchCameraModels() {
console.log('Critérios de pesquisa:', this.searchCriteria);
},
startTraining() {
this.isTraining = true;
// Lógica para iniciar treinamento
},
integrateModel() {
// Lógica para integrar o modelo
},
addName() {
if (this.newName && !this.namesList.includes(this.newName)) {
this.namesList.push(this.newName);
this.newName = '';
if (this.selectedCameraForTraining) {
this.isTraining = true;
this.trainingProgress = 0;
this.trainingMessage = 'Iniciando treinamento...';
const simulateTraining = setInterval(() => {
this.trainingProgress += 20;
switch(this.trainingProgress) {
case 20:
this.trainingMessage = 'Carregando modelos de reconhecimento...';
break;
case 40:
this.trainingMessage = 'Processando imagens de treinamento...';
break;
case 60:
this.trainingMessage = 'Ajustando parâmetros do modelo...';
break;
case 80:
this.trainingMessage = 'Finalizando treinamento...';
break;
case 100:
this.trainingMessage = 'Treinamento concluído!';
this.trainingComplete = true;
clearInterval(simulateTraining);
break;
}
}, 1000);
}
},
removeName(index) {
this.namesList.splice(index, 1);
integrateModel() {
console.log('Integrando modelo:', this.selectedCameraForTraining);
this.trainingComplete = false;
this.isTraining = false;
},
},
};
</script>
<style scoped>
.dropzone {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
margin-top: 20px;
confirmAssignments() {
console.log('Atribuições confirmadas:', this.unidentifiedFaces);
}
}
}
.dropzone-active {
border-color: #3f51b5;
}
</style>
</script>

View File

@ -3,7 +3,7 @@
<v-container>
<v-card>
<v-card-title>
USUÁRIOS
Usuários
<v-spacer></v-spacer>
<v-text-field
v-model="search"