Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
70 commits
Select commit Hold shift + click to select a range
6191a6c
Enqueue the admin script
dkotter Dec 5, 2025
3ea858b
Add the initial filtering of the featured image component
dkotter Dec 5, 2025
eb1a9f6
Add the Generate Featured Image component
dkotter Dec 5, 2025
700b451
Use apiFetch instead of the Abilities API as we're likely to remove t…
dkotter Dec 5, 2025
b2cc9c1
Continue work on the generate component, matching what core does more…
dkotter Dec 5, 2025
4ae3f31
Add function to upload an image after it is generated so we have some…
dkotter Dec 5, 2025
7d0af2e
After the image is generated, store that as post meta and use that wh…
dkotter Dec 8, 2025
58e7c1d
Remove image when the remove button is clicked
dkotter Dec 8, 2025
c19454f
Move some functions to their own files
dkotter Dec 9, 2025
bf56c05
Rename component file. Update TODO
dkotter Dec 9, 2025
5a88935
Merge branch 'develop' into feature/image-generation-ui
dkotter Dec 12, 2025
64c743a
Use wp_delete_file instead of unlink
dkotter Dec 12, 2025
9f6ea3e
Automatically set the generated image as the featured image
dkotter Dec 12, 2025
a48de5e
Add the ability to pass in custom meta fields when importing an image…
dkotter Dec 12, 2025
f07d6e8
Set AI meta when the image is imported. Add a new component to render…
dkotter Dec 12, 2025
bf276e6
Remove code that set AI post meta as we now set that on the attachment
dkotter Dec 12, 2025
7c434ce
Show generate button even if we have a featured image set but change …
dkotter Dec 16, 2025
1975833
Add a general prompt creation ability that can be used to generate LL…
dkotter Dec 16, 2025
dca0912
When featured image generation is triggered, first get additional con…
dkotter Dec 16, 2025
e01934f
Increase the timeout value when generating an image. Remove test code
dkotter Dec 16, 2025
292cc69
Add tests for the prompt ability
dkotter Dec 16, 2025
92b3e56
Fix PHPCS error
dkotter Dec 16, 2025
7644204
Add support for the new gpt-image-1.5 model
dkotter Dec 16, 2025
a5db5fc
Ensure files use proper extensions
dkotter Dec 18, 2025
a56a6f3
Merge branch 'develop' into feature/image-generation-ui
dkotter Dec 18, 2025
731a6b1
Merge branch 'develop' into feature/image-generation-ui
dkotter Dec 22, 2025
460f529
Change how we increase the default request timeout
dkotter Dec 22, 2025
6f77dd3
Merge branch 'develop' into feature/image-generation-ui
dkotter Dec 23, 2025
6a2cc8d
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 2, 2026
c0a96a4
Add docs
dkotter Jan 2, 2026
78d141d
Add E2E tests
dkotter Jan 2, 2026
d9421eb
Merge branch 'develop' into feature/image-generation-ui
jeffpaul Jan 7, 2026
adb669c
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 8, 2026
e2c1dfe
Remove the generic Prompt Ability and instead register it as a specif…
dkotter Jan 8, 2026
03e8580
Add the system instructions for the prompt generation. Update the abi…
dkotter Jan 8, 2026
a5cb760
In our content normalization function, remove extra linebreaks so we …
dkotter Jan 8, 2026
6b31fae
Update our UI components to take advantage of the new prompt generati…
dkotter Jan 8, 2026
3c0db53
Update docs
dkotter Jan 8, 2026
8ff538b
Update tests
dkotter Jan 8, 2026
76d37bc
Fix PHPCS, PHPStan and Plugin Check errors
dkotter Jan 8, 2026
8bc914e
Merge branch 'develop' into feature/image-generation-ui
jeffpaul Jan 12, 2026
9f3f1b3
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 13, 2026
da5f8ce
Ensure missing dependency is in package.json
dkotter Jan 13, 2026
0ce9873
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 13, 2026
12aa540
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 14, 2026
21fb91c
Fix typescript errors
dkotter Jan 14, 2026
6f4e3fa
Merge branch 'develop' into feature/image-generation-ui
dkotter Jan 26, 2026
8aeca86
Use new function name
dkotter Jan 26, 2026
7459621
Merge branch 'develop' into feature/image-generation-ui
dkotter Feb 4, 2026
e2b7403
Add better capability checks on the image prompt generation Ability
dkotter Feb 4, 2026
b79aefb
Remove duplicate line; update system instructions a bit; update Exper…
dkotter Feb 4, 2026
c7f2d5c
Fix PHPStan error
dkotter Feb 4, 2026
5c41311
Fix unit tests
dkotter Feb 4, 2026
235fb96
Modify the generate image Ability so it will return not only the gene…
dkotter Feb 5, 2026
7ab0f22
Update the image description we use to take into account the model an…
dkotter Feb 5, 2026
a03157e
Merge branch 'develop' into feature/image-generation-ui
dkotter Feb 5, 2026
3de916b
Move the alt text generation function into a utility file so it can b…
dkotter Feb 5, 2026
f315328
If alt text generation is turned on, after generating the featured im…
dkotter Feb 5, 2026
056928c
Merge branch 'develop' into feature/image-generation-ui
dkotter Feb 5, 2026
2589ce6
Change the upload image function to use the new runAbility helper
dkotter Feb 6, 2026
3107949
Change the generate image function to use the new runAbility helper
dkotter Feb 6, 2026
d5d7314
Change the generate image prompt function to use the new runAbility h…
dkotter Feb 6, 2026
6533439
Extract out our types to better manage those
dkotter Feb 6, 2026
b06c238
Add a progress message below the generate button that shows what step…
dkotter Feb 6, 2026
e31cf88
Update docs
dkotter Feb 6, 2026
658511b
Remove attributes that aren't needed
dkotter Feb 6, 2026
c0b0714
Add utility function to trim text to a certain length. Use this to se…
dkotter Feb 6, 2026
4cd9d6c
Merge branch 'develop' into feature/image-generation-ui
dkotter Feb 6, 2026
b4903e5
Fix lint errors
dkotter Feb 6, 2026
04c2df4
Fix tests
dkotter Feb 6, 2026
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
54 changes: 23 additions & 31 deletions docs/experiments/image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

