edge-impulse-linux 1.17.1 → 1.17.3
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/build/cli/linux/runner-downloader.js +7 -2
- package/build/cli/linux/runner-downloader.js.map +1 -1
- package/build/cli/linux/runner-utils.d.ts +9 -2
- package/build/cli/linux/runner-utils.js +143 -52
- package/build/cli/linux/runner-utils.js.map +1 -1
- package/build/cli/linux/runner.js +77 -7
- package/build/cli/linux/runner.js.map +1 -1
- package/build/cli/linux/webserver/public/webserver.js +249 -25
- package/build/cli/linux/webserver/public/webserver.js.map +1 -1
- package/build/cli/linux/webserver/views/index.d.ts +2 -0
- package/build/cli/linux/webserver/views/index.js +33 -7
- package/build/cli/linux/webserver/views/index.js.map +1 -1
- package/build/library/classifier/image-classifier.d.ts +4 -1
- package/build/library/classifier/image-classifier.js +4 -4
- package/build/library/classifier/image-classifier.js.map +1 -1
- package/build/library/classifier/linux-impulse-runner-types.d.ts +1 -0
- package/build/sdk/studio/sdk/api/classifyApi.d.ts +0 -32
- package/build/sdk/studio/sdk/api/classifyApi.js +0 -64
- package/build/sdk/studio/sdk/api/classifyApi.js.map +1 -1
- package/build/sdk/studio/sdk/api/rawDataApi.d.ts +55 -1
- package/build/sdk/studio/sdk/api/rawDataApi.js +166 -1
- package/build/sdk/studio/sdk/api/rawDataApi.js.map +1 -1
- package/build/sdk/studio/sdk/model/batchEditBoundingBoxesRequest.d.ts +32 -0
- package/build/sdk/studio/sdk/model/batchEditBoundingBoxesRequest.js +34 -0
- package/build/sdk/studio/sdk/model/batchEditBoundingBoxesRequest.js.map +1 -0
- package/build/sdk/studio/sdk/model/classifyJobResponsePage.d.ts +4 -0
- package/build/sdk/studio/sdk/model/classifyJobResponsePage.js +5 -0
- package/build/sdk/studio/sdk/model/classifyJobResponsePage.js.map +1 -1
- package/build/sdk/studio/sdk/model/classifyJobResponsePageAllOf.d.ts +4 -0
- package/build/sdk/studio/sdk/model/classifyJobResponsePageAllOf.js +5 -0
- package/build/sdk/studio/sdk/model/classifyJobResponsePageAllOf.js.map +1 -1
- package/build/sdk/studio/sdk/model/modelPrediction.d.ts +5 -0
- package/build/sdk/studio/sdk/model/modelPrediction.js +5 -0
- package/build/sdk/studio/sdk/model/modelPrediction.js.map +1 -1
- package/build/sdk/studio/sdk/model/models.d.ts +1 -0
- package/build/sdk/studio/sdk/model/models.js +3 -0
- package/build/sdk/studio/sdk/model/models.js.map +1 -1
- package/build/sdk/studio/sdk/model/permission.d.ts +1 -1
- package/build/sdk/studio/sdk/model/permission.js +1 -1
- package/build/sdk/studio/sdk/model/permission.js.map +1 -1
- package/cli/linux/webserver/public/assets/mobileclient.css +36 -3
- package/cli/linux/webserver/public/webserver.js +280 -25
- package/package.json +1 -1
|
@@ -1,7 +1,11 @@
|
|
|
1
|
-
window.WebServer = async () => {
|
|
1
|
+
window.WebServer = async (vmStr) => {
|
|
2
|
+
|
|
3
|
+
const vm = JSON.parse(decodeURIComponent(vmStr));
|
|
4
|
+
console.log('vm', vm);
|
|
2
5
|
|
|
3
6
|
const els = {
|
|
4
7
|
title: document.querySelector('#header-row h1'),
|
|
8
|
+
cameraOuterContainer: document.querySelector('#capture-camera .capture-camera-outer'),
|
|
5
9
|
cameraContainer: document.querySelector('#capture-camera .capture-camera-inner'),
|
|
6
10
|
cameraImg: document.querySelector('#capture-camera img'),
|
|
7
11
|
timePerInference: document.querySelector('#time-per-inference'),
|
|
@@ -29,6 +33,9 @@ window.WebServer = async () => {
|
|
|
29
33
|
];
|
|
30
34
|
let colorIx = 0;
|
|
31
35
|
const labelToColor = { };
|
|
36
|
+
let isFirstClassification = true;
|
|
37
|
+
let inferenceIx = 0;
|
|
38
|
+
let lastClassification;
|
|
32
39
|
|
|
33
40
|
function switchView(el) {
|
|
34
41
|
for (let k of Object.keys(els.views)) {
|
|
@@ -150,6 +157,7 @@ window.WebServer = async () => {
|
|
|
150
157
|
inputEl.oninput = () => {
|
|
151
158
|
if (typeof threshold[k] === 'number') {
|
|
152
159
|
if (!inputEl.value || isNaN(Number(inputEl.value))) return;
|
|
160
|
+
if (k === 'min_score' && Number(inputEl.value) === 0) return;
|
|
153
161
|
|
|
154
162
|
threshold[k] = Number(inputEl.value);
|
|
155
163
|
}
|
|
@@ -194,20 +202,75 @@ window.WebServer = async () => {
|
|
|
194
202
|
bindThresholdSettings(opts.thresholds);
|
|
195
203
|
});
|
|
196
204
|
|
|
205
|
+
const onWindowResize = () => {
|
|
206
|
+
if (els.cameraContainer.naturalWidth === 0) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
let oldStyleWidth = els.cameraImg.style.width;
|
|
211
|
+
const containerWidth = els.cameraOuterContainer.getBoundingClientRect().width;
|
|
212
|
+
|
|
213
|
+
// height >480
|
|
214
|
+
if (els.cameraImg.naturalHeight > 480) {
|
|
215
|
+
// and fits within the container? just display as is
|
|
216
|
+
if (els.cameraImg.naturalWidth < containerWidth) {
|
|
217
|
+
els.cameraImg.style.width = els.cameraImg.naturalWidth + 'px';
|
|
218
|
+
}
|
|
219
|
+
else {
|
|
220
|
+
// does not fit within container? just use 100%
|
|
221
|
+
els.cameraImg.style.width = containerWidth + 'px';
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
else {
|
|
225
|
+
// what if we resize to 480 high?
|
|
226
|
+
const factor = els.cameraImg.naturalWidth / els.cameraImg.naturalHeight;
|
|
227
|
+
let newHeight = 480;
|
|
228
|
+
let newWidth = newHeight * factor;
|
|
229
|
+
|
|
230
|
+
// fits within the container? just display as is
|
|
231
|
+
if (newWidth < containerWidth) {
|
|
232
|
+
els.cameraImg.style.width = newWidth + 'px';
|
|
233
|
+
}
|
|
234
|
+
else {
|
|
235
|
+
// does not fit within container? just use 100%
|
|
236
|
+
els.cameraImg.style.width = containerWidth + 'px';
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
if (oldStyleWidth !== els.cameraImg.style.width && lastClassification) {
|
|
241
|
+
onClassification({
|
|
242
|
+
dontUpdateTable: true,
|
|
243
|
+
...lastClassification,
|
|
244
|
+
});
|
|
245
|
+
}
|
|
246
|
+
};
|
|
247
|
+
|
|
248
|
+
let isFirstImage = true;
|
|
197
249
|
socket.on('image', (opts) => {
|
|
250
|
+
if (isFirstImage) {
|
|
251
|
+
els.cameraImg.onload = () => {
|
|
252
|
+
onWindowResize();
|
|
253
|
+
els.cameraImg.onload = null;
|
|
254
|
+
};
|
|
255
|
+
isFirstImage = false;
|
|
256
|
+
}
|
|
198
257
|
els.cameraImg.src = opts.img;
|
|
199
258
|
});
|
|
259
|
+
window.addEventListener('resize', onWindowResize);
|
|
200
260
|
|
|
201
|
-
let
|
|
202
|
-
|
|
261
|
+
let lastFivePerfCalPredictions = [];
|
|
262
|
+
|
|
263
|
+
function onClassification(opts) {
|
|
264
|
+
lastClassification = opts;
|
|
203
265
|
|
|
204
|
-
socket.on('classification', (opts) => {
|
|
205
266
|
let result = opts.result;
|
|
206
267
|
let modelType = opts.modelType;
|
|
207
268
|
|
|
208
269
|
els.timePerInference.textContent = opts.timeMs;
|
|
209
270
|
els.additionalInfo.textContent = opts.additionalInfo;
|
|
210
|
-
|
|
271
|
+
if (!vm.isEmbedView) {
|
|
272
|
+
els.timePerInferenceContainer.style.display = '';
|
|
273
|
+
}
|
|
211
274
|
els.additionalInfoContainer.style.display = '';
|
|
212
275
|
|
|
213
276
|
console.log('classification', opts.result, opts.timeMs);
|
|
@@ -218,16 +281,39 @@ window.WebServer = async () => {
|
|
|
218
281
|
|
|
219
282
|
els.imageClassify.row.style.display = 'none';
|
|
220
283
|
|
|
221
|
-
if (result.classification) {
|
|
284
|
+
if (result.classification && !opts.dontUpdateTable) {
|
|
285
|
+
const showOnlyTopResults = Object.keys(result.classification).length > 10 && !result.visual_anomaly_grid;
|
|
286
|
+
|
|
222
287
|
if (isFirstClassification) {
|
|
223
|
-
|
|
224
|
-
|
|
288
|
+
if (showOnlyTopResults) {
|
|
289
|
+
// only 1 results th
|
|
290
|
+
{
|
|
291
|
+
let th = document.createElement('th');
|
|
292
|
+
th.scope = 'col';
|
|
293
|
+
th.textContent = th.title = 'Top 5 results';
|
|
294
|
+
th.classList.add('px-0', 'text-center');
|
|
295
|
+
els.resultsThead.appendChild(th);
|
|
296
|
+
}
|
|
225
297
|
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
298
|
+
// unless also have anomaly...
|
|
299
|
+
if (Object.keys(result.classification).indexOf('anomaly') > -1) {
|
|
300
|
+
let th = document.createElement('th');
|
|
301
|
+
th.scope = 'col';
|
|
302
|
+
th.textContent = th.title = 'anomaly';
|
|
303
|
+
th.classList.add('px-0', 'text-center');
|
|
304
|
+
els.resultsThead.appendChild(th);
|
|
305
|
+
}
|
|
306
|
+
}
|
|
307
|
+
else {
|
|
308
|
+
for (let ix = 0; ix < Object.keys(result.classification).length; ix++) {
|
|
309
|
+
const key = Object.keys(result.classification)[ix];
|
|
310
|
+
|
|
311
|
+
let th = document.createElement('th');
|
|
312
|
+
th.scope = 'col';
|
|
313
|
+
th.classList.add('px-0', 'text-center');
|
|
314
|
+
th.textContent = th.title = key;
|
|
315
|
+
els.resultsThead.appendChild(th);
|
|
316
|
+
}
|
|
231
317
|
}
|
|
232
318
|
|
|
233
319
|
if (result.visual_anomaly_grid) {
|
|
@@ -244,12 +330,31 @@ window.WebServer = async () => {
|
|
|
244
330
|
|
|
245
331
|
els.imageClassify.row.style.display = '';
|
|
246
332
|
|
|
247
|
-
let conclusion = 'uncertain';
|
|
333
|
+
let conclusion = vm.hasPerformanceCalibration ? '...' : 'uncertain';
|
|
248
334
|
let highest = Math.max(...Object.values(result.classification));
|
|
249
335
|
|
|
250
336
|
for (let k of Object.keys(result.classification)) {
|
|
251
337
|
if (result.classification[k] >= 0.55) {
|
|
252
|
-
|
|
338
|
+
if (vm.hasPerformanceCalibration) {
|
|
339
|
+
conclusion = k;
|
|
340
|
+
}
|
|
341
|
+
else {
|
|
342
|
+
conclusion = k + ' (' + result.classification[k].toFixed(2) + ')';
|
|
343
|
+
}
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// for perfcal models, if one of the last 5 predictions was the keyword => select that
|
|
348
|
+
if (vm.hasPerformanceCalibration) {
|
|
349
|
+
lastFivePerfCalPredictions.push(conclusion);
|
|
350
|
+
if (lastFivePerfCalPredictions.length > 5) {
|
|
351
|
+
lastFivePerfCalPredictions = lastFivePerfCalPredictions.slice(
|
|
352
|
+
lastFivePerfCalPredictions.length - 5);
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const dedup = Array.from(new Set(lastFivePerfCalPredictions));
|
|
356
|
+
if (dedup.length === 2 && dedup.find(x => x === '...')) {
|
|
357
|
+
conclusion = dedup.find(x => x !== '...');
|
|
253
358
|
}
|
|
254
359
|
}
|
|
255
360
|
|
|
@@ -264,15 +369,53 @@ window.WebServer = async () => {
|
|
|
264
369
|
let td1 = document.createElement('td');
|
|
265
370
|
td1.textContent = (++inferenceIx).toString();
|
|
266
371
|
tr.appendChild(td1);
|
|
267
|
-
|
|
372
|
+
if (showOnlyTopResults) {
|
|
373
|
+
// only print top 5
|
|
268
374
|
let td = document.createElement('td');
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
375
|
+
|
|
376
|
+
let results = [];
|
|
377
|
+
for (const key of Object.keys(result.classification)) {
|
|
378
|
+
if (key === 'anomaly') continue;
|
|
379
|
+
|
|
380
|
+
results.push({
|
|
381
|
+
label: key,
|
|
382
|
+
value: result.classification[key],
|
|
383
|
+
});
|
|
384
|
+
}
|
|
385
|
+
|
|
386
|
+
const top = results.sort((a, b) => b.value - a.value).slice(0, 5);
|
|
387
|
+
for (let ix = 0; ix < top.length; ix++) {
|
|
388
|
+
let span = ix === 0 ? document.createElement('strong') : document.createElement('span');
|
|
389
|
+
span.textContent = `${top[ix].label}: ${top[ix].value.toFixed(2)}`;
|
|
390
|
+
td.appendChild(span);
|
|
391
|
+
|
|
392
|
+
if (ix !== top.length - 1) {
|
|
393
|
+
let commaSpan = document.createElement('span');
|
|
394
|
+
commaSpan.textContent = ', ';
|
|
395
|
+
td.appendChild(commaSpan);
|
|
396
|
+
}
|
|
273
397
|
}
|
|
274
398
|
tr.appendChild(td);
|
|
399
|
+
|
|
400
|
+
if (Object.keys(result.classification).indexOf('anomaly') > -1) {
|
|
401
|
+
let anomalyTd = document.createElement('td');
|
|
402
|
+
anomalyTd.classList.add('text-center');
|
|
403
|
+
anomalyTd.textContent = result.classification.anomaly.toFixed(2);
|
|
404
|
+
tr.appendChild(anomalyTd);
|
|
405
|
+
}
|
|
406
|
+
}
|
|
407
|
+
else {
|
|
408
|
+
for (let k of Object.keys(result.classification)) {
|
|
409
|
+
let td = document.createElement('td');
|
|
410
|
+
td.classList.add('text-center');
|
|
411
|
+
td.textContent = result.classification[k].toFixed(2);
|
|
412
|
+
if (result.classification[k] === highest && !isVisualAnomaly) {
|
|
413
|
+
td.style.fontWeight = 600;
|
|
414
|
+
}
|
|
415
|
+
tr.appendChild(td);
|
|
416
|
+
}
|
|
275
417
|
}
|
|
418
|
+
|
|
276
419
|
if (result.visual_anomaly_grid) {
|
|
277
420
|
let td = document.createElement('td');
|
|
278
421
|
td.classList.add('text-center');
|
|
@@ -300,18 +443,61 @@ window.WebServer = async () => {
|
|
|
300
443
|
}
|
|
301
444
|
|
|
302
445
|
els.imageClassify.text.textContent = conclusion;
|
|
446
|
+
|
|
447
|
+
// for image classification models, draw overlay on top of the image with top 3 conclusions in embed mode
|
|
448
|
+
if (vm.sensorType === 'camera' && vm.isEmbedView) {
|
|
449
|
+
let results = [];
|
|
450
|
+
for (const key of Object.keys(result.classification)) {
|
|
451
|
+
if (key === 'anomaly') continue;
|
|
452
|
+
|
|
453
|
+
results.push({
|
|
454
|
+
label: key,
|
|
455
|
+
value: result.classification[key],
|
|
456
|
+
});
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
// don't change order if we only have 3
|
|
460
|
+
const top = results.length > 3 ?
|
|
461
|
+
results.sort((a, b) => b.value - a.value).slice(0, 3) :
|
|
462
|
+
results;
|
|
463
|
+
if (result.visual_anomaly_grid) {
|
|
464
|
+
// also visual AD? add to top results
|
|
465
|
+
top.push({
|
|
466
|
+
label: 'anomaly',
|
|
467
|
+
value: result.visual_anomaly_max,
|
|
468
|
+
});
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
let classificationOverlayEl = els.cameraContainer.querySelector('.classification-top-overlay');
|
|
472
|
+
if (!classificationOverlayEl) {
|
|
473
|
+
classificationOverlayEl = document.createElement('div');
|
|
474
|
+
classificationOverlayEl.classList.add('classification-top-overlay');
|
|
475
|
+
els.cameraContainer.appendChild(classificationOverlayEl);
|
|
476
|
+
}
|
|
477
|
+
classificationOverlayEl.textContent = '';
|
|
478
|
+
|
|
479
|
+
for (const topRes of top) {
|
|
480
|
+
const topDiv = document.createElement('div');
|
|
481
|
+
topDiv.textContent = `${topRes.label} (${topRes.value.toFixed(2)})`;
|
|
482
|
+
classificationOverlayEl.appendChild(topDiv);
|
|
483
|
+
}
|
|
484
|
+
|
|
485
|
+
els.imageClassify.row.style.display = 'none';
|
|
486
|
+
}
|
|
303
487
|
}
|
|
304
488
|
if (result.bounding_boxes) {
|
|
305
489
|
let factor = els.cameraImg.naturalHeight / els.cameraImg.clientHeight;
|
|
306
490
|
|
|
307
|
-
for (let b of result.bounding_boxes) {
|
|
491
|
+
for (let b of result.object_tracking || result.bounding_boxes) {
|
|
308
492
|
let bb = {
|
|
309
493
|
x: b.x / factor,
|
|
310
494
|
y: b.y / factor,
|
|
311
495
|
width: b.width / factor,
|
|
312
496
|
height: b.height / factor,
|
|
313
|
-
label: b
|
|
314
|
-
|
|
497
|
+
label: 'object_id' in b ?
|
|
498
|
+
`${b.label} (ID ${b.object_id})` :
|
|
499
|
+
b.label,
|
|
500
|
+
value: 'value' in b ? b.value : undefined,
|
|
315
501
|
};
|
|
316
502
|
|
|
317
503
|
if (!labelToColor[bb.label]) {
|
|
@@ -345,7 +531,10 @@ window.WebServer = async () => {
|
|
|
345
531
|
let label = document.createElement('div');
|
|
346
532
|
label.classList.add('bounding-box-label');
|
|
347
533
|
label.style.background = color;
|
|
348
|
-
label.textContent = bb.label
|
|
534
|
+
label.textContent = bb.label;
|
|
535
|
+
if (typeof bb.value === 'number') {
|
|
536
|
+
label.textContent += ' (' + bb.value.toFixed(2) + ')';
|
|
537
|
+
}
|
|
349
538
|
if (modelType === 'constrained_object_detection') {
|
|
350
539
|
el.style.whiteSpace = 'nowrap';
|
|
351
540
|
}
|
|
@@ -406,9 +595,75 @@ window.WebServer = async () => {
|
|
|
406
595
|
els.cameraContainer.appendChild(el);
|
|
407
596
|
}
|
|
408
597
|
}
|
|
409
|
-
}
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
socket.on('classification', onClassification);
|
|
410
601
|
|
|
411
602
|
if (els.websocketAddress) {
|
|
412
603
|
els.websocketAddress.textContent = `ws://${location.host}`;
|
|
413
604
|
}
|
|
605
|
+
|
|
606
|
+
// Here's a helper function that'll loop every second, checks if "classification-top-overlay" is present
|
|
607
|
+
// and then switches between white/black text automatically. If this element is not created, it just sits idle
|
|
608
|
+
(async () => {
|
|
609
|
+
function getAvgBrightness(img, x, y, w, h) {
|
|
610
|
+
const canvas = document.createElement('canvas');
|
|
611
|
+
const ctx = canvas.getContext('2d');
|
|
612
|
+
canvas.width = img.naturalWidth;
|
|
613
|
+
canvas.height = img.naturalHeight;
|
|
614
|
+
ctx.drawImage(img, 0, 0, canvas.width, canvas.height);
|
|
615
|
+
|
|
616
|
+
// Scale from CSS coords (relative to image on page) to natural pixel coords
|
|
617
|
+
const scaleX = img.naturalWidth / img.clientWidth;
|
|
618
|
+
const scaleY = img.naturalHeight / img.clientHeight;
|
|
619
|
+
|
|
620
|
+
const sx = (x - img.getBoundingClientRect().x) * scaleX;
|
|
621
|
+
const sy = (y - img.getBoundingClientRect().y) * scaleY;
|
|
622
|
+
const sw = w * scaleX;
|
|
623
|
+
const sh = h * scaleY;
|
|
624
|
+
|
|
625
|
+
const data = ctx.getImageData(sx, sy, sw, sh).data;
|
|
626
|
+
|
|
627
|
+
let total = 0;
|
|
628
|
+
for (let i = 0; i < data.length; i += 4) {
|
|
629
|
+
const r = data[i];
|
|
630
|
+
const g = data[i + 1];
|
|
631
|
+
const b = data[i + 2];
|
|
632
|
+
total += (r * 299 + g * 587 + b * 114) / 1000;
|
|
633
|
+
}
|
|
634
|
+
|
|
635
|
+
// debug info
|
|
636
|
+
// const cropCanvas = document.createElement('canvas');
|
|
637
|
+
// cropCanvas.width = sw;
|
|
638
|
+
// cropCanvas.height = sh;
|
|
639
|
+
// const cctx = cropCanvas.getContext('2d');
|
|
640
|
+
// cctx.drawImage(img, sx, sy, sw, sh, 0, 0, sw, sh);
|
|
641
|
+
// const cropEl = document.querySelector('#crop') || document.createElement('img');
|
|
642
|
+
// cropEl.id = 'crop';
|
|
643
|
+
// cropEl.src = cropCanvas.toDataURL('image/png');
|
|
644
|
+
// document.body.appendChild(cropEl);
|
|
645
|
+
|
|
646
|
+
return total / (data.length / 4);
|
|
647
|
+
}
|
|
648
|
+
|
|
649
|
+
if (!vm.isEmbedView || vm.sensorType !== 'camera') return;
|
|
650
|
+
|
|
651
|
+
while (1) {
|
|
652
|
+
const labelEl = document.querySelector('.classification-top-overlay');
|
|
653
|
+
if (!labelEl) {
|
|
654
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
655
|
+
continue;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
const rect = labelEl.getBoundingClientRect();
|
|
659
|
+
const brightness = getAvgBrightness(els.cameraImg, rect.x, rect.y, rect.width, rect.height);
|
|
660
|
+
if (brightness > 180) {
|
|
661
|
+
labelEl.style.color = 'black';
|
|
662
|
+
}
|
|
663
|
+
else {
|
|
664
|
+
labelEl.style.color = 'white';
|
|
665
|
+
}
|
|
666
|
+
await new Promise(resolve => setTimeout(resolve, 1000));
|
|
667
|
+
}
|
|
668
|
+
})();
|
|
414
669
|
};
|