@jseeio/jsee 0.2.3 → 0.2.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/README.md +53 -28
- package/dist/jsee.js +1 -1
- package/dist/jsee.runtime.js +1 -1
- package/dist/worker_bundle.js +1 -0
- package/jest-puppeteer.config.js +12 -0
- package/jest.config.js +7 -0
- package/load/index.html +40 -0
- package/main.js +323 -173
- package/package.json +12 -7
- package/src/app.js +22 -7
- package/src/utils.js +52 -0
- package/src/worker.js +65 -58
- package/templates/bulma-app.vue +76 -7
- package/templates/bulma-output.vue +1 -7
- package/test/code.html +25 -0
- package/test/codew.html +25 -0
- package/test/example-async-function.js +0 -0
- package/test/example-async-init.js +0 -0
- package/test/example-class.js +8 -0
- package/test/example-sum.js +3 -0
- package/test/minimal.html +14 -0
- package/test/minimal1.html +13 -0
- package/test/minimal2.html +15 -0
- package/test/minimal3.html +10 -0
- package/test/minimal4.html +22 -0
- package/test/string.html +26 -0
- package/test/stringw.html +29 -0
- package/test/sum.schema.json +17 -0
- package/test/sumw.schema.json +15 -0
- package/test.js +160 -0
- package/webpack.config.js +5 -1
- package/404.html +0 -22
- package/load.html +0 -31
|
@@ -0,0 +1 @@
|
|
|
1
|
+
(()=>{var __webpack_modules__={555:()=>{eval("function getModelFuncJS(schema, target, log=console.log) {\n switch (schema.model.type) {\n case 'class':\n log('Init class')\n const modelClass = new target()\n return (...a) => {\n return modelClass[this.schema.model.method || 'predict'](...a)\n }\n case 'async-init':\n log('Function with async init')\n return target().then((m) => {\n log('Async init resolved: ', m)\n this.modelFunc = m\n })\n default:\n log('Init function')\n return target\n }\n}\n\n\n//# sourceURL=webpack://@jseeio/jsee/./src/utils.js?")},736:(__unused_webpack_module,__unused_webpack___webpack_exports__,__webpack_require__)=>{"use strict";eval("/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0__ = __webpack_require__(555);\n/* harmony import */ var _utils_js__WEBPACK_IMPORTED_MODULE_0___default = /*#__PURE__*/__webpack_require__.n(_utils_js__WEBPACK_IMPORTED_MODULE_0__);\n\n\nconsole.log('!!!', _utils_js__WEBPACK_IMPORTED_MODULE_0__.getModelFuncJS)\n\nfunction log () {\n const args = Array.prototype.slice.call(arguments);\n args.unshift('[Worker]')\n console.log(args)\n postMessage({_status: 'log', _log: args})\n}\n\nfunction initTF (model) {\n throw new Error('Tensorflow in worker (not implemented)')\n}\n\nfunction initPython (model) {\n throw new Error('Python in worker (not implemented)')\n}\n\nfunction initJS (model) {\n this.container = model.container\n\n if (model.code) {\n log('Load code as a string')\n // https://github.com/altbdoor/blob-worker/blob/master/blobWorker.js\n importScripts(URL.createObjectURL(new Blob([model.code], { type: 'text/javascript' })))\n } else if (model.url) {\n log('Load script from URL:', model.url)\n importScripts(model.url)\n } else {\n log('No script provided')\n }\n\n this.modelFunc = (0,_utils_js__WEBPACK_IMPORTED_MODULE_0__.getModelFuncJS)(model, this[model.name], log)\n console.log('>>>', this.modelFunc)\n postMessage({_status: 'loaded'})\n}\n\nonmessage = function (e) {\n\n var data = e.data\n log('Received message of type:', typeof data)\n\n if ((typeof data === 'object') && ((data.url) || (data.code))) {\n /*\n INIT MESSAGE\n */\n let model = data\n log('Init...')\n\n switch (model.type) {\n case 'tf':\n initTF(model)\n break\n case 'py':\n initPython(model)\n break\n case 'function':\n case 'class':\n case 'async-init':\n case 'async-function':\n initJS.call(this, model)\n break\n }\n } else {\n /*\n :w\n CALL MESSAGE\n */\n var res\n if (typeof this.modelFunc === 'string') {\n // Python model:\n log('Calling Python model')\n /*\n const keys = Object.keys(data)\n for (let key of keys) {\n self[key] = data[key];\n }\n self.pyodide.runPythonAsync(this.model, () => {})\n .then((res) => {\n console.log('[Worker] Py results: ', typeof res, res)\n\t postMessage(res)\n })\n .catch((err) => {\n // self.postMessage({error : err.message});\n })\n */\n } else {\n // JavaScript model\n log('Calling JavaScript model')\n if (this.container === 'args') {\n log('Applying inputs as arguments')\n res = this.modelFunc.apply(null, data)\n } else {\n // JS object or array\n log('Applying inputs as object/array')\n res = this.modelFunc(data, log)\n }\n // Return promise value or just regular value\n // Promise.resolve handles both cases\n Promise.resolve(res).then(r => { postMessage(r) })\n }\n }\n}\n\n\n//# sourceURL=webpack://@jseeio/jsee/./src/worker.js?")}},__webpack_module_cache__={};function __webpack_require__(e){var n=__webpack_module_cache__[e];if(void 0!==n)return n.exports;var o=__webpack_module_cache__[e]={exports:{}};return __webpack_modules__[e](o,o.exports,__webpack_require__),o.exports}__webpack_require__.n=e=>{var n=e&&e.__esModule?()=>e.default:()=>e;return __webpack_require__.d(n,{a:n}),n},__webpack_require__.d=(e,n)=>{for(var o in n)__webpack_require__.o(n,o)&&!__webpack_require__.o(e,o)&&Object.defineProperty(e,o,{enumerable:!0,get:n[o]})},__webpack_require__.o=(e,n)=>Object.prototype.hasOwnProperty.call(e,n);var __webpack_exports__=__webpack_require__(736)})();
|
package/jest.config.js
ADDED
package/load/index.html
ADDED
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
<!DOCTYPE html>
|
|
2
|
+
<html>
|
|
3
|
+
<head>
|
|
4
|
+
<meta charset="utf-8" />
|
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0"/>
|
|
6
|
+
<title>JSEE Schema Loader</title>
|
|
7
|
+
<style>
|
|
8
|
+
body {
|
|
9
|
+
font-family: sans-serif;
|
|
10
|
+
}
|
|
11
|
+
#jsee-container {
|
|
12
|
+
max-width: 1100px;
|
|
13
|
+
margin: auto;
|
|
14
|
+
}
|
|
15
|
+
</style>
|
|
16
|
+
</head>
|
|
17
|
+
<body>
|
|
18
|
+
<div id="jsee-container"></div>
|
|
19
|
+
<script src="/dist/jsee.js" type="text/javascript"></script>
|
|
20
|
+
<script>window.JSEE || document.write('<script src="https://cdn.jsdelivr.net/npm/@jseeio/jsee@latest/dist/jsee.runtime.js">\x3C/script>')</script>
|
|
21
|
+
<script>window.JSEE || document.write('<script src="https://unpkg.com/@jseeio/jsee@latest/dist/jsee.runtime.js">\x3C/script>')</script>
|
|
22
|
+
<script>
|
|
23
|
+
var params = new URLSearchParams(window.location.search)
|
|
24
|
+
var schemaStr = params.get('s')
|
|
25
|
+
if (schemaStr) {
|
|
26
|
+
let schema
|
|
27
|
+
try {
|
|
28
|
+
schema = JSON.parse(schemaStr);
|
|
29
|
+
} catch (e) {
|
|
30
|
+
schema = schemaStr.includes('/') ? schemaStr : '/apps/' + schemaStr + '/schema.json'
|
|
31
|
+
}
|
|
32
|
+
console.log('[Loader] Schema:', schemaStr, schema)
|
|
33
|
+
const env = new JSEE({
|
|
34
|
+
container: document.getElementById('jsee-container'),
|
|
35
|
+
schema: schema
|
|
36
|
+
})
|
|
37
|
+
}
|
|
38
|
+
</script>
|
|
39
|
+
</body>
|
|
40
|
+
</html>
|
package/main.js
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import { createVueApp } from './src/app'
|
|
2
2
|
import Worker from './src/worker.js'
|
|
3
3
|
|
|
4
|
+
const utils = require('./src/utils')
|
|
5
|
+
|
|
4
6
|
const { Notyf } = require('notyf')
|
|
5
7
|
const notyf = new Notyf({
|
|
6
8
|
types: [
|
|
@@ -25,8 +27,11 @@ require('notyf/notyf.min.css')
|
|
|
25
27
|
const fetch = window['fetch']
|
|
26
28
|
const Blob = window['Blob']
|
|
27
29
|
|
|
30
|
+
let verbose = true
|
|
28
31
|
function log () {
|
|
29
|
-
|
|
32
|
+
if (verbose) {
|
|
33
|
+
console.log(`[JSEE v${VERSION}]`, ...arguments)
|
|
34
|
+
}
|
|
30
35
|
}
|
|
31
36
|
|
|
32
37
|
// const Worker = window['Worker']
|
|
@@ -42,6 +47,20 @@ function isObject (item) {
|
|
|
42
47
|
return (typeof item === 'object' && !Array.isArray(item) && item !== null)
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
function getName (code) {
|
|
51
|
+
switch (typeof code) {
|
|
52
|
+
case 'function':
|
|
53
|
+
return code.name
|
|
54
|
+
case 'string':
|
|
55
|
+
const words = code.split(' ')
|
|
56
|
+
const functionIndex = words.findIndex((word) => word == 'function')
|
|
57
|
+
const name = words[functionIndex + 1]
|
|
58
|
+
return name.includes('(') ? undefined : name
|
|
59
|
+
default:
|
|
60
|
+
return undefined
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
45
64
|
// Return input value
|
|
46
65
|
function getValue (input) {
|
|
47
66
|
if (input.type === 'group') {
|
|
@@ -55,19 +74,85 @@ function getValue (input) {
|
|
|
55
74
|
}
|
|
56
75
|
}
|
|
57
76
|
|
|
77
|
+
function getModelType (model) {
|
|
78
|
+
if (model.code && typeof model.code === 'string' && model.code.split(' ').map(v => v.trim()).includes('def')) {
|
|
79
|
+
return 'py'
|
|
80
|
+
} else if (model.url) {
|
|
81
|
+
return 'post'
|
|
82
|
+
}
|
|
83
|
+
return 'function'
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Nice trick to get a function parameters by Jack Allan
|
|
87
|
+
// From: https://stackoverflow.com/a/9924463/2998960
|
|
88
|
+
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg
|
|
89
|
+
const ARGUMENT_NAMES = /([^\s,]+)/g
|
|
90
|
+
function getParamNames (func) {
|
|
91
|
+
const fnStr = func.toString().replace(STRIP_COMMENTS, '')
|
|
92
|
+
let result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES)
|
|
93
|
+
if (result === null)
|
|
94
|
+
result = []
|
|
95
|
+
return result
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function getInputs (model) {
|
|
99
|
+
if (model.code) {
|
|
100
|
+
const params = getParamNames(model.code)
|
|
101
|
+
return params.map(p => ({
|
|
102
|
+
'name': p,
|
|
103
|
+
'type': 'string'
|
|
104
|
+
}))
|
|
105
|
+
}
|
|
106
|
+
return []
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
function getFunctionContainer (target) {
|
|
110
|
+
// Check if the number of parameters is > 1, then 'args'
|
|
111
|
+
}
|
|
112
|
+
|
|
58
113
|
export default class JSEE {
|
|
59
|
-
constructor (params) {
|
|
60
|
-
|
|
114
|
+
constructor (params, alt1, alt2) {
|
|
115
|
+
|
|
116
|
+
// Check if JSEE was initialized with args rather than with a params object
|
|
117
|
+
if (('model' in params) || (typeof params === 'string') || (typeof params === 'function') || !(typeof alt === 'undefined')) {
|
|
118
|
+
params = {
|
|
119
|
+
'schema': params,
|
|
120
|
+
'container': alt1,
|
|
121
|
+
'verbose': alt2
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Set global verbose flag
|
|
126
|
+
verbose = !(params.verbose === false)
|
|
127
|
+
|
|
128
|
+
// Previous naming
|
|
61
129
|
params.schema = params.schema || params.config
|
|
130
|
+
|
|
131
|
+
log('Initializing JSEE with parameters: ', params)
|
|
62
132
|
this.params = params
|
|
63
133
|
this.__version__ = VERSION
|
|
64
134
|
|
|
65
|
-
// Get schema then initialize a
|
|
66
|
-
|
|
67
|
-
|
|
135
|
+
// Get schema then initialize a new environment
|
|
136
|
+
switch (typeof params.schema) {
|
|
137
|
+
case 'object':
|
|
68
138
|
log('Received schema as object')
|
|
139
|
+
if (typeof params.schema.model === 'function') {
|
|
140
|
+
params.schema.model = {
|
|
141
|
+
code: params.schema.model
|
|
142
|
+
}
|
|
143
|
+
}
|
|
69
144
|
this.init(params.schema)
|
|
70
|
-
|
|
145
|
+
break
|
|
146
|
+
case 'function':
|
|
147
|
+
log('Received schema as function')
|
|
148
|
+
params.schema = {
|
|
149
|
+
model: {
|
|
150
|
+
code: params.schema,
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
this.init(params.schema)
|
|
154
|
+
break
|
|
155
|
+
case 'string':
|
|
71
156
|
log('Received schema as string')
|
|
72
157
|
this.schemaUrl = params.schema.indexOf('json') ? params.schema : params.schema + '.json'
|
|
73
158
|
fetch(this.schemaUrl)
|
|
@@ -79,7 +164,10 @@ export default class JSEE {
|
|
|
79
164
|
.catch((err) => {
|
|
80
165
|
console.error(err)
|
|
81
166
|
})
|
|
82
|
-
|
|
167
|
+
break
|
|
168
|
+
default:
|
|
169
|
+
log('No schema provided')
|
|
170
|
+
notyf.error('No schema provided')
|
|
83
171
|
}
|
|
84
172
|
}
|
|
85
173
|
|
|
@@ -87,206 +175,141 @@ export default class JSEE {
|
|
|
87
175
|
notyf.success(txt)
|
|
88
176
|
}
|
|
89
177
|
|
|
90
|
-
// Initialize model from schema
|
|
91
178
|
init (schema) {
|
|
92
|
-
|
|
179
|
+
this.loadCode(schema).then((code) => { // -> code
|
|
180
|
+
this.initSchema(schema, code) // -> this.schema
|
|
181
|
+
this.initVue() // -> this.app, this.data
|
|
182
|
+
this.initWorker() // -> this.worker
|
|
183
|
+
this.initRender() // -> this.renderFunc
|
|
184
|
+
this.initModel() // -> this.modelFunc (depends on this.worker)
|
|
185
|
+
})
|
|
186
|
+
}
|
|
93
187
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
188
|
+
loadCode (schema) {
|
|
189
|
+
const initPromise = new Promise((resolve, reject) => {
|
|
190
|
+
// Unwind this ball of possible cases
|
|
191
|
+
let url = schema.model.url
|
|
192
|
+
if (url && (url.includes('.js') || url.includes('.py'))) {
|
|
193
|
+
// Update model URL if needed
|
|
194
|
+
if (!url.includes('/') && this.schemaUrl && this.schemaUrl.includes('/')) {
|
|
195
|
+
url = window.location.protocol + '//' + window.location.host + this.schemaUrl.split('/').slice(0, -1).join('/') + '/' + url
|
|
196
|
+
log(`Changed the old model URL to ${url} (based on the schema URL)`)
|
|
197
|
+
}
|
|
198
|
+
fetch(url)
|
|
199
|
+
.then(res => res.text())
|
|
200
|
+
.then(res => {
|
|
201
|
+
log('Loaded code from:', url)
|
|
202
|
+
resolve(res)
|
|
203
|
+
})
|
|
204
|
+
} else if (!(typeof schema.model.code === 'undefined')) {
|
|
205
|
+
log('Code is: schema.model.code')
|
|
206
|
+
resolve(schema.model.code)
|
|
207
|
+
} else {
|
|
208
|
+
log('No code. Probably API...')
|
|
209
|
+
resolve(undefined)
|
|
210
|
+
}
|
|
211
|
+
})
|
|
212
|
+
|
|
213
|
+
return initPromise
|
|
214
|
+
}
|
|
99
215
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
216
|
+
initSchema (schema, code) {
|
|
217
|
+
log('Initializing schema')
|
|
218
|
+
|
|
219
|
+
// Check for empty model block
|
|
220
|
+
if (typeof schema.model === 'undefined') {
|
|
221
|
+
schema.model = {}
|
|
106
222
|
}
|
|
107
223
|
|
|
224
|
+
schema.model.code = code
|
|
225
|
+
|
|
226
|
+
// Check for super minimal config
|
|
108
227
|
// Check for worker flag
|
|
109
228
|
if (typeof schema.model.worker === 'undefined') {
|
|
110
229
|
schema.model.worker = true
|
|
111
230
|
}
|
|
112
231
|
|
|
113
232
|
// Check inputs
|
|
233
|
+
// Relies on model.code
|
|
234
|
+
// So run after possible fetching
|
|
114
235
|
if (typeof schema.inputs === 'undefined') {
|
|
115
|
-
schema.
|
|
236
|
+
schema.model.container = 'args'
|
|
237
|
+
schema.inputs = getInputs(schema.model)
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Relies on input check
|
|
241
|
+
// Set default input type
|
|
242
|
+
schema.inputs.forEach(input => {
|
|
243
|
+
if (typeof input.type === 'undefined') {
|
|
244
|
+
input.type = 'string'
|
|
245
|
+
}
|
|
246
|
+
})
|
|
247
|
+
|
|
248
|
+
// Infer model type
|
|
249
|
+
if (typeof schema.model.type === 'undefined') {
|
|
250
|
+
schema.model.type = getModelType(schema.model)
|
|
116
251
|
}
|
|
117
252
|
|
|
118
|
-
//
|
|
119
|
-
if (typeof schema.model.name === 'undefined')
|
|
120
|
-
|
|
121
|
-
if (schema.model.url) {
|
|
122
|
-
// 1. Get the name from the file name
|
|
253
|
+
// Update model name if absent
|
|
254
|
+
if (typeof schema.model.name === 'undefined'){
|
|
255
|
+
if ((schema.model.url) && (schema.model.url.includes('.js'))) {
|
|
123
256
|
schema.model.name = schema.model.url.split('/').pop().split('.')[0]
|
|
124
|
-
log('Use name from url: ', schema.model.name)
|
|
257
|
+
log('Use model name from url: ', schema.model.name)
|
|
125
258
|
} else if (schema.model.code) {
|
|
126
|
-
|
|
127
|
-
schema.model.name = schema.model.code.name
|
|
128
|
-
log('Use name from code: ', schema.model.name)
|
|
259
|
+
schema.model.name = getName(schema.model.code)
|
|
129
260
|
}
|
|
130
261
|
}
|
|
131
262
|
|
|
263
|
+
// At this point we have all code in model.code or api
|
|
132
264
|
this.schema = clone(schema)
|
|
265
|
+
}
|
|
133
266
|
|
|
134
|
-
|
|
135
|
-
|
|
267
|
+
initVue () {
|
|
268
|
+
log('Initializing VUE')
|
|
269
|
+
this.app = createVueApp(this, (container) => {
|
|
136
270
|
// Called when the app is mounted
|
|
137
271
|
// FYI "this" here refers to port object
|
|
138
272
|
this.outputsContainer = container.querySelector('#outputs')
|
|
139
273
|
this.inputsContainer = container.querySelector('#inputs')
|
|
140
274
|
this.modelContainer = container.querySelector('#model')
|
|
141
|
-
|
|
142
275
|
// Init overlay
|
|
143
276
|
this.overlay = new Overlay(this.inputsContainer ? this.inputsContainer : this.outputsContainer)
|
|
144
|
-
})
|
|
277
|
+
}, log)
|
|
145
278
|
this.data = this.app.$data
|
|
279
|
+
}
|
|
146
280
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
this.
|
|
152
|
-
let script = document.createElement('script')
|
|
153
|
-
script.src = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js'
|
|
154
|
-
script.onload = async () => {
|
|
155
|
-
this.pyodide = await loadPyodide({ indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/" });
|
|
156
|
-
notyf.success('Loaded: Python')
|
|
157
|
-
const resRaw = await fetch(this.schema.model.url)
|
|
158
|
-
const res = await resRaw.text()
|
|
159
|
-
this.pymodel = res
|
|
160
|
-
log('Loaded python code:', res)
|
|
161
|
-
// Check if micropip is used
|
|
162
|
-
if (res.includes('micropip')) {
|
|
163
|
-
await this.pyodide.loadPackage('micropip')
|
|
164
|
-
log('Loaded micropip')
|
|
165
|
-
}
|
|
166
|
-
// Import packages if defined
|
|
167
|
-
if ('packages' in this.schema.model) {
|
|
168
|
-
await this.pyodide.loadPackage(this.schema.model.packages)
|
|
169
|
-
log('Loaded packages from schema')
|
|
170
|
-
} else {
|
|
171
|
-
await this.pyodide.loadPackagesFromImports(res)
|
|
172
|
-
log('Loaded packages from Python code')
|
|
173
|
-
}
|
|
281
|
+
initWorker () {
|
|
282
|
+
if (this.schema.model.worker) {
|
|
283
|
+
log('Initializing Worker')
|
|
284
|
+
this.worker = new Worker()
|
|
285
|
+
this.worker.onmessage = (e) => {
|
|
174
286
|
this.overlay.hide()
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
.then(res => res.text())
|
|
185
|
-
.then(res => {
|
|
186
|
-
log('Loaded js code for worker')
|
|
187
|
-
this.schema.model.code = res
|
|
188
|
-
this.worker.postMessage(this.schema.model)
|
|
189
|
-
})
|
|
190
|
-
} else if (typeof this.schema.model.code !== 'undefined') {
|
|
191
|
-
this.worker.postMessage(this.schema.model)
|
|
192
|
-
} else {
|
|
193
|
-
notyf.error('No code provided')
|
|
194
|
-
}
|
|
195
|
-
|
|
196
|
-
this.worker.onmessage = (e) => {
|
|
197
|
-
this.overlay.hide()
|
|
198
|
-
const res = e.data
|
|
199
|
-
if ((typeof res === 'object') && (res._status)) {
|
|
200
|
-
switch (res._status) {
|
|
201
|
-
case 'loaded':
|
|
202
|
-
notyf.success('Loaded: JS model (in worker)')
|
|
203
|
-
break
|
|
204
|
-
case 'log':
|
|
205
|
-
log(...res._log)
|
|
206
|
-
break
|
|
207
|
-
}
|
|
208
|
-
} else {
|
|
209
|
-
log('Response from worker:', res)
|
|
210
|
-
this.output(res)
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
this.worker.onerror = (e) => {
|
|
214
|
-
this.overlay.hide()
|
|
215
|
-
notyf.error(e.message)
|
|
216
|
-
log('Error from worker:', e)
|
|
217
|
-
}
|
|
218
|
-
} else {
|
|
219
|
-
// Initialize model in main window
|
|
220
|
-
log('Init model in window')
|
|
221
|
-
let script = document.createElement('script')
|
|
222
|
-
script.src = this.schema.model.url
|
|
223
|
-
script.onload = () => {
|
|
224
|
-
notyf.success('Loaded: JS model')
|
|
225
|
-
this.overlay.hide()
|
|
226
|
-
log('Loaded JS model in main window')
|
|
227
|
-
|
|
228
|
-
// Initializing the model (same in worker)
|
|
229
|
-
if (this.schema.model.type === 'class') {
|
|
230
|
-
log('Init class')
|
|
231
|
-
const modelClass = new window[this.schema.model.name]()
|
|
232
|
-
this.modelFunc = (...a) => {
|
|
233
|
-
return modelClass[this.schema.model.method || 'predict'](...a)
|
|
234
|
-
}
|
|
235
|
-
} else if (this.schema.model.type === 'async-init') {
|
|
236
|
-
log('Init function with promise')
|
|
237
|
-
window[this.schema.model.name]().then((m) => {
|
|
238
|
-
log('Async init resolved: ', m)
|
|
239
|
-
this.modelFunc = m
|
|
240
|
-
})
|
|
241
|
-
} else {
|
|
242
|
-
log('Init function')
|
|
243
|
-
this.modelFunc = window[this.schema.model.name]
|
|
287
|
+
const res = e.data
|
|
288
|
+
if ((typeof res === 'object') && (res._status)) {
|
|
289
|
+
switch (res._status) {
|
|
290
|
+
case 'loaded':
|
|
291
|
+
notyf.success('Loaded: JS model (in worker)')
|
|
292
|
+
break
|
|
293
|
+
case 'log':
|
|
294
|
+
log(...res._log)
|
|
295
|
+
break
|
|
244
296
|
}
|
|
297
|
+
} else {
|
|
298
|
+
log('Response from worker:', res)
|
|
299
|
+
this.output(res)
|
|
245
300
|
}
|
|
246
|
-
document.head.appendChild(script)
|
|
247
301
|
}
|
|
248
|
-
|
|
249
|
-
// Initialize TF
|
|
250
|
-
let script = document.createElement('script')
|
|
251
|
-
script.src = 'dist/tf.min.js'
|
|
252
|
-
script.onload = () => {
|
|
253
|
-
log('Loaded TF.js')
|
|
302
|
+
this.worker.onerror = (e) => {
|
|
254
303
|
this.overlay.hide()
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
})
|
|
258
|
-
}
|
|
259
|
-
document.head.appendChild(script)
|
|
260
|
-
} else if (this.schema.model.type === 'get') {
|
|
261
|
-
this.overlay.hide()
|
|
262
|
-
this.modelFunc = (data) => {
|
|
263
|
-
log('Sending GET request to', this.schema.model.url)
|
|
264
|
-
const query = new window['URLSearchParams'](data).toString()
|
|
265
|
-
log('Generated query string:', query)
|
|
266
|
-
const resPromise = fetch(this.schema.model.url +'?' + query)
|
|
267
|
-
.then(response => response.json())
|
|
268
|
-
return resPromise
|
|
269
|
-
}
|
|
270
|
-
} else if (this.schema.model.type === 'post') {
|
|
271
|
-
this.overlay.hide()
|
|
272
|
-
this.modelFunc = (data) => {
|
|
273
|
-
log('Sending POST request to', this.schema.model.url)
|
|
274
|
-
const resPromise = fetch(this.schema.model.url, {
|
|
275
|
-
method: 'POST',
|
|
276
|
-
headers: {
|
|
277
|
-
'Accept': 'application/json',
|
|
278
|
-
'Content-Type': 'application/json'
|
|
279
|
-
},
|
|
280
|
-
body: JSON.stringify(data)
|
|
281
|
-
}).then(response => response.json())
|
|
282
|
-
return resPromise
|
|
304
|
+
notyf.error(e.message)
|
|
305
|
+
log('Error from worker:', e)
|
|
283
306
|
}
|
|
284
307
|
}
|
|
308
|
+
}
|
|
285
309
|
|
|
286
|
-
|
|
287
|
-
// -----------
|
|
310
|
+
initRender () {
|
|
288
311
|
if (this.schema.render && this.schema.render.url) {
|
|
289
|
-
log('
|
|
312
|
+
log('Initializing a render function')
|
|
290
313
|
let script = document.createElement('script')
|
|
291
314
|
script.src = this.schema.render.url
|
|
292
315
|
script.onload = () => {
|
|
@@ -315,6 +338,135 @@ export default class JSEE {
|
|
|
315
338
|
}
|
|
316
339
|
}
|
|
317
340
|
|
|
341
|
+
initModel () {
|
|
342
|
+
log('Initializing a model function')
|
|
343
|
+
switch (this.schema.model.type) {
|
|
344
|
+
case 'py':
|
|
345
|
+
this.initPython()
|
|
346
|
+
break
|
|
347
|
+
case 'tf':
|
|
348
|
+
this.initTF()
|
|
349
|
+
break
|
|
350
|
+
case 'function':
|
|
351
|
+
case 'class':
|
|
352
|
+
case 'async-init':
|
|
353
|
+
case 'async-function':
|
|
354
|
+
this.initJS()
|
|
355
|
+
break
|
|
356
|
+
case 'get':
|
|
357
|
+
case 'post':
|
|
358
|
+
this.initAPI()
|
|
359
|
+
break
|
|
360
|
+
default:
|
|
361
|
+
notyf.error('No type information')
|
|
362
|
+
break
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
initPython () {
|
|
367
|
+
// Add loading indicator
|
|
368
|
+
this.overlay.show()
|
|
369
|
+
let script = document.createElement('script')
|
|
370
|
+
script.src = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js'
|
|
371
|
+
script.onload = async () => {
|
|
372
|
+
this.pyodide = await loadPyodide({ indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/" });
|
|
373
|
+
notyf.success('Loaded: Python')
|
|
374
|
+
log('Loaded python code:', res)
|
|
375
|
+
// Check if micropip is used
|
|
376
|
+
if (res.includes('micropip')) {
|
|
377
|
+
await this.pyodide.loadPackage('micropip')
|
|
378
|
+
log('Loaded micropip')
|
|
379
|
+
}
|
|
380
|
+
// Import packages if defined
|
|
381
|
+
if ('packages' in this.schema.model) {
|
|
382
|
+
await this.pyodide.loadPackage(this.schema.model.packages)
|
|
383
|
+
log('Loaded packages from schema')
|
|
384
|
+
} else {
|
|
385
|
+
await this.pyodide.loadPackagesFromImports(res)
|
|
386
|
+
log('Loaded packages from Python code')
|
|
387
|
+
}
|
|
388
|
+
this.overlay.hide()
|
|
389
|
+
}
|
|
390
|
+
document.head.appendChild(script)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
initJS () {
|
|
394
|
+
// 1. String input <- loaded from url or code(string)
|
|
395
|
+
// 2. Target object (can be function, class or a function with async init <- code(object)
|
|
396
|
+
// 3. Model function
|
|
397
|
+
|
|
398
|
+
// We always start from 1 or 2
|
|
399
|
+
// For window execution we go: [1 ->] 2 -> 3
|
|
400
|
+
// For worker: [2 ->] 1 -> Worker
|
|
401
|
+
|
|
402
|
+
if (this.schema.model.worker) {
|
|
403
|
+
// Worker: Initialize worker with the model
|
|
404
|
+
// 2 -> 1
|
|
405
|
+
if (typeof this.schema.model.code === 'function') {
|
|
406
|
+
log('Convert code in schema to string for WebWorker')
|
|
407
|
+
this.schema.model.code = this.schema.model.code.toString()
|
|
408
|
+
}
|
|
409
|
+
// Wrap anonymous functions
|
|
410
|
+
if (!this.schema.model.name) {
|
|
411
|
+
this.schema.model.code = `function anon () { return (${this.schema.model.code})(...arguments) }`
|
|
412
|
+
this.schema.model.name = 'anon'
|
|
413
|
+
}
|
|
414
|
+
this.worker.postMessage(this.schema.model)
|
|
415
|
+
} else {
|
|
416
|
+
// Main: Initialize model in main window
|
|
417
|
+
|
|
418
|
+
// Target here represents raw JS object (e.g. class), not the final callable function
|
|
419
|
+
let target
|
|
420
|
+
if (typeof this.schema.model.code === 'string') {
|
|
421
|
+
// 1 -> 2
|
|
422
|
+
// Danger zone
|
|
423
|
+
if (this.schema.model.name) {
|
|
424
|
+
log('Evaluating code from string (has name)')
|
|
425
|
+
target = Function(
|
|
426
|
+
`${this.schema.model.code} ;return ${this.schema.model.name}`
|
|
427
|
+
)()
|
|
428
|
+
} else {
|
|
429
|
+
log('Evaluating code from string (no name)')
|
|
430
|
+
target = eval(`(${this.schema.model.code})`) // ( ͡° ͜ʖ ͡°) YEAHVAL
|
|
431
|
+
}
|
|
432
|
+
} else {
|
|
433
|
+
target = this.schema.model.code
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
// Need promise here in case of async init
|
|
437
|
+
Promise.resolve(utils.getModelFuncJS(this.schema.model, target, log))
|
|
438
|
+
.then(m => {
|
|
439
|
+
this.overlay.hide()
|
|
440
|
+
notyf.success('Loaded: JS model')
|
|
441
|
+
this.modelFunc = m
|
|
442
|
+
})
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
|
|
446
|
+
initAPI () {
|
|
447
|
+
this.overlay.hide()
|
|
448
|
+
if (this.schema.model.worker) {
|
|
449
|
+
// Worker:
|
|
450
|
+
this.worker.postMessage(this.schema.model)
|
|
451
|
+
} else {
|
|
452
|
+
// Main:
|
|
453
|
+
this.modelFunc = utils.getModelFuncAPI(model, log)
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
initTF () {
|
|
458
|
+
let script = document.createElement('script')
|
|
459
|
+
script.src = 'dist/tf.min.js'
|
|
460
|
+
script.onload = () => {
|
|
461
|
+
log('Loaded TF.js')
|
|
462
|
+
this.overlay.hide()
|
|
463
|
+
window['tf'].loadLayersModel(this.schema.model.url).then(res => {
|
|
464
|
+
log('Loaded Tensorflow model')
|
|
465
|
+
})
|
|
466
|
+
}
|
|
467
|
+
document.head.appendChild(script)
|
|
468
|
+
}
|
|
469
|
+
|
|
318
470
|
run () {
|
|
319
471
|
const schema = this.schema
|
|
320
472
|
const data = this.data
|
|
@@ -326,7 +478,7 @@ export default class JSEE {
|
|
|
326
478
|
log('Pass inputs as function arguments')
|
|
327
479
|
inputValues = data.inputs.map(input => getValue(input))
|
|
328
480
|
} else {
|
|
329
|
-
log('Pass inputs
|
|
481
|
+
log('Pass inputs as object')
|
|
330
482
|
inputValues = {}
|
|
331
483
|
data.inputs.forEach(input => {
|
|
332
484
|
if (input.name) {
|
|
@@ -346,7 +498,7 @@ export default class JSEE {
|
|
|
346
498
|
data.inputs.forEach(input => {
|
|
347
499
|
this.pyodide.globals.set(input.name, input.value);
|
|
348
500
|
})
|
|
349
|
-
this.pyodide.runPythonAsync(this.
|
|
501
|
+
this.pyodide.runPythonAsync(this.schema.model.code, () => {})
|
|
350
502
|
.then((res) => {
|
|
351
503
|
if (schema.outputs && schema.outputs.length) {
|
|
352
504
|
const resultObj = {}
|
|
@@ -385,8 +537,6 @@ export default class JSEE {
|
|
|
385
537
|
Promise.resolve(res).then(r => { this.output(r) })
|
|
386
538
|
}
|
|
387
539
|
break
|
|
388
|
-
case 'api':
|
|
389
|
-
break
|
|
390
540
|
}
|
|
391
541
|
}
|
|
392
542
|
|