From b948d0b38c02c4dee3e63d33e9e40a326828c357 Mon Sep 17 00:00:00 2001 From: Darin Kotter Date: Thu, 13 Feb 2025 10:25:51 -0700 Subject: [PATCH 1/3] Add OpenAI as a Provider for the Image Text Extraction Feature --- README.md | 2 +- .../Features/ImageTextExtraction.php | 11 + .../Classifai/Providers/OpenAI/ChatGPT.php | 199 +++++++++++++++--- readme.txt | 2 +- .../provider-settings/openai-chatgpt.js | 7 +- 5 files changed, 187 insertions(+), 34 deletions(-) diff --git a/README.md b/README.md index 9ee8fa844..65dc89174 100644 --- a/README.md +++ b/README.md @@ -29,7 +29,7 @@ Tap into leading cloud-based services like [OpenAI](https://openai.com/), [Micro * Find similar terms to merge together using either [OpenAI's Embedding API](https://platform.openai.com/docs/guides/embeddings) or [Microsoft Azure's OpenAI service](https://azure.microsoft.com/en-us/products/ai-services/openai-service) in combination with [ElasticPress](https://github.com/10up/ElasticPress). Note this only compares top-level terms and if you merge a term that has children, these become top-level terms as per default WordPress behavior * BETA: Recommend content based on overall site traffic via [Microsoft Azure's AI Personalizer API](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) *(note that this service has been [deprecated by Microsoft](https://learn.microsoft.com/en-us/azure/ai-services/personalizer/) and as such, will no longer work. We are looking to replace this with a new provider to maintain the same functionality (see [issue#392](https://github.com/10up/classifai/issues/392))* * Generate image alt text using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/), [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat), [xAI's Grok](https://x.ai/) or locally using [Ollama](https://ollama.com/) -* Generate image tags and extract text from images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) or locally using [Ollama](https://ollama.com/) +* Generate image tags and extract text from images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/), [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat) or locally using [Ollama](https://ollama.com/) * Smartly crop images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) * Scan PDF files for embedded text and save for use in post meta using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) * Bulk classify content with [WP-CLI](https://wp-cli.org/) diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 11cb144c0..a06c457df 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -3,6 +3,7 @@ namespace Classifai\Features; use Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Providers\Localhost\OllamaMultimodal as OllamaMM; use Classifai\Services\ImageProcessing; use WP_REST_Server; @@ -43,6 +44,7 @@ public function __construct() { // Contains just the providers this feature supports. $this->supported_providers = [ ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), OllamaMM::ID => __( 'Ollama', 'classifai' ), ]; } @@ -67,6 +69,15 @@ public function get_settings( $index = false ) { $settings = parent::get_settings( $index ); // Keep using the original prompt from the codebase to allow updates. + if ( $settings && ! empty( $settings[ ChatGPT::ID ]['prompt'] ) ) { + foreach ( $settings[ ChatGPT::ID ]['prompt'] as $key => $prompt ) { + if ( 1 === intval( $prompt['original'] ) ) { + $settings[ ChatGPT::ID ]['prompt'][ $key ]['prompt'] = $this->prompt; + break; + } + } + } + if ( $settings && ! empty( $settings[ OllamaMM::ID ]['prompt'] ) ) { foreach ( $settings[ OllamaMM::ID ]['prompt'] as $key => $prompt ) { if ( 1 === intval( $prompt['original'] ) ) { diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index dc74753bd..3973d0a39 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -7,6 +7,7 @@ use Classifai\Features\ContentResizing; use Classifai\Features\DescriptiveTextGenerator; +use Classifai\Features\ImageTextExtraction; use Classifai\Features\ExcerptGeneration; use Classifai\Features\TitleGeneration; use Classifai\Features\KeyTakeaways; @@ -138,6 +139,7 @@ public function get_default_provider_settings(): array { break; case DescriptiveTextGenerator::ID: + case ImageTextExtraction::ID: $common_settings['prompt'] = [ [ 'title' => esc_html__( 'ClassifAI default', 'classifai' ), @@ -207,6 +209,8 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '' case 'descriptive_text': $return = $this->generate_descriptive_text( $post_id, $args ); break; + case 'ocr': + return $this->ocr_processing( $post_id, $args ); case 'excerpt': $return = $this->generate_excerpt( $post_id, $args ); break; @@ -232,36 +236,10 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '' * @return string|WP_Error */ public function generate_descriptive_text( int $post_id = 0, array $args = [] ) { - // Check to be sure the attachment exists and is an image. - if ( ! wp_attachment_is_image( $post_id ) ) { - return new WP_Error( 'invalid', esc_html__( 'This attachment can\'t be processed.', 'classifai' ) ); - } + $image_url = $this->get_image_url( $post_id ); - $metadata = wp_get_attachment_metadata( $post_id ); - - if ( ! $metadata || ! is_array( $metadata ) ) { - return new WP_Error( 'invalid', esc_html__( 'No valid metadata found.', 'classifai' ) ); - } - - $image_url = get_modified_image_source_url( $post_id ); - - if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { - if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { - $image_url = get_largest_size_and_dimensions_image_url( - get_attached_file( $post_id ), - wp_get_attachment_url( $post_id ), - $metadata, - [ 512, 2000 ], - [ 512, 2000 ], - 100 * MB_IN_BYTES - ); - } else { - $image_url = wp_get_attachment_url( $post_id ); - } - } - - if ( empty( $image_url ) ) { - return new WP_Error( 'error', esc_html__( 'Valid image size not found. Make sure the image is bigger than 512x512px.', 'classifai' ) ); + if ( is_wp_error( $image_url ) ) { + return $image_url; } $feature = new DescriptiveTextGenerator(); @@ -348,6 +326,110 @@ public function generate_descriptive_text( int $post_id = 0, array $args = [] ) return $response; } + /** + * Extract text out of an image. + * + * @param int $post_id Post ID for the attachment. + * @param array $args Arguments passed in. + * @return string|WP_Error + */ + public function ocr_processing( int $post_id = 0, array $args = [] ) { + $image_url = $this->get_image_url( $post_id ); + + if ( is_wp_error( $image_url ) ) { + return $image_url; + } + + $feature = new ImageTextExtraction(); + $settings = $feature->get_settings(); + + // These checks (and the one above) happen in the REST permission_callback, + // but we run them again here in case this method is called directly. + if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ( ! $feature->is_feature_enabled() && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image Text Extraction is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); + } + + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); + + /** + * Filter the prompt we will send to ChatGPT. + * + * @since x.x.x + * @hook classifai_chatgpt_ocr_prompt + * + * @param {string} $prompt Prompt we are sending to ChatGPT. + * @param {int} $post_id ID of attachment we are describing. + * + * @return {string} Prompt. + */ + $prompt = apply_filters( 'classifai_chatgpt_ocr_prompt', get_default_prompt( $settings[ static::ID ]['prompt'] ?? [] ) ?? $feature->prompt, $post_id ); + + /** + * Filter the request body before sending to ChatGPT. + * + * @since x.x.x + * @hook classifai_chatgpt_ocr_request_body + * + * @param {array} $body Request body that will be sent to ChatGPT. + * @param {int} $post_id ID of attachment we are describing. + * + * @return {array} Request body. + */ + $body = apply_filters( + 'classifai_chatgpt_ocr_request_body', + [ + 'model' => $this->chatgpt_model, + 'messages' => [ + [ + 'role' => 'system', + 'content' => $prompt, + ], + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => $image_url, + 'detail' => 'auto', + ], + ], + ], + ], + ], + 'temperature' => 0.2, + 'max_tokens' => 300, + ], + $post_id + ); + + // Make our API request. + $response = $request->post( + $this->chatgpt_url, + [ + 'body' => wp_json_encode( $body ), + ] + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + // Extract out the text response, if it exists. + if ( ! empty( $response['choices'] ) ) { + foreach ( $response['choices'] as $choice ) { + if ( isset( $choice['message'], $choice['message']['content'] ) ) { + // ChatGPT often adds quotes to strings, so remove those as well as extra spaces. + $response = sanitize_text_field( trim( $choice['message']['content'], ' "\'' ) ); + } + } + } else { + $response = new WP_Error( 'no_choices', esc_html__( 'No choices were returned from OpenAI.', 'classifai' ) ); + } + + return $response; + } + /** * Generate an excerpt using ChatGPT. * @@ -870,6 +952,65 @@ public function get_content( int $post_id = 0, int $return_length = 0, bool $use return apply_filters( 'classifai_chatgpt_content', $content, $post_id ); } + /** + * Get the proper sized image URL for the attachment ID. + * + * @param int $attachment_id The attachment ID. + * @param array $args Arguments passed in. + * @return string|WP_Error + */ + public function get_image_url( int $attachment_id, array $args = [] ) { + // Check to be sure the attachment exists and is an image. + if ( ! wp_attachment_is_image( $attachment_id ) ) { + return new WP_Error( 'invalid', esc_html__( 'This attachment can\'t be processed.', 'classifai' ) ); + } + + $metadata = wp_get_attachment_metadata( $attachment_id ); + + if ( ! $metadata || ! is_array( $metadata ) ) { + return new WP_Error( 'invalid', esc_html__( 'No valid metadata found.', 'classifai' ) ); + } + + // Set our basic arguments. + $args = wp_parse_args( + array_filter( $args ), + [ + 'width' => [ + 'min' => 512, + 'max' => 2000, + ], + 'height' => [ + 'min' => 512, + 'max' => 2000, + ], + 'filesize' => 100 * MB_IN_BYTES, + ] + ); + + $image_url = get_modified_image_source_url( $attachment_id ); + + if ( empty( $image_url ) || ! filter_var( $image_url, FILTER_VALIDATE_URL ) ) { + if ( isset( $metadata['sizes'] ) && is_array( $metadata['sizes'] ) ) { + $image_url = get_largest_size_and_dimensions_image_url( + get_attached_file( $attachment_id ), + wp_get_attachment_url( $attachment_id ), + $metadata, + [ $args['width']['min'], $args['width']['max'] ], + [ $args['height']['min'], $args['height']['max'] ], + $args['filesize'] + ); + } else { + $image_url = wp_get_attachment_url( $attachment_id ); + } + } + + if ( empty( $image_url ) ) { + return new WP_Error( 'error', esc_html__( 'Valid image size not found. Make sure the image is bigger than 512x512px.', 'classifai' ) ); + } + + return $image_url; + } + /** * Returns the debug information for the provider settings. * diff --git a/readme.txt b/readme.txt index aefae8cbd..d8af02bd9 100644 --- a/readme.txt +++ b/readme.txt @@ -30,7 +30,7 @@ Tap into leading cloud-based services like [OpenAI](https://openai.com/), [Micro * Find similar terms to merge together using either [OpenAI's Embedding API](https://platform.openai.com/docs/guides/embeddings) or [Microsoft Azure's OpenAI service](https://azure.microsoft.com/en-us/products/ai-services/openai-service) in combination with [ElasticPress](https://github.com/10up/ElasticPress). Note this only compares top-level terms and if you merge a term that has children, these become top-level terms as per default WordPress behavior * BETA: Recommend content based on overall site traffic via [Microsoft Azure's AI Personalizer API](https://azure.microsoft.com/en-us/services/cognitive-services/personalizer/) _(note that this service has been deprecated by Microsoft and as such, will no longer work. We are looking to replace this with a new provider to maintain the same functionality)_ * Generate image alt text using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/), [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat), [xAI's Grok](https://x.ai/) or locally using [Ollama](https://ollama.com/) -* Generate image tags and extract text from images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) or locally using [Ollama](https://ollama.com/) +* Generate image tags and extract text from images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/), [OpenAI's ChatGPT API](https://platform.openai.com/docs/guides/chat) or locally using [Ollama](https://ollama.com/) * Smartly crop images using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) * Scan PDF files for embedded text and save for use in post meta using [Microsoft Azure's AI Vision API](https://azure.microsoft.com/en-us/services/cognitive-services/computer-vision/) * Bulk classify content with [WP-CLI](https://wp-cli.org/) diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js index 80bd2e000..7a3cabd09 100644 --- a/src/js/settings/components/provider-settings/openai-chatgpt.js +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -104,9 +104,10 @@ export const OpenAIChatGPTSettings = ( { isConfigured = false } ) => { /> ) } - { [ 'feature_descriptive_text_generator' ].includes( - featureName - ) && ( + { [ + 'feature_descriptive_text_generator', + 'feature_image_to_text_generator', + ].includes( featureName ) && ( Date: Thu, 13 Feb 2025 10:36:18 -0700 Subject: [PATCH 2/3] Add OpenAI as a Provider for the Image Tags Generator Feature --- .../Classifai/Features/ImageTagsGenerator.php | 11 ++ .../Classifai/Providers/OpenAI/ChatGPT.php | 112 ++++++++++++++++++ .../provider-settings/openai-chatgpt.js | 1 + 3 files changed, 124 insertions(+) diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index d042c6cb8..6042ba373 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -3,6 +3,7 @@ namespace Classifai\Features; use Classifai\Providers\Azure\ComputerVision; +use Classifai\Providers\OpenAI\ChatGPT; use Classifai\Providers\Localhost\OllamaMultimodal as OllamaMM; use Classifai\Services\ImageProcessing; use WP_REST_Server; @@ -49,6 +50,7 @@ public function __construct() { // Contains just the providers this feature supports. $this->supported_providers = [ ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), + ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), OllamaMM::ID => __( 'Ollama', 'classifai' ), ]; } @@ -73,6 +75,15 @@ public function get_settings( $index = false ) { $settings = parent::get_settings( $index ); // Keep using the original prompt from the codebase to allow updates. + if ( $settings && ! empty( $settings[ ChatGPT::ID ]['prompt'] ) ) { + foreach ( $settings[ ChatGPT::ID ]['prompt'] as $key => $prompt ) { + if ( 1 === intval( $prompt['original'] ) ) { + $settings[ ChatGPT::ID ]['prompt'][ $key ]['prompt'] = $this->prompt; + break; + } + } + } + if ( $settings && ! empty( $settings[ OllamaMM::ID ]['prompt'] ) ) { foreach ( $settings[ OllamaMM::ID ]['prompt'] as $key => $prompt ) { if ( 1 === intval( $prompt['original'] ) ) { diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index 3973d0a39..fe2a3fb0c 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -8,6 +8,7 @@ use Classifai\Features\ContentResizing; use Classifai\Features\DescriptiveTextGenerator; use Classifai\Features\ImageTextExtraction; +use Classifai\Features\ImageTagsGenerator; use Classifai\Features\ExcerptGeneration; use Classifai\Features\TitleGeneration; use Classifai\Features\KeyTakeaways; @@ -140,6 +141,7 @@ public function get_default_provider_settings(): array { case DescriptiveTextGenerator::ID: case ImageTextExtraction::ID: + case ImageTagsGenerator::ID: $common_settings['prompt'] = [ [ 'title' => esc_html__( 'ClassifAI default', 'classifai' ), @@ -211,6 +213,8 @@ public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '' break; case 'ocr': return $this->ocr_processing( $post_id, $args ); + case 'tags': + return $this->generate_image_tags( $post_id, $args ); case 'excerpt': $return = $this->generate_excerpt( $post_id, $args ); break; @@ -421,6 +425,114 @@ public function ocr_processing( int $post_id = 0, array $args = [] ) { if ( isset( $choice['message'], $choice['message']['content'] ) ) { // ChatGPT often adds quotes to strings, so remove those as well as extra spaces. $response = sanitize_text_field( trim( $choice['message']['content'], ' "\'' ) ); + + if ( 'none' === $response ) { + $response = new WP_Error( 'no_choices', esc_html__( 'No text found.', 'classifai' ) ); + } + } + } + } else { + $response = new WP_Error( 'no_choices', esc_html__( 'No choices were returned from OpenAI.', 'classifai' ) ); + } + + return $response; + } + + /** + * Generate tags for an image. + * + * @param int $post_id Post ID for the attachment. + * @param array $args Arguments passed in. + * @return string|WP_Error + */ + public function generate_image_tags( int $post_id = 0, array $args = [] ) { + $image_url = $this->get_image_url( $post_id ); + + if ( is_wp_error( $image_url ) ) { + return $image_url; + } + + $feature = new ImageTagsGenerator(); + $settings = $feature->get_settings(); + + // These checks (and the one above) happen in the REST permission_callback, + // but we run them again here in case this method is called directly. + if ( empty( $settings ) || ( isset( $settings[ static::ID ]['authenticated'] ) && false === $settings[ static::ID ]['authenticated'] ) || ( ! $feature->is_feature_enabled() && ( ! defined( 'WP_CLI' ) || ! WP_CLI ) ) ) { + return new WP_Error( 'not_enabled', esc_html__( 'Image tag generation is disabled or OpenAI authentication failed. Please check your settings.', 'classifai' ) ); + } + + $request = new APIRequest( $settings[ static::ID ]['api_key'] ?? '', $feature->get_option_name() ); + + /** + * Filter the prompt we will send to ChatGPT. + * + * @since x.x.x + * @hook classifai_chatgpt_image_tag_prompt + * + * @param {string} $prompt Prompt we are sending to ChatGPT. + * @param {int} $post_id ID of attachment we are describing. + * + * @return {string} Prompt. + */ + $prompt = apply_filters( 'classifai_chatgpt_image_tag_prompt', get_default_prompt( $settings[ static::ID ]['prompt'] ?? [] ) ?? $feature->prompt, $post_id ); + + /** + * Filter the request body before sending to ChatGPT. + * + * @since x.x.x + * @hook classifai_chatgpt_image_tag_request_body + * + * @param {array} $body Request body that will be sent to ChatGPT. + * @param {int} $post_id ID of attachment we are describing. + * + * @return {array} Request body. + */ + $body = apply_filters( + 'classifai_chatgpt_image_tag_request_body', + [ + 'model' => $this->chatgpt_model, + 'messages' => [ + [ + 'role' => 'system', + 'content' => $prompt, + ], + [ + 'role' => 'user', + 'content' => [ + [ + 'type' => 'image_url', + 'image_url' => [ + 'url' => $image_url, + 'detail' => 'auto', + ], + ], + ], + ], + ], + 'temperature' => 0.2, + 'max_tokens' => 300, + ], + $post_id + ); + + // Make our API request. + $response = $request->post( + $this->chatgpt_url, + [ + 'body' => wp_json_encode( $body ), + ] + ); + + if ( is_wp_error( $response ) ) { + return $response; + } + + // Extract out the text response, if it exists. + if ( ! empty( $response['choices'] ) ) { + foreach ( $response['choices'] as $choice ) { + if ( isset( $choice['message'], $choice['message']['content'] ) ) { + $response = array_filter( explode( '- ', $choice['message']['content'] ) ); + $response = array_map( 'trim', $response ); } } } else { diff --git a/src/js/settings/components/provider-settings/openai-chatgpt.js b/src/js/settings/components/provider-settings/openai-chatgpt.js index 7a3cabd09..f1f2c8b86 100644 --- a/src/js/settings/components/provider-settings/openai-chatgpt.js +++ b/src/js/settings/components/provider-settings/openai-chatgpt.js @@ -107,6 +107,7 @@ export const OpenAIChatGPTSettings = ( { isConfigured = false } ) => { { [ 'feature_descriptive_text_generator', 'feature_image_to_text_generator', + 'feature_image_tags_generator', ].includes( featureName ) && ( Date: Thu, 13 Feb 2025 11:01:36 -0700 Subject: [PATCH 3/3] Add tests; fix issue tests found --- .../Classifai/Providers/OpenAI/ChatGPT.php | 11 ++- .../image-processing-openai-chatgpt.test.js | 96 ++++++++++++++++++- 2 files changed, 105 insertions(+), 2 deletions(-) diff --git a/includes/Classifai/Providers/OpenAI/ChatGPT.php b/includes/Classifai/Providers/OpenAI/ChatGPT.php index fe2a3fb0c..5e6bece54 100644 --- a/includes/Classifai/Providers/OpenAI/ChatGPT.php +++ b/includes/Classifai/Providers/OpenAI/ChatGPT.php @@ -323,6 +323,9 @@ public function generate_descriptive_text( int $post_id = 0, array $args = [] ) if ( isset( $choice['message'], $choice['message']['content'] ) ) { // ChatGPT often adds quotes to strings, so remove those as well as extra spaces. $response = sanitize_text_field( trim( $choice['message']['content'], ' "\'' ) ); + + // Save full results for later. + update_post_meta( $post_id, 'classifai_computer_vision_captions', $response ); } } } @@ -426,8 +429,11 @@ public function ocr_processing( int $post_id = 0, array $args = [] ) { // ChatGPT often adds quotes to strings, so remove those as well as extra spaces. $response = sanitize_text_field( trim( $choice['message']['content'], ' "\'' ) ); - if ( 'none' === $response ) { + if ( ! $response || 'none' === $response ) { $response = new WP_Error( 'no_choices', esc_html__( 'No text found.', 'classifai' ) ); + } else { + // Save all the results for later + update_post_meta( $post_id, 'classifai_computer_vision_ocr', $response ); } } } @@ -533,6 +539,9 @@ public function generate_image_tags( int $post_id = 0, array $args = [] ) { if ( isset( $choice['message'], $choice['message']['content'] ) ) { $response = array_filter( explode( '- ', $choice['message']['content'] ) ); $response = array_map( 'trim', $response ); + + // Save all the tags for later. + update_post_meta( $post_id, 'classifai_computer_vision_image_tags', $response ); } } } else { diff --git a/tests/cypress/integration/image-processing/image-processing-openai-chatgpt.test.js b/tests/cypress/integration/image-processing/image-processing-openai-chatgpt.test.js index 45cf90d6c..05c27632e 100644 --- a/tests/cypress/integration/image-processing/image-processing-openai-chatgpt.test.js +++ b/tests/cypress/integration/image-processing/image-processing-openai-chatgpt.test.js @@ -3,7 +3,7 @@ import { getChatGPTData } from '../../plugins/functions'; [ 'openai_chatgpt', 'xai_grok' ].forEach( ( provider ) => { const providerName = 'openai_chatgpt' === provider ? 'OpenAI ChatGPT' : 'xAI Grok'; - describe( `[${ providerName }] Image Processing Tests`, () => { + describe( `[${ providerName }] Descriptive Text Generator Tests`, () => { let imageEditLink = ''; let mediaModelLink = ''; @@ -245,3 +245,97 @@ import { getChatGPTData } from '../../plugins/functions'; } ); } ); } ); + +describe( `OpenAI ChatGPT Image Tag and Text Generator Tests`, () => { + let imageEditLink = ''; + let mediaModelLink = ''; + + before( () => { + cy.login(); + + const imageProcessingFeatures = [ + 'feature_image_tags_generator', + 'feature_image_to_text_generator', + ]; + + imageProcessingFeatures.forEach( ( feature ) => { + cy.visitFeatureSettings( `image_processing/${ feature }` ); + cy.wait( 100 ); + cy.enableFeature(); + cy.selectProvider( 'openai_chatgpt' ); + cy.get( '#openai_chatgpt_api_key' ).clear().type( 'password' ); + cy.allowFeatureToAdmin(); + cy.get( '.classifai-settings__user-based-opt-out input' ).uncheck(); + + // Disable access for all users. + cy.disableFeatureForUsers(); + + cy.saveFeatureSettings(); + } ); + + cy.optInAllFeatures(); + } ); + + beforeEach( () => { + cy.login(); + } ); + + it( 'Can see Image Processing actions on edit media page and verify generated data.', () => { + cy.visit( '/wp-admin/upload.php?mode=grid' ); // Ensure grid mode is enabled. + cy.visit( '/wp-admin/media-new.php' ); + cy.get( '#plupload-upload-ui' ).should( 'exist' ); + cy.get( '#plupload-upload-ui input[type=file]' ).attachFile( + '../../../assets/img/onboarding-1.png' + ); + + cy.get( '#media-items .media-item a.edit-attachment', { + timeout: 20000, + } ).should( 'exist' ); + cy.get( '#media-items .media-item a.edit-attachment' ) + .invoke( 'attr', 'href' ) + .then( ( editLink ) => { + imageEditLink = editLink; + cy.visit( editLink ); + } ); + + // Verify Metabox with Image processing actions. + cy.get( '.postbox-header h2, #classifai_image_processing h2' ) + .first() + .contains( 'ClassifAI Image Processing' ); + cy.get( '#classifai_image_processing label[for=rescan-tags]' ).contains( + 'Rescan image for new tags' + ); + cy.get( '#classifai_image_processing label[for=rescan-ocr]' ).contains( + 'Rescan for text' + ); + + // Verify generated Data. + const tags = [ 'Hello there', 'how may I assist you today?' ]; + cy.get( '#attachment_content' ).should( + 'have.value', + 'Hello there, how may I assist you today?' + ); + cy.get( + '#classifai-image-tags ul.tagchecklist li span.screen-reader-text' + ) + .each( ( tag ) => { + return expect( + tag.text().replace( 'Remove term: ', '' ) + ).to.be.oneOf( tags ); + } ) + .then( ( imageTags ) => { + expect( imageTags ).to.have.length( tags.length ); + } ); + } ); + + it( 'Can see Image Processing actions on media modal', () => { + const imageId = imageEditLink.split( 'post=' )[ 1 ]?.split( '&' )[ 0 ]; + mediaModelLink = `wp-admin/upload.php?item=${ imageId }`; + cy.visit( mediaModelLink ); + cy.get( '.media-modal' ).should( 'exist' ); + + // Verify Image processing actions. + cy.get( '#classifai-rescan-image-tags' ).contains( 'Rescan' ); + cy.get( '#classifai-rescan-ocr' ).contains( 'Rescan' ); + } ); +} );