PluginProbe
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin / 0.9.7
OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin v0.9.7
1.1.10 1.1.9 1.1.8 1.1.7 1.1.6 1.1.5 1.1.4 1.1.3 1.1.2 1.1.1 1.1.0 1.0.1 1.0.0 0.9.8 0.9.7 0.9.6 0.9.4 0.9.5 0.9.3 0.9.2 0.9.1 0.9.0 0.8.9 0.8.8 0.8.7 All 34 releases
desktop-mode / includes / ai-copilot / analysis.php

analysis.php in OpenStation: Desktop Windows, Dock & Virtual Desktops for WP Admin 0.9.7, at includes/ai-copilot/analysis.php

188 lines 8.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Desktop Mode — AI Copilot analysis: prompts, schemas, meta storage.
4 *
5 * This module owns:
6 * - The JSON Schema for comment analysis (filterable).
7 * - The prompt builder that converts a comment into a chat message array.
8 * - Meta read/write helpers so the job callback never touches meta keys
9 * directly; the key name lives in one place.
10 *
11 * Comment analysis is the only auto-analysis the copilot performs (it feeds
12 * the comments-window spam score). Posts, pages, and terms are not analyzed.
13 *
14 * Meta key: `_desktop_mode_ai_analysis` (prefixed underscore → hidden from
15 * the Custom Fields UI by default).
16 *
17 * @package WPDesktopMode
18 */
19
20 defined( 'ABSPATH' ) || exit;
21
22 /** Meta key used to store the per-comment AI analysis. */
23 const DESKTOP_MODE_AI_META_KEY = '_desktop_mode_ai_analysis';
24
25 /** Max characters of comment text sent to the provider. */
26 const DESKTOP_MODE_AI_CONTENT_MAX_CHARS = 3000;
27
28 // ---------------------------------------------------------------------------
29 // JSON Schemas
30 // ---------------------------------------------------------------------------
31
32 /**
33 * JSON Schema for comment analysis.
34 *
35 * Captures a topic label and summary plus the `harmful` and `spam`
36 * booleans that drive the comments-window spam score.
37 *
38 * @since 0.5.0
39 *
40 * @return array
41 */
42 function desktop_mode_ai_schema_comment() {
43 $schema = array(
44 'type' => 'object',
45 'additionalProperties' => false,
46 'required' => array( 'topic', 'ai_summary', 'harmful', 'spam' ),
47 'properties' => array(
48 'topic' => array(
49 'type' => 'string',
50 'description' => 'A concise topic label (max 10 words) capturing the nature and tone of the comment. Include sentiment cues when relevant — e.g. "hostile criticism of article quality", "enthusiastic praise for travel tips", "spam promotion of hotel deals". This label is used by a search engine to match user queries like "negative comment" or "congratulatory message".',
51 ),
52 'ai_summary' => array(
53 'type' => 'string',
54 'description' => 'A 1-2 sentence summary that captures both WHAT the commenter said AND HOW they said it (tone, sentiment, intent). A negative or angry comment should be described as such. Examples: "The commenter aggressively dismisses the article as low quality and insults the author\'s credibility." / "A reader warmly congratulates the author on the new baby." / "A promotional spam comment linking to a hotel deals website with no relevance to the post."',
55 ),
56 'harmful' => array(
57 'type' => 'boolean',
58 'description' => 'True when the comment is hostile, insulting, demeaning, or abusive — regardless of whether it contains explicit language. Set to TRUE for: personal attacks on the author or other commenters ("garbage article", "you clearly have no idea", "embarrassing journalism", "stop writing about X"), aggressive condescension, hate speech, threats, or harassment. Set to FALSE for: polite disagreement, constructive criticism, or promotional spam that is off-topic but not hostile. Note: a comment can be spam=true AND harmful=false (promotional but not hostile), or harmful=true AND spam=false (angry but on-topic).',
59 ),
60 'spam' => array(
61 'type' => 'boolean',
62 'description' => 'True when the comment is promotional, automated, or wholly unrelated to the post content. Clear signals: external links to commercial sites (cheaphotelsnow.biz, etc.), ALL CAPS promotional text, trigger phrases like "CLICK HERE", "BOOK NOW", "AMAZING deals", "LIMITED TIME OFFER", excessive exclamation marks, generic praise unrelated to the post subject. Set to FALSE for comments that are angry, negative, or critical — those belong under `harmful`, not `spam`. A hostile but on-topic comment is NOT spam.',
63 ),
64 ),
65 );
66
67 /**
68 * Filters the JSON Schema used for comment AI analysis.
69 *
70 * @since 0.5.0
71 *
72 * @param array $schema The JSON Schema array.
73 */
74 return (array) apply_filters( 'desktop_mode_ai_schema_comment', $schema );
75 }
76
77 // ---------------------------------------------------------------------------
78 // Prompt builders
79 // ---------------------------------------------------------------------------
80
81 /**
82 * Builds the messages array for a comment.
83 *
84 * The parent post's title is included so the model can judge `spam`
85 * (off-topic detection requires knowing what the post is about).
86 *
87 * @since 0.5.0
88 *
89 * @param WP_Comment $comment
90 * @return array Chat messages array.
91 */
92 function desktop_mode_ai_messages_for_comment( WP_Comment $comment ) {
93 $text = wp_strip_all_tags( $comment->comment_content );
94 $text = mb_substr( preg_replace( '/\s+/', ' ', trim( $text ) ), 0, DESKTOP_MODE_AI_CONTENT_MAX_CHARS );
95
96 $user_text = "Analyze the following WordPress comment.\n\n";
97 $user_text .= "Comment:\n{$text}\n\n";
98
99 // Give the model post context so it can evaluate relevance (spam).
100 $post_id = (int) $comment->comment_post_ID;
101 if ( $post_id > 0 ) {
102 $post = get_post( $post_id );
103 if ( $post instanceof WP_Post ) {
104 $user_text .= 'Post title: ' . wp_strip_all_tags( $post->post_title ) . "\n";
105 }
106 }
107
108 $user_text .= "\n\nClassification rules:\n";
109 $user_text .= "- `harmful = true`: the comment is hostile, insulting, or demeaning — e.g. attacks on the author's competence, aggressive rhetoric, threats, hate speech. Tone matters: an angry rant calling the article \"garbage\" is harmful even without explicit language.\n";
110 $user_text .= "- `spam = true`: the comment is promotional or off-topic — e.g. commercial links, ALL CAPS sales copy, \"CLICK HERE\" / \"BOOK NOW\", generic praise unrelated to the post.\n";
111 $user_text .= "- These are INDEPENDENT flags. A hostile but on-topic comment is harmful=true, spam=false. A promotional but politely worded comment is spam=true, harmful=false. Both can be true simultaneously.\n";
112 $user_text .= "- The `topic` and `ai_summary` fields MUST capture the tone and sentiment so that search queries like \"negative comment\", \"angry reader\", or \"spam\" return the correct results.";
113
114 /**
115 * Filters the user message sent to the provider for comment analysis.
116 *
117 * @since 0.5.0
118 *
119 * @param string $user_text The composed user message.
120 * @param WP_Comment $comment The comment being analyzed.
121 */
122 $user_text = (string) apply_filters( 'desktop_mode_ai_comment_prompt', $user_text, $comment );
123
124 return array(
125 array(
126 'role' => 'system',
127 'content' => 'You are a content moderation assistant for a WordPress site. Your analysis is used by a semantic search engine, so the topic label and summary must reflect the comment\'s TONE and SENTIMENT — not just its subject matter. An angry, insulting comment must be described as angry and insulting. A promotional spam comment must be described as promotional spam. A warm congratulatory message must be described as warm and positive. Accurate tone labelling is critical for search to work.',
128 ),
129 array(
130 'role' => 'user',
131 'content' => $user_text,
132 ),
133 );
134 }
135
136 // ---------------------------------------------------------------------------
137 // Meta read / write
138 // ---------------------------------------------------------------------------
139
140 /**
141 * Saves an AI analysis result as comment meta.
142 *
143 * Comments are the only entity the copilot analyzes. The `$entity_type`
144 * parameter is retained for call-site/signature stability but only
145 * `'comment'` is supported — any other value is a no-op that returns false.
146 *
147 * @since 0.5.0
148 *
149 * @param string $entity_type Only `'comment'` is supported.
150 * @param int $entity_id Comment ID.
151 * @param array $analysis The structured output array from the provider.
152 * @return bool
153 */
154 function desktop_mode_ai_save_meta( $entity_type, $entity_id, array $analysis ) {
155 $entity_id = (int) $entity_id;
156 if ( $entity_id <= 0 || 'comment' !== $entity_type ) {
157 return false;
158 }
159
160 // Stamp when the analysis was performed so consumers can detect staleness.
161 $analysis['analyzed_at'] = time();
162
163 return false !== update_comment_meta( $entity_id, DESKTOP_MODE_AI_META_KEY, $analysis );
164 }
165
166 /**
167 * Retrieves a previously saved comment AI analysis, or null if none exists.
168 *
169 * Only `'comment'` is supported (see {@see desktop_mode_ai_save_meta}); any
170 * other `$entity_type` returns null.
171 *
172 * @since 0.5.0
173 *
174 * @param string $entity_type Only `'comment'` is supported.
175 * @param int $entity_id Comment ID.
176 * @return array|null
177 */
178 function desktop_mode_ai_get_meta( $entity_type, $entity_id ) {
179 $entity_id = (int) $entity_id;
180 if ( $entity_id <= 0 || 'comment' !== $entity_type ) {
181 return null;
182 }
183
184 $raw = get_comment_meta( $entity_id, DESKTOP_MODE_AI_META_KEY, true );
185
186 return is_array( $raw ) && ! empty( $raw ) ? $raw : null;
187 }
188