multiple fixes

This commit is contained in:
Andre Henriques 2024-05-11 01:11:07 +01:00
parent 0ac6ac8dce
commit 0c0d16c846
34 changed files with 340 additions and 454 deletions

View File

@ -136,17 +136,16 @@ func processZipFile(c *Context, model *BaseModel) {
return return
} }
if paths[0] != "training" { if paths[0] == "training" {
training = InsertIfNotPresent(training, paths[1]) training = InsertIfNotPresent(training, paths[1])
} else if paths[0] != "testing" { } else if paths[0] == "testing" {
testing = InsertIfNotPresent(testing, paths[1]) testing = InsertIfNotPresent(testing, paths[1])
} }
} }
if !reflect.DeepEqual(testing, training) { if !reflect.DeepEqual(testing, training) {
c.Logger.Info("Diff", "testing", testing, "training", training) c.Logger.Warn("Diff", "testing", testing, "training", training)
failed("Testing and Training datesets are diferent") c.Logger.Warn("Testing and traing datasets differ")
return
} }
base_path := path.Join("savedData", model.Id, "data") base_path := path.Join("savedData", model.Id, "data")

View File

@ -37,7 +37,7 @@ func ReadJPG(scope *op.Scope, imagePath string, channels int64) *image.Image {
return image.Scale(0, 255) return image.Scale(0, 255)
} }
func runModelNormal(base BasePack, model *BaseModel, def_id string, inputImage *tf.Tensor) (order int, confidence float32, err error) { func runModelNormal(model *BaseModel, def_id string, inputImage *tf.Tensor) (order int, confidence float32, err error) {
order = 0 order = 0
err = nil err = nil
@ -193,7 +193,7 @@ func ClassifyTask(base BasePack, task Task) (err error) {
} }
} else { } else {
base.GetLogger().Info("Running model normal", "model", model.Id, "def", def_id) base.GetLogger().Info("Running model normal", "model", model.Id, "def", def_id)
vi, confidence, err = runModelNormal(base, model, def_id, inputImage) vi, confidence, err = runModelNormal(model, def_id, inputImage)
if err != nil { if err != nil {
task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Failed to run model") task.UpdateStatusLog(base, TASK_FAILED_RUNNING, "Failed to run model")
return return

View File

@ -1298,12 +1298,6 @@ func generateExpandableDefinition(c BasePack, model *BaseModel, target_accuracy
order++ order++
// handle the errors inside the pervious if block
if err != nil {
failed()
return
}
// Create the blocks // Create the blocks
loop := int((math.Log(float64(model.Width)) / math.Log(float64(10)))) loop := int((math.Log(float64(model.Width)) / math.Log(float64(10))))

View File

@ -27,5 +27,11 @@ module.exports = {
parser: '@typescript-eslint/parser' parser: '@typescript-eslint/parser'
} }
} }
] ],
rules: {
'svelte/no-at-html-tags': 'off',
// TODO remove this
'@typescript-eslint/no-explicit-any': 'off'
}
}; };

View File

