@jseeio/jsee 0.2.7 → 0.3.0

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/main.js DELETED
@@ -1,620 +0,0 @@
1
- import { createVueApp } from './src/app'
2
- import Worker from './src/worker.js'
3
-
4
- const utils = require('./src/utils')
5
-
6
- const { Notyf } = require('notyf')
7
- const notyf = new Notyf({
8
- types: [
9
- {
10
- type: 'success',
11
- background: '#00d1b2',
12
- },
13
- {
14
- type: 'error',
15
- background: '#f14668',
16
- duration: 2000,
17
- dismissible: true
18
- }
19
- ]
20
- })
21
-
22
- // const createVueApp = require('./src/app')
23
- const Overlay = require('./src/overlay')
24
-
25
- require('notyf/notyf.min.css')
26
-
27
- const fetch = window['fetch']
28
- const Blob = window['Blob']
29
-
30
- let verbose = true
31
- function log () {
32
- if (verbose) {
33
- console.log(`[JSEE v${VERSION}]`, ...arguments)
34
- }
35
- }
36
-
37
- // const Worker = window['Worker']
38
-
39
- // Deep clone a simple object
40
- function clone (obj) {
41
- // return JSON.parse(JSON.stringify(obj))
42
- return Object.assign({}, obj)
43
- }
44
-
45
- // https://stackoverflow.com/questions/8511281/check-if-a-value-is-an-object-in-javascript
46
- function isObject (item) {
47
- return (typeof item === 'object' && !Array.isArray(item) && item !== null)
48
- }
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
-
64
- // Return input value
65
- function getValue (input) {
66
- if (input.type === 'group') {
67
- const value = {}
68
- input.elements.forEach(el => {
69
- value[el.name] = getValue(el)
70
- })
71
- return value
72
- } else {
73
- return input.value
74
- }
75
- }
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
-
113
- export default class JSEE {
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
129
- params.schema = params.schema || params.config
130
-
131
- log('Initializing JSEE with parameters: ', params)
132
- this.params = params
133
- this.__version__ = VERSION
134
-
135
- // Get schema then initialize a new environment
136
- switch (typeof params.schema) {
137
- case 'object':
138
- log('Received schema as object')
139
- if (typeof params.schema.model === 'function') {
140
- params.schema.model = {
141
- code: params.schema.model
142
- }
143
- }
144
- this.init(params.schema)
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':
156
- log('Received schema as string')
157
- this.schemaUrl = params.schema.indexOf('json') ? params.schema : params.schema + '.json'
158
- fetch(this.schemaUrl)
159
- .then(res => res.json())
160
- .then(res => {
161
- log('Loaded schema from url')
162
- this.init(res)
163
- })
164
- .catch((err) => {
165
- console.error(err)
166
- })
167
- break
168
- default:
169
- log('No schema provided')
170
- notyf.error('No schema provided')
171
- }
172
- }
173
-
174
- notify (txt) {
175
- notyf.success(txt)
176
- }
177
-
178
- init (schema) {
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
- }
187
-
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
- }
215
-
216
- initSchema (schema, code) {
217
- log('Initializing schema')
218
-
219
- // Check for empty model block
220
- if (typeof schema.model === 'undefined') {
221
- schema.model = {}
222
- }
223
-
224
- schema.model.code = code
225
-
226
- // Check for super minimal config
227
- // Check for worker flag
228
- if (typeof schema.model.worker === 'undefined') {
229
- schema.model.worker = true
230
- }
231
-
232
- // Check inputs
233
- // Relies on model.code
234
- // So run after possible fetching
235
- if (typeof schema.inputs === 'undefined') {
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)
251
- }
252
-
253
- // Update model name if absent
254
- if (typeof schema.model.name === 'undefined'){
255
- if ((schema.model.url) && (schema.model.url.includes('.js'))) {
256
- schema.model.name = schema.model.url.split('/').pop().split('.')[0]
257
- log('Use model name from url: ', schema.model.name)
258
- } else if (schema.model.code) {
259
- schema.model.name = getName(schema.model.code)
260
- }
261
- }
262
-
263
- // At this point we have all code in model.code or api
264
- this.schema = clone(schema)
265
- }
266
-
267
- initVue () {
268
- log('Initializing VUE')
269
- this.app = createVueApp(this, (container) => {
270
- // Called when the app is mounted
271
- // FYI "this" here refers to port object
272
- this.outputsContainer = container.querySelector('#outputs')
273
- this.inputsContainer = container.querySelector('#inputs')
274
- this.modelContainer = container.querySelector('#model')
275
- // Init overlay
276
- this.overlay = new Overlay(this.inputsContainer ? this.inputsContainer : this.outputsContainer)
277
- }, log)
278
- this.data = this.app.$data
279
- }
280
-
281
- initWorker () {
282
- if (this.schema.model.worker) {
283
- log('Initializing Worker')
284
- this.worker = new Worker()
285
- this.worker.onmessage = (e) => {
286
- this.overlay.hide()
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
296
- }
297
- } else {
298
- log('Response from worker:', res)
299
- this.output(res)
300
- }
301
- }
302
- this.worker.onerror = (e) => {
303
- this.overlay.hide()
304
- notyf.error(e.message)
305
- log('Error from worker:', e)
306
- }
307
- }
308
- }
309
-
310
- initRender () {
311
- if (this.schema.render && this.schema.render.url) {
312
- log('Initializing a render function')
313
- let script = document.createElement('script')
314
- script.src = this.schema.render.url
315
- script.onload = () => {
316
- notyf.success('Loaded: JS render')
317
- log('Loaded JS render')
318
-
319
- // Initializing the render (same in worker)
320
- if (this.schema.render.type === 'class') {
321
- log('Init render as class')
322
- const renderClass = new window[this.schema.render.name]()
323
- this.renderFunc = (...a) => {
324
- return renderClass[this.schema.render.method || 'render'](...a)
325
- }
326
- } else if (this.schema.render.type === 'async-init') {
327
- log('Init render function with promise')
328
- window[this.schema.render.name]().then((m) => {
329
- log('Async rebder init resolved: ', m)
330
- this.renderFunc = m
331
- })
332
- } else {
333
- log('Init render as function')
334
- this.renderFunc = window[this.schema.render.name]
335
- }
336
- }
337
- document.head.appendChild(script)
338
- }
339
- }
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
-
470
- run () {
471
- const schema = this.schema
472
- const data = this.data
473
- log('Running the model...')
474
- // Collect input values
475
- let inputValues
476
-
477
- if (schema.model && schema.model.container && schema.model.container === 'args') {
478
- log('Pass inputs as function arguments')
479
- inputValues = data.inputs.map(input => getValue(input))
480
- } else {
481
- log('Pass inputs as object')
482
- inputValues = {}
483
- data.inputs.forEach(input => {
484
- if (input.name) {
485
- inputValues[input.name] = getValue(input)
486
- }
487
- })
488
- }
489
- log('Input values:', inputValues)
490
- // We have all input values here, pass them to worker, window.modelFunc or tf
491
- if (!schema.model.autorun) {
492
- this.overlay.show()
493
- }
494
- switch (schema.model.type) {
495
- case 'tf':
496
- break
497
- case 'py':
498
- data.inputs.forEach(input => {
499
- this.pyodide.globals.set(input.name, input.value);
500
- })
501
- this.pyodide.runPythonAsync(this.schema.model.code, () => {})
502
- .then((res) => {
503
- if (schema.outputs && schema.outputs.length) {
504
- const resultObj = {}
505
- schema.outputs.forEach(output => {
506
- resultObj[output.name] = this.pyodide.globals.get(output.name).toJs()
507
- })
508
- this.output(resultObj)
509
- } else {
510
- this.output(res)
511
- }
512
- })
513
- .catch((err) => {
514
- log(err)
515
- window['M'].toast({html: 'Error in code'})
516
- })
517
- break
518
-
519
- case 'class':
520
- case 'function':
521
- case 'async-init':
522
- case 'async-function':
523
- case 'get':
524
- case 'post':
525
- if (this.schema.model.worker) {
526
- this.worker.postMessage(inputValues)
527
- } else {
528
- // Run in main window
529
- var res
530
- if (this.schema.model.container === 'args') {
531
- res = this.modelFunc.apply(null, inputValues)
532
- } else {
533
- log('Applying inputs as object')
534
- res = this.modelFunc(inputValues)
535
- }
536
- log('modelFunc results:', res)
537
- Promise.resolve(res).then(r => { this.output(r) })
538
- }
539
- break
540
- }
541
- }
542
-
543
- output (res) {
544
- // TODO: Think about all edge cases
545
- // * No output field, but reactivity
546
- this.overlay.hide()
547
-
548
- if (typeof res === 'undefined') {
549
- return
550
- }
551
-
552
- log('Got output results of type:', typeof res)
553
-
554
- // Process results (res)
555
- const inputNames = this.schema.inputs.map(i => i.name)
556
- if (isObject(res) && Object.keys(res).every(key => inputNames.includes(key))) {
557
- // Update inputs from results
558
- log('Updating inputs:', Object.keys(res))
559
- this.data.inputs.forEach((input, i) => {
560
- if (input.name && (typeof res[input.name] !== 'undefined')) {
561
- log('Updating input: ', input.name, 'with data:', res[input.name])
562
- const r = res[input.name]
563
- if (typeof r === 'object') {
564
- Object.keys(r).forEach(k => {
565
- input[k] = r[k]
566
- })
567
- } else {
568
- input.value = r
569
- }
570
- }
571
- })
572
- } else if (this.renderFunc) {
573
- // Pass results to a custom render function
574
- log('Calling a render function...')
575
- this.renderFunc(res)
576
- } else if (Array.isArray(res) && res.length) {
577
- // Result is array
578
- if (this.data.outputs && this.data.outputs.length) {
579
- // We have outputs defined
580
- if (this.data.outputs.length === res.length) {
581
- // Same length
582
- this.data.outputs.forEach((output, i) => {
583
- output.value = res[i]
584
- })
585
- } else {
586
- // Different length
587
- this.data.outputs[0].value = res
588
- }
589
- } else {
590
- // Outputs are not defined
591
- this.data.outputs = [{
592
- 'type': 'array',
593
- 'value': res
594
- }]
595
- }
596
- } else if (typeof res === 'object') {
597
- if (this.data.outputs && this.data.outputs.length) {
598
- this.data.outputs.forEach((output, i) => {
599
- if (output.name && (typeof res[output.name] !== 'undefined')) {
600
- log('Updating output: ', output.name)
601
- output.value = res[output.name]
602
- }
603
- })
604
- } else {
605
- this.data.outputs = [{
606
- 'type': 'object',
607
- 'value': res
608
- }]
609
- }
610
- } else if (this.schema.outputs && this.schema.outputs.length === 1) {
611
- // One output value passed as raw js object
612
- this.data.outputs[0].value = res
613
- } else {
614
- this.data.outputs = [{
615
- 'type': typeof res,
616
- 'value': res
617
- }]
618
- }
619
- }
620
- }
File without changes
File without changes