PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.8.9
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.8.9
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / api.php
ai-engine / classes Last commit date
data 1 year ago engines 11 months ago exceptions 1 year ago modules 1 year ago query 1 year ago rest 1 year ago services 11 months ago admin.php 1 year ago api.php 11 months ago core.php 11 months ago discussion.php 1 year ago event.php 1 year ago init.php 11 months ago logging.php 1 year 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