diff --git a/web/include/db_info.inc.php b/web/include/db_info.inc.php index 8ee3d87..251fe61 100644 --- a/web/include/db_info.inc.php +++ b/web/include/db_info.inc.php @@ -145,6 +145,15 @@ static $OJ_LOG_TRACE_ENABLED=false; static $OJ_SaaS_ENABLE=false; static $OJ_MENU_NEWS=true; +/* LLM AI Review - AI错误点评功能 */ +static $OJ_LLM_ENABLED=false; // 总开关,设为true开启AI点评 +static $OJ_LLM_API_URL="https://api.openai.com/v1/chat/completions"; // API地址(兼容OpenAI格式) +static $OJ_LLM_API_KEY=""; // 填入你的API Key +static $OJ_LLM_MODEL="gpt-4o-mini"; // 模型名称 +static $OJ_LLM_MAX_TOKENS=1024; // 最大输出token数 +static $OJ_LLM_TIMEOUT=30; // 请求超时(秒) +static $OJ_LLM_SYSTEM_PROMPT=""; // 自定义系统prompt,留空使用内置默认 + require_once(dirname(__FILE__) . "/pdo.php"); require_once(dirname(__FILE__) . "/init.php"); diff --git a/web/llm-review.php b/web/llm-review.php new file mode 100644 index 0000000..7f1c73e --- /dev/null +++ b/web/llm-review.php @@ -0,0 +1,212 @@ + "AI点评功能未开启"], JSON_UNESCAPED_UNICODE); + exit; +} + +// 检查登录 +if (!isset($_SESSION[$OJ_NAME . '_user_id'])) { + echo json_encode(["error" => "请先登录"], JSON_UNESCAPED_UNICODE); + exit; +} + +// 获取 solution_id +if (!isset($_GET['sid'])) { + echo json_encode(["error" => "缺少参数 sid"], JSON_UNESCAPED_UNICODE); + exit; +} +$sid = intval($_GET['sid']); +if ($sid <= 0) { + echo json_encode(["error" => "无效的 solution_id"], JSON_UNESCAPED_UNICODE); + exit; +} + +// ---- 1. 检查缓存 ---- +$cache = pdo_query("SELECT `review` FROM `llm_review` WHERE `solution_id`=?", $sid); +if ($cache !== -1 && !empty($cache)) { + echo json_encode(["review" => $cache[0]['review']], JSON_UNESCAPED_UNICODE); + exit; +} + +// ---- 2. 查询 solution 信息 ---- +$solution = pdo_query("SELECT * FROM `solution` WHERE `solution_id`=?", $sid); +if (empty($solution)) { + echo json_encode(["error" => "找不到该提交记录"], JSON_UNESCAPED_UNICODE); + exit; +} +$sol = $solution[0]; +$problem_id = intval($sol['problem_id']); +$language = intval($sol['language']); +$result_code = intval($sol['result']); +$pass_rate = floatval($sol['pass_rate']) * 100; +$lang_name = isset($language_name[$language]) ? $language_name[$language] : "unknown"; + +// ---- 3. 查询学生源码 ---- +$src = pdo_query("SELECT `source` FROM `source_code_user` WHERE `solution_id`=?", $sid); +$student_code = (!empty($src) && $src !== -1) ? $src[0]['source'] : ""; + +// ---- 4. 查询题目信息 ---- +$prob = pdo_query("SELECT `title`,`description`,`input`,`output`,`sample_input`,`sample_output`,`hint` FROM `problem` WHERE `problem_id`=?", $problem_id); +if (empty($prob) || $prob === -1) { + echo json_encode(["error" => "找不到该题目"], JSON_UNESCAPED_UNICODE); + exit; +} +$p = $prob[0]; + +// ---- 5. 查询第一个 AC 代码(同语言) ---- +$first_ac = pdo_query( + "SELECT `solution_id` FROM `solution` WHERE `problem_id`=? AND `result`=4 AND `language`=? ORDER BY `in_date` ASC LIMIT 1", + $problem_id, $language +); +$reference_code = ""; +if (!empty($first_ac) && $first_ac !== -1) { + $ref_src = pdo_query("SELECT `source` FROM `source_code_user` WHERE `solution_id`=?", intval($first_ac[0]['solution_id'])); + if (!empty($ref_src) && $ref_src !== -1) { + $reference_code = $ref_src[0]['source']; + } +} + +// ---- 6. 查询 diff/error 信息 ---- +$diff_info = ""; +if ($result_code == 11) { + // 编译错误 + $err = pdo_query("SELECT `error` FROM `compileinfo` WHERE `solution_id`=?", $sid); + if (!empty($err) && $err !== -1) { + $diff_info = $err[0]['error']; + } +} else { + // 运行时/WA 等 + $err = pdo_query("SELECT `error` FROM `runtimeinfo` WHERE `solution_id`=?", $sid); + if (!empty($err) && $err !== -1) { + $diff_info = $err[0]['error']; + } +} + +// ---- 7. 组装 Prompt ---- +$system_prompt = isset($OJ_LLM_SYSTEM_PROMPT) && $OJ_LLM_SYSTEM_PROMPT !== "" + ? $OJ_LLM_SYSTEM_PROMPT + : << "API Key未配置"], JSON_UNESCAPED_UNICODE); + exit; +} + +$request_body = json_encode([ + "model" => $model, + "messages" => [ + ["role" => "system", "content" => $system_prompt], + ["role" => "user", "content" => $user_prompt] + ], + "max_tokens" => $max_tokens, + "temperature" => 0.7 +]); + +$ch = curl_init($api_url); +curl_setopt_array($ch, [ + CURLOPT_POST => true, + CURLOPT_POSTFIELDS => $request_body, + CURLOPT_HTTPHEADER => [ + "Content-Type: application/json", + "Authorization: Bearer " . $api_key + ], + CURLOPT_RETURNTRANSFER => true, + CURLOPT_TIMEOUT => $timeout, + CURLOPT_SSL_VERIFYPEER => true +]); + +$response = curl_exec($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$curl_error = curl_error($ch); +curl_close($ch); + +if ($response === false) { + echo json_encode(["error" => "网络请求失败: " . $curl_error], JSON_UNESCAPED_UNICODE); + exit; +} + +if ($http_code !== 200) { + echo json_encode(["error" => "API请求失败 (HTTP $http_code)"], JSON_UNESCAPED_UNICODE); + exit; +} + +$resp_data = json_decode($response, true); +if (!$resp_data || !isset($resp_data['choices'][0]['message']['content'])) { + echo json_encode(["error" => "API返回格式异常"], JSON_UNESCAPED_UNICODE); + exit; +} + +$review_text = $resp_data['choices'][0]['message']['content']; + +// ---- 9. 缓存结果 ---- +// 自动建表(首次调用时) +pdo_query("CREATE TABLE IF NOT EXISTS `llm_review` ( + `solution_id` INT NOT NULL, + `review` TEXT NOT NULL, + `create_time` DATETIME NOT NULL, + PRIMARY KEY (`solution_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + +pdo_query("INSERT INTO `llm_review` (`solution_id`, `review`, `create_time`) VALUES (?, ?, NOW())", $sid, $review_text); + +// ---- 10. 返回结果 ---- +echo json_encode(["review" => $review_text], JSON_UNESCAPED_UNICODE); diff --git a/web/template/syzoj/reinfo.php b/web/template/syzoj/reinfo.php index cf19225..810360b 100644 --- a/web/template/syzoj/reinfo.php +++ b/web/template/syzoj/reinfo.php @@ -177,5 +177,80 @@ + +
+
+
+ 🤖 AI 点评 +
+
+
+ + + + +
+
+
+
+ +