data
11 months ago
engines
11 months ago
exceptions
11 months ago
modules
11 months ago
query
11 months ago
rest
11 months ago
services
11 months ago
admin.php
11 months ago
api.php
11 months ago
core.php
11 months ago
discussion.php
11 months ago
event.php
11 months ago
init.php
11 months ago
logging.php
11 months ago
reply.php
11 months ago
rest.php
11 months ago
api.php
892 lines
| 1 | <?php |
| 2 | |
| 3 | class Meow_MWAI_API { |
| 4 | public $core; |
| 5 | private $chatbot_module; |
| 6 | private $discussions_module; |
| 7 | private $bearer_token; |
| 8 | private $debug = false; |
| 9 | |
| 10 | public function __construct( $chatbot_module, $discussions_module ) { |
| 11 | global $mwai_core; |
| 12 | $this->core = $mwai_core; |
| 13 | $this->chatbot_module = $chatbot_module; |
| 14 | $this->discussions_module = $discussions_module; |
| 15 | add_action( 'rest_api_init', [ $this, 'rest_api_init' ] ); |
| 16 | $this->debug = $this->core->get_option( 'server_debug_mode' ); |
| 17 | } |
| 18 | |
| 19 | #region REST API |
| 20 | public function rest_api_init() { |
| 21 | $public_api = $this->core->get_option( 'public_api' ); |
| 22 | if ( !$public_api ) { |
| 23 | return; |
| 24 | } |
| 25 | $this->bearer_token = $this->core->get_option( 'public_api_bearer_token' ); |
| 26 | if ( !empty( $this->bearer_token ) ) { |
| 27 | add_filter( 'mwai_allow_public_api', [ $this, 'auth_via_bearer_token' ], 10, 3 ); |
| 28 | } |
| 29 | |
| 30 | register_rest_route( 'mwai/v1', '/simpleAuthCheck', [ |
| 31 | 'methods' => 'GET', |
| 32 | 'callback' => [ $this, 'rest_simpleAuthCheck' ], |
| 33 | 'permission_callback' => function ( $request ) { |
| 34 | return $this->core->can_access_public_api( 'simpleAuthCheck', $request ); |
| 35 | }, |
| 36 | ] ); |
| 37 | register_rest_route( 'mwai/v1', '/simpleTextQuery', [ |
| 38 | 'methods' => 'POST', |
| 39 | 'callback' => [ $this, 'rest_simpleTextQuery' ], |
| 40 | 'permission_callback' => function ( $request ) { |
| 41 | return $this->core->can_access_public_api( 'simpleTextQuery', $request ); |
| 42 | }, |
| 43 | ] ); |
| 44 | register_rest_route( 'mwai/v1', '/simpleImageQuery', [ |
| 45 | 'methods' => 'POST', |
| 46 | 'callback' => [ $this, 'rest_simpleImageQuery' ], |
| 47 | 'permission_callback' => function ( $request ) { |
| 48 | return $this->core->can_access_public_api( 'simpleImageQuery', $request ); |
| 49 | }, |
| 50 | ] ); |
| 51 | register_rest_route( 'mwai/v1', '/simpleImageEditQuery', [ |
| 52 | 'methods' => 'POST', |
| 53 | 'callback' => [ $this, 'rest_simpleImageEditQuery' ], |
| 54 | 'permission_callback' => function ( $request ) { |
| 55 | return $this->core->can_access_public_api( 'simpleImageEditQuery', $request ); |
| 56 | }, |
| 57 | ] ); |
| 58 | register_rest_route( 'mwai/v1', '/simpleVisionQuery', [ |
| 59 | 'methods' => 'POST', |
| 60 | 'callback' => [ $this, 'rest_simpleVisionQuery' ], |
| 61 | 'permission_callback' => function ( $request ) { |
| 62 | return $this->core->can_access_public_api( 'simpleVisionQuery', $request ); |
| 63 | }, |
| 64 | ] ); |
| 65 | register_rest_route( 'mwai/v1', '/simpleJsonQuery', [ |
| 66 | 'methods' => 'POST', |
| 67 | 'callback' => [ $this, 'rest_simpleJsonQuery' ], |
| 68 | 'permission_callback' => function ( $request ) { |
| 69 | return $this->core->can_access_public_api( 'simpleJsonQuery', $request ); |
| 70 | }, |
| 71 | ] ); |
| 72 | register_rest_route( 'mwai/v1', '/moderationCheck', [ |
| 73 | 'methods' => 'POST', |
| 74 | 'callback' => [ $this, 'rest_moderationCheck' ], |
| 75 | 'permission_callback' => function ( $request ) { |
| 76 | return $this->core->can_access_public_api( 'moderationCheck', $request ); |
| 77 | }, |
| 78 | ] ); |
| 79 | register_rest_route( 'mwai/v1', '/simpleTranscribeAudio', [ |
| 80 | 'methods' => 'POST', |
| 81 | 'callback' => [ $this, 'rest_simpleTranscribeAudio' ], |
| 82 | 'permission_callback' => function ( $request ) { |
| 83 | return $this->core->can_access_public_api( 'simpleTranscribeAudio', $request ); |
| 84 | }, |
| 85 | ] ); |
| 86 | |
| 87 | if ( $this->chatbot_module ) { |
| 88 | register_rest_route( 'mwai/v1', '/simpleChatbotQuery', [ |
| 89 | 'methods' => 'POST', |
| 90 | 'callback' => [ $this, 'rest_simpleChatbotQuery' ], |
| 91 | 'permission_callback' => function ( $request ) { |
| 92 | return $this->core->can_access_public_api( 'simpleChatbotQuery', $request ); |
| 93 | }, |
| 94 | ] ); |
| 95 | |
| 96 | register_rest_route( 'mwai/v1', '/listChatbots', [ |
| 97 | 'methods' => 'GET', |
| 98 | 'callback' => [ $this, 'rest_listChatbots' ], |
| 99 | 'permission_callback' => function ( $request ) { |
| 100 | return $this->core->can_access_public_api( 'listChatbots', $request ); |
| 101 | }, |
| 102 | ] ); |
| 103 | } |
| 104 | } |
| 105 | |
| 106 | public function rest_simpleAuthCheck( $request ) { |
| 107 | try { |
| 108 | $params = $request->get_params(); |
| 109 | $current_user = wp_get_current_user(); |
| 110 | $current_email = $current_user->user_email; |
| 111 | return new WP_REST_Response( [ 'success' => true, 'data' => [ |
| 112 | 'type' => 'email', |
| 113 | 'value' => $current_email |
| 114 | ] ], 200 ); |
| 115 | } |
| 116 | catch ( Exception $e ) { |
| 117 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 118 | } |
| 119 | } |
| 120 | |
| 121 | public function auth_via_bearer_token( $allow, $feature, $extra ) { |
| 122 | if ( !empty( $extra ) && !empty( $extra->get_header( 'Authorization' ) ) ) { |
| 123 | $token = $extra->get_header( 'Authorization' ); |
| 124 | $token = str_replace( 'Bearer ', '', $token ); |
| 125 | if ( $token === $this->bearer_token ) { |
| 126 | // We set the current user to the first admin. |
| 127 | $admin = $this->core->get_admin_user(); |
| 128 | wp_set_current_user( $admin->ID, $admin->user_login ); |
| 129 | return true; |
| 130 | } |
| 131 | } |
| 132 | return $allow; |
| 133 | } |
| 134 | |
| 135 | public function rest_simpleChatbotQuery( $request ) { |
| 136 | try { |
| 137 | $params = $request->get_params(); |
| 138 | $botId = isset( $params['botId'] ) ? $params['botId'] : ''; |
| 139 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 140 | if ( empty( $message ) ) { |
| 141 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 142 | } |
| 143 | $chatId = isset( $params['chatId'] ) ? $params['chatId'] : null; |
| 144 | $params = null; |
| 145 | if ( !empty( $chatId ) ) { |
| 146 | $params = [ 'chatId' => $chatId ]; |
| 147 | } |
| 148 | if ( empty( $botId ) || empty( $message ) ) { |
| 149 | throw new Exception( 'The botId and message are required.' ); |
| 150 | } |
| 151 | |
| 152 | if ( $this->debug ) { |
| 153 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 154 | $debug = sprintf( 'REST [SimpleChatbotQuery]: %s, %s', $shortMessage, json_encode( $params ) ); |
| 155 | Meow_MWAI_Logging::log( $debug ); |
| 156 | } |
| 157 | |
| 158 | $reply = $this->simpleChatbotQuery( $botId, $message, $params, false ); |
| 159 | return new WP_REST_Response( [ |
| 160 | 'success' => true, |
| 161 | 'data' => $reply['reply'], |
| 162 | 'extra' => [ |
| 163 | 'actions' => $reply['actions'], |
| 164 | 'chatId' => $reply['chatId'] |
| 165 | ] |
| 166 | ], 200 ); |
| 167 | } |
| 168 | catch ( Exception $e ) { |
| 169 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 170 | } |
| 171 | } |
| 172 | |
| 173 | public function rest_listChatbots( $request ) { |
| 174 | try { |
| 175 | // Get all chatbots |
| 176 | $chatbots = get_option( 'mwai_chatbots', [] ); |
| 177 | $environments = $this->core->get_option( 'ai_envs' ); |
| 178 | $mcp_envs = $this->core->get_option( 'mcp_envs', [] ); |
| 179 | |
| 180 | // Get all models from all environments |
| 181 | $all_models = []; |
| 182 | foreach ( $environments as $env ) { |
| 183 | try { |
| 184 | $engine = Meow_MWAI_Engines_Factory::get( $this->core, $env['id'] ); |
| 185 | $env_models = $engine->retrieve_models(); |
| 186 | foreach ( $env_models as $model ) { |
| 187 | $all_models[$model['model']] = $model; |
| 188 | } |
| 189 | } |
| 190 | catch ( Exception $e ) { |
| 191 | // Skip environments that fail |
| 192 | } |
| 193 | } |
| 194 | |
| 195 | // Debug: Log model info for gpt-4.1-mini |
| 196 | if ( $this->debug && isset( $all_models['gpt-4.1-mini'] ) ) { |
| 197 | error_log( '[AI Engine API] Model info for gpt-4.1-mini: ' . json_encode( $all_models['gpt-4.1-mini'] ) ); |
| 198 | } |
| 199 | |
| 200 | // Get registered functions |
| 201 | $functions = apply_filters( 'mwai_functions_list', [] ); |
| 202 | $function_names = []; |
| 203 | foreach ( $functions as $function ) { |
| 204 | $function_names[] = $function->name ?? 'unknown'; |
| 205 | } |
| 206 | |
| 207 | $result = []; |
| 208 | |
| 209 | foreach ( $chatbots as $chatbotId => $chatbot ) { |
| 210 | // Debug log the chatbot structure |
| 211 | if ( $this->debug && ( $chatbot['name'] ?? '' ) === 'Jordy' ) { |
| 212 | error_log( '[AI Engine API] Jordy chatbot structure: ' . json_encode( $chatbot ) ); |
| 213 | } |
| 214 | |
| 215 | // Basic info |
| 216 | $info = [ |
| 217 | 'id' => $chatbot['botId'] ?? $chatbotId, // Use botId if available, fallback to array index |
| 218 | 'name' => $chatbot['name'] ?? 'Unnamed', |
| 219 | 'type' => 'chat', // Default type |
| 220 | 'model' => null, |
| 221 | 'model_name' => null, |
| 222 | 'environment' => null, |
| 223 | 'environment_name' => null, |
| 224 | 'functions' => [], |
| 225 | 'tools' => [], // Add tools array |
| 226 | 'mcp_servers' => [] |
| 227 | ]; |
| 228 | |
| 229 | // Determine chatbot type |
| 230 | if ( !empty( $chatbot['type'] ) ) { |
| 231 | $info['type'] = $chatbot['type']; |
| 232 | } |
| 233 | else { |
| 234 | // Try to infer type from model or other properties |
| 235 | if ( !empty( $chatbot['model'] ) ) { |
| 236 | if ( strpos( $chatbot['model'], 'realtime' ) !== false ) { |
| 237 | $info['type'] = 'realtime'; |
| 238 | } |
| 239 | else if ( strpos( $chatbot['model'], 'image' ) !== false || strpos( $chatbot['model'], 'dall-e' ) !== false ) { |
| 240 | $info['type'] = 'images'; |
| 241 | } |
| 242 | else if ( !empty( $chatbot['assistantId'] ) ) { |
| 243 | $info['type'] = 'assistant'; |
| 244 | } |
| 245 | } |
| 246 | } |
| 247 | |
| 248 | // Get model info |
| 249 | if ( !empty( $chatbot['model'] ) ) { |
| 250 | $info['model'] = $chatbot['model']; |
| 251 | // Find model name |
| 252 | if ( isset( $all_models[$chatbot['model']] ) ) { |
| 253 | $info['model_name'] = $all_models[$chatbot['model']]['name'] ?? $chatbot['model']; |
| 254 | } |
| 255 | else { |
| 256 | $info['model_name'] = $chatbot['model']; |
| 257 | } |
| 258 | } |
| 259 | |
| 260 | // Get environment info |
| 261 | if ( !empty( $chatbot['envId'] ) ) { |
| 262 | $info['environment'] = $chatbot['envId']; |
| 263 | // Find environment name |
| 264 | foreach ( $environments as $env ) { |
| 265 | if ( $env['id'] === $chatbot['envId'] ) { |
| 266 | $info['environment_name'] = $env['name'] ?? $chatbot['envId']; |
| 267 | break; |
| 268 | } |
| 269 | } |
| 270 | } |
| 271 | |
| 272 | // Check if it uses functions - get specific function names if available |
| 273 | if ( !empty( $chatbot['functions'] ) ) { |
| 274 | if ( is_array( $chatbot['functions'] ) ) { |
| 275 | // Functions are stored as array of objects with id and type |
| 276 | $chatbot_functions = []; |
| 277 | foreach ( $chatbot['functions'] as $func ) { |
| 278 | if ( isset( $func['id'] ) ) { |
| 279 | // Try to find function name by ID |
| 280 | $func_name = $this->get_function_name_by_id( $func['id'] ); |
| 281 | if ( $func_name ) { |
| 282 | $chatbot_functions[] = $func_name; |
| 283 | } |
| 284 | else { |
| 285 | // Fallback: include the ID if name not found |
| 286 | $chatbot_functions[] = 'function_' . $func['id']; |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | $info['functions'] = $chatbot_functions; |
| 291 | } |
| 292 | else if ( $chatbot['functions'] === true ) { |
| 293 | // If functions is just true, it uses all registered functions |
| 294 | $info['functions'] = $function_names; |
| 295 | } |
| 296 | } |
| 297 | |
| 298 | // Check for tools (Web Search, Image Generation) |
| 299 | if ( !empty( $chatbot['tools'] ) && is_array( $chatbot['tools'] ) ) { |
| 300 | // Filter tools based on model capabilities |
| 301 | $supported_tools = []; |
| 302 | if ( !empty( $chatbot['model'] ) ) { |
| 303 | // Try exact match first |
| 304 | $model_key = $chatbot['model']; |
| 305 | $model_info = $all_models[$model_key] ?? null; |
| 306 | |
| 307 | // If not found and it's an OpenRouter model, try without prefix |
| 308 | if ( !$model_info && strpos( $model_key, '/' ) !== false ) { |
| 309 | $model_key = substr( $model_key, strpos( $model_key, '/' ) + 1 ); |
| 310 | $model_info = $all_models[$model_key] ?? null; |
| 311 | } |
| 312 | |
| 313 | if ( $model_info ) { |
| 314 | $model_tools = $model_info['tools'] ?? []; |
| 315 | |
| 316 | // Only include tools that are both configured AND supported by the model |
| 317 | foreach ( $chatbot['tools'] as $tool ) { |
| 318 | if ( in_array( $tool, $model_tools ) ) { |
| 319 | $supported_tools[] = $tool; |
| 320 | } |
| 321 | } |
| 322 | } |
| 323 | else { |
| 324 | // If model not found in our list, keep all configured tools |
| 325 | // This allows custom models to use tools |
| 326 | $supported_tools = $chatbot['tools']; |
| 327 | } |
| 328 | } |
| 329 | $info['tools'] = $supported_tools; |
| 330 | } |
| 331 | |
| 332 | // Check for MCP servers |
| 333 | if ( !empty( $chatbot['mcp_servers'] ) && is_array( $chatbot['mcp_servers'] ) ) { |
| 334 | foreach ( $chatbot['mcp_servers'] as $mcpServer ) { |
| 335 | if ( !empty( $mcpServer['id'] ) ) { |
| 336 | // Find MCP server name |
| 337 | foreach ( $mcp_envs as $mcp ) { |
| 338 | if ( $mcp['id'] === $mcpServer['id'] ) { |
| 339 | $info['mcp_servers'][] = [ |
| 340 | 'id' => $mcp['id'], |
| 341 | 'name' => $mcp['name'] ?? 'Unnamed MCP Server' |
| 342 | ]; |
| 343 | break; |
| 344 | } |
| 345 | } |
| 346 | } |
| 347 | } |
| 348 | } |
| 349 | |
| 350 | $result[] = $info; |
| 351 | } |
| 352 | |
| 353 | return new WP_REST_Response( [ |
| 354 | 'success' => true, |
| 355 | 'data' => $result |
| 356 | ], 200 ); |
| 357 | } |
| 358 | catch ( Exception $e ) { |
| 359 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 360 | } |
| 361 | } |
| 362 | |
| 363 | public function rest_simpleTextQuery( $request ) { |
| 364 | try { |
| 365 | $params = $request->get_params(); |
| 366 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 367 | if ( empty( $message ) ) { |
| 368 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 369 | } |
| 370 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 371 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 372 | if ( !empty( $scope ) ) { |
| 373 | $options['scope'] = $scope; |
| 374 | } |
| 375 | if ( empty( $message ) ) { |
| 376 | throw new Exception( 'The message is required.' ); |
| 377 | } |
| 378 | |
| 379 | if ( $this->debug ) { |
| 380 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 381 | $debug = sprintf( 'REST [SimpleTextQuery]: %s, %s', $shortMessage, json_encode( $options ) ); |
| 382 | Meow_MWAI_Logging::log( $debug ); |
| 383 | } |
| 384 | |
| 385 | $reply = $this->simpleTextQuery( $message, $options ); |
| 386 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 387 | } |
| 388 | catch ( Exception $e ) { |
| 389 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 390 | } |
| 391 | } |
| 392 | |
| 393 | public function rest_simpleImageQuery( $request ) { |
| 394 | try { |
| 395 | $params = $request->get_params(); |
| 396 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 397 | if ( empty( $message ) ) { |
| 398 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 399 | } |
| 400 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 401 | $resolution = isset( $params['resolution'] ) ? $params['resolution'] : ''; |
| 402 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 403 | if ( !empty( $scope ) ) { |
| 404 | $options['scope'] = $scope; |
| 405 | } |
| 406 | if ( empty( $message ) ) { |
| 407 | throw new Exception( 'The message is required.' ); |
| 408 | } |
| 409 | if ( !empty( $resolution ) ) { |
| 410 | $options['resolution'] = $resolution; |
| 411 | } |
| 412 | |
| 413 | if ( $this->debug ) { |
| 414 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 415 | $debug = sprintf( 'REST [SimpleImageQuery]: %s, %s', $shortMessage, json_encode( $options ) ); |
| 416 | Meow_MWAI_Logging::log( $debug ); |
| 417 | } |
| 418 | |
| 419 | $reply = $this->simpleImageQuery( $message, $options ); |
| 420 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 421 | } |
| 422 | catch ( Exception $e ) { |
| 423 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 424 | } |
| 425 | } |
| 426 | |
| 427 | public function rest_simpleImageEditQuery( $request ) { |
| 428 | try { |
| 429 | $params = $request->get_params(); |
| 430 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 431 | if ( empty( $message ) ) { |
| 432 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 433 | } |
| 434 | $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0; |
| 435 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 436 | $resolution = isset( $params['resolution'] ) ? $params['resolution'] : ''; |
| 437 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 438 | if ( !empty( $scope ) ) { |
| 439 | $options['scope'] = $scope; |
| 440 | } |
| 441 | if ( empty( $message ) ) { |
| 442 | throw new Exception( 'The message is required.' ); |
| 443 | } |
| 444 | if ( empty( $mediaId ) ) { |
| 445 | throw new Exception( 'The mediaId is required.' ); |
| 446 | } |
| 447 | if ( !empty( $resolution ) ) { |
| 448 | $options['resolution'] = $resolution; |
| 449 | } |
| 450 | |
| 451 | if ( $this->debug ) { |
| 452 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 453 | $debug = sprintf( 'REST [SimpleImageEditQuery]: %s, %s', $shortMessage, json_encode( $options ) ); |
| 454 | Meow_MWAI_Logging::log( $debug ); |
| 455 | } |
| 456 | |
| 457 | $reply = $this->simpleImageEditQuery( $message, $mediaId, $options ); |
| 458 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 459 | } |
| 460 | catch ( Exception $e ) { |
| 461 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 462 | } |
| 463 | } |
| 464 | |
| 465 | public function rest_simpleVisionQuery( $request ) { |
| 466 | try { |
| 467 | $params = $request->get_params(); |
| 468 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 469 | if ( empty( $message ) ) { |
| 470 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 471 | } |
| 472 | $url = isset( $params['url'] ) ? $params['url'] : ''; |
| 473 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 474 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 475 | if ( !empty( $scope ) ) { |
| 476 | $options['scope'] = $scope; |
| 477 | } |
| 478 | if ( empty( $message ) ) { |
| 479 | throw new Exception( 'The message is required.' ); |
| 480 | } |
| 481 | if ( empty( $url ) ) { |
| 482 | throw new Exception( 'The url is required.' ); |
| 483 | } |
| 484 | |
| 485 | if ( $this->debug ) { |
| 486 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 487 | $debug = sprintf( 'REST [SimpleVisionQuery]: %s, %s', $shortMessage, json_encode( $options ) ); |
| 488 | Meow_MWAI_Logging::log( $debug ); |
| 489 | } |
| 490 | |
| 491 | $reply = $this->simpleVisionQuery( $message, $url, null, $options ); |
| 492 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 493 | } |
| 494 | catch ( Exception $e ) { |
| 495 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 496 | } |
| 497 | } |
| 498 | |
| 499 | public function rest_simpleJsonQuery( $request ) { |
| 500 | try { |
| 501 | $params = $request->get_params(); |
| 502 | $message = isset( $params['message'] ) ? $params['message'] : ''; |
| 503 | if ( empty( $message ) ) { |
| 504 | $message = isset( $params['prompt'] ) ? $params['prompt'] : ''; |
| 505 | } |
| 506 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 507 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 508 | if ( !empty( $scope ) ) { |
| 509 | $options['scope'] = $scope; |
| 510 | } |
| 511 | if ( empty( $message ) ) { |
| 512 | throw new Exception( 'The message is required.' ); |
| 513 | } |
| 514 | |
| 515 | if ( $this->debug ) { |
| 516 | $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 ); |
| 517 | $debug = sprintf( 'REST [SimpleJsonQuery]: %s, %s', $shortMessage, json_encode( $options ) ); |
| 518 | Meow_MWAI_Logging::log( $debug ); |
| 519 | } |
| 520 | |
| 521 | $reply = $this->simpleJsonQuery( $message, $options ); |
| 522 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 523 | } |
| 524 | catch ( Exception $e ) { |
| 525 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 526 | } |
| 527 | } |
| 528 | |
| 529 | public function rest_moderationCheck( $request ) { |
| 530 | try { |
| 531 | $params = $request->get_params(); |
| 532 | $text = $params['text']; |
| 533 | if ( empty( $text ) ) { |
| 534 | throw new Exception( 'The text is required.' ); |
| 535 | } |
| 536 | |
| 537 | if ( $this->debug ) { |
| 538 | $shortText = Meow_MWAI_Logging::shorten( $text, 64 ); |
| 539 | $debug = sprintf( 'REST [ModerationCheck]: %s', $shortText ); |
| 540 | Meow_MWAI_Logging::log( $debug ); |
| 541 | } |
| 542 | |
| 543 | $reply = $this->moderationCheck( $text ); |
| 544 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 545 | } |
| 546 | catch ( Exception $e ) { |
| 547 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 548 | } |
| 549 | } |
| 550 | |
| 551 | public function rest_simpleTranscribeAudio( $request ) { |
| 552 | try { |
| 553 | $params = $request->get_params(); |
| 554 | $url = isset( $params['url'] ) ? $params['url'] : ''; |
| 555 | $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0; |
| 556 | $options = isset( $params['options'] ) ? $params['options'] : []; |
| 557 | $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api'; |
| 558 | |
| 559 | if ( !empty( $scope ) ) { |
| 560 | $options['scope'] = $scope; |
| 561 | } |
| 562 | |
| 563 | // Get file path from mediaId if provided |
| 564 | $path = null; |
| 565 | if ( $mediaId > 0 ) { |
| 566 | $path = get_attached_file( $mediaId ); |
| 567 | if ( empty( $path ) ) { |
| 568 | throw new Exception( 'The media file cannot be found.' ); |
| 569 | } |
| 570 | } |
| 571 | |
| 572 | if ( empty( $url ) && empty( $path ) ) { |
| 573 | throw new Exception( 'Either a URL or a mediaId is required.' ); |
| 574 | } |
| 575 | |
| 576 | if ( $this->debug ) { |
| 577 | $debug = sprintf( 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s', |
| 578 | $url ? 'provided' : 'none', |
| 579 | $mediaId, |
| 580 | json_encode( $options ) |
| 581 | ); |
| 582 | Meow_MWAI_Logging::log( $debug ); |
| 583 | } |
| 584 | |
| 585 | $reply = $this->simpleTranscribeAudio( $url, $path, $options ); |
| 586 | return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 ); |
| 587 | } |
| 588 | catch ( Exception $e ) { |
| 589 | return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 ); |
| 590 | } |
| 591 | } |
| 592 | #endregion |
| 593 | |
| 594 | #region Simple API |
| 595 | /** |
| 596 | * Executes a vision query.` |
| 597 | * |
| 598 | * @param string $message The prompt for the AI. |
| 599 | * @param string $url The URL of the image to analyze. |
| 600 | * @param string|null $path The path to the image file. If provided, the image data will be read from this file. |
| 601 | * @param array $params Additional parameters for the AI query. |
| 602 | * |
| 603 | * @return string The result of the AI query. |
| 604 | */ |
| 605 | public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) { |
| 606 | global $mwai_core; |
| 607 | $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' ); |
| 608 | $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' ); |
| 609 | if ( empty( $ai_vision_default_model ) ) { |
| 610 | $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION; |
| 611 | } |
| 612 | $query = new Meow_MWAI_Query_Text( $message ); |
| 613 | if ( !empty( $ai_vision_default_env ) ) { |
| 614 | $query->set_env_id( $ai_vision_default_env ); |
| 615 | } |
| 616 | if ( !empty( $ai_vision_default_model ) ) { |
| 617 | $query->set_model( $ai_vision_default_model ); |
| 618 | } |
| 619 | $query->inject_params( $params ); |
| 620 | if ( isset( $params['image_remote_upload'] ) ) { |
| 621 | $query->image_remote_upload = $params['image_remote_upload']; |
| 622 | } |
| 623 | if ( !empty( $url ) ) { |
| 624 | $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) ); |
| 625 | } |
| 626 | else if ( !empty( $path ) ) { |
| 627 | $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) ); |
| 628 | } |
| 629 | $reply = $mwai_core->run_query( $query ); |
| 630 | return $reply->result; |
| 631 | } |
| 632 | |
| 633 | /** |
| 634 | * Executes a chatbot query. |
| 635 | * It will use the discussion if chatId is provided in the parameters. |
| 636 | * |
| 637 | * @param string $botId The ID of the chatbot. |
| 638 | * @param string $message The prompt for the AI. |
| 639 | * @param array $params Additional parameters for the AI query. |
| 640 | * |
| 641 | * @return string The result of the AI query. |
| 642 | */ |
| 643 | public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) { |
| 644 | if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) { |
| 645 | if ( $this->core->get_option( 'chatbot_discussions' ) ) { |
| 646 | $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] ); |
| 647 | if ( !empty( $discussion ) ) { |
| 648 | $params['messages'] = $discussion['messages']; |
| 649 | } |
| 650 | } |
| 651 | else { |
| 652 | $this->core->log( 'The chatId was provided; but the discussions are not enabled.' ); |
| 653 | } |
| 654 | } |
| 655 | $data = $this->chatbot_module->chat_submit( $botId, $message, null, $params ); |
| 656 | return $onlyReply ? $data['reply'] : $data; |
| 657 | } |
| 658 | |
| 659 | /** |
| 660 | * Executes a text query. |
| 661 | * |
| 662 | * @param string $message The prompt for the AI. |
| 663 | * @param array $params Additional parameters for the AI query. |
| 664 | * |
| 665 | * @return string The result of the AI query. |
| 666 | */ |
| 667 | public function simpleTextQuery( $message, $params = [] ) { |
| 668 | global $mwai_core; |
| 669 | $query = new Meow_MWAI_Query_Text( $message ); |
| 670 | $query->inject_params( $params ); |
| 671 | $reply = $mwai_core->run_query( $query ); |
| 672 | return $reply->result; |
| 673 | } |
| 674 | |
| 675 | public function simpleImageQuery( $message, $params = [] ) { |
| 676 | global $mwai_core; |
| 677 | $query = new Meow_MWAI_Query_Image( $message ); |
| 678 | $query->inject_params( $params ); |
| 679 | $reply = $mwai_core->run_query( $query ); |
| 680 | return $reply->result; |
| 681 | } |
| 682 | |
| 683 | public function simpleImageEditQuery( $message, $mediaId, $params = [] ) { |
| 684 | global $mwai_core; |
| 685 | $query = new Meow_MWAI_Query_EditImage( $message ); |
| 686 | $query->inject_params( $params ); |
| 687 | $path = get_attached_file( $mediaId ); |
| 688 | if ( empty( $path ) ) { |
| 689 | throw new Exception( 'The media cannot be found.' ); |
| 690 | } |
| 691 | // TODO: Maybe 'vision' should be 'edit'. |
| 692 | $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) ); |
| 693 | $reply = $mwai_core->run_query( $query ); |
| 694 | return $reply->result; |
| 695 | } |
| 696 | |
| 697 | /** |
| 698 | * Generates an image relevant to the text. |
| 699 | */ |
| 700 | public function imageQueryForMediaLibrary( $message, $params = [], $postId = null ) { |
| 701 | $query = new Meow_MWAI_Query_Image( $message ); |
| 702 | $query->inject_params( $params ); |
| 703 | $query->set_local_download( null ); |
| 704 | $reply = $this->core->run_query( $query ); |
| 705 | preg_match( '/\!\[Image\]\((.*?)\)/', $reply->result, $matches ); |
| 706 | $url = $matches[1] ?? $reply->result; |
| 707 | |
| 708 | // Check if the URL is already a WordPress attachment URL to avoid duplicates |
| 709 | $attachmentId = null; |
| 710 | $upload_dir = wp_upload_dir(); |
| 711 | if ( strpos( $url, $upload_dir['baseurl'] ) === 0 ) { |
| 712 | // This is already a local WordPress upload, try to find the attachment ID |
| 713 | // First try by GUID |
| 714 | global $wpdb; |
| 715 | $attachmentId = $wpdb->get_var( $wpdb->prepare( |
| 716 | "SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'", |
| 717 | $url |
| 718 | ) ); |
| 719 | |
| 720 | // If not found by GUID, try by attachment URL (more reliable) |
| 721 | if ( empty( $attachmentId ) ) { |
| 722 | $attachmentId = attachment_url_to_postid( $url ); |
| 723 | } |
| 724 | } |
| 725 | |
| 726 | // If not found or not a local URL, add it to the media library |
| 727 | if ( empty( $attachmentId ) ) { |
| 728 | $attachmentId = $this->core->add_image_from_url( $url, null, null, null, null, null, $postId ); |
| 729 | if ( empty( $attachmentId ) ) { |
| 730 | throw new Exception( 'Could not add the image to the Media Library.' ); |
| 731 | } |
| 732 | } |
| 733 | |
| 734 | // TODO: We should create a nice title, caption, and alt. |
| 735 | $media = [ |
| 736 | 'id' => $attachmentId, |
| 737 | 'url' => wp_get_attachment_url( $attachmentId ), |
| 738 | 'title' => get_the_title( $attachmentId ), |
| 739 | 'caption' => wp_get_attachment_caption( $attachmentId ), |
| 740 | 'alt' => get_post_meta( $attachmentId, '_wp_attachment_image_alt', true ) |
| 741 | ]; |
| 742 | return $media; |
| 743 | } |
| 744 | |
| 745 | /** |
| 746 | * Executes a query that will have to return a JSON result. |
| 747 | * |
| 748 | * @param string $message The prompt for the AI. |
| 749 | * @param array $params Additional parameters for the AI query. |
| 750 | * |
| 751 | * @return array The result of the AI query. |
| 752 | */ |
| 753 | public function simpleJsonQuery( $message, $url = null, $path = null, $params = [] ) { |
| 754 | if ( !empty( $url ) || !empty( $path ) ) { |
| 755 | throw new Exception( 'The url and path are not supported yet by the simpleJsonQuery.' ); |
| 756 | } |
| 757 | global $mwai_core; |
| 758 | $query = new Meow_MWAI_Query_Text( $message . "\nYour reply must be a formatted JSON." ); |
| 759 | $query->inject_params( $params ); |
| 760 | $query->set_response_format( 'json' ); |
| 761 | $ai_json_default_env = $mwai_core->get_option( 'ai_json_default_env' ); |
| 762 | $ai_json_default_model = $mwai_core->get_option( 'ai_json_default_model' ); |
| 763 | if ( !empty( $ai_json_default_env ) ) { |
| 764 | $query->set_env_id( $ai_json_default_env ); |
| 765 | } |
| 766 | if ( !empty( $ai_json_default_model ) ) { |
| 767 | $query->set_model( $ai_json_default_model ); |
| 768 | } |
| 769 | else { |
| 770 | $query->set_model( MWAI_FALLBACK_MODEL_JSON ); |
| 771 | } |
| 772 | $reply = $mwai_core->run_query( $query ); |
| 773 | try { |
| 774 | $json = json_decode( $reply->result, true ); |
| 775 | return $json; |
| 776 | } |
| 777 | catch ( Exception $e ) { |
| 778 | throw new Exception( 'The result is not a valid JSON.' ); |
| 779 | } |
| 780 | } |
| 781 | |
| 782 | /** |
| 783 | * Executes an audio transcription query. |
| 784 | * |
| 785 | * @param string $url The URL of the audio file to transcribe. |
| 786 | * @param string|null $path The path to the audio file. If provided, the audio data will be read from this file. |
| 787 | * @param array $params Additional parameters for the transcription query. |
| 788 | * |
| 789 | * @return string The transcribed text. |
| 790 | */ |
| 791 | public function simpleTranscribeAudio( $url = null, $path = null, $params = [] ) { |
| 792 | global $mwai_core; |
| 793 | $ai_audio_default_env = $this->core->get_option( 'ai_audio_default_env' ); |
| 794 | $ai_audio_default_model = $this->core->get_option( 'ai_audio_default_model' ); |
| 795 | |
| 796 | if ( empty( $ai_audio_default_model ) ) { |
| 797 | $ai_audio_default_model = 'whisper-1'; // Default transcription model |
| 798 | } |
| 799 | |
| 800 | $query = new Meow_MWAI_Query_Transcribe(); |
| 801 | |
| 802 | if ( !empty( $ai_audio_default_env ) ) { |
| 803 | $query->set_env_id( $ai_audio_default_env ); |
| 804 | } |
| 805 | if ( !empty( $ai_audio_default_model ) ) { |
| 806 | $query->set_model( $ai_audio_default_model ); |
| 807 | } |
| 808 | |
| 809 | $query->inject_params( $params ); |
| 810 | |
| 811 | if ( !empty( $url ) ) { |
| 812 | // Use 'files' as the purpose for audio files |
| 813 | $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'files' ) ); |
| 814 | } |
| 815 | else if ( !empty( $path ) ) { |
| 816 | // Use 'files' as the purpose for audio files |
| 817 | $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'files' ) ); |
| 818 | } |
| 819 | else { |
| 820 | throw new Exception( 'Either a URL or a path must be provided for the audio file.' ); |
| 821 | } |
| 822 | |
| 823 | $reply = $mwai_core->run_query( $query ); |
| 824 | return $reply->result; |
| 825 | } |
| 826 | #endregion |
| 827 | |
| 828 | #region Standard API |
| 829 | /** |
| 830 | * Checks if a text is safe or not. |
| 831 | * |
| 832 | * @param string $text The text to check. |
| 833 | * |
| 834 | * @return bool True if the text is safe, false otherwise. |
| 835 | */ |
| 836 | public function moderationCheck( $text ) { |
| 837 | global $mwai_core; |
| 838 | $openai = Meow_MWAI_Engines_Factory::get_openai( $mwai_core ); |
| 839 | $res = $openai->moderate( $text ); |
| 840 | if ( !empty( $res ) && !empty( $res['results'] ) ) { |
| 841 | return (bool) $res['results'][0]['flagged']; |
| 842 | } |
| 843 | } |
| 844 | #endregion |
| 845 | |
| 846 | #region Standard API (No REST API) |
| 847 | |
| 848 | /** |
| 849 | * Checks the status of the AI environments. |
| 850 | * |
| 851 | * @return array The types of environments that are available. |
| 852 | */ |
| 853 | public function checkStatus() { |
| 854 | $env_types = []; |
| 855 | $ai_envs = $this->core->get_option( 'ai_envs' ); |
| 856 | if ( empty( $ai_envs ) ) { |
| 857 | throw new Exception( 'There are no AI environments yet.' ); |
| 858 | } |
| 859 | foreach ( $ai_envs as $env ) { |
| 860 | if ( !empty( $env['apikey'] ) ) { |
| 861 | if ( !in_array( $env['type'], $env_types ) ) { |
| 862 | $env_types[] = $env['type']; |
| 863 | } |
| 864 | } |
| 865 | } |
| 866 | if ( empty( $env_types ) ) { |
| 867 | throw new Exception( 'There are no AI environments with an API key yet.' ); |
| 868 | } |
| 869 | return $env_types; |
| 870 | } |
| 871 | |
| 872 | /** |
| 873 | * Get function name by ID |
| 874 | */ |
| 875 | private function get_function_name_by_id( $funcId ) { |
| 876 | // Get function from registry using the static method |
| 877 | $function = MeowPro_MWAI_FunctionAware::get_function( 'code-engine', $funcId ); |
| 878 | if ( $function && isset( $function->name ) ) { |
| 879 | return $function->name; |
| 880 | } |
| 881 | |
| 882 | // If not found, try snippet-vault type as well |
| 883 | $function = MeowPro_MWAI_FunctionAware::get_function( 'snippet-vault', $funcId ); |
| 884 | if ( $function && isset( $function->name ) ) { |
| 885 | return $function->name; |
| 886 | } |
| 887 | |
| 888 | return null; |
| 889 | } |
| 890 | #endregion |
| 891 | } |
| 892 |