PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.1.6
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.1.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 / rest.php
ai-engine / classes Last commit date
data 11 months ago engines 8 months ago exceptions 11 months 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 11 months ago event.php 11 months ago init.php 8 months ago logging.php 11 months ago reply.php 8 months ago rest.php 8 months ago
rest.php
2633 lines
1 <?php
2
3 class Meow_MWAI_Rest {
4 private $core = null;
5 private $namespace = 'mwai/v1';
6
7 public function __construct( $core ) {
8 $this->core = $core;
9 add_action( 'rest_api_init', [ $this, 'rest_init' ] );
10 }
11
12 /**
13 * Retrieve the message from the parameters and optionally sanitize it.
14 *
15 * @param array &$params The parameters array, passed by reference.
16 * @param bool $sanitize Whether to sanitize the message using sanitize_text_field.
17 * @return string The retrieved (and optionally sanitized) message.
18 */
19 public function retrieve_message( &$params, $sanitize = false ): string {
20 if ( isset( $params['message'] ) ) {
21 $message = $params['message'];
22 }
23 elseif ( isset( $params['prompt'] ) ) {
24 $message = $params['prompt'];
25 unset( $params['prompt'] );
26 $params['message'] = $message;
27 Meow_MWAI_Logging::deprecated( '"prompt" is deprecated, please use "message" instead.' );
28 }
29 else {
30 $message = '';
31 }
32
33 if ( $sanitize ) {
34 $message = sanitize_text_field( $message );
35 }
36
37 return $message;
38 }
39
40 /**
41 * Helper method to create REST responses with automatic token refresh
42 *
43 * @param array $data The response data
44 * @param int $status HTTP status code
45 * @return WP_REST_Response
46 */
47 protected function create_rest_response( $data, $status = 200 ) {
48 // Always check if we need to provide a new nonce
49 $current_nonce = $this->core->get_nonce( true );
50 $request_nonce = isset( $_SERVER['HTTP_X_WP_NONCE'] ) ? $_SERVER['HTTP_X_WP_NONCE'] : null;
51
52 // Check if nonce is approaching expiration (WordPress nonces last 12-24 hours)
53 // We'll refresh if the nonce is older than 10 hours to be safe
54 $should_refresh = false;
55
56 if ( $request_nonce ) {
57 // Try to determine the age of the nonce
58 // WordPress uses a tick system where each tick is 12 hours
59 // If we're in the second half of the nonce's life, refresh it
60 $time = time();
61 $nonce_tick = wp_nonce_tick();
62
63 // Verify if the nonce is still valid but getting old
64 $verify = wp_verify_nonce( $request_nonce, 'wp_rest' );
65 if ( $verify === 2 ) {
66 // Nonce is valid but was generated 12-24 hours ago
67 $should_refresh = true;
68 // Log will be written when token is included in response
69 }
70 }
71
72 // If the nonce has changed or should be refreshed, include the new one
73 if ( $should_refresh || ( $request_nonce && $current_nonce !== $request_nonce ) ) {
74 $data['new_token'] = $current_nonce;
75
76 // Log if server debug mode is enabled
77 if ( $this->core->get_option( 'server_debug_mode' ) ) {
78 error_log( '[AI Engine] Token refresh: Nonce refreshed (12-24 hours old)' );
79 }
80 }
81
82 return new WP_REST_Response( $data, $status );
83 }
84
85 public function rest_init() {
86 try {
87 // Session Endpoint
88 register_rest_route( $this->namespace, '/start_session', [
89 'methods' => 'POST',
90 'permission_callback' => '__return_true', // Public endpoint for guest users
91 'callback' => [ $this, 'rest_start_session' ],
92 ] );
93
94 // Settings Endpoints
95 register_rest_route( $this->namespace, '/settings/update', [
96 'methods' => 'POST',
97 'permission_callback' => [ $this->core, 'can_access_settings' ],
98 'callback' => [ $this, 'rest_settings_update' ],
99 ] );
100 register_rest_route( $this->namespace, '/settings/options', [
101 'methods' => 'GET',
102 'permission_callback' => [ $this->core, 'can_access_settings' ],
103 'callback' => [ $this, 'rest_settings_list' ],
104 ] );
105 register_rest_route( $this->namespace, '/settings/reset', [
106 'methods' => 'POST',
107 'permission_callback' => [ $this->core, 'can_access_settings' ],
108 'callback' => [ $this, 'rest_settings_reset' ],
109 ] );
110 register_rest_route( $this->namespace, '/settings/chatbots', [
111 'methods' => ['GET', 'POST'],
112 'permission_callback' => [ $this->core, 'can_access_settings' ],
113 'callback' => [ $this, 'rest_settings_chatbots' ],
114 ] );
115 register_rest_route( $this->namespace, '/settings/themes', [
116 'methods' => ['GET', 'POST'],
117 'permission_callback' => [ $this->core, 'can_access_settings' ],
118 'callback' => [ $this, 'rest_settings_themes' ],
119 ] );
120
121 // System Endpoints
122 register_rest_route( $this->namespace, '/system/logs/list', [
123 'methods' => 'POST',
124 'permission_callback' => [ $this->core, 'can_access_settings' ],
125 'callback' => [ $this, 'rest_system_logs_list' ],
126 ] );
127 register_rest_route( $this->namespace, '/system/logs/delete', [
128 'methods' => 'POST',
129 'permission_callback' => [ $this->core, 'can_access_settings' ],
130 'callback' => [ $this, 'rest_system_logs_delete' ],
131 ] );
132 register_rest_route( $this->namespace, '/system/logs/meta', [
133 'methods' => 'POST',
134 'permission_callback' => [ $this->core, 'can_access_settings' ],
135 'callback' => [ $this, 'rest_system_logs_meta_get' ],
136 ] );
137 register_rest_route( $this->namespace, '/system/logs/activity', [
138 'methods' => 'POST',
139 'permission_callback' => [ $this->core, 'can_access_settings' ],
140 'callback' => [ $this, 'rest_system_logs_activity' ],
141 ] );
142 register_rest_route( $this->namespace, '/system/logs/activity_daily', [
143 'methods' => 'POST',
144 'permission_callback' => [ $this->core, 'can_access_settings' ],
145 'callback' => [ $this, 'rest_system_logs_activity_daily' ],
146 ] );
147 register_rest_route( $this->namespace, '/system/templates', [
148 'methods' => 'POST',
149 'permission_callback' => [ $this->core, 'can_access_features' ],
150 'callback' => [ $this, 'rest_system_templates_save' ],
151 ] );
152 register_rest_route( $this->namespace, '/system/templates', [
153 'methods' => 'GET',
154 'permission_callback' => [ $this->core, 'can_access_features' ],
155 'callback' => [ $this, 'rest_system_templates_get' ],
156 ] );
157
158 // AI Endpoints
159 register_rest_route( $this->namespace, '/ai/models', [
160 'methods' => 'POST',
161 'permission_callback' => [ $this->core, 'can_access_features' ],
162 'callback' => [ $this, 'rest_ai_models' ],
163 ] );
164 register_rest_route( $this->namespace, '/ai/test_connection', [
165 'methods' => 'POST',
166 'permission_callback' => [ $this->core, 'can_access_settings' ],
167 'callback' => [ $this, 'rest_ai_test_connection' ],
168 ] );
169 register_rest_route( $this->namespace, '/ai/completions', [
170 'methods' => 'POST',
171 'permission_callback' => [ $this->core, 'can_access_features' ],
172 'callback' => [ $this, 'rest_ai_completions' ],
173 ] );
174 register_rest_route( $this->namespace, '/ai/images', [
175 'methods' => 'POST',
176 'permission_callback' => [ $this->core, 'can_access_features' ],
177 'callback' => [ $this, 'rest_ai_images' ],
178 ] );
179 register_rest_route( $this->namespace, '/ai/image_edit', [
180 'methods' => 'POST',
181 'permission_callback' => [ $this->core, 'can_access_features' ],
182 'callback' => [ $this, 'rest_ai_image_edit' ],
183 ] );
184 register_rest_route( $this->namespace, '/ai/copilot', [
185 'methods' => 'POST',
186 'permission_callback' => [ $this->core, 'can_access_features' ],
187 'callback' => [ $this, 'rest_ai_copilot' ],
188 ] );
189
190 register_rest_route( $this->namespace, '/ai/magic_wand', [
191 'methods' => 'POST',
192 'callback' => [ $this, 'rest_ai_magic_wand' ],
193 'permission_callback' => [ $this->core, 'can_access_features' ],
194 ] );
195 register_rest_route( $this->namespace, '/ai/moderate', [
196 'methods' => 'POST',
197 'permission_callback' => [ $this->core, 'can_access_settings' ],
198 'callback' => [ $this, 'rest_ai_moderate' ],
199 ] );
200 register_rest_route( $this->namespace, '/ai/transcribe_audio', [
201 'methods' => 'POST',
202 'permission_callback' => [ $this->core, 'can_access_settings' ],
203 'callback' => [ $this, 'rest_ai_transcribe_audio' ],
204 ] );
205 register_rest_route( $this->namespace, '/ai/transcribe_image', [
206 'methods' => 'POST',
207 'permission_callback' => [ $this->core, 'can_access_settings' ],
208 'callback' => [ $this, 'rest_ai_transcribe_image' ],
209 ] );
210 register_rest_route( $this->namespace, '/ai/json', [
211 'methods' => 'POST',
212 'permission_callback' => [ $this->core, 'can_access_settings' ],
213 'callback' => [ $this, 'rest_ai_json' ],
214 ] );
215
216 // MCP Endpoints
217 register_rest_route( $this->namespace, '/mcp/functions', [
218 'methods' => 'GET',
219 'permission_callback' => [ $this->core, 'can_access_settings' ],
220 'callback' => [ $this, 'rest_mcp_functions' ],
221 ] );
222
223 // Helpers Endpoints
224 register_rest_route( $this->namespace, '/helpers/update_post_title', [
225 'methods' => 'POST',
226 'permission_callback' => [ $this->core, 'can_access_features' ],
227 'callback' => [ $this, 'rest_helpers_update_title' ],
228 ] );
229 register_rest_route( $this->namespace, '/helpers/update_post_excerpt', [
230 'methods' => 'POST',
231 'permission_callback' => [ $this->core, 'can_access_features' ],
232 'callback' => [ $this, 'rest_helpers_update_excerpt' ],
233 ] );
234 register_rest_route( $this->namespace, '/helpers/create_post', [
235 'methods' => 'POST',
236 'permission_callback' => [ $this->core, 'can_access_features' ],
237 'callback' => [ $this, 'rest_helpers_create_post' ],
238 ] );
239 register_rest_route( $this->namespace, '/helpers/create_image', [
240 'methods' => 'POST',
241 'permission_callback' => [ $this->core, 'can_access_features' ],
242 'callback' => [ $this, 'rest_helpers_create_images' ],
243 ] );
244 register_rest_route( $this->namespace, '/helpers/generate_image_meta', [
245 'methods' => 'POST',
246 'permission_callback' => [ $this->core, 'can_access_features' ],
247 'callback' => [ $this, 'rest_helpers_generate_image_meta' ],
248 ] );
249 register_rest_route( $this->namespace, '/helpers/update_media_metadata', [
250 'methods' => 'POST',
251 'permission_callback' => [ $this->core, 'can_access_features' ],
252 'callback' => [ $this, 'rest_helpers_update_media_metadata' ],
253 ] );
254 register_rest_route( $this->namespace, '/helpers/create_video', [
255 'methods' => 'POST',
256 'permission_callback' => [ $this->core, 'can_access_features' ],
257 'callback' => [ $this, 'rest_helpers_create_video' ],
258 ] );
259 register_rest_route( $this->namespace, '/helpers/video_status', [
260 'methods' => 'POST',
261 'permission_callback' => [ $this->core, 'can_access_features' ],
262 'callback' => [ $this, 'rest_helpers_video_status' ],
263 ] );
264 register_rest_route( $this->namespace, '/helpers/download_video', [
265 'methods' => 'POST',
266 'permission_callback' => [ $this->core, 'can_access_features' ],
267 'callback' => [ $this, 'rest_helpers_download_video' ],
268 ] );
269 register_rest_route( $this->namespace, '/helpers/delete_video', [
270 'methods' => 'POST',
271 'permission_callback' => [ $this->core, 'can_access_features' ],
272 'callback' => [ $this, 'rest_helpers_delete_video' ],
273 ] );
274 register_rest_route( $this->namespace, '/helpers/save_video_to_library', [
275 'methods' => 'POST',
276 'permission_callback' => [ $this->core, 'can_access_features' ],
277 'callback' => [ $this, 'rest_helpers_save_video_to_library' ],
278 ] );
279 register_rest_route( $this->namespace, '/helpers/delete_video_from_library', [
280 'methods' => 'POST',
281 'permission_callback' => [ $this->core, 'can_access_features' ],
282 'callback' => [ $this, 'rest_helpers_delete_video_from_library' ],
283 ] );
284 register_rest_route( $this->namespace, '/helpers/list_draft_media', [
285 'methods' => 'GET',
286 'permission_callback' => [ $this->core, 'can_access_features' ],
287 'callback' => [ $this, 'rest_helpers_list_draft_media' ],
288 ] );
289 register_rest_route( $this->namespace, '/helpers/approve_media', [
290 'methods' => 'POST',
291 'permission_callback' => [ $this->core, 'can_access_features' ],
292 'callback' => [ $this, 'rest_helpers_approve_media' ],
293 ] );
294 register_rest_route( $this->namespace, '/helpers/reject_media', [
295 'methods' => 'POST',
296 'permission_callback' => [ $this->core, 'can_access_features' ],
297 'callback' => [ $this, 'rest_helpers_reject_media' ],
298 ] );
299 register_rest_route( $this->namespace, '/helpers/count_posts', [
300 'methods' => 'GET',
301 'permission_callback' => [ $this->core, 'can_access_features' ],
302 'callback' => [ $this, 'rest_helpers_count_posts' ],
303 ] );
304 register_rest_route( $this->namespace, '/helpers/posts_ids', [
305 'methods' => 'GET',
306 'permission_callback' => [ $this->core, 'can_access_features' ],
307 'callback' => [ $this, 'rest_helpers_posts_ids' ],
308 ] );
309 register_rest_route( $this->namespace, '/helpers/post_types', [
310 'methods' => 'GET',
311 'permission_callback' => [ $this->core, 'can_access_features' ],
312 'callback' => [ $this, 'rest_helpers_post_types' ],
313 ] );
314 register_rest_route( $this->namespace, '/helpers/post_content', [
315 'methods' => 'GET',
316 'permission_callback' => [ $this->core, 'can_access_features' ],
317 'callback' => [ $this, 'rest_helpers_post_content' ],
318 ] );
319 register_rest_route( $this->namespace, '/helpers/check_posts_content', [
320 'methods' => 'POST',
321 'permission_callback' => [ $this->core, 'can_access_features' ],
322 'callback' => [ $this, 'rest_helpers_check_posts_content' ],
323 ] );
324 register_rest_route( $this->namespace, '/helpers/run_tasks', [
325 'methods' => 'POST',
326 'permission_callback' => [ $this->core, 'can_access_features' ],
327 'callback' => [ $this, 'rest_helpers_run_tasks' ],
328 ] );
329 register_rest_route( $this->namespace, '/helpers/optimize_database', [
330 'methods' => 'POST',
331 'permission_callback' => [ $this->core, 'can_access_settings' ],
332 'callback' => [ $this, 'rest_helpers_optimize_database' ],
333 ] );
334 register_rest_route( $this->namespace, '/helpers/cron_events', [
335 'methods' => 'GET',
336 'permission_callback' => [ $this->core, 'can_access_features' ],
337 'callback' => [ $this, 'rest_helpers_cron_events' ],
338 ] );
339 register_rest_route( $this->namespace, '/helpers/run_cron', [
340 'methods' => 'POST',
341 'permission_callback' => [ $this->core, 'can_access_features' ],
342 'callback' => [ $this, 'rest_helpers_run_cron' ],
343 ] );
344
345 // OpenAI Endpoints
346 register_rest_route( $this->namespace, '/openai/files/list', [
347 'methods' => 'GET',
348 'permission_callback' => [ $this->core, 'can_access_settings' ],
349 'callback' => [ $this, 'rest_openai_files_get' ],
350 ] );
351 register_rest_route( $this->namespace, '/openai/files/upload', [
352 'methods' => 'POST',
353 'permission_callback' => [ $this->core, 'can_access_settings' ],
354 'callback' => [ $this, 'rest_openai_files_upload' ],
355 ] );
356 register_rest_route( $this->namespace, '/openai/files/delete', [
357 'methods' => 'POST',
358 'permission_callback' => [ $this->core, 'can_access_settings' ],
359 'callback' => [ $this, 'rest_openai_files_delete' ],
360 ] );
361 register_rest_route( $this->namespace, '/openai/files/download', [
362 'methods' => 'POST',
363 'permission_callback' => [ $this->core, 'can_access_settings' ],
364 'callback' => [ $this, 'rest_openai_files_download' ],
365 ] );
366 register_rest_route( $this->namespace, '/openai/files/finetune', [
367 'methods' => 'POST',
368 'permission_callback' => [ $this->core, 'can_access_settings' ],
369 'callback' => [ $this, 'rest_openai_files_finetune' ],
370 ] );
371 register_rest_route( $this->namespace, '/openai/finetunes/list_deleted', [
372 'methods' => 'GET',
373 'permission_callback' => [ $this->core, 'can_access_settings' ],
374 'callback' => [ $this, 'rest_openai_deleted_finetunes_get' ],
375 ] );
376
377 // register_rest_route( $this->namespace, '/openai/models', array(
378 // 'methods' => 'GET',
379 // 'permission_callback' => [ $this->core, 'can_access_settings' ],
380 // 'callback' => [ $this, 'rest_openai_models_get' ],
381 // ) );
382
383 register_rest_route( $this->namespace, '/openai/finetunes/list', [
384 'methods' => 'GET',
385 'permission_callback' => [ $this->core, 'can_access_settings' ],
386 'callback' => [ $this, 'rest_openai_finetunes_get' ],
387 ] );
388 register_rest_route( $this->namespace, '/openai/finetunes/delete', [
389 'methods' => 'POST',
390 'permission_callback' => [ $this->core, 'can_access_settings' ],
391 'callback' => [ $this, 'rest_openai_finetunes_delete' ],
392 ] );
393 register_rest_route( $this->namespace, '/openai/finetunes/cancel', [
394 'methods' => 'POST',
395 'permission_callback' => [ $this->core, 'can_access_settings' ],
396 'callback' => [ $this, 'rest_openai_finetunes_cancel' ],
397 ] );
398
399 // Logging Endpoints
400 register_rest_route( $this->namespace, '/get_logs', [
401 'methods' => 'GET',
402 'permission_callback' => [ $this->core, 'can_access_features' ],
403 'callback' => [ $this, 'rest_get_logs' ]
404 ] );
405 register_rest_route( $this->namespace, '/clear_logs', [
406 'methods' => 'GET',
407 'permission_callback' => [ $this->core, 'can_access_features' ],
408 'callback' => [ $this, 'rest_clear_logs' ]
409 ] );
410
411 // Forms Endpoints
412 register_rest_route( $this->namespace, '/forms/list', [
413 'methods' => 'GET',
414 'permission_callback' => [ $this->core, 'can_access_settings' ],
415 'callback' => [ $this, 'rest_forms_list' ]
416 ] );
417 register_rest_route( $this->namespace, '/forms/get', [
418 'methods' => 'GET',
419 'permission_callback' => [ $this->core, 'can_access_settings' ],
420 'callback' => [ $this, 'rest_forms_get' ]
421 ] );
422 register_rest_route( $this->namespace, '/forms/create', [
423 'methods' => 'POST',
424 'permission_callback' => [ $this->core, 'can_access_settings' ],
425 'callback' => [ $this, 'rest_forms_create' ]
426 ] );
427 register_rest_route( $this->namespace, '/forms/update', [
428 'methods' => 'POST',
429 'permission_callback' => [ $this->core, 'can_access_settings' ],
430 'callback' => [ $this, 'rest_forms_update' ]
431 ] );
432 register_rest_route( $this->namespace, '/forms/delete', [
433 'methods' => 'POST',
434 'permission_callback' => [ $this->core, 'can_access_settings' ],
435 'callback' => [ $this, 'rest_forms_delete' ]
436 ] );
437 }
438 catch ( Exception $e ) {
439 Meow_MWAI_Logging::error( 'REST API initialization failed: ' . $e->getMessage() );
440 }
441 }
442
443 public function rest_start_session() {
444 try {
445 $sessionId = $this->core->get_session_id();
446 $restNonce = $this->core->get_nonce( true );
447
448 $response = [
449 'success' => true,
450 'sessionId' => $sessionId,
451 'restNonce' => $restNonce
452 ];
453
454 // If in test mode and we have a new token, it will be added by create_rest_response
455 // But we also want to ensure the restNonce matches the test token if available
456 if ( get_option( 'mwai_token_test_mode' ) ) {
457 $token_data = get_option( 'mwai_test_token_data' );
458 if ( $token_data && isset( $token_data['token'] ) ) {
459 $response['restNonce'] = $token_data['token'];
460 }
461 }
462
463 return $this->create_rest_response( $response, 200 );
464 }
465 catch ( Exception $e ) {
466 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
467 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
468 }
469 }
470
471 public function rest_settings_list() {
472 return $this->create_rest_response( [
473 'success' => true,
474 'options' => $this->core->get_all_options()
475 ], 200 );
476 }
477
478 public function rest_helpers_cron_events( $request ) {
479 try {
480 // Only show AI Engine cron events (those starting with mwai_)
481 $cron_events = [];
482 $crons = _get_cron_array();
483
484 // Get transient data for last run status (we'll store this when crons run)
485 $last_run_data = get_transient( 'mwai_cron_last_run' ) ?: [];
486
487 // Get all scheduled events and filter for AI Engine ones
488 foreach ( $crons as $timestamp => $cron ) {
489 foreach ( $cron as $hook => $details ) {
490 // Only process AI Engine hooks (starting with mwai_)
491 if ( strpos( $hook, 'mwai_' ) !== 0 ) {
492 continue;
493 }
494
495 $schedule_key = array_keys( $details )[0];
496 $schedule_info = $details[$schedule_key];
497
498 // Get schedule display name
499 $schedule = $schedule_info['schedule'];
500 $schedules = wp_get_schedules();
501 $schedule_display = isset( $schedules[$schedule]['display'] ) ?
502 $schedules[$schedule]['display'] : $schedule;
503
504 $event_info = [
505 'hook' => $hook,
506 'name' => $this->get_cron_display_name( $hook ),
507 'description' => $this->get_cron_description( $hook ),
508 'next_run' => $timestamp,
509 'next_run_human' => '',
510 'last_run' => isset( $last_run_data[$hook]['time'] ) ? $last_run_data[$hook]['time'] : null,
511 'last_run_human' => isset( $last_run_data[$hook]['time'] ) ?
512 human_time_diff( $last_run_data[$hook]['time'], time() ) . ' ago' :
513 'Never',
514 'last_status' => isset( $last_run_data[$hook]['status'] ) ? $last_run_data[$hook]['status'] : 'unknown',
515 'schedule' => $schedule_display,
516 'is_running' => false,
517 'is_scheduled' => true
518 ];
519
520 // Calculate next run time properly
521 // If we have a last run time and schedule interval, calculate the actual next run
522 if ( isset( $last_run_data[$hook]['time'] ) && isset( $schedules[$schedule]['interval'] ) ) {
523 $interval = $schedules[$schedule]['interval'];
524 $last_run = $last_run_data[$hook]['time'];
525 $expected_next_run = $last_run + $interval;
526
527 // If the scheduled timestamp is in the past but we ran recently,
528 // the next run should be based on the last actual run
529 if ( $timestamp < time() &&
530 $last_run > ( time() - $interval ) ) {
531 // Cron ran recently, calculate next run from last run time
532 $event_info['next_run'] = $expected_next_run;
533 $event_info['next_run_human'] = 'In ' . human_time_diff( time(), $expected_next_run );
534 } else if ( $timestamp < time() ) {
535 // Genuinely overdue
536 $event_info['next_run_human'] = 'Overdue by ' . human_time_diff( time(), $timestamp );
537 } else {
538 // Future scheduled time
539 $event_info['next_run_human'] = 'In ' . human_time_diff( time(), $timestamp );
540 }
541 } else {
542 // No last run data, use the scheduled timestamp but be conservative about "overdue"
543 if ( $timestamp < time() ) {
544 // Only show as overdue if it's significantly past due (more than the schedule interval)
545 // to avoid false positives for crons that might be running but not tracked
546 $time_past_due = time() - $timestamp;
547 $interval = isset( $schedules[$schedule]['interval'] ) ? $schedules[$schedule]['interval'] : 3600; // Default 1 hour
548
549 if ( $time_past_due > $interval ) {
550 $event_info['next_run_human'] = 'Overdue by ' . human_time_diff( time(), $timestamp );
551 } else {
552 $event_info['next_run_human'] = 'Due to run';
553 }
554 } else {
555 $event_info['next_run_human'] = 'In ' . human_time_diff( time(), $timestamp );
556 }
557 }
558
559 // Check if currently running (via transient)
560 $running_transient = get_transient( 'mwai_cron_running_' . $hook );
561 if ( $running_transient ) {
562 $event_info['is_running'] = true;
563 }
564
565 $cron_events[] = $event_info;
566 }
567 }
568
569 return $this->create_rest_response( [ 'success' => true, 'events' => $cron_events ], 200 );
570 }
571 catch ( Exception $e ) {
572 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
573 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
574 }
575 }
576
577 public function rest_helpers_run_cron( $request ) {
578 try {
579 $params = $request->get_json_params();
580 $hook = isset( $params['hook'] ) ? $params['hook'] : null;
581
582 if ( empty( $hook ) ) {
583 return $this->create_rest_response( [ 'success' => false, 'message' => 'No cron hook provided' ], 400 );
584 }
585
586 // Only allow running AI Engine crons (starting with mwai_)
587 if ( strpos( $hook, 'mwai_' ) !== 0 ) {
588 return $this->create_rest_response( [ 'success' => false, 'message' => 'Invalid cron hook' ], 400 );
589 }
590
591 // Prevent running the Tasks Runner hooks directly - they should only run via cron
592 if ( $hook === 'mwai_tasks_internal_run' || $hook === 'mwai_tasks_internal_dev_run' ) {
593 return $this->create_rest_response( [
594 'success' => false,
595 'message' => 'The Tasks Runner cannot be triggered manually. It runs automatically based on its schedule.'
596 ], 403 );
597 }
598
599 // Check if the hook exists
600 if ( !has_action( $hook ) ) {
601 return $this->create_rest_response( [ 'success' => false, 'message' => 'Cron hook not found' ], 404 );
602 }
603
604 // Run the cron action
605 do_action( $hook );
606
607 return $this->create_rest_response( [
608 'success' => true,
609 'message' => 'Cron executed successfully',
610 'hook' => $hook
611 ], 200 );
612 }
613 catch ( Exception $e ) {
614 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
615 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
616 }
617 }
618
619 private function get_cron_display_name( $hook ) {
620 $names = [
621 'mwai_tasks_internal_run' => 'Tasks Runner',
622 'mwai_tasks_internal_dev_run' => 'Tasks Runner (Dev)',
623 'mwai_cleanup_oauth' => 'OAuth Cleanup',
624 'mwai_files_cleanup' => 'Files Cleanup',
625 'mwai_discussions' => 'Discussions Cleanup'
626 ];
627 return isset( $names[$hook] ) ? $names[$hook] : $hook;
628 }
629
630 private function get_cron_description( $hook ) {
631 $descriptions = [
632 'mwai_tasks_internal_run' => 'Processes background tasks and queued operations.',
633 'mwai_tasks_internal_dev_run' => 'Processes tasks in development mode (every 5 seconds).',
634 'mwai_cleanup_oauth' => 'Cleans up expired OAuth tokens and sessions.',
635 'mwai_files_cleanup' => 'Removes expired files based on expiration dates.',
636 'mwai_discussions' => 'Maintains chat discussions database and removes old entries.'
637 ];
638 return isset( $descriptions[$hook] ) ? $descriptions[$hook] : '';
639 }
640
641 public function rest_settings_update( $request ) {
642 try {
643 $params = $request->get_json_params();
644 $value = $params['options'];
645 $options = $this->core->update_options( $value );
646 $success = !!$options;
647 $message = __( $success ? 'OK' : 'Could not update options.', 'ai-engine' );
648 return $this->create_rest_response( [ 'success' => $success, 'message' => $message, 'options' => $options ], 200 );
649 }
650 catch ( Exception $e ) {
651 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
652 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
653 }
654 }
655
656 public function rest_settings_reset() {
657 try {
658 $options = $this->core->reset_options();
659 $success = !!$options;
660 $message = __( $success ? 'OK' : 'Could not reset options.', 'ai-engine' );
661 return $this->create_rest_response( [ 'success' => $success, 'message' => $message, 'options' => $options ], 200 );
662 }
663 catch ( Exception $e ) {
664 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
665 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
666 }
667 }
668
669 public function rest_ai_models( $request ) {
670 try {
671 $params = $request->get_json_params();
672 $envId = $params['envId'];
673 $engine = Meow_MWAI_Engines_Factory::get( $this->core, $envId );
674 $models = $engine->retrieve_models();
675 return $this->create_rest_response( [ 'success' => true, 'models' => $models ], 200 );
676 }
677 catch ( Exception $e ) {
678 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
679 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
680 }
681 }
682
683 public function rest_ai_test_connection( $request ) {
684 try {
685 $params = $request->get_json_params();
686 $envId = $params['env_id'];
687
688 // Get the environment details
689 $env = null;
690 $envs = $this->core->get_option( 'ai_envs' );
691 foreach ( $envs as $e ) {
692 if ( $e['id'] === $envId ) {
693 $env = $e;
694 break;
695 }
696 }
697
698 if ( !$env ) {
699 throw new Exception( __( 'Environment not found.', 'ai-engine' ) );
700 }
701
702 // Get the engine and test connection
703 $engine = Meow_MWAI_Engines_Factory::get( $this->core, $envId );
704 $result = $engine->connection_check();
705
706 // Format the response based on provider
707 $response = [
708 'success' => true,
709 'provider' => $env['type'],
710 'name' => $env['name'],
711 'data' => $result
712 ];
713
714 return $this->create_rest_response( $response, 200 );
715 }
716 catch ( Exception $e ) {
717 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
718 return $this->create_rest_response( [
719 'success' => false,
720 'error' => $message,
721 'provider' => isset( $env ) ? $env['type'] : 'unknown'
722 ], 200 ); // Return 200 even on error for consistent modal display
723 }
724 }
725
726 public function rest_ai_completions( $request ) {
727 try {
728 $params = $request->get_json_params();
729 $message = $this->retrieve_message( $params );
730 $query = new Meow_MWAI_Query_Text( $message );
731 $query->inject_params( $params );
732
733 // Handle streaming
734 $stream = $params['stream'] ?? false;
735 $streamCallback = null;
736 if ( $stream ) {
737 $streamCallback = function ( $reply ) use ( $query ) {
738 //$raw = _wp_specialchars( $reply, ENT_NOQUOTES, 'UTF-8', true );
739 $raw = $reply;
740 $this->core->stream_push( [ 'type' => 'live', 'data' => $raw ], $query );
741 if ( ob_get_level() > 0 ) {
742 ob_flush();
743 }
744 flush();
745 };
746 if ( headers_sent( $filename, $linenum ) ) {
747 throw new Exception( "Headers already sent in $filename on line $linenum. Cannot start streaming." );
748 }
749 header( 'Cache-Control: no-cache' );
750 header( 'Content-Type: text/event-stream' );
751 header( 'X-Accel-Buffering: no' ); // This is useful to disable buffering in nginx through headers.
752 ob_implicit_flush( true );
753 if ( ob_get_level() > 0 ) {
754 ob_end_flush();
755 }
756 }
757
758 // Process Reply
759 $reply = $this->core->run_query( $query, $streamCallback );
760 $restRes = [
761 'success' => true,
762 'data' => $reply->result,
763 'usage' => $reply->usage
764 ];
765 if ( $stream ) {
766 $this->core->stream_push( [ 'type' => 'end', 'data' => json_encode( $restRes ) ], $query );
767 die();
768 }
769 return $this->create_rest_response( $restRes, 200 );
770 }
771 catch ( Exception $e ) {
772 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
773 if ( $stream ) {
774 $this->core->stream_push( [ 'type' => 'error', 'data' => $message ], $query );
775 }
776 else {
777 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
778 }
779 }
780 }
781
782 public function rest_ai_images( $request ) {
783 try {
784 $params = $request->get_json_params();
785 $message = $this->retrieve_message( $params );
786 $query = new Meow_MWAI_Query_Image( $message );
787 $query->inject_params( $params );
788 $reply = $this->core->run_query( $query );
789 return $this->create_rest_response( [ 'success' => true, 'data' => $reply->results, 'usage' => $reply->usage ], 200 );
790 }
791 catch ( Exception $e ) {
792 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
793 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
794 }
795 }
796
797 public function rest_ai_image_edit( $request ) {
798 try {
799 // Check if this is a multipart request with files
800 $files = $request->get_file_params();
801 $params = null;
802
803 // Debug logging
804 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
805 error_log( '[AI Engine Queries] Image Edit Request - Method: ' . $request->get_method() );
806 $content_type = $request->get_content_type();
807 if ( is_array( $content_type ) ) {
808 error_log( '[AI Engine Queries] Image Edit Request - Content-Type: ' . $content_type['value'] );
809 }
810 else {
811 error_log( '[AI Engine Queries] Image Edit Request - Content-Type: ' . $content_type );
812 }
813 error_log( '[AI Engine Queries] Image Edit Request - Has files: ' . ( !empty( $files ) ? 'yes (' . count( $files ) . ')' : 'no' ) );
814 }
815
816 if ( !empty( $files ) ) {
817 // Handle multipart form data - get all params including POST data
818 $params = $request->get_params();
819 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
820 error_log( '[AI Engine Queries] Image Edit Request - Using form data params' );
821 }
822 }
823 else {
824 // Try to get body params first (for form data without files)
825 $body_params = $request->get_body_params();
826 if ( !empty( $body_params ) ) {
827 $params = $body_params;
828 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
829 error_log( '[AI Engine Queries] Image Edit Request - Using body params' );
830 }
831 }
832 else {
833 // Handle JSON request
834 $params = $request->get_json_params();
835 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
836 error_log( '[AI Engine Queries] Image Edit Request - Using JSON params' );
837 }
838 }
839 }
840
841 // Ensure params is always an array
842 if ( empty( $params ) ) {
843 $params = [];
844 }
845
846 // Debug logging
847 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
848 error_log( '[AI Engine Queries] Image Edit Request - Has files: ' . ( !empty( $files ) ? 'yes' : 'no' ) );
849 error_log( '[AI Engine Queries] Image Edit Request - Params: ' . json_encode( $params ) );
850 }
851
852 $message = $this->retrieve_message( $params );
853 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
854 $query = new Meow_MWAI_Query_EditImage( $message );
855
856 // The inject_params method will handle setting the file from mediaId
857 $query->inject_params( $params );
858
859 // Handle mask file if provided
860 if ( !empty( $files['mask'] ) ) {
861 $mask_file = $files['mask'];
862 if ( $mask_file['error'] === UPLOAD_ERR_OK ) {
863 $mask_data = file_get_contents( $mask_file['tmp_name'] );
864 $query->set_mask( Meow_MWAI_Query_DroppedFile::from_data( $mask_data, 'vision', $mask_file['type'] ) );
865 }
866 }
867
868 $reply = $this->core->run_query( $query );
869 return $this->create_rest_response( [ 'success' => true, 'data' => $reply->results, 'usage' => $reply->usage ], 200 );
870 }
871 catch ( Exception $e ) {
872 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
873 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
874 }
875 }
876
877 public function rest_ai_magic_wand( $request ) {
878 try {
879 $params = $request->get_json_params();
880 $action = isset( $params['action'] ) ? $params['action'] : null;
881 $data = isset( $params['data'] ) ? $params['data'] : null;
882 if ( empty( $data ) || empty( $action ) ) {
883 return $this->create_rest_response( [ 'success' => false, 'message' => 'An action and some data are required.' ], 500 );
884 }
885 $data = apply_filters( 'mwai_magic_wand_' . $action, '', $data );
886 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
887 }
888 catch ( Exception $e ) {
889 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
890 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
891 }
892 }
893
894 public function rest_ai_copilot( $request ) {
895 try {
896 $params = $request->get_json_params();
897 $action = sanitize_text_field( $params['action'] );
898 $message = $this->retrieve_message( $params, true );
899 $context = sanitize_text_field( $params['context'] );
900 $postId = !empty( $params['postId'] ) ? intval( $params['postId'] ) : null;
901 if ( empty( $action ) || empty( $message ) ) {
902 return $this->create_rest_response( [ 'success' => false, 'message' => 'Copilot needs an action and a prompt.' ], 500 );
903 }
904
905 global $mwai;
906 $result = null;
907 $params = [ 'scope' => 'copilot' ];
908
909 if ( $action === 'text' ) {
910 $prompt = "Here is the current article: \n\n===\n\n" . $context . "\n\n===\n\nIn this article, instead of the [== CURRENT BLOCK ==] placeholder, the author needs additional content. This new content should use the same tone, style, context, it should naturally flow in the article. The author shared additional information for this request:\n\n===\n\n" . $message . "\n\n===\n\nPlease provide the additional content. Only output the additional content, not the entire article, no need for extra information, and no need for the placeholders. Only output the content that should be added.";
911 if ( !empty( $model ) ) {
912 $params['model'] = $model;
913 }
914 $result = $mwai->simpleTextQuery( $prompt, $params );
915 }
916 else if ( $action === 'image' ) {
917 $prompt = "Here is the current article: \n\n===\n\n" . $context . "\n\n===\n\nIn this article, instead of the [== CURRENT BLOCK ==] placeholder, the author needs an image. Please write a detailed description (prompt) for that image that would fit this context. The image should be relevant to the article. The author shared additional information for this request:\n\n===\n\n" . $message . "\n\n===\n\nPlease only output the description for the image, not the entire article, no need for extra information, and no need for the placeholders. Only output the description.";
918
919 // Create the image
920 $simplifiedPrompt = $mwai->simpleTextQuery( $prompt, $params );
921 $media = $mwai->imageQueryForMediaLibrary( $simplifiedPrompt, $params, $postId );
922 $result = [ 'media' => $media ];
923 }
924 return $this->create_rest_response( [ 'success' => true, 'data' => $result ], 200 );
925 }
926 catch ( Exception $e ) {
927 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
928 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
929 }
930 }
931
932 public function rest_helpers_update_title( $request ) {
933 try {
934 $params = $request->get_json_params();
935 $title = sanitize_text_field( $params['title'] );
936 $postId = intval( $params['postId'] );
937 $post = get_post( $postId );
938 if ( !$post ) {
939 throw new Exception( __( 'There is no post with this ID.', 'ai-engine' ) );
940 }
941 $post->post_title = $title;
942 //$post->post_name = sanitize_title( $title );
943 wp_update_post( $post );
944 return $this->create_rest_response( [ 'success' => true, 'message' => 'Title updated.' ], 200 );
945 }
946 catch ( Exception $e ) {
947 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
948 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
949 }
950 }
951
952 public function rest_helpers_update_excerpt( $request ) {
953 try {
954 $params = $request->get_json_params();
955 $excerpt = sanitize_text_field( $params['excerpt'] );
956 $postId = intval( $params['postId'] );
957 $post = get_post( $postId );
958 if ( !$post ) {
959 throw new Exception( __( 'There is no post with this ID.', 'ai-engine' ) );
960 }
961 $post->post_excerpt = $excerpt;
962 wp_update_post( $post );
963 return $this->create_rest_response( [ 'success' => true, 'message' => 'Excerpt updated.' ], 200 );
964 }
965 catch ( Exception $e ) {
966 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
967 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
968 }
969 }
970
971 public function rest_helpers_create_post( $request ) {
972 try {
973 $params = $request->get_json_params();
974 $title = sanitize_text_field( $params['title'] );
975 $content = sanitize_textarea_field( $params['content'] );
976 $excerpt = sanitize_text_field( $params['excerpt'] );
977 $postType = sanitize_text_field( $params['postType'] );
978 $post = new stdClass();
979 $post->post_title = $title;
980 $post->post_excerpt = $excerpt;
981 $post->post_content = $content;
982 $post->post_status = 'draft';
983 $post->post_type = isset( $postType ) ? $postType : 'post';
984 // TODO: Let's try to avoid using Markdown to create the Post
985 // Instead, we should create Gutenberg Blocks, or simple HTML.
986 // Then, we can get rid of the library for Markdown.
987 $post->post_content = $this->core->markdown_to_html( $post->post_content );
988 $postId = wp_insert_post( $post );
989 return $this->create_rest_response( [ 'success' => true, 'postId' => $postId ], 200 );
990 }
991 catch ( Exception $e ) {
992 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
993 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
994 }
995 }
996
997 public function rest_helpers_create_images( $request ) {
998 try {
999 $params = $request->get_json_params();
1000 $title = sanitize_text_field( $params['title'] );
1001 $caption = sanitize_text_field( $params['caption'] );
1002 $alt = sanitize_text_field( $params['alt'] );
1003 $description = sanitize_text_field( $params['description'] );
1004 $url = $params['url'];
1005 $filename = sanitize_text_field( $params['filename'] );
1006
1007 // Prepare AI metadata
1008 $ai_metadata = [];
1009 if ( !empty( $params['model'] ) ) {
1010 $ai_metadata['model'] = $params['model'];
1011 }
1012 if ( !empty( $params['latency'] ) ) {
1013 $ai_metadata['latency'] = $params['latency'];
1014 }
1015 if ( !empty( $params['env_id'] ) ) {
1016 $ai_metadata['env_id'] = $params['env_id'];
1017 }
1018
1019 // Debug logging
1020 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
1021 error_log( '[AI Engine] create_image metadata: ' . json_encode( $ai_metadata ) );
1022 }
1023
1024 // Create as mwai_image post type (draft image)
1025 $attachmentId = $this->core->add_image_from_url( $url, $filename, $title, $description, $caption, $alt, null, 'inherit', 'mwai_image', $ai_metadata );
1026
1027 // Add to user's draft media
1028 $user_id = get_current_user_id();
1029 $draft_media = get_user_meta( $user_id, 'mwai_draft_media', true );
1030 if ( !is_array( $draft_media ) ) {
1031 $draft_media = [];
1032 }
1033 $draft_media[] = [
1034 'attachment_id' => $attachmentId,
1035 'type' => 'image',
1036 'created_at' => time()
1037 ];
1038 update_user_meta( $user_id, 'mwai_draft_media', $draft_media );
1039
1040 return $this->create_rest_response( [ 'success' => true, 'attachmentId' => $attachmentId ], 200 );
1041 }
1042 catch ( Exception $e ) {
1043 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1044 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1045 }
1046 }
1047
1048 public function rest_helpers_generate_image_meta( $request ) {
1049 try {
1050 global $mwai;
1051 $params = $request->get_json_params();
1052 $attachment_id = isset( $params['attachmentId'] ) ? intval( $params['attachmentId'] ) : null;
1053
1054 if ( empty( $attachment_id ) ) {
1055 throw new Exception( __( 'The attachment ID is required.', 'ai-engine' ) );
1056 }
1057
1058 // Get the file path from the attachment ID
1059 $file_path = get_attached_file( $attachment_id );
1060 if ( empty( $file_path ) || !file_exists( $file_path ) ) {
1061 throw new Exception( __( 'Could not find the attachment file.', 'ai-engine' ) );
1062 }
1063
1064 $prompt = 'Describe this image and suggest a short title and description. '
1065 . 'Also suggest an SEO-friendly filename (lowercase, ASCII characters only, with hyphens instead of spaces). '
1066 . 'Return a JSON with the keys: title, description, filename.';
1067
1068 // Use file path instead of URL to avoid network issues
1069 $result = $mwai->simpleVisionQuery( $prompt, null, $file_path, [ 'scope' => 'admin-tools' ] );
1070 $result = preg_replace( '/^```json\s*/', '', $result );
1071 $result = preg_replace( '/\s*```$/', '', $result );
1072 if ( is_string( $result ) ) {
1073 $data = json_decode( $result, true );
1074 }
1075 else {
1076 $data = $result;
1077 }
1078 if ( !is_array( $data ) ) {
1079 $data = [];
1080 }
1081 $data = array_merge( [ 'title' => '', 'description' => '', 'filename' => '' ], $data );
1082 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
1083 }
1084 catch ( Exception $e ) {
1085 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1086 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1087 }
1088 }
1089
1090 public function rest_helpers_update_media_metadata( $request ) {
1091 try {
1092 $params = $request->get_json_params();
1093 $attachment_id = intval( $params['attachmentId'] );
1094 $title = sanitize_text_field( $params['title'] ?? '' );
1095 $description = sanitize_text_field( $params['description'] ?? '' );
1096 $caption = sanitize_text_field( $params['caption'] ?? '' );
1097 $alt = sanitize_text_field( $params['alt'] ?? '' );
1098 $filename = sanitize_file_name( $params['filename'] ?? '' );
1099
1100 if ( !$attachment_id ) {
1101 throw new Exception( __( 'Attachment ID is required.', 'ai-engine' ) );
1102 }
1103
1104 // Generate slug from filename (without extension)
1105 $slug = '';
1106 if ( !empty( $filename ) ) {
1107 $slug = pathinfo( $filename, PATHINFO_FILENAME );
1108 }
1109
1110 // Update post title, content (description), caption, and slug
1111 $update_data = [
1112 'ID' => $attachment_id,
1113 'post_title' => $title,
1114 'post_content' => $description,
1115 'post_excerpt' => $caption
1116 ];
1117
1118 if ( !empty( $slug ) ) {
1119 $update_data['post_name'] = $slug;
1120 }
1121
1122 wp_update_post( $update_data );
1123
1124 // Update alt text
1125 if ( !empty( $alt ) ) {
1126 update_post_meta( $attachment_id, '_wp_attachment_image_alt', $alt );
1127 }
1128
1129 // Update filename if provided
1130 $new_url = null;
1131 if ( !empty( $filename ) ) {
1132 $file_path = get_attached_file( $attachment_id );
1133 if ( $file_path ) {
1134 $path_parts = pathinfo( $file_path );
1135 $new_file_path = $path_parts['dirname'] . '/' . $filename;
1136 if ( rename( $file_path, $new_file_path ) ) {
1137 update_attached_file( $attachment_id, $new_file_path );
1138 // Build new URL from file path for custom post types
1139 $upload_dir = wp_upload_dir();
1140 $new_url = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $new_file_path );
1141 }
1142 }
1143 }
1144
1145 $response = [ 'success' => true ];
1146 if ( $new_url ) {
1147 $response['url'] = $new_url;
1148 }
1149
1150 return $this->create_rest_response( $response, 200 );
1151 }
1152 catch ( Exception $e ) {
1153 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1154 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1155 }
1156 }
1157
1158 public function rest_openai_files_get() {
1159 try {
1160 $envId = isset( $_GET['envId'] ) ? $_GET['envId'] : null;
1161 $purposeFilter = isset( $_GET['purpose'] ) ? $_GET['purpose'] : null;
1162 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1163 $files = $openai->list_files( $purposeFilter );
1164 return $this->create_rest_response( [ 'success' => true, 'files' => $files ], 200 );
1165 }
1166 catch ( Exception $e ) {
1167 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1168 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1169 }
1170 }
1171
1172 public function rest_openai_deleted_finetunes_get() {
1173 try {
1174 $envId = isset( $_GET['envId'] ) ? $_GET['envId'] : null;
1175 $legacy = isset( $_GET['legacy'] ) ? $_GET['legacy'] === 'true' : false;
1176 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1177 $finetunes = $openai->list_deleted_finetunes( $legacy );
1178 return $this->create_rest_response( [ 'success' => true, 'finetunes' => $finetunes ], 200 );
1179 }
1180 catch ( Exception $e ) {
1181 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1182 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1183 }
1184 }
1185
1186 public function rest_openai_finetunes_get() {
1187 try {
1188 $envId = isset( $_GET['envId'] ) ? $_GET['envId'] : null;
1189 $legacy = isset( $_GET['legacy'] ) ? $_GET['legacy'] === 'true' : false;
1190 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1191 $finetunes = $openai->list_finetunes( $legacy );
1192 return $this->create_rest_response( [ 'success' => true, 'finetunes' => $finetunes ], 200 );
1193 }
1194 catch ( Exception $e ) {
1195 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1196 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1197 }
1198 }
1199
1200 public function rest_openai_files_upload( $request ) {
1201 try {
1202 $params = $request->get_json_params();
1203 $envId = $params['envId'];
1204 ;
1205 $filename = sanitize_text_field( $params['filename'] );
1206 $data = $params['data'];
1207 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1208 $file = $openai->upload_file( $filename, $data );
1209 return $this->create_rest_response( [ 'success' => true, 'file' => $file ], 200 );
1210 }
1211 catch ( Exception $e ) {
1212 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1213 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1214 }
1215 }
1216
1217 public function rest_openai_files_delete( $request ) {
1218 try {
1219 $params = $request->get_json_params();
1220 $envId = $params['envId'];
1221 ;
1222 $fileId = $params['fileId'];
1223 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1224 $openai->delete_file( $fileId );
1225 return $this->create_rest_response( [ 'success' => true ], 200 );
1226 }
1227 catch ( Exception $e ) {
1228 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1229 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1230 }
1231 }
1232
1233 public function rest_openai_finetunes_cancel( $request ) {
1234 try {
1235 $params = $request->get_json_params();
1236 $envId = $params['envId'];
1237 ;
1238 $finetuneId = $params['finetuneId'];
1239 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1240 $openai->cancel_finetune( $finetuneId );
1241 return $this->create_rest_response( [ 'success' => true ], 200 );
1242 }
1243 catch ( Exception $e ) {
1244 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1245 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1246 }
1247 }
1248
1249 public function rest_openai_finetunes_delete( $request ) {
1250 try {
1251 $params = $request->get_json_params();
1252 $envId = $params['envId'];
1253 ;
1254 $modelId = $params['modelId'];
1255 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1256 $openai->delete_finetune( $modelId );
1257 return $this->create_rest_response( [ 'success' => true ], 200 );
1258 }
1259 catch ( Exception $e ) {
1260 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1261 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1262 }
1263 }
1264
1265 public function rest_openai_files_download( $request ) {
1266 try {
1267 $params = $request->get_json_params();
1268 $envId = $params['envId'];
1269 ;
1270 $fileId = $params['fileId'];
1271 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1272 $filename = $openai->download_file( $fileId );
1273 $data = file_get_contents( $filename );
1274 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
1275 }
1276 catch ( Exception $e ) {
1277 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1278 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1279 }
1280 }
1281
1282 public function rest_openai_files_finetune( $request ) {
1283 try {
1284 $params = $request->get_json_params();
1285 $envId = $params['envId'];
1286 ;
1287 $fileId = $params['fileId'];
1288 $model = $params['model'];
1289 $suffix = $params['suffix'];
1290 $hyperparams = [
1291 'nEpochs' => isset( $params['nEpochs'] ) ? $params['nEpochs'] : null,
1292 'batchSize' => isset( $params['batchSize'] ) ? $params['batchSize'] : null,
1293 ];
1294 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1295 $finetune = $openai->run_finetune( $fileId, $model, $suffix, $hyperparams );
1296 return $this->create_rest_response( [ 'success' => true, 'finetune' => $finetune ], 200 );
1297 }
1298 catch ( Exception $e ) {
1299 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1300 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1301 }
1302 }
1303
1304 public function rest_helpers_count_posts( $request ) {
1305 try {
1306 $params = $request->get_query_params();
1307 $postType = $params['postType'];
1308 $postStatus = !empty( $params['postStatus'] ) ? explode( ',', $params['postStatus'] ) : [ 'publish' ];
1309 $count = wp_count_posts( $postType );
1310 $count = array_sum( array_intersect_key( (array) $count, array_flip( $postStatus ) ) );
1311 return $this->create_rest_response( [ 'success' => true, 'count' => $count ], 200 );
1312 }
1313 catch ( Exception $e ) {
1314 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1315 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1316 }
1317 }
1318
1319 public function rest_helpers_posts_ids( $request ) {
1320 try {
1321 $params = $request->get_query_params();
1322 $postType = $params['postType'];
1323 $postStatus = !empty( $params['postStatus'] ) ? explode( ',', $params['postStatus'] ) : [ 'publish' ];
1324 $posts = get_posts( [
1325 'posts_per_page' => -1,
1326 'post_type' => $postType,
1327 'post_status' => $postStatus,
1328 'fields' => 'ids'
1329 ] );
1330 return $this->create_rest_response( [ 'success' => true, 'postIds' => $posts ], 200 );
1331 }
1332 catch ( Exception $e ) {
1333 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1334 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1335 }
1336 }
1337
1338 public function rest_helpers_post_content( $request ) {
1339 try {
1340 $params = $request->get_query_params();
1341 $offset = (int) $params['offset'];
1342 $postType = $params['postType'];
1343 $postStatus = isset( $params['postStatus'] ) ? explode( ',', $params['postStatus'] ) : [ 'publish' ];
1344 $postId = (int) $params['postId'];
1345
1346 $post = null;
1347 if ( !empty( $postId ) ) {
1348 $post = get_post( $postId );
1349 if ( $post->post_status !== 'publish' && $post->post_status !== 'future'
1350 && $post->post_status !== 'draft' && $post->post_status !== 'private' ) {
1351 $post = null;
1352 }
1353 }
1354 else {
1355 $posts = get_posts( [
1356 'posts_per_page' => 1,
1357 'post_type' => $postType,
1358 'offset' => $offset,
1359 'post_status' => $postStatus,
1360 ] );
1361 $post = count( $posts ) === 0 ? null : $posts[0];
1362 }
1363 if ( !$post ) {
1364 return $this->create_rest_response( [ 'success' => false, 'message' => 'Post not found' ], 404 );
1365 }
1366 $cleanPost = $this->core->get_post( $post );
1367 return $this->create_rest_response( [ 'success' => true, 'content' => $cleanPost['content'],
1368 'checksum' => $cleanPost['checksum'], 'language' => $cleanPost['language'], 'excerpt' => $cleanPost['excerpt'],
1369 'postId' => $cleanPost['postId'], 'title' => $cleanPost['title'], 'url' => $cleanPost['url'] ], 200 );
1370 }
1371 catch ( Exception $e ) {
1372 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1373 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1374 }
1375 }
1376
1377 // Batch check which posts have content (for Push All optimization)
1378 public function rest_helpers_check_posts_content( $request ) {
1379 try {
1380 $params = $request->get_json_params();
1381 $postIds = isset( $params['postIds'] ) ? $params['postIds'] : [];
1382
1383 if ( empty( $postIds ) || !is_array( $postIds ) ) {
1384 return $this->create_rest_response( [
1385 'success' => false,
1386 'message' => 'postIds array is required'
1387 ], 400 );
1388 }
1389
1390 $postsWithContent = [];
1391
1392 // Check each post ID for content
1393 foreach ( $postIds as $postId ) {
1394 $postId = (int) $postId;
1395 $content = $this->core->get_post_content( $postId );
1396
1397 // Only include posts that have content
1398 if ( !empty( $content ) ) {
1399 $postsWithContent[] = $postId;
1400 }
1401 }
1402
1403 return $this->create_rest_response( [
1404 'success' => true,
1405 'postsWithContent' => $postsWithContent
1406 ], 200 );
1407 }
1408 catch ( Exception $e ) {
1409 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1410 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1411 }
1412 }
1413
1414 public function rest_helpers_run_tasks( $request ) {
1415 try {
1416 // Prevent concurrent execution with a transient lock
1417 $lock_key = 'mwai_rest_run_tasks_lock';
1418 if ( get_transient( $lock_key ) ) {
1419 // Log excessive calls for debugging
1420 if ( $this->core->get_option( 'dev_mode' ) ) {
1421 error_log( '[AI Engine] WARNING: rest_helpers_run_tasks called while already running' );
1422 }
1423 return $this->create_rest_response( [
1424 'success' => false,
1425 'message' => 'Tasks are already running. Please wait.'
1426 ], 429 ); // 429 Too Many Requests
1427 }
1428
1429 // Set lock for 30 seconds
1430 set_transient( $lock_key, true, 30 );
1431
1432 // Log task execution start
1433 if ( $this->core->get_option( 'dev_mode' ) ) {
1434 error_log( '[AI Engine] rest_helpers_run_tasks triggered via REST API' );
1435 }
1436
1437 try {
1438 do_action( 'mwai_tasks_run' );
1439 delete_transient( $lock_key );
1440 return $this->create_rest_response( [ 'success' => true ], 200 );
1441 }
1442 catch ( Exception $e ) {
1443 delete_transient( $lock_key );
1444 throw $e;
1445 }
1446 }
1447 catch ( Exception $e ) {
1448 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1449 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1450 }
1451 }
1452
1453 public function rest_helpers_optimize_database( $request ) {
1454 try {
1455 global $wpdb;
1456 $results = [];
1457
1458 // Add indexes to optimize query performance
1459 $indexes = [
1460 // mwai_logs indexes
1461 [ 'table' => 'mwai_logs', 'name' => 'idx_mwai_logs_time', 'columns' => 'time' ],
1462 [ 'table' => 'mwai_logs', 'name' => 'idx_mwai_logs_userId', 'columns' => 'userId' ],
1463 [ 'table' => 'mwai_logs', 'name' => 'idx_mwai_logs_envId', 'columns' => 'envId' ],
1464 [ 'table' => 'mwai_logs', 'name' => 'idx_mwai_logs_refId', 'columns' => 'refId' ],
1465 [ 'table' => 'mwai_logs', 'name' => 'idx_mwai_logs_time_model', 'columns' => 'time, model' ],
1466
1467 // mwai_logmeta indexes
1468 [ 'table' => 'mwai_logmeta', 'name' => 'idx_mwai_logmeta_log_id', 'columns' => 'log_id' ],
1469
1470 // mwai_vectors indexes
1471 [ 'table' => 'mwai_vectors', 'name' => 'idx_mwai_vectors_envId_status_dbId', 'columns' => 'envId, status, dbId' ],
1472 [ 'table' => 'mwai_vectors', 'name' => 'idx_mwai_vectors_refId', 'columns' => 'refId' ],
1473 [ 'table' => 'mwai_vectors', 'name' => 'idx_mwai_vectors_status', 'columns' => 'status' ],
1474 [ 'table' => 'mwai_vectors', 'name' => 'idx_mwai_vectors_updated', 'columns' => 'updated' ],
1475
1476 // mwai_files indexes
1477 [ 'table' => 'mwai_files', 'name' => 'idx_mwai_files_expires', 'columns' => 'expires' ],
1478 [ 'table' => 'mwai_files', 'name' => 'idx_mwai_files_userId', 'columns' => 'userId' ],
1479 [ 'table' => 'mwai_files', 'name' => 'idx_mwai_files_purpose', 'columns' => 'purpose' ],
1480
1481 // mwai_filemeta indexes
1482 [ 'table' => 'mwai_filemeta', 'name' => 'idx_mwai_filemeta_file_id', 'columns' => 'file_id' ],
1483
1484 // mwai_chats indexes
1485 [ 'table' => 'mwai_chats', 'name' => 'idx_mwai_chats_chatId_botId', 'columns' => 'chatId, botId' ],
1486 [ 'table' => 'mwai_chats', 'name' => 'idx_mwai_chats_chatId_userId', 'columns' => 'chatId, userId' ],
1487 [ 'table' => 'mwai_chats', 'name' => 'idx_mwai_chats_updated', 'columns' => 'updated' ],
1488 ];
1489
1490 // Add indexes
1491 foreach ( $indexes as $index ) {
1492 $table = $wpdb->prefix . $index['table'];
1493 $index_name = $index['name'];
1494 $columns = $index['columns'];
1495
1496 // Check if index already exists
1497 $existing = $wpdb->get_var( $wpdb->prepare(
1498 "SELECT COUNT(*) FROM INFORMATION_SCHEMA.STATISTICS
1499 WHERE table_schema = %s AND table_name = %s AND index_name = %s",
1500 DB_NAME, $table, $index_name
1501 ) );
1502
1503 if ( !$existing ) {
1504 $wpdb->query( "ALTER TABLE `$table` ADD INDEX `$index_name` ($columns)" );
1505 $results[] = "Added index $index_name on $table";
1506 }
1507 }
1508
1509 // Clean up old logs (older than 3 months)
1510 $three_months_ago = date( 'Y-m-d H:i:s', strtotime( '-3 months' ) );
1511
1512 // Delete old logs
1513 $deleted_logs = $wpdb->query( $wpdb->prepare(
1514 "DELETE FROM {$wpdb->prefix}mwai_logs WHERE time < %s",
1515 $three_months_ago
1516 ) );
1517 $results[] = "Deleted $deleted_logs old log entries";
1518
1519 // Delete orphaned logmeta
1520 $deleted_logmeta = $wpdb->query(
1521 "DELETE lm FROM {$wpdb->prefix}mwai_logmeta lm
1522 LEFT JOIN {$wpdb->prefix}mwai_logs l ON lm.log_id = l.id
1523 WHERE l.id IS NULL"
1524 );
1525 $results[] = "Deleted $deleted_logmeta orphaned logmeta entries";
1526
1527 // Delete old chats (older than 3 months)
1528 $deleted_chats = $wpdb->query( $wpdb->prepare(
1529 "DELETE FROM {$wpdb->prefix}mwai_chats WHERE updated < %s",
1530 $three_months_ago
1531 ) );
1532 $results[] = "Deleted $deleted_chats old chat discussions";
1533
1534 // Optimize tables
1535 $tables = [ 'mwai_logs', 'mwai_logmeta', 'mwai_vectors', 'mwai_files', 'mwai_filemeta', 'mwai_chats' ];
1536 foreach ( $tables as $table ) {
1537 $wpdb->query( "OPTIMIZE TABLE {$wpdb->prefix}$table" );
1538 }
1539 $results[] = "Optimized all AI Engine tables";
1540
1541 $message = implode( "\n", $results );
1542 return $this->create_rest_response( [ 'success' => true, 'message' => $message ], 200 );
1543 }
1544 catch ( Exception $e ) {
1545 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1546 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1547 }
1548 }
1549
1550 public function rest_system_templates_get( $request ) {
1551 try {
1552 $params = $request->get_query_params();
1553 $category = $params['category'];
1554 $templates = [];
1555 $templates_option = get_option( 'mwai_templates', [] );
1556 if ( !is_array( $templates_option ) ) {
1557 update_option( 'mwai_templates', [] );
1558 }
1559 $categories = array_column( $templates_option, 'category' );
1560 $index = array_search( $category, $categories );
1561 $templates = [];
1562 if ( $index !== false ) {
1563 $templates = $templates_option[$index]['templates'];
1564 }
1565 return $this->create_rest_response( [ 'success' => true, 'templates' => $templates ], 200 );
1566 }
1567 catch ( Exception $e ) {
1568 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1569 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1570 }
1571 }
1572
1573 public function rest_system_templates_save( $request ) {
1574 try {
1575 $params = $request->get_json_params();
1576 $category = $params['category'];
1577 $templates = $params['templates'];
1578 $templates_option = get_option( 'mwai_templates', [] );
1579 $categories = array_column( $templates_option, 'category' );
1580 $index = array_search( $category, $categories );
1581 if ( $index !== false && $index >= 0 ) {
1582 $templates_option[$index]['templates'] = $templates;
1583 }
1584 else {
1585 $group = [ 'category' => $category, 'templates' => $templates ];
1586 $templates_option[] = $group;
1587 }
1588
1589 update_option( 'mwai_templates', $templates_option );
1590 return $this->create_rest_response( [ 'success' => true ], 200 );
1591 }
1592 catch ( Exception $e ) {
1593 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1594 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1595 }
1596 }
1597
1598 public function rest_system_logs_list( $request ) {
1599 try {
1600 $params = $request->get_json_params();
1601 $offset = $params['offset'];
1602 $limit = $params['limit'];
1603 $filters = $params['filters'];
1604 $sort = isset( $params['sort'] ) ? $params['sort'] : null;
1605 $logs = apply_filters( 'mwai_stats_logs_list', [], $offset, $limit, $filters, $sort );
1606 return $this->create_rest_response( [ 'success' => true, 'total' => $logs['total'], 'logs' => $logs['rows'] ], 200 );
1607 }
1608 catch ( Exception $e ) {
1609 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1610 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1611 }
1612 }
1613
1614 public function rest_system_logs_delete( $request ) {
1615 try {
1616 $params = $request->get_json_params();
1617 $logIds = $params['logIds'];
1618 $success = apply_filters( 'mwai_stats_logs_delete', true, $logIds );
1619 return $this->create_rest_response( [ 'success' => $success ], 200 );
1620 }
1621 catch ( Exception $e ) {
1622 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1623 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1624 }
1625 }
1626
1627 public function rest_system_logs_meta_get( $request ) {
1628 try {
1629 $params = $request->get_json_params();
1630 $logId = $params['logId'];
1631 $metaKeys = $params['metaKeys'];
1632 $data = apply_filters( 'mwai_stats_logs_meta', [], $logId, $metaKeys );
1633 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
1634 }
1635 catch ( Exception $e ) {
1636 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1637 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1638 }
1639 }
1640
1641 public function rest_system_logs_activity( $request ) {
1642 try {
1643 $params = $request->get_json_params();
1644 $hours = isset( $params['hours'] ) ? intval( $params['hours'] ) : 24;
1645 $data = apply_filters( 'mwai_stats_logs_activity', [], $hours );
1646 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
1647 }
1648 catch ( Exception $e ) {
1649 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1650 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1651 }
1652 }
1653
1654 public function rest_system_logs_activity_daily( $request ) {
1655 try {
1656 $params = $request->get_json_params();
1657 $days = isset( $params['days'] ) ? intval( $params['days'] ) : 31;
1658 $byModel = isset( $params['byModel'] ) ? (bool) $params['byModel'] : false;
1659
1660 if ( $byModel ) {
1661 $data = apply_filters( 'mwai_stats_logs_activity_daily_by_model', [], $days );
1662 } else {
1663 $data = apply_filters( 'mwai_stats_logs_activity_daily', [], $days );
1664 }
1665
1666 return $this->create_rest_response( [ 'success' => true, 'data' => $data ], 200 );
1667 }
1668 catch ( Exception $e ) {
1669 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1670 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1671 }
1672 }
1673
1674 public function rest_ai_moderate( $request ) {
1675 try {
1676 $params = $request->get_json_params();
1677 $envId = $params['envId'];
1678 $text = $params['text'];
1679 if ( !$text ) {
1680 return $this->create_rest_response( [ 'success' => false, 'message' => 'Text not found.' ], 404 );
1681 }
1682 $openai = Meow_MWAI_Engines_Factory::get_openai( $this->core, $envId );
1683 $results = $openai->moderate( $text );
1684 return $this->create_rest_response( [ 'success' => true, 'results' => $results ], 200 );
1685 }
1686 catch ( Exception $e ) {
1687 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1688 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1689 }
1690 }
1691
1692 public function rest_ai_transcribe_audio( $request ) {
1693 try {
1694 global $mwai;
1695 $params = $request->get_json_params();
1696 $url = !empty( $params['url'] ) ? $params['url'] : null;
1697 $mediaId = isset( $params['mediaId'] ) ? intval( $params['mediaId'] ) : 0;
1698 $path = !empty( $params['path'] ) ? $params['path'] : null;
1699
1700 // If mediaId is provided, get the file path
1701 if ( !$path && $mediaId > 0 ) {
1702 $path = get_attached_file( $mediaId );
1703 if ( empty( $path ) ) {
1704 throw new Exception( __( 'The media file cannot be found.', 'ai-engine' ) );
1705 }
1706 }
1707
1708 // Set the scope for admin tools
1709 if ( !isset( $params['scope'] ) ) {
1710 $params['scope'] = 'admin-tools';
1711 }
1712
1713 $result = $mwai->simpleTranscribeAudio( $url, $path, $params );
1714 return $this->create_rest_response( [ 'success' => true, 'data' => $result ], 200 );
1715 }
1716 catch ( Exception $e ) {
1717 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1718 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1719 }
1720 }
1721
1722 public function rest_ai_transcribe_image( $request ) {
1723 try {
1724 global $mwai;
1725 $params = $request->get_json_params();
1726 $message = $this->retrieve_message( $params );
1727 $url = !empty( $params['url'] ) ? $params['url'] : null;
1728 // This could lead to a security issue, so let's avoid using path directly.
1729 //$path = !empty( $params['path'] ) ? $params['path'] : null;
1730 $result = $mwai->simpleVisionQuery( $message, $url );
1731 return $this->create_rest_response( [ 'success' => true, 'data' => $result ], 200 );
1732 }
1733 catch ( Exception $e ) {
1734 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1735 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1736 }
1737 }
1738
1739 public function rest_ai_json( $request ) {
1740 try {
1741 global $mwai;
1742 $params = $request->get_json_params();
1743 $message = $this->retrieve_message( $params );
1744 $result = $mwai->simpleJsonQuery( $message );
1745 return $this->create_rest_response( [ 'success' => true, 'data' => $result ], 200 );
1746 }
1747 catch ( Exception $e ) {
1748 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1749 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1750 }
1751 }
1752
1753 public function rest_mcp_functions( $request ) {
1754 try {
1755 // Get all registered MCP tools
1756 $tools = apply_filters( 'mwai_mcp_tools', [] );
1757
1758 // Format the response
1759 $response = [
1760 'success' => true,
1761 'count' => count( $tools ),
1762 'functions' => $tools
1763 ];
1764
1765 return $this->create_rest_response( $response, 200 );
1766 }
1767 catch ( Exception $e ) {
1768 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1769 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1770 }
1771 }
1772
1773 public function rest_helpers_post_types() {
1774 try {
1775 $postTypes = $this->core->get_post_types();
1776 return $this->create_rest_response( [ 'success' => true, 'postTypes' => $postTypes ], 200 );
1777 }
1778 catch ( Exception $e ) {
1779 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1780 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1781 }
1782 }
1783
1784 public function rest_settings_themes( $request ) {
1785 try {
1786 $method = $request->get_method();
1787 if ( $method === 'GET' ) {
1788 $themes = $this->core->get_themes();
1789 return $this->create_rest_response( [ 'success' => true, 'themes' => $themes ], 200 );
1790 }
1791 else if ( $method === 'POST' ) {
1792 $params = $request->get_json_params();
1793 $themes = $params['themes'];
1794 $themes = $this->core->update_themes( $themes );
1795 return $this->create_rest_response( [ 'success' => true, 'themes' => $themes ], 200 );
1796 }
1797 }
1798 catch ( Exception $e ) {
1799 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1800 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1801 }
1802 }
1803
1804 public function rest_settings_chatbots( $request ) {
1805 try {
1806 $method = $request->get_method();
1807 if ( $method === 'GET' ) {
1808 $chatbots = $this->core->get_chatbots();
1809 return $this->create_rest_response( [ 'success' => true, 'chatbots' => $chatbots ], 200 );
1810 }
1811 else if ( $method === 'POST' ) {
1812 $params = $request->get_json_params();
1813 $chatbots = $params['chatbots'];
1814 $chatbots = $this->core->update_chatbots( $chatbots );
1815 return $this->create_rest_response( [ 'success' => true, 'chatbots' => $chatbots ], 200 );
1816 }
1817 return $this->create_rest_response( [ 'success' => false, 'message' => 'Method not allowed' ], 405 );
1818 }
1819 catch ( Exception $e ) {
1820 $message = apply_filters( 'mwai_ai_exception', $e->getMessage() );
1821 return $this->create_rest_response( [ 'success' => false, 'message' => $message ], 500 );
1822 }
1823 }
1824
1825 #region Logs
1826
1827 public function rest_get_logs() {
1828 $logs = Meow_MWAI_Logging::get();
1829 return $this->create_rest_response( [ 'success' => true, 'data' => $logs ], 200 );
1830 }
1831
1832 public function rest_clear_logs() {
1833 Meow_MWAI_Logging::clear();
1834 return $this->create_rest_response( [ 'success' => true ], 200 );
1835 }
1836
1837 #endregion
1838
1839 #region Forms
1840
1841 public function rest_forms_list( $request ) {
1842 try {
1843 $args = [
1844 'post_type' => 'mwai_form',
1845 'posts_per_page' => 100,
1846 'post_status' => 'any',
1847 'orderby' => 'date',
1848 'order' => 'DESC'
1849 ];
1850
1851 $posts = get_posts( $args );
1852 $forms = array_map( function( $post ) {
1853 return [
1854 'id' => $post->ID,
1855 'title' => $post->post_title,
1856 'status' => $post->post_status
1857 ];
1858 }, $posts );
1859
1860 return $this->create_rest_response( [ 'success' => true, 'forms' => $forms ], 200 );
1861 }
1862 catch ( Exception $e ) {
1863 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
1864 }
1865 }
1866
1867 public function rest_forms_get( $request ) {
1868 try {
1869 $id = intval( $request->get_param( 'id' ) );
1870 if ( !$id ) {
1871 return $this->create_rest_response( [ 'success' => false, 'message' => 'Invalid form ID' ], 400 );
1872 }
1873
1874 $post = get_post( $id );
1875 if ( !$post || $post->post_type !== 'mwai_form' ) {
1876 return $this->create_rest_response( [ 'success' => false, 'message' => 'Form not found' ], 404 );
1877 }
1878
1879 $form = [
1880 'id' => $post->ID,
1881 'title' => [
1882 'raw' => $post->post_title,
1883 'rendered' => $post->post_title
1884 ],
1885 'content' => [
1886 'raw' => $post->post_content,
1887 'rendered' => $post->post_content
1888 ],
1889 'status' => $post->post_status
1890 ];
1891
1892 return $this->create_rest_response( [ 'success' => true, 'form' => $form ], 200 );
1893 }
1894 catch ( Exception $e ) {
1895 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
1896 }
1897 }
1898
1899 public function rest_forms_create( $request ) {
1900 try {
1901 $params = $request->get_json_params();
1902 $title = isset( $params['title'] ) ? $params['title'] : 'Untitled Form';
1903
1904 $post_data = [
1905 'post_title' => $title,
1906 'post_content' => '',
1907 'post_status' => 'draft',
1908 'post_type' => 'mwai_form'
1909 ];
1910
1911 $post_id = wp_insert_post( $post_data );
1912
1913 if ( is_wp_error( $post_id ) ) {
1914 return $this->create_rest_response( [ 'success' => false, 'message' => $post_id->get_error_message() ], 500 );
1915 }
1916
1917 $post = get_post( $post_id );
1918 $form = [
1919 'id' => $post->ID,
1920 'title' => [
1921 'raw' => $post->post_title,
1922 'rendered' => $post->post_title
1923 ],
1924 'status' => $post->post_status
1925 ];
1926
1927 return $this->create_rest_response( [ 'success' => true, 'form' => $form ], 200 );
1928 }
1929 catch ( Exception $e ) {
1930 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
1931 }
1932 }
1933
1934 public function rest_forms_update( $request ) {
1935 try {
1936 $params = $request->get_json_params();
1937 $id = isset( $params['id'] ) ? intval( $params['id'] ) : 0;
1938
1939 if ( !$id ) {
1940 return $this->create_rest_response( [ 'success' => false, 'message' => 'Invalid form ID' ], 400 );
1941 }
1942
1943 $post = get_post( $id );
1944 if ( !$post || $post->post_type !== 'mwai_form' ) {
1945 return $this->create_rest_response( [ 'success' => false, 'message' => 'Form not found' ], 404 );
1946 }
1947
1948 $post_data = [ 'ID' => $id ];
1949
1950 if ( isset( $params['title'] ) ) {
1951 $post_data['post_title'] = $params['title'];
1952 }
1953
1954 if ( isset( $params['content'] ) ) {
1955 $post_data['post_content'] = $params['content'];
1956 }
1957
1958 if ( isset( $params['status'] ) ) {
1959 $post_data['post_status'] = $params['status'];
1960 }
1961
1962 $result = wp_update_post( $post_data );
1963
1964 if ( is_wp_error( $result ) ) {
1965 return $this->create_rest_response( [ 'success' => false, 'message' => $result->get_error_message() ], 500 );
1966 }
1967
1968 $post = get_post( $id );
1969 $form = [
1970 'id' => $post->ID,
1971 'title' => [
1972 'raw' => $post->post_title,
1973 'rendered' => $post->post_title
1974 ],
1975 'content' => [
1976 'raw' => $post->post_content,
1977 'rendered' => $post->post_content
1978 ],
1979 'status' => $post->post_status
1980 ];
1981
1982 return $this->create_rest_response( [ 'success' => true, 'form' => $form ], 200 );
1983 }
1984 catch ( Exception $e ) {
1985 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
1986 }
1987 }
1988
1989 public function rest_forms_delete( $request ) {
1990 try {
1991 $params = $request->get_json_params();
1992 $id = isset( $params['id'] ) ? intval( $params['id'] ) : 0;
1993
1994 if ( !$id ) {
1995 return $this->create_rest_response( [ 'success' => false, 'message' => 'Invalid form ID' ], 400 );
1996 }
1997
1998 $post = get_post( $id );
1999 if ( !$post || $post->post_type !== 'mwai_form' ) {
2000 return $this->create_rest_response( [ 'success' => false, 'message' => 'Form not found' ], 404 );
2001 }
2002
2003 $result = wp_delete_post( $id, true );
2004
2005 if ( !$result ) {
2006 return $this->create_rest_response( [ 'success' => false, 'message' => 'Failed to delete form' ], 500 );
2007 }
2008
2009 return $this->create_rest_response( [ 'success' => true ], 200 );
2010 }
2011 catch ( Exception $e ) {
2012 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2013 }
2014 }
2015
2016 #endregion
2017
2018 #region Video Generation Helpers
2019
2020 public function rest_helpers_create_video( $request ) {
2021 try {
2022 $params = $request->get_json_params();
2023 $prompt = sanitize_text_field( $params['prompt'] );
2024 $model = sanitize_text_field( $params['model'] ?? 'sora-2' );
2025 $size = sanitize_text_field( $params['size'] ?? '720x1280' );
2026 $seconds = absint( $params['seconds'] ?? 4 );
2027 $envId = sanitize_text_field( $params['envId'] ?? '' );
2028
2029 // Check if envId is provided (defaults not supported for videos yet)
2030 if ( empty( $envId ) ) {
2031 throw new Exception( 'Please select a specific environment and model in the Video Generator. Default environments are not yet supported for video generation.' );
2032 }
2033
2034 // Get API key from environment
2035 $env = $this->core->get_ai_env( $envId );
2036 $api_key = $env['apikey'] ?? '';
2037
2038 if ( empty( $api_key ) ) {
2039 throw new Exception( 'OpenAI API key not found.' );
2040 }
2041
2042 // Prepare multipart boundary
2043 $boundary = wp_generate_password( 24, false );
2044 $body = '';
2045
2046 // Add model
2047 $body .= "--{$boundary}\r\n";
2048 $body .= "Content-Disposition: form-data; name=\"model\"\r\n\r\n";
2049 $body .= "{$model}\r\n";
2050
2051 // Add prompt
2052 $body .= "--{$boundary}\r\n";
2053 $body .= "Content-Disposition: form-data; name=\"prompt\"\r\n\r\n";
2054 $body .= "{$prompt}\r\n";
2055
2056 // Add size
2057 $body .= "--{$boundary}\r\n";
2058 $body .= "Content-Disposition: form-data; name=\"size\"\r\n\r\n";
2059 $body .= "{$size}\r\n";
2060
2061 // Add seconds
2062 $body .= "--{$boundary}\r\n";
2063 $body .= "Content-Disposition: form-data; name=\"seconds\"\r\n\r\n";
2064 $body .= "{$seconds}\r\n";
2065
2066 $body .= "--{$boundary}--\r\n";
2067
2068 // Call OpenAI API to create video
2069 $response = wp_remote_post( 'https://api.openai.com/v1/videos', [
2070 'headers' => [
2071 'Authorization' => 'Bearer ' . $api_key,
2072 'Content-Type' => 'multipart/form-data; boundary=' . $boundary
2073 ],
2074 'body' => $body,
2075 'timeout' => 30
2076 ] );
2077
2078 if ( is_wp_error( $response ) ) {
2079 throw new Exception( $response->get_error_message() );
2080 }
2081
2082 $response_body = json_decode( wp_remote_retrieve_body( $response ), true );
2083
2084 if ( isset( $response_body['error'] ) ) {
2085 throw new Exception( $response_body['error']['message'] ?? 'Unknown error' );
2086 }
2087
2088 // Record usage (price is calculated per second)
2089 $usage = $this->core->record_videos_usage( $model, $size, $seconds );
2090
2091 // Log to Query Logs (Statistics)
2092 try {
2093 if ( class_exists( 'MeowPro_MWAI_Stats' ) && class_exists( 'MeowPro_MWAI_Statistics' ) ) {
2094 $statsObject = new MeowPro_MWAI_Stats();
2095 $statsObject->session = $params['session'] ?? null;
2096 $statsObject->scope = 'admin-tools';
2097 $statsObject->feature = 'video-generator';
2098 $statsObject->model = $model;
2099 $statsObject->envId = $envId;
2100 $statsObject->units = $seconds;
2101 $statsObject->type = 'seconds';
2102 $statsObject->price = $usage['price'] ?? 0;
2103 $statsObject->accuracy = $usage['accuracy'] ?? 'full';
2104
2105 $statistics = new MeowPro_MWAI_Statistics();
2106 $statistics->commit_stats( $statsObject );
2107 }
2108 }
2109 catch ( Exception $statsError ) {
2110 // Log the error but don't fail the video creation
2111 error_log( '[AI Engine Video] Failed to log statistics: ' . $statsError->getMessage() );
2112 }
2113
2114 // Store metadata for later retrieval when video completes
2115 if ( isset( $response_body['id'] ) ) {
2116 set_transient( 'mwai_video_metadata_' . $response_body['id'], [
2117 'model' => $model,
2118 'env_id' => $envId,
2119 'created_at' => time()
2120 ], 7 * DAY_IN_SECONDS );
2121 }
2122
2123 return $this->create_rest_response( [ 'success' => true, 'video' => $response_body, 'usage' => $usage ], 200 );
2124 }
2125 catch ( Exception $e ) {
2126 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2127 }
2128 }
2129
2130 public function rest_helpers_video_status( $request ) {
2131 try {
2132 $params = $request->get_json_params();
2133 $video_ids = $params['videoIds'] ?? [];
2134 $envId = sanitize_text_field( $params['envId'] ?? '' );
2135
2136 if ( empty( $video_ids ) ) {
2137 return $this->create_rest_response( [ 'success' => true, 'videos' => [] ], 200 );
2138 }
2139
2140 // Get API key from environment
2141 $env = $this->core->get_ai_env( $envId );
2142 $api_key = $env['apikey'] ?? '';
2143
2144 if ( empty( $api_key ) ) {
2145 throw new Exception( 'OpenAI API key not found.' );
2146 }
2147
2148 $videos = [];
2149 foreach ( $video_ids as $video_id ) {
2150 $response = wp_remote_get( 'https://api.openai.com/v1/videos/' . $video_id, [
2151 'headers' => [
2152 'Authorization' => 'Bearer ' . $api_key
2153 ],
2154 'timeout' => 15
2155 ] );
2156
2157 if ( !is_wp_error( $response ) ) {
2158 $body = json_decode( wp_remote_retrieve_body( $response ), true );
2159 if ( !isset( $body['error'] ) ) {
2160 // If video is completed and we haven't saved it yet, download and save to media library
2161 if ( $body['status'] === 'completed' && empty( get_transient( 'mwai_video_saved_' . $video_id ) ) ) {
2162 // Retrieve metadata that was stored when video was created
2163 $metadata = get_transient( 'mwai_video_metadata_' . $video_id );
2164 $ai_metadata = [];
2165 if ( $metadata ) {
2166 $ai_metadata = [
2167 'model' => $metadata['model'] ?? null,
2168 'env_id' => $metadata['env_id'] ?? null,
2169 'latency' => isset( $metadata['created_at'] ) ? ( time() - $metadata['created_at'] ) : null
2170 ];
2171 }
2172
2173 $attachment_id = $this->download_and_save_video( $video_id, $api_key, '', '', $ai_metadata );
2174 if ( $attachment_id ) {
2175 $body['attachment_id'] = $attachment_id;
2176 // Build URL from file path for custom post types
2177 $file_path = get_attached_file( $attachment_id );
2178 $upload_dir = wp_upload_dir();
2179 $body['url'] = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $file_path );
2180 // Mark as saved so we don't download again
2181 set_transient( 'mwai_video_saved_' . $video_id, $attachment_id, DAY_IN_SECONDS );
2182 // Clean up metadata transient
2183 delete_transient( 'mwai_video_metadata_' . $video_id );
2184 }
2185 }
2186 // Check if we already have this video saved
2187 else if ( $body['status'] === 'completed' ) {
2188 $attachment_id = get_transient( 'mwai_video_saved_' . $video_id );
2189 if ( $attachment_id ) {
2190 $body['attachment_id'] = $attachment_id;
2191 // Build URL from file path for custom post types
2192 $file_path = get_attached_file( $attachment_id );
2193 $upload_dir = wp_upload_dir();
2194 $body['url'] = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $file_path );
2195 }
2196 }
2197 $videos[] = $body;
2198 }
2199 else {
2200 // Include error information in the response
2201 error_log( 'AI Engine: Video generation failed for ID ' . $video_id . ': ' . json_encode( $body['error'] ) );
2202 $videos[] = [
2203 'id' => $video_id,
2204 'status' => 'failed',
2205 'error' => $body['error']
2206 ];
2207 }
2208 }
2209 else {
2210 // WP HTTP error
2211 error_log( 'AI Engine: Failed to check video status for ID ' . $video_id . ': ' . $response->get_error_message() );
2212 $videos[] = [
2213 'id' => $video_id,
2214 'status' => 'failed',
2215 'error' => [ 'message' => $response->get_error_message() ]
2216 ];
2217 }
2218 }
2219
2220 return $this->create_rest_response( [ 'success' => true, 'videos' => $videos ], 200 );
2221 }
2222 catch ( Exception $e ) {
2223 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2224 }
2225 }
2226
2227 public function rest_helpers_download_video( $request ) {
2228 try {
2229 $params = $request->get_json_params();
2230 $video_id = sanitize_text_field( $params['videoId'] );
2231 $envId = sanitize_text_field( $params['envId'] ?? '' );
2232
2233 // Get API key from environment
2234 $env = $this->core->get_ai_env( $envId );
2235 $api_key = $env['apikey'] ?? '';
2236
2237 if ( empty( $api_key ) ) {
2238 throw new Exception( 'OpenAI API key not found.' );
2239 }
2240
2241 $temp_file = wp_tempnam( $video_id . '.mp4' );
2242
2243 $response = wp_remote_get( 'https://api.openai.com/v1/videos/' . $video_id . '/content', [
2244 'headers' => [
2245 'Authorization' => 'Bearer ' . $api_key
2246 ],
2247 'timeout' => 120,
2248 'stream' => true,
2249 'filename' => $temp_file
2250 ] );
2251
2252 if ( is_wp_error( $response ) ) {
2253 throw new Exception( $response->get_error_message() );
2254 }
2255
2256 $file_data = file_get_contents( $temp_file );
2257 $base64 = base64_encode( $file_data );
2258
2259 unlink( $temp_file );
2260
2261 return $this->create_rest_response( [
2262 'success' => true,
2263 'data' => $base64,
2264 'mimeType' => 'video/mp4'
2265 ], 200 );
2266 }
2267 catch ( Exception $e ) {
2268 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2269 }
2270 }
2271
2272 public function rest_helpers_delete_video( $request ) {
2273 try {
2274 $params = $request->get_json_params();
2275 $video_id = sanitize_text_field( $params['videoId'] );
2276 $envId = sanitize_text_field( $params['envId'] ?? '' );
2277
2278 // Get API key from environment
2279 $env = $this->core->get_ai_env( $envId );
2280 $api_key = $env['apikey'] ?? '';
2281
2282 if ( empty( $api_key ) ) {
2283 throw new Exception( 'OpenAI API key not found.' );
2284 }
2285
2286 $response = wp_remote_request( 'https://api.openai.com/v1/videos/' . $video_id, [
2287 'method' => 'DELETE',
2288 'headers' => [
2289 'Authorization' => 'Bearer ' . $api_key
2290 ],
2291 'timeout' => 15
2292 ] );
2293
2294 if ( is_wp_error( $response ) ) {
2295 throw new Exception( $response->get_error_message() );
2296 }
2297
2298 return $this->create_rest_response( [ 'success' => true ], 200 );
2299 }
2300 catch ( Exception $e ) {
2301 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2302 }
2303 }
2304
2305 private function download_and_save_video( $video_id, $api_key, $title = '', $description = '', $ai_metadata = [] ) {
2306 try {
2307 // Download video content
2308 $response = wp_remote_get( 'https://api.openai.com/v1/videos/' . $video_id . '/content', [
2309 'headers' => [
2310 'Authorization' => 'Bearer ' . $api_key
2311 ],
2312 'timeout' => 120
2313 ] );
2314
2315 if ( is_wp_error( $response ) ) {
2316 error_log( 'Error downloading video: ' . $response->get_error_message() );
2317 return false;
2318 }
2319
2320 $video_data = wp_remote_retrieve_body( $response );
2321 if ( empty( $video_data ) ) {
2322 error_log( 'Empty video data received' );
2323 return false;
2324 }
2325
2326 // Generate filename
2327 $filename = $video_id . '.mp4';
2328 $upload_dir = wp_upload_dir();
2329 $file_path = $upload_dir['path'] . '/' . $filename;
2330
2331 // Save to file
2332 file_put_contents( $file_path, $video_data );
2333
2334 // Prepare attachment data - use mwai_video post type (draft video)
2335 $attachment = [
2336 'post_mime_type' => 'video/mp4',
2337 'post_title' => !empty( $title ) ? $title : 'AI Generated Video',
2338 'post_content' => $description,
2339 'post_status' => 'inherit',
2340 'post_type' => 'mwai_video'
2341 ];
2342
2343 // Use wp_insert_post instead of wp_insert_attachment to allow custom post types
2344 $attachment_id = wp_insert_post( $attachment );
2345
2346 // Set the attached file manually since we're not using wp_insert_attachment
2347 update_attached_file( $attachment_id, $file_path );
2348
2349 if ( is_wp_error( $attachment_id ) ) {
2350 error_log( 'Error creating attachment: ' . $attachment_id->get_error_message() );
2351 return false;
2352 }
2353
2354 // Generate attachment metadata
2355 require_once ABSPATH . 'wp-admin/includes/image.php';
2356 $attach_data = wp_generate_attachment_metadata( $attachment_id, $file_path );
2357 wp_update_attachment_metadata( $attachment_id, $attach_data );
2358
2359 // Store AI-related metadata
2360 if ( !empty( $ai_metadata['model'] ) ) {
2361 update_post_meta( $attachment_id, 'mwai_model', sanitize_text_field( $ai_metadata['model'] ) );
2362 }
2363 if ( !empty( $ai_metadata['latency'] ) ) {
2364 update_post_meta( $attachment_id, 'mwai_latency', floatval( $ai_metadata['latency'] ) );
2365 }
2366 if ( !empty( $ai_metadata['env_id'] ) ) {
2367 update_post_meta( $attachment_id, 'mwai_env_id', sanitize_text_field( $ai_metadata['env_id'] ) );
2368 }
2369
2370 // Add to user's draft media
2371 $user_id = get_current_user_id();
2372 $draft_media = get_user_meta( $user_id, 'mwai_draft_media', true );
2373 if ( !is_array( $draft_media ) ) {
2374 $draft_media = [];
2375 }
2376 $draft_media[] = [
2377 'attachment_id' => $attachment_id,
2378 'type' => 'video',
2379 'openai_id' => $video_id,
2380 'created_at' => time()
2381 ];
2382 update_user_meta( $user_id, 'mwai_draft_media', $draft_media );
2383
2384 return $attachment_id;
2385 }
2386 catch ( Exception $e ) {
2387 error_log( 'Exception in download_and_save_video: ' . $e->getMessage() );
2388 return false;
2389 }
2390 }
2391
2392 public function rest_helpers_save_video_to_library( $request ) {
2393 try {
2394 $params = $request->get_json_params();
2395 $video_id = sanitize_text_field( $params['videoId'] );
2396 $title = sanitize_text_field( $params['title'] );
2397 $description = sanitize_text_field( $params['description'] );
2398 $filename = sanitize_file_name( $params['filename'] );
2399 $envId = sanitize_text_field( $params['envId'] ?? '' );
2400
2401 // Ensure filename has .mp4 extension
2402 if ( !preg_match( '/\.mp4$/i', $filename ) ) {
2403 $filename .= '.mp4';
2404 }
2405
2406 // Get API key from environment
2407 $env = $this->core->get_ai_env( $envId );
2408 $api_key = $env['apikey'] ?? '';
2409
2410 if ( empty( $api_key ) ) {
2411 throw new Exception( 'OpenAI API key not found.' );
2412 }
2413
2414 // Download video content
2415 $response = wp_remote_get( 'https://api.openai.com/v1/videos/' . $video_id . '/content', [
2416 'headers' => [
2417 'Authorization' => 'Bearer ' . $api_key
2418 ],
2419 'timeout' => 120
2420 ] );
2421
2422 if ( is_wp_error( $response ) ) {
2423 throw new Exception( $response->get_error_message() );
2424 }
2425
2426 $video_data = wp_remote_retrieve_body( $response );
2427
2428 // Upload to WordPress media library
2429 $upload_dir = wp_upload_dir();
2430 $file_path = $upload_dir['path'] . '/' . $filename;
2431
2432 file_put_contents( $file_path, $video_data );
2433
2434 $attachment = [
2435 'post_mime_type' => 'video/mp4',
2436 'post_title' => $title,
2437 'post_content' => $description,
2438 'post_status' => 'inherit'
2439 ];
2440
2441 $attach_id = wp_insert_attachment( $attachment, $file_path );
2442
2443 require_once( ABSPATH . 'wp-admin/includes/image.php' );
2444 $attach_data = wp_generate_attachment_metadata( $attach_id, $file_path );
2445 wp_update_attachment_metadata( $attach_id, $attach_data );
2446
2447 return $this->create_rest_response( [
2448 'success' => true,
2449 'attachmentId' => $attach_id
2450 ], 200 );
2451 }
2452 catch ( Exception $e ) {
2453 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2454 }
2455 }
2456
2457 public function rest_helpers_delete_video_from_library( $request ) {
2458 try {
2459 $params = $request->get_json_params();
2460 $attachment_id = absint( $params['attachmentId'] );
2461
2462 if ( empty( $attachment_id ) ) {
2463 throw new Exception( 'Attachment ID is required.' );
2464 }
2465
2466 $deleted = wp_delete_attachment( $attachment_id, true );
2467
2468 if ( !$deleted ) {
2469 throw new Exception( 'Failed to delete attachment.' );
2470 }
2471
2472 return $this->create_rest_response( [ 'success' => true ], 200 );
2473 }
2474 catch ( Exception $e ) {
2475 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2476 }
2477 }
2478
2479 public function rest_helpers_list_draft_media( $request ) {
2480 try {
2481 $type = $request->get_param( 'type' ); // 'image', 'video', or null for all
2482 $user_id = get_current_user_id();
2483 $draft_media = get_user_meta( $user_id, 'mwai_draft_media', true );
2484
2485 if ( !is_array( $draft_media ) ) {
2486 return $this->create_rest_response( [ 'success' => true, 'media' => [] ], 200 );
2487 }
2488
2489 $media_items = [];
2490 foreach ( $draft_media as $item ) {
2491 // Filter by type if specified
2492 if ( $type && $item['type'] !== $type ) {
2493 continue;
2494 }
2495
2496 $attachment_id = $item['attachment_id'];
2497 $attachment = get_post( $attachment_id );
2498
2499 if ( $attachment ) {
2500 // For custom post types (mwai_image, mwai_video), build URL from file path
2501 $file_path = get_attached_file( $attachment_id );
2502 $upload_dir = wp_upload_dir();
2503 $url = str_replace( $upload_dir['basedir'], $upload_dir['baseurl'], $file_path );
2504
2505 $model = get_post_meta( $attachment_id, 'mwai_model', true );
2506 $generation_time = get_post_meta( $attachment_id, 'mwai_latency', true );
2507 $env_id = get_post_meta( $attachment_id, 'mwai_env_id', true );
2508
2509 // Debug logging
2510 if ( $this->core->get_option( 'queries_debug_mode' ) ) {
2511 error_log( '[AI Engine] list_draft_media - attachment_id: ' . $attachment_id . ' model: ' . var_export( $model, true ) . ' generation_time: ' . var_export( $generation_time, true ) . ' env_id: ' . var_export( $env_id, true ) );
2512 }
2513
2514 $media_items[] = [
2515 'attachment_id' => $attachment_id,
2516 'type' => $item['type'],
2517 'openai_id' => $item['openai_id'] ?? null,
2518 'url' => $url,
2519 'title' => $attachment->post_title,
2520 'description' => $attachment->post_content,
2521 'filename' => basename( $file_path ),
2522 'created_at' => $item['created_at'],
2523 'model' => $model,
2524 'generation_time' => $generation_time,
2525 'env_id' => $env_id
2526 ];
2527 }
2528 }
2529
2530 return $this->create_rest_response( [ 'success' => true, 'media' => $media_items ], 200 );
2531 }
2532 catch ( Exception $e ) {
2533 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2534 }
2535 }
2536
2537 public function rest_helpers_approve_media( $request ) {
2538 try {
2539 $params = $request->get_json_params();
2540 $attachment_id = absint( $params['attachmentId'] );
2541 $openai_id = sanitize_text_field( $params['openaiId'] ?? '' );
2542 $envId = sanitize_text_field( $params['envId'] ?? '' );
2543
2544 if ( empty( $attachment_id ) ) {
2545 throw new Exception( 'Attachment ID is required.' );
2546 }
2547
2548 // Convert from mwai_image/mwai_video to attachment post type
2549 wp_update_post( [
2550 'ID' => $attachment_id,
2551 'post_type' => 'attachment',
2552 'post_status' => 'inherit'
2553 ] );
2554
2555 // Remove from draft media list
2556 $user_id = get_current_user_id();
2557 $draft_media = get_user_meta( $user_id, 'mwai_draft_media', true );
2558 if ( is_array( $draft_media ) ) {
2559 $draft_media = array_filter( $draft_media, function( $item ) use ( $attachment_id ) {
2560 return $item['attachment_id'] !== $attachment_id;
2561 } );
2562 update_user_meta( $user_id, 'mwai_draft_media', array_values( $draft_media ) );
2563 }
2564
2565 // Delete video from OpenAI if applicable
2566 if ( !empty( $openai_id ) ) {
2567 $env = $this->core->get_ai_env( $envId );
2568 $api_key = $env['apikey'] ?? '';
2569
2570 if ( !empty( $api_key ) ) {
2571 wp_remote_request( 'https://api.openai.com/v1/videos/' . $openai_id, [
2572 'method' => 'DELETE',
2573 'headers' => [ 'Authorization' => 'Bearer ' . $api_key ],
2574 'timeout' => 15
2575 ] );
2576 }
2577 }
2578
2579 return $this->create_rest_response( [ 'success' => true ], 200 );
2580 }
2581 catch ( Exception $e ) {
2582 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2583 }
2584 }
2585
2586 public function rest_helpers_reject_media( $request ) {
2587 try {
2588 $params = $request->get_json_params();
2589 $attachment_id = absint( $params['attachmentId'] );
2590 $openai_id = sanitize_text_field( $params['openaiId'] ?? '' );
2591 $envId = sanitize_text_field( $params['envId'] ?? '' );
2592
2593 if ( empty( $attachment_id ) ) {
2594 throw new Exception( 'Attachment ID is required.' );
2595 }
2596
2597 // Delete attachment from WordPress
2598 wp_delete_attachment( $attachment_id, true );
2599
2600 // Remove from draft media list
2601 $user_id = get_current_user_id();
2602 $draft_media = get_user_meta( $user_id, 'mwai_draft_media', true );
2603 if ( is_array( $draft_media ) ) {
2604 $draft_media = array_filter( $draft_media, function( $item ) use ( $attachment_id ) {
2605 return $item['attachment_id'] !== $attachment_id;
2606 } );
2607 update_user_meta( $user_id, 'mwai_draft_media', array_values( $draft_media ) );
2608 }
2609
2610 // Delete video from OpenAI if applicable
2611 if ( !empty( $openai_id ) ) {
2612 $env = $this->core->get_ai_env( $envId );
2613 $api_key = $env['apikey'] ?? '';
2614
2615 if ( !empty( $api_key ) ) {
2616 wp_remote_request( 'https://api.openai.com/v1/videos/' . $openai_id, [
2617 'method' => 'DELETE',
2618 'headers' => [ 'Authorization' => 'Bearer ' . $api_key ],
2619 'timeout' => 15
2620 ] );
2621 }
2622 }
2623
2624 return $this->create_rest_response( [ 'success' => true ], 200 );
2625 }
2626 catch ( Exception $e ) {
2627 return $this->create_rest_response( [ 'success' => false, 'message' => $e->getMessage() ], 500 );
2628 }
2629 }
2630
2631 #endregion
2632 }
2633