$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; } ?>