Skip to content
Merged
Changes from 1 commit
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
0a7405d
Add Comment Moderation experiment
Jameswlepage Dec 19, 2025
a27bb86
Merge branch 'develop' into feature/comment-moderation
jeffpaul Dec 19, 2025
03966de
Update readme with comment moderation feature
jeffpaul Dec 20, 2025
0ee4cd8
Create screenshot-#.png
jeffpaul Dec 20, 2025
9eae9fd
Merge branch 'develop' into feature/comment-moderation
dkotter Feb 3, 2026
5443ec1
Ensure we use the proper function to avoid errors
dkotter Feb 3, 2026
2729f85
Merge branch 'develop' into feature/comment-moderation
dkotter Apr 14, 2026
c066377
Migrate experiment over to being a feature and ensure it's loaded pro…
dkotter Apr 14, 2026
f3662fa
Clean up the Comment_Analysis Ability
dkotter Apr 14, 2026
c4872e9
Set a json schema instead of asking the LLM to return in json. Fix a …
dkotter Apr 14, 2026
ffcd9a6
Fix wrong string
dkotter Apr 15, 2026
2db6ad1
Merge branch 'develop' into feature/comment-moderation
dkotter Apr 22, 2026
83e5ae4
Remove the comment reply Ability for now
dkotter Apr 22, 2026
3c3f72b
Remove more code around comment replies. Code cleanup
dkotter Apr 22, 2026
3bf92ee
Fix lint errors; remove more comment reply code; move our columns aft…
dkotter Apr 22, 2026
570f76e
Remove our query arg after processing
dkotter Apr 22, 2026
d85a89c
When a new comment is created, send it through our comment analysis a…
dkotter Apr 22, 2026
4d7b5e1
Update docs
dkotter Apr 22, 2026
fb99112
Add unit tests
dkotter Apr 22, 2026
e3587c6
Add E2E tests
dkotter Apr 22, 2026
de8395a
Change category to admin
dkotter Apr 22, 2026
8cde2b5
Fix lint and test errors
dkotter Apr 22, 2026
b7ee30f
Fix an infinite retry loop on failed analysis. Fix plugin check error
dkotter Apr 22, 2026
922bbb2
Add an inline link to trigger analysis. Fix a potential issue with re…
dkotter Apr 22, 2026
61e8a4e
Merge branch 'develop' into feature/comment-moderation
dkotter Apr 23, 2026
317a954
Run format
dkotter Apr 23, 2026
63a07a7
Merge branch 'develop' into feature/comment-moderation
dkotter May 6, 2026
df9f427
Update text used for the comment analysis action. Add a filter around…
dkotter May 6, 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
Prev Previous commit
Next Next commit
Update docs
  • Loading branch information
dkotter committed Apr 22, 2026
commit 4d7b5e1b4e1cc53ea361526189369acd8d1a8b06
251 changes: 222 additions & 29 deletions docs/experiments/comment-moderation.md
Original file line number Diff line number Diff line change
@@ -1,34 +1,227 @@
# Comment Moderation

## Summary
Adds AI-powered sentiment analysis, toxicity scoring, and reply suggestions to the classic Comments screen. Moderators can see badges directly in `edit-comments.php`, run bulk analysis, and request suggested replies without leaving wp-admin.

## Key Hooks & Entry Points
- `WordPress\AI\Experiments\Comment_Moderation\Comment_Moderation::register()` wires everything once the experiment is enabled:
- `wp_abilities_api_init` → registers `ai/comment-analysis` and `ai/reply-suggestion` abilities (`includes/Abilities/Comment_Moderation/*.php`).
- `manage_edit-comments_columns`, `manage_comments_custom_column` → inject sentiment/toxicity columns.
- `bulk_actions-edit-comments`, `handle_bulk_actions-edit-comments`, `admin_notices` → add the “Analyze with AI” bulk flow and status notices.
- `comment_row_actions` → adds the “AI Reply” row action.
- `admin_enqueue_scripts` → enqueues the React bundle on `edit-comments.php`.
- `admin_head-edit-comments.php` → prints inline badge styles so they render even when JS fails.
- REST and comment-meta updates happen via the two abilities; the experiment itself only orchestrates UI + enqueue points.

## Assets & Data Flow
1. `enqueue_assets()` loads `experiments/comment-moderation` (`src/experiments/comment-moderation/index.tsx`) and localizes `window.CommentModerationData` with `enabled` + nonce.
2. The React entry mounts two controllers:
- `LazyAnalysisController` polls for comments that need analysis and calls `runAbility( 'ai/comment-analysis' )`, updating comment meta and refreshing rows in place.
- `ReplyModalController` opens a modal when an “AI Reply” row action is clicked, calling `runAbility( 'ai/reply-suggestion' )` to fetch draft replies the moderator can paste.
3. Both controllers rely on the shared `run-ability.ts` helper so they can use the Abilities API client when available or fall back to REST calls.
4. Ability responses are persisted via comment meta (`_ai_toxicity_score`, `_ai_sentiment`, `_ai_analysis_status`, `_ai_analyzed_at`), which the PHP column renderers read to display badges.

