diff --git a/includes/Classifai/Admin/Settings.php b/includes/Classifai/Admin/Settings.php index fbbe97f7d..3fbea4090 100644 --- a/includes/Classifai/Admin/Settings.php +++ b/includes/Classifai/Admin/Settings.php @@ -171,6 +171,7 @@ public function get_features( bool $with_instance = false ): array { 'roles' => $feature->get_roles(), 'enable_description' => $feature->get_enable_description(), 'taxonomies' => $feature->get_taxonomies(), + 'providers_data' => $feature->supported_providers_data, ); // Add taxonomies under post types for language processing features to allow filtering by post type. diff --git a/includes/Classifai/Features/AudioTranscriptsGeneration.php b/includes/Classifai/Features/AudioTranscriptsGeneration.php index 24b66832e..9b7dc7d86 100644 --- a/includes/Classifai/Features/AudioTranscriptsGeneration.php +++ b/includes/Classifai/Features/AudioTranscriptsGeneration.php @@ -26,6 +26,13 @@ class AudioTranscriptsGeneration extends Feature { */ const ID = 'feature_audio_transcripts_generation'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '08.audio-transcripts-generation.md'; + /** * Constructor. */ @@ -40,6 +47,16 @@ public function __construct() { OpenAISpeechToText::ID => __( 'OpenAI Audio Transcription', 'classifai' ), ElevenLabsSpeechToText::ID => __( 'ElevenLabs Audio Transcription', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + SpeechToText::ID => preg_match( '/## Set Up via OpenAI Speech to Text(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/Classification.php b/includes/Classifai/Features/Classification.php index cd21cb098..2ab6fdd00 100644 --- a/includes/Classifai/Features/Classification.php +++ b/includes/Classifai/Features/Classification.php @@ -30,6 +30,13 @@ class Classification extends Feature { */ const ID = 'feature_classification'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '01.classification.md'; + /** * Constructor. */ @@ -46,6 +53,19 @@ public function __construct() { AzureEmbeddings::ID => __( 'Azure OpenAI Embeddings', 'classifai' ), OllamaEmbeddings::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + NLU::ID => preg_match( '/## Set Up via IBM Watson(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAIEmbeddings::ID => preg_match( '/## Set Up via OpenAI Embeddings(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + AzureEmbeddings::ID => '', + OllamaEmbeddings::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/ContentGeneration.php b/includes/Classifai/Features/ContentGeneration.php index 76667ff12..ed0760e4f 100644 --- a/includes/Classifai/Features/ContentGeneration.php +++ b/includes/Classifai/Features/ContentGeneration.php @@ -27,6 +27,13 @@ class ContentGeneration extends Feature { */ const ID = 'feature_content_generation'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '04.content-generation.md'; + /** * Prompt for creating content. * @@ -95,6 +102,18 @@ public function __construct() { OpenAI::ID => __( 'Azure OpenAI', 'classifai' ), Ollama::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ChatGPT::ID => preg_match( '/## Set Up via OpenAI ChatGPT(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAI::ID => preg_match( '/## Set Up via Azure OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Ollama::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/ContentResizing.php b/includes/Classifai/Features/ContentResizing.php index f97a4b0f5..432320bad 100644 --- a/includes/Classifai/Features/ContentResizing.php +++ b/includes/Classifai/Features/ContentResizing.php @@ -41,6 +41,13 @@ class ContentResizing extends Feature { */ public $expand_prompt = 'Increase the content length no more than 2 to 4 sentences.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '05.content-resizing.md'; + /** * Constructor. */ @@ -59,6 +66,21 @@ public function __construct() { ChromeAI::ID => __( 'Chrome AI (experimental)', 'classifai' ), Ollama::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ChatGPT::ID => preg_match( '/## Set Up via OpenAI ChatGPT(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + GeminiAPI::ID => preg_match( '/## Set Up via Google AI \(Gemini API\)(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAI::ID => preg_match( '/## Set Up via Azure OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Grok::ID => '', + ChromeAI::ID => '', + Ollama::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/DescriptiveTextGenerator.php b/includes/Classifai/Features/DescriptiveTextGenerator.php index a7dbc7f27..a4da441fa 100644 --- a/includes/Classifai/Features/DescriptiveTextGenerator.php +++ b/includes/Classifai/Features/DescriptiveTextGenerator.php @@ -31,6 +31,13 @@ class DescriptiveTextGenerator extends Feature { */ public $prompt = 'You are an assistant that generates descriptions of images that are used on a website. You will be provided with an image and will describe the main item you see in the image, giving details but staying concise. There is no need to say "the image contains" or similar, just describe what is actually in the image. This text will be important for screen readers, so make sure it is descriptive and accurate but not overly verbose. Before returning the text, re-evaluate your response and ensure you are following the above points, in particular ensuring the text is concise.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '12.descriptive-text-generator.md'; + /** * Constructor. */ @@ -47,6 +54,19 @@ public function __construct() { Grok::ID => __( 'xAI Grok', 'classifai' ), OllamaMM::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ComputerVision::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ChatGPT::ID => '', + Grok::ID => '', + OllamaMM::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/ExcerptGeneration.php b/includes/Classifai/Features/ExcerptGeneration.php index ebaea0e24..91acf07c1 100644 --- a/includes/Classifai/Features/ExcerptGeneration.php +++ b/includes/Classifai/Features/ExcerptGeneration.php @@ -41,6 +41,13 @@ class ExcerptGeneration extends Feature { */ public $woo_prompt = 'Create a concise, compelling summary for an ecommerce product that highlights key features, benefits, and unique selling points. Keep it within {{WORDS}} words and ensure it pairs well with the product title: {{TITLE}}.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '03.excerpt-generation.md'; + /** * Constructor. */ @@ -59,6 +66,21 @@ public function __construct() { ChromeAI::ID => __( 'Chrome AI (experimental)', 'classifai' ), Ollama::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ChatGPT::ID => preg_match( '/## Set Up via OpenAI ChatGPT(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + GeminiAPI::ID => preg_match( '/## Set Up via Google AI \(Gemini API\)(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAI::ID => preg_match( '/## Set Up via Azure OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Grok::ID => '', + ChromeAI::ID => '', + Ollama::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/Feature.php b/includes/Classifai/Features/Feature.php index 22bced6aa..457bc16b5 100644 --- a/includes/Classifai/Features/Feature.php +++ b/includes/Classifai/Features/Feature.php @@ -7,6 +7,7 @@ use function Classifai\find_provider_class; use function Classifai\should_use_legacy_settings_panel; use function Classifai\get_asset_info; +use function Classifai\safe_wp_remote_get; abstract class Feature { /** @@ -28,6 +29,20 @@ abstract class Feature { */ const PLUGIN_AREA_SCRIPT = 'classifai-plugin-fill-js'; + /** + * Plugin feature instruction directory URL. + * + * @var string + */ + const PLUGIN_FEATURE_INSTRUCTION_DIR = 'https://raw.githubusercontent.com/10up/classifai/refs/heads/develop/wp-hooks-docs/docs/02.feature-configuration'; + + /** + * Locally hosted instruction file. + * + * @var string + */ + const PLUGIN_LOCALLY_HOSTED_INSTRUCTION_URL = 'https://raw.githubusercontent.com/10up/classifai/refs/heads/develop/wp-hooks-docs/docs/03.advanced-docs/06.run-locally-hosted-llms.md'; + /** * Feature label. * @@ -58,6 +73,20 @@ abstract class Feature { */ public $supported_providers = []; + /** + * Array of providers data supported by the feature. + * + * @var \Classifai\Providers\Provider[] + */ + public $supported_providers_data = []; + + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = ''; + /** * Set up necessary hooks. */ @@ -1411,4 +1440,86 @@ public function run( ...$args ) { $this ); } + + /** + * Get Feature Instructions from related file. + * + * @return string + */ + public function get_instruction_content() { + $instruction_file = $this->instruction_file ?? ''; + $provider = static::ID; + + if ( empty( $instruction_file ) || empty( $provider ) ) { + return; + } + + $transient_key = 'classifai_instruction_' . $provider; + + // Get content from cache. + $feature_instruction = get_transient( $transient_key ); + + if ( ! empty( $feature_instruction ) ) { + return $feature_instruction; + } + + $instruction_content_url = trailingslashit( self::PLUGIN_FEATURE_INSTRUCTION_DIR ) . $instruction_file; + + // Get content. + $instruction_request = safe_wp_remote_get( $instruction_content_url ); + + if ( is_wp_error( $instruction_request ) ) { + return esc_html__( 'Instruction content cannot be downloaded.', 'classifai' ); + } + + $feature_instruction = wp_remote_retrieve_body( $instruction_request ); + + if ( empty( $feature_instruction ) || ! is_string( $feature_instruction ) ) { + return esc_html__( 'Instruction content cannot be downloaded.', 'classifai' ); + } + + $feature_instruction = wp_kses_post( $feature_instruction ); + + // Cache content. + set_transient( $transient_key, $feature_instruction, DAY_IN_SECONDS ); + + return $feature_instruction; + } + + /** + * Get Feature Instructions for locally hosted LLMs. + * + * @return string + */ + public function get_locally_hosted_llm_instruction() { + + $transient_key = 'classifai_instruction_locally_hosted_llm'; + + // Get content from cache. + $locally_hosted_instruction = get_transient( $transient_key ); + + if ( ! empty( $locally_hosted_instruction ) ) { + return $locally_hosted_instruction; + } + + $instruction_content_url = self::PLUGIN_LOCALLY_HOSTED_INSTRUCTION_URL; + $instruction_request = safe_wp_remote_get( $instruction_content_url ); + + if ( is_wp_error( $instruction_request ) ) { + return esc_html__( 'Instruction content cannot be downloaded.', 'classifai' ); + } + + $locally_hosted_instruction = wp_remote_retrieve_body( $instruction_request ); + + if ( empty( $locally_hosted_instruction ) || ! is_string( $locally_hosted_instruction ) ) { + return esc_html__( 'Instruction content cannot be downloaded.', 'classifai' ); + } + + $locally_hosted_instruction = wp_kses_post( preg_replace( '/^---\s*[\s\S]*?\s*---\s*/', '', $locally_hosted_instruction ) ); + + // Cache content. + set_transient( $transient_key, $locally_hosted_instruction, DAY_IN_SECONDS ); + + return $locally_hosted_instruction; + } } diff --git a/includes/Classifai/Features/ImageCropping.php b/includes/Classifai/Features/ImageCropping.php index b7935e57a..59d5983c2 100644 --- a/includes/Classifai/Features/ImageCropping.php +++ b/includes/Classifai/Features/ImageCropping.php @@ -21,6 +21,13 @@ class ImageCropping extends Feature { */ const ID = 'feature_image_cropping'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '14.image-cropping.md'; + /** * WP_Filesystem_Base instance. * @@ -43,6 +50,16 @@ public function __construct() { $this->supported_providers = [ ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ComputerVision::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/ImageGeneration.php b/includes/Classifai/Features/ImageGeneration.php index a840f8a49..2ba42ae2c 100644 --- a/includes/Classifai/Features/ImageGeneration.php +++ b/includes/Classifai/Features/ImageGeneration.php @@ -25,6 +25,13 @@ class ImageGeneration extends Feature { */ const ID = 'feature_image_generation'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '16.image-generation.md'; + /** * Constructor. */ @@ -41,6 +48,17 @@ public function __construct() { TogetherAIImages::ID => __( 'Together AI', 'classifai' ), LocalhostStableDiffusion::ID => __( 'Stable Diffusion (local)', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + OpenAIImages::ID => preg_match( '/## Set Up via OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + GoogleAIImagen::ID => preg_match( '/## Set Up via Google AI Imagen(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/ImageTagsGenerator.php b/includes/Classifai/Features/ImageTagsGenerator.php index 03b883c43..4092f8707 100644 --- a/includes/Classifai/Features/ImageTagsGenerator.php +++ b/includes/Classifai/Features/ImageTagsGenerator.php @@ -38,6 +38,13 @@ class ImageTagsGenerator extends Feature { EOD; // phpcs:enable Squiz.PHP.Heredoc.NotAllowed + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '13.image-tags-generator.md'; + /** * Constructor. */ @@ -53,6 +60,18 @@ public function __construct() { ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), OllamaMM::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ComputerVision::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ChatGPT::ID => '', + OllamaMM::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/ImageTextExtraction.php b/includes/Classifai/Features/ImageTextExtraction.php index 8c4144735..3972c79b8 100644 --- a/includes/Classifai/Features/ImageTextExtraction.php +++ b/includes/Classifai/Features/ImageTextExtraction.php @@ -32,6 +32,13 @@ class ImageTextExtraction extends Feature { */ public $prompt = 'You are an assistant that extracts text from images. You will be provided with an image and will return whatever text is in the image. Return only the text, nothing else. If there is no text present, return the word none.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '15.image-text-extraction.md'; + /** * Constructor. */ @@ -47,6 +54,18 @@ public function __construct() { ChatGPT::ID => __( 'OpenAI ChatGPT', 'classifai' ), OllamaMM::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ComputerVision::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ChatGPT::ID => '', + OllamaMM::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/KeyTakeaways.php b/includes/Classifai/Features/KeyTakeaways.php index d7fa00e52..e990e79e4 100644 --- a/includes/Classifai/Features/KeyTakeaways.php +++ b/includes/Classifai/Features/KeyTakeaways.php @@ -32,6 +32,13 @@ class KeyTakeaways extends Feature { */ public $prompt = 'The content you will be provided with is from an already written article. This article has the title of: {{TITLE}}. Your task is to carefully read through this article and provide a summary that captures all the important points. This summary should be concise and limited to about 2-4 points but should also be detailed enough to allow someone to quickly grasp the full article. Read the article a few times before providing the summary and trim each point down to be as concise as possible.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '06.key-takeaways.md'; + /** * Constructor. */ @@ -47,6 +54,18 @@ public function __construct() { OpenAI::ID => __( 'Azure OpenAI', 'classifai' ), Ollama::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ChatGPT::ID => preg_match( '/## Set Up via OpenAI ChatGPT(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAI::ID => preg_match( '/## Set Up via Azure OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Ollama::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/Moderation.php b/includes/Classifai/Features/Moderation.php index 3ce97190f..a637fb0dd 100644 --- a/includes/Classifai/Features/Moderation.php +++ b/includes/Classifai/Features/Moderation.php @@ -20,6 +20,13 @@ class Moderation extends Feature { */ const ID = 'feature_moderation'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '09.moderation.md'; + /** * Constructor. */ @@ -33,6 +40,16 @@ public function __construct() { $this->supported_providers = [ ModerationProvider::ID => __( 'OpenAI Moderation', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ModerationProvider::ID => preg_match( '/## Set Up via OpenAI Moderation(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/PDFTextExtraction.php b/includes/Classifai/Features/PDFTextExtraction.php index 0772b2254..ad9f84c4b 100644 --- a/includes/Classifai/Features/PDFTextExtraction.php +++ b/includes/Classifai/Features/PDFTextExtraction.php @@ -22,6 +22,13 @@ class PDFTextExtraction extends Feature { */ const ID = 'feature_pdf_to_text_generation'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '17.pdf-text-extraction.md'; + /** * Constructor. */ @@ -35,6 +42,16 @@ public function __construct() { $this->supported_providers = [ ComputerVision::ID => __( 'Microsoft Azure AI Vision', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ComputerVision::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/RecommendedContent.php b/includes/Classifai/Features/RecommendedContent.php index 09b9287d2..a5b95cfd6 100644 --- a/includes/Classifai/Features/RecommendedContent.php +++ b/includes/Classifai/Features/RecommendedContent.php @@ -19,6 +19,13 @@ class RecommendedContent extends Feature { */ const ID = 'feature_recommended_content'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '18.recommended-content.md'; + /** * Constructor. */ @@ -32,6 +39,17 @@ public function __construct() { $this->supported_providers = [ OpenAIEmbeddings::ID => __( 'OpenAI Embeddings', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + OpenAIEmbeddings::ID => preg_match( '/## Set Up via OpenAI Embeddings(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + PersonalizerProvider::ID => '', + ], + ]; } /** diff --git a/includes/Classifai/Features/Smart404.php b/includes/Classifai/Features/Smart404.php index 534ab9d01..e4dc2225e 100644 --- a/includes/Classifai/Features/Smart404.php +++ b/includes/Classifai/Features/Smart404.php @@ -23,6 +23,13 @@ class Smart404 extends Feature { */ const ID = 'feature_smart_404'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '10.smart-404.md'; + /** * Constructor. */ @@ -38,6 +45,18 @@ public function __construct() { AzureEmbeddings::ID => __( 'Azure OpenAI Embeddings', 'classifai' ), OllamaEmbeddings::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + OpenAIEmbeddings::ID => preg_match( '/## Set Up the Smart 404 Feature(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + AzureEmbeddings::ID => preg_match( '/## Set Up the Smart 404 Feature(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OllamaEmbeddings::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/TermCleanup.php b/includes/Classifai/Features/TermCleanup.php index c30a324e5..83d92962f 100644 --- a/includes/Classifai/Features/TermCleanup.php +++ b/includes/Classifai/Features/TermCleanup.php @@ -25,6 +25,13 @@ class TermCleanup extends Feature { */ const ID = 'feature_term_cleanup'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '11.term-cleanup.md'; + /** * Setting page URL. * @@ -68,6 +75,18 @@ public function __construct() { AzureEmbeddings::ID => __( 'Azure OpenAI Embeddings', 'classifai' ), OllamaEmbeddings::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + OpenAIEmbeddings::ID => preg_match( '/## Set Up the Term Cleanup Feature(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + AzureEmbeddings::ID => preg_match( '/## Set Up the Term Cleanup Feature(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OllamaEmbeddings::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/includes/Classifai/Features/TextToSpeech.php b/includes/Classifai/Features/TextToSpeech.php index 9e275de56..a10ba896d 100644 --- a/includes/Classifai/Features/TextToSpeech.php +++ b/includes/Classifai/Features/TextToSpeech.php @@ -55,6 +55,13 @@ class TextToSpeech extends Feature { */ const AUDIO_HASH_KEY = '_classifai_post_audio_hash'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '07.text-to-speech.md'; + /** * Constructor. */ @@ -71,6 +78,18 @@ public function __construct() { OpenAITTS::ID => __( 'OpenAI Text to Speech', 'classifai' ), ElevenLabsTTS::ID => __( 'ElevenLabs', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + AmazonPolly::ID => preg_match( '/## Set Up via Amazon Polly(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Speech::ID => preg_match( '/## Set Up via Microsoft Azure(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAITTS::ID => preg_match( '/## Set Up via OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + ], + ]; } /** diff --git a/includes/Classifai/Features/TitleGeneration.php b/includes/Classifai/Features/TitleGeneration.php index efcd11170..8560129a3 100644 --- a/includes/Classifai/Features/TitleGeneration.php +++ b/includes/Classifai/Features/TitleGeneration.php @@ -41,6 +41,13 @@ class TitleGeneration extends Feature { */ public $woo_prompt = 'Write an SEO-friendly, engaging product title that encourages clicks and purchases. Use key details like product type, attributes, and categories while keeping it within 40 to 60 characters and format it in sentence case.'; + /** + * Instruction file name for the feature. + * + * @var string + */ + public $instruction_file = '02.title-generation.md'; + /** * Constructor. */ @@ -59,6 +66,21 @@ public function __construct() { ChromeAI::ID => __( 'Chrome AI (experimental)', 'classifai' ), Ollama::ID => __( 'Ollama', 'classifai' ), ]; + + // Get readme content. + $readme_content = $this->get_instruction_content(); + + // Contains supported providers data. + $this->supported_providers_data = [ + 'instruction' => [ + ChatGPT::ID => preg_match( '/## Set Up via OpenAI ChatGPT(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + GeminiAPI::ID => preg_match( '/## Set Up via Google AI \(Gemini API\)(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + OpenAI::ID => preg_match( '/## Set Up via Azure OpenAI(.*?)(?:\n## |\z)/s', $readme_content, $matches ) ? $matches[1] : '', + Grok::ID => '', + ChromeAI::ID => '', + Ollama::ID => $this->get_locally_hosted_llm_instruction(), + ], + ]; } /** diff --git a/package-lock.json b/package-lock.json index 4a2feda18..a21bb7c6c 100644 --- a/package-lock.json +++ b/package-lock.json @@ -12,6 +12,7 @@ "@wordpress/icons": "^10.17.0", "choices.js": "^11.1.0", "classnames": "^2.5.1", + "marked": "^16.2.0", "motion": "^12.23.12", "react-router-dom": "^7.5.2", "tippy.js": "^6.3.7" @@ -17412,6 +17413,18 @@ "dev": true, "license": "MIT" }, + "node_modules/marked": { + "version": "16.2.0", + "resolved": "https://registry.npmjs.org/marked/-/marked-16.2.0.tgz", + "integrity": "sha512-LbbTuye+0dWRz2TS9KJ7wsnD4KAtpj0MVkWc90XvBa6AslXsT0hTBVH5k32pcSyHH1fst9XEFJunXHktVy0zlg==", + "license": "MIT", + "bin": { + "marked": "bin/marked.js" + }, + "engines": { + "node": ">= 20" + } + }, "node_modules/marky": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/marky/-/marky-1.3.0.tgz", diff --git a/package.json b/package.json index ac8df05ec..160d4ffeb 100644 --- a/package.json +++ b/package.json @@ -54,6 +54,7 @@ "@wordpress/icons": "^10.17.0", "choices.js": "^11.1.0", "classnames": "^2.5.1", + "marked": "^16.2.0", "motion": "^12.23.12", "react-router-dom": "^7.5.2", "tippy.js": "^6.3.7" diff --git a/src/js/settings/components/provider-settings/index.js b/src/js/settings/components/provider-settings/index.js index dfe8f24af..4d69d316d 100644 --- a/src/js/settings/components/provider-settings/index.js +++ b/src/js/settings/components/provider-settings/index.js @@ -205,6 +205,10 @@ export const ProviderSettings = () => { { ! configured && ( + * + * @return {JSX.Element} The rendered FieldInfo button and modal. + * + * Notes: + * - The modal content is loaded dynamically from feature context data. + * - The icon can be customized based on info type. + * - Uses `dangerouslySetInnerHTML` to render content. + * - The content rendering here has already been sanitized on the server. + */ +export const FieldInfo = ( props ) => { + const { featureName } = useFeatureContext(); + const [ isOpen, setOpen ] = useState( false ); + const [ icon, setIcon ] = useState( 'info' ); + const [ fieldInfoContent, setFieldInfoContent ] = useState( '' ); + const [ infoTitle, setInfoTitle ] = useState( '' ); + const openModal = () => setOpen( true ); + const closeModal = () => setOpen( false ); + const feature = getFeature( featureName ); + + useEffect( () => { + switch ( props.fieldInfo.type ) { + case 'instruction': + setFieldInfoContent( + feature.providers_data[ props.fieldInfo.type ][ + props.fieldInfo.provider + ] || __( 'No info found.', 'classifai' ) + ); + setInfoTitle( + `${ + feature.label + + ' - ' + + feature.providers[ props.fieldInfo.provider ] + }` || '' + ); + setIcon( 'editor-help' ); + break; + + default: + break; + } + }, [ + props.fieldInfo, + feature.providers, + feature.providers_data, + feature.label, + ] ); + + return ( + <> + + { isOpen && ( + +
+ + ) } + + ); +}; diff --git a/src/js/settings/components/settings-row/index.js b/src/js/settings/components/settings-row/index.js index 341d65999..a08cd0703 100644 --- a/src/js/settings/components/settings-row/index.js +++ b/src/js/settings/components/settings-row/index.js @@ -2,6 +2,7 @@ * External dependencies */ import classNames from 'classnames'; +import { FieldInfo } from './field-info'; /** * Settings row component. @@ -13,7 +14,12 @@ import classNames from 'classnames'; export const SettingsRow = ( props ) => { return (
-
{ props.label }
+
+ { props.label } + { props.fieldInfo && ( + + ) } +
{ props.children }
diff --git a/src/scss/settings.scss b/src/scss/settings.scss index 336d3bbd4..c41fdabac 100644 --- a/src/scss/settings.scss +++ b/src/scss/settings.scss @@ -94,11 +94,25 @@ } .settings-label { + display: flex; align-items: center; font-weight: bold; flex-shrink: 0; flex: 20%; padding: 12px 0px; + + .settings-info-button { + + &:hover { + background: none; + outline: none; + } + + &:focus { + box-shadow: none; + outline: none; + } + } } .settings-control { @@ -708,3 +722,14 @@ } } // End: Credential Reuse Modal + +.tools_page_classifai { + + .settings-info-modal { + + ul { + list-style: unset; + padding: revert; + } + } +}