PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.2.2
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.2.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 7 months ago exceptions 1 year ago modules 7 months ago query 7 months ago rest 8 months ago services 7 months ago admin.php 7 months ago api.php 7 months ago core.php 7 months ago discussion.php 1 year ago event.php 1 year ago init.php 7 months ago logging.php 1 year ago reply.php 7 months ago rest.php 7 months ago
api.php
1245 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 /**
21 * Sanitize REST API options to remove sensitive parameters
22 * that should not be controlled by untrusted users
23 */
24 private function sanitize_rest_options( $options ) {
25 if ( !is_array( $options ) ) {
26 return [];
27 }
28
29 // List of sensitive parameters that should not be set via REST API
30 $blocked_params = [
31 'apiKey',
32 'path', // Block path parameter to prevent PHAR deserialization attacks
33 ];
34 $blocked_params = apply_filters( 'mwai_blocked_rest_params', $blocked_params, $options );
35
36 // Remove blocked parameters
37 foreach ( $blocked_params as $param ) {
38 unset( $options[$param] );
39 }
40
41 return $options;
42 }
43
44 public function rest_api_init() {
45 $public_api = $this->core->get_option( 'public_api' );
46 if ( !$public_api ) {
47 return;
48 }
49 $this->bearer_token = $this->core->get_option( 'public_api_bearer_token' );
50 if ( !empty( $this->bearer_token ) ) {
51 add_filter( 'mwai_allow_public_api', [ $this, 'auth_via_bearer_token' ], 10, 3 );
52 }
53
54 register_rest_route( 'mwai/v1', '/simpleAuthCheck', [
55 'methods' => 'GET',
56 'callback' => [ $this, 'rest_simpleAuthCheck' ],
57 'permission_callback' => function ( $request ) {
58 return $this->core->can_access_public_api( 'simpleAuthCheck', $request );
59 },
60 ] );
61 register_rest_route( 'mwai/v1', '/simpleTextQuery', [
62 'methods' => 'POST',
63 'callback' => [ $this, 'rest_simpleTextQuery' ],
64 'permission_callback' => function ( $request ) {
65 return $this->core->can_access_public_api( 'simpleTextQuery', $request );
66 },
67 ] );
68 register_rest_route( 'mwai/v1', '/simpleFastTextQuery', [
69 'methods' => 'POST',
70 'callback' => [ $this, 'rest_simpleFastTextQuery' ],
71 'permission_callback' => function ( $request ) {
72 return $this->core->can_access_public_api( 'simpleFastTextQuery', $request );
73 },
74 ] );
75 register_rest_route( 'mwai/v1', '/simpleImageQuery', [
76 'methods' => 'POST',
77 'callback' => [ $this, 'rest_simpleImageQuery' ],
78 'permission_callback' => function ( $request ) {
79 return $this->core->can_access_public_api( 'simpleImageQuery', $request );
80 },
81 ] );
82 register_rest_route( 'mwai/v1', '/simpleImageEditQuery', [
83 'methods' => 'POST',
84 'callback' => [ $this, 'rest_simpleImageEditQuery' ],
85 'permission_callback' => function ( $request ) {
86 return $this->core->can_access_public_api( 'simpleImageEditQuery', $request );
87 },
88 ] );
89 register_rest_route( 'mwai/v1', '/simpleVisionQuery', [
90 'methods' => 'POST',
91 'callback' => [ $this, 'rest_simpleVisionQuery' ],
92 'permission_callback' => function ( $request ) {
93 return $this->core->can_access_public_api( 'simpleVisionQuery', $request );
94 },
95 ] );
96 register_rest_route( 'mwai/v1', '/simpleJsonQuery', [
97 'methods' => 'POST',
98 'callback' => [ $this, 'rest_simpleJsonQuery' ],
99 'permission_callback' => function ( $request ) {
100 return $this->core->can_access_public_api( 'simpleJsonQuery', $request );
101 },
102 ] );
103 register_rest_route( 'mwai/v1', '/moderationCheck', [
104 'methods' => 'POST',
105 'callback' => [ $this, 'rest_moderationCheck' ],
106 'permission_callback' => function ( $request ) {
107 return $this->core->can_access_public_api( 'moderationCheck', $request );
108 },
109 ] );
110 register_rest_route( 'mwai/v1', '/simpleTranscribeAudio', [
111 'methods' => 'POST',
112 'callback' => [ $this, 'rest_simpleTranscribeAudio' ],
113 'permission_callback' => function ( $request ) {
114 return $this->core->can_access_public_api( 'simpleTranscribeAudio', $request );
115 },
116 ] );
117 register_rest_route( 'mwai/v1', '/simpleFileUpload', [
118 'methods' => 'POST',
119 'callback' => [ $this, 'rest_simpleFileUpload' ],
120 'permission_callback' => function ( $request ) {
121 return $this->core->can_access_public_api( 'simpleFileUpload', $request );
122 },
123 ] );
124
125 if ( $this->chatbot_module ) {
126 register_rest_route( 'mwai/v1', '/simpleChatbotQuery', [
127 'methods' => 'POST',
128 'callback' => [ $this, 'rest_simpleChatbotQuery' ],
129 'permission_callback' => function ( $request ) {
130 return $this->core->can_access_public_api( 'simpleChatbotQuery', $request );
131 },
132 ] );
133
134 register_rest_route( 'mwai/v1', '/listChatbots', [
135 'methods' => 'GET',
136 'callback' => [ $this, 'rest_listChatbots' ],
137 'permission_callback' => function ( $request ) {
138 return $this->core->can_access_public_api( 'listChatbots', $request );
139 },
140 ] );
141 }
142 }
143
144 public function rest_simpleAuthCheck( $request ) {
145 try {
146 $params = $request->get_params();
147 $current_user = wp_get_current_user();
148 $current_email = $current_user->user_email;
149 return new WP_REST_Response( [ 'success' => true, 'data' => [
150 'type' => 'email',
151 'value' => $current_email
152 ] ], 200 );
153 }
154 catch ( Exception $e ) {
155 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
156 }
157 }
158
159 public function auth_via_bearer_token( $allow, $feature, $extra ) {
160 if ( !empty( $extra ) && !empty( $extra->get_header( 'Authorization' ) ) ) {
161 $token = $extra->get_header( 'Authorization' );
162 $token = str_replace( 'Bearer ', '', $token );
163 if ( $token === $this->bearer_token ) {
164 // We set the current user to the first admin.
165 $admin = $this->core->get_admin_user();
166 wp_set_current_user( $admin->ID, $admin->user_login );
167 return true;
168 }
169 }
170 return $allow;
171 }
172
173 public function rest_simpleChatbotQuery( $request ) {
174 try {
175 $params = $request->get_params();
176 $botId = isset( $params['botId'] ) ? $params['botId'] : '';
177 $message = isset( $params['message'] ) ? $params['message'] : '';
178 if ( empty( $message ) ) {
179 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
180 }
181 $chatId = isset( $params['chatId'] ) ? $params['chatId'] : null;
182 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
183 $fileIds = isset( $params['fileIds'] ) ? $params['fileIds'] : null;
184 $queryParams = [];
185 if ( !empty( $chatId ) ) {
186 $queryParams['chatId'] = $chatId;
187 }
188 if ( !empty( $fileId ) ) {
189 $queryParams['fileId'] = $fileId;
190 }
191 if ( !empty( $fileIds ) && is_array( $fileIds ) ) {
192 $queryParams['fileIds'] = $fileIds;
193 }
194 if ( empty( $botId ) || empty( $message ) ) {
195 throw new Exception( __( 'The botId and message are required.', 'ai-engine' ) );
196 }
197
198 if ( $this->debug ) {
199 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
200 $debug = sprintf( 'REST [SimpleChatbotQuery]: %s, %s', $shortMessage, json_encode( $queryParams ) );
201 Meow_MWAI_Logging::log( $debug );
202 }
203
204 $reply = $this->simpleChatbotQuery( $botId, $message, $queryParams, false );
205 return new WP_REST_Response( [
206 'success' => true,
207 'data' => $reply['reply'],
208 'extra' => [
209 'actions' => $reply['actions'],
210 'chatId' => $reply['chatId']
211 ]
212 ], 200 );
213 }
214 catch ( Exception $e ) {
215 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
216 }
217 }
218
219 public function rest_listChatbots( $request ) {
220 try {
221 // Get all chatbots
222 $chatbots = get_option( 'mwai_chatbots', [] );
223 $environments = $this->core->get_option( 'ai_envs' );
224 $mcp_envs = $this->core->get_option( 'mcp_envs', [] );
225
226 // Get all models from all environments
227 $all_models = [];
228 foreach ( $environments as $env ) {
229 try {
230 $engine = Meow_MWAI_Engines_Factory::get( $this->core, $env['id'] );
231 $env_models = $engine->retrieve_models();
232 foreach ( $env_models as $model ) {
233 $all_models[$model['model']] = $model;
234 }
235 }
236 catch ( Exception $e ) {
237 // Skip environments that fail
238 }
239 }
240
241 // Debug: Log model info for gpt-4.1-mini
242 if ( $this->debug && isset( $all_models['gpt-4.1-mini'] ) ) {
243 error_log( '[AI Engine API] Model info for gpt-4.1-mini: ' . json_encode( $all_models['gpt-4.1-mini'] ) );
244 }
245
246 // Get registered functions
247 $functions = apply_filters( 'mwai_functions_list', [] );
248 $function_names = [];
249 foreach ( $functions as $function ) {
250 $function_names[] = $function->name ?? 'unknown';
251 }
252
253 $result = [];
254
255 foreach ( $chatbots as $chatbotId => $chatbot ) {
256 // Debug log the chatbot structure
257 if ( $this->debug && ( $chatbot['name'] ?? '' ) === 'Jordy' ) {
258 error_log( '[AI Engine API] Jordy chatbot structure: ' . json_encode( $chatbot ) );
259 }
260
261 // Basic info
262 $info = [
263 'id' => $chatbot['botId'] ?? $chatbotId, // Use botId if available, fallback to array index
264 'name' => $chatbot['name'] ?? 'Unnamed',
265 'type' => 'chat', // Default type
266 'model' => null,
267 'model_name' => null,
268 'environment' => null,
269 'environment_name' => null,
270 'functions' => [],
271 'tools' => [], // Add tools array
272 'mcp_servers' => []
273 ];
274
275 // Determine chatbot type
276 if ( !empty( $chatbot['type'] ) ) {
277 $info['type'] = $chatbot['type'];
278 }
279 else {
280 // Try to infer type from model or other properties
281 if ( !empty( $chatbot['model'] ) ) {
282 if ( strpos( $chatbot['model'], 'realtime' ) !== false ) {
283 $info['type'] = 'realtime';
284 }
285 else if ( strpos( $chatbot['model'], 'image' ) !== false || strpos( $chatbot['model'], 'dall-e' ) !== false ) {
286 $info['type'] = 'images';
287 }
288 else if ( !empty( $chatbot['assistantId'] ) ) {
289 $info['type'] = 'assistant';
290 }
291 }
292 }
293
294 // Get model info
295 if ( !empty( $chatbot['model'] ) ) {
296 $info['model'] = $chatbot['model'];
297 // Find model name
298 if ( isset( $all_models[$chatbot['model']] ) ) {
299 $info['model_name'] = $all_models[$chatbot['model']]['name'] ?? $chatbot['model'];
300 }
301 else {
302 $info['model_name'] = $chatbot['model'];
303 }
304 }
305
306 // Get environment info
307 if ( !empty( $chatbot['envId'] ) ) {
308 $info['environment'] = $chatbot['envId'];
309 // Find environment name
310 foreach ( $environments as $env ) {
311 if ( $env['id'] === $chatbot['envId'] ) {
312 $info['environment_name'] = $env['name'] ?? $chatbot['envId'];
313 break;
314 }
315 }
316 }
317
318 // Check if it uses functions - get specific function names if available
319 if ( !empty( $chatbot['functions'] ) ) {
320 if ( is_array( $chatbot['functions'] ) ) {
321 // Functions are stored as array of objects with id and type
322 $chatbot_functions = [];
323 foreach ( $chatbot['functions'] as $func ) {
324 if ( isset( $func['id'] ) ) {
325 // Try to find function name by ID
326 $func_name = $this->get_function_name_by_id( $func['id'] );
327 if ( $func_name ) {
328 $chatbot_functions[] = $func_name;
329 }
330 else {
331 // Fallback: include the ID if name not found
332 $chatbot_functions[] = 'function_' . $func['id'];
333 }
334 }
335 }
336 $info['functions'] = $chatbot_functions;
337 }
338 else if ( $chatbot['functions'] === true ) {
339 // If functions is just true, it uses all registered functions
340 $info['functions'] = $function_names;
341 }
342 }
343
344 // Check for tools (Web Search, Image Generation)
345 if ( !empty( $chatbot['tools'] ) && is_array( $chatbot['tools'] ) ) {
346 // Filter tools based on model capabilities
347 $supported_tools = [];
348 if ( !empty( $chatbot['model'] ) ) {
349 // Try exact match first
350 $model_key = $chatbot['model'];
351 $model_info = $all_models[$model_key] ?? null;
352
353 // If not found and it's an OpenRouter model, try without prefix
354 if ( !$model_info && strpos( $model_key, '/' ) !== false ) {
355 $model_key = substr( $model_key, strpos( $model_key, '/' ) + 1 );
356 $model_info = $all_models[$model_key] ?? null;
357 }
358
359 if ( $model_info ) {
360 $model_tools = $model_info['tools'] ?? [];
361
362 // Only include tools that are both configured AND supported by the model
363 foreach ( $chatbot['tools'] as $tool ) {
364 if ( in_array( $tool, $model_tools ) ) {
365 $supported_tools[] = $tool;
366 }
367 }
368 }
369 else {
370 // If model not found in our list, keep all configured tools
371 // This allows custom models to use tools
372 $supported_tools = $chatbot['tools'];
373 }
374 }
375 $info['tools'] = $supported_tools;
376 }
377
378 // Check for MCP servers
379 if ( !empty( $chatbot['mcp_servers'] ) && is_array( $chatbot['mcp_servers'] ) ) {
380 foreach ( $chatbot['mcp_servers'] as $mcpServer ) {
381 if ( !empty( $mcpServer['id'] ) ) {
382 // Find MCP server name
383 foreach ( $mcp_envs as $mcp ) {
384 if ( $mcp['id'] === $mcpServer['id'] ) {
385 $info['mcp_servers'][] = [
386 'id' => $mcp['id'],
387 'name' => $mcp['name'] ?? 'Unnamed MCP Server'
388 ];
389 break;
390 }
391 }
392 }
393 }
394 }
395
396 $result[] = $info;
397 }
398
399 return new WP_REST_Response( [
400 'success' => true,
401 'data' => $result
402 ], 200 );
403 }
404 catch ( Exception $e ) {
405 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
406 }
407 }
408
409 public function rest_simpleTextQuery( $request ) {
410 try {
411 $params = $request->get_params();
412 $message = isset( $params['message'] ) ? $params['message'] : '';
413 if ( empty( $message ) ) {
414 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
415 }
416 $options = isset( $params['options'] ) ? $params['options'] : [];
417 $options = $this->sanitize_rest_options( $options );
418 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
419 if ( !empty( $scope ) ) {
420 $options['scope'] = $scope;
421 }
422 if ( empty( $message ) ) {
423 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
424 }
425
426 if ( $this->debug ) {
427 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
428 $debug = sprintf( 'REST [SimpleTextQuery]: %s, %s', $shortMessage, json_encode( $options ) );
429 Meow_MWAI_Logging::log( $debug );
430 }
431
432 $reply = $this->simpleTextQuery( $message, $options );
433 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
434 }
435 catch ( Exception $e ) {
436 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
437 }
438 }
439
440 public function rest_simpleFastTextQuery( $request ) {
441 try {
442 $params = $request->get_params();
443 $message = isset( $params['message'] ) ? $params['message'] : '';
444 if ( empty( $message ) ) {
445 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
446 }
447 $options = isset( $params['options'] ) ? $params['options'] : [];
448 $options = $this->sanitize_rest_options( $options );
449 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
450 if ( !empty( $scope ) ) {
451 $options['scope'] = $scope;
452 }
453 if ( empty( $message ) ) {
454 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
455 }
456
457 if ( $this->debug ) {
458 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
459 $debug = sprintf( 'REST [SimpleFastTextQuery]: %s, %s', $shortMessage, json_encode( $options ) );
460 Meow_MWAI_Logging::log( $debug );
461 }
462
463 $reply = $this->simpleFastTextQuery( $message, $options );
464 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
465 }
466 catch ( Exception $e ) {
467 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
468 }
469 }
470
471 public function rest_simpleImageQuery( $request ) {
472 try {
473 $params = $request->get_params();
474 $message = isset( $params['message'] ) ? $params['message'] : '';
475 if ( empty( $message ) ) {
476 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
477 }
478 $options = isset( $params['options'] ) ? $params['options'] : [];
479 $resolution = isset( $params['resolution'] ) ? $params['resolution'] : '';
480 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
481 if ( !empty( $scope ) ) {
482 $options['scope'] = $scope;
483 }
484 if ( empty( $message ) ) {
485 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
486 }
487 if ( !empty( $resolution ) ) {
488 $options['resolution'] = $resolution;
489 }
490
491 if ( $this->debug ) {
492 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
493 $debug = sprintf( 'REST [SimpleImageQuery]: %s, %s', $shortMessage, json_encode( $options ) );
494 Meow_MWAI_Logging::log( $debug );
495 }
496
497 $reply = $this->simpleImageQuery( $message, $options );
498 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
499 }
500 catch ( Exception $e ) {
501 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
502 }
503 }
504
505 public function rest_simpleImageEditQuery( $request ) {
506 try {
507 $params = $request->get_params();
508 $message = isset( $params['message'] ) ? $params['message'] : '';
509 if ( empty( $message ) ) {
510 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
511 }
512 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
513 $options = isset( $params['options'] ) ? $params['options'] : [];
514 $resolution = isset( $params['resolution'] ) ? $params['resolution'] : '';
515 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
516 if ( !empty( $scope ) ) {
517 $options['scope'] = $scope;
518 }
519 if ( empty( $message ) ) {
520 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
521 }
522 if ( empty( $mediaId ) ) {
523 throw new Exception( 'The mediaId is required.' );
524 }
525 if ( !empty( $resolution ) ) {
526 $options['resolution'] = $resolution;
527 }
528
529 if ( $this->debug ) {
530 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
531 $debug = sprintf( 'REST [SimpleImageEditQuery]: %s, %s', $shortMessage, json_encode( $options ) );
532 Meow_MWAI_Logging::log( $debug );
533 }
534
535 $reply = $this->simpleImageEditQuery( $message, $mediaId, $options );
536 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
537 }
538 catch ( Exception $e ) {
539 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
540 }
541 }
542
543 public function rest_simpleVisionQuery( $request ) {
544 try {
545 $params = $request->get_params();
546 $message = isset( $params['message'] ) ? $params['message'] : '';
547 if ( empty( $message ) ) {
548 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
549 }
550 $url = isset( $params['url'] ) ? $params['url'] : '';
551
552 // Check for common parameter mistakes and provide helpful guidance
553 if ( empty( $url ) && isset( $params['imageUrl'] ) ) {
554 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "imageUrl"?' );
555 }
556 if ( empty( $url ) && isset( $params['image_url'] ) ) {
557 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "image_url"?' );
558 }
559
560 $options = isset( $params['options'] ) ? $params['options'] : [];
561 $options = $this->sanitize_rest_options( $options );
562 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
563 if ( !empty( $scope ) ) {
564 $options['scope'] = $scope;
565 }
566 if ( empty( $message ) ) {
567 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
568 }
569 if ( empty( $url ) ) {
570 throw new Exception( 'The "url" parameter is required for image analysis.' );
571 }
572
573 if ( $this->debug ) {
574 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
575 $debug = sprintf( 'REST [SimpleVisionQuery]: %s, %s', $shortMessage, json_encode( $options ) );
576 Meow_MWAI_Logging::log( $debug );
577 }
578
579 $reply = $this->simpleVisionQuery( $message, $url, null, $options );
580 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
581 }
582 catch ( Exception $e ) {
583 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
584 }
585 }
586
587 public function rest_simpleJsonQuery( $request ) {
588 try {
589 $params = $request->get_params();
590 $message = isset( $params['message'] ) ? $params['message'] : '';
591 if ( empty( $message ) ) {
592 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
593 }
594 $options = isset( $params['options'] ) ? $params['options'] : [];
595 $options = $this->sanitize_rest_options( $options );
596 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
597 if ( !empty( $scope ) ) {
598 $options['scope'] = $scope;
599 }
600 if ( empty( $message ) ) {
601 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
602 }
603
604 if ( $this->debug ) {
605 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
606 $debug = sprintf( 'REST [SimpleJsonQuery]: %s, %s', $shortMessage, json_encode( $options ) );
607 Meow_MWAI_Logging::log( $debug );
608 }
609
610 $reply = $this->simpleJsonQuery( $message, null, null, $options );
611 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
612 }
613 catch ( Exception $e ) {
614 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
615 }
616 }
617
618 public function rest_moderationCheck( $request ) {
619 try {
620 $params = $request->get_params();
621 $text = isset( $params['text'] ) ? $params['text'] : '';
622
623 // Check for common parameter mistakes and provide helpful guidance
624 if ( empty( $text ) && isset( $params['message'] ) ) {
625 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "message"?' );
626 }
627 if ( empty( $text ) && isset( $params['content'] ) ) {
628 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "content"?' );
629 }
630
631 if ( empty( $text ) ) {
632 throw new Exception( 'The "text" parameter is required for content moderation.' );
633 }
634
635 if ( $this->debug ) {
636 $shortText = Meow_MWAI_Logging::shorten( $text, 64 );
637 $debug = sprintf( 'REST [ModerationCheck]: %s', $shortText );
638 Meow_MWAI_Logging::log( $debug );
639 }
640
641 $reply = $this->moderationCheck( $text );
642 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
643 }
644 catch ( Exception $e ) {
645 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
646 }
647 }
648
649 public function rest_simpleTranscribeAudio( $request ) {
650 try {
651 $params = $request->get_params();
652 $url = isset( $params['url'] ) ? $params['url'] : '';
653 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
654
655 // Check for common parameter mistakes and provide helpful guidance
656 if ( empty( $url ) && empty( $mediaId ) ) {
657 if ( isset( $params['audioUrl'] ) ) {
658 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audioUrl"?' );
659 }
660 if ( isset( $params['audio_url'] ) ) {
661 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audio_url"?' );
662 }
663 if ( isset( $params['file'] ) ) {
664 throw new Exception( 'Use "url" for remote files or "mediaId" for uploaded files. Found "file" parameter instead.' );
665 }
666 }
667
668 $options = isset( $params['options'] ) ? $params['options'] : [];
669 $options = $this->sanitize_rest_options( $options );
670 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
671
672 if ( !empty( $scope ) ) {
673 $options['scope'] = $scope;
674 }
675
676 // Get file path from mediaId if provided
677 $path = null;
678 if ( $mediaId > 0 ) {
679 $path = get_attached_file( $mediaId );
680 if ( empty( $path ) ) {
681 throw new Exception( 'The media file cannot be found.' );
682 }
683 }
684
685 if ( empty( $url ) && empty( $path ) ) {
686 throw new Exception( 'Either a "url" parameter or a "mediaId" parameter is required for audio transcription.' );
687 }
688
689 if ( $this->debug ) {
690 $debug = sprintf( 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s',
691 $url ? 'provided' : 'none',
692 $mediaId,
693 json_encode( $options )
694 );
695 Meow_MWAI_Logging::log( $debug );
696 }
697
698 $reply = $this->simpleTranscribeAudio( $url, $path, $options );
699 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
700 }
701 catch ( Exception $e ) {
702 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
703 }
704 }
705
706 public function rest_simpleFileUpload( $request ) {
707 try {
708 $params = $request->get_params();
709 $files = $request->get_file_params();
710
711 // Check if file is provided
712 if ( empty( $files['file'] ) ) {
713 // Check for base64 encoded file data
714 $base64 = isset( $params['base64'] ) ? $params['base64'] : '';
715 $filename = isset( $params['filename'] ) ? $params['filename'] : '';
716
717 if ( empty( $base64 ) ) {
718 throw new Exception( 'Either a file upload or base64 encoded data is required.' );
719 }
720
721 // Handle base64 upload
722 $options = isset( $params['options'] ) ? $params['options'] : [];
723 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
724 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
725 $target = isset( $params['target'] ) ? $params['target'] : null;
726 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
727
728 if ( empty( $filename ) ) {
729 $filename = 'upload-' . time() . '.png'; // Default filename for base64
730 }
731
732 // Log the request if debug is enabled
733 if ( $this->debug ) {
734 $debug = sprintf( 'REST [SimpleFileUpload]: base64 upload, filename=%s, purpose=%s',
735 $filename,
736 $purpose
737 );
738 Meow_MWAI_Logging::log( $debug );
739 }
740
741 $result = $this->simpleFileUpload( null, $base64, $filename, $purpose, $ttl, $target, $metadata );
742 }
743 else {
744 // Handle regular file upload
745 $file = $files['file'];
746 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
747 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
748 $target = isset( $params['target'] ) ? $params['target'] : null;
749 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
750
751 if ( $this->debug ) {
752 $debug = sprintf( 'REST [SimpleFileUpload]: file upload, name=%s, purpose=%s',
753 $file['name'],
754 $purpose
755 );
756 Meow_MWAI_Logging::log( $debug );
757 }
758
759 $result = $this->simpleFileUpload( $file, null, null, $purpose, $ttl, $target, $metadata );
760 }
761
762 return new WP_REST_Response( [ 'success' => true, 'data' => $result ], 200 );
763 }
764 catch ( Exception $e ) {
765 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
766 }
767 }
768 #endregion
769
770 #region Simple API
771 /**
772 * Executes a vision query.`
773 *
774 * @param string $message The prompt for the AI.
775 * @param string $url The URL of the image to analyze.
776 * @param string|null $path The path to the image file. If provided, the image data will be read from this file.
777 * @param array $params Additional parameters for the AI query.
778 *
779 * @return string The result of the AI query.
780 */
781 public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) {
782 global $mwai_core;
783 $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' );
784 $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' );
785 if ( empty( $ai_vision_default_model ) ) {
786 $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION;
787 }
788 $query = new Meow_MWAI_Query_Text( $message );
789 if ( !empty( $ai_vision_default_env ) ) {
790 $query->set_env_id( $ai_vision_default_env );
791 }
792 if ( !empty( $ai_vision_default_model ) ) {
793 $query->set_model( $ai_vision_default_model );
794 }
795 $query->inject_params( $params );
796 if ( isset( $params['image_remote_upload'] ) ) {
797 $query->image_remote_upload = $params['image_remote_upload'];
798 }
799 if ( !empty( $url ) ) {
800 $query->add_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) );
801 }
802 else if ( !empty( $path ) ) {
803 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
804 }
805 $reply = $mwai_core->run_query( $query );
806 return $reply->result;
807 }
808
809 /**
810 * Executes a chatbot query.
811 * It will use the discussion if chatId is provided in the parameters.
812 *
813 * @param string $botId The ID of the chatbot.
814 * @param string $message The prompt for the AI.
815 * @param array $params Additional parameters for the AI query.
816 *
817 * @return string The result of the AI query.
818 */
819 public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) {
820 if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) {
821 if ( $this->core->get_option( 'chatbot_discussions' ) && $this->discussions_module ) {
822 $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] );
823 if ( !empty( $discussion ) ) {
824 $params['messages'] = $discussion['messages'];
825
826 // CRITICAL: Also pass the discussion metadata for Responses API support
827 // The chatbot module needs the previousResponseId from discussion's extra field
828 if ( !empty( $discussion['extra'] ) ) {
829 $extra = json_decode( $discussion['extra'], true );
830
831 // Check for both possible field names
832 $responseId = $extra['previousResponseId'] ?? $extra['responseId'] ?? null;
833 $responseDate = $extra['previousResponseDate'] ?? $extra['responseDate'] ?? null;
834
835 if ( !empty( $responseId ) ) {
836 // Check if the response ID is still valid (not older than 30 days)
837 $responseDateTimestamp = !empty( $responseDate ) ? strtotime( $responseDate ) : 0;
838 $thirtyDaysAgo = time() - ( 30 * 24 * 60 * 60 );
839
840 if ( $responseDateTimestamp > $thirtyDaysAgo ) {
841 // Pass the previousResponseId directly in params
842 // This will be picked up by inject_params in the query
843 $params['previousResponseId'] = $responseId;
844 }
845 }
846 }
847 }
848 }
849 else {
850 Meow_MWAI_Logging::log( 'The chatId was provided; but the discussions are not enabled.' );
851 }
852 }
853 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
854 $fileIds = isset( $params['fileIds'] ) ? $params['fileIds'] : [];
855 $data = $this->chatbot_module->chat_submit( $botId, $message, $fileId, $params, false, $fileIds );
856 return $onlyReply ? $data['reply'] : $data;
857 }
858
859 /**
860 * Executes a text query.
861 *
862 * @param string $message The prompt for the AI.
863 * @param array $params Additional parameters for the AI query.
864 *
865 * @return string The result of the AI query.
866 */
867 public function simpleTextQuery( $message, $params = [] ) {
868 global $mwai_core;
869 $query = new Meow_MWAI_Query_Text( $message );
870 $query->inject_params( $params );
871 $reply = $mwai_core->run_query( $query );
872 return $reply->result;
873 }
874
875 public function simpleFastTextQuery( $message, $params = [] ) {
876 global $mwai_core;
877 $query = new Meow_MWAI_Query_Text( $message );
878
879 // Use the Default (Fast) model and environment
880 $fastDefaultModel = $mwai_core->get_option( 'ai_fast_default_model' );
881 if ( !empty( $fastDefaultModel ) ) {
882 $query->set_model( $fastDefaultModel );
883 }
884
885 $fastDefaultEnv = $mwai_core->get_option( 'ai_fast_default_env' );
886 if ( !empty( $fastDefaultEnv ) ) {
887 $query->set_env_id( $fastDefaultEnv );
888 }
889
890 // Inject any additional params (which may override the defaults)
891 $query->inject_params( $params );
892
893 try {
894 $reply = $mwai_core->run_query( $query );
895 return $reply->result;
896 }
897 catch ( Exception $e ) {
898 // If Fast Model fails, try with default model
899 Meow_MWAI_Logging::warn( "Fast Model failed: " . $e->getMessage() . " - Falling back to default model." );
900
901 // Create a new query with default model/env
902 $fallbackQuery = new Meow_MWAI_Query_Text( $message );
903
904 $defaultModel = $mwai_core->get_option( 'ai_default_model' );
905 if ( !empty( $defaultModel ) ) {
906 $fallbackQuery->set_model( $defaultModel );
907 }
908
909 $defaultEnv = $mwai_core->get_option( 'ai_default_env' );
910 if ( !empty( $defaultEnv ) ) {
911 $fallbackQuery->set_env_id( $defaultEnv );
912 }
913
914 // Inject params again (except model/env which we just set)
915 $fallbackParams = $params;
916 unset( $fallbackParams['model'] );
917 unset( $fallbackParams['envId'] );
918 $fallbackQuery->inject_params( $fallbackParams );
919
920 $reply = $mwai_core->run_query( $fallbackQuery );
921 return $reply->result;
922 }
923 }
924
925 public function simpleImageQuery( $message, $params = [] ) {
926 global $mwai_core;
927 $query = new Meow_MWAI_Query_Image( $message );
928 $query->inject_params( $params );
929 $reply = $mwai_core->run_query( $query );
930 return $reply->result;
931 }
932
933 public function simpleImageEditQuery( $message, $mediaId, $params = [] ) {
934 global $mwai_core;
935 $query = new Meow_MWAI_Query_EditImage( $message );
936 $query->inject_params( $params );
937 $path = get_attached_file( $mediaId );
938 if ( empty( $path ) ) {
939 throw new Exception( 'The media cannot be found.' );
940 }
941 // TODO: Maybe 'vision' should be 'edit'.
942 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
943 $reply = $mwai_core->run_query( $query );
944 return $reply->result;
945 }
946
947 /**
948 * Generates an image relevant to the text.
949 */
950 public function imageQueryForMediaLibrary( $message, $params = [], $postId = null ) {
951 $query = new Meow_MWAI_Query_Image( $message );
952 $query->inject_params( $params );
953 $query->set_local_download( null );
954 $reply = $this->core->run_query( $query );
955 preg_match( '/\!\[Image\]\((.*?)\)/', $reply->result, $matches );
956 $url = $matches[1] ?? $reply->result;
957
958 // Check if the URL is already a WordPress attachment URL to avoid duplicates
959 $attachmentId = null;
960 $upload_dir = wp_upload_dir();
961 if ( strpos( $url, $upload_dir['baseurl'] ) === 0 ) {
962 // This is already a local WordPress upload, try to find the attachment ID
963 // First try by GUID
964 global $wpdb;
965 $attachmentId = $wpdb->get_var( $wpdb->prepare(
966 "SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'",
967 $url
968 ) );
969
970 // If not found by GUID, try by attachment URL (more reliable)
971 if ( empty( $attachmentId ) ) {
972 $attachmentId = attachment_url_to_postid( $url );
973 }
974 }
975
976 // If not found or not a local URL, add it to the media library
977 if ( empty( $attachmentId ) ) {
978 $attachmentId = $this->core->add_image_from_url( $url, null, null, null, null, null, $postId );
979 if ( empty( $attachmentId ) ) {
980 throw new Exception( 'Could not add the image to the Media Library.' );
981 }
982 }
983
984 // TODO: We should create a nice title, caption, and alt.
985 $media = [
986 'id' => $attachmentId,
987 'url' => wp_get_attachment_url( $attachmentId ),
988 'title' => get_the_title( $attachmentId ),
989 'caption' => wp_get_attachment_caption( $attachmentId ),
990 'alt' => get_post_meta( $attachmentId, '_wp_attachment_image_alt', true )
991 ];
992 return $media;
993 }
994
995 /**
996 * Executes a query that will have to return a JSON result.
997 *
998 * @param string $message The prompt for the AI.
999 * @param array $params Additional parameters for the AI query.
1000 *
1001 * @return array The result of the AI query.
1002 */
1003 public function simpleJsonQuery( $message, $url = null, $path = null, $params = [] ) {
1004 if ( !empty( $url ) || !empty( $path ) ) {
1005 throw new Exception( 'The url and path are not supported yet by the simpleJsonQuery.' );
1006 }
1007 global $mwai_core;
1008 $query = new Meow_MWAI_Query_Text( $message . "\nYour reply must be a formatted JSON." );
1009 $query->inject_params( $params );
1010 $query->set_response_format( 'json' );
1011 $ai_json_default_env = $mwai_core->get_option( 'ai_json_default_env' );
1012 $ai_json_default_model = $mwai_core->get_option( 'ai_json_default_model' );
1013 if ( !empty( $ai_json_default_env ) ) {
1014 $query->set_env_id( $ai_json_default_env );
1015 }
1016 if ( !empty( $ai_json_default_model ) ) {
1017 $query->set_model( $ai_json_default_model );
1018 }
1019 else {
1020 $query->set_model( MWAI_FALLBACK_MODEL_JSON );
1021 }
1022 $reply = $mwai_core->run_query( $query );
1023 try {
1024 $json = json_decode( $reply->result, true );
1025 return $json;
1026 }
1027 catch ( Exception $e ) {
1028 throw new Exception( 'The result is not a valid JSON.' );
1029 }
1030 }
1031
1032 /**
1033 * Uploads a file to the system.
1034 *
1035 * @param array|null $file The file array from $_FILES.
1036 * @param string|null $base64 Base64 encoded file data.
1037 * @param string|null $filename The filename for base64 uploads.
1038 * @param string $purpose The purpose of the file upload (e.g., 'files', 'vision', 'assistant').
1039 * @param int $ttl Time to live in seconds. Default 3600 (1 hour).
1040 * @param string|null $target Target location: 'uploads' or 'library'.
1041 * @param array $metadata Additional metadata to store with the file.
1042 *
1043 * @return array Array with 'id' (refId) and 'url' of the uploaded file.
1044 */
1045 public function simpleFileUpload( $file = null, $base64 = null, $filename = null, $purpose = 'files', $ttl = 3600, $target = null, $metadata = [] ) {
1046 global $mwai_core;
1047
1048 if ( !$this->core->files ) {
1049 throw new Exception( 'Files module is not available.' );
1050 }
1051
1052 // Determine target from settings if not provided
1053 if ( empty( $target ) ) {
1054 $target = $this->core->get_option( 'image_local_upload', 'uploads' );
1055 }
1056
1057 try {
1058 if ( !empty( $base64 ) ) {
1059 // Handle base64 upload
1060 if ( empty( $filename ) ) {
1061 $filename = 'upload-' . time() . '.dat';
1062 }
1063
1064 // Validate filename extension for base64 uploads
1065 $validate = wp_check_filetype( $filename );
1066 if ( $validate['type'] == false ) {
1067 throw new Exception( 'File type is not allowed.' );
1068 }
1069
1070 // For base64 uploads, we need to decode and create a temp file first
1071 $binary = base64_decode( $base64 );
1072 if ( !$binary ) {
1073 throw new Exception( 'Invalid base64 data.' );
1074 }
1075
1076 // Create a temporary file
1077 $tmp_path = wp_tempnam( 'mwai-upload' );
1078 file_put_contents( $tmp_path, $binary );
1079
1080 // Use the regular upload method
1081 $refId = $this->core->files->upload_file(
1082 $tmp_path,
1083 $filename,
1084 $purpose,
1085 $metadata,
1086 null, // envId
1087 $target,
1088 $ttl
1089 );
1090
1091 // Clean up temp file if it was uploaded to library
1092 if ( $target === 'library' && file_exists( $tmp_path ) ) {
1093 @unlink( $tmp_path );
1094 }
1095
1096 $url = $this->core->files->get_url( $refId );
1097
1098 return [
1099 'id' => $refId,
1100 'url' => $url
1101 ];
1102 }
1103 else if ( !empty( $file ) && is_array( $file ) ) {
1104 // Handle regular file upload
1105 if ( !empty( $file['error'] ) ) {
1106 throw new Exception( 'File upload error: ' . $file['error'] );
1107 }
1108
1109 $refId = $this->core->files->upload_file(
1110 $file['tmp_name'],
1111 $file['name'],
1112 $purpose,
1113 $metadata,
1114 null, // envId
1115 $target,
1116 $ttl
1117 );
1118
1119 $url = $this->core->files->get_url( $refId );
1120
1121 return [
1122 'id' => $refId,
1123 'url' => $url
1124 ];
1125 }
1126 else {
1127 throw new Exception( 'Either a file or base64 data must be provided.' );
1128 }
1129 }
1130 catch ( Exception $e ) {
1131 throw new Exception( 'File upload failed: ' . $e->getMessage() );
1132 }
1133 }
1134
1135 /**
1136 * Executes an audio transcription query.
1137 *
1138 * @param string $url The URL of the audio file to transcribe.
1139 * @param string|null $path The path to the audio file. If provided, the audio data will be read from this file.
1140 * @param array $params Additional parameters for the transcription query.
1141 *
1142 * @return string The transcribed text.
1143 */
1144 public function simpleTranscribeAudio( $url = null, $path = null, $params = [] ) {
1145 global $mwai_core;
1146 $ai_audio_default_env = $this->core->get_option( 'ai_audio_default_env' );
1147 $ai_audio_default_model = $this->core->get_option( 'ai_audio_default_model' );
1148
1149 if ( empty( $ai_audio_default_model ) ) {
1150 $ai_audio_default_model = 'whisper-1'; // Default transcription model
1151 }
1152
1153 $query = new Meow_MWAI_Query_Transcribe();
1154
1155 if ( !empty( $ai_audio_default_env ) ) {
1156 $query->set_env_id( $ai_audio_default_env );
1157 }
1158 if ( !empty( $ai_audio_default_model ) ) {
1159 $query->set_model( $ai_audio_default_model );
1160 }
1161
1162 $query->inject_params( $params );
1163
1164 if ( !empty( $url ) ) {
1165 // Use 'files' as the purpose for audio files
1166 $query->add_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'files' ) );
1167 }
1168 else if ( !empty( $path ) ) {
1169 // Use 'files' as the purpose for audio files
1170 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'files' ) );
1171 }
1172 else {
1173 throw new Exception( 'Either a URL or a path must be provided for the audio file.' );
1174 }
1175
1176 $reply = $mwai_core->run_query( $query );
1177 return $reply->result;
1178 }
1179 #endregion
1180
1181 #region Standard API
1182 /**
1183 * Checks if a text is safe or not.
1184 *
1185 * @param string $text The text to check.
1186 *
1187 * @return bool True if the text is safe, false otherwise.
1188 */
1189 public function moderationCheck( $text ) {
1190 global $mwai_core;
1191 $openai = Meow_MWAI_Engines_Factory::get_openai( $mwai_core );
1192 $res = $openai->moderate( $text );
1193 if ( !empty( $res ) && !empty( $res['results'] ) ) {
1194 return (bool) $res['results'][0]['flagged'];
1195 }
1196 }
1197 #endregion
1198
1199 #region Standard API (No REST API)
1200
1201 /**
1202 * Checks the status of the AI environments.
1203 *
1204 * @return array The types of environments that are available.
1205 */
1206 public function checkStatus() {
1207 $env_types = [];
1208 $ai_envs = $this->core->get_option( 'ai_envs' );
1209 if ( empty( $ai_envs ) ) {
1210 throw new Exception( 'There are no AI environments yet.' );
1211 }
1212 foreach ( $ai_envs as $env ) {
1213 if ( !empty( $env['apikey'] ) ) {
1214 if ( !in_array( $env['type'], $env_types ) ) {
1215 $env_types[] = $env['type'];
1216 }
1217 }
1218 }
1219 if ( empty( $env_types ) ) {
1220 throw new Exception( 'There are no AI environments with an API key yet.' );
1221 }
1222 return $env_types;
1223 }
1224
1225 /**
1226 * Get function name by ID
1227 */
1228 private function get_function_name_by_id( $funcId ) {
1229 // Get function from registry using the static method
1230 $function = MeowPro_MWAI_FunctionAware::get_function( 'code-engine', $funcId );
1231 if ( $function && isset( $function->name ) ) {
1232 return $function->name;
1233 }
1234
1235 // If not found, try snippet-vault type as well
1236 $function = MeowPro_MWAI_FunctionAware::get_function( 'snippet-vault', $funcId );
1237 if ( $function && isset( $function->name ) ) {
1238 return $function->name;
1239 }
1240
1241 return null;
1242 }
1243 #endregion
1244 }
1245