The Comment Moderation experiment adds AI-powered sentiment and toxicity analysis to the classic Comments admin screen (`edit-comments.php`). It also performs automatic moderation when new comments are created: comments with **high toxicity** and **negative sentiment** are moved to moderation. The experiment exposes one WordPress Ability (`ai/comment-analysis`) that can be used from the UI or via REST API.

## Overview

### For End Users

When enabled, the Comments list table gets two additional columns: **Sentiment** and **Toxicity**. Each comment can show a badge with the current analysis state or result.

**Key Features:**

- Adds sentiment and toxicity badges to the Comments admin list table
- Adds a bulk action (**Analyze with AI**) to queue selected comments for analysis
- Processes queued comments in the browser, sequentially, to avoid server overload
- Automatically moderates newly created comments when analysis indicates high risk
- Uses one shared ability (`ai/comment-analysis`) for both automated and manual workflows

**Automatic moderation rule (default):**

- If `toxicity_score >= 0.7` **and** `sentiment === 'negative'`, the comment is set to pending moderation (`comment_approved = '0'`).

### For Developers

The experiment consists of three main parts:

1. **Experiment Class** (`WordPress\AI\Experiments\Comment_Moderation\Comment_Moderation`): Registers hooks, list-table columns, bulk action handling, automatic moderation on insert, and asset loading
2. **Ability Class** (`WordPress\AI\Abilities\Comment_Moderation\Comment_Analysis`): Runs AI analysis, validates output, and stores result metadata on comments
3. **Frontend Controller** (`src/experiments/comment-moderation/components/LazyAnalysisController.tsx`): Detects pending/failed badges in the DOM, runs analysis sequentially, updates badges in place, and strips queue query args from the URL after processing

## Architecture & Implementation

### Key Hooks & Entry Points

- `WordPress\AI\Experiments\Comment_Moderation\Comment_Moderation::register()` wires everything when the experiment is enabled:
- `wp_abilities_api_init` -> registers `ai/comment-analysis` (`includes/Abilities/Comment_Moderation/Comment_Analysis.php`)
- `wp_insert_comment` -> `moderate_comment()` analyzes newly inserted comments and may set them to moderation
- `manage_edit-comments_columns`, `manage_comments_custom_column` -> adds/renders `Sentiment` and `Toxicity` columns
- `bulk_actions-edit-comments`, `handle_bulk_actions-edit-comments`, `admin_notices` -> adds and handles **Analyze with AI** with queued-count notice
- `admin_enqueue_scripts` -> enqueues `experiments/comment-moderation` on `edit-comments.php`
- `admin_head-edit-comments.php` -> prints badge styles inline

### Assets & Data Flow

1. **PHP side:**
- `enqueue_assets()` loads `experiments/comment-moderation` (`src/experiments/comment-moderation/index.tsx`)
- Localizes `window.aiCommentModerationData` with:
- `enabled`: whether the experiment is enabled

2. **React side:**
- `index.tsx` mounts `LazyAnalysisController` after `domReady`
- `LazyAnalysisController` scans for badges marked `data-ai-status="pending"` or `data-ai-status="failed"`
- For each comment, it calls `runAbility( 'ai/comment-analysis', { comment_id } )` and updates sentiment/toxicity badges from the response
- Processing is sequential (one comment at a time)
- After processing (or if nothing is pending), it removes `wpai_analysis_queued` from the URL via `history.replaceState()` so refresh does not re-show the queue notice

3. **Ability execution:**
- Validates `comment_id` and permission (`moderate_comments`)
- Prevents concurrent runs for the same comment if status is already `processing`
- Builds prompt from comment author + content and requests strict JSON output (`toxicity_score`, `sentiment`)
- Stores analysis metadata on the comment:
- `_wpai_toxicity_score`
- `_wpai_sentiment`
- `_wpai_analysis_status`
- `_wpai_analyzed_at`

4. **Automatic moderation:**
- `moderate_comment()` executes the ability on new comments
- Applies default moderation threshold logic (`>= 0.7` + `negative`)
- Uses a filter so integrators can override the moderation decision

### Input Schema

The `ai/comment-analysis` ability accepts:

