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]);
}
/**
* 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.
*/

View File

@ -383,6 +383,24 @@
automaticLayout: 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

View File

@ -669,6 +669,8 @@ function toggleFullscreenScreen() {
let activeViolations = [];
let sosPollInterval = null;
let acknowledgedViolationCount = 0;
let sosDismissed = false;
function switchIdeTab(tab) {
document.getElementById('tab-content-code').style.display = (tab === 'code') ? 'block' : 'none';
@ -687,6 +689,9 @@ function switchIdeTab(tab) {
function openReviewModal(id, name, uid) {
currentInterviewId = id;
acknowledgedViolationCount = 0;
sosDismissed = false;
document.getElementById('modal-cand-name').innerText = name;
document.getElementById('modal-cand-uid').innerText = 'Unique Submission ID: ' + uid;
@ -703,7 +708,7 @@ function openReviewModal(id, name, uid) {
pollReviewData();
clearInterval(sosPollInterval);
sosPollInterval = setInterval(pollReviewData, 4000);
sosPollInterval = setInterval(pollReviewData, 2000);
}
function pollReviewData() {
@ -712,7 +717,7 @@ function pollReviewData() {
.then(r => r.json())
.then(data => {
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-notes-display').innerText = data.candidate_notes || 'No candidate notes written yet.';
@ -728,17 +733,15 @@ function pollReviewData() {
}
let logsHtml = '';
let cheatingDetected = false;
activeViolations = [];
let currentViolations = [];
if (data.proctor_logs && data.proctor_logs.length > 0) {
data.proctor_logs.forEach(l => {
let icon = '🔴';
if (l.type === 'focus_lost') icon = '🟡';
if (l.type === 'gaze_anomaly') icon = '👁️';
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost') {
cheatingDetected = true;
activeViolations.push(l);
if (l.type === 'paste_event' || l.type === 'tab_switch' || l.type === 'focus_lost' || l.type === 'gaze_anomaly') {
currentViolations.push(l);
}
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 || ''}
@ -750,9 +753,21 @@ function pollReviewData() {
}
document.getElementById('modal-logs-display').innerHTML = logsHtml;
activeViolations = currentViolations;
const totalViolations = currentViolations.length;
const sosBtn = document.getElementById('sos-alert-btn');
if (cheatingDetected) {
if (totalViolations > 0) {
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 {
sosBtn.style.display = 'none';
}
@ -777,10 +792,13 @@ function openSosModal() {
function stopSosAlarm() {
document.getElementById('sos-modal').classList.remove('active');
acknowledgedViolationCount = activeViolations.length;
sosDismissed = true;
const sosBtn = document.getElementById('sos-alert-btn');
if (sosBtn) {
sosBtn.style.animation = 'none';
sosBtn.innerText = '✅ SOS Acknowledged';
sosBtn.innerText = '✅ SOS Muted (Auto-rearm active)';
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/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/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/drawing/{id}', [InterviewController::class, 'saveCandidateDrawing'])->name('interview.candidate.drawing');