@jaak.ai/stamps 2.0.0-dev.58 → 2.1.0-beta.1
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/README.md +11 -1
- 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 +644 -60
- 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 +211 -12
- package/dist/collection/components/my-component/my-component.js +618 -58
- package/dist/collection/components/my-component/my-component.js.map +1 -1
- package/dist/collection/services/DetectionService.js +48 -5
- package/dist/collection/services/DetectionService.js.map +1 -1
- package/dist/collection/services/ServiceContainer.js +3 -2
- package/dist/collection/services/ServiceContainer.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 +650 -60
- 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 +644 -60
- 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-a76ae84d.entry.js +2 -0
- package/dist/jaak-stamps-webcomponent/p-a76ae84d.entry.js.map +1 -0
- package/dist/types/components/my-component/my-component.d.ts +25 -0
- package/dist/types/components.d.ts +8 -0
- package/dist/types/services/DetectionService.d.ts +2 -1
- package/dist/types/services/ServiceContainer.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-39560f23.entry.js +0 -2
- package/dist/jaak-stamps-webcomponent/p-39560f23.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();
|
|
@@ -632,6 +632,7 @@ class DeviceStrategyFactory {
|
|
|
632
632
|
class DetectionService {
|
|
633
633
|
debug;
|
|
634
634
|
useDocumentClassification;
|
|
635
|
+
useDocumentDetector;
|
|
635
636
|
session;
|
|
636
637
|
mobileNetSession;
|
|
637
638
|
mobileNetClassMap;
|
|
@@ -649,9 +650,10 @@ class DetectionService {
|
|
|
649
650
|
// Static canvas pool for reuse across instances
|
|
650
651
|
static canvasPool = new Map();
|
|
651
652
|
static MAX_POOL_SIZE = 5;
|
|
652
|
-
constructor(debug = false, useDocumentClassification = false) {
|
|
653
|
+
constructor(debug = false, useDocumentClassification = false, useDocumentDetector = true) {
|
|
653
654
|
this.debug = debug;
|
|
654
655
|
this.useDocumentClassification = useDocumentClassification;
|
|
656
|
+
this.useDocumentDetector = useDocumentDetector;
|
|
655
657
|
this.deviceStrategy = DeviceStrategyFactory.createStrategy();
|
|
656
658
|
this.initializeCanvasPool();
|
|
657
659
|
}
|
|
@@ -659,13 +661,23 @@ class DetectionService {
|
|
|
659
661
|
if (this.modelLoaded || this.session) {
|
|
660
662
|
return;
|
|
661
663
|
}
|
|
664
|
+
if (!this.useDocumentDetector) {
|
|
665
|
+
// Skip loading the detection model but mark as "loaded" for compatibility
|
|
666
|
+
this.modelLoaded = true;
|
|
667
|
+
return;
|
|
668
|
+
}
|
|
662
669
|
try {
|
|
663
670
|
const sessionOptions = this.deviceStrategy.getSessionOptions(this.debug);
|
|
664
671
|
try {
|
|
665
672
|
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, sessionOptions);
|
|
666
673
|
}
|
|
667
674
|
catch (error) {
|
|
668
|
-
|
|
675
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
676
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
677
|
+
error.message.includes('Out of memory') ||
|
|
678
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
679
|
+
error.message.includes('no available backend found');
|
|
680
|
+
if (isMemoryError) {
|
|
669
681
|
const fallbackOptions = {
|
|
670
682
|
executionProviders: ['wasm'],
|
|
671
683
|
graphOptimizationLevel: 'disabled',
|
|
@@ -675,8 +687,17 @@ class DetectionService {
|
|
|
675
687
|
executionMode: 'sequential',
|
|
676
688
|
interOpNumThreads: 1,
|
|
677
689
|
intraOpNumThreads: 1,
|
|
690
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
691
|
+
enableProfiling: false,
|
|
692
|
+
sessionLogSeverityLevel: 4,
|
|
693
|
+
sessionLogVerbosityLevel: 0,
|
|
678
694
|
};
|
|
679
|
-
|
|
695
|
+
try {
|
|
696
|
+
this.session = await window.ort.InferenceSession.create(this.MODEL_PATH, fallbackOptions);
|
|
697
|
+
}
|
|
698
|
+
catch (fallbackError) {
|
|
699
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
700
|
+
}
|
|
680
701
|
}
|
|
681
702
|
else {
|
|
682
703
|
throw error;
|
|
@@ -716,7 +737,12 @@ class DetectionService {
|
|
|
716
737
|
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, sessionOptions);
|
|
717
738
|
}
|
|
718
739
|
catch (error) {
|
|
719
|
-
|
|
740
|
+
// Múltiples tipos de errores de memoria que pueden ocurrir
|
|
741
|
+
const isMemoryError = error.message.includes('failed to allocate a buffer') ||
|
|
742
|
+
error.message.includes('Out of memory') ||
|
|
743
|
+
error.message.includes('RangeError: Out of memory') ||
|
|
744
|
+
error.message.includes('no available backend found');
|
|
745
|
+
if (isMemoryError) {
|
|
720
746
|
const fallbackOptions = {
|
|
721
747
|
executionProviders: ['wasm'],
|
|
722
748
|
graphOptimizationLevel: 'disabled',
|
|
@@ -726,8 +752,17 @@ class DetectionService {
|
|
|
726
752
|
executionMode: 'sequential',
|
|
727
753
|
interOpNumThreads: 1,
|
|
728
754
|
intraOpNumThreads: 1,
|
|
755
|
+
// Configuraciones adicionales para dispositivos con poca memoria
|
|
756
|
+
enableProfiling: false,
|
|
757
|
+
sessionLogSeverityLevel: 4,
|
|
758
|
+
sessionLogVerbosityLevel: 0,
|
|
729
759
|
};
|
|
730
|
-
|
|
760
|
+
try {
|
|
761
|
+
this.mobileNetSession = await window.ort.InferenceSession.create(this.MOBILENET_MODEL_PATH, fallbackOptions);
|
|
762
|
+
}
|
|
763
|
+
catch (fallbackError) {
|
|
764
|
+
throw new Error(`no available backend found. ERR: [wasm] RangeError: Out of memory`);
|
|
765
|
+
}
|
|
731
766
|
}
|
|
732
767
|
else {
|
|
733
768
|
throw error;
|
|
@@ -739,6 +774,10 @@ class DetectionService {
|
|
|
739
774
|
}
|
|
740
775
|
}
|
|
741
776
|
isModelLoaded() {
|
|
777
|
+
if (!this.useDocumentDetector) {
|
|
778
|
+
// If detector is disabled, consider it "loaded" for compatibility
|
|
779
|
+
return this.modelLoaded;
|
|
780
|
+
}
|
|
742
781
|
return this.modelLoaded && !!this.session;
|
|
743
782
|
}
|
|
744
783
|
preprocess(video) {
|
|
@@ -763,6 +802,10 @@ class DetectionService {
|
|
|
763
802
|
return new window.ort.Tensor("float16", float16Data, [1, 3, this.INPUT_SIZE, this.INPUT_SIZE]);
|
|
764
803
|
}
|
|
765
804
|
async runInference(inputTensor) {
|
|
805
|
+
if (!this.useDocumentDetector) {
|
|
806
|
+
// Return empty array when document detector is disabled
|
|
807
|
+
return [];
|
|
808
|
+
}
|
|
766
809
|
if (!this.session) {
|
|
767
810
|
throw new Error('Detection model not loaded');
|
|
768
811
|
}
|
|
@@ -1040,7 +1083,7 @@ class ServiceContainer {
|
|
|
1040
1083
|
const eventBus = new EventBusService();
|
|
1041
1084
|
const stateManager = new StateManagerService(eventBus);
|
|
1042
1085
|
const cameraService = new CameraService(eventBus, config.preferredCamera);
|
|
1043
|
-
const detectionService = new DetectionService(config.debug, config.useDocumentClassification);
|
|
1086
|
+
const detectionService = new DetectionService(config.debug, config.useDocumentClassification, config.useDocumentDetector);
|
|
1044
1087
|
// Register services
|
|
1045
1088
|
this.services.set('eventBus', eventBus);
|
|
1046
1089
|
this.services.set('stateManager', stateManager);
|
|
@@ -1076,7 +1119,7 @@ class ServiceContainer {
|
|
|
1076
1119
|
}
|
|
1077
1120
|
}
|
|
1078
1121
|
|
|
1079
|
-
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%);color:#fff;font-size:14px;font-weight:600;text-align:center;white-space:normal;background:rgba(0, 0, 0, 0.25);backdrop-filter:blur(12px);padding:12px 20px;border-radius:8px;max-width:300px;z-index:20;border:1px solid rgba(255, 255, 255, 0.1)}.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}.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}.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)}}.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}}.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:slideInFromLeft 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}}";
|
|
1122
|
+
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}}";
|
|
1080
1123
|
|
|
1081
1124
|
const JaakStamps = class {
|
|
1082
1125
|
constructor(hostRef) {
|
|
@@ -1090,6 +1133,7 @@ const JaakStamps = class {
|
|
|
1090
1133
|
maskSize = 90;
|
|
1091
1134
|
cropMargin = 20;
|
|
1092
1135
|
useDocumentClassification = false;
|
|
1136
|
+
useDocumentDetector = true;
|
|
1093
1137
|
preferredCamera = 'auto';
|
|
1094
1138
|
captureDelay = 1500;
|
|
1095
1139
|
enableBackDocumentTimer = false;
|
|
@@ -1130,6 +1174,11 @@ const JaakStamps = class {
|
|
|
1130
1174
|
detectionRate: 0
|
|
1131
1175
|
};
|
|
1132
1176
|
backDocumentTimerRemaining = 0;
|
|
1177
|
+
showManualCaptureButton = false;
|
|
1178
|
+
performanceDegradedMode = false; // Auto-switched to manual mode due to performance
|
|
1179
|
+
showPerformanceMessage = false; // Show performance message temporarily
|
|
1180
|
+
captureStateVersion = 0; // Triggers re-render when StateManager changes
|
|
1181
|
+
processingButton = null; // Track which button is processing
|
|
1133
1182
|
// Services
|
|
1134
1183
|
serviceContainer;
|
|
1135
1184
|
eventBus;
|
|
@@ -1148,12 +1197,12 @@ const JaakStamps = class {
|
|
|
1148
1197
|
alignmentStartTime;
|
|
1149
1198
|
alignmentTimer;
|
|
1150
1199
|
backDocumentTimer;
|
|
1200
|
+
orientationTimer;
|
|
1151
1201
|
// Performance monitoring
|
|
1152
1202
|
performanceMetrics = {
|
|
1153
1203
|
fps: 0,
|
|
1154
1204
|
inferenceTime: 0,
|
|
1155
1205
|
memoryUsage: 0,
|
|
1156
|
-
cpuUsage: 0,
|
|
1157
1206
|
onnxLoadTime: 0,
|
|
1158
1207
|
frameProcessingTime: 0,
|
|
1159
1208
|
totalDetections: 0,
|
|
@@ -1172,18 +1221,29 @@ const JaakStamps = class {
|
|
|
1172
1221
|
MIN_INFERENCE_INTERVAL = 50;
|
|
1173
1222
|
performanceHistory = [];
|
|
1174
1223
|
PERFORMANCE_HISTORY_SIZE = 10;
|
|
1224
|
+
// Sistema simplificado de monitoreo de rendimiento
|
|
1225
|
+
PERFORMANCE_THRESHOLD_MS = 2000; // Umbral único para todos los frames
|
|
1226
|
+
SLOW_FRAMES_TO_TRIGGER = 2; // Cuántos frames lentos consecutivos antes de cambiar
|
|
1227
|
+
slowFrameCount = 0; // Contador de frames consecutivos lentos
|
|
1228
|
+
processedFramesCount = 0; // Contador total de frames procesados
|
|
1229
|
+
hasAutoSwitchedToManual = false;
|
|
1175
1230
|
// Canvas pool for optimized screenshot capture
|
|
1176
1231
|
canvasPool = [];
|
|
1177
1232
|
MAX_CANVAS_POOL_SIZE = 3;
|
|
1178
1233
|
async componentDidLoad() {
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
this.
|
|
1234
|
+
try {
|
|
1235
|
+
this.updateStatus('Preparando capturador...', 'Configurando herramientas de captura', 'initializing');
|
|
1236
|
+
await this.initializeServices();
|
|
1237
|
+
this.updateStatus('Preparando funciones...', 'Configurando herramientas de trabajo', 'initializing');
|
|
1238
|
+
await this.setupEventListeners();
|
|
1239
|
+
this.updateStatus('Configurando cámara...', 'Detectando dispositivos disponibles', 'initializing');
|
|
1240
|
+
await this.initializeComponent();
|
|
1241
|
+
if (this.debug) {
|
|
1242
|
+
this.initializePerformanceMonitor();
|
|
1243
|
+
}
|
|
1244
|
+
}
|
|
1245
|
+
catch (error) {
|
|
1246
|
+
this.updateStatus('Error de inicialización', error.message, 'error');
|
|
1187
1247
|
}
|
|
1188
1248
|
}
|
|
1189
1249
|
async initializeServices() {
|
|
@@ -1193,6 +1253,7 @@ const JaakStamps = class {
|
|
|
1193
1253
|
maskSize: this.maskSize,
|
|
1194
1254
|
cropMargin: this.cropMargin,
|
|
1195
1255
|
useDocumentClassification: this.useDocumentClassification,
|
|
1256
|
+
useDocumentDetector: this.useDocumentDetector,
|
|
1196
1257
|
preferredCamera: this.preferredCamera,
|
|
1197
1258
|
captureDelay: this.captureDelay
|
|
1198
1259
|
};
|
|
@@ -1203,14 +1264,8 @@ const JaakStamps = class {
|
|
|
1203
1264
|
this.detectionService = this.serviceContainer.getDetectionService();
|
|
1204
1265
|
}
|
|
1205
1266
|
async setupEventListeners() {
|
|
1206
|
-
this.eventBus.on('state-changed', (
|
|
1207
|
-
this.handleStateChange(
|
|
1208
|
-
});
|
|
1209
|
-
this.eventBus.on('camera-changed', () => {
|
|
1210
|
-
// Camera changed event
|
|
1211
|
-
});
|
|
1212
|
-
this.eventBus.on('error', () => {
|
|
1213
|
-
// Handle service errors
|
|
1267
|
+
this.eventBus.on('state-changed', () => {
|
|
1268
|
+
this.handleStateChange();
|
|
1214
1269
|
});
|
|
1215
1270
|
}
|
|
1216
1271
|
async initializeComponent() {
|
|
@@ -1250,27 +1305,39 @@ const JaakStamps = class {
|
|
|
1250
1305
|
document.head.appendChild(script);
|
|
1251
1306
|
await new Promise((resolve) => {
|
|
1252
1307
|
script.onload = () => {
|
|
1253
|
-
setTimeout(() => {
|
|
1254
|
-
this.finalizeInitialization();
|
|
1308
|
+
setTimeout(async () => {
|
|
1309
|
+
await this.finalizeInitialization();
|
|
1255
1310
|
resolve(undefined);
|
|
1256
1311
|
}, 300);
|
|
1257
1312
|
};
|
|
1258
1313
|
});
|
|
1259
1314
|
}
|
|
1260
1315
|
else {
|
|
1261
|
-
setTimeout(() => {
|
|
1262
|
-
this.finalizeInitialization();
|
|
1316
|
+
setTimeout(async () => {
|
|
1317
|
+
await this.finalizeInitialization();
|
|
1263
1318
|
}, 300);
|
|
1264
1319
|
}
|
|
1265
1320
|
}
|
|
1266
|
-
finalizeInitialization() {
|
|
1321
|
+
async finalizeInitialization() {
|
|
1267
1322
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1268
|
-
|
|
1269
|
-
|
|
1270
|
-
|
|
1271
|
-
|
|
1272
|
-
|
|
1273
|
-
|
|
1323
|
+
// Check camera permissions before showing "ready" status
|
|
1324
|
+
const hasPermissions = await this.checkCameraPermissions();
|
|
1325
|
+
if (hasPermissions) {
|
|
1326
|
+
this.currentStatus = {
|
|
1327
|
+
message: 'Listo para capturar',
|
|
1328
|
+
description: '',
|
|
1329
|
+
type: 'ready',
|
|
1330
|
+
isInitialized: true
|
|
1331
|
+
};
|
|
1332
|
+
}
|
|
1333
|
+
else {
|
|
1334
|
+
this.currentStatus = {
|
|
1335
|
+
message: 'Permisos de cámara requeridos',
|
|
1336
|
+
description: 'Por favor, otorgue permisos de cámara para continuar',
|
|
1337
|
+
type: 'error',
|
|
1338
|
+
isInitialized: true
|
|
1339
|
+
};
|
|
1340
|
+
}
|
|
1274
1341
|
this.emitReadyEvent();
|
|
1275
1342
|
}
|
|
1276
1343
|
updateStatus(message, description, type = 'loading') {
|
|
@@ -1285,7 +1352,28 @@ const JaakStamps = class {
|
|
|
1285
1352
|
const isDocumentReady = !!window.ort && this.detectionService.isModelLoaded();
|
|
1286
1353
|
this.isReady.emit(isDocumentReady);
|
|
1287
1354
|
}
|
|
1288
|
-
|
|
1355
|
+
async checkCameraPermissions() {
|
|
1356
|
+
try {
|
|
1357
|
+
if (!navigator.permissions) {
|
|
1358
|
+
// Fallback: try to access camera directly
|
|
1359
|
+
try {
|
|
1360
|
+
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
|
|
1361
|
+
stream.getTracks().forEach(track => track.stop());
|
|
1362
|
+
return true;
|
|
1363
|
+
}
|
|
1364
|
+
catch {
|
|
1365
|
+
return false;
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
const permission = await navigator.permissions.query({ name: 'camera' });
|
|
1369
|
+
return permission.state === 'granted';
|
|
1370
|
+
}
|
|
1371
|
+
catch (error) {
|
|
1372
|
+
return false;
|
|
1373
|
+
}
|
|
1374
|
+
}
|
|
1375
|
+
handleStateChange() {
|
|
1376
|
+
// Handle state changes from StateManager if needed
|
|
1289
1377
|
}
|
|
1290
1378
|
initializeResizeObserver() {
|
|
1291
1379
|
if ('ResizeObserver' in window && this.detectionContainer) {
|
|
@@ -1294,6 +1382,18 @@ const JaakStamps = class {
|
|
|
1294
1382
|
});
|
|
1295
1383
|
resizeObserver.observe(this.detectionContainer.parentElement);
|
|
1296
1384
|
}
|
|
1385
|
+
// Handle mobile device orientation changes
|
|
1386
|
+
window.addEventListener('orientationchange', () => {
|
|
1387
|
+
// Clear any existing orientation timer
|
|
1388
|
+
if (this.orientationTimer) {
|
|
1389
|
+
clearTimeout(this.orientationTimer);
|
|
1390
|
+
}
|
|
1391
|
+
// Add delay to ensure video dimensions are updated after rotation
|
|
1392
|
+
this.orientationTimer = window.setTimeout(() => {
|
|
1393
|
+
this.handleResize();
|
|
1394
|
+
this.orientationTimer = undefined;
|
|
1395
|
+
}, 300);
|
|
1396
|
+
});
|
|
1297
1397
|
}
|
|
1298
1398
|
handleResize() {
|
|
1299
1399
|
if (this.detectionContainer) {
|
|
@@ -1302,6 +1402,36 @@ const JaakStamps = class {
|
|
|
1302
1402
|
this.updateMaskDimensions(rect);
|
|
1303
1403
|
}
|
|
1304
1404
|
}
|
|
1405
|
+
triggerRerender() {
|
|
1406
|
+
this.captureStateVersion = this.captureStateVersion + 1;
|
|
1407
|
+
}
|
|
1408
|
+
getGuideText(step) {
|
|
1409
|
+
if (step === 'completed') {
|
|
1410
|
+
return 'Proceso completado';
|
|
1411
|
+
}
|
|
1412
|
+
// Check if we should show performance message
|
|
1413
|
+
if (this.showPerformanceMessage && step === 'front') {
|
|
1414
|
+
return 'Modo manual activado por rendimiento. Use el botón para capturar.';
|
|
1415
|
+
}
|
|
1416
|
+
if (step === 'front') {
|
|
1417
|
+
// Para el frente, si el detector manual está activo, mostrar instrucciones de captura manual
|
|
1418
|
+
if (this.showManualCaptureButton) {
|
|
1419
|
+
return 'Posicione el frente de su documento en el marco y presione el botón para capturar';
|
|
1420
|
+
}
|
|
1421
|
+
// Instrucción básica de alineación
|
|
1422
|
+
return 'Alinee su identificación con el marco';
|
|
1423
|
+
}
|
|
1424
|
+
else if (step === 'back') {
|
|
1425
|
+
// Para el reverso, mostrar instrucciones específicas según el modo
|
|
1426
|
+
if (this.useDocumentDetector) {
|
|
1427
|
+
return 'Si tu documento no tiene lado trasero, da clic en el botón para continuar con el proceso';
|
|
1428
|
+
}
|
|
1429
|
+
else {
|
|
1430
|
+
return 'Posicione el reverso de su documento en el marco y capture, o salte este paso';
|
|
1431
|
+
}
|
|
1432
|
+
}
|
|
1433
|
+
return 'Alinee su identificación con el marco';
|
|
1434
|
+
}
|
|
1305
1435
|
updateMaskDimensions(containerRect) {
|
|
1306
1436
|
if (!this.videoRef)
|
|
1307
1437
|
return;
|
|
@@ -1440,8 +1570,14 @@ const JaakStamps = class {
|
|
|
1440
1570
|
}
|
|
1441
1571
|
}
|
|
1442
1572
|
async skipBackCapture() {
|
|
1573
|
+
console.log('🟡 Botón clicked: Saltar reverso');
|
|
1574
|
+
// Set processing state immediately
|
|
1575
|
+
this.processingButton = 'skip-back';
|
|
1576
|
+
// Add small delay to show spinner
|
|
1577
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
1443
1578
|
const readyCheck = this.isComponentReady();
|
|
1444
1579
|
if (!readyCheck.ready) {
|
|
1580
|
+
this.processingButton = null;
|
|
1445
1581
|
return { success: false, error: readyCheck.message };
|
|
1446
1582
|
}
|
|
1447
1583
|
try {
|
|
@@ -1458,6 +1594,10 @@ const JaakStamps = class {
|
|
|
1458
1594
|
catch (error) {
|
|
1459
1595
|
return { success: false, error: error.message };
|
|
1460
1596
|
}
|
|
1597
|
+
finally {
|
|
1598
|
+
// Always clear processing state when done
|
|
1599
|
+
this.processingButton = null;
|
|
1600
|
+
}
|
|
1461
1601
|
}
|
|
1462
1602
|
async getStatus() {
|
|
1463
1603
|
const readyCheck = this.isComponentReady();
|
|
@@ -1512,9 +1652,21 @@ const JaakStamps = class {
|
|
|
1512
1652
|
if (this.stateManager) {
|
|
1513
1653
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1514
1654
|
}
|
|
1515
|
-
|
|
1655
|
+
try {
|
|
1656
|
+
await this.detectionService.loadModel();
|
|
1657
|
+
}
|
|
1658
|
+
catch (modelError) {
|
|
1659
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
1660
|
+
throw modelError;
|
|
1661
|
+
}
|
|
1516
1662
|
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1517
|
-
|
|
1663
|
+
try {
|
|
1664
|
+
await this.detectionService.loadClassificationModel();
|
|
1665
|
+
}
|
|
1666
|
+
catch (classificationError) {
|
|
1667
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
1668
|
+
throw classificationError;
|
|
1669
|
+
}
|
|
1518
1670
|
const loadEndTime = performance.now();
|
|
1519
1671
|
const loadTime = loadEndTime - loadStartTime;
|
|
1520
1672
|
// Record ONNX load time
|
|
@@ -1623,17 +1775,63 @@ const JaakStamps = class {
|
|
|
1623
1775
|
// Always allow getting capture delay, even during initialization
|
|
1624
1776
|
return this.captureDelay;
|
|
1625
1777
|
}
|
|
1778
|
+
// Memory and device capability detection
|
|
1779
|
+
checkDeviceCapabilities() {
|
|
1780
|
+
const deviceMemory = navigator.deviceMemory;
|
|
1781
|
+
// Siempre intentar usar ML, independientemente del dispositivo
|
|
1782
|
+
// Las restricciones anteriores no eran confiables
|
|
1783
|
+
return {
|
|
1784
|
+
canUseML: true,
|
|
1785
|
+
reason: 'Dispositivo compatible con detección automática.',
|
|
1786
|
+
memoryMB: deviceMemory ? deviceMemory * 1024 : null
|
|
1787
|
+
};
|
|
1788
|
+
}
|
|
1626
1789
|
// DETECTION METHODS
|
|
1627
1790
|
async startDetection() {
|
|
1628
1791
|
try {
|
|
1792
|
+
// Verificar capacidades del dispositivo antes de cargar modelos
|
|
1793
|
+
const capabilities = this.checkDeviceCapabilities();
|
|
1794
|
+
// Si el dispositivo no puede usar ML o tenemos problemas de memoria, usar modo manual
|
|
1795
|
+
if (!capabilities.canUseML && this.useDocumentDetector) {
|
|
1796
|
+
this.switchToManualModeWithWarning(capabilities.reason);
|
|
1797
|
+
return;
|
|
1798
|
+
}
|
|
1629
1799
|
// Paso 1: Verificar y cargar modelos si es necesario
|
|
1630
1800
|
if (!this.detectionService.isModelLoaded()) {
|
|
1631
1801
|
const loadStartTime = performance.now();
|
|
1632
1802
|
this.updateStatus('Preparando reconocimiento...', 'Configurando detección de documentos', 'loading');
|
|
1633
1803
|
this.stateManager.updateCaptureState({ isLoading: true });
|
|
1634
|
-
|
|
1804
|
+
try {
|
|
1805
|
+
await this.detectionService.loadModel();
|
|
1806
|
+
}
|
|
1807
|
+
catch (modelError) {
|
|
1808
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
1809
|
+
if (modelError.message.includes('Out of memory') || modelError.message.includes('no available backend')) {
|
|
1810
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
1811
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para detección automática ${memoryInfo}. Modo manual activado - funciona igual de bien!`);
|
|
1812
|
+
return;
|
|
1813
|
+
}
|
|
1814
|
+
else {
|
|
1815
|
+
this.switchToManualModeWithError('Error al cargar modelo de detección');
|
|
1816
|
+
throw modelError;
|
|
1817
|
+
}
|
|
1818
|
+
}
|
|
1635
1819
|
this.updateStatus('Optimizando detección...', 'Configurando reconocimiento de documentos', 'loading');
|
|
1636
|
-
|
|
1820
|
+
try {
|
|
1821
|
+
await this.detectionService.loadClassificationModel();
|
|
1822
|
+
}
|
|
1823
|
+
catch (classificationError) {
|
|
1824
|
+
// Si es un error de memoria, cambiar a modo manual con mensaje específico
|
|
1825
|
+
if (classificationError.message.includes('Out of memory') || classificationError.message.includes('no available backend')) {
|
|
1826
|
+
const memoryInfo = navigator.deviceMemory ? `(${navigator.deviceMemory}GB disponible)` : '';
|
|
1827
|
+
this.switchToManualModeWithWarning(`Memoria insuficiente para clasificación automática ${memoryInfo}. Modo manual activado para optimizar rendimiento.`);
|
|
1828
|
+
return;
|
|
1829
|
+
}
|
|
1830
|
+
else {
|
|
1831
|
+
this.switchToManualModeWithError('Error al cargar modelo de clasificación');
|
|
1832
|
+
throw classificationError;
|
|
1833
|
+
}
|
|
1834
|
+
}
|
|
1637
1835
|
const loadEndTime = performance.now();
|
|
1638
1836
|
const loadTime = loadEndTime - loadStartTime;
|
|
1639
1837
|
// Record ONNX load time
|
|
@@ -1643,14 +1841,40 @@ const JaakStamps = class {
|
|
|
1643
1841
|
}
|
|
1644
1842
|
// Paso 2: Detectar y configurar dispositivos
|
|
1645
1843
|
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
1646
|
-
|
|
1647
|
-
|
|
1844
|
+
try {
|
|
1845
|
+
await this.cameraService.enumerateDevices();
|
|
1846
|
+
}
|
|
1847
|
+
catch (enumerateError) {
|
|
1848
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
1849
|
+
}
|
|
1850
|
+
try {
|
|
1851
|
+
await this.updateCameraInfoWithAutofocus();
|
|
1852
|
+
}
|
|
1853
|
+
catch (cameraInfoError) {
|
|
1854
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
1855
|
+
}
|
|
1648
1856
|
// Paso 3: Configurar cámara seleccionada
|
|
1649
1857
|
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
1650
|
-
|
|
1651
|
-
|
|
1652
|
-
|
|
1653
|
-
|
|
1858
|
+
let stream;
|
|
1859
|
+
try {
|
|
1860
|
+
stream = await this.cameraService.setupCamera();
|
|
1861
|
+
}
|
|
1862
|
+
catch (setupError) {
|
|
1863
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
1864
|
+
}
|
|
1865
|
+
// Paso 4: Inicializar video y mostrar estado de análisis inicial
|
|
1866
|
+
if (this.useDocumentDetector) {
|
|
1867
|
+
this.updateStatus('Analizando estabilidad...', 'Evaluando rendimiento del sistema', 'loading');
|
|
1868
|
+
}
|
|
1869
|
+
else {
|
|
1870
|
+
this.updateStatus('Captura activa', 'Posicione su documento y use el botón para capturar', 'active');
|
|
1871
|
+
}
|
|
1872
|
+
try {
|
|
1873
|
+
await this.initializeVideoStream(stream);
|
|
1874
|
+
}
|
|
1875
|
+
catch (videoError) {
|
|
1876
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
1877
|
+
}
|
|
1654
1878
|
// Paso 5: Calibrar máscara
|
|
1655
1879
|
if (this.detectionContainer) {
|
|
1656
1880
|
const container = this.detectionContainer.parentElement;
|
|
@@ -1660,6 +1884,10 @@ const JaakStamps = class {
|
|
|
1660
1884
|
// Finalizar e iniciar captura
|
|
1661
1885
|
this.startTime = Date.now();
|
|
1662
1886
|
this.stateManager.updateCaptureState({ isLoading: false });
|
|
1887
|
+
// Mostrar botón manual si el detector está deshabilitado
|
|
1888
|
+
if (!this.useDocumentDetector) {
|
|
1889
|
+
this.showManualCaptureButton = true;
|
|
1890
|
+
}
|
|
1663
1891
|
this.detectFrame();
|
|
1664
1892
|
}
|
|
1665
1893
|
catch (err) {
|
|
@@ -1677,6 +1905,12 @@ const JaakStamps = class {
|
|
|
1677
1905
|
this.videoRef.onloadedmetadata = async () => {
|
|
1678
1906
|
await this.videoRef.play();
|
|
1679
1907
|
this.stateManager.updateCaptureState({ isVideoActive: true });
|
|
1908
|
+
// Recalculate mask dimensions when new camera stream loads
|
|
1909
|
+
if (this.detectionContainer) {
|
|
1910
|
+
const container = this.detectionContainer.parentElement;
|
|
1911
|
+
const rect = container.getBoundingClientRect();
|
|
1912
|
+
this.updateMaskDimensions(rect);
|
|
1913
|
+
}
|
|
1680
1914
|
resolve();
|
|
1681
1915
|
};
|
|
1682
1916
|
});
|
|
@@ -1691,6 +1925,18 @@ const JaakStamps = class {
|
|
|
1691
1925
|
const captureState = this.stateManager.getCaptureState();
|
|
1692
1926
|
if (!this.videoRef || !this.detectionContainer || !this.detectionService.isModelLoaded())
|
|
1693
1927
|
return;
|
|
1928
|
+
// Show manual capture button when document detector is disabled or performance is degraded
|
|
1929
|
+
if (!this.useDocumentDetector || this.performanceDegradedMode) {
|
|
1930
|
+
this.showManualCaptureButton = true;
|
|
1931
|
+
// Continue the loop for UI updates but skip detection logic
|
|
1932
|
+
if (captureState.step !== 'completed') {
|
|
1933
|
+
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
1934
|
+
}
|
|
1935
|
+
return;
|
|
1936
|
+
}
|
|
1937
|
+
else if (!this.performanceDegradedMode) {
|
|
1938
|
+
this.showManualCaptureButton = false;
|
|
1939
|
+
}
|
|
1694
1940
|
if (captureState.isDetectionPaused) {
|
|
1695
1941
|
if (captureState.step !== 'completed') {
|
|
1696
1942
|
this.animationId = requestAnimationFrame(() => this.detectFrame());
|
|
@@ -1718,10 +1964,27 @@ const JaakStamps = class {
|
|
|
1718
1964
|
this.lastInferenceTime = currentTime;
|
|
1719
1965
|
// Measure preprocessing and inference time
|
|
1720
1966
|
const inferenceStartTime = performance.now();
|
|
1721
|
-
|
|
1722
|
-
|
|
1723
|
-
|
|
1724
|
-
|
|
1967
|
+
let inputTensor;
|
|
1968
|
+
let detections;
|
|
1969
|
+
let inferenceTime;
|
|
1970
|
+
try {
|
|
1971
|
+
inputTensor = this.detectionService.preprocess(this.videoRef);
|
|
1972
|
+
}
|
|
1973
|
+
catch (preprocessError) {
|
|
1974
|
+
this.switchToManualModeWithError('Error al procesar imagen');
|
|
1975
|
+
return;
|
|
1976
|
+
}
|
|
1977
|
+
try {
|
|
1978
|
+
// Standard detection without timeout (timeout is handled by frame counting)
|
|
1979
|
+
detections = await this.detectionService.runInference(inputTensor);
|
|
1980
|
+
inferenceTime = performance.now() - inferenceStartTime;
|
|
1981
|
+
}
|
|
1982
|
+
catch (inferenceError) {
|
|
1983
|
+
this.switchToManualModeWithError('Error en detección automática');
|
|
1984
|
+
return;
|
|
1985
|
+
}
|
|
1986
|
+
// Increment frame count
|
|
1987
|
+
this.processedFramesCount++;
|
|
1725
1988
|
// Record ONNX performance metrics
|
|
1726
1989
|
if (this.debug) {
|
|
1727
1990
|
this.recordOnnxPerformance(0, inferenceTime);
|
|
@@ -1760,9 +2023,30 @@ const JaakStamps = class {
|
|
|
1760
2023
|
}
|
|
1761
2024
|
this.updateMaskColor(detections);
|
|
1762
2025
|
// Record frame processing metrics
|
|
2026
|
+
const frameEndTime = performance.now();
|
|
2027
|
+
const totalFrameTime = frameEndTime - frameStartTime;
|
|
2028
|
+
// Sistema simplificado: Verificar rendimiento de cada frame (solo si no se ha cambiado a manual)
|
|
2029
|
+
if (this.useDocumentDetector && !this.hasAutoSwitchedToManual) {
|
|
2030
|
+
// Verificar si el frame es lento
|
|
2031
|
+
if (totalFrameTime >= this.PERFORMANCE_THRESHOLD_MS) {
|
|
2032
|
+
this.slowFrameCount++;
|
|
2033
|
+
// Si tenemos suficientes frames lentos consecutivos, cambiar a manual
|
|
2034
|
+
if (this.slowFrameCount >= this.SLOW_FRAMES_TO_TRIGGER) {
|
|
2035
|
+
this.switchToManualMode();
|
|
2036
|
+
}
|
|
2037
|
+
}
|
|
2038
|
+
else {
|
|
2039
|
+
// Frame rápido, reiniciar contador
|
|
2040
|
+
if (this.slowFrameCount > 0) {
|
|
2041
|
+
this.slowFrameCount = 0;
|
|
2042
|
+
}
|
|
2043
|
+
}
|
|
2044
|
+
// Cambiar mensaje de estado después de analizar los primeros 2 frames
|
|
2045
|
+
if (this.processedFramesCount === 2) {
|
|
2046
|
+
this.updateStatus('Captura activa', 'Buscando documento en el marco de captura', 'active');
|
|
2047
|
+
}
|
|
2048
|
+
}
|
|
1763
2049
|
if (this.debug) {
|
|
1764
|
-
const frameEndTime = performance.now();
|
|
1765
|
-
const totalFrameTime = frameEndTime - frameStartTime;
|
|
1766
2050
|
this.recordFrameProcessing(totalFrameTime, detections.length);
|
|
1767
2051
|
}
|
|
1768
2052
|
// Continue detection loop
|
|
@@ -1798,6 +2082,12 @@ const JaakStamps = class {
|
|
|
1798
2082
|
clearTimeout(this.alignmentTimer);
|
|
1799
2083
|
this.alignmentTimer = undefined;
|
|
1800
2084
|
}
|
|
2085
|
+
if (this.orientationTimer) {
|
|
2086
|
+
clearTimeout(this.orientationTimer);
|
|
2087
|
+
this.orientationTimer = undefined;
|
|
2088
|
+
}
|
|
2089
|
+
// Liberar recursos de ONNX al limpiar el componente
|
|
2090
|
+
this.releaseOnnxResources();
|
|
1801
2091
|
if (this.performanceUpdateInterval) {
|
|
1802
2092
|
clearInterval(this.performanceUpdateInterval);
|
|
1803
2093
|
this.performanceUpdateInterval = undefined;
|
|
@@ -1818,7 +2108,7 @@ const JaakStamps = class {
|
|
|
1818
2108
|
isCapturing: false
|
|
1819
2109
|
};
|
|
1820
2110
|
const cameraInfo = this.cameraInfoWithAutofocus;
|
|
1821
|
-
return (index.h("div", { key: '
|
|
2111
|
+
return (index.h("div", { key: 'dda3e3054a18dac59fd6dab4bf722f405d1c1b0f', class: "detector-container" }, index.h("div", { key: 'c260bf52307e072a634cddcc273c1f55037d01ef', class: "video-container" }, index.h("video", { key: '5f20ce9d95c5ece7ab90a2da30f28034b86a57a0', 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: '6afd8a46c16592c67e6bb93f1655de1660338f30', 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: {
|
|
1822
2112
|
position: 'absolute',
|
|
1823
2113
|
left: `${box.x}px`,
|
|
1824
2114
|
top: `${box.y}px`,
|
|
@@ -1827,9 +2117,9 @@ const JaakStamps = class {
|
|
|
1827
2117
|
border: '2px solid #32406C',
|
|
1828
2118
|
pointerEvents: 'none',
|
|
1829
2119
|
boxSizing: 'border-box'
|
|
1830
|
-
} })))), this.isMaskReady && (index.h("div", { key: '
|
|
2120
|
+
} })))), this.isMaskReady && (index.h("div", { key: '178f2022bed334f8c9c8c866a92b44c8cb0369b1', class: "overlay-mask" }, index.h("div", { key: 'acb1bc996409095afd29ffa5a038c3eeae6c7ed9', class: "card-outline" }, index.h("div", { key: 'dcc952d9011c5fd13b229f3a759cc74721a1ee11', class: "side side-top" }), index.h("div", { key: 'addb25f6c41ad10a09b6918e6357052294d14adf', class: "side side-right" }), index.h("div", { key: 'a1ec08f3bf1b16731a224c034c76dd8c6412597b', class: "side side-bottom" }), index.h("div", { key: '22f3c158f2c2bb1dc582f796c39ea60bc11505dd', class: "side side-left" })), !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '5b150f7df5b0c5402e42bae4601936d4e5d1f5d4', class: `guide-text ${this.showPerformanceMessage ? 'performance-warning-text' : ''}` }, this.getGuideText(captureState.step))), captureState.step === 'back' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '17b0a81437eb8691cb4dc5042c72bda01ac92be0', class: "back-capture-section" }, index.h("div", { key: 'a65ccbd5f53b06ec340b36a0a2348cf04920b3df', class: "back-capture-buttons" }, (!this.useDocumentDetector || this.performanceDegradedMode) && (index.h("button", { key: '383d7398079dbb8b1adfe695407cc598059b2a94', class: "capture-button primary-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-back' && (index.h("span", { key: '86bddcfeb6f90da9b18926f4c6e1208296502dcf', class: "button-spinner" })), "Capturar Reverso")), index.h("button", { key: '5c9d116cbb9b5bc16481b2b90c8451182ad436c5', class: "skip-button", onClick: () => this.skipBackCapture(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'skip-back' && (index.h("span", { key: '881f00cfc3a24167bc6b8da5849cb404101b692d', class: "button-spinner" })), this.enableBackDocumentTimer && this.backDocumentTimerRemaining > 0
|
|
1831
2121
|
? `Saltar reverso (${this.backDocumentTimerRemaining}s)`
|
|
1832
|
-
: 'Saltar reverso'))), captureState.isVideoActive && (index.h("div", { key: '
|
|
2122
|
+
: 'Saltar reverso')))), captureState.isVideoActive && (index.h("div", { key: 'd929fc0eeeeee944f3d722fde98c52642443e9a5', class: "camera-controls" }, index.h("button", { key: '1afcfae9d4a23aaede153f27ba6895a33182ada1', 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: 'f37feefd9def935417467dffb52b9fe8d8acea79', class: "camera-selector-dropdown" }, index.h("div", { key: 'a8479ec6a6c4076b5dc40677e1988295100e4d0d', class: "camera-selector-header" }, index.h("span", { key: '074f02967354293a1e5df4e3ac8a126faee33d04' }, "Seleccionar C\u00E1mara"), index.h("button", { key: 'd6ba0252fb269c3a04967fb0e5c075166f202d71', class: "close-selector", onClick: () => this.toggleCameraSelector(), type: "button" }, "\u00D7")), index.h("div", { key: '63f5f9be2b05da71f4fe5a4e38b11ab3e39fb1e0', 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: '4ca46fdebe0eaa078f1dae4d2f1af03321577c7f', class: "device-info" }, index.h("small", { key: '10c54ddc5a196b11a69077b045119187ed09f4be' }, "Dispositivo: ", cameraInfo.deviceType)))), this.showManualCaptureButton && captureState.step === 'front' && !captureState.showFlipAnimation && !captureState.showSuccessAnimation && (index.h("div", { key: '25393018948798f4d9d5f44738e61c7a0a6be990', class: "manual-capture-section" }, index.h("button", { key: '7a6c8686c0db3e352f9689bc41f7fd3f612c800c', class: "manual-capture-button", onClick: () => this.takeManualScreenshot(), type: "button", disabled: this.processingButton !== null }, this.processingButton === 'capture-front' && (index.h("span", { key: '1916b97da3f8dce7d06b86b21f0361dd7ce10b95', class: "button-spinner" })), "Capturar Frente"))))), captureState.isCapturing && (index.h("div", { key: 'aed05a3280dc96121564ffea87945f441d4286f6', class: "capture-animation" })), captureState.showFlipAnimation && (index.h("div", { key: '63d262e5e10325d145fada8e91a878c47305f2b9', class: "flip-animation" }, index.h("div", { key: '2a3699f47e595c5e4ab00dfd11e1e66ab2e13039', class: "id-card-icon" }), index.h("div", { key: 'df195fe9a6db4ec3e14b04b4e932d55adb7c3f3c', class: "flip-text" }, "\u00A1Voltea tu identificaci\u00F3n!"))), captureState.showSuccessAnimation && (index.h("div", { key: '670341c302a31c8017a9083ebf35e2988122ac3f', class: "success-animation" }, index.h("div", { key: '27b0b0d91de6a882182b6ccdebecc7c440997e36', class: "check-icon" }), index.h("div", { key: '71ee5327faf48ea26c2ae36a8a6906c448c3a389', class: "success-text" }, "\u00A1Proceso completado!"))), index.h("div", { key: '39c92aa0b82f6169058b1092894845fcba3f27b4', class: `component-status status-${this.currentStatus.type}` }, (this.currentStatus.type === 'loading' || this.currentStatus.type === 'initializing') && (index.h("div", { key: '74b02ba81756f0e8bfb6e2feafdb2c44fb80afad', class: "status-spinner" })), index.h("div", { key: 'c7419efb2c6e2760019ecce619534e24ae3d7351', class: "status-content" }, index.h("div", { key: '57f315384630f17f6332eddfea628b333299e831', class: "status-message" }, this.currentStatus.message), this.currentStatus.description && (index.h("div", { key: 'ac8632fdd68c24ed00192cb04a13026ada1f953d', class: "status-description" }, this.currentStatus.description)))), this.debug && (index.h("div", { key: 'e51286a839795b18cfac2874e7e1f6e9f8e3bb4b', class: "performance-monitor" }, index.h("div", { key: 'f8cd289d260af9df2daf23bdfb8db70fe8bf6f45', class: "performance-expanded" }, index.h("div", { key: 'bf2becb2ee3ad3bffb82b0e90246a4385a19f567', class: "metrics-row" }, index.h("div", { key: 'f334c2b5de45df73aec6e06cd371151110789a45', class: "metric-compact" }, index.h("span", { key: 'cbf32f72eb3aaf1bf770d651c32123a43b81035c', class: "metric-label" }, "FPS"), index.h("span", { key: '9d2d2f46ba3fdb23c010f781044f00b1dd1250e6', class: `metric-value ${this.performanceData.fps < 15 ? 'warning' : this.performanceData.fps < 10 ? 'danger' : 'good'}` }, this.performanceData.fps)), index.h("div", { key: 'f29406e8a5b00f5838a144de9fda77ce53177d43', class: "metric-compact" }, index.h("span", { key: '8f44e29f6134c340f0a58f8aa321612c8a9bbfb8', class: "metric-label" }, "MEM"), index.h("span", { key: '7a58e164d292329d95054b2679a90e031f6fe4c2', class: `metric-value ${this.performanceData.memoryUsage > 100 ? 'warning' : this.performanceData.memoryUsage > 200 ? 'danger' : 'good'}` }, this.performanceData.memoryUsage, "MB"))), index.h("div", { key: 'd0284a0e65fc2879557d73fc2e03295c29f512ae', class: "metrics-row" }, index.h("div", { key: '78dcbce552e65c95fcd01158f3aa8916ef0e7175', class: "metric-compact" }, index.h("span", { key: '673cfbc8c9dabb020a3d3c18ee07558a61da9b51', class: "metric-label" }, "INF"), index.h("span", { key: '833761b7a875c931a8b31913930c3b132f71f618', class: `metric-value ${this.performanceData.inferenceTime > 100 ? 'warning' : this.performanceData.inferenceTime > 200 ? 'danger' : 'good'}` }, this.performanceData.inferenceTime, "ms")), index.h("div", { key: '324801e3e69a8de181755367201d8e8c643e8d72', class: "metric-compact" }, index.h("span", { key: 'f928d9b2dfbf81f5738ab08a42ebb76ff4a483de', class: "metric-label" }, "FRAME"), index.h("span", { key: 'dc9f2e4a1e848be4d240d89c89126203555ae203', class: `metric-value ${this.performanceData.frameProcessingTime > 50 ? 'warning' : this.performanceData.frameProcessingTime > 100 ? 'danger' : 'good'}` }, this.performanceData.frameProcessingTime, "ms"))), index.h("div", { key: '7ca0a7b44719b1884334edab586267f021cf86c1', class: "metrics-row" }, index.h("div", { key: '262667c4320d337dae2220b77f142cf293542562', class: "metric-compact" }, index.h("span", { key: '578b53d251e58933f654a5afe558931e4333f022', class: "metric-label" }, "DET"), index.h("span", { key: '4fb36eaebebd73e5cb06d582fa3b4ece61463761', class: "metric-value good" }, this.performanceData.successfulDetections, "/", this.performanceData.totalDetections)), index.h("div", { key: 'c17192e3964164ad603427de3befa1615b027022', class: "metric-compact" }, index.h("span", { key: 'd004e74797b9468e94c2af996bb62c1a3ca8d58e', class: "metric-label" }, "RATE"), index.h("span", { key: '0960e8760fd0ac2456a9e76598e5392c24e42d3b', class: `metric-value ${this.performanceData.detectionRate < 30 ? 'danger' : this.performanceData.detectionRate < 60 ? 'warning' : 'good'}` }, this.performanceData.detectionRate, "%")))))), index.h("div", { key: '3588fba1fe51af1335d643107ba0355aca6f4f8c', class: "watermark" }, index.h("img", { key: 'd53ec6b0ee9f97f602f4f8857ec605427b2ee478', src: "https://storage.googleapis.com/jaak-static/commons/powered-by-jaak.png", alt: "Powered by Jaak" })))));
|
|
1833
2123
|
}
|
|
1834
2124
|
// Utility methods
|
|
1835
2125
|
updateDetectionBoxes(boxes) {
|
|
@@ -1944,6 +2234,141 @@ const JaakStamps = class {
|
|
|
1944
2234
|
}
|
|
1945
2235
|
}
|
|
1946
2236
|
}
|
|
2237
|
+
async takeManualScreenshot() {
|
|
2238
|
+
console.log('🔵 Botón clicked: Capturar (Frente/Reverso)');
|
|
2239
|
+
// Set processing state immediately
|
|
2240
|
+
const captureState = this.stateManager?.getCaptureState();
|
|
2241
|
+
if (captureState?.step === 'front') {
|
|
2242
|
+
this.processingButton = 'capture-front';
|
|
2243
|
+
}
|
|
2244
|
+
else if (captureState?.step === 'back') {
|
|
2245
|
+
this.processingButton = 'capture-back';
|
|
2246
|
+
}
|
|
2247
|
+
console.log('🔵 processingButton set to:', this.processingButton);
|
|
2248
|
+
// Add small delay to show spinner
|
|
2249
|
+
await new Promise(resolve => setTimeout(resolve, 100));
|
|
2250
|
+
if (!this.videoRef) {
|
|
2251
|
+
this.processingButton = null;
|
|
2252
|
+
return;
|
|
2253
|
+
}
|
|
2254
|
+
// When using manual capture, use mask coordinates for cropping
|
|
2255
|
+
// Note: processingButton will be cleared inside takeScreenshotWithMaskCoordinates
|
|
2256
|
+
await this.takeScreenshotWithMaskCoordinates();
|
|
2257
|
+
}
|
|
2258
|
+
async takeScreenshotWithMaskCoordinates() {
|
|
2259
|
+
if (!this.videoRef || !this.detectionContainer)
|
|
2260
|
+
return;
|
|
2261
|
+
this.stateManager.updateCaptureState({ isCapturing: true });
|
|
2262
|
+
this.triggerCaptureAnimation();
|
|
2263
|
+
// Use pooled canvas for optimization
|
|
2264
|
+
const captureCanvas = this.getPooledCanvas(this.videoRef.videoWidth, this.videoRef.videoHeight);
|
|
2265
|
+
const captureCtx = captureCanvas.getContext('2d', { alpha: false });
|
|
2266
|
+
captureCtx.clearRect(0, 0, captureCanvas.width, captureCanvas.height);
|
|
2267
|
+
captureCtx.drawImage(this.videoRef, 0, 0, captureCanvas.width, captureCanvas.height);
|
|
2268
|
+
// Calculate mask coordinates for cropping
|
|
2269
|
+
const container = this.detectionContainer.parentElement;
|
|
2270
|
+
const containerRect = container.getBoundingClientRect();
|
|
2271
|
+
// Get mask dimensions from CSS properties
|
|
2272
|
+
const maskWidthPercent = parseFloat(this.el.style.getPropertyValue('--mask-width').replace('%', '')) || 90;
|
|
2273
|
+
const maskHeightPercent = parseFloat(this.el.style.getPropertyValue('--mask-height').replace('%', '')) || 56.25;
|
|
2274
|
+
const maskCenterXPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-x').replace('%', '')) || 50;
|
|
2275
|
+
const maskCenterYPercent = parseFloat(this.el.style.getPropertyValue('--mask-center-y').replace('%', '')) || 50;
|
|
2276
|
+
// Convert percentages to actual video coordinates
|
|
2277
|
+
const videoWidth = this.videoRef.videoWidth;
|
|
2278
|
+
const videoHeight = this.videoRef.videoHeight;
|
|
2279
|
+
const videoAspectRatio = videoWidth / videoHeight;
|
|
2280
|
+
const containerAspectRatio = containerRect.width / containerRect.height;
|
|
2281
|
+
let displayedVideoWidth, displayedVideoHeight;
|
|
2282
|
+
let videoOffsetX = 0, videoOffsetY = 0;
|
|
2283
|
+
if (videoAspectRatio > containerAspectRatio) {
|
|
2284
|
+
displayedVideoWidth = containerRect.width;
|
|
2285
|
+
displayedVideoHeight = containerRect.width / videoAspectRatio;
|
|
2286
|
+
videoOffsetY = (containerRect.height - displayedVideoHeight) / 2;
|
|
2287
|
+
}
|
|
2288
|
+
else {
|
|
2289
|
+
displayedVideoHeight = containerRect.height;
|
|
2290
|
+
displayedVideoWidth = containerRect.height * videoAspectRatio;
|
|
2291
|
+
videoOffsetX = (containerRect.width - displayedVideoWidth) / 2;
|
|
2292
|
+
}
|
|
2293
|
+
// Calculate mask coordinates in video space
|
|
2294
|
+
const maskWidthInContainer = (maskWidthPercent / 100) * containerRect.width;
|
|
2295
|
+
const maskHeightInContainer = (maskHeightPercent / 100) * containerRect.height;
|
|
2296
|
+
const maskCenterXInContainer = (maskCenterXPercent / 100) * containerRect.width;
|
|
2297
|
+
const maskCenterYInContainer = (maskCenterYPercent / 100) * containerRect.height;
|
|
2298
|
+
// Convert to video coordinates
|
|
2299
|
+
const scaleX = videoWidth / displayedVideoWidth;
|
|
2300
|
+
const scaleY = videoHeight / displayedVideoHeight;
|
|
2301
|
+
const maskCenterXInVideo = (maskCenterXInContainer - videoOffsetX) * scaleX;
|
|
2302
|
+
const maskCenterYInVideo = (maskCenterYInContainer - videoOffsetY) * scaleY;
|
|
2303
|
+
const maskWidthInVideo = maskWidthInContainer * scaleX;
|
|
2304
|
+
const maskHeightInVideo = maskHeightInContainer * scaleY;
|
|
2305
|
+
// Calculate crop coordinates
|
|
2306
|
+
const cropX = Math.max(0, maskCenterXInVideo - (maskWidthInVideo / 2) - this.cropMargin);
|
|
2307
|
+
const cropY = Math.max(0, maskCenterYInVideo - (maskHeightInVideo / 2) - this.cropMargin);
|
|
2308
|
+
const cropWidth = Math.min(maskWidthInVideo + (2 * this.cropMargin), videoWidth - cropX);
|
|
2309
|
+
const cropHeight = Math.min(maskHeightInVideo + (2 * this.cropMargin), videoHeight - cropY);
|
|
2310
|
+
// Use pooled canvas for cropped version
|
|
2311
|
+
const croppedCanvas = this.getPooledCanvas(cropWidth, cropHeight);
|
|
2312
|
+
const croppedCtx = croppedCanvas.getContext('2d', { alpha: false });
|
|
2313
|
+
croppedCtx.clearRect(0, 0, croppedCanvas.width, croppedCanvas.height);
|
|
2314
|
+
croppedCtx.drawImage(this.videoRef, cropX, cropY, cropWidth, cropHeight, 0, 0, cropWidth, cropHeight);
|
|
2315
|
+
const captureState = this.stateManager.getCaptureState();
|
|
2316
|
+
if (captureState.step === 'front') {
|
|
2317
|
+
this.stateManager.setCapturedImages({
|
|
2318
|
+
front: {
|
|
2319
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2320
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2321
|
+
}
|
|
2322
|
+
});
|
|
2323
|
+
// Check if document classification is enabled (independent of detector)
|
|
2324
|
+
if (this.useDocumentClassification) {
|
|
2325
|
+
try {
|
|
2326
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2327
|
+
if (classification && classification.class === 'passport') {
|
|
2328
|
+
this.completeProcess(true);
|
|
2329
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2330
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2331
|
+
// Clear processing button when passport is detected
|
|
2332
|
+
this.processingButton = null;
|
|
2333
|
+
return;
|
|
2334
|
+
}
|
|
2335
|
+
}
|
|
2336
|
+
catch (classifyError) {
|
|
2337
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
2338
|
+
}
|
|
2339
|
+
}
|
|
2340
|
+
this.stateManager.updateCaptureState({
|
|
2341
|
+
step: 'back',
|
|
2342
|
+
isDetectionPaused: true,
|
|
2343
|
+
showFlipAnimation: true
|
|
2344
|
+
});
|
|
2345
|
+
this.triggerRerender();
|
|
2346
|
+
setTimeout(() => {
|
|
2347
|
+
this.stateManager.updateCaptureState({
|
|
2348
|
+
showFlipAnimation: false,
|
|
2349
|
+
isDetectionPaused: false
|
|
2350
|
+
});
|
|
2351
|
+
this.triggerRerender();
|
|
2352
|
+
this.startBackDocumentTimer();
|
|
2353
|
+
// Clear processing button after animation completes
|
|
2354
|
+
this.processingButton = null;
|
|
2355
|
+
}, 3000);
|
|
2356
|
+
}
|
|
2357
|
+
else if (captureState.step === 'back') {
|
|
2358
|
+
this.stateManager.setCapturedImages({
|
|
2359
|
+
back: {
|
|
2360
|
+
fullFrame: captureCanvas.toDataURL('image/png'),
|
|
2361
|
+
cropped: croppedCanvas.toDataURL('image/png')
|
|
2362
|
+
}
|
|
2363
|
+
});
|
|
2364
|
+
this.completeProcess(false);
|
|
2365
|
+
// Clear processing button after back capture completes
|
|
2366
|
+
this.processingButton = null;
|
|
2367
|
+
}
|
|
2368
|
+
// Return canvases to pool after use
|
|
2369
|
+
this.returnCanvasToPool(captureCanvas);
|
|
2370
|
+
this.returnCanvasToPool(croppedCanvas);
|
|
2371
|
+
}
|
|
1947
2372
|
async takeScreenshot() {
|
|
1948
2373
|
if (!this.videoRef || !this.lastDetectedBox)
|
|
1949
2374
|
return;
|
|
@@ -1977,10 +2402,15 @@ const JaakStamps = class {
|
|
|
1977
2402
|
});
|
|
1978
2403
|
// Check if document classification is enabled
|
|
1979
2404
|
if (this.useDocumentClassification) {
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
2405
|
+
try {
|
|
2406
|
+
const classification = await this.detectionService.classifyDocument(croppedCanvas);
|
|
2407
|
+
if (classification && classification.class === 'passport') {
|
|
2408
|
+
this.completeProcess(true);
|
|
2409
|
+
return;
|
|
2410
|
+
}
|
|
2411
|
+
}
|
|
2412
|
+
catch (classifyError) {
|
|
2413
|
+
// No cambiar a manual aquí, solo registrar el error y continuar
|
|
1984
2414
|
}
|
|
1985
2415
|
}
|
|
1986
2416
|
this.stateManager.updateCaptureState({
|
|
@@ -2017,6 +2447,135 @@ const JaakStamps = class {
|
|
|
2017
2447
|
cardOutline?.classList.remove('capturing');
|
|
2018
2448
|
}, 600);
|
|
2019
2449
|
}
|
|
2450
|
+
// Método simplificado para cambio a modo manual
|
|
2451
|
+
switchToManualMode() {
|
|
2452
|
+
// Only switch if detector is enabled and not already switched
|
|
2453
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2454
|
+
return;
|
|
2455
|
+
}
|
|
2456
|
+
// Detener todo el monitoreo de performance
|
|
2457
|
+
this.stopPerformanceMonitoring();
|
|
2458
|
+
// Liberar recursos de ONNX
|
|
2459
|
+
this.releaseOnnxResources();
|
|
2460
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2461
|
+
this.resetMaskToWhite();
|
|
2462
|
+
this.detectionBoxes = [];
|
|
2463
|
+
this.hasAutoSwitchedToManual = true;
|
|
2464
|
+
this.performanceDegradedMode = true;
|
|
2465
|
+
this.showManualCaptureButton = true;
|
|
2466
|
+
this.showPerformanceMessage = true;
|
|
2467
|
+
// Hide performance message after 5 seconds
|
|
2468
|
+
setTimeout(() => {
|
|
2469
|
+
this.showPerformanceMessage = false;
|
|
2470
|
+
}, 5000);
|
|
2471
|
+
}
|
|
2472
|
+
// Método para cambiar a modo manual por errores de ONNX
|
|
2473
|
+
switchToManualModeWithWarning(warningMessage) {
|
|
2474
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2475
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2476
|
+
return;
|
|
2477
|
+
}
|
|
2478
|
+
// No necesitamos liberar recursos ONNX porque no se han cargado aún
|
|
2479
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2480
|
+
this.resetMaskToWhite();
|
|
2481
|
+
this.detectionBoxes = [];
|
|
2482
|
+
// Cambiar a modo manual
|
|
2483
|
+
this.hasAutoSwitchedToManual = true;
|
|
2484
|
+
this.performanceDegradedMode = true;
|
|
2485
|
+
this.showManualCaptureButton = true;
|
|
2486
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
2487
|
+
// Actualizar el estado con mensaje informativo (no error)
|
|
2488
|
+
this.updateStatus('Modo manual activado', warningMessage, 'active');
|
|
2489
|
+
// Continuar con la configuración de cámara
|
|
2490
|
+
this.continueWithCameraSetup();
|
|
2491
|
+
}
|
|
2492
|
+
async continueWithCameraSetup() {
|
|
2493
|
+
try {
|
|
2494
|
+
// Paso 2: Detectar y configurar dispositivos
|
|
2495
|
+
this.updateStatus('Configurando cámara...', 'Buscando dispositivos disponibles', 'loading');
|
|
2496
|
+
try {
|
|
2497
|
+
await this.cameraService.enumerateDevices();
|
|
2498
|
+
}
|
|
2499
|
+
catch (enumerateError) {
|
|
2500
|
+
throw new Error(`Error al enumerar dispositivos: ${enumerateError.message}`);
|
|
2501
|
+
}
|
|
2502
|
+
try {
|
|
2503
|
+
await this.updateCameraInfoWithAutofocus();
|
|
2504
|
+
}
|
|
2505
|
+
catch (cameraInfoError) {
|
|
2506
|
+
throw new Error(`Error al actualizar información de cámara: ${cameraInfoError.message}`);
|
|
2507
|
+
}
|
|
2508
|
+
// Paso 3: Configurar cámara seleccionada
|
|
2509
|
+
this.updateStatus('Ajustando cámara...', 'Configurando calidad de imagen', 'loading');
|
|
2510
|
+
let stream;
|
|
2511
|
+
try {
|
|
2512
|
+
stream = await this.cameraService.setupCamera();
|
|
2513
|
+
}
|
|
2514
|
+
catch (setupError) {
|
|
2515
|
+
throw new Error(`Error al configurar cámara: ${setupError.message}`);
|
|
2516
|
+
}
|
|
2517
|
+
// Paso 4: Inicializar video
|
|
2518
|
+
this.updateStatus('Captura manual', 'Posicione su documento y use el botón para capturar', 'active');
|
|
2519
|
+
try {
|
|
2520
|
+
await this.initializeVideoStream(stream);
|
|
2521
|
+
}
|
|
2522
|
+
catch (videoError) {
|
|
2523
|
+
throw new Error(`Error al inicializar video: ${videoError.message}`);
|
|
2524
|
+
}
|
|
2525
|
+
// Paso 5: Calibrar máscara
|
|
2526
|
+
if (this.detectionContainer) {
|
|
2527
|
+
const container = this.detectionContainer.parentElement;
|
|
2528
|
+
const rect = container.getBoundingClientRect();
|
|
2529
|
+
this.updateMaskDimensions(rect);
|
|
2530
|
+
}
|
|
2531
|
+
// Finalizar
|
|
2532
|
+
this.startTime = Date.now();
|
|
2533
|
+
this.stateManager.updateCaptureState({ isLoading: false });
|
|
2534
|
+
// Mostrar botón manual (ya debe estar habilitado)
|
|
2535
|
+
this.showManualCaptureButton = true;
|
|
2536
|
+
}
|
|
2537
|
+
catch (error) {
|
|
2538
|
+
this.updateStatus('Error de cámara', error.message, 'error');
|
|
2539
|
+
throw error;
|
|
2540
|
+
}
|
|
2541
|
+
}
|
|
2542
|
+
switchToManualModeWithError(errorMessage) {
|
|
2543
|
+
// Solo cambiar si el detector está habilitado y no se ha cambiado ya
|
|
2544
|
+
if (!this.useDocumentDetector || this.hasAutoSwitchedToManual) {
|
|
2545
|
+
return;
|
|
2546
|
+
}
|
|
2547
|
+
// Liberar recursos de ONNX inmediatamente
|
|
2548
|
+
this.releaseOnnxResources();
|
|
2549
|
+
// Resetear máscara a blanco y limpiar detecciones
|
|
2550
|
+
this.resetMaskToWhite();
|
|
2551
|
+
this.detectionBoxes = [];
|
|
2552
|
+
// Cambiar a modo manual
|
|
2553
|
+
this.hasAutoSwitchedToManual = true;
|
|
2554
|
+
this.performanceDegradedMode = true;
|
|
2555
|
+
this.showManualCaptureButton = true;
|
|
2556
|
+
this.useDocumentDetector = false; // Desactivar detector automático
|
|
2557
|
+
// Actualizar el estado con mensaje de error
|
|
2558
|
+
this.updateStatus('Modo manual activado', errorMessage, 'error');
|
|
2559
|
+
// Ocultar mensaje después de 5 segundos
|
|
2560
|
+
setTimeout(() => {
|
|
2561
|
+
this.updateStatus('Captura manual', 'Use el botón para capturar', 'ready');
|
|
2562
|
+
}, 5000);
|
|
2563
|
+
}
|
|
2564
|
+
stopPerformanceMonitoring() {
|
|
2565
|
+
// Nota: No necesitamos limpiar más variables porque ya no se usarán
|
|
2566
|
+
// El sistema automáticamente saltará la lógica de performance una vez que hasAutoSwitchedToManual = true
|
|
2567
|
+
}
|
|
2568
|
+
// Método para liberar recursos de ONNX cuando se cambia a modo manual
|
|
2569
|
+
releaseOnnxResources() {
|
|
2570
|
+
try {
|
|
2571
|
+
// Liberar recursos del servicio de detección
|
|
2572
|
+
if (this.detectionService) {
|
|
2573
|
+
this.detectionService.cleanup();
|
|
2574
|
+
}
|
|
2575
|
+
}
|
|
2576
|
+
catch (error) {
|
|
2577
|
+
}
|
|
2578
|
+
}
|
|
2020
2579
|
completeProcess(skippedBack = false) {
|
|
2021
2580
|
this.stateManager.updateCaptureState({
|
|
2022
2581
|
step: 'completed',
|
|
@@ -2130,6 +2689,13 @@ const JaakStamps = class {
|
|
|
2130
2689
|
this.detectionBoxes = [];
|
|
2131
2690
|
this.alignmentStartTime = undefined;
|
|
2132
2691
|
this.hasDocumentDetected = false;
|
|
2692
|
+
// Reset sistema simplificado
|
|
2693
|
+
this.processedFramesCount = 0;
|
|
2694
|
+
this.slowFrameCount = 0;
|
|
2695
|
+
// Reset performance degradation state
|
|
2696
|
+
this.hasAutoSwitchedToManual = false;
|
|
2697
|
+
this.performanceDegradedMode = false;
|
|
2698
|
+
this.showPerformanceMessage = false;
|
|
2133
2699
|
if (this.alignmentTimer) {
|
|
2134
2700
|
clearTimeout(this.alignmentTimer);
|
|
2135
2701
|
this.alignmentTimer = undefined;
|
|
@@ -2233,6 +2799,24 @@ const JaakStamps = class {
|
|
|
2233
2799
|
// Performance is good, use base frame skip
|
|
2234
2800
|
return this.BASE_FRAME_SKIP;
|
|
2235
2801
|
}
|
|
2802
|
+
// Método para resetear la máscara a color blanco
|
|
2803
|
+
resetMaskToWhite() {
|
|
2804
|
+
const cardOutline = this.el.shadowRoot?.querySelector('.card-outline');
|
|
2805
|
+
const corners = this.el.shadowRoot?.querySelectorAll('.corner');
|
|
2806
|
+
const topSide = this.el.shadowRoot?.querySelector('.side-top');
|
|
2807
|
+
const rightSide = this.el.shadowRoot?.querySelector('.side-right');
|
|
2808
|
+
const bottomSide = this.el.shadowRoot?.querySelector('.side-bottom');
|
|
2809
|
+
const leftSide = this.el.shadowRoot?.querySelector('.side-left');
|
|
2810
|
+
// Remover todas las clases de alineación y estado perfecto
|
|
2811
|
+
cardOutline?.classList.remove('perfect-match');
|
|
2812
|
+
corners?.forEach(corner => corner.classList.remove('perfect-match'));
|
|
2813
|
+
topSide?.classList.remove('aligned');
|
|
2814
|
+
rightSide?.classList.remove('aligned');
|
|
2815
|
+
bottomSide?.classList.remove('aligned');
|
|
2816
|
+
leftSide?.classList.remove('aligned');
|
|
2817
|
+
// Resetear estado de alineación
|
|
2818
|
+
this.sideAlignment = { top: false, right: false, bottom: false, left: false };
|
|
2819
|
+
}
|
|
2236
2820
|
// Canvas pool management for screenshots
|
|
2237
2821
|
getPooledCanvas(width, height) {
|
|
2238
2822
|
// Try to find a canvas with matching dimensions
|