-
Notifications
You must be signed in to change notification settings - Fork 52
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Add Chrome AI as a Provider for our text generation Features #819
Open
dkotter
wants to merge
7
commits into
develop
Choose a base branch
from
feature/chrome-ai
base: develop
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
7 commits
Select commit
Hold shift + click to select a range
81be83e
Initial implementation of the Chrome AI API with the excerpt generati…
dkotter 3420cda
Add support for generating titles using Chrome AI
dkotter 6e579c9
Add support for content resizing using Chrome AI
dkotter 9b97859
Move our Chrome AI JS code into a JS file instead of rendering that i…
dkotter 929a15d
Ensure the AI method we need is available so an error isn't thrown
dkotter cd7514c
Merge branch 'develop' into feature/chrome-ai
dkotter 3457370
Fix title generation
dkotter File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,339 @@ | ||
<?php | ||
/** | ||
* Chrome AI integration | ||
*/ | ||
|
||
namespace Classifai\Providers\Browser; | ||
|
||
use Classifai\Features\ContentResizing; | ||
use Classifai\Features\ExcerptGeneration; | ||
use Classifai\Features\TitleGeneration; | ||
use Classifai\Providers\Provider; | ||
use Classifai\Normalizer; | ||
use WP_Error; | ||
|
||
use function Classifai\get_default_prompt; | ||
|
||
class ChromeAI extends Provider { | ||
|
||
/** | ||
* Provider ID | ||
* | ||
* @var string | ||
*/ | ||
const ID = 'chrome_ai'; | ||
|
||
/** | ||
* ChromeAI constructor. | ||
* | ||
* @param \Classifai\Features\Feature $feature_instance The feature instance. | ||
*/ | ||
public function __construct( $feature_instance = null ) { | ||
$this->feature_instance = $feature_instance; | ||
} | ||
|
||
/** | ||
* Render the provider fields. | ||
*/ | ||
public function render_provider_fields() { | ||
do_action( 'classifai_' . static::ID . '_render_provider_fields', $this ); | ||
} | ||
|
||
/** | ||
* Returns the default settings for this provider. | ||
* | ||
* @return array | ||
*/ | ||
public function get_default_provider_settings(): array { | ||
$common_settings = [ | ||
'authenticated' => true, | ||
]; | ||
|
||
return $common_settings; | ||
} | ||
|
||
/** | ||
* Sanitize the settings for this provider. | ||
* | ||
* @param array $new_settings The settings array. | ||
* @return array | ||
*/ | ||
public function sanitize_settings( array $new_settings ): array { | ||
return $new_settings; | ||
} | ||
|
||
/** | ||
* Common entry point for all REST endpoints for this provider. | ||
* | ||
* @param int $post_id The Post ID we're processing. | ||
* @param string $route_to_call The route we are processing. | ||
* @param array $args Optional arguments to pass to the route. | ||
* @return string|WP_Error | ||
*/ | ||
public function rest_endpoint_callback( $post_id = 0, string $route_to_call = '', array $args = [] ) { | ||
if ( ! $post_id || ! get_post( $post_id ) ) { | ||
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate titles.', 'classifai' ) ); | ||
} | ||
|
||
$route_to_call = strtolower( $route_to_call ); | ||
$return = ''; | ||
|
||
// Handle all of our routes. | ||
switch ( $route_to_call ) { | ||
case 'excerpt': | ||
$return = $this->generate_excerpt( $post_id, $args ); | ||
break; | ||
case 'title': | ||
$return = $this->generate_title( $post_id, $args ); | ||
break; | ||
case 'resize_content': | ||
$return = $this->resize_content( $post_id, $args ); | ||
break; | ||
} | ||
|
||
return $return; | ||
} | ||
|
||
/** | ||
* Generate an excerpt. | ||
* | ||
* @param int $post_id The Post ID we're processing | ||
* @param array $args Arguments passed in. | ||
* @return string|WP_Error | ||
*/ | ||
public function generate_excerpt( int $post_id = 0, array $args = [] ) { | ||
if ( ! $post_id || ! get_post( $post_id ) ) { | ||
return new WP_Error( 'post_id_required', esc_html__( 'A valid post ID is required to generate an excerpt.', 'classifai' ) ); | ||
} | ||
|
||
$feature = new ExcerptGeneration(); | ||
$settings = $feature->get_settings(); | ||
$args = wp_parse_args( | ||
array_filter( $args ), | ||
[ | ||
'content' => '', | ||
'title' => get_the_title( $post_id ), | ||
] | ||
); | ||
|
||
// 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 ) || ! $feature->is_feature_enabled() ) { | ||
return new WP_Error( 'not_enabled', esc_html__( 'Excerpt generation is disabled or authentication failed. Please check your settings.', 'classifai' ) ); | ||
} | ||
|
||
$excerpt_length = absint( $settings['length'] ?? 55 ); | ||
$excerpt_prompt = esc_textarea( get_default_prompt( $settings['generate_excerpt_prompt'] ) ?? $feature->prompt ); | ||
|
||
// Replace our variables in the prompt. | ||
$prompt_search = array( '{{WORDS}}', '{{TITLE}}' ); | ||
$prompt_replace = array( $excerpt_length, $args['title'] ); | ||
$prompt = str_replace( $prompt_search, $prompt_replace, $excerpt_prompt ); | ||
|
||
/** | ||
* Filter the prompt we will send to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_excerpt_prompt | ||
* | ||
* @param {string} $prompt Prompt we are sending. Gets added before post content. | ||
* @param {int} $post_id ID of post we are summarizing. | ||
* @param {int} $excerpt_length Length of final excerpt. | ||
* | ||
* @return {string} Prompt. | ||
*/ | ||
$prompt = apply_filters( 'classifai_chrome_ai_excerpt_prompt', $prompt, $post_id, $excerpt_length ); | ||
|
||
/** | ||
* Filter the request body before sending to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_excerpt_request_body | ||
* | ||
* @param {array} $body Request body that will be sent. | ||
* @param {int} $post_id ID of post we are summarizing. | ||
* | ||
* @return {array} Request body. | ||
*/ | ||
$body = apply_filters( | ||
'classifai_chrome_ai_excerpt_request_body', | ||
[ | ||
'prompt' => 'You will be provided with content delimited by triple quotes. ' . $prompt, | ||
'content' => $this->get_content( $post_id, $excerpt_length, false, $args['content'] ), | ||
'func' => static::ID, | ||
], | ||
$post_id | ||
); | ||
|
||
return $body; | ||
} | ||
|
||
/** | ||
* Generate a title using Chrome AI. | ||
* | ||
* @param int $post_id The Post Id we're processing | ||
* @param array $args Arguments passed in. | ||
* @return string|WP_Error | ||
*/ | ||
public function generate_title( int $post_id = 0, array $args = [] ) { | ||
if ( ! $post_id || ! get_post( $post_id ) ) { | ||
return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to generate titles.', 'classifai' ) ); | ||
} | ||
|
||
$feature = new TitleGeneration(); | ||
$settings = $feature->get_settings(); | ||
$args = wp_parse_args( | ||
array_filter( $args ), | ||
[ | ||
'content' => '', | ||
] | ||
); | ||
|
||
// These checks happen in the REST permission_callback, | ||
// but we run them again here in case this method is called directly. | ||
if ( empty( $settings ) || ! $feature->is_feature_enabled() ) { | ||
return new WP_Error( 'not_enabled', esc_html__( 'Title generation is disabled or authentication failed. Please check your settings.', 'classifai' ) ); | ||
} | ||
|
||
$prompt = esc_textarea( get_default_prompt( $settings['generate_title_prompt'] ) ?? $feature->prompt ); | ||
|
||
/** | ||
* Filter the prompt we will send to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_title_prompt | ||
* | ||
* @param {string} $prompt Prompt we are sending. Gets added before post content. | ||
* @param {int} $post_id ID of post we are summarizing. | ||
* @param {array} $args Arguments passed to endpoint. | ||
* | ||
* @return {string} Prompt. | ||
*/ | ||
$prompt = apply_filters( 'classifai_chrome_ai_title_prompt', $prompt, $post_id, $args ); | ||
|
||
/** | ||
* Filter the request body before sending to Azure OpenAI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_title_request_body | ||
* | ||
* @param {array} $body Request body that will be sent. | ||
* @param {int} $post_id ID of post we are summarizing. | ||
* | ||
* @return {array} Request body. | ||
*/ | ||
$body = apply_filters( | ||
'classifai_chrome_ai_title_request_body', | ||
[ | ||
'prompt' => 'You will be provided with content delimited by triple quotes. ' . $prompt, | ||
'content' => $this->get_content( $post_id, 0, false, $args['content'] ), | ||
'func' => static::ID, | ||
], | ||
$post_id | ||
); | ||
|
||
return $body; | ||
} | ||
|
||
/** | ||
* Resizes content. | ||
* | ||
* @param int $post_id The Post ID we're processing | ||
* @param array $args Arguments passed in. | ||
* @return string|WP_Error | ||
*/ | ||
public function resize_content( int $post_id, array $args = array() ) { | ||
if ( ! $post_id || ! get_post( $post_id ) ) { | ||
return new WP_Error( 'post_id_required', esc_html__( 'Post ID is required to resize content.', 'classifai' ) ); | ||
} | ||
|
||
$feature = new ContentResizing(); | ||
$settings = $feature->get_settings(); | ||
|
||
if ( 'shrink' === $args['resize_type'] ) { | ||
$prompt = esc_textarea( get_default_prompt( $settings['condense_text_prompt'] ) ?? $feature->condense_prompt ); | ||
} else { | ||
$prompt = esc_textarea( get_default_prompt( $settings['expand_text_prompt'] ) ?? $feature->expand_prompt ); | ||
} | ||
|
||
/** | ||
* Filter the resize prompt we will send to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_' . $args['resize_type'] . '_content_prompt | ||
* | ||
* @param {string} $prompt Resize prompt we are sending. Gets added as a system prompt. | ||
* @param {int} $post_id ID of post. | ||
* @param {array} $args Arguments passed to endpoint. | ||
* | ||
* @return {string} Prompt. | ||
*/ | ||
$prompt = apply_filters( 'classifai_chrome_ai_' . $args['resize_type'] . '_content_prompt', $prompt, $post_id, $args ); | ||
|
||
/** | ||
* Filter the resize request body before sending to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_resize_content_request_body | ||
* | ||
* @param {array} $body Request body that will be sent. | ||
* @param {int} $post_id ID of post. | ||
* | ||
* @return {array} Request body. | ||
*/ | ||
$body = apply_filters( | ||
'classifai_chrome_ai_resize_content_request_body', | ||
[ | ||
'prompt' => 'You will be provided with content delimited by triple quotes. ' . $prompt, | ||
'content' => esc_html( $args['content'] ), | ||
'func' => static::ID, | ||
], | ||
$post_id | ||
); | ||
|
||
return $body; | ||
} | ||
|
||
/** | ||
* Get our content. | ||
* | ||
* We don't trim content here as we don't know for sure which model | ||
* someone is using. | ||
* | ||
* @param int $post_id Post ID to get content from. | ||
* @param int $return_length Word length of returned content. | ||
* @param bool $use_title Whether to use the title or not. | ||
* @param string $post_content The post content. | ||
* @return string | ||
*/ | ||
public function get_content( int $post_id = 0, int $return_length = 0, bool $use_title = true, string $post_content = '' ): string { | ||
$normalizer = new Normalizer(); | ||
|
||
if ( empty( $post_content ) ) { | ||
$post = get_post( $post_id ); | ||
$post_content = apply_filters( 'the_content', $post->post_content ); | ||
} | ||
|
||
$post_content = preg_replace( '#\[.+\](.+)\[/.+\]#', '$1', $post_content ); | ||
|
||
// Add the title to the content, if needed, and normalize things. | ||
if ( $use_title ) { | ||
$content = $normalizer->normalize( $post_id, $post_content ); | ||
} else { | ||
$content = $normalizer->normalize_content( $post_content, '', $post_id ); | ||
} | ||
|
||
/** | ||
* Filter content that will get sent to Chrome AI. | ||
* | ||
* @since x.x.x | ||
* @hook classifai_chrome_ai_content | ||
* | ||
* @param {string} $content Content that will be sent. | ||
* @param {int} $post_id ID of post we are summarizing. | ||
* | ||
* @return {string} Content. | ||
*/ | ||
return apply_filters( 'classifai_chrome_ai_content', $content, $post_id ); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Right now this Provider shows up even if your browser doesn't support Chrome AI. Would be great to remove it but doesn't cause any problems if you do select it. With the settings refactor in #502, I think will be much easier to remove this since all settings are registered in javascript at that point, so may leave this until it's integrated in there