Files
perceptron-viewer/resources/js/components/PerceptronSetup.vue
T
Ninluc 25b03fc39a
linter / quality (push) Failing after 1m11s
tests / ci (8.3) (push) Successful in 3m55s
Cancel Training
2026-09-18 23:08:19 +02:00

406 lines
14 KiB
Vue

<script setup lang="ts">
import { router, useForm } from '@inertiajs/vue3';
import { ref, watch } from 'vue';
import {
Form,
FormControl,
FormField,
FormItem,
FormLabel,
} from '@/components/ui/form';
import {
NativeSelect,
NativeSelectOption,
} from '@/components/ui/native-select';
import { cancel } from '@/routes/perceptron';
import type {
Dataset,
InitializationMethod,
PerceptronType,
ValidationErrors,
} from '@/types/perceptron';
import Button from './ui/button/Button.vue';
import Card from './ui/card/Card.vue';
import CardContent from './ui/card/CardContent.vue';
import CardHeader from './ui/card/CardHeader.vue';
import CardTitle from './ui/card/CardTitle.vue';
import FormError from './ui/form/FormError.vue';
import Input from './ui/input/Input.vue';
import Spinner from './ui/spinner/Spinner.vue';
const props = defineProps<{
type: PerceptronType;
datasets: Dataset[];
selectedDataset: string;
initializationMethod: InitializationMethod;
hiddenLayers: number;
hiddenLayersNeurons: number;
minError: number;
defaultLearningRate: number;
sessionId: string;
defaultMaxIterations: number;
maxIterationsLimit: number;
errors: ValidationErrors;
}>();
const selectedDatasetCopy = ref(props.selectedDataset);
const selectedMethod = ref(props.initializationMethod);
const hiddenLayers = ref(props.hiddenLayers);
const hiddenLayersNeurons = ref(props.hiddenLayersNeurons);
const minError = ref(props.minError);
const learningRate = ref(props.defaultLearningRate);
const maxIterations = ref(props.defaultMaxIterations);
const maxIterationsInput = ref<{
inputElement: HTMLInputElement | null;
} | null>(null);
const form = useForm({
type: props.type,
dataset: selectedDatasetCopy.value,
weight_init_method: selectedMethod.value,
hidden_layers: hiddenLayers.value,
hidden_layers_neurons: hiddenLayersNeurons.value,
min_error: minError.value,
learning_rate: learningRate.value,
session_id: props.sessionId,
training_id: '',
max_iterations: maxIterations.value,
});
function handleMaxIterationsInput(event: Event) {
const input = event.target as HTMLInputElement;
const numericValue = Number(input.value);
const normalizedValue = Number.isFinite(numericValue)
? Math.min(Math.max(numericValue, 1), props.maxIterationsLimit)
: 1;
console.debug('[max_iterations] native input', {
raw: input.value,
numericValue,
normalizedValue,
limit: props.maxIterationsLimit,
});
input.value = String(normalizedValue);
maxIterations.value = normalizedValue;
}
watch(maxIterations, (value) => {
const input = maxIterationsInput.value?.inputElement;
console.debug('[max_iterations] ref changed', {
value,
displayed: input?.value,
});
if (input && input.value !== String(value)) {
input.value = String(value);
}
});
watch(selectedDatasetCopy, (newvalue) => {
form.clearErrors('dataset');
const selectedDatasetCopy =
props.datasets.find((dataset) => dataset.label === newvalue) || null;
// LearningRate
learningRate.value = props.defaultLearningRate;
if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultLearningRate !== undefined
) {
learningRate.value = selectedDatasetCopy.defaultLearningRate;
}
// MinError
minError.value = props.minError;
if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultMinError !== undefined
) {
minError.value = selectedDatasetCopy.defaultMinError;
}
// MaxIterations
maxIterations.value = props.defaultMaxIterations;
if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultMaxIterations !== undefined
) {
maxIterations.value = selectedDatasetCopy.defaultMaxIterations;
}
// HiddenLayers
hiddenLayers.value = props.hiddenLayers;
if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultHiddenLayers !== undefined
) {
hiddenLayers.value = selectedDatasetCopy.defaultHiddenLayers;
}
hiddenLayersNeurons.value = props.hiddenLayersNeurons;
if (
selectedDatasetCopy &&
selectedDatasetCopy.defaultHiddenLayersNeurons !== undefined
) {
hiddenLayersNeurons.value =
selectedDatasetCopy.defaultHiddenLayersNeurons;
}
});
const trainingId = ref<string>('');
function startTraining() {
if (!selectedDatasetCopy.value) {
form.setError(
'dataset',
"Un dataset est nécessaire avant de lancer l'entraînement.",
);
console.log(form.errors);
return;
}
trainingId.value = `${props.sessionId}-${Date.now()}`;
emit('update:trainingId', trainingId.value);
Object.assign(form, {
type: props.type,
dataset: selectedDatasetCopy.value,
weight_init_method: selectedMethod.value,
hidden_layers: hiddenLayers.value,
hidden_layers_neurons: hiddenLayersNeurons.value,
min_error: minError.value,
learning_rate: learningRate.value,
session_id: props.sessionId,
training_id: trainingId.value,
max_iterations: maxIterations.value,
});
console.debug('[max_iterations] submitting', {
displayed: maxIterationsInput.value?.inputElement?.value,
refValue: maxIterations.value,
formValue: form.max_iterations,
limit: props.maxIterationsLimit,
});
form.post('/perceptron/run', {
preserveScroll: true,
});
}
const emit = defineEmits(['update:selectedDataset', 'update:trainingId']);
async function cancelTraining() {
form.cancel();
router.post(
cancel(),
{
training_id: trainingId.value,
},
{
preserveScroll: true,
},
);
}
watch(selectedDatasetCopy, (newValue) => {
emit('update:selectedDataset', newValue);
});
</script>
<template>
<Card>
<CardHeader>
<CardTitle>Configuration du Perceptron</CardTitle>
</CardHeader>
<CardContent>
<Form
class="grid auto-cols-max grid-flow-row grid-cols-1 gap-4 space-y-6 md:grid-cols-2"
cancel-on-unmount
>
<!-- DATASET -->
<FormField name="dataset">
<FormItem>
<FormLabel>Dataset</FormLabel>
<FormControl>
<NativeSelect
name="dataset"
id="dataset-select"
v-model="selectedDatasetCopy"
class="cursor-pointer"
>
<NativeSelectOption value="" disabled
>Sélectionnez un dataset</NativeSelectOption
>
<NativeSelectOption
v-for="dataset in props.datasets"
v-bind:key="dataset.label"
:value="dataset.label"
>
{{ dataset.label.replace(/_/g, ' ') }}
</NativeSelectOption>
</NativeSelect>
</FormControl>
<FormError
:error="
form.errors.dataset ||
props.errors?.selectedDatasetCopy
"
/>
</FormItem>
</FormField>
<!-- DEFAULT WEIGHTS -->
<FormField name="weight_init_method">
<FormItem>
<FormLabel
>Méthode d'initialisation des poids</FormLabel
>
<FormControl>
<NativeSelect
name="weight_init_method"
id="weight_init_method"
v-model="selectedMethod"
class="cursor-pointer"
>
<NativeSelectOption
v-for="method in props.type == 'multilayer'
? ['random']
: ['zeros', 'random']"
v-bind:key="method"
:value="method"
>
{{ method }}
</NativeSelectOption>
</NativeSelect>
</FormControl>
</FormItem>
</FormField>
<!-- HIDDEN LAYERS -->
<FormField
name="hidden_layers"
v-if="props.type === 'multilayer'"
>
<FormItem>
<FormLabel>Nombre de couches cachées</FormLabel>
<FormControl>
<!-- TODO : MAX input -->
<Input
ref="maxIterationsInput"
type="number"
v-model="hiddenLayers"
min="1"
max="5"
step="1"
class="w-min"
/>
</FormControl>
</FormItem>
</FormField>
<!-- HIDDEN LAYERS NEURONS -->
<FormField
name="hidden_layers_neurons"
v-if="props.type === 'multilayer'"
>
<FormItem>
<FormLabel
>Nombre de neurones par couche cachée</FormLabel
>
<FormControl>
<!-- TODO : MAX input -->
<Input
type="number"
v-model="hiddenLayersNeurons"
min="1"
max="5"
step="1"
class="w-min"
/>
</FormControl>
</FormItem>
</FormField>
<!-- MIN ERROR -->
<FormField name="min_error" v-if="props.type !== 'simple'">
<FormItem>
<FormLabel>Erreur minimale</FormLabel>
<FormControl>
<Input
type="number"
v-model="minError"
min="0"
step="0.001"
class="w-min"
/>
</FormControl>
</FormItem>
</FormField>
<!-- LEARNING RATE -->
<FormField name="learning_rate">
<FormItem>
<FormLabel>Taux d'apprentissage</FormLabel>
<FormControl>
<Input
type="number"
v-model="learningRate"
min="0"
step="0.001"
class="w-min"
/>
</FormControl>
</FormItem>
</FormField>
<!-- MAX ITERATIONS -->
<FormField name="max_iterations">
<FormItem>
<FormLabel>Nombre maximum d'époques</FormLabel>
<FormControl>
<Input
type="number"
:model-value="maxIterations"
min="1"
:max="props.maxIterationsLimit"
step="1"
class="w-min"
@input="handleMaxIterationsInput"
/>
</FormControl>
<div
v-if="
form.errors.max_iterations ||
props.errors.max_iterations
"
>
{{
form.errors.max_iterations ||
props.errors.max_iterations
}}
</div>
</FormItem>
</FormField>
</Form>
<Transition name="fade">
<Button
variant="outline"
class="mt-6 cursor-pointer"
:disabled="form.processing"
@click="startTraining"
>Lancer<Spinner v-if="form.processing" class="ml-1"
/></Button>
</Transition>
<Transition name="fade">
<Button
variant="outline"
class="mt-6 ml-4 cursor-pointer"
@click="cancelTraining"
v-if="form.processing"
>Annuler</Button
>
</Transition>
</CardContent>
</Card>
</template>