Skip to content
Closed
Show file tree
Hide file tree
Changes from 6 commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
de84150
Add the Abilities API plugin via composer
dkotter Sep 9, 2025
20116f6
Add a standardized way to register an ability in the base Feature cla…
dkotter Sep 9, 2025
1e7b926
First attempt at register an ability in the Title Generation Feature
dkotter Sep 9, 2025
cfb9227
Add a proper execute callback. Change the output schema to support WP…
dkotter Sep 10, 2025
75df803
Add a proper permissions callback. Move the block editor title genera…
dkotter Sep 10, 2025
2ac28f4
Move the classic editor implementation of title generation over to th…
dkotter Sep 10, 2025
e770861
Ignore PHPStan error about the wp_register_ability function
dkotter Sep 10, 2025
7d9bd24
Make the ID param required, as we check that in our permission callback
dkotter Sep 15, 2025
762e0d5
Merge branch 'develop' into try/abilities-api
dkotter Sep 15, 2025
3d69326
Ensure the content we return matches the output schema we provide, ot…
dkotter Sep 16, 2025
7291769
Register Image Generation as an ability
dkotter Sep 25, 2025
9fc21ec
Modify Image Providers to support new response structure
dkotter Sep 25, 2025
7dbc05d
Change image generation in the media library to use the new abilities…
dkotter Sep 25, 2025
631fa7c
Ensure image generation works in the media inserter still
dkotter Sep 25, 2025
3aa5fcc
Add a filter around the input schema, allowing each Provider the abil…
dkotter Sep 25, 2025
a32dc8b
Remove unused import
dkotter Sep 25, 2025
1b55ed8
Ensure our schema isn't nested
dkotter Sep 25, 2025
c677596
Use proper argument name
dkotter Sep 25, 2025
42c01d6
Modify image generation a bit so if we want URLs, instead of base64, …
dkotter Sep 26, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,8 @@
"php": ">=7.4",
"yahnis-elsts/plugin-update-checker": "5.1",
"aws/aws-sdk-php": "^3.300",
"woocommerce/action-scheduler": "3.8.1"
"woocommerce/action-scheduler": "3.8.1",
"wordpress/abilities-api": "^0.1"
},
"autoload": {
"psr-4": {
Expand Down
76 changes: 75 additions & 1 deletion composer.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

23 changes: 23 additions & 0 deletions includes/Classifai/Features/Feature.php
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,10 @@ public function setup() {
if ( $this->is_feature_enabled() ) {
$this->feature_setup();
}

if ( $this->is_enabled() ) {
add_action( 'abilities_api_init', [ $this, 'abilities_api_init' ] );
}
}

/**
Expand All @@ -84,6 +88,25 @@ public function setup() {
public function feature_setup() {
}

/**
* Register an ability after the abilities API is initialized.
*
* Only fires if the Feature is enabled and configured.
*/
public function abilities_api_init() {
if ( function_exists( 'wp_register_ability' ) ) {
$this->register_ability();

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've set this up to match what we already do in this class but wondering if it would be better to have a method_exists check here and remove the empty method below?

}
}

/**
* Register the ability for the Feature.
*
* Override this method in the Feature to register an ability.
*/
public function register_ability() {
}

/**
* Assigns user roles to the $roles array.
*/
Expand Down
114 changes: 113 additions & 1 deletion includes/Classifai/Features/TitleGeneration.php
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,58 @@
add_action( 'edit_form_before_permalink', [ $this, 'register_generated_titles_template' ] );
}

/**
* Register the ability for the Feature.
*/
public function register_ability() {
wp_register_ability(

Check failure on line 102 in includes/Classifai/Features/TitleGeneration.php

View workflow job for this annotation

GitHub Actions / PHPStan

Function wp_register_ability not found.
'classifai/generate-title',
[
'label' => esc_html__( 'Generate title suggestions', 'classifai' ),
'description' => esc_html__( 'Use AI to generate title suggestions based on content. This can be content that is passed in or content that is pulled from a post via the post ID. Will return an array of title suggestions.', 'classifai' ),
'input_schema' => [
'type' => 'object',
'properties' => [
'content' => [
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Content to generate a title for.', 'classifai' ),
],
'id' => [
'type' => 'integer',
'sanitize_callback' => 'absint',
'description' => esc_html__( 'Post ID to generate a title for.', 'classifai' ),
],
'n' => [
'type' => 'integer',
'minimum' => 1,
'maximum' => 10,
'sanitize_callback' => 'absint',
'description' => esc_html__( 'Number of titles to generate', 'classifai' ),
],
],
],
'output_schema' => [
'type' => 'object',

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my testing, seems we need to return an object in order to support any WP_Error's that may return. This does mean we have to change the structure of what we return to be more than just a simple array unfortunately

'properties' => [
'titles' => [
'type' => 'array',
'items' => [
'type' => 'string',
'description' => esc_html__( 'Title suggestion', 'classifai' ),
],
],
],
],
'execute_callback' => [ $this, 'abilities_api_callback' ],
'permission_callback' => [ $this, 'abilities_api_permissions_check' ],
'meta' => [
'type' => 'tool',
],
]
);
}

/**
* Register any needed endpoints.
*/
Expand Down Expand Up @@ -202,6 +254,66 @@
return parent::rest_endpoint_callback( $request );
}

