This commit is contained in:
subhajit 2026-07-21 15:33:47 +05:30
parent d1125bb69e
commit 167090816e
4 changed files with 59 additions and 9 deletions

View File

@ -309,6 +309,19 @@ public function sendWarning(Request $request, $id)
return response()->json(['success' => true, 'warning' => $warning]); return response()->json(['success' => true, 'warning' => $warning]);
} }
/**
* Live Sync Candidate Code (before submission).
*/
public function syncCandidateCode(Request $request, $id)
{
$interview = Interview::findOrFail($id);
$interview->update([
'submitted_code' => $request->input('code'),
'submitted_language' => $request->input('language', $interview->language),
]);
return response()->json(['success' => true]);
}
/** /**
* Save Candidate Live Notepad Content. * Save Candidate Live Notepad Content.
*/ */

View File

@ -383,6 +383,24 @@
automaticLayout: true, automaticLayout: true,
minimap: { enabled: true } minimap: { enabled: true }
}); });
// Real-Time Code Sync to Interviewer (Before Submission)
let codeSyncTimeout = null;
editor.onDidChangeModelContent(function () {
clearTimeout(codeSyncTimeout);
codeSyncTimeout = setTimeout(function () {
const code = editor.getValue();
const lang = document.getElementById('language-select').value;
fetch(`{{ route("interview.candidate.sync-code", $interview->id) }}`, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-CSRF-TOKEN': '{{ csrf_token() }}'
},
body: JSON.stringify({ code: code, language: lang })
});
}, 600);
});
}); });
// Handle Language Switch // Handle Language Switch

View File

