186 lines
5.8 KiB
PHP
186 lines
5.8 KiB
PHP
<?php
|
|
/**
|
|
* AKSRT AI — AI 子系统核心
|
|
*
|
|
* 统一管理 AI provider 调用、速率限制和输出处理。
|
|
* 默认关闭,需管理员显式启用。
|
|
*
|
|
* @package AKSRT
|
|
*/
|
|
|
|
defined('ABSPATH') || exit;
|
|
|
|
class AKSRT_AI {
|
|
|
|
private static $instance = null;
|
|
|
|
const PROVIDER_NONE = 'none';
|
|
const PROVIDER_OPENAI = 'openai';
|
|
|
|
public static function get_instance(): self {
|
|
if (null === self::$instance) {
|
|
self::$instance = new self();
|
|
}
|
|
return self::$instance;
|
|
}
|
|
|
|
/**
|
|
* 获取 AI 配置
|
|
*/
|
|
public function get_config(): array {
|
|
$defaults = [
|
|
'enabled' => false,
|
|
'provider' => self::PROVIDER_NONE,
|
|
'api_key' => '',
|
|
'api_base' => 'https://api.openai.com/v1',
|
|
'model' => 'gpt-3.5-turbo',
|
|
'max_tokens' => 1000,
|
|
'rate_limit' => 10, // 每分钟最大调用次数
|
|
];
|
|
|
|
$saved = get_option('aksrt_ai_settings', []);
|
|
if (!is_array($saved)) $saved = [];
|
|
|
|
return array_merge($defaults, $saved);
|
|
}
|
|
|
|
/**
|
|
* 更新 AI 配置
|
|
*/
|
|
public function update_config(array $config): bool {
|
|
$current = $this->get_config();
|
|
$merged = array_merge($current, $config);
|
|
|
|
// 如果 api_key 为掩码,保留原值
|
|
if (isset($config['api_key']) && $config['api_key'] === '***') {
|
|
$merged['api_key'] = $current['api_key'];
|
|
}
|
|
|
|
return update_option('aksrt_ai_settings', $merged);
|
|
}
|
|
|
|
/**
|
|
* 检查 AI 是否可用
|
|
*/
|
|
public function is_available(): bool {
|
|
$config = $this->get_config();
|
|
return !empty($config['enabled'])
|
|
&& $config['provider'] !== self::PROVIDER_NONE
|
|
&& !empty($config['api_key']);
|
|
}
|
|
|
|
/**
|
|
* 获取 AI 状态
|
|
*/
|
|
public function get_status(): array {
|
|
$config = $this->get_config();
|
|
|
|
return [
|
|
'enabled' => !empty($config['enabled']),
|
|
'provider' => $config['provider'],
|
|
'configured' => !empty($config['api_key']),
|
|
'model' => $config['model'],
|
|
'api_key' => !empty($config['api_key']) ? '***' : '',
|
|
'rate_limit' => $config['rate_limit'],
|
|
'available' => $this->is_available(),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 调用 AI 生成
|
|
*
|
|
* @param string $prompt 提示词
|
|
* @param array $options 选项
|
|
* @return array{success: bool, content?: string, message?: string}
|
|
*/
|
|
public function generate(string $prompt, array $options = []): array {
|
|
if (!$this->is_available()) {
|
|
return ['success' => false, 'message' => 'AI 服务未启用或未配置'];
|
|
}
|
|
|
|
// 速率限制检查
|
|
if (!$this->check_rate_limit()) {
|
|
return ['success' => false, 'message' => '请求过于频繁,请稍后再试'];
|
|
}
|
|
|
|
$config = $this->get_config();
|
|
$max_tokens = (int) ($options['max_tokens'] ?? $config['max_tokens']);
|
|
$temperature = (float) ($options['temperature'] ?? 0.7);
|
|
|
|
$response = wp_remote_post($config['api_base'] . '/chat/completions', [
|
|
'headers' => [
|
|
'Content-Type' => 'application/json',
|
|
'Authorization' => 'Bearer ' . $config['api_key'],
|
|
],
|
|
'body' => wp_json_encode([
|
|
'model' => $config['model'],
|
|
'messages' => [
|
|
['role' => 'system', 'content' => '你是一个内容助手,提供建议性内容。输出仅作为参考建议,不会自动应用到线上内容。'],
|
|
['role' => 'user', 'content' => $prompt],
|
|
],
|
|
'max_tokens' => $max_tokens,
|
|
'temperature' => $temperature,
|
|
]),
|
|
'timeout' => 30,
|
|
]);
|
|
|
|
// 记录调用
|
|
$this->record_call();
|
|
|
|
if (is_wp_error($response)) {
|
|
return ['success' => false, 'message' => 'API 请求失败: ' . $response->get_error_message()];
|
|
}
|
|
|
|
$body = json_decode(wp_remote_retrieve_body($response), true);
|
|
$status_code = wp_remote_retrieve_response_code($response);
|
|
|
|
if ($status_code !== 200) {
|
|
$error_msg = $body['error']['message'] ?? '未知错误';
|
|
return ['success' => false, 'message' => 'API 错误 (' . $status_code . '): ' . $error_msg];
|
|
}
|
|
|
|
$message = $body['choices'][0]['message'] ?? [];
|
|
$content = $message['content'] ?? '';
|
|
|
|
// 兼容推理模型(如 mimo-v2.5-pro):content 为空时取 reasoning_content
|
|
if (empty($content) && !empty($message['reasoning_content'])) {
|
|
$content = $message['reasoning_content'];
|
|
}
|
|
|
|
if (empty($content)) {
|
|
return ['success' => false, 'message' => 'AI 返回内容为空(可能 max_tokens 不足)'];
|
|
}
|
|
|
|
return [
|
|
'success' => true,
|
|
'content' => trim($content),
|
|
'reasoning' => $message['reasoning_content'] ?? '',
|
|
'usage' => $body['usage'] ?? [],
|
|
'finish_reason' => $body['choices'][0]['finish_reason'] ?? '',
|
|
];
|
|
}
|
|
|
|
/**
|
|
* 速率限制检查
|
|
*/
|
|
private function check_rate_limit(): bool {
|
|
$config = $this->get_config();
|
|
$limit = (int) $config['rate_limit'];
|
|
if ($limit <= 0) return true;
|
|
|
|
$key = 'aksrt_ai_rate_' . get_current_user_id();
|
|
$count = (int) get_transient($key);
|
|
|
|
return $count < $limit;
|
|
}
|
|
|
|
/**
|
|
* 记录调用
|
|
*/
|
|
private function record_call(): void {
|
|
$key = 'aksrt_ai_rate_' . get_current_user_id();
|
|
$count = (int) get_transient($key);
|
|
set_transient($key, $count + 1, MINUTE_IN_SECONDS);
|
|
}
|
|
}
|