PluginProbe
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot / 4.5.0
BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot v4.5.0
4.9.1 4.9.0 4.8.2 4.8.1 4.8.0 4.7.0 4.6.2 4.6.1 4.6.0 4.5.6 4.5.5 4.5.4 4.5.3 4.5.2 4.5.1 4.5.0 4.4.1 4.4.0 3.3.4 3.4.0 3.4.1 3.4.2 3.5.0 3.5.1 3.5.2 All 199 releases
betterdocs / includes / REST / AIEdit.php

AIEdit.php in BetterDocs – AI Documentation, Knowledge Base, MCP Server, Docs, Wikis, FAQ & Chatbot 4.5.0, at includes/REST/AIEdit.php

227 lines 7.5 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 namespace WPDeveloper\BetterDocs\REST;
4
5 use WP_REST_Request;
6 use WPDeveloper\BetterDocs\Core\BaseAPI;
7
8 class AIEdit extends BaseAPI {
9
10 const MAX_SELECTION_LENGTH = 12000;
11 const MAX_INSTRUCTION_LENGTH = 1000;
12
13 public function register() {
14 $this->post(
15 '/ai-edit',
16 [ $this, 'generate' ],
17 [
18 'post_id' => [
19 'type' => 'integer',
20 'required' => true
21 ],
22 'action' => [
23 'type' => 'string',
24 'required' => true
25 ],
26 'selection' => [
27 'type' => 'string',
28 'required' => false,
29 'default' => ''
30 ],
31 'selection_type' => [
32 'type' => 'string',
33 'required' => false,
34 'default' => 'block'
35 ],
36 'instruction' => [
37 'type' => 'string',
38 'required' => false,
39 'default' => ''
40 ],
41 'option' => [
42 'type' => 'string',
43 'required' => false,
44 'default' => ''
45 ]
46 ]
47 );
48 }
49
50 public function permission_check() {
51 $post_id = isset( $_REQUEST['post_id'] ) ? intval( $_REQUEST['post_id'] ) : 0;
52
53 if ( $post_id <= 0 ) {
54 return current_user_can( 'edit_posts' );
55 }
56
57 return current_user_can( 'edit_post', $post_id );
58 }
59
60 public function generate( WP_REST_Request $request ) {
61 $write_ai = betterdocs()->ai_autowrtie;
62
63 if ( empty( $write_ai ) || ! $write_ai->isEnabledWriteWithAI() ) {
64 return $this->error(
65 'ai_disabled',
66 __( 'Write with AI is disabled. Enable it from BetterDocs settings.', 'betterdocs' ),
67 400
68 );
69 }
70
71 $api_key = $write_ai->get_api_key();
72 if ( empty( $api_key ) ) {
73 return $this->error(
74 'ai_no_key',
75 __( 'OpenAI API key is missing. Add one in BetterDocs settings.', 'betterdocs' ),
76 400
77 );
78 }
79
80 $action = sanitize_key( (string) $request->get_param( 'action' ) );
81 $selection_type = sanitize_key( (string) $request->get_param( 'selection_type' ) );
82 $selection = (string) $request->get_param( 'selection' );
83 $instruction = (string) $request->get_param( 'instruction' );
84 $option = sanitize_text_field( (string) $request->get_param( 'option' ) );
85
86 if ( strlen( $selection ) > self::MAX_SELECTION_LENGTH ) {
87 $selection = substr( $selection, 0, self::MAX_SELECTION_LENGTH );
88 }
89 if ( strlen( $instruction ) > self::MAX_INSTRUCTION_LENGTH ) {
90 $instruction = substr( $instruction, 0, self::MAX_INSTRUCTION_LENGTH );
91 }
92
93 $instruction = wp_kses_post( $instruction );
94 $selection = wp_kses_post( $selection );
95
96 if ( $selection_type !== 'inline' ) {
97 $selection_type = 'block';
98 }
99
100 $presets = self::get_presets();
101
102 if ( $action !== 'custom' && ! isset( $presets[ $action ] ) ) {
103 return $this->error(
104 'ai_bad_action',
105 __( 'Unknown AI action.', 'betterdocs' ),
106 400
107 );
108 }
109
110 if ( trim( wp_strip_all_tags( $instruction ) ) === '' ) {
111 return $this->error(
112 'ai_empty_instruction',
113 __( 'Please provide a prompt for the AI.', 'betterdocs' ),
114 400
115 );
116 }
117
118 if ( trim( wp_strip_all_tags( $selection ) ) === '' && $action !== 'continue' ) {
119 return $this->error(
120 'ai_empty_selection',
121 __( 'No content selected for the AI to work on.', 'betterdocs' ),
122 400
123 );
124 }
125
126 $prompt = $this->build_prompt( $action, $presets, $selection, $selection_type, $instruction, $option );
127
128 $result = $write_ai->generate_openai_response_full( $prompt );
129
130 if ( empty( $result['success'] ) ) {
131 $message = isset( $result['error'] ) ? (string) $result['error'] : __( 'Unknown AI error.', 'betterdocs' );
132 return $this->error( 'ai_upstream', $message, 502 );
133 }
134
135 $content = $this->clean_output( (string) $result['content'] );
136
137 if ( $content === '' ) {
138 return $this->error(
139 'ai_empty_response',
140 __( 'The AI returned no content. Try again or rephrase your instruction.', 'betterdocs' ),
141 502
142 );
143 }
144
145 return $this->success(
146 [
147 'content' => $content,
148 'action' => $action,
149 'usage' => [
150 'prompt_tokens' => isset( $result['prompt_tokens'] ) ? $result['prompt_tokens'] : null,
151 'completion_tokens' => isset( $result['completion_tokens'] ) ? $result['completion_tokens'] : null,
152 'total_tokens' => isset( $result['total_tokens'] ) ? $result['total_tokens'] : null,
153 ],
154 'model' => isset( $result['model'] ) ? $result['model'] : null,
155 ]
156 );
157 }
158
159 protected function build_prompt( $action, $presets, $selection, $selection_type, $instruction, $option ) {
160 $core = trim( $instruction );
161
162 $preset = $action !== 'custom' && isset( $presets[ $action ] ) ? $presets[ $action ] : null;
163 $format_hint = isset( $preset['format'] ) && $preset['format']
164 ? $preset['format']
165 : ( $selection_type === 'inline'
166 ? __( 'Return only plain text (no block markup, no quotes, no explanations).', 'betterdocs' )
167 : __( 'Return only valid Gutenberg-compatible HTML using standard tags such as <p>, <h2>, <ul>, <ol>, <li>, <strong>, <em>, <a>, <code>. Do not include explanations, preambles, code fences, or markdown.', 'betterdocs' ) );
168
169 $prompt = $core . "\n\n";
170 $prompt .= __( 'Formatting requirements:', 'betterdocs' ) . ' ' . $format_hint . "\n\n";
171 $prompt .= __( 'Content to work on:', 'betterdocs' ) . "\n";
172 $prompt .= "---\n" . $selection . "\n---";
173
174 return $prompt;
175 }
176
177 protected function clean_output( $content ) {
178 $content = trim( $content );
179
180 $content = preg_replace( '/^```(?:html|HTML)?\s*\n?/', '', $content );
181 $content = preg_replace( '/\n?```\s*$/', '', $content );
182
183 return trim( $content );
184 }
185
186 public static function get_presets() {
187 return [
188 'improve' => [
189 'prompt' => 'Improve the writing quality, clarity, grammar, and structure of the content below while preserving its original meaning and intent.'
190 ],
191 'rewrite' => [
192 'prompt' => 'Rewrite the content below with different wording while keeping the same meaning, tone, and level of detail.'
193 ],
194 'shorten' => [
195 'prompt' => 'Make the content below more concise. Preserve all essential information; remove redundancy and filler.'
196 ],
197 'expand' => [
198 'prompt' => 'Expand the content below with more detail, examples, and clear explanations. Keep the original voice.'
199 ],
200 'simplify' => [
201 'prompt' => 'Rewrite the content below in simpler language so it is easy to understand for a non-technical reader.'
202 ],
203 'fix_grammar' => [
204 'prompt' => 'Fix only the grammar, spelling, and punctuation in the content below. Do not rewrite or change the meaning or style.'
205 ],
206 'change_tone' => [
207 'prompt' => 'Rewrite the content below in a {option} tone while preserving its meaning.',
208 'needs_option' => true
209 ],
210 'translate' => [
211 'prompt' => 'Translate the content below into {option}. Preserve meaning, tone, and any HTML structure.',
212 'needs_option' => true
213 ],
214 'summarize' => [
215 'prompt' => 'Summarize the content below into 2 to 3 clear sentences.'
216 ],
217 'continue' => [
218 'prompt' => 'Continue writing naturally from where the content below ends. Match its tone, style, and level of detail.'
219 ],
220 'create_table' => [
221 'prompt' => 'Extract the statistics, figures, or comparable data from the content below and represent them as a single HTML table. Infer clear column headers. Include every distinct data point. If no tabular data can be reasonably inferred, return the best possible structured representation.',
222 'format' => 'Return only one complete <table> element with <thead> and <tbody>. Use <th scope="col"> for header cells. Do not include <style>, <script>, inline CSS classes, comments, explanations, preambles, code fences, or markdown.'
223 ]
224 ];
225 }
226 }
227