@jseeio/jsee 0.2.8 → 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,621 +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).filter(p => !['(', ')', '#', '{', '}'].some(c => p.includes(c)))
101
- log('Trying to infer inputs from params:', params)
102
- return params.map(p => ({
103
- 'name': p,
104
- 'type': 'string'
105
- }))
106
- }
107
- return []
108
- }
109
-
110
- function getFunctionContainer (target) {
111
- // Check if the number of parameters is > 1, then 'args'
112
- }
113
-
114
- export default class JSEE {
115
- constructor (params, alt1, alt2) {
116
-
117
- // Check if JSEE was initialized with args rather than with a params object
118
- if (('model' in params) || (typeof params === 'string') || (typeof params === 'function') || !(typeof alt === 'undefined')) {
119
- params = {
120
- 'schema': params,
121
- 'container': alt1,
122
- 'verbose': alt2
123
- }
124
- }
125
-
126
- // Set global verbose flag
127
- verbose = !(params.verbose === false)
128
-
129
- // Previous naming
130
- params.schema = params.schema || params.config
131
-
132
- log('Initializing JSEE with parameters: ', params)
133
- this.params = params
134
- this.__version__ = VERSION
135
-
136
- // Get schema then initialize a new environment
137
- switch (typeof params.schema) {
138
- case 'object':
139
- log('Received schema as object')
140
- if (typeof params.schema.model === 'function') {
141
- params.schema.model = {
142
- code: params.schema.model
143
- }
144
- }
145
- this.init(params.schema)
146
- break
147
- case 'function':
148
- log('Received schema as function')
149
- params.schema = {
150
- model: {
151
- code: params.schema,
152
- }
153
- }
154
- this.init(params.schema)
155
- break
156
- case 'string':
157
- log('Received schema as string')
158
- this.schemaUrl = params.schema.indexOf('json') ? params.schema : params.schema + '.json'
159
- fetch(this.schemaUrl)
160
- .then(res => res.json())
161
- .then(res => {
162
- log('Loaded schema from url')
163
- this.init(res)
164
- })
165
- .catch((err) => {
166
- console.error(err)
167
- })
168
- break
169
- default:
170
- log('No schema provided')
171
- notyf.error('No schema provided')
172
- }
173
- }
174
-
175
- notify (txt) {
176
- notyf.success(txt)
177
- }
178
-
179
- init (schema) {
180
- this.loadCode(schema).then((code) => { // -> code
181
- this.initSchema(schema, code) // -> this.schema
182
- this.initVue() // -> this.app, this.data
183
- this.initWorker() // -> this.worker
184
- this.initRender() // -> this.renderFunc
185
- this.initModel() // -> this.modelFunc (depends on this.worker)
186
- })
187
- }
188
-
189
- loadCode (schema) {
190
- const initPromise = new Promise((resolve, reject) => {
191
- // Unwind this ball of possible cases
192
- let url = schema.model.url
193
- if (url && (url.includes('.js') || url.includes('.py'))) {
194
- // Update model URL if needed
195
- if (!url.includes('/') && this.schemaUrl && this.schemaUrl.includes('/')) {
196
- url = window.location.protocol + '//' + window.location.host + this.schemaUrl.split('/').slice(0, -1).join('/') + '/' + url
197
- log(`Changed the old model URL to ${url} (based on the schema URL)`)
198
- }
199
- fetch(url)
200
- .then(res => res.text())
201
- .then(res => {
202
- log('Loaded code from:', url)
203
- resolve(res)
204
- })
205
- } else if (!(typeof schema.model.code === 'undefined')) {
206
- log('Code is: schema.model.code')
207
- resolve(schema.model.code)
208
- } else {
209
- log('No code. Probably API...')
210
- resolve(undefined)
211
- }
212
- })
213
-
214
- return initPromise
215
- }
216
-
217
- initSchema (schema, code) {
218
- log('Initializing schema')
219
-
220
- // Check for empty model block
221
- if (typeof schema.model === 'undefined') {
222
- schema.model = {}
223
- }
224
-
225
- schema.model.code = code
226
-
227
- // Check for super minimal config
228
- // Check for worker flag
229
- if (typeof schema.model.worker === 'undefined') {
230
- schema.model.worker = true
231
- }
232
-
233
- // Check inputs
234
- // Relies on model.code
235
- // So run after possible fetching
236
- if (typeof schema.inputs === 'undefined') {
237
- schema.model.container = 'args'
238
- schema.inputs = getInputs(schema.model)
239
- }
240
-
241
- // Relies on input check
242
- // Set default input type
243
- schema.inputs.forEach(input => {
244
- if (typeof input.type === 'undefined') {
245
- input.type = 'string'
246
- }
247
- })
248
-
249
- // Infer model type
250
- if (typeof schema.model.type === 'undefined') {
251
- schema.model.type = getModelType(schema.model)
252
- }
253
-
254
- // Update model name if absent
255
- if (typeof schema.model.name === 'undefined'){
256
- if ((schema.model.url) && (schema.model.url.includes('.js'))) {
257
- schema.model.name = schema.model.url.split('/').pop().split('.')[0]
258
- log('Use model name from url: ', schema.model.name)
259
- } else if (schema.model.code) {
260
- schema.model.name = getName(schema.model.code)
261
- }
262
- }
263
-
264
- // At this point we have all code in model.code or api
265
- this.schema = clone(schema)
266
- }
267
-
268
- initVue () {
269
- log('Initializing VUE')
270
- this.app = createVueApp(this, (container) => {
271
- // Called when the app is mounted
272
- // FYI "this" here refers to port object
273
- this.outputsContainer = container.querySelector('#outputs')
274
- this.inputsContainer = container.querySelector('#inputs')
275
- this.modelContainer = container.querySelector('#model')
276
- // Init overlay
277
- this.overlay = new Overlay(this.inputsContainer ? this.inputsContainer : this.outputsContainer)
278
- }, log)
279
- this.data = this.app.$data
280
- }
281
-
282
- initWorker () {
283
- if (this.schema.model.worker) {
284
- log('Initializing Worker')
285
- this.worker = new Worker()
286
- this.worker.onmessage = (e) => {
287
- this.overlay.hide()
288
- const res = e.data
289
- if ((typeof res === 'object') && (res._status)) {
290
- switch (res._status) {
291
- case 'loaded':
292
- notyf.success('Loaded: JS model (in worker)')
293
- break
294
- case 'log':
295
- log(...res._log)
296
- break
297
- }
298
- } else {
299
- log('Response from worker:', res)
300
- this.output(res)
301
- }
302
- }
303
- this.worker.onerror = (e) => {
304
- this.overlay.hide()
305
- notyf.error(e.message)
306
- log('Error from worker:', e)
307
- }
308
- }
309
- }
310
-
311
- initRender () {
312
- if (this.schema.render && this.schema.render.url) {
313
- log('Initializing a render function')
314
- let script = document.createElement('script')
315
- script.src = this.schema.render.url
316
- script.onload = () => {
317
- notyf.success('Loaded: JS render')
318
- log('Loaded JS render')
319
-
320
- // Initializing the render (same in worker)
321
- if (this.schema.render.type === 'class') {
322
- log('Init render as class')
323
- const renderClass = new window[this.schema.render.name]()
324
- this.renderFunc = (...a) => {
325
- return renderClass[this.schema.render.method || 'render'](...a)
326
- }
327
- } else if (this.schema.render.type === 'async-init') {
328
- log('Init render function with promise')
329
- window[this.schema.render.name]().then((m) => {
330
- log('Async rebder init resolved: ', m)
331
- this.renderFunc = m
332
- })
333
- } else {
334
- log('Init render as function')
335
- this.renderFunc = window[this.schema.render.name]
336
- }
337
- }
338
- document.head.appendChild(script)
339
- }
340
- }
341
-
342
- initModel () {
343
- log('Initializing a model function')
344
- switch (this.schema.model.type) {
345
- case 'py':
346
- this.initPython()
347
- break
348
- case 'tf':
349
- this.initTF()
350
- break
351
- case 'function':
352
- case 'class':
353
- case 'async-init':
354
- case 'async-function':
355
- this.initJS()
356
- break
357
- case 'get':
358
- case 'post':
359
- this.initAPI()
360
- break
361
- default:
362
- notyf.error('No type information')
363
- break
364
- }
365
- }
366
-
367
- initPython () {
368
- // Add loading indicator
369
- this.overlay.show()
370
- let script = document.createElement('script')
371
- script.src = 'https://cdn.jsdelivr.net/pyodide/v0.18.1/full/pyodide.js'
372
- script.onload = async () => {
373
- this.pyodide = await loadPyodide({ indexURL : "https://cdn.jsdelivr.net/pyodide/v0.18.1/full/" });
374
- notyf.success('Loaded: Python')
375
- log('Loaded python code:', res)
376
- // Check if micropip is used
377
- if (res.includes('micropip')) {
378
- await this.pyodide.loadPackage('micropip')
379
- log('Loaded micropip')
380
- }
381
- // Import packages if defined
382
- if ('packages' in this.schema.model) {
383
- await this.pyodide.loadPackage(this.schema.model.packages)
384
- log('Loaded packages from schema')
385
- } else {
386
- await this.pyodide.loadPackagesFromImports(res)
387
- log('Loaded packages from Python code')
388
- }
389
- this.overlay.hide()
390
- }
391
- document.head.appendChild(script)
392
- }
393
-
394
- initJS () {
395
- // 1. String input <- loaded from url or code(string)
396
- // 2. Target object (can be function, class or a function with async init <- code(object)
397
- // 3. Model function
398
-
399
- // We always start from 1 or 2
400
- // For window execution we go: [1 ->] 2 -> 3
401
- // For worker: [2 ->] 1 -> Worker
402
-
403
- if (this.schema.model.worker) {
404
- // Worker: Initialize worker with the model
405
- // 2 -> 1
406
- if (typeof this.schema.model.code === 'function') {
407
- log('Convert code in schema to string for WebWorker')
408
- this.schema.model.code = this.schema.model.code.toString()
409
- }
410
- // Wrap anonymous functions
411
- if (!this.schema.model.name) {
412
- this.schema.model.code = `function anon () { return (${this.schema.model.code})(...arguments) }`
413
- this.schema.model.name = 'anon'
414
- }
415
- this.worker.postMessage(this.schema.model)
416
- } else {
417
- // Main: Initialize model in main window
418
-
419
- // Target here represents raw JS object (e.g. class), not the final callable function
420
- let target
421
- if (typeof this.schema.model.code === 'string') {
422
- // 1 -> 2
423
- // Danger zone
424
- if (this.schema.model.name) {
425
- log('Evaluating code from string (has name)')
426
- target = Function(
427
- `${this.schema.model.code} ;return ${this.schema.model.name}`
428
- )()
429
- } else {
430
- log('Evaluating code from string (no name)')
431
- target = eval(`(${this.schema.model.code})`) // ( ͡° ͜ʖ ͡°) YEAHVAL
432
- }
433
- } else {
434
- target = this.schema.model.code
435
- }
436
-
437
- // Need promise here in case of async init
438
- Promise.resolve(utils.getModelFuncJS(this.schema.model, target, log))
439
- .then(m => {
440
- this.overlay.hide()
441
- notyf.success('Loaded: JS model')
442
- this.modelFunc = m
443
- })
444
- }
445
- }
446
-
447
- initAPI () {
448
- this.overlay.hide()
449
- if (this.schema.model.worker) {
450
- // Worker:
451
- this.worker.postMessage(this.schema.model)
452
- } else {
453
- // Main:
454
- this.modelFunc = utils.getModelFuncAPI(model, log)
455
- }
456
- }
457
-
458
- initTF () {
459
- let script = document.createElement('script')
460
- script.src = 'dist/tf.min.js'
461
- script.onload = () => {
462
- log('Loaded TF.js')
463
- this.overlay.hide()
464
- window['tf'].loadLayersModel(this.schema.model.url).then(res => {
465
- log('Loaded Tensorflow model')
466
- })
467
- }
468
- document.head.appendChild(script)
469
- }
470
-
471
- run () {
472
- const schema = this.schema
473
- const data = this.data
474
- log('Running the model...')
475
- // Collect input values
476
- let inputValues
477
-
478
- if (schema.model && schema.model.container && schema.model.container === 'args') {
479
- log('Pass inputs as function arguments')
480
- inputValues = data.inputs.map(input => getValue(input))
481
- } else {
482
- log('Pass inputs as object')
483
- inputValues = {}
484
- data.inputs.forEach(input => {
485
- if (input.name) {
486
- inputValues[input.name] = getValue(input)
487
- }
488
- })
489
- }
490
- log('Input values:', inputValues)
491
- // We have all input values here, pass them to worker, window.modelFunc or tf
492
- if (!schema.model.autorun) {
493
- this.overlay.show()
494
- }
495
- switch (schema.model.type) {
496
- case 'tf':
497
- break
498
- case 'py':
499
- data.inputs.forEach(input => {
500
- this.pyodide.globals.set(input.name, input.value);
501
- })
502
- this.pyodide.runPythonAsync(this.schema.model.code, () => {})
503
- .then((res) => {
504
- if (schema.outputs && schema.outputs.length) {
505
- const resultObj = {}
506
- schema.outputs.forEach(output => {
507
- resultObj[output.name] = this.pyodide.globals.get(output.name).toJs()
508
- })
509
- this.output(resultObj)
510
- } else {
511
- this.output(res)
512
- }
513
- })
514
- .catch((err) => {
515
- log(err)
516
- window['M'].toast({html: 'Error in code'})
517
- })
518
- break
519
-
520
- case 'class':
521
- case 'function':
522
- case 'async-init':
523
- case 'async-function':
524
- case 'get':
525
- case 'post':
526
- if (this.schema.model.worker) {
527
- this.worker.postMessage(inputValues)
528
- } else {
529
- // Run in main window
530
- var res
531
- if (this.schema.model.container === 'args') {
532
- res = this.modelFunc.apply(null, inputValues)
533
- } else {
534
- log('Applying inputs as object')
535
- res = this.modelFunc(inputValues)
536
- }
537
- log('modelFunc results:', res)
538
- Promise.resolve(res).then(r => { this.output(r) })
539
- }
540
- break
541
- }
542
- }
543
-
544
- output (res) {
545
- // TODO: Think about all edge cases
546
- // * No output field, but reactivity
547
- this.overlay.hide()
548
-
549
- if (typeof res === 'undefined') {
550
- return
551
- }
552
-
553
- log('Got output results of type:', typeof res)
554
-
555
- // Process results (res)
556
- const inputNames = this.schema.inputs.map(i => i.name)
557
- if (isObject(res) && Object.keys(res).every(key => inputNames.includes(key))) {
558
- // Update inputs from results
559
- log('Updating inputs:', Object.keys(res))
560
- this.data.inputs.forEach((input, i) => {
561
- if (input.name && (typeof res[input.name] !== 'undefined')) {
562
- log('Updating input: ', input.name, 'with data:', res[input.name])
563
- const r = res[input.name]
564
- if (typeof r === 'object') {
565
- Object.keys(r).forEach(k => {
566
- input[k] = r[k]
567
- })
568
- } else {
569
- input.value = r
570
- }
571
- }
572
- })
573
- } else if (this.renderFunc) {
574
- // Pass results to a custom render function
575
- log('Calling a render function...')
576
- this.renderFunc(res)
577
- } else if (Array.isArray(res) && res.length) {
578
- // Result is array
579
- if (this.data.outputs && this.data.outputs.length) {
580
- // We have outputs defined
581
- if (this.data.outputs.length === res.length) {
582
- // Same length
583
- this.data.outputs.forEach((output, i) => {
584
- output.value = res[i]
585
- })
586
- } else {
587
- // Different length
588
- this.data.outputs[0].value = res
589
- }
590
- } else {
591
- // Outputs are not defined
592
- this.data.outputs = [{
593
- 'type': 'array',
594
- 'value': res
595
- }]
596
- }
597
- } else if (typeof res === 'object') {
598
- if (this.data.outputs && this.data.outputs.length) {
599
- this.data.outputs.forEach((output, i) => {
600
- if (output.name && (typeof res[output.name] !== 'undefined')) {
601
- log('Updating output: ', output.name)
602
- output.value = res[output.name]
603
- }
604
- })
605
- } else {
606
- this.data.outputs = [{
607
- 'type': 'object',
608
- 'value': res
609
- }]
610
- }
611
- } else if (this.schema.outputs && this.schema.outputs.length === 1) {
612
- // One output value passed as raw js object
613
- this.data.outputs[0].value = res
614
- } else {
615
- this.data.outputs = [{
616
- 'type': typeof res,
617
- 'value': res
618
- }]
619
- }
620
- }
621
- }
File without changes
File without changes