/**
* Check if a given request has access to generate a title.
*
* TODO: If we want to keep our custom REST endpoint,
* we can look to merge these permission callbacks together
* to avoid duplicating code.
*
* @param array $input The input array.
* @return bool|WP_Error
*/
public function abilities_api_permissions_check( array $input ) {
$post_id = $input['id'] ?? null;

// Ensure we have a logged in user that can edit the item.
if ( empty( $post_id ) || ! current_user_can( 'edit_post', $post_id ) ) {
return false;
}

$post_type = get_post_type( $post_id );
$post_type_obj = get_post_type_object( $post_type );

// Ensure the post type is allowed in REST endpoints.
if ( ! $post_type || empty( $post_type_obj ) || empty( $post_type_obj->show_in_rest ) ) {
return false;
}

// Ensure the feature is enabled. Also runs a user check.
if ( ! $this->is_feature_enabled() ) {
return new WP_Error( 'not_enabled', esc_html__( 'Title generation not currently enabled.', 'classifai' ) );
}

return true;
}

/**
* Request handler for the abilities API.
*
* @param array $input The input array.
* @return \WP_REST_Response
*/
public function abilities_api_callback( array $input ) {
$args = wp_parse_args(
$input,
[
'content' => null,
'id' => null,
'n' => null,
]
);

return $this->run(
$args['id'],
'title',
[
'num' => $args['n'],
'content' => $args['content'],
]
);
}

/**
* Enqueue the editor scripts.
*/
Expand Down Expand Up @@ -299,7 +411,7 @@
'enabledFeatures' => [
0 => [
'feature' => 'title',
'path' => '/classifai/v1/generate-title/',
'path' => '/wp/v2/abilities/classifai/generate-title/run/',
'buttonText' => __( 'Generate titles', 'classifai' ),
'modalTitle' => __( 'Select a title', 'classifai' ),
'selectBtnText' => __( 'Select', 'classifai' ),
Expand Down
6 changes: 3 additions & 3 deletions src/js/features/title-generation/classic/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -100,10 +100,10 @@ const scriptData = classifaiChatGPTData.enabledFeatures.reduce(
spinnerEl.show();
isProcessing = true;

const path = scriptData.title?.path + postId;

apiFetch( {
path,
path: scriptData.title?.path,
method: 'POST',
data: { input: { id: postId } },
} )
.then( ( result ) => {
generateTextEl.css( 'opacity', '1' );
Expand Down
2 changes: 1 addition & 1 deletion src/js/features/title-generation/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ const TitleGenerationPlugin = () => {
apiFetch( {
path,
method: 'POST',
data: { id: postId, content: postContent },
data: { input: { id: postId, content: postContent } },
} ).then(
async ( res ) => {
// Support calling a function from the response for browser AI.
Expand Down
Loading