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.** **JSON Server para a API mockada.**
## Clone o repositório: ## Clone o repositório:
- git clone https://github.com/seu-usuario/TARS-LAB.git - git clone https://gitlab.com/tars-labs/front-face-recognition.git
- cd olhe-ai - cd front-face-recognition
## Instale as dependências: ## Instale as dependências:
Certifique-se de ter o **Node.js** e o **npm** instalados. Em seguida, instale as dependências do projeto. 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: ## Inicie o servidor de desenvolvimento:
- npm run dev - npm run dev
## Inicie o servidor de produção:
- npx run mock
## Acesse a aplicação no navegador: ## Acesse a aplicação no navegador:
http://localhost:5173/ http://localhost:5173/
@ -46,101 +43,4 @@ Os testes de integração estão localizados na pasta tests/integration. Execute
---- ----
## Estrutura ## 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-container>
<v-card> <v-card>
<v-tabs v-model="activeTab"> <v-tabs v-model="activeTab">
<v-tab>Data/Hora</v-tab> <v-tab>Cadastro</v-tab>
<v-tab>Câmera_id</v-tab> <v-tab>Câmera ID</v-tab>
<v-tab>Pesquisar</v-tab> <v-tab>Pesquisar</v-tab>
<v-tab>Treino</v-tab> <v-tab>Treino</v-tab>
<v-tab>Lista de Nomes</v-tab> <v-tab>Lista de Nomes</v-tab>
</v-tabs> </v-tabs>
<v-card-text> <v-card-text>
<v-tabs-items v-model="activeTab"> <!-- Cadastro Tab -->
<!-- Tab Data/Hora --> <v-form v-if="activeTab === 0" @submit.prevent="registerCameraModel">
<v-tab-item> <v-row>
<v-row> <v-col cols="12" md="6">
<v-col cols="12" md="6"> <v-text-field
<v-menu v-model="cameraModel.name"
v-model="dateMenu" label="Nome do Modelo"
:close-on-content-click="false" required
max-width="290" ></v-text-field>
> </v-col>
<template v-slot:activator="{ on }"> <v-col cols="12" md="6">
<v-text-field <v-text-field
v-model="selectedDate" v-model="cameraModel.id"
label="Data" label="ID"
readonly required
v-on="on" ></v-text-field>
prepend-icon="mdi-calendar" </v-col>
></v-text-field> <v-col cols="12">
</template> <v-textarea
<v-date-picker v-model="cameraModel.description"
v-model="selectedDate" label="Descrição"
@change="dateMenu = false" rows="3"
></v-date-picker> ></v-textarea>
</v-menu> </v-col>
</v-col> <v-col cols="12">
<v-col cols="12" md="6"> <v-text-field
<v-menu type="datetime-local"
ref="timeMenu" v-model="cameraModel.registrationDate"
v-model="timeMenu" label="Data e Hora de Registro"
:close-on-content-click="false" required
:return-value="selectedTime" ></v-text-field>
max-width="290" </v-col>
> <v-col cols="12">
<template v-slot:activator="{ on }"> <v-btn type="submit" color="primary">Cadastrar Modelo</v-btn>
<v-text-field </v-col>
v-model="selectedTime" </v-row>
label="Hora" </v-form>
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>
<!-- Tab Câmera_id --> <!-- Pesquisar Tab -->
<v-tab-item> <v-form v-if="activeTab === 2" @submit.prevent="searchCameraModels">
<v-select <v-row>
v-model="selectedCamera" <v-col cols="12" md="6">
:items="cameras" <v-text-field
label="Selecione a câmera" v-model="searchCriteria.id"
item-text="name" label="ID do Modelo"
item-value="id" ></v-text-field>
prepend-icon="mdi-camera" </v-col>
></v-select> <v-col cols="12" md="6">
</v-tab-item> <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 --> <!-- Treino Tab -->
<v-tab-item> <v-row v-if="activeTab === 3">
<v-text-field <v-col>
v-model="searchQuery" <v-card outlined>
label="Pesquisar modelos" <v-card-title>Treinamento de Modelo de Câmera</v-card-title>
prepend-icon="mdi-magnify" <v-card-text>
clearable <v-row>
></v-text-field> <v-col cols="12" md="8">
</v-tab-item> <v-select
:items="untrainedCameras"
<!-- Tab Treino --> v-model="selectedCameraForTraining"
<v-tab-item> label="Selecionar Câmera"
<v-row> item-text="name"
<v-col cols="12"> return-object
<v-card ></v-select>
class="dropzone" </v-col>
:class="{ 'dropzone-active': isDragging }" <v-col cols="12" md="4" class="d-flex">
@dragenter.prevent="isDragging = true" <v-btn
@dragleave.prevent="isDragging = false" color="primary"
@dragover.prevent class="mr-2"
@drop.prevent="handleDrop" @click="startTraining"
> :disabled="!selectedCameraForTraining"
<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"
> >
<template v-slot:default="{ value }"> Iniciar Treinamento
<strong>{{ Math.ceil(value) }}%</strong> </v-btn>
</template> <v-btn
</v-progress-linear> color="success"
<div class="text-subtitle-1 mt-2">{{ trainingStatus }}</div> @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-text>
</v-card> </v-card>
</v-col> </v-card-text>
</v-row> </v-card>
</v-tab-item> </v-col>
</v-row>
<!-- Tab Lista de Nomes --> <!-- Lista de Nomes Tab -->
<v-tab-item> <v-row v-if="activeTab === 4" class="mt-4">
<v-row> <v-col md="6" sm="12">
<v-col cols="12"> <v-card outlined>
<v-text-field <v-card-title class="subtitle-1">
v-model="newName" Rostos Identificados
label="Adicionar nome" </v-card-title>
append-outer-icon="mdi-plus" <v-card-text>
@click:append-outer="addName" <v-list>
@keyup.enter="addName" <v-list-item
></v-text-field> v-for="(face, index) in identifiedFaces"
</v-col>
<v-col cols="12">
<v-chip-group column>
<v-chip
v-for="(name, index) in namesList"
:key="index" :key="index"
close
@click:close="removeName(index)"
> >
{{ name }} <v-list-item-avatar>
</v-chip> <v-icon large>mdi-account-circle</v-icon>
</v-chip-group> </v-list-item-avatar>
</v-col> <v-list-item-content>
</v-row> ID: {{ face.id }} - {{ face.name }}
</v-tab-item> <v-chip small :color="face.isActive ? 'success' : 'error'">
</v-tabs-items> {{ 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-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> </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> </v-container>
</app-layout> </app-layout>
</template> </template>
@ -251,75 +198,96 @@
export default { export default {
data() { data() {
return { return {
activeTab: null, activeTab: 0,
dateMenu: false, cameraModel: {
timeMenu: false, name: '',
selectedDate: '', id: '',
selectedTime: '', description: '',
selectedCamera: null, registrationDate: null
cameras: [], // A lista de câmeras
searchQuery: '',
uploadedFiles: [],
isDragging: false,
isTraining: false,
trainingProgress: 0,
trainingStatus: '',
namesList: [],
newName: '',
snackbar: {
show: false,
color: 'success',
text: '',
}, },
canStartTraining: false, searchCriteria: {
trainedModel: null, 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: { methods: {
handleDrop() { registerCameraModel() {
this.isDragging = false; console.log('Registrando modelo de câmera:', this.cameraModel);
this.cameraModel = {
name: '',
id: '',
description: '',
registrationDate: null
};
}, },
handleFileInput(event) { searchCameraModels() {
const files = event.target.files; console.log('Critérios de pesquisa:', this.searchCriteria);
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);
}, },
startTraining() { startTraining() {
this.isTraining = true; if (this.selectedCameraForTraining) {
// Lógica para iniciar treinamento this.isTraining = true;
}, this.trainingProgress = 0;
integrateModel() { this.trainingMessage = 'Iniciando treinamento...';
// Lógica para integrar o modelo
}, const simulateTraining = setInterval(() => {
addName() { this.trainingProgress += 20;
if (this.newName && !this.namesList.includes(this.newName)) {
this.namesList.push(this.newName); switch(this.trainingProgress) {
this.newName = ''; 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) { integrateModel() {
this.namesList.splice(index, 1); console.log('Integrando modelo:', this.selectedCameraForTraining);
this.trainingComplete = false;
this.isTraining = false;
}, },
}, confirmAssignments() {
}; console.log('Atribuições confirmadas:', this.unidentifiedFaces);
</script> }
}
<style scoped>
.dropzone {
border: 2px dashed #ccc;
padding: 20px;
text-align: center;
margin-top: 20px;
} }
</script>
.dropzone-active {
border-color: #3f51b5;
}
</style>

View File

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