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