@ -36,7 +36,7 @@
class="icon" class="icon"
class:adapt={replace_slot && file && !notExpand} class:adapt={replace_slot && file && !notExpand}
type="button" type="button"
on:click={() => fileInput.click()} onclick={() => fileInput.click()}
> >
{#if replace_slot && file} {#if replace_slot && file}
<slot name="replaced" {file}> <slot name="replaced" {file}>
@ -54,6 +54,6 @@
required required
{accept} {accept}
bind:this={fileInput} bind:this={fileInput}
on:change={onChange} onchange={onChange}
/> />
</div> </div>

View File

@ -1,57 +0,0 @@
<script context="module" lang="ts">
export type DisplayFn = (
msg: string,
options?: {
type?: 'error' | 'success';
timeToShow?: number;
}
) => void;
</script>
<script lang="ts">
let message = $state<string | undefined>(undefined);
let type = $state<'error' | 'success'>('error');
let timeout: number | undefined = undefined;
export function clear() {
if (timeout) clearTimeout(timeout);
message = undefined;
}
export function display(
msg: string,
options?: {
type?: 'error' | 'success';
timeToShow?: number;
}
) {
if (timeout) clearTimeout(timeout);
if (!msg) {
message = undefined;
return;
}
let { type: l_type, timeToShow } = options ?? { type: 'error', timeToShow: undefined };
if (l_type) {
type = l_type;
}
message = msg;
if (timeToShow) {
timeout = setTimeout(() => {
message = undefined;
timeout = undefined;
}, timeToShow);
}
}
</script>
{#if message}
<div class="form-msg {type}">
{message}
</div>
{/if}

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
let { title } = $props<{ title: string }>(); let { title }: { title: string } = $props();
let isHovered = $state(false); let isHovered = $state(false);
let x = $state(0); let x = $state(0);
@ -30,10 +30,10 @@
<div <div
bind:this={div} bind:this={div}
on:mouseover={mouseOver} onmouseover={mouseOver}
on:mouseleave={mouseLeave} onmouseleave={mouseLeave}
on:mousemove={mouseMove} onmousemove={mouseMove}
on:focus={focus} onfocus={focus}
role="tooltip" role="tooltip"
class="tooltipContainer" class="tooltipContainer"
> >

6
webpage/src/lib/utils.ts Normal file
View File

@ -0,0 +1,6 @@
export function preventDefault(fn: any) {
return function (event: Event) {
event.preventDefault();
fn.call(this, event);
};
}

View File

@ -12,7 +12,7 @@
let width = $state(0); let width = $state(0);
let height = $state(0); let height = $state(0);
function drag(simulation: any) { function drag(simulation: d3.Simulation<d3.HierarchyNode<Base>, undefined>) {
function dragstarted(event: any, d: any) { function dragstarted(event: any, d: any) {
if (!event.active) simulation.alphaTarget(0.3).restart(); if (!event.active) simulation.alphaTarget(0.3).restart();
d.fx = d.x; d.fx = d.x;

View File

@ -3,6 +3,7 @@
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import { userStore } from '../UserStore.svelte'; import { userStore } from '../UserStore.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { preventDefault } from 'src/lib/utils';
let submitted = $state(false); let submitted = $state(false);
@ -39,7 +40,7 @@
<div class="login-page"> <div class="login-page">
<div> <div>
<h1>Login</h1> <h1>Login</h1>
<form on:submit|preventDefault={onSubmit} class:submitted> <form onsubmit={preventDefault(onSubmit)} class:submitted>
<fieldset> <fieldset>
<label for="email">Email</label> <label for="email">Email</label>
<input type="email" required name="email" bind:value={loginData.email} /> <input type="email" required name="email" bind:value={loginData.email} />

View File

@ -85,11 +85,4 @@
.list-header .expand { .list-header .expand {
flex-grow: 1; flex-grow: 1;
} }
.list-header .button,
.list-header button {
padding: 10px 10px;
height: calc(100% - 20px);
margin-top: 5px;
}
</style> </style>

View File

@ -5,6 +5,7 @@
import { notificationStore } from 'src/lib/NotificationsStore.svelte'; import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import { preventDefault } from 'src/lib/utils';
let submitted = $state(false); let submitted = $state(false);
@ -44,7 +45,7 @@
<main> <main>
<h1>Create new Model</h1> <h1>Create new Model</h1>
<form class:submitted on:submit|preventDefault={onSubmit}> <form class:submitted onsubmit={preventDefault(onSubmit)}>
<fieldset> <fieldset>
<label for="name">Name</label> <label for="name">Name</label>
<input id="name" name="name" required bind:value={data.name} /> <input id="name" name="name" required bind:value={data.name} />

View File

@ -30,18 +30,18 @@
import BaseModelInfo from './BaseModelInfo.svelte'; import BaseModelInfo from './BaseModelInfo.svelte';
import DeleteModel from './DeleteModel.svelte'; import DeleteModel from './DeleteModel.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { get, rdelete } from 'src/lib/requests.svelte'; import { get, rdelete, showMessage } from 'src/lib/requests.svelte';
import MessageSimple from '$lib/MessageSimple.svelte'; import { preventDefault } from 'src/lib/utils';
import ModelData from './ModelData.svelte'; import ModelData from './ModelData.svelte';
import DeleteZip from './DeleteZip.svelte'; import DeleteZip from './DeleteZip.svelte';
import RunModel from './RunModel.svelte'; import RunModel from './RunModel.svelte';
import Tabs from 'src/lib/Tabs.svelte'; import Tabs from 'src/lib/Tabs.svelte';
import TasksDataPage from './TasksDataPage.svelte'; import TasksDataPage from './TasksDataPage.svelte';
import ModelDataPage from './ModelDataPage.svelte'; import ModelDataPage from './ModelDataPage.svelte';
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
let model: Promise<Model> = $state(new Promise(() => {})); let model: Promise<Model> = $state(new Promise(() => {}));
let _model: Model | undefined = $state(undefined); let _model: Model | undefined = $state(undefined);
@ -92,10 +92,7 @@
getModel(); getModel();
}); });
let resetMessages: MessageSimple;
async function resetModel() { async function resetModel() {
resetMessages.display('');
let _model = await model; let _model = await model;
try { try {
@ -105,11 +102,7 @@
getModel(); getModel();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not reset model!');
resetMessages.display(await e.json());
} else {
resetMessages.display('Could not reset model!');
}
} }
} }
@ -147,7 +140,8 @@
<div slot="buttons" let:setActive let:isActive> <div slot="buttons" let:setActive let:isActive>
<button <button
class="tab" class="tab"
on:click|preventDefault={setActive('model')} type="button"
onclick={setActive('model')}
class:selected={isActive('model')} class:selected={isActive('model')}
> >
Model Model
@ -155,7 +149,8 @@
{#if _model && [2, 3, 4, 5, 6, 7, -6, -7].includes(_model.status)} {#if _model && [2, 3, 4, 5, 6, 7, -6, -7].includes(_model.status)}
<button <button
class="tab" class="tab"
on:click|preventDefault={setActive('model-data')} type="button"
onclick={setActive('model-data')}
class:selected={isActive('model-data')} class:selected={isActive('model-data')}
> >
Model Data Model Data
@ -164,7 +159,8 @@
{#if _model && [5, 6, 7, -6, -7].includes(_model.status)} {#if _model && [5, 6, 7, -6, -7].includes(_model.status)}
<button <button
class="tab" class="tab"
on:click|preventDefault={setActive('tasks')} type="button"
onclick={setActive('tasks')}
class:selected={isActive('tasks')} class:selected={isActive('tasks')}
> >
Tasks Tasks
@ -172,7 +168,7 @@
{/if} {/if}
</div> </div>
{#if _model} {#if _model}
<ModelDataPage model={_model} on:reload={getModel} active={isActive('model-data')} /> <ModelDataPage model={_model} onreload={getModel} active={isActive('model-data')} />
<TasksDataPage model={_model} active={isActive('tasks')} /> <TasksDataPage model={_model} active={isActive('tasks')} />
{/if} {/if}
<div class="content" class:selected={isActive('model')}> <div class="content" class:selected={isActive('model')}>
@ -200,12 +196,12 @@
<!-- PRE TRAINING STATUS --> <!-- PRE TRAINING STATUS -->
{:else if m.status == 2} {:else if m.status == 2}
<BaseModelInfo model={m} /> <BaseModelInfo model={m} />
<ModelData model={m} on:reload={getModel} /> <ModelData model={m} onreload={getModel} />
<!-- {{ template "train-model-card" . }} --> <!-- {{ template "train-model-card" . }} -->
<DeleteModel model={m} /> <DeleteModel model={m} />
{:else if m.status == -2} {:else if m.status == -2}
<BaseModelInfo model={m} /> <BaseModelInfo model={m} />
<DeleteZip model={m} on:reload={getModel} /> <DeleteZip model={m} onreload={getModel} />
<DeleteModel model={m} /> <DeleteModel model={m} />
{:else if m.status == 3} {:else if m.status == 3}
<BaseModelInfo model={m} /> <BaseModelInfo model={m} />
@ -215,10 +211,9 @@
</div> </div>
{:else if m.status == -3 || m.status == -4} {:else if m.status == -3 || m.status == -4}
<BaseModelInfo model={m} /> <BaseModelInfo model={m} />
<form on:submit|preventDefault={resetModel}> <form onsubmit={preventDefault(resetModel)}>
Failed Prepare for training.<br /> Failed Prepare for training.<br />
<div class="spacer"></div> <div class="spacer"></div>
<MessageSimple bind:this={resetMessages} />
<button class="danger"> Try Again </button> <button class="danger"> Try Again </button>
</form> </form>
<DeleteModel model={m} /> <DeleteModel model={m} />
@ -337,7 +332,7 @@
<div class="card">Model expading... Processing ZIP file</div> <div class="card">Model expading... Processing ZIP file</div>
{/if} {/if}
{#if m.status == -6} {#if m.status == -6}
<DeleteZip model={m} on:reload={getModel} expand /> <DeleteZip model={m} onreload={getModel} expand />
{/if} {/if}
{#if m.status == -7} {#if m.status == -7}
<form> <form>
@ -346,7 +341,7 @@
</form> </form>
{/if} {/if}
{#if m.model_type == 2} {#if m.model_type == 2}
<ModelData simple model={m} on:reload={getModel} /> <ModelData simple model={m} onreload={getModel} />
{/if} {/if}
<DeleteModel model={m} /> <DeleteModel model={m} />
{:else} {:else}
@ -383,10 +378,4 @@
table tr th:first-child { table tr th:first-child {
border-left: none; border-left: none;
} }
table tr td button,
table tr td .button {
padding: 5px 10px;
box-shadow: 0 2px 5px 1px #66666655;
}
</style> </style>

View File

@ -1,6 +1,6 @@
<script lang="ts"> <script lang="ts">
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
let { model } = $props<{ model: Model }>(); let { model }: { model: Model } = $props();
</script> </script>
<div class="card model-card"> <div class="card model-card">

View File

@ -1,39 +1,31 @@
<script lang="ts"> <script lang="ts">
import MessageSimple from 'src/lib/MessageSimple.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import { rdelete } from '$lib/requests.svelte'; import { rdelete, showMessage } from '$lib/requests.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
let { model } = $props<{ model: Model }>(); let { model }: { model: Model } = $props();
let name: string = $state(''); let name: string = $state('');
let submmited: boolean = $state(false); let submmited: boolean = $state(false);
let messageSimple: MessageSimple;
async function deleteModel() { async function deleteModel() {
submmited = true; submmited = true;
messageSimple.display('');
try { try {
await rdelete('models/delete', { id: model.id, name }); await rdelete('models/delete', { id: model.id, name });
goto('/models'); goto('/models');
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not delete the model');
messageSimple.display(await e.json());
} else {
messageSimple.display('Could not delete the model');
}
} }
} }
</script> </script>
<form on:submit|preventDefault={deleteModel} class:submmited class="danger-bg"> <form onsubmit={deleteModel} class:submmited class="danger-bg">
<fieldset> <fieldset>
<label for="name"> <label for="name">
To delete this model please type "{model.name}": To delete this model please type "{model.name}":
</label> </label>
<input name="name" id="name" required bind:value={name} /> <input name="name" id="name" required bind:value={name} />
</fieldset> </fieldset>
<MessageSimple bind:this={messageSimple} />
<button class="danger"> Delete </button> <button class="danger"> Delete </button>
</form> </form>

View File

@ -1,32 +1,30 @@
<script lang="ts"> <script lang="ts">
import { rdelete } from 'src/lib/requests.svelte'; import { rdelete, showMessage } from 'src/lib/requests.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte'; import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { createEventDispatcher } from 'svelte'; import { preventDefault } from 'src/lib/utils';
let message: MessageSimple; let {
model,
let { model, expand } = $props<{ model: Model; expand?: boolean }>(); expand,
onreload = () => {}
const dispatch = createEventDispatcher<{ reload: void }>(); }: {
model: Model;
expand?: boolean;
onreload?: () => void;
} = $props();
async function deleteZip() { async function deleteZip() {
message.clear();
try { try {
await rdelete('models/data/delete-zip-file', { id: model.id }); await rdelete('models/data/delete-zip-file', { id: model.id });
dispatch('reload'); onreload();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not delete the zip file');
message.display(await e.json());
} else {
message.display('Could not delete the zip file');
}
} }
} }
</script> </script>
<form on:submit|preventDefault={deleteZip}> <form onsubmit={preventDefault(deleteZip)}>
{#if expand} {#if expand}
Failed to proccess the zip file.<br /> Failed to proccess the zip file.<br />
Delete file and upload a correct version do add more classes.<br /> Delete file and upload a correct version do add more classes.<br />
@ -37,6 +35,5 @@
<br /> <br />
{/if} {/if}
<div class="spacer"></div> <div class="spacer"></div>
<MessageSimple bind:this={message} />
<button class="danger"> Delete Zip File </button> <button class="danger"> Delete Zip File </button>
</form> </form>

View File

@ -1,38 +1,29 @@
<script lang="ts" context="module">
export type Class = {
name: string;
id: string;
status: number;
};
</script>
<script lang="ts"> <script lang="ts">
import FileUpload from 'src/lib/FileUpload.svelte'; import FileUpload from 'src/lib/FileUpload.svelte';
import Tabs from 'src/lib/Tabs.svelte'; import Tabs from 'src/lib/Tabs.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import { postFormData, get } from 'src/lib/requests.svelte'; import type { Class } from './types';
import MessageSimple from 'src/lib/MessageSimple.svelte'; import { postFormData, get, showMessage } from 'src/lib/requests.svelte';
import { createEventDispatcher } from 'svelte';
import ModelTable from './ModelTable.svelte'; import ModelTable from './ModelTable.svelte';
import TrainModel from './TrainModel.svelte'; import TrainModel from './TrainModel.svelte';
import ZipStructure from './ZipStructure.svelte'; import ZipStructure from './ZipStructure.svelte';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { preventDefault } from 'src/lib/utils';
let { model, simple } = $props<{ model: Model; simple?: boolean }>(); let {
model,
simple,
onreload = () => {}
}: { model: Model; simple?: boolean; onreload?: () => void } = $props();
let classes: Class[] = $state([]); let classes: Class[] = $state([]);
let has_data: boolean = $state(false); let has_data: boolean = $state(false);
let file: File | undefined = $state(); let file: File | undefined = $state();
const dispatch = createEventDispatcher<{
reload: void;
}>();
let uploading: Promise<void> = $state(Promise.resolve()); let uploading: Promise<void> = $state(Promise.resolve());
let numberOfInvalidImages = $state(0); let numberOfInvalidImages = $state(0);
let uploadImage: MessageSimple;
async function uploadZip() { async function uploadZip() {
if (!file) return; if (!file) return;
@ -44,13 +35,9 @@
try { try {
await postFormData('models/data/upload', form); await postFormData('models/data/upload', form);
dispatch('reload'); onreload();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not upload data');
uploadImage.display(await e.json());
} else {
uploadImage.display('');
}
} }
uploading = Promise.resolve(); uploading = Promise.resolve();
@ -67,8 +54,8 @@
classes = data.classes; classes = data.classes;
numberOfInvalidImages = data.number_of_invalid_images; numberOfInvalidImages = data.number_of_invalid_images;
has_data = data.has_data; has_data = data.has_data;
} catch { } catch (e) {
return; showMessage(e, notificationStore, 'Could not get information on classes');
} }
} }
</script> </script>
@ -80,22 +67,22 @@
<p>You need to upload data so the model can train.</p> <p>You need to upload data so the model can train.</p>
<Tabs active="upload" let:isActive> <Tabs active="upload" let:isActive>
<div slot="buttons" let:setActive let:isActive> <div slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive('upload')} on:click={setActive('upload')}> <button class="tab" class:selected={isActive('upload')} onclick={setActive('upload')}>
Upload Upload
</button> </button>
<button <button
class="tab" class="tab"
class:selected={isActive('create-class')} class:selected={isActive('create-class')}
on:click={setActive('create-class')} onclick={setActive('create-class')}
> >
Create Class Create Class
</button> </button>
<button class="tab" class:selected={isActive('api')} on:click={setActive('api')}> <button class="tab" class:selected={isActive('api')} onclick={setActive('api')}>
Api Api
</button> </button>
</div> </div>
<div class="content" class:selected={isActive('upload')}> <div class="content" class:selected={isActive('upload')}>
<form on:submit|preventDefault={uploadZip}> <form onsubmit={preventDefault(uploadZip)}>
<fieldset class="file-upload"> <fieldset class="file-upload">
<label for="file">Data file</label> <label for="file">Data file</label>
<div class="form-msg"> <div class="form-msg">
@ -115,7 +102,6 @@
</div> </div>
</FileUpload> </FileUpload>
</fieldset> </fieldset>
<MessageSimple bind:this={uploadImage} />
{#if file} {#if file}
{#await uploading} {#await uploading}
<button disabled> Uploading </button> <button disabled> Uploading </button>
@ -126,7 +112,7 @@
</form> </form>
</div> </div>
<div class="content" class:selected={isActive('create-class')}> <div class="content" class:selected={isActive('create-class')}>
<ModelTable {classes} {model} on:reload={() => dispatch('reload')} /> <ModelTable {classes} {model} {onreload} />
</div> </div>
<div class="content" class:selected={isActive('api')}>TODO</div> <div class="content" class:selected={isActive('api')}>TODO</div>
</Tabs> </Tabs>
@ -139,33 +125,11 @@
These images will be delete when the model trains. These images will be delete when the model trains.
</p> </p>
{/if} {/if}
<Tabs active="create-class" let:isActive> <ModelTable {classes} {model} {onreload} />
<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} {/if}
</div> </div>
{/if} {/if}
{#if classes.some((item) => item.status == 1) && ![-6, 6].includes(model.status)} {#if classes.some((item) => item.status == 1) && ![-6, 6].includes(model.status)}
<TrainModel <TrainModel number_of_invalid_images={numberOfInvalidImages} {model} {has_data} {onreload} />
number_of_invalid_images={numberOfInvalidImages}
{model}
{has_data}
on:reload={() => dispatch('reload')}
/>
{/if} {/if}

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { createEventDispatcher } from 'svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import ModelData from './ModelData.svelte'; import ModelData from './ModelData.svelte';
import { post, showMessage } from 'src/lib/requests.svelte'; import { post, showMessage } from 'src/lib/requests.svelte';
@ -7,9 +6,11 @@
import type { ModelStats } from './types'; import type { ModelStats } from './types';
import DeleteZip from './DeleteZip.svelte'; import DeleteZip from './DeleteZip.svelte';
let { model, active }: { model: Model; active?: boolean } = $props(); let {
model,
const dispatch = createEventDispatcher<{ reload: void }>(); active,
onreload = () => {}
}: { model: Model; active?: boolean; onreload?: () => void } = $props();
$effect(() => { $effect(() => {
if (active) getData(); if (active) getData();
@ -34,14 +35,14 @@
{/if} {/if}
{#if [-6, -2].includes(model.status)} {#if [-6, -2].includes(model.status)}
<DeleteZip {model} on:reload={() => dispatch('reload')} expand /> <DeleteZip {model} {onreload} expand />
{/if} {/if}
<ModelData <ModelData
{model} {model}
on:reload={() => { onreload={() => {
getData(); getData();
dispatch('reload'); onreload();
}} }}
/> />
</div> </div>

View File

@ -97,7 +97,7 @@
}); });
</script> </script>
<div><canvas bind:this={ctx} /></div> <div><canvas bind:this={ctx}></canvas></div>
<style lang="scss"> <style lang="scss">
canvas { canvas {

View File

@ -1,33 +1,24 @@
<script lang="ts" context="module">
export type Image = {
file_path: string;
mode: number;
status: number;
id: string;
};
</script>
<script lang="ts"> <script lang="ts">
import Tabs from 'src/lib/Tabs.svelte'; import Tabs from 'src/lib/Tabs.svelte';
import type { Class } from './ModelData.svelte'; import type { Class, Image } from './types';
import { post, postFormData, rdelete, showMessage } from 'src/lib/requests.svelte'; import { post, postFormData, rdelete, showMessage } from 'src/lib/requests.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import FileUpload from 'src/lib/FileUpload.svelte'; import FileUpload from 'src/lib/FileUpload.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte';
import { createEventDispatcher } from 'svelte';
import ZipStructure from './ZipStructure.svelte'; import ZipStructure from './ZipStructure.svelte';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
const dispatch = createEventDispatcher<{ reload: void }>(); import { preventDefault } from 'src/lib/utils.js';
import CreateNewClass from './api/CreateNewClass.svelte';
let selected_class: Class | undefined = $state(); let selected_class: Class | undefined = $state();
let { classes, model }: { classes: Class[]; model: Model } = $props(); let { classes, model, onreload }: { classes: Class[]; model: Model; onreload?: () => void } =
$props();
let createClass: { className: string } = $state({ let createClass: { className: string } = $state({
className: '' className: ''
}); });
let page = $state(0); let page = $state(-1);
let showNext = $state(false); let showNext = $state(false);
let image_list = $state<Image[]>([]); let image_list = $state<Image[]>([]);
@ -41,6 +32,7 @@
}); });
async function getList() { async function getList() {
console.log(page);
try { try {
let res = await post('models/data/list', { let res = await post('models/data/list', {
id: selected_class?.id ?? '', id: selected_class?.id ?? '',
@ -53,19 +45,20 @@
} }
} }
$effect(() => {
getList();
});
$effect(() => { $effect(() => {
if (selected_class) { if (selected_class) {
page = 0; page = 0;
getList();
} }
}); });
let file: File | undefined = $state(); let file: File | undefined = $state();
let uploadImage: MessageSimple;
let uploading = $state(Promise.resolve()); let uploading = $state(Promise.resolve());
async function uploadZip() { async function uploadZip() {
uploadImage.clear();
if (!file) return; if (!file) return;
uploading = new Promise(() => {}); uploading = new Promise(() => {});
@ -76,19 +69,14 @@
try { try {
await postFormData('models/data/class/upload', form); await postFormData('models/data/class/upload', form);
dispatch('reload'); if (onreload) onreload();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Failed to upload');
uploadImage.display(await e.json());
} else {
uploadImage.display('');
}
} }
uploading = Promise.resolve(); uploading = Promise.resolve();
} }
let createNewClassMessages: MessageSimple;
async function createNewClass() { async function createNewClass() {
try { try {
const r = await post('models/data/class/new', { const r = await post('models/data/class/new', {
@ -100,7 +88,7 @@
classes = classes; classes = classes;
getList(); getList();
} catch (e) { } catch (e) {
showMessage(e, createNewClassMessages); showMessage(e, notificationStore);
} }
} }
@ -109,12 +97,11 @@
rdelete('models/data/point', { id }); rdelete('models/data/point', { id });
getList(); getList();
} catch (e) { } catch (e) {
console.error('TODO notify user', e); showMessage(e, notificationStore);
} }
} }
let addFile: File | undefined = $state(); let addFile: File | undefined = $state();
let addImageMessages: MessageSimple;
let adding = $state(Promise.resolve()); let adding = $state(Promise.resolve());
let uploadImageDialog: HTMLDialogElement; let uploadImageDialog: HTMLDialogElement;
async function addImage() { async function addImage() {
@ -136,7 +123,7 @@
addFile = undefined; addFile = undefined;
getList(); getList();
} catch (e) { } catch (e) {
showMessage(e, addImageMessages); showMessage(e, notificationStore);
} }
} }
</script> </script>
@ -151,30 +138,30 @@
{#each classes as item} {#each classes as item}
<button <button
style="width: auto; white-space: nowrap;" style="width: auto; white-space: nowrap;"
on:click={() => setActiveClass(item, setActive)} onclick={() => setActiveClass(item, setActive)}
class="tab" class="tab"
class:selected={isActive(item.name)} class:selected={isActive(item.name)}
> >
{item.name} {item.name}
{#if model.model_type == 2} {#if model.model_type == 2}
{#if item.status == 1} {#if item.status == 1}
<span class="bi bi-book" style="color: orange;" /> <span class="bi bi-book" style="color: orange;"></span>
{:else if item.status == 2} {:else if item.status == 2}
<span class="bi bi-book" style="color: green;" /> <span class="bi bi-book" style="color: green;"></span>
{:else if item.status == 3} {:else if item.status == 3}
<span class="bi bi-check" style="color: green;" /> <span class="bi bi-check" style="color: green;"></span>
{/if} {/if}
{/if} {/if}
</button> </button>
{/each} {/each}
</div> </div>
<button <button
on:click={() => { onclick={() => {
setActive('-----New Class-----')(); setActive('-----New Class-----')();
selected_class = undefined; selected_class = undefined;
}} }}
> >
<span class="bi bi-plus" /> <span class="bi bi-plus"></span>
</button> </button>
</div> </div>
{#if selected_class == undefined && isActive('-----New Class-----')} {#if selected_class == undefined && isActive('-----New Class-----')}
@ -184,21 +171,31 @@
<div slot="buttons" let:setActive let:isActive> <div slot="buttons" let:setActive let:isActive>
<button <button
class="tab" class="tab"
on:click|preventDefault={setActive('zip')} type="button"
onclick={setActive('zip')}
class:selected={isActive('zip')} class:selected={isActive('zip')}
> >
Zip Zip
</button> </button>
<button <button
class="tab" class="tab"
on:click|preventDefault={setActive('empty')} type="button"
onclick={setActive('empty')}
class:selected={isActive('empty')} class:selected={isActive('empty')}
> >
Empty Class Empty Class
</button> </button>
<button
class="tab"
type="button"
onclick={setActive('api')}
class:selected={isActive('api')}
>
API
</button>
</div> </div>
<div class="content" class:selected={isActive('zip')}> <div class="content" class:selected={isActive('zip')}>
<form on:submit|preventDefault={uploadZip}> <form onsubmit={preventDefault(uploadZip)}>
<fieldset class="file-upload"> <fieldset class="file-upload">
<label for="file">Data file</label> <label for="file">Data file</label>
<div class="form-msg"> <div class="form-msg">
@ -218,7 +215,6 @@
</div> </div>
</FileUpload> </FileUpload>
</fieldset> </fieldset>
<MessageSimple bind:this={uploadImage} />
{#if file} {#if file}
{#await uploading} {#await uploading}
<button disabled> Uploading </button> <button disabled> Uploading </button>
@ -229,7 +225,7 @@
</form> </form>
</div> </div>
<div class="content" class:selected={isActive('empty')}> <div class="content" class:selected={isActive('empty')}>
<form on:submit|preventDefault={createNewClass}> <form onsubmit={preventDefault(createNewClass)}>
<div class="form-msg"> <div class="form-msg">
This Creates an empty class that allows images to be added after This Creates an empty class that allows images to be added after
</div> </div>
@ -237,10 +233,12 @@
<label for="className">Class Name</label> <label for="className">Class Name</label>
<input required name="className" bind:value={createClass.className} /> <input required name="className" bind:value={createClass.className} />
</fieldset> </fieldset>
<MessageSimple bind:this={createNewClassMessages} />
<button> Create New Class </button> <button> Create New Class </button>
</form> </form>
</div> </div>
<div class="content" class:selected={isActive('api')}>
<CreateNewClass {model} />
</div>
</Tabs> </Tabs>
</div> </div>
{/if} {/if}
@ -258,7 +256,7 @@
{:else} {:else}
Class to train Class to train
{/if} {/if}
<button on:click={() => uploadImageDialog.showModal()}> Upload Image </button> <button onclick={() => uploadImageDialog.showModal()}> Upload Image </button>
</h2> </h2>
<table> <table>
<thead> <thead>
@ -314,7 +312,7 @@
{/if} {/if}
</td> </td>
<td style="width: 3ch"> <td style="width: 3ch">
<button class="danger" on:click={() => deleteDataPoint(image.id)}> <button class="danger" onclick={() => deleteDataPoint(image.id)}>
<span class="bi bi-trash"></span> <span class="bi bi-trash"></span>
</button> </button>
</td> </td>
@ -325,7 +323,7 @@
<div class="flex justify-center align-center"> <div class="flex justify-center align-center">
<div class="grow-1 flex justify-end align-center"> <div class="grow-1 flex justify-end align-center">
{#if page > 0} {#if page > 0}
<button on:click={() => (page -= 1)}> Prev </button> <button onclick={() => (page -= 1)}> Prev </button>
{/if} {/if}
</div> </div>
@ -335,7 +333,7 @@
<div class="grow-1 flex justify-start align-center"> <div class="grow-1 flex justify-start align-center">
{#if showNext} {#if showNext}
<button on:click={() => (page += 1)}> Next </button> <button onclick={() => (page += 1)}> Next </button>
{/if} {/if}
</div> </div>
</div> </div>
@ -345,7 +343,7 @@
{/if} {/if}
<dialog class="newImageDialog" bind:this={uploadImageDialog}> <dialog class="newImageDialog" bind:this={uploadImageDialog}>
<form on:submit|preventDefault={addImage}> <form onsubmit={preventDefault(addImage)}>
<fieldset class="file-upload"> <fieldset class="file-upload">
<label for="file">Data file</label> <label for="file">Data file</label>
<div class="form-msg"> <div class="form-msg">
@ -365,7 +363,6 @@
</div> </div>
</FileUpload> </FileUpload>
</fieldset> </fieldset>
<MessageSimple bind:this={addImageMessages} />
{#if addFile} {#if addFile}
{#await adding} {#await adding}
<button disabled> Uploading </button> <button disabled> Uploading </button>
@ -415,10 +412,4 @@
table tr th:first-child { table tr th:first-child {
border-left: none; border-left: none;
} }
table tr td button,
table tr td .button {
padding: 5px 10px;
box-shadow: 0 2px 5px 1px #66666655;
}
</style> </style>

View File

@ -2,20 +2,22 @@
import { post, postFormData, showMessage } from 'src/lib/requests.svelte'; import { post, postFormData, showMessage } from 'src/lib/requests.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import FileUpload from 'src/lib/FileUpload.svelte'; import FileUpload from 'src/lib/FileUpload.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte'; import { onDestroy } from 'svelte';
import { createEventDispatcher, onDestroy } from 'svelte';
import Spinner from 'src/lib/Spinner.svelte'; import Spinner from 'src/lib/Spinner.svelte';
import type { Task } from './tasks/TasksTable.svelte'; import type { Task } from './tasks/types';
import Tabs from 'src/lib/Tabs.svelte'; import Tabs from 'src/lib/Tabs.svelte';
import hljs from 'highlight.js'; import hljs from 'highlight.js';
import { notificationStore } from 'src/lib/NotificationsStore.svelte'; import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { preventDefault } from 'src/lib/utils';
let { model } = $props<{ model: Model }>(); let {
model,
onupload = () => {},
ontaskReload = () => {}
}: { model: Model; onupload?: () => void; ontaskReload?: () => void } = $props();
let file: File | undefined = $state(); let file: File | undefined = $state();
const dispatch = createEventDispatcher<{ upload: void; taskReload: void }>();
let _result: Promise<Task> = $state(new Promise(() => {})); let _result: Promise<Task> = $state(new Promise(() => {}));
let run = $state(false); let run = $state(false);
@ -32,7 +34,7 @@
const r = await post('tasks/task', { id: last_task }); const r = await post('tasks/task', { id: last_task });
if ([0, 1, 2, 3].includes(r.status)) { if ([0, 1, 2, 3].includes(r.status)) {
setTimeout(reloadLastTimeout, 500); setTimeout(reloadLastTimeout, 500);
setTimeout(() => dispatch('taskReload'), 500); setTimeout(ontaskReload, 500);
} else { } else {
_result = Promise.resolve(r); _result = Promise.resolve(r);
} }
@ -59,7 +61,7 @@
showMessage(e, notificationStore, 'Could not run the model'); showMessage(e, notificationStore, 'Could not run the model');
} }
dispatch('upload'); onupload();
} }
onDestroy(() => { onDestroy(() => {
@ -71,10 +73,10 @@
<Tabs active="upload" let:isActive> <Tabs active="upload" let:isActive>
<div class="buttons" slot="buttons" let:setActive let:isActive> <div class="buttons" slot="buttons" let:setActive let:isActive>
<button class="tab" class:selected={isActive('upload')} on:click={setActive('upload')}> <button class="tab" class:selected={isActive('upload')} onclick={setActive('upload')}>
Upload Upload
</button> </button>
<button class="tab" class:selected={isActive('api')} on:click={setActive('api')}> Api </button> <button class="tab" class:selected={isActive('api')} onclick={setActive('api')}> Api </button>
</div> </div>
<div class="content" class:selected={isActive('api')}> <div class="content" class:selected={isActive('api')}>
<div class="codeinfo"> <div class="codeinfo">
@ -134,7 +136,7 @@ const r = await fetch('${window.location.protocol}//${window.location.hostname}/
</div> </div>
</div> </div>
<div class="content" class:selected={isActive('upload')}> <div class="content" class:selected={isActive('upload')}>
<form on:submit|preventDefault={submit} style="box-shadow: none;"> <form onsubmit={preventDefault(submit)} style="box-shadow: none;">
<fieldset class="file-upload"> <fieldset class="file-upload">
<label for="file">Image</label> <label for="file">Image</label>
<div class="form-msg">Run image through them model and get the result</div> <div class="form-msg">Run image through them model and get the result</div>

View File

@ -1,5 +1,4 @@
<script lang="ts"> <script lang="ts">
import { post } from 'src/lib/requests.svelte';
import type { Model } from 'src/routes/models/edit/+page.svelte'; import type { Model } from 'src/routes/models/edit/+page.svelte';
import RunModel from './RunModel.svelte'; import RunModel from './RunModel.svelte';
import TasksTable from './tasks/TasksTable.svelte'; import TasksTable from './tasks/TasksTable.svelte';
@ -12,7 +11,7 @@
{#if active} {#if active}
<div class="content selected"> <div class="content selected">
<RunModel {model} on:upload={() => table.getList()} on:taskReload={() => table.getList()} /> <RunModel {model} onupload={() => table.getList()} ontaskReload={() => table.getList()} />
<TasksTable {model} bind:this={table} /> <TasksTable {model} bind:this={table} />
<Stats {model} /> <Stats {model} />
</div> </div>

View File

@ -1,14 +1,20 @@
<script lang="ts"> <script lang="ts">
import MessageSimple from 'src/lib/MessageSimple.svelte'; import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import type { Model } from './+page.svelte'; import type { Model } from './+page.svelte';
import { post } from 'src/lib/requests.svelte'; import { post, showMessage } from 'src/lib/requests.svelte';
import { createEventDispatcher } from 'svelte'; import { preventDefault } from 'src/lib/utils';
let { number_of_invalid_images, has_data, model } = $props<{ let {
number_of_invalid_images,
has_data,
model,
onreload = () => {}
}: {
number_of_invalid_images: number; number_of_invalid_images: number;
has_data: boolean; has_data: boolean;
model: Model; model: Model;
}>(); onreload?: () => void;
} = $props();
let data = $state({ let data = $state({
model_type: 'simple', model_type: 'simple',
@ -18,46 +24,32 @@
let submitted = $state(false); let submitted = $state(false);
let dispatch = createEventDispatcher<{ reload: void }>();
let messages: MessageSimple;
async function submit() { async function submit() {
messages.clear();
submitted = true; submitted = true;
try { try {
await post('models/train', { await post('models/train', {
id: model.id, id: model.id,
...data ...data
}); });
dispatch('reload'); onreload();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not start the training of the model');
messages.display(await e.json());
} else {
messages.display('Could not start the training of the model');
}
} }
} }
async function submitRetrain() { async function submitRetrain() {
messages.clear();
submitted = true; submitted = true;
try { try {
await post('model/train/retrain', { id: model.id }); await post('model/train/retrain', { id: model.id });
dispatch('reload'); onreload();
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not start the training of the model');
messages.display(await e.json());
} else {
messages.display('Could not start the training of the model');
}
} }
} }
</script> </script>
{#if model.status == 2} {#if model.status == 2}
<form class:submitted on:submit|preventDefault={submit}> <form class:submitted onsubmit={preventDefault(submit)}>
{#if has_data} {#if has_data}
{#if number_of_invalid_images > 0} {#if number_of_invalid_images > 0}
<p class="danger"> <p class="danger">
@ -65,7 +57,6 @@
These images will be delete when the model trains. These images will be delete when the model trains.
</p> </p>
{/if} {/if}
<MessageSimple bind:this={messages} />
<!-- TODO expading mode --> <!-- TODO expading mode -->
<fieldset> <fieldset>
<legend> Model Type </legend> <legend> Model Type </legend>
@ -111,7 +102,7 @@
{/if} {/if}
</form> </form>
{:else} {:else}
<form class:submitted on:submit|preventDefault={submitRetrain}> <form class:submitted onsubmit={submitRetrain}>
{#if has_data} {#if has_data}
<h2>This model has new classes and can be expanded</h2> <h2>This model has new classes and can be expanded</h2>
{#if number_of_invalid_images > 0} {#if number_of_invalid_images > 0}
@ -120,7 +111,6 @@
These images will be delete when the model trains. These images will be delete when the model trains.
</p> </p>
{/if} {/if}
<MessageSimple bind:this={messages} />
<button> Retrain </button> <button> Retrain </button>
{:else} {:else}
<h2>To train the model please provide data to the model first</h2> <h2>To train the model please provide data to the model first</h2>

View File

@ -0,0 +1,27 @@
<script lang="ts">
import hljs from 'highlight.js';
import type { Model } from '../+page.svelte';
let { model }: { model: Model } = $props();
</script>
To create a new class via the API you can:
<pre style="font-family: Fira Code;">{@html hljs.highlight(
`let form = new FormData();
form.append('json_data', JSON.stringify({
id: '${model.id}',
name: 'New class name'
}));
form.append('file', file, 'file');
const headers = new Headers();
headers.append('response-type', 'application/json');
headers.append('token', token);
const r = await fetch('${window.location.protocol}//${window.location.hostname}/models/data/class/new', {
method: 'POST',
headers: headers,
body: form
});`,
{ language: 'javascript' }
).value}</pre>

View File

@ -1,5 +1,5 @@
<script lang="ts"> <script lang="ts">
import { onDestroy, onMount } from 'svelte'; import { onDestroy } from 'svelte';
import type { Model } from '../+page.svelte'; import type { Model } from '../+page.svelte';
import { post, showMessage } from 'src/lib/requests.svelte'; import { post, showMessage } from 'src/lib/requests.svelte';
import type { DataPoint, TasksStatsDay } from 'src/types/stats/task'; import type { DataPoint, TasksStatsDay } from 'src/types/stats/task';
@ -124,7 +124,6 @@
nc_error: 'Non Classfication Error', nc_error: 'Non Classfication Error',
nc_success: 'Non Classfication Success' nc_success: 'Non Classfication Success'
}; };
let t = s.total;
let labels = new Array(24).fill(0).map((_, i) => i); let labels = new Array(24).fill(0).map((_, i) => i);
lineChart = new Chart(line, { lineChart = new Chart(line, {
type: 'line', type: 'line',

View File

@ -40,7 +40,6 @@
<script lang="ts"> <script lang="ts">
import { post, showMessage } from 'src/lib/requests.svelte'; import { post, showMessage } from 'src/lib/requests.svelte';
import type { Model } from '../+page.svelte'; import type { Model } from '../+page.svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte';
import Tooltip from 'src/lib/Tooltip.svelte'; import Tooltip from 'src/lib/Tooltip.svelte';
import type { Task } from './types'; import type { Task } from './types';
@ -70,7 +69,6 @@
} }
}); });
let userPreceptionMessages: MessageSimple;
// This returns a function that performs the call and does not do the call it self // This returns a function that performs the call and does not do the call it self
function userPreception(task: string, agree: number) { function userPreception(task: string, agree: number) {
return async function () { return async function () {
@ -89,7 +87,6 @@
<div> <div>
<h2>Tasks</h2> <h2>Tasks</h2>
<MessageSimple bind:this={userPreceptionMessages} />
<table> <table>
<thead> <thead>
<tr> <tr>
@ -146,14 +143,14 @@
<div> <div>
{#if task.user_confirmed != 1} {#if task.user_confirmed != 1}
<Tooltip title="Agree with the result of the task"> <Tooltip title="Agree with the result of the task">
<button type="button" on:click={userPreception(task.id, 1)}> <button type="button" onclick={userPreception(task.id, 1)}>
<span class="bi bi-check"></span> <span class="bi bi-check"></span>
</button> </button>
</Tooltip> </Tooltip>
{/if} {/if}
{#if task.user_confirmed != -1} {#if task.user_confirmed != -1}
<Tooltip title="Disagree with the result"> <Tooltip title="Disagree with the result">
<button class="danger" type="button" on:click={userPreception(task.id, -1)}> <button class="danger" type="button" onclick={userPreception(task.id, -1)}>
<span class="bi bi-x-lg"></span> <span class="bi bi-x-lg"></span>
</button> </button>
</Tooltip> </Tooltip>
@ -198,7 +195,7 @@
<div class="flex justify-center align-center"> <div class="flex justify-center align-center">
<div class="grow-1 flex justify-end align-center"> <div class="grow-1 flex justify-end align-center">
{#if page > 0} {#if page > 0}
<button on:click={() => (page -= 1)}> Prev </button> <button onclick={() => (page -= 1)}> Prev </button>
{/if} {/if}
</div> </div>
@ -208,7 +205,7 @@
<div class="grow-1 flex justify-start align-center"> <div class="grow-1 flex justify-start align-center">
{#if showNext} {#if showNext}
<button on:click={() => (page += 1)}> Next </button> <button onclick={() => (page += 1)}> Next </button>
{/if} {/if}
</div> </div>
</div> </div>
@ -219,10 +216,6 @@
width: 100%; width: 100%;
display: flex; display: flex;
justify-content: space-between; justify-content: space-between;
& > button {
margin: 3px 5px;
}
} }
table { table {

View File

@ -3,3 +3,16 @@ export type ModelStats = Array<{
training: number; training: number;
testing: number; testing: number;
}>; }>;
export type Class = {
name: string;
id: string;
status: number;
};
export type Image = {
file_path: string;
mode: number;
status: number;
id: string;
};

View File

@ -3,6 +3,7 @@
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import { userStore } from '../UserStore.svelte'; import { userStore } from '../UserStore.svelte';
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { preventDefault } from 'src/lib/utils';
let submitted = $state(false); let submitted = $state(false);
@ -39,7 +40,7 @@
<div class="login-page"> <div class="login-page">
<div> <div>
<h1>Register</h1> <h1>Register</h1>
<form on:submit|preventDefault={onSubmit} class:submitted> <form onsubmit={preventDefault(onSubmit)} class:submitted>
<fieldset> <fieldset>
<label for="username">Username</label> <label for="username">Username</label>
<input required name="username" bind:value={loginData.username} /> <input required name="username" bind:value={loginData.username} />

View File

@ -4,10 +4,11 @@
import { onMount } from 'svelte'; import { onMount } from 'svelte';
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import { post } from 'src/lib/requests.svelte'; import { post, showMessage } from 'src/lib/requests.svelte';
import MessageSimple, { type DisplayFn } from 'src/lib/MessageSimple.svelte';
import TokenTable from './TokenTable.svelte'; import TokenTable from './TokenTable.svelte';
import DeleteUser from './DeleteUser.svelte'; import DeleteUser from './DeleteUser.svelte';
import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { preventDefault } from 'src/lib/utils';
onMount(() => { onMount(() => {
if (!userStore.isLogin()) { if (!userStore.isLogin()) {
@ -26,12 +27,8 @@
let submiitedEmail = $state(false); let submiitedEmail = $state(false);
let submiitedPassword = $state(false); let submiitedPassword = $state(false);
let msgEmail: MessageSimple;
let msgPassword: MessageSimple;
async function onSubmitEmail() { async function onSubmitEmail() {
submiitedEmail = true; submiitedEmail = true;
msgEmail.display('');
if (!userStore.user) return; if (!userStore.user) return;
@ -44,31 +41,31 @@
...userStore.user, ...userStore.user,
...req ...req
}; };
msgEmail.display('User updated successufly!', { type: 'success', timeToShow: 10000 }); notificationStore.add({
message: 'User updated successufly!',
type: 'success',
timeToLive: 10000
});
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not update email');
msgEmail.display(await e.json());
} else {
msgEmail.display('Could not update email');
}
} }
} }
async function onSubmitPassword() { async function onSubmitPassword() {
submiitedPassword = true; submiitedPassword = true;
msgPassword.display('');
if (!userStore.user) return; if (!userStore.user) return;
try { try {
await post('user/info/password', passwordData); await post('user/info/password', passwordData);
passwordData = { old_password: '', password: '', password2: '' }; passwordData = { old_password: '', password: '', password2: '' };
msgPassword.display('Password updated successufly!', { type: 'success', timeToShow: 10000 });
notificationStore.add({
message: 'Password updated successufly!',
type: 'success',
timeToLive: 10000
});
} catch (e) { } catch (e) {
if (e instanceof Response) { showMessage(e, notificationStore, 'Could not update password');
msgPassword.display(await e.json());
} else {
msgPassword.display('Could not update password');
}
} }
} }
</script> </script>
@ -80,15 +77,14 @@
<div class="login-page"> <div class="login-page">
<div> <div>
<h1>User Infomation</h1> <h1>User Infomation</h1>
<form on:submit|preventDefault={onSubmitEmail} class:submiitedEmail> <form onsubmit={onSubmitEmail} class:submiitedEmail>
<fieldset> <fieldset>
<label for="email">Email</label> <label for="email">Email</label>
<input type="email" required name="email" bind:value={email} /> <input type="email" required name="email" bind:value={email} />
</fieldset> </fieldset>
<MessageSimple bind:this={msgEmail} />
<button> Update </button> <button> Update </button>
</form> </form>
<form on:submit|preventDefault={onSubmitPassword} class:submiitedPassword> <form onsubmit={preventDefault(onSubmitPassword)} class:submiitedPassword>
<fieldset> <fieldset>
<label for="old_password">Old Password</label> <label for="old_password">Old Password</label>
<input <input
@ -106,7 +102,6 @@
<label for="password2">Repeat New Password</label> <label for="password2">Repeat New Password</label>
<input required bind:value={passwordData.password2} name="password2" type="password" /> <input required bind:value={passwordData.password2} name="password2" type="password" />
</fieldset> </fieldset>
<MessageSimple bind:this={msgPassword} />
<div> <div>
<button> Update </button> <button> Update </button>
</div> </div>

View File

@ -1,36 +1,32 @@
<script lang="ts"> <script lang="ts">
import {createEventDispatcher} from 'svelte';
import MessageSimple from 'src/lib/MessageSimple.svelte';
import Tooltip from 'src/lib/Tooltip.svelte'; import Tooltip from 'src/lib/Tooltip.svelte';
import 'src/styles/forms.css'; import 'src/styles/forms.css';
import {post} from 'src/lib/requests.svelte'; import { post } from 'src/lib/requests.svelte';
import Spinner from 'src/lib/Spinner.svelte'; import Spinner from 'src/lib/Spinner.svelte';
import { preventDefault } from 'src/lib/utils';
let { onreload = () => {} }: { onreload?: () => void } = $props();
const dispatch = createEventDispatcher<{reload: void}>();
let addNewToken = $state(false); let addNewToken = $state(false);
let messages: MessageSimple;
let expiry_date: HTMLInputElement = $state(undefined as any); let expiry_date: HTMLInputElement = $state(undefined as any);
type NewToken = {
name: string,
expiry: number,
token: string,
}
let token: Promise<NewToken> | undefined = $state(); type NewToken = {
name: string;
expiry: number;
token: string;
};
let token: Promise<NewToken> | undefined = $state();
let newToken: { let newToken: {
name: string; name: string;
expiry: string; expiry: string;
password: string; password: string;
} = $state({ } = $state({
name: '', name: '',
expiry: '', expiry: '',
password: '', password: ''
}); });
async function createToken(e: SubmitEvent & { currentTarget: HTMLFormElement }) { async function createToken(e: SubmitEvent & { currentTarget: HTMLFormElement }) {
@ -47,86 +43,85 @@
} }
} }
try { try {
const r = await post("user/token/add", { const r = await post('user/token/add', {
name: newToken.name, name: newToken.name,
expiry: expiry, expiry: expiry,
password: newToken.password, password: newToken.password
}); });
token = Promise.resolve(r) token = Promise.resolve(r);
setTimeout(() => dispatch('reload'), 500) setTimeout(onreload, 500);
} catch (e) { } catch (e) {
token = undefined; token = undefined;
console.error("Notify user", e) console.error('Notify user', e);
} }
} }
$effect(() => { $effect(() => {
if (expiry_date) { if (expiry_date) {
if (isNaN(Number(newToken.expiry))) { if (isNaN(Number(newToken.expiry))) {
expiry_date.setCustomValidity('Invalid Number'); expiry_date.setCustomValidity('Invalid Number');
} else { } else {
expiry_date.setCustomValidity(''); expiry_date.setCustomValidity('');
} }
} }
}); });
</script> </script>
{#if addNewToken} {#if addNewToken}
<div> <div>
<h2>Add New Token</h2> <h2>Add New Token</h2>
{#if !token} {#if !token}
<form on:submit|preventDefault={createToken}> <form onsubmit={preventDefault(createToken)}>
<fieldset> <fieldset>
<label for="name">Name</label> <label for="name">Name</label>
<input required bind:value={newToken.name} name="name" /> <input required bind:value={newToken.name} name="name" />
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="expiry_date">Expiry Date</label> <label for="expiry_date">Expiry Date</label>
<div class="flex"> <div class="flex">
<input bind:this={expiry_date} bind:value={newToken.expiry} name="expiry_date" /> <input bind:this={expiry_date} bind:value={newToken.expiry} name="expiry_date" />
<Tooltip title="Time in seconds. Leave empty to last forever"> <Tooltip title="Time in seconds. Leave empty to last forever">
<span class="center-question bi bi-question-circle-fill" /> <span class="center-question bi bi-question-circle-fill"></span>
</Tooltip> </Tooltip>
</div> </div>
</fieldset> </fieldset>
<fieldset> <fieldset>
<label for="password">Password</label> <label for="password">Password</label>
<input required bind:value={newToken.password} name="password" /> <input required bind:value={newToken.password} name="password" />
</fieldset> </fieldset>
<MessageSimple bind:this={messages} /> <div>
<div> <button> Update </button>
<button> Update </button> </div>
</div> </form>
</form> {:else}
{:else} {#await token}
{#await token} <Spinner /> Generating
<Spinner /> Generating {:then t}
{:then t} <h3>Token generated</h3>
<h3> Token generated </h3> <form onsubmit={preventDefault(() => {})}>
<form on:submit|preventDefault={() => {}}> <fieldset>
<fieldset> <label for="token">Token</label>
<label for="token">Token</label> <div class="flex">
<div class="flex"> <input value={t.token} oninput={(e) => e.preventDefault()} name="token" />
<input value={t.token} on:input={(e) => e.preventDefault() } name="token" /> <div style="width: 5em;">
<div style="width: 5em;"> <button onclick={() => navigator.clipboard.writeText(t.token)}>
<button on:click={() => navigator.clipboard.writeText(t.token)} > <span class="bi bi-clipboard"></span>
<span class="bi bi-clipboard" /> </button>
</button> </div>
</div> </div>
</div> </fieldset>
</fieldset> <div>
<div> <button onclick={() => (token = undefined)}> Generate new token </button>
<button on:click={() => token = undefined}> Generate new token </button> </div>
</div> </form>
</form> {:catch e}
{:catch e} {e}
{e} {/await}
{/await} {/if}
{/if}
</div> </div>
{:else} {:else}
<div> <div>
<button class="expander" on:click={() => (addNewToken = true)}> Add New Token </button> <button class="expander" onclick={() => (addNewToken = true)}> Add New Token </button>
</div> </div>
{/if} {/if}

View File

@ -2,6 +2,7 @@
import { goto } from '$app/navigation'; import { goto } from '$app/navigation';
import { notificationStore } from 'src/lib/NotificationsStore.svelte'; import { notificationStore } from 'src/lib/NotificationsStore.svelte';
import { rdelete, showMessage } from 'src/lib/requests.svelte'; import { rdelete, showMessage } from 'src/lib/requests.svelte';
import { preventDefault } from 'src/lib/utils';
import { userStore } from 'src/routes/UserStore.svelte'; import { userStore } from 'src/routes/UserStore.svelte';
let data = $state({ password: '' }); let data = $state({ password: '' });
@ -23,7 +24,7 @@
} }
</script> </script>
<form class="danger-bg" on:submit|preventDefault={deleteUser}> <form class="danger-bg" onsubmit={preventDefault(deleteUser)}>
<h2 class="no-top-margin">Delete user</h2> <h2 class="no-top-margin">Delete user</h2>
Deleting the user will delete all your data stored in the service including the images. Deleting the user will delete all your data stored in the service including the images.
<fieldset> <fieldset>

View File

@ -10,7 +10,7 @@
import { post, rdelete } from 'src/lib/requests.svelte'; import { post, rdelete } from 'src/lib/requests.svelte';
import { userStore } from 'src/routes/UserStore.svelte'; import { userStore } from 'src/routes/UserStore.svelte';
import AddToken from './AddToken.svelte'; import AddToken from './AddToken.svelte';
let page = $state(0); let page = $state(0);
let showNext = $state(false); let showNext = $state(false);
@ -70,7 +70,7 @@
{new Date(token.create_date).toLocaleString()} {new Date(token.create_date).toLocaleString()}
</td> </td>
<td> <td>
<button class="danger" on:click={() => removeToken(token)}> <button class="danger" onclick={() => removeToken(token)}>
<span class="bi bi-trash"></span> <span class="bi bi-trash"></span>
</button> </button>
</td> </td>
@ -81,7 +81,7 @@
<div class="flex justify-center align-center"> <div class="flex justify-center align-center">
<div class="grow-1 flex justify-end align-center"> <div class="grow-1 flex justify-end align-center">
{#if page > 0} {#if page > 0}
<button on:click={() => (page -= 1)}> Prev </button> <button onclick={() => (page -= 1)}> Prev </button>
{/if} {/if}
</div> </div>
@ -91,25 +91,15 @@
<div class="grow-1 flex justify-start align-center"> <div class="grow-1 flex justify-start align-center">
{#if showNext} {#if showNext}
<button on:click={() => (page += 1)}> Next </button> <button onclick={() => (page += 1)}> Next </button>
{/if} {/if}
</div> </div>
</div> </div>
</div> </div>
<AddToken on:reload={getList} /> <AddToken onreload={getList} />
<style lang="scss"> <style lang="scss">
.buttons {
width: 100%;
display: flex;
justify-content: space-between;
& > button {
margin: 3px 5px;
}
}
table { table {
width: 100%; width: 100%;
box-shadow: 0 2px 8px 1px #66666622; box-shadow: 0 2px 8px 1px #66666622;
@ -132,10 +122,4 @@
table tr th:first-child { table tr th:first-child {
border-left: none; border-left: none;
} }
table tr td button,
table tr td .button {
padding: 5px 10px;
box-shadow: 0 2px 5px 1px #66666655;
}
</style> </style>

View File

@ -65,6 +65,7 @@ button.expander::after {
a.button { a.button {
text-decoration: none; text-decoration: none;
height: 1.6em;
} }
.flex { .flex {
@ -190,3 +191,12 @@ form.danger-bg {
background-color: var(--danger-transparent); background-color: var(--danger-transparent);
border: 1px solid var(--danger); border: 1px solid var(--danger);
} }
pre {
font-family: 'Fira Code';
background-color: #f6f8fa;
word-break: break-word;
white-space: pre-wrap;
border-radius: 10px;
padding: 10px;
}

View File

@ -12,10 +12,10 @@ const config = {
// If your environment is not supported or you settled on a specific environment, switch out the adapter. // If your environment is not supported or you settled on a specific environment, switch out the adapter.
// See https://kit.svelte.dev/docs/adapters for more information about adapters. // See https://kit.svelte.dev/docs/adapters for more information about adapters.
adapter: adapter(), adapter: adapter(),
alias: { alias: {
src: "src", src: 'src',
routes: "src/routes", routes: 'src/routes'
} }
} }
}; };