@jseeio/jsee 0.2.2 → 0.2.6
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 +27 -0
- 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.html +8 -4
- package/main.js +287 -155
- 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 +58 -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/string.html +26 -0
- package/test/stringw.html +29 -0
- package/test/sum.schema.json +17 -0
- package/test/sumw.schema.json +17 -0
- package/test.js +121 -0
- package/webpack.config.js +5 -1
- package/dist/port.js +0 -1
- package/dist/port.runtime.js +0 -1
|
@@ -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.html
CHANGED
|
@@ -13,11 +13,15 @@
|
|
|
13
13
|
<script src="dist/jsee.js" type="text/javascript"></script>
|
|
14
14
|
<script>
|
|
15
15
|
var params = new URLSearchParams(window.location.search)
|
|
16
|
-
var
|
|
17
|
-
if (
|
|
18
|
-
|
|
19
|
-
|
|
16
|
+
var schemaStr = params.get('s')
|
|
17
|
+
if (schemaStr) {
|
|
18
|
+
let schema
|
|
19
|
+
try {
|
|
20
|
+
schema = JSON.parse(schemaStr);
|
|
21
|
+
} catch (e) {
|
|
22
|
+
schema = schemaStr.includes('/') ? schemaStr : '/apps/' + schemaStr + '/schema.json'
|
|
20
23
|
}
|
|
24
|
+
console.log('[Loader] Schema:', schemaStr, schema)
|
|
21
25
|
const env = new JSEE({
|
|
22
26
|
container: document.getElementById('jsee-container'),
|
|
23
27
|
schema: schema
|
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,12 @@ function isObject (item) {
|
|
|
42
47
|
return (typeof item === 'object' && !Array.isArray(item) && item !== null)
|
|
43
48
|
}
|
|
44
49
|
|
|
50
|
+
function getName (code) {
|
|
51
|
+
const words = code.split(' ')
|
|
52
|
+
const functionIndex = words.findIndex((word) => word == 'function')
|
|
53
|
+
return words[numberIndex + 1]
|
|
54
|
+
}
|
|
55
|
+
|
|
45
56
|
// Return input value
|
|
46
57
|
function getValue (input) {
|
|
47
58
|
if (input.type === 'group') {
|
|
@@ -55,14 +66,60 @@ function getValue (input) {
|
|
|
55
66
|
}
|
|
56
67
|
}
|
|
57
68
|
|
|
69
|
+
function getType (model) {
|
|
70
|
+
if (model.code && typeof model.code === 'string' && model.code.split(' ').map(v => v.trim()).includes('def')) {
|
|
71
|
+
return 'py'
|
|
72
|
+
} else if (model.url) {
|
|
73
|
+
return 'post'
|
|
74
|
+
}
|
|
75
|
+
return 'function'
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Nice trick to get a function parameters by Jack Allan
|
|
79
|
+
// From: https://stackoverflow.com/a/9924463/2998960
|
|
80
|
+
const STRIP_COMMENTS = /((\/\/.*$)|(\/\*[\s\S]*?\*\/))/mg
|
|
81
|
+
const ARGUMENT_NAMES = /([^\s,]+)/g
|
|
82
|
+
function getParamNames (func) {
|
|
83
|
+
const fnStr = func.toString().replace(STRIP_COMMENTS, '')
|
|
84
|
+
let result = fnStr.slice(fnStr.indexOf('(')+1, fnStr.indexOf(')')).match(ARGUMENT_NAMES)
|
|
85
|
+
if (result === null)
|
|
86
|
+
result = []
|
|
87
|
+
return result
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function getInputs (model) {
|
|
91
|
+
if (model.code) {
|
|
92
|
+
const params = getParamNames(model.code)
|
|
93
|
+
return params.map(p => ({
|
|
94
|
+
'name': p,
|
|
95
|
+
'type': 'string'
|
|
96
|
+
}))
|
|
97
|
+
}
|
|
98
|
+
return []
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
function getFunctionContainer (target) {
|
|
102
|
+
// Check if the number of parameters is > 1, then 'args'
|
|
103
|
+
}
|
|
104
|
+
|
|
58
105
|
export default class JSEE {
|
|
59
|
-
constructor (params) {
|
|
106
|
+
constructor (params, alt) {
|
|
107
|
+
// Check if JSEE was initialized with 2 args rather than 1 obj
|
|
108
|
+
if (('model' in params) || !(typeof alt === 'undefined')) {
|
|
109
|
+
params = {
|
|
110
|
+
'schema': params,
|
|
111
|
+
'container': alt
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
verbose = !(params.verbose === false)
|
|
116
|
+
|
|
60
117
|
log('Initializing JSEE with parameters: ', params)
|
|
61
118
|
params.schema = params.schema || params.config
|
|
62
119
|
this.params = params
|
|
63
120
|
this.__version__ = VERSION
|
|
64
121
|
|
|
65
|
-
// Get schema then initialize a
|
|
122
|
+
// Get schema then initialize a new environment
|
|
66
123
|
if (params.schema) {
|
|
67
124
|
if (typeof params.schema === 'object') {
|
|
68
125
|
log('Received schema as object')
|
|
@@ -87,191 +144,143 @@ export default class JSEE {
|
|
|
87
144
|
notyf.success(txt)
|
|
88
145
|
}
|
|
89
146
|
|
|
90
|
-
// Initialize model from schema
|
|
91
147
|
init (schema) {
|
|
92
|
-
|
|
148
|
+
this.loadCode(schema).then((code) => { // -> code
|
|
149
|
+
this.initSchema(schema, code) // -> this.schema
|
|
150
|
+
this.initVue() // -> this.app, this.data
|
|
151
|
+
this.initWorker() // -> this.worker
|
|
152
|
+
this.initRender() // -> this.renderFunc
|
|
153
|
+
this.initModel() // -> this.modelFunc (depends on this.worker)
|
|
154
|
+
})
|
|
155
|
+
}
|
|
93
156
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
157
|
+
loadCode (schema) {
|
|
158
|
+
const initPromise = new Promise((resolve, reject) => {
|
|
159
|
+
// Unwind this ball of possible cases
|
|
160
|
+
let url = schema.model.url
|
|
161
|
+
if (url && (url.includes('.js') || url.includes('.py'))) {
|
|
162
|
+
// Update model URL if needed
|
|
163
|
+
if (!url.includes('/') && this.schemaUrl && this.schemaUrl.includes('/')) {
|
|
164
|
+
url = window.location.protocol + '//' + window.location.host + this.schemaUrl.split('/').slice(0, -1).join('/') + '/' + url
|
|
165
|
+
log(`Changed the old model URL to ${url} (based on the schema URL)`)
|
|
166
|
+
}
|
|
167
|
+
fetch(url)
|
|
168
|
+
.then(res => res.text())
|
|
169
|
+
.then(res => {
|
|
170
|
+
log('Loaded code from:', url)
|
|
171
|
+
resolve(res)
|
|
172
|
+
})
|
|
173
|
+
} else if (typeof schema === 'function') {
|
|
174
|
+
log('Code is: schema')
|
|
175
|
+
resolve(schema)
|
|
176
|
+
} else if (typeof schema.model === 'function') {
|
|
177
|
+
log('Code is: schema.model')
|
|
178
|
+
resolve(schema.model)
|
|
179
|
+
} else if (!(typeof schema.model.code === 'undefined')) {
|
|
180
|
+
log('Code is: schema.model.code')
|
|
181
|
+
resolve(schema.model.code)
|
|
182
|
+
} else {
|
|
183
|
+
log('No code. Probably API...')
|
|
184
|
+
resolve(undefined)
|
|
185
|
+
}
|
|
186
|
+
})
|
|
187
|
+
|
|
188
|
+
return initPromise
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
initSchema (schema, code) {
|
|
192
|
+
log('Initializing schema')
|
|
99
193
|
|
|
100
|
-
//
|
|
101
|
-
if (
|
|
102
|
-
|
|
103
|
-
log('Schema URL:', this.schemaUrl)
|
|
104
|
-
schema.model.url = window.location.protocol + '//' + window.location.host + this.schemaUrl.split('/').slice(0, -1).join('/') + '/' + oldModelUrl
|
|
105
|
-
log('Changed the old model URL to absolute one:', oldModelUrl, schema.model.url)
|
|
194
|
+
// Check for empty model block
|
|
195
|
+
if (typeof schema.model === 'undefined') {
|
|
196
|
+
schema.model = {}
|
|
106
197
|
}
|
|
107
198
|
|
|
199
|
+
schema.model.code = code
|
|
200
|
+
|
|
201
|
+
// Check for super minimal config
|
|
108
202
|
// Check for worker flag
|
|
109
203
|
if (typeof schema.model.worker === 'undefined') {
|
|
110
204
|
schema.model.worker = true
|
|
111
205
|
}
|
|
112
206
|
|
|
113
207
|
// Check inputs
|
|
208
|
+
// Relies on model.code
|
|
209
|
+
// So run after possible fetching
|
|
114
210
|
if (typeof schema.inputs === 'undefined') {
|
|
115
|
-
schema.
|
|
211
|
+
schema.model.container = 'args'
|
|
212
|
+
schema.inputs = getInputs(schema.model)
|
|
116
213
|
}
|
|
117
214
|
|
|
118
|
-
//
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
if (
|
|
122
|
-
|
|
123
|
-
schema.model.name = schema.model.url.split('/').pop().split('.')[0]
|
|
124
|
-
log('Use name from url: ', schema.model.name)
|
|
125
|
-
} else if (schema.model.code) {
|
|
126
|
-
// 2. Get the name from the url
|
|
127
|
-
schema.model.name = schema.model.code.name
|
|
128
|
-
log('Use name from code: ', schema.model.name)
|
|
215
|
+
// Relies on input check
|
|
216
|
+
// Set default input type
|
|
217
|
+
schema.inputs.forEach(input => {
|
|
218
|
+
if (typeof input.type === 'undefined') {
|
|
219
|
+
input.type = 'string'
|
|
129
220
|
}
|
|
221
|
+
})
|
|
222
|
+
|
|
223
|
+
// Infer model type
|
|
224
|
+
if (typeof schema.model.type === 'undefined') {
|
|
225
|
+
schema.model.type = getType(schema.model)
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
// Update model name if absent
|
|
229
|
+
if ((typeof schema.model.name === 'undefined') && (schema.model.url) && (schema.model.url.includes('.js'))) {
|
|
230
|
+
schema.model.name = schema.model.url.split('/').pop().split('.')[0]
|
|
231
|
+
log('Use model name from url: ', schema.model.name)
|
|
130
232
|
}
|
|
131
233
|
|
|
234
|
+
// At this point we have all code in model.code or api
|
|
132
235
|
this.schema = clone(schema)
|
|
236
|
+
}
|
|
133
237
|
|
|
134
|
-
|
|
135
|
-
|
|
238
|
+
initVue () {
|
|
239
|
+
log('Initializing VUE')
|
|
240
|
+
this.app = createVueApp(this, (container) => {
|
|
136
241
|
// Called when the app is mounted
|
|
137
242
|
// FYI "this" here refers to port object
|
|
138
243
|
this.outputsContainer = container.querySelector('#outputs')
|
|
139
244
|
this.inputsContainer = container.querySelector('#inputs')
|
|
140
245
|
this.modelContainer = container.querySelector('#model')
|
|
141
|
-
|
|
142
246
|
// Init overlay
|
|
143
247
|
this.overlay = new Overlay(this.inputsContainer ? this.inputsContainer : this.outputsContainer)
|
|
144
|
-
})
|
|
248
|
+
}, log)
|
|
145
249
|
this.data = this.app.$data
|
|
250
|
+
}
|
|
146
251
|
|
|
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
|
-
}
|
|
252
|
+
initWorker () {
|
|
253
|
+
if (this.schema.model.worker) {
|
|
254
|
+
log('Initializing Worker')
|
|
255
|
+
this.worker = new Worker()
|
|
256
|
+
this.worker.onmessage = (e) => {
|
|
174
257
|
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]
|
|
258
|
+
const res = e.data
|
|
259
|
+
if ((typeof res === 'object') && (res._status)) {
|
|
260
|
+
switch (res._status) {
|
|
261
|
+
case 'loaded':
|
|
262
|
+
notyf.success('Loaded: JS model (in worker)')
|
|
263
|
+
break
|
|
264
|
+
case 'log':
|
|
265
|
+
log(...res._log)
|
|
266
|
+
break
|
|
244
267
|
}
|
|
268
|
+
} else {
|
|
269
|
+
log('Response from worker:', res)
|
|
270
|
+
this.output(res)
|
|
245
271
|
}
|
|
246
|
-
document.head.appendChild(script)
|
|
247
272
|
}
|
|
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')
|
|
273
|
+
this.worker.onerror = (e) => {
|
|
254
274
|
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
|
-
const query = new window['URLSearchParams'](data).toString()
|
|
264
|
-
log('Generated query string:', query)
|
|
265
|
-
const resPromise = fetch(this.schema.model.url +'?' + query)
|
|
266
|
-
.then(response => response.json())
|
|
267
|
-
return resPromise
|
|
275
|
+
notyf.error(e.message)
|
|
276
|
+
log('Error from worker:', e)
|
|
268
277
|
}
|
|
269
278
|
}
|
|
279
|
+
}
|
|
270
280
|
|
|
271
|
-
|
|
272
|
-
// -----------
|
|
281
|
+
initRender () {
|
|
273
282
|
if (this.schema.render && this.schema.render.url) {
|
|
274
|
-
log('
|
|
283
|
+
log('Initializing a render function')
|
|
275
284
|
let script = document.createElement('script')
|
|
276
285
|
script.src = this.schema.render.url
|
|
277
286
|
script.onload = () => {
|
|
@@ -300,6 +309,130 @@ export default class JSEE {
|
|
|
300
309
|
}
|
|
301
310
|
}
|
|
302
311
|
|
|
312
|
+
initModel () {
|
|
313
|
+
log('Initializing a model function')
|
|
314
|
+
switch (this.schema.model.type) {
|
|
315
|
+
case 'py':
|
|
316
|
+
this.initPython()
|
|
317
|
+
break
|
|
318
|
+
case 'tf':
|
|
319
|
+
this.initTF()
|
|
320
|
+
break
|
|
321
|
+
case 'function':
|
|
322
|
+
case 'class':
|
|
323
|
+
case 'async-init':
|
|
324
|
+
case 'async-function':
|
|
325
|
+
this.initJS()
|
|
326
|
+
break
|
|
327
|
+
case 'get':
|
|
328
|
+
case 'post':
|
|
329
|
+
this.initAPI()
|
|
330
|
+
break
|
|
331
|
+
default:
|
|
332
|
+
notyf.error('No type information')
|
|
333
|
+
break
|
|
334
|
+
}
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
initPython () {
|
|
338
|
+
// Add loading indicator
|
|
339
|
+
this.overlay.show()
|
|
340
|
+
let script = document.createElement('script')
|
|
341
|
+
script.src = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js'
|
|
342
|
+
script.onload = async () => {
|
|
343
|
+
this.pyodide = await loadPyodide({ indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/" });
|
|
344
|
+
notyf.success('Loaded: Python')
|
|
345
|
+
log('Loaded python code:', res)
|
|
346
|
+
// Check if micropip is used
|
|
347
|
+
if (res.includes('micropip')) {
|
|
348
|
+
await this.pyodide.loadPackage('micropip')
|
|
349
|
+
log('Loaded micropip')
|
|
350
|
+
}
|
|
351
|
+
// Import packages if defined
|
|
352
|
+
if ('packages' in this.schema.model) {
|
|
353
|
+
await this.pyodide.loadPackage(this.schema.model.packages)
|
|
354
|
+
log('Loaded packages from schema')
|
|
355
|
+
} else {
|
|
356
|
+
await this.pyodide.loadPackagesFromImports(res)
|
|
357
|
+
log('Loaded packages from Python code')
|
|
358
|
+
}
|
|
359
|
+
this.overlay.hide()
|
|
360
|
+
}
|
|
361
|
+
document.head.appendChild(script)
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
initJS () {
|
|
365
|
+
// 1. String input <- loaded from url or code(string)
|
|
366
|
+
// 2. Target object (can be function, class or a function with async init <- code(object)
|
|
367
|
+
// 3. Model function
|
|
368
|
+
|
|
369
|
+
// We always start from 1 or 2
|
|
370
|
+
// For window execution we go: [1 ->] 2 -> 3
|
|
371
|
+
// For worker: [2 ->] 1 -> Worker
|
|
372
|
+
|
|
373
|
+
if (this.schema.model.worker) {
|
|
374
|
+
// Worker: Initialize worker with the model
|
|
375
|
+
if (typeof this.schema.model.code !== 'string') {
|
|
376
|
+
// 2 -> 1
|
|
377
|
+
log('Convert code in schema to string for WebWorker')
|
|
378
|
+
this.schema.model.code = this.schema.model.code.toString()
|
|
379
|
+
}
|
|
380
|
+
this.worker.postMessage(this.schema.model)
|
|
381
|
+
} else {
|
|
382
|
+
// Main: Initialize model in main window
|
|
383
|
+
|
|
384
|
+
// Target here represents raw JS object (e.g. class), not the final callable function
|
|
385
|
+
let target
|
|
386
|
+
if (typeof this.schema.model.code === 'string') {
|
|
387
|
+
// 1 -> 2
|
|
388
|
+
// Danger zone
|
|
389
|
+
if (this.schema.model.name) {
|
|
390
|
+
log('Evaluating code from string (has name)')
|
|
391
|
+
target = Function(
|
|
392
|
+
`${this.schema.model.code} ;return ${this.schema.model.name}`
|
|
393
|
+
)()
|
|
394
|
+
} else {
|
|
395
|
+
log('Evaluating code from string (no name)')
|
|
396
|
+
target = eval(`(${this.schema.model.code})`) // ( ͡° ͜ʖ ͡°) YEAHVAL
|
|
397
|
+
}
|
|
398
|
+
} else {
|
|
399
|
+
target = this.schema.model.code
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
// Need promise here in case of async init
|
|
403
|
+
Promise.resolve(utils.getModelFuncJS(this.schema.model, target, log))
|
|
404
|
+
.then(m => {
|
|
405
|
+
this.overlay.hide()
|
|
406
|
+
notyf.success('Loaded: JS model')
|
|
407
|
+
this.modelFunc = m
|
|
408
|
+
})
|
|
409
|
+
}
|
|
410
|
+
}
|
|
411
|
+
|
|
412
|
+
initAPI () {
|
|
413
|
+
this.overlay.hide()
|
|
414
|
+
if (this.schema.model.worker) {
|
|
415
|
+
// Worker:
|
|
416
|
+
this.worker.postMessage(this.schema.model)
|
|
417
|
+
} else {
|
|
418
|
+
// Main:
|
|
419
|
+
this.modelFunc = utils.getModelFuncAPI(model, log)
|
|
420
|
+
}
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
initTF () {
|
|
424
|
+
let script = document.createElement('script')
|
|
425
|
+
script.src = 'dist/tf.min.js'
|
|
426
|
+
script.onload = () => {
|
|
427
|
+
log('Loaded TF.js')
|
|
428
|
+
this.overlay.hide()
|
|
429
|
+
window['tf'].loadLayersModel(this.schema.model.url).then(res => {
|
|
430
|
+
log('Loaded Tensorflow model')
|
|
431
|
+
})
|
|
432
|
+
}
|
|
433
|
+
document.head.appendChild(script)
|
|
434
|
+
}
|
|
435
|
+
|
|
303
436
|
run () {
|
|
304
437
|
const schema = this.schema
|
|
305
438
|
const data = this.data
|
|
@@ -311,7 +444,7 @@ export default class JSEE {
|
|
|
311
444
|
log('Pass inputs as function arguments')
|
|
312
445
|
inputValues = data.inputs.map(input => getValue(input))
|
|
313
446
|
} else {
|
|
314
|
-
log('Pass inputs
|
|
447
|
+
log('Pass inputs as object')
|
|
315
448
|
inputValues = {}
|
|
316
449
|
data.inputs.forEach(input => {
|
|
317
450
|
if (input.name) {
|
|
@@ -331,7 +464,7 @@ export default class JSEE {
|
|
|
331
464
|
data.inputs.forEach(input => {
|
|
332
465
|
this.pyodide.globals.set(input.name, input.value);
|
|
333
466
|
})
|
|
334
|
-
this.pyodide.runPythonAsync(this.
|
|
467
|
+
this.pyodide.runPythonAsync(this.schema.model.code, () => {})
|
|
335
468
|
.then((res) => {
|
|
336
469
|
if (schema.outputs && schema.outputs.length) {
|
|
337
470
|
const resultObj = {}
|
|
@@ -354,6 +487,7 @@ export default class JSEE {
|
|
|
354
487
|
case 'async-init':
|
|
355
488
|
case 'async-function':
|
|
356
489
|
case 'get':
|
|
490
|
+
case 'post':
|
|
357
491
|
if (this.schema.model.worker) {
|
|
358
492
|
this.worker.postMessage(inputValues)
|
|
359
493
|
} else {
|
|
@@ -369,8 +503,6 @@ export default class JSEE {
|
|
|
369
503
|
Promise.resolve(res).then(r => { this.output(r) })
|
|
370
504
|
}
|
|
371
505
|
break
|
|
372
|
-
case 'api':
|
|
373
|
-
break
|
|
374
506
|
}
|
|
375
507
|
}
|
|
376
508
|
|
package/package.json
CHANGED
|
@@ -1,15 +1,16 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@jseeio/jsee",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.6",
|
|
4
4
|
"description": "",
|
|
5
5
|
"main": "dist/jsee.js",
|
|
6
6
|
"private": false,
|
|
7
7
|
"scripts": {
|
|
8
|
-
"build-dev": "webpack --mode=development --progress",
|
|
9
|
-
"build": "webpack --mode=production --progress &&
|
|
10
|
-
"watch": "nodemon --watch . --ignore dist --ext vue,js,css,html --exec 'npm run build-dev'",
|
|
11
|
-
"prepublishOnly": "npm run build",
|
|
12
|
-
"test": "
|
|
8
|
+
"build-dev": "webpack --mode=development --progress --stats-children --env DEVELOPMENT",
|
|
9
|
+
"build": "webpack --mode=production --progress && webpack --mode=production --progress --env RUNTIME",
|
|
10
|
+
"watch": "nodemon --watch . --ignore dist --ext vue,js,css,html --exec 'npm run build-dev && npm test'",
|
|
11
|
+
"prepublishOnly": "npm run build && npm test",
|
|
12
|
+
"test": "jest test.js --detectOpenHandles",
|
|
13
|
+
"test-head": "HEADLESS=false npm test"
|
|
13
14
|
},
|
|
14
15
|
"author": "Anton Zemlyansky",
|
|
15
16
|
"license": "MIT",
|
|
@@ -31,10 +32,14 @@
|
|
|
31
32
|
"@babel/preset-env": "^7.16.5",
|
|
32
33
|
"babel-loader": "^8.2.3",
|
|
33
34
|
"css-loader": "^6.5.1",
|
|
35
|
+
"http-server": "^14.0.0",
|
|
36
|
+
"jest": "^27.4.5",
|
|
37
|
+
"jest-puppeteer": "^6.0.3",
|
|
34
38
|
"node-sass": "^7.0.0",
|
|
35
39
|
"nodemon": "^2.0.15",
|
|
36
|
-
"puppeteer": "^1.
|
|
40
|
+
"puppeteer": "^1.20.0",
|
|
37
41
|
"sass-loader": "^12.4.0",
|
|
42
|
+
"source-map-loader": "^3.0.0",
|
|
38
43
|
"style-loader": "^3.3.1",
|
|
39
44
|
"tape": "^4.9.1",
|
|
40
45
|
"terser-webpack-plugin": "^5.3.0",
|