feat: Implement Zero-Touch Autonomous Forensic AI Video & Curriculum Analyzer (Gemini timeline chaptering, strictly chronological Socratic checkpoints, and curriculum grounding)

This commit is contained in:
Hamza-Ayed
2026-08-28 01:42:59 +03:00
parent 54ceab1aae
commit 4d01962cf8
5 changed files with 450 additions and 88 deletions
+35 -11
View File
@@ -7,16 +7,19 @@ use App\Core\Response;
use App\Core\Database;
use App\Core\Security;
use App\Services\VideoService;
use App\Services\AiVideoAnalyzerService;
use App\Services\CurriculumService;
class VideoController
{
/**
* Upload Video directly via API and create/attach to Lesson
* Upload Video directly via API, transcode HLS, and trigger Autonomous Gemini AI Analysis
* POST /api/teacher/videos/upload-direct
*/
public function uploadDirect(Request $request, Response $response): void
{
VideoService::ensureSchema();
CurriculumService::ensureSchema();
$courseId = (int)($request->getBody()['course_id'] ?? $_POST['course_id'] ?? 0);
$title = trim((string)($request->getBody()['title'] ?? $_POST['title'] ?? ''));
@@ -67,13 +70,17 @@ class VideoController
]
);
// Autonomous Zero-Touch AI Analysis & Socratic Checkpoint Generation (Silent Background Execution)
$aiReport = AiVideoAnalyzerService::processLessonAutonomously($lessonId);
$response->status(201)->json([
'status' => 'success',
'message' => 'تم رفع وحفظ ملف الفيديو وتقطيعه بتقنية HLS بنجاح!',
'message' => 'تم رفع الفيديو وتقطيعه بتقنية HLS وتوليد الفحص السقراطي الذكي تلقائياً بنجاح!',
'data' => array_merge($uploadResult, [
'lesson_id' => $lessonId,
'title' => $title,
'course_id' => $courseId
'lesson_id' => $lessonId,
'title' => $title,
'course_id' => $courseId,
'ai_analysis' => $aiReport
])
]);
} catch (\Throwable $e) {
@@ -117,18 +124,19 @@ class VideoController
}
/**
* Link an existing or newly created Bunny Video ID to a Course Lesson
* Link Bunny Video ID to a Course Lesson with Autonomous AI Socratic Generation
* POST /api/teacher/videos/bunny-link
*/
public function linkBunnyLesson(Request $request, Response $response): void
{
VideoService::ensureSchema();
CurriculumService::ensureSchema();
$body = $request->getBody();
$courseId = (int)($body['course_id'] ?? 0);
$title = trim((string)($body['title'] ?? ''));
$bunnyVideoId = trim((string)($body['bunny_video_id'] ?? ''));
$duration = (int)($body['duration_seconds'] ?? 0);
$duration = (int)($body['duration_seconds'] ?? 600);
$sequenceOrder = (int)($body['sequence_order'] ?? 1);
if (!$courseId || empty($title) || empty($bunnyVideoId)) {
@@ -154,13 +162,17 @@ class VideoController
[$courseId, $title, $sequenceOrder, $bunnyVideoId, $duration]
);
// Autonomous AI Analysis for Bunny Lessons
$aiReport = AiVideoAnalyzerService::processLessonAutonomously($lessonId);
$response->status(201)->json([
'status' => 'success',
'message' => 'تم ربط درس Bunny Stream بنجاح!',
'message' => 'تم ربط درس Bunny Stream وتوليد نقاط الفحص السقراطي تلقائياً!',
'data' => [
'lesson_id' => $lessonId,
'bunny_video_id' => $bunnyVideoId,
'storage_type' => 'bunny_stream'
'storage_type' => 'bunny_stream',
'ai_analysis' => $aiReport
]
]);
}
@@ -272,12 +284,13 @@ class VideoController
}
/**
* Get Lesson Playback Data with Signed DRM Tokens and Socratic Checkpoints
* Get Lesson Playback Data with Chapters Roadmap and Socratic Checkpoints
* GET /api/lessons/{id}/playback
*/
public function getPlaybackData(Request $request, Response $response): void
{
VideoService::ensureSchema();
CurriculumService::ensureSchema();
$lessonId = (int)$request->getParam('id');
if (!$lessonId) {
@@ -291,6 +304,13 @@ class VideoController
return;
}
// If lesson has no checkpoints yet, generate them autonomously
$existingCount = Database::selectOne("SELECT COUNT(*) as cnt FROM exams WHERE lesson_id = ? AND scope = 'in_video_checkpoint'", [$lessonId]);
if (empty($existingCount['cnt'])) {
AiVideoAnalyzerService::processLessonAutonomously($lessonId);
$lesson = Database::selectOne("SELECT * FROM lessons WHERE id = ? LIMIT 1", [$lessonId]);
}
// Fetch attached in-video Socratic Checkpoints with Questions and Options
$exams = Database::select(
"SELECT e.id as exam_id, e.uuid as exam_uuid, e.title, e.timestamp_seconds, e.rewind_on_fail_seconds, e.passing_percentage
@@ -312,7 +332,8 @@ class VideoController
'exam_id' => (int)$ex['exam_id'],
'timestamp_seconds' => (int)$ex['timestamp_seconds'],
'rewind_on_fail_seconds' => (int)$ex['rewind_on_fail_seconds'],
'question_text' => $q['question_text'] ?? 'ما هي الإجابة الصحيحة؟',
'question_text' => $q['question_text'] ?? 'سؤال فحص فهم الفكرة:',
'explanation' => $q['explanation_text'] ?? '',
'options' => array_map(function ($o) {
return [
'id' => (int)$o['id'],
@@ -341,6 +362,8 @@ class VideoController
$playbackInfo = array_merge(['storage_type' => 'bunny_stream'], $signedData);
}
$chapters = !empty($lesson['timeline_chapters_json']) ? json_decode($lesson['timeline_chapters_json'], true) : [];
$response->json([
'status' => 'success',
'data' => [
@@ -353,6 +376,7 @@ class VideoController
'storage_type' => $storageType
],
'playback' => $playbackInfo,
'chapters' => $chapters ?: [],
'checkpoints' => $checkpoints ?: []
]
]);