diff --git a/core/sim/sim_3_01/clang.c b/core/sim/sim_3_01/clang.c new file mode 100644 index 0000000..e69de29 diff --git a/web/include/db_info.inc.php b/web/include/db_info.inc.php index 330eeff..24abc85 100644 --- a/web/include/db_info.inc.php +++ b/web/include/db_info.inc.php @@ -146,7 +146,7 @@ static $OJ_SaaS_ENABLE=false; static $OJ_MENU_NEWS=true; /* LLM AI Review - AI错误点评功能 */ -static $OJ_LLM_ENABLED=false; // 总开关,设为true开启AI点评 +static $OJ_LLM_ENABLED=true; // 总开关,设为true开启AI点评 static $OJ_LLM_API_URL="https://api.minimaxi.com/anthropic/v1/messages"; // API地址(MiniMax Anthropic兼容格式) static $OJ_LLM_API_KEY="sk-cp-w571CJpA_6WP5P6b_xGVbjOqXOGBWTQsiNY9xxAPambouIhD7ic3GHIoWce0ON_Y7Q0rVLzBWtgwwS8cQVUxS1guBxYkPJoJ33q0jEcx0Qei60bx5La4fB4"; // 填入你的MiniMax API Key static $OJ_LLM_MODEL="MiniMax-M3"; // 模型名称,如 MiniMax-M3 / MiniMax-M2.7 / MiniMax-M2.5 / MiniMax-M2.5-highspeed diff --git a/web/llm-chat-api.php b/web/llm-chat-api.php new file mode 100644 index 0000000..9427969 --- /dev/null +++ b/web/llm-chat-api.php @@ -0,0 +1,275 @@ + $msg]); +} + +// ---- 基础校验 ---- +if (!isset($OJ_LLM_ENABLED) || !$OJ_LLM_ENABLED) { + header("Content-Type: application/json; charset=utf-8"); + echo json_encode(["error" => "AI聊天功能未开启"], JSON_UNESCAPED_UNICODE); + exit; +} + +session_start(); +if (!isset($_SESSION[$OJ_NAME . '_user_id'])) { + header("Content-Type: application/json; charset=utf-8"); + echo json_encode(["error" => "请先登录"], JSON_UNESCAPED_UNICODE); + exit; +} + +$user_id = $_SESSION[$OJ_NAME . '_user_id']; + +// ---- 自动建表 ---- +pdo_query("CREATE TABLE IF NOT EXISTS `llm_chat_session` ( + `session_id` INT AUTO_INCREMENT PRIMARY KEY, + `user_id` VARCHAR(48) NOT NULL, + `title` VARCHAR(200) NOT NULL DEFAULT '新对话', + `create_time` DATETIME NOT NULL, + `update_time` DATETIME NOT NULL, + INDEX `idx_user` (`user_id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + +pdo_query("CREATE TABLE IF NOT EXISTS `llm_chat_message` ( + `id` INT AUTO_INCREMENT PRIMARY KEY, + `session_id` INT NOT NULL, + `role` VARCHAR(20) NOT NULL, + `content` TEXT NOT NULL, + `create_time` DATETIME NOT NULL, + INDEX `idx_session` (`session_id`), + INDEX `idx_session_role` (`session_id`, `id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;"); + +// ---- GET 请求 ---- +if ($_SERVER['REQUEST_METHOD'] === 'GET') { + header("Content-Type: application/json; charset=utf-8"); + $action = isset($_GET['action']) ? $_GET['action'] : ''; + + if ($action === 'sessions') { + $sessions = pdo_query( + "SELECT `session_id`, `title`, `create_time`, `update_time` FROM `llm_chat_session` WHERE `user_id`=? ORDER BY `update_time` DESC", + $user_id + ); + echo json_encode(["sessions" => $sessions !== -1 ? $sessions : []], JSON_UNESCAPED_UNICODE); + + } elseif ($action === 'messages') { + $session_id = isset($_GET['session_id']) ? intval($_GET['session_id']) : 0; + if ($session_id <= 0) { echo json_encode(["error" => "无效的会话ID"]); exit; } + + $check = pdo_query("SELECT 1 FROM `llm_chat_session` WHERE `session_id`=? AND `user_id`=?", $session_id, $user_id); + if (empty($check) || $check === -1) { echo json_encode(["error" => "无权访问"]); exit; } + + $messages = pdo_query("SELECT `role`, `content` FROM `llm_chat_message` WHERE `session_id`=? ORDER BY `id` ASC", $session_id); + echo json_encode(["messages" => $messages !== -1 ? $messages : []], JSON_UNESCAPED_UNICODE); + + } elseif ($action === 'last_session') { + $last = pdo_query("SELECT `session_id` FROM `llm_chat_session` WHERE `user_id`=? ORDER BY `update_time` DESC LIMIT 1", $user_id); + echo json_encode(["session_id" => (!empty($last) && $last !== -1) ? intval($last[0]['session_id']) : 0], JSON_UNESCAPED_UNICODE); + } + exit; +} + +// ---- DELETE 请求 ---- +if ($_SERVER['REQUEST_METHOD'] === 'DELETE') { + header("Content-Type: application/json; charset=utf-8"); + $input = json_decode(file_get_contents('php://input'), true); + $session_id = isset($input['session_id']) ? intval($input['session_id']) : 0; + if ($session_id <= 0) { echo json_encode(["error" => "无效的会话ID"]); exit; } + + $check = pdo_query("SELECT 1 FROM `llm_chat_session` WHERE `session_id`=? AND `user_id`=?", $session_id, $user_id); + if (empty($check) || $check === -1) { echo json_encode(["error" => "无权删除"]); exit; } + + pdo_query("DELETE FROM `llm_chat_message` WHERE `session_id`=?", $session_id); + pdo_query("DELETE FROM `llm_chat_session` WHERE `session_id`=?", $session_id); + echo json_encode(["success" => true], JSON_UNESCAPED_UNICODE); + exit; +} + +// ---- POST 请求 ---- +$input = json_decode(file_get_contents('php://input'), true); +if (!$input) { header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "无效请求"]); exit; } + +$action = isset($input['action']) ? $input['action'] : 'chat'; + +// 创建新会话 +if ($action === 'create_session') { + header("Content-Type: application/json; charset=utf-8"); + $title = isset($input['title']) ? trim($input['title']) : '新对话'; + $now = date('Y-m-d H:i:s'); + $session_id = pdo_query("INSERT INTO `llm_chat_session` (`user_id`, `title`, `create_time`, `update_time`) VALUES (?, ?, ?, ?)", $user_id, $title, $now, $now); + echo json_encode(["session_id" => $session_id, "title" => $title], JSON_UNESCAPED_UNICODE); + exit; +} + +// 重命名会话 +if ($action === 'rename_session') { + header("Content-Type: application/json; charset=utf-8"); + $session_id = isset($input['session_id']) ? intval($input['session_id']) : 0; + $title = isset($input['title']) ? trim($input['title']) : ''; + if ($session_id <= 0 || empty($title)) { echo json_encode(["error" => "参数错误"]); exit; } + pdo_query("UPDATE `llm_chat_session` SET `title`=? WHERE `session_id`=? AND `user_id`=?", $title, $session_id, $user_id); + echo json_encode(["success" => true], JSON_UNESCAPED_UNICODE); + exit; +} + +// ---- 聊天(流式SSE) ---- +$message = isset($input['message']) ? trim($input['message']) : ''; +$session_id = isset($input['session_id']) ? intval($input['session_id']) : 0; + +if (empty($message)) { header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "消息不能为空"]); exit; } + +// 自动创建会话 +if ($session_id <= 0) { + $now = date('Y-m-d H:i:s'); + $auto_title = mb_substr($message, 0, 20, 'UTF-8'); + if (mb_strlen($message, 'UTF-8') > 20) $auto_title .= '...'; + $session_id = pdo_query("INSERT INTO `llm_chat_session` (`user_id`, `title`, `create_time`, `update_time`) VALUES (?, ?, ?, ?)", $user_id, $auto_title, $now, $now); +} + +// 验证归属 +$check = pdo_query("SELECT 1 FROM `llm_chat_session` WHERE `session_id`=? AND `user_id`=?", $session_id, $user_id); +if (empty($check) || $check === -1) { header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "无权访问"]); exit; } + +// 保存用户消息 +$now = date('Y-m-d H:i:s'); +pdo_query("INSERT INTO `llm_chat_message` (`session_id`, `role`, `content`, `create_time`) VALUES (?, 'user', ?, ?)", $session_id, $message, $now); +pdo_query("UPDATE `llm_chat_session` SET `update_time`=? WHERE `session_id`=?", $now, $session_id); + +// 获取历史(最近10条上下文) +$history = pdo_query("SELECT `role`, `content` FROM `llm_chat_message` WHERE `session_id`=? ORDER BY `id` DESC LIMIT 11", $session_id); +$messages = []; +if ($history !== -1 && !empty($history)) { + $history = array_reverse($history); + $recent = array_slice($history, 0, -1); + $recent = array_slice($recent, -10); + foreach ($recent as $msg) { + $messages[] = ['role' => $msg['role'], 'content' => $msg['content']]; + } +} +$messages[] = ['role' => 'user', 'content' => $message]; + +// ---- SSE 响应头 ---- +header("Content-Type: text/event-stream; charset=utf-8"); +header("Cache-Control: no-cache"); +header("X-Accel-Buffering: no"); +if (function_exists('apache_setenv')) @apache_setenv('no-gzip', '1'); +@ini_set('zlib.output_compression', 0); +@ini_set('implicit_flush', 1); +while (ob_get_level()) ob_end_flush(); + +sse_send("session", ["session_id" => $session_id]); + +// ---- 系统提示词 ---- +$system_prompt = <<<'EOP' +你是一位名叫"MiniMax老师"的AI助教,正在与学生进行日常对话。 + +你的职责: +1. 用亲切、友好的语气和学生交流 +2. 回答学生的各种问题,包括但不限于学习、生活、编程概念等 +3. 提供有帮助的信息和建议 +4. 使用中文回答 + +严格禁止的行为: +1. 绝对不要帮助学生解答任何编程题目、算法题、数据结构题等OJ题目 +2. 绝对不要提供任何题目的解题思路、代码实现或算法分析 +3. 如果学生询问关于具体OJ题目的问题,请礼貌地拒绝并解释你需要自己思考来完成题目 +4. 不要帮助学生完成任何看起来像是作业或考试的编程任务 + +你可以做的事情: +- 解释编程概念和原理(不针对具体题目) +- 讨论学习方法和技巧 +- 回答日常生活问题 +- 聊天闲谈 +- 其他与做题无关的问题 +EOP; + +$api_url = isset($OJ_LLM_API_URL) ? $OJ_LLM_API_URL : "https://api.minimaxi.com/anthropic/v1/messages"; +$api_key = isset($OJ_LLM_API_KEY) ? $OJ_LLM_API_KEY : ""; +$model = isset($OJ_LLM_MODEL) ? $OJ_LLM_MODEL : "MiniMax-M2.7"; +$max_tokens = isset($OJ_LLM_MAX_TOKENS) ? intval($OJ_LLM_MAX_TOKENS) : 2048; +$timeout = isset($OJ_LLM_TIMEOUT) ? intval($OJ_LLM_TIMEOUT) : 120; + +if (empty($api_key)) { sse_error("API Key未配置"); exit; } + +$request_body = json_encode([ + "model" => $model, "max_tokens" => $max_tokens, "system" => $system_prompt, + "temperature" => 0.8, "stream" => true, "messages" => $messages +]); + +// ---- 流式转发 ---- +$GLOBALS['_llm_alive'] = true; +$GLOBALS['_llm_full_text'] = ""; +$GLOBALS['_llm_buffer'] = ""; +register_shutdown_function(function() { $GLOBALS['_llm_alive'] = false; }); + +$ch = curl_init($api_url); +curl_setopt_array($ch, [ + CURLOPT_POST => true, CURLOPT_POSTFIELDS => $request_body, + CURLOPT_HTTPHEADER => ["Content-Type: application/json", "x-api-key: " . $api_key, "anthropic-version: 2023-06-01"], + CURLOPT_RETURNTRANSFER => false, CURLOPT_TIMEOUT => $timeout, CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_WRITEFUNCTION => function($ch, $chunk) use (&$_llm_alive, &$_llm_full_text, &$_llm_buffer) { + if (!$_llm_alive) return 0; + $_llm_buffer .= $chunk; + while (($pos = strpos($_llm_buffer, "\n")) !== false) { + $line = substr($_llm_buffer, 0, $pos); + $_llm_buffer = substr($_llm_buffer, $pos + 1); + $line = trim($line); + if (empty($line)) continue; + if (strncmp($line, "data: ", 6) === 0) { + $json_str = substr($line, 6); + if ($json_str === "[DONE]") continue; + $data = json_decode($json_str, true); + if (!$data) continue; + $type = isset($data['type']) ? $data['type'] : ''; + if ($type === 'content_block_delta' && isset($data['delta']['text'])) { + $_llm_full_text .= $data['delta']['text']; + sse_send("chunk", ["text" => $data['delta']['text']]); + } elseif ($type === 'message_delta' && isset($data['delta']['stop_reason'])) { + if (in_array($data['delta']['stop_reason'], ['end_turn', 'max_tokens'])) { + sse_send("done", []); + return 0; + } + } elseif (in_array($type, ['message_start', 'ping', 'content_block_start', 'content_block_stop'])) { + continue; + } elseif ($type === 'error') { + sse_send("error", ["message" => "API错误: " . (isset($data['error']['message']) ? $data['error']['message'] : '未知')]); + return 0; + } + } + } + return strlen($chunk); + } +]); + +$result = curl_exec($ch); +$http_code = curl_getinfo($ch, CURLINFO_HTTP_CODE); +$curl_error = curl_error($ch); +curl_close($ch); + +// 保存AI回复 +if (!empty($_llm_full_text)) { + $save_now = date('Y-m-d H:i:s'); + pdo_query("INSERT INTO `llm_chat_message` (`session_id`, `role`, `content`, `create_time`) VALUES (?, 'assistant', ?, ?)", $session_id, $_llm_full_text, $save_now); +} + +if (!$_llm_alive) exit; +if ($result === false && empty($_llm_full_text)) { sse_error("网络请求失败: " . ($curl_error ?: "未知")); exit; } +if (empty($_llm_full_text) && $http_code !== 200) { sse_error("API请求失败 (HTTP $http_code)"); exit; } +if (empty($_llm_full_text)) { sse_error("API返回为空"); exit; } +?> diff --git a/web/llm-chat.php b/web/llm-chat.php new file mode 100644 index 0000000..9fb0cb5 --- /dev/null +++ b/web/llm-chat.php @@ -0,0 +1,515 @@ + + + + +
+ +
+ +
+
+ + +
+
+
M
+
+

