PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 3.4.9
AI Engine – The Chatbot, AI Framework & MCP for WordPress v3.4.9
3.5.7 3.5.6 3.5.5 3.5.4 3.5.3 3.5.2 3.5.1 3.5.0 3.4.9 3.4.8 3.4.7 0.2.1 1.6.91 0.2.2 1.6.92 0.2.3 1.6.93 0.2.4 1.6.94 0.2.5 1.6.95 0.2.6 1.6.96 0.2.7 1.6.97 0.2.8 1.6.98 0.2.9 1.6.99 0.3.0 1.7.0 0.3.1 1.7.1 0.3.2 1.7.2 0.3.3 1.7.3 0.3.4 1.7.4 0.3.5 1.7.5 0.3.6 1.7.6 0.4.0 1.7.7 0.4.1 1.7.8 0.4.2 1.7.9 0.4.3 1.8.0 0.4.4 1.8.1 0.4.5 1.8.2 0.4.6 1.8.3 0.4.7 1.8.4 0.4.8 1.8.5 0.4.9 1.8.6 0.5.0 1.8.7 0.5.1 1.8.8 0.5.2 1.8.9 0.5.3 1.9.0 0.5.4 1.9.1 0.5.5 1.9.2 0.5.6 1.9.3 0.5.7 1.9.4 0.5.8 1.9.5 0.5.9 1.9.6 0.6.0 1.9.7 0.6.1 1.9.8 0.6.2 1.9.81 0.6.3 1.9.82 0.6.4 1.9.83 0.6.5 1.9.84 0.6.6 1.9.85 0.6.7 1.9.86 0.6.8 1.9.87 0.6.9 1.9.88 0.7.0 1.9.89 0.7.1 1.9.90 0.7.2 1.9.91 0.7.3 1.9.92 0.7.4 1.9.93 0.7.5 1.9.94 0.7.6 1.9.95 0.7.7 1.9.96 0.7.8 1.9.97 0.7.9 1.9.98 0.8.0 1.9.99 0.8.1 2.0.0 0.8.2 2.0.1 0.8.3 2.0.2 0.8.4 2.0.3 0.8.5 2.0.4 0.8.6 2.0.5 0.8.7 2.0.6 0.8.8 2.0.7 0.8.9 2.0.8 0.9.0 2.0.9 0.9.2 2.1.0 0.9.3 2.1.1 0.9.4 2.1.2 0.9.5 2.1.3 0.9.6 2.1.4 0.9.7 2.1.5 0.9.8 2.1.6 0.9.81 2.1.7 0.9.82 2.1.8 0.9.83 2.1.9 0.9.84 2.2.0 0.9.85 2.2.1 0.9.86 2.2.2 0.9.87 2.2.3 0.9.88 2.2.4 0.9.89 2.2.5 0.9.9 2.2.51 0.9.91 2.2.52 0.9.92 2.2.53 0.9.93 2.2.54 0.9.94 2.2.56 0.9.95 2.2.57 0.9.96 2.2.6 0.9.97 2.2.60 0.9.98 2.2.61 0.9.99 2.2.62 1.0.0 2.2.63 1.0.01 2.2.70 1.0.1 2.2.80 1.0.2 2.2.81 1.0.3 2.2.90 1.0.4 2.2.91 1.0.5 2.2.92 1.0.6 2.2.93 1.0.7 2.2.94 1.0.8 2.2.95 1.0.9 2.3.0 1.1.0 2.3.1 1.1.1 2.3.2 1.1.2 2.3.3 1.1.3 2.3.4 1.1.4 2.3.5 1.1.5 2.3.6 1.1.6 2.3.7 1.1.7 2.3.8 1.1.8 2.3.9 1.1.9 2.4.0 1.2.0 2.4.1 1.2.1 2.4.2 1.2.2 2.4.3 1.2.21 2.4.4 1.2.3 2.4.5 1.2.30 2.4.6 1.3.0 2.4.7 1.3.1 2.4.8 1.3.2 2.4.9 1.3.3 2.5.0 1.3.31 2.5.1 1.3.32 2.5.2 1.3.33 2.5.3 1.3.34 2.5.4 1.3.35 2.5.5 1.3.36 2.5.6 1.3.37 2.5.7 1.3.38 2.5.8 1.3.39 2.5.9 1.3.40 2.6.0 1.3.41 2.6.1 1.3.42 2.6.2 1.3.43 2.6.3 1.3.44 2.6.5 1.3.45 2.6.6 1.3.46 2.6.7 1.3.47 2.6.8 1.3.48 2.6.9 1.3.49 2.7.0 1.3.50 2.7.1 1.3.51 2.7.2 1.3.52 2.7.3 1.3.53 2.7.4 1.3.54 2.7.5 1.3.56 2.7.6 1.3.57 2.7.7 1.3.58 2.7.8 1.3.59 2.7.9 1.3.60 2.8.0 1.3.61 2.8.1 1.3.62 2.8.2 1.3.63 2.8.3 1.3.64 2.8.4 1.3.65 2.8.5 1.3.66 2.8.6 1.3.67 2.8.7 1.3.68 2.8.8 1.3.69 2.8.9 1.3.70 2.9.0 1.3.71 2.9.1 1.3.72 2.9.2 1.3.73 2.9.3 1.3.74 2.9.4 1.3.75 2.9.5 1.3.76 2.9.6 1.3.77 2.9.7 1.3.78 2.9.8 1.3.79 2.9.9 1.3.80 3.0.0 1.3.81 3.0.1 1.3.82 3.0.2 1.3.83 3.0.3 1.3.84 3.0.4 1.3.85 3.0.5 1.3.86 3.0.6 1.3.87 3.0.7 1.3.88 3.0.8 1.3.89 3.0.9 1.3.90 3.1.0 1.3.91 3.1.1 1.3.92 3.1.2 1.3.93 3.1.3 1.3.94 3.1.4 1.3.95 3.1.5 1.3.96 3.1.6 1.3.97 3.1.7 1.3.98 3.1.8 1.3.99 3.1.9 1.4.0 3.2.0 1.4.1 3.2.1 1.4.2 3.2.2 1.4.3 3.2.3 1.4.4 3.2.4 1.4.5 3.2.5 1.4.6 3.2.6 1.4.7 3.2.7 1.4.8 3.2.8 1.4.9 3.2.9 1.5.0 3.3.0 1.5.1 3.3.1 1.5.2 3.3.2 1.5.3 3.3.3 1.5.4 3.3.4 1.5.5 3.3.5 1.5.6 3.3.6 1.5.7 3.3.7 1.5.8 3.3.8 1.5.9 3.3.9 1.6.0 3.4.0 1.6.1 3.4.1 1.6.2 3.4.2 1.6.3 3.4.3 1.6.5 3.4.4 1.6.51 3.4.5 1.6.52 3.4.6 1.6.53 1.6.54 1.6.55 1.6.56 1.6.57 1.6.58 1.6.59 1.6.60 1.6.61 1.6.62 1.6.63 1.6.64 1.6.65 1.6.66 1.6.67 1.6.68 trunk 1.6.69 0.0.1 1.6.70 0.0.2 1.6.71 0.0.3 1.6.72 0.0.4 1.6.73 0.0.5 1.6.74 0.0.6 1.6.75 0.0.7 1.6.76 0.0.8 1.6.77 0.0.9 1.6.78 0.1.0 1.6.79 0.1.1 1.6.81 0.1.2 1.6.82 0.1.3 1.6.83 0.1.4 1.6.84 0.1.5 1.6.85 0.1.6 1.6.86 0.1.7 1.6.87 0.1.8 1.6.88 0.1.9 1.6.89 0.2.0 1.6.90
ai-engine / classes / modules / wand.php
ai-engine / classes / modules Last commit date
advisor.php 3 months ago chatbot.php 2 months ago discussions.php 2 months ago editor-assistant.php 3 months ago files.php 3 months ago forms-manager.php 3 months ago gdpr.php 4 months ago search.php 3 months ago security.php 11 months ago tasks-examples.php 6 months ago tasks.php 1 month ago wand.php 3 months ago
wand.php
394 lines
1 <?php
2
3 class Meow_MWAI_Modules_Wand {
4 private $core;
5
6 public static $features = [
7 'correctText' => [
8 'label' => 'Correct Text',
9 'sublabel' => 'Grammar & Spelling',
10 'arguments' => ['postId', 'text'],
11 'where' => 'blockContext',
12 'group' => 'first',
13 'multiBlock' => true
14 ],
15 'enhanceText' => [
16 'label' => 'Enhance Text',
17 'sublabel' => 'Readibility & Quality',
18 'arguments' => ['postId', 'text'],
19 'where' => 'blockContext',
20 'group' => 'first',
21 'multiBlock' => true
22 ],
23 'longerText' => [
24 'label' => 'Longer Text',
25 'sublabel' => 'Readibility',
26 'arguments' => ['postId', 'text'],
27 'where' => 'blockContext',
28 'group' => 'first'
29 ],
30 'shorterText' => [
31 'label' => 'Shorter Text',
32 'sublabel' => 'Readibility',
33 'arguments' => ['postId', 'text'],
34 'where' => 'blockContext',
35 'group' => 'first'
36 ],
37 'translateText' => [
38 'label' => 'Translate Text',
39 'sublabel' => 'To Post Language',
40 'arguments' => ['postId', 'text', 'language'],
41 'where' => 'blockContext',
42 'group' => 'first',
43 'multiBlock' => true
44 ],
45 'translateSection' => [
46 'label' => 'Translate Post',
47 'sublabel' => 'To Post Language',
48 'arguments' => ['postId', 'text', 'context'],
49 'where' => 'postContext', // We should probably handle this dynamically on the front-side
50 'group' => 'first' // This is random
51 ],
52 'suggestSynonyms' => [
53 'label' => 'Suggest Synonyms',
54 'sublabel' => 'For Selected Words',
55 'arguments' => ['postId', 'text', 'selectedText'],
56 'where' => 'blockContext',
57 'group' => 'second'
58 ],
59 'generateImage' => [
60 'label' => 'Generate Image',
61 'sublabel' => 'For This Text',
62 'arguments' => ['postId', 'text'],
63 'where' => 'blockContext',
64 'group' => 'third'
65 ],
66 'suggestExcerpts' => [
67 'label' => 'Suggest Excerpts',
68 'sublabel' => 'Generate SEO-Optimized Excerpts',
69 'arguments' => ['postId'],
70 'where' => 'postActions'
71 ],
72 'suggestTitles' => [
73 'label' => 'Suggest Titles',
74 'sublabel' => 'Generate SEO-Optimized Titles',
75 'arguments' => ['postId'],
76 'where' => 'postActions'
77 ]
78 ];
79
80 public function __construct( $core ) {
81 $this->core = $core;
82 $this->register_filters();
83 }
84
85 private function register_filters() {
86 foreach ( self::$features as $action => $feature ) {
87 add_filter( 'mwai_magic_wand_' . $action, [ $this, 'action_' . $action ], 10, 2 );
88 }
89 }
90
91 // Separator used for multi-block content (must match frontend)
92 public const BLOCK_SEPARATOR = "\n\n---MWAI_BLOCK_SEPARATOR---\n\n";
93
94 private function createWandQuery( $postId ) {
95 $query = new Meow_MWAI_Query_Text( '', 4096 );
96 $query->set_scope( 'admin-tools' );
97 $language = !empty( $postId ) ? $this->core->get_post_language( $postId ) : '';
98 return [ $query, $language ];
99 }
100
101 private function keepLanguage( $language ) {
102 return !empty( $language )
103 ? " Ensure the reply is in the same language as the original text ({$language})."
104 : '';
105 }
106
107 /**
108 * Common method to process text actions (e.g., correct, enhance, lengthen, shorten text).
109 *
110 * @param array $arguments The arguments provided for the action.
111 * @param string $messagePrefix The prefix for the message to be set in the query.
112 * @return array The result of the text processing.
113 */
114 private function processTextAction( $arguments, $messagePrefix ) {
115 $postId = $arguments['postId'];
116 $isJson = isset( $arguments['json'] ) && !empty( $arguments['json'] );
117 $isMultiBlock = isset( $arguments['isMultiBlock'] ) && $arguments['isMultiBlock'];
118 $blockType = isset( $arguments['blockType'] ) ? $arguments['blockType'] : null;
119
120 if ( $isJson ) {
121 // Handle structured JSON data for complex blocks
122 $jsonData = $arguments['json'];
123 $text = json_encode( $jsonData, JSON_PRETTY_PRINT );
124
125 // Add specific instructions for JSON handling
126 $jsonInstructions = "\n\nIMPORTANT: The input is JSON data. You must return ONLY valid JSON (no markdown, no code blocks, no explanations). Return the EXACT SAME JSON structure, only modifying the text content within it.";
127
128 if ( $blockType === 'core/list' ) {
129 $jsonInstructions .= " This is a list with 'type' and 'items' fields. Return: {\"type\": \"list\", \"items\": [...modified items...]}";
130 }
131 elseif ( $blockType === 'core/table' ) {
132 $jsonInstructions .= " This is a table with 'type' and 'rows' fields. Each row has a 'cells' array. Return: {\"type\": \"table\", \"rows\": [{\"cells\": [...modified cells...]}, ...]}";
133 }
134
135 $messagePrefix .= $jsonInstructions;
136 }
137 elseif ( $isMultiBlock ) {
138 // Handle multi-block content with separators
139 $text = $arguments['text'];
140 $blockCount = isset( $arguments['blockCount'] ) ? (int) $arguments['blockCount'] : 0;
141 $multiBlockInstructions = "\n\nIMPORTANT: The input contains {$blockCount} separate text blocks, separated by '---MWAI_BLOCK_SEPARATOR---'. You MUST preserve these exact separators in your response. Process each block independently while maintaining the separator structure. Do not merge blocks or remove separators.";
142 $messagePrefix .= $multiBlockInstructions;
143 }
144 else {
145 // Handle regular text
146 $text = $arguments['text'];
147 }
148
149 [ $query, $language ] = $this->createWandQuery( $postId );
150 $query->set_instructions( $messagePrefix . $this->keepLanguage( $language ) );
151 $query->set_message( $text );
152 $reply = $this->core->run_query( $query );
153
154 $result = $reply->result;
155 $responseType = 'text';
156
157 // If we sent JSON, we must get JSON back
158 if ( $isJson ) {
159 // First, try to extract JSON from markdown code blocks if present
160 $jsonPattern = '/```(?:json)?\s*\n?(.+?)\n?```/s';
161 if ( preg_match( $jsonPattern, $result, $matches ) ) {
162 $result = trim( $matches[1] );
163 }
164
165 // Now parse the JSON
166 $parsedJson = json_decode( $result, true );
167 if ( json_last_error() === JSON_ERROR_NONE ) {
168 // Validate that the JSON has the expected structure
169 if ( $blockType === 'core/list' && isset( $parsedJson['type'] ) && $parsedJson['type'] === 'list' && isset( $parsedJson['items'] ) ) {
170 $result = $parsedJson;
171 $responseType = 'json';
172 }
173 elseif ( $blockType === 'core/table' && isset( $parsedJson['type'] ) && $parsedJson['type'] === 'table' && isset( $parsedJson['rows'] ) ) {
174 $result = $parsedJson;
175 $responseType = 'json';
176 }
177 else {
178 // JSON is valid but doesn't match expected structure
179 error_log( 'AI Engine: JSON response does not match expected structure for block type: ' . $blockType );
180 throw new Exception( 'Invalid JSON structure returned by AI' );
181 }
182 }
183 else {
184 // JSON parsing failed
185 error_log( 'AI Engine: Failed to parse AI response as JSON. Error: ' . json_last_error_msg() );
186 error_log( 'AI Engine: Raw response: ' . $result );
187 throw new Exception( 'AI did not return valid JSON' );
188 }
189 }
190
191 return [
192 'mode' => 'replace',
193 'type' => $responseType,
194 'result' => $result,
195 'results' => $reply->results
196 ];
197 }
198
199 /**
200 * Handles the correction of text by checking and correcting grammatical errors.
201 */
202 public function action_correctText( $value, $arguments ) {
203 $prompt = apply_filters( 'mwai_prompt_correctText', "Correct the typos and grammar mistakes in this text without altering its content. Ensure the reply is in the same language as the original text.\n\n", $arguments );
204 return $this->processTextAction( $arguments, $prompt );
205 }
206
207 /**
208 * Enhances the text's readability and quality.
209 */
210 public function action_enhanceText( $value, $arguments ) {
211 $prompt = apply_filters( 'mwai_prompt_enhanceText', "Enhance this text by improving readability and quality, using a more suitable vocabulary, and refining its structure.\n\n", $arguments );
212 return $this->processTextAction( $arguments, $prompt );
213 }
214
215 /**
216 * Lengthens the text to improve readability.
217 */
218 public function action_longerText( $value, $arguments ) {
219 $prompt = apply_filters( 'mwai_prompt_longerText', "Expand the subsequent text to a minimum of three times its original length, integrating relevant and accurate information to enrich its content. If the text is a story, amplify its charm by elaborating on essential aspects, enhancing readability, and creating a sense of engagement for the reader. Maintain consistency in tone and vocabulary throughout the expansion process.\n\n", $arguments );
220 return $this->processTextAction( $arguments, $prompt );
221 }
222
223 /**
224 * Shortens the text to improve readability.
225 */
226 public function action_shorterText( $value, $arguments ) {
227 $prompt = apply_filters( 'mwai_prompt_shorterText', "Condense the following text by reducing its length to half, while retaining the core elements of the original narrative. Focus on maintaining the essence of the story and its key details.\n\n", $arguments );
228 return $this->processTextAction( $arguments, $prompt );
229 }
230
231 /**
232 * Suggests synonyms for selected words in the text.
233 */
234 public function action_suggestSynonyms( $value, $arguments ) {
235 $postId = $arguments['postId'];
236 $selectedText = $arguments['selectedText'];
237 [ $query, $language ] = $this->createWandQuery( $postId );
238 $prompt = apply_filters( 'mwai_prompt_suggestSynonyms', "Provide 5 synonyms or 5 ways of rephrasing the given word or sentence while retaining the original meaning and preserving the initial and final punctuation and spacing if any. Offer only the resulting word or expression, without additional context. If a suitable synonym or alternative cannot be identified, ensure that a creative response is still provided. Separate every suggestion with a new line, and that's it." . $this->keepLanguage( $language ), $arguments );
239 $query->set_instructions( $prompt );
240 $query->set_message( $selectedText );
241 $query->set_temperature( 1 );
242 $reply = $this->core->run_query( $query );
243 $lines = explode( "\n", $reply->result );
244 $results = [];
245 foreach ( $lines as $line ) {
246 $trimmed = trim( $line );
247 if ( !empty( $trimmed ) ) {
248 $results[] = $trimmed;
249 }
250 }
251 return [
252 'mode' => 'suggest',
253 'type' => $reply->type,
254 'result' => $results[0] ?? '',
255 'results' => $results
256 ];
257 }
258
259 /**
260 * Generates an image relevant to the text.
261 */
262 public function action_generateImage( $value, $arguments ) {
263 global $mwai;
264 $postId = $arguments['postId'];
265 $text = $arguments['text'];
266 $prompt = apply_filters( 'mwai_prompt_generateImage', "Generate an image that is relevant to the following text:\n\n", $arguments );
267 $message = $prompt . $text;
268 $media = $mwai->imageQueryForMediaLibrary( $message, $params = [], $postId );
269 return [
270 'mode' => 'insertMedia',
271 'type' => 'image',
272 'media' => $media
273 ];
274 }
275
276 /**
277 * Translates the specified text of text to the target language.
278 *
279 * @param mixed $value Unused parameter
280 * @param array $arguments Contains postId, text, and context
281 * @return array Translation result
282 */
283 public function action_translateSection( $value, $arguments ) {
284 $postId = $arguments['postId'];
285 $text = $arguments['text'];
286
287 if ( empty( $text ) ) {
288 return [
289 'mode' => 'replace',
290 'type' => 'text',
291 'result' => '',
292 'results' => []
293 ];
294 }
295
296 $context = $arguments['context'];
297 [ $query, $targetLanguage ] = $this->createWandQuery( $postId );
298 $instructions = "Translate the following section into {$targetLanguage}.\n\n" .
299 "Translation guidelines:\n" .
300 "1. Maintain the original tone, mood, and nuance.\n" .
301 "2. Preserve the intended meaning as accurately as possible.\n" .
302 "3. Ensure the translation fits seamlessly within the broader context.\n" .
303 "4. Use appropriate idiomatic expressions in the target language when applicable.\n" .
304 "5. Maintain any formatting or special characters present in the original text.\n\n" .
305 'Provide only the translated section, without any additional content.';
306 $instructions = apply_filters( 'mwai_prompt_translateSection', $instructions, $arguments );
307 $query->set_instructions( $instructions );
308 $message = "[SECTION TO TRANSLATE]\n{$text}\n[END SECTION TO TRANSLATE]\n\n" .
309 "Broader context (for reference only, do not translate):\n\n" .
310 "[CONTEXT]\n{$context}\n[END CONTEXT]";
311 $query->set_message( $message );
312 $reply = $this->core->run_query( $query );
313
314 // Clean up the result, just in case...
315 $result = $reply->result;
316 $result = str_replace( '[TRANSLATED SECTION]', '', $result );
317 $result = str_replace( '[END TRANSLATED SECTION]', '', $result );
318 $result = trim( $result );
319 $results = [];
320 foreach ( $reply->results as $r ) {
321 $r = str_replace( '[TRANSLATED SECTION]', '', $r );
322 $r = str_replace( '[END TRANSLATED SECTION]', '', $r );
323 $r = trim( $r );
324 $results[] = $r;
325 }
326
327 return [
328 'mode' => 'replace',
329 'type' => $reply->type,
330 'result' => $result,
331 'results' => $results
332 ];
333 }
334
335 /**
336 * Translates the text to the specified language.
337 */
338 public function action_translateText( $value, $arguments ) {
339 $postId = $arguments['postId'];
340 $text = $arguments['text'];
341 [ $query, $language ] = $this->createWandQuery( $postId );
342 $prompt = apply_filters( 'mwai_prompt_translateText', "Translate the text into {$language}, preserving the tone, mood, and nuance, while staying as true as possible to the original meaning. Provide only the translated text, without any additional content.", $arguments );
343 $query->set_instructions( $prompt );
344 $query->set_message( $text );
345 $reply = $this->core->run_query( $query );
346 return [
347 'mode' => 'replace',
348 'type' => $reply->type,
349 'result' => $reply->result,
350 'results' => $reply->results
351 ];
352 }
353
354 /**
355 * Suggests SEO-optimized excerpts for the text.
356 */
357 public function action_suggestExcerpts( $value, $arguments ) {
358 $postId = $arguments['postId'];
359 $text = $this->core->get_post_content( $postId );
360 [ $query, $language ] = $this->createWandQuery( $postId );
361 $prompt = apply_filters( 'mwai_prompt_suggestExcerpts', 'Craft a clear, SEO-optimized introduction for the following text, using 120 to 170 characters. Ensure the introduction is concise and relevant, without including any URLs.' . $this->keepLanguage( $language ), $arguments );
362 $query->set_instructions( $prompt );
363 $query->set_message( $text );
364 $query->set_max_results( 5 );
365 $reply = $this->core->run_query( $query );
366 return [
367 'mode' => 'suggest',
368 'type' => $reply->type,
369 'result' => $reply->result,
370 'results' => $reply->results
371 ];
372 }
373
374 /**
375 * Suggests SEO-optimized titles for the text.
376 */
377 public function action_suggestTitles( $value, $arguments ) {
378 $postId = $arguments['postId'];
379 $text = $this->core->get_post_content( $postId );
380 [ $query, $language ] = $this->createWandQuery( $postId );
381 $prompt = apply_filters( 'mwai_prompt_suggestTitles', 'Generate a concise, SEO-optimized title for the following text, without using quotes or any other formatting. Focus on clarity and relevance to the content.' . $this->keepLanguage( $language ), $arguments );
382 $query->set_instructions( $prompt );
383 $query->set_message( $text );
384 $query->set_max_results( 5 );
385 $reply = $this->core->run_query( $query );
386 return [
387 'mode' => 'suggest',
388 'type' => $reply->type,
389 'result' => $reply->result,
390 'results' => $reply->results
391 ];
392 }
393 }
394