## Summary

The Image Generation experiment adds AI-powered featured image generation to the WordPress post editor. It provides a "Generate featured image" button in the featured image panel that uses AI to create images based on post content. The experiment registers two WordPress Abilities (`ai/image-generation` and `ai/image-import`) that can be used both through the admin UI and directly via REST API requests.
The Image Generation experiment adds AI-powered featured image generation to the WordPress post editor. It provides a "Generate featured image" button in the featured image panel that uses AI to create images based on post content. The experiment registers three WordPress Abilities (`ai/image-generation`, `ai/image-import`, `ai/image-prompt-generation`) that can be used both through the admin UI and directly via REST API requests.

## Overview

Expand All @@ -13,9 +13,11 @@ When enabled, the Image Generation experiment adds a "Generate featured image" b
**Key Features:**

- One-click featured image generation from post content
- Step-by-step progress messages during generation (e.g. "Generating image prompt", "Generating image", "Generating alt text", "Importing image")
- Automatically imports generated images into the media library
- Sets generated images as featured images
- Uses AI to create an image generation prompt from post context
- Optional AI-generated alt text when the Alt Text Generation experiment is enabled
- Works with any post type that supports featured images
- Visual indicator for AI-generated images

Expand Down Expand Up @@ -44,27 +46,26 @@ All three abilities can be called directly via REST API, making them useful for
1. **PHP Side:**
- `enqueue_assets()` loads `experiments/image-generation` (`src/experiments/image-generation/index.ts`) and localizes `window.aiImageGenerationData` with:
- `enabled`: Whether the experiment is enabled
- `generateImagePath`: REST API path to image generation ability (`wp-abilities/v1/abilities/ai/image-generation/run`)
- `importPath`: REST API path to image import ability (`wp-abilities/v1/abilities/ai/image-import/run`)
- `getContextPath`: REST API path to get post details (`wp-abilities/v1/abilities/ai/get-post-details/run`)
- `generatePromptPath`: REST API path to image prompt generation ability (`wp-abilities/v1/abilities/ai/image-prompt-generation/run`)
- `altTextEnabled`: Whether the alt text generation experiment is enabled

