Files
perceptron-viewer/resources/js/components/PerceptronSetup.vue
T
Ninluc 6d4fdffee9
linter / quality (push) Successful in 5m1s
tests / ci (8.4) (push) Successful in 5m8s
tests / ci (8.5) (push) Successful in 6m31s
Wiki
2026-09-15 19:44:58 +02:00

325 lines
12 KiB
Vue

<script setup lang="ts">
import { 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 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 Input from './ui/input/Input.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: '',
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) => {
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) {
alert('Veuillez sélectionner un dataset avant de lancer l\'entraînement.');
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']);
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"
>
<!-- 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>
<div v-if="props.errors?.selectedDatasetCopy">{{ props.errors?.selectedDatasetCopy }}</div>
</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>
<Button variant="outline" class="cursor-pointer mt-6" @click="startTraining">Lancer</Button>
</CardContent>
</Card>
</template>