diff options
Diffstat (limited to 'frontend')
14 files changed, 1121 insertions, 12 deletions
diff --git a/frontend/src/app/_data/Experiment.ts b/frontend/src/app/_data/Experiment.ts index 828431cc..cff77535 100644 --- a/frontend/src/app/_data/Experiment.ts +++ b/frontend/src/app/_data/Experiment.ts @@ -1,5 +1,4 @@ import { ProblemType } from "./Model"; - export default class Experiment { _id: string = ''; uploaderId: string = ''; diff --git a/frontend/src/app/_elements/dataset-load/dataset-load.component.ts b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts new file mode 100644 index 00000000..73dbf2d2 --- /dev/null +++ b/frontend/src/app/_elements/dataset-load/dataset-load.component.ts @@ -0,0 +1,100 @@ +import { Component, OnInit, ViewChild, ViewChildren } from '@angular/core'; +import { AddNewDatasetComponent } from '../add-new-dataset/add-new-dataset.component'; +import { ModelsService } from 'src/app/_services/models.service'; +import shared from 'src/app/Shared'; +import Dataset from 'src/app/_data/Dataset'; +import { DatatableComponent, TableData } from 'src/app/_elements/datatable/datatable.component'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import { CsvParseService } from 'src/app/_services/csv-parse.service'; +import { Output, EventEmitter } from '@angular/core'; +import { SignalRService } from 'src/app/_services/signal-r.service'; + +@Component({ + selector: 'app-dataset-load', + templateUrl: './dataset-load.component.html', + styleUrls: ['./dataset-load.component.css'] +}) +export class DatasetLoadComponent implements OnInit { + + @Output() selectedDatasetChangeEvent = new EventEmitter<Dataset>(); + + @ViewChild(AddNewDatasetComponent) addNewDatasetComponent!: AddNewDatasetComponent; + @ViewChild(AddNewDatasetComponent) datatable!: DatatableComponent; + + datasetLoaded: boolean = false; + selectedDatasetLoaded: boolean = false; + + showMyDatasets: boolean = true; + myDatasets?: Dataset[]; + existingDatasetSelected: boolean = false; + selectedDataset?: Dataset; + + tableData: TableData = new TableData(); + + term: string = ""; + + constructor(private models: ModelsService, private datasets: DatasetsService, private csv: CsvParseService, private signalRService: SignalRService) { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets; + }); + } + + viewMyDatasetsForm() { + this.showMyDatasets = true; + if (this.selectedDataset != undefined) + this.resetSelectedDataset(); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + } + viewNewDatasetForm() { + this.showMyDatasets = false; + if (this.selectedDataset != undefined) + this.resetSelectedDataset(); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + } + + refreshMyDatasets() { + this.datasets.getMyDatasets().subscribe((datasets) => { + this.myDatasets = datasets; + this.showMyDatasets = true; + }); + } + + selectThisDataset(dataset: Dataset) { + this.selectedDataset = dataset; + this.selectedDatasetLoaded = false; + this.existingDatasetSelected = true; + this.tableData.hasHeader = this.selectedDataset.hasHeader; + + this.tableData.hasInput = true; + this.tableData.loaded = false; + + this.datasets.getDatasetFile(dataset.fileId).subscribe((file: string | undefined) => { + if (file) { + this.tableData.loaded = true; + this.tableData.numRows = this.selectedDataset!.rowCount; + this.tableData.numCols = this.selectedDataset!.columnInfo.length; + this.tableData.data = this.csv.csvToArray(file, (dataset.delimiter == "razmak") ? " " : (dataset.delimiter == "") ? "," : dataset.delimiter); + //this.resetCbsAndRbs(); //TREBA DA SE DESI + //this.refreshThreeNullValueRadioOptions(); //TREBA DA SE DESI + this.selectedDatasetLoaded = true; + + this.selectedDatasetChangeEvent.emit(this.selectedDataset); + } + }); + } + + resetSelectedDataset(): boolean { + this.selectedDatasetChangeEvent.emit(this.selectedDataset); + return true; + } + + ngOnInit(): void { + if (this.signalRService.hubConnection) { + this.signalRService.hubConnection.on("NotifyDataset", _ => { + this.refreshMyDatasets(); + }); + } else { + console.warn("Dataset-Load: No connection!"); + } + } +} diff --git a/frontend/src/app/_elements/model-load/model-load.component.html b/frontend/src/app/_elements/model-load/model-load.component.html new file mode 100644 index 00000000..dcb35c21 --- /dev/null +++ b/frontend/src/app/_elements/model-load/model-load.component.html @@ -0,0 +1,215 @@ +<div> + <div class="d-flex flex-row justify-content-center align-items-center mt-3 mb-5"> + <button type="button" id="btnMyModel" class="btn" (click)="viewMyModelsForm()" + [ngClass]="{'btnType1': showMyModels, 'btnType2': !showMyModels}"> + Izaberite model iz kolekcije + </button> + <h3 class="mt-3 mx-3">ili</h3> + <button type="button" id="btnNewModel" class="btn" (click)="viewNewModelForm()" + [ngClass]="{'btnType1': !showMyModels, 'btnType2': showMyModels}"> + Dodajte novi model + </button> + </div> + + <div *ngIf="showMyModels" class="px-5 my-3"> + <input *ngIf="showMyModels" type="text" class="form-control" placeholder="Pretraga" [(ngModel)]="term"> + </div> + <div *ngIf="showMyModels" class="px-5"> + <div class="overflow-auto" style="max-height: 500px;"> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let model of myModels|filter:term|filter:(forExperiment ? forExperiment.type : '')" + [ngClass]="{'selectedModelClass': this.selectedModel == model}"> + <app-item-model name="usersModel" [model]="model" (click)="selectThisModel(model);"> + </app-item-model> + </li> + </ul> + <div class="px-5 mt-5"> + <!--prikaz izabranog modela--> + </div> + </div> + </div> + + + <div *ngIf="!showMyModels"> + <div class="form-group row mt-3 mb-2 d-flex justify-content-center"> + + <div class="col-3"> + <label for="name" class="col-form-label">Naziv modela:</label> + <input type="text" class="form-control" name="name" placeholder="Naziv..." [(ngModel)]="newModel.name"> + </div> + <div class="col-5"> + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="newModel.description"></textarea> + </div> + </div> + + </div> + <h2 class="mt-5 mb-4 mx-5">Parametri treniranja modela:</h2> + <div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3"> + <label for="type" class="col-form-label">Tip problema: </label> + </div> + <div class="col-2"> + <select id=typeOptions class="form-select" name="type" [(ngModel)]="newModel.type" + (change)="filterOptions()"> + <option + *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"></div> + <div class="col-3"> + <label for="hiddenLayers" class="col-form-label">Broj skrivenih slojeva: </label> + </div> + <div class="col-1"> + <input type="number" min="1" class="form-control" name="hiddenLayers" + [(ngModel)]="newModel.hiddenLayers" + (change)="newModel.hiddenLayerActivationFunctions = [].constructor(newModel.hiddenLayers).fill(newModel.hiddenLayerActivationFunctions[0])" + (ngModelChange)="updateGraph()"> + </div> + </div> + + <div class="row p-2"> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="optimizer" class="col-form-label">Optimizacija: </label> + </div> + <div class="col-2"> + <select id=optimizerOptions class="form-select" name="optimizer" [(ngModel)]="newModel.optimizer"> + <option + *ngFor="let option of Object.keys(Optimizer); let optionName of Object.values(Optimizer)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"> + </div> + <div class="col-3"> + <label for="hiddenLayerNeurons" class="col-form-label">Broj neurona skrivenih slojeva: </label> + </div> + <div class="col-1"> + <input type="number" min="1" class="form-control" name="hiddenLayerNeurons" + [(ngModel)]="newModel.hiddenLayerNeurons" (ngModelChange)="updateGraph()"> + </div> + </div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3"> + <label for="lossFunction" class="col-form-label">Funkcija troška: </label> + </div> + <div class="col-2"> + <select id=lossFunctionOptions class="form-select" name="lossFunction" + [(ngModel)]="newModel.lossFunction" aria-checked="true"> + <option + *ngFor="let option of Object.keys(lossFunction); let optionName of Object.values(lossFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col-1"></div> + <div class="col-3"> + <label for="batchSize" class="col-form-label">Broj uzorka po iteraciji: </label> + </div> + <div class="col-1"> + + <input type="number" min="0" step="1" max="7" class="form-control" name="batchSizePower" [(ngModel)]="batchSizePower" (click)="updateBatchSize()" > + {{newModel.batchSize}} + + </div> + + <div class="row p-2"> + <div class="col-1"></div> + <div class="col-3 m-1"> + <label for="epochs" class="col-form-label">Broj epoha: </label> + </div> + <div class="col-1"> + <input type="number" min="1" max="1000" class="form-control" name="epochs" + [(ngModel)]="newModel.epochs"> + </div> + </div> + </div> + + <div class="m-5"> + <app-graph [model]="newModel" [inputCols]="1"></app-graph> + </div> + + <h3 class="mx-5 mt-4">Aktivacione funkcije:</h3> + + <div class="row p-2" style="align-self: center;"> + <div class="col-1"></div> + <div class="col-3"> + <label for="hiddenLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije<br>skrivenih slojeva:</label> + </div> + <div class="col-2 mt-2"> + <div *ngFor="let item of [].constructor(newModel.hiddenLayers); let i = index"> + <div class="input-group mb-2"> + <div class="input-group-prepend"> + <span class="input-group-text">#{{i+1}}</span> + </div> + <select [id]="'hiddenLayerActivationFunctionOption_'+i" class="form-select" + [(ngModel)]="newModel.hiddenLayerActivationFunctions[i]" > + <option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + </div> + </div> + <div class="col-1"></div> + <div class="col-2"> + <label for="outputLayerActivationFunction" class="col-form-label" + style="text-align: center;">Funkcija aktivacije<br>izlaznog sloja:</label> + </div> + <div class="col-2 mt-2"> + <select id=outputLayerActivationFunctionOptions class="form-select" + name="outputLayerActivationFunction" [(ngModel)]="newModel.outputLayerActivationFunction"> + <option + *ngFor="let option of Object.keys(ActivationFunction); let optionName of Object.values(ActivationFunction)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="col"> + </div> + </div> + </div> + + <div class="form-check form-check-inline overflow-auto m-4" style="width: max-content;"> + <h3>Izaberite metrike:</h3> + <div id="divMetricsinput" class="mt-2 mx-5"> + + <div *ngFor="let option of Object.keys(metrics); let optionName of Object.values(metrics) " + class="form-check form-check-inline"> + + <input name="cbmetrics" class="form-check-input" type="checkbox" value="{{option}}" + id="metrics_{{option}}" style="float: left;" checked> + <label class="form-check-label" for="metrics_{{option}}" for="inlineCheckbox2"> + {{optionName}} + </label> + </div> + </div> + </div> + + <div class="form-group row mt-3 mb-3"> + <div class="col"></div> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" + (click)="uploadModel();">Sačuvaj + model</button> + <div class="col"></div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/_elements/model-load/model-load.component.ts b/frontend/src/app/_elements/model-load/model-load.component.ts new file mode 100644 index 00000000..dbca3d17 --- /dev/null +++ b/frontend/src/app/_elements/model-load/model-load.component.ts @@ -0,0 +1,114 @@ +import { Component, OnInit, ViewChild, Output, EventEmitter, Input } from '@angular/core'; +import Shared from 'src/app/Shared'; +import Experiment from 'src/app/_data/Experiment'; +import Model, { ActivationFunction, LossFunction, LossFunctionBinaryClassification, LossFunctionMultiClassification, LossFunctionRegression, Metrics, MetricsBinaryClassification, MetricsMultiClassification, MetricsRegression, NullValueOptions, Optimizer, ProblemType } from 'src/app/_data/Model'; +import { ModelsService } from 'src/app/_services/models.service'; +import { GraphComponent } from '../graph/graph.component'; + + +@Component({ + selector: 'app-model-load', + templateUrl: './model-load.component.html', + styleUrls: ['./model-load.component.css'] +}) +export class ModelLoadComponent implements OnInit { + + @ViewChild(GraphComponent) graph!: GraphComponent; + @Input() forExperiment?:Experiment; + @Output() selectedModelChangeEvent = new EventEmitter<Model>(); + + newModel: Model = new Model(); + myModels?: Model[]; + selectedModel?: Model; + + ProblemType = ProblemType; + ActivationFunction = ActivationFunction; + metrics: any = Metrics; + LossFunction = LossFunction; + Optimizer = Optimizer; + Object = Object; + document = document; + shared = Shared; + + term: string = ""; + selectedProblemType: string = ''; + selectedMetrics = []; + lossFunction: any = LossFunction; + + showMyModels: boolean = true; + + constructor(private modelsService: ModelsService) { + this.modelsService.getMyModels().subscribe((models) => { + this.myModels = models; + }); + } + + ngOnInit(): void { + } + batchSizePower:number=1; + updateBatchSize() + { + this.newModel.batchSize=2**this.batchSizePower; + } + + updateGraph() { + this.graph.update(); + } + + getMetrics() { + this.newModel.metrics = []; + let cb = document.getElementsByName("cbmetrics"); + + for (let i = 0; i < cb.length; i++) { + let chb = <HTMLInputElement>cb[i]; + if (chb.checked == true) + this.newModel.metrics.push(chb.value); + } + } + + uploadModel() { + this.getMetrics(); + + this.newModel.uploaderId = Shared.userId; + + this.modelsService.addModel(this.newModel).subscribe((response) => { + Shared.openDialog('Model dodat', 'Model je uspešno dodat u bazu.'); + // treba da se selektuje nov model u listi modela + //this.selectedModel = + }, (error) => { + Shared.openDialog('Greška', 'Model sa unetim nazivom već postoji u Vašoj kolekciji. Promenite naziv modela i nastavite sa kreiranim datasetom.'); + }); + } + + filterOptions() { + switch (this.newModel.type) { + case 'regresioni': + this.lossFunction = LossFunctionRegression; + this.metrics = MetricsRegression; + break; + case 'binarni-klasifikacioni': + this.lossFunction = LossFunctionBinaryClassification; + this.metrics = MetricsBinaryClassification; + break; + case 'multi-klasifikacioni': + this.lossFunction = LossFunctionMultiClassification; + this.metrics = MetricsMultiClassification; + break; + default: + break; + } + } + + viewMyModelsForm() { + this.showMyModels = true; + } + viewNewModelForm() { + this.showMyModels = false; + } + + selectThisModel(model: Model) { + this.selectedModel = model; + this.selectedModelChangeEvent.emit(this.selectedModel); + } + +} diff --git a/frontend/src/app/_modals/login-modal/login-modal.component.ts b/frontend/src/app/_modals/login-modal/login-modal.component.ts index 062a0550..ccd78509 100644 --- a/frontend/src/app/_modals/login-modal/login-modal.component.ts +++ b/frontend/src/app/_modals/login-modal/login-modal.component.ts @@ -50,7 +50,9 @@ export class LoginModalComponent implements OnInit { this.userInfoService.getUserInfo().subscribe((response) => { shared.photoId = response.photoId; }); + location.reload(); } + }); } else { diff --git a/frontend/src/app/_modals/register-modal/register-modal.component.ts b/frontend/src/app/_modals/register-modal/register-modal.component.ts index 3726b2e0..9da5484d 100644 --- a/frontend/src/app/_modals/register-modal/register-modal.component.ts +++ b/frontend/src/app/_modals/register-modal/register-modal.component.ts @@ -162,6 +162,7 @@ export class RegisterModalComponent implements OnInit { this.authService.authenticate(response); this.closeButton.nativeElement.click(); + location.reload(); //(<HTMLSelectElement>document.getElementById('linkToLoginModal')).click(); }, (error) => console.warn(error)); } diff --git a/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts new file mode 100644 index 00000000..66b3755e --- /dev/null +++ b/frontend/src/app/_pages/filter-datasets/filter-datasets.component.ts @@ -0,0 +1,48 @@ +import { Component, OnInit } from '@angular/core'; +import { DatasetsService } from 'src/app/_services/datasets.service'; +import Dataset from 'src/app/_data/Dataset'; +import {Router} from '@angular/router' +import { JwtHelperService } from '@auth0/angular-jwt'; +import { CookieService } from 'ngx-cookie-service'; +import shared from 'src/app/Shared'; + +@Component({ + selector: 'app-filter-datasets', + templateUrl: './filter-datasets.component.html', + styleUrls: ['./filter-datasets.component.css'] +}) +export class FilterDatasetsComponent implements OnInit { + + shared = shared; + publicDatasets?: Dataset[]; + term: string = ""; + constructor(private datasets: DatasetsService,private router:Router, private cookie: CookieService) { + this.datasets.getPublicDatasets().subscribe((datasets) => { + this.publicDatasets = datasets; + }); + } + + ngOnInit(): void { + + } + addDataset(dataset: Dataset):void{ + //this.router.navigateByUrl('/predict?id='+id); + const helper = new JwtHelperService(); + const decodedToken = helper.decodeToken(this.cookie.get("token")); + const newDataset={...dataset}; + newDataset._id = ""; + newDataset.isPublic = false; + newDataset.lastUpdated = new Date(); + newDataset.uploaderId = decodedToken.uploaderId; + let name=prompt("Unesite naziv dataset-a",newDataset.name); + newDataset.name=name as string; + if(name!=null && name!="") + this.datasets.addDataset(newDataset).subscribe((response:string)=>{ + shared.openDialog("Obaveštenje", "Uspešno ste dodali dataset sa nazivom " + newDataset.name); + },(error)=>{ + shared.openDialog("Obaveštenje", "U svojoj kolekciji već imate dataset sa ovim imenom. Molimo Vas da unesete drugo ime."); + }); + + }; + +} diff --git a/frontend/src/app/_pages/my-models/my-models.component.html b/frontend/src/app/_pages/my-models/my-models.component.html new file mode 100644 index 00000000..9b281239 --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.html @@ -0,0 +1,43 @@ +<div id="header"> + <h1>Moji modeli</h1> +</div> +<div id="wrapper"> + <div id="container" class="container p-5" style="background-color: rgba(255, 255, 255, 0.8); min-height: 100%;"> + <div class="row mt-3 mb-2 d-flex justify-content-center"> + + <div class="col-sm-6" style="margin-bottom: 10px;"> + </div> + + <div class="row"> + <div class="col-sm-4" style="margin-bottom: 10px;" *ngFor="let model of myModels"> + <app-item-model [model]="model"></app-item-model> + + <div class="panel-footer row"><!-- panel-footer --> + <div class="col-xs-6 text-center"> + <div> + <button type="button" class="btn btn-default btn-lg"style="min-width: 7rem;float: left;" (click)="useThisModel(model)" mat-raised-button color="primary">Koristi + <span class="glyphicon glyphicon-search"></span> + </button> + <button (click)="deleteThisModel(model)" mat-raised-button color="warn" style="min-width: 7rem;float: right" ><mat-icon>delete</mat-icon></button> + + + </div> + </div> + </div><!-- end panel-footer --> + + + + </div> + </div> + <div class="text-center" *ngIf="this.myModels.length == 0" > + <h2>Nema rezultata</h2> + </div> + </div> + + </div> + + + + + + </div> diff --git a/frontend/src/app/_pages/my-models/my-models.component.ts b/frontend/src/app/_pages/my-models/my-models.component.ts new file mode 100644 index 00000000..d379fa69 --- /dev/null +++ b/frontend/src/app/_pages/my-models/my-models.component.ts @@ -0,0 +1,59 @@ +import { Component, OnInit } from '@angular/core'; +import { Router } from '@angular/router'; +import shared from 'src/app/Shared'; +import Model from 'src/app/_data/Model'; +import { ModelsService } from 'src/app/_services/models.service'; + +@Component({ + selector: 'app-my-models', + templateUrl: './my-models.component.html', + styleUrls: ['./my-models.component.css'] +}) +export class MyModelsComponent implements OnInit { + myModels: Model[] = []; + //myModel: Model; + + constructor(private modelsS : ModelsService, private router : Router) { + + + + } + + ngOnInit(): void { + this.getAllMyModels(); + + } +/* + editModel(): void{ + this.modelsS.editModel().subscribe(m => { + this.myModel = m; + + }) + } +*/ + +deleteThisModel(model: Model): void{ + shared.openYesNoDialog('Brisanje seta podataka','Da li ste sigurni da želite da obrišete model?',() => { + this.modelsS.deleteModel(model).subscribe((response) => { + this.getAllMyModels(); + }, (error) =>{ + if (error.error == "Model with name = {name} deleted") { + shared.openDialog("Obaveštenje", "Greška prilikom brisanja modela."); + } + }); + }); +} + + +useThisModel(model: Model): void{ + + this.router.navigate(['/training']) + +} + getAllMyModels(): void{ + this.modelsS.getMyModels().subscribe(m => { + this.myModels = m; + }); + } + +} diff --git a/frontend/src/app/_services/auth.service.ts b/frontend/src/app/_services/auth.service.ts index dd46615d..cc5ad688 100644 --- a/frontend/src/app/_services/auth.service.ts +++ b/frontend/src/app/_services/auth.service.ts @@ -22,7 +22,7 @@ export class AuthService { } register(user: any) { - return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user }, { responseType: 'text' }); + return this.http.post(`${Configuration.settings.apiURL}/auth/register`, { ...user },{ headers: this.authHeader() , responseType: 'text' }); } getGuestToken() { @@ -52,26 +52,19 @@ export class AuthService { } var property = jwtHelper.decodeToken(this.cookie.get('token')); var username = property['name']; - if (username != "") { this.refresher = setTimeout(() => { this.http.post(`${Configuration.settings.apiURL}/auth/renewJwt`, {}, { headers: this.authHeader(), responseType: 'text' }).subscribe((response) => { this.authenticate(response); }); }, exp.getTime() - new Date().getTime() - 60000); - } - else { - this.refresher = setTimeout(() => { - this.getGuestToken().subscribe((response) => { - this.authenticate(response); - }); - }, exp.getTime() - new Date().getTime() - 60000); - } + } addGuestToken() { this.getGuestToken().subscribe((token) => { this.authenticate(token); + location.reload(); }); } @@ -106,4 +99,15 @@ export class AuthService { authHeader() { return new HttpHeaders().set("Authorization", "Bearer " + this.cookie.get('token')); } + alreadyGuest(){ + if(this.cookie.check('token')){ + const token = this.cookie.get('token'); + const decodedToken = jwtHelper.decodeToken(token); + if(decodedToken.role=="Guest") + return true; + } + return false; + } + + } diff --git a/frontend/src/app/app.component.ts b/frontend/src/app/app.component.ts index f9bc2726..e301b46f 100644 --- a/frontend/src/app/app.component.ts +++ b/frontend/src/app/app.component.ts @@ -40,7 +40,8 @@ export class AppComponent implements OnInit, AfterViewInit { } }); if (!this.authService.isAuthenticated()) { - this.authService.addGuestToken(); + if(!this.authService.alreadyGuest()) + this.authService.addGuestToken(); } this.signalRService.startConnection(); //this.startHttpRequest(); diff --git a/frontend/src/app/experiment/experiment.component.html b/frontend/src/app/experiment/experiment.component.html new file mode 100644 index 00000000..62236cce --- /dev/null +++ b/frontend/src/app/experiment/experiment.component.html @@ -0,0 +1,255 @@ +<div id="header"> + <h1>Napravite svoju veštačku neuronsku mrežu</h1> +</div> +<div id="wrapper"> + <div id="container" class="container p-5" style="background-color: white; min-height: 100%;"> + + <div class="d-flex flex-row justify-content-center align-items-center my-3"> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="0">Izvor podataka</a> + <mat-icon>arrow_forward</mat-icon> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="1">Preprocesiranje</a> + <mat-icon>arrow_forward</mat-icon> + <a href="#" data-bs-target="#carouselExampleControls" data-bs-slide-to="2">Dodaj eksperiment</a> + </div> + + <div id="carouselExampleControls" class="carousel slide px-5 mt-5" data-bs-wrap="false" data-bs-ride="carousel" data-bs-interval="false"> + <div class="carousel-inner"> + <div class="carousel-item active mt-2"> + <h2 class="mb-5">1. Izvor podataka</h2> + <app-dataset-load (selectedDatasetChangeEvent)="updateDataset($event)"></app-dataset-load> + </div> + + <div class="carousel-item mt-2"> + <h2 class="mb-4">2. Preprocesiranje</h2> + + <div class="px-5"> + <h3>Biranje ulaznih i izlaznih kolona:</h3> + <div *ngIf="selectedDataset"> + <div class="row"> + <div class="col d-flex justify-content-center"> + <h3>Izaberite ulazne kolone:</h3> + <div id="divInputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> + <input class="form-check-input" type="checkbox" value="{{item.columnName}}" id="cb_{{item.columnName}}" name="cbsNew" [checked]="experiment.outputColumn != item.columnName" [disabled]="experiment.outputColumn == item.columnName" (change)="checkedColumnsChanged(item, 0); resetColumnEncodings();"> + <label class="form-check-label" for="cb_{{item.columnName}}"> + {{item.columnName}} + </label> + </div> + </div> + </div> + <div class="col d-flex justify-content-left"> + <h3>Izaberite izlaznu kolonu:</h3> + <div id="divOutputs" class="form-check mt-2"> + <br> + <div *ngFor="let item of selectedDataset.columnInfo; let i = index"> + <input class="form-check-input" type="radio" value="{{item.columnName}}" id="rb_{{item.columnName}}" name="rbsNew" [(ngModel)]="this.experiment.outputColumn" (change)="experiment.outputColumn = item.columnName; checkedColumnsChanged(item, 1); resetColumnEncodings();" + checked> + <label class="form-check-label" for="rb_{{item.columnName}}"> + {{item.columnName}} + </label> + </div> + </div> + </div> + </div> + </div> + + + <div class="mt-5 mb-3 mx-3" *ngIf="countSelectedNullCols() == 0"> + <h3 class="border p-2 text-center"><i>Izabrane kolone nemaju nedostajuće vrednosti koje možete popuniti.</i></h3> + </div> + + <div *ngIf="countSelectedNullCols() != 0"> + <h3 class="mt-5">Popunjavanje nedostajućih vrednosti:</h3> + <div class="form-check" *ngIf="selectedDataset"> + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteRows " class="form-check-input" value="deleteRows" name="fillMissing " id="delRows" checked data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> + <label for="delRows" class="form-check-label ">Obriši sve + redove sa nedostajućim vrednostima ({{selectedDataset.nullRows}} od {{selectedDataset.rowCount}})</label><br> + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.DeleteColumns " class="form-check-input" value="deleteCols" name="fillMissing " id="delCols" data-bs-toggle="collapse" data-bs-target="#fillMissingCustom.show"> + <label for="delCols" class="form-check-label ">Obriši sve + kolone sa nedostajućim vrednostima ({{countSelectedNullCols()}} od {{selectedDataset.columnInfo.length}})</label><br> + <input type="radio" [(ngModel)]="experiment.nullValues " [value]="NullValueOptions.Replace " class="form-check-input" name="fillMissing" id="replace" data-bs-toggle="collapse" data-bs-target="#fillMissingCustom:not(.show)"> + <label for="replace" class="form-check-label">Izabraću + vrednosti koje će da zamene nedostajuće vrednosti za svaku kolonu...</label><br><br> + <div class="collapse" id="fillMissingCustom"> + <div> + <label for="columnReplacers" class="form-label">Unesite zamenu za svaku kolonu:</label> + <div class="my-3 " *ngIf="getSelectedColumnsArrayWithoutNullVals().length> 0"> + <label class="text-center form-control mx-3 text-secondary"> + Kolone <span style="font-style: italic;" *ngFor="let colname of getSelectedColumnsArrayWithoutNullVals(); let i = index"> + <span *ngIf="i != getSelectedColumnsArrayWithoutNullVals().length - 1">{{colname}}, </span> + <span *ngIf="i == getSelectedColumnsArrayWithoutNullVals().length - 1">{{colname}} </span> + </span> + nemaju nedostajućih vrednosti za popunjavanje. + </label> + </div> + <div id="columnReplacers"> + <div *ngFor="let column of selectedColumnsInfoArray; let i = index" class="my-3"> + <div *ngIf="column.numNulls > 0"> + <span class="w-20 mx-3"> + {{column.columnName}} + <span class="small" style="color:gray;">({{column.numNulls}} null)</span> + </span> + + <div class="d-flex flex-row justify-content-end"> + <div class="flex-grow-3 mx-3 me-auto"> + <div class="input-group"> + <div class="input-group-prepend"> + <label [for]="'fillCol_'+column.columnName" class="form-control"> + Zameni + <input type="radio" [id]="'fillCol_'+column.columnName" + [name]="'delOp_'+column.columnName"> + </label> + </div> + <input type="text" class="form-control" [id]="'fillText_'+column.columnName" (keyup)="checkFillColRadio(column.columnName)" placeholder="Unesi vrednost..."> + + <div class="input-group-append"> + <select [id]="'replaceOptions'+i" class="form-control btn-primary" *ngIf="column.isNumber" (change)="replace($event, column); checkFillColRadio(column.columnName);"> + <option + *ngFor="let option of Object.keys(ReplaceWith); let optionName of Object.values(ReplaceWith)" + [value]="option"> + {{ optionName }} + </option> + </select> + <select [id]="'replaceOptions'+i" class="form-control btn-outline-primary" *ngIf="!column.isNumber && column.numNulls > 0" (change)="replace($event, column); checkFillColRadio(column.columnName);"> + <option *ngFor="let option of column.uniqueValues" [value]="option"> + {{ option }} + </option> + </select> + </div> + </div> + </div> + + <div class="flex-shrink-1 mx-3"> + <div class="input-group"> + <label class="form-control" [for]="'delCol_'+column.columnName">Izbriši + kolonu + <input type="radio" [id]="'delCol_'+column.columnName" + [name]="'delOp_'+column.columnName" + (change)="emptyFillTextInput(column.columnName)"></label> + </div> + </div> + + <div class="flex-shrink-1 mx-3"> + <div class="input-group"> + <label class="form-control" [for]="'delRows_'+column.columnName">Izbriši + redove + <input type="radio" [id]="'delRows_'+column.columnName" + [name]="'delOp_'+column.columnName" checked + (change)="emptyFillTextInput(column.columnName)"></label> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + </div> + + <div id="randomOptions" class="mt-5"> + <div class="p-2 m-2"> + <label for="type" class="form-check-label">Želite li da redosled podataka bude nasumičan?</label> + <input class="mx-3 form-check-input" type="checkbox" [(ngModel)]="experiment.randomOrder" type="checkbox" value="" checked> + </div> + <div class="border m-3"> + <div class="row p-2 m-2"> + <div class="col-4"> + <label for="splitYesNo" class="form-check-label"> + <h3>Podela test skupa: + <input id="splitYesNo" class="form-check-input" type="checkbox" + [checked]="experiment.randomTestSet" + (change)="experiment.randomTestSet = !experiment.randomTestSet"> + </h3> + </label> + </div> + <div class="col-8"> + trening + <mat-slider style="width: 85%;" min="10" max="90" step="10" value="10" name="randomTestSetDistribution" thumbLabel [disabled]="!experiment.randomTestSet" [(ngModel)]="tempTestSetDistribution"> + </mat-slider> + test + </div> + </div> + + <div class="row p-2 m-2"> + <label for="percentage" class="form-label">Procenat podataka koji se uzima za trening + skup:</label> + <input id="percentage" type="number" class="form-control mx-3" style=" max-width: 15%" min="10" max="90" step="10" value="90" [(ngModel)]="tempTestSetDistribution" [disabled]="!experiment.randomTestSet"> + </div> + </div> + </div> + + <div id="encodingsForColumns" class="px-3 my-5"> + <label for="encoding" class="col-form-label mb-2">Enkoding za izabrane kolone:</label> + + <div class="row d-flex flex-row justify-content-between align-items-center"> + <div class="col-5" *ngFor="let item of [].constructor(selectedColumnsInfoArray.length); let i = index"> + <div class="input-group mb-2"> + <div class="input-group-prepend"> + <span class="input-group-text">{{selectedColumnsInfoArray[i].columnName}}</span> + </div> + <select [id]="'encodingOption_'+i" class="form-select" [(ngModel)]="experiment.encodings[i].encoding"> + <option + *ngFor="let option of Object.keys(Encoding); let optionName of Object.values(Encoding)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + </div> + </div> + </div> + + </div> + + + </div> + + <div class="carousel-item mt-2"> + <h2 class="mb-4">3. Dodaj eskperiment</h2> + + <div class="row"> + <div class="col"></div> + <div class="col-8"> + <label for="name" class="col-form-label">Naziv eksperimenta:</label> + <input type="text" class="form-control mb-3" name="name" placeholder="Naziv..." [(ngModel)]="experiment.name"> + + <label for="desc" class="col-sm-2 col-form-label">Opis:</label> + <div> + <textarea class="form-control" name="desc" rows="3" [(ngModel)]="experiment.description"></textarea> + </div> + <label for="desc" class="col-sm-2 col-form-label mt-3">Tip problema:</label> + <div class="col-4"> + <select id="typeOptions" class="form-select" name="type" [(ngModel)]="experiment.type"> + <option + *ngFor="let option of Object.keys(ProblemType); let optionName of Object.values(ProblemType)" + [value]="option"> + {{ optionName }} + </option> + </select> + </div> + <div class="form-group row mt-5 mb-3"> + <div class="col"></div> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="saveExperiment();">Sačuvaj + eksperiment</button> + <div class="col"></div> + </div> + </div> + <div class="col"></div> + </div> + </div> + + </div> + + <div class="m-3 d-flex flex-row justify-content-between align-items-center" style=" margin-left: auto;"> + <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="prev"> + <mat-icon>arrow_backward</mat-icon> + </button> + <button mat-fab color="primary" data-bs-target="#carouselExampleControls" data-bs-slide="next"> + <mat-icon>arrow_forward</mat-icon> + </button> + </div> + </div> + </div> +</div>
\ No newline at end of file diff --git a/frontend/src/app/experiment/experiment.component.ts b/frontend/src/app/experiment/experiment.component.ts new file mode 100644 index 00000000..2d0f6ec5 --- /dev/null +++ b/frontend/src/app/experiment/experiment.component.ts @@ -0,0 +1,222 @@ +import { Component, OnInit } from '@angular/core'; +import Experiment, { NullValReplacer, NullValueOptions, ReplaceWith, Encoding } from '../_data/Experiment'; +import Model,{ProblemType} from '../_data/Model'; +import Dataset, { ColumnInfo } from '../_data/Dataset'; +import { ModelsService } from '../_services/models.service'; +import Shared from '../Shared'; +import { ExperimentsService } from '../_services/experiments.service'; +import { ColumnEncoding } from '../_data/Experiment'; + +@Component({ + selector: 'app-experiment', + templateUrl: './experiment.component.html', + styleUrls: ['./experiment.component.css'] +}) +export class ExperimentComponent implements OnInit { + + experiment: Experiment = new Experiment(); + selectedModel?: Model; + selectedDataset?: Dataset; + trainingResult: any; // any za sad, promeni kasnije + + NullValueOptions = NullValueOptions; + ReplaceWith = ReplaceWith; + Encoding = Encoding; + ColumnEncoding = ColumnEncoding; + Object = Object; + ProblemType=ProblemType; + selectedColumnsInfoArray: ColumnInfo[] = []; + selectedNotNullColumnsArray: string[] = []; + + tempTestSetDistribution = 90; + + constructor(private modelsService: ModelsService, private experimentsService: ExperimentsService) { + } + + ngOnInit(): void { + } + + updateDataset(dataset: Dataset) { + this.selectedDataset = dataset; + this.selectedColumnsInfoArray = this.selectedDataset.columnInfo; + this.selectedNotNullColumnsArray = []; + this.experiment.outputColumn = this.selectedDataset.columnInfo[this.selectedDataset.columnInfo.length - 1].columnName; + + this.resetColumnEncodings(); + console.log(this.experiment.encodings); + } + + resetColumnEncodings() { + this.experiment.encodings = []; + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + this.experiment.encodings.push(new ColumnEncoding(this.selectedColumnsInfoArray[i].columnName, Encoding.Label)); + } + } + + getInputById(id: string): HTMLInputElement { + return document.getElementById(id) as HTMLInputElement; + } + + arrayColumn = (arr: any[][], n: number) => [...this.dropEmptyString(new Set(arr.map(x => x[n])))]; + + dropEmptyString(set: Set<any>): Set<string> { + if (set.has("")) + set.delete(""); + if (set.has(null)) + set.delete(null); + if (set.has(undefined)) + set.delete(undefined); + return set; + } + + emptyFillTextInput(colName: string) { + (<HTMLInputElement>document.getElementById("fillText_" + colName)).value = ""; + } + + checkFillColRadio(colName: string) { + (<HTMLInputElement>document.getElementById("fillCol_" + colName)).checked = true; + } + + checkedColumnsChanged(checkedColumnInfo: ColumnInfo, buttonType: number) { //0-input,1-output + let col = this.selectedColumnsInfoArray.find(x => x.columnName == checkedColumnInfo.columnName); + if (buttonType == 0) { //inputCol + if (col == undefined) + this.selectedColumnsInfoArray.push(checkedColumnInfo); + else + this.selectedColumnsInfoArray = this.selectedColumnsInfoArray.filter(x => x.columnName != checkedColumnInfo.columnName); + } + else { //outputCol + if (col == undefined) //ako je vec cekiran neki output, samo dodaj sad ovaj, a taj output postaje input i ostaje u nizu + this.selectedColumnsInfoArray.push(checkedColumnInfo); + } + //console.log(this.selectedColumnsInfoArray); + } + + replace(event: Event, column: ColumnInfo) { + let option = (<HTMLInputElement>event.target).value; + + const input = (<HTMLInputElement>document.getElementById("fillText_" + column.columnName)); + if (column.isNumber) { + switch (option) { + case ReplaceWith.Max: + input.value = "" + column.max; + break; + case ReplaceWith.Min: + input.value = "" + column.min; + break; + case ReplaceWith.Mean: + input.value = "" + column.mean; + break; + case ReplaceWith.Median: + input.value = "" + column.median; + break; + case ReplaceWith.None: + break; + } + } else { + input.value = option; + } + } + + getSelectedColumnsArrayWithoutNullVals(): string[] { + let colNames: string[] = []; + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + if (oneColInfo.numNulls == 0) + colNames.push(oneColInfo.columnName); + } + return colNames; + } + + getNullValuesReplacersArray(): NullValReplacer[] { + let array: NullValReplacer[] = []; + + if (this.experiment.nullValues == NullValueOptions.Replace) { + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + + if (oneColInfo.numNulls > 0) { //ako kolona nema null vrednosti, ne dodajemo je u niz + if ((<HTMLInputElement>document.getElementById("delCol_" + oneColInfo.columnName)).checked) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.DeleteColumns, + value: "" + }); + } + else if ((<HTMLInputElement>document.getElementById("delRows_" + oneColInfo.columnName)).checked) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.DeleteRows, + value: "" + }); + } + else if (((<HTMLInputElement>document.getElementById("fillCol_" + oneColInfo.columnName)).checked)) { + array.push({ + column: oneColInfo.columnName, + option: NullValueOptions.Replace, + value: (<HTMLInputElement>document.getElementById("fillText_" + oneColInfo.columnName)).value + }); + } + } + } + } + return array; + } + + saveExperiment() { + if (this.selectedDataset == undefined) { + Shared.openDialog("Greška", "Izvor podataka nije izabran!"); + return; + } + if (this.experiment.outputColumn == '') { + Shared.openDialog("Greška", "Molimo Vas da izaberete izlaznu kolonu."); + return; + } + if (this.selectedColumnsInfoArray.length <= 1) { //jer izlazna je izabrana + Shared.openDialog("Greška", "Molimo Vas da izaberete ulazne kolone."); + return; + } + + this.experiment._id = ''; + this.experiment.uploaderId = ''; + this.experiment.datasetId = this.selectedDataset._id; + + let pom = this.selectedColumnsInfoArray.filter(x => x.columnName != this.experiment.outputColumn); + for (let i = 0; i < pom.length; i++) + this.experiment.inputColumns.push(pom[i].columnName); + + this.selectedColumnsInfoArray = this.selectedColumnsInfoArray.filter(x => x.numNulls > 0); //obavezno + this.experiment.nullValuesReplacers = this.getNullValuesReplacersArray(); + + this.experiment.randomTestSetDistribution = 1 - Math.round(this.tempTestSetDistribution / 100 * 10) / 10; + + console.log("Eksperiment:", this.experiment); + + this.experimentsService.addExperiment(this.experiment).subscribe((response) => { + this.experiment = response; + + this.selectedColumnsInfoArray = []; + this.selectedNotNullColumnsArray = []; + this.experiment.encodings = []; + + Shared.openDialog("Obaveštenje", "Eksperiment je uspešno kreiran."); + }, (error) => { + if (error.error == "Experiment with this name exists") { + Shared.openDialog("Greška", "Eksperiment sa unetim nazivom već postoji u Vašoj kolekciji. Unesite neki drugi naziv."); + } + }); + } + + countSelectedNullCols(): number { + let counter: number = 0; + + for (let i = 0; i < this.selectedColumnsInfoArray.length; i++) { + let oneColInfo = this.selectedColumnsInfoArray[i]; + if (oneColInfo.numNulls > 0) + ++counter; + } + return counter; + } +} diff --git a/frontend/src/app/training/training.component.html b/frontend/src/app/training/training.component.html new file mode 100644 index 00000000..672e75fb --- /dev/null +++ b/frontend/src/app/training/training.component.html @@ -0,0 +1,46 @@ +<div id="header"> + <h1>Trenirajte veštačku neuronsku mrežu</h1> +</div> +<div id="wrapper" class="mb-4"> + <div id="container" class="container p-5 row" style="background-color: white; min-height: 100%;"> + <div class="col"></div> + + <div class="col-10"> + + <h2>1. Izaberite eksperiment iz kolekcije</h2> + <div class="px-5 mt-5 mb-3"> + <input type="text" class="form-control" placeholder="Pretraga" + [(ngModel)]="term"> + </div> + <div class="overflow-auto px-5" style="max-height: 500px;"> + <ul class="list-group"> + <li class="list-group-item p-3" *ngFor="let experiment of myExperiments|filter:term" + [ngClass]="{'selectedExperimentClass': this.selectedExperiment == experiment}"> + <app-item-experiment [experiment]="experiment" + (click)="selectThisExperiment(experiment);"></app-item-experiment> + </li> + </ul> + </div> + + <h2 class="mt-5 mb-2">2. Izaberite model</h2> + <app-model-load (selectedModelChangeEvent)="selectModel($event)" [forExperiment]="selectedExperiment"></app-model-load> + + <h2 class="my-5">3. Treniranje modela</h2> + + <div class="d-flex flex-row justify-content-center align-items-center my-3"> + <button class="btn btn-lg col-4" style="background-color:#003459; color:white;" (click)="trainModel();">Treniraj + model</button> + </div> + + <h2 class="mt-5">Rezultati treniranja</h2> + <div class="m-3" *ngIf="trainingResult"> + <h2 class="my-2">Rezultati treniranja:</h2> + <p> + {{trainingResult}} + </p> + </div> + </div> + + <div class="col"></div> + </div> +</div>
\ No newline at end of file |