From 50cef4a5f4f42f72b45a740c07ff2da1f9be770c Mon Sep 17 00:00:00 2001 From: DhanviNautiyal <167422557+DhanviNautiyal@users.noreply.github.com> Date: Sat, 4 Apr 2026 05:30:36 +0530 Subject: [PATCH] feat(ai): modified blur detector by adding edge density and flicker rate. --- backend/src/index.ts | 3 + .../notifications/services/InviteService.ts | 4 +- .../database/interfaces/ISettingRepository.ts | 2 +- backend/tsconfig.json | 2 +- frontend/package.json | 1 + .../src/components/EditProctoringModal.tsx | 3 +- frontend/src/components/ai/BlurDetector.tsx | 146 ++++++++++++++++- .../src/components/ai/BlurDetectorWorker.ts | 147 ++++++++++++++++-- frontend/src/components/floating-video.tsx | 50 ++++-- package.json | 2 +- pnpm-lock.yaml | 65 +++++++- pnpm-workspace.yaml | 2 + 12 files changed, 381 insertions(+), 46 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 8735bd863..5caad3e6d 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -19,6 +19,9 @@ import { authorizationChecker } from './shared/functions/authorizationChecker.js import { currentUserChecker } from './shared/functions/currentUserChecker.js'; import { startCron } from './utils/startCron.js'; import { GLOBAL_TYPES } from './types.js'; +import dns from 'dns'; + +dns.setServers(["1.1.1.1", "8.8.8.8"]); const app = express(); const globalRateLimiter = createRateLimiter(); diff --git a/backend/src/modules/notifications/services/InviteService.ts b/backend/src/modules/notifications/services/InviteService.ts index 6db3af469..b22515074 100644 --- a/backend/src/modules/notifications/services/InviteService.ts +++ b/backend/src/modules/notifications/services/InviteService.ts @@ -85,7 +85,7 @@ export class InviteService extends BaseService { `Before you begin, please carefully read and follow the instructions below to ensure a smooth and compliant experience:\n` + `- Speaking is strictly prohibited. If the system detects speaking, there is zero tolerance, and the video will immediately roll back to the start, pausing with an alert dialog.\n` + `- Ensure your camera remains uninterrupted. Any camera interruptions will be detected and may result in penalty score increases and video rollback.\n` + - `- Do not use a blurred background. The AI proctoring system tracks background clarity. A blurred background may trigger penalties and video rollback.\n` + + `- Do not use virtual backgrounds, background blur, or any software-based background effects. The AI proctoring system detects artificial background manipulation and may trigger penalties.\n` + `- No other person should appear near you during the session. The system monitors for additional individuals in the camera’s view. Detection of more than one person leads to immediate video rollback and a pause until the area is clear.\n` + `- Allow microphone access. The system needs mic access to detect speaking, which is strictly prohibited and may result in penalties and video rollback.\n\n` + `By following these rules, you help maintain the integrity and fairness of the course environment.\n\n` + @@ -268,7 +268,7 @@ export class InviteService extends BaseService { // courseVersionId, // )) // : false; - +//-- // const invite = new Invite( // email, // new ObjectId(courseId), diff --git a/backend/src/shared/database/interfaces/ISettingRepository.ts b/backend/src/shared/database/interfaces/ISettingRepository.ts index 07b1d474e..4dd2b2a2a 100644 --- a/backend/src/shared/database/interfaces/ISettingRepository.ts +++ b/backend/src/shared/database/interfaces/ISettingRepository.ts @@ -22,7 +22,7 @@ import { // Enum representing the different components of proctoring that can be enabled or disabled. export enum ProctoringComponent { CAMERAMICRO = 'cameraMic', - BLURDETECTION = 'blurDetection', // bulrDetection + BLURDETECTION = 'blurDetection', // blurDetection FACECOUNTDETECTION = 'faceCountDetection', // faceCountDetection HANDGESTUREDETECTION = 'handGestureDetection', // handGestureDetection VOICEDETECTION = 'voiceDetection', // voiceDetection diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 4af91a769..7c72f731a 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -30,7 +30,7 @@ "#courses/*": ["./modules/courses/*"], "#users/*": ["./modules/users/*"], "#quizzes/*": ["./modules/quizzes/*"], - "#settings/*": ["./modules/settings/*"], + "#setting/*": ["./modules/setting/*"], "#ejectionPolicy/*": ["./modules/ejectionPolicy/*"] }, diff --git a/frontend/package.json b/frontend/package.json index 322799566..20e332c72 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -24,6 +24,7 @@ "@emotion/styled": "^11.14.0", "@mediapipe/face_detection": "^0.4.1646425229", "@mediapipe/face_mesh": "^0.4.1633559619", + "@mediapipe/selfie_segmentation": "^0.1.1675465747", "@mediapipe/tasks-audio": "0.10.22-rc.20250304", "@mediapipe/tasks-vision": "0.10.22-rc.20250304", "@mui/icons-material": "^7.1.0", diff --git a/frontend/src/components/EditProctoringModal.tsx b/frontend/src/components/EditProctoringModal.tsx index 57d9180be..f82007a47 100644 --- a/frontend/src/components/EditProctoringModal.tsx +++ b/frontend/src/components/EditProctoringModal.tsx @@ -72,7 +72,8 @@ export function ProctoringModal({ try { const result = await getSettings(courseId, courseVersionId); if (result) { - setDetectors(result.settings?.proctors?.detectors?.map((d: any) => ({ name: d.detectorName, enabled: d.settings.enabled }))) + //setDetectors(result.settings?.proctors?.detectors?.map((d: any) => ({ name: d.detectorName, enabled: d.settings.enabled }))) + setDetectors(result.settings?.proctors?.detectors?.map((d: any) => ({name: d.detectorName,enabled:d.detectorName === ProctoringComponent.BLURDETECTION? true: d.settings.enabled})))//Backend independent setLinearProgressionEnabled(result.settings?.linearProgressionEnabled) setSeekForwardEnabled(result.settings?.seekForwardEnabled ?? false) setIsPublic(result.settings?.isPublic ?? false) diff --git a/frontend/src/components/ai/BlurDetector.tsx b/frontend/src/components/ai/BlurDetector.tsx index a7ba71b7e..a29ff3450 100644 --- a/frontend/src/components/ai/BlurDetector.tsx +++ b/frontend/src/components/ai/BlurDetector.tsx @@ -2,16 +2,30 @@ import React, { useEffect, useRef } from "react"; import type { BlurDetectionProps } from "@/types/ai.types"; +import { SelfieSegmentation } from "@mediapipe/selfie_segmentation"; + +/*const frameDiff = (prev: ImageData, curr: ImageData): number => { + let diff = 0; + for (let i = 0; i < prev.data.length; i += 4) { + diff += Math.abs(prev.data[i] - curr.data[i]); + } + return diff / (prev.data.length / 4); +};*/ + + const BlurDetection: React.FC = ({ videoRef, setIsBlur }) => { const workerRef = useRef(null); const blurStartTimeRef = useRef(null); + const segmentationRef = useRef(null); + const prevFrameRef = useRef(null); + useEffect(() => { workerRef.current = new Worker(new URL("./BlurDetectorWorker.ts", import.meta.url), { type: "module", }); - workerRef.current.onmessage = (event) => { + /*workerRef.current.onmessage = (event) => { const { isBlurry } = event.data; if (isBlurry) { @@ -33,22 +47,126 @@ const BlurDetection: React.FC = ({ videoRef, setIsBlur }) => } }; + return () => { + workerRef.current?.terminate(); + }; + }, [setIsBlur]);*/ + //Updated response handler + workerRef.current.onmessage = (event) => { + const { score } = event.data; + + const isSuspicious = score > 0.6; + + if (isSuspicious) { + if (!blurStartTimeRef.current) { + blurStartTimeRef.current = Date.now(); + } else { + const elapsedTime = Date.now() - blurStartTimeRef.current; + + if (elapsedTime >= 2000 && elapsedTime < 2200) { + // warning + } else if (elapsedTime >= 5000 && elapsedTime < 5200) { + // flag + blurStartTimeRef.current = null; + } + } + setIsBlur("Yes"); + } else { + blurStartTimeRef.current = null; + setIsBlur("No"); + } + }; + return () => { workerRef.current?.terminate(); }; }, [setIsBlur]); + //Init Mediapipe + useEffect(() => { + const segmentation = new SelfieSegmentation({ + locateFile: (file) => + `https://cdn.jsdelivr.net/npm/@mediapipe/selfie_segmentation/${file}`, + }); + + segmentation.setOptions({ + modelSelection: 1, + }); + + segmentation.onResults((results) => { + processSegmentation(results); + }); + + segmentationRef.current = segmentation; + }, []); + + //Process segmentation results + const processSegmentation = (results: any) => { + const video = videoRef.current; + if (!video || !workerRef.current) return; + + const width = video.videoWidth; + const height = video.videoHeight; + + const canvas = document.createElement("canvas"); + const ctx = canvas.getContext("2d"); + + canvas.width = width; + canvas.height = height; + + if (!ctx) return; + + try { + // Draw frame + ctx.drawImage(video, 0, 0, width, height); + const frame = ctx.getImageData(0, 0, width, height); + + // Flicker detection + let flick = 0; + if (prevFrameRef.current) { + for (let i = 0; i < frame.data.length; i += 4) { + flick += Math.abs(frame.data[i] - prevFrameRef.current.data[i]); + } + flick /= frame.data.length / 4; + } + + prevFrameRef.current = frame; + + // Get segmentation mask + const maskCanvas = document.createElement("canvas"); + const maskCtx = maskCanvas.getContext("2d"); + + maskCanvas.width = width; + maskCanvas.height = height; + + maskCtx?.drawImage(results.segmentationMask, 0, 0, width, height); + const mask = maskCtx?.getImageData(0, 0, width, height); + + // Send to worker (UPDATED FORMAT) + workerRef.current.postMessage({ + frame, + mask, + flick, + }); + + } catch (error) { + console.warn("Segmentation failed:", error); + } + }; + + //Frame loop (updated) + useEffect(() => { if (!videoRef.current) return; - const captureFrame = () => { + const captureFrame = async() => { const video = videoRef.current; if (!video || video.readyState !== 4 || video.videoWidth === 0 || video.videoHeight === 0) { return; // Skip processing if video isn't ready } - const canvas = document.createElement("canvas"); + /*const canvas = document.createElement("canvas"); const ctx = canvas.getContext("2d"); // Set canvas dimensions to match video @@ -64,7 +182,21 @@ const BlurDetection: React.FC = ({ videoRef, setIsBlur }) => console.warn("Failed to capture frame:", error); } } - }; + };*/ + if (!segmentationRef.current) return; + try { + // Use MediaPipe instead of manual canvas extraction + await segmentationRef.current?.send({ image: video }); + + } catch (error) { + console.warn("Segmentation send failed:", error); + } + }; + + const interval = setInterval(captureFrame, 500); // ~2 FPS (optimized) + + return () => clearInterval(interval); +}, [videoRef]); // if (video) { // const canvas = document.createElement("canvas"); @@ -79,9 +211,9 @@ const BlurDetection: React.FC = ({ videoRef, setIsBlur }) => // } // }; - const interval = setInterval(captureFrame, 500); - return () => clearInterval(interval); - }, [videoRef]); + //const interval = setInterval(captureFrame, 500); + //return () => clearInterval(interval); + //}, [videoRef]); return null; // No need to return a video element }; diff --git a/frontend/src/components/ai/BlurDetectorWorker.ts b/frontend/src/components/ai/BlurDetectorWorker.ts index 04fdcc5df..54d2a206a 100644 --- a/frontend/src/components/ai/BlurDetectorWorker.ts +++ b/frontend/src/components/ai/BlurDetectorWorker.ts @@ -1,18 +1,24 @@ -self.onmessage = (event) => { +/*self.onmessage = (event) => { const imageData = event.data; if (!imageData) return; - - function rgbToGrayscale(imageData: ImageData): Uint8ClampedArray { - const gray = new Uint8ClampedArray(imageData.width * imageData.height); + */ + +self.onmessage = (event) => { + const { frame, mask, flick } = event.data; + if (!frame || !mask) return; + + const { data, width, height } = frame; + function rgbToGrayscale(frame:ImageData): Uint8ClampedArray { + const gray = new Uint8ClampedArray(frame.width * frame.height); for (let i = 0; i < gray.length; i++) { - const r = imageData.data[i * 4]; - const g = imageData.data[i * 4 + 1]; - const b = imageData.data[i * 4 + 2]; + const r = frame.data[i * 4]; + const g = frame.data[i * 4 + 1]; + const b = frame.data[i * 4 + 2]; gray[i] = 0.299 * r + 0.587 * g + 0.114 * b; } return gray; } - + //laplacian--> detects blur function computeLaplacian(gray: Uint8ClampedArray, width: number, height: number): Float32Array { const laplacian = new Float32Array(gray.length); const kernel = [ @@ -35,7 +41,7 @@ self.onmessage = (event) => { } return laplacian; } - + //variance for laplacian--> if low variance, image is blurry function computeVariance(data: Float32Array): number { let mean = 0; for (let i = 0; i < data.length; i++) { @@ -50,16 +56,123 @@ self.onmessage = (event) => { variance /= data.length; return variance; } - + //Edge density using sobel operator--> if low edge density, image is blurry + function computeEdgeDensity( + gray: Uint8ClampedArray, + width: number, + height: number + ): number { + let edges = 0; + + const sobelX = [ + -1, 0, 1, + -2, 0, 2, + -1, 0, 1, + ]; + + const sobelY = [ + -1, -2, -1, + 0, 0, 0, + 1, 2, 1, + ]; + + for (let y = 1; y < height - 1; y++) { + for (let x = 1; x < width - 1; x++) { + let gx = 0; + let gy = 0; + + for (let ky = -1; ky <= 1; ky++) { + for (let kx = -1; kx <= 1; kx++) { + const pixel = gray[(y + ky) * width + (x + kx)]; + const idx = (ky + 1) * 3 + (kx + 1); + + gx += pixel * sobelX[idx]; + gy += pixel * sobelY[idx]; + } + } + + const magnitude = Math.sqrt(gx * gx + gy * gy); + + if (magnitude > 100) edges++; + } + } + + return edges / (width * height); + } + + //background variance + /*let bgValues = []; + + for (let i = 0; i < data.length; i += 4) { + const isBackground = mask.data[i] < 128; + + if (isBackground) { + const grayPixel = + 0.299 * data[i] + + 0.587 * data[i + 1] + + 0.114 * data[i + 2]; + + bgValues.push(grayPixel); + } + } + + let bgVariance = 0; + + if (bgValues.length > 0) { + const meanBg = + bgValues.reduce((a, b) => a + b, 0) / bgValues.length; + + bgVariance = + bgValues.reduce((sum, v) => sum + (v - meanBg) ** 2, 0) / + bgValues.length; + }*/ + let sum = 0; + let sumSq = 0; + let count = 0; + for (let i = 0; i < data.length; i += 4) { + const isBackground = mask.data[i] < 128; + if (isBackground) { + const grayPixel =0.299 * data[i] + 0.587 * data[i + 1] + 0.114 * data[i + 2]; + sum += grayPixel; + sumSq += grayPixel * grayPixel; + count++; + } + } + let bgVariance = 0; + if (count > 0) { + const mean = sum / count; + bgVariance = sumSq / count - mean * mean; + }//direct math, no storing in array, less memory + // **Perform Blur Check** - const scale = 0.5; - const width = Math.floor(imageData.width * scale); - const height = Math.floor(imageData.height * scale); - const gray = rgbToGrayscale(imageData); + //const scale = 0.5; + //const width = Math.floor(frame.width * scale); + //const height = Math.floor(frame.height * scale); + const gray = rgbToGrayscale(frame); const laplacian = computeLaplacian(gray, width, height); const variance = computeVariance(laplacian); - const isBlurry = variance < 250; - - self.postMessage({ isBlurry, variance }); + //const isBlurry = variance < 250; + const edgeDensity = computeEdgeDensity(gray, width, height); + + // Combine metrics (simple heuristic) + const safeFlick = flick ?? 0; + const blurScore = variance < 140 ? 1 : 0; + const edgeScore = edgeDensity < 0.07 ? 1 : 0; + const bgScore = bgVariance < 60 ? 1 : 0; + const flickScore = safeFlick > 25 ? 1 : 0; + + const score = + 0.4 * blurScore + + 0.3 * edgeScore + + 0.2 * bgScore + + 0.1 * flickScore; + + self.postMessage({ + score, + variance, + edgeDensity, + bgVariance, + flick: safeFlick, + }); }; \ No newline at end of file diff --git a/frontend/src/components/floating-video.tsx b/frontend/src/components/floating-video.tsx index 3bc175013..cf6870142 100644 --- a/frontend/src/components/floating-video.tsx +++ b/frontend/src/components/floating-video.tsx @@ -73,6 +73,7 @@ function FloatingVideo({ const [penaltyPoints, setPenaltyPoints] = useState(0); const [penaltyType, setPenaltyType] = useState(""); const [contiguousAnomalyPoints, setContiguousAnomalyPoints] = useState(0); + const [blurStartTime, setBlurStartTime] = useState(null); // Thumbs-up challenge states const [isThumbsUpChallenge, setIsThumbsUpChallenge] = useState(false); @@ -116,7 +117,8 @@ function FloatingVideo({ }, [settings]); // Check which components are enabled - const isBlurDetectionEnabled = isComponentEnabled('blurDetection'); + //const isBlurDetectionEnabled = isComponentEnabled('blurDetection'); + const isBlurDetectionEnabled = true;//not dependent on backend, always on for frontend anomaly detection and reporting. subject to change to previous line based on future decisions. const isFaceCountDetectionEnabled = isComponentEnabled('faceCountDetection'); const isHandGestureDetectionEnabled = false; //isComponentEnabled('handGestureDetection'); const isVoiceDetectionEnabled = isComponentEnabled('voiceDetection'); @@ -220,6 +222,7 @@ function FloatingVideo({ // Image record anomaly effect const reportImage = useReportAnomalyImage(); +const lastBlurReportedRef = useRef(0); const lastCalledRef = useRef(0); useEffect(() => { const handleImageAnomaly = async () => { @@ -233,11 +236,17 @@ const lastCalledRef = useRef(0); lastCalledRef.current = now; if (anomaly && anomalyType !== "voiceDetection") { - const video = videoRef.current; - if (!video || video.videoWidth === 0 || video.videoHeight === 0) { - console.log("Video not ready for screenshot"); - return; - } + if (anomalyType === "blurDetection") { + if (now - lastBlurReportedRef.current < 5000) { + return; //skip if called within 5 sec + } + lastBlurReportedRef.current = now; + } + const video = videoRef.current; + if (!video || video.videoWidth === 0 || video.videoHeight === 0) { + console.log("Video not ready for screenshot"); + return; + } // Add validation for course data if (!courseStore.currentCourse?.courseId || @@ -586,7 +595,7 @@ const lastCalledRef = useRef(0); // } let newPenaltyPoints = 0; let newPenaltyType = ""; - setAnomalies(['']); + setAnomalies(['']);//Check once (remove '' if causing issue) // Condition 1: If speaking is detected (only if voice detection is enabled) if (isSpeaking === "Yes" && isVoiceDetectionEnabled) { @@ -619,10 +628,31 @@ const lastCalledRef = useRef(0); } // Condition 3: If the screen is blurred (only if blur detection is enabled) - if (isBlur === "Yes" && isBlurDetectionEnabled) { + /*if (isBlur === "Yes" && isBlurDetectionEnabled) { setAnomalies([...anomalies, "blurDetection"]); newPenaltyType = "Blur"; newPenaltyPoints += 1; + }*/ + if (isBlur === "Yes" && isBlurDetectionEnabled) { + setBlurStartTime(prev => { + if (!prev) return Date.now(); + + const duration = Date.now() - prev; + + if (duration >= 2000) { + if (!anomalies.includes("blurDetection")) { + setAnomalies([...anomalies, "blurDetection"]); + } + newPenaltyType = "Blur"; + newPenaltyPoints += 1; + + return Date.now(); + } + return prev; + }); + } + else { + setBlurStartTime(null); } // Condition 4: If not focused (only if focus tracking is enabled) @@ -705,7 +735,7 @@ const lastCalledRef = useRef(0); // Reset contiguous anomaly points when no anomalies are detected if(contiguousAnomalyPoints>0) setContiguousAnomalyPoints(0); } - }, 100); // Update every second + }, 500); // Update every 0.5 second instead of 0.1 second(too fast) for stable scoring and to reduce performance overhead. return () => clearInterval(interval); }, [readyToDetect, @@ -1418,7 +1448,7 @@ const lastCalledRef = useRef(0); )} - +//-- {/* AI Components - Only render if enabled in proctoring settings */}
{isBlurDetectionEnabled && ( diff --git a/package.json b/package.json index 34d8dca20..9a9dce7ee 100644 --- a/package.json +++ b/package.json @@ -23,5 +23,5 @@ "dependencies": { "vibe": "link:cli" }, - "packageManager": "pnpm@10.12.1+sha512.f0dda8580f0ee9481c5c79a1d927b9164f2c478e90992ad268bbb2465a736984391d6333d2c327913578b2804af33474ca554ba29c04a8b13060a717675ae3ac" + "packageManager": "pnpm@10.33.0" } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 3cfc4dca7..0196cfabf 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -243,7 +243,7 @@ importers: version: 1.0.1 eslint-plugin-require-extensions: specifier: ^0.1.3 - version: 0.1.3(eslint@8.57.1) + version: 0.1.3(eslint@9.39.1(jiti@2.6.1)) gts: specifier: ^6.0.2 version: 6.0.2(@types/eslint@9.6.1)(typescript@5.9.3) @@ -432,6 +432,9 @@ importers: '@mediapipe/face_mesh': specifier: ^0.4.1633559619 version: 0.4.1633559619 + '@mediapipe/selfie_segmentation': + specifier: ^0.1.1675465747 + version: 0.1.1675465747 '@mediapipe/tasks-audio': specifier: 0.10.22-rc.20250304 version: 0.10.22-rc.20250304 @@ -3641,6 +3644,9 @@ packages: '@mediapipe/face_mesh@0.4.1633559619': resolution: {integrity: sha512-Vc8cdjxS5+O2gnjWH9KncYpUCVXT0h714KlWAsyqJvJbIgUJBqpppbIx8yWcAzBDxm/5cYSuBI5p5ySIPxzcEg==} + '@mediapipe/selfie_segmentation@0.1.1675465747': + resolution: {integrity: sha512-IxYxNhwE5VwOm52L1yoFWYLP7q9Pd+NJjzOC5tlepfvEGaY3o9hslhUrx9BgseqdfZtKSDtd/4NfCSMjNzQalA==} + '@mediapipe/tasks-audio@0.10.22-rc.20250304': resolution: {integrity: sha512-3rUl/8/+/J7kkoGU2a6mLQnBpw5KktsKmBKtL50GopzBUE0vd8D0dXwGPHAL2tIRYiQ1TMv1UDJSPrRrHTbnPA==} @@ -3819,42 +3825,49 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-arm64-musl@1.1.1': resolution: {integrity: sha512-+2Rzdb3nTIYZ0YJF43qf2twhqOCkiSrHx2Pg6DJaCPYhhaxbLcdlV8hCRMHghQ+EtZQWGNcS2xF4KxBhSGeutg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@napi-rs/nice-linux-ppc64-gnu@1.1.1': resolution: {integrity: sha512-4FS8oc0GeHpwvv4tKciKkw3Y4jKsL7FRhaOeiPei0X9T4Jd619wHNe4xCLmN2EMgZoeGg+Q7GY7BsvwKpL22Tg==} engines: {node: '>= 10'} cpu: [ppc64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-riscv64-gnu@1.1.1': resolution: {integrity: sha512-HU0nw9uD4FO/oGCCk409tCi5IzIZpH2agE6nN4fqpwVlCn5BOq0MS1dXGjXaG17JaAvrlpV5ZeyZwSon10XOXw==} engines: {node: '>= 10'} cpu: [riscv64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-s390x-gnu@1.1.1': resolution: {integrity: sha512-2YqKJWWl24EwrX0DzCQgPLKQBxYDdBxOHot1KWEq7aY2uYeX+Uvtv4I8xFVVygJDgf6/92h9N3Y43WPx8+PAgQ==} engines: {node: '>= 10'} cpu: [s390x] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-gnu@1.1.1': resolution: {integrity: sha512-/gaNz3R92t+dcrfCw/96pDopcmec7oCcAQ3l/M+Zxr82KT4DljD37CpgrnXV+pJC263JkW572pdbP3hP+KjcIg==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@napi-rs/nice-linux-x64-musl@1.1.1': resolution: {integrity: sha512-xScCGnyj/oppsNPMnevsBe3pvNaoK7FGvMjT35riz9YdhB2WtTG47ZlbxtOLpjeO9SqqQ2J2igCmz6IJOD5JYw==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@napi-rs/nice-openharmony-arm64@1.1.1': resolution: {integrity: sha512-6uJPRVwVCLDeoOaNyeiW0gp2kFIM4r7PL2MczdZQHkFi9gVlgm+Vn+V6nTWRcu856mJ2WjYJiumEajfSm7arPQ==} @@ -4129,36 +4142,42 @@ packages: engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm-musl@2.5.1': resolution: {integrity: sha512-6E+m/Mm1t1yhB8X412stiKFG3XykmgdIOqhjWj+VL8oHkKABfu/gjFj8DvLrYVHSBNC+/u5PeNrujiSQ1zwd1Q==} engines: {node: '>= 10.0.0'} cpu: [arm] os: [linux] + libc: [musl] '@parcel/watcher-linux-arm64-glibc@2.5.1': resolution: {integrity: sha512-LrGp+f02yU3BN9A+DGuY3v3bmnFUggAITBGriZHUREfNEzZh/GO06FF5u2kx8x+GBEUYfyTGamol4j3m9ANe8w==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-arm64-musl@2.5.1': resolution: {integrity: sha512-cFOjABi92pMYRXS7AcQv9/M1YuKRw8SZniCDw0ssQb/noPkRzA+HBDkwmyOJYp5wXcsTrhxO0zq1U11cK9jsFg==} engines: {node: '>= 10.0.0'} cpu: [arm64] os: [linux] + libc: [musl] '@parcel/watcher-linux-x64-glibc@2.5.1': resolution: {integrity: sha512-GcESn8NZySmfwlTsIur+49yDqSny2IhPeZfXunQi48DMugKeZ7uy1FX83pO0X22sHntJ4Ub+9k34XQCX+oHt2A==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [glibc] '@parcel/watcher-linux-x64-musl@2.5.1': resolution: {integrity: sha512-n0E2EQbatQ3bXhcH2D1XIAANAcTZkQICBPVaxMeaCVBtOpBZpWJuf7LwyWPSBDITb7In8mqQgJ7gH8CILCURXg==} engines: {node: '>= 10.0.0'} cpu: [x64] os: [linux] + libc: [musl] '@parcel/watcher-win32-arm64@2.5.1': resolution: {integrity: sha512-RFzklRvmc3PkjKjry3hLF9wD7ppR4AKcWNzH7kXR7GUe0Igb3Nz8fyPwtZCSquGrhU5HhUNDr/mKBqj7tqA2Vw==} @@ -5003,56 +5022,67 @@ packages: resolution: {integrity: sha512-k9oD15soC/Ln6d2Wv/JOFPzZXIAIFLp6B+i14KhxAfnq76ajt0EhYc5YPeX6W1xJkAdItcVT+JhKl1QZh44/qw==} cpu: [arm] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm-musleabihf@4.53.3': resolution: {integrity: sha512-vTNlKq+N6CK/8UktsrFuc+/7NlEYVxgaEgRXVUVK258Z5ymho29skzW1sutgYjqNnquGwVUObAaxae8rZ6YMhg==} cpu: [arm] os: [linux] + libc: [musl] '@rollup/rollup-linux-arm64-gnu@4.53.3': resolution: {integrity: sha512-RGrFLWgMhSxRs/EWJMIFM1O5Mzuz3Xy3/mnxJp/5cVhZ2XoCAxJnmNsEyeMJtpK+wu0FJFWz+QF4mjCA7AUQ3w==} cpu: [arm64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-arm64-musl@4.53.3': resolution: {integrity: sha512-kASyvfBEWYPEwe0Qv4nfu6pNkITLTb32p4yTgzFCocHnJLAHs+9LjUu9ONIhvfT/5lv4YS5muBHyuV84epBo/A==} cpu: [arm64] os: [linux] + libc: [musl] '@rollup/rollup-linux-loong64-gnu@4.53.3': resolution: {integrity: sha512-JiuKcp2teLJwQ7vkJ95EwESWkNRFJD7TQgYmCnrPtlu50b4XvT5MOmurWNrCj3IFdyjBQ5p9vnrX4JM6I8OE7g==} cpu: [loong64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-ppc64-gnu@4.53.3': resolution: {integrity: sha512-EoGSa8nd6d3T7zLuqdojxC20oBfNT8nexBbB/rkxgKj5T5vhpAQKKnD+h3UkoMuTyXkP5jTjK/ccNRmQrPNDuw==} cpu: [ppc64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-gnu@4.53.3': resolution: {integrity: sha512-4s+Wped2IHXHPnAEbIB0YWBv7SDohqxobiiPA1FIWZpX+w9o2i4LezzH/NkFUl8LRci/8udci6cLq+jJQlh+0g==} cpu: [riscv64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-riscv64-musl@4.53.3': resolution: {integrity: sha512-68k2g7+0vs2u9CxDt5ktXTngsxOQkSEV/xBbwlqYcUrAVh6P9EgMZvFsnHy4SEiUl46Xf0IObWVbMvPrr2gw8A==} cpu: [riscv64] os: [linux] + libc: [musl] '@rollup/rollup-linux-s390x-gnu@4.53.3': resolution: {integrity: sha512-VYsFMpULAz87ZW6BVYw3I6sWesGpsP9OPcyKe8ofdg9LHxSbRMd7zrVrr5xi/3kMZtpWL/wC+UIJWJYVX5uTKg==} cpu: [s390x] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-gnu@4.53.3': resolution: {integrity: sha512-3EhFi1FU6YL8HTUJZ51imGJWEX//ajQPfqWLI3BQq4TlvHy4X0MOr5q3D2Zof/ka0d5FNdPwZXm3Yyib/UEd+w==} cpu: [x64] os: [linux] + libc: [glibc] '@rollup/rollup-linux-x64-musl@4.53.3': resolution: {integrity: sha512-eoROhjcc6HbZCJr+tvVT8X4fW3/5g/WkGvvmwz/88sDtSJzO7r/blvoBDgISDiCjDRZmHpwud7h+6Q9JxFwq1Q==} cpu: [x64] os: [linux] + libc: [musl] '@rollup/rollup-openharmony-arm64@4.53.3': resolution: {integrity: sha512-OueLAWgrNSPGAdUdIjSWXw+u/02BRTcnfw9PN41D2vq/JSEPnJnVuBgw18VkN8wcd4fjUs+jFHVM4t9+kBSNLw==} @@ -5549,24 +5579,28 @@ packages: engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [glibc] '@swc/core-linux-arm64-musl@1.15.2': resolution: {integrity: sha512-5av6VYZZeneiYIodwzGMlnyVakpuYZryGzFIbgu1XP8wVylZxduEzup4eP8atiMDFmIm+s4wn8GySJmYqeJC0A==} engines: {node: '>=10'} cpu: [arm64] os: [linux] + libc: [musl] '@swc/core-linux-x64-gnu@1.15.2': resolution: {integrity: sha512-1nO/UfdCLuT/uE/7oB3EZgTeZDCIa6nL72cFEpdegnqpJVNDI6Qb8U4g/4lfVPkmHq2lvxQ0L+n+JdgaZLhrRA==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [glibc] '@swc/core-linux-x64-musl@1.15.2': resolution: {integrity: sha512-Ksfrb0Tx310kr+TLiUOvB/I80lyZ3lSOp6cM18zmNRT/92NB4mW8oX2Jo7K4eVEI2JWyaQUAFubDSha2Q+439A==} engines: {node: '>=10'} cpu: [x64] os: [linux] + libc: [musl] '@swc/core-win32-arm64-msvc@1.15.2': resolution: {integrity: sha512-IzUb5RlMUY0r1A9IuJrQ7Tbts1wWb73/zXVXT8VhewbHGoNlBKE0qUhKMED6Tv4wDF+pmbtUJmKXDthytAvLmg==} @@ -5654,24 +5688,28 @@ packages: engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-arm64-musl@4.1.17': resolution: {integrity: sha512-HvZLfGr42i5anKtIeQzxdkw/wPqIbpeZqe7vd3V9vI3RQxe3xU1fLjss0TjyhxWcBaipk7NYwSrwTwK1hJARMg==} engines: {node: '>= 10'} cpu: [arm64] os: [linux] + libc: [musl] '@tailwindcss/oxide-linux-x64-gnu@4.1.17': resolution: {integrity: sha512-M3XZuORCGB7VPOEDH+nzpJ21XPvK5PyjlkSFkFziNHGLc5d6g3di2McAAblmaSUNl8IOmzYwLx9NsE7bplNkwQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [glibc] '@tailwindcss/oxide-linux-x64-musl@4.1.17': resolution: {integrity: sha512-k7f+pf9eXLEey4pBlw+8dgfJHY4PZ5qOUFDyNf7SI6lHjQ9Zt7+NcscjpwdCEbYi6FI5c2KDTDWyf2iHcCSyyQ==} engines: {node: '>= 10'} cpu: [x64] os: [linux] + libc: [musl] '@tailwindcss/oxide-wasm32-wasi@4.1.17': resolution: {integrity: sha512-cEytGqSSoy7zK4JRWiTCx43FsKP/zGr0CsuMawhH67ONlH+T79VteQeJQRO/X7L0juEUA8ZyuYikcRBf0vsxhg==} @@ -11191,24 +11229,28 @@ packages: engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [glibc] lightningcss-linux-arm64-musl@1.30.2: resolution: {integrity: sha512-5Vh9dGeblpTxWHpOx8iauV02popZDsCYMPIgiuw97OJ5uaDsL86cnqSFs5LZkG3ghHoX5isLgWzMs+eD1YzrnA==} engines: {node: '>= 12.0.0'} cpu: [arm64] os: [linux] + libc: [musl] lightningcss-linux-x64-gnu@1.30.2: resolution: {integrity: sha512-Cfd46gdmj1vQ+lR6VRTTadNHu6ALuw2pKR9lYq4FnhvgBc4zWY1EtZcAc6EffShbb1MFrIPfLDXD6Xprbnni4w==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [glibc] lightningcss-linux-x64-musl@1.30.2: resolution: {integrity: sha512-XJaLUUFXb6/QG2lGIW6aIk6jKdtjtcffUT0NKvIqhSBY3hh9Ch+1LCeH80dR9q9LBjG3ewbDjnumefsLsP6aiA==} engines: {node: '>= 12.0.0'} cpu: [x64] os: [linux] + libc: [musl] lightningcss-win32-arm64-msvc@1.30.2: resolution: {integrity: sha512-FZn+vaj7zLv//D/192WFFVA0RgHawIcHqLX9xuWiQt7P0PtdFEVaxgF9rjM/IRYHQXNnk61/H/gb2Ei+kUQ4xQ==} @@ -11356,6 +11398,9 @@ packages: lodash@4.17.21: resolution: {integrity: sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==} + lodash@4.18.1: + resolution: {integrity: sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==} + log-symbols@4.1.0: resolution: {integrity: sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==} engines: {node: '>=10'} @@ -20948,6 +20993,8 @@ snapshots: '@mediapipe/face_mesh@0.4.1633559619': {} + '@mediapipe/selfie_segmentation@0.1.1675465747': {} + '@mediapipe/tasks-audio@0.10.22-rc.20250304': {} '@mediapipe/tasks-vision@0.10.22-rc.20250304': {} @@ -27398,6 +27445,10 @@ snapshots: dependencies: eslint: 8.57.1 + eslint-config-prettier@9.1.0(eslint@9.39.1(jiti@2.6.1)): + dependencies: + eslint: 9.39.1(jiti@2.6.1) + eslint-plugin-es@4.1.0(eslint@8.57.1): dependencies: eslint: 8.57.1 @@ -27424,7 +27475,7 @@ snapshots: synckit: 0.9.3 optionalDependencies: '@types/eslint': 9.6.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-config-prettier: 9.1.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-react-hooks@5.2.0(eslint@9.39.1(jiti@2.6.1)): dependencies: @@ -27434,9 +27485,9 @@ snapshots: dependencies: eslint: 9.39.1(jiti@2.6.1) - eslint-plugin-require-extensions@0.1.3(eslint@8.57.1): + eslint-plugin-require-extensions@0.1.3(eslint@9.39.1(jiti@2.6.1)): dependencies: - eslint: 8.57.1 + eslint: 9.39.1(jiti@2.6.1) eslint-scope@5.1.1: dependencies: @@ -28615,7 +28666,7 @@ snapshots: '@typescript-eslint/parser': 5.62.0(eslint@8.57.1)(typescript@5.9.3) chalk: 4.1.2 eslint: 8.57.1 - eslint-config-prettier: 9.1.0(eslint@8.57.1) + eslint-config-prettier: 9.1.0(eslint@9.39.1(jiti@2.6.1)) eslint-plugin-n: 15.7.0(eslint@8.57.1) eslint-plugin-prettier: 5.2.1(@types/eslint@9.6.1)(eslint-config-prettier@9.1.0(eslint@8.57.1))(eslint@8.57.1)(prettier@3.3.3) execa: 5.1.1 @@ -30438,6 +30489,8 @@ snapshots: lodash@4.17.21: {} + lodash@4.18.1: {} + log-symbols@4.1.0: dependencies: chalk: 4.1.2 @@ -34885,7 +34938,7 @@ snapshots: direction: 1.0.4 is-hotkey: 0.2.0 is-plain-object: 5.0.0 - lodash: 4.17.21 + lodash: 4.18.1 scroll-into-view-if-needed: 3.1.0 slate: 0.112.0 tiny-invariant: 1.3.1 diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 09d26f715..3d8a0e2db 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -7,3 +7,5 @@ packages: - mcp - e2e +onlyBuiltDependencies: + - "routing-controllers-openapi" \ No newline at end of file