@jaak.ai/stamps 2.1.0-dev.2 → 2.1.0-dev.20
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/dist/cjs/app-globals-V2Kpy_OQ.js +8 -0
- package/dist/cjs/app-globals-V2Kpy_OQ.js.map +1 -0
- package/dist/cjs/{index-BfhtOB0D.js → index-Ga0t6BMe.js} +176 -119
- package/dist/cjs/index-Ga0t6BMe.js.map +1 -0
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js +5 -4
- package/dist/cjs/jaak-stamps-webcomponent.cjs.js.map +1 -1
- package/dist/cjs/jaak-stamps.cjs.entry.js +542 -68
- package/dist/cjs/jaak-stamps.cjs.entry.js.map +1 -1
- package/dist/cjs/jaak-stamps.entry.cjs.js.map +1 -1
- package/dist/cjs/loader.cjs.js +4 -3
- package/dist/cjs/loader.cjs.js.map +1 -1
- package/dist/collection/collection-manifest.json +1 -1
- package/dist/collection/components/my-component/my-component.css +56 -12
- package/dist/collection/components/my-component/my-component.js +429 -67
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/CameraService.js +84 -2
- package/dist/collection/services/CameraService.js.map +1 -1
- package/dist/collection/services/DetectionService.js +32 -4
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/components/index.js +165 -113
- package/dist/components/index.js.map +1 -1
- package/dist/components/jaak-stamps.js +546 -68
- package/dist/components/jaak-stamps.js.map +1 -1
- package/dist/esm/app-globals-DQuL1Twl.js +6 -0
- package/dist/esm/app-globals-DQuL1Twl.js.map +1 -0
- package/dist/esm/{index-BP1Q4KOg.js → index-Drbzcuq-.js} +177 -119
- package/dist/esm/index-Drbzcuq-.js.map +1 -0
- package/dist/esm/jaak-stamps-webcomponent.js +5 -4
- package/dist/esm/jaak-stamps-webcomponent.js.map +1 -1
- package/dist/esm/jaak-stamps.entry.js +542 -68
- package/dist/esm/jaak-stamps.entry.js.map +1 -1
- package/dist/esm/loader.js +4 -3
- package/dist/esm/loader.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps-webcomponent.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/jaak-stamps.entry.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/loader.esm.js.map +1 -1
- package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-DQuL1Twl.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js +3 -0
- package/dist/jaak-stamps-webcomponent/p-Drbzcuq-.js.map +1 -0
- package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-c06e5d7b.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +19 -0
- package/dist/types/services/CameraService.d.ts +1 -0
- package/dist/types/stencil-public-runtime.d.ts +12 -5
- package/package.json +3 -1
- package/dist/cjs/index-BfhtOB0D.js.map +0 -1
- package/dist/esm/index-BP1Q4KOg.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-41e88688.entry.js.map +0 -1
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js +0 -3
- package/dist/jaak-stamps-webcomponent/p-BP1Q4KOg.js.map +0 -1
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var index = require('./index-
|
|
3
|
+
var index = require('./index-Ga0t6BMe.js');
|
|
4
4
|
|
|
5
5
|
class EventBusService {
|
|
6
6
|
events = new Map();
|
|
@@ -206,7 +206,21 @@ class CameraService {
|
|
|
206
206
|
tempStream.getTracks().forEach(track => track.stop());
|
|
207
207
|
}
|
|
208
208
|
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
209
|
-
|
|
209
|
+
const allCameras = devices.filter(device => device.kind === 'videoinput');
|
|
210
|
+
// Filter out virtual cameras
|
|
211
|
+
this.availableCameras = allCameras.filter(camera => {
|
|
212
|
+
const isVirtual = this.isVirtualCamera(camera);
|
|
213
|
+
if (isVirtual) {
|
|
214
|
+
console.log(`Virtual camera detected and filtered: ${camera.label}`);
|
|
215
|
+
}
|
|
216
|
+
return !isVirtual;
|
|
217
|
+
});
|
|
218
|
+
// Check if there are physical cameras available
|
|
219
|
+
if (this.availableCameras.length === 0) {
|
|
220
|
+
console.log('No physical cameras available');
|
|
221
|
+
this.eventBus.emit('error', new Error('Cámaras virtuales no están permitidas. Use una cámara física.'));
|
|
222
|
+
return [];
|
|
223
|
+
}
|
|
210
224
|
// Cache the results for optimization
|
|
211
225
|
CameraService.deviceEnumerationCache = {
|
|
212
226
|
devices: this.availableCameras,
|
|
@@ -256,6 +270,21 @@ class CameraService {
|
|
|
256
270
|
});
|
|
257
271
|
this.lastStreamConstraints = finalConstraints;
|
|
258
272
|
const stream = await this.currentStreamPromise;
|
|
273
|
+
// Validate that active stream is not from a virtual camera
|
|
274
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
275
|
+
if (videoTrack) {
|
|
276
|
+
const settings = videoTrack.getSettings();
|
|
277
|
+
const deviceId = settings.deviceId;
|
|
278
|
+
if (deviceId) {
|
|
279
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
280
|
+
const currentDevice = devices.find(device => device.deviceId === deviceId);
|
|
281
|
+
if (currentDevice && this.isVirtualCamera(currentDevice)) {
|
|
282
|
+
console.log(`Virtual camera detected in active stream: ${currentDevice.label}`);
|
|
283
|
+
stream.getTracks().forEach(track => track.stop());
|
|
284
|
+
throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
|
|
285
|
+
}
|
|
286
|
+
}
|
|
287
|
+
}
|
|
259
288
|
return stream;
|
|
260
289
|
}
|
|
261
290
|
catch (error) {
|
|
@@ -273,7 +302,23 @@ class CameraService {
|
|
|
273
302
|
audio: false
|
|
274
303
|
});
|
|
275
304
|
this.lastStreamConstraints = basicConstraints;
|
|
276
|
-
|
|
305
|
+
const stream = await this.currentStreamPromise;
|
|
306
|
+
// Validate fallback stream as well
|
|
307
|
+
const videoTrack = stream.getVideoTracks()[0];
|
|
308
|
+
if (videoTrack) {
|
|
309
|
+
const settings = videoTrack.getSettings();
|
|
310
|
+
const deviceId = settings.deviceId;
|
|
311
|
+
if (deviceId) {
|
|
312
|
+
const devices = await navigator.mediaDevices.enumerateDevices();
|
|
313
|
+
const currentDevice = devices.find(device => device.deviceId === deviceId);
|
|
314
|
+
if (currentDevice && this.isVirtualCamera(currentDevice)) {
|
|
315
|
+
console.log(`Virtual camera detected in fallback stream: ${currentDevice.label}`);
|
|
316
|
+
stream.getTracks().forEach(track => track.stop());
|
|
317
|
+
throw new Error('Cámaras virtuales no están permitidas. Use una cámara física.');
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
}
|
|
321
|
+
return stream;
|
|
277
322
|
}
|
|
278
323
|
}
|
|
279
324
|
async switchCamera(cameraId) {
|
|
@@ -282,6 +327,12 @@ class CameraService {
|
|
|
282
327
|
await this.enumerateDevices();
|
|
283
328
|
return;
|
|
284
329
|
}
|
|
330
|
+
// Validate that the target camera is not virtual
|
|
331
|
+
if (this.isVirtualCamera(selectedCamera)) {
|
|
332
|
+
console.log(`Attempted to switch to virtual camera: ${selectedCamera.label}`);
|
|
333
|
+
this.eventBus.emit('error', new Error('No se puede cambiar a cámara virtual'));
|
|
334
|
+
return;
|
|
335
|
+
}
|
|
285
336
|
await this.setSelectedCamera(cameraId);
|
|
286
337
|
}
|
|
287
338
|
isRearCamera(stream) {
|
|
@@ -553,6 +604,37 @@ class CameraService {
|
|
|
553
604
|
invalidateDeviceCache() {
|
|
554
605
|
CameraService.deviceEnumerationCache = null;
|
|
555
606
|
}
|
|
607
|
+
isVirtualCamera(device) {
|
|
608
|
+
const label = device.label.toLowerCase();
|
|
609
|
+
const virtualCameraIndicators = [
|
|
610
|
+
'obs',
|
|
611
|
+
'virtual',
|
|
612
|
+
'snap camera',
|
|
613
|
+
'manycam',
|
|
614
|
+
'xsplit',
|
|
615
|
+
'camtwist',
|
|
616
|
+
'ecamm',
|
|
617
|
+
'nvidia broadcast',
|
|
618
|
+
'droidcam',
|
|
619
|
+
'iriun',
|
|
620
|
+
'elgato',
|
|
621
|
+
'streamlabs',
|
|
622
|
+
'wirecast',
|
|
623
|
+
'zoom virtual',
|
|
624
|
+
'teams virtual',
|
|
625
|
+
'mmhmm',
|
|
626
|
+
'camo',
|
|
627
|
+
'reincubate camo',
|
|
628
|
+
'webcamoid',
|
|
629
|
+
'splitcam',
|
|
630
|
+
'cheese',
|
|
631
|
+
'guvcview',
|
|
632
|
+
'yawcam',
|
|
633
|
+
'webcam 7',
|
|
634
|
+
'altserver'
|
|
635
|
+
];
|
|
636
|
+
return virtualCameraIndicators.some(indicator => label.includes(indicator));
|
|
637
|
+
}
|
|
556
638
|
}
|
|
557
639
|
|
|
558
640
|
class LowMemoryDeviceStrategy {
|
|
@@ -672,7 +754,12 @@ class DetectionService {
|
|
|
672
754
|
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
|
|
673
755
|
}
|
|
674
756
|
catch (error) {
|
|
675
|
-
|
|
757
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
758
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
759
|
+
error.message.includes('Out of memory') ||
|
|
760
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
761
|
+
error.message.includes('no available backend found');
|
|
762
|
+
if (isMemoryError) {
|
|
676
763
|
const fallbackOptions = {
|
|
677
764
|
executionProviders: ['wasm'],
|
|
678
765
|
graphOptimizationLevel: 'disabled',
|
|
@@ -682,8 +769,17 @@ class DetectionService {
|
|
|
682
769
|
executionMode: 'sequential',
|
|
683
770
|
interOpNumThreads: 1,
|
|
684
771
|
intraOpNumThreads: 1,
|
|
772
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
773
|
+
enableProfiling: false,
|
|
774
|
+
sessionLogSeverityLevel: 4,
|
|
775
|
+
sessionLogVerbosityLevel: 0,
|
|
685
776
|
};
|
|
686
|
-
|
|
777
|
+
try {
|
|
778
|
+
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
|
|
779
|
+
}
|
|
780
|
+
catch (fallbackError) {
|
|
781
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
782
|
+
}
|
|
687
783
|
}
|
|
688
784
|
else {
|
|
689
785
|
throw error;
|
|
@@ -723,7 +819,12 @@ class DetectionService {
|
|
|
723
819
|
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
|
|
724
820
|
}
|
|
725
821
|
catch (error) {
|
|
726
|
-
|
|
822
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
823
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
824
|
+
error.message.includes('Out of memory') ||
|
|
825
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
826
|
+
error.message.includes('no available backend found');
|
|
827
|
+
if (isMemoryError) {
|
|
727
828
|
const fallbackOptions = {
|
|
728
829
|
executionProviders: ['wasm'],
|
|
729
830
|
graphOptimizationLevel: 'disabled',
|
|
@@ -733,8 +834,17 @@ class DetectionService {
|
|
|
733
834
|
executionMode: 'sequential',
|
|
734
835
|
interOpNumThreads: 1,
|
|
735
836
|
intraOpNumThreads: 1,
|
|
837
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
838
|
+
enableProfiling: false,
|
|
839
|
+
sessionLogSeverityLevel: 4,
|
|
840
|
+
sessionLogVerbosityLevel: 0,
|
|
736
841
|
};
|
|
737
|
-
|
|
842
|
+
try {
|
|
843
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
|
|
844
|
+
}
|
|
845
|
+
catch (fallbackError) {
|
|
846
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
847
|
+
}
|
|
738
848
|
}
|
|
739
849
|
else {
|
|
740
850
|
throw error;
|
|
@@ -1091,7 +1201,7 @@ class ServiceContainer {
|
|
|
1091
1201
|
}
|
|
1092
1202
|
}
|
|
1093
1203
|
|
|
1094
|
-
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);animation:drawCheck 0.4s ease-out 0.2s both}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(255, 255, 255, 0.3);border-top:2px solid #ffffff;border-radius:50%;animation:spin 1s linear infinite}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{background:#e9ecef;color:#6c757d;cursor:not-allowed;transform:none}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}}";
|
|
1204
|
+
const myComponentCss = ":host{display:block;width:100%;height:100%;font-family:system-ui, -apple-system, sans-serif;color:#1a1a1a}.detector-container{display:flex;flex-direction:column;align-items:center;width:100%;height:100%}h1{font-size:24px;font-weight:500;color:#333;margin:0 0 24px 0}.video-container{position:relative;width:100%;height:100%;background:#333;border:1px solid #e0e0e0;border-radius:8px;overflow:hidden}video,.detection-overlay{position:absolute;width:100%;height:100%;border-radius:8px}video.mirror,.detection-overlay.mirror{transform:rotateY(180deg)}.detection-overlay{z-index:1;pointer-events:none}video{z-index:0}.detection-box{transition:opacity 0.2s ease}.status{margin-top:16px;font-size:12px;color:#666}.overlay-mask{position:absolute;top:0;left:0;width:100%;height:100%;z-index:10;display:block;pointer-events:none}.card-outline{position:absolute;top:var(--mask-center-y, 50%);left:var(--mask-center-x, 50%);transform:translate(-50%, -50%);width:var(--mask-width, 88%);height:var(--mask-height, 55%);border:none;border-radius:4px;background:transparent;opacity:0.8}.side{position:absolute;background:#999;transition:background-color 0.3s ease;border-radius:1px}.side-top.aligned{border-left-color:#28a745;border-top-color:#28a745}.side-right.aligned{border-right-color:#28a745;border-top-color:#28a745}.side-bottom.aligned{border-left-color:#28a745;border-bottom-color:#28a745}.side-left.aligned{border-right-color:#28a745;border-bottom-color:#28a745}.side-top{top:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-right{top:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-top:6px solid #ffffff;background:transparent}.side-bottom{bottom:-3px;left:-3px;width:30px;height:30px;border-left:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.side-left{bottom:-3px;right:-3px;width:30px;height:30px;border-right:6px solid #ffffff;border-bottom:6px solid #ffffff;background:transparent}.guide-text{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);color:#ffffff;font-size:12px;font-weight:500;text-align:center;white-space:normal;padding:13px;max-width:300px;z-index:20;height:fit-content;width:fit-content;animation:slideInFromTopCentered 0.3s ease-out}.quality-indicator{position:absolute;top:20px;right:20px;display:flex;align-items:center;gap:8px;background:rgba(0, 0, 0, 0.8);padding:8px 12px;border-radius:20px;z-index:25;transition:all 0.3s ease}.quality-indicator.warning{background:rgba(255, 152, 0, 0.9)}.quality-indicator.good{background:rgba(76, 175, 80, 0.9)}.quality-score{color:#fff;font-size:12px;font-weight:600;min-width:30px;text-align:center}.quality-status{width:20px;height:20px;border-radius:50%;display:flex;align-items:center;justify-content:center;font-size:14px;font-weight:bold;color:#fff;transition:all 0.3s ease}.quality-status.ready{background:#4CAF50}.quality-status.not-ready{background:#f44336}.card-outline.quality-warning{animation:qualityWarning 1s ease-in-out infinite alternate}@keyframes qualityWarning{0%{box-shadow:0 0 0 3px rgba(255, 152, 0, 0.6)}100%{box-shadow:0 0 0 6px rgba(255, 152, 0, 0.3)}}.capture-animation{position:absolute;top:0;left:0;width:100%;height:100%;background:#fff;opacity:0;z-index:30;pointer-events:none;animation:captureFlash 0.6s ease-out}@keyframes captureFlash{0%{opacity:0}15%{opacity:0.8}30%{opacity:0}45%{opacity:0.4}60%{opacity:0}100%{opacity:0}}.card-outline.capturing{animation:pulseGreen 0.6s ease-out}@keyframes pulseGreen{0%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}50%{border-color:#28a745;box-shadow:0 0 0 8px rgba(40, 167, 69, 0.6)}100%{border-color:#28a745;box-shadow:0 0 0 4px rgba(40, 167, 69, 0)}}.flip-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showFlipInstruction 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.id-card-icon{width:80px;height:50px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:8px;position:relative;animation:flipCard 2s ease-in-out infinite;box-shadow:0 4px 12px rgba(0, 0, 0, 0.3)}.id-card-icon::before{content:'';position:absolute;top:8px;left:8px;width:16px;height:12px;background:rgba(255, 255, 255, 0.9);border-radius:2px}.id-card-icon::after{content:'';position:absolute;top:25px;left:8px;width:64px;height:3px;background:rgba(255, 255, 255, 0.7);border-radius:1px;box-shadow:0 6px 0 rgba(255, 255, 255, 0.7), 0 12px 0 rgba(255, 255, 255, 0.7)}.flip-text{margin-top:12px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px}@keyframes showFlipInstruction{0%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.8)}}@keyframes flipCard{0%{transform:rotateY(0deg)}50%{transform:rotateY(180deg)}100%{transform:rotateY(360deg)}}.success-animation{position:absolute;top:50%;left:50%;transform:translate(-50%, -50%);z-index:35;pointer-events:none;opacity:0;animation:showSuccessMessage 3s ease-in-out;display:flex;flex-direction:column;align-items:center;justify-content:center}.check-icon{width:80px;height:80px;background:linear-gradient(135deg, #6c757d 0%, #495057 100%);border-radius:50%;position:relative;animation:bounceSuccess 0.6s ease-out;box-shadow:0 4px 16px rgba(108, 117, 125, 0.4);display:flex;align-items:center;justify-content:center}.check-icon::before{content:'';position:absolute;width:20px;height:35px;border:4px solid #fff;border-top:none;border-left:none;transform:rotate(45deg);}.success-text{margin-top:16px;color:#fff;font-size:14px;font-weight:600;text-align:center;background:rgba(128, 128, 128, 0.8);padding:12px 20px;border-radius:20px;max-width:300px;animation:fadeInUp 0.5s ease-out 0.4s both}@keyframes showSuccessMessage{0%{opacity:0;transform:translate(-50%, -50%) scale(0.5)}15%{opacity:1;transform:translate(-50%, -50%) scale(1)}85%{opacity:1;transform:translate(-50%, -50%) scale(1)}100%{opacity:0;transform:translate(-50%, -50%) scale(0.9)}}@keyframes bounceSuccess{0%{transform:scale(0)}50%{transform:scale(1.1)}100%{transform:scale(1)}}@keyframes drawCheck{0%{width:0;height:0}50%{width:20px;height:0}100%{width:20px;height:35px}}@keyframes fadeInUp{0%{opacity:0;transform:translateY(20px)}100%{opacity:1;transform:translateY(0)}}.skip-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.back-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:320px}.back-capture-buttons{display:flex;gap:12px;align-items:center;justify-content:center;flex-wrap:wrap}.capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.capture-button:hover{background:#f8f9fa}.capture-button:active{background:#e9ecef;transform:translateY(1px)}.capture-button:disabled,.capture-button.primary-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.back-capture-section{bottom:16px;max-width:300px}.back-capture-buttons{flex-direction:column;gap:8px;width:100%}.capture-button,.back-capture-buttons .skip-button{width:100%;max-width:200px;padding:10px 20px;font-size:13px}.capture-button:disabled,.capture-button.primary-button:disabled,.skip-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}.skip-explanation{background:rgba(0, 0, 0, 0.5);color:#ffffff;padding:10px 16px;border-radius:8px;font-size:14px;font-weight:500;text-align:center;margin-bottom:12px;box-shadow:0 2px 8px rgba(0, 0, 0, 0.2);backdrop-filter:blur(12px);border:1px solid rgba(255, 255, 255, 0.1);animation:fadeIn 0.3s ease-in-out}@keyframes fadeIn{from{opacity:0;transform:translateY(10px)}to{opacity:1;transform:translateY(0)}}.guide-text.performance-warning-text{animation:gentlePulse 2s ease-in-out infinite;background:rgba(255, 107, 107, 0.3);border:1px solid rgba(255, 107, 107, 0.5)}@keyframes gentlePulse{0%,100%{transform:translate(-50%, -50%) scale(1);background:rgba(255, 107, 107, 0.3)}50%{transform:translate(-50%, -50%) scale(1.02);background:rgba(255, 107, 107, 0.4)}}.skip-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.skip-button:hover{background:#f8f9fa}.skip-button:active{background:#e9ecef;transform:translateY(1px)}.skip-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}.camera-controls{position:absolute;top:16px;right:16px;z-index:25;display:flex;gap:8px;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}.camera-selector-button{height:32px;padding:0 10px;border:none;border-radius:8px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);color:#ffffff;font-size:12px;font-weight:500;cursor:pointer;transition:all 0.2s ease;display:flex;align-items:center;justify-content:center;border:1px solid rgba(255, 255, 255, 0.1);white-space:nowrap;min-width:fit-content}.camera-selector-button:hover{background:rgba(0, 0, 0, 0.8);border-color:rgba(255, 255, 255, 0.2);transform:translateY(-1px)}.camera-selector-button:active{transform:translateY(0);background:rgba(0, 0, 0, 0.9)}.camera-selector-button:disabled,.camera-selector-button.loading{opacity:0.7;cursor:not-allowed;pointer-events:none}.camera-selector-button:disabled:hover,.camera-selector-button.loading:hover{transform:none;background:rgba(0, 0, 0, 0.6);border-color:rgba(255, 255, 255, 0.1)}.button-spinner{width:16px;height:16px;border:2px solid rgba(0, 0, 0, 0.2);border-top:2px solid #333333;border-radius:50%;animation:spin 1s linear infinite;display:inline-block;margin-right:8px;vertical-align:middle}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.camera-selector-dropdown{position:absolute;top:56px;right:16px;z-index:30;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;min-width:260px;max-width:300px;border:1px solid rgba(255, 255, 255, 0.1);overflow:hidden;pointer-events:auto;animation:slideInFromTop 0.3s ease-out}@keyframes slideInFromTop{0%{opacity:0;transform:translateY(-8px) scale(0.95)}100%{opacity:1;transform:translateY(0) scale(1)}}@keyframes slideInFromTopCentered{0%{opacity:0;transform:translate(-50%, -50%) translateY(-8px) scale(0.95)}100%{opacity:1;transform:translate(-50%, -50%) translateY(0) scale(1)}}.camera-selector-header{display:flex;justify-content:space-between;align-items:center;padding:8px 12px;border-bottom:1px solid rgba(255, 255, 255, 0.1)}.camera-selector-header span{font-weight:500;color:#ffffff;font-size:13px;opacity:0.9}.close-selector{width:20px;height:20px;border:none;background:none;color:rgba(255, 255, 255, 0.7);font-size:16px;cursor:pointer;display:flex;align-items:center;justify-content:center;border-radius:4px;transition:all 0.2s ease}.close-selector:hover{background:rgba(255, 255, 255, 0.1);color:#ffffff}.camera-list{padding:4px 0;max-height:240px;overflow-y:auto}.camera-option{width:100%;padding:8px 12px;border:none;background:none;text-align:left;cursor:pointer;transition:all 0.2s ease;display:flex;justify-content:flex-start;align-items:center;gap:8px;color:rgba(255, 255, 255, 0.9)}.camera-option:hover{background:rgba(255, 255, 255, 0.08)}.camera-option.selected{background:rgba(255, 255, 255, 0.12);color:#ffffff}.camera-label{font-size:13px;flex:1;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;margin-right:0;font-weight:400;display:flex;align-items:center;gap:6px}.camera-label .autofocus-icon{font-size:12px;color:#ffffff !important;opacity:0.8;flex-shrink:0}.selected-indicator{font-size:14px;color:#ffffff;opacity:0.9;flex-shrink:0}.device-info{padding:6px 12px;border-top:1px solid rgba(255, 255, 255, 0.1);background:rgba(255, 255, 255, 0.05)}.device-info small{color:rgba(255, 255, 255, 0.6);font-size:11px;text-transform:capitalize;font-weight:400}@media (max-width: 480px){.camera-controls{top:12px;right:12px;gap:6px}.camera-selector-button{height:36px;padding:0 12px;font-size:11px;border-radius:6px}.camera-selector-dropdown{right:12px;top:48px;min-width:240px;max-width:calc(100vw - 24px)}.camera-selector-header{padding:6px 10px}.camera-option{padding:6px 10px}.device-info{padding:4px 10px}}.watermark{position:absolute;bottom:12px;right:12px;z-index:15;pointer-events:none;opacity:0.7}.watermark img{height:24px;width:auto;filter:drop-shadow(0 1px 2px rgba(0, 0, 0, 0.3))}.component-status{position:absolute;top:16px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);display:flex;align-items:center;gap:6px;padding:6px 10px;z-index:40;height:32px;width:fit-content;animation:slideInFromTop 0.3s ease-out}.status-spinner{width:16px;height:16px;border:1px solid rgba(255, 255, 255, 0.3);border-top:1px solid #ffffff;border-radius:50%;animation:statusSpin 1s linear infinite;flex-shrink:0}.status-content{display:flex;flex-direction:column;gap:1px}.status-message{color:#ffffff;font-size:12px;font-weight:500;margin:0}.status-description{color:rgba(255, 255, 255, 0.7);font-size:10px;line-height:1.2;margin:0}@keyframes statusSpin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}@media (max-width: 480px){.component-status{top:12px;left:12px;padding:4px 8px;gap:4px;height:28px;border-radius:8px}.status-spinner{width:14px;height:14px}.status-message{font-size:11px}.status-description{font-size:9px}.guide-text{font-size:11px;padding:4px 8px;border-radius:8px}}.loading-overlay{position:absolute;top:0;left:0;width:100%;height:100%;background:rgba(0, 0, 0, 0.85);backdrop-filter:blur(4px);display:flex;flex-direction:column;align-items:center;justify-content:center;z-index:30;border-radius:8px}.loading-spinner{width:50px;height:50px;border:3px solid rgba(255, 255, 255, 0.15);border-top:3px solid #28a745;border-radius:50%;animation:spin 1s ease-in-out infinite;margin-bottom:16px}.loading-text{color:#ffffff;font-size:14px;font-weight:500;text-align:center;opacity:0.9;margin-bottom:4px}.loading-description{color:rgba(255, 255, 255, 0.7);font-size:12px;text-align:center;opacity:0.8}@keyframes spin{0%{transform:rotate(0deg)}100%{transform:rotate(360deg)}}.performance-monitor{position:absolute;top:70px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromTop 0.3s ease-out;transition:all 0.3s ease}@keyframes slideInFromLeft{0%{opacity:0;transform:translateX(-20px) scale(0.95)}100%{opacity:1;transform:translateX(0) scale(1)}}.performance-expanded{padding:6px 10px}.metrics-row{display:flex;gap:8px;margin-bottom:4px}.metrics-row:last-child{margin-bottom:0}.metric-compact{display:flex;flex-direction:column;align-items:center;gap:2px;min-width:50px}.metric-label{font-size:9px;color:rgba(255, 255, 255, 0.6);font-weight:500;text-transform:uppercase;letter-spacing:0.3px}.metric-value{font-size:10px;font-weight:600;font-family:'Monaco', 'Menlo', 'Consolas', monospace;padding:2px 4px;border-radius:3px;text-align:center;white-space:nowrap}.metric-value.good{color:#4ade80;background:rgba(74, 222, 128, 0.1);border:1px solid rgba(74, 222, 128, 0.2)}.metric-value.warning{color:#fbbf24;background:rgba(251, 191, 36, 0.1);border:1px solid rgba(251, 191, 36, 0.2)}.metric-value.danger{color:#f87171;background:rgba(248, 113, 113, 0.1);border:1px solid rgba(248, 113, 113, 0.2)}@media (max-width: 480px){.performance-monitor{top:60px;left:12px;width:fit-content}.performance-expanded{padding:4px 8px}.metrics-row{gap:6px;margin-bottom:3px}.metric-compact{min-width:45px;gap:1px}.metric-label{font-size:8px}.metric-value{font-size:9px;padding:1px 3px}}.quality-monitor{position:absolute;top:200px;left:16px;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(20px);border-radius:12px;border:1px solid rgba(255, 255, 255, 0.1);z-index:35;width:fit-content;animation:slideInFromLeft 0.3s ease-out;transition:all 0.3s ease}.quality-text{padding:6px 10px;font-size:11px;color:white;font-family:'Monaco', 'Menlo', 'Consolas', monospace;line-height:1.4}.quality-fail{color:#ff9999}@keyframes pulse{0%,100%{opacity:1}50%{opacity:0.7}}.metric-value.danger{animation:pulse 2s infinite}@media (max-width: 480px){.quality-monitor{top:160px;left:12px}.quality-text{padding:4px 8px;font-size:10px}}.manual-capture-section{position:absolute;bottom:20px;left:50%;transform:translateX(-50%);z-index:25;display:flex;flex-direction:column;align-items:center;width:100%;max-width:300px}.manual-capture-button{pointer-events:auto;background:#fff;color:#333;border:none;border-radius:25px;padding:12px 24px;font-size:14px;font-weight:600;cursor:pointer;transition:all 0.3s ease}.manual-capture-button:hover{background:#f8f9fa}.manual-capture-button:active{background:#e9ecef;transform:translateY(1px)}.manual-capture-button:disabled{cursor:not-allowed !important;transform:none !important;opacity:0.7 !important;transition:none !important}@media (max-width: 480px){.manual-capture-section{bottom:16px;max-width:280px}.manual-capture-button{padding:10px 20px;font-size:13px}.manual-capture-button:disabled{opacity:0.7 !important;cursor:not-allowed !important}}";
|
|
1095
1205
|
|
|
1096
1206
|
const JaakStamps = class {
|
|
1097
1207
|
constructor(hostRef) {
|
|
@@ -1147,6 +1257,10 @@ const JaakStamps = class {
|
|
|
1147
1257
|
};
|
|
1148
1258
|
backDocumentTimerRemaining = 0;
|
|
1149
1259
|
showManualCaptureButton = false;
|
|
1260
|
+
performanceDegradedMode = false; // Auto-switched to manual mode due to performance
|
|
1261
|
+
showPerformanceMessage = false; // Show performance message temporarily
|
|
1262
|
+
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
1263
|
+
processingButton = null; // Track which button is processing
|
|
1150
1264
|
// Services
|
|
1151
1265
|
serviceContainer;
|
|
1152
1266
|
eventBus;
|
|
@@ -1165,12 +1279,12 @@ const JaakStamps = class {
|
|
|
1165
1279
|
alignmentStartTime;
|
|
1166
1280
|
alignmentTimer;
|
|
1167
1281
|
backDocumentTimer;
|
|
1282
|
+
orientationTimer;
|
|
1168
1283
|
// Performance monitoring
|
|
1169
1284
|
performanceMetrics = {
|
|
1170
1285
|
fps: 0,
|
|
1171
1286
|
inferenceTime: 0,
|
|
1172
1287
|
memoryUsage: 0,
|
|
1173
|
-
cpuUsage: 0,
|
|
1174
1288
|
onnxLoadTime: 0,
|
|
1175
1289
|
frameProcessingTime: 0,
|
|
1176
1290
|
totalDetections: 0,
|
|
@@ -1189,18 +1303,29 @@ const JaakStamps = class {
|
|
|
1189
1303
|
MIN_INFERENCE_INTERVAL = 50;
|
|
1190
1304
|
performanceHistory = [];
|
|
1191
1305
|
PERFORMANCE_HISTORY_SIZE = 10;
|
|
1306
|
+
// Sistema simplificado de monitoreo de rendimiento
|
|
1307
|
+
PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
|
|
1308
|
+
SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
|
|
1309
|
+
slowFrameCount = 0; // Contador de frames consecutivos lentos
|
|
1310
|
+
processedFramesCount = 0; // Contador total de frames procesados
|
|
1311
|
+
hasAutoSwitchedToManual = false;
|
|
1192
1312
|
// Canvas pool for optimized screenshot capture
|
|
1193
1313
|
canvasPool = [];
|
|
1194
1314
|
MAX_CANVAS_POOL_SIZE = 3;
|
|
1195
1315
|
async componentDidLoad() {
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1203
|
-
this.
|
|
1316
|
+
try {
|
|
1317
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
1318
|
+
await this.initializeServices();
|
|
1319
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
1320
|
+
await this.setupEventListeners();
|
|
1321
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
1322
|
+
await this.initializeComponent();
|
|
1323
|
+
if (this.debug) {
|
|
1324
|
+
this.initializePerformanceMonitor();
|
|
1325
|
+
}
|
|
1326
|
+
}
|
|
1327
|
+
catch (error) {
|
|
1328
|
+
this.updateStatus('Error de inicialización', error.message, 'error');
|
|
1204
1329
|
}
|
|
1205
1330
|
}
|
|
1206
1331
|
async initializeServices() {
|
|
@@ -1221,14 +1346,8 @@ const JaakStamps = class {
|
|
|
1221
1346
|
this.detectionService = this.serviceContainer.getDetectionService();
|
|
1222
1347
|
}
|
|
1223
1348
|
async setupEventListeners() {
|
|
1224
|
-
this.eventBus.on('state-changed', (
|
|
1225
|
-
this.handleStateChange(
|
|
1226
|
-
});
|
|
1227
|
-
this.eventBus.on('camera-changed', () => {
|
|
1228
|
-
// Camera changed event
|
|
1229
|
-
});
|
|
1230
|
-
this.eventBus.on('error', () => {
|
|
1231
|
-
// Handle service errors
|
|
1349
|
+
this.eventBus.on('state-changed', () => {
|
|
1350
|
+
this.handleStateChange();
|
|
1232
1351
|
});
|
|
1233
1352
|
}
|
|
1234
1353
|
async initializeComponent() {
|
|
@@ -1264,7 +1383,7 @@ const JaakStamps = class {
|
|
|
1264
1383
|
if (!window.ort) {
|
|
1265
1384
|
this.updateStatus('Preparando herramientas...', 'Configurando funciones de captura', 'initializing');
|
|
1266
1385
|
const script = document.createElement('script');
|
|
1267
|
-
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web/dist/ort.min.js';
|
|
1386
|
+
script.src = 'https://cdn.jsdelivr.net/npm/onnxruntime-web@1.23.0/dist/ort.min.js';
|
|
1268
1387
|
document.head.appendChild(script);
|
|
1269
1388
|
await new Promise((resolve) => {
|
|
1270
1389
|
script.onload = () => {
|
|
@@ -1335,7 +1454,8 @@ const JaakStamps = class {
|
|
|
1335
1454
|
return false;
|
|
1336
1455
|
}
|
|
1337
1456
|
}
|
|
1338
|
-
handleStateChange(
|
|
1457
|
+
handleStateChange() {
|
|
1458
|
+
// Handle state changes from StateManager if needed
|
|
1339
1459
|
}
|
|
1340
1460
|
initializeResizeObserver() {
|
|
1341
1461
|
if ('ResizeObserver' in window && this.detectionContainer) {
|
|
@@ -1344,6 +1464,18 @@ const JaakStamps = class {
|
|
|
1344
1464
|
});
|
|
1345
1465
|
resizeObserver.observe(this.detectionContainer.parentElement);
|
|
1346
1466
|
}
|
|
1467
|
+
// Handle mobile device orientation changes
|
|
1468
|
+
window.addEventListener('orientationchange', () => {
|
|
1469
|
+
// Clear any existing orientation timer
|
|
1470
|
+
if (this.orientationTimer) {
|
|
1471
|
+
clearTimeout(this.orientationTimer);
|
|
1472
|
+
}
|
|
1473
|
+
// Add delay to ensure video dimensions are updated after rotation
|
|
1474
|
+
this.orientationTimer = window.setTimeout(() => {
|
|
1475
|
+
this.handleResize();
|
|
1476
|
+
this.orientationTimer = undefined;
|
|
1477
|
+
}, 300);
|
|
1478
|
+
});
|
|
1347
1479
|
}
|
|
1348
1480
|
handleResize() {
|
|
1349
1481
|
if (this.detectionContainer) {
|
|
@@ -1352,10 +1484,17 @@ const JaakStamps = class {
|
|
|
1352
1484
|
this.updateMaskDimensions(rect);
|
|
1353
1485
|
}
|
|
1354
1486
|
}
|
|
1487
|
+
triggerRerender() {
|
|
1488
|
+
this.captureStateVersion = this.captureStateVersion + 1;
|
|
1489
|
+
}
|
|
1355
1490
|
getGuideText(step) {
|
|
1356
1491
|
if (step === 'completed') {
|
|
1357
1492
|
return 'Proceso completado';
|
|
1358
1493
|
}
|
|
1494
|
+
// Check if we should show performance message
|
|
1495
|
+
if (this.showPerformanceMessage && step === 'front') {
|
|
1496
|
+
return 'Modo manual activado por rendimiento. Use el botón para capturar.';
|
|
1497
|
+
}
|
|
1359
1498
|
if (step === 'front') {
|
|
1360
1499
|
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
1361
1500
|
if (this.showManualCaptureButton) {
|
|
@@ -1513,8 +1652,14 @@ const JaakStamps = class {
|
|
|
1513
1652
|
}
|
|
1514
1653
|
}
|
|
1515
1654
|
async skipBackCapture() {
|
|
1655
|
+
console.log('🟡 Botón clicked: Saltar reverso');
|
|
1656
|
+
// Set processing state immediately
|
|
1657
|
+
this.processingButton = 'skip-back';
|
|
1658
|
+
// Add small delay to show spinner
|
|
1659
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1516
1660
|
const readyCheck = this.isComponentReady();
|
|
1517
1661
|
if (!readyCheck.ready) {
|
|
1662
|
+
this.processingButton = null;
|
|
1518
1663
|
return { success: false, error: readyCheck.message };
|
|
1519
1664
|
}
|
|
1520
1665
|
try {
|
|
@@ -1531,6 +1676,10 @@ const JaakStamps = class {
|
|
|
1531
1676
|
catch (error) {
|
|
1532
1677
|
return { success: false, error: error.message };
|
|
1533
1678
|
}
|
|
1679
|
+
finally {
|
|
1680
|
+
// Always clear processing state when done
|
|
1681
|
+
this.processingButton = null;
|
|
1682
|
+
}
|
|
1534
1683
|
}
|
|
1535
1684
|
async getStatus() {
|
|
1536
1685
|
const readyCheck = this.isComponentReady();
|
|
@@ -1585,9 +1734,21 @@ const JaakStamps = class {
|
|
|
1585
1734
|
if (this.stateManager) {
|
|
1586
1735
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1587
1736
|
}
|
|
1588
|
-
|
|
1737
|
+
try {
|
|
1738
|
+
await this.detectionService.loadModel();
|
|
1739
|
+
}
|
|
1740
|
+
catch (modelError) {
|
|
1741
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
1742
|
+
throw modelError;
|
|
1743
|
+
}
|
|
1589
1744
|
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1590
|
-
|
|
1745
|
+
try {
|
|
1746
|
+
await this.detectionService.loadClassificationModel();
|
|
1747
|
+
}
|
|
1748
|
+
catch (classificationError) {
|
|
1749
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
1750
|
+
throw classificationError;
|
|
1751
|
+
}
|
|
1591
1752
|
const loadEndTime = performance.now();
|
|
1592
1753
|
const loadTime = loadEndTime - loadStartTime;
|
|
1593
1754
|
// Record ONNX load time
|
|
@@ -1696,17 +1857,63 @@ const JaakStamps = class {
|
|
|
1696
1857
|
// Always allow getting capture delay, even during initialization
|
|
1697
1858
|
return this.captureDelay;
|
|
1698
1859
|
}
|
|
1860
|
+
// Memory and device capability detection
|
|
1861
|
+
checkDeviceCapabilities() {
|
|
1862
|
+
const deviceMemory = navigator.deviceMemory;
|
|
1863
|
+
// Siempre intentar usar ML, independientemente del dispositivo
|
|
1864
|
+
// Las restricciones anteriores no eran confiables
|
|
1865
|
+
return {
|
|
1866
|
+
canUseML: true,
|
|
1867
|
+
reason: 'Dispositivo compatible con detección automática.',
|
|
1868
|
+
memoryMB: deviceMemory ? deviceMemory * 1024 : null
|
|
1869
|
+
};
|
|
1870
|
+
}
|
|
1699
1871
|
// DETECTION METHODS
|
|
1700
1872
|
async startDetection() {
|
|
1701
1873
|
try {
|
|
1874
|
+
// Verificar capacidades del dispositivo antes de cargar modelos
|
|
1875
|
+
const capabilities = this.checkDeviceCapabilities();
|
|
1876
|
+
// Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
|
|
1877
|
+
if (!capabilities.canUseML && this.useDocumentDetector) {
|
|
1878
|
+
this.switchToManualModeWithWarning(capabilities.reason);
|
|
1879
|
+
return;
|
|
1880
|
+
}
|
|
1702
1881
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
1703
1882
|
if (!this.detectionService.isModelLoaded()) {
|
|
1704
1883
|
const loadStartTime = performance.now();
|
|
1705
1884
|
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
1706
1885
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1707
|
-
|
|
1886
|
+
try {
|
|
1887
|
+
await this.detectionService.loadModel();
|
|
1888
|
+
}
|
|
1889
|
+
catch (modelError) {
|
|
1890
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
1891
|
+
if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
|
|
1892
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
1893
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
|
|
1894
|
+
return;
|
|
1895
|
+
}
|
|
1896
|
+
else {
|
|
1897
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
1898
|
+
throw modelError;
|
|
1899
|
+
}
|
|
1900
|
+
}
|
|
1708
1901
|
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1709
|
-
|
|
1902
|
+
try {
|
|
1903
|
+
await this.detectionService.loadClassificationModel();
|
|
1904
|
+
}
|
|
1905
|
+
catch (classificationError) {
|
|
1906
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
1907
|
+
if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
|
|
1908
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
1909
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
|
|
1910
|
+
return;
|
|
1911
|
+
}
|
|
1912
|
+
else {
|
|
1913
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
1914
|
+
throw classificationError;
|
|
1915
|
+
}
|
|
1916
|
+
}
|
|
1710
1917
|
const loadEndTime = performance.now();
|
|
1711
1918
|
const loadTime = loadEndTime - loadStartTime;
|
|
1712
1919
|
// Record ONNX load time
|
|
@@ -1716,19 +1923,40 @@ const JaakStamps = class {
|
|
|
1716
1923
|
}
|
|
1717
1924
|
// Paso 2: Detectar y configurar dispositivos
|
|
1718
1925
|
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
1719
|
-
|
|
1720
|
-
|
|
1926
|
+
try {
|
|
1927
|
+
await this.cameraService.enumerateDevices();
|
|
1928
|
+
}
|
|
1929
|
+
catch (enumerateError) {
|
|
1930
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
1931
|
+
}
|
|
1932
|
+
try {
|
|
1933
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1934
|
+
}
|
|
1935
|
+
catch (cameraInfoError) {
|
|
1936
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
1937
|
+
}
|
|
1721
1938
|
// Paso 3: Configurar cámara seleccionada
|
|
1722
1939
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1723
|
-
|
|
1724
|
-
|
|
1940
|
+
let stream;
|
|
1941
|
+
try {
|
|
1942
|
+
stream = await this.cameraService.setupCamera();
|
|
1943
|
+
}
|
|
1944
|
+
catch (setupError) {
|
|
1945
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
1946
|
+
}
|
|
1947
|
+
// Paso 4: Inicializar video y mostrar estado de análisis inicial
|
|
1725
1948
|
if (this.useDocumentDetector) {
|
|
1726
|
-
this.updateStatus('
|
|
1949
|
+
this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
|
|
1727
1950
|
}
|
|
1728
1951
|
else {
|
|
1729
1952
|
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1730
1953
|
}
|
|
1731
|
-
|
|
1954
|
+
try {
|
|
1955
|
+
await this.initializeVideoStream(stream);
|
|
1956
|
+
}
|
|
1957
|
+
catch (videoError) {
|
|
1958
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
1959
|
+
}
|
|
1732
1960
|
// Paso 5: Calibrar máscara
|
|
1733
1961
|
if (this.detectionContainer) {
|
|
1734
1962
|
const container = this.detectionContainer.parentElement;
|
|
@@ -1759,6 +1987,12 @@ const JaakStamps = class {
|
|
|
1759
1987
|
this.videoRef.onloadedmetadata = async () => {
|
|
1760
1988
|
await this.videoRef.play();
|
|
1761
1989
|
this.stateManager.updateCaptureState({ isVideoActive: true });
|
|
1990
|
+
// Recalculate mask dimensions when new camera stream loads
|
|
1991
|
+
if (this.detectionContainer) {
|
|
1992
|
+
const container = this.detectionContainer.parentElement;
|
|
1993
|
+
const rect = container.getBoundingClientRect();
|
|
1994
|
+
this.updateMaskDimensions(rect);
|
|
1995
|
+
}
|
|
1762
1996
|
resolve();
|
|
1763
1997
|
};
|
|
1764
1998
|
});
|
|
@@ -1773,8 +2007,8 @@ const JaakStamps = class {
|
|
|
1773
2007
|
const captureState = this.stateManager.getCaptureState();
|
|
1774
2008
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
1775
2009
|
return;
|
|
1776
|
-
// Show manual capture button when document detector is disabled
|
|
1777
|
-
if (!this.useDocumentDetector) {
|
|
2010
|
+
// Show manual capture button when document detector is disabled or performance is degraded
|
|
2011
|
+
if (!this.useDocumentDetector || this.performanceDegradedMode) {
|
|
1778
2012
|
this.showManualCaptureButton = true;
|
|
1779
2013
|
// Continue the loop for UI updates but skip detection logic
|
|
1780
2014
|
if (captureState.step !== 'completed') {
|
|
@@ -1782,7 +2016,7 @@ const JaakStamps = class {
|
|
|
1782
2016
|
}
|
|
1783
2017
|
return;
|
|
1784
2018
|
}
|
|
1785
|
-
else {
|
|
2019
|
+
else if (!this.performanceDegradedMode) {
|
|
1786
2020
|
this.showManualCaptureButton = false;
|
|
1787
2021
|
}
|
|
1788
2022
|
if (captureState.isDetectionPaused) {
|
|
@@ -1812,10 +2046,27 @@ const JaakStamps = class {
|
|
|
1812
2046
|
this.lastInferenceTime = currentTime;
|
|
1813
2047
|
// Measure preprocessing and inference time
|
|
1814
2048
|
const inferenceStartTime = performance.now();
|
|
1815
|
-
|
|
1816
|
-
|
|
1817
|
-
|
|
1818
|
-
|
|
2049
|
+
let inputTensor;
|
|
2050
|
+
let detections;
|
|
2051
|
+
let inferenceTime;
|
|
2052
|
+
try {
|
|
2053
|
+
inputTensor = this.detectionService.preprocess(this.videoRef);
|
|
2054
|
+
}
|
|
2055
|
+
catch (preprocessError) {
|
|
2056
|
+
this.switchToManualModeWithError('Error al procesar imagen');
|
|
2057
|
+
return;
|
|
2058
|
+
}
|
|
2059
|
+
try {
|
|
2060
|
+
// Standard detection without timeout (timeout is handled by frame counting)
|
|
2061
|
+
detections = await this.detectionService.runInference(inputTensor);
|
|
2062
|
+
inferenceTime = performance.now() - inferenceStartTime;
|
|
2063
|
+
}
|
|
2064
|
+
catch (inferenceError) {
|
|
2065
|
+
this.switchToManualModeWithError('Error en detección automática');
|
|
2066
|
+
return;
|
|
2067
|
+
}
|
|
2068
|
+
// Increment frame count
|
|
2069
|
+
this.processedFramesCount++;
|
|
1819
2070
|
// Record ONNX performance metrics
|
|
1820
2071
|
if (this.debug) {
|
|
1821
2072
|
this.recordOnnxPerformance(0, inferenceTime);
|
|
@@ -1854,9 +2105,30 @@ const JaakStamps = class {
|
|
|
1854
2105
|
}
|
|
1855
2106
|
this.updateMaskColor(detections);
|
|
1856
2107
|
// Record frame processing metrics
|
|
2108
|
+
const frameEndTime = performance.now();
|
|
2109
|
+
const totalFrameTime = frameEndTime - frameStartTime;
|
|
2110
|
+
// Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
|
|
2111
|
+
if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
|
|
2112
|
+
// Verificar si el frame es lento
|
|
2113
|
+
if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
|
|
2114
|
+
this.slowFrameCount++;
|
|
2115
|
+
// Si tenemos suficientes frames lentos consecutivos, cambiar a manual
|
|
2116
|
+
if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
|
|
2117
|
+
this.switchToManualMode();
|
|
2118
|
+
}
|
|
2119
|
+
}
|
|
2120
|
+
else {
|
|
2121
|
+
// Frame rápido, reiniciar contador
|
|
2122
|
+
if (this.slowFrameCount > 0) {
|
|
2123
|
+
this.slowFrameCount = 0;
|
|
2124
|
+
}
|
|
2125
|
+
}
|
|
2126
|
+
// Cambiar mensaje de estado después de analizar los primeros 2 frames
|
|
2127
|
+
if (this.processedFramesCount === 2) {
|
|
2128
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
2129
|
+
}
|
|
2130
|
+
}
|
|
1857
2131
|
if (this.debug) {
|
|
1858
|
-
const frameEndTime = performance.now();
|
|
1859
|
-
const totalFrameTime = frameEndTime - frameStartTime;
|
|
1860
2132
|
this.recordFrameProcessing(totalFrameTime, detections.length);
|
|
1861
2133
|
}
|
|
1862
2134
|
// Continue detection loop
|
|
@@ -1892,6 +2164,12 @@ const JaakStamps = class {
|
|
|
1892
2164
|
clearTimeout(this.alignmentTimer);
|
|
1893
2165
|
this.alignmentTimer = undefined;
|
|
1894
2166
|
}
|
|
2167
|
+
if (this.orientationTimer) {
|
|
2168
|
+
clearTimeout(this.orientationTimer);
|
|
2169
|
+
this.orientationTimer = undefined;
|
|
2170
|
+
}
|
|
2171
|
+
// Liberar recursos de ONNX al limpiar el componente
|
|
2172
|
+
this.releaseOnnxResources();
|
|
1895
2173
|
if (this.performanceUpdateInterval) {
|
|
1896
2174
|
clearInterval(this.performanceUpdateInterval);
|
|
1897
2175
|
this.performanceUpdateInterval = undefined;
|
|
@@ -1912,7 +2190,7 @@ const JaakStamps = class {
|
|
|
1912
2190
|
isCapturing: false
|
|
1913
2191
|
};
|
|
1914
2192
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1915
|
-
return (index.h("div", { key: '
|
|
2193
|
+
return (index.h("div", { key: 'f9cbca2fd6b8548a29ace7fcb972b53f402f61af', class: "detector-container" }, index.h("div", { key: '4f3cac734eb6bfd44a8de644eadca17e7893e11a', class: "video-container" }, index.h("video", { key: '5e15a38c989a72d5274281272d41ba064447ca6f', ref: el => this.videoRef = el, autoplay: true, muted: true, playsinline: true, class: this.shouldMirrorVideo ? 'mirror' : '', style: { display: captureState.isVideoActive ? 'block' : 'none' } }), index.h("div", { key: '38c8ad8b691db60e9b5a2a8b641fe6febbeacfca', ref: el => this.detectionContainer = el, class: `detection-overlay ${this.shouldMirrorVideo ? 'mirror' : ''}` }, this.debug && this.detectionBoxes.map((box, index$1) => (index.h("div", { key: index$1, class: "detection-box", style: {
|
|
1916
2194
|
position: 'absolute',
|
|
1917
2195
|
left: `${box.x}px`,
|
|
1918
2196
|
top: `${box.y}px`,
|
|
@@ -1921,9 +2199,9 @@ const JaakStamps = class {
|
|
|
1921
2199
|
border: '2px solid #32406C',
|
|
1922
2200
|
pointerEvents: 'none',
|
|
1923
2201
|
boxSizing: 'border-box'
|
|
1924
|
-
} })))), this.isMaskReady && (index.h("div", { key: '
|
|
2202
|
+
} })))), this.isMaskReady && (index.h("div", { key: 'c56fde56b7a86a4e8a4cf558745291ea5cde12fd', class: "overlay-mask" }, index.h("div", { key: '04ed3f01f271dc84eed594cb4b60c2453645bd41', class: "card-outline" }, index.h("div", { key: '648bd423a5e886d4768115237de5896c7223b30b', class: "side side-top" }), index.h("div", { key: 'b8878059493ca61961b9b3c82ecc18b50b79ac85', class: "side side-right" }), index.h("div", { key: '09e5ad64ff33dbd5c112bf52b71e14409ab6aa65', class: "side side-bottom" }), index.h("div", { key: 'b8ba3d83c9bc1b4a0da859d7ed4b9a15eaddcc2a', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '3279701a240f77ac96e68a34ad95c099340c9a6a', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '2adf96d0a57e482372fd9586229dac2301b64fd4', class: "back-capture-section" }, index.h("div", { key: '305a147c9cfee854c60ac946a48a1d21b2792725', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode || this.showManualCaptureButton) && (index.h("button", { key: '43270a0647f39307229c540dd5130fb58b1ff4fb', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: 'eb4666603774564eef95a120ac13faeb9e10c8b4', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: 'cac667d9548ae2314ad5a677c5ed90f25184f728', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '0af7aa4106ce189ae2d79bec78ede01abe25b1e7', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1925
2203
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
1926
|
-
: 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: '
|
|
2204
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'de6e5c4fad4d4d996b894457c9af52bc71e533fb', class: "camera-controls" }, index.h("button", { key: 'cfddcf2420d5f5923a6b02ca446bbe1b8e9fc5e4', class: `camera-selector-button ${this.isSwitchingCamera ? 'loading' : ''}`, onClick: () => this.toggleCameraSelector(), type: "button", title: "Seleccionar c\u00E1mara", disabled: this.isSwitchingCamera }, this.isSwitchingCamera ? (index.h("div", { class: "button-spinner" })) : ('Cámaras')))), this.showCameraSelector && cameraInfo.availableCameras.length > 0 && (index.h("div", { key: '5fb73f958b9a5a6f9242032c4530b0bdab84f6c5', class: "camera-selector-dropdown" }, index.h("div", { key: '8ee2d26b1001e571e7b6c326e927926fa08287fd', class: "camera-selector-header" }, index.h("span", { key: '78350b0fb1bd933f7c8c59d8dab0b7122ffbe363' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'bb750e0ddd5a1ee5ff96bd57805a7f90b8821e9f', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: 'd0f6343cf1b7367f3da33ec8cf64891109d923b8', class: "camera-list" }, cameraInfo.availableCameras.map((camera) => (index.h("button", { key: camera.id, class: `camera-option ${cameraInfo.selectedCameraId === camera.id ? 'selected' : ''}`, onClick: () => this.handleCameraSwitch(camera.id), type: "button" }, index.h("span", { class: "camera-label" }, camera.label || `Cámara ${cameraInfo.availableCameras.indexOf(camera) + 1}`, camera.hasAutofocus && (index.h("span", { class: "autofocus-icon", title: "Autofoco disponible" }, "\u29BF"))), cameraInfo.selectedCameraId === camera.id && (index.h("span", { class: "selected-indicator" }, "\u2713")))))), index.h("div", { key: 'c9642fbfb0acea766d3f0542f4cc9f17f4af4d7b', class: "device-info" }, index.h("small", { key: 'b6e9507ff7addf011f339f6c22a88a96b40db68a' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '04ae9262409284782850b90945eb7038673e2097', class: "manual-capture-section" }, index.h("button", { key: '27c315e503ccba468b89ebd58d6b70f40ddd06ba', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '8f2dcd2df9a218b37eb0b8204a8724362cfeaa53', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: '6fd6fe1d0213b01c8119a67fb8108f73227feee1', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: 'fba3816b48b99ca64510ecbefc0d41431569c9ae', class: "flip-animation" }, index.h("div", { key: 'beea1c8245b5d81f1008efc6d1b079a61c821e7f', class: "id-card-icon" }), index.h("div", { key: 'f0fbc34f619a653e2c0ade764ca99e7aa582d157', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: 'e4a0e6d31112e3a50024d1d0430b4ad997049fe7', class: "success-animation" }, index.h("div", { key: '5dd3f781f55630bc904c81207548ae35bc79b452', class: "check-icon" }), index.h("div", { key: 'a10ebc557582325091efb0abd12d62b27a43b98c', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '975865955dee1803b5215ecf049701c98b324855', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '84f6f67202e26ce80f7fc5d5f321e23b5bc55c57', class: "status-spinner" })), index.h("div", { key: 'a1ae56ed156e8feb279a1bf160f749c5d0b5ce45', class: "status-content" }, index.h("div", { key: 'd62932554cea257ce19763df26e0ba702e50eeca', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'c5506fa12dd0d37d26dc26c9f2381c843330451f', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: '2097eda63185f336f7319014fae2ef6db1e41761', class: "performance-monitor" }, index.h("div", { key: 'd611c494c6b1a5930ef64aebdcba41e5d94c9ee6', class: "performance-expanded" }, index.h("div", { key: '800d46831e7b9bec260d9c5e67ea1a3eb9c2933e', class: "metrics-row" }, index.h("div", { key: 'dd0db1faa9050d98e71da4b5cd26a4a484c37c64', class: "metric-compact" }, index.h("span", { key: 'f0e6ea61a35ddb2ae5c2a6aff8c255a02c5c1219', class: "metric-label" }, "FPS"), index.h("span", { key: 'cbf4df4d142f6f01e45f566a8ede8fbbf29ad673', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: '83861ad31fa46c8199a5583e4b3f8eefd44bacd6', class: "metric-compact" }, index.h("span", { key: '41b8de4cda47959dfc9df393e95fdadde534dd21', class: "metric-label" }, "MEM"), index.h("span", { key: 'b268f13cc78066e240ed158f1443113a94827aff', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: '9f9a9d0690906b186492af23b900c5fa0f5cff89', class: "metrics-row" }, index.h("div", { key: '84fa6e453dc36e4a74e8b573447b17230766229e', class: "metric-compact" }, index.h("span", { key: 'f17d52899090c89f532d74c552dd8c5338fc2d83', class: "metric-label" }, "INF"), index.h("span", { key: '2beca0d7ce55ed574f6b01efc086c5846983e024', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '981e300cf076e81bfe946974f6cffd63619eacde', class: "metric-compact" }, index.h("span", { key: '3dfb0c996a69eb87b0f711322675cad69c471146', class: "metric-label" }, "FRAME"), index.h("span", { key: '3ae2ac6e90cda77f23e559f3197c3b761f9bdc40', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '2c51216838d87728d9d7764e5ba8a50fa02eb493', class: "metrics-row" }, index.h("div", { key: 'eda810ffb80ec38ab84583f59b26706f90a50fc9', class: "metric-compact" }, index.h("span", { key: '978ba9c0c67d6da6b29b3535af33a314bab9276a', class: "metric-label" }, "DET"), index.h("span", { key: '3e5e169fd4a3906283d8f918b5bbc7c5df6ccd0f', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'b4b915c745e35c09470a4dc5edfc112d32cfb8b6', class: "metric-compact" }, index.h("span", { key: '3e2a7b88a4446813c955efb0dfa9e2958e8e1448', class: "metric-label" }, "RATE"), index.h("span", { key: 'f4f073f7ee622167a241c6a1c535ef580c13d9ab', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '76c556e92f6d7980539f1a30efe8751f665164fb', class: "watermark" }, index.h("img", { key: 'af23e115d6b4e92a72e55d6d98fde0e4b1d09e9b', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
1927
2205
|
}
|
|
1928
2206
|
// Utility methods
|
|
1929
2207
|
updateDetectionBoxes(boxes) {
|
|
@@ -2039,9 +2317,24 @@ const JaakStamps = class {
|
|
|
2039
2317
|
}
|
|
2040
2318
|
}
|
|
2041
2319
|
async takeManualScreenshot() {
|
|
2042
|
-
|
|
2320
|
+
console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
|
|
2321
|
+
// Set processing state immediately
|
|
2322
|
+
const captureState = this.stateManager?.getCaptureState();
|
|
2323
|
+
if (captureState?.step === 'front') {
|
|
2324
|
+
this.processingButton = 'capture-front';
|
|
2325
|
+
}
|
|
2326
|
+
else if (captureState?.step === 'back') {
|
|
2327
|
+
this.processingButton = 'capture-back';
|
|
2328
|
+
}
|
|
2329
|
+
console.log('🔵 processingButton set to:', this.processingButton);
|
|
2330
|
+
// Add small delay to show spinner
|
|
2331
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2332
|
+
if (!this.videoRef) {
|
|
2333
|
+
this.processingButton = null;
|
|
2043
2334
|
return;
|
|
2335
|
+
}
|
|
2044
2336
|
// When using manual capture, use mask coordinates for cropping
|
|
2337
|
+
// Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
|
|
2045
2338
|
await this.takeScreenshotWithMaskCoordinates();
|
|
2046
2339
|
}
|
|
2047
2340
|
async takeScreenshotWithMaskCoordinates() {
|
|
@@ -2105,18 +2398,25 @@ const JaakStamps = class {
|
|
|
2105
2398
|
if (captureState.step === 'front') {
|
|
2106
2399
|
this.stateManager.setCapturedImages({
|
|
2107
2400
|
front: {
|
|
2108
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
2109
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
2401
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
2402
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
2110
2403
|
}
|
|
2111
2404
|
});
|
|
2112
2405
|
// Check if document classification is enabled (independent of detector)
|
|
2113
2406
|
if (this.useDocumentClassification) {
|
|
2114
|
-
|
|
2115
|
-
|
|
2116
|
-
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2407
|
+
try {
|
|
2408
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2409
|
+
if (classification && classification.class === 'passport') {
|
|
2410
|
+
this.completeProcess(true);
|
|
2411
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2412
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2413
|
+
// Clear processing button when passport is detected
|
|
2414
|
+
this.processingButton = null;
|
|
2415
|
+
return;
|
|
2416
|
+
}
|
|
2417
|
+
}
|
|
2418
|
+
catch (classifyError) {
|
|
2419
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
2120
2420
|
}
|
|
2121
2421
|
}
|
|
2122
2422
|
this.stateManager.updateCaptureState({
|
|
@@ -2124,22 +2424,28 @@ const JaakStamps = class {
|
|
|
2124
2424
|
isDetectionPaused: true,
|
|
2125
2425
|
showFlipAnimation: true
|
|
2126
2426
|
});
|
|
2427
|
+
this.triggerRerender();
|
|
2127
2428
|
setTimeout(() => {
|
|
2128
2429
|
this.stateManager.updateCaptureState({
|
|
2129
2430
|
showFlipAnimation: false,
|
|
2130
2431
|
isDetectionPaused: false
|
|
2131
2432
|
});
|
|
2433
|
+
this.triggerRerender();
|
|
2132
2434
|
this.startBackDocumentTimer();
|
|
2435
|
+
// Clear processing button after animation completes
|
|
2436
|
+
this.processingButton = null;
|
|
2133
2437
|
}, 3000);
|
|
2134
2438
|
}
|
|
2135
2439
|
else if (captureState.step === 'back') {
|
|
2136
2440
|
this.stateManager.setCapturedImages({
|
|
2137
2441
|
back: {
|
|
2138
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
2139
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
2442
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
2443
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
2140
2444
|
}
|
|
2141
2445
|
});
|
|
2142
2446
|
this.completeProcess(false);
|
|
2447
|
+
// Clear processing button after back capture completes
|
|
2448
|
+
this.processingButton = null;
|
|
2143
2449
|
}
|
|
2144
2450
|
// Return canvases to pool after use
|
|
2145
2451
|
this.returnCanvasToPool(captureCanvas);
|
|
@@ -2172,16 +2478,21 @@ const JaakStamps = class {
|
|
|
2172
2478
|
if (captureState.step === 'front') {
|
|
2173
2479
|
this.stateManager.setCapturedImages({
|
|
2174
2480
|
front: {
|
|
2175
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
2176
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
2481
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
2482
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
2177
2483
|
}
|
|
2178
2484
|
});
|
|
2179
2485
|
// Check if document classification is enabled
|
|
2180
2486
|
if (this.useDocumentClassification) {
|
|
2181
|
-
|
|
2182
|
-
|
|
2183
|
-
|
|
2184
|
-
|
|
2487
|
+
try {
|
|
2488
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2489
|
+
if (classification && classification.class === 'passport') {
|
|
2490
|
+
this.completeProcess(true);
|
|
2491
|
+
return;
|
|
2492
|
+
}
|
|
2493
|
+
}
|
|
2494
|
+
catch (classifyError) {
|
|
2495
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
2185
2496
|
}
|
|
2186
2497
|
}
|
|
2187
2498
|
this.stateManager.updateCaptureState({
|
|
@@ -2200,8 +2511,8 @@ const JaakStamps = class {
|
|
|
2200
2511
|
else if (captureState.step === 'back') {
|
|
2201
2512
|
this.stateManager.setCapturedImages({
|
|
2202
2513
|
back: {
|
|
2203
|
-
fullFrame: captureCanvas.toDataURL('image/
|
|
2204
|
-
cropped: croppedCanvas.toDataURL('image/
|
|
2514
|
+
fullFrame: captureCanvas.toDataURL('image/jpeg'),
|
|
2515
|
+
cropped: croppedCanvas.toDataURL('image/jpeg')
|
|
2205
2516
|
}
|
|
2206
2517
|
});
|
|
2207
2518
|
this.completeProcess(false);
|
|
@@ -2218,6 +2529,138 @@ const JaakStamps = class {
|
|
|
2218
2529
|
cardOutline?.classList.remove('capturing');
|
|
2219
2530
|
}, 600);
|
|
2220
2531
|
}
|
|
2532
|
+
// Método simplificado para cambio a modo manual
|
|
2533
|
+
switchToManualMode() {
|
|
2534
|
+
// Only switch if detector is enabled and not already switched
|
|
2535
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2536
|
+
return;
|
|
2537
|
+
}
|
|
2538
|
+
// Detener todo el monitoreo de performance
|
|
2539
|
+
this.stopPerformanceMonitoring();
|
|
2540
|
+
// Liberar recursos de ONNX
|
|
2541
|
+
this.releaseOnnxResources();
|
|
2542
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2543
|
+
this.resetMaskToWhite();
|
|
2544
|
+
this.detectionBoxes = [];
|
|
2545
|
+
this.hasAutoSwitchedToManual = true;
|
|
2546
|
+
this.performanceDegradedMode = true;
|
|
2547
|
+
this.showManualCaptureButton = true;
|
|
2548
|
+
this.showPerformanceMessage = true;
|
|
2549
|
+
// Hide performance message after 5 seconds
|
|
2550
|
+
setTimeout(() => {
|
|
2551
|
+
this.showPerformanceMessage = false;
|
|
2552
|
+
}, 5000);
|
|
2553
|
+
}
|
|
2554
|
+
// Método para cambiar a modo manual por errores de ONNX
|
|
2555
|
+
switchToManualModeWithWarning(warningMessage) {
|
|
2556
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2557
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2558
|
+
return;
|
|
2559
|
+
}
|
|
2560
|
+
// No necesitamos liberar recursos ONNX porque no se han cargado aún
|
|
2561
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2562
|
+
this.resetMaskToWhite();
|
|
2563
|
+
this.detectionBoxes = [];
|
|
2564
|
+
// Cambiar a modo manual
|
|
2565
|
+
this.hasAutoSwitchedToManual = true;
|
|
2566
|
+
this.performanceDegradedMode = true;
|
|
2567
|
+
this.showManualCaptureButton = true;
|
|
2568
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
2569
|
+
// Actualizar el estado con mensaje informativo (no error)
|
|
2570
|
+
this.updateStatus('Modo manual activado', warningMessage, 'active');
|
|
2571
|
+
// Continuar con la configuración de cámara
|
|
2572
|
+
this.continueWithCameraSetup();
|
|
2573
|
+
}
|
|
2574
|
+
async continueWithCameraSetup() {
|
|
2575
|
+
try {
|
|
2576
|
+
// Paso 2: Detectar y configurar dispositivos
|
|
2577
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
2578
|
+
try {
|
|
2579
|
+
await this.cameraService.enumerateDevices();
|
|
2580
|
+
}
|
|
2581
|
+
catch (enumerateError) {
|
|
2582
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
2583
|
+
}
|
|
2584
|
+
try {
|
|
2585
|
+
await this.updateCameraInfoWithAutofocus();
|
|
2586
|
+
}
|
|
2587
|
+
catch (cameraInfoError) {
|
|
2588
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
2589
|
+
}
|
|
2590
|
+
// Paso 3: Configurar cámara seleccionada
|
|
2591
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
2592
|
+
let stream;
|
|
2593
|
+
try {
|
|
2594
|
+
stream = await this.cameraService.setupCamera();
|
|
2595
|
+
}
|
|
2596
|
+
catch (setupError) {
|
|
2597
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
2598
|
+
}
|
|
2599
|
+
// Paso 4: Inicializar video
|
|
2600
|
+
this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
|
|
2601
|
+
try {
|
|
2602
|
+
await this.initializeVideoStream(stream);
|
|
2603
|
+
}
|
|
2604
|
+
catch (videoError) {
|
|
2605
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
2606
|
+
}
|
|
2607
|
+
// Paso 5: Calibrar máscara
|
|
2608
|
+
if (this.detectionContainer) {
|
|
2609
|
+
const container = this.detectionContainer.parentElement;
|
|
2610
|
+
const rect = container.getBoundingClientRect();
|
|
2611
|
+
this.updateMaskDimensions(rect);
|
|
2612
|
+
}
|
|
2613
|
+
// Finalizar
|
|
2614
|
+
this.startTime = Date.now();
|
|
2615
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
2616
|
+
// Mostrar botón manual (ya debe estar habilitado)
|
|
2617
|
+
this.showManualCaptureButton = true;
|
|
2618
|
+
}
|
|
2619
|
+
catch (error) {
|
|
2620
|
+
this.updateStatus('Error de cámara', error.message, 'error');
|
|
2621
|
+
throw error;
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2624
|
+
switchToManualModeWithError(errorMessage) {
|
|
2625
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2626
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2627
|
+
return;
|
|
2628
|
+
}
|
|
2629
|
+
// Liberar recursos de ONNX inmediatamente
|
|
2630
|
+
this.releaseOnnxResources();
|
|
2631
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2632
|
+
this.resetMaskToWhite();
|
|
2633
|
+
this.detectionBoxes = [];
|
|
2634
|
+
// Cambiar a modo manual
|
|
2635
|
+
this.hasAutoSwitchedToManual = true;
|
|
2636
|
+
this.performanceDegradedMode = true;
|
|
2637
|
+
this.showManualCaptureButton = true;
|
|
2638
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
2639
|
+
// Actualizar el estado con mensaje de error
|
|
2640
|
+
this.updateStatus('Modo manual activado', errorMessage, 'error');
|
|
2641
|
+
// Continuar con la configuración de cámara para mostrar el video
|
|
2642
|
+
this.continueWithCameraSetup().catch(() => {
|
|
2643
|
+
// Si falla la configuración de cámara, mantener el mensaje de error pero cambiar el estado
|
|
2644
|
+
setTimeout(() => {
|
|
2645
|
+
this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
|
|
2646
|
+
}, 5000);
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
stopPerformanceMonitoring() {
|
|
2650
|
+
// Nota: No necesitamos limpiar más variables porque ya no se usarán
|
|
2651
|
+
// El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
|
|
2652
|
+
}
|
|
2653
|
+
// Método para liberar recursos de ONNX cuando se cambia a modo manual
|
|
2654
|
+
releaseOnnxResources() {
|
|
2655
|
+
try {
|
|
2656
|
+
// Liberar recursos del servicio de detección
|
|
2657
|
+
if (this.detectionService) {
|
|
2658
|
+
this.detectionService.cleanup();
|
|
2659
|
+
}
|
|
2660
|
+
}
|
|
2661
|
+
catch (error) {
|
|
2662
|
+
}
|
|
2663
|
+
}
|
|
2221
2664
|
completeProcess(skippedBack = false) {
|
|
2222
2665
|
this.stateManager.updateCaptureState({
|
|
2223
2666
|
step: 'completed',
|
|
@@ -2288,6 +2731,12 @@ const JaakStamps = class {
|
|
|
2288
2731
|
try {
|
|
2289
2732
|
// Close the selector immediately when user selects a camera
|
|
2290
2733
|
this.showCameraSelector = false;
|
|
2734
|
+
// Check if user selected the same camera that's already active
|
|
2735
|
+
const currentCameraId = this.cameraService.getSelectedCameraId();
|
|
2736
|
+
if (currentCameraId === cameraId) {
|
|
2737
|
+
// Same camera selected, just close the selector without switching
|
|
2738
|
+
return;
|
|
2739
|
+
}
|
|
2291
2740
|
this.isSwitchingCamera = true;
|
|
2292
2741
|
// Stop current video stream
|
|
2293
2742
|
if (this.videoStream) {
|
|
@@ -2331,6 +2780,13 @@ const JaakStamps = class {
|
|
|
2331
2780
|
this.detectionBoxes = [];
|
|
2332
2781
|
this.alignmentStartTime = undefined;
|
|
2333
2782
|
this.hasDocumentDetected = false;
|
|
2783
|
+
// Reset sistema simplificado
|
|
2784
|
+
this.processedFramesCount = 0;
|
|
2785
|
+
this.slowFrameCount = 0;
|
|
2786
|
+
// Reset performance degradation state
|
|
2787
|
+
this.hasAutoSwitchedToManual = false;
|
|
2788
|
+
this.performanceDegradedMode = false;
|
|
2789
|
+
this.showPerformanceMessage = false;
|
|
2334
2790
|
if (this.alignmentTimer) {
|
|
2335
2791
|
clearTimeout(this.alignmentTimer);
|
|
2336
2792
|
this.alignmentTimer = undefined;
|
|
@@ -2434,6 +2890,24 @@ const JaakStamps = class {
|
|
|
2434
2890
|
// Performance is good, use base frame skip
|
|
2435
2891
|
return this.BASE_FRAME_SKIP;
|
|
2436
2892
|
}
|
|
2893
|
+
// Método para resetear la máscara a color blanco
|
|
2894
|
+
resetMaskToWhite() {
|
|
2895
|
+
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
2896
|
+
const corners = this.el.shadowRoot?.querySelectorAll('.corner');
|
|
2897
|
+
const topSide = this.el.shadowRoot?.querySelector('.side-top');
|
|
2898
|
+
const rightSide = this.el.shadowRoot?.querySelector('.side-right');
|
|
2899
|
+
const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
|
|
2900
|
+
const leftSide = this.el.shadowRoot?.querySelector('.side-left');
|
|
2901
|
+
// Remover todas las clases de alineación y estado perfecto
|
|
2902
|
+
cardOutline?.classList.remove('perfect-match');
|
|
2903
|
+
corners?.forEach(corner => corner.classList.remove('perfect-match'));
|
|
2904
|
+
topSide?.classList.remove('aligned');
|
|
2905
|
+
rightSide?.classList.remove('aligned');
|
|
2906
|
+
bottomSide?.classList.remove('aligned');
|
|
2907
|
+
leftSide?.classList.remove('aligned');
|
|
2908
|
+
// Resetear estado de alineación
|
|
2909
|
+
this.sideAlignment = { top: false, right: false, bottom: false, left: false };
|
|
2910
|
+
}
|
|
2437
2911
|
// Canvas pool management for screenshots
|
|
2438
2912
|
getPooledCanvas(width, height) {
|
|
2439
2913
|
// Try to find a canvas with matching dimensions
|