Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
46 commits
Select commit Hold shift + click to select a range
99d21c0
Cleanup, move aplication state into another file
r59q May 20, 2025
e952a0d
Fix test
r59q May 20, 2025
bfa7a54
Revert the ModelTraining store
r59q May 20, 2025
5bd3b17
Only retrain knn after knn has been trained at least once
r59q May 22, 2025
6967a90
Fix compile for TestMLModelTrainer
r59q May 27, 2025
2224f32
Fix reconnect bug
r59q May 27, 2025
d6e9520
Create a drawer component
r59q May 28, 2025
59f5b8c
Add drawer with buttons
r59q Jun 2, 2025
9978502
Fix tooltip
r59q Jun 2, 2025
15c6e2b
Add missing translations
r59q Jun 2, 2025
c6074ed
Default drawer to be closed
r59q Jun 4, 2025
57f48b4
Mark models as untrained whenever another is selected
r59q Jun 4, 2025
b0fc5c8
Rename application state type
r59q Jun 4, 2025
5a68c9f
Prettier
r59q Jun 4, 2025
ad70e58
Rename ApplicationState file
r59q Jun 4, 2025
942ee7a
Refactor state store into a new devices store to handle microbit state
r59q Jun 4, 2025
2377f59
Add license identifier to devices store
r59q Jun 5, 2025
315172c
Move config into the training page
r59q Jun 10, 2025
51ca447
Add failsafe for model settings features
r59q Jun 10, 2025
8fd62cd
Add functionality to export to csv
r59q Jun 16, 2025
0371e3f
Implement CSV export
r59q Jun 16, 2025
d16895d
Remove unused component
r59q Jun 16, 2025
1875e44
Slight updates to the live data buffer
r59q Jun 25, 2025
4c80afc
Increase the performance of the livedatabuffer
r59q Jun 25, 2025
618aaa9
Add documentation of LiveDataBuffer
r59q Jun 25, 2025
211f341
Prettier
r59q Jun 25, 2025
bb5d8f4
Add predicted filtered value to store
r59q Jun 25, 2025
d937831
Add normalized input calculations
r59q Jun 25, 2025
298f4f2
Simplify logger
r59q Jun 25, 2025
ed1253c
Simplify logger
r59q Jun 25, 2025
cbf5224
prettier
r59q Jun 25, 2025
6e92193
Remove unused part of playground
r59q Jun 27, 2025
e0ccd81
Add fingerprints
r59q Jun 30, 2025
e55d644
Implement own version of fingerprint visualization from TFlow
r59q Jul 8, 2025
fce65f5
Make tooltip for fingerprint feel better by using polling intervals
r59q Jul 8, 2025
bdacaec
Make recording fingerprint reactive to highlighted axes and filters
r59q Jul 8, 2025
d3bf3cb
Fix bug where live data shows wrong labels
r59q Jul 8, 2025
1f6c201
Make EnableFingerprint store
r59q Jul 20, 2025
ed05cb5
prettier
r59q Jul 20, 2025
ffbbeaa
Add fingerprint checkbox
r59q Jul 20, 2025
9492de1
Move hovering UI on live 3d
r59q Jul 20, 2025
b6d5764
Fix blinking arrows
r59q Jul 24, 2025
cca112e
Implement a switch component
r59q Jul 28, 2025
377e1cd
Fix aria issue
r59q Jul 28, 2025
90cedc3
Hide fingerprint toggle when simple
r59q Aug 13, 2025
466346a
Make unbranded
r59q Aug 13, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion features.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"makecode": true,
"liveGraphInputValues": true,
"recordingScrubberValues": true,
"modelValidation": true
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
}
13 changes: 9 additions & 4 deletions src/App.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,6 @@
import Router from './router/Router.svelte';
import { Feature, getFeature } from './lib/FeatureToggles';
import { welcomeLog } from './lib/utils/Logger';
import { DeviceRequestStates, state } from './lib/stores/Stores';
import MediaQuery from './components/layout/MediaQuery.svelte';
import BottomBarMenuView from './components/layout/BottomBarMenuView.svelte';
import CookieBanner from './components/features/cookie-bannner/CookieBanner.svelte';
Expand All @@ -39,11 +38,17 @@
import OverlayView from './components/layout/OverlayView.svelte';
import SideBarMenuView from './components/layout/SideBarMenuView.svelte';
import PageContentView from './components/layout/PageContentView.svelte';
import { stores } from './lib/stores/Stores';
import { DeviceRequestStates } from './lib/domain/Devices';
import { isLoading } from './lib/stores/ApplicationState';

const devices = stores.getDevices();

welcomeLog();

