feat: improved visibility of dataset and improved speed for expand import zip file

This commit is contained in:
2024-04-09 17:28:59 +01:00
parent 00369640a8
commit 143ad3b02b
14 changed files with 727 additions and 464 deletions

Binary file not shown.

View File

@@ -3,7 +3,8 @@
"version": "0.0.1",
"private": true,
"scripts": {
"dev": "vite dev",
"dev:raw": "vite dev",
"dev": "vite dev --port 5001 --host",
"build": "vite build",
"preview": "vite preview",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
@@ -30,5 +31,9 @@
"typescript": "^5.0.0",
"vite": "^5.0.3"
},
"type": "module"
"type": "module",
"dependencies": {
"chart.js": "^4.4.2",
"svelte-chartjs": "^3.1.5"
}
}

View File

@@ -1,5 +1,5 @@
<script lang="ts">
let {active} = $props<{active?: string}>();
let {active, nobox} = $props<{active?: string, nobox?: boolean}>();
function setActive(name: string) {
return () => active = name;
@@ -10,7 +10,7 @@
}
</script>
<div class="tabs">
<div class="tabs" class:nobox>
<div class="tab-buttons">
<slot name="buttons" {setActive} {isActive} />
</div>
@@ -58,5 +58,16 @@
display: block;
box-shadow: 0 2px 2px 1px #66666655;
}
&.nobox {
box-shadow: none;
overflow: visible;
:global(.content.selected) {
box-shadow: none;
}
}
}
</style>

View File

