Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
e7b5109
Load the assets on the post edit screen
dkotter Dec 17, 2025
5f8e927
Build our assets
dkotter Dec 17, 2025
12d8d47
Add the base plugin registration and render component
dkotter Dec 17, 2025
a400786
Wire up the generation of a summary when the button is clicked
dkotter Dec 17, 2025
bb17301
Insert a paragraph block with the summary after it is generated. Make…
dkotter Dec 17, 2025
d4940db
Store the summary in post meta after it is generated
dkotter Dec 18, 2025
bc96c0f
Update outdated dependencies
dkotter Dec 18, 2025
d0392cf
Create a paragraph variation block and use that when we insert the su…
dkotter Dec 18, 2025
74acd10
Check if we have an existing summary block and if so, pull the conten…
dkotter Dec 18, 2025
b0a099f
Add custom toolbar controls to paragraph blocks marked as AI summaries
dkotter Dec 18, 2025
96ed39f
Move shared functionality into a hook so we can easily pull that in w…
dkotter Dec 18, 2025
cddf1e8
Fix some unrelated failing plugin checks
dkotter Dec 18, 2025
a211e84
Change prefix to see if that fixes plugin check
dkotter Dec 18, 2025
b12611c
Try turning off sniff
dkotter Dec 18, 2025
a3f1e4e
Merge branch 'develop' into feature/content-summarization-ui
jeffpaul Dec 20, 2025
54a312e
Create screenshot-#.gif
jeffpaul Dec 20, 2025
dd8ea07
add content summarization to readme
jeffpaul Dec 20, 2025
a3c9cbe
Merge branch 'develop' into feature/content-summarization-ui
dkotter Dec 23, 2025
82d699e
Merge branch 'feature/content-summarization-experiment' into feature/…
dkotter Jan 2, 2026
6dd4a3d
Add docs
dkotter Jan 2, 2026
a10e5b2
Merge branch 'feature/content-summarization-experiment' into feature/…
dkotter Jan 13, 2026
9474974
Merge branch 'develop' into feature/content-summarization-ui
dkotter Jan 13, 2026
ced27f7
Fix typescript errors
dkotter Jan 14, 2026
7298a2d
Add E2E tests
dkotter Jan 14, 2026
7e2b375
Merge branch 'develop' into feature/content-summarization-ui
dkotter Jan 14, 2026
47883ad
Merge branch 'develop' into feature/content-summarization-ui
dkotter Jan 26, 2026
479232b
Update docs
dkotter Jan 26, 2026
91e8eca
Merge branch 'develop' into feature/content-summarization-ui
dkotter Feb 4, 2026
7144cab
Update outdated node dependencies
dkotter Feb 4, 2026
6f7c015
Ensure our meta is registered for all post types so things work on al…
dkotter Feb 4, 2026
17c5e43
Add some basic styling to try and ensure the loading state of the but…
dkotter Feb 4, 2026
4a58262
Merge branch 'develop' into feature/content-summarization-ui
jeffpaul Feb 5, 2026
a329598
Merge branch 'develop' into feature/content-summarization-ui
dkotter Feb 5, 2026
9ccf626
Add a stylesheet for the Summarization experiment and move all styles…
dkotter Feb 5, 2026
1d52cef
Instead of always creating a new summary block, only create a new one…
dkotter Feb 5, 2026
7d9be85
Merge branch 'develop' into feature/content-summarization-ui
jeffpaul Feb 5, 2026
73bfa5f
Downgrade @wordpress/scripts to see if that fixes dependency flag
dkotter Feb 5, 2026
5c8e821
Merge branch 'develop' into feature/content-summarization-ui
dkotter Feb 5, 2026
c2098b0
Downgrade all dependencies to what is in develop
dkotter Feb 5, 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
Binary file added .wordpress-org/screenshot-#.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
507 changes: 507 additions & 0 deletions docs/experiments/summarization.md

Large diffs are not rendered by default.

45 changes: 45 additions & 0 deletions includes/Experiments/Summarization/Summarization.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

use WordPress\AI\Abilities\Summarization\Summarization as Summarization_Ability;
use WordPress\AI\Abstracts\Abstract_Experiment;
use WordPress\AI\Asset_Loader;

