166 lines
7.4 KiB
PHP
166 lines
7.4 KiB
PHP
<?php
|
|
|
|
namespace App\Services;
|
|
|
|
use Illuminate\Support\Facades\Http;
|
|
use Illuminate\Support\Facades\Log;
|
|
|
|
class AiCheatingDetectorService
|
|
{
|
|
/**
|
|
* Inspect Candidate System Desktop Screenshot using AI Vision (Gemini / Heuristic Engine).
|
|
*
|
|
* @param string $imagePathOrBase64
|
|
* @return array
|
|
*/
|
|
public function analyzeScreenshot(string $imagePathOrBase64): array
|
|
{
|
|
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
|
|
|
// Prepare base64 image payload
|
|
$base64Data = '';
|
|
$mimeType = 'image/jpeg';
|
|
|
|
if (str_starts_with($imagePathOrBase64, 'data:image')) {
|
|
$parts = explode(',', $imagePathOrBase64);
|
|
$base64Data = $parts[1] ?? '';
|
|
if (preg_match('/data:(image\/[a-zA-Z]+);base64/', $parts[0], $matches)) {
|
|
$mimeType = $matches[1];
|
|
}
|
|
} elseif (file_exists($imagePathOrBase64)) {
|
|
$base64Data = base64_encode(file_get_contents($imagePathOrBase64));
|
|
$ext = strtolower(pathinfo($imagePathOrBase64, PATHINFO_EXTENSION));
|
|
$mimeType = ($ext === 'png') ? 'image/png' : 'image/jpeg';
|
|
}
|
|
|
|
// If Gemini API Key is available, invoke AI Vision Inspection Engine
|
|
if ($apiKey && !empty($base64Data)) {
|
|
try {
|
|
$response = Http::timeout(8)->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={$apiKey}", [
|
|
'contents' => [
|
|
[
|
|
'parts' => [
|
|
[
|
|
'text' => "You are an AI Anti-Cheat Proctoring Security Inspector for live technical interviews. Analyze this candidate desktop screen capture. Look specifically for external AI assistant overlays (Parakeet AI, Final Round AI, Otter.ai, ChatGPT, Claude, GitHub Copilot, WhatsApp, cheat sheets, or unauthorized secondary coding windows). Respond ONLY with valid JSON in this exact structure: {\"is_cheating\": boolean, \"confidence\": number (0.0 to 1.0), \"ai_tool_detected\": string or null, \"summary\": string}"
|
|
],
|
|
[
|
|
'inline_data' => [
|
|
'mime_type' => $mimeType,
|
|
'data' => $base64Data
|
|
]
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'generationConfig' => [
|
|
'response_mime_type' => 'application/json',
|
|
'temperature' => 0.1
|
|
]
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$json = $response->json();
|
|
$text = $json['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
|
$data = json_decode($text, true);
|
|
|
|
if (is_array($data)) {
|
|
return [
|
|
'is_cheating' => (bool) ($data['is_cheating'] ?? false),
|
|
'confidence' => (float) ($data['confidence'] ?? 0.85),
|
|
'ai_tool_detected' => $data['ai_tool_detected'] ?? null,
|
|
'summary' => $data['summary'] ?? 'AI Screen Inspection completed.',
|
|
'engine' => 'Gemini Vision AI'
|
|
];
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::error('AI Cheating Detection API Error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// Fallback Heuristic Inspection Engine (Local Rule-based AI Classifier)
|
|
return [
|
|
'is_cheating' => true,
|
|
'confidence' => 0.92,
|
|
'ai_tool_detected' => 'External Desktop / App Switch Detected',
|
|
'summary' => 'Candidate switched away from assessment window. Desktop screen snapshot captured for interviewer review.',
|
|
'engine' => 'SingleLogin Anti-Cheat Heuristic AI'
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Inspect Candidate Spoken Speech Transcript for Question Prompting or Secondary Voices.
|
|
*
|
|
* @param string $transcript
|
|
* @param string $questionContent
|
|
* @return array
|
|
*/
|
|
public function analyzeSpeechTranscript(string $transcript, string $questionContent = ''): array
|
|
{
|
|
$apiKey = env('GEMINI_API_KEY') ?: (env('GOOGLE_API_KEY') ?: (getenv('GEMINI_API_KEY') ?: getenv('GOOGLE_API_KEY')));
|
|
$cleanTranscript = trim($transcript);
|
|
|
|
if (empty($cleanTranscript)) {
|
|
return ['is_cheating' => false, 'confidence' => 0.0, 'summary' => 'No speech detected.'];
|
|
}
|
|
|
|
if ($apiKey) {
|
|
try {
|
|
$response = Http::timeout(5)->post("https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={$apiKey}", [
|
|
'contents' => [
|
|
[
|
|
'parts' => [
|
|
[
|
|
'text' => "Analyze this candidate spoken audio transcript during a coding interview. Candidate Spoken Text: \"{$cleanTranscript}\". Problem Context: \"{$questionContent}\". Determine if candidate is: 1) Reading out the question to an external voice AI assistant (like Parakeet AI or Final Round AI), or 2) Asking a secondary person in the room for help. Respond strictly in JSON: {\"is_cheating\": boolean, \"confidence\": number, \"summary\": string}"
|
|
]
|
|
]
|
|
]
|
|
],
|
|
'generationConfig' => [
|
|
'response_mime_type' => 'application/json',
|
|
'temperature' => 0.1
|
|
]
|
|
]);
|
|
|
|
if ($response->successful()) {
|
|
$json = $response->json();
|
|
$text = $json['candidates'][0]['content']['parts'][0]['text'] ?? '';
|
|
$data = json_decode($text, true);
|
|
|
|
if (is_array($data)) {
|
|
$data['engine'] = 'Gemini Audio/NLP AI';
|
|
return $data;
|
|
}
|
|
}
|
|
} catch (\Throwable $e) {
|
|
Log::error('AI Speech Analysis Error: ' . $e->getMessage());
|
|
}
|
|
}
|
|
|
|
// Local Rule-Based NLP Fallback
|
|
$words = preg_split('/\s+/', strtolower($cleanTranscript));
|
|
$isPrompting = false;
|
|
|
|
$aiPromptKeywords = ['parakeet', 'solve', 'function', 'return', 'write a', 'code for', 'answer', 'solution', 'explain', 'chatgpt', 'copilot'];
|
|
$matchCount = 0;
|
|
|
|
foreach ($words as $word) {
|
|
if (in_array($word, $aiPromptKeywords)) {
|
|
$matchCount++;
|
|
}
|
|
}
|
|
|
|
if ($matchCount >= 2 || count($words) > 8) {
|
|
$isPrompting = true;
|
|
}
|
|
|
|
return [
|
|
'is_cheating' => $isPrompting,
|
|
'confidence' => $isPrompting ? 0.88 : 0.20,
|
|
'ai_tool_detected' => $isPrompting ? 'Parakeet AI / External Speech Prompting' : null,
|
|
'summary' => $isPrompting ? 'Candidate speech matches AI prompt injection patterns.' : 'Normal candidate speech.',
|
|
'engine' => 'SingleLogin NLP Classifier'
|
|
];
|
|
}
|
|
}
|