-
Notifications
You must be signed in to change notification settings - Fork 140
feat: implement AI-driven behavioral video sentiment analyzer (#1744) #1749
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,107 @@ | ||
| import React, { useEffect, useRef, useState } from 'react'; | ||
| import * as faceapi from 'face-api.js'; | ||
|
|
||
| const VideoSentimentAnalyzer = ({ onAnalysisComplete }) => { | ||
| const videoRef = useRef(); | ||
| const [isModelLoaded, setIsModelLoaded] = useState(false); | ||
| const [expressions, setExpressions] = useState({}); | ||
| const [isAnalyzing, setIsAnalyzing] = useState(false); | ||
|
|
||
| useEffect(() => { | ||
| const loadModels = async () => { | ||
| try { | ||
| await faceapi.nets.tinyFaceDetector.loadFromUri('/models'); | ||
| await faceapi.nets.faceExpressionNet.loadFromUri('/models'); | ||
| setIsModelLoaded(true); | ||
| } catch (err) { | ||
| console.error("Error loading face-api models", err); | ||
| } | ||
|
desireddymohithreddy0925 marked this conversation as resolved.
|
||
| }; | ||
| loadModels(); | ||
| }, []); | ||
|
|
||
| const startVideo = () => { | ||
| navigator.mediaDevices.getUserMedia({ video: true }) | ||
| .then((stream) => { | ||
| videoRef.current.srcObject = stream; | ||
| setIsAnalyzing(true); | ||
| }) | ||
| .catch((err) => console.error(err)); | ||
|
desireddymohithreddy0925 marked this conversation as resolved.
|
||
| }; | ||
|
|
||
| const stopVideo = () => { | ||
| const stream = videoRef.current?.srcObject; | ||
| if (stream) { | ||
| stream.getTracks().forEach(track => track.stop()); | ||
| } | ||
| setIsAnalyzing(false); | ||
| if (onAnalysisComplete) { | ||
| onAnalysisComplete(expressions); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win Clear expression state between frames and sessions.
Proposed state reset .then((stream) => {
videoRef.current.srcObject = stream;
+ setExpressions({});
setIsAnalyzing(true);
...
- if (detections.length > 0) {
- setExpressions(detections[0].expressions);
- }
+ setExpressions(detections[0]?.expressions ?? {});Also applies to: 51-52 🤖 Prompt for AI Agents |
||
| } | ||
| }; | ||
|
|
||
| const handleVideoPlay = () => { | ||
| setInterval(async () => { | ||
| if (videoRef.current && isAnalyzing) { | ||
| const detections = await faceapi.detectAllFaces( | ||
| videoRef.current, | ||
| new faceapi.TinyFaceDetectorOptions() | ||
| ).withFaceExpressions(); | ||
|
|
||
| if (detections.length > 0) { | ||
| setExpressions(detections[0].expressions); | ||
| } | ||
| } | ||
| }, 1000); | ||
|
desireddymohithreddy0925 marked this conversation as resolved.
|
||
| }; | ||
|
|
||
| return ( | ||
| <div className="p-4 bg-gray-900 rounded-xl shadow-lg border border-gray-700"> | ||
| <h2 className="text-xl font-bold text-white mb-4">AI Behavioral Sentiment Analyzer</h2> | ||
|
|
||
| {!isModelLoaded ? ( | ||
| <p className="text-gray-400">Loading AI Models...</p> | ||
| ) : ( | ||
| <div className="flex flex-col items-center"> | ||
| <div className="relative w-full max-w-md aspect-video bg-black rounded-lg overflow-hidden mb-4"> | ||
| <video | ||
| ref={videoRef} | ||
| autoPlay | ||
| muted | ||
| onPlay={handleVideoPlay} | ||
| className="w-full h-full object-cover" | ||
| /> | ||
| </div> | ||
|
|
||
| <div className="flex gap-4 mb-4"> | ||
| {!isAnalyzing ? ( | ||
| <button onClick={startVideo} className="px-4 py-2 bg-blue-600 text-white rounded-lg hover:bg-blue-500 transition-colors"> | ||
| Start Mock Interview | ||
| </button> | ||
| ) : ( | ||
| <button onClick={stopVideo} className="px-4 py-2 bg-red-600 text-white rounded-lg hover:bg-red-500 transition-colors"> | ||
| End Session | ||
| </button> | ||
| )} | ||
| </div> | ||
|
|
||
| {Object.keys(expressions).length > 0 && ( | ||
| <div className="w-full bg-gray-800 p-4 rounded-lg"> | ||
| <h3 className="text-white font-semibold mb-2">Real-time Analysis</h3> | ||
| <div className="grid grid-cols-2 gap-2 text-sm"> | ||
| {Object.entries(expressions).map(([exp, val]) => ( | ||
| <div key={exp} className="flex justify-between text-gray-300"> | ||
| <span className="capitalize">{exp}</span> | ||
| <span>{(val * 100).toFixed(1)}%</span> | ||
| </div> | ||
| ))} | ||
| </div> | ||
| </div> | ||
| )} | ||
| </div> | ||
| )} | ||
| </div> | ||
| ); | ||
| }; | ||
|
|
||
| export default VideoSentimentAnalyzer; | ||
Uh oh!
There was an error while loading. Please reload this page.