PluginProbe ʕ •ᴥ•ʔ
AI Engine – The Chatbot, AI Framework & MCP for WordPress / 0.2.3
AI Engine – The Chatbot, AI Framework & MCP for WordPress v0.2.3
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 / rest.php
ai-engine / classes Last commit date
modules 3 years ago admin.php 3 years ago ai.php 3 years ago answer.php 3 years ago core.php 3 years ago init.php 3 years ago openai.php 3 years ago query.php 3 years ago queryimage.php 3 years ago querytext.php 3 years ago rest.php 3 years ago ui.php 3 years ago
rest.php
422 lines
1 <?php
2
3 class Meow_MWAI_Rest
4 {
5 private $core = null;
6 private $namespace = 'ai-engine/v1';
7
8 public function __construct( $core ) {
9 if ( !current_user_can( 'administrator' ) ) {
10 return;
11 }
12 $this->core = $core;
13 add_action( 'rest_api_init', array( $this, 'rest_api_init' ) );
14 }
15
16 function rest_api_init() {
17 try {
18 register_rest_route( $this->namespace, '/update_option', array(
19 'methods' => 'POST',
20 'permission_callback' => array( $this->core, 'can_access_settings' ),
21 'callback' => array( $this, 'rest_update_option' )
22 ) );
23 register_rest_route( $this->namespace, '/all_settings', array(
24 'methods' => 'GET',
25 'permission_callback' => array( $this->core, 'can_access_settings' ),
26 'callback' => array( $this, 'rest_all_settings' ),
27 ) );
28 register_rest_route( $this->namespace, '/make_completions', array(
29 'methods' => 'POST',
30 'permission_callback' => array( $this->core, 'can_access_features' ),
31 'callback' => array( $this, 'make_completions' ),
32 ) );
33 register_rest_route( $this->namespace, '/make_images', array(
34 'methods' => 'POST',
35 'permission_callback' => array( $this->core, 'can_access_features' ),
36 'callback' => array( $this, 'make_images' ),
37 ) );
38 register_rest_route( $this->namespace, '/make_titles', array(
39 'methods' => 'POST',
40 'permission_callback' => array( $this->core, 'can_access_features' ),
41 'callback' => array( $this, 'make_titles' ),
42 ) );
43 register_rest_route( $this->namespace, '/make_excerpts', array(
44 'methods' => 'POST',
45 'permission_callback' => array( $this->core, 'can_access_features' ),
46 'callback' => array( $this, 'make_excerpts' ),
47 ) );
48 register_rest_route( $this->namespace, '/update_post_title', array(
49 'methods' => 'POST',
50 'permission_callback' => array( $this->core, 'can_access_features' ),
51 'callback' => array( $this, 'update_post_title' ),
52 ) );
53 register_rest_route( $this->namespace, '/update_post_excerpt', array(
54 'methods' => 'POST',
55 'permission_callback' => array( $this->core, 'can_access_features' ),
56 'callback' => array( $this, 'update_post_excerpt' ),
57 ) );
58 register_rest_route( $this->namespace, '/create_post', array(
59 'methods' => 'POST',
60 'permission_callback' => array( $this->core, 'can_access_features' ),
61 'callback' => array( $this, 'create_post' ),
62 ) );
63 register_rest_route( $this->namespace, '/create_image', array(
64 'methods' => 'POST',
65 'permission_callback' => array( $this->core, 'can_access_features' ),
66 'callback' => array( $this, 'create_image' ),
67 ) );
68 register_rest_route( $this->namespace, '/openai_files', array(
69 'methods' => 'GET',
70 'permission_callback' => array( $this->core, 'can_access_features' ),
71 'callback' => array( $this, 'openai_files_get' ),
72 ) );
73 register_rest_route( $this->namespace, '/openai_files', array(
74 'methods' => 'DELETE',
75 'permission_callback' => array( $this->core, 'can_access_features' ),
76 'callback' => array( $this, 'openai_files_delete' ),
77 ) );
78 register_rest_route( $this->namespace, '/openai_files', array(
79 'methods' => 'POST',
80 'permission_callback' => array( $this->core, 'can_access_features' ),
81 'callback' => array( $this, 'openai_files_upload' ),
82 ) );
83 register_rest_route( $this->namespace, '/openai_files_download', array(
84 'methods' => 'POST',
85 'permission_callback' => array( $this->core, 'can_access_features' ),
86 'callback' => array( $this, 'openai_files_download' ),
87 ) );
88 register_rest_route( $this->namespace, '/openai_files_finetune', array(
89 'methods' => 'POST',
90 'permission_callback' => array( $this->core, 'can_access_features' ),
91 'callback' => array( $this, 'openai_files_finetune' ),
92 ) );
93 register_rest_route( $this->namespace, '/openai_finetunes', array(
94 'methods' => 'GET',
95 'permission_callback' => array( $this->core, 'can_access_features' ),
96 'callback' => array( $this, 'openai_finetunes_get' ),
97 ) );
98 }
99 catch ( Exception $e ) {
100 var_dump( $e );
101 }
102 }
103
104 function rest_all_settings() {
105 return new WP_REST_Response( [
106 'success' => true,
107 'data' => $this->core->get_all_options()
108 ], 200 );
109 }
110
111 function rest_update_option( $request ) {
112 try {
113 $params = $request->get_json_params();
114 $value = $params['options'];
115 $options = $this->core->update_options( $value );
116 $success = !!$options;
117 $message = __( $success ? 'OK' : "Could not update options.", 'ai-engine' );
118 return new WP_REST_Response([ 'success' => $success, 'message' => $message, 'options' => $options ], 200 );
119 }
120 catch ( Exception $e ) {
121 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
122 }
123 }
124
125 function createValidationResult( $result = true, $message = null) {
126 $message = $message ? $message : __( 'OK', 'ai-engine' );
127 return [ 'result' => $result, 'message' => $message ];
128 }
129
130 function validate_updated_option( $option_name ) {
131 $option_checkbox = get_option( 'mwai_option_checkbox', false );
132 $option_text = get_option( 'mwai_option_text', 'Default' );
133 if ( $option_checkbox === '' )
134 update_option( 'mwai_option_checkbox', false );
135 if ( $option_text === '' )
136 update_option( 'mwai_option_text', 'Default' );
137 return $this->createValidationResult();
138 }
139
140 function setup_query_based_on_params( $query, $params ) {
141 if ( isset( $params['model'] ) ) {
142 $query->setModel( $params['model'] );
143 }
144 if ( isset( $params['temperature'] ) ) {
145 $query->setTemperature( $params['temperature'] );
146 }
147 if ( isset( $params['apiKey'] ) ) {
148 $query->setApiKey( $params['apiKey'] );
149 }
150 if ( isset( $params['maxResults'] ) ) {
151 $query->setMaxResults( $params['maxResults'] );
152 }
153 return $query;
154 }
155
156 function make_completions( $request ) {
157 try {
158 $params = $request->get_json_params();
159 $prompt = $params['prompt'];
160 $query = new Meow_MWAI_QueryText( $prompt, 2048 );
161 $query = $this->setup_query_based_on_params( $query, $params );
162 $answer = $this->core->ai->run( $query );
163 return new WP_REST_Response([ 'success' => true, 'data' => $answer->result, 'usage' => $answer->usage ], 200 );
164 }
165 catch ( Exception $e ) {
166 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
167 }
168 }
169
170 function make_images( $request ) {
171 try {
172 $params = $request->get_json_params();
173 $prompt = $params['prompt'];
174 $query = new Meow_MWAI_QueryImage( $prompt );
175 $query = $this->setup_query_based_on_params( $query, $params );
176 $answer = $this->core->ai->run( $query );
177 return new WP_REST_Response([ 'success' => true, 'data' => $answer->results, 'usage' => $answer->usage ], 200 );
178 }
179 catch ( Exception $e ) {
180 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
181 }
182 }
183
184 function make_titles( $request ) {
185 try {
186 $params = $request->get_json_params();
187 $postId = intval( $params['postId'] );
188 $text = $this->core->get_text_from_postId( $postId );
189 $prompt = "Create short SEO-friendly title for this text: " . $text;
190 $query = new Meow_MWAI_QueryText( $prompt, 40 );
191 $query->setMaxResults( 5 );
192 $answer = $this->core->ai->run( $query );
193 return new WP_REST_Response([ 'success' => true, 'data' => $answer->results ], 200 );
194 }
195 catch ( Exception $e ) {
196 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
197 }
198 }
199
200 function make_excerpts( $request ) {
201 try {
202 $params = $request->get_json_params();
203 $postId = intval( $params['postId'] );
204 $text = $this->core->get_text_from_postId( $postId );
205 $prompt = "Create SEO-friendly introduction to this text, 120 to 170 characters max, no URLs: " . $text;
206 $query = new Meow_MWAI_QueryText( $prompt, 140 );
207 $query->setMaxResults( 5 );
208 $answer = $this->core->ai->run( $query );
209 return new WP_REST_Response([ 'success' => true, 'data' => $answer->results ], 200 );
210 }
211 catch ( Exception $e ) {
212 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
213 }
214 }
215
216 function update_post_title( $request ) {
217 try {
218 $params = $request->get_json_params();
219 $title = sanitize_text_field( $params['title'] );
220 $postId = intval( $params['postId'] );
221 $post = get_post( $postId );
222 if ( !$post ) {
223 throw new Exception( 'There is no post with this ID.' );
224 }
225 $post->post_title = $title;
226 //$post->post_name = sanitize_title( $title );
227 wp_update_post( $post );
228 return new WP_REST_Response([ 'success' => true, 'message' => "Title updated." ], 200 );
229 }
230 catch ( Exception $e ) {
231 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
232 }
233 }
234
235 function update_post_excerpt( $request ) {
236 try {
237 $params = $request->get_json_params();
238 $excerpt = sanitize_text_field( $params['excerpt'] );
239 $postId = intval( $params['postId'] );
240 $post = get_post( $postId );
241 if ( !$post ) {
242 throw new Exception( 'There is no post with this ID.' );
243 }
244 $post->post_excerpt = $excerpt;
245 wp_update_post( $post );
246 return new WP_REST_Response([ 'success' => true, 'message' => "Excerpt updated." ], 200 );
247 }
248 catch ( Exception $e ) {
249 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
250 }
251 }
252
253 function create_post( $request ) {
254 try {
255 $params = $request->get_json_params();
256 $title = sanitize_text_field( $params['title'] );
257 // Sanitize content that contains line returns and HTML tags
258 $content = sanitize_textarea_field( $params['content'] );
259 $excerpt = sanitize_text_field( $params['excerpt'] );
260 //$postType = sanitize_text_field( $params['postType'] );
261 $post = new stdClass();
262 $post->post_title = $title;
263 $post->post_excerpt = $excerpt;
264 $post->post_content = $content;
265 $post->post_status = 'draft';
266 $post->post_type = 'post';
267 $post->post_content = $this->core->markdown_to_html( $post->post_content );
268 $postId = wp_insert_post( $post );
269 return new WP_REST_Response([ 'success' => true, 'postId' => $postId ], 200 );
270 }
271 catch ( Exception $e ) {
272 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
273 }
274 }
275
276 function curl_download( $Url ) {
277 if ( !function_exists( 'curl_init' ) ) {
278 die( 'CURL is not installed!' );
279 }
280 $ch = curl_init();
281 curl_setopt( $ch, CURLOPT_URL, $Url );
282 curl_setopt( $ch, CURLOPT_RETURNTRANSFER, true );
283 $output = curl_exec( $ch );
284 curl_close( $ch );
285 return $output;
286 }
287
288 function create_image( $request ) {
289 try {
290 $params = $request->get_json_params();
291 $title = sanitize_text_field( $params['title'] );
292 $caption = sanitize_text_field( $params['caption'] );
293 $alt = sanitize_text_field( $params['alt'] );
294 $description = sanitize_text_field( $params['description'] );
295 $url = $params['url'];
296 $filename = sanitize_text_field( $params['filename'] );
297 $image_data = $this->curl_download( $url );
298 if ( !$image_data ) {
299 throw new Exception( 'Could not download the image.' );
300 }
301 $upload_dir = wp_upload_dir();
302 if ( empty( $filename ) ) {
303 $filename = basename( $url );
304 }
305 $wp_filetype = wp_check_filetype( $filename );
306 if ( wp_mkdir_p( $upload_dir['path'] ) ) {
307 $file = $upload_dir['path'] . '/' . $filename;
308 }
309 else {
310 $file = $upload_dir['basedir'] . '/' . $filename;
311 }
312
313 // Make sure the file is unique, if not, add a number to the end of the file before the extension
314 $i = 1;
315 $parts = pathinfo( $file );
316 while ( file_exists( $file ) ) {
317 $file = $parts['dirname'] . '/' . $parts['filename'] . '-' . $i . '.' . $parts['extension'];
318 $i++;
319 }
320
321 // Write the file
322 file_put_contents( $file, $image_data );
323 $attachment = [
324 'post_mime_type' => $wp_filetype['type'],
325 'post_title' => $title,
326 'post_content' => $description,
327 'post_excerpt' => $caption,
328 'post_status' => 'inherit'
329 ];
330 // Register the file as a Media Library attachment
331 $attachmentId = wp_insert_attachment( $attachment, $file );
332 require_once( ABSPATH . 'wp-admin/includes/image.php' );
333 $attachment_data = wp_generate_attachment_metadata( $attachmentId, $file );
334 wp_update_attachment_metadata( $attachmentId, $attachment_data );
335 update_post_meta( $attachmentId, '_wp_attachment_image_alt', $alt );
336 return new WP_REST_Response([ 'success' => true, 'attachmentId' => $attachmentId ], 200 );
337 }
338 catch ( Exception $e ) {
339 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
340 }
341 }
342
343 function openai_files_get( $request ) {
344 try {
345 //$params = $request->get_json_params();
346 $openai = new Meow_MWAI_OpenAI( $this->core );
347 $files = $openai->listFiles();
348 return new WP_REST_Response([ 'success' => true, 'files' => $files ], 200 );
349 }
350 catch ( Exception $e ) {
351 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
352 }
353 }
354
355 function openai_finetunes_get( $request ) {
356 try {
357 //$params = $request->get_json_params();
358 $openai = new Meow_MWAI_OpenAI( $this->core );
359 $finetunes = $openai->listFineTunes();
360 return new WP_REST_Response([ 'success' => true, 'finetunes' => $finetunes ], 200 );
361 }
362 catch ( Exception $e ) {
363 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
364 }
365 }
366
367 function openai_files_upload( $request ) {
368 try {
369 $params = $request->get_json_params();
370 $filename = sanitize_text_field( $params['filename'] );
371 $data = $params['data'];
372 $openai = new Meow_MWAI_OpenAI( $this->core );
373 $file = $openai->uploadFile( $filename, $data );
374 return new WP_REST_Response([ 'success' => true, 'file' => $file ], 200 );
375 }
376 catch ( Exception $e ) {
377 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
378 }
379 }
380
381 function openai_files_delete( $request ) {
382 try {
383 $params = $request->get_json_params();
384 $fileId = $params['fileId'];
385 $openai = new Meow_MWAI_OpenAI( $this->core );
386 $openai->deleteFile( $fileId );
387 return new WP_REST_Response([ 'success' => true ], 200 );
388 }
389 catch ( Exception $e ) {
390 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
391 }
392 }
393
394 function openai_files_download( $request ) {
395 try {
396 $params = $request->get_json_params();
397 $fileId = $params['fileId'];
398 $openai = new Meow_MWAI_OpenAI( $this->core );
399 $data = $openai->downloadFile( $fileId );
400 return new WP_REST_Response([ 'success' => true, 'data' => $data ], 200 );
401 }
402 catch ( Exception $e ) {
403 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
404 }
405 }
406
407 function openai_files_finetune( $request ) {
408 try {
409 $params = $request->get_json_params();
410 $fileId = $params['fileId'];
411 $model = $params['model'];
412 $suffix = $params['suffix'];
413 $openai = new Meow_MWAI_OpenAI( $this->core );
414 $finetune = $openai->fineTuneFile( $fileId, $model, $suffix );
415 return new WP_REST_Response([ 'success' => true, 'finetune' => $finetune ], 200 );
416 }
417 catch ( Exception $e ) {
418 return new WP_REST_Response([ 'success' => false, 'message' => $e->getMessage() ], 500 );
419 }
420 }
421 }
422