if (CookieManager.isReconnectFlagSet()) {
$state.offerReconnect = true;
$state.reconnectState = DeviceRequestStates.INPUT;
$devices.offerReconnect = true;
$devices.reconnectState = DeviceRequestStates.INPUT;
CookieManager.unsetReconnectFlag();
}

Expand All @@ -56,7 +61,7 @@
<!-- Denies mobile users access to the platform -->
<IncompatiblePlatformView />
{:else}
{#if $state.isLoading}
{#if $isLoading}
<main class="h-screen w-screen bg-primary flex absolute z-10" transition:fade>
<LoadingSpinner />
</main>
Expand Down
5 changes: 5 additions & 0 deletions src/StaticConfiguration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,5 +148,10 @@ class StaticConfiguration {
* Whether the knn model should be normalized by default
*/
public static readonly knnNormalizedDefault = false;

/**
* Whether fingerprinting should be enabled by default
*/
public static readonly enableFingerprintByDefault: boolean = false;
}
export default StaticConfiguration;
116 changes: 116 additions & 0 deletions src/__tests__/csv/csv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
/**
* @vitest-environment jsdom
*/
/**
* (c) 2023-2025, Center for Computational Thinking and Design at Aarhus University and contributors
*
* SPDX-License-Identifier: MIT
*/

import { writable } from 'svelte/store';
import type { RecordingData } from '../../lib/domain/RecordingData';
import Gesture from '../../lib/domain/stores/gesture/Gesture';
import type { PersistedGestureData } from '../../lib/domain/stores/gesture/Gestures';
import type GestureConfidence from '../../lib/domain/stores/gesture/GestureConfidence';
import {
serializeGestureRecordingsToCSV,
serializeRecordingToCsvWithoutGestureName,
} from '../../lib/utils/CSVUtils';

describe('CSV Test', () => {
// A crude way to enforce direction of dependencies, inspired by ArchUnit for java
test('Convert recording', () => {
const input: RecordingData = {
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1, 2, 3],
},
{
vector: [4, 5, 6],
},
{
vector: [7, 8, 9],
},
],
};
const data = writable({
recordings: [input],
name: 'Test;Gesture',
} as PersistedGestureData);
const confidence = writable({}) as unknown as GestureConfidence;
const gesture: Gesture = new Gesture(data, confidence, () => void 0);
const result = serializeGestureRecordingsToCSV([gesture]);
expect(result).toBe(
'gesture;sample;x;y;z\nTest\\;Gesture;0;1;2;3\nTest\\;Gesture;1;4;5;6\nTest\\;Gesture;2;7;8;9',
);
});

test('Convert multiple gestures', () => {
const input1: RecordingData = {
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1, 2, 3],
},
{
vector: [4, 5, 6],
},
],
};
const input2: RecordingData = {
ID: 456,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [7, 8, 9],
},
{
vector: [10, 11, 12],
},
],
};
const data1 = writable({
recordings: [input1],
name: 'Gesture1',
} as PersistedGestureData);
const data2 = writable({
recordings: [input2],
name: 'Gesture2',
} as PersistedGestureData);
const confidence = writable({}) as unknown as GestureConfidence;
const gesture1: Gesture = new Gesture(data1, confidence, () => void 0);
const gesture2: Gesture = new Gesture(data2, confidence, () => void 0);
const result = serializeGestureRecordingsToCSV([gesture1, gesture2]);
expect(result).toBe(
'gesture;sample;x;y;z\n' +
'Gesture1;0;1;2;3\n' +
'Gesture1;1;4;5;6\n' +
'Gesture2;0;7;8;9\n' +
'Gesture2;1;10;11;12',
);
});

test('Serialize recording without gesture name (with headers)', () => {
const input: RecordingData = {
ID: 123,
labels: ['x', 'y', 'z'],
samples: [
{
vector: [1, 2, 3],
},
{
vector: [4, 5, 6],
},
{
vector: [7, 8, 9],
},
],
};

const result = serializeRecordingToCsvWithoutGestureName(input);
expect(result).toBe('sample;x;y;z\n' + '0;1;2;3\n' + '1;4;5;6\n' + '2;7;8;9');
});
});
5 changes: 5 additions & 0 deletions src/__tests__/mocks/mlmodel/TestMLModelTrainer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,17 @@
*
* SPDX-License-Identifier: MIT
*/
import type { ModelInfo } from '../../../lib/domain/ModelRegistry';
import ModelRegistry from '../../../lib/domain/ModelRegistry';
import type { ModelTrainer } from '../../../lib/domain/ModelTrainer';
import type { TrainingDataRepository } from '../../../lib/domain/TrainingDataRepository';
import TestMLModel from './TestMLModel';

