From 353b2a7dfd363d51f6c32e4f3e68bc1760f31810 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20S=C3=B8derlind?= Date: Fri, 24 Jul 2026 01:07:53 +0200 Subject: [PATCH 01/11] Refactor AI layer to use WordPress 7 AI Connector Replace the plugin's custom provider/API-key management with the WordPress Core AI connector API (wp_get_connectors / wp_ai_client_prompt), following the approach in soderlind/llms-md. - Rewrite AiManager as a thin orchestrator over wp_ai_client_prompt() ->generate_text(); connector detection via wp_get_connectors(), JSON parsing with Markdown code-fence stripping, reusing existing draft/tour validation. suggest_completion stays pure-PHP heuristics. - Delete OpenAiProvider, AzureOpenAiProvider, AnthropicProvider, AiProviderInterface and Security\Encryption (keys now owned by Core). - Slim SettingsPage: connector status + enable toggle + connector-ID provider dropdown + optional model override (act_ai_provider / act_ai_model). - AiController: generate_tour calls AiManager directly; get_status reports connector-based availability. - Bump Requires at least to 7.0, drop the sodium requirement, add one-time upgrade cleanup of legacy encrypted-key options, expand uninstall.php. - Add filters: admin_coach_tours_ai_connector_configured, admin_coach_tours_ai_provider_id, admin_coach_tours_ai_model. - Add AiManagerTest (mocks wp_ai_client_prompt); update docs. Assisted-by: GitHub Copilot:Claude Opus 4.8 --- README.md | 21 +- admin-coach-tours.php | 55 ++- php/AI/AiManager.php | 757 +++++++++++++++++++++++++-------- php/AI/AiProviderInterface.php | 82 ---- php/AI/AnthropicProvider.php | 635 --------------------------- php/AI/AzureOpenAiProvider.php | 683 ----------------------------- php/AI/OpenAiProvider.php | 633 --------------------------- php/Rest/AiController.php | 39 +- php/Security/Encryption.php | 133 ------ php/Settings/SettingsPage.php | 237 +++-------- readme.txt | 21 +- tests/php/AiManagerTest.php | 253 +++++++++++ uninstall.php | 13 +- 13 files changed, 980 insertions(+), 2582 deletions(-) delete mode 100644 php/AI/AiProviderInterface.php delete mode 100644 php/AI/AnthropicProvider.php delete mode 100644 php/AI/AzureOpenAiProvider.php delete mode 100644 php/AI/OpenAiProvider.php delete mode 100644 php/Security/Encryption.php create mode 100644 tests/php/AiManagerTest.php diff --git a/README.md b/README.md index a3f613c..cbee720 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # Admin Coach Tours [![Version](https://img.shields.io/badge/version-0.3.6-blue.svg)](https://github.com/soderlind/admin-coach-tours) -[![WordPress](https://img.shields.io/badge/WordPress-6.8%2B-blue.svg)](https://wordpress.org) +[![WordPress](https://img.shields.io/badge/WordPress-7.0%2B-blue.svg)](https://wordpress.org) [![PHP](https://img.shields.io/badge/PHP-8.3%2B-purple.svg)](https://php.net) [![License](https://img.shields.io/badge/license-GPL--2.0--or--later-green.svg)](https://www.gnu.org/licenses/gpl-2.0.html) @@ -24,28 +24,25 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera ## Requirements -- WordPress 6.8+ +- WordPress 7.0+ - PHP 8.3+ -- sodium extension (for API key encryption) -- AI provider API key (OpenAI, Azure OpenAI, or Anthropic) +- At least one WordPress AI provider connector configured ## Installation 1. Upload `admin-coach-tours` to `/wp-content/plugins/` 2. Activate the plugin -3. Go to **Tools → Coach Tours** to configure AI +3. Go to **Tools → Coach Tours** to enable AI ## Setup ### Configure AI Provider -1. Navigate to **Tours → Settings** -2. Enable AI Features -3. Select your provider: - - **OpenAI** — Add your API key - - **Azure OpenAI** — Add your API key and endpoint URL - - **Anthropic** — Add your API key -4. Save settings +1. Configure at least one WordPress AI provider connector +2. Navigate to **Tools → Coach Tours** +3. Enable AI Features +4. Optionally choose a preferred provider and model override +5. Save settings ## Usage diff --git a/admin-coach-tours.php b/admin-coach-tours.php index 77d4755..1449646 100644 --- a/admin-coach-tours.php +++ b/admin-coach-tours.php @@ -12,7 +12,7 @@ * Plugin URI: https://github.com/soderlind/admin-coach-tours * Description: Interactive guided tours for WordPress admin, enabling educators to create step-by-step tutorials and pupils to learn with guided overlays. * Version: 0.4.1 - * Requires at least: 6.8 + * Requires at least: 7.0 * Requires PHP: 8.3 * Author: Per Soderlind * Author URI: https://github.com/soderlind @@ -57,7 +57,7 @@ /** * Minimum WordPress version. */ -const MIN_WP_VERSION = '6.8'; +const MIN_WP_VERSION = '7.0'; /** * Minimum PHP version. @@ -100,11 +100,6 @@ function check_requirements(): bool { ); } - // Check sodium extension. - if ( ! function_exists( 'sodium_crypto_secretbox' ) ) { - $errors[] = __( 'Admin Coach Tours requires the PHP Sodium extension for secure API key storage.', 'admin-coach-tours' ); - } - if ( ! empty( $errors ) ) { add_action( 'admin_notices', @@ -160,6 +155,52 @@ function init(): void { // Enqueue admin assets (for settings page). add_action( 'admin_enqueue_scripts', __NAMESPACE__ . '\\enqueue_admin_assets' ); + + // One-time cleanup of legacy AI options after upgrading to the WP AI connector. + add_action( 'admin_init', __NAMESPACE__ . '\\maybe_upgrade' ); +} + +/** + * Run one-time upgrade routines when the stored version differs from the current one. + * + * Removes legacy provider API-key options that are now managed by the + * WordPress AI connector (wp_get_connectors) instead of this plugin. + * + * @return void + */ +function maybe_upgrade(): void { + $stored = get_option( 'act_version', '' ); + + if ( VERSION === $stored ) { + return; + } + + // Legacy options that stored encrypted API keys and per-provider config. + $legacy_options = [ + 'act_ai_provider', + 'act_ai_openai_api_key', + 'act_ai_openai_model', + 'act_ai_azure_api_key', + 'act_ai_azure_endpoint', + 'act_ai_azure_deployment', + 'act_ai_azure_model', + 'act_ai_anthropic_api_key', + 'act_ai_anthropic_model', + 'act_ai_api_key', + 'act_ai_endpoint', + 'act_encryption_key', + ]; + + // Preserve act_ai_provider only if it now holds a connector ID (repurposed). + // A fresh install has no legacy encrypted keys, so removing the others is safe. + foreach ( $legacy_options as $option ) { + if ( 'act_ai_provider' === $option ) { + continue; + } + delete_option( $option ); + } + + update_option( 'act_version', VERSION ); } /** diff --git a/php/AI/AiManager.php b/php/AI/AiManager.php index aa6e15d..7fbe10d 100644 --- a/php/AI/AiManager.php +++ b/php/AI/AiManager.php @@ -2,7 +2,9 @@ /** * AI Manager. * - * Manages AI providers and orchestrates AI operations. + * Thin orchestrator around the WordPress AI connector API + * (wp_get_connectors / wp_ai_client_prompt). API keys and provider + * configuration are owned by WordPress Core connectors, not this plugin. * * @package AdminCoachTours * @since 0.1.0 @@ -12,33 +14,31 @@ namespace AdminCoachTours\AI; -use AdminCoachTours\Security\Encryption; - /** * AI Manager class. */ class AiManager { /** - * Option name for AI settings. + * Option: whether AI features are enabled. * * @var string */ - private const OPTION_NAME = 'act_ai_settings'; + private const OPTION_ENABLED = 'act_ai_enabled'; /** - * Registered providers. + * Option: preferred connector (provider) ID. Empty = auto. * - * @var array + * @var string */ - private array $providers = []; + private const OPTION_PROVIDER = 'act_ai_provider'; /** - * Encryption helper. + * Option: preferred model ID override. Empty = provider default. * - * @var Encryption|null + * @var string */ - private ?Encryption $encryption = null; + private const OPTION_MODEL = 'act_ai_model'; /** * Singleton instance. @@ -62,271 +62,666 @@ public static function get_instance(): self { /** * Constructor. */ - private function __construct() { - $this->encryption = new Encryption(); - $this->register_default_providers(); - } + private function __construct() {} /** - * Register default AI providers. + * Check if AI features are enabled and a connector is configured. + * + * @return bool True if AI is available. */ - private function register_default_providers(): void { - $this->register_provider( new OpenAiProvider( $this->encryption ) ); - $this->register_provider( new AzureOpenAiProvider( $this->encryption ) ); - $this->register_provider( new AnthropicProvider( $this->encryption ) ); + public function is_available(): bool { + if ( ! (bool) get_option( self::OPTION_ENABLED, false ) ) { + return false; + } + + return $this->is_connector_configured(); + } + /** + * Check whether at least one usable AI provider connector exists. + * + * @return bool True if a configured connector is available. + */ + public function is_connector_configured(): bool { /** - * Filter to allow registering additional AI providers. + * Short-circuit connector detection. * - * @since 0.1.0 - * @param AiManager $manager The AI manager instance. + * @since 0.5.0 + * @param bool|null $configured Return a boolean to override detection, or null to let the plugin decide. */ - do_action( 'admin_coach_tours_register_ai_providers', $this ); + $filtered = apply_filters( 'admin_coach_tours_ai_connector_configured', null ); + if ( is_bool( $filtered ) ) { + return $filtered; + } + + return count( $this->get_configured_provider_ids() ) > 0; } /** - * Register an AI provider. + * Get the IDs of all configured AI provider connectors. * - * @param AiProviderInterface $provider Provider instance. + * @return array List of connector IDs. */ - public function register_provider( AiProviderInterface $provider ): void { - $this->providers[ $provider->get_id()] = $provider; + public function get_configured_provider_ids(): array { + if ( function_exists( 'wp_supports_ai' ) && ! wp_supports_ai() ) { + return []; + } + + if ( ! function_exists( 'wp_get_connectors' ) ) { + return []; + } + + try { + $connectors = wp_get_connectors(); + } catch ( \Throwable $e ) { + return []; + } + + $provider_ids = []; + + foreach ( (array) $connectors as $connector_id => $connector ) { + if ( ! is_array( $connector ) ) { + continue; + } + + if ( ( $connector[ 'type' ] ?? '' ) !== 'ai_provider' ) { + continue; + } + + $auth = $connector[ 'authentication' ] ?? []; + if ( ! is_array( $auth ) ) { + continue; + } + + $method = $auth[ 'method' ] ?? ''; + + if ( 'none' === $method ) { + $provider_ids[] = (string) $connector_id; + continue; + } + + if ( 'api_key' !== $method ) { + continue; + } + + $setting_name = (string) ( $auth[ 'setting_name' ] ?? '' ); + if ( '' === $setting_name ) { + continue; + } + + $env_var_name = (string) ( $auth[ 'env_var_name' ] ?? '' ); + $constant_name = (string) ( $auth[ 'constant_name' ] ?? '' ); + + if ( function_exists( '_wp_connectors_get_api_key_source' ) ) { + $source = _wp_connectors_get_api_key_source( $setting_name, $env_var_name, $constant_name ); + if ( 'none' !== $source ) { + $provider_ids[] = (string) $connector_id; + } + } + } + + return array_values( + array_unique( + array_filter( $provider_ids, static fn( string $id ): bool => '' !== $id ) + ) + ); + } + + /** + * Get configured connectors as an id => label map (for the settings UI). + * + * @return array Map of connector ID to human-readable label. + */ + public function get_configured_connectors(): array { + $ids = $this->get_configured_provider_ids(); + + if ( empty( $ids ) || ! function_exists( 'wp_get_connectors' ) ) { + return []; + } + + try { + $connectors = wp_get_connectors(); + } catch ( \Throwable $e ) { + return []; + } + + $map = []; + + foreach ( $ids as $id ) { + $connector = $connectors[ $id ] ?? null; + $label = $id; + + if ( is_array( $connector ) ) { + $label = (string) ( $connector[ 'label' ] ?? $connector[ 'name' ] ?? $id ); + } + + $map[ $id ] = '' !== $label ? $label : $id; + } + + return $map; } /** - * Get all registered providers. + * Resolve the provider (connector) ID to use for generation. * - * @return array + * @return string Connector ID, or empty string for automatic selection. */ - public function get_providers(): array { - return $this->providers; + public function resolve_provider_id(): string { + /** + * Filter the AI provider (connector) ID. + * + * @since 0.5.0 + * @param string $provider_id Provider ID. Empty = use stored option / auto. + */ + $filtered = (string) apply_filters( 'admin_coach_tours_ai_provider_id', '' ); + if ( '' !== $filtered ) { + return $filtered; + } + + $ids = $this->get_configured_provider_ids(); + $stored = (string) get_option( self::OPTION_PROVIDER, '' ); + + if ( '' !== $stored && in_array( $stored, $ids, true ) ) { + return $stored; + } + + return $ids[ 0 ] ?? ''; } /** - * Get a specific provider. + * Resolve the model ID override to use for generation. * - * @param string $provider_id Provider ID. - * @return AiProviderInterface|null Provider or null. + * @return string Model ID, or empty string for the provider default. */ - public function get_provider( string $provider_id ): ?AiProviderInterface { - return $this->providers[ $provider_id ] ?? null; + public function resolve_model(): string { + /** + * Filter the AI model ID. + * + * @since 0.5.0 + * @param string $model Model ID. Empty = provider default. + */ + $filtered = (string) apply_filters( 'admin_coach_tours_ai_model', '' ); + if ( '' !== $filtered ) { + return $filtered; + } + + return (string) get_option( self::OPTION_MODEL, '' ); } /** - * Get the active provider. + * Generate a step draft using AI. * - * @return AiProviderInterface|null Active provider or null. + * @param array $element_context Context about the target element. + * @param array $tour_context Context about the tour (optional). + * @return array|\WP_Error Generated draft or error. */ - public function get_active_provider(): ?AiProviderInterface { - $settings = $this->get_settings(); - $active_id = $settings[ 'active_provider' ] ?? ''; - - if ( $active_id && isset( $this->providers[ $active_id ] ) ) { - $provider = $this->providers[ $active_id ]; - if ( $provider->is_configured() ) { - return $provider; - } + public function generate_step_draft( array $element_context, array $tour_context = [] ): array|\WP_Error { + if ( ! $this->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); } - // Fall back to first configured provider. - foreach ( $this->providers as $provider ) { - if ( $provider->is_configured() ) { - return $provider; - } + $system_prompt = $this->build_draft_system_prompt(); + $user_prompt = $this->build_draft_user_prompt( $element_context, $tour_context ); + + $text = $this->run_prompt( $system_prompt, $user_prompt, 0.7, 800 ); + + if ( is_wp_error( $text ) ) { + return $text; } - return null; + $content = $this->decode_json( $text ); + + if ( null === $content ) { + return new \WP_Error( + 'parse_error', + __( 'Could not parse AI response.', 'admin-coach-tours' ) + ); + } + + return $this->validate_and_sanitize_draft( $content ); } /** - * Get AI settings. + * Generate a complete tour using AI. * - * @return array Settings. + * @param string $system_prompt The system prompt with task instructions. + * @param string $user_message Optional user message for freeform queries. + * @return array|\WP_Error Generated tour with title and steps, or error. */ - public function get_settings(): array { - return [ - 'active_provider' => get_option( 'act_ai_provider', '' ), - 'enabled' => (bool) get_option( 'act_ai_enabled', false ), - ]; + public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { + if ( ! $this->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } + + $user = '' !== $user_message + ? $user_message + : 'Generate the tour now. Return only valid JSON.'; + + $text = $this->run_prompt( $system_prompt, $user, 0.7, 4000 ); + + if ( is_wp_error( $text ) ) { + return $text; + } + + $content = $this->decode_json( $text ); + + if ( null === $content ) { + return new \WP_Error( + 'parse_error', + __( 'Could not parse AI response.', 'admin-coach-tours' ) + ); + } + + // Check for scope error from freeform queries. + if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { + return new \WP_Error( + 'out_of_scope', + $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) + ); + } + + return $this->validate_and_sanitize_tour( $content ); } /** - * Update AI settings. + * Suggest a completion condition based on element context (heuristics, no AI call). * - * @param array $settings Settings to save. - * @return bool True on success. + * @param array $element_context Context about the target element. + * @return array Suggested completion. */ - public function update_settings( array $settings ): bool { - // Sanitize settings. - $sanitized = [ - 'active_provider' => sanitize_key( $settings[ 'active_provider' ] ?? '' ), - 'enabled' => (bool) ( $settings[ 'enabled' ] ?? false ), - 'providers' => [], - ]; + public function suggest_completion( array $element_context ): array { + $tag = $element_context[ 'tagName' ] ?? ''; + $role = $element_context[ 'role' ] ?? ''; + + // Checkbox - suggest click. + if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { + return [ + 'type' => 'clickTarget', + 'params' => [], + ]; + } - // Sanitize provider-specific settings. - if ( isset( $settings[ 'providers' ] ) && is_array( $settings[ 'providers' ] ) ) { - foreach ( $settings[ 'providers' ] as $provider_id => $provider_settings ) { - $provider = $this->get_provider( $provider_id ); - if ( $provider ) { - $schema = $provider->get_settings_schema(); - $sanitized[ 'providers' ][ $provider_id ] = $this->sanitize_provider_settings( - $provider_settings, - $schema - ); - } - } + // Input fields - suggest value change. + if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { + return [ + 'type' => 'domValueChanged', + 'params' => [], + ]; + } + + // Button or link - suggest click. + if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { + return [ + 'type' => 'clickTarget', + 'params' => [], + ]; } - return update_option( self::OPTION_NAME, $sanitized ); + // Default to manual. + return [ + 'type' => 'manual', + 'params' => [], + ]; } /** - * Sanitize provider settings based on schema. + * Run a prompt through the WordPress AI client and return the text output. * - * @param array $settings Provider settings. - * @param array $schema Settings schema. - * @return array Sanitized settings. + * @param string $system_prompt System instruction. + * @param string $user_message User message / prompt. + * @param float $temperature Sampling temperature. + * @param int $max_tokens Maximum output tokens. + * @return string|\WP_Error Generated text or error. */ - private function sanitize_provider_settings( array $settings, array $schema ): array { - $sanitized = []; + private function run_prompt( string $system_prompt, string $user_message, float $temperature, int $max_tokens ): string|\WP_Error { + if ( ! function_exists( 'wp_ai_client_prompt' ) ) { + return new \WP_Error( + 'ai_unavailable', + __( 'The WordPress AI client is not available.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } - foreach ( $schema as $key => $config ) { - if ( ! isset( $settings[ $key ] ) ) { - if ( isset( $config[ 'default' ] ) ) { - $sanitized[ $key ] = $config[ 'default' ]; - } - continue; - } + try { + $builder = wp_ai_client_prompt( $user_message ); - $value = $settings[ $key ]; - $type = $config[ 'type' ] ?? 'string'; + if ( method_exists( $builder, 'using_system_instruction' ) ) { + $builder = $this->apply_builder( $builder, 'using_system_instruction', $system_prompt ); + } else { + // No system-instruction support: fold it into the prompt. + $builder = wp_ai_client_prompt( trim( $system_prompt . "\n\n" . $user_message ) ); + } - switch ( $type ) { - case 'string': - $sanitized[ $key ] = sanitize_text_field( $value ); - break; + $builder = $this->apply_builder( $builder, 'using_temperature', $temperature ); + $builder = $this->apply_builder( $builder, 'using_max_tokens', $max_tokens ); - case 'secret': - // Encrypt API keys. - if ( ! empty( $value ) ) { - $sanitized[ $key ] = $this->encryption->encrypt( $value ); - } - break; + $provider_id = $this->resolve_provider_id(); + if ( '' !== $provider_id ) { + $builder = $this->apply_builder( $builder, 'using_provider', $provider_id ); + } - case 'boolean': - $sanitized[ $key ] = (bool) $value; - break; + $model = $this->resolve_model(); + if ( '' !== $model ) { + $builder = $this->apply_builder( $builder, 'using_model', $model ); + } - case 'integer': - $sanitized[ $key ] = (int) $value; - break; + $result = $builder->generate_text(); + } catch ( \Throwable $e ) { + return new \WP_Error( 'api_error', $e->getMessage(), [ 'status' => 502 ] ); + } - case 'float': - $sanitized[ $key ] = (float) $value; - break; + if ( is_wp_error( $result ) ) { + return $result; + } - case 'select': - if ( in_array( $value, $config[ 'options' ] ?? [], true ) ) { - $sanitized[ $key ] = $value; - } elseif ( isset( $config[ 'default' ] ) ) { - $sanitized[ $key ] = $config[ 'default' ]; - } - break; + if ( is_string( $result ) && '' !== trim( $result ) ) { + return $result; + } - default: - $sanitized[ $key ] = sanitize_text_field( $value ); + if ( is_array( $result ) ) { + $candidate = $result[ 'text' ] ?? $result[ 'content' ] ?? ''; + if ( is_string( $candidate ) && '' !== trim( $candidate ) ) { + return $candidate; } } - return $sanitized; + return new \WP_Error( + 'invalid_response', + __( 'The AI client returned an empty response.', 'admin-coach-tours' ), + [ 'status' => 502 ] + ); } /** - * Check if AI is enabled and configured. + * Call a fluent builder method if it exists, returning the resulting builder. * - * @return bool True if AI is available. + * @param object $builder The prompt builder. + * @param string $method Method name. + * @param mixed $value Argument. + * @return object The (possibly new) builder. */ - public function is_available(): bool { - $settings = $this->get_settings(); - - if ( ! ( $settings[ 'enabled' ] ?? false ) ) { - return false; + private function apply_builder( object $builder, string $method, mixed $value ): object { + if ( ! method_exists( $builder, $method ) ) { + return $builder; } - return null !== $this->get_active_provider(); + $result = $builder->{$method}( $value ); + + return is_object( $result ) ? $result : $builder; } /** - * Generate a step draft using AI. + * Decode a JSON string from an AI response, tolerating Markdown code fences. * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour (optional). - * @return array|\WP_Error Generated draft or error. + * @param string $text Raw AI text. + * @return array|null Decoded array, or null on failure. */ - public function generate_step_draft( array $element_context, array $tour_context = [] ): array|\WP_Error { - if ( ! $this->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ) - ); + private function decode_json( string $text ): ?array { + $text = trim( $text ); + + // Strip ```json ... ``` fences if present. + if ( str_starts_with( $text, '```' ) ) { + $text = (string) preg_replace( '/^```(?:json)?\s*/i', '', $text ); + $text = (string) preg_replace( '/\s*```$/', '', $text ); + $text = trim( $text ); } - $provider = $this->get_active_provider(); + $data = json_decode( $text, true ); + if ( is_array( $data ) ) { + return $data; + } - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is configured.', 'admin-coach-tours' ) - ); + // Fallback: extract the first {...} block. + if ( preg_match( '/\{.*\}/s', $text, $matches ) ) { + $data = json_decode( $matches[ 0 ], true ); + if ( is_array( $data ) ) { + return $data; + } } - return $provider->generate_step_draft( $element_context, $tour_context ); + return null; } /** - * Suggest a completion condition. + * Build the system prompt for a single step draft. * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. + * @return string System prompt. */ - public function suggest_completion( array $element_context ): array|\WP_Error { - if ( ! $this->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ) - ); + private function build_draft_system_prompt(): string { + return <<<'PROMPT' +You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. + +Given information about a UI element, generate: +1. A concise title (5-10 words) describing the action +2. Helpful content (1-3 sentences) explaining what to do and why +3. A suggested completion condition type + +Respond ONLY with valid JSON in this exact format: +{ + "title": "Click the Publish button", + "content": "The Publish button makes your content live...", + "suggestedCompletion": { + "type": "clickTarget", + "params": {} + } +} + +Completion types available: +- clickTarget: User must click the highlighted element +- domValueChanged: User must change a form field value +- manual: User clicks continue button +- wpData: Watch for WordPress data store changes + +Be friendly but concise. Focus on the action and its purpose. +PROMPT; + } + + /** + * Build the user prompt for a single step draft. + * + * @param array $element_context Element context. + * @param array $tour_context Tour context. + * @return string User prompt. + */ + private function build_draft_user_prompt( array $element_context, array $tour_context ): string { + $parts = [ 'Generate step content for this UI element:' ]; + + $parts[] = 'Element: ' . wp_json_encode( $element_context ); + + if ( ! empty( $tour_context ) ) { + $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); } - $provider = $this->get_active_provider(); + return implode( "\n\n", $parts ); + } - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is configured.', 'admin-coach-tours' ) - ); + /** + * Validate and sanitize a single step draft. + * + * @param array $content Raw content from AI. + * @return array Sanitized draft. + */ + private function validate_and_sanitize_draft( array $content ): array { + $draft = [ + 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), + 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), + ]; + + if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { + $completion = $content[ 'suggestedCompletion' ]; + $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; + + if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { + $draft[ 'suggestedCompletion' ] = [ + 'type' => $completion[ 'type' ], + 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], + ]; + } } - return $provider->suggest_completion( $element_context ); + return $draft; } /** - * Get providers info for settings page. + * Validate and sanitize a generated tour. * - * @return array Provider info. + * @param array $content Raw tour content from AI. + * @return array|\WP_Error Sanitized tour or error. */ - public function get_providers_info(): array { - $info = []; - - foreach ( $this->providers as $id => $provider ) { - $info[ $id ] = [ - 'id' => $id, - 'name' => $provider->get_name(), - 'configured' => $provider->is_configured(), - 'schema' => $provider->get_settings_schema(), + private function validate_and_sanitize_tour( array $content ): array|\WP_Error { + if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { + return new \WP_Error( + 'invalid_tour_format', + __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) + ); + } + + if ( empty( $content[ 'steps' ] ) ) { + return new \WP_Error( + 'empty_tour', + __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) + ); + } + + $tour = [ + 'title' => sanitize_text_field( $content[ 'title' ] ), + 'steps' => [], + ]; + + $allowed_completion_types = [ + 'clickTarget', + 'domValueChanged', + 'manual', + 'wpData', + 'elementAppear', + 'elementDisappear', + 'customEvent', + ]; + + $allowed_precondition_types = [ + 'ensureEditor', + 'ensureSidebarOpen', + 'ensureSidebarClosed', + 'selectSidebarTab', + 'openInserter', + 'closeInserter', + 'selectBlock', + 'focusElement', + 'scrollIntoView', + 'openModal', + 'closeModal', + 'insertBlock', + ]; + + $allowed_locator_types = [ + 'css', + 'role', + 'testId', + 'dataAttribute', + 'ariaLabel', + 'contextual', + 'wpBlock', + ]; + + foreach ( $content[ 'steps' ] as $index => $step ) { + $sanitized_step = [ + 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), + 'order' => (int) ( $step[ 'order' ] ?? $index ), + 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), + 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), + 'target' => [ + 'locators' => [], + 'constraints' => [ + 'visible' => true, + ], + ], + 'preconditions' => [], + 'completion' => [ + 'type' => 'manual', + ], ]; + + // Process locators. + if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { + foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { + if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { + continue; + } + + if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { + continue; + } + + $sanitized_step[ 'target' ][ 'locators' ][] = [ + 'type' => $locator[ 'type' ], + 'value' => sanitize_text_field( $locator[ 'value' ] ), + 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), + 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), + ]; + } + } + + // Process constraints. + if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { + $constraints = $step[ 'target' ][ 'constraints' ]; + $sanitized_step[ 'target' ][ 'constraints' ] = [ + 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), + 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), + ]; + } + + // Process preconditions. + if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { + foreach ( $step[ 'preconditions' ] as $precondition ) { + if ( ! isset( $precondition[ 'type' ] ) ) { + continue; + } + + if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { + continue; + } + + $sanitized_precondition = [ + 'type' => $precondition[ 'type' ], + ]; + + if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { + $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); + } + + $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; + } + } + + // Process completion. + if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { + $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; + + if ( in_array( $completion_type, $allowed_completion_types, true ) ) { + $sanitized_step[ 'completion' ] = [ + 'type' => $completion_type, + ]; + + if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { + $sanitized_step[ 'completion' ][ 'params' ] = array_map( + 'sanitize_text_field', + $step[ 'completion' ][ 'params' ] + ); + } + } + } + + $tour[ 'steps' ][] = $sanitized_step; } - return $info; + return $tour; } } diff --git a/php/AI/AiProviderInterface.php b/php/AI/AiProviderInterface.php deleted file mode 100644 index 5120a73..0000000 --- a/php/AI/AiProviderInterface.php +++ /dev/null @@ -1,82 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'Anthropic (Claude)', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - return ! empty( $api_key ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the model to use. - * - * @return string Model name. - */ - private function get_model(): string { - $model = get_option( 'act_ai_' . self::ID . '_model', '' ); - return ! empty( $model ) ? $model : 'claude-3-haiku-20240307'; - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'Anthropic API key is not configured.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 30, - 'headers' => [ - 'x-api-key' => $api_key, - 'anthropic-version' => self::API_VERSION, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'system' => $system_prompt, - 'messages' => [ - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'max_tokens' => 500, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'content' ][ 0 ][ 'text' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Anthropic.', 'admin-coach-tours' ) - ); - } - - // Extract JSON from response. - $text = $data[ 'content' ][ 0 ][ 'text' ]; - $json_match = []; - - if ( preg_match( '/\{[^{}]*\}/s', $text, $json_match ) ) { - $content = json_decode( $json_match[ 0 ], true ); - } else { - $content = json_decode( $text, true ); - } - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics for simplicity. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your Anthropic API key.', 'admin-coach-tours' ), - 'required' => true, - ], - 'model' => [ - 'type' => 'select', - 'label' => __( 'Model', 'admin-coach-tours' ), - 'description' => __( 'The Claude model to use.', 'admin-coach-tours' ), - 'options' => [ - 'claude-3-haiku-20240307', - 'claude-3-sonnet-20240229', - 'claude-3-opus-20240229', - 'claude-3-5-sonnet-20241022', - ], - 'default' => 'claude-3-haiku-20240307', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - // Basic format check. - if ( ! str_starts_with( $settings[ 'api_key' ], 'sk-ant-' ) ) { - return new \WP_Error( - 'invalid_api_key', - __( 'Invalid Anthropic API key format.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'Anthropic API key is not configured.', 'admin-coach-tours' ) - ); - } - - $user_content = ! empty( $user_message ) - ? $user_message - : 'Generate the tour now. Return only valid JSON.'; - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'x-api-key' => $api_key, - 'anthropic-version' => self::API_VERSION, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'system' => $system_prompt, - 'messages' => [ - [ - 'role' => 'user', - 'content' => $user_content, - ], - ], - 'max_tokens' => 4000, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'content' ][ 0 ][ 'text' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Anthropic.', 'admin-coach-tours' ) - ); - } - - // Extract JSON from response - Claude may wrap it in markdown code blocks. - $text = $data[ 'content' ][ 0 ][ 'text' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from Anthropic: ' . $text ); - - // Try to extract JSON from code blocks first. - if ( preg_match( '/```(?:json)?\s*(\{[\s\S]*?\})\s*```/', $text, $json_match ) ) { - $content = json_decode( $json_match[ 1 ], true ); - } elseif ( preg_match( '/(\{[\s\S]*\})/', $text, $json_match ) ) { - // Fall back to finding any JSON object. - $content = json_decode( $json_match[ 1 ], true ); - } else { - $content = json_decode( $text, true ); - } - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/AI/AzureOpenAiProvider.php b/php/AI/AzureOpenAiProvider.php deleted file mode 100644 index 5d020fb..0000000 --- a/php/AI/AzureOpenAiProvider.php +++ /dev/null @@ -1,683 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'Azure OpenAI', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - return ! empty( $api_key ) && ! empty( $endpoint ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the Azure endpoint URL. - * - * @return string|null Endpoint URL or null. - */ - private function get_endpoint(): ?string { - $endpoint = get_option( 'act_ai_' . self::ID . '_endpoint', '' ); - return ! empty( $endpoint ) ? $endpoint : null; - } - - /** - * Get the deployment name. - * - * @return string Deployment name. - */ - private function get_deployment(): string { - $deployment = get_option( 'act_ai_' . self::ID . '_deployment', '' ); - return ! empty( $deployment ) ? $deployment : 'gpt-4o-mini'; - } - - /** - * Get the API version. - * - * @return string API version. - */ - private function get_api_version(): string { - $api_version = get_option( 'act_ai_' . self::ID . '_api_version', '' ); - return ! empty( $api_version ) ? $api_version : '2024-08-01-preview'; - } - - /** - * Build the API URL. - * - * @return string Full API URL. - */ - private function build_api_url(): string { - $endpoint = rtrim( $this->get_endpoint() ?? '', '/' ); - $deployment = $this->get_deployment(); - $version = $this->get_api_version(); - - return sprintf( - '%s/openai/deployments/%s/chat/completions?api-version=%s', - $endpoint, - rawurlencode( $deployment ), - rawurlencode( $version ) - ); - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - - if ( ! $api_key || ! $endpoint ) { - return new \WP_Error( - 'not_configured', - __( 'Azure OpenAI is not fully configured. Please set both the API key and endpoint.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - $this->build_api_url(), - [ - 'timeout' => 30, - 'headers' => [ - 'api-key' => $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 500, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Azure OpenAI.', 'admin-coach-tours' ) - ); - } - - $content = json_decode( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ], true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics instead of AI for this simple case. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - // Button or link - suggest click. - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Input fields - suggest value change. - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - // Checkbox - suggest click. - if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Default to manual. - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'endpoint' => [ - 'type' => 'text', - 'label' => __( 'Endpoint URL', 'admin-coach-tours' ), - 'description' => __( 'Your Azure OpenAI resource endpoint (e.g., https://your-resource.openai.azure.com).', 'admin-coach-tours' ), - 'required' => true, - ], - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your Azure OpenAI API key.', 'admin-coach-tours' ), - 'required' => true, - 'sensitive' => true, - ], - 'deployment' => [ - 'type' => 'text', - 'label' => __( 'Deployment Name', 'admin-coach-tours' ), - 'description' => __( 'The name of your model deployment in Azure.', 'admin-coach-tours' ), - 'default' => 'gpt-4o-mini', - ], - 'api_version' => [ - 'type' => 'text', - 'label' => __( 'API Version', 'admin-coach-tours' ), - 'description' => __( 'The Azure OpenAI API version to use (e.g., 2024-08-01-preview).', 'admin-coach-tours' ), - 'default' => '2024-08-01-preview', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - if ( empty( $settings[ 'endpoint' ] ) ) { - return new \WP_Error( - 'missing_endpoint', - __( 'Endpoint URL is required.', 'admin-coach-tours' ) - ); - } - - // Validate endpoint URL format. - if ( ! filter_var( $settings[ 'endpoint' ], FILTER_VALIDATE_URL ) ) { - return new \WP_Error( - 'invalid_endpoint', - __( 'Endpoint must be a valid URL.', 'admin-coach-tours' ) - ); - } - - // Ensure endpoint uses HTTPS. - if ( ! str_starts_with( $settings[ 'endpoint' ], 'https://' ) ) { - return new \WP_Error( - 'insecure_endpoint', - __( 'Endpoint must use HTTPS.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - $endpoint = $this->get_endpoint(); - - if ( ! $api_key || ! $endpoint ) { - return new \WP_Error( - 'not_configured', - __( 'Azure OpenAI is not configured.', 'admin-coach-tours' ) - ); - } - - $user_content = ! empty( $user_message ) - ? $user_message - : 'Generate the tour now. Return only valid JSON.'; - - $response = wp_remote_post( - $this->build_api_url(), - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'api-key' => $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_content, - ], - ], - 'max_tokens' => 4000, - 'response_format' => [ 'type' => 'json_object' ], - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from Azure OpenAI.', 'admin-coach-tours' ) - ); - } - - $raw_content = $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from Azure OpenAI: ' . $raw_content ); - - $content = json_decode( $raw_content, true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/AI/OpenAiProvider.php b/php/AI/OpenAiProvider.php deleted file mode 100644 index 6f03169..0000000 --- a/php/AI/OpenAiProvider.php +++ /dev/null @@ -1,633 +0,0 @@ -encryption = $encryption; - } - - /** - * Get the provider identifier. - * - * @return string Provider ID. - */ - public function get_id(): string { - return self::ID; - } - - /** - * Get the provider display name. - * - * @return string Provider name. - */ - public function get_name(): string { - return __( 'OpenAI', 'admin-coach-tours' ); - } - - /** - * Check if the provider is configured and ready to use. - * - * @return bool True if configured. - */ - public function is_configured(): bool { - $api_key = $this->get_api_key(); - return ! empty( $api_key ); - } - - /** - * Get the API key. - * - * @return string|null API key or null. - */ - private function get_api_key(): ?string { - $encrypted_key = get_option( 'act_ai_' . self::ID . '_api_key', '' ); - - if ( empty( $encrypted_key ) ) { - return null; - } - - $decrypted = $this->encryption->decrypt( $encrypted_key ); - - return $decrypted ?: null; - } - - /** - * Get the model to use. - * - * @return string Model name. - */ - private function get_model(): string { - $model = get_option( 'act_ai_' . self::ID . '_model', '' ); - return ! empty( $model ) ? $model : 'gpt-4o-mini'; - } - - /** - * Generate a step draft using AI. - * - * @param array $element_context Context about the target element. - * @param array $tour_context Context about the tour. - * @return array|\WP_Error Generated draft or error. - */ - public function generate_step_draft( array $element_context, array $tour_context ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'OpenAI API key is not configured.', 'admin-coach-tours' ) - ); - } - - $system_prompt = $this->build_system_prompt(); - $user_prompt = $this->build_user_prompt( $element_context, $tour_context ); - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 30, - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'messages' => [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - [ - 'role' => 'user', - 'content' => $user_prompt, - ], - ], - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 500, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from OpenAI.', 'admin-coach-tours' ) - ); - } - - $content = json_decode( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ], true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_draft( $content ); - } - - /** - * Suggest a completion condition based on element context. - * - * @param array $element_context Context about the target element. - * @return array|\WP_Error Suggested completion or error. - */ - public function suggest_completion( array $element_context ): array|\WP_Error { - // Use heuristics instead of AI for this simple case. - $tag = $element_context[ 'tagName' ] ?? ''; - $role = $element_context[ 'role' ] ?? ''; - - // Button or link - suggest click. - if ( in_array( $tag, [ 'button', 'a' ], true ) || 'button' === $role || 'link' === $role ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Input fields - suggest value change. - if ( in_array( $tag, [ 'input', 'textarea', 'select' ], true ) || 'textbox' === $role ) { - return [ - 'type' => 'domValueChanged', - 'params' => [], - ]; - } - - // Checkbox - suggest click. - if ( 'checkbox' === $role || ( 'input' === $tag && 'checkbox' === ( $element_context[ 'type' ] ?? '' ) ) ) { - return [ - 'type' => 'clickTarget', - 'params' => [], - ]; - } - - // Default to manual. - return [ - 'type' => 'manual', - 'params' => [], - ]; - } - - /** - * Get provider settings schema. - * - * @return array Settings schema. - */ - public function get_settings_schema(): array { - return [ - 'api_key' => [ - 'type' => 'secret', - 'label' => __( 'API Key', 'admin-coach-tours' ), - 'description' => __( 'Your OpenAI API key.', 'admin-coach-tours' ), - 'required' => true, - ], - 'model' => [ - 'type' => 'select', - 'label' => __( 'Model', 'admin-coach-tours' ), - 'description' => __( 'The OpenAI model to use.', 'admin-coach-tours' ), - 'options' => [ 'gpt-4o-mini', 'gpt-4o', 'gpt-4-turbo', 'gpt-3.5-turbo' ], - 'default' => 'gpt-4o-mini', - ], - ]; - } - - /** - * Validate provider settings. - * - * @param array $settings Settings to validate. - * @return bool|\WP_Error True if valid, error otherwise. - */ - public function validate_settings( array $settings ): bool|\WP_Error { - if ( empty( $settings[ 'api_key' ] ) ) { - return new \WP_Error( - 'missing_api_key', - __( 'API key is required.', 'admin-coach-tours' ) - ); - } - - // Basic format check. - if ( ! str_starts_with( $settings[ 'api_key' ], 'sk-' ) ) { - return new \WP_Error( - 'invalid_api_key', - __( 'Invalid API key format.', 'admin-coach-tours' ) - ); - } - - return true; - } - - /** - * Build the system prompt. - * - * @return string System prompt. - */ - private function build_system_prompt(): string { - return <<<'PROMPT' -You are an expert WordPress admin UI instructor. Your task is to generate clear, helpful step content for a guided tour of the WordPress admin interface. - -Given information about a UI element, generate: -1. A concise title (5-10 words) describing the action -2. Helpful content (1-3 sentences) explaining what to do and why -3. A suggested completion condition type - -Respond ONLY with valid JSON in this exact format: -{ - "title": "Click the Publish button", - "content": "The Publish button makes your content live...", - "suggestedCompletion": { - "type": "clickTarget", - "params": {} - } -} - -Completion types available: -- clickTarget: User must click the highlighted element -- domValueChanged: User must change a form field value -- manual: User clicks continue button -- wpData: Watch for WordPress data store changes - -Be friendly but concise. Focus on the action and its purpose. -PROMPT; - } - - /** - * Build the user prompt. - * - * @param array $element_context Element context. - * @param array $tour_context Tour context. - * @return string User prompt. - */ - private function build_user_prompt( array $element_context, array $tour_context ): string { - $parts = [ 'Generate step content for this UI element:' ]; - - $parts[] = 'Element: ' . wp_json_encode( $element_context ); - - if ( ! empty( $tour_context ) ) { - $parts[] = 'Tour context: ' . wp_json_encode( $tour_context ); - } - - return implode( "\n\n", $parts ); - } - - /** - * Validate and sanitize the AI draft. - * - * @param array $content Raw content from AI. - * @return array Sanitized draft. - */ - private function validate_and_sanitize_draft( array $content ): array { - $draft = [ - 'title' => sanitize_text_field( $content[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $content[ 'content' ] ?? '' ), - ]; - - if ( isset( $content[ 'suggestedCompletion' ] ) && is_array( $content[ 'suggestedCompletion' ] ) ) { - $completion = $content[ 'suggestedCompletion' ]; - $allowed_types = [ 'clickTarget', 'domValueChanged', 'manual', 'wpData' ]; - - if ( in_array( $completion[ 'type' ] ?? '', $allowed_types, true ) ) { - $draft[ 'suggestedCompletion' ] = [ - 'type' => $completion[ 'type' ], - 'params' => is_array( $completion[ 'params' ] ?? null ) ? $completion[ 'params' ] : [], - ]; - } - } - - return $draft; - } - - /** - * Generate a complete tour using AI. - * - * @since 0.3.0 - * @param string $system_prompt The system prompt with task instructions. - * @param string $user_message Optional user message for freeform queries. - * @return array|\WP_Error Generated tour with title and steps, or error. - */ - public function generate_tour( string $system_prompt, string $user_message = '' ): array|\WP_Error { - $api_key = $this->get_api_key(); - - if ( ! $api_key ) { - return new \WP_Error( - 'not_configured', - __( 'OpenAI API key is not configured.', 'admin-coach-tours' ) - ); - } - - $messages = [ - [ - 'role' => 'system', - 'content' => $system_prompt, - ], - ]; - - // Add user message for freeform queries. - if ( ! empty( $user_message ) ) { - $messages[] = [ - 'role' => 'user', - 'content' => $user_message, - ]; - } else { - // For predefined tasks, just request the tour. - $messages[] = [ - 'role' => 'user', - 'content' => 'Generate the tour now. Return only valid JSON.', - ]; - } - - $response = wp_remote_post( - self::API_ENDPOINT, - [ - 'timeout' => 60, - // Longer timeout for tour generation. - 'headers' => [ - 'Authorization' => 'Bearer ' . $api_key, - 'Content-Type' => 'application/json', - ], - 'body' => wp_json_encode( - [ - 'model' => $this->get_model(), - 'messages' => $messages, - 'response_format' => [ 'type' => 'json_object' ], - 'max_tokens' => 4000, - 'temperature' => 0.7, - ] - ), - ] - ); - - if ( is_wp_error( $response ) ) { - return $response; - } - - $code = wp_remote_retrieve_response_code( $response ); - $body = wp_remote_retrieve_body( $response ); - - if ( 200 !== $code ) { - $error_data = json_decode( $body, true ); - return new \WP_Error( - 'api_error', - $error_data[ 'error' ][ 'message' ] ?? __( 'API request failed.', 'admin-coach-tours' ), - [ 'status' => $code ] - ); - } - - $data = json_decode( $body, true ); - - if ( ! isset( $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ] ) ) { - return new \WP_Error( - 'invalid_response', - __( 'Invalid response from OpenAI.', 'admin-coach-tours' ) - ); - } - - $raw_content = $data[ 'choices' ][ 0 ][ 'message' ][ 'content' ]; - - // Log the raw AI response for debugging. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log - error_log( '[ACT AI Response] Raw content from OpenAI: ' . $raw_content ); - - $content = json_decode( $raw_content, true ); - - if ( ! $content ) { - return new \WP_Error( - 'parse_error', - __( 'Could not parse AI response.', 'admin-coach-tours' ) - ); - } - - // Log the parsed tour structure. - // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log, WordPress.PHP.DevelopmentFunctions.error_log_print_r - error_log( '[ACT AI Response] Parsed tour: ' . print_r( $content, true ) ); - - // Check for scope error from freeform queries. - if ( isset( $content[ 'error' ] ) && 'scope' === $content[ 'error' ] ) { - return new \WP_Error( - 'out_of_scope', - $content[ 'message' ] ?? __( 'This question is outside the scope of the editor assistant.', 'admin-coach-tours' ) - ); - } - - return $this->validate_and_sanitize_tour( $content ); - } - - /** - * Validate and sanitize a generated tour. - * - * @since 0.3.0 - * @param array $content Raw tour content from AI. - * @return array|\WP_Error Sanitized tour or error. - */ - private function validate_and_sanitize_tour( array $content ): array|\WP_Error { - if ( ! isset( $content[ 'title' ] ) || ! isset( $content[ 'steps' ] ) || ! is_array( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'invalid_tour_format', - __( 'AI response did not contain a valid tour structure.', 'admin-coach-tours' ) - ); - } - - if ( empty( $content[ 'steps' ] ) ) { - return new \WP_Error( - 'empty_tour', - __( 'AI generated a tour with no steps.', 'admin-coach-tours' ) - ); - } - - $tour = [ - 'title' => sanitize_text_field( $content[ 'title' ] ), - 'steps' => [], - ]; - - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; - - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; - - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; - - foreach ( $content[ 'steps' ] as $index => $step ) { - $sanitized_step = [ - 'id' => sanitize_key( $step[ 'id' ] ?? 'step-' . $index ), - 'order' => (int) ( $step[ 'order' ] ?? $index ), - 'title' => sanitize_text_field( $step[ 'title' ] ?? '' ), - 'content' => wp_kses_post( $step[ 'content' ] ?? '' ), - 'target' => [ - 'locators' => [], - 'constraints' => [ - 'visible' => true, - ], - ], - 'preconditions' => [], - 'completion' => [ - 'type' => 'manual', - ], - ]; - - // Process locators. - if ( isset( $step[ 'target' ][ 'locators' ] ) && is_array( $step[ 'target' ][ 'locators' ] ) ) { - foreach ( $step[ 'target' ][ 'locators' ] as $locator ) { - if ( ! isset( $locator[ 'type' ] ) || ! isset( $locator[ 'value' ] ) ) { - continue; - } - - if ( ! in_array( $locator[ 'type' ], $allowed_locator_types, true ) ) { - continue; - } - - $sanitized_step[ 'target' ][ 'locators' ][] = [ - 'type' => $locator[ 'type' ], - 'value' => sanitize_text_field( $locator[ 'value' ] ), - 'weight' => (int) ( $locator[ 'weight' ] ?? 50 ), - 'fallback' => (bool) ( $locator[ 'fallback' ] ?? false ), - ]; - } - } - - // Process constraints. - if ( isset( $step[ 'target' ][ 'constraints' ] ) && is_array( $step[ 'target' ][ 'constraints' ] ) ) { - $constraints = $step[ 'target' ][ 'constraints' ]; - $sanitized_step[ 'target' ][ 'constraints' ] = [ - 'visible' => (bool) ( $constraints[ 'visible' ] ?? true ), - 'inEditorIframe' => (bool) ( $constraints[ 'inEditorIframe' ] ?? false ), - ]; - } - - // Process preconditions. - if ( isset( $step[ 'preconditions' ] ) && is_array( $step[ 'preconditions' ] ) ) { - foreach ( $step[ 'preconditions' ] as $precondition ) { - if ( ! isset( $precondition[ 'type' ] ) ) { - continue; - } - - if ( ! in_array( $precondition[ 'type' ], $allowed_precondition_types, true ) ) { - continue; - } - - $sanitized_precondition = [ - 'type' => $precondition[ 'type' ], - ]; - - if ( isset( $precondition[ 'params' ] ) && is_array( $precondition[ 'params' ] ) ) { - $sanitized_precondition[ 'params' ] = array_map( 'sanitize_text_field', $precondition[ 'params' ] ); - } - - $sanitized_step[ 'preconditions' ][] = $sanitized_precondition; - } - } - - // Process completion. - if ( isset( $step[ 'completion' ] ) && is_array( $step[ 'completion' ] ) ) { - $completion_type = $step[ 'completion' ][ 'type' ] ?? 'manual'; - - if ( in_array( $completion_type, $allowed_completion_types, true ) ) { - $sanitized_step[ 'completion' ] = [ - 'type' => $completion_type, - ]; - - if ( isset( $step[ 'completion' ][ 'params' ] ) && is_array( $step[ 'completion' ][ 'params' ] ) ) { - $sanitized_step[ 'completion' ][ 'params' ] = array_map( - 'sanitize_text_field', - $step[ 'completion' ][ 'params' ] - ); - } - } - } - - $tour[ 'steps' ][] = $sanitized_step; - } - - return $tour; - } -} diff --git a/php/Rest/AiController.php b/php/Rest/AiController.php index ee1e265..37d8efe 100644 --- a/php/Rest/AiController.php +++ b/php/Rest/AiController.php @@ -84,26 +84,25 @@ public static function generate_draft( \WP_REST_Request $request ) { public static function get_status() { $ai_manager = AiManager::get_instance(); + $connectors = $ai_manager->get_configured_connectors(); + $active_provider = $ai_manager->resolve_provider_id(); + $status = [ 'available' => $ai_manager->is_available(), - 'activeProvider' => null, + 'activeProvider' => '' !== $active_provider + ? [ + 'id' => $active_provider, + 'name' => $connectors[ $active_provider ] ?? $active_provider, + ] + : null, 'providers' => [], ]; - $active_provider = $ai_manager->get_active_provider(); - - if ( $active_provider ) { - $status[ 'activeProvider' ] = [ - 'id' => $active_provider->get_id(), - 'name' => $active_provider->get_name(), - ]; - } - - foreach ( $ai_manager->get_providers() as $provider ) { + foreach ( $connectors as $id => $label ) { $status[ 'providers' ][] = [ - 'id' => $provider->get_id(), - 'name' => $provider->get_name(), - 'configured' => $provider->is_configured(), + 'id' => $id, + 'name' => $label, + 'configured' => true, ]; } @@ -656,17 +655,7 @@ public static function generate_tour( \WP_REST_Request $request ) { ); // Generate the tour. - $provider = $ai_manager->get_active_provider(); - - if ( ! $provider ) { - return new \WP_Error( - 'no_provider', - __( 'No AI provider is available.', 'admin-coach-tours' ), - [ 'status' => 503 ] - ); - } - - $result = $provider->generate_tour( $system_prompt, $query ); + $result = $ai_manager->generate_tour( $system_prompt, $query ); if ( is_wp_error( $result ) ) { $status = 500; diff --git a/php/Security/Encryption.php b/php/Security/Encryption.php deleted file mode 100644 index a90d1c2..0000000 --- a/php/Security/Encryption.php +++ /dev/null @@ -1,133 +0,0 @@ -key_cache ) { - return $this->key_cache; - } - - // Derive key from wp_salt('auth'). - $salt = wp_salt( 'auth' ); - - // Use BLAKE2b to derive a 32-byte key. - $this->key_cache = sodium_crypto_generichash( $salt, '', SODIUM_CRYPTO_SECRETBOX_KEYBYTES ); - - return $this->key_cache; - } - - /** - * Encrypt a string. - * - * @param string $plaintext String to encrypt. - * @return string Base64-encoded encrypted string with nonce prefix. - */ - public function encrypt( string $plaintext ): string { - if ( empty( $plaintext ) ) { - return ''; - } - - $key = $this->get_key(); - $nonce = random_bytes( SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - - $ciphertext = sodium_crypto_secretbox( $plaintext, $nonce, $key ); - - // Prepend nonce to ciphertext. - $combined = $nonce . $ciphertext; - - // Base64 encode for storage. - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_encode - return base64_encode( $combined ); - } - - /** - * Decrypt a string. - * - * @param string $encrypted Base64-encoded encrypted string. - * @return string|null Decrypted string or null on failure. - */ - public function decrypt( string $encrypted ): ?string { - if ( empty( $encrypted ) ) { - return null; - } - - // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode - $combined = base64_decode( $encrypted, true ); - - if ( false === $combined ) { - return null; - } - - // Check minimum length. - $min_length = SODIUM_CRYPTO_SECRETBOX_NONCEBYTES + SODIUM_CRYPTO_SECRETBOX_MACBYTES; - - if ( strlen( $combined ) < $min_length ) { - return null; - } - - $key = $this->get_key(); - - // Extract nonce and ciphertext. - $nonce = substr( $combined, 0, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - $ciphertext = substr( $combined, SODIUM_CRYPTO_SECRETBOX_NONCEBYTES ); - - // Decrypt. - $plaintext = sodium_crypto_secretbox_open( $ciphertext, $nonce, $key ); - - if ( false === $plaintext ) { - return null; - } - - return $plaintext; - } - - /** - * Check if encryption is available. - * - * @return bool True if sodium is available. - */ - public static function is_available(): bool { - return extension_loaded( 'sodium' ) || function_exists( 'sodium_crypto_secretbox' ); - } - - /** - * Securely wipe sensitive data from memory. - * - * @param string $data Data to wipe. - */ - public function wipe( string &$data ): void { - if ( function_exists( 'sodium_memzero' ) ) { - sodium_memzero( $data ); - } else { - // Fallback: overwrite with zeros. - $data = str_repeat( "\0", strlen( $data ) ); - } - } -} diff --git a/php/Settings/SettingsPage.php b/php/Settings/SettingsPage.php index d7b452f..8a8454e 100644 --- a/php/Settings/SettingsPage.php +++ b/php/Settings/SettingsPage.php @@ -2,7 +2,8 @@ /** * Settings Page. * - * Provides admin settings page for plugin configuration. + * Provides admin settings page for plugin configuration. AI provider API keys + * are managed by the WordPress AI connector (wp_get_connectors), not here. * * @package AdminCoachTours * @since 0.1.0 @@ -13,7 +14,6 @@ namespace AdminCoachTours\Settings; use AdminCoachTours\AI\AiManager; -use AdminCoachTours\Security\Encryption; /** * Settings Page class. @@ -41,20 +41,6 @@ class SettingsPage { */ private static ?self $instance = null; - /** - * Tracks which option names are sensitive (need encryption). - * - * @var array - */ - private array $sensitive_options = []; - - /** - * Encryption helper. - * - * @var Encryption|null - */ - private ?Encryption $encryption = null; - /** * Get singleton instance. * @@ -70,9 +56,7 @@ public static function get_instance(): self { /** * Constructor. */ - private function __construct() { - $this->encryption = new Encryption(); - } + private function __construct() {} /** * Initialize settings. @@ -158,7 +142,7 @@ public function register_settings(): void { 'act_ai', [ 'name' => 'act_ai_enabled', - 'description' => __( 'Enable AI-powered step draft generation.', 'admin-coach-tours' ), + 'description' => __( 'Enable AI-powered tour and step draft generation.', 'admin-coach-tours' ), ] ); @@ -167,59 +151,36 @@ public function register_settings(): void { 'act_ai_provider', [ 'type' => 'string', - 'sanitize_callback' => 'sanitize_key', - 'default' => 'openai', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', ] ); add_settings_field( 'act_ai_provider', - __( 'AI Provider', 'admin-coach-tours' ), + __( 'Preferred Provider', 'admin-coach-tours' ), [ $this, 'render_provider_field' ], self::MENU_SLUG, 'act_ai' ); - // Provider-specific settings. - $ai_manager = AiManager::get_instance(); + register_setting( + self::OPTION_GROUP, + 'act_ai_model', + [ + 'type' => 'string', + 'sanitize_callback' => 'sanitize_text_field', + 'default' => '', + ] + ); - foreach ( $ai_manager->get_providers() as $provider ) { - $provider_id = $provider->get_id(); - $schema = $provider->get_settings_schema(); - - foreach ( $schema as $field_id => $field_config ) { - $option_name = "act_ai_{$provider_id}_{$field_id}"; - - // Track sensitive fields for encryption. - if ( ! empty( $field_config[ 'sensitive' ] ) ) { - $this->sensitive_options[ $option_name ] = true; - } - - register_setting( - self::OPTION_GROUP, - $option_name, - [ - 'type' => $field_config[ 'type' ] ?? 'string', - 'sanitize_callback' => [ $this, 'sanitize_provider_field' ], - 'default' => $field_config[ 'default' ] ?? '', - ] - ); - - add_settings_field( - $option_name, - $field_config[ 'label' ] ?? $field_id, - [ $this, 'render_provider_setting_field' ], - self::MENU_SLUG, - 'act_ai', - [ - 'provider' => $provider_id, - 'field_id' => $field_id, - 'field' => $field_config, - 'name' => $option_name, - ] - ); - } - } + add_settings_field( + 'act_ai_model', + __( 'Model Override', 'admin-coach-tours' ), + [ $this, 'render_model_field' ], + self::MENU_SLUG, + 'act_ai' + ); } /** @@ -228,7 +189,7 @@ public function register_settings(): void { * @param string $hook Current admin page. */ public function enqueue_scripts( string $hook ): void { - // Settings page is now under Tools menu. + // Settings page is under the Tools menu. if ( 'tools_page_' . self::MENU_SLUG !== $hook ) { return; } @@ -275,21 +236,31 @@ public function render_page(): void { } /** - * Render AI section description. + * Render AI section description and connector status. */ public function render_ai_section(): void { echo '

' . esc_html__( - 'Configure AI providers for generating step drafts and completion suggestions.', + 'AI features use the WordPress AI connector. Configure at least one AI provider connector in WordPress, then enable AI features below.', 'admin-coach-tours' ) . '

'; $ai_manager = AiManager::get_instance(); + $connectors = $ai_manager->get_configured_connectors(); - if ( ! $ai_manager->is_available() ) { + if ( empty( $connectors ) ) { echo '

'; - esc_html_e( 'No AI provider is currently configured. Add an API key below to enable AI features.', 'admin-coach-tours' ); + esc_html_e( 'No configured AI provider connector was detected. AI features are unavailable until a connector is set up.', 'admin-coach-tours' ); echo '

'; + return; } + + echo '

'; + printf( + /* translators: %s: comma-separated list of configured provider labels. */ + esc_html__( 'Configured AI providers: %s', 'admin-coach-tours' ), + esc_html( implode( ', ', $connectors ) ) + ); + echo '

'; } /** @@ -311,133 +282,43 @@ public function render_checkbox_field( array $args ): void { } /** - * Render provider field. + * Render the preferred-provider dropdown, populated from configured connectors. */ public function render_provider_field(): void { $ai_manager = AiManager::get_instance(); - $providers = $ai_manager->get_providers(); - $current = get_option( 'act_ai_provider', 'openai' ); + $connectors = $ai_manager->get_configured_connectors(); + $current = (string) get_option( 'act_ai_provider', '' ); ?>

- +

'; - - if ( 'select' === $type && ! empty( $options ) ) { - echo ''; - } else { - $input_type = 'password' === $type ? 'password' : 'text'; - - if ( $is_sensitive ) { - // Show a masked field with option to reveal/update. - printf( - '', - esc_attr( $input_type ), - esc_attr( $name ), - esc_attr( $name ), - ! empty( $value ) ? esc_attr__( 'Leave empty to keep current', 'admin-coach-tours' ) : '' - ); - - if ( ! empty( $value ) ) { - echo ''; - } - } else { - printf( - '', - esc_attr( $input_type ), - esc_attr( $name ), - esc_attr( $name ), - esc_attr( $display_value ) - ); - } - } - - if ( ! empty( $desc ) ) { - echo '

' . esc_html( $desc ) . '

'; - } - - echo ''; - } - - /** - * Sanitize provider field. - * - * @param mixed $value Field value. - * @return mixed Sanitized value. + * Render the optional model-override text field. */ - public function sanitize_provider_field( $value ) { - // Get the option name from the filter. - $filter_name = current_filter(); - $option_name = ''; + public function render_model_field(): void { + $current = (string) get_option( 'act_ai_model', '' ); - if ( preg_match( '/^sanitize_option_(.+)$/', $filter_name, $matches ) ) { - $option_name = $matches[ 1 ]; - } - - // Keep existing value if empty (for sensitive fields). - if ( '' === $value && ! empty( $option_name ) ) { - $existing = get_option( $option_name ); - if ( ! empty( $existing ) ) { - return $existing; - } - } - - // Sanitize. - $value = sanitize_text_field( $value ); - - // Encrypt sensitive fields. - if ( ! empty( $option_name ) && ! empty( $this->sensitive_options[ $option_name ] ) && ! empty( $value ) ) { - $value = $this->encryption->encrypt( $value ); - } - - return $value; + printf( + '', + esc_attr( $current ), + esc_attr__( 'Provider default', 'admin-coach-tours' ) + ); + echo '

'; + esc_html_e( 'Optional model ID to use (e.g., a specific model name). Leave empty to use the provider default.', 'admin-coach-tours' ); + echo '

'; } } diff --git a/readme.txt b/readme.txt index ab8f60b..c408763 100644 --- a/readme.txt +++ b/readme.txt @@ -1,7 +1,7 @@ === Admin Coach Tours === Contributors: PerS Tags: gutenberg, block editor, tutorial, guided tour, ai, learning -Requires at least: 6.8 +Requires at least: 7.0 Tested up to: 6.9 Requires PHP: 8.3 Stable tag: 0.4.1 @@ -48,26 +48,23 @@ Admin Coach Tours helps WordPress users learn the block editor through AI-genera = Requirements = -* WordPress 6.8 or later +* WordPress 7.0 or later * PHP 8.3 or later -* sodium extension (for API key encryption) -* AI provider API key (OpenAI, Azure OpenAI, or Anthropic) +* At least one WordPress AI provider connector configured == Installation == 1. Upload the `admin-coach-tours` folder to `/wp-content/plugins/` 2. Activate the plugin through the 'Plugins' menu in WordPress -3. Go to **Tools → Coach Tours** to configure your AI provider +3. Go to **Tools → Coach Tours** to enable AI features = Configure AI Provider = -1. Navigate to **Tools → Coach Tours** -2. Enable AI Features -3. Select your provider: - * **OpenAI** — Add your API key - * **Azure OpenAI** — Add endpoint URL, API key, and deployment name - * **Anthropic** — Add your API key -4. Save settings +1. Configure at least one WordPress AI provider connector +2. Navigate to **Tools → Coach Tours** +3. Enable AI Features +4. Optionally choose a preferred provider and model override +5. Save settings == Usage == diff --git a/tests/php/AiManagerTest.php b/tests/php/AiManagerTest.php new file mode 100644 index 0000000..4fa56e2 --- /dev/null +++ b/tests/php/AiManagerTest.php @@ -0,0 +1,253 @@ +alias( + function ( $name, $default = false ) { + if ( 'act_ai_enabled' === $name ) { + return true; + } + return $default; + } + ); + + // Connector reported as configured via the short-circuit filter. + Functions\when( 'apply_filters' )->alias( + function ( $hook, $value = null ) { + if ( 'admin_coach_tours_ai_connector_configured' === $hook ) { + return true; + } + return $value; + } + ); + + Functions\when( 'is_wp_error' )->alias( + static fn( $thing ) => $thing instanceof \WP_Error + ); + Functions\when( 'wp_json_encode' )->alias( + static fn( $data, $options = 0 ) => json_encode( $data, (int) $options ) + ); + Functions\when( 'sanitize_text_field' )->alias( + static fn( $str ) => is_string( $str ) ? trim( strip_tags( $str ) ) : $str + ); + Functions\when( 'sanitize_key' )->alias( + static fn( $str ) => preg_replace( '/[^a-z0-9_\-]/', '', strtolower( (string) $str ) ) + ); + Functions\when( 'wp_kses_post' )->returnArg(); + } + + /** + * Tear down test. + */ + protected function tearDown(): void { + Monkey\tearDown(); + parent::tearDown(); + } + + /** + * Register a fake wp_ai_client_prompt returning the given text. + * + * @param string $response The canned AI response text. + */ + private function mock_ai_response( string $response ): void { + Functions\when( 'wp_ai_client_prompt' )->alias( + static function ( $prompt ) use ( $response ) { + return new class( $response ) { + /** + * Canned response. + * + * @var string + */ + private string $response; + + public function __construct( string $response ) { + $this->response = $response; + } + + public function using_system_instruction( $value ): self { + return $this; + } + + public function using_temperature( $value ): self { + return $this; + } + + public function using_max_tokens( $value ): self { + return $this; + } + + public function using_provider( $value ): self { + return $this; + } + + public function using_model( $value ): self { + return $this; + } + + public function generate_text() { + return $this->response; + } + }; + } + ); + } + + /** + * Test a step draft is parsed from a plain JSON response. + */ + public function test_generate_step_draft_parses_json(): void { + $this->mock_ai_response( + '{"title":"Click Publish","content":"

Publishes your post.

","suggestedCompletion":{"type":"clickTarget","params":{}}}' + ); + + $result = AiManager::get_instance()->generate_step_draft( [ 'tagName' => 'button' ] ); + + $this->assertIsArray( $result ); + $this->assertSame( 'Click Publish', $result[ 'title' ] ); + $this->assertSame( 'clickTarget', $result[ 'suggestedCompletion' ][ 'type' ] ); + } + + /** + * Test JSON wrapped in Markdown code fences is still parsed. + */ + public function test_generate_step_draft_strips_code_fences(): void { + $this->mock_ai_response( + "```json\n{\"title\":\"Add a heading\",\"content\":\"Insert a heading block.\"}\n```" + ); + + $result = AiManager::get_instance()->generate_step_draft( [ 'tagName' => 'button' ] ); + + $this->assertIsArray( $result ); + $this->assertSame( 'Add a heading', $result[ 'title' ] ); + } + + /** + * Test an unparseable response yields a parse_error WP_Error. + */ + public function test_generate_step_draft_returns_parse_error(): void { + $this->mock_ai_response( 'Sorry, I cannot do that.' ); + + $result = AiManager::get_instance()->generate_step_draft( [ 'tagName' => 'button' ] ); + + $this->assertInstanceOf( \WP_Error::class, $result ); + $this->assertSame( 'parse_error', $result->get_error_code() ); + } + + /** + * Test a full tour response is validated and sanitized. + */ + public function test_generate_tour_validates_structure(): void { + $tour = wp_json_encode( + [ + 'title' => 'Insert an Image', + 'steps' => [ + [ + 'id' => 'open-inserter', + 'order' => 0, + 'title' => 'Open the inserter', + 'content' => '

Click the plus button.

', + 'target' => [ + 'locators' => [ + [ + 'type' => 'css', + 'value' => '.editor-document-tools__inserter-toggle', + 'weight' => 80, + ], + [ + 'type' => 'invalidType', + 'value' => 'should be dropped', + ], + ], + ], + 'completion' => [ 'type' => 'clickTarget' ], + ], + ], + ] + ); + + $this->mock_ai_response( $tour ); + + $result = AiManager::get_instance()->generate_tour( 'system prompt' ); + + $this->assertIsArray( $result ); + $this->assertSame( 'Insert an Image', $result[ 'title' ] ); + $this->assertCount( 1, $result[ 'steps' ] ); + // Invalid locator type is filtered out, leaving one valid locator. + $this->assertCount( 1, $result[ 'steps' ][ 0 ][ 'target' ][ 'locators' ] ); + $this->assertSame( 'clickTarget', $result[ 'steps' ][ 0 ][ 'completion' ][ 'type' ] ); + } + + /** + * Test a scope error response yields an out_of_scope WP_Error. + */ + public function test_generate_tour_out_of_scope(): void { + $this->mock_ai_response( '{"error":"scope","message":"Outside the editor."}' ); + + $result = AiManager::get_instance()->generate_tour( 'system prompt', 'What is the weather?' ); + + $this->assertInstanceOf( \WP_Error::class, $result ); + $this->assertSame( 'out_of_scope', $result->get_error_code() ); + } + + /** + * Test generation is refused when AI features are disabled. + */ + public function test_generate_step_draft_refused_when_disabled(): void { + Functions\when( 'get_option' )->alias( + static fn( $name, $default = false ) => 'act_ai_enabled' === $name ? false : $default + ); + + $result = AiManager::get_instance()->generate_step_draft( [ 'tagName' => 'button' ] ); + + $this->assertInstanceOf( \WP_Error::class, $result ); + $this->assertSame( 'ai_not_available', $result->get_error_code() ); + } + + /** + * Test the completion heuristics without any AI call. + */ + public function test_suggest_completion_heuristics(): void { + $manager = AiManager::get_instance(); + + $this->assertSame( + 'clickTarget', + $manager->suggest_completion( [ 'tagName' => 'button' ] )[ 'type' ] + ); + $this->assertSame( + 'domValueChanged', + $manager->suggest_completion( [ 'tagName' => 'input' ] )[ 'type' ] + ); + $this->assertSame( + 'manual', + $manager->suggest_completion( [ 'tagName' => 'div' ] )[ 'type' ] + ); + } +} diff --git a/uninstall.php b/uninstall.php index f55225e..9c39e62 100644 --- a/uninstall.php +++ b/uninstall.php @@ -19,11 +19,22 @@ */ function act_delete_options(): void { $options = [ + 'act_version', + 'act_enable_pupil_mode', 'act_ai_enabled', 'act_ai_provider', + 'act_ai_model', + // Legacy options (pre-connector, encrypted keys and per-provider config). + 'act_ai_openai_api_key', + 'act_ai_openai_model', + 'act_ai_azure_api_key', + 'act_ai_azure_endpoint', + 'act_ai_azure_deployment', + 'act_ai_azure_model', + 'act_ai_anthropic_api_key', + 'act_ai_anthropic_model', 'act_ai_api_key', 'act_ai_endpoint', - 'act_ai_model', 'act_allow_post_content', 'act_encryption_key', ]; From 7af482a3e324ebc75d020008ac6f56c72c9c634b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Per=20S=C3=B8derlind?= Date: Fri, 24 Jul 2026 01:31:28 +0200 Subject: [PATCH 02/11] Refactor AI tour flow into deep modules MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Turn the shallow AiController into a thin HTTP adapter by concentrating the tour-generation implementation behind small interfaces (architecture review candidates A–E). - TourGenerator: deep module `generate(TourRequest): array|WP_Error` owning cache, RAG retrieval, prompt assembly, AI call, and validation. AiManager is injected (no singleton), so the pipeline is testable off the REST stack. - TourRequest: immutable value object; `from_rest()` owns the full input surface (editorContext/failureContext sanitization moved out of the controller). Routes.php now declares those args — the route contract is complete. - TaskPrompts owns prompt formatting: `get_system_prompt()` takes structured editor/failure context arrays and formats them internally (the format_* helpers moved out of the controller). - TourSchema: single source of truth for allowed locator/precondition/ completion types, shared by TaskPrompts::get_tour_schema (enums) and AiManager::validate_and_sanitize_tour — no more drift. - AiController: thin adapter (parse -> generate -> map_error). - JS store: fetchAiTasks routes /ai/tasks through the store control seam (PupilLauncher stops calling apiFetch directly); locale is threaded through requestAiTour so controls.js no longer reads window.adminCoachTours. Tests: add TourRequestTest and TourGeneratorTest; slim AiControllerTest; move format/sanitize tests to their new homes. PHP 59 + JS 86 green. Assisted-by: GitHub Copilot:Claude Opus 4.8 --- assets/js/pupil/PupilLauncher.jsx | 5 +- assets/js/store/actions.js | 19 +- assets/js/store/controls.js | 2 +- build/educator/index.asset.php | 2 +- build/educator/index.js | 2 +- build/pupil/index.asset.php | 2 +- build/pupil/index.js | 4 +- build/settings.asset.php | 2 +- php/AI/AiManager.php | 35 +- php/AI/TaskPrompts.php | 185 +++++++++- php/AI/TourGenerator.php | 191 ++++++++++ php/AI/TourRequest.php | 244 ++++++++++++ php/AI/TourSchema.php | 73 ++++ php/Rest/AiController.php | 595 +++++------------------------- php/Rest/Routes.php | 9 + tests/php/AiControllerTest.php | 339 +---------------- tests/php/TaskPromptsTest.php | 68 +++- tests/php/TourGeneratorTest.php | 162 ++++++++ tests/php/TourRequestTest.php | 180 +++++++++ 19 files changed, 1218 insertions(+), 901 deletions(-) create mode 100644 php/AI/TourGenerator.php create mode 100644 php/AI/TourRequest.php create mode 100644 php/AI/TourSchema.php create mode 100644 tests/php/TourGeneratorTest.php create mode 100644 tests/php/TourRequestTest.php diff --git a/assets/js/pupil/PupilLauncher.jsx b/assets/js/pupil/PupilLauncher.jsx index a4ceb48..2b98d30 100644 --- a/assets/js/pupil/PupilLauncher.jsx +++ b/assets/js/pupil/PupilLauncher.jsx @@ -11,7 +11,6 @@ import { useState, useEffect, useCallback, useRef, createPortal } from '@wordpress/element'; import { useSelect, useDispatch } from '@wordpress/data'; import { __ } from '@wordpress/i18n'; -import apiFetch from '@wordpress/api-fetch'; import { Dashicon } from '@wordpress/components'; const STORE_NAME = 'admin-coach-tours'; @@ -83,7 +82,7 @@ export default function PupilLauncher() { }, [ aiTourError, isPlaying, isOpen ] ); // Get dispatch actions. - const { requestAiTour, clearEphemeralTour, setAiTourError, setLastFailureContext } = useDispatch( STORE_NAME ); + const { requestAiTour, clearEphemeralTour, setAiTourError, setLastFailureContext, fetchAiTasks } = useDispatch( STORE_NAME ); /** * Fetch available tasks when launcher opens. @@ -93,7 +92,7 @@ export default function PupilLauncher() { setIsTasksLoading( true ); setTasksError( null ); - apiFetch( { path: '/admin-coach-tours/v1/ai/tasks' } ) + fetchAiTasks() .then( ( response ) => { if ( response.available && response.tasks ) { setTasks( response.tasks ); diff --git a/assets/js/store/actions.js b/assets/js/store/actions.js index 8c7a02c..7898edc 100644 --- a/assets/js/store/actions.js +++ b/assets/js/store/actions.js @@ -781,6 +781,9 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) yield setAiTourLoading( true ); yield setAiTourError( null ); + // Resolve the response language once, at the composition root. + const locale = window.adminCoachTours?.locale || ''; + try { // Gather editor context to help AI generate accurate selectors. const editorContext = yield { @@ -794,6 +797,7 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) postType, editorContext, failureContext, + locale, }; console.log( '[ACT AI Response] Full result:', result ); @@ -824,6 +828,20 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) } } +/** + * Fetch the available AI tasks through the store. + * + * Keeps all AI REST access behind the store's control seam so components + * don't call apiFetch directly or duplicate the endpoint path. + * + * @return {Generator} Generator resolving to the tasks response. + */ +export function* fetchAiTasks() { + return yield { + type: 'FETCH_AI_TASKS', + }; +} + /** * Start an ephemeral tour directly (for pre-loaded tours). * @@ -831,7 +849,6 @@ export function* requestAiTour( taskId, query, postType, failureContext = null ) * @return {Generator} Generator that sets up and starts the tour. */ export function* startEphemeralTour( tour ) { - // Add ID if not present. const tourWithId = { id: 'ephemeral', ...tour, diff --git a/assets/js/store/controls.js b/assets/js/store/controls.js index 1384134..a5dafe5 100644 --- a/assets/js/store/controls.js +++ b/assets/js/store/controls.js @@ -156,7 +156,7 @@ const controls = { postType: action.postType, editorContext: action.editorContext || null, failureContext: action.failureContext || null, - locale: window.adminCoachTours?.locale || '', + locale: action.locale || '', }, } ); }, diff --git a/build/educator/index.asset.php b/build/educator/index.asset.php index d68c6a4..1ac3a65 100644 --- a/build/educator/index.asset.php +++ b/build/educator/index.asset.php @@ -1 +1 @@ - array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives'), 'version' => 'fdeb44c02a995dfbf518'); + array('react', 'react-dom', 'react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-editor', 'wp-element', 'wp-i18n', 'wp-plugins', 'wp-primitives'), 'version' => '976f5831ec40e75f9dc1'); diff --git a/build/educator/index.js b/build/educator/index.js index 952b21d..3ab75a4 100644 --- a/build/educator/index.js +++ b/build/educator/index.js @@ -1 +1 @@ -(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={exports:{}};return n[e](i,i.exports,o),i.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);o.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,o.d(i,a),i},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};o.r(i),o.d(i,{activatePicker:()=>F,addStep:()=>W,clearAiDraft:()=>Q,clearEphemeralTour:()=>ae,clearResolvedTarget:()=>B,createTour:()=>T,deactivatePicker:()=>q,deleteStep:()=>K,endTour:()=>C,fetchTour:()=>E,fetchTours:()=>b,incrementResolutionAttempts:()=>j,markStepComplete:()=>N,nextStep:()=>R,previousStep:()=>A,receiveEphemeralTour:()=>ie,receiveTour:()=>m,receiveTours:()=>h,reorderSteps:()=>X,repeatStep:()=>k,requestAiDraft:()=>ee,requestAiTour:()=>se,resetCompletion:()=>L,saveTour:()=>y,selectStep:()=>$,setAiDraftError:()=>Z,setAiDraftLoading:()=>Y,setAiDraftResult:()=>J,setAiTourError:()=>re,setAiTourLoading:()=>ne,setCompletionSatisfied:()=>O,setCurrentStep:()=>_,setCurrentTour:()=>v,setLastError:()=>U,setLastFailureContext:()=>oe,setMode:()=>D,setPendingChanges:()=>H,setRecovering:()=>M,setResolvedTarget:()=>P,setSidebarOpen:()=>te,setToursError:()=>g,setToursLoading:()=>f,skipStep:()=>I,startEphemeralTour:()=>le,startPicking:()=>V,startTour:()=>x,stopPicking:()=>G,stopTour:()=>w,updateStep:()=>z,updateTour:()=>S});var a={};o.r(a),o.d(a,{getAiDraft:()=>$e,getAiDraftError:()=>qe,getAiDraftResult:()=>Ge,getAiTourError:()=>We,getCurrentStep:()=>Ee,getCurrentStepIndex:()=>be,getCurrentTour:()=>ve,getCurrentTourId:()=>me,getEphemeralTour:()=>Xe,getLastError:()=>Ne,getLastFailureContext:()=>Ke,getMode:()=>Ce,getPickingStepId:()=>Be,getProgress:()=>xe,getResolutionAttempts:()=>Le,getResolvedTarget:()=>De,getSelectedStep:()=>je,getSelectedStepId:()=>Me,getSkippedSteps:()=>Ie,getTotalSteps:()=>ye,getTour:()=>de,getTours:()=>ue,getToursByEditor:()=>he,getToursById:()=>ce,getToursByPostType:()=>ge,getToursError:()=>fe,hasNextStep:()=>Te,hasPendingChanges:()=>Ue,hasPreviousStep:()=>Se,isAiDraftLoading:()=>Fe,isAiDrafting:()=>Ve,isAiTourLoading:()=>ze,isCompletionSatisfied:()=>Ae,isEducatorMode:()=>we,isEphemeralTourActive:()=>Ye,isPickerActive:()=>Pe,isPupilMode:()=>_e,isRecovering:()=>Oe,isSidebarOpen:()=>He,isTourActive:()=>Re,isToursLoading:()=>pe,wasStepSkipped:()=>ke});var s={};o.r(s),o.d(s,{getTour:()=>et,getTours:()=>Qe,getToursByPostType:()=>tt});const l=window.wp.plugins,c=window.wp.data,u={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},d={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},p=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function f(e){return{type:d.SET_TOURS_LOADING,isLoading:e}}function g(e){return{type:d.SET_TOURS_ERROR,error:e}}function h(e){return{type:d.RECEIVE_TOURS,tours:e}}function m(e){return{type:d.RECEIVE_TOUR,tour:e}}function*v(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield m(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield m({id:e,title:"",steps:[],status:"draft"})}yield{type:d.SET_CURRENT_TOUR,tourId:e}}else yield{type:d.SET_CURRENT_TOUR,tourId:null}}function b(e={}){return{type:"FETCH_TOURS",args:e}}function E(e){return{type:"FETCH_TOUR",tourId:e}}function*y(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function*T(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield m(t)),t}catch(e){throw e}}function*S(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function x(e,t="pupil"){return{type:d.START_TOUR,tourId:e,mode:t}}function C(){return{type:d.END_TOUR}}function w(){return C()}function _(e){return{type:d.SET_CURRENT_STEP,stepIndex:e}}function R(){return{type:d.NEXT_STEP}}function A(){return{type:d.PREVIOUS_STEP}}function I(){return{type:d.SKIP_STEP}}function k(){return{type:d.REPEAT_STEP}}function D(e){return{type:d.SET_MODE,mode:e}}function O(e){return{type:d.SET_COMPLETION_SATISFIED,satisfied:e}}function L(){return{type:d.RESET_COMPLETION}}function N(){return R()}function P(e){return{type:d.SET_RESOLVED_TARGET,target:e}}function B(){return{type:d.CLEAR_RESOLVED_TARGET}}function M(e){return{type:d.SET_RECOVERING,isRecovering:e}}function j(){return{type:d.INCREMENT_RESOLUTION_ATTEMPTS}}function U(e){return{type:d.SET_LAST_ERROR,error:e}}function F(e=null){return{type:d.ACTIVATE_PICKER,stepId:e}}function V(e=null){return F(e)}function q(){return{type:d.DEACTIVATE_PICKER}}function G(){return q()}function $(e){return{type:d.SELECT_STEP,stepId:e}}function H(e){return{type:d.SET_PENDING_CHANGES,pending:e}}function*z(e,t,n){yield{type:d.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*W(e,t={},n=null){const r={id:p(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:d.ADD_STEP,tourId:e,step:r,index:n}}function*K(e,t){yield{type:d.DELETE_STEP,tourId:e,stepId:t}}function*X(e,t){yield{type:d.REORDER_STEPS,tourId:e,stepIds:t}}function Y(e){return{type:d.SET_AI_DRAFT_LOADING,isLoading:e}}function Z(e){return{type:d.SET_AI_DRAFT_ERROR,error:e}}function J(e){return{type:d.SET_AI_DRAFT_RESULT,result:e}}function Q(){return{type:d.CLEAR_AI_DRAFT}}function*ee(e,t){yield Y(!0),yield Z(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield J(n),n}catch(e){throw yield Z(e.message||"Failed to generate AI draft"),e}finally{yield Y(!1)}}function te(e){return{type:d.SET_SIDEBAR_OPEN,isOpen:e}}function ne(e){return{type:d.SET_AI_TOUR_LOADING,isLoading:e}}function re(e){return{type:d.SET_AI_TOUR_ERROR,error:e}}function oe(e){return{type:d.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function ie(e){return{type:d.RECEIVE_EPHEMERAL_TOUR,tour:e}}function ae(){return{type:d.CLEAR_EPHEMERAL_TOUR}}function*se(e,t,n,r=null){yield ne(!0),yield re(null);try{const o=yield{type:"GATHER_EDITOR_CONTEXT"},i=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:o,failureContext:r};console.log("[ACT AI Response] Full result:",i),console.log("[ACT AI Response] Tour:",JSON.stringify(i.tour,null,2));const a={id:"ephemeral",...i.tour};return yield ie(a),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil"),a}catch(e){throw yield re(e.message||"Failed to generate tour"),e}finally{yield ne(!1)}}function*le(e){const t={id:"ephemeral",...e};yield ie(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil")}function ce(e){return e.tours}const ue=(0,c.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function de(e,t){return e.tours[t]||null}function pe(e){return e.toursLoading}function fe(e){return e.toursError}const ge=(0,c.createSelector)((e,t)=>ue(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),he=(0,c.createSelector)((e,t)=>ue(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function me(e){return e.currentTourId}function ve(e){return e.currentTourId?e.tours[e.currentTourId]:null}function be(e){return e.currentStepIndex}const Ee=(0,c.createSelector)(e=>{const t=ve(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function ye(e){const t=ve(e);return t?.steps?.length||0}function Te(e){return e.currentStepIndex0}function xe(e){const t=ye(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function Ce(e){return e.mode}function we(e){return"educator"===e.mode}function _e(e){return"pupil"===e.mode}function Re(e){return null!==e.currentTourId&&null!==e.mode}function Ae(e){return e.completionSatisfied}function Ie(e){return e.skippedSteps}function ke(e,t){return e.skippedSteps.includes(t)}function De(e){return e.resolvedTarget}function Oe(e){return e.isRecovering}function Le(e){return e.resolutionAttempts}function Ne(e){return e.lastError}function Pe(e){return e.isPickerActive}function Be(e){return e.pickingStepId||null}function Me(e){return e.selectedStepId}const je=(0,c.createSelector)(e=>{const t=ve(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function Ue(e){return e.pendingChanges}function Fe(e){return e.aiDraftLoading}function Ve(e){return Fe(e)}function qe(e){return e.aiDraftError}function Ge(e){return e.aiDraftResult}function $e(e){return Ge(e)}function He(e){return e.sidebarOpen}function ze(e){return e.aiTourLoading}function We(e){return e.aiTourError}function Ke(e){return e.lastFailureContext}function Xe(e){return e.ephemeralTour}function Ye(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Ze=window.wp.apiFetch;var Je=o.n(Ze);function*Qe(){yield f(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield h(e)}catch(e){yield g(e.message||"Failed to fetch tours")}}function*et(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield m(t)}catch(e){yield g(e.message||"Failed to fetch tour")}}function*tt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield h(t)}catch(e){yield g(e.message||"Failed to fetch tours")}}function nt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,c.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,c.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,c.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function rt(){try{const e=(0,c.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:ot(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function ot(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function it(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:at(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:at(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:at(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:at(o)});const i=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of i){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function at(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function st(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function lt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let i=null;for(const e of o)if(i=r.querySelector(e),i)break;if(i||(i=r),i.scrollIntoView({behavior:"smooth",block:"center"}),i.focus(),"true"===i.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(i),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",i.tagName,i.className),!0}const ct={API_FETCH:e=>Je()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:rt(),visibleElements:nt(),uiSamples:it(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,c.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&st(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,c.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&st(e.attributes?.content));return t&&(await(0,c.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await lt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,c.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,c.select)("core/block-editor"),i=(r?.getBlocks()||[]).length;return await t.insertBlock(n,i,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await lt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Je()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Je()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Je()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Je()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Je()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:window.adminCoachTours?.locale||""}}),FETCH_AI_TASKS:()=>Je()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},ut="admin-coach-tours",dt=(0,c.createReduxStore)(ut,{reducer:function(e=u,t){switch(t.type){case d.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case d.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case d.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case d.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case d.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case d.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case d.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case d.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case d.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case d.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case d.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case d.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case d.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case d.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case d.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case d.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case d.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case d.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case d.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case d.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case d.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case d.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:i,selectors:a,resolvers:s,controls:ct,initialState:u});(0,c.select)(ut)||(0,c.register)(dt);const pt=window.wp.editor,ft=window.wp.element,gt=window.wp.i18n,ht=window.wp.components,mt=window.wp.primitives,vt=window.ReactJSXRuntime;var bt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M12 4a8 8 0 1 1 .001 16.001A8 8 0 0 1 12 4Zm0 1.5a6.5 6.5 0 1 0-.001 13.001A6.5 6.5 0 0 0 12 5.5Zm.75 11h-1.5V15h1.5v1.5Zm-.445-9.234a3 3 0 0 1 .445 5.89V14h-1.5v-1.25c0-.57.452-.958.917-1.01A1.5 1.5 0 0 0 12 8.75a1.5 1.5 0 0 0-1.5 1.5H9a3 3 0 0 1 3.305-2.984Z"})}),Et=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M16.5 7.5 10 13.9l-2.5-2.4-1 1 3.5 3.6 7.5-7.6z"})}),yt=(0,vt.jsx)(mt.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,vt.jsx)(mt.Path,{d:"M3.99961 13C4.67043 13.3354 4.6703 13.3357 4.67017 13.3359L4.67298 13.3305C4.67621 13.3242 4.68184 13.3135 4.68988 13.2985C4.70595 13.2686 4.7316 13.2218 4.76695 13.1608C4.8377 13.0385 4.94692 12.8592 5.09541 12.6419C5.39312 12.2062 5.84436 11.624 6.45435 11.0431C7.67308 9.88241 9.49719 8.75 11.9996 8.75C14.502 8.75 16.3261 9.88241 17.5449 11.0431C18.1549 11.624 18.6061 12.2062 18.9038 12.6419C19.0523 12.8592 19.1615 13.0385 19.2323 13.1608C19.2676 13.2218 19.2933 13.2686 19.3093 13.2985C19.3174 13.3135 19.323 13.3242 19.3262 13.3305L19.3291 13.3359C19.3289 13.3357 19.3288 13.3354 19.9996 13C20.6704 12.6646 20.6703 12.6643 20.6701 12.664L20.6697 12.6632L20.6688 12.6614L20.6662 12.6563L20.6583 12.6408C20.6517 12.6282 20.6427 12.6108 20.631 12.5892C20.6078 12.5459 20.5744 12.4852 20.5306 12.4096C20.4432 12.2584 20.3141 12.0471 20.1423 11.7956C19.7994 11.2938 19.2819 10.626 18.5794 9.9569C17.1731 8.61759 14.9972 7.25 11.9996 7.25C9.00203 7.25 6.82614 8.61759 5.41987 9.9569C4.71736 10.626 4.19984 11.2938 3.85694 11.7956C3.68511 12.0471 3.55605 12.2584 3.4686 12.4096C3.42484 12.4852 3.39142 12.5459 3.36818 12.5892C3.35656 12.6108 3.34748 12.6282 3.34092 12.6408L3.33297 12.6563L3.33041 12.6614L3.32948 12.6632L3.32911 12.664C3.32894 12.6643 3.32879 12.6646 3.99961 13ZM11.9996 16C13.9326 16 15.4996 14.433 15.4996 12.5C15.4996 10.567 13.9326 9 11.9996 9C10.0666 9 8.49961 10.567 8.49961 12.5C8.49961 14.433 10.0666 16 11.9996 16Z"})}),Tt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"})}),St=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"})}),xt=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"})});const Ct=window.React;var wt=o.n(Ct);const _t=window.ReactDOM,Rt="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function At(e){const t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function It(e){return"nodeType"in e}function kt(e){var t,n;return e?At(e)?e:It(e)&&null!=(t=null==(n=e.ownerDocument)?void 0:n.defaultView)?t:window:window}function Dt(e){const{Document:t}=kt(e);return e instanceof t}function Ot(e){return!At(e)&&e instanceof kt(e).HTMLElement}function Lt(e){return e instanceof kt(e).SVGElement}function Nt(e){return e?At(e)?e.document:It(e)?Dt(e)?e:Ot(e)||Lt(e)?e.ownerDocument:document:document:document}const Pt=Rt?Ct.useLayoutEffect:Ct.useEffect;function Bt(e){const t=(0,Ct.useRef)(e);return Pt(()=>{t.current=e}),(0,Ct.useCallback)(function(){for(var e=arguments.length,n=new Array(e),r=0;r{n.current!==e&&(n.current=e)},t),n}function jt(e,t){const n=(0,Ct.useRef)();return(0,Ct.useMemo)(()=>{const t=e(n.current);return n.current=t,t},[...t])}function Ut(e){const t=Bt(e),n=(0,Ct.useRef)(null),r=(0,Ct.useCallback)(e=>{e!==n.current&&(null==t||t(e,n.current)),n.current=e},[]);return[n,r]}function Ft(e){const t=(0,Ct.useRef)();return(0,Ct.useEffect)(()=>{t.current=e},[e]),t.current}let Vt={};function qt(e,t){return(0,Ct.useMemo)(()=>{if(t)return t;const n=null==Vt[e]?0:Vt[e]+1;return Vt[e]=n,e+"-"+n},[e,t])}function Gt(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const r=Object.entries(n);for(const[n,o]of r){const r=t[n];null!=r&&(t[n]=r+e*o)}return t},{...t})}}const $t=Gt(1),Ht=Gt(-1);function zt(e){if(!e)return!1;const{KeyboardEvent:t}=kt(e.target);return t&&e instanceof t}function Wt(e){if(function(e){if(!e)return!1;const{TouchEvent:t}=kt(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return function(e){return"clientX"in e&&"clientY"in e}(e)?{x:e.clientX,y:e.clientY}:null}const Kt=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Kt.Translate.toString(e),Kt.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Xt="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Yt(e){return e.matches(Xt)?e:e.querySelector(Xt)}const Zt={display:"none"};function Jt(e){let{id:t,value:n}=e;return wt().createElement("div",{id:t,style:Zt},n)}function Qt(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;return wt().createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":r,"aria-atomic":!0},n)}const en=(0,Ct.createContext)(null),tn={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},nn={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function rn(e){let{announcements:t=nn,container:n,hiddenTextDescribedById:r,screenReaderInstructions:o=tn}=e;const{announce:i,announcement:a}=function(){const[e,t]=(0,Ct.useState)("");return{announce:(0,Ct.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),s=qt("DndLiveRegion"),[l,c]=(0,Ct.useState)(!1);if((0,Ct.useEffect)(()=>{c(!0)},[]),function(e){const t=(0,Ct.useContext)(en);(0,Ct.useEffect)(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}((0,Ct.useMemo)(()=>({onDragStart(e){let{active:n}=e;i(t.onDragStart({active:n}))},onDragMove(e){let{active:n,over:r}=e;t.onDragMove&&i(t.onDragMove({active:n,over:r}))},onDragOver(e){let{active:n,over:r}=e;i(t.onDragOver({active:n,over:r}))},onDragEnd(e){let{active:n,over:r}=e;i(t.onDragEnd({active:n,over:r}))},onDragCancel(e){let{active:n,over:r}=e;i(t.onDragCancel({active:n,over:r}))}}),[i,t])),!l)return null;const u=wt().createElement(wt().Fragment,null,wt().createElement(Jt,{id:r,value:o.draggable}),wt().createElement(Qt,{id:s,announcement:a}));return n?(0,_t.createPortal)(u,n):u}var on;function an(){}function sn(e,t){return(0,Ct.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(on||(on={}));const ln=Object.freeze({x:0,y:0});function cn(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function un(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function dn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function pn(e){let{left:t,top:n,height:r,width:o}=e;return[{x:t,y:n},{x:t+o,y:n},{x:t,y:n+r},{x:t+o,y:n+r}]}function fn(e,t){if(!e||0===e.length)return null;const[n]=e;return t?n[t]:n}function gn(e,t,n){return void 0===t&&(t=e.left),void 0===n&&(n=e.top),{x:t+.5*e.width,y:n+.5*e.height}}const hn=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=gn(t,t.left,t.top),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=cn(gn(r),o);i.push({id:t,data:{droppableContainer:e,value:n}})}}return i.sort(un)};function mn(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),i=Math.min(t.top+t.height,e.top+e.height),a=o-r,s=i-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=[];for(const e of r){const{id:r}=e,i=n.get(r);if(i){const n=mn(i,t);n>0&&o.push({id:r,data:{droppableContainer:e,value:n}})}}return o.sort(dn)};function bn(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:ln}function En(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}const yn=En(1);const Tn={ignoreTransform:!1};function Sn(e,t){void 0===t&&(t=Tn);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:t,transformOrigin:r}=kt(e).getComputedStyle(e);t&&(n=function(e,t,n){const r=function(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!r)return e;const{scaleX:o,scaleY:i,x:a,y:s}=r,l=e.left-a-(1-o)*parseFloat(n),c=e.top-s-(1-i)*parseFloat(n.slice(n.indexOf(" ")+1)),u=o?e.width/o:e.width,d=i?e.height/i:e.height;return{width:u,height:d,top:c,right:l+u,bottom:c+d,left:l}}(n,t,r))}const{top:r,left:o,width:i,height:a,bottom:s,right:l}=n;return{top:r,left:o,width:i,height:a,bottom:s,right:l}}function xn(e){return Sn(e,{ignoreTransform:!0})}function Cn(e,t){const n=[];return e?function r(o){if(null!=t&&n.length>=t)return n;if(!o)return n;if(Dt(o)&&null!=o.scrollingElement&&!n.includes(o.scrollingElement))return n.push(o.scrollingElement),n;if(!Ot(o)||Lt(o))return n;if(n.includes(o))return n;const i=kt(e).getComputedStyle(o);return o!==e&&function(e,t){void 0===t&&(t=kt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{const r=t[e];return"string"==typeof r&&n.test(r)})}(o,i)&&n.push(o),function(e,t){return void 0===t&&(t=kt(e).getComputedStyle(e)),"fixed"===t.position}(o,i)?n:r(o.parentNode)}(e):n}function wn(e){const[t]=Cn(e,1);return null!=t?t:null}function Rn(e){return Rt&&e?At(e)?e:It(e)?Dt(e)||e===Nt(e).scrollingElement?window:Ot(e)?e:null:null:null}function An(e){return At(e)?e.scrollX:e.scrollLeft}function In(e){return At(e)?e.scrollY:e.scrollTop}function kn(e){return{x:An(e),y:In(e)}}var Dn;function On(e){return!(!Rt||!e)&&e===document.scrollingElement}function Ln(e){const t={x:0,y:0},n=On(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(Dn||(Dn={}));const Nn={x:.2,y:.2};function Pn(e,t,n,r,o){let{top:i,left:a,right:s,bottom:l}=n;void 0===r&&(r=10),void 0===o&&(o=Nn);const{isTop:c,isBottom:u,isLeft:d,isRight:p}=Ln(e),f={x:0,y:0},g={x:0,y:0},h=t.height*o.y,m=t.width*o.x;return!c&&i<=t.top+h?(f.y=Dn.Backward,g.y=r*Math.abs((t.top+h-i)/h)):!u&&l>=t.bottom-h&&(f.y=Dn.Forward,g.y=r*Math.abs((t.bottom-h-l)/h)),!p&&s>=t.right-m?(f.x=Dn.Forward,g.x=r*Math.abs((t.right-m-s)/m)):!d&&a<=t.left+m&&(f.x=Dn.Backward,g.x=r*Math.abs((t.left+m-a)/m)),{direction:f,speed:g}}function Bn(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:o}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:o,width:e.clientWidth,height:e.clientHeight}}function Mn(e){return e.reduce((e,t)=>$t(e,kn(t)),ln)}const jn=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+An(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+In(t),0)}]];class Un{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=Cn(t),r=Mn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(const[e,t,o]of jn)for(const i of t)Object.defineProperty(this,i,{get:()=>{const t=o(n),a=r[e]-t;return this.rect[i]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Fn{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,n){var r;null==(r=this.target)||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}}function Vn(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var qn,Gn;function $n(e){e.preventDefault()}function Hn(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(qn||(qn={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(Gn||(Gn={}));const zn={start:[Gn.Space,Gn.Enter],cancel:[Gn.Esc],end:[Gn.Space,Gn.Enter,Gn.Tab]},Wn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Gn.Right:return{...n,x:n.x+25};case Gn.Left:return{...n,x:n.x-25};case Gn.Down:return{...n,y:n.y+25};case Gn.Up:return{...n,y:n.y-25}}};class Kn{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:t}}=e;this.props=e,this.listeners=new Fn(Nt(t)),this.windowListeners=new Fn(kt(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(qn.Resize,this.handleCancel),this.windowListeners.add(qn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(qn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&function(e,t){if(void 0===t&&(t=Sn),!e)return;const{top:n,left:r,bottom:o,right:i}=t(e);wn(e)&&(o<=0||i<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(n),t(ln)}handleKeyDown(e){if(zt(e)){const{active:t,context:n,options:r}=this.props,{keyboardCodes:o=zn,coordinateGetter:i=Wn,scrollBehavior:a="smooth"}=r,{code:s}=e;if(o.end.includes(s))return void this.handleEnd(e);if(o.cancel.includes(s))return void this.handleCancel(e);const{collisionRect:l}=n.current,c=l?{x:l.left,y:l.top}:ln;this.referenceCoordinates||(this.referenceCoordinates=c);const u=i(e,{active:t,context:n.current,currentCoordinates:c});if(u){const t=Ht(u,c),r={x:0,y:0},{scrollableAncestors:o}=n.current;for(const n of o){const o=e.code,{isTop:i,isRight:s,isLeft:l,isBottom:c,maxScroll:d,minScroll:p}=Ln(n),f=Bn(n),g={x:Math.min(o===Gn.Right?f.right-f.width/2:f.right,Math.max(o===Gn.Right?f.left:f.left+f.width/2,u.x)),y:Math.min(o===Gn.Down?f.bottom-f.height/2:f.bottom,Math.max(o===Gn.Down?f.top:f.top+f.height/2,u.y))},h=o===Gn.Right&&!s||o===Gn.Left&&!l,m=o===Gn.Down&&!c||o===Gn.Up&&!i;if(h&&g.x!==u.x){const e=n.scrollLeft+t.x,i=o===Gn.Right&&e<=d.x||o===Gn.Left&&e>=p.x;if(i&&!t.y)return void n.scrollTo({left:e,behavior:a});r.x=i?n.scrollLeft-e:o===Gn.Right?n.scrollLeft-d.x:n.scrollLeft-p.x,r.x&&n.scrollBy({left:-r.x,behavior:a});break}if(m&&g.y!==u.y){const e=n.scrollTop+t.y,i=o===Gn.Down&&e<=d.y||o===Gn.Up&&e>=p.y;if(i&&!t.x)return void n.scrollTo({top:e,behavior:a});r.y=i?n.scrollTop-e:o===Gn.Down?n.scrollTop-d.y:n.scrollTop-p.y,r.y&&n.scrollBy({top:-r.y,behavior:a});break}}this.handleMove(e,$t(Ht(u,this.referenceCoordinates),r))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function Xn(e){return Boolean(e&&"distance"in e)}function Yn(e){return Boolean(e&&"delay"in e)}Kn.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=zn,onActivation:o}=t,{active:i}=n;const{code:a}=e.nativeEvent;if(r.start.includes(a)){const t=i.activatorNode.current;return!(t&&e.target!==t||(e.preventDefault(),null==o||o({event:e.nativeEvent}),0))}return!1}}];class Zn{constructor(e,t,n){var r;void 0===n&&(n=function(e){const{EventTarget:t}=kt(e);return e instanceof t?e:Nt(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;const{event:o}=e,{target:i}=o;this.props=e,this.events=t,this.document=Nt(i),this.documentListeners=new Fn(this.document),this.listeners=new Fn(n),this.windowListeners=new Fn(kt(i)),this.initialCoordinates=null!=(r=Wt(o))?r:ln,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(qn.Resize,this.handleCancel),this.windowListeners.add(qn.DragStart,$n),this.windowListeners.add(qn.VisibilityChange,this.handleCancel),this.windowListeners.add(qn.ContextMenu,$n),this.documentListeners.add(qn.Keydown,this.handleKeydown),t){if(null!=n&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Yn(t))return this.timeoutId=setTimeout(this.handleStart,t.delay),void this.handlePending(t);if(Xn(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){const{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(qn.Click,Hn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(qn.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;const{activated:n,initialCoordinates:r,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!r)return;const s=null!=(t=Wt(e))?t:ln,l=Ht(r,s);if(!n&&a){if(Xn(a)){if(null!=a.tolerance&&Vn(l,a.tolerance))return this.handleCancel();if(Vn(l,a.distance))return this.handleStart()}return Yn(a)&&Vn(l,a.tolerance)?this.handleCancel():void this.handlePending(a,l)}e.cancelable&&e.preventDefault(),i(s)}handleEnd(){const{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){const{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===Gn.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}const Jn={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class Qn extends Zn{constructor(e){const{event:t}=e,n=Nt(t.target);super(e,Jn,n)}}Qn.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!(!n.isPrimary||0!==n.button||(null==r||r({event:n}),0))}}];const er={move:{name:"mousemove"},end:{name:"mouseup"}};var tr;!function(e){e[e.RightClick=2]="RightClick"}(tr||(tr={})),class extends Zn{constructor(e){super(e,er,Nt(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button!==tr.RightClick&&(null==r||r({event:n}),!0)}}];const nr={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};var rr,or;(class extends Zn{constructor(e){super(e,nr)}static setup(){return window.addEventListener(nr.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(nr.move.name,e)};function e(){}}}).activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:o}=n;return!(o.length>1||(null==r||r({event:n}),0))}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(rr||(rr={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(or||(or={}));const ir={x:{[Dn.Backward]:!1,[Dn.Forward]:!1},y:{[Dn.Backward]:!1,[Dn.Forward]:!1}};var ar,sr;!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(ar||(ar={})),function(e){e.Optimized="optimized"}(sr||(sr={}));const lr=new Map;function cr(e,t){return jt(n=>e?n||("function"==typeof t?t(e):e):null,[t,e])}function ur(e){let{callback:t,disabled:n}=e;const r=Bt(t),o=(0,Ct.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.ResizeObserver)return;const{ResizeObserver:e}=window;return new e(r)},[n]);return(0,Ct.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}function dr(e){return new Un(Sn(e),e)}function pr(e,t,n){void 0===t&&(t=dr);const[r,o]=(0,Ct.useState)(null);function i(){o(r=>{if(!e)return null;var o;if(!1===e.isConnected)return null!=(o=null!=r?r:n)?o:null;const i=t(e);return JSON.stringify(r)===JSON.stringify(i)?r:i})}const a=function(e){let{callback:t,disabled:n}=e;const r=Bt(t),o=(0,Ct.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.MutationObserver)return;const{MutationObserver:e}=window;return new e(r)},[r,n]);return(0,Ct.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}({callback(t){if(e)for(const n of t){const{type:t,target:r}=n;if("childList"===t&&r instanceof HTMLElement&&r.contains(e)){i();break}}}}),s=ur({callback:i});return Pt(()=>{i(),e?(null==s||s.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==s||s.disconnect(),null==a||a.disconnect())},[e]),r}const fr=[];function gr(e,t){void 0===t&&(t=[]);const n=(0,Ct.useRef)(null);return(0,Ct.useEffect)(()=>{n.current=null},t),(0,Ct.useEffect)(()=>{const t=e!==ln;t&&!n.current&&(n.current=e),!t&&n.current&&(n.current=null)},[e]),n.current?Ht(e,n.current):ln}function hr(e){return(0,Ct.useMemo)(()=>e?function(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}(e):null,[e])}const mr=[];const vr=[{sensor:Qn,options:{}},{sensor:Kn,options:{}}],br={current:{}},Er={draggable:{measure:xn},droppable:{measure:xn,strategy:ar.WhileDragging,frequency:sr.Optimized},dragOverlay:{measure:Sn}};class yr extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,n;return null!=(t=null==(n=this.get(e))?void 0:n.node.current)?t:void 0}}const Tr={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new yr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:an},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:Er,measureDroppableContainers:an,windowRect:null,measuringScheduled:!1},Sr={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:an,draggableNodes:new Map,over:null,measureDroppableContainers:an},xr=(0,Ct.createContext)(Sr),Cr=(0,Ct.createContext)(Tr);function wr(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new yr}}}function _r(e,t){switch(t.type){case on.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case on.DragMove:return null==e.draggable.active?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case on.DragEnd:case on.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case on.RegisterDroppable:{const{element:n}=t,{id:r}=n,o=new yr(e.droppable.containers);return o.set(r,n),{...e,droppable:{...e.droppable,containers:o}}}case on.SetDroppableDisabled:{const{id:n,key:r,disabled:o}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;const a=new yr(e.droppable.containers);return a.set(n,{...i,disabled:o}),{...e,droppable:{...e.droppable,containers:a}}}case on.UnregisterDroppable:{const{id:n,key:r}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new yr(e.droppable.containers);return i.delete(n),{...e,droppable:{...e.droppable,containers:i}}}default:return e}}function Rr(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:o}=(0,Ct.useContext)(xr),i=Ft(r),a=Ft(null==n?void 0:n.id);return(0,Ct.useEffect)(()=>{if(!t&&!r&&i&&null!=a){if(!zt(i))return;if(document.activeElement===i.target)return;const e=o.get(a);if(!e)return;const{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(const e of[t.current,n.current]){if(!e)continue;const t=Yt(e);if(t){t.focus();break}}})}},[r,t,o,a,i]),null}const Ar=(0,Ct.createContext)({...ln,scaleX:1,scaleY:1});var Ir;!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(Ir||(Ir={}));const kr=(0,Ct.memo)(function(e){var t,n,r,o;let{id:i,accessibility:a,autoScroll:s=!0,children:l,sensors:c=vr,collisionDetection:u=vn,measuring:d,modifiers:p,...f}=e;const g=(0,Ct.useReducer)(_r,void 0,wr),[h,m]=g,[v,b]=function(){const[e]=(0,Ct.useState)(()=>new Set),t=(0,Ct.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,Ct.useCallback)(t=>{let{type:n,event:r}=t;e.forEach(e=>{var t;return null==(t=e[n])?void 0:t.call(e,r)})},[e]),t]}(),[E,y]=(0,Ct.useState)(Ir.Uninitialized),T=E===Ir.Initialized,{draggable:{active:S,nodes:x,translate:C},droppable:{containers:w}}=h,_=null!=S?x.get(S):null,R=(0,Ct.useRef)({initial:null,translated:null}),A=(0,Ct.useMemo)(()=>{var e;return null!=S?{id:S,data:null!=(e=null==_?void 0:_.data)?e:br,rect:R}:null},[S,_]),I=(0,Ct.useRef)(null),[k,D]=(0,Ct.useState)(null),[O,L]=(0,Ct.useState)(null),N=Mt(f,Object.values(f)),P=qt("DndDescribedBy",i),B=(0,Ct.useMemo)(()=>w.getEnabled(),[w]),M=(j=d,(0,Ct.useMemo)(()=>({draggable:{...Er.draggable,...null==j?void 0:j.draggable},droppable:{...Er.droppable,...null==j?void 0:j.droppable},dragOverlay:{...Er.dragOverlay,...null==j?void 0:j.dragOverlay}}),[null==j?void 0:j.draggable,null==j?void 0:j.droppable,null==j?void 0:j.dragOverlay]));var j;const{droppableRects:U,measureDroppableContainers:F,measuringScheduled:V}=function(e,t){let{dragging:n,dependencies:r,config:o}=t;const[i,a]=(0,Ct.useState)(null),{frequency:s,measure:l,strategy:c}=o,u=(0,Ct.useRef)(e),d=function(){switch(c){case ar.Always:return!1;case ar.BeforeDragging:return n;default:return!n}}(),p=Mt(d),f=(0,Ct.useCallback)(function(e){void 0===e&&(e=[]),p.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),g=(0,Ct.useRef)(null),h=jt(t=>{if(d&&!n)return lr;if(!t||t===lr||u.current!==e||null!=i){const t=new Map;for(let n of e){if(!n)continue;if(i&&i.length>0&&!i.includes(n.id)&&n.rect.current){t.set(n.id,n.rect.current);continue}const e=n.node.current,r=e?new Un(l(e),e):null;n.rect.current=r,r&&t.set(n.id,r)}return t}return t},[e,i,n,d,l]);return(0,Ct.useEffect)(()=>{u.current=e},[e]),(0,Ct.useEffect)(()=>{d||f()},[n,d]),(0,Ct.useEffect)(()=>{i&&i.length>0&&a(null)},[JSON.stringify(i)]),(0,Ct.useEffect)(()=>{d||"number"!=typeof s||null!==g.current||(g.current=setTimeout(()=>{f(),g.current=null},s))},[s,d,f,...r]),{droppableRects:h,measureDroppableContainers:f,measuringScheduled:null!=i}}(B,{dragging:T,dependencies:[C.x,C.y],config:M.droppable}),q=function(e,t){const n=null!=t?e.get(t):void 0,r=n?n.node.current:null;return jt(e=>{var n;return null==t?null:null!=(n=null!=r?r:e)?n:null},[r,t])}(x,S),G=(0,Ct.useMemo)(()=>O?Wt(O):null,[O]),$=function(){const e=!1===(null==k?void 0:k.autoScrollEnabled),t="object"==typeof s?!1===s.enabled:!1===s,n=T&&!e&&!t;return"object"==typeof s?{...s,enabled:n}:{enabled:n}}(),H=function(e,t){return cr(e,t)}(q,M.draggable.measure);!function(e){let{activeNode:t,measure:n,initialRect:r,config:o=!0}=e;const i=(0,Ct.useRef)(!1),{x:a,y:s}="boolean"==typeof o?{x:o,y:o}:o;Pt(()=>{if(!a&&!s||!t)return void(i.current=!1);if(i.current||!r)return;const e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;const o=bn(n(e),r);if(a||(o.x=0),s||(o.y=0),i.current=!0,Math.abs(o.x)>0||Math.abs(o.y)>0){const t=wn(e);t&&t.scrollBy({top:o.y,left:o.x})}},[t,a,s,r,n])}({activeNode:null!=S?x.get(S):null,config:$.layoutShiftCompensation,initialRect:H,measure:M.draggable.measure});const z=pr(q,M.draggable.measure,H),W=pr(q?q.parentElement:null),K=(0,Ct.useRef)({activatorEvent:null,active:null,activeNode:q,collisionRect:null,collisions:null,droppableRects:U,draggableNodes:x,draggingNode:null,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),X=w.getNodeFor(null==(t=K.current.over)?void 0:t.id),Y=function(e){let{measure:t}=e;const[n,r]=(0,Ct.useState)(null),o=ur({callback:(0,Ct.useCallback)(e=>{for(const{target:n}of e)if(Ot(n)){r(e=>{const r=t(n);return e?{...e,width:r.width,height:r.height}:r});break}},[t])}),i=(0,Ct.useCallback)(e=>{const n=function(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Ot(t)?t:e}(e);null==o||o.disconnect(),n&&(null==o||o.observe(n)),r(n?t(n):null)},[t,o]),[a,s]=Ut(i);return(0,Ct.useMemo)(()=>({nodeRef:a,rect:n,setRef:s}),[n,a,s])}({measure:M.dragOverlay.measure}),Z=null!=(n=Y.nodeRef.current)?n:q,J=T?null!=(r=Y.rect)?r:z:null,Q=Boolean(Y.nodeRef.current&&Y.rect),ee=bn(te=Q?null:z,cr(te));var te;const ne=hr(Z?kt(Z):null),re=function(e){const t=(0,Ct.useRef)(e),n=jt(n=>e?n&&n!==fr&&e&&t.current&&e.parentNode===t.current.parentNode?n:Cn(e):fr,[e]);return(0,Ct.useEffect)(()=>{t.current=e},[e]),n}(T?null!=X?X:q:null),oe=function(e,t){void 0===t&&(t=Sn);const[n]=e,r=hr(n?kt(n):null),[o,i]=(0,Ct.useState)(mr);function a(){i(()=>e.length?e.map(e=>On(e)?r:new Un(t(e),e)):mr)}const s=ur({callback:a});return Pt(()=>{null==s||s.disconnect(),a(),e.forEach(e=>null==s?void 0:s.observe(e))},[e]),o}(re),ie=function(e,t){let{transform:n,...r}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}(p,{transform:{x:C.x-ee.x,y:C.y-ee.y,scaleX:1,scaleY:1},activatorEvent:O,active:A,activeNodeRect:z,containerNodeRect:W,draggingNodeRect:J,over:K.current.over,overlayNodeRect:Y.rect,scrollableAncestors:re,scrollableAncestorRects:oe,windowRect:ne}),ae=G?$t(G,C):null,se=function(e){const[t,n]=(0,Ct.useState)(null),r=(0,Ct.useRef)(e),o=(0,Ct.useCallback)(e=>{const t=Rn(e.target);t&&n(e=>e?(e.set(t,kn(t)),new Map(e)):null)},[]);return(0,Ct.useEffect)(()=>{const t=r.current;if(e!==t){i(t);const a=e.map(e=>{const t=Rn(e);return t?(t.addEventListener("scroll",o,{passive:!0}),[t,kn(t)]):null}).filter(e=>null!=e);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(t)};function i(e){e.forEach(e=>{const t=Rn(e);null==t||t.removeEventListener("scroll",o)})}},[o,e]),(0,Ct.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>$t(e,t),ln):Mn(e):ln,[e,t])}(re),le=gr(se),ce=gr(se,[z]),ue=$t(ie,le),de=J?yn(J,ie):null,pe=A&&de?u({active:A,collisionRect:de,droppableRects:U,droppableContainers:B,pointerCoordinates:ae}):null,fe=fn(pe,"id"),[ge,he]=(0,Ct.useState)(null),me=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(Q?ie:$t(ie,ce),null!=(o=null==ge?void 0:ge.rect)?o:null,z),ve=(0,Ct.useRef)(null),be=(0,Ct.useCallback)((e,t)=>{let{sensor:n,options:r}=t;if(null==I.current)return;const o=x.get(I.current);if(!o)return;const i=e.nativeEvent,a=new n({active:I.current,activeNode:o,event:i,options:r,context:K,onAbort(e){if(!x.get(e))return;const{onDragAbort:t}=N.current,n={id:e};null==t||t(n),v({type:"onDragAbort",event:n})},onPending(e,t,n,r){if(!x.get(e))return;const{onDragPending:o}=N.current,i={id:e,constraint:t,initialCoordinates:n,offset:r};null==o||o(i),v({type:"onDragPending",event:i})},onStart(e){const t=I.current;if(null==t)return;const n=x.get(t);if(!n)return;const{onDragStart:r}=N.current,o={activatorEvent:i,active:{id:t,data:n.data,rect:R}};(0,_t.unstable_batchedUpdates)(()=>{null==r||r(o),y(Ir.Initializing),m({type:on.DragStart,initialCoordinates:e,active:t}),v({type:"onDragStart",event:o}),D(ve.current),L(i)})},onMove(e){m({type:on.DragMove,coordinates:e})},onEnd:s(on.DragEnd),onCancel:s(on.DragCancel)});function s(e){return async function(){const{active:t,collisions:n,over:r,scrollAdjustedTranslate:o}=K.current;let a=null;if(t&&o){const{cancelDrop:s}=N.current;a={activatorEvent:i,active:t,collisions:n,delta:o,over:r},e===on.DragEnd&&"function"==typeof s&&await Promise.resolve(s(a))&&(e=on.DragCancel)}I.current=null,(0,_t.unstable_batchedUpdates)(()=>{m({type:e}),y(Ir.Uninitialized),he(null),D(null),L(null),ve.current=null;const t=e===on.DragEnd?"onDragEnd":"onDragCancel";if(a){const e=N.current[t];null==e||e(a),v({type:t,event:a})}})}}ve.current=a},[x]),Ee=(0,Ct.useCallback)((e,t)=>(n,r)=>{const o=n.nativeEvent,i=x.get(r);if(null!==I.current||!i||o.dndKit||o.defaultPrevented)return;const a={active:i};!0===e(n,t.options,a)&&(o.dndKit={capturedBy:t.sensor},I.current=r,be(n,t))},[x,be]),ye=function(e,t){return(0,Ct.useMemo)(()=>e.reduce((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:t(e.handler,n)}))]},[]),[e,t])}(c,Ee);!function(e){(0,Ct.useEffect)(()=>{if(!Rt)return;const t=e.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(const e of t)null==e||e()}},e.map(e=>{let{sensor:t}=e;return t}))}(c),Pt(()=>{z&&E===Ir.Initializing&&y(Ir.Initialized)},[z,E]),(0,Ct.useEffect)(()=>{const{onDragMove:e}=N.current,{active:t,activatorEvent:n,collisions:r,over:o}=K.current;if(!t||!n)return;const i={active:t,activatorEvent:n,collisions:r,delta:{x:ue.x,y:ue.y},over:o};(0,_t.unstable_batchedUpdates)(()=>{null==e||e(i),v({type:"onDragMove",event:i})})},[ue.x,ue.y]),(0,Ct.useEffect)(()=>{const{active:e,activatorEvent:t,collisions:n,droppableContainers:r,scrollAdjustedTranslate:o}=K.current;if(!e||null==I.current||!t||!o)return;const{onDragOver:i}=N.current,a=r.get(fe),s=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,l={active:e,activatorEvent:t,collisions:n,delta:{x:o.x,y:o.y},over:s};(0,_t.unstable_batchedUpdates)(()=>{he(s),null==i||i(l),v({type:"onDragOver",event:l})})},[fe]),Pt(()=>{K.current={activatorEvent:O,active:A,activeNode:q,collisionRect:de,collisions:pe,droppableRects:U,draggableNodes:x,draggingNode:Z,draggingNodeRect:J,droppableContainers:w,over:ge,scrollableAncestors:re,scrollAdjustedTranslate:ue},R.current={initial:J,translated:de}},[A,q,pe,de,x,Z,J,U,w,ge,re,ue]),function(e){let{acceleration:t,activator:n=rr.Pointer,canScroll:r,draggingRect:o,enabled:i,interval:a=5,order:s=or.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:u,delta:d,threshold:p}=e;const f=function(e){let{delta:t,disabled:n}=e;const r=Ft(t);return jt(e=>{if(n||!r||!e)return ir;const o=Math.sign(t.x-r.x),i=Math.sign(t.y-r.y);return{x:{[Dn.Backward]:e.x[Dn.Backward]||-1===o,[Dn.Forward]:e.x[Dn.Forward]||1===o},y:{[Dn.Backward]:e.y[Dn.Backward]||-1===i,[Dn.Forward]:e.y[Dn.Forward]||1===i}}},[n,t,r])}({delta:d,disabled:!i}),[g,h]=function(){const e=(0,Ct.useRef)(null);return[(0,Ct.useCallback)((t,n)=>{e.current=setInterval(t,n)},[]),(0,Ct.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}(),m=(0,Ct.useRef)({x:0,y:0}),v=(0,Ct.useRef)({x:0,y:0}),b=(0,Ct.useMemo)(()=>{switch(n){case rr.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case rr.DraggableRect:return o}},[n,o,l]),E=(0,Ct.useRef)(null),y=(0,Ct.useCallback)(()=>{const e=E.current;if(!e)return;const t=m.current.x*v.current.x,n=m.current.y*v.current.y;e.scrollBy(t,n)},[]),T=(0,Ct.useMemo)(()=>s===or.TreeOrder?[...c].reverse():c,[s,c]);(0,Ct.useEffect)(()=>{if(i&&c.length&&b){for(const e of T){if(!1===(null==r?void 0:r(e)))continue;const n=c.indexOf(e),o=u[n];if(!o)continue;const{direction:i,speed:s}=Pn(e,o,b,t,p);for(const e of["x","y"])f[e][i[e]]||(s[e]=0,i[e]=0);if(s.x>0||s.y>0)return h(),E.current=e,g(y,a),m.current=s,void(v.current=i)}m.current={x:0,y:0},v.current={x:0,y:0},h()}else h()},[t,y,r,h,i,a,JSON.stringify(b),JSON.stringify(f),g,c,T,u,JSON.stringify(p)])}({...$,delta:C,draggingRect:de,pointerCoordinates:ae,scrollableAncestors:re,scrollableAncestorRects:oe});const Te=(0,Ct.useMemo)(()=>({active:A,activeNode:q,activeNodeRect:z,activatorEvent:O,collisions:pe,containerNodeRect:W,dragOverlay:Y,draggableNodes:x,droppableContainers:w,droppableRects:U,over:ge,measureDroppableContainers:F,scrollableAncestors:re,scrollableAncestorRects:oe,measuringConfiguration:M,measuringScheduled:V,windowRect:ne}),[A,q,z,O,pe,W,Y,x,w,U,ge,F,re,oe,M,V,ne]),Se=(0,Ct.useMemo)(()=>({activatorEvent:O,activators:ye,active:A,activeNodeRect:z,ariaDescribedById:{draggable:P},dispatch:m,draggableNodes:x,over:ge,measureDroppableContainers:F}),[O,ye,A,z,m,P,x,ge,F]);return wt().createElement(en.Provider,{value:b},wt().createElement(xr.Provider,{value:Se},wt().createElement(Cr.Provider,{value:Te},wt().createElement(Ar.Provider,{value:me},l)),wt().createElement(Rr,{disabled:!1===(null==a?void 0:a.restoreFocus)})),wt().createElement(rn,{...a,hiddenTextDescribedById:P}))}),Dr=(0,Ct.createContext)(null),Or="button";const Lr={timeout:25};function Nr(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Pr(e,t){return e.reduce((e,n,r)=>{const o=t.get(n);return o&&(e[r]=o),e},Array(e.length))}function Br(e){return null!==e&&e>=0}const Mr=e=>{let{rects:t,activeIndex:n,overIndex:r,index:o}=e;const i=Nr(t,r,n),a=t[o],s=i[o];return s&&a?{x:s.left-a.left,y:s.top-a.top,scaleX:s.width/a.width,scaleY:s.height/a.height}:null},jr={scaleX:1,scaleY:1},Ur=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:o,rects:i,overIndex:a}=e;const s=null!=(t=i[n])?t:r;if(!s)return null;if(o===n){const e=i[a];return e?{x:0,y:nn&&o<=a?{x:0,y:-s.height-l,...jr}:o=a?{x:0,y:s.height+l,...jr}:{x:0,y:0,...jr}},Fr="Sortable",Vr=wt().createContext({activeIndex:-1,containerId:Fr,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:Mr,disabled:{draggable:!1,droppable:!1}});function qr(e){let{children:t,id:n,items:r,strategy:o=Mr,disabled:i=!1}=e;const{active:a,dragOverlay:s,droppableRects:l,over:c,measureDroppableContainers:u}=(0,Ct.useContext)(Cr),d=qt(Fr,n),p=Boolean(null!==s.rect),f=(0,Ct.useMemo)(()=>r.map(e=>"object"==typeof e&&"id"in e?e.id:e),[r]),g=null!=a,h=a?f.indexOf(a.id):-1,m=c?f.indexOf(c.id):-1,v=(0,Ct.useRef)(f),b=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{b&&g&&u(f)},[b,f,g,u]),(0,Ct.useEffect)(()=>{v.current=f},[f]);const T=(0,Ct.useMemo)(()=>({activeIndex:h,containerId:d,disabled:y,disableTransforms:E,items:f,overIndex:m,useDragOverlay:p,sortedRects:Pr(f,l),strategy:o}),[h,d,y.draggable,y.droppable,E,f,m,l,p,o]);return wt().createElement(Vr.Provider,{value:T},t)}const Gr=e=>{let{id:t,items:n,activeIndex:r,overIndex:o}=e;return Nr(n,r,o).indexOf(t)},$r=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:o,items:i,newIndex:a,previousItems:s,previousContainerId:l,transition:c}=e;return!(!c||!r||s!==i&&o===a||!n&&(a===o||t!==l))},Hr={duration:200,easing:"ease"},zr="transform",Wr=Kt.Transition.toString({property:zr,duration:0,easing:"linear"}),Kr={roleDescription:"sortable"};function Xr(e){let{animateLayoutChanges:t=$r,attributes:n,disabled:r,data:o,getNewIndex:i=Gr,id:a,strategy:s,resizeObserverConfig:l,transition:c=Hr}=e;const{items:u,containerId:d,activeIndex:p,disabled:f,disableTransforms:g,sortedRects:h,overIndex:m,useDragOverlay:v,strategy:b}=(0,Ct.useContext)(Vr),E=function(e,t){var n,r;return"boolean"==typeof e?{draggable:e,droppable:!1}:{draggable:null!=(n=null==e?void 0:e.draggable)?n:t.draggable,droppable:null!=(r=null==e?void 0:e.droppable)?r:t.droppable}}(r,f),y=u.indexOf(a),T=(0,Ct.useMemo)(()=>({sortable:{containerId:d,index:y,items:u},...o}),[d,o,y,u]),S=(0,Ct.useMemo)(()=>u.slice(u.indexOf(a)),[u,a]),{rect:x,node:C,isOver:w,setNodeRef:_}=function(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:o}=e;const i=qt("Droppable"),{active:a,dispatch:s,over:l,measureDroppableContainers:c}=(0,Ct.useContext)(xr),u=(0,Ct.useRef)({disabled:n}),d=(0,Ct.useRef)(!1),p=(0,Ct.useRef)(null),f=(0,Ct.useRef)(null),{disabled:g,updateMeasurementsFor:h,timeout:m}={...Lr,...o},v=Mt(null!=h?h:r),b=ur({callback:(0,Ct.useCallback)(()=>{d.current?(null!=f.current&&clearTimeout(f.current),f.current=setTimeout(()=>{c(Array.isArray(v.current)?v.current:[v.current]),f.current=null},m)):d.current=!0},[m]),disabled:g||!a}),E=(0,Ct.useCallback)((e,t)=>{b&&(t&&(b.unobserve(t),d.current=!1),e&&b.observe(e))},[b]),[y,T]=Ut(E),S=Mt(t);return(0,Ct.useEffect)(()=>{b&&y.current&&(b.disconnect(),d.current=!1,b.observe(y.current))},[y,b]),(0,Ct.useEffect)(()=>(s({type:on.RegisterDroppable,element:{id:r,key:i,disabled:n,node:y,rect:p,data:S}}),()=>s({type:on.UnregisterDroppable,key:i,id:r})),[r]),(0,Ct.useEffect)(()=>{n!==u.current.disabled&&(s({type:on.SetDroppableDisabled,id:r,key:i,disabled:n}),u.current.disabled=n)},[r,i,n,s]),{active:a,rect:p,isOver:(null==l?void 0:l.id)===r,node:y,over:l,setNodeRef:T}}({id:a,data:T,disabled:E.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...l}}),{active:R,activatorEvent:A,activeNodeRect:I,attributes:k,setNodeRef:D,listeners:O,isDragging:L,over:N,setActivatorNodeRef:P,transform:B}=function(e){let{id:t,data:n,disabled:r=!1,attributes:o}=e;const i=qt("Draggable"),{activators:a,activatorEvent:s,active:l,activeNodeRect:c,ariaDescribedById:u,draggableNodes:d,over:p}=(0,Ct.useContext)(xr),{role:f=Or,roleDescription:g="draggable",tabIndex:h=0}=null!=o?o:{},m=(null==l?void 0:l.id)===t,v=(0,Ct.useContext)(m?Ar:Dr),[b,E]=Ut(),[y,T]=Ut(),S=function(e,t){return(0,Ct.useMemo)(()=>e.reduce((e,n)=>{let{eventName:r,handler:o}=n;return e[r]=e=>{o(e,t)},e},{}),[e,t])}(a,t),x=Mt(n);return Pt(()=>(d.set(t,{id:t,key:i,node:b,activatorNode:y,data:x}),()=>{const e=d.get(t);e&&e.key===i&&d.delete(t)}),[d,t]),{active:l,activatorEvent:s,activeNodeRect:c,attributes:(0,Ct.useMemo)(()=>({role:f,tabIndex:h,"aria-disabled":r,"aria-pressed":!(!m||f!==Or)||void 0,"aria-roledescription":g,"aria-describedby":u.draggable}),[r,f,h,m,g,u.draggable]),isDragging:m,listeners:r?void 0:S,node:b,over:p,setNodeRef:E,setActivatorNodeRef:T,transform:v}}({id:a,data:T,attributes:{...Kr,...n},disabled:E.draggable}),M=function(){for(var e=arguments.length,t=new Array(e),n=0;ne=>{t.forEach(t=>t(e))},t)}(_,D),j=Boolean(R),U=j&&!g&&Br(p)&&Br(m),F=!v&&L,V=F&&U?B:null,q=U?null!=V?V:(null!=s?s:b)({rects:h,activeNodeRect:I,activeIndex:p,overIndex:m,index:y}):null,G=Br(p)&&Br(m)?i({id:a,items:u,activeIndex:p,overIndex:m}):y,$=null==R?void 0:R.id,H=(0,Ct.useRef)({activeId:$,items:u,newIndex:G,containerId:d}),z=u!==H.current.items,W=t({active:R,containerId:d,isDragging:L,isSorting:j,id:a,index:y,items:u,newIndex:H.current.newIndex,previousItems:H.current.items,previousContainerId:H.current.containerId,transition:c,wasDragging:null!=H.current.activeId}),K=function(e){let{disabled:t,index:n,node:r,rect:o}=e;const[i,a]=(0,Ct.useState)(null),s=(0,Ct.useRef)(n);return Pt(()=>{if(!t&&n!==s.current&&r.current){const e=o.current;if(e){const t=Sn(r.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&a(n)}}n!==s.current&&(s.current=n)},[t,n,r,o]),(0,Ct.useEffect)(()=>{i&&a(null)},[i]),i}({disabled:!W,index:y,node:C,rect:x});return(0,Ct.useEffect)(()=>{j&&H.current.newIndex!==G&&(H.current.newIndex=G),d!==H.current.containerId&&(H.current.containerId=d),u!==H.current.items&&(H.current.items=u)},[j,G,d,u]),(0,Ct.useEffect)(()=>{if($===H.current.activeId)return;if(null!=$&&null==H.current.activeId)return void(H.current.activeId=$);const e=setTimeout(()=>{H.current.activeId=$},50);return()=>clearTimeout(e)},[$]),{active:R,activeIndex:p,attributes:k,data:T,rect:x,index:y,newIndex:G,items:u,isOver:w,isSorting:j,isDragging:L,listeners:O,node:C,overIndex:m,over:N,setNodeRef:M,setActivatorNodeRef:P,setDroppableNodeRef:_,setDraggableNodeRef:D,transform:null!=K?K:q,transition:K||z&&H.current.newIndex===y?Wr:F&&!zt(A)||!c?void 0:j||W?Kt.Transition.toString({...c,property:zr}):void 0}}function Yr(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&"object"==typeof t.sortable&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const Zr=[Gn.Down,Gn.Right,Gn.Up,Gn.Left],Jr=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:o,droppableContainers:i,over:a,scrollableAncestors:s}}=t;if(Zr.includes(e.code)){if(e.preventDefault(),!n||!r)return;const t=[];i.getEnabled().forEach(n=>{if(!n||null!=n&&n.disabled)return;const i=o.get(n.id);if(i)switch(e.code){case Gn.Down:r.topi.top&&t.push(n);break;case Gn.Left:r.left>i.left&&t.push(n);break;case Gn.Right:r.left{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=pn(t),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=pn(r),a=o.reduce((e,t,r)=>e+cn(n[r],t),0),s=Number((a/4).toFixed(4));i.push({id:t,data:{droppableContainer:e,value:s}})}}return i.sort(un)})({active:n,collisionRect:r,droppableRects:o,droppableContainers:t,pointerCoordinates:null});let d=fn(u,"id");if(d===(null==a?void 0:a.id)&&u.length>1&&(d=u[1].id),null!=d){const e=i.get(n.id),t=i.get(d),a=t?o.get(t.id):null,u=null==t?void 0:t.node.current;if(u&&a&&e&&t){const n=Cn(u).some((e,t)=>s[t]!==e),o=Qr(e,t),i=(c=t,!(!Yr(l=e)||!Yr(c))&&!!Qr(l,c)&&l.data.current.sortable.index0&&(0,vt.jsx)("div",{className:"act-step-target-info",children:(0,vt.jsxs)("code",{children:[e.target.locators[0].value.substring(0,30),e.target.locators[0].value.length>30?"…":""]})})]}),(0,vt.jsxs)(ht.FlexItem,{className:"act-step-actions",children:[(0,vt.jsx)(ht.Button,{icon:Tt,label:(0,gt.__)("Edit step","admin-coach-tours"),onClick:()=>t(e),size:"small"}),(0,vt.jsx)(ht.Button,{icon:St,label:(0,gt.__)("Delete step","admin-coach-tours"),onClick:()=>n(e.id),size:"small",isDestructive:!0})]})]})})}function to({tourId:e,steps:t=[],onEditStep:n,onAddStep:r}){const{reorderSteps:o,deleteStep:i}=(0,c.useDispatch)("admin-coach-tours"),a=function(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(e=>null!=e),[...t])}(sn(Qn,{activationConstraint:{distance:8}}),sn(Kn,{coordinateGetter:Jr})),s=(0,ft.useCallback)(n=>{const{active:r,over:i}=n;if(r.id!==i?.id){const n=t.findIndex(e=>e.id===r.id),a=t.findIndex(e=>e.id===i?.id);if(-1!==n&&-1!==a){const r=Nr(t.map(e=>e.id),n,a);o(e,r)}}},[e,t,o]),l=(0,ft.useCallback)(t=>{window.confirm((0,gt.__)("Are you sure you want to delete this step?","admin-coach-tours"))&&i(e,t)},[e,i]);if(0===t.length)return(0,vt.jsxs)("div",{className:"act-step-list-empty",children:[(0,vt.jsx)("p",{children:(0,gt.__)("No steps yet. Click the button below to add your first step.","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"primary",icon:xt,onClick:r,children:(0,gt.__)("Add First Step","admin-coach-tours")})]});const u=[...t].sort((e,t)=>e.order-t.order);return(0,vt.jsxs)("div",{className:"act-step-list",children:[(0,vt.jsx)(kr,{sensors:a,collisionDetection:hn,onDragEnd:s,children:(0,vt.jsx)(qr,{items:u.map(e=>e.id),strategy:Ur,children:u.map(e=>(0,vt.jsx)(eo,{step:e,onEdit:n,onDelete:l},e.id))})}),(0,vt.jsx)("div",{className:"act-step-list-footer act-button-group",children:(0,vt.jsx)(ht.Button,{variant:"secondary",icon:xt,onClick:r,children:(0,gt.__)("Add Step","admin-coach-tours")})})]})}var no=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})}),ro=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})}),oo=(0,vt.jsx)(mt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,vt.jsx)(mt.Path,{d:"M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z"})});function io(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function ao(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const i=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",i),i}function so(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function lo(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function co(e,t=document){switch(e.type){case"css":return lo(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),i={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let a=[];i[n]&&(a=Array.from(t.querySelectorAll(i[n])));const s=[...o,...a];return r?s.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):s}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return lo(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let i=null;if("first"===e)i=o[0]?.clientId;else if("last"===e)i=o[o.length-1]?.clientId;else if("selected"===e)i=r.getSelectedBlockClientId();else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,a=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",a.length),i=a[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);i=o[t]?.clientId}if(!i)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",i);const a=t.querySelector(`[data-block="${i}"]`);return a?(console.log("[ACT findByWpBlock] Found element:",a.tagName),[a]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function uo(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&so(e,n.withinContainer)&&(r+=10),ao(e)&&(r+=100),io(e)&&(r+=5),r}const po="admin-coach-tours";function fo({step:e,tourId:t,postType:n,onClose:r}){const[o,i]=(0,ft.useState)(e.title||""),[a,s]=(0,ft.useState)(e.content||""),[l,u]=(0,ft.useState)(e.completion?.type||"manual"),[d,p]=(0,ft.useState)(e.completion?.params||{}),[f,g]=(0,ft.useState)(!1),[h,m]=(0,ft.useState)(null),[v,b]=(0,ft.useState)(null),{aiDraft:E,isAiDrafting:y,aiDraftError:T}=(0,c.useSelect)(e=>{const t=e(po);return{aiDraft:t.getAiDraft(),isAiDrafting:t.isAiDrafting(),aiDraftError:t.getAiDraftError()}},[]),{updateStep:S,requestAiDraft:x,clearAiDraft:C,startPicking:w}=(0,c.useDispatch)(po),_=[{type:"clickTarget",label:"Click Target",description:"Complete when user clicks the target element",requiresTarget:!0,params:[]},{type:"domValueChanged",label:"Value Changed",description:"Complete when element value changes",requiresTarget:!0,params:[{name:"expectedValue",type:"string",optional:!0,description:"Expected value (if not set, any change completes)"},{name:"attributeName",type:"string",optional:!0,description:"Attribute to watch (defaults to value/textContent)"}]},{type:"wpData",label:"Store Change",description:"Complete when @wordpress/data store value changes",requiresTarget:!1,params:[{name:"storeName",type:"string",required:!0,description:"Store name (e.g., core/block-editor)"},{name:"selector",type:"string",required:!0,description:"Selector function name"},{name:"args",type:"array",optional:!0,description:"Arguments for selector"},{name:"expectedValue",type:"any",optional:!0,description:"Expected value"},{name:"comparator",type:"string",optional:!0,description:"equals, notEquals, truthy, falsy, contains, greaterThan, lessThan"}]},{type:"manual",label:"Manual",description:"Complete when user clicks continue button",requiresTarget:!1,params:[]},{type:"elementAppear",label:"Element Appears",description:"Complete when an element appears in DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"elementDisappear",label:"Element Disappears",description:"Complete when an element is removed from DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"customEvent",label:"Custom Event",description:"Complete when a custom event is dispatched",requiresTarget:!1,params:[{name:"eventName",type:"string",required:!0,description:"Custom event name"}]}],R=(0,ft.useCallback)(()=>{if(e.target){const t=function(e){const t=function(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),i=o.filter(e=>!e.fallback),a=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",i.length,"primary +",a.length,"fallback locators");for(const e of[...i,...a]){let n=co(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(io),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(ao),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>so(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:uo(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}(e);return{success:t.success,element:t.element,usedLocator:t.usedLocator,error:t.error,elementInfo:t.element?{tagName:t.element.tagName.toLowerCase(),id:t.element.id||null,className:t.element.className||null,textContent:t.element.textContent?.slice(0,50)||null,rect:t.element.getBoundingClientRect()}:null}}(e.target);b(t),setTimeout(()=>b(null),5e3)}},[e.target]),A=(0,ft.useCallback)(async()=>{g(!0),m(null);try{await S(t,e.id,{title:o.trim(),content:a.trim(),completion:{type:l,params:d}}),r()}catch(e){m(e.message||(0,gt.__)("Failed to save step.","admin-coach-tours"))}finally{g(!1)}},[t,e.id,o,a,l,d,S,r]),I=(0,ft.useCallback)(()=>{if(e.target){const t={...e.elementContext||{selector:e.target,stepId:e.id},existingTitle:o,existingContent:a};x(t,n)}},[e.id,e.target,e.elementContext,o,a,n,x]),k=(0,ft.useCallback)(()=>{E&&(E.title&&i(E.title),E.content&&s(E.content),E.suggestedCompletion&&(u(E.suggestedCompletion.type),p(E.suggestedCompletion||{})),C())},[E,C]),D=(0,ft.useCallback)(()=>{w(e.id)},[e.id,w]),O=(0,ft.useCallback)((e,t)=>{p(n=>({...n,[e]:t}))},[]),L=_.find(e=>e.type===l);return(0,vt.jsxs)("div",{className:"act-step-editor",children:[h&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!1,children:h}),(0,vt.jsx)(ht.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("Target Element","admin-coach-tours"),className:"act-step-editor-target",children:(0,vt.jsx)("div",{className:"act-target-info",children:e.target?.locators?.length>0?(0,vt.jsxs)(vt.Fragment,{children:[(0,vt.jsx)("code",{className:"act-target-selector",children:e.target.locators[0].value}),(0,vt.jsxs)(ht.Flex,{gap:2,style:{marginTop:"8px"},children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"secondary",size:"small",icon:no,onClick:R,children:(0,gt.__)("Test","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",size:"small",onClick:D,children:(0,gt.__)("Re-pick","admin-coach-tours")})})]}),v&&(0,vt.jsx)(ht.Notice,{status:v.success?"success":"error",isDismissible:!1,className:"act-target-test-result",children:v.success?(0,gt.__)("Target found successfully!","admin-coach-tours"):(0,gt.__)("Target not found. Consider re-picking.","admin-coach-tours")})]}):(0,vt.jsx)(ht.Button,{variant:"primary",icon:no,onClick:D,children:(0,gt.__)("Pick Target Element","admin-coach-tours")})})}),(0,vt.jsx)(ht.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,gt.__)("Step Title","admin-coach-tours"),value:o,onChange:i,placeholder:(0,gt.__)("e.g., Click the Add Block button","admin-coach-tours")}),(0,vt.jsx)(ht.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("Step Content","admin-coach-tours"),value:a,onChange:s,placeholder:(0,gt.__)("Explain what the user should do and why…","admin-coach-tours"),rows:4}),e.target&&(0,vt.jsxs)(ht.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,gt.__)("AI Assistance","admin-coach-tours"),className:"act-ai-draft-section",children:[y?(0,vt.jsxs)(ht.Flex,{align:"center",gap:2,children:[(0,vt.jsx)(ht.Spinner,{}),(0,vt.jsx)("span",{children:(0,gt.__)("Generating draft…","admin-coach-tours")})]}):E?(0,vt.jsxs)("div",{className:"act-ai-draft",children:[(0,vt.jsxs)("div",{className:"act-ai-draft-preview",children:[(0,vt.jsx)("strong",{children:E.title}),(0,vt.jsxs)("p",{children:[E.content?.substring(0,100),"…"]})]}),(0,vt.jsxs)(ht.Flex,{gap:2,children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"primary",size:"small",icon:Et,onClick:k,children:(0,gt.__)("Apply","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",size:"small",icon:ro,onClick:()=>C(),children:(0,gt.__)("Dismiss","admin-coach-tours")})})]})]}):(0,vt.jsx)(ht.Button,{variant:"secondary",icon:oo,onClick:I,children:(0,gt.__)("Generate with AI","admin-coach-tours")}),T&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!1,children:T})]}),(0,vt.jsx)(ht.SelectControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,gt.__)("Completion Condition","admin-coach-tours"),value:l,options:_.map(e=>({value:e.type,label:e.label})),onChange:e=>{u(e),p({})},help:L?.description}),L?.params&&L.params.length>0&&(0,vt.jsx)("div",{className:"act-completion-params",children:L.params.map(e=>(0,vt.jsx)(ht.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:e.name,value:d[e.name]||"",onChange:t=>O(e.name,t),help:e.description,required:e.required},e.name))}),(0,vt.jsx)("div",{className:"act-step-editor-actions",children:(0,vt.jsxs)(ht.Flex,{justify:"flex-end",gap:2,children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"tertiary",onClick:r,children:(0,gt.__)("Cancel","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)(ht.Button,{variant:"primary",onClick:A,isBusy:f,disabled:f||!o.trim(),children:(0,gt.__)("Save Step","admin-coach-tours")})})]})})]})}const go=[/^css-/i,/^sc-/i,/^emotion-/i,/^_/i,/^jsx-/i,/^styles?__/i,/^[a-z]{1,2}[0-9]+/i,/^[0-9]/i,/^svelte-/i];function ho(e){return e.filter(e=>{return!((t=e).length<=3||go.some(e=>e.test(t)));var t})}function mo(e){const t={},n=e.attributes;for(let e=0;ee.startsWith(t))||(t[e]=r.value)}}return t}function vo(e){if(e.getAttribute("role"))return e.getAttribute("role");const t=e.tagName.toLowerCase(),n=e.getAttribute("type"),r={button:"button",a:e.hasAttribute("href")?"link":null,input:{text:"textbox",search:"searchbox",email:"textbox",url:"textbox",tel:"textbox",password:"textbox",checkbox:"checkbox",radio:"radio",submit:"button",button:"button",reset:"button",range:"slider"},textarea:"textbox",select:"listbox",option:"option",img:e.hasAttribute("alt")?"img":null,nav:"navigation",main:"main",header:"banner",footer:"contentinfo",aside:"complementary",form:"form",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",ul:"list",ol:"list",li:"listitem",table:"table",dialog:"dialog"};return"input"===t&&r.input[n]?r.input[n]:r[t]||null}function bo(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||null}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||null}if(e.getAttribute("title"))return e.getAttribute("title");const n=e.tagName.toLowerCase();if("button"===n||"a"===n||"button"===e.getAttribute("role")){const t=e.textContent?.trim();if(t&&t.length<100)return t}return null}function Eo(e){return!(e&&!/^block-[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9]{8,}$/i.test(e)&&!/[-_][a-f0-9]{8,}/i.test(e))}const yo="admin-coach-tours",To=[".act-picker-overlay",".act-picker-highlight",".act-picker-toolbar",".edit-post-sidebar","#adminmenumain","#wpadminbar",".components-popover",".components-modal__screen-overlay",'iframe[name="editor-canvas"]'];function So(){return document.querySelector('iframe[name="editor-canvas"]')}function xo(e){for(const t of To){if(e.matches(t))return!0;if(e.closest(t))return!0}return!1}function Co({onCancel:e}){const[t,n]=(0,ft.useState)(null),[r,o]=(0,ft.useState)(null),i=(0,ft.useRef)(null),{pickingStepId:a,currentTourId:s}=(0,c.useSelect)(e=>{const t=e(yo);return{pickingStepId:t.getPickingStepId?.()||null,currentTourId:t.getCurrentTourId?.()||null}},[]),{stopPicking:l,addStep:u,updateStep:d}=(0,c.useDispatch)(yo),p=(0,ft.useRef)(!1),f=(0,ft.useRef)(null),g=(0,ft.useRef)(null),h=(0,ft.useCallback)((e,t=!1)=>{if(g.current)return;g.current=setTimeout(()=>{g.current=null},16);let r=null;if(t){if(r=e.target,r&&!xo(r)){if(r===f.current)return;f.current=r,p.current=!0;const e=So();if(e){const t=e.getBoundingClientRect(),i=r.getBoundingClientRect();n(r),o({top:t.top+i.top,left:t.left+i.left,width:i.width,height:i.height})}}}else if(r=document.elementsFromPoint(e.clientX,e.clientY).find(e=>!(e.closest(".act-picker-overlay")||xo(e)||"IFRAME"===e.tagName&&"editor-canvas"===e.name)),r&&r!==f.current){f.current=r,p.current=!1,n(r);const e=r.getBoundingClientRect();o({top:e.top,left:e.left,width:e.width,height:e.height})}},[]),m=(0,ft.useCallback)(e=>{e.preventDefault(),e.stopPropagation();const t=f.current;if(!t)return;const n=function(e,t={}){const{inEditorIframe:n=!1}=t,r=[],o={visible:!0};n&&(o.inEditorIframe=!0);const i=e.getAttribute("data-testid");i&&!Eo(i)&&r.push({type:"testId",value:i,weight:100,fallback:!1}),e.id&&!Eo(e.id)&&r.push({type:"css",value:`#${CSS.escape(e.id)}`,weight:95,fallback:!1});const a=vo(e),s=bo(e);if(a){const e=s?`${a}:${s}`:a;r.push({type:"role",value:e,weight:80,fallback:!1})}const l=mo(e);for(const[e,t]of Object.entries(l)){if("testid"===e||"reactid"===e)continue;if("block"===e&&t&&/^[a-f0-9-]{36}$/i.test(t))continue;const n="type"===e?85:e.startsWith("wp-")?75:70;if(r.push({type:"dataAttribute",value:t?`${e}:${t}`:e,weight:n,fallback:!1}),r.filter(e=>"dataAttribute"===e.type).length>=2)break}const c=function(e,t=3){const n=[];let r=e,o=0;for(;r&&r!==document.body&&o0&&(t+=i.slice(0,2).map(e=>`.${CSS.escape(e)}`).join(""));const a=r.getAttribute("data-testid");if(a&&0===o)t=`[data-testid="${a}"]`;else{const n=r.getAttribute("type");n&&"input"===e&&(t+=`[type="${n}"]`);const o=r.getAttribute("name");o&&["input","select","textarea"].includes(e)&&(t+=`[name="${o}"]`)}if(r.parentElement&&0===o){const e=Array.from(r.parentElement.children).filter(e=>e.tagName===r.tagName);e.length>1&&(t+=`:nth-of-type(${e.indexOf(r)+1})`)}n.unshift(t),r=r.parentElement,o++}return n.join(" > ")}(e,3);c&&r.push({type:"css",value:c,weight:60,fallback:!1});const u=e.getAttribute("aria-label");u&&r.push({type:"ariaLabel",value:u,weight:40,fallback:!0});const d=function(e,t=5){const n=["main","nav","aside","header","footer","section","article","form","dialog"],r=["main","navigation","complementary","banner","contentinfo","region","form","dialog","search"];let o=e.parentElement,i=0;for(;o&&o!==document.body&&i0){const e=ho(Array.from(o.classList));e.length>0&&(n+=`.${CSS.escape(e[0])}`)}return{element:o,selector:n,type:t||e}}const a=["edit-post-sidebar","block-editor","editor-styles-wrapper","components-popover","components-modal","interface-interface-skeleton"];for(const e of a)if(o.classList.contains(e))return{element:o,selector:`.${e}`,type:"editor-region"};o=o.parentElement,i++}return null}(e);if(d){o.withinContainer=d.selector;const t=e.tagName.toLowerCase(),n=ho(Array.from(e.classList));let i=t;n.length>0&&(i+=`.${CSS.escape(n[0])}`),r.push({type:"contextual",value:`${d.selector} >> ${i}`,weight:50,fallback:!0});const a=e.getAttribute("data-type");a&&r.push({type:"css",value:`[data-type="${a}"]:first-of-type`,weight:45,fallback:!0})}if(0===r.length){const t=e.tagName.toLowerCase(),n=e.parentElement;if(n){const o=Array.from(n.children).indexOf(e)+1;r.push({type:"css",value:`${t}:nth-child(${o})`,weight:10,fallback:!0})}}return r.sort((e,t)=>(t.weight||50)-(e.weight||50)),{locators:r,constraints:o}}(t,{inEditorIframe:p.current}),r=function(e){const t={tagName:e.tagName.toLowerCase()},n=vo(e);n&&(t.role=n),e.id&&!/^[a-z0-9_-]{20,}$/i.test(e.id)&&(t.id=e.id);const r=ho(Array.from(e.classList));r.length>0&&(t.classNames=r.slice(0,5));const o=e.textContent?.trim();o&&o.length<=200?t.textContent=o:o&&(t.textContent=`${o.slice(0,197)}...`),e.placeholder&&(t.placeholder=e.placeholder);const i=bo(e);i&&i!==t.textContent&&(t.label=i);const a=mo(e),s=Object.keys(a).filter(e=>e.startsWith("wp-")||"block"===e||"type"===e||"testid"===e);s.length>0&&(t.dataAttrs={},s.forEach(e=>{t.dataAttrs[e]=a[e]}));const l=[];let c=e.parentElement,u=0;for(;c&&c!==document.body&&u<3;){const e={tagName:c.tagName.toLowerCase()},t=vo(c);t&&(e.role=t),c.id&&!/^[a-z0-9_-]{20,}$/i.test(c.id)&&(e.id=c.id);const n=ho(Array.from(c.classList));n.length>0&&(e.classNames=n.slice(0,3)),l.push(e),c=c.parentElement,u++}return l.length>0&&(t.ancestors=l),t}(t),o={target:n,elementContext:r,completion:{type:"clickTarget",params:{}}};a?d(s,a,{target:n,elementContext:r}):u(s,o),l()},[a,s,u,d,l]),v=(0,ft.useCallback)(t=>{"Escape"===t.key&&(l(),e?.())},[l,e]),b=(0,ft.useRef)({handleMouseMove:h,handleClick:m,handleKeyDown:v});(0,ft.useEffect)(()=>{b.current={handleMouseMove:h,handleClick:m,handleKeyDown:v}},[h,m,v]),(0,ft.useEffect)(()=>{const e=e=>b.current.handleMouseMove(e,!1),t=e=>b.current.handleClick(e),n=e=>b.current.handleKeyDown(e),r=e=>b.current.handleMouseMove(e,!0),o=e=>{e.preventDefault(),e.stopPropagation(),b.current.handleClick(e)};document.addEventListener("mousemove",e,!0),document.addEventListener("click",t,!0),document.addEventListener("keydown",n,!0),document.body.style.overflow="hidden";let i=null,a=null;const s=()=>{const e=So();e?.contentDocument&&e.contentDocument!==i&&(i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0)),i=e.contentDocument,i.addEventListener("mousemove",r,!0),i.addEventListener("click",o,!0),i.addEventListener("keydown",n,!0))};return s(),a=setInterval(s,500),()=>{document.removeEventListener("mousemove",e,!0),document.removeEventListener("click",t,!0),document.removeEventListener("keydown",n,!0),document.body.style.overflow="",g.current&&clearTimeout(g.current),a&&clearInterval(a),i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0))}},[]);const E=(0,vt.jsxs)("div",{ref:i,className:"act-picker-overlay",style:{position:"fixed",top:0,left:0,right:0,bottom:0,zIndex:9999998,cursor:"crosshair",pointerEvents:"none"},children:[r?(0,vt.jsx)("div",{className:"act-picker-highlight",style:{position:"fixed",top:r.top,left:r.left,width:r.width,height:r.height,border:"3px solid #007cba",backgroundColor:"rgba(0, 124, 186, 0.15)",pointerEvents:"none",zIndex:9999999,boxSizing:"border-box",borderRadius:"3px",transition:"top 0.08s ease-out, left 0.08s ease-out, width 0.08s ease-out, height 0.08s ease-out",boxShadow:"0 0 0 2px rgba(0, 124, 186, 0.3)"}}):null,(()=>{if(!t)return null;const e=t.tagName.toLowerCase(),n=t.id,r=Array.from(t.classList).slice(0,3).join(".");let o=e;return n?o+=`#${n}`:r&&(o+=`.${r}`),(0,vt.jsx)("div",{className:"act-picker-element-info",style:{position:"fixed",bottom:"80px",left:"50%",transform:"translateX(-50%)",backgroundColor:"rgba(0, 0, 0, 0.8)",color:"#fff",padding:"8px 16px",borderRadius:"4px",fontFamily:"monospace",fontSize:"13px",maxWidth:"80%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",zIndex:9999999},children:o})})(),(0,vt.jsxs)("div",{className:"act-picker-toolbar",style:{position:"fixed",bottom:"20px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"12px",backgroundColor:"#fff",padding:"12px 20px",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",zIndex:9999999,pointerEvents:"auto"},children:[(0,vt.jsx)("span",{style:{alignSelf:"center",fontWeight:500},children:(0,gt.__)("Click an element to select it as target","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"tertiary",icon:ro,onClick:()=>{l(),e?.()},children:(0,gt.__)("Cancel","admin-coach-tours")})]})]});return(0,ft.createPortal)(E,document.body)}const wo="admin-coach-tours";(0,l.registerPlugin)("admin-coach-tours-educator",{render:function(){const[e,t]=(0,ft.useState)(null),[n,r]=(0,ft.useState)(!1),[o,i]=(0,ft.useState)(!1),{postType:a,postId:s,postTitle:l,postStatus:u,isSaving:d}=(0,c.useSelect)(e=>{const t=e("core/editor");return{postType:t?.getCurrentPostType?.()||"",postId:t?.getCurrentPostId?.()||0,postTitle:t?.getEditedPostAttribute?.("title")||"",postStatus:t?.getEditedPostAttribute?.("status")||"draft",isSaving:t?.isSavingPost?.()||!1}},[]),{currentTour:p,selectedStep:f,isPickerActive:g,isLoading:h}=(0,c.useSelect)(e=>{const t=e(wo);return{currentTour:t.getCurrentTour(),selectedStep:t.getSelectedStep(),isPickerActive:t.isPickerActive(),isLoading:t.isToursLoading()}},[]),{setCurrentTour:m,saveTour:v,startPicking:b,stopPicking:E,selectStep:y}=(0,c.useDispatch)(wo),{enableComplementaryArea:T}=(0,c.useDispatch)("core/interface");if((0,ft.useEffect)(()=>{"act_tour"===a&&s&&s!==p?.id&&m(s)},[a,s,p?.id,m]),(0,ft.useEffect)(()=>{if("act_tour"===a){const e=setTimeout(()=>{T("core","admin-coach-tours-educator/admin-coach-tours-sidebar")},100);return()=>clearTimeout(e)}},[a,T]),"act_tour"!==a)return null;const S=(0,ft.useCallback)(()=>{b()},[b]),x=(0,ft.useCallback)(()=>{E()},[E]),C=(0,ft.useCallback)(()=>{if(p?.id){const e=(p.postTypes||["post"])[0]||"post",t=new URL(window.location.origin+"/wp-admin/post-new.php");"post"!==e&&t.searchParams.set("post_type",e),t.searchParams.set("act_tour",p.id.toString()),console.log("[ACT Educator] Opening test URL:",t.toString()),window.open(t.toString(),"_blank")}},[p?.id,p?.postTypes]);if(h&&!p)return(0,vt.jsx)(vt.Fragment,{children:(0,vt.jsx)(pt.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,gt.__)("Tour Steps","admin-coach-tours"),icon:bt,children:(0,vt.jsx)("div",{className:"act-educator-sidebar",children:(0,vt.jsx)(ht.PanelBody,{children:(0,vt.jsx)(ht.Flex,{justify:"center",style:{padding:"24px"},children:(0,vt.jsx)(ht.Spinner,{})})})})})});const w=p?.steps||[];return(0,vt.jsxs)(vt.Fragment,{children:[(0,vt.jsx)(pt.PluginSidebarMoreMenuItem,{target:"admin-coach-tours-sidebar",icon:bt,children:(0,gt.__)("Tour Steps","admin-coach-tours")}),(0,vt.jsx)(pt.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,gt.__)("Tour Steps","admin-coach-tours"),icon:bt,children:(0,vt.jsxs)("div",{className:"act-educator-sidebar",children:[(0,vt.jsxs)(ht.PanelBody,{title:(0,gt.__)("Tour Info","admin-coach-tours"),initialOpen:!1,children:[(0,vt.jsxs)(ht.Flex,{justify:"space-between",align:"center",children:[(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)("strong",{children:l||(0,gt.__)("Untitled Tour","admin-coach-tours")})}),(0,vt.jsx)(ht.FlexItem,{children:(0,vt.jsx)("span",{className:"act-tour-status"+("publish"===u?" act-tour-status--published":""),children:"publish"===u?(0,gt.__)("Published","admin-coach-tours"):(0,gt.__)("Draft","admin-coach-tours")})})]}),(0,vt.jsx)("p",{className:"act-help-text",style:{marginTop:"8px"},children:(0,gt.__)("Use the block editor canvas as a sandbox to create your tour steps. Pick elements from the editor to target them in your tour.","admin-coach-tours")})]}),(0,vt.jsxs)(ht.PanelBody,{title:(0,gt.__)("Steps","admin-coach-tours")+` (${w.length})`,initialOpen:!0,children:[e&&(0,vt.jsx)(ht.Notice,{status:"error",isDismissible:!0,onRemove:()=>t(null),children:e}),o&&(0,vt.jsx)(ht.Notice,{status:"success",isDismissible:!1,children:(0,gt.__)("Steps saved successfully!","admin-coach-tours")}),(0,vt.jsx)(to,{tourId:s,steps:w,onEditStep:e=>y(e?.id??null),onAddStep:S}),w.length>0&&(0,vt.jsxs)("div",{className:"act-actions-footer",children:[(0,vt.jsx)(ht.Button,{variant:"primary",icon:Et,onClick:async()=>{if(p?.id){r(!0),t(null),i(!1);try{const e={steps:p.steps||[]};await v(p.id,e),i(!0),setTimeout(()=>i(!1),3e3)}catch(e){t(e.message||(0,gt.__)("Failed to save steps.","admin-coach-tours"))}finally{r(!1)}}},isBusy:n,disabled:n||d,children:n?(0,gt.__)("Saving…","admin-coach-tours"):(0,gt.__)("Save Steps","admin-coach-tours")}),(0,vt.jsx)(ht.Button,{variant:"secondary",icon:yt,onClick:C,disabled:0===w.length,children:(0,gt.__)("Test Tour","admin-coach-tours")})]})]}),f&&(0,vt.jsx)(ht.PanelBody,{title:(0,gt.__)("Edit Step","admin-coach-tours"),initialOpen:!0,children:(0,vt.jsx)(fo,{step:f,tourId:s,postType:"act_tour",onClose:()=>y(null)})})]})}),g&&(0,vt.jsx)(Co,{onCancel:x})]})},icon:null})})(); \ No newline at end of file +(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var i=r[e]={exports:{}};return n[e](i,i.exports,o),i.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var i=Object.create(null);o.r(i);var a={};e=e||[null,t({}),t([]),t(t)];for(var s=2&r&&n;("object"==typeof s||"function"==typeof s)&&!~e.indexOf(s);s=t(s))Object.getOwnPropertyNames(s).forEach(e=>a[e]=()=>n[e]);return a.default=()=>n,o.d(i,a),i},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var i={};o.r(i),o.d(i,{activatePicker:()=>F,addStep:()=>W,clearAiDraft:()=>Q,clearEphemeralTour:()=>ae,clearResolvedTarget:()=>B,createTour:()=>T,deactivatePicker:()=>q,deleteStep:()=>K,endTour:()=>C,fetchAiTasks:()=>le,fetchTour:()=>E,fetchTours:()=>b,incrementResolutionAttempts:()=>j,markStepComplete:()=>N,nextStep:()=>R,previousStep:()=>A,receiveEphemeralTour:()=>ie,receiveTour:()=>m,receiveTours:()=>h,reorderSteps:()=>X,repeatStep:()=>k,requestAiDraft:()=>ee,requestAiTour:()=>se,resetCompletion:()=>L,saveTour:()=>y,selectStep:()=>H,setAiDraftError:()=>Z,setAiDraftLoading:()=>Y,setAiDraftResult:()=>J,setAiTourError:()=>re,setAiTourLoading:()=>ne,setCompletionSatisfied:()=>O,setCurrentStep:()=>_,setCurrentTour:()=>v,setLastError:()=>U,setLastFailureContext:()=>oe,setMode:()=>D,setPendingChanges:()=>$,setRecovering:()=>M,setResolvedTarget:()=>P,setSidebarOpen:()=>te,setToursError:()=>g,setToursLoading:()=>f,skipStep:()=>I,startEphemeralTour:()=>ce,startPicking:()=>V,startTour:()=>x,stopPicking:()=>G,stopTour:()=>w,updateStep:()=>z,updateTour:()=>S});var a={};o.r(a),o.d(a,{getAiDraft:()=>$e,getAiDraftError:()=>Ge,getAiDraftResult:()=>He,getAiTourError:()=>Ke,getCurrentStep:()=>ye,getCurrentStepIndex:()=>Ee,getCurrentTour:()=>be,getCurrentTourId:()=>ve,getEphemeralTour:()=>Ye,getLastError:()=>Pe,getLastFailureContext:()=>Xe,getMode:()=>we,getPickingStepId:()=>Me,getProgress:()=>Ce,getResolutionAttempts:()=>Ne,getResolvedTarget:()=>Oe,getSelectedStep:()=>Ue,getSelectedStepId:()=>je,getSkippedSteps:()=>ke,getTotalSteps:()=>Te,getTour:()=>pe,getTours:()=>de,getToursByEditor:()=>me,getToursById:()=>ue,getToursByPostType:()=>he,getToursError:()=>ge,hasNextStep:()=>Se,hasPendingChanges:()=>Fe,hasPreviousStep:()=>xe,isAiDraftLoading:()=>Ve,isAiDrafting:()=>qe,isAiTourLoading:()=>We,isCompletionSatisfied:()=>Ie,isEducatorMode:()=>_e,isEphemeralTourActive:()=>Ze,isPickerActive:()=>Be,isPupilMode:()=>Re,isRecovering:()=>Le,isSidebarOpen:()=>ze,isTourActive:()=>Ae,isToursLoading:()=>fe,wasStepSkipped:()=>De});var s={};o.r(s),o.d(s,{getTour:()=>tt,getTours:()=>et,getToursByPostType:()=>nt});const l=window.wp.plugins,c=window.wp.data,u={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},d={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},p=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function f(e){return{type:d.SET_TOURS_LOADING,isLoading:e}}function g(e){return{type:d.SET_TOURS_ERROR,error:e}}function h(e){return{type:d.RECEIVE_TOURS,tours:e}}function m(e){return{type:d.RECEIVE_TOUR,tour:e}}function*v(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield m(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield m({id:e,title:"",steps:[],status:"draft"})}yield{type:d.SET_CURRENT_TOUR,tourId:e}}else yield{type:d.SET_CURRENT_TOUR,tourId:null}}function b(e={}){return{type:"FETCH_TOURS",args:e}}function E(e){return{type:"FETCH_TOUR",tourId:e}}function*y(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function*T(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield m(t)),t}catch(e){throw e}}function*S(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield m(n)),n}catch(e){throw e}}function x(e,t="pupil"){return{type:d.START_TOUR,tourId:e,mode:t}}function C(){return{type:d.END_TOUR}}function w(){return C()}function _(e){return{type:d.SET_CURRENT_STEP,stepIndex:e}}function R(){return{type:d.NEXT_STEP}}function A(){return{type:d.PREVIOUS_STEP}}function I(){return{type:d.SKIP_STEP}}function k(){return{type:d.REPEAT_STEP}}function D(e){return{type:d.SET_MODE,mode:e}}function O(e){return{type:d.SET_COMPLETION_SATISFIED,satisfied:e}}function L(){return{type:d.RESET_COMPLETION}}function N(){return R()}function P(e){return{type:d.SET_RESOLVED_TARGET,target:e}}function B(){return{type:d.CLEAR_RESOLVED_TARGET}}function M(e){return{type:d.SET_RECOVERING,isRecovering:e}}function j(){return{type:d.INCREMENT_RESOLUTION_ATTEMPTS}}function U(e){return{type:d.SET_LAST_ERROR,error:e}}function F(e=null){return{type:d.ACTIVATE_PICKER,stepId:e}}function V(e=null){return F(e)}function q(){return{type:d.DEACTIVATE_PICKER}}function G(){return q()}function H(e){return{type:d.SELECT_STEP,stepId:e}}function $(e){return{type:d.SET_PENDING_CHANGES,pending:e}}function*z(e,t,n){yield{type:d.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*W(e,t={},n=null){const r={id:p(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:d.ADD_STEP,tourId:e,step:r,index:n}}function*K(e,t){yield{type:d.DELETE_STEP,tourId:e,stepId:t}}function*X(e,t){yield{type:d.REORDER_STEPS,tourId:e,stepIds:t}}function Y(e){return{type:d.SET_AI_DRAFT_LOADING,isLoading:e}}function Z(e){return{type:d.SET_AI_DRAFT_ERROR,error:e}}function J(e){return{type:d.SET_AI_DRAFT_RESULT,result:e}}function Q(){return{type:d.CLEAR_AI_DRAFT}}function*ee(e,t){yield Y(!0),yield Z(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield J(n),n}catch(e){throw yield Z(e.message||"Failed to generate AI draft"),e}finally{yield Y(!1)}}function te(e){return{type:d.SET_SIDEBAR_OPEN,isOpen:e}}function ne(e){return{type:d.SET_AI_TOUR_LOADING,isLoading:e}}function re(e){return{type:d.SET_AI_TOUR_ERROR,error:e}}function oe(e){return{type:d.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function ie(e){return{type:d.RECEIVE_EPHEMERAL_TOUR,tour:e}}function ae(){return{type:d.CLEAR_EPHEMERAL_TOUR}}function*se(e,t,n,r=null){yield ne(!0),yield re(null);const o=window.adminCoachTours?.locale||"";try{const i=yield{type:"GATHER_EDITOR_CONTEXT"},a=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:i,failureContext:r,locale:o};console.log("[ACT AI Response] Full result:",a),console.log("[ACT AI Response] Tour:",JSON.stringify(a.tour,null,2));const s={id:"ephemeral",...a.tour};return yield ie(s),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil"),s}catch(e){throw yield re(e.message||"Failed to generate tour"),e}finally{yield ne(!1)}}function*le(){return yield{type:"FETCH_AI_TASKS"}}function*ce(e){const t={id:"ephemeral",...e};yield ie(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield x("ephemeral","pupil")}function ue(e){return e.tours}const de=(0,c.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function pe(e,t){return e.tours[t]||null}function fe(e){return e.toursLoading}function ge(e){return e.toursError}const he=(0,c.createSelector)((e,t)=>de(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),me=(0,c.createSelector)((e,t)=>de(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function ve(e){return e.currentTourId}function be(e){return e.currentTourId?e.tours[e.currentTourId]:null}function Ee(e){return e.currentStepIndex}const ye=(0,c.createSelector)(e=>{const t=be(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function Te(e){const t=be(e);return t?.steps?.length||0}function Se(e){return e.currentStepIndex0}function Ce(e){const t=Te(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function we(e){return e.mode}function _e(e){return"educator"===e.mode}function Re(e){return"pupil"===e.mode}function Ae(e){return null!==e.currentTourId&&null!==e.mode}function Ie(e){return e.completionSatisfied}function ke(e){return e.skippedSteps}function De(e,t){return e.skippedSteps.includes(t)}function Oe(e){return e.resolvedTarget}function Le(e){return e.isRecovering}function Ne(e){return e.resolutionAttempts}function Pe(e){return e.lastError}function Be(e){return e.isPickerActive}function Me(e){return e.pickingStepId||null}function je(e){return e.selectedStepId}const Ue=(0,c.createSelector)(e=>{const t=be(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function Fe(e){return e.pendingChanges}function Ve(e){return e.aiDraftLoading}function qe(e){return Ve(e)}function Ge(e){return e.aiDraftError}function He(e){return e.aiDraftResult}function $e(e){return He(e)}function ze(e){return e.sidebarOpen}function We(e){return e.aiTourLoading}function Ke(e){return e.aiTourError}function Xe(e){return e.lastFailureContext}function Ye(e){return e.ephemeralTour}function Ze(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Je=window.wp.apiFetch;var Qe=o.n(Je);function*et(){yield f(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield h(e)}catch(e){yield g(e.message||"Failed to fetch tours")}}function*tt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield m(t)}catch(e){yield g(e.message||"Failed to fetch tour")}}function*nt(e){yield f(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield h(t)}catch(e){yield g(e.message||"Failed to fetch tours")}}function rt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,c.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,c.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,c.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function ot(){try{const e=(0,c.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:it(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function it(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function at(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:st(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:st(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:st(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:st(o)});const i=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of i){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function st(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function lt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function ct(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let i=null;for(const e of o)if(i=r.querySelector(e),i)break;if(i||(i=r),i.scrollIntoView({behavior:"smooth",block:"center"}),i.focus(),"true"===i.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(i),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",i.tagName,i.className),!0}const ut={API_FETCH:e=>Qe()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:ot(),visibleElements:rt(),uiSamples:at(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,c.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&<(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,c.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&<(e.attributes?.content));return t&&(await(0,c.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await ct(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,c.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,c.select)("core/block-editor"),i=(r?.getBlocks()||[]).length;return await t.insertBlock(n,i,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await ct(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Qe()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Qe()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Qe()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Qe()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Qe()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:e.locale||""}}),FETCH_AI_TASKS:()=>Qe()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},dt="admin-coach-tours",pt=(0,c.createReduxStore)(dt,{reducer:function(e=u,t){switch(t.type){case d.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case d.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case d.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case d.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case d.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case d.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case d.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case d.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case d.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case d.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case d.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case d.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case d.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case d.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case d.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case d.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case d.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case d.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case d.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case d.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case d.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case d.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:i,selectors:a,resolvers:s,controls:ut,initialState:u});(0,c.select)(dt)||(0,c.register)(pt);const ft=window.wp.editor,gt=window.wp.element,ht=window.wp.i18n,mt=window.wp.components,vt=window.wp.primitives,bt=window.ReactJSXRuntime;var Et=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M12 4a8 8 0 1 1 .001 16.001A8 8 0 0 1 12 4Zm0 1.5a6.5 6.5 0 1 0-.001 13.001A6.5 6.5 0 0 0 12 5.5Zm.75 11h-1.5V15h1.5v1.5Zm-.445-9.234a3 3 0 0 1 .445 5.89V14h-1.5v-1.25c0-.57.452-.958.917-1.01A1.5 1.5 0 0 0 12 8.75a1.5 1.5 0 0 0-1.5 1.5H9a3 3 0 0 1 3.305-2.984Z"})}),yt=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M16.5 7.5 10 13.9l-2.5-2.4-1 1 3.5 3.6 7.5-7.6z"})}),Tt=(0,bt.jsx)(vt.SVG,{viewBox:"0 0 24 24",xmlns:"http://www.w3.org/2000/svg",children:(0,bt.jsx)(vt.Path,{d:"M3.99961 13C4.67043 13.3354 4.6703 13.3357 4.67017 13.3359L4.67298 13.3305C4.67621 13.3242 4.68184 13.3135 4.68988 13.2985C4.70595 13.2686 4.7316 13.2218 4.76695 13.1608C4.8377 13.0385 4.94692 12.8592 5.09541 12.6419C5.39312 12.2062 5.84436 11.624 6.45435 11.0431C7.67308 9.88241 9.49719 8.75 11.9996 8.75C14.502 8.75 16.3261 9.88241 17.5449 11.0431C18.1549 11.624 18.6061 12.2062 18.9038 12.6419C19.0523 12.8592 19.1615 13.0385 19.2323 13.1608C19.2676 13.2218 19.2933 13.2686 19.3093 13.2985C19.3174 13.3135 19.323 13.3242 19.3262 13.3305L19.3291 13.3359C19.3289 13.3357 19.3288 13.3354 19.9996 13C20.6704 12.6646 20.6703 12.6643 20.6701 12.664L20.6697 12.6632L20.6688 12.6614L20.6662 12.6563L20.6583 12.6408C20.6517 12.6282 20.6427 12.6108 20.631 12.5892C20.6078 12.5459 20.5744 12.4852 20.5306 12.4096C20.4432 12.2584 20.3141 12.0471 20.1423 11.7956C19.7994 11.2938 19.2819 10.626 18.5794 9.9569C17.1731 8.61759 14.9972 7.25 11.9996 7.25C9.00203 7.25 6.82614 8.61759 5.41987 9.9569C4.71736 10.626 4.19984 11.2938 3.85694 11.7956C3.68511 12.0471 3.55605 12.2584 3.4686 12.4096C3.42484 12.4852 3.39142 12.5459 3.36818 12.5892C3.35656 12.6108 3.34748 12.6282 3.34092 12.6408L3.33297 12.6563L3.33041 12.6614L3.32948 12.6632L3.32911 12.664C3.32894 12.6643 3.32879 12.6646 3.99961 13ZM11.9996 16C13.9326 16 15.4996 14.433 15.4996 12.5C15.4996 10.567 13.9326 9 11.9996 9C10.0666 9 8.49961 10.567 8.49961 12.5C8.49961 14.433 10.0666 16 11.9996 16Z"})}),St=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m19 7-3-3-8.5 8.5-1 4 4-1L19 7Zm-7 11.5H5V20h7v-1.5Z"})}),xt=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{fillRule:"evenodd",clipRule:"evenodd",d:"M12 5.5A2.25 2.25 0 0 0 9.878 7h4.244A2.251 2.251 0 0 0 12 5.5ZM12 4a3.751 3.751 0 0 0-3.675 3H5v1.5h1.27l.818 8.997a2.75 2.75 0 0 0 2.739 2.501h4.347a2.75 2.75 0 0 0 2.738-2.5L17.73 8.5H19V7h-3.325A3.751 3.751 0 0 0 12 4Zm4.224 4.5H7.776l.806 8.861a1.25 1.25 0 0 0 1.245 1.137h4.347a1.25 1.25 0 0 0 1.245-1.137l.805-8.861Z"})}),Ct=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M11 12.5V17.5H12.5V12.5H17.5V11H12.5V6H11V11H6V12.5H11Z"})});const wt=window.React;var _t=o.n(wt);const Rt=window.ReactDOM,At="undefined"!=typeof window&&void 0!==window.document&&void 0!==window.document.createElement;function It(e){const t=Object.prototype.toString.call(e);return"[object Window]"===t||"[object global]"===t}function kt(e){return"nodeType"in e}function Dt(e){var t,n;return e?It(e)?e:kt(e)&&null!=(t=null==(n=e.ownerDocument)?void 0:n.defaultView)?t:window:window}function Ot(e){const{Document:t}=Dt(e);return e instanceof t}function Lt(e){return!It(e)&&e instanceof Dt(e).HTMLElement}function Nt(e){return e instanceof Dt(e).SVGElement}function Pt(e){return e?It(e)?e.document:kt(e)?Ot(e)?e:Lt(e)||Nt(e)?e.ownerDocument:document:document:document}const Bt=At?wt.useLayoutEffect:wt.useEffect;function Mt(e){const t=(0,wt.useRef)(e);return Bt(()=>{t.current=e}),(0,wt.useCallback)(function(){for(var e=arguments.length,n=new Array(e),r=0;r{n.current!==e&&(n.current=e)},t),n}function Ut(e,t){const n=(0,wt.useRef)();return(0,wt.useMemo)(()=>{const t=e(n.current);return n.current=t,t},[...t])}function Ft(e){const t=Mt(e),n=(0,wt.useRef)(null),r=(0,wt.useCallback)(e=>{e!==n.current&&(null==t||t(e,n.current)),n.current=e},[]);return[n,r]}function Vt(e){const t=(0,wt.useRef)();return(0,wt.useEffect)(()=>{t.current=e},[e]),t.current}let qt={};function Gt(e,t){return(0,wt.useMemo)(()=>{if(t)return t;const n=null==qt[e]?0:qt[e]+1;return qt[e]=n,e+"-"+n},[e,t])}function Ht(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o{const r=Object.entries(n);for(const[n,o]of r){const r=t[n];null!=r&&(t[n]=r+e*o)}return t},{...t})}}const $t=Ht(1),zt=Ht(-1);function Wt(e){if(!e)return!1;const{KeyboardEvent:t}=Dt(e.target);return t&&e instanceof t}function Kt(e){if(function(e){if(!e)return!1;const{TouchEvent:t}=Dt(e.target);return t&&e instanceof t}(e)){if(e.touches&&e.touches.length){const{clientX:t,clientY:n}=e.touches[0];return{x:t,y:n}}if(e.changedTouches&&e.changedTouches.length){const{clientX:t,clientY:n}=e.changedTouches[0];return{x:t,y:n}}}return function(e){return"clientX"in e&&"clientY"in e}(e)?{x:e.clientX,y:e.clientY}:null}const Xt=Object.freeze({Translate:{toString(e){if(!e)return;const{x:t,y:n}=e;return"translate3d("+(t?Math.round(t):0)+"px, "+(n?Math.round(n):0)+"px, 0)"}},Scale:{toString(e){if(!e)return;const{scaleX:t,scaleY:n}=e;return"scaleX("+t+") scaleY("+n+")"}},Transform:{toString(e){if(e)return[Xt.Translate.toString(e),Xt.Scale.toString(e)].join(" ")}},Transition:{toString(e){let{property:t,duration:n,easing:r}=e;return t+" "+n+"ms "+r}}}),Yt="a,frame,iframe,input:not([type=hidden]):not(:disabled),select:not(:disabled),textarea:not(:disabled),button:not(:disabled),*[tabindex]";function Zt(e){return e.matches(Yt)?e:e.querySelector(Yt)}const Jt={display:"none"};function Qt(e){let{id:t,value:n}=e;return _t().createElement("div",{id:t,style:Jt},n)}function en(e){let{id:t,announcement:n,ariaLiveType:r="assertive"}=e;return _t().createElement("div",{id:t,style:{position:"fixed",top:0,left:0,width:1,height:1,margin:-1,border:0,padding:0,overflow:"hidden",clip:"rect(0 0 0 0)",clipPath:"inset(100%)",whiteSpace:"nowrap"},role:"status","aria-live":r,"aria-atomic":!0},n)}const tn=(0,wt.createContext)(null),nn={draggable:"\n To pick up a draggable item, press the space bar.\n While dragging, use the arrow keys to move the item.\n Press space again to drop the item in its new position, or press escape to cancel.\n "},rn={onDragStart(e){let{active:t}=e;return"Picked up draggable item "+t.id+"."},onDragOver(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was moved over droppable area "+n.id+".":"Draggable item "+t.id+" is no longer over a droppable area."},onDragEnd(e){let{active:t,over:n}=e;return n?"Draggable item "+t.id+" was dropped over droppable area "+n.id:"Draggable item "+t.id+" was dropped."},onDragCancel(e){let{active:t}=e;return"Dragging was cancelled. Draggable item "+t.id+" was dropped."}};function on(e){let{announcements:t=rn,container:n,hiddenTextDescribedById:r,screenReaderInstructions:o=nn}=e;const{announce:i,announcement:a}=function(){const[e,t]=(0,wt.useState)("");return{announce:(0,wt.useCallback)(e=>{null!=e&&t(e)},[]),announcement:e}}(),s=Gt("DndLiveRegion"),[l,c]=(0,wt.useState)(!1);if((0,wt.useEffect)(()=>{c(!0)},[]),function(e){const t=(0,wt.useContext)(tn);(0,wt.useEffect)(()=>{if(!t)throw new Error("useDndMonitor must be used within a children of ");return t(e)},[e,t])}((0,wt.useMemo)(()=>({onDragStart(e){let{active:n}=e;i(t.onDragStart({active:n}))},onDragMove(e){let{active:n,over:r}=e;t.onDragMove&&i(t.onDragMove({active:n,over:r}))},onDragOver(e){let{active:n,over:r}=e;i(t.onDragOver({active:n,over:r}))},onDragEnd(e){let{active:n,over:r}=e;i(t.onDragEnd({active:n,over:r}))},onDragCancel(e){let{active:n,over:r}=e;i(t.onDragCancel({active:n,over:r}))}}),[i,t])),!l)return null;const u=_t().createElement(_t().Fragment,null,_t().createElement(Qt,{id:r,value:o.draggable}),_t().createElement(en,{id:s,announcement:a}));return n?(0,Rt.createPortal)(u,n):u}var an;function sn(){}function ln(e,t){return(0,wt.useMemo)(()=>({sensor:e,options:null!=t?t:{}}),[e,t])}!function(e){e.DragStart="dragStart",e.DragMove="dragMove",e.DragEnd="dragEnd",e.DragCancel="dragCancel",e.DragOver="dragOver",e.RegisterDroppable="registerDroppable",e.SetDroppableDisabled="setDroppableDisabled",e.UnregisterDroppable="unregisterDroppable"}(an||(an={}));const cn=Object.freeze({x:0,y:0});function un(e,t){return Math.sqrt(Math.pow(e.x-t.x,2)+Math.pow(e.y-t.y,2))}function dn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return n-r}function pn(e,t){let{data:{value:n}}=e,{data:{value:r}}=t;return r-n}function fn(e){let{left:t,top:n,height:r,width:o}=e;return[{x:t,y:n},{x:t+o,y:n},{x:t,y:n+r},{x:t+o,y:n+r}]}function gn(e,t){if(!e||0===e.length)return null;const[n]=e;return t?n[t]:n}function hn(e,t,n){return void 0===t&&(t=e.left),void 0===n&&(n=e.top),{x:t+.5*e.width,y:n+.5*e.height}}const mn=e=>{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=hn(t,t.left,t.top),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=un(hn(r),o);i.push({id:t,data:{droppableContainer:e,value:n}})}}return i.sort(dn)};function vn(e,t){const n=Math.max(t.top,e.top),r=Math.max(t.left,e.left),o=Math.min(t.left+t.width,e.left+e.width),i=Math.min(t.top+t.height,e.top+e.height),a=o-r,s=i-n;if(r{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=[];for(const e of r){const{id:r}=e,i=n.get(r);if(i){const n=vn(i,t);n>0&&o.push({id:r,data:{droppableContainer:e,value:n}})}}return o.sort(pn)};function En(e,t){return e&&t?{x:e.left-t.left,y:e.top-t.top}:cn}function yn(e){return function(t){for(var n=arguments.length,r=new Array(n>1?n-1:0),o=1;o({...t,top:t.top+e*n.y,bottom:t.bottom+e*n.y,left:t.left+e*n.x,right:t.right+e*n.x}),{...t})}}const Tn=yn(1);const Sn={ignoreTransform:!1};function xn(e,t){void 0===t&&(t=Sn);let n=e.getBoundingClientRect();if(t.ignoreTransform){const{transform:t,transformOrigin:r}=Dt(e).getComputedStyle(e);t&&(n=function(e,t,n){const r=function(e){if(e.startsWith("matrix3d(")){const t=e.slice(9,-1).split(/, /);return{x:+t[12],y:+t[13],scaleX:+t[0],scaleY:+t[5]}}if(e.startsWith("matrix(")){const t=e.slice(7,-1).split(/, /);return{x:+t[4],y:+t[5],scaleX:+t[0],scaleY:+t[3]}}return null}(t);if(!r)return e;const{scaleX:o,scaleY:i,x:a,y:s}=r,l=e.left-a-(1-o)*parseFloat(n),c=e.top-s-(1-i)*parseFloat(n.slice(n.indexOf(" ")+1)),u=o?e.width/o:e.width,d=i?e.height/i:e.height;return{width:u,height:d,top:c,right:l+u,bottom:c+d,left:l}}(n,t,r))}const{top:r,left:o,width:i,height:a,bottom:s,right:l}=n;return{top:r,left:o,width:i,height:a,bottom:s,right:l}}function Cn(e){return xn(e,{ignoreTransform:!0})}function wn(e,t){const n=[];return e?function r(o){if(null!=t&&n.length>=t)return n;if(!o)return n;if(Ot(o)&&null!=o.scrollingElement&&!n.includes(o.scrollingElement))return n.push(o.scrollingElement),n;if(!Lt(o)||Nt(o))return n;if(n.includes(o))return n;const i=Dt(e).getComputedStyle(o);return o!==e&&function(e,t){void 0===t&&(t=Dt(e).getComputedStyle(e));const n=/(auto|scroll|overlay)/;return["overflow","overflowX","overflowY"].some(e=>{const r=t[e];return"string"==typeof r&&n.test(r)})}(o,i)&&n.push(o),function(e,t){return void 0===t&&(t=Dt(e).getComputedStyle(e)),"fixed"===t.position}(o,i)?n:r(o.parentNode)}(e):n}function Rn(e){const[t]=wn(e,1);return null!=t?t:null}function An(e){return At&&e?It(e)?e:kt(e)?Ot(e)||e===Pt(e).scrollingElement?window:Lt(e)?e:null:null:null}function In(e){return It(e)?e.scrollX:e.scrollLeft}function kn(e){return It(e)?e.scrollY:e.scrollTop}function Dn(e){return{x:In(e),y:kn(e)}}var On;function Ln(e){return!(!At||!e)&&e===document.scrollingElement}function Nn(e){const t={x:0,y:0},n=Ln(e)?{height:window.innerHeight,width:window.innerWidth}:{height:e.clientHeight,width:e.clientWidth},r={x:e.scrollWidth-n.width,y:e.scrollHeight-n.height};return{isTop:e.scrollTop<=t.y,isLeft:e.scrollLeft<=t.x,isBottom:e.scrollTop>=r.y,isRight:e.scrollLeft>=r.x,maxScroll:r,minScroll:t}}!function(e){e[e.Forward=1]="Forward",e[e.Backward=-1]="Backward"}(On||(On={}));const Pn={x:.2,y:.2};function Bn(e,t,n,r,o){let{top:i,left:a,right:s,bottom:l}=n;void 0===r&&(r=10),void 0===o&&(o=Pn);const{isTop:c,isBottom:u,isLeft:d,isRight:p}=Nn(e),f={x:0,y:0},g={x:0,y:0},h=t.height*o.y,m=t.width*o.x;return!c&&i<=t.top+h?(f.y=On.Backward,g.y=r*Math.abs((t.top+h-i)/h)):!u&&l>=t.bottom-h&&(f.y=On.Forward,g.y=r*Math.abs((t.bottom-h-l)/h)),!p&&s>=t.right-m?(f.x=On.Forward,g.x=r*Math.abs((t.right-m-s)/m)):!d&&a<=t.left+m&&(f.x=On.Backward,g.x=r*Math.abs((t.left+m-a)/m)),{direction:f,speed:g}}function Mn(e){if(e===document.scrollingElement){const{innerWidth:e,innerHeight:t}=window;return{top:0,left:0,right:e,bottom:t,width:e,height:t}}const{top:t,left:n,right:r,bottom:o}=e.getBoundingClientRect();return{top:t,left:n,right:r,bottom:o,width:e.clientWidth,height:e.clientHeight}}function jn(e){return e.reduce((e,t)=>$t(e,Dn(t)),cn)}const Un=[["x",["left","right"],function(e){return e.reduce((e,t)=>e+In(t),0)}],["y",["top","bottom"],function(e){return e.reduce((e,t)=>e+kn(t),0)}]];class Fn{constructor(e,t){this.rect=void 0,this.width=void 0,this.height=void 0,this.top=void 0,this.bottom=void 0,this.right=void 0,this.left=void 0;const n=wn(t),r=jn(n);this.rect={...e},this.width=e.width,this.height=e.height;for(const[e,t,o]of Un)for(const i of t)Object.defineProperty(this,i,{get:()=>{const t=o(n),a=r[e]-t;return this.rect[i]+a},enumerable:!0});Object.defineProperty(this,"rect",{enumerable:!1})}}class Vn{constructor(e){this.target=void 0,this.listeners=[],this.removeAll=()=>{this.listeners.forEach(e=>{var t;return null==(t=this.target)?void 0:t.removeEventListener(...e)})},this.target=e}add(e,t,n){var r;null==(r=this.target)||r.addEventListener(e,t,n),this.listeners.push([e,t,n])}}function qn(e,t){const n=Math.abs(e.x),r=Math.abs(e.y);return"number"==typeof t?Math.sqrt(n**2+r**2)>t:"x"in t&&"y"in t?n>t.x&&r>t.y:"x"in t?n>t.x:"y"in t&&r>t.y}var Gn,Hn;function $n(e){e.preventDefault()}function zn(e){e.stopPropagation()}!function(e){e.Click="click",e.DragStart="dragstart",e.Keydown="keydown",e.ContextMenu="contextmenu",e.Resize="resize",e.SelectionChange="selectionchange",e.VisibilityChange="visibilitychange"}(Gn||(Gn={})),function(e){e.Space="Space",e.Down="ArrowDown",e.Right="ArrowRight",e.Left="ArrowLeft",e.Up="ArrowUp",e.Esc="Escape",e.Enter="Enter",e.Tab="Tab"}(Hn||(Hn={}));const Wn={start:[Hn.Space,Hn.Enter],cancel:[Hn.Esc],end:[Hn.Space,Hn.Enter,Hn.Tab]},Kn=(e,t)=>{let{currentCoordinates:n}=t;switch(e.code){case Hn.Right:return{...n,x:n.x+25};case Hn.Left:return{...n,x:n.x-25};case Hn.Down:return{...n,y:n.y+25};case Hn.Up:return{...n,y:n.y-25}}};class Xn{constructor(e){this.props=void 0,this.autoScrollEnabled=!1,this.referenceCoordinates=void 0,this.listeners=void 0,this.windowListeners=void 0,this.props=e;const{event:{target:t}}=e;this.props=e,this.listeners=new Vn(Pt(t)),this.windowListeners=new Vn(Dt(t)),this.handleKeyDown=this.handleKeyDown.bind(this),this.handleCancel=this.handleCancel.bind(this),this.attach()}attach(){this.handleStart(),this.windowListeners.add(Gn.Resize,this.handleCancel),this.windowListeners.add(Gn.VisibilityChange,this.handleCancel),setTimeout(()=>this.listeners.add(Gn.Keydown,this.handleKeyDown))}handleStart(){const{activeNode:e,onStart:t}=this.props,n=e.node.current;n&&function(e,t){if(void 0===t&&(t=xn),!e)return;const{top:n,left:r,bottom:o,right:i}=t(e);Rn(e)&&(o<=0||i<=0||n>=window.innerHeight||r>=window.innerWidth)&&e.scrollIntoView({block:"center",inline:"center"})}(n),t(cn)}handleKeyDown(e){if(Wt(e)){const{active:t,context:n,options:r}=this.props,{keyboardCodes:o=Wn,coordinateGetter:i=Kn,scrollBehavior:a="smooth"}=r,{code:s}=e;if(o.end.includes(s))return void this.handleEnd(e);if(o.cancel.includes(s))return void this.handleCancel(e);const{collisionRect:l}=n.current,c=l?{x:l.left,y:l.top}:cn;this.referenceCoordinates||(this.referenceCoordinates=c);const u=i(e,{active:t,context:n.current,currentCoordinates:c});if(u){const t=zt(u,c),r={x:0,y:0},{scrollableAncestors:o}=n.current;for(const n of o){const o=e.code,{isTop:i,isRight:s,isLeft:l,isBottom:c,maxScroll:d,minScroll:p}=Nn(n),f=Mn(n),g={x:Math.min(o===Hn.Right?f.right-f.width/2:f.right,Math.max(o===Hn.Right?f.left:f.left+f.width/2,u.x)),y:Math.min(o===Hn.Down?f.bottom-f.height/2:f.bottom,Math.max(o===Hn.Down?f.top:f.top+f.height/2,u.y))},h=o===Hn.Right&&!s||o===Hn.Left&&!l,m=o===Hn.Down&&!c||o===Hn.Up&&!i;if(h&&g.x!==u.x){const e=n.scrollLeft+t.x,i=o===Hn.Right&&e<=d.x||o===Hn.Left&&e>=p.x;if(i&&!t.y)return void n.scrollTo({left:e,behavior:a});r.x=i?n.scrollLeft-e:o===Hn.Right?n.scrollLeft-d.x:n.scrollLeft-p.x,r.x&&n.scrollBy({left:-r.x,behavior:a});break}if(m&&g.y!==u.y){const e=n.scrollTop+t.y,i=o===Hn.Down&&e<=d.y||o===Hn.Up&&e>=p.y;if(i&&!t.x)return void n.scrollTo({top:e,behavior:a});r.y=i?n.scrollTop-e:o===Hn.Down?n.scrollTop-d.y:n.scrollTop-p.y,r.y&&n.scrollBy({top:-r.y,behavior:a});break}}this.handleMove(e,$t(zt(u,this.referenceCoordinates),r))}}}handleMove(e,t){const{onMove:n}=this.props;e.preventDefault(),n(t)}handleEnd(e){const{onEnd:t}=this.props;e.preventDefault(),this.detach(),t()}handleCancel(e){const{onCancel:t}=this.props;e.preventDefault(),this.detach(),t()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll()}}function Yn(e){return Boolean(e&&"distance"in e)}function Zn(e){return Boolean(e&&"delay"in e)}Xn.activators=[{eventName:"onKeyDown",handler:(e,t,n)=>{let{keyboardCodes:r=Wn,onActivation:o}=t,{active:i}=n;const{code:a}=e.nativeEvent;if(r.start.includes(a)){const t=i.activatorNode.current;return!(t&&e.target!==t||(e.preventDefault(),null==o||o({event:e.nativeEvent}),0))}return!1}}];class Jn{constructor(e,t,n){var r;void 0===n&&(n=function(e){const{EventTarget:t}=Dt(e);return e instanceof t?e:Pt(e)}(e.event.target)),this.props=void 0,this.events=void 0,this.autoScrollEnabled=!0,this.document=void 0,this.activated=!1,this.initialCoordinates=void 0,this.timeoutId=null,this.listeners=void 0,this.documentListeners=void 0,this.windowListeners=void 0,this.props=e,this.events=t;const{event:o}=e,{target:i}=o;this.props=e,this.events=t,this.document=Pt(i),this.documentListeners=new Vn(this.document),this.listeners=new Vn(n),this.windowListeners=new Vn(Dt(i)),this.initialCoordinates=null!=(r=Kt(o))?r:cn,this.handleStart=this.handleStart.bind(this),this.handleMove=this.handleMove.bind(this),this.handleEnd=this.handleEnd.bind(this),this.handleCancel=this.handleCancel.bind(this),this.handleKeydown=this.handleKeydown.bind(this),this.removeTextSelection=this.removeTextSelection.bind(this),this.attach()}attach(){const{events:e,props:{options:{activationConstraint:t,bypassActivationConstraint:n}}}=this;if(this.listeners.add(e.move.name,this.handleMove,{passive:!1}),this.listeners.add(e.end.name,this.handleEnd),e.cancel&&this.listeners.add(e.cancel.name,this.handleCancel),this.windowListeners.add(Gn.Resize,this.handleCancel),this.windowListeners.add(Gn.DragStart,$n),this.windowListeners.add(Gn.VisibilityChange,this.handleCancel),this.windowListeners.add(Gn.ContextMenu,$n),this.documentListeners.add(Gn.Keydown,this.handleKeydown),t){if(null!=n&&n({event:this.props.event,activeNode:this.props.activeNode,options:this.props.options}))return this.handleStart();if(Zn(t))return this.timeoutId=setTimeout(this.handleStart,t.delay),void this.handlePending(t);if(Yn(t))return void this.handlePending(t)}this.handleStart()}detach(){this.listeners.removeAll(),this.windowListeners.removeAll(),setTimeout(this.documentListeners.removeAll,50),null!==this.timeoutId&&(clearTimeout(this.timeoutId),this.timeoutId=null)}handlePending(e,t){const{active:n,onPending:r}=this.props;r(n,e,this.initialCoordinates,t)}handleStart(){const{initialCoordinates:e}=this,{onStart:t}=this.props;e&&(this.activated=!0,this.documentListeners.add(Gn.Click,zn,{capture:!0}),this.removeTextSelection(),this.documentListeners.add(Gn.SelectionChange,this.removeTextSelection),t(e))}handleMove(e){var t;const{activated:n,initialCoordinates:r,props:o}=this,{onMove:i,options:{activationConstraint:a}}=o;if(!r)return;const s=null!=(t=Kt(e))?t:cn,l=zt(r,s);if(!n&&a){if(Yn(a)){if(null!=a.tolerance&&qn(l,a.tolerance))return this.handleCancel();if(qn(l,a.distance))return this.handleStart()}return Zn(a)&&qn(l,a.tolerance)?this.handleCancel():void this.handlePending(a,l)}e.cancelable&&e.preventDefault(),i(s)}handleEnd(){const{onAbort:e,onEnd:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleCancel(){const{onAbort:e,onCancel:t}=this.props;this.detach(),this.activated||e(this.props.active),t()}handleKeydown(e){e.code===Hn.Esc&&this.handleCancel()}removeTextSelection(){var e;null==(e=this.document.getSelection())||e.removeAllRanges()}}const Qn={cancel:{name:"pointercancel"},move:{name:"pointermove"},end:{name:"pointerup"}};class er extends Jn{constructor(e){const{event:t}=e,n=Pt(t.target);super(e,Qn,n)}}er.activators=[{eventName:"onPointerDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return!(!n.isPrimary||0!==n.button||(null==r||r({event:n}),0))}}];const tr={move:{name:"mousemove"},end:{name:"mouseup"}};var nr;!function(e){e[e.RightClick=2]="RightClick"}(nr||(nr={})),class extends Jn{constructor(e){super(e,tr,Pt(e.event.target))}}.activators=[{eventName:"onMouseDown",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;return n.button!==nr.RightClick&&(null==r||r({event:n}),!0)}}];const rr={cancel:{name:"touchcancel"},move:{name:"touchmove"},end:{name:"touchend"}};var or,ir;(class extends Jn{constructor(e){super(e,rr)}static setup(){return window.addEventListener(rr.move.name,e,{capture:!1,passive:!1}),function(){window.removeEventListener(rr.move.name,e)};function e(){}}}).activators=[{eventName:"onTouchStart",handler:(e,t)=>{let{nativeEvent:n}=e,{onActivation:r}=t;const{touches:o}=n;return!(o.length>1||(null==r||r({event:n}),0))}}],function(e){e[e.Pointer=0]="Pointer",e[e.DraggableRect=1]="DraggableRect"}(or||(or={})),function(e){e[e.TreeOrder=0]="TreeOrder",e[e.ReversedTreeOrder=1]="ReversedTreeOrder"}(ir||(ir={}));const ar={x:{[On.Backward]:!1,[On.Forward]:!1},y:{[On.Backward]:!1,[On.Forward]:!1}};var sr,lr;!function(e){e[e.Always=0]="Always",e[e.BeforeDragging=1]="BeforeDragging",e[e.WhileDragging=2]="WhileDragging"}(sr||(sr={})),function(e){e.Optimized="optimized"}(lr||(lr={}));const cr=new Map;function ur(e,t){return Ut(n=>e?n||("function"==typeof t?t(e):e):null,[t,e])}function dr(e){let{callback:t,disabled:n}=e;const r=Mt(t),o=(0,wt.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.ResizeObserver)return;const{ResizeObserver:e}=window;return new e(r)},[n]);return(0,wt.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}function pr(e){return new Fn(xn(e),e)}function fr(e,t,n){void 0===t&&(t=pr);const[r,o]=(0,wt.useState)(null);function i(){o(r=>{if(!e)return null;var o;if(!1===e.isConnected)return null!=(o=null!=r?r:n)?o:null;const i=t(e);return JSON.stringify(r)===JSON.stringify(i)?r:i})}const a=function(e){let{callback:t,disabled:n}=e;const r=Mt(t),o=(0,wt.useMemo)(()=>{if(n||"undefined"==typeof window||void 0===window.MutationObserver)return;const{MutationObserver:e}=window;return new e(r)},[r,n]);return(0,wt.useEffect)(()=>()=>null==o?void 0:o.disconnect(),[o]),o}({callback(t){if(e)for(const n of t){const{type:t,target:r}=n;if("childList"===t&&r instanceof HTMLElement&&r.contains(e)){i();break}}}}),s=dr({callback:i});return Bt(()=>{i(),e?(null==s||s.observe(e),null==a||a.observe(document.body,{childList:!0,subtree:!0})):(null==s||s.disconnect(),null==a||a.disconnect())},[e]),r}const gr=[];function hr(e,t){void 0===t&&(t=[]);const n=(0,wt.useRef)(null);return(0,wt.useEffect)(()=>{n.current=null},t),(0,wt.useEffect)(()=>{const t=e!==cn;t&&!n.current&&(n.current=e),!t&&n.current&&(n.current=null)},[e]),n.current?zt(e,n.current):cn}function mr(e){return(0,wt.useMemo)(()=>e?function(e){const t=e.innerWidth,n=e.innerHeight;return{top:0,left:0,right:t,bottom:n,width:t,height:n}}(e):null,[e])}const vr=[];const br=[{sensor:er,options:{}},{sensor:Xn,options:{}}],Er={current:{}},yr={draggable:{measure:Cn},droppable:{measure:Cn,strategy:sr.WhileDragging,frequency:lr.Optimized},dragOverlay:{measure:xn}};class Tr extends Map{get(e){var t;return null!=e&&null!=(t=super.get(e))?t:void 0}toArray(){return Array.from(this.values())}getEnabled(){return this.toArray().filter(e=>{let{disabled:t}=e;return!t})}getNodeFor(e){var t,n;return null!=(t=null==(n=this.get(e))?void 0:n.node.current)?t:void 0}}const Sr={activatorEvent:null,active:null,activeNode:null,activeNodeRect:null,collisions:null,containerNodeRect:null,draggableNodes:new Map,droppableRects:new Map,droppableContainers:new Tr,over:null,dragOverlay:{nodeRef:{current:null},rect:null,setRef:sn},scrollableAncestors:[],scrollableAncestorRects:[],measuringConfiguration:yr,measureDroppableContainers:sn,windowRect:null,measuringScheduled:!1},xr={activatorEvent:null,activators:[],active:null,activeNodeRect:null,ariaDescribedById:{draggable:""},dispatch:sn,draggableNodes:new Map,over:null,measureDroppableContainers:sn},Cr=(0,wt.createContext)(xr),wr=(0,wt.createContext)(Sr);function _r(){return{draggable:{active:null,initialCoordinates:{x:0,y:0},nodes:new Map,translate:{x:0,y:0}},droppable:{containers:new Tr}}}function Rr(e,t){switch(t.type){case an.DragStart:return{...e,draggable:{...e.draggable,initialCoordinates:t.initialCoordinates,active:t.active}};case an.DragMove:return null==e.draggable.active?e:{...e,draggable:{...e.draggable,translate:{x:t.coordinates.x-e.draggable.initialCoordinates.x,y:t.coordinates.y-e.draggable.initialCoordinates.y}}};case an.DragEnd:case an.DragCancel:return{...e,draggable:{...e.draggable,active:null,initialCoordinates:{x:0,y:0},translate:{x:0,y:0}}};case an.RegisterDroppable:{const{element:n}=t,{id:r}=n,o=new Tr(e.droppable.containers);return o.set(r,n),{...e,droppable:{...e.droppable,containers:o}}}case an.SetDroppableDisabled:{const{id:n,key:r,disabled:o}=t,i=e.droppable.containers.get(n);if(!i||r!==i.key)return e;const a=new Tr(e.droppable.containers);return a.set(n,{...i,disabled:o}),{...e,droppable:{...e.droppable,containers:a}}}case an.UnregisterDroppable:{const{id:n,key:r}=t,o=e.droppable.containers.get(n);if(!o||r!==o.key)return e;const i=new Tr(e.droppable.containers);return i.delete(n),{...e,droppable:{...e.droppable,containers:i}}}default:return e}}function Ar(e){let{disabled:t}=e;const{active:n,activatorEvent:r,draggableNodes:o}=(0,wt.useContext)(Cr),i=Vt(r),a=Vt(null==n?void 0:n.id);return(0,wt.useEffect)(()=>{if(!t&&!r&&i&&null!=a){if(!Wt(i))return;if(document.activeElement===i.target)return;const e=o.get(a);if(!e)return;const{activatorNode:t,node:n}=e;if(!t.current&&!n.current)return;requestAnimationFrame(()=>{for(const e of[t.current,n.current]){if(!e)continue;const t=Zt(e);if(t){t.focus();break}}})}},[r,t,o,a,i]),null}const Ir=(0,wt.createContext)({...cn,scaleX:1,scaleY:1});var kr;!function(e){e[e.Uninitialized=0]="Uninitialized",e[e.Initializing=1]="Initializing",e[e.Initialized=2]="Initialized"}(kr||(kr={}));const Dr=(0,wt.memo)(function(e){var t,n,r,o;let{id:i,accessibility:a,autoScroll:s=!0,children:l,sensors:c=br,collisionDetection:u=bn,measuring:d,modifiers:p,...f}=e;const g=(0,wt.useReducer)(Rr,void 0,_r),[h,m]=g,[v,b]=function(){const[e]=(0,wt.useState)(()=>new Set),t=(0,wt.useCallback)(t=>(e.add(t),()=>e.delete(t)),[e]);return[(0,wt.useCallback)(t=>{let{type:n,event:r}=t;e.forEach(e=>{var t;return null==(t=e[n])?void 0:t.call(e,r)})},[e]),t]}(),[E,y]=(0,wt.useState)(kr.Uninitialized),T=E===kr.Initialized,{draggable:{active:S,nodes:x,translate:C},droppable:{containers:w}}=h,_=null!=S?x.get(S):null,R=(0,wt.useRef)({initial:null,translated:null}),A=(0,wt.useMemo)(()=>{var e;return null!=S?{id:S,data:null!=(e=null==_?void 0:_.data)?e:Er,rect:R}:null},[S,_]),I=(0,wt.useRef)(null),[k,D]=(0,wt.useState)(null),[O,L]=(0,wt.useState)(null),N=jt(f,Object.values(f)),P=Gt("DndDescribedBy",i),B=(0,wt.useMemo)(()=>w.getEnabled(),[w]),M=(j=d,(0,wt.useMemo)(()=>({draggable:{...yr.draggable,...null==j?void 0:j.draggable},droppable:{...yr.droppable,...null==j?void 0:j.droppable},dragOverlay:{...yr.dragOverlay,...null==j?void 0:j.dragOverlay}}),[null==j?void 0:j.draggable,null==j?void 0:j.droppable,null==j?void 0:j.dragOverlay]));var j;const{droppableRects:U,measureDroppableContainers:F,measuringScheduled:V}=function(e,t){let{dragging:n,dependencies:r,config:o}=t;const[i,a]=(0,wt.useState)(null),{frequency:s,measure:l,strategy:c}=o,u=(0,wt.useRef)(e),d=function(){switch(c){case sr.Always:return!1;case sr.BeforeDragging:return n;default:return!n}}(),p=jt(d),f=(0,wt.useCallback)(function(e){void 0===e&&(e=[]),p.current||a(t=>null===t?e:t.concat(e.filter(e=>!t.includes(e))))},[p]),g=(0,wt.useRef)(null),h=Ut(t=>{if(d&&!n)return cr;if(!t||t===cr||u.current!==e||null!=i){const t=new Map;for(let n of e){if(!n)continue;if(i&&i.length>0&&!i.includes(n.id)&&n.rect.current){t.set(n.id,n.rect.current);continue}const e=n.node.current,r=e?new Fn(l(e),e):null;n.rect.current=r,r&&t.set(n.id,r)}return t}return t},[e,i,n,d,l]);return(0,wt.useEffect)(()=>{u.current=e},[e]),(0,wt.useEffect)(()=>{d||f()},[n,d]),(0,wt.useEffect)(()=>{i&&i.length>0&&a(null)},[JSON.stringify(i)]),(0,wt.useEffect)(()=>{d||"number"!=typeof s||null!==g.current||(g.current=setTimeout(()=>{f(),g.current=null},s))},[s,d,f,...r]),{droppableRects:h,measureDroppableContainers:f,measuringScheduled:null!=i}}(B,{dragging:T,dependencies:[C.x,C.y],config:M.droppable}),q=function(e,t){const n=null!=t?e.get(t):void 0,r=n?n.node.current:null;return Ut(e=>{var n;return null==t?null:null!=(n=null!=r?r:e)?n:null},[r,t])}(x,S),G=(0,wt.useMemo)(()=>O?Kt(O):null,[O]),H=function(){const e=!1===(null==k?void 0:k.autoScrollEnabled),t="object"==typeof s?!1===s.enabled:!1===s,n=T&&!e&&!t;return"object"==typeof s?{...s,enabled:n}:{enabled:n}}(),$=function(e,t){return ur(e,t)}(q,M.draggable.measure);!function(e){let{activeNode:t,measure:n,initialRect:r,config:o=!0}=e;const i=(0,wt.useRef)(!1),{x:a,y:s}="boolean"==typeof o?{x:o,y:o}:o;Bt(()=>{if(!a&&!s||!t)return void(i.current=!1);if(i.current||!r)return;const e=null==t?void 0:t.node.current;if(!e||!1===e.isConnected)return;const o=En(n(e),r);if(a||(o.x=0),s||(o.y=0),i.current=!0,Math.abs(o.x)>0||Math.abs(o.y)>0){const t=Rn(e);t&&t.scrollBy({top:o.y,left:o.x})}},[t,a,s,r,n])}({activeNode:null!=S?x.get(S):null,config:H.layoutShiftCompensation,initialRect:$,measure:M.draggable.measure});const z=fr(q,M.draggable.measure,$),W=fr(q?q.parentElement:null),K=(0,wt.useRef)({activatorEvent:null,active:null,activeNode:q,collisionRect:null,collisions:null,droppableRects:U,draggableNodes:x,draggingNode:null,draggingNodeRect:null,droppableContainers:w,over:null,scrollableAncestors:[],scrollAdjustedTranslate:null}),X=w.getNodeFor(null==(t=K.current.over)?void 0:t.id),Y=function(e){let{measure:t}=e;const[n,r]=(0,wt.useState)(null),o=dr({callback:(0,wt.useCallback)(e=>{for(const{target:n}of e)if(Lt(n)){r(e=>{const r=t(n);return e?{...e,width:r.width,height:r.height}:r});break}},[t])}),i=(0,wt.useCallback)(e=>{const n=function(e){if(!e)return null;if(e.children.length>1)return e;const t=e.children[0];return Lt(t)?t:e}(e);null==o||o.disconnect(),n&&(null==o||o.observe(n)),r(n?t(n):null)},[t,o]),[a,s]=Ft(i);return(0,wt.useMemo)(()=>({nodeRef:a,rect:n,setRef:s}),[n,a,s])}({measure:M.dragOverlay.measure}),Z=null!=(n=Y.nodeRef.current)?n:q,J=T?null!=(r=Y.rect)?r:z:null,Q=Boolean(Y.nodeRef.current&&Y.rect),ee=En(te=Q?null:z,ur(te));var te;const ne=mr(Z?Dt(Z):null),re=function(e){const t=(0,wt.useRef)(e),n=Ut(n=>e?n&&n!==gr&&e&&t.current&&e.parentNode===t.current.parentNode?n:wn(e):gr,[e]);return(0,wt.useEffect)(()=>{t.current=e},[e]),n}(T?null!=X?X:q:null),oe=function(e,t){void 0===t&&(t=xn);const[n]=e,r=mr(n?Dt(n):null),[o,i]=(0,wt.useState)(vr);function a(){i(()=>e.length?e.map(e=>Ln(e)?r:new Fn(t(e),e)):vr)}const s=dr({callback:a});return Bt(()=>{null==s||s.disconnect(),a(),e.forEach(e=>null==s?void 0:s.observe(e))},[e]),o}(re),ie=function(e,t){let{transform:n,...r}=t;return null!=e&&e.length?e.reduce((e,t)=>t({transform:e,...r}),n):n}(p,{transform:{x:C.x-ee.x,y:C.y-ee.y,scaleX:1,scaleY:1},activatorEvent:O,active:A,activeNodeRect:z,containerNodeRect:W,draggingNodeRect:J,over:K.current.over,overlayNodeRect:Y.rect,scrollableAncestors:re,scrollableAncestorRects:oe,windowRect:ne}),ae=G?$t(G,C):null,se=function(e){const[t,n]=(0,wt.useState)(null),r=(0,wt.useRef)(e),o=(0,wt.useCallback)(e=>{const t=An(e.target);t&&n(e=>e?(e.set(t,Dn(t)),new Map(e)):null)},[]);return(0,wt.useEffect)(()=>{const t=r.current;if(e!==t){i(t);const a=e.map(e=>{const t=An(e);return t?(t.addEventListener("scroll",o,{passive:!0}),[t,Dn(t)]):null}).filter(e=>null!=e);n(a.length?new Map(a):null),r.current=e}return()=>{i(e),i(t)};function i(e){e.forEach(e=>{const t=An(e);null==t||t.removeEventListener("scroll",o)})}},[o,e]),(0,wt.useMemo)(()=>e.length?t?Array.from(t.values()).reduce((e,t)=>$t(e,t),cn):jn(e):cn,[e,t])}(re),le=hr(se),ce=hr(se,[z]),ue=$t(ie,le),de=J?Tn(J,ie):null,pe=A&&de?u({active:A,collisionRect:de,droppableRects:U,droppableContainers:B,pointerCoordinates:ae}):null,fe=gn(pe,"id"),[ge,he]=(0,wt.useState)(null),me=function(e,t,n){return{...e,scaleX:t&&n?t.width/n.width:1,scaleY:t&&n?t.height/n.height:1}}(Q?ie:$t(ie,ce),null!=(o=null==ge?void 0:ge.rect)?o:null,z),ve=(0,wt.useRef)(null),be=(0,wt.useCallback)((e,t)=>{let{sensor:n,options:r}=t;if(null==I.current)return;const o=x.get(I.current);if(!o)return;const i=e.nativeEvent,a=new n({active:I.current,activeNode:o,event:i,options:r,context:K,onAbort(e){if(!x.get(e))return;const{onDragAbort:t}=N.current,n={id:e};null==t||t(n),v({type:"onDragAbort",event:n})},onPending(e,t,n,r){if(!x.get(e))return;const{onDragPending:o}=N.current,i={id:e,constraint:t,initialCoordinates:n,offset:r};null==o||o(i),v({type:"onDragPending",event:i})},onStart(e){const t=I.current;if(null==t)return;const n=x.get(t);if(!n)return;const{onDragStart:r}=N.current,o={activatorEvent:i,active:{id:t,data:n.data,rect:R}};(0,Rt.unstable_batchedUpdates)(()=>{null==r||r(o),y(kr.Initializing),m({type:an.DragStart,initialCoordinates:e,active:t}),v({type:"onDragStart",event:o}),D(ve.current),L(i)})},onMove(e){m({type:an.DragMove,coordinates:e})},onEnd:s(an.DragEnd),onCancel:s(an.DragCancel)});function s(e){return async function(){const{active:t,collisions:n,over:r,scrollAdjustedTranslate:o}=K.current;let a=null;if(t&&o){const{cancelDrop:s}=N.current;a={activatorEvent:i,active:t,collisions:n,delta:o,over:r},e===an.DragEnd&&"function"==typeof s&&await Promise.resolve(s(a))&&(e=an.DragCancel)}I.current=null,(0,Rt.unstable_batchedUpdates)(()=>{m({type:e}),y(kr.Uninitialized),he(null),D(null),L(null),ve.current=null;const t=e===an.DragEnd?"onDragEnd":"onDragCancel";if(a){const e=N.current[t];null==e||e(a),v({type:t,event:a})}})}}ve.current=a},[x]),Ee=(0,wt.useCallback)((e,t)=>(n,r)=>{const o=n.nativeEvent,i=x.get(r);if(null!==I.current||!i||o.dndKit||o.defaultPrevented)return;const a={active:i};!0===e(n,t.options,a)&&(o.dndKit={capturedBy:t.sensor},I.current=r,be(n,t))},[x,be]),ye=function(e,t){return(0,wt.useMemo)(()=>e.reduce((e,n)=>{const{sensor:r}=n;return[...e,...r.activators.map(e=>({eventName:e.eventName,handler:t(e.handler,n)}))]},[]),[e,t])}(c,Ee);!function(e){(0,wt.useEffect)(()=>{if(!At)return;const t=e.map(e=>{let{sensor:t}=e;return null==t.setup?void 0:t.setup()});return()=>{for(const e of t)null==e||e()}},e.map(e=>{let{sensor:t}=e;return t}))}(c),Bt(()=>{z&&E===kr.Initializing&&y(kr.Initialized)},[z,E]),(0,wt.useEffect)(()=>{const{onDragMove:e}=N.current,{active:t,activatorEvent:n,collisions:r,over:o}=K.current;if(!t||!n)return;const i={active:t,activatorEvent:n,collisions:r,delta:{x:ue.x,y:ue.y},over:o};(0,Rt.unstable_batchedUpdates)(()=>{null==e||e(i),v({type:"onDragMove",event:i})})},[ue.x,ue.y]),(0,wt.useEffect)(()=>{const{active:e,activatorEvent:t,collisions:n,droppableContainers:r,scrollAdjustedTranslate:o}=K.current;if(!e||null==I.current||!t||!o)return;const{onDragOver:i}=N.current,a=r.get(fe),s=a&&a.rect.current?{id:a.id,rect:a.rect.current,data:a.data,disabled:a.disabled}:null,l={active:e,activatorEvent:t,collisions:n,delta:{x:o.x,y:o.y},over:s};(0,Rt.unstable_batchedUpdates)(()=>{he(s),null==i||i(l),v({type:"onDragOver",event:l})})},[fe]),Bt(()=>{K.current={activatorEvent:O,active:A,activeNode:q,collisionRect:de,collisions:pe,droppableRects:U,draggableNodes:x,draggingNode:Z,draggingNodeRect:J,droppableContainers:w,over:ge,scrollableAncestors:re,scrollAdjustedTranslate:ue},R.current={initial:J,translated:de}},[A,q,pe,de,x,Z,J,U,w,ge,re,ue]),function(e){let{acceleration:t,activator:n=or.Pointer,canScroll:r,draggingRect:o,enabled:i,interval:a=5,order:s=ir.TreeOrder,pointerCoordinates:l,scrollableAncestors:c,scrollableAncestorRects:u,delta:d,threshold:p}=e;const f=function(e){let{delta:t,disabled:n}=e;const r=Vt(t);return Ut(e=>{if(n||!r||!e)return ar;const o=Math.sign(t.x-r.x),i=Math.sign(t.y-r.y);return{x:{[On.Backward]:e.x[On.Backward]||-1===o,[On.Forward]:e.x[On.Forward]||1===o},y:{[On.Backward]:e.y[On.Backward]||-1===i,[On.Forward]:e.y[On.Forward]||1===i}}},[n,t,r])}({delta:d,disabled:!i}),[g,h]=function(){const e=(0,wt.useRef)(null);return[(0,wt.useCallback)((t,n)=>{e.current=setInterval(t,n)},[]),(0,wt.useCallback)(()=>{null!==e.current&&(clearInterval(e.current),e.current=null)},[])]}(),m=(0,wt.useRef)({x:0,y:0}),v=(0,wt.useRef)({x:0,y:0}),b=(0,wt.useMemo)(()=>{switch(n){case or.Pointer:return l?{top:l.y,bottom:l.y,left:l.x,right:l.x}:null;case or.DraggableRect:return o}},[n,o,l]),E=(0,wt.useRef)(null),y=(0,wt.useCallback)(()=>{const e=E.current;if(!e)return;const t=m.current.x*v.current.x,n=m.current.y*v.current.y;e.scrollBy(t,n)},[]),T=(0,wt.useMemo)(()=>s===ir.TreeOrder?[...c].reverse():c,[s,c]);(0,wt.useEffect)(()=>{if(i&&c.length&&b){for(const e of T){if(!1===(null==r?void 0:r(e)))continue;const n=c.indexOf(e),o=u[n];if(!o)continue;const{direction:i,speed:s}=Bn(e,o,b,t,p);for(const e of["x","y"])f[e][i[e]]||(s[e]=0,i[e]=0);if(s.x>0||s.y>0)return h(),E.current=e,g(y,a),m.current=s,void(v.current=i)}m.current={x:0,y:0},v.current={x:0,y:0},h()}else h()},[t,y,r,h,i,a,JSON.stringify(b),JSON.stringify(f),g,c,T,u,JSON.stringify(p)])}({...H,delta:C,draggingRect:de,pointerCoordinates:ae,scrollableAncestors:re,scrollableAncestorRects:oe});const Te=(0,wt.useMemo)(()=>({active:A,activeNode:q,activeNodeRect:z,activatorEvent:O,collisions:pe,containerNodeRect:W,dragOverlay:Y,draggableNodes:x,droppableContainers:w,droppableRects:U,over:ge,measureDroppableContainers:F,scrollableAncestors:re,scrollableAncestorRects:oe,measuringConfiguration:M,measuringScheduled:V,windowRect:ne}),[A,q,z,O,pe,W,Y,x,w,U,ge,F,re,oe,M,V,ne]),Se=(0,wt.useMemo)(()=>({activatorEvent:O,activators:ye,active:A,activeNodeRect:z,ariaDescribedById:{draggable:P},dispatch:m,draggableNodes:x,over:ge,measureDroppableContainers:F}),[O,ye,A,z,m,P,x,ge,F]);return _t().createElement(tn.Provider,{value:b},_t().createElement(Cr.Provider,{value:Se},_t().createElement(wr.Provider,{value:Te},_t().createElement(Ir.Provider,{value:me},l)),_t().createElement(Ar,{disabled:!1===(null==a?void 0:a.restoreFocus)})),_t().createElement(on,{...a,hiddenTextDescribedById:P}))}),Or=(0,wt.createContext)(null),Lr="button";const Nr={timeout:25};function Pr(e,t,n){const r=e.slice();return r.splice(n<0?r.length+n:n,0,r.splice(t,1)[0]),r}function Br(e,t){return e.reduce((e,n,r)=>{const o=t.get(n);return o&&(e[r]=o),e},Array(e.length))}function Mr(e){return null!==e&&e>=0}const jr=e=>{let{rects:t,activeIndex:n,overIndex:r,index:o}=e;const i=Pr(t,r,n),a=t[o],s=i[o];return s&&a?{x:s.left-a.left,y:s.top-a.top,scaleX:s.width/a.width,scaleY:s.height/a.height}:null},Ur={scaleX:1,scaleY:1},Fr=e=>{var t;let{activeIndex:n,activeNodeRect:r,index:o,rects:i,overIndex:a}=e;const s=null!=(t=i[n])?t:r;if(!s)return null;if(o===n){const e=i[a];return e?{x:0,y:nn&&o<=a?{x:0,y:-s.height-l,...Ur}:o=a?{x:0,y:s.height+l,...Ur}:{x:0,y:0,...Ur}},Vr="Sortable",qr=_t().createContext({activeIndex:-1,containerId:Vr,disableTransforms:!1,items:[],overIndex:-1,useDragOverlay:!1,sortedRects:[],strategy:jr,disabled:{draggable:!1,droppable:!1}});function Gr(e){let{children:t,id:n,items:r,strategy:o=jr,disabled:i=!1}=e;const{active:a,dragOverlay:s,droppableRects:l,over:c,measureDroppableContainers:u}=(0,wt.useContext)(wr),d=Gt(Vr,n),p=Boolean(null!==s.rect),f=(0,wt.useMemo)(()=>r.map(e=>"object"==typeof e&&"id"in e?e.id:e),[r]),g=null!=a,h=a?f.indexOf(a.id):-1,m=c?f.indexOf(c.id):-1,v=(0,wt.useRef)(f),b=!function(e,t){if(e===t)return!0;if(e.length!==t.length)return!1;for(let n=0;n{b&&g&&u(f)},[b,f,g,u]),(0,wt.useEffect)(()=>{v.current=f},[f]);const T=(0,wt.useMemo)(()=>({activeIndex:h,containerId:d,disabled:y,disableTransforms:E,items:f,overIndex:m,useDragOverlay:p,sortedRects:Br(f,l),strategy:o}),[h,d,y.draggable,y.droppable,E,f,m,l,p,o]);return _t().createElement(qr.Provider,{value:T},t)}const Hr=e=>{let{id:t,items:n,activeIndex:r,overIndex:o}=e;return Pr(n,r,o).indexOf(t)},$r=e=>{let{containerId:t,isSorting:n,wasDragging:r,index:o,items:i,newIndex:a,previousItems:s,previousContainerId:l,transition:c}=e;return!(!c||!r||s!==i&&o===a||!n&&(a===o||t!==l))},zr={duration:200,easing:"ease"},Wr="transform",Kr=Xt.Transition.toString({property:Wr,duration:0,easing:"linear"}),Xr={roleDescription:"sortable"};function Yr(e){let{animateLayoutChanges:t=$r,attributes:n,disabled:r,data:o,getNewIndex:i=Hr,id:a,strategy:s,resizeObserverConfig:l,transition:c=zr}=e;const{items:u,containerId:d,activeIndex:p,disabled:f,disableTransforms:g,sortedRects:h,overIndex:m,useDragOverlay:v,strategy:b}=(0,wt.useContext)(qr),E=function(e,t){var n,r;return"boolean"==typeof e?{draggable:e,droppable:!1}:{draggable:null!=(n=null==e?void 0:e.draggable)?n:t.draggable,droppable:null!=(r=null==e?void 0:e.droppable)?r:t.droppable}}(r,f),y=u.indexOf(a),T=(0,wt.useMemo)(()=>({sortable:{containerId:d,index:y,items:u},...o}),[d,o,y,u]),S=(0,wt.useMemo)(()=>u.slice(u.indexOf(a)),[u,a]),{rect:x,node:C,isOver:w,setNodeRef:_}=function(e){let{data:t,disabled:n=!1,id:r,resizeObserverConfig:o}=e;const i=Gt("Droppable"),{active:a,dispatch:s,over:l,measureDroppableContainers:c}=(0,wt.useContext)(Cr),u=(0,wt.useRef)({disabled:n}),d=(0,wt.useRef)(!1),p=(0,wt.useRef)(null),f=(0,wt.useRef)(null),{disabled:g,updateMeasurementsFor:h,timeout:m}={...Nr,...o},v=jt(null!=h?h:r),b=dr({callback:(0,wt.useCallback)(()=>{d.current?(null!=f.current&&clearTimeout(f.current),f.current=setTimeout(()=>{c(Array.isArray(v.current)?v.current:[v.current]),f.current=null},m)):d.current=!0},[m]),disabled:g||!a}),E=(0,wt.useCallback)((e,t)=>{b&&(t&&(b.unobserve(t),d.current=!1),e&&b.observe(e))},[b]),[y,T]=Ft(E),S=jt(t);return(0,wt.useEffect)(()=>{b&&y.current&&(b.disconnect(),d.current=!1,b.observe(y.current))},[y,b]),(0,wt.useEffect)(()=>(s({type:an.RegisterDroppable,element:{id:r,key:i,disabled:n,node:y,rect:p,data:S}}),()=>s({type:an.UnregisterDroppable,key:i,id:r})),[r]),(0,wt.useEffect)(()=>{n!==u.current.disabled&&(s({type:an.SetDroppableDisabled,id:r,key:i,disabled:n}),u.current.disabled=n)},[r,i,n,s]),{active:a,rect:p,isOver:(null==l?void 0:l.id)===r,node:y,over:l,setNodeRef:T}}({id:a,data:T,disabled:E.droppable,resizeObserverConfig:{updateMeasurementsFor:S,...l}}),{active:R,activatorEvent:A,activeNodeRect:I,attributes:k,setNodeRef:D,listeners:O,isDragging:L,over:N,setActivatorNodeRef:P,transform:B}=function(e){let{id:t,data:n,disabled:r=!1,attributes:o}=e;const i=Gt("Draggable"),{activators:a,activatorEvent:s,active:l,activeNodeRect:c,ariaDescribedById:u,draggableNodes:d,over:p}=(0,wt.useContext)(Cr),{role:f=Lr,roleDescription:g="draggable",tabIndex:h=0}=null!=o?o:{},m=(null==l?void 0:l.id)===t,v=(0,wt.useContext)(m?Ir:Or),[b,E]=Ft(),[y,T]=Ft(),S=function(e,t){return(0,wt.useMemo)(()=>e.reduce((e,n)=>{let{eventName:r,handler:o}=n;return e[r]=e=>{o(e,t)},e},{}),[e,t])}(a,t),x=jt(n);return Bt(()=>(d.set(t,{id:t,key:i,node:b,activatorNode:y,data:x}),()=>{const e=d.get(t);e&&e.key===i&&d.delete(t)}),[d,t]),{active:l,activatorEvent:s,activeNodeRect:c,attributes:(0,wt.useMemo)(()=>({role:f,tabIndex:h,"aria-disabled":r,"aria-pressed":!(!m||f!==Lr)||void 0,"aria-roledescription":g,"aria-describedby":u.draggable}),[r,f,h,m,g,u.draggable]),isDragging:m,listeners:r?void 0:S,node:b,over:p,setNodeRef:E,setActivatorNodeRef:T,transform:v}}({id:a,data:T,attributes:{...Xr,...n},disabled:E.draggable}),M=function(){for(var e=arguments.length,t=new Array(e),n=0;ne=>{t.forEach(t=>t(e))},t)}(_,D),j=Boolean(R),U=j&&!g&&Mr(p)&&Mr(m),F=!v&&L,V=F&&U?B:null,q=U?null!=V?V:(null!=s?s:b)({rects:h,activeNodeRect:I,activeIndex:p,overIndex:m,index:y}):null,G=Mr(p)&&Mr(m)?i({id:a,items:u,activeIndex:p,overIndex:m}):y,H=null==R?void 0:R.id,$=(0,wt.useRef)({activeId:H,items:u,newIndex:G,containerId:d}),z=u!==$.current.items,W=t({active:R,containerId:d,isDragging:L,isSorting:j,id:a,index:y,items:u,newIndex:$.current.newIndex,previousItems:$.current.items,previousContainerId:$.current.containerId,transition:c,wasDragging:null!=$.current.activeId}),K=function(e){let{disabled:t,index:n,node:r,rect:o}=e;const[i,a]=(0,wt.useState)(null),s=(0,wt.useRef)(n);return Bt(()=>{if(!t&&n!==s.current&&r.current){const e=o.current;if(e){const t=xn(r.current,{ignoreTransform:!0}),n={x:e.left-t.left,y:e.top-t.top,scaleX:e.width/t.width,scaleY:e.height/t.height};(n.x||n.y)&&a(n)}}n!==s.current&&(s.current=n)},[t,n,r,o]),(0,wt.useEffect)(()=>{i&&a(null)},[i]),i}({disabled:!W,index:y,node:C,rect:x});return(0,wt.useEffect)(()=>{j&&$.current.newIndex!==G&&($.current.newIndex=G),d!==$.current.containerId&&($.current.containerId=d),u!==$.current.items&&($.current.items=u)},[j,G,d,u]),(0,wt.useEffect)(()=>{if(H===$.current.activeId)return;if(null!=H&&null==$.current.activeId)return void($.current.activeId=H);const e=setTimeout(()=>{$.current.activeId=H},50);return()=>clearTimeout(e)},[H]),{active:R,activeIndex:p,attributes:k,data:T,rect:x,index:y,newIndex:G,items:u,isOver:w,isSorting:j,isDragging:L,listeners:O,node:C,overIndex:m,over:N,setNodeRef:M,setActivatorNodeRef:P,setDroppableNodeRef:_,setDraggableNodeRef:D,transform:null!=K?K:q,transition:K||z&&$.current.newIndex===y?Kr:F&&!Wt(A)||!c?void 0:j||W?Xt.Transition.toString({...c,property:Wr}):void 0}}function Zr(e){if(!e)return!1;const t=e.data.current;return!!(t&&"sortable"in t&&"object"==typeof t.sortable&&"containerId"in t.sortable&&"items"in t.sortable&&"index"in t.sortable)}const Jr=[Hn.Down,Hn.Right,Hn.Up,Hn.Left],Qr=(e,t)=>{let{context:{active:n,collisionRect:r,droppableRects:o,droppableContainers:i,over:a,scrollableAncestors:s}}=t;if(Jr.includes(e.code)){if(e.preventDefault(),!n||!r)return;const t=[];i.getEnabled().forEach(n=>{if(!n||null!=n&&n.disabled)return;const i=o.get(n.id);if(i)switch(e.code){case Hn.Down:r.topi.top&&t.push(n);break;case Hn.Left:r.left>i.left&&t.push(n);break;case Hn.Right:r.left{let{collisionRect:t,droppableRects:n,droppableContainers:r}=e;const o=fn(t),i=[];for(const e of r){const{id:t}=e,r=n.get(t);if(r){const n=fn(r),a=o.reduce((e,t,r)=>e+un(n[r],t),0),s=Number((a/4).toFixed(4));i.push({id:t,data:{droppableContainer:e,value:s}})}}return i.sort(dn)})({active:n,collisionRect:r,droppableRects:o,droppableContainers:t,pointerCoordinates:null});let d=gn(u,"id");if(d===(null==a?void 0:a.id)&&u.length>1&&(d=u[1].id),null!=d){const e=i.get(n.id),t=i.get(d),a=t?o.get(t.id):null,u=null==t?void 0:t.node.current;if(u&&a&&e&&t){const n=wn(u).some((e,t)=>s[t]!==e),o=eo(e,t),i=(c=t,!(!Zr(l=e)||!Zr(c))&&!!eo(l,c)&&l.data.current.sortable.index0&&(0,bt.jsx)("div",{className:"act-step-target-info",children:(0,bt.jsxs)("code",{children:[e.target.locators[0].value.substring(0,30),e.target.locators[0].value.length>30?"…":""]})})]}),(0,bt.jsxs)(mt.FlexItem,{className:"act-step-actions",children:[(0,bt.jsx)(mt.Button,{icon:St,label:(0,ht.__)("Edit step","admin-coach-tours"),onClick:()=>t(e),size:"small"}),(0,bt.jsx)(mt.Button,{icon:xt,label:(0,ht.__)("Delete step","admin-coach-tours"),onClick:()=>n(e.id),size:"small",isDestructive:!0})]})]})})}function no({tourId:e,steps:t=[],onEditStep:n,onAddStep:r}){const{reorderSteps:o,deleteStep:i}=(0,c.useDispatch)("admin-coach-tours"),a=function(){for(var e=arguments.length,t=new Array(e),n=0;n[...t].filter(e=>null!=e),[...t])}(ln(er,{activationConstraint:{distance:8}}),ln(Xn,{coordinateGetter:Qr})),s=(0,gt.useCallback)(n=>{const{active:r,over:i}=n;if(r.id!==i?.id){const n=t.findIndex(e=>e.id===r.id),a=t.findIndex(e=>e.id===i?.id);if(-1!==n&&-1!==a){const r=Pr(t.map(e=>e.id),n,a);o(e,r)}}},[e,t,o]),l=(0,gt.useCallback)(t=>{window.confirm((0,ht.__)("Are you sure you want to delete this step?","admin-coach-tours"))&&i(e,t)},[e,i]);if(0===t.length)return(0,bt.jsxs)("div",{className:"act-step-list-empty",children:[(0,bt.jsx)("p",{children:(0,ht.__)("No steps yet. Click the button below to add your first step.","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"primary",icon:Ct,onClick:r,children:(0,ht.__)("Add First Step","admin-coach-tours")})]});const u=[...t].sort((e,t)=>e.order-t.order);return(0,bt.jsxs)("div",{className:"act-step-list",children:[(0,bt.jsx)(Dr,{sensors:a,collisionDetection:mn,onDragEnd:s,children:(0,bt.jsx)(Gr,{items:u.map(e=>e.id),strategy:Fr,children:u.map(e=>(0,bt.jsx)(to,{step:e,onEdit:n,onDelete:l},e.id))})}),(0,bt.jsx)("div",{className:"act-step-list-footer act-button-group",children:(0,bt.jsx)(mt.Button,{variant:"secondary",icon:Ct,onClick:r,children:(0,ht.__)("Add Step","admin-coach-tours")})})]})}var ro=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m21.5 9.1-6.6-6.6-4.2 5.6c-1.2-.1-2.4.1-3.6.7-.1 0-.1.1-.2.1-.5.3-.9.6-1.2.9l3.7 3.7-5.7 5.7v1.1h1.1l5.7-5.7 3.7 3.7c.4-.4.7-.8.9-1.2.1-.1.1-.2.2-.3.6-1.1.8-2.4.6-3.6l5.6-4.1zm-7.3 3.5.1.9c.1.9 0 1.8-.4 2.6l-6-6c.8-.4 1.7-.5 2.6-.4l.9.1L15 4.9 19.1 9l-4.9 3.6z"})}),oo=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})}),io=(0,bt.jsx)(vt.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,bt.jsx)(vt.Path,{d:"M11.776 4.454a.25.25 0 01.448 0l2.069 4.192a.25.25 0 00.188.137l4.626.672a.25.25 0 01.139.426l-3.348 3.263a.25.25 0 00-.072.222l.79 4.607a.25.25 0 01-.362.263l-4.138-2.175a.25.25 0 00-.232 0l-4.138 2.175a.25.25 0 01-.363-.263l.79-4.607a.25.25 0 00-.071-.222L4.754 9.881a.25.25 0 01.139-.426l4.626-.672a.25.25 0 00.188-.137l2.069-4.192z"})});function ao(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function so(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const i=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",i),i}function lo(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function co(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function uo(e,t=document){switch(e.type){case"css":return co(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),i={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let a=[];i[n]&&(a=Array.from(t.querySelectorAll(i[n])));const s=[...o,...a];return r?s.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):s}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return co(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let i=null;if("first"===e)i=o[0]?.clientId;else if("last"===e)i=o[o.length-1]?.clientId;else if("selected"===e)i=r.getSelectedBlockClientId();else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,a=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",a.length),i=a[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);i=o[t]?.clientId}if(!i)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",i);const a=t.querySelector(`[data-block="${i}"]`);return a?(console.log("[ACT findByWpBlock] Found element:",a.tagName),[a]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function po(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&lo(e,n.withinContainer)&&(r+=10),so(e)&&(r+=100),ao(e)&&(r+=5),r}const fo="admin-coach-tours";function go({step:e,tourId:t,postType:n,onClose:r}){const[o,i]=(0,gt.useState)(e.title||""),[a,s]=(0,gt.useState)(e.content||""),[l,u]=(0,gt.useState)(e.completion?.type||"manual"),[d,p]=(0,gt.useState)(e.completion?.params||{}),[f,g]=(0,gt.useState)(!1),[h,m]=(0,gt.useState)(null),[v,b]=(0,gt.useState)(null),{aiDraft:E,isAiDrafting:y,aiDraftError:T}=(0,c.useSelect)(e=>{const t=e(fo);return{aiDraft:t.getAiDraft(),isAiDrafting:t.isAiDrafting(),aiDraftError:t.getAiDraftError()}},[]),{updateStep:S,requestAiDraft:x,clearAiDraft:C,startPicking:w}=(0,c.useDispatch)(fo),_=[{type:"clickTarget",label:"Click Target",description:"Complete when user clicks the target element",requiresTarget:!0,params:[]},{type:"domValueChanged",label:"Value Changed",description:"Complete when element value changes",requiresTarget:!0,params:[{name:"expectedValue",type:"string",optional:!0,description:"Expected value (if not set, any change completes)"},{name:"attributeName",type:"string",optional:!0,description:"Attribute to watch (defaults to value/textContent)"}]},{type:"wpData",label:"Store Change",description:"Complete when @wordpress/data store value changes",requiresTarget:!1,params:[{name:"storeName",type:"string",required:!0,description:"Store name (e.g., core/block-editor)"},{name:"selector",type:"string",required:!0,description:"Selector function name"},{name:"args",type:"array",optional:!0,description:"Arguments for selector"},{name:"expectedValue",type:"any",optional:!0,description:"Expected value"},{name:"comparator",type:"string",optional:!0,description:"equals, notEquals, truthy, falsy, contains, greaterThan, lessThan"}]},{type:"manual",label:"Manual",description:"Complete when user clicks continue button",requiresTarget:!1,params:[]},{type:"elementAppear",label:"Element Appears",description:"Complete when an element appears in DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"elementDisappear",label:"Element Disappears",description:"Complete when an element is removed from DOM",requiresTarget:!1,params:[{name:"selector",type:"string",required:!0,description:"CSS selector for element"}]},{type:"customEvent",label:"Custom Event",description:"Complete when a custom event is dispatched",requiresTarget:!1,params:[{name:"eventName",type:"string",required:!0,description:"Custom event name"}]}],R=(0,gt.useCallback)(()=>{if(e.target){const t=function(e){const t=function(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),i=o.filter(e=>!e.fallback),a=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",i.length,"primary +",a.length,"fallback locators");for(const e of[...i,...a]){let n=uo(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(ao),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(so),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>lo(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:po(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}(e);return{success:t.success,element:t.element,usedLocator:t.usedLocator,error:t.error,elementInfo:t.element?{tagName:t.element.tagName.toLowerCase(),id:t.element.id||null,className:t.element.className||null,textContent:t.element.textContent?.slice(0,50)||null,rect:t.element.getBoundingClientRect()}:null}}(e.target);b(t),setTimeout(()=>b(null),5e3)}},[e.target]),A=(0,gt.useCallback)(async()=>{g(!0),m(null);try{await S(t,e.id,{title:o.trim(),content:a.trim(),completion:{type:l,params:d}}),r()}catch(e){m(e.message||(0,ht.__)("Failed to save step.","admin-coach-tours"))}finally{g(!1)}},[t,e.id,o,a,l,d,S,r]),I=(0,gt.useCallback)(()=>{if(e.target){const t={...e.elementContext||{selector:e.target,stepId:e.id},existingTitle:o,existingContent:a};x(t,n)}},[e.id,e.target,e.elementContext,o,a,n,x]),k=(0,gt.useCallback)(()=>{E&&(E.title&&i(E.title),E.content&&s(E.content),E.suggestedCompletion&&(u(E.suggestedCompletion.type),p(E.suggestedCompletion||{})),C())},[E,C]),D=(0,gt.useCallback)(()=>{w(e.id)},[e.id,w]),O=(0,gt.useCallback)((e,t)=>{p(n=>({...n,[e]:t}))},[]),L=_.find(e=>e.type===l);return(0,bt.jsxs)("div",{className:"act-step-editor",children:[h&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!1,children:h}),(0,bt.jsx)(mt.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("Target Element","admin-coach-tours"),className:"act-step-editor-target",children:(0,bt.jsx)("div",{className:"act-target-info",children:e.target?.locators?.length>0?(0,bt.jsxs)(bt.Fragment,{children:[(0,bt.jsx)("code",{className:"act-target-selector",children:e.target.locators[0].value}),(0,bt.jsxs)(mt.Flex,{gap:2,style:{marginTop:"8px"},children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"secondary",size:"small",icon:ro,onClick:R,children:(0,ht.__)("Test","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",size:"small",onClick:D,children:(0,ht.__)("Re-pick","admin-coach-tours")})})]}),v&&(0,bt.jsx)(mt.Notice,{status:v.success?"success":"error",isDismissible:!1,className:"act-target-test-result",children:v.success?(0,ht.__)("Target found successfully!","admin-coach-tours"):(0,ht.__)("Target not found. Consider re-picking.","admin-coach-tours")})]}):(0,bt.jsx)(mt.Button,{variant:"primary",icon:ro,onClick:D,children:(0,ht.__)("Pick Target Element","admin-coach-tours")})})}),(0,bt.jsx)(mt.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,ht.__)("Step Title","admin-coach-tours"),value:o,onChange:i,placeholder:(0,ht.__)("e.g., Click the Add Block button","admin-coach-tours")}),(0,bt.jsx)(mt.TextareaControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("Step Content","admin-coach-tours"),value:a,onChange:s,placeholder:(0,ht.__)("Explain what the user should do and why…","admin-coach-tours"),rows:4}),e.target&&(0,bt.jsxs)(mt.BaseControl,{__nextHasNoMarginBottom:!0,label:(0,ht.__)("AI Assistance","admin-coach-tours"),className:"act-ai-draft-section",children:[y?(0,bt.jsxs)(mt.Flex,{align:"center",gap:2,children:[(0,bt.jsx)(mt.Spinner,{}),(0,bt.jsx)("span",{children:(0,ht.__)("Generating draft…","admin-coach-tours")})]}):E?(0,bt.jsxs)("div",{className:"act-ai-draft",children:[(0,bt.jsxs)("div",{className:"act-ai-draft-preview",children:[(0,bt.jsx)("strong",{children:E.title}),(0,bt.jsxs)("p",{children:[E.content?.substring(0,100),"…"]})]}),(0,bt.jsxs)(mt.Flex,{gap:2,children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"primary",size:"small",icon:yt,onClick:k,children:(0,ht.__)("Apply","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",size:"small",icon:oo,onClick:()=>C(),children:(0,ht.__)("Dismiss","admin-coach-tours")})})]})]}):(0,bt.jsx)(mt.Button,{variant:"secondary",icon:io,onClick:I,children:(0,ht.__)("Generate with AI","admin-coach-tours")}),T&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!1,children:T})]}),(0,bt.jsx)(mt.SelectControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:(0,ht.__)("Completion Condition","admin-coach-tours"),value:l,options:_.map(e=>({value:e.type,label:e.label})),onChange:e=>{u(e),p({})},help:L?.description}),L?.params&&L.params.length>0&&(0,bt.jsx)("div",{className:"act-completion-params",children:L.params.map(e=>(0,bt.jsx)(mt.TextControl,{__next40pxDefaultSize:!0,__nextHasNoMarginBottom:!0,label:e.name,value:d[e.name]||"",onChange:t=>O(e.name,t),help:e.description,required:e.required},e.name))}),(0,bt.jsx)("div",{className:"act-step-editor-actions",children:(0,bt.jsxs)(mt.Flex,{justify:"flex-end",gap:2,children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"tertiary",onClick:r,children:(0,ht.__)("Cancel","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)(mt.Button,{variant:"primary",onClick:A,isBusy:f,disabled:f||!o.trim(),children:(0,ht.__)("Save Step","admin-coach-tours")})})]})})]})}const ho=[/^css-/i,/^sc-/i,/^emotion-/i,/^_/i,/^jsx-/i,/^styles?__/i,/^[a-z]{1,2}[0-9]+/i,/^[0-9]/i,/^svelte-/i];function mo(e){return e.filter(e=>{return!((t=e).length<=3||ho.some(e=>e.test(t)));var t})}function vo(e){const t={},n=e.attributes;for(let e=0;ee.startsWith(t))||(t[e]=r.value)}}return t}function bo(e){if(e.getAttribute("role"))return e.getAttribute("role");const t=e.tagName.toLowerCase(),n=e.getAttribute("type"),r={button:"button",a:e.hasAttribute("href")?"link":null,input:{text:"textbox",search:"searchbox",email:"textbox",url:"textbox",tel:"textbox",password:"textbox",checkbox:"checkbox",radio:"radio",submit:"button",button:"button",reset:"button",range:"slider"},textarea:"textbox",select:"listbox",option:"option",img:e.hasAttribute("alt")?"img":null,nav:"navigation",main:"main",header:"banner",footer:"contentinfo",aside:"complementary",form:"form",h1:"heading",h2:"heading",h3:"heading",h4:"heading",h5:"heading",h6:"heading",ul:"list",ol:"list",li:"listitem",table:"table",dialog:"dialog"};return"input"===t&&r.input[n]?r.input[n]:r[t]||null}function Eo(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||null}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||null}if(e.getAttribute("title"))return e.getAttribute("title");const n=e.tagName.toLowerCase();if("button"===n||"a"===n||"button"===e.getAttribute("role")){const t=e.textContent?.trim();if(t&&t.length<100)return t}return null}function yo(e){return!(e&&!/^block-[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9-]{36}$/i.test(e)&&!/^[a-f0-9]{8,}$/i.test(e)&&!/[-_][a-f0-9]{8,}/i.test(e))}const To="admin-coach-tours",So=[".act-picker-overlay",".act-picker-highlight",".act-picker-toolbar",".edit-post-sidebar","#adminmenumain","#wpadminbar",".components-popover",".components-modal__screen-overlay",'iframe[name="editor-canvas"]'];function xo(){return document.querySelector('iframe[name="editor-canvas"]')}function Co(e){for(const t of So){if(e.matches(t))return!0;if(e.closest(t))return!0}return!1}function wo({onCancel:e}){const[t,n]=(0,gt.useState)(null),[r,o]=(0,gt.useState)(null),i=(0,gt.useRef)(null),{pickingStepId:a,currentTourId:s}=(0,c.useSelect)(e=>{const t=e(To);return{pickingStepId:t.getPickingStepId?.()||null,currentTourId:t.getCurrentTourId?.()||null}},[]),{stopPicking:l,addStep:u,updateStep:d}=(0,c.useDispatch)(To),p=(0,gt.useRef)(!1),f=(0,gt.useRef)(null),g=(0,gt.useRef)(null),h=(0,gt.useCallback)((e,t=!1)=>{if(g.current)return;g.current=setTimeout(()=>{g.current=null},16);let r=null;if(t){if(r=e.target,r&&!Co(r)){if(r===f.current)return;f.current=r,p.current=!0;const e=xo();if(e){const t=e.getBoundingClientRect(),i=r.getBoundingClientRect();n(r),o({top:t.top+i.top,left:t.left+i.left,width:i.width,height:i.height})}}}else if(r=document.elementsFromPoint(e.clientX,e.clientY).find(e=>!(e.closest(".act-picker-overlay")||Co(e)||"IFRAME"===e.tagName&&"editor-canvas"===e.name)),r&&r!==f.current){f.current=r,p.current=!1,n(r);const e=r.getBoundingClientRect();o({top:e.top,left:e.left,width:e.width,height:e.height})}},[]),m=(0,gt.useCallback)(e=>{e.preventDefault(),e.stopPropagation();const t=f.current;if(!t)return;const n=function(e,t={}){const{inEditorIframe:n=!1}=t,r=[],o={visible:!0};n&&(o.inEditorIframe=!0);const i=e.getAttribute("data-testid");i&&!yo(i)&&r.push({type:"testId",value:i,weight:100,fallback:!1}),e.id&&!yo(e.id)&&r.push({type:"css",value:`#${CSS.escape(e.id)}`,weight:95,fallback:!1});const a=bo(e),s=Eo(e);if(a){const e=s?`${a}:${s}`:a;r.push({type:"role",value:e,weight:80,fallback:!1})}const l=vo(e);for(const[e,t]of Object.entries(l)){if("testid"===e||"reactid"===e)continue;if("block"===e&&t&&/^[a-f0-9-]{36}$/i.test(t))continue;const n="type"===e?85:e.startsWith("wp-")?75:70;if(r.push({type:"dataAttribute",value:t?`${e}:${t}`:e,weight:n,fallback:!1}),r.filter(e=>"dataAttribute"===e.type).length>=2)break}const c=function(e,t=3){const n=[];let r=e,o=0;for(;r&&r!==document.body&&o0&&(t+=i.slice(0,2).map(e=>`.${CSS.escape(e)}`).join(""));const a=r.getAttribute("data-testid");if(a&&0===o)t=`[data-testid="${a}"]`;else{const n=r.getAttribute("type");n&&"input"===e&&(t+=`[type="${n}"]`);const o=r.getAttribute("name");o&&["input","select","textarea"].includes(e)&&(t+=`[name="${o}"]`)}if(r.parentElement&&0===o){const e=Array.from(r.parentElement.children).filter(e=>e.tagName===r.tagName);e.length>1&&(t+=`:nth-of-type(${e.indexOf(r)+1})`)}n.unshift(t),r=r.parentElement,o++}return n.join(" > ")}(e,3);c&&r.push({type:"css",value:c,weight:60,fallback:!1});const u=e.getAttribute("aria-label");u&&r.push({type:"ariaLabel",value:u,weight:40,fallback:!0});const d=function(e,t=5){const n=["main","nav","aside","header","footer","section","article","form","dialog"],r=["main","navigation","complementary","banner","contentinfo","region","form","dialog","search"];let o=e.parentElement,i=0;for(;o&&o!==document.body&&i0){const e=mo(Array.from(o.classList));e.length>0&&(n+=`.${CSS.escape(e[0])}`)}return{element:o,selector:n,type:t||e}}const a=["edit-post-sidebar","block-editor","editor-styles-wrapper","components-popover","components-modal","interface-interface-skeleton"];for(const e of a)if(o.classList.contains(e))return{element:o,selector:`.${e}`,type:"editor-region"};o=o.parentElement,i++}return null}(e);if(d){o.withinContainer=d.selector;const t=e.tagName.toLowerCase(),n=mo(Array.from(e.classList));let i=t;n.length>0&&(i+=`.${CSS.escape(n[0])}`),r.push({type:"contextual",value:`${d.selector} >> ${i}`,weight:50,fallback:!0});const a=e.getAttribute("data-type");a&&r.push({type:"css",value:`[data-type="${a}"]:first-of-type`,weight:45,fallback:!0})}if(0===r.length){const t=e.tagName.toLowerCase(),n=e.parentElement;if(n){const o=Array.from(n.children).indexOf(e)+1;r.push({type:"css",value:`${t}:nth-child(${o})`,weight:10,fallback:!0})}}return r.sort((e,t)=>(t.weight||50)-(e.weight||50)),{locators:r,constraints:o}}(t,{inEditorIframe:p.current}),r=function(e){const t={tagName:e.tagName.toLowerCase()},n=bo(e);n&&(t.role=n),e.id&&!/^[a-z0-9_-]{20,}$/i.test(e.id)&&(t.id=e.id);const r=mo(Array.from(e.classList));r.length>0&&(t.classNames=r.slice(0,5));const o=e.textContent?.trim();o&&o.length<=200?t.textContent=o:o&&(t.textContent=`${o.slice(0,197)}...`),e.placeholder&&(t.placeholder=e.placeholder);const i=Eo(e);i&&i!==t.textContent&&(t.label=i);const a=vo(e),s=Object.keys(a).filter(e=>e.startsWith("wp-")||"block"===e||"type"===e||"testid"===e);s.length>0&&(t.dataAttrs={},s.forEach(e=>{t.dataAttrs[e]=a[e]}));const l=[];let c=e.parentElement,u=0;for(;c&&c!==document.body&&u<3;){const e={tagName:c.tagName.toLowerCase()},t=bo(c);t&&(e.role=t),c.id&&!/^[a-z0-9_-]{20,}$/i.test(c.id)&&(e.id=c.id);const n=mo(Array.from(c.classList));n.length>0&&(e.classNames=n.slice(0,3)),l.push(e),c=c.parentElement,u++}return l.length>0&&(t.ancestors=l),t}(t),o={target:n,elementContext:r,completion:{type:"clickTarget",params:{}}};a?d(s,a,{target:n,elementContext:r}):u(s,o),l()},[a,s,u,d,l]),v=(0,gt.useCallback)(t=>{"Escape"===t.key&&(l(),e?.())},[l,e]),b=(0,gt.useRef)({handleMouseMove:h,handleClick:m,handleKeyDown:v});(0,gt.useEffect)(()=>{b.current={handleMouseMove:h,handleClick:m,handleKeyDown:v}},[h,m,v]),(0,gt.useEffect)(()=>{const e=e=>b.current.handleMouseMove(e,!1),t=e=>b.current.handleClick(e),n=e=>b.current.handleKeyDown(e),r=e=>b.current.handleMouseMove(e,!0),o=e=>{e.preventDefault(),e.stopPropagation(),b.current.handleClick(e)};document.addEventListener("mousemove",e,!0),document.addEventListener("click",t,!0),document.addEventListener("keydown",n,!0),document.body.style.overflow="hidden";let i=null,a=null;const s=()=>{const e=xo();e?.contentDocument&&e.contentDocument!==i&&(i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0)),i=e.contentDocument,i.addEventListener("mousemove",r,!0),i.addEventListener("click",o,!0),i.addEventListener("keydown",n,!0))};return s(),a=setInterval(s,500),()=>{document.removeEventListener("mousemove",e,!0),document.removeEventListener("click",t,!0),document.removeEventListener("keydown",n,!0),document.body.style.overflow="",g.current&&clearTimeout(g.current),a&&clearInterval(a),i&&(i.removeEventListener("mousemove",r,!0),i.removeEventListener("click",o,!0),i.removeEventListener("keydown",n,!0))}},[]);const E=(0,bt.jsxs)("div",{ref:i,className:"act-picker-overlay",style:{position:"fixed",top:0,left:0,right:0,bottom:0,zIndex:9999998,cursor:"crosshair",pointerEvents:"none"},children:[r?(0,bt.jsx)("div",{className:"act-picker-highlight",style:{position:"fixed",top:r.top,left:r.left,width:r.width,height:r.height,border:"3px solid #007cba",backgroundColor:"rgba(0, 124, 186, 0.15)",pointerEvents:"none",zIndex:9999999,boxSizing:"border-box",borderRadius:"3px",transition:"top 0.08s ease-out, left 0.08s ease-out, width 0.08s ease-out, height 0.08s ease-out",boxShadow:"0 0 0 2px rgba(0, 124, 186, 0.3)"}}):null,(()=>{if(!t)return null;const e=t.tagName.toLowerCase(),n=t.id,r=Array.from(t.classList).slice(0,3).join(".");let o=e;return n?o+=`#${n}`:r&&(o+=`.${r}`),(0,bt.jsx)("div",{className:"act-picker-element-info",style:{position:"fixed",bottom:"80px",left:"50%",transform:"translateX(-50%)",backgroundColor:"rgba(0, 0, 0, 0.8)",color:"#fff",padding:"8px 16px",borderRadius:"4px",fontFamily:"monospace",fontSize:"13px",maxWidth:"80%",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",zIndex:9999999},children:o})})(),(0,bt.jsxs)("div",{className:"act-picker-toolbar",style:{position:"fixed",bottom:"20px",left:"50%",transform:"translateX(-50%)",display:"flex",gap:"12px",backgroundColor:"#fff",padding:"12px 20px",borderRadius:"8px",boxShadow:"0 4px 12px rgba(0, 0, 0, 0.15)",zIndex:9999999,pointerEvents:"auto"},children:[(0,bt.jsx)("span",{style:{alignSelf:"center",fontWeight:500},children:(0,ht.__)("Click an element to select it as target","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"tertiary",icon:oo,onClick:()=>{l(),e?.()},children:(0,ht.__)("Cancel","admin-coach-tours")})]})]});return(0,gt.createPortal)(E,document.body)}const _o="admin-coach-tours";(0,l.registerPlugin)("admin-coach-tours-educator",{render:function(){const[e,t]=(0,gt.useState)(null),[n,r]=(0,gt.useState)(!1),[o,i]=(0,gt.useState)(!1),{postType:a,postId:s,postTitle:l,postStatus:u,isSaving:d}=(0,c.useSelect)(e=>{const t=e("core/editor");return{postType:t?.getCurrentPostType?.()||"",postId:t?.getCurrentPostId?.()||0,postTitle:t?.getEditedPostAttribute?.("title")||"",postStatus:t?.getEditedPostAttribute?.("status")||"draft",isSaving:t?.isSavingPost?.()||!1}},[]),{currentTour:p,selectedStep:f,isPickerActive:g,isLoading:h}=(0,c.useSelect)(e=>{const t=e(_o);return{currentTour:t.getCurrentTour(),selectedStep:t.getSelectedStep(),isPickerActive:t.isPickerActive(),isLoading:t.isToursLoading()}},[]),{setCurrentTour:m,saveTour:v,startPicking:b,stopPicking:E,selectStep:y}=(0,c.useDispatch)(_o),{enableComplementaryArea:T}=(0,c.useDispatch)("core/interface");if((0,gt.useEffect)(()=>{"act_tour"===a&&s&&s!==p?.id&&m(s)},[a,s,p?.id,m]),(0,gt.useEffect)(()=>{if("act_tour"===a){const e=setTimeout(()=>{T("core","admin-coach-tours-educator/admin-coach-tours-sidebar")},100);return()=>clearTimeout(e)}},[a,T]),"act_tour"!==a)return null;const S=(0,gt.useCallback)(()=>{b()},[b]),x=(0,gt.useCallback)(()=>{E()},[E]),C=(0,gt.useCallback)(()=>{if(p?.id){const e=(p.postTypes||["post"])[0]||"post",t=new URL(window.location.origin+"/wp-admin/post-new.php");"post"!==e&&t.searchParams.set("post_type",e),t.searchParams.set("act_tour",p.id.toString()),console.log("[ACT Educator] Opening test URL:",t.toString()),window.open(t.toString(),"_blank")}},[p?.id,p?.postTypes]);if(h&&!p)return(0,bt.jsx)(bt.Fragment,{children:(0,bt.jsx)(ft.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,ht.__)("Tour Steps","admin-coach-tours"),icon:Et,children:(0,bt.jsx)("div",{className:"act-educator-sidebar",children:(0,bt.jsx)(mt.PanelBody,{children:(0,bt.jsx)(mt.Flex,{justify:"center",style:{padding:"24px"},children:(0,bt.jsx)(mt.Spinner,{})})})})})});const w=p?.steps||[];return(0,bt.jsxs)(bt.Fragment,{children:[(0,bt.jsx)(ft.PluginSidebarMoreMenuItem,{target:"admin-coach-tours-sidebar",icon:Et,children:(0,ht.__)("Tour Steps","admin-coach-tours")}),(0,bt.jsx)(ft.PluginSidebar,{name:"admin-coach-tours-sidebar",title:(0,ht.__)("Tour Steps","admin-coach-tours"),icon:Et,children:(0,bt.jsxs)("div",{className:"act-educator-sidebar",children:[(0,bt.jsxs)(mt.PanelBody,{title:(0,ht.__)("Tour Info","admin-coach-tours"),initialOpen:!1,children:[(0,bt.jsxs)(mt.Flex,{justify:"space-between",align:"center",children:[(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)("strong",{children:l||(0,ht.__)("Untitled Tour","admin-coach-tours")})}),(0,bt.jsx)(mt.FlexItem,{children:(0,bt.jsx)("span",{className:"act-tour-status"+("publish"===u?" act-tour-status--published":""),children:"publish"===u?(0,ht.__)("Published","admin-coach-tours"):(0,ht.__)("Draft","admin-coach-tours")})})]}),(0,bt.jsx)("p",{className:"act-help-text",style:{marginTop:"8px"},children:(0,ht.__)("Use the block editor canvas as a sandbox to create your tour steps. Pick elements from the editor to target them in your tour.","admin-coach-tours")})]}),(0,bt.jsxs)(mt.PanelBody,{title:(0,ht.__)("Steps","admin-coach-tours")+` (${w.length})`,initialOpen:!0,children:[e&&(0,bt.jsx)(mt.Notice,{status:"error",isDismissible:!0,onRemove:()=>t(null),children:e}),o&&(0,bt.jsx)(mt.Notice,{status:"success",isDismissible:!1,children:(0,ht.__)("Steps saved successfully!","admin-coach-tours")}),(0,bt.jsx)(no,{tourId:s,steps:w,onEditStep:e=>y(e?.id??null),onAddStep:S}),w.length>0&&(0,bt.jsxs)("div",{className:"act-actions-footer",children:[(0,bt.jsx)(mt.Button,{variant:"primary",icon:yt,onClick:async()=>{if(p?.id){r(!0),t(null),i(!1);try{const e={steps:p.steps||[]};await v(p.id,e),i(!0),setTimeout(()=>i(!1),3e3)}catch(e){t(e.message||(0,ht.__)("Failed to save steps.","admin-coach-tours"))}finally{r(!1)}}},isBusy:n,disabled:n||d,children:n?(0,ht.__)("Saving…","admin-coach-tours"):(0,ht.__)("Save Steps","admin-coach-tours")}),(0,bt.jsx)(mt.Button,{variant:"secondary",icon:Tt,onClick:C,disabled:0===w.length,children:(0,ht.__)("Test Tour","admin-coach-tours")})]})]}),f&&(0,bt.jsx)(mt.PanelBody,{title:(0,ht.__)("Edit Step","admin-coach-tours"),initialOpen:!0,children:(0,bt.jsx)(go,{step:f,tourId:s,postType:"act_tour",onClose:()=>y(null)})})]})}),g&&(0,bt.jsx)(wo,{onCancel:x})]})},icon:null})})(); \ No newline at end of file diff --git a/build/pupil/index.asset.php b/build/pupil/index.asset.php index 10eec17..42d1a3e 100644 --- a/build/pupil/index.asset.php +++ b/build/pupil/index.asset.php @@ -1 +1 @@ - array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '108949c4cfd6402553b8'); + array('react-jsx-runtime', 'wp-api-fetch', 'wp-blocks', 'wp-components', 'wp-data', 'wp-element', 'wp-i18n', 'wp-primitives'), 'version' => '4010f53e6bfe793f350c'); diff --git a/build/pupil/index.js b/build/pupil/index.js index 699e1f8..4a61917 100644 --- a/build/pupil/index.js +++ b/build/pupil/index.js @@ -1,3 +1,3 @@ -(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var s=r[e]={exports:{}};return n[e](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var s=Object.create(null);o.r(s);var c={};e=e||[null,t({}),t([]),t(t)];for(var i=2&r&&n;("object"==typeof i||"function"==typeof i)&&!~e.indexOf(i);i=t(i))Object.getOwnPropertyNames(i).forEach(e=>c[e]=()=>n[e]);return c.default=()=>n,o.d(s,c),s},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var s={};o.r(s),o.d(s,{activatePicker:()=>Oe,addStep:()=>Ue,clearAiDraft:()=>He,clearEphemeralTour:()=>Qe,clearResolvedTarget:()=>we,createTour:()=>pe,deactivatePicker:()=>Ne,deleteStep:()=>Fe,endTour:()=>ge,fetchTour:()=>ue,fetchTours:()=>ae,incrementResolutionAttempts:()=>ve,markStepComplete:()=>ke,nextStep:()=>Ee,previousStep:()=>_e,receiveEphemeralTour:()=>Ye,receiveTour:()=>ie,receiveTours:()=>ce,reorderSteps:()=>qe,repeatStep:()=>Se,requestAiDraft:()=>We,requestAiTour:()=>Je,resetCompletion:()=>Ce,saveTour:()=>de,selectStep:()=>Be,setAiDraftError:()=>Ve,setAiDraftLoading:()=>Me,setAiDraftResult:()=>Ge,setAiTourError:()=>Ke,setAiTourLoading:()=>ze,setCompletionSatisfied:()=>Ae,setCurrentStep:()=>fe,setCurrentTour:()=>le,setLastError:()=>xe,setLastFailureContext:()=>Xe,setMode:()=>be,setPendingChanges:()=>De,setRecovering:()=>Re,setResolvedTarget:()=>Ie,setSidebarOpen:()=>$e,setToursError:()=>se,setToursLoading:()=>oe,skipStep:()=>ye,startEphemeralTour:()=>Ze,startPicking:()=>Pe,startTour:()=>he,stopPicking:()=>Le,stopTour:()=>Te,updateStep:()=>je,updateTour:()=>me});var c={};o.r(c),o.d(c,{getAiDraft:()=>Bt,getAiDraftError:()=>Nt,getAiDraftResult:()=>Lt,getAiTourError:()=>Ut,getCurrentStep:()=>ut,getCurrentStepIndex:()=>at,getCurrentTour:()=>lt,getCurrentTourId:()=>it,getEphemeralTour:()=>qt,getLastError:()=>kt,getLastFailureContext:()=>Ft,getMode:()=>gt,getPickingStepId:()=>wt,getProgress:()=>ht,getResolutionAttempts:()=>Ct,getResolvedTarget:()=>bt,getSelectedStep:()=>vt,getSelectedStepId:()=>Rt,getSkippedSteps:()=>yt,getTotalSteps:()=>dt,getTour:()=>nt,getTours:()=>tt,getToursByEditor:()=>ct,getToursById:()=>et,getToursByPostType:()=>st,getToursError:()=>ot,hasNextStep:()=>pt,hasPendingChanges:()=>xt,hasPreviousStep:()=>mt,isAiDraftLoading:()=>Ot,isAiDrafting:()=>Pt,isAiTourLoading:()=>jt,isCompletionSatisfied:()=>_t,isEducatorMode:()=>Tt,isEphemeralTourActive:()=>Mt,isPickerActive:()=>It,isPupilMode:()=>ft,isRecovering:()=>At,isSidebarOpen:()=>Dt,isTourActive:()=>Et,isToursLoading:()=>rt,wasStepSkipped:()=>St});var i={};o.r(i),o.d(i,{getTour:()=>Gt,getTours:()=>Vt,getToursByPostType:()=>Ht});const l=window.wp.element,a=window.wp.data,u=window.wp.i18n,d=window.wp.components,p=window.wp.primitives,m=window.ReactJSXRuntime;var h=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m14.5 6.5-1 1 3.7 3.7H4v1.6h13.2l-3.7 3.7 1 1 5.6-5.5z"})}),g=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})});function T({step:e,stepIndex:t,totalSteps:n,tourTitle:r,targetElement:o,resolutionError:s,expectedBlockType:c,isApplyingPreconditions:i,onContinue:a,onRepeat:p,onPrevious:T,onNext:f,onStop:E}){const[_,y]=(0,l.useState)({x:20,y:20}),[S,b]=(0,l.useState)(!1),[A,C]=(0,l.useState)({x:0,y:0}),[k,I]=(0,l.useState)(0),w=(0,l.useRef)(null),R=t===n-1,v="manual"===e?.completion?.type;(0,l.useEffect)(()=>{if(!o||S)return;const e=()=>{if(!o?.isConnected)return;const e=o.getBoundingClientRect();let t={top:0,left:0};if(o.ownerDocument!==document){const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const n=e.getBoundingClientRect();t={top:n.top,left:n.left}}}const n={top:e.top+t.top,bottom:e.bottom+t.top,left:e.left+t.left,right:e.right+t.left},r=320,s=200,c=20;let i,l;n.right+r+c0?(i=n.left-r-c,l=n.top):n.bottom+s+c0?(i=Math.max(c,n.left),l=n.top-s-c):(i=window.innerWidth-r-c,l=window.innerHeight-s-c),i=Math.max(c,Math.min(i,window.innerWidth-r-c)),l=Math.max(c,Math.min(l,window.innerHeight-s-c)),y({x:i,y:l})};e();const t=()=>{S||e()};window.addEventListener("scroll",t,{passive:!0}),window.addEventListener("resize",t,{passive:!0});const n=o.ownerDocument,r=n?.defaultView;return r&&r!==window&&r.addEventListener("scroll",t,{passive:!0}),()=>{if(window.removeEventListener("scroll",t),window.removeEventListener("resize",t),r&&r!==window)try{r.removeEventListener("scroll",t)}catch(e){}}},[o,S,k]);const x=(0,l.useCallback)(e=>{if(e.target.closest("button"))return;b(!0);const t=w.current.getBoundingClientRect();C({x:e.clientX-t.left,y:e.clientY-t.top})},[]),O=(0,l.useCallback)(e=>{if(!S)return;const t=e.clientX-A.x,n=e.clientY-A.y,r=w.current.getBoundingClientRect(),o=window.innerWidth-r.width,s=window.innerHeight-r.height;y({x:Math.max(0,Math.min(t,o)),y:Math.max(0,Math.min(n,s))})},[S,A]),P=(0,l.useCallback)(()=>{b(!1)},[]);return(0,l.useEffect)(()=>{if(S)return document.addEventListener("mousemove",O),document.addEventListener("mouseup",P),()=>{document.removeEventListener("mousemove",O),document.removeEventListener("mouseup",P)}},[S,O,P]),(0,m.jsxs)("div",{ref:w,className:"act-coach-panel",style:{position:"fixed",top:_.y,left:_.x,width:"360px",maxHeight:"450px",backgroundColor:"#fff",borderRadius:"12px",boxShadow:"0 8px 32px rgba(0, 0, 0, 0.18)",zIndex:9999990,display:"flex",flexDirection:"column",cursor:S?"grabbing":"default"},onMouseDown:x,children:[(0,m.jsx)("div",{className:"act-panel-header",style:{padding:"16px 20px",borderBottom:"1px solid #e0e0e0",cursor:"grab",userSelect:"none",background:"linear-gradient(to bottom, #fafafa, #fff)",borderRadius:"12px 12px 0 0"},children:(0,m.jsxs)(d.Flex,{justify:"space-between",align:"flex-start",children:[(0,m.jsxs)(d.FlexBlock,{children:[(0,m.jsx)("strong",{className:"act-panel-title",style:{fontSize:"16px",lineHeight:"1.4",display:"block",marginBottom:"6px",color:"#1e1e1e"},children:e?.title||(0,u.__)("Step","admin-coach-tours")+` ${t+1}`}),(0,m.jsxs)("div",{className:"act-panel-progress",style:{fontSize:"13px",color:"#757575",fontWeight:"500"},children:[`${t+1} / ${n}`,r&&` • ${r}`]})]}),(0,m.jsx)(d.FlexItem,{children:(0,m.jsx)(d.Button,{icon:g,label:(0,u.__)("Close tour","admin-coach-tours"),onClick:E,size:"small"})})]})}),(0,m.jsx)("div",{className:"act-panel-body",style:{padding:"20px",flex:1,overflow:"auto",fontSize:"14px",lineHeight:"1.6",color:"#1e1e1e"},children:i?(0,m.jsxs)("div",{className:"act-panel-loading",children:[(0,m.jsx)(d.Spinner,{}),(0,m.jsx)("span",{children:(0,u.__)("Preparing…","admin-coach-tours")})]}):s?(0,m.jsxs)("div",{className:"act-panel-error",children:[c?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("p",{style:{marginBottom:"8px"},children:(0,m.jsx)("strong",{children:(0,u.__)("This step expects a specific block type:","admin-coach-tours")})}),(0,m.jsx)("p",{style:{marginBottom:"12px",color:"#1e1e1e"},children:(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ +(()=>{"use strict";var e,t,n={997(e){e.exports=window.wp.blocks}},r={};function o(e){var t=r[e];if(void 0!==t)return t.exports;var s=r[e]={exports:{}};return n[e](s,s.exports,o),s.exports}o.n=e=>{var t=e&&e.__esModule?()=>e.default:()=>e;return o.d(t,{a:t}),t},t=Object.getPrototypeOf?e=>Object.getPrototypeOf(e):e=>e.__proto__,o.t=function(n,r){if(1&r&&(n=this(n)),8&r)return n;if("object"==typeof n&&n){if(4&r&&n.__esModule)return n;if(16&r&&"function"==typeof n.then)return n}var s=Object.create(null);o.r(s);var c={};e=e||[null,t({}),t([]),t(t)];for(var i=2&r&&n;("object"==typeof i||"function"==typeof i)&&!~e.indexOf(i);i=t(i))Object.getOwnPropertyNames(i).forEach(e=>c[e]=()=>n[e]);return c.default=()=>n,o.d(s,c),s},o.d=(e,t)=>{for(var n in t)o.o(t,n)&&!o.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},o.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),o.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var s={};o.r(s),o.d(s,{activatePicker:()=>ve,addStep:()=>De,clearAiDraft:()=>Ve,clearEphemeralTour:()=>Xe,clearResolvedTarget:()=>ke,createTour:()=>ue,deactivatePicker:()=>Oe,deleteStep:()=>je,endTour:()=>me,fetchAiTasks:()=>Qe,fetchTour:()=>le,fetchTours:()=>ie,incrementResolutionAttempts:()=>we,markStepComplete:()=>Ae,nextStep:()=>Te,previousStep:()=>fe,receiveEphemeralTour:()=>Ke,receiveTour:()=>se,receiveTours:()=>oe,reorderSteps:()=>Ue,repeatStep:()=>_e,requestAiDraft:()=>Ge,requestAiTour:()=>Ye,resetCompletion:()=>be,saveTour:()=>ae,selectStep:()=>Ne,setAiDraftError:()=>qe,setAiDraftLoading:()=>Fe,setAiDraftResult:()=>Me,setAiTourError:()=>$e,setAiTourLoading:()=>We,setCompletionSatisfied:()=>Se,setCurrentStep:()=>ge,setCurrentTour:()=>ce,setLastError:()=>Re,setLastFailureContext:()=>ze,setMode:()=>ye,setPendingChanges:()=>Le,setRecovering:()=>Ie,setResolvedTarget:()=>Ce,setSidebarOpen:()=>He,setToursError:()=>re,setToursLoading:()=>ne,skipStep:()=>Ee,startEphemeralTour:()=>Je,startPicking:()=>xe,startTour:()=>pe,stopPicking:()=>Pe,stopTour:()=>he,updateStep:()=>Be,updateTour:()=>de});var c={};o.r(c),o.d(c,{getAiDraft:()=>Lt,getAiDraftError:()=>Pt,getAiDraftResult:()=>Nt,getAiTourError:()=>jt,getCurrentStep:()=>at,getCurrentStepIndex:()=>lt,getCurrentTour:()=>it,getCurrentTourId:()=>ct,getEphemeralTour:()=>Ft,getLastError:()=>Ct,getLastFailureContext:()=>Ut,getMode:()=>ht,getPickingStepId:()=>It,getProgress:()=>mt,getResolutionAttempts:()=>At,getResolvedTarget:()=>St,getSelectedStep:()=>Rt,getSelectedStepId:()=>wt,getSkippedSteps:()=>_t,getTotalSteps:()=>ut,getTour:()=>tt,getTours:()=>et,getToursByEditor:()=>st,getToursById:()=>Ze,getToursByPostType:()=>ot,getToursError:()=>rt,hasNextStep:()=>dt,hasPendingChanges:()=>vt,hasPreviousStep:()=>pt,isAiDraftLoading:()=>xt,isAiDrafting:()=>Ot,isAiTourLoading:()=>Dt,isCompletionSatisfied:()=>Et,isEducatorMode:()=>gt,isEphemeralTourActive:()=>qt,isPickerActive:()=>kt,isPupilMode:()=>Tt,isRecovering:()=>bt,isSidebarOpen:()=>Bt,isTourActive:()=>ft,isToursLoading:()=>nt,wasStepSkipped:()=>yt});var i={};o.r(i),o.d(i,{getTour:()=>Ht,getTours:()=>Gt,getToursByPostType:()=>Wt});const l=window.wp.element,a=window.wp.data,u=window.wp.i18n,d=window.wp.components,p=window.wp.primitives,m=window.ReactJSXRuntime;var h=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m14.5 6.5-1 1 3.7 3.7H4v1.6h13.2l-3.7 3.7 1 1 5.6-5.5z"})}),g=(0,m.jsx)(p.SVG,{xmlns:"http://www.w3.org/2000/svg",viewBox:"0 0 24 24",children:(0,m.jsx)(p.Path,{d:"m13.06 12 6.47-6.47-1.06-1.06L12 10.94 5.53 4.47 4.47 5.53 10.94 12l-6.47 6.47 1.06 1.06L12 13.06l6.47 6.47 1.06-1.06L13.06 12Z"})});function T({step:e,stepIndex:t,totalSteps:n,tourTitle:r,targetElement:o,resolutionError:s,expectedBlockType:c,isApplyingPreconditions:i,onContinue:a,onRepeat:p,onPrevious:T,onNext:f,onStop:E}){const[_,y]=(0,l.useState)({x:20,y:20}),[S,b]=(0,l.useState)(!1),[A,C]=(0,l.useState)({x:0,y:0}),[k,I]=(0,l.useState)(0),w=(0,l.useRef)(null),R=t===n-1,v="manual"===e?.completion?.type;(0,l.useEffect)(()=>{if(!o||S)return;const e=()=>{if(!o?.isConnected)return;const e=o.getBoundingClientRect();let t={top:0,left:0};if(o.ownerDocument!==document){const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const n=e.getBoundingClientRect();t={top:n.top,left:n.left}}}const n={top:e.top+t.top,bottom:e.bottom+t.top,left:e.left+t.left,right:e.right+t.left},r=320,s=200,c=20;let i,l;n.right+r+c0?(i=n.left-r-c,l=n.top):n.bottom+s+c0?(i=Math.max(c,n.left),l=n.top-s-c):(i=window.innerWidth-r-c,l=window.innerHeight-s-c),i=Math.max(c,Math.min(i,window.innerWidth-r-c)),l=Math.max(c,Math.min(l,window.innerHeight-s-c)),y({x:i,y:l})};e();const t=()=>{S||e()};window.addEventListener("scroll",t,{passive:!0}),window.addEventListener("resize",t,{passive:!0});const n=o.ownerDocument,r=n?.defaultView;return r&&r!==window&&r.addEventListener("scroll",t,{passive:!0}),()=>{if(window.removeEventListener("scroll",t),window.removeEventListener("resize",t),r&&r!==window)try{r.removeEventListener("scroll",t)}catch(e){}}},[o,S,k]);const x=(0,l.useCallback)(e=>{if(e.target.closest("button"))return;b(!0);const t=w.current.getBoundingClientRect();C({x:e.clientX-t.left,y:e.clientY-t.top})},[]),O=(0,l.useCallback)(e=>{if(!S)return;const t=e.clientX-A.x,n=e.clientY-A.y,r=w.current.getBoundingClientRect(),o=window.innerWidth-r.width,s=window.innerHeight-r.height;y({x:Math.max(0,Math.min(t,o)),y:Math.max(0,Math.min(n,s))})},[S,A]),P=(0,l.useCallback)(()=>{b(!1)},[]);return(0,l.useEffect)(()=>{if(S)return document.addEventListener("mousemove",O),document.addEventListener("mouseup",P),()=>{document.removeEventListener("mousemove",O),document.removeEventListener("mouseup",P)}},[S,O,P]),(0,m.jsxs)("div",{ref:w,className:"act-coach-panel",style:{position:"fixed",top:_.y,left:_.x,width:"360px",maxHeight:"450px",backgroundColor:"#fff",borderRadius:"12px",boxShadow:"0 8px 32px rgba(0, 0, 0, 0.18)",zIndex:9999990,display:"flex",flexDirection:"column",cursor:S?"grabbing":"default"},onMouseDown:x,children:[(0,m.jsx)("div",{className:"act-panel-header",style:{padding:"16px 20px",borderBottom:"1px solid #e0e0e0",cursor:"grab",userSelect:"none",background:"linear-gradient(to bottom, #fafafa, #fff)",borderRadius:"12px 12px 0 0"},children:(0,m.jsxs)(d.Flex,{justify:"space-between",align:"flex-start",children:[(0,m.jsxs)(d.FlexBlock,{children:[(0,m.jsx)("strong",{className:"act-panel-title",style:{fontSize:"16px",lineHeight:"1.4",display:"block",marginBottom:"6px",color:"#1e1e1e"},children:e?.title||(0,u.__)("Step","admin-coach-tours")+` ${t+1}`}),(0,m.jsxs)("div",{className:"act-panel-progress",style:{fontSize:"13px",color:"#757575",fontWeight:"500"},children:[`${t+1} / ${n}`,r&&` • ${r}`]})]}),(0,m.jsx)(d.FlexItem,{children:(0,m.jsx)(d.Button,{icon:g,label:(0,u.__)("Close tour","admin-coach-tours"),onClick:E,size:"small"})})]})}),(0,m.jsx)("div",{className:"act-panel-body",style:{padding:"20px",flex:1,overflow:"auto",fontSize:"14px",lineHeight:"1.6",color:"#1e1e1e"},children:i?(0,m.jsxs)("div",{className:"act-panel-loading",children:[(0,m.jsx)(d.Spinner,{}),(0,m.jsx)("span",{children:(0,u.__)("Preparing…","admin-coach-tours")})]}):s?(0,m.jsxs)("div",{className:"act-panel-error",children:[c?(0,m.jsxs)(m.Fragment,{children:[(0,m.jsx)("p",{style:{marginBottom:"8px"},children:(0,m.jsx)("strong",{children:(0,u.__)("This step expects a specific block type:","admin-coach-tours")})}),(0,m.jsx)("p",{style:{marginBottom:"12px",color:"#1e1e1e"},children:(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ (0,u.__)("Please insert or select a %s block first, then try again.","admin-coach-tours"),c)})]}):(0,m.jsx)("p",{style:{marginBottom:"12px"},children:(0,u.__)("Could not find the target element. The UI may have changed.","admin-coach-tours")}),(0,m.jsx)(d.Button,{variant:"secondary",onClick:p,children:(0,u.__)("Try Again","admin-coach-tours")})]}):(0,m.jsx)(m.Fragment,{children:e?.content&&(0,m.jsx)("div",{className:"act-panel-content",dangerouslySetInnerHTML:{__html:e.content}})})}),(0,m.jsx)("div",{className:"act-panel-footer",style:{padding:"14px 20px",borderTop:"1px solid #e0e0e0",background:"#fafafa",borderRadius:"0 0 12px 12px"},children:(0,m.jsx)("div",{className:"act-panel-controls",children:(0,m.jsx)(d.Flex,{justify:"flex-end",align:"center",children:(0,m.jsx)(d.FlexItem,{children:v||R?(0,m.jsx)(d.Button,{variant:"primary",onClick:a,disabled:i||!!s,size:"small",children:R?(0,u.__)("Finish","admin-coach-tours"):(0,u.__)("Continue","admin-coach-tours")}):(0,m.jsx)(d.Button,{icon:h,label:(0,u.__)("Next","admin-coach-tours"),onClick:f,disabled:i||!!s,size:"small"})})})})})]})}const f={boxShadow:"0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5)",borderRadius:"4px",transition:"all 0.3s ease"};class E{constructor(e={}){this.options={usePulse:!0,overlayColor:"rgba(0, 0, 0, 0.5)",highlightColor:"#007cba",transitionDuration:300,...e},this.spotlightElement=null,this.targetElement=null,this.resizeObserver=null,this.styleElement=null,this._isAnimating=!1,this._init()}_init(){this.styleElement=document.createElement("style"),this.styleElement.textContent="\n@keyframes act-pulse {\n\t0% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t50% {\n\t\tbox-shadow: 0 0 0 8px rgba(0, 124, 186, 0.5), 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n\t100% {\n\t\tbox-shadow: 0 0 0 4px #007cba, 0 0 0 9999px rgba(0, 0, 0, 0.5);\n\t}\n}\n",document.head.appendChild(this.styleElement),this.spotlightElement=document.createElement("div"),this.spotlightElement.className="act-highlighter-spotlight",this.spotlightElement.setAttribute("aria-hidden","true"),Object.assign(this.spotlightElement.style,{position:"fixed",pointerEvents:"none",zIndex:9999980,...f}),this.spotlightElement.style.display="none",document.body.appendChild(this.spotlightElement),this.resizeObserver=new ResizeObserver(()=>{this._updatePosition()})}highlight(e){e&&e.isConnected?this.targetElement!==e&&(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,this.resizeObserver.observe(e),this._updatePosition(),this.spotlightElement.style.display="block",this.options.usePulse&&!this._isAnimating&&(this.spotlightElement.style.animation="act-pulse 2s infinite",this._isAnimating=!0),this._addScrollListeners()):this.clear()}_updatePosition(){if(!this.targetElement||!this.spotlightElement)return;const e=this.targetElement.getBoundingClientRect();let t={top:0,left:0};const n=this.targetElement.ownerDocument;if(n!==document){const e=document.querySelectorAll("iframe");for(const r of e)if(r.contentDocument===n){const e=r.getBoundingClientRect();t={top:e.top,left:e.left};break}}Object.assign(this.spotlightElement.style,{top:e.top+t.top-4+"px",left:e.left+t.left-4+"px",width:`${e.width+8}px`,height:`${e.height+8}px`})}_addScrollListeners(){this._scrollHandler=()=>{this._updatePosition()},this._scrollWindows=[];let e=this.targetElement?.parentElement;for(;e;)e.scrollHeight>e.clientHeight&&e.addEventListener("scroll",this._scrollHandler,{passive:!0}),e=e.parentElement;const t=this.targetElement?.ownerDocument,n=t?.defaultView;n&&n!==window&&(n.addEventListener("scroll",this._scrollHandler,{passive:!0}),n.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(n)),window.addEventListener("scroll",this._scrollHandler,{passive:!0}),window.addEventListener("resize",this._scrollHandler,{passive:!0}),this._scrollWindows.push(window)}_removeScrollListeners(){if(!this._scrollHandler)return;let e=this.targetElement?.parentElement;for(;e;)e.removeEventListener("scroll",this._scrollHandler),e=e.parentElement;if(this._scrollWindows){for(const e of this._scrollWindows)try{e.removeEventListener("scroll",this._scrollHandler),e.removeEventListener("resize",this._scrollHandler)}catch(e){}this._scrollWindows=[]}this._scrollHandler=null}clear(){this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners(),this.targetElement=null),this.spotlightElement&&(this.spotlightElement.style.display="none",this.spotlightElement.style.animation="none",this._isAnimating=!1)}async transitionTo(e){this.targetElement?(this.targetElement&&(this.resizeObserver.unobserve(this.targetElement),this._removeScrollListeners()),this.targetElement=e,e&&e.isConnected?(this.resizeObserver.observe(e),this._addScrollListeners(),await new Promise(e=>{requestAnimationFrame(()=>{this._updatePosition(),setTimeout(e,this.options.transitionDuration)})})):this.clear()):this.highlight(e)}setStyle(e){if(!this.spotlightElement)return;const t={default:"#007cba",success:"#00a32a",warning:"#dba617",error:"#d63638"},n=t[e]||t.default;this.spotlightElement.style.boxShadow=`0 0 0 4px ${n}, 0 0 0 9999px rgba(0, 0, 0, 0.5)`}async flash(){this.spotlightElement&&(this.setStyle("success"),await new Promise(e=>{setTimeout(()=>{this.setStyle("default"),e()},300)}))}destroy(){this.clear(),this.resizeObserver&&(this.resizeObserver.disconnect(),this.resizeObserver=null),this.spotlightElement&&this.spotlightElement.parentNode&&(this.spotlightElement.parentNode.removeChild(this.spotlightElement),this.spotlightElement=null),this.styleElement&&this.styleElement.parentNode&&(this.styleElement.parentNode.removeChild(this.styleElement),this.styleElement=null)}}function _(e){if(!e)return!1;if(!e.isConnected)return!1;const t=window.getComputedStyle(e);if("none"===t.display||"hidden"===t.visibility||"0"===t.opacity)return!1;const n=e.getBoundingClientRect();return 0!==n.width||0!==n.height}function y(e){const t=window.wp?.data;if(!t)return console.log("[ACT isWithinSelectedBlock] wp.data not available"),!0;const n=t.select("core/block-editor");if(!n)return console.log("[ACT isWithinSelectedBlock] core/block-editor not available"),!0;let r=n.getSelectedBlockClientId();if(console.log("[ACT isWithinSelectedBlock] Selected block clientId:",r),!r&&window.__actLastAppearedBlockClientId&&(r=window.__actLastAppearedBlockClientId,console.log("[ACT isWithinSelectedBlock] Using last appeared block:",r)),!r)return console.log("[ACT isWithinSelectedBlock] No block to scope to, allowing element"),!0;const o=(e.ownerDocument||document).querySelector(`[data-block="${r}"]`);if(!o)return console.log("[ACT isWithinSelectedBlock] Target block element not found in DOM"),!0;const s=o.contains(e);return console.log("[ACT isWithinSelectedBlock] Element within target block:",s),s}function S(e,t){if(!t)return!0;const n=(e.ownerDocument||document).querySelector(t);return!!n&&n.contains(e)}function b(e,t=document){try{return Array.from(t.querySelectorAll(e))}catch{return[]}}function A(e,t=document){switch(e.type){case"css":return b(e.value,t);case"role":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=Array.from(t.querySelectorAll(`[role="${n}"]`)),s={button:'button, input[type="button"], input[type="submit"]',textbox:'input[type="text"], input:not([type]), textarea',link:"a[href]",checkbox:'input[type="checkbox"]',radio:'input[type="radio"]',listbox:"select",option:"option",heading:"h1, h2, h3, h4, h5, h6",img:"img[alt]",navigation:"nav",main:"main",complementary:"aside",banner:"header",contentinfo:"footer",search:'[role="search"]',form:"form",region:"section[aria-label], section[aria-labelledby]",tab:'[role="tab"]',tabpanel:'[role="tabpanel"]',tablist:'[role="tablist"]',menu:'[role="menu"]',menuitem:'[role="menuitem"]',dialog:'dialog, [role="dialog"]'};let c=[];s[n]&&(c=Array.from(t.querySelectorAll(s[n])));const i=[...o,...c];return r?i.filter(e=>{const t=function(e){if(e.getAttribute("aria-label"))return e.getAttribute("aria-label");const t=e.getAttribute("aria-labelledby");if(t){const e=document.getElementById(t);if(e)return e.textContent?.trim()||""}if(e.id){const t=document.querySelector(`label[for="${e.id}"]`);if(t)return t.textContent?.trim()||""}return e.getAttribute("title")?e.getAttribute("title"):"BUTTON"===e.tagName||"A"===e.tagName||"button"===e.getAttribute("role")?e.textContent?.trim()||"":"INPUT"===e.tagName&&e.value?e.value:""}(e);return t&&t.toLowerCase().includes(r.toLowerCase())}):i}(e.value,t);case"testid":case"testId":return function(e,t=document){return Array.from(t.querySelectorAll(`[data-testid="${e}"]`))}(e.value,t);case"dataattribute":case"dataAttribute":return function(e,t=document){const[n,r]=e.split(":").map(e=>e.trim()),o=r?`[data-${n}="${r}"]`:`[data-${n}]`;try{return Array.from(t.querySelectorAll(o))}catch{return[]}}(e.value,t);case"arialabel":case"ariaLabel":return function(e,t=document){return Array.from(t.querySelectorAll("[aria-label]")).filter(t=>{const n=t.getAttribute("aria-label");return n&&n.toLowerCase().includes(e.toLowerCase())})}(e.value,t);case"contextual":return function(e,t=document){const n=e.split(">>").map(e=>e.trim());if(2===n.length){const[e,r]=n,o=t.querySelector(e);return o?Array.from(o.querySelectorAll(r)):[]}return b(e)}(e.value,t);case"wpBlock":case"wpblock":return function(e,t=document){if("inserted"===e||e.startsWith("inserted:")){const n="inserted"===e?"act-inserted-block":e.substring(9),r=window.__actInsertedBlocks;if(console.log("[ACT findByWpBlock] Looking for inserted block, markerId:",n,"map exists:",!!r,"has key:",r?.has?.(n)),r?.has?.(n)){const e=r.get(n);console.log("[ACT findByWpBlock] Looking for inserted block:",n,"clientId:",e);let o=t.querySelector(`[data-block="${e}"]`);if(!o){const n=document.querySelector('iframe[name="editor-canvas"]'),r=n?.contentDocument;r&&r!==t&&(o=r.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched iframe, found:",!!o))}if(o||t===document||(o=document.querySelector(`[data-block="${e}"]`),console.log("[ACT findByWpBlock] Searched main doc, found:",!!o)),o)return console.log("[ACT findByWpBlock] Found inserted block element"),[o]}return console.log("[ACT findByWpBlock] Inserted block not found for marker:",n,"Available markers:",r?Array.from(r.keys()):"none"),[]}const n=window.wp?.data;if(!n)return console.log("[ACT findByWpBlock] wp.data not available"),[];const r=n.select("core/block-editor");if(!r)return console.log("[ACT findByWpBlock] core/block-editor store not available"),[];const o=r.getBlocks();console.log("[ACT findByWpBlock] Found",o.length,"blocks in editor");let s=null;if("first"===e)s=o[0]?.clientId;else if("last"===e)s=o[o.length-1]?.clientId;else if("selected"===e)s=r.getSelectedBlockClientId();else if(e.startsWith("type:")){const t=e.substring(5).split(":"),n=t[0],r=t[1]?parseInt(t[1],10):0,c=o.filter(e=>e.name===n);console.log("[ACT findByWpBlock] Looking for type:",n,"- found",c.length),s=c[r]?.clientId}else if(e.startsWith("nth:")){const t=parseInt(e.substring(4),10);s=o[t]?.clientId}if(!s)return console.log("[ACT findByWpBlock] No matching block found for:",e),[];console.log("[ACT findByWpBlock] Target clientId:",s);const c=t.querySelector(`[data-block="${s}"]`);return c?(console.log("[ACT findByWpBlock] Found element:",c.tagName),[c]):(console.log("[ACT findByWpBlock] Element not found in DOM"),[])}(e.value,t);default:return[]}}function C(e,t,n){let r=t.weight||50;return e.id&&(r+=20),e.getAttribute("data-testid")&&(r+=15),n?.withinContainer&&S(e,n.withinContainer)&&(r+=10),y(e)&&(r+=100),_(e)&&(r+=5),r}function k(e){if(console.log("[ACT resolveTarget] Starting resolution",e),!e||!e.locators||0===e.locators.length)return console.log("[ACT resolveTarget] No locators provided"),{success:!1,error:"No locators provided"};const t=e.constraints||{};console.log("[ACT resolveTarget] Constraints:",t);const n=t.inEditorIframe||t.withinContainer&&[".editor-styles-wrapper",".block-editor-block-list__layout"].includes(t.withinContainer);console.log("[ACT resolveTarget] shouldSearchIframe:",n);let r=document;if(n){const e=function(){const e=document.querySelector('iframe[name="editor-canvas"]');return e?.contentDocument||null}();if(console.log("[ACT resolveTarget] iframeDoc:",e?"found":"NOT FOUND"),!e)return{success:!1,error:"Editor iframe not found"};r=e}const o=[...e.locators].sort((e,t)=>e.fallback!==t.fallback?e.fallback?1:-1:(t.weight||50)-(e.weight||50)),s=o.filter(e=>!e.fallback),c=o.filter(e=>e.fallback);console.log("[ACT resolveTarget] Trying",s.length,"primary +",c.length,"fallback locators");for(const e of[...s,...c]){let n=A(e,r);if(console.log("[ACT resolveTarget] Locator",e.type,":",e.value.substring(0,50),"-> found",n.length,"raw matches"),!1!==t.visible&&(n=n.filter(_),console.log("[ACT resolveTarget] After visibility filter:",n.length)),t.scopeToSelectedBlock&&(n=n.filter(y),console.log("[ACT resolveTarget] After selectedBlock filter:",n.length)),t.withinContainer&&(n=n.filter(e=>S(e,t.withinContainer)),console.log("[ACT resolveTarget] After container filter:",n.length)),0===n.length)continue;if(1===n.length)return console.log("[ACT resolveTarget] SUCCESS! Found element with",e.type),{success:!0,element:n[0],usedLocator:e};if("number"==typeof t.index&&n[t.index])return{success:!0,element:n[t.index],usedLocator:e};console.log("[ACT resolveTarget] Multiple matches (",n.length,"), disambiguating by specificity...");const o=n.map(n=>({element:n,score:C(n,e,t)}));return o.sort((e,t)=>t.score-e.score),console.log("[ACT resolveTarget] Scores:",o.map(e=>e.score)),{success:!0,element:o[0].element,usedLocator:e}}return console.log("[ACT resolveTarget] FAILED - No matching element found after trying all locators"),{success:!1,error:"No matching element found"}}async function I(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{const s=document.querySelector(e);s?r(s):Date.now()-n>t?r(null):requestAnimationFrame(o)};o()})}async function w(e,t=2e3){const n=Date.now();return new Promise(r=>{const o=()=>{try{if(e())return void r(!0)}catch{}Date.now()-n>t?r(!1):requestAnimationFrame(o)};o()})}const R=new Map;window.__actInsertedBlocks=R;const v=new Map;let x=0;function O(){R.clear(),v.clear(),x=0,delete window.__actLastAppearedBlockClientId,console.log("[ACT clearInsertedBlocks] Cleared all tracking")}function P(e,t,n){v.has(e)||v.set(e,{}),v.get(e)[t]=n}async function N(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlockElement] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]',"input:not([type])"];let s=null;for(const e of o)if(s=r.querySelector(e),s)break;if(s||(s=r),s.scrollIntoView({behavior:"smooth",block:"center"}),s.focus(),"true"===s.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(s),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlockElement] Focused:",s.tagName,s.className),!0}const L={ensureEditor:async function(){const e=(0,a.select)("core/block-editor");return!!e&&w(()=>{const t=e.getBlocks?.();return void 0!==t})},ensureSidebarOpen:async function(e=null){const t=(0,a.select)("core/edit-post"),n=(0,a.dispatch)("core/edit-post");if(!t||!n){const t=(0,a.select)("core/interface"),n=(0,a.dispatch)("core/interface");if(t&&n){const r=t.getActiveComplementaryArea?.("core/edit-post");return r||await(n.enableComplementaryArea?.("core/edit-post",e||"edit-post/document")),w(()=>!!t.getActiveComplementaryArea?.("core/edit-post"))}return!1}if(t.isEditorSidebarOpened?.()||t.isPluginSidebarOpened?.())return!0;try{return e?await(n.openGeneralSidebar?.(e)):await(n.openGeneralSidebar?.("edit-post/document")),w(()=>t.isEditorSidebarOpened?.())}catch{return!1}},ensureSidebarClosed:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e||!t){const e=(0,a.select)("core/interface"),t=(0,a.dispatch)("core/interface");if(e&&t){const n=e.getActiveComplementaryArea?.("core/edit-post");return n&&await(t.disableComplementaryArea?.("core/edit-post")),w(()=>!e.getActiveComplementaryArea?.("core/edit-post"))}return!1}const n=e.isEditorSidebarOpened?.();if(!n)return!0;try{return await(t.closeGeneralSidebar?.()),w(()=>!e.isEditorSidebarOpened?.())}catch{return!1}},selectSidebarTab:async function(e){const t=(0,a.dispatch)("core/edit-post");if(!t)return!1;const n={document:"edit-post/document",post:"edit-post/document",block:"edit-post/block"}[e]||e;try{return await(t.openGeneralSidebar?.(n)),w(()=>{const e=(0,a.select)("core/edit-post");return e?.getActiveGeneralSidebarName?.()===n})}catch{return!1}},openInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!0)),w(()=>e?.isInserterOpened?.())}catch{const e=document.querySelector('.edit-post-header-toolbar__inserter-toggle, button[aria-label*="inserter"], button[aria-label*="Add block"]');return!!e&&(e.click(),I(".block-editor-inserter__content, .editor-inserter__content").then(e=>!!e))}},closeInserter:async function(){const e=(0,a.select)("core/edit-post"),t=(0,a.dispatch)("core/edit-post");if(!e?.isInserterOpened?.())return!0;try{return await(t?.setIsInserterOpened?.(!1)),w(()=>!e?.isInserterOpened?.())}catch{return!1}},selectBlock:async function(e){const t=(0,a.dispatch)("core/block-editor");if(!t||!e)return!1;try{return await(t.selectBlock?.(e)),w(()=>{const t=(0,a.select)("core/block-editor");return t?.getSelectedBlockClientId?.()===e})}catch{return!1}},focusElement:async function(e){const t=await I(e);if(!t)return!1;try{return t.focus(),document.activeElement===t}catch{return!1}},scrollIntoView:async function(e){const t=await I(e);if(!t)return!1;try{return t.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),await new Promise(e=>setTimeout(e,300)),!0}catch{return!1}},openModal:async function(e,t){if(document.querySelector(t))return!0;const n=await I(e);return!!n&&(n.click(),I(t).then(e=>!!e))},closeModal:async function(e){const t=document.querySelector(e);if(!t)return!0;const n=t.querySelector('button[aria-label="Close"], .components-modal__header button, .components-popover__close');return n?(n.click(),w(()=>!document.querySelector(e))):(document.dispatchEvent(new KeyboardEvent("keydown",{key:"Escape",code:"Escape",bubbles:!0})),w(()=>!document.querySelector(e)))},insertBlock:async function(e="core/paragraph",t={},n="act-inserted-block"){console.log("[ACT insertBlock] Called with:",{blockName:e,markerId:n,currentStepIndex:x}),console.log("[ACT insertBlock] insertedBlocks map:",Array.from(R.entries())),console.log("[ACT insertBlock] insertedBlocksByStep:",Array.from(v.entries()));try{const{createBlock:r}=await Promise.resolve().then(o.t.bind(o,997,23)),s=(0,a.dispatch)("core/block-editor"),c=(0,a.select)("core/block-editor");if(!s||!r)return console.warn("[ACT insertBlock] Block editor not available"),!1;const i=function(e,t){const n=v.get(e);return n&&n[t]?n[t]:null}(x,n);if(console.log("[ACT insertBlock] stepClientId from getStepInsertedBlock:",i),i){const e=c.getBlock(i);if(console.log("[ACT insertBlock] existingBlock from store:",e?.name),e)return await s.selectBlock(i),await N(i),console.log("[ACT insertBlock] Reusing step block:",i,"for step:",x),!0;const t=document.querySelector('iframe[name="editor-canvas"]');if((t?.contentDocument||document).querySelector(`[data-block="${i}"]`))return await s.selectBlock(i),await N(i),console.log("[ACT insertBlock] Block still in DOM, reusing:",i),!0;console.log("[ACT insertBlock] Block not in store or DOM, checking for any existing blocks of this type")}if(console.log("[ACT insertBlock] Checking global map for:",n,"has:",R.has(n)),R.has(n)){const e=R.get(n),t=c.getBlock(e);if(console.log("[ACT insertBlock] existingBlock from global map:",t?.name),t)return await s.selectBlock(e),await N(e),P(x,n,e),console.log("[ACT insertBlock] Reusing existing block:",e),!0;const r=document.querySelector('iframe[name="editor-canvas"]');if((r?.contentDocument||document).querySelector(`[data-block="${e}"]`))return await s.selectBlock(e),await N(e),P(x,n,e),console.log("[ACT insertBlock] Block from global map still in DOM:",e),!0}const l=c.getBlocks()||[];console.log("[ACT insertBlock] Checking for existing blocks of type:",e,"found:",l.length,"total blocks"),console.log("[ACT insertBlock] Block names in editor:",l.map(e=>e.name));const u=l.find(t=>t.name===e);if(u)return console.log("[ACT insertBlock] Found existing block of same type:",u.clientId),await s.selectBlock(u.clientId),await N(u.clientId),R.set(n,u.clientId),P(x,n,u.clientId),console.log("[ACT insertBlock] Reusing existing block of type:",e),!0;if(l.length>0){const t=l[l.length-1];console.log("[ACT insertBlock] Considering last block:",t.name,t.clientId);const r=["core/paragraph","core/heading","core/list","core/quote"],o=r.includes(e),c=r.includes(t.name);if(o&&c)return console.log("[ACT insertBlock] Reusing last text block instead of creating new:",t.clientId),await s.selectBlock(t.clientId),await N(t.clientId),R.set(n,t.clientId),P(x,n,t.clientId),!0}console.log("[ACT insertBlock] No existing block found, creating new one");const d={...t,metadata:{...t.metadata||{},actMarkerId:n}},p=r(e,d),m="";await s.insertBlock(p,void 0,m,!0),R.set(n,p.clientId),P(x,n,p.clientId);const h=await w(()=>{const e=document.querySelector('iframe[name="editor-canvas"]'),t=e?.contentDocument;return!!(t||document).querySelector(`[data-block="${p.clientId}"]`)},3e3);return h&&(await s.selectBlock(p.clientId),await N(p.clientId)),console.log("[ACT insertBlock] Inserted block:",p.clientId,"markerId:",n,"step:",x,"success:",h),h}catch(e){return console.error("[ACT insertBlock] Error:",e),!1}}};async function B(e){const{type:t,params:n={}}=e,r=L[t];if(!r)return{success:!1,error:`Unknown precondition type: ${t}`,type:t};try{let e;switch(t){case"ensureEditor":case"ensureSidebarClosed":case"openInserter":case"closeInserter":e=await r();break;case"ensureSidebarOpen":e=await r(n.sidebar);break;case"selectSidebarTab":e=await r(n.tab);break;case"selectBlock":e=await r(n.clientId);break;case"focusElement":case"scrollIntoView":e=await r(n.selector);break;case"openModal":e=await r(n.trigger,n.modal);break;case"closeModal":e=await r(n.modal);break;case"insertBlock":e=await r(n.blockName||"core/paragraph",n.attributes||{},n.markerId||"act-inserted-block");break;default:e=!1}return{success:e,type:t,error:e?null:`Precondition failed: ${t}`}}catch(e){return{success:!1,type:t,error:`Precondition error: ${e.message}`}}}async function D(e){if(!e||0===e.length)return{success:!0,results:[]};const t=[];let n=!0;for(const r of e){const e=await B(r);t.push(e),e.success||(n=!1)}return{success:n,results:t,failedPreconditions:t.filter(e=>!e.success)}}function j(e,t=0){return new Promise(n=>{let r=null,o=null,s=!1;const c=(e,t=!1)=>{s||(s=!0,r&&clearTimeout(r),o&&clearInterval(o),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{c(!1,!0)},t));const i=()=>{try{e()&&c(!0)}catch{}};i(),s||(o=setInterval(i,100))})}function U(e,t={}){return new Promise(n=>{let r=!1,o=!1;const{timeout:s=0,gracePeriod:c=300}=t;let i=null;const l=()=>{e.removeEventListener("click",a,!0),e.ownerDocument!==document&&e.ownerDocument.removeEventListener("click",a,!0),i&&clearTimeout(i)},a=t=>{r||(o?(e===t.target||e.contains(t.target))&&(console.log("[ACT watchClickTarget] Click detected on target:",e.tagName),r=!0,l(),n({success:!0,event:"click"})):console.log("[ACT watchClickTarget] Ignoring click during grace period"))};e.addEventListener("click",a,{capture:!0}),e.ownerDocument!==document&&e.ownerDocument.addEventListener("click",a,{capture:!0}),setTimeout(()=>{o=!0,console.log("[ACT watchClickTarget] Armed after grace period, watching:",e.tagName,e.className)},c),s>0&&(i=setTimeout(()=>{r||(r=!0,l(),n({success:!1,timedOut:!0}))},s))})}function F(e,t={}){return new Promise(n=>{let r=!1;const{timeout:o=0,expectedValue:s,attributeName:c=null}=t;let i=null,l=null;const a=()=>{l&&l.disconnect(),e.removeEventListener("input",m),e.removeEventListener("change",h),i&&clearTimeout(i)},u=()=>c?e.getAttribute(c):"value"in e?e.value:e.isContentEditable?e.textContent:"checkbox"===e.type||"radio"===e.type?e.checked:e.textContent,d=u(),p=()=>{r||(()=>{const e=u();return void 0!==s?e===s:e!==d})()&&(r=!0,a(),n({success:!0,event:"valueChanged"}))},m=()=>p(),h=()=>p();e.addEventListener("input",m),e.addEventListener("change",h),l=new MutationObserver(()=>p()),l.observe(e,{attributes:!0,characterData:!0,subtree:!0,childList:!0}),o>0&&(i=setTimeout(()=>{r||(r=!0,a(),n({success:!1,timedOut:!0}))},o))})}function q(e={}){const{storeName:t,selector:n,args:r=[],expectedValue:o,comparator:s="equals",timeout:c=0}=e;return new Promise(e=>{let i=!1,l=null,u=null;const d=()=>{u&&u(),l&&clearTimeout(l)},p=(0,a.select)(t);if(!p||!p[n])return void e({success:!1,error:`Invalid store or selector: ${t}.${n}`});const m=()=>{i||(()=>{try{const e=p[n](...r);switch(s){case"equals":default:return e===o;case"notEquals":return e!==o;case"truthy":return!!e;case"falsy":return!e;case"contains":return(Array.isArray(e)||"string"==typeof e)&&e.includes(o);case"greaterThan":return e>o;case"lessThan":return eo}}catch{return!1}})()&&(i=!0,d(),e({success:!0,event:"wpDataChanged"}))};m(),i||(u=(0,a.subscribe)(()=>{m()})),c>0&&(l=setTimeout(()=>{i||(i=!0,d(),e({success:!1,timedOut:!0}))},c))})}function M(e={}){const{timeout:t=0}=e;let n=null,r=null,o=!1;return{promise:new Promise(e=>{n=e,t>0&&(r=setTimeout(()=>{o||(o=!0,e({success:!1,timedOut:!0}))},t))}),confirm:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!0,event:"manual"}))},cancel:()=>{o||(o=!0,r&&clearTimeout(r),n({success:!1,cancelled:!0}))}}}function V(e,t={}){const{timeout:n=0}=t,r=()=>{const t=new Set;document.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)});const n=document.querySelector('iframe[name="editor-canvas"]');return n?.contentDocument?.querySelectorAll(e).forEach(e=>{let n=e.getAttribute("data-block");if(!n){const t=e.closest("[data-block]");n=t?.getAttribute("data-block")}n&&t.add(n)}),t},o=r();return console.log("[ACT watchElementAppear] Existing matches:",o.size,"for selector:",e),j(()=>{const e=r();for(const t of e)if(!o.has(t))return window.__actNewlyAppearedBlockClientId=t,console.log("[ACT watchElementAppear] NEW element appeared:",t),!0;return!1},n).then(e=>(e.success&&window.__actNewlyAppearedBlockClientId&&(window.__actLastAppearedBlockClientId=window.__actNewlyAppearedBlockClientId,console.log("[ACT watchElementAppear] Tracked appeared block:",window.__actLastAppearedBlockClientId),delete window.__actNewlyAppearedBlockClientId),{...e,event:e.success?"elementAppeared":null}))}function G(e,t={}){const{timeout:n=0}=t;return j(()=>null===document.querySelector(e),n).then(e=>({...e,event:e.success?"elementDisappeared":null}))}function H(e,t={}){const{timeout:n=0,target:r=document}=t;return new Promise(t=>{let o=!1,s=null;const c=()=>{r.removeEventListener(e,i),s&&clearTimeout(s)},i=n=>{o||(o=!0,c(),t({success:!0,event:e,detail:n.detail}))};r.addEventListener(e,i,{once:!0}),n>0&&(s=setTimeout(()=>{o||(o=!0,c(),t({success:!1,timedOut:!0}))},n))})}function W(e){try{const t=(0,a.select)("core/block-editor");if(!t?.getBlocks)return!1;const n=t.getBlocks(),r=t=>{for(const n of t){if(n.name===e)return!0;if(n.innerBlocks?.length>0&&r(n.innerBlocks))return!0}return!1};return r(n)}catch(e){return console.warn("[ACT] Error checking for block type:",e),!1}}async function $(e,t,n=5e3){const r=t+1;if(r>=e.length)return{waited:!1};const o=function(e){if(!e?.target)return null;const{target:t}=e;if(t.constraints?.blockType)return t.constraints.blockType;const n=t.locators||[];for(const e of n){if("dataAttribute"===e.type&&"data-type"===e.attribute)return e.value;if("block"===e.type&&e.blockName)return e.blockName}for(const e of n)if("css"===e.type&&e.value){const t=e.value.match(/\[data-type=["']([^"']+)["']\]/);if(t)return t[1];const n=e.value.match(/\.wp-block-(\w+)/);if(n)return`core/${n[1]}`}return null}(e[r]);if(!o)return console.log("[ACT waitForNextStepBlock] Next step does not expect a specific block"),{waited:!1};if(console.log("[ACT waitForNextStepBlock] Next step expects block:",o),W(o))return console.log("[ACT waitForNextStepBlock] Block already exists"),{waited:!1,blockType:o};const s=await function(e,t=5e3){return new Promise(n=>{if(W(e))return console.log("[ACT waitForBlock] Block already exists:",e),void n({success:!0});console.log("[ACT waitForBlock] Waiting for block:",e);let r=null,o=null,s=!1;const c=(e,t=!1)=>{s||(s=!0,r&&clearTimeout(r),o&&o(),n({success:e,timedOut:t}))};t>0&&(r=setTimeout(()=>{console.log("[ACT waitForBlock] Timeout waiting for:",e),c(!1,!0)},t)),o=(0,a.subscribe)(()=>{W(e)&&(console.log("[ACT waitForBlock] Block appeared:",e),c(!0))})})}(o,n);return{waited:!0,blockType:o,success:s.success,timedOut:s.timedOut}}const z="admin-coach-tours";function K(e){if(!Array.isArray(e))return null;for(const t of e){if("css"===t.type&&"string"==typeof t.value){const e=t.value.match(/data-type=["']?(core\/[\w-]+)["']?/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}if("wpBlock"===t.type&&"string"==typeof t.value){const e=t.value.match(/type:(core\/[\w-]+)/i);if(e){const t=e[1].replace("core/","");return t.charAt(0).toUpperCase()+t.slice(1).replace(/-/g," ")}}}return null}function X(){console.log("[ACT TourRunner] Component function called");const[e,t]=(0,l.useState)(null),[n,r]=(0,l.useState)(null),[o,s]=(0,l.useState)(null),[c,i]=(0,l.useState)(null),[d,p]=(0,l.useState)(!1),[h,g]=(0,l.useState)(0),f=(0,l.useRef)(null),_=(0,l.useRef)(null),y=(0,l.useRef)(null),{isPlaying:S,currentTour:b,currentStep:A,stepIndex:C,totalSteps:I}=(0,a.useSelect)(e=>{const t=e(z),n={isPlaying:t.isPupilMode(),currentTour:t.getCurrentTour(),currentStep:t.getCurrentStep(),stepIndex:t.getCurrentStepIndex()||0,totalSteps:t.getTotalSteps()||0};return console.log("[ACT TourRunner] useSelect:",n),n},[]),{stopTour:w,nextStep:R,previousStep:v,repeatStep:P,setAiTourError:N,setLastFailureContext:L}=(0,a.useDispatch)(z);(0,l.useEffect)(()=>(f.current||(f.current=new E),()=>{f.current&&(f.current.destroy(),f.current=null)}),[]),(0,l.useEffect)(()=>{if(!S||!A)return f.current&&f.current.clear(),t(null),void O();let e=!0,n=null;const o=_.current;return(async()=>{if(r(null),p(!0),y.current?.cancel&&(console.log("[ACT TourRunner] Cancelling previous completion watcher"),y.current.cancel(),y.current=null),null!==o&&o!==C&&await async function(e){console.log("[ACT onLeaveStep] Leaving step:",e);const t=(0,a.dispatch)("core/block-editor"),n=(0,a.select)("core/block-editor");if(!t||!n)return;const r=n.getSelectedBlockClientId?.();if(r)try{await(t.clearSelectedBlock?.()),console.log("[ACT onLeaveStep] Deselected block:",r)}catch(e){console.warn("[ACT onLeaveStep] Could not deselect block:",e)}}(o),await async function(e){var t;console.log("[ACT onEnterStep] Entering step:",e),x=t=e,console.log("[ACT setCurrentStepIndex]",t)}(C),_.current=C,console.log("[ACT TourRunner] Preconditions for step:",C,A.preconditions),A.preconditions?.length>0){console.log("[ACT TourRunner] Applying",A.preconditions.length,"preconditions");const t=await D(A.preconditions);if(console.log("[ACT TourRunner] Precondition result:",t),!e)return;t.success||console.warn("Some preconditions failed:",t.failedPreconditions)}else console.log("[ACT TourRunner] No preconditions for this step");if(p(!1),A.target){const o=A.recovery?async()=>{await D(A.recovery)}:null,l=await async function(e,t=null){let n=k(e);if(n.success)return n;if(t)try{if(await t(),await new Promise(e=>setTimeout(e,100)),n=k(e),n.success)return{...n,recovered:!0}}catch(e){return{success:!1,error:`Recovery failed: ${e.message}`}}return n}(A.target,o);if(!e)return;if(l.success){n=l.element,t(l.element),r(null),s(null),i({success:!0,usedLocator:l.usedLocator,recovered:l.recovered||!1});let e=l.element.getAttribute("data-block");if(!e){const t=l.element.closest("[data-block]");t&&(e=t.getAttribute("data-block"),console.log("[ACT TourRunner] Found parent block:",e))}if(e)try{const t=(0,a.dispatch)("core/block-editor");t?.selectBlock&&(await t.selectBlock(e),console.log("[ACT TourRunner] Selected block:",e))}catch(e){console.warn("[ACT TourRunner] Could not select block:",e)}(c=l.element)&&(c.ownerDocument!==document?(c.scrollIntoView({behavior:"smooth",block:"center",inline:"center"}),setTimeout(()=>{const e=document.querySelector('iframe[name="editor-canvas"]');if(e){const t=c.getBoundingClientRect(),n=e.getBoundingClientRect(),r=n.top+t.top,o=n.top+t.bottom,s=window.innerHeight;if(!(r>=100&&o<=s-100)){const e=r+window.scrollY-s/2;window.scrollTo({top:Math.max(0,e),behavior:"smooth"})}}},150)):c.scrollIntoView({behavior:"smooth",block:"center",inline:"center"})),setTimeout(()=>{f.current&&l.element?.isConnected&&f.current.highlight(l.element)},350)}else if(n=null,t(null),r(l.error),s(K(A.target?.locators)),i({success:!1,error:l.error}),console.log("[ACT TourRunner] Target resolution failed, NOT auto-advancing. Error:",l.error),f.current&&f.current.clear(),h>0){console.log("[ACT TourRunner] Retry also failed. Failing the entire tour."),L({stepIndex:C,stepId:A.id,stepTitle:A.title,targetLocators:A.target?.locators||[],error:l.error,reason:"Step retry failed - target element could not be found after second attempt"}),O(),_.current=null,w();const e=K(A.target?.locators),t=e?(0,u.sprintf)(/* translators: %s: block type name */ /* translators: %s: block type name */ -(0,u.__)("Could not find the %s block. Make sure to insert or select the correct block, then try again.","admin-coach-tours"),e):(0,u.__)("The generated tour could not complete. Try again to get a fresh set of instructions.","admin-coach-tours");N(t)}}var c;if(A.completion&&n){if(console.log("[ACT TourRunner] Setting up completion watcher for step:",C,"type:",A.completion.type,"params:",A.completion.params),await new Promise(e=>setTimeout(e,100)),!e)return;const t=function(e,t=null){if(!e||!e.type)return M();const{type:n,params:r={}}=e,o=e.timeout||0;switch(n){case"clickTarget":return t?{promise:U(t,{timeout:o}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for clickTarget"}),cancel:()=>{}};case"domValueChanged":return t?{promise:F(t,{timeout:o,...r}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for domValueChanged"}),cancel:()=>{}};case"wpData":return{promise:q({timeout:o,...r}),cancel:()=>{}};case"manual":default:return M({timeout:o});case"elementAppear":return{promise:V(r.selector,{timeout:o}),cancel:()=>{}};case"elementDisappear":return{promise:G(r.selector,{timeout:o}),cancel:()=>{}};case"customEvent":return{promise:H(r.eventName,{timeout:o}),cancel:()=>{}}}}(A.completion,n);y.current=t,t.promise.then(async t=>{if(e&&t.success)if(console.log("[ACT TourRunner] Completion detected for step:",C),C{e&&(console.log("[ACT TourRunner] Auto-advancing from step:",C),R())},300)}else console.log("[ACT TourRunner] Last step completed, ending tour"),O(),R()})}})(),()=>{e=!1,y.current?.cancel&&(console.log("[ACT TourRunner] Cleanup: Cancelling completion watcher"),y.current.cancel(),y.current=null)}},[S,A,C,h,w,N]);const B=(0,l.useCallback)(async()=>{if(y.current?.confirm)y.current.confirm();else{if(C{y.current?.cancel&&(y.current.cancel(),y.current=null),g(e=>e+1),P()},[P]),W=(0,l.useCallback)(()=>{y.current?.cancel&&(y.current.cancel(),y.current=null),O(),_.current=null,w()},[w]);if(!S||!b||!A)return null;const X=(0,m.jsx)(T,{step:A,stepIndex:C,totalSteps:I,tourTitle:b.title,targetElement:e,resolutionError:n,expectedBlockType:o,isApplyingPreconditions:d,onContinue:B,onRepeat:j,onPrevious:v,onNext:R,onStop:W});return(0,l.createPortal)(X,document.body)}const Y=window.wp.apiFetch;var Q=o.n(Y);const J="admin-coach-tours",Z={media:"🖼️",content:"📝",layout:"📐",formatting:"✨",default:"📚"};function ee(){const[e,t]=(0,l.useState)(!1),[n,r]=(0,l.useState)([]),[o,s]=(0,l.useState)(!1),[c,i]=(0,l.useState)(null),[p,h]=(0,l.useState)(""),[g,T]=(0,l.useState)("tasks"),[f,E]=(0,l.useState)(!1),[_,y]=(0,l.useState)(null),S=(0,l.useRef)(null),{storeLoading:b,aiTourError:A,isPlaying:C,aiAvailable:k,lastFailureContext:I}=(0,a.useSelect)(e=>{const t=e(J);return{storeLoading:t.isAiTourLoading?.()??!1,aiTourError:t.getAiTourError?.()??null,isPlaying:null!==t.getCurrentTour(),aiAvailable:window.adminCoachTours?.aiAvailable??!1,lastFailureContext:t.getLastFailureContext?.()??null}},[]),w=f||b;(0,l.useEffect)(()=>{C&&f&&E(!1)},[C,f]),(0,l.useEffect)(()=>{!A||C||e||t(!0)},[A,C,e]);const{requestAiTour:R,clearEphemeralTour:v,setAiTourError:x,setLastFailureContext:O}=(0,a.useDispatch)(J);(0,l.useEffect)(()=>{e&&0===n.length&&!o&&(s(!0),i(null),Q()({path:"/admin-coach-tours/v1/ai/tasks"}).then(e=>{e.available&&e.tasks?r(e.tasks):i((0,u.__)("AI is not available.","admin-coach-tours"))}).catch(e=>{i(e.message||(0,u.__)("Failed to load tasks.","admin-coach-tours"))}).finally(()=>{s(!1)}))},[e,n.length,o]),(0,l.useEffect)(()=>{"chat"===g&&S.current&&S.current.focus()},[g]);const P=(0,l.useCallback)(e=>{E(!0),t(!1);const n=window.adminCoachTours?.postType||"post";y({type:"task",taskId:e,postType:n}),R(e,"",n)},[R]),N=(0,l.useCallback)(e=>{if(e.preventDefault(),!p.trim())return;E(!0),t(!1);const n=window.adminCoachTours?.postType||"post",r=p.trim();y({type:"chat",query:r,postType:n}),R("",r,n),h("")},[p,R]),L=(0,l.useCallback)(()=>{_&&(x(null),E(!0),t(!1),"task"===_.type?R(_.taskId,"",_.postType,I):"chat"===_.type&&R("",_.query,_.postType,I),O(null))},[_,I,R,x,O]),B=(0,l.useCallback)(()=>{x(null),y(null),O(null),E(!1)},[x,O]),D=(0,l.useCallback)(()=>{i(null),r([])},[]),j=(0,l.useCallback)(()=>{t(e=>!e)},[]),U=(0,l.useCallback)(()=>{t(!1)},[]);if(!k)return(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:j,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),children:(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("AI Coach","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:U,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsx)("div",{className:"act-pupil-launcher__content",children:(0,m.jsxs)("div",{className:"act-pupil-launcher__not-configured",children:[(0,m.jsx)("p",{children:(0,u.__)("AI is not configured yet.","admin-coach-tours")}),(0,m.jsxs)("p",{children:[(0,u.__)("Go to ","admin-coach-tours"),(0,m.jsx)("a",{href:"/wp-admin/tools.php?page=admin-coach-tours",children:(0,u.__)("Tools → Coach Tours","admin-coach-tours")}),(0,u.__)(" to add your API key.","admin-coach-tours")]})]})})]})]});const F=n.reduce((e,t)=>{const n=t.category||"other";return e[n]||(e[n]=[]),e[n].push(t),e},{}),q=w&&(0,l.createPortal)((0,m.jsx)("div",{className:"act-ai-loading-overlay",children:(0,m.jsxs)("div",{className:"act-ai-loading-overlay__content",children:[(0,m.jsx)("div",{className:"act-ai-loading-overlay__spinner"}),(0,m.jsx)("h2",{className:"act-ai-loading-overlay__title",children:(0,u.__)("Creating your guided tour...","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__text",children:(0,u.__)("AI is analyzing the editor and generating step-by-step instructions.","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__hint",children:(0,u.__)("This usually takes a few seconds.","admin-coach-tours")})]})}),document.body);return C?q||null:(0,m.jsxs)(m.Fragment,{children:[q,(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:j,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),disabled:w,children:w?(0,m.jsx)("span",{className:"act-pupil-launcher__spinner"}):(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[w&&(0,m.jsxs)("div",{className:"act-pupil-launcher__ai-loading",children:[(0,m.jsx)("div",{className:"act-pupil-launcher__ai-loading-spinner"}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-text",children:(0,u.__)("Creating your tour...","admin-coach-tours")}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-hint",children:(0,u.__)("AI is generating step-by-step instructions","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("What would you like to learn?","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:U,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__tabs",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("tasks"===g?"is-active":""),onClick:()=>T("tasks"),children:(0,u.__)("Common Tasks","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("chat"===g?"is-active":""),onClick:()=>T("chat"),children:(0,u.__)("Ask a Question","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__content",children:[A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:A}),(0,m.jsx)("p",{className:"act-pupil-launcher__error-hint",children:(0,u.__)("This might be a temporary issue.","admin-coach-tours")}),(0,m.jsxs)("div",{className:"act-pupil-launcher__error-actions",children:[_&&(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:L,disabled:w,children:(0,u.__)("Try Again","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__dismiss-btn",onClick:B,children:(0,u.__)("Dismiss","admin-coach-tours")})]})]}),c&&!A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:c}),(0,m.jsx)("div",{className:"act-pupil-launcher__error-actions",children:(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:D,children:(0,u.__)("Try Again","admin-coach-tours")})})]}),"tasks"===g&&!c&&(0,m.jsxs)("div",{className:"act-pupil-launcher__tasks",children:[o&&(0,m.jsx)("div",{className:"act-pupil-launcher__loading",children:(0,u.__)("Loading tasks...","admin-coach-tours")}),!o&&Object.entries(F).map(([e,t])=>(0,m.jsxs)("div",{className:"act-pupil-launcher__category",children:[(0,m.jsxs)("h4",{children:[(0,m.jsx)("span",{className:"act-pupil-launcher__category-icon",children:Z[e]||Z.default}),e.charAt(0).toUpperCase()+e.slice(1)]}),(0,m.jsx)("div",{className:"act-pupil-launcher__task-list",children:t.map(e=>(0,m.jsxs)("button",{className:"act-pupil-launcher__task",onClick:()=>P(e.id),disabled:w,children:[(0,m.jsx)("span",{className:"act-pupil-launcher__task-icon",children:e.icon?(0,m.jsx)(d.Dashicon,{icon:e.icon}):"📌"}),(0,m.jsx)("span",{className:"act-pupil-launcher__task-label",children:e.label})]},e.id))})]},e))]}),"chat"===g&&(0,m.jsxs)("div",{className:"act-pupil-launcher__chat",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__chat-hint",children:(0,u.__)("Ask about the WordPress editor:","admin-coach-tours")}),(0,m.jsxs)("form",{onSubmit:N,children:[(0,m.jsx)("input",{ref:S,type:"text",className:"act-pupil-launcher__chat-input",value:p,onChange:e=>h(e.target.value),placeholder:(0,u.__)("e.g., How do I add a gallery?","admin-coach-tours"),disabled:w}),(0,m.jsx)("button",{type:"submit",className:"act-pupil-launcher__chat-submit",disabled:w||!p.trim(),children:w?(0,u.__)("Generating...","admin-coach-tours"):(0,u.__)("Get Tour","admin-coach-tours")})]}),(0,m.jsx)("p",{className:"act-pupil-launcher__chat-note",children:(0,u.__)("AI will create a step-by-step tour to guide you.","admin-coach-tours")})]})]})]})]})]})}const te={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},ne={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},re=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function oe(e){return{type:ne.SET_TOURS_LOADING,isLoading:e}}function se(e){return{type:ne.SET_TOURS_ERROR,error:e}}function ce(e){return{type:ne.RECEIVE_TOURS,tours:e}}function ie(e){return{type:ne.RECEIVE_TOUR,tour:e}}function*le(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield ie(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield ie({id:e,title:"",steps:[],status:"draft"})}yield{type:ne.SET_CURRENT_TOUR,tourId:e}}else yield{type:ne.SET_CURRENT_TOUR,tourId:null}}function ae(e={}){return{type:"FETCH_TOURS",args:e}}function ue(e){return{type:"FETCH_TOUR",tourId:e}}function*de(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield ie(n)),n}catch(e){throw e}}function*pe(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield ie(t)),t}catch(e){throw e}}function*me(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield ie(n)),n}catch(e){throw e}}function he(e,t="pupil"){return{type:ne.START_TOUR,tourId:e,mode:t}}function ge(){return{type:ne.END_TOUR}}function Te(){return ge()}function fe(e){return{type:ne.SET_CURRENT_STEP,stepIndex:e}}function Ee(){return{type:ne.NEXT_STEP}}function _e(){return{type:ne.PREVIOUS_STEP}}function ye(){return{type:ne.SKIP_STEP}}function Se(){return{type:ne.REPEAT_STEP}}function be(e){return{type:ne.SET_MODE,mode:e}}function Ae(e){return{type:ne.SET_COMPLETION_SATISFIED,satisfied:e}}function Ce(){return{type:ne.RESET_COMPLETION}}function ke(){return Ee()}function Ie(e){return{type:ne.SET_RESOLVED_TARGET,target:e}}function we(){return{type:ne.CLEAR_RESOLVED_TARGET}}function Re(e){return{type:ne.SET_RECOVERING,isRecovering:e}}function ve(){return{type:ne.INCREMENT_RESOLUTION_ATTEMPTS}}function xe(e){return{type:ne.SET_LAST_ERROR,error:e}}function Oe(e=null){return{type:ne.ACTIVATE_PICKER,stepId:e}}function Pe(e=null){return Oe(e)}function Ne(){return{type:ne.DEACTIVATE_PICKER}}function Le(){return Ne()}function Be(e){return{type:ne.SELECT_STEP,stepId:e}}function De(e){return{type:ne.SET_PENDING_CHANGES,pending:e}}function*je(e,t,n){yield{type:ne.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*Ue(e,t={},n=null){const r={id:re(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:ne.ADD_STEP,tourId:e,step:r,index:n}}function*Fe(e,t){yield{type:ne.DELETE_STEP,tourId:e,stepId:t}}function*qe(e,t){yield{type:ne.REORDER_STEPS,tourId:e,stepIds:t}}function Me(e){return{type:ne.SET_AI_DRAFT_LOADING,isLoading:e}}function Ve(e){return{type:ne.SET_AI_DRAFT_ERROR,error:e}}function Ge(e){return{type:ne.SET_AI_DRAFT_RESULT,result:e}}function He(){return{type:ne.CLEAR_AI_DRAFT}}function*We(e,t){yield Me(!0),yield Ve(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield Ge(n),n}catch(e){throw yield Ve(e.message||"Failed to generate AI draft"),e}finally{yield Me(!1)}}function $e(e){return{type:ne.SET_SIDEBAR_OPEN,isOpen:e}}function ze(e){return{type:ne.SET_AI_TOUR_LOADING,isLoading:e}}function Ke(e){return{type:ne.SET_AI_TOUR_ERROR,error:e}}function Xe(e){return{type:ne.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function Ye(e){return{type:ne.RECEIVE_EPHEMERAL_TOUR,tour:e}}function Qe(){return{type:ne.CLEAR_EPHEMERAL_TOUR}}function*Je(e,t,n,r=null){yield ze(!0),yield Ke(null);try{const o=yield{type:"GATHER_EDITOR_CONTEXT"},s=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:o,failureContext:r};console.log("[ACT AI Response] Full result:",s),console.log("[ACT AI Response] Tour:",JSON.stringify(s.tour,null,2));const c={id:"ephemeral",...s.tour};return yield Ye(c),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield he("ephemeral","pupil"),c}catch(e){throw yield Ke(e.message||"Failed to generate tour"),e}finally{yield ze(!1)}}function*Ze(e){const t={id:"ephemeral",...e};yield Ye(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield he("ephemeral","pupil")}function et(e){return e.tours}const tt=(0,a.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function nt(e,t){return e.tours[t]||null}function rt(e){return e.toursLoading}function ot(e){return e.toursError}const st=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),ct=(0,a.createSelector)((e,t)=>tt(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function it(e){return e.currentTourId}function lt(e){return e.currentTourId?e.tours[e.currentTourId]:null}function at(e){return e.currentStepIndex}const ut=(0,a.createSelector)(e=>{const t=lt(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function dt(e){const t=lt(e);return t?.steps?.length||0}function pt(e){return e.currentStepIndex0}function ht(e){const t=dt(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function gt(e){return e.mode}function Tt(e){return"educator"===e.mode}function ft(e){return"pupil"===e.mode}function Et(e){return null!==e.currentTourId&&null!==e.mode}function _t(e){return e.completionSatisfied}function yt(e){return e.skippedSteps}function St(e,t){return e.skippedSteps.includes(t)}function bt(e){return e.resolvedTarget}function At(e){return e.isRecovering}function Ct(e){return e.resolutionAttempts}function kt(e){return e.lastError}function It(e){return e.isPickerActive}function wt(e){return e.pickingStepId||null}function Rt(e){return e.selectedStepId}const vt=(0,a.createSelector)(e=>{const t=lt(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function xt(e){return e.pendingChanges}function Ot(e){return e.aiDraftLoading}function Pt(e){return Ot(e)}function Nt(e){return e.aiDraftError}function Lt(e){return e.aiDraftResult}function Bt(e){return Lt(e)}function Dt(e){return e.sidebarOpen}function jt(e){return e.aiTourLoading}function Ut(e){return e.aiTourError}function Ft(e){return e.lastFailureContext}function qt(e){return e.ephemeralTour}function Mt(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}function*Vt(){yield oe(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield ce(e)}catch(e){yield se(e.message||"Failed to fetch tours")}}function*Gt(e){yield oe(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield ie(t)}catch(e){yield se(e.message||"Failed to fetch tour")}}function*Ht(e){yield oe(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield ce(t)}catch(e){yield se(e.message||"Failed to fetch tours")}}function Wt(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,a.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,a.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,a.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function $t(){try{const e=(0,a.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:zt(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function zt(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function Kt(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:Xt(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:Xt(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:Xt(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:Xt(o)});const s=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of s){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function Xt(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function Yt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function Qt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let s=null;for(const e of o)if(s=r.querySelector(e),s)break;if(s||(s=r),s.scrollIntoView({behavior:"smooth",block:"center"}),s.focus(),"true"===s.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(s),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",s.tagName,s.className),!0}const Jt={API_FETCH:e=>Q()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:$t(),visibleElements:Wt(),uiSamples:Kt(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,a.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&Yt(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,a.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&Yt(e.attributes?.content));return t&&(await(0,a.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await Qt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,a.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,a.select)("core/block-editor"),s=(r?.getBlocks()||[]).length;return await t.insertBlock(n,s,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await Qt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Q()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Q()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Q()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Q()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Q()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:window.adminCoachTours?.locale||""}}),FETCH_AI_TASKS:()=>Q()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},Zt="admin-coach-tours",en=(0,a.createReduxStore)(Zt,{reducer:function(e=te,t){switch(t.type){case ne.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case ne.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case ne.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case ne.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case ne.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case ne.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case ne.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case ne.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case ne.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case ne.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case ne.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case ne.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case ne.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case ne.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case ne.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case ne.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case ne.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case ne.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case ne.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case ne.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case ne.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case ne.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:s,selectors:c,resolvers:i,controls:Jt,initialState:te});(0,a.select)(Zt)||(0,a.register)(en);const tn="admin-coach-tours";function nn(){console.log("[ACT Pupil] Initializing... v4"),console.log("[ACT Pupil] TourRunner:",typeof X,X),console.log("[ACT Pupil] AI Available:",window.adminCoachTours?.aiAvailable);const e=document.createElement("div");e.id="admin-coach-tours-pupil",document.body.appendChild(e);try{(0,l.render)((0,m.jsx)(X,{}),e),console.log("[ACT Pupil] TourRunner rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering TourRunner:",e)}const t=document.createElement("div");t.id="admin-coach-tours-launcher",document.body.appendChild(t);try{(0,l.render)((0,m.jsx)(ee,{}),t),console.log("[ACT Pupil] PupilLauncher rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering PupilLauncher:",e)}const n=window.top||window,r=new URLSearchParams(n.location.search).get("act_tour");if(console.log("[ACT Pupil] URL search:",n.location.search),console.log("[ACT Pupil] act_tour param:",r),r){const e=parseInt(r,10);console.log("[ACT Pupil] Will fetch and start tour:",e);const t=(0,a.select)(tn).getTour(e);console.log("[ACT Pupil] Initial tour state:",t);const n=(0,a.subscribe)(()=>{const t=(0,a.select)(tn),r=t.getTour(e),o=t.isToursLoading();r&&!o&&(console.log("[ACT Pupil] Tour loaded:",r),console.log("[ACT Pupil] Tour steps:",r.steps,"count:",r.steps?.length),n(),(0,a.dispatch)(tn).startTour(e))});setTimeout(()=>{console.log("[ACT Pupil] Timeout reached, unsubscribing"),n()},1e4)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",nn):nn()})(); \ No newline at end of file +(0,u.__)("Could not find the %s block. Make sure to insert or select the correct block, then try again.","admin-coach-tours"),e):(0,u.__)("The generated tour could not complete. Try again to get a fresh set of instructions.","admin-coach-tours");N(t)}}var c;if(A.completion&&n){if(console.log("[ACT TourRunner] Setting up completion watcher for step:",C,"type:",A.completion.type,"params:",A.completion.params),await new Promise(e=>setTimeout(e,100)),!e)return;const t=function(e,t=null){if(!e||!e.type)return M();const{type:n,params:r={}}=e,o=e.timeout||0;switch(n){case"clickTarget":return t?{promise:U(t,{timeout:o}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for clickTarget"}),cancel:()=>{}};case"domValueChanged":return t?{promise:F(t,{timeout:o,...r}),cancel:()=>{}}:{promise:Promise.resolve({success:!1,error:"No target element for domValueChanged"}),cancel:()=>{}};case"wpData":return{promise:q({timeout:o,...r}),cancel:()=>{}};case"manual":default:return M({timeout:o});case"elementAppear":return{promise:V(r.selector,{timeout:o}),cancel:()=>{}};case"elementDisappear":return{promise:G(r.selector,{timeout:o}),cancel:()=>{}};case"customEvent":return{promise:H(r.eventName,{timeout:o}),cancel:()=>{}}}}(A.completion,n);y.current=t,t.promise.then(async t=>{if(e&&t.success)if(console.log("[ACT TourRunner] Completion detected for step:",C),C{e&&(console.log("[ACT TourRunner] Auto-advancing from step:",C),R())},300)}else console.log("[ACT TourRunner] Last step completed, ending tour"),O(),R()})}})(),()=>{e=!1,y.current?.cancel&&(console.log("[ACT TourRunner] Cleanup: Cancelling completion watcher"),y.current.cancel(),y.current=null)}},[S,A,C,h,w,N]);const B=(0,l.useCallback)(async()=>{if(y.current?.confirm)y.current.confirm();else{if(C{y.current?.cancel&&(y.current.cancel(),y.current=null),g(e=>e+1),P()},[P]),W=(0,l.useCallback)(()=>{y.current?.cancel&&(y.current.cancel(),y.current=null),O(),_.current=null,w()},[w]);if(!S||!b||!A)return null;const X=(0,m.jsx)(T,{step:A,stepIndex:C,totalSteps:I,tourTitle:b.title,targetElement:e,resolutionError:n,expectedBlockType:o,isApplyingPreconditions:d,onContinue:B,onRepeat:j,onPrevious:v,onNext:R,onStop:W});return(0,l.createPortal)(X,document.body)}const Y="admin-coach-tours",Q={media:"🖼️",content:"📝",layout:"📐",formatting:"✨",default:"📚"};function J(){const[e,t]=(0,l.useState)(!1),[n,r]=(0,l.useState)([]),[o,s]=(0,l.useState)(!1),[c,i]=(0,l.useState)(null),[p,h]=(0,l.useState)(""),[g,T]=(0,l.useState)("tasks"),[f,E]=(0,l.useState)(!1),[_,y]=(0,l.useState)(null),S=(0,l.useRef)(null),{storeLoading:b,aiTourError:A,isPlaying:C,aiAvailable:k,lastFailureContext:I}=(0,a.useSelect)(e=>{const t=e(Y);return{storeLoading:t.isAiTourLoading?.()??!1,aiTourError:t.getAiTourError?.()??null,isPlaying:null!==t.getCurrentTour(),aiAvailable:window.adminCoachTours?.aiAvailable??!1,lastFailureContext:t.getLastFailureContext?.()??null}},[]),w=f||b;(0,l.useEffect)(()=>{C&&f&&E(!1)},[C,f]),(0,l.useEffect)(()=>{!A||C||e||t(!0)},[A,C,e]);const{requestAiTour:R,clearEphemeralTour:v,setAiTourError:x,setLastFailureContext:O,fetchAiTasks:P}=(0,a.useDispatch)(Y);(0,l.useEffect)(()=>{e&&0===n.length&&!o&&(s(!0),i(null),P().then(e=>{e.available&&e.tasks?r(e.tasks):i((0,u.__)("AI is not available.","admin-coach-tours"))}).catch(e=>{i(e.message||(0,u.__)("Failed to load tasks.","admin-coach-tours"))}).finally(()=>{s(!1)}))},[e,n.length,o]),(0,l.useEffect)(()=>{"chat"===g&&S.current&&S.current.focus()},[g]);const N=(0,l.useCallback)(e=>{E(!0),t(!1);const n=window.adminCoachTours?.postType||"post";y({type:"task",taskId:e,postType:n}),R(e,"",n)},[R]),L=(0,l.useCallback)(e=>{if(e.preventDefault(),!p.trim())return;E(!0),t(!1);const n=window.adminCoachTours?.postType||"post",r=p.trim();y({type:"chat",query:r,postType:n}),R("",r,n),h("")},[p,R]),B=(0,l.useCallback)(()=>{_&&(x(null),E(!0),t(!1),"task"===_.type?R(_.taskId,"",_.postType,I):"chat"===_.type&&R("",_.query,_.postType,I),O(null))},[_,I,R,x,O]),D=(0,l.useCallback)(()=>{x(null),y(null),O(null),E(!1)},[x,O]),j=(0,l.useCallback)(()=>{i(null),r([])},[]),U=(0,l.useCallback)(()=>{t(e=>!e)},[]),F=(0,l.useCallback)(()=>{t(!1)},[]);if(!k)return(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:U,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),children:(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("AI Coach","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:F,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsx)("div",{className:"act-pupil-launcher__content",children:(0,m.jsxs)("div",{className:"act-pupil-launcher__not-configured",children:[(0,m.jsx)("p",{children:(0,u.__)("AI is not configured yet.","admin-coach-tours")}),(0,m.jsxs)("p",{children:[(0,u.__)("Go to ","admin-coach-tours"),(0,m.jsx)("a",{href:"/wp-admin/tools.php?page=admin-coach-tours",children:(0,u.__)("Tools → Coach Tours","admin-coach-tours")}),(0,u.__)(" to add your API key.","admin-coach-tours")]})]})})]})]});const q=n.reduce((e,t)=>{const n=t.category||"other";return e[n]||(e[n]=[]),e[n].push(t),e},{}),M=w&&(0,l.createPortal)((0,m.jsx)("div",{className:"act-ai-loading-overlay",children:(0,m.jsxs)("div",{className:"act-ai-loading-overlay__content",children:[(0,m.jsx)("div",{className:"act-ai-loading-overlay__spinner"}),(0,m.jsx)("h2",{className:"act-ai-loading-overlay__title",children:(0,u.__)("Creating your guided tour...","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__text",children:(0,u.__)("AI is analyzing the editor and generating step-by-step instructions.","admin-coach-tours")}),(0,m.jsx)("p",{className:"act-ai-loading-overlay__hint",children:(0,u.__)("This usually takes a few seconds.","admin-coach-tours")})]})}),document.body);return C?M||null:(0,m.jsxs)(m.Fragment,{children:[M,(0,m.jsxs)("div",{className:"act-pupil-launcher",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__fab",onClick:U,"aria-expanded":e,"aria-label":(0,u.__)("Open AI Coach","admin-coach-tours"),disabled:w,children:w?(0,m.jsx)("span",{className:"act-pupil-launcher__spinner"}):(0,m.jsx)("span",{className:"act-pupil-launcher__icon",children:"💡"})}),e&&(0,m.jsxs)("div",{className:"act-pupil-launcher__panel",children:[w&&(0,m.jsxs)("div",{className:"act-pupil-launcher__ai-loading",children:[(0,m.jsx)("div",{className:"act-pupil-launcher__ai-loading-spinner"}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-text",children:(0,u.__)("Creating your tour...","admin-coach-tours")}),(0,m.jsx)("span",{className:"act-pupil-launcher__ai-loading-hint",children:(0,u.__)("AI is generating step-by-step instructions","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__header",children:[(0,m.jsx)("h3",{children:(0,u.__)("What would you like to learn?","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__close",onClick:F,"aria-label":(0,u.__)("Close","admin-coach-tours"),children:"×"})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__tabs",children:[(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("tasks"===g?"is-active":""),onClick:()=>T("tasks"),children:(0,u.__)("Common Tasks","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__tab "+("chat"===g?"is-active":""),onClick:()=>T("chat"),children:(0,u.__)("Ask a Question","admin-coach-tours")})]}),(0,m.jsxs)("div",{className:"act-pupil-launcher__content",children:[A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:A}),(0,m.jsx)("p",{className:"act-pupil-launcher__error-hint",children:(0,u.__)("This might be a temporary issue.","admin-coach-tours")}),(0,m.jsxs)("div",{className:"act-pupil-launcher__error-actions",children:[_&&(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:B,disabled:w,children:(0,u.__)("Try Again","admin-coach-tours")}),(0,m.jsx)("button",{className:"act-pupil-launcher__dismiss-btn",onClick:D,children:(0,u.__)("Dismiss","admin-coach-tours")})]})]}),c&&!A&&(0,m.jsxs)("div",{className:"act-pupil-launcher__error",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__error-message",children:c}),(0,m.jsx)("div",{className:"act-pupil-launcher__error-actions",children:(0,m.jsx)("button",{className:"act-pupil-launcher__retry-btn",onClick:j,children:(0,u.__)("Try Again","admin-coach-tours")})})]}),"tasks"===g&&!c&&(0,m.jsxs)("div",{className:"act-pupil-launcher__tasks",children:[o&&(0,m.jsx)("div",{className:"act-pupil-launcher__loading",children:(0,u.__)("Loading tasks...","admin-coach-tours")}),!o&&Object.entries(q).map(([e,t])=>(0,m.jsxs)("div",{className:"act-pupil-launcher__category",children:[(0,m.jsxs)("h4",{children:[(0,m.jsx)("span",{className:"act-pupil-launcher__category-icon",children:Q[e]||Q.default}),e.charAt(0).toUpperCase()+e.slice(1)]}),(0,m.jsx)("div",{className:"act-pupil-launcher__task-list",children:t.map(e=>(0,m.jsxs)("button",{className:"act-pupil-launcher__task",onClick:()=>N(e.id),disabled:w,children:[(0,m.jsx)("span",{className:"act-pupil-launcher__task-icon",children:e.icon?(0,m.jsx)(d.Dashicon,{icon:e.icon}):"📌"}),(0,m.jsx)("span",{className:"act-pupil-launcher__task-label",children:e.label})]},e.id))})]},e))]}),"chat"===g&&(0,m.jsxs)("div",{className:"act-pupil-launcher__chat",children:[(0,m.jsx)("p",{className:"act-pupil-launcher__chat-hint",children:(0,u.__)("Ask about the WordPress editor:","admin-coach-tours")}),(0,m.jsxs)("form",{onSubmit:L,children:[(0,m.jsx)("input",{ref:S,type:"text",className:"act-pupil-launcher__chat-input",value:p,onChange:e=>h(e.target.value),placeholder:(0,u.__)("e.g., How do I add a gallery?","admin-coach-tours"),disabled:w}),(0,m.jsx)("button",{type:"submit",className:"act-pupil-launcher__chat-submit",disabled:w||!p.trim(),children:w?(0,u.__)("Generating...","admin-coach-tours"):(0,u.__)("Get Tour","admin-coach-tours")})]}),(0,m.jsx)("p",{className:"act-pupil-launcher__chat-note",children:(0,u.__)("AI will create a step-by-step tour to guide you.","admin-coach-tours")})]})]})]})]})]})}const Z={tours:{},toursLoading:!1,toursError:null,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,skippedSteps:[],isPickerActive:!1,pickingStepId:null,selectedStepId:null,pendingChanges:!1,tourProgress:{},isRecovering:!1,lastError:null,resolvedTarget:null,resolutionAttempts:0,sidebarOpen:!1,aiDraftLoading:!1,aiDraftError:null,aiDraftResult:null,aiTourLoading:!1,aiTourError:null,ephemeralTour:null,lastFailureContext:null},ee={SET_TOURS_LOADING:"SET_TOURS_LOADING",SET_TOURS_ERROR:"SET_TOURS_ERROR",RECEIVE_TOURS:"RECEIVE_TOURS",RECEIVE_TOUR:"RECEIVE_TOUR",SET_CURRENT_TOUR:"SET_CURRENT_TOUR",START_TOUR:"START_TOUR",END_TOUR:"END_TOUR",SET_CURRENT_STEP:"SET_CURRENT_STEP",NEXT_STEP:"NEXT_STEP",PREVIOUS_STEP:"PREVIOUS_STEP",SKIP_STEP:"SKIP_STEP",REPEAT_STEP:"REPEAT_STEP",SET_MODE:"SET_MODE",SET_COMPLETION_SATISFIED:"SET_COMPLETION_SATISFIED",RESET_COMPLETION:"RESET_COMPLETION",SET_RESOLVED_TARGET:"SET_RESOLVED_TARGET",CLEAR_RESOLVED_TARGET:"CLEAR_RESOLVED_TARGET",SET_RECOVERING:"SET_RECOVERING",INCREMENT_RESOLUTION_ATTEMPTS:"INCREMENT_RESOLUTION_ATTEMPTS",SET_LAST_ERROR:"SET_LAST_ERROR",ACTIVATE_PICKER:"ACTIVATE_PICKER",DEACTIVATE_PICKER:"DEACTIVATE_PICKER",SELECT_STEP:"SELECT_STEP",SET_PENDING_CHANGES:"SET_PENDING_CHANGES",UPDATE_STEP:"UPDATE_STEP",ADD_STEP:"ADD_STEP",DELETE_STEP:"DELETE_STEP",REORDER_STEPS:"REORDER_STEPS",SET_AI_DRAFT_LOADING:"SET_AI_DRAFT_LOADING",SET_AI_DRAFT_ERROR:"SET_AI_DRAFT_ERROR",SET_AI_DRAFT_RESULT:"SET_AI_DRAFT_RESULT",CLEAR_AI_DRAFT:"CLEAR_AI_DRAFT",SET_SIDEBAR_OPEN:"SET_SIDEBAR_OPEN",SET_AI_TOUR_LOADING:"SET_AI_TOUR_LOADING",RECEIVE_EPHEMERAL_TOUR:"RECEIVE_EPHEMERAL_TOUR",SET_AI_TOUR_ERROR:"SET_AI_TOUR_ERROR",CLEAR_EPHEMERAL_TOUR:"CLEAR_EPHEMERAL_TOUR",SET_LAST_FAILURE_CONTEXT:"SET_LAST_FAILURE_CONTEXT"},te=()=>"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx".replace(/[xy]/g,function(e){const t=16*Math.random()|0;return("x"===e?t:3&t|8).toString(16)});function ne(e){return{type:ee.SET_TOURS_LOADING,isLoading:e}}function re(e){return{type:ee.SET_TOURS_ERROR,error:e}}function oe(e){return{type:ee.RECEIVE_TOURS,tours:e}}function se(e){return{type:ee.RECEIVE_TOUR,tour:e}}function*ce(e){if(e){try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};t&&t.id&&(yield se(t))}catch(t){console.log("[ACT] Tour not found, creating placeholder for:",e),yield se({id:e,title:"",steps:[],status:"draft"})}yield{type:ee.SET_CURRENT_TOUR,tourId:e}}else yield{type:ee.SET_CURRENT_TOUR,tourId:null}}function ie(e={}){return{type:"FETCH_TOURS",args:e}}function le(e){return{type:"FETCH_TOUR",tourId:e}}function*ae(e,t){try{const n=yield{type:"SAVE_TOUR",tourId:e,tourData:t};return n?.id&&(yield se(n)),n}catch(e){throw e}}function*ue(e){try{const t=yield{type:"CREATE_TOUR",data:e};return t?.id&&(yield se(t)),t}catch(e){throw e}}function*de(e,t){try{const n=yield{type:"UPDATE_TOUR",tourId:e,data:t};return n?.id&&(yield se(n)),n}catch(e){throw e}}function pe(e,t="pupil"){return{type:ee.START_TOUR,tourId:e,mode:t}}function me(){return{type:ee.END_TOUR}}function he(){return me()}function ge(e){return{type:ee.SET_CURRENT_STEP,stepIndex:e}}function Te(){return{type:ee.NEXT_STEP}}function fe(){return{type:ee.PREVIOUS_STEP}}function Ee(){return{type:ee.SKIP_STEP}}function _e(){return{type:ee.REPEAT_STEP}}function ye(e){return{type:ee.SET_MODE,mode:e}}function Se(e){return{type:ee.SET_COMPLETION_SATISFIED,satisfied:e}}function be(){return{type:ee.RESET_COMPLETION}}function Ae(){return Te()}function Ce(e){return{type:ee.SET_RESOLVED_TARGET,target:e}}function ke(){return{type:ee.CLEAR_RESOLVED_TARGET}}function Ie(e){return{type:ee.SET_RECOVERING,isRecovering:e}}function we(){return{type:ee.INCREMENT_RESOLUTION_ATTEMPTS}}function Re(e){return{type:ee.SET_LAST_ERROR,error:e}}function ve(e=null){return{type:ee.ACTIVATE_PICKER,stepId:e}}function xe(e=null){return ve(e)}function Oe(){return{type:ee.DEACTIVATE_PICKER}}function Pe(){return Oe()}function Ne(e){return{type:ee.SELECT_STEP,stepId:e}}function Le(e){return{type:ee.SET_PENDING_CHANGES,pending:e}}function*Be(e,t,n){yield{type:ee.UPDATE_STEP,tourId:e,stepId:t,updates:n}}function*De(e,t={},n=null){const r={id:te(),order:0,title:"",instruction:"",hint:"",target:{locators:[],constraints:{visible:!0}},preconditions:[],completion:{type:"manual"},recovery:[{action:"reapplyPreconditions",timeout:1e3}],tags:[],version:1,...t};yield{type:ee.ADD_STEP,tourId:e,step:r,index:n}}function*je(e,t){yield{type:ee.DELETE_STEP,tourId:e,stepId:t}}function*Ue(e,t){yield{type:ee.REORDER_STEPS,tourId:e,stepIds:t}}function Fe(e){return{type:ee.SET_AI_DRAFT_LOADING,isLoading:e}}function qe(e){return{type:ee.SET_AI_DRAFT_ERROR,error:e}}function Me(e){return{type:ee.SET_AI_DRAFT_RESULT,result:e}}function Ve(){return{type:ee.CLEAR_AI_DRAFT}}function*Ge(e,t){yield Fe(!0),yield qe(null);try{const n=yield{type:"REQUEST_AI_DRAFT",elementContext:e,postType:t};return yield Me(n),n}catch(e){throw yield qe(e.message||"Failed to generate AI draft"),e}finally{yield Fe(!1)}}function He(e){return{type:ee.SET_SIDEBAR_OPEN,isOpen:e}}function We(e){return{type:ee.SET_AI_TOUR_LOADING,isLoading:e}}function $e(e){return{type:ee.SET_AI_TOUR_ERROR,error:e}}function ze(e){return{type:ee.SET_LAST_FAILURE_CONTEXT,failureContext:e}}function Ke(e){return{type:ee.RECEIVE_EPHEMERAL_TOUR,tour:e}}function Xe(){return{type:ee.CLEAR_EPHEMERAL_TOUR}}function*Ye(e,t,n,r=null){yield We(!0),yield $e(null);const o=window.adminCoachTours?.locale||"";try{const s=yield{type:"GATHER_EDITOR_CONTEXT"},c=yield{type:"REQUEST_AI_TOUR",taskId:e,query:t,postType:n,editorContext:s,failureContext:r,locale:o};console.log("[ACT AI Response] Full result:",c),console.log("[ACT AI Response] Tour:",JSON.stringify(c.tour,null,2));const i={id:"ephemeral",...c.tour};return yield Ke(i),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield pe("ephemeral","pupil"),i}catch(e){throw yield $e(e.message||"Failed to generate tour"),e}finally{yield We(!1)}}function*Qe(){return yield{type:"FETCH_AI_TASKS"}}function*Je(e){const t={id:"ephemeral",...e};yield Ke(t),yield{type:"ENSURE_EMPTY_PLACEHOLDER"},yield pe("ephemeral","pupil")}function Ze(e){return e.tours}const et=(0,a.createSelector)(e=>Object.values(e.tours),e=>[e.tours]);function tt(e,t){return e.tours[t]||null}function nt(e){return e.toursLoading}function rt(e){return e.toursError}const ot=(0,a.createSelector)((e,t)=>et(e).filter(e=>e.postTypes&&e.postTypes.includes(t)&&"publish"===e.status),(e,t)=>[e.tours,t]),st=(0,a.createSelector)((e,t)=>et(e).filter(e=>e.editor===t&&"publish"===e.status),(e,t)=>[e.tours,t]);function ct(e){return e.currentTourId}function it(e){return e.currentTourId?e.tours[e.currentTourId]:null}function lt(e){return e.currentStepIndex}const at=(0,a.createSelector)(e=>{const t=it(e);return t&&t.steps&&t.steps[e.currentStepIndex]||null},e=>[e.tours,e.currentTourId,e.currentStepIndex]);function ut(e){const t=it(e);return t?.steps?.length||0}function dt(e){return e.currentStepIndex0}function mt(e){const t=ut(e);return 0===t?0:Math.round((e.currentStepIndex+1)/t*100)}function ht(e){return e.mode}function gt(e){return"educator"===e.mode}function Tt(e){return"pupil"===e.mode}function ft(e){return null!==e.currentTourId&&null!==e.mode}function Et(e){return e.completionSatisfied}function _t(e){return e.skippedSteps}function yt(e,t){return e.skippedSteps.includes(t)}function St(e){return e.resolvedTarget}function bt(e){return e.isRecovering}function At(e){return e.resolutionAttempts}function Ct(e){return e.lastError}function kt(e){return e.isPickerActive}function It(e){return e.pickingStepId||null}function wt(e){return e.selectedStepId}const Rt=(0,a.createSelector)(e=>{const t=it(e);return t&&e.selectedStepId?t.steps.find(t=>t.id===e.selectedStepId):null},e=>[e.tours,e.currentTourId,e.selectedStepId]);function vt(e){return e.pendingChanges}function xt(e){return e.aiDraftLoading}function Ot(e){return xt(e)}function Pt(e){return e.aiDraftError}function Nt(e){return e.aiDraftResult}function Lt(e){return Nt(e)}function Bt(e){return e.sidebarOpen}function Dt(e){return e.aiTourLoading}function jt(e){return e.aiTourError}function Ut(e){return e.lastFailureContext}function Ft(e){return e.ephemeralTour}function qt(e){return"ephemeral"===e.currentTourId&&"pupil"===e.mode}const Mt=window.wp.apiFetch;var Vt=o.n(Mt);function*Gt(){yield ne(!0);try{const e=yield{type:"API_FETCH",request:{path:"/admin-coach-tours/v1/tours",method:"GET"}};yield oe(e)}catch(e){yield re(e.message||"Failed to fetch tours")}}function*Ht(e){yield ne(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours/${e}`,method:"GET"}};yield se(t)}catch(e){yield re(e.message||"Failed to fetch tour")}}function*Wt(e){yield ne(!0);try{const t=yield{type:"API_FETCH",request:{path:`/admin-coach-tours/v1/tours?post_type=${e}&editor=block`,method:"GET"}};yield oe(t)}catch(e){yield re(e.message||"Failed to fetch tours")}}function $t(){const e={inserterOpen:!1,sidebarOpen:!1,sidebarTab:null,toolbarVisible:!1,hasSelectedBlock:!1,selectedBlockType:null};try{const t=(0,a.select)("core/editor");t?.isInserterOpened&&(e.inserterOpen=t.isInserterOpened());const n=(0,a.select)("core/edit-post");if(n?.getActiveGeneralSidebarName){const t=n.getActiveGeneralSidebarName();e.sidebarOpen=!!t,e.sidebarTab=t||null}const r=(0,a.select)("core/block-editor");if(r?.getSelectedBlock){const t=r.getSelectedBlock();e.hasSelectedBlock=!!t,e.selectedBlockType=t?.name||null}e.toolbarVisible=!!document.querySelector(".block-editor-block-toolbar")}catch(e){console.warn("[ACT] Error getting visible elements:",e)}return e}function zt(){try{const e=(0,a.select)("core/block-editor");if(!e?.getBlocks)return[];const t=e.getBlocks(),n=e.getSelectedBlockClientId?.()||null,r=document.querySelector('iframe[name="editor-canvas"]'),o=r?.contentDocument||null;return t.map((e,t)=>{const r={name:e.name,clientId:e.clientId,isEmpty:Kt(e),isSelected:e.clientId===n,order:t};if(o){const t=o.querySelector(`[data-block="${e.clientId}"]`);t&&(r.domInfo={tagName:t.tagName.toLowerCase(),dataType:t.getAttribute("data-type"),dataBlock:e.clientId,hasRichText:!!t.querySelector(".block-editor-rich-text__editable"),editableSelector:t.querySelector(".block-editor-rich-text__editable")?`[data-block="${e.clientId}"] .block-editor-rich-text__editable`:null})}return r})}catch(e){return console.warn("[ACT] Error getting editor blocks:",e),[]}}function Kt(e){return!(e&&("core/paragraph"===e.name?e.attributes?.content&&""!==e.attributes.content:"core/image"===e.name?e.attributes?.url:"core/video"!==e.name||e.attributes?.src))}function Xt(){const e={inserterButton:null,publishButton:null,settingsButton:null,searchInput:null,emptyBlockPlaceholder:null};try{const t=[".editor-document-tools__inserter-toggle","button.block-editor-inserter-toggle",'[aria-label="Toggle block inserter"]'];for(const n of t){const t=document.querySelector(n);if(t){e.inserterButton={selector:n,ariaLabel:t.getAttribute("aria-label")||null,visible:Yt(t)};break}}const n=[".editor-post-publish-button",".editor-post-save-draft"];for(const t of n){const n=document.querySelector(t);if(n){e.publishButton={selector:t,text:n.textContent?.trim()||null,visible:Yt(n)};break}}const r=document.querySelector('button[aria-label="Settings"]');r&&(e.settingsButton={selector:'button[aria-label="Settings"]',visible:Yt(r)});const o=document.querySelector(".components-search-control__input");o&&(e.searchInput={selector:".components-search-control__input",visible:Yt(o)});const s=[{selector:".block-editor-default-block-appender__content",inIframe:!0},{selector:'[data-empty="true"] .block-editor-rich-text__editable',inIframe:!0},{selector:'p[data-empty="true"]',inIframe:!0},{selector:".block-editor-default-block-appender__content",inIframe:!1}];for(const{selector:t,inIframe:n}of s){let r=null;if(n){const e=document.querySelector('iframe[name="editor-canvas"]');e?.contentDocument&&(r=e.contentDocument.querySelector(t))}else r=document.querySelector(t);if(r){e.emptyBlockPlaceholder={selector:t,inIframe:n,placeholder:r.getAttribute("data-placeholder")||r.getAttribute("aria-label")||null,visible:!0};break}}}catch(e){console.warn("[ACT] Error sampling UI elements:",e)}return e}function Yt(e){if(!e)return!1;const t=e.getBoundingClientRect(),n=window.getComputedStyle(e);return t.width>0&&t.height>0&&"hidden"!==n.visibility&&"none"!==n.display}function Qt(e){if(null==e||""===e)return!0;if("string"==typeof e)return""===e.trim();if("object"==typeof e&&null!==e){if("number"==typeof e.length)return 0===e.length;if("function"==typeof e.toString){const t=e.toString();if("[object Object]"!==t)return""===t.trim()}if("function"==typeof e.toJSON){const t=e.toJSON();if("string"==typeof t)return""===t.trim()}}return!(!Array.isArray(e)||0!==e.length)}async function Jt(e){await new Promise(e=>setTimeout(e,100));const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument||document,r=n.querySelector(`[data-block="${e}"]`);if(!r)return console.warn("[ACT focusBlock] Block element not found:",e),!1;const o=['[contenteditable="true"]',".block-editor-rich-text__editable","textarea",'input[type="text"]'];let s=null;for(const e of o)if(s=r.querySelector(e),s)break;if(s||(s=r),s.scrollIntoView({behavior:"smooth",block:"center"}),s.focus(),"true"===s.getAttribute("contenteditable")){const e=n.getSelection(),t=n.createRange();t.selectNodeContents(s),t.collapse(!1),e?.removeAllRanges(),e?.addRange(t)}return console.log("[ACT focusBlock] Focused:",s.tagName,s.className),!0}const Zt={API_FETCH:e=>Vt()(e.request),GATHER_EDITOR_CONTEXT:()=>({editorBlocks:zt(),visibleElements:$t(),uiSamples:Xt(),wpVersion:window.adminCoachTours?.wpVersion||"unknown",timestamp:Date.now()}),ENSURE_EMPTY_PLACEHOLDER:()=>async function(){if(function(){try{const e=(0,a.select)("core/block-editor");return!!e?.getBlocks&&!!e.getBlocks().find(e=>"core/paragraph"===e.name&&Qt(e.attributes?.content))}catch(e){return console.warn("[ACT] Error checking for empty paragraph:",e),!1}}()){const e=(0,a.select)("core/block-editor"),t=(e?.getBlocks()||[]).find(e=>"core/paragraph"===e.name&&Qt(e.attributes?.content));return t&&(await(0,a.dispatch)("core/block-editor").selectBlock(t.clientId),console.log("[ACT] Selected existing empty paragraph:",t.clientId),await Jt(t.clientId)),{wasInserted:!1,clientId:t?.clientId||null}}console.log("[ACT] No empty paragraph found, inserting one");const e=await async function(){try{const{createBlock:e}=await Promise.resolve().then(o.t.bind(o,997,23)),t=(0,a.dispatch)("core/block-editor");if(!t||!e)return console.warn("[ACT] Block editor not available for inserting paragraph"),null;const n=e("core/paragraph",{content:""}),r=(0,a.select)("core/block-editor"),s=(r?.getBlocks()||[]).length;return await t.insertBlock(n,s,"",!1),await t.selectBlock(n.clientId),console.log("[ACT] Inserted and selected empty paragraph block:",n.clientId),n.clientId}catch(e){return console.error("[ACT] Error inserting empty paragraph:",e),null}}();return e?(await async function(e,t=3e3,n=50){const r=Date.now();for(;Date.now()-rsetTimeout(e,n))}return!1}(()=>{const t=document.querySelector('iframe[name="editor-canvas"]'),n=t?.contentDocument;return!!(n||document).querySelector(`[data-block="${e}"]`)},3e3)?(console.log("[ACT] Block appeared in DOM:",e),await Jt(e)):console.warn("[ACT] Block inserted but not found in DOM:",e),{wasInserted:!0,clientId:e}):{wasInserted:!1,clientId:null}}(),FETCH_TOUR:e=>Vt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"GET"}),FETCH_TOURS(e){const t=new URLSearchParams;e.args.postType&&t.append("post_type",e.args.postType),e.args.editor&&t.append("editor",e.args.editor);const n=t.toString(),r="/admin-coach-tours/v1/tours"+(n?`?${n}`:"");return Vt()({path:r,method:"GET"})},SAVE_TOUR:e=>(console.log("[ACT Controls] SAVE_TOUR:",e.tourId,e.tourData),console.log("[ACT Controls] Steps count:",e.tourData?.steps?.length),Vt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.tourData})),CREATE_TOUR:e=>Vt()({path:"/admin-coach-tours/v1/tours",method:"POST",data:e.data}),UPDATE_TOUR:e=>Vt()({path:`/admin-coach-tours/v1/tours/${e.tourId}`,method:"PUT",data:e.data}),REQUEST_AI_DRAFT:e=>Vt()({path:"/admin-coach-tours/v1/ai/generate-draft",method:"POST",data:{elementContext:e.elementContext,postType:e.postType}}),REQUEST_AI_TOUR:e=>Vt()({path:"/admin-coach-tours/v1/ai/generate-tour",method:"POST",data:{taskId:e.taskId,query:e.query,postType:e.postType,editorContext:e.editorContext||null,failureContext:e.failureContext||null,locale:e.locale||""}}),FETCH_AI_TASKS:()=>Vt()({path:"/admin-coach-tours/v1/ai/tasks",method:"GET"})},en="admin-coach-tours",tn=(0,a.createReduxStore)(en,{reducer:function(e=Z,t){switch(t.type){case ee.SET_TOURS_LOADING:return{...e,toursLoading:t.isLoading};case ee.SET_TOURS_ERROR:return{...e,toursError:t.error,toursLoading:!1};case ee.RECEIVE_TOURS:return{...e,tours:t.tours.reduce((e,t)=>(e[t.id]=t,e),{...e.tours}),toursLoading:!1,toursError:null};case ee.RECEIVE_TOUR:return{...e,tours:{...e.tours,[t.tour.id]:t.tour},toursLoading:!1};case ee.SET_CURRENT_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.tourId?"educator":null,selectedStepId:null};case ee.START_TOUR:return{...e,currentTourId:t.tourId,currentStepIndex:0,mode:t.mode||"pupil",completionSatisfied:!1,skippedSteps:[],lastError:null,resolutionAttempts:0};case ee.END_TOUR:return{...e,currentTourId:null,currentStepIndex:0,mode:null,completionSatisfied:!1,resolvedTarget:null,isRecovering:!1,lastError:null};case ee.SET_CURRENT_STEP:return{...e,currentStepIndex:t.stepIndex,completionSatisfied:!1,resolvedTarget:null,resolutionAttempts:0,lastError:null};case ee.NEXT_STEP:{const t=e.tours[e.currentTourId],n=e.currentStepIndex+1;return t&&ne.id===t.stepId?{...e,...t.updates}:e);return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},pendingChanges:!0}}case ee.ADD_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=[...n.steps],o=t.index??r.length;return r.splice(o,0,t.step),r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:t.step.id,pendingChanges:!0}}case ee.DELETE_STEP:{const n=e.tours[t.tourId];if(!n)return e;const r=n.steps.filter(e=>e.id!==t.stepId);return r.forEach((e,t)=>{e.order=t}),{...e,tours:{...e.tours,[t.tourId]:{...n,steps:r}},selectedStepId:e.selectedStepId===t.stepId?null:e.selectedStepId,pendingChanges:!0}}case ee.REORDER_STEPS:{const n=e.tours[t.tourId];if(!n)return e;const r={};n.steps.forEach(e=>{r[e.id]=e});const o=t.stepIds.map((e,t)=>({...r[e],order:t}));return{...e,tours:{...e.tours,[t.tourId]:{...n,steps:o}},pendingChanges:!0}}case ee.SET_AI_DRAFT_LOADING:return{...e,aiDraftLoading:t.isLoading,aiDraftError:t.isLoading?null:e.aiDraftError};case ee.SET_AI_DRAFT_ERROR:return{...e,aiDraftError:t.error,aiDraftLoading:!1};case ee.SET_AI_DRAFT_RESULT:return{...e,aiDraftResult:t.result,aiDraftLoading:!1,aiDraftError:null};case ee.CLEAR_AI_DRAFT:return{...e,aiDraftResult:null,aiDraftError:null,aiDraftLoading:!1};case ee.SET_SIDEBAR_OPEN:return{...e,sidebarOpen:t.isOpen};case ee.SET_AI_TOUR_LOADING:return{...e,aiTourLoading:t.isLoading,aiTourError:t.isLoading?null:e.aiTourError};case ee.SET_AI_TOUR_ERROR:return{...e,aiTourError:t.error,aiTourLoading:!1};case ee.RECEIVE_EPHEMERAL_TOUR:return{...e,ephemeralTour:t.tour,aiTourLoading:!1,aiTourError:null,tours:{...e.tours,ephemeral:t.tour}};case ee.CLEAR_EPHEMERAL_TOUR:return{...e,ephemeralTour:null,aiTourError:null,aiTourLoading:!1,lastFailureContext:null,tours:Object.fromEntries(Object.entries(e.tours).filter(([e])=>"ephemeral"!==e))};case ee.SET_LAST_FAILURE_CONTEXT:return{...e,lastFailureContext:t.failureContext};default:return e}},actions:s,selectors:c,resolvers:i,controls:Zt,initialState:Z});(0,a.select)(en)||(0,a.register)(tn);const nn="admin-coach-tours";function rn(){console.log("[ACT Pupil] Initializing... v4"),console.log("[ACT Pupil] TourRunner:",typeof X,X),console.log("[ACT Pupil] AI Available:",window.adminCoachTours?.aiAvailable);const e=document.createElement("div");e.id="admin-coach-tours-pupil",document.body.appendChild(e);try{(0,l.render)((0,m.jsx)(X,{}),e),console.log("[ACT Pupil] TourRunner rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering TourRunner:",e)}const t=document.createElement("div");t.id="admin-coach-tours-launcher",document.body.appendChild(t);try{(0,l.render)((0,m.jsx)(J,{}),t),console.log("[ACT Pupil] PupilLauncher rendered successfully")}catch(e){console.error("[ACT Pupil] Error rendering PupilLauncher:",e)}const n=window.top||window,r=new URLSearchParams(n.location.search).get("act_tour");if(console.log("[ACT Pupil] URL search:",n.location.search),console.log("[ACT Pupil] act_tour param:",r),r){const e=parseInt(r,10);console.log("[ACT Pupil] Will fetch and start tour:",e);const t=(0,a.select)(nn).getTour(e);console.log("[ACT Pupil] Initial tour state:",t);const n=(0,a.subscribe)(()=>{const t=(0,a.select)(nn),r=t.getTour(e),o=t.isToursLoading();r&&!o&&(console.log("[ACT Pupil] Tour loaded:",r),console.log("[ACT Pupil] Tour steps:",r.steps,"count:",r.steps?.length),n(),(0,a.dispatch)(nn).startTour(e))});setTimeout(()=>{console.log("[ACT Pupil] Timeout reached, unsubscribing"),n()},1e4)}}"loading"===document.readyState?document.addEventListener("DOMContentLoaded",rn):rn()})(); \ No newline at end of file diff --git a/build/settings.asset.php b/build/settings.asset.php index d22e62d..75e46fe 100644 --- a/build/settings.asset.php +++ b/build/settings.asset.php @@ -1 +1 @@ - array(), 'version' => '50e337b04598bcc38057'); + array(), 'version' => 'cc7122902a5a681b58ce'); diff --git a/php/AI/AiManager.php b/php/AI/AiManager.php index 7fbe10d..91d98ac 100644 --- a/php/AI/AiManager.php +++ b/php/AI/AiManager.php @@ -596,40 +596,11 @@ private function validate_and_sanitize_tour( array $content ): array|\WP_Error { 'steps' => [], ]; - $allowed_completion_types = [ - 'clickTarget', - 'domValueChanged', - 'manual', - 'wpData', - 'elementAppear', - 'elementDisappear', - 'customEvent', - ]; + $allowed_completion_types = TourSchema::COMPLETION_TYPES; - $allowed_precondition_types = [ - 'ensureEditor', - 'ensureSidebarOpen', - 'ensureSidebarClosed', - 'selectSidebarTab', - 'openInserter', - 'closeInserter', - 'selectBlock', - 'focusElement', - 'scrollIntoView', - 'openModal', - 'closeModal', - 'insertBlock', - ]; + $allowed_precondition_types = TourSchema::PRECONDITION_TYPES; - $allowed_locator_types = [ - 'css', - 'role', - 'testId', - 'dataAttribute', - 'ariaLabel', - 'contextual', - 'wpBlock', - ]; + $allowed_locator_types = TourSchema::LOCATOR_TYPES; foreach ( $content[ 'steps' ] as $index => $step ) { $sanitized_step = [ diff --git a/php/AI/TaskPrompts.php b/php/AI/TaskPrompts.php index e63f18b..5501a5b 100644 --- a/php/AI/TaskPrompts.php +++ b/php/AI/TaskPrompts.php @@ -216,21 +216,25 @@ private static function get_language_name_from_locale( string $locale ): string /** * Get the system prompt for tour generation. * - * @param string $task_id The task ID or 'freeform' for custom queries. - * @param string $user_query The user's query (for freeform). - * @param string $gutenberg_context RAG context from GutenbergKnowledgeBase. - * @param string $post_type The current post type. - * @param string $editor_context Current editor state (blocks, UI elements). - * @param string $failure_context Context from previous failed attempt (for retry). - * @param string $locale User's WordPress locale for response language. + * @param string $task_id The task ID or 'freeform' for custom queries. + * @param string $user_query The user's query (for freeform). + * @param string $gutenberg_context RAG context from GutenbergKnowledgeBase. + * @param string $post_type The current post type. + * @param array $editor_context Sanitized editor state (blocks, UI elements). + * @param array|null $failure_context Sanitized context from a previous failed attempt. + * @param string $locale User's WordPress locale for response language. * @return string The system prompt. */ - public static function get_system_prompt( string $task_id, string $user_query, string $gutenberg_context, string $post_type, string $editor_context = '', string $failure_context = '', string $locale = '' ): string { + public static function get_system_prompt( string $task_id, string $user_query, string $gutenberg_context, string $post_type, array $editor_context = [], ?array $failure_context = null, string $locale = '' ): string { $task = self::get_task( $task_id ); // Determine the display language based on locale. $language_instruction = self::get_language_instruction( $locale ); + // Format the structured context arrays into prompt sub-sections. + $editor_context_str = ! empty( $editor_context ) ? self::format_editor_context( $editor_context ) : ''; + $failure_context_str = ! empty( $failure_context ) ? self::format_failure_context( $failure_context ) : ''; + // Base system prompt. $system_prompt = << [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::LOCATOR_TYPES, + ], 'value' => [ 'type' => 'string' ], 'weight' => [ 'type' => 'integer' ], 'fallback' => [ 'type' => 'boolean' ], @@ -653,7 +806,10 @@ public static function get_tour_schema(): array { 'items' => [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::PRECONDITION_TYPES, + ], 'params' => [ 'type' => 'object' ], ], 'required' => [ 'type' ], @@ -662,7 +818,10 @@ public static function get_tour_schema(): array { 'completion' => [ 'type' => 'object', 'properties' => [ - 'type' => [ 'type' => 'string' ], + 'type' => [ + 'type' => 'string', + 'enum' => TourSchema::COMPLETION_TYPES, + ], 'params' => [ 'type' => 'object' ], ], 'required' => [ 'type' ], diff --git a/php/AI/TourGenerator.php b/php/AI/TourGenerator.php new file mode 100644 index 0000000..4294d8b --- /dev/null +++ b/php/AI/TourGenerator.php @@ -0,0 +1,191 @@ +ai = $ai; + } + + /** + * Generate a tour for the given request. + * + * @param TourRequest $request Sanitized request. + * @return array|\WP_Error `[ 'tour' => array, 'cached' => bool ]` or error. + */ + public function generate( TourRequest $request ): array|\WP_Error { + if ( ! $this->ai->is_available() ) { + return new \WP_Error( + 'ai_not_available', + __( 'AI is not configured or enabled.', 'admin-coach-tours' ), + [ 'status' => 503 ] + ); + } + + if ( ! $request->has_input() ) { + return new \WP_Error( + 'missing_input', + __( 'Please provide a task or question.', 'admin-coach-tours' ), + [ 'status' => 400 ] + ); + } + + // Cache lookup — skipped on contextual retries. + $cache_key = null; + if ( ! $request->has_failure_context() ) { + $cache_key = $this->cache_key( $request ); + $cached = $this->get_cached( $cache_key ); + + if ( false !== $cached ) { + return [ + 'tour' => $cached, + 'cached' => true, + ]; + } + } + + $system_prompt = $this->build_system_prompt( $request ); + + $tour = $this->ai->generate_tour( $system_prompt, $request->query() ); + + if ( is_wp_error( $tour ) ) { + return $tour; + } + + if ( null !== $cache_key ) { + $this->cache( $cache_key, $tour ); + } + + return [ + 'tour' => $tour, + 'cached' => false, + ]; + } + + /** + * Assemble the full system prompt from grounding + editor + failure context. + * + * @param TourRequest $request Sanitized request. + * @return string + */ + private function build_system_prompt( TourRequest $request ): string { + $task = '' !== $request->task_id() ? TaskPrompts::get_task( $request->task_id() ) : null; + $search_query = $task ? ( $task[ 'description' ] ?? $request->task_id() ) : $request->query(); + + $context_data = GutenbergKnowledgeBase::get_relevant_context( $search_query, 5 ); + $gutenberg_context = GutenbergKnowledgeBase::format_context_for_prompt( $context_data ); + + return TaskPrompts::get_system_prompt( + $request->task_id(), + $request->query(), + $gutenberg_context, + $request->post_type(), + $request->editor_context(), + $request->failure_context(), + $request->locale() + ); + } + + /** + * Build a cache key from the cache-significant parts of the request. + * + * @param TourRequest $request Sanitized request. + * @return string + */ + private function cache_key( TourRequest $request ): string { + $editor_context = $request->editor_context(); + + $key_data = [ + 'version' => self::CACHE_VERSION, + 'task' => $request->task_id(), + 'query' => $request->query(), + 'post_type' => $request->post_type(), + ]; + + if ( ! empty( $editor_context[ 'editorBlocks' ] ) ) { + $key_data[ 'blocks' ] = array_map( + static function ( $block ) { + return $block[ 'name' ] . ( ! empty( $block[ 'isEmpty' ] ) ? ':empty' : '' ); + }, + $editor_context[ 'editorBlocks' ] + ); + } + + if ( ! empty( $editor_context[ 'visibleElements' ] ) ) { + $key_data[ 'ui' ] = [ + 'inserterOpen' => $editor_context[ 'visibleElements' ][ 'inserterOpen' ] ?? false, + 'sidebarOpen' => $editor_context[ 'visibleElements' ][ 'sidebarOpen' ] ?? false, + ]; + } + + if ( ! empty( $editor_context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ) ) { + $key_data[ 'hasPlaceholder' ] = true; + } + + return 'act_tour_' . md5( (string) wp_json_encode( $key_data ) ); + } + + /** + * Read a cached tour. + * + * @param string $cache_key Cache key. + * @return array|false + */ + private function get_cached( string $cache_key ) { + $cached = get_transient( $cache_key ); + + if ( false !== $cached && is_array( $cached ) ) { + return $cached; + } + + return false; + } + + /** + * Cache a generated tour. + * + * @param string $cache_key Cache key. + * @param array $tour Tour data. + * @return void + */ + private function cache( string $cache_key, array $tour ): void { + $expiration = apply_filters( 'admin_coach_tours_cache_expiration', DAY_IN_SECONDS ); + set_transient( $cache_key, $tour, $expiration ); + } +} diff --git a/php/AI/TourRequest.php b/php/AI/TourRequest.php new file mode 100644 index 0000000..0a5f2d5 --- /dev/null +++ b/php/AI/TourRequest.php @@ -0,0 +1,244 @@ +get_param( 'taskId' ) ?? '' ); + $query = sanitize_text_field( $request->get_param( 'query' ) ?? '' ); + $post_type = sanitize_key( $request->get_param( 'postType' ) ?? 'post' ); + $locale = sanitize_text_field( $request->get_param( 'locale' ) ?? '' ); + + if ( '' === $locale ) { + $locale = get_user_locale(); + } + + $raw_editor = $request->get_param( 'editorContext' ); + $editor_context = is_array( $raw_editor ) ? self::sanitize_editor_context( $raw_editor ) : []; + + $raw_failure = $request->get_param( 'failureContext' ); + $failure_context = is_array( $raw_failure ) ? self::sanitize_failure_context( $raw_failure ) : null; + + return new self( $task_id, $query, $post_type, $locale, $editor_context, $failure_context ); + } + + /** + * Task ID. + * + * @return string + */ + public function task_id(): string { + return $this->task_id; + } + + /** + * Freeform query. + * + * @return string + */ + public function query(): string { + return $this->query; + } + + /** + * Post type. + * + * @return string + */ + public function post_type(): string { + return $this->post_type; + } + + /** + * Resolved locale. + * + * @return string + */ + public function locale(): string { + return $this->locale; + } + + /** + * Sanitized editor context. + * + * @return array + */ + public function editor_context(): array { + return $this->editor_context; + } + + /** + * Sanitized failure context, or null when not retrying. + * + * @return array|null + */ + public function failure_context(): ?array { + return $this->failure_context; + } + + /** + * Whether this request carries failure context (a contextual retry). + * + * @return bool + */ + public function has_failure_context(): bool { + return ! empty( $this->failure_context ); + } + + /** + * Whether the request has a task or a query to act on. + * + * @return bool + */ + public function has_input(): bool { + return '' !== $this->task_id || '' !== $this->query; + } + + /** + * Sanitize editor context from the frontend. + * + * @param array $context Raw editor context. + * @return array Sanitized context. + */ + private static function sanitize_editor_context( array $context ): array { + $sanitized = []; + + // Editor blocks with DOM info. + if ( isset( $context[ 'editorBlocks' ] ) && is_array( $context[ 'editorBlocks' ] ) ) { + $sanitized[ 'editorBlocks' ] = []; + foreach ( array_slice( $context[ 'editorBlocks' ], 0, 20 ) as $block ) { + $block_data = [ + 'name' => isset( $block[ 'name' ] ) ? sanitize_key( $block[ 'name' ] ) : '', + 'isEmpty' => isset( $block[ 'isEmpty' ] ) ? (bool) $block[ 'isEmpty' ] : false, + 'isSelected' => isset( $block[ 'isSelected' ] ) ? (bool) $block[ 'isSelected' ] : false, + 'order' => isset( $block[ 'order' ] ) ? absint( $block[ 'order' ] ) : 0, + 'clientId' => isset( $block[ 'clientId' ] ) ? sanitize_text_field( $block[ 'clientId' ] ) : '', + ]; + + // Include DOM info if available. + if ( isset( $block[ 'domInfo' ] ) && is_array( $block[ 'domInfo' ] ) ) { + $block_data[ 'domInfo' ] = [ + 'tagName' => isset( $block[ 'domInfo' ][ 'tagName' ] ) ? sanitize_key( $block[ 'domInfo' ][ 'tagName' ] ) : '', + 'dataType' => isset( $block[ 'domInfo' ][ 'dataType' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataType' ] ) : '', + 'dataBlock' => isset( $block[ 'domInfo' ][ 'dataBlock' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataBlock' ] ) : '', + 'hasRichText' => isset( $block[ 'domInfo' ][ 'hasRichText' ] ) ? (bool) $block[ 'domInfo' ][ 'hasRichText' ] : false, + 'editableSelector' => isset( $block[ 'domInfo' ][ 'editableSelector' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'editableSelector' ] ) : null, + ]; + } + + $sanitized[ 'editorBlocks' ][] = $block_data; + } + } + + // Visible elements. + if ( isset( $context[ 'visibleElements' ] ) && is_array( $context[ 'visibleElements' ] ) ) { + $ve = $context[ 'visibleElements' ]; + $sanitized[ 'visibleElements' ] = [ + 'inserterOpen' => isset( $ve[ 'inserterOpen' ] ) ? (bool) $ve[ 'inserterOpen' ] : false, + 'sidebarOpen' => isset( $ve[ 'sidebarOpen' ] ) ? (bool) $ve[ 'sidebarOpen' ] : false, + 'sidebarTab' => isset( $ve[ 'sidebarTab' ] ) ? sanitize_key( $ve[ 'sidebarTab' ] ) : null, + 'hasSelectedBlock' => isset( $ve[ 'hasSelectedBlock' ] ) ? (bool) $ve[ 'hasSelectedBlock' ] : false, + 'selectedBlockType' => isset( $ve[ 'selectedBlockType' ] ) ? sanitize_key( $ve[ 'selectedBlockType' ] ) : null, + ]; + } + + // UI samples. + if ( isset( $context[ 'uiSamples' ] ) && is_array( $context[ 'uiSamples' ] ) ) { + $samples = $context[ 'uiSamples' ]; + $sanitized[ 'uiSamples' ] = []; + + foreach ( [ 'inserterButton', 'publishButton', 'settingsButton', 'searchInput' ] as $key ) { + if ( isset( $samples[ $key ] ) && is_array( $samples[ $key ] ) ) { + $sanitized[ 'uiSamples' ][ $key ] = [ + 'selector' => isset( $samples[ $key ][ 'selector' ] ) ? sanitize_text_field( $samples[ $key ][ 'selector' ] ) : null, + 'visible' => isset( $samples[ $key ][ 'visible' ] ) ? (bool) $samples[ $key ][ 'visible' ] : false, + ]; + } + } + + // Handle emptyBlockPlaceholder separately (has additional inIframe property). + if ( isset( $samples[ 'emptyBlockPlaceholder' ] ) && is_array( $samples[ 'emptyBlockPlaceholder' ] ) ) { + $sanitized[ 'uiSamples' ][ 'emptyBlockPlaceholder' ] = [ + 'selector' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) ? sanitize_text_field( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) : null, + 'visible' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] : false, + 'inIframe' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] : false, + ]; + } + } + + return $sanitized; + } + + /** + * Sanitize failure context from the frontend. + * + * @param array $context Raw failure context. + * @return array Sanitized context. + */ + private static function sanitize_failure_context( array $context ): array { + $sanitized = [ + 'stepIndex' => isset( $context[ 'stepIndex' ] ) ? absint( $context[ 'stepIndex' ] ) : 0, + 'stepId' => isset( $context[ 'stepId' ] ) ? sanitize_text_field( $context[ 'stepId' ] ) : '', + 'stepTitle' => isset( $context[ 'stepTitle' ] ) ? sanitize_text_field( $context[ 'stepTitle' ] ) : '', + 'error' => isset( $context[ 'error' ] ) ? sanitize_text_field( $context[ 'error' ] ) : '', + 'reason' => isset( $context[ 'reason' ] ) ? sanitize_text_field( $context[ 'reason' ] ) : '', + ]; + + // Sanitize locators array. + if ( isset( $context[ 'targetLocators' ] ) && is_array( $context[ 'targetLocators' ] ) ) { + $sanitized[ 'targetLocators' ] = []; + foreach ( array_slice( $context[ 'targetLocators' ], 0, 5 ) as $locator ) { + $sanitized[ 'targetLocators' ][] = [ + 'type' => isset( $locator[ 'type' ] ) ? sanitize_key( $locator[ 'type' ] ) : '', + 'value' => isset( $locator[ 'value' ] ) ? sanitize_text_field( $locator[ 'value' ] ) : '', + 'weight' => isset( $locator[ 'weight' ] ) ? absint( $locator[ 'weight' ] ) : 0, + ]; + } + } + + return $sanitized; + } +} diff --git a/php/AI/TourSchema.php b/php/AI/TourSchema.php new file mode 100644 index 0000000..8adb097 --- /dev/null +++ b/php/AI/TourSchema.php @@ -0,0 +1,73 @@ + + */ + public const LOCATOR_TYPES = [ + 'css', + 'role', + 'testId', + 'dataAttribute', + 'ariaLabel', + 'contextual', + 'wpBlock', + ]; + + /** + * Allowed precondition types for a step. + * + * @var array + */ + public const PRECONDITION_TYPES = [ + 'ensureEditor', + 'ensureSidebarOpen', + 'ensureSidebarClosed', + 'selectSidebarTab', + 'openInserter', + 'closeInserter', + 'selectBlock', + 'focusElement', + 'scrollIntoView', + 'openModal', + 'closeModal', + 'insertBlock', + ]; + + /** + * Allowed completion types for a step. + * + * @var array + */ + public const COMPLETION_TYPES = [ + 'clickTarget', + 'domValueChanged', + 'manual', + 'wpData', + 'elementAppear', + 'elementDisappear', + 'customEvent', + ]; +} diff --git a/php/Rest/AiController.php b/php/Rest/AiController.php index 37d8efe..197430f 100644 --- a/php/Rest/AiController.php +++ b/php/Rest/AiController.php @@ -2,7 +2,8 @@ /** * AI Controller. * - * Handles AI-related REST API endpoints. + * Thin HTTP adapter over the AI modules. Parses REST requests, delegates to + * AiManager (drafts) and TourGenerator (tours), and maps results to HTTP. * * @package AdminCoachTours * @since 0.1.0 @@ -14,7 +15,8 @@ use AdminCoachTours\AI\AiManager; use AdminCoachTours\AI\TaskPrompts; -use AdminCoachTours\AI\GutenbergKnowledgeBase; +use AdminCoachTours\AI\TourGenerator; +use AdminCoachTours\AI\TourRequest; /** * AI Controller class. @@ -57,20 +59,7 @@ public static function generate_draft( \WP_REST_Request $request ) { $result = $ai_manager->generate_step_draft( $sanitized_context, $tour_context ); if ( is_wp_error( $result ) ) { - $status = 500; - - if ( 'not_configured' === $result->get_error_code() ) { - $status = 503; - } elseif ( 'api_error' === $result->get_error_code() ) { - $error_data = $result->get_error_data(); - $status = $error_data[ 'status' ] ?? 500; - } - - return new \WP_Error( - $result->get_error_code(), - $result->get_error_message(), - [ 'status' => $status ] - ); + return self::map_error( $result ); } return rest_ensure_response( $result ); @@ -84,7 +73,7 @@ public static function generate_draft( \WP_REST_Request $request ) { public static function get_status() { $ai_manager = AiManager::get_instance(); - $connectors = $ai_manager->get_configured_connectors(); + $connectors = $ai_manager->get_configured_connectors(); $active_provider = $ai_manager->resolve_provider_id(); $status = [ @@ -110,7 +99,84 @@ public static function get_status() { } /** - * Sanitize element context. + * Get available AI tasks for pupils. + * + * @since 0.3.0 + * @return \WP_REST_Response + */ + public static function get_tasks() { + $ai_manager = AiManager::get_instance(); + + $available = $ai_manager->is_available(); + + $response = [ + 'available' => $available, + 'tasks' => [], + ]; + + if ( $available ) { + $response[ 'tasks' ] = TaskPrompts::get_tasks(); + } + + return rest_ensure_response( $response ); + } + + /** + * Generate an AI tour from task or freeform query. + * + * @since 0.3.0 + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response|\WP_Error + */ + public static function generate_tour( \WP_REST_Request $request ) { + $generator = new TourGenerator( AiManager::get_instance() ); + $result = $generator->generate( TourRequest::from_rest( $request ) ); + + if ( is_wp_error( $result ) ) { + return self::map_error( $result ); + } + + return rest_ensure_response( + [ + 'tour' => $result[ 'tour' ], + 'ephemeral' => true, + 'cached' => $result[ 'cached' ], + ] + ); + } + + /** + * Map a generation WP_Error to an HTTP-status-bearing WP_Error. + * + * @param \WP_Error $error The error from the AI layer. + * @return \WP_Error + */ + private static function map_error( \WP_Error $error ): \WP_Error { + $code = $error->get_error_code(); + $status = 500; + + switch ( $code ) { + case 'ai_not_available': + case 'not_configured': + $status = 503; + break; + case 'missing_input': + $status = 400; + break; + case 'out_of_scope': + $status = 422; + break; + case 'api_error': + $data = $error->get_error_data(); + $status = is_array( $data ) ? ( $data[ 'status' ] ?? 500 ) : 500; + break; + } + + return new \WP_Error( $code, $error->get_error_message(), [ 'status' => $status ] ); + } + + /** + * Sanitize element context (single-element draft requests). * * @param array $context Raw context. * @return array Sanitized context. @@ -197,497 +263,4 @@ private static function sanitize_element_context( array $context ): array { return $sanitized; } - - /** - * Sanitize editor context from frontend. - * - * @since 0.3.0 - * @param array $context Raw editor context. - * @return array Sanitized context. - */ - private static function sanitize_editor_context( array $context ): array { - $sanitized = []; - - // Editor blocks with DOM info. - if ( isset( $context[ 'editorBlocks' ] ) && is_array( $context[ 'editorBlocks' ] ) ) { - $sanitized[ 'editorBlocks' ] = []; - foreach ( array_slice( $context[ 'editorBlocks' ], 0, 20 ) as $block ) { - $block_data = [ - 'name' => isset( $block[ 'name' ] ) ? sanitize_key( $block[ 'name' ] ) : '', - 'isEmpty' => isset( $block[ 'isEmpty' ] ) ? (bool) $block[ 'isEmpty' ] : false, - 'isSelected' => isset( $block[ 'isSelected' ] ) ? (bool) $block[ 'isSelected' ] : false, - 'order' => isset( $block[ 'order' ] ) ? absint( $block[ 'order' ] ) : 0, - 'clientId' => isset( $block[ 'clientId' ] ) ? sanitize_text_field( $block[ 'clientId' ] ) : '', - ]; - - // Include DOM info if available. - if ( isset( $block[ 'domInfo' ] ) && is_array( $block[ 'domInfo' ] ) ) { - $block_data[ 'domInfo' ] = [ - 'tagName' => isset( $block[ 'domInfo' ][ 'tagName' ] ) ? sanitize_key( $block[ 'domInfo' ][ 'tagName' ] ) : '', - 'dataType' => isset( $block[ 'domInfo' ][ 'dataType' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataType' ] ) : '', - 'dataBlock' => isset( $block[ 'domInfo' ][ 'dataBlock' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'dataBlock' ] ) : '', - 'hasRichText' => isset( $block[ 'domInfo' ][ 'hasRichText' ] ) ? (bool) $block[ 'domInfo' ][ 'hasRichText' ] : false, - 'editableSelector' => isset( $block[ 'domInfo' ][ 'editableSelector' ] ) ? sanitize_text_field( $block[ 'domInfo' ][ 'editableSelector' ] ) : null, - ]; - } - - $sanitized[ 'editorBlocks' ][] = $block_data; - } - } - - // Visible elements. - if ( isset( $context[ 'visibleElements' ] ) && is_array( $context[ 'visibleElements' ] ) ) { - $ve = $context[ 'visibleElements' ]; - $sanitized[ 'visibleElements' ] = [ - 'inserterOpen' => isset( $ve[ 'inserterOpen' ] ) ? (bool) $ve[ 'inserterOpen' ] : false, - 'sidebarOpen' => isset( $ve[ 'sidebarOpen' ] ) ? (bool) $ve[ 'sidebarOpen' ] : false, - 'sidebarTab' => isset( $ve[ 'sidebarTab' ] ) ? sanitize_key( $ve[ 'sidebarTab' ] ) : null, - 'hasSelectedBlock' => isset( $ve[ 'hasSelectedBlock' ] ) ? (bool) $ve[ 'hasSelectedBlock' ] : false, - 'selectedBlockType' => isset( $ve[ 'selectedBlockType' ] ) ? sanitize_key( $ve[ 'selectedBlockType' ] ) : null, - ]; - } - - // UI samples. - if ( isset( $context[ 'uiSamples' ] ) && is_array( $context[ 'uiSamples' ] ) ) { - $samples = $context[ 'uiSamples' ]; - $sanitized[ 'uiSamples' ] = []; - - foreach ( [ 'inserterButton', 'publishButton', 'settingsButton', 'searchInput' ] as $key ) { - if ( isset( $samples[ $key ] ) && is_array( $samples[ $key ] ) ) { - $sanitized[ 'uiSamples' ][ $key ] = [ - 'selector' => isset( $samples[ $key ][ 'selector' ] ) ? sanitize_text_field( $samples[ $key ][ 'selector' ] ) : null, - 'visible' => isset( $samples[ $key ][ 'visible' ] ) ? (bool) $samples[ $key ][ 'visible' ] : false, - ]; - } - } - - // Handle emptyBlockPlaceholder separately (has additional inIframe property). - if ( isset( $samples[ 'emptyBlockPlaceholder' ] ) && is_array( $samples[ 'emptyBlockPlaceholder' ] ) ) { - $sanitized[ 'uiSamples' ][ 'emptyBlockPlaceholder' ] = [ - 'selector' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) ? sanitize_text_field( $samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) : null, - 'visible' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'visible' ] : false, - 'inIframe' => isset( $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? (bool) $samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] : false, - ]; - } - } - - return $sanitized; - } - - /** - * Format editor context for AI prompt. - * - * @since 0.3.0 - * @param array $context Sanitized editor context. - * @return string Formatted context for prompt. - */ - private static function format_editor_context_for_prompt( array $context ): string { - $lines = [ 'CURRENT EDITOR STATE:' ]; - - // Check for empty block placeholder first - it's a priority starting point. - $has_empty_placeholder = ! empty( $context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ); - - if ( $has_empty_placeholder ) { - $lines[] = '⭐ STARTING POINT AVAILABLE: Empty block placeholder is visible!'; - $lines[] = ' Users can click it and type "/" to add blocks - teach this workflow!'; - } - - // Blocks in editor with targeting options. - if ( ! empty( $context[ 'editorBlocks' ] ) ) { - $lines[] = ''; - $lines[] = 'BLOCKS IN EDITOR (with targeting options):'; - - foreach ( $context[ 'editorBlocks' ] as $block ) { - $status = []; - if ( $block[ 'isEmpty' ] ) { - $status[] = 'empty'; - } - if ( $block[ 'isSelected' ] ) { - $status[] = 'SELECTED'; - } - $status_str = empty( $status ) ? '' : ' (' . implode( ', ', $status ) . ')'; - $lines[] = "- #{$block[ 'order' ]}: {$block[ 'name' ]}{$status_str}"; - - // Show targeting options. - $targets = []; - if ( $block[ 'isSelected' ] ) { - $targets[] = 'wpBlock: "selected" (recommended - currently selected)'; - } - if ( ! empty( $block[ 'clientId' ] ) ) { - $targets[] = "wpBlock: \"clientId:{$block[ 'clientId' ]}\""; - } - if ( ! empty( $block[ 'domInfo' ][ 'editableSelector' ] ) ) { - $targets[] = "css: \"{$block[ 'domInfo' ][ 'editableSelector' ]}\" (in iframe)"; - } - if ( ! empty( $block[ 'domInfo' ][ 'dataType' ] ) ) { - $targets[] = "css: \"[data-type=\\\"{$block[ 'domInfo' ][ 'dataType' ]}\\\"]\" (in iframe)"; - } - - if ( ! empty( $targets ) ) { - $lines[] = ' Targeting options:'; - foreach ( $targets as $target ) { - $lines[] = " • {$target}"; - } - } - } - } else { - $lines[] = 'Blocks in editor: (empty editor or new post)'; - } - - // UI state. - if ( ! empty( $context[ 'visibleElements' ] ) ) { - $ve = $context[ 'visibleElements' ]; - $state = []; - $state[] = $ve[ 'inserterOpen' ] ? 'Inserter panel is OPEN' : 'Inserter panel is closed'; - $state[] = $ve[ 'sidebarOpen' ] ? 'Settings sidebar is OPEN' : 'Settings sidebar is closed'; - - if ( $ve[ 'hasSelectedBlock' ] && $ve[ 'selectedBlockType' ] ) { - $state[] = 'Selected block: ' . $ve[ 'selectedBlockType' ]; - } - $lines[] = ''; - $lines[] = 'UI State: ' . implode( '. ', $state ); - } - - // Verified selectors from page. - if ( ! empty( $context[ 'uiSamples' ] ) ) { - $lines[] = ''; - $lines[] = 'VERIFIED SELECTORS (confirmed working on this page):'; - $verified_samples = $context[ 'uiSamples' ]; - - if ( ! empty( $verified_samples[ 'inserterButton' ][ 'selector' ] ) && $verified_samples[ 'inserterButton' ][ 'visible' ] ) { - $lines[] = '- Inserter button: ' . $verified_samples[ 'inserterButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'publishButton' ][ 'selector' ] ) && $verified_samples[ 'publishButton' ][ 'visible' ] ) { - $lines[] = '- Publish/Save button: ' . $verified_samples[ 'publishButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'settingsButton' ][ 'selector' ] ) && $verified_samples[ 'settingsButton' ][ 'visible' ] ) { - $lines[] = '- Settings button: ' . $verified_samples[ 'settingsButton' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'searchInput' ][ 'selector' ] ) && $verified_samples[ 'searchInput' ][ 'visible' ] ) { - $lines[] = '- Search input: ' . $verified_samples[ 'searchInput' ][ 'selector' ]; - } - if ( ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] ) && $verified_samples[ 'emptyBlockPlaceholder' ][ 'visible' ] ) { - $in_iframe = ! empty( $verified_samples[ 'emptyBlockPlaceholder' ][ 'inIframe' ] ) ? ' (in editor iframe)' : ''; - $lines[] = '- Empty block placeholder: ' . $verified_samples[ 'emptyBlockPlaceholder' ][ 'selector' ] . $in_iframe; - } - } - - return implode( "\n", $lines ); - } - - /** - * Sanitize failure context from frontend. - * - * @since 0.3.6 - * @param array $context Raw failure context. - * @return array Sanitized context. - */ - private static function sanitize_failure_context( array $context ): array { - $sanitized = [ - 'stepIndex' => isset( $context[ 'stepIndex' ] ) ? absint( $context[ 'stepIndex' ] ) : 0, - 'stepId' => isset( $context[ 'stepId' ] ) ? sanitize_text_field( $context[ 'stepId' ] ) : '', - 'stepTitle' => isset( $context[ 'stepTitle' ] ) ? sanitize_text_field( $context[ 'stepTitle' ] ) : '', - 'error' => isset( $context[ 'error' ] ) ? sanitize_text_field( $context[ 'error' ] ) : '', - 'reason' => isset( $context[ 'reason' ] ) ? sanitize_text_field( $context[ 'reason' ] ) : '', - ]; - - // Sanitize locators array. - if ( isset( $context[ 'targetLocators' ] ) && is_array( $context[ 'targetLocators' ] ) ) { - $sanitized[ 'targetLocators' ] = []; - foreach ( array_slice( $context[ 'targetLocators' ], 0, 5 ) as $locator ) { - $sanitized[ 'targetLocators' ][] = [ - 'type' => isset( $locator[ 'type' ] ) ? sanitize_key( $locator[ 'type' ] ) : '', - 'value' => isset( $locator[ 'value' ] ) ? sanitize_text_field( $locator[ 'value' ] ) : '', - 'weight' => isset( $locator[ 'weight' ] ) ? absint( $locator[ 'weight' ] ) : 0, - ]; - } - } - - return $sanitized; - } - - /** - * Format failure context for AI prompt. - * - * This helps the AI learn from previous failures and generate better selectors. - * - * @since 0.3.6 - * @param array $context Sanitized failure context. - * @return string Formatted context for prompt. - */ - private static function format_failure_context_for_prompt( array $context ): string { - $lines = [ - '', - '⚠️ PREVIOUS ATTEMPT FAILED - PLEASE FIX:', - '', - 'The previous tour generation failed at step ' . ( $context[ 'stepIndex' ] + 1 ) . '.', - ]; - - if ( ! empty( $context[ 'stepTitle' ] ) ) { - $lines[] = 'Step title: "' . $context[ 'stepTitle' ] . '"'; - } - - if ( ! empty( $context[ 'error' ] ) ) { - $lines[] = 'Error: ' . $context[ 'error' ]; - } - - if ( ! empty( $context[ 'targetLocators' ] ) ) { - $lines[] = ''; - $lines[] = 'The following selectors DID NOT WORK:'; - foreach ( $context[ 'targetLocators' ] as $locator ) { - $lines[] = ' ❌ ' . $locator[ 'type' ] . ': "' . $locator[ 'value' ] . '"'; - } - } - - $lines[] = ''; - $lines[] = 'REQUIREMENTS FOR THIS RETRY:'; - $lines[] = '1. Use DIFFERENT selectors than the ones that failed'; - $lines[] = '2. Prefer more general, reliable selectors (aria-label, data-type attributes)'; - $lines[] = '3. Consider if the step order is correct - maybe a precondition is missing'; - $lines[] = '4. Double-check inEditorIframe constraint - is the element really in/out of the iframe?'; - $lines[] = ''; - - return implode( "\n", $lines ); - } - - /** - * Get available AI tasks for pupils. - * - * @since 0.3.0 - * @return \WP_REST_Response - */ - public static function get_tasks() { - $ai_manager = AiManager::get_instance(); - - // Check if AI is available. - $available = $ai_manager->is_available(); - - $response = [ - 'available' => $available, - 'tasks' => [], - ]; - - if ( $available ) { - $response[ 'tasks' ] = TaskPrompts::get_tasks(); - } - - return rest_ensure_response( $response ); - } - - /** - * Generate a cache key for tour requests. - * - * @since 0.3.0 - * @param string $task_id Task ID. - * @param string $query Freeform query. - * @param string $post_type Post type. - * @param array $editor_context Editor context. - * @return string Cache key. - */ - private static function generate_cache_key( string $task_id, string $query, string $post_type, array $editor_context ): string { - // Build cache key from relevant context. - // Include version to invalidate cache when prompts change. - $key_data = [ - 'version' => '2', // Increment when prompt instructions change. - 'task' => $task_id, - 'query' => $query, - 'post_type' => $post_type, - ]; - - // Include key editor state that affects tour generation. - if ( ! empty( $editor_context[ 'editorBlocks' ] ) ) { - // Just include block names and empty status, not client IDs. - $key_data[ 'blocks' ] = array_map( - function ( $b ) { - return $b[ 'name' ] . ( $b[ 'isEmpty' ] ? ':empty' : '' ); - }, - $editor_context[ 'editorBlocks' ] - ); - } - - if ( ! empty( $editor_context[ 'visibleElements' ] ) ) { - $key_data[ 'ui' ] = [ - 'inserterOpen' => $editor_context[ 'visibleElements' ][ 'inserterOpen' ] ?? false, - 'sidebarOpen' => $editor_context[ 'visibleElements' ][ 'sidebarOpen' ] ?? false, - ]; - } - - // Check if empty placeholder is visible (important for workflow choice). - if ( ! empty( $editor_context[ 'uiSamples' ][ 'emptyBlockPlaceholder' ][ 'visible' ] ) ) { - $key_data[ 'hasPlaceholder' ] = true; - } - - // Generate hash. - $key_string = wp_json_encode( $key_data ); - $hash = md5( $key_string ); - - return 'act_tour_' . $hash; - } - - /** - * Get cached tour if available. - * - * @since 0.3.0 - * @param string $cache_key Cache key. - * @return array|false Cached tour or false. - */ - private static function get_cached_tour( string $cache_key ) { - $cached = get_transient( $cache_key ); - - if ( false !== $cached && is_array( $cached ) ) { - return $cached; - } - - return false; - } - - /** - * Cache a generated tour. - * - * @since 0.3.0 - * @param string $cache_key Cache key. - * @param array $tour Tour data. - * @return void - */ - private static function cache_tour( string $cache_key, array $tour ): void { - // Cache for 24 hours. - $expiration = apply_filters( 'admin_coach_tours_cache_expiration', DAY_IN_SECONDS ); - set_transient( $cache_key, $tour, $expiration ); - } - - /** - * Generate an AI tour from task or freeform query. - * - * @since 0.3.0 - * @param \WP_REST_Request $request Request object. - * @return \WP_REST_Response|\WP_Error - */ - public static function generate_tour( \WP_REST_Request $request ) { - $ai_manager = AiManager::get_instance(); - - if ( ! $ai_manager->is_available() ) { - return new \WP_Error( - 'ai_not_available', - __( 'AI is not configured or enabled.', 'admin-coach-tours' ), - [ 'status' => 503 ] - ); - } - - $task_id = sanitize_key( $request->get_param( 'taskId' ) ?? '' ); - $query = sanitize_text_field( $request->get_param( 'query' ) ?? '' ); - $post_type = sanitize_key( $request->get_param( 'postType' ) ?? 'post' ); - $locale = sanitize_text_field( $request->get_param( 'locale' ) ?? '' ); - - // Fall back to WordPress user locale if not provided. - if ( empty( $locale ) ) { - $locale = get_user_locale(); - } - - // Get and sanitize editor context. - $raw_editor_context = $request->get_param( 'editorContext' ); - $editor_context = []; - if ( is_array( $raw_editor_context ) ) { - $editor_context = self::sanitize_editor_context( $raw_editor_context ); - } - - // Get and sanitize failure context (for retry with learning). - $raw_failure_context = $request->get_param( 'failureContext' ); - $failure_context = null; - if ( is_array( $raw_failure_context ) ) { - $failure_context = self::sanitize_failure_context( $raw_failure_context ); - } - - // Require either a task or a query. - if ( empty( $task_id ) && empty( $query ) ) { - return new \WP_Error( - 'missing_input', - __( 'Please provide a task or question.', 'admin-coach-tours' ), - [ 'status' => 400 ] - ); - } - - // Don't use cache when retrying with failure context. - if ( empty( $failure_context ) ) { - // Check cache first. - $cache_key = self::generate_cache_key( $task_id, $query, $post_type, $editor_context ); - $cached_tour = self::get_cached_tour( $cache_key ); - - if ( false !== $cached_tour ) { - // Return cached tour. - return rest_ensure_response( - [ - 'tour' => $cached_tour, - 'ephemeral' => true, - 'cached' => true, - ] - ); - } - } - - // Get relevant Gutenberg context based on task/query. - $task = ! empty( $task_id ) ? TaskPrompts::get_task( $task_id ) : null; - $search_query = $task ? ( $task[ 'description' ] ?? $task_id ) : $query; - - $context_data = GutenbergKnowledgeBase::get_relevant_context( $search_query, 5 ); - $gutenberg_context = GutenbergKnowledgeBase::format_context_for_prompt( $context_data ); - - // Format editor context for prompt. - $editor_context_prompt = ''; - if ( ! empty( $editor_context ) ) { - $editor_context_prompt = self::format_editor_context_for_prompt( $editor_context ); - } - - // Format failure context for prompt (contextual retry). - $failure_context_prompt = ''; - if ( ! empty( $failure_context ) ) { - $failure_context_prompt = self::format_failure_context_for_prompt( $failure_context ); - } - - // Build the system prompt with editor context and failure context. - $system_prompt = TaskPrompts::get_system_prompt( - $task_id, - $query, - $gutenberg_context, - $post_type, - $editor_context_prompt, - $failure_context_prompt, - $locale - ); - - // Generate the tour. - $result = $ai_manager->generate_tour( $system_prompt, $query ); - - if ( is_wp_error( $result ) ) { - $status = 500; - - if ( 'not_configured' === $result->get_error_code() ) { - $status = 503; - } elseif ( 'api_error' === $result->get_error_code() ) { - $error_data = $result->get_error_data(); - $status = $error_data[ 'status' ] ?? 500; - } elseif ( 'out_of_scope' === $result->get_error_code() ) { - $status = 422; - } - - return new \WP_Error( - $result->get_error_code(), - $result->get_error_message(), - [ 'status' => $status ] - ); - } - - // Cache the successful result (only if we have a cache key). - if ( isset( $cache_key ) ) { - self::cache_tour( $cache_key, $result ); - } - - // Return the ephemeral tour (not persisted). - return rest_ensure_response( - [ - 'tour' => $result, - 'ephemeral' => true, - 'cached' => false, - ] - ); - } } diff --git a/php/Rest/Routes.php b/php/Rest/Routes.php index 7ceb466..c63bb8a 100644 --- a/php/Rest/Routes.php +++ b/php/Rest/Routes.php @@ -262,6 +262,15 @@ public function register_routes(): void { 'default' => '', 'description' => __( 'User locale for AI response language.', 'admin-coach-tours' ), ], + 'editorContext' => [ + 'type' => 'object', + 'default' => [], + 'description' => __( 'Current editor state (blocks, UI elements). Sanitized by TourRequest.', 'admin-coach-tours' ), + ], + 'failureContext' => [ + 'type' => 'object', + 'description' => __( 'Context from a previous failed attempt, for contextual retry. Sanitized by TourRequest.', 'admin-coach-tours' ), + ], ], ], ] diff --git a/tests/php/AiControllerTest.php b/tests/php/AiControllerTest.php index 5413567..80cec85 100644 --- a/tests/php/AiControllerTest.php +++ b/tests/php/AiControllerTest.php @@ -2,6 +2,11 @@ /** * Test AI Controller. * + * The controller is now a thin HTTP adapter; sanitization of editor/failure + * context lives in TourRequest and cache/orchestration in TourGenerator, each + * tested in their own files. This covers the element-context sanitization the + * controller still owns, plus the endpoint surface. + * * @package AdminCoachTours */ @@ -28,8 +33,6 @@ protected function setUp(): void { Functions\stubTranslationFunctions(); Functions\stubEscapeFunctions(); - - // Mock apply_filters to return the first argument. Functions\when( 'apply_filters' )->returnArg(); } @@ -42,48 +45,17 @@ protected function tearDown(): void { } /** - * Test generate_draft returns error when AI not available. + * Test the REST endpoint surface exists. */ - public function test_generate_draft_returns_error_when_ai_unavailable(): void { - // Create mock request. - $request = $this->createMock( \WP_REST_Request::class); - $request->method( 'get_param' ) - ->willReturnCallback( - function ( $param ) { - if ( 'elementContext' === $param ) { - return [ - 'tagName' => 'button', - 'role' => 'button', - ]; - } - return null; - } - ); - - // Mock AiManager to return not available. - $ai_manager = $this->getMockBuilder( \stdClass::class) - ->addMethods( [ 'is_available' ] ) - ->getMock(); - $ai_manager->method( 'is_available' )->willReturn( false ); - - // This test demonstrates the expected behavior. - // In a real scenario, we'd mock the singleton properly. - $this->assertTrue( true ); - } - - /** - * Test get_status returns proper structure. - */ - public function test_get_status_returns_structure(): void { - Functions\when( 'rest_ensure_response' )->returnArg(); - - // This would need proper mocking of AiManager. - // For now, verify the method exists and is callable. + public function test_endpoint_methods_exist(): void { + $this->assertTrue( method_exists( AiController::class, 'generate_draft' ) ); $this->assertTrue( method_exists( AiController::class, 'get_status' ) ); + $this->assertTrue( method_exists( AiController::class, 'get_tasks' ) ); + $this->assertTrue( method_exists( AiController::class, 'generate_tour' ) ); } /** - * Test sanitization of element context. + * Test sanitization of element context strips unsafe values. */ public function test_element_context_sanitization(): void { Functions\when( 'sanitize_key' )->alias( @@ -102,7 +74,6 @@ function ( $str ) { } ); - // Use reflection to test private sanitize method. $reflection = new \ReflectionClass( AiController::class); $method = $reflection->getMethod( 'sanitize_element_context' ); $method->setAccessible( true ); @@ -121,52 +92,6 @@ function ( $str ) { $this->assertStringNotContainsString( '