// Exit if accessed directly.
if ( ! defined( 'ABSPATH' ) ) {
Expand Down Expand Up @@ -45,7 +46,26 @@ protected function load_experiment_metadata(): array {
* @since 0.2.0
*/
public function register(): void {
$this->register_post_meta();
add_action( 'wp_abilities_api_init', array( $this, 'register_abilities' ) );
add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_assets' ) );
}

/**
* Register any needed post meta.
*
* @since x.x.x
*/
public function register_post_meta(): void {
register_meta(
'post',
'ai_generated_summary',
array(
'type' => 'string',
'single' => true,
'show_in_rest' => true,
)
);
}

/**
Expand All @@ -63,4 +83,29 @@ public function register_abilities(): void {
),
);
}

/**
* Enqueues and localizes the admin script.
*
* @since x.x.x
*
* @param string $hook_suffix The current admin page hook suffix.
*/
public function enqueue_assets( string $hook_suffix ): void {
// Load asset in new post and edit post screens only.
if ( 'post.php' !== $hook_suffix && 'post-new.php' !== $hook_suffix ) {
return;
}

Asset_Loader::enqueue_script( 'summarization', 'experiments/summarization' );
Asset_Loader::enqueue_style( 'summarization', 'experiments/summarization' );
Asset_Loader::localize_script(
'summarization',
'SummarizationData',
array(
'enabled' => $this->is_enabled(),
'path' => Summarization_Ability::path( $this->get_id() ),
)
);
}
}
1 change: 1 addition & 0 deletions readme.txt
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ This plugin is built on the [AI Building Blocks for WordPress](https://make.word
**Current Features:**

* **Title Generation** - Generate title suggestions for your posts with a single click. Perfect for brainstorming headlines or finding the right tone for your content.
* **Content Summarization** - Summarizes long-form content into digestible overviews.
* **Excerpt Generation** - Automatically create concise summaries for your posts.
* **Experiment Framework** - Opt-in system that lets you enable only the AI features you want to use.
* **Multi-Provider Support** - Works with popular AI providers like OpenAI, Google, and Anthropic.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/**
* External dependencies
*/
import React from 'react';

/**
* WordPress dependencies
*/
import { BlockControls } from '@wordpress/block-editor';
import { ToolbarGroup, ToolbarButton } from '@wordpress/components';
import { createHigherOrderComponent } from '@wordpress/compose';
import { __ } from '@wordpress/i18n';
import { update } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { useSummaryGeneration } from '../functions/useSummaryGeneration';

const { aiSummarizationData } = window as any;

/**
* Block controls component.
*/
const Controls = () => {
const { isSummarizing, hasSummary, handleSummarize } =
useSummaryGeneration();
const buttonLabel = hasSummary
? __( 'Re-generate AI Summary', 'ai' )
: __( 'Generate AI Summary', 'ai' );

// Ensure the experiment is enabled.
if ( ! aiSummarizationData?.enabled ) {
return null;
}

return (
<BlockControls>
<ToolbarGroup>
<ToolbarButton
label={ buttonLabel }
icon={ update }
className="ai-summarization-block-controls-button"
onClick={ handleSummarize }
disabled={ isSummarizing }
isBusy={ isSummarizing }
/>
</ToolbarGroup>
</BlockControls>
);
};

/**
* Add custom block controls to the summarization block.
*/
const SummarizationBlockControls = createHigherOrderComponent(
( BlockEdit: React.ComponentType< any > ) => {
return ( props: any ) => {
const {
name,
isSelected,
attributes: { aiGeneratedSummary = false },
} = props;

if ( name !== 'core/paragraph' || ! aiGeneratedSummary ) {
return <BlockEdit { ...props } />;
}

return (
<>
{ isSelected && <Controls { ...props } /> }
<BlockEdit { ...props } />
</>
);
};
},
'addBlockControls'
);

export default SummarizationBlockControls;
71 changes: 71 additions & 0 deletions src/experiments/summarization/components/SummarizationPlugin.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/**
* Summarization plugin component.
*/

/**
* WordPress dependencies
*/
import { Button, Flex, FlexItem } from '@wordpress/components';
import { PluginPostStatusInfo } from '@wordpress/editor';
import { __ } from '@wordpress/i18n';
import { update } from '@wordpress/icons';

/**
* Internal dependencies
*/
import { useSummaryGeneration } from '../functions/useSummaryGeneration';

const { aiSummarizationData } = window as any;

/**
* Summarization plugin component.
*/
export default function SummarizationPlugin() {
const { isSummarizing, hasSummary, handleSummarize } =
useSummaryGeneration();

const buttonLabel = hasSummary
? __( 'Re-generate AI Summary', 'ai' )
: __( 'Generate AI Summary', 'ai' );
const buttonDescription = hasSummary
? __(
'This will update the AI generated summary block with a new summary of the content of this post.',
'ai'
)
: __(
'This will create a block that is a summary of the content of this post, generated by AI.',
'ai'
);

// Ensure the experiment is enabled.
if ( ! aiSummarizationData?.enabled ) {
return null;
}

return (
<PluginPostStatusInfo>
<Flex
direction="column"
className="ai-summarization-plugin-container"
gap={ 2 }
>
<FlexItem>
<Button
variant="secondary"
label={ buttonLabel }
icon={ update }
onClick={ handleSummarize }
disabled={ isSummarizing }
isBusy={ isSummarizing }
__next40pxDefaultSize
>
{ buttonLabel }
</Button>
</FlexItem>
<FlexItem>
<span className="description">{ buttonDescription }</span>
</FlexItem>
</Flex>
</PluginPostStatusInfo>
);
}
39 changes: 39 additions & 0 deletions src/experiments/summarization/functions/generate-summary.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/**
* WordPress dependencies
*/
import apiFetch from '@wordpress/api-fetch';

const { aiSummarizationData } = window as any;

/**
* Generates a summary for the given post ID and content.
*
* @param {number} postId The ID of the post to generate a summary for.
* @param {string} content The content of the post to generate a summary for.
* @return {Promise<string>} A promise that resolves to the generated summary.
*/
export async function generateSummary(
postId: number,
content: string
): Promise< string > {
return apiFetch( {
path: aiSummarizationData?.path ?? '',
method: 'POST',
data: {
input: {
context: postId.toString(),
content,
},
},
} )
.then( ( response ) => {
if ( response && typeof response === 'string' ) {
return response as string;
}

throw new Error( 'Invalid response from API' );
} )
.catch( ( error ) => {
throw new Error( error.message );
} );
}
118 changes: 118 additions & 0 deletions src/experiments/summarization/functions/useSummaryGeneration.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
/**
* Shared hook for summary generation logic.
*/

/**
* WordPress dependencies
*/
import { createBlock, type BlockInstance } from '@wordpress/blocks';
import { store as blockEditorStore } from '@wordpress/block-editor';
import { dispatch, useDispatch, useSelect } from '@wordpress/data';
import { store as editorStore } from '@wordpress/editor';
import { useEffect, useState } from '@wordpress/element';
import { store as noticesStore } from '@wordpress/notices';

/**
* Internal dependencies
*/
import { generateSummary } from './generate-summary';

/**
* Summary generation hook.
*/
export function useSummaryGeneration() {
const { allBlocks, postId, content, meta } = useSelect( ( select ) => {
return {
allBlocks: select( blockEditorStore )[ 'getBlocks' ](), // eslint-disable-line dot-notation
postId: select( editorStore ).getCurrentPostId(),
content: select( editorStore ).getEditedPostContent(),
meta: select( editorStore ).getEditedPostAttribute( 'meta' ),
};
} );
const { editPost } = useDispatch( editorStore );
const [ isSummarizing, setIsSummarizing ] = useState( false );
const [ summary, setSummary ] = useState( '' );

// Check if a summary block exists and update state accordingly.
useEffect( () => {
const summaryBlock = allBlocks.find(
( block: BlockInstance ) =>
block.name === 'core/paragraph' &&
block.attributes[ 'aiGeneratedSummary' ] === true // eslint-disable-line dot-notation
);
if ( summaryBlock && summaryBlock.attributes.content ) {
setSummary( summaryBlock.attributes.content );
}
}, [ allBlocks ] );

/**
* Handles the summarization button click.
*/
const handleSummarize = async () => {
setIsSummarizing( true );
( dispatch( noticesStore ) as any ).removeNotice(
'ai_summarization_error'
);

try {
const generatedSummary = await generateSummary(
postId as number,
content
);
setSummary( generatedSummary );

// Store the summary in post meta (will require a manual save).
editPost( {
meta: {
...meta,
ai_generated_summary: generatedSummary,
},
} );

// Check if an existing AI summary block exists.
const existingSummaryBlock = allBlocks.find(
( block: BlockInstance ) =>
block.name === 'core/paragraph' &&
block.attributes[ 'aiGeneratedSummary' ] === true // eslint-disable-line dot-notation
);

if ( existingSummaryBlock ) {
// Update only the content of the existing block to preserve styles and other attributes.
/* eslint-disable dot-notation -- updateBlockAttributes from store index signature */
( dispatch( blockEditorStore ) as any )[
'updateBlockAttributes'
]( existingSummaryBlock.clientId, {
content: generatedSummary,
} );
/* eslint-enable dot-notation */
} else {
// Insert a new summary block at the top.
const summaryBlock = createBlock( 'core/paragraph', {
content: generatedSummary,
className: 'ai-summarization-summary',
aiGeneratedSummary: true,
} );
// eslint-disable-next-line dot-notation
( dispatch( blockEditorStore ) as any )[ 'insertBlock' ](
summaryBlock,
0
);
}
} catch ( error: any ) {
( dispatch( noticesStore ) as any ).createErrorNotice( error, {
id: 'ai_summarization_error',
isDismissible: true,
} );
setSummary( '' );
} finally {
setIsSummarizing( false );
}
};

return {
isSummarizing,
hasSummary: summary && summary.trim().length > 0,
summary,
handleSummarize,
};
}
12 changes: 12 additions & 0 deletions src/experiments/summarization/index.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
.ai-summarization-plugin-container {
.description {
color: #757575;
}
}

.block-editor-block-popover {
.ai-summarization-block-controls-button {
margin-top: 1px;
height: 46px;
}
}
Loading
Loading