Wiki
linter / quality (push) Successful in 5m1s
tests / ci (8.4) (push) Successful in 5m8s
tests / ci (8.5) (push) Successful in 6m31s

This commit is contained in:
2026-09-15 19:44:58 +02:00
parent ca6c9fe38c
commit 6d4fdffee9
82 changed files with 29906 additions and 151 deletions
+123 -22
View File
@@ -1,5 +1,6 @@
<script setup lang="ts">
import { Head } from '@inertiajs/vue3';
import { useEventListener } from '@vueuse/core';
import {
Chart as ChartJS,
Title,
@@ -16,12 +17,26 @@ import { computed, nextTick, ref, watch } from 'vue';
import LinkHeader from '@/components/LinkHeader.vue';
import Button from '@/components/ui/button/Button.vue';
import ScrollArea from '@/components/ui/scroll-area/ScrollArea.vue';
import {
Tooltip as UiTooltip,
TooltipContent,
TooltipProvider,
TooltipTrigger,
} from '@/components/ui/tooltip';
import { usePerceptronTraining } from '@/composables/usePerceptronTraining';
import type { Dataset, DatasetPoint, InitializationMethod, PerceptronType } from '@/types/perceptron';
import type { Dataset, DatasetPoint, InitializationMethod, PerceptronType, ValidationErrors } from '@/types/perceptron';
import IterationTable from '../components/IterationTable.vue';
import PerceptronDecisionGraph from '../components/PerceptronDecisionGraph.vue';
import PerceptronIterationsErrorsGraph from '../components/PerceptronIterationsErrorsGraph.vue';
import PerceptronSetup from '../components/PerceptronSetup.vue';
import HelpText from '@/components/HelpText.vue';
import { BookOpenText } from '@lucide/vue';
import Drawer from '@/components/ui/drawer/Drawer.vue';
import DrawerTrigger from '@/components/ui/drawer/DrawerTrigger.vue';
import DrawerContent from '@/components/ui/drawer/DrawerContent.vue';
import DrawerTitle from '@/components/ui/drawer/DrawerTitle.vue';
import KbdGroup from '@/components/ui/kbd/KbdGroup.vue';
import Kbd from '@/components/ui/kbd/Kbd.vue';
ChartJS.register(
Title,
@@ -44,10 +59,15 @@ const props = defineProps<{
minError: number;
learningRate: number;
maxIterations: number;
maxIterationsLimit: number;
maxDisplayedWeights: number;
errors: ValidationErrors;
}>();
const selectedDatasetName = ref<string>('');
const selectedDataset = computed(() =>
props.datasets.find((item) => item.label === selectedDatasetName.value),
);
const dataset = computed<DatasetPoint[]>(() => {
const selected = props.datasets.find(
(d) => d.label === selectedDatasetName.value,
@@ -91,8 +111,42 @@ const {
trainingEndReason,
} = usePerceptronTraining(props.sessionId);
const isRegression = computed(
() => (props.type === 'multilayer' || props.type === 'monolayer') && activationFunction.value === 'linear',
() => selectedDataset.value?.inputCount === 1 && activationFunction.value === 'linear',
);
const drawerOpen = ref(false);
const helpScrollContainer = ref<HTMLElement | null>(null);
const helpScrollTop = ref(0);
const handleHelpScroll = (event: Event) => {
helpScrollTop.value = (event.currentTarget as HTMLElement).scrollTop;
};
const restoreHelpScroll = async () => {
await nextTick();
if (helpScrollContainer.value) {
helpScrollContainer.value.scrollTop = helpScrollTop.value;
}
};
const handleDrawerOpenChange = async (open: boolean) => {
if (!open && helpScrollContainer.value) {
helpScrollTop.value = helpScrollContainer.value.scrollTop;
}
drawerOpen.value = open;
if (open) {
await restoreHelpScroll();
}
};
useEventListener('keydown', (event: KeyboardEvent) => {
if (event.key.toLowerCase() === 'w' /* && (event.ctrlKey || event.metaKey)*/) {
event.preventDefault();
void handleDrawerOpenChange(!drawerOpen.value);
}
});
function getActivationFunction(type: string): (x: number) => number {
switch (type) {
case 'step':
@@ -118,31 +172,78 @@ watch([iterations, trainingEnded], async () => {
await nextTick();
iterationScrollArea.value?.followBottom();
});
</script>
<template>
<Head title="Perceptron Viewer"></Head>
<main class="flex min-h-dvh flex-col gap-6 pb-6 space-y-6">
<main class="flex min-h-dvh flex-col pb-6 space-y-6">
<LinkHeader class="w-full" />
<PerceptronSetup
:type="props.type"
:datasets="props.datasets"
:selectedDataset="selectedDatasetName"
:initializationMethod="initializationMethod"
:hidden-layers="hiddenLayers"
:hidden-layers-neurons="hiddenLayersNeurons"
:minError="props.minError"
:sessionId="props.sessionId"
:defaultLearningRate="props.learningRate"
:defaultMaxIterations="props.maxIterations"
@update:selected-dataset="
(newValue) => {
selectedDatasetName = newValue;
}
"
@update:training-id="setTrainingId"
/>
<Drawer
:open="drawerOpen"
swipe-direction="left"
@update:open="handleDrawerOpenChange"
>
<TooltipProvider :delay-duration="300">
<UiTooltip>
<TooltipTrigger as-child>
<DrawerTrigger as-child>
<Button
variant="default"
size="icon-xl"
class="sticky top-3 z-20 ml-3 cursor-pointer rounded-full"
title="Wiki"
aria-label="Wiki"
>
<BookOpenText class="size-7" />
</Button>
</DrawerTrigger>
</TooltipTrigger>
<TooltipContent side="right">
<span>Wiki </span>
<KbdGroup>
<Kbd>W</Kbd>
</KbdGroup>
</TooltipContent>
</UiTooltip>
</TooltipProvider>
<DrawerContent class="w-[100ch]! max-w-[85vw]!">
<DrawerTitle class="sr-only">Wiki</DrawerTitle>
<div
ref="helpScrollContainer"
class="no-scrollbar overflow-y-auto px-4"
@scroll="handleHelpScroll"
>
<HelpText
:markdownFileName="props.type"
@loaded="restoreHelpScroll"
/>
</div>
</DrawerContent>
</Drawer>
<PerceptronSetup
:type="props.type"
:datasets="props.datasets"
:selectedDataset="selectedDatasetName"
:initializationMethod="initializationMethod"
:hidden-layers="hiddenLayers"
:hidden-layers-neurons="hiddenLayersNeurons"
:minError="props.minError"
:sessionId="props.sessionId"
:defaultLearningRate="props.learningRate"
:defaultMaxIterations="props.maxIterations"
:maxIterationsLimit="props.maxIterationsLimit"
:errors="props.errors"
@update:selected-dataset="
(newValue) => {
selectedDatasetName = newValue;
}
"
@update:training-id="setTrainingId"
/>
<div
class="align-items-start justify-content-center grid max-w-dvw grid-cols-2"
v-if="selectedDatasetName || iterations.length > 0"