25 KiB
Implementation Plan: Hybrid Block Refinement with @ Mention Support
Overview
Implement a hybrid refinement system that combines:
- Current workflow: "AI Refine" button in block toolbar (beginner-friendly)
- New workflow:
@blockmentions in chat (power-user friendly)
This provides both discoverability for beginners and speed for experts.
Current State Analysis
Existing Refinement Flow
- Location: assets/js/block-refine.js
- Trigger: Click "AI Refine" in block toolbar
- Flow: Modal opens → Type request → Submit → Block replaced
- Status: ✅ Working correctly after recent fixes
Chat System
- Location: assets/js/sidebar.js
- Current behavior: Article generation and chat messages
- Status: ✅ Working with tabbed interface
Implementation Plan
Phase 1: Backend - Add Refinement Endpoint to Chat System
File: includes/class-gutenberg-sidebar.php
Changes Needed:
- Add new REST endpoint:
/refine-from-chat(or modify/generate-planto handle refinement requests) - Parse mentions from chat messages: Extract
@block-refpatterns - Handle special mention syntax:
@this→ Currently selected block@previous→ Previous block@next→ Next block@all→ All blocks@paragraph-1,@heading-2→ Block by sequential ID
API Structure:
{
"topic": "Refine @this to be more engaging",
"context": "User's full message with mentions",
"postId": 123,
"selectedBlockClientId": "abc123",
"stream": true
}
Phase 2: Frontend - Add @ Mention Detection
File: assets/js/sidebar.js
Add to sendMessage() function:
- Detect
@mentions in user input - Extract mention patterns:
const mentionRegex = /@(\w+(?:-\d+)?|this|previous|next|all)/g; - Check if message contains refinement keywords:
- "refine", "rewrite", "edit", "improve", "change", "make it"
- If both detected → Treat as refinement request
New Message Handler:
const handleRefinementRequest = async (message) => {
// Extract mentions
const mentions = message.match( /@(\w+(?:-\d+)?|this|previous|next|all)/g );
// Resolve to actual block client IDs
const blocksToRefine = resolveMentionsToBlocks( mentions );
if ( blocksToRefine.length === 0 ) {
// No valid mentions, treat as normal chat
return false;
}
// Call refinement endpoint
await refineBlocksFromChat( blocksToRefine, message );
return true; // Handled as refinement
};
Phase 3: Backend - Chat Refinement Endpoint
New Method: refine_from_chat() in class-gutenberg-sidebar.php
Process:
- Receive chat message with mentions
- Resolve mentions to actual blocks
- Call existing
stream_block_refine()for each block - Stream responses back to chat
Implementation:
public function handle_refine_from_chat( $request ) {
$params = $request->get_json_params();
$message = $params['topic'] ?? '';
$selected_block = $params['selectedBlockClientId'] ?? '';
$post_id = $params['postId'] ?? 0;
// Parse mentions from message
preg_match_all( '/@(\w+(?:-\d+)?|this|previous|next|all)/i', $message, $matches );
// Resolve mentions to block client IDs
$blocks_to_refine = $this->resolve_mentions_to_blocks( $matches, $selected_block, $post_id );
if ( empty( $blocks_to_refine ) ) {
// No blocks mentioned, treat as normal chat
return $this->handle_generate_plan( $request );
}
// Stream refinement for each mentioned block
$this->stream_refinement_from_chat( $blocks_to_refine, $message, $post_id );
}
Phase 4: Block Resolution Logic
New Method: resolve_mentions_to_blocks() in class-gutenberg-sidebar.php
Resolution Rules:
private function resolve_mentions_to_blocks( $mentions, $selected_block, $post_id ) {
$all_blocks = get_blocks( $post_id );
$resolved = array();
foreach ( $mentions as $mention ) {
$mention_type = strtolower( $mention[1] );
switch ( $mention_type ) {
case 'this':
if ( $selected_block ) {
$resolved[] = $selected_block;
}
break;
case 'previous':
if ( $selected_block ) {
$index = array_search( $selected_block, $all_blocks );
if ( $index > 0 ) {
$resolved[] = $all_blocks[ $index - 1 ]['clientId'];
}
}
break;
case 'next':
if ( $selected_block ) {
$index = array_search( $selected_block, $all_blocks );
if ( $index < count( $all_blocks ) - 1 ) {
$resolved[] = $all_blocks[ $index + 1 ]['clientId'];
}
}
break;
case 'all':
// Return all paragraph and heading blocks
foreach ( $all_blocks as $block ) {
if ( in_array( $block['name'], array( 'core/paragraph', 'core/heading' ) ) ) {
$resolved[] = $block['clientId'];
}
}
break;
default:
// Handle sequential mentions like "paragraph-1", "heading-2"
if ( preg_match( '/^(\w+)-(\d+)$/', $mention_type, $matches ) ) {
$block_type = $matches[1]; // "paragraph", "heading"
$index = (int) $matches[2] - 1; // Convert to 0-based
foreach ( $all_blocks as $block ) {
if ( $block['name'] === 'core/' . $block_type ) ) {
if ( $index === 0 ) {
$resolved[] = $block['clientId'];
$index--;
}
}
}
}
break;
}
}
return array_unique( $resolved );
}
Phase 5: Visual Feedback
5.1. Block Highlighting
File: assets/css/editor.css
Add CSS for mentioned blocks:
.wpaw-block-mentioned {
outline: 2px solid #2271b1 !important;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(34, 113, 177, 0.2);
animation: wpaw-pulse 1.5s infinite;
}
@keyframes wpaw-pulse {
0%, 100% { box-shadow: 0 0 0 0px rgba(34, 113, 177, 0.2); }
50% { box-shadow: 0 0 0 6px rgba(34, 113, 177, 0.4); }
}
5.2. Autocomplete UI
File: assets/js/sidebar.js
Add mention autocomplete:
// When user types @ in chat input
const showMentionAutocomplete = (searchTerm) => {
const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks();
const filtered = allBlocks
.filter( b => b.name === 'core/paragraph' || b.name === 'core/heading' )
.map( ( b, index ) => ( {
const label = b.attributes.content || b.name;
const shortLabel = b.name === 'core/paragraph'
? `Paragraph ${index + 1}`
: `Heading ${index + 1}: ${label}`;
return {
id: `${b.name}-${index}`,
label: `${shortLabel}: "${label.substring( 0, 30 )}"`,
clientId: b.clientId,
};
} ) );
// Show dropdown with filtered options
showAutocompleteDropdown( filtered );
};
Phase 6: Chat Integration
Modify: sendMessage() in assets/js/sidebar.js
Flow:
const sendMessage = async () => {
const userMessage = input.trim();
// Check if this is a refinement request
const isRefinement = /refine|rewrite|edit|improve|change|make (it|them|this)/i.test( userMessage );
const hasMentions = /@/.test( userMessage );
if ( isRefinement && hasMentions ) {
// Handle as refinement request
await handleRefinementRequest( userMessage );
} else {
// Handle as normal chat/generation
// ...existing chat logic...
}
};
Phase 7: Enhanced Chat Experience
Add refinement summary to chat:
When refinement is requested:
- Add user message as chat bubble: "Refine @paragraph-3 to be more engaging"
- Add AI response: "✓ I've refined paragraph 3"
- Update block content in editor
- Show cost in Cost tab
Example Chat Flow:
User: Refine @this to be more concise
[Message added to chat]
AI: ✓ Refining current paragraph...
[Status timeline showing progress]
AI: ✅ Done! I've made the paragraph more concise while keeping the key information.
[Block updated in editor]
Detailed Implementation
Step 1: Backend Chat Refinement Endpoint
File: includes/class-gutenberg-sidebar.php
Add new REST route:
register_rest_route(
'wp-agentic-writer/v1',
'/refine-from-chat',
array(
'methods' => 'POST',
'callback' => array( $this, 'handle_refine_from_chat' ),
'permission_callback' => array( $this, 'check_edit_permission' ),
)
);
Implementation:
- Handle chat messages with
@mentions - Parse and resolve mentions to block client IDs
- Stream refinement responses back to chat
- Update blocks in real-time
Step 2: Frontend Mention Detection
File: assets/js/sidebar.js
Add to sendMessage() function:
// Detect if message is refinement request with mentions
const isRefinementWithMentions = /@(\w+(?:-\d+)?|this|previous|next|all)/i.test( userMessage ) &&
/refine|rewrite|edit|improve|change|make/i.test( userMessage );
if ( isRefinementWithMentions ) {
// Handle as refinement
await handleChatRefinement( userMessage, selectedBlockClientId );
return;
}
Step 3: Block Mention Resolution
Create helper function in assets/js/sidebar.js:
const resolveBlockMentions = ( mentions, selectedBlockId ) => {
const allBlocks = wp.data.select( 'core/block-editor' ).getBlocks();
const resolved = [];
mentions.forEach( mention => {
const type = mention.toLowerCase();
const match = mention.match( /^(\w+)-(\d+)$/ );
switch ( type ) {
case 'this':
if ( selectedBlockId ) resolved.push( selectedBlockId );
break;
case 'previous':
const selectedIndex = allBlocks.findIndex( b => b.clientId === selectedBlockId );
if ( selectedIndex > 0 ) {
resolved.push( allBlocks[ selectedIndex - 1 ].clientId );
}
break;
case 'next':
const selectedIndex = allBlocks.findIndex( b => b.clientId === selectedBlockId );
if ( selectedIndex < allBlocks.length - 1 ) {
resolved.push( allBlocks[ selectedIndex + 1 ].clientId );
}
break;
case 'all':
allBlocks.forEach( ( block, index ) => {
if ( block.name === 'core/paragraph' || block.name === 'core/heading' ) {
resolved.push( block.clientId );
}
} );
break;
default:
// Handle "paragraph-1", "heading-2" format
if ( match ) {
const blockType = 'core/' + match[1]; // paragraph or heading
const blockIndex = parseInt( match[2] ) - 1; // 1-based to 0-based
let currentIndex = 0;
allBlocks.forEach( ( block ) => {
if ( block.name === blockType ) {
if ( currentIndex === blockIndex ) {
resolved.push( block.clientId );
}
currentIndex++;
}
} );
}
break;
}
} );
return [ ...new Set( resolved ) ]; // Remove duplicates
};
Step 4: Chat Refinement Handler
Create new function in assets/js/sidebar.js:
const handleChatRefinement = async ( message, selectedBlockId ) => {
// Parse mentions from message
const mentionRegex = /@(\w+(?:-\d+)?|this|previous|next|all)/gi;
const mentions = [...message.matchAll( mentionRegex )].map( m => m[0] );
// Resolve to block client IDs
const blocksToRefine = resolveBlockMentions( mentions, selectedBlockId );
if ( blocksToRefine.length === 0 ) {
// No valid mentions found
alert( 'No valid blocks found to refine. Try mentioning blocks like @this, @previous, or specific blocks like @paragraph-1' );
return;
}
// Add user message to chat
setMessages( [ ...messages, { role: 'user', content: message } ] );
// Add timeline entry
setMessages( prev => [ ...prev, {
role: 'system',
type: 'timeline',
status: 'refining',
message: `Refining ${blocksToRefine.length} block(s)...`,
icon: '✏️',
} ] );
setIsLoading( true );
try {
// Call refinement endpoint
const response = await fetch( wpAgenticWriter.apiUrl + '/refine-from-chat', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-WP-Nonce': wpAgenticWriter.nonce,
},
body: JSON.stringify( {
topic: message,
context: message,
selectedBlockClientId: selectedBlockId,
blocksToRefine: blocksToRefine,
postId: wp.data.select( 'core/editor' ).getCurrentPostId(),
stream: true,
} ),
} );
if ( ! response.ok ) {
throw new Error( 'Refinement failed' );
}
// Handle streaming response
const reader = response.body.getReader();
const decoder = new TextDecoder();
let refinedCount = 0;
while ( true ) {
const { done, value } = await reader.read();
if ( done ) break;
const chunk = decoder.decode( value, { stream: true } );
const lines = chunk.split( '\n' );
for ( const line of lines ) {
if ( line.startsWith( 'data: ' ) ) {
try {
const data = JSON.parse( line.slice( 6 ) );
if ( data.type === 'error' ) {
throw new Error( data.message );
} else if ( data.type === 'block' ) {
// Replace block in editor
const newBlock = createBlockFromData( data.block );
const { replaceBlocks } = wp.data.dispatch( 'core/block-editor' );
data.block.blocks.forEach( ( blockData, idx ) => {
if ( blockData.clientId ) {
replaceBlocks( blockData.clientId, createBlockFromData( blockData ) );
}
} );
refinedCount++;
} else if ( data.type === 'complete' ) {
// Show completion message
setMessages( prev => [ ...prev, {
role: 'assistant',
content: `✅ Done! I've refined ${refinedCount} block(s) as requested.`,
} ] );
setMessages( prev => {
const newMessages = [ ...prev ];
const lastTimelineIndex = newMessages.findIndex( m => m.type === 'timeline' && m.status !== 'complete' );
if ( lastTimelineIndex !== -1 ) {
newMessages[lastTimelineIndex] = {
...newMessages[lastTimelineIndex],
status: 'complete',
message: `Refined ${refinedCount} block(s) successfully`,
icon: '✅',
};
}
return newMessages;
} );
}
} catch ( e ) {
console.error( 'Failed to parse streaming data:', line, e );
}
}
}
}
} catch ( error ) {
setMessages( prev => [ ...prev, {
role: 'system',
type: 'error',
content: 'Error: ' + error.message,
} ] );
} finally {
setIsLoading( false );
}
};
Step 5: Visual Feedback
File: assets/css/sidebar.css
Add block mention styles:
/* Block mention styles */
.wpaw-block-mentioned {
outline: 2px solid #2271b1 !important;
outline-offset: 2px;
box-shadow: 0 0 0 4px rgba(34, 113, 177, 0.2);
animation: wpaw-pulse 1.5s infinite;
transition: all 0.3s ease;
}
.wpaw-block-mentioned:hover {
outline-width: 3px;
box-shadow: 0 0 0 8px rgba(34, 113, 177, 0.3);
}
@keyframes wpaw-pulse {
0%, 100% {
box-shadow: 0 0 0 0px rgba( 34, 113, 177, 0.2);
}
50% {
box-shadow: 0 0 0 6px rgba(34, 113, 177, 0.4);
}
}
/* Mention autocomplete */
.wpaw-mention-autocomplete {
position: absolute;
background: white;
border: 1px solid #ddd;
border-radius: 4px;
max-height: 200px;
overflow-y: auto;
z-index: 1000;
box-shadow: 0 4px 6px rgba(0,0,0,0.1);
}
.wpaw-mention-option {
padding: 8px 12px;
cursor: pointer;
border-bottom: 1px solid #eee;
}
.wpaw-mention-option:hover {
background: #f0f0f0;
}
.wpaw-mention-option strong {
display: block;
color: #333;
font-size: 13px;
}
.wpaw-mention-option span {
display: block;
color: #666;
font-size: 12px;
margin-top: 2px;
}
User Experience
Scenario 1: Beginner (Toolbar Button)
Flow:
- Click block to select it
- Click "AI Refine" in toolbar
- Type: "Make this more engaging"
- Click "Refine"
- Block is refined
Experience: Clear, guided, discoverable
Scenario 2: Power User (Chat Mention)
Flow:
- Type in chat: "Refine @this to be more engaging"
- System highlights mentioned block
- Press Enter or click Send
- Chat shows: "✅ Done! I've refined the current block."
- Block is refined immediately
Experience: Fast, conversational, efficient
Scenario 3: Multi-Block Refinement
Flow:
- Type: "Refine @paragraph-1, @paragraph-2, and @paragraph-3 to be more concise"
- All three blocks get highlighted
- Send message
- All three blocks refined in sequence
- Chat shows summary
Experience: Powerful, batch operation
File Modifications Summary
New Files
- None (all modifications to existing files)
Modified Files
-
includes/class-gutenberg-sidebar.php
- Add
handle_refine_from_chat()method - Add
resolve_mentions_to_blocks()method - Add
/refine-from-chatREST route - Add
stream_refinement_from_chat()method
- Add
-
- Modify
sendMessage()to detect refinement mentions - Add
resolveBlockMentions()function - Add
handleChatRefinement()function - Add mention detection and autocomplete UI
- Modify
-
- Add
.wpaw-block-mentionedstyles - Add
.wpaw-pulseanimation - Add mention autocomplete dropdown styles
- Add
-
- Add block highlighting styles for mentioned blocks
Technical Details
Mention Syntax Reference
| Syntax | Description | Example |
|---|---|---|
@this |
Current selected block | "Refine @this to be more engaging" |
@previous |
Block before current | "Refine @previous to match tone" |
@next |
Block after current | "Refine @next for consistency" |
@all |
All blocks | "Refine @all to be more concise" |
@paragraph-1 |
1st paragraph | "Refine @paragraph-1 to be more exciting" |
@heading-2 |
2nd heading | "Refine @heading-2 to be more descriptive" |
@list-1 |
1st list | "Refine @list-1 to add more items" |
Block Type Aliases
For user-friendly mentions:
@paragraph=@paragraph-1(current paragraph of type paragraph)@heading=@heading-1(current heading of any level)@list=@list-1(current list)
Benefits
✅ Hybrid approach - Best of both worlds
✅ Beginner-friendly - Toolbar button remains discoverable
✅ Power-user features - @ mentions for speed
✅ Natural chat integration - Refinement becomes conversational
✅ Multi-block refinement - Refine multiple blocks at once
✅ Visual feedback - Block highlighting shows what's being refined
✅ Backward compatible - Current workflow preserved
Testing Checklist
Basic Functionality:
- Toolbar button still works
@thisrefines selected block@previousrefines previous block@nextrefines next block@allrefines all blocks@paragraph-1refines specific paragraph@heading-2refines specific heading
User Experience:
- Mention autocomplete appears when typing
@ - ] Mentioned blocks get highlighted with pulse animation
- Multiple blocks can be refined in one message
- Chat shows refinement summary
- Cost tracking includes refinement costs
- Error messages when blocks not found
Edge Cases:
- Invalid block reference:
@paragraph-99(out of range) - No blocks match:
@list-5when only 2 lists exist - Refinement request without mention: "Make this more concise" (uses selected block)
- Mixed: "Refine @paragraph-1 and @paragraph-2 to be more concise"
- Multiple mentions of same block: "Refine @this @this" (only refines once)
Rollback Plan
If issues occur:
- Remove mention detection from
sendMessage()in sidebar.js - Remove new endpoints from class-gutenberg-sidebar.php
- Remove mention CSS styles
- Toolbar button continues to work as fallback
Git commands:
# Rollback frontend changes
git checkout HEAD~1 assets/js/sidebar.js assets/css/sidebar.css
# Rollback backend changes
git checkout HEAD~1 includes/class-gutenberg-sidebar.php
Timeline Estimate
- Phase 1 (Backend endpoint): 2-3 hours
- Phase 2 (Frontend detection): 2-3 hours
- Phase 3 (Block resolution): 1-2 hours
- Phase 4 (Visual feedback): 1-2 hours
- Phase 5 (Testing): 1-2 hours
Total: 7-12 hours
Success Criteria
✅ Toolbar button works as before (no regression)
✅ @this mentions refine currently selected block
✅ @previous and @next work relative to selection
✅ @all refines all content blocks
✅ @paragraph-N syntax works for specific blocks
✅ Chat shows refinement summaries
✅ Mentioned blocks get visual highlight
✅ Autocomplete suggests available blocks
✅ Multi-block refinement works correctly
✅ Cost tracking includes refinement costs
✅ Error handling for invalid mentions
Future Enhancements
Phase 2 Ideas:
- Smart suggestions: "Refine all headings to be more descriptive"
- Batch operations: "Refine all lists to be more concise"
- Quick refinement: Click block → shows quick-refinement options in popover
- Refinement history: Undo/redo refinement changes
Advanced Features:
- Block type suggestions: "Suggest making this a list"
- Style transfer: "Make @paragraph-1 match the tone of @heading-2"
- Bulk refinement: "Refine all code blocks to use simpler language"
- Refinement templates: Predefined refinement options
Open Questions
-
Should
@paragraph-1be 1-based or 0-based?- I suggest 1-based (more intuitive for non-technical users)
-
Should we support content-based references?
- "Refine the block about SEO" (searches block content)
- "Refine the third paragraph" (counts paragraphs automatically)
-
Should mentions work during article generation?
- During initial article creation: "Refine @this section to add more examples"
- Could interrupt plan generation flow
-
Should we support block attributes?
- "Refine all blocks with 'team' to match company tone"
- "Refine code blocks to use simpler variable names"
Recommendation
Implement Phase 1-5 for MVP with:
- Core mention syntax (
@this,@previous,@next,@all,@type-N) - Basic visual feedback (highlighting)
- Toolbar button preservation (current workflow)
- Chat integration with summaries
Defer Phase 2 features (advanced usage patterns) to future iteration.
Ready to implement? Let me know if you want to:
- Proceed with implementation
- Adjust the approach
- Add/remove features
- Explore specific aspects in more detail