{"repo": "andy17269/lyrargon", "files": {"inc/agent.php": {"728592386a6da1ecc4cd1fd39a95110ab42be839": {"t": 1785746144.719484, "n": 12781, "h": "b4bd76bc996d3e9f", "c": " true, 'remaining' => 999 );\n\t}\n\t$ip = lyrargon_agent_get_client_ip();\n\t$key = 'lyr_agent_ip_qos_' . md5( $ip );\n\t$data = get_transient( $key );\n\tif ( false === $data || ! is_array( $data ) ) {\n\t\t$data = array();\n\t}\n\t$now = time();\n\t// 清理过期记录\n\t$data = array_values( array_filter( $data, function ( $t ) use ( $now, $window_seconds ) {\n\t\treturn $t > $now - $window_seconds;\n\t} ) );\n\t$remaining = max( 0, $max_requests - count( $data ) );\n\tif ( $remaining <= 0 ) {\n\t\treturn array( 'allowed' => false, 'remaining' => 0 );\n\t}\n\t$data[] = $now;\n\tset_transient( $key, $data, $window_seconds );\n\treturn array( 'allowed' => true, 'remaining' => $remaining );\n}\n\n/**\n * 检查日总消耗 QoS。\n * 每日消费计数存储为 WordPress 选项,每天零点自动重置。\n *\n * @param float $estimated_cost 预估消耗(以 token 数为单位,简化为每次请求 = 1 单位)\n * @param int $daily_limit 每日上限(单位数)\n * @return array [allowed: bool, current: int, limit: int]\n */\nfunction lyrargon_agent_check_daily_qos( $daily_limit = 100 ) {\n\tif ( $daily_limit <= 0 ) {\n\t\treturn array( 'allowed' => true, 'current' => 0, 'limit' => 0 );\n\t}\n\t$today = gmdate( 'Y-m-d' );\n\t$key = 'lyr_agent_daily_count_' . $today;\n\t$current = (int) get_option( $key, 0 );\n\tif ( $current >= $daily_limit ) {\n\t\treturn array( 'allowed' => false, 'current' => $current, 'limit' => $daily_limit );\n\t}\n\tupdate_option( $key, $current + 1 );\n\t// 确保每天过期后会重新计数(用 cron 或下次写入时重置)\n\tif ( $current === 0 ) {\n\t\twp_schedule_single_event( strtotime( 'tomorrow' ) + 60, 'lyrargon_agent_reset_daily_qos' );\n\t}\n\treturn array( 'allowed' => true, 'current' => $current + 1, 'limit' => $daily_limit );\n}\n\n/**\n * 重置日消耗计数器(由 cron 触发)\n */\nfunction lyrargon_agent_reset_daily_qos_action() {\n\t$today = gmdate( 'Y-m-d' );\n\tdelete_option( 'lyr_agent_daily_count_' . $today );\n}\nadd_action( 'lyrargon_agent_reset_daily_qos', 'lyrargon_agent_reset_daily_qos_action' );\n\n// ============== REST API 回调 ==============\n\n/**\n * 获取最近文章\n */\nfunction lyrargon_agent_rest_posts( $request ) {\n\t$per_page = min( (int) $request->get_param( 'per_page' ), 50 );\n\tif ( $per_page <= 0 ) {\n\t\t$per_page = 20;\n\t}\n\t$query = new WP_Query( array(\n\t\t'post_type' => array( 'post' ),\n\t\t'posts_per_page' => $per_page,\n\t\t'post_status' => 'publish',\n\t\t'no_found_rows' => true,\n\t\t'ignore_sticky_posts' => true,\n\t) );\n\t$results = array();\n\tforeach ( $query->posts as $post ) {\n\t\t$results[] = array(\n\t\t\t'id' => $post->ID,\n\t\t\t'title' => get_the_title( $post ),\n\t\t\t'url' => get_permalink( $post ),\n\t\t\t'excerpt' => wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post->post_content ) ), 60, '...' ),\n\t\t\t'date' => $post->post_date,\n\t\t);\n\t}\n\treturn new WP_REST_Response( $results, 200 );\n}\n\n/**\n * 获取最近说说\n */\nfunction lyrargon_agent_rest_shuoshuo( $request ) {\n\t$per_page = min( (int) $request->get_param( 'per_page' ), 50 );\n\tif ( $per_page <= 0 ) {\n\t\t$per_page = 20;\n\t}\n\t$query = new WP_Query( array(\n\t\t'post_type' => array( 'shuoshuo' ),\n\t\t'posts_per_page' => $per_page,\n\t\t'post_status' => 'publish',\n\t\t'no_found_rows' => true,\n\t\t'ignore_sticky_posts' => true,\n\t) );\n\t$results = array();\n\tforeach ( $query->posts as $post ) {\n\t\t$results[] = array(\n\t\t\t'id' => $post->ID,\n\t\t\t'content' => wp_strip_all_tags( $post->post_content ),\n\t\t\t'date' => $post->post_date,\n\t\t);\n\t}\n\treturn new WP_REST_Response( $results, 200 );\n}\n\n/**\n * 获取侧边栏公告\n */\nfunction lyrargon_agent_rest_announcement() {\n\t$content = get_option( 'lyrargon_sidebar_announcement', '' );\n\treturn new WP_REST_Response( array(\n\t\t'content' => $content,\n\t), 200 );\n}\n\n/**\n * 构建知识库上下文文本\n */\nfunction lyrargon_agent_build_context() {\n\t$parts = array();\n\n\t// 公告\n\t$announcement = get_option( 'lyrargon_sidebar_announcement', '' );\n\tif ( ! empty( $announcement ) ) {\n\t\t$parts[] = \"=== 博客公告 ===\\n\" . wp_strip_all_tags( $announcement ) . \"\\n\";\n\t}\n\n\t// 最近文章\n\t$post_count = (int) get_option( 'lyrargon_agent_context_post_count', 10 );\n\tif ( $post_count < 1 ) {\n\t\t$post_count = 10;\n\t}\n\t$posts = get_posts( array(\n\t\t'post_type' => 'post',\n\t\t'posts_per_page' => $post_count,\n\t\t'post_status' => 'publish',\n\t) );\n\tif ( ! empty( $posts ) ) {\n\t\t$lines = array();\n\t\tforeach ( $posts as $p ) {\n\t\t\t$lines[] = '- ' . get_the_title( $p ) . ' | ' . get_permalink( $p );\n\t\t}\n\t\t$parts[] = \"=== 最近文章 ===\\n\" . implode( \"\\n\", $lines ) . \"\\n\";\n\t}\n\n\t// 最近说说\n\t$shuoshuo_count = (int) get_option( 'lyrargon_agent_context_shuoshuo_count', 10 );\n\tif ( $shuoshuo_count < 1 ) {\n\t\t$shuoshuo_count = 10;\n\t}\n\t$shuoshuo = get_posts( array(\n\t\t'post_type' => 'shuoshuo',\n\t\t'posts_per_page' => $shuoshuo_count,\n\t\t'post_status' => 'publish',\n\t) );\n\tif ( ! empty( $shuoshuo ) ) {\n\t\t$lines = array();\n\t\tforeach ( $shuoshuo as $s ) {\n\t\t\t$lines[] = '- ' . wp_strip_all_tags( $s->post_content );\n\t\t}\n\t\t$parts[] = \"=== 最近说说 ===\\n\" . implode( \"\\n\", $lines ) . \"\\n\";\n\t}\n\n\treturn implode( \"\\n\", $parts );\n}\n\n/**\n * 搜索博客内容(用于 Agent 搜索功能)\n */\nfunction lyrargon_agent_search_content( $term ) {\n\tif ( empty( trim( $term ) ) ) {\n\t\treturn array();\n\t}\n\t$types = array( 'post', 'shuoshuo' );\n\t$query = new WP_Query( array(\n\t\t'post_type' => $types,\n\t\t's' => $term,\n\t\t'posts_per_page' => 8,\n\t\t'post_status' => 'publish',\n\t\t'no_found_rows' => true,\n\t\t'ignore_sticky_posts' => true,\n\t) );\n\t$results = array();\n\tforeach ( $query->posts as $post ) {\n\t\t$results[] = array(\n\t\t\t'id' => $post->ID,\n\t\t\t'title' => get_the_title( $post ),\n\t\t\t'url' => get_permalink( $post ),\n\t\t\t'type' => $post->post_type,\n\t\t\t'excerpt' => wp_trim_words( wp_strip_all_tags( strip_shortcodes( $post->post_content ) ), 40, '...' ),\n\t\t);\n\t}\n\treturn $results;\n}\n\n/**\n * 代理调用 OpenAI 兼容 API(chat/completions)\n */\nfunction lyrargon_agent_rest_chat( $request ) {\n\t// --- QoS 检查 ---\n\t$ip_limit = (int) get_option( 'lyrargon_agent_ip_qos_limit', 30 );\n\t$ip_window = (int) get_option( 'lyrargon_agent_ip_qos_window', 60 );\n\t$daily_limit = (int) get_option( 'lyrargon_agent_daily_qos_limit', 200 );\n\n\t$ip_check = lyrargon_agent_check_ip_qos( $ip_limit, $ip_window );\n\tif ( ! $ip_check['allowed'] ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'rate_limited',\n\t\t\t'message' => __( '请求过于频繁,请稍后再试', 'lyrargon' ),\n\t\t\t'retry_after_seconds' => 60,\n\t\t), 429 );\n\t}\n\n\t$daily_check = lyrargon_agent_check_daily_qos( $daily_limit );\n\tif ( ! $daily_check['allowed'] ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'daily_limit_reached',\n\t\t\t'message' => __( '今日 API 调用次数已达上限', 'lyrargon' ),\n\t\t), 429 );\n\t}\n\n\t// --- 读取配置 ---\n\t$api_endpoint = get_option( 'lyrargon_agent_api_endpoint', 'https://api.deepseek.com/v1/chat/completions' );\n\t$api_key = get_option( 'lyrargon_agent_api_key', '' );\n\t$model = get_option( 'lyrargon_agent_model', 'deepseek-chat' );\n\t$temperature = (float) get_option( 'lyrargon_agent_temperature', '0.7' );\n\t$max_tokens = (int) get_option( 'lyrargon_agent_max_tokens', '2048' );\n\t$system_prompt_template = get_option( 'lyrargon_agent_system_prompt',\n\t\t'你是一个博客助手,基于以下博客内容回答用户问题。请从博客内容中查找相关信息并给出准确回答。如果找不到相关信息,请如实告知。回答使用 Markdown 格式,文章链接请用 [标题](URL) 形式展示。'\n\t);\n\n\tif ( empty( $api_key ) ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'not_configured',\n\t\t\t'message' => __( 'AI Agent 尚未配置 API Key', 'lyrargon' ),\n\t\t), 503 );\n\t}\n\n\t// --- 解析请求 ---\n\t$body_json = $request->get_body();\n\t$body = json_decode( $body_json, true );\n\tif ( empty( $body ) || ! isset( $body['message'] ) ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'invalid_request',\n\t\t\t'message' => __( '请求参数无效', 'lyrargon' ),\n\t\t), 400 );\n\t}\n\n\t$user_message = trim( $body['message'] );\n\t$search_enabled = get_option( 'lyrargon_agent_search_enabled', 'true' ) === 'true';\n\t$context = '';\n\n\t// 获取知识库上下文\n\tif ( $search_enabled ) {\n\t\t$context = lyrargon_agent_build_context();\n\t}\n\n\t// 如有搜索词,额外搜索\n\t$search_results = array();\n\tif ( $search_enabled && strlen( $user_message ) > 1 ) {\n\t\t$search_results = lyrargon_agent_search_content( $user_message );\n\t}\n\tif ( ! empty( $search_results ) ) {\n\t\t$search_text = \"=== 搜索结果 ===\\n\";\n\t\tforeach ( $search_results as $r ) {\n\t\t\t$search_text .= \"- [{$r['title']}]({$r['url']}) ({$r['type']}): {$r['excerpt']}\\n\";\n\t\t}\n\t\t$context .= \"\\n\" . $search_text;\n\t}\n\n\t// 构建 System Prompt\n\t$system_content = $system_prompt_template;\n\tif ( ! empty( $context ) ) {\n\t\t$system_content .= \"\\n\\n以下是博客当前的内容,请基于此回答:\\n\" . $context;\n\t}\n\n\t// --- 构建请求消息 ---\n\t$messages = array(\n\t\tarray( 'role' => 'system', 'content' => $system_content ),\n\t);\n\tif ( ! empty( $body['history'] ) && is_array( $body['history'] ) ) {\n\t\tforeach ( $body['history'] as $msg ) {\n\t\t\tif ( isset( $msg['role'], $msg['content'] ) ) {\n\t\t\t\t$messages[] = array(\n\t\t\t\t\t'role' => $msg['role'],\n\t\t\t\t\t'content' => $msg['content'],\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\t$messages[] = array( 'role' => 'user', 'content' => $user_message );\n\n\t// --- 调用 AI API ---\n\t$request_args = array(\n\t\t'headers' => array(\n\t\t\t'Content-Type' => 'application/json',\n\t\t\t'Authorization' => 'Bearer ' . $api_key,\n\t\t),\n\t\t'body' => wp_json_encode( array(\n\t\t\t'model' => $model,\n\t\t\t'messages' => $messages,\n\t\t\t'temperature' => $temperature,\n\t\t\t'max_tokens' => $max_tokens,\n\t\t\t'stream' => false,\n\t\t) ),\n\t\t'timeout' => 60,\n\t);\n\n\t$response = wp_remote_post( $api_endpoint, $request_args );\n\n\tif ( is_wp_error( $response ) ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'api_error',\n\t\t\t'message' => $response->get_error_message(),\n\t\t), 502 );\n\t}\n\n\t$response_code = wp_remote_retrieve_response_code( $response );\n\t$response_body = wp_remote_retrieve_body( $response );\n\t$response_data = json_decode( $response_body, true );\n\n\tif ( $response_code !== 200 || empty( $response_data ) ) {\n\t\treturn new WP_REST_Response( array(\n\t\t\t'error' => 'api_error',\n\t\t\t'message' => isset( $response_data['error']['message'] ) ? $response_data['error']['message'] : __( 'AI API 返回错误', 'lyrargon' ),\n\t\t\t'response_code' => $response_code,\n\t\t), 502 );\n\t}\n\n\t$reply = '';\n\tif ( isset( $response_data['choices'][0]['message']['content'] ) ) {\n\t\t$reply = $response_data['choices'][0]['message']['content'];\n\t}\n\n\treturn new WP_REST_Response( array(\n\t\t'reply' => $reply,\n\t\t'qos' => array(\n\t\t\t'ip_remaining' => $ip_check['remaining'],\n\t\t\t'daily_current' => $daily_check['current'],\n\t\t\t'daily_limit' => $daily_check['limit'],\n\t\t),\n\t), 200 );\n}\n\n// ============== 注册 REST API 路由 ==============\n\nadd_action( 'rest_api_init', function () {\n\t// 获取文章\n\tregister_rest_route( 'lyrargon/v1', '/agent/posts', array(\n\t\t'methods' => WP_REST_Server::READABLE,\n\t\t'callback' => 'lyrargon_agent_rest_posts',\n\t\t'permission_callback' => '__return_true',\n\t\t'args' => array(\n\t\t\t'per_page' => array(\n\t\t\t\t'required' => false,\n\t\t\t\t'sanitize_callback' => 'absint',\n\t\t\t\t'default' => 20,\n\t\t\t),\n\t\t),\n\t) );\n\n\t// 获取说说\n\tregister_rest_route( 'lyrargon/v1', '/agent/shuoshuo', array(\n\t\t'methods' => WP_REST_Server::READABLE,\n\t\t'callback' => 'lyrargon_agent_rest_shuoshuo',\n\t\t'permission_callback' => '__return_true',\n\t\t'args' => array(\n\t\t\t'per_page' => array(\n\t\t\t\t'required' => false,\n\t\t\t\t'sanitize_callback' => 'absint',\n\t\t\t\t'default' => 20,\n\t\t\t),\n\t\t),\n\t) );\n\n\t// 获取公告\n\tregister_rest_route( 'lyrargon/v1', '/agent/announcement', array(\n\t\t'methods' => WP_REST_Server::READABLE,\n\t\t'callback' => 'lyrargon_agent_rest_announcement',\n\t\t'permission_callback' => '__return_true',\n\t) );\n\n\t// 聊天代理(POST)\n\tregister_rest_route( 'lyrargon/v1', '/agent/chat', array(\n\t\t'methods' => WP_REST_Server::CREATABLE,\n\t\t'callback' => 'lyrargon_agent_rest_chat',\n\t\t'permission_callback' => '__return_true',\n\t) );\n} );\n"}}}}