is_supported_for_text_generation(); } public static function rest( \WP_REST_Request $r ) { if ( ! self::available() ) { return new \WP_Error( 'akram_ai_off', __( 'Connect an AI provider to WordPress (WordPress 7.0 or later) to use the AI assistant.', 'akram' ), array( 'status' => 400 ) ); } $p = $r->get_json_params(); $mode = in_array( $p['mode'] ?? '', array( 'text', 'rewrite', 'section', 'alt' ), true ) ? $p['mode'] : 'text'; $prompt = trim( wp_strip_all_tags( (string) ( $p['prompt'] ?? '' ) ) ); $ctx = trim( wp_strip_all_tags( (string) ( $p['context'] ?? '' ) ) ); if ( '' === $prompt && 'alt' !== $mode ) { return new \WP_Error( 'akram_ai_empty', __( 'Describe what you want.', 'akram' ), array( 'status' => 400 ) ); } $rkey = 'akram_ai_' . get_current_user_id(); $hits = (int) get_transient( $rkey ); if ( $hits > 30 ) { return new \WP_Error( 'akram_ai_rate', __( 'Too many AI requests. Try again in a few minutes.', 'akram' ), array( 'status' => 429 ) ); } set_transient( $rkey, $hits + 1, 10 * MINUTE_IN_SECONDS ); $site = get_bloginfo( 'name' ) . ' — ' . get_bloginfo( 'description' ); $system = "You write website copy inside a visual page builder for the site \"{$site}\". Write clear, specific, human copy. Never use lorem ipsum, placeholder brackets or emojis unless asked. Match the language of the request."; switch ( $mode ) { case 'rewrite': $message = "Rewrite the following text. Instruction: {$prompt} Text: {$ctx} Return only the rewritten text, no preamble."; break; case 'alt': $message = "Write concise alt text (max 120 characters) for an image described by its file name and context: {$ctx}. Return only the alt text."; break; case 'section': $message = "Plan website sections for this request: {$prompt} " . ( $ctx ? "Page context: {$ctx} " : '' ) . 'Choose layouts that fit the content. Icons must be Lucide icon names (e.g. zap, shield-check, rocket, heart, globe, users, clock, star, sparkles, layers). Return JSON only.'; break; default: $message = $prompt . ( $ctx ? " Context: {$ctx}" : '' ) . " Return only the text to place on the page, no preamble or quotes."; } $builder = wp_ai_client_prompt( $message )->using_system_instruction( $system )->using_max_tokens( 'section' === $mode ? 8000 : 2000 ); if ( 'section' === $mode ) { $builder = $builder->as_json_response( self::section_schema() ); } $text = $builder->generate_text(); if ( is_wp_error( $text ) ) { return new \WP_Error( 'akram_ai_api', $text->get_error_message(), array( 'status' => 502 ) ); } $text = (string) $text; if ( 'section' === $mode ) { $json = json_decode( $text, true ); if ( ! is_array( $json ) ) { return new \WP_Error( 'akram_ai_parse', __( 'The AI response could not be read. Please try again.', 'akram' ), array( 'status' => 502 ) ); } return array( 'sections' => $json['sections'] ?? array() ); } return array( 'text' => trim( $text ) ); } private static function section_schema() { $str = array( 'type' => 'string' ); return array( 'type' => 'object', 'additionalProperties' => false, 'required' => array( 'sections' ), 'properties' => array( 'sections' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'additionalProperties' => false, 'required' => array( 'layout', 'eyebrow', 'title', 'subtitle', 'buttons', 'items', 'dark' ), 'properties' => array( 'layout' => array( 'type' => 'string', 'enum' => array( 'hero', 'features', 'split', 'stats', 'testimonials', 'faq', 'cta', 'pricing', 'steps', 'logos' ), ), 'eyebrow' => $str, 'title' => $str, 'subtitle' => $str, 'dark' => array( 'type' => 'boolean' ), 'buttons' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'additionalProperties' => false, 'required' => array( 'text', 'primary' ), 'properties' => array( 'text' => $str, 'primary' => array( 'type' => 'boolean' ), ), ), ), 'items' => array( 'type' => 'array', 'items' => array( 'type' => 'object', 'additionalProperties' => false, 'required' => array( 'icon', 'title', 'text', 'value' ), 'properties' => array( 'icon' => $str, 'title' => $str, 'text' => $str, 'value' => $str, ), ), ), ), ), ), ), ); } }