class TestMLModelTrainer implements ModelTrainer<TestMLModel> {
constructor(private numberOfGestures: number) {}
getModelInfo(): ModelInfo {
return ModelRegistry.NeuralNetwork;
}
public trainModel(trainingData: TrainingDataRepository): Promise<TestMLModel> {
return Promise.resolve(new TestMLModel(this.numberOfGestures));
}
Expand Down
4 changes: 3 additions & 1 deletion src/__viteBuildVariants__/ml-machine-simple/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"makecode": false,
"liveGraphInputValues": false,
"recordingScrubberValues": false,
"modelValidation": false
"modelValidation": false,
"modelSettings": false,
"fingerprint": false
}
4 changes: 3 additions & 1 deletion src/__viteBuildVariants__/ml-machine/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"makecode": true,
"liveGraphInputValues": true,
"recordingScrubberValues": true,
"modelValidation": true
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
}
4 changes: 3 additions & 1 deletion src/__viteBuildVariants__/unbranded/features.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,7 @@
"makecode": true,
"liveGraphInputValues": true,
"recordingScrubberValues": true,
"modelValidation": true
"modelValidation": true,
"modelSettings": true,
"fingerprint": true
}
6 changes: 4 additions & 2 deletions src/assets/messages/ui.da.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"content.data.noData.exampleName.shake": "Ryste",
"content.data.noData.exampleName.still": "Stille",
"content.data.noData.exampleName.circle": "Cirkel",
"content.data.tooltip.remove": "Fjern",
"content.trainer.failure.header": "Træning mislykkedes",
"content.trainer.failure.body": "Træningen resulterede ikke i en brugbar model. Grunden til dette ligger sandsynligvis i dataet. Hvis dataet i forskellige klasser minder for meget om hinanden, kan dette resultere i nogle forskellige problemer i træningsprocessen, der ikke gør det muligt at træne modellen ordentligt.",
"content.trainer.failure.todo": "Gå tilbage til datasiden og ændr i din data.",
Expand Down Expand Up @@ -111,8 +112,9 @@
"content.validation.infobox.classesContent": "Her kan du se de klasser, du har defineret på datasiden.",
"content.validation.noGestures.title": "Ingen klasser...",
"content.validation.noGestures.description": "Du har endnu ikke tilføjet nogen klasser. Gå til datatrinnet i menuen til venstre og tilføj nogle.",
"content.validation.tutorial.title": "Validierungssätze",
"content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.",
"content.validation.tutorial.title": "Valideringssæt",
"content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.",
"content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren",
"footer.connectButtonNotConnected": "Tilslut din BBC micro:bit",
"footer.disconnectButton": "Frakobl",
"footer.helpHeader": "Live graf",
Expand Down
6 changes: 4 additions & 2 deletions src/assets/messages/ui.de.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"content.data.noData.exampleName.shake": "Schütteln",
"content.data.noData.exampleName.still": "Still",
"content.data.noData.exampleName.circle": "Kreis",
"content.data.tooltip.remove": "Entfernen",
"content.trainer.failure.header": "Training fehlgeschlagen",
"content.trainer.failure.body": "Das Training führte nicht zu einem brauchbaren Modell. Der Grund dafür sind höchstwahrscheinlich die für das Training verwendeten Daten. Wenn die Daten für verschiedene Klassen zu ähnlich sind, kann dies zu Problemen im Trainingsprozess führen.",
"content.trainer.failure.todo": "Gehe zur Datenseite zurück und ändere die Daten.",
Expand Down Expand Up @@ -111,8 +112,9 @@
"content.validation.infobox.classesContent": "Hier siehst du die Klassen, die du auf der Datenseite definiert hast.",
"content.validation.noGestures.title": "Keine Gesten...",
"content.validation.noGestures.description": "Du hast noch keine Gesten hinzugefügt. Gehe zum Schritt „Daten“ im Menü auf der linken Seite und füge welche hinzu.",
"content.validation.tutorial.title": "Valideringssæt",
"content.validation.tutorial.description": "Opret et separat datasæt til at evaluere dit models ydeevne. Modellen vil ikke blive trænet på disse data.",
"content.validation.tutorial.title": "Validierungssätze",
"content.validation.tutorial.description": "Erstelle einen separaten Datensatz, um die Leistung deines Modells zu bewerten. Das Modell wird nicht mit diesen Daten trainiert.",
"content.validation.tutorial.trainmodelfirst": "Zuerst ein Modell trainieren",
"footer.connectButtonNotConnected": "Verbinde deinen micro:bit",
"footer.disconnectButton": "Trennen",
"footer.helpHeader": "Live-Diagramm",
Expand Down
2 changes: 2 additions & 0 deletions src/assets/messages/ui.en.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@
"content.data.noData.exampleName.shake": "Shake",
"content.data.noData.exampleName.still": "Still",
"content.data.noData.exampleName.circle": "Circle",
"content.data.tooltip.remove": "Remove",
"content.trainer.failure.header": "Training Failed",
"content.trainer.failure.body": "The training did not result in a usable model. The reason for this is most likely the data used for training. If the data for different classes are too similar, this can result in issues in the training process.",
"content.trainer.failure.todo": "Return to the data page and change your data.",
Expand Down Expand Up @@ -113,6 +114,7 @@
"content.validation.noGestures.description": "You haven't added any gestures yet. Go to the data step in the menu to the left and add some.",
"content.validation.tutorial.title": "Validation sets",
"content.validation.tutorial.description": "Create a separate dataset to evaluate the performance of your model. The model will not be trained on this data.",
"content.validation.tutorial.trainmodelfirst": "Train a model first",
"footer.connectButtonNotConnected": "Connect your micro:bit",
"footer.disconnectButton": "Disconnect",
"footer.helpHeader": "Live graph",
Expand Down
2 changes: 1 addition & 1 deletion src/components/features/OutdatedMicrobitWarning.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,8 @@
import StaticConfiguration from '../../StaticConfiguration';
import Microbits from '../../lib/microbit-interfacing/Microbits';
import { HexOrigin } from '../../lib/microbit-interfacing/HexOrigin';
import { DeviceRequestStates } from '../../lib/stores/Stores';
import StandardButton from '../ui/buttons/StandardButton.svelte';
import { DeviceRequestStates } from '../../lib/domain/Devices';
let hasBeenClosed = false;
export let targetRole: 'INPUT' | 'OUTPUT';
let showMakeCodeUpdateMessage =
Expand Down
4 changes: 2 additions & 2 deletions src/components/features/PleaseConnect.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -16,10 +16,10 @@
</script>

