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