MiniMax老师

+

AI助教 · 随时为你解答

+
+
+ +
+
+ +

你好,欢迎使用AI聊天

+

选择一个对话或创建新对话开始聊天

+
+
+ +
+ + +
+
+
+ + + + + diff --git a/web/llm-review.php b/web/llm-review.php index 90c04fc..7fbef21 100644 --- a/web/llm-review.php +++ b/web/llm-review.php @@ -1,50 +1,89 @@ $msg]); +} + +// ---- 基础校验 ---- if (!isset($OJ_LLM_ENABLED) || !$OJ_LLM_ENABLED) { + // 返回 JSON(非 SSE),因为前端可能用 $.ajax 调用 + header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "AI点评功能未开启"], JSON_UNESCAPED_UNICODE); exit; } // 检查登录 +session_start(); if (!isset($_SESSION[$OJ_NAME . '_user_id'])) { + header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "请先登录"], JSON_UNESCAPED_UNICODE); exit; } // 获取 solution_id if (!isset($_GET['sid'])) { + header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "缺少参数 sid"], JSON_UNESCAPED_UNICODE); exit; } $sid = intval($_GET['sid']); if ($sid <= 0) { + header("Content-Type: application/json; charset=utf-8"); echo json_encode(["error" => "无效的 solution_id"], JSON_UNESCAPED_UNICODE); exit; } // ---- 1. 检查缓存 ---- +// 自动建表(首次调用时) +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;"); + $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); + // 有缓存,走 SSE 立刻返回(前端统一用 SSE 接收) + header("Content-Type: text/event-stream; charset=utf-8"); + header("Cache-Control: no-cache"); + header("X-Accel-Buffering: no"); + sse_send("cached", ["text" => $cache[0]['review']]); + sse_send("done", []); exit; } +// ---- 设置 SSE 响应头 ---- +header("Content-Type: text/event-stream; charset=utf-8"); +header("Cache-Control: no-cache"); +header("X-Accel-Buffering: no"); +if (function_exists('apache_setenv')) { + @apache_setenv('no-gzip', '1'); +} +@ini_set('zlib.output_compression', 0); +@ini_set('implicit_flush', 1); +while (ob_get_level()) ob_end_flush(); + // ---- 2. 查询 solution 信息 ---- $solution = pdo_query("SELECT * FROM `solution` WHERE `solution_id`=?", $sid); if (empty($solution)) { - echo json_encode(["error" => "找不到该提交记录"], JSON_UNESCAPED_UNICODE); + sse_error("找不到该提交记录"); exit; } $sol = $solution[0]; @@ -61,7 +100,7 @@ $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); + sse_error("找不到该题目"); exit; } $p = $prob[0]; @@ -82,13 +121,11 @@ if (!empty($first_ac) && $first_ac !== -1) { // ---- 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']; @@ -139,7 +176,7 @@ if (!empty($reference_code)) { $user_prompt .= "\n请分析学生的错误,给出启发性的提示和引导,帮助学生自己发现问题并改正。\n"; -// ---- 8. 调用 LLM API ---- +// ---- 8. 流式调用 LLM API ---- $api_url = isset($OJ_LLM_API_URL) ? $OJ_LLM_API_URL : "https://api.minimaxi.com/anthropic/v1/messages"; $api_key = isset($OJ_LLM_API_KEY) ? $OJ_LLM_API_KEY : ""; $model = isset($OJ_LLM_MODEL) ? $OJ_LLM_MODEL : "MiniMax-M2.5"; @@ -147,21 +184,33 @@ $max_tokens = isset($OJ_LLM_MAX_TOKENS) ? intval($OJ_LLM_MAX_TOKENS) : 1024; $timeout = isset($OJ_LLM_TIMEOUT) ? intval($OJ_LLM_TIMEOUT) : 60; if (empty($api_key)) { - echo json_encode(["error" => "API Key未配置"], JSON_UNESCAPED_UNICODE); + sse_error("API Key未配置"); exit; } -// ---- Anthropic API 兼容格式 ---- $request_body = json_encode([ "model" => $model, "max_tokens" => $max_tokens, "system" => $system_prompt, "temperature" => 1.0, + "stream" => true, "messages" => [ ["role" => "user", "content" => $user_prompt] ] ]); +// ---- 流式 SSE 转发 ---- +// 追踪连接状态和累积文本 +$GLOBALS['_llm_alive'] = true; +$GLOBALS['_llm_full_text'] = ""; +$GLOBALS['_llm_buffer'] = ""; +$GLOBALS['_llm_sent_text'] = ""; // 已发送给前端的文本(用于增量比较) + +// 检测客户端断开 +register_shutdown_function(function() { + $GLOBALS['_llm_alive'] = false; +}); + $ch = curl_init($api_url); curl_setopt_array($ch, [ CURLOPT_POST => true, @@ -171,51 +220,97 @@ curl_setopt_array($ch, [ "x-api-key: " . $api_key, "anthropic-version: 2023-06-01" ], - CURLOPT_RETURNTRANSFER => true, + CURLOPT_RETURNTRANSFER => false, CURLOPT_TIMEOUT => $timeout, - CURLOPT_SSL_VERIFYPEER => true + CURLOPT_CONNECTTIMEOUT => 10, + CURLOPT_SSL_VERIFYPEER => true, + CURLOPT_WRITEFUNCTION => function($ch, $chunk) use (&$_llm_alive, &$_llm_full_text, &$_llm_buffer) { + if (!$_llm_alive) return 0; + + $_llm_buffer .= $chunk; + + // 按行解析 SSE + while (($pos = strpos($_llm_buffer, "\n")) !== false) { + $line = substr($_llm_buffer, 0, $pos); + $_llm_buffer = substr($_llm_buffer, $pos + 1); + + $line = trim($line); + if (empty($line)) continue; + + // 解析 data: 行 + if (strncmp($line, "data: ", 6) === 0) { + $json_str = substr($line, 6); + if ($json_str === "[DONE]") continue; + + $data = json_decode($json_str, true); + if (!$data) continue; + + $type = isset($data['type']) ? $data['type'] : ''; + + // content_block_delta: 增量文本 + if ($type === 'content_block_delta' && isset($data['delta']['text'])) { + $_llm_full_text .= $data['delta']['text']; + sse_send("chunk", ["text" => $data['delta']['text']]); + + // message_delta: 消息完成 + } elseif ($type === 'message_delta' && isset($data['delta']['stop_reason'])) { + if ($data['delta']['stop_reason'] === 'end_turn' || + $data['delta']['stop_reason'] === 'max_tokens') { + sse_send("done", []); + return 0; // 停止 curl + } + + // message_start / ping: 忽略 + } elseif ($type === 'message_start' || $type === 'ping') { + continue; + + // content_block_start / stop: 忽略 + } elseif ($type === 'content_block_start' || $type === 'content_block_stop') { + continue; + + // 错误事件 + } elseif ($type === 'error') { + $msg = isset($data['error']['message']) ? $data['error']['message'] : '未知错误'; + sse_send("error", ["message" => "API错误: " . $msg]); + return 0; + } + } + } + + return strlen($chunk); + } ]); -$response = curl_exec($ch); +$result = 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); +// 如果连接断开,不缓存也不报错 +if (!$_llm_alive) exit; + +// ---- 处理错误 ---- +if ($result === false && empty($_llm_full_text)) { + sse_error("网络请求失败: " . ($curl_error ?: "未知错误")); exit; } -if ($http_code !== 200) { - echo json_encode(["error" => "API请求失败 (HTTP $http_code)"], JSON_UNESCAPED_UNICODE); +// 检查 HTTP 错误(如果 write callback 没有收到数据) +if (empty($_llm_full_text) && $http_code !== 200) { + sse_error("API请求失败 (HTTP $http_code)"); exit; } -$resp_data = json_decode($response, true); -// Anthropic 响应格式: content 是一个数组,取 type=text 的块 -$review_text = ""; -if ($resp_data && isset($resp_data['content']) && is_array($resp_data['content'])) { - foreach ($resp_data['content'] as $block) { - if (isset($block['type']) && $block['type'] === 'text' && isset($block['text'])) { - $review_text .= $block['text']; - } - } -} -if (empty($review_text)) { - echo json_encode(["error" => "API返回格式异常"], JSON_UNESCAPED_UNICODE); +if (empty($_llm_full_text)) { + sse_error("API返回为空"); exit; } -// ---- 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;"); +// ---- 9. 缓存完整结果 ---- +$review_text = $_llm_full_text; -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); +$insert_ok = pdo_query("INSERT INTO `llm_review` (`solution_id`, `review`, `create_time`) VALUES (?, ?, NOW())", $sid, $review_text); +if ($insert_ok === -1) { + // 插入失败(可能已有记录),尝试更新 + pdo_query("UPDATE `llm_review` SET `review`=?, `create_time`=NOW() WHERE `solution_id`=?", $review_text, $sid); +} diff --git a/web/template/syzoj/header.php b/web/template/syzoj/header.php index 0446756..18d994c 100644 --- a/web/template/syzoj/header.php +++ b/web/template/syzoj/header.php @@ -222,13 +222,9 @@ a.label:hover { " href="status.php"> - - - - " - href="docs.php"> 文档 + + " + href="llm-chat.php"> AI聊天 " href="ranklist.php"> @@ -248,6 +244,9 @@ a.label:hover { } ?> + 0 ){ + $cid=intval($_GET['cid']); + ?> @@ -261,14 +260,11 @@ a.label:hover { - + " href="cppreference.php"> - 0 ){ - $cid=intval($_GET['cid']); - ?> - +