@@ -6,8 +6,8 @@
width: number;
height: number;
status: number;
model_type: number;
format: string;
model_type: number;
format: string;
};
export type Layer = {
@@ -25,85 +25,88 @@
</script>
<script lang="ts">
import { onMount } from "svelte";
import { onMount } from 'svelte';
import BaseModelInfo from "./BaseModelInfo.svelte";
import DeleteModel from "./DeleteModel.svelte";
import { goto } from "$app/navigation";
import { get, rdelete } from "src/lib/requests.svelte";
import MessageSimple from "$lib/MessageSimple.svelte"
import BaseModelInfo from './BaseModelInfo.svelte';
import DeleteModel from './DeleteModel.svelte';
import { goto } from '$app/navigation';
import { get, rdelete } from 'src/lib/requests.svelte';
import MessageSimple from '$lib/MessageSimple.svelte';
import ModelData from "./ModelData.svelte";
import DeleteZip from "./DeleteZip.svelte";
import ModelData from './ModelData.svelte';
import DeleteZip from './DeleteZip.svelte';
import 'src/styles/forms.css'
import RunModel from "./RunModel.svelte";
let model: Promise<Model> = $state(new Promise(() => {}));
let definitions: Promise<Definitions[]> = $state(new Promise(() => {}));
import ModelDataPage from './ModelDataPage.svelte';
let id: string | undefined = $state()
import 'src/styles/forms.css';
import RunModel from './RunModel.svelte';
import Tabs from 'src/lib/Tabs.svelte';
let model: Promise<Model> = $state(new Promise(() => {}));
let _model: Model | undefined = $state(undefined);
let definitions: Promise<Definitions[]> = $state(new Promise(() => {}));
async function getModel() {
try {
let temp_model: Model = await get(`models/edit?id=${id}`);
let id: string | undefined = $state();
console.log(temp_model)
async function getModel() {
try {
let temp_model: Model = await get(`models/edit?id=${id}`);
if (temp_model.status == 3) {
setTimeout(getModel, 2000);
}
if ([3, 6].includes(temp_model.status)) {
setTimeout(getModel, 2000);
}
if (temp_model.status == 4) {
setTimeout(getModel, 5000);
if (temp_model.status == 4) {
setTimeout(getModel, 5000);
definitions = await get(`models/edit/definitions?id=${id}`);
}
definitions = await get(`models/edit/definitions?id=${id}`);
}
model = Promise.resolve(temp_model);
} catch (e) {
if (e instanceof Response) {
model = Promise.reject(await e.json())
} else {
model = Promise.reject("Could not load model");
}
}
}
model = Promise.resolve(temp_model);
_model = temp_model;
} catch (e) {
if (e instanceof Response) {
model = Promise.reject(await e.json());
} else {
model = Promise.reject('Could not load model');
}
}
}
onMount(() => {
let url = new URLSearchParams(window.location.search);
const _id = url.get('id');
if (!_id) {
goto('/models')
return;
}
onMount(() => {
let url = new URLSearchParams(window.location.search);
const _id = url.get('id');
if (!_id) {
goto('/models');
return;
}
id = _id;
getModel();
});
id = _id;
getModel();
});
let resetMessages: MessageSimple;
let resetMessages: MessageSimple;
async function resetModel() {
resetMessages.display("");
let _model = await model;
async function resetModel() {
resetMessages.display('');
try {
await rdelete('models/train/reset', {
"id": _model.id
});
let _model = await model;
getModel();
} catch (e) {
if (e instanceof Response) {
resetMessages.display(await e.json())
} else {
resetMessages.display("Could not reset model!")
}
}
}
try {
await rdelete('models/train/reset', {
id: _model.id
});
// Auto reload after 2s when model.status 3,4
getModel();
} catch (e) {
if (e instanceof Response) {
resetMessages.display(await e.json());
} else {
resetMessages.display('Could not reset model!');
}
}
}
// Auto reload after 2s when model.status 3,4
</script>
<svelte:head>
@@ -127,186 +130,208 @@
-->
<main>
{#await model}
Loading
{:then m}
{#if m.status == 1}
<div>
<h1 class="text-center">
{m.name}
</h1>
<!-- TODO add cool animation -->
<h2 class="text-center">Preparing the model</h2>
</div>
{:else if m.status == -1}
<div>
<h1 class="text-center">
{m.name}
</h1>
<!-- TODO improve message -->
<h2 class="text-center">Failed to prepare model</h2>
<Tabs active="model" let:isActive nobox>
<div slot="buttons" let:setActive let:isActive>
<button
class="tab"
on:click|preventDefault={setActive('model')}
class:selected={isActive('model')}
>
Model
</button>
{#if _model && [2, 3, 4, 5, 6, 7].includes(_model.status)}
<button
class="tab"
on:click|preventDefault={setActive('model-data')}
class:selected={isActive('model-data')}
>
Model Data
</button>
{/if}
</div>
{#if _model}
<ModelDataPage model={_model} on:reload={getModel} active={isActive('model-data')} />
{/if}
<div class="content" class:selected={isActive('model')}>
{#await model}
Loading
{:then m}
{#if m.status == 1}
<div>
<h1 class="text-center">
{m.name}
</h1>
<!-- TODO add cool animation -->
<h2 class="text-center">Preparing the model</h2>
</div>
{:else if m.status == -1}
<div>
<h1 class="text-center">
{m.name}
</h1>
<!-- TODO improve message -->
<h2 class="text-center">Failed to prepare model</h2>
<DeleteModel model={m} />
</div>
<!-- PRE TRAINING STATUS -->
{:else if m.status == 2}
<BaseModelInfo model={m} />
<ModelData model={m} on:reload={getModel} />
<!-- {{ template "train-model-card" . }} -->
<DeleteModel model={m} />
{:else if m.status == -2}
<BaseModelInfo model={m} />
<DeleteZip model={m} on:reload={getModel} />
<DeleteModel model={m} />
{:else if m.status == 3}
<BaseModelInfo model={m} />
<div class="card">
<!-- TODO improve this -->
Processing zip file...
</div>
{:else if m.status == -3 || m.status == -4}
<BaseModelInfo model={m} />
<form on:submit={resetModel}>
Failed Prepare for training.<br />
<div class="spacer"></div>
<MessageSimple bind:this={resetMessages} />
<button class="danger"> Try Again </button>
</form>
<DeleteModel model={m} />
{:else if m.status == 4}
<BaseModelInfo model={m} />
<!-- TODO request /models/edit?id={m.id} -->
<div class="card">
<!-- TODO improve this -->
Training the model...<br />
<!-- TODO Add progress status on definitions -->
{#await definitions}
Loading
{:then defs}
<table>
<thead>
<tr>
<th> Done Progress </th>
<th> Training Round Progress </th>
<th> Accuracy </th>
<th> Status </th>
</tr>
</thead>
<tbody>
{#each defs as def}
<tr>
<td>
{def.epoch}
</td>
<td>
{def.epoch_progress}/20
</td>
<td>
{def.accuracy}%
</td>
<td style="text-align: center;">
{#if def.status == 2}
<span class="bi bi-book" style="color: green;"></span>
{:else if [3, 6, -3].includes(def.status)}
<span
class="bi bi-book-half"
style="color: {{
'3': 'green',
'-3': 'red',
'6': 'orange'
}[String(def.status)]};"
></span>
{:else}
{def.status}
{/if}
</td>
</tr>
{#if def.status == 3 && def.layers}
<DeleteModel model={m} />
</div>
<!-- PRE TRAINING STATUS -->
{:else if m.status == 2}
<BaseModelInfo model={m} />
<ModelData model={m} on:reload={getModel} />
<!-- {{ template "train-model-card" . }} -->
<DeleteModel model={m} />
{:else if m.status == -2}
<BaseModelInfo model={m} />
<DeleteZip model={m} on:reload={getModel} />
<DeleteModel model={m} />
{:else if m.status == 3}
<BaseModelInfo model={m} />
<div class="card">
<!-- TODO improve this -->
Processing zip file...
</div>
{:else if m.status == -3 || m.status == -4}
<BaseModelInfo model={m} />
<form on:submit={resetModel}>
Failed Prepare for training.<br />
<div class="spacer"></div>
<MessageSimple bind:this={resetMessages} />
<button class="danger"> Try Again </button>
</form>
<DeleteModel model={m} />
{:else if m.status == 4}
<BaseModelInfo model={m} />
<!-- TODO request /models/edit?id={m.id} -->
<div class="card">
<!-- TODO improve this -->
Training the model...<br />
<!-- TODO Add progress status on definitions -->
{#await definitions}
Loading
{:then defs}
<table>
<thead>
<tr>
<td colspan="4">
<svg viewBox="0 200 1000 600">
{#each def.layers as layer, i}
{@const sep_mod =
def.layers.length > 8
? Math.max(10, 100 - (def.layers.length - 8) * 10)
: 100}
{#if layer.layer_type == 1}
<polygon
points="50,450 200,250 200,550 50,750"
stroke="black"
stroke-width="2"
fill="green"
></polygon>
{:else if layer.layer_type == 4}
<polygon
points="{50 + i * sep_mod},450 {200 + i * sep_mod},250 {200 +
i * sep_mod},550 {50 + i * sep_mod},750"
stroke="black"
stroke-width="2"
fill="orange"
>
</polygon>
{:else if layer.layer_type == 3}
<polygon
points="{50 + i * sep_mod},450 {200 + i * sep_mod},250 {200 +
i * sep_mod},550 {50 + i * sep_mod},750"
stroke="black"
stroke-width="2"
fill="red"
>
</polygon>
{:else if layer.layer_type == 2}
<polygon
points="{50 + i * sep_mod},550 {200 + i * sep_mod},350 {200 +
i * sep_mod},450 {50 + i * sep_mod},650"
stroke="black"
stroke-width="2"
fill="blue"
>
</polygon>
{:else}
<div>
{layer.layer_type}
{layer.shape}
</div>
{/if}
{/each}
</svg>
</td>
<th> Done Progress </th>
<th> Training Round Progress </th>
<th> Accuracy </th>
<th> Status </th>
</tr>
{/if}
{/each}
</tbody>
</table>
{/await}
<!-- TODO Add ability to stop training -->
</div>
{:else if [5, 6, -6, 7, -7].includes(m.status)}
<BaseModelInfo model={m} />
<RunModel model={m} />
{#if m.status == 6}
<div class="card">
Model expading... Processing ZIP file
</div>
{/if}
{#if m.status == -6}
<DeleteZip model={m} on:reload={getModel} expand />
{/if}
{#if m.status == -7}
<form>
<!-- TODO add more info about the failure -->
Failed to train the model!
Try to retrain
</form>
{/if}
{#if m.model_type == 2}
<ModelData model={m} on:reload={getModel} />
{/if}
<DeleteModel model={m} />
{:else}
<h1>Unknown Status of the model.</h1>
{/if}
{/await}
</thead>
<tbody>
{#each defs as def}
<tr>
<td>
{def.epoch}
</td>
<td>
{def.epoch_progress}/20
</td>
<td>
{def.accuracy}%
</td>
<td style="text-align: center;">
{#if def.status == 2}
<span class="bi bi-book" style="color: green;"></span>
{:else if [3, 6, -3].includes(def.status)}
<span
class="bi bi-book-half"
style="color: {{
'3': 'green',
'-3': 'red',
'6': 'orange'
}[String(def.status)]};"
></span>
{:else}
{def.status}
{/if}
</td>
</tr>
{#if def.status == 3 && def.layers}
<tr>
<td colspan="4">
<svg viewBox="0 200 1000 600">
{#each def.layers as layer, i}
{@const sep_mod =
def.layers.length > 8
? Math.max(10, 100 - (def.layers.length - 8) * 10)
: 100}
{#if layer.layer_type == 1}
<polygon
points="50,450 200,250 200,550 50,750"
stroke="black"
stroke-width="2"
fill="green"
></polygon>
{:else if layer.layer_type == 4}
<polygon
points="{50 + i * sep_mod},450 {200 + i * sep_mod},250 {200 +
i * sep_mod},550 {50 + i * sep_mod},750"
stroke="black"
stroke-width="2"
fill="orange"
>
</polygon>
{:else if layer.layer_type == 3}
<polygon
points="{50 + i * sep_mod},450 {200 + i * sep_mod},250 {200 +
i * sep_mod},550 {50 + i * sep_mod},750"
stroke="black"
stroke-width="2"
fill="red"
>
</polygon>
{:else if layer.layer_type == 2}
<polygon
points="{50 + i * sep_mod},550 {200 + i * sep_mod},350 {200 +
i * sep_mod},450 {50 + i * sep_mod},650"
stroke="black"
stroke-width="2"
fill="blue"
>
</polygon>
{:else}
<div>
{layer.layer_type}
{layer.shape}
</div>
{/if}
{/each}
</svg>
</td>
</tr>
{/if}
{/each}
</tbody>
</table>
{/await}
<!-- TODO Add ability to stop training -->
</div>
{:else if [5, 6, -6, 7, -7].includes(m.status)}
<BaseModelInfo model={m} />
<RunModel model={m} />
{#if m.status == 6}
<div class="card">Model expading... Processing ZIP file</div>
{/if}
{#if m.status == -6}
<DeleteZip model={m} on:reload={getModel} expand />
{/if}
{#if m.status == -7}
<form>
<!-- TODO add more info about the failure -->
Failed to train the model! Try to retrain
</form>
{/if}
{#if m.model_type == 2}
<ModelData simple model={m} on:reload={getModel} />
{/if}
<DeleteModel model={m} />
{:else}
<h1>Unknown Status of the model.</h1>
{/if}
{/await}
</div>
</Tabs>
</main>
<style lang="scss">

View File

@@ -1,197 +1,195 @@
<script lang="ts" context="module">
export type Class = {
name: string;
id: string;
status: number;
}
export type Class = {
name: string;
id: string;
status: number;
};
</script>
<script lang="ts">
import FileUpload from "src/lib/FileUpload.svelte";
import Tabs from "src/lib/Tabs.svelte";
import type { Model } from "./+page.svelte";
import { postFormData, get } from "src/lib/requests.svelte";
import MessageSimple from "src/lib/MessageSimple.svelte";
import { createEventDispatcher } from "svelte";
import ModelTable from "./ModelTable.svelte";
import TrainModel from "./TrainModel.svelte";
import FileUpload from 'src/lib/FileUpload.svelte';
import Tabs from 'src/lib/Tabs.svelte';
import type { Model } from './+page.svelte';
import { postFormData, get } from 'src/lib/requests.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte';
import { createEventDispatcher } from 'svelte';
import ModelTable from './ModelTable.svelte';
import TrainModel from './TrainModel.svelte';
let { model } = $props<{model: Model}>();
let { model, simple } = $props<{ model: Model; simple?: boolean }>();
let classes: Class[] = $state([]);
let has_data: boolean = $state(false);
let classes: Class[] = $state([]);
let has_data: boolean = $state(false);
let file: File | undefined = $state();
let file: File | undefined = $state();
const dispatch = createEventDispatcher<{
reload: void
}>();
const dispatch = createEventDispatcher<{
reload: void;
}>();
let uploading: Promise<void> = $state(Promise.resolve())
let numberOfInvalidImages = $state(0);
let uploading: Promise<void> = $state(Promise.resolve());
let numberOfInvalidImages = $state(0);
let uploadImage: MessageSimple;
let uploadImage: MessageSimple;
async function uploadZip() {
if (!file) return;
async function uploadZip() {
if (!file) return;
uploading = new Promise(() => {});
uploading = new Promise(() => {});
let form = new FormData();
form.append('id', model.id);
form.append('file', file, 'upload.zip');
try {
await postFormData('models/data/upload', form);
dispatch('reload');
} catch (e) {
if (e instanceof Response) {
uploadImage.display(await e.json());
} else {
uploadImage.display('');
}
}
let form = new FormData();
form.append('id', model.id);
form.append('file', file, 'upload.zip');
uploading = Promise.resolve();
}
try {
await postFormData('models/data/upload', form);
dispatch('reload');
} catch (e) {
if (e instanceof Response) {
uploadImage.display(await e.json());
} else {
uploadImage.display('');
}
}
$effect(() => {
getData();
});
uploading = Promise.resolve();
}
async function getData() {
if (!model) return;
try {
let data = await get(`models/edit/classes?id=${model.id}`);
classes = data.classes
numberOfInvalidImages = data.number_of_invalid_images;
has_data = data.has_data;
} catch {
return;
}
}
$effect(() => {
getData();
});
async function getData() {
if (!model) return;
try {
let data = await get(`models/edit/classes?id=${model.id}`);
classes = data.classes;
numberOfInvalidImages = data.number_of_invalid_images;
has_data = data.has_data;
} catch {
return;
}
}
</script>
<div class="card">
<h3>
Training data
</h3>
{#if classes.length == 0}
<p>
You need to upload data so the model can train.
</p>
<Tabs active="upload" let:isActive>
<div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive("upload")} on:click={setActive("upload")}>
Upload
</button>
<button class="tab" class:selected={isActive("create-class")} on:click={setActive("create-class")}>
Create Class
</button>
<button class="tab" class:selected={isActive("api")} on:click={setActive("api")}>
Api
</button>
</div>
<div class="content" class:selected={isActive("upload")}>
<form on:submit|preventDefault={uploadZip}>
<fieldset class="file-upload" >
<label for="file">Data file</label>
<div class="form-msg">
Please provide a file that has the training and testing data<br/>
The file must have 2 folders one with testing images and one with training images. <br/>
Each of the folders will contain the classes of the model. The folders must be the same in testing and training.
The class folders must have the images for the classes.
<pre>
training\
class1\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
{#if !simple}
<div class="card">
<h3>Training data</h3>
{#if classes.length == 0}
<p>You need to upload data so the model can train.</p>
<Tabs active="upload" let:isActive>
<div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive('upload')} on:click={setActive('upload')}>
Upload
</button>
<button
class="tab"
class:selected={isActive('create-class')}
on:click={setActive('create-class')}
>
Create Class
</button>
<button class="tab" class:selected={isActive('api')} on:click={setActive('api')}>
Api
</button>
</div>
<div class="content" class:selected={isActive('upload')}>
<form on:submit|preventDefault={uploadZip}>
<fieldset class="file-upload">
<label for="file">Data file</label>
<div class="form-msg">
Please provide a file that has the training and testing data<br />
The file must have 2 folders one with testing images and one with training images.
<br />
Each of the folders will contain the classes of the model. The folders must be the same
in testing and training. The class folders must have the images for the classes.
<pre>
training\
class1\
img1.png
img2.png
img2.png
...
testing\
class1\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
...
testing\
class1\
img1.png
img2.png
img2.png
...
class2\
img1.png
img2.png
img2.png
...
...
</pre>
</div>
<FileUpload replace_slot bind:file={file} accept="application/zip" notExpand >
<img src="/imgs/upload-icon.png" alt="" />
<span>
Upload Zip File
</span>
<div slot="replaced" style="display: inline;">
<img src="/imgs/upload-icon.png" alt="" />
<span>
File selected
</span>
</div>
</FileUpload>
</fieldset>
<MessageSimple bind:this={uploadImage} />
{#if file}
{#await uploading}
<button disabled>
Uploading
</button>
{:then}
<button>
Add
</button>
{/await}
{/if}
</form>
</div>
<div class="content" class:selected={isActive("create-class")}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div>
<div class="content" class:selected={isActive("api")}>
TODO
</div>
</Tabs>
<div class="tabs">
</div>
{:else}
<p>
You need to upload data so the model can train.
</p>
{#if numberOfInvalidImages > 0}
<p class="danger">
There are images {numberOfInvalidImages} that were loaded that do not have the correct format. These images will be delete when the model trains.
</p>
{/if}
<Tabs active="create-class" let:isActive>
<div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive("create-class")} on:click={setActive("create-class")}>
Create Class
</button>
<button class="tab" class:selected={isActive("api")} on:click={setActive("api")}>
Api
</button>
</div>
<div class="content" class:selected={isActive("create-class")}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div>
<div class="content" class:selected={isActive("api")}>
TODO
</div>
</Tabs>
{/if}
</div>
</div>
<FileUpload replace_slot bind:file accept="application/zip" notExpand>
<img src="/imgs/upload-icon.png" alt="" />
<span> Upload Zip File </span>
<div slot="replaced" style="display: inline;">
<img src="/imgs/upload-icon.png" alt="" />
<span> File selected </span>
</div>
</FileUpload>
</fieldset>
<MessageSimple bind:this={uploadImage} />
{#if file}
{#await uploading}
<button disabled> Uploading </button>
{:then}
<button> Add </button>
{/await}
{/if}
</form>
</div>
<div class="content" class:selected={isActive('create-class')}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div>
<div class="content" class:selected={isActive('api')}>TODO</div>
</Tabs>
<div class="tabs"></div>
{:else}
<p>You need to upload data so the model can train.</p>
{#if numberOfInvalidImages > 0}
<p class="danger">
There are images {numberOfInvalidImages} that were loaded that do not have the correct format.
These images will be delete when the model trains.
</p>
{/if}
<Tabs active="create-class" let:isActive>
<div slot="buttons" let:setActive let:isActive>
<button
class="tab"
class:selected={isActive('create-class')}
on:click={setActive('create-class')}
>
Create Class
</button>
<button class="tab" class:selected={isActive('api')} on:click={setActive('api')}>
Api
</button>
</div>
<div class="content" class:selected={isActive('create-class')}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} />
</div>
<div class="content" class:selected={isActive('api')}>TODO</div>
</Tabs>
{/if}
</div>
{/if}
{#if classes.some((item) => item.status == 1) && ![-6, 6].includes(model.status)}
<TrainModel number_of_invalid_images={numberOfInvalidImages} {model} {has_data} on:reload={() => dispatch('reload')} />
<TrainModel
number_of_invalid_images={numberOfInvalidImages}
{model}
{has_data}
on:reload={() => dispatch('reload')}
/>
{/if}

View File

@@ -0,0 +1,48 @@
<script lang="ts">
import { createEventDispatcher } from "svelte";
import type { Model } from "./+page.svelte";
import ModelData from "./ModelData.svelte";
import { post } from "src/lib/requests.svelte";
import ModelDataPageStatsGraph from "./ModelDataPageStatsGraph.svelte";
import type { ModelStats } from "./types";
import DeleteZip from "./DeleteZip.svelte";
let { model, active } = $props<{model: Model, active?: boolean}>()
const dispatch = createEventDispatcher<{ reload: void }>();
$effect(() => {
if (active)
getData();
});
let stats: ModelStats | undefined = $state();
async function getData() {
if (!model) return;
try {
stats = await post(`models/class/stats`, { id: model.id });
} catch {
return;
}
}
</script>
<div class="content" class:selected={active}>
{#if stats}
<ModelDataPageStatsGraph data={stats} />
{/if}
{#if [-6, -2].includes(model.status)}
<DeleteZip model={model} on:reload={() => dispatch('reload')} expand />
{/if}
<ModelData model={model} on:reload={() => {
getData();
dispatch('reload');
}} />
</div>

View File

@@ -0,0 +1,89 @@
<script lang="ts">
import {
Chart,
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
BarController,
Colors
} from 'chart.js';
import type { ModelStats } from './types';
Chart.register(
Title,
Tooltip,
Legend,
BarElement,
CategoryScale,
LinearScale,
BarController,
Colors
);
let { data } = $props<{ data: ModelStats }>();
let ctx: HTMLCanvasElement;
let chart: Chart;
$effect(() => {
if (data && ctx) {
if (chart) {
console.log("update")
chart.data = {
labels: data.map((a) => a.name),
datasets: [
{
label: 'Training',
data: data.map((a) => a.training)
},
{
label: 'Testing',
data: data.map((a) => a.testing)
}
]
};
chart.update('resize');
} else {
console.log("create")
chart = new Chart(ctx, {
type: 'bar',
data: {
labels: data.map((a) => a.name),
datasets: [
{
label: 'Training',
data: data.map((a) => a.training)
},
{
label: 'Testing',
data: data.map((a) => a.testing)
}
]
},
options: {
scales: {
x: {
stacked: true
},
y: {
stacked: true
}
}
}
});
}
}
});
</script>
<div><canvas bind:this={ctx} /></div>
<style lang="scss">
canvas {
width: 100%;
}
</style>

View File

@@ -44,8 +44,6 @@
let res = await get('models/data/list?' + url.toString());
showNext = res.showNext;
image_list = res.image_list;
console.log(image_list);
} catch (e) {
console.error('TODO notify user', e);
}

View File

@@ -0,0 +1,5 @@
export type ModelStats = Array<{
name: string,
training: number,
testing: number,
}>