- Implement local backend AI provider with Ollama integration - Add Brave Search API integration for real-time search suggestions - Add image generation manager with multiple AI providers - Create hybrid provider system with local/cloud fallback - Add comprehensive settings UI with provider management - Implement Gutenberg sidebar with writing assistance controls - Add SEO schema generation for AI-generated content - Multiple provider support: OpenRouter, local backend, Codex
861 lines
30 KiB
Markdown
861 lines
30 KiB
Markdown
# Focus Keyword Anchor System & Image Generation Fixes
|
|
|
|
**Date:** January 30, 2026
|
|
**Version:** 1.0
|
|
**Status:** Implementation Plan
|
|
|
|
---
|
|
|
|
## Table of Contents
|
|
|
|
1. [Executive Summary](#executive-summary)
|
|
2. [Part A: Focus Keyword Anchor System](#part-a-focus-keyword-anchor-system)
|
|
3. [Part B: Image Generation Fixes](#part-b-image-generation-fixes)
|
|
4. [Part C: UI Redesign](#part-c-ui-redesign)
|
|
5. [Implementation Priority](#implementation-priority)
|
|
|
|
---
|
|
|
|
## Executive Summary
|
|
|
|
This document addresses three interconnected issues:
|
|
|
|
| Issue | Root Cause | Solution |
|
|
|-------|------------|----------|
|
|
| Context loss during conversation | No persistent topic anchor | Focus Keyword as central context driver |
|
|
| Image tables not created | Activation hook not re-run | Manual table creation + version check |
|
|
| Image generation errors | Method name mismatch + missing public method | Fix method signatures |
|
|
| Image toolbar not showing | Script dependency or block attribute issues | Debug and fix filter |
|
|
|
|
---
|
|
|
|
# Part A: Focus Keyword Anchor System
|
|
|
|
## Problem Statement
|
|
|
|
The agent loses conversation context because:
|
|
- Generic topic passed to outline generation
|
|
- Chat history truncated to last 10 messages
|
|
- No persistent anchor for user's intent
|
|
- Recency bias causes LLM to focus on recent refinements
|
|
|
|
## Solution: Focus Keyword as Context Anchor
|
|
|
|
### Core Concept
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ Focus Keyword = Single Source of Truth for Article Topic │
|
|
│ │
|
|
│ • Always visible at top of chat │
|
|
│ • Drives ALL API calls (clarity, planning, writing) │
|
|
│ • Accumulates suggestions from each AI response │
|
|
│ • User can select, change, or enter custom keyword │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### UI Design
|
|
|
|
#### Location: Top of Chatbox (Replacing Context Indicator)
|
|
|
|
**Current UI:**
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ [1 messages] [$0.0221] [~500 tokens] ............ [↕] │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**New UI (Compact - Default):**
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 🎯 [Switch Career Usia 30+ ▼] [$0.02] ............ [↕] │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
**New UI (Expanded - When textarea expanded):**
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 🎯 Focus Keyword │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Switch Career Usia 30+ [▼] │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ Suggestions: │
|
|
│ ○ Switch Career Usia 30+ (from response #1) │
|
|
│ ○ Vibe Coding untuk Pemula (from response #2) │
|
|
│ ○ AI Web Design Tanpa Coding (from response #3) │
|
|
│ ○ Custom... │
|
|
│ │
|
|
│ Session: $0.02 │ ~500 tokens │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
### Dropdown Behavior
|
|
|
|
1. **Empty State**: Placeholder "Select or enter focus keyword..."
|
|
2. **After Response #1**: 1 suggestion appears
|
|
3. **After Response #2**: 2 suggestions (cumulative)
|
|
4. **Max 5 suggestions**: Older ones rotate out, "Show all" option
|
|
5. **Custom Option**: Always last, triggers text input
|
|
6. **Selection**: Immediately saves to `postConfig.focus_keyword`
|
|
|
|
### Data Flow
|
|
|
|
```
|
|
User Message
|
|
│
|
|
▼
|
|
┌────────────────┐
|
|
│ AI Response │
|
|
│ + Extract │──────► Add to suggestions dropdown
|
|
│ keyword │
|
|
└────────────────┘
|
|
│
|
|
▼
|
|
User Selects Keyword (or AI auto-selects first suggestion)
|
|
│
|
|
▼
|
|
┌────────────────────────────────────────────────────────────┐
|
|
│ ALL subsequent API calls include: │
|
|
│ { │
|
|
│ focus_keyword: "Switch Career Usia 30+", │
|
|
│ topic: "...", │
|
|
│ chatHistory: [...], │
|
|
│ ... │
|
|
│ } │
|
|
└────────────────────────────────────────────────────────────┘
|
|
│
|
|
▼
|
|
Backend System Prompt includes:
|
|
"PRIMARY TOPIC: {focus_keyword}
|
|
All content must relate to this topic.
|
|
Recent conversation refinements should ENHANCE this topic, not replace it."
|
|
```
|
|
|
|
### Keyword Extraction Logic
|
|
|
|
```javascript
|
|
// In chat response handler
|
|
const extractFocusKeywordSuggestion = (aiResponse) => {
|
|
// Method 1: AI explicitly suggests (preferred)
|
|
// Look for pattern: "Focus Keyword Suggestion: ..."
|
|
const explicitMatch = aiResponse.match(/focus keyword suggestion[:\s]+["']?([^"'\n]+)["']?/i);
|
|
if (explicitMatch) return explicitMatch[1].trim();
|
|
|
|
// Method 2: Extract from first heading or bold text
|
|
const headingMatch = aiResponse.match(/^#+\s+(.+)$/m);
|
|
if (headingMatch) return headingMatch[1].trim();
|
|
|
|
// Method 3: Extract prominent phrase (first bold)
|
|
const boldMatch = aiResponse.match(/\*\*([^*]+)\*\*/);
|
|
if (boldMatch) return boldMatch[1].trim();
|
|
|
|
return null;
|
|
};
|
|
```
|
|
|
|
### Backend Integration
|
|
|
|
#### 1. Update System Prompts
|
|
|
|
**File:** `includes/class-gutenberg-sidebar.php`
|
|
|
|
```php
|
|
// In stream_generate_plan() - Line ~1723
|
|
$focus_keyword = $post_config['focus_keyword'] ?? '';
|
|
$focus_keyword_instruction = '';
|
|
if (!empty($focus_keyword)) {
|
|
$focus_keyword_instruction = "
|
|
PRIMARY TOPIC ANCHOR: \"{$focus_keyword}\"
|
|
|
|
CRITICAL: This article MUST be about \"{$focus_keyword}\".
|
|
- The title MUST include or relate to \"{$focus_keyword}\"
|
|
- All sections MUST support this primary topic
|
|
- Recent conversation refinements are meant to ENHANCE this topic, not REPLACE it
|
|
- If user discussed sub-topics (e.g., AI tools, web design), treat them as ASPECTS of the primary topic
|
|
";
|
|
}
|
|
|
|
$system_prompt = "You are an expert content strategist...
|
|
|
|
{$focus_keyword_instruction}
|
|
|
|
IMPORTANT CONSTRAINT: {$section_limit}
|
|
...";
|
|
```
|
|
|
|
#### 2. Update Chat Response to Include Keyword Suggestion
|
|
|
|
**File:** `includes/class-gutenberg-sidebar.php`
|
|
|
|
```php
|
|
// In handle_chat_request() - Add to system prompt
|
|
$system_prompt .= "
|
|
|
|
At the END of your response, if appropriate, suggest a focus keyword for the article in this format:
|
|
**Focus Keyword Suggestion:** [your suggested keyword]
|
|
|
|
The keyword should be:
|
|
- 2-5 words
|
|
- SEO-friendly
|
|
- Capture the main topic discussed
|
|
";
|
|
```
|
|
|
|
#### 3. Persist Focus Keyword
|
|
|
|
**File:** `includes/class-gutenberg-sidebar.php`
|
|
|
|
```php
|
|
// In handle_generate_plan() - Line ~1237
|
|
$focus_keyword = $post_config['focus_keyword'] ?? '';
|
|
|
|
// Save to post meta for persistence
|
|
if ($post_id > 0 && !empty($focus_keyword)) {
|
|
update_post_meta($post_id, '_wpaw_focus_keyword', $focus_keyword);
|
|
}
|
|
```
|
|
|
|
### Frontend Implementation
|
|
|
|
#### 1. New State Variables
|
|
|
|
**File:** `assets/js/sidebar.js`
|
|
|
|
```javascript
|
|
// Add new state
|
|
const [focusKeywordSuggestions, setFocusKeywordSuggestions] = wp.element.useState([]);
|
|
const [selectedFocusKeyword, setSelectedFocusKeyword] = wp.element.useState('');
|
|
|
|
// Load from postConfig on mount
|
|
wp.element.useEffect(() => {
|
|
if (postConfig.focus_keyword) {
|
|
setSelectedFocusKeyword(postConfig.focus_keyword);
|
|
}
|
|
}, [postConfig.focus_keyword]);
|
|
```
|
|
|
|
#### 2. Update postConfig When Keyword Changes
|
|
|
|
```javascript
|
|
const handleFocusKeywordChange = (keyword) => {
|
|
setSelectedFocusKeyword(keyword);
|
|
updatePostConfig('focus_keyword', keyword);
|
|
};
|
|
```
|
|
|
|
#### 3. Extract Suggestions from AI Responses
|
|
|
|
```javascript
|
|
// In streaming response handler, after accumulating content
|
|
const suggestion = extractFocusKeywordSuggestion(accumulatedContent);
|
|
if (suggestion && !focusKeywordSuggestions.includes(suggestion)) {
|
|
setFocusKeywordSuggestions(prev => {
|
|
const updated = [...prev, suggestion];
|
|
// Keep max 5 suggestions
|
|
return updated.slice(-5);
|
|
});
|
|
|
|
// Auto-select first suggestion if none selected
|
|
if (!selectedFocusKeyword) {
|
|
handleFocusKeywordChange(suggestion);
|
|
}
|
|
}
|
|
```
|
|
|
|
#### 4. Render Focus Keyword Bar (Replacing Context Indicator)
|
|
|
|
```javascript
|
|
const renderFocusKeywordBar = () => {
|
|
return wp.element.createElement('div', {
|
|
className: 'wpaw-focus-keyword-bar'
|
|
},
|
|
// Keyword dropdown
|
|
wp.element.createElement('div', { className: 'wpaw-focus-keyword-wrapper' },
|
|
wp.element.createElement('span', { className: 'wpaw-focus-keyword-icon' }, '🎯'),
|
|
wp.element.createElement('select', {
|
|
className: 'wpaw-focus-keyword-select',
|
|
value: selectedFocusKeyword,
|
|
onChange: (e) => {
|
|
if (e.target.value === '__custom__') {
|
|
// Show custom input
|
|
setShowCustomKeywordInput(true);
|
|
} else {
|
|
handleFocusKeywordChange(e.target.value);
|
|
}
|
|
}
|
|
},
|
|
wp.element.createElement('option', { value: '' }, 'Select focus keyword...'),
|
|
focusKeywordSuggestions.map((kw, idx) =>
|
|
wp.element.createElement('option', { key: idx, value: kw }, kw)
|
|
),
|
|
wp.element.createElement('option', { value: '__custom__' }, '+ Custom keyword...')
|
|
)
|
|
),
|
|
|
|
// Cost display (compact)
|
|
wp.element.createElement('span', { className: 'wpaw-cost-compact' },
|
|
'$' + cost.session.toFixed(2)
|
|
),
|
|
|
|
// Expand button
|
|
wp.element.createElement('button', {
|
|
className: 'wpaw-expand-btn',
|
|
onClick: () => setIsTextareaExpanded(!isTextareaExpanded)
|
|
}, isTextareaExpanded ? '↓' : '↑')
|
|
);
|
|
};
|
|
```
|
|
|
|
### CSS Styling
|
|
|
|
```css
|
|
.wpaw-focus-keyword-bar {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 8px;
|
|
padding: 6px 10px;
|
|
background: #1e1e1e;
|
|
border-bottom: 1px solid #3c3c3c;
|
|
font-size: 12px;
|
|
}
|
|
|
|
.wpaw-focus-keyword-wrapper {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 4px;
|
|
flex: 1;
|
|
}
|
|
|
|
.wpaw-focus-keyword-select {
|
|
flex: 1;
|
|
background: #2c2c2c;
|
|
border: 1px solid #3c3c3c;
|
|
color: #fff;
|
|
padding: 4px 8px;
|
|
border-radius: 4px;
|
|
font-size: 12px;
|
|
max-width: 200px;
|
|
}
|
|
|
|
.wpaw-focus-keyword-select:focus {
|
|
border-color: #007cba;
|
|
outline: none;
|
|
}
|
|
|
|
.wpaw-cost-compact {
|
|
color: #a7aaad;
|
|
font-size: 11px;
|
|
}
|
|
|
|
.wpaw-expand-btn {
|
|
background: transparent;
|
|
border: none;
|
|
color: #a7aaad;
|
|
cursor: pointer;
|
|
padding: 4px;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# Part B: Image Generation Fixes
|
|
|
|
## Error #1: Missing Database Tables
|
|
|
|
### Symptoms
|
|
```
|
|
WordPress database error Unknown error 1146 for query
|
|
SELECT * FROM wp_wpaw_images_variants...
|
|
```
|
|
|
|
### Root Cause
|
|
Tables are created in activation hook, but plugin was already active when code was added. Activation hook only runs on fresh activation.
|
|
|
|
### Fix: Add Version-Based Table Creation
|
|
|
|
**File:** `wp-agentic-writer.php`
|
|
|
|
```php
|
|
// Add after plugin initialization
|
|
add_action('plugins_loaded', 'wp_agentic_writer_maybe_create_tables');
|
|
|
|
function wp_agentic_writer_maybe_create_tables() {
|
|
$current_version = get_option('wpaw_db_version', '0');
|
|
$required_version = '1.1.0'; // Bump when adding new tables
|
|
|
|
if (version_compare($current_version, $required_version, '<')) {
|
|
// Create cost tracking table
|
|
wp_agentic_writer_create_cost_table();
|
|
|
|
// Create image management tables
|
|
WP_Agentic_Writer_Image_Manager::get_instance()->create_tables();
|
|
|
|
// Update version
|
|
update_option('wpaw_db_version', $required_version);
|
|
}
|
|
}
|
|
```
|
|
|
|
### Immediate Fix (Manual)
|
|
|
|
Run this SQL in phpMyAdmin or WP-CLI:
|
|
|
|
```sql
|
|
-- Table 1: wp_wpaw_images
|
|
CREATE TABLE IF NOT EXISTS `wp_wpaw_images` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`post_id` bigint(20) NOT NULL,
|
|
`agent_image_id` varchar(50) NOT NULL,
|
|
`placement` varchar(50) NOT NULL,
|
|
`section_title` text,
|
|
`prompt_initial` text,
|
|
`prompt_refined` text,
|
|
`alt_text_initial` text,
|
|
`alt_text_refined` text,
|
|
`image_model` varchar(100),
|
|
`status` varchar(20) DEFAULT 'pending',
|
|
`selected_variant_id` bigint(20) DEFAULT NULL,
|
|
`attachment_id` bigint(20) DEFAULT NULL,
|
|
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
`updated_at` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
UNIQUE KEY `idx_agent_image_id` (`agent_image_id`),
|
|
KEY `idx_post_id` (`post_id`),
|
|
KEY `idx_status` (`status`),
|
|
KEY `idx_created` (`created_at`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
|
|
-- Table 2: wp_wpaw_images_variants
|
|
CREATE TABLE IF NOT EXISTS `wp_wpaw_images_variants` (
|
|
`id` bigint(20) NOT NULL AUTO_INCREMENT,
|
|
`agentic_image_id` bigint(20) NOT NULL,
|
|
`post_id` bigint(20) NOT NULL,
|
|
`variant_number` tinyint(3) NOT NULL,
|
|
`prompt_used` text,
|
|
`temp_url` text,
|
|
`temp_path` text,
|
|
`generation_model` varchar(100),
|
|
`generation_cost` decimal(10,6) DEFAULT 0,
|
|
`width` int(11) DEFAULT NULL,
|
|
`height` int(11) DEFAULT NULL,
|
|
`status` varchar(20) DEFAULT 'temp',
|
|
`attachment_id` bigint(20) DEFAULT NULL,
|
|
`created_at` datetime DEFAULT CURRENT_TIMESTAMP,
|
|
PRIMARY KEY (`id`),
|
|
KEY `idx_agentic_image_id` (`agentic_image_id`),
|
|
KEY `idx_post_id` (`post_id`),
|
|
KEY `idx_status` (`status`)
|
|
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
|
|
```
|
|
|
|
---
|
|
|
|
## Error #2: Undefined Method `save_image_recommendation()`
|
|
|
|
### Symptoms
|
|
```
|
|
PHP Fatal error: Call to undefined method
|
|
WP_Agentic_Writer_Image_Manager::save_image_recommendation()
|
|
```
|
|
|
|
### Root Cause
|
|
|
|
**Caller (class-gutenberg-sidebar.php:2185):**
|
|
```php
|
|
$image_manager->save_image_recommendation(
|
|
$post_id,
|
|
$agent_image_id,
|
|
'section_' . $section_id,
|
|
$heading,
|
|
trim( $description ),
|
|
trim( $description )
|
|
);
|
|
```
|
|
|
|
**Actual Method (class-image-manager.php:320):**
|
|
```php
|
|
private function save_image_recommendations( $post_id, $images ) {
|
|
// Takes array of images, not individual params
|
|
}
|
|
```
|
|
|
|
### Issues:
|
|
1. Method name is plural (`save_image_recommendations`) vs singular (`save_image_recommendation`)
|
|
2. Method is `private`, not `public`
|
|
3. Method signature is different (expects array of images)
|
|
|
|
### Fix: Add Public Method with Correct Signature
|
|
|
|
**File:** `includes/class-image-manager.php`
|
|
|
|
Add after line 340:
|
|
|
|
```php
|
|
/**
|
|
* Save single image recommendation to database.
|
|
*
|
|
* @param int $post_id Post ID.
|
|
* @param string $agent_image_id Unique image identifier.
|
|
* @param string $placement Placement location.
|
|
* @param string $section_title Section title.
|
|
* @param string $prompt Image prompt/description.
|
|
* @param string $alt_text Alt text for image.
|
|
* @return int|false Insert ID or false on failure.
|
|
*/
|
|
public function save_image_recommendation( $post_id, $agent_image_id, $placement, $section_title, $prompt, $alt_text ) {
|
|
global $wpdb;
|
|
$table = $wpdb->prefix . 'wpaw_images';
|
|
|
|
$settings = get_option( 'wp_agentic_writer_settings', array() );
|
|
$image_model = $settings['image_model'] ?? 'openai/gpt-4o';
|
|
|
|
$result = $wpdb->insert(
|
|
$table,
|
|
array(
|
|
'post_id' => $post_id,
|
|
'agent_image_id' => $agent_image_id,
|
|
'placement' => $placement,
|
|
'section_title' => $section_title,
|
|
'prompt_initial' => $prompt,
|
|
'alt_text_initial' => $alt_text,
|
|
'image_model' => $image_model,
|
|
'status' => 'pending',
|
|
),
|
|
array( '%d', '%s', '%s', '%s', '%s', '%s', '%s', '%s' )
|
|
);
|
|
|
|
if ( $result ) {
|
|
return $wpdb->insert_id;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Error #3: Image Block Toolbar Not Showing
|
|
|
|
### Symptoms
|
|
No "Generate AI Image" button appears in image block toolbar.
|
|
|
|
### Potential Causes
|
|
|
|
1. **Script not loaded**: Check browser console for errors
|
|
2. **Block attribute missing**: `data-agent-image-id` not set on blocks
|
|
3. **Filter not applied**: WordPress filter may not be working
|
|
|
|
### Debug Steps
|
|
|
|
**Step 1: Check if script is loaded**
|
|
```javascript
|
|
// In browser console
|
|
console.log(typeof wp.hooks.applyFilters);
|
|
console.log(wp.hooks.hasFilter('editor.BlockEdit', 'wp-agentic-writer/image-generate-toolbar'));
|
|
```
|
|
|
|
**Step 2: Check if blocks have the attribute**
|
|
```javascript
|
|
// In browser console
|
|
wp.data.select('core/block-editor').getBlocks().forEach(block => {
|
|
if (block.name === 'core/image') {
|
|
console.log('Image block:', block.clientId, block.attributes);
|
|
}
|
|
});
|
|
```
|
|
|
|
**Step 3: Verify attribute exists**
|
|
|
|
The issue may be that `data-agent-image-id` is stored in block HTML but not in attributes.
|
|
|
|
### Fix: Update Block Detection Logic
|
|
|
|
**File:** `assets/js/block-image-generate.js`
|
|
|
|
The current code checks `block.attributes['data-agent-image-id']`, but the attribute might be stored differently.
|
|
|
|
```javascript
|
|
// Current (may not work)
|
|
const agentImageId = block?.attributes?.['data-agent-image-id'];
|
|
|
|
// Fix: Also check innerHTML for the attribute
|
|
const hasAgentImageId = () => {
|
|
if (block?.attributes?.['data-agent-image-id']) return true;
|
|
|
|
// Check innerHTML
|
|
const innerHTML = block?.attributes?.innerHTML || '';
|
|
if (innerHTML.includes('data-agent-image-id')) return true;
|
|
|
|
// Check original HTML
|
|
const originalContent = block?.originalContent || '';
|
|
if (originalContent.includes('data-agent-image-id')) return true;
|
|
|
|
return false;
|
|
};
|
|
|
|
if (!hasAgentImageId()) {
|
|
return wp.element.createElement(BlockEdit, props);
|
|
}
|
|
```
|
|
|
|
### Alternative Fix: Check for Placeholder Images
|
|
|
|
If the image block has no `url` but has `alt` text, it's likely a placeholder:
|
|
|
|
```javascript
|
|
const isPlaceholder = block?.name === 'core/image' &&
|
|
!block?.attributes?.url &&
|
|
block?.attributes?.alt;
|
|
|
|
if (!agentImageId && !isPlaceholder) {
|
|
return wp.element.createElement(BlockEdit, props);
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
# Part C: UI Redesign
|
|
|
|
## Current Context Indicator Bar
|
|
|
|
**Location:** `assets/js/sidebar.js` - `renderContextIndicator()`
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ [💬 1 messages] [💰 $0.0221] [~500 tokens] ..... [↕] │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## New Focus Keyword Bar Design
|
|
|
|
### Compact Mode (Default)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 🎯 [Switch Career Usia 30+ ▼] [$0.02] [↕] │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
│ │ │
|
|
└─ Dropdown with suggestions └─ Session cost └─ Expand
|
|
```
|
|
|
|
### Expanded Mode (When textarea expanded)
|
|
|
|
```
|
|
┌─────────────────────────────────────────────────────────────┐
|
|
│ 🎯 FOCUS KEYWORD │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ Switch Career Usia 30+ [▼] │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ 📝 AI Suggestions: │
|
|
│ ┌─────────────────────────────────────────────────────────┐ │
|
|
│ │ ● Switch Career Usia 30+ (Response #1) │ │
|
|
│ │ ○ Vibe Coding untuk Pemula (Response #2) │ │
|
|
│ │ ○ AI Web Design Tanpa Coding (Response #3) │ │
|
|
│ │ ○ + Enter custom keyword... │ │
|
|
│ └─────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ 💰 $0.02 this session │ 📊 ~500 tokens │
|
|
└─────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
## Code Changes Required
|
|
|
|
### Replace `renderContextIndicator()` with `renderFocusKeywordBar()`
|
|
|
|
**File:** `assets/js/sidebar.js`
|
|
|
|
```javascript
|
|
// REMOVE this function
|
|
const renderContextIndicator = () => { ... }
|
|
|
|
// ADD this function
|
|
const renderFocusKeywordBar = () => {
|
|
const hasKeyword = selectedFocusKeyword && selectedFocusKeyword.length > 0;
|
|
|
|
if (isTextareaExpanded) {
|
|
return renderExpandedFocusKeywordBar();
|
|
}
|
|
|
|
// Compact mode
|
|
return wp.element.createElement('div', {
|
|
className: 'wpaw-focus-keyword-bar wpaw-compact'
|
|
},
|
|
wp.element.createElement('div', { className: 'wpaw-fk-left' },
|
|
wp.element.createElement('span', { className: 'wpaw-fk-icon' }, '🎯'),
|
|
wp.element.createElement('select', {
|
|
className: 'wpaw-fk-select',
|
|
value: selectedFocusKeyword || '',
|
|
onChange: handleKeywordSelect,
|
|
disabled: isLoading
|
|
},
|
|
wp.element.createElement('option', { value: '' },
|
|
hasKeyword ? 'Change keyword...' : 'Select focus keyword...'
|
|
),
|
|
...focusKeywordSuggestions.map((kw, i) =>
|
|
wp.element.createElement('option', { key: i, value: kw },
|
|
kw.length > 25 ? kw.substring(0, 25) + '...' : kw
|
|
)
|
|
),
|
|
wp.element.createElement('option', { value: '__custom__' }, '+ Custom...')
|
|
)
|
|
),
|
|
wp.element.createElement('span', { className: 'wpaw-fk-cost' },
|
|
'$' + (cost.session || 0).toFixed(2)
|
|
),
|
|
wp.element.createElement('button', {
|
|
className: 'wpaw-fk-expand',
|
|
onClick: () => setIsTextareaExpanded(true),
|
|
title: 'Expand'
|
|
}, '↕')
|
|
);
|
|
};
|
|
|
|
const renderExpandedFocusKeywordBar = () => {
|
|
return wp.element.createElement('div', {
|
|
className: 'wpaw-focus-keyword-bar wpaw-expanded'
|
|
},
|
|
// Header
|
|
wp.element.createElement('div', { className: 'wpaw-fk-header' },
|
|
wp.element.createElement('span', null, '🎯 FOCUS KEYWORD'),
|
|
wp.element.createElement('button', {
|
|
className: 'wpaw-fk-collapse',
|
|
onClick: () => setIsTextareaExpanded(false)
|
|
}, '↓')
|
|
),
|
|
|
|
// Main input
|
|
wp.element.createElement('div', { className: 'wpaw-fk-main-input' },
|
|
showCustomKeywordInput
|
|
? wp.element.createElement('input', {
|
|
type: 'text',
|
|
className: 'wpaw-fk-custom-input',
|
|
placeholder: 'Enter custom focus keyword...',
|
|
value: customKeywordInput,
|
|
onChange: (e) => setCustomKeywordInput(e.target.value),
|
|
onKeyDown: (e) => {
|
|
if (e.key === 'Enter') {
|
|
handleFocusKeywordChange(customKeywordInput);
|
|
setShowCustomKeywordInput(false);
|
|
}
|
|
},
|
|
autoFocus: true
|
|
})
|
|
: wp.element.createElement('select', {
|
|
className: 'wpaw-fk-select-full',
|
|
value: selectedFocusKeyword || '',
|
|
onChange: handleKeywordSelect
|
|
},
|
|
wp.element.createElement('option', { value: '' }, 'Select focus keyword...'),
|
|
...focusKeywordSuggestions.map((kw, i) =>
|
|
wp.element.createElement('option', { key: i, value: kw }, kw)
|
|
),
|
|
wp.element.createElement('option', { value: '__custom__' }, '+ Enter custom keyword...')
|
|
)
|
|
),
|
|
|
|
// Suggestions list
|
|
focusKeywordSuggestions.length > 0 && wp.element.createElement('div', {
|
|
className: 'wpaw-fk-suggestions'
|
|
},
|
|
wp.element.createElement('div', { className: 'wpaw-fk-suggestions-label' },
|
|
'📝 AI Suggestions:'
|
|
),
|
|
focusKeywordSuggestions.map((kw, i) =>
|
|
wp.element.createElement('div', {
|
|
key: i,
|
|
className: 'wpaw-fk-suggestion-item' + (kw === selectedFocusKeyword ? ' selected' : ''),
|
|
onClick: () => handleFocusKeywordChange(kw)
|
|
},
|
|
wp.element.createElement('span', { className: 'wpaw-fk-radio' },
|
|
kw === selectedFocusKeyword ? '●' : '○'
|
|
),
|
|
wp.element.createElement('span', { className: 'wpaw-fk-suggestion-text' }, kw),
|
|
wp.element.createElement('span', { className: 'wpaw-fk-suggestion-source' },
|
|
'(Response #' + (i + 1) + ')'
|
|
)
|
|
)
|
|
)
|
|
),
|
|
|
|
// Stats
|
|
wp.element.createElement('div', { className: 'wpaw-fk-stats' },
|
|
wp.element.createElement('span', null, '💰 $' + (cost.session || 0).toFixed(2) + ' this session'),
|
|
wp.element.createElement('span', null, '│'),
|
|
wp.element.createElement('span', null, '📊 ~' + (messages.length * 500) + ' tokens')
|
|
)
|
|
);
|
|
};
|
|
```
|
|
|
|
---
|
|
|
|
# Implementation Priority
|
|
|
|
## Phase 1: Critical Fixes (Day 1)
|
|
|
|
| Task | File | Priority |
|
|
|------|------|----------|
|
|
| Create missing database tables | Manual SQL or add version check | CRITICAL |
|
|
| Add `save_image_recommendation()` method | `class-image-manager.php` | CRITICAL |
|
|
| Fix image toolbar block detection | `block-image-generate.js` | HIGH |
|
|
|
|
## Phase 2: Focus Keyword System (Day 2-3)
|
|
|
|
| Task | File | Priority |
|
|
|------|------|----------|
|
|
| Add state variables for focus keyword | `sidebar.js` | HIGH |
|
|
| Implement keyword extraction from responses | `sidebar.js` | HIGH |
|
|
| Create `renderFocusKeywordBar()` | `sidebar.js` | HIGH |
|
|
| Add CSS styling | `sidebar.css` | MEDIUM |
|
|
| Update backend to use focus_keyword | `class-gutenberg-sidebar.php` | HIGH |
|
|
|
|
## Phase 3: Integration & Testing (Day 4)
|
|
|
|
| Task | Priority |
|
|
|------|----------|
|
|
| Test image generation flow end-to-end | HIGH |
|
|
| Test focus keyword persistence across modes | HIGH |
|
|
| Test context continuity with focus keyword | HIGH |
|
|
| Verify outline generation uses focus keyword | HIGH |
|
|
|
|
---
|
|
|
|
# Files to Modify Summary
|
|
|
|
| File | Changes |
|
|
|------|---------|
|
|
| `wp-agentic-writer.php` | Add `plugins_loaded` hook for table creation |
|
|
| `includes/class-image-manager.php` | Add public `save_image_recommendation()` method |
|
|
| `assets/js/block-image-generate.js` | Fix block detection logic |
|
|
| `assets/js/sidebar.js` | Replace context indicator with focus keyword bar |
|
|
| `assets/css/sidebar.css` | Add focus keyword bar styles |
|
|
| `includes/class-gutenberg-sidebar.php` | Update prompts to use focus_keyword |
|
|
|
|
---
|
|
|
|
# Testing Checklist
|
|
|
|
## Image Generation
|
|
- [ ] Tables exist in database
|
|
- [ ] No PHP errors on article generation
|
|
- [ ] Image toolbar button appears on placeholder images
|
|
- [ ] Modal opens when clicking toolbar button
|
|
- [ ] Image generation works end-to-end
|
|
|
|
## Focus Keyword System
|
|
- [ ] Focus keyword bar appears above chat input
|
|
- [ ] Suggestions accumulate after each AI response
|
|
- [ ] Selecting keyword updates postConfig
|
|
- [ ] Custom keyword input works
|
|
- [ ] Expanded view shows all suggestions
|
|
- [ ] Keyword persists after page refresh
|
|
- [ ] Outline generation uses selected keyword
|
|
- [ ] Context maintained across chat → planning → writing modes
|
|
|
|
---
|
|
|
|
**Document Status:** Ready for Implementation
|
|
**Last Updated:** January 30, 2026
|