@ -669,6 +669,8 @@ function toggleFullscreenScreen() {
let activeViolations = []; let activeViolations = [];
let sosPollInterval = null; let sosPollInterval = null;
let acknowledgedViolationCount = 0;
let sosDismissed = false;
function switchIdeTab(tab) { function switchIdeTab(tab) {
document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none'; document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none';
@ -687,6 +689,9 @@ function switchIdeTab(tab) {
function openReviewModal(id, name, uid) { function openReviewModal(id, name, uid) {
currentInterviewId = id; currentInterviewId = id;
acknowledgedViolationCount = 0;
sosDismissed = false;
document.getElementById('modal-cand-name').innerText = name; document.getElementById('modal-cand-name').innerText = name;
document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid; document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid;
@ -703,7 +708,7 @@ function openReviewModal(id, name, uid) {
pollReviewData(); pollReviewData();
clearInterval(sosPollInterval); clearInterval(sosPollInterval);
sosPollInterval = setInterval(pollReviewData, 4000); sosPollInterval = setInterval(pollReviewData, 2000);
} }
function pollReviewData() { function pollReviewData() {
@ -712,7 +717,7 @@ function pollReviewData() {
.then(r => r.json()) .then(r => r.json())
.then(data => { .then(data => {
document.getElementById('modal-code-lang').innerText = (data.submitted_language || 'UNKNOWN').toUpperCase(); document.getElementById('modal-code-lang').innerText = (data.submitted_language || 'UNKNOWN').toUpperCase();
document.getElementById('modal-code-display').innerText = data.submitted_code || '// Candidate has not submitted code yet.'; document.getElementById('modal-code-display').innerText = data.submitted_code || '// Candidate has not typed any code yet.';
document.getElementById('modal-output-display').innerText = data.code_output || 'No execution output.'; document.getElementById('modal-output-display').innerText = data.code_output || 'No execution output.';
document.getElementById('modal-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.'; document.getElementById('modal-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.';
@ -728,17 +733,15 @@ function pollReviewData() {
} }
let logsHtml = ''; let logsHtml = '';
let cheatingDetected = false; let currentViolations = [];
activeViolations = [];
if (data.proctor_logs && data.proctor_logs.length > 0) { if (data.proctor_logs && data.proctor_logs.length > 0) {
data.proctor_logs.forEach(l => { data.proctor_logs.forEach(l => {
let icon = '🔴'; let icon = '🔴';
if (l.type === 'focus_lost') icon = '🟡'; if (l.type === 'focus_lost') icon = '🟡';
if (l.type === 'gaze_anomaly') icon = '👁️'; if (l.type === 'gaze_anomaly') icon = '👁️';
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost') { if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost' || l.type === 'gaze_anomaly') {
cheatingDetected = true; currentViolations.push(l);
activeViolations.push(l);
} }
logsHtml += `<div style="margin-bottom:6px; border-bottom:1px solid rgba(255,255,255,0.05); padding-bottom:4px;"> logsHtml += `<div style="margin-bottom:6px; border-bottom:1px solid rgba(255,255,255,0.05); padding-bottom:4px;">
<strong>${icon} ${l.type.toUpperCase()}:</strong> ${l.details || ''} <strong>${icon} ${l.type.toUpperCase()}:</strong> ${l.details || ''}
@ -750,9 +753,21 @@ function pollReviewData() {
} }
document.getElementById('modal-logs-display').innerHTML = logsHtml; document.getElementById('modal-logs-display').innerHTML = logsHtml;
activeViolations = currentViolations;
const totalViolations = currentViolations.length;
const sosBtn = document.getElementById('sos-alert-btn'); const sosBtn = document.getElementById('sos-alert-btn');
if (cheatingDetected) {
if (totalViolations > 0) {
sosBtn.style.display = 'inline-block'; sosBtn.style.display = 'inline-block';
// AUTOMATIC SOS TRIGGER & RE-ARMING ON NEW CHEATING EVENTS
if (totalViolations > acknowledgedViolationCount) {
sosDismissed = false;
sosBtn.style.animation = 'sosPulse 1.2s infinite alternate';
sosBtn.innerText = '🚨 SOS CHEATING DETECTED!';
sosBtn.style.background = 'linear-gradient(135deg, #ef4444 0%, #b91c1c 100%)';
openSosModal();
}
} else { } else {
sosBtn.style.display = 'none'; sosBtn.style.display = 'none';
} }
@ -777,10 +792,13 @@ function openSosModal() {
function stopSosAlarm() { function stopSosAlarm() {
document.getElementById('sos-modal').classList.remove('active'); document.getElementById('sos-modal').classList.remove('active');
acknowledgedViolationCount = activeViolations.length;
sosDismissed = true;
const sosBtn = document.getElementById('sos-alert-btn'); const sosBtn = document.getElementById('sos-alert-btn');
if (sosBtn) { if (sosBtn) {
sosBtn.style.animation = 'none'; sosBtn.style.animation = 'none';
sosBtn.innerText = '✅ SOS Acknowledged'; sosBtn.innerText = '✅ SOS Muted (Auto-rearm active)';
sosBtn.style.background = '#475569'; sosBtn.style.background = '#475569';
} }
} }

View File

@ -32,6 +32,7 @@
Route::post('/candidate/submit-code/{id}', [InterviewController::class, 'submitCode'])->name('interview.submit'); Route::post('/candidate/submit-code/{id}', [InterviewController::class, 'submitCode'])->name('interview.submit');
Route::post('/candidate/log-violation/{id}', [InterviewController::class, 'logViolation'])->name('interview.violation'); Route::post('/candidate/log-violation/{id}', [InterviewController::class, 'logViolation'])->name('interview.violation');
Route::post('/candidate/upload-recording/{id}', [InterviewController::class, 'uploadRecording'])->name('interview.upload-recording'); Route::post('/candidate/upload-recording/{id}', [InterviewController::class, 'uploadRecording'])->name('interview.upload-recording');
Route::post('/candidate/sync-code/{id}', [InterviewController::class, 'syncCandidateCode'])->name('interview.candidate.sync-code');
Route::post('/candidate/notes/{id}', [InterviewController::class, 'saveCandidateNotes'])->name('interview.candidate.notes'); Route::post('/candidate/notes/{id}', [InterviewController::class, 'saveCandidateNotes'])->name('interview.candidate.notes');
Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing'); Route::post('/candidate/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing');