self::TIMEOUT, /** * Headers sent to the AI service, for add-ons with their own service plan. * * @param array $headers */ 'headers' => apply_filters('baton_ai_headers', [ 'content-type' => 'application/json', // counts this site's daily allowance; a one-way hash, so the service never learns the address 'x-baton-site' => self::siteId(), ]), 'body' => wp_json_encode([ 'steps' => array_map(fn(array $step) => [ // the menu place in the dashboard, else the page's own name 'page' => ($step['place'] ?? '') !== '' ? $step['place'] : Baton_Admin::pageName($step), 'tag' => $step['tag'] ?? '', 'kind' => $step['kind'] ?? Baton_Walkthroughs::guessKind((string) ($step['tag'] ?? '')), 'label' => $step['label'] ?? '', ], array_values($steps)), ]), ]); if (is_wp_error($response)) { return new WP_Error('baton_ai_unreachable', __('The AI service could not be reached.', 'baton')); } $code = (int) wp_remote_retrieve_response_code($response); $body = json_decode(wp_remote_retrieve_body($response), true); if ($code === 429 && ($body['error'] ?? '') === 'daily_limit') { return new WP_Error('baton_ai_daily_limit', sprintf( /* translators: %d: AI rewrites allowed per day */ __('You’ve used today’s %d AI rewrites. More tomorrow, or unlimited with Baton Pro.', 'baton'), (int) ($body['limit'] ?? 10) )); } if ($code === 429) { return new WP_Error('baton_ai_busy', __('The AI service is busy. Try again in a minute.', 'baton')); } $texts = $body['steps'] ?? null; if ($code !== 200 || !is_array($texts) || count($texts) !== count($steps)) { return new WP_Error('baton_ai_bad_response', __('The AI service sent back something unexpected.', 'baton')); } return array_map( fn($text) => is_string($text) ? mb_substr(sanitize_text_field($text), 0, 200) : '', array_values($texts) ); } public static function siteId(): string { return hash('sha256', untrailingslashit(home_url())); } /** * Rewrites every step's text with AI, keeping the old text for any the AI left blank. * * @return array|WP_Error the steps with new text */ public static function rewrite(array $steps) { $texts = self::describe($steps); if (is_wp_error($texts)) { return $texts; } foreach (array_values($texts) as $i => $text) { if ($text !== '') { $steps[$i]['text'] = $text; } } return $steps; } } }