feat: consolidate docs, backend/session infra, and settings updates

This commit is contained in:
Dwindi Ramadhana
2026-05-28 00:58:20 +07:00
parent 2424acf726
commit 44e06eed88
102 changed files with 35423 additions and 11181 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,833 @@
# Implementation Plan: Hybrid Block Refinement with @ Mention Support
## Overview
Implement a hybrid refinement system that combines:
1. **Current workflow**: "AI Refine" button in block toolbar (beginner-friendly)
2. **New workflow**: `@block` mentions 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](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](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](includes/class-gutenberg-sidebar.php)
**Changes Needed:**
1. **Add new REST endpoint**: `/refine-from-chat` (or modify `/generate-plan` to handle refinement requests)
2. **Parse mentions from chat messages**: Extract `@block-ref` patterns
3. **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:**
```json
{
"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](assets/js/sidebar.js)
**Add to `sendMessage()` function:**
1. **Detect `@` mentions** in user input
2. **Extract mention patterns**:
```javascript
const mentionRegex = /@(\w+(?:-\d+)?|this|previous|next|all)/g;
```
3. **Check if message contains refinement keywords**:
- "refine", "rewrite", "edit", "improve", "change", "make it"
4. **If both detected → Treat as refinement request**
**New Message Handler:**
```javascript
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:**
1. **Receive chat message with mentions**
2. **Resolve mentions to actual blocks**
3. **Call existing `stream_block_refine()` for each block**
4. **Stream responses back to chat**
**Implementation:**
```php
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:**
```php
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](assets/css/editor.css)
**Add CSS for mentioned blocks:**
```css
.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](assets/js/sidebar.js)
**Add mention autocomplete:**
```javascript
// 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](assets/js/sidebar.js)
**Flow:**
```javascript
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:
1. Add user message as chat bubble: "Refine @paragraph-3 to be more engaging"
2. Add AI response: "✓ I've refined paragraph 3"
3. Update block content in editor
4. 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](includes/class-gutenberg-sidebar.php)
**Add new REST route:**
```php
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](assets/js/sidebar.js)
**Add to `sendMessage()` function:**
```javascript
// 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](assets/js/sidebar.js):
```javascript
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](assets/js/sidebar.js):
```javascript
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](assets/css/sidebar.css)
**Add block mention styles:**
```css
/* 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:**
1. Click block to select it
2. Click "AI Refine" in toolbar
3. Type: "Make this more engaging"
4. Click "Refine"
5. Block is refined
**Experience:** Clear, guided, discoverable
### Scenario 2: Power User (Chat Mention)
**Flow:**
1. Type in chat: "Refine @this to be more engaging"
2. System highlights mentioned block
3. Press Enter or click Send
4. Chat shows: "✅ Done! I've refined the current block."
5. Block is refined immediately
**Experience:** Fast, conversational, efficient
### Scenario 3: Multi-Block Refinement
**Flow:**
1. Type: "Refine @paragraph-1, @paragraph-2, and @paragraph-3 to be more concise"
2. All three blocks get highlighted
3. Send message
4. All three blocks refined in sequence
5. Chat shows summary
**Experience:** Powerful, batch operation
---
## File Modifications Summary
### New Files
- None (all modifications to existing files)
### Modified Files
1. **[includes/class-gutenberg-sidebar.php](includes/class-gutenberg-sidebar.php)**
- Add `handle_refine_from_chat()` method
- Add `resolve_mentions_to_blocks()` method
- Add `/refine-from-chat` REST route
- Add `stream_refinement_from_chat()` method
2. **[assets/js/sidebar.js](assets/js/sidebar.js)**
- Modify `sendMessage()` to detect refinement mentions
- Add `resolveBlockMentions()` function
- Add `handleChatRefinement()` function
- Add mention detection and autocomplete UI
3. **[assets/css/sidebar.css](assets/css/sidebar.css)**
- Add `.wpaw-block-mentioned` styles
- Add `.wpaw-pulse` animation
- Add mention autocomplete dropdown styles
4. **[assets/css/editor.css](assets/css/editor.css)**
- 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
- [ ] `@this` refines selected block
- [ ] `@previous` refines previous block
- [ ] `@next` refines next block
- [ ] `@all` refines all blocks
- [ ] `@paragraph-1` refines specific paragraph
- [ ] `@heading-2` refines 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-5` when 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:
1. Remove mention detection from `sendMessage()` in sidebar.js
2. Remove new endpoints from class-gutenberg-sidebar.php
3. Remove mention CSS styles
4. Toolbar button continues to work as fallback
**Git commands:**
```bash
# 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
1. **Should `@paragraph-1` be 1-based or 0-based?**
- I suggest **1-based** (more intuitive for non-technical users)
2. **Should we support content-based references?**
- "Refine the block about SEO" (searches block content)
- "Refine the third paragraph" (counts paragraphs automatically)
3. **Should mentions work during article generation?**
- During initial article creation: "Refine @this section to add more examples"
- Could interrupt plan generation flow
4. **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:
1. Proceed with implementation
2. Adjust the approach
3. Add/remove features
4. Explore specific aspects in more detail

View File

@@ -0,0 +1,613 @@
# Implementation Plan: Enhanced Clarification Quiz System
## Overview
Improve the clarification quiz to appear more frequently and gather comprehensive contextual information (target outcome, education level, marketing type, etc.) using predefined options users can select from.
---
## Phase 1: Add Settings Configuration
### File: `includes/class-settings.php`
**Location:** Add new section after existing settings (around line 200+)
**New Settings to Add:**
```php
/**
* Clarification Quiz Settings Section
*/
public function add_clarification_quiz_settings() {
add_settings_section(
'wp_aw_clarification_quiz',
__( 'Clarification Quiz', 'wp-agentic-writer' ),
array( $this, 'clarification_quiz_section_callback' ),
'wp-agentic-writer'
);
// Enable/Disable Quiz
add_settings_field(
'enable_clarification_quiz',
__( 'Enable Clarification Quiz', 'wp-agentic-writer' ),
array( $this, 'render_checkbox' ),
'wp-agentic-writer',
'wp_aw_clarification_quiz',
array(
'label_for' => 'enable_clarification_quiz',
'default' => true,
'description' => __( 'Automatically ask clarifying questions when context is missing.', 'wp-agentic-writer' ),
)
);
// Confidence Threshold
add_settings_field(
'clarity_confidence_threshold',
__( 'Confidence Threshold', 'wp-agentic-writer' ),
array( $this, 'render_select' ),
'wp-agentic-writer',
'wp_aw_clarification_quiz',
array(
'label_for' => 'clarity_confidence_threshold',
'default' => '0.6',
'options' => array(
'0.5' => __( 'Very Sensitive (50%) - Quiz appears frequently', 'wp-agentic-writer' ),
'0.6' => __( 'Sensitive (60%) - Recommended', 'wp-agentic-writer' ),
'0.7' => __( 'Balanced (70%)', 'wp-agentic-writer' ),
'0.8' => __( 'Strict (80%) - Current default', 'wp-agentic-writer' ),
'0.9' => __( 'Very Strict (90%) - Quiz rarely appears', 'wp-agentic-writer' ),
),
'description' => __( 'Lower threshold = quiz appears more often. Higher threshold = only very unclear requests trigger quiz.', 'wp-agentic-writer' ),
)
);
// Required Context Categories
add_settings_field(
'required_context_categories',
__( 'Required Context', 'wp-agentic-writer' ),
array( $this, 'render_multiselect' ),
'wp-agentic-writer',
'wp_aw_clarification_quiz',
array(
'label_for' => 'required_context_categories',
'default' => array( 'target_outcome', 'target_audience', 'tone', 'content_depth', 'expertise_level', 'content_type', 'pov' ),
'options' => array(
'target_outcome' => __( 'Target Outcome (education/marketing/sales)', 'wp-agentic-writer' ),
'target_audience' => __( 'Target Audience (who reads this)', 'wp-agentic-writer' ),
'tone' => __( 'Tone of Voice (formal/casual/technical)', 'wp-agentic-writer' ),
'content_depth' => __( 'Content Depth (overview/guide/analysis)', 'wp-agentic-writer' ),
'expertise_level' => __( 'Expertise Level (beginner/intermediate/advanced)', 'wp-agentic-writer' ),
'content_type' => __( 'Content Type (tutorial/opinion/how-to)', 'wp-agentic-writer' ),
'pov' => __( 'Point of View (first/third person)', 'wp-agentic-writer' ),
),
'description' => __( 'Select which context categories must be clear before writing. Uncheck categories you don\'t need.', 'wp-agentic-writer' ),
)
);
register_setting( 'wp-agentic-writer', 'enable_clarification_quiz' );
register_setting( 'wp-agentic-writer', 'clarity_confidence_threshold' );
register_setting( 'wp-agentic-writer', 'required_context_categories' );
}
```
**Helper Methods to Add:**
```php
/**
* Render multiselect field
*/
public function render_multiselect( $args ) {
$name = $args['label_for'];
$value = get_option( $name, $args['default'] );
if ( ! is_array( $value ) ) {
$value = $args['default'];
}
echo '<select multiple name="' . esc_attr( $name ) . '[]" id="' . esc_attr( $name ) . '" class="regular-text">';
foreach ( $args['options'] as $key => $label ) {
$selected = in_array( $key, $value ) ? 'selected' : '';
echo '<option value="' . esc_attr( $key ) . '" ' . $selected . '>' . esc_html( $label ) . '</option>';
}
echo '</select>';
if ( isset( $args['description'] ) ) {
echo '<p class="description">' . wp_kses_post( $args['description'] ) . '</p>';
}
}
public function clarification_quiz_section_callback() {
echo '<p>' . __( 'Configure when and how the clarification quiz appears to gather context for better article generation.', 'wp-agentic-writer' ) . '</p>';
}
```
**Hook Integration:**
Add to `__construct()` or settings initialization:
```php
add_action( 'admin_init', array( $this, 'add_clarification_quiz_settings' ), 20 );
```
---
## Phase 2: Update Clarity Check System Prompt
### File: `includes/class-gutenberg-sidebar.php`
**Location:** Lines 1195-1231 (check_clarity method)
**Replace the current system prompt with:**
```php
$required_categories = get_option( 'required_context_categories', array(
'target_outcome',
'target_audience',
'tone',
'content_depth',
'expertise_level',
'content_type',
'pov'
) );
$threshold = get_option( 'clarity_confidence_threshold', '0.6' );
$enabled = get_option( 'enable_clarification_quiz', true );
// If quiz is disabled, always return clear
if ( ! $enabled ) {
return new WP_REST_Response(
array(
'result' => array(
'is_clear' => true,
'confidence' => 1.0,
'questions' => array()
),
),
200
);
}
$system_prompt = "You are an expert editor who determines if an article request has sufficient context to write effectively.
Evaluate the user's request and determine which context categories are clear:
CATEGORIES TO EVALUATE:
1. target_outcome - What should this content achieve? (education/marketing/sales/entertainment/brand_awareness)
2. target_audience - Who is reading this? (demographics, role, knowledge level)
3. tone - How should we sound? (formal/casual/technical/friendly/professional/conversational)
4. content_depth - How comprehensive? (quick_overview/standard_guide/detailed_analysis/comprehensive)
5. expertise_level - Reader's knowledge? (beginner/intermediate/advanced/expert)
6. content_type - What format? (tutorial/how_to/opinion/comparison/listicle/case_study/news_analysis)
7. pov - Whose perspective? (first_person/third_person/expert_voice/neutral)
For each MISSING category, generate a clarifying question using PREDEFINED OPTIONS.
Use 'single_choice' or 'multiple_choice' types - NEVER 'open_text'.
QUESTION STRUCTURE:
{
'id': 'q1',
'category': 'target_outcome',
'question': 'What is the primary goal of this content?',
'type': 'single_choice',
'options': [
{ 'value': 'Education - Teach something new', 'default': true },
{ 'value': 'Marketing - Promote a product/service', 'default': false },
{ 'value': 'Sales - Drive conversions/signups', 'default': false },
{ 'value': 'Entertainment - Engage and entertain', 'default': false },
{ 'value': 'Brand Awareness - Build authority/trust', 'default': false }
]
}
CONFIDENCE CALCULATION:
- Start at 100% (1.0)
- Subtract 15% for each missing required category
- If confidence < {$threshold}, generate questions for ALL missing categories
Return ONLY valid JSON with this structure:
{
'is_clear': true/false,
'confidence': 0.0-1.0,
'missing_categories': ['category1', 'category2'],
'questions': [ ... ]
}
No markdown, no explanation - just JSON.";
$messages = array(
array(
'role' => 'system',
'content' => $system_prompt,
),
array(
'role' => 'user',
'content' => "Topic: {$topic}\n\nRequired Categories: " . implode( ', ', $required_categories ) . "\n\nEvaluate this request and determine which context is missing.",
),
);
```
---
## Phase 3: Improve Fallback Behavior
### File: `includes/class-gutenberg-sidebar.php`
**Location:** Around lines 1603-1615
**Add new helper method:**
```php
/**
* Get default clarification questions when AI fails
*
* @since 0.1.0
* @param string $topic User's topic.
* @return array Clarification result with default questions.
*/
private function get_default_clarification_questions( $topic ) {
$required_categories = get_option( 'required_context_categories', array(
'target_outcome',
'target_audience',
'tone',
'content_depth',
'expertise_level',
'content_type',
'pov'
) );
$questions = array();
$question_id = 1;
$question_templates = array(
'target_outcome' => array(
'category' => 'target_outcome',
'question' => 'What is the primary goal of this content?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Education - Teach something new', 'default' => true ),
array( 'value' => 'Marketing - Promote a product/service', 'default' => false ),
array( 'value' => 'Sales - Drive conversions', 'default' => false ),
array( 'value' => 'Entertainment - Engage readers', 'default' => false ),
array( 'value' => 'Brand Awareness - Build authority', 'default' => false ),
)
),
'target_audience' => array(
'category' => 'target_audience',
'question' => 'Who is the primary audience for this content?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'General public / Beginners', 'default' => true ),
array( 'value' => 'Professionals in the field', 'default' => false ),
array( 'value' => 'Potential customers', 'default' => false ),
array( 'value' => 'Existing customers/users', 'default' => false ),
array( 'value' => 'Industry peers / Experts', 'default' => false ),
)
),
'tone' => array(
'category' => 'tone',
'question' => 'What tone should this content have?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Professional & Authoritative', 'default' => true ),
array( 'value' => 'Friendly & Conversational', 'default' => false ),
array( 'value' => 'Technical & Detailed', 'default' => false ),
array( 'value' => 'Casual & Entertaining', 'default' => false ),
array( 'value' => 'Formal & Academic', 'default' => false ),
)
),
'content_depth' => array(
'category' => 'content_depth',
'question' => 'How comprehensive should this content be?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Quick overview (500-800 words)', 'default' => false ),
array( 'value' => 'Standard guide (800-1500 words)', 'default' => true ),
array( 'value' => 'Detailed analysis (1500-2500 words)', 'default' => false ),
array( 'value' => 'Comprehensive deep-dive (2500+ words)', 'default' => false ),
)
),
'expertise_level' => array(
'category' => 'expertise_level',
'question' => 'What is the target audience\'s expertise level?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Beginner - No prior knowledge', 'default' => true ),
array( 'value' => 'Intermediate - Basic understanding', 'default' => false ),
array( 'value' => 'Advanced - Deep technical knowledge', 'default' => false ),
array( 'value' => 'Expert - Industry professional', 'default' => false ),
)
),
'content_type' => array(
'category' => 'content_type',
'question' => 'What type of content works best for this topic?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Tutorial / How-to guide', 'default' => true ),
array( 'value' => 'Opinion / Commentary', 'default' => false ),
array( 'value' => 'Comparison / Review', 'default' => false ),
array( 'value' => 'Listicle / Tips', 'default' => false ),
array( 'value' => 'Case study', 'default' => false ),
array( 'value' => 'News analysis', 'default' => false ),
)
),
'pov' => array(
'category' => 'pov',
'question' => 'From what perspective should this be written?',
'type' => 'single_choice',
'options' => array(
array( 'value' => 'Third person (objective, "it", "they")', 'default' => true ),
array( 'value' => 'First person (personal, "I", "my")', 'default' => false ),
array( 'value' => 'Expert voice (authoritative, experienced)', 'default' => false ),
array( 'value' => 'Neutral / Unbiased', 'default' => false ),
)
),
);
foreach ( $required_categories as $category ) {
if ( isset( $question_templates[ $category ] ) ) {
$q = $question_templates[ $category ];
$q['id'] = 'q' . $question_id++;
$questions[] = $q;
}
}
return array(
'is_clear' => false,
'confidence' => 0.0,
'missing_categories' => $required_categories,
'questions' => $questions
);
}
```
**Update error handling (lines 1603-1615):**
```php
if ( is_wp_error( $response ) ) {
// Log error
error_log( 'WP Agentic Writer: Clarity check API error - ' . $response->get_error_message() );
// Use default questions instead of skipping
return $this->get_default_clarification_questions( $topic );
}
if ( null === $result ) {
// Log parse error
error_log( 'WP Agentic Writer: Failed to parse clarity check JSON' );
// Use default questions instead of skipping
return $this->get_default_clarification_questions( $topic );
}
```
---
## Phase 4: Update Frontend Quiz Display
### File: `assets/js/sidebar.js`
**Enhancement 1: Add category labels to questions**
Find the question rendering code and add category display:
```javascript
// Inside renderClarificationQuestion function
const categoryLabels = {
'target_outcome': '🎯 Target Outcome',
'target_audience': '👥 Target Audience',
'tone': '🎨 Tone of Voice',
'content_depth': '📏 Content Depth',
'expertise_level': '📊 Expertise Level',
'content_type': '📝 Content Type',
'pov': '👁️ Point of View'
};
// Add category badge above question
if (question.category && categoryLabels[question.category]) {
html += '<div class="clarification-category-badge">';
html += categoryLabels[question.category];
html += '</div>';
}
```
**Enhancement 2: Show which categories are clear**
Update progress display to show collected vs missing context:
```javascript
// Update progress display
function updateClarificationProgress(total, current, clearCategories = []) {
const progressEl = document.querySelector('.clarification-progress');
if (!progressEl) return;
let html = `<div class="progress-info">`;
html += `<span>Question ${current} of ${total}</span>`;
// Show what we already know
if (clearCategories.length > 0) {
html += `<div class="clear-context">`;
html += `<strong>Already clear:</strong> `;
html += clearCategories.map(cat => {
const labels = {
'target_outcome': 'Goal',
'target_audience': 'Audience',
'tone': 'Tone',
'content_depth': 'Depth',
'expertise_level': 'Level',
'content_type': 'Format',
'pov': 'Perspective'
};
return labels[cat] || cat;
}).join(', ');
html += `</div>`;
}
html += `</div>`;
html += `<div class="progress-bar"><div class="progress-fill" style="width: ${(current/total)*100}%"></div></div>`;
progressEl.innerHTML = html;
}
```
**Enhancement 3: Add "Skip this question" option**
```javascript
// Add skip button to question card
html += `<button class="skip-question-btn" data-question-id="${question.id}">`;
html += `Skip (not applicable)`;
html += `</button>`;
// Handle skip
document.addEventListener('click', (e) => {
if (e.target.classList.contains('skip-question-btn')) {
const questionId = e.target.dataset.questionId;
skipQuestion(questionId);
}
});
function skipQuestion(questionId) {
// Mark as skipped with "Not applicable" value
clarificationAnswers[questionId] = {
skipped: true,
value: 'Not applicable'
};
nextClarificationQuestion();
}
```
---
## Phase 5: Pass Clarification Context to Plan Generation
### File: `includes/class-gutenberg-sidebar.php`
**Location:** Find `generate_plan` method (around line 900+)
**Add clarification context integration:**
```php
// Before generating the plan, check if we have clarification answers
$clarity_context = '';
if ( ! empty( $params['clarificationAnswers'] ) && is_array( $params['clarificationAnswers'] ) ) {
$clarity_context = "\n\n=== CONTEXT FROM CLARIFICATION QUIZ ===\n";
// Group by category
$grouped = array();
foreach ( $params['clarificationAnswers'] as $answer ) {
$category = $answer['category'] ?? 'other';
$value = $answer['value'] ?? $answer['answer'] ?? '';
$skipped = $answer['skipped'] ?? false;
if ( ! $skipped && ! empty( $value ) ) {
$grouped[ $category ] = $value;
}
}
// Format for prompt
$category_labels = array(
'target_outcome' => 'Primary Goal',
'target_audience' => 'Target Audience',
'tone' => 'Tone of Voice',
'content_depth' => 'Content Depth',
'expertise_level' => 'Expertise Level',
'content_type' => 'Content Type',
'pov' => 'Point of View',
);
foreach ( $grouped as $category => $value ) {
$label = $category_labels[ $category ] ?? ucwords( str_replace( '_', ' ', $category ) );
$clarity_context .= "- {$label}: {$value}\n";
}
$clarity_context .= "=== END CONTEXT ===\n";
}
// Add to planning prompt
$plan_prompt = $clarity_context . "\n" . $plan_prompt;
```
---
## Phase 6: Testing Checklist
### Manual Testing Steps:
1. **Settings Page Test:**
- Go to WP Agentic Writer settings
- Verify new "Clarification Quiz" section appears
- Test enable/disable toggle
- Change confidence threshold to different values
- Select/deselect required context categories
- Save settings and verify they persist
2. **Vague Topic Test:**
- Enter very vague topic: "write about AI"
- Verify quiz appears with all missing categories
- Verify all questions have predefined options (no text inputs)
- Answer all questions and verify plan reflects choices
3. **Specific Topic Test:**
- Enter detailed topic with all context
- Verify quiz doesn't appear (or only asks for truly missing info)
- Plan generation proceeds immediately
4. **Threshold Test:**
- Set threshold to 0.5 (very sensitive)
- Enter semi-clear topic
- Verify quiz appears
- Set threshold to 0.9 (very strict)
- Enter same topic
- Verify quiz doesn't appear
5. **Category Filter Test:**
- Uncheck some categories in settings
- Enter vague topic
- Verify quiz only asks for checked categories
- Unchecked categories are skipped
6. **API Failure Test:**
- Temporarily break API connection (invalid key)
- Enter vague topic
- Verify fallback questions appear
- All categories show default predefined options
7. **Skip Question Test:**
- Start quiz
- Click "Skip" on a question
- Verify it moves to next question
- Verify skipped answer marked as "Not applicable"
8. **Plan Integration Test:**
- Complete full quiz with specific answers
- Generate plan
- Verify plan reflects quiz answers (tone, audience, goal, etc.)
---
## Success Criteria
✅ Settings page has Clarification Quiz section with all 3 fields
✅ Quiz triggers more frequently with 0.6 threshold (vs 0.8)
✅ All quiz questions use predefined options (no open_text type)
✅ Fallback questions appear when AI fails
✅ Clarification answers appear in generated plan
✅ Users can configure which categories are required
✅ Users can enable/disable quiz entirely
✅ Frontend shows category labels and progress
✅ Can skip individual questions
---
## Rollback Plan
If issues occur:
1. Revert `class-settings.php` changes (remove new settings)
2. Revert system prompt to original (simple 3-criteria check)
3. Revert threshold to hardcoded 0.8
4. Revert fallback to original "assume clear" behavior
Keep Git commits organized by phase for easy partial rollback.
---
## Future Enhancements (Out of Scope)
- Add custom question types via filters
- Save quiz answers per user/site for faster future generations
- A/B test different thresholds
- Analytics on which categories are most often missing
- Import/export context presets
- Multi-language support for question options

File diff suppressed because it is too large Load Diff