PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.9.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.9.2
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 11 months 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 1 year ago logging.php 1 year ago reply.php 11 months ago rest.php 11 months ago
api.php
983 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
511 // Check for common parameter mistakes and provide helpful guidance
512 if ( empty( $url ) && isset( $params['imageUrl'] ) ) {
513 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "imageUrl"?' );
514 }
515 if ( empty( $url ) && isset( $params['image_url'] ) ) {
516 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "image_url"?' );
517 }
518
519 $options = isset( $params['options'] ) ? $params['options'] : [];
520 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
521 if ( !empty( $scope ) ) {
522 $options['scope'] = $scope;
523 }
524 if ( empty( $message ) ) {
525 throw new Exception( 'The message is required.' );
526 }
527 if ( empty( $url ) ) {
528 throw new Exception( 'The "url" parameter is required for image analysis.' );
529 }
530
531 if ( $this->debug ) {
532 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
533 $debug = sprintf( 'REST [SimpleVisionQuery]: %s, %s', $shortMessage, json_encode( $options ) );
534 Meow_MWAI_Logging::log( $debug );
535 }
536
537 $reply = $this->simpleVisionQuery( $message, $url, null, $options );
538 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
539 }
540 catch ( Exception $e ) {
541 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
542 }
543 }
544
545 public function rest_simpleJsonQuery( $request ) {
546 try {
547 $params = $request->get_params();
548 $message = isset( $params['message'] ) ? $params['message'] : '';
549 if ( empty( $message ) ) {
550 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
551 }
552 $options = isset( $params['options'] ) ? $params['options'] : [];
553 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
554 if ( !empty( $scope ) ) {
555 $options['scope'] = $scope;
556 }
557 if ( empty( $message ) ) {
558 throw new Exception( 'The message is required.' );
559 }
560
561 if ( $this->debug ) {
562 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
563 $debug = sprintf( 'REST [SimpleJsonQuery]: %s, %s', $shortMessage, json_encode( $options ) );
564 Meow_MWAI_Logging::log( $debug );
565 }
566
567 $reply = $this->simpleJsonQuery( $message, null, null, $options );
568 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
569 }
570 catch ( Exception $e ) {
571 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
572 }
573 }
574
575 public function rest_moderationCheck( $request ) {
576 try {
577 $params = $request->get_params();
578 $text = isset( $params['text'] ) ? $params['text'] : '';
579
580 // Check for common parameter mistakes and provide helpful guidance
581 if ( empty( $text ) && isset( $params['message'] ) ) {
582 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "message"?' );
583 }
584 if ( empty( $text ) && isset( $params['content'] ) ) {
585 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "content"?' );
586 }
587
588 if ( empty( $text ) ) {
589 throw new Exception( 'The "text" parameter is required for content moderation.' );
590 }
591
592 if ( $this->debug ) {
593 $shortText = Meow_MWAI_Logging::shorten( $text, 64 );
594 $debug = sprintf( 'REST [ModerationCheck]: %s', $shortText );
595 Meow_MWAI_Logging::log( $debug );
596 }
597
598 $reply = $this->moderationCheck( $text );
599 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
600 }
601 catch ( Exception $e ) {
602 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
603 }
604 }
605
606 public function rest_simpleTranscribeAudio( $request ) {
607 try {
608 $params = $request->get_params();
609 $url = isset( $params['url'] ) ? $params['url'] : '';
610 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
611
612 // Check for common parameter mistakes and provide helpful guidance
613 if ( empty( $url ) && empty( $mediaId ) ) {
614 if ( isset( $params['audioUrl'] ) ) {
615 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audioUrl"?' );
616 }
617 if ( isset( $params['audio_url'] ) ) {
618 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audio_url"?' );
619 }
620 if ( isset( $params['file'] ) ) {
621 throw new Exception( 'Use "url" for remote files or "mediaId" for uploaded files. Found "file" parameter instead.' );
622 }
623 }
624
625 $options = isset( $params['options'] ) ? $params['options'] : [];
626 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
627
628 if ( !empty( $scope ) ) {
629 $options['scope'] = $scope;
630 }
631
632 // Get file path from mediaId if provided
633 $path = null;
634 if ( $mediaId > 0 ) {
635 $path = get_attached_file( $mediaId );
636 if ( empty( $path ) ) {
637 throw new Exception( 'The media file cannot be found.' );
638 }
639 }
640
641 if ( empty( $url ) && empty( $path ) ) {
642 throw new Exception( 'Either a "url" parameter or a "mediaId" parameter is required for audio transcription.' );
643 }
644
645 if ( $this->debug ) {
646 $debug = sprintf( 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s',
647 $url ? 'provided' : 'none',
648 $mediaId,
649 json_encode( $options )
650 );
651 Meow_MWAI_Logging::log( $debug );
652 }
653
654 $reply = $this->simpleTranscribeAudio( $url, $path, $options );
655 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
656 }
657 catch ( Exception $e ) {
658 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
659 }
660 }
661 #endregion
662
663 #region Simple API
664 /**
665 * Executes a vision query.`
666 *
667 * @param string $message The prompt for the AI.
668 * @param string $url The URL of the image to analyze.
669 * @param string|null $path The path to the image file. If provided, the image data will be read from this file.
670 * @param array $params Additional parameters for the AI query.
671 *
672 * @return string The result of the AI query.
673 */
674 public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) {
675 global $mwai_core;
676 $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' );
677 $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' );
678 if ( empty( $ai_vision_default_model ) ) {
679 $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION;
680 }
681 $query = new Meow_MWAI_Query_Text( $message );
682 if ( !empty( $ai_vision_default_env ) ) {
683 $query->set_env_id( $ai_vision_default_env );
684 }
685 if ( !empty( $ai_vision_default_model ) ) {
686 $query->set_model( $ai_vision_default_model );
687 }
688 $query->inject_params( $params );
689 if ( isset( $params['image_remote_upload'] ) ) {
690 $query->image_remote_upload = $params['image_remote_upload'];
691 }
692 if ( !empty( $url ) ) {
693 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) );
694 }
695 else if ( !empty( $path ) ) {
696 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
697 }
698 $reply = $mwai_core->run_query( $query );
699 return $reply->result;
700 }
701
702 /**
703 * Executes a chatbot query.
704 * It will use the discussion if chatId is provided in the parameters.
705 *
706 * @param string $botId The ID of the chatbot.
707 * @param string $message The prompt for the AI.
708 * @param array $params Additional parameters for the AI query.
709 *
710 * @return string The result of the AI query.
711 */
712 public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) {
713 if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) {
714 if ( $this->core->get_option( 'chatbot_discussions' ) ) {
715 $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] );
716 if ( !empty( $discussion ) ) {
717 $params['messages'] = $discussion['messages'];
718 }
719 }
720 else {
721 $this->core->log( 'The chatId was provided; but the discussions are not enabled.' );
722 }
723 }
724 $data = $this->chatbot_module->chat_submit( $botId, $message, null, $params );
725 return $onlyReply ? $data['reply'] : $data;
726 }
727
728 /**
729 * Executes a text query.
730 *
731 * @param string $message The prompt for the AI.
732 * @param array $params Additional parameters for the AI query.
733 *
734 * @return string The result of the AI query.
735 */
736 public function simpleTextQuery( $message, $params = [] ) {
737 global $mwai_core;
738 $query = new Meow_MWAI_Query_Text( $message );
739 $query->inject_params( $params );
740 $reply = $mwai_core->run_query( $query );
741 return $reply->result;
742 }
743
744 public function simpleFastTextQuery( $message, $params = [] ) {
745 global $mwai_core;
746 $query = new Meow_MWAI_Query_Text( $message );
747
748 // Use the Default (Fast) model and environment
749 $fastDefaultModel = $mwai_core->get_option( 'ai_fast_default_model' );
750 if ( !empty( $fastDefaultModel ) ) {
751 $query->set_model( $fastDefaultModel );
752 }
753
754 $fastDefaultEnv = $mwai_core->get_option( 'ai_fast_default_env' );
755 if ( !empty( $fastDefaultEnv ) ) {
756 $query->set_env_id( $fastDefaultEnv );
757 }
758
759 // Inject any additional params (which may override the defaults)
760 $query->inject_params( $params );
761
762 $reply = $mwai_core->run_query( $query );
763 return $reply->result;
764 }
765
766 public function simpleImageQuery( $message, $params = [] ) {
767 global $mwai_core;
768 $query = new Meow_MWAI_Query_Image( $message );
769 $query->inject_params( $params );
770 $reply = $mwai_core->run_query( $query );
771 return $reply->result;
772 }
773
774 public function simpleImageEditQuery( $message, $mediaId, $params = [] ) {
775 global $mwai_core;
776 $query = new Meow_MWAI_Query_EditImage( $message );
777 $query->inject_params( $params );
778 $path = get_attached_file( $mediaId );
779 if ( empty( $path ) ) {
780 throw new Exception( 'The media cannot be found.' );
781 }
782 // TODO: Maybe 'vision' should be 'edit'.
783 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
784 $reply = $mwai_core->run_query( $query );
785 return $reply->result;
786 }
787
788 /**
789 * Generates an image relevant to the text.
790 */
791 public function imageQueryForMediaLibrary( $message, $params = [], $postId = null ) {
792 $query = new Meow_MWAI_Query_Image( $message );
793 $query->inject_params( $params );
794 $query->set_local_download( null );
795 $reply = $this->core->run_query( $query );
796 preg_match( '/\!\[Image\]\((.*?)\)/', $reply->result, $matches );
797 $url = $matches[1] ?? $reply->result;
798
799 // Check if the URL is already a WordPress attachment URL to avoid duplicates
800 $attachmentId = null;
801 $upload_dir = wp_upload_dir();
802 if ( strpos( $url, $upload_dir['baseurl'] ) === 0 ) {
803 // This is already a local WordPress upload, try to find the attachment ID
804 // First try by GUID
805 global $wpdb;
806 $attachmentId = $wpdb->get_var( $wpdb->prepare(
807 "SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'",
808 $url
809 ) );
810
811 // If not found by GUID, try by attachment URL (more reliable)
812 if ( empty( $attachmentId ) ) {
813 $attachmentId = attachment_url_to_postid( $url );
814 }
815 }
816
817 // If not found or not a local URL, add it to the media library
818 if ( empty( $attachmentId ) ) {
819 $attachmentId = $this->core->add_image_from_url( $url, null, null, null, null, null, $postId );
820 if ( empty( $attachmentId ) ) {
821 throw new Exception( 'Could not add the image to the Media Library.' );
822 }
823 }
824
825 // TODO: We should create a nice title, caption, and alt.
826 $media = [
827 'id' => $attachmentId,
828 'url' => wp_get_attachment_url( $attachmentId ),
829 'title' => get_the_title( $attachmentId ),
830 'caption' => wp_get_attachment_caption( $attachmentId ),
831 'alt' => get_post_meta( $attachmentId, '_wp_attachment_image_alt', true )
832 ];
833 return $media;
834 }
835
836 /**
837 * Executes a query that will have to return a JSON result.
838 *
839 * @param string $message The prompt for the AI.
840 * @param array $params Additional parameters for the AI query.
841 *
842 * @return array The result of the AI query.
843 */
844 public function simpleJsonQuery( $message, $url = null, $path = null, $params = [] ) {
845 if ( !empty( $url ) || !empty( $path ) ) {
846 throw new Exception( 'The url and path are not supported yet by the simpleJsonQuery.' );
847 }
848 global $mwai_core;
849 $query = new Meow_MWAI_Query_Text( $message . "\nYour reply must be a formatted JSON." );
850 $query->inject_params( $params );
851 $query->set_response_format( 'json' );
852 $ai_json_default_env = $mwai_core->get_option( 'ai_json_default_env' );
853 $ai_json_default_model = $mwai_core->get_option( 'ai_json_default_model' );
854 if ( !empty( $ai_json_default_env ) ) {
855 $query->set_env_id( $ai_json_default_env );
856 }
857 if ( !empty( $ai_json_default_model ) ) {
858 $query->set_model( $ai_json_default_model );
859 }
860 else {
861 $query->set_model( MWAI_FALLBACK_MODEL_JSON );
862 }
863 $reply = $mwai_core->run_query( $query );
864 try {
865 $json = json_decode( $reply->result, true );
866 return $json;
867 }
868 catch ( Exception $e ) {
869 throw new Exception( 'The result is not a valid JSON.' );
870 }
871 }
872
873 /**
874 * Executes an audio transcription query.
875 *
876 * @param string $url The URL of the audio file to transcribe.
877 * @param string|null $path The path to the audio file. If provided, the audio data will be read from this file.
878 * @param array $params Additional parameters for the transcription query.
879 *
880 * @return string The transcribed text.
881 */
882 public function simpleTranscribeAudio( $url = null, $path = null, $params = [] ) {
883 global $mwai_core;
884 $ai_audio_default_env = $this->core->get_option( 'ai_audio_default_env' );
885 $ai_audio_default_model = $this->core->get_option( 'ai_audio_default_model' );
886
887 if ( empty( $ai_audio_default_model ) ) {
888 $ai_audio_default_model = 'whisper-1'; // Default transcription model
889 }
890
891 $query = new Meow_MWAI_Query_Transcribe();
892
893 if ( !empty( $ai_audio_default_env ) ) {
894 $query->set_env_id( $ai_audio_default_env );
895 }
896 if ( !empty( $ai_audio_default_model ) ) {
897 $query->set_model( $ai_audio_default_model );
898 }
899
900 $query->inject_params( $params );
901
902 if ( !empty( $url ) ) {
903 // Use 'files' as the purpose for audio files
904 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'files' ) );
905 }
906 else if ( !empty( $path ) ) {
907 // Use 'files' as the purpose for audio files
908 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'files' ) );
909 }
910 else {
911 throw new Exception( 'Either a URL or a path must be provided for the audio file.' );
912 }
913
914 $reply = $mwai_core->run_query( $query );
915 return $reply->result;
916 }
917 #endregion
918
919 #region Standard API
920 /**
921 * Checks if a text is safe or not.
922 *
923 * @param string $text The text to check.
924 *
925 * @return bool True if the text is safe, false otherwise.
926 */
927 public function moderationCheck( $text ) {
928 global $mwai_core;
929 $openai = Meow_MWAI_Engines_Factory::get_openai( $mwai_core );
930 $res = $openai->moderate( $text );
931 if ( !empty( $res ) && !empty( $res['results'] ) ) {
932 return (bool) $res['results'][0]['flagged'];
933 }
934 }
935 #endregion
936
937 #region Standard API (No REST API)
938
939 /**
940 * Checks the status of the AI environments.
941 *
942 * @return array The types of environments that are available.
943 */
944 public function checkStatus() {
945 $env_types = [];
946 $ai_envs = $this->core->get_option( 'ai_envs' );
947 if ( empty( $ai_envs ) ) {
948 throw new Exception( 'There are no AI environments yet.' );
949 }
950 foreach ( $ai_envs as $env ) {
951 if ( !empty( $env['apikey'] ) ) {
952 if ( !in_array( $env['type'], $env_types ) ) {
953 $env_types[] = $env['type'];
954 }
955 }
956 }
957 if ( empty( $env_types ) ) {
958 throw new Exception( 'There are no AI environments with an API key yet.' );
959 }
960 return $env_types;
961 }
962
963 /**
964 * Get function name by ID
965 */
966 private function get_function_name_by_id( $funcId ) {
967 // Get function from registry using the static method
968 $function = MeowPro_MWAI_FunctionAware::get_function( 'code-engine', $funcId );
969 if ( $function && isset( $function->name ) ) {
970 return $function->name;
971 }
972
973 // If not found, try snippet-vault type as well
974 $function = MeowPro_MWAI_FunctionAware::get_function( 'snippet-vault', $funcId );
975 if ( $function && isset( $function->name ) ) {
976 return $function->name;
977 }
978
979 return null;
980 }
981 #endregion
982 }
983