PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 2.9.6
AI Engine – The Chatbot, AI Framework & MCP for WordPress v2.9.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 11 months ago engines 11 months ago exceptions 11 months ago modules 11 months ago query 11 months ago rest 11 months ago services 11 months ago admin.php 11 months ago api.php 11 months ago core.php 11 months ago discussion.php 11 months ago event.php 11 months ago init.php 11 months ago logging.php 11 months ago reply.php 11 months ago rest.php 11 months ago
api.php
1244 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', // Prevent API key override
32 'apikey', // Case variations
33 'api_key',
34 'organizationId',
35 'organization_id',
36 'envId', // Prevent environment switching
37 'env_id',
38 ];
39
40 // Remove blocked parameters
41 foreach ( $blocked_params as $param ) {
42 unset( $options[$param] );
43 }
44
45 return $options;
46 }
47
48 public function rest_api_init() {
49 $public_api = $this->core->get_option( 'public_api' );
50 if ( !$public_api ) {
51 return;
52 }
53 $this->bearer_token = $this->core->get_option( 'public_api_bearer_token' );
54 if ( !empty( $this->bearer_token ) ) {
55 add_filter( 'mwai_allow_public_api', [ $this, 'auth_via_bearer_token' ], 10, 3 );
56 }
57
58 register_rest_route( 'mwai/v1', '/simpleAuthCheck', [
59 'methods' => 'GET',
60 'callback' => [ $this, 'rest_simpleAuthCheck' ],
61 'permission_callback' => function ( $request ) {
62 return $this->core->can_access_public_api( 'simpleAuthCheck', $request );
63 },
64 ] );
65 register_rest_route( 'mwai/v1', '/simpleTextQuery', [
66 'methods' => 'POST',
67 'callback' => [ $this, 'rest_simpleTextQuery' ],
68 'permission_callback' => function ( $request ) {
69 return $this->core->can_access_public_api( 'simpleTextQuery', $request );
70 },
71 ] );
72 register_rest_route( 'mwai/v1', '/simpleFastTextQuery', [
73 'methods' => 'POST',
74 'callback' => [ $this, 'rest_simpleFastTextQuery' ],
75 'permission_callback' => function ( $request ) {
76 return $this->core->can_access_public_api( 'simpleFastTextQuery', $request );
77 },
78 ] );
79 register_rest_route( 'mwai/v1', '/simpleImageQuery', [
80 'methods' => 'POST',
81 'callback' => [ $this, 'rest_simpleImageQuery' ],
82 'permission_callback' => function ( $request ) {
83 return $this->core->can_access_public_api( 'simpleImageQuery', $request );
84 },
85 ] );
86 register_rest_route( 'mwai/v1', '/simpleImageEditQuery', [
87 'methods' => 'POST',
88 'callback' => [ $this, 'rest_simpleImageEditQuery' ],
89 'permission_callback' => function ( $request ) {
90 return $this->core->can_access_public_api( 'simpleImageEditQuery', $request );
91 },
92 ] );
93 register_rest_route( 'mwai/v1', '/simpleVisionQuery', [
94 'methods' => 'POST',
95 'callback' => [ $this, 'rest_simpleVisionQuery' ],
96 'permission_callback' => function ( $request ) {
97 return $this->core->can_access_public_api( 'simpleVisionQuery', $request );
98 },
99 ] );
100 register_rest_route( 'mwai/v1', '/simpleJsonQuery', [
101 'methods' => 'POST',
102 'callback' => [ $this, 'rest_simpleJsonQuery' ],
103 'permission_callback' => function ( $request ) {
104 return $this->core->can_access_public_api( 'simpleJsonQuery', $request );
105 },
106 ] );
107 register_rest_route( 'mwai/v1', '/moderationCheck', [
108 'methods' => 'POST',
109 'callback' => [ $this, 'rest_moderationCheck' ],
110 'permission_callback' => function ( $request ) {
111 return $this->core->can_access_public_api( 'moderationCheck', $request );
112 },
113 ] );
114 register_rest_route( 'mwai/v1', '/simpleTranscribeAudio', [
115 'methods' => 'POST',
116 'callback' => [ $this, 'rest_simpleTranscribeAudio' ],
117 'permission_callback' => function ( $request ) {
118 return $this->core->can_access_public_api( 'simpleTranscribeAudio', $request );
119 },
120 ] );
121 register_rest_route( 'mwai/v1', '/simpleFileUpload', [
122 'methods' => 'POST',
123 'callback' => [ $this, 'rest_simpleFileUpload' ],
124 'permission_callback' => function ( $request ) {
125 return $this->core->can_access_public_api( 'simpleFileUpload', $request );
126 },
127 ] );
128
129 if ( $this->chatbot_module ) {
130 register_rest_route( 'mwai/v1', '/simpleChatbotQuery', [
131 'methods' => 'POST',
132 'callback' => [ $this, 'rest_simpleChatbotQuery' ],
133 'permission_callback' => function ( $request ) {
134 return $this->core->can_access_public_api( 'simpleChatbotQuery', $request );
135 },
136 ] );
137
138 register_rest_route( 'mwai/v1', '/listChatbots', [
139 'methods' => 'GET',
140 'callback' => [ $this, 'rest_listChatbots' ],
141 'permission_callback' => function ( $request ) {
142 return $this->core->can_access_public_api( 'listChatbots', $request );
143 },
144 ] );
145 }
146 }
147
148 public function rest_simpleAuthCheck( $request ) {
149 try {
150 $params = $request->get_params();
151 $current_user = wp_get_current_user();
152 $current_email = $current_user->user_email;
153 return new WP_REST_Response( [ 'success' => true, 'data' => [
154 'type' => 'email',
155 'value' => $current_email
156 ] ], 200 );
157 }
158 catch ( Exception $e ) {
159 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
160 }
161 }
162
163 public function auth_via_bearer_token( $allow, $feature, $extra ) {
164 if ( !empty( $extra ) && !empty( $extra->get_header( 'Authorization' ) ) ) {
165 $token = $extra->get_header( 'Authorization' );
166 $token = str_replace( 'Bearer ', '', $token );
167 if ( $token === $this->bearer_token ) {
168 // We set the current user to the first admin.
169 $admin = $this->core->get_admin_user();
170 wp_set_current_user( $admin->ID, $admin->user_login );
171 return true;
172 }
173 }
174 return $allow;
175 }
176
177 public function rest_simpleChatbotQuery( $request ) {
178 try {
179 $params = $request->get_params();
180 $botId = isset( $params['botId'] ) ? $params['botId'] : '';
181 $message = isset( $params['message'] ) ? $params['message'] : '';
182 if ( empty( $message ) ) {
183 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
184 }
185 $chatId = isset( $params['chatId'] ) ? $params['chatId'] : null;
186 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
187 $queryParams = [];
188 if ( !empty( $chatId ) ) {
189 $queryParams['chatId'] = $chatId;
190 }
191 if ( !empty( $fileId ) ) {
192 $queryParams['fileId'] = $fileId;
193 }
194 if ( empty( $botId ) || empty( $message ) ) {
195 throw new Exception( 'The botId and message are required.' );
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.' );
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.' );
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.' );
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.' );
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.' );
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.' );
602 }
603
604 if ( $this->debug ) {
605 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
606 $debug = sprintf( 'REST [SimpleJsonQuery]: %s, %s', $shortMessage, json_encode( $options ) );
607 Meow_MWAI_Logging::log( $debug );
608 }
609
610 $reply = $this->simpleJsonQuery( $message, null, null, $options );
611 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
612 }
613 catch ( Exception $e ) {
614 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
615 }
616 }
617
618 public function rest_moderationCheck( $request ) {
619 try {
620 $params = $request->get_params();
621 $text = isset( $params['text'] ) ? $params['text'] : '';
622
623 // Check for common parameter mistakes and provide helpful guidance
624 if ( empty( $text ) && isset( $params['message'] ) ) {
625 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "message"?' );
626 }
627 if ( empty( $text ) && isset( $params['content'] ) ) {
628 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "content"?' );
629 }
630
631 if ( empty( $text ) ) {
632 throw new Exception( 'The "text" parameter is required for content moderation.' );
633 }
634
635 if ( $this->debug ) {
636 $shortText = Meow_MWAI_Logging::shorten( $text, 64 );
637 $debug = sprintf( 'REST [ModerationCheck]: %s', $shortText );
638 Meow_MWAI_Logging::log( $debug );
639 }
640
641 $reply = $this->moderationCheck( $text );
642 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
643 }
644 catch ( Exception $e ) {
645 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
646 }
647 }
648
649 public function rest_simpleTranscribeAudio( $request ) {
650 try {
651 $params = $request->get_params();
652 $url = isset( $params['url'] ) ? $params['url'] : '';
653 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
654
655 // Check for common parameter mistakes and provide helpful guidance
656 if ( empty( $url ) && empty( $mediaId ) ) {
657 if ( isset( $params['audioUrl'] ) ) {
658 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audioUrl"?' );
659 }
660 if ( isset( $params['audio_url'] ) ) {
661 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audio_url"?' );
662 }
663 if ( isset( $params['file'] ) ) {
664 throw new Exception( 'Use "url" for remote files or "mediaId" for uploaded files. Found "file" parameter instead.' );
665 }
666 }
667
668 $options = isset( $params['options'] ) ? $params['options'] : [];
669 $options = $this->sanitize_rest_options( $options );
670 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
671
672 if ( !empty( $scope ) ) {
673 $options['scope'] = $scope;
674 }
675
676 // Get file path from mediaId if provided
677 $path = null;
678 if ( $mediaId > 0 ) {
679 $path = get_attached_file( $mediaId );
680 if ( empty( $path ) ) {
681 throw new Exception( 'The media file cannot be found.' );
682 }
683 }
684
685 if ( empty( $url ) && empty( $path ) ) {
686 throw new Exception( 'Either a "url" parameter or a "mediaId" parameter is required for audio transcription.' );
687 }
688
689 if ( $this->debug ) {
690 $debug = sprintf( 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s',
691 $url ? 'provided' : 'none',
692 $mediaId,
693 json_encode( $options )
694 );
695 Meow_MWAI_Logging::log( $debug );
696 }
697
698 $reply = $this->simpleTranscribeAudio( $url, $path, $options );
699 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
700 }
701 catch ( Exception $e ) {
702 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
703 }
704 }
705
706 public function rest_simpleFileUpload( $request ) {
707 try {
708 $params = $request->get_params();
709 $files = $request->get_file_params();
710
711 // Check if file is provided
712 if ( empty( $files['file'] ) ) {
713 // Check for base64 encoded file data
714 $base64 = isset( $params['base64'] ) ? $params['base64'] : '';
715 $filename = isset( $params['filename'] ) ? $params['filename'] : '';
716
717 if ( empty( $base64 ) ) {
718 throw new Exception( 'Either a file upload or base64 encoded data is required.' );
719 }
720
721 // Handle base64 upload
722 $options = isset( $params['options'] ) ? $params['options'] : [];
723 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
724 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
725 $target = isset( $params['target'] ) ? $params['target'] : null;
726 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
727
728 if ( empty( $filename ) ) {
729 $filename = 'upload-' . time() . '.png'; // Default filename for base64
730 }
731
732 // Log the request if debug is enabled
733 if ( $this->debug ) {
734 $debug = sprintf( 'REST [SimpleFileUpload]: base64 upload, filename=%s, purpose=%s',
735 $filename,
736 $purpose
737 );
738 Meow_MWAI_Logging::log( $debug );
739 }
740
741 $result = $this->simpleFileUpload( null, $base64, $filename, $purpose, $ttl, $target, $metadata );
742 }
743 else {
744 // Handle regular file upload
745 $file = $files['file'];
746 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
747 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
748 $target = isset( $params['target'] ) ? $params['target'] : null;
749 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
750
751 if ( $this->debug ) {
752 $debug = sprintf( 'REST [SimpleFileUpload]: file upload, name=%s, purpose=%s',
753 $file['name'],
754 $purpose
755 );
756 Meow_MWAI_Logging::log( $debug );
757 }
758
759 $result = $this->simpleFileUpload( $file, null, null, $purpose, $ttl, $target, $metadata );
760 }
761
762 return new WP_REST_Response( [ 'success' => true, 'data' => $result ], 200 );
763 }
764 catch ( Exception $e ) {
765 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
766 }
767 }
768 #endregion
769
770 #region Simple API
771 /**
772 * Executes a vision query.`
773 *
774 * @param string $message The prompt for the AI.
775 * @param string $url The URL of the image to analyze.
776 * @param string|null $path The path to the image file. If provided, the image data will be read from this file.
777 * @param array $params Additional parameters for the AI query.
778 *
779 * @return string The result of the AI query.
780 */
781 public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) {
782 global $mwai_core;
783 $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' );
784 $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' );
785 if ( empty( $ai_vision_default_model ) ) {
786 $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION;
787 }
788 $query = new Meow_MWAI_Query_Text( $message );
789 if ( !empty( $ai_vision_default_env ) ) {
790 $query->set_env_id( $ai_vision_default_env );
791 }
792 if ( !empty( $ai_vision_default_model ) ) {
793 $query->set_model( $ai_vision_default_model );
794 }
795 $query->inject_params( $params );
796 if ( isset( $params['image_remote_upload'] ) ) {
797 $query->image_remote_upload = $params['image_remote_upload'];
798 }
799 if ( !empty( $url ) ) {
800 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) );
801 }
802 else if ( !empty( $path ) ) {
803 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
804 }
805 $reply = $mwai_core->run_query( $query );
806 return $reply->result;
807 }
808
809 /**
810 * Executes a chatbot query.
811 * It will use the discussion if chatId is provided in the parameters.
812 *
813 * @param string $botId The ID of the chatbot.
814 * @param string $message The prompt for the AI.
815 * @param array $params Additional parameters for the AI query.
816 *
817 * @return string The result of the AI query.
818 */
819 public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) {
820 if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) {
821 if ( $this->core->get_option( 'chatbot_discussions' ) ) {
822 $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] );
823 if ( !empty( $discussion ) ) {
824 $params['messages'] = $discussion['messages'];
825
826 // CRITICAL: Also pass the discussion metadata for Responses API support
827 // The chatbot module needs the previousResponseId from discussion's extra field
828 if ( !empty( $discussion['extra'] ) ) {
829 $extra = json_decode( $discussion['extra'], true );
830
831 // Check for both possible field names
832 $responseId = $extra['previousResponseId'] ?? $extra['responseId'] ?? null;
833 $responseDate = $extra['previousResponseDate'] ?? $extra['responseDate'] ?? null;
834
835 if ( !empty( $responseId ) ) {
836 // Check if the response ID is still valid (not older than 30 days)
837 $responseDateTimestamp = !empty( $responseDate ) ? strtotime( $responseDate ) : 0;
838 $thirtyDaysAgo = time() - ( 30 * 24 * 60 * 60 );
839
840 if ( $responseDateTimestamp > $thirtyDaysAgo ) {
841 // Pass the previousResponseId directly in params
842 // This will be picked up by inject_params in the query
843 $params['previousResponseId'] = $responseId;
844 }
845 }
846 }
847 }
848 }
849 else {
850 Meow_MWAI_Logging::log( 'The chatId was provided; but the discussions are not enabled.' );
851 }
852 }
853 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
854 $data = $this->chatbot_module->chat_submit( $botId, $message, $fileId, $params );
855 return $onlyReply ? $data['reply'] : $data;
856 }
857
858 /**
859 * Executes a text query.
860 *
861 * @param string $message The prompt for the AI.
862 * @param array $params Additional parameters for the AI query.
863 *
864 * @return string The result of the AI query.
865 */
866 public function simpleTextQuery( $message, $params = [] ) {
867 global $mwai_core;
868 $query = new Meow_MWAI_Query_Text( $message );
869 $query->inject_params( $params );
870 $reply = $mwai_core->run_query( $query );
871 return $reply->result;
872 }
873
874 public function simpleFastTextQuery( $message, $params = [] ) {
875 global $mwai_core;
876 $query = new Meow_MWAI_Query_Text( $message );
877
878 // Use the Default (Fast) model and environment
879 $fastDefaultModel = $mwai_core->get_option( 'ai_fast_default_model' );
880 if ( !empty( $fastDefaultModel ) ) {
881 $query->set_model( $fastDefaultModel );
882 }
883
884 $fastDefaultEnv = $mwai_core->get_option( 'ai_fast_default_env' );
885 if ( !empty( $fastDefaultEnv ) ) {
886 $query->set_env_id( $fastDefaultEnv );
887 }
888
889 // Inject any additional params (which may override the defaults)
890 $query->inject_params( $params );
891
892 try {
893 $reply = $mwai_core->run_query( $query );
894 return $reply->result;
895 }
896 catch ( Exception $e ) {
897 // If Fast Model fails, try with default model
898 Meow_MWAI_Logging::warn( "Fast Model failed: " . $e->getMessage() . " - Falling back to default model." );
899
900 // Create a new query with default model/env
901 $fallbackQuery = new Meow_MWAI_Query_Text( $message );
902
903 $defaultModel = $mwai_core->get_option( 'ai_default_model' );
904 if ( !empty( $defaultModel ) ) {
905 $fallbackQuery->set_model( $defaultModel );
906 }
907
908 $defaultEnv = $mwai_core->get_option( 'ai_default_env' );
909 if ( !empty( $defaultEnv ) ) {
910 $fallbackQuery->set_env_id( $defaultEnv );
911 }
912
913 // Inject params again (except model/env which we just set)
914 $fallbackParams = $params;
915 unset( $fallbackParams['model'] );
916 unset( $fallbackParams['envId'] );
917 $fallbackQuery->inject_params( $fallbackParams );
918
919 $reply = $mwai_core->run_query( $fallbackQuery );
920 return $reply->result;
921 }
922 }
923
924 public function simpleImageQuery( $message, $params = [] ) {
925 global $mwai_core;
926 $query = new Meow_MWAI_Query_Image( $message );
927 $query->inject_params( $params );
928 $reply = $mwai_core->run_query( $query );
929 return $reply->result;
930 }
931
932 public function simpleImageEditQuery( $message, $mediaId, $params = [] ) {
933 global $mwai_core;
934 $query = new Meow_MWAI_Query_EditImage( $message );
935 $query->inject_params( $params );
936 $path = get_attached_file( $mediaId );
937 if ( empty( $path ) ) {
938 throw new Exception( 'The media cannot be found.' );
939 }
940 // TODO: Maybe 'vision' should be 'edit'.
941 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
942 $reply = $mwai_core->run_query( $query );
943 return $reply->result;
944 }
945
946 /**
947 * Generates an image relevant to the text.
948 */
949 public function imageQueryForMediaLibrary( $message, $params = [], $postId = null ) {
950 $query = new Meow_MWAI_Query_Image( $message );
951 $query->inject_params( $params );
952 $query->set_local_download( null );
953 $reply = $this->core->run_query( $query );
954 preg_match( '/\!\[Image\]\((.*?)\)/', $reply->result, $matches );
955 $url = $matches[1] ?? $reply->result;
956
957 // Check if the URL is already a WordPress attachment URL to avoid duplicates
958 $attachmentId = null;
959 $upload_dir = wp_upload_dir();
960 if ( strpos( $url, $upload_dir['baseurl'] ) === 0 ) {
961 // This is already a local WordPress upload, try to find the attachment ID
962 // First try by GUID
963 global $wpdb;
964 $attachmentId = $wpdb->get_var( $wpdb->prepare(
965 "SELECT ID FROM {$wpdb->posts} WHERE guid = %s AND post_type = 'attachment'",
966 $url
967 ) );
968
969 // If not found by GUID, try by attachment URL (more reliable)
970 if ( empty( $attachmentId ) ) {
971 $attachmentId = attachment_url_to_postid( $url );
972 }
973 }
974
975 // If not found or not a local URL, add it to the media library
976 if ( empty( $attachmentId ) ) {
977 $attachmentId = $this->core->add_image_from_url( $url, null, null, null, null, null, $postId );
978 if ( empty( $attachmentId ) ) {
979 throw new Exception( 'Could not add the image to the Media Library.' );
980 }
981 }
982
983 // TODO: We should create a nice title, caption, and alt.
984 $media = [
985 'id' => $attachmentId,
986 'url' => wp_get_attachment_url( $attachmentId ),
987 'title' => get_the_title( $attachmentId ),
988 'caption' => wp_get_attachment_caption( $attachmentId ),
989 'alt' => get_post_meta( $attachmentId, '_wp_attachment_image_alt', true )
990 ];
991 return $media;
992 }
993
994 /**
995 * Executes a query that will have to return a JSON result.
996 *
997 * @param string $message The prompt for the AI.
998 * @param array $params Additional parameters for the AI query.
999 *
1000 * @return array The result of the AI query.
1001 */
1002 public function simpleJsonQuery( $message, $url = null, $path = null, $params = [] ) {
1003 if ( !empty( $url ) || !empty( $path ) ) {
1004 throw new Exception( 'The url and path are not supported yet by the simpleJsonQuery.' );
1005 }
1006 global $mwai_core;
1007 $query = new Meow_MWAI_Query_Text( $message . "\nYour reply must be a formatted JSON." );
1008 $query->inject_params( $params );
1009 $query->set_response_format( 'json' );
1010 $ai_json_default_env = $mwai_core->get_option( 'ai_json_default_env' );
1011 $ai_json_default_model = $mwai_core->get_option( 'ai_json_default_model' );
1012 if ( !empty( $ai_json_default_env ) ) {
1013 $query->set_env_id( $ai_json_default_env );
1014 }
1015 if ( !empty( $ai_json_default_model ) ) {
1016 $query->set_model( $ai_json_default_model );
1017 }
1018 else {
1019 $query->set_model( MWAI_FALLBACK_MODEL_JSON );
1020 }
1021 $reply = $mwai_core->run_query( $query );
1022 try {
1023 $json = json_decode( $reply->result, true );
1024 return $json;
1025 }
1026 catch ( Exception $e ) {
1027 throw new Exception( 'The result is not a valid JSON.' );
1028 }
1029 }
1030
1031 /**
1032 * Uploads a file to the system.
1033 *
1034 * @param array|null $file The file array from $_FILES.
1035 * @param string|null $base64 Base64 encoded file data.
1036 * @param string|null $filename The filename for base64 uploads.
1037 * @param string $purpose The purpose of the file upload (e.g., 'files', 'vision', 'assistant').
1038 * @param int $ttl Time to live in seconds. Default 3600 (1 hour).
1039 * @param string|null $target Target location: 'uploads' or 'library'.
1040 * @param array $metadata Additional metadata to store with the file.
1041 *
1042 * @return array Array with 'id' (refId) and 'url' of the uploaded file.
1043 */
1044 public function simpleFileUpload( $file = null, $base64 = null, $filename = null, $purpose = 'files', $ttl = 3600, $target = null, $metadata = [] ) {
1045 global $mwai_core;
1046
1047 if ( !$this->core->files ) {
1048 throw new Exception( 'Files module is not available.' );
1049 }
1050
1051 // Determine target from settings if not provided
1052 if ( empty( $target ) ) {
1053 $target = $this->core->get_option( 'image_local_upload', 'uploads' );
1054 }
1055
1056 try {
1057 if ( !empty( $base64 ) ) {
1058 // Handle base64 upload
1059 if ( empty( $filename ) ) {
1060 $filename = 'upload-' . time() . '.dat';
1061 }
1062
1063 // Validate filename extension for base64 uploads
1064 $validate = wp_check_filetype( $filename );
1065 if ( $validate['type'] == false ) {
1066 throw new Exception( 'File type is not allowed.' );
1067 }
1068
1069 // For base64 uploads, we need to decode and create a temp file first
1070 $binary = base64_decode( $base64 );
1071 if ( !$binary ) {
1072 throw new Exception( 'Invalid base64 data.' );
1073 }
1074
1075 // Create a temporary file
1076 $tmp_path = wp_tempnam( 'mwai-upload' );
1077 file_put_contents( $tmp_path, $binary );
1078
1079 // Use the regular upload method
1080 $refId = $this->core->files->upload_file(
1081 $tmp_path,
1082 $filename,
1083 $purpose,
1084 $metadata,
1085 null, // envId
1086 $target,
1087 $ttl
1088 );
1089
1090 // Clean up temp file if it was uploaded to library
1091 if ( $target === 'library' && file_exists( $tmp_path ) ) {
1092 @unlink( $tmp_path );
1093 }
1094
1095 $url = $this->core->files->get_url( $refId );
1096
1097 return [
1098 'id' => $refId,
1099 'url' => $url
1100 ];
1101 }
1102 else if ( !empty( $file ) && is_array( $file ) ) {
1103 // Handle regular file upload
1104 if ( !empty( $file['error'] ) ) {
1105 throw new Exception( 'File upload error: ' . $file['error'] );
1106 }
1107
1108 $refId = $this->core->files->upload_file(
1109 $file['tmp_name'],
1110 $file['name'],
1111 $purpose,
1112 $metadata,
1113 null, // envId
1114 $target,
1115 $ttl
1116 );
1117
1118 $url = $this->core->files->get_url( $refId );
1119
1120 return [
1121 'id' => $refId,
1122 'url' => $url
1123 ];
1124 }
1125 else {
1126 throw new Exception( 'Either a file or base64 data must be provided.' );
1127 }
1128 }
1129 catch ( Exception $e ) {
1130 throw new Exception( 'File upload failed: ' . $e->getMessage() );
1131 }
1132 }
1133
1134 /**
1135 * Executes an audio transcription query.
1136 *
1137 * @param string $url The URL of the audio file to transcribe.
1138 * @param string|null $path The path to the audio file. If provided, the audio data will be read from this file.
1139 * @param array $params Additional parameters for the transcription query.
1140 *
1141 * @return string The transcribed text.
1142 */
1143 public function simpleTranscribeAudio( $url = null, $path = null, $params = [] ) {
1144 global $mwai_core;
1145 $ai_audio_default_env = $this->core->get_option( 'ai_audio_default_env' );
1146 $ai_audio_default_model = $this->core->get_option( 'ai_audio_default_model' );
1147
1148 if ( empty( $ai_audio_default_model ) ) {
1149 $ai_audio_default_model = 'whisper-1'; // Default transcription model
1150 }
1151
1152 $query = new Meow_MWAI_Query_Transcribe();
1153
1154 if ( !empty( $ai_audio_default_env ) ) {
1155 $query->set_env_id( $ai_audio_default_env );
1156 }
1157 if ( !empty( $ai_audio_default_model ) ) {
1158 $query->set_model( $ai_audio_default_model );
1159 }
1160
1161 $query->inject_params( $params );
1162
1163 if ( !empty( $url ) ) {
1164 // Use 'files' as the purpose for audio files
1165 $query->set_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'files' ) );
1166 }
1167 else if ( !empty( $path ) ) {
1168 // Use 'files' as the purpose for audio files
1169 $query->set_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'files' ) );
1170 }
1171 else {
1172 throw new Exception( 'Either a URL or a path must be provided for the audio file.' );
1173 }
1174
1175 $reply = $mwai_core->run_query( $query );
1176 return $reply->result;
1177 }
1178 #endregion
1179
1180 #region Standard API
1181 /**
1182 * Checks if a text is safe or not.
1183 *
1184 * @param string $text The text to check.
1185 *
1186 * @return bool True if the text is safe, false otherwise.
1187 */
1188 public function moderationCheck( $text ) {
1189 global $mwai_core;
1190 $openai = Meow_MWAI_Engines_Factory::get_openai( $mwai_core );
1191 $res = $openai->moderate( $text );
1192 if ( !empty( $res ) && !empty( $res['results'] ) ) {
1193 return (bool) $res['results'][0]['flagged'];
1194 }
1195 }
1196 #endregion
1197
1198 #region Standard API (No REST API)
1199
1200 /**
1201 * Checks the status of the AI environments.
1202 *
1203 * @return array The types of environments that are available.
1204 */
1205 public function checkStatus() {
1206 $env_types = [];
1207 $ai_envs = $this->core->get_option( 'ai_envs' );
1208 if ( empty( $ai_envs ) ) {
1209 throw new Exception( 'There are no AI environments yet.' );
1210 }
1211 foreach ( $ai_envs as $env ) {
1212 if ( !empty( $env['apikey'] ) ) {
1213 if ( !in_array( $env['type'], $env_types ) ) {
1214 $env_types[] = $env['type'];
1215 }
1216 }
1217 }
1218 if ( empty( $env_types ) ) {
1219 throw new Exception( 'There are no AI environments with an API key yet.' );
1220 }
1221 return $env_types;
1222 }
1223
1224 /**
1225 * Get function name by ID
1226 */
1227 private function get_function_name_by_id( $funcId ) {
1228 // Get function from registry using the static method
1229 $function = MeowPro_MWAI_FunctionAware::get_function( 'code-engine', $funcId );
1230 if ( $function && isset( $function->name ) ) {
1231 return $function->name;
1232 }
1233
1234 // If not found, try snippet-vault type as well
1235 $function = MeowPro_MWAI_FunctionAware::get_function( 'snippet-vault', $funcId );
1236 if ( $function && isset( $function->name ) ) {
1237 return $function->name;
1238 }
1239
1240 return null;
1241 }
1242 #endregion
1243 }
1244