Files
hack/tools/scripts/llm-key-hunter/results/_cache/content/asley_chhs-testing.json
T

1 line
311 KiB
JSON

{"repo": "asley/chhs-testing", "files": {"modules/ChatBot/moduleFunctions.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745766.0855227, "n": 21952, "h": "6781a141c0d264fc", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Forms\\Form;\nuse Gibbon\\Services\\Format as GibbonFormat;\nuse Gibbon\\Domain\\System\\ModuleGateway;\nuse Gibbon\\Domain\\DataSet;\nuse Gibbon\\Tables\\DataTable;\n\n// Include Gibbon core functions\nrequire_once __DIR__ . '/../../functions.php';\n\n/**\n * Get a setting value by scope\n * @param PDO $connection2 The database connection\n * @param string $scope The setting scope\n * @param string $name The setting name\n * @return string The setting value\n */\nfunction getSettingByScope($connection2, $scope, $name) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope=:scope AND name=:name\";\n $result = $connection2->prepare($sql);\n $result->execute(['scope' => $scope, 'name' => $name]);\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Check if a file is a valid PDF\n * @param string $file The file path\n * @return bool Whether the file is a valid PDF\n */\nfunction isValidPDF($file) {\n if (empty($file)) return false;\n \n // Check file extension\n $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));\n if ($extension !== 'pdf') return false;\n \n // Check file mime type\n $finfo = finfo_open(FILEINFO_MIME_TYPE);\n $mimeType = finfo_file($finfo, $file);\n finfo_close($finfo);\n \n return $mimeType === 'application/pdf';\n}\n\n/**\n * Extract text from PDF file\n * @param string $pdfFile The PDF file path\n * @return string The extracted text\n */\nfunction extractTextFromPDF($pdfFile) {\n if (!isValidPDF($pdfFile)) {\n throw new Exception('Invalid PDF file');\n }\n \n // Create temporary files\n $tempPdf = tempnam(sys_get_temp_dir(), 'pdf_');\n $tempTxt = tempnam(sys_get_temp_dir(), 'txt_');\n \n try {\n // Copy PDF to temp location\n if (!copy($pdfFile, $tempPdf)) {\n throw new Exception('Failed to copy PDF file');\n }\n \n // Convert PDF to text\n exec(\"pdftotext '{$tempPdf}' '{$tempTxt}' 2>&1\", $output, $returnVar);\n \n if ($returnVar !== 0) {\n throw new Exception('Failed to convert PDF: ' . implode(\"\\n\", $output));\n }\n \n // Read converted text\n $text = file_get_contents($tempTxt);\n \n return $text;\n \n } finally {\n // Clean up temp files\n if (file_exists($tempPdf)) unlink($tempPdf);\n if (file_exists($tempTxt)) unlink($tempTxt);\n }\n}\n\n/**\n * Store the API key securely\n * @param PDO $connection2 The database connection\n * @param string $apiKey The API key to store\n * @return bool Whether the operation was successful\n */\nfunction storeAPIKey($connection2, $apiKey) {\n try {\n $sql = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) \n VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek integration', :apiKey)\n ON DUPLICATE KEY UPDATE value = :apiKey\";\n \n $result = $connection2->prepare($sql);\n return $result->execute(['apiKey' => $apiKey]);\n } catch (PDOException $e) {\n error_log('Error storing API key: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get the API key securely\n * @param PDO $connection2 The database connection\n * @return string The API key\n */\nfunction getSecureAPIKey($connection2) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection2->prepare($sql);\n $result->execute();\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Get student assessment data\n * @param PDO $connection2 The database connection\n * @param int $studentID The student ID\n * @return array The assessment data\n */\nfunction getStudentAssessmentData($connection2, $studentID) {\n try {\n $sql = \"SELECT \n ia.name as assessment_name,\n iac.attainmentValue,\n iac.effortValue,\n iac.comment as teacher_feedback,\n c.name as course_name\n FROM gibbonInternalAssessmentEntry iac\n JOIN gibbonInternalAssessmentColumn ia ON (ia.gibbonInternalAssessmentColumnID=iac.gibbonInternalAssessmentColumnID)\n JOIN gibbonCourse c ON (c.gibbonCourseID=ia.gibbonCourseID)\n WHERE iac.gibbonPersonID=:studentID\n ORDER BY ia.timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['studentID' => $studentID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (Exception $e) {\n error_log('Error getting student assessment data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Make a secure call to the OpenAI API\n * \n * @param string $apiKey\n * @param array $data\n * @return array|null\n */\nfunction callOpenAIAPI($apiKey, $data) {\n try {\n // Initialize cURL session\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n \n // Set cURL options\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ],\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_SSL_VERIFYPEER => true, // Verify SSL certificate\n CURLOPT_SSL_VERIFYHOST => 2, // Verify hostname\n CURLOPT_CONNECTTIMEOUT => 10, // Connection timeout\n CURLOPT_TIMEOUT => 30, // Response timeout\n CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, // Only allow HTTPS\n ]);\n \n // Execute request\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n \n // Check for errors\n if ($httpCode !== 200) {\n error_log('OpenAI API error: HTTP ' . $httpCode);\n error_log('Response: ' . $response);\n return null;\n }\n \n // Close cURL session\n curl_close($ch);\n \n // Parse and return response\n return json_decode($response, true);\n \n } catch (Exception $e) {\n error_log('Error calling OpenAI API: ' . $e->getMessage());\n return null;\n }\n}\n\n// Only declare if not already defined\nif (!function_exists('isActionAccessible')) {\n /**\n * Check if a user has access to a specific action\n * @param mixed $guid The global unique identifier\n * @param PDO $connection2 The database connection\n * @param string $address The action address to check\n * @return bool Whether the user has access\n */\n function isActionAccessible($guid, $connection2, $address) {\n try {\n $data = array('gibbonRoleID' => $_SESSION[$guid]['gibbonRoleIDCurrent'], 'address' => $address);\n $sql = \"SELECT DISTINCT gibbonAction.gibbonActionID \n FROM gibbonAction \n JOIN gibbonPermission ON (gibbonAction.gibbonActionID=gibbonPermission.gibbonActionID) \n WHERE (gibbonPermission.gibbonRoleID=:gibbonRoleID AND gibbonAction.URLList LIKE CONCAT('%', :address, '%'))\";\n $result = $connection2->prepare($sql);\n $result->execute($data);\n return ($result->rowCount() > 0);\n } catch (PDOException $e) {\n return false;\n }\n }\n}\n\n/**\n * Helper functions for the ChatBot module\n */\n\n/**\n * Check if a user has access to the ChatBot module\n *\n * @param string $guid\n * @param PDO $connection2\n * @return bool\n */\nfunction chatbotCheckAccess($guid, $connection2) {\n return isActionAccessible($guid, $connection2, '/modules/ChatBot/chatbot.php');\n}\n\n/**\n * Get the DeepSeek API key from settings\n *\n * @param SettingGateway $settingGateway\n * @return string|null\n */\nfunction chatbotGetApiKey($settingGateway) {\n return $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n}\n\n/**\n * Format chat messages for display\n *\n * @param array $messages\n * @return string\n */\nfunction chatbotFormatMessages($messages) {\n $output = '';\n foreach ($messages as $message) {\n $type = $message['type'];\n $content = $message['content'];\n $output .= \"<div class='message {$type}-message'>\";\n $output .= \"<div class='message-content'>{$content}</div>\";\n $output .= \"</div>\";\n }\n return $output;\n}\n\n/**\n * Validate chat message\n *\n * @param string $message\n * @return bool\n */\nfunction chatbotValidateMessage($message) {\n return !empty(trim($message));\n}\n\n/**\n * Sanitize chat message\n *\n * @param string $message\n * @return string\n */\nfunction chatbotSanitizeMessage($message) {\n return strip_tags(trim($message));\n}\n\n/**\n * Store AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @param int $gibbonCourseClassID The course class ID\n * @param int $assessmentID The assessment ID\n * @param string $recommendation The AI recommendation\n * @return bool Whether the operation was successful\n */\nfunction storeRecommendation($connection2, $gibbonPersonID, $gibbonCourseClassID, $assessmentID, $recommendation) {\n try {\n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $gibbonCourseClassID,\n 'assessmentID' => $assessmentID,\n 'recommendation' => $recommendation\n ]);\n } catch (PDOException $e) {\n error_log('Error storing recommendation: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @return array The recommendations\n */\nfunction getStudentRecommendations($connection2, $gibbonPersonID) {\n try {\n $sql = \"SELECT r.*, \n ia.name as assessment_name,\n c.name as course_name\n FROM gibbonChatBotRecommendations r\n JOIN gibbonInternalAssessment ia ON (ia.gibbonInternalAssessmentID = r.assessmentID)\n JOIN gibbonCourseClass cc ON (cc.gibbonCourseClassID = r.gibbonCourseClassID)\n JOIN gibbonCourse c ON (c.gibbonCourseID = cc.gibbonCourseID)\n WHERE r.gibbonPersonID = :gibbonPersonID\n ORDER BY r.timestamp DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['gibbonPersonID' => $gibbonPersonID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting student recommendations: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Add training data to the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $question The question\n * @param string $answer The answer\n * @param int $createdBy The user ID who created the entry\n * @param string|null $category The category (optional)\n * @return bool Whether the operation was successful\n */\nfunction addTrainingData($connection2, $question, $answer, $createdBy, $category = null) {\n try {\n $sql = \"INSERT INTO gibbonChatBotTraining \n (question, answer, category, created_by) \n VALUES \n (:question, :answer, :category, :created_by)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'question' => $question,\n 'answer' => $answer,\n 'category' => $category,\n 'created_by' => $createdBy\n ]);\n } catch (PDOException $e) {\n error_log('Error adding training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get training data for the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $category Optional category filter\n * @return array The training data\n */\nfunction getTrainingData($connection2, $category = null) {\n try {\n $sql = \"SELECT * FROM gibbonChatBotTraining\";\n $params = [];\n \n if ($category) {\n $sql .= \" WHERE category = :category\";\n $params['category'] = $category;\n }\n \n $sql .= \" ORDER BY timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute($params);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting training data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Delete training data\n * @param PDO $connection2 The database connection\n * @param int $id The training data ID\n * @return bool Whether the operation was successful\n */\nfunction deleteTrainingData($connection2, $id) {\n try {\n $sql = \"DELETE FROM gibbonChatBotTraining WHERE gibbonChatBotTrainingID = :id\";\n $result = $connection2->prepare($sql);\n return $result->execute(['id' => $id]);\n } catch (PDOException $e) {\n error_log('Error deleting training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate AI recommendations based on assessment data\n * @param array $assessment The assessment data\n * @param string $apiKey The DeepSeek API key\n * @return string|null The AI recommendation\n */\nfunction generateAssessmentRecommendation($assessment, $apiKey) {\n try {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n $data = [\n 'model' => 'deepseek-v4-flash',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (Exception $e) {\n error_log('Error generating assessment recommendation: ' . $e->getMessage());\n }\n \n return null;\n}\n\n/**\n * Get student performance metrics\n * @param int $gibbonPersonID\n * @return string HTML output of performance metrics\n */\nfunction getStudentPerformanceMetrics($gibbonPersonID) {\n global $pdo;\n \n try {\n // Get assessment data\n $data = getStudentAssessmentData($gibbonPersonID);\n \n // Calculate metrics\n $metrics = [\n 'average_attainment' => calculateAverageAttainment($data),\n 'progress_indicators' => analyzeProgressTrends($data),\n 'subject_breakdown' => getSubjectPerformance($data)\n ];\n \n // Generate HTML output\n $output = '<div class=\"metrics-grid\">';\n foreach ($metrics as $key => $value) {\n $output .= generateMetricCard($key, $value);\n }\n $output .= '</div>';\n \n return $output;\n } catch (Exception $e) {\n return \"Error generating performance metrics: \" . $e->getMessage();\n }\n}\n\n/**\n * Generate AI recommendations based on student data\n * @param int $gibbonPersonID\n * @return string HTML output of recommendations\n */\nfunction generateAIRecommendations($gibbonPersonID) {\n try {\n // Get comprehensive student data\n $assessmentData = getStudentAssessmentData($gibbonPersonID);\n $feedbackData = getTeacherFeedback($gibbonPersonID);\n $engagementData = getStudentEngagementData($gibbonPersonID);\n \n // Prepare data for AI analysis\n $analysisData = [\n 'assessment_history' => $assessmentData,\n 'teacher_feedback' => $feedbackData,\n 'engagement_metrics' => $engagementData,\n 'learning_patterns' => analyzeLearningPatterns($gibbonPersonID)\n ];\n \n // Get AI recommendations\n $recommendations = callDeepSeekAPI($analysisData);\n \n // Format recommendations\n return formatAIRecommendations($recommendations);\n } catch (Exception $e) {\n return \"Error generating AI recommendations: \" . $e->getMessage();\n }\n}\n\n/**\n * Process and store training data\n * @param array $data Training data array\n * @return bool Success status\n */\nfunction processTrainingData($data) {\n try {\n // Validate training data\n validateTrainingData($data);\n \n // Enrich data with institutional context\n $enrichedData = enrichTrainingData($data);\n \n // Store in database\n storeTrainingData($enrichedData);\n \n // Update AI model\n updateAIModel($enrichedData);\n \n return true;\n } catch (Exception $e) {\n error_log(\"Training data processing error: \" . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate learning analytics\n * @param array $params Analytics parameters\n * @return array Analytics data\n */\nfunction generateLearningAnalytics($params) {\n try {\n // Gather data\n $performanceData = getPerformanceData($params);\n $engagementData = getEngagementData($params);\n $patternData = getPatternData($params);\n \n // Process analytics\n $analytics = [\n 'performance' => analyzePerformance($performanceData),\n 'engagement' => analyzeEngagement($engagementData),\n 'patterns' => analyzePatterns($patternData),\n 'recommendations' => generateRecommendations([\n 'performance' => $performanceData,\n 'engagement' => $engagementData,\n 'patterns' => $patternData\n ])\n ];\n \n return $analytics;\n } catch (Exception $e) {\n error_log(\"Analytics generation error: \" . $e->getMessage());\n return null;\n }\n}\n\n/**\n * Format AI recommendations for display\n * @param array $recommendations\n * @return string Formatted HTML\n */\nfunction formatAIRecommendations($recommendations) {\n $output = '<div class=\"recommendations-container\">';\n \n // Format subject-specific recommendations\n if (isset($recommendations['subjects'])) {\n $output .= '<div class=\"subject-recommendations\">';\n $output .= '<h4>Subject-Specific Recommendations</h4>';\n foreach ($recommendations['subjects'] as $subject => $data) {\n $output .= formatSubjectRecommendation($subject, $data);\n }\n $output .= '</div>';\n }\n \n // Format learning strategy recommendations\n if (isset($recommendations['strategies'])) {\n $output .= '<div class=\"strategy-recommendations\">';\n $output .= '<h4>Learning Strategy Recommendations</h4>';\n foreach ($recommendations['strategies'] as $strategy) {\n $output .= formatStrategyRecommendation($strategy);\n }\n $output .= '</div>';\n }\n \n // Format improvement suggestions\n if (isset($recommendations['improvements'])) {\n $output .= '<div class=\"improvement-suggestions\">';\n $output .= '<h4>Areas for Improvement</h4>';\n foreach ($recommendations['improvements'] as $area => $suggestions) {\n $output .= formatImprovementSuggestions($area, $suggestions);\n }\n $output .= '</div>';\n }\n \n $output .= '</div>';\n return $output;\n}\n\n"}, "228065060bc341b101409b1b5a4415a502b48a3a": {"t": 1785756298.6214683, "n": 21952, "h": "6781a141c0d264fc", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Forms\\Form;\nuse Gibbon\\Services\\Format as GibbonFormat;\nuse Gibbon\\Domain\\System\\ModuleGateway;\nuse Gibbon\\Domain\\DataSet;\nuse Gibbon\\Tables\\DataTable;\n\n// Include Gibbon core functions\nrequire_once __DIR__ . '/../../functions.php';\n\n/**\n * Get a setting value by scope\n * @param PDO $connection2 The database connection\n * @param string $scope The setting scope\n * @param string $name The setting name\n * @return string The setting value\n */\nfunction getSettingByScope($connection2, $scope, $name) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope=:scope AND name=:name\";\n $result = $connection2->prepare($sql);\n $result->execute(['scope' => $scope, 'name' => $name]);\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Check if a file is a valid PDF\n * @param string $file The file path\n * @return bool Whether the file is a valid PDF\n */\nfunction isValidPDF($file) {\n if (empty($file)) return false;\n \n // Check file extension\n $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));\n if ($extension !== 'pdf') return false;\n \n // Check file mime type\n $finfo = finfo_open(FILEINFO_MIME_TYPE);\n $mimeType = finfo_file($finfo, $file);\n finfo_close($finfo);\n \n return $mimeType === 'application/pdf';\n}\n\n/**\n * Extract text from PDF file\n * @param string $pdfFile The PDF file path\n * @return string The extracted text\n */\nfunction extractTextFromPDF($pdfFile) {\n if (!isValidPDF($pdfFile)) {\n throw new Exception('Invalid PDF file');\n }\n \n // Create temporary files\n $tempPdf = tempnam(sys_get_temp_dir(), 'pdf_');\n $tempTxt = tempnam(sys_get_temp_dir(), 'txt_');\n \n try {\n // Copy PDF to temp location\n if (!copy($pdfFile, $tempPdf)) {\n throw new Exception('Failed to copy PDF file');\n }\n \n // Convert PDF to text\n exec(\"pdftotext '{$tempPdf}' '{$tempTxt}' 2>&1\", $output, $returnVar);\n \n if ($returnVar !== 0) {\n throw new Exception('Failed to convert PDF: ' . implode(\"\\n\", $output));\n }\n \n // Read converted text\n $text = file_get_contents($tempTxt);\n \n return $text;\n \n } finally {\n // Clean up temp files\n if (file_exists($tempPdf)) unlink($tempPdf);\n if (file_exists($tempTxt)) unlink($tempTxt);\n }\n}\n\n/**\n * Store the API key securely\n * @param PDO $connection2 The database connection\n * @param string $apiKey The API key to store\n * @return bool Whether the operation was successful\n */\nfunction storeAPIKey($connection2, $apiKey) {\n try {\n $sql = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) \n VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek integration', :apiKey)\n ON DUPLICATE KEY UPDATE value = :apiKey\";\n \n $result = $connection2->prepare($sql);\n return $result->execute(['apiKey' => $apiKey]);\n } catch (PDOException $e) {\n error_log('Error storing API key: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get the API key securely\n * @param PDO $connection2 The database connection\n * @return string The API key\n */\nfunction getSecureAPIKey($connection2) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection2->prepare($sql);\n $result->execute();\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Get student assessment data\n * @param PDO $connection2 The database connection\n * @param int $studentID The student ID\n * @return array The assessment data\n */\nfunction getStudentAssessmentData($connection2, $studentID) {\n try {\n $sql = \"SELECT \n ia.name as assessment_name,\n iac.attainmentValue,\n iac.effortValue,\n iac.comment as teacher_feedback,\n c.name as course_name\n FROM gibbonInternalAssessmentEntry iac\n JOIN gibbonInternalAssessmentColumn ia ON (ia.gibbonInternalAssessmentColumnID=iac.gibbonInternalAssessmentColumnID)\n JOIN gibbonCourse c ON (c.gibbonCourseID=ia.gibbonCourseID)\n WHERE iac.gibbonPersonID=:studentID\n ORDER BY ia.timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['studentID' => $studentID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (Exception $e) {\n error_log('Error getting student assessment data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Make a secure call to the OpenAI API\n * \n * @param string $apiKey\n * @param array $data\n * @return array|null\n */\nfunction callOpenAIAPI($apiKey, $data) {\n try {\n // Initialize cURL session\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n \n // Set cURL options\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ],\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_SSL_VERIFYPEER => true, // Verify SSL certificate\n CURLOPT_SSL_VERIFYHOST => 2, // Verify hostname\n CURLOPT_CONNECTTIMEOUT => 10, // Connection timeout\n CURLOPT_TIMEOUT => 30, // Response timeout\n CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, // Only allow HTTPS\n ]);\n \n // Execute request\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n \n // Check for errors\n if ($httpCode !== 200) {\n error_log('OpenAI API error: HTTP ' . $httpCode);\n error_log('Response: ' . $response);\n return null;\n }\n \n // Close cURL session\n curl_close($ch);\n \n // Parse and return response\n return json_decode($response, true);\n \n } catch (Exception $e) {\n error_log('Error calling OpenAI API: ' . $e->getMessage());\n return null;\n }\n}\n\n// Only declare if not already defined\nif (!function_exists('isActionAccessible')) {\n /**\n * Check if a user has access to a specific action\n * @param mixed $guid The global unique identifier\n * @param PDO $connection2 The database connection\n * @param string $address The action address to check\n * @return bool Whether the user has access\n */\n function isActionAccessible($guid, $connection2, $address) {\n try {\n $data = array('gibbonRoleID' => $_SESSION[$guid]['gibbonRoleIDCurrent'], 'address' => $address);\n $sql = \"SELECT DISTINCT gibbonAction.gibbonActionID \n FROM gibbonAction \n JOIN gibbonPermission ON (gibbonAction.gibbonActionID=gibbonPermission.gibbonActionID) \n WHERE (gibbonPermission.gibbonRoleID=:gibbonRoleID AND gibbonAction.URLList LIKE CONCAT('%', :address, '%'))\";\n $result = $connection2->prepare($sql);\n $result->execute($data);\n return ($result->rowCount() > 0);\n } catch (PDOException $e) {\n return false;\n }\n }\n}\n\n/**\n * Helper functions for the ChatBot module\n */\n\n/**\n * Check if a user has access to the ChatBot module\n *\n * @param string $guid\n * @param PDO $connection2\n * @return bool\n */\nfunction chatbotCheckAccess($guid, $connection2) {\n return isActionAccessible($guid, $connection2, '/modules/ChatBot/chatbot.php');\n}\n\n/**\n * Get the DeepSeek API key from settings\n *\n * @param SettingGateway $settingGateway\n * @return string|null\n */\nfunction chatbotGetApiKey($settingGateway) {\n return $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n}\n\n/**\n * Format chat messages for display\n *\n * @param array $messages\n * @return string\n */\nfunction chatbotFormatMessages($messages) {\n $output = '';\n foreach ($messages as $message) {\n $type = $message['type'];\n $content = $message['content'];\n $output .= \"<div class='message {$type}-message'>\";\n $output .= \"<div class='message-content'>{$content}</div>\";\n $output .= \"</div>\";\n }\n return $output;\n}\n\n/**\n * Validate chat message\n *\n * @param string $message\n * @return bool\n */\nfunction chatbotValidateMessage($message) {\n return !empty(trim($message));\n}\n\n/**\n * Sanitize chat message\n *\n * @param string $message\n * @return string\n */\nfunction chatbotSanitizeMessage($message) {\n return strip_tags(trim($message));\n}\n\n/**\n * Store AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @param int $gibbonCourseClassID The course class ID\n * @param int $assessmentID The assessment ID\n * @param string $recommendation The AI recommendation\n * @return bool Whether the operation was successful\n */\nfunction storeRecommendation($connection2, $gibbonPersonID, $gibbonCourseClassID, $assessmentID, $recommendation) {\n try {\n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $gibbonCourseClassID,\n 'assessmentID' => $assessmentID,\n 'recommendation' => $recommendation\n ]);\n } catch (PDOException $e) {\n error_log('Error storing recommendation: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @return array The recommendations\n */\nfunction getStudentRecommendations($connection2, $gibbonPersonID) {\n try {\n $sql = \"SELECT r.*, \n ia.name as assessment_name,\n c.name as course_name\n FROM gibbonChatBotRecommendations r\n JOIN gibbonInternalAssessment ia ON (ia.gibbonInternalAssessmentID = r.assessmentID)\n JOIN gibbonCourseClass cc ON (cc.gibbonCourseClassID = r.gibbonCourseClassID)\n JOIN gibbonCourse c ON (c.gibbonCourseID = cc.gibbonCourseID)\n WHERE r.gibbonPersonID = :gibbonPersonID\n ORDER BY r.timestamp DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['gibbonPersonID' => $gibbonPersonID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting student recommendations: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Add training data to the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $question The question\n * @param string $answer The answer\n * @param int $createdBy The user ID who created the entry\n * @param string|null $category The category (optional)\n * @return bool Whether the operation was successful\n */\nfunction addTrainingData($connection2, $question, $answer, $createdBy, $category = null) {\n try {\n $sql = \"INSERT INTO gibbonChatBotTraining \n (question, answer, category, created_by) \n VALUES \n (:question, :answer, :category, :created_by)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'question' => $question,\n 'answer' => $answer,\n 'category' => $category,\n 'created_by' => $createdBy\n ]);\n } catch (PDOException $e) {\n error_log('Error adding training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get training data for the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $category Optional category filter\n * @return array The training data\n */\nfunction getTrainingData($connection2, $category = null) {\n try {\n $sql = \"SELECT * FROM gibbonChatBotTraining\";\n $params = [];\n \n if ($category) {\n $sql .= \" WHERE category = :category\";\n $params['category'] = $category;\n }\n \n $sql .= \" ORDER BY timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute($params);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting training data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Delete training data\n * @param PDO $connection2 The database connection\n * @param int $id The training data ID\n * @return bool Whether the operation was successful\n */\nfunction deleteTrainingData($connection2, $id) {\n try {\n $sql = \"DELETE FROM gibbonChatBotTraining WHERE gibbonChatBotTrainingID = :id\";\n $result = $connection2->prepare($sql);\n return $result->execute(['id' => $id]);\n } catch (PDOException $e) {\n error_log('Error deleting training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate AI recommendations based on assessment data\n * @param array $assessment The assessment data\n * @param string $apiKey The DeepSeek API key\n * @return string|null The AI recommendation\n */\nfunction generateAssessmentRecommendation($assessment, $apiKey) {\n try {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n $data = [\n 'model' => 'deepseek-v4-flash',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (Exception $e) {\n error_log('Error generating assessment recommendation: ' . $e->getMessage());\n }\n \n return null;\n}\n\n/**\n * Get student performance metrics\n * @param int $gibbonPersonID\n * @return string HTML output of performance metrics\n */\nfunction getStudentPerformanceMetrics($gibbonPersonID) {\n global $pdo;\n \n try {\n // Get assessment data\n $data = getStudentAssessmentData($gibbonPersonID);\n \n // Calculate metrics\n $metrics = [\n 'average_attainment' => calculateAverageAttainment($data),\n 'progress_indicators' => analyzeProgressTrends($data),\n 'subject_breakdown' => getSubjectPerformance($data)\n ];\n \n // Generate HTML output\n $output = '<div class=\"metrics-grid\">';\n foreach ($metrics as $key => $value) {\n $output .= generateMetricCard($key, $value);\n }\n $output .= '</div>';\n \n return $output;\n } catch (Exception $e) {\n return \"Error generating performance metrics: \" . $e->getMessage();\n }\n}\n\n/**\n * Generate AI recommendations based on student data\n * @param int $gibbonPersonID\n * @return string HTML output of recommendations\n */\nfunction generateAIRecommendations($gibbonPersonID) {\n try {\n // Get comprehensive student data\n $assessmentData = getStudentAssessmentData($gibbonPersonID);\n $feedbackData = getTeacherFeedback($gibbonPersonID);\n $engagementData = getStudentEngagementData($gibbonPersonID);\n \n // Prepare data for AI analysis\n $analysisData = [\n 'assessment_history' => $assessmentData,\n 'teacher_feedback' => $feedbackData,\n 'engagement_metrics' => $engagementData,\n 'learning_patterns' => analyzeLearningPatterns($gibbonPersonID)\n ];\n \n // Get AI recommendations\n $recommendations = callDeepSeekAPI($analysisData);\n \n // Format recommendations\n return formatAIRecommendations($recommendations);\n } catch (Exception $e) {\n return \"Error generating AI recommendations: \" . $e->getMessage();\n }\n}\n\n/**\n * Process and store training data\n * @param array $data Training data array\n * @return bool Success status\n */\nfunction processTrainingData($data) {\n try {\n // Validate training data\n validateTrainingData($data);\n \n // Enrich data with institutional context\n $enrichedData = enrichTrainingData($data);\n \n // Store in database\n storeTrainingData($enrichedData);\n \n // Update AI model\n updateAIModel($enrichedData);\n \n return true;\n } catch (Exception $e) {\n error_log(\"Training data processing error: \" . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate learning analytics\n * @param array $params Analytics parameters\n * @return array Analytics data\n */\nfunction generateLearningAnalytics($params) {\n try {\n // Gather data\n $performanceData = getPerformanceData($params);\n $engagementData = getEngagementData($params);\n $patternData = getPatternData($params);\n \n // Process analytics\n $analytics = [\n 'performance' => analyzePerformance($performanceData),\n 'engagement' => analyzeEngagement($engagementData),\n 'patterns' => analyzePatterns($patternData),\n 'recommendations' => generateRecommendations([\n 'performance' => $performanceData,\n 'engagement' => $engagementData,\n 'patterns' => $patternData\n ])\n ];\n \n return $analytics;\n } catch (Exception $e) {\n error_log(\"Analytics generation error: \" . $e->getMessage());\n return null;\n }\n}\n\n/**\n * Format AI recommendations for display\n * @param array $recommendations\n * @return string Formatted HTML\n */\nfunction formatAIRecommendations($recommendations) {\n $output = '<div class=\"recommendations-container\">';\n \n // Format subject-specific recommendations\n if (isset($recommendations['subjects'])) {\n $output .= '<div class=\"subject-recommendations\">';\n $output .= '<h4>Subject-Specific Recommendations</h4>';\n foreach ($recommendations['subjects'] as $subject => $data) {\n $output .= formatSubjectRecommendation($subject, $data);\n }\n $output .= '</div>';\n }\n \n // Format learning strategy recommendations\n if (isset($recommendations['strategies'])) {\n $output .= '<div class=\"strategy-recommendations\">';\n $output .= '<h4>Learning Strategy Recommendations</h4>';\n foreach ($recommendations['strategies'] as $strategy) {\n $output .= formatStrategyRecommendation($strategy);\n }\n $output .= '</div>';\n }\n \n // Format improvement suggestions\n if (isset($recommendations['improvements'])) {\n $output .= '<div class=\"improvement-suggestions\">';\n $output .= '<h4>Areas for Improvement</h4>';\n foreach ($recommendations['improvements'] as $area => $suggestions) {\n $output .= formatImprovementSuggestions($area, $suggestions);\n }\n $output .= '</div>';\n }\n \n $output .= '</div>';\n return $output;\n}\n\n"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756299.9833863, "n": 21948, "h": "4e3f7455eb0bcd79", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Forms\\Form;\nuse Gibbon\\Services\\Format as GibbonFormat;\nuse Gibbon\\Domain\\System\\ModuleGateway;\nuse Gibbon\\Domain\\DataSet;\nuse Gibbon\\Tables\\DataTable;\n\n// Include Gibbon core functions\nrequire_once __DIR__ . '/../../functions.php';\n\n/**\n * Get a setting value by scope\n * @param PDO $connection2 The database connection\n * @param string $scope The setting scope\n * @param string $name The setting name\n * @return string The setting value\n */\nfunction getSettingByScope($connection2, $scope, $name) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope=:scope AND name=:name\";\n $result = $connection2->prepare($sql);\n $result->execute(['scope' => $scope, 'name' => $name]);\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Check if a file is a valid PDF\n * @param string $file The file path\n * @return bool Whether the file is a valid PDF\n */\nfunction isValidPDF($file) {\n if (empty($file)) return false;\n \n // Check file extension\n $extension = strtolower(pathinfo($file, PATHINFO_EXTENSION));\n if ($extension !== 'pdf') return false;\n \n // Check file mime type\n $finfo = finfo_open(FILEINFO_MIME_TYPE);\n $mimeType = finfo_file($finfo, $file);\n finfo_close($finfo);\n \n return $mimeType === 'application/pdf';\n}\n\n/**\n * Extract text from PDF file\n * @param string $pdfFile The PDF file path\n * @return string The extracted text\n */\nfunction extractTextFromPDF($pdfFile) {\n if (!isValidPDF($pdfFile)) {\n throw new Exception('Invalid PDF file');\n }\n \n // Create temporary files\n $tempPdf = tempnam(sys_get_temp_dir(), 'pdf_');\n $tempTxt = tempnam(sys_get_temp_dir(), 'txt_');\n \n try {\n // Copy PDF to temp location\n if (!copy($pdfFile, $tempPdf)) {\n throw new Exception('Failed to copy PDF file');\n }\n \n // Convert PDF to text\n exec(\"pdftotext '{$tempPdf}' '{$tempTxt}' 2>&1\", $output, $returnVar);\n \n if ($returnVar !== 0) {\n throw new Exception('Failed to convert PDF: ' . implode(\"\\n\", $output));\n }\n \n // Read converted text\n $text = file_get_contents($tempTxt);\n \n return $text;\n \n } finally {\n // Clean up temp files\n if (file_exists($tempPdf)) unlink($tempPdf);\n if (file_exists($tempTxt)) unlink($tempTxt);\n }\n}\n\n/**\n * Store the API key securely\n * @param PDO $connection2 The database connection\n * @param string $apiKey The API key to store\n * @return bool Whether the operation was successful\n */\nfunction storeAPIKey($connection2, $apiKey) {\n try {\n $sql = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) \n VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek integration', :apiKey)\n ON DUPLICATE KEY UPDATE value = :apiKey\";\n \n $result = $connection2->prepare($sql);\n return $result->execute(['apiKey' => $apiKey]);\n } catch (PDOException $e) {\n error_log('Error storing API key: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get the API key securely\n * @param PDO $connection2 The database connection\n * @return string The API key\n */\nfunction getSecureAPIKey($connection2) {\n try {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection2->prepare($sql);\n $result->execute();\n if ($result->rowCount() == 1) {\n $row = $result->fetch();\n return $row['value'];\n }\n } catch (PDOException $e) {\n return '';\n }\n return '';\n}\n\n/**\n * Get student assessment data\n * @param PDO $connection2 The database connection\n * @param int $studentID The student ID\n * @return array The assessment data\n */\nfunction getStudentAssessmentData($connection2, $studentID) {\n try {\n $sql = \"SELECT \n ia.name as assessment_name,\n iac.attainmentValue,\n iac.effortValue,\n iac.comment as teacher_feedback,\n c.name as course_name\n FROM gibbonInternalAssessmentEntry iac\n JOIN gibbonInternalAssessmentColumn ia ON (ia.gibbonInternalAssessmentColumnID=iac.gibbonInternalAssessmentColumnID)\n JOIN gibbonCourse c ON (c.gibbonCourseID=ia.gibbonCourseID)\n WHERE iac.gibbonPersonID=:studentID\n ORDER BY ia.timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['studentID' => $studentID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (Exception $e) {\n error_log('Error getting student assessment data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Make a secure call to the OpenAI API\n * \n * @param string $apiKey\n * @param array $data\n * @return array|null\n */\nfunction callOpenAIAPI($apiKey, $data) {\n try {\n // Initialize cURL session\n $ch = curl_init('https://api.openai.com/v1/chat/completions');\n \n // Set cURL options\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ],\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_SSL_VERIFYPEER => true, // Verify SSL certificate\n CURLOPT_SSL_VERIFYHOST => 2, // Verify hostname\n CURLOPT_CONNECTTIMEOUT => 10, // Connection timeout\n CURLOPT_TIMEOUT => 30, // Response timeout\n CURLOPT_PROTOCOLS => CURLPROTO_HTTPS, // Only allow HTTPS\n ]);\n \n // Execute request\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n \n // Check for errors\n if ($httpCode !== 200) {\n error_log('OpenAI API error: HTTP ' . $httpCode);\n error_log('Response: ' . $response);\n return null;\n }\n \n // Close cURL session\n curl_close($ch);\n \n // Parse and return response\n return json_decode($response, true);\n \n } catch (Exception $e) {\n error_log('Error calling OpenAI API: ' . $e->getMessage());\n return null;\n }\n}\n\n// Only declare if not already defined\nif (!function_exists('isActionAccessible')) {\n /**\n * Check if a user has access to a specific action\n * @param mixed $guid The global unique identifier\n * @param PDO $connection2 The database connection\n * @param string $address The action address to check\n * @return bool Whether the user has access\n */\n function isActionAccessible($guid, $connection2, $address) {\n try {\n $data = array('gibbonRoleID' => $_SESSION[$guid]['gibbonRoleIDCurrent'], 'address' => $address);\n $sql = \"SELECT DISTINCT gibbonAction.gibbonActionID \n FROM gibbonAction \n JOIN gibbonPermission ON (gibbonAction.gibbonActionID=gibbonPermission.gibbonActionID) \n WHERE (gibbonPermission.gibbonRoleID=:gibbonRoleID AND gibbonAction.URLList LIKE CONCAT('%', :address, '%'))\";\n $result = $connection2->prepare($sql);\n $result->execute($data);\n return ($result->rowCount() > 0);\n } catch (PDOException $e) {\n return false;\n }\n }\n}\n\n/**\n * Helper functions for the ChatBot module\n */\n\n/**\n * Check if a user has access to the ChatBot module\n *\n * @param string $guid\n * @param PDO $connection2\n * @return bool\n */\nfunction chatbotCheckAccess($guid, $connection2) {\n return isActionAccessible($guid, $connection2, '/modules/ChatBot/chatbot.php');\n}\n\n/**\n * Get the DeepSeek API key from settings\n *\n * @param SettingGateway $settingGateway\n * @return string|null\n */\nfunction chatbotGetApiKey($settingGateway) {\n return $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n}\n\n/**\n * Format chat messages for display\n *\n * @param array $messages\n * @return string\n */\nfunction chatbotFormatMessages($messages) {\n $output = '';\n foreach ($messages as $message) {\n $type = $message['type'];\n $content = $message['content'];\n $output .= \"<div class='message {$type}-message'>\";\n $output .= \"<div class='message-content'>{$content}</div>\";\n $output .= \"</div>\";\n }\n return $output;\n}\n\n/**\n * Validate chat message\n *\n * @param string $message\n * @return bool\n */\nfunction chatbotValidateMessage($message) {\n return !empty(trim($message));\n}\n\n/**\n * Sanitize chat message\n *\n * @param string $message\n * @return string\n */\nfunction chatbotSanitizeMessage($message) {\n return strip_tags(trim($message));\n}\n\n/**\n * Store AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @param int $gibbonCourseClassID The course class ID\n * @param int $assessmentID The assessment ID\n * @param string $recommendation The AI recommendation\n * @return bool Whether the operation was successful\n */\nfunction storeRecommendation($connection2, $gibbonPersonID, $gibbonCourseClassID, $assessmentID, $recommendation) {\n try {\n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $gibbonCourseClassID,\n 'assessmentID' => $assessmentID,\n 'recommendation' => $recommendation\n ]);\n } catch (PDOException $e) {\n error_log('Error storing recommendation: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get AI recommendations for a student\n * @param PDO $connection2 The database connection\n * @param int $gibbonPersonID The student ID\n * @return array The recommendations\n */\nfunction getStudentRecommendations($connection2, $gibbonPersonID) {\n try {\n $sql = \"SELECT r.*, \n ia.name as assessment_name,\n c.name as course_name\n FROM gibbonChatBotRecommendations r\n JOIN gibbonInternalAssessment ia ON (ia.gibbonInternalAssessmentID = r.assessmentID)\n JOIN gibbonCourseClass cc ON (cc.gibbonCourseClassID = r.gibbonCourseClassID)\n JOIN gibbonCourse c ON (c.gibbonCourseID = cc.gibbonCourseID)\n WHERE r.gibbonPersonID = :gibbonPersonID\n ORDER BY r.timestamp DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute(['gibbonPersonID' => $gibbonPersonID]);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting student recommendations: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Add training data to the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $question The question\n * @param string $answer The answer\n * @param int $createdBy The user ID who created the entry\n * @param string|null $category The category (optional)\n * @return bool Whether the operation was successful\n */\nfunction addTrainingData($connection2, $question, $answer, $createdBy, $category = null) {\n try {\n $sql = \"INSERT INTO gibbonChatBotTraining \n (question, answer, category, created_by) \n VALUES \n (:question, :answer, :category, :created_by)\";\n \n $result = $connection2->prepare($sql);\n return $result->execute([\n 'question' => $question,\n 'answer' => $answer,\n 'category' => $category,\n 'created_by' => $createdBy\n ]);\n } catch (PDOException $e) {\n error_log('Error adding training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Get training data for the ChatBot\n * @param PDO $connection2 The database connection\n * @param string $category Optional category filter\n * @return array The training data\n */\nfunction getTrainingData($connection2, $category = null) {\n try {\n $sql = \"SELECT * FROM gibbonChatBotTraining\";\n $params = [];\n \n if ($category) {\n $sql .= \" WHERE category = :category\";\n $params['category'] = $category;\n }\n \n $sql .= \" ORDER BY timestampCreated DESC\";\n \n $result = $connection2->prepare($sql);\n $result->execute($params);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (PDOException $e) {\n error_log('Error getting training data: ' . $e->getMessage());\n return [];\n }\n}\n\n/**\n * Delete training data\n * @param PDO $connection2 The database connection\n * @param int $id The training data ID\n * @return bool Whether the operation was successful\n */\nfunction deleteTrainingData($connection2, $id) {\n try {\n $sql = \"DELETE FROM gibbonChatBotTraining WHERE gibbonChatBotTrainingID = :id\";\n $result = $connection2->prepare($sql);\n return $result->execute(['id' => $id]);\n } catch (PDOException $e) {\n error_log('Error deleting training data: ' . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate AI recommendations based on assessment data\n * @param array $assessment The assessment data\n * @param string $apiKey The DeepSeek API key\n * @return string|null The AI recommendation\n */\nfunction generateAssessmentRecommendation($assessment, $apiKey) {\n try {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n $data = [\n 'model' => 'deepseek-chat',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (Exception $e) {\n error_log('Error generating assessment recommendation: ' . $e->getMessage());\n }\n \n return null;\n}\n\n/**\n * Get student performance metrics\n * @param int $gibbonPersonID\n * @return string HTML output of performance metrics\n */\nfunction getStudentPerformanceMetrics($gibbonPersonID) {\n global $pdo;\n \n try {\n // Get assessment data\n $data = getStudentAssessmentData($gibbonPersonID);\n \n // Calculate metrics\n $metrics = [\n 'average_attainment' => calculateAverageAttainment($data),\n 'progress_indicators' => analyzeProgressTrends($data),\n 'subject_breakdown' => getSubjectPerformance($data)\n ];\n \n // Generate HTML output\n $output = '<div class=\"metrics-grid\">';\n foreach ($metrics as $key => $value) {\n $output .= generateMetricCard($key, $value);\n }\n $output .= '</div>';\n \n return $output;\n } catch (Exception $e) {\n return \"Error generating performance metrics: \" . $e->getMessage();\n }\n}\n\n/**\n * Generate AI recommendations based on student data\n * @param int $gibbonPersonID\n * @return string HTML output of recommendations\n */\nfunction generateAIRecommendations($gibbonPersonID) {\n try {\n // Get comprehensive student data\n $assessmentData = getStudentAssessmentData($gibbonPersonID);\n $feedbackData = getTeacherFeedback($gibbonPersonID);\n $engagementData = getStudentEngagementData($gibbonPersonID);\n \n // Prepare data for AI analysis\n $analysisData = [\n 'assessment_history' => $assessmentData,\n 'teacher_feedback' => $feedbackData,\n 'engagement_metrics' => $engagementData,\n 'learning_patterns' => analyzeLearningPatterns($gibbonPersonID)\n ];\n \n // Get AI recommendations\n $recommendations = callDeepSeekAPI($analysisData);\n \n // Format recommendations\n return formatAIRecommendations($recommendations);\n } catch (Exception $e) {\n return \"Error generating AI recommendations: \" . $e->getMessage();\n }\n}\n\n/**\n * Process and store training data\n * @param array $data Training data array\n * @return bool Success status\n */\nfunction processTrainingData($data) {\n try {\n // Validate training data\n validateTrainingData($data);\n \n // Enrich data with institutional context\n $enrichedData = enrichTrainingData($data);\n \n // Store in database\n storeTrainingData($enrichedData);\n \n // Update AI model\n updateAIModel($enrichedData);\n \n return true;\n } catch (Exception $e) {\n error_log(\"Training data processing error: \" . $e->getMessage());\n return false;\n }\n}\n\n/**\n * Generate learning analytics\n * @param array $params Analytics parameters\n * @return array Analytics data\n */\nfunction generateLearningAnalytics($params) {\n try {\n // Gather data\n $performanceData = getPerformanceData($params);\n $engagementData = getEngagementData($params);\n $patternData = getPatternData($params);\n \n // Process analytics\n $analytics = [\n 'performance' => analyzePerformance($performanceData),\n 'engagement' => analyzeEngagement($engagementData),\n 'patterns' => analyzePatterns($patternData),\n 'recommendations' => generateRecommendations([\n 'performance' => $performanceData,\n 'engagement' => $engagementData,\n 'patterns' => $patternData\n ])\n ];\n \n return $analytics;\n } catch (Exception $e) {\n error_log(\"Analytics generation error: \" . $e->getMessage());\n return null;\n }\n}\n\n/**\n * Format AI recommendations for display\n * @param array $recommendations\n * @return string Formatted HTML\n */\nfunction formatAIRecommendations($recommendations) {\n $output = '<div class=\"recommendations-container\">';\n \n // Format subject-specific recommendations\n if (isset($recommendations['subjects'])) {\n $output .= '<div class=\"subject-recommendations\">';\n $output .= '<h4>Subject-Specific Recommendations</h4>';\n foreach ($recommendations['subjects'] as $subject => $data) {\n $output .= formatSubjectRecommendation($subject, $data);\n }\n $output .= '</div>';\n }\n \n // Format learning strategy recommendations\n if (isset($recommendations['strategies'])) {\n $output .= '<div class=\"strategy-recommendations\">';\n $output .= '<h4>Learning Strategy Recommendations</h4>';\n foreach ($recommendations['strategies'] as $strategy) {\n $output .= formatStrategyRecommendation($strategy);\n }\n $output .= '</div>';\n }\n \n // Format improvement suggestions\n if (isset($recommendations['improvements'])) {\n $output .= '<div class=\"improvement-suggestions\">';\n $output .= '<h4>Areas for Improvement</h4>';\n foreach ($recommendations['improvements'] as $area => $suggestions) {\n $output .= formatImprovementSuggestions($area, $suggestions);\n }\n $output .= '</div>';\n }\n \n $output .= '</div>';\n return $output;\n}\n\n"}}, "modules/aiTeacher/student_ai_tutor.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745766.6398969, "n": 9574, "h": "d1156eb6fff95766", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Get user photo\n $sqlPerson = \"SELECT image_240 FROM gibbonPerson WHERE gibbonPersonID = :personID\";\n $resultPerson = $pdo->select($sqlPerson, ['personID' => $gibbonPersonID]);\n $person = $resultPerson->fetch();\n $userPhoto = $person['image_240'] ?? '';\n $absoluteURL = $gibbon->session->get('absoluteURL');\n\n // Check if resuming an existing session\n $sessionID = $_GET['sessionID'] ?? null;\n\n // If sessionID provided, verify it belongs to this user\n if ($sessionID) {\n $sql = \"SELECT sessionID FROM aiTeacherChatSessions\n WHERE sessionID = :sessionID AND gibbonPersonID = :personID\";\n $result = $pdo->executeQuery(['sessionID' => $sessionID, 'personID' => $gibbonPersonID], $sql);\n\n if ($result->rowCount() === 0) {\n // Invalid session, create new one\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n } else {\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Add MathJax for mathematical expressions\n echo '<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\" async></script>';\n echo '<style>\n .ai-numbered-list, .ai-bullet-list { margin: 10px 0; padding-left: 25px; }\n .ai-numbered-list li, .ai-bullet-list li { margin: 5px 0; }\n .math-block { display: block; margin: 15px 0; text-align: center; }\n .math-inline { display: inline; }\n .message-bubble p { margin: 8px 0; }\n .message-bubble p:first-child { margin-top: 0; }\n .message-bubble p:last-child { margin-bottom: 0; }\n </style>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n\n // Generate avatar HTML\n if ($isStudent) {\n // Use student's photo\n if (!empty($userPhoto)) {\n $photoPath = $absoluteURL . '/' . $userPhoto;\n $avatarHTML = '<img src=\"' . $photoPath . '\" style=\"width: 36px; height: 36px; border-radius: 50%; object-fit: cover;\">';\n } else {\n // Default placeholder if no photo\n $avatarHTML = '<img src=\"' . $absoluteURL . '/themes/Default/img/anonymous_240.jpg\" style=\"width: 36px; height: 36px; border-radius: 50%; object-fit: cover;\">';\n }\n } else {\n // AI avatar - use robot emoji\n $avatarHTML = '🤖';\n }\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatarHTML . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n // Use markdown and math rendering for AI messages, simple formatting for student messages\n if ($isStudent) {\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n } else {\n echo renderMarkdownAndMath($msg['message']);\n }\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatarHTML . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $absoluteURL . '\">';\n echo '<input type=\"hidden\" id=\"userPhoto\" value=\"' . htmlspecialchars($userPhoto) . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "efc3b33048fa694a8872e5c9517aaa2fbf59f204": {"t": 1785756299.189468, "n": 9574, "h": "d1156eb6fff95766", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Get user photo\n $sqlPerson = \"SELECT image_240 FROM gibbonPerson WHERE gibbonPersonID = :personID\";\n $resultPerson = $pdo->select($sqlPerson, ['personID' => $gibbonPersonID]);\n $person = $resultPerson->fetch();\n $userPhoto = $person['image_240'] ?? '';\n $absoluteURL = $gibbon->session->get('absoluteURL');\n\n // Check if resuming an existing session\n $sessionID = $_GET['sessionID'] ?? null;\n\n // If sessionID provided, verify it belongs to this user\n if ($sessionID) {\n $sql = \"SELECT sessionID FROM aiTeacherChatSessions\n WHERE sessionID = :sessionID AND gibbonPersonID = :personID\";\n $result = $pdo->executeQuery(['sessionID' => $sessionID, 'personID' => $gibbonPersonID], $sql);\n\n if ($result->rowCount() === 0) {\n // Invalid session, create new one\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n } else {\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Add MathJax for mathematical expressions\n echo '<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\" async></script>';\n echo '<style>\n .ai-numbered-list, .ai-bullet-list { margin: 10px 0; padding-left: 25px; }\n .ai-numbered-list li, .ai-bullet-list li { margin: 5px 0; }\n .math-block { display: block; margin: 15px 0; text-align: center; }\n .math-inline { display: inline; }\n .message-bubble p { margin: 8px 0; }\n .message-bubble p:first-child { margin-top: 0; }\n .message-bubble p:last-child { margin-bottom: 0; }\n </style>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n\n // Generate avatar HTML\n if ($isStudent) {\n // Use student's photo\n if (!empty($userPhoto)) {\n $photoPath = $absoluteURL . '/' . $userPhoto;\n $avatarHTML = '<img src=\"' . $photoPath . '\" style=\"width: 36px; height: 36px; border-radius: 50%; object-fit: cover;\">';\n } else {\n // Default placeholder if no photo\n $avatarHTML = '<img src=\"' . $absoluteURL . '/themes/Default/img/anonymous_240.jpg\" style=\"width: 36px; height: 36px; border-radius: 50%; object-fit: cover;\">';\n }\n } else {\n // AI avatar - use robot emoji\n $avatarHTML = '🤖';\n }\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatarHTML . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n // Use markdown and math rendering for AI messages, simple formatting for student messages\n if ($isStudent) {\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n } else {\n echo renderMarkdownAndMath($msg['message']);\n }\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatarHTML . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $absoluteURL . '\">';\n echo '<input type=\"hidden\" id=\"userPhoto\" value=\"' . htmlspecialchars($userPhoto) . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "0b84281cff4dc2315def5fff6a77a093eaca5f52": {"t": 1785756300.6338928, "n": 8460, "h": "37090cb655bf8dca", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Check if resuming an existing session\n $sessionID = $_GET['sessionID'] ?? null;\n\n // If sessionID provided, verify it belongs to this user\n if ($sessionID) {\n $sql = \"SELECT sessionID FROM aiTeacherChatSessions\n WHERE sessionID = :sessionID AND gibbonPersonID = :personID\";\n $result = $pdo->executeQuery(['sessionID' => $sessionID, 'personID' => $gibbonPersonID], $sql);\n\n if ($result->rowCount() === 0) {\n // Invalid session, create new one\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n } else {\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Add MathJax for mathematical expressions\n echo '<script src=\"https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js\" async></script>';\n echo '<style>\n .ai-numbered-list, .ai-bullet-list { margin: 10px 0; padding-left: 25px; }\n .ai-numbered-list li, .ai-bullet-list li { margin: 5px 0; }\n .math-block { display: block; margin: 15px 0; text-align: center; }\n .math-inline { display: inline; }\n .message-bubble p { margin: 8px 0; }\n .message-bubble p:first-child { margin-top: 0; }\n .message-bubble p:last-child { margin-bottom: 0; }\n </style>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n $avatar = $isStudent ? '👤' : '🤖';\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n // Use markdown and math rendering for AI messages, simple formatting for student messages\n if ($isStudent) {\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n } else {\n echo renderMarkdownAndMath($msg['message']);\n }\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $gibbon->session->get('absoluteURL') . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "c10673e9ae9c5284dc569b8c256897f2c6cf1c24": {"t": 1785756301.8735542, "n": 7613, "h": "1311fef68474de45", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Check if resuming an existing session\n $sessionID = $_GET['sessionID'] ?? null;\n\n // If sessionID provided, verify it belongs to this user\n if ($sessionID) {\n $sql = \"SELECT sessionID FROM aiTeacherChatSessions\n WHERE sessionID = :sessionID AND gibbonPersonID = :personID\";\n $result = $pdo->executeQuery(['sessionID' => $sessionID, 'personID' => $gibbonPersonID], $sql);\n\n if ($result->rowCount() === 0) {\n // Invalid session, create new one\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n } else {\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n }\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n $avatar = $isStudent ? '👤' : '🤖';\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $gibbon->session->get('absoluteURL') . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "6e2315c09113c7dbb2d5f8416cf1903ce8878ec1": {"t": 1785756306.326054, "n": 6978, "h": "ca8cc39c9ce43bf7", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n $avatar = $isStudent ? '👤' : '🤖';\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $gibbon->session->get('absoluteURL') . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "c8048cb80b9e0ad166f7b5f116d905850d8813e8": {"t": 1785756308.0685246, "n": 7002, "h": "d7417093c5712175", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Get or create chat session\n $sessionID = getOrCreateChatSession($connection2, $gibbonPersonID, $gibbonSchoolYearID);\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($connection2);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Get conversation history for current session\n $context = getConversationContext($connection2, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n $avatar = $isStudent ? '👤' : '🤖';\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $gibbon->session->get('absoluteURL') . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}, "0d2085f58265d908285062976c1dc44f6a37f817": {"t": 1785756309.3540063, "n": 6978, "h": "ca8cc39c9ce43bf7", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Tutor Chat'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/student_ai_tutor.php') == false) {\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get current user\n $gibbonPersonID = $gibbon->session->get('gibbonPersonID');\n $gibbonSchoolYearID = $gibbon->session->get('gibbonSchoolYearID');\n\n // Get or create chat session\n $sessionID = getOrCreateChatSession($pdo, $gibbonPersonID, $gibbonSchoolYearID);\n\n // Get AI settings to check if configured\n $settings = getAITeacherSettings($pdo);\n\n if (empty($settings['deepseek_api_key'])) {\n echo '<div class=\"error\">';\n echo __('AI Tutor is not configured. Please contact your administrator.');\n echo '</div>';\n return;\n }\n\n // Add CSS and JavaScript\n echo '<link rel=\"stylesheet\" type=\"text/css\" href=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/css/student_tutor.css\">';\n echo '<script src=\"' . $gibbon->session->get('absoluteURL') . '/modules/aiTeacher/js/student_tutor.js\"></script>';\n\n // Get conversation history for current session\n $context = getConversationContext($pdo, $sessionID, 50);\n\n echo '<div class=\"ai-tutor-container\">';\n\n // Header\n echo '<div class=\"ai-tutor-header\">';\n echo '<h2>' . __('AI Tutor') . '</h2>';\n echo '<p class=\"text-xs opacity-75\">' . __('Ask me anything about your coursework. I\\'m here to help you learn!') . '</p>';\n echo '<div class=\"ai-tutor-actions\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/student_chat_history.php\" class=\"button-link\">' . __('View History') . '</a>';\n echo '<button onclick=\"clearChat()\" class=\"button-link secondary\">' . __('New Chat') . '</button>';\n echo '</div>';\n echo '</div>';\n\n // Chat messages area\n echo '<div class=\"ai-tutor-messages\" id=\"chatMessages\">';\n\n if (empty($context)) {\n // Welcome message\n echo '<div class=\"ai-message\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"message-bubble\">';\n echo '<p>' . __('Hi! I\\'m your AI tutor. I can help you understand concepts, guide you through problems, and support your learning journey.') . '</p>';\n echo '<p class=\"text-sm mt-2\">' . __('What would you like to learn about today?') . '</p>';\n echo '</div>';\n echo '</div>';\n } else {\n // Load previous messages\n foreach ($context as $msg) {\n $isStudent = ($msg['sender'] === 'student');\n $messageClass = $isStudent ? 'student-message' : 'ai-message';\n $avatar = $isStudent ? '👤' : '🤖';\n\n echo '<div class=\"' . $messageClass . '\">';\n if (!$isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '<div class=\"message-bubble\">';\n echo '<p>' . nl2br(htmlspecialchars($msg['message'])) . '</p>';\n echo '<span class=\"message-time\">' . date('g:i A', strtotime($msg['timestamp'])) . '</span>';\n echo '</div>';\n if ($isStudent) {\n echo '<div class=\"message-avatar\">' . $avatar . '</div>';\n }\n echo '</div>';\n }\n }\n\n echo '</div>';\n\n // Typing indicator (hidden by default)\n echo '<div class=\"ai-typing-indicator\" id=\"typingIndicator\" style=\"display: none;\">';\n echo '<div class=\"message-avatar\">🤖</div>';\n echo '<div class=\"typing-dots\">';\n echo '<span></span><span></span><span></span>';\n echo '</div>';\n echo '</div>';\n\n // Input area\n echo '<div class=\"ai-tutor-input\">';\n echo '<form id=\"chatForm\" onsubmit=\"return sendMessage(event);\">';\n echo '<input type=\"hidden\" id=\"sessionID\" value=\"' . htmlspecialchars($sessionID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonPersonID\" value=\"' . htmlspecialchars($gibbonPersonID) . '\">';\n echo '<input type=\"hidden\" id=\"gibbonSchoolYearID\" value=\"' . htmlspecialchars($gibbonSchoolYearID) . '\">';\n echo '<input type=\"hidden\" id=\"absoluteURL\" value=\"' . $gibbon->session->get('absoluteURL') . '\">';\n\n echo '<div class=\"input-wrapper\">';\n echo '<textarea id=\"messageInput\"\n name=\"message\"\n placeholder=\"' . __('Ask your question here...') . '\"\n rows=\"1\"\n maxlength=\"500\"\n onkeydown=\"handleKeyPress(event)\"\n oninput=\"autoResize(this)\"></textarea>';\n echo '<button type=\"submit\" id=\"sendButton\" class=\"send-button\">';\n echo '<svg width=\"24\" height=\"24\" viewBox=\"0 0 24 24\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\">';\n echo '<path d=\"M22 2L11 13M22 2l-7 20-4-9-9-4 20-7z\"/>';\n echo '</svg>';\n echo '</button>';\n echo '</div>';\n\n echo '<div class=\"input-footer\">';\n echo '<span class=\"char-count\"><span id=\"charCount\">0</span>/500</span>';\n echo '<span class=\"help-text\">' . __('Press Enter to send, Shift+Enter for new line') . '</span>';\n echo '</div>';\n\n echo '</form>';\n echo '</div>';\n\n // Rating and feedback area (shown after AI response)\n echo '<div class=\"message-feedback\" id=\"messageFeedback\" style=\"display: none;\">';\n echo '<p class=\"text-sm\">' . __('Was this response helpful?') . '</p>';\n echo '<div class=\"feedback-buttons\">';\n echo '<button onclick=\"rateResponse(\\'helpful\\')\" class=\"feedback-btn helpful\">👍 ' . __('Helpful') . '</button>';\n echo '<button onclick=\"rateResponse(\\'not_helpful\\')\" class=\"feedback-btn not-helpful\">👎 ' . __('Not Helpful') . '</button>';\n echo '<button onclick=\"flagMessage()\" class=\"feedback-btn flag\">🚩 ' . __('Flag for Teacher') . '</button>';\n echo '</div>';\n echo '</div>';\n\n echo '</div>'; // End container\n\n // Initialize JavaScript\n echo '<script>';\n echo 'document.addEventListener(\"DOMContentLoaded\", function() {';\n echo ' scrollToBottom();';\n echo ' document.getElementById(\"messageInput\").focus();';\n echo '});';\n echo '</script>';\n}\n"}}, "modules/ChatBot/api/fix_settings.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745772.03387, "n": 1663, "h": "6d6945a3eb52c9a1", "c": "<?php\n// Include Gibbon core with absolute path\nrequire_once dirname(__FILE__) . '/../../../gibbon.php';\n\ntry {\n // First check if setting exists\n $sql = \"SELECT * FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection2->query($sql);\n \n if ($result->rowCount() == 0) {\n // Setting doesn't exist, create it\n $sql = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) \n VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek AI service', '')\";\n $connection2->query($sql);\n echo \"Created DeepSeek API key setting<br/>\";\n } else {\n echo \"DeepSeek API key setting exists<br/>\";\n $setting = $result->fetch();\n if (empty($setting['value'])) {\n echo \"Warning: API key is not set<br/>\";\n }\n }\n \n // Show all ChatBot settings\n $sql = \"SELECT * FROM gibbonSetting WHERE scope='ChatBot'\";\n $result = $connection2->query($sql);\n $settings = $result->fetchAll();\n \n echo \"<pre>\";\n echo \"Current ChatBot Settings:\\n\";\n foreach ($settings as $setting) {\n echo \"- {$setting['name']}: \" . (empty($setting['value']) ? 'Not set' : 'Set') . \"\\n\";\n echo \" Description: {$setting['description']}\\n\";\n }\n echo \"</pre>\";\n \n // Add link to settings page\n echo \"<p>To update the API key, <a href='../../settings.php'>go to ChatBot Settings</a></p>\";\n \n} catch (Exception $e) {\n echo \"Error: \" . $e->getMessage() . \"<br/>\";\n echo \"SQL State: \" . $e->getCode() . \"<br/>\";\n error_log(\"ChatBot settings error: \" . $e->getMessage());\n} "}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756301.333605, "n": 1663, "h": "6d6945a3eb52c9a1", "c": "<?php\n// Include Gibbon core with absolute path\nrequire_once dirname(__FILE__) . '/../../../gibbon.php';\n\ntry {\n // First check if setting exists\n $sql = \"SELECT * FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection2->query($sql);\n \n if ($result->rowCount() == 0) {\n // Setting doesn't exist, create it\n $sql = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) \n VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek AI service', '')\";\n $connection2->query($sql);\n echo \"Created DeepSeek API key setting<br/>\";\n } else {\n echo \"DeepSeek API key setting exists<br/>\";\n $setting = $result->fetch();\n if (empty($setting['value'])) {\n echo \"Warning: API key is not set<br/>\";\n }\n }\n \n // Show all ChatBot settings\n $sql = \"SELECT * FROM gibbonSetting WHERE scope='ChatBot'\";\n $result = $connection2->query($sql);\n $settings = $result->fetchAll();\n \n echo \"<pre>\";\n echo \"Current ChatBot Settings:\\n\";\n foreach ($settings as $setting) {\n echo \"- {$setting['name']}: \" . (empty($setting['value']) ? 'Not set' : 'Set') . \"\\n\";\n echo \" Description: {$setting['description']}\\n\";\n }\n echo \"</pre>\";\n \n // Add link to settings page\n echo \"<p>To update the API key, <a href='../../settings.php'>go to ChatBot Settings</a></p>\";\n \n} catch (Exception $e) {\n echo \"Error: \" . $e->getMessage() . \"<br/>\";\n echo \"SQL State: \" . $e->getCode() . \"<br/>\";\n error_log(\"ChatBot settings error: \" . $e->getMessage());\n} "}}, "modules/aiTeacher/index.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745772.5442655, "n": 6285, "h": "e8c8b26454c957d7", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\nuse Gibbon\\Module\\aiTeacher\\DeepSeekAPI;\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/index.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo);\n \n // Check if API key is configured\n if (empty($settings['deepseek_api_key'])) {\n $page->addError(__('DeepSeek API key is not configured. Please contact your administrator.'));\n } else {\n // Display dashboard\n echo '<h2>' . __('AI Teacher Dashboard') . '</h2>';\n \n // Quick actions\n echo '<div class=\"linkTop\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/curriculum_support.php\">' . __('Curriculum Support') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/assessment_analysis.php\">' . __('Assessment Analysis') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/resource_generator.php\">' . __('Resource Generator') . '</a>';\n echo '</div>';\n \n // Recent activity\n echo '<h3>' . __('Recent Activity') . '</h3>';\n \n $sql = \"SELECT l.*, p.preferredName, p.surname \n FROM aiTeacherLogs l \n JOIN gibbonPerson p ON l.gibbonPersonID = p.gibbonPersonID \n ORDER BY l.timestamp DESC \n LIMIT 5\";\n $result = $pdo->executeQuery(array(), $sql);\n \n if ($result->rowCount() > 0) {\n echo '<table class=\"fullWidth colorOddEven\">';\n echo '<tr>';\n echo '<th>' . __('Date') . '</th>';\n echo '<th>' . __('User') . '</th>';\n echo '<th>' . __('Action') . '</th>';\n echo '<th>' . __('Subject') . '</th>';\n echo '</tr>';\n \n while ($row = $result->fetch()) {\n echo '<tr>';\n echo '<td>' . date('Y-m-d H:i', strtotime($row['timestamp'])) . '</td>';\n echo '<td>' . $row['preferredName'] . ' ' . $row['surname'] . '</td>';\n echo '<td>' . __($row['action']) . '</td>';\n echo '<td>' . $row['subject'] . '</td>';\n echo '</tr>';\n }\n \n echo '</table>';\n } else {\n echo '<p>' . __('No recent activity found.') . '</p>';\n }\n \n // Quick tools\n echo '<h3>' . __('Quick Tools') . '</h3>';\n \n echo '<div class=\"linkTop\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/generate_lesson_plan.php\">' . __('Generate Lesson Plan') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/generate_assessment.php\">' . __('Generate Assessment') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/upload_resource.php\">' . __('Upload Resource') . '</a>';\n echo '</div>';\n \n // System status\n echo '<h3>' . __('System Status') . '</h3>';\n \n try {\n $api = new DeepSeekAPI($settings['deepseek_api_key']);\n // Construct the data payload for the send() method\n // $data = [ // Old data structure for send()\n // 'model' => 'deepseek-chat', \n // 'messages' => [\n // ['role' => 'user', 'content' => 'Test connection']\n // ]\n // ];\n // $testResponse = $api->send($data); // Old problematic call\n \n $testContent = $api->generateResponse('Test connection'); // Corrected: Use public method\n \n // Check if the API call was successful and the response structure\n // if (isset($testResponse['success']) && $testResponse['success'] && isset($testResponse['response']['choices'])) { // Old check\n if ($testContent !== null) { // Corrected: generateResponse returns content string or null\n echo '<p class=\"success\">' . __('AI system is operational.') . '</p>';\n } else {\n // $errorMessage = __('AI system is not responding correctly.'); // Old error message\n // if (isset($testResponse['error'])) {\n // $errorMessage .= ' Error: ' . htmlspecialchars($testResponse['error']);\n // } else if (isset($testResponse['response']['error']['message'])) {\n // $errorMessage .= ' API Error: ' . htmlspecialchars($testResponse['response']['error']['message']);\n // }\n // echo '<p class=\"error\">' . $errorMessage . '</p>';\n $errorMessage = __('AI system is not responding correctly. Please check server logs for details.'); // Corrected error message\n echo '<p class=\"error\">' . $errorMessage . '</p>';\n }\n } catch (Exception $e) {\n echo '<p class=\"error\">' . __('AI system connection error: ') . htmlspecialchars($e->getMessage()) . '</p>';\n }\n }\n}"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756294.2456677, "n": 6285, "h": "e8c8b26454c957d7", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\nuse Gibbon\\Module\\aiTeacher\\DeepSeekAPI;\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/index.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo);\n \n // Check if API key is configured\n if (empty($settings['deepseek_api_key'])) {\n $page->addError(__('DeepSeek API key is not configured. Please contact your administrator.'));\n } else {\n // Display dashboard\n echo '<h2>' . __('AI Teacher Dashboard') . '</h2>';\n \n // Quick actions\n echo '<div class=\"linkTop\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/curriculum_support.php\">' . __('Curriculum Support') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/assessment_analysis.php\">' . __('Assessment Analysis') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/resource_generator.php\">' . __('Resource Generator') . '</a>';\n echo '</div>';\n \n // Recent activity\n echo '<h3>' . __('Recent Activity') . '</h3>';\n \n $sql = \"SELECT l.*, p.preferredName, p.surname \n FROM aiTeacherLogs l \n JOIN gibbonPerson p ON l.gibbonPersonID = p.gibbonPersonID \n ORDER BY l.timestamp DESC \n LIMIT 5\";\n $result = $pdo->executeQuery(array(), $sql);\n \n if ($result->rowCount() > 0) {\n echo '<table class=\"fullWidth colorOddEven\">';\n echo '<tr>';\n echo '<th>' . __('Date') . '</th>';\n echo '<th>' . __('User') . '</th>';\n echo '<th>' . __('Action') . '</th>';\n echo '<th>' . __('Subject') . '</th>';\n echo '</tr>';\n \n while ($row = $result->fetch()) {\n echo '<tr>';\n echo '<td>' . date('Y-m-d H:i', strtotime($row['timestamp'])) . '</td>';\n echo '<td>' . $row['preferredName'] . ' ' . $row['surname'] . '</td>';\n echo '<td>' . __($row['action']) . '</td>';\n echo '<td>' . $row['subject'] . '</td>';\n echo '</tr>';\n }\n \n echo '</table>';\n } else {\n echo '<p>' . __('No recent activity found.') . '</p>';\n }\n \n // Quick tools\n echo '<h3>' . __('Quick Tools') . '</h3>';\n \n echo '<div class=\"linkTop\">';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/generate_lesson_plan.php\">' . __('Generate Lesson Plan') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/generate_assessment.php\">' . __('Generate Assessment') . '</a> | ';\n echo '<a href=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/upload_resource.php\">' . __('Upload Resource') . '</a>';\n echo '</div>';\n \n // System status\n echo '<h3>' . __('System Status') . '</h3>';\n \n try {\n $api = new DeepSeekAPI($settings['deepseek_api_key']);\n // Construct the data payload for the send() method\n // $data = [ // Old data structure for send()\n // 'model' => 'deepseek-chat', \n // 'messages' => [\n // ['role' => 'user', 'content' => 'Test connection']\n // ]\n // ];\n // $testResponse = $api->send($data); // Old problematic call\n \n $testContent = $api->generateResponse('Test connection'); // Corrected: Use public method\n \n // Check if the API call was successful and the response structure\n // if (isset($testResponse['success']) && $testResponse['success'] && isset($testResponse['response']['choices'])) { // Old check\n if ($testContent !== null) { // Corrected: generateResponse returns content string or null\n echo '<p class=\"success\">' . __('AI system is operational.') . '</p>';\n } else {\n // $errorMessage = __('AI system is not responding correctly.'); // Old error message\n // if (isset($testResponse['error'])) {\n // $errorMessage .= ' Error: ' . htmlspecialchars($testResponse['error']);\n // } else if (isset($testResponse['response']['error']['message'])) {\n // $errorMessage .= ' API Error: ' . htmlspecialchars($testResponse['response']['error']['message']);\n // }\n // echo '<p class=\"error\">' . $errorMessage . '</p>';\n $errorMessage = __('AI system is not responding correctly. Please check server logs for details.'); // Corrected error message\n echo '<p class=\"error\">' . $errorMessage . '</p>';\n }\n } catch (Exception $e) {\n echo '<p class=\"error\">' . __('AI system connection error: ') . htmlspecialchars($e->getMessage()) . '</p>';\n }\n }\n}"}}, "modules/ChatBot/index.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745776.9634418, "n": 1756, "h": "bae2268379ab7726", "c": "<?php\ninclude '../../gibbon.php';\n\nif (! isActionAccessible($guid, $connection2, '/modules/ChatBot/index.php')) {\n // Access denied\n $page->addError($session->get('strAccessDenied'));\n} else {\n // Proceed!\n $page->breadcrumbs->add('AI Teaching Assistant');\n \n // Add module CSS and JS\n $page->stylesheets->add('chatbot', 'modules/ChatBot/css/chatbot.css');\n $page->scripts->add('chatbot', 'modules/ChatBot/js/chatbot.js');\n \n // Get API key from settings\n $apiKey = getSettingByScope($connection2, 'ChatBot', 'deepseek_api_key');\n \n ?>\n <div class=\"content-wrapper\">\n <div class=\"chatbot-page\">\n <div class=\"chatbot-header\">\n <h2>AI Teaching Assistant</h2>\n <div class=\"training-mode-toggle\">\n <input type=\"checkbox\" id=\"trainingMode\">\n <label for=\"trainingMode\">Training Mode</label>\n </div>\n </div>\n \n <div class=\"chatbot-container\">\n <div class=\"chat-messages\"></div>\n <div class=\"chat-input\">\n <textarea placeholder=\"Type your message here...\" rows=\"1\"></textarea>\n <button type=\"button\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n \n <script>\n window.addEventListener('load', function() {\n window.chatBot = new ChatBot({\n apiEndpoint: '<?php echo $session->get('absoluteURL'); ?>/modules/ChatBot/src',\n isTrainingMode: false,\n isAdmin: <?php echo $session->get('isAdmin') ? 'true' : 'false'; ?>\n });\n });\n </script>\n <?php\n}\n?> "}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756301.2722747, "n": 1756, "h": "bae2268379ab7726", "c": "<?php\ninclude '../../gibbon.php';\n\nif (! isActionAccessible($guid, $connection2, '/modules/ChatBot/index.php')) {\n // Access denied\n $page->addError($session->get('strAccessDenied'));\n} else {\n // Proceed!\n $page->breadcrumbs->add('AI Teaching Assistant');\n \n // Add module CSS and JS\n $page->stylesheets->add('chatbot', 'modules/ChatBot/css/chatbot.css');\n $page->scripts->add('chatbot', 'modules/ChatBot/js/chatbot.js');\n \n // Get API key from settings\n $apiKey = getSettingByScope($connection2, 'ChatBot', 'deepseek_api_key');\n \n ?>\n <div class=\"content-wrapper\">\n <div class=\"chatbot-page\">\n <div class=\"chatbot-header\">\n <h2>AI Teaching Assistant</h2>\n <div class=\"training-mode-toggle\">\n <input type=\"checkbox\" id=\"trainingMode\">\n <label for=\"trainingMode\">Training Mode</label>\n </div>\n </div>\n \n <div class=\"chatbot-container\">\n <div class=\"chat-messages\"></div>\n <div class=\"chat-input\">\n <textarea placeholder=\"Type your message here...\" rows=\"1\"></textarea>\n <button type=\"button\">\n <i class=\"fas fa-paper-plane\"></i>\n </button>\n </div>\n </div>\n </div>\n </div>\n \n <script>\n window.addEventListener('load', function() {\n window.chatBot = new ChatBot({\n apiEndpoint: '<?php echo $session->get('absoluteURL'); ?>/modules/ChatBot/src',\n isTrainingMode: false,\n isAdmin: <?php echo $session->get('isAdmin') ? 'true' : 'false'; ?>\n });\n });\n </script>\n <?php\n}\n?> "}}, "modules/aiTeacher/settings.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745777.367991, "n": 5520, "h": "9c7ba028147a28a3", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'), 'index.php');\n$page->breadcrumbs->add(__('Settings'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/settings.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo); // This function might also need a slight tweak if it specifically looks for deepseek_api_key by name\n \n // Ensure settings rows exist\n $settingNames = [\n 'openai_api_key' => 'OpenAI API Key for AI integration',\n 'deepseek_api_key' => 'DeepSeek API Key for AI integration',\n 'upload_path' => 'Path for storing uploaded resources',\n 'score_threshold' => 'Threshold for student performance alerts (percentage)'\n ];\n\n foreach ($settingNames as $name => $desc) {\n $sqlCheck = \"SELECT COUNT(*) FROM aiTeacherSettings WHERE scope = 'aiTeacher' AND name = :name\";\n $result = $pdo->executeQuery(['name' => $name], $sqlCheck);\n if ($result->fetchColumn() == 0) {\n $insertSql = \"INSERT INTO aiTeacherSettings (scope, name, value, description) VALUES ('aiTeacher', :name, '', :desc)\";\n $pdo->executeQuery(['name' => $name, 'desc' => $desc], $insertSql);\n }\n }\n\n // Handle form submission\n if (isset($_POST['submit'])) {\n // Validate input\n $openai_api_key = $_POST['openai_api_key'] ?? ''; // Changed\n $upload_path = $_POST['upload_path'] ?? 'uploads/aiTeacher';\n $score_threshold = $_POST['score_threshold'] ?? 60;\n $deepseek_api_key = $_POST['deepseek_api_key'] ?? '';\n \n // Update settings\n $sql = \"UPDATE aiTeacherSettings SET value = :value WHERE scope = 'aiTeacher' AND name = :name\";\n \n $pdo->executeQuery(\n array('value' => $openai_api_key, 'name' => 'openai_api_key'), // Changed\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $upload_path, 'name' => 'upload_path'),\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $score_threshold, 'name' => 'score_threshold'),\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $deepseek_api_key, 'name' => 'deepseek_api_key'),\n $sql\n );\n \n $page->addSuccess(__('Settings updated successfully.'));\n }\n \n // Display form\n echo '<form method=\"post\" action=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/settings.php\">';\n \n echo '<table class=\"fullWidth\">';\n \n // OpenAI API Key // Changed comment\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('OpenAI API Key') . '</b><br/>'; // Changed\n echo __('Enter your OpenAI API key for AI integration.') . '<br/>'; // Changed\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"password\" name=\"openai_api_key\" value=\"' . htmlspecialchars($settings['openai_api_key'] ?? '') . '\" style=\"width: 300px\">'; // Changed name and settings key\n echo '</td>';\n echo '</tr>';\n \n // DeepSeek API Key\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('DeepSeek API Key') . '</b><br/>';\n echo __('Enter your DeepSeek API key for AI integration.') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"password\" name=\"deepseek_api_key\" value=\"' . htmlspecialchars($settings['deepseek_api_key'] ?? '') . '\" style=\"width: 300px\">';\n echo '</td>';\n echo '</tr>';\n \n // Upload Path\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('Upload Path') . '</b><br/>';\n echo __('Path for storing uploaded resources.') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"text\" name=\"upload_path\" value=\"' . htmlspecialchars($settings['upload_path'] ?? 'uploads/aiTeacher') . '\" style=\"width: 300px\">';\n echo '</td>';\n echo '</tr>';\n \n // Score Threshold\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('Score Threshold') . '</b><br/>';\n echo __('Threshold for student performance alerts (percentage).') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"number\" name=\"score_threshold\" value=\"' . htmlspecialchars($settings['score_threshold'] ?? '60') . '\" min=\"0\" max=\"100\" style=\"width: 100px\">';\n echo '</td>';\n echo '</tr>';\n \n echo '</table>';\n \n echo '<div class=\"right\">';\n echo '<input type=\"submit\" name=\"submit\" value=\"' . __('Save') . '\">';\n echo '</div>';\n \n echo '</form>';\n}"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756300.3326864, "n": 5520, "h": "9c7ba028147a28a3", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'), 'index.php');\n$page->breadcrumbs->add(__('Settings'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/settings.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo); // This function might also need a slight tweak if it specifically looks for deepseek_api_key by name\n \n // Ensure settings rows exist\n $settingNames = [\n 'openai_api_key' => 'OpenAI API Key for AI integration',\n 'deepseek_api_key' => 'DeepSeek API Key for AI integration',\n 'upload_path' => 'Path for storing uploaded resources',\n 'score_threshold' => 'Threshold for student performance alerts (percentage)'\n ];\n\n foreach ($settingNames as $name => $desc) {\n $sqlCheck = \"SELECT COUNT(*) FROM aiTeacherSettings WHERE scope = 'aiTeacher' AND name = :name\";\n $result = $pdo->executeQuery(['name' => $name], $sqlCheck);\n if ($result->fetchColumn() == 0) {\n $insertSql = \"INSERT INTO aiTeacherSettings (scope, name, value, description) VALUES ('aiTeacher', :name, '', :desc)\";\n $pdo->executeQuery(['name' => $name, 'desc' => $desc], $insertSql);\n }\n }\n\n // Handle form submission\n if (isset($_POST['submit'])) {\n // Validate input\n $openai_api_key = $_POST['openai_api_key'] ?? ''; // Changed\n $upload_path = $_POST['upload_path'] ?? 'uploads/aiTeacher';\n $score_threshold = $_POST['score_threshold'] ?? 60;\n $deepseek_api_key = $_POST['deepseek_api_key'] ?? '';\n \n // Update settings\n $sql = \"UPDATE aiTeacherSettings SET value = :value WHERE scope = 'aiTeacher' AND name = :name\";\n \n $pdo->executeQuery(\n array('value' => $openai_api_key, 'name' => 'openai_api_key'), // Changed\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $upload_path, 'name' => 'upload_path'),\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $score_threshold, 'name' => 'score_threshold'),\n $sql\n );\n \n $pdo->executeQuery(\n array('value' => $deepseek_api_key, 'name' => 'deepseek_api_key'),\n $sql\n );\n \n $page->addSuccess(__('Settings updated successfully.'));\n }\n \n // Display form\n echo '<form method=\"post\" action=\"' . $gibbon->session->get('absoluteURL') . '/index.php?q=/modules/aiTeacher/settings.php\">';\n \n echo '<table class=\"fullWidth\">';\n \n // OpenAI API Key // Changed comment\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('OpenAI API Key') . '</b><br/>'; // Changed\n echo __('Enter your OpenAI API key for AI integration.') . '<br/>'; // Changed\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"password\" name=\"openai_api_key\" value=\"' . htmlspecialchars($settings['openai_api_key'] ?? '') . '\" style=\"width: 300px\">'; // Changed name and settings key\n echo '</td>';\n echo '</tr>';\n \n // DeepSeek API Key\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('DeepSeek API Key') . '</b><br/>';\n echo __('Enter your DeepSeek API key for AI integration.') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"password\" name=\"deepseek_api_key\" value=\"' . htmlspecialchars($settings['deepseek_api_key'] ?? '') . '\" style=\"width: 300px\">';\n echo '</td>';\n echo '</tr>';\n \n // Upload Path\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('Upload Path') . '</b><br/>';\n echo __('Path for storing uploaded resources.') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"text\" name=\"upload_path\" value=\"' . htmlspecialchars($settings['upload_path'] ?? 'uploads/aiTeacher') . '\" style=\"width: 300px\">';\n echo '</td>';\n echo '</tr>';\n \n // Score Threshold\n echo '<tr>';\n echo '<td>';\n echo '<b>' . __('Score Threshold') . '</b><br/>';\n echo __('Threshold for student performance alerts (percentage).') . '<br/>';\n echo '</td>';\n echo '<td class=\"right\">';\n echo '<input type=\"number\" name=\"score_threshold\" value=\"' . htmlspecialchars($settings['score_threshold'] ?? '60') . '\" min=\"0\" max=\"100\" style=\"width: 100px\">';\n echo '</td>';\n echo '</tr>';\n \n echo '</table>';\n \n echo '<div class=\"right\">';\n echo '<input type=\"submit\" name=\"submit\" value=\"' . __('Save') . '\">';\n echo '</div>';\n \n echo '</form>';\n}"}}, "modules/ChatBot/api/chat.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745780.505213, "n": 8793, "h": "c14adba938f7da19", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Set PHP configuration\nini_set('max_execution_time', 300);\nini_set('memory_limit', '256M');\nset_time_limit(300);\n\nuse Gibbon\\Contracts\\Database\\Connection;\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Domain\\System\\ModuleGateway;\nuse Gibbon\\Module\\ChatBot\\Domain\\ChatGateway;\nuse Gibbon\\Module\\ChatBot\\DeepSeekAPI;\n\n// Include Gibbon core files\nrequire_once '../../../gibbon.php';\nrequire_once __DIR__ . '/../moduleFunctions.php';\nrequire_once __DIR__ . '/../src/Domain/ChatGateway.php';\nrequire_once __DIR__ . '/../src/DeepSeekAPI.php';\n\n// Set headers and enable error logging\nheader('Content-Type: application/json');\nerror_reporting(E_ALL);\nini_set('display_errors', 0);\nini_set('log_errors', 1);\n//ini_set('error_log', '/Applications/MAMP/logs/php_error.log');\n\ntry {\n // Check if user is logged in\n if (!$session->has('username')) {\n error_log(\"ChatBot: User not authenticated\");\n http_response_code(401);\n echo json_encode([\n 'error' => 'Please log in to use the AI Teaching Assistant.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n // Get the DeepSeek API key from settings\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n error_log(\"ChatBot: DeepSeek API key not configured\");\n http_response_code(500);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is not properly configured. Please contact your administrator.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n // Validate the incoming request\n $input = json_decode(file_get_contents('php://input'), true);\n if (json_last_error() !== JSON_ERROR_NONE) {\n error_log(\"ChatBot: Invalid JSON input - \" . json_last_error_msg());\n http_response_code(400);\n echo json_encode([\n 'error' => 'Invalid request format. Please try again.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n if (empty($input['message'])) {\n error_log(\"ChatBot: Empty message received\");\n http_response_code(400);\n echo json_encode([\n 'error' => 'Please enter a message.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n try {\n // Initialize the DeepSeek API\n $api = new DeepSeekAPI($apiKey);\n error_log(\"ChatBot: Processing request for user \" . $session->get('username'));\n\n // Get the response\n $response = $api->getResponse($input['message']);\n \n // Validate response format\n if (empty($response['answer'])) {\n error_log(\"ChatBot: Empty response from API\");\n throw new Exception(\"Invalid response format from AI service\");\n }\n\n // Log success and response content\n $answer = $response['answer'];\n $logContent = is_array($answer) ? json_encode($answer) : $answer;\n error_log(\"ChatBot: Successfully processed request. Response: \" . substr($logContent, 0, 100) . \"...\");\n\n // Return the response\n $jsonResponse = json_encode($response);\n if ($jsonResponse === false) {\n error_log(\"ChatBot: Failed to encode API response to JSON. Error: \" . json_last_error_msg());\n throw new Exception(\"Failed to encode response from AI service.\");\n }\n echo $jsonResponse;\n\n } catch (Exception $e) {\n error_log(\"ChatBot: Error processing request - \" . $e->getMessage());\n \n // Handle specific error cases\n if (strpos($e->getMessage(), 'Authentication Fails') !== false) {\n http_response_code(401);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is not properly configured. Please contact your administrator.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n } else if (strpos($e->getMessage(), 'timeout') !== false) {\n http_response_code(504);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is taking too long to respond. Please try again.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n } else {\n error_log(\"ChatBot Error Stack Trace: \" . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => 'An unexpected error occurred: ' . htmlspecialchars($e->getMessage()),\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n }\n }\n\n} catch (PDOException $e) {\n // Database errors\n error_log('ChatBot Database Error: ' . $e->getMessage());\n error_log('ChatBot Database Error Stack Trace: ' . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => 'A database error occurred. Please try again later.',\n 'details' => $e->getMessage()\n ]);\n \n} catch (Exception $e) {\n // General errors\n error_log('ChatBot Error: ' . $e->getMessage());\n error_log('ChatBot Error Stack Trace: ' . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => $e->getMessage(),\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n}\n\nclass InternalAssessmentTrainer {\n private $pdo;\n \n public function __construct($pdo) {\n $this->pdo = $pdo;\n }\n \n public function getTeacherFeedback() {\n $sql = \"SELECT \n ia.gibbonInternalAssessmentID,\n ia.name as assessment_name,\n iac.comment as teacher_feedback,\n iac.attainmentValue,\n iac.effortValue,\n c.name as course_name,\n c.nameShort as course_code,\n student.preferredName as student_name,\n student.gibbonPersonID\n FROM gibbonInternalAssessmentColumn ia \n JOIN gibbonInternalAssessmentEntry iac ON iac.gibbonInternalAssessmentColumnID=ia.gibbonInternalAssessmentColumnID\n JOIN gibbonCourse c ON c.gibbonCourseID=ia.gibbonCourseID\n JOIN gibbonPerson student ON student.gibbonPersonID=iac.gibbonPersonID\n WHERE iac.comment IS NOT NULL AND iac.comment != ''\n ORDER BY ia.gibbonInternalAssessmentID DESC\";\n \n try {\n $result = $this->pdo->query($sql);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (Exception $e) {\n error_log('Error fetching Internal Assessment feedback: ' . $e->getMessage());\n return [];\n }\n }\n \n public function formatTrainingData($feedbackData) {\n $trainingData = [];\n foreach ($feedbackData as $entry) {\n $trainingData[] = [\n 'context' => [\n 'course' => $entry['course_name'],\n 'assessment' => $entry['assessment_name'],\n 'attainment' => $entry['attainmentValue'],\n 'effort' => $entry['effortValue']\n ],\n 'feedback' => $entry['teacher_feedback'],\n 'metadata' => [\n 'course_code' => $entry['course_code'],\n 'student_id' => $entry['gibbonPersonID']\n ]\n ];\n }\n return $trainingData;\n }\n \n public function trainChatbot($trainingData) {\n // Format data for DeepSeek API\n $systemPrompt = \"You are an educational AI assistant trained on teacher feedback. Your role is to:\n1. Analyze patterns in student performance\n2. Identify common areas for improvement\n3. Suggest specific strategies for enhancement\n4. Provide constructive, encouraging guidance\n5. Reference real examples from teacher feedback\";\n \n $messages = [\n [\n 'role' => 'system',\n 'content' => $systemPrompt\n ],\n [\n 'role' => 'user',\n 'content' => 'Train using this feedback data: ' . json_encode($trainingData)\n ]\n ];\n \n return $messages;\n }\n} "}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756293.4539733, "n": 8793, "h": "c14adba938f7da19", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Set PHP configuration\nini_set('max_execution_time', 300);\nini_set('memory_limit', '256M');\nset_time_limit(300);\n\nuse Gibbon\\Contracts\\Database\\Connection;\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Domain\\System\\ModuleGateway;\nuse Gibbon\\Module\\ChatBot\\Domain\\ChatGateway;\nuse Gibbon\\Module\\ChatBot\\DeepSeekAPI;\n\n// Include Gibbon core files\nrequire_once '../../../gibbon.php';\nrequire_once __DIR__ . '/../moduleFunctions.php';\nrequire_once __DIR__ . '/../src/Domain/ChatGateway.php';\nrequire_once __DIR__ . '/../src/DeepSeekAPI.php';\n\n// Set headers and enable error logging\nheader('Content-Type: application/json');\nerror_reporting(E_ALL);\nini_set('display_errors', 0);\nini_set('log_errors', 1);\n//ini_set('error_log', '/Applications/MAMP/logs/php_error.log');\n\ntry {\n // Check if user is logged in\n if (!$session->has('username')) {\n error_log(\"ChatBot: User not authenticated\");\n http_response_code(401);\n echo json_encode([\n 'error' => 'Please log in to use the AI Teaching Assistant.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n // Get the DeepSeek API key from settings\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n error_log(\"ChatBot: DeepSeek API key not configured\");\n http_response_code(500);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is not properly configured. Please contact your administrator.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n // Validate the incoming request\n $input = json_decode(file_get_contents('php://input'), true);\n if (json_last_error() !== JSON_ERROR_NONE) {\n error_log(\"ChatBot: Invalid JSON input - \" . json_last_error_msg());\n http_response_code(400);\n echo json_encode([\n 'error' => 'Invalid request format. Please try again.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n if (empty($input['message'])) {\n error_log(\"ChatBot: Empty message received\");\n http_response_code(400);\n echo json_encode([\n 'error' => 'Please enter a message.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n exit;\n }\n\n try {\n // Initialize the DeepSeek API\n $api = new DeepSeekAPI($apiKey);\n error_log(\"ChatBot: Processing request for user \" . $session->get('username'));\n\n // Get the response\n $response = $api->getResponse($input['message']);\n \n // Validate response format\n if (empty($response['answer'])) {\n error_log(\"ChatBot: Empty response from API\");\n throw new Exception(\"Invalid response format from AI service\");\n }\n\n // Log success and response content\n $answer = $response['answer'];\n $logContent = is_array($answer) ? json_encode($answer) : $answer;\n error_log(\"ChatBot: Successfully processed request. Response: \" . substr($logContent, 0, 100) . \"...\");\n\n // Return the response\n $jsonResponse = json_encode($response);\n if ($jsonResponse === false) {\n error_log(\"ChatBot: Failed to encode API response to JSON. Error: \" . json_last_error_msg());\n throw new Exception(\"Failed to encode response from AI service.\");\n }\n echo $jsonResponse;\n\n } catch (Exception $e) {\n error_log(\"ChatBot: Error processing request - \" . $e->getMessage());\n \n // Handle specific error cases\n if (strpos($e->getMessage(), 'Authentication Fails') !== false) {\n http_response_code(401);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is not properly configured. Please contact your administrator.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n } else if (strpos($e->getMessage(), 'timeout') !== false) {\n http_response_code(504);\n echo json_encode([\n 'error' => 'The AI Teaching Assistant is taking too long to respond. Please try again.',\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n } else {\n error_log(\"ChatBot Error Stack Trace: \" . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => 'An unexpected error occurred: ' . htmlspecialchars($e->getMessage()),\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n }\n }\n\n} catch (PDOException $e) {\n // Database errors\n error_log('ChatBot Database Error: ' . $e->getMessage());\n error_log('ChatBot Database Error Stack Trace: ' . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => 'A database error occurred. Please try again later.',\n 'details' => $e->getMessage()\n ]);\n \n} catch (Exception $e) {\n // General errors\n error_log('ChatBot Error: ' . $e->getMessage());\n error_log('ChatBot Error Stack Trace: ' . $e->getTraceAsString());\n http_response_code(500);\n echo json_encode([\n 'error' => $e->getMessage(),\n 'timestamp' => date('Y-m-d H:i:s')\n ]);\n}\n\nclass InternalAssessmentTrainer {\n private $pdo;\n \n public function __construct($pdo) {\n $this->pdo = $pdo;\n }\n \n public function getTeacherFeedback() {\n $sql = \"SELECT \n ia.gibbonInternalAssessmentID,\n ia.name as assessment_name,\n iac.comment as teacher_feedback,\n iac.attainmentValue,\n iac.effortValue,\n c.name as course_name,\n c.nameShort as course_code,\n student.preferredName as student_name,\n student.gibbonPersonID\n FROM gibbonInternalAssessmentColumn ia \n JOIN gibbonInternalAssessmentEntry iac ON iac.gibbonInternalAssessmentColumnID=ia.gibbonInternalAssessmentColumnID\n JOIN gibbonCourse c ON c.gibbonCourseID=ia.gibbonCourseID\n JOIN gibbonPerson student ON student.gibbonPersonID=iac.gibbonPersonID\n WHERE iac.comment IS NOT NULL AND iac.comment != ''\n ORDER BY ia.gibbonInternalAssessmentID DESC\";\n \n try {\n $result = $this->pdo->query($sql);\n return $result->fetchAll(PDO::FETCH_ASSOC);\n } catch (Exception $e) {\n error_log('Error fetching Internal Assessment feedback: ' . $e->getMessage());\n return [];\n }\n }\n \n public function formatTrainingData($feedbackData) {\n $trainingData = [];\n foreach ($feedbackData as $entry) {\n $trainingData[] = [\n 'context' => [\n 'course' => $entry['course_name'],\n 'assessment' => $entry['assessment_name'],\n 'attainment' => $entry['attainmentValue'],\n 'effort' => $entry['effortValue']\n ],\n 'feedback' => $entry['teacher_feedback'],\n 'metadata' => [\n 'course_code' => $entry['course_code'],\n 'student_id' => $entry['gibbonPersonID']\n ]\n ];\n }\n return $trainingData;\n }\n \n public function trainChatbot($trainingData) {\n // Format data for DeepSeek API\n $systemPrompt = \"You are an educational AI assistant trained on teacher feedback. Your role is to:\n1. Analyze patterns in student performance\n2. Identify common areas for improvement\n3. Suggest specific strategies for enhancement\n4. Provide constructive, encouraging guidance\n5. Reference real examples from teacher feedback\";\n \n $messages = [\n [\n 'role' => 'system',\n 'content' => $systemPrompt\n ],\n [\n 'role' => 'user',\n 'content' => 'Train using this feedback data: ' . json_encode($trainingData)\n ]\n ];\n \n return $messages;\n }\n} "}}, "modules/ChatBot/api_test.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745781.4736953, "n": 10505, "h": "599d29ab3871d6ad", "c": "<?php\n// Enable error reporting\nerror_reporting(E_ALL);\nini_set('display_errors', 1);\n\n// Include Gibbon core files\nrequire_once __DIR__ . '/../../gibbon.php';\n\n// Get database connection from Gibbon\n$connection2 = $container->get('db');\n\necho \"<html><head><title>DeepSeek API Test</title>\";\necho \"<style>\n body { font-family: Arial, sans-serif; margin: 20px; }\n h1, h2 { color: #333; }\n pre { background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto; }\n .success { color: green; font-weight: bold; }\n .error { color: red; font-weight: bold; }\n .info { color: blue; }\n .section { margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }\n</style>\";\necho \"</head><body>\";\necho \"<h1>DeepSeek API Connection Test</h1>\";\n\n// PART 1: Database Connection and API Key Retrieval\necho \"<div class='section'>\";\necho \"<h2>1. Database Connection</h2>\";\n\ntry {\n // Get the API key from the database using Gibbon's connection\n $query = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\");\n $query->execute();\n $result = $query->fetch(PDO::FETCH_ASSOC);\n $apiKey = $result ? $result['value'] : '';\n \n if (empty($apiKey)) {\n echo \"<p class='error'>❌ No API key found in the database.</p>\";\n echo \"<p>Please configure the DeepSeek API key in the ChatBot settings.</p>\";\n } else {\n $maskedKey = substr($apiKey, 0, 4) . str_repeat('*', strlen($apiKey) - 8) . substr($apiKey, -4);\n echo \"<p class='success'>✅ API key found in database: $maskedKey</p>\";\n \n // Get model name\n $modelQuery = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='model_name'\");\n $modelQuery->execute();\n $modelResult = $modelQuery->fetch(PDO::FETCH_ASSOC);\n $model = $modelResult ? $modelResult['value'] : 'deepseek-chat';\n \n // Get max tokens\n $tokensQuery = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='max_tokens'\");\n $tokensQuery->execute();\n $tokensResult = $tokensQuery->fetch(PDO::FETCH_ASSOC);\n $maxTokens = $tokensResult ? $tokensResult['value'] : '2000';\n \n echo \"<p><strong>Model:</strong> $model</p>\";\n echo \"<p><strong>Max Tokens:</strong> $maxTokens</p>\";\n }\n} catch (PDOException $e) {\n echo \"<p class='error'>❌ Database Error: \" . $e->getMessage() . \"</p>\";\n $apiKey = '';\n}\necho \"</div>\";\n\n// PART 2: API Connection Test\nif (!empty($apiKey)) {\n echo \"<div class='section'>\";\n echo \"<h2>2. DeepSeek API Connection Test</h2>\";\n echo \"<p>Testing connection to DeepSeek API...</p>\";\n \n // Create a basic curl request to test the API key\n $ch = curl_init('https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n $data = [\n 'model' => $model,\n 'messages' => [\n ['role' => 'system', 'content' => 'You are a helpful assistant.'],\n ['role' => 'user', 'content' => 'Say hello!']\n ],\n 'max_tokens' => intval($maxTokens)\n ];\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n curl_setopt($ch, CURLOPT_TIMEOUT, 30);\n \n $response = curl_exec($ch);\n $info = curl_getinfo($ch);\n $error = curl_error($ch);\n curl_close($ch);\n \n // Display cURL info\n echo \"<h3>cURL Info:</h3>\";\n echo \"<pre>\";\n echo \"HTTP Status: \" . $info['http_code'] . \"\\n\";\n echo \"Total time: \" . $info['total_time'] . \" seconds\\n\";\n echo \"Connect time: \" . $info['connect_time'] . \" seconds\\n\";\n echo \"Size download: \" . $info['size_download'] . \" bytes\\n\";\n echo \"</pre>\";\n \n if ($error) {\n echo \"<p class='error'>❌ API Connection Error: \" . htmlspecialchars($error) . \"</p>\";\n } elseif ($info['http_code'] >= 400) {\n echo \"<p class='error'>❌ API Error: HTTP Status \" . $info['http_code'] . \"</p>\";\n $responseData = json_decode($response, true);\n if ($responseData && isset($responseData['error'])) {\n echo \"<p class='error'>Error Message: \" . htmlspecialchars($responseData['error']['message']) . \"</p>\";\n if (isset($responseData['error']['type'])) {\n echo \"<p class='error'>Error Type: \" . htmlspecialchars($responseData['error']['type']) . \"</p>\";\n }\n }\n echo \"<p>Raw Response:</p>\";\n echo \"<pre>\" . htmlspecialchars($response) . \"</pre>\";\n } else {\n echo \"<p class='success'>✅ API Connection Successful! HTTP Status \" . $info['http_code'] . \"</p>\";\n $responseData = json_decode($response, true);\n if ($responseData) {\n echo \"<p class='success'>Response received from DeepSeek API!</p>\";\n if (isset($responseData['choices'][0]['message']['content'])) {\n $aiResponse = $responseData['choices'][0]['message']['content'];\n echo \"<p><strong>AI Response:</strong></p>\";\n echo \"<pre>\" . htmlspecialchars($aiResponse) . \"</pre>\";\n }\n if (isset($responseData['usage'])) {\n echo \"<p><strong>Token Usage:</strong></p>\";\n echo \"<pre>\";\n echo \"Prompt tokens: \" . $responseData['usage']['prompt_tokens'] . \"\\n\";\n echo \"Completion tokens: \" . $responseData['usage']['completion_tokens'] . \"\\n\";\n echo \"Total tokens: \" . $responseData['usage']['total_tokens'] . \"\\n\";\n echo \"</pre>\";\n }\n }\n }\n echo \"</div>\";\n \n // PART 3: JavaScript Integration Test\n echo \"<div class='section'>\";\n echo \"<h2>3. JavaScript Integration Test</h2>\";\n echo \"<p>This test will attempt to use the same API to send a message using JavaScript (similar to how the chatbot works).</p>\";\n echo \"<div id='jsTestArea'>\";\n echo \"<textarea id='testMessage' style='width: 100%; height: 100px;' placeholder='Enter a test message...'></textarea><br>\";\n echo \"<button id='testButton' style='padding: 10px; margin-top: 10px;'>Send Test Message</button>\";\n echo \"<div id='jsResult' style='margin-top: 15px;'></div>\";\n echo \"</div>\";\n \n echo \"<script>\n document.getElementById('testButton').addEventListener('click', async function() {\n const message = document.getElementById('testMessage').value;\n if (!message) {\n document.getElementById('jsResult').innerHTML = '<p class=\\\"error\\\">Please enter a message</p>';\n return;\n }\n \n document.getElementById('jsResult').innerHTML = '<p>Sending request to DeepSeek API...</p>';\n \n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer \" . $apiKey . \"'\n },\n body: JSON.stringify({\n model: '\" . $model . \"',\n messages: [\n {role: 'system', content: 'You are a helpful assistant.'},\n {role: 'user', content: message}\n ],\n max_tokens: \" . $maxTokens . \"\n })\n });\n \n const result = await response.json();\n let resultHtml = '<h3>API Response:</h3>';\n \n if (response.ok) {\n resultHtml += '<p class=\\\"success\\\">✅ Success! HTTP Status: ' + response.status + '</p>';\n if (result.choices && result.choices[0] && result.choices[0].message) {\n resultHtml += '<p><strong>AI Response:</strong></p>';\n resultHtml += '<pre>' + result.choices[0].message.content + '</pre>';\n }\n if (result.usage) {\n resultHtml += '<p><strong>Token Usage:</strong></p>';\n resultHtml += '<pre>';\n resultHtml += 'Prompt tokens: ' + result.usage.prompt_tokens + '\\\\n';\n resultHtml += 'Completion tokens: ' + result.usage.completion_tokens + '\\\\n';\n resultHtml += 'Total tokens: ' + result.usage.total_tokens + '\\\\n';\n resultHtml += '</pre>';\n }\n } else {\n resultHtml += '<p class=\\\"error\\\">❌ Error! HTTP Status: ' + response.status + '</p>';\n if (result.error) {\n resultHtml += '<p class=\\\"error\\\">Error Message: ' + result.error.message + '</p>';\n if (result.error.type) {\n resultHtml += '<p class=\\\"error\\\">Error Type: ' + result.error.type + '</p>';\n }\n }\n resultHtml += '<pre>' + JSON.stringify(result, null, 2) + '</pre>';\n }\n \n document.getElementById('jsResult').innerHTML = resultHtml;\n } catch (error) {\n document.getElementById('jsResult').innerHTML = '<p class=\\\"error\\\">❌ JavaScript Error: ' + error.message + '</p>';\n }\n });\n </script>\";\n echo \"</div>\";\n}\n\n// PART 4: Troubleshooting and Recommendations\necho \"<div class='section'>\";\necho \"<h2>4. Troubleshooting</h2>\";\n\necho \"<h3>Common Issues:</h3>\";\necho \"<ul>\";\necho \"<li><strong>Invalid API Key</strong>: Ensure the API key is copied correctly from DeepSeek.</li>\";\necho \"<li><strong>CORS Issues</strong>: API requests from JavaScript may be blocked by CORS policy.</li>\";\necho \"<li><strong>Network Connectivity</strong>: Check if your server can reach api.deepseek.com.</li>\";\necho \"<li><strong>Rate Limiting</strong>: DeepSeek may rate limit your requests.</li>\";\necho \"<li><strong>Model Availability</strong>: Ensure the selected model is available in your DeepSeek plan.</li>\";\necho \"</ul>\";\n\necho \"<h3>Next Steps:</h3>\";\necho \"<ul>\";\necho \"<li>Check the PHP error logs for any additional information.</li>\";\necho \"<li>Try restarting your web server.</li>\";\necho \"<li>Verify your DeepSeek account is active and has sufficient credits.</li>\";\necho \"<li>Check if you need to create a proxy endpoint on your server to avoid CORS issues.</li>\";\necho \"</ul>\";\necho \"</div>\";\n\necho \"</body></html>\";\n?> "}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756300.619957, "n": 10505, "h": "599d29ab3871d6ad", "c": "<?php\n// Enable error reporting\nerror_reporting(E_ALL);\nini_set('display_errors', 1);\n\n// Include Gibbon core files\nrequire_once __DIR__ . '/../../gibbon.php';\n\n// Get database connection from Gibbon\n$connection2 = $container->get('db');\n\necho \"<html><head><title>DeepSeek API Test</title>\";\necho \"<style>\n body { font-family: Arial, sans-serif; margin: 20px; }\n h1, h2 { color: #333; }\n pre { background-color: #f5f5f5; padding: 10px; border-radius: 5px; overflow-x: auto; }\n .success { color: green; font-weight: bold; }\n .error { color: red; font-weight: bold; }\n .info { color: blue; }\n .section { margin-bottom: 30px; padding: 15px; border: 1px solid #ddd; border-radius: 5px; }\n</style>\";\necho \"</head><body>\";\necho \"<h1>DeepSeek API Connection Test</h1>\";\n\n// PART 1: Database Connection and API Key Retrieval\necho \"<div class='section'>\";\necho \"<h2>1. Database Connection</h2>\";\n\ntry {\n // Get the API key from the database using Gibbon's connection\n $query = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\");\n $query->execute();\n $result = $query->fetch(PDO::FETCH_ASSOC);\n $apiKey = $result ? $result['value'] : '';\n \n if (empty($apiKey)) {\n echo \"<p class='error'>❌ No API key found in the database.</p>\";\n echo \"<p>Please configure the DeepSeek API key in the ChatBot settings.</p>\";\n } else {\n $maskedKey = substr($apiKey, 0, 4) . str_repeat('*', strlen($apiKey) - 8) . substr($apiKey, -4);\n echo \"<p class='success'>✅ API key found in database: $maskedKey</p>\";\n \n // Get model name\n $modelQuery = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='model_name'\");\n $modelQuery->execute();\n $modelResult = $modelQuery->fetch(PDO::FETCH_ASSOC);\n $model = $modelResult ? $modelResult['value'] : 'deepseek-chat';\n \n // Get max tokens\n $tokensQuery = $connection2->prepare(\"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='max_tokens'\");\n $tokensQuery->execute();\n $tokensResult = $tokensQuery->fetch(PDO::FETCH_ASSOC);\n $maxTokens = $tokensResult ? $tokensResult['value'] : '2000';\n \n echo \"<p><strong>Model:</strong> $model</p>\";\n echo \"<p><strong>Max Tokens:</strong> $maxTokens</p>\";\n }\n} catch (PDOException $e) {\n echo \"<p class='error'>❌ Database Error: \" . $e->getMessage() . \"</p>\";\n $apiKey = '';\n}\necho \"</div>\";\n\n// PART 2: API Connection Test\nif (!empty($apiKey)) {\n echo \"<div class='section'>\";\n echo \"<h2>2. DeepSeek API Connection Test</h2>\";\n echo \"<p>Testing connection to DeepSeek API...</p>\";\n \n // Create a basic curl request to test the API key\n $ch = curl_init('https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n $data = [\n 'model' => $model,\n 'messages' => [\n ['role' => 'system', 'content' => 'You are a helpful assistant.'],\n ['role' => 'user', 'content' => 'Say hello!']\n ],\n 'max_tokens' => intval($maxTokens)\n ];\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n curl_setopt($ch, CURLOPT_TIMEOUT, 30);\n \n $response = curl_exec($ch);\n $info = curl_getinfo($ch);\n $error = curl_error($ch);\n curl_close($ch);\n \n // Display cURL info\n echo \"<h3>cURL Info:</h3>\";\n echo \"<pre>\";\n echo \"HTTP Status: \" . $info['http_code'] . \"\\n\";\n echo \"Total time: \" . $info['total_time'] . \" seconds\\n\";\n echo \"Connect time: \" . $info['connect_time'] . \" seconds\\n\";\n echo \"Size download: \" . $info['size_download'] . \" bytes\\n\";\n echo \"</pre>\";\n \n if ($error) {\n echo \"<p class='error'>❌ API Connection Error: \" . htmlspecialchars($error) . \"</p>\";\n } elseif ($info['http_code'] >= 400) {\n echo \"<p class='error'>❌ API Error: HTTP Status \" . $info['http_code'] . \"</p>\";\n $responseData = json_decode($response, true);\n if ($responseData && isset($responseData['error'])) {\n echo \"<p class='error'>Error Message: \" . htmlspecialchars($responseData['error']['message']) . \"</p>\";\n if (isset($responseData['error']['type'])) {\n echo \"<p class='error'>Error Type: \" . htmlspecialchars($responseData['error']['type']) . \"</p>\";\n }\n }\n echo \"<p>Raw Response:</p>\";\n echo \"<pre>\" . htmlspecialchars($response) . \"</pre>\";\n } else {\n echo \"<p class='success'>✅ API Connection Successful! HTTP Status \" . $info['http_code'] . \"</p>\";\n $responseData = json_decode($response, true);\n if ($responseData) {\n echo \"<p class='success'>Response received from DeepSeek API!</p>\";\n if (isset($responseData['choices'][0]['message']['content'])) {\n $aiResponse = $responseData['choices'][0]['message']['content'];\n echo \"<p><strong>AI Response:</strong></p>\";\n echo \"<pre>\" . htmlspecialchars($aiResponse) . \"</pre>\";\n }\n if (isset($responseData['usage'])) {\n echo \"<p><strong>Token Usage:</strong></p>\";\n echo \"<pre>\";\n echo \"Prompt tokens: \" . $responseData['usage']['prompt_tokens'] . \"\\n\";\n echo \"Completion tokens: \" . $responseData['usage']['completion_tokens'] . \"\\n\";\n echo \"Total tokens: \" . $responseData['usage']['total_tokens'] . \"\\n\";\n echo \"</pre>\";\n }\n }\n }\n echo \"</div>\";\n \n // PART 3: JavaScript Integration Test\n echo \"<div class='section'>\";\n echo \"<h2>3. JavaScript Integration Test</h2>\";\n echo \"<p>This test will attempt to use the same API to send a message using JavaScript (similar to how the chatbot works).</p>\";\n echo \"<div id='jsTestArea'>\";\n echo \"<textarea id='testMessage' style='width: 100%; height: 100px;' placeholder='Enter a test message...'></textarea><br>\";\n echo \"<button id='testButton' style='padding: 10px; margin-top: 10px;'>Send Test Message</button>\";\n echo \"<div id='jsResult' style='margin-top: 15px;'></div>\";\n echo \"</div>\";\n \n echo \"<script>\n document.getElementById('testButton').addEventListener('click', async function() {\n const message = document.getElementById('testMessage').value;\n if (!message) {\n document.getElementById('jsResult').innerHTML = '<p class=\\\"error\\\">Please enter a message</p>';\n return;\n }\n \n document.getElementById('jsResult').innerHTML = '<p>Sending request to DeepSeek API...</p>';\n \n try {\n const response = await fetch('https://api.deepseek.com/v1/chat/completions', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/json',\n 'Authorization': 'Bearer \" . $apiKey . \"'\n },\n body: JSON.stringify({\n model: '\" . $model . \"',\n messages: [\n {role: 'system', content: 'You are a helpful assistant.'},\n {role: 'user', content: message}\n ],\n max_tokens: \" . $maxTokens . \"\n })\n });\n \n const result = await response.json();\n let resultHtml = '<h3>API Response:</h3>';\n \n if (response.ok) {\n resultHtml += '<p class=\\\"success\\\">✅ Success! HTTP Status: ' + response.status + '</p>';\n if (result.choices && result.choices[0] && result.choices[0].message) {\n resultHtml += '<p><strong>AI Response:</strong></p>';\n resultHtml += '<pre>' + result.choices[0].message.content + '</pre>';\n }\n if (result.usage) {\n resultHtml += '<p><strong>Token Usage:</strong></p>';\n resultHtml += '<pre>';\n resultHtml += 'Prompt tokens: ' + result.usage.prompt_tokens + '\\\\n';\n resultHtml += 'Completion tokens: ' + result.usage.completion_tokens + '\\\\n';\n resultHtml += 'Total tokens: ' + result.usage.total_tokens + '\\\\n';\n resultHtml += '</pre>';\n }\n } else {\n resultHtml += '<p class=\\\"error\\\">❌ Error! HTTP Status: ' + response.status + '</p>';\n if (result.error) {\n resultHtml += '<p class=\\\"error\\\">Error Message: ' + result.error.message + '</p>';\n if (result.error.type) {\n resultHtml += '<p class=\\\"error\\\">Error Type: ' + result.error.type + '</p>';\n }\n }\n resultHtml += '<pre>' + JSON.stringify(result, null, 2) + '</pre>';\n }\n \n document.getElementById('jsResult').innerHTML = resultHtml;\n } catch (error) {\n document.getElementById('jsResult').innerHTML = '<p class=\\\"error\\\">❌ JavaScript Error: ' + error.message + '</p>';\n }\n });\n </script>\";\n echo \"</div>\";\n}\n\n// PART 4: Troubleshooting and Recommendations\necho \"<div class='section'>\";\necho \"<h2>4. Troubleshooting</h2>\";\n\necho \"<h3>Common Issues:</h3>\";\necho \"<ul>\";\necho \"<li><strong>Invalid API Key</strong>: Ensure the API key is copied correctly from DeepSeek.</li>\";\necho \"<li><strong>CORS Issues</strong>: API requests from JavaScript may be blocked by CORS policy.</li>\";\necho \"<li><strong>Network Connectivity</strong>: Check if your server can reach api.deepseek.com.</li>\";\necho \"<li><strong>Rate Limiting</strong>: DeepSeek may rate limit your requests.</li>\";\necho \"<li><strong>Model Availability</strong>: Ensure the selected model is available in your DeepSeek plan.</li>\";\necho \"</ul>\";\n\necho \"<h3>Next Steps:</h3>\";\necho \"<ul>\";\necho \"<li>Check the PHP error logs for any additional information.</li>\";\necho \"<li>Try restarting your web server.</li>\";\necho \"<li>Verify your DeepSeek account is active and has sufficient credits.</li>\";\necho \"<li>Check if you need to create a proxy endpoint on your server to avoid CORS issues.</li>\";\necho \"</ul>\";\necho \"</div>\";\n\necho \"</body></html>\";\n?> "}}, "modules/aiTeacher/assessment_analysis.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745784.1736329, "n": 10762, "h": "8bca3e2420c62479", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Initialize PDO connection\nglobal $pdo;\nif (!isset($pdo)) {\n $pdo = $connection2 ?? new PDO($dsn, $username, $password);\n}\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\nrequire_once __DIR__ . '/src/DeepSeekAPI.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'), 'index.php');\n$page->breadcrumbs->add(__('Assessment Analysis'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/assessment_analysis.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo);\n $aiEnabled = !empty($settings['deepseek_api_key']);\n $threshold = isset($_GET['threshold']) ? intval($_GET['threshold']) : 60;\n $selectedCourse = $_GET['course'] ?? '';\n $selectedAssessmentName = $_GET['assessment_name'] ?? ''; // New: Get selected assessment name\n\n // Get list of courses\n $courses = $pdo->executeQuery([], \"SELECT gibbonCourseID, name FROM gibbonCourse ORDER BY name\");\n\n // New: Get list of distinct assessment names\n $assessmentNamesResult = $pdo->executeQuery([], \"SELECT DISTINCT name FROM gibbonInternalAssessmentColumn WHERE name IS NOT NULL AND name <> '' ORDER BY name ASC\");\n $assessmentNames = $assessmentNamesResult->fetchAll(\\PDO::FETCH_COLUMN);\n\n\n // Add some CSS for better visibility\n echo <<<CSS\n<style>\n .filter-form-container {\n margin-bottom: 20px;\n display: flex;\n align-items: center;\n gap: 15px; /* Adds space between form elements */\n }\n .filter-form-container label {\n display: flex;\n align-items: center;\n gap: 5px; /* Space between label text and input */\n }\n .filter-form-container input[type=\"number\"] {\n width: 80px; /* Increased width for threshold */\n padding: 5px;\n border: 1px solid #ccc;\n border-radius: 4px;\n }\n .filter-form-container .button,\n .filter-form-container input[type=\"submit\"] { /* Target Gibbon's default button class and the submit input */\n padding: 6px 12px;\n background-color: #007bff; /* A visible background color */\n color: white;\n border: 1px solid #007bff;\n border-radius: 4px;\n cursor: pointer;\n text-decoration: none; /* If it's an <a> styled as a button */\n display: inline-block; /* Ensure proper rendering */\n }\n .filter-form-container .button:hover,\n .filter-form-container input[type=\"submit\"]:hover {\n background-color: #0056b3;\n border-color: #0056b3;\n }\n\n /* Style for the \"Get AI Recommendation\" button in the table */\n .get-ai-recommendation {\n padding: 6px 12px;\n background-color: #28a745; /* Green background, or choose another distinct color */\n color: white;\n border: 1px solid #28a745;\n border-radius: 4px;\n cursor: pointer;\n text-decoration: none;\n display: inline-block;\n font-size: 0.9em; /* Match other button font size if desired */\n }\n .get-ai-recommendation:hover {\n background-color: #218838; /* Darker green on hover */\n border-color: #1e7e34;\n }\n</style>\nCSS;\n\n // Filter form\n echo '<form method=\"get\" action=\"' . $gibbon->session->get('absoluteURL') . '/index.php\" class=\"filter-form-container\">'; // Added a class for styling\n echo '<input type=\"hidden\" name=\"q\" value=\"/modules/aiTeacher/assessment_analysis.php\">';\n echo '<label>' . __('Course') . ': ';\n echo '<select name=\"course\">';\n echo '<option value=\"\">' . __('Please select...') . '</option>';\n while ($course = $courses->fetch()) {\n $selected = ($selectedCourse == $course['gibbonCourseID']) ? 'selected' : '';\n echo '<option value=\"' . $course['gibbonCourseID'] . '\" ' . $selected . '>' . htmlspecialchars($course['name']) . '</option>';\n }\n echo '</select></label>'; // Removed trailing space\n\n // New: Assessment Name Filter Dropdown\n echo '<label>' . __('Assessment Name') . ': ';\n echo '<select name=\"assessment_name\">';\n echo '<option value=\"\">' . __('All Assessments') . '</option>';\n foreach ($assessmentNames as $assessmentName) {\n $selected = ($selectedAssessmentName == $assessmentName) ? 'selected' : '';\n echo '<option value=\"' . htmlspecialchars($assessmentName) . '\" ' . $selected . '>' . htmlspecialchars($assessmentName) . '</option>';\n }\n echo '</select></label>';\n\n echo '<label>' . __('Failing Threshold') . ': ';\n // Removed inline style, will be handled by CSS block\n echo '<input type=\"number\" name=\"threshold\" value=\"' . $threshold . '\" min=\"0\" max=\"100\">'; \n echo '</label>'; // Removed trailing space\n echo '<input type=\"submit\" value=\"' . __('Filter') . '\" class=\"button\">';\n echo '</form>';\n\n if ($selectedCourse) {\n // Query students in the selected course with their latest assessment\n $sql = \"\n SELECT\n s.gibbonPersonID,\n s.preferredName,\n s.surname,\n c.gibbonCourseID,\n c.name AS courseName,\n iac.name AS assessmentColumnName, -- Added for clarity, can be used if needed\n iae.attainmentValue\n FROM\n gibbonInternalAssessmentEntry iae\n JOIN gibbonInternalAssessmentColumn iac ON iae.gibbonInternalAssessmentColumnID = iac.gibbonInternalAssessmentColumnID\n JOIN gibbonCourseClass ccc ON iac.gibbonCourseClassID = ccc.gibbonCourseClassID\n JOIN gibbonCourse c ON ccc.gibbonCourseID = c.gibbonCourseID\n JOIN gibbonPerson s ON iae.gibbonPersonIDStudent = s.gibbonPersonID\n WHERE\n c.gibbonCourseID = :courseID\";\n\n $params = ['courseID' => $selectedCourse];\n\n if (!empty($selectedAssessmentName)) {\n $sql .= \" AND iac.name = :assessmentName\";\n $params['assessmentName'] = $selectedAssessmentName;\n }\n\n $sql .= \" ORDER BY s.surname, s.preferredName, iac.name\"; // Added iac.name to ordering for consistency if multiple assessments per student\n \n $result = $pdo->executeQuery($params, $sql);\n $students = $result->fetchAll();\n \n // Filter only failing students\n $failingStudents = array_filter($students, function($student) use ($threshold) {\n return floatval($student['attainmentValue']) < $threshold;\n });\n \n if (count($failingStudents) > 0) {\n echo '<table class=\"fullWidth colorOddEven\">';\n echo '<tr>';\n echo '<th>' . __('Student') . '</th>';\n echo '<th>' . __('Course') . '</th>';\n echo '<th>' . __('Score') . '</th>';\n echo '<th>' . __('AI Recommendation') . '</th>';\n echo '</tr>';\n \n foreach ($failingStudents as $student) {\n $studentId = htmlspecialchars($student['gibbonPersonID']);\n $studentName = htmlspecialchars($student['preferredName'] . ' ' . $student['surname']);\n $courseName = htmlspecialchars($student['courseName']);\n $score = htmlspecialchars($student['attainmentValue']);\n echo '<tr>';\n echo '<td>' . $studentName . '</td>';\n echo '<td>' . $courseName . '</td>';\n echo '<td>' . $score . '</td>';\n echo '<td>';\n echo '<button class=\"get-ai-recommendation\" data-student-id=\"' . $studentId . '\" data-student-name=\"' . $studentName . '\" data-course-name=\"' . $courseName . '\" data-score=\"' . $score . '\">Get AI Recommendation</button>';\n echo '<div class=\"ai-recommendation-result\" id=\"ai-recommendation-' . $studentId . '\"></div>';\n echo '</td>';\n echo '</tr>';\n }\n echo '</table>';\n \n // Define the URL for JavaScript\n $getRecommendationURL = $gibbon->session->get('absoluteURL').'/modules/aiTeacher/get_ai_recommendation.php';\n ?>\n <script>\n const getRecommendationUrl = '<?php echo $getRecommendationURL; ?>';\n document.querySelectorAll('.get-ai-recommendation').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var studentId = this.getAttribute('data-student-id');\n var studentName = this.getAttribute('data-student-name');\n var courseName = this.getAttribute('data-course-name');\n var score = this.getAttribute('data-score');\n var resultDiv = document.getElementById('ai-recommendation-' + studentId);\n resultDiv.innerHTML = 'Loading...';\n fetch(getRecommendationUrl, { // Use the dynamic URL\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n studentId: studentId, // Add studentId here\n studentName: studentName,\n courseName: courseName,\n score: score\n })\n })\n .then(response => response.text())\n .then(data => {\n resultDiv.innerHTML = data;\n })\n .catch(err => {\n resultDiv.innerHTML = 'Error fetching recommendation.';\n });\n });\n });\n </script>\n <?php\n } else {\n echo '<p>' . __('No failing students found for this course.') . '</p>';\n }\n } else {\n echo '<p>' . __('Please select a course to view students.') . '</p>';\n }\n} // Closing brace for the isActionAccessible check"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756301.1779785, "n": 10762, "h": "8bca3e2420c62479", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Gibbon includes\nrequire_once __DIR__ . '/../../gibbon.php';\nrequire_once __DIR__ . '/../../functions.php';\n\n// Initialize PDO connection\nglobal $pdo;\nif (!isset($pdo)) {\n $pdo = $connection2 ?? new PDO($dsn, $username, $password);\n}\n\n// Module includes\nrequire_once __DIR__ . '/moduleFunctions.php';\nrequire_once __DIR__ . '/src/DeepSeekAPI.php';\n\n$page->breadcrumbs->add(__('AI Teacher Assistance'), 'index.php');\n$page->breadcrumbs->add(__('Assessment Analysis'));\n\n// Check if user has access\nif (isActionAccessible($guid, $connection2, '/modules/aiTeacher/assessment_analysis.php') == false) {\n // Access denied\n $page->addError(__('You do not have access to this action.'));\n} else {\n // Get module settings\n $settings = getAITeacherSettings($pdo);\n $aiEnabled = !empty($settings['deepseek_api_key']);\n $threshold = isset($_GET['threshold']) ? intval($_GET['threshold']) : 60;\n $selectedCourse = $_GET['course'] ?? '';\n $selectedAssessmentName = $_GET['assessment_name'] ?? ''; // New: Get selected assessment name\n\n // Get list of courses\n $courses = $pdo->executeQuery([], \"SELECT gibbonCourseID, name FROM gibbonCourse ORDER BY name\");\n\n // New: Get list of distinct assessment names\n $assessmentNamesResult = $pdo->executeQuery([], \"SELECT DISTINCT name FROM gibbonInternalAssessmentColumn WHERE name IS NOT NULL AND name <> '' ORDER BY name ASC\");\n $assessmentNames = $assessmentNamesResult->fetchAll(\\PDO::FETCH_COLUMN);\n\n\n // Add some CSS for better visibility\n echo <<<CSS\n<style>\n .filter-form-container {\n margin-bottom: 20px;\n display: flex;\n align-items: center;\n gap: 15px; /* Adds space between form elements */\n }\n .filter-form-container label {\n display: flex;\n align-items: center;\n gap: 5px; /* Space between label text and input */\n }\n .filter-form-container input[type=\"number\"] {\n width: 80px; /* Increased width for threshold */\n padding: 5px;\n border: 1px solid #ccc;\n border-radius: 4px;\n }\n .filter-form-container .button,\n .filter-form-container input[type=\"submit\"] { /* Target Gibbon's default button class and the submit input */\n padding: 6px 12px;\n background-color: #007bff; /* A visible background color */\n color: white;\n border: 1px solid #007bff;\n border-radius: 4px;\n cursor: pointer;\n text-decoration: none; /* If it's an <a> styled as a button */\n display: inline-block; /* Ensure proper rendering */\n }\n .filter-form-container .button:hover,\n .filter-form-container input[type=\"submit\"]:hover {\n background-color: #0056b3;\n border-color: #0056b3;\n }\n\n /* Style for the \"Get AI Recommendation\" button in the table */\n .get-ai-recommendation {\n padding: 6px 12px;\n background-color: #28a745; /* Green background, or choose another distinct color */\n color: white;\n border: 1px solid #28a745;\n border-radius: 4px;\n cursor: pointer;\n text-decoration: none;\n display: inline-block;\n font-size: 0.9em; /* Match other button font size if desired */\n }\n .get-ai-recommendation:hover {\n background-color: #218838; /* Darker green on hover */\n border-color: #1e7e34;\n }\n</style>\nCSS;\n\n // Filter form\n echo '<form method=\"get\" action=\"' . $gibbon->session->get('absoluteURL') . '/index.php\" class=\"filter-form-container\">'; // Added a class for styling\n echo '<input type=\"hidden\" name=\"q\" value=\"/modules/aiTeacher/assessment_analysis.php\">';\n echo '<label>' . __('Course') . ': ';\n echo '<select name=\"course\">';\n echo '<option value=\"\">' . __('Please select...') . '</option>';\n while ($course = $courses->fetch()) {\n $selected = ($selectedCourse == $course['gibbonCourseID']) ? 'selected' : '';\n echo '<option value=\"' . $course['gibbonCourseID'] . '\" ' . $selected . '>' . htmlspecialchars($course['name']) . '</option>';\n }\n echo '</select></label>'; // Removed trailing space\n\n // New: Assessment Name Filter Dropdown\n echo '<label>' . __('Assessment Name') . ': ';\n echo '<select name=\"assessment_name\">';\n echo '<option value=\"\">' . __('All Assessments') . '</option>';\n foreach ($assessmentNames as $assessmentName) {\n $selected = ($selectedAssessmentName == $assessmentName) ? 'selected' : '';\n echo '<option value=\"' . htmlspecialchars($assessmentName) . '\" ' . $selected . '>' . htmlspecialchars($assessmentName) . '</option>';\n }\n echo '</select></label>';\n\n echo '<label>' . __('Failing Threshold') . ': ';\n // Removed inline style, will be handled by CSS block\n echo '<input type=\"number\" name=\"threshold\" value=\"' . $threshold . '\" min=\"0\" max=\"100\">'; \n echo '</label>'; // Removed trailing space\n echo '<input type=\"submit\" value=\"' . __('Filter') . '\" class=\"button\">';\n echo '</form>';\n\n if ($selectedCourse) {\n // Query students in the selected course with their latest assessment\n $sql = \"\n SELECT\n s.gibbonPersonID,\n s.preferredName,\n s.surname,\n c.gibbonCourseID,\n c.name AS courseName,\n iac.name AS assessmentColumnName, -- Added for clarity, can be used if needed\n iae.attainmentValue\n FROM\n gibbonInternalAssessmentEntry iae\n JOIN gibbonInternalAssessmentColumn iac ON iae.gibbonInternalAssessmentColumnID = iac.gibbonInternalAssessmentColumnID\n JOIN gibbonCourseClass ccc ON iac.gibbonCourseClassID = ccc.gibbonCourseClassID\n JOIN gibbonCourse c ON ccc.gibbonCourseID = c.gibbonCourseID\n JOIN gibbonPerson s ON iae.gibbonPersonIDStudent = s.gibbonPersonID\n WHERE\n c.gibbonCourseID = :courseID\";\n\n $params = ['courseID' => $selectedCourse];\n\n if (!empty($selectedAssessmentName)) {\n $sql .= \" AND iac.name = :assessmentName\";\n $params['assessmentName'] = $selectedAssessmentName;\n }\n\n $sql .= \" ORDER BY s.surname, s.preferredName, iac.name\"; // Added iac.name to ordering for consistency if multiple assessments per student\n \n $result = $pdo->executeQuery($params, $sql);\n $students = $result->fetchAll();\n \n // Filter only failing students\n $failingStudents = array_filter($students, function($student) use ($threshold) {\n return floatval($student['attainmentValue']) < $threshold;\n });\n \n if (count($failingStudents) > 0) {\n echo '<table class=\"fullWidth colorOddEven\">';\n echo '<tr>';\n echo '<th>' . __('Student') . '</th>';\n echo '<th>' . __('Course') . '</th>';\n echo '<th>' . __('Score') . '</th>';\n echo '<th>' . __('AI Recommendation') . '</th>';\n echo '</tr>';\n \n foreach ($failingStudents as $student) {\n $studentId = htmlspecialchars($student['gibbonPersonID']);\n $studentName = htmlspecialchars($student['preferredName'] . ' ' . $student['surname']);\n $courseName = htmlspecialchars($student['courseName']);\n $score = htmlspecialchars($student['attainmentValue']);\n echo '<tr>';\n echo '<td>' . $studentName . '</td>';\n echo '<td>' . $courseName . '</td>';\n echo '<td>' . $score . '</td>';\n echo '<td>';\n echo '<button class=\"get-ai-recommendation\" data-student-id=\"' . $studentId . '\" data-student-name=\"' . $studentName . '\" data-course-name=\"' . $courseName . '\" data-score=\"' . $score . '\">Get AI Recommendation</button>';\n echo '<div class=\"ai-recommendation-result\" id=\"ai-recommendation-' . $studentId . '\"></div>';\n echo '</td>';\n echo '</tr>';\n }\n echo '</table>';\n \n // Define the URL for JavaScript\n $getRecommendationURL = $gibbon->session->get('absoluteURL').'/modules/aiTeacher/get_ai_recommendation.php';\n ?>\n <script>\n const getRecommendationUrl = '<?php echo $getRecommendationURL; ?>';\n document.querySelectorAll('.get-ai-recommendation').forEach(function(btn) {\n btn.addEventListener('click', function() {\n var studentId = this.getAttribute('data-student-id');\n var studentName = this.getAttribute('data-student-name');\n var courseName = this.getAttribute('data-course-name');\n var score = this.getAttribute('data-score');\n var resultDiv = document.getElementById('ai-recommendation-' + studentId);\n resultDiv.innerHTML = 'Loading...';\n fetch(getRecommendationUrl, { // Use the dynamic URL\n method: 'POST',\n headers: {'Content-Type': 'application/json'},\n body: JSON.stringify({\n studentId: studentId, // Add studentId here\n studentName: studentName,\n courseName: courseName,\n score: score\n })\n })\n .then(response => response.text())\n .then(data => {\n resultDiv.innerHTML = data;\n })\n .catch(err => {\n resultDiv.innerHTML = 'Error fetching recommendation.';\n });\n });\n });\n </script>\n <?php\n } else {\n echo '<p>' . __('No failing students found for this course.') . '</p>';\n }\n } else {\n echo '<p>' . __('Please select a course to view students.') . '</p>';\n }\n} // Closing brace for the isActionAccessible check"}}, "modules/ChatBot/manifest.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745791.6899595, "n": 10090, "h": "c3d9401b445c4b16", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Basic variables\n$name = 'ChatBot';\n$description = 'AI-powered teaching assistant for teachers';\n$entryURL = 'chatbot.php';\n$type = 'Additional';\n$category = 'Learn';\n$version = '1.0.00';\n$author = 'Asley Smith';\n$url = '';\n\n// Module tables\n$moduleTables = array();\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotFeedback` (\n `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `user_message` text NOT NULL,\n `ai_response` text NOT NULL,\n `feedback_type` enum('like','dislike') NOT NULL,\n `feedback_text` text,\n `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotTraining` (\n `gibbonChatBotTrainingID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `question` text NOT NULL,\n `answer` text NOT NULL,\n `approved` tinyint(1) NOT NULL DEFAULT '0',\n `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotTrainingID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotCourseMaterials` (\n `gibbonChatBotCourseMaterialsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `title` varchar(255) NOT NULL,\n `type` varchar(50) NOT NULL,\n `description` text NULL,\n `filePath` varchar(255) NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `dateAdded` date NOT NULL,\n `gibbonPersonIDCreator` int(10) unsigned zerofill NOT NULL,\n PRIMARY KEY (`gibbonChatBotCourseMaterialsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentProgress` (\n `gibbonChatBotStudentProgressID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `progress` decimal(5,2) NOT NULL DEFAULT '0.00',\n `lastActivity` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentProgressID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentAnalytics` (\n `gibbonChatBotStudentAnalyticsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `analyticsData` text NOT NULL,\n `dateGenerated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentAnalyticsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotInterventions` (\n `gibbonChatBotInterventionsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `interventionType` varchar(50) NOT NULL,\n `description` text NOT NULL,\n `status` enum('Active','Completed','Cancelled') NOT NULL DEFAULT 'Active',\n `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `dateModified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotInterventionsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n// Module settings\n$gibbonSetting = array();\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek AI service', '');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'model_name', 'Model Name', 'DeepSeek model name', 'deepseek-v4-flash');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'max_tokens', 'Max Tokens', 'Maximum number of tokens to generate in responses', '4000');\";\n\n// Action rows\n$actionRows = array();\n\n$actionRows[] = [\n 'name' => 'AI Teaching Assistant',\n 'precedence' => '0',\n 'category' => 'Learning',\n 'description' => 'Access the AI teaching assistant',\n 'URLList' => 'chatbot.php',\n 'entryURL' => 'chatbot.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'Y',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'Y',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'View Student Assessments',\n 'precedence' => '1',\n 'category' => 'Learning',\n 'description' => 'View and analyze student assessment data',\n 'URLList' => 'assessment_integration.php',\n 'entryURL' => 'assessment_integration.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage Training',\n 'precedence' => '2',\n 'category' => 'Admin',\n 'description' => 'Manage ChatBot training data',\n 'URLList' => 'training.php,upload.php',\n 'entryURL' => 'training.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage ChatBot',\n 'precedence' => '3',\n 'category' => 'Admin',\n 'description' => 'Configure ChatBot settings',\n 'URLList' => 'settings.php',\n 'entryURL' => 'settings.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'N',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'N',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Learning Management',\n 'precedence' => '4',\n 'category' => 'Learning',\n 'description' => 'Manage learning resources and content',\n 'URLList' => 'learning_management.php,learning_management_addProcess.php,learning_management_edit.php,learning_management_delete.php',\n 'entryURL' => 'learning_management.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'AI Learning & Feedback Analytics',\n 'precedence' => '5',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback and AI learning data',\n 'URLList' => 'ai_learning.php',\n 'entryURL' => 'ai_learning.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Feedback Analytics',\n 'precedence' => '6',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback data',\n 'URLList' => 'feedback.php,api/feedback.php',\n 'entryURL' => 'feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Check Feedback Database',\n 'precedence' => '7',\n 'category' => 'Admin',\n 'description' => 'Check and verify ChatBot feedback database status',\n 'URLList' => 'db_check_feedback.php',\n 'entryURL' => 'db_check_feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n// Return module configuration\nreturn [\n 'name' => $name,\n 'version' => $version,\n 'description' => $description,\n 'author' => $author,\n 'url' => $url,\n 'entryURL' => $entryURL,\n 'type' => $type,\n 'category' => $category,\n 'tables' => $moduleTables,\n 'gibbonSettings' => $gibbonSetting,\n 'actionRows' => $actionRows\n];\n"}, "228065060bc341b101409b1b5a4415a502b48a3a": {"t": 1785756293.9305758, "n": 10090, "h": "c3d9401b445c4b16", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Basic variables\n$name = 'ChatBot';\n$description = 'AI-powered teaching assistant for teachers';\n$entryURL = 'chatbot.php';\n$type = 'Additional';\n$category = 'Learn';\n$version = '1.0.00';\n$author = 'Asley Smith';\n$url = '';\n\n// Module tables\n$moduleTables = array();\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotFeedback` (\n `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `user_message` text NOT NULL,\n `ai_response` text NOT NULL,\n `feedback_type` enum('like','dislike') NOT NULL,\n `feedback_text` text,\n `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotTraining` (\n `gibbonChatBotTrainingID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `question` text NOT NULL,\n `answer` text NOT NULL,\n `approved` tinyint(1) NOT NULL DEFAULT '0',\n `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotTrainingID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotCourseMaterials` (\n `gibbonChatBotCourseMaterialsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `title` varchar(255) NOT NULL,\n `type` varchar(50) NOT NULL,\n `description` text NULL,\n `filePath` varchar(255) NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `dateAdded` date NOT NULL,\n `gibbonPersonIDCreator` int(10) unsigned zerofill NOT NULL,\n PRIMARY KEY (`gibbonChatBotCourseMaterialsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentProgress` (\n `gibbonChatBotStudentProgressID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `progress` decimal(5,2) NOT NULL DEFAULT '0.00',\n `lastActivity` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentProgressID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentAnalytics` (\n `gibbonChatBotStudentAnalyticsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `analyticsData` text NOT NULL,\n `dateGenerated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentAnalyticsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotInterventions` (\n `gibbonChatBotInterventionsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `interventionType` varchar(50) NOT NULL,\n `description` text NOT NULL,\n `status` enum('Active','Completed','Cancelled') NOT NULL DEFAULT 'Active',\n `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `dateModified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotInterventionsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n// Module settings\n$gibbonSetting = array();\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek AI service', '');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'model_name', 'Model Name', 'DeepSeek model name', 'deepseek-v4-flash');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'max_tokens', 'Max Tokens', 'Maximum number of tokens to generate in responses', '4000');\";\n\n// Action rows\n$actionRows = array();\n\n$actionRows[] = [\n 'name' => 'AI Teaching Assistant',\n 'precedence' => '0',\n 'category' => 'Learning',\n 'description' => 'Access the AI teaching assistant',\n 'URLList' => 'chatbot.php',\n 'entryURL' => 'chatbot.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'Y',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'Y',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'View Student Assessments',\n 'precedence' => '1',\n 'category' => 'Learning',\n 'description' => 'View and analyze student assessment data',\n 'URLList' => 'assessment_integration.php',\n 'entryURL' => 'assessment_integration.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage Training',\n 'precedence' => '2',\n 'category' => 'Admin',\n 'description' => 'Manage ChatBot training data',\n 'URLList' => 'training.php,upload.php',\n 'entryURL' => 'training.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage ChatBot',\n 'precedence' => '3',\n 'category' => 'Admin',\n 'description' => 'Configure ChatBot settings',\n 'URLList' => 'settings.php',\n 'entryURL' => 'settings.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'N',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'N',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Learning Management',\n 'precedence' => '4',\n 'category' => 'Learning',\n 'description' => 'Manage learning resources and content',\n 'URLList' => 'learning_management.php,learning_management_addProcess.php,learning_management_edit.php,learning_management_delete.php',\n 'entryURL' => 'learning_management.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'AI Learning & Feedback Analytics',\n 'precedence' => '5',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback and AI learning data',\n 'URLList' => 'ai_learning.php',\n 'entryURL' => 'ai_learning.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Feedback Analytics',\n 'precedence' => '6',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback data',\n 'URLList' => 'feedback.php,api/feedback.php',\n 'entryURL' => 'feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Check Feedback Database',\n 'precedence' => '7',\n 'category' => 'Admin',\n 'description' => 'Check and verify ChatBot feedback database status',\n 'URLList' => 'db_check_feedback.php',\n 'entryURL' => 'db_check_feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n// Return module configuration\nreturn [\n 'name' => $name,\n 'version' => $version,\n 'description' => $description,\n 'author' => $author,\n 'url' => $url,\n 'entryURL' => $entryURL,\n 'type' => $type,\n 'category' => $category,\n 'tables' => $moduleTables,\n 'gibbonSettings' => $gibbonSetting,\n 'actionRows' => $actionRows\n];\n"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756297.511358, "n": 10108, "h": "209bb5889c501a41", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Basic variables\n$name = 'ChatBot';\n$description = 'AI-powered teaching assistant for teachers';\n$entryURL = 'chatbot.php';\n$type = 'Additional';\n$category = 'Learn';\n$version = '1.0.00';\n$author = 'Asley Smith';\n$url = '';\n\n// Module tables\n$moduleTables = array();\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotFeedback` (\n `id` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `user_message` text NOT NULL,\n `ai_response` text NOT NULL,\n `feedback_type` enum('like','dislike') NOT NULL,\n `feedback_text` text,\n `timestamp` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`id`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotTraining` (\n `gibbonChatBotTrainingID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `question` text NOT NULL,\n `answer` text NOT NULL,\n `approved` tinyint(1) NOT NULL DEFAULT '0',\n `created_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotTrainingID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotCourseMaterials` (\n `gibbonChatBotCourseMaterialsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `title` varchar(255) NOT NULL,\n `type` varchar(50) NOT NULL,\n `description` text NULL,\n `filePath` varchar(255) NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `dateAdded` date NOT NULL,\n `gibbonPersonIDCreator` int(10) unsigned zerofill NOT NULL,\n PRIMARY KEY (`gibbonChatBotCourseMaterialsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentProgress` (\n `gibbonChatBotStudentProgressID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `progress` decimal(5,2) NOT NULL DEFAULT '0.00',\n `lastActivity` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentProgressID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotStudentAnalytics` (\n `gibbonChatBotStudentAnalyticsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `analyticsData` text NOT NULL,\n `dateGenerated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotStudentAnalyticsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n$moduleTables[] = \"\nCREATE TABLE `gibbonChatBotInterventions` (\n `gibbonChatBotInterventionsID` int(10) unsigned zerofill NOT NULL AUTO_INCREMENT,\n `gibbonPersonID` int(10) unsigned zerofill NOT NULL,\n `gibbonCourseID` int(8) unsigned zerofill NOT NULL,\n `gibbonSchoolYearID` int(3) unsigned zerofill NOT NULL,\n `interventionType` varchar(50) NOT NULL,\n `description` text NOT NULL,\n `status` enum('Active','Completed','Cancelled') NOT NULL DEFAULT 'Active',\n `dateCreated` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `dateModified` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n PRIMARY KEY (`gibbonChatBotInterventionsID`)\n) ENGINE=InnoDB DEFAULT CHARSET=utf8;\";\n\n// Module settings\n$gibbonSetting = array();\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'deepseek_api_key', 'DeepSeek API Key', 'API key for DeepSeek AI service', '');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'model_name', 'Model Name', 'DeepSeek model name (e.g., deepseek-chat)', 'deepseek-chat');\";\n$gibbonSetting[] = \"INSERT INTO gibbonSetting (scope, name, nameDisplay, description, value) VALUES ('ChatBot', 'max_tokens', 'Max Tokens', 'Maximum number of tokens to generate in responses', '4000');\";\n\n// Action rows\n$actionRows = array();\n\n$actionRows[] = [\n 'name' => 'AI Teaching Assistant',\n 'precedence' => '0',\n 'category' => 'Learning',\n 'description' => 'Access the AI teaching assistant',\n 'URLList' => 'chatbot.php',\n 'entryURL' => 'chatbot.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'Y',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'Y',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'View Student Assessments',\n 'precedence' => '1',\n 'category' => 'Learning',\n 'description' => 'View and analyze student assessment data',\n 'URLList' => 'assessment_integration.php',\n 'entryURL' => 'assessment_integration.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage Training',\n 'precedence' => '2',\n 'category' => 'Admin',\n 'description' => 'Manage ChatBot training data',\n 'URLList' => 'training.php,upload.php',\n 'entryURL' => 'training.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Manage ChatBot',\n 'precedence' => '3',\n 'category' => 'Admin',\n 'description' => 'Configure ChatBot settings',\n 'URLList' => 'settings.php',\n 'entryURL' => 'settings.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'N',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'N',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Learning Management',\n 'precedence' => '4',\n 'category' => 'Learning',\n 'description' => 'Manage learning resources and content',\n 'URLList' => 'learning_management.php,learning_management_addProcess.php,learning_management_edit.php,learning_management_delete.php',\n 'entryURL' => 'learning_management.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'AI Learning & Feedback Analytics',\n 'precedence' => '5',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback and AI learning data',\n 'URLList' => 'ai_learning.php',\n 'entryURL' => 'ai_learning.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Feedback Analytics',\n 'precedence' => '6',\n 'category' => 'Admin',\n 'description' => 'View and analyze ChatBot feedback data',\n 'URLList' => 'feedback.php,api/feedback.php',\n 'entryURL' => 'feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n$actionRows[] = [\n 'name' => 'Check Feedback Database',\n 'precedence' => '7',\n 'category' => 'Admin',\n 'description' => 'Check and verify ChatBot feedback database status',\n 'URLList' => 'db_check_feedback.php',\n 'entryURL' => 'db_check_feedback.php',\n 'defaultPermissionAdmin' => 'Y',\n 'defaultPermissionTeacher' => 'Y',\n 'defaultPermissionStudent' => 'N',\n 'defaultPermissionParent' => 'N',\n 'defaultPermissionSupport' => 'Y',\n 'categoryPermissionStaff' => 'Y',\n 'categoryPermissionStudent' => 'N',\n 'categoryPermissionParent' => 'N',\n 'categoryPermissionOther' => 'N'\n];\n\n// Return module configuration\nreturn [\n 'name' => $name,\n 'version' => $version,\n 'description' => $description,\n 'author' => $author,\n 'url' => $url,\n 'entryURL' => $entryURL,\n 'type' => $type,\n 'category' => $category,\n 'tables' => $moduleTables,\n 'gibbonSettings' => $gibbonSetting,\n 'actionRows' => $actionRows\n];\n"}}, "modules/ChatBot/simple_api_test.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785745793.53718, "n": 7183, "h": "0889cbc57bdce8a3", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Module includes\nrequire_once '../../gibbon.php';\n\n// Check if core Gibbon functions are available\nif (!function_exists('__') || !function_exists('isActionAccessible')) {\n die('Fatal Error: Gibbon core functions not loaded');\n}\n\n// Import required classes\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\View\\Page;\n\n// Basic initialization\nif (!isset($container)) {\n die('Fatal Error: Gibbon container not initialized');\n}\n\nif (!isset($guid) || !isset($connection2)) {\n die('Fatal Error: Gibbon core variables not initialized');\n}\n\n// Setup routes - moved outside try block to ensure it's available in catch blocks\n$page = new Page($container, ['address' => $_GET['q'] ?? '']);\n\nif (!$page instanceof Page) {\n die('Fatal Error: Failed to initialize Page object');\n}\n\ntry {\n // Check access\n if (!isActionAccessible($guid, $connection2, '/modules/ChatBot/simple_api_test.php')) {\n // Access denied\n $page->addWarning(__('You do not have access to this action.'));\n return;\n }\n\n // Get session\n $session = $container->get('session');\n\n // Set page breadcrumb\n $page->breadcrumbs\n ->add(__('ChatBot'), 'chatbot.php')\n ->add(__('API Test'));\n\n // Get the API key from settings\n try {\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n } catch (Exception $e) {\n $page->addError('Failed to load API key: ' . $e->getMessage());\n return;\n }\n} catch (Exception $e) {\n die('Fatal Error: ' . $e->getMessage());\n}\n\n// Add CSS\necho \"<link rel='stylesheet' type='text/css' href='\" . $session->get('absoluteURL') . \"/modules/ChatBot/css/chatbot.css'>\";\n\n// Page title\necho \"<h2>\" . __('API Test') . \"</h2>\";\n\n// Add sidebar menu\n$page->addSidebarExtra('\n <div class=\"column-no-break\">\n <h2>' . __('ChatBot Menu') . '</h2>\n <ul class=\"moduleMenu\">\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/chatbot.php\">' . __('AI Teaching Assistant') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/assessment_integration.php\">' . __('Assessment Integration') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/learning_management.php\">' . __('Learning Management') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/settings.php\">' . __('Settings') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/feedback.php\">' . __('Feedback Analytics') . '</a></li>\n <li class=\"selected\"><a href=\"' . $session->get('absoluteURL') . '/modules/ChatBot/simple_api_test.php\" style=\"color:#2a7fff;\">' . __('Test API Key') . ' <i class=\"fas fa-external-link-alt fa-xs\"></i></a></li>\n </ul>\n </div>\n');\n\n// Create the test form\n$form = Gibbon\\Forms\\Form::create('apiTest', $session->get('absoluteURL') . '/modules/ChatBot/api_test_process.php');\n$form->setTitle('API Key Test');\n\n$form->addHiddenValue('address', $session->get('address'));\n$form->addHiddenValue('gibbonCSRFToken', $session->get('gibbonCSRFToken'));\n\n$row = $form->addRow();\n$row->addLabel('testMessage', __('Test Message'))\n ->description(__('Enter a message to test the API connection.'));\n$row->addTextArea('testMessage')\n ->setRows(3)\n ->setValue('What is 2+2?')\n ->required();\n\n$row = $form->addRow();\n$row->addFooter();\n$row->addSubmit('Test API');\n\necho $form->getOutput();\n\n// Add results container\necho '<div id=\"testResults\" class=\"test-results\" style=\"display:none;\">\n <h3>' . __('Test Results') . '</h3>\n <div class=\"message-container\"></div>\n</div>';\n\n// Add JavaScript for form handling\n?>\n<script>\ndocument.getElementById('apiTest').addEventListener('submit', function(e) {\n e.preventDefault();\n \n const testMessage = document.querySelector('textarea[name=\"testMessage\"]').value;\n const token = document.querySelector('input[name=\"gibbonCSRFToken\"]').value;\n const resultsDiv = document.getElementById('testResults');\n const messageContainer = resultsDiv.querySelector('.message-container');\n \n // Show loading state\n resultsDiv.style.display = 'block';\n messageContainer.innerHTML = '<div class=\"loading\">Testing API connection...</div>';\n \n // Send test request\n fetch('<?php echo $session->get('absoluteURL'); ?>/modules/ChatBot/api_test_process.php', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n testMessage: testMessage,\n gibbonCSRFToken: token\n })\n })\n .then(response => response.json())\n .then(data => {\n messageContainer.innerHTML = '';\n \n if (data.success) {\n messageContainer.innerHTML = `\n <div class=\"success\">\n <h4>API Test Successful!</h4>\n <p><strong>Message sent:</strong> ${testMessage}</p>\n <p><strong>Response received:</strong> ${data.response}</p>\n <p><strong>Response time:</strong> ${data.responseTime}ms</p>\n </div>`;\n } else {\n messageContainer.innerHTML = `\n <div class=\"error\">\n <h4>API Test Failed</h4>\n <p><strong>Error:</strong> ${data.error}</p>\n </div>`;\n }\n })\n .catch(error => {\n messageContainer.innerHTML = `\n <div class=\"error\">\n <h4>API Test Failed</h4>\n <p><strong>Error:</strong> ${error.message}</p>\n </div>`;\n });\n});\n</script>\n\n<style>\n.test-results {\n margin-top: 20px;\n padding: 15px;\n background: #f8f9fa;\n border-radius: 4px;\n}\n\n.test-results h3 {\n margin-top: 0;\n color: #333;\n}\n\n.loading {\n padding: 10px;\n background: #e9ecef;\n border-radius: 4px;\n text-align: center;\n}\n\n.success {\n padding: 15px;\n background: #d4edda;\n border: 1px solid #c3e6cb;\n border-radius: 4px;\n color: #155724;\n}\n\n.error {\n padding: 15px;\n background: #f8d7da;\n border: 1px solid #f5c6cb;\n border-radius: 4px;\n color: #721c24;\n}\n\n.success h4, .error h4 {\n margin-top: 0;\n margin-bottom: 10px;\n}\n\n.success p, .error p {\n margin: 5px 0;\n}\n</style>\n?>"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756291.3493762, "n": 7183, "h": "0889cbc57bdce8a3", "c": "<?php\n/*\nGibbon, Flexible & Open School System\nCopyright (C) 2010, Ross Parker\n\nThis program is free software: you can redistribute it and/or modify\nit under the terms of the GNU General Public License as published by\nthe Free Software Foundation, either version 3 of the License, or\n(at your option) any later version.\n\nThis program is distributed in the hope that it will be useful,\nbut WITHOUT ANY WARRANTY; without even the implied warranty of\nMERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the\nGNU General Public License for more details.\n\nYou should have received a copy of the GNU General Public License\nalong with this program. If not, see <http://www.gnu.org/licenses/>.\n*/\n\n// Module includes\nrequire_once '../../gibbon.php';\n\n// Check if core Gibbon functions are available\nif (!function_exists('__') || !function_exists('isActionAccessible')) {\n die('Fatal Error: Gibbon core functions not loaded');\n}\n\n// Import required classes\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\View\\Page;\n\n// Basic initialization\nif (!isset($container)) {\n die('Fatal Error: Gibbon container not initialized');\n}\n\nif (!isset($guid) || !isset($connection2)) {\n die('Fatal Error: Gibbon core variables not initialized');\n}\n\n// Setup routes - moved outside try block to ensure it's available in catch blocks\n$page = new Page($container, ['address' => $_GET['q'] ?? '']);\n\nif (!$page instanceof Page) {\n die('Fatal Error: Failed to initialize Page object');\n}\n\ntry {\n // Check access\n if (!isActionAccessible($guid, $connection2, '/modules/ChatBot/simple_api_test.php')) {\n // Access denied\n $page->addWarning(__('You do not have access to this action.'));\n return;\n }\n\n // Get session\n $session = $container->get('session');\n\n // Set page breadcrumb\n $page->breadcrumbs\n ->add(__('ChatBot'), 'chatbot.php')\n ->add(__('API Test'));\n\n // Get the API key from settings\n try {\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n } catch (Exception $e) {\n $page->addError('Failed to load API key: ' . $e->getMessage());\n return;\n }\n} catch (Exception $e) {\n die('Fatal Error: ' . $e->getMessage());\n}\n\n// Add CSS\necho \"<link rel='stylesheet' type='text/css' href='\" . $session->get('absoluteURL') . \"/modules/ChatBot/css/chatbot.css'>\";\n\n// Page title\necho \"<h2>\" . __('API Test') . \"</h2>\";\n\n// Add sidebar menu\n$page->addSidebarExtra('\n <div class=\"column-no-break\">\n <h2>' . __('ChatBot Menu') . '</h2>\n <ul class=\"moduleMenu\">\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/chatbot.php\">' . __('AI Teaching Assistant') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/assessment_integration.php\">' . __('Assessment Integration') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/learning_management.php\">' . __('Learning Management') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/settings.php\">' . __('Settings') . '</a></li>\n <li><a href=\"' . $session->get('absoluteURL') . '/index.php?q=/modules/ChatBot/feedback.php\">' . __('Feedback Analytics') . '</a></li>\n <li class=\"selected\"><a href=\"' . $session->get('absoluteURL') . '/modules/ChatBot/simple_api_test.php\" style=\"color:#2a7fff;\">' . __('Test API Key') . ' <i class=\"fas fa-external-link-alt fa-xs\"></i></a></li>\n </ul>\n </div>\n');\n\n// Create the test form\n$form = Gibbon\\Forms\\Form::create('apiTest', $session->get('absoluteURL') . '/modules/ChatBot/api_test_process.php');\n$form->setTitle('API Key Test');\n\n$form->addHiddenValue('address', $session->get('address'));\n$form->addHiddenValue('gibbonCSRFToken', $session->get('gibbonCSRFToken'));\n\n$row = $form->addRow();\n$row->addLabel('testMessage', __('Test Message'))\n ->description(__('Enter a message to test the API connection.'));\n$row->addTextArea('testMessage')\n ->setRows(3)\n ->setValue('What is 2+2?')\n ->required();\n\n$row = $form->addRow();\n$row->addFooter();\n$row->addSubmit('Test API');\n\necho $form->getOutput();\n\n// Add results container\necho '<div id=\"testResults\" class=\"test-results\" style=\"display:none;\">\n <h3>' . __('Test Results') . '</h3>\n <div class=\"message-container\"></div>\n</div>';\n\n// Add JavaScript for form handling\n?>\n<script>\ndocument.getElementById('apiTest').addEventListener('submit', function(e) {\n e.preventDefault();\n \n const testMessage = document.querySelector('textarea[name=\"testMessage\"]').value;\n const token = document.querySelector('input[name=\"gibbonCSRFToken\"]').value;\n const resultsDiv = document.getElementById('testResults');\n const messageContainer = resultsDiv.querySelector('.message-container');\n \n // Show loading state\n resultsDiv.style.display = 'block';\n messageContainer.innerHTML = '<div class=\"loading\">Testing API connection...</div>';\n \n // Send test request\n fetch('<?php echo $session->get('absoluteURL'); ?>/modules/ChatBot/api_test_process.php', {\n method: 'POST',\n headers: {\n 'Content-Type': 'application/x-www-form-urlencoded',\n },\n body: new URLSearchParams({\n testMessage: testMessage,\n gibbonCSRFToken: token\n })\n })\n .then(response => response.json())\n .then(data => {\n messageContainer.innerHTML = '';\n \n if (data.success) {\n messageContainer.innerHTML = `\n <div class=\"success\">\n <h4>API Test Successful!</h4>\n <p><strong>Message sent:</strong> ${testMessage}</p>\n <p><strong>Response received:</strong> ${data.response}</p>\n <p><strong>Response time:</strong> ${data.responseTime}ms</p>\n </div>`;\n } else {\n messageContainer.innerHTML = `\n <div class=\"error\">\n <h4>API Test Failed</h4>\n <p><strong>Error:</strong> ${data.error}</p>\n </div>`;\n }\n })\n .catch(error => {\n messageContainer.innerHTML = `\n <div class=\"error\">\n <h4>API Test Failed</h4>\n <p><strong>Error:</strong> ${error.message}</p>\n </div>`;\n });\n});\n</script>\n\n<style>\n.test-results {\n margin-top: 20px;\n padding: 15px;\n background: #f8f9fa;\n border-radius: 4px;\n}\n\n.test-results h3 {\n margin-top: 0;\n color: #333;\n}\n\n.loading {\n padding: 10px;\n background: #e9ecef;\n border-radius: 4px;\n text-align: center;\n}\n\n.success {\n padding: 15px;\n background: #d4edda;\n border: 1px solid #c3e6cb;\n border-radius: 4px;\n color: #155724;\n}\n\n.error {\n padding: 15px;\n background: #f8d7da;\n border: 1px solid #f5c6cb;\n border-radius: 4px;\n color: #721c24;\n}\n\n.success h4, .error h4 {\n margin-top: 0;\n margin-bottom: 10px;\n}\n\n.success p, .error p {\n margin: 5px 0;\n}\n</style>\n?>"}}, "modules/aiTeacher/script.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785746143.996883, "n": 738, "h": "77633d6e9e2fbdd5", "c": "<?php\n$ch = curl_init('https://api.deepseek.com/v1/chat/completions');\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\ncurl_setopt($ch, CURLOPT_POST, true);\ncurl_setopt($ch, CURLOPT_POSTFIELDS, '{\"model\":\"deepseek-coder\",\"messages\":[{\"role\":\"system\",\"content\":\"Test\"},{\"role\":\"user\",\"content\":\"Hello\"}],\"temperature\":0.7,\"max_tokens\":2000}');\ncurl_setopt($ch, CURLOPT_HTTPHEADER, array(\n 'Content-Type: application/json',\n 'Authorization: Bearer sk-e8ca9e37845441468076295c174512e4'\n));\ncurl_setopt($ch, CURLOPT_TIMEOUT, 15);\ncurl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);\n$response = curl_exec($ch);\nif ($response === false) {\n echo \"Error: \" . curl_error($ch) . PHP_EOL;\n} else {\n echo $response . PHP_EOL;\n}\ncurl_close($ch);\n"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756300.4309351, "n": 738, "h": "77633d6e9e2fbdd5", "c": "<?php\n$ch = curl_init('https://api.deepseek.com/v1/chat/completions');\ncurl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\ncurl_setopt($ch, CURLOPT_POST, true);\ncurl_setopt($ch, CURLOPT_POSTFIELDS, '{\"model\":\"deepseek-coder\",\"messages\":[{\"role\":\"system\",\"content\":\"Test\"},{\"role\":\"user\",\"content\":\"Hello\"}],\"temperature\":0.7,\"max_tokens\":2000}');\ncurl_setopt($ch, CURLOPT_HTTPHEADER, array(\n 'Content-Type: application/json',\n 'Authorization: Bearer sk-e8ca9e37845441468076295c174512e4'\n));\ncurl_setopt($ch, CURLOPT_TIMEOUT, 15);\ncurl_setopt($ch, CURLOPT_CONNECTTIMEOUT, 5);\n$response = curl_exec($ch);\nif ($response === false) {\n echo \"Error: \" . curl_error($ch) . PHP_EOL;\n} else {\n echo $response . PHP_EOL;\n}\ncurl_close($ch);\n"}}, "modules/ChatBot/api/deepseek_api.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785746167.4539561, "n": 12992, "h": "2c6e27267d38d384", "c": " <?php\nnamespace Gibbon\\Module\\ChatBot;\n\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Contracts\\Database\\Connection;\n\nclass DeepSeekAPI\n{\n private $apiKey;\n private $apiEndpoint = 'https://api.deepseek.com/v1/chat/completions';\n private $modelName = 'deepseek-chat';\n private $maxTokens = 1000;\n private $maxRetries = 3;\n private $timeout = 60;\n private $rateLimitDelay = 1; // Base delay in seconds\n\n\n // In your PHP API handler (e.g., api.php) Added by Asley in April 2025\n$apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n\n$response = $httpClient->post('https://api.deepseek.com/v1/chat/completions', [\n 'headers' => [\n 'Authorization' => 'Bearer ' . $apiKey,\n 'Content-Type' => 'application/json',\n ],\n 'json' => [\n 'model' => 'deepseek-chat',\n 'messages' => [['role' => 'user', 'content' => $userMessage]]\n]);\n//Ended\n\n\n\n public function __construct($apiKey = null, $connection = null, $container = null)\n {\n if ($apiKey === null) {\n // Try to get API key from Gibbon settings\n try {\n // Try to use container if available (preferred method)\n if (isset($container) && $container->has(SettingGateway::class)) {\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty in settings');\n }\n }\n // Fallback to using provided database connection\n else if ($connection instanceof Connection) {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection->select($sql);\n \n if (empty($result)) {\n throw new \\Exception('DeepSeek API key not found in settings');\n }\n \n $apiKey = $result[0]['value'];\n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty');\n }\n }\n // Last resort: try to get database connection from globals\n else if (isset($GLOBALS['container']) && $GLOBALS['container']->has(SettingGateway::class)) {\n $settingGateway = $GLOBALS['container']->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty in settings');\n }\n } \n else {\n throw new \\Exception('No valid connection or container available');\n }\n } catch (\\Exception $e) {\n error_log('DeepSeekAPI Database Error: ' . $e->getMessage());\n throw new \\Exception('Failed to retrieve API key from database: ' . $e->getMessage());\n }\n }\n \n $this->apiKey = $apiKey;\n \n // Try to get model settings if available\n if (isset($container) && $container->has(SettingGateway::class)) {\n $settingGateway = $container->get(SettingGateway::class);\n $modelName = $settingGateway->getSettingByScope('ChatBot', 'model_name');\n $maxTokens = $settingGateway->getSettingByScope('ChatBot', 'max_tokens');\n \n if (!empty($modelName)) $this->modelName = $modelName;\n if (!empty($maxTokens)) $this->maxTokens = intval($maxTokens);\n }\n }\n\n /**\n * Get a response from the DeepSeek API with rate limiting and exponential backoff\n *\n * @param string $message The user's message\n * @param bool $isTrainingMode Whether training mode is enabled\n * @return array The AI's response\n * @throws \\Exception\n */\n public function getResponse($message, $isTrainingMode = false)\n {\n error_log('DeepSeekAPI: Preparing request for message: ' . substr($message, 0, 100) . '...');\n \n // Validate input\n if (empty($message)) {\n error_log('DeepSeekAPI Error: Empty message provided');\n throw new \\Exception('Message cannot be empty');\n }\n\n // Define system prompt based on mode\n $systemPrompt = $this->getSystemPrompt($isTrainingMode);\n\n // Prepare request data\n $data = [\n 'model' => $this->modelName,\n 'messages' => [\n ['role' => 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $message]\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $isTrainingMode ? 0.7 : 0.5,\n 'stream' => false\n ];\n\n error_log('DeepSeekAPI: Request data prepared: ' . json_encode($data));\n\n // Try up to maxRetries times with exponential backoff\n $lastError = null;\n for ($attempt = 1; $attempt <= $this->maxRetries; $attempt++) {\n try {\n error_log(\"DeepSeekAPI: Attempt $attempt of {$this->maxRetries}\");\n \n // Apply rate limiting delay if not first attempt\n if ($attempt > 1) {\n $delay = $this->rateLimitDelay * pow(2, $attempt - 1); // Exponential backoff\n error_log(\"DeepSeekAPI: Waiting {$delay} seconds before retry\");\n sleep($delay);\n }\n \n // Initialize cURL\n $ch = curl_init($this->apiEndpoint);\n if ($ch === false) {\n throw new \\Exception('Failed to initialize cURL');\n }\n\n // Set cURL options\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $this->apiKey\n ],\n CURLOPT_TIMEOUT => $this->timeout,\n CURLOPT_SSL_VERIFYPEER => true\n ]);\n\n error_log('DeepSeekAPI: Sending request to ' . $this->apiEndpoint);\n\n // Execute request\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n $curlError = curl_error($ch);\n \n error_log('DeepSeekAPI: Response received - HTTP Code: ' . $httpCode);\n error_log('DeepSeekAPI: Raw response: ' . substr($response, 0, 1000));\n\n curl_close($ch);\n\n // Handle cURL errors\n if ($response === false) {\n throw new \\Exception('Failed to connect to AI service: ' . $curlError);\n }\n\n // Handle rate limiting\n if ($httpCode === 429) {\n $retryAfter = $this->getRetryAfterHeader($response) ?? $this->rateLimitDelay * pow(2, $attempt);\n error_log(\"DeepSeekAPI: Rate limited. Waiting {$retryAfter} seconds.\");\n sleep($retryAfter);\n continue;\n }\n\n // Handle other HTTP errors\n if ($httpCode !== 200) {\n $errorData = json_decode($response, true);\n $errorMessage = isset($errorData['error']['message']) ? $errorData['error']['message'] : 'Unknown error';\n throw new \\Exception('AI service error: ' . $errorMessage);\n }\n\n // Parse response\n $responseData = json_decode($response, true);\n if (json_last_error() !== JSON_ERROR_NONE) {\n throw new \\Exception('Invalid response from AI service: ' . json_last_error_msg());\n }\n\n // Extract message content\n if (!isset($responseData['choices'][0]['message']['content'])) {\n throw new \\Exception('Unexpected response format from AI service');\n }\n\n $content = $responseData['choices'][0]['message']['content'];\n error_log('DeepSeekAPI: Successfully received content: ' . substr($content, 0, 100) . '...');\n\n return [\n 'success' => true,\n 'answer' => $content,\n 'timestamp' => date('Y-m-d H:i:s'),\n 'usage' => $responseData['usage'] ?? null\n ];\n\n } catch (\\Exception $e) {\n $lastError = $e;\n error_log('DeepSeekAPI Error on attempt ' . $attempt . ': ' . $e->getMessage());\n \n // Don't retry on certain errors\n if ($this->isNonRetryableError($e)) {\n throw $e;\n }\n \n if ($attempt < $this->maxRetries) {\n continue;\n }\n }\n }\n\n // If we get here, all retries failed\n throw $lastError ?? new \\Exception('Failed to get response from AI service after ' . $this->maxRetries . ' attempts');\n }\n\n /**\n * Get the system prompt based on mode\n *\n * @param bool $trainingMode\n * @return string\n */\n private function getSystemPrompt($trainingMode)\n {\n if ($trainingMode) {\n return \"You are an AI teaching assistant trained to provide detailed, comprehensive educational guidance. \" .\n \"Focus on clear explanations, practical examples, and constructive feedback. \" .\n \"Use markdown formatting for better readability:\\n\" .\n \"- Use # for main headings\\n\" .\n \"- Use ## for subheadings\\n\" .\n \"- Use **bold** for emphasis\\n\" .\n \"- Use `code` for technical terms\\n\" .\n \"- Use numbered lists for steps\\n\" .\n \"- Use bullet points for key concepts\";\n }\n\n return \"You are a helpful AI teaching assistant. Provide clear, concise answers to educational questions. \" .\n \"Focus on being informative and supportive while maintaining a professional tone.\";\n }\n\n /**\n * Test the API connection\n *\n * @return bool\n * @throws \\Exception\n */\n public function testConnection()\n {\n try {\n // Check DNS resolution first\n $host = parse_url($this->apiEndpoint, PHP_URL_HOST);\n if (!$host) {\n throw new \\Exception('Invalid API endpoint URL');\n }\n\n $dnsCheck = dns_get_record($host, DNS_A);\n if (empty($dnsCheck)) {\n throw new \\Exception('Could not resolve API host');\n }\n\n error_log('DeepSeek API Test: DNS resolution successful for ' . $host);\n error_log('DeepSeek API Test: IP addresses - ' . implode(', ', array_column($dnsCheck, 'ip')));\n\n $response = $this->getResponse('Test connection');\n return true;\n } catch (\\Exception $e) {\n error_log('DeepSeek API Test Connection Failed: ' . $e->getMessage());\n throw new \\Exception('API connection test failed: ' . $e->getMessage());\n }\n }\n\n /**\n * Get retry-after value from response headers\n *\n * @param string $response The raw response\n * @return int|null The number of seconds to wait\n */\n private function getRetryAfterHeader($response)\n {\n $headers = [];\n $rawHeaders = substr($response, 0, strpos($response, \"\\r\\n\\r\\n\"));\n foreach (explode(\"\\r\\n\", $rawHeaders) as $line) {\n if (strpos($line, ':') !== false) {\n list($key, $value) = explode(':', $line, 2);\n $headers[strtolower(trim($key))] = trim($value);\n }\n }\n \n return isset($headers['retry-after']) ? (int)$headers['retry-after'] : null;\n }\n\n /**\n * Check if an error should not be retried\n *\n * @param \\Exception $error The error to check\n * @return bool\n */\n private function isNonRetryableError(\\Exception $error)\n {\n $nonRetryableMessages = [\n 'API key not found',\n 'Invalid API key',\n 'Permission denied',\n 'Invalid request format'\n ];\n \n foreach ($nonRetryableMessages as $message) {\n if (stripos($error->getMessage(), $message) !== false) {\n return true;\n }\n }\n \n return false;\n }\n}"}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756299.305897, "n": 12992, "h": "2c6e27267d38d384", "c": " <?php\nnamespace Gibbon\\Module\\ChatBot;\n\nuse Gibbon\\Domain\\System\\SettingGateway;\nuse Gibbon\\Contracts\\Database\\Connection;\n\nclass DeepSeekAPI\n{\n private $apiKey;\n private $apiEndpoint = 'https://api.deepseek.com/v1/chat/completions';\n private $modelName = 'deepseek-chat';\n private $maxTokens = 1000;\n private $maxRetries = 3;\n private $timeout = 60;\n private $rateLimitDelay = 1; // Base delay in seconds\n\n\n // In your PHP API handler (e.g., api.php) Added by Asley in April 2025\n$apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n\n$response = $httpClient->post('https://api.deepseek.com/v1/chat/completions', [\n 'headers' => [\n 'Authorization' => 'Bearer ' . $apiKey,\n 'Content-Type' => 'application/json',\n ],\n 'json' => [\n 'model' => 'deepseek-chat',\n 'messages' => [['role' => 'user', 'content' => $userMessage]]\n]);\n//Ended\n\n\n\n public function __construct($apiKey = null, $connection = null, $container = null)\n {\n if ($apiKey === null) {\n // Try to get API key from Gibbon settings\n try {\n // Try to use container if available (preferred method)\n if (isset($container) && $container->has(SettingGateway::class)) {\n $settingGateway = $container->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty in settings');\n }\n }\n // Fallback to using provided database connection\n else if ($connection instanceof Connection) {\n $sql = \"SELECT value FROM gibbonSetting WHERE scope='ChatBot' AND name='deepseek_api_key'\";\n $result = $connection->select($sql);\n \n if (empty($result)) {\n throw new \\Exception('DeepSeek API key not found in settings');\n }\n \n $apiKey = $result[0]['value'];\n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty');\n }\n }\n // Last resort: try to get database connection from globals\n else if (isset($GLOBALS['container']) && $GLOBALS['container']->has(SettingGateway::class)) {\n $settingGateway = $GLOBALS['container']->get(SettingGateway::class);\n $apiKey = $settingGateway->getSettingByScope('ChatBot', 'deepseek_api_key');\n \n if (empty($apiKey)) {\n throw new \\Exception('DeepSeek API key is empty in settings');\n }\n } \n else {\n throw new \\Exception('No valid connection or container available');\n }\n } catch (\\Exception $e) {\n error_log('DeepSeekAPI Database Error: ' . $e->getMessage());\n throw new \\Exception('Failed to retrieve API key from database: ' . $e->getMessage());\n }\n }\n \n $this->apiKey = $apiKey;\n \n // Try to get model settings if available\n if (isset($container) && $container->has(SettingGateway::class)) {\n $settingGateway = $container->get(SettingGateway::class);\n $modelName = $settingGateway->getSettingByScope('ChatBot', 'model_name');\n $maxTokens = $settingGateway->getSettingByScope('ChatBot', 'max_tokens');\n \n if (!empty($modelName)) $this->modelName = $modelName;\n if (!empty($maxTokens)) $this->maxTokens = intval($maxTokens);\n }\n }\n\n /**\n * Get a response from the DeepSeek API with rate limiting and exponential backoff\n *\n * @param string $message The user's message\n * @param bool $isTrainingMode Whether training mode is enabled\n * @return array The AI's response\n * @throws \\Exception\n */\n public function getResponse($message, $isTrainingMode = false)\n {\n error_log('DeepSeekAPI: Preparing request for message: ' . substr($message, 0, 100) . '...');\n \n // Validate input\n if (empty($message)) {\n error_log('DeepSeekAPI Error: Empty message provided');\n throw new \\Exception('Message cannot be empty');\n }\n\n // Define system prompt based on mode\n $systemPrompt = $this->getSystemPrompt($isTrainingMode);\n\n // Prepare request data\n $data = [\n 'model' => $this->modelName,\n 'messages' => [\n ['role' => 'system', 'content' => $systemPrompt],\n ['role' => 'user', 'content' => $message]\n ],\n 'max_tokens' => $this->maxTokens,\n 'temperature' => $isTrainingMode ? 0.7 : 0.5,\n 'stream' => false\n ];\n\n error_log('DeepSeekAPI: Request data prepared: ' . json_encode($data));\n\n // Try up to maxRetries times with exponential backoff\n $lastError = null;\n for ($attempt = 1; $attempt <= $this->maxRetries; $attempt++) {\n try {\n error_log(\"DeepSeekAPI: Attempt $attempt of {$this->maxRetries}\");\n \n // Apply rate limiting delay if not first attempt\n if ($attempt > 1) {\n $delay = $this->rateLimitDelay * pow(2, $attempt - 1); // Exponential backoff\n error_log(\"DeepSeekAPI: Waiting {$delay} seconds before retry\");\n sleep($delay);\n }\n \n // Initialize cURL\n $ch = curl_init($this->apiEndpoint);\n if ($ch === false) {\n throw new \\Exception('Failed to initialize cURL');\n }\n\n // Set cURL options\n curl_setopt_array($ch, [\n CURLOPT_RETURNTRANSFER => true,\n CURLOPT_POST => true,\n CURLOPT_POSTFIELDS => json_encode($data),\n CURLOPT_HTTPHEADER => [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $this->apiKey\n ],\n CURLOPT_TIMEOUT => $this->timeout,\n CURLOPT_SSL_VERIFYPEER => true\n ]);\n\n error_log('DeepSeekAPI: Sending request to ' . $this->apiEndpoint);\n\n // Execute request\n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n $curlError = curl_error($ch);\n \n error_log('DeepSeekAPI: Response received - HTTP Code: ' . $httpCode);\n error_log('DeepSeekAPI: Raw response: ' . substr($response, 0, 1000));\n\n curl_close($ch);\n\n // Handle cURL errors\n if ($response === false) {\n throw new \\Exception('Failed to connect to AI service: ' . $curlError);\n }\n\n // Handle rate limiting\n if ($httpCode === 429) {\n $retryAfter = $this->getRetryAfterHeader($response) ?? $this->rateLimitDelay * pow(2, $attempt);\n error_log(\"DeepSeekAPI: Rate limited. Waiting {$retryAfter} seconds.\");\n sleep($retryAfter);\n continue;\n }\n\n // Handle other HTTP errors\n if ($httpCode !== 200) {\n $errorData = json_decode($response, true);\n $errorMessage = isset($errorData['error']['message']) ? $errorData['error']['message'] : 'Unknown error';\n throw new \\Exception('AI service error: ' . $errorMessage);\n }\n\n // Parse response\n $responseData = json_decode($response, true);\n if (json_last_error() !== JSON_ERROR_NONE) {\n throw new \\Exception('Invalid response from AI service: ' . json_last_error_msg());\n }\n\n // Extract message content\n if (!isset($responseData['choices'][0]['message']['content'])) {\n throw new \\Exception('Unexpected response format from AI service');\n }\n\n $content = $responseData['choices'][0]['message']['content'];\n error_log('DeepSeekAPI: Successfully received content: ' . substr($content, 0, 100) . '...');\n\n return [\n 'success' => true,\n 'answer' => $content,\n 'timestamp' => date('Y-m-d H:i:s'),\n 'usage' => $responseData['usage'] ?? null\n ];\n\n } catch (\\Exception $e) {\n $lastError = $e;\n error_log('DeepSeekAPI Error on attempt ' . $attempt . ': ' . $e->getMessage());\n \n // Don't retry on certain errors\n if ($this->isNonRetryableError($e)) {\n throw $e;\n }\n \n if ($attempt < $this->maxRetries) {\n continue;\n }\n }\n }\n\n // If we get here, all retries failed\n throw $lastError ?? new \\Exception('Failed to get response from AI service after ' . $this->maxRetries . ' attempts');\n }\n\n /**\n * Get the system prompt based on mode\n *\n * @param bool $trainingMode\n * @return string\n */\n private function getSystemPrompt($trainingMode)\n {\n if ($trainingMode) {\n return \"You are an AI teaching assistant trained to provide detailed, comprehensive educational guidance. \" .\n \"Focus on clear explanations, practical examples, and constructive feedback. \" .\n \"Use markdown formatting for better readability:\\n\" .\n \"- Use # for main headings\\n\" .\n \"- Use ## for subheadings\\n\" .\n \"- Use **bold** for emphasis\\n\" .\n \"- Use `code` for technical terms\\n\" .\n \"- Use numbered lists for steps\\n\" .\n \"- Use bullet points for key concepts\";\n }\n\n return \"You are a helpful AI teaching assistant. Provide clear, concise answers to educational questions. \" .\n \"Focus on being informative and supportive while maintaining a professional tone.\";\n }\n\n /**\n * Test the API connection\n *\n * @return bool\n * @throws \\Exception\n */\n public function testConnection()\n {\n try {\n // Check DNS resolution first\n $host = parse_url($this->apiEndpoint, PHP_URL_HOST);\n if (!$host) {\n throw new \\Exception('Invalid API endpoint URL');\n }\n\n $dnsCheck = dns_get_record($host, DNS_A);\n if (empty($dnsCheck)) {\n throw new \\Exception('Could not resolve API host');\n }\n\n error_log('DeepSeek API Test: DNS resolution successful for ' . $host);\n error_log('DeepSeek API Test: IP addresses - ' . implode(', ', array_column($dnsCheck, 'ip')));\n\n $response = $this->getResponse('Test connection');\n return true;\n } catch (\\Exception $e) {\n error_log('DeepSeek API Test Connection Failed: ' . $e->getMessage());\n throw new \\Exception('API connection test failed: ' . $e->getMessage());\n }\n }\n\n /**\n * Get retry-after value from response headers\n *\n * @param string $response The raw response\n * @return int|null The number of seconds to wait\n */\n private function getRetryAfterHeader($response)\n {\n $headers = [];\n $rawHeaders = substr($response, 0, strpos($response, \"\\r\\n\\r\\n\"));\n foreach (explode(\"\\r\\n\", $rawHeaders) as $line) {\n if (strpos($line, ':') !== false) {\n list($key, $value) = explode(':', $line, 2);\n $headers[strtolower(trim($key))] = trim($value);\n }\n }\n \n return isset($headers['retry-after']) ? (int)$headers['retry-after'] : null;\n }\n\n /**\n * Check if an error should not be retried\n *\n * @param \\Exception $error The error to check\n * @return bool\n */\n private function isNonRetryableError(\\Exception $error)\n {\n $nonRetryableMessages = [\n 'API key not found',\n 'Invalid API key',\n 'Permission denied',\n 'Invalid request format'\n ];\n \n foreach ($nonRetryableMessages as $message) {\n if (stripos($error->getMessage(), $message) !== false) {\n return true;\n }\n }\n \n return false;\n }\n}"}}, "modules/ChatBot/src/Domain/Recommendations/RecommendationGenerator.php": {"5eacf702b1d14d58114e1990619ea4913b19ff59": {"t": 1785746168.4523878, "n": 5848, "h": "2e2c52b295b18cfa", "c": "<?php\nnamespace Gibbon\\Module\\ChatBot\\Domain\\Recommendations;\n\nuse Gibbon\\Domain\\Students\\StudentGateway;\nuse Gibbon\\Domain\\Assessment\\InternalAssessmentGateway;\n\n/**\n * AI Recommendation Generator\n *\n * @version v23.0.00\n * @since v23.0.00\n */\nclass RecommendationGenerator\n{\n protected $studentGateway;\n protected $assessmentGateway;\n protected $connection;\n\n public function __construct($connection, StudentGateway $studentGateway, InternalAssessmentGateway $assessmentGateway)\n {\n $this->connection = $connection;\n $this->studentGateway = $studentGateway;\n $this->assessmentGateway = $assessmentGateway;\n }\n\n /**\n * Generate recommendations for a student based on their assessment data\n *\n * @param string $gibbonPersonID\n * @return array\n */\n public function generateRecommendations($gibbonPersonID)\n {\n // Get student's assessment data\n $criteria = $this->assessmentGateway->newQueryCriteria()\n ->sortBy(['date', 'DESC']);\n\n $assessments = $this->assessmentGateway->queryAssessmentsByStudent($criteria, $gibbonPersonID);\n \n $recommendations = [];\n foreach ($assessments as $assessment) {\n $recommendation = $this->generateSingleRecommendation($assessment);\n if ($recommendation) {\n $recommendations[] = [\n 'assessment' => $assessment,\n 'recommendation' => $recommendation\n ];\n \n // Store the recommendation\n $this->storeRecommendation($gibbonPersonID, $assessment, $recommendation);\n }\n }\n \n return $recommendations;\n }\n\n /**\n * Generate a recommendation for a single assessment\n *\n * @param array $assessment\n * @return string|null\n */\n protected function generateSingleRecommendation($assessment)\n {\n // Get the API key\n $apiKey = $this->getSecureAPIKey();\n if (empty($apiKey)) {\n return null;\n }\n \n // Prepare the prompt for the AI\n $prompt = $this->preparePrompt($assessment);\n \n try {\n // Call the DeepSeek API\n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n $data = [\n 'model' => 'deepseek-v4-flash',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (\\Exception $e) {\n return null;\n }\n \n return null;\n }\n\n /**\n * Prepare the AI prompt based on assessment data\n *\n * @param array $assessment\n * @return string\n */\n protected function preparePrompt($assessment)\n {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n return $prompt;\n }\n\n /**\n * Store a recommendation in the database\n *\n * @param string $gibbonPersonID\n * @param array $assessment\n * @param string $recommendation\n * @return bool\n */\n protected function storeRecommendation($gibbonPersonID, $assessment, $recommendation)\n {\n try {\n $data = [\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $assessment['gibbonCourseClassID'],\n 'assessmentID' => $assessment['assessmentID'],\n 'recommendation' => $recommendation\n ];\n \n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $stmt = $this->connection->prepare($sql);\n return $stmt->execute($data);\n } catch (\\Exception $e) {\n return false;\n }\n }\n\n /**\n * Get the secure API key\n *\n * @return string|null\n */\n protected function getSecureAPIKey()\n {\n return getSecureAPIKey($this->connection);\n }\n} "}, "228065060bc341b101409b1b5a4415a502b48a3a": {"t": 1785756292.1091142, "n": 5848, "h": "2e2c52b295b18cfa", "c": "<?php\nnamespace Gibbon\\Module\\ChatBot\\Domain\\Recommendations;\n\nuse Gibbon\\Domain\\Students\\StudentGateway;\nuse Gibbon\\Domain\\Assessment\\InternalAssessmentGateway;\n\n/**\n * AI Recommendation Generator\n *\n * @version v23.0.00\n * @since v23.0.00\n */\nclass RecommendationGenerator\n{\n protected $studentGateway;\n protected $assessmentGateway;\n protected $connection;\n\n public function __construct($connection, StudentGateway $studentGateway, InternalAssessmentGateway $assessmentGateway)\n {\n $this->connection = $connection;\n $this->studentGateway = $studentGateway;\n $this->assessmentGateway = $assessmentGateway;\n }\n\n /**\n * Generate recommendations for a student based on their assessment data\n *\n * @param string $gibbonPersonID\n * @return array\n */\n public function generateRecommendations($gibbonPersonID)\n {\n // Get student's assessment data\n $criteria = $this->assessmentGateway->newQueryCriteria()\n ->sortBy(['date', 'DESC']);\n\n $assessments = $this->assessmentGateway->queryAssessmentsByStudent($criteria, $gibbonPersonID);\n \n $recommendations = [];\n foreach ($assessments as $assessment) {\n $recommendation = $this->generateSingleRecommendation($assessment);\n if ($recommendation) {\n $recommendations[] = [\n 'assessment' => $assessment,\n 'recommendation' => $recommendation\n ];\n \n // Store the recommendation\n $this->storeRecommendation($gibbonPersonID, $assessment, $recommendation);\n }\n }\n \n return $recommendations;\n }\n\n /**\n * Generate a recommendation for a single assessment\n *\n * @param array $assessment\n * @return string|null\n */\n protected function generateSingleRecommendation($assessment)\n {\n // Get the API key\n $apiKey = $this->getSecureAPIKey();\n if (empty($apiKey)) {\n return null;\n }\n \n // Prepare the prompt for the AI\n $prompt = $this->preparePrompt($assessment);\n \n try {\n // Call the DeepSeek API\n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n $data = [\n 'model' => 'deepseek-v4-flash',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (\\Exception $e) {\n return null;\n }\n \n return null;\n }\n\n /**\n * Prepare the AI prompt based on assessment data\n *\n * @param array $assessment\n * @return string\n */\n protected function preparePrompt($assessment)\n {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n return $prompt;\n }\n\n /**\n * Store a recommendation in the database\n *\n * @param string $gibbonPersonID\n * @param array $assessment\n * @param string $recommendation\n * @return bool\n */\n protected function storeRecommendation($gibbonPersonID, $assessment, $recommendation)\n {\n try {\n $data = [\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $assessment['gibbonCourseClassID'],\n 'assessmentID' => $assessment['assessmentID'],\n 'recommendation' => $recommendation\n ];\n \n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $stmt = $this->connection->prepare($sql);\n return $stmt->execute($data);\n } catch (\\Exception $e) {\n return false;\n }\n }\n\n /**\n * Get the secure API key\n *\n * @return string|null\n */\n protected function getSecureAPIKey()\n {\n return getSecureAPIKey($this->connection);\n }\n} "}, "030e63497fd5a0ea218107feedd91d649324421f": {"t": 1785756296.986118, "n": 5844, "h": "2f8a6179b72e8e95", "c": "<?php\nnamespace Gibbon\\Module\\ChatBot\\Domain\\Recommendations;\n\nuse Gibbon\\Domain\\Students\\StudentGateway;\nuse Gibbon\\Domain\\Assessment\\InternalAssessmentGateway;\n\n/**\n * AI Recommendation Generator\n *\n * @version v23.0.00\n * @since v23.0.00\n */\nclass RecommendationGenerator\n{\n protected $studentGateway;\n protected $assessmentGateway;\n protected $connection;\n\n public function __construct($connection, StudentGateway $studentGateway, InternalAssessmentGateway $assessmentGateway)\n {\n $this->connection = $connection;\n $this->studentGateway = $studentGateway;\n $this->assessmentGateway = $assessmentGateway;\n }\n\n /**\n * Generate recommendations for a student based on their assessment data\n *\n * @param string $gibbonPersonID\n * @return array\n */\n public function generateRecommendations($gibbonPersonID)\n {\n // Get student's assessment data\n $criteria = $this->assessmentGateway->newQueryCriteria()\n ->sortBy(['date', 'DESC']);\n\n $assessments = $this->assessmentGateway->queryAssessmentsByStudent($criteria, $gibbonPersonID);\n \n $recommendations = [];\n foreach ($assessments as $assessment) {\n $recommendation = $this->generateSingleRecommendation($assessment);\n if ($recommendation) {\n $recommendations[] = [\n 'assessment' => $assessment,\n 'recommendation' => $recommendation\n ];\n \n // Store the recommendation\n $this->storeRecommendation($gibbonPersonID, $assessment, $recommendation);\n }\n }\n \n return $recommendations;\n }\n\n /**\n * Generate a recommendation for a single assessment\n *\n * @param array $assessment\n * @return string|null\n */\n protected function generateSingleRecommendation($assessment)\n {\n // Get the API key\n $apiKey = $this->getSecureAPIKey();\n if (empty($apiKey)) {\n return null;\n }\n \n // Prepare the prompt for the AI\n $prompt = $this->preparePrompt($assessment);\n \n try {\n // Call the DeepSeek API\n $ch = curl_init();\n curl_setopt($ch, CURLOPT_URL, 'https://api.deepseek.com/v1/chat/completions');\n curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);\n curl_setopt($ch, CURLOPT_POST, true);\n curl_setopt($ch, CURLOPT_HTTPHEADER, [\n 'Content-Type: application/json',\n 'Authorization: Bearer ' . $apiKey\n ]);\n \n $data = [\n 'model' => 'deepseek-chat',\n 'messages' => [\n [\n 'role' => 'system',\n 'content' => 'You are an educational AI assistant providing specific, actionable recommendations for academic improvement based on assessment data and teacher feedback.'\n ],\n [\n 'role' => 'user',\n 'content' => $prompt\n ]\n ],\n 'max_tokens' => 500\n ];\n \n curl_setopt($ch, CURLOPT_POSTFIELDS, json_encode($data));\n \n $response = curl_exec($ch);\n $httpCode = curl_getinfo($ch, CURLINFO_HTTP_CODE);\n curl_close($ch);\n \n if ($httpCode === 200) {\n $result = json_decode($response, true);\n return $result['choices'][0]['message']['content'];\n }\n } catch (\\Exception $e) {\n return null;\n }\n \n return null;\n }\n\n /**\n * Prepare the AI prompt based on assessment data\n *\n * @param array $assessment\n * @return string\n */\n protected function preparePrompt($assessment)\n {\n $prompt = \"Based on the following assessment data:\\n\";\n $prompt .= \"Subject: {$assessment['subject']}\\n\";\n $prompt .= \"Grade: {$assessment['grade']}\\n\";\n $prompt .= \"Teacher Feedback: {$assessment['feedback']}\\n\\n\";\n $prompt .= \"Please provide specific, actionable recommendations for improvement in this subject. Consider:\\n\";\n $prompt .= \"1. Study strategies\\n\";\n $prompt .= \"2. Practice exercises\\n\";\n $prompt .= \"3. Additional resources\\n\";\n $prompt .= \"4. Areas needing focus based on the assessment\\n\";\n $prompt .= \"5. Time management suggestions\";\n \n return $prompt;\n }\n\n /**\n * Store a recommendation in the database\n *\n * @param string $gibbonPersonID\n * @param array $assessment\n * @param string $recommendation\n * @return bool\n */\n protected function storeRecommendation($gibbonPersonID, $assessment, $recommendation)\n {\n try {\n $data = [\n 'gibbonPersonID' => $gibbonPersonID,\n 'gibbonCourseClassID' => $assessment['gibbonCourseClassID'],\n 'assessmentID' => $assessment['assessmentID'],\n 'recommendation' => $recommendation\n ];\n \n $sql = \"INSERT INTO gibbonChatBotRecommendations \n (gibbonPersonID, gibbonCourseClassID, assessmentID, recommendation) \n VALUES \n (:gibbonPersonID, :gibbonCourseClassID, :assessmentID, :recommendation)\";\n \n $stmt = $this->connection->prepare($sql);\n return $stmt->execute($data);\n } catch (\\Exception $e) {\n return false;\n }\n }\n\n /**\n * Get the secure API key\n *\n * @return string|null\n */\n protected function getSecureAPIKey()\n {\n return getSecureAPIKey($this->connection);\n }\n} "}}}}