PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.7
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.7
16.3-a.5 16.3-a.7 16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 All 506 releases
jetpack / _inc / lib / core-api / wpcom-endpoints / class-wpcom-rest-api-v2-endpoint-ai.php

class-wpcom-rest-api-v2-endpoint-ai.php in Jetpack – WP Security, Backup, Speed, & Growth 16.3-a.7, at _inc/lib/core-api/wpcom-endpoints/class-wpcom-rest-api-v2-endpoint-ai.php

327 lines 8.9 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * REST API endpoint for the Jetpack AI blocks.
4 *
5 * @package automattic/jetpack
6 * @since 11.8
7 */
8
9 use Automattic\Jetpack\Connection\Client;
10
11 if ( ! defined( 'ABSPATH' ) ) {
12 exit( 0 );
13 }
14
15 /**
16 * Class WPCOM_REST_API_V2_Endpoint_AI
17 */
18 class WPCOM_REST_API_V2_Endpoint_AI extends WP_REST_Controller {
19 /**
20 * Namespace prefix.
21 *
22 * @var string
23 */
24 public $namespace = 'wpcom/v2';
25
26 /**
27 * Endpoint base route.
28 *
29 * @var string
30 */
31 public $rest_base = 'jetpack-ai';
32
33 /**
34 * WPCOM_REST_API_V2_Endpoint_AI constructor.
35 */
36 public function __construct() {
37 $this->is_wpcom = true;
38 $this->wpcom_is_wpcom_only_endpoint = true;
39
40 add_action( 'rest_api_init', array( $this, 'maybe_register_routes' ) );
41 }
42
43 /**
44 * Register routes on `rest_api_init`, gating on the AI feature state.
45 *
46 * The Jetpack_AI_Helper checks (which load the helper and instantiate
47 * Search/Connection classes) run here rather than in the constructor so that
48 * code is only loaded when the REST API is actually in use, not on every
49 * front-end, cron, or login request.
50 */
51 public function maybe_register_routes() {
52 if ( ! class_exists( 'Jetpack_AI_Helper' ) ) {
53 require_once JETPACK__PLUGIN_DIR . '_inc/lib/class-jetpack-ai-helper.php';
54 }
55
56 // Register routes that don't require Jetpack AI to be enabled.
57 $this->register_basic_routes();
58
59 if ( Jetpack_AI_Helper::is_ai_chat_enabled() ) {
60 $this->register_ai_chat_routes();
61 }
62
63 if ( ! \Jetpack_AI_Helper::is_enabled() ) {
64 return;
65 }
66
67 // Register routes that require Jetpack AI to be enabled.
68 $this->register_routes();
69 }
70
71 /**
72 * Register routes.
73 */
74 public function register_routes() {
75 register_rest_route(
76 $this->namespace,
77 $this->rest_base . '/completions',
78 array(
79 array(
80 'methods' => WP_REST_Server::CREATABLE,
81 'callback' => array( $this, 'request_gpt_completion' ),
82 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
83 ),
84 'args' => array(
85 'content' => array(
86 'type' => 'string',
87 'required' => true,
88 'sanitize_callback' => 'sanitize_textarea_field',
89 ),
90 'post_id' => array(
91 'required' => false,
92 'type' => 'integer',
93 ),
94 'skip_cache' => array(
95 'required' => false,
96 'type' => 'boolean',
97 'description' => 'Whether to skip the cache and make a new request',
98 ),
99 ),
100 )
101 );
102
103 register_rest_route(
104 $this->namespace,
105 $this->rest_base . '/images/generations',
106 array(
107 array(
108 'methods' => WP_REST_Server::CREATABLE,
109 'callback' => array( $this, 'request_dalle_generation' ),
110 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
111 ),
112 'args' => array(
113 'prompt' => array(
114 'type' => 'string',
115 'required' => true,
116 'sanitize_callback' => 'sanitize_textarea_field',
117 ),
118 'post_id' => array(
119 'required' => false,
120 'type' => 'integer',
121 ),
122 ),
123 )
124 );
125 }
126
127 /**
128 * Register routes for the AI Chat block.
129 * Relies on a site connection and Jetpack Search.
130 */
131 public function register_ai_chat_routes() {
132 register_rest_route(
133 $this->namespace,
134 '/jetpack-search/ai/search',
135 array(
136 array(
137 'methods' => WP_REST_Server::READABLE,
138 'callback' => array( $this, 'request_chat_with_site' ),
139 'permission_callback' => '__return_true',
140 ),
141 'args' => array(
142 'query' => array(
143 'description' => 'Your question to the site',
144 'required' => true,
145 'sanitize_callback' => 'sanitize_text_field',
146 ),
147 'answer_prompt' => array(
148 'description' => 'Answer prompt override',
149 'required' => false,
150 'sanitize_callback' => 'sanitize_text_field',
151 ),
152 ),
153 )
154 );
155
156 register_rest_route(
157 $this->namespace,
158 '/jetpack-search/ai/rank',
159 array(
160 array(
161 'methods' => WP_REST_Server::CREATABLE,
162 'callback' => array( $this, 'rank_response' ),
163 'permission_callback' => '__return_true',
164 ),
165 'args' => array(
166 'cache_key' => array(
167 'description' => 'Cache key of your response',
168 'required' => true,
169 'sanitize_callback' => 'sanitize_text_field',
170 ),
171 'comment' => array(
172 'description' => 'Optional feedback',
173 'required' => false,
174 'sanitize_callback' => 'sanitize_text_field',
175 ),
176 'rank' => array(
177 'description' => 'How do you rank this response',
178 'required' => false,
179 'sanitize_callback' => 'sanitize_text_field',
180 ),
181 ),
182 )
183 );
184 }
185
186 /**
187 * Register routes that don't require Jetpack AI to be enabled.
188 */
189 public function register_basic_routes() {
190 register_rest_route(
191 $this->namespace,
192 $this->rest_base . '/ai-assistant-feature',
193 array(
194 array(
195 'methods' => WP_REST_Server::READABLE,
196 'callback' => array( $this, 'request_get_ai_assistance_feature' ),
197 'permission_callback' => array( 'Jetpack_AI_Helper', 'get_status_permission_check' ),
198 ),
199 )
200 );
201 }
202
203 /**
204 * Get a response from chatting with the site.
205 * Uses Jetpack Search.
206 *
207 * @param WP_REST_Request $request The request.
208 * @return mixed
209 */
210 public function request_chat_with_site( $request ) {
211 $question = $request->get_param( 'query' );
212 $blog_id = \Jetpack_Options::get_option( 'id' );
213 $response = Client::wpcom_json_api_request_as_blog(
214 sprintf( '/sites/%d/jetpack-search/ai/search', $blog_id ) . '?force=wpcom',
215 2,
216 array(
217 'method' => 'GET',
218 'headers' => array( 'content-type' => 'application/json' ),
219 'timeout' => MINUTE_IN_SECONDS,
220 ),
221 array(
222 'query' => $question,
223 /**
224 * Filter for an answer prompt override.
225 * Example: "Talk like a cowboy."
226 *
227 * @param string $prompt_override The prompt override string.
228 *
229 * @since 12.6
230 */
231 'answer_prompt' => apply_filters( 'jetpack_ai_chat_answer_prompt', false ),
232 ),
233 'wpcom'
234 );
235
236 if ( is_wp_error( $response ) ) {
237 return $response;
238 }
239
240 $status_code = wp_remote_retrieve_response_code( $response );
241 $data = json_decode( wp_remote_retrieve_body( $response ) );
242
243 // Forward the real upstream error instead of a generic 500. See SEARCH-351.
244 if ( $status_code >= 400 || empty( $data->cache_key ) ) {
245 $code = isset( $data->code ) && is_scalar( $data->code ) ? (string) $data->code : 'invalid_ask_response';
246 $message = isset( $data->message ) && is_scalar( $data->message ) ? (string) $data->message : __( 'Invalid response from the server.', 'jetpack' );
247 $status = $status_code >= 400 ? $status_code : 400;
248 return new WP_Error( $code, $message, array( 'status' => $status ) );
249 }
250
251 return $data;
252 }
253
254 /**
255 * Rank a response from chatting with the site.
256 *
257 * @param WP_REST_Request $request The request.
258 * @return mixed
259 */
260 public function rank_response( $request ) {
261 $rank = $request->get_param( 'rank' );
262 $comment = $request->get_param( 'comment' );
263 $cache_key = $request->get_param( 'cache_key' );
264
265 if ( strpos( $cache_key, 'jp-search-ai-' ) !== 0 ) {
266 return new WP_Error( 'invalid_cache_key', __( 'Invalid cached context for the answer feedback.', 'jetpack' ), array( 'status' => 400 ) );
267 }
268
269 $blog_id = \Jetpack_Options::get_option( 'id' );
270 $response = Client::wpcom_json_api_request_as_blog(
271 sprintf( '/sites/%d/jetpack-search/ai/rank', $blog_id ) . '?force=wpcom',
272 2,
273 array(
274 'method' => 'GET',
275 'headers' => array( 'content-type' => 'application/json' ),
276 'timeout' => 30,
277 ),
278 array(
279 'rank' => $rank,
280 'comment' => $comment,
281 'cache_key' => $cache_key,
282 ),
283 'wpcom'
284 );
285
286 if ( is_wp_error( $response ) ) {
287 return $response;
288 }
289
290 $data = json_decode( wp_remote_retrieve_body( $response ) );
291
292 if ( 'ok' !== $data ) {
293 return new WP_Error( 'invalid_feedback_response', __( 'Invalid response from the server.', 'jetpack' ), array( 'status' => 400 ) );
294 }
295
296 return $data;
297 }
298
299 /**
300 * Get completions for a given text.
301 *
302 * @param WP_REST_Request $request The request.
303 */
304 public function request_gpt_completion( $request ) {
305 return Jetpack_AI_Helper::get_gpt_completion( $request['content'], $request['post_id'], $request['skip_cache'] );
306 }
307
308 /**
309 * Get image generations for a given prompt.
310 *
311 * @param WP_REST_Request $request The request.
312 */
313 public function request_dalle_generation( $request ) {
314 return Jetpack_AI_Helper::get_dalle_generation( $request['prompt'], $request['post_id'] );
315 }
316
317 /**
318 * Collect and provide relevat data about the AI feature,
319 * such as the number of requests made.
320 */
321 public function request_get_ai_assistance_feature() {
322 return Jetpack_AI_Helper::get_ai_assistance_feature();
323 }
324 }
325
326 wpcom_rest_api_v2_load_plugin( 'WPCOM_REST_API_V2_Endpoint_AI' );
327