Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
d3fe737
Add reference as a property to our image generation input schema. Whe…
dkotter Mar 9, 2026
6a2d2dc
Add back our image refinement flow to the inline image generation exp…
dkotter Mar 9, 2026
dab8c69
Update tests and docs
dkotter Mar 9, 2026
2a65da5
Force images we generate to be PNGs for now
dkotter Mar 9, 2026
a71c00e
Keep track of all the prompts used so we can save all of those with t…
dkotter Mar 10, 2026
104ce80
When refining an image, show the original image next to the refined i…
dkotter Mar 10, 2026
e3aa769
Don't save duplicate prompts when generating another image using the …
dkotter Mar 10, 2026
0b3d069
Fix failing test
dkotter Mar 10, 2026
b81f460
Merge branch 'develop' into feature/image-edits
dkotter Mar 10, 2026
3a5c5d8
Add the refine image flow to our Media Library image generation. Move…
dkotter Mar 10, 2026
9f19c3f
Fix E2E tests
dkotter Mar 10, 2026
ab60a51
Merge branch 'develop' into feature/image-edits
dkotter Mar 10, 2026
91cc87a
Inject our component when we are in the media editing view as there i…
dkotter Mar 11, 2026
8223262
Add our image editing component that adds our built-in preset buttons…
dkotter Mar 11, 2026
b6cb998
Add icons to our buttons to match the other buttons on the edit scree…
dkotter Mar 11, 2026
45f604e
Merge branch 'develop' into feature/image-edits
dkotter Mar 11, 2026
5d3aff4
Merge branch 'develop' into feature/media-library-image-edits
dkotter Mar 11, 2026
26c7388
Add a refine image flow, matching what we have in our image generatio…
dkotter Mar 11, 2026
d2c0da2
Hide the normal image edit controls when the AI editing tools are sho…
dkotter Mar 11, 2026
7fc0e06
Update the background removal prompt to be more robust. Add a functio…
dkotter Mar 11, 2026
0c84047
Remove transparency function for now
dkotter Mar 12, 2026
ad055bd
Merge branch 'develop' into feature/media-library-image-edits
dkotter Mar 12, 2026
0af54b0
Merge branch 'develop' into feature/media-library-image-edits
dkotter Mar 12, 2026
6d1f877
Update the expand image functionality to first expand the canvas of t…
dkotter Mar 12, 2026
cb224f4
Add shared state management so we can track each image generation tha…
dkotter Mar 12, 2026
92bb780
Add the Google Provider in our test environment and mock some of the …
dkotter Mar 12, 2026
c66f88c
Add E2E tests though set them as skipped for now as they will fail un…
dkotter Mar 12, 2026
730fa7b
Fix E2E tests
dkotter Mar 12, 2026
a1c33eb
Fix eslint errors
dkotter Mar 13, 2026
a6bbd89
Merge branch 'develop' into feature/image-edits
dkotter Mar 16, 2026
5f5670b
Remove the hardcoding of PNG output for now as this breaks image edits
dkotter Mar 16, 2026
2fae697
Merge branch 'develop' into feature/image-edits
dkotter Mar 17, 2026
5bf9beb
Update the Experiment name and description
dkotter Mar 17, 2026
2e2e115
Fix unit tests
dkotter Mar 17, 2026
c158c6e
Merge branch 'feature/image-edits' into feature/media-library-image-e…
dkotter Mar 17, 2026
8b07ac7
Merge branch 'develop' into feature/media-library-image-edits
dkotter Mar 18, 2026
524cce0
Remove the AI Edit button and instead show our new buttons by default…
dkotter Mar 18, 2026
0cf177d
Fix E2E tests
dkotter Mar 18, 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
2 changes: 1 addition & 1 deletion .wp-env.json
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
{
"$schema": "https://schemas.wp.org/trunk/wp-env.json",
"core": "WordPress/WordPress#master",
"plugins": [ ".", "https://downloads.wordpress.org/plugin/ai-provider-for-openai.zip" ],
"plugins": [ ".", "https://downloads.wordpress.org/plugin/ai-provider-for-google.zip", "https://downloads.wordpress.org/plugin/ai-provider-for-openai.zip" ],
"env": {
"development": {
"config": {
Expand Down
29 changes: 19 additions & 10 deletions docs/experiments/image-generation.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,13 +11,14 @@ The Image Generation experiment adds AI-powered image generation to the WordPres
When enabled, the Image Generation experiment adds:

- **Featured image panel:** A "Generate featured image" button that creates AI images from post content. The image is imported into the media library and set as the featured image. Images are marked with an "AI Generated Featured Image" label.
- **Block buttons:** A "Generate Image" inline and toolbar button on Image, Cover, Media & Text, and Gallery blocks. Clicking it opens a modal where you describe the image, generate it, preview it, and insert it into the block.
- **Block buttons:** A "Generate Image" inline and toolbar button on Image, Cover, Media & Text, and Gallery blocks. Clicking it opens a modal where you describe the image, generate it, preview it, optionally refine it (edit with follow-up prompts using the current image as reference), and insert it into the block.

**Key Features:**

- One-click featured image generation from post content
- Inline image generation from supported blocks (Image, Cover, Media & Text, Gallery)
- Modal flow for inline generation: describe → generate → preview → keep, or start over
- Modal flow for inline generation: describe → generate → preview → keep, refine, or start over
- Image refinement: use the current generated image as a reference for follow-up prompts (models that support edits use it as context)
- 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 or inserts them into blocks
Expand All @@ -32,7 +33,7 @@ The experiment consists of four main components:

1. **Experiment Class** (`WordPress\AI\Experiments\Image_Generation\Image_Generation`): Handles registration, asset enqueuing, featured image and inline block editor UI integration, and post meta registration
2. **Generate Image Prompt Ability** (`WordPress\AI\Abilities\Image\Generate_Image_Prompt`): Generates optimized image generation prompts from post content and context
3. **Generate Image Ability** (`WordPress\AI\Abilities\Image\Generate_Image`): Generates base64-encoded images from prompts using AI models
3. **Generate Image Ability** (`WordPress\AI\Abilities\Image\Generate_Image`): Generates base64-encoded images from prompts (and optionally from a reference image for refining) using AI models
4. **Import Image Ability** (`WordPress\AI\Abilities\Image\Import_Base64_Image`): Imports base64-encoded images into the WordPress media library

All three abilities can be called directly via REST API, making them useful for automation, bulk processing, or custom integrations.
Expand Down Expand Up @@ -79,8 +80,9 @@ All three abilities can be called directly via REST API, making them useful for
- `editor.BlockEdit` with `withGenerateImageToolbarButton` (`ai/image-generation-inline-toolbar`): adds a "Generate Image" toolbar button in block controls
- `editor.MediaUpload` with `withGenerateImageInlineButton` (`ai/image-generation-inline-button`): adds an inline "Generate Image" button in the MediaUpload placeholder area (uses `updateBlockAttributes` from the block editor store since MediaUpload does not receive `setAttributes`)
- When either button is clicked, `GenerateImageInlineModal` opens with an idle state (prompt input). The user submits a prompt and the modal:
- Calls `runAbility( 'ai/image-generation', { prompt } )`
- Shows preview with "Keep", and "Start Over" actions
- Calls `runAbility( 'ai/image-generation', { prompt } )` (or `{ prompt, reference }` when refining)
- Shows preview with "Keep", "Refine", and "Start Over" actions
- "Refine" switches to refinment state: user enters a follow-up prompt; the current image is passed as `reference` so models supporting edits can use it as context
- "Keep" calls `uploadImage()` (with optional alt text generation) and `insertIntoBlock()` to insert the imported image into the block
- `insertIntoBlock()` sets block attributes based on block type: `core/image` (id, url, alt), `core/cover` (id, url, alt, dimRatio: 50, isDark: false, sizeSlug: 'full'), `core/media-text` (mediaId, mediaUrl, mediaType), `core/gallery` (appends a new inner `core/image` block)

Expand All @@ -92,7 +94,8 @@ All three abilities can be called directly via REST API, making them useful for
- Uses AI with a dedicated system instruction to generate an optimized image generation prompt
- Returns a plain text prompt string suitable for image generation models
- **Image Generation** (via `ai/image-generation`):
- Accepts `prompt` (string) as required input
- Accepts `prompt` (string) as required input and optional `reference` (base64-encoded string) for image editing
- If `reference` is provided, uses it as a reference image for editing
- Uses AI image generation models (via `get_preferred_image_models()`)
- Sets request timeout to 90 seconds for longer generation times
- Returns an object `{ image: { data, provider_metadata, model_metadata } }` where `data` is the base64-encoded image
Expand Down Expand Up @@ -137,11 +140,16 @@ array(
array(
'type' => 'object',
'properties' => array(
'prompt' => array(
'prompt' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Prompt used to generate an image.',
),
'reference' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => 'Optional base64-encoded image to use as a reference for editing.',
),
),
'required' => array( 'prompt' ),
)
Expand Down Expand Up @@ -730,7 +738,7 @@ You can extend the React components to add custom UI elements:

2. **Modify the inline generation modal:**
- Edit `src/experiments/image-generation/components/GenerateImageInlineModal.tsx`
- The modal supports idle (prompt input), generating, and preview (keep/start over) states
- The modal supports idle (prompt input), generating, preview (keep/refine/start over) states
- Customize the flow, UI copy, or add new actions

3. **Add or change supported blocks for inline generation:**
Expand Down Expand Up @@ -788,7 +796,8 @@ add_filter( 'wp_generate_attachment_metadata', function( $metadata, $attachment_
- Add an Image, Cover, Media & Text, or Gallery block
- Select the block and click the "Generate Image" toolbar or inline button
- Enter a prompt (e.g. "A sunset over mountains") and click Generate
- Verify the preview appears with "Keep", and "Start Over"
- Verify the preview appears with "Keep", "Refine", and "Start Over"
- Click "Refine", add a follow-up prompt (e.g. "Add clouds"), and Generate; verify the image updates
- Click "Keep" and verify the image is imported and inserted into the block
- Test with each supported block type to ensure correct attribute mapping

Expand Down Expand Up @@ -845,7 +854,7 @@ npm run test:php
- The ability uses `get_preferred_image_models()` to determine which AI image models to use
- Models are tried in order until one succeeds
- Default models include Google's Gemini (e.g. gemini-3-pro-image-preview, gemini-2.5-flash-image), Imagen, and OpenAI's DALL-E 3 and GPT-image models
- All default models support image generation
- All default models support image generation; the Google models also support image editing (when a reference image is provided)
- Request timeout is set to 90 seconds to accommodate longer image generation times

### Prompt Generation
Expand Down
61 changes: 51 additions & 10 deletions includes/Abilities/Image/Generate_Image.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
use Throwable;
use WP_Error;
use WordPress\AI\Abstracts\Abstract_Ability;
use WordPress\AiClient\Files\DTO\File;
use WordPress\AiClient\Files\Enums\FileTypeEnum;
use WordPress\AiClient\Providers\DTO\ProviderMetadata;
use WordPress\AiClient\Providers\Http\DTO\RequestOptions;
Expand All @@ -35,11 +36,16 @@ protected function input_schema(): array {
return array(
'type' => 'object',
'properties' => array(
'prompt' => array(
'prompt' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Prompt used to generate an image.', 'ai' ),
),
'reference' => array(
'type' => 'string',
'sanitize_callback' => 'sanitize_text_field',
'description' => esc_html__( 'Optional base64-encoded image to use as a reference image for edits.', 'ai' ),
),
),
'required' => array( 'prompt' ),
);
Expand Down Expand Up @@ -106,8 +112,10 @@ protected function output_schema(): array {
* @since 0.2.0
*/
protected function execute_callback( $input ) {
$reference_image = ! empty( $input['reference'] ) ? (string) $input['reference'] : null;

// Generate the image.
$result = $this->generate_image( $input['prompt'] );
$result = $this->generate_image( $input['prompt'], $reference_image );

// If we have an error, return it.
if ( is_wp_error( $result ) ) {
Expand Down Expand Up @@ -162,18 +170,18 @@ protected function meta(): array {
* @since 0.2.0
*
* @param string $prompt The prompt to generate an image from.
* @param string|null $reference_image Optional base64-encoded image to use as a reference for edits.
* @return array{data: string, provider_metadata: array<string, string>, model_metadata: array<string, string>}|\WP_Error The generated image data, or a WP_Error on failure.
*/
protected function generate_image( string $prompt ) { // phpcs:ignore Generic.NamingConventions.ConstructorName.OldStyle
$request_options = new RequestOptions();
$request_options->setTimeout( 90 );
protected function generate_image( string $prompt, ?string $reference_image = null ) { // phpcs:ignore Generic.NamingConventions.ConstructorName.OldStyle
$prompt_builder = $this->get_prompt_builder( $prompt, $reference_image );

if ( is_wp_error( $prompt_builder ) ) {
return $prompt_builder;
}

// Generate the image using the AI client.
$result = wp_ai_client_prompt( $prompt )
->using_request_options( $request_options )
->as_output_file_type( FileTypeEnum::inline() )
->using_model_preference( ...get_preferred_image_models() )
->generate_image_result();
$result = $prompt_builder->generate_image_result();

if ( is_wp_error( $result ) ) {
return $result;
Expand Down Expand Up @@ -217,4 +225,37 @@ protected function generate_image( string $prompt ) { // phpcs:ignore Generic.Na

return $data;
}

/**
* Gets a prompt builder for generating an image.
*
* @since x.x.x
*
* @param string $prompt The prompt to generate an image from.
* @param string|null $reference_image Optional base64-encoded image to use as a reference for edits.
* @return \WP_AI_Client_Prompt_Builder|\WP_Error The prompt builder, or a WP_Error on failure.
*/
private function get_prompt_builder( string $prompt, ?string $reference_image = null ) {
$request_options = new RequestOptions();
$request_options->setTimeout( 90 );

$prompt_builder = wp_ai_client_prompt( $prompt )
->using_request_options( $request_options )
->as_output_file_type( FileTypeEnum::inline() )
->using_model_preference( ...get_preferred_image_models() );

if ( null !== $reference_image ) {
try {
$file = new File( $reference_image );
$prompt_builder = $prompt_builder->with_file( $file );
} catch ( Throwable $t ) {
return new WP_Error(
'invalid_reference',
esc_html__( 'The reference image is not valid base64-encoded data.', 'ai' )
);
}
}

return $prompt_builder;
}
}
4 changes: 2 additions & 2 deletions includes/Experiments/Image_Generation/Image_Generation.php
Original file line number Diff line number Diff line change
Expand Up @@ -40,8 +40,8 @@ public static function get_id(): string {
*/
protected function load_metadata(): array {
return array(
'label' => __( 'Image Generation', 'ai' ),
'description' => __( 'Generate featured images and inline images using AI', 'ai' ),
'label' => __( 'Image Generation and Editing', 'ai' ),
'description' => __( 'Generate and edit images using AI', 'ai' ),
'category' => Experiment_Category::EDITOR,
);
}
Expand Down
Loading
Loading