@jarve/bug-reporter 0.1.1 → 0.3.0
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 +8 -7
- package/dist/index.d.mts +5 -1
- package/dist/index.d.ts +5 -1
- package/dist/index.js +299 -152
- package/dist/index.js.map +1 -1
- package/dist/index.mjs +299 -152
- package/dist/index.mjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -57,16 +57,21 @@ function cn(...inputs) {
|
|
|
57
57
|
|
|
58
58
|
// src/floating-button.tsx
|
|
59
59
|
var import_jsx_runtime = require("react/jsx-runtime");
|
|
60
|
-
function FloatingButton({ isActive, onClick }) {
|
|
60
|
+
function FloatingButton({ isActive, onClick, position = "right" }) {
|
|
61
|
+
const sideClasses = position === "left" ? "left-4 md:left-6" : "right-4 md:right-6";
|
|
61
62
|
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
62
63
|
"button",
|
|
63
64
|
{
|
|
64
65
|
onClick,
|
|
65
66
|
className: cn(
|
|
66
|
-
"fixed
|
|
67
|
-
"hover:scale-110 focus:
|
|
68
|
-
|
|
69
|
-
"bottom-4
|
|
67
|
+
"fixed z-[9999] flex items-center justify-center rounded-full shadow-lg transition-all duration-200",
|
|
68
|
+
"hover:scale-110 focus:ring-2 focus:ring-offset-2 focus:outline-none",
|
|
69
|
+
// size + vertical position
|
|
70
|
+
"bottom-4 h-11 w-11 md:bottom-6 md:h-12 md:w-12",
|
|
71
|
+
// horizontal side
|
|
72
|
+
sideClasses,
|
|
73
|
+
// active vs idle colors
|
|
74
|
+
isActive ? "animate-pulse bg-red-500 text-white focus:ring-red-400" : "bg-indigo-600 text-white hover:bg-indigo-700 focus:ring-indigo-400"
|
|
70
75
|
),
|
|
71
76
|
title: isActive ? "Cancel bug capture" : "Report a bug",
|
|
72
77
|
"aria-label": isActive ? "Cancel bug capture" : "Report a bug",
|
|
@@ -134,7 +139,18 @@ function buildSelectorPath(element, stopAt) {
|
|
|
134
139
|
}
|
|
135
140
|
return parts.join(" > ");
|
|
136
141
|
}
|
|
137
|
-
function
|
|
142
|
+
function isTouchCapable() {
|
|
143
|
+
return "ontouchstart" in window || navigator.maxTouchPoints > 0;
|
|
144
|
+
}
|
|
145
|
+
function extractCoordinates(e) {
|
|
146
|
+
return {
|
|
147
|
+
pageX: e.pageX,
|
|
148
|
+
pageY: e.pageY,
|
|
149
|
+
clientX: e.clientX,
|
|
150
|
+
clientY: e.clientY
|
|
151
|
+
};
|
|
152
|
+
}
|
|
153
|
+
function collectElementInfo(target, section, coords) {
|
|
138
154
|
const sectionRect = section.getBoundingClientRect();
|
|
139
155
|
const dataAttributes = {};
|
|
140
156
|
for (const attr of Array.from(target.attributes)) {
|
|
@@ -150,10 +166,10 @@ function collectElementInfo(target, section, event) {
|
|
|
150
166
|
ariaLabel: target.getAttribute("aria-label") || null,
|
|
151
167
|
dataAttributes,
|
|
152
168
|
selectorPath: buildSelectorPath(target, section),
|
|
153
|
-
clickX:
|
|
154
|
-
clickY:
|
|
155
|
-
relativeClickX:
|
|
156
|
-
relativeClickY:
|
|
169
|
+
clickX: coords.pageX,
|
|
170
|
+
clickY: coords.pageY,
|
|
171
|
+
relativeClickX: coords.clientX - sectionRect.left,
|
|
172
|
+
relativeClickY: coords.clientY - sectionRect.top
|
|
157
173
|
};
|
|
158
174
|
}
|
|
159
175
|
function collectMetadata(sectionElement, siteId, reporterName, reporterEmail, clickedElement) {
|
|
@@ -185,8 +201,7 @@ var errorListener = null;
|
|
|
185
201
|
var rejectionListener = null;
|
|
186
202
|
function startCapturing() {
|
|
187
203
|
if (isCapturing) return;
|
|
188
|
-
if (console.error.__bugReporterPatched)
|
|
189
|
-
return;
|
|
204
|
+
if (console.error.__bugReporterPatched) return;
|
|
190
205
|
isCapturing = true;
|
|
191
206
|
capturedErrors = [];
|
|
192
207
|
originalConsoleError = console.error;
|
|
@@ -378,6 +393,15 @@ function clearCapturedNetworkErrors() {
|
|
|
378
393
|
|
|
379
394
|
// src/capture-overlay.tsx
|
|
380
395
|
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
396
|
+
function dataUrlToBlob(dataUrl) {
|
|
397
|
+
var _a;
|
|
398
|
+
const [header, base64] = dataUrl.split(",");
|
|
399
|
+
const mime = ((_a = header.match(/:(.*?);/)) == null ? void 0 : _a[1]) || "image/png";
|
|
400
|
+
const bytes = atob(base64);
|
|
401
|
+
const arr = new Uint8Array(bytes.length);
|
|
402
|
+
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
403
|
+
return new Blob([arr], { type: mime });
|
|
404
|
+
}
|
|
381
405
|
function CaptureOverlay({
|
|
382
406
|
isActive,
|
|
383
407
|
siteId,
|
|
@@ -389,48 +413,30 @@ function CaptureOverlay({
|
|
|
389
413
|
const [hoveredElement, setHoveredElement] = (0, import_react.useState)(null);
|
|
390
414
|
const [hoveredRect, setHoveredRect] = (0, import_react.useState)(null);
|
|
391
415
|
const [isCapturing3, setIsCapturing] = (0, import_react.useState)(false);
|
|
416
|
+
const [isTouchMode, setIsTouchMode] = (0, import_react.useState)(false);
|
|
417
|
+
const [selectedSection, setSelectedSection] = (0, import_react.useState)(null);
|
|
418
|
+
const [selectedRect, setSelectedRect] = (0, import_react.useState)(null);
|
|
419
|
+
const [selectedTarget, setSelectedTarget] = (0, import_react.useState)(null);
|
|
392
420
|
const overlayRef = (0, import_react.useRef)(null);
|
|
393
421
|
const hoveredElementRef = (0, import_react.useRef)(null);
|
|
394
422
|
const rafRef = (0, import_react.useRef)(null);
|
|
395
|
-
const
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
setHoveredElement(null);
|
|
405
|
-
setHoveredRect(null);
|
|
406
|
-
hoveredElementRef.current = null;
|
|
407
|
-
return;
|
|
408
|
-
}
|
|
409
|
-
const section = getNearestSection(target);
|
|
410
|
-
setHoveredElement(section);
|
|
411
|
-
hoveredElementRef.current = section;
|
|
412
|
-
setHoveredRect(section ? section.getBoundingClientRect() : null);
|
|
413
|
-
});
|
|
414
|
-
},
|
|
415
|
-
[isActive, isCapturing3]
|
|
416
|
-
);
|
|
417
|
-
const handleClick = (0, import_react.useCallback)(
|
|
418
|
-
async (e) => {
|
|
419
|
-
var _a, _b;
|
|
420
|
-
if (!isActive || isCapturing3) return;
|
|
421
|
-
const target = e.target;
|
|
422
|
-
if (!(target instanceof HTMLElement)) return;
|
|
423
|
-
if (target.closest("[data-bug-reporter]")) return;
|
|
424
|
-
e.preventDefault();
|
|
425
|
-
e.stopPropagation();
|
|
426
|
-
const section = getNearestSection(target);
|
|
427
|
-
if (!section) return;
|
|
428
|
-
const elementInfo = collectElementInfo(target, section, e);
|
|
423
|
+
const touchCoordsRef = (0, import_react.useRef)(null);
|
|
424
|
+
(0, import_react.useEffect)(() => {
|
|
425
|
+
if (isActive) {
|
|
426
|
+
setIsTouchMode(isTouchCapable());
|
|
427
|
+
}
|
|
428
|
+
}, [isActive]);
|
|
429
|
+
const captureScreenshot = (0, import_react.useCallback)(
|
|
430
|
+
async (section, target, coords) => {
|
|
431
|
+
const elementInfo = collectElementInfo(target, section, coords);
|
|
429
432
|
setIsCapturing(true);
|
|
430
433
|
try {
|
|
431
434
|
setHoveredElement(null);
|
|
432
435
|
setHoveredRect(null);
|
|
433
436
|
hoveredElementRef.current = null;
|
|
437
|
+
setSelectedSection(null);
|
|
438
|
+
setSelectedRect(null);
|
|
439
|
+
setSelectedTarget(null);
|
|
434
440
|
await new Promise((r) => setTimeout(r, 50));
|
|
435
441
|
const MAX_DIMENSION = 2e3;
|
|
436
442
|
const sectionRect = section.getBoundingClientRect();
|
|
@@ -440,12 +446,7 @@ function CaptureOverlay({
|
|
|
440
446
|
pixelRatio,
|
|
441
447
|
skipFonts: true
|
|
442
448
|
});
|
|
443
|
-
const
|
|
444
|
-
const mime = ((_a = header.match(/:(.*?);/)) == null ? void 0 : _a[1]) || "image/png";
|
|
445
|
-
const bytes = atob(base64);
|
|
446
|
-
const arr = new Uint8Array(bytes.length);
|
|
447
|
-
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
448
|
-
const blob = new Blob([arr], { type: mime });
|
|
449
|
+
const blob = dataUrlToBlob(dataUrl);
|
|
449
450
|
const metadata = collectMetadata(
|
|
450
451
|
section,
|
|
451
452
|
siteId,
|
|
@@ -457,7 +458,10 @@ function CaptureOverlay({
|
|
|
457
458
|
const networkErrors = getCapturedNetworkErrors();
|
|
458
459
|
onCapture({ screenshot: blob, metadata, consoleErrors, networkErrors });
|
|
459
460
|
} catch (err) {
|
|
460
|
-
console.warn(
|
|
461
|
+
console.warn(
|
|
462
|
+
"Bug reporter: first capture attempt failed, retrying with simpler settings",
|
|
463
|
+
err
|
|
464
|
+
);
|
|
461
465
|
try {
|
|
462
466
|
const dataUrl = await (0, import_html_to_image.toPng)(section, {
|
|
463
467
|
quality: 0.6,
|
|
@@ -465,12 +469,7 @@ function CaptureOverlay({
|
|
|
465
469
|
skipFonts: true,
|
|
466
470
|
cacheBust: true
|
|
467
471
|
});
|
|
468
|
-
const
|
|
469
|
-
const mime = ((_b = header.match(/:(.*?);/)) == null ? void 0 : _b[1]) || "image/png";
|
|
470
|
-
const bytes = atob(base64);
|
|
471
|
-
const arr = new Uint8Array(bytes.length);
|
|
472
|
-
for (let i = 0; i < bytes.length; i++) arr[i] = bytes.charCodeAt(i);
|
|
473
|
-
const retryBlob = new Blob([arr], { type: mime });
|
|
472
|
+
const retryBlob = dataUrlToBlob(dataUrl);
|
|
474
473
|
const metadata = collectMetadata(
|
|
475
474
|
section,
|
|
476
475
|
siteId,
|
|
@@ -481,7 +480,7 @@ function CaptureOverlay({
|
|
|
481
480
|
const consoleErrors = getCapturedErrors();
|
|
482
481
|
const networkErrors = getCapturedNetworkErrors();
|
|
483
482
|
onCapture({ screenshot: retryBlob, metadata, consoleErrors, networkErrors });
|
|
484
|
-
} catch (
|
|
483
|
+
} catch (e) {
|
|
485
484
|
console.error("Bug reporter: screenshot capture failed after retry");
|
|
486
485
|
const metadata = collectMetadata(
|
|
487
486
|
section,
|
|
@@ -503,7 +502,75 @@ function CaptureOverlay({
|
|
|
503
502
|
setIsCapturing(false);
|
|
504
503
|
}
|
|
505
504
|
},
|
|
506
|
-
[
|
|
505
|
+
[siteId, reporterName, reporterEmail, onCapture]
|
|
506
|
+
);
|
|
507
|
+
const handleMouseMove = (0, import_react.useCallback)(
|
|
508
|
+
(e) => {
|
|
509
|
+
if (!isActive || isCapturing3 || isTouchMode) return;
|
|
510
|
+
if (rafRef.current) return;
|
|
511
|
+
rafRef.current = requestAnimationFrame(() => {
|
|
512
|
+
rafRef.current = null;
|
|
513
|
+
const target = e.target;
|
|
514
|
+
if (!(target instanceof HTMLElement)) return;
|
|
515
|
+
if (target.closest("[data-bug-reporter]")) {
|
|
516
|
+
setHoveredElement(null);
|
|
517
|
+
setHoveredRect(null);
|
|
518
|
+
hoveredElementRef.current = null;
|
|
519
|
+
return;
|
|
520
|
+
}
|
|
521
|
+
const section = getNearestSection(target);
|
|
522
|
+
setHoveredElement(section);
|
|
523
|
+
hoveredElementRef.current = section;
|
|
524
|
+
setHoveredRect(section ? section.getBoundingClientRect() : null);
|
|
525
|
+
});
|
|
526
|
+
},
|
|
527
|
+
[isActive, isCapturing3, isTouchMode]
|
|
528
|
+
);
|
|
529
|
+
const handleClick = (0, import_react.useCallback)(
|
|
530
|
+
async (e) => {
|
|
531
|
+
if (!isActive || isCapturing3 || isTouchMode) return;
|
|
532
|
+
const target = e.target;
|
|
533
|
+
if (!(target instanceof HTMLElement)) return;
|
|
534
|
+
if (target.closest("[data-bug-reporter]")) return;
|
|
535
|
+
e.preventDefault();
|
|
536
|
+
e.stopPropagation();
|
|
537
|
+
const section = getNearestSection(target);
|
|
538
|
+
if (!section) return;
|
|
539
|
+
await captureScreenshot(section, target, extractCoordinates(e));
|
|
540
|
+
},
|
|
541
|
+
[isActive, isCapturing3, isTouchMode, captureScreenshot]
|
|
542
|
+
);
|
|
543
|
+
const handleTouchEnd = (0, import_react.useCallback)(
|
|
544
|
+
(e) => {
|
|
545
|
+
if (!isActive || isCapturing3) return;
|
|
546
|
+
const touch = e.changedTouches[0];
|
|
547
|
+
if (!touch) return;
|
|
548
|
+
const target = document.elementFromPoint(touch.clientX, touch.clientY);
|
|
549
|
+
if (!(target instanceof HTMLElement)) return;
|
|
550
|
+
if (target.closest("[data-bug-reporter]")) return;
|
|
551
|
+
const section = getNearestSection(target);
|
|
552
|
+
if (!section) return;
|
|
553
|
+
setSelectedSection(section);
|
|
554
|
+
setSelectedRect(section.getBoundingClientRect());
|
|
555
|
+
setSelectedTarget(target);
|
|
556
|
+
touchCoordsRef.current = extractCoordinates(touch);
|
|
557
|
+
},
|
|
558
|
+
[isActive, isCapturing3]
|
|
559
|
+
);
|
|
560
|
+
const handleConfirmCapture = (0, import_react.useCallback)(async () => {
|
|
561
|
+
if (!selectedSection || !selectedTarget || !touchCoordsRef.current) return;
|
|
562
|
+
await captureScreenshot(selectedSection, selectedTarget, touchCoordsRef.current);
|
|
563
|
+
}, [selectedSection, selectedTarget, captureScreenshot]);
|
|
564
|
+
const handlePointerDown = (0, import_react.useCallback)(
|
|
565
|
+
(e) => {
|
|
566
|
+
if (!isActive) return;
|
|
567
|
+
if (e.pointerType === "touch") {
|
|
568
|
+
setIsTouchMode(true);
|
|
569
|
+
} else if (e.pointerType === "mouse") {
|
|
570
|
+
setIsTouchMode(false);
|
|
571
|
+
}
|
|
572
|
+
},
|
|
573
|
+
[isActive]
|
|
507
574
|
);
|
|
508
575
|
const handleKeyDown = (0, import_react.useCallback)(
|
|
509
576
|
(e) => {
|
|
@@ -516,60 +583,131 @@ function CaptureOverlay({
|
|
|
516
583
|
[isActive, onCancel]
|
|
517
584
|
);
|
|
518
585
|
const handleScroll = (0, import_react.useCallback)(() => {
|
|
519
|
-
if (
|
|
520
|
-
|
|
521
|
-
|
|
586
|
+
if (hoveredElementRef.current) {
|
|
587
|
+
setHoveredRect(hoveredElementRef.current.getBoundingClientRect());
|
|
588
|
+
}
|
|
589
|
+
if (selectedSection) {
|
|
590
|
+
setSelectedRect(selectedSection.getBoundingClientRect());
|
|
591
|
+
}
|
|
592
|
+
}, [selectedSection]);
|
|
522
593
|
(0, import_react.useEffect)(() => {
|
|
523
594
|
if (!isActive) {
|
|
524
595
|
setHoveredElement(null);
|
|
525
596
|
setHoveredRect(null);
|
|
526
597
|
hoveredElementRef.current = null;
|
|
598
|
+
setSelectedSection(null);
|
|
599
|
+
setSelectedRect(null);
|
|
600
|
+
setSelectedTarget(null);
|
|
601
|
+
touchCoordsRef.current = null;
|
|
527
602
|
return;
|
|
528
603
|
}
|
|
529
|
-
document.addEventListener("
|
|
530
|
-
document.addEventListener("click", handleClick, true);
|
|
604
|
+
document.addEventListener("pointerdown", handlePointerDown);
|
|
531
605
|
document.addEventListener("keydown", handleKeyDown);
|
|
532
606
|
window.addEventListener("scroll", handleScroll, { passive: true });
|
|
607
|
+
if (isTouchMode) {
|
|
608
|
+
document.addEventListener("touchend", handleTouchEnd, { passive: true });
|
|
609
|
+
} else {
|
|
610
|
+
document.addEventListener("mousemove", handleMouseMove, true);
|
|
611
|
+
document.addEventListener("click", handleClick, true);
|
|
612
|
+
}
|
|
533
613
|
return () => {
|
|
534
|
-
document.removeEventListener("
|
|
535
|
-
document.removeEventListener("click", handleClick, true);
|
|
614
|
+
document.removeEventListener("pointerdown", handlePointerDown);
|
|
536
615
|
document.removeEventListener("keydown", handleKeyDown);
|
|
537
616
|
window.removeEventListener("scroll", handleScroll);
|
|
617
|
+
document.removeEventListener("touchend", handleTouchEnd);
|
|
618
|
+
document.removeEventListener("mousemove", handleMouseMove, true);
|
|
619
|
+
document.removeEventListener("click", handleClick, true);
|
|
538
620
|
if (rafRef.current) cancelAnimationFrame(rafRef.current);
|
|
539
621
|
};
|
|
540
|
-
}, [
|
|
541
|
-
|
|
622
|
+
}, [
|
|
623
|
+
isActive,
|
|
624
|
+
isTouchMode,
|
|
625
|
+
handleMouseMove,
|
|
626
|
+
handleClick,
|
|
627
|
+
handleTouchEnd,
|
|
628
|
+
handlePointerDown,
|
|
629
|
+
handleKeyDown,
|
|
630
|
+
handleScroll
|
|
631
|
+
]);
|
|
632
|
+
const highlightRect = isTouchMode ? selectedRect : hoveredRect;
|
|
633
|
+
const showHighlight = isTouchMode ? !!selectedSection : !!hoveredElement && !!hoveredRect;
|
|
634
|
+
if (!isActive) return null;
|
|
542
635
|
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
543
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.
|
|
636
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
544
637
|
"div",
|
|
545
638
|
{
|
|
546
639
|
"data-bug-reporter": true,
|
|
547
640
|
role: "alert",
|
|
548
641
|
"aria-live": "assertive",
|
|
549
|
-
className: "fixed top-0
|
|
550
|
-
children: [
|
|
551
|
-
"
|
|
552
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
642
|
+
className: "fixed top-0 right-0 left-0 z-[10000] flex items-center justify-center gap-3 bg-indigo-600 px-4 py-2 text-center text-sm font-medium text-white",
|
|
643
|
+
children: isTouchMode ? /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
644
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { children: "Tap the section with the bug" }),
|
|
645
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
646
|
+
"button",
|
|
647
|
+
{
|
|
648
|
+
onClick: onCancel,
|
|
649
|
+
className: "min-h-[44px] rounded-md bg-white/20 px-3 py-1 text-sm font-medium",
|
|
650
|
+
children: "Cancel"
|
|
651
|
+
}
|
|
652
|
+
)
|
|
653
|
+
] }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(import_jsx_runtime2.Fragment, { children: [
|
|
654
|
+
"Click on the section with the bug. Press",
|
|
655
|
+
" ",
|
|
656
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("kbd", { className: "mx-1 rounded bg-indigo-800 px-1.5 py-0.5 text-xs", children: "Esc" }),
|
|
553
657
|
" to cancel."
|
|
554
|
-
]
|
|
658
|
+
] })
|
|
555
659
|
}
|
|
556
660
|
),
|
|
557
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
661
|
+
showHighlight && highlightRect && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
558
662
|
"div",
|
|
559
663
|
{
|
|
560
664
|
ref: overlayRef,
|
|
561
665
|
"data-bug-reporter": true,
|
|
562
|
-
className: "
|
|
666
|
+
className: "pointer-events-none fixed z-[9998] rounded-sm border-2 border-indigo-500 transition-all duration-150 ease-out",
|
|
563
667
|
style: {
|
|
564
|
-
top:
|
|
565
|
-
left:
|
|
566
|
-
width:
|
|
567
|
-
height:
|
|
668
|
+
top: highlightRect.top - 2,
|
|
669
|
+
left: highlightRect.left - 2,
|
|
670
|
+
width: highlightRect.width + 4,
|
|
671
|
+
height: highlightRect.height + 4,
|
|
568
672
|
backgroundColor: "rgba(99, 102, 241, 0.08)"
|
|
569
673
|
}
|
|
570
674
|
}
|
|
571
675
|
),
|
|
572
|
-
|
|
676
|
+
isTouchMode && selectedSection && !isCapturing3 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
677
|
+
"div",
|
|
678
|
+
{
|
|
679
|
+
"data-bug-reporter": true,
|
|
680
|
+
className: "fixed right-0 bottom-0 left-0 z-[10000] border-t border-gray-200 bg-white shadow-lg",
|
|
681
|
+
style: { paddingBottom: "env(safe-area-inset-bottom, 0px)" },
|
|
682
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex items-center justify-between gap-3 px-4 py-3", children: [
|
|
683
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "truncate text-sm font-medium text-gray-900", children: "Capture this section?" }),
|
|
684
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex shrink-0 gap-2", children: [
|
|
685
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
686
|
+
"button",
|
|
687
|
+
{
|
|
688
|
+
onClick: () => {
|
|
689
|
+
setSelectedSection(null);
|
|
690
|
+
setSelectedRect(null);
|
|
691
|
+
setSelectedTarget(null);
|
|
692
|
+
touchCoordsRef.current = null;
|
|
693
|
+
},
|
|
694
|
+
className: "min-h-[44px] rounded-md border border-gray-300 px-4 text-sm font-medium text-gray-700",
|
|
695
|
+
children: "Cancel"
|
|
696
|
+
}
|
|
697
|
+
),
|
|
698
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
|
|
699
|
+
"button",
|
|
700
|
+
{
|
|
701
|
+
onClick: handleConfirmCapture,
|
|
702
|
+
className: "min-h-[44px] rounded-md bg-indigo-600 px-4 text-sm font-medium text-white",
|
|
703
|
+
children: "Capture"
|
|
704
|
+
}
|
|
705
|
+
)
|
|
706
|
+
] })
|
|
707
|
+
] })
|
|
708
|
+
}
|
|
709
|
+
),
|
|
710
|
+
!isTouchMode && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("style", { children: `* { cursor: crosshair !important; }` })
|
|
573
711
|
] });
|
|
574
712
|
}
|
|
575
713
|
|
|
@@ -635,7 +773,9 @@ function ReportModal({
|
|
|
635
773
|
})
|
|
636
774
|
});
|
|
637
775
|
if (response.status === 401) {
|
|
638
|
-
console.error(
|
|
776
|
+
console.error(
|
|
777
|
+
"Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
|
|
778
|
+
);
|
|
639
779
|
setMessages([
|
|
640
780
|
{
|
|
641
781
|
role: "assistant",
|
|
@@ -732,9 +872,7 @@ function ReportModal({
|
|
|
732
872
|
setModalState("submitted");
|
|
733
873
|
} catch (err) {
|
|
734
874
|
console.error("Bug reporter: failed to submit report", err);
|
|
735
|
-
setErrorMessage(
|
|
736
|
-
err instanceof Error ? err.message : "Failed to submit report"
|
|
737
|
-
);
|
|
875
|
+
setErrorMessage(err instanceof Error ? err.message : "Failed to submit report");
|
|
738
876
|
setModalState("error");
|
|
739
877
|
}
|
|
740
878
|
},
|
|
@@ -747,10 +885,7 @@ function ReportModal({
|
|
|
747
885
|
if (!input.trim() || isLoading || !captureResult) return;
|
|
748
886
|
const userMessage = input.trim();
|
|
749
887
|
setInput("");
|
|
750
|
-
const newMessages = [
|
|
751
|
-
...messages,
|
|
752
|
-
{ role: "user", content: userMessage }
|
|
753
|
-
];
|
|
888
|
+
const newMessages = [...messages, { role: "user", content: userMessage }];
|
|
754
889
|
setMessages(newMessages);
|
|
755
890
|
setIsLoading(true);
|
|
756
891
|
try {
|
|
@@ -766,7 +901,9 @@ function ReportModal({
|
|
|
766
901
|
})
|
|
767
902
|
});
|
|
768
903
|
if (response.status === 401) {
|
|
769
|
-
console.error(
|
|
904
|
+
console.error(
|
|
905
|
+
"Bug reporter: invalid or missing API key. Check your BugReporter apiKey prop."
|
|
906
|
+
);
|
|
770
907
|
setMessages([
|
|
771
908
|
...newMessages,
|
|
772
909
|
{
|
|
@@ -778,10 +915,7 @@ function ReportModal({
|
|
|
778
915
|
}
|
|
779
916
|
if (!response.ok) throw new Error("Failed to get AI response");
|
|
780
917
|
const data = await response.json();
|
|
781
|
-
setMessages([
|
|
782
|
-
...newMessages,
|
|
783
|
-
{ role: "assistant", content: data.message }
|
|
784
|
-
]);
|
|
918
|
+
setMessages([...newMessages, { role: "assistant", content: data.message }]);
|
|
785
919
|
if (data.readyToSubmit && data.structuredReport) {
|
|
786
920
|
await submitReport(
|
|
787
921
|
[...newMessages, { role: "assistant", content: data.message }],
|
|
@@ -814,7 +948,7 @@ function ReportModal({
|
|
|
814
948
|
onClose();
|
|
815
949
|
}
|
|
816
950
|
function handleKeyDown(e) {
|
|
817
|
-
if (e.key === "Enter" && !e.shiftKey) {
|
|
951
|
+
if (e.key === "Enter" && !e.shiftKey && !isTouchCapable()) {
|
|
818
952
|
e.preventDefault();
|
|
819
953
|
sendMessage();
|
|
820
954
|
}
|
|
@@ -832,65 +966,66 @@ function ReportModal({
|
|
|
832
966
|
"div",
|
|
833
967
|
{
|
|
834
968
|
className: cn(
|
|
835
|
-
"
|
|
836
|
-
"w-full max-w-lg
|
|
837
|
-
"max-[768px]:mx-0 max-[768px]:
|
|
969
|
+
"flex flex-col overflow-hidden rounded-xl border border-gray-200 bg-white shadow-2xl dark:border-gray-800 dark:bg-gray-950",
|
|
970
|
+
"mx-4 w-full max-w-lg",
|
|
971
|
+
"max-[768px]:mx-0 max-[768px]:h-full max-[768px]:max-w-none max-[768px]:rounded-none",
|
|
838
972
|
"min-[769px]:max-h-[85vh]"
|
|
839
973
|
),
|
|
840
974
|
children: [
|
|
841
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between
|
|
975
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center justify-between border-b border-gray-200 bg-gray-50/30 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/30", children: [
|
|
842
976
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { children: [
|
|
843
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "font-semibold text-
|
|
977
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h2", { className: "text-sm font-semibold text-gray-900 dark:text-gray-100", children: "Bug Report" }),
|
|
844
978
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-gray-500 dark:text-gray-400", children: siteId })
|
|
845
979
|
] }),
|
|
846
980
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
847
981
|
"button",
|
|
848
982
|
{
|
|
849
983
|
onClick: handleClose,
|
|
850
|
-
className: "p-1.5
|
|
984
|
+
className: "rounded-md p-1.5 transition-colors hover:bg-gray-100 dark:hover:bg-gray-800",
|
|
851
985
|
"aria-label": "Close",
|
|
852
986
|
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "h-4 w-4 text-gray-600 dark:text-gray-400" })
|
|
853
987
|
}
|
|
854
988
|
)
|
|
855
989
|
] }),
|
|
856
|
-
screenshotUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "
|
|
990
|
+
screenshotUrl && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-b border-gray-200 bg-gray-50/10 px-4 py-3 dark:border-gray-800 dark:bg-gray-900/10", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
857
991
|
"img",
|
|
858
992
|
{
|
|
859
993
|
src: screenshotUrl,
|
|
860
994
|
alt: "Captured section",
|
|
861
|
-
className: "
|
|
995
|
+
className: "max-h-40 w-full rounded-md border border-gray-200 object-contain dark:border-gray-700"
|
|
862
996
|
}
|
|
863
997
|
) }),
|
|
864
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex-1 overflow-y-auto px-4 py-3
|
|
865
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.CheckCircle2, { className: "h-12 w-12 text-green-500
|
|
866
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "font-semibold text-
|
|
867
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
868
|
-
"Reference:
|
|
869
|
-
|
|
998
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "min-h-0 flex-1 space-y-3 overflow-y-auto px-4 py-3", children: modalState === "submitted" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
|
|
999
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.CheckCircle2, { className: "mb-3 h-12 w-12 text-green-500" }),
|
|
1000
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Report Submitted" }),
|
|
1001
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: [
|
|
1002
|
+
"Reference:",
|
|
1003
|
+
" ",
|
|
1004
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("code", { className: "rounded bg-gray-100 px-1.5 py-0.5 text-xs dark:bg-gray-800", children: reportId == null ? void 0 : reportId.slice(0, 8) })
|
|
870
1005
|
] }),
|
|
871
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
1006
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-2 text-sm text-gray-500 dark:text-gray-400", children: "Thanks for the report \u2014 we'll look into it." }),
|
|
872
1007
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
873
1008
|
"button",
|
|
874
1009
|
{
|
|
875
1010
|
onClick: handleClose,
|
|
876
|
-
className: "mt-4 px-4 py-2
|
|
1011
|
+
className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
|
|
877
1012
|
children: "Done"
|
|
878
1013
|
}
|
|
879
1014
|
)
|
|
880
1015
|
] }) : modalState === "error" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8 text-center", children: [
|
|
881
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "h-12 w-12 text-red-500
|
|
882
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "font-semibold text-
|
|
883
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400
|
|
1016
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.X, { className: "mb-3 h-12 w-12 text-red-500" }),
|
|
1017
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("h3", { className: "text-lg font-semibold text-gray-900 dark:text-gray-100", children: "Submission Failed" }),
|
|
1018
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "mt-1 text-sm text-gray-500 dark:text-gray-400", children: errorMessage || "Something went wrong. Please try again." }),
|
|
884
1019
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
885
1020
|
"button",
|
|
886
1021
|
{
|
|
887
1022
|
onClick: () => setModalState("chatting"),
|
|
888
|
-
className: "mt-4 px-4 py-2
|
|
1023
|
+
className: "mt-4 rounded-md bg-indigo-600 px-4 py-2 text-sm text-white transition-colors hover:bg-indigo-700",
|
|
889
1024
|
children: "Try Again"
|
|
890
1025
|
}
|
|
891
1026
|
)
|
|
892
1027
|
] }) : modalState === "submitting" ? /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col items-center justify-center py-8", children: [
|
|
893
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "h-8 w-8 animate-spin text-indigo-500
|
|
1028
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "mb-3 h-8 w-8 animate-spin text-indigo-500" }),
|
|
894
1029
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Submitting your report..." })
|
|
895
1030
|
] }) : /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(import_jsx_runtime3.Fragment, { children: [
|
|
896
1031
|
(captureResult == null ? void 0 : captureResult.screenshot.size) === 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("p", { className: "text-xs text-amber-600", children: "Screenshot could not be captured. Please describe the visual issue in detail." }),
|
|
@@ -899,42 +1034,53 @@ function ReportModal({
|
|
|
899
1034
|
{
|
|
900
1035
|
className: cn(
|
|
901
1036
|
"text-sm leading-relaxed",
|
|
902
|
-
msg.role === "assistant" ? "
|
|
1037
|
+
msg.role === "assistant" ? "rounded-lg bg-gray-100/50 p-3 text-gray-900 dark:bg-gray-800/50 dark:text-gray-100" : "ml-8 rounded-lg bg-indigo-600 p-3 text-white"
|
|
903
1038
|
),
|
|
904
1039
|
children: msg.content
|
|
905
1040
|
},
|
|
906
1041
|
i
|
|
907
1042
|
)),
|
|
908
|
-
isLoading && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "bg-gray-100/50 dark:bg-gray-800/50
|
|
1043
|
+
isLoading && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 rounded-lg bg-gray-100/50 p-3 dark:bg-gray-800/50", children: [
|
|
909
1044
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Loader2, { className: "h-3.5 w-3.5 animate-spin text-gray-500" }),
|
|
910
1045
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm text-gray-500 dark:text-gray-400", children: "Thinking..." })
|
|
911
1046
|
] }),
|
|
912
1047
|
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { ref: chatEndRef })
|
|
913
1048
|
] }) }),
|
|
914
|
-
modalState === "chatting" && /* @__PURE__ */ (0, import_jsx_runtime3.
|
|
915
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
|
|
922
|
-
|
|
923
|
-
|
|
924
|
-
|
|
925
|
-
|
|
926
|
-
|
|
927
|
-
|
|
928
|
-
|
|
929
|
-
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("
|
|
930
|
-
|
|
1049
|
+
modalState === "chatting" && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "border-t border-gray-200 px-4 py-3 dark:border-gray-800", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
1050
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
1051
|
+
"textarea",
|
|
1052
|
+
{
|
|
1053
|
+
ref: inputRef,
|
|
1054
|
+
value: input,
|
|
1055
|
+
onChange: (e) => setInput(e.target.value),
|
|
1056
|
+
onKeyDown: handleKeyDown,
|
|
1057
|
+
placeholder: "Describe what's wrong...",
|
|
1058
|
+
rows: 2,
|
|
1059
|
+
className: "w-full resize-none rounded-md border border-gray-300 bg-white px-3 py-2 text-sm text-gray-900 focus:border-transparent focus:ring-2 focus:ring-indigo-400 focus:outline-none dark:border-gray-700 dark:bg-gray-950 dark:text-gray-100",
|
|
1060
|
+
disabled: isLoading
|
|
1061
|
+
}
|
|
1062
|
+
),
|
|
1063
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex flex-col gap-2", children: [
|
|
1064
|
+
captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "flex-1 text-xs text-amber-600", children: [
|
|
1065
|
+
[
|
|
1066
|
+
captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
|
|
1067
|
+
captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
|
|
1068
|
+
].filter(Boolean).join(" + "),
|
|
1069
|
+
" ",
|
|
1070
|
+
"captured \u2014 these will be included in the report."
|
|
1071
|
+
] }),
|
|
1072
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex justify-end gap-2", children: [
|
|
1073
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
|
|
931
1074
|
"button",
|
|
932
1075
|
{
|
|
933
1076
|
onClick: sendMessage,
|
|
934
1077
|
disabled: !input.trim() || isLoading,
|
|
935
|
-
className: "
|
|
1078
|
+
className: "flex items-center gap-1 rounded-md bg-indigo-600 px-3 py-2 text-nowrap text-white transition-colors hover:bg-indigo-700 disabled:cursor-not-allowed disabled:opacity-50",
|
|
936
1079
|
title: "Send message",
|
|
937
|
-
children:
|
|
1080
|
+
children: [
|
|
1081
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)(import_lucide_react2.Send, { className: "h-4 w-4" }),
|
|
1082
|
+
/* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Send message" })
|
|
1083
|
+
]
|
|
938
1084
|
}
|
|
939
1085
|
),
|
|
940
1086
|
messages.length >= 2 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
@@ -942,22 +1088,14 @@ function ReportModal({
|
|
|
942
1088
|
{
|
|
943
1089
|
onClick: handleManualSubmit,
|
|
944
1090
|
disabled: isLoading,
|
|
945
|
-
className: "px-
|
|
1091
|
+
className: "rounded-md bg-green-600 px-3 py-2 text-xs font-medium text-nowrap text-white transition-colors hover:bg-green-700 disabled:opacity-50",
|
|
946
1092
|
title: "Submit report now",
|
|
947
|
-
children: "Submit"
|
|
1093
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-sm font-medium", children: "Submit report" })
|
|
948
1094
|
}
|
|
949
1095
|
)
|
|
950
1096
|
] })
|
|
951
|
-
] }),
|
|
952
|
-
captureResult && (captureResult.consoleErrors.length > 0 || captureResult.networkErrors.length > 0) && /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("p", { className: "text-xs text-amber-600 mt-1.5", children: [
|
|
953
|
-
[
|
|
954
|
-
captureResult.consoleErrors.length > 0 ? `${captureResult.consoleErrors.length} console error${captureResult.consoleErrors.length !== 1 ? "s" : ""}` : null,
|
|
955
|
-
captureResult.networkErrors.length > 0 ? `${captureResult.networkErrors.length} failed request${captureResult.networkErrors.length !== 1 ? "s" : ""}` : null
|
|
956
|
-
].filter(Boolean).join(" + "),
|
|
957
|
-
" ",
|
|
958
|
-
"captured \u2014 these will be included in the report."
|
|
959
1097
|
] })
|
|
960
|
-
] })
|
|
1098
|
+
] }) })
|
|
961
1099
|
]
|
|
962
1100
|
}
|
|
963
1101
|
)
|
|
@@ -971,8 +1109,10 @@ function JarveBugReporter({
|
|
|
971
1109
|
apiUrl,
|
|
972
1110
|
apiKey,
|
|
973
1111
|
user,
|
|
1112
|
+
buttonPosition,
|
|
974
1113
|
children
|
|
975
1114
|
}) {
|
|
1115
|
+
const safeApiKey = apiKey || "";
|
|
976
1116
|
const [captureMode, setCaptureMode] = (0, import_react3.useState)(false);
|
|
977
1117
|
const [captureResult, setCaptureResult] = (0, import_react3.useState)(null);
|
|
978
1118
|
const [showModal, setShowModal] = (0, import_react3.useState)(false);
|
|
@@ -1001,12 +1141,19 @@ function JarveBugReporter({
|
|
|
1001
1141
|
clearCapturedErrors();
|
|
1002
1142
|
clearCapturedNetworkErrors();
|
|
1003
1143
|
}, []);
|
|
1004
|
-
const siteId =
|
|
1144
|
+
const siteId = safeApiKey.startsWith("brk_") ? safeApiKey.slice(4, 12) : "external";
|
|
1005
1145
|
const reporterName = (user == null ? void 0 : user.name) || "Anonymous";
|
|
1006
1146
|
const reporterEmail = (user == null ? void 0 : user.email) || "unknown@external";
|
|
1007
1147
|
return /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(import_jsx_runtime4.Fragment, { children: [
|
|
1008
1148
|
children,
|
|
1009
|
-
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1149
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1150
|
+
FloatingButton,
|
|
1151
|
+
{
|
|
1152
|
+
isActive: captureMode,
|
|
1153
|
+
onClick: toggleCaptureMode,
|
|
1154
|
+
position: buttonPosition
|
|
1155
|
+
}
|
|
1156
|
+
),
|
|
1010
1157
|
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
1011
1158
|
CaptureOverlay,
|
|
1012
1159
|
{
|