2. **React Side:**
- The React entry point (`featured-image.tsx`) hooks into the featured image panel using the `editor.PostFeaturedImage` filter
- `GenerateFeaturedImage` component renders a button that:
- `GenerateFeaturedImage` component renders a button and progress UI that:
- Gets current post ID and content from the editor store
- Calls `generateImage()` function which:
- Gets post context (title, type) via `getContext()`
- Tracks `progressMessage` state and passes an `onProgress` callback to `generateImage()` and `uploadImage()`
- Calls `generateImage( postId, content, { onProgress } )`, which:
- Gets post context (title, type) via `getContext()` (uses `ai/get-post-details` ability)
- Formats context using `formatContext()`
- Calls `generatePrompt()` to create an image generation prompt from content and context
- Calls the image generation ability with the generated prompt
- Returns base64-encoded image data
- Calls `uploadImage()` function which:
- Calls the image import ability with the base64 data
- Sets `ai_generated` meta to mark the image
- Invokes `onProgress( 'Generating image prompt' )`, then calls `generatePrompt()` to create an image generation prompt from content and context
- Invokes `onProgress( 'Generating image' )`, then calls the `ai/image-generation` ability with the generated prompt
- Returns generated image data (base64 data, prompt, provider/model metadata)
- Calls `uploadImage( imageData, { onProgress } )`, which:
- If the Alt Text Generation experiment is enabled (`aiImageGenerationData.altTextEnabled`): invokes `onProgress( 'Generating alt text' )`, then calls `generateAltText()` and uses the result as `alt_text`; otherwise uses the prompt as fallback alt text
- Invokes `onProgress( 'Importing image' )`, then calls the `ai/image-import` ability with base64 data, metadata, and `ai_generated` meta
- Returns attachment data (id, url, title)
- Updates the editor store to set the imported image as featured image
- Handles loading states and error notifications
- Shows a loading state on the button and a progress message (with spinner) under the button while generating; clears both on success or error
- Handles error notifications via the notices store
- `AILabel` component displays a label for AI-generated images by checking the `ai_generated` meta

