Skip to content
This repository has been archived by the owner on Feb 23, 2024. It is now read-only.

Commit

Permalink
Fix pattern route performance (#11535)
Browse files Browse the repository at this point in the history
* fix pattern route performance

* update namespace

* improve middleware

* improve ProductSchema

* improve error handling

* update identifier

* fix middleware

* update description

* use schema to return the response

* Break down the generate_content method and create the new fetch_dummy_products_to_update method for handling the fetch of dummy products to be updated.

* Ensure the Product endpoint relies on the fetch_dummy_products_to_update method for fetching dummy products to avoid code repetition and add safety checks and handle errors in case certain properties are not available.

* Add error handling for the Products endpoint.

* Remove memory limit increase and update docblocks.

* re-add set_time_limit

---------

Co-authored-by: Patricia Hillebrandt <[email protected]>
  • Loading branch information
gigitux and nefeline committed Nov 8, 2023
1 parent 461a73c commit 6372d37
Show file tree
Hide file tree
Showing 14 changed files with 746 additions and 131 deletions.
103 changes: 42 additions & 61 deletions src/Patterns/ProductUpdater.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ class ProductUpdater {
* @param array $images The array of images.
* @param string $business_description The business description.
*
* @return bool|WP_Error True if the content was generated successfully, WP_Error otherwise.
* @return array|WP_Error The generated content for the products. An error if the content could not be generated.
*/
public function generate_content( $ai_connection, $token, $images, $business_description ) {
if ( empty( $business_description ) ) {
Expand All @@ -28,16 +28,48 @@ public function generate_content( $ai_connection, $token, $images, $business_des

if ( $last_business_description === $business_description ) {
if ( is_string( $business_description ) && is_string( $last_business_description ) ) {
return true;
return array(
'product_content' => array(),
);
} else {
return new \WP_Error( 'business_description_not_found', __( 'No business description provided for generating AI content.', 'woo-gutenberg-products-block' ) );
}
}

$ai_selected_products_images = $this->get_images_information( $images );
$products_information_list = $this->assign_ai_selected_images_to_dummy_products_information_list( $ai_selected_products_images );

$response = $this->generate_product_content( $ai_connection, $token, $products_information_list );

if ( is_wp_error( $response ) ) {
$error_msg = $response;
} elseif ( empty( $response ) || ! isset( $response['completion'] ) ) {
$error_msg = new \WP_Error( 'missing_completion_key', __( 'The response from the AI service is empty or missing the completion key.', 'woo-gutenberg-products-block' ) );
}

if ( isset( $error_msg ) ) {
return $error_msg;
}

$product_content = json_decode( $response['completion'], true );

return array(
'product_content' => $product_content,
);
}

/**
* Return all dummy products that were not modified by the store owner.
*
* @return array|WP_Error An array with the dummy products that need to have their content updated by AI.
*/
public function fetch_dummy_products_to_update() {
$real_products = $this->fetch_product_ids();

if ( is_array( $real_products ) && count( $real_products ) > 0 ) {
return true;
return array(
'product_content' => array(),
);
}

$dummy_products = $this->fetch_product_ids( 'dummy' );
Expand Down Expand Up @@ -82,62 +114,7 @@ function ( $product ) {
}
}

if ( empty( $dummy_products_to_update ) ) {
return true;
}

$ai_selected_products_images = $this->get_images_information( $images );
$products_information_list = $this->assign_ai_selected_images_to_dummy_products_information_list( $ai_selected_products_images );

$response = $this->generate_product_content( $ai_connection, $token, $products_information_list );

if ( is_wp_error( $response ) ) {
$error_msg = $response;
} elseif ( empty( $response ) || ! isset( $response['completion'] ) ) {
$error_msg = new \WP_Error( 'missing_completion_key', __( 'The response from the AI service is empty or missing the completion key.', 'woo-gutenberg-products-block' ) );
}

if ( isset( $error_msg ) ) {
$this->update_dummy_products( $dummy_products_to_update, $products_information_list );

return $error_msg;
}

$product_content = json_decode( $response['completion'], true );

if ( is_null( $product_content ) ) {
$this->update_dummy_products( $dummy_products_to_update, $products_information_list );

return new \WP_Error( 'invalid_json', __( 'The response from the AI service is not a valid JSON.', 'woo-gutenberg-products-block' ) );
}

// This is required to allow the usage of the media_sideload_image function outside the context of /wp-admin/.
// See https://developer.wordpress.org/reference/functions/media_sideload_image/ for more details.
require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';

$this->update_dummy_products( $dummy_products_to_update, $product_content );

return true;
}

/**
* Update the dummy products with the content from the information list.
*
* @param array $dummy_products_to_update The dummy products to update.
* @param array $products_information_list The products information list.
*/
public function update_dummy_products( $dummy_products_to_update, $products_information_list ) {
$i = 0;
foreach ( $dummy_products_to_update as $dummy_product ) {
if ( ! isset( $products_information_list[ $i ] ) ) {
continue;
}

$this->update_product_content( $dummy_product, $products_information_list[ $i ] );
++$i;
}
return $dummy_products_to_update;
}

/**
Expand Down Expand Up @@ -273,11 +250,15 @@ public function update_product_content( $product, $ai_generated_product_content
if ( ! isset( $ai_generated_product_content['image']['src'] ) || ! isset( $ai_generated_product_content['image']['alt'] ) || ! isset( $ai_generated_product_content['title'] ) || ! isset( $ai_generated_product_content['description'] ) ) {
return;
}

require_once ABSPATH . 'wp-admin/includes/media.php';
require_once ABSPATH . 'wp-admin/includes/file.php';
require_once ABSPATH . 'wp-admin/includes/image.php';

// Since the media_sideload_image function is expensive and can take longer to complete
// the process of downloading the external image and uploading it to the media library,
// here we are increasing the time limit and the memory limit to avoid any issues.
// here we are increasing the time limit to avoid any issues.
set_time_limit( 60 );
wp_raise_memory_limit();

$product_image_id = media_sideload_image( $ai_generated_product_content['image']['src'], $product->get_id(), $ai_generated_product_content['image']['alt'], 'id' );

Expand Down
89 changes: 89 additions & 0 deletions src/StoreApi/Routes/V1/AI/BusinessDescription.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<?php

namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI;

use Automattic\WooCommerce\Blocks\Patterns\ProductUpdater;
use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute;

/**
* BusinessDescription class.
*
* @internal
*/
class BusinessDescription extends AbstractRoute {
/**
* The route identifier.
*
* @var string
*/
const IDENTIFIER = 'ai/business-description';

/**
* The schema item identifier.
*
* @var string
*/
const SCHEMA_TYPE = 'ai/business-description';

/**
* Get the path of this REST route.
*
* @return string
*/
public function get_path() {
return '/ai/business-description';
}

/**
* Get method arguments for this REST route.
*
* @return array An array of endpoints.
*/
public function get_args() {
return [
[
'methods' => \WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_response' ],
'permission_callback' => [ Middleware::class, 'is_authorized' ],
'args' => [
'business_description' => [
'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
'type' => 'string',
],
],
],
'schema' => [ $this->schema, 'get_public_item_schema' ],
'allow_batch' => [ 'v1' => true ],
];
}

/**
* Update the last business description.
*
* @param \WP_REST_Request $request Request object.
*
* @return bool|string|\WP_Error|\WP_REST_Response
*/
protected function get_route_post_response( \WP_REST_Request $request ) {

$business_description = $request->get_param( 'business_description' );

if ( ! $business_description ) {
return $this->error_to_response(
new \WP_Error(
'invalid_business_description',
__( 'Invalid business description.', 'woo-gutenberg-products-block' )
)
);
}

update_option( 'last_business_description_with_ai_content_generated', $business_description );

return rest_ensure_response(
array(
'ai_content_generated' => true,
)
);
}

}
118 changes: 118 additions & 0 deletions src/StoreApi/Routes/V1/AI/Images.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
<?php

namespace Automattic\WooCommerce\StoreApi\Routes\V1\AI;

use Automattic\WooCommerce\Blocks\AI\Connection;
use Automattic\WooCommerce\Blocks\Images\Pexels;
use Automattic\WooCommerce\StoreApi\Routes\V1\AbstractRoute;

/**
* Patterns class.
*/
class Images extends AbstractRoute {
/**
* The route identifier.
*
* @var string
*/
const IDENTIFIER = 'ai/images';

/**
* The schema item identifier.
*
* @var string
*/
const SCHEMA_TYPE = 'ai/images';

/**
* Get the path of this REST route.
*
* @return string
*/
public function get_path() {
return '/ai/images';
}

/**
* Get method arguments for this REST route.
*
* @return array An array of endpoints.
*/
public function get_args() {
return [
[
'methods' => \WP_REST_Server::CREATABLE,
'callback' => [ $this, 'get_response' ],
'permission_callback' => [ Middleware::class, 'is_authorized' ],
'args' => [
'business_description' => [
'description' => __( 'The business description for a given store.', 'woo-gutenberg-products-block' ),
'type' => 'string',
],
],
],
'schema' => [ $this->schema, 'get_public_item_schema' ],
'allow_batch' => [ 'v1' => true ],
];
}

/**
* Generate Images from Pexels
*
* @param \WP_REST_Request $request Request object.
*
* @return bool|string|\WP_Error|\WP_REST_Response
*/
protected function get_route_post_response( \WP_REST_Request $request ) {

$business_description = sanitize_text_field( wp_unslash( $request['business_description'] ) );

if ( empty( $business_description ) ) {
$business_description = get_option( 'woo_ai_describe_store_description' );
}

$last_business_description = get_option( 'last_business_description_with_ai_content_generated' );

if ( $last_business_description === $business_description ) {
return rest_ensure_response(
$this->prepare_item_for_response(
[
'ai_content_generated' => true,
'images' => array(),
],
$request
)
);
}

$ai_connection = new Connection();

$site_id = $ai_connection->get_site_id();

if ( is_wp_error( $site_id ) ) {
return $this->error_to_response( $site_id );
}

$token = $ai_connection->get_jwt_token( $site_id );

if ( is_wp_error( $token ) ) {
return $this->error_to_response( $token );
}

$images = ( new Pexels() )->get_images( $ai_connection, $token, $business_description );

if ( is_wp_error( $images ) ) {
return $this->error_to_response( $images );
}

return rest_ensure_response(
$this->prepare_item_for_response(
[
'ai_content_generated' => true,
'images' => $images,
],
$request
)
);
}
}
Loading

0 comments on commit 6372d37

Please sign in to comment.