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