3. **Ability Execution Flow:**
Expand Down Expand Up @@ -657,7 +658,9 @@ You can customize what metadata is saved when importing images by modifying the
src/experiments/image-generation/functions/upload-image.ts
```

Or by filtering the input before calling the import ability via REST API.
`uploadImage( imageData, options? )` accepts generated image data and an optional `options` object with `onProgress?: ( message: string ) => void` for progress callbacks. When the Alt Text Generation experiment is enabled, it generates alt text via `generateAltText()` before importing; otherwise it uses the image prompt as alt text.

You can also filter the input before calling the import ability via REST API.

### Customizing Post Context

Expand All @@ -677,8 +680,9 @@ src/experiments/image-generation/functions/format-context.ts

You can extend the React components to add custom UI elements:

1. **Modify the generate button component:**
1. **Modify the generate button and progress UI:**
- Edit `src/experiments/image-generation/components/GenerateFeaturedImage.tsx`
- The component renders a button and, while generating, a progress container (`.ai-featured-image__progress`) that displays the current step and a spinner; progress is driven by the `onProgress` callbacks passed to `generateImage()` and `uploadImage()`

2. **Customize the AI label:**
- Edit `src/experiments/image-generation/components/AILabel.tsx`
Expand Down Expand Up @@ -722,6 +726,7 @@ add_filter( 'wp_generate_attachment_metadata', function( $metadata, $attachment_
- Create or edit a post with content
- Scroll to the featured image panel
- Click the "Generate featured image" button
- Verify progress messages appear in order: "Generating image prompt", "Generating image", then "Generating alt text" (if Alt Text experiment is enabled), then "Importing image"
- Verify the image is generated, imported, and set as featured image
- Verify the "AI Generated Featured Image" label appears
- Click "Generate new featured image" to test regeneration
Expand Down Expand Up @@ -763,7 +768,7 @@ npm run test:php
### Performance

- Image generation is an AI operation and may take 30-90 seconds (timeout is set to 90 seconds)
- The UI shows a loading state while generation is in progress
- The UI shows a loading state on the button and step-by-step progress messages below it ("Generating image prompt" → "Generating image" → "Generating alt text" (if enabled) → "Importing image") so users know which step is running
- Base64 image data can be large; ensure adequate memory and request timeout settings
- Consider implementing caching for frequently accessed images if generating images in bulk

Expand Down Expand Up @@ -819,16 +824,3 @@ npm run test:php
- Temporary files are properly cleaned up after import
- User permissions are checked before allowing image generation or import
- All input is sanitized using WordPress sanitization functions

## Related Files

- **Experiment:** `includes/Experiments/Image_Generation/Image_Generation.php`
- **Generate Image Prompt Ability:** `includes/Abilities/Image/Generate_Image_Prompt.php`
- **Generate Image Prompt System Instruction:** `includes/Abilities/Image/image-prompt-system-instruction.php`
- **Generate Image Ability:** `includes/Abilities/Image/Generate_Image.php`
- **Import Image Ability:** `includes/Abilities/Image/Import_Base64_Image.php`
- **React Entry:** `src/experiments/image-generation/featured-image.tsx`
- **React Components:** `src/experiments/image-generation/components/`
- **React Functions:** `src/experiments/image-generation/functions/`
- **Tests:** `tests/Integration/Includes/Abilities/Image_GenerationTest.php`
- **Tests:** `tests/Integration/Includes/Experiments/Image_Generation/Image_GenerationTest.php`
98 changes: 86 additions & 12 deletions includes/Abilities/Image/Generate_Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,13 @@

namespace WordPress\AI\Abilities\Image;

use Throwable;
use WP_Error;
use WordPress\AI\Abstracts\Abstract_Ability;
use WordPress\AI_Client\AI_Client;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Models\DTO\ModelMetadata;

use function WordPress\AI\get_preferred_image_models;

Expand Down Expand Up @@ -49,8 +52,51 @@ protected function input_schema(): array {
*/
protected function output_schema(): array {
return array(
'type' => 'string',
'description' => esc_html__( 'The base64 encoded image data.', 'ai' ),
'type' => 'object',
'properties' => array(
'image' => array(
'type' => 'object',
'description' => esc_html__( 'Generated image data.', 'ai' ),
'properties' => array(
'data' => array(
'type' => 'string',
'description' => esc_html__( 'The base64 encoded image data.', 'ai' ),
),
'provider_metadata' => array(
'type' => 'object',
'description' => esc_html__( 'Information about the provider that generated the image.', 'ai' ),
'properties' => array(
'id' => array(
'type' => 'string',
'description' => esc_html__( 'The provider ID.', 'ai' ),
),
'name' => array(
'type' => 'string',
'description' => esc_html__( 'The provider name.', 'ai' ),
),
'type' => array(
'type' => 'string',
'description' => esc_html__( 'The provider type.', 'ai' ),
),
),
),
'model_metadata' => array(
'type' => 'object',
'description' => esc_html__( 'Information about the model that generated the image.', 'ai' ),
'properties' => array(
'id' => array(
'type' => 'string',
'description' => esc_html__( 'The model ID.', 'ai' ),
),
'name' => array(
'type' => 'string',
'description' => esc_html__( 'The model name.', 'ai' ),
),
),
),
),
),
),
);
}

Expand All @@ -77,7 +123,9 @@ protected function execute_callback( $input ) {
}

// Return the image data in the format the Ability expects.
return sanitize_text_field( trim( $result ) );
return array(
'image' => $result,
);
}

/**
Expand Down Expand Up @@ -114,27 +162,53 @@ protected function meta(): array {
* @since 0.2.0
*
* @param string $prompt The prompt to generate an image from.
* @return string|\WP_Error The generated image data, or a WP_Error if there was an error.
* @return array{data: string, provider_metadata: array<string, string>, model_metadata: array<string, string>}|\WP_Error The generated image data, provider metadata, and model metadata, or a WP_Error if there was an error.
*/
protected function generate_image( string $prompt ) { // phpcs:ignore Generic.NamingConventions.ConstructorName.OldStyle
// Generate the image using the AI client.
$file = AI_Client::prompt_with_wp_error( $prompt )
$result = AI_Client::prompt_with_wp_error( $prompt )
->as_output_file_type( FileTypeEnum::inline() )
->using_model_preference( ...get_preferred_image_models() )
->generate_image();
->generate_image_result();

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

// Return the base64 encoded image data.
$data = $file->getBase64Data();
$data = array(
'data' => '',
'provider_metadata' => array(),
'model_metadata' => array(),
);

if ( empty( $data ) ) {
try {
// Get the File from the result.
$file = $result->toImageFile();

// Return the base64 encoded image data.
$data['data'] = sanitize_text_field( trim( $file->getBase64Data() ?? '' ) );

if ( empty( $data['data'] ) ) {
return new WP_Error(
'no_image_data',
esc_html__( 'No image data was generated.', 'ai' )
);
}

// Get details about the provider and model that generated the image.
$data['provider_metadata'] = $result->getProviderMetadata()->toArray();
$data['model_metadata'] = $result->getModelMetadata()->toArray();

// Remove data we don't care about.
unset( $data['provider_metadata'][ ProviderMetadata::KEY_CREDENTIALS_URL ] );
unset( $data['model_metadata'][ ModelMetadata::KEY_SUPPORTED_OPTIONS ] );
unset( $data['model_metadata'][ ModelMetadata::KEY_SUPPORTED_CAPABILITIES ] );
} catch ( Throwable $t ) {
return new WP_Error(
'no_image_data',
esc_html__( 'No image data was generated.', 'ai' )
esc_html__( 'No image data was generated.', 'ai' ),
$t
);
}

Expand Down
Loading
Loading