PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.1.3
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.1.3
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 8 months ago exceptions 1 year ago modules 8 months ago query 8 months ago rest 8 months ago services 8 months ago admin.php 8 months ago api.php 8 months ago core.php 8 months ago discussion.php 1 year ago event.php 1 year ago init.php 8 months ago logging.php 1 year ago reply.php 8 months ago rest.php 8 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',
32 ];
33 $blocked_params = apply_filters( 'mwai_blocked_rest_params', $blocked_params, $options );
34
35 // Remove blocked parameters
36 foreach ( $blocked_params as $param ) {
37 unset( $options[$param] );
38 }
39
40 return $options;
41 }
42
43 public function rest_api_init() {
44 $public_api = $this->core->get_option( 'public_api' );
45 if ( !$public_api ) {
46 return;
47 }
48 $this->bearer_token = $this->core->get_option( 'public_api_bearer_token' );
49 if ( !empty( $this->bearer_token ) ) {
50 add_filter( 'mwai_allow_public_api', [ $this, 'auth_via_bearer_token' ], 10, 3 );
51 }
52
53 register_rest_route( 'mwai/v1', '/simpleAuthCheck', [
54 'methods' => 'GET',
55 'callback' => [ $this, 'rest_simpleAuthCheck' ],
56 'permission_callback' => function ( $request ) {
57 return $this->core->can_access_public_api( 'simpleAuthCheck', $request );
58 },
59 ] );
60 register_rest_route( 'mwai/v1', '/simpleTextQuery', [
61 'methods' => 'POST',
62 'callback' => [ $this, 'rest_simpleTextQuery' ],
63 'permission_callback' => function ( $request ) {
64 return $this->core->can_access_public_api( 'simpleTextQuery', $request );
65 },
66 ] );
67 register_rest_route( 'mwai/v1', '/simpleFastTextQuery', [
68 'methods' => 'POST',
69 'callback' => [ $this, 'rest_simpleFastTextQuery' ],
70 'permission_callback' => function ( $request ) {
71 return $this->core->can_access_public_api( 'simpleFastTextQuery', $request );
72 },
73 ] );
74 register_rest_route( 'mwai/v1', '/simpleImageQuery', [
75 'methods' => 'POST',
76 'callback' => [ $this, 'rest_simpleImageQuery' ],
77 'permission_callback' => function ( $request ) {
78 return $this->core->can_access_public_api( 'simpleImageQuery', $request );
79 },
80 ] );
81 register_rest_route( 'mwai/v1', '/simpleImageEditQuery', [
82 'methods' => 'POST',
83 'callback' => [ $this, 'rest_simpleImageEditQuery' ],
84 'permission_callback' => function ( $request ) {
85 return $this->core->can_access_public_api( 'simpleImageEditQuery', $request );
86 },
87 ] );
88 register_rest_route( 'mwai/v1', '/simpleVisionQuery', [
89 'methods' => 'POST',
90 'callback' => [ $this, 'rest_simpleVisionQuery' ],
91 'permission_callback' => function ( $request ) {
92 return $this->core->can_access_public_api( 'simpleVisionQuery', $request );
93 },
94 ] );
95 register_rest_route( 'mwai/v1', '/simpleJsonQuery', [
96 'methods' => 'POST',
97 'callback' => [ $this, 'rest_simpleJsonQuery' ],
98 'permission_callback' => function ( $request ) {
99 return $this->core->can_access_public_api( 'simpleJsonQuery', $request );
100 },
101 ] );
102 register_rest_route( 'mwai/v1', '/moderationCheck', [
103 'methods' => 'POST',
104 'callback' => [ $this, 'rest_moderationCheck' ],
105 'permission_callback' => function ( $request ) {
106 return $this->core->can_access_public_api( 'moderationCheck', $request );
107 },
108 ] );
109 register_rest_route( 'mwai/v1', '/simpleTranscribeAudio', [
110 'methods' => 'POST',
111 'callback' => [ $this, 'rest_simpleTranscribeAudio' ],
112 'permission_callback' => function ( $request ) {
113 return $this->core->can_access_public_api( 'simpleTranscribeAudio', $request );
114 },
115 ] );
116 register_rest_route( 'mwai/v1', '/simpleFileUpload', [
117 'methods' => 'POST',
118 'callback' => [ $this, 'rest_simpleFileUpload' ],
119 'permission_callback' => function ( $request ) {
120 return $this->core->can_access_public_api( 'simpleFileUpload', $request );
121 },
122 ] );
123
124 if ( $this->chatbot_module ) {
125 register_rest_route( 'mwai/v1', '/simpleChatbotQuery', [
126 'methods' => 'POST',
127 'callback' => [ $this, 'rest_simpleChatbotQuery' ],
128 'permission_callback' => function ( $request ) {
129 return $this->core->can_access_public_api( 'simpleChatbotQuery', $request );
130 },
131 ] );
132
133 register_rest_route( 'mwai/v1', '/listChatbots', [
134 'methods' => 'GET',
135 'callback' => [ $this, 'rest_listChatbots' ],
136 'permission_callback' => function ( $request ) {
137 return $this->core->can_access_public_api( 'listChatbots', $request );
138 },
139 ] );
140 }
141 }
142
143 public function rest_simpleAuthCheck( $request ) {
144 try {
145 $params = $request->get_params();
146 $current_user = wp_get_current_user();
147 $current_email = $current_user->user_email;
148 return new WP_REST_Response( [ 'success' => true, 'data' => [
149 'type' => 'email',
150 'value' => $current_email
151 ] ], 200 );
152 }
153 catch ( Exception $e ) {
154 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
155 }
156 }
157
158 public function auth_via_bearer_token( $allow, $feature, $extra ) {
159 if ( !empty( $extra ) && !empty( $extra->get_header( 'Authorization' ) ) ) {
160 $token = $extra->get_header( 'Authorization' );
161 $token = str_replace( 'Bearer ', '', $token );
162 if ( $token === $this->bearer_token ) {
163 // We set the current user to the first admin.
164 $admin = $this->core->get_admin_user();
165 wp_set_current_user( $admin->ID, $admin->user_login );
166 return true;
167 }
168 }
169 return $allow;
170 }
171
172 public function rest_simpleChatbotQuery( $request ) {
173 try {
174 $params = $request->get_params();
175 $botId = isset( $params['botId'] ) ? $params['botId'] : '';
176 $message = isset( $params['message'] ) ? $params['message'] : '';
177 if ( empty( $message ) ) {
178 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
179 }
180 $chatId = isset( $params['chatId'] ) ? $params['chatId'] : null;
181 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
182 $fileIds = isset( $params['fileIds'] ) ? $params['fileIds'] : null;
183 $queryParams = [];
184 if ( !empty( $chatId ) ) {
185 $queryParams['chatId'] = $chatId;
186 }
187 if ( !empty( $fileId ) ) {
188 $queryParams['fileId'] = $fileId;
189 }
190 if ( !empty( $fileIds ) && is_array( $fileIds ) ) {
191 $queryParams['fileIds'] = $fileIds;
192 }
193 if ( empty( $botId ) || empty( $message ) ) {
194 throw new Exception( __( 'The botId and message are required.', 'ai-engine' ) );
195 }
196
197 if ( $this->debug ) {
198 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
199 $debug = sprintf( 'REST [SimpleChatbotQuery]: %s, %s', $shortMessage, json_encode( $queryParams ) );
200 Meow_MWAI_Logging::log( $debug );
201 }
202
203 $reply = $this->simpleChatbotQuery( $botId, $message, $queryParams, false );
204 return new WP_REST_Response( [
205 'success' => true,
206 'data' => $reply['reply'],
207 'extra' => [
208 'actions' => $reply['actions'],
209 'chatId' => $reply['chatId']
210 ]
211 ], 200 );
212 }
213 catch ( Exception $e ) {
214 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
215 }
216 }
217
218 public function rest_listChatbots( $request ) {
219 try {
220 // Get all chatbots
221 $chatbots = get_option( 'mwai_chatbots', [] );
222 $environments = $this->core->get_option( 'ai_envs' );
223 $mcp_envs = $this->core->get_option( 'mcp_envs', [] );
224
225 // Get all models from all environments
226 $all_models = [];
227 foreach ( $environments as $env ) {
228 try {
229 $engine = Meow_MWAI_Engines_Factory::get( $this->core, $env['id'] );
230 $env_models = $engine->retrieve_models();
231 foreach ( $env_models as $model ) {
232 $all_models[$model['model']] = $model;
233 }
234 }
235 catch ( Exception $e ) {
236 // Skip environments that fail
237 }
238 }
239
240 // Debug: Log model info for gpt-4.1-mini
241 if ( $this->debug && isset( $all_models['gpt-4.1-mini'] ) ) {
242 error_log( '[AI Engine API] Model info for gpt-4.1-mini: ' . json_encode( $all_models['gpt-4.1-mini'] ) );
243 }
244
245 // Get registered functions
246 $functions = apply_filters( 'mwai_functions_list', [] );
247 $function_names = [];
248 foreach ( $functions as $function ) {
249 $function_names[] = $function->name ?? 'unknown';
250 }
251
252 $result = [];
253
254 foreach ( $chatbots as $chatbotId => $chatbot ) {
255 // Debug log the chatbot structure
256 if ( $this->debug && ( $chatbot['name'] ?? '' ) === 'Jordy' ) {
257 error_log( '[AI Engine API] Jordy chatbot structure: ' . json_encode( $chatbot ) );
258 }
259
260 // Basic info
261 $info = [
262 'id' => $chatbot['botId'] ?? $chatbotId, // Use botId if available, fallback to array index
263 'name' => $chatbot['name'] ?? 'Unnamed',
264 'type' => 'chat', // Default type
265 'model' => null,
266 'model_name' => null,
267 'environment' => null,
268 'environment_name' => null,
269 'functions' => [],
270 'tools' => [], // Add tools array
271 'mcp_servers' => []
272 ];
273
274 // Determine chatbot type
275 if ( !empty( $chatbot['type'] ) ) {
276 $info['type'] = $chatbot['type'];
277 }
278 else {
279 // Try to infer type from model or other properties
280 if ( !empty( $chatbot['model'] ) ) {
281 if ( strpos( $chatbot['model'], 'realtime' ) !== false ) {
282 $info['type'] = 'realtime';
283 }
284 else if ( strpos( $chatbot['model'], 'image' ) !== false || strpos( $chatbot['model'], 'dall-e' ) !== false ) {
285 $info['type'] = 'images';
286 }
287 else if ( !empty( $chatbot['assistantId'] ) ) {
288 $info['type'] = 'assistant';
289 }
290 }
291 }
292
293 // Get model info
294 if ( !empty( $chatbot['model'] ) ) {
295 $info['model'] = $chatbot['model'];
296 // Find model name
297 if ( isset( $all_models[$chatbot['model']] ) ) {
298 $info['model_name'] = $all_models[$chatbot['model']]['name'] ?? $chatbot['model'];
299 }
300 else {
301 $info['model_name'] = $chatbot['model'];
302 }
303 }
304
305 // Get environment info
306 if ( !empty( $chatbot['envId'] ) ) {
307 $info['environment'] = $chatbot['envId'];
308 // Find environment name
309 foreach ( $environments as $env ) {
310 if ( $env['id'] === $chatbot['envId'] ) {
311 $info['environment_name'] = $env['name'] ?? $chatbot['envId'];
312 break;
313 }
314 }
315 }
316
317 // Check if it uses functions - get specific function names if available
318 if ( !empty( $chatbot['functions'] ) ) {
319 if ( is_array( $chatbot['functions'] ) ) {
320 // Functions are stored as array of objects with id and type
321 $chatbot_functions = [];
322 foreach ( $chatbot['functions'] as $func ) {
323 if ( isset( $func['id'] ) ) {
324 // Try to find function name by ID
325 $func_name = $this->get_function_name_by_id( $func['id'] );
326 if ( $func_name ) {
327 $chatbot_functions[] = $func_name;
328 }
329 else {
330 // Fallback: include the ID if name not found
331 $chatbot_functions[] = 'function_' . $func['id'];
332 }
333 }
334 }
335 $info['functions'] = $chatbot_functions;
336 }
337 else if ( $chatbot['functions'] === true ) {
338 // If functions is just true, it uses all registered functions
339 $info['functions'] = $function_names;
340 }
341 }
342
343 // Check for tools (Web Search, Image Generation)
344 if ( !empty( $chatbot['tools'] ) && is_array( $chatbot['tools'] ) ) {
345 // Filter tools based on model capabilities
346 $supported_tools = [];
347 if ( !empty( $chatbot['model'] ) ) {
348 // Try exact match first
349 $model_key = $chatbot['model'];
350 $model_info = $all_models[$model_key] ?? null;
351
352 // If not found and it's an OpenRouter model, try without prefix
353 if ( !$model_info && strpos( $model_key, '/' ) !== false ) {
354 $model_key = substr( $model_key, strpos( $model_key, '/' ) + 1 );
355 $model_info = $all_models[$model_key] ?? null;
356 }
357
358 if ( $model_info ) {
359 $model_tools = $model_info['tools'] ?? [];
360
361 // Only include tools that are both configured AND supported by the model
362 foreach ( $chatbot['tools'] as $tool ) {
363 if ( in_array( $tool, $model_tools ) ) {
364 $supported_tools[] = $tool;
365 }
366 }
367 }
368 else {
369 // If model not found in our list, keep all configured tools
370 // This allows custom models to use tools
371 $supported_tools = $chatbot['tools'];
372 }
373 }
374 $info['tools'] = $supported_tools;
375 }
376
377 // Check for MCP servers
378 if ( !empty( $chatbot['mcp_servers'] ) && is_array( $chatbot['mcp_servers'] ) ) {
379 foreach ( $chatbot['mcp_servers'] as $mcpServer ) {
380 if ( !empty( $mcpServer['id'] ) ) {
381 // Find MCP server name
382 foreach ( $mcp_envs as $mcp ) {
383 if ( $mcp['id'] === $mcpServer['id'] ) {
384 $info['mcp_servers'][] = [
385 'id' => $mcp['id'],
386 'name' => $mcp['name'] ?? 'Unnamed MCP Server'
387 ];
388 break;
389 }
390 }
391 }
392 }
393 }
394
395 $result[] = $info;
396 }
397
398 return new WP_REST_Response( [
399 'success' => true,
400 'data' => $result
401 ], 200 );
402 }
403 catch ( Exception $e ) {
404 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
405 }
406 }
407
408 public function rest_simpleTextQuery( $request ) {
409 try {
410 $params = $request->get_params();
411 $message = isset( $params['message'] ) ? $params['message'] : '';
412 if ( empty( $message ) ) {
413 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
414 }
415 $options = isset( $params['options'] ) ? $params['options'] : [];
416 $options = $this->sanitize_rest_options( $options );
417 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
418 if ( !empty( $scope ) ) {
419 $options['scope'] = $scope;
420 }
421 if ( empty( $message ) ) {
422 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
423 }
424
425 if ( $this->debug ) {
426 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
427 $debug = sprintf( 'REST [SimpleTextQuery]: %s, %s', $shortMessage, json_encode( $options ) );
428 Meow_MWAI_Logging::log( $debug );
429 }
430
431 $reply = $this->simpleTextQuery( $message, $options );
432 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
433 }
434 catch ( Exception $e ) {
435 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
436 }
437 }
438
439 public function rest_simpleFastTextQuery( $request ) {
440 try {
441 $params = $request->get_params();
442 $message = isset( $params['message'] ) ? $params['message'] : '';
443 if ( empty( $message ) ) {
444 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
445 }
446 $options = isset( $params['options'] ) ? $params['options'] : [];
447 $options = $this->sanitize_rest_options( $options );
448 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
449 if ( !empty( $scope ) ) {
450 $options['scope'] = $scope;
451 }
452 if ( empty( $message ) ) {
453 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
454 }
455
456 if ( $this->debug ) {
457 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
458 $debug = sprintf( 'REST [SimpleFastTextQuery]: %s, %s', $shortMessage, json_encode( $options ) );
459 Meow_MWAI_Logging::log( $debug );
460 }
461
462 $reply = $this->simpleFastTextQuery( $message, $options );
463 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
464 }
465 catch ( Exception $e ) {
466 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
467 }
468 }
469
470 public function rest_simpleImageQuery( $request ) {
471 try {
472 $params = $request->get_params();
473 $message = isset( $params['message'] ) ? $params['message'] : '';
474 if ( empty( $message ) ) {
475 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
476 }
477 $options = isset( $params['options'] ) ? $params['options'] : [];
478 $resolution = isset( $params['resolution'] ) ? $params['resolution'] : '';
479 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
480 if ( !empty( $scope ) ) {
481 $options['scope'] = $scope;
482 }
483 if ( empty( $message ) ) {
484 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
485 }
486 if ( !empty( $resolution ) ) {
487 $options['resolution'] = $resolution;
488 }
489
490 if ( $this->debug ) {
491 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
492 $debug = sprintf( 'REST [SimpleImageQuery]: %s, %s', $shortMessage, json_encode( $options ) );
493 Meow_MWAI_Logging::log( $debug );
494 }
495
496 $reply = $this->simpleImageQuery( $message, $options );
497 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
498 }
499 catch ( Exception $e ) {
500 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
501 }
502 }
503
504 public function rest_simpleImageEditQuery( $request ) {
505 try {
506 $params = $request->get_params();
507 $message = isset( $params['message'] ) ? $params['message'] : '';
508 if ( empty( $message ) ) {
509 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
510 }
511 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
512 $options = isset( $params['options'] ) ? $params['options'] : [];
513 $resolution = isset( $params['resolution'] ) ? $params['resolution'] : '';
514 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
515 if ( !empty( $scope ) ) {
516 $options['scope'] = $scope;
517 }
518 if ( empty( $message ) ) {
519 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
520 }
521 if ( empty( $mediaId ) ) {
522 throw new Exception( 'The mediaId is required.' );
523 }
524 if ( !empty( $resolution ) ) {
525 $options['resolution'] = $resolution;
526 }
527
528 if ( $this->debug ) {
529 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
530 $debug = sprintf( 'REST [SimpleImageEditQuery]: %s, %s', $shortMessage, json_encode( $options ) );
531 Meow_MWAI_Logging::log( $debug );
532 }
533
534 $reply = $this->simpleImageEditQuery( $message, $mediaId, $options );
535 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
536 }
537 catch ( Exception $e ) {
538 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
539 }
540 }
541
542 public function rest_simpleVisionQuery( $request ) {
543 try {
544 $params = $request->get_params();
545 $message = isset( $params['message'] ) ? $params['message'] : '';
546 if ( empty( $message ) ) {
547 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
548 }
549 $url = isset( $params['url'] ) ? $params['url'] : '';
550
551 // Check for common parameter mistakes and provide helpful guidance
552 if ( empty( $url ) && isset( $params['imageUrl'] ) ) {
553 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "imageUrl"?' );
554 }
555 if ( empty( $url ) && isset( $params['image_url'] ) ) {
556 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "image_url"?' );
557 }
558
559 $options = isset( $params['options'] ) ? $params['options'] : [];
560 $options = $this->sanitize_rest_options( $options );
561 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
562 if ( !empty( $scope ) ) {
563 $options['scope'] = $scope;
564 }
565 if ( empty( $message ) ) {
566 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
567 }
568 if ( empty( $url ) ) {
569 throw new Exception( 'The "url" parameter is required for image analysis.' );
570 }
571
572 if ( $this->debug ) {
573 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
574 $debug = sprintf( 'REST [SimpleVisionQuery]: %s, %s', $shortMessage, json_encode( $options ) );
575 Meow_MWAI_Logging::log( $debug );
576 }
577
578 $reply = $this->simpleVisionQuery( $message, $url, null, $options );
579 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
580 }
581 catch ( Exception $e ) {
582 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
583 }
584 }
585
586 public function rest_simpleJsonQuery( $request ) {
587 try {
588 $params = $request->get_params();
589 $message = isset( $params['message'] ) ? $params['message'] : '';
590 if ( empty( $message ) ) {
591 $message = isset( $params['prompt'] ) ? $params['prompt'] : '';
592 }
593 $options = isset( $params['options'] ) ? $params['options'] : [];
594 $options = $this->sanitize_rest_options( $options );
595 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
596 if ( !empty( $scope ) ) {
597 $options['scope'] = $scope;
598 }
599 if ( empty( $message ) ) {
600 throw new Exception( __( 'The message is required.', 'ai-engine' ) );
601 }
602
603 if ( $this->debug ) {
604 $shortMessage = Meow_MWAI_Logging::shorten( $message, 64 );
605 $debug = sprintf( 'REST [SimpleJsonQuery]: %s, %s', $shortMessage, json_encode( $options ) );
606 Meow_MWAI_Logging::log( $debug );
607 }
608
609 $reply = $this->simpleJsonQuery( $message, null, null, $options );
610 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
611 }
612 catch ( Exception $e ) {
613 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
614 }
615 }
616
617 public function rest_moderationCheck( $request ) {
618 try {
619 $params = $request->get_params();
620 $text = isset( $params['text'] ) ? $params['text'] : '';
621
622 // Check for common parameter mistakes and provide helpful guidance
623 if ( empty( $text ) && isset( $params['message'] ) ) {
624 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "message"?' );
625 }
626 if ( empty( $text ) && isset( $params['content'] ) ) {
627 throw new Exception( 'Parameter "text" is required. Did you mean to use "text" instead of "content"?' );
628 }
629
630 if ( empty( $text ) ) {
631 throw new Exception( 'The "text" parameter is required for content moderation.' );
632 }
633
634 if ( $this->debug ) {
635 $shortText = Meow_MWAI_Logging::shorten( $text, 64 );
636 $debug = sprintf( 'REST [ModerationCheck]: %s', $shortText );
637 Meow_MWAI_Logging::log( $debug );
638 }
639
640 $reply = $this->moderationCheck( $text );
641 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
642 }
643 catch ( Exception $e ) {
644 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
645 }
646 }
647
648 public function rest_simpleTranscribeAudio( $request ) {
649 try {
650 $params = $request->get_params();
651 $url = isset( $params['url'] ) ? $params['url'] : '';
652 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
653
654 // Check for common parameter mistakes and provide helpful guidance
655 if ( empty( $url ) && empty( $mediaId ) ) {
656 if ( isset( $params['audioUrl'] ) ) {
657 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audioUrl"?' );
658 }
659 if ( isset( $params['audio_url'] ) ) {
660 throw new Exception( 'Parameter "url" is required. Did you mean to use "url" instead of "audio_url"?' );
661 }
662 if ( isset( $params['file'] ) ) {
663 throw new Exception( 'Use "url" for remote files or "mediaId" for uploaded files. Found "file" parameter instead.' );
664 }
665 }
666
667 $options = isset( $params['options'] ) ? $params['options'] : [];
668 $options = $this->sanitize_rest_options( $options );
669 $scope = isset( $params['scope'] ) ? $params['scope'] : 'public-api';
670
671 if ( !empty( $scope ) ) {
672 $options['scope'] = $scope;
673 }
674
675 // Get file path from mediaId if provided
676 $path = null;
677 if ( $mediaId > 0 ) {
678 $path = get_attached_file( $mediaId );
679 if ( empty( $path ) ) {
680 throw new Exception( 'The media file cannot be found.' );
681 }
682 }
683
684 if ( empty( $url ) && empty( $path ) ) {
685 throw new Exception( 'Either a "url" parameter or a "mediaId" parameter is required for audio transcription.' );
686 }
687
688 if ( $this->debug ) {
689 $debug = sprintf( 'REST [SimpleTranscribeAudio]: url=%s, mediaId=%d, %s',
690 $url ? 'provided' : 'none',
691 $mediaId,
692 json_encode( $options )
693 );
694 Meow_MWAI_Logging::log( $debug );
695 }
696
697 $reply = $this->simpleTranscribeAudio( $url, $path, $options );
698 return new WP_REST_Response( [ 'success' => true, 'data' => $reply ], 200 );
699 }
700 catch ( Exception $e ) {
701 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
702 }
703 }
704
705 public function rest_simpleFileUpload( $request ) {
706 try {
707 $params = $request->get_params();
708 $files = $request->get_file_params();
709
710 // Check if file is provided
711 if ( empty( $files['file'] ) ) {
712 // Check for base64 encoded file data
713 $base64 = isset( $params['base64'] ) ? $params['base64'] : '';
714 $filename = isset( $params['filename'] ) ? $params['filename'] : '';
715
716 if ( empty( $base64 ) ) {
717 throw new Exception( 'Either a file upload or base64 encoded data is required.' );
718 }
719
720 // Handle base64 upload
721 $options = isset( $params['options'] ) ? $params['options'] : [];
722 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
723 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
724 $target = isset( $params['target'] ) ? $params['target'] : null;
725 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
726
727 if ( empty( $filename ) ) {
728 $filename = 'upload-' . time() . '.png'; // Default filename for base64
729 }
730
731 // Log the request if debug is enabled
732 if ( $this->debug ) {
733 $debug = sprintf( 'REST [SimpleFileUpload]: base64 upload, filename=%s, purpose=%s',
734 $filename,
735 $purpose
736 );
737 Meow_MWAI_Logging::log( $debug );
738 }
739
740 $result = $this->simpleFileUpload( null, $base64, $filename, $purpose, $ttl, $target, $metadata );
741 }
742 else {
743 // Handle regular file upload
744 $file = $files['file'];
745 $purpose = isset( $params['purpose'] ) ? $params['purpose'] : 'files';
746 $ttl = isset( $params['ttl'] ) ? intval( $params['ttl'] ) : 3600;
747 $target = isset( $params['target'] ) ? $params['target'] : null;
748 $metadata = isset( $params['metadata'] ) ? $params['metadata'] : [];
749
750 if ( $this->debug ) {
751 $debug = sprintf( 'REST [SimpleFileUpload]: file upload, name=%s, purpose=%s',
752 $file['name'],
753 $purpose
754 );
755 Meow_MWAI_Logging::log( $debug );
756 }
757
758 $result = $this->simpleFileUpload( $file, null, null, $purpose, $ttl, $target, $metadata );
759 }
760
761 return new WP_REST_Response( [ 'success' => true, 'data' => $result ], 200 );
762 }
763 catch ( Exception $e ) {
764 return new WP_REST_Response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
765 }
766 }
767 #endregion
768
769 #region Simple API
770 /**
771 * Executes a vision query.`
772 *
773 * @param string $message The prompt for the AI.
774 * @param string $url The URL of the image to analyze.
775 * @param string|null $path The path to the image file. If provided, the image data will be read from this file.
776 * @param array $params Additional parameters for the AI query.
777 *
778 * @return string The result of the AI query.
779 */
780 public function simpleVisionQuery( $message, $url, $path = null, $params = [] ) {
781 global $mwai_core;
782 $ai_vision_default_env = $this->core->get_option( 'ai_vision_default_env' );
783 $ai_vision_default_model = $this->core->get_option( 'ai_vision_default_model' );
784 if ( empty( $ai_vision_default_model ) ) {
785 $ai_vision_default_model = MWAI_FALLBACK_MODEL_VISION;
786 }
787 $query = new Meow_MWAI_Query_Text( $message );
788 if ( !empty( $ai_vision_default_env ) ) {
789 $query->set_env_id( $ai_vision_default_env );
790 }
791 if ( !empty( $ai_vision_default_model ) ) {
792 $query->set_model( $ai_vision_default_model );
793 }
794 $query->inject_params( $params );
795 if ( isset( $params['image_remote_upload'] ) ) {
796 $query->image_remote_upload = $params['image_remote_upload'];
797 }
798 if ( !empty( $url ) ) {
799 $query->add_file( Meow_MWAI_Query_DroppedFile::from_url( $url, 'vision' ) );
800 }
801 else if ( !empty( $path ) ) {
802 $query->add_file( Meow_MWAI_Query_DroppedFile::from_path( $path, 'vision' ) );
803 }
804 $reply = $mwai_core->run_query( $query );
805 return $reply->result;
806 }
807
808 /**
809 * Executes a chatbot query.
810 * It will use the discussion if chatId is provided in the parameters.
811 *
812 * @param string $botId The ID of the chatbot.
813 * @param string $message The prompt for the AI.
814 * @param array $params Additional parameters for the AI query.
815 *
816 * @return string The result of the AI query.
817 */
818 public function simpleChatbotQuery( $botId, $message, $params = [], $onlyReply = true ) {
819 if ( !isset( $params['messages'] ) && isset( $params['chatId'] ) ) {
820 if ( $this->core->get_option( 'chatbot_discussions' ) && $this->discussions_module ) {
821 $discussion = $this->discussions_module->get_discussion( $botId, $params['chatId'] );
822 if ( !empty( $discussion ) ) {
823 $params['messages'] = $discussion['messages'];
824
825 // CRITICAL: Also pass the discussion metadata for Responses API support
826 // The chatbot module needs the previousResponseId from discussion's extra field
827 if ( !empty( $discussion['extra'] ) ) {
828 $extra = json_decode( $discussion['extra'], true );
829
830 // Check for both possible field names
831 $responseId = $extra['previousResponseId'] ?? $extra['responseId'] ?? null;
832 $responseDate = $extra['previousResponseDate'] ?? $extra['responseDate'] ?? null;
833
834 if ( !empty( $responseId ) ) {
835 // Check if the response ID is still valid (not older than 30 days)
836 $responseDateTimestamp = !empty( $responseDate ) ? strtotime( $responseDate ) : 0;
837 $thirtyDaysAgo = time() - ( 30 * 24 * 60 * 60 );
838
839 if ( $responseDateTimestamp > $thirtyDaysAgo ) {
840 // Pass the previousResponseId directly in params
841 // This will be picked up by inject_params in the query
842 $params['previousResponseId'] = $responseId;
843 }
844 }
845 }
846 }
847 }
848 else {
849 Meow_MWAI_Logging::log( 'The chatId was provided; but the discussions are not enabled.' );
850 }
851 }
852 $fileId = isset( $params['fileId'] ) ? $params['fileId'] : null;
853 $fileIds = isset( $params['fileIds'] ) ? $params['fileIds'] : [];
854 $data = $this->chatbot_module->chat_submit( $botId, $message, $fileId, $params, false, $fileIds );
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->add_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->add_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->add_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