Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
2 changes: 1 addition & 1 deletion composer.json
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,6 @@
"format": "Automatically fix coding standards issues",
"lint": "Check coding standards compliance",
"test": "Run PHPUnit tests",
"stan": "Run static analysis with PHPStan"
"phpstan": "Run static analysis with PHPStan"
}
}
155 changes: 155 additions & 0 deletions docs/TESTING_REST_API.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
# Testing REST API Endpoints

This document provides instructions for testing the AI plugin's REST API endpoints using curl.

## Prerequisites

- WordPress site running (e.g., `https://wordpress-ai.test`)
- Administrator account credentials
- curl installed

## Authentication Methods

### Method 1: Application Password (Recommended)

1. **Create an Application Password:**
- Log into WordPress admin
- Go to Users → Your Profile
- Scroll to "Application Passwords"
- Create a new application password (name it "Testing" or similar)
- Copy the generated password (you'll only see it once)

2. **Make the request:**

```bash
curl -X POST "https://wordpress-ai.test/wp-json/wp-abilities/v1/abilities/ai/excerpt-generation/run" \
-u "your-username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"content": "This is a sample article about artificial intelligence and machine learning. AI has revolutionized many industries including healthcare, finance, and transportation. Machine learning algorithms can now process vast amounts of data to identify patterns and make predictions that were previously impossible."
}
}'
```

### Method 2: Cookie Authentication with Nonce

1. **Get a nonce:**
- Log into WordPress admin in your browser
- Open browser DevTools → Console
- Run: `wpApiSettings.nonce` (if available)

2. **Get your session cookie:**
- Open DevTools → Application/Storage → Cookies
- Copy the `wordpress_logged_in_[hash]` cookie value

3. **Make the request:**

```bash
curl -X POST "https://wordpress-ai.test/wp-json/wp-abilities/v1/abilities/ai/excerpt-generation/run" \
-H "Content-Type: application/json" \
-H "X-WP-Nonce: YOUR_NONCE_HERE" \
-H "Cookie: wordpress_logged_in_[hash]=YOUR_COOKIE_VALUE_HERE" \
-d '{
"input": {
"content": "This is a sample article about artificial intelligence and machine learning. AI has revolutionized many industries including healthcare, finance, and transportation. Machine learning algorithms can now process vast amounts of data to identify patterns and make predictions that were previously impossible."
}
}'
```

## Example: Excerpt Generation

**Endpoint:** `POST /wp-json/wp-abilities/v1/abilities/ai/excerpt-generation/run`

**Request Body:**
```json
{
"input": {
"content": "This is a sample article about artificial intelligence and machine learning. AI has revolutionized many industries including healthcare, finance, and transportation. Machine learning algorithms can now process vast amounts of data to identify patterns and make predictions that were previously impossible."
}
}
```

**Full curl command (using Application Password):**
```bash
curl -X POST "https://wordpress-ai.test/wp-json/wp-abilities/v1/abilities/ai/excerpt-generation/run" \
-u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{
"input": {
"content": "This is a sample article about artificial intelligence and machine learning. AI has revolutionized many industries including healthcare, finance, and transportation. Machine learning algorithms can now process vast amounts of data to identify patterns and make predictions that were previously impossible."
}
}'
```

**Expected Response:**
```json
{
"excerpts": [
"This article explores how artificial intelligence and machine learning have transformed industries like healthcare, finance, and transportation. These technologies enable algorithms to analyze massive datasets, uncovering patterns and generating predictions that were once beyond human capability.",
"...",
"..."
]
}
```

## Example: Title Generation

**Endpoint:** `POST /wp-json/wp-abilities/v1/abilities/ai/title-generation/run`

**Request Body:**
```json
{
"input": {
"content": "Your article content here...",
"candidates": 3
}
}
```

**Full curl command:**
```bash
curl -X POST "https://wordpress-ai.test/wp-json/wp-abilities/v1/abilities/ai/title-generation/run" \
-u "admin:xxxx xxxx xxxx xxxx xxxx xxxx" \
-H "Content-Type: application/json" \
-d '{
"input": {
"content": "Your article content here...",
"candidates": 3
}
}'
```

## Common Errors

### 401 Unauthorized
- **Cause:** Invalid or missing authentication
- **Solution:** Verify your Application Password or nonce/cookie are correct

### 405 Method Not Allowed
- **Cause:** Using GET instead of POST
- **Solution:** Ensure you're using `-X POST` in curl

### 400 Bad Request
- **Cause:** Invalid input format
- **Solution:** Ensure the request body has `"input"` as the top-level key, and the JSON is valid

### 404 Not Found
- **Cause:** Ability not registered
- **Solution:** Ensure the plugin is activated and the ability is registered

## Testing with Postman

1. **Create a new POST request**
2. **URL:** `https://wordpress-ai.test/wp-json/wp-abilities/v1/abilities/ai/excerpt-generation/run`
3. **Authorization:**
- Type: Basic Auth
- Username: Your WordPress username
- Password: Application Password (not your regular password)
4. **Headers:**
- `Content-Type: application/json`
5. **Body:**
- Select "raw" and "JSON"
- Paste the JSON with `input` wrapper


226 changes: 226 additions & 0 deletions includes/Abilities/Excerpt_Generation/Excerpt_Generation.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
<?php
/**
* Excerpt generation WordPress Ability implementation.
*
* @package WordPress\AI
*/

declare( strict_types=1 );

namespace WordPress\AI\Abilities\Excerpt_Generation;

use WP_Error;
use WordPress\AI\Abstracts\Abstract_Ability;
use WordPress\AI_Client\AI_Client;

use function WordPress\AI\get_post_context;
use function WordPress\AI\get_preferred_models;
use function WordPress\AI\normalize_content;

/**
* Excerpt generation WordPress Ability.
*
* @since x.x.x
*/
class Excerpt_Generation extends Abstract_Ability {

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function input_schema(): array {
return array(
'type' => 'object',
'properties' => array(
'content' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Content to generate excerpt suggestions for.', 'ai' ),
),
'post_id' => array(
'type' => 'integer',
'sanitize_callback' => 'absint',
'description' => esc_html__( 'Content from this post will be used to generate excerpt suggestions. This overrides the content parameter if both are provided.', 'ai' ),
),
),
);
}

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function output_schema(): array {
return array(
'type' => 'string',
'description' => esc_html__( 'Generated excerpt.', 'ai' ),
);
}

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function execute_callback( $input ) {
// Default arguments.
$args = wp_parse_args(
$input,
array(
'content' => null,
'post_id' => null,
),
);

// If a post ID is provided, ensure the post exists before using its' content.
if ( $args['post_id'] ) {
$post = get_post( $args['post_id'] );

if ( ! $post ) {
return new WP_Error(
'post_not_found',
/* translators: %d: Post ID. */
sprintf( esc_html__( 'Post with ID %d not found.', 'ai' ), absint( $args['post_id'] ) )
);
}

// Get the post context.
$context = get_post_context( $args['post_id'] );

// Default to the passed in content if it exists.
if ( $args['content'] ) {
$context['content'] = normalize_content( $args['content'] );
}
} else {
$context = array(
'content' => normalize_content( $args['content'] ?? '' ),
);
}

// If we have no content, return an error.
if ( empty( $context['content'] ) ) {
return new WP_Error(
'content_not_provided',
esc_html__( 'Content is required to generate excerpt suggestions.', 'ai' )
);
}

// Generate the excerpts.
$result = $this->generate_excerpt( $context );

// If we have an error, return it.
if ( is_wp_error( $result ) ) {
return $result;
}

// If we have no results, return an error.
if ( empty( $result ) ) {
return new WP_Error(
'no_results',
esc_html__( 'No excerpt suggestions were generated.', 'ai' )
);
}

// Return the excerpts in the format the Ability expects.
return sanitize_textarea_field( trim( $result, ' "\'' ) );
}

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function permission_callback( $args ) {
$post_id = isset( $args['post_id'] ) ? absint( $args['post_id'] ) : null;

if ( $post_id ) {
$post = get_post( $args['post_id'] );

// Ensure the post exists.
if ( ! $post ) {
return new WP_Error(
'post_not_found',
/* translators: %d: Post ID. */
sprintf( esc_html__( 'Post with ID %d not found.', 'ai' ), absint( $args['post_id'] ) )
);
}

// Ensure the user has permission to read this particular post.
if ( ! current_user_can( 'read_post', $post_id ) ) {
return new WP_Error(
'insufficient_capabilities',
esc_html__( 'You do not have permission to generate excerpts for this post.', 'ai' )
);
}

// Ensure the post type is allowed in REST endpoints.
$post_type = get_post_type( $post_id );

if ( ! $post_type ) {
return false;
}

$post_type_obj = get_post_type_object( $post_type );

if ( ! $post_type_obj || empty( $post_type_obj->show_in_rest ) ) {
return false;
}
} elseif ( ! current_user_can( 'edit_posts' ) ) {
// Ensure the user has permission to edit posts in general.
return new WP_Error(
'insufficient_capabilities',
esc_html__( 'You do not have permission to generate excerpts.', 'ai' )
);
}

return true;
}

/**
* {@inheritDoc}
*
* @since x.x.x
*/
protected function meta(): array {
return array(
'show_in_rest' => true,
);
}

/**
* Generate an excerpt suggestion from the given content.
*
* @since x.x.x
*
* @param string|array<string, string> $context The context to generate an excerpt from.
* @return string|\WP_Error The generated excerpt, or a WP_Error if there was an error.
*/
protected function generate_excerpt( $context ) {
// Convert the context to a string if it's an array.
if ( is_array( $context ) ) {
$context = implode(
"\n",
array_map(
static function ( $key, $value ) {
return sprintf(
'%s: %s',
ucwords( str_replace( '_', ' ', $key ) ),
$value
);
},
array_keys( $context ),
$context
)
);
}

// Generate the excerpts using the AI client.
return AI_Client::prompt_with_wp_error( '"""' . $context . '"""' )
->using_system_instruction( $this->get_system_instruction() )
->using_temperature( 0.7 )
->using_model_preference( ...get_preferred_models() )
->generate_text();
}
}
Loading
Loading