```php
array(
'type' => 'object',
'properties' => array(
'comment_id' => array(
'type' => 'integer',
'description' => 'The ID of the comment to analyze.',
'required' => true,
),
),
'required' => array( 'comment_id' ),
)
```

### Output Schema

The ability returns:

```php
array(
'type' => 'object',
'properties' => array(
'comment_id' => array( 'type' => 'integer' ),
'toxicity_score' => array(
'type' => 'number',
'minimum' => 0,
'maximum' => 1,
),
'sentiment' => array(
'type' => 'string',
'enum' => array( 'positive', 'negative', 'neutral' ),
),
),
)
```

### Permissions

- `ai/comment-analysis` requires `current_user_can( 'moderate_comments' )`
- List table UI and bulk action are intended for comment moderators

## Using the Ability via REST API

### Endpoint

```text
POST /wp-json/wp-abilities/v1/abilities/ai/comment-analysis/run
```

### Authentication

You can authenticate using either:

1. **Application Password** (Recommended)
2. **Cookie Authentication with Nonce**

See [TESTING_REST_API.md](../TESTING_REST_API.md) for detailed authentication instructions.

### Request Example

```bash
curl -X POST "https://yoursite.com/wp-json/wp-abilities/v1/abilities/ai/comment-analysis/run" \
-u "username:application-password" \
-H "Content-Type: application/json" \
-d '{
"input": {
"comment_id": 123
}
}'
```

**Response:**

```json
{
"comment_id": 123,
"toxicity_score": 0.81,
"sentiment": "negative"
}
```

### Error Responses

The ability may return:

- `missing_comment_id`: `comment_id` was not provided
- `comment_not_found`: no comment exists for the given ID
- `already_processing`: comment is currently being analyzed
- `parse_error`: AI output could not be parsed as expected JSON
- `insufficient_capabilities`: current user lacks moderation permissions

## Extending the Experiment

### Customize moderation behavior

Use `wpai_comment_moderation_should_moderate` to override the default threshold-based moderation decision:

```php
add_filter( 'wpai_comment_moderation_should_moderate', function( $should_moderate, $analysis, $comment_id ) {
if ( $analysis['toxicity_score'] >= 0.6 ) {
return true;
}
return $should_moderate;
}, 10, 3 );
```

## Testing
1. Enable Experiments globally and toggle **Comment Moderation** under `Settings → AI Experiments`.
2. Visit `Comments → All Comments`. Pending comments should show “Analyze with AI” badges; clicking one should enqueue an analysis request and update the badge once complete.
3. Select multiple comments, choose the “Analyze with AI” bulk action, and confirm the inline notice reports how many were queued.
4. Approve a comment and click its “AI Reply” row action. The modal should display suggested replies; applying one should copy it into the WordPress reply form.
5. Toggle the experiment off and reload the page—columns, badges, row/bulk actions, and scripts should disappear.

## Notes
- The experiment only runs for users with `moderate_comments`.
- Analysis locks each comment while it is processing to prevent duplicate requests.
- Replies and analysis rely on AI credentials; without valid credentials the whole experiment remains disabled via the shared experiment toggle logic.

### Manual Testing

1. **Enable the experiment:**
- Go to `Settings -> AI`
- Enable global AI features and toggle **Comment Moderation**
- Ensure valid AI connector credentials are configured

2. **List table badges and bulk analysis:**
- Go to `Comments -> All Comments`
- Select multiple comments and run **Analyze with AI**
- Confirm a success notice reports queued count
- Verify pending badges transition to analyzed badges (or failed state) as processing runs
- Confirm `wpai_analysis_queued` is removed from the URL after processing, and refresh does not re-show the queue notice

3. **Automatic moderation on insert:**
- Submit new comments with varied tone/content
- Confirm high-toxicity negative comments are moved to moderation (`comment_approved = 0`)
- Confirm lower-risk or non-negative comments are not auto-moderated by default

4. **REST API:**
- Call `POST /wp-json/wp-abilities/v1/abilities/ai/comment-analysis/run` with a valid `comment_id`
- Verify response shape and error handling for invalid IDs or insufficient permissions

## Notes & Considerations

### Requirements

- Requires valid AI credentials and text-generation-capable models
- Requires users with comment moderation capabilities for ability access
- Runs in wp-admin on the classic comments list screen (`edit-comments.php`)

### Performance & Behavior

- Frontend processing is sequential by design to avoid sending many concurrent requests
- Ability-level status locking prevents duplicate simultaneous analysis on the same comment
- Failed comments are marked `failed` and are retried on subsequent scans because failed badges remain queryable

### Limitations

- Works only on the classic comments list table (no block-based comments UI integration here)
- Analysis is not batched server-side; one ability invocation per comment
- Current retry behavior for failed badges is best-effort and may repeat on reload until resolved