PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.2.8
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.2.8
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 6 months ago exceptions 1 year ago modules 6 months ago query 6 months ago rest 8 months ago services 6 months ago admin.php 7 months ago api.php 6 months ago core.php 6 months ago discussion.php 1 year ago event.php 1 year ago init.php 7 months ago logging.php 1 year ago reply.php 6 months ago rest.php 6 months ago
api.php
1246 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(
691 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s',
692 $url ? 'provided' : 'none',
693 $mediaId,
694 json_encode( $options )
695 );
696 Meow_MWAI_Logging::log( $debug );
697 }
698
699 $reply = $this->simpleTranscribeAudio( $url, $path, $options );
700 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
701 }
702 catch ( Exception $e ) {
703 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
704 }
705 }
706
707 public function rest_simpleFileUpload( $request ) {
708 try {
709 $params = $request->get_params();
710 $files = $request->get_file_params();
711
712 // Check if file is provided
713 if ( empty( $files['file'] ) ) {
714 // Check for base64 encoded file data
715 $base64 = isset( $params['base64'] ) ? $params['base64'] : '';
716 $filename = isset( $params['filename'] ) ? $params['filename'] : '';
717
718 if ( empty( $base64 ) ) {
719 throw new Exception( 'Either a file upload or base64 encoded data is required.' );
720 }
721
722 // Handle base64 upload
723 $options = isset( $params['options'] ) ? $params['options'] : [];
724 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'analysis';
725 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
726 $target = isset( $params['target'] ) ? $params['target'] : null;
727 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
728
729 if ( empty( $filename ) ) {
730 $filename = 'upload-' . time() . '.png'; // Default filename for base64
731 }
732
733 // Log the request if debug is enabled
734 if ( $this->debug ) {
735 $debug = sprintf(
736 'REST [SimpleFileUpload]: base64 upload, filename=%s, purpose=%s',
737 $filename,
738 $purpose
739 );
740 Meow_MWAI_Logging::log( $debug );
741 }
742
743 $result = $this->simpleFileUpload( null, $base64, $filename, $purpose, $ttl, $target, $metadata );
744 }
745 else {
746 // Handle regular file upload
747 $file = $files['file'];
748 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'analysis';
749 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
750 $target = isset( $params['target'] ) ? $params['target'] : null;
751 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
752
753 if ( $this->debug ) {
754 $debug = sprintf(
755 'REST [SimpleFileUpload]: file upload, name=%s, purpose=%s',
756 $file['name'],
757 $purpose
758 );
759 Meow_MWAI_Logging::log( $debug );
760 }
761
762 $result = $this->simpleFileUpload( $file, null, null, $purpose, $ttl, $target, $metadata );
763 }
764
765 return new WP_REST_Response( [ 'success' => true, 'data' => $result ], 200 );
766 }
767 catch ( Exception $e ) {
768 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
769 }
770 }
771 #endregion
772
773 #region Simple API
774 /**
775 * Executes a vision query.`
776 *
777 * @param string $message The prompt for the AI.
778 * @param string $url The URL of the image to analyze.
779 * @param string|null $path The path to the image file. If provided, the image data will be read from this file.
780 * @param array $params Additional parameters for the AI query.
781 *
782 * @return string The result of the AI query.
783 */
784 public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) {
785 global $mwai_core;
786 $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' );
787 $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' );
788 if ( empty( $ai_vision_default_model ) ) {
789 $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION;
790 }
791 $query = new Meow_MWAI_Query_Text( $message );
792 if ( !empty( $ai_vision_default_env ) ) {
793 $query->set_env_id( $ai_vision_default_env );
794 }
795 if ( !empty( $ai_vision_default_model ) ) {
796 $query->set_model( $ai_vision_default_model );
797 }
798 $query->inject_params( $params );
799 if ( isset( $params['image_remote_upload'] ) ) {
800 $query->image_remote_upload = $params['image_remote_upload'];
801 }
802 if ( !empty( $url ) ) {
803 $query->add_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) );
804 }
805 else if ( !empty( $path ) ) {
806 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
807 }
808 $reply = $mwai_core->run_query( $query );
809 return $reply->result;
810 }
811
812 /**
813 * Executes a chatbot query.
814 * It will use the discussion if chatId is provided in the parameters.
815 *
816 * @param string $botId The ID of the chatbot.
817 * @param string $message The prompt for the AI.
818 * @param array $params Additional parameters for the AI query.
819 *
820 * @return string The result of the AI query.
821 */
822 public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) {
823 if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) {
824 if ( $this->core->get_option( 'chatbot_discussions' ) && $this->discussions_module ) {
825 $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] );
826 if ( !empty( $discussion ) ) {
827 $params['messages'] = $discussion['messages'];
828
829 // CRITICAL: Also pass the discussion metadata for Responses API support
830 // The chatbot module needs the previousResponseId from discussion's extra field
831 if ( !empty( $discussion['extra'] ) ) {
832 $extra = json_decode( $discussion['extra'], true );
833
834 // Check for both possible field names
835 $responseId = $extra['previousResponseId'] ?? $extra['responseId'] ?? null;
836 $responseDate = $extra['previousResponseDate'] ?? $extra['responseDate'] ?? null;
837
838 if ( !empty( $responseId ) ) {
839 // Check if the response ID is still valid (not older than 30 days)
840 $responseDateTimestamp = !empty( $responseDate ) ? strtotime( $responseDate ) : 0;
841 $thirtyDaysAgo = time() - ( 30 * 24 * 60 * 60 );
842
843 if ( $responseDateTimestamp > $thirtyDaysAgo ) {
844 // Pass the previousResponseId directly in params
845 // This will be picked up by inject_params in the query
846 $params['previousResponseId'] = $responseId;
847 }
848 }
849 }
850 }
851 }
852 else {
853 Meow_MWAI_Logging::log( 'The chatId was provided; but the discussions are not enabled.' );
854 }
855 }
856 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
857 $fileIds = isset( $params['fileIds'] ) ? $params['fileIds'] : [];
858 $data = $this->chatbot_module->chat_submit( $botId, $message, $fileId, $params, false, $fileIds );
859 return $onlyReply ? $data['reply'] : $data;
860 }
861
862 /**
863 * Executes a text query.
864 *
865 * @param string $message The prompt for the AI.
866 * @param array $params Additional parameters for the AI query.
867 *
868 * @return string The result of the AI query.
869 */
870 public function simpleTextQuery( $message, $params = [] ) {
871 global $mwai_core;
872 $query = new Meow_MWAI_Query_Text( $message );
873 $query->inject_params( $params );
874 $reply = $mwai_core->run_query( $query );
875 return $reply->result;
876 }
877
878 public function simpleFastTextQuery( $message, $params = [] ) {
879 global $mwai_core;
880 $query = new Meow_MWAI_Query_Text( $message );
881
882 // Use the Default (Fast) model and environment
883 $fastDefaultModel = $mwai_core->get_option( 'ai_fast_default_model' );
884 if ( !empty( $fastDefaultModel ) ) {
885 $query->set_model( $fastDefaultModel );
886 }
887
888 $fastDefaultEnv = $mwai_core->get_option( 'ai_fast_default_env' );
889 if ( !empty( $fastDefaultEnv ) ) {
890 $query->set_env_id( $fastDefaultEnv );
891 }
892
893 // Inject any additional params (which may override the defaults)
894 $query->inject_params( $params );
895
896 try {
897 $reply = $mwai_core->run_query( $query );
898 return $reply->result;
899 }
900 catch ( Exception $e ) {
901 // If Fast Model fails, try with default model
902 Meow_MWAI_Logging::warn( 'Fast Model failed: ' . $e->getMessage() . ' - Falling back to default model.' );
903
904 // Create a new query with default model/env
905 $fallbackQuery = new Meow_MWAI_Query_Text( $message );
906
907 $defaultModel = $mwai_core->get_option( 'ai_default_model' );
908 if ( !empty( $defaultModel ) ) {
909 $fallbackQuery->set_model( $defaultModel );
910 }
911
912 $defaultEnv = $mwai_core->get_option( 'ai_default_env' );
913 if ( !empty( $defaultEnv ) ) {
914 $fallbackQuery->set_env_id( $defaultEnv );
915 }
916
917 // Inject params again (except model/env which we just set)
918 $fallbackParams = $params;
919 unset( $fallbackParams['model'] );
920 unset( $fallbackParams['envId'] );
921 $fallbackQuery->inject_params( $fallbackParams );
922
923 $reply = $mwai_core->run_query( $fallbackQuery );
924 return $reply->result;
925 }
926 }
927
928 public function simpleImageQuery( $message, $params = [] ) {
929 global $mwai_core;
930 $query = new Meow_MWAI_Query_Image( $message );
931 $query->inject_params( $params );
932 $reply = $mwai_core->run_query( $query );
933 return $reply->result;
934 }
935
936 public function simpleImageEditQuery( $message, $mediaId, $params = [] ) {
937 global $mwai_core;
938 $query = new Meow_MWAI_Query_EditImage( $message );
939 $query->inject_params( $params );
940 $path = get_attached_file( $mediaId );
941 if ( empty( $path ) ) {
942 throw new Exception( 'The media cannot be found.' );
943 }
944 // TODO: Maybe 'vision' should be 'edit'.
945 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
946 $reply = $mwai_core->run_query( $query );
947 return $reply->result;
948 }
949
950 /**
951 * Generates an image relevant to the text.
952 */
953 public function imageQueryForMediaLibrary( $message, $params = [], $postId = null ) {
954 $query = new Meow_MWAI_Query_Image( $message );
955 $query->inject_params( $params );
956 $query->set_local_download( null );
957 $reply = $this->core->run_query( $query );
958 preg_match( '/\!\[Image\]\((.*?)\)/', $reply->result, $matches );
959 $url = $matches[1] ?? $reply->result;
960
961 // Check if the URL is already a WordPress attachment URL to avoid duplicates
962 $attachmentId = null;
963 $upload_dir = wp_upload_dir();
964 if ( strpos( $url, $upload_dir['baseurl'] ) === 0 ) {
965 // This is already a local WordPress upload, try to find the attachment ID
966 // First try by GUID
967 global $wpdb;
968 $attachmentId = $wpdb->get_var( $wpdb->prepare(
969 "SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'",
970 $url
971 ) );
972
973 // If not found by GUID, try by attachment URL (more reliable)
974 if ( empty( $attachmentId ) ) {
975 $attachmentId = attachment_url_to_postid( $url );
976 }
977 }
978
979 // If not found or not a local URL, add it to the media library
980 if ( empty( $attachmentId ) ) {
981 $attachmentId = $this->core->add_image_from_url( $url, null, null, null, null, null, $postId );
982 if ( empty( $attachmentId ) ) {
983 throw new Exception( 'Could not add the image to the Media Library.' );
984 }
985 }
986
987 // TODO: We should create a nice title, caption, and alt.
988 $media = [
989 'id' => $attachmentId,
990 'url' => wp_get_attachment_url( $attachmentId ),
991 'title' => get_the_title( $attachmentId ),
992 'caption' => wp_get_attachment_caption( $attachmentId ),
993 'alt' => get_post_meta( $attachmentId, '_wp_attachment_image_alt', true )
994 ];
995 return $media;
996 }
997
998 /**
999 * Executes a query that will have to return a JSON result.
1000 *
1001 * @param string $message The prompt for the AI.
1002 * @param array $params Additional parameters for the AI query.
1003 *
1004 * @return array The result of the AI query.
1005 */
1006 public function simpleJsonQuery( $message, $url = null, $path = null, $params = [] ) {
1007 if ( !empty( $url ) || !empty( $path ) ) {
1008 throw new Exception( 'The url and path are not supported yet by the simpleJsonQuery.' );
1009 }
1010 global $mwai_core;
1011 $query = new Meow_MWAI_Query_Text( $message . "\nYour reply must be a formatted JSON." );
1012 $query->inject_params( $params );
1013 $query->set_response_format( 'json' );
1014 $ai_json_default_env = $mwai_core->get_option( 'ai_json_default_env' );
1015 $ai_json_default_model = $mwai_core->get_option( 'ai_json_default_model' );
1016 if ( !empty( $ai_json_default_env ) ) {
1017 $query->set_env_id( $ai_json_default_env );
1018 }
1019 if ( !empty( $ai_json_default_model ) ) {
1020 $query->set_model( $ai_json_default_model );
1021 }
1022 else {
1023 $query->set_model( MWAI_FALLBACK_MODEL_JSON );
1024 }
1025 $reply = $mwai_core->run_query( $query );
1026 try {
1027 $json = json_decode( $reply->result, true );
1028 return $json;
1029 }
1030 catch ( Exception $e ) {
1031 throw new Exception( 'The result is not a valid JSON.' );
1032 }
1033 }
1034
1035 /**
1036 * Uploads a file to the system.
1037 *
1038 * @param array|null $file The file array from $_FILES.
1039 * @param string|null $base64 Base64 encoded file data.
1040 * @param string|null $filename The filename for base64 uploads.
1041 * @param string $purpose The purpose of the file upload (e.g., 'files', 'vision', 'assistant').
1042 * @param int $ttl Time to live in seconds. Default 3600 (1 hour).
1043 * @param string|null $target Target location: 'uploads' or 'library'.
1044 * @param array $metadata Additional metadata to store with the file.
1045 *
1046 * @return array Array with 'id' (refId) and 'url' of the uploaded file.
1047 */
1048 public function simpleFileUpload( $file = null, $base64 = null, $filename = null, $purpose = 'analysis', $ttl = 3600, $target = null, $metadata = [] ) {
1049 global $mwai_core;
1050
1051 if ( !$this->core->files ) {
1052 throw new Exception( 'Files module is not available.' );
1053 }
1054
1055 // Determine target from settings if not provided
1056 if ( empty( $target ) ) {
1057 $target = $this->core->get_option( 'image_local_upload', 'uploads' );
1058 }
1059
1060 try {
1061 if ( !empty( $base64 ) ) {
1062 // Handle base64 upload
1063 if ( empty( $filename ) ) {
1064 $filename = 'upload-' . time() . '.dat';
1065 }
1066
1067 // Validate filename extension for base64 uploads
1068 $validate = wp_check_filetype( $filename );
1069 if ( $validate['type'] == false ) {
1070 throw new Exception( 'File type is not allowed.' );
1071 }
1072
1073 // For base64 uploads, we need to decode and create a temp file first
1074 $binary = base64_decode( $base64 );
1075 if ( !$binary ) {
1076 throw new Exception( 'Invalid base64 data.' );
1077 }
1078
1079 // Create a temporary file
1080 $tmp_path = wp_tempnam( 'mwai-upload' );
1081 file_put_contents( $tmp_path, $binary );
1082
1083 // Use the regular upload method
1084 $refId = $this->core->files->upload_file(
1085 $tmp_path,
1086 $filename,
1087 $purpose,
1088 $metadata,
1089 null, // envId
1090 $target,
1091 $ttl
1092 );
1093
1094 // Clean up temp file if it was uploaded to library
1095 if ( $target === 'library' && file_exists( $tmp_path ) ) {
1096 @unlink( $tmp_path );
1097 }
1098
1099 $url = $this->core->files->get_url( $refId );
1100
1101 return [
1102 'id' => $refId,
1103 'url' => $url
1104 ];
1105 }
1106 else if ( !empty( $file ) && is_array( $file ) ) {
1107 // Handle regular file upload
1108 if ( !empty( $file['error'] ) ) {
1109 throw new Exception( 'File upload error: ' . $file['error'] );
1110 }
1111
1112 $refId = $this->core->files->upload_file(
1113 $file['tmp_name'],
1114 $file['name'],
1115 $purpose,
1116 $metadata,
1117 null, // envId
1118 $target,
1119 $ttl
1120 );
1121
1122 $url = $this->core->files->get_url( $refId );
1123
1124 return [
1125 'id' => $refId,
1126 'url' => $url
1127 ];
1128 }
1129 else {
1130 throw new Exception( 'Either a file or base64 data must be provided.' );
1131 }
1132 }
1133 catch ( Exception $e ) {
1134 throw new Exception( 'File upload failed: ' . $e->getMessage() );
1135 }
1136 }
1137
1138 /**
1139 * Executes an audio transcription query.
1140 *
1141 * @param string $url The URL of the audio file to transcribe.
1142 * @param string|null $path The path to the audio file. If provided, the audio data will be read from this file.
1143 * @param array $params Additional parameters for the transcription query.
1144 *
1145 * @return string The transcribed text.
1146 */
1147 public function simpleTranscribeAudio( $url = null, $path = null, $params = [] ) {
1148 global $mwai_core;
1149 $ai_audio_default_env = $this->core->get_option( 'ai_audio_default_env' );
1150 $ai_audio_default_model = $this->core->get_option( 'ai_audio_default_model' );
1151
1152 if ( empty( $ai_audio_default_model ) ) {
1153 $ai_audio_default_model = 'whisper-1'; // Default transcription model
1154 }
1155
1156 $query = new Meow_MWAI_Query_Transcribe();
1157
1158 if ( !empty( $ai_audio_default_env ) ) {
1159 $query->set_env_id( $ai_audio_default_env );
1160 }
1161 if ( !empty( $ai_audio_default_model ) ) {
1162 $query->set_model( $ai_audio_default_model );
1163 }
1164
1165 $query->inject_params( $params );
1166
1167 if ( !empty( $url ) ) {
1168 $query->add_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'analysis' ) );
1169 }
1170 else if ( !empty( $path ) ) {
1171 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'analysis' ) );
1172 }
1173 else {
1174 throw new Exception( 'Either a URL or a path must be provided for the audio file.' );
1175 }
1176
1177 $reply = $mwai_core->run_query( $query );
1178 return $reply->result;
1179 }
1180 #endregion
1181
1182 #region Standard API
1183 /**
1184 * Checks if a text is safe or not.
1185 *
1186 * @param string $text The text to check.
1187 *
1188 * @return bool True if the text is safe, false otherwise.
1189 */
1190 public function moderationCheck( $text ) {
1191 global $mwai_core;
1192 $openai = Meow_MWAI_Engines_Factory::get_openai( $mwai_core );
1193 $res = $openai->moderate( $text );
1194 if ( !empty( $res ) && !empty( $res['results'] ) ) {
1195 return (bool) $res['results'][0]['flagged'];
1196 }
1197 }
1198 #endregion
1199
1200 #region Standard API (No REST API)
1201
1202 /**
1203 * Checks the status of the AI environments.
1204 *
1205 * @return array The types of environments that are available.
1206 */
1207 public function checkStatus() {
1208 $env_types = [];
1209 $ai_envs = $this->core->get_option( 'ai_envs' );
1210 if ( empty( $ai_envs ) ) {
1211 throw new Exception( 'There are no AI environments yet.' );
1212 }
1213 foreach ( $ai_envs as $env ) {
1214 if ( !empty( $env['apikey'] ) ) {
1215 if ( !in_array( $env['type'], $env_types ) ) {
1216 $env_types[] = $env['type'];
1217 }
1218 }
1219 }
1220 if ( empty( $env_types ) ) {
1221 throw new Exception( 'There are no AI environments with an API key yet.' );
1222 }
1223 return $env_types;
1224 }
1225
1226 /**
1227 * Get function name by ID
1228 */
1229 private function get_function_name_by_id( $funcId ) {
1230 // Get function from registry using the static method
1231 $function = MeowPro_MWAI_FunctionAware::get_function( 'code-engine', $funcId );
1232 if ( $function && isset( $function->name ) ) {
1233 return $function->name;
1234 }
1235
1236 // If not found, try snippet-vault type as well
1237 $function = MeowPro_MWAI_FunctionAware::get_function( 'snippet-vault', $funcId );
1238 if ( $function && isset( $function->name ) ) {
1239 return $function->name;
1240 }
1241
1242 return null;
1243 }
1244 #endregion
1245 }
1246