<div>
<p class="text-center text-xl bold m-auto">
<p class="text-center text-lg bold m-auto">
{$t('menu.trainer.notConnected1')}
</p>
<p class="text-center text-xl bold m-auto">
<p class="text-center text-md bold m-auto">
{$t('menu.trainer.notConnected2')}
</p>
<div class="text-center ml-auto mr-auto mb-2 mt-2">
Expand Down
15 changes: 9 additions & 6 deletions src/components/features/ReconnectPrompt.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -10,12 +10,15 @@
import { btPatternInput, btPatternOutput } from '../../lib/stores/connectionStore';
import Microbits from '../../lib/microbit-interfacing/Microbits';
import { MBSpecs } from 'microbyte';
import { DeviceRequestStates, state } from '../../lib/stores/Stores';
import StandardButton from '../ui/buttons/StandardButton.svelte';
import { stores } from '../../lib/stores/Stores';
import { DeviceRequestStates } from '../../lib/domain/Devices';

const devices = stores.getDevices();

let reconnectText: string;
let reconnectButtonText: string;
state.subscribe(s => {
devices.subscribe(s => {
if (s.reconnectState === DeviceRequestStates.INPUT) {
reconnectText = $t('popup.disconnectedWarning.input');
reconnectButtonText = $t('popup.disconnectedWarning.reconnectButton.input');
Expand All @@ -26,7 +29,7 @@
});
// When disconnected by lost connection, offer the option to attempt to reconnect
let hideReconnectMessageAfterTimeout = false;
state.subscribe(s => {
devices.subscribe(s => {
if (s.offerReconnect) {
hideReconnectMessageAfterTimeout = true;
}
Expand All @@ -47,7 +50,7 @@
};

void connect().then(() => {
$state.offerReconnect = false;
$devices.offerReconnect = false;
});
};
</script>
Expand All @@ -59,15 +62,15 @@
<div class="absolute right-2 top-2 svelte-1rnkjvh">
<button
class="hover:bg-gray-100 rounded outline-transparent w-8 svelte-1rnkjvh"
on:click={() => ($state.offerReconnect = false)}>
on:click={() => ($devices.offerReconnect = false)}>
<i
class="fas fa-plus text-lg text-gray-600 hover:text-gray-800 duration-75 svelte-1rnkjvh"
style="transform: rotate(45deg);" />
</button>
</div>
<p>{reconnectText}</p>
<div class="flex justify-center">
<StandardButton onClick={() => reconnect($state.reconnectState)}>
<StandardButton onClick={() => reconnect($devices.reconnectState)}>
{reconnectButtonText}
</StandardButton>
</div>
Expand Down
Loading
Loading