diff --git a/src/api.js b/src/api.js index 2b324942ccc5b643b84ed1df7036cefda26217cb..cb1a088dd1616b2dbfe7c311d659f709f85d5a75 100644 --- a/src/api.js +++ b/src/api.js @@ -302,5 +302,79 @@ export const api = { } catch (error) { return null } + }, + async postModel (model) { + const url = this.$serverurl + 'models' + const token = await localStorage.getItem('token') + try { + const response = await fetch(url, { + method: 'POST', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token + }, + body: JSON.stringify({ + name: model.name, + shortDescription: model.shortDescription, + longDescription: model.longDescription, + performance: model.performance, + performanceUnit: model.performanceUnit, + parameterCount: model.parameterCount, + tags: model.tags + }) + }) + return await response.json() + } catch (error) { + return null + } + }, + async putModel (id, model) { + const url = serverurl + 'models' + '?id=' + id + const token = await localStorage.getItem('token') + const response = await fetch(url, { + method: 'PUT', + headers: { + Accept: 'application/json', + 'Content-Type': 'application/json', + Authorization: 'Bearer ' + token + }, + body: JSON.stringify({ + name: model.name, + shortDescription: model.shortDescription, + longDescription: model.longDescription, + performance: model.performance, + performanceUnit: model.performanceUnit, + parameterCount: model.parameterCount, + tags: model.tags + }) + }) + try { + return await response.json() + } catch (error) { + return null + } + }, + async uploadModelFile (id, file) { + const data = new FormData() + const url = serverurl + 'models/upload' + const token = await localStorage.getItem('token') + + data.append('file', file) + data.append('id', id) + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + Authorization: 'Bearer ' + token + }, + body: data + }) + if (response.status !== 200) return false + return true + } catch (error) { + return false + } } } diff --git a/src/components/MarkdownEditor.vue b/src/components/MarkdownEditor.vue index 24798291d48282509eda207cc5b901febfbd625d..42d3660a51cfaff9d3c49ec91d1e4047b392035a 100644 --- a/src/components/MarkdownEditor.vue +++ b/src/components/MarkdownEditor.vue @@ -2,7 +2,7 @@ <div class="markdownEditor container"> <b-tabs> <b-tab-item label="Edit" class="edit"> - <b-input type="textarea" v-model="input"/> + <b-input type="textarea" v-model="input" maxlength="5000"/> </b-tab-item> <b-tab-item label="Preview"> diff --git a/src/components/ModelUpdate.vue b/src/components/ModelUpdate.vue new file mode 100644 index 0000000000000000000000000000000000000000..ee91af24b8c83c7fce186752dbe2133ecdc7e3ae --- /dev/null +++ b/src/components/ModelUpdate.vue @@ -0,0 +1,154 @@ +<template> + <div class="modal-card"> + <b-progress :value="progress" size="is-large" show-value :type="status"> + {{statusMessage}} + </b-progress> + <b-progress v-if="subProgress" size="is-large"> + {{subMessage}} + </b-progress> + <b-button + v-if="progress === 100" + type="is-success" + tag="router-link" + :to="{ name: 'Model', query: { id: model.id } }" + > + Go to model page + </b-button> + </div> +</template> + +<script> +import { api } from '@/api.js' + +export default { + name: 'ModelUpdate', + props: { + model: { + type: Object, + default: function () { + return {} + } + } + }, + data () { + return { + progress: 0, + status: 'is-primary', + statusMessage: '', + subProgress: false, + subMessage: '', + modelId: null + } + }, + mounted () { + this.saveModel() + }, + methods: { + preCheck () { + if (this.model.name === undefined || this.model.name === null || this.model.name === '') { + this.setError('The model name is empty') + return false + } + + return true + }, + setProgress (step) { + switch (step) { + case 0: + this.statusMessage = 'Sending description' + this.progress = 25 + break + case 1: + this.statusMessage = 'Sending model file' + this.progress = 50 + break + case 2: + this.statusMessage = 'Sending custom layers model' + this.progress = 75 + break + case 3: + this.statusMessage = 'Update completed !' + this.progress = 100 + this.status = 'is-success' + break + } + }, + setError (message) { + this.statusMessage = message + this.status = 'is-danger' + }, + setSubMessage (message) { + this.subMessage = message + }, + async saveModel () { + if (!this.preCheck()) return + this.setProgress(0) + var response = await api.putModel(this.model.id, this.model) + if (response.error) { + this.setError(response.message) + return + } else { + this.modelId = response.message + } + + if (this.model.file !== undefined) { + console.log('Upload model ' + this.model.file) + this.setProgress(1) + if (!await api.uploadModelFile(this.model.id, this.model.file)) { + this.setError('Model upload error') + return + } + } + + if (this.model.customLayers !== undefined) { + console.log('Upload layers ' + this.model.customLayers) + this.setProgress(2) + if (!await this.uploadLayers()) { + this.setError('Custom layer upload error') + return + } + } + + this.setProgress(3) + }, + async uploadLayers () { + this.subProgress = true + const token = await localStorage.getItem('token') + for (var i = 0; i < this.model.customLayers.length; i++) { + const layer = this.model.customLayers[i] + const data = new FormData() + const url = this.$serverurl + 'models/uploadLayer' + + this.subMessage = 'Uploading ' + layer.name + + console.log('Uploading layer ' + i + ' | ' + layer.file) + if (layer.file === undefined) continue + if (layer.name === undefined) continue + + data.append('file', layer.file) + data.append('name', layer.name) + data.append('id', this.modelId) + + try { + const response = await fetch(url, { + method: 'POST', + headers: { + Authorization: 'Bearer ' + token + }, + body: data + }) + if (response.status !== 200) return false + } catch (error) { + return false + } + } + this.subProgress = false + return true + } + } +} +</script> + +<style> + +</style> diff --git a/src/components/ModelUpload.vue b/src/components/ModelUpload.vue index 1d1e304ad2ff49e9dc7f9d9e9fac8c7f09cd6595..8f451e4316424cbaf72c2f5551ec1168aa1a2475 100644 --- a/src/components/ModelUpload.vue +++ b/src/components/ModelUpload.vue @@ -18,6 +18,8 @@ </template> <script> +import { api } from '@/api.js' + export default { name: 'ModelUpload', props: { @@ -42,6 +44,14 @@ export default { this.saveModel() }, methods: { + preCheck () { + if (this.model.name === undefined || this.model.name === null || this.model.name === '') { + this.setError('The model name is empty') + return false + } + + return true + }, setProgress (step) { switch (step) { case 0: @@ -71,8 +81,9 @@ export default { this.subMessage = message }, async saveModel () { + if (!this.preCheck()) return this.setProgress(0) - var response = await this.uploadModel() + var response = await api.postModel(this.model) if (response.error) { this.setError(response.message) return @@ -83,7 +94,7 @@ export default { if (this.model.file !== undefined) { console.log('Upload model ' + this.model.file) this.setProgress(1) - if (!await this.uploadModelFile()) { + if (!await api.uploadModelFile(this.modelId, this.model.file)) { this.setError('Model upload error') return } @@ -100,54 +111,6 @@ export default { this.setProgress(3) }, - async uploadModel () { - const url = this.$serverurl + 'models' - const token = await localStorage.getItem('token') - try { - const response = await fetch(url, { - method: 'POST', - headers: { - Accept: 'application/json', - 'Content-Type': 'application/json', - Authorization: 'Bearer ' + token - }, - body: JSON.stringify({ - name: this.model.name, - shortDescription: this.model.shortDescription, - longDescription: this.model.longDescription, - performance: this.model.performance, - performanceUnit: this.model.performanceUnit, - parameterCount: this.model.parameterCount, - tags: this.model.tags - }) - }) - return await response.json() - } catch (error) { - return null - } - }, - async uploadModelFile () { - const data = new FormData() - const url = this.$serverurl + 'models/upload' - const token = await localStorage.getItem('token') - - data.append('file', this.model.file) - data.append('id', this.modelId) - - try { - const response = await fetch(url, { - method: 'POST', - headers: { - Authorization: 'Bearer ' + token - }, - body: data - }) - if (response.status !== 200) return false - return true - } catch (error) { - return false - } - }, async uploadLayers () { this.subProgress = true const token = await localStorage.getItem('token') diff --git a/src/components/ModelValidator.vue b/src/components/ModelValidator.vue deleted file mode 100644 index d100af2e24a9f1b19fa8938fe07c76586b0a2552..0000000000000000000000000000000000000000 --- a/src/components/ModelValidator.vue +++ /dev/null @@ -1,38 +0,0 @@ -<template> - <div class="modelValidator"> - - </div> -</template> - -<script> -export default { - name: 'ModelValidator', - props: { - model: { - type: Object, - default: function () { - return {} - } - } - }, - data () { - return { - errorMessage: '' - } - }, - methods: { - validate () { - if (this.model.name === undefined || this.model.name === null || this.model.name === '') { - this.errorMessage = 'The model name is empty' - return false - } - - return true - } - } -} -</script> - -<style> - -</style> diff --git a/src/views/Model.vue b/src/views/Model.vue index bee31850496dae3eb98445aec406f368d7d192dc..c147cc3994ae8316ba1138ded1da2c3a84893dde 100644 --- a/src/views/Model.vue +++ b/src/views/Model.vue @@ -31,9 +31,14 @@ <small>{{model.performanceUnit}}: </small><strong>{{model.performance}}</strong> <br> <br> - <b-button v-if="model.checksum" type="is-primary" size="is-medium" icon-left="download"> - Download model - </b-button> + <div v-if="model.checksum"> + <b-button v-if="model.checksum" type="is-primary" size="is-medium" icon-left="download"> + Download model + </b-button> + <br> + <br> + <small>Checksum: {{model.checksum}}</small> + </div> <h2 v-else class="title is-5">No file provided</h2> <hr> <strong>{{model.customLayers.length}} customs layers</strong> diff --git a/src/views/ModelAdd.vue b/src/views/ModelAdd.vue index 9acae99427ec4c358de2cffbb7787c0d84b47863..ea3716f577354bec04993295da213e6e49d3b52f 100644 --- a/src/views/ModelAdd.vue +++ b/src/views/ModelAdd.vue @@ -3,7 +3,7 @@ <b-steps v-model="activeStep"> <b-step-item step="1" label="Description" clickable> <b-field label="Model name"> - <b-input maxlength="30" size="is-large" v-model="model.name"/> + <b-input minlength="3" maxlength="100" size="is-large" v-model="model.name"/> </b-field> <b-field label="Short description"> @@ -16,13 +16,13 @@ <b-field label="Performance"> <b-field> - <b-input type="number" placeholder="98" step="0.01" v-model="model.performance"/> - <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit"/> + <b-input type="number" placeholder="98" max="10000000" step="0.01" v-model="model.performance"/> + <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit" maxlength="50"/> </b-field> </b-field> <b-field label="Number of parameters"> - <b-input type="number" min="0" placeholder="0" v-model="model.parameterCount"/> + <b-input type="number" min="0" max="10000000" placeholder="0" v-model="model.parameterCount"/> </b-field> </b-step-item> diff --git a/src/views/ModelEdit.vue b/src/views/ModelEdit.vue index 87b39ebf0e9007b54c97d6c08c1d40c02e6d8b28..5b1f89af37a11d122cdfe464ed0484e9cb349343 100644 --- a/src/views/ModelEdit.vue +++ b/src/views/ModelEdit.vue @@ -1,73 +1,76 @@ <template> <div class="modelEdit container"> <div class="box"> - <h1 class="title">Model name</h1> - <b-field> - <b-input maxlength="30" size="is-large" v-model="model.name"/> - </b-field> - - <hr> - <h1 class="title">Description</h1> - <b-field label="Short description"> - <b-input maxlength="200" type="textarea" v-model="model.shortDescription"/> - </b-field> - - <b-field label="Long description"> - <markdownEditor v-bind:input="model.longDescription" v-on:update-description="model.longDescription = $event"/> - </b-field> + <b-tabs> + <b-tab-item label="Description"> + <h1 class="title">Description</h1> + <b-field label="Model name"> + <b-input minlength="3" maxlength="100" size="is-large" v-model="model.name"/> + </b-field> - <b-field label="Performance"> - <b-field> - <b-input type="number" placeholder="98" step="0.01" v-model="model.performance"/> - <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit"/> - </b-field> - </b-field> + <b-field label="Short description"> + <b-input maxlength="200" type="textarea" v-model="model.shortDescription"/> + </b-field> - <b-field label="Number of parameters"> - <b-input type="number" min="0" placeholder="0" v-model="model.parameterCount"/> - </b-field> + <b-field label="Long description"> + <markdownEditor v-bind:input="model.longDescription" v-on:update-description="model.longDescription = $event"/> + </b-field> - <hr> - <h1 class="title">Tags</h1> - <tagEditor v-bind:modelTags="model.tags" v-on:update-tags="model.tags = $event"/> + <b-field label="Performance"> + <b-field> + <b-input type="number" placeholder="98" max="10000000" step="0.01" v-model="model.performance"/> + <b-autocomplete open-on-focus placeholder="accuracy" :data="performanceTypes" v-model="model.performanceUnit" maxlength="50"/> + </b-field> + </b-field> - <hr> - <h1 class="title">Files</h1> - <div class="columns"> - <div class="column"> - <b-field label="Model"> - <b-upload v-model="model.file" drag-drop expanded> - <section class="section"> - <div class="content has-text-centered"> - <p> - <b-icon icon="upload" size="is-large"/> - </p> - <p>Drop your model here or click to upload</p> - </div> - </section> - </b-upload> - <span v-if="model.file" class="file-name">{{ model.file.name }}</span> + <b-field label="Number of parameters"> + <b-input type="number" min="0" max="10000000" placeholder="0" v-model="model.parameterCount"/> </b-field> - </div> + </b-tab-item> - <div class="column rightColumn"> - <layersEditor v-bind:layers="model.customLayers" v-on:update-layers="model.customLayers = $event"/> - </div> - </div> + <b-tab-item label="Tags"> + <h1 class="title">Tags</h1> + <tagEditor v-bind:modelTags="model.tags" v-on:update-tags="model.tags = $event"/> + </b-tab-item> - </div> + <b-tab-item label="Files"> + <h1 class="title">Files</h1> + <h2 class="subtitle has-text-danger">Uploading files will replace the current files</h2> + <div class="columns"> + <div class="column"> + <b-field label="Model"> + <b-upload v-model="model.file" drag-drop expanded> + <section class="section"> + <div class="content has-text-centered"> + <p> + <b-icon icon="upload" size="is-large"/> + </p> + <p>Drop your model here or click to upload</p> + </div> + </section> + </b-upload> + <span v-if="model.file" class="file-name">{{ model.file.name }}</span> + </b-field> + </div> + <div class="column rightColumn"> + <layersEditor v-bind:layers="model.customLayers" v-on:update-layers="model.customLayers = $event"/> + </div> + </div> + </b-tab-item> - <div class="box"> - <b-button type="is-danger" @click="removePrompt" expanded>Delete model</b-button> + <b-tab-item label="Danger"> + <b-button type="is-danger" @click="removePrompt" expanded>Delete model</b-button> + </b-tab-item> + </b-tabs> </div> <div class="box buttons"> <b-button tag="router-link" :to="{ name: 'Account' }">Cancel</b-button> - <b-button type="is-success" @click="openUploadModal">Save</b-button> + <b-button type="is-success" @click="openUpdateModal">Save</b-button> </div> - <b-modal :active.sync="isUploadModalActive" has-modal-card trap-focus aria-role="dialog" aria-modal> - <ModelUpload v-bind:model="model"/> + <b-modal :active.sync="isUpdateModalActive" has-modal-card trap-focus aria-role="dialog" aria-modal> + <ModelUpdate v-bind:model="model"/> </b-modal> </div> </template> @@ -76,7 +79,7 @@ import markdownEditor from '@/components/MarkdownEditor.vue' import tagEditor from '@/components/TagEditor.vue' import layersEditor from '@/components/LayersEditor.vue' -import ModelUpload from '@/components/ModelUpload.vue' +import ModelUpdate from '@/components/ModelUpdate.vue' import { api } from '@/api.js' export default { @@ -85,7 +88,7 @@ export default { markdownEditor, tagEditor, layersEditor, - ModelUpload + ModelUpdate }, props: { model: { @@ -97,7 +100,12 @@ export default { }, data () { return { - isUploadModalActive: false + isUpdateModalActive: false, + performanceTypes: [ + 'accuracy', + 'error rate', + 'MSE' + ] } }, mounted () { @@ -123,6 +131,9 @@ export default { hasIcon: true, onConfirm: () => api.removeModel(this.model.id) }) + }, + openUpdateModal () { + this.isUpdateModalActive = true } } }