PluginProbe
Elementor Website Builder – more than just a page builder / 3.17.1
Elementor Website Builder – more than just a page builder v3.17.1
4.3.0-beta2 4.3.0-beta1 4.2.4 4.2.3 4.2.2 4.2.1 4.2.0 4.1.5 4.2.0-beta2 4.2.0-dev2 4.2.0-beta1 4.1.4 4.1.3 4.1.2 4.1.1 4.1.0 4.1.0-beta3 4.1.0-dev3 4.0.9 4.1.0-beta2 4.1.0-dev2 4.0.8 4.1.0-beta1 4.1.0-dev1 4.0.7 All 451 releases
elementor / modules / ai / module.php

module.php in Elementor Website Builder – more than just a page builder 3.17.1, at modules/ai/module.php

925 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 namespace Elementor\Modules\Ai;
3
4 use Elementor\Core\Base\Module as BaseModule;
5 use Elementor\Core\Common\Modules\Connect\Module as ConnectModule;
6 use Elementor\Core\Experiments\Manager as Experiments_Manager;
7 use Elementor\Core\Utils\Collection;
8 use Elementor\Modules\Ai\Connect\Ai;
9 use Elementor\Plugin;
10 use Elementor\User;
11
12 if ( ! defined( 'ABSPATH' ) ) {
13 exit; // Exit if accessed directly.
14 }
15
16 class Module extends BaseModule {
17 const HISTORY_TYPE_ALL = 'all';
18 const HISTORY_TYPE_TEXT = 'text';
19 const HISTORY_TYPE_CODE = 'code';
20 const HISTORY_TYPE_IMAGE = 'images';
21 const HISTORY_TYPE_BLOCK = 'blocks';
22 const VALID_HISTORY_TYPES = [
23 self::HISTORY_TYPE_ALL,
24 self::HISTORY_TYPE_TEXT,
25 self::HISTORY_TYPE_CODE,
26 self::HISTORY_TYPE_IMAGE,
27 self::HISTORY_TYPE_BLOCK,
28 ];
29
30 const LAYOUT_EXPERIMENT = 'ai-layout';
31
32 public function get_name() {
33 return 'ai';
34 }
35
36 public function __construct() {
37 parent::__construct();
38
39 $this->register_layout_experiment();
40
41 add_action( 'elementor/connect/apps/register', function ( ConnectModule $connect_module ) {
42 $connect_module->register_app( 'ai', Ai::get_class_name() );
43 } );
44
45 add_action( 'elementor/ajax/register_actions', function( $ajax ) {
46 $handlers = [
47 'ai_get_user_information' => [ $this, 'ajax_ai_get_user_information' ],
48 'ai_get_completion_text' => [ $this, 'ajax_ai_get_completion_text' ],
49 'ai_get_edit_text' => [ $this, 'ajax_ai_get_edit_text' ],
50 'ai_get_custom_code' => [ $this, 'ajax_ai_get_custom_code' ],
51 'ai_get_custom_css' => [ $this, 'ajax_ai_get_custom_css' ],
52 'ai_set_get_started' => [ $this, 'ajax_ai_set_get_started' ],
53 'ai_set_status_feedback' => [ $this, 'ajax_ai_set_status_feedback' ],
54 'ai_get_image_prompt_enhancer' => [ $this, 'ajax_ai_get_image_prompt_enhancer' ],
55 'ai_get_text_to_image' => [ $this, 'ajax_ai_get_text_to_image' ],
56 'ai_get_image_to_image' => [ $this, 'ajax_ai_get_image_to_image' ],
57 'ai_get_image_to_image_mask' => [ $this, 'ajax_ai_get_image_to_image_mask' ],
58 'ai_get_image_to_image_outpainting' => [ $this, 'ajax_ai_get_image_to_image_outpainting' ],
59 'ai_get_image_to_image_upscale' => [ $this, 'ajax_ai_get_image_to_image_upscale' ],
60 'ai_get_image_to_image_remove_background' => [ $this, 'ajax_ai_get_image_to_image_remove_background' ],
61 'ai_get_image_to_image_replace_background' => [ $this, 'ajax_ai_get_image_to_image_replace_background' ],
62 'ai_upload_image' => [ $this, 'ajax_ai_upload_image' ],
63 'ai_generate_layout' => [ $this, 'ajax_ai_generate_layout' ],
64 'ai_get_layout_prompt_enhancer' => [ $this, 'ajax_ai_get_layout_prompt_enhancer' ],
65 'ai_get_history' => [ $this, 'ajax_ai_get_history' ],
66 'ai_delete_history_item' => [ $this, 'ajax_ai_delete_history_item' ],
67 'ai_toggle_favorite_history_item' => [ $this, 'ajax_ai_toggle_favorite_history_item' ],
68 ];
69
70 foreach ( $handlers as $tag => $callback ) {
71 $ajax->register_ajax_action( $tag, $callback );
72 }
73 } );
74
75 add_action( 'elementor/editor/before_enqueue_scripts', function() {
76 $this->enqueue_main_script();
77
78 if ( $this->is_layout_active() ) {
79 $this->enqueue_layout_script();
80 }
81 } );
82
83 add_action( 'elementor/editor/after_enqueue_styles', function() {
84 wp_enqueue_style(
85 'elementor-ai-editor',
86 $this->get_css_assets_url( 'modules/ai/editor' ),
87 [],
88 ELEMENTOR_VERSION
89 );
90 } );
91
92 add_action( 'elementor/preview/enqueue_styles', function() {
93 if ( $this->is_layout_active() ) {
94 wp_enqueue_style(
95 'elementor-ai-layout-preview',
96 $this->get_css_assets_url( 'modules/ai/layout-preview' ),
97 [],
98 ELEMENTOR_VERSION
99 );
100 }
101 } );
102
103 add_filter( 'elementor/document/save/data', function ( $data ) {
104 if ( $this->is_layout_active() ) {
105 return $this->remove_temporary_containers( $data );
106 }
107
108 return $data;
109 } );
110 }
111
112 private function register_layout_experiment() {
113 Plugin::$instance->experiments->add_feature( [
114 'name' => static::LAYOUT_EXPERIMENT,
115 'title' => esc_html__( 'Build with AI', 'elementor' ),
116 'default' => Experiments_Manager::STATE_INACTIVE,
117 'status' => Experiments_Manager::RELEASE_STATUS_ALPHA,
118 'hidden' => true,
119 'dependencies' => [
120 'container',
121 ],
122 ] );
123 }
124
125 private function enqueue_main_script() {
126 wp_enqueue_script(
127 'elementor-ai',
128 $this->get_js_assets_url( 'ai' ),
129 [
130 'react',
131 'react-dom',
132 'backbone-marionette',
133 'elementor-web-cli',
134 'wp-date',
135 'elementor-common',
136 'elementor-editor-modules',
137 'elementor-editor-document',
138 'elementor-v2-ui',
139 'elementor-v2-icons',
140 ],
141 ELEMENTOR_VERSION,
142 true
143 );
144
145 wp_localize_script(
146 'elementor-ai',
147 'ElementorAiConfig',
148 [
149 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
150 'connect_url' => $this->get_ai_connect_url(),
151 ]
152 );
153
154 wp_set_script_translations( 'elementor-ai', 'elementor' );
155 }
156
157 private function enqueue_layout_script() {
158 wp_enqueue_script(
159 'elementor-ai-layout',
160 $this->get_js_assets_url( 'ai-layout' ),
161 [
162 'react',
163 'react-dom',
164 'backbone-marionette',
165 'elementor-common',
166 'elementor-web-cli',
167 'elementor-editor-modules',
168 'elementor-ai',
169 'elementor-v2-ui',
170 'elementor-v2-icons',
171 ],
172 ELEMENTOR_VERSION,
173 true
174 );
175
176 wp_set_script_translations( 'elementor-ai-layout', 'elementor' );
177 }
178
179 private function is_layout_active() {
180 return Plugin::$instance->experiments->is_feature_active( self::LAYOUT_EXPERIMENT );
181 }
182
183 private function remove_temporary_containers( $data ) {
184 if ( empty( $data['elements'] ) ) {
185 return $data;
186 }
187
188 // If for some reason the document has been saved during an AI Layout session,
189 // ensure that the temporary containers are removed from the data.
190 $data['elements'] = array_filter( $data['elements'], function( $element ) {
191 $is_preview_container = strpos( $element['id'], 'e-ai-preview-container' ) === 0;
192 $is_screenshot_container = strpos( $element['id'], 'e-ai-screenshot-container' ) === 0;
193
194 return ! $is_preview_container && ! $is_screenshot_container;
195 } );
196
197 return $data;
198 }
199
200 private function get_ai_connect_url() {
201 $app = $this->get_ai_app();
202
203 return $app->get_admin_url( 'authorize', [
204 'utm_source' => 'ai-popup',
205 'utm_campaign' => 'connect-account',
206 'utm_medium' => 'wp-dash',
207 'source' => 'generic',
208 ] );
209 }
210
211 public function ajax_ai_get_user_information( $data ) {
212 $app = $this->get_ai_app();
213
214 if ( ! $app->is_connected() ) {
215 return [
216 'is_connected' => false,
217 'connect_url' => $this->get_ai_connect_url(),
218 ];
219 }
220
221 $user_usage = wp_parse_args( $app->get_usage(), [
222 'hasAiSubscription' => false,
223 'usedQuota' => 0,
224 'quota' => 100,
225 ] );
226
227 return [
228 'is_connected' => true,
229 'is_get_started' => User::get_introduction_meta( 'ai_get_started' ),
230 'usage' => $user_usage,
231 ];
232 }
233
234 private function verify_permissions( $editor_post_id ) {
235 $document = Plugin::$instance->documents->get( $editor_post_id );
236
237 if ( ! $document ) {
238 throw new \Exception( 'Document not found' );
239 }
240
241 if ( ! $document->is_built_with_elementor() || ! $document->is_editable_by_current_user() ) {
242 throw new \Exception( 'Access denied' );
243 }
244 }
245
246 public function ajax_ai_get_image_prompt_enhancer( $data ) {
247 $this->verify_permissions( $data['editor_post_id'] );
248
249 $app = $this->get_ai_app();
250
251 if ( empty( $data['prompt'] ) ) {
252 throw new \Exception( 'Missing prompt' );
253 }
254
255 if ( ! $app->is_connected() ) {
256 throw new \Exception( 'not_connected' );
257 }
258
259 $result = $app->get_image_prompt_enhanced( $data['prompt'] );
260 if ( is_wp_error( $result ) ) {
261 throw new \Exception( $result->get_error_message() );
262 }
263
264 return [
265 'text' => $result['text'],
266 'response_id' => $result['responseId'],
267 'usage' => $result['usage'],
268 ];
269 }
270
271 public function ajax_ai_get_completion_text( $data ) {
272 $this->verify_permissions( $data['editor_post_id'] );
273
274 $app = $this->get_ai_app();
275
276 if ( empty( $data['prompt'] ) ) {
277 throw new \Exception( 'Missing prompt' );
278 }
279
280 if ( ! $app->is_connected() ) {
281 throw new \Exception( 'not_connected' );
282 }
283
284 $context = $this->get_request_context( $data );
285
286 $result = $app->get_completion_text( $data['prompt'], $context );
287 if ( is_wp_error( $result ) ) {
288 throw new \Exception( $result->get_error_message() );
289 }
290
291 return [
292 'text' => $result['text'],
293 'response_id' => $result['responseId'],
294 'usage' => $result['usage'],
295 ];
296 }
297
298 private function get_ai_app() : Ai {
299 return Plugin::$instance->common->get_component( 'connect' )->get_app( 'ai' );
300 }
301
302 private function get_request_context( $data ) {
303 if ( empty( $data['context'] ) ) {
304 return [];
305 }
306
307 return $data['context'];
308 }
309
310 public function ajax_ai_get_edit_text( $data ) {
311 $this->verify_permissions( $data['editor_post_id'] );
312
313 $app = $this->get_ai_app();
314
315 if ( empty( $data['input'] ) ) {
316 throw new \Exception( 'Missing input' );
317 }
318
319 if ( empty( $data['instruction'] ) ) {
320 throw new \Exception( 'Missing instruction' );
321 }
322
323 if ( ! $app->is_connected() ) {
324 throw new \Exception( 'not_connected' );
325 }
326
327 $context = $this->get_request_context( $data );
328
329 $result = $app->get_edit_text( $data['input'], $data['instruction'], $context );
330 if ( is_wp_error( $result ) ) {
331 throw new \Exception( $result->get_error_message() );
332 }
333
334 return [
335 'text' => $result['text'],
336 'response_id' => $result['responseId'],
337 'usage' => $result['usage'],
338 ];
339 }
340
341 public function ajax_ai_get_custom_code( $data ) {
342 $app = $this->get_ai_app();
343
344 if ( empty( $data['prompt'] ) ) {
345 throw new \Exception( 'Missing prompt' );
346 }
347
348 if ( empty( $data['language'] ) ) {
349 throw new \Exception( 'Missing language' );
350 }
351
352 if ( ! $app->is_connected() ) {
353 throw new \Exception( 'not_connected' );
354 }
355
356 $context = $this->get_request_context( $data );
357
358 $result = $app->get_custom_code( $data['prompt'], $data['language'], $context );
359 if ( is_wp_error( $result ) ) {
360 throw new \Exception( $result->get_error_message() );
361 }
362
363 return [
364 'text' => $result['text'],
365 'response_id' => $result['responseId'],
366 'usage' => $result['usage'],
367 ];
368 }
369
370 public function ajax_ai_get_custom_css( $data ) {
371 $this->verify_permissions( $data['editor_post_id'] );
372
373 $app = $this->get_ai_app();
374
375 if ( empty( $data['prompt'] ) ) {
376 throw new \Exception( 'Missing prompt' );
377 }
378
379 if ( empty( $data['html_markup'] ) ) {
380 $data['html_markup'] = '';
381 }
382
383 if ( empty( $data['element_id'] ) ) {
384 throw new \Exception( 'Missing element_id' );
385 }
386
387 if ( ! $app->is_connected() ) {
388 throw new \Exception( 'not_connected' );
389 }
390
391 $context = $this->get_request_context( $data );
392
393 $result = $app->get_custom_css( $data['prompt'], $data['html_markup'], $data['element_id'], $context );
394 if ( is_wp_error( $result ) ) {
395 throw new \Exception( $result->get_error_message() );
396 }
397
398 return [
399 'text' => $result['text'],
400 'response_id' => $result['responseId'],
401 'usage' => $result['usage'],
402 ];
403 }
404
405 public function ajax_ai_set_get_started( $data ) {
406 $app = $this->get_ai_app();
407
408 User::set_introduction_viewed( [
409 'introductionKey' => 'ai_get_started',
410 ] );
411
412 return $app->set_get_started();
413 }
414
415 public function ajax_ai_set_status_feedback( $data ) {
416 if ( empty( $data['response_id'] ) ) {
417 throw new \Exception( 'Missing response_id' );
418 }
419
420 $app = $this->get_ai_app();
421
422 if ( ! $app->is_connected() ) {
423 throw new \Exception( 'not_connected' );
424 }
425
426 $app->set_status_feedback( $data['response_id'] );
427
428 return [];
429 }
430
431 public function ajax_ai_get_text_to_image( $data ) {
432 $this->verify_permissions( $data['editor_post_id'] );
433
434 if ( empty( $data['prompt'] ) ) {
435 throw new \Exception( 'Missing prompt' );
436 }
437
438 $app = $this->get_ai_app();
439
440 if ( ! $app->is_connected() ) {
441 throw new \Exception( 'not_connected' );
442 }
443
444 $context = $this->get_request_context( $data );
445
446 $result = $app->get_text_to_image( $data['prompt'], $data['promptSettings'], $context );
447
448 if ( is_wp_error( $result ) ) {
449 throw new \Exception( $result->get_error_message() );
450 }
451
452 return [
453 'images' => $result['images'],
454 'response_id' => $result['responseId'],
455 'usage' => $result['usage'],
456 ];
457 }
458
459 public function ajax_ai_get_image_to_image( $data ) {
460 $this->verify_permissions( $data['editor_post_id'] );
461
462 $app = $this->get_ai_app();
463
464 if ( empty( $data['prompt'] ) ) {
465 throw new \Exception( 'Missing prompt' );
466 }
467
468 if ( empty( $data['image'] ) || empty( $data['image']['id'] ) ) {
469 throw new \Exception( 'Missing Image' );
470 }
471
472 if ( empty( $data['promptSettings'] ) ) {
473 throw new \Exception( 'Missing prompt settings' );
474 }
475
476 if ( ! $app->is_connected() ) {
477 throw new \Exception( 'not_connected' );
478 }
479
480 $context = $this->get_request_context( $data );
481
482 $result = $app->get_image_to_image( [
483 'prompt' => $data['prompt'],
484 'promptSettings' => $data['promptSettings'],
485 'attachment_id' => $data['image']['id'],
486 ], $context );
487
488 if ( is_wp_error( $result ) ) {
489 throw new \Exception( $result->get_error_message() );
490 }
491
492 return [
493 'images' => $result['images'],
494 'response_id' => $result['responseId'],
495 'usage' => $result['usage'],
496 ];
497 }
498
499 public function ajax_ai_get_image_to_image_upscale( $data ) {
500 $this->verify_permissions( $data['editor_post_id'] );
501
502 $app = $this->get_ai_app();
503
504 if ( empty( $data['image'] ) || empty( $data['image']['id'] ) ) {
505 throw new \Exception( 'Missing Image' );
506 }
507
508 if ( empty( $data['promptSettings'] ) ) {
509 throw new \Exception( 'Missing prompt settings' );
510 }
511
512 if ( ! $app->is_connected() ) {
513 throw new \Exception( 'not_connected' );
514 }
515
516 $context = $this->get_request_context( $data );
517
518 $result = $app->get_image_to_image_upscale( [
519 'promptSettings' => $data['promptSettings'],
520 'attachment_id' => $data['image']['id'],
521 ], $context );
522
523 if ( is_wp_error( $result ) ) {
524 throw new \Exception( $result->get_error_message() );
525 }
526
527 return [
528 'images' => $result['images'],
529 'response_id' => $result['responseId'],
530 'usage' => $result['usage'],
531 ];
532 }
533
534 public function ajax_ai_get_image_to_image_replace_background( $data ) {
535 $this->verify_permissions( $data['editor_post_id'] );
536
537 $app = $this->get_ai_app();
538
539 if ( empty( $data['image'] ) || empty( $data['image']['id'] ) ) {
540 throw new \Exception( 'Missing Image' );
541 }
542
543 if ( empty( $data['prompt'] ) ) {
544 throw new \Exception( 'Prompt Missing' );
545 }
546
547 if ( ! $app->is_connected() ) {
548 throw new \Exception( 'not_connected' );
549 }
550
551 $context = $this->get_request_context( $data );
552
553 $result = $app->get_image_to_image_replace_background( [
554 'attachment_id' => $data['image']['id'],
555 'prompt' => $data['prompt'],
556 ], $context );
557
558 if ( is_wp_error( $result ) ) {
559 throw new \Exception( $result->get_error_message() );
560 }
561
562 return [
563 'images' => $result['images'],
564 'response_id' => $result['responseId'],
565 'usage' => $result['usage'],
566 ];
567 }
568
569 public function ajax_ai_get_image_to_image_remove_background( $data ) {
570 $this->verify_permissions( $data['editor_post_id'] );
571
572 $app = $this->get_ai_app();
573
574 if ( empty( $data['image'] ) || empty( $data['image']['id'] ) ) {
575 throw new \Exception( 'Missing Image' );
576 }
577
578 if ( ! $app->is_connected() ) {
579 throw new \Exception( 'not_connected' );
580 }
581
582 $context = $this->get_request_context( $data );
583
584 $result = $app->get_image_to_image_remove_background( [
585 'attachment_id' => $data['image']['id'],
586 ], $context );
587
588 if ( is_wp_error( $result ) ) {
589 throw new \Exception( $result->get_error_message() );
590 }
591
592 return [
593 'images' => $result['images'],
594 'response_id' => $result['responseId'],
595 'usage' => $result['usage'],
596 ];
597 }
598
599 public function ajax_ai_get_image_to_image_mask( $data ) {
600 $this->verify_permissions( $data['editor_post_id'] );
601
602 $app = $this->get_ai_app();
603
604 if ( empty( $data['prompt'] ) ) {
605 throw new \Exception( 'Missing prompt' );
606 }
607
608 if ( empty( $data['image'] ) || empty( $data['image']['id'] ) ) {
609 throw new \Exception( 'Missing Image' );
610 }
611
612 if ( empty( $data['promptSettings'] ) ) {
613 throw new \Exception( 'Missing prompt settings' );
614 }
615
616 if ( ! $app->is_connected() ) {
617 throw new \Exception( 'not_connected' );
618 }
619
620 if ( empty( $data['mask'] ) ) {
621 throw new \Exception( 'Missing Mask' );
622 }
623
624 $context = $this->get_request_context( $data );
625
626 $result = $app->get_image_to_image_mask( [
627 'prompt' => $data['prompt'],
628 'promptSettings' => $data['promptSettings'],
629 'attachment_id' => $data['image']['id'],
630 'mask' => $data['mask'],
631 ], $context );
632
633 if ( is_wp_error( $result ) ) {
634 throw new \Exception( $result->get_error_message() );
635 }
636
637 return [
638 'images' => $result['images'],
639 'response_id' => $result['responseId'],
640 'usage' => $result['usage'],
641 ];
642 }
643 public function ajax_ai_get_image_to_image_outpainting( $data ) {
644 $this->verify_permissions( $data['editor_post_id'] );
645
646 $app = $this->get_ai_app();
647
648 if ( empty( $data['prompt'] ) ) {
649 throw new \Exception( 'Missing prompt' );
650 }
651
652 if ( ! $app->is_connected() ) {
653 throw new \Exception( 'not_connected' );
654 }
655
656 if ( empty( $data['mask'] ) ) {
657 throw new \Exception( 'Missing Expended Image' );
658 }
659
660 $context = $this->get_request_context( $data );
661
662 $result = $app->get_image_to_image_out_painting( [
663 'prompt' => $data['prompt'],
664 'mask' => $data['mask'],
665 ], $context );
666
667 if ( is_wp_error( $result ) ) {
668 throw new \Exception( $result->get_error_message() );
669 }
670
671 return [
672 'images' => $result['images'],
673 'response_id' => $result['responseId'],
674 'usage' => $result['usage'],
675 ];
676 }
677
678 public function ajax_ai_upload_image( $data ) {
679 if ( empty( $data['image'] ) ) {
680 throw new \Exception( 'Missing image data' );
681 }
682
683 $image = $data['image'];
684
685 if ( empty( $image['image_url'] ) ) {
686 throw new \Exception( 'Missing image_url' );
687 }
688
689 $image_data = $this->upload_image( $image['image_url'], $data['prompt'], $data['editor_post_id'] );
690
691 if ( is_wp_error( $image_data ) ) {
692 throw new \Exception( $image_data->get_error_message() );
693 }
694
695 if ( ! empty( $image['use_gallery_image'] ) && ! empty( $image['id'] ) ) {
696 $app = $this->get_ai_app();
697 $app->set_used_gallery_image( $image['id'] );
698 }
699
700 return [
701 'image' => array_merge( $image_data, $data ),
702 ];
703 }
704
705 public function ajax_ai_generate_layout( $data ) {
706 $this->verify_permissions( $data['editor_post_id'] );
707
708 $app = $this->get_ai_app();
709
710 if ( empty( $data['prompt'] ) ) {
711 throw new \Exception( 'Missing prompt' );
712 }
713
714 if ( ! $app->is_connected() ) {
715 throw new \Exception( 'not_connected' );
716 }
717
718 $result = $app->generate_layout(
719 $data['prompt'],
720 $this->prepare_generate_layout_context(),
721 $data['variationType']
722 );
723
724 if ( is_wp_error( $result ) ) {
725 throw new \Exception( $result->get_error_message() );
726 }
727
728 $template = $result['text']['elements'][0] ?? null;
729
730 if ( empty( $template ) || ! is_array( $template ) ) {
731 throw new \Exception( 'unknown_error' );
732 }
733
734 return [
735 'all' => [],
736 'text' => $template,
737 'response_id' => $result['responseId'],
738 'usage' => $result['usage'],
739 ];
740 }
741
742 public function ajax_ai_get_layout_prompt_enhancer( $data ) {
743 $this->verify_permissions( $data['editor_post_id'] );
744
745 $app = $this->get_ai_app();
746
747 if ( empty( $data['prompt'] ) ) {
748 throw new \Exception( 'Missing prompt' );
749 }
750
751 if ( ! $app->is_connected() ) {
752 throw new \Exception( 'not_connected' );
753 }
754
755 $result = $app->get_layout_prompt_enhanced( $data['prompt'] );
756
757 if ( is_wp_error( $result ) ) {
758 throw new \Exception( $result->get_error_message() );
759 }
760
761 return [
762 'text' => $result['text'] ?? $data['prompt'],
763 'response_id' => $result['responseId'] ?? '',
764 'usage' => $result['usage'] ?? '',
765 ];
766 }
767
768 private function prepare_generate_layout_context() {
769 $kit = Plugin::$instance->kits_manager->get_active_kit();
770
771 if ( ! $kit ) {
772 return [];
773 }
774
775 $kits_data = Collection::make( $kit->get_data()['settings'] ?? [] );
776
777 $colors = $kits_data
778 ->filter( function ( $_, $key ) {
779 return in_array( $key, [ 'system_colors', 'custom_colors' ], true );
780 } )
781 ->flatten()
782 ->filter( function ( $val ) {
783 return ! empty( $val['_id'] );
784 } )
785 ->map( function ( $val ) {
786 return [
787 'id' => $val['_id'],
788 'label' => $val['title'] ?? null,
789 'value' => $val['color'] ?? null,
790 ];
791 } );
792
793 $typography = $kits_data
794 ->filter( function ( $_, $key ) {
795 return in_array( $key, [ 'system_typography', 'custom_typography' ], true );
796 } )
797 ->flatten()
798 ->filter( function ( $val ) {
799 return ! empty( $val['_id'] );
800 } )
801 ->map( function ( $val ) {
802 $font_size = null;
803
804 if ( isset(
805 $val['typography_font_size']['unit'],
806 $val['typography_font_size']['size']
807 ) ) {
808 $prop = $val['typography_font_size'];
809
810 $font_size = 'custom' === $prop['unit']
811 ? $prop['size']
812 : $prop['size'] . $prop['unit'];
813 }
814
815 return [
816 'id' => $val['_id'],
817 'label' => $val['title'] ?? null,
818 'value' => [
819 'family' => $val['typography_font_family'] ?? null,
820 'weight' => $val['typography_font_weight'] ?? null,
821 'style' => $val['typography_font_style'] ?? null,
822 'size' => $font_size,
823 ],
824 ];
825 } );
826
827 return [
828 'globals' => [
829 'colors' => $colors->all(),
830 'typography' => $typography->all(),
831 ],
832 ];
833 }
834
835 private function upload_image( $image_url, $image_title, $parent_post_id = 0 ) {
836 if ( ! current_user_can( 'upload_files' ) ) {
837 throw new \Exception( 'Not Allowed to Upload images' );
838 }
839
840 $attachment_id = media_sideload_image( $image_url, $parent_post_id, $image_title, 'id' );
841
842 if ( ! empty( $attachment_id['error'] ) ) {
843 return new \WP_Error( 'upload_error', $attachment_id['error'] );
844 }
845
846 return [
847 'id' => $attachment_id,
848 'url' => wp_get_attachment_image_url( $attachment_id, 'full' ),
849 'alt' => $image_title,
850 'source' => 'library',
851 ];
852 }
853
854 public function ajax_ai_get_history( $data ): array {
855 $type = $data['type'] ?? self::HISTORY_TYPE_ALL;
856
857 if ( ! in_array( $type, self::VALID_HISTORY_TYPES, true ) ) {
858 throw new \Exception( 'Invalid history type' );
859 }
860
861 $page = sanitize_text_field( $data['page'] ?? 1 );
862 $limit = sanitize_text_field( $data['limit'] ?? 10 );
863
864 $app = $this->get_ai_app();
865
866 if ( ! $app->is_connected() ) {
867 throw new \Exception( 'not_connected' );
868 }
869
870 $context = $this->get_request_context( $data );
871
872 $result = $app->get_history_by_type( $type, $page, $limit, $context );
873
874 if ( is_wp_error( $result ) ) {
875 throw new \Exception( $result->get_error_message() );
876 }
877
878 return $result;
879 }
880
881 public function ajax_ai_delete_history_item( $data ): array {
882 if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) {
883 throw new \Exception( 'Missing id parameter' );
884 }
885
886 $app = $this->get_ai_app();
887
888 if ( ! $app->is_connected() ) {
889 throw new \Exception( 'not_connected' );
890 }
891
892 $context = $this->get_request_context( $data );
893
894 $result = $app->delete_history_item( $data['id'], $context );
895
896 if ( is_wp_error( $result ) ) {
897 throw new \Exception( $result->get_error_message() );
898 }
899
900 return [];
901 }
902
903 public function ajax_ai_toggle_favorite_history_item( $data ): array {
904 if ( empty( $data['id'] ) || ! wp_is_uuid( $data['id'] ) ) {
905 throw new \Exception( 'Missing id parameter' );
906 }
907
908 $app = $this->get_ai_app();
909
910 if ( ! $app->is_connected() ) {
911 throw new \Exception( 'not_connected' );
912 }
913
914 $context = $this->get_request_context( $data );
915
916 $result = $app->toggle_favorite_history_item( $data['id'], $context );
917
918 if ( is_wp_error( $result ) ) {
919 throw new \Exception( $result->get_error_message() );
920 }
921
922 return [];
923 }
924 }
925