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